RLTrader의 실행 모듈 개발

2020-04-11 • rltraderstock, 주식투자, reinforcement learning, rl, 강화학습, main • 5 min read

강화학습 주식투자 실행 모듈(main.py)은 다양한 조건으로 강화학습을 수행할 수 있게 프로그램 인자를 구성하며 입력받은 인자에 따라 학습기 클래스를 이용해 강화학습을 수행하고 학습한 신경망들을 저장하는 메인 모듈입니다. 이 모듈에 대한 프로그램 인자와 강화학습 실행 로직을 살펴봅니다.

코드 조각 1: 프로그램 인자 설정

다양한 옵션을 지정해 강화학습을 수행할 수 있게 프로그램 인자를 구성합니다. 다음 코드조각은 프로그램 인자 설정 부분을 보여줍니다.

강화학습 주식투자 실행 모듈: 프로그램 인자 설정

import os
import sys
import logging
import argparse
import json

import settings
import utils
import data_manager

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--stock_code', nargs='+')
    parser.add_argument('--ver', choices=['v1', 'v2'], default='v2')
    parser.add_argument('--rl_method', 
        choices=['dqn', 'pg', 'ac', 'a2c', 'a3c'])
    parser.add_argument('--net', 
        choices=['dnn', 'lstm', 'cnn'], default='dnn')
    parser.add_argument('--num_steps', type=int, default=1)
    parser.add_argument('--lr', type=float, default=0.01)
    parser.add_argument('--discount_factor', type=float, default=0.9)
    parser.add_argument('--start_epsilon', type=float, default=0)
    parser.add_argument('--balance', type=int, default=10000000)
    parser.add_argument('--num_epoches', type=int, default=100)
    parser.add_argument('--delayed_reward_threshold', 
        type=float, default=0.05)
    parser.add_argument('--backend', 
        choices=['tensorflow', 'plaidml'], default='tensorflow')
    parser.add_argument('--output_name', default=utils.get_time_str())
    parser.add_argument('--value_network_name')
    parser.add_argument('--policy_network_name')
    parser.add_argument('--reuse_models', action='store_true')
    parser.add_argument('--learning', action='store_true')
    parser.add_argument('--start_date', default='20170101')
    parser.add_argument('--end_date', default='20171231')
    args = parser.parse_args()

인자들이 꽤 많은데, 각각에 대해 살펴보겠습니다.

파이썬 팁: 파이썬 프로그램 인자를 argparse 모듈의 ArgumentParser 클래스로 쉽게 구성할 수 있습니다. ArgumentParser 클래스 객체를 생성하고 add_argument() 함수로 프로그램 인자를 추가할 수 있습니다. 위치 인자와 키워드 인자를 구성할 수 있습니다. 키워드 인자는 인자명이 '-'나 '--'로 시작합니다. 인자의 유형을 type으로, 인자로 들어올 수 있는 값을 choices로, 인자의 기본 값을 default로 지정할 수 있습니다. ArgumentParser 클래스에 관한 더 상세한 정보는 파이썬 공식 문서에서 확인하기를 권합니다.

https://docs.python.org/ko/3/library/argparse.html

  • --stock_code: 강화학습의 환경이 될 주식의 종목 코드입니다. A3C의 경우 여러 개의 종목 코드를 입력합니다.
  • --ver: RLTrader의 버전을 명시합니다. 기본값으로 v2를 사용합니다.
  • --rl_method: 강화학습 방식을 설정합니다. dqn, pg, ac, a2c, a3c 중에서 하나를 정합니다.
  • --net: 가치 신경망과 정책 신경망에서 사용할 신경망 유형을 선택합니다. dnn, lstm, cnn 중에서 하나를 정합니다.
  • --num_steps: lstmcnn에서 사용할 Step 크기를 정합니다. 이 크기만큼 자질 벡터의 크기가 확장됩니다.
  • --lr: 학습 속도를 정합니다.0.01, 0.001 등으로 정할 수 있습니다.
  • --discount_factor: 할인율을 정합니다. 0.9, 0.8 등으로 정할 수 있습니다.
  • --start_epsilon: 시작 탐험률을 정합니다. 에포크가 수행되면서 탐험률은 감소합니다. 1, 0.5 등으로 정할 수 있습니다.
  • --balance: 주식투자 시뮬레이션을 위한 초기 자본금을 설정합니다.
  • --num_epoches: 수행할 에포크 수를 지정합니다. 100, 1000 등으로 정할 수 있습니다.
  • --delayed_reward_threshold: 지연 보상의 임곗값을 정합니다. 0.05, 0.1 등으로 정할 수 있습니다.
  • --backend: Keras의 백엔드로 사용할 프레임워크를 설정합니다. tensorflowplaidml을 선택할 수 있습니다.
  • --output_name: 로그, 가시화 파일, 신경망 모델 등의 출력 파일을 저장할 폴더의 이름
  • --value_network_name: 가치 신경망 모델 파일명
  • --policy_network_name: 정책 신경망 모델 파일명
  • --reuse_models: 신경망 모델 재사용 유무
  • --learning: 강화학습 유무
  • --start_date: 차트 데이터 및 학습 데이터 시작 날짜
  • --end_date: 차트 데이터 및 학습 데이터 끝 날짜

코드 조각 2: 강화학습 설정

다음 코드조각에서 입력받은 프로그램 인자에 맞게 강화학습 설정을 이어갑니다.

예제 5.65 강화학습 주식투자 실행 모듈: 강화학습 설정 (1)

    # Keras 백엔드 설정
    if args.backend == 'tensorflow':
        os.environ['KERAS_BACKEND'] = 'tensorflow'
    elif args.backend == 'plaidml':
        os.environ['KERAS_BACKEND'] = 'plaidml.keras.backend'

    # 출력 경로 설정
    output_path = os.path.join(settings.BASE_DIR, 
        'output/{}_{}_{}'.format(args.output_name, args.rl_method, args.net))
    if not os.path.isdir(output_path):
        os.makedirs(output_path)

    # 파라미터 기록
    with open(os.path.join(output_path, 'params.json'), 'w') as f:
        f.write(json.dumps(vars(args)))

    # 로그 기록 설정
    file_handler = logging.FileHandler(filename=os.path.join(
        output_path, "{}.log".format(args.output_name)), encoding='utf-8')
    stream_handler = logging.StreamHandler(sys.stdout)
    file_handler.setLevel(logging.DEBUG)
    stream_handler.setLevel(logging.INFO)
    logging.basicConfig(format="%(message)s",
        handlers=[file_handler, stream_handler], level=logging.DEBUG)

backend 인자에서 tensorflowplaidml을 입력할 수 있습니다. tensorflow가 기본값이며 plaidml을 입력했을 때 PlaidML 백엔드가 활성화될 수 있게 환경변수 KERAS_BACKEND를 수정합니다.

가능하다면 tensorflow를 사용하기를 권장합니다. plaidML의 경우 스레드를 사용하는 A3C가 동작하지 않을 수 있습니다.

그리고 출력 경로를 입력받은 output_name, rl_method, net 인자로 설정합니다. 그 경로는 output/<output_name>_<rl_method>_<net>이 됩니다.

파이썬 팁: 파일 경로는 윈도우 운영체제에서는 \로 구분하고 리눅스 계열의 운영체제에서는 /로 구분합니다. 이렇게 운영체제마다 경로 구성법이 다르기 때문에 파이썬에서는 os.path.join() 함수로 경로를 구성하는 것이 좋습니다. os.path.join('parent', 'child')는 윈도우에서는 'parent\child'가 되고 리눅스에서는 'parent/child'가 됩니다.

파이썬 팁: os.path 모듈에는 경로와 관련된 다양한 함수들이 있습니다. os.path.exists(path) 함수는 path가 존재하는지 확인합니다. os.path.isdir(path) 함수는 path가 존재하고 폴더인지 확인합니다. os.path.isfile(path)path가 존재하고 파일인지 확인합니다.

파이썬 팁: os.makedirs(path) 함수는 path에 포함된 폴더가 없을 경우 생성합니다. path/a/b/c이고 현재 /a라는 경로만 존재한다면 /a 폴더 하위에 b 폴더를 생성하고 /a/b 폴더 하위에 c 폴더를 생성해 최종적으로 /a/b/c 경로가 존재하게 만듭니다.

이 경로에 먼저 입력받은 프로그램 인자를 JSON 형태로 저장합니다. 프로그램 인자를 딕셔너리로 만든 다음 params.json 파일에 JSON 형태로 저장합니다. 그리고 이 출력 경로에 로그 파일을 생성합니다.

파이썬 팁: JSON은 JavaScript Object Notation의 약자로, 제이슨이라고 읽습니다. JSON은 파이썬의 딕셔너리(dictionary)와 상호 호환이 잘됩니다. 파이썬의 json 모듈의 loads() 함수와 dumps() 함수로 JSON 문자열에서 딕셔너리로, 딕셔너리에서 JSON 문자열로 변환할 수 있습니다.

파이썬 팁: ArgumentParser 객체에 저장돼 있는 프로그램 인자들은 파이썬 내장함수 vars()로 딕셔너리 형태로 얻을 수 있습니다.

다음은 강화학습에 필요한 에이전트 클래스와 학습기 클래스를 임포트하고 가치 신경망과 정책 신경망의 모델 파일 경로를 설정하는 부분입니다.

강화학습 주식투자 실행 모듈: 강화학습 설정 (2)

    # 로그, Keras 백엔드 설정을 먼저하고 RLTrader 모듈을 이후에 임포트해야 함
    from agent import Agent
    from learners import DQNLearner, PolicyGradientLearner, \
        ActorCriticLearner, A2CLearner, A3CLearner

    # 모델 경로 준비
    value_network_path = ''
    policy_network_path = ''
    if args.value_network_name is not None:
        value_network_path = os.path.join(settings.BASE_DIR, 
            'models/{}.h5'.format(args.value_network_name))
    else:
        value_network_path = os.path.join(
            output_path, '{}_{}_value_{}.h5'.format(
                args.rl_method, args.net, args.output_name))
    if args.policy_network_name is not None:
        policy_network_path = os.path.join(settings.BASE_DIR, 
            'models/{}.h5'.format(args.policy_network_name))
    else:
        policy_network_path = os.path.join(
            output_path, '{}_{}_policy_{}.h5'.format(
                args.rl_method, args.net, args.output_name))

에이전트와 강화학습 학습기 클래스들을 여기서 임포트하는 이유는 로그 설정을 먼저 해야지 해당 클래스에도 이 설정이 적용되기 때문입니다.

가치 신경망 모델 경로는 value_network_name이 지정된 경우 models 폴더에 <value_network_name>.h5 파일로 준비합니다. 이는 모델을 재사용할 때 편하게 모델을 지정하기 위한 옵션입니다. 비슷하게 정책 신경망 모델도 policy_network_name이 지정돼 있으면 models 폴더에 저장합니다. value_network_namepolicy_network_name 인자가 없는 경우 출력 경로에 모델 파일을 저장합니다.

코드 조각 2: 강화학습 실행

다음은 학습기 클래스의 인자를 설정하는 부분을 보여줍니다.

강화학습 주식투자 실행 모듈: 강화학습 실행 (1)

    common_params = {}
    list_stock_code = []
    list_chart_data = []
    list_training_data = []
    list_min_trading_unit = []
    list_max_trading_unit = []

    for stock_code in args.stock_code:
        # 차트 데이터, 학습 데이터 준비
        chart_data, training_data = data_manager.load_data(
            os.path.join(settings.BASE_DIR, 
            'data/{}/{}.csv'.format(args.ver, stock_code)), 
            args.start_date, args.end_date, ver=args.ver)

        # 최소/최대 투자 단위 설정
        min_trading_unit = max(int(100000 / chart_data.iloc[-1]['close']), 1)
        max_trading_unit = max(int(1000000 / chart_data.iloc[-1]['close']), 1)

        # 공통 파라미터 설정
        common_params = {'rl_method': args.rl_method, 
            'delayed_reward_threshold': args.delayed_reward_threshold,
            'net': args.net, 'num_steps': args.num_steps, 'lr': args.lr,
            'output_path': output_path, 'reuse_models': args.reuse_models}

A3C에서는 여러 주식 종목의 강화학습을 병렬로 진행하기 때문에 종목 코드, 차트 데이터, 학습 데이터, 최소 및 최대 투자 단위를 리스트로 보관합니다.

최소 투자 단위는 10만 원 안에 매수할 수 있는 주식 수, 최대 투자 단위에는 100만 원 안으로 매수할 수 있는 주식 수를 정해줬습니다.

강화학습 방법들은 공통 파라미터를 가집니다. common_params 딕셔너리에 이 공통 파라미터들을 저장합니다. 강화학습 방법, 지연 보상 임곗값, 신경망 종류, LSTM과 CNN에서 사용할 스텝 수, 학습 속도, 출력 경로, 신경망 모델 재사용 여부를 저장합니다.

다음은 강화학습을 수행하기 위해 학습기 클래스를 생성하고 실행하는 부분입니다.

강화학습 주식투자 실행 모듈: 강화학습 실행 (2)

        # 강화학습 시작
        learner = None
        if args.rl_method != 'a3c':
            common_params.update({'stock_code': stock_code,
                'chart_data': chart_data, 
                'training_data': training_data,
                'min_trading_unit': min_trading_unit, 
                'max_trading_unit': max_trading_unit})
            if args.rl_method == 'dqn':
                learner = DQNLearner(**{**common_params, 
                'value_network_path': value_network_path})
            elif args.rl_method == 'pg':
                learner = PolicyGradientLearner(**{**common_params, 
                'policy_network_path': policy_network_path})
            elif args.rl_method == 'ac':
                learner = ActorCriticLearner(**{**common_params, 
                    'value_network_path': value_network_path, 
                    'policy_network_path': policy_network_path})
            elif args.rl_method == 'a2c':
                learner = A2CLearner(**{**common_params, 
                    'value_network_path': value_network_path, 
                    'policy_network_path': policy_network_path})
            if learner is not None:
                learner.run(balance=args.balance, 
                    num_epoches=args.num_epoches, 
                    discount_factor=args.discount_factor, 
                    start_epsilon=args.start_epsilon,
                    learning=args.learning)
                learner.save_models()
        else:
            list_stock_code.append(stock_code)
            list_chart_data.append(chart_data)
            list_training_data.append(training_data)
            list_min_trading_unit.append(min_trading_unit)
            list_max_trading_unit.append(max_trading_unit)

강화학습 종류에 맞게 강화학습 학습기 클래스를 정하고 가치 신경망과 정책 신경망의 경로를 지정해줍니다. 학습기 클래스의 객체를 생성하고 객체의 run() 함수를 호출해 강화학습을 시작합니다. 강화학습은 학습 데이터의 크기, 에포크, 장비의 성능 등에 의해 수행 시간에 큰 차이가 있을 수 있습니다.

강화학습을 마치면 학습한 신경망 모델들을 저장하기 위해 save_models() 함수를 호출합니다.

A3C 강화학습을 위해 A3CLearner 클래스 객체를 생성하고 run()save_models() 함수를 호출하는 부분입니다.

강화학습 주식투자 실행 모듈: 강화학습 실행 (3)

    if args.rl_method == 'a3c':
        learner = A3CLearner(**{
            **common_params, 
            'list_stock_code': list_stock_code, 
            'list_chart_data': list_chart_data, 
            'list_training_data': list_training_data,
            'list_min_trading_unit': list_min_trading_unit, 
            'list_max_trading_unit': list_max_trading_unit,
            'value_network_path': value_network_path, 
            'policy_network_path': policy_network_path})

        learner.run(balance=args.balance, num_epoches=args.num_epoches, 
                    discount_factor=args.discount_factor, 
                    start_epsilon=args.start_epsilon,
                    learning=args.learning)
        learner.save_models()

A3CLearner 클래스는 차트 데이터, 학습 데이터 등의 인자를 리스트로 받습니다. A3CLearner 클래스의 run() 함수에서는 스레드를 이용해 복수의 A2C 강화학습을 병렬로 수행합니다. 각 A2C 강화학습은 가치 신경망과 정책 신경망을 공유합니다.

강화학습 종료 후 A3C 강화학습으로 학습한 가치 신경망과 정책 신경망을 파일로 저장합니다.

파이썬 팁: 대부분의 프로그래밍 언어에서 프로그램을 시작하기 위한 부분을 main이라고합니다. 파이썬에서는 if __name__ == "__main__: 구문 안에 작성된 코드가 main의 역할을 합니다.