Transformer, BERT, GPT
Attention is All You Need - 이 문구 때문에 뉴진스 노래가 계속 머리에 맴돌았다
링크텍스트
Transformer는 RNN에 비해 학습 속도도 빠르고, 성능도 더 좋다(RNN사용안함)
트랜스포머는 병렬로 데이터를 처리한다
인코더디코더의 컨셉만 가져가고 RNN을 제거하여 학습시간을 단축했다
Transformer란 attention 매커니즘을 극대화한 기계 번역을 위한 새로운 모델순차적으로 들어온다는 점인데트랜스포머의 구조를 단순하게 시각화한 그림은 아래 참고
트랜스포머는 인코더 블록과 디코더 블록이 6개씩 모여있는 구조이다.
왼쪽은 인코더 블럭, 오른쪽은 디코더 블럭

내부 구조를 살펴보면

그럼 다시 이걸 하나하나 뜯어보자
??포지셔널 인코딩 왜 필요한가??
트랜스포머에서는 병렬화를 위해 모든 단어 벡터를 동시에 입력 받는데
컴퓨터는 바-보라서 어떤 단어가 어디에 위치한지 모름!
그래서 컴퓨터에게 단어의 위치정보를 알려주기 위해 위치정보를 담은 벡터를 제공해줘야 함
이런 단어의 상대적인 위치 정보(위치는 아무래도 상대적이지..)를 담은 백터를 만드는 과정을
Positional Encoding (위치 인코딩) 이라고 한다.
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)
셀프어텐션은 세가지 가중치 벡터를 대상으로 어텐션 적용

The animal didn't cross the street because it was too tired
위 문장을 제대로 번역하라면 'it'과 같은 지시 대명사가 어떤 대상을 가르키는 지 파악해야 하는데
트랜스포머에서는 번역하려는 문장 내부 요소의 관계를 파악하기 위해
문장 자신에 대한 어텐션 매커니즘 적용하고, 이를 Self-Attention 이라고 한다!

어텐션을 이해하기 위해서 Attention을 다룰 때 등장했던 검색 시스템 아이디어를 보자!

검색시스템은 3단계를 거쳐 작동하는데
Self-Attention은 위의 세 가지 백터를 대상으로 어텐션을 적용하며
그 방식은 기존 Attention 매커니즘과 거의 동일
먼저, 특정 단어의 쿼리(q) 벡터와 모든 단어의 키(k) 벡터를 내적, 내적을 통해 나오는 값이 Attention 스코어(Score)
트랜스포머에서는 이 가중치를 q,k,v 벡터 차원 의 제곱근인 로 나누어줍니다.
계산값을 안정적으로 만들어주기 위한 계산 보정. (바로 소프트 맥스를 먹이는 게 아님)다음으로 Softmax를 취해줍니다. 이를 통해 쿼리에 해당하는 단어와 문장 내 다른 단어가 가지는 관계의 비율을 구할 수 있습니다.
마지막으로 밸류(v) 각 단어의 벡터를 곱해준 후 모두 더하면 Self-Attention 과정이 마무리됩니다.
정리하자면
인풋- 임베딩 - 쿼리/키 백터내적 - 스코어 출력 - 가중치를 q,k,v의 벡터차원의 제곱근으로 나누기 - 소프트 맥스 - 밸류x소프트맥스 - sum !
너무 어려워 너무어려워!!!
아래는 일단 코드. 마찬가지로 이해는 반밖에 몬했다. (ㅜ_ㅜ)
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
그리고 위와 같은 형태가 여러개 모여있는... Multi-head Attention으로 넘어가기..
Multi-Head Attention 을 적용하면 여러 개의 Attention 메커니즘을 동시에 병렬적으로 실행
각 헤드마다 다른 Attention 결과를 내어주기 때문에 앙상블과 유사한 효과를 얻을 수 있고,
병렬화 효과를 극대화 할 수 있음
예시를 그림으로 보자면

이렇게 각각의 z0 ~ z7까지 (예시가 8인 경우!) 행렬을 출력하고, 이후에 이어 붙이기.
그리고 또 다른 파라미터 행렬인 w0와의 내적을 통해 최종 결과 출력

최종적으로 생성된 행렬z는 입력 행렬x와 동일한 형태를 가지고 있음

트랜스포머의 sub-layer에서 출력된 벡터는 Layer normalization과 Skip connection을 거치게 됨
Layer normalization의 효과는 Batch normalization과 유사 학습이 훨씬 빠르고 잘 되도록 함
Skip connection(혹은 Residual connection)은 역전파 과정에서 정보가 소실되지 않도록 함

ADD&Norm 를 거친 이후 FFNN(Feed forward neural network) 로 들어감
은닉층의 차원이 늘어났다가 다시 원래 차원으로 줄어드는 단순한 2층 신경망
활성화 함수(Activation function)으로 ReLU를 사용함
def point_wise_feed_forward_network(d_model, dff):
"""
FFNN을 구현한 코드입니다.
Args:
d_model : 모델의 차원입니다.
dff : 은닉층의 차원 수입니다. 논문에서는 2048을 사용하였습니다.
"""
return tf.keras.Sequential([
tf.keras.layers.Dense(dff, activation='relu'), # (batch_size, seq_len, dff)
tf.keras.layers.Dense(d_model) # (batch_size, seq_len, d_model)
])
Masked Self-Attention은 디코더 블록에서 사용하기 위해서 마스킹 과정이 포함된 Self-Attention
언어 모델에서 디코더가 단어를 생성할 때에는 Auto-Regressive(왼쪽 단어를 보고 오른쪽 단어를 반복하여 예측)하게 진행됨.
RNN을 사용한 번역 모델에서도 생성하려는 단어 이후의 왼쪽에 있는 단어 정보만을 고려하여 단어를 생성함.
이러한 방법은 트랜스포머에서도 유지해주게 되는데
단어가 순차적으로 입력되는 RNN과 달리 트랜스포머에서는 타깃 문장 역시 한 번에 입력되기 때문에
해당 위치 타깃 단어 뒤에 위치한 단어는 Self-Attention에 영향을 주지 않도록 마스킹(Masking)을 해주게 됨!


디코더에서 Masked Self-Attention 층을 지난 벡터는 Encoder-Decoder Attention 층으로 들어가는데 좋은 번역을 위해서는 번역할 문장과 번역된 문장 간의 관계가 중요하다.(당연)
번역할 문장과 번역되는 문장의 정보 관계를 엮어주는 부분이 바로 이 부분.
이 층에서는 디코더 블록의 Masked Self-Attention으로부터 출력된 벡터를 쿼리(Q) 벡터로 사용합니다.
키(K)와 밸류(V) 벡터는 최상위(=6번째) 인코더 블록에서 사용했던 값을 그대로 가져와서 사용하는데 Encoder-Decoder Attention 층의 계산 과정은 Self-Attention 했던 것과 동일하다.

디코더의 최상층을 통과한 벡터들은 Linear 층을 지난 후 Softmax를 통해 예측할 단어의 확률을 구하게 된다.
https://gall.dcinside.com/mgallery/board/view/?id=thesingularity&no=57914
-- 동기분이 공유해준 글인데 훑어보니깐 대강~ 이해에 도움이 된다.
지난 주 학습이 촘촘하지 않다보니깐 파란 하늘에 구름이 떠다니는 것처럼 부분,,부분 이해가 되는데 결국 뭐든 반복학습이 답이다! 개념은 와닿지만 코드가 안 와닿는 부분은 코드를 계속 반복하면 될일 같고, 딥러닝 자체 개념이 부족한 부분은 주말에 복습으로 극복하기로!
아쟈쟈 화이팅!