Q学習

Q 学習は、行動価値 Q(s,a) を直接学びながら最適行動を目指す方法です。状態価値を経由せず、各行動をその場で比べられるので、モデルなし制御の代表例になります。

参考動画(外部)

授業本編ではなく、別の説明で見直したいときの参考材料です。

max を入れた瞬間に「最善ならどうなるか」を学び始める

Q 学習の特徴は、次状態で実際に取った行動ではなく、そこで取りうる最善行動の価値を目標値に入れることです。だからこそ off-policy になり、探索しながらでも最適方策へ寄せる更新ができます。

ここで見たいのは、Q(s,a) の更新式そのものより、なぜ max が policy の外側にいるのかです。実際の行動列と、学習が目指す行動列が一致しなくてよいのが Q 学習の強さであり、同時に過大評価の入口にもなります。

読み進める視点

参照用の return、1 回の Q 更新、greedy action の抽出、探索と活用の切り替え、最後に得られた方策の読み方を順に見ます。

この notebook の主題は、表形式 Q 学習の骨格です。あとで deep RL を読むときも、target の中身は結局ここで見た r+γmaxQr + \gamma \max Q から始まっています。

読み方の軸

Q 学習では「実際に何をしたか」より「そこから最善ならどうするか」を更新します。この差が、SARSA との対比でいちばん重要です。

検証 1: Q学習の更新準備

Q学習の更新式確認のため、割引率と報酬列を先に定義します。

rewards = [0.0, 1.0, 0.2, 1.4]
gamma = 0.91
g = 0.0
for r in reversed(rewards):
    g = r + gamma * g
print('task = q-learning', 'reference_return=', round(g, 6))

次セルでmax演算を含む更新規則に接続します。

検証 2: ベルマン更新を1回行う

次に、価値更新を1ステップだけ計算します。1回更新でも、再帰構造の意味は十分に見えてきます。

v_next = {'s0': 0.4, 's1': 0.8}
reward = {'left': 0.2, 'right': 1.0}
trans = {'left': 's0', 'right': 's1'}
v_s = max(reward[a] + gamma * v_next[trans[a]] for a in ['left', 'right'])
print('updated V(s)=', round(v_s, 6))

ベルマン更新は『今の価値』を『次状態の価値』で再定義する操作です。この再帰が強化学習の中心です。

定義の確認

  1. Q(s,a)Q(s,a)+α[r+γmaxaQ(s,a)Q(s,a)]Q(s,a) \leftarrow Q(s,a) + \alpha[r + \gamma\max_{a'}Q(s',a') - Q(s,a)]

検証 3: Q値更新を比較する

ここで Q学習の更新式をコードに写し、数値の動きを確認します。式を読むだけでは掴みにくい感覚を得る段階です。

Q = {('s0','left'): 0.3, ('s0','right'): 0.1, ('s1','left'): 0.5, ('s1','right'): 0.7}
alpha = 0.2
r, s, a, s_next = 1.0, 's0', 'right', 's1'
td_target = r + gamma * max(Q[(s_next,'left')], Q[(s_next,'right')])
Q[(s,a)] += alpha * (td_target - Q[(s,a)])
print('Q(s0,right)=', round(Q[(s,a)], 6))

更新後の値が過去の値とどれだけ違うかは、学習率と TD 誤差で決まります。ここが調整ポイントです。

検証 4: 探索と活用の切り替え

次に、探索率を変えたときの行動選択を見ます。探索不足は局所最適に閉じる典型的な原因です。

import random

random.seed(7)

def choose_action(q_left, q_right, epsilon):
    if random.random() < epsilon:
        return random.choice(['left', 'right'])
    return 'left' if q_left >= q_right else 'right'

samples_high_eps = [choose_action(0.4, 0.7, 0.5) for _ in range(8)]
samples_low_eps = [choose_action(0.4, 0.7, 0.1) for _ in range(8)]
print('epsilon=0.5 ->', samples_high_eps)
print('epsilon=0.1 ->', samples_low_eps)

探索率は固定せず、学習段階に応じて減衰させるのが一般的です。初期は広く探索し、後半で活用へ寄せます。

検証 5: 方策評価の簡易チェック

最後に、方策の平均報酬を簡易的に比較します。アルゴリズムの評価は、更新式だけでなく結果の検証が不可欠です。

episode_rewards = [1.2, 0.8, 1.5, 1.1, 1.4]
avg_reward = sum(episode_rewards) / len(episode_rewards)
variance = sum((r - avg_reward) ** 2 for r in episode_rewards) / len(episode_rewards)
print('avg =', round(avg_reward, 4))
print('var =', round(variance, 4))

平均だけでなく分散を見ると、方策の安定性も評価できます。実運用ではこの二軸が重要です。

この節の要点

  1. Q 学習は Q(s,a) を直接学ぶ。
  2. 目標値に max_{a'} Q(s',a') を入れるので off-policy になる。
  3. max の強気さが、後で SARSA との振る舞いの差を生む。