【问题标题】:Cartpole-v0 loss increasing using DQN使用 DQN 增加 Cartpole-v0 损失
【发布时间】:2019-11-19 17:22:39
【问题描述】:

您好,我正在尝试训练 DQN 来解决健身房的 Cartpole 问题。 出于某种原因,Loss 看起来像这样(橙色线)。你们可以看看我的代码并帮助解决这个问题吗?我已经对超参数进行了相当多的尝试,所以我认为它们不是这里的问题。

class DQN(nn.Module):
    def __init__(self, input_dim, output_dim):
        super(DQN, self).__init__()
        self.linear1 = nn.Linear(input_dim, 16)
        self.linear2 = nn.Linear(16, 32)
        self.linear3 = nn.Linear(32, 32)
        self.linear4 = nn.Linear(32, output_dim)


    def forward(self, x):
        x = F.relu(self.linear1(x))
        x = F.relu(self.linear2(x))
        x = F.relu(self.linear3(x))
        return self.linear4(x)


final_epsilon = 0.05
initial_epsilon = 1
epsilon_decay = 5000
global steps_done
steps_done = 0


def select_action(state):
    global steps_done
    sample = random.random()
    eps_threshold = final_epsilon + (initial_epsilon - final_epsilon) * \
                    math.exp(-1. * steps_done / epsilon_decay)
    if sample > eps_threshold:
        with torch.no_grad():
            state = torch.Tensor(state)
            steps_done += 1
            q_calc = model(state)
            node_activated = int(torch.argmax(q_calc))
            return node_activated
    else:
        node_activated = random.randint(0,1)
        steps_done += 1
        return node_activated


class ReplayMemory(object): # Stores [state, reward, action, next_state, done]

    def __init__(self, capacity):
        self.capacity = capacity
        self.memory = [[],[],[],[],[]]

    def push(self, data):
        """Saves a transition."""
        for idx, point in enumerate(data):
            #print("Col {} appended {}".format(idx, point))
            self.memory[idx].append(point)

    def sample(self, batch_size):
        rows = random.sample(range(0, len(self.memory[0])), batch_size)
        experiences = [[],[],[],[],[]]
        for row in rows:
            for col in range(5):
                experiences[col].append(self.memory[col][row])
        return experiences

    def __len__(self):
        return len(self.memory[0])


input_dim, output_dim = 4, 2
model = DQN(input_dim, output_dim)
target_net = DQN(input_dim, output_dim)
target_net.load_state_dict(model.state_dict())
target_net.eval()
tau = 2
discount = 0.99

learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

memory = ReplayMemory(65536)
BATCH_SIZE = 128


def optimize_model():
    if len(memory) < BATCH_SIZE:
        return 0
    experiences = memory.sample(BATCH_SIZE)
    state_batch = torch.Tensor(experiences[0])
    action_batch = torch.LongTensor(experiences[1]).unsqueeze(1)
    reward_batch = torch.Tensor(experiences[2])
    next_state_batch = torch.Tensor(experiences[3])
    done_batch = experiences[4]

    pred_q = model(state_batch).gather(1, action_batch)

    next_state_q_vals = torch.zeros(BATCH_SIZE)

    for idx, next_state in enumerate(next_state_batch):
        if done_batch[idx] == True:
            next_state_q_vals[idx] = -1
        else:
            # .max in pytorch returns (values, idx), we only want vals
            next_state_q_vals[idx] = (target_net(next_state_batch[idx]).max(0)[0]).detach()


    better_pred = (reward_batch + next_state_q_vals).unsqueeze(1)

    loss = F.smooth_l1_loss(pred_q, better_pred)
    optimizer.zero_grad()
    loss.backward()
    for param in model.parameters():
        param.grad.data.clamp_(-1, 1)
    optimizer.step()
    return loss


points = []
losspoints = []

#save_state = torch.load("models/DQN_target_11.pth")
#model.load_state_dict(save_state['state_dict'])
#optimizer.load_state_dict(save_state['optimizer'])



env = gym.make('CartPole-v0')
for i_episode in range(5000):
    observation = env.reset()
    episode_loss = 0
    if episode % tau == 0:
        target_net.load_state_dict(model.state_dict())
    for t in range(1000):
        #env.render()
        state = observation
        action = select_action(observation)
        observation, reward, done, _ = env.step(action)

        if done:
            next_state = [0,0,0,0]
        else:
            next_state = observation

        memory.push([state, action, reward, next_state, done])
        episode_loss = episode_loss + float(optimize_model(i_episode))
        if done:
            points.append((i_episode, t+1))
            print("Episode {} finished after {} timesteps".format(i_episode, t+1))
            print("Avg Loss: ", episode_loss / (t+1))
            losspoints.append((i_episode, episode_loss / (t+1)))
            if (i_episode % 100 == 0):
                eps = final_epsilon + (initial_epsilon - final_epsilon) * \
                    math.exp(-1. * steps_done / epsilon_decay)
                print(eps)
            if ((i_episode+1) % 5001 == 0):
                save = {'state_dict': model.state_dict(), 'optimizer': optimizer.state_dict()}
                torch.save(save, "models/DQN_target_" + str(i_episode // 5000) + ".pth")
            break
env.close()




x = [coord[0] * 100 for coord in points]
y = [coord[1] for coord in points]

x2 = [coord[0] * 100 for coord in losspoints]
y2 = [coord[1] for coord in losspoints]

plt.plot(x, y)
plt.plot(x2, y2)
plt.show()

我基本上遵循了 pytorch 的教程,除了使用 env 返回的状态而不是像素。我还更改了回放内存,因为我在那里遇到了问题。除此之外,其他的一切都保持不变。

编辑:

我在小批量上尝试过拟合,并且损失看起来像 this 而不更新目标网络和 this 更新时

编辑 2:

这绝对是目标网络的问题,我尝试删除它,损失似乎没有成倍增加

【问题讨论】:

    标签: python pytorch reinforcement-learning openai-gym


    【解决方案1】:

    您的tau 值太小,小目标网络更新导致DQN 传输不稳定。您可以尝试使用 1000(OpenAI Baseline 的 DQN 示例)或 10000(Deepmind 的 Nature 论文)。

    在 Deepmind 2015 年的 Nature 论文中,它指出:

    在线 Q-learning 的第二个修改旨在进一步提高我们使用神经网络的方法的稳定性,是使用单独的网络在 Q-learning 更新中生成轨迹 yj。更准确地说,每次 C 更新我们都克隆网络 Q 以获得目标网络 Q' 并使用 Q' 生成 Q 学习目标 yj 以用于后续 C 对 Q 的更新。 与标准在线 Q 学习相比,这种修改使算法更加稳定,其中增加 Q(st,at) 的更新通常也会增加 Q(st+1, a) 对于所有 a,因此也增加了目标 yj,可能导致策略的振荡或发散。使用较旧的参数集生成目标会在对 Q 进行更新的时间与更新影响目标 yj 的时间之间增加延迟,从而使发散或振荡的可能性大大降低。

    Human-level control through deep reinforcement learning, Mnih et al., 2015

    我已经使用tau=2tau=10tau=100tau=1000tau=10000 的设置运行了您的代码。 tau=100 的更新频率解决了这个问题(达到最大步数 200)。

    tau=2

    tau=10

    tau=100

    tau=1000

    tau=10000

    以下是修改后的代码。

    import random
    import math
    import matplotlib.pyplot as plt
    
    import torch
    from torch import nn
    import torch.nn.functional as F
    import gym
    
    class DQN(nn.Module):
        def __init__(self, input_dim, output_dim):
            super(DQN, self).__init__()
            self.linear1 = nn.Linear(input_dim, 16)
            self.linear2 = nn.Linear(16, 32)
            self.linear3 = nn.Linear(32, 32)
            self.linear4 = nn.Linear(32, output_dim)
    
    
        def forward(self, x):
            x = F.relu(self.linear1(x))
            x = F.relu(self.linear2(x))
            x = F.relu(self.linear3(x))
            return self.linear4(x)
    
    
    final_epsilon = 0.05
    initial_epsilon = 1
    epsilon_decay = 5000
    global steps_done
    steps_done = 0
    
    
    def select_action(state):
        global steps_done
        sample = random.random()
        eps_threshold = final_epsilon + (initial_epsilon - final_epsilon) * \
                        math.exp(-1. * steps_done / epsilon_decay)
        if sample > eps_threshold:
            with torch.no_grad():
                state = torch.Tensor(state)
                steps_done += 1
                q_calc = model(state)
                node_activated = int(torch.argmax(q_calc))
                return node_activated
        else:
            node_activated = random.randint(0,1)
            steps_done += 1
            return node_activated
    
    
    class ReplayMemory(object): # Stores [state, reward, action, next_state, done]
    
        def __init__(self, capacity):
            self.capacity = capacity
            self.memory = [[],[],[],[],[]]
    
        def push(self, data):
            """Saves a transition."""
            for idx, point in enumerate(data):
                #print("Col {} appended {}".format(idx, point))
                self.memory[idx].append(point)
    
        def sample(self, batch_size):
            rows = random.sample(range(0, len(self.memory[0])), batch_size)
            experiences = [[],[],[],[],[]]
            for row in rows:
                for col in range(5):
                    experiences[col].append(self.memory[col][row])
            return experiences
    
        def __len__(self):
            return len(self.memory[0])
    
    
    input_dim, output_dim = 4, 2
    model = DQN(input_dim, output_dim)
    target_net = DQN(input_dim, output_dim)
    target_net.load_state_dict(model.state_dict())
    target_net.eval()
    tau = 100
    discount = 0.99
    
    learning_rate = 1e-4
    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
    
    memory = ReplayMemory(65536)
    BATCH_SIZE = 128
    
    
    def optimize_model():
        if len(memory) < BATCH_SIZE:
            return 0
        experiences = memory.sample(BATCH_SIZE)
        state_batch = torch.Tensor(experiences[0])
        action_batch = torch.LongTensor(experiences[1]).unsqueeze(1)
        reward_batch = torch.Tensor(experiences[2])
        next_state_batch = torch.Tensor(experiences[3])
        done_batch = experiences[4]
    
        pred_q = model(state_batch).gather(1, action_batch)
    
        next_state_q_vals = torch.zeros(BATCH_SIZE)
    
        for idx, next_state in enumerate(next_state_batch):
            if done_batch[idx] == True:
                next_state_q_vals[idx] = -1
            else:
                # .max in pytorch returns (values, idx), we only want vals
                next_state_q_vals[idx] = (target_net(next_state_batch[idx]).max(0)[0]).detach()
    
    
        better_pred = (reward_batch + next_state_q_vals).unsqueeze(1)
    
        loss = F.smooth_l1_loss(pred_q, better_pred)
        optimizer.zero_grad()
        loss.backward()
        for param in model.parameters():
            param.grad.data.clamp_(-1, 1)
        optimizer.step()
        return loss
    
    
    points = []
    losspoints = []
    
    #save_state = torch.load("models/DQN_target_11.pth")
    #model.load_state_dict(save_state['state_dict'])
    #optimizer.load_state_dict(save_state['optimizer'])
    
    
    
    env = gym.make('CartPole-v0')
    for i_episode in range(5000):
        observation = env.reset()
        episode_loss = 0
        if i_episode % tau == 0:
            target_net.load_state_dict(model.state_dict())
        for t in range(1000):
            #env.render()
            state = observation
            action = select_action(observation)
            observation, reward, done, _ = env.step(action)
    
            if done:
                next_state = [0,0,0,0]
            else:
                next_state = observation
    
            memory.push([state, action, reward, next_state, done])
            episode_loss = episode_loss + float(optimize_model())
            if done:
                points.append((i_episode, t+1))
                print("Episode {} finished after {} timesteps".format(i_episode, t+1))
                print("Avg Loss: ", episode_loss / (t+1))
                losspoints.append((i_episode, episode_loss / (t+1)))
                if (i_episode % 100 == 0):
                    eps = final_epsilon + (initial_epsilon - final_epsilon) * \
                        math.exp(-1. * steps_done / epsilon_decay)
                    print(eps)
                if ((i_episode+1) % 5001 == 0):
                    save = {'state_dict': model.state_dict(), 'optimizer': optimizer.state_dict()}
                    torch.save(save, "models/DQN_target_" + str(i_episode // 5000) + ".pth")
                break
    env.close()
    
    
    
    
    x = [coord[0] * 100 for coord in points]
    y = [coord[1] for coord in points]
    
    x2 = [coord[0] * 100 for coord in losspoints]
    y2 = [coord[1] for coord in losspoints]
    
    plt.plot(x, y)
    plt.plot(x2, y2)
    plt.show()
    

    这是您的绘图代码的结果。

    tau=100

    tau=10000

    【讨论】:

    • 哇,真的很深入的回答。非常感谢!
    • 请标记坐标轴
    猜你喜欢
    • 2019-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-08
    • 2021-08-19
    • 1970-01-01
    • 2017-09-18
    • 1970-01-01
    相关资源
    最近更新 更多