通常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转换
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打分错误,目前还未找到原因。。。