2022년 8월 13일 토요일

tensorflow keras / RNN, LSTM

RNN/LSTM

RNN (LSTM, GRU, 이하 LSTM으로 사용함)은 시계열 분석이나 자연어 처리 (NLP) 등에 널리 사용됩니다. 시계열 데이터, 자연어의 단어 시퀀스, 문장 시퀀스 등은 모두 시간에 따라 변하면서, 과거와 현재가 서로 연관성을 가진 데이터입니다. 

여기에서는 keras LSTM 을 매우 다양한 유형으로 LSTM을 구성할 수 있습니다. 단방향 (unidirectional)/양방향 (bidirectional), many-to-one, many-to-many, multi-layered LSTM, stacked LSTM 등이 있고, 이들을 서로 조합하면서 다양한 방식으로 구성할 수 있다.

여기에서는 단순한 단층-단방향 many-to-one 유형에 대해 살펴 보도록 하겠습니다.

기존에 많은 예제들은 문자 예측이나 문자열 예측이 대부분 이었습니다. 오히려 이런 부분들이 RNN을 이해하는데 어려움이 있기 때문에 여기에서는 최대한 기존에 사용했던 숫자 형태를 이용해서 진행하겠습니다.


데이터 준비

예제로 이용할 데이터는 금융 데이터, 주식, 주가 예측 와 비슷한 형태로 가정할 것 입니다. 대략 이런 형태의 데이터를 준비하였습니다.


위 데이터는 삼각함수를 이용하여 적당히 생성 시켰습니다. 함수는 가능하면 양수를 가지도록 적당히 +4 정도로 했습니다.


데이터 생성

사용할 데이터를 준비하였는데 이번에는 이것을 데이터 파일로 만드는 것을 진행할 것입니다. 코드는 아래와 같은 형태가 되고 x 에 따른 y 값을 저장하는데 range는 0~14.4 정도까지 생성했습니다. (range는 정수 값만 사용이 가능하므로, x입력을 위해서 부동 소수점 수가 필요하기 때문에 14000/1000 나누는 코드가 있습니다.)

import math
import matplotlib.pyplot as plt
import numpy as np

time_step_size = 5
timed_y_list = []
for xx in range(0, 14400, 1):
    x = xx/1000.0
    y = 2.0 * math.sin(x) + math.sin(2.0*x)+math.sin(6.0*x)+math.cos(x) + 4.0
    timed_y_list.append(y)

timed_y_data = np.array(timed_y_list)
print(timed_y_data)
np.savetxt("timed_y_data.csv", timed_y_data, delimiter=',')

timed_y_data.csv

5.000000000000000000e+00
5.009999462333439624e+00
5.019997698669415698e+00
5.029994483019190277e+00
5.039989589410644122e+00
5.049982791896089118e+00
5.059973864560072698e+00
5.069962581527184931e+00
5.079948716969867384e+00
5.089932045116213999e+00
5.099912340257776400e+00
5.109889376757358548e+00
......

앞쪽 데이터만 출력해보면 위와 같이 저장되어 있는데 결국 이 데이터는 x 도 아닌 y 도 아닌 이상한 데이터가 됩니다.

RNN의 경우 어떤 부분이 x, y로 만들어서 학습을 해야할까요?

데이터를 구성하기 전에 many to one 구조는 아래 그림을 참고해 주세요


이번에 학습시킬 데이터는 many to one 입니다. 다시 설명하자면 입력은 여러개하고 출력은 하나만 하게 되는 형태입니다.
timed_y_data.csv 파일을 가지고 설명을 하자면 예를 들어 many부분(time step)을 5라고 설정한다면 5개의 입력(x)가 들어가야 하고 하나의 출력은 하나(y)가 됩니다.
이것을 앞에서 3개만 예를들어 그려봤습니다.
이것을 실제 데이터로 x 와 y를 다른 파일로 구성해서 저장해봤습니다. 아래와 같은 형태로 됩니다.
코드로는 아래와 같은 형태가 됩니다.
import math
import matplotlib.pyplot as plt
import numpy as np

time_step_size = 5
timed_y_list = []
x_list = []
y_list = []
for xx in range(0, 14400, 1):
    x = xx/1000.0
    y = 2.0 * math.sin(x) + math.sin(2.0*x)+math.sin(6.0*x)+math.cos(x) + 4.0
    timed_y_list.append(y)

timed_y_data = np.array(timed_y_list)
print(timed_y_data)
np.savetxt("timed_y_data.csv", timed_y_data, delimiter=',')

for xx in range(len(timed_y_list)-time_step_size):
    x_list.append(timed_y_list[xx:xx+time_step_size])
    y_list.append(timed_y_list[xx+time_step_size])

x_np = np.array(x_list)
y_np = np.array(y_list)
print(x_np)
print(y_np)

np.savetxt("x_data.csv", x_np, delimiter=',')
np.savetxt("y_data.csv", y_np, delimiter=',')


fig, ax = plt.subplots()
ax.plot(timed_y_data)
plt.savefig("timed_y_data.png")

저장한 것을 놓고 보면 x 에 5개의 입력을 가지는 한개의 출력 y를 가지는 형태가 됩니다. 이것을 학습 하면 되는데... 결국 RNN이 아니더라도 NN layer로도 충분이 학습 가능할 것 같습니다. 이건 RNN을 학습하고 마지막에 따로 진행해 보도록 하겠습니다.


RNN의 입력

RNN의 가이드는 여기 보시면 됩니다. LSTM이나 SimpleRNN이나 모두 첫번째 인자로는 units인데 이건 출력 갯수 입니다.
https://www.tensorflow.org/api_docs/python/tf/keras/layers/SimpleRNN
https://www.tensorflow.org/api_docs/python/tf/keras/layers/LSTM
https://www.tensorflow.org/guide/keras/rnn?hl=ko#setup
그러면 입력을 어떻게 해야 할까요?
기존의 Linear Layer 다르게 차원이 하나 더 추가가 됩니다. 
xdata로 우리는 앞에서 5개의 변수를 준비했습니다.
기존 Linear 데이터 입력 [1, 2, 3, 4, 5] 형태 => RNN에서는 [[1],[2],[3],[4],[5]] 이런식으로 준비해야 합니다. (마지막 차원이고 사실 변환 안해도 tensorflow가 처리를 해주긴 합니다.)
이 부분 설명이 좀 더 복잡한데... text embeding 을 할때 이방식이 유용해집니다. 여기에서는 1,2,3,4,5 로 예를 들었지만, text라고 한다면 각각이 엔코딩된 값이 될 겁니다. 엔코딩 값들은 한개의 차원을 소모하게 되며, RNN layer에 의해 제일 하위 차원이 사라지게 됩니다.


_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_1 (InputLayer)        [(None, None, 5)]         0         
                                                                 
 dense (Dense)               (None, None, 6)           36        
                                                                 
 dense_1 (Dense)             (None, None, 1)           7      

기존 Linear Layer 차원은 그대로 유지

_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_1 (InputLayer)        [(None, 5, 1)]            0         
                                                                 
 simple_rnn (SimpleRNN)      (None, 6)                 48        
                                                                 
 dense (Dense)               (None, 1)                 7        

RNN에서는 차원이 하나 사라짐


차원을 변환하는 부분을 CustomDataset 에서 x_data = np.reshape(x_data, (-1, 1)) 여기에서 만들어 두었습니다. 즉 x data를 읽어들일때 차원을 변환 하도록 하였습니다.

class CustomDataset:
    def __init__(self, x_tensor_filename, y_tensor_filename, base_idx=None, cnt=None):
        self.x_fn = x_tensor_filename
        self.y_fn = y_tensor_filename
        self.base_idx = base_idx
        self.cnt = cnt
        if self.base_idx is None:
            with open(y_tensor_filename, 'r') as fp:
                for count, line in enumerate(fp):
                    pass
            self.total_len = count + 1
        else:
            self.total_len = cnt
        x_data = np.loadtxt(self.x_fn, delimiter=',', skiprows=0, max_rows=1)
        self.feature_cnt = len(x_data)

    def __getitem__(self, index):
        """
        주어진 인덱스 index 에 해당하는 샘플을 데이터셋에서 불러오고 반환합니다.
        """
        if self.base_idx is not None:
            index = index + self.base_idx
        x_data = np.loadtxt(self.x_fn, delimiter=',', skiprows=index, max_rows=1)
        # [0.37454012 0.95071431 0.73199394]
        if x_data.shape == ():
            x_data = np.array([x_data])
        y_data = np.loadtxt(self.y_fn, delimiter=',', skiprows=index, max_rows=1)
        # 1.5258549401766552
        if y_data.shape == ():
            y_data = np.array([y_data])
        # [1.5258549401766552]
        # x_data [5.          5.00999946 5.0199977 5.02999448 5.03998959] y_data [2.40299196]
        x_data = np.reshape(x_data, (-1, 1))
        # x_data [[5.        ][5.00999946][5.0199977 ][5.02999448][5.03998959]] y_data [2.40299196]
        return x_data, y_data

    def __len__(self):
        """
        데이터셋의 샘플 개수를 반환합니다.
        """
        return self.total_len


Model

모델 구성하는 부분만 빼고 기본 소스는 Linear Layer 작업할 때와 거의 동일합니다.

기본 소스는 다음 링크를 참고하세요

https://swlock.blogspot.com/2022/08/tensorflow-keras-train-regression.html

input의 shape 에서 5는 time step의 수가 됩니다. data를 구성할때 앞의 몇개의 시계열 데이터를 이용해서 만들었는지 정보입니다. 그리고 뒤에 1은 해당 데이터가 몇개의 feature를 사용하는지 혹은 몇 개의 데이터를 이용해서 엔코딩되어 있는지 나타냅니다. 텍스트 예제의 경우 이 부분이 보통 onehot encoding으로 되어있어 상당히 큰 (feature)수치를 가지고 있게 됩니다.

inputs = keras.Input(shape=(5, 1))
x = layers.SimpleRNN(6)(inputs)
x = layers.Dense(1, activation="linear")(x)
outputs = x
model = keras.Model(inputs, outputs)
model.summary()

위 두가지 부분만 바꾸고 나머지는 거의 비슷하게 구성하였습니다.


훈련

GPU를 사용하지 않고 진행했더니, 훈련 시간은 생각보다 오래 걸렸습니다. validation 데이터로 0.0208 loss 가 발생 하였습니다.

14395 11516 2879
2022-08-13 18:23:59.001158: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'cusolver64_11.dll'; dlerror: cusolver64_11.dll not found
2022-08-13 18:23:59.003608: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'cudnn64_8.dll'; dlerror: cudnn64_8.dll not found
2022-08-13 18:23:59.003760: W tensorflow/core/common_runtime/gpu/gpu_device.cc:1850] Cannot dlopen some GPU libraries. Please make sure the missing libraries mentioned above are installed properly if you would like to use GPU. Follow the guide at https://www.tensorflow.org/install/gpu for how to download and setup the required libraries for your platform.
Skipping registering GPU devices...
2022-08-13 18:23:59.004247: I tensorflow/core/platform/cpu_feature_guard.cc:193] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX AVX2
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
Model: "model"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_1 (InputLayer)        [(None, 5, 1)]            0         
                                                                 
 simple_rnn (SimpleRNN)      (None, 6)                 48        
                                                                 
 dense (Dense)               (None, 1)                 7         
                                                                 
=================================================================
Total params: 55
Trainable params: 55
Non-trainable params: 0
_________________________________________________________________
Epoch 1/50
116/116 [==============================] - 101s 874ms/step - loss: 3.7985 - val_loss: 4.1360
Epoch 2/50
116/116 [==============================] - 99s 853ms/step - loss: 1.7602 - val_loss: 2.4225
Epoch 3/50
116/116 [==============================] - 99s 857ms/step - loss: 1.0908 - val_loss: 1.5594
Epoch 4/50
116/116 [==============================] - 101s 872ms/step - loss: 0.6845 - val_loss: 0.9763
Epoch 5/50
116/116 [==============================] - 100s 864ms/step - loss: 0.4588 - val_loss: 0.6872
Epoch 6/50
116/116 [==============================] - 110s 954ms/step - loss: 0.3262 - val_loss: 0.4753
Epoch 7/50
116/116 [==============================] - 116s 1s/step - loss: 0.2494 - val_loss: 0.3548
Epoch 8/50
116/116 [==============================] - 107s 926ms/step - loss: 0.1984 - val_loss: 0.2714
Epoch 9/50
116/116 [==============================] - 109s 939ms/step - loss: 0.1632 - val_loss: 0.2266
Epoch 10/50
116/116 [==============================] - 106s 922ms/step - loss: 0.1373 - val_loss: 0.1841
Epoch 11/50
116/116 [==============================] - 112s 974ms/step - loss: 0.1157 - val_loss: 0.1762
Epoch 12/50
116/116 [==============================] - 111s 960ms/step - loss: 0.1002 - val_loss: 0.1350
Epoch 13/50
116/116 [==============================] - 114s 986ms/step - loss: 0.0866 - val_loss: 0.1165
Epoch 14/50
116/116 [==============================] - 106s 916ms/step - loss: 0.0768 - val_loss: 0.1015
Epoch 15/50
116/116 [==============================] - 104s 899ms/step - loss: 0.0673 - val_loss: 0.0911
Epoch 16/50
116/116 [==============================] - 106s 917ms/step - loss: 0.0598 - val_loss: 0.0920
Epoch 17/50
116/116 [==============================] - 105s 908ms/step - loss: 0.0517 - val_loss: 0.0738
Epoch 18/50
116/116 [==============================] - 105s 908ms/step - loss: 0.0471 - val_loss: 0.0676
Epoch 19/50
116/116 [==============================] - 107s 925ms/step - loss: 0.0435 - val_loss: 0.0622
Epoch 20/50
116/116 [==============================] - 123s 1s/step - loss: 0.0403 - val_loss: 0.0588
Epoch 21/50
116/116 [==============================] - 120s 1s/step - loss: 0.0368 - val_loss: 0.0545
Epoch 22/50
116/116 [==============================] - 111s 950ms/step - loss: 0.0339 - val_loss: 0.0522
Epoch 23/50
116/116 [==============================] - 118s 1s/step - loss: 0.0314 - val_loss: 0.0470
Epoch 24/50
116/116 [==============================] - 110s 952ms/step - loss: 0.0283 - val_loss: 0.0441
Epoch 25/50
116/116 [==============================] - 122s 1s/step - loss: 0.0273 - val_loss: 0.0447
Epoch 26/50
116/116 [==============================] - 123s 1s/step - loss: 0.0250 - val_loss: 0.0430
Epoch 27/50
116/116 [==============================] - 108s 930ms/step - loss: 0.0242 - val_loss: 0.0389
Epoch 28/50
116/116 [==============================] - 105s 907ms/step - loss: 0.0228 - val_loss: 0.0366
Epoch 29/50
116/116 [==============================] - 105s 906ms/step - loss: 0.0222 - val_loss: 0.0382
Epoch 30/50
116/116 [==============================] - 105s 910ms/step - loss: 0.0206 - val_loss: 0.0344
Epoch 31/50
116/116 [==============================] - 106s 921ms/step - loss: 0.0201 - val_loss: 0.0336
Epoch 32/50
116/116 [==============================] - 106s 916ms/step - loss: 0.0196 - val_loss: 0.0320
Epoch 33/50
116/116 [==============================] - 105s 912ms/step - loss: 0.0177 - val_loss: 0.0337
Epoch 34/50
116/116 [==============================] - 106s 916ms/step - loss: 0.0183 - val_loss: 0.0305
Epoch 35/50
116/116 [==============================] - 105s 909ms/step - loss: 0.0176 - val_loss: 0.0280
Epoch 36/50
116/116 [==============================] - 106s 917ms/step - loss: 0.0172 - val_loss: 0.0305
Epoch 37/50
116/116 [==============================] - 111s 955ms/step - loss: 0.0166 - val_loss: 0.0265
Epoch 38/50
116/116 [==============================] - 113s 977ms/step - loss: 0.0163 - val_loss: 0.0264
Epoch 39/50
116/116 [==============================] - 116s 1s/step - loss: 0.0153 - val_loss: 0.0275
Epoch 40/50
116/116 [==============================] - 115s 989ms/step - loss: 0.0156 - val_loss: 0.0249
Epoch 41/50
116/116 [==============================] - 118s 1s/step - loss: 0.0154 - val_loss: 0.0238
Epoch 42/50
116/116 [==============================] - 121s 1s/step - loss: 0.0147 - val_loss: 0.0232
Epoch 43/50
116/116 [==============================] - 110s 944ms/step - loss: 0.0141 - val_loss: 0.0266
Epoch 44/50
116/116 [==============================] - 123s 1s/step - loss: 0.0146 - val_loss: 0.0239
Epoch 45/50
116/116 [==============================] - 105s 906ms/step - loss: 0.0139 - val_loss: 0.0232
Epoch 46/50
116/116 [==============================] - 107s 925ms/step - loss: 0.0136 - val_loss: 0.0215
Epoch 47/50
116/116 [==============================] - 120s 1s/step - loss: 0.0134 - val_loss: 0.0211
Epoch 48/50
116/116 [==============================] - 148s 1s/step - loss: 0.0129 - val_loss: 0.0214
Epoch 49/50
116/116 [==============================] - 131s 1s/step - loss: 0.0127 - val_loss: 0.0200
Epoch 50/50
116/116 [==============================] - 104s 901ms/step - loss: 0.0126 - val_loss: 0.0208


RNN으로 구현한 소스

from tensorflow import keras
from tensorflow.keras.utils import Sequence
from tensorflow.keras import layers
import tensorflow as tf
import numpy as np
import math

batch_size = 100
lr = 1e-3
n_epochs = 50
train_data_ratio = 0.8


class CustomDataset:
    def __init__(self, x_tensor_filename, y_tensor_filename, base_idx=None, cnt=None):
        self.x_fn = x_tensor_filename
        self.y_fn = y_tensor_filename
        self.base_idx = base_idx
        self.cnt = cnt
        if self.base_idx is None:
            with open(y_tensor_filename, 'r') as fp:
                for count, line in enumerate(fp):
                    pass
            self.total_len = count + 1
        else:
            self.total_len = cnt
        x_data = np.loadtxt(self.x_fn, delimiter=',', skiprows=0, max_rows=1)
        self.feature_cnt = len(x_data)

    def __getitem__(self, index):
        """
        주어진 인덱스 index 에 해당하는 샘플을 데이터셋에서 불러오고 반환합니다.
        """
        if self.base_idx is not None:
            index = index + self.base_idx
        x_data = np.loadtxt(self.x_fn, delimiter=',', skiprows=index, max_rows=1)
        # [0.37454012 0.95071431 0.73199394]
        if x_data.shape == ():
            x_data = np.array([x_data])
        y_data = np.loadtxt(self.y_fn, delimiter=',', skiprows=index, max_rows=1)
        # 1.5258549401766552
        if y_data.shape == ():
            y_data = np.array([y_data])
        # [1.5258549401766552]
        # x_data [5.          5.00999946 5.0199977 5.02999448 5.03998959] y_data [2.40299196]
        x_data = np.reshape(x_data, (-1, 1))
        # x_data [[5.        ][5.00999946][5.0199977 ][5.02999448][5.03998959]] y_data [2.40299196]
        return x_data, y_data

    def __len__(self):
        """
        데이터셋의 샘플 개수를 반환합니다.
        """
        return self.total_len


class CustomDataloader(Sequence):
    def __init__(self, _dataset, batch_size=1, shuffle=False):
        self.dataset = _dataset
        self.batch_size = batch_size
        self.total_len = math.ceil(len(self.dataset) / self.batch_size)
        self.shuffle = shuffle
        self.indexer = np.arange(len(self.dataset))
        self.on_epoch_end()

    def __getitem__(self, index):
        indexer = self.indexer[index * self.batch_size:(index + 1) * self.batch_size]
        batch_x = [self.dataset[i][0] for i in indexer]
        batch_y = [self.dataset[i][1] for i in indexer]
        #[[5.         5.00999946 5.0199977  5.02999448 5.03998959]
        # [5.00999946 5.0199977  5.02999448 5.03998959 5.04998279]
        # ...
        return np.array(batch_x), np.array(batch_y)

    def __len__(self):
        return self.total_len

    def on_epoch_end(self):
        if self.shuffle:
            np.random.shuffle(self.indexer)
            print("shuffle")


temp_dataset = CustomDataset("x_data.csv", "y_data.csv")
train_dataset_cnt = int(len(temp_dataset) * train_data_ratio)
val_dataset_cnt = len(temp_dataset) - train_dataset_cnt
train_dataset = CustomDataset("x_data.csv", "y_data.csv", 0, train_dataset_cnt)
val_dataset = CustomDataset("x_data.csv", "y_data.csv", train_dataset_cnt, val_dataset_cnt)
print(len(temp_dataset), train_dataset_cnt, val_dataset_cnt)
train_loader = CustomDataloader(train_dataset, batch_size=batch_size, shuffle=False)
val_loader = CustomDataloader(val_dataset, batch_size=batch_size, shuffle=False)

# https://keras.io/api/layers/
inputs = keras.Input(shape=(5, 1))
x = layers.SimpleRNN(6)(inputs)
x = layers.Dense(1, activation="linear")(x)
outputs = x
model = keras.Model(inputs, outputs)
model.summary()

# https://keras.io/api/optimizers/
# https://keras.io/api/losses/
INPUT_OPTIMIZER = tf.keras.optimizers.SGD(learning_rate=lr)
LOSS = tf.keras.losses.MeanSquaredError(reduction="auto", name="mean_squared_error")
model.compile(optimizer=INPUT_OPTIMIZER, loss=LOSS)
model.fit(train_loader, batch_size=batch_size, epochs=n_epochs, validation_data=val_loader)
model.save("model_rnn.keras")

LSTM 으로 모델만 바꾼 소스

from tensorflow import keras
from tensorflow.keras.utils import Sequence
from tensorflow.keras import layers
import tensorflow as tf
import numpy as np
import math

batch_size = 100
lr = 1e-3
n_epochs = 50
train_data_ratio = 0.8


class CustomDataset:
    def __init__(self, x_tensor_filename, y_tensor_filename, base_idx=None, cnt=None):
        self.x_fn = x_tensor_filename
        self.y_fn = y_tensor_filename
        self.base_idx = base_idx
        self.cnt = cnt
        if self.base_idx is None:
            with open(y_tensor_filename, 'r') as fp:
                for count, line in enumerate(fp):
                    pass
            self.total_len = count + 1
        else:
            self.total_len = cnt
        x_data = np.loadtxt(self.x_fn, delimiter=',', skiprows=0, max_rows=1)
        self.feature_cnt = len(x_data)

    def __getitem__(self, index):
        """
        주어진 인덱스 index 에 해당하는 샘플을 데이터셋에서 불러오고 반환합니다.
        """
        if self.base_idx is not None:
            index = index + self.base_idx
        x_data = np.loadtxt(self.x_fn, delimiter=',', skiprows=index, max_rows=1)
        # [0.37454012 0.95071431 0.73199394]
        if x_data.shape == ():
            x_data = np.array([x_data])
        y_data = np.loadtxt(self.y_fn, delimiter=',', skiprows=index, max_rows=1)
        # 1.5258549401766552
        if y_data.shape == ():
            y_data = np.array([y_data])
        # [1.5258549401766552]
        # x_data [5.          5.00999946 5.0199977 5.02999448 5.03998959] y_data [2.40299196]
        x_data = np.reshape(x_data, (-1, 1))
        # x_data [[5.        ][5.00999946][5.0199977 ][5.02999448][5.03998959]] y_data [2.40299196]
        return x_data, y_data

    def __len__(self):
        """
        데이터셋의 샘플 개수를 반환합니다.
        """
        return self.total_len


class CustomDataloader(Sequence):
    def __init__(self, _dataset, batch_size=1, shuffle=False):
        self.dataset = _dataset
        self.batch_size = batch_size
        self.total_len = math.ceil(len(self.dataset) / self.batch_size)
        self.shuffle = shuffle
        self.indexer = np.arange(len(self.dataset))
        self.on_epoch_end()

    def __getitem__(self, index):
        indexer = self.indexer[index * self.batch_size:(index + 1) * self.batch_size]
        batch_x = [self.dataset[i][0] for i in indexer]
        batch_y = [self.dataset[i][1] for i in indexer]
        #[[5.         5.00999946 5.0199977  5.02999448 5.03998959]
        # [5.00999946 5.0199977  5.02999448 5.03998959 5.04998279]
        # ...
        return np.array(batch_x), np.array(batch_y)

    def __len__(self):
        return self.total_len

    def on_epoch_end(self):
        if self.shuffle:
            np.random.shuffle(self.indexer)
            print("shuffle")


temp_dataset = CustomDataset("x_data.csv", "y_data.csv")
train_dataset_cnt = int(len(temp_dataset) * train_data_ratio)
val_dataset_cnt = len(temp_dataset) - train_dataset_cnt
train_dataset = CustomDataset("x_data.csv", "y_data.csv", 0, train_dataset_cnt)
val_dataset = CustomDataset("x_data.csv", "y_data.csv", train_dataset_cnt, val_dataset_cnt)
print(len(temp_dataset), train_dataset_cnt, val_dataset_cnt)
train_loader = CustomDataloader(train_dataset, batch_size=batch_size, shuffle=False)
val_loader = CustomDataloader(val_dataset, batch_size=batch_size, shuffle=False)

# https://keras.io/api/layers/
inputs = keras.Input(shape=(5, 1))
x = layers.LSTM(6)(inputs)
x = layers.Dense(1, activation="linear")(x)
outputs = x
model = keras.Model(inputs, outputs)
model.summary()

# https://keras.io/api/optimizers/
# https://keras.io/api/losses/
INPUT_OPTIMIZER = tf.keras.optimizers.SGD(learning_rate=lr)
LOSS = tf.keras.losses.MeanSquaredError(reduction="auto", name="mean_squared_error")
model.compile(optimizer=INPUT_OPTIMIZER, loss=LOSS)
model.fit(train_loader, batch_size=batch_size, epochs=n_epochs, validation_data=val_loader)
model.save("model_lstm.keras")

NN으로 구현한 소스

from tensorflow import keras
from tensorflow.keras.utils import Sequence
from tensorflow.keras import layers
import tensorflow as tf
import numpy as np
import math

batch_size = 100
lr = 1e-3
n_epochs = 5
train_data_ratio = 0.8


class CustomDataset:
    def __init__(self, x_tensor_filename, y_tensor_filename, base_idx=None, cnt=None):
        self.x_fn = x_tensor_filename
        self.y_fn = y_tensor_filename
        self.base_idx = base_idx
        self.cnt = cnt
        if self.base_idx is None:
            with open(y_tensor_filename, 'r') as fp:
                for count, line in enumerate(fp):
                    pass
            self.total_len = count + 1
        else:
            self.total_len = cnt

    def __getitem__(self, index):
        """
        주어진 인덱스 index 에 해당하는 샘플을 데이터셋에서 불러오고 반환합니다.
        """
        if self.base_idx is not None:
            index = index + self.base_idx
        x_data = np.loadtxt(self.x_fn, delimiter=',', skiprows=index, max_rows=1)
        # [0.37454012 0.95071431 0.73199394]
        if x_data.shape == ():
            x_data = np.array([x_data])
        y_data = np.loadtxt(self.y_fn, delimiter=',', skiprows=index, max_rows=1)
        # 1.5258549401766552
        if y_data.shape == ():
            y_data = np.array([y_data])
        # [1.5258549401766552]
        return x_data, y_data

    def __len__(self):
        """
        데이터셋의 샘플 개수를 반환합니다.
        """
        return self.total_len


class CustomDataloader(Sequence):
    def __init__(self, _dataset, batch_size=1, shuffle=False):
        self.dataset = _dataset
        self.batch_size = batch_size
        self.total_len = math.ceil(len(self.dataset) / self.batch_size)
        self.shuffle = shuffle
        self.indexer = np.arange(len(self.dataset))
        self.on_epoch_end()

    def __getitem__(self, index):
        indexer = self.indexer[index * self.batch_size:(index + 1) * self.batch_size]
        batch_x = [self.dataset[i][0] for i in indexer]
        batch_y = [self.dataset[i][1] for i in indexer]
        return np.array(batch_x), np.array(batch_y)

    def __len__(self):
        return self.total_len

    def on_epoch_end(self):
        if self.shuffle:
            np.random.shuffle(self.indexer)
            print("shuffle")


temp_dataset = CustomDataset("x_data.csv", "y_data.csv")
train_dataset_cnt = int(len(temp_dataset) * train_data_ratio)
val_dataset_cnt = len(temp_dataset) - train_dataset_cnt
train_dataset = CustomDataset("x_data.csv", "y_data.csv", 0, train_dataset_cnt)
val_dataset = CustomDataset("x_data.csv", "y_data.csv", train_dataset_cnt, val_dataset_cnt)
print(len(temp_dataset), train_dataset_cnt, val_dataset_cnt)
train_loader = CustomDataloader(train_dataset, batch_size=batch_size, shuffle=False)
val_loader = CustomDataloader(val_dataset, batch_size=batch_size, shuffle=False)

# https://keras.io/api/layers/
inputs = keras.Input(shape=(None, 5))
x = layers.Dense(6, activation="linear")(inputs)
x = layers.Dense(1, activation="linear")(x)
outputs = x
model = keras.Model(inputs, outputs)
model.summary()

# https://keras.io/api/optimizers/
# https://keras.io/api/losses/
INPUT_OPTIMIZER = tf.keras.optimizers.SGD(learning_rate=lr)
LOSS = tf.keras.losses.MeanSquaredError(reduction="auto", name="mean_squared_error")
model.compile(optimizer=INPUT_OPTIMIZER, loss=LOSS)
model.fit(train_loader, batch_size=batch_size, epochs=n_epochs, validation_data=val_loader)
model.save("model_nn.keras")


비교

import numpy as np
import tensorflow as tf
import math
import matplotlib.pyplot as plt


if __name__ == '__main__':
    flag = False
    fig, ax = plt.subplots()
    for model_name in ["model_rnn.keras", "model_lstm.keras", "model_nn.keras"]:
        model = tf.keras.models.load_model(model_name)
        model.summary()
        timed_y_list = []
        graph_data = []
        x_list = []
        time_step_size = 5
        predict_count = 20
        for xx in range(14400, 14400+predict_count+time_step_size, 1):
            x = xx / 1000.0
            y = 2.0 * math.sin(x) + math.sin(2.0 * x) + math.sin(6.0 * x) + math.cos(x) + 4.0
            timed_y_list.append(y)

        timed_y_data = np.array(timed_y_list)

        x_list.extend(timed_y_list[0:time_step_size])
        graph_data.extend(timed_y_list[0:time_step_size])
        for idx in range(predict_count):
            print("xlist",x_list)
            x_data = np.array(x_list)
            if model_name == "model_nn.keras":
                rsdata = np.reshape(x_data, (1, 5))
                print(rsdata)
                y = model.predict(rsdata, batch_size=1)
            else:
                rsdata = np.reshape(x_data, (1, -1, 1))
                print(rsdata)
                y = model.predict(rsdata, batch_size=1)
            print("y", y)
            x_list.append(y[0][0])
            x_list.pop(0)
            graph_data.append(y[0][0])

        if not flag:
            flag = True
            ax.plot(timed_y_data, label="real")

        ax.plot(np.array(graph_data), label=model_name)

    plt.legend()
    plt.savefig("tester.png")

결과 데이터를 마지막에 계속 추가해서 새로운 입력을 만들어서 호출 하는 방식으로 20개 정도 예측하도록 하였고 이것을 그래프로도 그려보았습니다.
nn으로 했을때 입력 포맷이 약간 달라지기 때문에 아래와 같이 작업 하였습니다.
            if model_name == "model_nn.keras":
                rsdata = np.reshape(x_data, (1, 5))
                print(rsdata)
                y = model.predict(rsdata, batch_size=1)
            else:
                rsdata = np.reshape(x_data, (1, -1, 1))
                print(rsdata)
                y = model.predict(rsdata, batch_size=1)

최적화를 하지 않은 상태에서 결론만 놓고 본다면 rnn,LSTM을 사용하지 않고 기존 nn에 feature를 늘리는 방식으로도 예측이 잘된다고 볼 수 있습니다.


댓글 없음:

댓글 쓰기