1. 트랜스포머
- 자연어 처리에서 주로 사용하는 딥러닝 아키텍처 중 하나.
- RNN과 LSTM은 트랜스포머로 대체됨.
- BERT, GPT, T5 등과 같은 다양한 자연어 처리(NLP) 모델에 트랜스포머 아키텍처가 적용됨
1.1 트랜스포머 소개
- 기존 RNN과 LSTM 네트워크에는 다음 단어 예측, 기계번역, 텍스트 생성 등의 태스크에서 널리 사용되었다. 하지만 이러한 네트워크는 장기 의존성 문제가 있다. 복하기 위해 'Attention Is All You Need' 논문에서 트랜스포머라는 아키텍처가 제안되었다.
- 트랜스포머는 RNN을 사용하지 않는다
- 트랜스포머를 한 단어로 표현하자면 병렬화이다. 트랜스포머는 일을 최대한 한방에 처리하려고 한다.
병렬화 : 순차적인 직렬 프로그램을 분할하고 분할된 단위를 동시에 병렬로 수행함으로써 성능을 향상하는 프로그래밍 기술.
- 즉 RNN이 순차적으로 첫 번째부터 마지막까지 입력한 데이터를 계산해서 입력된 단어들을 인코딩하는 반면, 트랜스포머는 이 과정을 한 번에 처리하는 것을 의미한다.
* RNN과 트랜스포머의 차이
먼저 RNN과 트랜스포머는 인코더와 디코더로 구성된 모델이다.
먼저 원문에 입력 문장(원문)을 입력하면 인코더는 입력 문장의 표현 방법을 학습시키고 그 결과를 디코더로 보낸다.
디코더는 인코더에서 학습한 표현 결과를 입력받아 사용자가 원하는 문장을 생성한다.
1) RNN Based Encoder Decoder
- 입력 값이 I love you 일 경우 인코더는 I부터 순차적으로 상태값을 계산함. 최종적으로 이 상태값을 문맥 백터 (context vector)로 사용함.
- 디코더는 이 문맥 벡터를 기반으로 입력된 문장을 번역한다.
- 위와 같은 예제는 전통적인 인코더 디코더 모델이며, 이러한 모델은 문맥 백터의 크기가 고정되어 있다.
- 그렇기에 책과 같이 긴 문장들은 고정된 문맥 벡터에 모든 내용을 저장하기가 힘들어서 번역 결과가 엉터리가 되는 경우가 많음.
그래서 사람들은 RNN을 대신할 모델을 찾았고, 그렇게 해서 등장한 것이 트랜스포머이다.
1.2 트랜스포머의 인코더 이해하기
- 트랜스포머는 N개의 인코더가 쌓인 형태이다.
- 인코더의 결괏값은 그다음 인코더의 입력값으로 들어간다.
- 즉, 가장 최초의 인코더에 대한 입력값으로 입력 문장을 넣게 되고, 최종 인코더의 결괏값으로 입력 문장에 따르는 표현 결과를 얻는다.
인코더의 동작 원리
- 인코더는 어떤 원리로 작동하며 입력 문장으로 어떤 결괏값을 생성할까?
- 우선 이를 이해하기 위해 인코더의 각 구성 요소를 이해해야 한다.
- 인코더는두 가지 요소로 구성되며 모든 인코더 블록은 형태가 동일하다.
- 멀티 헤드 어텐션(multi-head attention)
- 피드포워드 네트워크(feedforward network)
우선 멀티 헤드 어텐션의 원리를 이해하기 위해선 셀프 어텐션의 원리를 이해해야 한다.
1.2.1 셀프 어텐션(self-attention)의 작동 원리.
1) 어텐션(Attention)이란?
- 트랜스포머 아키텍처를 제안한 논문의 제목을 해석해 보자면 "Attention이야말로 모든 것"이라고 의미하고 있다.
- 트랜스포머가 자연어 분야를 평정한 데에는 바로 이 Attention을 적극적으로 기용한 데에 있다.
- 그중에서도 트랜스포머의 핵심은 self-attention에 있다.
- Attention이 의미하는 것은 강조이다. 일반적으로 어떤 문장을 통해 의미를 전달하려고 할 때 사실 핵심은 한 두 단어에 압축되어 있다.
- 즉, 여러 단어가 모여 하나의 문장을 이루어도 단어 각각의 중요도는 다르다.
- 트랜스포머는 이러한 언어의 특징을 중점적으로 다루었다.
- 트랜스포머는 모델에게 언어를 가르칠 때 문장 내에서 언어의 중요도 차이가 있음을 '강하게'알려 준 것이다.
- 트랜스포머의 핵심은 self-attention이라고 하였는데, 이는 스스로가 스스로를 강조함을 의미한다. 다음 예시를 참고해 보자.
2)self-attention
A dog ate the food because it was hungry
- 이 문장을 살펴보도록 하자. 이 문장에서 it은 dog나 food를 의미할 수 있다.
- 하지만 문장을 자세히 살펴보면 it은 food가 아닌 dog를 의미한다는 것을 쉽게 알 수 있다.
- 위와 같은 문장이 주어질 경우 모델이 이러한 관계를 파악하기 위해 셀프 어텐션이 필요하다.
- 이 문장이 입력되었을 때, 모델은 가장 먼저 단어 'A'의 표현을, 다음으로는 'dog'의 표현을 계산한다.
- 각 단어의 표현들은 문장 안에 있는 모든 단어의 표현과 연결해 해당 단어가 문장 내에서 갖는 의미를 이해한다.
- 예를 들어 'it'이라는 단어의 표현을 계산한다는 것은 모델에서 'it'이라는 단어의 의미를 이해하기 위해 문장 내 모든 단어와 'it'이라는 단어를 연결하는 작업을 수행하는 것을 의미한다.
- 다음 그림은 'it'이라는 단어의 표현을 계산하기 위해 'it'을 문장의 모든 단어와 연결하는 작업을 보여준다.
- 이와 같은 연결 작업으로 모델은 'it'이 'food'가 아닌 'dog'와 관련이 있다는 것을 학습한다. (dog를 잇는 선이 다른 단어보다 두껍게 표시됨.)
- 그럼 셀프 어텐션은 내부적으로 어떤 원리로 작동할까?
3) 셀프 어텐션의 내부 작동 원리
(1) 임베딩 행렬 생성하기
- 입력 문장이 'I am good'이라고 가정해 보자. 이 입력 문장을 기준으로 각 단어의 임베딩(embedding)을 추출한다.
- 임베딩이란 각각의 단어를 표현하는 벡터값을 의미하며, 임베딩값은 모델 학습 과정에서 같이 학습된다.
- 단어 'I'에 대한 임베딩 x_1=[1.76,2.22,...,6.66]
- 단어 'am'에 대한 임베딩 x_2=[7.77,0.631,...,5.35]
- 단어 'good'에 대한 임베딩 x_3=[11.46,10.22,...,3.33]
와 같이 입력 문장 'I am good'을 입력 행렬 X(임베딩 행렬 또는 입력 임베딩)로 표현할 수 있다.
- 행렬 x의 차원은 [문장길이*임베딩 차원]의 형태가 된다.
- 위의 경우에는 단어의 수는 3개, 임베딩 차원을 512라고 한다면 행렬 X의 차원은 [3*512]가 된다.
(2) 쿼리, 키, 벨류 행렬 생성하기
- 이제 입력 행렬 X로부터 쿼리(query, Q) 행렬, 키(key, K) 행렬, 밸류(value, V) 행렬을 생성한다. 이 세 가지 행렬들은 셀프 어텐션에서 사용된다.
- 우선 쿼리, 키, 밸류 행렬을 어떻게 만드는지부터 살펴보자면, 행렬을 생성하기 위해서는 Wq , Wk, Wv라는 3개의 가중치 행렬(weight matrix)을 생성한 다음. 이 가중치 행렬을 입력 행렬 X에 곱해 Q, K, V 행렬을 생성한다.
- 이때 Q,K,V 벡터의 차원이 64라고 가정하면, Q, K, V의 행렬의 차원은 [단어의 길이*64]가 된다.
-이 세 가지 행렬은 과연 어떻게 사용되는 것일까?
(3) 셀프 어텐션의 작동 원리 이해하기
- 이제 Q, K, V 행렬이 셀프 어텐션에 어떻게 사용되는지 알아보자.
- 위에서 사용한 문장 'I am good'문장을 예로 들업자.
- 단어 'I'의 표현을 계산하려면, 단어 'I'와 전체 문장에 있는 단어와 연결하는 과정을 수행한다.
- 위와 같은 방법을 적용하는 이유는 -> 특정 단어와 문장 내에 있는 모든 단어가 어떤 연관이 있는지 이해하면 좀 더 좋은 표현을 학습시키는데 도움이 된다.
- Q, K, V 행렬은 특정 단어와 문장 내에 있는 모든 단어와 연결할 때 사용된다.
- 셀프 어텐션은 총 4단계로 이루어져 있다.
1단계
- 첫 번째 단계는 쿼리(Q) 행렬과 키(K) 행렬의 전치 행렬에 대한 내적 연산을 수행한다.
- 이때 쿼리와 키 행렬 사이 내적을 계산하는 이유는 무엇일까? 연산결과는 정확히 무엇을 의미하는 걸까?
- 두 벡터 사이의 내적을 계산한다는 것은 두 벡터가 얼마나 유사한지를 나타낸다.
- Q와 K의 전치행렬을 계산한 행렬의 첫 번째 행을 보자. 첫 번째 행은 쿼리 벡터 q1(I)과 키 벡터 k1(I), k2(am), k3(good)의 내적을 계산한다는 것을 알 수 있다.
- 쿼리 벡터 q1(I)과 키 벡터 k1(I),k2(am),k3(good)의 내적을 계산한 것은 쿼리 벡터 q1(I)과 키 벡터 k1(I),k2(am),k3(good) 사이의 유사도를 계산한 것이다.
2단계
- 다음 단계는 $QK^T$ 행렬의 키 벡터 차원의 제곱근값으로 나눈 것이다.
- 논문에서는 $QK^T$와 같이 내적을 이용하는 경우 스케일링 ($\frack {1}{\sqrt d_k}$)을 하지 않는 경우 결과가 매우 안좋았다고 하는데
- 키 벡터의 차원이 높은 경우 softmax()함수를 거치게 될경우 너무 작은 gradient값을 가지게 되므로 $\frac {1}{\sqrt d_k}$로 스케일링 하는 과정이 필요하다.
- 이와 같은 방법을 적용하면 안정적인 gradient를 얻을 수 있다.
$$\frac {QK^T}{\sqrt d_k}$$
3단계
- 이전 단계에서 계산한 유사도 값은 비정규화된 형태(unnormalized form)이다. 따라서 소프트맥스 함수(softmax function)를 사용해 정규화 작업을 진행한다.
- softmax 함수를 적용하면 전체 값의 합은 1이 되며 0과 1 사이의 값을 갖는다.
$$softmax( \frac{QK^T}{\sqrt d_k} )$$
- 이렇게 softmax 함수를 적용하여 얻은 행렬을 스코어(score) 행렬이라고 한다.
- 위 점수를 바탕으로 문장 내 각 단어가 문장에 있는 전체 단어와 얼마나 연관되어 있는지 알 수 있다.
- 위 스코어 행렬의 첫 번째 행을 보면 단어 'I'는 자기 자신과 90%, 단어 'am'과는 10%, 단어 'good'과는 3% 관련되어 있다는 것을 알 수 있다.
4단계
- 지금까지 쿼리, 키 행렬에 대해 내적을 계산하고, 소프트맥스 함수를 사용해 내적 값에 대한 정규화 작업을 진행했다.
- 그다음 과정은 어텐션(Z) 행렬을 계산하는 것이다.
- 어텐션 행렬은 문장의 각 단어의 벡터값을 가진다.
- 스코어 행렬 $softmax( \frac{QK^T}{\sqrt d_k} )$ 에 밸류 행렬(V)을 곱하면 어텐션 행렬(Z)을 구할 수 있다.
- 다음은 어텐션 행렬을 계산한 다음 얻은 결과이다.
- 어텐션 행렬 Z의 첫 번째 행 $z_1$은 단어 I의 셀프 어텐션을 의미한다.
- 첫번째 행 $z_1$을 보면 다음과 같이 계산된다.
- 즉 I의 셀프 어텐션 값은 밸류벡터 $V_1(I)$의 90% 값과 $V_2(am)$ 70%, $V_3(good)$ 3%의 합으로 구한다.
- 또 다른 예시를 들어 보자.
- A dog ate the food becuase it was hungry의 문장을 살펴보면 it 은 food가 아닌 dog를 의미한다.
- 이때 단어 'it'에 대해 위의 방법으로 셀프 어텐션을 계산해 다음과 같은 결과를 얻었다고 가정했을 때 다음과 같다.
- 단어 'it'의 셀프 어텐션 값은 밸류 벡터 $v_2(dog)$가 100% 반영된 결과로 볼 수 있다.
- 이는 모델에서 'it'이 'food'가 아닌 'dog'와 관련이 있다는 것을 알 수 있다.
- 셀프 어텐션 계산 방법을 적용하게 된다면 단어가 문장 내에 있는 다른 단어와 얼마나 연관성이 있는지를 알 수 있다.
- 이를 통해 어텐션 행렬(Z)은 다음 식으로 구할 수 있는 것을 알 수 있다. $$z = softmax(\frac{QK^T}{\sqrt d_k})V$$
- 지금까지 살펴본 셀프 어텐션의 단계를 요약하면 다음과 같다.
1. 쿼리 행렬과 키 행렬 간의 내적을 계산하고 유사도 값을 산출한다.
2. $QK^T$를 행렬의 차원의 제곱근($\sqrt d_k$)로 나눈다.
3. 스코어 행렬에 소프트맥스 함수를 적용해 정규화 작업을 진행한다.$softmax( \frac{QK^T}{\sqrt d_k} )$
4. 마지막으로 스코어 행렬에 밸류 행렬을 곱해 어텐션 행렬 Z를 산출한다.
- 셀프 어텐션은 쿼리와 키 벡터의 내적을 계산한 다음 $\sqrt d_k$로 나누기 때문에 Scaled Dot-Product Attention이라고도 부른다.
1.2.2 멀티 헤드 어텐션 원리
- 앞에서 셀프 어텐션을 통해 Attention Matrix를 계산하는 것을 알아보았다.
- Transformer에선 셀프 어텐션의 아이디어를 확장하여 멀티 헤드 어텐션을 도입한다.
- 멀티 헤드 어텐션이라는 것은 한번의 어텐션을 하는 것이 아닌 어텐션을 병렬로 여러 번 사용하는 것을 의미한다.
- 이때 각각의 어텐션 값 행렬을 어텐션 헤드라고 부른다.
- 이때 가중치 행렬의 값은 N개의 어텐션 헤드마다 전부 다르다.
병렬 어텐션의 효과
- 어텐션 행렬을 병렬로 구한다는 것은 각각 다른 시각으로 정보를 수집하겠다는 의미이다.
- 예를 들어 '철수' 밥을 먹지 않았다. 왜냐하면 그는 너무 배부르기 때문이다.'라는 문장이 입력 데이터라고 가정하고, '그'라는 단어를 인코딩할 때
- 첫 번째 어텐션 헤드는 '그'와 '사람'의 유사도를 높게 본다면, 두 번째 어텐션 헤드는 '그'와 '배부르기 때문이다'의 연관도를 높게 볼 수 있다.
- 즉 병렬로 어텐션 행렬을 구한다는 것은 서로 다른 기준으로 다양한 형태의 정보를 추출할 수 있는 장점이 존재한다.
병렬 어텐션을 모두 수행하였다면 모든 어텐션 헤드를 연결(Concatenate)한다.
$$멀티 헤드 어텐션 = concatenate(Z_1,Z_2,....,Z_i)W_0$$
- 예를 들어 8개의 어텐션 행렬을 ($Z_1,Z_2,....,Z_8$)구한다고 하면, 해당 행렬을 계산한 후 ($Z_i = sofmax(\frac{Q_iK_i^T}{\sqrt d_i})V_i$) 그 어텐션 헤드들을 연결(concatenate) 한 후 새로운 가중치 행렬 $W_o$에 곱하면 우리가 원하는 차원의 행렬곱을 구할 수 있다.
이러한 방식으로 self-attention model을 사용해서 주어진 입력 데이터를 encoding 하게 되면, RNN의 장기 의존성 문제를 근본적으로 해결할 수 있게 된다.
- 그 이유는 각각의 단어들이 입력 데이터에 존재하는 각 단어들의 유사도를 직접적으로 구하고, 그중 유사도가 높은 단어를 단어의 time step 간의 차이가 얼마나 가까웠든 멀었든 상관없이 그 정보를 직접적으로 꺼내갈 수 있는 장점이 있기 때문이다.
- 하지만 이러한 과정에서 self-attention이 가지는 하나의 단점은 memory의 요구량이다.
- self-attention model의 가장 핵심 수식이었던 $QK^T$를 보면 각각의 모든 Q 벡터와 K벡터 간에 내적을 하게 되는데,
- 주어진 입력 데이터의 단어의 길이가 N개라고 했을 때 N이 늘어나면 늘어날수록 $QK^T$ 행렬의 가로, 세로 사이즈는 결국 N이 될 것이고, 그 행렬들을 계산하고 저장하는 과정에서 N의 제곱만큼의 memory size가 요구된다.
1.2.3 위치 인코딩으로 위치 정보 학습(Positional Encoding)
- 'I am good'이라는 입력 문장이 있다고 가정하자. RNN에서는 단어 단위로 I 가 입력된 다음에 'am'이라는 단어가 전달된다.
- 네트워크는 문장을 완전히 이해하기 위해 문장을 단어 단위로 나누어서 입력한다.
- 하지만 트랜스포머에서는 위와 같은 순환구조를 따르지 않고, 문장에 있는 모든 단어를 병렬 형태로 입력한다.
- 병렬로 단어를 입력하는 것은 학습 시간을 줄이고 RNN의 장기 의존성 문제를 해결하는데 도움이 된다.
- 하지만 단어를 병렬로 입력하면 단어의 순서 정보가 유지되지 않는다는 단점이 존재한다.
- 문장의 의미를 이해하기 위해서는 단어의 순서가 매우 중요하다.
- 트랜스포머에 단어의 순서 정보를 제공하기 위해 어떤 방법을 적용해야 할까?
- 예를 들어보자 'I am good'이라는 문장을 임베딩 차원이 4인 임베딩 행렬로 표현한다면 다음과 같다.
- 이 임베딩 행렬(입력 행렬)을 트랜스포머에 바로 입력하면 단어 순서 정보를 이해할 수 없다.
- 입력 행렬을 트랜스포머에 직접 전달하는 대신 네트워크에서 문장의 의미를 이해할 수 있도록 단어의 순서를 표현하는 정보를 추가로 제공해야 한다. 이를 위해 위치 인코딩이라는 새로운 방법을 활용한다.
- 위치 인코딩이라는 이름에서 알 수 있듯이 문장에서 단어의 위치(단어의 순서)를 나타내는 인코딩이다.
- 위치 인코딩 행렬 P의 차원은 입력 행렬 X의 차원과 동일하다.
- 트랜스포머에 입력하기 전에 입력행렬 X에 위치 인코딩 행렬 P를 더한 후 입력을 한다.
- 그렇다면 위치 인코딩은 어떻게 계산되는 것일까?
- 위치 값을 지정해 주기 전 반드시 지켜야 할 규칙이 있다.
1) 모든 위치값은 시퀀스의 길이나 Input이 정확히 무엇인지에 관계없이 동일한 식별자를 가져야 한다.
- 따라서 시퀀스가 변경되더라도 위치 임베딩은 동일하게 유지될 수 있다.
2) 모든 위치 값은 너무 크면 안 된다.
- 위치 값이 너무 커져버리면, 단어 간 유사도 및 의미를 유추할 수 있는 의미정보 값이 상대적으로 작아지게 되고, Attention Layer에서 제대로 학습 및 훈련이 되지 않을 수 있다.
Positional Encoding을 어떻게 설정해야 할까?
- 위치 벡터를 부여하는 벙법으로는 다음과 같이 간단히 두 가지 방법을 떠올릴 수 있다.
1) 각 토큰에 대해 1부터 시작하여 시퀀스 데이터의 길이까지 번호를 매긴다.
- 하지만 이 방식을 사용하게 되면 위치 임베딩의 두 번째 요구 사항을 위반하게 된다.
- 시퀀스 길이가 길어질수록 위치 벡터 값 또한 점점 커진다는 문제점이 있고
- 위치 벡터가 특정한 범위를 가지고 있지 않아 모델의 일반화 역시 불가능해진다. (숫자가 매우 커질 수 있고, 훈련 시 학습할 때 보다 큰 값이 입력값으로 들어오게 될 수 있으므로. )
일반화 : 학습 데이터와 Input data가 달라져도 성능 차이가 나지 않게 하는 것을 일반화라고 한다.
2) 첫 번째 토큰에는 0, 마지막 토큰은 1을 부여하고, 그 사이를 1/단어수로 나누어 나온 값을 적용한다.
- 이 방법을 적용하게 된다면 위치 임베딩의 두 번째 요구사항은 충족하게 된다. (범위가 정해짐, 값이 너무 크지 않음.)
- 하지만 이 경우 첫 번째 요구 사항을 위반하게 된다.
- 시퀀스 길이에 따라서 같은 위치 정보에 해당하는 벡터의 위치 벡터 값이 달라질 수 있고, 시퀀스의 총길이도 알 수 없다.
- 또한 단어 간 위치벡터의 차이 역시 달라지는 문제점이 발생한다.
따라서 단어 의미 정보가 변질되지 않도록 위치 벡터값이 너무 커서도 안되고, 같은 위치의 토큰은 항상 같은 위치 벡터값을 가지고 있어야 한다.
그래서 해당 논문에서는 sine, cosine함수를 이용하여 위치 벡터를 부여한다.
- sine, cosine함수는 어떻게 positional encoding의 조건에 부합할까?
1번 요구사항
->sine, cosine함수는 -1~1 사이를 반복하는 주기함수이다. 즉 1을 초과하지 않고, -1 미만으로 떨어지지 않으므로 값이 너무 커지지 않는 조건을 만족시킨다.
2번 요구사항
- sine & cosine 외에도 일정 구간 내에 있는 함수로는 Sigmoid함수가 있다.
- 하지만 sigmoid 함수의 경우 긴 문장의 시퀀스가 주어진 경우, 위치 벡터 값의 차가 미미해지는 문제가 발생할 수 있다.
- sine, cosine 함수는 -1~1사이를 주기적으로 반복하는 함수이기에 긴 문장이 주어지더라도, 위치 벡터값의 차가 작지 않게 된다.
- 그러나 문제는 sine, cosine 함수는 -1~1사이를 반복하는 주기함수이기 때문에 토큰들의 위치 벡터값이 같은 경우가 발생할 수 있다.
- 하지만 각 벡터 n개당(위 이미지는 4개의 벡터를 가짐) n개의 서로 다른 sine, cosine파형을 제공해 준다면 서로 겹치지 않게 할 수 있다. (물론 모든 주기의 공배수만큼 지난 위치는 겹칠 수 있겠지만, 그 정도는 충분히 단어의 위치를 표현할 수 있다.)
- 논문에서는 이렇게 파형이 다른 sine, cosine 함수를 생성하기 위해 다음과 같은 공식을 제공한다.
(논문에서는 이렇게 제시하고 있지만 자체적으로 커스터마이징 하는 경우도 존재한다고 한다. )
- 위의 식을 보면 pos는 문장에서 단어의 위치를, i는 position을 표현하기 위한 차원의 인덱스를, $d_model$은 차원의 수를 나타낸다.
* i는 차원의 인덱스라고 했는데 0,0,1,1 이렇게 나오는 이유
- positional encoding 행렬의 차원이 짝수인 경우 사인 함수를, 홀수인 경우 코사인 함수를 사용한다.
P(0,0) = P(0,2i) --> i = 0
P(0,1) = P(0, 2i+1) --> i = 0
P(0,2) = P(0,2i) --> i = 1
P(0,3) = P(0,2i+1) --> i = 1
- 위치 인코딩 P계산한 후 임베딩 행렬 X에 대해 Summation을 수행 후 인코더의 입력 행렬로 입력한다.
1.2.4 포지션-와이즈 피드포워드 네트워크(Position-wise FFNN)
- position-wise FFNN은 인코더, 디코더에서 공동적으로 가지고 있는 서브층이다.
- 여기서 x는 멀티 헤드 어텐션의 결과로 나온 행렬을 의미한다.
- 활성화 함수는 ReLU를 사용한다.
- $W_1, b_1, W_2, b_2$는 하나의 인코더 층 내에서는 다른 단어들마다 동일한 파라미터 값을 사용한다.
- 하지만 인코더 층 마다는 서로 다른 값을 가진다.
1.2.5 add와 norm 요소
- add와 norm 요소는 기본적으로 레이어 정규화와(layer normalization) 잔차 연결이다(Residual connection)
1. 잔차 연결(Residual connection)
- 일반적인 신경망 모델 학습 시 모델의 층이 깊어질수록 학습 결과가 좋다고 알려져 있다.
- 하지만 층을 너무 깊이 쌓거나 노드 수를 너무 크게 증가시키면 입력 정보가 여러 층을 거치면서 이전 층에 대한 정보 손실이 발생할 수 있고, 가중치가 잘못된 방향으로 갱신되는 문제가 발생할 수 있다.
- 그래서 이전 층의 정보를 이용하기 위해 이전 층의 정보를 연결하는 잔차 연결을 적용한다.
- 위 그림은 입력 $x$와 함수 $F(x)$의 값을 더한 함수 $H(x)$의 구조를 보이고 있다.
- Transformer에서는 multi-head attention, FFNN 계산을 구하고 마지막에 가중치 행렬 W를 곱하여 입력값과 출력값이 동일한 차원을 가지도록 하고 있다.
- 그로 인해 더하기 연산이 가능하게 된다.
- 만약 서브층이 Multi-head Attention이었다면 아래의 수식과 같다.
2. 레이어 정규화(Layer Normalization)
- 잔차 연결을 거친 결과는 이어서 층 정규화 과정을 거치게 된다.
- 잔차 연결의 입력값을 x, 잔차 연결과 층 정규화를 모두 거친 결과 값을 LN이라고 할 경우, 잔차 연결 후 층 정규화 연산을 수식으로 표현하면 다음과 같다.
$$LN = LayerNorm(x + Sublayer(x))$$
- 층 정규화는 Tensor의 마지막 차원에 대해서 평균과 분산을 구하고, 이를 가지고 어떤 수식(LayerNorm)을 통해 값을 정규화하여 학습에 도움을 주게 된다.
-아래 그림은 $d_model$의 한 단어에 대한 임베딩 벡터들을 화살표로 표현한 것이다.
- 층 정규화를 위해 먼저 화살표 방향으로 각각 평균$u$와 분산 $σ^2$을 구한다..
- 여기서 각 화설표 방향의 벡터를 $x_i$라고 칭해보겠다.
- 층 정규화를 수행한 후에 벡터 $x_i$는 $ln_i$벡터로 정규화가 된다.
- 수식으로 표현하자면 $ln_i = LayerNorm(x_i)$ 와 같다.
-층 정규화를 2가지 과정으로 나누어 설명하자면.
- 평 군과 분산을 통한 정규화
- 감마와 베타를 도입
- 우선 평균과 분산을 통해 벡터 $x_i$를 정규화해 준다.
- 벡터 $x_i$의 각 차원을 $k$라고 하였을 때, $x_ik$는 아래의 수식과 같이 정규화될 수 있다.
- 위의 ε(입실론)은 분모가 0이 되는 것을 방지하는 값이다.
- 이제 γ(감마)와 β(베타)라는 벡터를 준비한다. 이들의 초기값은 각각 1과 0이다.
- (감마)와 β(베타)는 학습 가능한 파라미터이며 최종 수식은 다음과 같다.
1.3 트랜스포머 디코더 이해하기.
- 지금까지 우리는 번역기를 만든다고 가정했을 때 입력 문장의 표현을 학습하는 인코더에 대해 인코더가 어떤 방법으로 입력 문장을 학습하는지에 대해 다뤄 보았다
- 이제 인코더의 결괏값을 가지고 디코더의 입력값으로 사용한다.
- 디코더는 인코더의 표현을 입력값으로 사용하고 원하는 문장을 생성한다.
- 인코더 부분을 다룰 때 인코더 N개를 누적해서 쌓을 수 있다는 것을 배웠다.
- 인코더와 유사하게 디코더 역시 N개를 누적해서 쌓을 수 있다.
- 디코더는 이전 디코더의 결괏값을 입력값으로 받는다는 인코더와 동일한 점을 가지고 있지만, 인코더의 입력 문장 표현(인코더의 출력값)이 모든 디코더에 전송되는 것을 알 수 있다.
- 즉 디코더의 입력값은 이전 디코더의 결괏값, 인코더의 표현(인코더의 출력값) 이렇게 2개를 입력데이터로 받는다.
- 디코더는 <sos> 토큰(start of sentence token)의 입력을 시작으로 이전 타임스텝에서 새로 생성한 단어를 조합해 입력값을 생성하고, 이을 이용 해 <eos> 토큰(end of sentence token)을 생성할 때까지 다음 단어를 예측한다.
- 이전 타임스텝에서 생성한 단어를 다음 타임스텝의 입력값으로 넣기 위해선 임베딩으로 변환하여야 한다. 우리는 이를 출력 임베딩이라고 한다.
- 인코더에서 입력 행렬에 위치 인코딩을 더해 입력한 것처럼, 디코더 역시 입력값을 바로 입력하는 것이 아니라 위치 인코딩을 추가한 값을 디코더의 입력값으로 사용한다.
디코더의 세부 구조
- 디코더 블록은 인코더 블록과 유사하게 서브 레이어에 멀티 헤드 어텐션과 피드포워드 네트워크를 포함한다.
- 하지만 인코더와 다르게 두 가지 형태의 멀티 헤드 어텐션을 사용한다.
1.3.1 마스크 된 멀티 헤드 어텐션(Masked Multi-Head Attention)
- 디코더에서 문장을 입력할 때 <sos> 토큰을 입력하는 것에서 시작하여 이전 타입스텝에서 생성한(예측한) 단어를 점점 하나씩 추가하는 형태로 입력을 반복한다.
- 인코더에서의 셀프 어텐션은 각 단어의 의미를 이해하기 위해 각 단어와 문장 내 전체 단어를 연결하여 유사도를 계산하였다.
- 그러나 디코더에서는 입력 행렬을 생성할 때 이전 단계에서 생성한 단어만 입력 문장으로 넣는다.
- 예를 들어 디코더의 t=2에서 [<sos>, I]라는 단어를 예측했다면 이는 t=3의 입력 문장이 될 것이다.
- 이러한 로직을 트랜스포머에서는 어떻게 구현을 했을까?
- 인코더의 입력 문장이 "나는 너를 좋아해" 라면 모델의 학습을 위해 타깃 문장 "I love you"가 존재할 것이다.
- 이 타깃문장은 디코더의 입력 문장이 된다.
입력 문장 | 타깃 문장 |
나는 너를 사랑해 | I love you |
- 이 입력 문장을 임베딩으로 변환한 후 위치 인코딩을 추가하여 디코더의 입력행렬 X로 만든다.
- 단어의 셀프 어텐션을 구하여야 하므로 Q(쿼리), K(키), V(벨류) 행렬을 생성한다. (생성 방법은 인코더와 같음)
- 마스킹 처리는 $\frac {QK^T}{\sqrt d_k}$ 계산을 한 이후에 이루어지게 된다.
- 각 타임 스텝 별 모델이 아직 예측하지 않은 단어들은 마스킹 처리하여 학습을 진행한다.
- 예를 들어 위 행렬의 첫 번째 행을 보자. <sos>의 다음 단어를 예측한다고 할 때 모델에서는 <sos> 오른쪽 있는 모든 단어를 참조하지 말아야 한다.(텍스트 생성 시 사용이 불가능하다)
- 그렇기 때문에 <sos> 오른쪽에 있는 모든 단어를 -∞로 바꾸어 마스킹을 수행한다.
- 이제 두 번째 행을 보면 'I'의 다음 단어를 예측하기 위해 모델에서는 'I' 오른쪽에 있는 단어를 참조하지 말아야 한다. 따라서 'I'오른쪽에 있는 모든 단어를 마스킹해 준다.
- Love 도 동일한 형태로 처리한다.
- 이제 이렇게 마스킹한 $\frac {QK^T}{\sqrt d_k}$ 행렬에 softmax() 함수를 적용하고, V(벨류) 행렬에 곱해 최종적으로 여러 개의 어텐션 행렬을 구한다(멀티 헤드 어텐션이므로).
- 여러개의 어텐션 행렬들을 concatenate 한 이후 가중치 행렬 W를 곱해 우리가 원하는 크기의 행렬로 생성한다.
- 이렇게 구한 어텐션 행렬 M을 다음 서브레이어에 있는 다른 형태의 멀티 헤드 어텐션의 입력값으로 넣는다.
1.3.2 멀티 헤드 어텐션(Multi-Head Attention)
- 다음은 인코더와 디코더를 결합한 트랜스포머의 모습이다.
- 이때 디코더의 멀티 헤드 어텐션을 보면 입력 데이터를 2개를 받는 것을 볼 수 있다.
- 하나는 Masked Multi-Head Attention 레이어의 출력값, 나머지 하나는 인코더의 출력값(표현 행렬)이다.
- 인코더의 표현값을 R, 이전 서브레이어인 마스크 된 멀티 헤드 어텐션의 결과로 나온 어텐션 행렬을 M이라고 한다.
- 여기서 인코더의 결과와 디코더의 결과 사이에 상호작용이 일어나므로 두 번째 레이어를 인코더-디코더 어텐션이라고도 부른다.
그럼 입력값이 두 개인 멀티헤드 어텐션은 어떻게 동작할까?
- 어텐션 행렬을 계산하기 위해 우선 Q,K, V 행렬을 생성해야 한다.
- 이전 서브 레이어(Masked Multi-Head Attention)의 출력값 M을 사용해 쿼리 행렬 Q를 생성하고, 인코더 표현 값인 R을 사용해 K,V 행렬을 생성한다. 현재 멀티 헤드 어텐션을 사용하므로 헤드 i를 기준으로 다음 절차를 따른다.
1. 어텐션 행렬 M에 가중치 $W^Q_i$를 곱해 쿼리 행렬 $Q_i$를 생성한다.
2. 인코더 표현값 R에 가중치 행렬 $W^K_i , W^V_i$를 각각 곱해 키, 벨류 행렬 $K_i, V_i$를 생성한다.
왜 Q,K,V행렬을 이렇게 생성하는 것일까?
- 일반적으로 쿼리 행렬은 타킷 문장의 표현을 포함하므로 타깃 문장에 대한 값인 M의 값을 참조한다.
- 키와 밸류 행렬은 입력 문장의 표현을 가져서 R의 값을 참조한다.
1. $QK^T$
- 행렬의 첫번째 행에서 쿼리벡터 $q_1$(<sos>)와 모든 키 벡터 $k_1(난), k_2(널), k_3(사랑해)$ 사이의 내적을 계산하여 첫번째 타깃 단어 <sos> 가 입력 문장의 모든 단어 (난 널 사랑해)와 얼마나 유사한지를 게산하는지로 해석할 수 있다.
- 즉 $QK^T$는 타킷 문장 표현 과 입력 문장 표현 간의 유사도를 계산한다.
2. $softmax(\frac {QK^T}{\sqrt d_k})$
- 이후 정규화 작업을 위해 $softmax(\frac {QK^T}{\sqrt d_k})$ 계산을 거치면 스코어 행렬을 얻을 수 있다.
3. $softmax(\frac {QK^T}{\sqrt d_k}) V$
- 그다음으로 스코어 행렬에 벨류 행렬을 콥하면 어텐션 행렬 Z를 얻을 수 있다.
- Z를 타겟 문장의 어텐션 행렬이라고 부른다.
- 또한 Z를 다음과 같이 표현할 수 있다.
- Z의 경우 각 스코어에 대한 가중치를 반영한 벡터값의 합으로 계산된다.
- 예를 들어 단어 'I', $z_2$의 셀프 벡터 값을 계산한다고 가정해 보자.
- 위 식을 보면 $z_2$ 의 98%는 $v_1(난)$, 나머지 2%는 $v_1(널)$dmf vhgkagksek.
- 즉 모델에서 타깃 단어 'I'가 입력 단어 '난'을 의미한다고 해석할 수 있다.
3. $concatenate(z_1,z_2,...,z_k)W_o$
- 이와 유사하게 k 개의 헤드에 대해 어텐션 행렬을 구한 후 이를 연결하고, 가중치 행렬 $W_o$를 곱하면 최종 어텐션 행렬을 얻을 수 있게 된다.
1.3.3~1.3.4 피드 포워드 네트워크 , add & norm
- 인코더-디코더 어텐션 레이어 다음의 서브 레이어는 피드 포워드 네트워크이다.
- 인코더-디코더 어텐션 레이어의 결괏값(어텐션 행렬)을 입력값으로 받아서 디코더의 표현으로 값을 출력한다.
- 이는 인코더의 피드 포워드 네트워크와 동일한 구조를 가지고 있다.
- add&norm도 인코더와 동일한 방식이다.
1.3.5 선형과 소프트맥스 레이어
- 디코더가 타깃 문장에 대한 표현을 학습시키면 최상위 디코더에서 얻은 출력 값을 Linear 및 softmax 레이어에 전달한다.
Linear Layer : 단순한 fully connected layer, 마지막 decoder block의 output vector를 logit vector로 반환
Softmax Layer : Linear에서 생산된 logit vector를 각 token이 위치할 확률로 바꿔줌.
- 최상위 디코더의 출력값을 가져와서 Linear layer, softmax layer를 거쳐 로짓 값을 확률 값으로 변환한 다음, 디코더에서 가장 높은 확률 값을 갖는 인덱스의 단어로 다음단어로 예측한다.
- 예를 들어 보자 디코더의 입력 단어가 <sos>, 'I'라고 할 때 디코더는 입력 단어를 보고 다음 단어를 예측한다.
- 이를 위해 디코더의 최상위 출력값을 가져와서 Linear Layer에 입력한다.
- 이 Linear Layer에서는 타겟 문장의 크기와 동일한 크기의 로짓 벡터를 생성한다.
- 이 로짓값이 다음과 같다고 가정해보자.
- 이 로짓값에 소프트맥스 함수를 적용하고 확률값을 얻는다.
- 이 확률 벡터 중 확률이 가장 높은 인덱스 1의 단어 love가 타깃 문장의 다음 단어로 선정될 것이다.
- 이런 방식으로 디코더는 다음 단어를 예측한다.
1.4 트랜스포머 학습
- 손실 함수값을 최소화하는 방향으로 트랜스포머 네트워크를 학습시킬 수 있다.
- 이때 트랜스포머에서는 무엇이 예측값으로, 실제값으로 지정해 주어야 할까?
- 앞에서 디코더가 vocab에 대한 확률 분포를 예측하고 확률이 가장 큰 단어를 다음 단어로 선택한다는 것을 배웠다.
- 즉, 올바른 문장을 생성하려면 예측 확률 분포와 실제 확률 분포 사이의 차이를 최소화해야 한다.
- 그러려면 두 분포의 차이를 알아야 하며, 교차 엔트로피(cross-entropy)를 사용하면 분포의 차이를 알 수 있다.
- 따라서 손실 함수를 교차 엔트로피 손실(cross-entropy loss)로 정의하고 예측 확률 분포와 실제 확률 분포를 최소화 하도록 모델을 학습한다.
- 이때 optimizer는 Adam을 사용한다.
- 그리고 모델의 과적합(overfitting)을 방지하기 위해 각 서브레이의 출력에 드롭아웃을 적용하고, 임베딩 및 위치 인코딩의 합을 구할 때도 드롭아웃을 적용해야 한다는 것이다.
1.5 Complexity
- 본 논문에서는 3가지의 목표를 두고 self-attention 을 고안해 냈다고 한다.
1. 각각의 레이어마다 계산 복잡도가 줄어든다.
2. 병렬적 처리가 가능하다.
3. 장기 의존성 문제 해결이 가능하다.
- 위 표는 self-attention, rnn, convolution 메커니즘을 각각 사용했을때에 대한 효율성을 분석한 표이다.
- 이때 n은 시퀀스의 길이(단어의 길이), d는 표현 차원(representation dimension)
- Sequential Operations 부분을 보면 self attention의 경우 한번의 병렬적 처리로 시퀀셜 데이터를 처리할 수 있지만, rnn의 경우 시퀀셜 데이터의 길이만큼 들어가게 된다. 이를 통해 rnn에 비해 네트워크에 들어가는 입력의 횟수가 적다는 것을 알 수 있다.
- Complexity per layer를 볼때에도 self-attention이 rnn에 비해 복잡도가 더 낮은 것을 볼 수 있다.
본 논문에서는 보통 시퀀스의 길이(n)이 d보다는 짧은 경우가 많다고 한다.
'책 정리 > 구글 Bert의 정석' 카테고리의 다른 글
5장. 지식 증류 기반 BERT 파생 모델 (0) | 2023.02.08 |
---|---|
4.4장-Span BERT (0) | 2023.02.01 |
4-3장. ELECTRA (1) | 2023.02.01 |
3장. BERT 활용하기 (2) | 2023.01.31 |
2장. BERT 시작하기 (0) | 2023.01.29 |