이제 드디어 정말 드디어 Transformer에 다가갈 수준이 되었다.
때는 2017년 아주 제목 어그로를 기똥차게 끈 논문이 하나 등장한다. 바로 이쪽 계열 사람들이라면 안들어본 사람이 없다는 'Attention is all you need'이다.
자 본격적으로 시작하기 전에 이 논문 이전에 해왔던 것들을 다시 정리해보자.
RNN을 사용했고, LSTM을 사용했다. 그리고 그걸 조금씩 발전시켰고, 다양한 데이터셋들이 등장하고, Task들이 정립되어가고 있었다.
Attentive니 뭐니 이야기 했지만 결과적으로는 LSTM의 연장선이었고 성능이 좋았으나 문제점이 있었다. RNN과 LSTM 모두의 문제점은 바로 '느리다'는 것이다.
사실 느린것을 제외하더라도 vanishing or exploding gradients 등의 몇몇 문제점들이 더 있었지만 그건 일단 잠시 넘어가도록 하자.
그렇다면 왜 느렸을까?
이 사진을 보자... 출력이 나오면 다시 입력으로 넣어줘야 한다. 물론 초창기여서 더 그런 것도 있지만 기본적으로 RNN과 LSTM은 순차적으로 처리를 해줘야 했다.
이전 결과물이 나와야 다음 결과물을 뽑아낼 수 있는 것이다.
인공지능의 비약적인 발전은 하드웨어의 발전과 함께 했다고들 한다. 지금은 코인 때문에 비싼 몸값을 자랑하는 그래픽 카드들의 발전으로 인해 인공지능이 한껏 자신을 뽐내고 있다. 하지만 이는 '병렬 계산'이 가능할때의 이야기이다. 한꺼번에 여러 계산을 할 수 있을 때 좋은 것이다. 저런 식으로 하나하나씩 뽑고 다음 단계로 넘어가는 것은 느릴 뿐더러 발전된 하드웨어의 뽕맛을 보지 못한다.
그렇다면 목표가 세워지지 않는가? 목표는 바로 병렬화이다.
Transformer의 가장 큰 장점은 바로 이 병렬화이다.
기존엔 순차적으로 계산했던 것을 한번에 뽬! 하고 뽑아 낼 수 있게 된 것이다.
어떻게 그런게 가능했는지, 그리고 그 속에선 어떤 연산이 이루어 지고 있는지 살펴보도록 하자.
큰 그림을 그릴 것이다. 생략하고 빼먹는 것들이 있을 예정이다. (Normalize 방식, skip connection,...등) 감안하고 보도록 하자.
논문의 전체적인 아키텍처는 다음과 같다. 하나씩 알아가보자.
먼저 input Embedding이다. 이건 이전 게시물에서도 이야기 했었지만 다시금 짧게 언급하고 넘어가겠다.
우리는 어떤 단어들이 공간상에서 비슷한 녀석들이 모이길 원한다. 동물들을 지칭하는 단어들은 걔네들 끼리, 감정을 뜻하는 단어들은 한쪽으로 모이길 원하는 것이다. 이렇게 단어별로 뭔가 의미를 지니는 벡터가 될 수 있도록 해주는 것이 word Embedding 이다. 관련된 포스팅은 이전에 해 두었으니 (나름 재밌게 써놨다고 생각한다.) 관심있다면 한번 보는 것도 좋다.
https://rollingpig.tistory.com/45
이렇게 나온 벡터에 Positional Encoder를 통과시켜 위치 정보를 더해준다. 기존과 다르게 한번에 뽬! 하고 해결할 것이기 때문에 단어들이 몇번 째에 있는 단어인지 네트워크에게 알려줄 필요가 있다. 이는 여러 다양한 방법들을 통해 더할 수 있겠지만 (실제로 선형 변환을 통해 나타낼수만 있다면 어떤 식으로 위치정보를 더해줘도 상관 없다.) 어텐션이즈얼유니드에서는 sin과 cos을 사용하여 위치정보를 줬다. 이것도 상당히 재미있는 녀석이다. 사실 Positional Encoding만 가지고 게시물 하나를 쓸 수 있을 것 같다. 하지만 다음을 기약하도록 하겠다. 일단은 위치 정보를 상대적으로 넣어줬다는 개념으로 알고 넘어가자.
이렇게 위치 정보까지 더해 준 벡터들은 다음 단계로 넘어가게 된다.
Multi-Head Attention과 Feed Forward 이다. 이를 우리는 Encoder Block이라 부른다.
Attention에 대해서는 지난 게시물에서 어느정도 정리했었다. 이것도 나름 재밌게 써놨으니 관심있으면 보길 바란다.
https://rollingpig.tistory.com/57?category=944834
하지만 이름이 조금 바뀌었다. 바로 Multi가 앞에 붙은 것이다.
일단 기본적으로 attention은 self-attention을 의미한다. 문장 내에서 다른 단어들과 비교하였을 때 어떤 단어와 가장 연관이 높은지 계산한다고 생각하면 된다. 근데 왜 multi라는 말을 붙였을까?
바로 8번 수행하기 때문이다. 사실 self-attention을 수행하면 당연하겠지만 자기자신에게 제일 많이 집중하게 된다. 하지만 우리가 원하는 것은 다른 단어와의 관계이므로 8번 수행하여 이를 평균을 내서 조금 더 쉽게 우리가 원하는 바를 수행하는 것이다.
저 8개의 벡터들을 단어별로 weighted average 하여 나온 결과물은 Feed Forward Network로 들어간다. 별건 아니고 그냥 MLP를 통과시켰다고 이해해도 좋다. attention된 단어들 간의 조합을 통해 다음 네트워크의 input으로 들어갈 수 있는 형태로 바꿔주는 역할을 한다.
그림 상에서는 마치 input size와 output size가 다른 것 같은데 같다.. 다른 사진을 가져다가 붙이다 보니 저렇게 되었다. 이런 과정을 N번 반복하게 되면 결과적으로 단어 개수 만큼의 벡터들이 나오게 된다.
지금 예시에서는 4개의 벡터가 나올 것이다. 얘네는 이제 디코더로 이동하게 된다.
output은 아쉽게도 한번에 뽬! 이 안된다.
encoder를 통해서 나온 벡터들은 사실 그냥 디코더로 들어가 지는 것이 아니다.
특정한 매트릭스와의 곱을 통해 K, V로 바뀌어 넘어가게 된다.
위 그림에서 X로 표현된 것은 Encoder를 통해서 나온 벡터들이다. 각각 self-attention을 통해 주변 단어들과의 관계를 익혔고, 그게 벡터화 되어 나온 녀석들이다. 이걸 이제 W 매트릭스와 곱하여 각각 Queries, Keys, Values로 나뉘게 된다.
엥 그게 무슨소리지? 라는 생각이 들수도 있다. 사실 여기서부터 개념이 살짝 어렵다. 하지만 걱정말자. 큰그림을 이해하면 어느정도 보일 것이다.
자 생각해보자. 각각의 단어들은 Encoder를 통해 자기자신의 뜻과 더불어 주변 단어들과의 관계를 대표하는 벡터가 되어 나왔다. 이제 이걸 서로 다른 3개의 W라는 매트릭스에 곱할 것이다. 그래서 각각 쿼리, 키, 밸류라는 값들이 나온다.
위위 그림을 다시 보자.
첫 번째 단어에 대해 나온 쿼리와 다른 단어들의 키들을 곱해서 어떤 값을 얻어낸다. 이 값들을 softmax 시켜 각각의 밸류와 곱해준 것이 z1이다.
헷갈리면 안되는 것은 한 단어의 쿼리와 다른 모든 키들을 곱한다는 것이다. 그리고 나서 다른 모든 벨류들과 다 곱하여 이를 더한다는 것이다. 즉, 단어 1의 쿼리와 단어 2의 키가 곱해졌을 때 그 값이 높다면 두 단어 간의 연관 관계가 높다고 판단하여 단어 2의 밸류값을 많이 가져가겠다는 의미가 된다.
다시 정리해보자. 각각의 단어들에는 쿼리, 키, 벨류가 존재한다. 쿼리는 내 단어가 다른거랑 얼마나 관련있을 지 구하는 한쪽이다. 키는 쿼리랑 얼마나 비슷한지 맞춰보는 다른 한쪽이다. 두개를 맞춰봤는데 영 아니다 싶으면 값이 낮을 것이고 이거이거 상당히 맞는 녀석이다 싶으면 네녀석의 벨류를 가져가겠다! 가 되는 것이다.
예를 들면, It is raining 이라는 문장에서 이를 해석하면 비가 온다. 라는 문장이 될 것이다. 이 때 It 은 raining의 벡터값을 많이 가져올 것이다. 즉, It의 query와 raining의 key 값이 비슷해서 (곱이 높은 값을 가져서) it에 대해 raining의 value값이 많이 포함되었을 것이다.
자 이제 다시 디코더로 넘어가보자. 일단 설명을 위해 출력물이 조금 있었다고 해보자.
그러면 그 출력물들에 대해서 똑같이 Positional Encoding을 거쳐서 새로운 입력으로 넣어준다. 그리고 똑같이 self-attention을 가해준다. 근데 이 때는 자기 이전에 출력된 녀석들에 대해서만 attention을 해준다. 그래서 이걸 Masked-Multi-Head Attention이라 하는 것이다.
이렇게 이전 값들에 대해서만 attention을 해준다고 생각하면 된다.
그 이후에는 Encoder 에서 넘어온 값들을 가지고 똑같이 멀티헤드어텐션을 해준다. 그리고 Feed Forward를 통과시키고 N번 반복 후 Linear Layer 후에 softmax를 통해 아웃풋을 만드는 것이다.
쭉 정리를 해보자면 결국 입력 값들에 대해 self attention을 통해 서로간에 뭐가 중헌디를 알아내고, 디코더의 아웃풋들을 하나씩 내면서 (맨 처음에는 SOS 값이 들어갈 것이다) 이를 가지고 엔코더에서 알아낸 뭣이 중헌디를 가지고 무엇을 출력하면 좋을지 하나씩 알아내는 것이다.
이 때, 앞서 말한 query, key, value 값은 행렬 연산을 통해 한번에 계산 할 수 있다.
이 내용들은 Transformer의 시초와도 같은 녀석이고 중요하다. 이 글을 통해 큰 그림을 그렸다면 좋겠다.
다음 게시물에서는 이후 발전된 녀석들과 더불에서 CV에서 어떤 식으로 적용하는지까지 짧게 정리해보겠다.
'NLP' 카테고리의 다른 글
ELMO, BERT, GPT, 그리고 ViT (0) | 2021.06.11 |
---|---|
NLP Task 정리 (0) | 2021.06.11 |
인공지능에서 Attention 이란? _ seq to seq (0) | 2021.06.11 |
NLP 벤치마크 GLUE (0) | 2021.06.11 |
RNN과 LSTM (0) | 2021.05.02 |