Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Connect Four 強化学習プロジェクト

Connect系ゲーム(N目並べ)を題材に、DQN(Deep Q-Network)による強化学習を実装・実験するプロジェクトです。

当初はNumPyのみで手書き実装していましたが、学習速度のボトルネック解消のためステージ8でPyTorchに移行しました。NumPy手書きフェーズでDQNの仕組み(順伝播・逆伝播・Adam)を一から理解した上で、実用的な速度を得ています。

概要

  • 目的: 強化学習の基礎(DQN)を、Connect系ゲームで手を動かしながら学ぶ
  • 最終目標: 初心者の人間より強くなる
  • 実装: PyTorch(CPU)。ステージ7まではNumPy手書き
  • サブプロジェクト:
    • 4moku/: Connect Four(6×7, 4目)— DQN学習11ステージの試行錯誤でDQN単体の限界を確認
    • 3moku/: Connect Three(5×5, 3目)— 完了。DQNが人間に勝てるレベルに到達(4,000epで最適戦略獲得)
    • alphazero/: AlphaZero方式(MCTS+NN)— 実装完了、本格学習開始

学習ロードマップ

ステージ 内容 状態
1 vs ランダムAI 廃止(理由は後述)
2 カリキュラム学習(段階的に強い相手) 廃止(理由は後述)
3 Self-play + 混合学習 廃止(理由は後述)
4 vs ルールベースAI固定(先手固定) 廃止(理由は後述)
5 NoisyRuleBasedカリキュラム + 攻撃優先中間報酬 + プール方式Phase 4 廃止(中間報酬過大・next_stateバグ)
6 遅延コールバック方式 + 小スケール中間報酬 + カリキュラム再学習 廃止(目標高すぎ + プール不要)
7 シンプルカリキュラム + 小スケール中間報酬 廃止(中間報酬の累積で停滞)
8 カリキュラム + 中間報酬なし(勝敗±1.0のみ) 廃止(Phase 2で30000ep停滞、防御を学べず)
9 カリキュラム + 極小中間報酬(MLP) 廃止(Phase 1突破に32000ep + Phase 2停滞)
10 CNN + カリキュラム + 中間報酬なし 廃止(カリキュラムが「方策の上書き」と判明)
11 CNN + vs ルールベースAI固定 + 中間報酬なし 実行中

セットアップ

# 仮想環境の作成
python -m venv .venv

# 依存パッケージのインストール
.venv\Scripts\pip install numpy flask
.venv\Scripts\pip install torch --index-url https://download.pytorch.org/whl/cpu

使い方

学習

# ゼロからカリキュラム学習(推奨)
.venv\Scripts\python train.py --episodes 30000

# チェックポイントから再開(バッファ+ε+Adam状態を引き継ぐ)
.venv\Scripts\python train.py --load-path weights/dqn_connect4 --episodes 30000

# バッファを引き継がずに再開
.venv\Scripts\python train.py --load-path weights/dqn_connect4 --no-buffer --episodes 30000

完了後、weights/dqn_connect4.npz(重み)と weights/dqn_connect4_checkpoint.npz(バッファ+学習状態)が保存されます。学習ログは weights/train_log.txt にリアルタイム出力されます。

ブラウザで対戦

.venv\Scripts\python web/app.py

http://localhost:5000 をブラウザで開いてください。

  • CPU(赤)が先攻、プレイヤー(橙)が後攻
  • DQNはPLAYER1(先攻)として学習しているため、WebUIもそれに合わせた配置になっています

ファイル構成

4moku/
├── env/
│   └── connect4_env.py         # ゲームロジック(重力・勝利判定・状態表現・中間報酬)
├── agents/
│   ├── base_agent.py           # 抽象基底クラス
│   ├── human_agent.py          # 人間プレイヤー(WebUI経由)
│   ├── random_agent.py         # ランダムAI
│   ├── rule_based_agent.py     # ルールベースAI(勝ち手・阻止・中央優先)
│   └── dqn_agent.py            # DQNエージェント(NumPy手書き実装)
├── web/
│   ├── app.py                  # Flask サーバー(CPU先攻・プレイヤー後攻)
│   └── templates/
│       └── index.html          # ブラウザUI(アニメーション付き)
├── weights/
│   ├── dqn_connect4.npz        # 学習済み重み
│   ├── train_log.txt           # 学習ログ(リアルタイム書き出し)
│   ├── snapshots/              # 自己ベスト更新時のスナップショット
│   └── archive/                # 過去の学習重み(参照用)
├── game_runner.py              # ターン管理(env・agent・UIを繋ぐ)
├── train.py                    # 学習スクリプト
└── CLAUDE.md                   # 設計メモ・開発ログ(詳細はこちら)

DQNの仕組みと実装

ゲームの状態表現

盤面を (3, 6, 7) の3次元配列で表します:

チャンネル0: 自分のコマがある場所 → 1.0、それ以外 → 0.0
チャンネル1: 相手のコマがある場所 → 1.0、それ以外 → 0.0
チャンネル2: 自分のターンなら盤面全体が 1.0、相手のターンなら 0.0

ネットワーク構造(CNN-A, ステージ10〜)

入力: (3, 6, 7) — 3チャンネルの盤面

Conv2d(3 → 32, 3×3, padding=1)
    ↓ ReLU
Conv2d(32 → 64, 3×3, padding=1)
    ↓ ReLU
flatten → 2688次元
Linear(2688 → 256)
    ↓ ReLU
Linear(256 → 7)

出力: 各列のQ値(7次元)
パラメータ数: 約710K

CNNの畳み込みフィルタにより、3連・4連パターンや防御位置を空間的に認識できます。旧MLP版(ステージ8〜9)ではflatten(126次元)で空間情報が失われ、パターン認識に中間報酬の「ヒント」が必要でしたが、CNNでは不要になりました。

出力の7つの値が「各列に置いたときの期待価値(Q値)」です。埋まった列は -inf でマスクし、有効な列の中から最大Q値の列を選びます。

ベルマン方程式とターゲットQ値

Q値の更新式:

Q(s, a) ← r + γ * max Q(s', a')

このプロジェクトでのポイントGameRunner が遅延コールバック方式を採用し、on_step_end を「相手が1手打った後(再び自分のターン)」に呼ぶ。これにより s' は常に「自分のターンの状態」になるため、通常のDQN更新式がそのまま使える。

旧実装では s' が「相手のターンの状態」だったため r - γ * max Q(s') と符号を反転していたが、Q値予測の入力として不適切だった(ネットワークは自分のターンの状態で学習しているため)。

中間報酬(シェーピング報酬)— 現在は廃止

ステージ5〜9で中間報酬の様々なスケール(大→中→小→極小→ゼロ)を試したが、全て問題を引き起こした

ステージ スケール 問題
5 大(累積200%超) 「3連作り」に最適化、勝ちを逃す
7 中(累積20〜25%) 負けても報酬プラス、Phase 2停滞
9 極小(累積6〜9%) Ph1突破4倍遅い + Ph2停滞、Q値不安定
8 ゼロ MLP: 空間パターンを認識できず停滞

結論: 中間報酬のチューニングではなく、ネットワーク構造(CNN)でパターン認識を解決するのが正しいアプローチ。ステージ10以降は勝敗報酬(±1.0)のみで学習。


ハイパーパラメータ

パラメータ 意味
lr 5e-4 Adamオプティマイザの学習率
gamma 0.99 割引率(未来の報酬の重み)
epsilon_start 1.0 探索率の初期値(完全ランダム)
epsilon_end 0.10 探索率の最小値(0.05では固定相手への過学習が激しかったため)
epsilon_decay 0.99990 探索率の減衰率(1ステップごと)
batch_size 128 1回の学習に使うサンプル数
buffer_capacity 200,000 経験再生バッファの容量
warmup_steps 2,000 学習開始前のバッファ蓄積ステップ数
target_update_interval 500 ターゲットネットの同期間隔(ステップ)(200では不安定だったため)

過去の方針と廃止理由

各ステージの「失敗→原因分析→次の戦略」を時系列で記録しています。

ステージ1・2: ランダムAI相手 → 防御を学べなかった

ランダム相手で勝率79%を達成したDQNが、ルールベースAI相手には勝率0%。ランダムAIは攻撃を防ぐ動機を与えないため、「攻撃だけのAI」にしかならなかった。先手・後手の混在も戦略の干渉を招いた。

教訓: 戦略を持たない相手では汎用的な実力がつかない。先手固定で学習すべき。

ステージ3: Self-play → 破滅的忘却

DQN同士の対戦で vs Random 77% → 44% に崩壊(破滅的忘却)。「対DQN戦略」に特化して汎用性を失った。

教訓: Self-playは相手の多様性を確保しないと崩壊する。

ステージ4: ルールベースAI固定 → 強すぎて学べない + 過学習

学習中の評価(ε=0.05混入)では33%に見えたが、ε=0.0では**0%**だった。εの「まぐれ勝ち」が数字を水増し。ルールベースAI(vs ランダム勝率99%)は初期DQNには強すぎて、負けシグナルしか得られなかった。

教訓: いきなり強い相手と戦わせても信号が薄すぎる。段階的に強くすべき。

ステージ5: NoisyRuleBasedカリキュラム → 部分的成功 + 構造的バグ2つ発見

Phase 3(noise=0.2)でvs RuleBased 99.5%を達成。しかしPhase 4移行後に崩壊(86% → 5%)。

原因分析で構造的なバグを2つ発見:

  1. next_stateが「相手のターンの状態」だったGameRunneron_step_end を自分が打った直後に呼んでいたため、next_state は相手の番の盤面。ネットワークは「自分のターンの状態」で学習しているので、max Q(s',a') の予測が信頼できない。r - γ*maxQ と符号反転する対症療法をしていたが本質的に不正確だった。Phase 3まではnoise=0.2のランダム性がこの不正確さを吸収していたが、Phase 4(noise=0.1)で致命的に。

  2. 中間報酬が勝敗報酬を上回りうるスケールだった — 3連作成+0.50、見逃し-0.80 vs 勝敗±1.0。1ゲーム中に中間報酬が複数回発生し、累積が勝敗を超える。「勝つ」より「3連を作り続ける」方がQ値的に得になりうる。

教訓: two-playerゲームではnext_stateの視点が重要。中間報酬は勝敗を超えてはならない。

ステージ6: 遅延コールバック + 目標80% + プール方式Phase4 → 目標高すぎ

ステージ5のバグ2つを修正(遅延コールバック + 中間報酬縮小)してゼロから再学習。Phase 1目標80%でep25500まで学習したが、vs Noisy(0.8)が73〜80%で振動し2回連続クリアに到達できず足踏み。noise=0.8(80%ランダム)相手の目標80%は運の要素が大きすぎた。

教訓: ほぼランダムな相手での目標は控えめに。早く次フェーズに進む方が学びが大きい。

ステージ7: シンプルカリキュラム + 小スケール中間報酬 → Phase 2 で停滞

Phase 1(noise=0.8)はep2000で突破したが、Phase 2(noise=0.5、目標70%)でvs Noisy 45〜55%のまま42000エピソード以上停滞。平均報酬は0.96前後で高止まり(勝率45%なのに報酬が高い)。中間報酬(3連+0.10等)が毎手蓄積され、1ゲームの累積中間報酬が+0.20〜0.25に達し、負けても中間報酬で相殺されていた。Q値が「勝つこと」より「中間報酬を稼ぐこと」に最適化された。

教訓: 中間報酬は「1回の値」ではなく「1ゲームの累積値」で勝敗報酬と比較すべき。ステージ5(大スケール)でもステージ7(小スケール)でも同じ構造の問題が発生した。

ステージ8: カリキュラム + 中間報酬なし → 防御を学べず停滞

中間報酬を完全廃止。Phase 1はep8000で突破したが、Phase 2(noise=0.5)でvs Noisy 42〜58%のまま30000エピソード以上停滞。vs RuleBasedは全期間を通じて0%。勝敗報酬だけではConnect Fourのゲーム長(15〜20手)に対して信号が薄すぎ、「なぜ負けたか(防御をサボったから)」を学べなかった。

教訓: 中間報酬が「多すぎる」のも「ゼロ」も問題(MLP構造では)。

ステージ9: カリキュラム + 極小中間報酬(MLP)→ ステージ8より悪化

極小中間報酬(3連+0.03, 防御+0.02, 見逃し-0.05)を再導入。Phase 1突破にep32000かかり(ステージ8はep8000 → 4倍遅い)、Phase 2(noise=0.5)でvs Noisy 39〜57%のまま18000ep停滞。学習が激しく振動(69%→20%→74%→45%)し、極小スケールでもQ値の安定性を壊した。

教訓: 中間報酬は大→中→小→極小→ゼロの全スケールで失敗。問題の本質はMLPの空間認識能力の限界であり、中間報酬チューニングでは解決しない。ネットワーク構造の改善(CNN)が正しいアプローチ。


ステージ10: CNN + カリキュラム + 中間報酬なし → カリキュラムが「方策の上書き」と判明

CNNの効果は明確: ep4000で vs RuleBased 53%(全ステージ最高記録)。Phase 1目標を75%→70%に引き下げ後、カリキュラム全Phase通過。しかしPhase 5(ルールベースAI固定)ではvs RuleBased 16%が最高で、その後0%に逆戻り。

最大の発見: カリキュラム学習は「段階的強化」ではなく「方策の上書き」だった。 ランダムAI向けの方策(攻撃のみ)とルールベースAI向けの方策(攻撃+防御)は根本的に異なり、DQNは1つの方策しか持てないため、noiseを下げるたびに前フェーズの方策が書き換えられるだけだった。

教訓: 対人ゲームにおけるカリキュラム学習は、相手タイプごとの最適方策が異なる場合、「段階的強化」ではなく「方策の上書き」になる。


3moku(Connect Three)の過去の方針と廃止理由

Stage 1: カリキュラム学習 → 実装前に廃止

4mokuの教訓(方策の上書き)をそのまま適用。カリキュラムは採用せず。

Stage 2: vs 簡易RuleBased直接対戦 → RuleBasedが弱すぎた

3,000epで vs RuleBased 100%達成。4mokuと違い、3mokuではDQNが十分に学習可能なことを確認。しかし人間が対戦すると簡単に勝ててしまう。簡易RuleBased(即勝ち/即防御/中央寄りの3ルール)は2手先を読めないため、フォーク(両端リーチ)に対応できない。

教訓: DQNの強さは対戦相手の強さに制約される。対戦相手を強化しなければDQNも強くならない。

Stage 3: vs 強化版RuleBased直接対戦 → ep3000で再び100%、先手有利すぎる問題

RuleBasedにフォーク検出・トラップ回避・リーチスコアリングを追加したが、DQNはep3000で100%に到達。3moku先手はゲーム構造上有利すぎ、RuleBasedの強化だけでは天井テストとして不十分。スナップショット保存がベスト更新時のみだったため、100%到達後の汎化性能の変化を追跡できなかった。

教訓: vs RuleBased 100%でも人間に勝てる保証はない。汎化性能指標(vs Random)の併用と、100%到達後の定期スナップショットが必要。

Stage 4: vs 強化版RuleBased + スナップショット改善 + vs Random指標 → 完了

100%時に毎回スナップショット保存、vs Random勝率を評価指標に追加。ep2000で vs RuleBased 100%到達、ep4000で vs Random 94%(汎化性能ピーク)。人間が対戦しても全く勝てないレベルに到達。

3moku総括

  • DQNは3moku(5×5, 3目)で人間より強いAIを実現(4mokuでは未達成だった最終目標)
  • DQNの適用限界: 3moku(状態空間~10^6)は通用、4moku(~10^12)は通用しない
  • 対戦相手の強さがDQNの天井を決める。評価指標は複数持つべき(単一100%は汎化性能を保証しない)

AlphaZero(Connect Four)— DQNの限界を超える

4mokuで11ステージにわたりDQNの限界を体感した結果、MCTS + ニューラルネット(AlphaZero方式) に移行。

DQNの3つの限界とAlphaZeroの解決策

DQNの限界 AlphaZeroの解決
先読みなし — Q値一発で手を決める MCTS — 50回のシミュレーションで数手先まで探索
信号が薄い — 勝敗報酬が最終手にしか付かない MCTSの行動確率πが教師信号 — 各手に直接の教師がある
Self-playが不安定 — 弱い方策同士が退化 MCTSが品質保証 — NNが弱くても探索で補正される

実装構成

alphazero/
├── env/connect4_env.py    # ゲームロジック
├── network.py             # デュアルヘッドNN(Policy + Value, 残差4ブロック)
├── mcts.py                # モンテカルロ木探索
├── self_play.py           # Self-playデータ生成
├── train.py               # 学習スクリプト(CPU軽量版: 100イテレーション)
├── agents/alphazero_agent.py  # 推論用エージェント
└── web/                   # Flask WebUI(ポート5001)

実行方法

# 学習(CPU環境向け、100イテレーション)
.venv\Scripts\python alphazero/train.py

# WebUI
.venv\Scripts\python alphazero/web/app.py
# ブラウザで http://localhost:5001 を開く

学習の期待される進行

DQNではvs RuleBased 16%が最高だった。AlphaZeroがこれを超えられるかが見どころ。

詳細は alphazero/CLAUDE.md を参照。


現行方針の設計思想(ステージ11)

カリキュラム学習を廃止し、ルールベースAI固定で直接学習:

変更点(ステージ10 → 11)

  1. カリキュラム学習を廃止: ランダム→ルールベースの段階的移行は「方策の上書き」に過ぎなかった
  2. ルールベースAI固定で直接学習: ステージ10 ep11000スナップショット(vs RuleBased 16%)から再開
  3. CNN-A・中間報酬なし・遅延コールバック方式は維持

なぜルールベースAI固定が今なら可能と考えるか

ステージ4で失敗した原因(next_stateバグ、MLP構造、terminal_reward符号バグ)は全て解消済み。

技術的基盤

  • CNN-A: 3×3畳み込みフィルタで3連・4連パターン、防御位置を空間的に認識
  • 中間報酬なし: 勝敗報酬(±1.0)のみ
  • GameRunner 遅延コールバック方式: next_state が「自分のターンの状態」になり、DQN更新式が正確に機能
  • チェックポイント保存: ReplayBuffer + ε + Adam状態を _checkpoint.pt に保存
  • スナップショットは2系統: best_ep* + phaseup_ph*
  • eval負荷削減: eval_interval=1000、vs Noisy 200戦、vs RuleBased 100戦

最大の学び: DQNの限界と「相手を読む」こと

このプロジェクトを通じて得られた最も本質的な知見。

問題: 方策の両立ができない

10ステージの実験を通じて、一貫したジレンマが観察された:

  • ランダム相手に最適化 → ルールベースAI相手の勝率が下がる
  • ルールベースAI相手に最適化 → ランダム相手の勝率が下がる

同じ盤面でも、相手によって最適な手が異なる。 しかしDQNは「盤面だけ」を見て手を決めるため、相手を区別できない。

原因: マルコフ性の仮定

DQN(Q学習全般)はマルコフ性を前提とする ― 「現在の状態だけで最適行動が決まる」。しかし対人ゲームでは「相手がどういうプレイヤーか」という情報が重要であり、これは過去の手の履歴を見ないと分からない。

盤面という「状態」には相手の傾向が含まれない。つまり状態が不完全であり、厳密にはPOMDP(部分観測マルコフ決定過程)になっている。

格ゲーのプロが「戦いながら相手の癖を読んで刺しに行く」のは、まさにこの「相手モデル」を動的に構築している行為。現在のDQNにはこの機構がない。

解決の方向性

アプローチ 概要
状態に履歴を追加 相手の直近N手を入力に含める(最も簡単)
RNN/Transformer 手の系列から相手パターンを暗黙的に学習(AlphaStar等が採用)
Opponent Modeling 相手の方策を明示的に推定する別モジュール
MCTS + NN ゲーム木探索で相手非依存の最適戦略を目指す(AlphaZero方式)

このプロジェクトでの意義

教科書的なDQNの「万能でない部分」を実験で体感し、なぜAlphaZeroやPOMDP系の手法が必要になるのかを動機づける経験が得られた。「DQNを実装して限界を知る → 次のステップ(MCTS、系列モデル等)の必要性を理解する」という学習の流れとして、非常に有意義だった。

→ この知見を踏まえ、alphazero/ でMCTS+NN方式を実装。 DQNの限界を超えられるか、本格学習で検証中。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages