暇さえあればアルゴリズムいじり

暇があればアルゴリズムいじり

Obsessed with algorithms whenever I have a free moment.

AI & IT Engineer / Father of 3

"Dream shall be realized with dream — Always tinkering with algorithms"

迷路問題を強化学習で解く(5)

先日の迷路問題について迷路が変動するような環境下でもゴールを目指せるエージェントの構築を検討していきます。

今回はまず、画像入力のTransformerモデルでDDQNで迷路環境の学習が可能かの検証を行っていきます。

本日テーマ:

DDQN×画像入力のTransformerにより静的迷路のゴールが出来るか確認

 

前回記事:前回行ったことは以下記事をご参照ください

yoshishinnze.hatenablog.com

DDQNについて

まずは今回利用する予定の強化学習アルゴリズムであるDDQNとはどんなアルゴリズムであるかについて説明を行います。

DDQN(Double Deep Q-Network)は、DQN(Deep Q-Network)の「過大評価バイアス」を軽減するための改良手法です。

以下に、DDQNの概要・仕組み・メリットを整理します。

1. DDQNの位置づけ

  • ベース: DQN(Deep Q-Network)
  • 目的: Q学習における「最大化バイアス(max overestimation bias)」を抑える
  • 特徴: 行動選択と価値評価を分離する「Double Q-learning」の考え方を深層学習に適用

2. DQNの問題点:最大化バイアス

通常のDQNの更新式(1ステップ)

Target=r+γmaxaQtarget(s,a)\text{Target} = r + \gamma \max_{a'} Q_{\text{target}}(s', a')

ここで、

  • Qtarget Q_{\text{target}} :ターゲットネットワーク
  • maxa \max_{a'} :次状態 s s' で最も高いQ値を持つ行動を選択

問題点

  • ノイズや推定誤差により、ある行動のQ値が偶然高く見積もられると、その行動が過大評価される
  • これが繰り返されると、Q値が全体的に過大評価される「最大化バイアス」が生じます。
  • 結果として、学習が不安定になったり、収束が遅くなったりします。

: 3つの行動のQ値推定

次状態 s′s's′ での真のQ値が以下の通りだとします(真の値は未知)。

行動Aの真のQ値: 5 行動Bの真のQ値: 3 行動Cの真のQ値: 1

しかし、推定にはノイズが乗るため、観測されるQ値は以下のようになることがあります。

行動Aの推定Q値: 5.2(少し高め) 行動Bの推定Q値: 3.5(少し高め) 行動Cの推定Q値: 4.0(ノイズで大きく上振れ)

このとき、maxを取ると行動Cが選ばれ、Q値は4.0として扱われます。しかし真の値は1なので、 3.0も過大評価されています。 このように、「たまたまノイズで高く出た行動」がmaxで選ばれることで、真の値よりも大きく見積もられる現象が起こります。 本当に良い行動ではなくノイズに引っ張られることで、あるべき姿からどんどん乖離していきます。 また一度乖離すると中々元の状態に戻れません。

3. DDQNのアイデア:行動選択と評価の分離

DDQNでは、「どの行動を取るか」の決定「その行動の価値を評価する」ことを別々のネットワークで行います。

DDQNの更新式

Target=r+γQtarget(s,argmaxaQonline(s,a))\text{Target} = r + \gamma Q_{\text{target}}\left(s', \arg\max_{a'} Q_{\text{online}}(s', a')\right)

  • 行動選択(どの行動を取るか): オンラインネットワーク Qonline Q_{\text{online}} を使用
  • 価値評価(その行動の価値はいくらか): ターゲットネットワーク Qtarget Q_{\text{target}} を使用

評価者は急な変動に追従しません。少し良くなったということを認識していつもより良い加点を行うくらいです。 これにより、偶然高い値が出た行動を過大評価しにくくなります。

4. DDQNの具体的な手順

  1. 現在の状態 s s から、オンラインネットワーク Qonline Q_{\text{online}} で行動 a a を選択(ε-greedyなど)。
  2. 環境から r,s r, s' を得る。
  3. 次状態 s s' での行動選択:
    • オンラインネットワーク Qonline Q_{\text{online}} を用いて、最適行動 a=argmaxaQonline(s,a) a'^* = \arg\max_{a'} Q_{\text{online}}(s', a') を決定。
  4. 価値評価
    • ターゲットネットワーク Qtarget Q_{\text{target}} を用いて、Qtarget(s,a) Q_{\text{target}}(s', a'^*) を計算。
  5. ターゲット値

    Target=r+γQtarget(s,a)\text{Target} = r + \gamma Q_{\text{target}}(s', a'^*)

  6. オンラインネットワーク Qonline Q_{\text{online}} を、Targetに近づくように更新。
  7. 一定ステップごとにターゲットネットワークをオンラインネットワークで更新。

5. DDQNのメリット

(1) 過大評価バイアスの軽減

  • 行動選択と価値評価を分離することで、ノイズによる過大評価を抑制
  • Q値の推定がより正確になり、学習が安定します。

(2) 学習の安定性向上

  • DQNに比べて、Q値の発散や振動が起こりにくい
  • 特にノイズの多い環境や複雑なタスクで効果を発揮します。

(3) 実装の容易さ

  • DQNからの変更点はターゲット値の計算部分だけで、実装が比較的簡単です。
  • 追加のハイパーパラメータはほぼ不要。

6. DDQNのデメリット・注意点

(1) 計算コストのわずかな増加

  • 次状態での行動選択にオンラインネットワークを使用するため、わずかに計算量が増える
  • ただし、現代のGPU環境では無視できるレベルです。

(2) すべての問題で劇的に改善するわけではない

  • シンプルな環境では、DQNとDDQNの差が小さい場合もあります。
  • しかし、複雑な環境やノイズの多い環境では効果が顕著です。

画像入力のTransformerモデル

迷路の画像を入力とし、Transformerモデルで行動を選択する場合の特徴期待できる効果について整理します。

1. モデルの特徴

(1) 画像としての迷路表現

  • 迷路を2次元グリッドの「画像」として扱います。
  • 各チャネルに異なる情報を割り当て(例:壁・スタート・ゴール・エージェント位置)、多チャンネル画像として入力します。
  • これにより、空間的な構造(どこに壁があるか、ゴールまでの経路はどうか)を視覚的に捉えられます。

(2) Transformerによるトークン化

  • 画像を小さなパッチ(またはセル)に分割し、それぞれを「トークン」として扱います。
  • 各トークンを埋め込みベクトルに変換し、位置エンコーディングを加えることで、空間位置をモデルが認識できるようにします。

(3) Self-Attentionによる関係性の学習

  • TransformerのSelf-Attentionにより、任意の2つのセル間の関係を直接学習します。
  • 例:
    • 「スタートセル」と「ゴールセル」の関係
    • 「現在位置」と「周囲の壁」の関係
    • 「遠くの分岐点」と「現在の経路選択」の関係

(4) 出力:行動の確率分布またはQ値

  • Transformerの出力をMLPに通し、各行動の確率分布(方策)またはQ値を出力します。
  • これにより、画像から直接行動を決定する「端から端(end-to-end)」の学習が可能です。

2. 期待できる効果

(1) 空間構造の理解

  • CNNと同様に局所的な壁のパターンを捉えつつ、Transformerにより長距離の依存関係(スタート〜ゴールの経路) も直接扱えます。
  • これにより、迷路全体の構造をより深く理解できます。

(2) 部分観測への適応

  • エージェントが周囲のみを観測する「部分観測」設定でも、Transformerが過去の観測履歴を統合し、迷路全体のメンタルマップを構築する助けになります。
  • これはRNNの代替としても有効です。

(3) ランダム迷路への汎化

  • エピソードごとに迷路が変わる場合、Transformerは特定の迷路を暗記するのではなく、迷路の一般構造(壁の配置パターン、経路の性質)を学習しやすいです。
  • これにより、訓練で見ていない新しい迷路にも適応できる「汎化性能」が期待できます。

(4) マルチエージェント環境での活用

  • マルチエージェント迷路では、Transformerが他エージェントの位置や行動の関係を捉えるのに適しています。
  • 各エージェントの観測画像をトークンとして統合し、協調や競合のパターンを学習できます。

(5) 探索の効率化

  • Self-Attentionにより、ゴールまでの経路候補未探索領域の重要性を推定しやすくなります。
  • これにより、無駄な探索を減らし、効率的にゴールへ向かう方策を学習できる可能性があります。

3. 注意点(期待しすぎない方がよい点)

(1) 小規模迷路では過剰な可能性

  • 5×5のような小規模迷路では、状態空間が非常に小さいため、単純なQ学習や小さなMLPでも十分なことが多いです。
  • Transformerはパラメータ数が多く、計算コストも高いため、過学習や計算の無駄になる可能性があります。

(2) 学習データと計算資源

  • Transformerは多くのデータと計算資源を必要とします。
  • 迷路が単純でエピソード数が少ない場合、学習が進みにくいこともあります。

(3) 実装の複雑さ

  • 画像のパッチ分割、位置エンコーディング、Transformerの層の設計など、実装がやや複雑です。
  • 初学者にとっては、シンプルな手法から始める方が理解しやすい場合もあります。

実装

実装コードは以下レポジトリに保管しています。

https://github.com/Shinichi0713/Reinforce-Learning-Study/tree/main/basic/11_maze

画像入力+Transformerで迷路を解くために必要な、環境コードの実装法バッファーの実装法のキーポイントを整理します。

1. 環境コードの実装法(画像観測の提供)

(1) 迷路を画像として表現する 迷路を多チャンネル画像(例:4チャンネル)として表現します。

  • チャネル0: 壁(W) = 1.0、それ以外 = 0.0
  • チャネル1: スタート(S) = 1.0、それ以外 = 0.0
  • チャネル2: ゴール(G) = 1.0、それ以外 = 0.0
  • チャネル3: エージェント位置(A) = 1.0、それ以外 = 0.0

形状は (C, H, W)(例:(4, 5, 5))とします。

(2) 画像観測を返すメソッドの追加 MazeEnv クラスに、迷路の現在状態を画像として返すメソッドを追加します。

def get_image_observation(self):
    """
    迷路を画像(NumPy配列)として返す
    形状: (4, H, W)
    """
    rows = len(self.maze)
    cols = len(self.maze[0])
    obs = np.zeros((4, rows, cols), dtype=np.float32)

    for i in range(rows):
        for j in range(cols):
            cell = self.maze[i][j]
            if cell == 'W':
                obs[0, i, j] = 1.0
            elif cell == 'S':
                obs[1, i, j] = 1.0
            elif cell == 'G':
                obs[2, i, j] = 1.0
            elif cell == '.':
                pass  # 何も立てない
    # エージェント位置
    x, y = self.state
    obs[3, x, y] = 1.0
    return obs

(3) 学習ループでの使用 学習ループでは、get_image_observation() を用いて状態を取得します。

state_img = env.get_image_observation()
action = agent.act(state_img)
next_state, reward, done = env.step(action)
next_state_img = env.get_image_observation()

2. バッファーの実装法(画像データの扱い)

(1) 経験再生バッファーの設計 画像データはメモリを消費するため、効率的な保存とサンプリングが重要です。

  • 保存: 各経験を (state_img, action, reward, next_state_img, done) として保存。
  • サンプリング: バッチサイズ分の経験をランダムに抽出し、np.stack でバッチ化。

(2) ReplayBuffer クラスの実装例

class ReplayBuffer:
    def __init__(self, capacity):
        self.buffer = deque(maxlen=capacity)

    def push(self, state, action, reward, next_state, done):
        self.buffer.append((state, action, reward, next_state, done))

    def sample(self, batch_size):
        batch = random.sample(self.buffer, batch_size)
        state, action, reward, next_state, done = zip(*batch)
        # 画像データをNumPy配列にスタック
        state = np.stack(state)
        next_state = np.stack(next_state)
        action = np.array(action)
        reward = np.array(reward)
        done = np.array(done)
        return state, action, reward, next_state, done

    def __len__(self):
        return len(self.buffer)

(3) メモリ効率の工夫(必要に応じて)

  • 画像を float32 ではなく float16uint8 で保存(品質とメモリのトレードオフ)。
  • 大きな画像の場合は、ダウンサンプリングや圧縮を検討。

3. Transformerモデルとの連携

(1) モデル入力の形状 Transformerモデルは、画像を以下の形状で受け取るように設計します。

  • 入力: (batch_size, C, H, W)(例:(32, 4, 5, 5)
  • モデル内で (batch_size, H*W, d_model) に変形し、Transformerに入力。

(2) 学習時のデータフロー

  1. バッファーから state_batch をサンプリング(形状: (batch_size, C, H, W))。
  2. state_batch をTensorに変換し、Transformerモデルに入力。
  3. 出力のQ値とターゲット値の誤差を最小化するように更新。

総括

今回は実装までしました。

CNNに比べて以下のようなメリットを見込んでいます。

1. CNNはダウンサンプリングがあり、構造上、おおよその位置推定には向いていても正確な位置予測が難しい。TransformerはPositional Encoderによりグリッドの座標の正確な位置を理解できる。

2. スタートとゴールの関係重要性と、途中に存在する障害物の関係性を理解できる。

ということで、スタートとゴールが毎回変化するような問題の場合は威力を発揮してくれると考えています。

 

とはいえ、Transformerといえど、画像入力の場合はなかなかうまくいかないと思います。 次回学習の結果を報告します。