一 背景:
在实际项目中往往会有不同种类的模型,如xgb的训练完保存为json格式、pkl格式以及原始booster的.model格式,预测需要用到pmml可以在spark中借助大数据的能力的跑因此需要对模型的格式进行转换。
二 方案:
方法一:通过python的方式
需要安装sklearn2pmml和nyoka。两个包借助于xgboost_to_pmml方法和PMMLPipeline对象实现纯python代码的转换,但是对同一份数据,python转换后的PMML模型和json版的模型diff比较大。python使用32位浮点存储叶子节点和并用64位sigmoid运算的结果是和pmml没有加入 x-mathContext=”float” 的预测结果相同的。而当xgboost默认做32位sigmoid运算时,pmml的 x-mathContext=”float” 并不能在底层上同样实现正确的32位sigmoid运算。
通过下面两种方法可以缓解:在python中使用 model._final_estimator.predict(mapper.transform(data), output_margin=True) 得到所有子树叶节点值的统计和后,手动计算64位浮点sigmoid运算 sigmoid = lambda x: 1 / (1 + np.exp(-x, dtype=np.float64)) ,同时在pmml文件中删除RegressionModel中的x-mathContext=”float”,且删除OutputField中的dataType=”float”,可以保证python和pmml的预测结果相同。
不进行任何改动,但是使用到预测结果的阈值需要进行分组时,阈值不能过于精准,建议四舍五入至4位小数,否者阈值是0.09169989时,python预测了0.09169989,但是pmml却预测出0.091699906,会使线上线下分组结果不统一。
出现问题:sklearn2pmml PMMLPipeline TypeError: Last step of Pipeline should implement fit or be the string 'passthrough'
原因:加载的模型用到了booster类,该类并没有实现fit方法
解决:需要改变加载模型的方式,首先创建 XGBClassifier或XGBRegressor类对象,创建Booster对象加载模型,将booster对象赋值给模型对象的_Booster属性,然后把模型对象传入PMMLPipeLine的创建参数里,模型对象实现了fit方法,因此上面的问题解决。
方法二:通过python+java的方式。
借助python先将json的模型,转成.model格式,然后生成xgb.fmap将编号和特征名称对应起来,利用jpmml-xgboost进行转换 https://pan.baidu.com/s/1vmPHmHZuAKIfkAOQnLcADA
通过下面的命令:
java -jar pmml-xgboost-example-executable-1.7-SNAPSHOT.jar --model-input xgb_model_utf-8.model --fmap-input xgb.fmap --target-name PredictScore --pmml-output xgb_model_utf-8.pmml
总结:(1)对xgb模型的加载,可以通过booster的方式,但是该类没有实现fit方法,也可以创建具体的分类或回归的模型对象,将booser赋值给模型对象的_Booster, 通过模型对象进行predict
(2)模型对象的predict方法的参数是ndarry类型,可以通过pandas处理数据,pandas.iloc[:,:].values直接作为入参。
(3)python调用shell的过程,遇到上文有outputfile的写文件操作,一定要将该文件的句柄通过flush操作,等待所有数据都写完之后再执行os.system