マルコフ決定過程(MDP)の定義と性質を解説

マルコフ決定過程(Markov Decision Process, MDP)は、強化学習の数学的な基盤です。エージェントが環境の中で逐次的に意思決定を行い、累積報酬を最大化する問題を厳密に定式化するための枠組みであり、ロボット制御、ゲームAI、自動運転、航空宇宙分野の軌道計画など、あらゆる意思決定問題のモデリングに用いられます。

MDPはマルコフ連鎖を拡張した概念です。マルコフ連鎖では状態遷移が確率的に決まりますが、MDPではさらに「行動」と「報酬」が加わります。エージェントは各時刻で行動を選択でき、その選択によって遷移先の状態と得られる報酬が変化します。本記事では、MDPの定義を構成する各要素を一つずつ丁寧に導入し、方策や価値関数の数学的定義、それらの間の関係式を省略なく導出します。

本記事の内容

  • MDPの定義(5つ組 $(\mathcal{S}, \mathcal{A}, P, R, \gamma)$)
  • 方策 $\pi(a|s)$ の定義(確率的方策と決定的方策)
  • 状態価値関数 $V^{\pi}(s)$ と行動価値関数 $Q^{\pi}(s,a)$ の定義
  • $V^{\pi}$ と $Q^{\pi}$ の関係式の導出
  • 有限ホライズン・無限ホライズン・平均報酬の定式化
  • Pythonによるグリッドワールド環境の実装と方策の可視化

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

MDPとは

マルコフ決定過程は、マルコフ性を持つ逐次的意思決定問題のモデルです。マルコフ連鎖では状態の遷移が確率的に定まるだけでしたが、MDPではエージェントが行動を選択できるようになり、さらに行動の結果として報酬を受け取ります。

直感的には、「今の状態だけを見て次の行動を決め、その結果として報酬を受け取りながら最良の振る舞いを見つける」問題を数学的に記述したものがMDPです。

MDPの数学的定義

MDPは5つ組 $(\mathcal{S}, \mathcal{A}, P, R, \gamma)$ で定義されます。

状態空間 $\mathcal{S}$

エージェントが取りうる全ての状態の集合です。

$$ \mathcal{S} = \{s_1, s_2, \dots, s_n\} $$

有限MDPでは $|\mathcal{S}| < \infty$ です。グリッドワールドの場合、各セルが一つの状態に対応します。

行動空間 $\mathcal{A}$

エージェントが選択できる全ての行動の集合です。状態に依存して利用可能な行動が変わる場合は $\mathcal{A}(s)$ と書くこともあります。

$$ \mathcal{A} = \{a_1, a_2, \dots, a_m\} $$

グリッドワールドでは $\mathcal{A} = \{\text{上}, \text{下}, \text{左}, \text{右}\}$ が典型的です。

状態遷移確率 $P$

状態 $s$ で行動 $a$ を取ったとき、次の状態が $s’$ になる確率です。

$$ P(s’ | s, a) = \Pr(S_{t+1} = s’ \mid S_t = s, A_t = a) $$

これはマルコフ性を反映しています。つまり、次の状態は現在の状態と行動のみに依存し、過去の履歴には依存しません。

$$ \Pr(S_{t+1} = s’ \mid S_t = s, A_t = a, S_{t-1}, A_{t-1}, \dots) = \Pr(S_{t+1} = s’ \mid S_t = s, A_t = a) $$

また、確率の正規化条件を満たします。

$$ \sum_{s’ \in \mathcal{S}} P(s’ | s, a) = 1, \quad \forall s \in \mathcal{S}, \forall a \in \mathcal{A} $$

報酬関数 $R$

状態 $s$ で行動 $a$ を取って状態 $s’$ に遷移したときに受け取る報酬です。

$$ R(s, a, s’) = \mathbb{E}[R_{t+1} \mid S_t = s, A_t = a, S_{t+1} = s’] $$

しばしば簡略化して $R(s, a)$ や $R(s)$ と書くこともあります。$R(s, a)$ の場合は

$$ R(s, a) = \sum_{s’ \in \mathcal{S}} P(s’ | s, a) R(s, a, s’) $$

として定義されます。

割引率 $\gamma$

将来の報酬をどれだけ割り引くかを制御するパラメータです。

$$ \gamma \in [0, 1) $$

$\gamma = 0$ の場合、エージェントは即時報酬のみを最大化します。$\gamma$ が $1$ に近いほど、将来の報酬を重視します。$\gamma < 1$ であることにより、無限ホライズンの場合でも累積報酬が有界になります。

割引報酬和(リターン)

時刻 $t$ 以降にエージェントが受け取る割引報酬和(リターン)$G_t$ は次のように定義されます。

$$ G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \cdots = \sum_{k=0}^{\infty} \gamma^k R_{t+k+1} $$

ここで $R_{t+1}$ は時刻 $t$ から $t+1$ に遷移する際に得られる報酬です。

リターンの再帰的表現

$G_t$ は次のように再帰的に表すことができます。

$$ \begin{align} G_t &= R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \cdots \\ &= R_{t+1} + \gamma (R_{t+2} + \gamma R_{t+3} + \cdots) \\ &= R_{t+1} + \gamma G_{t+1} \end{align} $$

この再帰的な関係は、後にベルマン方程式を導出する際の出発点になります。

リターンの有界性

$|R_t| \leq R_{\max}$ として報酬が有界であるとき、$\gamma < 1$ のもとで

$$ |G_t| \leq \sum_{k=0}^{\infty} \gamma^k R_{\max} = \frac{R_{\max}}{1 – \gamma} $$

となり、リターンは有界です。等比級数の公式 $\sum_{k=0}^{\infty} r^k = \frac{1}{1-r}$($|r| < 1$)を用いました。

方策の定義

方策(policy)$\pi$ は、エージェントが各状態でどのように行動を選択するかを記述する規則です。

確率的方策

確率的方策は、状態 $s$ における行動 $a$ の選択確率を与える条件付き確率分布です。

$$ \pi(a | s) = \Pr(A_t = a \mid S_t = s) $$

全ての状態 $s$ に対して

$$ \sum_{a \in \mathcal{A}} \pi(a | s) = 1, \quad \pi(a | s) \geq 0 $$

を満たします。

決定的方策

決定的方策は、各状態に対して一つの行動を決定的に割り当てる写像です。

$$ \pi: \mathcal{S} \to \mathcal{A}, \quad a = \pi(s) $$

決定的方策は確率的方策の特殊ケースとして、ディラックのデルタ関数を用いて

$$ \pi(a | s) = \begin{cases} 1 & \text{if } a = \pi(s) \\ 0 & \text{otherwise} \end{cases} $$

と表すことができます。

定常方策と非定常方策

定常方策は時刻に依存しない方策、すなわち全ての $t$ に対して $\pi_t = \pi$ です。非定常方策は時刻に依存して変化する方策 $\pi_t(a|s)$ です。有限MDPでは、最適方策として定常決定的方策が存在することが知られています。

状態価値関数 $V^{\pi}(s)$

方策 $\pi$ のもとでの状態価値関数は、状態 $s$ から出発して方策 $\pi$ に従い行動したときの期待リターンです。

$$ \begin{equation} V^{\pi}(s) = \mathbb{E}_{\pi}[G_t \mid S_t = s] \end{equation} $$

$\mathbb{E}_{\pi}$ は方策 $\pi$ に従って行動を選択した場合の期待値を意味します。

$V^{\pi}(s)$ の展開

定義を展開してみましょう。

$$ \begin{align} V^{\pi}(s) &= \mathbb{E}_{\pi}[G_t \mid S_t = s] \\ &= \mathbb{E}_{\pi}\left[\sum_{k=0}^{\infty} \gamma^k R_{t+k+1} \mid S_t = s\right] \\ &= \mathbb{E}_{\pi}\left[R_{t+1} + \gamma \sum_{k=0}^{\infty} \gamma^k R_{t+k+2} \mid S_t = s\right] \\ &= \mathbb{E}_{\pi}\left[R_{t+1} + \gamma G_{t+1} \mid S_t = s\right] \end{align} $$

最後の行で $G_{t+1} = \sum_{k=0}^{\infty} \gamma^k R_{t+k+2}$ を用いました。

行動価値関数 $Q^{\pi}(s, a)$

方策 $\pi$ のもとでの行動価値関数は、状態 $s$ で行動 $a$ を取り、その後は方策 $\pi$ に従って行動したときの期待リターンです。

$$ \begin{equation} Q^{\pi}(s, a) = \mathbb{E}_{\pi}[G_t \mid S_t = s, A_t = a] \end{equation} $$

$V^{\pi}(s)$ との違いは、最初の行動が $\pi$ に従うのではなく $a$ に固定されている点です。

$V^{\pi}$ と $Q^{\pi}$ の関係式の導出

$V^{\pi}$ を $Q^{\pi}$ で表す

状態 $s$ での価値は、その状態で取りうる各行動 $a$ の行動価値を、方策 $\pi$ で重み付き平均したものです。

全確率の法則を用いて

$$ \begin{align} V^{\pi}(s) &= \mathbb{E}_{\pi}[G_t \mid S_t = s] \\ &= \sum_{a \in \mathcal{A}} \pi(a | s) \, \mathbb{E}_{\pi}[G_t \mid S_t = s, A_t = a] \\ &= \sum_{a \in \mathcal{A}} \pi(a | s) \, Q^{\pi}(s, a) \end{align} $$

2行目では $A_t$ について条件分けを行い、全確率の法則 $\mathbb{E}[X] = \sum_a P(A=a) \mathbb{E}[X \mid A=a]$ を適用しました。3行目で $Q^{\pi}(s,a)$ の定義を代入しました。

$$ \begin{equation} \boxed{V^{\pi}(s) = \sum_{a \in \mathcal{A}} \pi(a | s) \, Q^{\pi}(s, a)} \end{equation} $$

$Q^{\pi}$ を $V^{\pi}$ で表す

行動価値関数は、即時報酬と次の状態の価値を使って表すことができます。

$$ \begin{align} Q^{\pi}(s, a) &= \mathbb{E}_{\pi}[G_t \mid S_t = s, A_t = a] \\ &= \mathbb{E}_{\pi}[R_{t+1} + \gamma G_{t+1} \mid S_t = s, A_t = a] \end{align} $$

ここで、$S_{t+1}$ について条件分けを行います。$S_{t+1} = s’$ の確率は $P(s’|s,a)$ です。

$$ \begin{align} Q^{\pi}(s, a) &= \sum_{s’ \in \mathcal{S}} P(s’ | s, a) \left[ R(s, a, s’) + \gamma \, \mathbb{E}_{\pi}[G_{t+1} \mid S_{t+1} = s’] \right] \end{align} $$

マルコフ性より、$G_{t+1}$ の分布は $S_{t+1} = s’$ のみに依存するため

$$ \mathbb{E}_{\pi}[G_{t+1} \mid S_{t+1} = s’] = V^{\pi}(s’) $$

したがって

$$ \begin{equation} \boxed{Q^{\pi}(s, a) = \sum_{s’ \in \mathcal{S}} P(s’ | s, a) \left[ R(s, a, s’) + \gamma \, V^{\pi}(s’) \right]} \end{equation} $$

$V^{\pi}$ のベルマン期待方程式(予告)

上記の2つの関係式を組み合わせると、$V^{\pi}$ の再帰的な方程式が得られます。

$$ \begin{align} V^{\pi}(s) &= \sum_{a \in \mathcal{A}} \pi(a | s) \, Q^{\pi}(s, a) \\ &= \sum_{a \in \mathcal{A}} \pi(a | s) \sum_{s’ \in \mathcal{S}} P(s’ | s, a) \left[ R(s, a, s’) + \gamma \, V^{\pi}(s’) \right] \end{align} $$

これがベルマン期待方程式です。この方程式の詳細な解析は次の記事で行います。

有限ホライズン・無限ホライズン・平均報酬

MDPの定式化には、計画期間によっていくつかのバリエーションがあります。

有限ホライズン MDP

計画期間が $T$ ステップで終了する場合のリターンは

$$ G_t = \sum_{k=0}^{T-t-1} \gamma^k R_{t+k+1} $$

です。有限ホライズンでは $\gamma = 1$ としても $G_t$ は有界です。時刻によって最適行動が変わるため、最適方策は一般に非定常方策になります。

無限ホライズン MDP(割引報酬)

本記事で主に扱ってきた定式化で、$\gamma < 1$ のもとで

$$ G_t = \sum_{k=0}^{\infty} \gamma^k R_{t+k+1} $$

とします。割引率 $\gamma < 1$ により無限和が収束し、最適方策として定常方策が存在します。

平均報酬 MDP

無限ホライズンにおいて、割引ではなく1ステップあたりの平均報酬を最大化する定式化です。

$$ \rho^{\pi} = \lim_{T \to \infty} \frac{1}{T} \mathbb{E}_{\pi}\left[\sum_{t=1}^{T} R_t\right] $$

この定式化は、割引率 $\gamma \to 1$ の極限として解釈することもでき、定常的な環境で長期的なパフォーマンスを評価する際に用いられます。

差分報酬(differential reward)を用いて

$$ V^{\pi}(s) = \mathbb{E}_{\pi}\left[\sum_{k=0}^{\infty} (R_{t+k+1} – \rho^{\pi}) \mid S_t = s\right] $$

と価値関数を定義し、対応するベルマン方程式を導くことができます。

具体例: グリッドワールド

MDPの概念を具体例で確認しましょう。$4 \times 4$ のグリッドワールドを考えます。

設定: – 状態空間: $\mathcal{S} = \{(i, j) \mid 0 \leq i, j \leq 3\}$(16個のセル) – 行動空間: $\mathcal{A} = \{\text{上}, \text{下}, \text{左}, \text{右}\}$ – 左上 $(0,0)$ がスタート、右下 $(3,3)$ がゴール(終端状態) – ゴールに到達すると報酬 $+1$、それ以外のステップでは報酬 $-0.04$(移動コスト) – 壁にぶつかると位置が変わらない – 遷移は決定的(選んだ方向に必ず移動) – 割引率 $\gamma = 0.99$

この設定では、遷移確率は

$$ P(s’ | s, a) = \begin{cases} 1 & \text{if } s’ \text{ は } s \text{ から行動 } a \text{ で到達する状態} \\ 0 & \text{otherwise} \end{cases} $$

となります。

ランダム方策として、全ての状態で各行動を等確率で選ぶ方策を考えます。

$$ \pi_{\text{random}}(a | s) = \frac{1}{|\mathcal{A}|} = \frac{1}{4}, \quad \forall a \in \mathcal{A}, \forall s \in \mathcal{S} $$

Pythonでの実装

グリッドワールド環境

まず、グリッドワールドのMDP環境をPythonで実装します。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

class GridWorld:
    """4x4 グリッドワールド MDP 環境"""

    def __init__(self, rows=4, cols=4, gamma=0.99):
        self.rows = rows
        self.cols = cols
        self.gamma = gamma

        # 行動の定義: 上, 下, 左, 右
        self.actions = [0, 1, 2, 3]
        self.action_names = ['↑', '↓', '←', '→']
        # 各行動に対応する移動量 (行の変化, 列の変化)
        self.action_deltas = [(-1, 0), (1, 0), (0, -1), (0, 1)]

        # 状態数と行動数
        self.n_states = rows * cols
        self.n_actions = len(self.actions)

        # ゴール状態(右下)
        self.goal_state = (rows - 1, cols - 1)

        # 報酬の設定
        self.goal_reward = 1.0
        self.step_reward = -0.04

    def state_to_idx(self, row, col):
        """(行, 列) → 状態インデックス"""
        return row * self.cols + col

    def idx_to_state(self, idx):
        """状態インデックス → (行, 列)"""
        return idx // self.cols, idx % self.cols

    def is_terminal(self, row, col):
        """終端状態かどうかを判定"""
        return (row, col) == self.goal_state

    def get_next_state(self, row, col, action):
        """状態遷移: 現在状態と行動から次の状態を返す"""
        if self.is_terminal(row, col):
            return row, col

        dr, dc = self.action_deltas[action]
        new_row = row + dr
        new_col = col + dc

        # 壁にぶつかったら動かない
        if new_row < 0 or new_row >= self.rows or new_col < 0 or new_col >= self.cols:
            new_row, new_col = row, col

        return new_row, new_col

    def get_reward(self, row, col, action, next_row, next_col):
        """報酬関数 R(s, a, s')"""
        if self.is_terminal(row, col):
            return 0.0
        if (next_row, next_col) == self.goal_state:
            return self.goal_reward
        return self.step_reward

    def get_transition_prob(self, row, col, action):
        """
        遷移確率を返す: [(確率, 次の行, 次の列, 報酬)]
        決定的環境なので確率は常に1.0
        """
        if self.is_terminal(row, col):
            return [(1.0, row, col, 0.0)]

        next_row, next_col = self.get_next_state(row, col, action)
        reward = self.get_reward(row, col, action, next_row, next_col)
        return [(1.0, next_row, next_col, reward)]


# グリッドワールドの生成
env = GridWorld(rows=4, cols=4, gamma=0.99)
print(f"状態数: {env.n_states}")
print(f"行動数: {env.n_actions}")
print(f"割引率: {env.gamma}")
print(f"ゴール: {env.goal_state}")

方策の定義と可視化

ランダム方策と、手動で定義した決定的方策を比較します。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

class GridWorld:
    """4x4 グリッドワールド MDP 環境"""

    def __init__(self, rows=4, cols=4, gamma=0.99):
        self.rows = rows
        self.cols = cols
        self.gamma = gamma
        self.actions = [0, 1, 2, 3]
        self.action_names = ['↑', '↓', '←', '→']
        self.action_deltas = [(-1, 0), (1, 0), (0, -1), (0, 1)]
        self.n_states = rows * cols
        self.n_actions = len(self.actions)
        self.goal_state = (rows - 1, cols - 1)
        self.goal_reward = 1.0
        self.step_reward = -0.04

    def state_to_idx(self, row, col):
        return row * self.cols + col

    def idx_to_state(self, idx):
        return idx // self.cols, idx % self.cols

    def is_terminal(self, row, col):
        return (row, col) == self.goal_state

    def get_next_state(self, row, col, action):
        if self.is_terminal(row, col):
            return row, col
        dr, dc = self.action_deltas[action]
        new_row, new_col = row + dr, col + dc
        if new_row < 0 or new_row >= self.rows or new_col < 0 or new_col >= self.cols:
            new_row, new_col = row, col
        return new_row, new_col

    def get_reward(self, row, col, action, next_row, next_col):
        if self.is_terminal(row, col):
            return 0.0
        if (next_row, next_col) == self.goal_state:
            return self.goal_reward
        return self.step_reward

    def get_transition_prob(self, row, col, action):
        if self.is_terminal(row, col):
            return [(1.0, row, col, 0.0)]
        next_row, next_col = self.get_next_state(row, col, action)
        reward = self.get_reward(row, col, action, next_row, next_col)
        return [(1.0, next_row, next_col, reward)]

def policy_evaluation(env, policy, theta=1e-8, max_iter=10000):
    """
    方策評価: 方策 π のもとでの状態価値関数 V^π を反復法で計算
    """
    V = np.zeros(env.n_states)

    for iteration in range(max_iter):
        delta = 0
        for s in range(env.n_states):
            row, col = env.idx_to_state(s)
            if env.is_terminal(row, col):
                continue

            v = 0
            for a in env.actions:
                # 方策による行動確率
                pi_a = policy[s, a]
                # 遷移確率と報酬
                for prob, next_row, next_col, reward in env.get_transition_prob(row, col, a):
                    s_next = env.state_to_idx(next_row, next_col)
                    # ベルマン期待方程式
                    v += pi_a * prob * (reward + env.gamma * V[s_next])

            delta = max(delta, abs(v - V[s]))
            V[s] = v

        if delta < theta:
            break

    return V

def visualize_policy_and_value(env, policy, V, title="方策と状態価値関数"):
    """方策(矢印)と状態価値関数(色)を可視化"""
    fig, axes = plt.subplots(1, 2, figsize=(14, 6))

    # --- 左: 方策の可視化 ---
    ax = axes[0]
    ax.set_title("Policy (方策)", fontsize=14)
    ax.set_xlim(-0.5, env.cols - 0.5)
    ax.set_ylim(-0.5, env.rows - 0.5)
    ax.set_aspect('equal')
    ax.invert_yaxis()

    # グリッド線
    for i in range(env.rows + 1):
        ax.axhline(i - 0.5, color='black', linewidth=1)
    for j in range(env.cols + 1):
        ax.axvline(j - 0.5, color='black', linewidth=1)

    # 矢印の方向
    arrow_dx = [0, 0, -0.3, 0.3]  # 上, 下, 左, 右
    arrow_dy = [-0.3, 0.3, 0, 0]

    for s in range(env.n_states):
        row, col = env.idx_to_state(s)
        if env.is_terminal(row, col):
            ax.text(col, row, 'G', ha='center', va='center', fontsize=16, fontweight='bold', color='red')
            continue

        # 確率が最も高い行動を矢印で表示
        best_a = np.argmax(policy[s])
        ax.annotate('', xy=(col + arrow_dx[best_a], row + arrow_dy[best_a]),
                     xytext=(col, row),
                     arrowprops=dict(arrowstyle='->', color='blue', lw=2))

    ax.set_xticks(range(env.cols))
    ax.set_yticks(range(env.rows))

    # --- 右: 状態価値関数の可視化 ---
    ax = axes[1]
    ax.set_title("$V^{\\pi}(s)$ (状態価値関数)", fontsize=14)

    V_grid = V.reshape(env.rows, env.cols)
    im = ax.imshow(V_grid, cmap='RdYlGn', interpolation='nearest')

    # 各セルに数値を表示
    for i in range(env.rows):
        for j in range(env.cols):
            ax.text(j, i, f'{V_grid[i, j]:.2f}', ha='center', va='center', fontsize=12, fontweight='bold')

    ax.set_xticks(range(env.cols))
    ax.set_yticks(range(env.rows))
    plt.colorbar(im, ax=ax)

    plt.suptitle(title, fontsize=16)
    plt.tight_layout()
    plt.show()

# --- グリッドワールドの生成 ---
env = GridWorld(rows=4, cols=4, gamma=0.99)

# --- ランダム方策の定義 ---
random_policy = np.ones((env.n_states, env.n_actions)) / env.n_actions

# ランダム方策の評価
V_random = policy_evaluation(env, random_policy)
print("ランダム方策のもとでの状態価値関数:")
print(V_random.reshape(env.rows, env.cols).round(3))

# --- 決定的方策の定義(右または下に進む貪欲方策)---
greedy_policy = np.zeros((env.n_states, env.n_actions))
for s in range(env.n_states):
    row, col = env.idx_to_state(s)
    if env.is_terminal(row, col):
        greedy_policy[s, 0] = 1.0  # 終端状態ではどの行動でもよい
    elif row < env.rows - 1:
        greedy_policy[s, 1] = 1.0  # 下に進む
    else:
        greedy_policy[s, 3] = 1.0  # 右に進む

# 決定的方策の評価
V_greedy = policy_evaluation(env, greedy_policy)
print("\n貪欲方策(下→右)のもとでの状態価値関数:")
print(V_greedy.reshape(env.rows, env.cols).round(3))

# --- 可視化 ---
visualize_policy_and_value(env, random_policy, V_random, title="ランダム方策と状態価値関数")
visualize_policy_and_value(env, greedy_policy, V_greedy, title="貪欲方策(下→右)と状態価値関数")

このコードでは、以下のことを行っています。

  1. GridWorld クラスでMDPの構成要素($\mathcal{S}, \mathcal{A}, P, R, \gamma$)を実装
  2. policy_evaluation 関数でベルマン期待方程式を反復的に解き、$V^{\pi}(s)$ を計算
  3. ランダム方策と決定的方策(下→右)の2つを定義し、それぞれの状態価値関数を比較
  4. 方策を矢印で、状態価値関数を色の濃淡で可視化

ランダム方策ではゴールから遠い状態ほど価値が低く、ゴール付近のみ正の値を持ちます。一方、下→右に進む貪欲方策では、全ての状態からゴールへ確実に到達できるため、ゴールから遠い状態でも比較的高い価値を持ちます。

$Q^{\pi}(s,a)$ の計算

$V^{\pi}$ から $Q^{\pi}$ を計算するコードも実装します。

import numpy as np

def compute_q_from_v(env, V):
    """
    V^π から Q^π を計算する
    Q^π(s, a) = Σ_{s'} P(s'|s,a) [R(s,a,s') + γ V^π(s')]
    """
    Q = np.zeros((env.n_states, env.n_actions))

    for s in range(env.n_states):
        row, col = env.idx_to_state(s)
        if env.is_terminal(row, col):
            continue

        for a in env.actions:
            for prob, next_row, next_col, reward in env.get_transition_prob(row, col, a):
                s_next = env.state_to_idx(next_row, next_col)
                Q[s, a] += prob * (reward + env.gamma * V[s_next])

    return Q

# V^π と Q^π の関係を確認
# (上のコードで env, V_random, random_policy が定義済みとする)
Q_random = compute_q_from_v(env, V_random)

# V^π(s) = Σ_a π(a|s) Q^π(s, a) を確認
V_reconstructed = np.sum(random_policy * Q_random, axis=1)
print("V^π(s) を Q^π(s,a) から再構成:")
print(V_reconstructed.reshape(4, 4).round(3))
print("\n直接計算した V^π(s):")
print(V_random.reshape(4, 4).round(3))
print("\n差の最大値:", np.max(np.abs(V_reconstructed - V_random)))

このコードにより、$V^{\pi}(s) = \sum_{a} \pi(a|s) Q^{\pi}(s,a)$ の関係が数値的に成り立つことを確認できます。

まとめ

本記事では、マルコフ決定過程(MDP)の定義と性質について解説しました。

  • MDPは5つ組 $(\mathcal{S}, \mathcal{A}, P, R, \gamma)$ で定義され、逐次的意思決定問題の標準的な枠組みである
  • 方策 $\pi(a|s)$ には確率的方策と決定的方策があり、エージェントの行動規則を記述する
  • 状態価値関数 $V^{\pi}(s) = \mathbb{E}_{\pi}[G_t \mid S_t = s]$ は状態の良さを、行動価値関数 $Q^{\pi}(s,a) = \mathbb{E}_{\pi}[G_t \mid S_t = s, A_t = a]$ は状態と行動の組の良さを評価する
  • $V^{\pi}(s) = \sum_a \pi(a|s) Q^{\pi}(s,a)$ および $Q^{\pi}(s,a) = \sum_{s’} P(s’|s,a)[R + \gamma V^{\pi}(s’)]$ の関係が成り立つ
  • 有限ホライズン、無限ホライズン(割引報酬)、平均報酬の3つの定式化がある

次のステップとして、以下の記事も参考にしてください。