Upload
ryosuke-takashima
View
1.639
Download
4
Embed Size (px)
Citation preview
Embedded Probabilistic Programming
~埋め込み DSL による確率プログラミング~
2010/6/30
濡れた芝生
雨が降る確率: 30%
スプリンクラーが動く確率: 50%
雨が降っても濡れない確率: 10%
スプリンクラーが動いても濡れない確率: 20%
何か別の理由で濡れる確率: 10%
芝生が濡れているときに、雨が降った確率は?Pr (rain | grass_is_wet)
ふつうにプログラミング
OCaml でふつうに書くとこんな感じ。モンテカルロ法で解くと 47% くらい。
let flip p = dist [(p, true); (1.-.p, false)]let grassModel = let rain = flip 0.3 and sprinkler = flip 0.5 in let grass_is_wet = flip 0.9 && rain || flip 0.8 && sprinkler
|| flip 0.1 in if grass_is_wet then rain else fail()
確率分布 DSL
こんな DSL を用意する。( Haskell )
type Prob = Floatdata PM adist :: [(Prob, a)] -> PM acon :: PM Bool -> PM Bool -> PM Booldis :: PM Bool -> PM Bool -> PM Boolif_ :: PM Bool -> PM a -> PM a -> PM alet_ :: PM a -> (PM a -> PM b) -> PM b
DSL で記述した確率モデル
芝生の例は DSL でこんな風に書ける。
grassModel = let_ (flip_ 0.3) (\ rain -> let_ (flip_ 0.5) (\ sprinkler -> let_ (dis (con (flip_ 0.9) rain) (dis (con (flip_ 0.8) sprinkler) (flip_ 0.1))) (\ grassIsWet -> if_ grassIsWet rain (dist []))))
確率を木構造で表現
T
T
T X
T
T X T X
F
F X
F
F X F X F X F X F X
0.1 0.9
0.20.8
0.10.9
0.1 0.9 0.1 0.9
0.8 0.2
0.9 0.1
0.5 0.5
0.1 0.9
0.20.8
0.1 0.9
0.20.8
0.9 0.1
0.1 0.9 0.1 0.9
0.8 0.2
0.9
0.1 0.9 0.1 0.9
0.8 0.2
0.1
0.5 0.5
0.3 0.7
1.0
木探索で DSL を実装(1)
data VC a = V a | C (PV a)type PV a = [(Prob, VC a)]type PM a = PV a
pvUnit :: a -> PV apvUnit x = [(1.0, V x)]
pvBind :: PV a -> (a -> PV b) -> PV bpvBind m f = map g m where g (p, V x) = (p, C (f x)) g (p, C t) = (p, C (pvBind (t f)))
木探索で DSL を実装(2)
dist ch = map (\(p,v) -> (p, V v)) ch
con e1 e2 = pvBind e1 (\v1 -> if v1 then e2 else pvUnit False)
dis e1 e2 = pvBind e1 (\v1 -> if v1 then pvUnit True else e2)
if_ et e1 e2 = pvBind et (\t -> if t then e1 else e2)
木の構造から分かること
最低でも 5 段の探索は必要。
10 段以上かかるのは非効率な部分があるから。( pvBind とか)
より効率的な表現ができないか?
継続渡し形式( Continuation Passing Style / CPS )
継続≒後でやってほしいこと
継続を与えられた関数は、新しい継続を作って下位の関数に渡す。
継続を与えられた値は、その継続を自分自身に適用して返す。
浅井せんせ~!!!
CPS で DSL を実装
data VC a = V a | C (PV a)type PV a = [(Prob, VC a)]type PM a = (a -> PV Bool) -> PV Bool
dist ch k = map (\(p,v) -> (p, C (k v))) ch
con e1 e2 k = e1 (\v1 -> if v1 then e2 k else k False)
dis e1 e2 k = e1 (\v1 -> if v1 then k True else e2 k)
if_ et e1 e2 k = et (\t -> if t then e1 k else e2 k)
限定継続( Delimited Continuation )
shift と reset です
僕が説明するよりは・・・
浅井せんせ~!!!
限定継続の扱いの違い
OCamllet rec times lst = match lst with [] -> 1 | 0 :: rest -> shift (fun cont -> 0) | first :: rest -> first * times rest
Haskelltimes lst = case lst of [] -> return 1 0 : rest -> shift (\ cont -> 0) first : rest -> (first *) `liftM` times rest
← 戻り値は数値
← 戻り値は継続
shift / reset で実装( OCaml )
let dist ch = shift (fun k ->List.map (fun (p,v) -> (p, C (fun () -> k v))) ch)
let neg e = not e
let con e1 e2 = e1 && e2
let dis e1 e2 = e1 || e2
let if_ et e1 e2 = if et then e1 () else e2 ()
let reify0 m = reset (fun () -> pv_unit (m ()))
shift / reset で実装( Haskell )
dist ch = shift (\k ->map (\ (p,v) -> (p, C (k v))) ch)
neg = liftM not
con = liftM2 (&&)
dis = liftM2 (||)
if_ et e1 e2 = et >>= (\t -> if t then e1 else e2)
reify0 m = reset (pvUnit `liftM` m)
いろいろ余計なものが付いてる
Haskell は残念な子なのか
Haskell では
限定継続を使っても
生のデータを扱うことができない
モナドとは
モナドはクラスの一種
Haskell のクラスは Java や C# のインターフェイス
特定のメソッドを実装すれば、そのクラスのインスタンスになれる
インターフェイスの主な意義は標準化
たいていモナドを使わなくても書ける(ただし IO を除く)
デザパタ厨は、色々なモナドを覚えましょう
モナド専用の記法( do )が用意されている
モナドの力
do 記法を使うと、擬似的に生のデータを扱える
grassModel = do rain <- flip_ 0.3 sprinkler <- flip_ 0.5 wetByRain <- flip_ 0.9 wetBySprinkler<- flip_ 0.8 wetByOther <- flip_ 0.1 let grassIsWet = wetByRain && rain || wetBySprinkler && sprinkler || wetByOther if grassIsWet then return rain else dist []
メモして高速化
コインを n 回投げて、排他的論理和( XOR )をとる。
何も考えないと、高さ n の二分木の探索→ O(2^n)
同じ構造が何度も出てくるので、先に計算して結果を再利用した方が速い。→ O(n)
メモして高速化
F T F T F T F T FT F T F T F T
T F T FT F T F
T F T F
T F
T F
T
1回目
2回目
3回目
4回目
5回目
N 回までの計算結果を再利用できる
重み付き選択で高速化
酔っ払いがコインを投げる。( 9 割方は失くす)10 回投げて、すべて表が出る確率は?
正確な値を求めるのが困難なのでサンプリング。→いつまで経っても成功しない。
不都合な値は選ばずに重みを調整する。
遅延評価で高速化
コインを投げてぜんぶ表が出る確率
事象の発生と観測のタイミングがずれると、非効率になることがある。
ぎりぎりまで観測しなければいい!
遅延評価で高速化
ぎりぎりまで評価(観測)せず、分からないままにしておく
通常版 遅延評価版
まとめ
シームレスな DSL を用意するなら OCaml で。副作用万歳!
参照透明な Haskell で限定継続は扱いにくい。でも Haskell には do がある。モナド!
高速化めんどい。
参考
Embedded Probabilistic Programminghttp://okmij.org/ftp/kakuritu/dsl-paper.pdf
継続を使った Printf の型付けhttp://pllab.is.ocha.ac.jp/~asai/papers/contfest08slide.pdf