天天看点

《树莓派开发实战(第2版)》——2.5 用Apply和Chain构建更复杂的模型

本节书摘来异步社区《概率编程实战》一书中的第2章,第2.5节,作者:【美】avi pfeffer(艾维·费弗),更多章节内容可以访问云栖社区“异步社区”公众号查看。

figaro提供两种构建模型的有用工具,称作apply和chain。这两种工具都是重要的元素。apply可以在figaro中引入scala,利用其全部能力。chain可以无限的方式创建元素之间的相互依赖关系。目前为止您所看到的复合元素(如if和复合flip)可以特定的预定义方式创建依赖关系。chain可以超越这些预定义依赖关系,创建您所需要的任何依赖。

我们从apply开始,这个元素在com.cra.figaro.language包中。apply以一个元素和一个scala函数作为参数,它代表将scala函数应用到该元素值以获得新值的过程。例如:

val monthquality = apply(sunnydaysinmonth,

(i: int) =&gt; if (i &gt; 10) "good"; else if (i &gt; 5) "average"; else "poor")<code>`</code>

现在,您可以查询monthquality。不管使用哪一个版本的monthquality,得到的答案都相同:

val teamwinsinmonth = binomial(5, 0.4)

val monthquality = apply(sunnydaysinmonth, teamwinsinmonth,

这里,apply的两个元素参数是sunnydaysinmonth 和teamwinsinmonth,它们都是element[int]。函数参数有名为days和wins的integer参数。这个函数创建一个局部变量x,其值等于days * wins。注意,因为days和wins是常规的scala integer变量,x也是常规scala变量,而不是figaro元素。实际上,apply函数参数中的任何东西都是常规的scala内容。apply取得在常规scala值上操作的scala函数,将其“提升”为在figaro元素上操作的函数。

现在,查询这个版本的monthquality:

val goodmood = chain(monthquality, (s: string) =&gt;

图2-12展示了这个元素的结构。和apply类似,chain取两个参数:一个元素和一个函数。在本例中,元素是父节点,函数被称为链函数。chain和apply之间的差别是apply中的函数返回常规的scala值,而chain中的函数返回一个元素。在本例中,函数返回一个flip,选择哪一个flip取决于monthquality的值。所以,这个函数取得类型为字符串的参数,返回一个element[boolean]。

《树莓派开发实战(第2版)》——2.5 用Apply和Chain构建更复杂的模型

这个chain元素定义的随机过程如图2-13所示。这一过程有3个步骤。首先,为父节点生成一个值。在本例中,为monthquality生成值average。其次,对该值应用链函数以获得一个元素,该元素称作结果元素。在例子中,您可以检查链函数的定义,发现结果元素是flip(0.6)。第三,从结果元素生成一个值,例子中生成的是true。这个值成为子节点的值。

《树莓派开发实战(第2版)》——2.5 用Apply和Chain构建更复杂的模型

图2-13 chain元素定义的随机过程。首先,为父节点生成一个值。接下来,根据链函数选择结果元素。最后,从结果元素生成一个值

下面我们总结chain中涉及的所有类型。chain由两个类型参数化——父节点的值类型(称作t)和子节点的值类型(称作u)。

父节点类型为element[t]。

父节点值类型为t。

链函数类型为t =&gt; element[u]。这意味着该函数从t类型得出element[u]。

结果元素的类型为element[u]。

链值类型为u。

子节点的类型为element[u]。这是整个chain元素的类型。

在我们的例子中,goodmood是element[boolean],可以查询其值为true的概率:

val sunnytoday = flip(0.2)

val goodmood = chain(monthquality, sunnytoday,

(quality: string, sunny: boolean) =&gt;

println(variableelimination.probability(goodmood, true))

// prints 0.2896316752495942<code>`</code>

注意:

和apply不同,chain结构仅定义为一个或者两个参数。如果需要更多参数,可以结合chain和apply。首先使用apply将参数元素打包为单一元素,该元素的取值是参数值的元组。将这个元素传递给chain。这样chain就得到了元素求值中需要的所有信息。

使用apply和chain的map及flatmap

熟悉scala的读者请注意,在某种程度上,figaro元素类似于scala集合(如列表)。列表包含一组值,而元素包含一个随机值。正如可以对列表中的每个值应用函数以获得新列表那样,您可以对包含在元素中的随机值应用函数以得到一个新元素。这正是apply所做的!对于列表,对列表中的每个值应用某个函数是通过使用map方法实现的。类似地,使用apply就为元素定义了map操作。因此,可以将apply(flip(0.2), (b: boolean) =&gt; !b)写作flip(0.2).map(!_)。

同样,对于列表,可以对每个值应用某个函数返回列表,然后用flatmap将所有结果列表扁平化为单一列表。chain以同样的方式对元素中包含的随机值应用函数,获得另一个元素,然后取出元素中的值。所以,元素的flatmap用chain定义。因此,您可以将chain(uniform(0, 0.5), (d: double) =&gt; flip(d))写做uniform(0, 0.5).flatmap(flip(_))。(顺便说一句,要注意使用chain定义复合flip的方式。许多figaro复合元素可以用chain定义。)

scala中最棒的特性之一是任何定义了map和flatmap的类型都可以用于for循环。可以对元素使用for标记。

可以编写如下代码: