xgboost介紹
xgboost是大規模並行boosted tree的工具,它是目前最快最好的開源boosted tree工具包,比常見的工具包快10倍以上。在數據科學方面,有大量kaggle選手選用它進行數據挖掘比賽,其中包括兩個以上kaggle比賽的奪冠方案。在工業界規模方面,xgboost的分佈式版本有廣泛的可移植性,支持在YARN, MPI, Sungrid Engine等各個平臺上面運行,並且保留了單機並行版本的各種優化,使得它可以很好地解決於工業界規模的問題。本文主要針對xgboost單機訓練&預測以及spark訓練與預測進行介紹&demo介紹,方便後面人借鑑使用。
xgboost-python環境&Demo
Anaconda 3安裝
Anaconda是專注於數據分析的Python發行版本,包含了conda、Python等190多個科學包及其依賴項。Anaconda通過管理工具包、開發環境、Python版本,大大簡化了你的工作流程。不僅可以方便地安裝、更新、卸載工具包,而且安裝時能自動安裝相應的依賴包,同時還能使用不同的虛擬環境隔離不同要求的項目。在 Anaconda 官網中是這麼宣傳自己的:適用於企業級大數據分析的Python工具。其包含了720多個數據科學相關的開源包,在數據可視化、機器學習、深度學習等多方面都有涉及。不僅可以做數據分析,甚至可以用在大數據和人工智能領域。
創建python環境
conda create -n xgboost-en
激活&退出環境
source/conda activate xgboost-env
source/conda deactivate
安裝xgboost
-
首先安裝libgcc: conda install libgcc
-
然後安裝xgboost: pip install xgboost
xgboost demo
訓練數據準備
xgboost支持libsvm格式,使得訓練加速,當然也支持numpy格式數據等,這裏使用libsvm格式數據,採用數據集可以從這裏
獲取
訓練
import xgboost as xgb
from sklearn.metrics import roc_auc_score
dtest = xgb.DMatrix('test.data')
dtrain = xgb.DMatrix('train.data')
param = {
'max_depth': 5,
'eta': 0.08,
'silent': 0,
'objective': 'binary:logistic',
'gamma': 0.2,
'subsample': 0.8,
'colsample_bytree': 0.8,
'eval_metric': 'auc',
'nthread': 16
}
watchlist = [(dtest, 'eval'), (dtrain, 'train')]
num_round = 100
bst = xgb.train(param, dtrain, num_round, watchlist)
bst.save_model('xgboost_demo.model')
# evaluate.
y_test = dtest.get_label()
y_pred = bst.predict(dtest)
auc = roc_auc_score(y_test, y_pred)
print (auc)
預測
import xgboost as xgb
from sklearn.metrics import roc_auc_score
xgbmodel = xgb.load('xgboost_demo.model')
dtest = xgb.DMatrix('test.data')
y_pred = bst.predict(dtest)
spark版xgboost環境&Demo
XGBoost4J-Spark這個項目通過使XGBoost適應Apache Spark的MLLIB框架,無縫集成XGBoost和Apache Spark。通過集成,用戶不僅可以使用XGBoost的高性能算法實現,還可以利用Spark強大的數據處理引擎實現以下功能:
-
特徵工程:特徵提取,變換,降維和選擇等.
-
管道:構造,評估和調整ML管道
-
持久性:持久性並加載機器學習模型甚至整個管道
依賴
XGBoost4J-Spark在0.72版本需要Apache Spark 2.3+,否則會出現各種異常情況,引入以下依賴:
<!--xgboost-->
<dependency>
<groupId>ml.dmlc</groupId>
<artifactId>xgboost4j</artifactId>
<version>0.72</version>
</dependency>
<dependency>
<groupId>ml.dmlc</groupId>
<artifactId>xgboost4j-spark</artifactId>
<version>0.72</version>
</dependency>
準備libsvm格式訓練數據
libSVM是臺灣林智仁(Chih-Jen Lin)教授2001年開發的一套支持向量機的庫,這套庫運算速度還是挺快的,可以很方便的對數據做分類或迴歸。由於libSVM程序小,運用靈活,輸入參數少,並且是開源的,易於擴展,因此成爲目前國內應用最多的SVM的庫。
libsvm格式訓練與測試數據文件的格式如下所示:
<label> <index1>:<value1> <index2>:<value2>…
每行包含一個實例,並且以字符’\n’結束。對於分類來講,
spark訓練讀取的libsvm格式有兩個要求:
1. index是從1開始的
2. index是必須是遞增的
我們可以通過程序生成相應格式的訓練文件。
xgboost Demo
spark訓練Demo
spark通過指定輸入格式是libsvm,會將數據解析爲Labeled point,定義如下:
case class LabeledPoint @Since("1.0.0") (
@Since("0.8.0") label: Double,
@Since("1.0.0") features: Vector)
label是Double類型,可以根據取值情況來進行迴歸或者是分類;features可能是dense vector也可能是sparse vector。
import ml.dmlc.xgboost4j.scala.spark.XGBoost
import org.apache.spark.ml.linalg.DenseVector
import org.apache.spark.sql.SparkSession
object SparkXgboostTrain {
def main(args: Array[String]) {
if(args.length != 3) {
println("usage: training_path test_path model_path")
sys.exit(1)
}
val inputTrainPath = args(0)
val inputTestPath = args(1)
val modelPath = args(2)
val spark = SparkSession
.builder()
.appName("SparkXgboostTrainApplication")
.config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
.enableHiveSupport()
.getOrCreate()
val trainDF = spark.read.format("libsvm").load(inputTrainPath)
val testDF = spark.read.format("libsvm").load(inputTestPath)
val params = List(
"eta" -> 0.08f,
"max_depth" -> 5,
"objective" -> "binary:logistic",
"gamma" -> 0.2f,
"subsample" -> 0.8f,
"colsample_bytree" -> 0.8f,
"eval_metric" -> "auc"
).toMap
// 訓練
val xgbModel = XGBoost.trainWithDataFrame(trainDF, params,
round=100, nWorkers=16, useExternalMemory=true)
xgbModel.save(modelPath)
// 預測
import spark.implicits._
val predictDf = xgbModel.transform(testDF).
select("label", "probabilities").
map(row => (row.get(0).asInstanceOf[Double],
row.get(1).asInstanceOf[DenseVector].toArray(1))).
toDF("label", "score")
predictDf.show()
}
}
完整的訓練腳本如上所示,先讀取libsvm格式的訓練和預測數據[schema <label Double, features Vector>],訓練後進行預測,預測結果會有如下幾列數據< features, label, rawPrediction, probability, prediction>,我們可以直接根據prediction來進行分類或者通過probability進行分數獲取,獲取方法
row.get(1).asInstanceOf[DenseVector].toArray(1))
然後我們就可以根據具體的業務進行判斷。
-----------------±---------±-------------------±-------------------±---------+
| features| label| rawPrediction| probability| prediction|
±----------------±---------±-------------------±-------------------±---------+
|[5.1,3.5,1.4,0.2]| 0.0|[3.45569849014282…|[0.99579632282257…| 0.0|
|[4.9,3.0,1.4,0.2]| 0.0|[3.45569849014282…|[0.99618089199066…| 0.0|
|[4.7,3.2,1.3,0.2]| 0.0|[3.45569849014282…|[0.99643349647521…| 0.0|
|[4.6,3.1,1.5,0.2]| 0.0|[3.45569849014282…|[0.99636095762252…| 0.0|
|[5.0,3.6,1.4,0.2]| 0.0|[3.45569849014282…|[0.99579632282257…| 0.0|
|[5.4,3.9,1.7,0.4]| 0.0|[3.45569849014282…|[0.99428516626358…| 0.0|
|[4.6,3.4,1.4,0.3]| 0.0|[3.45569849014282…|[0.99643349647521…| 0.0|
|[5.0,3.4,1.5,0.2]| 0.0|[3.45569849014282…|[0.99579632282257…| 0.0|
|[4.4,2.9,1.4,0.2]| 0.0|[3.45569849014282…|[0.99618089199066…| 0.0|
|[4.9,3.1,1.5,0.1]| 0.0|[3.45569849014282…|[0.99636095762252…| 0.0|
|[5.4,3.7,1.5,0.2]| 0.0|[3.45569849014282…|[0.99428516626358…| 0.0|
|[4.8,3.4,1.6,0.2]| 0.0|[3.45569849014282…|[0.99643349647521…| 0.0|
|[4.8,3.0,1.4,0.1]| 0.0|[3.45569849014282…|[0.99618089199066…| 0.0|
|[4.3,3.0,1.1,0.1]| 0.0|[3.45569849014282…|[0.99618089199066…| 0.0|
|[5.8,4.0,1.2,0.2]| 0.0|[3.45569849014282…|[0.97809928655624…| 0.0|
|[5.7,4.4,1.5,0.4]| 0.0|[3.45569849014282…|[0.97809928655624…| 0.0|
|[5.4,3.9,1.3,0.4]| 0.0|[3.45569849014282…|[0.99428516626358…| 0.0|
|[5.1,3.5,1.4,0.3]| 0.0|[3.45569849014282…|[0.99579632282257…| 0.0|
|[5.7,3.8,1.7,0.3]| 0.0|[3.45569849014282…|[0.97809928655624…| 0.0|
|[5.1,3.8,1.5,0.3]| 0.0|[3.45569849014282…|[0.99579632282257…| 0.0|
±----------------±---------±-------------------±-------------------±---------+
spark預測測試Demo
我們實際使用過程中,我們的數據都是各種特徵數據,我們希望通過spark xgboost進行預測時候我們需要進行轉換爲Vector[SparseVector],需要三個要素(特徵總數目,特徵index Array, 特徵值value Array),比如我們有一個n維的特徵向量vec,value爲0表示不包含改特徵,我們可以不包含在SparseVector裏面,我們先統計它所包含的特徵有多少<除去0的>,然後構造特徵index Array和特徵值value Array,最後組成Vector
import org.apache.spark.ml.linalg.DenseVector
/** 首先統計不爲0的特徵個數,爲0表示不存在該特徵 */
var features = 0
for (i <- 0 until vec.length) {
if (vec(i) > 0) {
features += 1
}
}
/** 構造xgboost預測需要的Vector */
val indices: Array[Int] = new Array[Int](features)
val values: Array[Double] = new Array[Double](features)
var index = 0
for (i <- 0 until vec.length) {
if (vec(i) > 0) {
indices(index) = i
values(index) = vec(i)
index = index + 1
}
}
Vectors.sparse(vec.length, indices, values)
對Vector數據進行預測
import ml.dmlc.xgboost4j.scala.spark.XGBoostModel
// 預測
val xgbModel = XGBoostModel.load(modelPath)
import spark.implicits._
// xgboostDf:schema: label[Double], features[Vector]
// xgboostDf必須包含一個字段features,解釋看下面源碼
val xgboostPredictDf = xgbModel.transform(xgboostDf)
xgboostPredictDf.select("label", "probabilities").
map(row => (row.get(0).asInstanceOf[Double],
row.get(1).asInstanceOf[DenseVector].toArray(1))).
toDF("label", "score")
predictDf.show()
xgboostDf數據集必須包含一個字段features<如果不自行設置的情況下>,解釋看下面源碼
override def transform(testSet: Dataset[_]): DataFrame = {
transformImpl(testSet)
}
protected def transformImpl(dataset: Dataset[_]): DataFrame = {
val predictUDF = udf { (features: Any) =>
predict(features.asInstanceOf[FeaturesType])
}
dataset.withColumn($(predictionCol), predictUDF(col($(featuresCol))))
}
// 默認的輸出prediction字段表示結果分類結果或者回歸值
setDefault(predictionCol, "prediction")
// 默認的預測的列名字是features,可以自行設置,在transform前進行設置
setDefault(featuresCol, "features")
// 可以通過setFeaturesCol方法進行設置特徵列,默認features
xgboostModel.setFeaturesCol("xxx")
排序
xgboost的預測結果是0~1之間的概率值,我們將它看作是user對改item的打分數據,排序是對多個item進行打分,然後選取得分topN的作爲最終結果,這個根據具體的業務進行選擇TopN
調參
調參是一種技術活,這裏只給出幾個關於調參的鏈接:
https://blog.csdn.net/han_xiaoyang/article/details/52665396
https://blog.csdn.net/sinat_35512245/article/details/79700029
注意點
spark loadLibSvm格式數據對數據文件的要求是:index是從1開始的,而xgboost訓練時候是從0開始的,而且要求index是遞增的,如下源碼所示:
// org.apache.spark.mllib.util.MLUtils
private[spark] def parseLibSVMRecord(line: String): (Double, Array[Int], Array[Double]) = {
val items = line.split(' ')
val label = items.head.toDouble
val (indices, values) = items.tail.filter(_.nonEmpty).map { item =>
val indexAndValue = item.split(':')
// Convert 1-based indices to 0-based.
val index = indexAndValue(0).toInt - 1
val value = indexAndValue(1).toDouble
(index, value)
}.unzip
// check if indices are one-based and in ascending order
var previous = -1
var i = 0
val indicesLength = indices.length
while (i < indicesLength) {
val current = indices(i)
// 必須遞增
require(current > previous, s"indices should be one-based and in ascending order;"
+ s""" found current=$current, previous=$previous; line="$line""")
previous = current
i += 1
}
(label, indices.toArray, values.toArray)
}