https://fastcampus.co.kr/data_online_aideep
1. Seq2Seq Original
Seq2Seq Model
앞에서 제시한 5가지 유형 중 Many to Many에 해당된다 (입력, 출력이 모두 sequence 형태인 연속적인 데이터가 들어옴)
하나하나 cell인 plain RNN에서 발전된 형태인 LSTM(Long Short Term Memory)이나 GRU(Gated Recurrent Unit)을 주로 사용한다.
(다만 LSTM이나 GRU도 멀수록 잊혀지는 Gradient Vanishing 문제를 완벽히 해결하지는 못함)
Encoder, Decoder 2개의 파트(서로 다른 RNN이므로 서로 다른 paramter: $W_{xh}, W_{hh}, b, b_{y}$)로 구성이 되며,
Encoder의 마지막 time step에서의 hidden state vector (context vector)를
Decoder의 처음 time step에서의 hidden state vector의 Input으로 사용한다.
Train (학습 과정)
- teacher forcing (supervised learning)
- 입력을 강제로 밀어넣어주는 기법 - 번역된 문장이 준비되어 있어야 하며, 이를 Decoder의 Input으로 넣음
Test (테스트 과정)
- Output을 다음 Time step의 Input vector로 사용
- 연쇄적으로 잘못된 output이 나오는 도미노현상이 가능
ENCODER
- 입력 신호에서 필요한 정보를 추출
- 단어별로 각 time step에 해당 vector input으로 주어져서 RNN Module이 매 time step마다 잘 처리하고 축적하는 과정이 일어남
- 마지막 hidden state vector $h_{t}$ (context vector)는 입력 sequence에 주어진 모든 단어들의 정보들을 잘 축적한 vector
DECODER
- 특정 단어들의 sequence로 이루어진 답을 예측하여 output으로 반환
- RNN Module이 동작해서 각 time step에 해당하는 단어를 예측해야 하는 task가 수행됨
- 가장 최초로 등장하는 단어로서, 특수문자로서 start of sentence <sos>라는 단어를 입력으로 주어 '문장의 생성을 시작할 것이다'라는 것을 알려주고 첫번째 단어를 예측해주는 task를 수행하게 됨
- Auto-Regressive: 다음 단어로서 예측한 단어를 다음 time step에 input으로 입력으로 줘서 그 다음 단어를 연쇄적으로 예측하게 됨
- 최종적으로 '단어 생성이 끝이 났다'라는 end of sentence <eos> 토큰이 예측될 때까지 생성 과정을 반복적으로 수행
Encoder와 Decoder는 서로 parameter가 공유되지 않는 별개의 RNN을 사용한다
입력 sequence의 길이와 상관없이 각 time step마다 생성되는 hidden state vector는 같은 dimension으로 이루어져 있다
- 그래야만 output vector 다시 input vector로 사용이 가능하다
- 축적해야 하는 정보는 time step이 더 길어짐에 따라서 점점 더 많아지지만 정보는 결국 동일한 dimension으로 이루어진 vector 안에 어떻게든 욱여 담아야 한다
- 즉, Sequence가 길어질 때 정보를 유실하는 경우가 많다
2. Code - Seq2Seq Model
import torch
from torch import nn, optim
import matplotlib.pyplot as plt
BATCH_SIZE = 1
LR = 5e-1
EPOCH = 100
criterion = nn.CrossEntropyLoss()
num_classes_ko = 3
num_classes_en = 6
input_size_ko = 3
output_size_ko = 3
input_size_en = 6
output_size_en = 6
hidden_size = 3
idx2word_ko = ["저는", "강사", "입니다"]
idx2word_en = ["<sos>", "I", "am", "an", "instructor", "<eos>"]
x_data_ko = [0, 1, 2]
x_data_en = [0, 1, 2, 3, 4]
one_hot_lookup_ko = torch.eye(3).tolist()
one_hot_lookup_en = torch.eye(6).tolist()
y_data = [1, 2, 3, 4, 5]
seq_len_ko = 3
seq_len_en = 5
x_one_hot_ko = [one_hot_lookup_ko[x] for x in x_data_ko]
x_one_hot_en = [one_hot_lookup_en[x] for x in x_data_en]
X_ko = torch.tensor(x_one_hot_ko).unsqueeze(
dim=0
) # X.shape = (Data_size, seq_len, input_size)
X_en = torch.tensor(x_one_hot_en).unsqueeze(dim=0)
Y = torch.tensor(y_data).reshape(1, -1, 1) # Y.shape = (Data_size, seq_len, 1)
print(X_ko)
print(X_en)
print(Y)
tensor([[[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]]])
tensor([[[1., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0.],
[0., 0., 0., 1., 0., 0.],
[0., 0., 0., 0., 1., 0.]]])
tensor([[[1],
[2],
[3],
[4],
[5]]])
class Custom_Dataset(torch.utils.data.Dataset):
def __init__(self, X_src, X_trg, Y):
self.X_src = X_src
self.X_trg = X_trg
self.Y = Y
def __len__(self):
return self.X_src.shape[0]
def __getitem__(self, idx):
x_src = self.X_src[idx]
x_trg = self.X_trg[idx]
y = self.Y[idx]
return (x_src, x_trg), y
custom_DS = Custom_Dataset(X_ko, X_en, Y)
train_DL = torch.utils.data.DataLoader(custom_DS, batch_size = BATCH_SIZE, shuffle = False)
class LSTM(nn.Module):
def __init__(self, input_size, hidden_size):
super().__init__()
self.lstm = nn.LSTM(
input_size=input_size, hidden_size=hidden_size, batch_first=True
)
def forward(self, x, h_init, c_init):
# out, (h, c) = self.lstm(x, (h_init, c_init)) # x.shape = (BS,seq_len,input_size)
# out.shape = (BS,seq_len,hidden_size), h.shape = (D*num_layer, BS, hidden_size) == c.shape
# out.shape 은 개단채, h.shape은 레개채
# rnn cell 한번씩 통과시켜서 각 hi 를 저장하자
self.h = []
out = torch.tensor([])
hi = h_init
ci = c_init
seq_len = x.shape[1]
for j in range(seq_len):
x_letter = x[:, j, :].unsqueeze(
dim=1
) # x.shape = (BS,seq_len,input_size) 를 맞춰줌
_, (hi, ci) = self.lstm(
x_letter, (hi, ci)
) # y_hat.shape = (BS,seq_len,hidden_size)
hi.retain_grad()
self.h += [hi]
out = torch.cat(
[out, hi.permute(1, 0, 2)], dim=1
) # out.shape = (BS,seq_len,hidden_size)
# .permite(1,0,2) 는 사실 D*num_layer=1 이라서 가능한거지 엄밀하게는 틀림 (개레채로 출력)
return out, (hi.permute(1, 0, 2), ci.permute(1, 0, 2))
class Encoder(nn.Module):
def __init__(self, input_size, hidden_size):
super().__init__()
self.cells = LSTM(input_size=input_size, hidden_size=hidden_size)
def forward(self, src, h_init, c_init):
_, (h, _) = self.cells(src, h_init, c_init)
return h
class Decoder(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super().__init__()
self.cells = LSTM(input_size=input_size, hidden_size=hidden_size)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, trg, h_init, c_init):
out, (h, c) = self.cells(trg, h_init, c_init)
dec_out = self.fc(out)
return dec_out, (h, c)
class seq2seq(nn.Module):
def __init__(self, encoder, decoder):
super().__init__()
self.enc = encoder
self.dec = decoder
def forward(self, src, trg):
h_init = torch.zeros(
1, src.shape[0], hidden_size
) # h.shape = (D*num_layer, BS, hidden_size) 를 맞춰줌
c_init = torch.zeros(
1, src.shape[0], hidden_size
) # 어차피 src.shape[0] == trg.shape[0]
h_enc = self.enc(src, h_init, c_init)
dec_out, _ = self.dec(trg, h_enc, c_init)
return dec_out
model = LSTM(input_size_ko, hidden_size)
print(model)
(src_batch, _), _ = next(iter(train_DL))
# x.shape = (BS,seq_len,input_size) 를 맞춰줌
h_init = torch.zeros(1, src_batch.shape[0], hidden_size) # h.shape = (D*num_layer, BS, hidden_size) == c.shape 를 맞춰줌
c_init = torch.zeros(1, src_batch.shape[0], hidden_size)
y_hat, (h_hat, c_hat) = model(src_batch, h_init, c_init) # 0 번째 글자
# y_hat.shape = (BS,seq_len,hidden_size)
print(src_batch.shape)
print(y_hat.shape)
print(h_hat.shape)
print(c_hat.shape)
LSTM(
(lstm): LSTM(3, 3, batch_first=True)
)
torch.Size([1, 3, 3])
torch.Size([1, 3, 3])
torch.Size([1, 1, 3])
torch.Size([1, 1, 3])
encoder = Encoder(input_size_ko, hidden_size)
decoder = Decoder(input_size_en, hidden_size, output_size_en)
model = seq2seq(encoder, decoder)
print(model)
(x_src, x_trg), _ = next(iter(train_DL))
y_hat = model(x_src, x_trg)
print(x_src.shape)
print(x_trg.shape)
print(y_hat.shape)
eq2seq(
(enc): Encoder(
(cells): LSTM(
(lstm): LSTM(3, 3, batch_first=True)
)
)
(dec): Decoder(
(cells): LSTM(
(lstm): LSTM(6, 3, batch_first=True)
)
(fc): Linear(in_features=3, out_features=6, bias=True)
)
)
torch.Size([1, 3, 3])
torch.Size([1, 5, 6])
torch.Size([1, 5, 6])
def Train(model, train_DL, criterion, **kwargs):
optimizer = optim.SGD(model.parameters(), lr=kwargs["LR"])
NoT = len(train_DL.dataset) # Number of training data
loss_history = []
mean_grad_history_enc = torch.tensor([])
mean_grad_history_dec = torch.tensor([])
model.train() # train mode로!
for ep in range(kwargs["EPOCH"]):
rloss = 0
rgrad_enc = torch.zeros(seq_len_ko)
rgrad_dec = torch.zeros(seq_len_en)
print("predicted string: ", end="")
for (x_src, x_trg), y_batch in train_DL:
# x_src.shape = (BS, seq_len, input_size)
# y_batch.shape = (BS, seq_len, 1)
# grad (batch)
y_hat = model(x_src, x_trg) # y_hat.shape = (BS,seq_len,hidden_size)
L4 = criterion(
y_hat[0, -2, :].reshape(1, -1), y_batch[0, -2, :].reshape(-1)
) # y_batch는 1D 여야 함
# 0 번째 data에 대해서만 L4 계산 (멀수록 잊혀지는 현상만 보기 위해)
# L4 는 instructor가 나오게 하기 위한 loss
L4.backward()
rgrad_enc += torch.tensor(
[
model.enc.cells.h[i].grad.abs().sum() * x_src.shape[0]
for i in range(seq_len_ko)
]
)
rgrad_dec += torch.tensor(
[
model.dec.cells.h[i].grad.abs().sum() * x_src.shape[0]
for i in range(seq_len_en)
]
)
# inference
y_hat = model(x_src, x_trg)
# cross entropy loss
loss = 0
for i in range(x_src.shape[0]):
loss += criterion(
y_hat[i], y_batch[i].reshape(-1)
) # y_batch는 1D 여야 함
pred = y_hat[i].argmax(dim=1)
print(*[idx2word_en[p] for p in pred])
# update
optimizer.zero_grad() # gradient 누적을 막기 위함
loss.backward() # backpropagation
optimizer.step() # weight update
# loss accumulation
loss_b = (
loss.item() * x_src.shape[0]
) # batch loss # BATCH_SIZE 로 하면 마지막 16개도 32개로 계산해버림
rloss += loss_b # running loss
# grad and weight (epoch)
mean_grad_history_enc = torch.cat(
[mean_grad_history_enc, rgrad_enc.reshape(1, -1) / NoT], dim=0
)
mean_grad_history_dec = torch.cat(
[mean_grad_history_dec, rgrad_dec.reshape(1, -1) / NoT], dim=0
)
# print loss
loss_e = rloss / NoT # epoch loss
loss_history += [loss_e]
print("Epoch:", ep + 1, "train loss:", round(loss_e, 5))
print("-" * 20)
return loss_history, mean_grad_history_enc, mean_grad_history_dec
predicted string: instructor am instructor am am
Epoch: 1 train loss: 1.83366
--------------------
predicted string: instructor am instructor am am
Epoch: 2 train loss: 1.80942
--------------------
predicted string: instructor am instructor am am
Epoch: 3 train loss: 1.78833
--------------------
predicted string: instructor am instructor am am
Epoch: 4 train loss: 1.7699
--------------------
predicted string: instructor am instructor am am
Epoch: 5 train loss: 1.75373
--------------------
predicted string: instructor am am am am
Epoch: 6 train loss: 1.73946
--------------------
predicted string: instructor am am am am
Epoch: 7 train loss: 1.72681
--------------------
predicted string: instructor am am am am
Epoch: 8 train loss: 1.71552
--------------------
predicted string: instructor am am am am
Epoch: 9 train loss: 1.70536
--------------------
predicted string: instructor am am am am
Epoch: 10 train loss: 1.69615
--------------------
predicted string: instructor am am am am
Epoch: 11 train loss: 1.68774
--------------------
predicted string: instructor am am am am
Epoch: 12 train loss: 1.68
--------------------
predicted string: instructor am am instructor am
Epoch: 13 train loss: 1.6728
--------------------
predicted string: instructor am am instructor am
Epoch: 14 train loss: 1.66606
--------------------
predicted string: instructor am am instructor am
Epoch: 15 train loss: 1.6597
--------------------
predicted string: instructor am am instructor am
Epoch: 16 train loss: 1.65363
--------------------
predicted string: instructor am am instructor am
Epoch: 17 train loss: 1.64781
--------------------
predicted string: instructor am am instructor am
Epoch: 18 train loss: 1.64217
--------------------
predicted string: instructor am am instructor am
Epoch: 19 train loss: 1.63668
--------------------
predicted string: instructor am am instructor am
Epoch: 20 train loss: 1.63128
--------------------
predicted string: instructor am am instructor am
Epoch: 21 train loss: 1.62595
--------------------
predicted string: instructor am am instructor am
Epoch: 22 train loss: 1.62064
--------------------
predicted string: instructor am an instructor am
Epoch: 23 train loss: 1.61532
--------------------
predicted string: instructor am an instructor instructor
Epoch: 24 train loss: 1.60997
--------------------
predicted string: instructor am an instructor instructor
Epoch: 25 train loss: 1.60455
--------------------
predicted string: instructor am an instructor instructor
Epoch: 26 train loss: 1.59903
--------------------
predicted string: instructor am an instructor instructor
Epoch: 27 train loss: 1.59338
--------------------
predicted string: instructor am an instructor instructor
Epoch: 28 train loss: 1.58758
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 29 train loss: 1.58159
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 30 train loss: 1.57539
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 31 train loss: 1.56894
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 32 train loss: 1.56221
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 33 train loss: 1.55516
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 34 train loss: 1.54777
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 35 train loss: 1.54
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 36 train loss: 1.5318
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 37 train loss: 1.52315
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 38 train loss: 1.51398
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 39 train loss: 1.50427
--------------------
predicted string: instructor am an instructor <eos>
Epoch: 40 train loss: 1.49397
--------------------
predicted string: instructor am an instructor instructor
Epoch: 41 train loss: 1.48304
--------------------
predicted string: instructor am an instructor instructor
Epoch: 42 train loss: 1.47142
--------------------
predicted string: instructor am an instructor instructor
Epoch: 43 train loss: 1.45908
--------------------
predicted string: instructor am an instructor instructor
Epoch: 44 train loss: 1.44597
--------------------
predicted string: instructor am an instructor instructor
Epoch: 45 train loss: 1.43205
--------------------
predicted string: instructor am an instructor instructor
Epoch: 46 train loss: 1.41727
--------------------
predicted string: instructor am an instructor instructor
Epoch: 47 train loss: 1.40162
--------------------
predicted string: instructor am an instructor instructor
Epoch: 48 train loss: 1.38506
--------------------
predicted string: instructor am an instructor instructor
Epoch: 49 train loss: 1.36756
--------------------
predicted string: instructor am an instructor instructor
Epoch: 50 train loss: 1.34913
--------------------
predicted string: instructor am an instructor instructor
Epoch: 51 train loss: 1.32976
--------------------
predicted string: instructor am an instructor instructor
Epoch: 52 train loss: 1.30946
--------------------
predicted string: instructor am an instructor instructor
Epoch: 53 train loss: 1.28827
--------------------
predicted string: instructor am an instructor instructor
Epoch: 54 train loss: 1.26623
--------------------
predicted string: instructor am an instructor instructor
Epoch: 55 train loss: 1.2434
--------------------
predicted string: instructor am an instructor instructor
Epoch: 56 train loss: 1.21986
--------------------
predicted string: instructor am an instructor instructor
Epoch: 57 train loss: 1.19571
--------------------
predicted string: instructor am an instructor instructor
Epoch: 58 train loss: 1.17104
--------------------
predicted string: instructor am an instructor instructor
Epoch: 59 train loss: 1.14597
--------------------
predicted string: instructor am an instructor instructor
Epoch: 60 train loss: 1.12063
--------------------
predicted string: instructor am an instructor instructor
Epoch: 61 train loss: 1.09515
--------------------
predicted string: I am an instructor <eos>
Epoch: 62 train loss: 1.06964
--------------------
predicted string: I am an instructor <eos>
Epoch: 63 train loss: 1.04423
--------------------
predicted string: I am an instructor <eos>
Epoch: 64 train loss: 1.01903
--------------------
predicted string: I am an instructor <eos>
Epoch: 65 train loss: 0.99414
--------------------
predicted string: I am an instructor <eos>
Epoch: 66 train loss: 0.96966
--------------------
predicted string: I am an instructor <eos>
Epoch: 67 train loss: 0.94565
--------------------
predicted string: I am an instructor <eos>
Epoch: 68 train loss: 0.92218
--------------------
predicted string: I am an instructor <eos>
Epoch: 69 train loss: 0.89931
--------------------
predicted string: I am an instructor <eos>
Epoch: 70 train loss: 0.87706
--------------------
predicted string: I am an instructor <eos>
Epoch: 71 train loss: 0.85548
--------------------
predicted string: I am an instructor <eos>
Epoch: 72 train loss: 0.83457
--------------------
predicted string: I am an instructor <eos>
Epoch: 73 train loss: 0.81436
--------------------
predicted string: I am an instructor <eos>
Epoch: 74 train loss: 0.79484
--------------------
predicted string: I am an instructor <eos>
Epoch: 75 train loss: 0.77602
--------------------
predicted string: I am an <eos> <eos>
Epoch: 76 train loss: 0.7579
--------------------
predicted string: I am an <eos> <eos>
Epoch: 77 train loss: 0.74046
--------------------
predicted string: I am an <eos> <eos>
Epoch: 78 train loss: 0.7237
--------------------
predicted string: I am an <eos> <eos>
Epoch: 79 train loss: 0.7076
--------------------
predicted string: I am an <eos> <eos>
Epoch: 80 train loss: 0.69215
--------------------
predicted string: I am an <eos> <eos>
Epoch: 81 train loss: 0.67733
--------------------
predicted string: I am an <eos> <eos>
Epoch: 82 train loss: 0.66313
--------------------
predicted string: I am an <eos> <eos>
Epoch: 83 train loss: 0.64953
--------------------
predicted string: I am an <eos> <eos>
Epoch: 84 train loss: 0.6365
--------------------
predicted string: I am an <eos> <eos>
Epoch: 85 train loss: 0.62403
--------------------
predicted string: I am an <eos> <eos>
Epoch: 86 train loss: 0.61209
--------------------
predicted string: I am an <eos> <eos>
Epoch: 87 train loss: 0.60066
--------------------
predicted string: I am an <eos> <eos>
Epoch: 88 train loss: 0.58972
--------------------
predicted string: I am an <eos> <eos>
Epoch: 89 train loss: 0.57925
--------------------
predicted string: I am an <eos> <eos>
Epoch: 90 train loss: 0.56922
--------------------
predicted string: I am an <eos> <eos>
Epoch: 91 train loss: 0.55961
--------------------
predicted string: I am an instructor <eos>
Epoch: 92 train loss: 0.55041
--------------------
predicted string: I am an instructor <eos>
Epoch: 93 train loss: 0.54159
--------------------
predicted string: I am an instructor <eos>
Epoch: 94 train loss: 0.53312
--------------------
predicted string: I am an instructor <eos>
Epoch: 95 train loss: 0.525
--------------------
predicted string: I am an instructor <eos>
Epoch: 96 train loss: 0.51719
--------------------
predicted string: I am an instructor <eos>
Epoch: 97 train loss: 0.50968
--------------------
predicted string: I am an instructor <eos>
Epoch: 98 train loss: 0.50245
--------------------
predicted string: I am an instructor <eos>
Epoch: 99 train loss: 0.49549
--------------------
predicted string: I am an instructor <eos>
Epoch: 100 train loss: 0.48877
--------------------
plt.figure(figsize=[15,8])
plt.plot(range(1,EPOCH+1), mean_grad_history_enc[:,0], label = "$\Sigma$ |h1.grad|", linewidth=3)
plt.plot(range(1,EPOCH+1), mean_grad_history_enc[:,1], label = "$\Sigma$ |h2.grad|", linewidth=3)
plt.plot(range(1,EPOCH+1), mean_grad_history_enc[:,2], label = "$\Sigma$ |h3.grad|", linewidth=3)
plt.xlabel("Epoch", fontsize=20)
plt.ylabel("mean absolute grad value", fontsize=20)
plt.legend(fontsize=20)
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
plt.grid()
plt.figure(figsize=[15,8])
plt.plot(range(1,EPOCH+1), mean_grad_history_dec[:,0], label = "$\Sigma$ |s1.grad|", linewidth=3)
plt.plot(range(1,EPOCH+1), mean_grad_history_dec[:,1], label = "$\Sigma$ |s2.grad|", linewidth=3)
plt.plot(range(1,EPOCH+1), mean_grad_history_dec[:,2], label = "$\Sigma$ |s3.grad|", linewidth=3)
plt.plot(range(1,EPOCH+1), mean_grad_history_dec[:,3], label = "$\Sigma$ |s4.grad|", linewidth=3)
plt.xlabel("Epoch", fontsize=20)
plt.ylabel("mean absolute grad value", fontsize=20)
plt.legend(fontsize=20)
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
plt.grid()
# test 로 teacher forcing 말고 하나씩 넣어서 결과 얻기
(x_src, x_trg), y_batch = next(iter(train_DL))
pred = 0 # <sos> 로 시작
model.eval()
for i in range(x_src.shape[0]):
h_init = torch.zeros(1, x_src.shape[0], hidden_size) # h.shape = (D*num_layer, BS, hidden_size) 를 맞춰줌
c_init = torch.zeros(1, x_src.shape[0], hidden_size) # 어차피 src.shape[0] == trg.shape[0]
h = model.enc(x_src[i].unsqueeze(dim=0), h_init,c_init)
c = c_init
while idx2word_en[pred] != "<eos>":
pred_word=torch.tensor(x_one_hot_en[pred]).reshape(1,-1,input_size_en)
y_hat, (h, c) = model.dec(pred_word, h, c)
pred = y_hat[i].argmax(dim=1)
print(idx2word_en[pred], end=" ")
I am an instructor <eos>
Seq2Seq Problem: Long-term Dependency Issue (time step간의 차이가 멀 때)
Gradient Vanishing(Exploding) - Backpropagation: '멀수록 잊혀진다'
- gradient vanishing / Exploding - Encoder, Decoder 모두에 해당하는 문제
Bottleneck Problem - Forward Propagation: '갈수록 흐려진다'
- Context Vector (Encoder의 마지막 Hidden state vector) 에 마지막 단어의 정보가 가장 많이 담긴다
- 매 time step에서의 hidden state vector의 dimension을 유지하려면 정보의 손실이 필연적
- 입력 sequence가 늘어날 수록 상대적으로 context vector에 정보가 더 많이 담긴다
- 첫 단어 정보 (Input Vector)는 Activation Function을 많이 통과했기에 갈 수록 흐려지므로 마지막 단어의 정보가 더 많이 담겨있다
- 그런데 Decoder는 Context Vector에 의존하여 번역(즉 context vector에 모든 정보가 쏠리는 현상 - Bottleneck)하다 보니, Encoder의 last input에 가장 집중해서 본다는 문제가 발생
3. Seq2Seq with Attention
Solution
- Bottleneck problem or 병목 현상을 해결하기 위해 Attention이라는 추가적인 Module이 기존 Seq2Seq model에 도입
- 입력 Sequence로서 어떤 한자로 된 문장이 주어져서 이를 영어로 번역하는 task
- 입력 Sequence에 주어지는 각각의 한자 워드를 encoder에서 encoding을 한 후
Decoder가 encoder의 context vector 뿐만 아니라 encoding된 hidden state vector 중에 필요로 하는 vector를 가져가서 time step의 prediction에 직접적으로 사용
Commonality
- Encoder의 Context Vector가 Decoder의 최초 time step의 Hidden State Vector $h_{0}$로 동작하는 건 동일
Differences
- Decoder의 각 time step에서 추가적으로 Encoder에 있는 여러 hidden state vector로부터 필요로 하는 정보를 취사선택해서 추가적인 입력으로써 예측에 사용하는 형태로 동작
- decoder의 첫 번째 time step의 hidden state vector를 가지고 4개의 word에 대한 encoder의 각 time step별 hidden state vector와의 유사도를 계산
- 실제 Decoder의 첫 번째 time step에서 가장 필요로 하는 encoder time step의 hidden state vector와 높은 유사도를 띄도록 model이 전체적으로 학습이 됨
유사도: 초록색 vector와 빨간색 vector가 내적을 함으로써 그 scalar 값이 클수록 관련성이 높은 정보라고 생각함
- 네 개의 서로 다른 encoder hidden state vector와 특정 decoder hidden state vector가 내적한 네 개의 값 (Attention Score)을 가지고 softmax layer를 통과시켜 (Attention Distribution)어떤 확률 vector를 얻게 됨
- 확률 벡터를 encoder hidden state vector에 가중치로 반영하여 네 개 encoder hidden state vector의 가중합을 뽑아냄으로써 attention model의 output을 생성 (Attention Output)
- Encoder hidden state vector의 가중합에 해당하는 vector (Attention Output)를 decoder의 output layer에 추가적인 입력으로 사용
Total Process
- 1) Decoder의 각 time step의 hidden state vector를 기준으로
- 2) Encoder의 모든 hidden state vector와 내적하여 유사도를 구하고 거기에 softmax를 취하여 나온 상대적인 합이 1인 형태인 가중치 vector를 구하고
- 3) 각 가중치를 각각의 encoder의 hidden state vector에 반영하여 encoder hidden state vector의 가중합을 attention model의 결과값으로 주어 추가적인 입력으로 설정
- Hidden State Vector & Attention Output을 conatenate한 Vector가 (총 8개의 dimension으로 이루어진 입력 vector) 가 output layer에 input으로 주어짐
- decoder time step이 진행됨에 따라 그때그때 만들어지는 decoder hidden state vector를 새로운 유사도를 측정할 기준이 되는 vector로 사용
- 새로운 내적을 통한 유사도 vector를 뽑고 다시 softmax layer를 취해서 상대적인 가중치를 뽑고 그 가중치를 encoder hidden state vector에 적용해서 최종 attention output을 얻게 된다
- 이를 두 번째 time step에 추가적인 입력 vector로서 Decoder hidden state vector와 concatenate시켜 최종 입력 vector로 작용하여 output 단어를 예측하게 됨
Advantages
- 기존 Seq2Seq Model의 병목현상을 해결
- NMT(기계 번역 task)의 성능을 끌어올리게 됨
- Gradient Vanishing 문제를 해결
- Attention Module이 없었다면 매우 긴 path: Decoder -> Encoder
- 추가적인 path를 생성: encoder의 hidden state vector까지 갈 수 있는 지름길을 제공
한계점
다만 아직 Feed Forward에서 '갈수록 흐려지는' 문제를 해결하지는 못함
내적의 대상이 되는 Encoder의 Hidden State Vector 자체가 이미 반영하는 정보의 중요도가 다르기 때문이다
해당 time step에서 멀어질수록 이전 time step의 input vector를 덜 반영하는 상태로 Encoder의 hidden state vector를 만들다 보니
내적을 해도 내적의 대상이 되는 정보에서 Input Vector들이 균등하게 반영되어 있지 않다
Computational Graph 관점
- 아래의 경로: 기존의 Original Seq2Seq Model은 Backpropagation 시 기존의 경로를 거슬러 올라가야 하기 때문에 Encoder의 초반 time step의 input vector일 수록 gradient vanishing 문제가 발생 (Bottleneck Problem)
- 위의 경로: Attention을 도입하면서 Encoder의 모든 time step에 해당하는 hidden state vector에 직접적으로 접근이 가능하기 때문에 Bottleneck 현상을 완화 (일종의 지름길) - 새로운 Gradient Path 제공
Machine Translation
- 입력 Sequence를 영어로 번역할 때 Decoder에서 각 영어 단어를 번역하는 과정에 집중
- 해당 Decoder time step에서 Encoder의 어느 Input vector에 Attention Weight를 더 많이 가했나를 시각화한 자료
- 어순의 역전에 해당하는 이러한 서로 다른 언어간의 패턴을 attention module을 통해 효과적으로 학습 가능