Skip to content

Instantly share code, notes, and snippets.

@ryuchan00
Created January 5, 2026 01:03
Show Gist options
  • Select an option

  • Save ryuchan00/bee2b9f22a094518f571abe1be0c6031 to your computer and use it in GitHub Desktop.

Select an option

Save ryuchan00/bee2b9f22a094518f571abe1be0c6031 to your computer and use it in GitHub Desktop.
UNOの深層学習
# coding: utf-8
# DQN(Deep Q-Network)によるUNOゲームの強化学習
# PyTorchを使用して、AIエージェントがUNOのような簡易カードゲームを学習する
# 強化学習:環境と相互作用しながら、報酬を最大化する行動を学習する機械学習手法
# DQN:Q学習にディープニューラルネットワークを組み合わせた手法
import torch # PyTorch:ディープラーニングフレームワーク
import torch.nn as nn # ニューラルネットワークモジュール
import torch.optim as optim # 最適化アルゴリズム
import numpy as np # 数値計算ライブラリ
import random # ランダム生成
from collections import deque # 固定長キュー(経験リプレイ用)
# AIの脳:シンプルな3層の全結合ネットワーク
class UNONet(nn.Module):
"""DQNで使用するニューラルネットワーク
状態(state)を入力として受け取り、各行動の価値(Q値)を出力する
Q値:その状態でその行動を取った場合に期待できる将来の報酬の総和
Attributes:
fc1: 第1層(入力層 -> 隠れ層1)
fc2: 第2層(隠れ層1 -> 隠れ層2)
fc3: 第3層(隠れ層2 -> 出力層)
"""
def __init__(self, state_size, action_size):
"""
Parameters:
state_size (int): 状態の次元数(この例では3: 自分の手札, 相手の手札, 場札の色)
action_size (int): 行動の種類数(この例では2: カードを出す, 山札から引く)
"""
super(UNONet, self).__init__()
self.fc1 = nn.Linear(state_size, 64) # 入力層 -> 隠れ層1(64ユニット)
self.fc2 = nn.Linear(64, 64) # 隠れ層1 -> 隠れ層2(64ユニット)
self.fc3 = nn.Linear(64, action_size) # 隠れ層2 -> 出力層(各行動のQ値)
def forward(self, x):
"""順伝播:入力から出力を計算
Parameters:
x (torch.Tensor): 入力状態
Returns:
torch.Tensor: 各行動のQ値
"""
x = torch.relu(self.fc1(x)) # 1層目:ReLU活性化関数
x = torch.relu(self.fc2(x)) # 2層目:ReLU活性化関数
return self.fc3(x) # 3層目:Q値を出力(活性化なし)
class DQNAgent:
"""DQN(Deep Q-Network)エージェント
強化学習のエージェント:環境を観察し、行動を選択し、経験から学習する
主要な概念:
- Experience Replay:過去の経験を記憶し、ランダムにサンプリングして学習
- ε-greedy法:探索(ランダム行動)と活用(学習済み知識)のバランス
- Q学習:状態と行動のペアの価値(Q値)を学習する
"""
def __init__(self, state_size, action_size):
"""
Parameters:
state_size (int): 状態の次元数
action_size (int): 行動の種類数
"""
self.state_size = state_size
self.action_size = action_size
self.memory = deque(maxlen=2000) # 経験リプレイバッファ(最大2000個の経験を保存)
self.gamma = 0.95 # 割引率:将来の報酬の重要度(0.95 = 将来の報酬を95%の価値で評価)
self.epsilon = 1.0 # 探索率(ε):ランダム行動を取る確率(最初は100%探索)
self.epsilon_min = 0.01 # εの最小値(最終的に1%はランダム行動)
self.epsilon_decay = 0.995 # εの減衰率(学習が進むにつれて探索を減らす)
self.model = UNONet(state_size, action_size) # Q値を推定するニューラルネットワーク
self.optimizer = optim.Adam(self.model.parameters(), lr=0.001) # Adam最適化(学習率0.001)
self.criterion = nn.MSELoss() # 損失関数:平均二乗誤差(予測Q値と目標Q値の差を最小化)
def act(self, state):
"""行動選択:ε-greedy法で行動を決定
探索と活用のジレンマを解決する戦略:
- ε(イプシロン)の確率でランダムに行動(探索)
- (1-ε)の確率でQ値が最大の行動を選択(活用)
Parameters:
state (numpy.ndarray): 現在の状態
Returns:
int: 選択された行動のインデックス
"""
# ε-greedy法:一定確率でランダム行動、それ以外はAIの判断
if np.random.rand() <= self.epsilon:
return random.randrange(self.action_size) # ランダムに行動を選択(探索)
# AIの判断による行動選択(活用)
state = torch.FloatTensor(state) # NumPy配列をPyTorchテンソルに変換
act_values = self.model(state) # ニューラルネットワークで各行動のQ値を計算
return torch.argmax(act_values).item() # Q値が最大の行動を選択
def replay(self, batch_size):
"""経験リプレイ:過去の経験から学習(DQNの核心部分)
Experience Replay:
- メモリから過去の経験をランダムにサンプリング
- 連続した経験の相関を減らし、学習を安定化
- 同じ経験を複数回使用でき、データ効率が良い
学習の流れ:
1. 経験 (state, action, reward, next_state, done) を取得
2. 目標Q値を計算:reward + γ * max(Q(next_state))
3. 現在のQ値との誤差を最小化するように学習
Parameters:
batch_size (int): 一度に学習する経験の数
"""
# 過去の経験から学習(ここが深層学習のメイン)
if len(self.memory) < batch_size:
return # メモリが十分でなければ学習しない
# メモリからランダムにバッチサイズ分の経験をサンプリング
minibatch = random.sample(self.memory, batch_size)
# バッチ内の各経験について学習
for state, action, reward, next_state, done in minibatch:
# 目標Q値の計算(ベルマン方程式に基づく)
target = reward # 終了状態の場合は即座の報酬のみ
if not done: # 終了していない場合
# 目標Q値 = 即座の報酬 + 割引率 × 次状態での最大Q値
next_state = torch.FloatTensor(next_state)
target = (reward + self.gamma * torch.max(self.model(next_state)).item())
# 現在の状態でのQ値を取得
state = torch.FloatTensor(state)
target_f = self.model(state) # 全行動のQ値を取得
# 実際にとった行動のQ値を目標値(正解)に更新
target_f = target_f.clone().detach() # 勾配計算を切り離す
target_f[action] = target # 取った行動のQ値のみを目標値に置き換え
# ニューラルネットワークの学習実行
self.optimizer.zero_grad() # 勾配をリセット
output = self.model(state) # 現在のQ値を再計算
loss = self.criterion(output, target_f) # 損失を計算(予測Q値と目標Q値の誤差)
loss.backward() # 誤差逆伝播で勾配を計算
self.optimizer.step() # パラメータを更新
# εの減衰:学習が進むにつれて探索率を下げる(徐々に活用重視へ)
if self.epsilon > self.epsilon_min:
self.epsilon *= self.epsilon_decay
# =============================================================================
# メイン実行部分:UNOゲームの簡易シミュレーションと学習
# =============================================================================
# 環境の定義
# 状態:[自分の手札枚数, 相手の手札枚数, 場札の色(0-3)] と仮定(計3次元)
# 行動:[カードを出す(0), 山札から引く(1)] と仮定(計2次元)
state_size = 3 # 状態空間の次元数
action_size = 2 # 行動空間の次元数
# DQNエージェントの作成
agent = DQNAgent(state_size, action_size)
# 学習パラメータ
episodes = 500 # エピソード数(ゲームを500回プレイ)
batch_size = 32 # 一度に学習する経験の数
# 学習ループ:各エピソードで1ゲームをプレイ
for e in range(episodes):
# 初期状態:自分2枚、相手2枚、場札の色ランダム
state = np.array([2, 2, random.randint(0, 3)])
# 1エピソード(1ゲーム)の実行
for time in range(10): # 最大10ターン
# 行動選択(ε-greedy法)
action = agent.act(state)
# 簡易ルール処理(環境のシミュレーション)
if action == 0: # カードを出した場合
next_state = state.copy()
next_state[0] -= 1 # 自分の手札が1枚減る
reward = 10 if next_state[0] == 0 else 1 # 手札0枚(上がり)で高報酬、それ以外は小報酬
else: # 山札から引いた場合
next_state = state.copy()
next_state[0] += 1 # 自分の手札が1枚増える
reward = -1 # 負の報酬(ペナルティ)
# 終了判定:手札が0以下になったらゲーム終了
done = next_state[0] <= 0
# 経験をメモリに保存 (state, action, reward, next_state, done)
agent.memory.append((state, action, reward, next_state, done))
# 状態を更新
state = next_state
# ゲーム終了時の処理
if done:
print(f"episode: {e}/{episodes}, score: {time}, e: {agent.epsilon:.2}")
break
# エピソード終了後に経験リプレイで学習
agent.replay(batch_size)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment