天天看點

《樹莓派開發實戰(第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标記。

可以編寫如下代碼: