Pytorch - LSTM

signer do·2023년 9월 25일
0
post-thumbnail

1. LSTM 개념

time sequence가 늘어나면 역전파 시 hyperbolic tangent 함수를 미분하면 0~1 사이의 값이 나오고, gradient 값이 backpropagation될 때 time sequence가 길어질수록 model이 제대로 학습 하지 못하는 vanishing gradient 현상이 일어난다.

기존의 RNN에 long-term memory를 담당하는 부분을 추가. 기존에는 hidden_state만 있었다면, cell_state라는 것을 추가함.
RNN에 비해 하나의 state가 더 생김.

1) Cell State

장기기억을 담당.

  • 곱하기(x): 기존의 정보를 얼마나 남길 것인지에 따라 비중( Ct1C_{t-1} )을 곱합
  • 더하기(+): 현재 들어온 data(xtx_t)와 직전의 hidden state( ht1h_{t-1} )를 통해 정보를 추가하는 부분
    Ct=ftCt1+itCt~C_t = f_t*C_{t-1}+i_t*\tilde{C_t}

2) forget gate

기존의 정보들로 구성되어 있는 cell state의 값을 얼마나 잊어버릴지.

  • 현재 시점의 입력값( xtx_{t} )과 직전 시점의 hidden state의 값( ht1h_{t-1} )를 입력으로 받는 한 층의 인공 신경망
    fi=σ(Wifxt+bif+Whfht1+bhf)f_i=σ(W_{if}x_{t}+b_{if} + W_{hf}h_{t-1} + b_{hf})
    기존의 정보를 얼마나 전달할지

3) input gate

어떤 정보를 얼만큼 cell state에 새롭게 저장할 것인지 정함.
현재 시점의 입력값( xtx_{t} )과 직전 시점의 hidden state의 값( ht1h_{t-1} )을 받아서 한 번은 σσ 함수를 통과시키고, tanhtanh 함수를 통과시킴.

  • it=σ(Wiixt+bii+Whiht1+bhi)i_t = σ(W_{ii}x_t+b_{ii}+W_{hi}h_{t-1}+b_{hi})
    • σ는 0~1 사이의 비중으로 새롭게 추가할 정보를 얼만큼의 비중으로 cell state에 더할 지
  • Ct~=tanh(WiCxt+biC+Whcht1+bhc)\tilde{C_t} = tanh(W_{iC}x_t+b_{iC}+W_{hc}h_{t-1}+b_{hc})
    • tanhtanh는 -1~1 사이의 값을 가지고 새롭게 cell state에 추가할 정보가 됨.

4) hidden state update

새로운 hidden state는 update된 cell state 값을 tanhtanh를 통과시킨 것에 비중을 곱한 값으로 생성됨.

  • ht=ottanh(ct)h_t=o_t*tanh(c_t)
  • ot=σ(Wioxt+bio+Whoht1+bho)o_t = σ(W_{io}x_t+b_{io}+W_{ho}h_{t-1}+b_{ho})

2.Library

pip install unidecode
import torch
import torch.nn as nn

import unidecode
import string
import random
import re
import time, math

2) text data 가져오기

!rm -r data
import os

try:
  os.mkdir("./data")
except:
  pass
!wget https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/tinyshakespeare/input.txt -P ./data

유니코드 문자열을 ASCII로 변환하는 코드

file = unidecode.unidecode(open('./data/input.txt').read())
file_len = len(file)
print('file_len =', file_len)

print(type(file)) # class str

3. Functions for text processing

1) Random Chunk

chunk_len = 200

def random_chunk():
  start_index = random.randint(0, file_len-chunk_len)
  end_index = start_index+chunk_len
  return file[start_index:end_index]

print(random_chunk())

2) Character to Tensor

def char_tensor(string):
  tensor = torch.zeros(len(string)).long()
  for c in range(len(string)):
    tensor[c] = all_characters.index(string[c])
  return tensor

print(char_tensor('ABCdef'))

3) Chunk into input & label

def random_training_set():
  chunk = random_chunk()
  input = char_tensor(chunk[:-1])
  label = char_tensor(chunk[1:])
  return input, label, chunk[1:]

4. Model & parameters & optimizer

1) parameters

num_epochs=2000
print_every = 100
plot_every = 10
hidden_size = 100
batch_size = 1
num_layers = 1
embedding_size = 70
lr = 0.002

2) Model

class RNN(nn.Module):
  def __init__(self, input_size, embedding_size, hidden_size, output_size, num_layers=1):
    super(RNN, self).__init__()
    self.input_size = input_size
    self.embedding_size = embedding_size
    self.hidden_size = hidden_size
    self.output_size = output_size
    self.num_layers = num_layers

    self.encoder = nn.Embedding(self.input_size, self.embedding_size)
    self.rnn = nn.LSTM(self.embedding_size, self.hidden_size, self.num_layers)
    self.decoder = nn.Linear(self.hidden_size, self.output_size)

  def forward(self, input, hidden):
    # chat_tensor():       A   -> [36]

    # input.view(1,-1): [36]   -> [[36]]
    # encoder():        [[36]] -> [[[, , , , , , ]]] (1,1,70)
    out = self.encoder(input.view(1,-1))

    # hidden [[[,,,,,]]] (num_layers, batch_size, 100)
    # rnn():            (1,1,100) -> (1,1,100), (1,1,100)
    out, hidden = self.rnn(out, hidden)

    # out.view(1,-1):   (1,1,100) -> (1,100)
    # decoder():        (1, hidden_size) -> (1, n_characters)
    out = self.decoder(out.view(batch_size,-1))
    return out, hidden

  def init_hidden(self):
    hidden = torch.zeros(self.num_layers, batch_size, self.hidden_size)
    return hidden

model = RNN(n_characters, embedding_size, hidden_size, n_characters, num_layers)

2.1) model Test

input = char_tensor("A")
print(input) # tensor([36])
hidden = model.init_hidden()
print(hidden.size()) # (1,1,100)

out, hidden = model(input, hidden)
print(out.size())

3) Loss & Optimizer

optimizer = torch.optim.Adam(model.parameters(), lr=lr)
loss_func = nn.CrossEntropyLoss()

4) Test function

def test():
  hidden = model.init_hidden()

  # 시작 문자 랜덤으로 잡기
  start_str = "b"
  input = char_tensor(start_str)
  x = input
  print(start_str, end="")

  for i in range(chunk_len):
    output, hidden = model(x, hidden)

    # .view(-1): 1D 벡터로 평탄화
    # .div(0.8): 0.8로 원소들 나누기
    # .exp(): e의 지수승으로
    output_dist = output.detach().view(-1).div(0.8).exp()

    # torch.multinomial() 다항분포로부터 sampling
    # output_list: [e1, e2, e3, ..., en] 은 확률로 해석될 수 있는 tensor
    # num_samples: sampling할 갯수
    # sampling된 index 값이 반환.
    top_i = torch.multinomial(output_dist, num_samples=1)[0]
    # 예측된 문자
    predicted_char = all_characters[top_i]

    print(predicted_char, end="")

    x = char_tensor(predicted_char)

5. Train

for i in range(num_epochs):
  # random한 chunk 가져오기
  input, label, label_string = random_training_set()
  # recurrent할 hidden vector 초기화
  hidden = model.init_hidden()

  # loss tensor 정의
  loss = torch.tensor([0]).type(torch.FloatTensor)
  optimizer.zero_grad()
  for j in range(chunk_len-1):
    x  = input[j]
    y_ = label[j].unsqueeze(0).type(torch.LongTensor)
    y, hidden = model(x, hidden)
    loss += loss_func(y, y_)

  loss.backward()
  optimizer.step()

  if i % 100 == 0:
    print("\n", f"{i}번째 문자 당 평균 {(loss/chunk_len).detach().item()}", "\n")
    test()
    print("\n********************")
    print(label_string)
    print("\n", "="*100)
profile
Don't hesitate!

0개의 댓글