sklearn2pmml xgboost缺失值(missing)处理的坑
今天同事在部署xgboost pmml模型时遇到了大坑,线上spark预测和本地python预测结果怎么都不对应,记录一下处理过程。
看了下同事的代码,貌似也没有问题
from sklearn2pmml import PMMLPipeline
from sklearn2pmml import sklearn2pmml
from xgboost import XGBClassifier
weight = train_y.sum() * 1.0/ (len(train_data) - train_y.sum())
xgb_clf = XGBClassifier(learning_rate=0.1,n_estimators=100,max_depth=3,objective='binary:logistic',seed=1,silent=1,reg_alpha=3,reg_lambda=0.9,scale_pos_weight=1/weight,missing=-9999, eval_metric='auc')
pipeline = PMMLPipeline([('classifier',xgb_clf)])
pipeline.fit(train_x,train_y)
sklearn2pmml(pipeline,'data/a_card_1.pmml',with_repr=True)
首先注意到和之前不同点在于这次缺失值不是nan了,这引起了我的警觉,重新训练了下模型,把样本缺失值处理为np.nan,训练时missing设为默认值None,这时和线上对比发现一致了,果然是missing value的问题。
sklearn2pmml对于xgboost并没有暴露missing这个参数,所以对于missing不为None的童鞋可使用https://github.com/jpmml/jpmml-xgboost 转化。
xgb_clf.get_booster().dump_model('/tmp/a_card_model.dump.txt')
xgb_clf.get_booster().save_model('/tmp/xgb.model')
java -jar target/jpmml-xgboost-executable-1.3-SNAPSHOT.jar --model-input /tmp/xgb.model --fmap-input /tmp/xgb.fmap --pmml-output xgboost_miss.pmml --missing-value -9999
fmap可通过以下方式产生
fmap(feature map file):实现feature id和feature name的对应
格式为 featmap.txt: <featureid> <featurename> <q or i or int>\n
Feature id从0开始直到特征的个数为止,从小到大排列。
i表示是二分类特征
q表示数值变量,如年龄,时间等。q可以缺省
int表示特征为整数(when int is hinted, the decision boundary will be integer)
可根据以下语句通过读取pkl文件的feature_name生成,或者根据feature顺序通过别的方式生成
def ceate_feature_map(file_name,features):
outfile = open(file_name, 'w')
for i, feat in enumerate(features):
outfile.write('{0}\t{1}\tq\n'.format(i, feat))
通过对比PMML可以发现不同点就在于DataField增加了missing配置
<DataDictionary>
<DataField name="_target" optype="categorical" dataType="integer">
<Value value="0"/>
<Value value="1"/>
</DataField>
<DataField name="pas_age" optype="continuous" dataType="float">
<Value value="-9999" property="missing"/>
</DataField>
<DataField name="last_gulf_call_days" optype="continuous" dataType="float">
<Value value="-9999" property="missing"/>
</DataField>
....
</DataDictionary>
可以手动在之前的PMLL文件中增加即可解决这个问题。
当然我觉得更好的方式就是使用默认值,即np.nan,对应到spark也就是null,非常自然。
不过没怎么看懂PMML是怎么处理缺失值的,贴一段xgboost原生和PMML对比
booster[0]:
0:[last_30_days_invoice_value<2407.28491] yes=1,no=2,missing=2
1:[last_6_month_finish_count_variation_coefficient<0.61500001] yes=3,no=4,missing=4
3:[last_6_month_fast_finish_order_max_actual_cost<86.4949951] yes=7,no=8,missing=8
7:leaf=-0.0717158243
8:leaf=-0.147665188
4:[last_1_year_taxi_finish_order_actual_cost<505.25] yes=9,no=10,missing=9
9:leaf=-0.0261387583
10:leaf=-0.178924426
2:[app_system_tools_wifi_category_number_rate<0.0645833313] yes=5,no=6,missing=5
5:[last_1_year_night_finish_rate<0.0652500018] yes=11,no=12,missing=12
11:leaf=-0.0177322756
12:leaf=0.0268170126
6:[app_stock_sub_category_number_rate<0.0875959098] yes=13,no=14,missing=13
13:leaf=0.06783209
14:leaf=-0.0312540941
<Segment id="1">
<True/>
<TreeModel functionName="regression" missingValueStrategy="none" noTrueChildStrategy="returnLastPrediction" splitCharacteristic="multiSplit" x-mathContext="float">
<MiningSchema>
<MiningField name="last_6_month_fast_finish_order_max_actual_cost"/>
<MiningField name="last_1_year_night_finish_rate"/>
<MiningField name="last_30_days_invoice_value"/>
<MiningField name="app_stock_sub_category_number_rate"/>
<MiningField name="app_system_tools_wifi_category_number_rate"/>
<MiningField name="last_1_year_taxi_finish_order_actual_cost"/>
<MiningField name="last_6_month_finish_count_variation_coefficient"/>
</MiningSchema>
<Node score="0.026817013">
<True/>
<Node score="-0.026138758">
<SimplePredicate field="last_30_days_invoice_value" operator="lessThan" value="2407.285"/>
<Node score="-0.14766519">
<SimplePredicate field="last_6_month_finish_count_variation_coefficient" operator="lessThan" value="0.615"/>
<Node score="-0.071715824">
<SimplePredicate field="last_6_month_fast_finish_order_max_actual_cost" operator="lessThan" value="86.494995"/>
</Node>
</Node>
<Node score="-0.17892443">
<SimplePredicate field="last_1_year_taxi_finish_order_actual_cost" operator="greaterOrEqual" value="505.25"/>
</Node>
</Node>
<Node score="0.06783209">
<SimplePredicate field="app_system_tools_wifi_category_number_rate" operator="greaterOrEqual" value="0.06458333"/>
<Node score="-0.031254094">
<SimplePredicate field="app_stock_sub_category_number_rate" operator="greaterOrEqual" value="0.08759591"/>
</Node>
</Node>
<Node score="-0.017732276">
<SimplePredicate field="last_1_year_night_finish_rate" operator="lessThan" value="0.06525"/>
</Node>
</Node>
</TreeModel>
</Segment>
xgboost有明确的当遇到缺失值如何处理说明,但PMML貌似并没有,看出的童鞋麻烦告知我一下,非常感谢。
我们实现了配置化在Spark上部署模型,如一模型部署配置如下
sparkConf:
appName: driverCCardPMML
enableHiveSupport: true
appConf:
debug: true
limit: 10
savePath: /user/fbi/model_deploy/
sourcePath: /user/fbi/model_source/
tree:
desc: C卡PMML
name: model_pmml
parameters:
pmmlPath: zkc_driver_ccard_v1.1.pmml
excludeOriginColumn: true
excludeExcept: ["uid"]
children:
- desc: 加载C卡原数据
name: data_source
parameters:
type: hql_file
path: datasource.sql
saveTable: model_driver_c_card_source
transformer:
- desc: 数据类型转换
name: feature_data_type
parameters:
originalType: ["decimal"]
targetType: double
transformer:
- desc: 落库
name: data_sink
parameters:
auto: true
path: /user/fbi/
db: riskmanage_dm
table: model_driver_c_card_v1
tableName: 模型-司机-C卡-v1
最近也在反思是否有更好的离线部署方式,如DSL,比如通过spark-sql可以完全实现上面的处理流程,当然需要稍微扩展下spark-sql语法,是否值得尝试?
大家对离线模型都是如何部署的,欢迎交流。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)