PMML部署算法模型

5,495 阅读5分钟

标签模型部署 PMML

对于算法工程师来说,如何将训练好的模型成功部署上线也是工作的重要环节之一。当然,对于python训练的模型,在python环境下部署是个很自然的想法,但是在我本人大部分的实际项目中,都是需要实现模型的跨平台(比如java环境)部署,而PMML(Predictive Model Markup Language, 预测模型标记语言)就是解决跨平台部署的一种方式。

1. PMML是什么? 🤔

PMML是一套基于XML标准,与平台和环境无关的模型表示语言。它主要是通过了XML schema定义和储存了一个算法模型的核心元素:

  • 数据字典:描述输入数据
  • 数据转换:定义了原始数据数据预处理的方式,比如标准化、缺失值处理、哑变量的生成等
  • 模型定义:模型的类型及参数,比如树模型的分裂节点等
  • 模型输出:模型的输出结果

不难看出,通过定义PMML中的所有核心元素,可以完成数据挖掘的所有流程,即后端开发人员部署时,只需要将数据读取后,调用PMML文件,然后就能得到输出数据,而不用关注数据转化、模型参数等问题,加快了模型的部署效率。

2. Python怎么实现PMML文件打包? 💻

本文采用Iris鸢尾花数据集作为例子,通过Pipeline的方式训练一个XGBoost分类器,然后将其打包输出PMML文件。Pipeline的方式是指,将许多不同的数据预处理步骤和一个或者多个模型,像拼接管道一样按顺序合并在一起,从而完成数据挖掘的所有阶段,简化模型的训练和部署。

Iris鸢尾花数据集,包含4个特征变量(sepal length, sepal width, petal length和petal width)以及1个类别标签(0-setosa, 1-versicolor, 2-virginica),我们的模型目标是:基于特征变量,将3类鸢尾花区分开。

Step 0: 导入所需包
# import packages
import pandas as pd
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn_pandas import DataFrameMapper
from sklearn.preprocessing import StandardScaler
from sklearn2pmml import PMMLPipeline
from sklearn2pmml import sklearn2pmml
from xgboost import XGBClassifier
Step 1: 使用python训练一个模型
# read data
iris = datasets.load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# split train and test dataset
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123)

# define datamapper
mapper = DataFrameMapper([
  (["sepal length (cm)", "sepal width (cm)"], StandardScaler()),
  (["petal length (cm)"], None)
], df_out=True)


# define a model
xgb = XGBClassifier(n_estimators=5, seed=123)

# make a pipeline
pip_model = PMMLPipeline([
  ('mapper', mapper),
  ("classifier", xgb)]
)

# train a model & predict on test data
pip_model.fit(X_train, y_train)
pred_prob_pip = pip_model.predict_proba(X_test)

在例子中,我们先使用DataFrameMapper定义了输入的特征变量只包含3个(去除了petal width),并且只对sepal length和sepal width进行标准化处理,而petal length则不做任何处理。然后定义了一个XGBClassifier,最后通过PMMLPipeline将两个数据处理流程和模型结合在一起进行训练。

Step 2: 保存为PMML文件
# save model 
sklearn2pmml(pip_model, "iris_model.pmml", with_repr = True)

输出的PMML文件可以由任何编辑器打开,形式如下:

1.png

Step 3: python中调用PMML校验结果
from pypmml import Model
model = Model.fromFile('iris_model.pmml')
pred_prob_reloaded_model = model.predict(X_test)

重新读取PMML文件和原Pipeline预测结果在python环境下应该保持一致。

3. 可能会遇到什么坑?😂

上面的例子实现很简单,但是真正部署时,总会遇到一些坑,毕竟大家都是在踩坑中成长的嘛。下面就列出我在部署PMML过程中遇见的一些问题,仅供参考。

  • 坑1: 可以训练模型但是却不能生成pmml文件

    可能是由于环境原因导致,sklearn/sklearn2pmml/sklearn_pandas需要适配,而且当sklearn2pmml版本过高而打jar版本过低,则会导致文件无法读取的问题,本文使用版本信息如下:

    • sklearn2pmml = 0.53.0
    • sklearn = 0.23.1
    • sklearn_pandas = 2.2.0
  • 坑2: Java调用PMML和原Pipeline模型预测不一致(正常情况下,小数点后5位应该保持一致

    导致这个问题的原因可能是两个: (1)数据类型不一致;(2)缺失值的处理。

    • 数据类型不一致,可以尝试以下2个解决方案:

      • 因为生成pmml文件时,所有的变量默认是double类型,而有些分类变量是string类型,所以可以考虑对分类变量进行labelencoder处理成数值型
      • 将pmml文件中double改成string
    • 缺失值的处理:当数据中存在缺失值,且不是为nan,而是以-9999代替时,PMML文件需要在datamapper和模型中同时定义-9999为缺失值, 否则就会出现预测不一致现象。此时应该

      # define datamapper
      mapper = DataFrameMapper([
        (["sepal length (cm)", "sepal width (cm)"], StandardScaler()),
        (["petal length (cm)"], ContinuousDomain(missing_values=-9999.0, with_data = False))
      ], df_out=True)
      
      
      # define a model
      xgb = XGBClassifier(n_estimators=5, seed=123, missing=-9999.0)
      

      注:使用ContinuousDomain后会根据训练数据找到每个特征的数据范围,而当测试数据数显超出此范围的数值后会报错,因此需要设置with_data=False来去除这一限制。

  • 坑3: 原Pipeline模型和python读取PMML文件模型预测结果不一致

    这种情况很大程度上也和缺失值有关。如果在正确处理坑2的后,还出现此类问题,可能是由于python读取PMML文件模型预测的时候,对于缺失特征也传入并赋值-9999。正确做法是,使用pypmml调用PMML文件,应该对缺失特征不传入,或者赋值为nan,这样pypmml调用PMML文件和原Pipeline模型预测结果就能保持一致。

敬请期待

  • Python构造Pipeline模型
  • 使用Scala调用python训练的PMML

禁止无授权转载,转载请联系作者,否则作者将保留一切合法权利。