--41.Attention.ipynb--
어텐션 함수
어텐션을 함수로 표현하면
어텐션 함수는 주어진 '쿼리(Query)'에 대해서 모든 '키(Key)'와의 유사도를 각각 구합니다.
그리고 구해낸 이 유사도를 키와 맵핑되어있는 각각의 '값(Value)'에 반영해줍니다.
그리고 유사도가 반영된 '값(Value)'을 모두 더해서 리턴합니다.
지금부터 배우게 되는 Seq2Seq + 어텐션 모델에서 Q, K, V에 해당되는 각각의 Query, Keys, Values는 각각 다음과 같습니다.
Q = Query : t 시점(타임스텝)의 디코더 셀에서의 은닉 상태
K = Keys : 모든 시점의 인코더 셀의 은닉 상태들
V = Values : 모든 시점의 인코더 셀의 은닉 상태들
[전체적인 개요(예시)]
디코더의 '세번째 LSTM 셀'에서 출력 단어를 '예측'할 때,
어텐션 메커니즘을 사용하는 모습을 보여줍니다.
디코더의 첫번째, 두번째 LSTM 셀은 이미 어텐션 메커니즘을 통해 je와 suis를 예측하는 과정을 거쳤다고 가정합니다.
디코더의 세번째 LSTM 셀은 출력 단어를 예측하기 위해서 인코더의 모든 입력 단어들의 정보를 다시 한번 참고하고자 합니다.
중간 과정에 대한 설명은 현재는 생략, 주목할 것은 인코더의 소프트맥스 함수 !!.
소프트맥스 함수를 통해 나온 결과값은 I, am, a, student 단어 각각이 '출력 단어를 예측'할 때 '얼마나 도움'이 되는지의 정도를 수치화한 값.
빨간 직사각형의 크기로 소프트맥스 함수의 결과값의 크기를 표현. 직사각형의 크기가 클 수록 도움이 되는 정도의 크기가 큽니다.
각 입력 단어가 디코더의 예측에 도움이 되는 정도가 수치화하여 측정되면 이를 '하나의 정보'로 담아서 디코더로 전송됩니다. 그림에서는 초록색 삼각형이 이에 해당됩니다.
결과적으로, 디코더는 출력 단어를 더 정확하게 예측할 확률이 높아진다.
인코더의 각 시점의 은닉 상태는 라고 합시다
디코더의 현재 시점 (timestep) t의 디코더 은닉 상태를 라고 합시다
이전 장에서 배웠던 내용!
그런데 어텐션 메커니즘에서는 출력 단어 예측에 또 다른 값을 필요하다.
어텐션 값 이 현재 시전 t 에서의 출력 예측에 구체적으로 어떻게 반영되는지는 나중에 설명하고.
지금부터 배우는 모든 과정은 를 구하기 위한 여정입니다. 그리고 그 여정의 첫 걸음은 바로 어텐션 스코어(Attention Score)를 구하는 일입니다.
어텐션 스코어란 현재 디코더의 타임스텝 t에서 단어를 예측하기 위해, 인코더의 모든 은닉 상태 각각이 디코더의 현 시점의 은닉 상태 와 얼마나 유사한지를 판단하는 스코어값입니다
여기서는 dot-product attention 에선 이렇게 계산한다
이제 지금까지 준비해온 정보들을 하나로 합치는 단계다.
어텐션의 최종 결과값을 얻기 위해서 각 인코더의 은닉 상태와 어텐션 가중치들을 곱하고, 최종적으로 모두 더합니다.
어텐션의 최종 결과. 즉, 어텐션 함수의 출력값인 어텐션 값(Attention Value)
에 대한 식을 보여줍니다.
이러한 어텐션 값 은 종종 인코더의 문맥을 포함하고 있다고하여, 컨텍스트 벡터(context vector)라고도 불립니다. 앞서 배운 가장 기본적인 seq2seq에서는 인코더의 마지막 은닉 상태를 컨텍스트 벡터라고 부르는 것과 대조됩니다.
다음은 논문에서의 내용입니다
를 바로 출력층으로 보내기 전에 신경망 연산을 한 번 더 추가하였습니다.
가중치 행렬과 곱한 후에 하이퍼볼릭탄젠트() 함수를 지나도록 하여 출력층 연산을 위한 새로운 벡터인
를 얻습니다.
어텐션 메커니즘을 사용하지 않는 seq2seq에서는 출력층의 입력이 t시점의 은닉 상태인
였던 반면, 어텐션 메커니즘에서는 출력층의 입력이 가 되는 셈입니다.
이를 식으로 표현하면 다음과 같습니다.
를 출력층의 입력으로 사용하여 예측 벡터를 얻습니다.
| 이 름 | 스 코 어 |
|---|---|
| dot | |
| scaled dot | |
| general | |
| concat | |
| location - base |
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
import tensorflow as tf
from tensorflow import keras
tf.keras.utils.set_random_seed(42)
tf.config.experimental.enable_op_determinism()
base_path = r'/content/drive/MyDrive/dataset'
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
def download_zip(url, output_path):
response = requests.get(url, headers=headers, stream=True)
if response.status_code == 200:
with open(output_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print(f"ZIP file downloaded to {output_path}")
else:
print(f"Failed to download. HTTP Response Code: {response.status_code}")
url = "http://www.manythings.org/anki/fra-eng.zip"
output_path = "fra-eng.zip"
import zipfile
path = os.getcwd()
zipfilename = os.path.join(path, output_path)
with zipfile.ZipFile(zipfilename, 'r') as zip_ref:
zip_ref.extractall(path)
lines = pd.read_csv('fra.txt', names=['src', 'tar', 'lic'], sep='\t')
del lines['lic'] # 불필요한 컬럼은 제거
lines = lines.loc[:, 'src':'tar']
lines = lines[0:60000]
lines.tar = lines.tar.apply(lambda x : '\t '+ x + ' \n') # 와 대신 \t를 시작 심볼, \n을 종료 심볼로 간주하여 추가
src_vocab = set()
for line in lines.src: # 1줄씩 읽음
for char in line: # 1개의 문자씩 읽음
src_vocab.add(char)
tar_vocab = set()
for line in lines.tar:
for char in line:
tar_vocab.add(char)
src_vocab_size = len(src_vocab) + 1
tar_vocab_size = len(tar_vocab) + 1
src_vocab = sorted(list(src_vocab))
tar_vocab = sorted(list(tar_vocab))
src_to_index = dict([(word, i+1) for i, word in enumerate(src_vocab)])
tar_to_index = dict([(word, i+1) for i, word in enumerate(tar_vocab)])
encoder_input = []
for line in lines.src: # 입력데이터(src) 문장라인
encoder_input.append([src_to_index[char] for char in line]) # 라인에서 글자를 인덱스로 변환하여 넣어줌
decoder_input = []
for line in lines.tar:
decoder_input.append([tar_to_index[char] for char in line]) # 목표 데이터에 해당하는 사전 사죵
decoder_target = []
for line in lines.tar:
timestep = 0
encoded_line = []
for char in line:
if timestep > 0:
encoded_line.append(tar_to_index[char])
timestep = timestep + 1
decoder_target.append(encoded_line)
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.utils import to_categorical
max_src_len = max([len(line) for line in lines.src])
max_tar_len = max([len(line) for line in lines.tar])
encoder_input = pad_sequences(encoder_input, maxlen=max_src_len, padding='post')
decoder_input = pad_sequences(decoder_input, maxlen=max_tar_len, padding='post')
decoder_target = pad_sequences(decoder_target, maxlen=max_tar_len, padding='post')
encoder_input = to_categorical(encoder_input)
decoder_input = to_categorical(decoder_input)
decoder_target = to_categorical(decoder_target)
from tensorflow.keras.layers import Input, LSTM, Embedding, Dense
from tensorflow.keras.models import Model
encoder_inputs = Input(shape=(None, src_vocab_size)) # 글자 사전의 크기만큼 입력된다
encoder_lstm = LSTM(256, return_state=True) # True로 해줘야 h, c 리턴한다.
encoder_outputs, state_h, state_c = encoder_lstm(encoder_inputs)
encoder_states = [state_h, state_c]
from keras.layers import Attention
decoderinputs = Input(shape=(None, tar_vocab_size)) # target 사전크기 만큼의 one-hot 벡터가 입력
decoder_lstm = LSTM(256, return_sequences=True, return_state=True)
decoder_outputs, , _ = decoder_lstm(decoder_inputs, initial_state=encoder_states)
S_ = tf.concat([state_h[:, tf.newaxis, :], decoder_outputs[:, :-1, :]], axis=1)
attention = Attention()
contextvector = attention([S, encoder_outputs]) # 컨텍스트 벡터 생성!
concat = tf.concat([decoder_outputs, context_vector], axis=-1)
decoder_softmax_layer = Dense(tar_vocab_size, activation='softmax')
decoder_outputs = decoder_softmax_layer(concat)
model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
model.load_weights(os.path.join(base_path, 'eng2fra_attention.keras'))
encoder_model = Model(encoder_inputs,
[encoder_outputs, encoder_states])
decoder_state_input_h = Input(shape=(256,))
decoder_state_input_c = Input(shape=(256,))
encoder_state_h = Input(shape=(256,))
encoder_outputs = Input(shape=(256,))
decoder_states_inputs = [decoder_state_input_h, decoder_state_input_c]
decoder_outputs, state_h, state_c = decoder_lstm(decoder_inputs, initial_state=decoder_states_inputs)
decoder_states = [state_h, state_c]
S = tf.concat([encoder_state_h[:, tf.newaxis, :], decoder_outputs[:, :-1, :]], axis=1 )
context_vector = attention([S, encoder_outputs])
decoder_concat = tf.concat([decoder_outputs, context_vector], axis=-1)
decoder_outputs = decoder_softmax_layer(decoder_concat)
decoder_model = Model(inputs=[decoder_inputs, encoder_state_h, encoder_outputs] + decoder_states_inputs,
outputs=[decoder_outputs] + decoder_states)
index_to_src = dict((i, char) for char, i in src_to_index.items())
index_to_tar = dict((i, char) for char, i in tar_to_index.items())
def decode_sequence(input_seq):
outputs_input, states_value = encoder_model.predict(input_seq)
target_seq = np.zeros((1, 1, tar_vocab_size))
target_seq[0, 0, tar_to_index['\t']] = 1.
stop_condition = False
decoded_sentence = ""
while not stop_condition:
# 이점 시점의 상태 states_value를 현 시점의 초기 상태로 사용
output_tokens, h, c = decoder_model.predict([target_seq, states_value[0], outputs_input] + states_value)
# 예측 결과를 문자로 변환
sampled_token_index = np.argmax(output_tokens[0, -1, :])
sampled_char = index_to_tar[sampled_token_index]
# 현재 시점의 예측 문자를 예측 문장에 추가
decoded_sentence += sampled_char
# <eos>에 도달하거나 최대 길이를 넘으면 중단.
if (sampled_char == '\n' or
len(decoded_sentence) > max_tar_len):
stop_condition = True
# 현재 시점의 예측 결과를 다음 시점의 입력으로 사용하기 위해 저장
target_seq = np.zeros((1, 1, tar_vocab_size))
target_seq[0, 0, sampled_token_index] = 1.
# 현재 시점의 상태를 다음 시점의 상태로 사용하기 위해 저장
states_value = [h, c]
return decoded_sentence
input_idx = [3, 5, 100, 300, 1001] # 입력문장의 인덱스들
for idx in input_idx:
print(lines.src[idx])
for seq_index in [3, 50, 100, 300, 1001]:
input_seq = encoder_input[seq_index:seq_index+1]
decoded_sentence = decode_sequence(input_seq)
print("-" * 35)
print("입력문장:", lines.src[seq_index])
print("정답문장:", lines.tar[seq_index]2:len(lines.tar[seq_index]) - 1])
print("번역문장:", decoded_sentence[1:len(decoded_sentence) - 1])