내용
순환신경망(RNN)
RNN
RNN
전통적 신경망들은 전부 은닉층에서 활성화 함수를 지난 값은 오직 출력층 방향으로만 향했습니다. 이와 같은 신경망들을 피드 포워드 신경망(Feed Forward Neural Network)이라고 합니다. 그에 반해 RNN(Recurrent Neural Network)은 은닉층의 노드에서 활성화 함수를 통해 나온 결과값을 출력층 방향으로도 보내면서, 다시 은닉층 노드의 결과를 다음 은닉층의 입력으로 보내는 특징을 갖고있습니다.
다음 그림에서 x는 입력층의 입력벡터, y를 출력층의 출력벡터를 나타내며 은닉층에서 활성함수를 통해 결과를 출력층으로 보냅니다. 이 층을 cell(메모리 셀)이라고 합니다. 즉, 현 시점(t)의 cell은 이전 시점(t-1)의 cell로 부터의 출력을 입력으로 사용합니다. 이렇게 은닉상태에서 다른 은닉상태로 전달하는 값을 은닉상태(hidden state)라고 합니다. 결과적으로 다음 그림에서 나타낸 것과 같이 현 시점에는 이전시점으로부터의 은닉상태와 현시점의 입력벡터가 전달됩니다.
위 그림에서 cell은 hidden layer(은닉층)을 나타내며 x1은 은닉층1(cell1)을 통과하여 출력 y1을 생성합니다. 이 생성에 관여되는 매개변수는 cell2에 영향을 주고 이층에 입력인 x2에 대응하여 출력 y2를 반환합니다. 이 과정의 반복으로 최종 출력인 yn이 생성됩니다.
위 과정은 다음 그림과 같이 축약하여 나타낼 수 있습니다.
다음 그림은 두 시점 간의 RNN을 신경망으로 나타낸 것입니다. 4개의 입력벡터와 2개의 은닉상태 그리고 각 시점에서 2개의 출력벡터를 반환합니다. 시점간의 4개의 은닉상태가 존재합니다.
위 과정은 다음과 같이 축약하여 나타낼 수 있습니다. 현재시점 t에서의 은닉상태를 ht라고 하면 이를 계산하기 위해 입력되는 가중치는 이전 셀로부터 전달되는 은닉상태인 ht-1에서의 가중치 wh와 입력벡터(층)으로부터의 가중치 wx입니다. 즉, 현 시점 t에서의 은닉층과 출력층에서의 계산은 다음과 같습니다.
- 은닉층
- ht=tanh(wxxt+whht-1+b)
- 출력층
- yt=f(wyht+b)
입력 벡터의 차원이 d×1이라면 은닉층에 관련된 연산의 각 차원을 나타내보면 다음과 같습니다.
- ht=tanh(wxxt+whht-1+b)
- ht=tanh((Dh, Dh)×(Dh, 1)×(Dh, d)×(d,1)+(Dh, 1)=(Dh,1)
일반적으로 ht의 계산은 tanh() 함수를 사용하지만 ReLU() 함수를 사용하기도 합니다.
python으로 RNN 구현
ht의 계산을 위한 의사코드(pseudocode)는 다음과 같습니다.
hidden_state_t=0 #최기은닉상태(초기화) for input_t in input_length: #시점마다의 입력벡터 output_t=tanh(input_t, hidden_state_t) # 현시점마다 입력벡터와 이전시점의 은닉상태로 연산 #출력층에 입력되는 결과 즉, 현시점에서 은닉상태 hidden_state_t=output_t #은닉상태의 업데이트
위 의사코드에서 input_length는 총 시점의 수가 되며 각 셀(은닉층)은 직전시점의 은닉상태와 현시점의 입력벡터를 비선형함수(활성화 함수)인 tanh()로 연산합니다. 그 결과가 현시점 은닉층의 은닉상태가 됩니다.
위 코드를 구현해봅니다. 여기서는 입력벡터는 2차원으로 즉, (시점들, 입력크기) 차원의 임의 데이터를 사용합니다. 대부분의 경우 입력벡터의 차원은 (batch_size, 시점들, 입력크기)와 같이 3차원입니다.
numpy를 사용하여 모델(layers)를 구성해봅니다.
import numpy as np
timesteps=10 inputSize=4 hiddenSize=8 np.random.seed(0) inputs=np.random.random((timesteps, inputSize)) hiddenState=np.zeros((hiddenSize,)) print(hiddenState)
[0. 0. 0. 0. 0. 0. 0. 0.]
wx=np.random.random((hiddenSize, inputSize)) wh=np.random.random((hiddenSize, hiddenSize)) b=np.random.random((hiddenSize,)) print(wx.shape, wh.shape, b.shape)
(8, 4) (8, 8) (8,)
totalHiddenState=[] for i in inputs: output=np.tanh(np.dot(wx, i)+np.dot(wh, hiddenState)+b) totalHiddenState.append(list(output)) print(np.shape(totalHiddenState)) hiddenState=output
(1, 8) (2, 8) (3, 8) (4, 8) (5, 8) (6, 8) (7, 8) (8, 8) (9, 8) (10, 8)
print(totalHiddenState)
[[0.911701180297855, 0.9348851294094611, …, 0.9999817310901117, 0.9999932053003053]]
torch.nn 모듈을 사용하여 모델(layers)를 구성해봅니다.
import torch import torch.nn as nn
입력의 크기와 은닉상태의 크기를 정의합니다. 은닉상태의 크기는 RNN의 하이퍼파리미터(hyper-parameter)입니다. 입력텐서는 (배치, 시점, features)의 차원을 가집니다.
batchSize=1 timestep=10 inputSize=5 hiddenSize=8 inputs=torch.Tensor(batchSize, timestep, inputSize) inputs
tensor([[[-1.9208e-29, 3.0963e-41, 0.0000e+00, 0.0000e+00, 0.0000e+00], ⋮, [ 1.3563e-19, 2.7045e-43, 0.0000e+00, -1.9327e-29, 3.0963e-41]]])
nn.RNN()
을 사용하여 셀을 구성합니다.
cell=nn.RNN(inputSize, hiddenSize, batch_first=True) output, _status=cell(inputs) output.shape
torch.Size([1, 10, 8])
위와 같이 구축한 cell에 입력 벡터를 전달하면 두개의 값이 반환됩니다. 첫번째는 모든 은닉상태의 값들이고 두번째는 마지막 은닉상태의 값입니다.
_status.shape
torch.Size([1, 1, 8])
1개 이상의 은닉층들을 구성할 수 있습니다. nn.RNN()의 함수에 num_layers의 매개변수의 전달값으로 결정됩니다. 각 은닉층에서의 연산은 위와 동일하지만 최종 은닉상태의 차원은 은닉층 당 반환되므로 위의 차원과 차이를 보입니다. 즉, 그 차원은 (층의수, 배치크기, 은닉상태의 크기)가 됩니다.
cell=nn.RNN(inputSize, hiddenSize, num_layers=2, batch_first=True) output, _status=cell(inputs) output.shape
torch.Size([1, 10, 8])
_status.shape
torch.Size([2, 1, 8])
양방향 RNN
다음 그림과 같이 t 시점을 분석하기 위해 이전시점(t-1)의 결과를 사용하였지만 이후 시점(t+1)의 값들을 적용할 수 있습니다.
위 nn.RNN() 함수에서 매개변수 bidirectional=True로 설정함으로서 실행할 수 있습니다.
cell=nn.RNN(inputSize, hiddenSize, num_layers=2, batch_first=True, bidirectional=True)
output,_status=cell(inputs) output.shape
torch.Size([1, 10, 16])
_status.shape
torch.Size([4, 1, 8])
주가 데이터에 rnn 층 생성
FinanceDataReader 패키지를 사용하여 특정한 주가 데이터를 호출합니다.
import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split import torch.nn.functional as F import FinanceDataReader as fdr
st=pd.Timestamp(2021,8, 26) et=pd.Timestamp(2022, 3, 25) kos=fdr.DataReader('KS11', st, et) kos.head(3)
Close | Open | High | Low | Volume | Change | |
---|---|---|---|---|---|---|
Date | ||||||
2021-08-26 | 3128.53 | 3151.30 | 3159.37 | 3121.04 | 727720000.0 | -0.0058 |
2021-08-27 | 3133.90 | 3116.29 | 3143.01 | 3106.97 | 520670000.0 | 0.0017 |
2021-08-30 | 3144.19 | 3160.87 | 3164.87 | 3132.77 | 602860000.0 | 0.0033 |
데이터 중 'Close'를 label, 나머지를 feature로 설정합니다. feature와 label 은 하루의 시차를 둡니다.
ind=kos.drop('Close', axis=1) de=kos['Close']
호출한 데이터의 형식은 pandas의 DataFrame 입니다. 이를 numpy 배열 형식으로 전환하기 위해 .values
속성을 사용합니다.
x=ind.values[:-1, :] y=de.values[1:].reshape(-1,1)
features의 표준화를 실시합니다. sklearn.preprocessing.StandardScaler()
클래스를 사용합니다.
xScaler=StandardScaler().fit(x) xN=xScaler.transform(x) xN[:3]
array([[ 1.42790852, 1.39979448, 1.36045449, 0.77341112, -0.45492534],[ 1.20354701, 1.29394449, 1.27088467, -0.69620736, 0.24960505], [ 1.48923784, 1.43537974, 1.43512783, -0.11283164, 0.39990487]])
데이터를 torch.float32 형으로 전환합니다.
xtensor=torch.tensor(xN, dtype=torch.float) xtensor.dtype, xtensor.shape
(torch.float32, torch.Size([140, 5]))
현재 데이터는 (sample, feature)의 차원을 가집니다. 이 차원을 (batch, sample, feature)로 변경하기 위해 .view()
메소드를 적용합니다.
xtensor1=xtensor.view(1, 140,5) xtensor1.shape
torch.Size([1, 140, 5])
batchSize, timestemp, inputSize=xtensor1.shape hiddenSize=8 layerN=2 cell=nn.RNN(inputSize, hiddenSize, num_layers=2, batch_first=True) _status,output=cell(xtensor1)
output.shape, _status.shape
(torch.Size([2, 1, 8]), torch.Size([1, 140, 8]))
다음은 RNN 층에서 최종 출력되는 은닉상태의 입니다.
output
tensor([[[ 0.2074, 0.0375, -0.1976, -0.6594, -0.7519, 0.9465, -0.1608,0.3195]], [[ 0.5834, -0.2388, -0.5469, 0.1141, 0.0517, 0.8232, -0.0088, -0.0515]]], grad_fn=
댓글
댓글 쓰기