跳到正文

在线学习算法

市场每天都在变化。一次拟合多年使用的模型,注定会失败。在线学习在新数据到来时更新模型。主要有两种:在线 Bagging 和在线 Boosting。

第 11 篇 · 共 12 篇 约 10 分钟

在线学习:因为市场不会停

用 2010–2020 数据拟合、到 2024 年部署的模型会有一个问题:世界变了。模型学到的是 2010–2020 期间特征和收益之间的关系,这些关系在 2024 年可能不成立。模型上线时已经过时,而且每天都在变得更过时。

标准修正是定期重拟合:每月、每季度或每年重拟合一次。这能用但浪费(每次都从头拟合,会丢掉信息),而且死板(你得承诺一个固定计划)。

在线学习是更灵活的方法:模型随着新数据到来不断更新。每条新观测触发一次增量更新。模型永远是“新”的,因为它已经看到了最近的数据。

参考是原始的在线 Bagging 论文(Oza, 2001)和在线 Boosting 论文(Oza & Russell, 2001)。

(Oza, N. C., “Online Bagging and Boosting,” Proceedings of the IEEE International Conference on Systems, Man and Cybernetics, 2001, pp. 2340–2345。)

为什么在线学习对金融很重要

三个原因。

原因 1:非平稳性。

金融时间序列不平稳。特征和收益之间的关系随时间变化。在固定数据集上训练的模型,是按历史状态校准的,在当前状态可能定价错误。

在线学习通过随新数据到来更新模型来应对这一点。模型适应当前状态,逐渐忘记旧状态。

原因 2:概念漂移。

即使在“平稳”状态内,特征和收益之间的关系也会逐渐漂移。60 天动量信号的 IC 在 2010–2015 是 0.05,在 2015–2020 是 0.03。在线学习检测这种漂移并调整模型。

原因 3:计算效率。

复杂模型(例如 1000 棵树的梯度提升)的完整重拟合可能需要几分钟到几小时。在线更新只需要几毫秒。对高频策略,在线学习是唯一选择。

(Oza, 2001。)

在线 Bagging 算法

在线 Bagging 是标准 Bagging 的在线类比。算法:

  1. 初始化 KK 个基模型(例如决策树),随机初始权重。
  2. 对每条新观测 (xt,yt)(x_t, y_t):
    • 对每个基模型 kk:
      • 从 Poisson(λ\lambda) 分布抽 KtK_t。
      • 用 (xt,yt)(x_t, y_t) 重复 KtK_t 次更新基模型 kk。
  3. 预测时对 KK 个基模型的预测取平均。

Poisson 分布是关键。对在线 Bagging,每条观测呈现给每个基模型的次数服从均值为 λ\lambda 的 Poisson 分布。λ=1\lambda = 1 时,每条观测平均呈现一次。λ>1\lambda > 1 时,观测被呈现多次(过采样)。λ<1\lambda < 1 时,欠采样。

λ\lambda 的选择取决于应用。对大多数金融应用,λ=1\lambda = 1 是合理的起点。

为什么是 Poisson?因为在线下 Bagging 中,每条观测会出现在自助样本的随机子集中。一条观测出现在自助样本中的次数服从 Poisson(1) 分布。在线 Bagging 通过从 Poisson(λ\lambda) 抽样来复现这个分布。

(Oza, 2001。)

在线 Boosting 算法

在线 Boosting 比在线 Bagging 难,因为 Boosting 需要在观测上保持权重,而这些权重随模型更新而变化。

算法(简化的 Oza-Russell):

  1. 初始化 KK 个基模型,权重相等 αk=1/K\alpha_k = 1/K。
  2. 对每条新观测 (xt,yt)(x_t, y_t):
    • 在观测上保持权重 wtw_t。
    • 对每个基模型 kk:
      • 从 Poisson(λ⋅wt\lambda \cdot w_t) 分布抽 KtK_t。
      • 用 (xt,yt)(x_t, y_t) 重复 KtK_t 次更新基模型 kk。
      • 计算基模型 kk 在 (xt,yt)(x_t, y_t) 上的误差 ϵk\epsilon_k。
      • 更新模型权重:αk←αk⋅(ϵk/(1−ϵk))0.5\alpha_k \leftarrow \alpha_k \cdot (\epsilon_k / (1 - \epsilon_k))^{0.5}(或类似形式)。
    • 更新观测权重:wt+1=wt⋅∏k(ϵk/(1−ϵk))0.5w_{t+1} = w_t \cdot \prod_k (\epsilon_k / (1 - \epsilon_k))^{0.5}。

直觉是:被错分的观测在后续更新中获得更高权重。这和线下 Boosting 一样,只是增量地应用。

αk\alpha_k 的更新规则取决于具体的在线 Boosting 变体。Oza-Russell 形式用 AdaBoost 的更新规则。

(Oza, N. C., & Russell, S., “Online Bagging and Boosting,” Proceedings of the IEEE International Conference on Systems, Man and Cybernetics, 2001。)

在线 Bagging 实务

一个实务实现:

import numpy as np
from sklearn.tree import DecisionTreeClassifier

class OnlineBaggingClassifier:
    def __init__(self, n_estimators=100, lambda_param=1.0, random_state=None):
        self.n_estimators = n_estimators
        self.lambda_param = lambda_param
        self.random_state = random_state
        self.estimators_ = []
        self.rng = np.random.default_rng(random_state)
    
    def partial_fit(self, X, y, classes=None):
        """Update the model with one or more new observations."""
        if not self.estimators_:
            self.classes_ = classes if classes is not None else np.unique(y)
            self.estimators_ = [DecisionTreeClassifier() for _ in range(self.n_estimators)]
            # Initialize with the first batch
            for est in self.estimators_:
                est.partial_fit(X, y, classes=self.classes_)
            return self
        
        for i in range(len(X)):
            for est in self.estimators_:
                k = self.rng.poisson(self.lambda_param)
                if k > 0:
                    x_rep = np.tile(X[i:i+1], (k, 1))
                    y_rep = np.full(k, y[i])
                    est.partial_fit(x_rep, y_rep)
        
        return self
    
    def predict(self, X):
        predictions = np.array([est.predict(X) for est in self.estimators_])
        # Majority vote
        return np.array([np.bincount(predictions[:, i]).argmax() for i in range(X.shape[0])])
    
    def predict_proba(self, X):
        probs = np.array([est.predict_proba(X) for est in self.estimators_])
        return probs.mean(axis=0)

关键方法是 partial_fit,它用 Poisson 分布的重复次数更新每个基模型。

基模型需要支持 partial_fit。scikit-learn 的决策树支持,逻辑回归、朴素贝叶斯和其他一些模型也支持。梯度提升和随机森林不支持(它们需要完整重拟合)。

选基模型

对在线学习,基模型必须支持增量更新。最常见的选择:

决策树(例如带 partial_fit 的 DecisionTreeClassifier):快、灵活、支持增量更新。在线 Bagging 的标准选择。

逻辑回归:简单、快、理论成熟。通过 SGD 支持增量更新。不错的基线。

朴素贝叶斯:非常快、稳健、支持增量更新。适合高频数据。

在线梯度下降模型:任何用 SGD 训练的模型都能在线更新。包括逻辑回归、SVM 和神经网络。

对在线 Boosting,基模型必须支持增量更新,并且有“误差”的概念(用于权重更新)。决策树和逻辑回归都行。

(Bottou, L., “Online Algorithms and Stochastic Approximations,” Online Learning and Neural Networks, Cambridge University Press, 1998。)

漂移问题

一个朴素的在线学习系统有个问题:新数据到来时,模型更新以适应新数据。但模型也保留对旧数据的记忆。两者相互竞争。

如果世界缓慢变化(渐变漂移),模型适应良好。如果世界突变(状态切换),模型适应慢,因为旧数据仍然有影响。

应对突变可以:

限制历史窗口:不是用从时间开始的所有数据,只用最近 NN 个观测。模型自动“忘记”旧数据。

给旧观测降权:用指数衰减因子给旧观测加权。旧数据的有效贡献随时间下降。

用滑动窗口:定期在最近 NN 个观测上重拟合模型。这是一种带硬重置的在线学习。

一个常见模式:把在线学习(用于细粒度更新)和定期重拟合(用于状态切换)结合。在线更新处理渐变漂移;定期重拟合处理突变。

(Oza, 2001。)

在线学习加滑动窗口

一个简单的组合:

  1. 维护最近 NN 个观测的滑动窗口。
  2. 用在线学习在新观测到来时更新模型。
  3. 当窗口滑动(也就是最老的观测被丢掉)时,在当前窗口上重拟合模型。

这结合了在线学习的优点(更新快)和滑动窗口的优点(对状态切换的锐利适应)。

典型选择:N=252N = 252(一个交易日年)。在重拟合之间做在线更新;每 NN 个观测或每季度重拟合一次。

更精细的选择:用 CUSUM 检验(前一篇文章)检测状态切换,只在检测到切换时重拟合。这避免了稳定期内的不必要重拟合。

在线 Bagging vs 在线 Boosting

两种方法有不同的优势。

在线 Bagging:

  • 减少方差,不减少偏差。
  • 适合高方差基模型(例如深度决策树)。
  • 对标签噪声不太敏感。
  • 大多数应用的标准选择。

在线 Boosting:

  • 同时减少方差和偏差。
  • 适合高偏差基模型(例如浅决策树)。
  • 对噪声更敏感:错标的观测会获得更高权重。
  • 基模型太简单无法拟合数据时更好。

对大多数金融应用,在线 Bagging 是更安全的选择。金融数据噪声大,Boosting 可能对噪声过拟合。

一个有用的对比:两个都拟合,在 holdout 集上评估,选样本外表现更好的那个。

具体例子

设定:2010–2024 年标普 500 日频收益。特征:20 天动量、60 天均值回归、实现波动率和 VIX。标签:h=5h = 5 的三重障碍。

线下基线:在 2010–2020 数据上拟合的梯度提升分类器,在 2021–2024 上评估。夏普 = 1.2。

在线 Bagging:100 棵决策树,λ=1\lambda = 1,每天用新观测更新。夏普 = 1.5。

带滑动窗口的在线 Bagging:和上面一样,但每 252 天在最近数据上重拟合。夏普 = 1.7。

在线 Boosting:100 棵浅决策树(max depth = 3),Oza-Russell 更新。夏普 = 1.4。

带滑动窗口的在线 Bagging 胜出。原因是:它适应状态切换(每 252 天重拟合),同时具备 Bagging 的方差缩减。

在线 Boosting 有竞争力但不更好。在噪声大的金融数据中,Bagging 的稳健性取胜。

常见陷阱

五种陷阱。

陷阱 1:用的基模型不支持 partial_fit。

scikit-learn 的梯度提升不支持 partial_fit。你得每次从头重拟合,这就违背了在线学习的初衷。用决策树、逻辑回归或其他支持增量更新的模型。

陷阱 2:λ\lambda 设得太大。

λ=10\lambda = 10 时,每条观测平均被呈现给每个基模型 10 次。模型会过拟合最近观测,过快地忘记旧观测。大多数应用用 λ∈[0.5,2]\lambda \in [0.5, 2]。

陷阱 3:状态切换后没有重置模型。

在线学习逐步适应。突变后,模型适应慢。检测到状态切换时(例如通过 CUSUM),从头重拟合模型。

陷阱 4:忽略概念漂移。

在线学习假设世界渐变。如果变化非单调(例如特征的 IC 在正负之间振荡),在线学习可能没用。对非单调漂移用滑动窗口。

陷阱 5:忘了交易成本。

在线学习频繁更新模型。策略可能因此更频繁地交易。交易成本可能侵蚀收益。评估时要包含交易成本。

什么时候不用在线学习

三种情况。

情况 1:平稳环境。

如果数据生成过程是平稳的(金融里罕见,但某些实验室设置中可能),在线学习增加了复杂性但没收益。用线下学习。

情况 2:非常小的数据集。

如果观测少于 1000 条,在线学习不稳定。模型没有足够数据可学,增量更新反而增加噪声。用线下学习加交叉验证。

情况 3:无法增量更新的复杂模型。

深度神经网络、梯度提升和其他复杂模型难以增量更新。从头重拟合的成本可能比实现增量更新低。坚持用定期重拟合。

大多数其他情况(特别是对非平稳金融数据),在线学习值得引入这种额外的复杂性。

与不平衡 Bagging 的联系

在线 Bagging 可以和平衡 Bagging(来自 Bagging 不平衡一文)结合。组合:

  1. 为每个类维护单独的缓冲区。
  2. 新观测到来时,把它加到对应类的缓冲区。
  3. 训练每个基模型时,从缓冲区抽一个平衡子样本。

这结合了 Bagging 的方差缩减和欠采样的类别平衡,并随新数据不断更新。

实现更复杂(需要维护单独缓冲区),但得到的模型能同时处理非平稳和类别不平衡。

(AFML, §6.5。)

理论保证

在线学习在一定条件下有理论保证。最重要的:遗憾界。

对在线学习者,预测 ptp_t 观察结果 yty_t,损失 l(pt,yt)l(p_t, y_t),TT 轮后的遗憾是:

RT=∑t=1Tl(pt,yt)−min⁡p∗∑t=1Tl(p∗,yt)R_T = \sum_{t=1}^{T} l(p_t, y_t) - \min_{p^*} \sum_{t=1}^{T} l(p^*, y_t)

这是在线学习者的累积损失减去事后最优固定预测器的累积损失。对有界损失的在线梯度下降,RT=O(T)R_T = O(\sqrt{T})。每轮平均遗憾随 T→∞T \to \infty 趋于零。

含义是:在平稳环境中,在线学习者收敛到最优固定预测器的表现。在非平稳环境中,在线学习者可能收敛较慢,但最终会跟踪移动的最优点。

对 λ=1\lambda = 1 的在线 Bagging,每轮期望遗憾由基学习器的方差决定。对在线 Boosting,遗憾界由基学习器的间隔决定。

理论保证在某些情况下弱于实际表现(金融数据违反独立同分布假设),但它们提供有用的 sanity check。有界遗憾的在线学习者做的还算合理。

(Zinkevich, M., “Online Convex Programming and Generalized Infinitesimal Gradient Ascent,” Proceedings of the 20th International Conference on Machine Learning, 2003, pp. 928–936。)

计算成本

在线学习的每次更新成本低于线下重拟合。对 nn 条训练观测、mm 个特征的模型:

线下重拟合:每次 O(nm)O(nm)。对 10,000 条观测 50 个特征的数据集,是 500,000 次运算。对深度神经网络,可能上百万上亿。

在线更新:每次观测 O(m)O(m)。对 50 个特征,是 50 次运算。提速 10,000 倍。

权衡是:线下重拟合可以用二阶方法(例如牛顿法),收敛更快。线上更新用一阶方法(梯度下降),需要更多迭代,但每次迭代便宜。

对每日重训练的策略,在线更新明显更快。对每月重训练的策略,线下重拟合可以接受。

(Bottou, L., “Stochastic Gradient Descent Tricks,” Neural Networks: Tricks of the Trade, Springer, 2012, pp. 421–436。)

Mini-Batch 变体

实际中,在线学习通常用 mini-batch:不在每条观测上更新,而是在一个小 batch(例如 32–256 条观测)上更新。这有这些好处:

  • 减少更新噪声(梯度在 batch 上取平均)。
  • 允许向量化计算(在现代硬件上更快)。
  • 平滑学习率自适应。

Mini-batch 在线学习者是混合体:以 batch 形式处理数据,但每个 batch 当成一次更新。模型仍然增量更新(一次一个 batch),但每次更新噪声更小。

金融应用中,32–256 的 mini-batch 大小是典型值。更大(例如 1000)会接近线下行为。更小(例如 1)是真正的在线学习。

Mini-batch 变体是合理的默认:比纯在线学习更稳定,比线下重拟合更快。

在线学习能带来什么

一个能适应变化市场条件的模型。改进很难事先量化,但相比固定模型通常夏普提高 10%–30%。改进在状态切换时最大,在稳定期最小。

代价是复杂性:更多代码、更多超参数、更多潜在失败模式。一个简单的在线 Bagging 加定期重拟合是合理的起点,等到简单方法不够时再上更复杂的变体。

对大多数金融应用,λ=1\lambda = 1 的在线 Bagging 加 252 天的滑动窗口是稳健的默认。它通过在线更新处理渐变漂移,通过重拟合处理突变。实现简单(不到 100 行代码)。相比固定模型夏普通常提升 10%–30%。

更大的视角

在线学习是处理非平稳性的一种方法。其他方法包括:

  • 定期重拟合:每 NN 个观测重拟合模型。
  • 滑动窗口训练:在最近 NN 个观测上训练。
  • 状态切换模型:维护多个模型,每个状态一个,根据检测到的状态切换。
  • 自适应正则化:模型老化时增加正则化,鼓励它忘记旧数据。

每种方法都有权衡。在线学习最灵活但最复杂。定期重拟合最简单但最死板。滑动窗口训练是一个折中。

对大多数金融应用,在线 Bagging 加定期重拟合(例如每 252 天)是稳健的默认。在线更新处理渐变漂移;定期重拟合处理状态切换。

关键洞见是:世界在变,你的模型应该跟着变。在线学习是让这一点发生的一种方法。

(Oza, 2001;Bottou, 1998。)