小坷笔记:强化学习零碎知识点笔记

小坷笔记:强化学习零碎知识点笔记
坷强化学习:折扣回报与贝尔曼方程核心概念
1. 折扣回报 (Discounted Return) 的理论公式:
衡量一个状态的好坏,不仅看当下,还要看未来。
$$
G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \dots = \sum_{k=0}^{\infty} \gamma^k R_{t+k+1}
$$
- $\gamma$ (Gamma):折扣率 (0 到 1 之间)。决定了智能体是“目光短浅”($\gamma$ 接近 0)还是“高瞻远瞩”($\gamma$ 接近 1)。
2. 解决“未知未来”的两大落地方法:
- 蒙特卡洛方法 (Monte Carlo):必须等一个回合 (Episode) 彻底结束,拿到所有真实的 $R$ 序列后,再反向计算 $G_t$ 进行更新。属于“事后结算”。
- 时序差分法 (Temporal Difference, TD):不需要等回合结束,走一步算一步。核心在于利用贝尔曼方程:
$$V(s_t) = \mathbb{E}[R_{t+1} + \gamma V(s_{t+1})]$$
利用【当下真实的即时奖励 $R_{t+1}$】加上【下一个状态的预估价值 $\gamma V(s_{t+1})$】,来更新【当前状态的价值 $V(s_t)$】。
核心机制:经验回放 (Experience Replay)
1. 经验元组 (Transition Tuple)
智能体与环境交互的基本记录格式:$[s, a, r, s’]$
- $s$: 当前状态 (State)
- $a$: 动作 (Action)
- $r$: 奖励 (Reward)
- $s’$: 下一状态 (Next State)
(注:有时候还会加上一个 $done$ 标志,记录游戏是否结束,变成 $[s, a, r, s’, done]$)
2. 机制流程
- 存储 (Store):智能体在探索环境时,将每一步的 $[s, a, r, s’]$ 存入一个固定容量的队列 (Memory/Buffer) 中。如果存满了,新的经验会挤掉最老的经验。
- 采样 (Sample):在训练更新网络时,从 Memory 中随机抽取 (Random Sample) 一批大小为
batch_size的经验进行学习。
3. 为什么必须随机抽样?
- 打破时间相关性 (Break Correlation):连续的样本之间高度相似,会导致神经网络训练产生震荡甚至崩溃。随机抽样能让送入网络的数据分布更加均匀。
- 提高数据利用率:过去犯下的惨痛错误(稀有经验),可以在未来被多次随机抽中并反复复习,而不是经历一次就被遗忘了。
机器学习核心概念:偏差、方差与拟合状态
在机器学习中,模型的总误差可以分解为:$Error = Bias^2 + Variance + Noise$
1. 欠拟合 (Underfitting) $\rightarrow$ 高偏差 (High Bias)
- 表现:模型在“训练集”和“测试集”上的表现都很差,准确率都不高。
- 原因:模型过于简单(如用直线拟合曲线),表达能力不足,无法捕捉数据中的真实规律。
- 形象比喻:瞄准镜歪了。预测结果集体偏离真实值。
2. 过拟合 (Overfitting) $\rightarrow$ 高方差 (High Variance)
- 表现:模型在“训练集”上表现极好(甚至 100% 准确),但在“测试集”上表现极其糟糕。
- 原因:模型过于复杂,把训练数据里的噪声和偶然特征也当成规律死记硬背了下来。泛化能力极差。
- 形象比喻:手抖得厉害。稍微改变一下训练数据,训练出的模型就会剧烈变动,极不稳定。
3. 终极目标:偏差-方差权衡 (Bias-Variance Tradeoff)
我们想要的是一个既能看懂规律(低偏差),又不过度敏感(低方差)的模型,也就是打靶时既对准靶心,手又稳(子弹密集击中靶心)。
Advantage Actor-Critic(A2C)
首先定义几个核心变量。$s$ 代表机器人当前的状态(例如各关节的位姿、速度、绳索张力),$a$ 代表输出的动作(例如电机的控制电压或目标力矩),$r$ 是环境给出的即时奖励,$\gamma$ 是用于折算未来收益的折扣因子(通常在 0.9 到 0.99 之间,决定了机器人有多看重长远收益)。
Critic 网络(价值网络)
Critic 网络的内部参数记作 $\phi$。它的任务是预测在状态 $s$ 下,未来能获得的总回报,即价值函数 $V_\phi(s)$。当机器人执行动作 $a$ 后,环境反馈了即时奖励 $r$,并进入下一个状态 $s’$。此时我们可以计算出一个“更真实的期望值”,即目标值(TD Target):
$$y = r + \gamma V_\phi(s’)$$
Critic 网络需要让自己的预测 $V_\phi(s)$ 尽可能逼近这个目标值 $y$。因此,Critic 的损失函数(Loss)通常采用均方误差:
$$L_C(\phi) = \frac{1}{2} (V_\phi(s) - y)^2$$
有了损失函数后,Critic 通过求导计算梯度,并以学习率 $\alpha_C$ 更新自身参数 $\phi$,使得下一次预测更准:
$$\phi \leftarrow \phi - \alpha_C \nabla_\phi L_C(\phi)$$
Actor 网络(策略网络)
Actor 网络的内部参数记作 $\theta$。它的输出是一个概率分布 $\pi_\theta(a|s)$,表示在状态 $s$ 下选择动作 $a$ 的概率。Actor 更新的基础是优势函数(Advantage Function),记作 $A(s, a)$。它衡量的是“当前动作实际带来的收益”与“Critic 预期的平均收益”之间的差值。公式直接利用了前面计算的 TD 误差:
$$A(s, a) = r + \gamma V_\phi(s’) - V_\phi(s)$$
如果 $A(s, a) > 0$,说明动作 $a$ 表现优于预期;反之则差于预期。Actor 的损失函数旨在最大化好动作的概率,由于通常使用梯度下降优化器,所以加上负号将其转化为最小化问题:
$$L_A(\theta) = - \log \pi_\theta(a|s) A(s, a)$$
Actor 根据这个损失函数计算梯度,并以学习率 $\alpha_A$ 更新自身参数 $\theta$:
$$\theta \leftarrow \theta - \alpha_A \nabla_\theta L_A(\theta)$$
完整训练闭环
下面是仿真环境中代码实际运行的流水线:
第一步(采集数据):Actor 接收机器人状态 $s$,根据概率 $\pi_\theta(a|s)$ 采样得到动作 $a$。第二步(环境交互):仿真环境执行动作 $a$,解算绳驱系统的动力学,返回即时奖励 $r$ 和新的状态 $s’$。第三步(计算优势值):Critic 根据 $r$ 和新老状态,计算出目标值 $y = r + \gamma V_\phi(s’)$,并得出优势值 $A(s, a) = y - V_\phi(s)$。第四步(更新 Critic):Critic 计算自身误差 $L_C(\phi)$,执行梯度下降更新参数 $\phi$,提升估值精度。第五步(更新 Actor):Actor 利用计算好的优势值 $A(s, a)$,计算策略误差 $L_A(\theta)$,执行梯度下降更新参数 $\theta$,优化控制策略。
可视化交互组件方便理解:
1 | <div style="border: 1px solid #e2e8f0; padding: 20px; border-radius: 8px; font-family: sans-serif; background-color: #ffffff; color: #333;"> |
Actor-Critic 与 A2C 算法区别
1. 基础版:Actor-Critic (AC)
- 评分标准:使用绝对价值(如 $Q$ 值)来更新策略。
- 缺点:无法区分是“动作选得好”还是“当前状态本身就好”。导致策略梯度更新时方差极大 (High Variance),训练非常不稳定。
2. 进阶版:Advantage Actor-Critic (A2C)
- 评分标准:引入优势函数 $A(s,a)$。
- 核心公式:$A(s,a) = Q(s,a) - V(s)$
- $A > 0$:该动作比平均水平好,增加该动作的概率。
- $A < 0$:该动作比平均水平差,降低该动作的概率。
- 优点:相当于引入了一个“基线 (Baseline)”。剥离了状态本身带来的好坏,只评估动作的相对好坏。大幅降低了方差 (Reduced Variance),让训练变得极其稳定和高效。
np.array_equal
比较numpy数组尽量不用==,要用np.array_equal。
两个 numpy 数组用==时,不会像普通变量 / 列表那样返回「单个 True/False」,而是对两个数组中对应位置的元素逐一比较 ,返回一个和原数组形状完全相同的布尔数组。
1 | import numpy as np |
np.array_equal(a, b)是专门判断两个数组是否完全相等的函数,核心特性:
- 先判断两个数组的形状是否一致(比如都是 2 维、都是 N×2),形状不同直接返回 False;
- 形状一致则逐元素比较,所有元素都相等才返回单个 True,否则返回单个 False;
- 返回值是单个布尔值,可以直接用在if判断里,完美解决数组比较的问题。
1 | np.array_equal(loc1, loc2) # False(元素不全等) |
@dataclass、default_factory 与 Lambda
1. @dataclass 装饰器
来源:from dataclasses import dataclass (Python 3.7+)
定义:一个用于简化类定义的装饰器,专门用于创建主要存储数据的类(Data Class)。
核心功能:自动生成 __init__、__repr__、__eq__ 等样板代码,让代码极度简洁。
写法对比
- 传统写法(手动挡):
1
2
3
4
5
6class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})" - Dataclass 写法(自动挡):
1
2
3
4
class Point:
x: int
y: int
2. field(default_factory=…)
核心痛点:可变默认参数陷阱
在 Python 类中,绝对不能直接使用可变对象(如 list, dict, set)作为类变量的默认值。
错误写法:items: list = []
后果:所有该类的实例会共享同一个列表内存地址。修改 A 对象的列表,B 对象的列表也会随之改变。
为了解决共享问题,我们需要告诉 Python:不要使用现成的对象,而是每次实例化时,调用一个“工厂函数”现场创建一个新对象。
示例:
1 | taskTypes: List[str] = field(default_factory=lambda: ["search", "fire", "facility"]) |
ROS 2 与 Gazebo 桥接器 (ros_gz_bridge) 语法
在 Launch 文件中配置 parameter_bridge 时,字符串格式非常严谨,核心公式为:话题名称@ROS数据类型<方向号>Gazebo数据类型
1. 符号含义:
@:分隔符,分隔话题名称和数据类型。]:单向通信,数据从 ROS 2 流向 Gazebo(例如:下发控制指令、推力)。[:单向通信,数据从 Gazebo 流向 ROS 2(例如:获取传感器数据、Odometry 里程计位置)。@或==(有些版本支持双向):双向通信。
2. 核心代码结构:
1 | from launch_ros.actions import Node |
为什么 VS Code 找不到代码引用/不高亮?
问题现象
在 VS Code 中选中一个函数(比如 step),但在其他类中调用它的地方(比如 self.bandit.step())却没有高亮显示。右键点击“查找所有引用”也毫无反应,让人误以为这个函数没被用过。
根本原因:Python 的“动态类型”特性
Python 是一门非常自由的语言,声明变量时不需要指定类型。
当你写下 def __init__(self, bandit): 时,VS Code 的代码分析器(Pylance/IntelliSense)并不知道传入的 bandit 到底是个什么对象(是数字?是字符串?还是老虎机?)。因为不知道身份,VS Code 为了避免报错,干脆就不进行跨文件/跨类的高亮关联。
终极解决办法:类型提示 (Type Hint)
在定义参数时,顺手给它“贴个标签”,明确告诉 VS Code 它的真实身份。
- 修改前(VS Code 无法识别):
1
def __init__(self, bandit):
- 修改后(VS Code 瞬间变聪明): (加上
1
def __init__(self, bandit: BernoulliBandit):
: BernoulliBandit后,VS Code 瞬间就能把两个类关联起来,代码高亮、Ctrl + 点击跳转、自动补全全部复活!)
备用方案:暴力搜索法
如果是在阅读别人写的老代码(没有类型提示),千万别依赖高亮来判断函数有没有被调用。请直接使用:
- 单文件搜索:
Ctrl + F - 全局搜索(最管用):
Ctrl + Shift + F,在整个工程文件夹里直接搜索函数名。
rsl_rl
1. 简介
rsl_rl 是一个基于 PyTorch 实现的强化学习库,由 ETH Zurich 的 RSL 实验室开发。它专门为同步的大规模并行采样而设计,通常作为 Isaac Gym 或 Isaac Lab 的后端算法库。
2. 核心特点
- 极致的训练速度:通过 GPU 端的向量化环境采样,可以在几十分钟内完成传统 RL 需要几天才能完成的训练量。
- PPO 算法优化:内置了高度优化的 PPO (Proximal Policy Optimization) 算法,非常适合处理高维连续动作空间(如足式机器人的关节电机控制)。
- 轻量化架构:相比于 Stable Baselines3 (SB3) 或 Ray Rllib,它的代码结构非常精简,开发者可以轻松修改网络结构或 Loss 函数。
- 足式机器人适配:内置了处理刚体动力学任务中常见的观察值、特权信息(Privileged Information)和周期性奖励函数的逻辑。
3. 算法原理:PPO 简述
rsl_rl 主要实现的是 Actor-Critic 架构的 PPO 算法。其目标函数公式如下:
$$J^{CLIP}(\theta) = \hat{\mathbb{E}}_t \left[ \min(r_t(\theta) \hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \hat{A}_t) \right]$$
其中:
- $r_t(\theta)$ 是新旧策略的概率比。
- $\hat{A}_t$ 是优势函数(Advantage Function)。
- $\epsilon$ 是裁剪超参数,防止策略更新步长过大。
4. 关键文件结构
在使用 rsl_rl 时,你通常会接触到以下核心类:
OnPolicyRunner: 负责管理整个训练循环(存储、评估、存盘)。PPO: 算法核心,处理梯度更新和 Loss 计算。ActorCritic: 定义神经网络架构(通常包括 Actor 网络和 Critic 网络)。RolloutStorage: 在 GPU 上直接存储经验轨迹的数据结构。
5. 典型工作流
- 环境定义:在 Isaac Gym/Lab 中定义机器人的 URDF、传感器和奖励函数。
- 配置参数:通过 Python 的
config类定义超参数(如learning_rate,num_steps_per_env,entropy_coef)。 - 启动训练:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from rsl_rl.runners import OnPolicyRunner
# 初始化环境
env = VecEnv(...)
# 初始化训练器
runner = OnPolicyRunner(env, train_cfg, log_dir)
# 开始学习
runner.learn(num_learning_iterations=1000, init_with_graceful_stop=True)
# 变量占位符与拆包赋值
## 1. 语法现象:`_, var = function()`
在 Python 中,当一个函数返回多个值(以元组形式)时,如果我们只需要其中的某一个或某几个,可以使用 `_` 作为占位符。
### 示例代码
```python
def get_user_info():
# 返回 (姓名, 年龄, 职业)
return "wkh", 23, "算法工程师"
# 只需要姓名,忽略其他信息
name, _, _ = get_user_info()
# 或者使用 * 忽略剩余所有
name, *_ = get_user_info()
跑RL训练流程
不要在带界面的情况下跑训练(极慢),标准操作分为三步:
Step 1: 纯后台训练 (Train)
通过 –headless 剥离渲染,将 100% 的算力交给张量计算和物理引擎。
1 | # 激活环境并挂载 C++ 底层库隔离区或者其他配置 |
Step 2: 数据监控 (Monitor)
代码会自动在 logs 目录下按时间戳存档。使用 TensorBoard 查看“大脑”发育情况。
1 | # 建议使用绝对路径启动,防止找不到数据 |
核心指标: Train/mean_reward(平均奖励,需稳步上升)和 Episode/length(存活时间,需逐渐变长)。注意: 数据非实时写入,需让程序运行几分钟后刷新网页。
Step 3: 前台可视化验收 (Play)
加载最新训练好的模型权重(只做前向推理,算力压力极小),弹窗查看实际表现。
1 | # 运行前确保处于 Xorg 桌面环境 |
pip install -e . (可编辑安装)
一句话总结:在当前目录下,以“开发者可编辑模式”将项目安装到当前的 Conda 环境中。它的本质是建立一个指向源代码的快捷方式,而不是复制文件。
-e (全称 –editable):代表“可编辑模式” (Editable mode)。
. :代表“当前终端所在的目录”。(前提:该目录下必须存在 setup.py 或 pyproject.toml 等项目构建文件)。
普通安装(没有
-e): 比如你pip install numpy,系统会把numpy的代码复制一份,扔进你的Conda环境的一个深层文件夹(site-packages)里。可编辑安装(加了
-e): 系统不会复制代码,它只是在你的Conda环境里建立了一个快捷方式,指向你当前下载的这个rsl_rl文件夹。
为什么要用 -e:因为如果原作者代码有 Bug,或者你想修改底层算法,由于系统是指向这个文件夹的,你只要在这个文件夹里修改了代码保存,你的环境里会立刻生效,不需要重新 pip install。
以 rsl_rl 为例:
你使用
git clone下载了rsl_rl的源代码文件夹到你的电脑上。你在终端
cd rsl_rl进入该文件夹。执行
pip install -e .。
结果: 此时在当前环境内, Python 已经认识了 rsl_rl 这个包。无论你的终端以后切换到电脑的哪个目录下运行代码,只要遇到 import rsl_rl,系统都会通过快捷方式,飞回你最初 clone 下来的那个源码文件夹去读取逻辑。
注意:绝对不能移动源代码文件夹! 如果你把下载的 rsl_rl 文件夹剪切到了另一个硬盘或目录,快捷方式就会断裂。当你再次运行代码时,会直接报错 ModuleNotFoundError: No module named 'rsl_rl'。






