RNN Time-Series 예측(2)

본 예제는 모두를 위한 딥러닝 시즌2의 데이터(data-02-stock_daily.csv)와 모델을 제외한 소스 코드를 참고했습니다.

# Reference
# 모두를 위한 딥러닝 시즌 2 - PyTorch
# Lab-11-4 RNN timeseries

필요한 라이브러리를 임포트 합니다.

import torch
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler

모델에 사용할 파라메터를 셋팅해줍니다. seq_length는 입력 시퀀스 정보, data_dim은 입력 데이터의 차원, hidden_dim은 출력 데이터의 차원, output_dim은 최종 예측 데이터의 차원 입니다.

# hyper parameters
seq_length = 7
data_dim = 5
hidden_dim = 30
output_dim = 1
learning_rate = 0.01
iterations = 501

해당 데이터는 주가 데이터로 개장 포인트, 가장 높은 포인트, 가장 낮은 포인트, 폐장시 포인트와 거래량으로 5개 변수 데이터를 가지고 있습니다. 본 예측은 학습 데이터를 7일로 분리하여 다음 폐장 포인트를 예측하는 모델입니다.

# load data
xy = np.loadtxt("data-02-stock_daily.csv", delimiter=",")
xy = xy[::-1]  # reverse order

학습용 데이터와 테스트용 데이터를 분리하는 내용입니다. 학습 데이터와 검증 데이터는 7:3 비율로 분리합니다.

# split train-test set
train_size = int(len(xy) * 0.7)
train_set = xy[0:train_size]
test_set = xy[train_size - seq_length:]
train_set.shape, test_set.shape

입력한 데이터를 학습에 사용하기 위해서는 정규화 과정이 필요합니다.
정규화를 왜 해야 하는지에 대해서는 아래의 그래프를 참고하시기 바랍니다.

해당 데이터셋은 총 5개로 구성되어 있습니다. 그중 4개의 데이터는 단위가 비슷하기 때문에 그래프를 통해 보면 유사한 형태를 보이고 있습니다. 그러나 Volume 이라는 컬럼을 같이 표현하고자 한다면 입력 단위의 차이가 매우 크기 때문에 아래의 그림과 같이 나머지 데이터는 식별이 불가능하게 됩니다.

  • 단위의 차이로 인해서 Volume 데이터를 시각화 하는데 한계가 있음

그러나 MinMaxScaler를 활용하여 정규화 하게 되면 모든 데이터를 0,1의 범위 안에 표현할 수 있기 때문에 모든 그래프를 한번에 그릴 수 있습니다. 그리고 이렇게 표현한 데이터는 다시 원래 단위의 형태로 복원 할 수 있습니다.

학습에서 MinMaxScaler를 사용하는 이유는 다차원 데이터값을 비교 분석하기 쉽게 만들어주고 자료의 오버플로우나 언더플로우를 방지해주고 최적과 과정에서 안정성 및 수렴 속도를 향상 시키기 위함입니다.

MinMaxScaler 수행후 데이터 시각화
scaler = MinMaxScaler()
scaler.fit(train_set)
print(scaler.n_samples_seen_, scaler.data_min_, scaler.data_max_, scaler.feature_range)
train_set = scaler.transform(train_set)
scaler.fit(test_set)
print(scaler.n_samples_seen_, scaler.data_min_, scaler.data_max_, scaler.feature_range)
test_set = scaler.transform(test_set)

build_dataset 함수는 RNN 학습을 위해서 입력 텐서를 만들어 주는 부분입니다.
time_series[0:7,], time_series[7,[-1]] 형식으로 되어 있습니다.

# make dataset to input
def build_dataset(time_series, seq_length):
    dataX = []
    dataY = []
    for i in range(0, len(time_series) - seq_length):
        _x = time_series[i:i + seq_length, :]
        _y = time_series[i + seq_length, [-1]]  # Next close price
        #print(_x, "->", _y)
        dataX.append(_x)
        dataY.append(_y)
    return np.array(dataX), np.array(dataY)
# make train-test dataset to input
trainX, trainY = build_dataset(train_set, seq_length)
testX, testY = build_dataset(test_set, seq_length)
print(trainX.shape, trainY.shape)

# convert to tensor
trainX_tensor = torch.FloatTensor(trainX)
trainY_tensor = torch.FloatTensor(trainY)

testX_tensor = torch.FloatTensor(testX)
testY_tensor = torch.FloatTensor(testY)

이제 학습 데이터를 통해서 데이터를 학습하는 모델을 만듭니다. 본 예제에서는 BiLSTM 방식으로 4개의 층을 쌓아 올린 형태입니다.

위의 그림은 해당 데이터에 대한 입력 데이터 형태와 입력와 출력의 형태는 아래 그림과 같습니다. 일단 벡터는 (n,7,5) -> (n,7,30) 형태로 나옵니다. 그러나 BiLSTM 모델을 사용했기 때문에 마지막 output은 30*2의 형태가 됩니다.

class Net(torch.nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim, layers):
        super(Net, self).__init__()
        self.rnn = torch.nn.LSTM(input_dim, hidden_dim, num_layers=layers, batch_first=True, bidirectional=True)
        self.layers = torch.nn.Sequential(
            torch.nn.Linear(hidden_dim*2, 20),
            torch.nn.Linear(20, 10),
            torch.nn.Linear(10, output_dim)
        )

    def forward(self, x):
        x, (hidden, cell) = self.rnn(x)
        x = self.layers(x[:, -1, ])
        return x

net = Net(data_dim, hidden_dim, output_dim, 4)

이제 학습을 수행합니다.

# loss & optimizer setting
criterion = torch.nn.MSELoss()
optimizer = optim.Adam(net.parameters(), lr=learning_rate)

# start training
for i in range(iterations):
    outputs = net(trainX_tensor)
    loss = criterion(outputs, trainY_tensor)
    
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    if i%50 == 0:
        print(i, loss.item())

학습이 완료된 후 테스트를 수행합니다.

net.eval()
predict_data = net(testX_tensor).data.numpy()
plt.grid(True)
plt.autoscale(axis='x', tight=True)
plt.plot(testY)
plt.plot(predict_data, color='red')
plt.legend(['original', 'prediction'])
plt.show()

학습된 결과와 원본 데이터를 통해 비교해보면 예측이 비교적 잘됐음을 알 수 있습니다.

“RNN Time-Series 예측(2)”에 대한 4개의 댓글

    1. 제가 위에서 그림으로 표현했는데 아마도 제 그림 솜씨가 별로였나봐요 ㅎ
      데이터는 위의 설명과 같이 1-6일의 데이터를 통해서 마지막 7일의 데이터를 예측하는 내용입니다.
      그러니까 필요한 데이터는 맨 마지막 7번째 데이터가 필요한거죠~
      그것을 표시하는 것이 x[:, -1, ] 입니다.

      1. 답변 감사드립니다.
        그림은 매우 훌륭해요. ㅎㅎ

        죄송한데 몇가지만 더 여쭤볼께요..
        박사님이 올려주신 code 응용해서 뭘 좀 만들고 있는데 3개월째가 되도록 진행이 안되네요..;;
        방금 올려주신 답변 보고 이페이지를 100번도 넘게 보면서 지금까지 계속 고민중입니다.

        1. 7일의 데이터를 입력으로 넣어주면 출력도 7일치가 나오는데 그중에 맨 마지막것을 사용한다는 것인가요?

        – 설명에 “벡터는 (n,7,5) -> (n,5,30) 형태로 나옵니다.” 요렇게 적어져 있는데요. 요것만 보면 마지막 7일의 데이터가 아니라 feature의 마지막을 사용한다는 의미같이 들리는데요.??
        – 7일이든 5개든 네트워크를 통과해 나오면 데이터가 단순이 hidden_dim과만 관련이 있을꺼 같은생각이 들어서요.. 그렇다면 그냥 7일째가 아니라 그저 7개의 데이터가 나오고 그중 마지막 것을 사용한다는 뜻이 아닌가요?

        2. 제가 텐서 구조 표현법에 대해 잘 몰라서 죄송합니다만 기본적인거 좀 여쭤보면
        x[:, -1, ] 여기에서 각 항은 무엇을 의미하나요?
        첫번째 “:” 보통 전부라고 생각하는데 무엇의 전부인가요. 아무리 생각해도 그림의 n은 아닐꺼 같고, 7*30중에서 어떤것을 뜻하는 건지 모르겠습니다. 두번째 쉼표 뒤에 빈칸은 무엇을 의미하나요?

        3. 입력데이터 구성할때 y를 time_series[7,[-1]] 이렇게 구성하는데 -1에 대괄호를 넣어주는 이유는 뭔가요?

        4. 답변과 관련해서 7일의 데이터를 입력으로 하고 8일차를 출력하는게 아닌가요?
        _x = time_series[i:i + seq_length, :] 요걸 보면 0~6(7개) 인거 같은데요..;;(무지 햇갈려요)

        5. output_dim은 hidden_dim 당 하나씩 출력한다는 뜻인가요? hidden_dim이 30이라 출력도 30*output_dim*2가 되는건가요?

        아….. 너무 질문이 많아서 죄송합니다.
        바쁘실테니 아무거나 되는대로 답변주시면 고맙겠습니다.(__)

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다