教師付き学習でパラメータの推定

2015-10-12

続・わかりやすい パターン認識

第5章 教師付き学習と教師なし学習

5.3 教師付き学習 (p.83)

例題5.2:「教師付き学習」でパラメータの推定を行う

  • 箱に種類のサイコロ()が大量に入っていて、それぞれの含有率()は不明
  • サイコロの種類によって各目の出易さが違う() (これも不明)
  • 箱からサイコロを取り出してそのサイコロの種類を記録し、投げて出た目を観測して元の箱に戻す、という操作を回繰り返す
    • サイコロを取り出した回数は
    • サイコロを投げて出た目がであった回数は
  • 観測結果から含有率()及び目の出易さ()を最尤推定で推定せよ

コード

# param_infer_supervised.rb
def main
# πi: それぞれのサイコロが箱の中にどのような割合で混ざっているか
pi = [0.1, 0.4, 0.5]

# θi: それぞれのサイコロを投げた時に表が出る確率
theta = [[0.8, 0.2],
[0.6, 0.4],
[0.3, 0.7]]

# 試行回数
n = 100
nss = make_trial(n, pi, theta)
p nss

# パラメータを最尤推定
ps, tss = infer_params_supervised(nss)

# 結果表示
ps.size.times do |i|
printf("%2d: %.2f: %s\n", i, ps[i], tss[i].map {|t| sprintf('%.2f', t)}.join(', '))
end
end

# 教師付き学習の最尤推定でパラメータの推定を行う
# _ns_ :: サイコロの各種類・各目が出た回数 n_ik
def infer_params_supervised(nss)
n = nss.flatten.sum # 総試行回数
nis = nss.map(&:sum) # 各サイコロの種類が取り出された回数

pi = nis.map do |ni| # 式(5.44): 最尤推定による各サイコロの含有率の推定値
ni.to_f / n # $\pi_i = \frac{n_i}{n}$
end

theta = nss.each_with_index.map do |ns, i| # 式(5.47): 最尤推定による各サイコロの出目の推定値
ns.map do |nik|
nik.to_f / nis[i] # $\theta_{ik} = \frac{n_{ik}}{n_i}$
end
end

return pi, theta
end

# 箱からサイコロを取り出して投げる、という試行をn回行い、
# その結果サイコロの各種類の各目が出た回数を二重配列で返す
def make_trial(n, pi, theta)
c = theta.size # サイコロの種類の数
m = theta[0].size # 出る目の種類の数
nss = Array.new(c) { Array.new(m) {0} }
n.times do
st = pick_dice(pi)
xt = roll_dice(theta[st])
nss[st][xt] += 1
end
return nss
end

# 箱からサイコロを無作為に取り出す
def pick_dice(pi)
random_choise(pi)
end

# サイコロを投げてどの目が出たかを返す
def roll_dice(theta)
random_choise(theta)
end

# 確率でランダムに選び、選んだインデクスを返す
def random_choise(probs)
r = rand
probs.each_with_index do |p, i|
return i if r < p
r -= p
end
return probs.size - 1 # 誤差対策
end

class Array
def sum
self.inject(:+)
end
end

main

# 結果:
#[[9, 3], [23, 14], [13, 38]]
# 0: 0.12: 0.75, 0.25
# 1: 0.37: 0.62, 0.38
# 2: 0.51: 0.25, 0.75

教師付き学習の最尤推定は、観測結果の頻度の割合を計算すればいいだけなので簡単