作者:Luciano Strika
翻译:吴振东
校对:车前子
本文约4000字,建议阅读14分钟。
本文将介绍XGBoost的定义,并利用这一模型对人类指数官方数据集进行回归预测。
一座漂亮的森林,是如此的随机!来源:Pixabay
今天我们将会用XGBoost提升树对人类发展指数官方数据集进行回归预测。XGBoost是一种可以使用提升树进行多核并行训练的框架。今天我们将用XGBoost提升树对人类发展指数官方数据集进行回归预测。谁说监督学习全都是针对分类问题的?
XGBoost:是什么?
XGBoost是一种Python框架,它可以让我们使用提升树进行多核并行训练。在R语言中同样可以使用XGBoost,但在这里我们不多做介绍。
任务:回归
提升树是一种针对回归的机器学习模型。这意味着给模型输入集和数值标签,模型可以估计出一个函数使所输出的标签与输入相匹配。与分类问题不同,我们这里所关心的标签是连续值,而不是一组离散的类别值。
举个例子,我们已知一个人的体重和年龄,想预测他的身高,而不是将ta划分为男性、女性或其他类别。
对于每棵决策树,我们都是从根节点开始,然后根据判断结果分配到左边或右边子节点。最终,我们可以到达叶节点并返回这个叶节点中的值。
XGBoost模型:什么是梯度提升树?
提升树和随机森林很相似:他们都是决策树的融合。不管怎样,每个叶节点会在我们的预测空间上返回一个数值(或是向量)。
针对每一个非叶节点的子节点,提升树都需要决定:在一个确定的特征值和一个临界值之间进行数值比较。
目前只是定义了一棵回归树。提升树和随机森林到底有什么区别呢?
提升树和随机森林:不同之处
训练一棵提升树与随机森林不同,每次增加一颗新树时我们都需要改变标签。
对于每颗新树,我们需要用原始标签值减去前面所有树的预测值之和与学习率的乘积,来更新标签。
这样一来,每棵树都可以通过更正前面所有树的错误来进行有效的学习。
因此,在预测阶段,我们只需要直接返回所有树的预测值之和,再乘以学习率就可以了。
这也意味着,与随机森林和打包决策树不同,如果我们随意增加树的数量,那么这个模型就会过拟合。我们现在将要学习如何来解释这个问题。
想要更多地了解提升树,我强烈建议你去阅读XGBoost的官方文档。我从中学到了很多东西,文档中有一些精美的图片可以解释其中的基础知识。
XGBoost的官方文档:
https://xgboost.readthedocs.io/en/latest/tutorials/model.html
如果你想要深层次地挖掘这个模型,我强烈推荐《统计学习导论》,这是一本让我对这方面概念茅塞顿开的好书。
《统计学习导论》:
https://www.bookdepository.com/book/9781461471370/?a_aid=strikingloo&chan=ws
在Python上使用XGBoost
XGBoost的API相当简单明了,但是我们还是要学一些关于超参数的知识。首先我要向你们展示今天的任务。
今天任务的数据集:HDI(Human Development Index 人类发展指数)公开数据
HDI数据集包含很多关于大部分国家发展水平的信息,涉及到多个领域和多方面的度量,跨越了数十年的时间维度。针对今天的文章,我决定只选取最新的可用数据——2017年,以接近目前的实际情况。
接着我还需要对数据进行整理和清洗,使原数据集更易管理和使用。
想查看这篇文章的GitHub知识库可以访问此链接(https://github.com/StrikingLoo/XGboost_HDI_analysis),我建议你用Jupyter Notebook来进行阅读。我会把最新的相关章节加入其中。
1. 用Pandas进行数据预处理
首先我们要让这个数据集载入到内存中,由于每一列包含每一年的数据,各行中分别为国家和各种指标,处理起来非常困难。
我们可以通过下面这两行代码来改造这组数据:
{country: {metric1: value1, metric2: value2, etc.}
for country in countries }
现在我们可以把数据提供给XGBoost模型了。考虑到数据中的指标已经是数值型了,所以在训练前就不需要其他的预处理了。
import pandas as pd
import xgboost as xgb
df = pd.read_csv("2018_all_indicators.csv")
df = df[['dimension','indicator_name','iso3','country_name','2017']]
df.shape # (25636, 5)
df = df.dropna() # getting rid of NaNs
df.shape #(12728, 5)
df.iso3.unique().shape # 195 countries
这一小段代码的作用是删除数据中有空值的行,并告知我们共有195个不同国家的数据。
如果上面哪一项操作对你来说比较陌生,或者说是哪里有你无法理解的问题,请你先看一下我之前对pandas的介绍。
查看数据集中都有哪些指标(特征),我们使用unique方法。
df['indicator_name'].unique()
这里载入了97条数据,我就不一一列举了。有些数据是关于健康的,有些是关于教育的,有些是关于经济的,还有少部分是关于女性权利的。
我觉得最有意思的是我们的标签是期望寿命,而数据都是在讲一些关于国家的指标。
当然,你也可以试着用同样的代码分析不同的标签,得出结果后可以与我联系!
在我们获得了大量的指标后,我只是手动选取了其中一些我觉得与我们的标签有关联(或许没有)的指标,其实也可以选择其他的指标。
下面的代码是为了把我们这些形式古怪的数据集整理为地更易使用的格式。
def add_indicator_to(dictionary, indicator):
indicator_data = df[df['indicator_name']==indicator]
zipped_values = zip(list(indicator_data['iso3']), list(indicator_data['2017']))
for k, v in zipped_values:
try:
dictionary[k][indicator] = v
except:
print("failed with key: "+k+" on "+indicator)
indic = {}
indicators = [
'Share of seats in parliament (% held by women)',
'Infants lacking immunization, measles (% of one-year-olds)',
'Youth unemployment rate (female to male ratio)',
'Expected years of schooling (years)',
'Expected years of schooling, female (years)',
'Expected years of schooling, male (years)',
'Unemployment, total (% of labour force)',
'Unemployment, youth (% ages 15–24)',
'Vulnerable employment (% of total employment)',
]
for indicator in indicators:
add_indicator_to(indic, indicator)
最终我们把数据从词典形式转化成了DataFrame,我意识到这实际就是转置——原来的行变成了现在的列,列变成了行。
接着我是这样处理的。
final_df = pd.DataFrame(indic).transpose()
2. 特征相关性分析
假设我所选取的特征适合回归分析,但是这种假设只是基于我的个人直觉。
所以我决定检查一下这种假设在统计学上是不是成立,下面是我如何创建一个相关性矩阵。
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
fig = sns.heatmap(final_df.corr())
plt.title("Correlation Heatmap")
plt.tight_layout()
plt.savefig("correlation_heatmap", figsize=(96,72))
这是结果:
与预料中的一样,“出生时的预期寿命”与“接种疫苗(反对疫苗组织,请记住这一点!)”“就业情况脆弱”“教育情况”几项指标高度相关。
我希望用这些特征(矩阵中正负相关性强的特征组合)进行建模预测标签。
幸运的是,XGBoost还为我们提供了一个检查特征重要性方法,来检测模型是基于哪些特征进行预测的。
这与提升树相比是一种很大的优势,例如,解释神经网络模型的决策是非常有难度的,甚至连我们自己都很难相信这个模型是合理的。稍后文章会做详细介绍。
3. 训练XGBoost模型
终于到了训练模型的时候!在经历了所有的预处理阶段后,你是不是猜测这一阶段会非常困难?其实并不是,这里只需要一个简单方法调用。
我们首先要确定训练集数据和测试集数据中没有重复的部分。
# The features we use to train the model obviously can't contain the label.
train_features = [feature for feature in list(final_df) if feature != life_expec]
train_df = final_df.iloc[:150][train_features]
train_label = final_df.iloc[:150][life_expec]
test_df = final_df.iloc[150:][train_features]
test_label = final_df.iloc[150:][life_expec]
接下来就是你最期待的部分,这段代码是用来训练XGBoost模型。
import xgboost as xgb
dtrain = xgb.DMatrix(train_df, label=train_label)
dtest = xgb.DMatrix(test_df, label=test_label)
# specify parameters via map
param = {'max_depth':6,
'eta':1, #this is the default value anyway
'colsample_bytree':1 #this is the default value anyway
}
num_round = 15
initial_trees = xgb.train(param, dtrain, num_round)
你会注意到训练模型和形成预测的代码非常简单。不过在此之前,这里面有一个小圈套。你在这里所看到的这些值是模型的超参数,这会影响模型训练或预测的效果。
XGBoost 超参数入门
max_depth 指的是在集合中允许每棵树所到达的最大深度。如果这个参数很大,那么树就倾向于更复杂,在其他条件相同的情况下,通常会更快地出现过拟合()。
eta是学习率。就像我前面说过的一样,这个值会在拟合下一棵树之前乘以每棵树的输出,并在预测时将这些乘积求和。如果将eta设置成默认值1,它就没有任何作用。如果设置成小一点的数,这个模型的收敛时间会慢一些,但一般情况下拟合数据的情况会更好(存在过拟合风险)。它的作用与神经网络的学习率相似。
colsample_bytree指的是,对于每棵树产生分支时,有多少可用的列。默认值是1,代表着“所有都可用”。我们可能会设置一个较小的数值,这样所有的树就不会反复地只用那些最好用的特征。这样,这个模型对于不同数据分布的鲁棒性更好,而且过拟合的情况更少。
最后,num_rounds指的是训练的轮次:举例说就是在哪里需要我们检查是否增添新的树。如果相应轮次后目标函数没有改善,训练将会停止。
评估我们的结果
让我们来看下这个模型学习效果如何!
import math
def msesqrt(std, test_label, preds):
squared_errors = [diff*diff for diff in (test_label - preds)]
return math.sqrt(sum(squared_errors)/len(preds))
msesqrt(std, test_label, first_preds) # 4.02
2017年的预期寿命标准差略大于7(我用Pandas检查过),所以不要嘲笑我的均方根误差是4!整个训练过程超级快,尽管这么小的数据集并没有充分利用XGBoost的多核功能。
不过我感觉这个模型还是欠拟合:这意味着模型还没有发挥出它的全部潜力。
超参数调优:让我们来进行迭代
既然我们觉得模型欠拟合,那就让我们用更复杂的树(max_depth = 6),缩小学习率(eta = 0.1),并增加训练轮次到40。
param = {'max_depth':5,
'eta':.1,
'colsample_bytree':.75
}
num_round = 40
new_tree = xgb.train(param, dtrain, num_round)
# make prediction
new_preds = new_tree.predict(dtest)
这一次我们用了同样的指标,结果让人非常开心!测试集数据的错误率已经降到了3.15!这比我们标签标准差的一半还要低,而且也是有统计学准确性的。
想象一下,你只是基于他们国家的一部分统计数据去预测一个人的预期寿命,就可以做到预测误差仅为3年。(当然,这种解释有可能是错误的,因为在单一国家的预期寿命偏差绝对是非零的。)
理解XGBoost的决策:特征重要性
这个模型看起来非常准确。那么,它是基于哪些特征来做决定的呢?为了给予我们一定的帮助,XGBoost提供了plot_importance。这个方法可以列出所有特征重要性的排名(如果我们定义一个值N,那就会展示前N个最重要的特征)。
但重要性是如何来衡量的呢?
默认的算法会测量出我们的决策树所利用每个特征的百分比(使用某个特征的节点数与节点总数之比),这里也有其他的选择,每一种算法都不相同。
article on Medium对此提供了清晰的讲解,在看到这篇文章之前我并不是特别理解这些方法,只能把它们机械地联系在一起,实际上这只是用不同方式来解释同样一件事而已。
article on Medium:
https://towardsdatascience.com/be-careful-when-interpreting-your-features-importance-in-xgboost-6e16132588e7
针对我们的情况,第一个模型给出了这样的图表:
这代表着我们的假设是成立的,那些与标签正、负相关性强的特征同时也是模型中最重要的。
这是针对第二个也是表现更好的那个模型的特征重要性图表:
前三个特征在我们的两个模型中都最为重要,尽管第一个模型看起来过于依赖“expected years of schooling(期望受教育的年限)”。基于其他模型可能很难去做这样简洁的分析。
总结
XGBoost为我们提供了一个很好的回归模型,甚至可以协助我们理解它是用哪些特征去做预测的。
特征相关性分析帮助我们推倒出哪些特征或许是最重要的,实际情况与我们的预期也是相符的。
最后我觉得应该强调一下这种模型训练的速度到底有多快,即使我们刚才演示的那个例子没有很好地展现出这个特点。
以后我会试着用XGBoost模型来训练数量级更大的数据。如果你能想到哪些数据集,请告诉我!另外,如果这个数据集能运用到时间序列分析那就更有意思了,但是我在这些领域还没有太多的经验。有哪些相关的书籍、文章或其他来源可以推荐我去看一下吗?请在下面留言!
作者简介:
Luciano Strika是布宜诺斯艾利斯大学的计算机科学系学生,同时也是MercadoLibre的数据科学家。他会在www.datastuff.tech上发表一些关于机器学习的文章。
原文标题:
Intro to XGBoost: Predicting Life Expectancy with Supervised Learning
原文链接:
https://www.kdnuggets.com/2019/05/intro-xgboost-predicting-life-expectancy-supervised-learning.html
译者简介
吴振东,法国洛林大学计算机与决策专业硕士。现从事人工智能和大数据相关工作,以成为数据科学家为终生奋斗目标。来自山东济南,不会开挖掘机,但写得了Java、Python和PPT。
翻译组招募信息
工作内容:需要一颗细致的心,将选取好的外文文章翻译成流畅的中文。如果你是数据科学/统计学/计算机类的留学生,或在海外从事相关工作,或对自己外语水平有信心的朋友欢迎加入翻译小组。
你能得到:定期的翻译培训提高志愿者的翻译水平,提高对于数据科学前沿的认知,海外的朋友可以和国内技术应用发展保持联系,THU数据派产学研的背景为志愿者带来好的发展机遇。
其他福利:来自于名企的数据科学工作者,北大清华以及海外等名校学生他们都将成为你在翻译小组的伙伴。
点击文末“阅读原文”加入数据派团队~
转载须知
如需转载,请在开篇显著位置注明作者和出处(转自:数据派ID:datapi),并在文章结尾放置数据派醒目二维码。有原创标识文章,请发送【文章名称-待授权公众号名称及ID】至联系邮箱,申请白名单授权并按要求编辑。
发布后请将链接反馈至联系邮箱(见下方)。未经许可的转载以及改编者,我们将依法追究其法律责任。
点击“阅读原文”拥抱组织