PMML 预测模型教程

什么是PMML

PMML 是一种基于XML的标准语言,用于表达数据挖掘模型,可以用来在不同的应用程序中交换模型。
一种非常有用的应用场景是在生产环境中部署用各种建模工具训练出来的模型。
目前最新的标准是4.3 http://dmg.org/pmml/pmml-v4-3.html

PMML 文件的结构遵从了用于构建预测解决方案的常用步骤,包括:

  1. 数据词典,这是一种数据分析阶段的产品,可以识别和定义哪些输入数据字段对于解决眼前的问题是最有用的。这可以包括数值、顺序和分类字段。
  2. 挖掘架构,定义了处理缺少值和离群值的策略。这非常有用,因为通常情况,当将模型应用于实践时,所需的输入数据字段可能为空或者被误呈现。
  3. 数据转换,定义了将原始输入数据预处理至派生字段所需的计算。派生字段(有时也称为特征检测器)对输入字段进行合并或修改,以获取更多相关信息。例如,为了预测停车所需的制动压力,一个预测模型可能将室外温度和水的存在(是否在下雨?)作为原始数据。派生字段可能会将这两个字段结合起来,以探测路上是否结冰。然后结冰字段被作为模型的直接输入来预测停车所需的制动压力。
  4. 模型定义,定义了用于构建模型的结构和参数。PMML 涵盖了多种统计技术。例如,为了呈现一个神经网络,它定义了所有的神经层和神经元之间的连接权重。对于一个决策树来说,它定义了所有树节点及简单和复合谓语。
  5. 输出,定义了预期模型输出。对于一个分类任务来说,输出可以包括预测类及与所有可能类相关的概率。
  6. 目标,定义了应用于模型输出的后处理步骤。对于一个回归任务来说,此步骤支持将输出转变为人们很容易就可以理解的分数(预测结果)。
  7. 模型解释,定义了将测试数据传递至模型时获得的性能度量标准(与训练数据相对)。这些度量标准包括字段相关性、混淆矩阵、增益图及接收者操作特征(ROC)曲线图。
  8. 模型验证,定义了一个包含输入数据记录和预期模型输出的示例集。这是非常重要的一个步骤,因为在应用程序之间移动模型时,该模型需要通过匹配测试。这样就可以确保,在呈现相同的输入时,新系统可以生成与旧系统同样的输出。如果实际情况是这样的话,一个模型将被认为经过了验证,且随时可用于实践。

一个通用的PMML文件结构如下(参考http://dmg.org/pmml/v4-3/GeneralStructure.html):

<?xml version="1.0"?>
<PMML version="4.3"
  xmlns="http://www.dmg.org/PMML-4_3"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <Header copyright="Example.com"/>
  <DataDictionary> ... </DataDictionary>

  ... a model ...

</PMML>

使用PMML发布预测模型

下面以目前应用广泛的 XGBoost 模型为例,介绍使用PMML发布预测模型。

首先,我们需要有一个XGBoost模型,为此,可以以Iris数据集训练一个简单的二分类模型(只用其中的两类)。 然后利用 XGBoost 训练得到模型文件。

import xgboost as xgb
from sklearn.datasets import load_iris

iris = load_iris()
mask = iris.target < 2
X = iris.data[mask,:]
y = iris.target[mask]

params = {
    'objective' : 'reg:logistic',
    'num_round' : 10,
    'max_depth' : 3
}
dtrain = xgb.DMatrix(X, label=y)
evallist = [(dtrain, 'train')]
bst = xgb.train(params, dtrain, evals=evallist)
bst.save_model('xgb.bin')
[0] train-rmse:0.364576
[1] train-rmse:0.27088
[2] train-rmse:0.203626
[3] train-rmse:0.154579
[4] train-rmse:0.118482
[5] train-rmse:0.091745
[6] train-rmse:0.071832
[7] train-rmse:0.056919
[8] train-rmse:0.045683
[9] train-rmse:0.037156

然后生成一个特征映射文件,因为模型文件中没有特征名,只有特征id。

f = open('fmap.txt', 'w')
for i, fn in enumerate(iris.feature_names):
    f.write('%d\t%s\t%s\n' % (i, fn, 'q'))
f.close()

利用 jpmml-xgboost 项目https://github.com/jpmml/jpmml-xgboost提供的工具,进行转换。
你也可以直接下载我已经编译好的jar包
然后执行下述命令,即可得到转换后的PMML文件 xgb.pmml.xml。

!java -jar converter-executable-1.2-SNAPSHOT.jar --model-input xgb.bin  --fmap-input fmap.txt  --pmml-output xgb.pmml.xml

得到PMML文件xgb.pmml.xml后,我们就可以在生产环境部署了。
PMML模型的部署可以使用 https://github.com/jpmml/jpmml-evaluator 进行部署,
可以很容易应用到分布式环境!

下面是一段预测的代码:

InputStream is = new FileInputStream("path-to-pmml-file");
PMML pmml = PMMLUtil.unmarshal(is);

// 这里的LocatorTransformer是为了将模型转换为可序列化的对象,如果不需要在分布式环境(如Spark)使用模型,就可以不用转换
LocatorTransformer lt = new LocatorTransformer();
lt.applyTo(pmml);

Evaluator evaluator = ModelEvaluatorFactory.newInstance().newModelEvaluator(pmml);

// 预测
List<InputField> fields = evaluator.getActiveFields();

Map<FieldName, Double> input = new HashMap<>();
for(InputField field : fields){
    input.put(field.getName(), 1.2); //对每一个特征指定对应的值
}
Map<FieldName, ?> results = evaluator.evaluate(input);
List<TargetField> output = evaluator.getTargetFields();
Object value = results.get(output.get(0).getName());

PMML文件分析

基本框架分析

打开 xgb.pmml.xml 文件,我们可以看到一个实际可用的PMML文件结构。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PMML xmlns="http://www.dmg.org/PMML-4_3" version="4.3">
    <Header>
        <Application name="JPMML-XGBoost" version="1.2-SNAPSHOT"/>
        <Timestamp>2017-10-17T03:41:51Z</Timestamp>
    </Header>
    <DataDictionary>
        <DataField name="_target" optype="continuous" dataType="float"/>
        <DataField name="sepal width (cm)" optype="continuous" dataType="float"/>
        <DataField name="petal length (cm)" optype="continuous" dataType="float"/>
        <DataField name="petal width (cm)" optype="continuous" dataType="float"/>
    </DataDictionary>
    <MiningModel functionName="regression" x-mathContext="float">
        ...
    </MiningModel>
</PMML>

PMML文件是基于XML格式的文本文件,有且只有一个根节点PMML
PMML 根节点除了 xmlns 属性外,有且只有一个属性 version,它的值表明PMML标准的版本。

PMML 子元素有两个是必须的,HeaderDataDictionary

Header 头部信息,只包含说明信息,对预测逻辑没有影响,通常包括:
- 包含的属性
- copyright 版权
- description 描述文本
- modelVersion 模型版本
- 包含的子元素
- Application 描述生成PMML文件的软件相关信息,本例子说明这个PMML是由 JPMML-XGBoost 软件生成的,该软件版本是 1.2-SNAPSHOT
- Timestamp 生成的时间戳
- Annotation 可选,描述模型版本更新信息

DataDictionary 数据字典,描述字段信息,包括模型的输入字段和输出字段,这里_target是输出字段,其他三个是输入字段。每一个字段用 DataField 元素描述refDataField 有三个必须的属性:name 字段或特征名字,optype 操作类型,dataType 数据类型。 DataDictionary 只负责字段的定义,对字段的处理比如缺失值处理应该在模型的MiningField 区域定义。

optype 是操作类型,有三个可选值:categorical 类别变量,只能进行相等的判断; ordinal 序数变量还可以进行顺序比较; continuous 只有这个操作类型才能进行算术运算,这种类型的变量应用比较多。

dataType 是数据类型,大约有十几种类型[ref]

PMML 可选的元素有4个,分别是:ref。包括 string, interger, float, double, date 等常见的数据类型。

  1. MiningBuildTask 可以包含任意XML值,用于表达模型训练时的相关信息,对模型预测没有影响。
  2. TransformationDictionary 变换字典,用于定义各种变换。
  3. MODEL-ELEMENT 这是个模型元素集合,用来表达模型的参数和预测逻辑。具体使用时,可以是这个集合里面任意一种元素,在这个例子里面,用得就是 MiningModel 这个元素,还可以是GeneralRegressionModel等18个元素中的任意一个,可以参看链接
  4. Extension 扩展信息

MODEL-ELEMENT 大约包括18个不同的模型,每一个模型都有几个相同的属性。functionName 用于定义该模型是回归、分类还是聚类等等,是必须的属性。PMML里面一共定义了7种类型ref,常用的有回归regression、分类classification、聚类clustering。另外两个可选的属性是:modelNamealgorithmName,只用于提示,对模型预测没有实质性影响。
MODEL-ELEMENT 都包含了这样一些元素:MiningSchema(必须)、 OutputTargets等,这些在后面的章节将会详细介绍。

模型分析

找到 MiningModel 区块,可以看到这个元素的主要结构如下(非主要结构已被省略):

<MiningModel functionName="regression" x-mathContext="float">
    <MiningSchema>
        <MiningField name="_target" usageType="target"/>
        <MiningField name="sepal width (cm)"/>
        <MiningField name="petal width (cm)"/>
        <MiningField name="petal length (cm)"/>
    </MiningSchema>
    <Segmentation multipleModelMethod="modelChain">
        <Segment id="1">
            <True/>
            <MiningModel functionName="regression" x-mathContext="float">
                ...
            </MiningModel>
        </Segment>
        <Segment id="2">
            <True/>
            <RegressionModel functionName="regression" normalizationMethod="logit" x-mathContext="float">
                ...
            </RegressionModel>
        </Segment>
    </Segmentation>
</MiningModel>

MiningModel 实际上是一种通用的模型,通常用于模型的融合。它包含了一个特有子的元素 Segmentation,用于融合多个模型。本文的例子里面顶层的模型(即MiningModel)包含了两个模型。多个模型的融合方式是modelChain,即前一个模型的输出作为后一个模型的输入。融合方式由Segmentation的属性multipleModelMethod指定。实际上,因为XGBoost用的是回归树,然后将所有树的输出结果相加得到第一个模型的输出;第二个模型只是对第一个模型的输出做了一个简单的logit变换,将原始值转换为概率值。

MiningSchema 是所有模型都必须有的子元素,包括模型内的子模型也都有。对于所有输入这个模型的数据,都必须经过 MiningSchema,这个元素里面包含了这个模型用到的所有字段,相比 DataDictionaryMiningSchema可以为每个模型定义特有的一些信息,还包括缺失值异常值处理等特征预处理操作。ref

MiningField

每一个字段由MiningField定义,它包含以下属性:

<MiningField name="foo" missingValueReplacement="3.14" missingValueTreatment="asMean"/>

Segmentation

多个模型用 Segmentation 来组织,每一个模型都被包括在子元素 Segment 中。 Segmentation 只有一个属性 multipleModelMethod 用来表明多个模型的组合方式,可以取得值如下ref

每一个Segment包含属性idweightweight可选属性,在加权融合的情况下才有用。每一个Segment包含的子元素有 PREDICATEMODEL-ELEMENT。这个例子中的PREDICATE<True/>,表明使用这个模型计算预测值,如果为<False/>则不使用。模型元素有两个,一个是MiningModel,另一个是TreeModelTreeModel我们在后面介绍。

Output & Target

OutputTarget 都可以用于定义模型的输出。

Target 可以对输出结果做简单的线性变换:$(f(x) = 10 + 3.14 x)$

<Targets>
  <Target field="amount" rescaleConstant="10" rescaleFactor="3.14" min="-10" max="10.5" castInteger="round"/>
</Targets>

Output 则可以应用更复杂的变换,OutputFieldfeature属性,可以输出很多有用的信息ref,例如预测原始值,决策树叶子结点的ID值等等。
下面是一个对模型输出结果做变换$(f(x) = 10^{x + 0.5} - 1)$的例子,这个例子来自于用XGBoost对log1p后的值做回归,因为多个决策树的结果加和后,要乘以0.5,所以反变换就是上面这个表达式了。

<Output>
    <OutputField name="rawResult" dataType="double" feature="predictedValue" />
    <OutputField name="_target" dataType="double" feature="transformedValue" >
        <!--  pow(10, s+0.5) - 1 -->
        <Apply function="round">
            <Apply function="-">
                <Apply function="pow">
                    <Constant dataType="double">10.0</Constant>
                    <Apply function="+">
                        <FieldRef field="rawResult"></FieldRef>
                        <Constant dataType="double">0.5</Constant>
                    </Apply>
                </Apply>
                <Constant dataType="double">1.0</Constant>
            </Apply>
        </Apply>
    </OutputField>
</Output>

PMML内置了常用的数学函数,函数的应用非常简单,直接创建一个Apply元素,并指定属性function为函数名即可,然后将参数依次作为子元素。参数可以是常数Constant和其他字段FieldRef,甚至一个新的函数应用结果Apply。用户也可以创建自定义函数,将函数的定义放到 TransformationDictionary 中,然后就可以直接引用了。

参考

  1. https://www.ibm.com/developerworks/cn/opensource/ind-PMML1/index.html
  2. PMML-4_3标准文档 http://dmg.org/pmml/pmml-v4-3.html