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

简介:

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

2.5 用Apply和Chain构建更复杂的模型

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

2.5.1 Apply

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

val sunnyDaysInMonth = Binomial(30, 0.2)
def getQuality(i: Int): String =
  if (i > 10) "good"; else if (i > 5) "average"; else "poor"
val monthQuality = Apply(sunnyDaysInMonth, getQuality)
println(VariableElimination.probability(monthQuality, "good"))
// prints 0.025616255335326698```
上述代码中的第2行和第3行定义一个名为getQuality的函数。这个函数取一个Integer型参数,getQuality函数中的参数局部名称为i。根据第3行中的代码,该函数返回一个字符串。

第4行定义一个Apply元素monthQuality。Apply元素的结构如图2-9所示,它取两个参数,第一个是元素,在本例中是Element[Int]类型的sunnyDaysInMonth。第二个参数是函数,参数类型与元素的值类型相同。在我们的例子中,函数getQuality取得一个Integer型参数,因此两者互相匹配。该函数可以返回任何类型的值,在我们的例子中,函数返回一个字符串。

<div style="text-align: center"><img src="https://yqfile.alicdn.com/f1e137d41c82a7bcc250cc1250ad1e380334cac4.png" width="" height="">
</div>

下面介绍Apply元素定义随机过程的方式。它首先生成第一个元素参数的值。在我们的例子中,生成当月中晴天的特定数量,我们假定生成的是7。然后,该过程取得第二个函数参数,并将其应用到生成的值。在我们的例子中,过程将函数getQuality应用到7,获得结果average。该结果成为Apply元素的值。从这里可以看出,Apply的可能值和函数参数的返回值类型相同。在我们的例子中,Apply元素是一个Element[String]。

如果您对Scala还很陌生,那么我来介绍一下匿名函数。如果只想在一个位置上使用,为每个Apply元素定义单独的函数可能令人烦恼,尤其是上例中的简短函数。Scala提供了匿名函数,可以直接在使用的位置定义。图2-10展示了一个匿名函数的结构,它的定义和getQuality相同。这一结构的组件类似于命名函数。函数有一个参数i,类型为Integer。=>符号表示定义的是匿名函数。最后是一个函数体,与getQuality相同。

<div style="text-align: center"><img src="https://yqfile.alicdn.com/753fae657cee68ae4303e0d04daccb9a50e2c784.png" width="" height="">
</div>

您可以使用匿名函数定义和前一个元素等价的Apply元素:

val monthQuality = Apply(sunnyDaysInMonth,
(i: Int) => if (i > 10) "good"; else if (i > 5) "average"; else "poor")`
现在,您可以查询monthQuality。不管使用哪一个版本的monthQuality,得到的答案都相同:

println(VariableElimination.probability(monthQuality, "good"))
// prints 0.025616255335326698```
虽然Apply的这个例子是人为的,但是使用它有许多实际的理由。下面是几个例子。

您有一个双精度元素,希望将其值舍入为最近的整数。
您有一个元素的值类型是一个数据结构,希望概括该数据结构的一个属性。您可能有一个以列表为基础的元素,希望知道列表项目数量大于10的概率。
两个元素之间的关系最好由一个物理学模型编码。在这种情况下,您可以使用一个Scala函数表示物理关系,使用Apply将物理模型嵌入Figaro。
多参数Apply

使用Apply时,并不限于只有一个参数的Scala函数。Figaro中定义的Apply可以使用最多5个参数。使用多于一个参数的Apply,是将多个元素结合在一起承载另一个元素的好办法。例如,下面是两个参数的Apply:

val teamWinsInMonth = Binomial(5, 0.4)
val monthQuality = Apply(sunnyDaysInMonth, teamWinsInMonth,

 (days: Int, wins: Int) => {
 val x = days * wins
 if (x > 20) "good"; else if (x > 10) "average"; else "poor"
 })```

这里,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:

println(VariableElimination.probability(monthQuality, "good"))
// prints 0.15100056576418375```
得出的概率值略微提升。似乎,我的垒球队有机会振奋人心。更重要的是,尽管这是一个简单的例子,但是例中的概率在没有Figaro的情况下也很难计算。

####2.5.2 Chain
顾名思义,Chain(链)用于将元素链接为一个模型,模型中的元素依赖于另一个元素,那个元素又依赖其他的元素,依次类推。这与概率的链式法则相关,第5章将介绍这一法则。但是,理解Chain并不需要知道链式法则。

Chain也包含在com.cra.figaro.language包中。解释Chain的最简单方式是通过一张图。图2-11展示了两个元素:goodMood元素依赖monthQuality元素。如果您将其看作一个随机过程,该过程首先生成monthQuality的值,然后使用该值生成goodMood的值。这是贝叶斯网络的一个简单例子,第4章中您将学习这种方法。图中借用了贝叶斯网络的术语:monthQuality称为父节点,goodMood称为子节点。

<div style="text-align: center"><img src="https://yqfile.alicdn.com/48523d3d1e5f43d1bcadd254164fe34341e5c050.png" width="" height="">
</div>

因为goodMood依赖于monthQuality,goodMood使用Chain定义。monthQuality元素在前一小节已经定义。下面是goodMood的定义:

val goodMood = Chain(monthQuality, (s: String) =>

 if (s == "good") Flip(0.9)
 else if (s == "average") Flip(0.6)
 else Flip(0.1))```

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

73e42be61d2f9dc3079d95ada9c8fedbe5599e2e

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

676078147e46d9a666828bb9ee7852e1a928d2c6

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

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

父节点类型为Element[T]。
父节点值类型为T。
链函数类型为T => Element[U]。这意味着该函数从T类型得出Element[U]。
结果元素的类型为Element[U]。
链值类型为U。
子节点的类型为Element[U]。这是整个Chain元素的类型。
在我们的例子中,goodMood是Element[Boolean],可以查询其值为true的概率:

println(VariableElimination.probability(goodMood, true))
// prints 0.3939286578054374```
多参数Chain

让我们来考虑一个稍微复杂些的模型,其中goodMood依赖monthQuality 和 sunnyToday,如图2-14所示。

<div style="text-align: center"><img src="https://yqfile.alicdn.com/5dc6df2f9b859e0adc934163f1f846e68d3902c6.png" width="" height="">
</div>

可以使用一个双参数的Chain捕捉上述事实。在本例中,链中的函数有两个参数——名为quality的字符串参数和名为sunny的布尔型参数,返回一个Element[Boolean]。goodMood同样是Element[Boolean]。

下面是代码:

val sunnyToday = Flip(0.2)
val goodMood = Chain(monthQuality, sunnyToday,
(quality: String, sunny: Boolean) =>

if (sunny) {
  if (quality == "good") Flip(0.9)
  else if (quality == "average") Flip(0.7)
  else Flip(0.4)
} else {
  if (quality == "good") Flip(0.6)
  else if (quality == "average") Flip(0.3)
  else Flip(0.05)
})

println(VariableElimination.probability(goodMood, true))
// prints 0.2896316752495942`
注意:

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

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

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

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

可以编写如下代码:

for { winProb <- Uniform(0, 0.5); win <- Flip(winProb) } yield !win```
相关文章
|
机器学习/深度学习 算法 算法框架/工具
Tensorflow源码解析1 -- 内核架构和源码结构
# 1 主流深度学习框架对比 当今的软件开发基本都是分层化和模块化的,应用层开发会基于框架层。比如开发Linux Driver会基于Linux kernel,开发Android app会基于Android Framework。深度学习也不例外,框架层为上层模型开发提供了强大的多语言接口、稳定的运行时、高效的算子,以及完备的通信层和设备层管理层。因此,各大公司早早的就开始了深度学习框架的研
6054 0
|
2月前
|
设计模式 测试技术 Go
Go 项目必备:Wire 依赖注入工具的深度解析与实战应用
在现代软件开发中,依赖注入(Dependency Injection,简称 DI)已经成为一种广泛采用的设计模式。它的核心思想是通过外部定义的方式,将组件之间的依赖关系解耦,从而提高代码的可维护性、可扩展性和可测试性。然而,随着项目规模的增长,手动管理复杂的依赖关系变得日益困难。这时,依赖注入代码生成工具就显得尤为重要。在众多工具中,Wire 以其简洁、强大和易用性脱颖而出,成为 Go 语言项目中的宠儿。本文将带你深入了解 Wire 的安装、基本使用、核心概念以及高级用法,并通过一个实际的 web 博客项目示例,展示如何利用 Wire 简化依赖注入的实现。准备好了吗?让我们开始这场代码解耦的奇
|
监控 Cloud Native API
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析(六)
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析
|
存储 缓存 Kubernetes
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析(四)
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析
|
设计模式 缓存 算法
【Java并发编程实战14】构建自定义同步工具(Building-Custom-Synchronizers)(下)
JDK包含许多存在状态依赖的类,例如FutureTask、Semaphore和BlockingQueue,他们的一些操作都有前提条件,例如非空、任务已完成等。
143 0
|
缓存 Java API
【Java并发编程实战14】构建自定义同步工具(Building-Custom-Synchronizers)(中)
JDK包含许多存在状态依赖的类,例如FutureTask、Semaphore和BlockingQueue,他们的一些操作都有前提条件,例如非空、任务已完成等。
81 0
|
缓存 安全 Java
【Java并发编程实战14】构建自定义同步工具(Building-Custom-Synchronizers)(上)
JDK包含许多存在状态依赖的类,例如FutureTask、Semaphore和BlockingQueue,他们的一些操作都有前提条件,例如非空、任务已完成等。
106 0
|
Kubernetes Cloud Native API
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析(十)
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析(十)
|
JSON Kubernetes Cloud Native
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析(八)
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析
|
缓存 Kubernetes Cloud Native
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析(九)
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析
带你读《云原生应用开发 Operator原理与实践》第三章 Kubebuilder 原理3.3 Controller-runtime 模块分析(九)