어텐션
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torch
import os
os.environ['KERAS_BACKEND'] = 'torch'
import keras
print('PyTorch', torch.__version__)
print('Keras', keras.__version__)2015 Attention¶
2015년 어텐션(Bahdanau et al, 2015)이 처음 제시되었을 때, 어텐션은 2014년 RNN 트랜스포머 연구에 대한 개선으로 제시되었습니다.
import keras
from keras import layers
# 모의 데이터
query = np.array([[1, 2, 3]])
value = np.array([[4, 5, 6]])
# embedding
어휘수, 벡터차원 = 10, 2
Q = layers.Embedding(어휘수, 벡터차원, name='query_embedding')(query)
V = layers.Embedding(어휘수, 벡터차원, name='value_embedding')(value)
# 임베딩 출력
# print(f'Q:{Q.shape}, V: {V.shape}')
assert query.shape[1] == Q.shape[1] and Q.shape[-1] == 벡터차원
assert value.shape[1] == V.shape[1] and V.shape[-1] == 벡터차원
# 더하기 Attention 계산
# Q: (batch, Tq, 1, dim)
# V: (batch, 1, Tv, dim)
Q_reshape = keras.ops.expand_dims(Q, axis=2)
V_reshape = keras.ops.expand_dims(V, axis=1)
어텐션점수 = keras.ops.sum(keras.ops.tanh(Q_reshape + V_reshape), axis=-1)
print('Attention score:', 어텐션점수.shape)
# # softmax 정규화
어텐션점수 = keras.ops.softmax(어텐션점수, axis=-1)
어텐션적용 = keras.ops.matmul(어텐션점수, V)
# keras 구현과 비교
x1 = keras.ops.convert_to_numpy(어텐션적용)
print('어텐션 출력:', x1.shape)
# 원래는 학습 가능한 scale을 사용하지만, 내부에서 무작위 값으로 초기화하기 때문에 값의 비교를 위해 scale을 사용하지 않음
x2 = layers.AdditiveAttention(use_scale=False)([Q, V])
x2 = keras.ops.convert_to_numpy(x2)
print('Keras AdditiveAttention:', x2.shape)
# 두 결과 비교
X_all = np.concatenate([x1.squeeze(), x2.squeeze()], axis=1)
assert np.allclose(x1, x2)Dot-Product Attention¶
내적 어텐션 (Luong, 2015)은 먼저 제시된 어텐션의 연산을 보다 효율적으로 정리한 것입니다.
입력 시퀀스는 임베딩 과정을 통해 정해진 벡터 차원(여기서는 128차원)으로 변환됩니다. 이 변환된 벡터는 입력 시퀀스의 각 단어를 고정된 차원의 벡터로 표현하는 것입니다.
입력 시퀀스는 알고자 하는 정보이기 때문에 어텐션 메커니즘에서 질의(query)로 사용됩니다. 질의는 관심을 두고자 하는 대상의 표현을 나타냅니다.
셀프 어텐션(Self-Attention)에서는 질의(query), 색인(key), 그리고 값(value)이 모두 동일한 입력 시퀀스에서 가져옵니다. 이는 시퀀스 내에서 각 단어가 다른 단어들과 어떻게 관련되는지를 학습하기 위함입니다.
연산은 다음과 같은 과정으로 진행됩니다.
질의(query)와 색인(key) 벡터 내적(dot product)으로 두 값의 어텐션 점수가 계산됩니다. 어텐션 점수의 형식은 질의의 각 시퀀스에 대한 색인 값이기 때문에 질의의 시퀀스 길이를 , 색인 시퀀스의 길이를 라고 한다면, 어텐션 행렬은 형태가 됩니다.
$$
QK^{T} \in \mathbb{R}^{T_Q \times T_K}, \quad
Q \in \mathbb{R}^{T_Q \times d}, K \in \mathbb{R}^{T_K \times d}
$$
예를 들어, 질의와 색인이 서로 다른 경우를 가정해보겠습니다. 질의가 "transformer attention"이라고 했을 때, 색인이 "전력, 영화, 음악, AI"라고 하겠습니다.
| 질의(Query) | 전력 | 영화 | 음악 | AI |
|---|---|---|---|---|
| transformer | 0.1 | 0.2 | 0.3 | 0.4 |
| attention | 0.2 | 0.1 | 0.4 | 0.3 |
표에서 열의 값들은 색인 단어들입니다. 질의의 각 단어에 대해 색인 단어들과의 관계가 계산되는 것이 어텐션 점수입니다. 질의어가 두 개이고 색인이 네 개인 예시의 경우 형식으로 어텐션 점수가 계산됩니다.
import keras
from keras import layers
query = keras.Input(shape=(None, ), dtype='int32', name='query')
value = keras.Input(shape=(None,), dtype='int32', name='value')
# embedding
어휘수, 벡터차원 = 10000, 128
Q = layers.Embedding(어휘수, 벡터차원, name='query_embedding')(query)
V = layers.Embedding(어휘수, 벡터차원, name='value_embedding')(value)
model = keras.Model(inputs=[query, value], outputs=[Q, V], name='embedding_model')
# 모의 데이터
query = np.array([[1, 2, 3]])
value = np.array([[4, 5, 6]])
# 임베딩 출력
Q, V = model.predict([query, value], verbose=False)
print(f'Q:{Q.shape}, V: {V.shape}')
print(f'V.T:{keras.ops.transpose(V, axes=[0, 2, 1]).shape}')
# Attention 계산
어텐션점수 = keras.ops.dot(Q, keras.ops.transpose(V, axes=[0, 2, 1]))
# softmax 정규화
어텐션점수 = keras.ops.softmax(어텐션점수, axis=-1)
print('Attention score:', 어텐션점수.shape)
어텐션적용 = keras.ops.dot(어텐션점수, V)
print('어텐션 출력:', 어텐션적용.shape)
# 출력을 keras.Attention 구현과 비교 (일치해야 함)
x1 = keras.ops.convert_to_numpy(어텐션적용)
print('Attention:', x1.shape)
x2 = layers.Attention()([Q, V])
x2 = keras.ops.convert_to_numpy(x2)
assert np.allclose(x1, x2), 'keras.Attention 계층 출력과 구현된 출력이 일치하지 않음'Self- Attention¶
셀프 어텐션의 경우는, 질의와 색인이 같습니다. 즉, 문장 내 다른 단어들과의 관계가 계산됩니다. 예를 들어, "배 타고 배 먹으니 배 아프다"라는 문장을 가정해 보겠습니다.
이 문장의 각 단어는 “배”, “타고”, “배”, “먹으니”, “배”, "아프다"입니다. 셀프 어텐션을 사용하여 각 단어가 다른 단어들과의 관계를 나타내는 어텐션 점수를 계산하면 다음과 같은 표를 얻을 수 있습니다.
어텐션 점수¶
| 질의/색인 | 배(1) | 타고 | 배(2) | 먹으니 | 배(3) | 아프다 |
|---|---|---|---|---|---|---|
| 배(1) | 1.0 | 0.6 | 0.1 | 0.1 | 0.4 | 0.2 |
| 타고 | 0.6 | 1.0 | 0.3 | 0.1 | 0.1 | 0.1 |
| 배(2) | 0.1 | 0.3 | 1.0 | 0.4 | 0.3 | 0.2 |
| 먹으니 | 0.1 | 0.1 | 0.4 | 1.0 | 0.3 | 0.5 |
| 배(3) | 0.4 | 0.1 | 0.3 | 0.3 | 1.0 | 0.6 |
| 아프다 | 0.2 | 0.1 | 0.2 | 0.5 | 0.6 | 1.0 |
각 단어가 문장 내 다른 단어들과의 관계를 나타내는 값입니다. 예를 들어, "배(1)"는 “타고”, "배(2)"와 높은 관계를 가지며, "배(3)"와는 약한 관계를 가집니다. "먹으니"는 "배(2)"와 "배(3)"와 강한 관계를 가집니다. "아프다"는 "배(3)"와 강한 관계를 가집니다.
어텐션 점수 (Softmax)¶
| 질의/색인 | 배(1) | 타고 | 배(2) | 먹으니 | 배(3) | 아프다 |
|---|---|---|---|---|---|---|
| 배(1) | 0.408 | 0.149 | 0.243 | 0.061 | 0.090 | 0.049 |
| 타고 | 0.205 | 0.513 | 0.154 | 0.051 | 0.051 | 0.051 |
| 배(2) | 0.258 | 0.129 | 0.430 | 0.172 | 0.215 | 0.086 |
| 먹으니 | 0.048 | 0.048 | 0.192 | 0.478 | 0.144 | 0.287 |
| 배(3) | 0.091 | 0.045 | 0.227 | 0.136 | 0.455 | 0.227 |
| 아프다 | 0.048 | 0.048 | 0.086 | 0.216 | 0.259 | 0.432 |
미정규화 어텐션 점수를 소프트맥스 함수를 사용하여 정규화한 값입니다. 이 값들은 각 행에서 합이 1이 되며, 각 단어에 대해 다른 단어와의 관계를 확률로 나타냅니다.
이와 같은 방식으로 각 단어의 어텐션 점수를 정규화하여 최종 어텐션 점수를 얻습니다.
멀티 헤드 어텐션¶
import keras
from keras import layers
시퀀스길이, 벡터차원 = 20, 128
query = keras.Input(shape=(시퀀스길이, 벡터차원))
key = value = query
attention = layers.MultiHeadAttention(num_heads=8, key_dim=벡터차원)(query, value)
assert attention.shape[1:] == (시퀀스길이, 벡터차원)