Transformer, Positional encoding, Self-Attention
들어가기에 앞서, 오늘 배운 transformer 개념이 세션 영상이나 노트만으로는 이해하기가 어려워서 다른 영상도 찾아보았었다.
Transformer
란 어제 배운 Attention
매커니즘을 극대화한 기계 번역을 위한 새로운 모델이다. 매우 높은 성능을 자랑하여 최근 자연어 처리 모델 SOTA(State-of-Art)의 기본 아이디어는 모두 이 트랜스포머를 기반으로 하고 있다고 한다. SOTA(State-of-Art)
란? 현재 최고 수준의 결과라고 한다. 즉, 위 말을 풀어보면 '최근 자연어 처리에서 최고 성능을 보이는 모델의 기본 아이디어는 모두 이 트랜스포머를 기반으로 하고 있다.'고 할 수 있겠다.)Transformer의 장점에 대해 얘기하기 위해 이전에 배운 RNN의 단점에 대해 먼저 얘기해보자.
왜 TIL 제목에 Attention is All You Need
를 넣어놨냐면 구글에서 트랜스포머 모델을 제안한 논문의 제목이기 때문이다.
그럼 해당 논문에서 제시한 구조 이미지를 한 번 보도록 하자.
인코더 블록
이고 그 오른쪽이 디코더 블록
이다. 각각 옆에 N x
라고 들어가 있는 건 저게 딱 하나만 있는게 아니라 여러 개 있을 수 있다는 뜻이다. (논문에서는 6개로 제안했는데, 강필성 교수님에 의하면 이게 magic number는 아니라고 한다. 꼭 6개여야만 하는 논리적인 이유는 없다는 뜻)인코더 블록
은 크게 Multi-Head (self) Attention
과 Feed Forward
두 개의 layer로 이루어져있다. 반면 디코더 블록
은 Masked Multi-head (self) Attention
과 Multi-head (Encoder-Decoder) Attention
, Feed Forward
3개의 layer로 이루어져 있다.자, 구조에 대해서는 다음 목차에서 좀 더 본격적으로 다뤄보도록 하겠다.
하나하나 전부 깊이있게 짚고 넘어가려고 하기보단 우선 오늘은 어떤 역할을 하는지를 기억하는 느낌으로 보자고~!
Positional encoding
이다.Positional encoding
은 단어의 상대적인 위치 정보를 담은 벡터를 만드는 과정을 말한다. 수학적으로는 아래와 같이 식이 이루어져 있다고는 하는데.. 수식을 이해하려고 하기보단 우선 왜 필요하고 뭔지 정도만 알고 넘어가도 된다고 한다. (sin, cos을 사용하는 방법이 있다 정도는 기억해두자)def get_angles(pos, i, d_model):
"""
sin, cos 안에 들어갈 수치를 구하는 함수입니다.
"""
angle_rates = 1 / np.power(10000, (2 * (i//2)) / np.float32(d_model))
return pos * angle_rates
def positional_encoding(position, d_model):
"""
위치 인코딩(Positional Encoding)을 구하는 함수입니다.
"""
angle_rads = get_angles(np.arange(position)[:, np.newaxis],
np.arange(d_model)[np.newaxis, :],
d_model)
# apply sin to even indices in the array; 2i
angle_rads[:, 0::2] = np.sin(angle_rads[:, 0::2])
# apply cos to odd indices in the array; 2i+1
angle_rads[:, 1::2] = np.cos(angle_rads[:, 1::2])
pos_encoding = angle_rads[np.newaxis, ...]
return tf.cast(pos_encoding, dtype=tf.float32)
이렇게 Positional Encoding과 Input Embeding이 더해져 인코더 블록으로 들어가게 된다. 참고로 여기서 +
는 행렬의 concat이 아니라 그냥 요소끼리 더하는 걸 말한다! 휴~ 하나 끝!
트랜스포머의 주요 매커니즘으로 오늘은 사실상 다른 건 다 몰라도 이 self attention 하나라도 제대로 알고 넘어가자고 했다. (미리 말하자면, 시간이 된다면 위 강필성 교수님의 영상을 보는게 아래 글보다 훨씬 이해할 때 도움이 될 거라는 점..)
다음과 같은 문장이 있다고 해보자.
The animal didn't cross the street because it was too tired
it
이 the animal
이라는 걸 우리 사람은 직관적으로 알 수 있지만, 컴퓨터는 그렇지 않다.Query, Key, Value
이다. 아래 그림을 보자. (출처 - 강필성 교수님 영상)it
)에 대한 쿼리와 각 단어들의 키를 통해 Score를 계산한다. 아래처럼! 이건 attention에서처럼 어떤 단어에 더 주목할지를 결정하는 과정으로 생각하면 된다. 즉, 쿼리행렬과 각 키 행렬 간의 내적 및 softmax 함수를 거쳐 확률값을 나타낸다는 뜻이다.정리해보니 생각보다 이해하기 어렵진 않지..?
자, self-attention 매커니즘 정리!
- 특정 단어의 쿼리(q) 벡터와 모든 단어의 키(k) 벡터를 내적한다.
(내적을 통해 나오는 값이 Attention 스코어(Score)가 된다)- 이 가중치를 q,k,v 벡터 차원 의 제곱근인 로 나누어준다.
(계산값을 안정적으로 만들어주기 위한 계산 보정)- Softmax를 취해준다.
이를 통해 쿼리에 해당하는 단어와 문장 내 다른 단어가 가지는 관계의 비율을 구할 수 있다.- 마지막으로 밸류(v) 각 단어의 벡터를 곱해준 후 모두 더한다.
def scaled_dot_product_attention(q, k, v, mask):
"""
Attention 가중치를 구하는 함수입니다.
q, k, v 의 leading dimension은 동일해야 합니다.
k, v의 penultimate dimension이 동일해야 합니다, i.e.: seq_len_k = seq_len_v.
Mask는 타입(padding or look ahead)에 따라 다른 차원을 가질 수 있습니다.
덧셈시에는 브로드캐스팅 될 수 있어야합니다.
Args:
q: query shape == (..., seq_len_q, depth)
k: key shape == (..., seq_len_k, depth)
v: value shape == (..., seq_len_v, depth_v)
mask: Float tensor with shape broadcastable
to (..., seq_len_q, seq_len_k). Defaults to None.
Returns:
output, attention_weights
"""
matmul_qk = tf.matmul(q, k, transpose_b=True) # (..., seq_len_q, seq_len_k)
# matmul_qk(쿼리와 키의 내적)을 dk의 제곱근으로 scaling 합니다.
dk = tf.cast(tf.shape(k)[-1], tf.float32)
scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)
# 마스킹을 진행합니다.
if mask is not None:
scaled_attention_logits += (mask * -1e9)
# 소프트맥스(softmax) 함수를 통해서 attention weight 를 구해봅시다.
attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1) # (..., seq_len_q, seq_len_k)
output = tf.matmul(attention_weights, v) # (..., seq_len_q, depth_v)
return output, attention_weights
이제는 이런 single attention이 모여있는 형태인 Multi-head Attention으로 넘어가보자구~~
Multi-Head Attention은 위에서 본 여러 개의 Attention 메커니즘을 동시에 병렬적으로 실행하는 것을 말한다. 각 Head마다 다른 Attention 결과를 내어주기 때문에 앙상블과 유사한 효과를 얻을 수 있으며, 병렬화 효과를 극대화 할 수 있다고 한다. 이 말의 의미는 아래 그림을 보는게 이해하기 더 나을 듯.
다른 그림으로 보면 이것과 같다. (논문에서는 8개의 attention을 사용했다고 함)
참고로 입력된 임베딩 벡터의 차원과 multi-head attention을 거쳐 나온 출력 벡터의 차원이 같다는 것 위 이미지에서도 보여주고 있는데 기억! (이 인코더 차원이 유지되어 다음 인코더로 넘어가야 계산이 가능하다고 함. 그냥 이렇구나~ 의미만 알아둬도 지금은 될 것 같다.)
정리하면 이 이미지로 표현할 수 있다!
Layer normalization
의 효과는 Batch normalization과 유사하며, 학습이 훨씬 빠르고 잘 되도록 한다. Skip connection(혹은 Residual connection)
은 역전파 과정에서 정보가 소실되지 않도록 한다.이것들에 대해서는 다음 스프린트 때 자세히 배울 거라고 하니 우선 패스!
Masked
가 붙어있다는 것에 주목해야 한다. 나머지 과정은 위에서 봤던 Multi-head attention과 똑같다. -1e9
))위처럼 마스킹하는 과정이 들어간다는 것을 제외하고는 multi-head attention과 동일하다는 점. Cheating을 막기 위해 그런거라는 점을 잘 기억하면 될 것 같다.
Multi-head (self) Attention
이었는데 이번엔 self
대신 (Encoder-Decoder)
라는 단어가 들어가 있다! 무슨 뜻일까? 인코더의 첫번째 서브층 : Query = Key = Value
디코더의 첫번째 서브층 : Query = Key = Value
디코더의 두번째 서브층 : Query : 디코더 행렬 / Key = Value : 인코더 행렬
오늘 노트도 어제와 마찬가지로 노트에 있는 구현 코드를 다 이해하는 것보다는 개념 자체를 잘 이해하는 것이 훨씬 중요하다는 것을 강조했다.
실습 과제는 주로 구현 함수가 주어지고 일부 내용을 채우는거였는데, 그걸 그대로 옮겨두는 건 의미가 없는 것같고, 나중에 시간을 갖고 코드 파헤치는 시간을 겨봐야겠다.