コード例 #1
0
class PolicyLearner:
    def __init__(self,
                 stock_code,
                 chart_data,
                 training_data=None,
                 min_trading_unit=1,
                 max_trading_unit=2,
                 delayed_reward_threshold=.05,
                 lr=0.01):
        self.stock_code = stock_code  # 종목코드
        # 차트 데이터
        self.chart_data = chart_data
        # 환경
        self.environment = Environment(stock_code, chart_data)  # 환경 객체
        # 에이전트 객체
        self.agent = Agent(self.environment,
                           min_trading_unit=min_trading_unit,
                           max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold)
        # 학습 데이터 매칭
        self.training_data = training_data  # 학습 데이터
        # 샘플 초기 상태
        self.sample = None
        # 현재 학습 데이터 인덱스
        self.training_data_idx = -1
        # 입력은 환경에서 발생하는 모든 요소를 반영한다.
        # 정책 신경망; 입력 크기 = 학습 데이터의 크기 + 에이전트 상태 크기
        self.num_features = self.training_data.shape[1] + self.agent.STATE_DIM
        # 정첵 신경멍 객체 생성 및 매칭, 입력 계층 갯수, 출력 갯수, 학습률을 넣어 준다.
        self.policy_network = PolicyNetwork(input_dim=self.num_features,
                                            output_dim=self.agent.NUM_ACTIONS,
                                            lr=lr)
        self.visualizer = Visualizer()  # 가시화 모듈

    # 학습 초기화 - 다시 시작할 때 초기화 한다.
    def reset(self):
        self.sample = None
        self.training_data_idx = -1

    # 학습을 수행하는 함수
    def fit(self,
            num_epoches=1000,
            max_memory=60,
            balance=10000000,
            discount_factor=0,
            start_epsilon=.5,
            learning=True):
        logger.info(
            "LR: {lr}, DF: {discount_factor}, "
            "TU: [{min_trading_unit}, {max_trading_unit}], "
            "DRT: {delayed_reward_threshold}".format(
                lr=self.policy_network.lr,
                discount_factor=discount_factor,
                min_trading_unit=self.agent.min_trading_unit,
                max_trading_unit=self.agent.max_trading_unit,
                delayed_reward_threshold=self.agent.delayed_reward_threshold))

        # 가시화 준비
        # 차트 데이터는 변하지 않으므로 미리 가시화
        self.visualizer.prepare(self.environment.chart_data)

        # 가시화 결과 저장할 폴더 준비
        epoch_summary_dir = os.path.join(
            settings.BASE_DIR, 'epoch_summary/%s/epoch_summary_%s' %
            (self.stock_code, settings.timestr))
        # 에폭 요약 디렉토리를 만든다.
        if not os.path.isdir(epoch_summary_dir):
            os.makedirs(epoch_summary_dir)

        # 에이전트 초기 자본금 설정
        self.agent.set_balance(balance)

        # 학습에 대한 정보 초기화
        max_portfolio_value = 0
        # 에포크에서 성공한  숫자
        epoch_win_cnt = 0

        # 학습 반복
        for epoch in range(num_epoches):
            # 에포크 관련 정보 초기화
            loss = 0.
            # 반복 카운트
            itr_cnt = 0
            # 수익이 발생한 횟수 카운트
            win_cnt = 0
            # 탐험 횟수 - 심층 신경망이 아닌 탐색에 의한 회수
            exploration_cnt = 0
            # 배치 크기
            batch_size = 0
            # 긍정적인 학습 숫자
            pos_learning_cnt = 0
            # 부정적인 학습 숫자
            neg_learning_cnt = 0

            # 메모리 초기화
            memory_sample = []
            # 액션 리스트
            memory_action = []
            # 보상 리스트
            memory_reward = []
            # 확률 리스트
            memory_prob = []
            # 프로파일 가치
            memory_pv = []
            # 보유 주식수
            memory_num_stocks = []
            # 탐험 인덱스 기억
            memory_exp_idx = []
            # 학습 인덱스 기억
            memory_learning_idx = []

            # 환경, 에이전트, 정책 신경망 초기화
            self.environment.reset()
            self.agent.reset()
            self.policy_network.reset()
            self.reset()

            # 가시화 초기화
            self.visualizer.clear([0, len(self.chart_data)])

            # 학습을 진행할 수록 탐험 비율 감소
            if learning:
                epsilon = start_epsilon * (1. - float(epoch) /
                                           (num_epoches - 1))
            else:
                epsilon = 0

            while True:
                # 샘플 생성
                next_sample = self._build_sample()
                if next_sample is None:
                    break

                # 정책 신경망 또는 탐험에 의한 행동 결정
                action, confidence, exploration = self.agent.decide_action(
                    self.policy_network, self.sample, epsilon)

                # 결정한 행동을 수행하고 즉시 보상과 지연 보상 획득
                immediate_reward, delayed_reward = self.agent.act(
                    action, confidence)

                # 행동 및 행동에 대한 결과를 기억
                memory_sample.append(next_sample)
                # 현재의 행동을 저장
                memory_action.append(action)
                # 직접 보상 저장
                memory_reward.append(immediate_reward)
                # 포트폴리오 가치 저장
                memory_pv.append(self.agent.portfolio_value)
                # 주식 갯수 저장
                memory_num_stocks.append(self.agent.num_remain)
                # 샘플값(입력값), 액션값, 보상값을 튜플로 만들어 저장
                memory = [
                    (memory_sample[i], memory_action[i], memory_reward[i])
                    for i in list(range(len(memory_action)))[-max_memory:]
                ]
                if exploration:
                    # 탐색 인덱스를 저장한다.
                    memory_exp_idx.append(itr_cnt)
                    # 액션수만큼 갯수를 가진 리스트를 만들어 붙인다.
                    # 여기에는 출력계층(매수, 매도)에 대한 확률이 들어간다.
                    # 그러나 탐색일 때는 그 확률이 없으므로 이용할 수 없음 값으로 채운다.
                    memory_prob.append([np.nan] * Agent.NUM_ACTIONS)
                else:
                    memory_prob.append(self.policy_network.prob)

                # 반복에 대한 정보 갱신
                batch_size += 1
                itr_cnt += 1
                # 탐험에 의한 것이면 탐험 갯수 추가
                exploration_cnt += 1 if exploration else 0
                # 지연보상이 발생했으면 승리한 회수 추가
                win_cnt += 1 if delayed_reward > 0 else 0

                # 지연보상이 없고 배치크기가 최대 메모리보다 크거나 같으면
                if delayed_reward == 0 and batch_size >= max_memory:
                    # 즉시 보상으로 지연보상 값을 대치하고
                    delayed_reward = immediate_reward
                    # 기준 포트폴리오 값도 바꾸어준다.
                    self.agent.base_portfolio_value = self.agent.portfolio_value
                # 학습 모드이고 지연 보상이 존재할 경우 정책 신경망 갱신
                if learning and delayed_reward != 0:
                    # 배치 학습 데이터 크기
                    batch_size = min(batch_size, max_memory)
                    # 배치 학습 데이터 생성
                    x, y = self._get_batch(memory, batch_size, discount_factor,
                                           delayed_reward)
                    if len(x) > 0:
                        if delayed_reward > 0:
                            pos_learning_cnt += 1
                        else:
                            neg_learning_cnt += 1
                        # 정책 신경망 학습 시킴 - 지연 보상이 발생했을 때만 진행시킨다.
                        loss += self.policy_network.train_on_batch(x, y)
                        memory_learning_idx.append([itr_cnt, delayed_reward])
                    batch_size = 0

            # 에포크 관련 정보 가시화
            num_epoches_digit = len(str(num_epoches))
            epoch_str = str(epoch + 1).rjust(num_epoches_digit, '0')

            self.visualizer.plot(epoch_str=epoch_str,
                                 num_epoches=num_epoches,
                                 epsilon=epsilon,
                                 action_list=Agent.ACTIONS,
                                 actions=memory_action,
                                 num_stocks=memory_num_stocks,
                                 outvals=memory_prob,
                                 exps=memory_exp_idx,
                                 learning=memory_learning_idx,
                                 initial_balance=self.agent.initial_balance,
                                 pvs=memory_pv)
            self.visualizer.save(
                os.path.join(
                    epoch_summary_dir,
                    'epoch_summary_%s_%s.png' % (settings.timestr, epoch_str)))

            # 에포크 관련 정보 로그 기록
            if pos_learning_cnt + neg_learning_cnt > 0:
                loss /= pos_learning_cnt + neg_learning_cnt
            logger.info(
                "[Epoch %s/%s]\tEpsilon:%.4f\t#Expl.:%d/%d\t"
                "#Buy:%d\t#Sell:%d\t#Hold:%d\t"
                "#Stocks:%d\tPV:%s\t"
                "POS:%s\tNEG:%s\tLoss:%10.6f" %
                (epoch_str, num_epoches, epsilon, exploration_cnt, itr_cnt,
                 self.agent.num_buy, self.agent.num_sell, self.agent.num_hold,
                 self.agent.num_remain,
                 locale.currency(self.agent.portfolio_value, grouping=True),
                 pos_learning_cnt, neg_learning_cnt, loss))

            # 학습 관련 정보 갱신
            max_portfolio_value = max(max_portfolio_value,
                                      self.agent.portfolio_value)
            if self.agent.portfolio_value > self.agent.initial_balance:
                epoch_win_cnt += 1

        # 학습 관련 정보 로그 기록
        logger.info("Max PV: %s, \t # Win: %d" % (locale.currency(
            max_portfolio_value, grouping=True), epoch_win_cnt))

    # 배치 학습 데이터를 가져옴
    def _get_batch(self, memory, batch_size, discount_factor, delayed_reward):
        # create an ndarray with all zeros
        x = np.zeros((batch_size, 1, self.num_features))
        y = np.full((batch_size, self.agent.NUM_ACTIONS), 0.5)

        # 배열을 역순으로 도는 것은 가장 최근의 것에 가장 많은 점수를 주기 위해서이다.
        # 과거로 갈 수록 지연보상을 적용할 근거가 떨어지기 때문에 먼 과거의 일일 수록 할인 요인을 적용한다.
        for i, (sample, action,
                reward) in enumerate(reversed(memory[-batch_size:])):
            # x[i] 에는 특징값 수만큼 값들이 3차원으로 구성되어 들어갑니다.
            # 일단 2차원은 반드시 구성하고 데이터가 남을 경우 3차원을 구성하게 된다.
            x[i] = np.array(sample).reshape((-1, 1, self.num_features))
            # y[i, action]에는 액션을 취하면 1.0, 취하지 않았으면 0.5로 들어갑니다.
            # action 0이면 매수이므로 [1.0, 0.5]라고 들어가게 됩니다.
            # action 1이면 매도이므로 [0.5, 1.0]라고 들어가게 됩니다.
            y[i, action] = (delayed_reward + 1) / 2
            # 할인요소를 i승하여 곱해줌.
            if discount_factor > 0:
                y[i, action] *= discount_factor**i
        return x, y

    # 샘플 생성함수
    def _build_sample(self):
        # 차트 데이터를 가져온다.
        self.environment.observe()
        # 학습데이터 크기 끝에 도달하지 않았다면
        if len(self.training_data) > self.training_data_idx + 1:
            # 학습 인덱스 증가
            self.training_data_idx += 1
            temp2 = self.training_data.iloc[self.training_data_idx]
            # 입력값 한세트를 가져옴
            self.sample = self.training_data.iloc[
                self.training_data_idx].tolist()
            # 입력값 세트에 상태들을 더함 - 이것이 최종 입력 계층 값이 됨.
            self.sample.extend(self.agent.get_states())
            # 입력 계층 값을 반환한다.
            return self.sample
        return None

    # 모델을 로드해 거래를 한다.
    def trade(self, model_path=None, balance=2000000):
        if model_path is None:
            return
        # 모델을 로드한다.
        self.policy_network.load_model(model_path=model_path)
        self.fit(balance=balance, num_epoches=1, learning=False)
コード例 #2
0
class ReinforcementLearner:
    __metaclass__ = abc.ABCMeta
    lock = threading.Lock()

    # rl_method: 강화학습 기법을 의미, 이 값은 하위 클래스에 따라 달라진다. (DQNLener는 dq, A2CLener는 ac 등)
    # stock_code: 학습을 진행하는 주식 종목 코드
    # chart_data: 주식 일봉 차트(환경에 해당)
    # training_data: 학습을 위해서 전처리된 데이터
    # min_trading_unit: 투자 최소 단위
    # max_trading_unit: 투자 최대 단위
    # delayed_reward_threshold: 지연 보상 임계값, 수익 or 손실률이 임계값보다 크면 지연 보상이 발생
    # mini_batch_size: ??
    # net: 신경망 종류, 이 값에 따라서, 가치 신경망, 정챙신경망으로 사용할 신경망 클래스가 달라짐
    # n_steps: LSTM, CNN 신경망에서 사용하는 샘플 묶음 크기
    # lr: (learn rate?), 학습 속도, 너무 크면 학습이 진행 안되고, 너무 작으면 학습이 오래 걸림
    # value_network, policy_network: 값을 들어오는 경우, 해당 모델을 가치 신경망, 정책신경망으로 사용
    # output_path: 학습 과정에서 발생하는 로그, 가시화 결과 및 학습 종료 후 저장되는 신겯망 모델 파일의 저장 위치 결정
    def __init__(self,
                 rl_method='rl',
                 stock_code=None,
                 chart_data=None,
                 training_data=None,
                 min_trading_unit=1,
                 max_trading_unit=2,
                 delayed_reward_threshold=.05,
                 net='dnn',
                 num_steps=1,
                 lr=0.001,
                 value_network=None,
                 policy_network=None,
                 output_path='',
                 reuse_models=True):
        # 인자 확인
        assert min_trading_unit > 0
        assert max_trading_unit > 0
        assert max_trading_unit >= min_trading_unit
        assert num_steps > 0
        assert lr > 0
        # 강화학습 기법 설정
        self.rl_method = rl_method
        # 환경 설정
        self.stock_code = stock_code  # 강화학습 대상이 되는 주식 종목 코드
        self.chart_data = chart_data  # 주식 종목의 차트 데이터
        self.environment = Environment(chart_data)  # 강화학습 환경 객체
        # 에이전트 설정
        self.agent = Agent(self.environment,
                           min_trading_unit=min_trading_unit,
                           max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold)
        # 학습 데이터
        self.training_data = training_data
        self.sample = None
        self.training_data_idx = -1
        # 벡터 크기 = 학습 데이터 벡터 크기 + 에이전트 상태 크기
        self.num_features = self.agent.STATE_DIM
        if self.training_data is not None:
            self.num_features += self.training_data.shape[1]
        # 신경망 클래스 객체는, 본 클래스의 하위 클래스에서 생성
        # 신경망 설정
        self.net = net
        self.num_steps = num_steps
        self.lr = lr
        self.value_network = value_network  # 가치 신경망
        self.policy_network = policy_network  # 정책 신경망
        self.reuse_models = reuse_models
        # 가시화 모듈
        self.visualizer = Visualizer()
        # 메모리
        # 강화 학습 과정에서 발생하는 각종 데이터를 쌓아두기 위해서, memory라는 변수 정의
        self.memory_sample = []  # 학습 데이터 샘플
        self.memory_action = []  # 수행한 행동
        self.memory_reward = []  # 획득한 보상
        self.memory_value = []  # 행동의 예측 가치
        self.memory_policy = []  # 핻동의 예측?확률?
        self.memory_pv = []  # 포트폴리오 가치
        self.memory_num_stocks = []  # 보유 주식수
        self.memory_exp_idx = []  # 탐험 위치
        self.memory_learning_idx = []  # 학습 위치
        # 에포크 관련 정보
        self.loss = 0.  # 손실
        self.itr_cnt = 0  # 수익발생 횟수
        self.exploration_cnt = 0  # 탐험 횟수
        self.batch_size = 0  # 배치 크기?
        self.learning_cnt = 0  # 학습 횟수
        # 로그 등 출력 경로
        self.output_path = output_path

    # 가치 신경망 생성 함수
    # 팩토리 함수 느낌
    # 가치 신경망은, 손익율을 회귀분석하는 모델로 보면 된다. 따라서, activation은 선형, loss는 mse이다.???
    def init_value_network(self,
                           shared_network=None,
                           activation='linear',
                           loss='mse'):
        if self.net == 'dnn':
            self.value_network = DNN(input_dim=self.num_features,
                                     output_dim=self.agent.NUM_ACTIONS,
                                     lr=self.lr,
                                     shared_network=shared_network,
                                     activation=activation,
                                     loss=loss)
        elif self.net == 'lstm':
            self.value_network = LSTMNetwork(input_dim=self.num_features,
                                             output_dim=self.agent.NUM_ACTIONS,
                                             lr=self.lr,
                                             num_steps=self.num_steps,
                                             shared_network=shared_network,
                                             activation=activation,
                                             loss=loss)
        elif self.net == 'cnn':
            self.value_network = CNN(input_dim=self.num_features,
                                     output_dim=self.agent.NUM_ACTIONS,
                                     lr=self.lr,
                                     num_steps=self.num_steps,
                                     shared_network=shared_network,
                                     activation=activation,
                                     loss=loss)
        if self.reuse_models and \
            os.path.exists(self.value_network_path): # reuse_models이 True이고, value_network_path 값이 있으면 신경망 모델 파일을 불러온다...
            self.value_network.load_model(model_path=self.value_network_path)

    # 정책 신경망 생성 함수
    # activation이 sigmoid로 다르다.
    # 정책신경망은 PV을 높이기 위해 취하기 좋은 행동에 대한 '분류' 모델
    # 활성 함수로 sigmoid을 써서 0 ~ 1 시아의 값으로 확률로 사용하기 위함
    def init_policy_network(self,
                            shared_network=None,
                            activation='sigmoid',
                            loss='binary_crossentropy'):
        if self.net == 'dnn':
            self.policy_network = DNN(input_dim=self.num_features,
                                      output_dim=self.agent.NUM_ACTIONS,
                                      lr=self.lr,
                                      shared_network=shared_network,
                                      activation=activation,
                                      loss=loss)
        elif self.net == 'lstm':
            self.policy_network = LSTMNetwork(
                input_dim=self.num_features,
                output_dim=self.agent.NUM_ACTIONS,
                lr=self.lr,
                num_steps=self.num_steps,
                shared_network=shared_network,
                activation=activation,
                loss=loss)
        elif self.net == 'cnn':
            self.policy_network = CNN(input_dim=self.num_features,
                                      output_dim=self.agent.NUM_ACTIONS,
                                      lr=self.lr,
                                      num_steps=self.num_steps,
                                      shared_network=shared_network,
                                      activation=activation,
                                      loss=loss)
        if self.reuse_models and \
            os.path.exists(self.policy_network_path):
            self.policy_network.load_model(model_path=self.policy_network_path)

    # 초기화 함수
    # 에포크 초기화 함수
    # 에포크마다 데이터가 새로 쌓이는 변수들을 초기화 한다.
    def reset(self):
        self.sample = None  # 읽어온 학습 데이터가 샘플에 할당됨(초기화에선 None)
        self.training_data_idx = -1  # 학습 데이터를 처음부터 다시 읽기위해서 -1로 설정
        # 환경 초기화
        self.environment.reset()  # 환경클래스의 reset호출
        # 에이전트 초기화
        self.agent.reset()  # 에이전트가 제공하는 reset호출
        # 가시화 초기화
        self.visualizer.clear([0, len(self.chart_data)])  # 가시화 클래스의 clear호출
        # 메모리 초기화
        self.memory_sample = []
        self.memory_action = []
        self.memory_reward = []
        self.memory_value = []
        self.memory_policy = []
        self.memory_pv = []
        self.memory_num_stocks = []
        self.memory_exp_idx = []
        self.memory_learning_idx = []
        # 에포크 관련 정보 초기화
        self.loss = 0.  # 신경망의 결과가 학습 데이터와 얼마나 차이가 있는지를 저장하는 변수 loss가 줄어야 좋은거임!
        self.itr_cnt = 0  # 수행한 에포크 수를 저장
        self.exploration_cnt = 0  # 탐험 수 저장, epsilon이 0.1dlrh 100번 투자 결정이 있다고 한다면 약 10번의 무작위 투자
        self.batch_size = 0  # 학습할 미니 배치 크기
        self.learning_cnt = 0  # 한 에포크 동안 수행한 미니 배치 학습 횟수

    # 환경 객체에서 샘플을 획득하는 함수
    # 학습 데이터플 구성하는 샘플 하나를 생성하는 함수
    def build_sample(self):
        self.environment.observe()  # 차트 데이터의 현재 인덱스에서, 다음 인덱스 데이터를 읽게한다.
        if len(self.training_data
               ) > self.training_data_idx + 1:  # 학습 데이터 존재 확인
            self.training_data_idx += 1
            self.sample = self.training_data.iloc[
                self.training_data_idx].tolist()  # 샘플 가져옴, 샘플은 26개의 값임
            self.sample.extend(
                self.agent.get_states())  # 에이전트에서 2개 값을 추가! (28개값!)
            return self.sample
        return None

    # 배치 학습 데이터 생성 함수, 추상 메소드로 하위 클래스가 반드시 구현해야 한다.!
    @abc.abstractmethod
    def get_batch(self, batch_size, delayed_reward, discount_factor):
        pass

    # 가치 신경망 및 정책 신경망 학습 함수
    # get_batch을 호출해서 배치 학습 데이터를 생성
    # 가치 신경망 및 정책 신경망의 train_on_batch을 호출하여, 학습 시킴
    # 가치 신경망: DQN, AC, A2C
    # 정책 신경망: PolicyGradient, AC, A2C
    def update_networks(self, batch_size, delayed_reward, discount_factor):
        # 배치 학습 데이터 생성
        x, y_value, y_policy = self.get_batch(batch_size, delayed_reward,
                                              discount_factor)
        if len(x) > 0:
            loss = 0
            if y_value is not None:
                # 가치 신경망 갱신
                loss += self.value_network.train_on_batch(x, y_value)
            if y_policy is not None:
                # 정책 신경망 갱신
                loss += self.policy_network.train_on_batch(x, y_policy)
            return loss  # 학습 후 손실 반환
        return None

    # 가치 신경망 및 정책 신경망 학습 요청 함수
    # 배치 학습 데이터의 크기를 정하고, update_networks 호출(위함수)
    # _loss에 로 총 loss을 생성
    def fit(self, delayed_reward, discount_factor, full=False):
        batch_size = len(self.memory_reward) if full \
            else self.batch_size
        # 배치 학습 데이터 생성 및 신경망 갱신
        if batch_size > 0:
            _loss = self.update_networks(batch_size, delayed_reward,
                                         discount_factor)
            if _loss is not None:
                self.loss += abs(_loss)
                self.learning_cnt += 1  # 학습 횟수 저장, loss / learning_cnt하면 에포크의 학습 손실을 알 수 있음
                self.memory_learning_idx.append(
                    self.training_data_idx)  # 학습 위치 저장
            self.batch_size = 0

    # 에포크 정보 가시화 함수
    def visualize(self, epoch_str, num_epoches, epsilon):
        self.memory_action = [Agent.ACTION_HOLD] \
            * (self.num_steps - 1) + self.memory_action
        self.memory_num_stocks = [0] * (self.num_steps - 1) \
            + self.memory_num_stocks
        if self.value_network is not None:
            self.memory_value = [np.array([np.nan] \
                * len(Agent.ACTIONS))] * (self.num_steps - 1) \
                    + self.memory_value
        if self.policy_network is not None:
            self.memory_policy = [np.array([np.nan] \
                * len(Agent.ACTIONS))] * (self.num_steps - 1) \
                    + self.memory_policy
        self.memory_pv = [self.agent.initial_balance] \
            * (self.num_steps - 1) + self.memory_pv
        self.visualizer.plot(
            epoch_str=epoch_str,
            num_epoches=num_epoches,
            epsilon=epsilon,
            action_list=Agent.ACTIONS,
            actions=self.memory_action,
            num_stocks=self.memory_num_stocks,
            outvals_value=self.memory_value,
            outvals_policy=self.memory_policy,
            exps=self.memory_exp_idx,
            learning_idxes=self.memory_learning_idx,
            initial_balance=self.agent.initial_balance,
            pvs=self.memory_pv,
        )
        self.visualizer.save(
            os.path.join(self.epoch_summary_dir,
                         'epoch_summary_{}.png'.format(epoch_str)))

    # 강화학습 수행 함수
    # 핵심 함수!
    def run(
        self,
        num_epoches=100,  # 총 수행할 반복 학습 횟수, 너무 크면 학습에 걸리는 시간이 길어짐
        balance=10000000,  # 초기 투자금
        discount_factor=0.9,  # 상태-행동 가치를 구할때 적용할 할인율, 과거로 갈수록 현재 보상을 약하게 적용한다.
        start_epsilon=0.5,  # 초기 탐험 비율
        learning=True  # 학습을 마치면 학습된 가치 신경망모델, 정책 신경망 모델이 생성된다. 이런 신경망 모델으 만들꺼면 True, 이미 학습된 모델로, 투자 시뮬레이션일때는 False
    ):
        info = "[{code}] RL:{rl} Net:{net} LR:{lr} " \
            "DF:{discount_factor} TU:[{min_trading_unit}," \
            "{max_trading_unit}] DRT:{delayed_reward_threshold}".format(
            code=self.stock_code, rl=self.rl_method, net=self.net,
            lr=self.lr, discount_factor=discount_factor,
            min_trading_unit=self.agent.min_trading_unit,
            max_trading_unit=self.agent.max_trading_unit,
            delayed_reward_threshold=self.agent.delayed_reward_threshold
        )
        with self.lock:
            logging.info(info)  # 강화 학습의 설정값을 로깅 한다.

        # 시작 시간
        time_start = time.time()

        # 가시화 준비
        # 차트 데이터는 변하지 않으므로 미리 가시화
        self.visualizer.prepare(self.environment.chart_data, info)

        # 가시화 결과 저장할 폴더 준비
        # epoch_summary_ 라는 폴더에 저장
        self.epoch_summary_dir = os.path.join(
            self.output_path, 'epoch_summary_{}'.format(self.stock_code))
        if not os.path.isdir(self.epoch_summary_dir):
            os.makedirs(self.epoch_summary_dir)
        else:
            for f in os.listdir(self.epoch_summary_dir):
                os.remove(os.path.join(self.epoch_summary_dir, f))

        # 에이전트 초기 자본금 설정
        self.agent.set_balance(balance)

        # 학습에 대한 정보 초기화
        max_portfolio_value = 0
        epoch_win_cnt = 0

        # 학습 반복
        for epoch in range(num_epoches):
            time_start_epoch = time.time()

            # step 샘플을 만들기 위한 큐
            # deque사용 - 참고: https://opensourcedev.tistory.com/3
            q_sample = collections.deque(maxlen=self.num_steps)

            # 환경, 에이전트, 신경망, 가시화, 메모리 초기화
            self.reset()

            # 학습을 진행할 수록 탐험 비율 감소
            if learning:
                epsilon = start_epsilon \
                    * (1. - float(epoch) / (num_epoches - 1))
                self.agent.reset_exploration()
            else:
                epsilon = start_epsilon
                self.agent.reset_exploration(alpha=0)

            while True:
                # 샘플 생성
                next_sample = self.build_sample()
                if next_sample is None:
                    break  # 샘플 만큼 while문 반복

                # num_steps만큼 샘플 저장
                q_sample.append(next_sample)
                if len(q_sample) < self.num_steps:
                    continue

                # 가치, 정책 신경망 예측
                # 각 신경망의 predict함수 호출
                pred_value = None
                pred_policy = None
                if self.value_network is not None:
                    pred_value = self.value_network.predict(list(q_sample))
                if self.policy_network is not None:
                    pred_policy = self.policy_network.predict(list(q_sample))

                # 신경망 또는 탐험에 의한 행동 결정
                # 행동, 결정에 대한 확신도, 무작위 탐험 유무
                action, confidence, exploration = \
                    self.agent.decide_action(
                        pred_value, pred_policy, epsilon)

                # 결정한 행동을 수행하고 즉시 보상과 지연 보상 획득
                immediate_reward, delayed_reward = \
                    self.agent.act(action, confidence)

                # 행동 및 행동에 대한 결과를 기억
                self.memory_sample.append(list(q_sample))
                self.memory_action.append(action)
                self.memory_reward.append(immediate_reward)
                if self.value_network is not None:
                    self.memory_value.append(pred_value)
                if self.policy_network is not None:
                    self.memory_policy.append(pred_policy)
                self.memory_pv.append(self.agent.portfolio_value)
                self.memory_num_stocks.append(self.agent.num_stocks)
                if exploration:
                    self.memory_exp_idx.append(self.training_data_idx)

                # 반복에 대한 정보 갱신
                self.batch_size += 1
                self.itr_cnt += 1
                self.exploration_cnt += 1 if exploration else 0  # 3항연산???

                # 지연 보상 발생된 경우 미니 배치 학습
                # 지연보상은 지연보상 임계치가 넘는 손익률이 발생하면 주어진다.
                if learning and (delayed_reward != 0):
                    self.fit(delayed_reward, discount_factor)

            # 에포크 종료 후 학습 (while문 이후 미니 배치 학습)
            if learning:
                self.fit(self.agent.profitloss, discount_factor, full=True)

            # 에포크 관련 정보 로그 기록
            num_epoches_digit = len(str(num_epoches))
            epoch_str = str(epoch + 1).rjust(num_epoches_digit,
                                             '0')  # 문자열을 자리수에 맞게 정렬(우측) 함수
            time_end_epoch = time.time()
            elapsed_time_epoch = time_end_epoch - time_start_epoch
            if self.learning_cnt > 0:
                self.loss /= self.learning_cnt
            logging.info("[{}][Epoch {}/{}] Epsilon:{:.4f} "
                         "#Expl.:{}/{} #Buy:{} #Sell:{} #Hold:{} "
                         "#Stocks:{} PV:{:,.0f} "
                         "LC:{} Loss:{:.6f} ET:{:.4f}".format(
                             self.stock_code, epoch_str, num_epoches, epsilon,
                             self.exploration_cnt, self.itr_cnt,
                             self.agent.num_buy, self.agent.num_sell,
                             self.agent.num_hold, self.agent.num_stocks,
                             self.agent.portfolio_value, self.learning_cnt,
                             self.loss, elapsed_time_epoch))

            # 에포크 관련 정보 가시화
            self.visualize(epoch_str, num_epoches, epsilon)

            # 학습 관련 정보 갱신
            max_portfolio_value = max(max_portfolio_value,
                                      self.agent.portfolio_value)
            if self.agent.portfolio_value > self.agent.initial_balance:
                epoch_win_cnt += 1

        # 종료 시간
        time_end = time.time()
        elapsed_time = time_end - time_start

        # 학습 관련 정보 로그 기록
        with self.lock:
            logging.info("[{code}] Elapsed Time:{elapsed_time:.4f} "
                         "Max PV:{max_pv:,.0f} #Win:{cnt_win}".format(
                             code=self.stock_code,
                             elapsed_time=elapsed_time,
                             max_pv=max_portfolio_value,
                             cnt_win=epoch_win_cnt))

    # 가치 신경망 및 정책 신경망 저장 함수
    def save_models(self):
        if self.value_network is not None and \
                self.value_network_path is not None:
            self.value_network.save_model(self.value_network_path)
        if self.policy_network is not None and \
                self.policy_network_path is not None:
            self.policy_network.save_model(self.policy_network_path)
コード例 #3
0
class ReinforcementLearner:
    __metaclass__ = abc.ABCMeta
    lock = threading.Lock()

    def __init__(self,
                 rl_method='rl',
                 stock_code=None,
                 chart_data=None,
                 training_data=None,
                 min_trading_unit=1,
                 max_trading_unit=2,
                 delayed_reward_threshold=.05,
                 net='dnn',
                 num_steps=1,
                 lr=0.001,
                 value_network=None,
                 policy_network=None,
                 output_path='',
                 reuse_models=True):
        # 인자 확인
        assert min_trading_unit > 0
        assert max_trading_unit > 0
        assert max_trading_unit >= min_trading_unit
        assert num_steps > 0
        assert lr > 0
        # 강화학습 기법 설정
        self.rl_method = rl_method
        # 환경 설정
        self.stock_code = stock_code
        self.chart_data = chart_data
        self.environment = Environment(chart_data)
        # 에이전트 설정
        self.agent = Agent(self.environment,
                           min_trading_unit=min_trading_unit,
                           max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold)
        # 학습 데이터
        self.training_data = training_data
        self.sample = None
        self.training_data_idx = -1
        # 벡터 크기 = 학습 데이터 벡터 크기 + 에이전트 상태 크기
        self.num_features = self.agent.STATE_DIM
        if self.training_data is not None:
            self.num_features += self.training_data.shape[1]
        # 신경망 설정
        self.net = net
        self.num_steps = num_steps
        self.lr = lr
        self.value_network = value_network
        self.policy_network = policy_network
        self.reuse_models = reuse_models
        self.critic = value_network
        self.actor = policy_network
        self.tau = 0.001
        # 가시화 모듈
        self.visualizer = Visualizer()
        # 메모리
        self.memory_sample = []
        self.memory_action = []
        self.memory_reward = []
        self.memory_value = []
        self.memory_policy = []
        self.memory_target_policy = []
        self.memory_target_value = []
        self.memory_target_action = []
        self.memory_pv = []
        self.memory_num_stocks = []
        self.memory_exp_idx = []
        self.memory_learning_idx = []
        # 에포크 관련 정보
        self.loss = 0.
        self.itr_cnt = 0
        self.exploration_cnt = 0
        self.batch_size = 0
        self.learning_cnt = 0
        # 로그 등 출력 경로
        self.output_path = output_path

    def init_policy_network(self,
                            shared_network=None,
                            activation='sigmoid',
                            loss='binary_crossentropy'):
        if self.rl_method == 'ddpg':
            print("actor")
            self.actor = ActorNetwork(input_dim=self.num_features,
                                      output_dim=self.agent.NUM_ACTIONS,
                                      num_steps=self.num_steps,
                                      activation=activation,
                                      loss=loss,
                                      lr=self.lr)
            print(self.actor)
        elif self.net == 'dnn':
            self.policy_network = DNN(input_dim=self.num_features,
                                      output_dim=self.agent.NUM_ACTIONS,
                                      lr=self.lr,
                                      shared_network=shared_network,
                                      activation=activation,
                                      loss=loss)
        elif self.net == 'lstm':
            self.policy_network = LSTMNetwork(
                input_dim=self.num_features,
                output_dim=self.agent.NUM_ACTIONS,
                lr=self.lr,
                num_steps=self.num_steps,
                shared_network=shared_network,
                activation=activation,
                loss=loss)
        elif self.net == 'cnn':
            self.policy_network = CNN(input_dim=self.num_features,
                                      output_dim=self.agent.NUM_ACTIONS,
                                      lr=self.lr,
                                      num_steps=self.num_steps,
                                      shared_network=shared_network,
                                      activation=activation,
                                      loss=loss)
        elif self.net == 'cnn':
            self.policy_network = CNN(input_dim=self.num_features,
                                      output_dim=self.agent.NUM_ACTIONS,
                                      lr=self.lr,
                                      num_steps=self.num_steps,
                                      shared_network=shared_network,
                                      activation=activation,
                                      loss=loss)
        if self.reuse_models and \
                os.path.exists(self.policy_network_path):
            self.policy_network.load_model(model_path=self.policy_network_path)

    def init_value_network(self,
                           shared_network=None,
                           activation='linear',
                           loss='mse'):
        if self.rl_method == 'ddpg':
            self.critic = CriticNetwork(input_dim=self.num_features,
                                        output_dim=self.agent.NUM_ACTIONS,
                                        num_steps=self.num_steps,
                                        activation=activation,
                                        loss=loss,
                                        lr=self.lr)
        elif self.net == 'dnn':
            self.value_network = DNN(input_dim=self.num_features,
                                     output_dim=self.agent.NUM_ACTIONS,
                                     lr=self.lr,
                                     shared_network=shared_network,
                                     activation=activation,
                                     loss=loss)
        elif self.net == 'lstm':
            self.value_network = LSTMNetwork(input_dim=self.num_features,
                                             output_dim=self.agent.NUM_ACTIONS,
                                             lr=self.lr,
                                             num_steps=self.num_steps,
                                             shared_network=shared_network,
                                             activation=activation,
                                             loss=loss)
        elif self.net == 'cnn':
            self.value_network = CNN(input_dim=self.num_features,
                                     output_dim=self.agent.NUM_ACTIONS,
                                     lr=self.lr,
                                     num_steps=self.num_steps,
                                     shared_network=shared_network,
                                     activation=activation,
                                     loss=loss)
        elif self.net == 'cnn':
            self.value_network = CNN(input_dim=self.num_features,
                                     output_dim=self.agent.NUM_ACTIONS,
                                     lr=self.lr,
                                     num_steps=self.num_steps,
                                     shared_network=shared_network,
                                     activation=activation,
                                     loss=loss)

        if self.reuse_models and \
                os.path.exists(self.value_network_path):
            self.value_network.load_model(model_path=self.value_network_path)

    def reset(self):
        self.sample = None
        self.training_data_idx = -1
        # 환경 초기화
        self.environment.reset()
        # 에이전트 초기화
        self.agent.reset()
        # 가시화 초기화
        self.visualizer.clear([0, len(self.chart_data)])
        # 메모리 초기화
        self.memory_sample = []
        self.memory_action = []
        self.memory_target_policy = []
        self.memory_target_value = []
        self.memory_target_action = []
        self.memory_reward = []
        self.memory_value = []
        self.memory_policy = []
        self.memory_pv = []
        self.memory_num_stocks = []
        self.memory_exp_idx = []
        self.memory_learning_idx = []
        # 에포크 관련 정보 초기화
        self.loss = 0.
        self.itr_cnt = 0
        self.exploration_cnt = 0
        self.batch_size = 0
        self.learning_cnt = 0

    def build_sample(self):
        self.environment.observe()
        if len(self.training_data) > self.training_data_idx + 1:
            self.training_data_idx += 1
            self.sample = self.training_data.iloc[
                self.training_data_idx].tolist()
            self.sample.extend(self.agent.get_states())
            return self.sample
        return None

    @abc.abstractmethod
    def get_batch(self, batch_size, delayed_reward, discount_factor):
        pass

    @abc.abstractmethod
    def train(self, batch_size, delayed_reward, discount_factor):
        pass

    def update_networks(self, batch_size, delayed_reward, discount_factor):
        # 배치 학습 데이터 생성
        x, y_value, y_policy = self.get_batch(batch_size, delayed_reward,
                                              discount_factor)
        if len(x) > 0:
            loss = 0
            if y_value is not None:
                # 가치 신경망 갱신
                loss += self.critic.train_on_batch(x, y_value)
                self.critic.transfer_weights()
            if y_policy is not None:
                # 정책 신경망 갱신
                loss += self.actor.train_on_batch(x, y_policy)
                self.actor.transfer_weights()
            return loss
        return None

    def fit(self, delayed_reward, discount_factor, full=False):
        batch_size = len(self.memory_reward) if full \
            else self.batch_size
        # 배치 학습 데이터 생성 및 신경망 갱신
        if batch_size > 0:
            _loss = self.update_networks(batch_size, delayed_reward,
                                         discount_factor)
            if _loss is not None:
                self.loss += abs(_loss)
                self.learning_cnt += 1
                self.memory_learning_idx.append(self.training_data_idx)
            self.batch_size = 0

    def visualize(self, epoch_str, num_epoches, epsilon):
        self.memory_action = [Agent.ACTION_HOLD] \
                             * (self.num_steps - 1) + self.memory_action
        self.memory_num_stocks = [0] * (self.num_steps - 1) \
                                 + self.memory_num_stocks
        if self.value_network is not None:
            self.memory_value = [np.array([np.nan] \
                                          * len(Agent.ACTIONS))] * (self.num_steps - 1) \
                                + self.memory_value
        if self.policy_network is not None:
            self.memory_policy = [np.array([np.nan] \
                                           * len(Agent.ACTIONS))] * (self.num_steps - 1) \
                                 + self.memory_policy
        self.memory_pv = [self.agent.initial_balance] \
                         * (self.num_steps - 1) + self.memory_pv
        self.visualizer.plot(
            epoch_str=epoch_str,
            num_epoches=num_epoches,
            epsilon=epsilon,
            action_list=Agent.ACTIONS,
            actions=self.memory_action,
            num_stocks=self.memory_num_stocks,
            outvals_value=self.memory_value,
            outvals_policy=self.memory_policy,
            exps=self.memory_exp_idx,
            learning_idxes=self.memory_learning_idx,
            initial_balance=self.agent.initial_balance,
            pvs=self.memory_pv,
        )
        self.visualizer.save(
            os.path.join(self.epoch_summary_dir,
                         'epoch_summary_{}.png'.format(epoch_str)))

    def run(self,
            num_epoches=100,
            balance=10000000,
            discount_factor=0.9,
            start_epsilon=0.5,
            learning=True):
        info = "[{code}] RL:{rl} Net:{net} LR:{lr} " \
               "DF:{discount_factor} TU:[{min_trading_unit}," \
               "{max_trading_unit}] DRT:{delayed_reward_threshold}".format(
            code=self.stock_code, rl=self.rl_method, net=self.net,
            lr=self.lr, discount_factor=discount_factor,
            min_trading_unit=self.agent.min_trading_unit,
            max_trading_unit=self.agent.max_trading_unit,
            delayed_reward_threshold=self.agent.delayed_reward_threshold
        )
        with self.lock:
            logging.info(info)

        # 시작 시간
        time_start = time.time()

        # 가시화 준비
        # 차트 데이터는 변하지 않으므로 미리 가시화
        self.visualizer.prepare(self.environment.chart_data, info)

        # 가시화 결과 저장할 폴더 준비
        self.epoch_summary_dir = os.path.join(
            self.output_path, 'epoch_summary_{}'.format(self.stock_code))
        if not os.path.isdir(self.epoch_summary_dir):
            os.makedirs(self.epoch_summary_dir)
        else:
            for f in os.listdir(self.epoch_summary_dir):
                os.remove(os.path.join(self.epoch_summary_dir, f))

        # 에이전트 초기 자본금 설정
        self.agent.set_balance(balance)

        # 학습에 대한 정보
        max_portfolio_value = 0
        epoch_win_cnt = 0

        # 학습 반복
        for epoch in range(num_epoches):
            time_start_epoch = time.time()

            # step 샘플을 만들기 위한 큐
            q_sample = collections.deque(maxlen=self.num_steps)

            # 환경, 에이전트, 신경망, 가시화, 메모리 초기화
            self.reset()
            # 학습을 진행할 수록 탐험 비율 감소
            if learning:
                epsilon = start_epsilon \
                          * (1. - float(epoch) / (num_epoches - 1))
                self.agent.reset_exploration()
            else:
                epsilon = start_epsilon
            while True:
                # 샘플 생성
                next_sample = self.build_sample()
                if next_sample is None:
                    break

                # num_steps만큼 샘플 저장
                q_sample.append(next_sample)
                if len(q_sample) < self.num_steps:
                    continue

                # 가치, 정책 신경망 예측
                pred_value = None
                pred_policy = None
                pred_target_policy = None
                pred_target_value = None
                if self.critic is not None:
                    pred_value = self.critic.predict(list(q_sample))
                    pred_target_value = self.critic.target_predict(
                        list(q_sample))
                if self.actor is not None:
                    pred_policy = self.actor.predict(list(q_sample))
                    pred_target_policy = self.actor.target_predict(
                        list(q_sample))

                # 신경망 또는 탐험에 의한 행동 결정
                action, confidence, exploration = \
                    self.agent.decide_action(pred_value, pred_policy, epsilon)

                # target 값을 이용한 행동 결정
                target_action, target_confidence, target_exploration = \
                    self.agent.decide_action(pred_target_policy, pred_target_value, epsilon)

                #결정한 행동을 수행하고 즉시 보상과 지연 보상 획득
                immediate_reward, delayed_reward = \
                    self.agent.act(action, confidence)

                # 행동 및 행동에 대한 결과를 기억
                self.memory_sample.append(list(q_sample))
                self.memory_action.append(action)
                self.memory_reward.append(immediate_reward)
                self.memory_target_action.append(target_action)
                self.memory_target_policy.append(pred_target_policy)
                self.memory_target_value.append(pred_target_value)
                if self.value_network is not None:
                    self.memory_value.append(pred_value)
                if self.policy_network is not None:
                    self.memory_policy.append(pred_policy)
                self.memory_pv.append(self.agent.portfolio_value)
                self.memory_num_stocks.append(self.agent.num_stocks)
                if exploration:
                    self.memory_exp_idx.append(self.training_data_idx)

                # 반복에 대한 정보 갱신
                self.batch_size += 1
                self.itr_cnt += 1
                self.exploration_cnt += 1 if exploration else 0

                # 지연 보상 발생된 경우 미니 배치 학습
                if learning and (delayed_reward != 0):
                    self.fit(delayed_reward, discount_factor)
            # 에포크 종료 후 학습
            if learning:
                self.fit(self.agent.profitloss, discount_factor, full=True)
            # 에포크 관련 정보 로그 기록
            num_epoches_digit = len(str(num_epoches))
            epoch_str = str(epoch + 1).rjust(num_epoches_digit, '0')
            time_end_epoch = time.time()
            elapsed_time_epoch = time_end_epoch - time_start_epoch
            if self.learning_cnt > 0:
                logging.info("[{}][Epoch {}/{}] Epsilon:{:.4f} "
                             "#Expl.:{}/{} #Buy:{} #Sell:{} #Hold:{} "
                             "#Stocks:{} PV:{:,.0f} "
                             "LC:{} Loss:{:.6f} ET:{:.4f}".format(
                                 self.stock_code, epoch_str, num_epoches,
                                 epsilon, self.exploration_cnt, self.itr_cnt,
                                 self.agent.num_buy, self.agent.num_sell,
                                 self.agent.num_hold, self.agent.num_stocks,
                                 self.agent.portfolio_value, self.learning_cnt,
                                 self.loss, elapsed_time_epoch))

            # 에포크 관련 정보 가시화
            self.visualize(epoch_str, num_epoches, epsilon)

            # 학습 관련 정보 갱신
            max_portfolio_value = max(max_portfolio_value,
                                      self.agent.portfolio_value)
            if self.agent.portfolio_value > self.agent.initial_balance:
                epoch_win_cnt += 1

        # 종료 시간
        time_end = time.time()
        elapsed_time = time_end - time_start

        # 학습 관련 정보 로그 기록
        with self.lock:
            logging.info("[{code}] Elapsed Time:{elapsed_time:.4f} "
                         "Max PV:{max_pv:,.0f} #Win:{cnt_win}".format(
                             code=self.stock_code,
                             elapsed_time=elapsed_time,
                             max_pv=max_portfolio_value,
                             cnt_win=epoch_win_cnt))

    def save_models(self):
        if self.value_network is not None and \
                self.value_network_path is not None:
            self.value_network.save_model(self.value_network_path)
        if self.policy_network is not None and \
                self.policy_network_path is not None:
            self.policy_network.save_model(self.policy_network_path)
コード例 #4
0
class PolicyLearner:
    def __init__(self,
                 stock_code,
                 chart_data,
                 training_data=None,
                 min_trading_unit=1,
                 max_trading_unit=2,
                 delayed_reward_threshold=.05,
                 lr=0.01):
        self.stock_code = stock_code  # 종목코드
        self.chart_data = chart_data
        self.environment = Environment(chart_data)  # 환경 객체
        # Environment 클래스는 차트 데이터를 순서대로 읽으면서 주가, 거래량 등의 환경을 제공한다.
        # 에이전트 객체
        self.agent = Agent(self.environment,
                           min_trading_unit=min_trading_unit,
                           max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold)
        self.training_data = training_data  # 학습 데이터. 학습에 사용할 특징(feature)들을 포함한다.
        self.sample = None
        self.training_data_idx = -1
        # 정책 신경망; 입력 크기(17개) = 학습 데이터의 크기(15개) + 에이전트 상태 크기(2개)
        self.num_features = self.training_data.shape[1] + self.agent.STATE_DIM
        self.policy_network = PolicyNetwork(input_dim=self.num_features,
                                            output_dim=self.agent.NUM_ACTIONS,
                                            lr=lr)
        self.visualizer = Visualizer()  # 가시화 모듈

    def reset(self):  # 에포크 초기화 함수 부분
        self.sample = None
        self.training_data_idx = -1  # 학습 데이터를 읽어가면서 이 값은 1씩 증가

    def fit(  #학습 함수 선언 부분. 핵심 함수이다.
            # num_epoches : 수행할 반복 학습의 전체 횟수. (너무 크게 잡으면 학습 소요 시간이 너무 길어짐)
            # max_memory : 배치 학습 데이터를 만들기 위해 과거 데이터를 저장할 배열.
            # balance : 에이전트의 초기 투자 자본금을 정해주기 위한 인자
            # discount_factor : 지연 보상이 발생했을 때 그 이전 지연 보상이 발생한 시점과 현재 지연 보상이 발생한
            # 시점 사이에서 수행한 행동들 전체에 현재의 지연 보상을 적용한다.
            # 과거로 갈수록 현재 지연 보상을 적용할 판단 근거가 흐려지기 때문에 먼 과걱의 행동일수록 할인 요인을
            # 적용하여 지연 보상을 약하게 적용한다.
            # start_epsilon : 초기 탐험 비율. 학습이 전혀 되어 있지 않은 초기에는 탐험 비율을 크게 해서
            # 더 많은 탐험, 즉 무작위 투자를 수행하도록 해야 한다. 탐험을 통해 특정 상황에서 좋은 행동과
            # 그렇지 않은 행동을 결정하기 위한 경험을 쌓는다.
            # learning : 학습 유무를 정하는 boolean 값. 학습을 마치면 학습된 정책 신경망 모델이 만들어지는데,
            # 이렇게 학습을 해서 정책 신경망 모델을 만들고자 한다면 learning을 True로,
            # 학습된 모델을 가지고 투자 시뮬레이션만 하려 한다면 learning을 False로 준다.
            self,
            num_epoches=1000,
            max_memory=60,
            balance=10000000,
            discount_factor=0,
            start_epsilon=.5,
            learning=True):
        logger.info(
            "LR: {lr}, DF: {discount_factor}, "
            "TU: [{min_trading_unit}, {max_trading_unit}], "
            "DRT: {delayed_reward_threshold}".format(
                lr=self.policy_network.lr,
                discount_factor=discount_factor,
                min_trading_unit=self.agent.min_trading_unit,
                max_trading_unit=self.agent.max_trading_unit,
                delayed_reward_threshold=self.agent.delayed_reward_threshold))

        # 가시화 준비
        # 차트 데이터는 변하지 않으므로 미리 가시화
        self.visualizer.prepare(self.environment.chart_data)

        # 가시화 결과 저장할 폴더 준비
        epoch_summary_dir = os.path.join(  # 폴더의 경로를 변수로 저장
            settings.BASE_DIR, 'epoch_summary/%s/epoch_summary_%s' %
            (self.stock_code, settings.timestr))
        if not os.path.isdir(
                epoch_summary_dir):  # 해당 경로가 없으면 이 경로를 구성하는 폴더들을 생성
            os.makedirs(epoch_summary_dir)

        # 에이전트 초기 자본금 설정
        self.agent.set_balance(balance)

        # 학습에 대한 정보 초기화
        max_portfolio_value = 0
        epoch_win_cnt = 0

        # 학습 반복
        for epoch in range(num_epoches):
            # 에포크 관련 정보 초기화
            loss = 0.  # 정책 신경망의 결과가 학습 데이터와 얼마나 차이가 있는지를 저장하는 변수. 학습 중 줄어드는게 좋음.
            itr_cnt = 0  # 수행한 에포크 수를 저장
            win_cnt = 0  # 수행한 에포크 중에서 수익이 발생한 에포크 수를 저장. 포트폴리오 가치가 초기 자본금보다 높아진 에포크 수.
            exploration_cnt = 0  # 무작위 투자를 수행한 횟수. epsilon이 0.1이고 100번의 투자 결정이 있으면 약 10번의 무작위 투자를 함
            batch_size = 0
            pos_learning_cnt = 0  # 수익이 발생하여 긍정적 지연 보상을 준 수
            neg_learning_cnt = 0  # 손실이 발생하여 부정적 지연 보상을 준 수

            # 메모리 초기화
            # 메모리 리스트에 저장하는 데이터는 샘플, 행동, 즉시보상, 정책 신경망의 출력, 포트폴리오 가치,
            # 보유 주식 수, 탐험 위치, 학습 위치이다.
            memory_sample = []
            memory_action = []
            memory_reward = []
            memory_prob = []
            memory_pv = []
            memory_num_stocks = []
            memory_exp_idx = []
            memory_learning_idx = []

            # 환경, 에이전트, 정책 신경망 초기화
            self.environment.reset()
            self.agent.reset()
            self.policy_network.reset()
            self.reset()

            # 가시화 초기화
            self.visualizer.clear([0, len(self.chart_data)
                                   ])  # 2, 3, 4번째 차트를 초기화함. x축 데이터 범위를 파라미터로

            # 학습을 진행할 수록 탐험 비율 감소
            # 무작위 투자 비율인 epsilon 값을 정함
            # fit() 함수의 인자로 넘어오는 최초 무작위 투자 비율인 start_epsilon 값에 현재 epoch 수에 학습 진행률을 곱해서 정함
            # ex) start_epsilon이 0.3이면 첫 번째 에포크에서는 30%의 확률로 무작위 투자를 진행함.
            # 수행할 에포크 수가 100이라고 했을 때, 50번째 에포크에서는 0.3 * (1 - 49/99) = 0.51
            if learning:
                epsilon = start_epsilon * (1. - float(epoch) /
                                           (num_epoches - 1))
            else:
                epsilon = 0

            while True:
                # 샘플 생성
                next_sample = self._build_sample()
                if next_sample is None:  # 마지막까지 데이터를 다 읽은 것이므로 반복문 종료
                    break

                # 정책 신경망 또는 탐험에 의한 행동 결정
                # 매수와 매도 중 하나를 결정. 이 행동 결정은 무작위 투자 비율인 epsilon 값의 확률로 무작위로 하거나
                # 그렇지 않은 경우 정책 신경망의 출력을 통해 결정된다. 정책 신경망의 출력은 매수를 했을 때와 매도를 했을 때의
                # 포트폴리오 가치를 높일 확률을 의미한다. 즉 매수에 대한 정책 신경망 출력이 매도에 대한 출력보다 높으면 매수, 반대는 매도
                # decide_action() 함수가 반환하는 값은 세 가지.
                # 결정한 행동인 action, 결정에 대한 확신도인 confidence, 무작위 투자 유무인 exploration.
                action, confidence, exploration = self.agent.decide_action(
                    self.policy_network, self.sample, epsilon)

                # 결정한 행동을 수행하고 (act 함수) 즉시 보상과 지연 보상 획득 (act가 반환함)
                immediate_reward, delayed_reward = self.agent.act(
                    action, confidence)

                # 행동 및 행동에 대한 결과를 기억 (메모리에 저장)
                memory_sample.append(next_sample)  # 각 데이터를 메모리에 추가
                memory_action.append(action)
                memory_reward.append(immediate_reward)
                memory_pv.append(self.agent.portfolio_value)
                memory_num_stocks.append(self.agent.num_stocks)
                memory = [
                    (  # 학습데이터의 샘플, 에이전트 행동, 즉시보상, 포트폴리오 가치, 보유 주식 수를 저장하는 배열
                        memory_sample[i], memory_action[i], memory_reward[i])
                    for i in list(range(len(memory_action)))[-max_memory:]
                ]
                if exploration:  # 무작위 투자로 행동을 결정한 경우에 현재의 인덱스를 memory_exp_idx에 저장
                    memory_exp_idx.append(itr_cnt)
                    memory_prob.append(
                        [np.nan] * Agent.NUM_ACTIONS
                    )  # memory_prob은 정책 신경망의 출력을 그대로 저장하는 배열
                    # 무작위 투자에서는 정책 신경망의 출력이 없기 때문에 NumPy의 Not A Number(nan) 값을 넣어줌
                else:  # 무작위 투자가 아닌 경우 정책 신경망의 출력을 그대로 저장
                    memory_prob.append(self.policy_network.prob)
                # 메모리 변수들의 목적은 (1) 학습에서 배치 학습 데이터로 사용 (2) 가시화기에서 차트를 그릴 때 사용

                # 반복에 대한 정보 갱신
                batch_size += 1  # 배치 크기
                itr_cnt += 1  # 반복 카운팅 횟수
                exploration_cnt += 1 if exploration else 0  # 무작위 투자 횟수 (탐험을 한 경우에만)
                win_cnt += 1 if delayed_reward > 0 else 0  # 수익이 발생한 횟수를 증가시킴 (지연 보상이 0보다 큰 경우에만)

                # 지연 보상이 발생한 경우 학습을 수행하는 부분
                # 학습 모드이고 지연 보상이 존재할 경우 정책 신경망 갱신
                # 학습 없이 메모리가 최대 크기만큼 다 찼을 경우 즉시 보상으로 지연 보상을 대체하여 학습을 진행
                if delayed_reward == 0 and batch_size >= max_memory:
                    delayed_reward = immediate_reward
                    self.agent.base_portfolio_value = self.agent.portfolio_value
                if learning and delayed_reward != 0:
                    # 배치 학습 데이터 크기
                    batch_size = min(
                        batch_size, max_memory
                    )  # 배치 데이터 크기는 memory 변수의 크기인 max_memory보다는 작아야 함
                    # 배치 학습 데이터 생성
                    x, y = self._get_batch(memory, batch_size, discount_factor,
                                           delayed_reward)
                    if len(x) > 0:  # 긍정 학습과 부정 학습 횟수 세기
                        if delayed_reward > 0:
                            pos_learning_cnt += 1
                        else:
                            neg_learning_cnt += 1
                        # 정책 신경망 갱신
                        loss += self.policy_network.train_on_batch(
                            x, y)  # 준비한 배치 데이터로 학습을 진행함
                        memory_learning_idx.append([itr_cnt, delayed_reward
                                                    ])  # 학습이 진행된 인덱스를 저장함
                    batch_size = 0  # 학습이 진행되었으니 배치 데이터 크기를 초기화함

            # 에포크 관련 정보 가시화
            num_epoches_digit = len(str(
                num_epoches))  # 총 에포크 수의 문자열 길이를 확인함. 총 에포크 수가 1000이면 길이는 4
            epoch_str = str(epoch + 1).rjust(
                num_epoches_digit, '0'
            )  # 현재 에포크 수를 4자리 문자열로 만들어 줌. 첫 에포크는 1 -> 0001로 문자열을 자리수에 맞게 정렬

            self.visualizer.plot(epoch_str=epoch_str,
                                 num_epoches=num_epoches,
                                 epsilon=epsilon,
                                 action_list=Agent.ACTIONS,
                                 actions=memory_action,
                                 num_stocks=memory_num_stocks,
                                 outvals=memory_prob,
                                 exps=memory_exp_idx,
                                 learning=memory_learning_idx,
                                 initial_balance=self.agent.initial_balance,
                                 pvs=memory_pv)
            self.visualizer.save(
                os.path.join(  #가시화한 에포크 수행 결과를 파일로 저장함
                    epoch_summary_dir,
                    'epoch_summary_%s_%s.png' % (settings.timestr, epoch_str)))

            # 에포크 관련 정보 로그 기록
            # 총 에포크 중에서 몇 번째 에포크를 수행했는지, 탐험률, 탐험 횟수, 매수 횟수, 매도 횟수, 관망 횟수,
            # 보유 주식 수, 포트폴리오 가치, 긍정적 학습 횟수, 부정적 학습 횟수, 학습 손실을 로그로 남긴다.
            if pos_learning_cnt + neg_learning_cnt > 0:
                loss /= pos_learning_cnt + neg_learning_cnt
            logger.info(
                "[Epoch %s/%s]\tEpsilon:%.4f\t#Expl.:%d/%d\t"
                "#Buy:%d\t#Sell:%d\t#Hold:%d\t"
                "#Stocks:%d\tPV:%s\t"
                "POS:%s\tNEG:%s\tLoss:%10.6f" %
                (epoch_str, num_epoches, epsilon, exploration_cnt, itr_cnt,
                 self.agent.num_buy, self.agent.num_sell, self.agent.num_hold,
                 self.agent.num_stocks,
                 locale.currency(self.agent.portfolio_value, grouping=True),
                 pos_learning_cnt, neg_learning_cnt, loss))

            # 학습 관련 정보 갱신
            # 하나의 에포크 수행이 완료되었기 때문에 전체 학습에 대한 통계 정보를 갱신한다.
            # 관리하고 있는 학습 통계 정보는 달성한 최대 포트 폴리오 가치 max_portfolio_value와 쉭이 발생한 에포크의 수 epoch_win_cnt이다.
            max_portfolio_value = max(max_portfolio_value,
                                      self.agent.portfolio_value)
            if self.agent.portfolio_value > self.agent.initial_balance:
                epoch_win_cnt += 1
        # 여기 까지가 학습 반복 for문의 코드 블록이다.

        # 학습 관련 정보 로그 기록
        logger.info("Max PV: %s, \t # Win: %d" % (locale.currency(
            max_portfolio_value, grouping=True), epoch_win_cnt))

    def _get_batch(self, memory, batch_size, discount_factor,
                   delayed_reward):  # 미니 배치 데이터 생성
        x = np.zeros(
            (batch_size, 1, self.num_features))  # x는 일련의 학습 데이터 및 에이전트 상태
        # x 배열의 형태는 배치 데이터 크기, 학습 데이터 특징 크기로 2차원으로 구성됨.
        y = np.full((batch_size, self.agent.NUM_ACTIONS), 0.5)  # y는 일련의 지연 보상
        # y 배열의 형태는 배치 데이터 크기, 정책 신경망이 결정하는 에이전트 행동의 수. 2차원으로. 0.5 일괄적으로 채워둠.

        for i, (sample, action,
                reward) in enumerate(reversed(memory[-batch_size:])):
            x[i] = np.array(sample).reshape(
                (-1, 1, self.num_features))  # 특징벡터 지정
            y[i, action] = (delayed_reward +
                            1) / 2  # 지연 보상으로 정답(레이블)을 설정하여 학습 데이터 구성
            # 지연 보상이 1인 경우 1로, -1인 경우 0으로 레이블을 지정
            if discount_factor > 0:
                y[i, action] *= discount_factor**i  # 할인 요인이 있을 경우
        return x, y

    # 학습 데이터를 구성하는 샘플 하나를 생성하는 함수
    def _build_sample(self):
        self.environment.observe()  # 차트 데이터의 현재 인덱스에서 다음 인덱스 데이터를 읽음
        if len(self.training_data
               ) > self.training_data_idx + 1:  # 학습 데이터의 다음 인덱스가 존재하는지 확인
            self.training_data_idx += 1
            self.sample = self.training_data.iloc[
                self.training_data_idx].tolist()  # 인덱스의 데이터를 받아와서 sample로 저장
            self.sample.extend(
                self.agent.get_states())  # sample에 에이전트 상태를 15개에서 +2개하여 17개로.
            return self.sample
        return None

    # 학습된 정책 신경망 모델로 주식투자 시뮬레이션을 진행
    def trade(self, model_path=None, balance=2000000):
        if model_path is None:
            return
        self.policy_network.load_model(
            model_path=model_path)  # 학습된 신경망 모델을 정책 신경망 객체의 load_model로 적용
        self.fit(balance=balance, num_epoches=1, learning=False)
コード例 #5
0
class PolicyLearner:
    def __init__(self,
                 coin_code,
                 coin_chart,
                 training_data=None,
                 min_trading_unit=1,
                 max_trading_unit=2,
                 delayed_reward_threshold=.05,
                 lr=0.01):

        self.coin_code = coin_code
        self.coin_chart = coin_chart

        self.environment = Environment(coin_chart)
        self.agent = Agent(self.environment,
                           min_trading_unit=min_trading_unit,
                           max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold)

        self.training_data = training_data
        self.sample = None
        self.training_data_idx = -1

        self.num_features = self.training_data.shape[
            1] + self.agent.STATE_DIM  # -1 # input_dim = 15 + 2 = 17
        self.policy_network = PolicyNetwork(input_dim=self.num_features,
                                            output_dim=self.agent.NUM_ACTIONS,
                                            lr=lr)

        self.visualizer = Visualizer()

    def reset(self):
        self.sample = None
        self.training_data_idx = -1

    def fit(self,
            num_epoches=1000,
            max_memory=60,
            balance=10000000,
            discount_factor=0,
            start_epsilon=.5,
            learning=True,
            exploration=False):

        logger.info(
            "Learning Rate: {lr}, Discount Factor: {discount_factor}, "
            "Trading Unit: [{min_trading_unit}, {max_trading_unit}], "
            "Delayed Reward threshold: {delayed_reward_threshold}".format(
                lr=self.policy_network.lr,
                discount_factor=discount_factor,
                min_trading_unit=self.agent.min_trading_unit,
                max_trading_unit=self.agent.max_trading_unit,
                delayed_reward_threshold=self.agent.delayed_reward_threshold))

        self.visualizer.prepare(self.environment.coin_chart)
        epoch_summary_dir = os.path.join(
            settings.BASE_DIR, 'epoch_summary/%s/epoch_summary_%s' %
            (self.coin_code, settings.timestr))
        if not os.path.isdir(epoch_summary_dir):
            os.makedirs(epoch_summary_dir)

        self.agent.set_balance(balance)

        max_portfolio_value = 0
        epoch_win_cnt = 0

        for epoch in range(num_epoches):
            loss = 0.
            itr_cnt = 0
            win_cnt = 0
            exploration_cnt = 0
            batch_size = 0
            pos_learning_cnt = 0
            neg_learning_cnt = 0

            memory_sample = []
            memory_action = []
            memory_reward = []
            memory_prob = []
            memory_pv = []
            memory_num_coins = []
            memory_exp_idx = []
            memory_learning_idx = []

            self.environment.reset()
            self.agent.reset()
            self.policy_network.reset()
            self.reset()
            self.visualizer.clear([0, len(self.coin_chart)])

            if learning:
                epsilon = start_epsilon * (1. - float(epoch) /
                                           (num_epoches - 1))
            else:
                epsilon = 0

            if exploration:
                epsilon = 1

            while True:
                next_sample = self._build_sample()
                print(next_sample)

                if next_sample is None:
                    break

                action, confidence, exploration = self.agent.decide_action(
                    self.policy_network, self.sample, epsilon)

                immediate_reward, delayed_reward = self.agent.act(
                    action, confidence)

                memory_sample.append(next_sample)
                memory_action.append(action)
                memory_reward.append(immediate_reward)
                memory_pv.append(self.agent.portfolio_value)
                memory_num_coins.append(self.agent.num_coins)

                memory = [
                    (memory_sample[i], memory_action[i], memory_reward[i])
                    for i in list(range(len(memory_action)))[-max_memory:]
                ]

                if exploration:
                    memory_exp_idx.append(itr_cnt)
                    memory_prob.append([np.nan] * Agent.NUM_ACTIONS)
                else:
                    memory_prob.append(self.policy_network.prob)

                batch_size += 1
                itr_cnt += 1
                exploration_cnt += 1 if exploration else 0
                win_cnt += 1 if delayed_reward > 0 else 0

                if delayed_reward == 0 and batch_size >= max_memory:
                    delayed_reward = immediate_reward
                    self.agent.base_portfolio_value = self.agent.portfolio_value

                if learning and delayed_reward != 0:
                    batch_size = min(batch_size, max_memory)
                    x, y = self._get_batch(memory, batch_size, discount_factor,
                                           delayed_reward)
                    if len(x) > 0:
                        if delayed_reward > 0:
                            pos_learning_cnt += 1
                        else:
                            neg_learning_cnt += 1
                        loss += self.policy_network.train_on_batch(x, y)
                        memory_learning_idx.append([itr_cnt, delayed_reward])
                    batch_size = 0

            num_epoches_digit = len(str(num_epoches))
            epoch_str = str(epoch + 1).rjust(num_epoches_digit, '0')

            self.visualizer.plot(epoch_str=epoch_str,
                                 num_epoches=num_epoches,
                                 epsilon=epsilon,
                                 action_list=Agent.ACTIONS,
                                 actions=memory_action,
                                 num_coins=memory_num_coins,
                                 outvals=memory_prob,
                                 exps=memory_exp_idx,
                                 learning=memory_learning_idx,
                                 initial_balance=self.agent.initial_balance,
                                 pvs=memory_pv)
            self.visualizer.save(
                os.path.join(
                    epoch_summary_dir,
                    'epoch_summary_%s_%s.png' % (settings.timestr, epoch_str)))

            if pos_learning_cnt + neg_learning_cnt > 0:
                loss /= pos_learning_cnt + neg_learning_cnt
            logger.info(
                "[Epoch %s/%s]\tEpsilon:%.4f\t#Expl.:%d/%d\t"
                "#Buy:%d\t#Sell:%d\t#Hold:%d\t"
                "#Coins:%d\tPV:%s\t"
                "POS:%s\tNEG:%s\tLoss:%10.6f" %
                (epoch_str, num_epoches, epsilon, exploration_cnt, itr_cnt,
                 self.agent.num_buy, self.agent.num_sell, self.agent.num_hold,
                 self.agent.num_coins,
                 locale.currency(self.agent.portfolio_value, grouping=True),
                 pos_learning_cnt, neg_learning_cnt, loss))

            max_portfolio_value = max(max_portfolio_value,
                                      self.agent.portfolio_value)
            if self.agent.portfolio_value > self.agent.initial_balance:
                epoch_win_cnt += 1

        logger.info("Max PV: %s, \t # Win: %d" % (locale.currency(
            max_portfolio_value, grouping=True), epoch_win_cnt))

    def _get_batch(self, memory, batch_size, discount_factor, delayed_reward):
        x = np.zeros((batch_size, 1, self.num_features))
        y = np.full((batch_size, self.agent.NUM_ACTIONS), 0.5)

        for i, (sample, action,
                reward) in enumerate(reversed(memory[-batch_size:])):
            x[i] = np.array(sample).reshape((-1, 1, self.num_features))
            y[i, action] = (delayed_reward + 1) / 2
            if discount_factor > 0:
                y[i, action] *= discount_factor**i
        return x, y

    def _build_sample(self):
        self.environment.observe()
        if len(self.training_data) > self.training_data_idx + 1:
            self.training_data_idx += 1
            self.sample = self.training_data.iloc[
                self.training_data_idx].tolist()
            self.sample.extend(self.agent.get_states())
            return self.sample
        return None

    def trade(self, model_path=None, balance=2000000, exploration=False):
        if model_path is None:
            return
        self.policy_network.load_model(model_path=model_path)

        if exploration:
            self.fit(balance=balance,
                     num_epoches=1,
                     learning=False,
                     exploration=True)
        else:
            self.fit(balance=balance, num_epoches=1, learning=False)

    def pv(self):
        return self.agent.portfolio_value
コード例 #6
0
class PolicyLearner:
    def __init__(self,
                 stock_code,
                 chart_data,
                 training_data=None,
                 min_trading_unit=1,
                 max_trading_unit=2,
                 delayed_reward_threshold=.05,
                 lr=0.01):
        self.stock_code = stock_code  # 종목코드
        self.chart_data = chart_data
        self.environment = Environment(chart_data)  # 환경 객체
        # 에이전트 객체
        self.agent = Agent(self.environment,
                           min_trading_unit=min_trading_unit,
                           max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold)
        self.training_data = training_data  # 학습 데이터
        self.samples = None
        self.training_data_idx = -1
        # 정책 신경망; 입력 크기 = 학습 데이터의 크기 + 에이전트 상태 크기
        self.num_features = self.training_data.shape[1] + self.agent.STATE_DIM
        self.policy_network = PolicyNetwork(input_dim=self.num_features,
                                            output_dim=self.agent.NUM_ACTIONS,
                                            lr=lr)
        self.visualizer = Visualizer()  # 가시화 모듈

    def reset(self):
        self.sample = None
        self.training_data_idx = -1

    def fit(self,
            num_epoches=1000,
            max_memory=60,
            balance=10000000,
            discount_factor=0,
            start_epsilon=.5,
            learning=True,
            index_change_rate=0):
        logger.info(
            "LR: {lr}, DF: {discount_factor}, "
            "TU: [{min_trading_unit}, {max_trading_unit}], "
            "DRT: {delayed_reward_threshold}".format(
                lr=self.policy_network.lr,
                discount_factor=discount_factor,
                min_trading_unit=self.agent.min_trading_unit,
                max_trading_unit=self.agent.max_trading_unit,
                delayed_reward_threshold=self.agent.delayed_reward_threshold))

        # 가시화 준비
        # 차트 데이터는 변하지 않으므로 미리 가시화
        self.visualizer.prepare(self.environment.chart_data)

        # 가시화 결과 저장할 폴더 준비
        epoch_summary_dir = os.path.join(
            settings.BASE_DIR, 'epoch_summary/%s/epoch_summary_%s' %
            (self.stock_code, settings.timestr))
        if not os.path.isdir(epoch_summary_dir):
            os.makedirs(epoch_summary_dir)

        # 에이전트 초기 자본금 설정
        self.agent.set_balance(balance)

        # 학습에 대한 정보 초기화
        max_portfolio_value = 0
        epoch_win_cnt = 0

        # 학습 반복
        for epoch in range(num_epoches):
            # 에포크 관련 정보 초기화
            loss = 0.
            itr_cnt = 0
            win_cnt = 0
            exploration_cnt = 0
            batch_size = 0
            pos_learning_cnt = 0
            neg_learning_cnt = 0

            # 메모리 초기화
            memory_sample = []
            memory_action = []
            memory_reward = []
            memory_prob = []
            memory_pv = []
            memory_num_stocks = []
            memory_exp_idx = []
            memory_learning_idx = []

            # 환경, 에이전트, 정책 신경망 초기화
            self.environment.reset()
            self.agent.reset()
            self.policy_network.reset()
            self.reset()

            # 가시화 초기화
            self.visualizer.clear([0, len(self.chart_data)])

            # 학습을 진행할 수록 탐험 비율 감소
            if learning:
                epsilon = start_epsilon * (1. - float(epoch) /
                                           (num_epoches - 1))
            else:
                epsilon = 0

            while True:
                # 샘플 생성
                next_sample = self._build_sample()
                if next_sample is None:
                    break

                # 정책 신경망 또는 탐험에 의한 행동 결정
                action, confidence, exploration = self.agent.decide_action(
                    self.policy_network, self.sample, epsilon)

                # 결정한 행동을 수행하고 즉시 보상과 지연 보상 획득
                immediate_reward, delayed_reward = self.agent.act(
                    action, confidence)

                # 행동 및 행동에 대한 결과를 기억
                memory_sample.append(next_sample)
                memory_action.append(action)
                memory_reward.append(immediate_reward)
                memory_pv.append(self.agent.portfolio_value)
                memory_num_stocks.append(self.agent.num_stocks)
                memory = [
                    (memory_sample[i], memory_action[i], memory_reward[i])
                    for i in list(range(len(memory_action)))[-max_memory:]
                ]
                if exploration:
                    memory_exp_idx.append(itr_cnt)
                    memory_prob.append([np.nan] * Agent.NUM_ACTIONS)
                else:
                    memory_prob.append(self.policy_network.prob)

                # 반복에 대한 정보 갱신
                batch_size += 1
                itr_cnt += 1
                exploration_cnt += 1 if exploration else 0
                win_cnt += 1 if delayed_reward > 0 else 0

                # 학습 모드이고 지연 보상이 존재할 경우 정책 신경망 갱신
                if delayed_reward == 0 and batch_size >= max_memory:
                    delayed_reward = immediate_reward
                    self.agent.base_portfolio_value = self.agent.portfolio_value
                if learning and delayed_reward != 0:
                    # 배치 학습 데이터 크기
                    batch_size = min(batch_size, max_memory)
                    # 배치 학습 데이터 생성
                    x, y = self._get_batch(memory, batch_size, discount_factor,
                                           delayed_reward)
                    if len(x) > 0:
                        if delayed_reward > 0:
                            pos_learning_cnt += 1
                        else:
                            neg_learning_cnt += 1
                        # 정책 신경망 갱신
                        loss += self.policy_network.train_on_batch(x, y)
                        memory_learning_idx.append([itr_cnt, delayed_reward])
                    batch_size = 0

            # 에포크 관련 정보 가시화
            num_epoches_digit = len(str(num_epoches))
            epoch_str = str(epoch + 1).rjust(num_epoches_digit, '0')

            self.visualizer.plot(epoch_str=epoch_str,
                                 num_epoches=num_epoches,
                                 epsilon=epsilon,
                                 action_list=Agent.ACTIONS,
                                 actions=memory_action,
                                 num_stocks=memory_num_stocks,
                                 outvals=memory_prob,
                                 exps=memory_exp_idx,
                                 learning=memory_learning_idx,
                                 initial_balance=self.agent.initial_balance,
                                 pvs=memory_pv)
            self.visualizer.save(
                os.path.join(
                    epoch_summary_dir,
                    'epoch_summary_%s_%s.png' % (settings.timestr, epoch_str)))

            # 에포크 관련 정보 로그 기록
            if pos_learning_cnt + neg_learning_cnt > 0:
                loss /= pos_learning_cnt + neg_learning_cnt
            logger.info(
                "[Epoch %s/%s]\tEpsilon:%.4f\t#Expl.:%d/%d\t"
                "#Buy:%d\t#Sell:%d\t#Hold:%d\t"
                "#Stocks:%d\tPV:%s\t"
                "POS:%s\tNEG:%s\tLoss:%10.6f" %
                (epoch_str, num_epoches, epsilon, exploration_cnt, itr_cnt,
                 self.agent.num_buy, self.agent.num_sell, self.agent.num_hold,
                 self.agent.num_stocks,
                 locale.currency(self.agent.portfolio_value, grouping=True),
                 pos_learning_cnt, neg_learning_cnt, loss))

            # 학습 관련 정보 갱신
            max_portfolio_value = max(max_portfolio_value,
                                      self.agent.portfolio_value)
            if self.agent.portfolio_value > self.agent.initial_balance:
                epoch_win_cnt += 1

        #수익율 계산
        profit = (max_portfolio_value - balance) / balance * 100

        # 학습 관련 정보 로그 기록
        if 0:
            logger.info("Max PV: %s, \t # Win: %d" % (locale.currency(
                max_portfolio_value, grouping=True), epoch_win_cnt))
        else:
            logger.info(
                "Max PV: %s, \t #Profit: %.2f, \t #Win: %d, \t  # Index : %.2f"
                % (locale.currency(max_portfolio_value, grouping=True), profit,
                   epoch_win_cnt, index_change_rate))

    def _get_batch(self, memory, batch_size, discount_factor, delayed_reward):
        x = np.zeros((batch_size, 1, self.num_features))
        y = np.full((batch_size, self.agent.NUM_ACTIONS), 0.5)

        for i, (sample, action,
                reward) in enumerate(reversed(memory[-batch_size:])):
            x[i] = np.array(sample).reshape((-1, 1, self.num_features))
            y[i, action] = (delayed_reward + 1) / 2
            if discount_factor > 0:
                y[i, action] *= discount_factor**i
        return x, y

    def _build_sample(self):
        self.environment.observe()
        if len(self.training_data) > self.training_data_idx + 1:
            self.training_data_idx += 1
            self.sample = self.training_data.iloc[
                self.training_data_idx].tolist()
            self.sample.extend(self.agent.get_states())
            return self.sample
        return None

    def trade(self, model_path=None, balance=2000000, index_change_rate=0):
        if model_path is None:
            return
        self.policy_network.load_model(model_path=model_path)
        self.fit(balance=balance,
                 num_epoches=1,
                 learning=False,
                 index_change_rate=index_change_rate)
コード例 #7
0
ファイル: policy_learner.py プロジェクト: PippasSong/TraderPy
class PolicyLearner:

    def __init__(self, stock_code, chart_data, training_data=None, min_trading_unit=1, max_trading_unit=2,
                 delayed_reward_threshold=.05, lr=0.01):
        self.stock_code = stock_code  # 종목코드
        self.chart_data = chart_data
        self.environment = Environment(chart_data)  # 환경 객체
        # 에이전트 객체
        self.agent = Agent(self.environment, min_trading_unit=min_trading_unit, max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold)
        self.training_data = training_data  # 학습 데이터
        self.sample = None
        self.training_data_idx = -1
        # 정책 신경망. 입력 크기 = 학습 데이터의 크기 + 에이전트 상태 크기
        self.num_features = self.training_data.shape[1] + self.agent.STATE_DIM
        self.policy_network = PolicyNetwork(input_dim=self.num_features, output_dim=self.agent.NUM_ACTIONS, lr=lr)
        self.visualizer = Visualizer()  # 가시화 모듈

    # 학습 데이터를 다시 읽기 위해 idx를 -1로 재설정
    def reset(self):
        self.sample = None
        self.training_data_idx = -1

    # num_epoches: 수행할 반복 학습의 전체 횟수
    # max_memory: 배치 학습 데이터를 만들기 위해 과거 데이터를 저장할 배열
    # balance: 에이전트의 초기 투자 자본금을 정해주기 위한 인자
    # discount_factor: 먼 과거의 행동일수록 할인 요인을 적용하여 지연 보상을 약하게 적용
    # start_epsilon: 초기 탐험 비율
    # learning: 학습 유무, 학습된 모델을 가지고 투자 시뮬레이션만 하려 한다면 False.
    # init_stocks: 초기에 보유한 주식의 수
    def fit(self, num_epoches=1000, max_memory=60, balance=1000000, discount_factor=0, start_epsilon=.5, learning=True, past_stock_value=0, cur_stock_value=0, init_stocks=0):
        logging.info("LR: {lr}, DF: {discount_factor}, "
                    "TU: [{min_trading_unit}, {max_trading_unit}],"
                    "DRT: {delayed_reward_threshold}".format(lr=self.policy_network.lr, discount_factor=discount_factor,
                                                             min_trading_unit=self.agent.min_trading_unit,
                                                             max_trading_unit=self.agent.max_trading_unit,
                                                             delayed_reward_threshold=self.agent.
                                                             delayed_reward_threshold))

        # 가시화 준비
        # 차트 데이터는 변하지 않으므로 미리 가시화
        self.visualizer.prepare(self.environment.chart_data)

        # 가시화 결과 저장할 폴더 준비
        # join(parent, child) 로 폴더 생성
        epoch_summary_dir = os.path.join(settings.BASE_DIR,
                                         'epoch_summary/%s/epoch_summary_%s' % (
                                             self.stock_code, settings.timestr))  # 가시화 결과를 저장시 날짜와 시간 지정
        if not os.path.isdir(epoch_summary_dir):  # path 가 존재하고 폴더인지 확인
            os.makedirs(epoch_summary_dir)  # path 에 포함된 폴더들이 없을 경우 생성해 줌

        # 에이전트 초기 자본금 설정
        self.agent.set_balance(balance)


        # 학습에 대한 정보 초기화
        max_portfolio_value = 0
        epoch_win_cnt = 0

        # 학습 반복
        for epoch in range(num_epoches):
            # 에포크 관련 정보 초기화
            loss = 0.  # 정책 신경망의 결과가 학습 데이터와 얼마나 차이가 있는지를 저장
            itr_cnt = 0  # 수행한 에포크 수를 저장
            win_cnt = 0  # 수행한 에포크 중에서 수익이 발생한 에포크 수를 저장
            exploration_cnt = 0  # 무작위 투자를 수행한 횟수를 저장
            batch_size = 0
            pos_learning_cnt = 0  # 수익이 발생하여 긍정적 지연 보상을 준 수
            neg_learning_cnt = 0  # 손실이 발생하여 부정적 지연 보상을 준 수

            # 메모리 초기화
            memory_sample = []
            memory_action = []
            memory_reward = []  # 즉시보상
            memory_prob = []  # 정책 신경망의 출력
            memory_pv = []  # 포트폴리오 가치
            memory_num_stocks = []  # 보유 주식 수
            memory_exp_idx = []  # 탐험 위치
            memory_learning_idx = []  # 학습 위치

            # 환경, 에이전트, 정책 신경망 초기화
            self.environment.reset()
            self.agent.reset(cur_stock_value, init_stocks)
            self.policy_network.reset()
            self.reset()

            # 가시화기 초기화
            self.visualizer.clear([0, len(self.chart_data)])  # x축 데이터 범위를 파라미터로 넣어준다

            # 학습을 진행할수록 탐험 비율 감소
            if learning:
                epsilon = start_epsilon * (1. - float(epoch) / (num_epoches - 1))
            else:
                epsilon = 0

            while True:
                # 샘플 생성
                next_sample = self._build_sample()
                if next_sample is None:
                    break

                # 정책 신경망 또는 탐험에 의한 행동 결정
                action, confidence, exploration = self.agent.decide_action(self.policy_network, self.sample, epsilon)

                # 결정한 행동을 수행하고 즉시 보상과 지연 보상 획득
                immediate_reward, delayed_reward = self.agent.act(action, confidence)

                # 행동 및 행동에 대한 결과를 기억
                memory_sample.append(next_sample)
                memory_action.append(action)
                memory_reward.append(immediate_reward)
                memory_pv.append(self.agent.portfolio_value)
                memory_num_stocks.append(self.agent.num_stocks)

                # 학습 데이터의 샘플, 에이전트 행동, 즉시보상, 포트폴리오 가치, 보유 주식 수를 저장하는 2차원 배열
                memory = [(memory_sample[i], memory_action[i], memory_reward[i])
                          for i in list(range(len(memory_action)))[-max_memory:]]
                if exploration:
                    memory_exp_idx.append(itr_cnt)  # 무작위 행동을 결정한 경우 현재의 인덱스를 저장
                    memory_prob.append([np.nan] * Agent.NUM_ACTIONS)  # 무작위 투자에서는 정책 신경망의 출력이 없기 때문에 nan값을 넣는다. 리스트에
                    # 곱하기를 하면 똑같은 리스트를 뒤에 붙여준다
                else:
                    memory_prob.append(self.policy_network.prob)  # 무작위 투자가 아닌 경우 신경망의 출력을 그대로 저장

                # 반복에 대한 정보 갱신
                batch_size += 1
                itr_cnt += 1
                exploration_cnt += 1 if exploration else 0  # 탐험을 한 경우에만 1을 증가시키고 그렇지 않으면 0을 더함
                win_cnt += 1 if delayed_reward > 0 else 0  # 지연 보상이 0보다 큰 경우에만 1을 증가시킴

                # 학습 모드이고 지연 보상이 존재할 경우 정책 신경망 갱신
                if delayed_reward == 0 and batch_size >= max_memory:
                    delayed_reward = immediate_reward
                    self.agent.base_portfolio_value = self.agent.portfolio_value
                if learning and delayed_reward != 0:
                    # 배치 학습 데이터 크기
                    batch_size = min(batch_size, max_memory)
                    # 배치 학습 데이터 생성
                    x, y = self._get_batch(
                        memory, batch_size, discount_factor, delayed_reward)
                    if len(x) > 0:
                        if delayed_reward > 0:
                            pos_learning_cnt += 1
                        else:
                            neg_learning_cnt += 1
                        # 정책 신경망 갱신
                        loss += self.policy_network.train_on_batch(x, y)
                        memory_learning_idx.append([itr_cnt, delayed_reward])
                    batch_size = 0  # 학습 수행 후 배치 데이터 크기를 초기화

            # 에포크 관련 정보 가시화
            num_epoches_digit = len(str(num_epoches))
            epoch_str = str(epoch + 1).rjust(num_epoches_digit, '0')  # 문자열을 자리수에 맞게 오른쪽으로 정렬해 주는 함수

            self.visualizer.plot(
                epoch_str=epoch_str, num_epoches=num_epoches, epsilon=epsilon,
                action_list=Agent.ACTIONS, actions=memory_action,
                num_stocks=memory_num_stocks, outvals=memory_prob,
                exps=memory_exp_idx, learning_idxes=memory_learning_idx,
                initial_balance=self.agent.initial_balance, pvs=memory_pv
            )
            # 수행 결과를 파일로 저장
            self.visualizer.save(
                os.path.join(epoch_summary_dir, 'epoch_summary_%s_%s.png' % (settings.timestr, epoch_str)))

            # 에포크 관련 정보 로그 기록
            # 콘솔창에 뜨는 정보
            if pos_learning_cnt + neg_learning_cnt > 0:
                loss /= pos_learning_cnt + neg_learning_cnt
            logging.info("[Epoch %s/%s]\tEpsilon:%.4f\t#Expl.:%d/%d\t"
                        "#Buy:%d\t#Sell:%d\t#Hold:%d\t"
                        "#Stocks:%d\tPV:%s\t(%s+%s*%s)\t"
                        "POS:%s\tNEG:%s\tLoss:%10.6f" % (
                            epoch_str, num_epoches, epsilon, exploration_cnt, itr_cnt,
                            self.agent.num_buy, self.agent.num_sell, self.agent.num_hold,
                            self.agent.num_stocks,
                            locale.currency(self.agent.portfolio_value, grouping=True),self.agent.balance, self.environment.get_price(), self.agent.num_stocks,
                            pos_learning_cnt, neg_learning_cnt, loss
                        ))

            # 학습 관련 정보 갱신
            max_portfolio_value = max(max_portfolio_value, self.agent.portfolio_value)
            if self.agent.portfolio_value > self.agent.initial_balance:
                epoch_win_cnt += 1

        # 학습 관련 정보 로그 기록
        logging.info("Max PV: %s, \t # Win: %d" % (
            locale.currency(max_portfolio_value, grouping=True), epoch_win_cnt
        ))

    # 미니 배치 데이터 생성
    def _get_batch(self, memory, batch_size, discount_factor, delayed_reward):
        x = np.zeros((batch_size, 1, self.num_features))  # 특징벡터를 지정
        y = np.full((batch_size, self.agent.NUM_ACTIONS), 0.5)  # 지연 보상으로 정답(레이블)을 설정
        for i, (sample, action, reward) in enumerate(reversed(memory[-batch_size:])):
            x[i] = np.array(sample).reshape((-1, 1, self.num_features))
            y[i, action] = (delayed_reward + 1) / 2
            if discount_factor > 0:
                y[i, action] *= discount_factor ** i
        return x, y

    # 학습 데이터를 구성하는 샘플 하나를 생성
    def _build_sample(self):
        self.environment.observe()  # 다음 인덱스 데이터를 읽도록 한다
        if len(
                self.training_data) > self.training_data_idx + 1:  # 다음 인덱스 데이터가 존재하면 training_data_idx인덱스의 데이터를 받아와서
            # sample로 저장
            self.training_data_idx += 1
            self.sample = self.training_data.iloc[self.training_data_idx].tolist()
            self.sample.extend(self.agent.get_states())
            return self.sample
        return None

    # 학습된 정책 신경망 모델로 주식투자 시뮬레이션
    # init_stocks: 초기에 보유한 주식 수
    def trade(self, model_path=None, balance=2000000, cur_stock_value=0, init_stocks=0):
        if model_path is None:
            return
        self.policy_network.load_model(model_path=model_path)  # 학습된 신경망 모델을 적용
        self.fit(balance=balance, num_epoches=1, learning=False, cur_stock_value=cur_stock_value, init_stocks=init_stocks)  # 학습을 진행하지 않고 정책 신경망에만 의존하여 투자 시뮬레이션을 진행
コード例 #8
0
class PolicyLearner:
    def __init__(self,
                 stock_code,
                 chart_data,
                 training_data=None,
                 min_trading_unit=1,
                 max_trading_unit=2,
                 delayed_reward_threshold=0.05,
                 l_rate=0.01):

        self.stock_code = stock_code
        self.chart_data = chart_data

        self.environment = Environment(chart_data)

        self.agent = Agent(self.environment,
                           min_trading_unit=min_trading_unit,
                           max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold)

        self.training_data = training_data
        self.sample = None
        self.training_data_idx = -1

        self.num_features = self.training_data.shape[1] + self.agent.STATE_DIM

        self.policy_network = PolicyNetwork(input_dim=self.num_features,
                                            output_dim=self.agent.NUM_ACTIONS,
                                            l_rate=l_rate)

        self.visualizer = Visualizer()

    def reset(self):
        self.sample = None
        self.training_data_idx = -1

    def fit(self,
            num_epoches=1000,
            max_memory=60,
            balance=1000000,
            discount_factor=0,
            start_epsilon=0.5,
            learning=True):
        logger.info(
            "LR: {l_rate}, DF: {discount_factor}, "
            "TU: [{min_trading_unit, {max_trading_unit}], "
            "DRT: {delayed_reward_threshold}".format(
                l_rate=self.policy_network.l_rate,
                discount_factor=discount_factor,
                min_trading_unit=self.agent.min_trading_unit,
                max_trading_unit=self.agent.max_trading_unit,
                delayed_reward_threshold=self.agent.delayed_reward_threshold))

        self.visualizer.prepare(self.environment.chart_data)

        # epoch_summary_dir = os.path.join(settings.BASE_DIR, 'epoch_summary/%s/epoch_summary_%s' % (
        #     self.stock_code, settings.timestr
        # ))
        epoch_summary_dir = os.path.join(settings.BASE_DIR, 'epoch_summary',
                                         '%s' % self.stock_code,
                                         'epoch_summary_%s' % settings.timestr)

        if not os.path.isdir(epoch_summary_dir):
            os.makedirs(epoch_summary_dir)

        self.agent.set_balance(balance)

        max_portfolio_value = 0
        epoch_win_cnt = 0

        for epoch in range(num_epoches):
            loss = 0.0
            itr_cnt = 0
            win_cnt = 0
            exploration_cnt = 0
            batch_size = 0
            pos_learning_cnt = 0
            neg_learning_cnt = 0

            memory_sample = []
            memory_action = []
            memory_reward = []
            memory_prob = []
            memory_pv = []
            memory_num_stocks = []
            memory_exp_idx = []
            memory_learning_idx = []

            self.environment.reset()
            self.agent.reset()
            self.policy_network.reset()
            self.reset()

            self.visualizer.clear([0, len(self.chart_data)])

            if learning:
                epsilon = start_epsilon * (1. - (float(epoch) /
                                                 (num_epoches - 1)))
            else:
                epsilon = 0

            while True:
                next_sample = self._build_sample()
                if next_sample in None:
                    break

                action, confidence, exploration = self.agent.decide_action(
                    self.policy_network, self.sample, epsilon)
                immediate_reward, delayed_reward = self.agent.act(
                    action, confidence)
コード例 #9
0
class PolicyLearner:
    def __init__(self,
                 stock_code,
                 chart_data,
                 training_data=None,
                 min_trading_unit=1,
                 max_trading_unit=2,
                 delayed_reward_threshold=.05,
                 lr=0.01,
                 tax=False):
        self.stock_code = stock_code  # Stock code
        self.chart_data = chart_data
        self.environment = Environment(chart_data)  # Environment object
        self.tax = tax
        # Agent object
        self.agent = Agent(self.environment,
                           min_trading_unit=min_trading_unit,
                           max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold,
                           tax=tax)
        self.training_data = training_data  # Training data
        self.sample = None
        self.training_data_idx = -1
        # Policy neural network; Input size = size of training data + agent state size
        self.num_features = self.training_data.shape[1] + self.agent.STATE_DIM
        self.policy_network = PolicyNetwork(input_dim=self.num_features,
                                            output_dim=self.agent.NUM_ACTIONS,
                                            lr=lr)
        self.visualizer = Visualizer()  # Visualization module

    def reset(self):
        self.sample = None
        self.training_data_idx = -1

    def fit(self,
            num_epoches=1000,
            max_memory=60,
            balance=10000000,
            discount_factor=0,
            start_epsilon=.5,
            learning=True,
            monkey=False):
        logging.info(
            "\n\nLR: {lr}, DF: {discount_factor}, "
            "TU: [{min_trading_unit}, {max_trading_unit}], "
            "DRT: {delayed_reward_threshold}, Tax: {tax}".format(
                lr=self.policy_network.lr,
                discount_factor=discount_factor,
                min_trading_unit=self.agent.min_trading_unit,
                max_trading_unit=self.agent.max_trading_unit,
                delayed_reward_threshold=self.agent.delayed_reward_threshold,
                tax=self.tax))

        # Visualization Preparation
        # Pre-visualization the chart data as it does not change
        self.visualizer.prepare(self.environment.chart_data)

        # Prepare the folders to store visualization results
        epoch_summary_dir = os.path.join(
            settings.BASE_DIR, 'epoch_summary/%s/epoch_summary_%s' %
            (self.stock_code, settings.timestr))
        if not os.path.isdir(epoch_summary_dir):
            os.makedirs(epoch_summary_dir)

        # Set agent's initial balance
        self.agent.set_balance(balance)

        # Initialize the information about training
        max_portfolio_value = 0
        epoch_win_cnt = 0

        # Training repetition
        for epoch in range(num_epoches):
            # Initialize the information about epoch
            loss = 0.
            itr_cnt = 0
            win_cnt = 0
            exploration_cnt = 0
            batch_size = 0
            pos_learning_cnt = 0
            neg_learning_cnt = 0

            # Initialize the memory
            memory_sample = []
            memory_action = []
            memory_reward = []
            memory_prob = []
            memory_pv = []
            memory_num_stocks = []
            memory_exp_idx = []
            memory_learning_idx = []

            # Initialize the environment, agent and policy nerual network
            self.environment.reset()
            self.agent.reset()
            self.policy_network.reset()
            self.reset()

            # Initialize the visualizer
            self.visualizer.clear([0, len(self.chart_data)])

            # Exploration rate decreases as you progress
            if monkey:
                epsilon = 1
            else:
                if learning:
                    epsilon = start_epsilon * (1. - float(epoch) /
                                               (num_epoches - 1))
                else:
                    epsilon = 0

            while True:
                # Sample generation
                next_sample = self._build_sample()
                if next_sample is None:
                    break

                # Actions decided by policy neural network or exploration
                action, confidence, exploration = self.agent.decide_action(
                    self.policy_network, self.sample, epsilon)

                # Perform the action you decided and earn immediate and delayed rewards
                immediate_reward, delayed_reward = self.agent.act(
                    action, confidence)

                # Store the actions and the consequences for the actions
                memory_sample.append(next_sample)
                memory_action.append(action)
                memory_reward.append(immediate_reward)
                memory_pv.append(self.agent.portfolio_value)
                memory_num_stocks.append(self.agent.num_stocks)
                memory = [
                    (memory_sample[i], memory_action[i], memory_reward[i])
                    for i in list(range(len(memory_action)))[-max_memory:]
                ]
                if exploration:
                    memory_exp_idx.append(itr_cnt)
                    memory_prob.append([np.nan] * Agent.NUM_ACTIONS)
                else:
                    memory_prob.append(self.policy_network.prob)

                # Update the information about iterations
                batch_size += 1
                itr_cnt += 1
                exploration_cnt += 1 if exploration else 0
                win_cnt += 1 if delayed_reward > 0 else 0

                # Update policy neural network when in training mode and delay rewards exist
                if delayed_reward == 0 and batch_size >= max_memory:
                    delayed_reward = immediate_reward
                    self.agent.base_portfolio_value = self.agent.portfolio_value
                if learning and delayed_reward != 0:
                    # Size of batch traning data
                    batch_size = min(batch_size, max_memory)
                    # Generate batch training data
                    x, y = self._get_batch(memory, batch_size, discount_factor,
                                           delayed_reward)
                    if len(x) > 0:
                        if delayed_reward > 0:
                            pos_learning_cnt += 1
                        else:
                            neg_learning_cnt += 1
                        # Update Policy neural network
                        loss += self.policy_network.train_on_batch(x, y)
                        memory_learning_idx.append([itr_cnt, delayed_reward])
                    batch_size = 0
            # Visualize the information about epoches
            num_epoches_digit = len(str(num_epoches))
            epoch_str = str(epoch + 1).rjust(num_epoches_digit, '0')

            self.visualizer.plot(epoch_str=epoch_str,
                                 num_epoches=num_epoches,
                                 epsilon=epsilon,
                                 action_list=Agent.ACTIONS,
                                 actions=memory_action,
                                 num_stocks=memory_num_stocks,
                                 outvals=memory_prob,
                                 exps=memory_exp_idx,
                                 learning=memory_learning_idx,
                                 initial_balance=self.agent.initial_balance,
                                 pvs=memory_pv)
            self.visualizer.save(
                os.path.join(
                    epoch_summary_dir,
                    'epoch_summary_%s_%s.png' % (settings.timestr, epoch_str)))

            # Record the information about epoches in log
            if pos_learning_cnt + neg_learning_cnt > 0:
                loss /= pos_learning_cnt + neg_learning_cnt
            logging.info("[Epoch {}/{}]\tEpsilon:{}\t#Expl.:{}/{}\t"
                         "#Buy:{}\t#Sell:{}\t#Hold:{}\t"
                         "#Stocks:{}\tPV:{:,}원\t"
                         "POS:{}\tNEG:{}\tLoss:{}".format(
                             epoch_str, num_epoches, round(epsilon,
                                                           4), exploration_cnt,
                             itr_cnt, self.agent.num_buy, self.agent.num_sell,
                             self.agent.num_hold, self.agent.num_stocks,
                             int(self.agent.portfolio_value), pos_learning_cnt,
                             neg_learning_cnt, round(loss, 6)))

            # Update the information about training
            max_portfolio_value = max(max_portfolio_value,
                                      self.agent.portfolio_value)
            if self.agent.portfolio_value > self.agent.initial_balance:
                epoch_win_cnt += 1

        # Record the information about training in log
        logging.info("Max PV: {:,}원, \t # Win: {}".format(
            int(max_portfolio_value), epoch_win_cnt))

    def _get_batch(self, memory, batch_size, discount_factor, delayed_reward):
        x = np.zeros((batch_size, 1, self.num_features))
        y = np.full((batch_size, self.agent.NUM_ACTIONS), 0.5)

        for i, (sample, action,
                _) in enumerate(reversed(memory[-batch_size:])):
            x[i] = np.array(sample).reshape((-1, 1, self.num_features))
            y[i, action] = (delayed_reward + 1) / 2
            if discount_factor > 0:
                y[i, action] *= discount_factor**i
        return x, y

    def _build_sample(self):
        self.environment.observe()
        if len(self.training_data) > self.training_data_idx + 1:
            self.training_data_idx += 1
            self.sample = self.training_data.iloc[
                self.training_data_idx].tolist()
            self.sample.extend(self.agent.get_states())
            return self.sample
        return None

    def trade(self, model_path=None, balance=2000000):
        if model_path is None:
            return
        self.policy_network.load_model(model_path=model_path)
        self.fit(balance=balance, num_epoches=1, learning=False)
コード例 #10
0
class PolicyLearner:
    def __init__(self,
                 stock_code,
                 chart_data,
                 training_data=None,
                 min_trading_unit=1,
                 max_trading_unit=2,
                 delayed_reward_threshold=.05,
                 lr=0.01):
        self.stock_code = stock_code
        self.chart_data = chart_data
        self.environment = Environment(chart_data)
        self.agent = Agent(self.environment,
                           min_trading_unit=min_trading_unit,
                           max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold)
        self.training_data = training_data
        self.sample = None
        self.training_data_idx = -1

        self.num_features = self.training_data.shape[1] + self.agent.STATE_DIM
        self.policy_network = PolicyNetwork(input_dim=self.num_features,
                                            output_dim=self.agent.NUM_ACTIONS,
                                            lr=lr)
        self.visualizer = Visualizer()

    def reset(self):
        self.sample = None
        self.training_data_idx = -1

    def fit(self,
            num_epoches=1000,
            max_memory=60,
            balance=10000000,
            discount_factor=0,
            start_epsilon=.5,
            learning=True):
        logger.info(
            "LR: {lr}, DF: {discount_factor}, "
            "TU: [{min_trading_unit}, {max_trading_unit}], "
            "DRT: {delayed_reward_threshold}".format(
                lr=self.policy_network.lr,
                discount_factor=discount_factor,
                min_trading_unit=self.agent.min_trading_unit,
                max_trading_unit=self.agent.max_trading_unit,
                delayed_reward_threshold=self.agent.delayed_reward_threshold))

        # Prepare for visualization
        self.visualizer.prepare(self.environment.chart_data)

        # Set the folder for saving the visual results
        epoch_summary_dir = os.path.join(
            settings.BASE_DIR, 'epoch_summary/%s/epoch_summary_%s' %
            (self.stock_code, settings.timestr))
        if not os.path.isdir(epoch_summary_dir):
            os.makedirs(epoch_summary_dir)

        # Set the initial balance
        self.agent.set_balance(balance)

        # Initialize the learning variables
        max_portfolio_value = 0
        epoch_win_cnt = 0

        # Learning iteration
        for epoch in range(num_epoches):
            # Initialize epoch variables
            loss = 0.  # Difference between policy learner and learning data
            itr_cnt = 0  # Iteration count
            win_cnt = 0  # The number of epochs that make profits
            exploration_cnt = 0  # The number of investment randomly
            batch_size = 0
            pos_learning_cnt = 0  # The number of providing positive delayed reward
            neg_learning_cnt = 0  # The number of providing negative delayed reward

            # Initialize memory
            memory_sample = []
            memory_action = []
            memory_reward = []
            memory_prob = []
            memory_pv = []
            memory_num_stocks = []
            memory_exp_idx = []
            memory_learning_idx = []

            self.environment.reset()
            self.agent.reset()
            self.policy_network.reset()
            self.reset()
            self.visualizer.clear([0, len(self.chart_data)])

            if learning:  # Decreasing exploration rate
                epsilon = start_epsilon * (1. - float(epoch) /
                                           (num_epoches - 1))
            else:
                epsilon = 0

            while True:
                next_sample = self._build_sample()
                if next_sample is None:
                    break
                action, confidence, exploration = self.agent.decide_action(
                    self.policy_network, self.sample, epsilon)
                immediate_reward, delayed_reward = self.agent.act(
                    action, confidence)

                # Memorize action and results
                memory_sample.append(next_sample)
                memory_action.append(action)
                memory_reward.append(immediate_reward)
                memory_pv.append(self.agent.portfolio_value)
                memory_num_stocks.append(self.agent.num_stocks)
                memory = [
                    (memory_sample[i], memory_action[i], memory_reward[i])
                    for i in list(range(len(memory_action)))[-max_memory:]
                ]
                if exploration:
                    memory_exp_idx.append(itr_cnt)
                    memory_prob.append([np.nan] * Agent.NUM_ACTIONS)
                else:
                    memory_prob.append(self.policy_network.prob)

                # Renew the batch info
                batch_size += 1
                itr_cnt += 1
                exploration_cnt += 1 if exploration else 0
                win_cnt += 1 if delayed_reward > 0 else 0

                # Renew policy learning if learning mode and delayed mode
                if delayed_reward == 0 and batch_size >= max_memory:
                    delayed_reward = immediate_reward
                if learning and delayed_reward != 0:
                    batch_size = min(batch_size, max_memory)
                    x, y = self._get_batch(memory, batch_size, discount_factor,
                                           delayed_reward)
                    if len(x) > 0:
                        if delayed_reward > 0:
                            pos_learning_cnt += 1
                        else:
                            neg_learning_cnt += 1
                        loss += self.policy_network.train_on_batch(x, y)
                        memory_learning_idx.append([itr_cnt, delayed_reward])
                    batch_size = 0

            # Visualize epoch info
            num_epoches_digit = len(str(num_epoches))
            epoch_str = str(epoch + 1).rjust(num_epoches_digit, '0')

            self.visualizer.plot(epoch_str=epoch_str,
                                 num_epoches=num_epoches,
                                 epsilon=epsilon,
                                 action_list=Agent.ACTIONS,
                                 actions=memory_action,
                                 num_stocks=memory_num_stocks,
                                 outvals=memory_prob,
                                 exps=memory_exp_idx,
                                 learning=memory_learning_idx,
                                 initial_balance=self.agent.initial_balance,
                                 pvs=memory_pv)
            self.visualizer.save(
                os.path.join(
                    epoch_summary_dir,
                    'epoch_summary_%s_%s.png' % (settings.timestr, epoch_str)))

            # Record logs of Epoch info
            if pos_learning_cnt + neg_learning_cnt > 0:
                loss /= pos_learning_cnt + neg_learning_cnt
            logger.info(
                "[Epoch %s/%s]\tEpsilon:%.4f\t#Expl.:%d/%d\t"
                "#Buy:%d\t#Sell:%d\t#Hold:%d\t"
                "#Stocks:%d\tPV:%s\t"
                "POS:%s\tNEG:%s\tLoss:%10.6f" %
                (epoch_str, num_epoches, epsilon, exploration_cnt, itr_cnt,
                 self.agent.num_buy, self.agent.num_sell, self.agent.num_hold,
                 self.agent.num_stocks,
                 locale.currency(self.agent.portfolio_value, grouping=True),
                 pos_learning_cnt, neg_learning_cnt, loss))

            # Renew learning info
            max_portfolio_value = max(max_portfolio_value,
                                      self.agent.portfolio_value)
            if self.agent.portfolio_value > self.agent.initial_balance:
                epoch_win_cnt += 1

        # Record logs of learning info
        logger.info("Max PV: %s, \t # Win: %d" % (locale.currency(
            max_portfolio_value, grouping=True), epoch_win_cnt))

    def _get_batch(self, memory, batch_size, discount_factor, delayed_reward):
        x = np.zeros((batch_size, 1, self.num_features))
        y = np.full((batch_size, self.agent.NUM_ACTIONS), 0.5)

        for i, (sample, action,
                reward) in enumerate(reversed(memory[-batch_size:])):
            x[i] = np.array(sample).reshape((-1, 1, self.num_features))
            y[i, action] = (delayed_reward + 1) / 2
            if discount_factor > 0:
                y[i, action] *= discount_factor**i
        return x, y

    def _build_sample(self):
        self.environment.observe()
        if len(self.training_data) > self.training_data_idx + 1:
            self.training_data_idx += 1
            self.sample = self.training_data.iloc[
                self.training_data_idx].tolist()
            self.sample.extend(self.agent.get_states())
            return self.sample
        return None

    def trade(self, model_path=None, balance=2000000):
        if model_path is None:
            return
        self.policy_network.load_model(model_path=model_path)
        self.fit(balance=balance, num_epoches=1, learning=False)
コード例 #11
0
class PolicyLearner:
    # chart_data Environment객체 생시 넣어줌
    def __init__(self,stock_code,chart_data,training_data = None,min_trading_unit=1, max_trading_unit=2,delayed_reward_threshold=.05,lr=0.01):
        #종목코드
        self.stock_code = stock_code
        self.chart_data = chart_data
        #환경 객체
        self.environment = Environment(chart_data)
        #에이전트 객체
        self.agent = Agent(self.environment,min_trading_unit=min_trading_unit,max_trading_unit=max_trading_unit,delayed_reward_threshold=delayed_reward_threshold)
        #학습 데이터
        self.training_data = training_data
        self.sample = None
        self.training_data_idx = -1
        #정책 신경망 : 입력크기 = 학습 데이터의 크기 + 에이전트 상태 크기
        self.num_features = self.training_data.shape[1] + self.agent.STATE_DIM
        self.policy_network = PolicyNetwork(input_dim=self.num_features,output_dim=self.agent.NUM_ACTIONS, lr=lr)
        #가시화 모듈
        self.visualizer = Visualizer()

    #에포크마다 호출하는 reset함수
    def reset(self):
        self.sample = None
        # 학습데이터를 읽어가면서 1씩 증가하는 변수
        self.training_data_idx = -1

    #max_memory 배치 학습 데이터를 만들기 위해 과거 데이터를 저장할 배열 balance 에이전트 초기 투자 자본금
    def fit(self,num_epoches=1000, max_memory=60, balance=1000000, discount_factor=0, start_epsilon=.5, learning= True):
        logger.info("LR:{lr}, DF : {discount_factor}, TU : [{min_trading_unit}, {max_trading_unit}],"
                    "DRT: {delayed_reward_threshold}".format(lr=self.policy_network.lr,discount_factor=discount_factor,min_trading_unit=self.agent.min_trading_unit,max_trading_unit=self.agent.max_trading_unit,
                                                             delayed_reward_threshold=self.agent.delayed_reward_threshold))

        #가시화 준비
        #ckxm 차트 데이터는 변하지 않음으로 미리 가시화
        self.visualizer.prepare(self.environment.chart_data)

        #가시화 결과 저장할 폴더 준비  폴더이름에 시간 날짜를 넣어 중복되는 일이 없도록 함
        epoch_summary_dir = os.path.join(settings.BASE_DIR,'epoch_summary/%s/epoch_summary_%s'%(self.stock_code,settings.timestr))
        if not os.path.isdir(epoch_summary_dir):
            os.makedirs(epoch_summary_dir)

        #에이전트 초기 자본금 설정
        self.agent.set_balance(balance)

        #학습에 대한 정보 초기화
        max_portfolio_value = 0
        epoch_win_cnt = 0

        # 학습 반복
        for epoch in range(num_epoches):
            #에포크 관련 정보 초기화
            loss = 0
            #수행한 에포크 수
            itr_cnt = 0
            #수익이 발생한 에포크 수
            win_cnt = 0
            #무작위 투자를 수행한 횟수
            exploration_cnt = 0
            batch_size = 0
            #수익이 발생하여 긍정적 지연 보상을 준 수
            pos_learning_cnt = 0
            #손실이 발생하여 부정적 지연 보상을 준 수
            neg_learning_cnt = 0

            #메모리 초기화
            memory_sample = []
            memory_action = []
            memory_reward = []
            memory_prob = []
            memory_pv = []
            memory_num_stocks = []
            memory_exp_idx = []
            memory_learning_idx = []

            #환경, 에이전트, 정책 신경망 초기화
            self.environment.reset()
            self.agent.reset()
            self.policy_network.reset()
            self.reset()

            #가시화기 초기화 --> 2,3,4 번 차트 초기화 및 x축차트 범위 파라미터로 삽입
            self.visualizer.clear([0,len(self.chart_data)])

            #학습을 진행할수록 탐험 비율 감소
            if learning:
                epsilon = start_epsilon*(1. - float(epoch)/(num_epoches -1))
            else:
                epsilon = 0

            #하나의 에포크를 수행하는 while
            while True:
                #샘플 생성
                next_sample = self._build_sample()
                if next_sample is None:
                    break

                #정책 신경망 또는 탐험에 의한 행동 결정  return 결정한 행동,결정에 대한 확신도, 무작위 투자 유무
                action, confidence, exploration = self.agent.decide_action(self.policy_network,self.sample,epsilon)

                #결정한 행동을 수행하고 즉시 보상과 지현 보상 획득
                immediate_reward, delayed_reward = self.agent.act(action,confidence)

                #행동 및 행동에 대한 결과를 기억
                memory_sample.append(next_sample)
                memory_action.append(action)
                memory_reward.append(immediate_reward)
                memory_pv.append(self.agent.portfolio_value)
                memory_num_stocks.append(self.agent.num_stocks)
                #학습 데이터의 샘플,에이전트 행동,즉시보상,포트폴리오 가치, 보유 주식수를 저장--> 위에 추가한것들 모아2차원 배열 생성
                memory = [( memory_sample[i],memory_action[i],memory_reward[i])
                          for i in list(range(len(memory_action)))[-max_memory:]]
                if exploration:
                    #무작위 투자인 경우
                    memory_exp_idx.append(itr_cnt)
                    #정책 신경망의 출력을 그대로 저장하는 배열
                    memory_prob.append([np.nan]*Agent.NUM_ACTIONS)
                else:
                    #정책 신경망의 출력을 그대로 저장
                    memory_prob.append(self.policy_network.prob)

                #반복에 대한 정보 갱신
                batch_size += 1
                itr_cnt += 1
                #탐험을 한경우에만 증가
                exploration_cnt += 1 if exploration else  0
                # 지연 보상이 0보다 큰경우에만 1을 증가
                win_cnt += 1 if delayed_reward > 0 else 0

                #지연 보상이 발생한 경웨 학습을 수행하는 부분
                #학습 모드이고 지연 보상이 존재할 경우 정책 신경망 갱신
                if delayed_reward == 0 and batch_size >= max_memory:
                    delayed_reward = immediate_reward
                if learning and delayed_reward != 0:
                    #배치 학습 데이터 크기  max_memory보다 작아야 함
                    batch_size = min(batch_size, max_memory)
                    #배치 학습 데이터 생성
                    x, y = self._get_batch(memory,batch_size,discount_factor,delayed_reward)

                    if len(x) >0:
                        #긍부정 학습횟수 체크
                        if delayed_reward > 0:
                            pos_learning_cnt += 1
                        else:
                            neg_learning_cnt += 1
                        #정책 신경망 갱신
                        # print("x : ",x)
                        # print("y : ",y)
                        # print("len(x) : ",len(x))
                        # print("len(y) : ",len(y))
                        # print("type(x): ",type(x))
                        # print("type(y): ", type(y))
                        # print("loss : ",loss)
                        # 1.123123124
                        # [12.23,54.3]
                        loss += self.policy_network.train_on_batch(x,y)
                        memory_learning_idx.append([itr_cnt,delayed_reward])
                    batch_size =0

            # 에포크 관련 정보 가시화
            #총에포크수 문자열 길이 체크 ex 1000번이면 4
            num_epoches_digit = len(str(num_epoches))
            #현제 에포크수를 num_epoches_digit 자릿수로 만들어줌 4이고 1epoch 이면 0001 이런식으로
            epoch_str = str(epoch + 1).rjust(num_epoches_digit, '0')

            self.visualizer.plot(
                #가시화기의 plot함수를 호출하여 에포크 수행 결과를 가시화
                epoch_str=epoch_str, num_epoches=num_epoches, epsilon=epsilon,
                action_list=Agent.ACTIONS, actions=memory_action,
                num_stocks=memory_num_stocks, outvals=memory_prob,
                exps=memory_exp_idx, learning=memory_learning_idx,
                initial_balance=self.agent.initial_balance, pvs=memory_pv
            )
            #수행 결과를 파일로 저장
            self.visualizer.save(os.path.join(
                epoch_summary_dir, 'epoch_summary_%s_%s.png' % (
                    settings.timestr, epoch_str)))

            # 에포크 관련 정보 로그 기록
            if pos_learning_cnt + neg_learning_cnt > 0:
                loss /= pos_learning_cnt + neg_learning_cnt
            logger.info("[Epoch %s/%s]\tEpsilon:%.4f\t#Expl.:%d/%d\t"
                        "#Buy:%d\t#Sell:%d\t#Hold:%d\t"
                        "#Stocks:%d\tPV:%s\t"
                        "POS:%s\tNEG:%s\tLoss:%10.6f" % (
                            epoch_str, num_epoches, epsilon, exploration_cnt, itr_cnt,
                            self.agent.num_buy, self.agent.num_sell, self.agent.num_hold,
                            self.agent.num_stocks,
                            locale.currency(self.agent.portfolio_value, grouping=True),
                            pos_learning_cnt, neg_learning_cnt, loss))

            # 학습 관련 정보 갱신
            max_portfolio_value = max(
                max_portfolio_value, self.agent.portfolio_value)
            if self.agent.portfolio_value > self.agent.initial_balance:
                epoch_win_cnt += 1

        # 최종 학습 결과 통꼐 정보 로그 기록 부분
        logger.info("Max PV: %s, \t # Win: %d" % (
            locale.currency(max_portfolio_value, grouping=True), epoch_win_cnt))



    #미니 배치 데이터 생성 함수 부분
    def _get_batch(self, memory, batch_size, discount_factor, delayed_reward):
        #일련의 학습 데이터 및 에이전트 상태 배치 데이터 크기,학습 데이터 특징 크기 2차원
        x = np.zeros((batch_size, 1, self.num_features))
        # 일련의 지연보상 데이터 크기, 정책 신경ㅁ아의 결정하는 에이전트의 행동의 수  2차원
        # 배치 데이터 ㅡ키근ㄴ 지연 보상이 발생될 때 결정되기 때문에 17과 2로 고정
        y = np.full((batch_size, self.agent.NUM_ACTIONS), 0.5)

        for i, (sample, action, reward) in enumerate(
                reversed(memory[-batch_size:])):
            x[i] = np.array(sample).reshape((-1, 1, self.num_features))
            y[i, action] = (delayed_reward + 1) / 2
            if discount_factor > 0:
                y[i, action] *= discount_factor ** i
        return x, y

    #학습 데이터 샘플 생성 부분
    def _build_sample(self):
        # 차트 데이터의 현제 인덱스에서 다음 인덱스 데이터를 읽음
        self.environment.observe()
        #학습 데이터의 다음 인덱스가 존재하는지 확인
        if len(self.training_data) > self.training_data_idx + 1:
            self.training_data_idx += 1
            self.sample = self.training_data.iloc[self.training_data_idx].tolist()
            self.sample.extend(self.agent.get_states())
            return self.sample
        return None

    # 투자 시뮬레이션을 하는 trade 함수
    def trade(self, model_path=None, balance=2000000):
        if model_path is None:
            return
        self.policy_network.load_model(model_path=model_path)
        self.fit(balance=balance, num_epoches=1, learning=False)
コード例 #12
0
class PolicyLearner:

    def __init__(self, stock_code, chart_data, training_data=None,
                 min_trading_unit=1, max_trading_unit=2,
                 delayed_reward_threshold=0.05, l_rate=0.01):
        self.stock_code = stock_code 
        self.chart_data = chart_data

        # 환경 객체
        self.environment = Environment(chart_data)
        
        # 에이전트 객체
        self.agent = Agent(self.environment,
                           min_trading_unit=min_trading_unit,
                           max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold)

        # 학습 데이터
        self.training_data = training_data 
        self.sample = None      # 여기서 sample도 training_data와 같이 17차원
        self.training_data_idx = -1

        # 정책 신경망의 Input layer에 들어오는 입력의 크기 또는 특징의 수(17) = 학습 데이터의 크기(15) + 에이전트 상태의 크기(2)
        # TODO self.training_data.shape의 [1]이 왜 학습 데이터의 크기인지 확인
        # TODO <해결> shape가 2차원이면 (n,15)일 것이고, 여기서 행은 전체 갯수이고, 열의 갯수가 학습 데이터의 크기로 들어갈 특징의 갯수일 것이다.
        #            shape가 2차원인 이유는 policy_network.py에서 설명했다.
        #              --> "Sequential 클래스의 predict() 함수는 여러 샘플을 한번에 받아서 신경망의 출력을 반환한다. 
        #                   하나의 샘플에 대한 결과만을 받고 싶어도 입력값을 샘플의 배열로 구성해야하기 때문에 2차원 배열로 재구성한다."
        self.num_features = self.training_data.shape[1] + self.agent.STATE_DIM
        # 정책 신경망 객체
        self.policy_network = PolicyNetwork(
            input_dim=self.num_features, output_dim=self.agent.NUM_ACTIONS, l_rate=l_rate)
        
        # 가시화기 객체 (에포크마다 투자 결과 가시화)
        self.visualizer = Visualizer()  


    # 에포크마다 호출하여 reset
    def reset(self):
        self.sample = None              # 읽어들인 데이터는 self.sample에 저장된다. 단, 이 초기화 단계에서는 읽어들인 데이터가 없으므로 None값을 갖는다.
        self.training_data_idx = -1     # 학습 데이터를 다시 처음부터 읽기 위해 -1로 재설정 (학습 데이터를 읽어가며 이 값은 1씩 증가하는데, 읽어 온 데이터는 self.sample에 저장된다.)


    # 학습 데이터 샘플 생성
    def _build_sample(self):
        # TODO self.environment.observe() 함수는 특정 행의 데이터를 전부(D,O,H,L,C,V) 반환한다. --> 그런데 반환을 받아주는 변수가 없다?
        #   --> 얘는 여기가 아니라 visualizer의 axes[0]을 그릴 때 필요한 것 같다. 그러나 심지어 거기서도 얘를 가공해서 쓰지않고 바로 chart_data에서 ohlcv를 가져와서 쓴다. --> 이 함수를 만든 이유를 파악할 수 없다.
        #       (envrironment.py의 get_price()가 사용하긴 한다. --> 근데 얘는 observe() 함수에서 갱신된 self.observation 값을 사용하려는 것이 목적이지 observe() 함수의 반환값은 필요없다.)
        #   --> 여기서 observe()를 사용하는 이유는 이 함수를 호출하여 차트 데이터의 현재 인덱스에서 다음 인덱스를 읽어들이기 위함이다. --> 이 목적은 알겠으나, self.obseravtion을 반환하는 이유를 알 수 없다.
        self.environment.observe()      # 환경 객체의 observe() 함수를 호출하여, 차트 데이터의 현재 인덱스에서 다음 인덱스 데이터를 읽도록 한다.
        if len(self.training_data) > self.training_data_idx + 1:        # 학습 데이터의 다음 인덱스가 존재하는지 확인
            self.training_data_idx += 1
            # TODO sample은 policy_network.py에서 2차원으로 만들어주는 것이므로 여기서는 1차원 형태가 맞는지 확인 (※ 여기서 sample의 길이는 15이고, 아래에서 에이전트 상태가 추가되어야 feature 길이가 17이 되는 것이다. --> 이는 training_data의 feature 길이 역시 15[-1,15]라는 뜻이다.)
            self.sample = self.training_data.iloc[self.training_data_idx].tolist()
            self.sample.extend(self.agent.get_states())     # 현재까지 sample 데이터는 15개의 값으로 구성되어 있는데,extend()를 통해
            return self.sample                              # sample에 에이전트 상태(2개)를 추가하여 17개의 값으로 구성되도록 한다.
        return None

    
    # TODO memory, sample, 그리고  batch_size가 정확히 무엇인지 알아야한다.
    # TODO <해결> memory = [(memory_sample[i], memory_action[i], memory_reward[i]) for i in list(range(len(memory_action)))[-max_memory:]]
    #                   =  즉 (각 거래일 idx마다의 데이터가 모여 만들어진) 학습 데이터 sample 배열 / 에이전트의 action 배열 / 즉시 reward 배열을 모아서 만든 2차원 배열
    #                   ex) memory_sample = [1,2,3,4,5]
    #                       memory_action = [6,7,8,9,10]
    #                       memory_reward = [11,12,13,14,15]
    #                       memory = [(memory_sample[i], memory_action[i], memory_reward[i]) for i in list(range(len(memory_action)))]
    #                       memory = [(1, 6, 11), (2, 7, 12), (3, 8, 13), (4, 9, 14), (5, 10, 15)]
    #                       |
    #            sample: training_data가 저장되므로 15(학습데이터)+2(상태데이터) 총 17개의 데이터를 갖는 데이터)
    #            batch_size: 배치 데이터의 크기는 지연 보상이 발생할 때 결정되기 때문에 매번 다르다.
    # 미니 배치 데이터 생성
    def _get_batch(self, memory, batch_size, discount_factor, delayed_reward):
        # TODO 아래 x의 np.zeros를 3차원이 아닌 2차원으로 만들면 안되나? --> np.zeros((batch_size, self.num_features))로.
        # TODO <해결??> 가운데 1을 없애고 2차원으로 만드는게 맞다. <-- 책을 보면 "x 배열의 형태는 배치 데이터 크기, 학습 데이터 특징 크기로 2차원으로 구성된다."가 맞다.
        # TODO <해결 아님> 가운데 1을 없애면, "expected lstm_1_input to have 3 dimensions, but got array with shape (60, 17)"와 같은 에러가 발생한다. 가운데 1이 있어야 한다. 
        # x = np.zeros((batch_size, self.num_features))
        x = np.zeros((batch_size, 1, self.num_features))            # 일련의 학습 데이터(15) 및 에이전트 상태(2) (np.zeros((1,2,3)) --> array([[[0., 0., 0.],[0., 0., 0.]]])이 된다. (인자는 튜플로 넘겨야한다. )
        y = np.full((batch_size, self.agent.NUM_ACTIONS), 0.5)      # 일련의 지연 보상 (np.full() --> 첫 번째 인자는 shape이고(다차원일 경우 튜플로 넘겨야한다.), 두 번째 인자 값으로 배열을 채운다.

        # 배치 데이터의 크기는 지연 보상이 발생할 때 결정되기 때문에 매번 다른 반면, 학습 데이터 특징의 크기(17)와 에이전트 행동 수(2)는 고정되어있다.

        # TODO for문의 'reward'가 Unused variable이다. --> 확인 필수
        for i, (sample, action, reward) in enumerate(       # action은 0(매수)과 1(매도)로 이루어져있다.
                reversed(memory[-batch_size:])):
            x[i] = np.array(sample).reshape((-1, 1, self.num_features))     # 학습 데이터 특징 벡터를 지정하고,
            y[i, action] = (delayed_reward + 1) / 2                         # 지연 보상을 정답(=Label)으로 설정하여 학습/훈련 데이터 셋을 구성 --> 지연 보상이 1인 경우 1로 레이블을 지정하고, 지연 보상이 -1인 경우 0으로 레이블을 지정한다.
            if discount_factor > 0:
                y[i, action] *= discount_factor ** i    # 더 과거에 더 큰 할인요인을 곱한다.
        return x, y


    # fit() 메서드: PolicyLearner 클래스의 핵심 함수
    """
    fit()의 Elements
        max_memory:      배치(batch) 학습 데이터를 만들기 위해 과거 데이터를 저장할 배열 (이 배열의 크기가 배치 학습 데이터의 크기와 같은지 확인)
        
        balance:         에이전트의 초기 투자 자본금을 정해주기 위한 인자
        
        discount_factor: !**지연 보상이 발생했을 때, 그 이전 지연 보상이 발생한 시점과 현재 지연 보상이 발생한 시점 사이에서 수행한 행동들 전체에 현재의! 지연 보상을 적용한다.
                         이때 과거로 갈수록 현재 지연 보상을 적용할 판단 근거가 흐려지기 때문에, 먼 과거의 행동일수록 할인 요인(discout factor)을 적용하여 지연 보상을 약하게 적용한다.**!
                         
        start_epsilon:   초기 탐험 비율 (학습이 되어 있지 않은 초기에 탐험 비율을 크게 해서 더 많은 탐험, 즉 무작위 투자를 수행하도록 해야한다. 이러한 탐험을 통해 특정 상황에서
                         좋은 행동과 그렇지 않은 행동을 결정하기 위한 경험을 쌓는다.) (탐험을 통한 학습이 지속적으로 쌓이게되면 탐험 비율을 줄여나간다.)
                         
        learning:        학습 유무를 정하는 boolean 값 (학습을 마치면 학습된 정책 신경망 모델이 만들어지는데, 이렇게 학습을 해서 정책 신경망 모델을 만들고자 한다면 learning을 True로,
                         이미 학습된 모델을 가지고 투자 시뮬레이션만 하려 한다면 False로 준다.)
    """
    def fit(
        self, num_epoches=1000, max_memory=60, balance=10000000,
        discount_factor=0, start_epsilon=.5, learning=True):
        logger.info("Learning Rate: {l_rate}, Discount Factor: {discount_factor}, "
                    "Trading Unit[min,max]: [{min_trading_unit}, {max_trading_unit}], "
                    "Delayed Reward Threshold: {delayed_reward_threshold}".format(
            l_rate=self.policy_network.l_rate,
            discount_factor=discount_factor,
            min_trading_unit=self.agent.min_trading_unit,
            max_trading_unit=self.agent.max_trading_unit,
            delayed_reward_threshold=self.agent.delayed_reward_threshold
        ))

        # 가시화 준비
        # 차트 데이터는 변하지 않으므로 미리 (한 번만) 가시화
        self.visualizer.prepare(self.environment.chart_data)

        # 가시화 결과를 저장할 폴더 준비
        epoch_summary_dir = os.path.join(
            settings.BASE_DIR, 'epoch_summary/%s/epoch_summary_%s' % (     # settings.BASE_DIR: 프로젝트 관련 파일들이 포함된 기본/루트 폴더 경로를 말한다.
                self.stock_code, settings.timestr))     # settings.timestr: 폴더 이름에 포함할 날짜와 시간 - 문자열 형식: %Y%m%d%H%M%S
        # epoch_summary_dir = os.path.join(         # --> 이렇게 코드를 작성하면 윈도우 계열, 리눅스 계열 OS에 상관없이 경로를 구성할 수 있다.
        #   settings.BASE_DIR, 'epoch_summary', '%s' % self.stock_code,
        #                      'epoch_summary_%s' % settings.timestr)   
        if not os.path.isdir(epoch_summary_dir):
            os.makedirs(epoch_summary_dir)

        # 에이전트 초기 자본금 설정
        self.agent.set_balance(balance)

        # 학습에 대한 정보 초기화
        max_portfolio_value = 0     # max_portfolio_value: 학습 과정에서 달성한 최대 포트폴리오 가치를 저장하는 변수
        epoch_win_cnt = 0           # epoch_win_cnt: 수익이 발생한 에포크 수를 저장하는 변수

        ### 학습 반복 for문 시작 ###
        for epoch in range(num_epoches):    
            # 에포크 관련 정보 초기화
            loss = 0.               # 정책 신경망의 결과가 학습/실제 데이터와 얼만큼 차이가 있는지 저장
            # TODO itr_cnt와 win_cnt는 에포크 수를 (카운트해서) 저장하는게 아니라, 거래일 idx를 (카운트해서) 저장하는 변수가 맞는 듯 하다. (심지어 win_cnt는 이 주석 위에 epoch_win_cnt가 따로 있다.)
            itr_cnt = 0             # 수행한 에포크 수를 저장 (반복 카운팅 횟수)
            win_cnt = 0             # 수행한 에포크 중에서 수익이 발생한 에포크 수를 저장 - 즉 포트폴리오 가치가 초기 자본금보다 높아진 에포크 수를 저장
            exploration_cnt = 0     # 무작위 투자(탐험)를 수행한 횟수를 저장
            batch_size = 0          # batch의 크기
            pos_learning_cnt = 0    # 수익이 발생하여 긍정적 지연 보상을 준 횟수
            neg_learning_cnt = 0    # 손실이 발생하여 부정적 지연 보상을 준 수

            # 메모리 초기화
            # TODO Q_1.각 변수가 어떤 값과 모양을 갖는지 잘 모르겠음, Q_2.특히 탐험 위치 및 학습 위치, 정확히 어느 위치인지 잘 모르겠음 --> 체크 필수!
            # TODO <해결> A_1. 우선 memory가 붙은 변수들 중 위 6개의 범위는 Visualizer에서 만든 x축이 갖는 범위만큼이며,
            #                 x축이 갖는 각 idx에서의 샘플 데이터값, 행동값, 즉시보상값, 정책 신경망의 출력값, 포트폴리오의 가치값, 보유 주식 수 값을 각각 가진다.
            #                 즉 쉽게 말해 x축의 범위인 거래일자마다의 샘플 데이터값, 행동값, 즉시보상값, 정책 신경망의 출력값, 포트폴리오의 가치값, 보유 주식 수 값을 가지는 것이다.
            #            A_2. 탐험 위치와 학습 위치 역시 범위가 x축이 갖는 범위 만큼인 것은 맞지만, 이 둘은 x축이 갖는 범위만큼의 각 idx들 중에서 탐험한 idx와학습한 idx만을 선택해서 가지는 것이다.
            memory_sample = []          # 샘플
            memory_action = []          # 행동
            memory_reward = []          # 즉시 보상
            memory_prob = []            # 정책 신경망의 출력
            memory_pv = []              # 포트폴리오 가치
            memory_num_stocks = []      # 보유 주식 수

            memory_exp_idx = []         # 탐험 위치
            memory_learning_idx = []    # 학습 위치     # TODO memory_learning_idx가 무엇인지 정확히 파악 후 visualizer.py의 151번 줄 TODO 해결할 것 (151번줄에서 얘(memory_learning_idx == visualizer.py의 learning) 때문에 막힘)
                                                      # TODO <해결> 지연 보상(으로 학습한) 위치와 지연 보상 값으로 이루어진 배열 --> [[지연 보상 위치, 지연 보상 값], [지연 보상 위치, 지연 보상 값], [지연 보상 위치, 지연 보상 값], ...]
                                                      #            이 학습 위치 변수의 이름을 바꿔야 할 필요가 있는 것 같다. --> 아래에서 memory_learning_idx.append([itr_cnt, delayed_reward])가 실행되므로 memory_learning_idx_val로 바꾼다.
            
            # 아래 코드를 보면 알겠지만 첫 에포크를 포함하여 에포크가 시작할 때마다 우선 모두 초기화(reset & clear)부터 하고 진행한다.
            # 환경, 에이전트, 정책 신경망, 정책 학습기 초기화 (이 클래스들은 각자 reset 함수를 정의했었음 - 다른 클래스들은 따로 reset 메서드를 정의하지 않았음)
            self.environment.reset()                                                    # (cf. Visualizer는 clear 함수로 초기화 함)
            self.agent.reset()
            self.policy_network.reset()
            self.reset()

            # 가시화 초기화
            self.visualizer.clear([0, len(self.chart_data)])

            # 학습을 진행할 수록 탐험 비율 감소
            if learning:
                epsilon = start_epsilon * (1. - float(epoch) / (num_epoches - 1))       # epoch는 학습을 반복하며 계속 1씩 증가할 것 --> epsilon은 계속 감소한다.
                # e = 1. / ((episode / 10) + 1)                                         # epoch / num_epoches는 학습 진행률을 의미하며,
            else:                                                                       # num_epoches에서 1을 빼는 이유는 0부터 시작하기 때문이다. (표기는 1부터 시작하지만 실제 학습시에는 0부터 시작)
                epsilon = 0

            # (현재 for문 속에서 선택된) 하나의 Epoch에 대해 돌아가는 while문
            while True:
                # 샘플 생성
                next_sample = self._build_sample()      # 행동을 결정하기 위한 데이터인 샘플 준비
                if next_sample is None:                 # next_sample이 None이라면 마지막까지 데이터를 다 읽은 것이므로 반복문을 종료한다.
                    break

                # 탐험 또는 정책 신경망에 의한 행동 결정 (E-Greedy: Exploration v.s Exploitation)
                # decide_action()의 반환값 3가지: --> action: 결정한 행동, confidence: 결정(된 행동)에 대한 확신도, exploration: 무작위 투자 유무 (반환 값 중 action은 매수 또는 매도 둘 중 하나가 된다. 관망 없음 - 이유는 5줄 아래 주석 참고)
                action, confidence, exploration = self.agent.decide_action(     # Exploitation, 즉 정책 신경망의 출력은 매수, 매도를 했을 때의 포트폴리오 가치를 높일 확률을 의미한다.
                    self.policy_network, self.sample, epsilon)                  # 즉 매수에 대한 정책 신경망의 출력이 매도에 대한 출력보다 높으면 매수를, 그 반대의 경우에는 매도를 선택한다.

                # 결정한 행동을 수행하고 "즉시 보상"과 "지연 보상" 획득
                immediate_reward, delayed_reward = self.agent.act(action, confidence)       # agent 모듈에 가서 act 함수를 보면 알겠지만, (일단 이 프로젝트에서 관망은 매수 및 매도가 불가능한 경우에만
                                                                                            # action이 hold가 된다. 따라서) decide_action 함수에서 일단 action이 매수 또는 매도로 결정되고,
                                                                                            # act 함수 초반에 매수/매도 상황이 불가능한 경우인지 아닌지, 즉 관망인지 아닌지 아닌지를 판단하게 된다.
                                                                                            # 따라서 agent의 decide_action 함수가 반환하는 action은 우선 매수 또는 매도가 맞으며,
                                                                                            # 그 뒤에 act 함수가 호출 됐을 때, 이 함수의 초반에 validate 검사에 합격하여 실제로 행해지는 action이
                                                                                            # 그 매수 또는 매도가 될지, 혹은 검사에 실패하여 관망이 될지의 여부가 결정된다.

                # 행동 및 행동에 대한 결과를 기억
                # 아래의 메모리 변수들은 2가지 목적으로 사용된다. --> 1) 학습에서 배치 학습 데이터로 사용  2) Visualizer에서 차트를 그릴 때 사용
                memory_sample.append(next_sample)
                memory_action.append(action)
                memory_reward.append(immediate_reward)
                memory_pv.append(self.agent.portfolio_value)
                memory_num_stocks.append(self.agent.num_stocks)
                memory = [(
                    memory_sample[i],            # memory: 위의 항목들(학습 데이터 sample 배열 / 에이전트의 action 배열 / 즉시 reward 배열)을 모아서 하나의 2차원 배열로 만든다.
                    memory_action[i],
                    memory_reward[i])
                    for i in list(range(len(memory_action)))[-max_memory:]        # -max_memory의 수만큼을 반복시킨다. (max_memory가 60이므로 [-60:]은 60개이다.
                ]
                """
                위의 memory 부분 참고 (다시 볼 때 이해한 후 지울 것)

                *** [-max_memory:] 부분 --> 어렵게 생각하지 말 것
                a = np.array([1,2,3,4,5])
                a[-5:] --> 1,2,3,4,5
                a[-5:-1] --> 1,2,3,4
                a[-4:] --> 2,3,4,5

                *** memory가 3차원이 아닌 2차원인 이유 --> 제대로 볼 것
                배열만으로 이루어진 2차원 == [[], [], ...]
                배열과 튜플로 이루어진 2차원 == [(), (), ...]
                즉 memory는 [] 안에 여러 개의 ()이 반복문으로 들어간다.
                최종적으로 memory의 shape는 [None, 3] (== [i+1, 3]) 이다.
                """

                if exploration:
                    # TODO itr_cnt가 수행한 에포크 수(반복 카운팅 횟수)를 저장하는 변수인데, 현재의 인덱스라고? --> 체크 필수! (에포크 수(돌아가는 에포크의 넘버)를 인덱스로 가져가는 건가? --> 체크 필수!
                    #       --> 에포크 수를 세는 변수가 아니라 거래일 idx를 세는 변수가 맞는 것 같다.
                    memory_exp_idx.append(itr_cnt)      # 무작위 투자로 행동을 결정한 경우, memory_exp_idx에 현재의 인덱스를 저장한다.
                    memory_prob.append([np.nan] * Agent.NUM_ACTIONS)        # memory_prob는 정책 신경망의 출력을 그대로 저장하는 배열인데, 무작위 투자에서는 정책 신경망의 출력이 없기 때문에 nan값을 준다.
                                                                            # [np.nan] * 2이므로 [nan, nan]이 된다. --> [매수확률이 nan, 매도확률이 nan]
                else:
                    memory_prob.append(self.policy_network.prob)            # 무작위 투자가 아닌 경우, 정책 신경망의 출력을 그대로 memory_prob에 저장한다. (정책 신경망의 출력: [매수확률, 매도확률])

                # 학습 함수의 반복에 대한 정보 갱신
                # TODO batch_size를 왜 1씩 증가시키지?? 내가 알고있는 batch의 개념이 아닌가? --> 체크 필수!
                batch_size += 1
                # TODO itr_cnt가 에포크 수를 세는 변수라고 해놓고 왜 한 에포크 내에서 반복되는 횟수(x축의 길이)를 카운트하고 있는가?
                #   --> 에포크 수를 세는 변수가 아니라 거래일 idx를 세는 변수가 맞는 것 같다.
                #       (exploration_cnt와 win_cnt도 마찬가지 - 다만 itr_cnt는 모든 횟수를 세기 때문에 index 역할도 되는 것이고, ex_cnt와 win_cnt는 특정 상황에만 카운트 되므로 횟수에 불과하다.)
                itr_cnt += 1
                exploration_cnt += 1 if exploration else 0      # (한 에포크 내에서 발생하는) 탐험 횟수 저장
                win_cnt += 1 if delayed_reward > 0 else 0       # (한 에포크 내에서 발생하는) 지연 보상 임계치를 초과하는 수익이 났으면 이전에 했던 행등들을 잘했다고 판단하여 긍정적으로(positive) 학습한다.

                # TODO 이 부분부터 마지막 끝까지 다시 이해하기 (헷갈림) --> 체크 필수!!!
                # 학습 함수의 정책 신경망 학습 부분: 지연 보상이 발생한 경우 학습을 수행하는 부분을 보여준다.
                # 학습 모드이고 지연 보상이 존재할 경우 정책 신경망 갱신
                if delayed_reward == 0 and batch_size >= max_memory:    # 지연 보상이 있어야 그때까지의 데이터를 모아 한 번에 batch 사이즈로 학습을 할 수 있다. 그런데 지연 보상이 없다. 이러면 학습한 것도 없는 상황인데,
                    delayed_reward = immediate_reward                   # 메모리마저 최대 크기만큼 다 찼다면, 지연 보상을 즉시 보상으로 대체하여 학습을 진행한다.
                    self.agent.base_portfolio_value = self.agent.portfolio_value
                if learning and delayed_reward != 0:        # 지연 보상이 발생한 경우
                    # 배치 학습 데이터 크기 (배치 학습에 사용할 배치 데이터 크기 결정)
                    batch_size = min(batch_size, max_memory)        # 배치 데이터 크기는 memory 변수의 크기인 max_memory 보다 작아야 한다.
                    # 배치 학습 데이터 생성
                    x, y = self._get_batch(
                        memory, batch_size, discount_factor, delayed_reward)     # _get_batch() 함수를 통해 배치 데이터를 준비한다.
                    if len(x) > 0:
                        if delayed_reward > 0:      # 로그 기록을 위해 긍정 학습과 부정 학습 횟수를 카운트
                            pos_learning_cnt += 1
                        else:
                            neg_learning_cnt += 1
                        # 정책 신경망 갱신
                        loss += self.policy_network.train_on_batch(x, y)        # 준비한 배치 데이터로 학습 진행 --> 학습은 정책 신경망 객체의 train_on_batch() 함수로 수행한다.
                        memory_learning_idx.append([itr_cnt, delayed_reward])
                    batch_size = 0
                    # TODO 위의 batch 사이즈는 내가 아는 batch의 개념이 아닌 듯함 --> 왜 0으로 초기화를 하는지? 체크 필수!
                    # TODO <해결> 책 p.56 읽어볼 것 (내가 아는 batch의 개념이 맞기는 한데, 조건이 걸린 batch이다.)
                    #            ==> "주식투자를 하면서 지연 보상을 줄 수 있을지 판단한다. 즉 투자를 하다가 임계치를 넘는 이익이나 손실이 발생하면 이때까지의 상황과 행동들을 학습 데이터로 생성한다.
                    #                 그리고 이 학습 데이터들을 한 번에 적용하여 정책 신경망을 업데이트 하는데, 이를 배치(batch) 학습 방법이라 한다."
                    #                 ※ "배치 데이터의 크기는 지연 보상이 발생될 때 결정되기 때문에 매번 다르다."

            # 에포크 관련 정보 가시화
            num_epoches_digit = len(str(num_epoches))
            epoch_str = str(epoch + 1).rjust(num_epoches_digit, '0')    # ex. epoch가 50이고, num_epoches_digit이 4라면 --> 0050이 된다.

            # 에포크 수행 결과 가시화
            self.visualizer.plot(
                epoch_str=epoch_str, num_epoches=num_epoches, epsilon=epsilon,
                action_list=Agent.ACTIONS, actions=memory_action,
                num_stocks=memory_num_stocks, outvals=memory_prob,
                exps=memory_exp_idx, learning=memory_learning_idx,
                initial_balance=self.agent.initial_balance, pvs=memory_pv
            )
            # 가시화한 에포크 수행 결과 저장
            self.visualizer.save(os.path.join(
                epoch_summary_dir, 'epoch_summary_%s_%s.png' % (
                    settings.timestr, epoch_str)))

            # 에포크 관련 정보 로그 기록
            if pos_learning_cnt + neg_learning_cnt > 0:
                loss /= pos_learning_cnt + neg_learning_cnt
            logger.info("[Epoch %s/%s]\tEpsilon:%.4f\t#Expl.:%d/%d\t"       # #Expl. 은 "무작위 투자를 수행한 횟수 / 수행한 에포크 수(반복 카운팅 횟수)" 이다.
                        "#Buy:%d\t#Sell:%d\t#Hold:%d\t"
                        "#Stocks:%d\tPV:%s\t"
                        "POS:%s\tNEG:%s\tLoss:%10.6f" % (
                            epoch_str, num_epoches, epsilon, exploration_cnt, itr_cnt,
                            self.agent.num_buy, self.agent.num_sell, self.agent.num_hold,
                            self.agent.num_stocks,
                            locale.currency(self.agent.portfolio_value, grouping=True),
                            pos_learning_cnt, neg_learning_cnt, loss))

            # 학습 관련 (통계) 정보 갱신
            max_portfolio_value = max(      # max_portfolio_value의 초기값은 0이다. --> 에포크를 늘려가며 max포트폴리오 가치가 갱신되는 것.
                max_portfolio_value, self.agent.portfolio_value)
            if self.agent.portfolio_value > self.agent.initial_balance:
                epoch_win_cnt += 1

        ### 학습 반복 for문 종료 ###  

        # 최종 학습 (결과) 관련 정보 로그 기록
        logger.info("Max PV: %s, \t # Win: %d" % (
            locale.currency(max_portfolio_value, grouping=True), epoch_win_cnt))

        ##### 여기까지가 fit() 함수의 영역이다. #####


    # (학습된 정책 신경망으로) 투자 시뮬레이션 진행
    def trade(self, model_path=None, balance=2000000):
        if model_path is None:
            return
        self.policy_network.load_model(model_path=model_path)  # 학습된 정책 신경망 모델을 정책 신경망 객체의 load_model로 적용시킨다.
        self.fit(balance=balance, num_epoches=1, learning=False)
コード例 #13
0
class PolicyLearner:
    def __init__(self,
                 stock_code,
                 chart_data,
                 training_data=None,
                 min_trading_unit=1,
                 max_trading_unit=2,
                 delayed_reward_threshold=.05,
                 lr=0.01):
        self.stock_code = stock_code  # stock code
        self.chart_data = chart_data
        self.environment = Environment(chart_data)  # Environment object
        # agent object
        self.agent = Agent(self.environment,
                           min_trading_unit=min_trading_unit,
                           max_trading_unit=max_trading_unit,
                           delayed_reward_threshold=delayed_reward_threshold)
        self.training_data = training_data  # learn data
        self.sample = None
        self.training_data_idx = -1
        # Policy neural network ; input size=learn data size + agent state size
        self.num_features = self.traning_data_shape[1] + self.agent.STATE_DIM
        self.policy_network = PolicyNetwork(input_dim=self.num_features,
                                            output_dim=self.agent.NUM_ACTIONS,
                                            lr=lr)
        self.visualizer = Visualizer()  # visual module

    def reset(self):  # Epoches reset function.
        self.sample = None
        self.training_data_idx = -1

    def fit(
            self,
            num_epoches=1000,
            max_memory=60,
            balance=1000000,  # Total repeat learning number = num epoches. learning is learning boolean value.
            discount_factor=0,
            start_epsilon=.5,
            learning=True
    ):  # Decided seed moeney. Epsilon is first explore rate.
        logger.info(
            "LR: {lr}, DF:{discount_factor},"  # Decided discount discount factor
            "TU=[{min_trading_unit}, {max_trading_unit}], "
            "DRT:{delayed_reward_threshold}".format(
                lr=self.policy_network.lr,
                discount_factor=discount_factor,
                min_trading_unit=self.agent.min_trading_unit,
                max_trading_unit=self.agent.max_trading_unit,
                delayed_reward_threshold=self.agent.delayed_reward_threshold))

        # Visualize ready
        # Already visualize because Chart data is not change
        self.visualizer.prepare(self.environment.chart_data)

        # Ready for save folder of Result for visualize
        epoch_summary_dir = os.path.join(
            settings.BASE_DIR, 'epoch_summary/%s/epoch_summary_%s' %
            (self.stock_code, settings.timestr))
        if not os.path.isdir(epoch_summary_dir):
            os.makedirs(epoch_summary_dir)

        # Setting for agent Seed money
        self.agent.set_balance(balance)

        # Information initialize for learn
        max_portfolio_value = 0
        epoch_win_cnt = 0

        # Learn repeat
        for epoch in range(num_epoches):
            # initialize epoches
            loss = 0.
            itr_cnt = 0
            win_cnt = 0
            exploration_cnt = 0
            batch_size = 0
            pos_learning_cnt = 0
            neg_learning_cnt = 0

            # initialize memory
            memory_sample = []
            memory_action = []
            memory_reward = []
            memory_prob = []
            memory_pv = []
            memory_num_stocks = []
            memory_exp_idx = []
            memory_learning_idx = []

            # Initialize environment, agent, policy neural network
            self.enviroment.reset()
            self.agent.reset()
            self.policy_network.reset()
            self.reset()

            # Visualizer reset
            self.visualizer.clear([0, len(self.chart_data)])

            # continued learn so decrease of explore rate.
            if learning:
                epsilon = start_epsilon * (1. - float(epoch) /
                                           (num_epoches - 1))
            else:
                epsilon = 0

            while True:
                # Create sample
                next_sample = self._bulid_sample()
                if next_sample is None:
                    break

                # Decision by Policy neural network or explore
                action, confidence, exploration = self.agent.decide_action(
                    self.policy_network, self.sample, epsilon)

                # Immediately gets bonus and delay bonus after Decision action.
                immediate_reward, delayed_reward = self.agent.act(
                    action, confidence)

                # Action and result save
                memory_sample.append(next_sample)
                memory_action.append(action)
                memory_reward.append(immediate_reward)
                memory_pv.append(self.agent.portfolio_value)
                memory_num_stocks.append(self.agent.num_stocks)
                memory = [
                    (memory_sample[i], memory_action[i], memory_reward[i])
                    for i in list(range(len(memory_action)))[-max_memory:]
                ]
                if exploration:
                    memory_exp_idx.append(itr_cnt)
                    memory_prob.append([np.nan] * Agent.NUM_ACTIONS)
                else:
                    memory_prob.append(self.policy_network.prob)

                # Refresh information for repeat.
                batch_size += 1
                itr_cnt += 1
                exploration_cnt += 1 if exploration else 0
                win_cnt += 1 if delayed_reward > 0 else 0

                # Learn mode and When get a delay bonus has exist, policy neural network reset.
                if delayed_reward == 0 and batch_size >= max_memory:
                    delayed_reward = immediate_reward
                if learning and delayed_reward != 0:
                    # batch learn data size
                    batch_size = min(batch_size, max_memory)
                    # Create batch learn data.
                    x, y = self._get_batch(  # ready for batch data
                        memory, batch_size, discount_factor, delayed_reward)
                if len(x) > 0:
                    if delayed_reward > 0:
                        pos_learning_cnt += 1  # Positive learning
                    else:
                        neg_learning_cnt += 1  # Negative learning
                    # Reset Policy neural network.
                    loss += self.policy_network.train_on_batch(x, y)
                    memory_learning_idx.append([itr_cnt, delayed_reward])
                    batch_size = 0

                # Epoch information visualize.
                num_epoches_digit = len(str(num_epoches))
                epoch_str = str(epoch + 1).rjust(num_epoches_digit,
                                                 '0')  # check string length

                self.visualizer.plot(
                    epoch_str=epoch_str,
                    num_epoches=num_epoches,
                    epsilon=epsilon,
                    action_list=Agent.ACTIONS,
                    actions=memory_action,
                    num_stocks=memory_num_stocks,
                    outvals=memory_prob,
                    exps=memory_exp_idx,
                    learning=memory_learning_idx,
                    initial_balance=self.agent.initial_balance,
                    pvs=memory_pv)
                self.visualizer.save(
                    os.path.join(
                        epoch_summary_dir, 'epoch_summary_%s_%s.png' %
                        (settings.timestr, epoch_str)))

                # epoch information log write.
                if pos_learning_cnt + neg_learning_cnt > 0:
                    loss /= pos_learning_cnt + neg_learning_cnt
                logger.info("[Epoch %s/%s]\tEpsilon:%.4f\t#Expl.:%d/%d\t"
                            "#Buy:%d\t#Sell:%d\t#Hold:%d\t"
                            "#Stocks:%d\tPV:%s\t"
                            "POS:%s\tNEG:%s\tLoss:%10.6f" %
                            (epoch_str, num_epoches, epsilon, exploration_cnt,
                             itr_cnt, self.agent.num_buy, self.agent.num_sell,
                             self.agent.num_hold, self.agent.num_stocks,
                             locale.currency(self.agent.portfolio_value,
                                             grouping=True), pos_learning_cnt,
                             neg_learning_cnt, loss))

                # learn information reset.
                max_portfolio_value = max(max_portfolio_value,
                                          self.agent.profitloss_value)
                if self.agent.portfolio_value > self.agent.initial_balance:
                    epoch_win_cnt += 1

                # learn information log write
                logger.info("Max PV : %s, \t # Win : %d" % (locale.currency(
                    max_portfolio_value, grouping=True), epoch_win_cnt))

    def _get_batch(self, memory, batch_size, discount_factor,
                   delayed_reward):  # Mini batch data create Function
        x = np.zeros((batch_size, 1,
                      self.num_features))  # State of learn data and agent
        y = np.full((batch_size, self.agent.NUM_ACTIONS), 0.5)  # delay bonus.

        for i, (sample, action,
                reward) in enumerate(reversed(memory[-batch_size:])):
            x[i] = np.array(sample).reshape(
                (-1, 1, self.num_features))  # select vector
            y[i, action] = (delayed_reward + 1) / 2  # delay bonus setting.
            if discount_factor > 0:
                y[i, action] *= discount_factor**i  # discount_factor
            return x, y

    def _build_sample(self):
        self.environment.observe()  # Next index read.
        if len(self.training_data
               ) > self.training_data_idx + 1:  # Check next index
            self.training_data_idx += 1
            self.sample = self.training_data.iloc[
                self.training_data_idx].tolist()
            self.sample.extend(self.agent.get_states())
            return self.sample
        return None

    def trade(self, model_path=None, balance=2000000):  # Simulate for trade
        if model_path is None:
            return
        self.policy_network.load_model(model_path=model_path)
        self.fit(balance=balance, num_epoches=1,
                 learning=False)  # Just simulate, not trade.