【研报分享】华泰金工林晓明团队:再论时序交叉验证对抗过拟合——华泰人工智能系列之十六

研报干货
标签: #<Tag:0x00007f51fe641318>

(sonyzh) #1

摘要

从基线模型设置和样本精确切分两个角度对时序交叉验证提出改进

华泰金工《对抗过拟合:从时序交叉验证谈起》研究发现,对于时间序列数据,传统K折交叉验证选择的模型存在过拟合风险,时序交叉验证能减轻过拟合。本文从基线模型(baseline model)的设置和训练集验证集的精确切分两个角度,对原有时序交叉验证方法提出改进。通过对比时序交叉验证、分组时序交叉验证以及四种基线模型,我们发现分组时序交叉验证表现优于时序交叉验证,两者均优于其余基线模型。针对时序数据进行机器学习模型调参时,推荐使用分组时序交叉验证方法以对抗过拟合。

从模型性能和单因子测试看,时序和分组时序交叉验证能减轻过拟合

从模型性能来看,将六种交叉验证方法按样本内表现排序:时序 < 分组时序 < 三种新的基线模型 < K折。从模型性能和单因子测试结果来看,将各方法按测试集表现排序:分组时序 > 时序 > 三种新的基线模型 > K折。上述结果表明,K折交叉验证选出的模型表现出较强的过拟合,时序和分组时序交叉验证能够一定程度上减轻过拟合。

时序和分组时序交叉验证带来的提升主要源于时序信息的保留

新基线模型的引入使得我们能够对时序为何优于K折进行归因分析。首先,和K折相比,三种新的基线模型使用更少样本,其表现略优于K折,说明模型表现的提升确实部分源于使用更少样本。其次,和三种新的基线模型相比,时序和分组时序交叉验证保留了时序信息,其表现优于三种新的基线模型,说明模型表现的提升主要源于时序信息的保留。

分组时序交叉验证确保验证集于时序上严格在训练集后,能提升模型表现

原始时序交叉验证对训练集和验证集的切分不够精细,可能出现同一月份样本一部分属于训练集一部分属于验证集。通过对scikit-learn库model_selection包进行改造,我们得以实现样本的精确切分,确保验证集在时序上严格位于训练集之后。相比于原始时序交叉验证,改造后的分组时序交叉验证在模型表现上有小幅提升。

本文研究导读

如果将机器学习算法比作基金经理做投资决策的过程,那么交叉验证调参相当于设计一套制度选拔优秀的基金经理。作为机器学习的顶层设计部分,交叉验证理应受到更多重视,然而因其过程相对细碎繁杂,技术含量看似不高,在以往的研究报告中没有得到足够重视。随着对机器学习方法理解的逐渐深入,我们发现交叉验证调参环节作为“挑选算法的算法”,其重要性不亚于挑选算法本身。

本文围绕机器学习对抗对拟合的方法——时序交叉验证作进一步研究。机器学习模型调参的传统方法是K折交叉验证。在华泰金工人工智能系列之十四《对抗过拟合:从时序交叉验证谈起》(20181128)一文中,我们证实了K折交叉验证应用于时间序列数据存在模型过拟合的风险,而时序交叉验证能够降低过拟合程度。借助时序交叉验证的机器学习选股策略能够获得更高并且更稳定的收益。

本文是对上篇报告的拾遗和改进,从以下两个角度进行探讨:

1. 上篇报告的基线模型(baseline model)不合理,无法区分时序交叉验证带来的提升究竟来自“保留样本时序信息”还是“使用更少的样本”。本文设置更合理的基线模型,提出“训练集折半的K折交叉验证”和“乱序递进式交叉验证”两种方法供对照之用,希望厘清时序交叉验证带来提升的真实原因;

2. 上篇报告对训练集和验证集的切分不够精细,可能出现同一月份样本一部分属于训练集一部分属于验证集,违背了时序交叉验证的本意。本文对基于Python的机器学习库scikit-learn的model_selection包进行改造,提出新方法“分组时序交叉验证”,从而实现更精细的训练集和验证集切分。

本文针对上述两方面作深入研究,测试了包括时序交叉验证、分组时序交叉验证、四种基线模型在内的六种交叉验证方法,以模型性能和单因子测试表现作为评价依据。结果显示,时序交叉验证表现优于新的基线模型,而新的基线模型表现略优于K折交叉验证,表明时序交叉验证带来的提升主要源于时序信息的保留,小部分源于使用更少的样本。同时,分组时序交叉验证表现略优于原始时序交叉验证,表明对训练集和验证集进行精细切分能够小幅提升模型表现。

时序交叉验证的改进

关于模型调参和交叉验证的基本概念,本文不再赘述,感兴趣的读者请参考华泰金工人工智能系列之十四《对抗过拟合:从时序交叉验证谈起》(20181128)。

本研究共测试六种交叉验证方法,分为三组:

1. “K折交叉验证”和“时序交叉验证”是上篇报告测试比较的两种原始交叉验证方法。其中前者为基线模型作对照之用,后者是上篇报告推荐使用的方法。

  1. “训练集折半的K折交叉验证”和“乱序递进式交叉验证”是基于改进思路1提出的两种新的基线模型。基线模型仍作对照之用,不是原始方法的提升,目的是探索时序交叉验证带来提升的真实原因。

3. “分组时序交叉验证”和“乱序分组递进式交叉验证”是基于改进思路2提出的两种新方法。其中前者是本篇报告推荐使用的方法,后者是针对前者单独设计的新基线模型。

下面我们将逐一介绍六种交叉验证方法。

K折和时序交叉验证

K折交叉验证(k-fold cross-validation)是最经典和最常用的交叉验证方法之一。如图表1所示,将全体样本等分为K份(通常需要事先随机打乱,K在3~20之间),每次用其中的1份作为验证集,其余K-1份作为训练集。重复K次,直到所有部分都被验证过。取K个验证集的平均正确率(或F1分数、AUC、平方损失、对数损失等其它模型评价指标)用以衡量该模型(或该组超参数)的整体表现。

时序交叉验证(time series cross-validation)如图表2所示,适用于时间序列数据。将保留时序信息的数据等分(或依据其它标准切分)成K+1份,第i次验证时取第i+1份作为验证集,第1至i份作为训练集,重复K次。同样取K个验证集的平均表现作为模型间比较的依据。

K折交叉验证广泛应用于图像识别、语音识别、自然语言处理等机器学习技术最为活跃的领域。K折交叉验证的使用前提是样本服从独立同分布。图像、语音、自然语言等领域的数据通常满足独立同分布原则,而金融领域的时间序列数据往往存在较强的时序相关性。理论上,K折交叉验证不适用于时序数据;实际上,在金融领域K折交叉验证仍被大量地、错误地使用。

在华泰金工人工智能系列之十四《对抗过拟合:从时序交叉验证谈起》(20181128)的研究中,我们采用机器学习公共数据集以及全A选股数据集,比较K折和时序这两种交叉验证方法的表现。从实践结果来看,对于非时序数据,两种交叉验证方法表现接近。对于时序数据,相比于K折交叉验证,时序交叉验证在样本内数据集上的表现相对较差,但是在测试集上表现更好,表现出更低的过拟合程度;时序交叉验证倾向于选择超参数“简单”的模型,同样体现出更低的过拟合程度。两种交叉验证的差异在逻辑回归等简单模型上仅略有体现,而在XGBoost等复杂模型上体现更为明显。借助时序交叉验证的机器学习选股策略能够获得更高并且更稳定的收益。

然而,上述研究存在不完美之处,以下试举两例说明,同时引申出两种改进思路。

改进思路1——更合理的基线模型

上篇报告的第一处缺陷在于基线模型设置不合理。我们希望证明“时序”优于“K折”,因此将K折交叉验证视为基线模型以作对照之用。时序和K折交叉验证的核心区别为以下两点,这两点也可视作时序交叉验证带来提升的可能原因:

1. 时序交叉验证保留样本的时序信息(假设1);

2. 时序交叉验证使用更少(接近一半)的样本(假设2)。

当我们采用K折交叉验证作为基线模型时,我们并不能回答时序交叉验证展现出的优势主要源于以上哪一点。事实上,存在一种极端的可能,即时序交叉验证的优势完全来源于使用更少样本(假设2),此时使用K折交叉验证对一半样本进行训练和调参,就可能得到和时序交叉验证同样好的表现。然而这和我们采用保留样本时序信息(假设1)的时序交叉验证的初衷相违背。换言之,由于基线模型设置不合理,我们无法厘清时序交叉验证带来提升的真实原因。

针对上述缺陷,我们提出的第一个改进思路是设置更丰富的基线模型,包括采用“训练集折半的K折交叉验证”,以及采用形式类似时序交叉验证但样本时序关系被破坏的“乱序递进式交叉验证”。如果时序交叉验证仍优于新的基线模型,那么表明时序交叉验证带来提升确实源于时序信息的保留。

训练集折半的K折交叉验证如图表3所示。在K折交叉验证的基础上,保留验证集不变,每次随机取一半长度的原训练集作为新训练集。该方法各次训练所使用的平均样本量和时序交叉验证基本相同。如果该方法表现较好,接近于时序交叉验证,那么说明保留时序信息(假设1)不是时序交叉验证带来提升的主要原因;类似地,如果该方法表现较差,接近于K折交叉验证,那么说明使用更少的样本(假设2)不是时序交叉验证带来提升的主要原因。

乱序递进式交叉验证如图表4所示。在时序交叉验证基础上,将样本打乱,破坏时序信息。该方法每次训练所使用的样本量均和时序交叉验证相同。该方法用于模型比较的逻辑和训练集折半的K折交叉验证类似,不再赘述。

改进思路2——更精细的切分方法

上篇报告的第二处缺陷在于时序交叉验证对训练集和验证集的切分不够精细。此前我们使用scikit-learn库model_selection包下的TimeSeriesSplit类进行数据切分。默认参数下,TimeSeriesSplit将样本内数据集等分成若干份,第i次验证时取前i份作为训练集,第i+1份作为验证集。然而,在我们的选股数据集中,每个月份包含的有效样本数不一致。如果简单调用TimeSeriesSplit,会出现同一月份数据分属不同“折”的情况,即同一月份数据部分出现在训练集部分出现在验证集,违背了时序交叉验证的本意。

针对上述缺陷,我们提出的第二个改进思路是对scikit-learn库的model_selection包进行改造,增加一个新的类GroupTimeSeriesSplit,从而实现分组时序交叉验证(grouped time series cross-validation)。将样本所属月份通过参数groups传递给GroupTimeSeriesSplit类,切分时不再将数据等分,而是切在相邻两个月的分界处,确保验证集在时序关系上严格位于训练集之后,如图表5所示。

分组时序交叉验证的难点不在于方法的构想,而在于代码实现。本文附录部分介绍了对scikit-learn库model_selection包进行改造,增加GroupTimeSeriesSplit类以及在主函数中调用该类的详细方法。

乱序分组递进式交叉验证是针对分组时序交叉验证单独设计的新基线模型,如图表6所示。该方法每次进行验证时,训练集和验证集的样本量和分组时序交叉验证完全相同。具体实现方式为:首先根据样本所属月份信息,确定各次验证的训练集和验证集长度;随后将数据打乱,最后调用GroupTimeSeriesSplit类进行分组“时序”(实际上是乱序)交叉验证。该方法用于模型比较的逻辑和其余两个基线模型相似,不再赘述。

下表是对本文测试的六种交叉验证方法的汇总。


乱序分组递进式交叉验证是针对分组时序交叉验证单独设计的新基线

方法

人工智能选股模型测试流程

本文选用逻辑回归和XGBoost作为基学习器,两者分别作为简单模型和复杂模型的代表。测试流程包含如下步骤:

1. 数据获取:

a) 股票池:全A股。剔除ST股票,剔除每个截面期下一交易日停牌的股票,剔除上市3个月内的股票,每只股票视作一个样本。

b) 回测区间:2011年1月31日至2019年1月31日。

2. 特征和标签提取:每个自然月的最后一个交易日,计算之前报告里的70个因子暴露度,作为样本的原始特征,因子池如图表10所示。计算下一整个自然月的个股超额收益(以沪深300指数为基准),在每个月末截面期,选取下月收益排名前30%的股票作为正例(y = 1),后30%的股票作为负例(y = 0),作为样本的标签。

3. 特征预处理:

a) 中位数去极值:设第T期某因子在所有个股上的暴露度序列为D\_i,D\_M 为该序列中位数,D_{M1}为序列|D\_i-D\_M|的中位数,则将序列中所有大于D\_M+5D\_{M1}的数重设为D\_M+5D\_{M1},将序列中所有小于D\_M-5D\_{M1}的数重设为D\_M-5D\_{M1};

b) 缺失值处理:得到新的因子暴露度序列后,将因子暴露度缺失的地方设为中信一级行业相同个股的平均值;

c) 行业市值中性化:将填充缺失值后的因子暴露度对行业哑变量和取对数后的市值做线性回归,取残差作为新的因子暴露度;

d) 标准化:将中性化处理后的因子暴露度序列减去其现在的均值、除以其标准差,得到一个新的近似服从N(0, 1)分布的序列。

4. 滚动训练集和验证集的合成:由于月度滚动训练模型的时间开销较大,本文采用年度滚动训练方式,全体样本内外数据共分为八个阶段,如下图所示。例如预测2011年时,将2005~2010年共72个月数据合并作为样本内数据集;预测T年时,将T-6至T-1年的72个月合并作为样本内数据。根据不同的交叉验证方法(图表1~6),划分训练集和验证集,交叉验证的折数均为12。对于分组时序交叉验证,每次训练集长度均为6个月的整数倍,验证集长度均等于6个月。对于K折交叉验证和训练集折半的K折交叉验证,验证次数为12次;对于其余四种交叉验证方法,验证次数为11次。凡涉及将数据打乱的交叉验证方法,随机数种子点均相同,从而保证打乱的方式相同。


5.样本内训练:使用逻辑回归或XGBoost基学习器对训练集进行训练。

6.交叉验证调参:对全部超参数组合进行网格搜索,选择验证集平均AUC最高的一组超参数作为模型最终的超参数。不同交叉验证方法可能得到不同的最优超参数。超参数设置和调参范围如下表所示。

7. 样本外测试:确定最优超参数后,以T月末截面期所有样本预处理后的特征作为模型的输入,得到每个样本的预测值。将预测值视作合成后的因子,采用回归法、IC分析法和分层回测法进行单因子测试。

8. 模型评价:a) 测试集的正确率、AUC等衡量模型性能的指标;b) 单因子测试得到的统计指标和回测绩效。

单因子测试

回归法和IC值分析法

测试模型构建方法如下:

1. 股票池:全A股,剔除ST股票,剔除每个截面期下一交易日停牌的股票,剔除上市3个月以内的股票。

2. 回测区间:2011-01-31至2019-01-31。

3. 截面期:每个月月末,用当前截面期因子值与当前截面期至下个截面期内的个股收益进行回归和计算Rank IC值。

4. 数据处理方法:对于分类模型,将模型对股票下期上涨概率的预测值视作单因子。对于回归模型,将回归预测值视作单因子。因子值为空的股票不参与测试。

5. 回归测试中采用加权最小二乘回归(WLS),使用个股流通市值的平方根作为权重。IC测试时对单因子进行行业市值中性。

分层回测法

依照因子值对股票进行打分,构建投资组合回测,是最直观的衡量因子优劣的手段。测试模型构建方法如下:

1. 股票池、回测区间、截面期均与回归法相同。

2. 换仓:在每个自然月最后一个交易日核算因子值,在下个自然月首个交易日按当日收盘价换仓,交易费用以双边千分之四计。

3. 分层方法:因子先用中位数法去极值,然后进行市值、行业中性化处理(方法论详见上一小节),将股票池内所有个股按因子从大到小进行排序,等分N层,每层内部的个股等权配置。当个股总数目无法被N整除时采用任一种近似方法处理均可,实际上对分层组合的回测结果影响很小。

4. 多空组合收益计算方法:用Top组每天的收益减去Bottom组每天的收益,得到每日多空收益序列,则多空组合在第n天的净值等于1+当日收益的累乘。

5. 评价方法:全部N层组合年化收益率(观察是否单调变化),多空组合的年化收益率、夏普比率、最大回撤等。

结果

最优超参数

首先我们展示逻辑回归和XGBoost历年滚动训练得到的最优超参数,如下表所示。

对于逻辑回归的正则化项系数C(实际在scikit-learn库里为正则化系数的倒数),时序和分组时序交叉验证两种方法的C值全部在万分位数量级,其余四种基线模型的C值大部分在千分位数量级。C值越小,对正则化项的惩罚越大,模型的拟合能力越弱而泛化能力越强。换言之,时序和分组时序交叉验证选出的逻辑回归模型更可能出现欠拟合,更不容易出现过拟合。

对于XGBoost的学习速率learning_rate,相比于调参范围(0.01,0.025,0.05,0.075,0.1),六种交叉验证方法得到的最优超参数集中在0.025~0.075之间,时序和分组时序交叉验证方法相比于其余四种基线模型没有明显差异。

对于XGBoost的最大树深度max_depth,时序和分组时序交叉验证两种方法的最优超参数均为3或5,其余四种基线模型的最优超参数大部分为10。最大树深度越浅,树模型的分裂规则越简单,模型的拟合能力越弱而泛化能力越强。换言之,时序和分组时序交叉验证选出的XGBoost模型更可能出现欠拟合,更不容易出现过拟合。

对于XGBoost的行采样比例subsample,K折交叉验证的最优超参数大于其余五种方法。行采样比例越高,模型相对越复杂,模型的拟合能力越强而泛化能力越弱。换言之,K折交叉验证选出的模型更可能出现过拟合,更不容易出现欠拟合。

总的来看,无论基学习器是逻辑回归还是XGBoost,时序和分组时序交叉验证都选出了更“简单”的模型,过拟合风险更低。

模型性能

接下来我们展示逻辑回归和XGBoost的模型性能,关注样本内和测试集的各月平均正确率和AUC,详细结果如下表所示。

接下来我们展示逻辑回归和XGBoost的模型性能,关注样本内和测试集的各月平均正确率和AUC,详细结果如下表所示。

对于逻辑回归(图表14),2013年6月前,时序交叉验证稳定地弱于K折,其余方法和K折无明显差异;2013年6月后,时序和分组时序交叉验证稳定地优于K折,乱序递进式和乱序分组递进式几乎重合(由于最优超参数相同,参考图表12)并且略优于K折。对于XGBoost(图表15),除2017年4~7月以外,时序和分组时序交叉验证均稳定、大幅优于K折,其余三种交叉验证方法也均略优于K折。

需要说明的是,图表14中训练集折半的K折交叉验证自2016年2月后一直持平,原因是该方法得到的最优超参数和K折相同(参考图表12),其AUC和K折AUC的差值为0,故差值的累积值保持不变。图表17、图表19的情况与之相同。

综合上述结果,我们可以对六种交叉验证方法的表现大致进行排序:

1. 按样本内表现排序:时序 < 分组时序 < 三种新的基线模型 < K折(原基线模型);

2. 按测试集表现排序:分组时序 > 时序 > 三种新的基线模型 > K折(原基线模型)。

至此我们可以回答本文开头提出的问题:时序相对于K折交叉验证的提升主要源于保留时序信息(假设1)还是使用更少的样本(假设2)?首先,和K折相比,三种新的基线模型使用更少样本,其表现略优于K折,表明模型表现的提升确实部分源于使用更少样本。其次,和三种新的基线模型相比,时序和分组时序交叉验证保留了时序信息,其表现优于三种新的基线模型,表明模型表现的提升主要源于时序信息的保留。

单因子测试

将机器学习模型的输出视为单因子,则可进行单因子测试。六种交叉验证方法单因子测试回归法和IC值分析法的详细结果如下表所示。和模型性能结果类似,无论基学习器是逻辑回归还是XGBoost,对于|t|均值、t均值、因子收益率均值、RankIC均值这四项指标,时序和分组时序交叉验证表现相对较好,其次是三种新的基线模型,K折交叉验证表现相对较差。时序和分组时序交叉验证的缺点在于RankIC的波动较大,从而导致IC_IR和IC>0占比这两项指标不占优势。

在每个测试月份,将除K折之外五种交叉验证方法的因子收益率减去K折的因子收益率,再逐月累加,结果如下图所示。对于逻辑回归(图表17),分组时序交叉验证在大部分时间段优于其余五种方法,时序交叉验证的波动较大,乱序递进式和乱序分组递进式几乎重合且略优于K折。对于XGBoost(图表18),2015年4月前,五种方法相比于K折无明显优势;2015年4月后,时序和分组时序交叉验证稳定、大幅优于三种新基线模型,新基线模型略优于K折。

在每个测试月份,将除K折之外五种交叉验证方法的RankIC减去K折的RankIC,再逐月累加,结果如下图所示。对于逻辑回归(图表19),时序和分组时序交叉验证在2017年弱于K折,其余时间段均优于K折;其余三种新基线模型在2015年后小幅优于K折。对于XGBoost(图表20),五种交叉验证方法在2011年9月~2012年2月、2017年和2018年上半年弱于K折,其余时间段优于K折,时序和分组时序的优势更明显。

需要特别说明的是,下图展示的RankIC结果和《对抗过拟合:从时序交叉验证谈起》一文的RankIC结果有差异,原因在于本文计算RankIC时对因子做行业市值中性处理,此前研究未做中性化。实际上,如不做中性化,时序和分组时序交叉验证大部分时间段均稳定优于其余四种基线模型。

单因子分层回测的详细结果如下表所示。无论基学习器是逻辑回归还是XGBoost,时序和分组时序交叉验证在TOP组合年化收益率、多空组合年化收益率、多空组合Calmar比率上均优于其余四种基线模型,分组时序略优于时序。时序和分组时序交叉验证的缺点在于多空组合收益波动较大,因而在多空组合夏普比率上不占优势。

相比于多空组合表现,我们更关注因子在多头的表现。TOP组合的详细绩效分析如下表所示。无论基学习器是逻辑回归还是XGBoost,时序和分组时序交叉验证在TOP组合年化收益率、夏普比率、最大回撤、Calmar比率上全面优于其余四种基线模型,分组时序交叉验证略优于时序。

构建策略组合及回测分析

基于六种交叉验证方法,我们构建了行业、市值中性全A选股策略并进行回测。首先考察基学习器为逻辑回归的情形,如下表所示(注意超额收益最大回撤的数值越小,色阶越偏红)。当行业市值中性基准为沪深300时,相比于其它方法,时序和分组时序交叉验证在年化超额收益率、超额收益最大回撤、信息比率、Calmar比率上稍有优势。当行业市值中性基准为中证500时,分组时序交叉验证整体上看略优于其它方法,但是也和策略组合的个股权重偏离上限有关。

其次考察基学习器为XGBoost的情形。当行业市值中性基准为沪深300时,时序交叉验证在个股权重偏离上限较小时表现相对较好,分组时序交叉验证没有明显优势。当行业市值中性基准为中证500时,时序交叉验证在年化超额收益率上稍占优,但是在其余指标上无优势,分组时序交叉验证也没有优势。

总的来说,由于构建策略组合涉及中性化基准的选取,个股权重偏离上限的选取,并且和单因子测试相比又增加了市值中性的限制,相当于在机器学习模型上增加了更多不可控因素,因而得到的回测结果相对杂乱。客观地看,时序和分组时序交叉验证得到了更好的模型和更好的单因子测试效果,但是我们的行业市值中性策略组合没有把模型的优势体现出来。如何设计更合理的策略组合构建方式从而展现出模型的优势,如何修改机器学习模型以适应特定的策略组合构建方式,可能是未来的思考方向。

总结和讨论

如何防止过拟合是机器学习研究者始终面临的考验。随着对该问题理解的逐步深入,我们发现机器学习在其它领域的成功经验并不能直接照搬到金融领域,其中最突出的问题之一是交叉验证的方式,经典的K折交叉验证应用于金融时间序列数据可能导致过拟合。我们也发现时序交叉验证是对抗过拟合的重要途径之一。

本文从基线模型的设置和训练集验证集的精确切分两个角度,对原有时序交叉验证方法提出改进。通过对比时序交叉验证、分组时序交叉验证以及四种基线模型,我们得到以下重要结论:

1. 从模型性能来看,将六种交叉验证方法按样本内表现排序:时序 < 分组时序 < 三种新的基线模型 < K折。从模型性能和单因子测试结果来看,将各方法按测试集表现排序:分组时序 > 时序 > 三种新的基线模型 > K折。K折交叉验证得到的模型表现出较强的过拟合,时序和分组时序交叉验证能够一定程度上减轻过拟合。

2. 新基线模型的引入使得我们能够对时序为何优于K折进行归因分析。首先,和K折相比,三种新的基线模型使用更少样本,其表现略优于K折,说明模型表现的提升确实部分源于使用更少样本。其次,和三种新的基线模型相比,时序和分组时序交叉验证保留了时序信息,其表现优于三种新的基线模型,说明模型表现的提升主要源于时序信息的保留。

3. 通过对scikit-learn库model_selection包的改造,我们得以实现样本的精确切分,确保验证集在时序上严格位于训练集之后。相比于原始时序交叉验证,改造后的分组时序交叉验证在模型表现上有小幅提升。

最后,思考一个有趣的问题。我们通过《对抗过拟合:从时序交叉验证谈起》和本文证明了K折交叉验证存在过拟合风险,时序和分组时序交叉验证能够减轻过拟合。然而,如何证明这两篇报告得到的结论不是一种过拟合呢?这样的质疑似乎可以永远迭代下去。进一步地想,作为人类,我们生也有涯,只能从有限的历史中探寻规律。任何基于历史研究得到的结论都不能保证未来仍然成立,那么就都可以被质疑为过拟合,但是能够指导我们做出决策的,也仅有那些“过拟合”的经验而已。说到底,我们需要一套科学的手段评估过拟合的程度,排除那些真正有害的过拟合。如何破解量化研究的过拟合困境,可能成为未来研究的重点。

附录:分组时序交叉验证的代码实现

在进行原始时序交叉验证时,我们使用基于Python的scikit-learn库model_selection包下的TimeSeriesSplit类进行数据切分。默认参数下,TimeSeriesSplit将样本内数据集等分成若干份,第i次验证时取前i份作为训练集,第i+1份作为验证集。但是在选股问题里,每个月份包含的有效样本数不一致。如果简单调用TimeSeriesSplit,会出现同一月份数据一部分出现在训练集一部分出现在验证集的情况。

本文提出分组时序交叉验证方法,其思路是在切分时不将数据等分,而是切在相邻两个月的分界处,确保验证集在时序关系上严格位于训练集之后。分组时序交叉验证的难点不在于方法构想,而在于实现。我们需要对scikit-learn库的model_selection包进行改造,增加一个新的类GroupTimeSeriesSplit。将样本所属月份通过参数groups传递给GroupTimeSeriesSplit类。代码实现方式分为三步:1)修改model_selection模块的_split.py;2)修改model_selection包的__init__.py;3)在主函数中调用GroupTimeSeriesSplit。核心是第一步。

下面我们展示具体的代码实现步骤,测试用的Python版本为3.6,scikit-learn库版本为0.19及0.20。

修改model_selection包的_split.py

_split.py是model_selection包的核心模块之一,用来实现各种训练集和验证集的切分方法。我们新定义的GroupTimeSeriesSplit类继承自_BaseKFold类(第1行);在类的初始化方法__init__中定义了传递给类的唯一参数n_splits(第2行)。n_splits表示验证次数,通常设为折数n_folds减1(第10行),例如验证11次则将数据分成12份。

GroupTimeSeriesSplit类的核心方法是split,该方法接受三个参数:特征X,标签y,分组标签groups(第7行)。参数groups不能为空值,否则抛出错误ValueError(12~13行)。

读取到groups参数后(如每条样本的月份编号),对groups去重并升序排列,得到去重后的分组标签unique_groups(第16行),分组个数n_groups即为unique_groups的长度(第17行)。

我们规定分组个数n_groups必须为折数n_folds的整数倍。例如72个月样本内数据集是折数12的倍数,从而保证每一折包含整数个月。若不满足整除性要求,则抛出错误ValueError(19~22行)。

将分组标签groups转换为ndarray类型(第24行),计算每一折包含的分组个数n_groups_per_fold(第25行)。

对每一次验证进行遍历(第27行)。每一次迭代中,首先确定训练集包含的分组编号train_groups(第28行),其次确定验证集包含的分组编号test_groups(第29行),最后寻找分组标签groups介于train_groups最大最小值之间的样本作为训练集(第30行),分组标签groups介于test_groups最大最小值之间的样本作为验证集(第31行),通过yield返回当前迭代的结果。

修改model_selection包的__init__.py

__init__.py是model_selection包的模块之一,当我们导入model_selection包时,实际上是导入了该__init__.py文件。对__init__.py的修改较为简单,只需要添加两行代码。首先从之前修改的_split中新导入GroupTimeSeriesSplit类(第1行),其次在__all__元组中新加入’GroupTimeSeriesSplit’字符串(第6行)。

主函数中调用GroupTimeSeriesSplit类

下面我们展示如何在主函数中调用之前写好的GroupTimeSeriesSplit类。我们将以XGBoost分类器作为基学习器,并使用网格搜索方法对一定范围内的超参数组合进行遍历。需要事先导入XGBoost分类器(第1行),GroupTimeSeriesSplit类和网格搜索(第2行)。

创建XGBoost分类器的实例化对象xgb。为保证实验结果可重复,设置随机数种子点random_state为任意整数,这里设为42(第4行)。对三项超参数进行调参,分别设置学习速率learning_rate(第5行)、最大树深度max_depth(第6行)、行采样比例subsample(第7行)的网格搜索范围,保存为列表变量parameters。

创建分组时序交叉验证GroupTimeSeriesSplit类的实例化对象gptscv,传入唯一参数验证次数n_splits,参数值设为11(第9行)。

创建网格搜索的实例化对象clf,参数基学习器estimator设为之前定义的xgb,参数网格搜索范围设为之前定义的parameters,参数模型评价指标scoring设为’roc_auc’即AUC,参数交叉验证方法cv设为gptscv的split方法(第12~13行)。

需要说明的是,gptscv.split本质上是一个生成器,用来返回每次验证的训练集和验证集的行索引。这里split方法传入三个输入参数:特征X,标签y,分组标签groups。

最后我们调用clf的fit方法,使用定义好的交叉验证和网格搜索模型,对样本内数据集的特征X和标签y进行拟合。拟合结果可以通过查看clf的属性得到。

风险提示

时序和分组时序交叉验证方法是对传统模型调参方法的改进,高度依赖基学习器表现。该方法是对历史投资规律的挖掘,若未来市场投资环境发生变化导致基学习器失效,则该方法存在失效的可能。时序和分组交叉验证方法存在一定欠拟合风险。

作者:华泰金工林晓明团队