2021.11.08 - [딥러닝관련/기초 이론] - seq2seq, attention 정리
위 글에 이어 transformer를 정리해보고자 한다.
(내가 아는 트랜스포머는 마이클베이의 트랜스포....)
Transformer가 나온 논문은 아래와 같다고 한다.
https://arxiv.org/abs/1706.03762
[모델 구조]
- seq2seq 구조
- encoder-decoder
- attention만으로 모델 구현
- RNN 사용하지 않음
[seq2seq 모델의 한계]
- Encoder가 입력 sequence를 하나의 vector로 압축하는 과정에서 정보가 일부 손실된다. (문제점)
(-> 이를 해결하기 위해 attention이 사용됨)
-> 그런데 attention을 RNN의 보정을 위한 용도가 아니라 아예 어텐션으로 encoder와 decoder를 만들면?
[transformer의 주요 하이퍼파라미터]
- encoder와 decoder에서의 정해진 입력과 출력의 크기
- embedding 차원도 이에 해당
- 각 encoder와 decoder가 다음 층의 encoder와 decoder로 값을 보낼 때도 이 차원을 유지
- encoder decoder의 layer 수
- attention 사용 시, 1번 하는 것 보다 여러 개로 분할해서 병렬로 attention을 수행
- 결과값을 다시 하나로 합치는 방식
- 이 때 병렬의 개수
- hidden layer의 크기
[transformer]
- seq2seq와 같이 encoder-decoder 구조.
다른점
- encoder, decoder 단위가 N개 존재할 수 있다는 점.
이전 seq2seq는 encoder-decoder에서 각각 하나의 RNN이 t개의 시점을 가지는 구조라면,
트랜스포머는 encoder-decoder라는 단위가 N개로 구성되는 구조
아래를 보면 encoder-decoder를 6개씩 사용
아래는 encoder로부터 정보를 받아 decoder가 출력을 만들어내는 트랜스포머 구조
단순하게 보면 RNN이 없는 encoder-decoder와 같다.
[Positional Encoding]
RNN이 자연어 처리에서 유용했던 이유
- 단어의 위치에 따라 단어를 순차적으로 입력받아서 처리하는 RNN의 특성으로 인해
position information을 가질 수 있다는 점
하지만 transformer는 단어 입력을 순차적으로 받는 방식이 아니다.
따라서 단어의 위치 정보를 다른 방식으로 알려줄 필요가 있다.
transformer는 단어의 위치 정보를 얻기 위해서,
각 단어의 embedding vector에 위치 정보들을 더하여 모델의 입력으로 사용
이를 positional encoding
위 그림은,
입력으로 사용되는 embedding vector들이
transformer의 입력으로 사용되기 전에
positional encoding 값이 더해지는 것을 보여줌
위는 encoder의 입력으로 들어가기 전 positional encoding과 embedding vector가 더해지는 과정
그렇다면 positional encoding 값들은 어떤 값?
positional encoding은 위의 함수를 사용
사인 함수와 코사인 함수의 값을 embedding vector에 더해주므로서 단어의 순서 정보를 더하여 준다.
위 함수를 이해하려면 embedding vector와 positional encoding의 덧셈은
embedding vector가 모여 만들어진 문장 vector matrix와
positional encoding matrix의 덧셈 연산을 통해 이루어진다는 점을 이해
pos : embedding vector의 위치
i : embedding vector 내의 차원 index
앞서 언급한 사인 코사인 식에 따르면,
i가
짝수(pos, 2i)인 경우에는 사인 함수의 값을 사용
홀수(pos, 2i+1)인 경우에는 코사인 함수를 사용
$$d_{model}$$
- transformer 모든 layer의 output dimension을 의미하는 transformer의 hyper-parameter
- embedding vector의 차원으로도 쓰임(실제 논문은 512차원이라고 함)
위와 같은 positional encoding 방법을 사용하면 순서 정보가 보존된다.
예를 들어,
embedding vector에 positional encoding 값을 더하면
같은 단어라고 하더라도 문장 내의 위치에 따라서,
transformer의 입력으로 들어가는 embedding vector의 값이 달라진다.
-> transformer의 입력은 순서 정보가 고려된 embedding vector
[Attention]
transformer에서 사용하는 세 가지의 attention
Self Attention : Query, Key, Value가 동일한 경우(encoder에서 이뤄짐)
Encoder-Decoder Attention : Query(decoder vector)와 Key, Value(encoder vector)가 다름
(Self attention이라고 부르지 않음)
-> Query, Key, Value가 같다는 것은 값이 아닌 vector의 출처가 같다는 의미
이전 글에서 아래와 같이 언급하였다.
Query: t 시점의 decoder 셀에서의 hidden state
Keys: 모든 시점의 encoder 셀의 hidden state
Values: 모든 시점의 encoder 셀의 hidden state
더 쉽게 생각하면
Query : 영향을 받는 단어
Key : 영향을 주는 단어
Value : 영향에 대한 가중치
라고 볼 수 있다.
Encoder Self-attention
Query = Key = Value
Masked Decoder Self-attention
Query = Key = Value
Encoder-Decoder attention
Query : 디코더 벡터 / Key = Value : 인코더 벡터
위 이미지는 각각 transformer의 구조에서 세 가지 attention이 각각 어디에서 이루어지는지
Multi-head : attention을 병렬적으로 수행하는 방법
[Encoder]
- num_lyaers 수 만큼 encoder의 layer을 쌓는다.
- 하나의 layer는 두 개의 sub layer로 나뉘어진다.
1. Multi-head self attention
(self attention을 병렬로)
2. Position-wise Feed Forward Neural Network
(일반적인 feed-forward 신경망)
[Encoder self-attention]
1) Self attention의 의미와 이점
Attention 함수
- 주어진 '쿼리(Query)'에 대해서 모든 '키(Key)'와의 유사도 계산
- 구해낸 이 유사도를 weight로 하여 key와 mapping되어 있는 각각의 '값(Value)'에 반영
유사도가 반영된 '값(Value)'을 모두 weighted-sum 하여 리턴
그렇다면 self-attention 이란
-> 말그대로 자기 자신에게 attention을 진행한다는 의미
기존의 attention
Query : t 시점의 decoder 셀에서의 hidden state
Keys : 모든 시점의 encoder 셀의 hidden states
Values : 모든 시점의 encoder 셀의 hidden states
그런데 t시점이라는 것은 계속 변화하면서 반복적으로 쿼리를 수행
이는 전체 시점에 대해서 일반화
Querys : 모든 시점의 decoder 셀에서의 hidden states
Keys : 모든 시점의 encoder 셀의 hidden states
Values : 모든 시점의 encoder 셀의 hidden states
위와 같이 기존에는 decoder 셀의 hidden state가 Q이고,
encoder 셀의 hidden state가 K라는 점에서 Q와 K가 서로 다른 값을 가지고 있다.
하지만 이것이 self attention에서는 Q, K, V가 모두 동일한 값을 가지고 있다.
Q : 입력 문장의 모든 단어 벡터들
K : 입력 문장의 모든 단어 벡터들
V : 입력 문장의 모든 단어 벡터들
self-attention의 효과
- 우리는 오른쪽의 it이 animal인 것을 쉽게 알 수 있지만 기계는 그렇지 않다.
- self-attention은 입력 문장 내의 단어들끼리 유사도를 구하므로써
it이 animal과 연관되었을 확률이 높다는 것을 찾아냄.
2) Q, K, V vector 얻기
Self-attention은 Q, K, V에 대해서 초기 입력인 d_model의 차원을 가지는 단어 vector보다 더 작은 차원을 가진다.
논문에서는 d_model = 512 차원을 가졌던 각 단어 vector들을 64의 차원을 가지는 Q, K, V 벡터로 변환
64라는 값은 트랜스포머의 다른 하이퍼파라미터인 num_heads로 결정
위는 student라는 단어 vector를 Q, K, V vector로 변환하는 과정.
기존 vector * 작은 weight vector = 작은 vector
weight vector는 d_model x (d_model / num_heads)의 크기를 가진다
위 weight matrix는 훈련 과정에서 학습된다.
d_model = 512
num_heads = 8
Q, K, V channel = 512 / 8 ---> 64
3) Scaled dot-product attention
위에서 Q, K, V vector를 얻었다면 나머지는 기존의 attention mechanism과 동일
Q vector는 모든 K vector에 대하여 attention score를 구한 후,
attention distribution을 구한 뒤에,
이를 사용하여 모든 V vector를 weighted sum하여 attention value 아니면 context vector를 구함.
이를 모든 Q에 대해서 반복.
Transformer에서는 간단한 dot product를 사용하는 것이 아닌,
여기에 특정값으로 나눠준 attention 함수인 아래 식 사용
(기존 dot product attention에서 값을 스케일링한 효과)
--> Scaled dot-product attention
I am a student 라는 단어가 있다고 가정
윈는 단어 I에 대한 Q vector는 모든 K vector에 대해서 attention score를 구하는 것을 보여줌
위 그림에서 attention score는 각각 단어 I가 단어 I, am, a, student와 얼마나 연관이 있나 보여주는 수치
Transformer에서는 두 vector의 dot-product를 scaling하는 값으로,
K vector의 dimension을 나타내는 d_k에 루트를 씌운 형태
d_k = d_model / num_heads
-> 64
-> 루트 d_k
->8
이제 attention score에 softmax 함수를 사용하여 Attention distribution을 구한 후,
각 V vector와 weighted sum하여 attention value를 구한다.
--> 이를 단어 I에 대한 attention value 또는 단어 I에 대한 context vector라고 부름.
--> 이제 I가 끝났으니, am, a student에 대해 모두 수행해야 한다.
4) 행렬 연산으로 일괄처리
한 단어씩 하지 말고 행렬 연산을 통해 일괄적으로 연산을 수행할 수 있다.
Attention score 계산
위의 결과 행렬의 값에 전제척으로 루트 d_k를 나누어주면,
각 행과 열이 attention score 값을 가지는 행렬.
Attention matrix를 구했다면 attention 분포를 구하고, 이를 이용하여 모든 단어에 대한 attention score를 구하는 일.
score 행렬에 softmax 함수를 사용하고, v matrix 을 곱함
이렇게 되면 각 단어의 attention 값을 모두 가지는 attention value 행렬이 결과
행렬 연산을 통해 모든 값이 일괄 계산되는 과정을 식
입력 문장의 길이를 seq_len이라고 가정
문장 행렬의 크기 : (seq_len, d_model)
-> weight matrix를 곱해 Q, K, V를 만들어야 함
Q, K matrix 크기 (seq_len, d_k)
V matrix 크기 (seq_len, d_v)
W^q, W^k는 -> (d_model, d_k)
W^v -> (d_model, d_v)의 크기를 가짐
단, 논문에서 d_k와 d_v의 크기는 d_model / num heads과 같음.
결과적으로 위 softmax 수식을 적용하여 나오는 attention value 행렬 a의 크기는 (seq_len, d_v)
5. Scaled dot-product attention 구현
# source code from https://wikidocs.net/31379
def scaled_dot_product_attention(query, key, value, mask):
# query 크기 : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
# key 크기 : (batch_size, num_heads, key의 문장 길이, d_model/num_heads)
# value 크기 : (batch_size, num_heads, value의 문장 길이, d_model/num_heads)
# padding_mask : (batch_size, 1, 1, key의 문장 길이)
# 1. Q와 K의 곱. 어텐션 스코어 행렬.
matmul_qk = tf.matmul(query, key, transpose_b=True)
# 2. 스케일링
# dk의 루트값으로 나눠준다.
depth = tf.cast(tf.shape(key)[-1], tf.float32)
logits = matmul_qk / tf.math.sqrt(depth)
# 마스킹. 어텐션 스코어 행렬의 마스킹 할 위치에 매우 작은 음수값을 넣는다.
# 매우 작은 값이므로 소프트맥스 함수를 지나면 행렬의 해당 위치의 값은 0이 된다.
if mask is not None:
logits += (mask * -1e9)
# 3. 소프트맥스 함수는 마지막 차원인 key의 문장 길이 방향으로 수행된다.
# attention weight : (batch_size, num_heads, query의 문장 길이, key의 문장 길이)
attention_weights = tf.nn.softmax(logits, axis=-1)
# 4. output : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
output = tf.matmul(attention_weights, value)
return output, attention_weights
# 임의의 Query, Key, Value인 Q, K, V 행렬 생성 ---> 입력
np.set_printoptions(suppress=True)
temp_k = tf.constant([[10,0,0],
[0,10,0],
[0,0,10],
[0,0,10]], dtype=tf.float32) # (4, 3)
temp_v = tf.constant([[ 1,0],
[ 10,0],
[ 100,5],
[1000,6]], dtype=tf.float32) # (4, 2)
temp_q = tf.constant([[0, 10, 0]], dtype=tf.float32) # (1, 3) ---> temp_k의 두 번쨰 값과 일치
# 함수 실행
temp_out, temp_attn = scaled_dot_product_attention(temp_q, temp_k, temp_v, None)
print(temp_attn) # 어텐션 분포(어텐션 가중치의 나열)
print(temp_out) # 어텐션 값
tf.Tensor([[0. 1. 0. 0.]], shape=(1, 4), dtype=float32)
tf.Tensor([[10. 0.]], shape=(1, 2), dtype=float32)
Query는 4개의 Key값 중 두번째 값과 일치하므로 어텐션 분포는 [0, 1, 0, 0]의 값
value의 두 번째 값인 [10, 0] 이 출력
Query값 [0, 0, 10]로 했을 때
Key의 세번째 값과, 네번째 값 두 개의 값 모두와 일치하는 값
temp_q = tf.constant([[0, 0, 10]], dtype=tf.float32)
temp_out, temp_attn = scaled_dot_product_attention(temp_q, temp_k, temp_v, None)
print(temp_attn) # 어텐션 분포(어텐션 가중치의 나열)
print(temp_out) # 어텐션 값
tf.Tensor([[0. 0. 0.5 0.5]], shape=(1, 4), dtype=float32)
tf.Tensor([[550. 5.5]], shape=(1, 2), dtype=float32)
Query의 값은 Key의 세번째 값, 네번째 값
모두 유사하다는 의미에서
attention 분포는 [0, 0, 0.5, 0.5]
결과적으로 나오는 값
[550, 5.5]
--> [100, 5] * 0.5 + [1000, 6] * 0.5
(weighted sum)
입력이 여러개라면
temp_q = tf.constant([[0, 0, 10], [0, 10, 0], [10, 10, 0]], dtype=tf.float32) # (3, 3)
temp_out, temp_attn = scaled_dot_product_attention(temp_q, temp_k, temp_v, None)
print(temp_attn) # 어텐션 분포(어텐션 가중치의 나열)
print(temp_out) # 어텐션 값
tf.Tensor(
[[0. 0. 0.5 0.5]
[0. 1. 0. 0. ]
[0.5 0.5 0. 0. ]], shape=(3, 4), dtype=float32)
tf.Tensor(
[[550. 5.5]
[ 10. 0. ]
[ 5.5 0. ]], shape=(3, 2), dtype=float32)
6. Multi-head attention
위에서 input vector(d_models)에 대해서
num_heads로 나눈 차원을 가지는 Q. K, V vector로 바꾸고 attention 수행
그러면 왜 차원을 축소 시켰는지 알아봄
transformer는 한 번의 attention을 하는 것보다
여러번의 어텐션을 병렬로 사용하는 것이 효과적이라고 판단.
따라서 d_model의 차원을 num_heads로 나누어
d_model/num_heads의 차원을 가지는 Q, K, V에 대해서 num_heads 개의
병렬 attention을 수행
num_heads가 8이면 8개의 병렬 attention이 수행.
이때 각각의 attention value matrix를 attention head.
W^Q, W^K, W^V의 값은 8개의 attention head마다 전부 다르다.
병렬 attention의 효과
-> 다른 시각으로 정보 수집
ex) '그 동물은 길을 건너지 않았다. 왜냐하면 그것은 너무 피곤하였기 때문이다.'
첫번째 attention head는 '그것(it)'과 '동물(animal)'의 연관도를 높게 본다
두번째 어텐션 헤드는 '그것(it)'과 '피곤하였기 때문이다(tired)'의 연관도를 높게 본다
병렬 attention을 수행하면 위와 같이 모든 attention head를 concatenate 한다.
모두 연결된 attention matrix의 행렬 크기는 (seq_len, d_model)
(detail한 차원은 논문과 다르기 때문에 동작 방식만 보아라.)
Attention head를 모두 연결한 행렬(concatednated matrix)는 또 다른 weight matrix W^0을 곱한다.
이렇게 나온 결과가 multi-head attention의 최종 결과물.
7. Multi-head attention 구현
Multi-head attention에는 두 종류의 weight matrix 행렬이 있음.
1. Q, K, V 행렬을 만들기 위한 weight matrix인 WQ, WK, WV matrix
2. Attention head들을 concatenation 후에 곱해주는 WO 행렬
Multi-head attention 구현 크게 다섯 가지 파트로 구성.
1. WQ, WK, WV에 해당하는 d_model 크기의 Dense layer을 지나게한다.
2. 지정된 헤드 수(num_heads)만큼 split.
3. scaled dot-product attention.
4. 나눠졌던 헤드들을 concatenatetion한다.
5. WO에 해당하는 Dense layer을 지나게 한다.
# 2021-11-10 code originated from https://wikidocs.net/31379
class MultiHeadAttention(tf.keras.layers.Layer):
def __init__(self, d_model, num_heads, name="multi_head_attention"):
super(MultiHeadAttention, self).__init__(name=name)
self.num_heads = num_heads
self.d_model = d_model
assert d_model % self.num_heads == 0
# d_model을 num_heads로 나눈 값.
# 논문 기준 : 64
self.depth = d_model // self.num_heads
# WQ, WK, WV에 해당하는 밀집층 정의
self.query_dense = tf.keras.layers.Dense(units=d_model)
self.key_dense = tf.keras.layers.Dense(units=d_model)
self.value_dense = tf.keras.layers.Dense(units=d_model)
# WO에 해당하는 밀집층 정의
self.dense = tf.keras.layers.Dense(units=d_model)
# num_heads 개수만큼 q, k, v를 split하는 함수
def split_heads(self, inputs, batch_size):
inputs = tf.reshape(
inputs, shape=(batch_size, -1, self.num_heads, self.depth))
return tf.transpose(inputs, perm=[0, 2, 1, 3])
def call(self, inputs):
query, key, value, mask = inputs['query'], inputs['key'], inputs[
'value'], inputs['mask']
batch_size = tf.shape(query)[0]
# 1. WQ, WK, WV에 해당하는 밀집층 지나기
# q : (batch_size, query의 문장 길이, d_model)
# k : (batch_size, key의 문장 길이, d_model)
# v : (batch_size, value의 문장 길이, d_model)
# 참고) 인코더(k, v)-디코더(q) 어텐션에서는 query 길이와 key, value의 길이는 다를 수 있다.
query = self.query_dense(query)
key = self.key_dense(key)
value = self.value_dense(value)
# 2. 헤드 나누기
# q : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
# k : (batch_size, num_heads, key의 문장 길이, d_model/num_heads)
# v : (batch_size, num_heads, value의 문장 길이, d_model/num_heads)
query = self.split_heads(query, batch_size)
key = self.split_heads(key, batch_size)
value = self.split_heads(value, batch_size)
# 3. 스케일드 닷 프로덕트 어텐션. 앞서 구현한 함수 사용.
# (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
scaled_attention, _ = scaled_dot_product_attention(query, key, value, mask)
# (batch_size, query의 문장 길이, num_heads, d_model/num_heads)
scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])
# 4. 헤드 연결(concatenate)하기
# (batch_size, query의 문장 길이, d_model)
concat_attention = tf.reshape(scaled_attention,
(batch_size, -1, self.d_model))
# 5. WO에 해당하는 밀집층 지나기
# (batch_size, query의 문장 길이, d_model)
outputs = self.dense(concat_attention)
return outputs
8. Padding Mask
앞에 scaled_dot_product_attention 구현한 함수를 보면 mask 값에 대해서 작은 값을 더해준다.
def scaled_dot_product_attention(query, key, value, mask):
... 중략 ...
logits += (mask * -1e9) # 어텐션 스코어 행렬인 logits에 mask*-1e9 값을 더해주고 있다.
... 중략 ...
이는 입력 문장에 <PAD> 토큰이 있을 경우 attention에서 제외하기 위한 연산.
<PAD>가 포함된 입력 문장의 self-attention
<PAD>는 실질적인 의미를 가진 단어가 아니다.
Transformer에서는 Key의 경우에 <PAD> token이 존재한다면
이에 대해서는 유사도를 구하지 않도록 masking을 해줌
masking의 이미 : attention에서 제외하기 위해 값을 가린다는 의미
Attention score matrix에서
행에 해당하는 문자는 query,
열에 해당하는 문자는 key,
Key에 <pad>가 있는 경우 열 전체에 masking
masking : attention score matrix의 masking 위치에 매우 작은 음수값을 넣어주는 것
[연산]
위 attention score matrix는 softmax를 거치기 전.
현재 masking 위치에 매우 작은 음수 값이 있으므로,
attention score matrix가 softmax를 거치면 0에 매우 가까웃 숫자가 나옴.
유사도 구하는 일에 <pad>토큰이 반영되지 않음
padding mask를 구현하는 방법
입력된 정수 시퀀스에서 패딩 토큰의 인덱스인지, 아닌지를 판별하는 함수를 구현
아래 함수는 정수 시퀀스에서 0인 경우 1로 변환, 그렇지 않은 경우 0으로 변환
def create_padding_mask(x):
mask = tf.cast(tf.math.equal(x, 0), tf.float32)
# (batch_size, 1, 1, key의 문장 길이)
return mask[:, tf.newaxis, tf.newaxis, :]
아래의 입력을 넣었을 때
print(create_padding_mask(tf.constant([[1, 21, 777, 0, 0]])))
결과
tf.Tensor([[[[0. 0. 0. 1. 1.]]]], shape=(1, 1, 1, 5), dtype=float32)
1의 값을 가진 위치의 열을 어텐션 스코어 행렬에서 마스킹하는 용도로 사용할 수 있음
[Position-wise FFNN]
- encoder와 decoder에서 공통적으로 가지고 있는 sub layer
- Fully conntected Feed Forward Neural Network로 볼 수 있다.
위 식을 그림으로 표현하면 아래와 같다.
x : multi head attention의 결과 (seq_len, d_model)의 크기를 가지는 행렬
W_1 : (d_model, d_ff) 의 크기
W_2 : (d_ff, d_model) 의 크기
d_ff : 2,048(hyper parameter)
W_1, b_1, W_2, b_2 : 하나의 encoder layer 내에서는 다른 문장, 단어들마다 정확하게 동일하게 사용
하지만 encoder layer마다는 다른 값을 가짐.
위쪽 왼편 이미지는,
encoder의 입력을 vector 단위로 보았을 때,
각 vector들이 multi-head attention이라는 encoder 내 sub layer를 지나 FFNN을 통과하는 것
(Position-wise FFNN)
실제로는 위 이미지가 입력으로 들어감
두 번재 sub layer를 지난 encoder의 최종 출력은
여전히 encoder의 입력 크기였던 (seq_len, d_model) 이 유지
[Residual connection 과 layer normalization]
- Add & norm 이 추가됨
- Add : residual connection
- Norm : layer normalization
Residual Connection
- F(x) : sub layer에 해당
Residual connection
위는 residual connection의 과정
Layer Normalization
Residual connection 이후 layer normalization을 거침
Normalization하는 과정
d_model에 해당하는 차원에 대해서 normalization
평균과 분산을 구한 뒤 화살표 방향 vector를 x라고 명명하면
layer normalization 후 vector x는 ln이라는 vector로 normalization 된다.
layer normalization의 수식
1. 평균과 분산을 통한 normalization
2. Gamma, beta를 도입하는 것
k차원
분모 0 방지를 위해 epsilon
1과 0으로 초기화된 gamma, beta vector 준비
gamma와 beta를 학습 가능한 파라미터
최종 수식
[Encoder 구현]
# source code is originated from https://wikidocs.net/31379
def encoder_layer(dff, d_model, num_heads, dropout, name="encoder_layer"):
inputs = tf.keras.Input(shape=(None, d_model), name="inputs")
# 인코더는 패딩 마스크 사용
padding_mask = tf.keras.Input(shape=(1, 1, None), name="padding_mask")
# 멀티-헤드 어텐션 (첫번째 서브층 / 셀프 어텐션)
attention = MultiHeadAttention(
d_model, num_heads, name="attention")({
'query': inputs, 'key': inputs, 'value': inputs, # Q = K = V
'mask': padding_mask # 패딩 마스크 사용
})
# 드롭아웃 + 잔차 연결과 층 정규화
attention = tf.keras.layers.Dropout(rate=dropout)(attention)
attention = tf.keras.layers.LayerNormalization(
epsilon=1e-6)(inputs + attention)
# 포지션 와이즈 피드 포워드 신경망 (두번째 서브층)
outputs = tf.keras.layers.Dense(units=dff, activation='relu')(attention)
outputs = tf.keras.layers.Dense(units=d_model)(outputs)
# 드롭아웃 + 잔차 연결과 층 정규화
outputs = tf.keras.layers.Dropout(rate=dropout)(outputs)
outputs = tf.keras.layers.LayerNormalization(
epsilon=1e-6)(attention + outputs)
return tf.keras.Model(
inputs=[inputs, padding_mask], outputs=outputs, name=name)
[Encoder 쌓기]
def encoder(vocab_size, num_layers, dff,
d_model, num_heads, dropout,
name="encoder"):
inputs = tf.keras.Input(shape=(None,), name="inputs")
# 인코더는 패딩 마스크 사용
padding_mask = tf.keras.Input(shape=(1, 1, None), name="padding_mask")
# 포지셔널 인코딩 + 드롭아웃
embeddings = tf.keras.layers.Embedding(vocab_size, d_model)(inputs)
embeddings *= tf.math.sqrt(tf.cast(d_model, tf.float32))
embeddings = PositionalEncoding(vocab_size, d_model)(embeddings)
outputs = tf.keras.layers.Dropout(rate=dropout)(embeddings)
# 인코더를 num_layers개 쌓기
for i in range(num_layers):
outputs = encoder_layer(dff=dff, d_model=d_model, num_heads=num_heads,
dropout=dropout, name="encoder_layer_{}".format(i),
)([outputs, padding_mask])
return tf.keras.Model(
inputs=[inputs, padding_mask], outputs=outputs, name=name)
[From Encoder to Decoder]
참고
https://blog.pingpong.us/transformer-review/
'딥러닝관련 > 기초 이론' 카테고리의 다른 글
Swin Transformer (0) | 2021.11.25 |
---|---|
Vision Transformer (ViT) 정리 : An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale (0) | 2021.11.12 |
seq2seq, attention 정리 (0) | 2021.11.08 |
선형(linear) vs 비선형(non-linear) (4) | 2021.11.05 |
Squeeze-and-Excitation Networks(SENET) (0) | 2021.10.19 |