强化学习紧急预习

Last updated on December 22, 2025 12:15 AM

紧急预习用的,比较草,有 AIGC,自用。

REINFORCE

之前的类似 DQN 的方法是对价值函数用神经网络来近似,这里考虑直接对策略用网络。

我们的目标是,找到最佳的参数 θ\theta^*

θ=argmaxθEτpθ(τ)[tr(st,at)]\theta^* = \arg\max_{\theta} E_{\tau \sim p_{\theta}(\tau)} \left[\sum_t r(s_t, a_t)\right]

即最大化服从 pθ(τ)p_\theta (\tau) 的轨迹 τ\tau 的轨迹回报的期望(J(θ)J(\theta))。

这个东西显然算不了,可以考虑 MC 近似,一次采样 NN 条轨迹:

J(θ)=Eτpθ(τ)[tr(st,at)]1Nitr(si,t,ai,t)J(\theta) =E_{\tau \sim p_{\theta}(\tau)} \left[\sum_t r(s_t, a_t)\right] \approx \frac 1N \sum_i \sum_t r(s_{i,t}, a_{i,t})

对数恒等式:

pθ(τ)θlogpθ(τ)=pθ(τ)θpθ(τ)pθ(τ)=θpθ(τ)p_\theta (\tau) \nabla_\theta \log p_\theta (\tau) = p_\theta (\tau) \frac{\nabla_\theta p_\theta (\tau)}{p_{\theta}(\tau)} = \nabla_\theta p_\theta (\tau)

利用这个可以把 θpθ(τ)\nabla_\theta p_\theta (\tau) 这个没法算的东西拆开。

从积分的角度推导 JθJ_\theta

J(θ)=Eτpθ(τ)[tr(st,at)]=pθ(τ)r(τ)dτJ(\theta) =E_{\tau \sim p_{\theta}(\tau)} \left[\sum_t r(s_t, a_t)\right] = \int p_\theta (\tau) r(\tau) \mathrm{d} \tau

求梯度:

θJ(θ)=θpθ(τ)r(τ)dτ=pθ(τ)θlogpθ(τ)r(τ)dτ=Eτpθ(τ)[θlogpθ(τ)r(τ)]\begin{aligned} \nabla_\theta J(\theta) &= \int \nabla_\theta p_\theta (\tau) r(\tau) \mathrm{d} \tau\\ &= \int p_\theta (\tau) \nabla_\theta \log p_\theta(\tau) r(\tau) \mathrm{d} \tau\\ &= E_{\tau \sim p_\theta (\tau)} \left[\nabla_\theta \log p_\theta(\tau) r(\tau) \right] \end{aligned}

我们看一下 pθ(τ)p_\theta (\tau),发现对于一个轨迹 {s1,a1,,sT,aT}\{s_1,a_1,\cdots, s_T, a_T\},我们知道

pθ(τ)=p(s1)t=1Tπθ(atst)p(st+1st,at)p_\theta (\tau) = p(s_1) \prod_{t=1}^T \pi_\theta (a_t | s_t) p(s_{t+1}| s_t,a_t)

其中 πθ(atst)\pi_\theta (a_t | s_t) 为模型输出的“在 sts_t 下选择 ata_t 的概率”,p(st+1st,at)p(s_{t+1}|s_t,a_t)环境动力学,与 θ\theta 无关。两边取对数,再关于 θ\theta 求梯度:

logpθ(τ)=logp(s1)+t=1T(logπθ(atst)+logp(st+1st,at))θlogpθ(τ)=θlogp(s1)+t=1T(θlogπθ(atst)+θlogp(st+1st,at))=t=1Tθlogπθ(atst)\begin{aligned} \log p_\theta(\tau) &= \log p(s_1) + \sum_{t=1}^T \left(\log \pi_\theta(a_t|s_t) + \log p(s_{t+1}|s_t,a_t) \right)\\ \nabla_\theta\log p_\theta(\tau) &= {\color{gray}{\nabla_\theta\log p(s_1)}} + \sum_{t=1}^T \left(\nabla_\theta\log \pi_\theta(a_t|s_t) + {\color{gray}{\nabla_\theta\log p(s_{t+1}|s_t,a_t)}} \right)\\ &= \sum_{t=1}^T \nabla_\theta \log \pi_\theta (a_t|s_t) \end{aligned}

标灰色的部分因为和 θ\theta 无关,所以求梯度的时候都被消掉了。

于是我们最终得到

θJ(θ)=Eτpθ(τ)[θlogpθ(τ)r(τ)]=Eτpθ(τ)[(t=1Tθlogπθ(atst))(t=1Tr(st,at))]\begin{aligned} \nabla_\theta J(\theta) &= E_{\tau \sim p_\theta (\tau)} \left[\nabla_\theta \log p_\theta(\tau) r(\tau) \right]\\ &= E_{\tau\sim p_\theta (\tau)}\left[\left(\sum_{t=1}^T \nabla_\theta \log \pi_\theta (a_t | s_t) \right) \cdot \left( \sum_{t=1}^T r(s_t,a_t) \right) \right] \end{aligned}

这个强大之处在于:

  • Model free:不需要知道环境是怎么运作的;
  • 奖励不需要可导:完全不需要对奖励求导,只需要采样累计回报就可以了;
  • 通过对数导数技巧允许我们将“求整个概率分布变化的梯度”这样一个不可能完成的任务,转化成了“在玩游戏过程中,看看哪些动作带来了高分,就增大那个动作的概率对数”这样一个可以通过代码实现的任务。

所以得到最简单的策略梯度算法 REINFORCE:

REINFORCE 算法:

  1. πθ(atst)\pi_\theta(a_t|s_t) 中采样 τi{\tau^i}(run episodes)
  2. θJ(θ)i(tθlogπθ(atisti))(tr(stiati))\nabla_\theta J(\theta) \approx \sum_i (\sum_t \nabla_\theta \log \pi_\theta(a_t^i|s_t^i))(\sum_t r(s_t^i|a_t^i))
  3. θθ+αθJ(θ)\theta \leftarrow \theta + \alpha \nabla_\theta J(\theta)

Python 的直观实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import torch
import torch.optim as optim
import numpy as np

def train_one_episode(env, policy_net, optimizer):
# 1. 玩游戏,收集数据 (对应公式中的期望 E)
# 我们没法求无穷积分,所以我们玩一局游戏作为“采样”
states, actions, rewards = [], [], []
state = env.reset()
done = False

while not done:
# 获取策略网络的输出概率
# probs 对应幻灯片里的 π_θ(a|s)
probs = policy_net(torch.tensor(state))

# 根据概率采样动作
dist = torch.distributions.Categorical(probs)
action = dist.sample()

# 记录 log_prob (这是求导的关键!)
# 对应幻灯片里的 log π_θ(a_t | s_t)
log_prob = dist.log_prob(action)

# 与环境交互
next_state, reward, done, _ = env.step(action.item())

# 存储这一步的信息
rewards.append(reward)
# 这里的 saved_log_probs 稍后会自动帮我们计算 ∇ log π
policy_net.saved_log_probs.append(log_prob)
state = next_state

# 2. 计算总回报 (对应公式中的 Σ r)
# 也就是幻灯片里右边那个括号
R = sum(rewards)

# 3. 计算损失函数 (Loss)
policy_loss = []

# 对每一步进行处理
for log_prob in policy_net.saved_log_probs:
# 公式是: ∇ J = E [ ∇ log π * R ]
# 因为 PyTorch 默认是做梯度下降(最小化 Loss),而我们想做梯度上升(最大化回报)
# 所以我们在公式前面加个负号
loss = -log_prob * R
policy_loss.append(loss)

# 求和得到整条轨迹的 Loss
# 对应幻灯片底部公式的两个求和符号乘积
final_loss = torch.stack(policy_loss).sum()

# 4. 反向传播更新参数 (Direct Differentiation)
optimizer.zero_grad()
final_loss.backward() # 这一步自动计算了 ∇θ
optimizer.step() # 更新 θ

和 Behavior Cloning 的不同在于,BC 平等对待每个 (s,a)(s,a) 对(人家干啥你就干啥),RL 的话会对于 r(τ)r(\tau) 加权,对带来更高回报的进行更大加权。

BC:数据太好也不行,太差也不行

RL:几乎没法在真机上做。

REINFORCE 可能遇到的问题:

  1. 虽然是无偏的,但是方差可能很高
  2. 采样效率很低:必须要 roll 很多 episodes
  3. 严格 on-policy,回合更新且数据利用率低(只能用当前 policy 跑出来的轨迹,之前的全被丢掉了)
  4. 用一局的总回报更新每个动作:一开始好但后面坏掉的局面
  5. 而且只有 reward 的相对值有价值,绝对值不太有价值。如果 rr 全部大于 0,不是什么好事。而如果一个“好”的轨迹的 reward 是 0 那就更坏事了。

降低方差

Reward to go

考虑因果性,后面的策略不影响前面的:

θJ(θ)=Eτpθ(τ)[(t=1Tθlogπθ(atst))(t=tTr(st,at))]\nabla_\theta J(\theta) = E_{\tau\sim p_\theta (\tau)}\left[\left(\sum_{t=1}^T \nabla_\theta \log \pi_\theta (a_t | s_t) \right) \cdot \left( \sum_{t'=t}^T r(s_t',a_t') \right) \right]

可以证明这个也是无偏的。

Baseline

NN 条轨迹 reward 的平均值:

b=1Ni=1Nr(τi)b = \frac 1N \sum_{i=1}^N r(\tau_i)

然后:

θJ(θ)1Ni=1Nθlogpθ(τ)(r(τ)b)\nabla_\theta J(\theta) \approx \frac 1N \sum_{i=1}^N \nabla_\theta \log p_\theta(\tau) (r(\tau) - b)

合法性源于 E[θlogpθ(τ)b]=0E[\nabla_\theta \log p_\theta (\tau) b] = 0,证明依旧利用那个对数:

E[θlogpθ(τ)b]=pθ(τ)θlogpθ(τ)bdτ=θpθ(τ)bdτ=bθpθ(τ)dτ=bθ1=0E[\nabla_\theta \log p_\theta (\tau) b] = \int p_\theta(\tau) \nabla_\theta \log p_\theta(\tau) b\mathrm{d}\tau = \int \nabla_\theta p_\theta(\tau) b \mathrm{d}\tau = b \nabla_\theta \int p_\theta(\tau) \mathrm{d} \tau = b \nabla_\theta 1 = 0

所以加减常数是完全不影响正确性的。

基于将方差表达为关于 bb 的函数可以求出一个最优的 bb。具体推导比较复杂,但实践中这通常不是特别有必要。

Actor-Critic

引入

直觉:策略梯度中的 reward-to-go 式子只是一条轨迹的,肯定方差是比较大的。 那么如果我们能用 QQ 函数来替代,方差肯定会减小了,注意到这也是无偏的。具体证明使用重期望公式,这里不展开了。

定义 QQ 函数

Qπ(st,at):=t=tTEπθ[r(st,at)st,at]Q^{\pi} (s_t, a_t) := \sum_{t' = t}^T E_{\pi_\theta} [r(s_{t'}, a_{t'}) | s_t, a_t]

补充定义价值函数 VV

Vπ(st):=t=tTEπθ[r(st,at)st]=Eatπ(atst)[Qπ(st,at)]V^\pi(s_t) := \sum_{t' = t}^T E_{\pi_\theta} [r(s_{t'}, a_{t'}) | s_t] = E_{a_t \sim \pi(a_t | s_t)}[Q^\pi(s_t, a_t)]

注意到减去一个 baseline 也是无偏的,所以直接考虑减去 Vπ(st)V^\pi(s_t)(可以理解为平均回报)来评估 ata_t 这个动作的优势。定义优势函数

Aπ(st,at)=Qπ(st,at)Vπ(st)A^\pi(s_t, a_t) = Q^\pi (s_t, a_t) - V^\pi (s_t)

但是我们总不可能通过真的从 sts_t 开始继续 roll out 那么多条轨迹来进行评估 VVQQ,怎么办呢?使用神经网络来拟合。但是拟合三个网络还是有点蠢了而且没必要,所以我们利用 Bellman 方程给的近似关系只拟合价值函数 VV

我们发现,Qπ(st,at)r(st,at)+Vπ(st+1)Q^\pi (s_t, a_t) \approx r(s_t, a_t) + V^\pi (s_{t+1}),$A(s_t, a_t) = Q^\pi(s_t, a_t) - V^\pi(S_t) \approx r(s_t, a_t) + V^\pi(s_{t+1}) - V^\pi (s_t) $,所以只用一个 VV 就 OK 了。

于是整个算法的大致流程如下:actor 玩一把/几把游戏 -> critic 网络拟合 VπV^\pi -> 进行策略提升。

如何求价值函数

考虑在状态 si,ts_{i,t} 最终拿到了总分 yi,ty_{i,t}。然后就用监督学习,输入是状态 sis_i,标签是跑出来的总回报 yiy_i,用 V^ϕπ\hat{V}_\phi^\pi 来最小化预测误差。这个本质上就是 N=1N=1 的 Monte Carlo 估计,虽然无偏但是训练非常不稳定。

为了降低方差,引入 Bootstrapping 方法(自己拽着自己往前跑),利用 Critic 对下一步状态的估计来更新当前对 sts_t 的估计,target 变成

yi,tr(si,t,ai,t)+V^ϕπ(si,t+1)y_{i,t} \approx r(s_{i,t}, a_{i,t}) + \hat V_\phi^\pi (s_{i, t+1})

带上折扣因子 γ\gamma(更着重近的奖励)的话,就是 r(si,t,ai,t)+γV^ϕπ(si,t+1)r(s_{i,t}, a_{i,t}) + \gamma \hat V_\phi^\pi(s_{i,t+1})。这就是很经典的 TD Target。

这样子可以显著降低方差但是由于 V^\hat V 是网络估计出来的值,所以引入了偏差

这样,优势函数 A(s,a)A(s,a) 的估计值变成经典的 TD error 的形式:

A^π(st,at)=r(st,at)+γV^ϕπ(st+1)V^ϕπ(st)\hat A^\pi(s_t, a_t) = r(s_t, a_t) + \gamma \hat V_\phi^\pi (s_{t+1}) - \hat V_\phi^\pi(s_t)

关于折扣因子,对于 Monte Carlo 策略梯度,γtt\gamma^{t'-t}γt1\gamma^{t'-1} 的区别见 0414 EAI 第 46 页。后者为严格推导出的但是实际中用前者(discounted reward-to-go)

偏差与方差的权衡

我们现在有了两种计算 AA (或者说 Target) 的方法:

  1. Monte Carlo: γkrt+kV(st)\sum \gamma^k r_{t+k} - V(s_t) \to 无偏,高方差。
  2. TD(0): rt+γV(st+1)V(st)r_t + \gamma V(s_{t+1}) - V(s_t) \to 低方差,高偏差。

能不能折中一下?

N-step Returns

我们可以不只看 1 步,也不看无穷步,而是看 nn 步:

A^nπ(st,at)=k=0n1γkrt+k+γnV^ϕπ(st+n)V^ϕπ(st)\hat{A}_n^\pi(s_t, a_t) = \sum_{k=0}^{n-1} \gamma^k r_{t+k} + \gamma^n \hat{V}_\phi^\pi(s_{t+n}) - \hat{V}_\phi^\pi(s_t)

  • n=1n=1 就是 TD(0)。
  • n=n=\infty 就是 Monte Carlo。
  • nn 越大,方差越大,偏差越小。

2. GAE (Generalized Advantage Estimation)

GAE 是目前最常用的方法。它的核心思想是把所有可能的 nn-step returns 进行加权平均(权重随 nn 指数衰减)。

定义 δt=rt+γV(st+1)V(st)\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t) 为单步 TD error。

GAE 的计算公式为:

A^tGAE=k=0(γλ)kδt+k\hat{A}^{GAE}_t = \sum_{k=0}^\infty (\gamma \lambda)^k \delta_{t+k}

其中 λ[0,1]\lambda \in [0, 1] 是一个超参数:

  • λ=0\lambda = 0: 变回 TD(0),方差最小,偏差最大。
  • λ=1\lambda = 1: 变回 Monte Carlo,无偏,方差最大。
  • 通常取 λ=0.95\lambda = 0.95,在两者之间取得很好的平衡。

网络架构设计

在代码实现中,Actor 和 Critic 的网络结构有两种常见设计:

  1. Two Network Design (独立网络)

    • Actor 网络 πθ(as)\pi_\theta(a|s) 和 Critic 网络 Vϕ(s)V_\phi(s) 完全独立,参数不共享。
    • 优点:简单,训练稳定,互不干扰。
    • 缺点:无法共享特征(feature),如果输入是图像(CNN),计算量会由两倍。
  2. Shared Network Design (共享网络)

    • 前面几层(Backbone)共享参数提取特征,最后分两个头(Heads):一个输出动作概率,一个输出标量价值。

    • 优点:计算效率高,特征共享可能加速收敛。

    • 缺点:两个任务(策略学习和价值回归)可能会相互干扰(不同尺度的梯度),需要精细调参(Loss权重)。

Batch vs Online Actor-Critic

两种 actor-critic 的核心都一样:

  • Actor:用策略梯度更新参数 (\theta)
  • Critic:拟合状态价值 (\hat V_\phi(s)),并用它构造 advantage 给 actor 用

差别主要在于:critic/actor 是“攒一批再更新”(batch),还是“每一步都更新”(online)

共同核心:1-step TD advantage(TD-error)

用 critic 近似:

V^ϕ(s)Vπ(s)\hat V_\phi(s)\approx V^\pi(s)

最常用的 advantage 估计是 1-step bootstrap:

A^(s,a)=r(s,a)+γV^ϕ(s)V^ϕ(s)\hat A(s,a) = r(s,a) + \gamma \hat V_\phi(s') - \hat V_\phi(s)

也常写成 TD-error:

δt:=rt+γV^ϕ(st+1)V^ϕ(st),A^t=δt\delta_t := r_t + \gamma \hat V_\phi(s_{t+1}) - \hat V_\phi(s_t), \quad \hat A_t=\delta_t

Actor 的梯度估计:

θJ(θ)iθlogπθ(aisi) A^(si,ai)\nabla_\theta J(\theta)\approx \sum_i \nabla_\theta \log \pi_\theta(a_i|s_i)\ \hat A(s_i,a_i)

更新:

θθ+αθJ(θ)\theta \leftarrow \theta + \alpha \nabla_\theta J(\theta)

注:如果有 terminal(done),需要把 bootstrap 切断:

δt:=rt+γ(1dt+1)V^ϕ(st+1)V^ϕ(st)\delta_t := r_t + \gamma(1-d_{t+1})\hat V_\phi(s_{t+1}) - \hat V_\phi(s_t)


Batch actor-critic:先采样一批,再集中更新

直觉:先把数据攒起来,再用 mini-batch 的方式把 critic 拟合得更靠谱,然后用这一批数据统一更新 actor(梯度更平滑、更稳定)。

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
repeat:
# 1) collect a batch with current policy
D = {}
for step = 1..N:
a ~ πθ(a|s)
(s', r, done) = env.step(a)
store (s, a, r, s', done) into D
if done: reset env and get new s
else: s = s'

# 2) update critic (value fitting)
# target 可以是 MC / n-step / TD(lambda) 等,这里写成最常见的 TD target
for k = 1..K_v: # K_v: critic updates per batch
sample minibatch B from D
y = r + γ(1-done) Vφ(s')
minimize Σ (Vφ(s) - y)^2 over φ

# 3) compute advantages on the batch (1-step TD form)
for (s,a,r,s',done) in D:
A = r + γ(1-done) Vφ(s') - Vφ(s)

# 4) update actor with the batch advantages
for k = 1..K_π: # K_π: actor updates per batch
sample minibatch B from D
maximize Σ log πθ(a|s) * A over θ
until convergence

特点:

  • ✅ 梯度更稳:一次用一批样本估计方向,噪声更小
  • ❌ 更新更“滞后”:要等攒够一批数据才能更新一次

Online actor-critic:交互一步,立刻更新一步

直觉:每走一步就用当前 transition 更新 critic,再用同一个 TD-error 立刻更新 actor(反应快,但更 noisy)。

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
initialize θ, φ
s = env.reset()

repeat:
# 1) act
a ~ πθ(a|s)
(s', r, done) = env.step(a)

# 2) critic update (one-step TD)
δ = r + γ(1-done) Vφ(s') - Vφ(s)
φ ← φ - β * ∇φ (δ^2) # value regression / TD learning

# 3) actor update (policy gradient with TD advantage)
θ ← θ + α * ∇θ log πθ(a|s) * δ

if done: s = env.reset()
else: s = s'
until convergence

特点:

  • ✅ 反应快:每一步都能立刻修正策略
  • ❌ 更抖:单步更新的方差更大,通常要更小学习率或做一些稳定化(例如把 online 改成“小 batch online”、或用 GAE / n-step)

一个容易混的点:critic 的 target 和 actor 的 advantage 不一定必须同一种

常见组合是:

  • critic 用某种 return target 做回归(TD / n-step / λ\lambda-return)
  • actor 用 advantage 做更新(1-step TD-error 或 GAE)

它们都在做同一件事:用 V^ϕ\hat V_\phi 把“未来回报”压缩成一个可学习的信号,从而降低策略梯度的方差。


强化学习紧急预习
https://blog.imyangty.com/note-rl/
Author
YangTY
Posted on
December 20, 2025
Licensed under