最近在使用xgboost on spark过程中发现一个问题,发现对于标签型的稀疏变量(即有值就为1),得到的树节点如下:
0:[tag1<2.00000095] yes=1,no=2,missing=2
既然为1和0,为什么条件会是小于2呢?这样不是会导致所有值都满足条件吗?
于是,带着这个问题,查找了相关的资料。美团的技术团队遇到过xgboost on spark 的缺失值问题,我觉得可能就是这个问题导致的。
美团的解决办法:https://tech.meituan.com/2019/08/15/problems-caused-by-missing-xgboost-values-and-their-in-depth-analysis.html
文中描述:
在XGBoost on Spark场景下,默认将Float.NaN作为缺失值。如果数据集中的某一行存储结构是DenseVector,实际执行时,该行的缺失值是Float.NaN。而如果数据集中的某一行存储结构是SparseVector,由于XGBoost on Spark仅仅使用了SparseVector中的非0值,也就导致该行数据的缺失值是Float.NaN和0。
其实,看文章中的解决方案就是将Sparse Vector转为Dense Vector。
具体措施是修改dmlc的源码。
1、从github上下载xgboost on spark 0.72版本,地址:https://github.com/dmlc/xgboost/tree/release_0.72。并将源码复制到spark scala工程中。在pom.xml中去掉xgboost on spark的引用。后面直接在工程中使用源码;
2、修改两处代码。
(1)ml.dmlc.xgboost4j.scala.spark.XGBoostEstimator 132行:
val values = features match { // case v: SparseVector => (v.indices, v.values.map(_.toFloat)) //SparseVector的数据,先转成Dense case v: SparseVector => v.toArray.map(_.toFloat) case v: DenseVector => v.values.map(_.toFloat) } XGBLabeledPoint(label.toFloat, null, values, baseMargin = baseMargin, weight = weight)
(2)ml.dmlc.xgboost4j.scala.spark.DataUtils 67行:
// XGBLabeledPoint(0.0f, v.indices, v.values.map(_.toFloat)) //SparseVector的数据,先转成Dense XGBLabeledPoint(0.0f, null, v.toArray.map(_.toFloat))