DQNの精度向上のアプローチを試す

# はじめに
今回は、前回の記事の続きになります。

https://qiita.com/UKI_datascience/items/368b0e590eb56ccc232a

# 前回の問題点
- Epsilon（探索率）が常に一定だった（完全ランダムに行動を選択している）
- 学習が進んでいない

# やること
- 学習が進むにつれて、Epsilon（探索率）を減少させる
- 報酬が最大化するようにDQNの精度を向上させる

### 使用コード（前回からの変更箇所のみ）
こちらのコードでは、DQNAgentを定義して、実際に学習させています。

主な変更点はこちらです。
- Epsilon（探索率）と0.995との積を使うことで、探索率が少しずつ減少する仕組みを導入
- 報酬が最大化するようにDQNの精度向上
    - ターゲットネットワークの導入
    - 学習率を変更（0.001→0.0005）
    - エピソード数を変更（1000→10000）
    - ミニバッチサイズの変更（32→64）
    - 経験リプレイのバッファサイズの変更（2000→5000）

```python
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を減少させる
```

```python
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
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3457746/8760ca6a-e4ef-4b28-b597-1861311f9077.png)

Epsilon（探索率）は順調に下がっていて、意図通りになったかなと思います。
しかし、9700エピソードの時点でも報酬が1329と低い値とっていて、思うような結果になりませんでした。

もちろん、すべてのアプローチを同時に試したわけではなく、一つずつ試しているのですが、すべて似たような結果になったので、今回はまとめて提示しました。

# おわりに
今回の結果を見てみると、なかなか報酬が最大化しないなというのが正直なところです。ただ、10000エピソードの後半（9000エピソードを超えたあたり）からは、少しずつ移動平均が上がっていて、各エピソードの報酬も安定してきているように見えます。もしかしたら、強化学習の精度を上げるためには、もっとエピソード数を増やしたり、データ数を増やしたりした方が良いのかなと思いました