1.简述

PMML全称预言模型标记模型(Predictive Model Markup Language),以XML 为载体出现数据挖掘模型。PMML 容许您在不同的应用程序之间轻松共享预测分析模型。因而,您能够在一个零碎中定型一个模型,在 PMML 中对其进行表白,而后将其挪动到另一个零碎中,而不需思考剖析和预测过程中的具体实现细节。使得模型的部署解脱了模型开发和产品整合的解放。艰深地讲, 我有一个决策树模型, 应用成果也不错, 那么就能够把树的构造(节点间的父子关系, 节点内的丰盛信息 等)序列化为PMML文件, 共享给其他人应用.  这样无论你的模型是sklearn,R还是Spark MLlib生成的,咱们都能够将其转化为规范的XML格局来存储。当咱们须要将这个PMML的模型用于部署的时候,能够应用指标环境的解析PMML模型的库来加载模型,并做预测。

附PMML4.4官网文档:http://dmg.org/pmml/v4-4/Mult...
附jpmml源码github地址:https://github.com/jpmml

2. 次要构造

PMML 文件的构造听从了用于构建预测解决方案的罕用步骤,包含:

数据词典 
这是一种数据分析阶段的产品,能够辨认和定义哪些输出数据字段对于解决眼前的问题是最有用的。这能够包含数值、程序和分类字段。
开掘架构 
定义了解决短少值和离群值的策略。这十分有用,因为通常状况,当将模型利用于实际时,所需的输出数据字段可能为空或者被误出现。
数据转换 
定义了将原始输出数据预处理至派生字段所需的计算。派生字段(有时也称为特色检测器)对输出字段进行合并或批改,以获取更多相干信息。例如,为了预测停车所需的制动压力,一个预测模型可能将室外温度和水的存在(是否在下雨?)作为原始数据。派生字段可能会将这两个字段联合起来,以探测路上是否结冰。而后结冰字段被作为模型的间接输出来预测停车所需的制动压力。
模型定义 
定义了用于构建模型的构造和参数。PMML 涵盖了多种统计技术。例如,为了出现一个神经网络,它定义了所有的神经层和神经元之间的连贯权重。对于一个决策树来说,它定义了所有树节点及简略和复合谓语。
输入 
定义了预期模型输入。对于一个分类工作来说,输入能够包含预测类及与所有可能类相干的概率。
指标 
定义了利用于模型输入的后处理步骤。对于一个回归工作来说,此步骤反对将输入转变为人们很容易就能够了解的分数(预测后果)。
模型解释 
定义了将测试数据传递至模型时取得的性能度量规范(与训练数据绝对)。这些度量规范包含字段相关性、混同矩阵、增益图及接收者操作特色(ROC)曲线图。
模型验证 
定义了一个蕴含输出数据记录和预期模型输入的示例集。这是十分重要的一个步骤,因为在应用程序之间挪动模型时,该模型须要通过匹配测试。这样就能够确保,在出现雷同的输出时,新零碎能够生成与旧零碎同样的输入。 如果理论状况是这样的话,一个模型将被认为通过了验证,且随时可用于实际。
 
一个通用的PMML文件构造如下(参考http://dmg.org/pmml/v4-3/Gene...:

<?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>

 

3 PMML模型的生成和加载相干类库

PMML模型的生成相干的库须要看咱们应用的离线训练库。如果咱们应用的是sklearn,那么能够应用sklearn2pmml这个python库来做模型文件的生成,这个库装置很简略,应用"pip install sklearn2pmml"即可。如果应用的是Spark MLlib, 这个库有一些模型曾经自带了保留PMML模型的办法,惋惜并不全。如果是R,则须要安装包"XML"和“PMML”。此外,JAVA库JPMML能够用来生成R,SparkMLlib,xgBoost,Sklearn的模型对应的PMML文件。github地址是:https://github.com/jpmml/jpmml。

加载PMML模型须要指标环境反对PMML加载的库,如果是JAVA,则能够用JPMML来加载PMML模型文件。

能够看出,要应用PMML,须要两步的工作,第一块是将离线训练失去的模型转化为PMML模型文件,第二块是将PMML模型文件载入在线预测环境,进行预测。这两块都须要相干的库反对。

不过,当训练和预测应用同一种开发语言的时候,PMML 就没有必要应用了,因为任何两头格局都会就义掉独有的优化。

整个流程分为两局部:离线和在线。
离线局部流程是将样本进行特色工程,而后进行训练,生成模型。个别离线局部罕用 Python 中的 sklearn、R 或者 Spark ML 来训练模型。
在线局部是依据申请失去样本数据,对这些数据采纳与离线特色工程一样的形式来解决,而后应用模型进行评估。个别在线局部罕用 Java、C++ 来开发。
离线局部与在线局部是通过 PMML 连贯的,也就是说离线训练好了模型之后,将模型导出为 PMML 文件,在线局部加载该 PMML 文件生成对应的评估模型。
咱们能够看到,PMML 是连贯离线与在线环节的要害,个别导出 PMML 文件和 加载 PMML 文件都须要各个语言来做独自的实现。不过侥幸的是,曾经有很多大神实现了这些,能够参见:https://github.com/jpmml 。

 

4. PMML模型生成和加载示例

将离线训练失去的模型转化为PMML模型文件

上面给一个示例,应用sklearn生成一个决策树模型,用sklearn2pmml生成模型文件,用JPMML加载模型文件,并做预测。

首先是用用sklearn生成一个决策树模型,因为咱们是须要保留PMML文件,所以最好把模型先放到一个Pipeline数组外面。这个数组外面除了咱们的决策树模型以外,还能够有归一化,降维等预处理操作,这里作为一个示例,咱们Pipeline数组外面只有决策树模型。代码如下:

import numpy as npimport matplotlib.pyplot as plt%matplotlib inlineimport pandas as pdfrom sklearn import treefrom sklearn2pmml.pipeline import PMMLPipelinefrom sklearn2pmml import sklearn2pmml import osos.environ["PATH"] += os.pathsep + 'C:/Program Files/Java/jdk1.8.0_171/bin' X=[[1,2,3,1],[2,4,1,5],[7,8,3,6],[4,8,4,7],[2,5,6,9]]y=[0,1,0,2,1]pipeline = PMMLPipeline([("classifier", tree.DecisionTreeClassifier(random_state=9))]);pipeline.fit(X,y) sklearn2pmml(pipeline, ".\demo.pmml", with_repr = True)

下面这段代码做了一个非常简单的决策树分类模型,只有5个训练样本,特色有4个,输入类别有3个。理论利用时,咱们须要将模型调参结束后才将其放入PMMLPipeline进行保留。运行代码后,在当前目录会失去一个PMML的XML文件,能够间接关上看,内容大略如下:

<?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-SkLearn" version="1.5.3"/>        <Timestamp>2018-06-24T05:47:17Z</Timestamp>    </Header>    <MiningBuildTask>        <Extension>PMMLPipeline(steps=[('classifier', DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,            max_features=None, max_leaf_nodes=None,            min_impurity_decrease=0.0, min_impurity_split=None,            min_samples_leaf=1, min_samples_split=2,            min_weight_fraction_leaf=0.0, presort=False, random_state=9,            splitter='best'))])</Extension>    </MiningBuildTask>    <DataDictionary>        <DataField name="y" optype="categorical" dataType="integer">            <Value value="0"/>            <Value value="1"/>            <Value value="2"/>        </DataField>        <DataField name="x3" optype="continuous" dataType="float"/>        <DataField name="x4" optype="continuous" dataType="float"/>    </DataDictionary>    <TransformationDictionary>        <DerivedField name="double(x3)" optype="continuous" dataType="double">            <FieldRef field="x3"/>        </DerivedField>        <DerivedField name="double(x4)" optype="continuous" dataType="double">            <FieldRef field="x4"/>        </DerivedField>    </TransformationDictionary>    <TreeModel functionName="classification" missingValueStrategy="nullPrediction" splitCharacteristic="multiSplit">        <MiningSchema>            <MiningField name="y" usageType="target"/>            <MiningField name="x3"/>            <MiningField name="x4"/>        </MiningSchema>        <Output>            <OutputField name="probability(0)" optype="continuous" dataType="double" feature="probability" value="0"/>            <OutputField name="probability(1)" optype="continuous" dataType="double" feature="probability" value="1"/>            <OutputField name="probability(2)" optype="continuous" dataType="double" feature="probability" value="2"/>        </Output>        <Node>            <True/>            <Node>                <SimplePredicate field="double(x3)" operator="lessOrEqual" value="3.5"/>                <Node score="1" recordCount="1.0">                    <SimplePredicate field="double(x3)" operator="lessOrEqual" value="2.0"/>                    <ScoreDistribution value="0" recordCount="0.0"/>                    <ScoreDistribution value="1" recordCount="1.0"/>                    <ScoreDistribution value="2" recordCount="0.0"/>                </Node>                <Node score="0" recordCount="2.0">                    <True/>                    <ScoreDistribution value="0" recordCount="2.0"/>                    <ScoreDistribution value="1" recordCount="0.0"/>                    <ScoreDistribution value="2" recordCount="0.0"/>                </Node>            </Node>            <Node score="2" recordCount="1.0">                <SimplePredicate field="double(x4)" operator="lessOrEqual" value="8.0"/>                <ScoreDistribution value="0" recordCount="0.0"/>                <ScoreDistribution value="1" recordCount="0.0"/>                <ScoreDistribution value="2" recordCount="1.0"/>            </Node>            <Node score="1" recordCount="1.0">                <True/>                <ScoreDistribution value="0" recordCount="0.0"/>                <ScoreDistribution value="1" recordCount="1.0"/>                <ScoreDistribution value="2" recordCount="0.0"/>            </Node>        </Node>    </TreeModel></PMML>

能够看到外面就是决策树模型的树结构节点的各个参数,以及输出值。咱们的输出被定义为x1-x4,输入定义为y。

将PMML模型文件载入在线预测环境,进行预测

创立一个Maven或者gradle工程,退出JPMML的依赖,这里给出maven在pom.xml的依赖,gradle的构造是相似的。

<dependency>    <groupId>org.jpmml</groupId>    <artifactId>pmml-evaluator</artifactId>    <version>1.4.1</version></dependency><dependency>    <groupId>org.jpmml</groupId>    <artifactId>pmml-evaluator-extension</artifactId>    <version>1.4.1</version></dependency>

接着就是读取模型文件并预测的代码了,具体代码如下:

import org.dmg.pmml.FieldName;import org.dmg.pmml.PMML;import org.jpmml.evaluator.*;import org.xml.sax.SAXException; import javax.xml.bind.JAXBException;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.util.HashMap;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;public class PMMLDemo {    private Evaluator loadPmml(){        PMML pmml = new PMML();        InputStream inputStream = null;        try {            inputStream = new FileInputStream("D:/demo.pmml");        } catch (IOException e) {            e.printStackTrace();        }        if(inputStream == null){            return null;        }        InputStream is = inputStream;        try {            pmml = org.jpmml.model.PMMLUtil.unmarshal(is);        } catch (SAXException e1) {            e1.printStackTrace();        } catch (JAXBException e1) {            e1.printStackTrace();        }finally {            //敞开输出流            try {                is.close();            } catch (IOException e) {                e.printStackTrace();            }        }        ModelEvaluatorFactory modelEvaluatorFactory = ModelEvaluatorFactory.newInstance();        Evaluator evaluator = modelEvaluatorFactory.newModelEvaluator(pmml);        pmml = null;        return evaluator;    }    private int predict(Evaluator evaluator,int a, int b, int c, int d) {        Map<String, Integer> data = new HashMap<String, Integer>();        data.put("x1", a);        data.put("x2", b);        data.put("x3", c);        data.put("x4", d);        List<InputField> inputFields = evaluator.getInputFields();        //过模型的原始特色,从画像中获取数据,作为模型输出        Map<FieldName, FieldValue> arguments = new LinkedHashMap<FieldName, FieldValue>();        for (InputField inputField : inputFields) {            FieldName inputFieldName = inputField.getName();            Object rawValue = data.get(inputFieldName.getValue());            FieldValue inputFieldValue = inputField.prepare(rawValue);            arguments.put(inputFieldName, inputFieldValue);        }         Map<FieldName, ?> results = evaluator.evaluate(arguments);        List<TargetField> targetFields = evaluator.getTargetFields();         TargetField targetField = targetFields.get(0);        FieldName targetFieldName = targetField.getName();         Object targetFieldValue = results.get(targetFieldName);        System.out.println("target: " + targetFieldName.getValue() + " value: " + targetFieldValue);        int primitiveValue = -1;        if (targetFieldValue instanceof Computable) {            Computable computable = (Computable) targetFieldValue;            primitiveValue = (Integer)computable.getResult();        }        System.out.println(a + " " + b + " " + c + " " + d + ":" + primitiveValue);        return primitiveValue;    }    public static void main(String args[]){        PMMLDemo demo = new PMMLDemo();        Evaluator model = demo.loadPmml();        demo.predict(model,1,8,99,1);        demo.predict(model,111,89,9,11);     }}

 
代码里有两个函数,第一个loadPmml是加载模型的,第二个predict是读取预测样本并返回预测值的。

代码运行后果如下:

target: y value: {result=2, probability_entries=[0=0.0, 1=0.0, 2=1.0], entityId=5, confidence_entries=[]}1 8 99 1:2target: y value: {result=1, probability_entries=[0=0.0, 1=1.0, 2=0.0], entityId=6, confidence_entries=[]}111 89 9 11:1

也就是样本(1,8,99,1)被预测为类别2,而(111,89,9,11)被预测为类别1。

以上就是PMML生成和加载的一个示例,应用起来其实门槛并不高,也很简略。

5. PMML总结与思考

第一个就是PMML为了满足跨平台,就义了很多平台独有的优化,所以很多时候咱们用算法库本人的保留模型的API失去的模型文件,要比生成的PMML模型文件小很多。同时PMML文件加载速度也比算法库本人独有格局的模型文件加载慢很多。

第二个就是PMML加载失去的模型和算法库本人独有的模型相比,预测会有一点点的偏差,当然这个偏差并不大。比方某一个样本,用sklearn的决策树模型预测为类别1,然而如果咱们把这个决策树落盘为一个PMML文件,并用JAVA加载后,持续预测方才这个样本,有较小的概率呈现预测的后果不为类别1.

第三个就是对于超大模型,比方大规模的集成学习模型,比方xgboost, 随机森林,或者tensorflow,生成的PMML文件很容易失去几个G,甚至上T,这时应用PMML文件加载预测速度会十分慢,此时举荐为模型建设一个专有的环境,就没有必要去思考跨平台了。

此外,对于TensorFlow,不举荐应用PMML的形式来跨平台。可能的办法一是TensorFlow serving,本人搭建预测服务,然而会稍有些简单。另一个办法就是将模型保留为TensorFlow的模型文件,并用TensorFlow独有的JAVA库加载来做预测。