专栏 | 监督&强化学习模型在金融市场的应用

机器之心专栏

作者:Cerulean

本文介绍了多个监督学习和强化学习模型在金融市场的应用,作者主要描述了多个论文的核心思想与实现,并且全面概括了其在 Github 上维护的项目。

项目地址:https://github.com/Ceruleanacg/Personae

目前,在本项目中:

  • 实现了 4 个强化学习模型。
  • 实现了 3 个监督学习模型。
  • 实现了 1 个简单的交易所,提供基本的买入、持有、卖出操作(卖空仍在开发中),标的物可以是股票组合或者期货合约组合。

对于监督学习模型的数据集:

我们采用 2008 年 1 月 1 日到 2018 年 1 月 1 日这个区间内,

  • 招商银行(600036)
  • 交通银行(601328)
  • 中信银行(601998)
  • 工商银行(601389)

这四只银行股在第 T 天的,

  • 开盘价(Open)
  • 收盘价(Close)
  • 最高价(High)
  • 最低价(Low)
  • 交易量(Volume)

作为输入数据,第 T+1 天的收盘价(Close)作为输出数据,进行训练,其中,这个区间前 70% 的数据作为训练数据,后 30% 作为测试数据,目前没有设置验证集数据。

下图是目前的实验结果,就目前的实验结果来看,监督学习的表现要好于强化学习。

图例 :蓝色的折线是测试数据集,其他颜色的折线是三种不同的监督学习模型在测试集上的预测。

接下来,我们将会依次对这 3 个监督学习模型与 4 个强化学习模型做一个简短的介绍。

1. Naive-LSTM (LSTM)

该模型是基于 LSTM 和 Dense(全连接)的基本模型,输入是序列长度为 5,即第 T 到第 T+4 天的 OCHLV 数据,输出是一个实数,代表了第 T+5 的预测收盘价格。

arXiv:1506.02078: Visualizing and Understanding Recurrent Network

模型计算图:

以下是构建模型的核心代码:

def _init_nn(self):
    self.rnn = self.add_rnn(1, self.hidden_size)
    self.rnn_output, _ = tf.nn.dynamic_rnn(self.rnn, self.x, dtype=tf.float32)
    self.rnn_output = self.rnn_output[:, -1]
    self.rnn_output_dense = self.add_fc(self.rnn_output, 16)
    self.y = self.add_fc(self.rnn_output_dense, self.y_space)

可以看出,第一行代码调用了项目中封装的用于构建 LSTM 层的 API,第二行代码用于计算该层输出和状态序列,第四行和第五行构造了一个全连接层并计算最终的输出。

2. TreNet (HNN)

IJCAI 2017. Hybrid Neural Networks for Learning the Trend in Time Series

上述引用的论文提出了一种混合神经网络的结构,同时用 RNN 与 CNN 提取序列特征,然后将输出拼接作为全连接层的输入,最后输出最终的预测结果。

模型计算图:

以下是构建模型的核心代码:

def _init_nn(self):
    self.rnn = self.add_rnn(1, self.hidden_size)
    self.rnn_output, _ = tf.nn.dynamic_rnn(self.rnn, self.rnn_x, dtype=tf.float32)
    self.rnn_output = self.rnn_output[:, -1]
    # self.cnn_x_input is a [-1, 5, 20, 1] tensor, after cnn, the shape will be [-1, 5, 20, 5].
    self.cnn = self.add_cnn(self.cnn_x, filters=2, kernel_size=[2, 2], pooling_size=[2, 2])
    self.cnn_output = tf.reshape(self.cnn, [-1, self.seq_length * self.x_space * 2])
    self.y_concat = tf.concat([self.rnn_output, self.cnn_output], axis=1)
    self.y_dense = self.add_fc(self.y_concat, 16)
    self.y = self.add_fc(self.y_dense, self.y_space)

可以看出,第一到第三行构造了 LSTM 层并计算结果,第四到第五行用项目封装的构造 CNN 的 API 构造了 CNN 层并计算了结果。最后拼接了 RNN 和 CNN 的结果,作为全连接层的输入,然后得到最终的计算结果。

3. DA-RNN (DualAttnRNN)

arXiv:1704.02971: A Dual-Stage Attention-Based Recurrent Neural Network for Time Series Prediction

上述引用的论文提出了一种基于注意力机制(Attention Based Model)的与 Seq-to-Seq 模型的网络结构,其创新点在于该模型连续两次使用注意力机制,在对原始序列使用注意力机制求权重后再次使用注意力机制对编码后的序列求权重,然后经解码与全连接层后输出结果。

模型计算图:

以下是构建模型的核心代码:

def _init_nn(self):
    # First Attn
    with tf.variable_scope("1st_encoder"):
        self.f_encoder_rnn = self.add_rnn(1, self.hidden_size)
        self.f_encoder_outputs, _ = tf.nn.dynamic_rnn(self.f_encoder_rnn, self.x, dtype=tf.float32)
        self.f_attn_inputs = self.add_fc(self.f_encoder_outputs, self.hidden_size, tf.tanh)
        self.f_attn_outputs = tf.nn.softmax(self.f_attn_inputs)
    with tf.variable_scope("1st_decoder"):
        self.f_decoder_input = tf.multiply(self.f_encoder_outputs, self.f_attn_outputs)
        self.f_decoder_rnn = self.add_rnn(1, self.hidden_size)
        self.f_decoder_outputs, _ = tf.nn.dynamic_rnn(self.f_decoder_rnn, self.f_decoder_input, dtype=tf.float32)
    # Second Attn
    with tf.variable_scope("2nd_encoder"):
        self.s_attn_input = self.add_fc(self.f_decoder_outputs, self.hidden_size, tf.tanh)
        self.s_attn_outputs = tf.nn.softmax(self.s_attn_input)
    with tf.variable_scope("2nd_decoder"):
        self.s_decoder_input = tf.multiply(self.f_decoder_outputs, self.s_attn_outputs)
        self.s_decoder_rnn = self.add_rnn(2, self.hidden_size)
        self.f_decoder_outputs, _ = tf.nn.dynamic_rnn(self.s_decoder_rnn, self.s_decoder_input, dtype=tf.float32)
        self.f_decoder_outputs_dense = self.add_fc(self.f_decoder_outputs[:, -1], 16)
        self.y = self.add_fc(self.f_decoder_outputs_dense, self.y_space)

可以看出,分别对应于四个变量命名空间,具有 2 组 4 个编解码层,在每一个编解码层都运用了一次注意力机制,求出当前序列的权重,然后与序列相乘后进行下一步的编解码工作,这是一种 Seq-to-Seq 的机制,更广泛地用于自然语言处理。最终解码的输出结果作为全连接层的输入,然后计算最终的结果。

以上是关于项目中监督学习模型的简短介绍,其中,所有模型的具体实现可以在项目链接中看到。

接下来是关于 3 个强化学习模型的介绍,但是在介绍强化学习模型前,我们首先对强化学习的数据和环境一个简短的概述。

Financial Market

这个文件实现了三个核心类,分别是:

  • Market
  • Trader
  • Position

他们分别代表了市场、交易员、持仓信息,最终 Market 类作为 Agent(强化学习模型)的 Environment(环境),接受 Agent 的 Action(动作),同时给出 Next State(下一状态)和 Reward(奖励),并进行迭代。

对于强化学习使用的数据,

我们使用这四只银行股在第 T 天的,

  • 开盘价(Open)
  • 收盘价(Close)
  • 最高价(High)
  • 最低价(Low)
  • 交易量(Volume)

和交易员在第 T 天的,

  • 现金(Cash)
  • 持仓价值(Holding Value)
  • 各持仓量(Holding Amount)

作为 State(状态),使用交易指令,

  • 买入(Buy)
  • 卖出(Sell)
  • 持有(Hold)

作为 Agent(智能体)的 Action(动作),其中,Reward Func(奖励函数)有 2 个版本,的计算代码如下:

def _update_reward(self, action_code, action_status, position):
    if action_code == ActionCode.Buy:
        if action_status == ActionStatus.Success:
            if position.pro_value > position.cur_value:
                self.reward += 70
            else:
                self.reward -= 50
        else:
            self.reward -= 100
    elif action_code == ActionCode.Sell:
        if action_statu    @staticmethod
    def _calculate_reward_v1(action_code, action_status, position):
        if action_status == ActionStatus.Failed:
            reward = -100
        else:
            if position.pro_value >= position.cur_value:
                if action_code == ActionCode.Hold:
                    reward = 50
                else:
                    reward = 100
            else:
                reward = - 50
        return reward

    @staticmethod
    def _calculate_reward_v2(_, action_status, position):
        if action_status == ActionStatus.Success:
            reward = position.pro_value - position.cur_value
        else:
            reward = -200
        return reward
s == ActionStatus.Success:
            if position.pro_value > position.cur_value:
                self.reward -= 70
            else:
                self.reward += 50
        else:
            self.reward -= 100
    else:
        if action_status == ActionStatus.Success:
            if position.pro_value > position.cur_value:
                self.reward += 70
            else:
                self.reward -= 50
        else:
            self.reward -= 100

图例 - 横坐标是时间,纵坐标是利润,其中蓝色折线是基准线,其他颜色的折线是强化学习模型表现

可以看出,除了 Policy Gradient 可以跑赢基准收益外,其他强化学习模型的收益甚至不如基准,这里非常值得讨论,目前笔者也在尝试从参数、输入特征、输出特征、奖励函数等多个角度考虑解决该问题。

接下来是关于强化学习模型的介绍:

1. Policy Gradient

NIPS. Vol. 99. 1999: Policy gradient methods for reinforcement learning with function approximation

模型计算图:

Basic Policy Gradient 的思想很朴素,重复及可能多的采样,对于一次采样的所有动作中,根据奖励函数值的正负决定梯度下降的方向,从而提高或者降低这些动作出现的概率。

以下是构建模型的核心代码:

def _init_nn(self):
    # Initialize predict actor and critic.
    w_init, b_init = tf.random_normal_initializer(.0, .3), tf.constant_initializer(0.1)

    with tf.variable_scope('nn'):
        first_dense = tf.layers.dense(self.s,
                                      50,
                                      tf.nn.relu,
                                      kernel_initializer=w_init,
                                      bias_initializer=b_init)

        second_dense = tf.layers.dense(first_dense,
                                       50,
                                       tf.nn.relu,
                                       kernel_initializer=w_init,
                                       bias_initializer=b_init)

        action_prob = tf.layers.dense(second_dense,
                                      self.a_space,
                                      tf.nn.tanh,
                                      kernel_initializer=w_init,
                                      bias_initializer=b_init)

        self.a_prob = action_prob
        self.a_s_prob = tf.nn.softmax(action_prob)

def _init_op(self):
    with tf.variable_scope('loss'):
        # a_one_hot = tf.one_hot(self.a, self.a_space)
        # negative_cross_entropy = -tf.reduce_sum(tf.log(self.a_prob) * a_one_hot)
        negative_cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.a_prob, labels=self.a)
        self.loss_fn = tf.reduce_mean(negative_cross_entropy * self.r)
    with tf.variable_scope('train'):
        self.train_op = tf.train.RMSPropOptimizer(self.learning_rate * 2).minimize(self.loss_fn)
    self.session.run(tf.global_variables_initializer())

本实现简单地采用两次全连接后输出 Softmax 后各个动作的概率,最后期望最小化采样动作的概率与真实概率乘以奖励函数的值的交叉熵。

2. Double DQN

arXiv:1509.06461: Deep Reinforcement Learning with Double Q-learning

模型计算图:

Double-DQN 采用评估网络与目标网络相互制约,期望避免传统 DQN 中容易出现的过度估计问题。首先使用评估网络预测下一个状态的状态-动作函数值,然后选取取得最大值的动作,计做 a_{max},接着用目标网络预测下一状态与采用 a_{max} 的状态值计算标签,然后期望最小化标签与评估网络对当前状态的状态-动作函数和当前动作的 Q 的均方差。

以下是构建模型的核心代码:

def _init_op(self):
    self.loss = tf.reduce_mean(tf.squared_difference(self.q_next, self.q_eval))
    self.train_op = tf.train.RMSPropOptimizer(self.learning_rate).minimize(self.loss)
    self.e_params = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='q_eval')
    self.t_params = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='q_target')
    self.update_q_target_op = [tf.assign(t, e) for t, e in zip(self.t_params, self.e_params)]
    self.session.run(tf.global_variables_initializer())

def train(self):
    # 1. If buffer length is less than buffer size, return.
    if self.buffer_length < self.buffer_size:
        return
    # 2. Update Q-Target if need.
    if self.total_step % self.update_q_target_step == 0:
        self.session.run(self.update_q_target_op)

    # 3. Get transition batch.
    s, a, r, s_next = self.get_transition_batch()

    # 4. Calculate q_eval_next.
    q_eval_next = self.session.run(self.q_eval, {self.s: s_next})

    # 5. Get action indices and make batch indices.
    a_indices = np.argmax(q_eval_next, axis=1)
    b_indices = np.arange(self.batch_size, dtype=np.int)

    # 6. Calculate q_target_next selected by actions.
    q_target_next = self.session.run(self.q_target, {self.s_next: s_next})
    q_target_next_with_a = q_target_next[b_indices, a_indices]

    # 7. Calculate labels.
    q_eval = self.session.run(self.q_eval, {self.s: s})
    q_next = q_eval.copy()
    q_next[b_indices, a.astype(np.int)] = r + self.gamma * q_target_next_with_a

    # 8. Calculate loss.
    _, self.critic_loss = self.session.run([self.train_op, self.loss], {self.s: s, self.q_next: q_next})

    # 9. Increase total step.
    self.total_step += 1

Double-DQN 的实现代码中,注释已经非常详尽,在这里就不再过多赘述。

3. Deep Deterministic Policy Gradient (DDPG)

arXiv:1509.02971: Continuous control with deep reinforcement learning

模型计算图:

DDPG 用于连续动作空间,在本问题中,对于四只股票的买卖持有的动作被映射到区间 [0, 11],其中,DDPG 使用 Actor-Critic Model,引入评估 Actor,目标 Actor 模型与评估 Critic,目标 Critic 模型两组四个网络,其中 Actor 模型用于预测动作,Critic 模型用于评估当前状态与动作的分数(状态-动作值函数),该方法期望最小化:评估 Critic 与评估 Actor 对当前状态-动作函数值与目标 Critic 和目标 Actor 对下一状态-动作函数值的均方差(如算法图所示),依次迭代改进目标 Critic 和目标 Actor。

以下是构建模型的核心代码:

def _init_nn(self):
    # Initialize predict actor and critic.
    self.a_predict = self.__build_actor_nn(self.s, "predict/actor", trainable=True)
    self.q_predict = self.__build_critic(self.s, self.a_predict, "predict/critic", trainable=True)
    # Initialize target actor and critic.
    self.a_next = self.__build_actor_nn(self.s_next, "target/actor", trainable=False)
    self.q_next = self.__build_critic(self.s_next, self.a_next, "target/critic", trainable=False)
    # Save scopes
    self.scopes = ["predict/actor", "target/actor", "predict/critic", "target/critic"]

def _init_op(self):
    # Get actor and critic parameters.
    params = [tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope) for scope in self.scopes]
    zipped_a_params, zipped_c_params = zip(params[0], params[1]), zip(params[2], params[3])
    # Initialize update actor and critic op.
    self.update_a = [tf.assign(t_a, (1 - self.tau) * t_a + self.tau * p_a) for p_a, t_a in zipped_a_params]
    self.update_c = [tf.assign(t_c, (1 - self.tau) * t_c + self.tau * p_c) for p_c, t_c in zipped_c_params]
    # Initialize actor loss and train op.
    self.a_loss = -tf.reduce_mean(self.q_predict)
    self.a_train_op = tf.train.RMSPropOptimizer(self.learning_rate).minimize(self.a_loss, var_list=params[0])
    # Initialize critic loss and train op.
    self.q_target = self.r + self.gamma * self.q_next
    self.c_loss = tf.losses.mean_squared_error(self.q_target, self.q_predict)
    self.c_train_op = tf.train.RMSPropOptimizer(self.learning_rate * 2).minimize(self.c_loss, var_list=params[2])
    # Initialize variables.
    self.session.run(tf.global_variables_initializer())

代码首先初始化两组四个网络,分别是评估 Actor、目标 Actor,评估 Critic,目标 Critic,然后根据 DDPG 的算法最小化评估 Critic 与评估 Actor 对当前状态-动作函数值与目标 Critic 和目标 Actor 对下一状态-动作函数值的均方差,依次迭代更新 Actor 与 Critic 直至收敛。

4. Dueling-DQN

arXiv:1511.06581: Dueling Network Architectures for Deep Reinforcement Learning

模型计算图:

算法:

相对于 DQN 直接输出状态-动作函数值,Dueling-DQN 的状态-动作函数值由上式决定,从网络结构上可以看出,在输出状态-动作函数值前,Dueling-DQN 的结构拆分了原 DQN 网络结构的最后一层,这样的思想很像 Actor-Critic 模型中的 Baseline,因为并不是每个状态都是十分重要的,有些时候对于这些状态,采取那个动作都不会有很大的影响。

以下是构建模型的核心代码:

def __build_critic_nn(self, s, scope):
    w_init, b_init = tf.random_normal_initializer(.0, .3), tf.constant_initializer(.1)
    with tf.variable_scope(scope):
        s_first_dense = tf.layers.dense(s,
                                        16,
                                        activation=tf.nn.relu,
                                        kernel_initializer=w_init,
                                        bias_initializer=b_init)

        s_second_dense = tf.layers.dense(s_first_dense,
                                         16,
                                         tf.nn.relu,
                                         kernel_initializer=w_init,
                                         bias_initializer=b_init)

        value = tf.layers.dense(s_second_dense,
                                1,
                                kernel_initializer=w_init,
                                bias_initializer=b_init)

        advantage = tf.layers.dense(s_second_dense,
                                    self.a_space,
                                    kernel_initializer=w_init,
                                    bias_initializer=b_init)

        q = value + (advantage - tf.reduce_mean(advantage, axis=1, keep_dims=True))

        return q

即最终对于某个状态-动作函数值而言,Advantage 的在不同动作维度上的值一定意义上描述了这个动作对于这个状态的重要性,最后加上 Q 值,避免了过度估计。

以上是最近关于强化学习和监督学习在金融市场中的一些应用和相关论文方法的实现。

目前仍有以下问题亟待探讨与解决:

  • 强化学习模型奖励函数的设计
  • 强化学习中基于值迭代的算法难以收敛
  • 监督学习的特征维度如何扩展
  • 同时,项目中可能有 Bug,欢迎各种 Issue 提出以及欢迎贡献各种代码 : )

原文发布于微信公众号 - 机器之心(almosthuman2014)

原文发表时间:2018-05-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏专知

【干货】GAN调研:多极扩展(跨域和条件的GAN扩展模型调研)

本文授权转载于知乎专栏作者:陈乐天 https://zhuanlan.zhihu.com/p/32103958 【摘要】 本文关注跨域(cross-domain...

2757
来自专栏专知

【干货】初学者指南|实现LSTM

【导读】时间序列建模目前已广泛应用在机器翻译、语音识别等相关领域,是目前AI领域不可或缺的重要技术。本文预测比特币价格为例,从头教你搭建长短期记忆网络LSTM。

1114
来自专栏Python数据科学

数据分析实战—北京二手房房价分析(建模篇)

本篇将继续上一篇数据分析之后进行数据挖掘建模预测,这两部分构成了一个简单的完整项目。结合两篇文章通过数据分析和挖掘的方法可以达到二手房屋价格预测的效果。

862
来自专栏MelonTeam专栏

机器学习入门系列02,Regression 回归:案例研究

引用课程:http://speech.ee.ntu.edu.tw/~tlkagk/courses_ML16.html 先看这里,可能由于你正在查看这个平...

1787
来自专栏人工智能LeadAI

神经网络瘦身:SqueezeNet

今年二月份,UC Berkeley和Stanford一帮人在arXiv贴了一篇文章: SqueezeNet: AlexNet-level accuracy wi...

37013
来自专栏AI科技评论

FAIR最新视觉论文集锦:FPN,RetinaNet,Mask和Mask-X RCNN(含代码实现)

这篇文章会从 FAIR 在基本模块上的创新开始,谈到 CNN,再到 one-shot 物体检测。之后会讲实例分割的创新。最后聊聊依靠弱半监督模型来扩展实例分割。...

6368
来自专栏生信技能树

一文看懂主成分分析

主成分分析法是数据挖掘中常用的一种降维算法,是Pearson在1901年提出的,再后来由hotelling在1933年加以发展提出的一种多变量的统计方法,其最主...

1.9K3
来自专栏量化投资与机器学习

【深度】监督&强化学习算法在A股中的应用

2994
来自专栏小鹏的专栏

为什么很多做人脸的Paper会最后加入一个Local Connected Conv?

Deep face:论文。 a. 人脸检测,使用6个基点 b. 二维剪切,将人脸部分裁剪出来 c. 67个基点,然后Delaunay三角化,在轮廓处添加三角形来...

2615
来自专栏AI2ML人工智能to机器学习

化曲为直の神奇

要学好人工智能、机器学习、统计、 计量、量化、优化等等和统计相关的内容, 少不了三大块数学: 微积分、线性代数和概率统计。 前面我们在“概率分布の三奥义”里面探...

320

扫码关注云+社区