Created
January 5, 2026 01:03
-
-
Save ryuchan00/bee2b9f22a094518f571abe1be0c6031 to your computer and use it in GitHub Desktop.
UNOの深層学習
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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