XGBoost R 教程

介绍

XGBoost 是 eXtreme Gradient Boosting 包的缩写。

本Vignette的目的是向您展示如何使用 XGBoost 构建模型并进行预测。

这是 @friedman2000additive 和 @friedman2001greedy 提出的梯度提升框架的一个高效且可扩展的实现。包含两个求解器:

  • 线性 模型 ;

  • 树学习 算法。

它支持多种目标函数,包括 回归分类排序。该包设计为可扩展的,因此用户也可以轻松定义自己的目标函数。

它已被用于赢得了几次Kaggle比赛。

它有几个特点:

  • 速度:它可以在 WindowsLinux 上使用 OpenMP 自动进行并行计算。通常情况下,它的速度比经典的 gbm 快 10 倍以上。

  • 输入类型:它接受几种输入数据类型:

    • 密集 矩阵: R密集 矩阵, 即 matrix ;

    • 稀疏 矩阵: R稀疏 矩阵, 即 Matrix::dgCMatrix ;

    • 数据文件:本地数据文件 ;

    • xgb.DMatrix: 它自己的类(推荐)。

  • 稀疏性:它接受 稀疏 输入,适用于 树提升器线性提升器,并且针对 稀疏 输入进行了优化;

  • 自定义:它支持自定义的目标函数和评估函数。

安装

GitHub 版本

对于每周更新的版本(强烈推荐),从 GitHub 安装:

install.packages("drat", repos="https://cran.rstudio.com")
drat:::addRepo("dmlc")
install.packages("xgboost", repos="http://dmlc.ml/drat/", type = "source")

Windows 用户需要先安装 Rtools

CRAN 版本

版本 0.4-2 已在 CRAN 上发布,您可以通过以下方式安装它:

install.packages("xgboost")

以前可用的版本可以从 CRAN 档案 获取

学习

在本教程中,我们将加载 XGBoost 包。

require(xgboost)

数据集展示

在这个例子中,我们的目标是预测一个蘑菇是否可以食用(就像在许多教程中一样,示例数据与你日常生活中使用的相同 :-)。

蘑菇数据引自UCI机器学习库。@Bache+Lichman:2013。

数据集加载

我们将加载包中嵌入的 agaricus 数据集,并将它们链接到变量。

数据集已经分为:

  • train: 将用于构建模型;

  • test: 将用于评估我们模型的质量。

为什么要将数据集 分成 两部分?

在第一部分我们将构建我们的模型。在第二部分我们将希望测试它并评估其质量。如果不划分数据集,我们将在算法已经见过的数据上测试模型。

data(agaricus.train, package='xgboost')
data(agaricus.test, package='xgboost')
train <- agaricus.train
test <- agaricus.test

在现实世界中,如何划分 traintest 数据取决于你。如何进行划分超出了本文的范围,然而 caret 包可能会 有所帮助

每个变量是一个包含两个元素的 列表,即 标签数据

str(train)
## List of 2
##  $ data :Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
##   .. ..@ i       : int [1:143286] 2 6 8 11 18 20 21 24 28 32 ...
##   .. ..@ p       : int [1:127] 0 369 372 3306 5845 6489 6513 8380 8384 10991 ...
##   .. ..@ Dim     : int [1:2] 6513 126
##   .. ..@ Dimnames:List of 2
##   .. .. ..$ : NULL
##   .. .. ..$ : chr [1:126] "cap-shape=bell" "cap-shape=conical" "cap-shape=convex" "cap-shape=flat" ...
##   .. ..@ x       : num [1:143286] 1 1 1 1 1 1 1 1 1 1 ...
##   .. ..@ factors : list()
##  $ label: num [1:6513] 1 0 0 1 0 0 0 1 0 0 ...

label 是我们数据集的结果,这意味着它是我们将尝试预测的二元 分类

让我们探索我们数据集的维度。

dim(train$data)
## [1] 6513  126
dim(test$data)
## [1] 1611  126

这个数据集非常小,以避免使 R 包过于庞大,然而 XGBoost 被设计为能够非常高效地管理大型数据集。

如下所示,data 存储在一个 dgCMatrix 中,这是一个 稀疏 矩阵,而 label 向量是一个 numeric 向量({0,1}):

class(train$data)[1]
## [1] "dgCMatrix"
class(train$label)
## [1] "numeric"

使用 XGBoost 的基本训练

这一步是整个过程中对我们模型质量最关键的部分。

基础培训

我们正在使用 train 数据。如上所述,datalabel 都存储在一个 list 中。

稀疏 矩阵中,包含 0 的单元格不会存储在内存中。因此,在一个主要由 0 组成的数据集中,内存大小会减少。这种情况非常常见。

我们将使用以下参数训练决策树模型:

  • objective = "binary:logistic": 我们将训练一个二分类模型;

  • max.depth = 2: 树不会很深,因为我们的情况非常简单;

  • nthread = 2: 我们将要使用的CPU线程数;

  • nrounds = 2: 数据将进行两次处理,第二次处理将进一步减少真实值与预测值之间的差异,从而增强模型。

bstSparse <- xgboost(data = train$data, label = train$label, max.depth = 2, eta = 1, nthread = 2, nrounds = 2, objective = "binary:logistic")
## [0]	train-error:0.046522
## [1]	train-error:0.022263

你的特征与 标签 之间的关系越复杂,你需要的遍历次数就越多。

参数变化

密集矩阵

或者,你可以将你的数据集放入一个 密集 矩阵中,即一个基本的 R 矩阵。

bstDense <- xgboost(data = as.matrix(train$data), label = train$label, max.depth = 2, eta = 1, nthread = 2, nrounds = 2, objective = "binary:logistic")
## [0]	train-error:0.046522
## [1]	train-error:0.022263
xgb.DMatrix

XGBoost 提供了一种将它们分组到 xgb.DMatrix 中的方法。你甚至可以添加其他元数据。这对于我们将要探索的最先进功能将非常有用。

dtrain <- xgb.DMatrix(data = train$data, label = train$label)
bstDMatrix <- xgboost(data = dtrain, max.depth = 2, eta = 1, nthread = 2, nrounds = 2, objective = "binary:logistic")
## [0]	train-error:0.046522
## [1]	train-error:0.022263
详细选项

XGBoost 有几个功能可以帮助你查看内部的学习进度。其目的是帮助你设置最佳参数,这是模型质量的关键。

查看训练进度最简单的方法之一是设置 verbose 选项(更多高级技巧请参见下方)。

# verbose = 0, no message
bst <- xgboost(data = dtrain, max.depth = 2, eta = 1, nthread = 2, nrounds = 2, objective = "binary:logistic", verbose = 0)
# verbose = 1, print evaluation metric
bst <- xgboost(data = dtrain, max.depth = 2, eta = 1, nthread = 2, nrounds = 2, objective = "binary:logistic", verbose = 1)
## [0]	train-error:0.046522
## [1]	train-error:0.022263
# verbose = 2, also print information about tree
bst <- xgboost(data = dtrain, max.depth = 2, eta = 1, nthread = 2, nrounds = 2, objective = "binary:logistic", verbose = 2)
## [11:41:01] amalgamation/../src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 6 extra nodes, 0 pruned nodes, max_depth=2
## [0]	train-error:0.046522
## [11:41:01] amalgamation/../src/tree/updater_prune.cc:74: tree pruning end, 1 roots, 4 extra nodes, 0 pruned nodes, max_depth=2
## [1]	train-error:0.022263

使用 XGBoost 进行基本预测

执行预测

我们构建的模型的目的是对新数据进行分类。如前所述,我们将使用 test 数据集进行这一步骤。

pred <- predict(bst, test$data)

# size of the prediction vector
print(length(pred))
## [1] 1611
# limit display of predictions to the first 10
print(head(pred))
## [1] 0.28583017 0.92392391 0.28583017 0.28583017 0.05169873 0.92392391

这些数字看起来不像 二元分类 {0,1}。在使用这些结果之前,我们需要进行一个简单的转换。

将回归转换为二元分类

XGBoost 唯一做的事情是 回归XGBoost 使用 标签 向量来构建其 回归 模型。

我们如何使用 回归 模型来进行二元分类?

如果我们考虑回归应用于我们数据的含义,我们得到的数字是数据将被分类为 1 的概率。因此,我们将设定规则,如果特定数据的此概率 > 0.5,则该观察结果被分类为 1(否则为 0)。

prediction <- as.numeric(pred > 0.5)
print(head(prediction))
## [1] 0 1 0 0 0 1

测量模型性能

为了衡量模型性能,我们将计算一个简单的指标,即 平均误差

err <- mean(as.numeric(pred > 0.5) != test$label)
print(paste("test-error=", err))
## [1] "test-error= 0.0217256362507759"

注意,算法在模型构建过程中没有接触过 test 数据。

步骤解释:

  1. as.numeric(pred > 0.5) 应用了我们的规则,即当概率(<=> 回归 <=> 预测)> 0.5 时,观察结果被分类为 1,否则为 0

  2. probabilityVectorPreviouslyComputed != test$label 计算真实数据与计算概率之间的误差向量;

  3. mean(vectorOfErrors) 计算 平均误差 本身。

最重要的是要记住,要进行分类,只需对 标签 进行回归,然后应用一个阈值

多类 分类的工作方式类似。

这个指标是 0.02 ,非常低:我们的 yummly 蘑菇模型工作得很好!

高级功能

以下大部分功能已经实现,以帮助您通过更好地理解其内容来改进您的模型。

数据集准备

对于以下高级功能,我们需要按照上述说明将数据放入 xgb.DMatrix 中。

dtrain <- xgb.DMatrix(data = train$data, label=train$label)
dtest <- xgb.DMatrix(data = test$data, label=test$label)

使用 xgb.train 衡量学习进度

xgboost (简单) 和 xgb.train (高级) 函数都可以训练模型。

xgb.train 的特殊功能之一是能够在每轮学习后跟踪学习进度。由于提升的工作方式,存在一个回合数过多导致过拟合的时刻。你可以将此功能视为交叉验证方法的表亲。以下技术将帮助你避免过拟合或优化学习时间,即尽早停止学习。

衡量模型学习进展的一种方法是向 XGBoost 提供一个已经分类的第二个数据集。因此,它可以在第一个数据集上学习,并在第二个数据集上测试其模型。在学习过程中,每轮之后都会测量一些指标。

在某种程度上,它类似于我们上面用平均误差所做的。主要区别在于,上面是在构建模型之后,而现在是在构建过程中测量误差。

为了这个示例的目的,我们使用 watchlist 参数。它是一个 xgb.DMatrix 的列表,每个都带有一个名称。

watchlist <- list(train=dtrain, test=dtest)

bst <- xgb.train(data=dtrain, max.depth=2, eta=1, nthread = 2, nrounds=2, watchlist=watchlist, objective = "binary:logistic")
## [0]	train-error:0.046522	test-error:0.042831
## [1]	train-error:0.022263	test-error:0.021726

XGBoost 在每一轮都计算了上述相同的平均误差指标(我们将 nrounds 设置为 2,这就是为什么我们有两行)。显然,train-error 数值与训练数据集(算法从中学习的那个)相关,而 test-error 数值与测试数据集相关。

训练误差和测试误差相关的指标非常相似,在某种程度上,这是有道理的:我们从训练数据集中学到的东西与测试数据集中的观察结果相匹配。

如果你用自己的数据集没有得到这样的结果,你应该考虑你是如何将数据集划分为训练集和测试集的。可能有些地方需要修正。再次,caret 包可能会 帮助

为了更好地理解学习进展,您可能需要一些特定的指标,甚至可以使用多个评估指标。

bst <- xgb.train(data=dtrain, max.depth=2, eta=1, nthread = 2, nrounds=2, watchlist=watchlist, eval.metric = "error", eval.metric = "logloss", objective = "binary:logistic")
## [0]	train-error:0.046522	train-logloss:0.233376	test-error:0.042831	test-logloss:0.226686
## [1]	train-error:0.022263	train-logloss:0.136658	test-error:0.021726	test-logloss:0.137874

eval.metric 允许我们在每一轮监控两个新的指标,loglosserror

线性提升

到目前为止,我们所进行的所有学习都是基于提升树的。XGBoost 实现了第二种算法,基于线性提升。与之前的命令唯一的区别是 booster = "gblinear" 参数(以及移除 eta 参数)。

bst <- xgb.train(data=dtrain, booster = "gblinear", nthread = 2, nrounds=2, watchlist=watchlist, eval.metric = "error", eval.metric = "logloss", objective = "binary:logistic")
## [0]	train-error:0.024720	train-logloss:0.184616	test-error:0.022967	test-logloss:0.184234
## [1]	train-error:0.004146	train-logloss:0.069885	test-error:0.003724	test-logloss:0.068081

在这种情况下,线性提升 比基于决策树的算法略微获得更好的性能指标。

在简单的情况下,这会发生是因为没有什么比线性算法更适合捕捉线性链接了。然而,决策树在捕捉预测变量和结果之间的非线性链接方面要好得多。因为没有银弹,我们建议你用你自己的数据集检查这两种算法,以便了解应该使用哪种算法。

操作 xgb.DMatrix

保存 / 加载

与保存模型类似,xgb.DMatrix 对象(它同时包含数据集和结果)也可以使用 xgb.DMatrix.save 函数保存。

xgb.DMatrix.save(dtrain, "dtrain.buffer")
## [1] TRUE
# to load it in, simply call xgb.DMatrix
dtrain2 <- xgb.DMatrix("dtrain.buffer")
## [11:41:01] 6513x126 matrix with 143286 entries loaded from dtrain.buffer
bst <- xgb.train(data=dtrain2, max.depth=2, eta=1, nthread = 2, nrounds=2, watchlist=watchlist, objective = "binary:logistic")
## [0]	train-error:0.046522	test-error:0.042831
## [1]	train-error:0.022263	test-error:0.021726

信息提取

可以使用 getinfo 函数从 xgb.DMatrix 中提取信息。接下来我们将提取 label 数据。

label = getinfo(dtest, "label")
pred <- predict(bst, dtest)
err <- as.numeric(sum(as.integer(pred > 0.5) != label))/length(label)
print(paste("test-error=", err))
## [1] "test-error= 0.0217256362507759"

从学习到的模型中查看特征重要性/影响

特征重要性与 R gbm 包的相对影响(rel.inf)相似。

importance_matrix <- xgb.importance(model = bst)
print(importance_matrix)
xgb.plot.importance(importance_matrix = importance_matrix)

从模型中查看树

你可以使用 xgb.dump 将你学习的树转储到文本文件中。

xgb.dump(bst, with_stats = TRUE)
##  [1] "booster[0]"
##  [2] "0:[f28<-1.00136e-05] yes=1,no=2,missing=1,gain=4000.53,cover=1628.25"
##  [3] "1:[f55<-1.00136e-05] yes=3,no=4,missing=3,gain=1158.21,cover=924.5"
##  [4] "3:leaf=1.71218,cover=812"
##  [5] "4:leaf=-1.70044,cover=112.5"
##  [6] "2:[f108<-1.00136e-05] yes=5,no=6,missing=5,gain=198.174,cover=703.75"
##  [7] "5:leaf=-1.94071,cover=690.5"
##  [8] "6:leaf=1.85965,cover=13.25"
##  [9] "booster[1]"
## [10] "0:[f59<-1.00136e-05] yes=1,no=2,missing=1,gain=832.545,cover=788.852"
## [11] "1:[f28<-1.00136e-05] yes=3,no=4,missing=3,gain=569.725,cover=768.39"
## [12] "3:leaf=0.784718,cover=458.937"
## [13] "4:leaf=-0.96853,cover=309.453"
## [14] "2:leaf=-6.23624,cover=20.4624"

你可以使用 ```xgb.plot.tree`` 绘制模型中的树。

xgb.plot.tree(model = bst)

如果你为 fname 参数提供一个路径,你可以将树保存到你的硬盘上。

保存和加载模型

也许你的数据集很大,训练模型需要花费很多时间?也许你不喜欢一遍又一遍地重复同样的任务?在这些非常罕见的情况下,你会想要保存你的模型并在需要时加载它。

对你有帮助的是,XGBoost 实现了这些功能。

# save model to binary local file
xgb.save(bst, "xgboost.model")
## [1] TRUE

xgb.save 函数在一切顺利时应返回 TRUE,否则会崩溃。

一个有趣的测试,看看我们保存的模型与原始模型有多么相同,就是比较两者的预测结果。

# load binary model to R
bst2 <- xgb.load("xgboost.model")
pred2 <- predict(bst2, test$data)

# And now the test
print(paste("sum(abs(pred2-pred))=", sum(abs(pred2-pred))))
## [1] "sum(abs(pred2-pred))= 0"

结果是 0?我们很好!

在某些非常特定的情况下,例如当你想从 caret 包中控制 XGBoost 时,你可能希望将模型保存为 R 二进制向量。请参见下文了解如何操作。

# save model to R's raw vector
rawVec <- xgb.save.raw(bst)

# print class
print(class(rawVec))
## [1] "raw"
# load binary model to R
bst3 <- xgb.load(rawVec)
pred3 <- predict(bst3, test$data)

# pred3 should be identical to pred
print(paste("sum(abs(pred3-pred))=", sum(abs(pred3-pred))))
## [1] "sum(abs(pred3-pred))= 0"

又是 0?看起来 XGBoost 工作得相当不错!

参考文献