Python模型上线pmml以及自定义函数转换(1)

通常xgb或lgb模型通过pmml上线都比较简单,但是逻辑回归模型因为涉及到woe的转换,就要通过自定义转换函数的方式来实现。

1、常规转换-模型训练好之后立即转换

import joblib
from sklearn2pmml import PMMLPipeline,sklearn2pmml


# 保存模型 python可读入
def dump(clf, fp='clf'):
    joblib.dump(feature_names_2, 'feature_' + fp +'.pkl')
    joblib.dump(clf, fp + '.pkl')

    pipeline = PMMLPipeline([("classifier", clf)])
    pipeline.fit(data_train[feature_names_2], data_train['target'])
    sklearn2pmml(pipeline, fp + '.pmml', with_repr=True)

以上clf是训练好的模型,data_train是训练数据集,feature_names_2是入模特征,这样在模型训练好之后就可以立即将模型转换成pkl和pmml的形式。

2、只有模型和特征的pkl文件,没有训练集
先将模型和特征的pkl进行load,然后在运行以下代码就可以保存为pmml

def pmml_save(model, feat_list, save_file_path):

    from sklearn2pmml import sklearn2pmml, PMMLPipeline
    from sklearn_pandas import DataFrameMapper

    if not save_file_path.endswith('.pmml'):
        raise Exception('参数save_file_path后缀必须为pmml, 请检查!')

    mapper = DataFrameMapper([([i], None) for i in feat_list])
    pipeline = PMMLPipeline([('mapper', mapper), ("classifier", model)])
    sklearn2pmml(pipeline, pmml=save_file_path)

    print('模型文件已保存至{}'.format(save_file_path))
    
pmml_save(model, feature_names_2, 'xgb_0708.pmml')    

注意:pkl转pmml失败,以及pmml上线问题,基本上都是sklearn2pmml的版本问题,pmml4.3比pmml4.4更稳定,部署不易出错,建议使用pmml4.3。对应所需包
的版本如下:sklearn:0.22.0,sklearn2pmml:0.56.0,lightgbm:3.2.1,xgboost:1.0.2

3、数据预处理的pmml转换

from sklearn2pmml import sklearn2pmml, PMMLPipeline
from sklearn_pandas import DataFrameMapper
from sklearn2pmml.preprocessing import ExpressionTransformer    

mapper = DataFrameMapper([
(['age'], ExpressionTransformer("-0.1111 if X[0] <= 25 else (0.3333 if X[0]<=30 else 1.4598)")),
(['tx_riskscore'], ExpressionTransformer("-0.42264 if X[0] <= 40  else (0.2344 if X[0]<=70 else 1.44323)")),
(['rh_fraud_score'], ExpressionTransformer("-0.8888 if X[0] <= 50 else (0.8822 if X[0]<=60 else 1.12123)")),
]) 


pipeline = PMMLPipeline([('mapper', mapper),("classifier", sklearn.linear_model.LogisticRegression())])
pipeline.fit(data2[data2.columns.difference(['cpd30'])], data2['cpd30'])
sklearn2pmml(pipeline, 'test.pmml', with_repr=True)

使用ExpressionTransformer函数,来定义ifelse语句,然后再对转换后的woe变量训练模型,下面是转换后的pmml,欢迎测试。
在这里插入图片描述
在这里插入图片描述
4、模型上线缺失值问题
如果lgb模型训练时在本地没有做缺失值处理,用sklearn2pmml生成的pmml文件上线后是没有问题的。但是如果pmml要对缺失的变量做二次衍生,就会由于java未传参导致报错。

解决方法:用pandas.isnull(X[0]) 表示是否缺失!!!!最主要的是使用ExpressionTransformer if语句的时候,先要进行是否missing判断。

#逻辑回归woe转换
(["App_SMALLLOAN_Installed_24M"], ExpressionTransformer("0.21722199999999997 if pandas.isnull(X[0]) else(0.770462 if X[0]<=0.5 else(0.067147 if X[0]<=2.5 else(-0.8716940000000001 if X[0]<=6.5 else(-1.8263049999999998 if X[0]<=13.0 else(-2.789888 if X[0]<=24.5 else -3.313811 )))))"),{"alias":"W_App_SMALLLOAN_Installed_24M"}),

#生成中间变量,分母为0的处理
(['cts_msg_002','cts_msg_018'],[ExpressionTransformer('X[0]/X[1] if pandas.isnull(X[1]) else(0 if X[1]==0 else X[0]/X[1])')],{'alias':'cts_msg_ys_003'}),

还有一个状况,就是假设建模的时候没有做缺失值处理,上线的时候因为某些原因必须要填充缺失值,怎么办呢??
在尝试了FunctionTransformer(ufunc),以及自定义 Transformer等操作方式之后,采用如下的方式:

可以先将缺失值填充为-9999,然后对特征进行下面的操作:特征值为-9999的进行np.log1(x),这样可以在pmml文件里将其转换为nan。

(['CPL_INDM_EDU_LEVEL'], [ExpressionTransformer("numpy.log1p(X[0]) if X[0]==-9999 else X[0]")]),

5、类别变量及中文字符
类别变量的处理时,譬如使用X[0]==‘已婚’,会遇到两个问题,第一,中文字符在转pmml的时候出错,第二,转好的pmml文件,java对象接收类别变量参数的报错。问题一目前是先用英文字符替代,然后在pmml文件中改成中文;出现问题二的原因是java把类别变量当成了连续型,然后强制类型转换double报错,所以只需要pmml文件中数据类型改成字符型就好,但目前没有找到合适的代码转换方式,也是在pmml中手动修改
将optype从‘continuous’改成‘categorical’,dadaType从‘double’改成‘string’

<DataField name="PB_Pqo_QueR_n9_CCardAppQueNumProp" optype="continuous" dataType="double"/>
<DataField name="PB_Pqo_QueR_n9_NBank_TotalAppQueSetupTypeNum" optype="continuous" dataType="double"/>
<DataField name="PB_PerInfo_Sp_MarSta" optype="categorical" dataType="string"/>
<DataField name="PB_PubInfo_FundR_Newest_PaymentStatus" optype="categorical" dataType="string"/>

6、一些问题
目前在对模型保存为pmml文件需要对数据再次fit一遍,

pipeline.fit(data_train, data_train['target'])

需要注意一些问题,
1、数据类型,最好在python中将数据类型强转为float,因为整数值int64在pmml中进行除法等运算可能会对结果取余;
2、再次fit过程中,可能会因为python和java精度的问题,模型的结构可能产生偏差

参考解决思路pmml官方文档自定义转换报错

完整的例子:生成中间变量的pmml转换

from sklearn.dummy import DummyRegressor
from sklearn2pmml import PMMLPipeline, sklearn2pmml
from sklearn2pmml.preprocessing import ExpressionTransformer
from sklearn_pandas import DataFrameMapper
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression,LogisticRegression
import lightgbm as lgb

df = pd.DataFrame(np.random.randint(0,100,size=(10000, 6)), columns=list('ABCDEF'))
df['target'] = df['F'].apply(lambda x:1 if x>50 else 0)

#变量衍生
df['sub1'] = df.apply(lambda x:x['A']-x['B'], axis=1)
df['mean1'] = df.apply(lambda x:(x['A']+x['B']+x['C'])/3, axis=1)
df['sum2'] = df.apply(lambda x:x['C']+x['D'], axis=1)

features= ['sub1','mean1','sum2','A','E']
model = lgb.LGBMClassifier(n_estimators=100, max_depth=3, random_state=100).fit(df[features], df['target'])

转pmml

# from sklearn2pmml.decoration import Alias

# mapper = DataFrameMapper([
#     (['A','B'], Alias(ExpressionTransformer("X[0] + X[1]"), name = "sum1")),
#     (['C','D'], Alias(ExpressionTransformer("X[0] + X[1] "), name = "sum2")),
#     (['A'], None),
#     (['E'], None),
# ])

mapper = DataFrameMapper([
    (['A','B'], [ExpressionTransformer("X[0] - X[1]")],{"alias":"sub1"}),
    (['A','B', 'C'], [ExpressionTransformer("(X[0] + X[1] + X[2]) / 3")],{"alias":"mean1"}),
    (['C','D'], [ExpressionTransformer("X[0] + X[1]")],{"alias":"sum2"}),
    (['A'], None),
    (['E'], None),
])

pipeline = PMMLPipeline([
    ("mapper", mapper),
    ("classifier", model)
])
pipeline.fit(df, df['target'])
sklearn2pmml(pipeline, "pipeline_01.pmml", with_repr = True)

模型预测结果和pmml生成模型预测结果对比,结果一致:

def load_pmml_model(save_file_path):

    from pypmml import Model
    if not os.path.exists(save_file_path):
        raise Exception('参数save_file_path指向的文件路径不存在, 请检查!')

    model = Model.fromFile(save_file_path)
    return model

model1 = load_pmml_model("pipeline_01.pmml")
df['pred'] = model.predict_proba(df[features])[:, 1]
df['pred1'] = model1.predict(df)['probability(1)']    

注意,
1、DataFrameMapper中变量的顺序一定要与训练好的模型model中变量features顺序一致
2、这里在验证分数是否一致时,一定要对数据df的索引重置

df.reset_index(drop=True, inplace=True)

不然pmml打分错误,目前还未找到原因。。。

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值