问:radar 提供哪些功能?
答:radar 目前主要提供一个风控引擎平台,通过规则引擎统一管理风险,并支持可视化配置和修改,最大程度的做到所见即所得。

问:radar 怎么集成其他平台或者数据能力?
答:radar 通过插件机制,集成其他数据能力,目前系统自带手机号码段和IP转换(ip2region)两个数据处理插件,通过插件的形式获取关联数据,后续前端页面会提供插件管理功能,方便大家落地的到具体的应用场景。
问: 作为一款实时的风控引擎,radar 为什么会采用 Springboot + Mongodb + Groovy 的架构设计?
为什么没有采用其它更加优秀的 Storm, Flink ,Drools 等框架?

答:作为一款开源版的风控引擎,在设计之初也经过多次调研,开源的目的是设计成通用风控解决方案,普及风控知识以及解决方法,首要的目的是通用,易用,好用,所以在选择技术方案的时候做了一些取舍,不是说 storm, flink 在实时流式处理方面不好,选择 mongodb 主要是考虑在 数据存储方式,数据时间窗口更具有优势,弱schema、json格式文档存储,自动失效,nosql,shading 更加具有通用性。
而 Drools 是大家都较为熟悉的非常优秀的规则引擎框架,之所以选择Groovy 自定义规则引擎,主要是考虑 Drools 在可视化编辑方面,还不够灵活,前端支持难度大,参考了开源社区其他人做的产品,好多还是手写 drools 脚本,而radar 做到了规则定义全中文支持,看不到变量的定义,配置灵活多变,当然groovy +(juel)+(QLExpress)动态脚本也有缺点,动态编译非常消耗性能,高并发可能导致频繁fullGc的问题。
问:目前radar底层使用mongodb,考虑的是长时间窗口(目前推荐3个月),相对于其它实时流式引擎(flink)来说,只能说是准实时,在处理时间窗口较短的场景(秒级,分钟,小时)有明显的弱势,后续是否需要支持flink,以应对实时性非常高的场景?
答:从目前项目架构来说,特征(abstraction)的提取的实现类是基于mongodb来实现的,理论上来说,只需要在基于flink 再实现一遍即可,后续将列入版本计划,同时支持 mongodb 和flink 通过配置选择使用。
问:规则的解析和执行是在什么时候进行的?
答:目前特征(abstraction)的提取和策略集(activation)的执行 都涉及到规则脚本的执行,规则的解析的逻辑在前端页面,具体可以参考 GroovyScriptUtil.java 和 groovyUtil.jsx (react) 这两个类的使用。
问: radar 的评分怎么做的?
答: radar 采用综合累计积分, 简单的来说: f(r) = (ax +n1)+ (by + n2) + (cz + n3) , f(r)为风险总分, xyz 为 特征,, abc 为权重 , n 为偏移量。
image.png
如图,过滤条件 1天内IP交易次数大于 30,假设现在达到31,
这条策略的计分为:20 + 1 31 100% = 51
最后简化成数学表达式:
image.png
问: 机器学习支持的方式(PMML)? 参考链接:https://www.cnblogs.com/pinard/p/9220199.html
答:现阶段很多机器学习框架都支持java 直接调用,所以直接集成相应的api 就好。

PMML概述

PMML是数据挖掘的一种通用的规范,它用统一的XML格式来描述我们生成的机器学习模型。这样无论你的模型是sklearn,R还是Spark MLlib生成的,我们都可以将其转化为标准的XML格式来存储。当我们需要将这个PMML的模型用于部署的时候,可以使用目标环境的解析PMML模型的库来加载模型,并做预测。
可以看出,要使用PMML,需要两步的工作,第一块是将离线训练得到的模型转化为PMML模型文件,第二块是将PMML模型文件载入在线预测环境,进行预测。这两块都需要相关的库支持。

2. PMML模型的生成和加载相关类库

PMML模型的生成相关的库需要看我们使用的离线训练库。如果我们使用的是sklearn,那么可以使用sklearn2pmml这个python库来做模型文件的生成,这个库安装很简单,使用”pip install sklearn2pmml”即可,相关的使用我们后面会有一个demo。如果使用的是Spark MLlib, 这个库有一些模型已经自带了保存PMML模型的方法,可惜并不全。如果是R,则需要安装包”XML”和“PMML”。此外,JAVA库JPMML可以用来生成R,SparkMLlib,xgBoost,Sklearn的模型对应的PMML文件。github地址是:https://github.com/jpmml/jpmml。
加载PMML模型需要目标环境支持PMML加载的库,如果是JAVA,则可以用JPMML来加载PMML模型文件。相关的使用我们后面会有一个demo。

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

下面我们给一个示例,使用sklearn生成一个决策树模型,用sklearn2pmml生成模型文件,用JPMML加载模型文件,并做预测。
完整代码参见我的github:https://github.com/ljpzzz/machinelearning/blob/master/model-in-product/sklearn-jpmml
首先是用用sklearn生成一个决策树模型,由于我们是需要保存PMML文件,所以最好把模型先放到一个Pipeline数组里面。这个数组里面除了我们的决策树模型以外,还可以有归一化,降维等预处理操作,这里作为一个示例,我们Pipeline数组里面只有决策树模型。代码如下:

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. %matplotlib inline
  4. import pandas as pd
  5. from sklearn import tree
  6. from sklearn2pmml.pipeline import PMMLPipeline
  7. from sklearn2pmml import sklearn2pmml
  8. import os
  9. os.environ["PATH"] += os.pathsep + 'C:/Program Files/Java/jdk1.8.0_171/bin'
  10. X=[[1,2,3,1],[2,4,1,5],[7,8,3,6],[4,8,4,7],[2,5,6,9]]
  11. y=[0,1,0,2,1]
  12. pipeline = PMMLPipeline([("classifier", tree.DecisionTreeClassifier(random_state=9))]);
  13. pipeline.fit(X,y)
  14. sklearn2pmml(pipeline, ".\demo.pmml", with_repr = True)

上面这段代码做了一个非常简单的决策树分类模型,只有5个训练样本,特征有4个,输出类别有3个。实际应用时,我们需要将模型调参完毕后才将其放入PMMLPipeline进行保存。运行代码后,我们在当前目录会得到一个PMML的XML文件,可以直接打开看,内容大概如下:

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  2. <PMML xmlns="http://www.dmg.org/PMML-4_3" version="4.3">
  3. <Header>
  4. <Application name="JPMML-SkLearn" version="1.5.3"/>
  5. <Timestamp>2018-06-24T05:47:17Z</Timestamp>
  6. </Header>
  7. <MiningBuildTask>
  8. <Extension>PMMLPipeline(steps=[('classifier', DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
  9. max_features=None, max_leaf_nodes=None,
  10. min_impurity_decrease=0.0, min_impurity_split=None,
  11. min_samples_leaf=1, min_samples_split=2,
  12. min_weight_fraction_leaf=0.0, presort=False, random_state=9,
  13. splitter='best'))])</Extension>
  14. </MiningBuildTask>
  15. <DataDictionary>
  16. <DataField name="y" optype="categorical" dataType="integer">
  17. <Value value="0"/>
  18. <Value value="1"/>
  19. <Value value="2"/>
  20. </DataField>
  21. <DataField name="x3" optype="continuous" dataType="float"/>
  22. <DataField name="x4" optype="continuous" dataType="float"/>
  23. </DataDictionary>
  24. <TransformationDictionary>
  25. <DerivedField name="double(x3)" optype="continuous" dataType="double">
  26. <FieldRef field="x3"/>
  27. </DerivedField>
  28. <DerivedField name="double(x4)" optype="continuous" dataType="double">
  29. <FieldRef field="x4"/>
  30. </DerivedField>
  31. </TransformationDictionary>
  32. <TreeModel functionName="classification" missingValueStrategy="nullPrediction" splitCharacteristic="multiSplit">
  33. <MiningSchema>
  34. <MiningField name="y" usageType="target"/>
  35. <MiningField name="x3"/>
  36. <MiningField name="x4"/>
  37. </MiningSchema>
  38. <Output>
  39. <OutputField name="probability(0)" optype="continuous" dataType="double" feature="probability" value="0"/>
  40. <OutputField name="probability(1)" optype="continuous" dataType="double" feature="probability" value="1"/>
  41. <OutputField name="probability(2)" optype="continuous" dataType="double" feature="probability" value="2"/>
  42. </Output>
  43. <Node>
  44. <True/>
  45. <Node>
  46. <SimplePredicate field="double(x3)" operator="lessOrEqual" value="3.5"/>
  47. <Node score="1" recordCount="1.0">
  48. <SimplePredicate field="double(x3)" operator="lessOrEqual" value="2.0"/>
  49. <ScoreDistribution value="0" recordCount="0.0"/>
  50. <ScoreDistribution value="1" recordCount="1.0"/>
  51. <ScoreDistribution value="2" recordCount="0.0"/>
  52. </Node>
  53. <Node score="0" recordCount="2.0">
  54. <True/>
  55. <ScoreDistribution value="0" recordCount="2.0"/>
  56. <ScoreDistribution value="1" recordCount="0.0"/>
  57. <ScoreDistribution value="2" recordCount="0.0"/>
  58. </Node>
  59. </Node>
  60. <Node score="2" recordCount="1.0">
  61. <SimplePredicate field="double(x4)" operator="lessOrEqual" value="8.0"/>
  62. <ScoreDistribution value="0" recordCount="0.0"/>
  63. <ScoreDistribution value="1" recordCount="0.0"/>
  64. <ScoreDistribution value="2" recordCount="1.0"/>
  65. </Node>
  66. <Node score="1" recordCount="1.0">
  67. <True/>
  68. <ScoreDistribution value="0" recordCount="0.0"/>
  69. <ScoreDistribution value="1" recordCount="1.0"/>
  70. <ScoreDistribution value="2" recordCount="0.0"/>
  71. </Node>
  72. </Node>
  73. </TreeModel>
  74. </PMML>

可以看到里面就是决策树模型的树结构节点的各个参数,以及输入值。我们的输入被定义为x1-x4,输出定义为y。
有了PMML模型文件,我们就可以写JAVA代码来读取加载这个模型并做预测了。
我们创建一个Maven或者gradle工程,加入JPMML的依赖,这里给出maven在pom.xml的依赖,gradle的结构是类似的。

  1. <dependency>
  2. <groupId>org.jpmml</groupId>
  3. <artifactId>pmml-evaluator</artifactId>
  4. <version>1.4.1</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.jpmml</groupId>
  8. <artifactId>pmml-evaluator-extension</artifactId>
  9. <version>1.4.1</version>
  10. </dependency>

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

  1. import org.dmg.pmml.FieldName;
  2. import org.dmg.pmml.PMML;
  3. import org.jpmml.evaluator.*;
  4. import org.xml.sax.SAXException;
  5. import javax.xml.bind.JAXBException;
  6. import java.io.FileInputStream;
  7. import java.io.IOException;
  8. import java.io.InputStream;
  9. import java.util.HashMap;
  10. import java.util.LinkedHashMap;
  11. import java.util.List;
  12. import java.util.Map;
  13. /**
  14. * Created by 刘建平Pinard on 2018/6/24.
  15. */
  16. public class PMMLDemo {
  17. private Evaluator loadPmml(){
  18. PMML pmml = new PMML();
  19. InputStream inputStream = null;
  20. try {
  21. inputStream = new FileInputStream("D:/demo.pmml");
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. }
  25. if(inputStream == null){
  26. return null;
  27. }
  28. InputStream is = inputStream;
  29. try {
  30. pmml = org.jpmml.model.PMMLUtil.unmarshal(is);
  31. } catch (SAXException e1) {
  32. e1.printStackTrace();
  33. } catch (JAXBException e1) {
  34. e1.printStackTrace();
  35. }finally {
  36. //关闭输入流
  37. try {
  38. is.close();
  39. } catch (IOException e) {
  40. e.printStackTrace();
  41. }
  42. }
  43. ModelEvaluatorFactory modelEvaluatorFactory = ModelEvaluatorFactory.newInstance();
  44. Evaluator evaluator = modelEvaluatorFactory.newModelEvaluator(pmml);
  45. pmml = null;
  46. return evaluator;
  47. }
  48. private int predict(Evaluator evaluator,int a, int b, int c, int d) {
  49. Map<String, Integer> data = new HashMap<String, Integer>();
  50. data.put("x1", a);
  51. data.put("x2", b);
  52. data.put("x3", c);
  53. data.put("x4", d);
  54. List<InputField> inputFields = evaluator.getInputFields();
  55. //过模型的原始特征,从画像中获取数据,作为模型输入
  56. Map<FieldName, FieldValue> arguments = new LinkedHashMap<FieldName, FieldValue>();
  57. for (InputField inputField : inputFields) {
  58. FieldName inputFieldName = inputField.getName();
  59. Object rawValue = data.get(inputFieldName.getValue());
  60. FieldValue inputFieldValue = inputField.prepare(rawValue);
  61. arguments.put(inputFieldName, inputFieldValue);
  62. }
  63. Map<FieldName, ?> results = evaluator.evaluate(arguments);
  64. List<TargetField> targetFields = evaluator.getTargetFields();
  65. TargetField targetField = targetFields.get(0);
  66. FieldName targetFieldName = targetField.getName();
  67. Object targetFieldValue = results.get(targetFieldName);
  68. System.out.println("target: " + targetFieldName.getValue() + " value: " + targetFieldValue);
  69. int primitiveValue = -1;
  70. if (targetFieldValue instanceof Computable) {
  71. Computable computable = (Computable) targetFieldValue;
  72. primitiveValue = (Integer)computable.getResult();
  73. }
  74. System.out.println(a + " " + b + " " + c + " " + d + ":" + primitiveValue);
  75. return primitiveValue;
  76. }
  77. public static void main(String args[]){
  78. PMMLDemo demo = new PMMLDemo();
  79. Evaluator model = demo.loadPmml();
  80. demo.predict(model,1,8,99,1);
  81. demo.predict(model,111,89,9,11);
  82. }
  83. }

代码里有两个函数,第一个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生成和加载的一个示例,使用起来其实门槛并不高,也很简单。

4. PMML总结与思考

PMML的确是跨平台的利器,但是是不是就没有缺点呢?肯定是有的!
第一个就是PMML为了满足跨平台,牺牲了很多平台独有的优化,所以很多时候我们用算法库自己的保存模型的API得到的模型文件,要比生成的PMML模型文件小很多。同时PMML文件加载速度也比算法库自己独有格式的模型文件加载慢很多。
第二个就是PMML加载得到的模型和算法库自己独有的模型相比,预测会有一点点的偏差,当然这个偏差并不大。比如某一个样本,用sklearn的决策树模型预测为类别1,但是如果我们把这个决策树落盘为一个PMML文件,并用JAVA加载后,继续预测刚才这个样本,有较小的概率出现预测的结果不为类别1.
第三个就是对于超大模型,比如大规模的集成学习模型,比如xgboost, 随机森林,或者tensorflow,生成的PMML文件很容易得到几个G,甚至上T,这时使用PMML文件加载预测速度会非常慢,此时推荐为模型建立一个专有的环境,就没有必要去考虑跨平台了。
此外,对于TensorFlow,不推荐使用PMML的方式来跨平台。可能的方法一是TensorFlow serving,自己搭建预测服务,但是会稍有些复杂。另一个方法就是将模型保存为TensorFlow的模型文件,并用TensorFlow独有的JAVA库加载来做预测。
我们在下一篇会讨论用python+tensorflow训练保存模型,并用tensorflow的JAVA库加载做预测的方法和实例。

参考:https://www.cnblogs.com/pinard/category/894692.html


风险的定义

翻开手机里面的APP可以看到用户注册,用户登录,提现,充值,兑换,交易等常见的功能,所谓风险就是对这些交易(各种交易行为的统称)非正常程度的评估。

风险控制

通常我们是如何处理风险的了?
例如:注册,用户提交注册请求的时候,我们会检测当前设备或者IP 在当前时间段的注册数,如果达到某一数量,限制注册一段时间。
登录, 很典型的处理方式,累计密码错误次数(通常3次以上),我们会延长其尝试时间。
提现, 限制小额提现的交易次数, 大额交易需要提前预约等。

风险的量化

所谓量化,就是以用户的行为特征为依据,然后通过科学的方式进行计算,得出一个综合的分数,通过这个风险的分数可以直观的判断交易的风险程度。

风险的集中管理

我们知道企业做大后,会有很多产品线,而几乎每一个产品都需要做风险控制,通常我们都是把风险控制的逻辑写在相应的业务功能代码里,
大量重复的风控逻辑代码耦合在我们的业务逻辑里面,随着时间的累积,代码会变得异常复杂,会给后期的维护造成巨大的人力成本和风险。
所以风险的集中管理势在必行,只有通过一个统一的管理平台,使用规则引擎,采用可视化配置的形式,平台化管理不同产品风控策略才是一种更好的方式, 而这正是Radar的初衷。