목차
INRTO
[NMT(Neural Machine Translation)]
Transformer를 NMT에 써보겠다.
Transformer 아키텍처의 기초를 단계별로 살펴보고 이를 사용해 영어 문장을 독일어 문장으로 번역해 보겠습니다.
📜 NMT(Neural Machine Translation) 더 알아 보기
[NMT(Neural Machine Translation)의 개념]
NLP 작업 중 하나로,
인공신경망을 활용한 기계학습을 통해 언어 번역 모델 생성 및 번역 서비스 제공 기술
NMT는 자동화된 언어 번역을 위한 엔드 투 엔드 러닝 접근 방식
[NMT의 원리]
머신러닝 기술이 적용된 엔진을 통해 전체 문맥 파악 후 문장 내 단어, 순서, 문맥 의미차이 등 반영하여 문장단위 결과 출력
출처: Nvidia
[목표]
영어 문장을 독일어 문장으로 번역
[읽기 위해 필요한 지식]
- attention
- transformer: 정말 무조건무조건 정독하자 이거 읽고오면 이번 포스트는 껌이다.
[원 논문]
Attention is All You Need
[전체적인 transefomer 구조]
출처: 구글
👀, 🤷♀️ , 📜
이 아이콘들을 누르시면 코드, 개념 부가 설명을 보실 수 있습니다:)
Transformer 모델 개요
PyTorch 모듈 기본 클래스를 기반으로 기본 Transformer 모델 코드
기본 모델은 아래의 forward 메서드에 나와 있음
➡ 데이터가 인코더를 통과한 다음 디코더를 통과함
👀코드 보기
import torch.nn as nn
class TransformerModel(nn.Module):
def __init__(self, encoder, decoder):
super().__init__()
self._is_generation_fast = False
self.encoder = encoder
self.decoder = decoder
def forward(self, src_tokens, src_lengths, prev_output_tokens):
encoder_out, padding_mask = self.encoder(src_tokens, src_lengths)
decoder_out = self.decoder(prev_output_tokens, encoder_out, padding_mask)
return decoder_out
Encoder
개요
Transformer 논문에서 인코더와 디코더 모두 각각 𝐍=6 개의 동일한 레이어로 구성되므로 총 레이어는 12개입니다.
- 6개의 인코더 레이어에는 각각 2개의 하위 레이어가 있습니다.
- 첫 번째 하위 레이어: Multi-Head Self Attention 메커니즘
- 두 번째 하위 레이어: 단순한 Fully connected feed forward 네트워크
인코더: 소스 문장을 숨겨진 상태 벡터로 인코딩하는 데 사용 됨
디코더: 상태 벡터의 마지막 표현을 사용하여 대상 언어의 문자를 예측 함
인코드 블록 안
인코더 코드
여러 개의 TransformerEncoderLayers
(기본 6개)가 self.layers
에 추가되어 모델을 forward
로 통과할 때 호출됨.
TransformerEncoder.forward()
가 호출되면,
- 1️⃣ 입력 소스 토큰(
src_tokens
)이 임베딩된(embed_tokens
사용) 다음 포지셔널 인코딩에 추가됨(추후 설명) - 2️⃣ 드롭아웃 및 일부 텐서 조작 후 임베딩된 토큰
x
가for layer in self.layers
를 시작하는 루프에서 6개의TranformerEncoderLayers
각각을 통과함
👀 smplify 코드 보기
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.nn.functional as F
import math, copy, time
import seaborn as sns
from encoder_demos.demo_fairseq.models.fairseq_model import BaseFairseqModel, FairseqDecoder, FairseqEncoder
from encoder_demos.demo_fairseq.models.fairseq_incremental_decoder import FairseqIncrementalDecoder
import torch.nn as nn
class TransformerEncoder(nn.Module):
"""Transformer encoder."""
def __init__(self, args, embed_tokens, left_pad=True):
super().__init__()
self.dropout = args.dropout
self.fuse_dropout_add = args.fuse_dropout_add
self.fuse_relu_dropout = args.fuse_relu_dropout
embed_dim = embed_tokens.embedding_dim
self.padding_idx = embed_tokens.padding_idx
self.max_source_positions = args.max_source_positions
self.embed_tokens = embed_tokens
self.embed_scale = math.sqrt(embed_dim)
self.embed_positions = PositionalEmbedding(
args.max_source_positions, embed_dim, self.padding_idx,
left_pad=left_pad,
learned=args.encoder_learned_pos,
) if not args.no_token_positional_embeddings else None
# 2️⃣.2 for문에 붙어있는 함수 실행
self.layers = nn.ModuleList([])
self.layers.extend([
TransformerEncoderLayer(args)
for i in range(args.encoder_layers)
])
self.normalize = args.encoder_normalize_before
if self.normalize:
self.layer_norm = FusedLayerNorm(embed_dim) if args.fuse_layer_norm else nn.LayerNorm(embed_dim)
# ```TransformerEncoder.forward()```가 호출됨
def forward(self, src_tokens, src_lengths):
# 1️⃣ embed tokens and positions
x = self.embed_scale * self.embed_tokens(src_tokens)
if self.embed_positions is not None:
x += self.embed_positions(src_tokens)
x = F.dropout(x, p=self.dropout, training=self.training)
# B x T x C -> T x B x C
# The tensor needs to copy transposed because
# fused dropout is not capable of handing strided data
if self.fuse_dropout_add :
x = x.transpose(0, 1).contiguous()
else :
x = x.transpose(0, 1)
# compute padding mask
encoder_padding_mask = src_tokens.eq(self.padding_idx)
if not encoder_padding_mask.any():
_encoder_padding_mask = None
else:
_encoder_padding_mask = encoder_padding_mask
# 2️⃣.1 encoder layers : self.layers를 시작하는 루프에서 6개의 TranformerEncoderLayers 각각을 통과
for layer in self.layers:
x = layer(x, _encoder_padding_mask)
if self.normalize:
x = self.layer_norm(x)
return x, encoder_padding_mask # x.shape == T x B x C, encoder_padding_mask.shape == B x T
👀 전체 encoder 코드 보기
위의 경우는 이해를 돕기 위해 transformer 중 설명할 일부를 추출해온 것이다.
아래의 경우는 전체 코드이므로 참조만 해두자
forward 메서드를 살펴보면,
- 1️⃣ 임베딩된 토큰 x가 Self-Attention 메커니즘을 통과함(아래 설명 참조)
self.fuse_dropout
- 2️⃣ 기본적으로
self.fuse_dropout_add
는 True: Self Attention의 결과가 선형 레이어 fc1을 통과하고 나서 드롭아웃이 적용됨 - 3️⃣
self.fuse_relu_dropout은 False
: 두 번째 선형 레이어 fc2를 통과(드롭아웃 X) ➡ 출력층에 가까우면 드롭아웃 적용 안함
- 2️⃣ 기본적으로
class TransformerEncoderLayer(nn.Module):
"""Encoder layer block.
In the original paper each operation (multi-head attention or FFN) is
postprocessed with: dropout -> add residual -> layernorm.
In the tensor2tensor code they suggest that learning is more robust when
preprocessing each layer with layernorm and postprocessing with:
dropout -> add residual.
We default to the approach in the paper, but the tensor2tensor approach can
be enabled by setting `normalize_before=True`.
"""
def __init__(self, args):
super().__init__()
self.embed_dim = args.encoder_embed_dim
self.self_attn = MultiheadAttention(
self.embed_dim, args.encoder_attention_heads,
dropout=args.attention_dropout,
)
self.dropout = args.dropout
self.relu_dropout = args.relu_dropout
self.fuse_dropout_add = args.fuse_dropout_add
self.fuse_relu_dropout = args.fuse_relu_dropout
self.normalize_before = args.encoder_normalize_before
self.fc1 = Linear(self.embed_dim, args.encoder_ffn_embed_dim)
self.fc2 = Linear(args.encoder_ffn_embed_dim, self.embed_dim)
self.layer_norms = nn.ModuleList([FusedLayerNorm(self.embed_dim) for i in range(2)])
def forward(self, x, encoder_padding_mask):
residual = x
x = self.maybe_layer_norm(0, x, before=True)
# 1️⃣ Self-Attention 통과
x, _ = self.self_attn(query=x, key=x, value=x, key_padding_mask=encoder_padding_mask)
# 2️⃣ 드롭아웃이 적용
if self.fuse_dropout_add and self.training :
x = jit_dropout_add(x, residual, self.dropout, self.training)
else :
x = F.dropout(x, p=self.dropout, training=self.training)
x = residual + x
x = self.maybe_layer_norm(0, x, after=True)
residual = x
x = self.maybe_layer_norm(1, x, before=True)
# 3️⃣ 드롭아웃 X
if self.fuse_relu_dropout :
x = jit_relu_dropout(self.fc1(x), self.relu_dropout, self.training)
else :
x = F.threshold(self.fc1(x),0,0)
x = F.dropout(x, p=self.relu_dropout, training=self.training)
x = self.fc2(x)
if self.fuse_dropout_add and self.training :
x = jit_dropout_add(x, residual, self.dropout, self.training)
else :
x = F.dropout(x, p=self.dropout, training=self.training)
x = residual + x
x = self.maybe_layer_norm(1, x, after=True)
return x
def maybe_layer_norm(self, i, x, before=False, after=False):
assert before ^ after
if after ^ self.normalize_before:
return self.layer_norms[i](x)
else:
return x
Embedding
단어 임베딩은 네트워크가 텍스트 데이터에서 학습할 수 있는 능력을 개선하는 데 있어 핵심임
간단히 말해서, 단어 임베딩은 특정 단어의 벡터 표현임
더 알아보러 가기
임베딩은 맨 아래 인코더에서만 발생한다
입력 텍스트를 토큰화
- 토큰화하면 문장이 “문장의 끝(EOS)”을 나타내는 2로 끝나는 번호 목록으로 변환됨.
- 토큰화된 표현은 포지셔널 인코더와 결합 되어 임베딩된 벡터를 만들며 이 벡터는 그림 4에 표시된 Self-Attention 레이어에 대한 입력입니다.
- 토큰화 더 알아보기
👀 토큰화 코드 보기
import encoder_demos.tokenize as tok
input_text = "I am looking for a place to eat."
tokens = tok.demo(input_text)
print("\nInput text: %s\nTokenized output: " % input_text, tokens[0].numpy())
실행 결과
| [en] dictionary: 33712 types
| [de] dictionary: 33712 types
Input text: I am looking for a place to eat.
Tokenized output: [ 40 105 1804 19 14 358 12 9637 5 2]
즉 문장이 컴퓨터가 이해하기 쉬운 ID로 변황이 된 것을 확인할 수 있다.
이제 입력 텍스트를 변경하고 토큰화된 출력 벡터가 어떻게 변경되는지 확인해보자
➡ 번역을 했을때 잘 출력되는지 손실값을 보자
👀 토큰화 확인 코드 보기
# Translate an input sentence in English to German
import encoder_demos.functional_translation as ft
input_sentence = "I am looking for a place to eat."
e, g, h = ft.demo(input_sentence)
print("En:", e)
print("German:", g)
print('')
print("H:", h)
print('H is the hypothesis along with an average log-likelihood')
실행 결과
| [en] dictionary: 33712 types
| [de] dictionary: 33712 types
| loading model(s) from /data/checkpoints/JoC_Transformer_FP32_PyT_20190304.pt
| Sentences are being padded to multiples of: 1
generated batches in 0.00028586387634277344 s
En: I am looking for a place to eat.
German: Ich bin auf der Suche nach einem Ort zu ße.
H: -0.3850874900817871
H is the hypothesis along with an average log-likelihood
가설 점수는 로그 확률로 출력되므로 음수이다.
이 가설의 확률을 exp(H)로 계산할 수 있다.
👀 exp(H) 코드 보기
print('Probability of H = {}'.format(round(np.exp(h),4)))
실행 결과
Probability of H = 0.6804
즉 잘 번역할 확률이 \(1- (아까의 손실값)\) 인 약 0.68이다
positional encoding
positional encoding 자세히 알아보기
포지셔널 인코딩은 임베딩과 차원이 동일하므로(dmodel) 두 개를 합칠 수 있다.
➡ 모델이 입력 텍스트에서 각 단어의 위치를 이해가능
이 논문에선 positional encoding에 주파수가 서로 다른 사인 및 코사인 함수를 사용한다.
- pos: 위치
- i: 차원
📜 예제를 통한 위의 공식 설명
dmodel = 4라고 가정해 보자
➡ W 입력 시퀀스 위치로 Pos ∈ [0, L-1]은 4차원 배관으로 표현됨
전자W 벡터. i ∈ [0, 255]를 설정하면,
- 4차원 임베딩 벡터의 짝수 지수: sin(pos/100002i/dmodel) 함수를 사용
- 4차원 임베딩 벡터의 홀수 지수: cos(pos/100002i/dmodel)를 사용
입력 문장의 첫 번째 위치: pos = 0
임베딩 벡터의 첫 번째 지수: k=0
➡ 임베딩 벡터의 첫 번째 차원 k = 0의 첫 번째 PE(포지셔널 인코딩)는 sin(0/100000/4) = 짝수 이니까
➡ 두 번째 차원 k = 1의 두 번째 PE는 cos(0/100000/4) = 홀수 이니까
➡ 즉, 번째 차원 k = 2와 네 번째 차원 k =3의 경우 PE는 각각 sin(0/100002/4) 및 cos(0/100002/4)가 됩니다.
1️⃣
이제 입력 시퀀스의 첫 번째 단어에 대한 포지셔널 인코딩을 작성할 수 있음
\(PE (pos =0) = [sin(0/100000/4), cos(0/100000/4), sin(0/100002/4), cos(0/100002/4)].\)$
이를 간단한 형식으로 나타내면,
\(PE (pos =0) = [sin(0/100000), cos(0/100000), sin(0/100), cos(0/100)] = [0, 1, 0, 1].\)
2️⃣
다음 단계는 이 벡터를 임베딩 벡터 ew에 추가하고 새 벡터 e’w를 얻는 것
\(e^w = PE (pos =0) + e_w\)
예시에 넣어보면(위의 추천 포스트를 읽었다는 전제하에)
- e^w = embedding+ time info
- PE (pos =0) = positional encoding
- ew.= enbedding
3️⃣
그런 다음 pos = 1, 2, … L - 1에서 입력 시퀀스의 각 단어에 대한 \(e_w\)를 계산
[코드]
아래의 PositionalEncoding 모듈을 사용해 포지셔널 인코딩을 임베딩 벡터에 추가 가능
인코더 및 디코더 스택 모두에서 임베딩과 포지셔널 인코딩의 합계에 드롭아웃이 적용됨
- 원본 문서에서 기본 모델의 𝑃𝑑𝑟𝑜𝑝 비율은 0.1
- 아래는 PE 함수를 보여주기 위해 임베딩 dim =20인 0으로 이루어진 행렬을 사용하는 예제일 뿐니다!
👀 코드 보기
from torch.autograd import Variable
class PositionalEncoding(nn.Module):
"Implement the PE function."
def __init__(self, d_model, dropout, max_len=500):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout)
print("dropout:", dropout)
# Compute the positional encodings once in log space.
pe = torch.zeros(max_len, d_model)
print("d_model:", d_model)
position = torch.arange(0.0, max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0.0, d_model, 2) *
-(math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
print("pe:", pe[:,0:2])
self.register_buffer('pe', pe)
def forward(self, x):
x = x + Variable(self.pe[:, :x.size(1)],
requires_grad=False)
return self.dropout(x)
시각화
# visualization from harvardnlp/annotated-transformer GitHub (MIT license)
plt.figure(figsize=(15, 5))
pe = PositionalEncoding(20, 0)
y = pe.forward(Variable(torch.zeros(1, 100, 20)))
plt.plot(np.arange(100), y[0, :, 4:8].data.numpy())
_ = plt.legend(["dim %d"%p for p in [4,5,6,7]])
결과
dropout: 0
d_model: 20
pe: tensor([[[0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00],
[8.4147e-01, 5.4030e-01, 3.8767e-01, 9.2180e-01, 1.5783e-01, 9.8747e-01, 6.3054e-02, 9.9801e-01, 2.5116e-02, 9.9968e-01, 9.9998e-03, 9.9995e-01, 3.9811e-03, 9.9999e-01, 1.5849e-03, 1.0000e+00, 6.3096e-04, 1.0000e+00, 2.5119e-04, 1.0000e+00]]])
첫 번째 위치의 포지셔널 인코딩 값은 다음과 같음
[ 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 1.0000e+00]
이 값은 위의 PE 계산과 일치함
Self-Attention
영어 텍스트를 독일어로 번역하는 데 사용하는 함수를 살펴보는게 우리 목표다
아래 셀은 하나의 문장에서 전체 네트워크를 실행하고 결과를 출력한다
이 셀을 실행하려면 다음 파일이 있어야 한다
- 사전 트레이닝된 모델 체크포인트: /data/JoC_Transformer_FP32_PyT_20190304.pt
- En-De 데이터세트
1️⃣ Self-Attention을 시각화해야 하는 모듈을 설정
👀코드 보기
import encoder_demos.self_attention as sa
def normalize(arr):
out = np.zeros_like(arr)
for rowno in range(arr.shape[0]):
vals = arr[rowno, :]
out[rowno, :] = (vals - np.min(vals)) / (np.max(vals) - np.min(vals))
return out
2️⃣ Self-Attention 헤드 중 하나의 가중치 설정
이제 Self-Attention 헤드 중 하나의 가중치를 설정할 수 있다
이 단계에서 Transformer가 각 단어(예: “place”)를 문장의 다른 모든 단어와 비교한다.
➡ 이러한 비교 결과가 문장의 다른 모든 단어의 Attention 점수/가중치입니다.
- Attention 점수: 다른 단어가 각각 주어진 단어(예: “place”)의 다음 표현에 얼마나 기여하는지를 결정
입력 문장의 마지막 몇몇 단어와 EOS(문장의 끝) 문자 간의 어텐션 연관성이 가장 강함
각 단어의 가장 높은 Attention 값을 시각화하는 것이 좋음
➡ 이렇게 하려면 아래의 셀을 실행하되 어텐션 가중치를 정규화하는 라인의 주석을 제거(a_w = normalize(a_w)
)
👀 제거 전 전체를 보기 위한 코드 보기
sentence = "I am looking for a place to eat."
#sentence = "This is a much more complex sentence and, as a result, is much longer."
attn, attn_weights = sa.demo(sentence, return_early='self_attn')
a_w =attn_weights[0,:, :].cpu().numpy() #you can print the attention weights.
sentence += " EOS"
sentence = sentence.replace(".", " .").replace(",", " ,").split(" ")
#a_w = normalize(a_w)
sns.set()
plt.figure(figsize=(15,15))
ax = sns.heatmap(a_w, vmin=0, vmax=1, cmap=sns.diverging_palette(200,10, n=200), xticklabels=sentence, square=True, yticklabels=sentence, cbar=True,cbar_kws={"shrink": .82})
ax.xaxis.set_ticks_position('top')
plt.yticks(rotation=0)
plt.show()
| [en] dictionary: 33712 types
| [de] dictionary: 33712 types
| loading model(s) from /data/checkpoints/JoC_Transformer_FP32_PyT_20190304.pt
| Sentences are being padded to multiples of: 1
generated batches in 0.00019598007202148438 s
이제 주석을 제거하고 집중해야 할 부분을 더 집중해서 보자
- 첫 번째 단어 “I”에 대해 가장 높은 Attention 점수가 단어 “looking”에 주어진 것을 볼 수 있다.
- “eat”의 경우 EOS 문자의 어텐션 가중치가 가장 높다
- 두 번째로 높은 어텐션 점수는 “place”에 주어졌습니다.
👀 집중 코드 보기
sentence = "I am looking for a place to eat."
#sentence = "This is a much more complex sentence and, as a result, is much longer."
attn, attn_weights = sa.demo(sentence, return_early='self_attn')
a_w =attn_weights[0,:, :].cpu().numpy() #you can print the attention weights.
sentence += " EOS"
sentence = sentence.replace(".", " .").replace(",", " ,").split(" ")
a_w = normalize(a_w)
sns.set()
plt.figure(figsize=(15,15))
ax = sns.heatmap(a_w, vmin=0, vmax=1, cmap=sns.diverging_palette(200,10, n=200), xticklabels=sentence, square=True, yticklabels=sentence, cbar=True,cbar_kws={"shrink": .82})
ax.xaxis.set_ticks_position('top')
plt.yticks(rotation=0)
plt.show()
결과
| [en] dictionary: 33712 types
| [de] dictionary: 33712 types
| loading model(s) from /data/checkpoints/JoC_Transformer_FP32_PyT_20190304.pt
| Sentences are being padded to multiples of: 1
generated batches in 0.00020933151245117188 s
![image](https://user-images.githubusercontent.com/76824611/132597034-d2f28852-41df-46e8-8e30-474afb80c077.png
Multi-Head Attention
Multi-Head Attention 더 알아보기
셀프 어텐션에서 개선된 어텐션을 “Multi-Head Attention”이라고 함
Multi-Head Attention을 사용하면 모델이 다른 위치 또는 하위 공간에 집중할 수 있음
Decoder
개요
더 알아보기
인코딩 단계를 완료한 후 디코딩 단계가 시작됨
디코더는 인코더와 매우 유사한 구조를 갖고 있음
- 각 인코더 레이어의 2개의 하위 레이어
- 이 외) 디코더는 인코더 스택의 출력에 대해 Multi-Head Attention을 수행하는 세 번째 하위 레이어를 삽입합니다.
- 인코더와 마찬가지로 각 하위 레이어 주위에 Residual Connection이 있으며 뒤이어 레이어 정규화가 진행됩니다.
[TransformerDecoder 클래스]
간단하게 이해할 수 있도록 제거한 라인,
- 일부 정규화
- Data transposition
- args에서 읽어온 값
제거된 라인을 보려면 원래 구현을 참조하십시오.
코드self.layers
,
TransformerDecorderLayer
의args.decoder_layers
(기본 6개) 복사본으로 구성된nn.ModuleList
임
매서드forward
,
TransformerEncoderLayer
와 매우 유사한 방식: 임베딩된 토큰 x가 Self Attention 메커니즘을 통과한 후에 fc1과 fc2를 통과- 인코더와의 차이점: Self-Attention과 Fully Connected 레이어 사이에 Attention프로세스 존재
- x를 디코딩하고 결국에는 쉽게 이해할 수 있는 출력을 생성하는 Multi-Head Attention이 사용됨
디코딩 과정의 각 단계는 출력 시퀀스에서 엘리먼트를 출력하며,
이 경우에는 영어 번역 문장을 출력함
처리 과정
- 1) 인코더가 입력 시퀀스를 처리하면서 시작
- 2) 그런 다음 상단 인코더의 출력이 어텐션 벡터 K 및 V 세트로 변환됨
- 3) 이러한 벡터는 각 디코더에서
"인코더-디코더 Attention"
레이어에 사용되어 디코더가 입력 시퀀스의 해당 위치에 집중할 수 있게 해 줌.
아래 그림의 디코더 측은 다음 연산을 연속해서 수행함
Step1_out = OutputEmbedding512 + PositionEncoding512
Step2_Mask = masked_multihead_attention(Step1_out)
Step2_Norm1 = layer_normalization(Step2_Mask) + Step1_out
Step2_Multi = multihead_attention(Step2_Norm1 + out_enc) + Step2_Norm1
Step2_Norm2 = layer_normalization(Step2_Multi) + Step2_Multi
Step3_FNN = FNN(Step2_Norm2)
Step3_Norm = layer_normalization(Step3_FNN) + Step2_Norm2
out_dec = Step3_Norm
👀코드 보기
# Import libraries
import os
import io
import sys
import PIL
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch import Tensor
from typing import Optional, Dict
import math, copy, time
from encoder_demos.demo_fairseq.models.fairseq_model import BaseFairseqModel, FairseqDecoder, FairseqEncoder
from encoder_demos.demo_fairseq.models.fairseq_incremental_decoder import FairseqIncrementalDecoder
from torch import Tensor
from typing import Optional, Dict
class TransformerDecoder(FairseqIncrementalDecoder):
"""Transformer decoder."""
def __init__(self, args, embed_tokens, no_encoder_attn=False, left_pad=False):
super().__init__()
self.dropout = args.dropout
self.share_input_output_embed = args.share_decoder_input_output_embed
self.fuse_dropout_add = args.fuse_dropout_add
self.fuse_relu_dropout = args.fuse_relu_dropout
embed_dim = embed_tokens.embedding_dim
padding_idx = embed_tokens.padding_idx
self.max_target_positions = args.max_target_positions
self.embed_tokens = embed_tokens
self.embed_scale = math.sqrt(embed_dim)
self.embed_positions = PositionalEmbedding(
args.max_target_positions, embed_dim, padding_idx,
left_pad=left_pad,
learned=args.decoder_learned_pos,
) if not args.no_token_positional_embeddings else None
self.layers = nn.ModuleList([])
self.layers.extend([
TransformerDecoderLayer(args, no_encoder_attn)
for _ in range(args.decoder_layers)
])
def forward(self,
prev_output_tokens: Tensor,
encoder_out: Tensor,
encoder_padding_mask: Tensor,
incremental_state: Optional[Dict[str, Dict[str, Tensor]]]=None):
# embed positions
positions = self.embed_positions(
prev_output_tokens,
incremental_state=incremental_state,
) if self.embed_positions is not None else None
if incremental_state is not None:
prev_output_tokens = prev_output_tokens[:, -1:]
if positions is not None:
positions = positions[:, -1:]
# embed tokens and positions
x = self.embed_scale * self.embed_tokens(prev_output_tokens)
if positions is not None:
x += positions
x = F.dropout(x, p=self.dropout, training=self.training)
# B x T x C -> T x B x C
# The tensor needs to copy transposed because
# fused dropout is not capable of handing strided data
if self.fuse_dropout_add :
x = x.transpose(0, 1).contiguous()
else :
x = x.transpose(0, 1)
attn = None
# decoder layers
for layer in self.layers:
x, attn = layer(
x,
encoder_out,
encoder_padding_mask if encoder_padding_mask.any() else None,
incremental_state,
)
return x, attn
Masked Multi Head Attention
디코더의 Self Attention 레이어: 디코더의 각 위치가 해당 위치를 포함해 디코더의 모든 위치를 처리할 수 있게 해 줌.
- 디코더에서 자동 회귀 속성을 유지하기 위해 왼쪽 방향 정보 흐름을 방지해야함
- 출력 임베딩이 하나의 위치로 오프셋된다는 사실을 고려할 때 이 마스킹은 i 미만의 위치에서 알려진 출력에 따라서만 위치 i에 대한 예측이 이루어질 수 있도록 함
- 다시 말해서, 미래의 단어가 어텐션의 일부가 되지 않도록 Masked-Multi-Head Attention이 적용됨
👀 코드 보기
def subsequent_mask(size):
"Mask out subsequent positions."
attn_shape = (1, size, size)
subsequent_mask = np.tril(np.ones((1,10,10))).astype('uint8')
return torch.as_tensor(subsequent_mask)
subsequent_mask(10)[0]
결과
tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], dtype=torch.uint8)
마스킹은 단어와 소스 단어 뒤에(“미래에”) 나타나는 단어 사이의 유사성을 없애는 역할을 함.
➡ 단순히 그러한 정보를 제거하므로 모델에 사용될 수 없으며 선행 단어와의 유사성만 고려됨
👀 위의 마스킹 시각화 보기
plt.figure(figsize=(5,5))
cmap = plt.cm.GnBu_r
plt.imshow(subsequent_mask(10)[0], cmap=cmap)
None