ギブスサンプリングの実装

長いことわかっていなかったのだが、実装してようやく頭に入って来た気がする。

今回は「道具としてのベイズ統計」涌井良幸 日本実業社
の説明にあるギブスサンプリングをPythonで実装してみた。
パラメータは全部、本にある通り。

#! /usr/bin/python                                                                                                                                                           
# -*- coding:utf-8 -*-                                                                                                                                                       
"""                                                                                                                                                                          
Gibbs samplingの実装。「道具としてのベイズ統計」のパラメータをそのまま流用。                                                                                     
"""
import sys, math, random
import numpy as np
import pylab as pl
import scipy.stats as stats

number_of_data=30
Q=138.22
average=5.11

n_0=0.02
S_0=1.0
mu_0=5.0
m_0=0.25

mu_1=(number_of_data*average+(m_0*mu_0))/(m_0+number_of_data)
m_1=30.25
n_1=float(number_of_data)
n_1_times_S_1=138.24

def sample_mu_from_normal_distri_of_after(sigma):
    mu_in_mu_distribution_after=mu_1
    sigma_in_mu_distribution_after=(sigma)/m_1
    mu_now=stats.norm.rvs(mu_in_mu_distribution_after, sigma_in_mu_distribution_after)
    return mu_now
def sample_sigma_from_inverse_gamma_distri_of_after(mu_now):
    mu_in_sigma_distribution_after=(n_1+1.0)/2.0
    sigma_in_sigma_distribution_after=((n_1_times_S_1)+m_1*((mu_now-mu_1)**2))/2.0
    #ちょっとこの関数には妙な感じがするので、別の方法で                                                                                                                      
    #sigma_now=stats.invgamma.rvs(sigma_in_sigma_distribution_after, loc=mu_in_sigma_distribution_after)                                                                     
    #sigma_now=math.sqrt(sigma_now)                                                                                                                                          
    sigma_now=(1.0/random.gammavariate(mu_in_sigma_distribution_after, 1.0/sigma_in_sigma_distribution_after))
    return sigma_now
def main(ITER_NUM):
    sigma_now=4.0
    mu_list=[]
    sigma_list=[]
    for i in range(0, ITER_NUM):
        mu_now=sample_mu_from_normal_distri_of_after(sigma_now)
        sigma_now=sample_sigma_from_inverse_gamma_distri_of_after(mu_now)
        mu_list.append(mu_now)
        sigma_list.append(sigma_now)

    del mu_list[0:999]
    del sigma_list[0:999]
    mu_summation=0
    for x in mu_list:mu_summation+=x
    average_mu=float(mu_summation)/len(mu_list)
    sigma_summation=0
    for x in sigma_list:sigma_summation+=x
    average_sigma=sigma_summation/len(sigma_list)
    print average_mu, average_sigma
if __name__=='__main__':
    main(ITER_NUM=3000)

実行すると、尤度関数の
平均5.10653864685
分散4.76013929248

となり、まあ、しっかりできてますね。というのが確認できた。
ところで、気になるのは、逆ガンマ分布のサンプリング(つまり、乱数発生のさせ方)。
引用書の中ではExcelには逆ガンマ分布の乱数発生コマンドがないので、ガンマ分布の引数にラムダ(中心)の逆数を与え、乱数値の逆数をとるように。と指示されている。

けど、PythonにはどうやらScipyに逆ガンマ分布のコマンドが用意されているようで、
http://gaezipserver.appspot.com/python/sci/scipy/generated/scipy.stats.invgamma.html
しかも、乱数発生のコマンドもある。
けど、これを使うと、どうしても値が合わなくなってしまうのだ。
なので、せっかくなのだが、使わずに引用書の指示通りにした。
(該当箇所はコメントアウトしてある)

これも全部、英語が読めないのが悪いんだ....


類似して、Javaでも実装されている方もいらっしゃるので
http://d.hatena.ne.jp/yokoken00/20100128