16장
RNN과 어텐션을 사용한 자연어 처리
자연어 문제에서는 순환 신경망을 가장 많이 사용한다. 문장의 다음 글자를 예측하는 문자 단위 RNN character RNN 부터 시작한다.
상태가 없는 RNN stateless RNN, 상태가 있는 RNN stateful RNN 을 구축하고, 감정 분석등을 수행하는 RNN 을 구축한다.
다음엔 문자의 시퀀스가 아니라, 단어의 시퀀스로 문장을 다뤄 신경망 기계 번역 neural machine translation NMT 을 수행하기 위해 인코더-디코더 구조의 RNN 을 살펴본다.
각 타임스텝에서 집중해아 할 입력 부분을 선택하는 신경망 구성 요소 어텐션 메커니즘 attention mechanism 을 알아보고, 이를 통해 RNN 기반의 인코더-디코더 구조의 성능을 높이고, RNN을 모두 제거하고 어텐션 만으로 매우 좋은 성능을 내는 트랜스포머Transformer 구조를 살펴보고 마지막으로 트랜스포머를 기반으로한 GPT-2, BERT 같은 언어모델도 알아본다.
16.1 Char-RNN을 사용해 셰익스피어 문체 생성하기
16.1.1 훈련 데이터 셋 만들기
#https://github.com/karpathy/char-rnn 도 사용가능
url = "https://homl.info/shakespeare"
filpath = keras.utils.get_file("shakespeare.txt", url)
with open(filepath) as f:
shakespeare_text = f.read()
셰익스피어 텍스트를 Tokenizer 클래스를 통해서 정수로 인코딩한다.
tokenizer = keras.preprocessing.text.Tokenizer(char_level=True)
tokenizer.fit_on_texts(shakespeare_text)
char_level = True 를 지정해서, 단어수준이 아닌 글자수준 인코딩을 만든다.
이제 tokenizer으로 문장을 정수로 변환이 가능하다. 대신 tokenizer에 없는 문자는 무시된다.
tokenizer.sequences_to_texts 으로 정수에서 문자로,
tokenizer.texts_to_sequences 으로 문자에서 정수로 변환 가능하다.
이때 각 글자가 0 부터가 아니라 1 부터 시작된다.
16.1.2 순차 데이터셋을 나누는 방법
시계열 데이터를 다룰때는 시간에 따라 나눠서 훈련, 검증, 테스트로 나눈다. 같은 시간의 차원을 기준으로 나누게 된다면, 각 차원끼리의 연관성이 존재할 수 있어 편향이 발생할 수 있다. 따라서 시간에 따라 나누는 것이 안전하다. 이럴땐 암묵적으로 RNN이 과거에서 학습한 패턴이 미래에도 등장한다고 가정하지만, 변덕스러운 시계열의 경우 충분히 안정적인지 확인해야한다. 시간에 따라 검증세트와 모델의 오차를 그려보거나, 검증세트의 시간이 갈수록 성능이 나빠지면 충분히 안정되지 않았다고 판단할 수 있다. 그럴경우 더 짧은 시간 간격으로 모델을 훈련시키는 것이 좋다.
16.1.3 순차 데이터를 윈도 여러개로 자르기
훈련세트는 백만개 이상의 글자로 이루어진 길다란 형태의 시퀀스 하나이다. 이로인한 훈련은 샘플 한개로 훈련하는거랑 마찬가지이고, RNN은 백만개 이상의 층을 가진 심층 신경망과 비슷하다. 따라서 데이터셋을 window() 메서드를 사용해 작고 많은 전체의 부분 텍스트 윈도로 변환한다. 역전파도 부분만 진행한다. 이를 TBPTT truncated backpropagation through time 이라고 부른다.
#100개의 시퀀스에 window 길이 10, shift 1, 나머지는 버리기로하면
#1-10, 2-11, 3-12, ... , 90-99, 91-100 까지 총 91개의 부분 문자열이 만들어진다.
dataset = dataset.window(window_length, shift=1, drop_remainder=True)
window() 메서드는 각각의 윈도우에 해당하는 데이터셋들을 포함하는 데이터셋으로 중첩 데이터셋nested datset 이고, 데이터셋 메서드를 활용해서 각 윈도우를 변환할 때 유용하다. 하지만 모델은 텐서를 기대하기 때문에 중첩 데이터셋을 플랫 데이터셋 flat dataset 으로 변환해주는 flat_map() 메서드를 사용한다.
dataset = dataset.flat_map(lambda window: window.batch(window_length))
1개의 텐서로 만든 윈도들을 독립 분포로 만들고, 입력과 타깃을 분리한다.
그리고 범주형 입력을 one_hot 벡터로 인코딩한다.
16.1.4 Char-RNN 모델 만들고 훈련하기
글자 100개를 입력받아 다음 글자 1개를 예측하기위해 유닛128개의 GRU 2개와 입력에 20% 드롭아웃을 사용한다. 출력은 Dense 층으로 각 글자에 대한 확률을 출력한다.
sparse_categorical_crossentropy, Adam 을 사용한다.
16.1.5 Char-RNN 모델 사용하기
훈련이 끝난 모델에 새로운 텍스트를 만들어내게 하기위해서, 텍스트를 주입할때, 앞에서 수행한 전처리과정을 진행해야한다.
texts_to_sequences 를 이용해서 숫자로, 숫자를 원핫벡터로 변경하고 모델에 주입후 최고 확률 글자를 반환한다.
16.1.6 가짜 셰익스피어 텍스트 생성하기
초기 텍스트를 주입하고, 모델이 다음 글자를 예측해 붙이고 다시 예측해 붙이는 과정을 반복한다. 이럴경우 모델이 자주 예측하는 단어가 반복되는 경우가 많아서, tf.random.categorical() 함수를 통해 랜덤성을 부여해 더 다체로운 텍스트를 유도한다. 온도temperature 라는 숫자로 각 로짓 클래스 확률을 나누면 다양성을 제어할 수 있다. 0 에 가까워질 수록 높은 확률을 가진 글자를 선택한다.
https://colab.research.google.com/drive/131uxv4-82L7LT1ENzRSjldbyEulIJjRm#scrollTo=TextGenerate
Google Colaboratory Notebook
Run, share, and edit Python notebooks
colab.research.google.com
더 좋은 성능을 내기위해서, GRU 층과 뉴런 수를 늘리거나, 더 오래 훈련하거나, 규제를 추가할 수 있다. 매우 긴 시퀀스를 다룰 수 없는 LSTM, GRU의 특성상 윈도를 더 크게하면 훈련이 어려워진다.
16.1.7 상태가 있는 RNN
상태가 없는 RNN stateless RNN 은 훈련 반복마다 모델의 은닉 상태를 0 으로 초기화한다. 타임스텝마다 상태를 업데이트하고, 타임스텝이 끝나면 더이상 필요없기 때문에 버리지만, 마지막 상태를 다음 훈련 배치 초기 상태로 사용해서, 역전파는 짧은 시퀀스에서 일어나도록하고 장기간 패턴을 학습할 수 있도록하는 상태가 있는 RNN stateful RNN 을 다뤄본다.
Stateful RNN은 입력시퀀스가 이전 시퀀스와 연결되어야 한다는 점이 중요하다. 따라서 shuffle 은 사용하면 안되고, 1 배치 사이즈를 사용하고, window 생성시에 겹치지 않도록하는 shift 를 지정해야 한다. 타깃은 동일하게 윈도에서 1 shift 한 시퀀스를 타깃으로한다.
상태가 있는 RNN 층을 만들기위해서 stateful=True 를 지정해야하고, 에폭이 종료될 때마다 상태를 초기화 해주어야한다. Stateful RNN은 이전샘플과 연관성을 학습하기때문에, 데이터셋을 전부 돌면 다시 처음부터 연관성을 학습하기 위해서 그렇다.
16.2 감정 분석
IMDb 데이터셋의 영화리뷰 50000개의 바이트스트링 원본데이터셋을 텐서플로 연산만을 사용해서 전처리를 수행한다.
https://colab.research.google.com/drive/131uxv4-82L7LT1ENzRSjldbyEulIJjRm#scrollTo=Preprocessing
Google Colaboratory Notebook
Run, share, and edit Python notebooks
colab.research.google.com
tf.strings 을 이용해서 텍스트를 최대 300개씩 짜르고 <br / > 테그를 공백으로 알파벳이 아닌 글자는 공백으로, 300글자가 되지 않는 샘플에 <pad> 토큰으로 채워넣는다.
모든 훈련 데이터셋을 돌면서 단어 사전을 만들고, 가장 많이 등장하는 단어 10000개를 남기고 삭제한다.
각 단어를 ID로 전처리하기위해서 1000개의 oov 버킷을 사용하는 룩업 테이블을 만든다.
모델의 첫번째 층은 10000 + 1000개의 단어를 128차원의 벡터로 변환하는 임베딩층하고, GRU 128 각 타임스텝 반환, GRU 128 마지막 타임스텝만 반환, Dense 1 sigmoid
compile : loss = binary_crossentropy, optimizer = adam
16.2.1 마스킹
단어의 길이를 동일하게 맞추기위해서 <pad> 토큰을 사용하는데, 모델이 학습할 때는 이 토큰을 무시하도록 학습되어야 한다. Embeddng 층을 만들때 mask_zero=True 로 지정하면 임베딩층에서 ID = 0 인 토큰을 무시하도록 할 수 있다.
임베딩층에서 K.not_equal(inputs, 0 ) 인 마스크 텐서를 만들어 모델의 모든층에 타임스텝이 유지되는 동안 자동으로 전파된다. 두번째
GRU에서는 마지막 시퀀스만 반환하기때문에, 다음층 Dense로는 전파되지 않는다.
이러한 이유로 마스크를 사용할때는, 마스크를 받는 모든층은 마스킹을 지원해야한다. 마스킹을 지원하는 층은 support_masking 속성값이 True 이고, 사용자 정의 층을 구현하려면 call() 메서드에 mask 매개변수 추가, self.support_masking=True를 지정해야한다.
마스킹의 자동전파는 sequential 모델과 가장 잘맞고, 합성곱층과 섞는것은 항상 작동하지않아서, 함수형 API 또는 서브클래싱 API 를 통해 마스킹을 계산해 다음층으로 전달해야 한다.
훈련을 진행하고 텐서보드로 학습된 인베딩층을 시각화해서 단어간의 군집을 확인할 수 있다.
16.2.2 사전훈련된 임베딩 재사용하기
텐서플로 hub에서 사전 훈련된 모델 컴포넌드 모듈(module) 을 모델에 쉽게 추가할 수 있다.
tensorflow_hub.KerasLayer 에 URL을 통해 모듈을 다운받을 수 있다. 기본적으로 KerasLayer층은 훈련되지 않지만, 층을 만들때 trainable=True 로 지정해 작업에 맞게 미세조정도 가능하다.
TFHUB_CACHE_DIR 환경변수에 고정 디렉터리를 지정하면 매번 다운로드하지않고 캐싱하여 사용할 수 있다.
16.3 신경망 기계 번역을 위한 인코더-디코더 네트워크
영어문장을 인코더에 주입하면 디코더는 프랑스어 번역을 출력한다. 인코딩층에 영어문장이 거꾸로 입력된다. ( 마지막 단어부터 - 첫번째 단어까지 ) 인코딩층을 지나 디코더는 영어문장의 첫번째 단어와 <sos> 토큰을 입력으로 받아 그에 맞는 단어를 추론하고, 그 타임스텝에 맞는 타깃 토큰이 입력으로 들어가서 반복한다. 타깃이 <eos> 토큰이라면 종료한다.
추론시에는 타깃이 없기때문에, 예측했던 단어를 다음 타임스텝의 입력으로 사용한다.
단어를 임베딩으로 변환하고 ( 임베딩 행렬에 곱함 ), 임베딩에 따라 출력 어휘 사전 ( 프랑스어 ) 에 대한 점수를 출력하고, 확률로 변환, 그리고 가장 높은 확률의 단어를 예측한다.
- 모든 문장의 길이는 다양하기때문에, 입력시퀀스의 길이는 다르다. 이전처럼 특정한 토큰의 개수로 잘라낼 수 있지만, 감성분석의 경우 전체 문장이 의미가 있기때문에, 함부로 잘라낼 수 없다. 길이가 비슷한 문장들을 같은 종류의 버킷으로 묶는다. 예를들어 단어가 1~6개, 7~12 개 ... 로 나눈 문장들을 각각 동일한 길이가 되도록 패딩을 붙인 버킷으로 담는다.
- EOS 이후의 출력은 모두 무시한다. 이 토큰들은 손실에 영향을 미치지 않는다. (마스킹처리 되어야만한다.)
- 각 타임스텝에서의 출력이 모든 단어의 확률을 나타낸다면 매번 단어의 개수차원의 벡터를 출력해 연산 비용이 매우 높아진다. 이를 해결하기 위해서 타깃단어에 대한 로짓과, 무작위로 샘플링한 단어의 로짓을 고려하는 샘플링 소프트맥스 sampled softmax 이 있다. tf.nn.sampled_softmax_loss() 를 사용할 수 있다.
텐서플로 에드온 Addon 의 손쉬운 seq-2-seq 도구를 통해 인코더-디코더를 쉽게 만들수 있다.
에드온의 TrainingSampler으로 각 스텝에서 디코더에게 이전 스텝의 출력이 무엇인지 알려준다. 훈련시에는 타깃의 임베딩이고, 추론시에는 출력토큰의 임베딩이다. 실전에서는 훈련이 진행됨에따라 점진적으로 타깃에서 출력으로 입력을 옮기는게 좋다고 한다.
16.3.1 양방향 RNN
일반적인 순환신경망에서는 왼쪽에서 오른쪽으로 과거와 현재의 입력을 통해 출력을 예측하는 인과적casual인 방식은 기계번역과 같은 여러 종류의 NLP 작업에는 적합하지 않다. 따라서 동일한 입력에 대해서 두개의 순환층을 구성한다. 하나는 왼쪽에서 오른쪽으로, 하나는 오른쪽에서 왼쪽으로 읽고 타임 스텝마다 이 두 출력을 연결하는 양방향 순환 층 bidirectional recurrent layer 을 사용한다.
keras.layers.Bidirectional(RNN) 으로 구현한다. RNN 층을 복사하여 출력을 연결한다.
16.3.2 빔 검색
차근차근 단어하나씩 출력하는 문장에서 스텝마다 가장 가능성있는 단어 하나를 출력해서는 최고의 번역을 만들지 못한다. 모델은 실수가 있더라고 뒤로 돌아가지 못하기 때문이다. 앞서 발생한 실수를 고치는 방법중하나로 빔 검색beam search 방식이다. k 개의 가장 가능성있는 문장의 리스트를 유지하면서 디코더가 단계마다 이 문장의 단어를 하나씩 생성하여 가능성있는 k개의 문장을 만든다. 이때 k를 빔 너비beam width 라고 한다.
각 타임스텝마다 빔너비 3의 빔검색을 수행한다고하면, How 75%, What 3%, You 1%, ... ( 단어의 개수만큼 확률이 나옴 )의 확률이 나오고, 최상위 3개인 How, What, You와 각 확률을 유지한다. 그리고 각 문장에따라 다음단어를 예측하면, How - will 36%, are 32%, do 16% ... ( 단어의 개수만큼 ) 와 What - ... , You - ... 3개의 문장에 대해서 3 x 10,000 개의 확률을 각 이전 문장이 만들어질 확률 ( (How = 75%, What = 3%, You = 1% )과 곱해서 3x10000개의 후보 문장중에 가장 높은 확률 3개를 유지한다.
에드온을 이용해서 손쉽게 구현 가능하다.
tensorflow_addons.seq2seq.beam_search_decoder.BeamSearchDecoder( )
디코더 셀을 감싼 BeamSearchDecoder 을 만들고, 각 디코더에 인코더의 마지막 상태를 복사한다.
16.4 어텐션 메커니즘
어텐션 메커니즘은 각 타임스텝에서 (인코더에 의해 인코딩하여) 적절한 단어에 디코더가 초점을 맞추도록하는 기술이다. 적절한 단어에 집중하여 번역하면 입력 단어부터 번역까지 경로가 훨씬 짧아져서 RNN의 단기기억 제한성에 훨씬 적은 영향을 받는다. 그리고 특히 긴 문장에 대해서 최고 수준의 성능을 향상시켰다.
타임스텝을 거치면서 인코더의 모든 출력을 디코더로 전송 (어텐션으로 전송)해서 디코더 셀에서는 인코더 출력의 가중치 합을 계산해 주의를 집중할 단어를 결정한다. a(t,i) 가중치는 t번째 디코더 타임스텝에서 i 번째 인코더 출력의 가중치이다.
가중치 a 는 정렬 모델alignment model (또는 어텐션 층 attention layer) 이라고 불리는 작은 신경망에 의해 생성된다.
TimeDistributed의 Dense층에 디코더의 이전 은닉상태 ( 이전 타임스텝의 디코더 상태 )와 이전 인코더의 모든 출력을 연결한 것이 입력이고, 이 출력을 소프트맥스층을 통해서 인코더 출력에 대한 최종 가중치를 얻는다. ( 소프트 맥스층은 타임스텝 각각 적용되지 않는다. )
이를 바흐다나우 어텐션 혹은 이전 디코더의 은닉상태와 연결해서 연결 어텐션concatenative attention 이라고도 한다.
이 방식을 좀더 개선해서 민-탕 루옹 Minh-Thang Luong 등은 인코더의 출력 하나와 디코더의 이전 은닉 상태 사이의 유사도를 측정하는 것이기 때문에, 두 벡터 사이의 점곱으로 유사도를 측정하는 방식을 제안했다. 이때 두 벡터는 동일한 차원을 가져야 한다. 점곱은 하나의 점수를 만들고, 소프트맥스를 통과해 최종 가중치를 만든다. 또 다른방식으로 이전 타임스텝 디코더 상태가 아니라, 현재 디코더 은닉상태를 사용하는것이다. 어텐션 메커니즘의 출력을 사용해서 바로 디코더의 예측을 만든다. 또한 인코더의 출력을 점곱 계산전에 선형변환 ( 편향이 없는 Dense ) 을 통과하는 점곱 메커니즘 변종을 제안해서 연결 어텐션보다 더 나은 성능을 보여준다.
16.4.1 비주얼 어텐션
어텐션은 신경망 기계번역 NMT 이외의 목적으로 비주얼 어텐션 visual attention 을 이용한 이미지 캡션 생성이다. 합성곱 신경망이 이미지를 처리해서 특셩맵을 출력하고, 어텐션 메커니즘을 장착한 디코더 RNN이 한번에 한단어씩 캡션을 생성한다. 디코더 타임스텝마다 어텐션 모델을 사용해 이미지에서 적절한 부위에 집중한다.
-- 설명 가능성
설명 가능성이란 모델이 어떤 출력을 만들도록 이끄는 것이 무엇인지 이해할수있는 정도이다. 이는 올바르게 예측하지 못하는 모델에 유용하다. 잘못된 예측이 무엇에 초점을 맞추고 있는지 확인해서 문제를 해결할 수 있다.
16.4.2 트랜스포머 구조 : 어텐션이 필요한 전부다.
2017년 구글 연구팀은 트랜스포머transformer 라는 구조를 만들어 순환층이나 합성곱 층과같은 다른 층을 전혀 사용하지 않고 어텐션 메커니즘 ( 임베딩, 밀집, 정규화 층 )을 사용해 NMT문제에서 최고 수준 성능을 크게 향상시켰다. 트랜스포머의 장점은 훨씬 빠른 훈련과 병렬화가 쉽다는 점이다.
- 왼쪽은 인코더로 1D 의 단어 시퀀스로 표현된 문장의 배치를 입력으로 받는다. 인코더는 각 단어를 512 차원의 표현으로 인코딩한다. 위치 인코딩 윗부분은 N 번 쌓아올려진다. (논문에선 N=6) ( [ 배치, 입력문장의 최대 길이 ])
- 오른쪽은 디코더이고, 훈련시에는 타깃을 입력 ( 1D 단어 시퀀스 ) 받고 이 입력은 오른쪽으로 한 타임 스텝 이동되어있다. ( 맨처음에 SOS 토큰이 추가되었단 소리 ) 디코더도 N 번 쌓아올려지고, 인코더의 최종출력이 N 번 디코더에 주입된다. 디코더는 앞에서와 동일하게 각 타임스텝마다 다음 단어에 대한 확률을 출력한다. ( [ 배치, 출력문장의 최대 길이, 어휘 사전 길이 ])
- 추론시에 디코더는 타깃을 주입하지 못해, SOS으로 시작해 이전 타임스텝에서 출력된 단어를 주입한다. EOS 토큰을 출력할때까지 주입한다.
- 임베딩 층 2개, 스킵연결 5xN 개, 정규화층, 밀집층 2개 ( ReLu, 활성화 없음 ) 로 이루어진 피드포워드 모듈이 2xN개, SoftMax함수를 사용하는 밀집 출력층. 모든 층은 타임스텝에 독립적이다 ( Time-Distributed ). 따라서 각 단어는 다른단어와 독립적으로 처리된다.
- 인코더의 멀티-헤드 어텐션 multi-head attention 층은 관련이 많은 단어에 더 많은 주의를 기울이면서 동일한 문장에 있는 다른 관계를 인코딩한다. 이 메커니즘을 셀프-어텐션self-attention ( 문장 자기 자신에게 주의를 기울인다. )
- 디코더의 마스크드 멀티-헤드 어텐션 masked multi-head attention 층도 위와 비슷한 작업을 하지만, 각 단어는 이전에 등장한 단어에만 주의를 기울일 수 있고, 디코더 위쪽의 멀티-헤드 어텐션 층은 디코더가 입력문장에 있는 단어에 주의를 기울이는 곳이다. ( 현재 단어가 Queen 이면, 입력 문장에 있는 이 단어에 가장 주의를 많이 기울일 것이다. 이해안감 )
- 위치 인코딩positional encoding 은 단어 임베딩과 매우 유사한, 단어의 위치를 나타내는 단순한 밀집 벡터이다. n 번째 위치 인코딩이 n 번째 단어의 임베딩과 더해진다. ( 위치와 의미를 부여하는듯 ) 이로 모델이 각 단어의 위치를 알 수 있다. 멀티-헤드 어텐션 층은 단어의 순서나 위치를 고려하지 않고, 다른 층들은 타임스텝에 독립적으로 각단어의 위치를 알 방법이 없다. 단어의 상대적이거나 절대적인 위치는 중요하기 때문에 정보를 어떻게해서든 전달하기위해 사용된다.
위치 인코딩
i 번째 위치 인코딩과 i번째 단어의 임베딩이 더해져서 사용된다. 모델으로 위치 인코딩을 학습할 수 있지만 ( 보통 임베딩 층과같이 ), 여러 주기의 사인sine, 코사인cosine 함수로 정의한 고정된 위치 인코딩을 사용한다
P_p, i : 문장에서 p 번째 위치 단어 인코딩의 i 번째 원소
위와같은 주기함수를 사용하는 이유는, 위치마다 고유한 위치 인코딩이 만들어지기 때문에 단어 임베딩에 더하면 모델이 문장에 있는 단어의 절대적 위치를 알 수 있다. 이때 d 는 embedding 차원과 동일하다. p 위치에 있는 단어를 i 차원의 벡터로 인코딩할때, 동일한 문장에서 모든 p 위치에 따른 i 차원 벡터는 고유하다. 즉 p = 22, p = 60 는 다르다. 대신, 진동함수 선택에 따라서 상대적인 위치를 학습할 수도 있다. p = 22와 p = 60 의 인코딩 차원 i = 100 번째와, 101번째는 항상 동일한 값을 가진다. 이는 같은 주기의 사인과 코사인을 동시에 사용해야 하는 이유이다. 사인만 사용하게되면, p = 22, p =35 위치를 구별할 수 없다.
i 가 100 일때, p 에 대한 인코딩값은 sin 을 나타내고, PE[ : , i=100] = 위의 파란색
i 가 101 일때, p 에 대한 인코딩 값은 cos 을 나타낸다. PE[ : , 101] = 위의 빨간색
추가적인 이해는 노트북 참조
Google Colaboratory Notebook
Run, share, and edit Python notebooks
colab.research.google.com
멀티-헤드 어텐션
스케일드 점-곱 어텐션
멀티-헤드 어텐션은 스케일드 점-곱 어텐션 scaled dot-product attention 을 기본으로 한다. "They played chess" 라는 문장을 모델이 "They played chess" 를 번역할때, 입력문장에 인코더는 딕셔너리 룩업 형식으로 표현을 만든다. 인코딩된 단어의 표현을 key로, 인코딩된 단어를 value로 갖는다. 이때 인코딩된 단어의 표현은 예를 들면 동사, 주어와 같은 문장내에서 의미를 표현하는 방식이다. ( 훈련하는 동안 학습된다. ) 따라서 룩업에 사용할 키 ( 쿼리query ) 는 딕셔너리의 키와 완벽하게 매칭되지않아서, 룩업 키와, 딕셔너리 키의 유사도를 측정한다. 디코더가 번역하려는 문장내에서의 표현 ( 쿼리 ) 과 딕셔너리 키 간의 유사도 점수를 소프트 맥스함수로 합이 1 인 가중치로 변경한다. 이 가중치들을 딕셔너리 룩업의 value ( 인코딩된 단어들 ) 와 가중치 합을 구한다. 동사를 의미하는 키 가중치가 1에 가까우면, 가중치 합은 played 와 매우 가까워질 것이다. 전체과정을 미분가능한 딕셔너리 룩업으로 생각할 수 있다.
- Q는 행마다 쿼리 하나를 담은 행렬이다. [n_queries, d_keys], n_queries 는 쿼리 개수, d_keys 는 키의 차원 개수
- K는 행마다 키 하나를 담은 행렬이다. [n_keys, d_keys], n_keys 는 키와 값의 개수
- V는 행마다 값 하나를 담은 행렬이다. [n_keys, d_values], d_values 는 값의 차원
- QT^T 는 쿼리/키 쌍마다 하나의 유사도 점수를 담는다. [n_queries, n_keys] , 최종 Attention의 출력은 [n_queries, d_values] 의 크기. 각 행은 쿼리 결과 ( 값의 가중치 합, 유사한 단어표현들의 가중치 합들... )를 나타낸다.
- 스케일링 인자는 소프트맥스 함수가 포화되어 그레디언트가 너무 작아지지 않도록 유사도 점수를 낮춘다.
- 소프트 맥스 함수 계산전에, 유사도 점수에 아주큰 음수를 더해서 일부 키/값 쌍을 제외하도록 마스킹 처리할 수 있다. 이것이 마스크된 멀티-헤드 어텐션 ( 디코더에서 ) 에서 사용하는 방법이다.
keras.layers.Attention(use_scale=True, causal=True) 에서 use_scale=True 은 유사도 점수의 스케일을 적절히 낮추는 방법을 학습하고 ( 항상 sqrt(d_keys)로 나누는 트랜스포머와 조금 다르다. ), causal=True 는 각 출력 토큰은 미래 토큰이 아니라 이전 출력 토큰에만 주의를 기울인다.
멀티-헤드 어텐션
스케일드 점-곱 어텐션 층의 묶음이 멀티-헤드 어텐션이다. 각 층은 값, 키, 쿼리의 선형 변환이 선행되고 ( 편향 없는 Dense ) 어텐션층들의 출력은 단순히 연결되서 마지막 선형 변환이 수행된다. 여러개의 어텐션층을 사용하는 이유는, 하나의 played라는 한 단어 표현에는 동사라는 표현, 단어의 위치 인코딩 표현, 과거형과 같은 표현등 여러 표현이 합쳐져있는데, 하나의 어텐션 층만 사용하면 이런 특징들을 한번에 처리할 수 밖에없다. 따라서 이런 특징들을 선형변환으로 여러 단순화된 표현들로 투영시킨후, 하나의 어텐션 층에서 룩업단계를 진행하고 다시 원본 공간으로 투영하는 방식이다.