パラメータの事後分布計算を実装した
PRML3.3.1のパラメータの分布を読んで、パラメータの事後分布計算を実装した。
事前分布、事後分布、尤度関数の関係
観測データが得られた時に、そのデータがパラメータに依存していたとする。
この時、観測データがあるパラメータの条件下で得られる確率はである。
はの値によって変わる関数なので、尤度関数と呼ばれる。
また、パラメータの確率分布が事前分布であり、
観測データが得られた後でわかるパラメータの確からしさは事後分布である。
これら事前分布、事後分布、尤度関数は、ベイズの定理より以下の関係にある。(PRML 1.43式参照)
つまり、事後分布 尤度関数 事前分布 である。
尤度関数
観測データのそれぞれの値が独立同分布である時、観測データの得られる確率はは各データが得られる確率の積となる。
例えば、独立同分布で得られる値の同時確率はである。
同様に、パラメータの条件下で観測データの得られる確率は、
それぞれの観測値が得られる確率の積となるので以下のように表現できる。
特に、PRML3.1で導入した線形基底関数モデルを採用した場合に、入力と対応する目標値が得られた場合の尤度関数は、
なお、入力変数のモデル化はしないので、以降は省略する。
パラメータの分布
式(3.10)の尤度関数の指数部分がの2次形式であるため、
この尤度関数に対応する共役事前分布が以下のようなガウス分布で与えられる事に注目する。
事後分布は尤度関数と事前分布の積に比例するので、以下のように書ける。
この式の指数部分を平方完成して、観測データ取得後の事後分布も指数部分を展開して平方完成して比較すると、
以下のような期待値と共分散が得られる。
ここで、を事前分布とした場合に、
目標値と入力値が与えられた場合の、事後分布を計算した結果を示す。
目標値は、で計算したtに-1から1の範囲で得られるランダムな値を加えて作成した。
以下のグラフは、横軸に、縦軸に[w_1]を取り、色で座標上の確率を表したものである。
の座標で確立が集中的に高くなっていることがわかる。
この計算を行うプログラムを以下に示す。
import System.Random import Numeric.LinearAlgebra import Graphics.Gnuplot.Simple import Control.Applicative -- ノイズを付与する addNoise :: (RandomGen g, Random a, Num a) => (a, a) -> [a] -> g -> [a] addNoise range xs g = zipWith (+) xs (randomRs range g) -- 座標を文字列にする from2dToString :: (Show a) => [a] -> [a] -> String from2dToString xs ys = concat $ zipWith (\x y -> show x ++ " " ++ show y ++ "\n") xs ys -- 座標を文字列にする from3dToString :: (Show a1, Show a2, Show a3) => [(a1, a2)] -> [a3] -> String from3dToString xs ts = concat $ zipWith (\(x,y) z -> (show x) ++ " " ++ (show y) ++ " " ++ (show z) ++ "\n") xs ts -- 多変量ガウス分布 -- mu : D次元平均ベクトル 実装上はD×1の行列 -- sigma : D×Dの共分散行列 -- x : D次元ベクトルx 実装上はD×1の行列 -- gaussDist :: Matrix a -> Matrix a -> Matrix a -> a gaussDist mu sigma x = c * exp a where d = fromIntegral $ cols x c = 1.0 / (((2 * pi) ** (d / 0.5)) * ((det sigma) ** 0.5)) y = x - mu a = -0.5 * mahalanobisDistance mu sigma x mahalanobisDistance avrg covariance x = ((trans $ x - avrg) `multiply` (inv covariance) `multiply` y) @@> (0,0) where y = x - avrg -- 多項式基底関数 polynomialBasis :: Floating a => a -> a -> a polynomialBasis n x = x ** n -- 基底関数のリストと入力変数xからベクトル?(x)を計算する。 -- 実装上は M×1の行列 basisFuncVector basiss x = (m >< 1) $ [f x | f <- basiss] where m = length basiss -- 基底関数の配列と入力変数xから計画行列Φを計算する designMatrix basiss xs = (n >< m) [f x | x <- xs, f <- basiss] where n = length xs m = length basiss -- 事後分布 postDist alpha beta xs ts = gaussDist m s where phi = designMatrix (map polynomialBasis [0,1]) xs phiTphi = (trans phi) `multiply` phi n = (cols phiTphi) alphaI = diag . fromList $ take n $ repeat alpha betaI = diag . fromList $ take n $ repeat beta invS = alphaI + betaI `multiply` phiTphi s = inv invS m = betaI `multiply` s `multiply` (trans phi) `multiply` ((length ts >< 1) ts) -- 事後分布を受け取り、関数内で生成した各座標での確率を計算する。 -- 座標と確率を文字列にして返す。 calcDist dist = str where xy = [(x,y) :: (Double, Double) | x <- linearScale 100 (-5, 5) , y <- linearScale 100 (-5, 5)] xy' = map ((2 >< 1) . (\(x,y) -> [x,y])) xy z = map dist xy' str = from3dToString xy z -- 事後分布を計算してファイルに出力する。 -- xの個数が少ないと1を超えた確率が出る場合がある runPostDistCalc = do gen <- getStdGen let xs = linearScale 1000 (0, 10) f = \x -> 3.14 * x + 1.23 ts = addNoise (-1.0 ::Double, 1.0::Double) (map f xs) gen alpha = 1.0 beta = 1.0 g = postDist alpha beta xs ts str = calcDist g writeFile "gaussDist.dat" str return ()
目標値tと入力値xを一つずつ入力して事後分布を更新していく計算方法を考える事も出来る。
以下のアニメーションでは、更新された事後分布が徐々にに収束していく事がわかる。
このアニメーションの確率分布は以下のプログラムで計算した。
import System.Random import Numeric.LinearAlgebra import Graphics.Gnuplot.Simple import Control.Applicative import Control.Monad -- ガウス分布の共分散と期待値を保持するデータ構造 data GaussDist = GaussDist { mu :: Matrix Double , sigma :: Matrix Double } deriving (Show, Eq) -- パラメータWとその確率を保持するためのデータ構造 data Pos = Pos Double Double Double deriving Eq instance Show Pos where show (Pos x y z) = show x ++ " " ++ show y ++ " " ++ show z ++ "\n" -- 多変量ガウス分布 -- mu : D次元平均ベクトル 実装上はD×1の行列 -- sigma : D×Dの共分散行列 -- x : D次元ベクトルx 実装上はD×1の行列 -- gaussDist :: Matrix a -> Matrix a -> Matrix a -> a gaussDist mu sigma x = c * exp a where d = fromIntegral $ cols x c = 1.0 / (((2 * pi) ** (d / 0.5)) * ((det sigma) ** 0.5)) y = x - mu a = -0.5 * mahalanobisDistance mu sigma x mahalanobisDistance avrg covariance x = ((trans $ x - avrg) `multiply` (inv covariance) `multiply` y) @@> (0,0) where y = x - avrg -- 座標wの確率を計算する -- wはパラメータベクトル 実装上はD×1の行列 probability :: GaussDist -> Matrix Double -> Double probability dist w = gaussDist m s w where m = mu dist s = sigma dist -- 多項式基底関数 polynomialBasis :: Floating a => a -> a -> a polynomialBasis n x = x ** n -- 確率分布を更新する。演習3.8により共分散と期待値を更新する -- 第一引数 係数α -- 第二引数 係数β -- 第三引数 基底関数にx_n+1を適用した?_n+1縦ベクトル(実装上はM行1列の行列) -- 第四引数 新しい目標値t_n+1 -- 第五引数 古いガウス分布 update :: Double -> Double -> Matrix Double -> Double -> GaussDist -> GaussDist update alpha beta phi t dist = dist' where m = mu dist s = sigma dist invS' = (inv s) + ((1><1) [beta])*(phi `multiply` (trans phi)) lhs = (inv s) `multiply` m rhs = ((1><1) [beta * t]) * phi m' = (inv invS') `multiply` (lhs + rhs) dist' = GaussDist m' (inv invS') -- ノイズを付与する addNoise :: (RandomGen g, Random a, Num a) => (a, a) -> [a] -> g -> [a] addNoise range xs g = zipWith (+) xs (randomRs range g) -- 訓練データを作成する -- 関数t(x) = 3.14 x + 1.23から 0 <= x < 5の範囲で生成した値に -- -1.0 から 1.0の範囲の値をランダムに加えた(x,t)のリストを返す。 makeTrainingData :: IO [(Double, Double)] makeTrainingData = do gen <- getStdGen let xs = linearScale 1000 (0, 5) f = \x -> 3.14 * x + 1.23 ts = addNoise (-1.0 ::Double, 1.0::Double) (map f xs) gen xts = zip xs ts return xts -- 事後分布の逐次更新計算を行う -- 入力された目標値のリストから目標値を一つずつ取り出して事後分布を更新していく。 sequentialUpdateCalc :: Double -> Double -> [(Double -> Double)] -> GaussDist -> [(Double, Double)] -> [[Pos]] sequentialUpdateCalc alpha beta basiss priorDist xts = seqCalc priorDist xts where update' = update alpha beta -- -5 < w0 < 5 , -5 < w1 <5の範囲にある確率P(w1,w2)を全て計算する w = [(2 >< 1) [w0, w1] :: Matrix Double | w0 <- linearScale 100 (-5, 5) , w1 <- linearScale 100 (-5, 5)] -- 戻り値で座標wを返す際に行列のままだと表示しづらいので、リストに変換する wl = map (concat . toLists) w -- 実際の逐次計算は以下の関数で行う。 -- (x,t)を一つずつ seqCalc _ [] = [] seqCalc priorDist ((x,t):xts) = poss:(seqCalc postDist xts) where -- 基底関数にxを適用したベクトル?nを計算する phi = (2 >< 1) [f x | f <- basiss] -- 全ての(w0,w1)に対して、その座標での確率を計算する ps = map (probability priorDist) w -- w0,w1,P(w0,w1)からなるリストを作成する poss = zipWith (\ (w0:w1:[]) p -> Pos w0 w1 p) wl ps -- 事前分布から事後分布を計算する(演習3.8) postDist = update' phi t priorDist -- 文字列のリストの各要素をファイルに出力する。 writeSequential name strs = writeSeq 0 strs where writeSeq _ [] = return () writeSeq n (str:strs) = do let fileName = name ++ show n ++ ".dat" writeFile fileName str writeSeq (n + 1) strs -- 逐次更新処理を実行する runSequentialUpdate = do -- 訓練データを作成する xts <- makeTrainingData let basiss = map polynomialBasis ([0,1] :: [Double]) -- 事前分布 P(w) = N(w|m0,s0) m0 = (2 >< 1) [0,0] s0 = diag . fromList $ [1,1] priorDist = GaussDist m0 s0 -- 更新されていく確率分布を計算する posss = sequentialUpdateCalc 1 1 basiss priorDist xts -- 計算した確率分布を文字列にする strs = map (concat . (map show)) posss writeSequential "update" strs return ()