RLTrader의 신경망 모듈 개발

2020-04-01 • rltraderstock, 주식투자, reinforcement learning, rl, 강화학습, network, 신경망 • 10 min read

이번 절에서는 신경망 모듈에 포함된 여러 신경망 클래스의 속성과 함수를 살펴보고 파이썬으로 구현한 소스 코드를 상세히 확인합니다.

이 모듈은 기본적인 심층 신경망 DNN, 순환 신경망의 일종인 LSTM, 합성곱 신경망 CNN을 포함합니다. 다음은 DNN 신경망 구조의 예를 보여줍니다.

DNN 신경망 구조의 예

DNN 신경망 구조의 예

총 6개 층으로 구성되고 입력층은 28차원, 출력층은 2차원입니다. 입력층은 학습 데이터의 차원인 26차원과 에이전트 상태의 차원인 2차원을 합한 28차원이고, 출력층은 투자 행동인 매수와 매도로 2차원을 가집니다. 신경망의 구조는 원하는 대로 얼마든지 조정할 수 있습니다. 다만 신경망이 복잡해질수록 학습 데이터양이 많고 충분한 성능의 학습 장비가 필요할 수 있습니다.

은닉층은 활성화 함수로 Sigmoid를 사용했고 출력층의 경우 가치 신경망은 활성화 함수를 Linear로, 정책 신경망은 활성화 함수를 Sigmoid로 정했는데, 이 또한 다른 활성화 함수를 사용해 구성할 수 있습니다. 예를 들어 가치 신경망의 출력 활성화 함수로 tanh를, 정책 신경망의 출력 활성화 함수로 Softmax를 사용할 수 있습니다. 활성화 함수를 변경하면 학습 데이터를 생성하는 부분에서 레이블 부여 방식을 적절히 수정해야 합니다.

신경망 구조는 DNN 외에도 LSTM, CNN 등을 신경망으로 활용할 수 있습니다.

신경망 모듈의 주요 속성과 함수

신경망 모듈(networks.py)은 가치 신경망과 정책 신경망으로 사용하기 위한 다양한 신경망 클래스를 가집니다. 신경망 클래스의 주요 속성과 함수는 다음과 같습니다.

속성

  • shared_network: 신경망의 상단부로 여러 신경망이 공유할 수 있음 (예를 들어, A2C에서는 가치 신경망과 정책 신경망이 신경망의 상단부를 공유하고 하단 부분만 가치 예측과 확률 예측을 위해 달라짐)
  • activation: 신경망의 출력 레이어 활성화 함수 이름 ("linear", "sigmoid" 등)
  • loss: 신경망의 손실 함수
  • lr: 신경망의 학습 속도
  • model: Keras 라이브러리로 구성한 최종 신경망 모델

함수

  • predict(): 신경망을 통해 투자 행동별 가치나 확률 계산
  • train_on_batch(): 배치 학습을 위한 데이터 생성
  • save_model(): 학습한 신경망을 파일로 저장
  • load_model(): 파일로 저장한 신경망을 로드
  • get_shared_network(): 신경망의 상단부를 생성하는 클래스 함수

코드 조각 1: 신경망 클래스

다음은 networks 모듈의 의존성 임포트 부분입니다. 텐서플로 버전 2를 사용하기 위해 tensorflow 패키지 내에 있는 keras 패키지를 사용합니다. plaidML을 사용하기 위해 keras 패키지를 바로 사용합니다. 향후 keras 패키지에서 텐서플로 버전 2가 지원되면 이 부분은 간소화할 수 있습니다.

networks 모듈의 의존성 임포트 부분

import os
import threading
import numpy as np

class DummyGraph:
    def as_default(self): return self
    def __enter__(self): pass
    def __exit__(self, type, value, traceback): pass

def set_session(sess): pass

graph = DummyGraph()
sess = None

if os.environ['KERAS_BACKEND'] == 'tensorflow':
    from tensorflow.keras.models import Model
    from tensorflow.keras.layers import Input, Dense, LSTM, Conv2D, \
        BatchNormalization, Dropout, MaxPooling2D, Flatten
    from tensorflow.keras.optimizers import SGD
    from tensorflow.keras.backend import set_session
    import tensorflow as tf
    graph = tf.get_default_graph()
    sess = tf.compat.v1.Session()
elif os.environ['KERAS_BACKEND'] == 'plaidml.keras.backend':
    from keras.models import Model
    from keras.layers import Input, Dense, LSTM, Conv2D, \
        BatchNormalization, Dropout, MaxPooling2D, Flatten
    from keras.optimizers import SGD

다양한 딥러닝 라이브러리 중에서 깃허브 저장소(Github repository)의 사용 횟수(used by), 인기 점수(star), 복사 횟수(fork)를 근거로 사용자가 가장 많다고 판단되는 라이브러리는 TensorFlow입니다. 2020년 2월 기준, 주요 라이브러리의 사용 횟수와 인기 점수는 다음 표와 같습니다.

주요 딥러닝 라이브러리 깃허브 사용 횟수와 인기 점수

라이브러리 Used by Star Fork 비고
TensorFlow 65.3k 141k 80.1k
Keras 42.5k 47k 17.8k TensorFlow, plaidML 등을 백엔드로 사용
PyTorch 23.2k 36.5k 9.2k
Caffe - 29.9k 18.1k
Theano 7.1k 9.1k 2.5k 개발 중단됨
Lasagne 582 3.7k 981

2021월 12월 기준 Star 내림차순

라이브러리 Used by Star Fork 비고
TensorFlow 169k 161k 86k TF2에서 많은 이슈 발생
Keras - 53.5k 18.9k TensorFlow, plaidML 등이 백엔드, TensorFlow에 포함
PyTorch - 52.8k 14.5k 최근 급부상
Caffe - 32.2k 18.9k
Theano 11.7k 9.5k 2.5k 개발 중단됨
Lasagne 863 3.8k 977 Theano가 백엔드

TensorFlow가 독보적으로 높은 수치임을 볼 수 있습니다. TensorFlow의 인기 점수는 Caffe와 PyTorch의 4배에 달합니다. 그래서 TensorFlow를 쓰기로 결정을 했고 이를 더 쉽고 간단하게 사용하고 싶어서 Keras를 최종적으로 선택했습니다. 물론 다른 라이브러리를 사용하고 싶다면 이 모듈을 수정하면 됩니다.

Keras를 사용해서 얻을 수 있는 또 하나의 장점은 백엔드를 쉽게 교체할 수 있다는 것입니다. NVIDIA CUDA Toolkit을 사용하지 못하는 그래픽카드도 딥러닝에 활용할 수 있는 plaidML을 Keras 백엔드의 대안으로 사용할 수 있습니다.

파이썬 팁: 파이썬에서는 딥러닝을 위해 주로 TensorFlow, Keras, Lasagne, Caffe, PyTorch 등의 라이브러리를 사용합니다. 이들 중 Keras는 TensorFlow, Theano 등을 백엔드(backend)로 사용하는 상위 라이브러리입니다. 즉, Keras는 Tensorflow, Theano 등의 백엔드 라이브러리를 좀 더 쉽게 사용하게 해주는 래퍼(wrapper) 라이브러리입니다. Lasagne도 그와 비슷하게 Theano를 좀 더 쉽게 사용하게 해줍니다. 각 라이브러리의 URL은 다음과 같습니다.

  • TensorFlow: https://www.tensorflow.org/
  • Theano: http://deeplearning.net/software/theano/
  • Keras: https://keras.io/
  • Lasagne: https://github.com/Lasagne/Lasagne
  • Caffe: http://caffe.berkeleyvision.org/
  • PyTorch: https://pytorch.org/

필요한 Keras 클래스를 모두 임포트합니다. 신경망 요소로 Dense, LSTM, Conv2D 등이 사용됩니다. Keras의 백엔드에 따라 적절하게 모듈과 클래스를 임포트합니다. Keras 백엔드 이름은 os.environ에 'KERAS_BACKEND'의 키로 저장돼 있습니다. 이 값을 백엔드가 보고 TensorFlow인지 plaidML인지 알 수 있습니다.

plaidML을 사용하는 경우 TensorFlow를 사용할 때와 코드상의 차이가 나지 않게 하기 위해 더미 그래프 클래스 객체를 생성합니다. 그래프 객체는 TensorFlow를 사용하는 경우 기본 그래프를 tf.get_default_graph() 함수를 호출해 구해옵니다. 세션 역시 TensorFlow 사용 시 Session 클래스의 객체를 생성합니다.

파이썬 팁: TensorFlow에서 신경망 모델을 정의하기 위한 공간을 그래프(graph)라고 하며 정의한 모델을 실행하는 공간을 세션(session)이라고 합니다.

다음 코드는 뒤에서 다룰 DNN, LSTMNetwork, CNN 클래스의 상위 클래스로 사용되는 Network 클래스를 보여줍니다.

Network 클래스: 생성자와 함수

class Network:
    lock = threading.Lock()

    def __init__(self, input_dim=0, output_dim=0, lr=0.001, 
                shared_network=None, activation='sigmoid', loss='mse'):
        self.input_dim = input_dim
        self.output_dim = output_dim
        self.lr = lr
        self.shared_network = shared_network
        self.activation = activation
        self.loss = loss
        self.model = None

Network 클래스는 RLTrader에서 신경망이 공통으로 가질 속성과 함수를 정의해 놓은 클래스입니다. 이 자체를 사용한다기보다는 Network 클래스를 상속한 DNN, LSTMNetwork, CNN 클래스를 사용합니다.

파이썬 팁: 클래스 선언부에서 이름 오른쪽에 괄호 안에 상속할 클래스를 지정할 수 있습니다. 상속한 클래스는 super()로 참조할 수 있습니다. 일반적으로 클래스의 생성자 __init__() 안에서 상위 클래스의 생성자를 호출합니다.

A3C에서는 스레드를 이용해 병렬로 신경망을 사용하기 때문에 스레드 간의 충돌 방지를 위해 Lock 클래스의 객체를 가지고 있습니다.

속성으로는 입출력 데이터 크기, 학습 속도, 공유 신경망, 활성화 함수, 학습 손실, 최종 신경망 모델을 가지고 있습니다.

다음은 신경망 클래스의 함수들을 보여줍니다.

Network 클래스: 함수

    def predict(self, sample):
        with self.lock:
            with graph.as_default():
                if sess is not None:
                    set_session(sess)
                return self.model.predict(sample).flatten()

    def train_on_batch(self, x, y):
        loss = 0.
        with self.lock:
            with graph.as_default():
                if sess is not None:
                    set_session(sess)
                loss = self.model.train_on_batch(x, y)
        return loss

    def save_model(self, model_path):
        if model_path is not None and self.model is not None:
            self.model.save_weights(model_path, overwrite=True)

    def load_model(self, model_path):
        if model_path is not None:
            self.model.load_weights(model_path)

predict() 함수는 샘플에 대한 행동의 가치 또는 확률 예측하고 train_on_batch() 함수는 학습 데이터와 레이블 x, y를 입력으로 받아서 모델을 학습시킵니다. A3C에서는 여러 스레드가 병렬로 신경망을 사용할 수 있기 때문에 충돌이 일어나지 않게 스레드들의 동시 사용을 막습니다.

TensorFlow를 사용하는 경우 A3C의 스레드 사용에 대응하기 위해 그래프와 세션을 지정해 줍니다.

파이썬 팁: with 구문은 주어진 객체의 __enter__() 함수를 호출하고 with 구문을 빠져나올 때 이 객체의 __exit__() 함수를 호출합니다.

__enter__(self): with 구문에 들어갈 때 호출되는 함수 인터페이스

__exit__(self, type, value, traceback): with 구문을 빠져나올 때 호출되는 함수 인터페이스

파이썬 팁: 멀티 스레드 작업에서 스레드 간의 간섭 없이 어떠한 작업을 수행하기 위해 threading 모듈의 Lock 클래스를 사용할 수 있습니다. Lock 클래스의 객체를 생성하고 다음과 같이 with 구문으로 코드를 감싸주면 됩니다.

with <Lock 클래스 객체>: <Thread-safe 코드>

파이썬 팁: Keras의 Model 클래스의 함수인 train_on_batch()는 입력으로 들어온 학습 데이터 집합(배치, Batch)으로 신경망을 한 번 학습합니다.

상세한 설명은 https://keras.io/models/model/에서 확인할 수 있습니다.

또한 Network 클래스는 모델을 파일로 저장하는 save_model() 함수, 파일로부터 모델을 읽어오는 load_model() 함수 가지고 있습니다. 신경망을 학습하는 데는 많은 시간과 컴퓨팅 자원이 소모되므로 한 번 학습한 신경망을 저장해놓고 필요할 때 불러와서 사용하는 것은 필수 기능입니다.

현재까지의 데이터로 신경망을 학습하고 파일로 저장한 다음 앞으로의 투자에서 저장한 신경망 모델을 불러와서 사용할 수 있습니다. 불러온 신경망에서 추가로 학습을 진행해 개선된 신경망을 만들 수도 있습니다.

파이썬 팁: Keras의 Model 클래스의 함수인 save_weights()는 인공 신경망을 구성하기 위한 값들을 HDF5 파일로 저장합니다. 이렇게 저장한 파일을 load_weights() 함수로 불러올 수 있습니다. 즉, 훈련시킨 인공지능 신경망 모델을 HDF5 파일 형식으로 저장해둘 수 있는 것입니다. 그랬다가 해당 모델을 활용해야 할 때 HDF5 파일을 load_weights() 함수로 불러오면 즉각 활용할 수 있습니다.

상세한 설명은 About Keras Models에서 확인할 수 있습니다.

다음은 신경망 종류에 따라 공유 신경망을 획득하는 클래스 함수입니다.

Network 클래스: 공유 신경망 획득 함수

    @classmethod
    def get_shared_network(cls, net='dnn', num_steps=1, input_dim=0):
        with graph.as_default():
            if sess is not None:
                set_session(sess)
            if net == 'dnn':
                return DNN.get_network_head(Input((input_dim,)))
            elif net == 'lstm':
                return LSTMNetwork.get_network_head(
                    Input((num_steps, input_dim)))
            elif net == 'cnn':
                return CNN.get_network_head(
                    Input((1, num_steps, input_dim)))

get_shared_network() 함수는 DNN, LSTM, CNN 신경망의 공유 신경망을 생성하는 클래스 함수입니다. Network 클래스의 하위 클래스들은 각각 get_network_head() 클래스 함수를 가지고 있습니다. 신경망 유형에 따라 DNN, LSTMNetwork, CNN 클래스의 클래스 함수인 get_network_head()를 호출해 공유 신경망 모델을 가져옵니다.

파이썬 팁: 클래스 함수는 인스턴스를 만들지 않고 사용할 수 있는 함수입니다. ClassName.function_name()과 같이 호출할 수 있습니다. 유의할 점은 클래스 함수에서는 인스턴스 변수인 self.variable_name을 사용할 수 없습니다.

코드 조각 2: DNN 클래스

다음은 Network 클래스를 상속해 심층 신경망을 구현한 DNN 클래스를 보여줍니다.

DNN 클래스: 생성자

class DNN(Network):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        with graph.as_default():
            if sess is not None:
                set_session(sess)
            inp = None
            output = None
            if self.shared_network is None:
                inp = Input((self.input_dim,))
                output = self.get_network_head(inp).output
            else:
                inp = self.shared_network.input
                output = self.shared_network.output
            output = Dense(
                self.output_dim, activation=self.activation, 
                kernel_initializer='random_normal')(output)
            self.model = Model(inp, output)
            self.model.compile(
                optimizer=SGD(lr=self.lr), loss=self.loss)

DNN 클래스의 생성자에서 공유 신경망이 지정돼 있지 않으면 스스로 생성합니다. get_network_head() 함수를 보면 DNN 클래스가 어떠한 구조로 신경망을 구축하는지 알 수 있습니다. 이 부분을 수정해 원하는 대로 심층 신경망 구조를 만들 수 있습니다. 이렇게 생성한 신경망 모델에 출력 레이어를 붙이고 최적화 함수, 학습 속도, 손실 함수를 설정해 최종 모델을 생성합니다. 최적화 알고리즘(optimization algorithm)으로는 확률적 경사 하강법(SGD)을 사용합니다.

다음은 DNN 신경망 구조를 설정하는 클래스 함수입니다.

DNN 클래스: 신경망 구조 설정 클래스 함수

    @staticmethod
    def get_network_head(inp):
        output = Dense(256, activation='sigmoid', 
            kernel_initializer='random_normal')(inp)
        output = BatchNormalization()(output)
        output = Dropout(0.1)(output)
        output = Dense(128, activation='sigmoid', 
            kernel_initializer='random_normal')(output)
        output = BatchNormalization()(output)
        output = Dropout(0.1)(output)
        output = Dense(64, activation='sigmoid', 
            kernel_initializer='random_normal')(output)
        output = BatchNormalization()(output)
        output = Dropout(0.1)(output)
        output = Dense(32, activation='sigmoid', 
            kernel_initializer='random_normal')(output)
        output = BatchNormalization()(output)
        output = Dropout(0.1)(output)
        return Model(inp, output)

여기서는 Dropout을 통해 과적합을 일정 부분 피하고 배치 정규화(batch normalization)로 학습을 안정화했습니다. 배치 정규화는 은닉 레이어의 입력을 정규화해 학습을 가속화하는 방법입니다.

은닉 레이어의 활성화 함수는 Sigmoid로 설정돼 있습니다. 여기서 레이어 개수, 각 레이어의 차원, 활성화 함수, 드롭아웃 비율 등을 얼마든지 수정할 수 있습니다. get_network_head() 클래스 함수는 뒤에서 다룰 다른 신경망 클래스들도 모두 가지고 있습니다.

다음 코드조각에서는 DNN 클래스의 배치 학습 함수와 예측 함수를 보여줍니다.

DNN 클래스: 배치 학습 함수와 예측 함수

    def train_on_batch(self, x, y):
        x = np.array(x).reshape((-1, self.input_dim))
        return super().train_on_batch(x, y)

    def predict(self, sample):
        sample = np.array(sample).reshape((1, self.input_dim))
        return super().predict(sample)

train_on_batch() 함수와 predict() 함수는 학습 데이터나 샘플의 형태(shape)를 적절히 변경하고 상위 클래스의 함수를 그대로 호출합니다. DNN은 (배치 크기, 자질 벡터 차원)의 모양을 가집니다. Model 클래스의 predict() 함수는 여러 샘플을 한꺼번에 받아서 신경망의 출력을 반환합니다. 하나의 샘플에 대한 결과만 받고 싶어도 샘플의 배열로 입력값을 구성해야 하기 때문에 2차원 배열로 재구성했습니다. 파이썬 팁: NumPy의 array() 함수는 파이썬 리스트를 N-dimensional array(ndarray) 형식으로 만들어줍니다. 즉, 파이썬 기본 리스트 자료 구조를 NumPy 라이브러리에서 사용하는 자료 구조로 변환하는 것입니다. sample의 크기는 input_dim의 값인 28로 1차원 배열입니다. 그림 5.5와 같이 이 배열을 1행 28열인 2차원 배열로 모양을 변환합니다.

sample을 Keras 입력 형식에 맞게 변환

파이썬 팁: NumPy ndarray는 reshape() 함수로 배열을 다른 차원으로 변환할 수 있습니다. 예를 들어 1차원 배열인 [ 1, 2, 3, 4, 5, 6 ]이 있을 때 이 배열의 shape(모양)은 (6,)입니다. 이를 (3, 2)로 만들면 [ [1, 2], [3, 4], [5, 6] ]이 됩니다. 이때 유의할 점은 배열의 총 크기는 변하지 않아야 합니다. 앞의 예에서는 변환한 후의 크기가 3×2=6이므로 1차원 배열일 때와 같습니다.

Keras에서 Model 클래스는 전체 신경망을 구성하는 클래스입니다. DNN 신경망에서 하나의 노드를 Dense 클래스로 구성합니다. Dense 클래스는 DNN 신경망 구조의 예에서 진한 원에 해당됩니다.

TensorFlow, Keras와 관련해 더 깊게 공부하고 싶다면 쿠지라 히코우즈쿠의 저서 <파이썬을 이용한 머신러닝, 딥러닝 실전 개발 입문>(위키북스 2019)을 추천합니다. TensorFlow와 Keras의 공식 문서 사이트 또한 참고할 것을 추천합니다.

  • TensorFlow 공식 문서 튜토리얼 사이트: https://www.tensorflow.org/tutorials/
  • Keras 공식 문서 사이트: https://keras.io/

코드 조각 3: LSTMNetwork 클래스

다음 코드조각에서는 Network 클래스를 상속해 LSTM 신경망을 구현한 LSTMNetwork 클래스를 보여줍니다.

LSTMNetwork 클래스: 생성자

class LSTMNetwork(Network):
    def __init__(self, *args, num_steps=1, **kwargs):
        super().__init__(*args, **kwargs)
        with graph.as_default():
            if sess is not None:
                set_session(sess)
            self.num_steps = num_steps
            inp = None
            output = None
            if self.shared_network is None:
                inp = Input((self.num_steps, self.input_dim))
                output = self.get_network_head(inp).output
            else:
                inp = self.shared_network.input
                output = self.shared_network.output
            output = Dense(
                self.output_dim, activation=self.activation, 
                kernel_initializer='random_normal')(output)
            self.model = Model(inp, output)
            self.model.compile(
                optimizer=SGD(lr=self.lr), loss=self.loss)

DNN과 전체적으로 구현은 비슷하지만 속성의 차이로 LSTMNetwork 클래스는 num_steps 변수를 가지고 있습니다. 몇 개의 샘플을 묶어서 LSTM 신경망의 입력으로 사용할지 결정하는 것입니다. 그에 따라 train_on_batch() 함수와 predict() 함수에서 학습 데이터와 샘플의 형태를 변경할 때 num_steps 변수를 사용하게 됩니다.

다음 코드조각은 LSTMNetwork 클래스의 신경망 구조 설정을 위한 클래스 함수를 보여줍니다.

LSTMNetwork 클래스: 신경망 구조 설정 클래스 함수

  @staticmethod
  def get_network_head(inp):
      output = LSTM(256, dropout=0.1, 
          return_sequences=True, stateful=False,
          kernel_initializer='random_normal')(inp)
      output = BatchNormalization()(output)
      output = LSTM(128, dropout=0.1,
          return_sequences=True, stateful=False,
          kernel_initializer='random_normal')(output)
      output = BatchNormalization()(output)
      output = LSTM(64, dropout=0.1,
          return_sequences=True, stateful=False,
          kernel_initializer='random_normal')(output)
      output = BatchNormalization()(output)
      output = LSTM(32, dropout=0.1,
          stateful=False,
          kernel_initializer='random_normal')(output)
      output = BatchNormalization()(output)
      return Model(inp, output)

get_network_head() 함수에서 LSTM 신경망의 구조를 정합니다. 이 함수를 원하는 대로 수정해 LSTM 신경망 구조를 조정할 수 있습니다. LSTM 레이어를 여러 겹 쌓을 경우 마지막 LSTM 레이어를 제외하고는 return_sequences=True 인자를 줘야 합니다. return_sequences에 True를 주면 해당 레이어의 출력의 개수를 num_steps만큼 유지합니다. 다음은 LSTMNetwork 클래스의 train_on_batch() 함수와 predict() 함수를 보여줍니다.

LSTMNetwork 클래스: 배치 학습 함수와 예측 함수

    def train_on_batch(self, x, y):
        x = np.array(x).reshape((-1, self.num_steps, self.input_dim))
        return super().train_on_batch(x, y)

    def predict(self, sample):
        sample = np.array(sample).reshape(
            (1, self.num_steps, self.input_dim))
        return super().predict(sample)

LSTM은 (배치 크기, 스텝 수, 자질 벡터 차원)의 모양으로 학습 데이터를 가집니다. train_on_batch() 함수와 predict() 함수에서 샘플의 형태를 변경하고 상위 클래스인 Network 클래스의 함수를 호출합니다.

코드 조각 4: CNN 클래스

다음 코드조각은 Network 클래스를 상속해 구현한 CNN 클래스를 보여줍니다. 이 클래스 역시 DNN, LSTM 클래스와 구조상 크게 다르지 않습니다.

CNN 클래스: 생성자

class CNN(Network):
    def __init__(self, *args, num_steps=1, **kwargs):
        super().__init__(*args, **kwargs)
        with graph.as_default():
            if sess is not None:
                set_session(sess)
            self.num_steps = num_steps
            inp = None
            output = None
            if self.shared_network is None:
                inp = Input((self.num_steps, self.input_dim, 1))
                output = self.get_network_head(inp).output
            else:
                inp = self.shared_network.input
                output = self.shared_network.output
            output = Dense(
                self.output_dim, activation=self.activation,
                kernel_initializer='random_normal')(output)
            self.model = Model(inp, output)
            self.model.compile(
                optimizer=SGD(lr=self.lr), loss=self.loss)

CNN 신경망 역시 LSTM 신경망과 마찬가지로 다차원 데이터를 다룰 수 있습니다. 컨볼루션 레이어는 1D, 2D, 3D 등으로 다양한 차원을 다룰 수 있습니다. 여기서는 2차원 데이터를 다루기 위해 케라스의 Conv2D 클래스를 활용합니다.

CNN 신경망 변수로 어울리는 이름은 아니지만, num_steps로 2차원 크기를 조정합니다. 주식 데이터 자체가 시계열이기 때문에 LSTM 마찬가지로 이름을 num_steps로 정했습니다.

다음은 CNN 클래스의 신경망 구조 설정을 위한 클래스 함수를 보여줍니다.

CNN 클래스: 신경망 구조 설정 클래스 함수

    @staticmethod
    def get_network_head(inp):
        output = Conv2D(256, kernel_size=(1, 5),
            padding='same', activation='sigmoid',
            kernel_initializer='random_normal')(inp)
        output = BatchNormalization()(output)
        output = MaxPooling2D(pool_size=(1, 2))(output)
        output = Dropout(0.1)(output)
        output = Conv2D(128, kernel_size=(1, 5),
            padding='same', activation='sigmoid',
            kernel_initializer='random_normal')(output)
        output = BatchNormalization()(output)
        output = MaxPooling2D(pool_size=(1, 2))(output)
        output = Dropout(0.1)(output)
        output = Conv2D(64, kernel_size=(1, 5),
            padding='same', activation='sigmoid',
            kernel_initializer='random_normal')(output)
        output = BatchNormalization()(output)
        output = MaxPooling2D(pool_size=(1, 2))(output)
        output = Dropout(0.1)(output)
        output = Conv2D(32, kernel_size=(1, 5),
            padding='same', activation='sigmoid',
            kernel_initializer='random_normal')(output)
        output = BatchNormalization()(output)
        output = MaxPooling2D(pool_size=(1, 2))(output)
        output = Dropout(0.1)(output)
        output = Flatten()(output)
        return Model(inp, output)

CNN 클래스의 신경망은 2차원 합성곱 레이어를 여러 겹 쌓아올린 구조를 가집니다. Conv2D의 padding 옵션을 "same"으로 줘서 입력과 출력의 크기가 같게 설정했습니다. 합성곱 윈도우 크기로 사용되는 kernel_size는 간단하게 (1, 5)로 정했습니다. 다른 신경망 종류도 마찬가지지만, CNN 신경망의 경우 특히 가변점이 많기 때문에 다양한 파라미터로 실험이 필요합니다.

다음에서 CNN 클래스의 train_on_batch() 함수와 predict() 함수를 보여줍니다.

CNN 클래스: 배치 학습 함수와 예측 함수

    def train_on_batch(self, x, y):
        x = np.array(x).reshape((-1, self.num_steps, self.input_dim, 1))
        return super().train_on_batch(x, y)

    def predict(self, sample):
        sample = np.array(sample).reshape(
            (-1, self.num_steps, self.input_dim, 1))
        return super().predict(sample)

2차원 합성곱 신경망이므로 (배치 크기, 스텝 수, 자질 벡터 차원, 1)의 모양으로 학습 데이터를 가집니다. 보통 학성곱 신경망은 이미지 데이터를 취급해 마지막 차원으로 RGB(Red, Green, Blue)와 같은 이미지 채널이 들어갑니다. 주식 데이터에는 채널이라 할 것이 없으므로 1로 고정했습니다. 학습 데이터와 샘플의 모양을 바꾸고 Network 클래스의 함수를 그대로 호출합니다.