xgboost實戰--python&spark訓練預測

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

  1. 首先安裝libgcc: conda install libgcc

  2. 然後安裝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)
  }

參考

  1. https://blog.csdn.net/aganliang/article/details/102667276

  2. https://s0xgboost0readthedocs0io.icopy.site/en/latest/jvm/xgboost4j_spark_tutorial.html

  3. https://www.cnblogs.com/wj-1314/p/9402324.html

  4. https://blog.csdn.net/wangzfox/article/details/45787725

  5. https://zhuanlan.zhihu.com/p/78409671

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章