반응형

안녕하세요. 오늘은 RNN을 Pytorch로 직접 구현해보고, Tanh(hyperbolic tangent) 대신, Sigmoid나 ReLU를 사용하면 어떻게 되는지 실험을 한 번 해보겠습니다. 

 

www.youtube.com/watch?v=tlyzfIYvMWE&list=PLSAJwo7mw8jn8iaXwT4MqLbZnS-LJwnBd&index=26

제가 Pytorch를 처음 공부할 때 위 영상을 많이 참고 했습니다. 

 

Pytorch를 처음 공부하시는 분이시라면, 위 딥러닝 홀로서기 Pytorch Kist 영상을 보시는 걸 추천드립니다. 

 

자료도 아낌없이 GIt에 올라와 있고, 라이브 코딩을 하면서 수업이 진행되기 때문에 접근하는 노하우를 기를 수 있다고 생각합니다. 

 

코드는 위 영상 GIthub의 자료를 수정한 내용입니다. 

 

우선 RNN(Recurrent Neural Network)에 구조 대해 이야기 해보겠습니다. 

 

RNN은 Input과 Output을 Sequence 단위로 처리하는 네트워크 구조입니다. 

 

일반 신경망과 달리 순환 신경망은 Cell 부분이 순환하게 됩니다.

Hidden Layer는 바로 이전 Hidden Layer의 입력을 현재 입력으로 사용하는 것이 가장 큰 특징입니다.

 

이런 RNN의 순환 구조 때문에 시계열 데이터에 가장 많이 사용되고 있습니다. 

 

위 구조는 간단한 식으로 표현할 수 있습니다.

 

그렇다면 activation function인 tanh 말고, 지금 가장 많이 사용한다는 ReLU를 사용하면 안될까 라는 궁금증이 생겼습니다. 

 

Sigmoid의 미분의 최대값이 0.25이기 때문에, Deep해질수록 Vanishing Gradient 가 발생합니다. 

 

Vanishing 문제를 해결하기 위해 만든 것이 Tanh 입니다. 

 

Tanh는 0~1인 Sigmoid 확장판이며, 미분의 최대값이 1로 Vanishing Graidient를 해결합니다. 

 

ReLU는 -1~1을 반환하는 Tanh 대신, x가 0보다 크면 그대로 값을 보내게 되어, 속도와 학습률이 향상됐습니다.  

 

하지만, RNN의 내부는 계속 순환하는 구조로 값이 1보다 크게 되면, ReLU 특성상 값이 발산할 수 있기 때문에 적합하지 않다고 합니다.   

 

그렇다면, 간단한 데이터에서 발산하지 않는 환경에서 ReLU를 사용하게 된다면 어떻게 될지 궁금했습니다. 

 

위 영상의 Git 코드를 사용했으며, 학습이 실패한 영상입니다. 

 

수정을 하진 않고, 코드를 그대로 사용했으며, 활성화 함수만 바꿔가면서 학습 결과를 확인했습니다. 

 

import torch
import torch.nn as nn


class RNN(nn.Module):
    def __init__(self, input_dim, output_dim, hid_dim, batch_size): 
        super(RNN, self).__init__()
        
        self.input_dim = input_dim
        self.output_dim = output_dim
        self.hid_dim = hid_dim
        self.batch_size = batch_size
        
        self.wx = nn.Linear(self.input_dim, self.hid_dim, bias=False)
        self.wh = nn.Linear(self.hid_dim, self.hid_dim, bias=False)
        self.wy = nn.Linear(self.hid_dim, self.output_dim, bias=False)
        self.act_fn = nn.Tanh()  # nn.Sigmoid() nn.ReLU() nn.LeakyReLU() 
        
        self.hidden = self.init_hidden()
        
    def init_hidden(self, batch_size=None):
        if batch_size is None:
            batch_size = self.batch_size
        return torch.zeros(batch_size, self.hid_dim)
    
    def forward(self, x):
        h = self.act_fn(self.wx(x) + self.wh(self.hidden))
        y = self.wy(h)
        return y, h

대략 이런 그런 그림이 그려집니다. 분명 Loss 전부 떨어졌으나 모델 성능은 별로 인 것 같습니다. 

 

ReLU가 조금이나마 따라가는 듯하나 값을 예측을 하지 못했습니다. 

 

그래서 LeakyReLU를 사용해 또 결과를 확인 했습니다. 

 

Sinc함수???

LeakyReLU를 사용했을 땐, Sinc 함수가 그려졌습니다. 

 

궁금증을 해결하고자 활성화함수를 바꿔가면서 학습을 시켰으나 오히려 궁금증이 더 커졌습니다.

 

이 부분은 좀 더 공부를 해봐야 할 것 같습니다... 딥러닝 고수분들 피드백 부탁드립니다. 

728x90
반응형
반응형

오늘은 Pytorch를 통해 RNN을 알아보겠습니다. 

 

https://www.youtube.com/watch?v=bPRfnlG6dtU&t=2674s

RNN의 기본구조를 모르시면 위 링크를 보시는걸 추천드립니다.

 

Pytorch document에 RNN을 확인하겠습니다.

https://pytorch.org/docs/stable/nn.html

 

RNN with Pytorch

 

RNN with Book

 

 

1. RNN (default)

 

RNN의 입력은 [sequence, batch_size, input_size] 으로 이루어집니다.

 

import torch
import torch.nn as nn


input = torch.randn(4, 7, 5)
print(input.size())
# 결과 
# torch.Size([4, 7, 5])

sequence = 4차원,

batch_size = 7차원,

input_size = 5차원 인 임의의 input 데이터를 생성했습니다.

 

2. nn.RNN

 

RNN의 기본 인자 값으로 input_size=5, hidden_size=4, num_layer=3 값을 받습니다. 

rnn_layer = nn.RNN(input_size=5, hidden_size=4, num_layers=3)
print(rnn_layer)

# 결과
# RNN(5, 4, num_layers=3, batch_first=True)

input_size는 Input의 input_size의 값을 받으므로, 5로 적어주시면 됩니다.

 

hidden_size는 nn.RNN을 걸치면 나오는 Output으로 볼 수 있으며, 위 그림의 주황색 박스입니다. 

 

hidden_size = 4 이므로 output 또한 4차원으로 만들어집니다. 

 

num_layer는 층으로 보시면 편할 것 같습니다. num_layer = 3 이므로 3개의 층이 생성됩니다. 

 

(output, hidden) = rnn_layer(input)

print("Output size : {}".format(output.size()))
print("Hidden size : {}".format(hidden.size()))

# 결과 
# Output size : torch.Size([4, 7, 4])
# Hidden size : torch.Size([3, 7, 4])

 

 

RNN Structure

RNN을 검색해보셨다면 위 그림은 정말 많이 보셨을거라고 생각합니다. 

 

처음에 RNN을 공부할 때 위 그림이 무슨 말인고 하고 별로 신경쓰지 않았지만, 상당히 중요한 그림입니다. 

 

nn.RNN의 함수를 사용하면 위 코딩처럼 output, hidden의 값을 뱉어냅니다. 

 

2개의 값을 뱉어낸 이유는 그림을 쉽게 이해할 수 있습니다. 

RNN return output, hidden

A 박스에 input X0의 값이 들어가면, output은 h0 의 값을 반환하고 hidden은 다음 Sequnece의 A박스에 값이 들어가므로 output과 hidden의 값을 반환합니다. 

 

Output size : torch.Size[4, 7, 4])

Hidden size : torch.Size[3, 7, 4])

 

값의 size를 찍어보면 이상한 형태로 사이즈가 반환됩니다. 

 

output : [sequence, batch_size, hidden_size] 

hidden : [num_layer, batch_size, hidden_size] 

 

값의 반환은 위처럼 규칙을 따라 모양이 변형됩니다. 

 

batch_size = 7

sequence = 4

input_size = 5

hidden_size =4

num_layer = 3

 

위에서 사용하는 인자들을 보면서 하나씩 봐보면 값이 일치합니다. 

 

조금 더 자세히 보도록 하겠습니다.

 

output[-1], hidden[-1]

output[-1]이란.

output : [sequence, batch_size, hidden_size]  에서 Sequence의 가장 끝을 의미합니다. (보라색 상자)

hidden[-1]이란.

hidden : [num_layer, batch_size, hidden_size]  에서 num_layer의 가장 끝을 의미합니다. (회색 상자)

 

결국 output[-1]과 hidden[-1]이 hT(k)를 지칭하므로, 둘은 같은 값을 의미합니다 한 번 찍어보겠습니다.

print(output[-1])
print(hidden[-1])

# 결과
tensor([[ 0.8153, -0.1394,  0.3829,  0.0173],
        [ 0.8094,  0.2289,  0.4830, -0.2243],
        [ 0.7936, -0.0426,  0.3890, -0.0643],
        [ 0.7714,  0.3094,  0.4685, -0.2558],
        [ 0.8282,  0.1141,  0.4310, -0.1885],
        [ 0.8027, -0.0607,  0.3745, -0.0249],
        [ 0.8292, -0.1473,  0.4938,  0.0935]], grad_fn=<SelectBackward>)

tensor([[ 0.8153, -0.1394,  0.3829,  0.0173],
        [ 0.8094,  0.2289,  0.4830, -0.2243],
        [ 0.7936, -0.0426,  0.3890, -0.0643],
        [ 0.7714,  0.3094,  0.4685, -0.2558],
        [ 0.8282,  0.1141,  0.4310, -0.1885],
        [ 0.8027, -0.0607,  0.3745, -0.0249],
        [ 0.8292, -0.1473,  0.4938,  0.0935]], grad_fn=<SelectBackward>)

 실제로도 들어있는 값들이 같음을 알 수 있습니다. 

 

 

2. batch_first=True ( default는 False이다. )

 

pytorch는 모델에 데이터를 넣을 때는 batch_size를 가장 먼저 앞에 나옵니다. 

 

하지만 RNN이나 LSTM 같은 경우는 batch_size가 아닌 sequence가 가장 먼저 나옵니다.

 

input : [sequence, batch_size, input_size]

 

model에서 나오는 output 또한 sequence가 가장 먼저 나옵니다.

 

output : [sequence, batch_size, hidden_size]

 

이렇게 sequence가 가장 앞에서 사용되면 가끔 데이터 차원이 헷갈립니다. 

 

그럴 때 사용하는 것이 batch_first=True 입니다. default는 False입니다.

 

batch_first를 True로 설정하면 batch_size가 제일 먼자 앞으로 이동합니다. 

 

input : [batch_size, sequence, input_size]

   

output : [batch_size, sequence, hidden_size]

 

hidden은 그대로입니다.

 

사용하는 방법은 간단합니다. 

 

import torch
import torch.nn as nn


input = torch.randn(4, 7, 5)
print(input.size())

rnn_layer = nn.RNN(input_size=5, hidden_size=4, num_layers=3, batch_first=True)
print(rnn_layer)

# 결과 
# RNN(5, 4, num_layers=3, batch_first=True)

 

batch_first를 True하게 되어 batch_size가 가장 앞으로 가게 됩니다. 

 

그렇다면 인자들에 전달된 값 또한 변합니다.

 

batch_size = 4

sequence = 7

input_size = 5

hidden_size =4

num_layer = 3

 

으로 변경됩니다.

 

print("Output size : {}".format(output.size()))
print("Hidden size : {}".format(hidden.size()))

#output : [batch_size, sequence, hidden_size]
#hidden : [num_layer, batch_size, hidden_size] 
# Output size : torch.Size([4, 7, 4])
# Hidden size : torch.Size([3, 4, 4])

모양이 달라졌지만 사이즈 이외에는 크게 바뀌는 것은 없습니다. 

 

indexing 하는 방법을 sequnece, num_layer를 똑같이 적용시켜주면 됩니다.

 

print(output[:, -1, :])
print()
print(hidden[-1])

#결과
tensor([[-0.2105,  0.4423,  0.8115, -0.2838],
        [-0.4891,  0.1518,  0.8839, -0.3675],
        [-0.5495,  0.3221,  0.8500, -0.4782],
        [-0.5822,  0.3100,  0.7938, -0.4242]], grad_fn=<SliceBackward>)

tensor([[-0.2105,  0.4423,  0.8115, -0.2838],
        [-0.4891,  0.1518,  0.8839, -0.3675],
        [-0.5495,  0.3221,  0.8500, -0.4782],
        [-0.5822,  0.3100,  0.7938, -0.4242]], grad_fn=<SelectBackward>)

 

 

728x90
반응형

+ Recent posts