本节书摘来异步社区《概率编程实战》一书中的第2章,第2.4节,作者:【美】Avi Pfeffer(艾维·费弗),更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.4 使用复合元素组合原子元素
在本节中,您将看到一些复合元素。前面已经说过,复合元素是构建于其他元素之上的更为复杂的元素。复合元素的例子很多,您将首先考察两个特殊的例子,If和Dist,然后了解如何使用大部分原子元素的复合版本。
2.4.1 If
您已经看到了If复合元素的一个例子。它由3个元素组成:一个测试,一个then子句和一个else子句。If表示这样的随机过程:首先检查测试的结果,如果测试值为true,则生成then子句的值;否则,生成else子句的值。图2-7展示了If元素的一个例子。If取3个参数,第一个参数是Element[Boolean],例中是sunnyToday元素。这个参数表示测试。第二个参数是then子句,当测试元素的值为true,则选中这个元素。如果测试元素值为false,则选择第三个参数——else子句。then和else子句必须有相同的值类型,这将成为If元素的值类型。
下面是If元素的执行情况:
val sunnyToday = Flip(0.2)
val greetingToday = If(sunnyToday,
Select(0.6 -> "Hello, world!", 0.4 -> "Howdy, universe!"),
Select(0.2 -> "Hello, world!", 0.8 -> "Oh no, not again"))
println(VariableElimination.probability(greetingToday, "Hello world!"))
// prints 0.27999999999999997```
上述代码打印输出0.28(在舍入误差范围内)是因为then子句被选中的概率为0.2(当sunnyToday为true),而这种情况下“Hello, world!”出现的概率为0.6。同时,else子句被选中的概率为0.8,此时“Hello, world!”出现的概率为0.2。所以,“Hello, world!”出现的总概率为(0.2 × 0.6) + (0.8 × 0.2) = 0.28。您可以通过明确评估两种情况看到这一结果:
sunnyToday.observe(true)
println(VariableElimination.probability(greetingToday, "Hello, world!"))
// prints 0.6 because the then clause is always taken
sunnyToday.observe(false)
println(VariableElimination.probability(greetingToday, "Hello, world!"))
// prints 0.2 because the else clause is always taken`
2.4.2 Dist
Dist是一个实用的复合元素,Dist与Select类似,但是它选择一组元素中的一个而不是选择一组值中的一个。每个选择本身是一个元素,这也就是Dist是复合元素的原因。如果您想要在本身是随机过程的复杂选择之间选择,Dist很实用。例如,足球中的角球可能从短传或者高吊传中开始。这可以由一个Dist元素表示,该元素在两个过程之间选择,一个从短传开始,另一个从高吊传中开始。
Dist在com.cra.figaro.language包中,下面是Dist元素的一个例子:
val goodMood = Dist(0.2 -> Flip(0.6), 0.8 -> Flip(0.2))
如图2-8所示,Dist元素的结构类似于图2-5中Select的结构。不同之处在于,选择的不是结果值而是结果元素。您可以这样认为:Select直接选择一组可能值中的一个,没有任何中间过程。Dist是间接的:它选择一组过程中的一个运行,在运行中产生一个值。在例子中,由Flip(0.6)表示的过程被选中的概率为0.2,由Flip(0.2)表示的过程被选中的概率为0.8。
下面是查询这一元素得到的结果:
println (VariableElimination.probability(goodMood, true))
// prints 0.28 = 0.2 * 0.6 + 0.8 * 0.2```
####2.4.3 原子元素的复合版本
您已经看到了采用数值参数的原子元素。例如,Flip以概率为参数,Normal以均值和方差为参数。如果您对这些数值参数不确定该怎么办?在Figaro中,答案很简单:将它们自身作为元素。
制作数值参数元素时,您就得到了原子元素的复合版本。例如,下面的代码定义一个复合Flip元素,并用它进行推理:
val sunnyTodayProbability = Uniform(0, 0.5)
val sunnyToday = Flip(sunnyTodayProbability)
println(Importance.probability(sunnyToday, true))
// prints something like 0.2548`
这里,sunnyTodayProbability表示对今天是晴天的概率的不确定性,您认为0~0.5的任何值可能性一样大。那么,sunnyToday为true的概率等于sunnyTodayProbability元素的值。一般来说,复合Flip取单一参数,即表示Flip为true概率的一个Element[Double]。
Normal提供了更多可能性。在概率推理中,假定正态分布有一个特定的已知方差,通过均值表示不确定性的情况很常见。例如,您可能假定温度的方差为100,而均值在40左右,但是对此不太确定。此时可以用如下代码捕捉这种不确定性:
val tempMean = Normal(40, 9)
val temperature = Normal(tempMean, 100)
println(Importance.probability(temperature, (d: Double) => d > 50))
// prints something like 0.164```
此外,您也可能对方差不确定,认为它可能是80或者105。此时可以使用如下代码:
val tempMean = Normal(40, 9)
val tempVariance = Select(0.5 -> 80.0, 0.5 -> 105.0)
val temperature = Normal(tempMean, tempVariance)
println(Importance.probability(temperature, (d: Double) => d > 50)
// prints something like 0.1549`