摘要

极端梯度提升 (eXtreme Gradient Boosting) 是一种基于决策树的集成机器学习方法,适用于分类和回归问题。其优点是速度快、效果好、能处理大规模数据、支持自定义损失函数等。

极端梯度提升(XGBoost)

XGBoost算法是运用提升法(boosting)建立多个CART回归树进行预测的机器学习方法。最终的预测结果由每棵CART树的结果累加得到。

套袋法和提升法都是对决策树的组合模型,本文将从这两种方法的对比入手,归纳随机森林和XGBoost两种算法的异同,再由CART回归树的建立介绍XGBoost的重要特点。

01

提升法(boosting)和套袋法(bagging)

在《基于随机森林算法构建投资组合》,我们提到单个决策树虽然具有解释性强、直观方便的优点,但其准确性无法与其它回归分类方法媲美。这时我们需要用到组合模型,即套袋法或提升法建立多棵决策树共同预测,减少单一决策树的不足。

随机森林采用的是套袋法,每棵决策树在样本中随机选一批样本进行独立训练,每棵决策树是相互独立的。而XGBoost采用的是提升法,由多个相关联的决策树联合决策,每一棵决策树的样本都与前面决策树的训练和预测有关。

套袋法和提升法都基于有放回的抽样思想。不同的是,套袋法采用有放回的均匀取样,而提升法根据错误率来取样,每次训练后,对训练失败的训练例赋予更大的权重(初始时对每个训练例赋予相同的权重)。因此,套袋法得到的各轮训练集是随机且相互独立的,而提升法得到的各轮训练集的选择与前面各轮的学习结果有关。

随机森林和XGBoost的异同归纳如下:

▷相同点:

1) 都由多棵决策树组成,并根据多棵树共同决定预测结果;

2) 都采用行(样本)采样、列(特征)采样,防止过拟合

▷不同点:

1) 随机森林中的树既可以是分类树,也可以是回归树,而XGBoost只能由CART回归树组成(XGBoost也支持线性分类器);

2) 随机森林中的树是并行生成的,即每一棵树生成是独立的;XGBoost则是串行生成的,下一棵树要去拟合前面所有树的残差;

3) 随机森林通过降低方差来提高性能,XGBoost通过降低偏差来提高性能(赋予训练失败的训练例更大的权重)。

02

CART回归树的建立

CART(Classification and Regression Trees),即分类与回归树。XGBoost的预测结果是由所有决策树的结果累加得到,每棵决策树都需要返回一个确定的预测值而不是一个类别,因此XGBoost基于CART回归树(但XGBoost算法既可用于分类,也可用于回归)。建立XGBoost模型需要建立CART回归树。要确定一棵CART回归树,需要确定两个要素,一是树的结构,二是各个叶子节点的值。所以用XGBoost求预测值的问题被分解为确定CART回归树要素的问题。

因为建立每棵树都是为了尽可能地拟合真实值与前面所有树预测结果的残差,如果用表示第i个样本的真实值,表示用t棵树预测的结果,建立第t棵树的目标函数可以表示为

表示第t棵树的预测值,是损失函数,是第t棵树的复杂度。XGBoost的一大特点就是在目标函数中加入树的复杂度作为正则化项,目的是让模型尽量简单,越简单的模型在未来的预测中倾向于具有更小的方差,使预测更稳定。由叶子的节点数T,叶子节点值wj,以及γλ两个系数决定。在使用XGBoost时,你可以设定系数大小。

将目标函数作泰勒二阶展开,并对wj求导,可以得出目标函数最小时的wj*和相应的目标函数值。目标函数可以衡量树结构的好坏,值越小,结构越好。根据这两个值,我们可以找出第t棵CART回归树的两个要素。

值得注意的是树的结构近乎无限多,无法测算所有可能结构的好坏程度。这时,我们采用贪心法,即只考虑当前节点的划分最优;同时对每个特征的分割也需要确定分割的标准(特征值的个数为n时,共有2^n-2种可能的划分),我们采用近似法,将样本按照预测误差对已知模型的一阶导从小到大排列,然后进行遍历,查看每个节点是否需要分割(特征值的个数为n时,共有n-1种划分)。通过以上方法,我们可以建立CART回归树,进而得到XGBoost模型。

03

XGBoost算法的特点

将XGBoost算法的主要特点归纳如下:

1) 将模型的复杂度加入到目标函数的正则项中,避免过拟合;

2) 损失函数由泰勒二阶展开,加快优化速度;

3) 在寻找树的最优结构和最佳分割点时,运用了贪心算法和近似贪心算法,用来加速和减小内存消耗;

4) XGBoost支持并行处理,不是模型上的并行,而是特征上的并行,指的是将特征值排序后以block形式储存在内存中,在后面的迭代中重复使用这个结构。

基本思路

本文将XGBoost模型作为一个股票的分类器。我们在这里把股票未来一个月的收益作为预测目标,把未来一个月收益率高的样本看做正例,收益率低的样本看做负例,在本文中我们将10%作为划分比例,读者可以用自己的方法来定义正例和负例。在构建组合时,我们把股票的特征输入已经训练好的模型中,模型就会输出每一条样本属于正例的概率,然后将这个概率作为选股因子。

01

数据说明

本文所使用的原始数据集包含2011年1月至2018年9月每个月月末的横截面数据,字段包括月末日期、股票代码、未来收益率、个股特征(包含停牌状态、ST状态、上市天数)等用于判断是否符合交易条件的特征和EP、ROE等参与训练用于选股的特征。由于本文使用本地数据集,读者如果有复现需要,可以根据文末的代码实例构建一个类似的数据集。

在输入模型进行训练之前需要先对数据集进行预处理。数据集包含不满足可交易条件的样本,需要将这些样本剔除。被定义为可交易的股票须同时满足以下条件:1)非ST;2)非停牌;3)上市时间超过3个月。

02

XGBoost的sklearn接口实现

模型训练

在Python生态圈中,有专门为XGBoost算法而设计的开源库——XGBoost。XGBoost库具有一系列优秀的特性。在效率方面,它高效的C++实现通常能够比其它机器学习库更快的完成训练任务;在灵活性方面,它可以深度定制每一个子分类学习器,并且灵活地选择损失函数;在易用性方面,XGBoost库也提供了各种语言的封装,使得不同语言的用户;最后,在可扩展性方面,它也提供了分布式训练,其分布版本可以在多种平台运行。本文也将使用XGBoost库实现算法。

同时,sklearn由于其API设计科学、功能全面,已经成为单机机器学习的标准。因此,很多其他机器学习库即使有自己的底层实现和API,也会在不同程度上兼容sklearn的API,以降低使用者的学习成本,并方便与sklearn对接。在Keras、TensorFlow等热门的机器学习库中都能找到sklearn API的影子。XGBoost也不例外,除了XGBoost的原生API以外,它还专门实现了一套sklearn API(详见参考文献中的XGBoost文档)。以下是对XGBoost库中sklearn API的介绍:

model = xgboost.XGBClassifier( )对模型进行初始化,括号里是用户需要输入的参数,这些参数都只和模型有关,和实际的训练数据无关。不同的模型有不同的参数,对于XGBoost的树形模型,主要参数有:

1) boost:输入gbtree、dart为树模型,gblinear为线性函数模型;

2) n_estimators:生成树的数目;

3) eta:学习率。更新时使用的下降步长,用于防止过拟合;

4) max_depth:树的最大深度。该值越大树的模型越复杂,越容易发生过拟合;

5) silent:0开启打印运行日志,1进入安静模式;

6) n_jobs:线程数目。

model.fit(X_train, y_train):调用fit方法之后,model对象的内部状态会发生改变,用户使用这个训练后的模型对新数据进行预测。

model.predict:对于回归问题,predict方法输出对结果的预测值;对于分类问题,输出的是样本属于第一类的概率。在分类问题中,还可以使用predict_proba方法,输出样本属于每个分类的概率。

注意,上述方法输出的数据结构多为numpy.array,而我们项目主要处理的数据结构是pandas.Series和pandas.DataFrame。因此我们需要定义以下两个wrapper函数,将输出转化为我们所需的数据结构。

make_model:输入所有历史数据,创建并训练模型。在实现上,它接受了一些主要参数用于创建XGBoost模型,在内部调用get_training_dataset得到训练集,调用model.fit训练模型。

make_factor:我们最终的目标是通过机器学习模型得到一个选股因子,因此我们定义一个函数,在预测日,根据模型进行预测,将模型的输出作为因子。在实现上,它在内部调用model.predict_proba方法,并将其转换为与我们项目一致的数据结构。

def make_model(rebalance_date, data, window=48,               n_estimators=100, max_depth=3, booster='gbtree', **kwargs):    X, y = get_training_dataset(rebalance_date, data)        if 'n_jobs' not in kwargs:        kwargs['n_jobs'] = 4    if 'slient' not in kwargs:        kwargs['slient'] = True        model = xgb.XGBClassifier(n_estimators=n_estimators, max_depth=max_depth, **kwargs)    model.fit(X,y)    return modeldef make_factor(rebalance_date, model, data):    features = get_prediction_dataset(rebalance_date, data)    res = pd.Series(model.predict_proba(features)[:,1], pd.MultiIndex.from_product([[rebalance_date], features.index], names=['date', 'symbol']))    return res

本文以48个月为窗口进行滚动训练。初始化时需要用掉的样本数一般称为burn in。在我们的例子里,第一个模型需用掉48期,故BURNIN_PERIODS = 48。模型训练一般在换仓日进行,我们先定义一个换仓日列表rebalance_dates用于循环,然后循环训练集以获得48期的选股因子数据。

BURNIN_PERIODS = 48rebalance_dates = raw_data.index.get_level_values('date').unique()[BURNIN_PERIODS: ]factor_list = []model_dict = {}for date in rebalance_dates:    print('Training model for {}'.format(date))    model = make_model(date, data)    model_dict[date] = model        factor = make_factor(date, model, data)    factor_list.append(factor)factor = pd.concat(factor_list)

构建目标组合

机器学习模型负责对每一条样本进行预测,得到每个个股的“分数”。本文将展示利用分位数组合构建目标持仓并计算组合净值的方式。

01

分位数组合

以下使用的构建分位数(quantile)组合的方法是每一期买入分数位于当前股票池前10%的股票。在这种方法下,每一期的持仓数量会略有不同,随着当期可投资股票的数量变化而变化。按分位数构建筑的方法还会用来评价因子的单调性,在这里我们同时计算十个分位数组合,并考察其单调性。

在实际操作中,我们使用pd.qcut将指定因子对每个横截面Series按分位数分组,返回每个个股每一天所属的组合,然后我们从分组构建目标组合。

def make_factor_data(factor, quantiles=10):    quantile_labels = list(range(1, quantiles + 1))    factor_quantile = (factor.groupby(level='date', group_keys=False).apply(pd.qcut,q=quantiles,labels=quantile_labels))   res = pd.concat({'factor': factor, 'factor_quantile': factor_quantile},axis=1)    return res

02

计算净值

计算组合的累计收益,一个简单的算法是:直接将个股的日度收益加权平均算出组合的日度收益,再累乘得到整个组合的累计收益。但这个算法对每一天都采用了相同的权重,相当于每一天都在rebalance。而实际各持仓股票的权重每天都会波动,只有在调仓日才等于目标权重。因此这种算法只能看作实际收益的近似值。

在这里我们采取更为精确的做法:计算对各持仓股票的累计收益,而不是日度收益进行加权平均。相当于期初将资金按权重分配到每个股票,期末各持仓股票的价值等于权重乘以累计收益,整个组合在期末的价值等于各持仓股票价值之和。但每一次调仓持仓股票及目标权重都可能发生变化,因此需要分别计算每个持仓期的组合净值,最后再将各期净值按时间顺序连接起来。而对净值的连接,相当于用上一时间段最后一天的净值作为初始资金,再投资到下一期。

以上计算方法可以总结为以下流程:使用net_value作为计算组合净值的主函数,以换仓日期、各换仓日的目标组合、所有个股日度收益为输入,循环调用period_net_value计算各个持仓周期的净值,最后调用merge_period_net_value按照再投资的方式将各个阶段的净值连接为整个回测区间的净值。

def net_value(rebalance_dates, portfolio_weights, daily_returns):    net_value_list = []    for start, end in zip(rebalance_dates[:-1], rebalance_dates[1:]):        weights = portfolio_weights[start]        # 每个持仓周期为左闭右开区间,以确保一个交易日属于且只属于一个持仓周期        holding_period = (daily_returns.index >= start) & (daily_returns.index < end)        assets_held = weights.index         rtn = daily_returns.loc[holding_period, assets_held].fillna(0)        net_value_list.append(period_net_value(rtn, weights)    res = merge_period_net_value(net_value_list)    return res    def period_net_value(daily_returns, weights):    asset_net_value = (1 + daily_returns).cumprod()    normalized_weights = weights / weights.sum()    portf_net_value = asset_net_value.dot(normalized_weights    return portf_net_value    def merge_period_net_value(period_net_values):    net_value_list = []    init_capital = 1    for nv in period_net_values:        nv *= init_capital        net_value_list.append(nv)        # 下一段净值的初始资金是上一段最后一天的值        init_capital = nv.iat[-1]    res = pd.concat(net_value_list)    # 整个回测期第一天的净值一定是1, 第一天的return其实用不到    res.iloc[0] = 1    return res

使用各换仓日分数前10的股票构建投资组合并计算净值:

top_10_portf = OrderedDict()for date, df in factor.groupby(level='date'):    top_10_portf[date] = pd.Series([1/10], index=df.nlargest(10).index.get_level_values('symbol'))

参考文献

陈天奇. Introduction to Boosted Trees.2014.

https://xgboost.readthedocs.io/en/latest/tutorials/model.html

机器学习算法系列(8): XgBoost.

https://plushunter.github.io/2017/01/26

欢迎点击阅读原文查看研究全部内容

题图 by Franki Chamakion Unsplash

Happy Coding

米筐量化研究平台

交流|学习|改变

长按扫码关注我们

xgboost算法_基于XGBoost算法构建投资组合相关推荐

  1. snm算法_基于SNM算法的大数据量中文地址清洗方法-计算机工程与应用.PDF

    基于SNM算法的大数据量中文地址清洗方法-计算机工程与应用 108 2014 ,50(5 ) Computer Engineering and Applications 计算机工程与应用 基于SNM ...

  2. snm算法_基于SNM算法的大数据量中文商品清洗方法

    基于 SNM 算法的大数据量中文商品清洗方法 ∗ 张苗苗 苏 勇 [摘 要] 摘 要 SNM 算法即邻近排序算法,是英文数据清洗最常用的算法[ 1 ] . 目前为止,因为中英文语义的差异等一些原因,中 ...

  3. js实现kmp算法_基于KMP算法JavaScript的实现方法分析

    算法的核心是部分匹配表和回退算法,部分匹配表的实现如下: function kmpGetStrPartMatchValue(str) { var prefix = []; var suffix = [ ...

  4. 【Matlab】智能优化算法_蜻蜓优化算法DA

    [Matlab]智能优化算法_蜻蜓优化算法DA 1.背景介绍 2.灵感 3.公式推导 3.1 勘探和开发操作 4.算法流程图 5.文件结构 6.伪代码 7.详细代码及注释 7.1 DA.m 7.2 d ...

  5. 【Matlab】智能优化算法_蚁狮优化算法ALO

    [Matlab]智能优化算法_蚁狮优化算法ALO 1.背景介绍 2.基本思想 3.公式推导 3.1 ALO算法的运算符 3.2 蚂蚁的随机游动 3.3 困在蚂蚁坑里 3.4 修建陷阱 3.5 蚂蚁划向 ...

  6. 【Matlab】智能优化算法_灰狼优化算法GWO

    [Matlab]智能优化算法_灰狼优化算法GWO 1.背景介绍 2.基本思想 2.1 等级制度 2.2 狩猎方式 3.公式推导 3.1 社会等级制度 3.2 包围猎物 3.3 包围猎物 3.4 攻击猎 ...

  7. c++多边形扫描线填充算法_基于3DGIS技术的梯形格网构建及其简化算法设计

    传统矢量地图LOD绘制流程包含简化.剖分与渲染三个步骤.由上述分析可知,传统矢量地图LOD绘制流程中简化与剖分是两个独立的过程,重复的剖分计算导致其渲染效率相对低下.梯形格网方法解决了传统方法中重复剖 ...

  8. vins中imu融合_基于非线性优化算法—当视觉SLAM遇到VINS会碰撞出怎样的火花?

    今天来给大家分享一个视觉SLAM中比较综合且复杂的系统-VINS.VINS旨在通过融合两个传感器测量数据获得移动机器人的位姿和特征点在空间中的位置,在现代控制理论学科中属于最优估计问题.在移动智能机器 ...

  9. fama matlab源码_基于优化算法改造的Fama-French三因子模型

    基于光大证券金融工程研报<站在巨人的肩膀上,从牛基组合到牛股发现 --FOF 专题研究系列之十六 >中提及的Carhart四因子Alpha优化模型,本文在Fama-French三因子模型上 ...

最新文章

  1. UVA 11491 Erasing and Winning 奖品的价值 (贪心)
  2. 如何阅读AI顶会论文,搭建深度学习知识体系框架?
  3. phpmyadmin mysql更新_mysql利用phpmyadmin实现数据库同步更新
  4. django前后端结合_Vue+Django前后端项目构建
  5. Unity3D碰撞触发函数
  6. 洛谷——P1590 失踪的7
  7. mysql 联合索引底层结构_MySQL联合索引底层数据结构
  8. 微信小程序实现微信登录详解(JAVA后台)
  9. 记事本编写java_编写运行最简单的java程序——使用记事本编写java程序
  10. cad插入块_CAD制图初学入门:CAD软件中属性定义的相关应用
  11. r730服务器安装系统蓝屏6,安装系统蓝屏解决解决方法
  12. python ipo模型是指什么?
  13. 淘宝直通车ROI,投产比怎么算,ROI计算公式
  14. 小红拿到了一个数组,她想取一些数使得取的数之和尽可能大,但要求这个和必须是 k 的倍数。你能帮帮她吗?
  15. poj2315足球游戏
  16. vivado 时序例外约束
  17. 《FLUENT 14.0超级学习手册》——1.3 CFD软件的构成
  18. CVPR 2022 | 微软提出MiniViT:极致压缩视觉Transformer
  19. 【转载】Mac安装 Java 反编译工具 JD-GUI
  20. win8计算机无法安装打印机驱动,win8怎么安装打印机驱动

热门文章

  1. 了解Python中的Args和Kwargs
  2. Win7如何修改文件夹背景颜色教学
  3. 职工工资程序c语言doc,职工工资管理系统C语言程序课程设计方案.doc
  4. mysql 上个月 汇总_还是问一个mysql的查询汇总的汇总问题
  5. 少打针少吃药 100个宝宝祛病小偏方
  6. 数据可视化(一)--DataFrame绘制饼图+输出中文标签+美化图表
  7. OpenGL-离屏渲染
  8. 我花10万学费买到这12张PPT
  9. 合并或注销重复领英帐号
  10. [原创]论当代大学生之劣根性