sklearn2pmml xgboost缺失值(missing)处理的坑

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 id0开始直到特征的个数为止,从小到大排列。
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:
  #spark任务名称, 必填
  appName: driverCCardPMML
  #是否启用hive支持
  enableHiveSupport: true
  #spark其它配置选项,如内存,shffle partitions数量等
appConf:
  #debug开启时,每个节点会做持久化
  debug: true
  #持久化数量,0代表全量
  limit: 10
  savePath: /user/fbi/model_deploy/
  sourcePath: /user/fbi/model_source/
#一个子节点只有一个父节点,所以树更合适
tree:
  #节点描述
  desc: C卡PMML
  #名称,用于标识一个组件
  name: model_pmml
  #传递给组件的参数,包括模型超参数,以及配置参数等
  parameters:
    #pmml文件路径,暂只支持本地文件,spark-submit可通过--files glm1.pmml上传
    pmmlPath: zkc_driver_ccard_v1.1.pmml
    #是否排除原始列,默认false(保留)
    excludeOriginColumn: true
    #排除例外,如uid等
    excludeExcept: ["uid"]
  #子节点合并所有结果,如果children只有一个,可省略joinType,joinKey
  #join类型,full(默认), inner, left, right
  children:
    - desc: 加载C卡原数据
      name: data_source
      parameters:
        #支持hql, hql_file json
        type: hql_file
        path: datasource.sql
        #方便模型校验,可配置saveTable,将负责数据源落库,将会保存到/user/fbi/model_source/year/month/day/model_driver_c_card_source.parquet
        saveTable: model_driver_c_card_source
      transformer:
        - desc: 数据类型转换
          name: feature_data_type
          parameters:
            #原始数据类型 tinyint,smallint, int, bigint, float, double,string, decimal
            originalType: ["decimal"]
            #目标数据类型
            targetType: double
            #排除的列名,可省略
            #exceptColumn: []      
  #transformer节点,pipeline模式
  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语法,是否值得尝试?

大家对离线模型都是如何部署的,欢迎交流。

你可能感兴趣的:(机器学习,大数据)