2
1

More than 1 year has passed since last update.

Bayes theoremとHMMとViterbi Algorithm 備忘録

Posted at

概要

よく参考にさせていただいている元Google brainのYouTuberの動画がすごくわかりやすかったので、Wikipediaの例に置き換えて書き残す。

前半はHMM(Hidden Markov Model)をBayes theorem視点から理解し、後半はその行動(observation)が連なったときに使用するcomputational costの低いViterbi algorithmについて理解する。
また最後にpomegranateの使い方も少し残す。

ここで扱うWikipediaの例とは
- Hidden state: rainy, sunny
- Observation: walk, shop, clean
のよくあるHMMである。

またパケージは旧来のHMMlearnではなく、より高機能なpomegranateを使用する。

  • 実施期間: 2021年12月
  • 環境:Ubuntu20.04LTS
  • パケージ:pomegranate

Hidden markov model (HMM)

天気(state)と行動(observation)の遷移について過去統計から確率で図示するところまで説明する。

前提として全stateの遷移は統計的にわかっているものとし、また各stateからemitするobservationも統計的にわかっているものとする。
もちろんここらがわからないといったケースも十分にあり、その場合はforward-backward algorithm(Baum-Welch algorithm)を使用すれば解決可能でありpomegranateではこちらがデフォルトになっている。

過去の統計から天気が下図のように変化することがわかっている。できるだけWikipediaの例になるようにしたので長くなってしまった。
Screenshot from 2021-12-31 11-23-14.png
変化は4パターンしかなく、勘定すると下記は容易にわかる。
Screenshot from 2021-12-31 11-24-47.png

天気、つまりstate(後々hidden state)のtransition probabilityは下図となる。
Screenshot from 2021-12-31 11-27-34.png

stateの初期状態をprior probability(事前確率)と呼び、次の3式で求めることができる。これで開始stateを確率で表現できる。
Screenshot from 2021-12-31 12-12-05.png

ここからはobservationの発生確率(emission probability)を考える。
stateがきっかけとなるobservationも統計的にわかっている。ここでは、walk, shop, cleanの3行動とする。
Screenshot from 2021-12-31 12-13-37.png
emission probabilityも上図を勘定するだけ。
Screenshot from 2021-12-31 12-31-47.png

総合的にまとめるとモデルは下図となる。
Screenshot from 2021-12-31 12-34-56.png

Bayes theorem視点でHidden stateの推論

上記までで天気->行動を図示したが、ここからは行動変化->天気変化をBayes theoremで推論する。
HMMの本体であり行動が実際にどのように推移したらそのとき天気がどのように推移していたらしいか推論する。stateは不明なのでhidden stateと呼ぶ。
この「推移していたらしい」をposterior probability(事後確率)として求める。

はじめに、observationが変化しないスポットでのhidden stateを推論してみる。
priorはすでにわかっており雨:晴れ=0.6:0.4なので雨3日、晴れ2日の計5日間の行動はモデルより下図となる。
Screenshot from 2021-12-31 12-57-03.png

上図より、observationがworkのときのposterior probabilityは
Screenshot from 2021-12-31 13-03-59.png

同様にshopとcleanは下記となる。
Screenshot from 2021-12-31 13-04-10.png

次に2連続変化するobservationから両日のhidden stateを推論してみる。
Screenshot from 2021-12-31 13-14-09.png

prior, transition, emissionの各確率を掛けるだけ、つまり下式の積算により最も確率の高いhidden state変化がどれなのかわかる。
Screenshot from 2021-12-31 13-20-04.png
月曜火曜の天気の変化は4パターンあるのでモデルは全体で下図の4モデルである。
Screenshot from 2021-12-31 13-15-40.png

つまりもっともらしいhidden stateの変化は雨->雨であったことがわかる。
2番目、3番目に大きかった確率を気にする必要がなければこのあと説明するViterbi algorithmでhidden stateの遷移は推論することができる。

Viterbi algorithmで推論

hidden state数やobservationの変化数が増えると上述の計算方法では爆発してしまう。2xLxn^L(L:Observation長, n:Hidden state数)
そげなときは最大確率のみに注目するViterbi algorithmをつかうっちゃ。
ここでは4日間のobservationからhidden stateを推論してみる。
Screenshot from 2021-12-31 13-32-08.png

手順は図示すると簡単で、それぞれの日における各hidden stateのposterior probabilityを計算し、最後にそれら4つの最大値を選ぶだけ。

1,2日目の変化は下図となる。
Screenshot from 2022-01-03 10-23-42.png

こんな感じに各日、各hidden stateの最大値を書き残していく。
Screenshot from 2021-12-31 13-49-56.png

Screenshot from 2021-12-31 13-50-20.png

最終的に最大確率(青字)を結べばhidden stateの推論はおわり。
Screenshot from 2021-12-31 13-52-44.png

pomegranateで確認

Viterbi algorithm

pomegranateのチュートリアルでは上記で使用したWikipediaの例が使われている。

HMMのモデルを作成する。

from pomegranate import *
import random
import math

random.seed(0)

model = HiddenMarkovModel(name="Rainy-Sunny")

rainyとsunnyのstateを作成する。このときそれぞれのstateからemitされるobservationもそのemission prob.と一緒に辞書型で定義する。

rainy = State(DiscreteDistribution({'walk': 0.1, 'shop': 0.4, 'clean': 0.5}), name='Rainy')
sunny = State(DiscreteDistribution({'walk': 0.6, 'shop': 0.3, 'clean': 0.1}), name='Sunny')

はじめのstateをprior prob.(事前確率)を使ってモデルに定義する。

model.add_transition(model.start, rainy, 0.6)
model.add_transition(model.start, sunny, 0.4)

transition prob.をモデルに定義する。

model.add_transition(rainy, rainy, 0.7)
model.add_transition(rainy, sunny, 0.3)
model.add_transition(sunny, rainy, 0.4)
model.add_transition(sunny, sunny, 0.6)

bake()というコンパイルのようなファイナライズ処理を行いモデルを固める。

model.bake(verbose=True)

# 可読性のため戻り値のmatrixのindexを定義する。
rainy_idx = model.states.index(rainy)   # bake後でないとindex()を認識しないから
sunny_idx = model.states.index(sunny)

推論させたい任意のシーケンスを設定する。ここでは手計算した前出のものを使用する。

sequence = [ 'shop', 'clean', 'walk', 'walk']

Viterbi algorithmで推論させる。

print(", ".join(state.name for i, state in model.viterbi(sequence)[1]))

Rainy-Sunny-start, Rainy, Rainy, Sunny, Sunny

手計算通りの結果が得られる。ここではstate.nameで抽出、joinしているが、viterbi()[1]の戻り値は下記であった。

print(model.viterbi(sequence)[1])

Screenshot from 2022-01-01 12-59-14.png

同時確率

同時確率(全確率和)による推定であればシーケンス途中の詳細を知ることができる。例えばforward algorithmの場合は下図のイメージに変わる。
Screenshot from 2022-01-03 10-30-22.png

naiveに全確率を計算させると爆発するのでforward, backward, forward-backward algorithmといったdynamic algorithmを使用する。詳細はWikipediaにコード付きで解説あり参照のこと。

Algorithm Official Docの説明
forward シーケンスを順方向に進み、各observationが各stateに整列する確率を計算する。全パスの対数確率の合計が出力される。アンダーフローのエラーを防ぐために各行を動的にスケールするため、正規化している。
backward シーケンスを逆方向に進み、各observationが各stateに整列する確率を計算する。全パスの対数確率の合計が出力される。
forward-backward emission matrixとtransition matrixを返す。emission matrixは全シーケンスが与えられたとき、各stateがそれらを生成する正規化された確率を返し、transition matrixはあるtransitionが使用される回数の期待値を返す。

実際のチュートリアルではモデルのend stateも定義している。end stateがなければ永遠にHMMをぐるぐる回り続けることになり現実的ではないからだろう。雨で終わるtransition prob.と晴れで終わるそれを各0.1と定義する。そのため上で求めた4つのtransition prob.からそれぞれ0.05差し引いている。
Screenshot from 2022-01-03 14-55-51.png

model.add_transition( rainy, rainy, 0.65 )
model.add_transition( rainy, sunny, 0.25 )
model.add_transition( sunny, rainy, 0.35 )
model.add_transition( sunny, sunny, 0.55 )
model.add_transition( rainy, model.end, 0.1 )
model.add_transition( sunny, model.end, 0.1 )

シーケンスが与えられたときの任意のステップが指定するhidden stateである確率は下記で得られる。

print(math.e**model.forward(sequence))

Screenshot from 2022-01-03 11-22-27.png
尚、end stateを設定しなければforwardの戻りは下記のようになり手計算と一致する。
Screenshot from 2022-01-01 14-27-18.png
行がシーケンスのステップ、列が各hidden stateの確率、最後から2列目は開始確率、最終列が終点確率になっている。従い、例えば最終ステップで晴れになる確率は下記で得られる。

print(math.e**model.forward(sequence)[len(sequence), sunny_idx])

0.007304040000000002

以降、わかりやすくするためにobservationのシーケンスを長くして確認する。Viterbiの結果も併記する。

sequence = [ 'walk', 'shop', 'clean', 'clean', 'walk', 'shop', 'walk', 'shop', 'clean', 'shop', 'clean', 'shop', 'clean', 'shop', 'walk', 'shop' ]
print(", ".join(state.name for i, state in model.viterbi(sequence)[1]))

Rainy-Sunny-start, Sunny, Rainy, Rainy, Rainy, Sunny, Sunny, Sunny, Rainy, Rainy, Rainy, Rainy, Rainy, Rainy, Rainy, Sunny, Sunny, Rainy-Sunny-end

forward-backward algorithmが出力するposterior prob.を確認する。

trans, ems = model.forward_backward(sequence)
# transition matrix
print(math.e**trans)
# emission matrix
print(math.e**ems)    # model.predict_proba(sequence)の戻りと同じ

Screenshot from 2022-01-03 12-01-42.png
はじめのtransition matrixは下図のような正方行列になっていて、algorithmが推論したstate A -> state Bへの遷移回数が出力されている。
Screenshot from 2022-01-03 12-03-54.png
実際にVitabiの結果を数えてみると下図となる。
Screenshot from 2022-01-03 12-08-00.png
Viterbiは最後のhidden stateはsunnyだが、forwardではややrainyが大きくなっている。
それ以外の大小関係はあっている。3

次のemission matrixは同様に1列目がrainy, 2列目がsunny, 各行が入力observationに対する確率を示している。まとめると下図となり、最終行のend stateへ渡るhidden stateの推論がviterbiと異なっている以外は同じ結果となっている。
Screenshot from 2022-01-03 12-28-42.png
離散値で出力するviterbiよりも、確率で出力してくれるforward-backwardの方が使い勝手が良いだろう。グラフが大きくなったときのcomputational costが気になるところ。

backward algorithmの出力も見ておく。

print(math.e**model.backward(sequence))

Screenshot from 2022-01-03 14-38-11.png

これだけがよくわからなかった。まず全般を通してDocumentには出力されるmatrixをどのように読むのか説明が一切ない。このbackwardは特に情報が少ないので予想になるが、行はシーケンスの終端から遡るので1行目がシーケンスの最後で最終行がシーケンスの先頭なことは間違いない。1,2列目はrainy, sunnyで間違いなく、3列目がend stateで4列目がstart stateになっているようにしか見えない。チュートリアルには各ステップの任意のhidden stateの確率が出力されているとなっているが、どのようにsumをとっても1にならず、本当に確率なのか不明。forward-backwardのためのbackwardなので、単独で使用する機会はないように思う。
forward-backwardが使えれば最強のように思えるが、今後使っていって何かわかったら更新することとする。

最後に最大の事後確率をつないだhidden stateの遷移を出力、というか表示する。確率はすでにforward-backwardで計算済み。

print(" ".join(state.name for i, state in model.maximum_a_posteriori(sequence)[1]))

Sunny Rainy Rainy Rainy Sunny Sunny Sunny Rainy Rainy Rainy Rainy Rainy Rainy Rainy Sunny Rainy

以上

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1