Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

어텐션

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 트랜스포머 연구에 대한 개선으로 제시되었습니다.

Attention=tanh(Q+K)\text{Attention} = tanh({Q + K})
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)이 모두 동일한 입력 시퀀스에서 가져옵니다. 이는 시퀀스 내에서 각 단어가 다른 단어들과 어떻게 관련되는지를 학습하기 위함입니다.

연산은 다음과 같은 과정으로 진행됩니다.

  1. 질의(query)와 색인(key) 벡터 내적(dot product)으로 두 값의 어텐션 점수가 계산됩니다. 어텐션 점수의 형식은 질의의 각 시퀀스에 대한 색인 값이기 때문에 질의의 시퀀스 길이를 TQT_Q, 색인 시퀀스의 길이를 TKT_K 라고 한다면, 어텐션 행렬은 TQ×TK T_Q \times T_K 형태가 됩니다.

$$

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
transformer0.10.20.30.4
attention0.20.10.40.3

표에서 열의 값들은 색인 단어들입니다. 질의의 각 단어에 대해 색인 단어들과의 관계가 계산되는 것이 어텐션 점수입니다. 질의어가 두 개이고 색인이 네 개인 예시의 경우 2×4 2 \times 4 형식으로 어텐션 점수가 계산됩니다.

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.00.60.10.10.40.2
타고0.61.00.30.10.10.1
배(2)0.10.31.00.40.30.2
먹으니0.10.10.41.00.30.5
배(3)0.40.10.30.31.00.6
아프다0.20.10.20.50.61.0

각 단어가 문장 내 다른 단어들과의 관계를 나타내는 값입니다. 예를 들어, "배(1)"는 “타고”, "배(2)"와 높은 관계를 가지며, "배(3)"와는 약한 관계를 가집니다. "먹으니"는 "배(2)"와 "배(3)"와 강한 관계를 가집니다. "아프다"는 "배(3)"와 강한 관계를 가집니다.

어텐션 점수 (Softmax)

질의/색인배(1)타고배(2)먹으니배(3)아프다
배(1)0.4080.1490.2430.0610.0900.049
타고0.2050.5130.1540.0510.0510.051
배(2)0.2580.1290.4300.1720.2150.086
먹으니0.0480.0480.1920.4780.1440.287
배(3)0.0910.0450.2270.1360.4550.227
아프다0.0480.0480.0860.2160.2590.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:] == (시퀀스길이, 벡터차원)