关于机器学习:机器学习PMML简述

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库加载来做预测。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理