본문 바로가기
딥러닝

pytorch로 구현하는 RNN(Recurrent Neural Network)

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

RNN을 파이토치로 구현하는 방법을 알아보자. 주식을 예측하는 도메인으로써 many to one 방식에 해당된다고 할 수 있다.

 

import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler

import torch
import torch.nn as nn
import torch.optim as optim

import matplotlib.pyplot as plt
from google.colab import drive
drive.mount('/content/gdrive')

 

 

cd/content/gdrive/My Drive/deeplearningbro/pytorch

곧바로 데이터를 다운받고 연습해볼 수 있도록 아래의 링크에 데이터를 담아두도록 한다. 데이터는 담아서 개인 코랩 연동된 drive에 담아준다

 

https://drive.google.com/file/d/1L-DXqRNV-qUX60mVJ0HsOcjX1RhoWkjC/view?usp=sharing

 

df = pd.read_csv('./data/kospi.csv')

데이터가 잘 불러와졌다.

df.head()

Date변수를 제외하고 최대최소 정규화를 진행해준다.

scaler = MinMaxScaler()
df[['Open','High','Low','Close','Volume']] = scaler.fit_transform(df[['Open','High','Low','Close','Volume']])
df.head()

데이터 프레임.info() 함수는 데이터프레임에 관한 정보를 알려준다.

df.info()

 

텐서 데이터를 만들어 주도록 하자. 그전에 gpu가 활성화가 잘 되어 있는지 device 함수를 통해 확인토록 하자.

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print(f'{device} is available')

 

데이터 셋을 target 기준으로 분리시켜주도록 한다.

X = df[['Open','High','Low','Volume']].values
y = df['Close'].values

 

 

RNN 을 사용하려고 하면 시계열데이터에서 sequence 길이를 정해주어야 한다. 그 길이는 5로 정의를 하였다.

다섯개씩 끊어주는 데이터를 만들어 준다. 

자세히 말하면 for 문이 처음돌 때 x 는 0~4 인덱스까지 5개의 값을 가지고, y는 5 인덱스의 값을 가진다. 즉, 5일간의 값으로 6일째되는 날의 값을 예측하는 것이다.

 

def seq_data(x, y, sequence_length):
  
  x_seq = []
  y_seq = []
  for i in range(len(x) - sequence_length):
    x_seq.append(x[i: i+sequence_length])
    y_seq.append(y[i+sequence_length])

  return torch.FloatTensor(x_seq).to(device), torch.FloatTensor(y_seq).to(device).view([-1, 1]) # float형 tensor로 변형, gpu사용가능하게 .to(device)를 사용.
  

 

 

시계열 데이터이니까 split을 200으로 해가지고, 앞쪽은 training에 사용하고, 뒷쪽은 test 데이터로 사용한다. size가 잘 나눠진것을 알 수 있다. size의 200은 데이터의 개수, 5일치(sequence 길이), 4개의 변수.

split = 200
sequence_length = 5

x_seq, y_seq = seq_data(X, y, sequence_length)

x_train_seq = x_seq[:split]
y_train_seq = y_seq[:split]
x_test_seq = x_seq[split:]
y_test_seq = y_seq[split:]
print(x_train_seq.size(), y_train_seq.size())
print(x_test_seq.size(), y_test_seq.size())

 

배치 형태로 만들어 주자. 시계열이니까 셔플을 막 하면 안된다.

train = torch.utils.data.TensorDataset(x_train_seq, y_train_seq)
test = torch.utils.data.TensorDataset(x_test_seq, y_test_seq)

batch_size = 20
train_loader = torch.utils.data.DataLoader(dataset=train, batch_size=batch_size, shuffle=False)
test_loader = torch.utils.data.DataLoader(dataset=test, batch_size=batch_size, shuffle=False)

 

RNN 에서는 입력노드 수를 정해줘야하고, 레이어의 개수 정해줘야하고, hidden state의 크기도 정해주어야 한다. layer나 hidden state를 더 많이 받는다는 것은 정보를 받는 가용성이 더 커지는 것이기 때문에 너무 크게 늘리면 오버피팅될 가능성이 있다.

input_size = x_seq.size(2)
num_layers = 2
hidden_size = 8

 

 

모델을 구축해보자.

파이토치에서는 rnn을 한줄로 제공을 해준다.

파이토치에서 RNN에서 입력하는 순서가 길이가 먼저들어와야하고 그다음 배치사이즈, 변수 크기 이렇게 들어와야한다. 그런데 지금 첫번째 두번째가 바뀌어야 한다. nn.RNN의 barch_first는 True로 하면 순서를 바꿀 필요없이 사용가능하다.

현재 일마다 output이 나오는 many to many 전략을 진행중에 있다. 즉, 마지막 output 하나만 가지고 예측을 하는 것이 아닌, 각각의 일자에 대해서 output이 나오면 그 output을 합쳐서 fully connected layer에 집어 넣어서 output을 계산한다.

일단 fully connected에 넣으려면 일렬로 만들어 주어야한다. 각 일마다 나오는 output의 크기는 hidden state 의 크기와 같다. 그래서 일마다 8개가 나오고($O^{(1)}$5일이니까($O^{(1)} ~\sim  O^{(5)}$5*8=40 개의 노드가 나오고 일렬로 펴서 fc(fully connected) 계산을 해주어 최종 예측값을 얻는다.

ref) deeplearningbro

class VanillaRNN(nn.Module):

  def __init__(self, input_size, hidden_size, sequence_length, num_layers, device):
    super(VanillaRNN, self).__init__()
    self.device = device
    self.hidden_size = hidden_size
    self.num_layers = num_layers
    self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
    self.fc = nn.Sequential(nn.Linear(hidden_size * sequence_length, 1), nn.Sigmoid())

  def forward(self, x):
    h0 = torch.zeros(self.num_layers, x.size()[0], self.hidden_size).to(self.device) # 초기 hidden state 설정하기.
    out, _ = self.rnn(x, h0) # out: RNN의 마지막 레이어로부터 나온 output feature 를 반환한다. hn: hidden state를 반환한다.
    out = out.reshape(out.shape[0], -1) # many to many 전략
    out = self.fc(out)
    return out
model = VanillaRNN(input_size=input_size,
                   hidden_size=hidden_size,
                   sequence_length=sequence_length,
                   num_layers=num_layers,
                   device=device).to(device)

 

regression 문제이기 때문에 loss function 을 MSE 로 두었다. 학습률은 0.001, 에폭은 200으로 설정하였다.

criterion = nn.MSELoss()

lr = 1e-3
num_epochs = 200
optimizer = optim.Adam(model.parameters(), lr=lr)

 

 

 

모델을 학습해보자. 역시 이전 CNN 에서 했던 것과 큰차이가 없다. 

SGD based optimizer를 사용하는 지도학습이다.

loss_graph = [] # 그래프 그릴 목적인 loss.
n = len(train_loader)

for epoch in range(num_epochs):
  running_loss = 0.0

  for data in train_loader:

    seq, target = data # 배치 데이터.
    out = model(seq)   # 모델에 넣고,
    loss = criterion(out, target) # output 가지고 loss 구하고,

    optimizer.zero_grad() # 
    loss.backward() # loss가 최소가 되게하는 
    optimizer.step() # 가중치 업데이트 해주고,
    running_loss += loss.item() # 한 배치의 loss 더해주고,

  loss_graph.append(running_loss / n) # 한 epoch에 모든 배치들에 대한 평균 loss 리스트에 담고,
  if epoch % 100 == 0:
    print('[epoch: %d] loss: %.4f'%(epoch, running_loss/n))

 

loss 를 그래프로 그려보자. 학습이 잘 되었다.

plt.figure(figsize=(20,10))
plt.plot(loss_graph)
plt.show()

plotting을 해보자.

actual 실제값과 train_loader, test_loader를 넣었을때 예측값들을 모두 죽 뽑아서 실제값 vs train+test 예측값을 모두 확인해본다.

가운데 학습셋이 후로 예측이 조금 쉬프트해지는 것을 확인할 수 있다.

 

def plotting(train_loader, test_loader, actual):
  with torch.no_grad():
    train_pred = []
    test_pred = []

    for data in train_loader:
      seq, target = data
      out = model(seq)
      train_pred += out.cpu().numpy().tolist()

    for data in test_loader:
      seq, target = data
      out = model(seq)
      test_pred += out.cpu().numpy().tolist()
      
  total = train_pred + test_pred
  plt.figure(figsize=(20,10))
  plt.plot(np.ones(100)*len(train_pred), np.linspace(0,1,100), '--', linewidth=0.6)
  plt.plot(actual, '--')
  plt.plot(total, 'b', linewidth=0.6)

  plt.legend(['train boundary', 'actual', 'prediction'])
  plt.show()

plotting(train_loader, test_loader, df['Close'][sequence_length:])

    

 

 

 

ref)

blog.floydhub.com/a-beginners-guide-on-recurrent-neural-networks-with-pytorch/

 

Beginner’s Guide on Recurrent Neural Networks with PyTorch

Learn the basics of Recurrent Neural Networks and build a simple Language Model with PyTorch

blog.floydhub.com

deeplearningbro

728x90
반응형

댓글