【深度强化学习】(2) Double DQN 模型解析,附Pytorch完整代码
创始人
2024-06-01 08:32:21
0

大家好,今天和大家分享一个深度强化学习算法 DQN 的改进版 Double DQN,并基于 OpenAI 的 gym 环境库完成一个小游戏,完整代码可以从我的 GitHub 中获得:

https://github.com/LiSir-HIT/Reinforcement-Learning/tree/main/Model


1. 算法原理

1.1 DQN 原理回顾

DQN 算法的原理是指导机器人不断与环境交互,理解最佳的行为方式,最终学习到最优的行为策略,机器人与环境的交互过程如下图所示。 

机器人与环境的交互过程是机器人在 t 时刻,采取动作 a_t 并作用于环境,然后环境从 t 时刻状态 s_t 转变到 t+1 时刻状态 s_{t+1},同时奖励函数对 a_t 进行评价得到奖励值 r_t。机器人根据 r_t 不断优化行为轨迹,最终学习到最优的行为策略。

整个强化学习过程可简化为马尔可夫决策过程(MDP)。其中所有状态均具备马尔可夫性。MDP 可用一个五元组 \left \langle S,A,P,R,\gamma \right \rangle 表示,各元素意义如下: 

(1)S 是有限状态集合,即机器人在环境中探索到的所有可能状态。s_t 表示机器人在 t 时刻的状态。

(2)A 是有限动作集合,即智能体根据 s_t 采取的所有可能动作集合。a_t 表示机器人在t 时刻状态采取的行为

(3)P 是状态转移概率,定义如下:P_{s_{t+1}}^a = P[S_{t+1}=s_{t+1}|S_t=s, A_t=a]

(4)R 是奖励函数,即机器人基于 s_t 采取 a_t 后获得的期望奖励,定义如下:R_s^a = E[R_{t+1}|s_{t+1}|S_t=s, A_t=a]

(5)γ 代表折扣因子,值域 [0,1],即未来的期望奖励在当前时刻的价值比例。

MDP 中,价值函数包括状态价值函数和动作价值函数。动作价值函数(Q 函数)表示在策略 \pi 的指导下,根据状态 s,采取行为 a 所获得的期望回报策略 \pi 表示状态到行为的映射,相当于机器人的决策策略,并根据不同的状态选择不同的行为,即 a = \pi (s)。机器人在策略 \pi 的指导下,Q 函数的定义如下:

Q_\pi (s,a)=E_\pi [G_t|S_t=s,A_t=a]

式中 G_t 表示折扣奖励,定义如下:

G_t=R_{t+1} + \gamma R_{t+2} + ... = \sum_{k=t}^{T}\gamma ^{k-t}R(s_k,a_k)

式中 \gamma ^t 随训练过程迭代减小,\gamma ^t 越小表示未来的奖励对当前时刻的奖励影响越小。

Q 函数的贝尔曼方程表示如下:

Q_\pi (s_t,a_t)=E[R(s_t,a_t)+\gamma E_{a_{t+1\sim \pi }}Q_\pi (s_{t+1}, a_{t+1})]

式中,R(s_t,a_t) 表示机器人在 s_t 时采取 a_t 所获得的即时奖励,等式右侧第二项表示机
器人执行策略 \pi 产生的未来累计奖励的期望

通过选取最大动作价值函数求解最优行为策略的公式表示如下: 

a = argmax_{a\in A}Q(s,a)

DQN 算法通过行为的奖励值构造算法训练的标签,并且其中的经验回放(Experience Replay)和目标网络有效的解决了数据相关性和非静态分布的问题。DQN 算法的结构示意图如下。

DQN 的网络结构由目标网络和估计网络组成,这两个网络的结构相同但参数不同估计网络具有最新的网络参数,计算当前状态-动作对的价值,并定期更新目标网络的参数,使其计算目标 Q 值。双网络结构打破了数据之间的相关性,使DQN 学习不同的数据分布。经验回放部分储存了智能体(机器人)的历史行为信息,其中包括多组行为序列对 (s,a,r,s'),即当前时刻状态 s ,行为 a ,奖励 r 以及下一时刻状态 s'。当 DQN 算法更新时,随机从经验池中抽取部分行为序列对进行经验回放,这种方式解决了经验池中数据相关性强和非静态分布导致的模型泛化能力差的问题。

DQN 算法通过贪婪法直接获得目标 Q 值贪婪法通过最大化方式使 Q 值快速向可能的优化目标收敛但易导致过估计Q 值的问题,使模型具有较大的偏差。DQN 算法过估计 Q 值的问题不适合机器人操作行为的研究,采用 Double DQN 算法解耦动作的选择和目标 Q 值的计算,以解决过估计 Q 值的问题。 


1.2 Double DQN 原理

Double  DQN 算法是 DQN 算法的改进版本,解决了 DQN 算法过估计行为价值的问题。DQN 算法中,某一时刻状态为非终止状态时,目标 Q 值的计算公式如下所示:

y_j = r_j + \gamma max_{a'}Q(s_{j+1},a';\theta ')

Double  DQN 算法不直接通过最大化的方式选取目标网络计算的所有可能 Q 值,而是首先通过估计网络选取最大 Q 值对应的动作,公式表示如下: 

a_{max} = argmax_a Q (s_{t+1}, a ; \theta )

然后目标网络根据 a_{max} 计算目标 Q 值,公式表示如下:

y_j = r_j+\gamma Q(s_{j+1},a_{max};\theta ')

最后将上面两个公式结合,目标 Q 值的最终表示形式如下:

y_j = r_j + \gamma Q(s_{j+1},argmax_aQ(s_{t+1,a;\theta });\theta ')

目标是最小化目标函数,即最小化估计 Q 值和目标 Q 值的差值,公式如下:

\delta = |Q(s_t,a_t)-y_t|=|Q(s_t,a_t;\theta ) - (r_t + \gamma Q(S_{t+1},argmax_aQ(s_{t+1},a;\theta );\theta ')) |

结合目标函数,损失函数定义如下:

loss=\begin{Bmatrix} \frac{1}{2}\delta ^2 & for |\delta | \leqslant 1 \\ |\delta |-\frac{1}{2} & otherwize \end{Bmatrix}

Double DQN 的伪代码:

Double DQN 算法结构如下。在 Double DQN 框架中存在两个神经网络模型,分别是训练网络与目标网络。这两个神经网络模型的结构完全相同,但是权重参数不同;每训练一段之间后,训练网络的权重参数才会复制给目标网络。训练时,训练网络用于估计当前的 Q(s_t,a),而目标网络用于估计 max_aQ(s_{t+1},a),这样就能保证真实值 Q_{target}(s_t,a) 的估计不会随着训练网络的不断自更新而变化过快。此外,DQN 还是一种支持离线学习的框架,即通过构建经验池的方式离线学习过去的经验。将均方误差 MSE(Q_{train}, Q_{target}) 作为训练模型的损失函数,通过梯度下降法进行反向传播,对训练模型进行更新;若干轮经验池采样后,再将训练模型的权重赋给目标模型,以此进行 Double DQN 框架下的模型自学习。 


2. 代码实现

模型构建部分的代码如下:

import torch
from torch import nn
from torch.nn import functional as F
import numpy as np
import collections  # 队列
import random# ----------------------------------- #
#(1)经验回放池
# ----------------------------------- #class ReplayBuffer:def __init__(self, capacity):# 创建一个队列,先进先出,队列长度不变self.buffer = collections.deque(maxlen=capacity)# 填充经验池def add(self, state, action, reward, next_state, done):self.buffer.append((state, action, reward, next_state, done))# 随机采样batch组样本数据def sample(self, batch_size):transitions = random.sample(self.buffer, batch_size)# 分别取出这些数据,*获取list中的所有值state, action, reward, next_state, done = zip(*transitions)# 将state变成数组,后面方便计算return np.array(state), action, reward, np.array(next_state), done# 队列的长度def size(self):return len(self.buffer)# ----------------------------------- #
#(2)构造网络,训练网络和目标网络共用该结构
# ----------------------------------- #class Net(nn.Module):def __init__(self, n_states, n_hiddens, n_actions):super(Net, self).__init__()# 只有一个隐含层self.fc1 = nn.Linear(n_states, n_hiddens)self.fc2 = nn.Linear(n_hiddens, n_actions)# 前向传播def forward(self, x):x = self.fc1(x)  # [b,n_states]-->[b,n_hiddens]x = self.fc2(x)  # [b,n_hiddens]-->[b,n_actions]return x# ----------------------------------- #
#(3)模型构建
# ----------------------------------- #class Double_DQN:#(1)初始化def __init__(self, n_states, n_hiddens, n_actions,learning_rate, gamma, epsilon,target_update, device):# 属性分配self.n_states = n_statesself.n_hiddens = n_hiddensself.n_actions = n_actionsself.learning_rate = learning_rateself.gamma = gammaself.epsilon = epsilonself.target_update = target_updateself.device = device# 记录迭代次数self.count = 0# 实例化训练网络self.q_net = Net(self.n_states, self.n_hiddens, self.n_actions)# 实例化目标网络self.target_q_net = Net(self.n_states, self.n_hiddens, self.n_actions)# 优化器,更新训练网络的参数self.optimizer = torch.optim.Adam(self.q_net.parameters(), lr=self.learning_rate)#(2)动作选择def take_action(self, state):# numpy[n_states]-->[1, n_states]-->Tensorstate = torch.Tensor(state[np.newaxis, :])# print('--------------------------')# print(state.shape)# 如果小于贪婪系数就取最大值reward最大的动作if np.random.random() < self.epsilon:# 获取当前状态下采取各动作的rewardactions_value = self.q_net(state)# 获取reward最大值对应的动作索引action = actions_value.argmax().item()# 如果大于贪婪系数就随即探索else:action = np.random.randint(self.n_actions)return action#(3)获取每个状态对应的最大的state_valuedef max_q_value(self, state):# list-->tensor[3]-->[1,3]state = torch.tensor(state, dtype=torch.float).view(1,-1)# 当前状态对应的每个动作的reward的最大值 [1,3]-->[1,11]-->intmax_q = self.q_net(state).max().item()return max_q#(4)网络训练def update(self, transitions_dict):# 当前状态,array_shape=[b,4]states = torch.tensor(transitions_dict['states'], dtype=torch.float)# 当前状态的动作,tuple_shape=[b]==>[b,1]actions = torch.tensor(transitions_dict['actions'], dtype=torch.int64).view(-1,1)# 选择当前动作的奖励, tuple_shape=[b]==>[b,1]rewards = torch.tensor(transitions_dict['rewards'], dtype=torch.float).view(-1,1)# 下一个时刻的状态array_shape=[b,4]next_states = torch.tensor(transitions_dict['next_states'], dtype=torch.float)# 是否到达目标 tuple_shape=[b,1]dones = torch.tensor(transitions_dict['dones'], dtype=torch.float).view(-1,1)# 当前状态[b,4]-->当前状态采取的动作及其奖励[b,2]-->actions中是每个状态下的动作索引# -->当前状态s下采取动作a得到的state_valueq_values = self.q_net(states).gather(1, actions)# 获取动作索引# .max(1)输出tuple每个特征的最大state_value及其索引,[1]获取的每个特征的动作索引shape=[b]max_action = self.q_net(next_states).max(1)[1].view(-1,1)# 下个状态的state_value。下一时刻的状态输入到目标网络,得到每个动作对应的奖励,使用训练出来的action索引选取最优动作max_next_q_values = self.target_q_net(next_states).gather(1, max_action)# 目标网络计算出的,当前状态的state_valueq_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())  # 更新目标网络# 迭代计数+1self.count += 1

3. 案例实现

我们使用 OpenAI 中的重力摆来验证模型,动作是连续型,代表力矩;状态包含三个;目的是让杆子竖直。

 环境交互和训练代码如下:

import torch
import numpy as np
import gym
from tqdm import tqdm
import matplotlib.pyplot as plt
from parsers import args
from RL_brain import ReplayBuffer, Double_DQN# GPU运算
device = torch.device("cuda") if torch.cuda.is_available() \else torch.device("cpu")# ------------------------------- #
#(1)加载环境
# ------------------------------- #env = gym.make("Pendulum-v1", render_mode="human")
n_states = env.observation_space.shape[0]  # 状态数 3
act_low = env.action_space.low  # 最小动作力矩 -2
act_high = env.action_space.high  # 最大动作力矩 +2
n_actions = 11  # 动作是连续的[-2,2],将其离散成11个动作# 确定离散动作区间后,确定其连续动作
def dis_to_con(discrete_action, n_actions):# discrete_action代表动作索引return act_low + (act_high-act_low) * (discrete_action/(n_actions-1))# 实例化经验池
replay_buffer = ReplayBuffer(args.capacity)# 实例化 Double-DQN
agent = Double_DQN(n_states,args.n_hiddens,n_actions,args.lr,args.gamma,args.epsilon,args.target_update,device)# ------------------------------- #
#(2)模型训练
# ------------------------------- #return_list = []  # 记录每次迭代的return,即链上的reward之和
max_q_value = 0  # 最大state_value
max_q_value_list = []  # 保存所有最大的state_valuefor i in range(10):  # 训练几个回合done = False  # 初始,未到达终点state = env.reset()[0]  # 重置环境episode_return = 0  # 记录每回合的returnwith tqdm(total=10, desc='Iteration %d' % i) as pbar:while True:# 状态state时做动作选择,返回索引action = agent.take_action(state)# 平滑处理最大state_valuemax_q_value = agent.max_q_value(state) * 0.005 + \max_q_value * 0.995# 保存每次迭代的最大state_valuemax_q_value_list.append(max_q_value)# 将action的离散索引连续化action_continuous = dis_to_con(action, n_actions)# 环境更新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() > args.min_size:# 在经验池中随机抽样batch组数据s, a, r, ns, d = replay_buffer.sample(args.batch_size)# 构造训练集transitions_dict = {'states': s,'actions': a,'next_states': ns,'rewards': r,'dones': d,}# 模型训练agent.update(transitions_dict)# 到达终点就停止if done is True: break# 保存每回合的returnreturn_list.append(episode_return)pbar.set_postfix({'step':agent.count,'return':'%.3f' % np.mean(return_list[-10:])})pbar.update(1)# ------------------------------- #
#(3)绘图
# ------------------------------- #plt.subplot(121)
plt.plot(return_list)
plt.title('return')
plt.subplot(122)
plt.plot(max_q_value_list)
plt.title('max_q_value')
plt.show()

相关内容

热门资讯

边锋杭麻圈安卓系统,边锋科技引... 你有没有听说过边锋杭麻圈安卓系统?这可是最近在游戏圈里火得一塌糊涂的存在哦!想象你正坐在家里,手握着...
ios系统能转移到安卓系统,轻... 你有没有想过,有一天你的手机从iOS系统跳转到安卓系统,会是怎样的体验呢?这可不是天方夜谭,随着科技...
安卓系统网络连接查看,安卓系统... 你有没有遇到过这种情况:手机里装了各种APP,可就是不知道哪个在偷偷消耗着你的流量?别急,今天就来教...
什么安卓系统优化的好,打造流畅... 你有没有发现,手机用久了,就像人一样,会变得有些“臃肿”呢?尤其是安卓系统,有时候感觉就像一个老态龙...
手机安卓系统ios系统怎么安装... 你有没有发现,现在手机里的软件真是五花八门,让人眼花缭乱?无论是安卓系统还是iOS系统,都能轻松安装...
按音量键报警安卓系统,安卓系统... 你有没有遇到过这种情况:手机突然发出刺耳的警报声,吓得你心跳加速,还以为发生了什么大事?别担心,今天...
安卓系统改win版系统怎么安装... 你有没有想过,把你的安卓手机换成Windows系统的电脑呢?想象那流畅的触控体验和强大的办公功能,是...
安卓系统如何备份到苹果,轻松实... 你是不是也有过这样的烦恼:手机里的照片、联系人、应用数据等等,突然间就消失得无影无踪?别担心,今天就...
修改安卓系统参数设置,安卓系统... 你有没有发现,你的安卓手机有时候就像一个不听话的小孩子,总是按照自己的节奏来,让你有点头疼?别急,今...
安卓手机gps定位系统,畅享智... 你有没有发现,现在不管走到哪里,手机都能帮你找到路?这都得归功于安卓手机的GPS定位系统。想象你站在...
华为不能安卓系统升级,探索创新... 你知道吗?最近有个大新闻在科技圈里炸开了锅,那就是华为的新款手机竟然不能升级安卓系统了!这可真是让人...
汽车加装安卓系统卡住,探究原因... 你有没有遇到过这样的尴尬情况:汽车加装了安卓系统,结果屏幕突然卡住了,就像被施了魔法一样,怎么也动弹...
电量壁纸安卓系统下载,打造个性... 手机电量告急,是不是又得赶紧找充电宝了?别急,今天就来给你安利一款超实用的电量壁纸,让你的安卓手机瞬...
iPhonex里面是安卓系统,... 你有没有想过,那个我们每天都离不开的iPhone,里面竟然可能是安卓系统?是的,你没听错,就是那个以...
ios系统比安卓系统好在哪里,... 你有没有想过,为什么有些人对iOS系统情有独钟,而有些人却对安卓系统爱不释手呢?今天,就让我带你从多...
安卓系统跟踪设置大小,跟踪设置... 你知道吗?现在智能手机几乎成了我们生活的必需品,而安卓系统作为全球最受欢迎的操作系统之一,它的跟踪设...
在线迎新系统下载安卓,轻松开启... 你有没有想过,开学季的到来,就像一场盛大的狂欢,而在这个狂欢中,有一个小助手,它默默地守护着你的入学...
安卓系统怎么申请微信号,一键申... 你有没有想过,在安卓手机上申请一个微信账号,竟然也能变得如此简单?没错,就是那个我们每天离不开的社交...
安卓手机系统里怎么清理,轻松优... 手机里的东西越来越多,是不是感觉安卓手机系统越来越慢了呢?别急,今天就来教你怎么清理安卓手机系统,让...
安卓系统改定位地址软件,轻松掌... 你是不是也和我一样,有时候想换个角度看世界,但又不想真的搬家?别急,今天就来给你揭秘一个神奇的小工具...