声明:本章使用 Python 3.12+ 环境,PyTorch 2.0+ 框架,OpenAI Gymnasium 环境库

学习目标

完成本章学习后,你将能够:

  • 理解强化学习与监督学习的本质区别
  • 掌握马尔可夫决策过程的数学框架
  • 推导贝尔曼方程并理解其价值
  • 实现 Q-Learning 和 DQN 算法
  • 使用深度强化学习解决实际控制问题

1. 强化学习概述

1.1 三种学习范式的对比

强化学习(Reinforcement Learning, RL)是机器学习三大范式之一,与监督学习和无监督学习形成鲜明对比:

学习范式 数据形式 反馈类型 典型应用
监督学习 (输入, 标签) 成对数据 即时、明确的正确/错误反馈 图像分类、语音识别
无监督学习 无标签数据 无外部反馈,发现内在结构 聚类、降维、生成模型
强化学习 状态-动作-奖励序列 延迟、稀疏的奖励信号 游戏AI、机器人控制

1.2 强化学习的核心要素

强化学习的本质是一个智能体(Agent)与环境(Environment)持续交互的过程:

核心概念解析:

  • 状态(State):环境在某一时刻的完整描述,记为 sts_tst
  • 动作(Action):智能体在给定状态下可执行的操作,记为 ata_tat
  • 奖励(Reward):环境对智能体动作的即时反馈,记为 rtr_trt
  • 策略(Policy):从状态到动作的映射,记为 π(a∣s)\pi(a|s)π(as)
  • 回报(Return):从当前时刻开始的累积折扣奖励:

Gt=rt+γrt+1+γ2rt+2+⋯=∑k=0∞γkrt+k G_t = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \cdots = \sum_{k=0}^{\infty} \gamma^k r_{t+k} Gt=rt+γrt+1+γ2rt+2+=k=0γkrt+k

其中 γ∈[0,1]\gamma \in [0, 1]γ[0,1] 是折扣因子,用于平衡即时奖励与未来奖励的重要性。

补充:强化学习之父 Richard Sutton 将 RL 定义为"通过试错来学习映射状态到动作,以最大化数值奖励信号"。


2. 马尔可夫决策过程

2.1 MDP 五元组定义

马尔可夫决策过程(Markov Decision Process, MDP)是强化学习的数学基础框架,由五元组 (S,A,P,R,γ)(S, A, P, R, \gamma)(S,A,P,R,γ) 构成:

符号 含义 数学表示
SSS 状态空间 所有可能状态的集合
AAA 动作空间 所有可能动作的集合
PPP 状态转移概率 $P(s’
RRR 奖励函数 R(s,a)=E[Rt+1∣St=s,At=a]R(s,a) = \mathbb{E}[R_{t+1} | S_t=s, A_t=a]R(s,a)=E[Rt+1St=s,At=a]
γ\gammaγ 折扣因子 γ∈[0,1]\gamma \in [0, 1]γ[0,1]

2.2 马尔可夫性质

马尔可夫性质是 MDP 的核心假设:未来状态仅依赖于当前状态,与历史无关

P[St+1∣St,At,St−1,At−1,…,S0,A0]=P[St+1∣St,At] \mathbb{P}[S_{t+1} | S_t, A_t, S_{t-1}, A_{t-1}, \ldots, S_0, A_0] = \mathbb{P}[S_{t+1} | S_t, A_t] P[St+1St,At,St1,At1,,S0,A0]=P[St+1St,At]

通俗理解:就像下棋时,当前棋盘局面已经包含了所有历史信息,无需知道之前的每一步如何走。

2.3 状态转移概率

对于确定性环境,给定状态 sss 和动作 aaa,下一个状态 s′s's 是确定的;对于随机环境,状态转移服从概率分布:

∑s′∈SP(s′∣s,a)=1,∀s∈S,a∈A \sum_{s' \in S} P(s'|s,a) = 1, \quad \forall s \in S, a \in A sSP(ss,a)=1,sS,aA


3. 价值函数与贝尔曼方程

3.1 状态价值函数 V

状态价值函数 Vπ(s)V^{\pi}(s)Vπ(s) 表示:从状态 sss 出发,遵循策略 π\piπ 所能获得的期望回报:

Vπ(s)=Eπ[Gt∣St=s]=Eπ[∑k=0∞γkRt+k+1∣St=s] V^{\pi}(s) = \mathbb{E}_{\pi}[G_t | S_t = s] = \mathbb{E}_{\pi}\left[\sum_{k=0}^{\infty} \gamma^k R_{t+k+1} \bigg| S_t = s\right] Vπ(s)=Eπ[GtSt=s]=Eπ[k=0γkRt+k+1 St=s]

3.2 动作价值函数 Q

动作价值函数 Qπ(s,a)Q^{\pi}(s,a)Qπ(s,a) 表示:从状态 sss 出发,执行动作 aaa 后遵循策略 π\piπ 的期望回报:

Qπ(s,a)=Eπ[Gt∣St=s,At=a]=Eπ[∑k=0∞γkRt+k+1∣St=s,At=a] Q^{\pi}(s,a) = \mathbb{E}_{\pi}[G_t | S_t = s, A_t = a] = \mathbb{E}_{\pi}\left[\sum_{k=0}^{\infty} \gamma^k R_{t+k+1} \bigg| S_t = s, A_t = a\right] Qπ(s,a)=Eπ[GtSt=s,At=a]=Eπ[k=0γkRt+k+1 St=s,At=a]

V 与 Q 的关系

Vπ(s)=∑aπ(a∣s)Qπ(s,a) V^{\pi}(s) = \sum_{a} \pi(a|s) Q^{\pi}(s,a) Vπ(s)=aπ(as)Qπ(s,a)

Qπ(s,a)=R(s,a)+γ∑s′P(s′∣s,a)Vπ(s′) Q^{\pi}(s,a) = R(s,a) + \gamma \sum_{s'} P(s'|s,a) V^{\pi}(s') Qπ(s,a)=R(s,a)+γsP(ss,a)Vπ(s)

3.3 贝尔曼期望方程

贝尔曼方程是强化学习最重要的方程,它将价值函数递归地表示为即时奖励与折扣未来价值之和。

状态价值的贝尔曼期望方程

Vπ(s)=∑aπ(a∣s)[R(s,a)+γ∑s′P(s′∣s,a)Vπ(s′)] V^{\pi}(s) = \sum_{a} \pi(a|s) \left[ R(s,a) + \gamma \sum_{s'} P(s'|s,a) V^{\pi}(s') \right] Vπ(s)=aπ(as)[R(s,a)+γsP(ss,a)Vπ(s)]

动作价值的贝尔曼期望方程

Qπ(s,a)=R(s,a)+γ∑s′P(s′∣s,a)∑a′π(a′∣s′)Qπ(s′,a′) Q^{\pi}(s,a) = R(s,a) + \gamma \sum_{s'} P(s'|s,a) \sum_{a'} \pi(a'|s') Q^{\pi}(s',a') Qπ(s,a)=R(s,a)+γsP(ss,a)aπ(as)Qπ(s,a)

3.4 贝尔曼最优方程

最优价值函数 V∗(s)V^*(s)V(s)Q∗(s,a)Q^*(s,a)Q(s,a) 是在所有策略中的最大值:

V∗(s)=max⁡πVπ(s),Q∗(s,a)=max⁡πQπ(s,a) V^*(s) = \max_{\pi} V^{\pi}(s), \quad Q^*(s,a) = \max_{\pi} Q^{\pi}(s,a) V(s)=πmaxVπ(s),Q(s,a)=πmaxQπ(s,a)

贝尔曼最优方程(非线性,因为包含 max 操作):

V∗(s)=max⁡a[R(s,a)+γ∑s′P(s′∣s,a)V∗(s′)] V^*(s) = \max_{a} \left[ R(s,a) + \gamma \sum_{s'} P(s'|s,a) V^*(s') \right] V(s)=amax[R(s,a)+γsP(ss,a)V(s)]

Q∗(s,a)=R(s,a)+γ∑s′P(s′∣s,a)max⁡a′Q∗(s′,a′) Q^*(s,a) = R(s,a) + \gamma \sum_{s'} P(s'|s,a) \max_{a'} Q^*(s',a') Q(s,a)=R(s,a)+γsP(ss,a)amaxQ(s,a)

一句话总结:贝尔曼方程告诉我们,当前状态的价值等于即时奖励加上折扣后的未来价值期望。


4. 动态规划求解

4.1 策略评估(Policy Evaluation)

给定一个策略 π\piπ,计算其状态价值函数的过程称为策略评估。

迭代算法

Vk+1(s)=∑aπ(a∣s)[R(s,a)+γ∑s′P(s′∣s,a)Vk(s′)] V_{k+1}(s) = \sum_{a} \pi(a|s) \left[ R(s,a) + \gamma \sum_{s'} P(s'|s,a) V_k(s') \right] Vk+1(s)=aπ(as)[R(s,a)+γsP(ss,a)Vk(s)]

重复迭代直到收敛:max⁡s∣Vk+1(s)−Vk(s)∣<θ\max_s |V_{k+1}(s) - V_k(s)| < \thetamaxsVk+1(s)Vk(s)<θ

4.2 策略改进(Policy Improvement)

基于当前价值函数,通过贪心选择改进策略:

π′(s)=arg⁡max⁡aQπ(s,a)=arg⁡max⁡a[R(s,a)+γ∑s′P(s′∣s,a)Vπ(s′)] \pi'(s) = \arg\max_{a} Q^{\pi}(s,a) = \arg\max_{a} \left[ R(s,a) + \gamma \sum_{s'} P(s'|s,a) V^{\pi}(s') \right] π(s)=argamaxQπ(s,a)=argamax[R(s,a)+γsP(ss,a)Vπ(s)]

策略改进定理:如果 π′\pi'π 是通过上述方式从 π\piπ 得到的,则 Vπ′(s)≥Vπ(s)V^{\pi'}(s) \geq V^{\pi}(s)Vπ(s)Vπ(s) 对所有状态成立。

4.3 策略迭代(Policy Iteration)

策略迭代交替进行策略评估和策略改进,直到策略收敛:

1. 初始化:随机选择策略 π
2. 策略评估:计算 V^π
3. 策略改进:基于 V^π 得到 π'
4. 如果 π' ≠ π,令 π = π',返回步骤 2
5. 输出最优策略 π*

4.4 价值迭代(Value Iteration)

价值迭代将策略评估和策略改进合并为一步:

Vk+1(s)=max⁡a[R(s,a)+γ∑s′P(s′∣s,a)Vk(s′)] V_{k+1}(s) = \max_{a} \left[ R(s,a) + \gamma \sum_{s'} P(s'|s,a) V_k(s') \right] Vk+1(s)=amax[R(s,a)+γsP(ss,a)Vk(s)]

收敛后,最优策略为:

π∗(s)=arg⁡max⁡a[R(s,a)+γ∑s′P(s′∣s,a)V∗(s′)] \pi^*(s) = \arg\max_{a} \left[ R(s,a) + \gamma \sum_{s'} P(s'|s,a) V^*(s') \right] π(s)=argamax[R(s,a)+γsP(ss,a)V(s)]

对比总结

方法 特点 收敛速度
策略迭代 每次完整评估策略,再改进 迭代次数少,但每次计算量大
价值迭代 评估和改进合并,不维护显式策略 迭代次数多,但每次计算量小

5. 蒙特卡洛与时序差分

5.1 蒙特卡洛方法(MC)

当环境模型 P(s′∣s,a)P(s'|s,a)P(ss,a) 未知时,无法直接使用动态规划。蒙特卡洛方法通过采样完整回合(episode)来估计价值。

首次访问 MC 算法

对于每个状态 sss,维护一个回报列表。每当 sss 在某回合首次被访问时,计算该回合从 sss 开始的回报 GGG,加入列表。

V(s)=1N(s)∑i=1N(s)Gi(s) V(s) = \frac{1}{N(s)} \sum_{i=1}^{N(s)} G_i(s) V(s)=N(s)1i=1N(s)Gi(s)

特点

  • 必须等待回合结束才能更新
  • 无偏估计,但方差大
  • 只能用于回合制任务(episodic tasks)

5.2 时序差分学习(TD)

TD 方法结合了动态规划和蒙特卡洛的优点,使用自举(bootstrapping)进行在线学习。

TD(0) 更新规则

V(St)←V(St)+α[Rt+1+γV(St+1)−V(St)] V(S_t) \leftarrow V(S_t) + \alpha \left[ R_{t+1} + \gamma V(S_{t+1}) - V(S_t) \right] V(St)V(St)+α[Rt+1+γV(St+1)V(St)]

其中 α\alphaα 是学习率,δt=Rt+1+γV(St+1)−V(St)\delta_t = R_{t+1} + \gamma V(S_{t+1}) - V(S_t)δt=Rt+1+γV(St+1)V(St) 称为 TD 误差。

5.3 SARSA 算法

SARSA 是 on-policy 的 TD 控制算法,更新 QQQ 函数:

Q(St,At)←Q(St,At)+α[Rt+1+γQ(St+1,At+1)−Q(St,At)] Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha \left[ R_{t+1} + \gamma Q(S_{t+1}, A_{t+1}) - Q(S_t, A_t) \right] Q(St,At)Q(St,At)+α[Rt+1+γQ(St+1,At+1)Q(St,At)]

算法名称来源于更新使用的五元组:(St,At,Rt+1,St+1,At+1)(S_t, A_t, R_{t+1}, S_{t+1}, A_{t+1})(St,At,Rt+1,St+1,At+1)

5.4 n-step TD

n-step TD 是 MC 和 TD(0) 的推广,使用前 n 步真实奖励和估计价值:

Gt:t+n=Rt+1+γRt+2+⋯+γn−1Rt+n+γnV(St+n) G_{t:t+n} = R_{t+1} + \gamma R_{t+2} + \cdots + \gamma^{n-1} R_{t+n} + \gamma^n V(S_{t+n}) Gt:t+n=Rt+1+γRt+2++γn1Rt+n+γnV(St+n)

V(St)←V(St)+α[Gt:t+n−V(St)] V(S_t) \leftarrow V(S_t) + \alpha \left[ G_{t:t+n} - V(S_t) \right] V(St)V(St)+α[Gt:t+nV(St)]

  • n = 1:退化为 TD(0)
  • n = T-t(回合长度):退化为 MC

6. Q-Learning 与 DQN

6.1 Q-Learning 算法

Q-Learning 是 off-policy 的 TD 控制算法,由 Watkins 于 1989 年提出。

核心更新公式

Q(St,At)←Q(St,At)+α[Rt+1+γmax⁡a′Q(St+1,a′)−Q(St,At)] Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha \left[ R_{t+1} + \gamma \max_{a'} Q(S_{t+1}, a') - Q(S_t, A_t) \right] Q(St,At)Q(St,At)+α[Rt+1+γamaxQ(St+1,a)Q(St,At)]

关键特性

  • Off-policy:学习最优策略的同时,可以遵循探索性策略(如 epsilon-贪心)
  • 收敛性:在适当条件下,Q-Learning 以概率 1 收敛到最优 Q∗Q^*Q
  • 表格形式:仅适用于离散、有限的状态和动作空间

6.2 深度 Q 网络(DQN)

当状态空间巨大或连续时,表格方法失效。DQN 使用神经网络近似 QQQ 函数:

Q(s,a;θ)≈Q∗(s,a)Q(s,a;\theta) \approx Q^*(s,a)Q(s,a;θ)Q(s,a)

损失函数

L(θ)=E(s,a,r,s′)∼D[(r+γmax⁡a′Q(s′,a′;θ−)−Q(s,a;θ))2] L(\theta) = \mathbb{E}_{(s,a,r,s') \sim D} \left[ \left( r + \gamma \max_{a'} Q(s',a';\theta^-) - Q(s,a;\theta) \right)^2 \right] L(θ)=E(s,a,r,s)D[(r+γamaxQ(s,a;θ)Q(s,a;θ))2]

6.3 DQN 的三大关键技术

1. 经验回放(Experience Replay)

将交互经验 (s,a,r,s′,done)(s, a, r, s', done)(s,a,r,s,done) 存储在回放缓冲区,训练时随机采样小批量数据:

  • 打破数据相关性,提高样本效率
  • 平滑数据分布,稳定训练

2. 目标网络(Target Network)

使用独立的网络参数 θ−\theta^-θ 计算目标值,定期从主网络复制:

  • 减少训练过程中的震荡
  • 提高学习稳定性

3. 奖励裁剪与帧堆叠

  • 将奖励裁剪到 [-1, 1] 范围,统一不同游戏的奖励尺度
  • 堆叠最近 4 帧作为输入,捕捉运动信息

6.4 DQN 的改进算法

算法 核心改进 主要优势
Double DQN 解耦动作选择与动作评估 解决 Q 值过估计问题
Dueling DQN 分离状态价值和优势函数 更好地学习哪些状态有价值
Prioritized Experience Replay 按 TD 误差优先级采样 提高重要样本的采样概率
Noisy Nets 参数化探索噪声 替代 epsilon-贪心,自适应探索
C51 学习价值分布而非期望值 建模 Q 值的完整分布
Rainbow 整合六种改进 Atari 游戏上达到 SOTA

7. 前沿算法简介(2024-2025)

7.1 近端策略优化(PPO)

PPO 由 OpenAI 于 2017 年提出,已成为策略梯度方法的默认选择。

核心思想:限制策略更新的幅度,避免破坏性的大更新。

目标函数

LCLIP(θ)=Et[min⁡(rt(θ)A^t,clip(rt(θ),1−ϵ,1+ϵ)A^t)] L^{CLIP}(\theta) = \mathbb{E}_t \left[ \min\left( r_t(\theta) \hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \hat{A}_t \right) \right] LCLIP(θ)=Et[min(rt(θ)A^t,clip(rt(θ),1ϵ,1+ϵ)A^t)]

其中 rt(θ)=πθ(at∣st)πθold(at∣st)r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}rt(θ)=πθold(atst)πθ(atst) 是概率比,A^t\hat{A}_tA^t 是优势函数估计。

2024-2025 进展

  • PPO-Clip 变体:自适应调整 clipping 参数
  • 分布式 PPO:在大规模集群上实现高效训练
  • 多模态 PPO:结合视觉-语言模型进行复杂任务学习

7.2 软演员-评论家(SAC)

SAC 是最大熵强化学习框架下的 off-policy 算法,在机器人控制任务上表现优异。

核心特点

  • 最大化期望回报的同时最大化策略熵
  • 鼓励探索,避免过早收敛到局部最优
  • 自动调整温度参数 α\alphaα

目标函数

J(π)=∑tE(st,at)∼ρπ[r(st,at)+αH(π(⋅∣st))] J(\pi) = \sum_{t} \mathbb{E}_{(s_t,a_t) \sim \rho_\pi} \left[ r(s_t, a_t) + \alpha \mathcal{H}(\pi(\cdot|s_t)) \right] J(π)=tE(st,at)ρπ[r(st,at)+αH(π(st))]

2024-2025 应用

  • 自动驾驶决策:处理连续控制和高维感知输入
  • 机械臂操作:实现样本高效的灵巧操作
  • 多智能体协作:在团队任务中实现协调行为

7.3 DreamerV3:世界模型学习

DreamerV3 是 DeepMind 于 2023-2025 年推出的世界模型强化学习算法。

核心创新

  • 通用性:无需调整超参数即可在超过 150 种不同任务上训练
  • 样本高效:在 Minecraft 中从零开始挖掘钻石,无需人类示例
  • 世界模型:学习环境的动态模型,在想象中规划

算法架构

  1. 表示学习:将高维观测编码为紧凑的潜在状态
  2. 动态预测:学习状态转移模型 pθ(st+1∣st,at)p_\theta(s_{t+1}|s_t,a_t)pθ(st+1st,at)
  3. 奖励预测:学习奖励模型 pθ(rt∣st)p_\theta(r_t|s_t)pθ(rtst)
  4. 策略学习:在世界模型中训练策略

2025 年突破

  • 成功在 Minecraft 中完成钻石挖掘任务
  • 在机器人仿真中实现零样本迁移到真实环境
  • 与大型语言模型结合,实现语言条件控制

7.4 EfficientZero-V2

清华大学高阳团队于 2024 年提出的样本高效强化学习框架。

主要贡献

  • 统一的框架适用于离散和连续控制任务
  • 基于采样的树搜索用于动作规划
  • 在 50k-200k 交互数据预算下超越 DreamerV3

7.5 应用领域展望

应用领域 典型场景 代表算法
游戏AI 围棋、星际争霸、Dota2 AlphaGo、AlphaStar、OpenAI Five
机器人控制 行走、抓取、导航 SAC、PPO、DreamerV3
自动驾驶 路径规划、决策控制 PPO、Model-based RL
推荐系统 序列推荐、动态定价 Contextual Bandits、DQN
金融交易 投资组合管理、高频交易 PPO、A3C
自然语言处理 对话系统、文本生成 RLHF(基于人类反馈的强化学习)

8. 实战案例:使用 DQN 解决 CartPole 问题

8.1 环境介绍

CartPole 是强化学习的经典入门环境:

  • 目标:通过左右移动小车,保持杆子直立
  • 状态空间:4维连续向量(小车位置、速度、杆子角度、角速度)
  • 动作空间:2个离散动作(左移、右移)
  • 奖励:每步存活获得 +1 奖励
  • 终止条件:杆子倾斜超过 15 度,或小车移出边界,或达到 500 步

8.2 DQN 实现代码

import gymnasium as gym
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
from collections import deque
import random

# 设置随机种子,保证实验可复现
SEED = 42
random.seed(SEED)
np.random.seed(SEED)
torch.manual_seed(SEED)

# 超参数配置
class Config:
    # 网络结构
    STATE_DIM = 4      # CartPole 状态维度
    ACTION_DIM = 2     # 动作空间大小
    HIDDEN_DIM = 128   # 隐藏层维度
    
    # 训练参数
    BATCH_SIZE = 64            # 批量大小
    LEARNING_RATE = 1e-3       # 学习率
    GAMMA = 0.99               # 折扣因子
    EPSILON_START = 1.0        # 初始探索率
    EPSILON_END = 0.01         # 最终探索率
    EPSILON_DECAY = 0.995      # 探索率衰减
    TARGET_UPDATE = 10         # 目标网络更新频率
    MEMORY_SIZE = 10000        # 经验回放缓冲区大小
    NUM_EPISODES = 500         # 训练回合数
    MAX_STEPS = 500            # 每回合最大步数


# 定义 Q 网络
class DQN(nn.Module):
    """
    深度 Q 网络:将状态映射到每个动作的价值
    输入: 状态 (batch_size, state_dim)
    输出: 每个动作的价值 (batch_size, action_dim)
    """
    def __init__(self, state_dim, action_dim, hidden_dim):
        super(DQN, self).__init__()
        self.fc1 = nn.Linear(state_dim, hidden_dim)
        self.fc2 = nn.Linear(hidden_dim, hidden_dim)
        self.fc3 = nn.Linear(hidden_dim, action_dim)
        self.relu = nn.ReLU()
        
    def forward(self, x):
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        return self.fc3(x)


# 经验回放缓冲区
class ReplayBuffer:
    """
    存储和采样训练经验的缓冲区
    使用 deque 实现固定容量的循环缓冲区
    """
    def __init__(self, capacity):
        self.buffer = deque(maxlen=capacity)
    
    def push(self, state, action, reward, next_state, done):
        """存储一条经验 (s, a, r, s', done)"""
        self.buffer.append((state, action, reward, next_state, done))
    
    def sample(self, batch_size):
        """随机采样一批经验用于训练"""
        batch = random.sample(self.buffer, batch_size)
        states, actions, rewards, next_states, dones = zip(*batch)
        return (
            torch.FloatTensor(np.array(states)),
            torch.LongTensor(actions),
            torch.FloatTensor(rewards),
            torch.FloatTensor(np.array(next_states)),
            torch.FloatTensor(dones)
        )
    
    def __len__(self):
        return len(self.buffer)


# DQN 智能体
class DQNAgent:
    """
    DQN 智能体:包含策略网络、目标网络和经验回放
    """
    def __init__(self, config):
        self.config = config
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        
        # 创建策略网络和目标网络
        self.policy_net = DQN(
            config.STATE_DIM, 
            config.ACTION_DIM, 
            config.HIDDEN_DIM
        ).to(self.device)
        
        self.target_net = DQN(
            config.STATE_DIM, 
            config.ACTION_DIM, 
            config.HIDDEN_DIM
        ).to(self.device)
        
        # 目标网络初始化为策略网络的副本
        self.target_net.load_state_dict(self.policy_net.state_dict())
        self.target_net.eval()  # 目标网络不计算梯度
        
        # 优化器和经验回放缓冲区
        self.optimizer = optim.Adam(
            self.policy_net.parameters(), 
            lr=config.LEARNING_RATE
        )
        self.memory = ReplayBuffer(config.MEMORY_SIZE)
        
        self.epsilon = config.EPSILON_START
        self.steps_done = 0
    
    def select_action(self, state, training=True):
        """
        使用 epsilon-贪心策略选择动作
        以 epsilon 概率随机探索,以 1-epsilon 概率选择最优动作
        """
        if training and random.random() < self.epsilon:
            return random.randrange(self.config.ACTION_DIM)
        
        with torch.no_grad():
            state = torch.FloatTensor(state).unsqueeze(0).to(self.device)
            q_values = self.policy_net(state)
            return q_values.argmax().item()
    
    def update_epsilon(self):
        """衰减探索率"""
        self.epsilon = max(
            self.config.EPSILON_END, 
            self.epsilon * self.config.EPSILON_DECAY
        )
    
    def learn(self):
        """
        从经验回放中采样并更新网络参数
        使用 MSE 损失计算当前 Q 值与目标 Q 值的差异
        """
        if len(self.memory) < self.config.BATCH_SIZE:
            return None
        
        # 采样一批经验
        states, actions, rewards, next_states, dones = self.memory.sample(
            self.config.BATCH_SIZE
        )
        
        states = states.to(self.device)
        actions = actions.to(self.device)
        rewards = rewards.to(self.device)
        next_states = next_states.to(self.device)
        dones = dones.to(self.device)
        
        # 计算当前 Q 值: Q(s, a)
        current_q = self.policy_net(states).gather(1, actions.unsqueeze(1))
        
        # 计算目标 Q 值: r + gamma * max Q(s', a')
        with torch.no_grad():
            next_q = self.target_net(next_states).max(1)[0]
            target_q = rewards + (1 - dones) * self.config.GAMMA * next_q
        
        # 计算 MSE 损失
        loss = nn.MSELoss()(current_q.squeeze(), target_q)
        
        # 反向传播和优化
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()
        
        return loss.item()
    
    def update_target_network(self):
        """将策略网络的参数复制到目标网络"""
        self.target_net.load_state_dict(self.policy_net.state_dict())


# 训练函数
def train_dqn():
    """
    主训练循环:与环境交互、存储经验、训练网络
    """
    config = Config()
    
    # 创建环境
    env = gym.make('CartPole-v1')
    agent = DQNAgent(config)
    
    scores = []           # 记录每回合得分
    scores_window = deque(maxlen=100)  # 最近 100 回合的得分
    
    print("开始训练 DQN...")
    print(f"设备: {agent.device}")
    
    for episode in range(config.NUM_EPISODES):
        state, _ = env.reset(seed=SEED + episode)
        score = 0
        
        for step in range(config.MAX_STEPS):
            # 选择动作并执行
            action = agent.select_action(state, training=True)
            next_state, reward, terminated, truncated, _ = env.step(action)
            done = terminated or truncated
            
            # 存储经验
            agent.memory.push(state, action, reward, next_state, float(done))
            
            # 更新网络
            loss = agent.learn()
            
            state = next_state
            score += reward
            agent.steps_done += 1
            
            if done:
                break
        
        # 更新探索率
        agent.update_epsilon()
        
        # 定期更新目标网络
        if episode % config.TARGET_UPDATE == 0:
            agent.update_target_network()
        
        scores_window.append(score)
        scores.append(score)
        avg_score = np.mean(scores_window)
        
        # 打印训练进度
        if episode % 50 == 0:
            print(f"回合 {episode:4d} | 得分: {score:3.0f} | "
                  f"平均得分: {avg_score:6.2f} | 探索率: {agent.epsilon:.3f}")
        
        # 检查是否解决环境(连续 100 回合平均得分 >= 475)
        if avg_score >= 475.0 and episode >= 100:
            print(f"\n环境在 {episode} 回合后解决!")
            print(f"平均得分: {avg_score:.2f}")
            break
    
    env.close()
    return agent, scores


# 测试训练好的模型
def test_agent(agent, num_episodes=10):
    """
    测试训练好的智能体性能
    """
    env = gym.make('CartPole-v1', render_mode='human')
    total_score = 0
    
    print("\n测试训练好的智能体...")
    for episode in range(num_episodes):
        state, _ = env.reset()
        score = 0
        
        for step in range(500):
            action = agent.select_action(state, training=False)
            state, reward, terminated, truncated, _ = env.step(action)
            score += reward
            
            if terminated or truncated:
                break
        
        total_score += score
        print(f"测试回合 {episode + 1}: 得分 = {score}")
    
    print(f"平均得分: {total_score / num_episodes:.2f}")
    env.close()


# 主程序
if __name__ == "__main__":
    # 训练
    agent, scores = train_dqn()
    
    # 保存模型
    torch.save(agent.policy_net.state_dict(), 'dqn_cartpole.pth')
    print("\n模型已保存到 dqn_cartpole.pth")
    
    # 绘制训练曲线(可选)
    try:
        import matplotlib.pyplot as plt
        plt.figure(figsize=(10, 5))
        plt.plot(scores)
        plt.title('DQN Training Progress on CartPole')
        plt.xlabel('Episode')
        plt.ylabel('Score')
        plt.grid(True)
        plt.savefig('dqn_training_curve.png')
        print("训练曲线已保存到 dqn_training_curve.png")
    except ImportError:
        print("matplotlib 未安装,跳过绘图")

8.3 代码解析

关键设计要点:

  1. 网络结构:使用两个隐藏层的全连接网络,输入状态输出每个动作的价值
  2. 经验回放:存储最近 10000 条经验,随机采样打破数据相关性
  3. 目标网络:每 10 个回合同步一次,稳定学习目标
  4. 探索策略:epsilon-贪心,从 1.0 逐渐衰减到 0.01
  5. 终止条件:连续 100 回合平均得分达到 475 分视为解决

9. 避坑小贴士

9.1 常见错误与解决方案

问题现象 可能原因 解决方案
Q 值发散,损失爆炸 学习率过大 降低学习率至 1e-4 或 5e-4
训练不稳定,波动大 目标网络更新太频繁 增加目标网络更新间隔
无法收敛,得分停滞 探索率衰减过快 减缓 epsilon 衰减速度
内存占用过高 回放缓冲区太大 减小 MEMORY_SIZE 或定期清理
GPU 利用率低 批量太小 增大 BATCH_SIZE 到 128 或 256

9.2 调试技巧

  1. 可视化 Q 值:监控 Q 值的变化趋势,正常情况下应逐渐收敛
  2. 检查探索:确保前期有足够的随机探索,避免陷入局部最优
  3. 梯度裁剪:添加梯度裁剪防止梯度爆炸
    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=10)
    
  4. 奖励缩放:某些环境需要对奖励进行归一化处理

9.3 超参数调优建议

  • 学习率:从 1e-3 开始,如果发散则降低,收敛慢则提高
  • 折扣因子:大多数任务使用 0.99,短期任务可尝试 0.9
  • 批量大小:64-256 是常见范围,更大的批量需要更多内存
  • 网络大小:简单任务用 64-128 隐藏单元,复杂任务用 256-512

10. 本章小结

本章系统介绍了强化学习的基础理论和实践方法:

核心概念回顾:

  1. MDP 框架:状态、动作、奖励、转移概率、折扣因子五元组
  2. 贝尔曼方程:价值函数的递归定义,是 RL 算法的理论基础
  3. 动态规划:策略迭代和价值迭代,适用于已知模型的场景
  4. TD 学习:结合采样和自举,适用于无模型场景
  5. Q-Learning:Off-policy 的 TD 控制算法,收敛到最优策略
  6. DQN:深度神经网络 + 经验回放 + 目标网络,解决高维状态问题

前沿进展:

  • PPO:策略梯度方法的默认选择,稳定高效
  • SAC:最大熵框架,在连续控制任务上表现优异
  • DreamerV3:世界模型学习,实现样本高效和通用性
  • RLHF:结合人类反馈,推动大语言模型发展

一句话总结:强化学习是让智能体通过与环境交互、试错学习,最终找到最优决策策略的机器学习方法。


感谢阅读!如有疑问,欢迎在评论区交流讨论。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐