본문 바로가기
딥러닝

RNN

by 볼록티 2021. 4. 10.
728x90
반응형

RNN 에 대해서 알아보자.

먼저 RNN 의 이론적인 부분을 훑어보면서 개념을 익히고, 실제 RNN 이 어디에 사용되고, 예제로써 어떻게 코드로 구현되고 어떻식으로 커스터마이징 하는지를 살펴보도록 하자.

 

음성인식, 자연어 등을 보면 시계열 적으로 즉 sequential 한 데이터로 이뤄진 경우가 많다. 이러한 것들은 시간의 흐름에 따라 의미가 달라지기도 하고, 연결이 되기도 한다.

 

보통의 NN에서 이전의 연산이 다음 연산에 영향을 줄 수 있게끔 해보자 해서 만들어 진것이 RNN 이다. 아래의 그림을 보면 가운데 화살표가 빙둘러져 다시 state로 돌아오는 것을 볼 수 있다. 

위 그림을 풀어서 보면 아래와 같이 나타나지게 된다. X 입력으로 인해서 계산된 state 를 A 라고 하면, 그 다음 계산하는데 영향을 미치게 된다. 이러한 방식이라서 series data 에 적합한 모델이라고 할 수 있다.

 

우선 RNN 에는 state 개념이 있는데, state를 먼저 계산해주고 나서 y 를 계산하는 것으로 시작한다. 아래의 수식에서 파란색 부분이 state를 나타낸다. state 를 계산할 때, 한 step 이전의 state 를 사용하는데 그부분이 아래의 초록색 박스에 있는 $h_{t-1}$ 이 그것이다. $x_{t}$ 를 입력으로 받으면 이전 단계 state 정보를 활용하여 $f_{W}$ 함수를 통한 처리로써 해당 step 의 state 값인 $h_{t}$ 를 출력하게 된다. 

그렇다면 구체적으로 어떻게 연산이 진행되는지 살펴보자. $h_{t-1}$ 과 $x_{t}$ 가 function 의 입력으로 들어왔기 때문에 각각 $WX$ 의 형태로 나타낸다. 즉 weight 를 곱해주게 된다. 이부부분이 $W_{hh}h_{t-1}$, $W_{xh}x_{t}$ 가 되겠다. 맵핑된 두 값을 더 하고, 여기에 non-linear function 인 tanh(RNN에서 잘 동작함.) 을 거쳐서 현재 state 값인 $h_{t}$ 를 내게 된다. 

 state 를 계산하였다면 이제 $y$를 계산해야하는데, 이 $y_{t}$ 는 $h_{t}$ 에 또다른 weight $W_{hy}$ 를 곱한 $W_{hy}h_{t}$ 로 계산한다. 

 $y_{t}$의 벡터가 어떤 벡터로 나오냐는 $W$ 가 어떤 형태냐에 따라서 결과값이 달라지게 된다. hidden state 에서도 역시 $W$ 들의 size에 따라서 달라진다.

입력 x 를 받고 , RNN 이라는 연산을 통해서 state 를 계산하고 그 state 가 다시 입력이 되는 방식이다. 아래의 그림처럼 표시를 하기도 하는데 그 이유는 모든 hidden state 에서 밑에 수식에 보라색 부분의 function 이 전부 동일하게 적용이 되기 때문에 아래처럼 간소하게 표현하기도 한다. 즉, $W_{hh}, W_{xh}, W_{hy}$ 가 모든 state 에 똑같이 적용이 된다는 것이다.

 

실제로 어떻게 연산이 되는지 그림으로 살펴보자. 예제를 통해서 살펴볼텐데 Charactor-level language model example 예제를 통해 살펴본다. sequence 가 'hello' 라고 할 때, Vocabulary 는 ['h', 'e', 'l', 'o'] 가 되겠고, 이제 입력은 time series 의 입력으로 받아들이게 된다.

 입력값에 대한 출력은 그 다음에 어떤 값이 올지를 맞추게끔 설정이 되고, 이 학습을 통해 만들어지는 것을 language model 이라고 할 수 있다. 이러한 예측을 하기위한 학습방법으로 RNN 을 사용한다.

 

 

먼저 가장 아랫단에 분홍색으로 표시된 input layer를 보면 기본적으로 간단한 one-hot encoding 을 사용하여 input layer 에 입력할 벡터를 구성해주고, 순서대로 입력을 해준다. 

첫번째로 계산이 들어가는 왼쪽 아래의 'h' 입력은 $W_{xh}$ 연산을 통해 hidden state 값으로 계산된다. 이 때 'h'는 $h_{t-1}$이 없기 때문에 이럴때는 보통 0으로 두고 시작한다. 즉 아래의 $h_{t}$ 수식 우항에 $W_{hh}h_{t-1}$은 없는 것과 마찬가지가 된다. 그래서 $x_{t}$에 $W_{xh}$ 를 곱한 값으로 계산한다. 당연히 처음에는 학습이 안되있기 때문에 $W_{xh}$는 임의로 설정이 되어 있다고 할 수 있겠다. 

 첫번째 hidden layer 에서 두번째 hidden state 로 넘어가는데 방금 계산한 [0.3, -0.1, 0.9] 벡터인 $h_{t-1}$과 $W_{hh}$와의 값과 입력으로 들어온 'e' 벡터 $x_{t}$와 $W_{xh}$계산 값을 더해가지고 hidden state의 값을 계산해낸다. 이를 오른쪽으로 죽 이어나가는 방식을 취한다. 그렇게 이전의 연산을 계속 활용한다는 것은 이전의 것을 기억하면서 학습한다고 생각할 수 있다.  

 

 

hidden layer 에서 output layer 로 넘어가는 연산은 아래와 같은 수식으로 진행이 된다. output $y_{t}$는 hidden state 의 값인 $h_{t}$에다가 $W_{hy}$를 곱한 값을 나타낸다. 

 output 으로 출력되는 벡터 size 는 $W_{hy}$ 의 size 에 따라 결정이 되는데, 우리는 알파벳 다음에 어떤 알파벳이 나올까를 예측하기 때문에 vocabulary에 있는 네 글자 중에 하나가 정답이 되므로 vocabulary size와 동일하게끔 $W$를 설정해준다고 할 수 있다. 

 output layer 에서 softmax 함수를 사용하여 값이 가장 큰 것이 prediction이 된다. 초록색 글씨가 우리가 원하는 character이며, 이부분이 가장 값이 커야 최종적으로 예측을 잘 했다고 할 수 있다. 하지만 입력을 'h', 'e' 로 주었을 때는 우리가 원하는 정답의 값이 가장 크지 않기 때문에 예측을 하지 못한 것을 알 수 있다. 반대로 'l', 'l' 이 입력으로 들어갔을 때는 'l', 'o' 가 예측값으로 출력이 잘 된것을 알 수 있다. 

이렇게 틀린 것들에 대해서는 cost 를 줄여야 하는데 output 으로 나온 것들의 cost 들을 다 더해서 평균내고, 이 cost 를 줄여나가는 식으로 학습해서 language 모델을 만들어 나간다. 

$y_{t} = W_{hy} h_{t}$

RNN 의 application 에 대해 살펴보자.

* Language Modeling 

* Speech Recognition : 음성인식같은 경우도 말 문장에 순서가 중요하게 작용하는 만큼 잘 된다.

* Machine Translation : sequence한 입력을 받고 sequence한 출력에 대해 학습이 가능하기 때문에 번역도 가능하다.

* Conversation Modeling/Question Answering : 챗봇을 만들 수 있다. 

* Image/Video Captioning

* Image/Music/Dance Generation

 

RNN 을 어떻게 활용하느냐에 따라서 그 어플리케이션들의 활용이 가능하다. 우리가 위의 개념에서 배운 내용은 Vinilla Neural Networks 이며 아래의 그림에서는 one to one 방식에 해당한다.

 

one to many 의 어플리케이션 중 하나인 Image Captioning 의 경우는 image 를 sequence of words 로 바꾸는 것이다. 이미지를 보고 해당 이미지를 설명하는 한 문장을 내뱉는다. 이미지를 하나 입력으로 받아서 여러개의 단어로 출력이 되면 이미지를 보고 문장을 생성할 수 있게 된다. many to one 에는 어떤 어플리케이션에서 사용을 할까? 그곳은 바로 Sentiment Classification 이다. 감성분석이라고 하며 sequence of words 를 입력으로 받아 좋다/나쁘다 등의 sentiment 감성을 출력한다. 

 many to many 방식에서는 Machine Translation 이 사용되는 방식이다. sequence of words 을 받아서 똑같이 sequence of words 로 출력하는 방식이다. 

가장 오른쪽의 똑같은 many to many 방식이 있는데, 이러한 방식에는 Videl classification on frame level 이 사용되는데, 입력으로 video 의 프레임이 여러개를 입력으로 받아서 각각을 설명해주는 출력을 해주게 된다.  

 

 

 아래의 그림처럼 RNN 도 layer 를 깊게 쌓으면서 충분히 깊게 만들어 낼 수 있다. 

 RNN 도 layer 가 깊어지면 학습하기 어려운 점들이 존재한다. 이러한 점을 보완한 LSTM과 GRU(자랑스런 한국인 'Cho' 가 만든) 모델들이 있다.

 


pytorch 를 사용하여 RNN 을 구축한 사례를 한번 보자.

 

 hidden state 에서의 값을 얻기 위해 layer에서 수행되는 계산식은 아래와 같다. 

$h_{t} = tanh(W_{ihx_{t}} + b_{ih} + W_{hh}h_{t-1} + b_{hh})$

pytorch 의 nn.RNN에서 제공하는 Parameters 를 살펴보자.

 

* input_size : 입력 데이터의 크기.

* hedden_size : 은닉층의 크기.

* num_layers : RNN 의 은닉층의 개수. default 는 1개.

* nonlinearity : 비선형 활성화 함수 선택. default는 tanh.

* bias : bias를 나타냄. default는 True.

* batch_first : If True, then the input and output tensors are provided as (batch, seq, feature). => True 인 경우 입출력 텐서가 batch, seq, feature로 사용됨. default는 False

* dropout : 드롭아웃 비율. default는 0.

* bidirectional : True 일 시, 양방향 RNN. default는 False

 

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim

sentences = ["i like dog", "i love coffee", "i hate milk", "you like cat", "you love milk", "you hate coffee"]
dtype = torch.float

"""
Word Processing
"""
word_list = list(set(" ".join(sentences).split()))
word_dict = {w: i for i, w in enumerate(word_list)}
number_dict = {i: w for i, w in enumerate(word_list)}
n_class = len(word_dict)


"""
TextRNN Parameter
"""
batch_size = len(sentences)
n_step = 2  # 학습 하려고 하는 문장의 길이 - 1
n_hidden = 5  # 은닉층 사이즈

def make_batch(sentences):
  input_batch = []
  target_batch = []

  for sen in sentences:
    word = sen.split()
    input = [word_dict[n] for n in word[:-1]]
    target = word_dict[word[-1]]

    input_batch.append(np.eye(n_class)[input])  # One-Hot Encoding
    target_batch.append(target)
  
  return input_batch, target_batch

input_batch, target_batch = make_batch(sentences)
input_batch = torch.tensor(input_batch, dtype=torch.float32, requires_grad=True)
target_batch = torch.tensor(target_batch, dtype=torch.int64)


"""
TextRNN
"""
class TextRNN(nn.Module):
  def __init__(self):
    super(TextRNN, self).__init__()

    self.rnn = nn.RNN(input_size=n_class, hidden_size=n_hidden, dropout=0.3)
    self.W = nn.Parameter(torch.randn([n_hidden, n_class]).type(dtype))
    self.b = nn.Parameter(torch.randn([n_class]).type(dtype))
    self.Softmax = nn.Softmax(dim=1)

  def forward(self, hidden, X):
    X = X.transpose(0, 1)
    outputs, hidden = self.rnn(X, hidden)
    outputs = outputs[-1]  # 최종 예측 Hidden Layer
    model = torch.mm(outputs, self.W) + self.b  # 최종 예측 최종 출력 층
    return model
	

"""
Training
"""
model = TextRNN()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)

for epoch in range(500):
  hidden = torch.zeros(1, batch_size, n_hidden, requires_grad=True)
  output = model(hidden, input_batch)
  loss = criterion(output, target_batch)

  if (epoch + 1) % 100 == 0:
    print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
  
  optimizer.zero_grad()
  loss.backward()
  optimizer.step()

input = [sen.split()[:2] for sen in sentences]

hidden = torch.zeros(1, batch_size, n_hidden, requires_grad=True)
predict = model(hidden, input_batch).data.max(1, keepdim=True)[1]
print([sen.split()[:2] for sen in sentences], '->', [number_dict[n.item()] for n in predict.squeeze()])

위의 구현된 코드는 문장을 입력으로 받아 다음에 나올 단어를 예측하도록 구현되었다. ' I like dog ' 를 학습에 사용하면 'I like' 를 입력으로 넣으면 'dog' 가 출력이 된다는 말이다. ~.~

 

 

 

 

 

 

 

ref)

www.youtube.com/watch?v=-SHPG_KMUkQ

Deep Learning from scratch 2

justkode.kr/deep-learning/pytorch-rnn

Pytorch Document

코드출처: graykode/nlp-tutorial (github)

 

728x90
반응형

'딥러닝' 카테고리의 다른 글

Pytorch로 구현하는 Multi-Layer Perceptron  (0) 2021.04.13
pytorch 기초 문법(tensor, backpropagation, data load)  (0) 2021.04.12
Activation Function  (0) 2020.09.24
multi-layer perceptron  (0) 2020.08.27
Single Layer Perceptron  (0) 2020.08.06

댓글