자연어 처리 뿐만 아니라 최근 컴퓨터 비전 영역에서도 사용되고 있는 유명한 모델인 Transformer를 알아보도록 하겠습니다.
트랜스포머는 2017년 구글에서 발표한 논문으로, Attention이라는 방식을 사용한 Transformer 구조를 제안한 논문입니다.
이 모델의 장점은 다음과 같이 요약할 수 있습니다.
- 어텐션 메커니즘을 사용하여 입력 문장의 모든 단어 사이의 상관 관계를 동시에 학습함으로써 자연어처리에 높은 성능을 보입니다.
- RNN 계열처럼 순차적인 계산을 할 필요가 없는 구조를 가지고 있기 때문에 병렬 처리가 가능해졌고, 이를 통해 학습 속도를 개선했습니다.
- 구성 요소가 재사용 가능하기 때문에 모델의 수정이나 확장이 용이합니다.
- 분석가 관점에서 Inductive Bias가 적습니다.(분석가가 조정해야 할 하이퍼파라미터가 적다고 보시면 됩니다.)
현재 사용되고 있는 대부분의 자연어 처리 모델들은 모두 이 구조를 사용하고 있다고 봐도 무방합니다. BERT는 이 모델의 인코더 부분을, GPT는 이 모델의 디코더 부분을 사용하고 있으며, 비전 쪽에서도 이 구조를 변형하여 사용하고 있고, 음성 등 다양한 분야에서도 사용되고 있습니다. 예를 들어 STT 분야에서 현재 가장 높은 성능을 보이는 모델은 w2v-BERT인데요. 여기서 BERT가 트랜스포머의 인코더 구조를 사용한 모델입니다. 심지어 wav2vec도 2.0에서는 트랜스포머 구조를 사용합니다!!
그럼 각 구조에 대해 설명해보도록 하겠습니다.
현재, 그리고 앞으로 나오는 다양한 모델들의 기본적인 구조는 트랜스포머의 구조를 사용할 것으로 보이기 때문에 아직 트랜스포머를 잘 모르고 계셨다면 한 번 이 포스터를 읽어보시면 좋을 것 같습니다.😀
Encoder
인코더는 동일한 블록 N = 6 개를 스택하여 구성되며, 하나의 블록에는 아래의 그림과 같이 두 개의 서브레이어가 있습니다.
첫 번째 서브레이어는 멀티 헤드 어텐션 메커니즘을 가지고 있고, 두 번째는 FFN입니다. 각각의 서브레이어는 Residual Connection을 사용하며, LayerNorm을 수행합니다. LayerNorm은 아래 링크에서 조금 더 자세한 설명을 확인할 수 있습니다. https://yonghyuc.wordpress.com/2020/03/04/batch-norm-vs-layer-norm/
즉 각 서브레이어의 출력은 다음과 같이 표현할 수 있습니다.
$$
LayerNorm(x +Sublayer(x))
$$
모델의 모든 서브레이어와 임베딩의 출력 차원은 $d_{model} = 512$로 설정합니다.
Position-wise Feed-Forward Networks
멀티 헤드 어텐션은 뒤에서 설명하기로 하고, 두 번째 서브레이어의 FFN은 다음과 같은 구조로 이루어져 있습니다. FC - RELU - Dropout - FC
여기서 inner-layer의 차원 $d_{ff}$는 2048이며 FFN을 공식으로 표현하면 다음과 같이 표현할 수 있습니다.
$$
FFN(x) = max(0, xW_1+b_1)W_2+b_2
$$
이 레이어는 각 포지션마다(개별 단어마다) 적용되기 때문에 Position-wise Feed-Forward Networks라고 불립니다. 한 레이어 안에서 모든 포지션은 같은 가중치를 적용하며, 레이어마다 가중치가 다릅니다.
다시 말하면, "Position-wise"라는 용어는 이 연산이 각 단어 벡터에 독립적으로 적용된다는 의미입니다. 즉, 단어 벡터의 순서나 위치에 대한 영향을 고려하지 않고 개별적으로 처리합니다. 이는 단어 벡터 간의 상대적인 위치 정보를 잃게 될 수 있지만, Transformer의 다른 부분인 Multi-Head Attention 메커니즘에서 위치 정보를 보존하도록 설계되어 있습니다.
추가로 Input을 할 때 Positional Encoding이라는 작업을 하는데요. 이 작업이 무엇인지에 대해 조금 더 설명해보도록 하겠습니다.
Input
Transformer는 RNN 계열 모델들과 달리 순차적인 처리를 할 필요가 없습니다. 이러한 순서 정보를 직접적으로 사용하지 않기 때문에, 단어의 상대적인 위치를 모델에 알려주기 위한 방법이 필요했고, 그 방법이 Positional Encoding입니다. 따라서 위의 그림과 같이 Transformer 모델의 첫 과정은 Inputs을 임베딩 벡터로 변환한 후에 Positional Encoding을 더해주는 방식으로 진행됩니다.
Positional Encoding
주기 함수를 사용하여 단어의 상대적인 위치를 입력하는 방식입니다. $sin, cos$ 함수를 임베딩 차원에 번갈아가며 적용하는 방식을 사용하는데, 서로 다른 위치를 설명할 수 있으면서도 주기함수 특성 상 값이 일정 범위에서만 유지될 수 있다는 점을 사용합니다. 조금 더 자세한 설명은 아래의 링크에서 확인하실 수 있습니다. https://www.blossominkyung.com/deeplearning/transfomer-positional-encoding
Positional Encoding을 수식으로 표현하면 다음과 같이 표현할 수 있습니다.
$$
PE_{(pos,2i)} = sin(pos/10000^{2i/d_{model}})\
PE_{(pos,2i+1)} = cos(pos/10000^{2i/d_{model}})
$$
이 때 $pos$는 포지션을, $i$는 차원을 의미합니다. $d_{model}$은 모델의 임베딩 디멘션입니다.
Decoder
디코더도 인코더와 마찬가지로 동일한 레이어 N = 6 개의 스택으로 구성됩니다. 디코더는 인코더 레이어에서 본 것과 동일한 두 개의 서브레이어가 있고, 조금 다른 서브레이어가 하나 더 있습니다.
이 서브레이어는 인코더 스택의 출력을 대상으로 멀티 헤드 어텐션을 수행합니다. 그림을 자세히 보면 인코더에서 어떠한 선이 들어오는 부분이 있는데, 이 부분이 인코더 스택의 출력을 받는 부분입니다. 조금 더 생각해보면 다른 어텐션들은 하나의 값에 대해서 Attention 연산을 하는데, 이 부분만 다른 두 곳에서 값을 받아서 Attention 연산을 하는 걸 확인할 수 있습니다. 즉 이 부분만 Self Attention이 아닙니다.
또한 디코더 스택의 셀프 어텐션 서브레이어에 대해 마스킹 작업을 하는데, 위치 i의 값을 예측할 때 그 이전의 정보만을 사용하기 위한 마스킹이라고 볼 수 있습니다. 다시 말하면 디코딩 시에 미래 시점의 정보를 제한하기 위한 마스킹입니다. 그림에서는 Masked Multi-Head Attention
이라고 적어놓은 것을 확인할 수 있습니다.
이 마스킹 방법은 이후에 설명하기로 하고 Result 쪽만 추가로 정리해보도록 하겠습니다. 참고로 Outputs가 들어오는 부분은 인코더와 동일하게 Positional Encoding을 수행합니다.
Result
위의 아키텍쳐에서 나온 결과는 Linear - Softmax 연산을 통해 최종 확률값을 출력합니다.
Attention
Attention
트랜스포머에서는 대부분 Self Attention이라는 개념을 사용하고 있습니다. 다만 아키텍쳐 상 딱 한 부분만 Attention을 사용하고 있는데 아래 그림과 같이 디코더의 마지막 어텐션은 K,V 부분은 encoder에서, Q부분은 decoder의 이전 output에서 가져오고 있습니다.
Attention과 Self Attention의 차이에 대한 설명은 아래 블로그에 자세히 적혀 있습니다. https://velog.io/@idj7183/Attention-TransformerSelf-Attention
Attention은 Query, Key, Value 간의 관계성을 추출하는 과정이라고도 하는데요. Query는 현재 시점의 토큰 값을, Key와 Value는 Attention을 통해 찾고자하는 토큰 값이라고 이해할 수 있습니다. 위의 그림에서 맨 오른쪽 화살표가 디코더에서 오는 현재 시점의 토큰 값 Query, 나머지 두 개는 인코더에서 오는 Key, Value 값입니다.
셀프 어텐션에서는 세 값 모두 디코더의 입력 시퀀스에서 가져옵니다.
저자들은 이 논문에서 제시한 어텐션을 Scale Dot-Product Attention이라고 명명했습니다. 공식은 다음과 같습니다.
$$
Attention(Q,K,V) = softmax({QK^T\over{\sqrt{d_k}}})V
$$
여기서 Query, Key의 차원은 $d_k$, Value의 차원은 $d_v$입니다.
기존에 자주 사용되는 방식은 Additive Attention과 Dot-Product Attention인데, 점곱 방식이 계산적으로 유리하지만 $d_k$가 커질수록 점곱의 크기가 증가하다보니 softmax 함수를 적용할 때 기울기가 매우 작아지는 현상이 발생했다고 합니다. 따라서 저자는 이를 보완하기 위해 점곱의 결과에 $1\over{\sqrt{d_k}}$를 사용하여 스케일링을 수행했습니다.
Self-Attention
트랜스포머에서 사용하는 Attention은 디코더의 마지막 어텐션 부분을 제외하고 Self-Attention을 사용하고 있습니다.
동일한 입력 벡터 시퀀스(X)에 대해 각각의 가중치 벡터를 곱함으로써 Query, Key, Value를 생성합니다. 이 가중치들은 학습을 통해 업데이트 되는 파라미터들입니다.
$$
Q = XW_Q\\
K = XW_K\\
V = XW_V
$$
셀프 어텐션은 각각의 쿼리 $Q$에 대해 다음과 같은 수식을 적용하여 구할 수 있습니다.
$$
Attention(Q,K,V) = softmax({QK^T\over{\sqrt{d_K}}})V
$$
이때의 $d_k$ 는 임베딩 차원을 의미합니다. $\sqrt{d_k}$ 로 나누면서 Vanising Gradient 등을 방지합니다.(위에 적은 내용입니다.)
그림으로 간단하게 셀프 어텐션을 표현해보도록 하겠습니다.
글자 길이 수 n = 2, 입력값으로 들어온 단어 임베딩 차원 = 3, 벡터 임베딩 차원 $d_k$ = 4라고 가정하고 임의의 Query를 다음과 같이 생성해 보겠습니다. 구조만 확인하는 것이므로 값은 모두 0을 넣었습니다.
글자 길이가 2, Input Embedding 차원이 3이므로 $X$의 shape는 다음과 같습니다.
$$
X =
\begin{bmatrix}
0 & 0 & 0\\
0 & 0 & 0\\
\end{bmatrix}
$$
이에 대해 벡터 임베딩 차원이 4이므로 $W_Q, W_K, W_V$의 shape는 다음과 같습니다.
$$
W_Q =
\begin{bmatrix}
0 & 0 & 0 & 0\\
0 & 0 & 0 & 0\\
0 & 0 & 0 & 0
\end{bmatrix}
$$
이 둘을 연산한 Query는 다음과 같습니다.
$$
Q = XW_Q = \begin{bmatrix}
0 & 0 & 0\\
0 & 0 & 0\\
\end{bmatrix}\begin{bmatrix}
0 & 0 & 0 & 0\\
0 & 0 & 0 & 0\\
0 & 0 & 0 & 0
\end{bmatrix}
=
\begin{bmatrix}
0 & 0 & 0 & 0\\
0 & 0 & 0 & 0\\
\end{bmatrix}
$$
$K, V$도 Query처럼 2x4일 것입니다. 이제 $QK^T$를 구해보도록 하겠습니다.
$$
QK^T=\begin{bmatrix}
0 & 0 & 0 & 0\\
0 & 0 & 0 & 0
\end{bmatrix}
\begin{bmatrix}
0 & 0\\
0 & 0\\
0 & 0\\
0 & 0
\end{bmatrix}
=
\begin{bmatrix}
0 & 0\\
0 & 0
\end{bmatrix}
$$
$QK^T$는 2x2 행렬입니다. 이에 대해 Softmax를 적용하고 Value를 곱해준 최종적인 shape는 2x4입니다. $\sqrt{d_K}$ 는 스칼라 값이므로 shape에는 영향을 주지 않습니다.
$$
softmax(QK^T)V=\begin{bmatrix}
0 & 0\\
0 & 0
\end{bmatrix}
\begin{bmatrix}
0 & 0 & 0 & 0\\
0 & 0 & 0 & 0
\end{bmatrix}
=
\begin{bmatrix}
0 & 0 & 0 & 0\\
0 & 0 & 0 & 0
\end{bmatrix}
$$
즉 결과값이 input값과 shape이 동일한 것도 확인하실 수 있습니다.
Multi-Head Attention
Self-Attention을 여러 번 수행한 것을 Multi-Head Attention이라고 합니다. 다음과 같은 수식으로 정리할 수 있습니다.
$$
MultiHead(Q,K,V) = concat([head_1, head_2, ..., head_h)W^o
$$
여기서 $head_i$ 는 i번째 $Attention$을 의미합니다.
마지막에 곱해주는 $W^o$의 크기는 Concat한 Self-Attention의 컬럼 길이 * 목표 차원수이며, $W^o$의 값은 learnable parameter입니다.
Masked Multi-Head Attention
디코딩 하는 시점에서 미래 시점의 정보를 알지 못하도록 미래 시점의 가중치를 0으로 마스킹 하는 방법을 사용합니다. 구체적으로는 softmax를 적용하기 직전에 mask를 하고자 하는 토큰에 대해 -inf를 더하거나 치환함으로써 softmax 후 해당 가중치가 0이 되게끔 작업을 합니다.
굳이 이렇게 하는 이유는 행렬 연산의 이점을 그대로 가져가면서도 masking을 하기 위함으로 보입니다.
Masking
지금까지 아키텍쳐에서 Masked Multi-Head Attention이라는 블록을 확인했습니다. Transformer에서는 두 가지 마스킹 방법을 사용하고 있는데, 어떤 방법이 있는지, 어떻게 동작하는지 살펴보도록 하겠습니다.
Padding Mask
input으로 들어오는 sentence의 길이는 서로 다를 가능성이 높습니다. 때문에 이러한 문장 길이를 동일하게 맞춰주는 작업이 필요하다고 할 수 있습니다. 이럴 경우 가장 긴 문장을 기준으로 짧은 문장의 뒤에 0을 마스킹해서 길이를 맞춰주는 과정이 필요합니다. 이러한 과정을 Padding이라고 하고, 이를 위한 마스킹을 Padding Masking이라고 합니다.
위의 그림을 보고 다시 설명을 해 보도록 하겠습니다. 만약 최대 길이기 6일 때 길이가 5인 문장이 들어온다면 마지막 단어에 대해서는 Attention이 계산되지 않아야 합니다.
파란색 박스 영역 위치의 값에 대해 -inf를 넣어준다면, 이후 softmax 연산을 통해 해당 위치의 Attention을 0으로 만들 수 있습니다. 이러한 작업을 하기 위치를 찾아주는 방법이 Padding Mask입니다. 이 마스킹 방법은 Encoder, Decoder 모두 사용됩니다.
Look-ahead masking
Masked Multi-Head Attention에서만 사용되는 마스킹 방법입니다. 디코더의 입력에 미래 시점의 단어가 들어가지 않게 하기 위한 마스킹이라고 볼 수 있습니다. 따라서 다음과 같은 그림으로 구현을 해야 합니다.
디코더의 Masked Multi-Head Attention에서도 당연히 Padding Masking도 되어있기 때문에 pad가 존재한다면 최종적으로 마스킹 된 부분은 다음 그림처럼 표현이 됩니다.
Train
학습 시에 Adam 옵티마이저를 사용했고, $\beta_1=0.9$, $\beta_2 = 0.98$, $\epsilon = 10^{-9}$ 로 설정하였으며 러닝레이트는 다음가 같이 구성했습니다.
$$
lrate = d_{model}^{-0.5}min(step_num^{-0.5}, step_num \times warmup_steps^{-1.5})
$$
warmup_steps까지 선형적으로 학습률을 증가시킨 후, step_number의 역 제곱근에 비례하여 학습률을 감소시켰습니다. $warmup_step=4000$ 입니다.
이 러닝레이트 조절 방법은 모델이 초기에 불안정한 상태에서 빠르게 수렴하도록 도와줍니다.
정리
지금까지 Attention is all you need 논문을 살펴보았습니다. 여기에서 사용된 트랜스포머 구조, 어텐션 기법은 이 논문 이후 대부분의 자연어 분야 및 다른 분야에서 사용되고 있습니다. 이미지 분야에서도 어떻게든 적용하여 상당히 좋은 성능을 발휘할 만큼 정말 안쓰이는 분야가 없다고 보시면 됩니다.
Chat GPT에서 사용하는 GPT-3.5 등의 LLM 모델들 역시 트랜스포머 구조를 기본으로 엄청나게 모델을 크게 키운 구조라고 보시면 되는데요. 그 만큼 지금이라도 한 번 이 논문을 살펴보시면 현재, 그리고 앞으로도 인공지능 쪽에서 사용되는 다른 모델들을 보시는 데 더 편하실 것 같습니다. 이 게시물을 읽어보시고 시간나실 때 논문까지 한 번 살펴보시면 정말 많은 도움이 되실 것이라고 생각합니다. 감사합니다.
참고
https://ratsgo.github.io/nlpbook/docs/language_model/transformers/