[NLP] 7. Transformer - Scaled Dot-Product Attention
Transformer: High level view
이전의 attention 모듈은 RNN 계열 기반의 seq2seq 모델의 추가적인 add-one 모듈로 사용되었지만,
순수하게 attention만으로 sequence를 입력/예측할 수 있는 모델 구조로 Transformer가 등장하였다.
(Attention is all you need)
RNN: Long-term dependency
위의 그림처럼 "I go home" 이라는 문장이 주어졌다고 가정하자.
RNN 모듈은 각 timestep마다 $x_{t}$와 $h_{t-1}$를 받아서 $h_{t}$를 만들어낸다.
또한 위의 그림에서처럼 처음 입력한 단어부터 그 이후에 등장하는 단어들에 대한 정보를 계속적으로 축적해가면서 $h_{1}$, $h_{2}$ 등의 hidden state vector를 인코딩한다.
마지막 timestep의 입력단어 "home"은 해당 timestep의 encoding hidden state vector의 주된 정보로 담겨지지만, "I"와 "go" 정보도 해당 hidden state vector에 담겨지는 것을 알 수 있다.
3번째 timestep의 hidden state vector가 멀리 떨어진 "I"라는 단어 정보를 포함하는 과정에서 RNN 모듈을 여러 timestep만큼 통과하게 된다.
RNN 모듈을 여러번 통과하는 과정에서 gradient vanishing/exploding이나 Long-term dependency같은 문제가 발생한다.
Bi-Directional RNNs
항상 정보가 왼쪽에서 오른쪽으로 흘러가는 RNN 모듈의 특성 상, 왼쪽에서 등장하는 단어는 오른쪽에서 등장하는 단어의 정보를 encoding vector에 담을 수 없다.
그래서 등장한 것이 sequence의 방향이 왼쪽→오른쪽인 Forward RNN, 오른쪽→왼쪽 방향으로 encoding하는 Backward RNN을 병렬적으로 구성하는 Bi-Directional RNN이다.
Forward RNN과 Backward RNN에 사용되는 모델의 파라미터는 서로 독립적이다.
Forward RNN에서 "go"의 왼쪽에 등장했던 단어들의 정보까지 encoding한 hidden state vector가 만들어지고
Backward RNN에서는 "go"의 오른쪽에 등장했던 단어들의 정보까지 encoding한 hidden state vector가 만들어진다.
만들어진 2개의 vector를 concat해줌으로써 "go"가 가지는 전체적인 sequence 정보를 반영한 encoding hidden state vector를 만들 수 있다.
Transformer: Long-term dependency
Transformer의 attention도 RNN이 주어진 sequence 내의 각 word별로 주변 단어 정보를 encoding한 hidden state vector를 생성해주는 것 처럼 입력과 출력은 동일함을 위의 그림을 통해 알 수 있다.
Transformer의 attention이 적용되는 방식은 seq2seq with attention에서 사용되는 방식과 비슷하다.
seq2seq with attention에서는 아래 설명과 같이 작동한다.
decoder의 특정 timestep의 hidden state vector는 우리가 찾고자하는 정보를 나타내는 벡터이고, 이 벡터와 Encoder 단에서 만들어진 hidden state vector들과 각각 내적을 수행해 유사도를 계산하고 softmax를 취해 확률값으로 만든다.
해당 확률값을 각각의 encoder의 hidden state vector에 가중치로서 적용해 가중평균을 내어 context 벡터를 만든다.
Transformer attention의 작동 방식을 크게 바라본다면,
"I"라는 단어의 encoding hidden state vector를 만드는 과정을 생각해보자.
"I" input 벡터가 seq2seq with attention의 decoder에서의 특정 timestep의 hidden state vector 역할인 우리가 찾고자 하는 정보를 나타내는 벡터로 볼 수 있다.
그리고 seq2seq with attention의 encoder의 hidden state 벡터 역할은 "I", "go", "home" input 벡터가 수행한다.
→ "I" 벡터가 자기 자신과 내적이 되고 2번째, 3번째 input 벡터와도 내적이 됨으로써 input 벡터 3개와의 내적에 기반한 유사도를 구하게 된다.→ 내적에 기반한 유사도에 softmax를 취해 얻어진 가중치를 input 벡터에 적용해 가중평균을 낸 결과를 "I"에 대한 encoding hidden state vector라고 생각할 수 있다!→ decoder의 hidden state vector와 encoder의 hidden state vector들 간의 구별 없이 동일한 set내의 벡터들로 계산을 할 수 있기에 self-attention 모듈이라고 부른다.
그러나 이렇게 계산하게 되면 자기 자신과 내적이 될 경우에는 유사도가 서로 다른 벡터와 내적했을 때 보다 훨씬 큰 값이 도출된다.
즉, 자기 자신에게 큰 가중치가 걸리는 양상이 나타날 것이고, encoding hidden state vector들도 자기 자신의 정보만을 주로 포함하는 벡터가 될 것이다.
그래서 attention 작동 과정을 좀 더 유연하게 확장하여 이러한 문제점을 개선하고자 하였다.
이번에는 Transformer attention의 작동방식을 자세하게 바라보자.
Transformer에서 제안된 self-attention 모듈은 동일한 set의 벡터에서 출발하더라도 각 역할에 따라 벡터가 서로 다른 형태(Query, Key, Value)로 변환될 수 있도록 하는 별도의 linear transformation matrix($W^{Q}$), ($W^{K}$), ($W^{V}$)가 정의되어 있다.
"I"라는 word를 encoding하는 경우, "I"에 해당하는 입력 벡터가 $W^{Q}$라는 matrix에 의해 query 벡터로 변환된다.
"I", "go", "home" 3개의 벡터들은 각각 $W^{K}$, $W^{V}$ matrix를 통해 Key, Value 벡터로 변환된다.
query 벡터와 각각의 key 벡터를 내적하고 key벡터의 dimension의 제곱근값으로 나누어준 뒤 softmax를 취해 상대적인 가중치들을 얻어낸다.
이 과정에서 "I"단어로 query벡터를 만들었고 첫번째 key벡터 역시 "I"로부터 만들어졌지만 내적값이 다른 key벡터와의 내적값보다 작을 수도 있게 된다. (-3.8 , -0.2, 5.9)
여기서 구해진 가중치 (0.2, 0.1, 0.7)는 value 벡터에 적용되고 이를 가중평균을 취해 최종적인 vector를 구할 수 있다.
즉, 이러한 방식으로 "I"라는 단어가 sequence 전체의 모든 단어들을 적절하게 고려한 형태로의 encoding hidden state vector를 계산할 수 있다.
행렬 연산의 관점에서도 살펴보자.
2개의 입력 단어 "Thinking"과 "Machines"가 주어져있다고 가정하자.
$X_{1}$과 $X_{2}$ 벡터를 concat한 $X$벡터의 첫번째 row가 $X_{1}$이고 두번째 row가 $X_{2}$이다.
"Thinking"단어의 query 벡터는 "Thinking"단어에 해당하는 4차원 벡터를 $W^{Q}$와 곱하면 구할 수 있다.
key 벡터와 value 벡터도 $W^{K}$, $W^{V}$ matrix와 곱하면 구할 수 있다.
sequence 길이가 굉장히 길다고 하더라도 self-attention 모듈을 적용해서 sequence 내 각각의 word 들을 encoding vector 로 만들게되면 그 tiemstep의 차이가 굉장히 크더라도 각각의 Key와 Value vector들로 변환되게 된다.
따라서 Query vector에 의한 내적에 의한 유사도가 높다면 timestep의 차이가 크더라도 멀리있었던 정보를 손쉽게 가져올 수 있다.
→ $t$ timestep에서 정보를 encoding하기 위해서 한참 이전의 정보를 갖고 오기 위해 timestep의 차이만큼 RNN 모듈을 통과함으로써 발생하는 정보의 손실 문제점을 근본적으로 개선한 형태의 모듈이라고 볼 수 있다.
→ Self-attention은 Long-Term dependency의 문제를 근본적으로 해결한 sequence encoding 기법이다.
Transformer: Scaled Dot-Product Attention
이번에는 Scaled Dot-Product Attention 모듈에 대해 살펴보자.
- 위 식에서의 attention module의 입력
- 하나의 query 벡터 $q$
- 쌍으로 이루어진 key 벡터와 value 벡터들
- Query 벡터와 Key 벡터는 내적 연산이 가능해야하기 때문에 같은 dimension의 벡터여야 한다.
- Value 벡터는 Query나 Key 벡터의 차원과 꼭 같지 않아도 된다.
$W^{V}$의 차원이 (4X5)로, Value 벡터의 dimension이 5차원이라고 하더라도각각의 5차원 벡터에 가중치 상수배를 곱해주어 가중평균을 내기때문에 query/key 벡터의 차원과 같지 않더라도 attention 모듈이 실행되는데는 아무 문제가 없다.
( 실제 Transformer 구현 상으로는 동일한 shape으로 mapping된 Q,K,V가 사용되어서 각 matrix의 shape는 모두 동일하다. )
위 식에서는 하나의 query 벡터를 입력으로 넣었지만, 이번에는 각각의 query 벡터를 row로 갖고 있는 matrix Q로 확장시켜서 생각해보자.
먼저 Query와 Key의 내적 연산을 진행한다.
첫번째 query와 key 벡터들과의 유사도는 결과 matrix의 첫번째 row에 형성되는 것을 알 수 있다.
n번째 query와 key q벡터들과의 유사도는 결과 matrix의 n번째 row에 형성될 것이다.
그렇게 구한 결과 matrix에 softmax연산을 취해주면 각각의 row 벡터들에 대해서 softmax 연산을 수행한다.
즉, 각 row별로 첫번째 query에 대한 2개의 key에 대한 가중치의 합이 1인 형태로 나오게 된다.
그렇게 나온 결과물에 Value matrix를 곱해준다.
위 그림과 같은 계산을 확장해보면 Value matrix의 row 벡터별로 각각의 가중치를 곱한 후 이를 더해서 만들어지는 벡터가 결과 matrix $Z$의 row 벡터를 형성한다는 것을 알 수 있다.
즉, 결과 matrix $Z$의 첫번째 row 벡터는 Query matrix의 첫번째 row벡터와 첫번째 query 벡터에 대한 attention 모듈의 output 벡터이다.
따라서 Query vector도 여러개가 주어져서 각각의 Query에 대한 attention 모듈의 output 을 계산해야하는 경우 각 query 벡터를 row vector로서 행렬을 구성한 후 행렬연산을 수행하면 각각의 Qeury vector에 대한 attention 모듈의 output vector를 한번에 계산할 수 있다.
→ 병렬적인 행렬연산이 가능하기 때문에 RNN 모델등에 비해 상대적으로 학습이 굉장히 빠르다.
이번에는 Query와 Key의 내적을 계산할 때 추가적으로 붙는 Scaling 과정인 $\sqrt{d}_k$로 내적값을 나누어주는 부분에 대해 이해해보자.
$QK^T$의 각각의 element들은 특정 Query 벡터 하나와 Key vector의 내적의 형태로 계산이 된다.
만약 2차원의 Query 벡터와 Key 벡터가 있다고 가정하자.
그리고 query 벡터와 key 벡터의 각각의 원소값(a,b,x,y)이 통계적으로 독립인 평균 0, 분산 1인 형태라고 가정하자.
$ax$, $by$의 각각의 평균이 모두 0이기 때문에 내적 값의 평균도 0이 된다.
통계적으로 독립이면서 분산이 1인 $a$와 $x$가 곱해지면 $ax$에 대한 분산도 1이된다.
$by$역시 마찬가지로 분산이 1이 된다.
→ 각각 분산이 1인 형태가 더해지면 최종적인 분산은 2가 된다.
그렇다면 이번에는 100차원으로 구성된 벡터의 내적을 수행한다고 생각해보자.
이 때는 최종적으로 분산도 100이 된다.
이 값을 softmax를 취해주게 되면 분산이 클수록 softmax의 확률분포가 큰 값에 몰리게 되는 패턴이 나타난다.
거꾸로 분산이 작은 경우에는 확률분포가 좀 더 고르게 나타난다.
→ 내적 계산이 이루어지는 Query 벡터와 Key 벡터의 dimension이 몇인지에 따라 softmax의 확률 분포가 의도치않게 Query/Key dimension의 영향을 받게 된다.
따라서 이 내적값에 분산을 일정하게 유지시켜 학습을 안정화하기 위해 내적값에 $\sqrt{d}_k$값으로 나누어주는 연산을 추가적으로 진행한다.
만약 의도치 않게 차원을 크게 설정하고 scaling없이 attention 모듈을 수행했을 때 종종 학습이 전혀 되지 않는 상황이 발생한다. 즉, softmax의 값이 한쪽으로 굉장히 몰리게 되는 경우에는 학습을 진행할 때 gradient vanishing 이 잘 발생될 수 있는 위험성이 있다.
→ softmax의 output을 적절한 정도의 범위로 조절하는 것이 학습에도 실제로 중요한 역할을 한다.