1.人类可读的规则
这一章的标题可能会冒犯一些开发人员。到目前为止,我们所讨论的所有规则都不是人类可读的吗?难道我们人类吗?本章背后的想法是引入其他方法来定义Drools中的规则,这些规则更便于用户使用。在本章中,“人”指的是非技术人员。 到目前为止,我们已经介绍了一种定义规则和知识的方法:DRL语言。这种语言即使在大多数情况下是不合适的,对于没有技术背景的用户来说也是不合适的。即使这样,DRL也需要一定的时间来熟悉它。Drools通过支持对DRL的不同抽象,从而使语言更简单,从而提供了其他的知识创建方法。 拥有更简单、更具体的语言为我们提供了一个巨大的优势:我们可以在开发周期中将主题专家(SME)囊括进开发小组。业务需求不再需要被开发人员转换为技术工件(例如类、规则等)。编写这些工件的人可以是具有业务专长的人。这是迈向商业规则引擎的圣杯的一步:业务人员编写的业务规则。 这一章将介绍Drools提供的一些抽象的抽象,以及我们可以从各种资源类型中生成DRL的其他一些方法。本章所包含的主题是: >如何在Drools中定义和使用领域特定语言(DSL) >如何使用电子表格来定义规则 >如何使用模板从结构化数据生成DRL >介绍如何使用PMML(预测模型标记语言)
1.1 领域特定语言(DSL)
我们将要讨论的第一个关于DRL的抽象是领域特定的语言,或者仅仅是DSL。DSL是为特定上下文定制DRL的一种很好的方法。在前面章节里,我们讨论了在Drools中创建规则和执行规则所需的所有概念。这些概念需要一定的知识。对于规则的LHS,我们需要了解DRL语法,并了解诸如模式匹配、模型的内部结构、对外部系统的调用等内容的概念。对于RHS,我们需要了解Java和在此上下文中存在的一些自动变量,例如kcontext和drools。为了编写业务规则,必须掌握所有这些知识,这似乎有点过分了,事实上的确如此。要填补DRL所需的技术需求和领域专家之间的差距,其中一种方法是使用DSL。 Drools DSL背后的概念很简单。创建一个包含面向业务的概念的字典文件,并将其翻译成DRL。领域专家仅需要在编写规则时,要了解面向业务的概念,而不必担心DRL的技术方面。 定义业务概念和DRL之间的转换的字典文件简单地称为Drools中的DSL。包含使用业务概念定义的规则的文件称为DSLR。当技术团队负责创建和维护dictionary文件(DSL)时,SME负责业务规则的创建和维护(DSLR)。 DSL是由Drools支持的;没有特殊的依赖项,在我们开始使用它之前,不需要任何配置。
1.1.1 字典文件(即DSL)
字典文件(或DSL)是一个包含DSL条目的文本文件(带有一个.dsl拓展名)。该文件以开括号“[”开始的行被认为是一个DSL条目,都被认为是DSL条目。以井号“#”开头的行被认为是注释。从一个既不是括号“[”也不是“#”号开始的行,被认为是前一个DSL条目的一部分。DSL条目的格式如下:
[<scope>][<type definition>]<dsl expression>=<replacement text>
<scope>部分目前支持四种类型: >[when]或者[condition]:这个DSL条目只被用于LHS,即规则的条件部分 >[then]或者[consequence]:这个DSL条目仅对于规则的RHS有用,就是规则的动作和结果。 >[*]:这个DSL条目在规则的条件和操作部分都是有效的 >[keyword]:这个DSL条目在DSLR文件的任何部分都是有效的,甚至在规则定义之外也是有效的。 < type definition >部分不是强制的(我们可以省略它,或者使用空的括号[]),被用于作为编辑器的提示,比方说KIE-Workbench。 在 <type definition>之后就是 <dsl expression> ,这是将在DSLR文件中匹配和替换的文本。一个 <dsl expression>包括一个java正则表达式(不会的去 https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html)和任意数量的嵌入式变量定义。变量定义包含在括号中("{"和"}"),它包含一个变量名和两个可选部分,由冒号“:”分隔开。如果只存在一个可选部分,则将其作为正则表达式来匹配将分配给该变量的文本;如果有两个可选部分,第一个是作为一个编辑器的提示(比如说是KIE-Workbench),第二个是正则表达式。一个<dsl expression>通常以等号结尾(=) 在等号之后, <replacement text>将会被用于替换DSLR中的任何与 <dsl expression>相匹配的部分。在 <dsl expression>部分定义的变量可以在这部分中使用,就像命名正则表达式匹配组一样,通过使用带括号内的变量的名称。可选地,变量名后面可能会有一个感叹号“!”以及一个变换函数。支持的转换函数在下面的表中详细说明。
Name | Description |
---|---|
ul | 把所有字母转换成大写字母。 |
lc | 把所有字母转换成小写字母。 |
ucfirst | 把第一个字母转换成大写字母,所有其他字母都转换成小写字母 |
num | 从字符串中提取所有的数字。如果字符串的最后两个数字在(.)或者(,)之前,那么就会插入一个十进制的标识 |
a?b/c | (其实就是三元运算符)将字符串与字符串a进行比较,如果它们是相等的,则将其替换为b,否则用c代替。但是c可以是另一个三元组a、b、c,这样整个结构实际上是一个转换表。 |
一个典型的(非常简单的)DSL条目可能看起来像下面这样:
[when]There is a Customer=Customer()
上面的这个DSL条目讲的是我们会加DSLR中的所有的“There is a Customer”文本都替换为Customer()表达式。
1.1.1.1 为模式添加约束
在创建特定于领域的语言时,一个常见的需求是能够将任意数量的约束添加到模式中。比如,我们可能想要去表达过滤器,比如像: a customer with age greater than 40, a customer with GOLD category,甚至是组合的表达式:a customer in the GOLD category and older than 40 years .在我们的模型中,为每个可能的约束组合使用一个单独的DSL条目是完全没有意义的。Drools允许我们为DSL中的模式去指定特别的约束,并且以任何方式将它们组合到DSLR文件中。通过将一个连接字符的符号“-”添加到DSL条目的 <dsl expression>部分,就可以完成这个魔术了,下面看实例:
[when]There is a Customer=Customer() [when]- age greater than {years:/d*} years=age > {years} [when]- category is {category:/w*}=category == Customer.Category. {category}
上述的DSL定义,允许我们创建一个规则,DSLR如下:
rule "A GOLD Customer older than 31 years"
when
There is a Customer
- age greater than 31 years
- category is GOLD
then
...
end
当以一个“-”作为开始的<dsl expression>被处理了,它的结果不会作为生成的DRL中的新行添加,而是将它添加到最后一个模式行作为约束。之前的DSLR代码将被翻译成以下的DRL:
rule "A GOLD Customer older than 31 years"
when
Customer(age > 31, category == Customer.Category.GOLD)
then
...
end
当单一模式出现多个约束时,总是使用逗号(),这意味着约束在DSL中都是AND的。 连接字符“-”也可以用于定义在[then]上下文中的DSL条目。在本例中,该条目被假定为modify语句的一部分,其内容被添加到前一行(这必须是一个有效的modify语句):
[when]There is a Customer=$c: Customer() [when]- age greater than {years:/d*} years=age > {years} [then]Update Customer=modify($c)/{/} [then]- set Category to {category:/w*}= setCategory(Customer.Category. {category})
使用上面的规则,我们可以写出如下的DSLR文件,内容如下:
rule "Mark Customers older than 31 years as BRONZE"
when
There is a Customer
- age greater than 31 years
then
Update Customer
- set Category to BRONZE
end
从例子中得到的DRL将是:
rule "Mark Customers older than 31 years as BRONZE"
when
$c: Customer(age > 31)
then
modify ($c) { setCategory(Customer.Category.BRONZE) }
end
1.1.2 规则文件
规则文件,也称为DSLR文件,是一个以.dslr为拓展名的文件。DSLR使用基于领域的语言来定义的。以前的Drools版本需要DSLR文件来指定使用哪一种DSL来处理它。现在的Drools已经不再是这样了;所有的被KIE Base包含的DSL文件都要被用于在相同的KIE Base下的DSLR文件, 将DSLR文件转换成DRL的步骤如下: 1.每个[keyword]条目都应用于整个DSLR.如果[keyword]包含变量,这些变量被捕获并替换为相应的值。 2.所有存在于DSLR的规则的LHS和RHS都被处理了。DSLR文本中的每一行都与DSL文件中定义的每个DSL条目相匹配。这意味着DSL文件中的条目顺序很重要。当找到匹配时,DSLR文本将被相应的DRL替换。如果DSL条目定义了任何变量,那么它的值将会从DSLR文件中复制到生成的文本中。 3.如果DSLR中的一行是以“-”开始的,那么扩展的结果被添加到先前扩展的模式中---如果这个上下文是[when]的话---或者以前扩展的modify语句,如果上下文是[then]的话。 详细的转换过程请参考一下地址: http://docs.jboss.org/drools/release/6.3.0.Final/drools-docs/html/ch08.html#d0e11300 默认情况下啊,原生DRL或者java语法在DSLR文件中是不允许的。然而,有一种机制可以在DSLR文件中混合DRL和java语句,供有经验的用户使用。任何在DSLR文件中以“>” 号开始的一行语句,将会被翻译器跳过并在最终的DRL资源中保持不变。这个功能提供了克服DSL的一些限制的方法
1.1.3 DSL故障排除
使用DSL/DSLR的最大的困难就是理解和修复错误。最根本的问题就是错误报告基于生成的DRL,并且不会引用用于创建DRL的高级DSLR语句。 我们可以使用一些方法使检测和纠正错误变得更容易。 我们在DSL中所拥有的第一个方便的特性是,使用一种特殊的注释开始我们可以让Drools记录关于DSL翻译过程的某些信息。这个特殊的注释可以包含以下关键字,这些关键字将启用DSL调试功能的特定特性:
关键字 | 描述 |
---|---|
result | 打印生成的DRL文本以及行号 |
steps | 打印每个扩展步骤的条件和结果行 |
keyword | 转储scope是“keyword”的DSL条目的内部表征 |
wnen | 转储scope是“when”或者“*”的DSL条目的内部表征 |
then | 转储scope是“then”或者“*”的DSL条目的内部表征 |
usage | 显示所有DSL条目的使用情况统计数据。 |
作为一个例子,我们可以像下面这样添加这么一行文本到DSL样例中
#/ debug display result, steps and usage
通过添加这一行,Drools将记录一些关于每个DSL语句的使用情况(usage)的统计信息,每个DSLR句子(step)中涉及的转换步骤,以及最终的DRL(result)。 PS:Drools将使用SLF4j来记录DSL中调试选项的输出。确保您为org.drools包配置了一个适当的日志记录器。 为了调试,我们还可以用另一种方法从DSL/DSLR生成DRL,它涉及到Drools编译器用于此任务的内部类:org.drools.compiler.compiler.DrlParser和 org.drools.compiler.lang.dsl.DefaultExpanderResolver 。使用这两个类的组合,我们可以将DSL和DSLR资源转换为DRL,以分析最终的结果。以下几行将把一些DSL和DSLR资源转换为普通DRL:
String dslContent = //Get the DSL content from somewhere
String dslrContent = //Get the DSLR content from somewhere
DrlParser parser = new DrlParser();
DefaultExpanderResolver resolver = new DefaultExpanderResolver(new
StringReader(dslContent));
String drl = parser.getExpandedDRL( dslrContent, resolver);
在执行此代码后,drl字符串将包含扩展的drl代码。 现在让我们来看一个具体的场景,展示到目前为止所介绍的所有主题。
1.1.4 一个简单的场景
现在让我们把它放在一个简单的例子中。回到我们的电子商店,让我们假设我们想为我们的客户创建一些分类规则。在这个简单的示例中,分类逻辑将基于客户的年龄。因为我们希望领域专家能够编写规则,所以我们必须为它们提供一个简单的、领域特定的语言,只包含适用于此场景的那些表达式。然后,领域专家将使用这种定制的语言来表达必要的业务逻辑。让我们假设我们想要实现的业务逻辑如下:
年龄Age | 分类Category |
---|---|
Between 18 and 21 (inclusive) | N/A |
Between 22 and 30 (inclusive) | BRONZE |
Between 31 and 40 (inclusive) | SILVER |
More than 40 | GOLD |
只有当客户没有已经分配的类别(意味着当前的类别是n/a)时,才应该应用分类规则。
接下来将展示所描述的场景的DSL
# Simple DSL example file
[keyword]avoid looping=no-loop true
[when]There is a Customer=$c:Customer()
[when]- with age between {low:/d*} and {high:/d*}=age >= {low}, age <= {high}
[when]- who is older than {low:/d*}=age > {low}
[when]- without a Category set=category == Customer.Category.NA
[then]Set Customer Category to {category:/w*}=modify($c)/{ setCategory(Customer.Category.{category}) /};
在初始注释之后的第一个句子是一个[keyword],它允许我们在规则中使用一个更友好的替代方法来替代no-loop属性。紧接着[keyword]的是我们定义的[wnen]语句。这些句子将允许我们为我们正在处理的场景编写必要的规则。最后是[then]语句。这个句子可以用来给客户设置一个类别。属于modify语句的括号必须被转义(/{和/}),因此它们不会与变量引用发生冲突。 下面的DSLR规则只是中领域专家需要编写的四个必需规则中的一个。这个场景的完整实现和单元测试可以在与本章相关的源包中找到:
rule "Categorize Customers between 22 and 30"
avoid looping
when
There is a Customer
- with age between 22 and 30
- without a Category set
then
Set Customer Category to BRONZE
end
在前面的规则被转换为DRL之后,结果将如下:
rule "Categorize Customers between 22 and 30"
no-loop true
when
$c: Customer( age >= 22, age <= 30, category == Customer.Category.NA)
then
modify($c){ setCategory(Customer.Category.BRONZE) }
end
我们可以清楚地发现前两个规则中的哪个更面向业务。DSL的局限性之一是,领域专家仍然需要知道可用的句子是什么,以及它们之间如何相互作用。他们仍然需要在文本编辑器中手工编写规则。但是DSL的优点是,通过设置有限的句子,可以很容易地为领域专家构建一个定制的UI。Drools的Eclipse插件和KIE-Workbench 都支持DSL和DSLR的创建和使用。不幸的是,这两个应用程序超出了本书的范围。正如前面提到的,本章包含的源包包含了前面描述的场景的完整实现,以及一个更详细的场景,其中客户的分类基于他们所拥有的订单的数量。 分类规则允许我们在本章中介绍下一个主题。
1.2 决策表Decision tables
如果有那么一个工具,每个业务人员---上到CEO下到秘书---知道如何去使用这个工具,它就是一个电子表格。事实上,大多数时候,他们对电子表格的了解比IT部门的绝大多数人都要多。如果Drools的目标之一是成为面向业务的规则引擎,那么还有什么比提供与电子表格的一流集成更好的呢? dsl是非常强大的,但是,如果没有适当的UI,用户仍然需要自己编写规则。即使,通过使用DSL,可用于编写规则的可用选项被缩小到非常特定的句子,语法错误的概率,错误的语句,无效代码等等仍然居高不下。 另一方面,决策表提供了比DSL更受约束的环境,因此减少了DSL的大部分风险。
1.2.1 什么是决策表
在Drools中一个决策表就是一个XLS文件或者CSV(Comma Separated Value) 格式化文件,它使用复杂的语法定义了一系列的规则。 使用XLS而不是任何其他电子表格格式的优点是,许多办公套件产品已经支持它了。现在可以使用XLS文件编辑任何最流行的办公套件,比如MS office、LibreOffice、OpenOffice等等。 Drools中的决策表需要一个特定的结构才能执行。这个结构帮助编译器识别电子表格的不同部分,它们在最终生成决策表时生成的规则中扮演不同的角色。没错,就像DSL/DSLR一样,决策表首先被转换成DRL,然后再将其编译为一个kIE Container的一部分。 根据我们的简单分类场景,根据年龄分配客户类别,可以很容易地使用一个非常简单的决策表来重写它们,如下面所示:
即使我们还没有讨论过决策表的结构,但是简单地看一下它是非常简单的。决策表的列表性质使其易于阅读和修改。 现在我们来分析一下决策表的不同部分以及它们的含义
1.2.2 决策表结构
当顶一个决策表的时候有两个主要的关键字:RuleSet和RuleTable。RuleSet(B2)关键字确定了决策表的真正开始位置。使用这个关键字的列也很重要;它决定了必须用于表中的任何其他关键字的列。RuleTable(B6)关键字标识一组规则的开始。 PS:只有XLS文件的第一个工作表将被扫描到规则定义。
1.2.2.1 RuleSet部分
RuleSet(C2)后面的单元格是可选的,它是为本表中包含的所有规则定义包名。如果是空的,则默认的包名是rule_table。 下面的规则集可以用来定义DRL构造(除了规则)和规则属性,用于文档中包含的所有规则。 RuleSet(C2)下面的部分可以用来在这个文档所包含之内的为所有的规则,定义其DRL结构(不包含规则)和规则属性。 在我们的样例中(如上图),两个键值对(B3-C3和B4-C4)被用于指定规则所必须的一些java包导入,以及全局属性NO-LOOP。这部分可用的关键字是:
关键字 | 值 | 使用 |
---|---|---|
RuleSet | 生成的DRL文件所在的包名,可选的,默认值是rule_table。 | 必须是第一个条目 |
Sequential | true或者false。如果是true,那么salience就会被使用来确保规则从上到下的执行 | 可选的,最多一次。如果省略了,就会没有顺序的触发。 |
EscapeQuotes | true或者false。如果设置true,引号被转义,这样它们就会出现在DRL中。 | 可选的,最多一次。如果省略了,引号就会被转义。 |
Import | 一个要导入的以逗号分隔的Java类列表 | 可选,可重复使用。 |
Variables | DRL全局变量的声明—也就是说,一个类型后面跟着一个变量名。必须用逗号分隔多个全局定义 | 可选,可重复使用。 |
Functions | 根据DRL语法,一个或多个函数定义。 | 可选,可重复使用。 |
Queries | 根据DRL语法,一个或多个查询定义。 | 可选,可重复使用。 |
Declare | 根据DRL语法,一个或多个声明性类型 | 可选,可重复使用。 |
PS:Drools决策表中的关键字是不区分大小写的
除了前面的关键字,还可以在规则集部分中指定一组属性。这些属性将影响当前文档中所有规则的行为。所有可用属性的列表:
关键字 | 属性 |
---|---|
PRIORITY | 定义规则的“salience(优先级)”值的整数。受“Sequential”标识控制 |
DURATION | 定义规则的“duration(持续时间)”值的长整型值 |
TIMER | 一个计时器的定义。参见“Timers and Calendars” |
ENABLED | 一个布尔值,true标识规则激活,false标识规则不可用 |
CALENDARS | 一个日历的定义。参见“Timers and Calendars” |
NO-LOOP | 一个布尔值。当设置为true时候,由操作结果导致的改变并不触发规则的重复执行 |
LOCK-ON-ACTIVE | 一个布尔值。在相同的ruleflow或agenda组中,禁止使用此标志设置所有规则的附加激活 |
AUTO-FOCUS | 一个布尔值。 |
ACTIVATION-GROUP | 识别激活(或XOR)组的字符串。 |
AGENDA-GROUP | 标识一个agenda组的字符串,它必须通过给予它“focus”来激活。 |
RULEFLOW-GROUP | 标识一个ruleflow组的字符串 |
PS:所有这些属性只能在每个决策表中使用一次。
RuleSet部分的属性将影响定义规则的整个包。这可能包括定义在决策表之外的其他资产中定义的规则。为了获得更细粒度的控制,可以在RuleTable部分中使用属性,然后对特定规则的特定值进行独立配置。
现在让我们继续讨论规则本身定义的部分:RuleTable部分。
1.2.1.2 RuleTable部分
RuleTable这个关键字必须在与RuleSet相同的列中,它标识出了标识规则模板的一个部分。决策表文档中的一个sheet可以包含多个Rule Table条目。 可以将字符串附加到RuleSet单元格(C5)的内容中,以指定一个公共前缀,该前缀将在所有生成规则的名称之间共享。生成的规则的名称将由这个值和每个规则定义的行号组成。 RuleTable之后的每一行,指定列类型。有5种支持**类型**的列:
关键字 | 值 | 使用 |
---|---|---|
Name | 提供从行中生成的规则的名称,以覆盖默认名称 | 可选的,最多一列 |
DESCRIPTION | 一个文本,在生成的规则中产生一个注释。 | 可选的,最多一列 |
CONDITION | 在一个条件下构造一个约束条件的代码片段和内插值 | 一个规则里至少有一个 |
ACTION | 用于为规则的结果构造一个操作的代码片段和插值值。 | 一个规则至少有一个 |
METADATA | 构造规则的元数据项的代码片段和插值值。 | 可选的,任意数量的列 |
此外,上一节中介绍的所有属性也可以在这一行中使用。
在我们的样例中,我们有三个条件(B7,C7,D7),但是只有一个action操作(E7).每一个条件相当于一个模式或者模式中的约束。每一个操作都相当于我们生成的规则的RHS执行代码。
列类型后的行中的单元格根据列的类型有不同的含义。
对于CONDITION这个类型的列,这些单元格中的值表示的是生成规则LHS的模式。如果一个模式中有多个约束,那么单元格就可以合并成一个(就像我们的例子中的B8)。CONDITION 列下的第二行用来指定模式中的一个或多个约束。一个名为$param的特殊变量可以用来指定单元的某些部分,该单元将在列中进一步插入值。
~~~明天翻译这块,看不懂
对于ACTION;类型的列来说,下一行中单元格的值是可选的,如果存在值的话,它表示的是LHS里的对象的引用,全局变量或者是绑定变量。
ACTION类型列之后的第二行是操作代码。这段代码也接受插值变量,它将被追加到相应规则的RHS。如果前面的单元包含一个对象引用(也就是说,它不是空的),这个单元中的代码通过添加一个主导周期和一个结束分号来附加到引用中。如果对象引用单元格为空,那么这个单元格的值,则使用其变量插值的值作为该单元格的值。在这个单元格中也可以使用forall构造。
对于METADATA类型的列,下面的第一行被忽略,第二行用作生成的规则元数据的值。在这个单元格中也允许插入变量。在插值后的这个单元格的值,一个 @ character 将被预先准备,结果将被添加到生成规则的元数据部分(在规则名和when关键字之间)中。
对于类型是NAME和DESCRIPTION类型的列,前两行不使用。列类型行之后的第三行用来为列提供友好的名称。Drools将不会使用这一行中的值,但是拥有这一行使决策表更易于阅读。
从第4行开始,非空白条目提供了如前所述的内插的数据。空白单元格的疏忽导致相应的condition/action/metadata声明这个规则。
1.2.2.3 回到我们的场景
在上一节中,我们介绍了如何使用决策表来按年龄进行客户分类的简单场景。现在,让我们分析一下示例决策表如何被转换为DRL:
前面的电子表格可以作为与本章相关联的源包的一部分,以及相应的单元测试。 PS:与DSL既然相反,Drools的决策表需要一个特定的依赖,可以使得转换为DRL。这个依赖就是org.drools:drools-decisiontables。 我们已经知道,在决策表的RuleSet部分中使用的两个关键字指定了包名和它中定义的规则的no-loop属性。 根据电子表格,这些规则由应用于单一模式的三个条件组成。在这种情况下,模式是类型Customer。注意,我们已经将一个变量绑定到我们的模式:$c。前两个条件引用了我们的Customer类的age属性。当条件仅由二进制操作符(如==、>、<等)组成时,$param的使用是可选的.单元格B9的条件可以写成age >。在这些情况下,Drools会理解插值值必须放在条件的末端.当操作符是"==",事情更加简单了:只需要命名我们想要使用的属性就可以了--在这里就是age。 当一个数据单元被清空时(例如在我们的示例中是C14),相关的条件将不会被包含在生成的规则中。在我们的场景中,第14行中的规则并没有为客户的年龄带来最大的值;条件C9是不需要的。 电子表格还显示,生成的规则将包含一个由modify语句组成的单一操作。该语句用于设置匹配条件判断的客户$c的类别属性。 从第11行开始,我们找到了四条规则的定义。对于这些行,列A只是规则的描述性名称,而Drools则忽略它。列B、C、D和E中的值提供了用于条件和操作的内插数据. 如果我们仔细观察,第三个条件(d11 - d14)的值看起来可疑,他们都是“NA”值。对于这些固定值的类型,我们有三种选择:我们可以重复每个规则的值,我们可以将所有的单元合并在一起,以避免重复相同的值,或者我们可以在约束本身中设置值。后一种选择提出了一些挑战。如果我们修改了条件(在D9单元格)的值,将其变为"category == Customer.Category.NA",我们仍然需要去为单元格D11:D14提供值;否则,将在生成的规则中忽略整个条件。问题是,如果我们在这些单元格中设置一个值,Drools将认识到该条件不包含任何插值变量,并假定我们正在尝试使用隐式的“==$param”操作符。生成的代码将会失效。处理没有插值变量的条件的一个可能的解决方案是,用逗号将它们附加到其他条件中。在我们的样例中,我们可以修改B9的条件,让它看起来像“ age > $param, category == Customer.Category.NA ”或者“ category == Customer.Category.NA, age > $param ”。在这个情况下,C9单元格的条件并不是一个很好的候选者,因为在本专栏中有一个空白单元格。正如我们所看到的,一个条件并不局限于单一的DRL条件。 以第11行和第14行中的规则为例,让我们看看这些规则生成的DRL是什么样子的:
package chapter07.dtable.simple;
import org.drools.devguide.eshop.model.Customer;
no-loop true
rule "Simple Customer Categorization_11"
when
$c: Customer(age > 18, age <= 21, category == Customer.Category.
NA)
then
modify($c) { setCategory(Customer.Category.NA)}
end
rule "Simple Customer Categorization_14"
when
$c: Customer(age > 40, category == Customer.Category.NA)
then
modify($c) { setCategory(Customer.Category.GOLD)}
end
在前面的DRL中,我们可以看到第11行和第14行被转换为DRL的结果。在DRL中有一些重要的事情需要注意: >包名是RuleSet关键字指定的名称。 >import语句和全局no-loop属性也与RuleSet部分中使用的属性相匹配。 >因为我们在规则中没有使用NAME列,所以使用了默认名称。默认的名称由RuleTable值和产生规则的行号组成。 >由于第14行包含一个空白单元格,因此在生成的规则中不存在对应的条件。
1.2.4 决策表的故障排除
因为决策表引入了用户写的内容和实际生成的DRL之间的间接关系,因此处理错误可能具有挑战性。 举个例子,假设在C9的条件中有一个笔误。我们假设我们无意中写下了“ age <= $param”,而不是正确的“ age =< $param ”。当包含这个输入错误的决策表被编译时,它将生成以下错误消息:
Error while creating KieBase[
Message [id=1, level=ERROR, path=chapter07/dtable-simple/customer-
classification-simple.xls, line=8, column=0 text=[ERR 102] Line 8:29
mismatched input '=' in rule "Simple Customer Categorization_11"],
Message [id=2, level=ERROR, path=chapter07/dtable-simple/customer-
classification-simple.xls, line=16, column=0 text=[ERR 102] Line 16:29
mismatched input '=' in rule "Simple Customer Categorization_12"],
Message [id=3, level=ERROR, path=chapter07/dtable-simple/customer-
classification-simple.xls, line=24, column=0 text=[ERR 102] Line 24:29
mismatched input '=' in rule "Simple Customer Categorization_13"],
Message [id=4, level=ERROR, path=chapter07/dtable-simple/customer-
classification-simple.xls, line=0, column=0 text=Parser returned a null
Package]]
这些信息在三个不同的规则中提到了错误: Simple Customer Categorization_11 , Simple Customer Categorization_12 和 Simple Customer Categorization_13 。在这所有的用例中,错误都是相同的"mismatched input '='".这里的问题是,每个错误都指向生成的DRL中的行和列,但是我们实际上不知道DRL是什么样子。 在决策表生成的DRL中处理错误的方法之一,可能是最好的方法,就是将其转储到可以分析的地方。 决策表可以很容易地通过使用 drools-decisiontables项目的org.drools.decisiontable.DecisionTableProviderImpl类转换为DRL文件。
InputStream dtableIS = //get the input stream to the decision table
file
DecisionTableProviderImpl dtp = new DecisionTableProviderImpl();
String drl = dtp.loadFromInputStream(dtableIS, null);
DecisionTableProviderImpl定义loadFromInputStream方法接受两个参数: >决策表文件的InputStream >一个可选的org.kie.internal.builder.DecisionTableConfiguration实例,让我们配置DRL转换的一些方面. 与本章相关的源包有一个前代码的工作示例。 当我们处理错误时,能够从决策表中生成DRL是很有价值的。行号和列号的错误消息可以追溯到DecisionTableProviderImpl生成的DRL文件。
1.2.5 增强决策表
我们刚刚介绍的示例展示了Drools中的决策表的基本内容。 为了让这些电子表格用户的生活更轻松,我们还可以做很多有趣的事情。大多数优秀的特性电子表格支持也得到Drools的决策表支持。我们讨论的特征是:collapsed/fixed/merged 行和列、函数、颜色、单元间的链接, 等等。通过结合这些特性,我们可以创建更多定制的电子表格来增强用户的整体体验。 作为一个例子,我们可以采用本章介绍的原始决策表,并应用一些更改,如下:
原始决策表的增强版本可以在源包中找到,名字叫 customer-classification-enhanced.xls 。 在这个新版本的决策表中,有一些增强的是: >第3、4、8和9行隐藏,以避免显示带有技术内容的单元格。 >D11:D14被合并以避免重复的“NA”值 >E11:E14现在使用下拉菜单在NA,BRONZE,SILVER或者GOLD之间选择一个值。这个下拉图在图像中是不可见的,但是我们可以在源包中与这个示例相关联的电子表格中检查它。 >列B中的单元格使用的是一种有条件的格式,当它的值与上一个规则的上限重叠时,它将用红色标记(即B13)。 这个示例只显示了决策表所带来的一些可能性,从而创建了一种更优雅、更简洁的定义规则的方法。源包包含了我们的高级分类规则的决策表版本,它使用客户的订单数量来设置它的类别.决策表的名字叫: customer-classification-advanced.xls 。决策表提供了一种很好的方法,可以轻松地创建大量的规则,而不需要太多的工作。一旦定义了决策表的结构,规则作者唯一的工作就是在其单元中添加、更新或删除值。在编写规则时出错的可能性仍然存在,但是,与dsls/dslrs相比,风险要低得多。 决策表与DSL的另一个优点是,我们不需要为前者提供任何特殊的UI。大多数时候,业务用户已经对电子表格很熟悉了。大多数时候,业务用户已经对电子表格很熟悉了。在开始编写自己的规则之前,不需要向用户引入新的UI。 但是,决策表并不适用于每种情况:最大的限制之一是,我们可以使用决策表建模的规则必须具有相同的结构。对于像得分、类别和分类这样的情况,规则的结构几乎是一样的,唯一改变的是它们的约束的值,决策表是一个非常有效的选项。对于规则的结构不一定保持不变的情况,决策表根本没有任何好处。 另一个限制决策表是,规则和数据的结构是紧密耦合的;它们不能被单独地重用。 对于需要更多灵活性的情况,我们可能需要考虑另一种选择:规则模板。
1.3 规则模板
Drools中的规则模板是一种使用模板文件和表格数据源动态生成DRL规则的方法。通过表格式数据源,我们指的是可以在表格中表达的数据。这种数据源的典型示例是电子表格和数据库表。 Drools官网邮件列表和论坛中最常见的问题之一是如何根据存储在我们的应用程序之外的数据生成规则。典型的情况是数据库内的数据。处理这个场景的一种方法是使用规则模板。 规则模板的另一个优点是,规则的数据和结构是完全解耦的。相同的模板可以用于不同的数据集,相同的数据集可以用于不同的模板。与决策表相比,这提供了很大的灵活性。
1.3.1 规则模板结构
Drools规则模板是一个包含特殊关键字的文本文件,用来划分模板的不同部分,并定义模板内的变量以及它们应该在哪里使用。 作为一个例子,我们分许一个叫做 customer-classification-simple.drt的模板文件,他在我们的项目代码下:
template header
minAge
maxAge
previousCategory
newCategory
package chapter07.ruletemplate.simple;
import org.drools.devguide.eshop.model.Customer;
template "classification-rules"
rule "Categorize Customers_@{row.rowNumber}"
no-loop true
when
$c: Customer(age >= @{minAge}, age <= @{maxAge}, category == Customer.Category.@{previousCategory})
then
modify ($c){ setCategory(Customer.Category.@{newCategory})};
end
end template
模板文件的第一个行包含了标志模板开头的关键字:template header。接下的四行在模板中将会使用的变量名。在模板中,变量的名称是内联定义的,而不是数据集的一部分。这个模板中使用的数据集中的每一个列都将根据相应的变量名来命名。列和变量之间的关系是它在数据集中的位置。在本例中,第一列将被命名为minAge、第二个maxAge、第三个previousCategory,以及第四个newCategory。在变量定义后面的空白区域标志着这个部分的结束。 模板变量定义部分之后,会出现包含包定义和导入语句的标准规则头文本。在如果我们希望在模板中包含全局变量、类型声明或函数,那么这里也是执行该操作的地方。 "template"关键字表示规则模板的开始。对于数据集中的每一行,将生成该模板的一个实例。模板的名称必须在整个模板文件中是唯一的。 接下来的就是规则模板本身了。在规则模板中,以前定义的变量可以通过使用 @{var_name}来访问。对于数据集中的每一行,变量将被设置,它们的占位符在模板中被替换。如果规则模板中使用的任何变量都是空的,那么整个模板就会被省略。单个template部分可以包含多个规则定义。 在我们的模板中有一个特殊的变量,叫做 @{row.rowNumber} 。这个变量将包含正在处理的行的数量,并且在其他事情中很有用,避免在生成的规则中重复的名称。 PS:规则模板在决策表上的优势之一是,规则模板中的变量可以在规则的任何地方使用:作为模式的类名、操作符、属性名等等。 规则模板的另一个优点是,变量可以在任何顺序中使用,并且可以在需要时多次使用。 要标记规则模板的末尾,必须使用关键字"end template"。 现在我们已经了解了规则模板的基本结构,接下来让我们看看如何处理它们,以及如何处理数据集,以生成DRL规则。
1.3.2 使用规则模板
规则模板相关的类定义在单个项目中:drools-templates。这个项目包含了解析一个模板并从数据集中创建具体的DRL的必要类,四种类型的数据源已经得到了支持:电子表格、数组、对象和SQL结果集。 对于基于电子表格模板,Drools支持在kmoudle.xml中对于它们的声明性定义,方式使通过在<kbase>标签内部使用特别的<ruleTemplate>标签来进行配置:
<kbase name="template-simple-KBase" packages="chapter07.template-
dtable">
<ruleTemplate
dtable="chapter07/template-dtable/template-data.xls"
template="chapter07/template-dtable/customer-classification-simple.drt" row="3" col="2"/>
<ksession name="templateSimpleKsession"/>
</kbase>
前面的代码片段展示了一个名字叫 customer-classification-simple.drt并带有名字为template-data.xls的数据源文件的模板是如何将其包含进template-simple-KBase这个KIE Base中的。这个代码片段是与本章相关联的源包的一部分。 我们将在本节的其余部分进行介绍的样例将会使用编程方法来处理模板,创建DRL,然后使用生成的DRL,来创建一个KIE Base。 与本节相关的所有测试(测试类在Ruletemplatestest类)都使用一个助手方法从包含DRL代码的字符串中创建一个KIE Session.为了避免在本章后面的章节中重复这个方法,我们来分析一下这个方法:
private KieSession createKieSessionFromDRL(String drl){
KieHelper kieHelper = new KieHelper();
kieHelper.addContent(drl, ResourceType.DRL);
Results results = kieHelper.verify();
if (results.hasMessages(Message.Level.WARNING, Message.Level.ERROR)){
List<Message> messages = results.getMessages(Message.Level.WARNING, Message.Level.ERROR);
for (Message message : messages) {
System.out.println("Error: "+message.getText());
}
throw new IllegalStateException("Compilation errors were found. Check the logs.");
}
return kieHelper.build().newKieSession();
}
该方法的实现很简单。它使用一个包含DRL语法的字符串作为参数,它使用KieHelper实用程序类来编译它,并从它创建一个KIE Base。该方法还检查DRL编译期间的错误或警告。一旦建立了一个KIE Base,就会返回一个新的KIE Session。 PS:KieHelper实用程序类并不是Drools的公共API的一部分。这个类提供了一些方便的方法来避免大多数的样板代码,这些代码是用来获取一个kie base或kie session的。由于这个类不是kie-api工件的一部分,所以将来可能会出现向后不兼容的变化。 现在让我们详细了解Drools规则模板所支持的四种数据源类型。
1.3.2.1 电子表格数据来源
我们将要讨论的第一种类型的数据源在某种程度上类似于一个决策表。在使用规则模板时,我们希望用于生成具体规则的数据可以存储在电子表格文件中。我们已经讨论了使用电子表格的好处,特别是对于非技术用户。 作为我们的规则模板的输入,可以使用如下的电子表格:
前面的电子表格只包含模板所需的数据,以及需要编辑该表的人的一些有用的头信息。电子表格不包含需要使用的模板的任何信息,也不包含需要生成的规则的结构。 为了将这个电子表格转换为DRL,我们将使用我们之前介绍的模板文件( customer-classification-simple.drt )和createKieSessionFromDRL()帮助方法。
InputStream template = RuleTemplatesTest.class.getResourceAsStream("/chapter07/template-dtable/customer-classification-simple.drt");
InputStream data = RuleTemplatesTest.class.getResourceAsStream("/chapter07/template-dtable/template-data.xls");
ExternalSpreadsheetCompiler converter = new ExternalSpreadsheetCompiler();
String drl = converter.compile(data, template, 3, 2);
System.out.println(drl);
KieSession ksession = this.createKieSessionFromDRL(drl);
代码的前两行是将模板和数据文件作为InputStream实例。第四行是使用助手类的一个实例ExternalSpreadsheetCompiler将模板文件和电子表格中的数据转换到到DRL文件里。 ExternalSpreadsheetCompiler的compile()方法有四个参数:数据,模板,以及电子表格中数据的起始的的行和列。在我们的用例里,数据起始值为3行2列
1.3.2.2 列表数据源
行和列另一种向模板提供数据的方法是使用一个二维的字符串数组。在本例中,数组的第一个维度被用作行,第二个维度是列:
public void testSimpleTemplateWithArrays(){
InputStream template = RuleTemplatesTest.class.getResourceAsStream("/chapter07/template-dtable/customer-classification-simple.drt");
DataProvider dataProvider = new ArrayDataProvider(new String[][]{
new String[]{"18", "21", "NA", "NA"},
new String[]{"22", "30", "NA", "BRONZE"},
new String[]{"31", "40", "NA", "SILVER"},
new String[]{"41", "150", "NA", "GOLD"},
});
DataProviderCompiler converter = new DataProviderCompiler();
String drl = converter.compile(dataProvider, template);
System.out.println(drl);
KieSession ksession = this.createKieSessionFromDRL(drl);
this.doTest(ksession);
}
前面的代码显示了DataProviderCompiler 类的一个实例如何使用一个二维的字符串数组作为数据源来处理一个模板。数据被封装在一个ArrayDataProvider实例中.ArrayDataProvider类实现了 DataProvider接口。如果您有一个特殊的、自定义的数据源,需要将其输入到您的规则模板中,那么您可以实现自己的DataProvider,并使用DataProviderCompiler 将其与模板连接起来。
1.3.2.3 对象数据源
将数据呈现给模板的一种更面向对象的友好方式是使用对象作为模型。我们可以使用对象集合来保存模板所需要的数据,而不是一个二维的字符串数组。当对象用作模板的数据源时,模板中定义的变量的名称必须与模型类中的属性名称相匹配: 作为一个例子,让我们创建一个类来包含我们的分类场景的数据:
public class ClassificationTemplateModel {
private int minAge;
private int maxAge;
private Customer.Category previousCategory;
private Customer.Category newCategory;
}
这个类的实例将与模板处理后的规则相对应。注意,这个类的属性的名称对应于模板头部中变量的名称:
public void testSimpleTemplateWithObjects(){
InputStream template = RuleTemplatesTest.class.getResourceAsStream("/chapter07/template-dtable/customer-classification-simple.drt");
List<ClassificationTemplateModel> data = new ArrayList<>();
data.add(new ClassificationTemplateModel(18, 21, Customer.Category.NA, Customer.Category.NA));
data.add(new ClassificationTemplateModel(22, 30, Customer.Category.NA, Customer.Category.BRONZE));
data.add(new ClassificationTemplateModel(31, 40, Customer.Category.NA, Customer.Category.SILVER));
data.add(new ClassificationTemplateModel(41, 150, Customer.Category.NA, Customer.Category.GOLD));
ObjectDataCompiler converter = new ObjectDataCompiler();
String drl = converter.compile(data, template);
System.out.println(drl);
KieSession ksession = this.createKieSessionFromDRL(drl);
this.doTest(ksession);
}
前面的代码展示了 ClassificationTemplateModel对象的List怎么被用于作为模板的数据源。在这个例子中,ObjectDataCompiler类的实例被用于处理模板个对象的列表。
1.3.2.4 SQL结果集数据源
我们将要讨论的最后一个选项是使用SQL结果集作为数据源来处理一个模板文件。我们这里使用的SQL结果集,是 java.sql.ResultSet类。一个ResultSet类可以通过多种方式获取----例如,JDBC。即使我们可以很容易地将ResultSet转换为二维数组或对象集合,并使用前面介绍的处理模板的方法,Drools已经为我们提供了直接处理ResultSet实例的方法。 我们假设在数据库中有如下表,称为 ClassificationRules
如果我们想使用该表中的信息使用规则模板生成DRL,我们可以使用以下代码:
@Test
public void testSimpleTemplateWithDatabase() throws Exception{
InputStream template = RuleTemplatesTest.class.getResourceAsStream("/chapter07/template-dtable/customer-classification-simple.drt");
// setup the HSQL database with our rules.
Class.forName("org.hsqldb.jdbcDriver");
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:drools-templates", "sa", "");
try {
executeInDB("CREATE TABLE ClassificationRules ( id INTEGER IDENTITY, minAge INTEGER, maxAge INTEGER, previousCategory VARCHAR(256), newCategory VARCHAR(256) )", conn);
executeInDB("INSERT INTO ClassificationRules VALUES (1, 18, 21, 'NA', 'NA')", conn);
executeInDB("INSERT INTO ClassificationRules VALUES (2, 22, 30, 'NA', 'BRONZE')", conn);
executeInDB("INSERT INTO ClassificationRules VALUES (3, 31, 40, 'NA', 'SILVER')", conn);
executeInDB("INSERT INTO ClassificationRules VALUES (4, 41, 150, 'NA', 'GOLD')", conn);
} catch (SQLException e) {
throw new IllegalStateException("Could not initialize in memory database", e);
}
Statement sta = conn.createStatement();
ResultSet rs = sta.executeQuery("SELECT minAge, maxAge, previousCategory, newCategory " +
" FROM ClassificationRules");
final ResultSetGenerator converter = new ResultSetGenerator();
final String drl = converter.compile(rs, template);
System.out.println(drl);
KieSession ksession = this.createKieSessionFromDRL(drl);
this.doTest(ksession);
}
前面的例子使用了标准的JDBC类,比方说 Connection , Statement 和 ResultSet。该代码执行针对 ClassificationRules表的查询,并获取结果作为ResultSet。然后使用Drools的 ResultSetGenerator,结果集ResultSet和模板被转换成DRL。 需要注意的是,即使Drools模板附带了一组方便的函数,我们仍然可以使用任何其他模板引擎,比方说Velocity,或者StringTemplate。当然,我们不会有任何DataProvider类,但是请记住,这些类的最终目标是生成DRL代码。并且,DRL文件毕竟是纯文本,所以可以不管我们想要什么技术或框架。 现在让我们转到本章的最后一个主题,它将告诉我们如何将Drools与PMML资源集成,以允许在规则引擎中使用非规则的知识资产。
1.4 PMML
预测模型标记语言(PMML)是基于XML语言的,旨在提供一种交换不同预测模型的方法,用于分类或回归目的,使用数据挖掘或机器学习技术生成。PMML最初是由数据挖掘组开发的(在1997年),最新的版本你是4.3,官网是www.dmg.org. 即使PMML本身不是面向业务的语言,现在也可以从各种已知的应用程序中生成PMML文档, 比如说Knime或者R语言。 Drools中的PMML支持相对较新的。它最初是作为一个实验模块开始的,但是从6.1版开始,PMML是Drools生态系统的一等公民。PMML标准可以用于编码和交换分类和回归模型,例如神经网络、决策树、得分卡和其他模型。通过采用PMML,Drools可以获得访问知识变现的更广泛的选项。 不幸的是,并非所有在PMML中支持的模型都支持Drools。支持的模型的列表正在增加,目前支持的集合是这样的: >Clustering >Simple Regression >Naïve Bayes >Neural Networks >Scorecards >Decision Trees >Support Vector Machines BUT:解释这些模型类型超出了我们这部分的内容~~~。更多的信息参考官网:http://dmg.org/pmml/v4-3/GeneralStructure.html
1.4.1 Drools的PMML
PMML文档是一个xml文档,由四个主要部分组成:Header, Data Dictionary, Data Transformation,和Model。 Header部分包含关于文档本身的元信息。诸如所使用的模型的信息、用于生成该模型的应用程序以及创建时间戳等元素都可以在本节中找到。 Data Dictionary(数据字典)部分定义了文档可能使用的所有可能的数据字段。对于每个数据字段,指定一个名称及其类型。 可以在文档中指定一个Data Transformation(数据转换)部分,以定义传入数据和模型所需的数据之间的任何映射。 具体类型的模型是在文档的模型部分中指定的。这个部分的内容取决于所使用的模型(例如:神经网络,积分卡)。模型部分可能包含以下子部分: >挖掘模式:这定义了模型中使用的数据字典部分中字段的子集,以及一些元数据,比如它们的使用类型 >输出:这定义了模型的输出字段 >目标target:这允许对于模型的输出字段进行后处理。 就像Drools中的其他资源类型一样,PMML资产可以用两种不同的方式进行编译:以声明方式使用的kmodule.xml文件,或者通过使用KieHelper或KieBuilder实例来编程。 如果我们想在知识库中包含PMML资源,那么我们的项目必须声明一个maven的依赖,依赖的是org.drools:drools-pmml(当然你也可以将jar包扔类路径下)。 与本章相关联的源包包含一个简单的PMML示例( org.drools.devguide.chapter07.PMMLTest ),该示例以编程方式从PMML资源中创建一个KIE Base。 当Drools编译PMML文档时,将分析其组件,并创建规则、声明类型和入口点的适当组合,以模拟正在处理的模型所执行的计算。最终的结果将是一组Drools资产,这些资产将模仿原始模型的行为。 生成的入口点是评估某些输入数据的预测模型的一种可能方法,从而促进外部数据流的绑定(请参阅后面的替代评估技术)。声明的类型保存当前的输入、内部和输出值以及它们的元数据;生产规则根据模型的评估语义生成输出值。 在Drools中对PMML模型进行评估比在将模型编译成一系列数学操作的本机引擎中进行评估要慢。Drools的目标不是现在的性能,而是提供一个混合KIE Base的抽象概念,它包含了规则和非规则的推理元素。 PS:在将来的版本中,Drools将支持编译和“explicit”(基于规则的)模型,以及在不同的实现之间切换的能力。基于规则的模型的一个重要特性是模型的参数总是在工作内存中被断言为事实,并且可以通过其他实现自适应的在线培训策略的规则修改。 在一个KIE Session中,我们必须指定模型的输入字段的值,这是通过使用PMML编译器生成的对应的入口点。PMML模型中的每个挖掘字段都将创建一个名为 in_<FIELD_NAME>的入口点,其中的 <FIELD_NAME>为字段名。 有三种方法可以指定模型的输入: 直接实例化输入类型,如声明类型。我们讨论了如何在第4章中介绍了如何实例化声明类型。现在,我们知道模型中的每个输入字段将生成声明类型,我们可以实例化它们并将它们插入相应的会话: >将Java bean绑定到模型,该模型包含数据字典中的每个条目的字段。 >启用模仿数据字典的特质的声明。模型中的每个输入字段都将生成一个相应的特质类定义,其名称为 <FIELD_NAME>Trait。然后,我们可以为数据字典中的每个条目提供一个字段,以满足模型的需要。 在将对应的字段插入到一个KIE Session中,并调用fireAllRules()之后,将在会话中生成相应的输出字段。这些输出字段也被建模为声明类型。然后我们可以使用第5章介绍的一些技巧,从会话中获得这些值。 PS:PMML模型是无状态的。输入值的任何变化都将由输出的变化来反映。目前不支持并行或持久的评估。 为了更好地理解如何编译和使用PMML,让我们为我们的客户分类方案实现一个非常简单的模型。
1.4.2 客户分类决策树示例
在Drools中支持的PMML模型之一是决策树.一个决策树允许创建树状图,当评估时候,其中每一个节点名标识一个条件,确定在它下面的分支是否也应该被评估。我们可以参考数据挖掘小组的网站获取更多关于决策树的信息( http://www.dmg.org/v4-2-1/TreeModel.html ) 对于我们简单的分类场景,一个客户的类别由他当前的类别和年龄决定,下面的决策树将满足我们的需求:
前面的图显示了我们示例的决策树。我们从一个Customer对象开始,我们评估的第一个属性是它的category。如果他的category类别属性早已被设置过(即不是NA),那么树生成了一个特殊的结果,指示当前的类别不应该被修改。如果分类是NA的话,那么下一个需要被评估的属性就是age年龄。对于年两,我们有四个已知的片段,每一个都将生成一个不同的类别来分配给客户。这个决策树的PMML版本可以在与本章相关联的源包中找到( customer-classification-simple.pmml.xml)。接下来分析这个文件的不同部分。
1.3.4.1 Header
第一部分就是Header:
<Header description="A simple decision tree model for customer categorization."/>
在本例中,本节只定义文档的描述。
1.3.4.2 DataDictionary
这一节定义了三个字段: previousCategory、age和result
<DataDictionary numberOfFields="3">
<DataField name="previousCategory" optype="categorical" dataType="string">
<Value value="NA"/>
<Value value="BRONZE"/>
<Value value="SILVER"/>
<Value value="GOLD"/>
</DataField>
<DataField name="age" optype="continuous" dataType="integer"/>
<DataField name="result" optype="categorical" dataType="string">
<Value value="NO_CHANGE"/>
<Value value="NA"/>
<Value value="BRONZE"/>
<Value value="SILVER"/>
<Value value="GOLD"/>
</DataField>
</DataDictionary>
PMML不是面向对象;这就是为什么我们需要将客户的属性分解成简单的元素。 每个字段将在编译时创建一个声明类型和一个入口点。
1.3.4.3 Model
在这个特殊的例子中,模型是由一个 <TreeModel>元素定义的。这一部分由定义树结构的一系列<Node>元素组成。 本节还定义了两个重要的子部分:挖掘模式和输出。 挖掘模式标识该模型中使用的数据字典中的字段集:
<MiningSchema>
<MiningField name="previousCategory"/>
<MiningField name="age"/>
<MiningField name="result" usageType="predicted"/>
</MiningSchema>
在这种情况下,result字段被标记为“predicted”。这将告诉模型,在执行模型时需要计算该字段。 输出部分定义了模型的输出:
<Output>
<OutputField name="newCategory" targetField="result" />
</Output>
这个输出字段映射到挖掘字段result上,在编译模型时也会生成Drools中的声明类型。这个声明类型的实例将包含执行的结果。 本例子的测试程序在 org.drools.devguide.chapter07.PMMLTest。它使用一个单独的DRL资源,它定义一个查询来提取这个模型生成的结果:
query getNewCategory()
NewCategory($cat: value, valid == true)
end
查询基本上过滤了类型NewCategory的所有对象(这是Drools生成的声明类型),这些对象处于有效状态并返回它们的值。 一旦我们有了包含这个PMML文件的编译版本的会话,我们就可以使用下面的代码片段来提供模型的输入域的值:
KieSession ksession = //obtain a KIE Session reference.
ksession.getEntryPoint("in_PreviousCategory").insert("NA");
ksession.getEntryPoint("in_Age").insert(34);
ksession.fireAllRules();
//execute 'getNewCategory' query to get the result.
正如我们前面提到的,每个输入字段都将生成一个惟一的入口点,可以用来设置它的值。前面的示例将值“NA”设置给 previousCategory字段,age的值为34。根据我们的决策树,预期结果将是SLIVER
1.4.3 PMML故障排除
与DSL、决策表和规则模板一样,PMML在实际执行的DRL上增加了抽象级别。当出现问题时,错误的原因并不总是很容易识别。 好消息是,不同于DSL、决策表和规则模板——pmml是一种基于xml的语言,它为它的结构和值使用了定义良好的模式( http://www.dmg.org/v4-2-1/pmml-4-2.xsd )。使用一个模式来验证我们的文档,将消除由畸形或无效的XML引起的过早错误。 但是,如果我们仍然需要知道当PMML文档被编译时发生了什么情况,那么就有一种方法可以转储编译过程生成的DRL。在与本节相关的单元测试中,有一个名为printGeneratedDRL()的方法,它确实执行了这个方法。这个方法使用了 org.drools.pmml.pmml_4_2.PMML4Compiler类去将PMML转换为DRL文件。在编译PMML资源时,这个类是由KIE Builder内部使用的类。
1.4.5 PMML的限制
我们已经提到过,在Drools中,PMML的支持相对较新的,而且存在一些后果。第一个明显的后果是,并不是所有的PMML支持的模型,Drools也会支持。这种差距应该随着每一个新版本的drools - pmml模块而缩小 。另一个后果是, 这个模块还没有接触到相当多的听众。在使用这个模块时,需要一些粗糙的边、不支持的特性,甚至是bug。 当使用PMML建模我们的场景时,我们必须记住,预测模型是定量的,而过程的原始值是连续的或绝对的。在设计包含PMML文档的解决方案时,来自面向对象的环境,比如Java,可能会带来一些挑战。正如在模型绑定讨论中所暗示的,可以使用一个或多个对象来交付和收集模型使用的特性的值,但是预测模型本身将不会意识到那些值的特定领域特性。这是预测模型的固有特性,开发人员不应该做额外的假设。 当前drools-pmml实现的最大限制之一可能是一个会话只能用于执行一个模型的单一实例。如果我们想在我们的简单的树上评估脸给个客户,我们需要创建两个独立的KIE Sessions或者在单个的会话中对每个客户进行顺序评估,同时进行评估的问题是没有办法去解决这个问题
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/11181.html