DQN代码逐行详解

首先,写下这篇博客有两方面原因,一方面是为了自己复习(一个月前明明理顺代码了,现在再看又忘了),另一方面帮助和我一样的初学者快速理解DQN的代码吧。

之前的DQN算法的博客,包含基础理论和代码:https://blog.csdn.net/qq_47997583/article/details/124457346
建议边看上面博客的代码边看本文。

1.代码整体

整体上来说,我们需要定义三个类ReplayBufferQnetDQN
首先从主函数部分开始看:
我们定义的num_episodes为500,通过两个for循环,将整体分为10个iteration,每个50个episode,然后通过进度条显示训练过程。每个episode首先初始化回合奖励为0,得到初始state和初始化done为False。之后执行训练直到当前回合的done是false,循环中首先通过将DQN实例化的agent中的take_action函数采样一个动作,将动作通过step函数得到返回的四元组,然后将这个四元组添加到经验回放池,之后更新state和回合奖励,当经验回放池中数据数量超过500开始进行训练。在训练的时候从经验回放池抽取64个数据传入agent的update函数进行参数更新。
在这里插入图片描述

for i in range(10):  #我们定义的num_episodes为500,这里分为10个iteration,每个50个episode,然后通过进度条显示训练过程
    with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:
        for i_episode in range(int(num_episodes / 10)):
            episode_return = 0
            state = env.reset()
            done = False
            while not done:
                action = agent.take_action(state)
                next_state, reward, done, _ = env.step(action)
                replay_buffer.add(state, action, reward, next_state, done)
                state = next_state
                episode_return += reward
                # 当buffer数据的数量超过一定值后,才进行Q网络训练
                if replay_buffer.size() > minimal_size:
                    b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)
                    transition_dict = {
                        'states': b_s,
                        'actions': b_a,
                        'next_states': b_ns,
                        'rewards': b_r,
                        'dones': b_d
                    }
                    agent.update(transition_dict)
            return_list.append(episode_return)
            if (i_episode + 1) % 10 == 0:
                pbar.set_postfix({
                    'episode':
                    '%d' % (num_episodes / 10 * i + i_episode + 1),
                    'return':
                    '%.3f' % np.mean(return_list[-10:])
                })
            pbar.update(1)

2.三个类的实现

1.ReplayBuffer类的实现
首先初始化定义时需要用collections包中的deque函数定义一个buffer。之后需要实现add,sample,size三个方法,其中add需要传入五元组(s,a,r,s,d)并且以元组的形式存入buffer中;sample函数传入batch_size的大小,通过random.sample函数抽样批量大小的数据到transition中,之后通过zip(*)函数解包(注:解包后是多个元组)得到的批量大小的五元组,最后返回五元组并将state转为numpy类型;size就是查看buffer大小。

class ReplayBuffer:
    ''' 经验回放池 '''
    def __init__(self, capacity):
        self.buffer = collections.deque(maxlen=capacity)  # 队列,先进先出

    def add(self, state, action, reward, next_state, done):  # 将数据加入buffer
        self.buffer.append((state, action, reward, next_state, done))

    def sample(self, batch_size):  # 从buffer中采样数据,数量为batch_size
        transitions = random.sample(self.buffer, batch_size)
        state, action, reward, next_state, done = zip(*transitions)
        return np.array(state), action, reward, np.array(next_state), done

    def size(self):  # 目前buffer中数据的数量
        return len(self.buffer)

2.Qnet实现

class Qnet(torch.nn.Module):
    ''' 只有一层隐藏层的Q网络 '''
    def __init__(self, state_dim, hidden_dim, action_dim):
        super(Qnet, self).__init__()
        self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
        self.fc2 = torch.nn.Linear(hidden_dim, action_dim)

    def forward(self, x):
        x = F.relu(self.fc1(x))  # 隐藏层使用ReLU激活函数
        return self.fc2(x)

3.DQN实现
DQN类的实现是核心的部分。其中需要实现take_action和update方法。

class DQN:
    ''' DQN算法 '''
    def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
                 epsilon, target_update, device):
        self.action_dim = action_dim
        self.q_net = Qnet(state_dim, hidden_dim,
                          self.action_dim).to(device)  # Q网络
        # 目标网络
        self.target_q_net = Qnet(state_dim, hidden_dim,
                                 self.action_dim).to(device)
        # 使用Adam优化器
        self.optimizer = torch.optim.Adam(self.q_net.parameters(),
                                          lr=learning_rate)
        self.gamma = gamma  # 折扣因子
        self.epsilon = epsilon  # epsilon-贪婪策略
        self.target_update = target_update  # 目标网络更新频率
        self.count = 0  # 计数器,记录更新次数
        self.device = device

    def take_action(self, state):  # epsilon-贪婪策略采取动作
        if np.random.random() < self.epsilon:
            action = np.random.randint(self.action_dim)
        else:
            state = torch.tensor([state], dtype=torch.float).to(self.device)
            action = self.q_net(state).argmax().item()
        return action

    def update(self, transition_dict):
        states = torch.tensor(transition_dict['states'],
                              dtype=torch.float).to(self.device)
        actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(
            self.device)
        rewards = torch.tensor(transition_dict['rewards'],
                               dtype=torch.float).view(-1, 1).to(self.device)
        next_states = torch.tensor(transition_dict['next_states'],
                                   dtype=torch.float).to(self.device)
        dones = torch.tensor(transition_dict['dones'],
                             dtype=torch.float).view(-1, 1).to(self.device)

        q_values = self.q_net(states).gather(1, actions)  # Q值
        # 下个状态的最大Q值
        max_next_q_values = self.target_q_net(next_states).max(1)[0].view(
            -1, 1)
        q_targets = rewards + self.gamma * max_next_q_values * (1 - dones
                                                                )  # TD误差目标
        dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))  # 均方误差损失函数
        self.optimizer.zero_grad()  # PyTorch中默认梯度会累积,这里需要显式将梯度置为0
        dqn_loss.backward()  # 反向传播更新参数
        self.optimizer.step()

        if self.count % self.target_update == 0:
            self.target_q_net.load_state_dict(
                self.q_net.state_dict())  # 更新目标网络
        self.count += 1

take_action
其中take_action方法中传入的state的形式如下:
在这里插入图片描述
经过这行代码处理后

state = torch.tensor([state], dtype=torch.float).to(self.device)

变为
在这里插入图片描述
将state传入神经网络然后输出1*2的向量,通过.argmax()函数取到较大Q值的索引值,.item()函数从tensor中取到实数。

action = self.q_net(state).argmax().item()

update
这是核心中的核心,传入的transition_dict如下图形式:
在这里插入图片描述
首先需要这个字典中的五个变量转为tensor并传到cuda中。
在这里插入图片描述
q_values是从t时刻估计动作价值函数Q,max_next_q_values从t+1时刻估计动作价值函数Q’。
看这行代码:

q_values = self.q_net(states).gather(1, actions)

在这里插入图片描述

gather(1, actions) # 按列索引,索引值为actions

在这里插入图片描述
在这里插入图片描述
在看这行代码

 max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1) # max(1)代表按列值索引

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

q_targets = rewards + self.gamma * max_next_q_values * (1 - dones)
 # 如果done了那么对应的Q值为0
dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))
# 定义为均方损失函数
self.optimizer.zero_grad()  
# PyTorch中默认梯度会累积,这里需要显式将梯度置为0
dqn_loss.backward()  
# 反向传播更新参数
self.optimizer.step()
  • 24
    点赞
  • 140
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,以下是一个简单的DQN代码示例,并逐行注释: ```python import gym import random import numpy as np from collections import deque from keras.models import Sequential from keras.layers import Dense from keras.optimizers import Adam class DQNAgent: def __init__(self, state_size, action_size): self.state_size = state_size self.action_size = action_size self.memory = deque(maxlen=2000) # 定义记忆库,长度为2000 self.gamma = 0.95 # 折扣因子 self.epsilon = 1.0 # 探索率 self.epsilon_min = 0.01 # 探索率的下限 self.epsilon_decay = 0.995 # 探索率的衰减率 self.learning_rate = 0.001 # 学习率 self.model = self._build_model() # 定义神经网络模型 def _build_model(self): # 定义一个三层的全连接神经网络模型 model = Sequential() model.add(Dense(24, input_dim=self.state_size, activation='relu')) model.add(Dense(24, activation='relu')) model.add(Dense(self.action_size, activation='linear')) model.compile(loss='mse', optimizer=Adam(lr=self.learning_rate)) return model def remember(self, state, action, reward, next_state, done): # 将当前状态、动作、奖励、下一个状态和done标识存储到记忆库中 self.memory.append((state, action, reward, next_state, done)) def act(self, state): # 以一定概率随机选择动作,或者根据当前状态选择最优动作 if np.random.rand() <= self.epsilon: return random.randrange(self.action_size) else: return np.argmax(self.model.predict(state)[0]) def replay(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 = (reward + self.gamma * np.amax(self.model.predict(next_state)[0])) target_f = self.model.predict(state) target_f[0][action] = target self.model.fit(state, target_f, epochs=1, verbose=0) if self.epsilon > self.epsilon_min: self.epsilon *= self.epsilon_decay def load(self, name): # 加载神经网络模型的权重 self.model.load_weights(name) def save(self, name): # 保存神经网络模型的权重 self.model.save_weights(name) if __name__ == "__main__": # 创建CartPole-v1环境 env = gym.make('CartPole-v1') state_size = env.observation_space.shape[0] action_size = env.action_space.n agent = DQNAgent(state_size, action_size) done = False batch_size = 32 for e in range(1000): state = env.reset() state = np.reshape(state, [1, state_size]) for time in range(500): env.render() action = agent.act(state) next_state, reward, done, _ = env.step(action) reward = reward if not done else -10 next_state = np.reshape(next_state, [1, state_size]) agent.remember(state, action, reward, next_state, done) state = next_state if done: print("episode: {}/{}, score: {}, e: {:.2}" .format(e, 1000, time, agent.epsilon)) break if len(agent.memory) > batch_size: agent.replay(batch_size) if e % 50 == 0: agent.save("cartpole-dqn.h5") ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值