动手学强化学习笔记-Dueling DQN

        在Dueling DQN中,我们引入优势函数,从而Q网络被建模为,其中V为状态价值函数,A则为该状态采取不同动作的优势函数,表示采取不同动作的差异性,我们利用神经网络分别输出状态价值函数和优势函数,再求和得到Q值。

        将状态价值函数和优势函数分别建模的好处是:某些环境下智能体只会关注状态价值而并不关心不同动作导致的差异,将二者分开建模可以更好地处理与动作关联较小的状态,因为每次更新时,函数状态价值函数V都会被更新,这样子的同时也会影响到其他动作的Q值,而传统的DQN算法只会更新某个动作的Q值,因此采用Dueling DQN算法能够更加准确地估计Q值。

        然而对于上述的Q值公式,存在对于V值和A值建模不唯一性的问题,如果加V之加上任意大小的常数C,再将所有A值减去C,则Q值不变,也就是说神经网络中的V和A是在上下波动的,如果波动的幅度相同,方向相反,那么神经网络的Q值输出相同,而V值和A值的上下波动会使得神经网络不稳定,没有很好的训练效果。

        为了解决这一问题,我们可以强制取到最优动作时的优势函数输出为0,即:

        当取最优动作a*时,A(s,a*)=maxA(s,a'),从而maxQ(s,a)=V(s),此时最优的Q有唯一的V,确保了V值建模的唯一性。对于该式子的证明,我们可以对优势函数A*(s,a)=Q*(s,a)-V*(s),两边同时取最优动作a*,则有maxA*(s,a*)=maxQ*(s,a*)-V*(s),又根据最优策略与Q值的关系(贝尔曼最优方程),有V*(s)=maxQ*(s,a),可以得到maxA*(s,a)=0。

        那么就可以得到Q*(s,a)=V*(s)+A*(s,a)-maxA*(s,a)。

        在实现过程中,我们还可以用平均操作替代max操作,即:

这个操作其实并没有什么科学依据,只是实践经验证明这样子的效果更加稳定

下面我们采用倒立摆环境

导入库

import random
import gym
import numpy as np
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
import rl_utils
from tqdm import tqdm

定义经验回放池

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是一个列表,每一个元素是一个聚合体
        transitions=random.sample(self.buffer,batch_size)
        #zip是将列表分离,然后将所有聚合体的第一个元素给state,第二个给action,以此类推
        state,action,reward,next_state,done=zip(*transitions)
        #所以分配之后,state,action等等都是一个列表
        return np.array(state),action,reward,np.array(next_state),done
    
    def size(self):  #目前buffer中数据的数量
        return len(self.buffer)

定义VA网络

class VAnet(torch.nn.Module):
    """只有一层隐藏层的A网络和V网络"""
    def __init__(self,state_dim,hidden_dim,action_dim):
        super(VAnet,self).__init__()
        self.fc1=torch.nn.Linear(state_dim,hidden_dim)  #共享网络部分
        #A值是状态-动作对的优势值,而V是状态的价值,不需要考虑动作,所以A网络输出层的感知机是action_dim个,而V网络是一个
        self.fc_A=torch.nn.Linear(hidden_dim,action_dim)
        self.fc_V=torch.nn.Linear(hidden_dim,1)
        
    def forward(self,x):
        A=self.fc_A(F.relu(self.fc1(x)))
        #print(A.shape)
        V=self.fc_V(F.relu(self.fc1(x)))
        #print(V.shape)
        #这里计算时,V以及A.mean(1).veiw(-1,1)会自动广播到与A的形状相同的张量再进行计算
        #A.mean(1)就是按列索引求平均值,也就是求每一行的平均值,所以将A变成[1,]的张量,然后再通过view增加他的维度,变成[1,1],其中维度1的1是根据view的1直接添加的,而view的-1表示自动计算
        #因为我们需要得到的是Q,也是状态-动作对的价值,也需要考虑到action,因此[1,11]的形状是没有问题的
        Q=V+A-A.mean(1).view(-1,1)   #Q值由V值和A值计算得到
        #print(Q.shape)
        return Q

在DQN代码的基础上进行修改,使用Dueling DQN

class DQN:
    ''' DQN算法,包括Double DQN和Dueling DQN '''
    def __init__(self,
                 state_dim,
                 hidden_dim,
                 action_dim,
                 learning_rate,
                 gamma,
                 epsilon,
                 target_update,
                 device,
                 dqn_type='VanillaDQN'):
        self.action_dim = action_dim
        if dqn_type == 'DuelingDQN':  # Dueling DQN采取不一样的网络框架
            self.q_net = VAnet(state_dim, hidden_dim,
                               self.action_dim).to(device)
            self.target_q_net = VAnet(state_dim, hidden_dim,
                                      self.action_dim).to(device)
        else:
            self.q_net = Qnet(state_dim, hidden_dim,
                              self.action_dim).to(device)
            self.target_q_net = Qnet(state_dim, hidden_dim,
                                     self.action_dim).to(device)
        self.optimizer = torch.optim.Adam(self.q_net.parameters(),
                                          lr=learning_rate)
        self.gamma = gamma
        self.epsilon = epsilon
        self.target_update = target_update
        self.count = 0
        self.dqn_type = dqn_type
        self.device = device

    def take_action(self, state):
        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 max_q_value(self, state):
        state = torch.tensor([state], dtype=torch.float).to(self.device)
        return self.q_net(state).max().item()

    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)
        if self.dqn_type == 'DoubleDQN':
            #max(1)得到的是最大值及其索引,最大值是[0],索引是[1],所以这里采用max(1)[1]是得到最大值的动作
            max_action = self.q_net(next_states).max(1)[1].view(-1, 1)
            #得到动作之后,再利用目标网络计算最大动作的Q值
            max_next_q_values = self.target_q_net(next_states).gather(
                1, max_action)
        else:
            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)
        dqn_loss = torch.mean(F.mse_loss(q_values, q_targets))
        self.optimizer.zero_grad()
        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

定义离线策略训练函数

def train_DQN(agent,env,num_episodes,replay_buffer,minimal_size,batch_size):
    return_list=[]
    max_q_value_list=[]
    max_q_value=0
    for i in range(10):
        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)
                    #指数加权移动平均法,对历史数据的逐步遗忘和当前数据的重要性更新
                    #加权处理,实现对历史数据的逐步遗忘和重要性更新
                    max_q_value=agent.max_q_value(state)*0.005+max_q_value*0.995   #平滑处理
                    max_q_value_list.append(max_q_value)   #保存每个状态的最大Q值
                    action_continuous=dis_to_con(action,env,agent.action_dim)
                    #训练神经网络时是将离散化的动作传进去的,然后让环境做动作时应该将动作还原回连续的状态
                    next_state,reward,done,_=env.step([action_continuous])
                    replay_buffer.add(state,action,reward,next_state,done)
                    state=next_state
                    episode_return+=reward
                    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,'rewards':b_r,'next_states':b_ns,'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)
    return return_list,max_q_value_list

进行训练,并将结果可视化

random.seed(0)
np.random.seed(0)
env.seed(0)
torch.manual_seed(0)
replay_buffer = rl_utils.ReplayBuffer(buffer_size)
agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon,
            target_update, device, 'DuelingDQN')
return_list, max_q_value_list = train_DQN(agent, env, num_episodes,
                                          replay_buffer, minimal_size,
                                          batch_size)

episodes_list = list(range(len(return_list)))
mv_return = rl_utils.moving_average(return_list, 5)
plt.plot(episodes_list, mv_return)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('Dueling DQN on {}'.format(env_name))
plt.show()

frames_list = list(range(len(max_q_value_list)))
plt.plot(frames_list, max_q_value_list)
plt.axhline(0, c='orange', ls='--')
plt.axhline(10, c='red', ls='--')
plt.xlabel('Frames')
plt.ylabel('Q value')
plt.title('Dueling DQN on {}'.format(env_name))
plt.show()

相比于传统的DQN,Dueling DQN在多个动作选择下的学习更加稳定,得到的回报最大值也更大。

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DQN算法是一种基于Q-learning的深度强化学习算法,其目标是习一个Q函数,使得该函数能够最大化累积奖励。DDQN算法是对DQN算法的改进,通过解决DQN算法中过高估计Q值的问题,提高了算法的性能。Dueling DQN算法则是在DDQN算法的基础上,提出了一种新的神经网络结构,使得算法的习效率更高。 下面是DQN算法的公式推导分析: 1. Q-learning的更新公式为:$Q(s_t,a_t) \leftarrow Q(s_t,a_t) + \alpha(r_{t+1} + \gamma \max_{a} Q(s_{t+1},a) - Q(s_t,a_t))$ 2. DQN算法使用了深度神经网络来逼近Q函数,将更新公式改为:$Q(s_t,a_t) \leftarrow Q(s_t,a_t) + \alpha(r_{t+1} + \gamma \max_{a} Q(s_{t+1},a; \theta^-) - Q(s_t,a_t; \theta))$,其中$\theta$为当前网络的参数,$\theta^-$为目标网络的参数,$\max_{a} Q(s_{t+1},a; \theta^-)$表示在下一个状态$s_{t+1}$中,选择动作$a$所得到的最大Q值。 3. DDQN算法在DQN算法的基础上,使用了双网络结构,解决了DQN算法中过高估计Q值的问题。更新公式为:$Q(s_t,a_t) \leftarrow Q(s_t,a_t) + \alpha(r_{t+1} + \gamma Q(s_{t+1},\arg\max_{a} Q(s_{t+1},a; \theta); \theta^-) - Q(s_t,a_t; \theta))$,其中$\arg\max_{a} Q(s_{t+1},a; \theta)$表示在下一个状态$s_{t+1}$中,选择动作$a$所得到的最大Q值对应的动作。 4. Dueling DQN算法在DDQN算法的基础上,提出了一种新的神经网络结构,使得算法的习效率更高。具体来说,Dueling DQN算法的输出包括两个分支,分别是该状态的状态价值V(标量)和每个动作的优势值A(与动作空间同维度的向量)。网络结构如下图所示: [Dueling DQN网络结构](https://img-blog.csdn.net/20170727145756345?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2hlbnhpYW9fYmFpZHUx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/q/80) 更新公式为:$Q(s_t,a_t) \leftarrow V(s_t) + (A(s_t,a_t) - \frac{1}{|\mathcal{A}|} \sum_{a} A(s_t,a))$,其中$V(s_t)$表示状态$s_t$的价值,$A(s_t,a_t)$表示在状态$s_t$下选择动作$a_t$的优势值,$\frac{1}{|\mathcal{A}|} \sum_{a} A(s_t,a)$表示所有动作的平均优势值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值