Actor-Critic架构

一句话概述

Actor-Critic架构是强化学习中最主流的框架——Actor(演员)负责决策:根据当前状态选择动作;Critic(评论家)负责评价:估计当前状态或动作的价值。Critic的评估结果用于指导Actor的更新,大幅降低了策略梯度的方差。Actor-Critic结合了基于策略和基于价值方法的优点,是现代RL算法(A2C、PPO、SAC、TD3)的标准架构。

💡 核心要点:①Actor输出动作概率分布(策略),Critic输出价值估计(V(s)或Q(s,a));②Critic用TD学习更新价值估计,Actor用Critic提供的优势函数更新策略;③Actor-Critic将REINFORCE的蒙特卡洛回报G_t替换为TD误差或优势函数,实现在线学习;④Actor和Critic通常共享底层特征提取网络,减少参数量并加速训练。

教学与演示

一、Actor-Critic的核心思想——分工合作

是什么(定义):Actor-Critic由两个网络组成:Actor π_θ(a|s)(策略网络)和Critic V_φ(s)或Q_φ(s,a)(价值网络)。Actor根据Critic的反馈来改进策略,Critic根据环境奖励来改进价值估计。两者相互促进:更好的Critic→更好的Actor更新→更好的策略→更好的数据→更好的Critic。

大白话 Actor-Critic = 演员 + 导演。演员(Actor)在台上表演,导演(Critic)在台下评论。演员根据导演的反馈调整表演,导演根据实际效果调整评价标准。两者配合,比单独一个人(纯策略)或一个评分系统(纯价值)更高效。

为什么(原理):Actor-Critic解决了REINFORCE的两大痛点:(1) 方差大——用Critic的低方差估计替代高方差G_t;(2) 不能在线学习——Critic的TD误差让每步都能更新。Critic的TD误差δ_t = r + γV(s') - V(s)天然是优势函数A(s,a)的无偏估计,用它来更新Actor既高效又低方差。

怎么做(实现)

import numpy as np

n_states = 9; n_actions = 4
actions = [(-1, 0), (1, 0), (0, -1), (0, 1)]

def step(state, action):
    row, col = state // 3, state % 3
    dr, dc = actions[action]
    nr = max(0, min(2, row + dr))
    nc = max(0, min(2, col + dc))
    next_state = nr * 3 + nc
    if next_state == 8:   r = 10.0
    elif next_state == 7: r = -5.0
    else:                 r = -0.1
    return next_state, r, next_state in [7, 8]

class ActorCritic:
    def __init__(self, n_states, n_actions):
        # Actor参数
        self.theta = np.zeros((n_states, n_actions))
        # Critic参数
        self.V = np.zeros(n_states)

    def act(self, state):
        logits = self.theta[state]; exp_l = np.exp(logits - np.max(logits))
        probs = exp_l / exp_l.sum()
        return np.random.choice(n_actions, p=probs), probs

    def update(self, state, action, reward, next_state, done, probs, alpha_a=0.01, alpha_c=0.1, gamma=0.9):
        # Critic: 计算TD误差并更新V
        td_target = reward + gamma * self.V[next_state] * (1 - done)
        td_error = td_target - self.V[state]
        self.V[state] += alpha_c * td_error  # Critic更新

        # Actor: 用TD误差作为优势函数更新策略
        grad = -probs.copy(); grad[action] += 1
        self.theta[state] += alpha_a * td_error * grad  # Actor更新

    def train_episode(self):
        state = 0; total = 0
        while state not in [7, 8]:
            action, probs = self.act(state)
            next_state, reward, done = step(state, action)
            self.update(state, action, reward, next_state, done, probs)
            total += reward; state = next_state
        return total

np.random.seed(42)
ac = ActorCritic(n_states, n_actions)
rewards = [ac.train_episode() for _ in range(300)]

print("=== Actor-Critic训练结果 ===")
print(f"最后10ep平均奖励: {np.mean(rewards[-10:]):.2f}")
action_names = ["上", "下", "左", "右"]
print(f"\nActor学到的策略:")
for i in range(3):
    row = []; 
    for j in range(3):
        s = i*3+j
        if s==8: row.append("目标")
        elif s==7: row.append("陷阱")
        else: row.append(action_names[np.argmax(ac.theta[s])])
    print("  "+"  ".join(f"{a:>4}" for a in row))
print(f"\nCritic学到的V值:")
for i in range(3):
    print("  "+"  ".join(f"{ac.V[i*3+j]:>6.2f}" for j in range(3)))
Actor-Critic更新规则\(\text{Actor: } \theta \leftarrow \theta + \alpha_a \cdot \delta_t \cdot \nabla_{\theta} \log \pi_{\theta}(a_t \mid s_t) \text{Critic: } \phi \leftarrow \phi + \alpha_c \cdot \delta_t \cdot \nabla_{\phi} V_{\phi}(s_t) \delta_t = r_{t+1} + \gamma V_{\phi}(s_{t+1}) - V_{\phi}(s_t)\)

什么用(应用):Actor-Critic是A2C、A3C、PPO、SAC、TD3等现代RL算法的基础架构。在机器人控制、游戏AI、自动驾驶等领域,几乎所有state-of-the-art方法都基于Actor-Critic。

哪些坑(缺点):需要同时训练两个网络,超参数更多;Actor和Critic的学习率需要协调(Critic通常学习更快);两个网络的目标可能冲突。

二、TD误差作为优势函数——为什么有效

是什么(定义):在Actor-Critic中,TD误差δ_t = r + γV(s') - V(s)是优势函数A(s,a)的无偏估计。因为E[δ_t] = E[Q(s,a) - V(s)] = A(s,a)。用TD误差替代蒙特卡洛回报G_t,既降低了方差,又实现了在线学习。

大白话 TD误差就是"惊喜"——实际奖励+预期未来价值,减去当前预期,看看"事情比想象的好还是坏"。如果比想象的好(δ>0),说明这个动作不错,值得多做;如果比想象的差(δ<0),说明这个动作不行,应该少做。

怎么做(实现)

import numpy as np

# ==================== TD误差 = 优势函数估计 ====================
# 模拟一个简单场景
V_s = 5.0    # 当前估计V(s)=5
V_s_next = 8.0  # 下一状态V(s')=8
reward = 1.0    # 即时奖励
gamma = 0.9

td_error = reward + gamma * V_s_next - V_s
print("=== TD误差 = 优势函数 ===")
print(f"V(s)={V_s}, r={reward}, V(s')={V_s_next}")
print(f"TD误差 = {reward} + {gamma}×{V_s_next} - {V_s} = {td_error:.1f}")
print(f"TD误差>0 → 实际比预期好 → 动作值得多做")
print(f"TD误差<0 → 实际比预期差 → 动作应该少做")
print(f"\n对比REINFORCE的G_t:")
print(f"  G_t = 整条轨迹的累积奖励(方差大,需要等episode结束)")
print(f"  TD误差 = 一步的'惊喜'(方差小,每步都能更新)")

什么用(应用):TD误差是Actor-Critic的核心学习信号。在A2C/A3C中,TD误差直接用于更新Actor和Critic;在PPO中,GAE(广义优势估计)将TD误差扩展到多步,平衡偏差和方差。

哪些坑(缺点):TD误差是有偏的(因为V(s)和V(s')的估计不准确);单步TD误差可能忽略长期依赖(需要用n步回报或GAE)。

三、Actor和Critic的网络设计

是什么(定义):在实际实现中,Actor和Critic通常共享底层特征提取网络,然后各自有一个输出头。共享网络减少参数量、加速训练,但也可能引入梯度冲突。在离散动作空间中,Actor输出动作概率分布(softmax),Critic输出V(s)标量;在连续动作空间中,Actor输出高斯分布参数(μ, σ),Critic输出V(s)标量。

怎么做(实现)

import numpy as np

# ==================== Actor-Critic网络结构 ====================
class SharedActorCritic:
    """共享底层的Actor-Critic网络(概念)"""
    def __init__(self, state_dim, n_actions, hidden_dim=64):
        # 共享特征层
        self.W_shared = np.random.randn(state_dim, hidden_dim) * 0.1
        self.b_shared = np.zeros(hidden_dim)
        # Actor头
        self.W_actor = np.random.randn(hidden_dim, n_actions) * 0.1
        self.b_actor = np.zeros(n_actions)
        # Critic头
        self.W_critic = np.random.randn(hidden_dim, 1) * 0.1
        self.b_critic = np.zeros(1)

    def forward(self, state):
        feat = np.maximum(0, state @ self.W_shared + self.b_shared)
        # Actor输出
        logits = feat @ self.W_actor + self.b_actor
        probs = np.exp(logits - np.max(logits))
        probs /= probs.sum()
        # Critic输出
        V = (feat @ self.W_critic + self.b_critic)[0]
        return probs, V

print("=== Actor-Critic网络结构 ===")
print("共享结构: 输入 → 共享特征层 → [Actor头, Critic头]")
print("  Actor头: 输出动作概率分布(softmax)")
print("  Critic头: 输出状态价值V(s)(标量)")
print()
print("优势:")
print("  1. 减少参数量:共享底层特征")
print("  2. 加速训练:特征表示被两个任务共同优化")
print("  3. 稳定性:共享特征提供了隐式的正则化")
print()
print("挑战:")
print("  1. 梯度冲突:Actor和Critic的优化目标可能冲突")
print("  2. 学习率协调:需要平衡两个头的学习速度")

什么用(应用):共享网络结构是A2C、A3C、PPO的标准做法。在OpenAI的PPO实现中,Actor和Critic共享CNN特征提取层,然后分叉为策略头和值函数头。

哪些坑(缺点):共享网络可能导致梯度冲突;Actor和Critic需要不同的学习率(通常Critic更快);如果输入特征差异大,分离网络可能更好。

四、Actor-Critic vs REINFORCE vs Q-learning

是什么(定义):Actor-Critic位于REINFORCE和Q-learning之间——REINFORCE用G_t(高方差、无偏),Q-learning用TD+max(低方差、有偏),Actor-Critic用TD误差(低方差、有偏但更灵活)。

怎么做(实现)

print("=" * 60)
print("REINFORCE vs Actor-Critic vs Q-learning")
print("=" * 60)
print()
print("REINFORCE:")
print("  更新信号: G_t (蒙特卡洛回报)")
print("  偏差: 无偏")
print("  方差: 高")
print("  在线: 否(需要完整轨迹)")
print("  动作空间: 连续+离散")
print()
print("Actor-Critic:")
print("  更新信号: TD误差 / 优势函数")
print("  偏差: 有偏(Critic估计不准)")
print("  方差: 低")
print("  在线: 是(每步更新)")
print("  动作空间: 连续+离散")
print()
print("Q-learning:")
print("  更新信号: TD误差(with max)")
print("  偏差: 有偏(max操作)")
print("  方差: 低")
print("  在线: 是(每步更新)")
print("  动作空间: 仅离散")
print()
print("Actor-Critic = REINFORCE的灵活性 + Q-learning的稳定性")

什么用(应用):理解三者的关系有助于选择正确的算法。Actor-Critic是当前最通用的选择,覆盖了大多数RL应用场景。

哪些坑(缺点):Actor-Critic虽然通用,但调参比Q-learning更复杂(需要协调两个学习率);在某些简单离散任务中,Q-learning可能更简单高效。

五、实战:Actor-Critic完整实现

是什么(定义):实现一个完整的Actor-Critic智能体,包含共享底层、分离的Actor和Critic头、以及在线训练循环。

怎么做(实现)

import numpy as np

n_states = 9; n_actions = 4
actions = [(-1, 0), (1, 0), (0, -1), (0, 1)]

def step(state, action):
    row, col = state // 3, state % 3
    dr, dc = actions[action]
    nr = max(0, min(2, row + dr))
    nc = max(0, min(2, col + dc))
    next_state = nr * 3 + nc
    if next_state == 8:   r = 10.0
    elif next_state == 7: r = -5.0
    else:                 r = -0.1
    return next_state, r, next_state in [7, 8]

class FullActorCritic:
    def __init__(self, hidden=32):
        # 共享特征层
        self.Ws = np.random.randn(n_states, hidden) * 0.1; self.bs = np.zeros(hidden)
        # Actor
        self.Wa = np.random.randn(hidden, n_actions) * 0.1; self.ba = np.zeros(n_actions)
        # Critic
        self.Wc = np.random.randn(hidden, 1) * 0.1; self.bc = np.zeros(1)

    def forward(self, state_vec):
        h = np.maximum(0, state_vec @ self.Ws + self.bs)
        logits = h @ self.Wa + self.ba
        probs = np.exp(logits - np.max(logits)); probs /= probs.sum()
        V = (h @ self.Wc + self.bc)[0]
        return probs, V, h

    def train_episode(self, alpha_a=0.01, alpha_c=0.1, gamma=0.9):
        state = 0; total = 0
        while state not in [7, 8]:
            sv = np.zeros(n_states); sv[state] = 1.0
            probs, V, h = self.forward(sv)
            action = np.random.choice(n_actions, p=probs)
            next_state, reward, done = step(state, action)
            nsv = np.zeros(n_states); nsv[next_state] = 1.0
            _, V_next, _ = self.forward(nsv)

            td = reward + gamma * V_next * (1-done) - V
            # Critic更新(简化)
            self.Wc += alpha_c * td * h.reshape(-1,1)
            self.bc += alpha_c * td
            # Actor更新
            grad = -probs.copy(); grad[action] += 1
            self.Wa += alpha_a * td * h.reshape(-1,1) @ grad.reshape(1,-1)
            self.ba += alpha_a * td * grad

            total += reward; state = next_state
        return total

np.random.seed(42)
ac = FullActorCritic()
rewards = [ac.train_episode() for _ in range(300)]
print(f"完整Actor-Critic: 最后10ep平均={np.mean(rewards[-10:]):.2f}")

什么用(应用):这个实现是理解A2C、PPO等现代算法的基础。从这里出发,添加n步回报→A2C,添加裁剪机制→PPO,添加异步训练→A3C。

哪些坑(缺点):简化实现没有使用优化器(如Adam);没有使用经验回放(On-Policy);梯度更新是简化的,实际中需要自动微分。

概念关系图谱

概念上位概念核心思想关键公式/方法特点
Actor策略网络选择动作π_θ(as)
Critic价值网络评估价值V_φ(s)评价者
TD误差学习信号实际vs预期的差距δ=r+γV(s')-V(s)驱动更新
共享网络架构设计Actor和Critic共享底层共享特征提取层减少参数
在线学习学习范式每步都能更新TD学习高效率

重点答疑

Q1: Actor-Critic和REINFORCE的核心区别?

REINFORCE用蒙特卡洛回报G_t(需要完整轨迹,方差大),Actor-Critic用TD误差/优势函数(可以每步更新,方差低)。Actor-Critic = REINFORCE - 蒙特卡洛 + Critic。

解答:REINFORCE是"事后总结",Actor-Critic是"边做边改"。TD误差让每步都能学习,Critic让学习信号更稳定。

Q2: Actor和Critic的学习率应该如何设置?

通常Critic的学习率大于Actor(如Critic=0.001, Actor=0.0001)。因为Critic需要快速适应变化的策略,而Actor需要保守更新(策略变化太大会导致不稳定)。在PPO中,通常使用相同的Adam优化器但不同的学习率。

解答:Critic学得快,Actor学得慢。Critic是"评论家",需要快速跟上演员的变化;Actor是"演员",改得太快容易"演砸"。

Q3: Actor-Critic中,Critic可以不学V(s)而学Q(s,a)吗?

可以。Critic学Q(s,a)的变体(如DDPG、TD3)适用于连续动作空间。学Q(s,a)的Critic可以直接指导Actor选择更好的动作。但学Q(s,a)需要处理argmax问题(在连续空间中困难),而DDPG通过让Actor直接近似argmax来解决。

解答:学V(s)更简单,学Q(s,a)信息更丰富。SAC学Q(s,a),PPO学V(s),各有应用场景。

Q4: 为什么Actor-Critic是On-Policy的?

因为Actor的更新需要当前策略π_θ的得分函数∇log π_θ(a|s),而得分函数依赖于当前策略。如果使用旧策略的数据,梯度方向会偏差。所以Actor-Critic通常需要On-Policy数据。SAC通过使用Off-Policy的Critic(学Q)和On-Policy的Actor来实现部分Off-Policy。

解答:Actor更新需要"当前策略"的信息,旧数据中的动作概率分布和新策略不同,梯度方向会偏差。这是PPO需要重新收集数据的原因。

章节单词汇总

英文音标中文
Actor-Critic/ˈæktər ˈkrɪtɪk/演员-评论家
shared network/ʃerd ˈnetwɜːrk/共享网络
TD error/tiː diː ˈerər/TD误差
online learning/ˈɒnlaɪn ˈlɜːrnɪŋ/在线学习
head/hed/头(网络输出分支)
backbone/ˈbækboʊn/骨干网络

面试练习

Q1 [单选] Actor-Critic中,Actor和Critic分别负责什么?

    undefined
解答:Actor(演员)负责决策选择动作,Critic(评论家)负责评估当前状态或动作的价值。

Q2 [单选] Actor-Critic相比REINFORCE的主要优势是什么?

    undefined
解答:Actor-Critic用Critic的低方差估计替代高方差G_t,且可以每步更新(在线学习)。

Q3 [多选] 以下哪些算法属于Actor-Critic架构?

    undefined
解答:A2C、PPO、SAC都是Actor-Critic架构。DQN是基于价值的方法,没有显式的Actor。

Q4 [单选] 在Actor-Critic中,TD误差δ_t = r + γV(s') - V(s)是什么的估计?

    undefined
解答:E[δ_t] = E[Q(s,a) - V(s)] = A(s,a),TD误差是优势函数的无偏估计。

Q5 [多选] 以下关于Actor-Critic的说法,哪些是正确的?

    undefined
解答:A正确,共享网络是标准做法。B正确,Critic使用TD学习。C错误,A2C/PPO是On-Policy的。D正确,Actor用TD误差乘以得分函数更新。