keita____
@keita____

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

隠れマルコフモデルのパラメータ推定が上手くいかない件について

解決したいこと

隠れマルコフモデルの実装として、カイジのイカサマサイコロを暴くというのをやっています。推定結果が0ばっかりで、真のパラメータとは程遠い結果となってしまいました。どこが原因なのでしょうか。

発生している問題・エラー

推定された遷移確率:
[[1. 0.]
 [0. 0.]]
推定された出力確率:
[[0.18333333 0.13333333 0.17333333 0.16333333 0.15       0.19666667]
 [0.         0.         0.         0.         0.         0.        ]]

該当するソースコード

import numpy as np
import matplotlib.pyplot as plt

class HMM:
    def __init__(self, states, observations):
        self.states = states
        self.observations = observations
        self.initial_prob = None
        self.transition_prob = None
        self.emission_prob = None

    def _init_params(self):
        n_states = len(self.states)
        n_observations = len(self.observations)
        self.initial_prob = np.array([1.0, 0])  # 初期状態を指定
        self.transition_prob = np.array([[0.99, 0.01], [0.05, 0.95]])  # 遷移確率を指定
        self.emission_prob = np.array([[1/6, 1/6, 1/6, 1/6, 1/6, 1/6], [0, 0, 0, 1/3, 1/3, 1/3]])

    def _estimate_params(self, hidden_states, observations):
        n_states = len(self.states)
        n_observations = len(self.observations)
        n_timesteps = len(hidden_states)

        # パラメータを初期化
        estimated_initial_prob = np.zeros(n_states)
        estimated_transition_prob = np.zeros((n_states, n_states))
        estimated_emission_prob = np.zeros((n_states, n_observations))

        # 初期確率の推定
        estimated_initial_prob[self.states.index(hidden_states[0])] = 1

        # 遷移確率の推定
        for t in range(n_timesteps - 1):
            current_state = self.states.index(hidden_states[t])
            next_state = self.states.index(hidden_states[t + 1])
            estimated_transition_prob[current_state, next_state] += 1

        # 出力確率の推定
        for t in range(n_timesteps):
            state = self.states.index(hidden_states[t])
            observation = self.observations.index(observations[t])
            estimated_emission_prob[state, observation] += 1

        estimated_initial_prob /= np.sum(estimated_initial_prob) + 1e-10
        estimated_transition_prob /= np.sum(estimated_transition_prob, axis=1, keepdims=True) + 1e-10
        estimated_emission_prob /= np.sum(estimated_emission_prob, axis=1, keepdims=True) + 1e-10

        return estimated_initial_prob, estimated_transition_prob, estimated_emission_prob

    def baum_welch(self, hidden_states, observations, n_iter=300):
        self._init_params()

        for _ in range(n_iter):
            # フォワードステップ
            forward_prob = self.forward(hidden_states, observations)

            # バックワードステップ
            backward_prob = self.backward(hidden_states, observations)

            # 推定されたパラメータの更新
            estimated_initial_prob, estimated_transition_prob, estimated_emission_prob = self._estimate_params(hidden_states, observations)

            # パラメータの更新
            self.initial_prob = estimated_initial_prob
            self.transition_prob = estimated_transition_prob
            self.emission_prob = estimated_emission_prob

        return self.initial_prob, self.transition_prob, self.emission_prob

    def forward(self, hidden_states, observations):
        n_timesteps = len(observations)
        n_states = len(self.states)
        forward_prob = np.zeros((n_timesteps, n_states))

        forward_prob[0, :] = self.initial_prob * self.emission_prob[:, self.observations.index(observations[0])]

        for t in range(1, n_timesteps):
            for j in range(n_states):
                forward_prob[t, j] = np.sum(forward_prob[t - 1, :] * self.transition_prob[:, j]) * self.emission_prob[j, self.observations.index(observations[t])]

        return forward_prob

    def backward(self, hidden_states, observations):
        n_timesteps = len(observations)
        n_states = len(self.states)
        backward_prob = np.zeros((n_timesteps, n_states))

        backward_prob[n_timesteps - 1, :] = 1

        for t in reversed(range(n_timesteps - 1)):
            for i in range(n_states):
                backward_prob[t, i] = np.sum(backward_prob[t + 1, :] * self.transition_prob[i, :] * self.emission_prob[:, self.observations.index(observations[t + 1])])

        return backward_prob

# パラメータの真の値
states = ['Fair', 'Biased']
observations = [1, 2, 3, 4, 5, 6]
initial_prob = np.array([1.0, 0.0])
transition_prob = np.array([[0.99, 0.01], [0.05, 0.95]])
emission_prob = np.array([[1/6, 1/6, 1/6, 1/6, 1/6, 1/6], [0, 0, 0, 1/3, 1/3, 1/3]])

# HMMのインスタンス化
hmm = HMM(states, observations)

# 隠れ状態の生成
hidden_states = []
observed_states = []

for _ in range(300):
    state = np.random.choice(states, p=initial_prob)
    # 観測結果の生成(修正後)
    observation = np.random.choice(observations, p=emission_prob[states.index(state)].tolist())
    hidden_states.append(state)
    observed_states.append(observation)

# 推定されたパラメータの取得
estimated_initial_prob, estimated_transition_prob, estimated_emission_prob = hmm.baum_welch(hidden_states, observed_states, n_iter=100)

print("推定された遷移確率:")
print(estimated_transition_prob)
print("推定された出力確率:")
print(estimated_emission_prob)

# 隠れ状態の推移の可視化
fig, ax = plt.subplots(figsize=(8, 6))
x = range(len(hidden_states))
y = [states.index(state) for state in hidden_states]
ax.plot(x, y, label='Hidden States')
ax.set_xticks(x)
ax.set_xticklabels(hidden_states)
ax.set_xlabel('Time Step')
ax.set_ylabel('Hidden State')
ax.legend()
plt.show()

# 観測結果の推移の可視化
fig, ax = plt.subplots(figsize=(8, 6))
x = range(len(observed_states))
ax.plot(x, observed_states, label='Observed States')
ax.set_xticks(x)
ax.set_xticklabels(hidden_states)
ax.set_xlabel('Time Step')
ax.set_ylabel('Observed State')
ax.legend()
plt.show()

0

2Answer

HMMの中身は見れてませんが、ぱっと見でおかしそうな点が観測値の生成部分だと思いました。
まず、for文内にあるstate がずっと initial_prob を使っているので、state の値がずっと同じままになっていると思います。あとは、transition_prob を使って state を遷移させましょう!

edited.py
state = np.random.choice(states, p=initial_prob)
for _ in range(300):
    observation = np.random.choice(observations, p=emission_prob[states.index(state)].tolist())
    hidden_states.append(state)
    observed_states.append(observation)
    # stateの遷移
    state = np.random.choice(states, p=transition_prob[states.index(state),:])
2Like

Comments

  1. @keita____

    Questioner

    ありがとうございます!

プログラミングに全く関係なくてすみません。

そもそもモデルの選択が誤っていませんか?
厳密にはカイジのサイコロではなく、ハンチョウのサイコロについてお話します。
通常のサイコロは6面に1,2,3,4,5,6が刻まれていますが、通常でない特殊サイは、6面に、4,4,5,5,6,6が刻まれています。

つまり、イカサマを統計学的手法で証明するには、指数分布モデルでしょう。この点は一条のメモをカイジがみて、イカサマを確信してます。

 カイジとハンチョウの対戦において456サイと通常のサイコロが入れ替わる確率の面で捉えると隠れ無しでマルコフモデルが適用されるでしょうか。

 さて、カイジがハンチョウのイカサマを見破ったプロセスではゲームの流れで補佐の役割が隠れマルコフモデルの条件になるのではないでしょうか?

また、456サイと通常のサイコロの入れ替わりを决定付ける条件はカイジ側での勝敗と掛札に左右されるように思います。

1Like

Comments

  1. @keita____

    Questioner

    回答ありがとうございます。おそらくカイジの場面を忠実に再現するなら隠れマルコフモデルではないということでしょうか?自分はカイジを見たことがなく、そしてカイジの有名な場面を忠実に再現することはあまり考えていません。言葉足らずで申し訳ありませんでした。自分がやりたかったのは、サイコロの性質(出力確率)とサイコロの使い分け(遷移確率)を隠れマルコフモデルを用いて推定したかったです。これについていろんなサイトに記事があるのですが、どれも実装コードは載っていないので困っています。

  2. コイントスの例が引き合いに説明されていますが、やはり、サイコロは指数分布モデルでは?

    サイコロのイカサマ交換において、出目の確率が変化します。このイカサマのタイミングを隠れマルコフモデルで解き明かせるかもしれません。

  3. @keita____

    Questioner

    ありがとうございます!

Your answer might help someone💌