아는 만큼 보인다

Transformer 모델 개념적으로 접근하기 / 디테일하게 파헤치기 본문

머신러닝&딥러닝

Transformer 모델 개념적으로 접근하기 / 디테일하게 파헤치기

계토 2023. 12. 15. 18:01

본 글에서는 transformer 논문을 하나하나 따라간다거나, attention 계산 방식을 나열하는 것 대신, 보다 개념적인 부분을 다루고 뒷쪽으로 갈수록 디테일한 부분에 대한 질문을 던지며 나아가 보고자 한다. Transformer를 다룬 다른 블로그글을 보고 부분 부분은 다 이해를 했지만 전반적으로 정리를 하고 싶은 사람에게 도움이 더 될 것 같다. 개인적으로는 면접 준비를 할 때 이렇게 정리한 게 도움이 되었다. 한가지 팁이 있다면, transformer 구조가 무조건 머리속에 잘 정리되어 있어야 하고 구성요소를 순서대로 잘 설명할 수 있으면 더 좋다.

 

참고로 차근차근 구현 방법에 대해 이해하고 싶다면 이 블로그를 추천한다.

Transformer model

Attention mechanism만을 활용하여 전체 문장을 한번에 처리함으로써, 기존 RNN 기반의 encoder-decoder 모델에서는 불가능했던 병렬처리를 가능하게 하여 학습 효율을 높인 모델이다. 또한, self-attention을 활용하여 문장 전체 맥락을 단어와의 연관성에 따라 단어에 한 번에 담을 수 있도록 해서, 긴 문장에서 먼 거리에 있는 단어 간의 관계도 효과적으로 담을 수 있게 한 모델이다.

어떻게 RNN을 성공적으로 제거할 수 있었을까? 

기존 RNN 기반 encoder-decoder 모델에서는 병렬화가 불가능해 학습/추론이 느렸고, long-distance dependency 문제가 있어 문장 뒤쪽으로 갈 수록 앞의 정보가 소실되는 문제가 있었다.

  1. Attention 개념을 기반으로 모델이 문장 내의 특정 부분에 집중할 수 있게 하였고
  2. Positional encoding을 이용해 문장의 순차적이고 상대적인 위치 정보를 보존할 수 있게 하여 RNN의 장점인 '순소 정보 활용'에 대한 부분을 대체하였다.
  3. 또한 decoder에서는 masking을 이용해 이전 시점의 값만이 이후에 영향을 미치도록 제한해, teacher forcing도 가능하게 했다.
  4. 이 과정을 행렬 연산으로 한번에 가능하게 하여, 학습 효율까지 챙겼다.

구성 요소들

Input embedding

단어를 embedding으로 mapping하는 첫번째 단계. 이 부분도 학습시킨다.

  • 이 때 코드를 자세히 보면, embedding 만들고 square root of d_embed를 곱해준다. 왜일까?
    • 정확한 이유가 논문에 있지는 않지만, 여러 자료들을 검색하고 종합해봤을 때, (아마도) positional embedding (-1, 1) 을 더해주기 전에 input embedding의 값을 키워주어 positional embedding의 영향력을 조금 줄여주기 위함이라는 견해가 많았다. 

Positional encoding

  • 목적: input을 순서대로 처리하여 순서를 자연스럽게 반영할 수 있는 RNN을 없애는 대신, 모델에 위치/순서에 대한 정보를 주기 위해 사용된다.
  • 각 token의 위치 정보가 담긴 고정된 길이의 벡터로, 인풋에 '더해진다'.
  • '상대적인' 위치 정보를 부여한다.
  • 장점: 다양한 길이의 문장을 다룰 수 있다. 학습 데이터 중 가장 긴 문장보다 더 긴 문장이 추론에서 들어와도, 에러 없이 상대적인 위치 값을 줄 수 있다. 
  • 원래 논문에서 사용된 sin과 cos을 활용한 positional encoding의 장점: 값이 항상 -1, 1 사이로 나와 positional encoding의 영향력을 제한할 수 있다.
  • 구현상 유의할 점: 학습되지 않으므로 train=False 혹은 requires_grad=False 등의 argument를 넣어주어야 한다.

Encoder와 Decoder의 역할

Encoder는 input을 받아서 각 단어에 대한 문맥 관련 내용을 담은 일종의 context vector (문맥 벡터)를 생성한다. 즉 '문맥화된 단어'를 내뱉는다. Decoder는 encoder의 output (context vector)와 input sentence (masked) 를 받아서 output sentence를 만드는 데에 기여한다.

Attention (self-attention 중심)

문장의 각 단어를 '문맥화'한 것이 핵심이다. 즉, 모델이 한 단어를 파악하기 위해 문장 전체에서 어떤 부분을 참고해야 하는지 알 수 있게 하였다. 모든 토큰들 사이의 관계를 직접 구해서 특정 단어를 기준으로 순서/거리에 관계없이 모두 정보를 반영할 수 있게 하였고, 긴 문장에서 앞의 내용이 소실되는 문제까지 해결할 수 있게 되었다.

 

query (관심이 있는 token, 문맥화하려는 token), key (참조하고 싶은 문장/token)의 dot product를 계산하여 관심 있는 토큰 query와 참고 대상인 key의 단어들 사이에 유사도/연관성(attention score)을 구한다. 이 연관성을 바탕으로, value (key와 같은 참고 대상 문장/token)를 곱해 '가중합'을 계산하면, 문장 내의 모든 단어 정보가 해당 단어(query)와의 '유사도'에 따라 적절하게 포함되어 context vector가 만들어진다. 즉 문장 속에서 그 단어가 가진 의미를 표상하고 있는 문맥 벡터가 나오는 것이다.

 

최종 문맥 벡터에는 자기 자신에 대한 정보(대부분) + 나머지 단어들의 정보가 중요도에 따라 조금씩 섞여서 들어온다. 이러한 방식으로 문맥을 포함한 벡터가 탄생한다.

 

이 부분에 대해 보다 시각적으로 이해하고 싶다면 유튜브 동영상을 추천한다(링크)

 

  • 구현상 유의할 점: query, key의 dot product를 계산한 후 square root of d_k (dimension of key)로 나누어준다(scaling). 
    • 이유: 학습 과정에서 gradient를 안정화시키기 위함이다. d_k가 커질수록 dot product 값이 커지므로 scaling을 통해 값이 너무 커지지 않게 한다. 이후 softmax가 취해져야 하는데 softmax의 입력값의 크기가 너무 크면 backpropagation시 그 입력에 대한 softmax의 gradient 값이 매우 작아지는 gradient vanishing의 위험성이 있기 때문에 이러한 작업을 해준다.
  • Multi-head attention이 필요한 이유: 복잡한 문장에서 각 head가 다양한 곳에 각각 집중할 수 있게 함으로써 다른 관점에서 attention을 수집하기 위함이다. 복잡한 패턴도 잘 학습할 수 있게 된다.
  • Masked multi-head attention layer in decoder: 특정 자리의 token을 생성해낼 때 그 앞의 token들 사이에서만 attention을 계산하여 뒤의 내용을 훔쳐볼 수 없게 한다. teacher-forcing
  • Cross multi-head attention layer in decoder: input sentence를 query, encoder output을 key & value로 사용하여, decoder가 문장을 만들 때 encoder로부터 온 기존 문장의 '문맥/의미'를 참고할 수 있도록 한다.

Position-wise feed-forward layer의 존재 이유

Non-linearity를 더해주어 더 목잡한 mapping을 배울 수 있게 한다. Attention이 다른 단어 간 글로벌한 정보를 반영한다면 이 layer는 각 단어에 각각 적용되어 단어 내 dimensiond에서 로컬 패턴을 본다. FC가 각 위치에 적용되어 이러한 이름이 붙었다.

여러 masking

  • Pad masking: 문장 길이는 다 다르지만 한 배치 안에서는 길이가 같아야 한다. 그래서 짧은 경우 padding을 해주고 attention 계산 시 (softmax 계산 전) 해당 부분은 masking 해주어 학습에 영향이 없도록 한다.
  • Subsequent masking: masked multi-head attention layer에 적용되는 masking. 뒤의 token을 보이지 않게 처리.
  • 구현상 유의할 점: positional encoding과 마찬가지로 '학습되지 않는다'

마지막 단계의 generator

Decoder 다음 단계로 generator가 붙어 있다. Decoder의 output은 n_batch x sequence_length x d_embed 이지만, generator거치면 n_batch, sequence_length, len(vocab)이 된다. 즉 전체 vocabulary내의 단어들에 대한 '확률값'을 얻을 수 있게 되어, 특정 자리에 올 확률이 가장 높은 단어들을 선택해서 최종 output을 만들 수 있다.

Residual connection, Layer normalization

각 block은 residual connection으로 구현되어 있고, layer normalization이 사용된다. 

  • 왜 transformer에서는 layer normalization을 사용할까?
    • 일반적으로 RNN 등 language model에서 layer normalization을 많이 사용한다. 
    • batch normalization을 하게 되면 한 batch 에 있는 모든 문장들의 각 feature에 대해 평균과 분산을 구하게 되고 time step 마다 이 파라미터가 계산되어 더 긴 문장이 추론에 들어오면 대응할 수 없다.
    • layer normalization은 한 문장 내에서 모든 피쳐에 대해 평균과 분산을 구한다. '문장'마다 구하게 된다. time step 별로 구하는게 아니므로 변하는 길이에 더 잘 대응할 수 있다. 

 

개인적으로 공부하고 찾아보며 정리한 내용이라 틀릴 수 있습니다. 틀린 내용이 있다면 마음껏 댓글 달아주시기 바랍니다.