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
코드출처: graykode/nlp-tutorial (github)
'딥러닝' 카테고리의 다른 글
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 |
댓글