はじめに
今回は、前回の記事の続きになります。
前回の問題点
- Epsilon(探索率)が常に一定だった(完全ランダムに行動を選択している)
- 学習が進んでいない
やること
- 学習が進むにつれて、Epsilon(探索率)を減少させる
- 報酬が最大化するようにDQNの精度を向上させる
使用コード(前回からの変更箇所のみ)
こちらのコードでは、DQNAgentを定義して、実際に学習させています。
主な変更点はこちらです。
- Epsilon(探索率)を2乗することで、探索率が少しずつ減少する仕組みを導入
- 報酬が最大化するようにDQNの精度向上
- ターゲットネットワークの導入
- 学習率を変更(0.001→0.0005)
- エピソード数を変更(1000→10000)
- ミニバッチサイズの変更(32→64)
- 経験リプレイのバッファサイズの変更(2000→5000)
import torch.nn as nn
import torch.optim as optim
from collections import deque
class DQN(nn.Module):
def __init__(self, state_size, action_size):
super(DQN, self).__init__()
self.fc1 = nn.Linear(state_size, 128)
self.fc2 = nn.Linear(128, 128)
self.fc3 = nn.Linear(128, action_size)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
return self.fc3(x)
class DQNAgent:
def __init__(self, state_size, action_size):
self.state_size = state_size
self.action_size = action_size
self.memory = deque(maxlen=5000)
self.gamma = 0.95 # 割引率
self.epsilon = 1.0 # 探索率
self.epsilon_min = 0.01
self.epsilon_decay = 0.995
self.learning_rate = 0.0005
self.model = DQN(state_size, action_size)
self.target_model = DQN(state_size, action_size) # ターゲットネットワーク
self.update_target_model() # ターゲットネットワークの初期化
self.optimizer = optim.Adam(self.model.parameters(), lr=self.learning_rate)
self.criterion = nn.MSELoss()
def act(self, state):
if np.random.rand() <= self.epsilon:
return random.randrange(self.action_size)
state = torch.FloatTensor(state).unsqueeze(0)
actions = self.model(state)
return torch.argmax(actions).item()
def update_target_model(self):
self.target_model.load_state_dict(self.model.state_dict()) # ターゲットネットワークを更新
def train(self, batch_size):
minibatch = random.sample(self.memory, batch_size)
for state, action, reward, next_state, done in minibatch:
target = reward
if not done:
target += self.gamma * torch.max(self.model(torch.FloatTensor(next_state).unsqueeze(0))).item()
target_f = self.model(torch.FloatTensor(state).unsqueeze(0))
target_f[0][action] = target
self.optimizer.zero_grad()
loss = self.criterion(target_f, self.model(torch.FloatTensor(state).unsqueeze(0)))
loss.backward()
self.optimizer.step()
def update_epsilon(self):
if self.epsilon > self.epsilon_min:
self.epsilon *= self.epsilon_decay # Epsilonを減少させる
import matplotlib.pyplot as plt
# 環境とDQNの初期化
env = TaxiDispatchEnv(df)
set_seed(42)
agent = DQNAgent(state_size=2, action_size=5)
# 報酬の記録リスト
total_rewards = []
# 学習ループ
episodes = 10000 # エピソード数
batch_size = 64 # 学習用のミニバッチサイズ
for e in range(episodes):
state = env.reset()
total_reward = 0 # そのエピソードの合計報酬
for time in range(100):
action = agent.act(state) # 行動を選択
next_state, reward, done, _ = env.step(action) # 環境を進める
agent.memory.append((state, action, reward, next_state, done)) # 経験を保存
state = next_state # 状態を更新
total_reward += reward # 報酬を加算
if done:
break
total_rewards.append(total_reward) # 報酬を記録
if len(agent.memory) > batch_size:
agent.train(batch_size) # DQNの学習を実行
agent.update_epsilon()
if e % 10 == 0: # 10エピソードごとにターゲットネットワークを更新
agent.update_target_model()
if e % 100 == 0:
print(f"Episode {e}, Total Reward: {total_reward}, Epsilon: {agent.epsilon}")
# 学習曲線をプロット
plt.figure(figsize=(10,5))
plt.plot(total_rewards, label="Total Reward per Episode", alpha=0.5)
plt.plot(pd.Series(total_rewards).rolling(100).mean(), label="Moving Average (100 episodes)", linewidth=3, color="red")
plt.xlabel("Episode")
plt.ylabel("Total Reward")
plt.title("Training Progress of DQN for Taxi Dispatch")
plt.legend()
plt.show()
分析結果
Episode 0, Total Reward: 20341.369099999996, Epsilon: 0.995
Episode 100, Total Reward: 9587.634300000003, Epsilon: 0.6027415843082742
Episode 200, Total Reward: 1285.6542000000002, Epsilon: 0.36512303261753626
Episode 300, Total Reward: 22009.842500000002, Epsilon: 0.2211807388415433
Episode 400, Total Reward: 6669.531500000002, Epsilon: 0.13398475271138335
Episode 500, Total Reward: 18566.231999999993, Epsilon: 0.0811640021330769
Episode 600, Total Reward: 11333.869199999997, Epsilon: 0.04916675299948831
Episode 700, Total Reward: 8789.645799999998, Epsilon: 0.029783765425331846
Episode 800, Total Reward: 9440.040000000006, Epsilon: 0.018042124582040707
Episode 900, Total Reward: 16137.161299999992, Epsilon: 0.010929385683282892
Episode 1000, Total Reward: 491.6718, Epsilon: 0.00998645168764533
Episode 1100, Total Reward: 17710.511599999998, Epsilon: 0.00998645168764533
Episode 1200, Total Reward: 20286.200399999994, Epsilon: 0.00998645168764533
Episode 1300, Total Reward: 10290.335699999998, Epsilon: 0.00998645168764533
Episode 1400, Total Reward: 16128.985200000003, Epsilon: 0.00998645168764533
Episode 1500, Total Reward: 5646.302700000001, Epsilon: 0.00998645168764533
Episode 1600, Total Reward: 13463.330900000003, Epsilon: 0.00998645168764533
Episode 1700, Total Reward: 27372.815599999998, Epsilon: 0.00998645168764533
Episode 1800, Total Reward: 16729.798799999997, Epsilon: 0.00998645168764533
Episode 1900, Total Reward: 5966.1325000000015, Epsilon: 0.00998645168764533
Episode 2000, Total Reward: 30143.304699999993, Epsilon: 0.00998645168764533
Episode 2100, Total Reward: 26198.2688, Epsilon: 0.00998645168764533
Episode 2200, Total Reward: 30268.187, Epsilon: 0.00998645168764533
Episode 2300, Total Reward: 3028.933399999999, Epsilon: 0.00998645168764533
Episode 2400, Total Reward: 9231.432700000001, Epsilon: 0.00998645168764533
...
Episode 9600, Total Reward: 39233.26070000001, Epsilon: 0.00998645168764533
Episode 9700, Total Reward: 1329.4756999999997, Epsilon: 0.00998645168764533
Episode 9800, Total Reward: 31979.492200000004, Epsilon: 0.00998645168764533
Episode 9900, Total Reward: 23788.365300000005, Epsilon: 0.00998645168764533
Epsilon(探索率)は順調に下がっていて、意図通りになったかなと思います。
しかし、9700エピソードの時点でも報酬が1329と低い値とっていて、思うような結果になりませんでした。
もちろん、すべてのアプローチを同時に試したわけではなく、一つずつ試しているのですが、すべて似たような結果になったので、今回はまとめて提示しました。
おわりに
今回の結果を見てみると、なかなか報酬が最大化しないなというのが正直なところです。ただ、10000エピソードの後半(9000エピソードを超えたあたり)からは、少しずつ移動平均が上がっていて、各エピソードの報酬も安定してきているように見えます。もしかしたら、強化学習の精度を上げるためには、もっとエピソード数を増やしたり、データ数を増やしたりした方が良いのかなと思いました