005. 딥러닝 공부 3일차 1
어제는 자연어 처리 방식에 대해 RNN과 LSTM을 이용하는 방식을 봤었는데,
오늘 추가로 공부해보니 이 방식은 이미 17년 18년도 쯤에 사장됬고 요즘은 Transformer를 쓴다고한다.
CNN도 많이 듣긴 들었는데 이게 디퓨전에 들어가는건가? 했었는데 디퓨전이랑은 또 다른거인거같다..
늦게 시작하니 정보가 많아서 편하긴 편한데, 어지럽기도 어지러운듯 하다.
그래도 정보가 많은게 최고다....
오늘은 이 영상 시리즈 (1~4편)을 모두 보고 시작했다.
서울대 최고...!(맞나?)
https://www.youtube.com/watch?v=Z201jwWo-xs
1. Transformer (Encode)
이전에 봤던 LSTM, RNN은 단어의 임베딩을 통해 의미를 추정하고, 앞뒤의 상관관계를 추정하는 방식이었다.
이 경우 단어를 어떻게 분할할 것인지 ( Space단위, 모든 char, 형태소 등... )에 의미 분석 정확도가 의존됬다.
트랜스포머 아키텍처는 이와 달리 문장을 통째로 이해하는 방식이다.
트랜스포머는 RNN과 같은 순환 방식이 아니라 Self-Attention이라는 방식을 사용한다.
문장을 입력받으면, N개의 인코더를 통해 Multi-head Attention과 Feedforward Network를 통해 문장의 표현방식을 학습하고, 이후 학습 결과를 가지고 디코더를 통해 문장을 생성한다.
그렇다면 Self-Attention은 어떤 방식으로 작동할까? ( 행렬 연산...! )
먼저 문장을 구성하는 각 단어에 대해 임베딩을 구성한다.
그러면 [단어 개수]x[임베딩 차원] 크기의 행렬이 생긴다. 이 행렬을 입력 행렬(X)라고 하자.
여기에 대해 3개의 가중치행렬 query, key, value를 생성하여 X에 곱한다.
그러면 총 3개의 행렬 Xq, Xk, Xv가 생성된다. 각각의 크기는 [단어 개수(n)]x[가중치 행렬의 차원(d)]이다.
여기에 대해 dot(Xq, Xk^T)를 수행한다. ※ Xk^T는 Xk의 전치행렬이다.
Xk의 전치행렬과 Xq의 dot product를 통해 Xqi와 Xk(1~n)의 유사도를 측정한 것이다.
각 행의 값들의 크기에 따라 어떤 단어와 관계가 높은지 추정한다.
(만약 1행의 q1k1~q1kn중 q1k3이 가장 크다면, 단어1은 단어3과 가장 연관이 크다.)
이렇게 구한 dot(Xq, Xk^T) 행렬에 대해 Xk 행렬의 차원값의 제곱근을 곱해주고, 그 행렬에 Softmax함수를 취한다.
그러면 Softmax(dot(Xq, Xk^T) * sqrt(d(Xk))) 의 형태가 된다.
sqrt(d(Xk))를 곱함에 따라 안정적인 그래디언트를 얻고, Softmax를 통해 각 행에 정규화를 진행한 것이다.
이를 통해 얻은 행렬을 Attention 행렬로 변환해주어야 한다.
이 과정은 앞에서 만들어놓고 사용하지 않은 Xv 행렬을 곱해주는 것으로 구한다.
이를 통해 각 단어의 Attention 행렬은 Xv의 값을 단어간 연관 추정에 따른 비율만큼 얻게 된다.
※ (q_n*k_n)의 값은 0~1이고 각 행에 정규화 되어 있으므로..
그렇다면 전체 식은 다음과 같다.
Attention-Matrix = dot(Xv, softmax( dot(Xq, Xk^T) * sqrt(d(Xk)) ))
이를 통해 문장 내 각 단어가 다른 단어들(자신을 포함한)과 얼마나 연결성을 가지고 있는지를 추정하여 문장을 이해한다.
또한, 이러한 과정을 Scaled dot product Attention이라고도 부른다.
하지만, 이번 챕터의 가장 위를 보면 우리가 사용하는 것은 Self-Attention이 아니라 Multi-head Attention이다.
Multi-head Attention은 무엇일까?
Self-Attention을 사용했을 때, 대명사 등에 대한 의미 추정에서 그 대명사가 가리키는 단어에 대한 어텐션이 높게 나온다면 잘 이해하겠지만, 그렇지 않은 경우도 있을 것이다. 이런 상황을 대비하기 위해 어텐션을 여러개 사용하게 되고, 이것이 Multi-head Attention이다.
이것을 만드는 방법은 단순히 Self Attention을 여러개 만든 후, 그 결과들을 모두 이어붙여 하나의 행렬로 만든 뒤, 임의의 가중치 행렬 W를 곱하는 방식으로 만든다.
이런 방식을 사용하기 때문에 기존 RNN에서 사용되었던 것처럼 단어를 앞에서부터 하나하나 입력하는 방식은 사용되지 않고, 문장 전체를 한 번에 입력하는 병렬방식으로 진행된다. 그렇다면 여기서 문제를 하나 더 생각해야 하는데,
'문장에서 단어간 의미만 파악하고, 단어의 순서는 고려하지 않는 것인가?' 라는 문제다.
이를 해결하기 위해 위치 인코딩을 통해 순서 정보를 제공해야한다.
최초 논문(Attention Is All You Need)에서는 위치 정보에 대해 Sin,Cos 함수를 사용했다.
위치 인코딩 행렬을 P, 단어의 문장에서의 위치는 pos, 정수 i에 대해
P(pos, 2i) = sin(pos / 10000^(2*i / embedding_dim))
P(pos, 2i+1) = cos(pos / 10000^(2*i / embedding_dim))
의 값을 가진다.
이렇게 구성된 행렬 P를 위에서 어텐션을 구하는데 사용된 입력 행렬 X에 더한 뒤 인코딩에 들어가게 된다.
[문장] -> [임베딩] + [위치 인코딩] -> [Self-Attention * N] -> [Multi-head Attention]
까지 살펴봤다.
여기까지 구한 [Multi-head Attention]에 대해 피드포워드( 2개의 Dense Layer와 ReLU 활성화 함수 )를 진행하면 하나의 인코딩 블록이 마무리 된다... 라고 생각했지만, 각각의 작업을 수행한 뒤에 Add&Norm을 통해 각 작업의 입력값과 출력값을 연결하여 데이터 분포를 정규화 하고 그래디언트의 전파를 돕는 작업을 하게 된다.
그렇다면 전체적인 트랜스포머 인코딩 과정은 다음과 같다.
무슨 텐서플로우에서 Conv2D, Pooling, Dense, LSTM 이런거 깔짝하다가 갑자기 확 복잡해진 느낌이다.
뭐 그만큼 성능이 엄청 올랐다고 하니까...
2. Transformer ( Decode )
인코딩을 통해 이렇게 문장의 의미구조를 파악하여 벡터로 만들어 냈다.
그렇다면 이것을 이용해 어떻게 문장을 만들어야할까?
일단 기본 구조는 아래와 같다.
중간에 왼쪽에서 오는것은 Encode의 결과물로, Masked를 제외한 모든 Multi-head Attention의 입력값은 Encode의 결과와 이전 Decode block의 결과가 같이 들어가게 된다.
자 먼저 Encode와 다른 Masked Multi-head Attention부터 보자.
Decode는 이전 RNN을 통한 문장 생성 방식과 같이 <sos>(문장 시작)부터 한 단어씩 생성하며 <eos>(문장 끝)이 나올 때 까지 문장을 이어나간다. 즉, Decode에서는 현재 생성한 문장 까지만 입력하고, 생성되지 않은 모든 단어는 masked된다.
이를 위해 Self-Attention에서 했던 것과 같이 dot(Xq, Xk^T) * sqrt(d(Xk)) 까지는 수행한다.
그리고, 현재 생성되지 않은 단어들은 모두 -inf로 mask해준다.(생성된 단어의 개수에 따라 행렬이 계속 커진다...)
Masked Multi-head Attention을 통해 참조하지 않을 위치의 단어들에 대해 모두 Mask를 진행했다.이렇게 출력된 Attention 행렬과 Encode에서 출력된 Attention 행렬 2개를 가지고 Multi-head Attention을 수행해야한다.이 때, 인코더의 표현값은 R, Masked Multi-head Attention의 표현값은 M이라고 한다.이전에는 입력값 행렬이 X 하나였기 때문에 Query, Key, Value를 모두 X 하나에서 만들었다.그러나 이번에는 입력값 행렬이 R, M으로 두 개이므로, Query는 M에서, Key, Value는 R에서 만들게 된다.즉 Rk, Rv, Qm 이 되고, 이를 이용해 똑같이 Multi-head Attention을 구하는 과정을 수행한다.
피드포워드나 Add&Norm도 작동방식은 같으므로, 모든 디코더를 통과한 후 Linear와 SoftMax에 대해 보자.디코더에서 출력된 값은 선형 레이어에 입력되고, 모든 vocab 단어에 대한 로짓 벡터를 만든다.그리고 SoftMax를 통해 이 로짓 벡터를 정규화하여 0~1로 확률값을 구한 후 최대 값을 구한다.
이것으로 Transformer의 Encode와 Decode의 각 블록의 작동방식에 대해 알아봤다...또한 이런 문장 생성은 loss를 최소화해야한다. ( loss가 높으면 뜬금없는 단어가 나올테니.. )그래서 예측 확률 분포와 실제 확률 분포의 차이를 최소화 하기 위해 cross-entropy loss 함수를 사용하고,같은 이유로 옵티마이저를 adam을 사용해야 한다.그리고 과대적합을 방지하기 위해 각 서브레이어마다 dropout을 적용해야한다.
이런 방식을 만든 연구자분들은 참 대단한 것 같다... 행렬을 어떻게 어떻게 잘 곱해서 이러면 된다... 라니...
참고 서적 :
https://books.google.co.kr/books?id=6T9NEAAAQBAJ
(구글 BERT의 정석)
책은 최대한 2020년 이후, 2021년 이후에 발간된 책으로 찾고있다..
기술이 하도 빨리 바뀌어서...