본문 바로가기
  • 비둘기다
  • 비둘기다
  • 비둘기다
군대시리즈/Paper Review

논문 리뷰) Attention is all you need : 트랜스포머의 아키텍처 ①

by parzival56 2025. 1. 23.

논문 다운로드 : https://arxiv.org/pdf/1706.03762

대학 생활을 하며 계속 CV위주로 공부와 활동들을 많이 해왔는데 문득 자연어 처리의 기본 이상은 해야겠다고 느꼈습니다. 요즘 AI프로그램들을 보면 인공지능의 각 분야들을 융합하여 사용하는 경우가 다반수입니다. 예를 들어 사진을 찍어 사진 안에 텍스트를 분석하려면 이미지를 처리하고, 텍스트를 분리한 뒤 텍스트 또한 처리해야 합니다.
이전에는 한 가지 분야를 파면 좋은 결과가 있는 경우도 많았지만 요즘은 마냥 그렇진 않은 것 같습니다. 그래서 이번을 계기로 자연어 처리에 손을 대보게 되었는데 생각보다 웹서핑만으로는 쉽지 않아 참고 서적을 구매하여 공부를 진행하였습니다.

교재 : 트랜스포머로 시작하는 자연어 처리 / Denis Rothman 저 / 영진 닷컴


위 교재를 선택한 이유라면 먼저 가장 최근에 개정된 책이기도 하고, 트랜스포머부터 Bert, GPT까지의 변화를 한 권에 설명한 책이 많지 않을 것 같아 선정했습니다.

아마 많은 분들이 자연어 처리를 시작할 때 트랜스포머부터 시작하실 거라 생각합니다. (아님말구요)
그럼 자연어 처리가 무엇인지부터 알아야 하는데 자연어 처리라 함은 요약하면 컴퓨터의 방식으로 인간이 언어를 이해하는 메커니즘을 구현하는 것이라고 생각합니다.
예를 들어 ‘나는 물을 마셨다.’라는 문장이 있을 때 이 정보를 컴퓨터에 때려박으면 이 텍스트를 기계어로 변환만 할 뿐 이 문장이 어떠한 의미도 가지지 않습니다. 그러나 이제 여기서 ‘나’가 물을 마시는 주체이고, ‘마시다’가 주체가 하는 행동이라는 것을 컴퓨터가 파악하게 하는 것입니다. 물론 역할이 이것만 있는 건 아니죠. 하지만 주어진 정보 있는 그대로를 받아들이는 컴퓨터에게 있어서 자연어를 처리하려면 이것이 우선적으로 실행되어야 합니다.

방금 말한 것들을 가능하게 해주는 모델이 바로 ‘트랜스포머’입니다.
이름 그대로 변환장치인데 인간의 언어를 기계가 알아들을 수 있게 바꿔준다고 생각하면 편할 것 같습니다.
좌측이 인코더 스택, 우측을 디코더 스택이라고 부릅니다. 양 스택을 모두 6층으로 구성이 되어 있고, 이 모델은 RNN, LSTM, CNN, 재귀 모두 사용하지 않습니다. 대신 attention이라는 것을 사용하는데 attention은 word-to-word, token-to-token연산으로 한 단어를 기준으로 단어 벡터들 간의 내적을 이용하여 기준 단어와 가장 연관성이 깊은 단어를 탐색하는 것입니다. 물론 자기 자신도 포함되기에 자신과의 연관성이 가장 높은 수치로 나오겠지만 문장 맥락의 파악을 통해 대략적인 관계 파악이 가능합니다.


  

인코더 스택


6층으로 구성되어 있기에 위 사진에서 N_x에서 x = 1~6이 됩니다. 논문의 오리지널 트랜스포머 모델에서의 차원(d_model)은 512로 두고 있으며 이 차원을 일정하게 유지해야 연산량, 리소스 사용량을 줄여 모델에 흐르는 정보를 쉽게 추적할 수 있습니다.

1. 입력 임베딩
먼저, 임베딩 서브층에서 토크나이저가 문장을 토큰으로 분리하면서 시작됩니다. 당연히 컴퓨터는 영어를 못하기 때문에 대문자로 소문자나 정수로 변환함으로써 단어들마다 값을 부여하는 것입니다. 그리고 이렇게 토큰화된 텍스트로 임베딩을 해야 합니다.
이 임베딩 서브층을 묘사하기 위해 구글에서 ‘스킵 그램’이라는 것을 발표했는데 이는 주어진 단어에 기초하여 주변 단어를 예측하는 역할을 수행합니다. 예를 들어 step size = 2라고 했을 때, word(i)를 기준으로 word(i-2), word(i-1), word(i+1), word(i+2)를 학습합니다. 스킵 그램의 모델은 일반적으로 입력, 가중치, 은닉층, 출력층으로 구성되고 d_model이 512이기에 각 단어당 512차원의 벡터를 뽑아냅니다. 출력이 나오고 나면 코사인 유사도로 두 단어의 임베딩 결과를 검증 가능합니다. 코사인 유사도는 단위구면에 벡터를 표현하기 위해 유클리드 노름을 사용하며 비교하려는 두 벡터의 내적으로 두 벡터 사이의 코사인 값을 나타낼 수 있습니다. 이걸 손으로 계산하진 않으니 사이킷런에서 찾아보시면 자세히 나와 있습니다.

예를 들어 “The black cat sat on the couch and the brown dog slept on the rug”라는 문장이 있을 때 black과 brown을 비교한다고 했을 때 결과적인 코사인 유사도는 굉장히 높을 것입니다. 왜냐하는 색상이라는 카테고리의 하위에 존재하기 때문입니다.

2. 위치 인코딩
만약에 위치벡터라는 것을 별개로 학습하게 되면 학습속도도 떨어지고, attention-sub층도 훨씬 복잡해지게 될 것입니다.
그래서 간단하게는 그냥 입력 임베딩에 위치 인코딩 값을 더해 시퀀스 내에 토큰의 위치를 표현합니다. 예를 들어 black이 2번째 단어, brown이 10번째 단어라면 black의 pos = 2, brown의 pos = 10이 되는 것입니다.
여기서 이제 각각의 단어 임베딩에 값을 더해서 정보를 추가해야 하는데 d_model이 512이기 때문에 512개의 숫자를 이용하여 black과 brown에 대한 정보를 줘야 합니다. 이 과정에서 “바스와니”를 사용하여 사인&코사인으로 단어 임베딩 차원 512개의 서로 다른 주기를 가지는 위치 인코딩 값 (PE값)을 생성합니다.
바스와니는 트랜스포머를 설계한 개발자의 이름으로 설계자의 방식에 초점을 맞춘다 하여 이렇게 부른다고 합니다.

def positional_encoding(pos,pe):
for i in range(0, 512, 2):
	pe[0][i] = math.sin(pos / 10000 ** ((2 * i)/d_model)))
	pe[0][i+1] = math.cos(pos / 10000 ** ((2 * i)/d_model)))
return pe

위 식을 보면 위에서 값이 정해지는 변수들이 많은데 방금처럼 black과 brown은 알파벳 구성이 비슷해 단어 임베딩 유사도가 높을 수 있습니다. 이런 사고를 방지하기 위해 위치 벡터가 존재하는 것입니다. 결국 이 두 단어는 문장 내에서 pos가 다르기 때문에 위치 벡터에 대한 유사도는 낮을 것입니다.

그럼 이제 구한 위치 벡터를 적용시켜야 합니다. 적용시키는 방법에는 여러 가지가 있는데 가장 쉬운 방법으로는 임베딩 벡터에 위치 인코딩을 더하는 것입니다.
입력을 x_1, x_2…라고 하고, 임베딩 벡터를 y_1, y_2…, 위치 벡터를 pe(n), 위치 인코딩을 pc(abc)라고 하겠습니다.
앞선 단어를 예로 들면 이제 x_1 = black -> y_1 = black + pe(2) = pc(black)이 되는 것입니다. 그냥 진짜 단순히 더하는 방식입니다.

여기서 문제점이 발생합니다. 이렇게 되면 위치 정보로 인해 단어 임베딩 정보가 훼손됩니다. 이유는 예를 들어 단어 a의 단어 임베딩 값을 1, 위치를 2라고 하고 단어 b의 단어 임베딩을 2, 위치를 1이라고 했을 때 단순히 더해버리면 둘 다 합이 3으로 두 단어의 구분이 힘들어집니다.
그래서 단어 임베딩 층의 정보를 확실하게 전달하는 게 필요한데 이 방법으로는 더하는 것이 아닌 임의의 값을 곱하면 됩니다. 이 임의의 값은 일종의 가중치 역할을 하여 단어 임베딩의 값에 대한 전달력을 상승시켜 줍니다.



다음 글에서 멀티-헤드 어텐션 층에 대해 알아보겠습니다. 읽어주셔서 감사합니다 ㅎㅎ





댓글