'[딥러닝 기계 번역] Transformer: Attention Is All You Need (꼼꼼한 딥러닝 논문 리뷰와 코드 실습)'를 보고 작성한 글입니다. 😀
Transformer : Attention Is All You Need (NIPS 2017)
Transformer 논문에서는 RNN을 사용하지 않고 오직 Attention 기법만으로 아키텍쳐를 설계하여 성능을 개선한다.
인코더
단어가 입력될 때 마다 단어들의 정보를 저장하는 hidden state 값을 갱신한다.
hidden state 값은 이전까지 입력되었던 단어들의 정보를 저장하기 때문에 마지막 단어가 입력되었을 때 hidden state 값은 source 문장 전체를 대표하는 하나의 context vector로 사용할 수 있다.
즉, context vector는 source 문장에 대한 문맥적인 정보를 담고 있다고 가정한다.
디코더
매번 출력 단어가 들어올 때 마다 context vector 로 부터 hidden state를 만들어 출력을 내보낸다.
그 다음 단계에서는 이전에 출력했던 단어를 다시 입력으로 들어와서 반복적으로 이전까지 출력했던 단어에 대한 정보를 가지고 있는 hidden state와 같이 입력받아 새롭게 hidden state를 갱신한다.
즉, 디코더에서는 hidden state 값을 매번 갱신하고, hidden state 값을 통해 출력이 eos(End of sequence)가 나올 때 까지 반복한다.
한계점
Source 문장을 대표하는 하나의 context vector를 만들어야하기 때문에 고정된 크기의 context vector에 정보를 압축하려고 하면 source 문장의 길이가 길수도 있고 짧을수도 있기 때문에 이러한 다양한 경우에 대해 항상 source 문장의 정보를 고정된 크기로 가지는 것은 전체 성능의 병목현상의 원인이 될 수 있다.
개선 방법 (RNN 이용)
고정된 크기의 context vector를 매번 디코더의 RNN 셀에서 참고하도록 만들어 성능을 개선할 수 있다.
Context vector에 대한 정보가 디코더의 RNN 을 거치면서 정보의 손실 정도를 줄일 수 있기 때문에 출력되는 문장이 길어져도 각각의 출력되는 단어에 이러한 context vector에 대한 정보를 다시 한번 넣어줄 수 있기 때문에 성능이 향상될 수 있다.
한계점
여전히 source 문장을 하나의 고정된 크기의 context vector에 압축해야한다는 것은 동일하기 때문에 병목현상은 여전히 발생한다.
인코더
인코더는 매번 단어가 출력되어 hidden state가 나올 때 마다 별도의 배열에 모두 저장한다.
디코더
디코더는 현재 단계의 hidden state를 만들 때 바로 이전의 hidden state 값을 이용해서 출력단의 hidden state 값과 소스 문장단의 hidden state 값을 서로 묶어 별도의 행렬곱을 수행해서 각각 에너지 값을 만든다. 이 때 에너지 값은 내가 현재 어떠한 단어를 출력하기 위해서 소스 문장에서 어떠한 단어에 초점을 둘 필요가 있는 지를 수치화한 값이다. 그래서 에너지 값에 softmax를 취해서 확률 값을 구한 뒤에 소스 문장의 각각의 hidden state 값에 대해서 어떤 vector에 더 많은 가중치를 두어서 참고하면 좋을지를 반영한다. 그리고 모든 가중치 값을 hidden state 곱한 것을 각각의 비율에 맞게 더하여 구한 w(weighted sum vector) 값을 매번 출력 단어를 만들기 위해서 반영한다.
즉, context vector 만 고려하는 것이 아닌 소스 문장에서 출력되었던 모든 hidden state 값들을 전부 반영해서 소스 문장의 단어들 중에 어떠한 단어에 주의 집중해서 출력 단어를 만들 것인지 모델이 고려하여 성능을 높일 수 있다.
에너지 : 소스 문장에서 나왔던 모든 출력값들 중에서 어떤 값과 가장 연관성이 있는지를 구하기 위한 수치를 계산한다.
- 디코더 파트에서 이전에 출력했던 정보와 인코더의 모든 출력값을 비교하여 계산한다.
- 모든 j를 고려 즉, 매번 출력 단어를 만들 때 마다 모든 인코더의 모든 출력을 고려한다.
- s는 디코더가 이전에 출력한 단어를 만들기 위해 사용했던 hidden state이다.
- h는 인코더의 모든 hidden state이다.
- 즉, 어떤 h값과 가장 많은 연관성을 가지는지를 에너지 값을 통해 구할 수 있다.
가중치 : 에너지 값들에 softmax를 취해 확률값을 계산한다.
매번 디코더에서 각각의 단어를 만들기 위해 hidden state를 이용한다. 이 때, 입력으로는 디코더의 이전에 사용한 hidden state 값과 에너지와 가중치를 통해 구한 context vector를 사용한다.
인코더의 모든 각각의 hidden state 값을 같이 묶어 에너지 값을 구한 뒤에 softmax를 취해서 비율값(가중치)을 구할 수 있다. 이러한 hidden state 값을 Weighted sum을 통해 context vector처럼 사용할 수 있다.
Attention의 가중치를 통해 시각화가 가능하다는 장점이 있다.
RNN과 CNN 등을 사용하지 않고 Attention만을 통해 아키텍쳐를 구현한다.
문장 내 각각의 단어들의 순서에 대한 정보를 알려주기 위해 Positional Encoding을 사용한다.
인코더가 여러번(N번) 중첩한다. 즉, Attention 과정을 여러 레이에서 반복한다.
일반적으로 어떠한 단어 정보를 네트워크에 넣기 위해서는 임베딩을 수행한다.
입력 차원은 특정 언어에서 존재할 수 있는 단어 개수와 같기 때문에 차원이 많을 수 있고, 각각의 정보들은 one-hot encoding 형태로 표현이 되기 때문에 네트워크에 넣기 전에는 임베딩 과정을 거쳐서 더욱더 작은 차원의 continous한 값(실수)으로 표현한다.
즉, "I am a teacher" 문장이 들어오면 Input Embedding Matrix를 만든다. 이 때, 행은 단어의 개수와 갖고 열은 embedding 차원과 같은 배열의 크기를 가진다. Embedding 차원은 모델을 만드는 사람이 임의로 결정한다(본 논문의 경우 512 사용).
Seq2Seq와 같이 RNN을 사용할 경우 각각의 단어가 RNN에 들어갈 때 순서에 맞게 들어가기 때문에 자동으로 각각의 hidden state 값은 순서에 대한 정보를 가지게 된다.
Transformer와 같이 RNN을 사용하지 않는 경우 위치에 대한 정보를 주기 위해서 임베딩을 사용해야 한다. 이를 위해 transformer에서는 Positional Encoding을 사용한다.
Attention이 받는 값은 입력 문장에 대한 정보인 Input Embedding Matrix와 위치에 대한 정보인 Positional Encoding 이 포함된 입력값이다.
인코더에서 진행하는 attention은 self-attention으로 각각의 단어가 서로의 attention score를 구해 어떤 단어와 높은 연관성을 가지는지에 대한 정보를 학습할 수 있다.
즉, attention은 전반적인 입력 문장에 대한 문맥에 대한 정보를 학습시킨다.
추가적으로 성능 향상을 위해 잔여 학습(Residual Learning)을 사용한다.
Residual Learning은 대표적인 이미지 분류 네트워크인 ResNet에서 사용하는 기법으로 어떠한 값을 레이어를 거쳐서 반복적으로 갱신하는 것이 아니라 특정 레이어를 건너 뛰어서 복사된 값을 그대로 넣어주는 기법을 의미한다.
특정 레이어를 건너 뛰어서 입력할 수 있도록 만드는 것을 일반적으로 Residual Connection이라 한다.
Residual Learning을 통해 전체 네트워크는 기존 정보를 입력받고 추가적으로 잔여된 부분만 학습하도록 만들기 때문에 전반적으로 학습 난이도가 낮아 초기 모델 수렴 속도가 높아져서 global optimal을 찾을 확률이 높아진다.
그러므로 다양한 네트워크에서 Residual Learning을 사용했을 때 일반적으로 성능이 좋아진다. Transformer 또한 그러한 아이디어를 채택하여 성능을 개선한다.
인코더의 동작 원리는 Attention을 수행하여 나온 값과 Residual Connection을 이용해서 바로 더해진 값을 같이 받아 Normalization까지 수행한 뒤에 그 결과를 내보낸다.
인코더의 동작 원리 : 실제로 입력값이 들어오면 Attention을 거치고, Residual Connection 이후에 Normalization을 하고, 그 다음에 Feedforward Layer를 거치고, 마찬가지로 Residual Connection 이후에 Normalization을 추가한다. 결과적으로 하나의 인코더 Layer에서 그 결과값을 뽑아낼 수 있다.
즉, 어텐션(Attention)과 정규화(Normalization)을 반복하는 방식으로 여러개의 레이어를 중첩해서 사용한다.
각각의 레이어는 서로 다른 파라미터를 가진다. 예를 들어, 레이어 1,2에 포함된 어텐션 및 Feedforward Layer에 포함된 파라미터는 서로 다르다.
레이어를 중첩해서 사용하기 때문에 입력값과 출력값의 차원(dimension)은 같다.
입력값이 들어오면 여러개의 인코딩 레이어를 반복해서 마지막 인코딩에서 나온 출력값은 디코더에 들어간다. Seq2Seq 모델의 어텐션 메커니즘을 사용했을 때와 마찬가지로 디코더는 매번 출력할 때 마다 입력 소스 문장 중에서 어떤 단어에 가장 많은 초점을 둘 지 알려주기 때문이다.
디코더 또한 여러개의 레이어로 구성되고 마지막 레이어에서 나오게 된 출력값이 번역을 수행한 결과인 출력 단어이고, 각각의 레이어는 인코더의 마지막 레이어에서 나오게된 출력값을 입력으로 받는다.
디코더의 동작 원리
- 단어 정보인 Ouput Embedding Matrix와 단어의 상대적인 위치 정보인 Positional Encoding을 추가한 뒤에 입력을 넣게된다.
- 첫번째 어텐션은 self-attention으로 각각의 단어들이 서로가 서로에게 어떠한 가중치를 가지는지 구하여서 출력되고 있는 문장에 대한 전반적인 표현을 학습할 수 있도록 한다.
- 두번째 어텐션은 인코더의 출력 정보를 받아 가각의 출력되고 있는 단어가 소스 문장에 어떠한 단어와 연관성이 있는지를 구한다.
- 디코더의 두번 째 Multi-head Attention 레이어는 '인코더 디코더 어텐션'이라고 부른다.
- 디코더의 입력 차원과 출력 차원을 같도록 만들어 각각의 디코더 레이어는 여러번 중첩되어 사용할 수 있다.
일반적으로 레이어 개수는 인코더와 디코더가 동일하도록 맞추는 경우가 많다.
인코더의 마지막 레이어의 출력값이 각각의 디코더 레이어에 입력된다.
RNN 및 LSTM을 사용할 때는 고정된 크기를 사용하고 입력 단어의 개수만큼 반복적으로 encoding layer를 거쳐서 매번 hidden state를 만든다.
Transformer를 사용할 때는 입력 단어 자체가 하나로 연결되어 한번에 입력되고 한번에 그에 대한 Attention 값을 구한다.
즉, RNN을 사용했을 때와는 다르게 위치 정보를 한번에 넣어 한번의 인코더를 거칠 때 마다 병렬적으로 출력값을 구할 수 있기 때문에 RNN을 사용했을 때 보다 일반적으로 계산 복잡도가 낮게 형성된다.
또한, 실제로 학습을 수행할 때는 입력값 전부를 넣을 수 있기 때문에 RNN을 사용하지 않고 학습을 진행할 수 있다는 장점이 있다. 하지만, 실제 모델에서 출력값을 내보낼 때는 디코더 아키텍쳐를 여러번 사용해서 <eos>가 나올 때 까지 반복하도록 만들어서 출력값을 구하도록 한다.
따라서, 중간에 context vecor로 압축하는 과정이 생략이 되기 때문에 네트워크 자체에서 LSTM과 RNN 구조를 사용할 필요가 없다는 장점이 있다.
Multi-head Attention은 중간에 Scaled Dot-Product Attention이 사용된다.
Scaled Dot-Product Attention
쿼리(Query) : 물어보는 주체, 키(Key) : 물어보는 대상, 값(Value)
물어보는 주체인 쿼리와 어텐션을 수행할 단어들이 키로 들어가 행렬곱(MatMul)을 수행한 뒤 스케일링을 하고, 필요에 따라 마스킹까지 진행한다. 그 후 SoftMax를 취해 어떠한 단어와 가장 높은 연광성을 가지는지 확률(비율)값을 구한다. 구해진 확률값과 Value값을 곱해 가중치가 적용된 결과적인 Attention Value를 구할 수 있다.
즉, 어떠한 단어가 다른 단어들에 대해 어떠한 가중치 값을 가지는지 구할 때 각각의 키에 대해 Attention Score를 구한다. 해당 Attention Score와 Value 값들을 곱해 Attention Value를 구한다.
Multi-head Attention에 입력값이 들어올 때 입력값들은 h개로 구분된다. 즉, 어떠한 입력 문장이 들어왔을 때 그것은 Value, Key, Query로 분류되며 이 때 h개의 서로 다른 Value, Key, Query로 구분될 수 있도록 만든다. 이렇게 하는 이유는 h개의 서로 다른 attention concept을 학습하도록 만들어서 더욱더 구분된 다양한 특징들을 학습하도록 유도할 수 있다는 장점이 있다.
입력으로 들어온 값은 3개로 복제되어 각각 Value, Key, Query로 들어가게 되고 이러한 Value, Key, Query 값들은 Linear 레이어를 통해 행렬곱을 수행하여 h개의 구분된 각각의 쿼리쌍들을 만들게 되고, 이 때 h는 head의 개수이기 때문에 각각 서로 다른 head 끼리 Value, Key, Query 쌍을 받아 Attention을 수행하여 결과값을 내보낸다. 입력값과 최종 출력값의 차원은 같아야하므로 Attention값들을 Concat을 통해 1차원으로 붙인 다음에 Linear 레이어를 거쳐 output값을 내보내게 된다.
Multi-head Attention은 전체 아키텍쳐(인코더, 디코더)에서 동일한 함수로 작동한다. 다른점은 사용되는 위치 마다 Value, Key, Query를 어떻게 사용할지가 달라질 수 있지만 기본적인 Attention 레이어의 동작 방식은 같다. 예를 들어, '인코더 디코더 어텐션'에서는 출력 단어가 Query가 되고, 각각의 출력 단어를 만들기 위해서 인코더의 어떠한 단어를 참고하면 좋은지 구하기 위해서 Key와 Value는 인코더의 출력값을 사용한다. 다시 말해, 각각의 단어를 출력하기 위해 어떠한 단어를 참고할지 인코더에게 물어보는 것이기 때문에 디코더에 있는 단어가 Query가 되고 인코더에 있는 각각의 값들이 Key와 Value가 될 수 있다.
Attention
- 하나의 어텐션은 Query, Key, Value를 받는다.
- 이 때, Query와 Key를 곱해 각 Query에 대해 각각의 Key에 대한 에너지 값을 구할 수 있다.
- 해당 에너지값을 softmax를 통해 확률값으로 나타내어 어떤 Key에 대해 높은 가중치를 가지는지 계산할 수 있다.
- Sclae Factor로 루트dk를 사용한다. 이 때 dk는 각각의 Key dimension이 된다. 이렇게 특정한 스케일로 나눠주는 이유는 softmax 함수가 0 근처에서는 gradient가 높게 형성되지만 값이 들쭉날쭉 조금씩 왼쪽 오른쪽으로 이동하면 기울기 값이 많이 줄어들기 때문에 gradient vanishing 문제를 피하기 위한 방법으로 이러한 Sclae Factor를 이용한다.
- 결과적으로 각각의 Query가 각각의 Key에 대해 어떠한 가중치를 가지는지 Score값을 구한 뒤에 Value값과 곱해 Attention Value를 구한다.
head
- 입력으로 들어오는 각각의 값에 대해 서로 다른 Linear Layer를 거치게 만들어 h개의 서로 다른 각각 Query, Key, Value값을 만들 수 있도록 한다.
- h개의 서로 다른 concept을 네트워크가 구분하여 학습하도록 만들어서 Attention을 수행하기 위한 다양한 Feature들을 학습하도록 만든다.
MultiHead
- 각 Head에 대한 출력값을 Concat함수를 통해 일자로 쭉 붙이고 마지막으로 Output Matrix와 곱해 결과적인 Multi-Head Attention 값을 구한다.
매번 입력값이 들어왔을 때 Query, Key, Value로 각각 들어가게 되고, 나올 때는 입력으로 들어왔던 차원과 동일한 차원을 가지기 때문에 이러한 Multi-Head Attention 레이어가 포함된 하나의 인코더 혹은 디코더 레이어는 중첩되서 사용될 수 있다.
해당 예제는 4차원의 임베딩 차원을 2개의 Head를 통해 2차원으로 표현한다.
Query는 각각의 다른 단어의 Key와 행렬곱을 통해 하나의 에너지 값을 구한다.
softmax에 들어가는 값을 정규화(Normalization)하기 위해 Sclae Factor로 나누어 준다.
그 후, softmax를 취해 실제로 각각의 Key에 대한 가중치를 구한다.
각각의 가중치와 Value값들을 곱하고 더한 뒤에 결과적인 Attention Value값을 구한다. (즉, Weighted Sum을 구하는 것)
단어는 3개, 임베딩 차원은 4, Head는 2개
모든 단어에 대한 Query값들을 한번에 각 Key값들과 곱하여 Attention Energies값을 구한다.
이 때, Attention Energies값은 각각의 단어가 각각의 Key값에 대해서 얼마나 높은 연관성을 표현하는 수치를 부여했는지 확인할 수 있다.
Attention Energies에 Softmax를 취해 각각의 행 마다 각 Key에 대한 확률값들을 계산하여 가중치를 구한다. 해당 가중치값들과 Value값들을 곱해 실제 Attention Value Matrix를 구할 수 있다.
Attention Value값 자체는 입력되었던 Query, Key, Value와 동일한 차원을 가진다.
마스크 행렬(mask matrix)를 통해 Attention Energies값에 마스크를 적용함으로서 특정 단어는 무시해서 Attention을 수행하지 않도록 만들 수 있다.
각각의 Head는 입력으로 들어온 Query, Key, Value와 같은 차원의 벡터를 만들기 때문에 각 Head 마다 Query, Key, Value값들을 각각 넣어서 Attention을 수행하는 값들을 Concat을 통해 하나로 만들어 다시 맨 처음 입력되었던 입력 차원과 같은 차원을 가지게 된다.
Multi-head Attention은 각각의 Head에 대해서 Attention을 수행한 뒤에 concat함수를 통해 결과를 쭉 이어서 붙이기 때문에 결과적으로 만들어진 행렬(Matrix)의 열의 개수는 원래 입력의 임베딩 차원과 동일하기 때문에 마지막으로 W가중치 값으로 dmodel x dmodel 차원을 가지는 행렬을 곱해서 Multi-head Attention의 값을 구할 수 있다.
Encoder Self-Attention
각각의 단어가 서로에게 어떠한 연관성을 가지는지를 Attention을 통해 구한다.
전체 문장에 대한 representation을 Learning할 수 있도록 하는것이 특징이다.
Masked Decoder Self-Attention
각각의 출력 단어가 다른 모든 출력 단어를 모두 참고하지 않고 앞쪽에 등장했던 단어들만 참고한다.
Encoder-Decoder Attention
Query가 Decoder에 있고, 각각의 Key와 Value는 인코더에 있다.
시각화 과정을 통해 Attention Score값을 그릴 수 있다.
하나의 문장에 포함된 각각의 단어들에 대한 상대적인 위치 정보를 모델에게 알려주기 위해 주기 함수를 활용한 공식을 사용한다.
네트워크가 각각의 입력 문장에 포함되어 있는 각 단어들의 상대적인 위치 정보를 알 수 있도록 이러한 주기성을 학습할 수 있도록 한다면 어떠한 함수도 사용이 가능하다. 즉, sin과 cos 함수가 아닌 다른 함수를 사용해도 된다. 그래서 Transformer 이후의 다양한 아키텍쳐에서는 위와 같은 주기 함수가 아닌 학습이 가능한 형태인 별도의 임베딩 레이어를 사용하기도 한다.
n과 dimension에 대해서 실제로 어떤식으로 각 단어의 위치에 대한 인코딩 정보가 들어가는지 그림으로 표현한다.
References
🏋🏻 개인적으로 공부한 내용을 기록하고 있습니다.
잘못된 부분이 있다면 과감하게 지적해주세요!! 🏋
'추천 시스템 > Paper' 카테고리의 다른 글
[논문 리뷰] Neural Collaborative Filtering (2017) (0) | 2021.11.25 |
---|---|
[논문 리뷰] Factorization Machines (0) | 2021.11.24 |
[논문 리뷰] Next Item Recommendation with Self-Attentive Metric Learning (0) | 2021.11.24 |
댓글