오늘은 좀 진지하게 Attention Is All You Need 논문과 함께 트랜스포머를 세부적으로 다뤄보고자 한다.
잘 이해 안되는 내용 정리하려는 거니 자잘한 내용은 과감하게 생략하겠다
트랜스포머 전체 구조
인코더(Encoder): 입력 문장을 받아 의미와 문맥을 압축한 벡터 표현을 만듬
디코더(Decoder): 인코더의 벡터 표현과 이전에 생성된 출력 단어들을 참조해 다음 단어 예측하고 문장 생성

Inputs 부터 따라가자
각 단어 토큰들이 Embedding 거치고 Positional 거쳐서 벡터 시퀀스로 변환된다.
처리된 벡터 시퀀스는 인코더 스택 가장 아래층으로 들어간다.
이미지 보면 왼쪽에 Nx 되어있는데 레이어 개수다.
논문에서는 N=6 개의 인코더 레이어를 차례로 통과한다고 설명했다.
각 레이어에서는 셀프 어텐션과 피드 포어드 네트워크를 거치며 주변 단어들 정보 통합해 자신의 의미를 계속 업데이트 한다.
중간중간 과정마다 Add & Norm이 있는데
Add => Residual(잔차) 연결 = 원본 더해두기
원래 입력 잊지 않게 새로 새로 계산하면서도 원래 입력 그대로 더하는 거다.
Norm => LayerNorm(정규화) = 모양 가지런히
더하면 값이 확 튀어버릴 수 있으니 스케일을 정돈한다.
보이지는 않지만 각 과정 진행하기 전에 dropout 한번씩 적용해준다.
마지막 레이어통과하면 최종 출력물은 입력 문장의 전체 문맥을 함축한 **Key와 Value 벡터 시퀀스 (K, V)**가 된다.
인코더 최종 출력은 N=6 개의 모든 디코더 레이어에 전달된다.
디코더는 문장 시작을 알리는 <SOS> 토큰을 첫 입력으로 받아 첫 번째 출력 단어를 예측하기 시작한다.
입력으로 받는 Outputs(shifted right)는 학습 시 정답 문장(target)을 한 칸 오른쪽으로 밀어서 만든 입력이다.
한칸 밀었으니 처음 부분 채워야하는데 그게 <SOS>
한칸 밀어낸 이유는 다음 단어를 예측해야하니깐
디코더는 일단 현재까지 생성된 출력 시퀀스(처음에는 <SOS>)에 대해 **'마스크드 셀프 어텐션'**을 수행해 이미 생성된 단어들 간의 관계를 파악한다.
마스크드 셀프 어텐션에서는 자기 자신에게 어텐션 하되, 미래 데이터는 못 보게 해서 현재까지의 정보만으로 다음 토큰을 예측하도록 일관성을 유지한다.
그 다음에야 인코더 결과를 연결한 **'인코더-디코더 어텐션'**에서 인코더가 전달한 입력 문장의 정보(K, V)를 참조한다.
이제 디코더는 현재 예측하려는 단어와 가장 관련이 깊은 입력 단어에 '집중' 한다.
이제 피드 포워드 네트워크 거쳐서 최종적으로 다음 단어에 대한 확률 계산하고 예측한다.
입력처리
전체적으로 둘러 봤으니 이제 다시 세부적으로 초기 입력 부분을 살펴보자.
임베딩이야 종종 이야기했으니 생략하고 **포지셔널 인코딩 (Positional Encoding)**에 대해 알아보자.
트랜스포머는 각 토큰의 '절대적인 위치'와 '상대적인 위치'에 대한 정보를 벡터에 명시적으로 주입한다.
논문에서는 주기가 다른 여러개의 sin, cos 함수로 위치 벡터를 생성한다.
특정 위치 pos에 대한 d_model차원의 위치 벡터는 다음과 같이 계산된다.
PE(pos, 2i) = sin(pos / 10000^(2i / d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i / d_model))
pos : 문장 내에서 토큰의 위치(0, 1, 2, ...)
i : 임베딩 벡터 내의 차원 인덱스(0, 1, ..., d_model/2 - 1)
셀프 어텐션
어텐션에는 세가지 역할을 수행하는 벡터가 있다.
쿼리(Query), 키(Key), 밸류(Value) 해당 벡터들은 원래의 입력 벡터(임베딩+포지셔널 인코딩)로 부터 생성된다.
쿼리(Q): 내가 지금 찾고 싶은 정보, 즉 현재 처리하고 있는 단어의 표현
키(K): 단어들이 자신을 표현하는 '간판'같은 벡터
밸류(V): 단어들이 가진 실제 의미 표현 벡터
계산 과정
점수(Score) 계산: 현재 단어의 쿼리(Q) 벡터를 문장 내 모든 단어들의 키(K) 벡터와 내적시킨다.
이 점수는 현재 단어와 다른 모든 단어 간의 '관련성'으로 계산된다.
가중치(Weight) 계산: 계산된 점수들을 스케일링 하고 소프트맥스(0~1 사이로 값 정제) 함수에 통과시켜 합이 1인 확률 분포로 만든다.
어텐션 가중치라고 불리며 점수가 높을수록 가중치가 커진다.
결과(Output) 계산: 각 단어의 밸류(V) 벡터에 해당하는 어텐션 가중치를 곱하고 모두 더한다.
이 과정을 통해 관련성이 높은 단어들의 의미(Value)가 더 많이 반영된, 현재 단어에 대한 새로운 표현 이 만들어진다.
Q, K, V 벡터 생성
Q, K, V 벡터는 각 단어의 입력 벡터 X (임베딩+포지셔널 인코딩)에 각각 별도의 가중치 행렬(Weight Matrix) W_Q, W_K, W_V를 곱하여 생성된다.
Q = X * W_QK = X * W_KV = X * W_V
입력 벡터 X를 서로 다른 가중치 행렬을 통해 세 가지 다른 공간(Q, K, V)으로 투형 시키는 과정이다.
이를 통해 모델은 어텐션을 수행하는 데 가장 적합한 표현을 스스로 학습한다.
스케일드 닷-프로덕트 어텐션(Scaled Dot-Product Attention)
논문에서는 위 과정을 하나의 수식으로 다음과 같이 표현한다.
Attention(Q, K, V) = softmax( (Q * K^T) / sqrt(d_k) ) V
Q * K^T: 쿼리 행렬 Q와 키 행렬 K를 전치(Transpose)한 K^T 행렬을 곱한다.
쿼리 벡터와 키 벡터 간의 내적을 한번에 계산한다는 것이다.
결과적으로 어텐션 점수 행렬이 나온다.
/ sqrt(d_k): 아까 계산된 어텐션 점수를 d_k(키 벡터의 차원)의 제곱근으로 나눈다.(Scaled의 의미)
이거 안하면 d_k 값이 클수록 Q와 K의 내적 값의 분산이 커지는 경향이 있고, 일부 값이 너무 커지면 소프트맥스 함수 통과할때 기울기가 0에 가까워진다.
softmax(...): 스케일링된 점수 행렬에 소프트맥스 함수를 적용해 각 행의 합이 1이 되는 어텐션 가중치 행렬을 만든다.
... * V: 계산된 어텐션 가중치 행렬과 밸류 행렬 V를 곱한다.
즉 가중치를 실제 값에 곱해서 가중합 하는 과정이다.
멀티 헤드 어텐션
하나의 셀프 어텐션만으로는 부족했다.
그러니 병렬적으로 돌려서 다양한 관점으로 동시에 바라보고 결과를 종합해보자 라는 개념이다.
d_model 차원의 Q, K, V 벡터를 h개(논문에서는 h=8개) 조각으로 나눈다.
각 조각(head)들이 독립적으로 어텐션을 수행 한 뒤, 그 결과들을 하나로 합친다.
즉 각 헤드는 서로 다른 '관점' 또는 '표현 부분 공간(representation subspace)'을 학습하게 된다.

계산 과정
선형 투영(Linear Projection): d_model(512) 차원의 Q, K, V 벡터 각각에 h(8)개 만큼의 서로 다른 가중치 행렬(W_Q_i, W_K_i, W_V_i)을 곱하여, h개의 서로 다른 Q, K, V 세트를 생성한다.
이 과정에서 각 헤드의 Q, K, V 벡터의 차원은 d_model / h 즉 512/8 = 64로 줄어든다.
병렬 어텐션(Parallel Attention): 8개의 헤드는 각각 64차원의 Q, K, V 벡터를 가지고 '스케일드 닷-프로덕트 어텐션'을 병렬적으로 수행한다.
그 결과 64차원 출력 벡터 8개가 생성되고 각 헤드는 독립적으로 서로 다른 종류의 관계를 학습한다.
결합(Concatenate): 8개의 출력 벡터들을 다시 하나로 이어 붙여 h * d_v = 8 * 64 = 512 차원의 벡터를 만든다.(나누고~다시 합치고)
최종 선형 투영(Final Linear Projection): 이렇게 합쳐진 벡터에 마지막으로 또 다른 가중치 행렬 W_O를 곱하여 최종 출력 벡터를 얻는다.
논문에서는 다음과 같은 수식으로 표현된다.
MultiHead(Q, K, V) = Concat(head_1, ..., head_h) * W_O
where head_i = Attention(Q W_Q_i, K W_K_i, V * W_V_i)