CS221 · 人工智能:原理与技术
Lecture 8 · 强化学习
源文件:lecture-08.md
Lecture 8 · 强化学习
CS221: Artificial Intelligence — Principles and Techniques · Stanford · Autumn 2025
📅 Oct 15 · 💻reinforcement_learning.py
承上启下:从已知模型到未知环境
上一讲 Lecture 7 · 马尔可夫决策过程 中,我们完全知道 MDP 的转移概率 $T(s,a,s')$ 和奖励函数 $\text{Reward}(s,a,s')$,于是可以坐在桌前用贝尔曼方程做纯计算:策略评估算出给定策略的价值 $V_\pi$,价值迭代算出最优策略 $\pi^*$。
本讲把「已知模型」这个假设也去掉:转移概率和奖励函数都不知道,环境是一个黑盒。智能体只能实际执行动作、观察环境返回的奖励和新状态,从交互经验中学习最优策略——这就是强化学习(Reinforcement Learning, RL)。掷骰子之前,你连骰子有几个面都不知道,只能靠掷。
本讲的四个算法(Model-Based、Monte Carlo、SARSA、Q-Learning)都假设状态可以逐个列举、Q 值存成一张表。当状态空间大到表存不下(如围棋)时怎么办?这是下一讲 Lecture 9 · 函数逼近与策略梯度 的主题。
1. 强化学习框架
1.1 核心循环
sequenceDiagram
participant Agent as 智能体 (RL 算法)
participant Env as 环境 (MDP)
loop 每个时间步
Agent->>Env: 动作 a_t
Env->>Agent: 奖励 r_t, 观测 s_{t+1}
Note over Agent: 学习并更新策略
end
每个时间步重复三件事:
- 智能体根据当前状态产生一个动作;
- 环境(内部其实是一个 MDP,但智能体看不到它的参数)返回奖励和新状态;
- 智能体把这条反馈 $(s, a, r, s')$ 吸收进自己的内部状态,改进未来的决策。
提示
与监督学习(Lecture 1 · 张量、梯度与监督学习)对比能看清 RL 的难点:监督学习每个样本都带正确答案 $y$,而 RL 只有一个标量奖励——它告诉你「这样做得了几分」,却不告诉你「正确动作是什么」;奖励还可能延迟到很多步之后才出现;更微妙的是,训练数据的分布由智能体自己的行为决定——走什么路,就只见过什么路。这三点(评价式反馈、延迟奖励、数据分布自决)是 RL 一切算法设计的根源。
1.2 RL 算法接口
class RLAlgorithm:
def get_action(self, state: Any) -> Any:
"""根据当前状态选择动作"""
raise NotImplementedError
def incorporate_feedback(self, state: Any, action: Any, reward: float,
next_state: Any, is_end: bool) -> None:
"""从反馈中学习,更新内部策略"""
raise NotImplementedError
所有 RL 算法都实现这两个方法:get_action 决定「怎么行动」,incorporate_feedback 决定「怎么从反馈中学」。本讲四个算法的差异全部体现在这两个方法内部。
1.3 模拟框架
def simulate(mdp: MDP, rl: RLAlgorithm, num_trials: int) -> float:
"""让 RL 算法在 MDP 中运行 num_trials 轮,返回平均效用"""
utilities = []
for trial in range(num_trials):
state = mdp.start_state()
steps = []
while not mdp.is_end(state):
# 智能体选择动作
action = rl.get_action(state)
# 环境产生结果(根据 MDP 采样)
successors = [s for s in mdp.successors(state) if s.action == action]
probs = [s.prob for s in successors]
step = np.random.choice(successors, p=probs)
# 智能体从反馈中学习
rl.incorporate_feedback(state, action, step.reward, step.state,
mdp.is_end(step.state))
steps.append(step)
state = step.state
utilities.append(compute_utility(steps, mdp.discount()))
return np.mean(utilities)
注意信息隔离:simulate 里的 mdp 只被用来采样,RL 算法拿到的仅仅是 $(s, a, r, s', \text{is\_end})$ 五元组——它永远看不到 mdp.successors 里的概率。官方讲义的实验环境是 FlakyTramMDP(num_locs=10, failure_prob=0.4)(Lecture 7 · 马尔可夫决策过程 的不可靠电车)。
1.4 策略 vs 智能体
| 维度 | 策略(Policy) | 智能体(RL Algorithm) |
|---|---|---|
| 定义 | 状态 → 动作的映射 | 有学习能力的策略 |
| 是否改变 | 静态(固定) | 动态(随经验改进) |
| 例子 | π(s) = "walk" |
Q-Learning、SARSA |
核心思想:RL 算法可以看作「会自我改写的策略」——它内部维护一个当前策略,并利用每条反馈逐步改进它。
2. 基于模型的强化学习(Model-Based RL)
2.1 核心思想
既然不知道 MDP,最朴素的想法:先从数据中估计出 MDP,再对估计出的 MDP 用上一讲的价值迭代求解。
三阶段流程:
- 探索阶段:用探索策略(如均匀随机选动作)与环境交互,累积转移与奖励数据;
- 计算阶段:对估计出的 $\hat{T}, \hat{R}$ 运行价值迭代,得到(估计意义下的)最优策略;
- 利用阶段:执行这个策略收割回报。
flowchart LR
A["探索<br/>随机动作"] --> B["估计 MDP<br/>T̂, R̂"]
B --> C["价值迭代<br/>计算 π*"]
C --> D["利用<br/>执行 π*"]
style A fill:#e1f5fe,stroke:#0277bd
style C fill:#ffe0b2,stroke:#e65100
style D fill:#c8e6c9,stroke:#2e7d32
2.2 估计 MDP:数数即可
转移概率用频率估计(即最大似然估计):
$$\hat{T}(s, a, s') = \frac{\#(s, a, s')}{\#(s, a)}$$
其中 $\#(s,a,s')$ 是「在 $s$ 执行 $a$ 落到 $s'$」被观测到的次数,$\#(s,a)$ 是「在 $s$ 执行 $a$」的总次数。奖励是确定值,记录一次即可。
class EstimatedMDP(MDP):
"""从反馈中学习的 MDP"""
def __init__(self, discount: float):
self.rewards = defaultdict(float) # (s, a, s') → reward
self.transitions = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
# state → action → next_state → count
self.end_states = set()
self.discount_ = discount
def incorporate_feedback(self, state, action, reward, next_state, is_end):
"""从一次交互中更新估计"""
self.rewards[(state, action, next_state)] = reward
self.transitions[state][action][next_state] += 1 # 计数
if is_end:
self.end_states.add(next_state)
def successors(self, state):
"""基于计数估计转移概率"""
successors = []
for action, next_state_counts in self.transitions[state].items():
total_count = sum(next_state_counts.values())
for next_state, count in next_state_counts.items():
prob = count / total_count # 频率估计概率
reward = self.rewards[(state, action, next_state)]
successors.append(Step(action, prob, reward, next_state))
return successors
例子
观测到 $(s{=}1, a{=}\text{tram})$ 共 10 次,其中 6 次到达 $s'{=}2$、4 次留在 $s'{=}1$,则
$$\hat{T}(1, \text{tram}, 2) = \tfrac{6}{10} = 0.6, \qquad \hat{T}(1, \text{tram}, 1) = \tfrac{4}{10} = 0.4$$
恰好接近真实的失败概率 0.4。样本越多,估计越准(大数定律)。
2.3 Model-Based 价值迭代算法
class ModelBasedValueIteration(RLAlgorithm):
def __init__(self, exploration_policy: Policy, discount: float):
self.exploration_policy = exploration_policy
self.exploitation_policy = None
self.mdp = EstimatedMDP(discount=discount)
def run_value_iteration(self):
"""对估计的 MDP 运行价值迭代"""
result = value_iteration(self.mdp)
self.exploitation_policy = result.pi
def get_action(self, state):
if self.exploitation_policy is None:
# 探索阶段:随机动作
return self.exploration_policy(state)
else:
# 利用阶段:执行最优策略
return self.exploitation_policy[state]
def incorporate_feedback(self, state, action, reward, next_state, is_end):
# 更新估计的 MDP
self.mdp.incorporate_feedback(state, action, reward, next_state, is_end)
注意
探索策略必须覆盖所有的 $(s,a)$ 对。频率估计只对见过的转移有数——如果某个动作从未被尝试,估计的 MDP 里它就不存在,价值迭代自然永远不会选它,哪怕它其实是最优动作。这就是为什么探索阶段要用随机策略而不是「看起来好」的策略:省下的探索会变成永久的盲区。
2.4 优缺点
优点:
- 概念清晰:把「学习」和「规划」干净地分成两个阶段,每个阶段都用已有工具(计数估计 + 价值迭代);
- 样本效率高:每条经验都沉淀进 $\hat{T}, \hat{R}$,之后的价值迭代可以反复利用全部数据。
缺点:
- 需要显式存储和估计整个 MDP:参数量是 $O(|S|^2 |A|)$ 级别,状态空间大时既存不下也估不准;
- 探索与利用需要手动分阶段切换,「探索多久才够」没有原则性答案。
改进方向:注意到我们最终要的只是最优策略(即每个状态的 $\arg\max_a Q^*$)——能否跳过建模 MDP,直接估计 Q 值?
3. 无模型蒙特卡洛(Model-Free Monte Carlo)
3.1 核心思想
回顾最优 Q 值的定义(Lecture 7 · 马尔可夫决策过程):
$$Q^*(s, a) = \sum_{s'} T(s, a, s')\left[\text{Reward}(s, a, s') + \gamma V^*(s')\right]$$
它是一个期望——而期望可以不通过模型、直接用样本平均来估计。这就是蒙特卡洛(Monte Carlo)思路:从 $(s,a)$ 出发跑完整回合,把实际获得的折扣效用记下来,多次平均:
$$\hat{Q}(s, a) = \frac{\text{从 } (s,a) \text{ 出发观测到的效用之和}}{\text{访问次数}}$$
这样 $T$ 和 $\text{Reward}$ 从头到尾都不需要显式出现——它们的影响已经「烘焙」在采样出的轨迹里了。
3.2 算法实现
class ModelFreeMonteCarlo(RLAlgorithm):
def __init__(self, exploration_policy: Policy, epsilon: float, discount: float):
self.exploration_policy = exploration_policy
self.epsilon = epsilon # ε-贪婪探索
self.discount = discount
# 维护每个 (s, a) 的累计效用和访问次数
self.sum_utilities = defaultdict(lambda: defaultdict(float)) # s → a → 总效用
self.counts = defaultdict(lambda: defaultdict(int)) # s → a → 计数
# 当前 rollout 的历史
self.rollout = []
self.start_state = None
def Q(self, state, action):
"""估计的 Q 值 = 平均效用"""
if self.counts[state][action] == 0:
return 0
return self.sum_utilities[state][action] / self.counts[state][action]
def pi(self, state):
"""当前策略:选择 Q 值最大的动作"""
actions = list(self.counts[state].keys())
q_values = [self.Q(state, a) for a in actions]
return actions[np.argmax(q_values)]
def get_action(self, state):
"""ε-贪婪探索"""
if len(self.counts[state]) == 0 or np.random.random() < self.epsilon:
return self.exploration_policy(state) # 探索
else:
return self.pi(state) # 利用
def incorporate_feedback(self, state, action, reward, next_state, is_end):
# 记录这一步
self.rollout.append(Step(action, 1, reward, next_state))
if self.start_state is None:
self.start_state = state
# 回合结束时,回溯计算每步的效用并更新统计量
if is_end:
utilities = [0] * (len(self.rollout) + 1)
# 从后往前计算效用
for i in reversed(range(len(self.rollout))):
step = self.rollout[i]
state = self.start_state if i == 0 else self.rollout[i-1].state
# u_i = r_i + γ u_{i+1}
utilities[i] = step.reward + self.discount * utilities[i+1]
# 更新累计统计
self.sum_utilities[state][step.action] += utilities[i]
self.counts[state][step.action] += 1
# 重置历史
self.start_state = None
self.rollout = []
实现要点:回合结束后从后往前回溯,利用递推 $u_i = r_i + \gamma\, u_{i+1}$ 一趟算出每一步位置的「后续效用」,再把它计入对应 $(s,a)$ 的平均。这个平均也可以写成等价的增量形式——
$$\hat{Q}(s,a) \leftarrow \hat{Q}(s,a) + \eta \left[u - \hat{Q}(s,a)\right], \qquad \eta = \frac{1}{\#(s,a)}$$
即「朝着新样本 $u$ 挪一小步」,为下一节的 TD 更新埋下伏笔。
3.3 ε-贪婪探索
探索-利用困境(Exploration-Exploitation Dilemma):只利用(总选当前最优)可能一辈子错过真正的好动作;只探索(总随机选)则永远不收割学到的知识。任何 RL 算法都必须在两者之间取舍。
ε-贪婪(ε-greedy)策略是最简单的折中:
$$\pi_{\text{act}}(s) = \begin{cases} \text{随机动作} & \text{以概率 } \epsilon \\ \arg\max_a \hat{Q}(s, a) & \text{以概率 } 1 - \epsilon \end{cases}$$
if np.random.random() < epsilon:
action = random_action() # 探索
else:
action = argmax_a Q(s, a) # 利用
提示
ε-贪婪解决了 Model-Based 方法「手动分阶段」的尴尬:探索和利用不再是先后两个阶段,而是每一步都以概率混合,学习与收割同时进行。官方实验取 $\epsilon = 0.4$——探索占比相当高,因为电车问题的最优路线需要连续「冒险」坐电车才能被发现。
3.4 优缺点
优点:
- 完全无模型:不存 $\hat T$、$\hat R$,只存一张 Q 表,空间 $O(|S||A|)$;
- 估计无偏(unbiased):效用样本是真实回报,平均值收敛到真实期望。
缺点:
- 必须等到回合结束才能计算效用、执行更新——反馈严重延迟,长回合下学习极慢,无限回合(持续型任务)下干脆不可用;
- 方差高:一条轨迹的效用累积了后续每一步的随机性,样本噪声大,需要很多回合才能平均掉。
改进方向:能否不等回合结束、每走一步就更新?
4. SARSA(同策略 TD 学习)
注意
SARSA 是 on-policy(同策略)、Q-Learning 是 off-policy(异策略),指的是「学习目标对应的策略」与「实际行动的策略」是否为同一个。这与 online/offline learning(在线/离线学习,指数据是边交互边到达还是事先给定的静态数据集)是两组完全不同的概念——SARSA 和 Q-Learning 都是在线算法。中文资料常把 off-policy 误译作「离线」,复习时注意区分。
4.1 核心思想:自举(Bootstrapping)
蒙特卡洛用真实效用做更新目标,代价是要等完整轨迹:
$$u = r_0 + \gamma r_1 + \gamma^2 r_2 + \cdots + \gamma^n r_n \quad (\text{需要整个回合})$$
SARSA 走一步就更新:真实效用中「未来」的部分用当前的 Q 值估计顶替——
$$u = r_0 + \gamma\, \hat{Q}_\pi(s_1, a_1) \quad (\text{只需一步观测})$$
这种「用自己的估计值构造自己的更新目标」正是上一讲价值迭代里见过的自举(bootstrapping),用在从采样数据中学习时称为时序差分(Temporal Difference, TD)学习。
graph LR
S0["s_0"] -->|"a_0, r_0"| S1["s_1"]
S1 -->|"a_1"| S2["s_2"]
S2 -->|"a_2"| S3["..."]
MC["蒙特卡洛<br/>等到结束"] -.需要完整轨迹.-> S3
SARSA["SARSA<br/>立即更新"] -.用 Q 估计截断.-> S1
style MC fill:#ffcdd2,stroke:#c62828
style SARSA fill:#c8e6c9,stroke:#2e7d32
提示
蒙特卡洛与 TD 是一对偏差-方差权衡:MC 的目标无偏但方差大(累积整条轨迹的随机性);TD 的目标只含一步随机性、方差小,但 $\hat{Q}$ 本身不准时目标有偏。实践中 TD 通常学得快得多——尤其在长回合问题里,它把「一整局的成败」拆成「每一步的落差」来学。TD 思想后面还会反复出现:Lecture 11 · 博弈 II:TD 学习与同时博弈 用它来学习棋局估值函数。
4.2 SARSA 算法
名称来源:每次更新恰好用到五元组 State–Action–Reward–State–Action:
$$s_t \xrightarrow{a_t} r_t,\ s_{t+1} \xrightarrow{a_{t+1}} \cdots$$
更新规则($\alpha$ 为学习率):
$$\hat{Q}_\pi(s, a) \leftarrow \hat{Q}_\pi(s, a) + \alpha \big[\underbrace{r + \gamma\, \hat{Q}_\pi(s', a')}_{\text{TD 目标}} - \hat{Q}_\pi(s, a)\big]$$
方括号内是 TD 误差(TD error):新证据(走了一步后的估计)与旧估计的落差。Q 值朝着消除这个落差的方向挪 $\alpha$ 步。
class SARSA(RLAlgorithm):
def __init__(self, exploration_policy, epsilon, discount, learning_rate):
self.exploration_policy = exploration_policy
self.epsilon = epsilon
self.discount = discount
self.learning_rate = learning_rate # 学习率 α
self.Q = defaultdict(lambda: defaultdict(float)) # s → a → Q值
def get_action(self, state):
"""ε-贪婪"""
if len(self.Q[state]) == 0 or np.random.random() < self.epsilon:
return self.exploration_policy(state)
else:
return self.pi(state)
def pi(self, state):
"""当前策略"""
actions = list(self.Q[state].keys())
q_values = [self.Q[state][a] for a in actions]
return actions[np.argmax(q_values)]
def incorporate_feedback(self, state, action, reward, next_state, is_end):
# 关键:用 get_action(含 ε 探索的当前行为策略)选择 next_action(on-policy)
next_action = self.get_action(next_state)
# 自举估计效用
u = reward + self.discount * self.Q[next_state].get(next_action, 0)
# 梯度更新:Q(s,a) ← Q(s,a) + α[u - Q(s,a)]
self.Q[state][action] += self.learning_rate * (u - self.Q[state][action])
4.3 为什么 SARSA 是同策略(on-policy)
SARSA 估计的是当前行为策略(含 ε 探索)的 Q 值 $Q_\pi(s,a)$,满足的递归是固定策略版的贝尔曼方程:
$$Q_\pi(s, a) = \sum_{s'} T(s, a, s') \left[ \text{Reward}(s, a, s') + \gamma\, Q_\pi(s', \pi(s')) \right]$$
代码中的对应:TD 目标里的 next_action 由 self.get_action(next_state) 产生——这正是智能体实际会执行的动作(可能是贪婪的,也可能是探索的随机动作)。「评估谁就按谁采样」,学习目标与行为一致,故称同策略。代价是:它学到的是「带着探索噪声行动的自己」的价值,而不是最优策略的价值。
5. Q-Learning(异策略 TD 学习)
5.1 核心区别
- SARSA:估计当前行为策略的 $Q_\pi$(on-policy);
- Q-Learning:直接估计最优策略的 $Q^*$(off-policy)。
问题:最优策略还不知道,怎么估计它的 Q 值?答案:构造 TD 目标时,下一步动作不用实际执行的那个,而是假设下一步按当前 Q 值贪婪地选最优——用贪婪策略充当最优策略的代理。
5.2 Q-Learning 算法
更新规则:
$$\hat{Q}(s, a) \leftarrow \hat{Q}(s, a) + \alpha \big[\, r + \gamma \max_{a'} \hat{Q}(s', a') - \hat{Q}(s, a) \big]$$
与 SARSA 唯一的区别:TD 目标里的 $\hat{Q}(s',a')$ 换成了 $\max_{a'} \hat{Q}(s', a')$。
class QLearning(SARSA): # 继承 SARSA,只改一行
def incorporate_feedback(self, state, action, reward, next_state, is_end):
# 关键:用 pi(贪婪策略)选择 next_action(off-policy)
next_action = self.pi(next_state) # 而非 self.get_action
# 自举估计效用(用贪婪动作)
u = reward + self.discount * self.Q[next_state].get(next_action, 0)
# 梯度更新
self.Q[state][action] += self.learning_rate * (u - self.Q[state][action])
一行之差:
- SARSA:
next_action = self.get_action(next_state)——下一步实际会怎么走(含探索); - Q-Learning:
next_action = self.pi(next_state)——下一步最优应该怎么走(纯贪婪)。
5.3 Off-Policy 的威力
行为策略(behavior policy)≠ 目标策略(target policy):
- 行为策略:实际与环境交互、负责探索的策略(ε-贪婪);
- 目标策略:学习目标所对应的策略(贪婪 ≈ 最优)。
Q-Learning 把两者解耦:一边用探索性的行为收集数据,一边学习最优策略的价值。探索造成的「绕路」不会污染学习目标——更新式里的 $\max$ 假定未来总是走最优。
graph TD
A["ε-贪婪<br/>行为策略"] -->|执行动作| B["环境"]
B -->|反馈| C["Q-Learning"]
C -->|更新| D["Q* 值<br/>最优策略"]
D -.目标策略.-> C
style A fill:#e1f5fe,stroke:#0277bd
style D fill:#c8e6c9,stroke:#2e7d32
说明
Sutton & Barto 的经典例子:智能体沿悬崖边从起点走到终点,掉崖大惩罚。Q-Learning 学到贴崖最短路(最优策略确实如此——它假设未来不犯错);SARSA 学到离崖一格的绕行路——因为它评估的是「带着 ε 概率随机乱走的自己」,贴崖行走对这个策略来说期望代价很高。训练期间(仍在探索时)SARSA 的实际得分反而更高。这说明 on/off-policy 不是实现细节,而是「为哪个策略负责」的语义差异。
5.4 贝尔曼方程视角
Q-Learning 更新式不是拍脑袋设计的——它正是贝尔曼最优方程(Lecture 7 · 马尔可夫决策过程)的采样版:
$$Q^*(s, a) = \sum_{s'} T(s, a, s') \left[ \text{Reward}(s, a, s') + \gamma \max_{a'} Q^*(s', a') \right]$$
已知模型时,价值迭代对 $s'$ 求期望;不知模型时,Q-Learning 用环境采出的单个 $s'$ 代替期望,再以小步长 $\alpha$ 平均掉采样噪声——相当于对损失 $\big(\hat{Q}(s,a) - \text{target}\big)^2$ 做随机梯度下降。价值迭代 : Q-Learning ≈ 全量梯度 : SGD。
6. 算法对比
6.1 总览
| 算法 | 需要模型 | 估计对象 | 同/异策略 | 更新时机 | 收敛目标 |
|---|---|---|---|---|---|
| Model-Based VI | 估计 $\hat T,\hat R$ | 间接(经 VI) | — | 探索期结束后 | $\pi^*$(若探索充分) |
| Monte Carlo | 不需要 | Q 值 | 同策略 | 回合结束 | $Q_\pi$ |
| SARSA | 不需要 | Q 值 | 同策略 | 每步 | $Q_\pi$ |
| Q-Learning | 不需要 | Q 值 | 异策略 | 每步 | $Q^*$ |
说明
六个选手在 FlakyTramMDP(10, 0.4) 上同台($\epsilon=0.4$、$\alpha=0.1$、$\gamma=1$、各测 20 局):固定基线 tram_if_possible_policy、Model-Based 的探索期与利用期、Monte Carlo、SARSA、Q-Learning。要点结论:Model-Based 探索期得分很差(随机走)而利用期接近最优,体现「先亏后赚」;三个无模型算法边学边得分,Q-Learning 最终逼近最优策略的水平。
6.2 可视化对比
mindmap
root((强化学习算法))
基于模型
估计 T 和 R
运行价值迭代
样本效率高
需存整个 MDP
无模型
蒙特卡洛
完整回合更新
无偏估计
高方差
SARSA
每步更新
自举 TD
同策略学 Q_π
Q-Learning
每步更新
自举 TD
异策略学 Q*
6.3 探索策略一览
所有算法都要回答「什么时候试新动作」。除 ε-贪婪外的常见方案:
- ε-贪婪(ε-greedy):以固定概率 ε 随机探索。简单、够用,是本讲所有无模型算法的默认选择;
- ε 衰减:训练初期 ε 大(多探索),随经验增长逐渐减小(多利用),缓解固定 ε「学好了还在乱走」的浪费;
- 玻尔兹曼探索(Boltzmann / softmax):按 $\pi(a \mid s) \propto \exp\!\big(\hat{Q}(s,a)/\tau\big)$ 采样——Q 值高的动作被选中的概率大,但不是独占;温度 $\tau$ 控制随机程度,$\tau \to 0$ 退化为贪婪;
- UCB(Upper Confidence Bound):给每个动作加上与访问次数成反比的「不确定性加成」,优先尝试访问少的动作——「乐观面对不确定性」,比盲目随机更有方向感。
# ε 衰减示例
epsilon = max(0.01, 1.0 - episode / 1000) # 从 1.0 衰减到 0.01
7. 学习率与收敛
7.1 学习率的作用
更新公式可以改写成凸组合形式:
$$\hat{Q}(s,a) \leftarrow (1-\alpha)\, \hat{Q}(s,a) + \alpha\, u$$
即新估计是「旧估计」与「新目标 $u$」的加权平均,$\alpha$ 决定信新的还是信旧的:
- $\alpha = 0$:完全不学,Q 值冻结;
- $\alpha = 1$:完全相信最新一个样本,之前的经验全部丢弃——噪声环境下会剧烈震荡;
- $\alpha \in (0,1)$:对历史样本做指数加权平均,越新的样本权重越大。
7.2 收敛条件(Robbins–Monro)
理论上 Q-Learning 收敛到 $Q^*$(Watkins & Dayan, 1992)需要两个条件:
- 充分探索:每个 $(s,a)$ 对被访问无限多次——没见过的动作谈不上估准;
- 学习率适当衰减(Robbins–Monro 条件):
$$\sum_{t=1}^{\infty} \alpha_t = \infty \qquad \text{且} \qquad \sum_{t=1}^{\infty} \alpha_t^2 < \infty$$
第一条保证步长总量无限——无论初始估计错得多离谱都能被修正过来;第二条保证步长平方和有限——采样噪声的累积影响最终趋于零,估计能稳定下来。例如 $\alpha_t = 1/t$ 同时满足两条(此时更新恰好等价于算术平均)。
实践:常用固定小学习率(官方实验 $\alpha = 0.1$)。理论上不保证收敛(会在 $Q^*$ 附近小幅波动),但对非平稳环境反而更合适——旧经验会自然被遗忘。
8. 总结
8.1 核心对比
flowchart TD
A["强化学习"] --> B{"知道 MDP?"}
B -->|是| C["价值迭代<br/>(上一讲)"]
B -->|否| D{"估计模型?"}
D -->|是| E["Model-Based<br/>估计 T, R"]
D -->|否| F{"何时更新?"}
F -->|回合结束| G["Monte Carlo<br/>完整轨迹"]
F -->|每步| H{"同/异策略?"}
H -->|同策略| I["SARSA<br/>学 Q_π"]
H -->|异策略| J["Q-Learning<br/>学 Q*"]
style C fill:#e3f2fd,stroke:#1976d2
style E fill:#fff3e0,stroke:#ef6c00
style G fill:#fce4ec,stroke:#c2185b
style I fill:#f3e5f5,stroke:#7b1fa2
style J fill:#e8f5e9,stroke:#2e7d32
8.2 关键要点
强化学习的三大核心挑战:
- 探索 vs 利用:只利用会错过更优动作,只探索则浪费已学知识;ε-贪婪等机制在两者间按概率折中;
- 延迟奖励:关键决策的回报可能许多步之后才显现(坐电车「亏」2 分钟,收益要到终点才能确认),算法必须能把远期结果归因到早期动作;
- 信用分配(credit assignment):一个回合的总分由许多动作共同造成,到底该表扬/责怪哪一步?MC 把整段回报记到途经的每个 $(s,a)$ 头上,TD 则用逐步的落差把功过一层层向前传。
四大算法一句话总结:
- Model-Based:先数数估计出 MDP,再用价值迭代规划(样本效率高,但要存整个模型、探索需手动分阶段);
- Monte Carlo:用完整回合的真实效用平均出 Q 值(无偏但高方差,且必须等回合结束);
- SARSA:每步自举更新,学当前行为策略的 $Q_\pi$(同策略——为「会探索的自己」负责);
- Q-Learning:每步自举更新,TD 目标取 $\max$,学最优策略的 $Q^*$(异策略——边随意探索边学最优,本质是贝尔曼最优方程的 SGD 版)。
说明
表格型 Q-Learning 是深度强化学习的直系祖先:DQN 用神经网络替代 Q 表打 Atari,AlphaGo 在此之上加搜索。大语言模型的对齐同样是 RL 框架的实例——把人类偏好或可验证的正确性当作奖励信号来优化策略,见 CS336 的 Lecture 15 · 对齐 I:SFT 与 RLHF(未找到对应页面) 与 Lecture 16 · 对齐 II:RLVR(未找到对应页面)。
下一讲预告:本讲的 Q 表要求逐个状态存数值。状态空间巨大(围棋约 $10^{170}$ 种状态)时表存不下,也无法泛化到没见过的状态 → 用函数逼近(神经网络)表示 Q 与策略,见 Lecture 9 · 函数逼近与策略梯度。
复习自测
题目
差别只在 TD 目标里下一步动作的来源:SARSA 用 get_action(s')(实际要执行的 ε-贪婪动作),Q-Learning 用 pi(s')(纯贪婪的 $\max_{a'}$)。TD 目标里「下一步按谁走」定义了「在评估哪个策略」:按实际行为走 → 学行为策略的 $Q_\pi$(同策略);按贪婪走 → 学最优策略的 $Q^*$(异策略),哪怕实际行为仍在探索。
题目
MC 的更新目标是真实效用 $u = \sum_t \gamma^t r_t$,只有回合结束才能算出来。它无偏(样本就是真实回报)但方差高(整条轨迹的随机性都累积在一个数里)。TD 用 $r + \gamma \hat{Q}(s',a')$ 截断未来:只含一步随机性、方差小、每步都能更新,但 $\hat{Q}$ 未收敛时目标有偏。长回合问题中 TD 通常显著更快。
题目
SARSA 用实际动作 $a_2$:目标 $= 5 + (-1) = 4$,更新 $\hat{Q}(s,a) = 0 + 0.5(4-0) = 2$。
Q-Learning 取 $\max$(即 $a_1$):目标 $= 5 + 2 = 7$,更新 $\hat{Q}(s,a) = 0 + 0.5(7-0) = 3.5$。
同一条经验,两个算法给出不同更新——因为它们在为不同的策略估值。
题目
初期 Q 值几乎全为默认值,贪婪策略会锁死在最先碰巧得到正反馈的动作上;未尝试过的动作 Q 值永远不更新,即使其中藏着最优动作也永远发现不了。这违反收敛条件之一「每个 $(s,a)$ 被访问无限次」。这就是探索-利用困境的极端形式:零探索 → 可能永久次优。
题目
每条经验都永久沉淀进 $\hat T, \hat R$ 的计数里,价值迭代在整个估计模型上反复传播这些信息——一条数据能影响所有状态的价值;而无模型 TD 每条数据只更新一个 $(s,a)$ 条目就丢弃。代价:要存 $O(|S|^2|A|)$ 规模的模型,状态空间大时估不准也存不下;且规划质量受模型误差上限约束(没见过的转移就是盲区)。
参考资料
- 💻 reinforcement_learning.py
- 📖 Sutton & Barto: Reinforcement Learning (2nd ed.) — RL 圣经;第 5 章 MC、第 6 章 TD(含悬崖行走例子)
- 📄 Q-Learning (Watkins, 1989) — Q-Learning 原始论文
- 📄 Q-Learning 收敛性证明 (Watkins & Dayan, 1992)
- 📄 SARSA (Rummery & Niranjan, 1994)
- 🎥 David Silver’s RL Course — DeepMind RL 课程