datawhale学习-深度强化学习7:DQN算法

        DQN:Deep Q-Network,即在Q-learning算法基础上加入了深度神经网络来近似动作价值函数Q(s,a),从而能够处理高维的状态空间。

1 深度网络

1.1 定义:

        深度神经网络就是一个函数,将输入向量x映射到输出向量y,并且拥有可以学习的参数,这些参数使用梯度下降的方法来优化。Q表可以近似动作价值函数Q(s,a),将状态向量s作为输入,并输出所有动作a=(a1,a2,...,an)对应的价值。

        Q表是一个二维表格,只能处理离散的状态和动作空间,神经网络则可以处理连续的输入,并且可以处理高维的状态空间。Q表中我们描述状态空间一般用的是状态个数,神经网络则是状态维度。无论是Q表还是神经网络,它们输出的都是预测,而不是直接输出动作。

1.2 更新:

        Q-learning算法的更新公式:

         α代表学习率,没有额外参数,只需要一步步迭代更新Q值即可。

        DQN算法的更新公式:引入了额外的网络参数θ。

         yt与Q值的绝对差值记为TD误差,可以写成损失函数的形式使用梯度下降的方式来求解参数θ:

1.3 区别与联系 

        由此可以看出,强度学习与深度学习训练方式一样,都是将样本输入网络中,然后通过梯度下降来更新网络参数,使得损失函数最小,逼近最真实的Q值。不同的地方在于,强化学习训练的样本是通过与环境实时交互得到的,而深度学习的样本是事先准备好的。强化学习用于解决序列决策问题,深度学习用于解决静态问题如回归、分类、识别等。

2 经验回放

     神经网络的训练集是事先准备好的,每次迭代时的样本都是从训练集中随机抽取的,其样本都是独立同分布的。而强化学习的样本都是从环境中实时交互得到的,这样的样本都是有关联的。但是由于使用梯度下降的一个假设前提就是样本是独立同分布的,以及使用的都是小批量梯度下降,不能每次都使用单个样本直接去迭代网络。

        所以DQN模型要使用梯度下降需要做一些处理,这就是经验回放的实现初衷。在DQN中,我们每次都将与环境交互得到的样本存储在一个经验回放中,然后从经验池中随机抽取一批样本来训练网络,以此来实现迭代样本独立同分布,满足梯度下降法的前提假设,避免训练的不稳定性。  

 

         经验回放的容量需要有一定的容量限制,这是因为在深度学习中得到的样本都是事先准备好的很好的样本,但是在强化学习中得到的样本都是智能体生成的实时样本,在训练初期可能可以得到很好的效果去朝着很好的方向去收敛,但是到后期这些产生的样本就相对来说质量不是很好,投入深度学习网络就会影响其稳定。所以其容量太小会导致收集的样本具有一定的局限性,太大则会失去经验本身的意义。

3 目标网络

        DQN算法中还有一个重要的技巧,即使用了一个每隔若干步才更新的目标网络。目标网络和当前网络结构相同,都用于近似Q值,在实践中每隔若干步才将每步更新的当前网络复制给目标网络,这样可以保证训练的稳定,避免Q值的估计发散。

4 实战:DQN算法(以下均来自学习资料)

 4.1 伪代码

        DQN的训练过程包括交互采样以及模型更新两个步骤,其中交互采样的目的是与环境交互产生样本,模型更新则是利用得到的样本来更新相关的网络参数。

 4.2 定义模型

        首先是定义两个神经网络,即当前网络以及目标网络,两个网络的结构相同,使用一个Python类来定义。

class MLP(nn.Module): # 所有网络必须继承 nn.Module 类,这是 PyTorch 的特性
    def __init__(self, input_dim,output_dim,hidden_dim=128):
        super(MLP, self).__init__() 
        # 定义网络的层,这里都是线性层
        self.fc1 = nn.Linear(input_dim, hidden_dim) # 输入层
        self.fc2 = nn.Linear(hidden_dim,hidden_dim) # 隐藏层
        self.fc3 = nn.Linear(hidden_dim, output_dim) # 输出层
        
    def forward(self, x):
        # 各层对应的激活函数
        x = F.relu(self.fc1(x)) 
        x = F.relu(self.fc2(x))
        return self.fc3(x) # 输出层不需要激活函数

        这里定义了一个三层的全连接网络,输入维度就是状态数,输出维度就是动作数,中间的隐藏层使用ReLU激活函数。使用pytorch的module类来定义网络,所以网络都必须继承这个类。在pytorch中只需要定义网络的前向传播,即forward函数,反向传播过程pytorch会自动完成,这也是pytorch的特性。

4.3 经验回放

        经验回放主要实现缓存样本以及取出样本两个功能。

class ReplayBuffer:
    def __init__(self, capacity):
        self.capacity = capacity # 经验回放的容量
        self.buffer = [] # 用列表存放样本
        self.position = 0 # 样本下标,便于覆盖旧样本
    
    def push(self, state, action, reward, next_state, done):
        ''' 缓存样本
        '''
        if len(self.buffer) < self.capacity: # 如果样本数小于容量
            self.buffer.append(None)
        self.buffer[self.position] = (state, action, reward, next_state, done)
        self.position = (self.position + 1) % self.capacity 
    
    def sample(self, batch_size):
        ''' 取出样本,即采样
        '''
        batch = random.sample(self.buffer, batch_size) # 随机采出小批量转移
        state, action, reward, next_state, done =  zip(*batch) # 解压成状态,动作等
        return state, action, reward, next_state, done
    
    def __len__(self):
        ''' 返回当前样本数
        '''
        return len(self.buffer)
 4.4 定义智能体

        智能体即策略的载体,主要功能是根据当前的状态输出动作和更新策略,分别于伪代码中的交互采样以及模型更新过程相对应。

class Agent:
    def __init__(self):
        # 定义当前网络
        self.policy_net = MLP(state_dim,action_dim).to(device) 
        # 定义目标网络
        self.target_net = MLP(state_dim,action_dim).to(device)
        # 将当前网络参数复制到目标网络中
        self.target_net.load_state_dict(self.policy_net.state_dict())
        # 定义优化器
        self.optimizer = optim.Adam(self.policy_net.parameters(), lr=learning_rate) 
        # 经验回放
        self.memory = ReplayBuffer(buffer_size)
        self.sample_count = 0  # 记录采样步数
    def sample_action(self,state):
        ''' 采样动作,主要用于训练
        '''
        self.sample_count += 1
        # epsilon 随着采样步数衰减
        self.epsilon = self.epsilon_end + (self.epsilon_start - self.epsilon_end) * math.exp(-1. * self.sample_count / self.epsilon_decay) 
        if random.random() > self.epsilon:
            with torch.no_grad(): # 不使用梯度
                state = torch.tensor(np.array(state), device=self.device, dtype=torch.float32).unsqueeze(dim=0)
                q_values = self.policy_net(state)
                action = q_values.max(1)[1].item() # choose action corresponding to the maximum q value
        else:
            action = random.randrange(self.action_dim)
    def predict_action(self,state):
        ''' 预测动作,主要用于测试
        '''
        with torch.no_grad():
            state = torch.tensor(np.array(state), device=self.device, dtype=torch.float32).unsqueeze(dim=0)
            q_values = self.policy_net(state)
            action = q_values.max(1)[1].item() # choose action corresponding to the maximum q value
        return action
    def update(self):
        pass

         DQN算法的更新方式:

def update(self, share_agent=None):
    # 当经验回放中样本数小于更新的批大小时,不更新算法
    if len(self.memory) < self.batch_size: # when transitions in memory donot meet a batch, not update
        return
    # 从经验回放中采样
    state_batch, action_batch, reward_batch, next_state_batch, done_batch = self.memory.sample(
        self.batch_size)
    # 转换成张量(便于GPU计算)
    state_batch = torch.tensor(np.array(state_batch), device=self.device, dtype=torch.float) 
    action_batch = torch.tensor(action_batch, device=self.device).unsqueeze(1) 
    reward_batch = torch.tensor(reward_batch, device=self.device, dtype=torch.float).unsqueeze(1) 
    next_state_batch = torch.tensor(np.array(next_state_batch), device=self.device, dtype=torch.float) 
    done_batch = torch.tensor(np.float32(done_batch), device=self.device).unsqueeze(1) 
    # 计算 Q 的实际值
    q_value_batch = self.policy_net(state_batch).gather(dim=1, index=action_batch) # shape(batchsize,1),requires_grad=True
    # 计算 Q 的估计值,即 r+\gamma Q_max
    next_max_q_value_batch = self.target_net(next_state_batch).max(1)[0].detach().unsqueeze(1) 
    expected_q_value_batch = reward_batch + self.gamma * next_max_q_value_batch* (1-done_batch)
    # 计算损失
    loss = nn.MSELoss()(q_value_batch, expected_q_value_batch)  
    # 梯度清零,避免在下一次反向传播时重复累加梯度而出现错误。
    self.optimizer.zero_grad()  
    # 反向传播
    loss.backward()
    # clip避免梯度爆炸
    for param in self.policy_net.parameters():  
        param.grad.data.clamp_(-1, 1)
    # 更新优化器
    self.optimizer.step() 
    # 每C(target_update)步更新目标网络
    if self.sample_count % self.target_update == 0: 
        self.target_net.load_state_dict(self.policy_net.state_dict())   
 4.5 定义环境

        环境名称叫做Cart Pole,中文译作推车杆游戏,属于OpenAI Gym平台。

官网环境介绍:Cart Pole - Gymnasium Documentation

         环境的奖励设置时每个时步下能维持杆不倒就给一个+1的奖励,因此没有终止状态。前面基于TD的算法都需要一个终止状态,所以我们可以设置一个环境的最大步数,比如我们可以设置如果在200个时步内坚持杆子不倒就近似说明学到了一个不错的策略。

4.6 设置参数

        定义好智能体与环境之后就可以设置参数了:

self.epsilon_start = 0.95  # epsilon 起始值
self.epsilon_end = 0.01  # epsilon 终止值
self.epsilon_decay = 500  # epsilon 衰减率
self.gamma = 0.95  # 折扣因子
self.lr = 0.0001  # 学习率
self.buffer_size = 100000  # 经验回放容量
self.batch_size = 64  # 批大小
self.target_update = 4  # 目标网络更新频率

5 本章小结

 本章讲了深度强化学习最为基础的 DQN 算法,相比于 Q-learning 算法,除了用神经网络来替代 Q 表这项改进之外,还提出了目标网络、经验回放等技巧,主要优化引入神经网络带来的局部最小值问题。最后,我们利用 PyTorch 框架实现了 DQN 算法并取得了不错的效果。

6 练习题

1.相比于Q-learning 算法,DQN 算法做了哪些改进?

改进主要包括:1、经验回放;2、目标网络;3、深度神经网络;4、TD误差。

2.为什么要在 DQN 算法中引入 greedy 策略?

引入该策略是为了在探索和利用中取得平衡,greedy策略意味着在选择动作时,算法会选择当前估计Q值最高的动作,这是一种利用已有知识来最大化累积奖励的方法。

3.DQN 算法为什么要多加一个目标网络?

为了解决Q-learning算法使用深度神经网络时的一些问题,可以提高训练的稳定性以及收敛性,可以防止过度估计以及减轻相关性。

4.经验回放的作用是什么?

经验回放通过存储和重新利用经验数据,使得深度强化学习算法更加稳定、高效地学习,并且能够更好地应对实际环境中的复杂性。可以解耦时间关系、数据重用、避免非静态分布以及提高样本效率。

  • 27
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值