本周Po文
这周我们共发表5篇文章。《基于 XGBoost 的组合策略…》等两篇详细讲解了机器学习构建组合策略的框架和常见问题。
文章要点与结论:
- 通过两阶段式方案实现多因子、多资产的组合策略构建。第一阶段基于XGBoost构建多个多因子单标的模型,第二阶段通过经典的均值方差方案进行组合优化。
- 应该基于分类而不是回归来构建机器学习模型。基于时序的价格预测几乎没有意义(价格序列不具有平稳性)。
- 分类模型是以多因子为特征,以未来一周期的涨跌幅度digitize化作为标签来训练构建的。
- 对XGBoost模型而言,基本上无须考虑因子标准化。因子标准化甚至有可能引入副作用;但另一方面,因子标准化也有利于正则惩罚项。
- 损失函数与度量函数的差别在于,损失函数要能计算梯度。MAPE函数的不可导性决定了它不适合作为损失函数。
- 在金融领域,相对误差比RMSE更有意义,因此我们介绍了SMAPE – 这是一个可作为损失函数的函数。
这两篇文章我们也集结后,发在本文末尾。
在这一期的量化工具专栏中,我们发表了获得NASA奖章的男人,带来了这些IPython技巧。IPython是非常轻量的交互式编程工具,尽管它的所有功能都可以在Notebook中找到,但它更轻,但仍然长袖擅舞,颇有飞燕之姿。
在不要去写书 除非是要与你的灵魂交流中,我们披露了《Python高效编程实践指南》出版过程中的一些冏事。这本书会对量化人构建稳健的交易系统非常有帮助。
话音刚落,周五就发生了Windows蓝屏事件,正是因为CrowedStrike缺乏完善的CI/CD,才导致这一事件发生。而CI/CD正是本书详细介绍的内容之一。
在问薪无愧,最全威的量化自学路线图中,我们介绍了Algos.org编写的这份量化自学大纲。
我们也将在8月底前,推出自己的量化自学路线图。除了更好的本地化之外(英文版中,一些工具、网站和数据源对国内市场不支持),我们还将更清晰地理出从入门到精通、从小白到不同的岗位的学习路径。
在大纲没有出来之前,大家可以暂时参照这份路线图:
基于XGBoost构建多因子策略
如何在投资组合策略中运用上机器学习方法? 最近,我们翻了下之前存过的论文,决定对《A portfolio Strategy Based on XGBoost Regression and Monte Carlo Method》这篇论文进行解读。
这是论文抽象出来的一个基本框架图。
这个框架能解决的什么问题呢?我们知道,在一个投资组合策略中,要重点考虑的第一个问题是,如何从给定的 universe 中,选择一部分股票纳入策略股票池;其次要考虑,这部分股票的持仓如何分配,使之在这个组合上,达到风险收益比最高。
后一部分,最经典的方法就是运用 MPT 理论,寻找有效投资前沿。这里既可以用凸优化求解,也可以使用蒙特卡洛方案。这一部分,我们之前有一个系列文章:投资组合理论与实战,从基本概念到实战细节,都讲得非常清楚,这里就不详述了。
如何从 universe 中选择股票进入股票池? 这在单因子模型中比较容易解决,就是选择因子分层中,表现最佳的那个分层 (tier) 的股票进入股票池。各标的的权重可以按因子载荷来分配,也可以使用 MPT 方法。
但如何在多因子模型中选择股票进入股票池?这一直是一个难题。我们常常提到的 Barra 模型也只是一个风控模型,并不能选择出最佳的股票池出来。
论文的思路是,将股票的纳入选择看成一个回归问题,即,通过多因子的训练,找出最能被模型预测的那些股票进入股票池。
作者给出的结果是,在 2021、2020 和 2019 年,龙头股票投资组合的回报率分别为 27.86%, 6.20%和 23.26%。不过,作者并没有给出基准比较,此外,也没有深入分析,如果这些结果有超额收益的话,这些超额是来自于 MPT 呢,还是来自于 XGBoost。
这也是我们要对这篇论文进行解读的地方。希望通过我们的解读,你也可以学习到,究竟应该如何分析他人的论文,从中汲取正确的部分。
错误的回归
论文中使用的是 XGBoost 回归模型。这可能是值得商榷的地方。在资产定价模型中,我们要预测的是股票在截面上的强弱,而不是在时序上的走势。
论文作者这里使用的方法是训练一个回归模型,从而使得它能较好地预测次日(或者后面一段时间的走势)。
右图是论文作者得到的结果之一。看起来模型能比较完美地预测次日走势。
显然,由于 XGBoost 回归模型本身没有预测股票强弱的能力,所以,即使通过回归模型找出了完美拟合的股票,也没有任何意义。因为一支下跌中的股票,也可能被完美地拟合出来。所以,论文中提到的收益,即使有超额,很可能也来自于 MPT 理论。
但是,作者仍然给出了一个如何通过 XGBoost 来寻找多因子模型中表现最佳个股的线索。我们只需要把它改造成一个分类模型,然后通过分类模型,筛选出表现最好的股票就可以了。
训练集中的 X 部分不用改变,但我们需要重新设定标签,即 y 部分。对给定的因子 X i X_i Xi,对应的 y i y_i yi需要能反映是上涨或者下跌。如果有可能,我们可以将标签设置为 5 类,-2 表示大跌,2 表示大涨,中间部分以此类推。
然后构造分类器进行训练。训练完成后,通过模型预测出来属于大涨标签的,我们就放入股票池,此时可以平均分配权重,也可以通过 MPT 理论来进行优化。
端到端训练及新的网络架构
论文作者使用的框架是两阶段式的,即先选择股票进入策略池,再通过 MPT 优化权重。
但即使是在第一阶段,它仍然是两段式的。每次进行训练时,它都只使用了一个标的的多因子数据。因此,如果 universe 中有 1000 支标的,就要训练出 1000 个模型(这是论文中暗示的方法,也可以考虑一个模型,训练 1000 次)。
这么做的原因是技术限制。XGBoost 只支持二维输入。如果我们要使用多个标的的多个因子同时进行训练,就必须使用 panel 格式的数据,或者将多个标的的多个因子进行一维展开。但如果标的数过多,展开后的训练会很困难。
也就是,由于技术限制,要么进行单因子的多标的同时训练,要么进行多因子的单标的训练。
但是,论文作者在这里给出了一个方法,就是你可以分别训练多个标的各自的模型,然后同样分别进行预测,然后再通过 MAPE 进行评估。当我们改成分类模型之后,可以简单地看分类结果,也可以结合分类的 metric 评估(在时序维度上的),选择准确性和分类结果都好的标的,纳入策略股票池。
损失函数与度量函数
接下来,我们要分析 MAPE 这个函数在论文中的使用。以此为契机,适当深入一点机器学习的原理,讲以下两个知识点:
2. XGBoost模型,因子数据是否要标准化
损失函数与度量函数
在机器学习中,有两类重要的函数,一类是目标函数(objective function),又称损失函数(loss function);一类是度量函数(metrics)。
损失函数用于模型训练。在训练过程中,通过梯度下降等方法,使得损失函数的值不断减小,直到无法继续下降为止,模型就训练完成。
训练完成之后的模型,将在test数据集上进行测试,并将预测的结果与真实值进行对比。为了将这个对比过程数值化,我们就引入了度量函数(metrics)。
在sklearn中,提供了大量的损失函数和度量函数。下图列举了部分Sklearn提供的损失函数和度量函数:
可以看出,度量函数的个数远多于损失函数,这是为什么呢?
在论文中,论文作者并没有披露他通过xgboost训练的具体过程,只是说直接使用了xgboost的database,这个表述有点奇怪,我们可以理解为在参数上使用了XGBoost的默认值好了。
但是他重点提到了使用MAPE,从过程来看,是在把MAPE当成度量函数进行事后评估。
在XGBoost中,如果没有特别指定目标函数,那么默认会使用带正则惩罚的RMSE(rooted mean square error)函数。RMSE也可以作为度量函数,在论文中,作者没有使用RMSE作为度量函数,而是选择了MAPE(mean absolute percentage error),原因何在?如果MAPE在这个场景下比RMSE更好,又为何不在训练中使用MAPE?
看上去无论目标函数也好,度量函数也好,都要使得预测值与真实值越接近越好。既然都有这个特性,为什么还需要区分这两类函数呢?
要回答这些问题,就要了解XGBoost的训练原理,核心是:它是如何求梯度下降的。
XGBoost:二阶泰勒展开
XGBoost是一种提升(Boosting)算法,它通过多个弱学习器叠加,构成一个强学习器。每次迭代时,新的树会修正现有模型的残差,即预测值与真实值之间的差异。这个差异的大小,就由目标函数来计算。
在XGBoost中,多个弱学习器的叠加采用了加法模型,
即最终的预测是所有弱学习器输出的加权和。这种模型允许我们使用泰勒展开来近似损失函数,从而进行高效的优化。
XGBoost对目标函数的优化是通过泰勒二阶展开,再求二阶导来实现的。使用二阶导数,XGBoost可以实现更快速的收敛,因为它不仅考虑了梯度的方向,还考虑了损失函数的形状。
f ( x ) ≈ f ( a ) + f ′ ( a ) ( x − a ) + f ′ ′ ( a ) 2 ! ( x − a ) 2 f(x) \approx f(a) + f'(a)(x-a) + \frac{f''(a)}{2!}(x-a)^2 f(x)≈f(a)+f′(a)(x−a)+2!f′′(a)(x−a)2
正是由于XGBoost内部优化原理,决定了我们选择目标函数时,目标函数必须是二阶可导的。
RMSE是二阶可导的,但MAPE不是:MAPE从定义上来看,它的取值可以为零,在这些零值点附近连一阶导都不存在,就更不用说二阶导了。下图是MAPE的公式:
$$
\text{MAPE} = 100\frac{1}{n}\sum_{i=1}^{n}\left|\frac{\text{实际值} - \text{预测值}}{\text{实际值}} \right|
$$
当预测值与实际值一致时,MAPE的值就会取零。
如何选择目标函数?
选择MAPE作为度量函数,不仅仅是便于在不同的模型之间进行比较,在金融领域它还有特殊的重要性:
我们更在乎预测值与真实值之间的相对误差,而不是绝对误差。在交易中,百分比才是王者。正因为这个原因,如果在训练时,能够使用MAPE作为目标函数,这样预测出来的准确度,要比我们通过RSME训练出来的准确度,更接近实际应用。
这就是在具体领域,我们改进算法的一个切入点。已经有人发明了被称为SMAPE的损失函数,它的公式是:
$$
\text{SMAPE} = \frac{100}{n} \sum_{t=1}^n \frac{\left|F_t-A_t\right|}{(|A_t|+|F_t|)/2}
$$
到目前为止,sklearn还没有提供这个函数,但我们可以自己实现,并通过sklearn的make_scorer方法接入到sklearn系统中:
from sklearn.metrics import make_scorerdef smape(y_true, y_pred):return np.mean(2.0 * np.abs(y_pred - y_true) / (np.abs(y_true) + np.abs(y_pred)))smape_scorer = make_scorer(smape, greater_is_better=False)# 使用举例:在GridSearchCV中使用
grid_search = GridSearchCV(estimator=model, param_grid=params, scoring=smape_scorer)
Question: 既然训练中不能使用MAPE,那么论文在测试中,又为何要使用MAPE呢?
答案其实很简单,是为了便于在多个模型之间进行比较。在论文作者的算法中,每支股票都必须有自己的模型。由于每支股票的绝对价格不一样,因此,它们的RSME是不一样的,而MAPE相当于一个归一化的指标,从而可以在不同的模型之间进行比较,最终选择出误差最小的模型对应的股票,纳入策略股池。
但我们前面也提到过,论文作者的这个模型没有意义,改用分类模型会好一些。如果改用分类模型的话,损失函数也不再是RSME了,度量函数也不能是MAPE了。
标准化
论文中还提到,在训练之前,他将因子数据进行了标准化。
实际上,这也是没有意义的一步。因为XGBoost是决策树模型,它是通过特征值的比较来进行分裂和划分数据的,显然,分裂点的比较,并不依赖数据的量纲,因此,标准化就没有意义,反而可能带来精度损失问题,得不偿失。
hint
如果因子数据使用单精度浮点数储存,那么如果两个小数只在小数点的第7位数字之后才产生差异,这两个数字在比较时,实际上是一样的。如果我们在进行标准化时,把两个原来有大小差异的数字,缩放到了只在第7位数字之后才出现差异,就产生了精度损失。
当然,事情也不能一概而论。XGBoost使用正则化来控制树的复杂度,包括对叶节点的权重进行L2正则化。如果你在训练XGBoost模型时,损失函数加了正则惩罚,而特征未经过标准化,正则化的效果可能会变差。
另外,论文中的方法是,每支股票一个模型,但如果只用一个模型,但拿1000支股票的数据来训练1000次呢?显然,这个时候,就必须要提前进行标准化了。
否则,收敛会很困难(当然,即使使用了标准化,也不保证就能收敛。能否收敛,要看众多股票是否真的具有同样的特征到标签的映射关系)。这并不是XGBoost的要求,而是根据我们使用XGBoost的方法带来的额外要求。
结论
对多数量化人来说,我们不可能像陈天奇那样自己撸一个机器学习框架出来,因此,要用同样的模型,做出更优的结果,就只能在数据标注、目标函数、评估函数和参数调优等方面下功夫了。这往往既需要有较深的领域知识,也要对具体的模型原理有一定的了解。
原文链接