強化学習(Reinforcement Learning)は、エージェントが環境と相互作用しながら、報酬を最大化する行動方針(ポリシー)を学習する機械学習の一分野です。教師あり学習とは異なり、正解ラベルが与えられるのではなく、行動の結果として得られる報酬信号を頼りに学習を進めます。
本記事では、強化学習の最も基本的なアルゴリズムであるQ学習から、深層学習と組み合わせたDQN(Deep Q-Network)までを、Pythonの実装コードとともに段階的に解説します。理論だけでなく、実際に動くコードで手を動かしながら理解を深められる構成になっています。
強化学習の多くのアルゴリズムは、マルコフ決定過程(Markov Decision Process、MDP)という数学的フレームワークに基づいています。MDPは以下の要素で構成されます。
マルコフ性(現在の状態のみが将来の状態を決定する性質)が成り立つ問題設定では、Q学習が理論的に最適解に収束することが証明されています。
Q学習の核心は、状態と行動のペアに対する価値(Q値)をテーブルとして管理し、経験を通じて更新していくことです。Q値の更新はベルマン方程式に基づきます。
直感的に説明すると、Q値は「この状態でこの行動を取ったら、将来的にどれだけの報酬が得られるか」の期待値です。
OpenAI Gymの「FrozenLake」環境を使って、Q学習を実装してみましょう。
import numpy as np
import gymnasium as gym
def q_learning(env, episodes=10000, alpha=0.1, gamma=0.99, epsilon=1.0, epsilon_decay=0.9995, epsilon_min=0.01):
q_table = np.zeros((env.observation_space.n, env.action_space.n))
rewards_history = []
for episode in range(episodes):
state, _ = env.reset()
total_reward = 0
done = False
while not done:
if np.random.random() < epsilon:
action = env.action_space.sample()
else:
action = np.argmax(q_table[state])
next_state, reward, terminated, truncated, _ = env.step(action)
done = terminated or truncated
best_next = np.max(q_table[next_state])
q_table[state, action] += alpha * (
reward + gamma * best_next * (1 - terminated) - q_table[state, action]
)
state = next_state
total_reward += reward
epsilon = max(epsilon_min, epsilon * epsilon_decay)
rewards_history.append(total_reward)
if (episode + 1) % 1000 == 0:
avg = np.mean(rewards_history[-1000:])
print(f"Episode {episode+1}, Avg Reward: {avg:.3f}, Epsilon: {epsilon:.3f}")
return q_table, rewards_history
env = gym.make('FrozenLake-v1', is_slippery=True)
q_table, history = q_learning(env)
このコードで注目すべきポイントは、epsilon-greedy戦略です。学習初期は高い確率でランダムな行動を取り(探索)、学習が進むにつれて学習済みのQ値に従った行動を増やしていきます(活用)。この探索と活用のバランスが、強化学習の成否を分ける重要な要素です。
Q学習はシンプルで強力ですが、状態空間が大きくなるとQテーブルのサイズが爆発的に増加するという根本的な限界があります。この限界を克服するために登場したのが、DQN(Deep Q-Network)です。DQNは、Qテーブルの代わりにニューラルネットワークでQ値を近似します。
PyTorchを使ってDQNを実装しましょう。ここではCartPole環境を使用します。
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import gymnasium as gym
from collections import deque
import random
class DQNetwork(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.network = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, 128),
nn.ReLU(),
nn.Linear(128, action_dim)
)
def forward(self, x):
return self.network(x)
class ReplayBuffer:
def __init__(self, capacity=10000):
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)
states, actions, rewards, next_states, dones = zip(*batch)
return (
torch.FloatTensor(np.array(states)),
torch.LongTensor(actions),
torch.FloatTensor(rewards),
torch.FloatTensor(np.array(next_states)),
torch.FloatTensor(dones)
)
def __len__(self):
return len(self.buffer)
class DQNAgent:
def __init__(self, state_dim, action_dim, lr=1e-3, gamma=0.99):
self.policy_net = DQNetwork(state_dim, action_dim)
self.target_net = DQNetwork(state_dim, action_dim)
self.target_net.load_state_dict(self.policy_net.state_dict())
self.optimizer = optim.Adam(self.policy_net.parameters(), lr=lr)
self.buffer = ReplayBuffer()
self.gamma = gamma
self.action_dim = action_dim
def select_action(self, state, epsilon):
if random.random() < epsilon:
return random.randrange(self.action_dim)
with torch.no_grad():
q_values = self.policy_net(torch.FloatTensor(state))
return q_values.argmax().item()
def train_step(self, batch_size=64):
if len(self.buffer) < batch_size:
return
states, actions, rewards, next_states, dones = self.buffer.sample(batch_size)
current_q = self.policy_net(states).gather(1, actions.unsqueeze(1))
with torch.no_grad():
max_next_q = self.target_net(next_states).max(1)[0]
target_q = rewards + self.gamma * max_next_q * (1 - dones)
loss = nn.MSELoss()(current_q.squeeze(), target_q)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
def update_target(self):
self.target_net.load_state_dict(self.policy_net.state_dict())
DQNの重要なテクニックとして、Experience Replay(経験再生)とTarget Network(ターゲットネットワーク)があります。Experience Replayは過去の経験をバッファに保存し、ランダムにサンプリングして学習することで、データの相関を減らし学習を安定化させます。Target Networkは、Q値の更新先を固定したネットワークで計算することで、学習の発散を防ぎます。
強化学習は奥が深い分野ですが、Q学習の基本を理解すれば、その後の発展的なアルゴリズム(A3C、PPO、SACなど)の理解もスムーズになります。まずは本記事のコードを動かしてみて、パラメータを変えながら挙動の変化を観察してみてください。実験を通じた直感が、この分野では何よりも重要です。