本博客的理论知识来自王树森老师《深度强化学习》,这本书写得简直太好了,强烈推荐,只是现在还在校对没出版,可能有些小瑕疵,但并不影响阅读和学习。
本次的 A2C 的原理我们从带基线的策略梯度开始,在对带基线的策略梯度做蒙特卡洛近似,得到策略梯度的一个无偏估计:
g
(
s
,
a
,
;
θ
)
=
[
Q
π
(
s
,
a
)
−
V
π
(
s
)
⋅
∇
ln
π
(
a
∣
s
;
θ
)
]
(1)
\boldsymbol{g}(s,a,;\boldsymbol{\theta}) = \left[ Q_\pi(s, a) - V_\pi (s) \cdot \nabla \ln \pi(a|s;\theta)\right] \tag1
g(s,a,;θ)=[Qπ(s,a)−Vπ(s)⋅∇lnπ(a∣s;θ)](1)
公式中的
Q
π
−
V
π
Q_\pi - V_\pi
Qπ−Vπ 被称作优势函数 (Advantage Function)。因此,基于上面公式得到的 Actor-Critic 方法被称为 Advantage Actor-Critic,缩写 A2C。A2C 属于 Actor-Critic 方法。有一个策略网络
π
(
a
∣
s
;
θ
)
\pi(a|s;\theta)
π(a∣s;θ),相当于演员,用于控制智能体运动。还有一个价值网络
v
(
s
;
w
)
v(s; w)
v(s;w),相当于评委,他的评分可以帮助策略网络(演员)改进技术。

训练价值网络: 训练价值网络
v
(
s
;
w
)
v(s; w)
v(s;w) 的算法是从贝尔曼公式来的:
V
π
(
s
t
)
=
E
A
t
∼
π
(
⋅
∣
s
t
;
θ
)
[
E
S
t
+
1
∼
p
(
⋅
∣
s
t
,
A
t
)
[
R
t
+
γ
⋅
V
π
(
S
t
+
1
)
]
]
V_\pi(s_t) = \mathbb{E}_{A_t∼\pi(\cdot|s_t;\theta)}[\mathbb{E}_{S_{t+1}∼p(\cdot|s_t, A_t)}[R_t + \gamma \cdot V_\pi(S_{t+1})]]
Vπ(st)=EAt∼π(⋅∣st;θ)[ESt+1∼p(⋅∣st,At)[Rt+γ⋅Vπ(St+1)]]
对贝尔曼方程左右两边做近似:
v
(
s
t
;
w
)
v(s_t; w)
v(st;w) 和
y
^
t
\hat y_t
y^t 都是对动作价值
V
π
(
s
t
)
Vπ_(s_t)
Vπ(st) 的估计。由于
y
^
t
\hat y_t
y^t 部分基于真实观测到的奖励
r
t
r_t
rt,我们认为
y
^
t
\hat y_t
y^t 比
v
(
s
t
;
w
)
v(s_t;\boldsymbol{w})
v(st;w) 更可靠。所以把
y
^
t
\hat y_t
y^t 固定住,更新
w
\boldsymbol{w}
w,使得
v
(
s
t
;
w
)
v(s_t;\boldsymbol{w})
v(st;w) 更接近
y
^
t
\hat y_t
y^t。具体一点,定义损失函数:
L
(
w
)
:
=
1
2
[
v
(
s
t
;
w
)
−
y
^
t
]
2
L(\boldsymbol{w}) := \frac{1}{2}[v(s_t;\boldsymbol{w}) - \hat y_t]^2
L(w):=21[v(st;w)−y^t]2
设
v
^
t
:
=
v
(
s
t
;
w
)
\hat v_t := v(s_t;\boldsymbol{w})
v^t:=v(st;w),损失函数的梯度为:
∇
w
L
(
w
)
=
(
v
^
t
−
y
^
t
)
⏟
T
D
误差
δ
t
⋅
∇
w
v
(
s
t
;
w
)
\nabla_{\boldsymbol{w}}L(\boldsymbol{w}) = \underbrace{(\hat v_t - \hat y_t)}_{TD\ 误差\ \delta_t} \cdot \nabla_{\boldsymbol{w}}v(s_t;\boldsymbol{w})
∇wL(w)=TD 误差 δt
(v^t−y^t)⋅∇wv(st;w)
定义 TD 误差为
δ
:
=
v
^
t
−
y
^
t
\delta := \hat v_t - \hat y_t
δ:=v^t−y^t。做一轮梯度下降更新
w
\boldsymbol{w}
w:
w
←
w
−
α
⋅
δ
t
⋅
∇
w
v
(
s
t
;
w
)
\boldsymbol{w} \leftarrow \boldsymbol{w}- \alpha \cdot \delta_t \cdot \nabla_{\boldsymbol{w}}v(s_t;\boldsymbol{w})
w←w−α⋅δt⋅∇wv(st;w)
这样可以让价值网络的预测
v
(
s
t
;
w
)
v(s_t;\boldsymbol{w})
v(st;w) 更接近
y
^
t
\hat y_t
y^t。
训练策略网络:A2C 从公式 (1) 出发,对
g
(
s
,
a
,
;
θ
)
\boldsymbol{g}(s,a,;\boldsymbol{\theta})
g(s,a,;θ) 做近似,记作
g
~
\boldsymbol{\tilde{g}}
g~,然后用
g
~
\boldsymbol{\tilde{g}}
g~ 更新策略网络参数
θ
\boldsymbol{\theta}
θ。下面我们做数学推导。回忆一下贝尔曼公式:
Q
π
(
s
t
,
a
t
)
=
E
S
t
+
1
∼
p
(
⋅
∣
s
t
,
A
t
)
[
R
t
+
γ
⋅
V
π
(
S
t
+
1
)
]
Q_\pi(s_t ,a_t) = \mathbb{E}_{S_{t+1}∼p(\cdot|s_t, A_t)}[R_t + \gamma \cdot V_\pi(S_{t+1})]
Qπ(st,at)=ESt+1∼p(⋅∣st,At)[Rt+γ⋅Vπ(St+1)]
把近似策略梯度
g
(
s
,
a
,
;
θ
)
\boldsymbol{g}(s,a,;\boldsymbol{\theta})
g(s,a,;θ) 中的
Q
π
(
s
t
,
a
t
)
Q_π(s_t, a_t)
Qπ(st,at) 替换成上面的期望,得到:
g
(
s
,
a
,
;
θ
)
=
[
Q
π
(
s
t
,
a
t
)
−
V
π
(
s
t
)
]
⋅
∇
θ
ln
π
(
a
t
∣
s
t
;
θ
)
=
[
E
S
t
+
1
[
R
t
+
γ
⋅
V
π
(
S
t
+
1
)
]
−
V
π
(
S
t
)
]
⋅
∇
θ
ln
π
(
a
t
∣
s
t
;
θ
)
当智能体执行动作
a
t
a_t
at 之后,环境给出新的状态
s
t
+
1
s_{t+1}
st+1 和奖励
r
t
r_t
rt;利用
s
t
+
1
s_{t+1}
st+1 和
r
t
r_t
rt 对上面的期望做蒙特卡洛近似,得到:
g
(
s
,
a
,
;
θ
)
≈
[
r
t
+
γ
⋅
V
π
(
s
t
+
1
)
−
V
π
(
s
t
)
]
⋅
∇
ln
π
(
a
∣
s
;
θ
)
\boldsymbol{g}(s,a,;\boldsymbol{\theta}) \approx [ r_t + \gamma \cdot V_\pi(s_{t+1}) - V_\pi(s_t)] \cdot \nabla \ln \pi(a|s;\theta)
g(s,a,;θ)≈[rt+γ⋅Vπ(st+1)−Vπ(st)]⋅∇lnπ(a∣s;θ)
进一步把状态价值函数
V
π
(
s
)
V_π(s)
Vπ(s) 替换成价值网络
v
(
s
;
w
)
v(s; \boldsymbol{w})
v(s;w),得到:
g
~
(
s
,
a
,
;
θ
)
:
=
[
r
t
+
γ
⋅
v
(
s
;
w
)
⏟
T
D
目标
y
^
t
−
v
(
s
;
w
)
⋅
∇
ln
π
(
a
∣
s
;
θ
)
\boldsymbol{\tilde g}(s,a,;\boldsymbol{\theta}):=[ \underbrace{r_t + \gamma \cdot v(s; \boldsymbol{w})}_{TD\ 目标 \ \hat y_t} - v(s; \boldsymbol{w}) \cdot \nabla \ln \pi(a|s;\theta)
g~(s,a,;θ):=[TD 目标 y^t
rt+γ⋅v(s;w)−v(s;w)⋅∇lnπ(a∣s;θ)
前面定义了 TD 目标和 TD 误差,
y
t
^
:
=
r
t
+
γ
⋅
v
(
s
t
+
1
;
w
)
\hat{y_t} := r_t + \gamma \cdot v(s_{t+1}; \boldsymbol{w})
yt^:=rt+γ⋅v(st+1;w) 和
δ
t
:
=
v
(
s
t
;
w
)
−
y
^
t
\delta_t := v(s_t;\boldsymbol{w}) - \hat y_t
δt:=v(st;w)−y^t。因此,可以将
g
(
s
,
a
,
;
θ
)
:
=
−
δ
t
⋅
∇
ln
π
(
a
∣
s
;
θ
)
\boldsymbol{g}(s,a,;\boldsymbol{\theta}) :=- \delta _t \cdot \nabla \ln \pi(a|s;\theta)
g(s,a,;θ):=−δt⋅∇lnπ(a∣s;θ)
g
~
\boldsymbol{\tilde{g}}
g~ 是
g
\boldsymbol{{g}}
g 的近似,所以也是策略梯度
∇
θ
J
(
θ
)
\nabla_{\boldsymbol{\theta}}J(\boldsymbol{\theta})
∇θJ(θ) 的近似。用
g
~
\boldsymbol{\tilde{g}}
g~更新策略网络参数
θ
\boldsymbol{\theta}
θ:
θ
←
θ
+
β
⋅
g
(
s
,
a
,
;
θ
)
\boldsymbol{\theta} \leftarrow \boldsymbol{\theta} + \beta \cdot \boldsymbol{g}(s,a,;\boldsymbol{\theta})
θ←θ+β⋅g(s,a,;θ)
这样可以让目标函数
J
(
θ
)
J(\boldsymbol{\theta})
J(θ) 变大。
下面概括 A2C 训练流程。设当前策略网络参数是 θ n o w \boldsymbol{\theta_{now}} θnow,价值网络参数是 w n o w \boldsymbol{w_{now}} wnow。执行下面的步骤,将参数更新成 θ n e w \boldsymbol{\theta_{new}} θnew 和 w n e w \boldsymbol{w_{new}} wnew:
上述训练价值网络的算法存在自举——即用价值网络自己的估值 v ^ t + 1 \hat v_{t + 1} v^t+1 去更新价值网络自己。为了缓解自举造成的偏差,可以使用目标网络 (Target Network) 计算 TD 目标。把目标网络记作 v ( s ; w − ) v(s; \boldsymbol{w^{-}}) v(s;w−),它的结构与价值网络的结构相同,但是参数不同。使用目标网络计算 TD 目标,那么 A2C 的训练就变成了:
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import gym
import numpy as np
LR_ACTOR = 0.01 # 策略网络的学习率
LR_CRITIC = 0.001 # 价值网络的学习率
GAMMA = 0.9 # 奖励的折扣因子
EPSILON = 0.9 # ϵ-greedy 策略的概率
TARGET_REPLACE_ITER = 100 # 目标网络更新的频率
env = gym.make('CartPole-v0') # 加载游戏环境
N_ACTIONS = env.action_space.n # 动作数
N_SPACES = env.observation_space.shape[0] # 状态数量
env = env.unwrapped
# 网络参数初始化,采用均值为 0,方差为 0.1 的高斯分布
def init_weights(m) :
if isinstance(m, nn.Linear) :
nn.init.normal_(m.weight, mean = 0, std = 0.1)
# 策略网络
class Actor(nn.Module) :
def __init__(self):
super(Actor, self).__init__()
self.net = nn.Sequential(
nn.Linear(N_SPACES, 50),
nn.ReLU(),
nn.Linear(50, N_ACTIONS) # 输出为各个动作的概率,维度为 3
)
def forward(self, s):
output = self.net(s)
output = F.softmax(output, dim = -1) # 概率归一化
return output
# 价值网络
class Critic(nn.Module) :
def __init__(self):
super(Critic, self).__init__()
self.net = nn.Sequential(
nn.Linear(N_SPACES, 20),
nn.ReLU(),
nn.Linear(20, 1) # 输出值是对当前状态的打分,维度为 1
)
def forward(self, s):
output = self.net(s)
return output
# A2C 的主体函数
class A2C :
def __init__(self):
# 初始化策略网络,价值网络和目标网络。价值网络和目标网络使用同一个网络
self.actor_net, self.critic_net, self.target_net = Actor().apply(init_weights), Critic().apply(init_weights), Critic().apply(init_weights)
self.learn_step_counter = 0 # 学习步数
self.optimizer_actor = optim.Adam(self.actor_net.parameters(), lr = LR_ACTOR) # 策略网络优化器
self.optimizer_critic = optim.Adam(self.critic_net.parameters(), lr = LR_CRITIC) # 价值网络优化器
self.criterion_critic = nn.MSELoss() # 价值网络损失函数
def choose_action(self, s):
s = torch.unsqueeze(torch.FloatTensor(s), dim = 0) # 增加维度
if np.random.uniform() < EPSILON : # ϵ-greedy 策略对动作进行采取
action_value = self.actor_net(s)
action = torch.max(action_value, dim = 1)[1].item()
else :
action = np.random.randint(0, N_ACTIONS)
return action
def learn(self, s, a, r, s_):
if self.learn_step_counter % TARGET_REPLACE_ITER == 0 : # 更新目标网络
self.target_net.load_state_dict(self.critic_net.state_dict())
self.learn_step_counter += 1
s = torch.FloatTensor(s)
s_ = torch.FloatTensor(s_)
q_actor = self.actor_net(s) # 策略网络
q_critic = self.critic_net(s) # 价值对当前状态进行打分
q_next = self.target_net(s_).detach() # 目标网络对下一个状态进行打分
q_target = r + GAMMA * q_next # 更新 TD 目标
td_error = (q_critic - q_target).detach() # TD 误差
# 更新价值网络
loss_critic = self.criterion_critic(q_critic, q_target)
self.optimizer_critic.zero_grad()
loss_critic.backward()
self.optimizer_critic.step()
# 更新策略网络
log_q_actor = torch.log(q_actor)
actor_loss = log_q_actor[a] * td_error
self.optimizer_actor.zero_grad()
actor_loss.backward()
self.optimizer_actor.step()
a2c = A2C()
for epoch in range(10000) :
s = env.reset()
ep_r = 0
while True :
env.render()
a = a2c.choose_action(s) # 选择动作
s_, r, done, info = env.step(a)# 执行动作
x, x_dot, theta, theta_dot = s_
# 修改奖励,为了更快收敛
r1 = (env.x_threshold - abs(x)) / env.x_threshold - 0.8
r2 = (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5
r = r1 + r2
ep_r += r
# 学习
a2c.learn(s, a, r, s_)
if done :
break
s = s_
print(f'Ep: {epoch} | Ep_r: {round(ep_r, 2)}')