Philarium

Connect4 DQN

深層強化学習による四目並べAI

強化学習の基礎を実装レベルで体得するため、三目並べの表形式Q学習から出発したプロジェクトです。 盤面が6×7に広がるConnect4(四目並べ)では状態数が爆発し、Q値をテーブルで持つ手法は破綻します。 そこでニューラルネットワークでQ関数を近似するDQNへ発展させ、 さらにQ値の過大評価バイアスを緩和するDouble DQNまで、 深層強化学習の系譜をたどり、PyTorchでスクラッチ実装しました。

手法

深層強化学習を安定させるための主要コンポーネントを、ライブラリに頼らず自前で実装しています。

Experience Replay

遷移(s, a, s', r)を容量10,000のリングバッファに蓄積し、ランダムサンプリングしたミニバッチで学習。 時系列の相関を断ち切り、勾配更新を安定化させる。

Target Network

TD目標を生成する専用ネットワークを分離し、一定エピソードごとに重みを同期。 「自分で作った目標を自分で追いかける」ことによる発散を抑制。

Double DQN

行動の「選択」をpolicy_net、「評価」をtarget_netに分離。 max演算子が持つQ値の過大評価バイアスを緩和する、DQNの代表的な改良手法。

ε-greedy 指数減衰

探索率εを0.9から0.05へ指数的に減衰。 学習序盤は広く盤面を探索し、終盤は学習済みの方策の活用へ滑らかに移行する。

コード例

学習ステップの核心部分です。Double DQNの「選択と評価の分離」がコードにそのまま現れています。

def optimize_model():
    """Experience Replayからミニバッチを取り出し、Double DQNで学習を1ステップ進める"""
    if len(memory) < BATCH_SIZE: return
    transitions = memory.sample(BATCH_SIZE)
    batch = Transition(*zip(*transitions))

    non_final_mask = torch.tensor(
        tuple(map(lambda s: s is not None, batch.next_state)),
        device=device, dtype=torch.bool)
    non_final_next_states = torch.cat([s for s in batch.next_state if s is not None])
    state_batch = torch.cat(batch.state)
    action_batch = torch.cat(batch.action)
    reward_batch = torch.cat(batch.reward)

    # 現在のQ値: policy_netが実際に取った行動の価値
    state_action_values = policy_net(state_batch).gather(1, action_batch)

    next_state_values = torch.zeros(BATCH_SIZE, device=device)
    with torch.no_grad():
        # === Double DQN の核心 ===
        # 1. 行動選択: policy_net が次の盤面で最善だと思う行動を選ぶ
        next_state_actions = policy_net(non_final_next_states).max(1)[1].unsqueeze(1)
        # 2. 価値評価: target_net が、policy_net の選んだ行動の価値を評価する
        #    → 選択と評価を分離し、max演算子によるQ値の過大評価バイアスを緩和
        next_state_values[non_final_mask] = (
            target_net(non_final_next_states).gather(1, next_state_actions).squeeze()
        )

    # TD目標: r + γ * Q_target(s', argmax_a Q_policy(s', a))
    expected_state_action_values = (next_state_values * GAMMA) + reward_batch

    # Huber損失 + 勾配クリッピングで外れ値に頑健な更新
    loss = nn.SmoothL1Loss()(state_action_values,
                             expected_state_action_values.unsqueeze(1))
    optimizer.zero_grad()
    loss.backward()
    torch.nn.utils.clip_grad_value_(policy_net.parameters(), 100)
    optimizer.step()

学習と対戦

学習設定

AdamW(amsgrad) + Huber損失(SmoothL1) + 勾配クリッピングという頑健な構成。 報酬は勝ち+1 / 負け-1のスパースな設計で、割引率γ=0.99により長手数の詰み筋まで価値を伝播させる。

チェックポイント再開

学習済みモデル(.pth)の保存・読み込みに対応し、中断からの追加学習が可能。 エピソード数を指定して段階的にAIを育てられる。

人間 vs AI 対戦モード

学習したモデルとCLI上で対戦できるインターフェースを実装。 学習の進み具合を「実際に対局して」体感的に評価できる。

学び

表形式Q学習からDQN、Double DQNへと段階的に発展させたことで、 「状態空間の爆発を関数近似で乗り越える」という深層強化学習の本質と、 ブートストラップ学習に固有の不安定性(目標の振動・価値の過大評価)への対策を、論文の追実装を通して体系的に理解しました。 Experience ReplayやTarget Networkを自分の手で書いたことで、 DQN(2015)からDouble DQN(2016)に至る改良の必然性を実装レベルで説明できるようになったプロジェクトです。