Transformer를 이해하기 위해서는 우선 Self attention에 대한 이해가 필요하다. 셀프 어텐션은 문장에서 각 단어끼리 얼마나 관계가 있는지를 계산해서 반영하는 방법이다. 즉, 셀프 어텐션으로 문장 안에서 단어들 간의 관계를 파악할 수 있는 것이다.
예를 들어, '나는 자연어 처리를 즐겨한다.'라는 문장에서 '자연어'라는 단어에 대해 관계를 측정한다. 이때, 밑의 표처럼 나왔다고 하면 각 단어에 대한 어텐션 스코어가 나온다. 어텐션 스코어는 각 단어 간의 관계를 측정한 값이고 어텐션 스코어 값을 하나의 테이블로 만든 것을 어텐션 맵이라고 부른다.
나는 | 자연어 | 자연어 | 자연어 | 처리를 | 자연어 | 즐겨한다 | 자연어 |
Dot product | Dot product |
Dot product |
Dot product |
||||
0.3 | 0.3 | 0.25 | 0.15 |
실제 문장이 모델에 적용될 때는 Embedding 된 벡터 형태로 입력이 될 것이다. 트랜스포머 모델에서는 각 단어 벡터간의 관계를 나타내는 어텐션 스코어를 구할 때, 단어 벡터끼리 내적 연산을 함으로써 구한다. (위의 예시는 트랜스포머를 가정해서 내적 연산을 해줬으며, 맨해튼 거리나 Dense 층을 거쳐 나온 값을 활용하는 방법도 있다.)
그 후, Softmax를 통과시켜 다른 단어와의 연관도 값을 확률로 나타낸다. 마지막으로 어텐션 스코어와 각 단어 벡터를 곱한 후 모두 더해주면(가중치를 각 벡터에 곱해주고 더해주는 가중합 과정) 단어에 대한 콘텍스트 벡터가 나오게 된다.
위의 예시를 다시 가져와 생각해보면, 이 경우에는 '자연어'에 대해 나온 어텐션 스코어를 다시 각 단어 벡터와 곱해주고, 모두 더해주면 '자연어'에 대한 컨텍스트 벡터가 나오는 것이다. 이 과정을 다른 단어들에 대해서도 수행한다.
이런 Self Attention은 같은 문장 내 모든 단어 쌍 사이의 의미적, 문법적 관계를 포착해내는 중요한 역할을 하게된다!
이제, Scaled Dot-Product Attention을 이해해보자. 스케일 내적 어텐션의 구조는 아래의 그림과 같다. 전체적인 개념은 앞에서 본 셀프 어텐션과 거의 동일하다.
위의 그림처럼 query, key, value가 입력값으로 들어간다. query는 위의 예시에서 '자연어' 같은 다른 단어와의 관계를 알아보려는 특정 단어가 되며 다른 단어들이 key, value가 되는 것이다. 이 경우, key=value라고 볼 수 있다.
1. query, key, value는 모두 문장인데 각 단어가 벡터로 되어있고, 이것들이 모여서 결국 행렬로 돼 있는 구조다. 그래서, Query와 Key를 행렬 계산을 먼저 해주는데, Key 행렬은 Query행렬의 Transpose 된 형태로 만들어 행렬곱을 해주면 결국 내적 연산과 같은 과정으로 각 단어에 대한 어텐션 스코어를 구함을 알 수 있다. 내적 연산을 한 값은 -1~1의 값을 가지게 될 텐데, 어떤 쿼리와 키가 중요한 역할을 하고 있다면 어텐션 블록은 이들 사이의 내적 값을 키우는 방식으로 학습을 할 것이다. 내적 값이 커지면 해당 쿼리와 키가 벡터 공간상 가까이 있을 가능성이 높아진다.
2. 행렬 연산(내적 연산)을 한 후, 벡터의 차원이 커지면 학습이 잘 안될 수 있기에 스케일링을 해준다.
3. 다음으로는 mask를 해주는데, 그 이유는 전체 문장을 한 번에 행렬 형태로 입력해주는 구조이므로 자신보다 뒤에 있는 단어를 참고해서 단어를 예측하는 상황이 생길 수 있기 때문이다. 따라서, 자신보다 뒤에 있는 단어를 참고하지 않게 해줘야 한다. 그래서, Query와 Transpose 된 Key의 행렬곱으로 생성된 어텐션 맵이 4*4로 생성되었을 때 Query 단어 뒤에 나오는 Key 단어들에 대해서 마스킹하면 아래의 표와 같이 된다.
Query/Key | 나는 | 자연어 | 처리를 | 즐겨한다 |
나는 | 23 | X | X | X |
자연어 | 16 | 27 | X | X |
처리를 | 14 | 20 | 23 | X |
즐겨한다 | 12 | 19 | 20 | 23 |
4. mask까지 해주면 softmax로 확률 값으로 변환시켜주고 Value와 행렬곱을 해줘서 각 콘텍스트 벡터를 구해준다.
(앞의 self attention과 동일한 과정)
※ 위의 내용을 코드로 구현해보면 다음과 같다.
def scaled_dot_product_attention(query, key, value, masked=False):
key_dim_size = float(key.get_shape().as_list()[-1])
key = tf.transpose(key, perm=[0, 2, 1])
# normalize를 위해 차원수로 나눠준다.
outputs = tf.matmul(query, key)/tf.sqrt(key_dim_size)
# attention score가 있는 attention map을 구한다.
if masked:
# 마스킹할 영역 준비
diag_vals = tf.ones_like(outputs[0, :, :])
# 행렬을 하삼각행렬로 만들기. 상삼각영역은 패딩 처리한다.
tril = tf.linalg.LinearOperatorLowerTriangular(diag_vals).to_dense()
# 배치크기만큼 확장.
masks = tf.tile(tf.expand_dims(tril, 0), [tf.shape(outputs)[0], 1, 1])
# padding 영역에 할당할 아주 작은 음수 값을 행렬로 만들어둠.
paddings = tf.ones_like(masks)*(-2**30)
# mask가 0이면 paddings의 값을 사용하고 아닐경우는 outputs를 사용함.
outputs = tf.where(tf.equal(masks, 0), paddings, outputs)
# softmax로 마스킹영역이 걸러짐.
attention_map = tf.nn.softmax(outputs)
return tf.matmul(attention_map, value) # 컨텍스트 벡터를 리턴해준다.
* 위에서 마스킹을 하는 방법은 mask 영역에 매우 작은 숫자를 할당해줘서 softmax를 거치며 0에 가까운 값을 갖도록 해주는 것이다.
총과정에 대해 수식으로 정리해보면 $ Attention(Q,K,V) = C^{T}V =Softmax(\frac{QK^{T}} {\sqrt{d_{k}}})V$ 으로 표현할 수 있다.
여기까지 스케일 내적 어텐션을 완성했다.
'머신러닝&딥러닝 > 자연어처리' 카테고리의 다른 글
Transformer(4) - 모델의 학습과정 정리 (0) | 2020.02.20 |
---|---|
Transformer(3) - Positional Encoding, Position-Wise Feedforward, Residual connection (0) | 2019.12.26 |
Transformer(2) - Multi head attention (2) | 2019.12.25 |
Embedding 이란 무엇인가 이해하기 (1) | 2019.12.20 |