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 np
import matplotlib.pyplot as plt
%matplotlib inline
import pandas as pd
from sklearn import tree
from sklearn2pmml.pipeline import PMMLPipeline
from sklearn2pmml import sklearn2pmml
import os
os.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:2
target: 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 库加载来做预测。