時間差分学習
前回の記事では、モンテカルロ法によるモデルフリー学習を学びました。モンテカルロ法は実際の経験から学習できる優れた手法ですが、エピソードが終了するまで学習できないという大きな制約がありました。
今回は、この制約を克服する時間差分学習(Temporal-Difference Learning;TD学習) について学びます。TD学習の中核となる概念がTD誤差です。この記事では、TD誤差が何を表し、どのように学習を可能にするのかを深く理解していきましょう。
なぜTD学習が必要なのか
モンテカルロ法の限界を振り返る
モンテカルロ法では、状態
これをインクリメンタルに更新する場合、以下のような形になります:
この式は「現在の推定値」と「新しいサンプル」の差に重み
学習率(ステップサイズ)の導入
上記の式では、サンプル数が増えるにつれて重み
しかし、以下のような状況では別のアプローチが必要になることがあります:
- 非定常環境:環境が時間とともに変化する場合、古いデータの影響を減らしたい
- 早期の学習:初期段階でより大きなステップで学習したい
-
計算の簡潔性:訪問回数
を記録する必要がないn
そこで、固定の学習率
この形式では:
-
:更新しない(学習しない)\alpha = 0 -
:完全に新しい値で置き換える\alpha = 1 -
:古い推定値と新しいサンプルを混ぜ合わせる0 < \alpha < 1
モンテカルロ法でもこの形式を使うことができますが、依然として
このアプローチの問題点:
- 待機時間:エピソード終了まで更新不可能
- メモリ使用:エピソード全体を保存する必要
- 継続的タスク:明確な終了がない問題に適用不可
解決策:時間差分学習
これらの問題を解決するのが時間差分学習(TD学習) です。TD学習の核心は、「エピソード全体の結果を待たずに、1ステップ先の情報だけで学習する」ことです。
基本的なアイデアは次の通りです:
- 現在の状態の価値 = 即時報酬 + 次の状態の推定価値(割引後)
これにより、各ステップで即座に学習が可能になります。
TD学習の直感的理解
通勤時間予測で理解するTD学習
毎日の通勤時間予測を例に、モンテカルロ法とTD学習の違いを見てみましょう。
シナリオ:家から会社まで、通常60分かかると予測している
モンテカルロ法の場合:
家を出発(予測:60分)
↓ [実際の通勤]
会社到着(実際:70分)
→ 予測を更新:次回は65分と予測
- 会社に着くまで予測を更新できない
TD学習の場合:
家を出発(全体予測:60分)
↓ 10分
最初の駅(ここまで10分、残り予測:50分)
→ でも駅の掲示板を見ると「会社まで55分」と表示
→ 予測誤差:5分の過小評価が判明
→ この時点で全体予測を修正:65分
- 各地点で予測を更新できる
TD誤差とは何か
上記の例で、「残り50分と思っていたが、実際は55分だった」という5分の差がTD誤差です。
より正確に言うと:
- TD誤差 = (実際に経験した即時的な結果)+(次の地点での予測)-(現在の予測)
この誤差を使って、現在の予測を改善します。
ブートストラップ:予測で予測を改善
TD学習の特徴的な概念がブートストラップ(Bootstrap) です。
強化学習におけるブートストラップとは何か
通常、何かを推定する際は「真の値」を使います。しかしブートストラップでは、別の「推定値」を使って推定を行います。通勤時間の例で説明すると:
ブートストラップを使わない場合(モンテカルロ法):
- 家での予測:60分
- 会社到着時の実際の時間:70分(これが真の値)
- 更新:予測を真の値(70分)に近づける
ブートストラップを使う場合(TD学習):
- 家での予測:60分
- 駅での状況:
- ここまで10分かかった(実際の値)
- 駅から会社まで55分の予測(これは推定値)
- 合計:10分 + 55分 = 65分
- 更新:予測を65分(推定値を含む)に近づける
なぜ「ブートストラップ」と呼ぶのか
「ブートストラップ」は「自分の靴紐を引っ張って自分を持ち上げる」という慣用句から来ています。自分自身の推定値を使って、自分自身を改善するという自己参照的な性質を表しています。
ブートストラップの利点と課題
利点:
- 即座の学習:完全な結果を待たずに学習開始
- メモリ効率:過去の全情報を保存する必要がない
- 継続的タスク対応:終了のない問題でも学習可能
課題:
- バイアス:推定値に誤差があると、それが伝播する
- 初期値依存:最初の推定が悪いと収束が遅い
- 理論的複雑性:収束の保証がより複雑
TD誤差の数学的定義
基本的な定義
時刻
TD誤差の解釈
TD誤差の各項の意味を詳しく見てみましょう:
-
:現在の予測V(s_t) - 状態
から得られる累積報酬の現在の推定値s_t
- 状態
-
:新しい情報に基づく予測r_{t+1} + \gamma V(s_{t+1}) - 実際に得られた即時報酬
r_{t+1} - 次状態の価値の割引値
\gamma V(s_{t+1}) - これらの和が「1ステップ進んだ後の新しい見積もり」
- 実際に得られた即時報酬
-
:予測誤差\delta_t - 正の値:現在の予測が過小評価
- 負の値:現在の予測が過大評価
- ゼロ:予測が正確
ベルマン方程式との関係
TD誤差とベルマン方程式の関係を理解するために、まずベルマン方程式を思い出しましょう。
ベルマン方程式の復習
前々回の記事で学んだベルマン方程式は、「ある状態の価値」と「次の状態の価値」の関係を表します:
これは「状態
現在の推定値での書き換え
今、私たちが持っている価値関数の推定値を
実際の経験との比較
実際に行動して得られた結果は:
- 即時報酬:
(実際の値)r_{t+1} - 次状態:
(実際に遷移した状態)s_{t+1} - 次状態の推定価値:
V(s_{t+1})
これらから、「もし今の推定が正しければ、
TD誤差の意味づけ
TD誤差は、この「あるべき値」と「現在の推定値」の差です:
つまり、TD誤差は:
- ベルマン方程式の残差:現在の推定値がベルマン方程式をどれだけ満たしていないか
- 推定の改善方向:どちらの方向に修正すべきかを示す
期待値とTD誤差
もし
つまり、真の価値関数では、TD誤差の期待値はゼロになります。これが、TD誤差を使って価値関数を改善できる理論的根拠です。
TD(0)アルゴリズム
基本的な更新式
TD誤差を使った最も基本的な学習アルゴリズムがTD(0) です。
TD(0)の「0」は何を意味するのか
TD(0)の括弧内の「0」は、何ステップ先まで実際の報酬を使うかを表します:
-
TD(0):0ステップ先まで実際の報酬を使用
- つまり、1ステップ目(
)のみ実際の報酬r_{t+1} - それ以降は推定値
を使用V(s_{t+1})
- つまり、1ステップ目(
-
TD(1):1ステップ先まで実際の報酬を使用
r_{t+1} + \gamma r_{t+2} + \gamma^2 V(s_{t+2})
-
TD(n):nステップ先まで実際の報酬を使用
r_{t+1} + \gamma r_{t+2} + ... + \gamma^n r_{t+n+1} + \gamma^{n+1} V(s_{t+n+1})
つまり、TD(0)は:
- 最も即座の更新:1ステップごとに学習
- 最も推定に依存:次の状態の価値は推定値に依存
- 最もシンプル:実装が最も簡単
アルゴリズムの流れ
- 状態
で行動s_t を実行a_t - 報酬
と次状態r_{t+1} を観測s_{t+1} - TD誤差
を計算\delta_t - 価値関数を更新
- 次のステップへ(エピソード終了を待たない)
Pythonによる実装
import numpy as np
from collections import defaultdict
class TD0Learning:
"""TD(0)による価値関数学習"""
def __init__(self, env, alpha=0.1, gamma=0.9):
self.env = env
self.alpha = alpha # 学習率
self.gamma = gamma # 割引率
self.V = defaultdict(float) # 価値関数
def td_error(self, state, reward, next_state):
"""TD誤差を計算"""
return reward + self.gamma * self.V[next_state] - self.V[state]
def update_value(self, state, td_error):
"""TD誤差を使って価値関数を更新"""
self.V[state] += self.alpha * td_error
def learn_episode(self, policy):
"""1エピソードの学習(オンライン更新)"""
state = self.env.reset()
episode_reward = 0
while True:
# 行動選択
action = policy(state)
# 環境との相互作用
next_state, reward, done = self.env.step(state, action)
episode_reward += reward
# TD誤差の計算
if done:
# 終端状態の価値は0
td_error = reward - self.V[state]
else:
td_error = self.td_error(state, reward, next_state)
# 価値関数の更新(エピソード中に即座に更新)
self.update_value(state, td_error)
if done:
break
state = next_state
return episode_reward
def learn(self, policy, num_episodes=1000, verbose=False):
"""複数エピソードの学習"""
episode_rewards = []
for episode in range(num_episodes):
reward = self.learn_episode(policy)
episode_rewards.append(reward)
if verbose and episode % 100 == 0:
avg_reward = np.mean(episode_rewards[-100:])
print(f"Episode {episode}: Average reward = {avg_reward:.2f}")
return self.V, episode_rewards
実装のポイント
- オンライン更新:各ステップで即座に価値関数を更新
- 終端状態の扱い:終端状態では次状態の価値を0として計算
- メモリ効率:エピソード全体を保存する必要がない
モンテカルロ法との比較
更新タイミングの違い
TD学習とモンテカルロ法の最も重要な違いは更新のタイミングです:
項目 | モンテカルロ法 | TD学習 |
---|---|---|
更新タイミング | エピソード終了後 | 各ステップ |
必要な情報 | 完全な累積報酬 |
1ステップの情報 |
学習の即時性 | 遅延学習 | オンライン学習 |
メモリ使用 | エピソード全体を保存 | 現在の状態のみ |
継続的タスク | 適用不可 | 適用可能 |
推定のターゲットの違い
両手法の更新式を比較してみましょう:
モンテカルロ法:
TD(0):
- MC法:実際の累積報酬
をターゲットとするG_t - TD法:ブートストラップ推定値
をターゲットとするr_{t+1} + \gamma V(s_{t+1})
バイアスと分散のトレードオフ
TD学習とモンテカルロ法は、バイアスと分散のトレードオフの関係にあります:
モンテカルロ法:
- バイアスなし:実際の累積報酬を使用
- 高分散:エピソードの確率的変動の影響を受けやすい
- 多くのサンプルが必要だが、真の値に収束
TD学習:
- バイアスあり:ブートストラップによる推定値を使用
- 低分散:1ステップの変動のみの影響
- 少ないサンプルで安定するが、初期値の影響を受ける
具体例での比較
グリッドワールドでの学習を例に、両手法の違いを見てみましょう:
def compare_mc_td(env, policy, num_episodes=1000):
"""モンテカルロ法とTD(0)の比較"""
# モンテカルロ法
mc_values = defaultdict(float)
mc_returns = defaultdict(list)
# TD(0)
td_learner = TD0Learning(env, alpha=0.1, gamma=0.9)
mc_errors = []
td_errors = []
for episode in range(num_episodes):
# モンテカルロ法のエピソード生成
mc_episode = []
state = env.reset()
while True:
action = policy(state)
next_state, reward, done = env.step(state, action)
mc_episode.append((state, action, reward))
# TD学習は各ステップで更新
if done:
td_error = reward - td_learner.V[state]
else:
td_error = td_learner.td_error(state, reward, next_state)
td_learner.update_value(state, td_error)
if done:
break
state = next_state
# モンテカルロ法の更新(エピソード終了後)
G = 0
for t in reversed(range(len(mc_episode))):
state, _, reward = mc_episode[t]
G = reward + 0.9 * G
mc_returns[state].append(G)
mc_values[state] = np.mean(mc_returns[state])
# 収束の追跡(真の価値が既知の場合)
if hasattr(env, 'true_values'):
mc_error = compute_error(mc_values, env.true_values)
td_error = compute_error(td_learner.V, env.true_values)
mc_errors.append(mc_error)
td_errors.append(td_error)
return mc_values, td_learner.V, mc_errors, td_errors
TD学習の制御問題への拡張
制御問題とは何か
これまでのTD(0)では、与えられた方策
方策評価 vs 制御問題
-
方策評価(Policy Evaluation):
- 「この方策はどれくらい良いか?」を評価
- 固定された方策
の価値\pi を求めるV^\pi(s) - これまで学んだTD(0)の範囲
-
制御問題(Control Problem):
- 「どうやったら最良の方策を見つけられるか?」を解決
- 最適方策
そのものを探索・学習\pi^* - 方策の評価と改善を繰り返す
なぜ行動価値関数Q(s,a)が必要か
制御問題では、行動を選択する必要があります。状態価値関数
-
:状態V(s) の価値(行動は固定方策で決まる)s -
:状態Q(s,a) で行動s を取った時の価値(行動選択に使える)a
最適行動:
SARSA:方策オン型TD制御
TD誤差の概念を行動価値関数
SARSAという名前は、更新に必要な要素
Q学習:方策オフ型TD制御
Q学習は、次状態での最大Q値を使用する方策オフ型アルゴリズムです:
SARSAとQ学習の違い
両者の本質的な違いは、次状態の価値をどう見積もるかにあります:
-
SARSA:実際に選択される行動
の価値を使用(方策オン型)a_{t+1} - Q学習:最適な行動の価値を使用(方策オフ型)
方策オン型・オフ型の判定基準
アルゴリズムが方策オン型か方策オフ型かは、以下の基準で判定できます:
-
方策オン型:更新に使う行動を、現在の方策でサンプリング(実際に選択)
- 例:SARSAでは
を現在の方策a_{t+1} でサンプリング\pi
- 例:SARSAでは
-
方策オフ型:更新に使う行動を、方策以外のルール(最大化など)で決定
- 例:Q学習では
という最大化で決定\max_a Q(s_{t+1}, a)
- 例:Q学習では
なお、n-step Q学習のように、両方の性質を持つハイブリッドな手法も存在します。その場合は、具体的な更新式を見て判断する必要があります。
この違いは、実際の学習結果に大きな影響を与えます。有名な崖歩き問題(Cliff Walking) を例に説明しましょう。
崖歩き問題のシナリオ
崖の縁を歩くロボットを想像してください。スタート地点からゴールまで、2つのルートがあります。崖の縁を通る最短ルートは各ステップで-1の報酬ですが、もしランダムな行動で崖に落ちると-100の大きなペナルティを受けます。一方、崖から離れた安全なルートは遠回りなので、各ステップで-2の報酬となります。
学習中、ε-グリーディ方策(ε=0.1)により、10%の確率でランダムな行動を取るとします。
SARSAが学ぶもの
SARSAは実際に取る行動を考慮します。崖の縁では、10%の確率でランダム行動により落下する可能性があることを学習に組み込みます。その結果、「崖の縁は期待値的にリスクが高い」と判断し、遠回りでも安全なルートを選ぶようになります。これは、実際の実行時の方策(探索を含む)での期待値を最大化しているためです。
Q学習が学ぶもの
一方、Q学習は「もし最適に行動したら」という仮定で学習します。最適な行動を取れば崖に落ちることはないので、崖の縁の最短ルートが最も価値が高いと学習します。しかし実際の実行時には探索でランダム行動を取るため、学習した最適ルートと実際の性能に乖離が生じる可能性があります。
この違いから、SARSAは「実行時の現実的な性能」を重視し、Q学習は「理想的な最適性能」を追求すると言えます。どちらが良いかは、問題設定や安全性の要求によって変わります。
実践例:TD誤差の可視化
これから実演すること
この実践例では、TD誤差が学習過程でどのように変化するかを可視化し、TD学習の動作を定量的に理解します。
目的:
- TD誤差が学習の進行とともにどう減少するかを確認
- 異なる状態でのTD誤差の変化パターンを比較
- TD学習の収束過程を視覚的に理解
使用する環境と手法:
- 前回記事のグリッドワールド環境(4×4マス)
- ランダム方策による探索
- TD(0)アルゴリズムで価値関数を学習
期待される結果:
- 学習初期:大きく不安定なTD誤差(価値関数の推定が不正確)
- 学習進行:TD誤差の減少と安定化(推定の改善)
- 収束時:TD誤差がほぼゼロに近づく(ベルマン方程式を満たす)
実装の概要:
- TD誤差追跡クラス:各ステップのTD誤差を記録・蓄積
- 可視化機能:学習曲線とTD誤差の推移をグラフ化
- 統計分析:学習初期と終期のTD誤差を定量比較
グリッドワールドでのTD誤差追跡
class TDErrorTracker:
"""TD誤差の追跡と可視化"""
def __init__(self, env, alpha=0.1, gamma=0.9):
self.env = env
self.alpha = alpha
self.gamma = gamma
self.V = defaultdict(float)
self.td_errors = [] # TD誤差の履歴
self.state_td_errors = defaultdict(list) # 状態ごとのTD誤差
def learn_with_tracking(self, policy, num_episodes=100):
"""TD誤差を追跡しながら学習"""
for episode in range(num_episodes):
state = self.env.reset()
episode_td_errors = []
while True:
action = policy(state)
next_state, reward, done = self.env.step(state, action)
# TD誤差の計算
if done:
td_error = reward - self.V[state]
else:
td_error = reward + self.gamma * self.V[next_state] - self.V[state]
# 記録
episode_td_errors.append(abs(td_error))
self.state_td_errors[state].append(td_error)
# 更新
self.V[state] += self.alpha * td_error
if done:
break
state = next_state
# エピソードの平均TD誤差
self.td_errors.append(np.mean(episode_td_errors))
def visualize_td_errors(self):
"""TD誤差の推移を可視化"""
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 4))
# エピソードごとの平均TD誤差
plt.subplot(1, 2, 1)
plt.plot(self.td_errors)
plt.title('平均TD誤差の推移')
plt.xlabel('エピソード')
plt.ylabel('平均|TD誤差|')
plt.yscale('log')
plt.grid(True)
# 状態ごとのTD誤差分布
plt.subplot(1, 2, 2)
states = list(self.state_td_errors.keys())[:4] # 最初の4状態
for i, state in enumerate(states):
errors = self.state_td_errors[state]
if len(errors) > 10:
# 移動平均
window = 10
moving_avg = np.convolve(errors, np.ones(window)/window, mode='valid')
plt.plot(moving_avg, label=f'状態{state}')
plt.title('状態別TD誤差の推移')
plt.xlabel('訪問回数')
plt.ylabel('TD誤差')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# 使用例
def demonstrate_td_error():
"""TD誤差の振る舞いを実演"""
# 環境とトラッカーの作成
env = GridWorld() # 前回記事の環境を使用
tracker = TDErrorTracker(env)
# ランダム方策で学習
def random_policy(state):
return np.random.choice(env.get_actions(state))
# 学習と追跡
tracker.learn_with_tracking(random_policy, num_episodes=500)
# 可視化
tracker.visualize_td_errors()
# TD誤差の統計
print("TD誤差の統計:")
print(f"初期10エピソードの平均: {np.mean(tracker.td_errors[:10]):.4f}")
print(f"最後10エピソードの平均: {np.mean(tracker.td_errors[-10:]):.4f}")
print(f"減少率: {(1 - tracker.td_errors[-1]/tracker.td_errors[0])*100:.1f}%")
TD誤差が示すもの
TD誤差の振る舞いから、学習の進行状況を理解できます:
-
学習初期:大きなTD誤差
- 価値関数の推定が不正確
- 予測と実際の差が大きい
-
学習中期:TD誤差の減少
- 価値関数が改善
- ベルマン方程式に近づく
-
学習後期:小さなTD誤差
- 価値関数が収束に近い
- 予測が正確
まとめと次のステップ
本記事で学んだこと
-
TD誤差の本質
- 予測と新しい情報の差
- ベルマン方程式の誤差項
- 学習シグナルとしての役割
-
TD学習の利点
- オンライン学習可能
- メモリ効率的
- 継続的タスクに適用可能
-
ブートストラップ
- 予測を使って予測を改善
- バイアスと分散のトレードオフ
-
制御への拡張
- SARSA(方策オン型)
- Q学習(方策オフ型)
TD誤差の重要性
TD誤差は強化学習において中心的な役割を果たします:
- 学習の駆動力:価値関数の改善方向を示す
- 収束の指標:学習の進行状況を測る
- 発展的手法の基礎:多くの現代的手法がTD誤差を活用
次回予告:より高度なTD学習手法
TD(0)の理解を基に、次回はより高度なTD学習手法を学びます:
次回の記事:多段階TD学習とEligibility Trace
- n-step TD学習:複数ステップの実際の報酬を使用
- TD(λ):指数的減衰による過去の状態への信用割り当て
- Eligibility Trace:効率的な実装とメモリの仕組み
これらの手法は、TD(0)とモンテカルロ法の中間的な性質を持ち、学習の効率と安定性を大幅に改善します。
強化学習学習ロードマップ
この記事シリーズでは、以下の順序で強化学習の核心概念を学んでいきます:
- ✅ 基礎理論:MDP、価値関数、ベルマン方程式(完了)
- ✅ モンテカルロ法:モデルフリー学習の出発点(完了)
- ✅ 時間差分学習:TD誤差とTD(0)、SARSA、Q学習(本記事)
- 🔄 高度なTD手法:n-step TD、TD(λ)、Eligibility Trace(次回)
- 🔜 深層強化学習への橋渡し:関数近似と深層学習の導入
- 🔜 方策ベース手法:Policy Gradient、REINFORCE
- 🔜 Actor-Criticアーキテクチャ:価値ベースと方策ベースの統合
- 🔜 現代的手法:PPO等の実用的アルゴリズム
TD誤差という概念は、これから学ぶすべての高度な手法の基盤となります。特に深層強化学習でも、TD誤差は価値関数の学習信号として中心的な役割を果たし続けます。
Discussion