乐趣区

关于机器学习:机器学习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 库加载来做预测。

退出移动版