본문 바로가기

파이썬/머신러닝

[#E21] Recurrent Neural Network (RNN - PART 3/3)

이전의 "hi hello" 문자열이나 LONG SEQUENCE RNN에서 사용했던 긴 문자열과는 달리, 사용자로부터 직접 데이터(문자열)을 입력받는 것을 DYNAMIC RNN이라고 합니다.


이것은 데이터에 이미 정해진 문자열 길이가 아닌 가변적인 문자열 길이를 사용한다는 의미입니다.


예로 3개의 문자열 'hello', 'eolll', 'lleel'이 있다고 가정합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
import tensorflow as tf
import numpy as np
import pprint
pp = pprint.PrettyPrinter(indent=4)
sess = tf.InteractiveSession()
 
= [1000]
= [0100]
= [0010]
= [0001]
 
x_data = np.array([[h, e, l, l, o], [e, o, l, l, l],
 [l, l, e, e, l]] ,dtype=np.float32)
cs


우리가 지금까지 사용했던 sequence_length는 데이터의 길이 값 = len(x_data)이었던 것과는 달리, DYNAMIC RNN은 다음과 같이 구성합니다


1
2
3
4
5
6
7
8
9
hidden_size = 2
 
with tf.variable_scope('3batch') as scope:
    cell = tf.contrib.rnn.BasicRNNCell(num_units=hidden_size)
    outputs, _states = tf.nn.dynamic_rnn(
    cell, x_data, sequence_length=[534], dtype=tf.float32)

    sess.run(tf.global_variables_initializer())
    pp.pprint(outputs.eval())
cs


이 속성은 3개의 문자열에서 각각 5개, 3개, 4개의 문자의 대한 output을 출력합니다.


[사용된 문자열]


hello -> hello


eolll -> eol


lleel -> llee


[output]


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
array([[[-0.61227953,  0.5256336 ], # h
        [-0.51040757-0.1740599 ], # e
        [ 0.26046374,  0.21115965], # l
        [ 0.4724385 , -0.26186004], # l
        [ 0.43014514,  0.2979847 ]], # o
 
       [[-0.217902  ,  0.14853615], # e
        [ 0.10625201,  0.09534559], # o
        [ 0.43874428-0.13767782], # l
        [ 0.        ,  0.        ],
        [ 0.        ,  0.        ]],
 
       [[ 0.41703776-0.0360325 ], # l
        [ 0.55141526-0.08852454], # l
        [ 0.01884381,  0.11428992], # e
        [-0.22966114,  0.04843425], # e
        [ 0.        ,  0.        ]]], dtype=float32)
cs

 

마지막으로는 Time Series Data를 사용하는 RNN이 있습니다. 이 데이터의 유형은 아래의 그림 및 텍스트를 참고하세요.


[그림]



[텍스트]


1
2
3
4
5
6
7
8
9
10
11
# http://finance.yahoo.com/quote/GOOG/history?ltr=1
# Open,High,Low,Volume,Close
828.659973,833.450012,828.349976,1247700,831.659973
823.02002,828.070007,821.655029,1597800,828.070007
819.929993,824.400024,818.97998,1281700,824.159973
819.359985,823,818.469971,1304000,818.97998
819,823,816,1053600,820.450012
816,820.958984,815.48999,1198100,819.23999
811.700012,815.25,809.780029,1129100,813.669983
...
...
cs


이번 RNN에서는 위의 주식형 텍스트 데이터를 사용합니다. 해당 데이터는 여기에서 다운받을 수 있습니다.


우리는 마지막 Close 값 예측을 목표로 합니다. 따라서 구성하는 RNN의 구조는 다음과 같습니다. 


INPUT : 5 (Open, High, Low, Volume, Close)


SEQUENCE : 7 (7일까지의 데이터만 사용)


OUTPUT : 1 (Close)


먼저 데이터 값이 복잡하므로 MinMaxScaler 및 hyper_parameter을 미리 정의합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import tensorflow as tf
import numpy as np
import matplotlib
import os
 
#for graph
import matplotlib.pyplot as plt
 
 
def MinMaxScaler(data):
    ''' Min Max Normalization
    Parameters
    ----------
    data : numpy.ndarray
        input data to be normalized
        shape: [Batch size, dimension]
    Returns
    ----------
    data : numpy.ndarry
        normalized data
        shape: [Batch size, dimension]
    References
    ----------
    .. [1] http://sebastianraschka.com/Articles/2014_about_feature_scaling.html
    '''
    numerator = data - np.min(data, 0)
    denominator = np.max(data, 0- np.min(data, 0)
    # noise term prevents the zero division
    return numerator / (denominator + 1e-7)
 
 
# train Parameters
input_dim = 5 #input
sequence_length = 7 #sequence
output_dim = 1 #output
hidden_size = 10 #user default
learning_rate = 0.01
iterations = 500 #learning count
cs


이제 데이터를 읽고 지정하는데, 아래와 같이 읽어온 데이터를 시간순으로 배열합니다.


1
2
3
# Open, High, Low, Volume, Close
xy = np.loadtxt('data-02-stock-daily.csv', delimiter=',')
xy = xy[::-1]  # reverse order (chronically ordered)
cs


기본적으로 학습은 총 데이터의 0.7, 나머지는 테스트용으로 사용합니다. 따라서 데이터를 다음과 같이 나눌 수 있습니다.


동시에 MinMaxScaler를 적용합니다.


1
2
3
4
5
6
7
8
# train/test split
train_size = int(len(xy) * 0.7)
train_set = xy[0:train_size]
test_set = xy[train_size - sequence_length:]  # Index from [train_size - sequence_length] to utilize past sequence
 
# Scale each
train_set = MinMaxScaler(train_set)
test_set = MinMaxScaler(test_set)
cs


test_set은 총 데이터의 30퍼센트 이외로 과거 시퀸스 7개를 더 포함하고 있습니다. 이렇게 과거의 시퀸스를 이용하면 더 높은 정확도를 얻을 수 있습니다.


이제 편리하게 함수를 이용해 dataX와 dataY를 구축합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
# build datasets
def build_dataset(time_series, sequence_length):
    dataX = []
    dataY = []
    for i in range(0len(time_series) - sequence_length):
        _x = time_series[i:i + sequence_length, :]
        _y = time_series[i + sequence_length, [-1]]  # Next close price
        print(_x, "->", _y)
        dataX.append(_x)
        dataY.append(_y)
    return np.array(dataX), np.array(dataY)
 
trainX, trainY = build_dataset(train_set, sequence_length)
testX, testY = build_dataset(test_set, sequence_length)
cs


마지막으로 RNN을 구성해야 하는데, 우리는 output이 1이므로 이를 고려해서 FCLayer를 추가합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# input place holders
= tf.placeholder(tf.float32, [None, sequence_length, input_dim])
= tf.placeholder(tf.float32, [None, 1])
 
# build a LSTM network
cell = tf.contrib.rnn.BasicLSTMCell(
    num_units=hidden_size, state_is_tuple=True, activation=tf.tanh)
outputs, _states = tf.nn.dynamic_rnn(cell, X, dtype=tf.float32)
 
Y_pred = tf.contrib.layers.fully_connected(
    outputs[:, -1], output_dim, activation_fn=None)  # We use the last cell's output
 
# cost/loss
loss = tf.reduce_sum(tf.square(Y_pred - Y))  # sum of the squares
# optimizer
optimizer = tf.train.AdamOptimizer(learning_rate)
train = optimizer.minimize(loss)
cs


이 속성은 기본적인 파이썬 슬라이싱 방법으로, 도출된 수많은 output 중에서 마지막 한개만을 가져옵니다.


이 속성활성 함수(activation function)을 지정합니다. 여기서 적절한 활성 함수를 지정하면 더 높은 정확도를 얻을 수 있습니다. (reLU, tanh, ...)


이제 학습 후 테스트, 그리고 matplotlib를 이용해 테스트 결과를 그래프로 표시합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
with tf.Session() as sess:
    init = tf.global_variables_initializer()
    sess.run(init)
 
    # Training step
    for i in range(iterations):
        _, step_loss = sess.run([train, loss], feed_dict={
                                X: trainX, Y: trainY})
        print("[step: {}] loss: {}".format(i, step_loss))
 
    # Test step
    test_predict = sess.run(Y_pred, feed_dict={X: testX})
 
    # Plot predictions
    plt.plot(testY)
    plt.plot(test_predict)
    plt.xlabel("Time Period")
    plt.ylabel("Stock Price")
    plt.show()
cs


[output]


그래프에서 보다시피 예측값과 실제값이 비교적 정확하게 일치하는 것을 알 수 있습니다.

 

RNN 모델은 이렇게 다양한 데이터 형식에 적용될 수 있는데, 아래의 그림과 같이 대표적으로 4개의 모델이 있습니다.



ONE TO ONE : Vanila RNN


ONE TO MANY : Image Captioning (이미지를 설명하는 문장 만들기)


MANY TO ONE : Sentiment Classification (문장에서 각 단어를 입력받아 감정적 표현으로 나타내기 - 슬픔, 기쁨, 감동 ...)


MANY TO MANY (1) : Machine Translation (문장 번역)


MNAY TO MANY (2) : Video Classification on frame level (여러 프레임을 입력받아 프레임 별 설명을 출력 - Captioning)