主要内容:
- 讲解DQN基本理论,
- 基于Pytorch实现DQN算法
- 开发智能体控制月球车着陆
- DQN完整代码下载链接见文末
文章目录
1 DQN基本原理
DQN算法是David Silver等人2015年在《Nature》发表的一个工作,报告了他们训练的DQN智能体在几十款Atari视频游戏中超越了人类的平均水平。而且他们的智能体是基于游戏画面做决策的,即和人类一样,这令很多人都非常的震惊。并且他们使用同一个智能体玩几十款游戏,因此该智能体具有一定的通用型,让人们看到了通用人工智能AGI的可能性。
1.1 Q-learnig等表格型RL算法的不足
Q-learning是一种表格型强化学习算法,可以解决规模不大的具有离散状态和动作空间的问题。但是,自然界更多的问题具有连续的状态和动作空间。或者,状态和动作的规模过于庞大,用表进行处理的存储空间和时间都无法实现。
1.2 神经网络拟合Q函数(NFQ)
为了应对连续装状态的问题,使用神经网络来拟合Q函数,可以估计任意状态下执行各个动作的未来奖励和的期望。这种想法其实很早就有了,且被称为Neural Fitted Q iteration (NFQ, Riedmiller, 2005)。
将神经网络的参数记为 w w w,则价值函数 Q ( s , a ; w ) Q(s,a;w) Q(s,a;w)的特性是由参数 w w w唯一确定的。
回顾Q-learning的更新:
Q
(
s
,
a
)
←
Q
(
s
,
a
)
+
α
[
r
+
γ
Q
(
s
′
,
a
′
)
−
Q
(
s
,
a
)
]
Q(s,a)\leftarrow Q(s,a)+\alpha\left[r+\gamma Q(s^{\prime},a^{\prime})-Q(s,a)\right]
Q(s,a)←Q(s,a)+α[r+γQ(s′,a′)−Q(s,a)]
现在没有表格了,Q函数的输入输出是由神经网络的参数
w
w
w决定的,需要更新参
w
w
w来更新Q函数。那么怎么调整神经网络的参数
w
w
w呢?在NFQ中使用了一个叫做target的概念,表达式是这样的:
t
a
r
g
e
t
=
r
+
γ
Q
(
s
′
,
a
′
)
target=r+\gamma Q(s^\prime,a^\prime)
target=r+γQ(s′,a′)
这个target是根据下个状态和即使奖励获得的,如果Q函数对当前的状态估计的足够准确,那么二者因该是相等的:
Q
(
s
,
a
)
=
t
a
r
g
e
t
Q(s,a)=target
Q(s,a)=target
因此,构造误差函数
e
r
r
o
r
=
[
Q
(
s
,
a
)
−
[
r
+
γ
Q
(
s
′
,
a
′
)
]
]
2
error=\left[Q(s,a)-\left[r+\gamma Q(s^{\prime},a^{\prime})\right]\right]^2
error=[Q(s,a)−[r+γQ(s′,a′)]]2
要想办法减小该误差。方法就是梯度下降。
怎么组织数据呢?按照 ⟨ s , a , r , s ′ ⟩ \langle s,a,r,s^\prime\rangle ⟨s,a,r,s′⟩这样的格式组织数据,形成样本集合 D \mathcal D D。这样就可以off-line的方式进行学习。
1.3 DQN算法
DQN与NFQ一脉相承,Riedmiller也是DQN的作者之一。
DQN作了这些方面的改进:
- 使用经验重放池 D \mathcal D D,每次从经验池中随机采样样本进行学习,打破样本之间的相关性;
- 使用带延迟的target网络 w − w^- w−,稳定训练过程。每隔一定时间用 w w w去更新 w − w^- w−。
DQN算法的工作流程,可以用下面的图表示
经验池的构建:智能体与环境交互,并将经验数据按照 ⟨ s , a , r , s ′ ⟩ \langle s,a,r,s^\prime\rangle ⟨s,a,r,s′⟩的形式存储到经验重放池中。假设经验重放池中有 N N N条样本,则经验池可以表示为 D = { s t , a t , r t + 1 , s t + 1 } t = 0 N − 1 D=\left\{s_t,a_t,r_{t+1},s_{t+1}\right\}_{t=0}^{N-1} D={st,at,rt+1,st+1}t=0N−1
智能体训练:从经验池 D \mathcal D D中随机采样一个mini-batch,计算损失函数,即单步时间差分误差的平方:
L ( w ) = ( y − Q ( s , a ; w ) ) 2 \mathcal{L}(w)=\left(y-Q(s,a;w)\right)^2 L(w)=(y−Q(s,a;w))2
其中
y
=
{
r
,
如果
s
′
是终止状态
r
+
γ
max
a
′
Q
(
s
′
,
a
′
;
w
−
)
,
其他情况
y=\begin{cases}r,\quad\text{如果}s^\prime\text{是终止状态}\\r+\gamma\max_{a^\prime}Q(s^\prime,a^\prime;w^-),\quad\text{其他情况}\end{cases}
y={r,如果s′是终止状态r+γmaxa′Q(s′,a′;w−),其他情况
考虑非终止状态,
L
(
w
)
=
(
r
+
γ
max
a
′
Q
(
s
′
,
a
′
;
w
−
)
−
Q
(
s
,
a
;
w
)
)
2
\mathcal{L}(w)=\left(r+\gamma\max_{a^\prime}Q\left(s^\prime,a^\prime;{\color{red}{w^{-}} }\right) - Q(s,a;w)\right)^2
L(w)=(r+γa′maxQ(s′,a′;w−)−Q(s,a;w))2
进行梯度下降调整神经网络参数时,针对的是
w
w
w,而
w
−
{\color{red}{w^-}}
w−此时是固定住的。那么
w
−
{\color{red}{w^-}}
w−是怎么更新的呢?他是通过与
w
w
w进行同步来更新的。这里具体来说,使用的是一种软更新的技术,即
w
−
←
(
1
−
ξ
)
w
−
+
ξ
w
{\color{red}{w^-}}\leftarrow(1-\xi){\color{red}{w^-}}+\xi w
w−←(1−ξ)w−+ξw
其中
ξ
>
0
\xi>0
ξ>0是一个很小的正数,称为更新率。
2 编程实现
下面介绍如何使用Pytorch开发一个DQN智能体,并训练它完月球车着陆任务。
2.1 引入需要用到的库
import gym
import random
import torch
import numpy as np
from collections import deque
import matplotlib.pyplot as plt
%matplotlib inline
2.2 实例化环境和智能体
按照下列代码创建环境
env = gym.make('LunarLander-v2')
env.seed(0)
print('State shape: ', env.observation_space.shape)
print('Number of actions: ', env.action_space.n)
State shape: (8,)
Number of actions: 4
先来看看这是个什么样的任务。
from dqn_agent import Agent
agent = Agent(state_size=8, action_size=4, seed=0)
# 看看随机选择动作的智能体与环境交互是什么样子
state = env.reset()
for j in range(200):
action = agent.act(state)
env.render()
state, reward, done, _ = env.step(action)
if done:
break
env.close()
2.3 训练DQN智能体
下面是实现训练过程的主要程序
def dqn(n_episodes=2000, max_t=1000, eps_start=1.0, eps_end=0.01, eps_decay=0.995):
"""Deep Q-Learning.
Params
======
n_episodes (int): maximum number of training episodes
max_t (int): maximum number of timesteps per episode
eps_start (float): starting value of epsilon, for epsilon-greedy action selection
eps_end (float): minimum value of epsilon
eps_decay (float): multiplicative factor (per episode) for decreasing epsilon
"""
scores = [] # 用于记录智能体的得分
scores_window = deque(maxlen=100) # 用于记录最近100幕得分
eps = eps_start # 初始探索率
for i_episode in range(1, n_episodes+1):
state = env.reset()
score = 0
for t in range(max_t):
action = agent.act(state, eps)
next_state, reward, done, _ = env.step(action)
agent.step(state, action, reward, next_state, done)
state = next_state
score += reward
if done:
break
scores_window.append(score) # 保存当前幕得分
scores.append(score) # 保存当前幕得分
eps = max(eps_end, eps_decay*eps) # 减小探索率
print('\rEpisode {}\tAverage Score: {:.2f}'.format(i_episode, np.mean(scores_window)), end="")
if i_episode % 100 == 0:
print('\rEpisode {}\tAverage Score: {:.2f}'.format(i_episode, np.mean(scores_window)))
if np.mean(scores_window)>=200.0:
print('\nEnvironment solved in {:d} episodes!\tAverage Score: {:.2f}'.format(i_episode-100, np.mean(scores_window)))
torch.save(agent.qnetwork_local.state_dict(), 'checkpoint.pth')
break
return scores
scores = dqn()
# plot the scores
fig = plt.figure()
ax = fig.add_subplot(111)
plt.plot(np.arange(len(scores)), scores)
plt.ylabel('Score')
plt.xlabel('Episode #')
plt.show()
Episode 100 Average Score: -135.01
Episode 200 Average Score: -106.86
Episode 274 Average Score: -91.379
2.4 DQN智能体实现文件dqn_agent.py
文件dqn_agent.py实现了DQN智能体的经验重放池、Q网络、学习方法等。
import numpy as np
import random
from collections import namedtuple, deque
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
class Agent():
"""DQN智能体,实现能够与环境交互并学习."""
def __init__(self, state_size, action_size, seed, buffer_size=int(1e5),
batch_size=64, gamma=0.99, tau=1e-3, lr=1e-3):
"""初始化智能体.
参数
======
state_size (int): (观测)状态维度大小
action_size (int): 动作数量
seed (int): 随机种子
"""
self.state_size = state_size
self.action_size = action_size
self.seed = random.seed(seed)
self.BUFFER_SIZE = buffer_size # 经验池大小
self.BATCH_SIZE = batch_size # 训练用的批大小
self.GAMMA = gamma # 折扣因子
self.TAU = tau # 目标网络更新参数
self.LR = lr # 优化器学习参数
# Q-Network
self.qnetwork_local = QNetwork(state_size, action_size, seed).to(device) # 用于和环境交互的神经网络
self.qnetwork_target = QNetwork(state_size, action_size, seed).to(device) # 目标神经网络
self.optimizer = optim.Adam(self.qnetwork_local.parameters(), lr=self.LR)
# 经验池
self.buffer = ReplayBuffer(action_size, self.BUFFER_SIZE, self.BATCH_SIZE, seed)
# 初始化交互次数计数器
self.t_step = 0
def add_traj(self, traj):
"""将一条轨迹存入经验池中
"""
for transition in traj:
self.buffer.memory.append(transition)
def act(self, state, eps=0., action_mask=None):
"""完成从状态到动作的映射.
Params
======
state (array_like): (观测)状态
eps (float): 即epsilon, 贪婪策略的探索率
"""
state = torch.from_numpy(state).float().unsqueeze(0).to(device)
self.qnetwork_local.eval()
with torch.no_grad():
action_values = self.qnetwork_local(state)
self.qnetwork_local.train()
if action_mask is not None:
action_values = action_values * torch.tensor(action_mask, dtype=torch.float32).to(device)
# 按照贪婪策略选择动作
if random.random() > eps:
return np.argmax(action_values.cpu().data.numpy())
elif action_mask is not None:
return np.random.choice(np.arange(self.action_size), p=action_mask / (np.sum(action_mask)) + 1e-9)
else:
return random.choice(np.arange(self.action_size))
def learn(self):
"""使用经验数据更新两个价值网络的参数。
这是DQN的核心部分。
Params
======
experiences (Tuple[torch.Tensor]): 按照(s, a, r, s', done) 这样的格式组织的经验数据
gamma (float): 折扣因子
"""
if len(self.buffer) < 10 * self.BATCH_SIZE:
return 0
experiences = self.buffer.sample()
states, actions, rewards, next_states, dones = experiences
# 使用目标网络估计下个状态的价值
Q_next = self.qnetwork_target(next_states).detach().max(1)[0].unsqueeze(1)
y = rewards + (self.GAMMA * Q_next * (1 - dones)) # 使用单步TD估计当前状态的价值
Q = self.qnetwork_local(states).gather(1, actions) # 使用局部网络直接估计当前状态的价值
loss = F.mse_loss(Q, y) # 计算TD误差,作为损失函数
# 用优化器优化神经网络
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
# ------------------- 更新目标网络参数 ------------------- #
self.soft_update(self.qnetwork_local, self.qnetwork_target, self.TAU)
def soft_update(self, local_model, target_model, tau):
"""两个神经网络参数之间的软更新:
θ_target = τ*θ_local + (1 - τ)*θ_target
Params
======
local_model (PyTorch model): 用于和环境交互的网络模型
target_model (PyTorch model): 目标网络模型
tau (float): 更新步长
"""
for target_param, local_param in zip(target_model.parameters(), local_model.parameters()):
target_param.data.copy_(tau * local_param.data + (1.0 - tau) * target_param.data)
class ReplayBuffer:
"""用于存储智能体与环境交互的经验"""
def __init__(self, action_size, buffer_size, batch_size, seed):
"""Initialize a ReplayBuffer object.
Params
======
action_size (int): 动作数量
buffer_size (int): 经验池大小
batch_size (int): 训练
seed (int): 随机种子
"""
self.action_size = action_size
self.memory = deque(maxlen=buffer_size)
self.batch_size = batch_size
self.transition = namedtuple("Transition", field_names=["state", "action", "reward", "next_state", "done"])
self.seed = random.seed(seed)
def sample(self):
"""从经验池中随机采样一定数量的样本."""
experiences = random.sample(self.memory, k=self.batch_size)
states = torch.from_numpy(np.vstack([e.state for e in experiences if e is not None])).float().to(device)
actions = torch.from_numpy(np.vstack([e.action for e in experiences if e is not None])).long().to(device)
rewards = torch.from_numpy(np.vstack([e.reward for e in experiences if e is not None])).float().to(device)
next_states = torch.from_numpy(np.vstack([e.next_state for e in experiences if e is not None])).float().to(
device)
dones = torch.from_numpy(np.vstack([e.done for e in experiences if e is not None]).astype(np.uint8)).float().to(
device)
return (states, actions, rewards, next_states, dones)
def __len__(self): # 方便别的地方使用len()获取经验池汇中transition的数量
return len(self.memory)
class QNetwork(nn.Module):
"""价值估计网络."""
def __init__(self, state_size, action_size, seed, hidden_size_1=256, hidden_size_2=128):
"""初始化价值网络.
Params
======
state_size (int): 状态的维度大小
action_size (int): 动作空维度数量
seed (int): 随机种子
hidden_size_1 (int): 第隐藏曾1的神经元数量
hidden_size_2 (int): 第隐藏曾2的神经元数量
"""
super(QNetwork, self).__init__()
self.seed = torch.manual_seed(seed)
self.input_layer = nn.Linear(state_size, hidden_size_1)
self.hidden_layer = nn.Linear(hidden_size_1, hidden_size_2)
self.output_layer = nn.Linear(hidden_size_2, action_size)
def forward(self, state):
"""数据的前向传播"""
x = F.relu(self.input_layer(state))
x = F.relu(self.hidden_layer(x))
return self.output_layer(x)
2.5 观看训练好的智能体
# 加载训练好的神经网络模型
agent.qnetwork_local.load_state_dict(torch.load('checkpoint.pth'))
for i in range(3):
state = env.reset()
for j in range(200):
action = agent.act(state)
env.render()
state, reward, done, _ = env.step(action)
if done:
break
env.close()
LunarLander-V2
3 代码下载
DQN源码下载地址:
链接:https://pan.baidu.com/s/1JMA46DvE7JuqVpKLUnTCtA
提取码:bb4m