Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

RL——离线强化学习整体介绍

  • 参考文献:
    • 综述1:A Survey on Offline Reinforcement Learning: Taxonomy, Review, and Open Problems(2022,最新更新2024年8月,持续更新)
    • 综述2:Offline Reinforcement Learning: Tutorial, Review, and Perspectives on Open Problems(2020)
    • 离线强化学习(A Survey on Offline Reinforcement Learning)

离线强化学习是什么?

  • 一句话描述:不与环境进行交互,只在固定数据集上进行策略学习的强化学习,也称为 Offline RL 或 Batch RL,自2020年以后基本都叫做Offline RL
  • 直接将off-policy方法使用到Offline RL场景会面临的核心问题是外推误差 ,外推误差(Extrapolation Error)的定义:off-policy值学习中,当前策略真实状态动作访问分布和数据集中的状态动作分布不匹配导致的一种误差,具体来说,包括Absent Data(状态动作对缺失),Training Mismatch(训练预测分布不一致),Model Bias(随机MDP的状态转移概率有偏差)等问题

离线强化学习的优缺点

  • 优点:
    • 应用安全:不需要与真实环境交互,能防止未知风险;
    • 训练高效:样本利用率高;
  • 缺点:
    • 外推误差:容易面临外推误差问题,导致策略学不好;
    • 样本限制:样本量多和行为策略探索性强的数据集较好,对于基于模仿学习的离线强化学习方法,一般需要行为策略是专家策略(本质也于外推误差相关,如果样本量太少,或者行为策略过于局限,都可能只收集到固定的某个局部状态或动作,加重外推误差问题)

离线强化学习数据集D4RL

  • 数据集原始论文:D4RL: Datasets for Deep Data-Driven Reinforcement Learning
  • D4RL的一些介绍:
    • D4RL: DATASETS FOR DEEP DATA-DRIVEN REINFORCEMENT LEARNING,知乎
    • offline RL | D4RL:最常用的 offline 数据集之一
  • 数据集安装:离线强化学习(Offline RL)系列2: (环境篇)D4RL数据集简介、安装及错误解决 - Jensen Wang的文章 - 知乎,直接安装D4RL会遇到一些问题,需要逐步解决
  • 数据集一般都会包含一些特性:
    • Narrow and Biased Data Distributions (NB) :少量且有偏数据集,即OOD问题可能很严重的数据
    • Undirected and Multitask Data (UM) :无方向和多任务数据,即当前收集到的数据不是为了解决问题而收集的。(理解:比如要出找到出口,但是行为策略不找出口而是随机开门关门打开柜子等操作)
    • Sparse Rewards (SR) :系数奖励,即奖励稀疏的场景
    • Suboptimal Data (SD) :次优数据,即使用次优策略收集的数据
    • Nonrepresentable Behavior Policies (NR) :不可表示的行为策略。(理解:当函数逼近器(function approximator)很难完全捕捉基础行为的复杂性时,策略难以被神经网络或其他函数逼近器表示出来)
    • Non-Markovian Behavior Policies (NM) :非马尔可夫行为策略,一般出现在人类Agent或者手动工程控制中?不遵循马尔可夫性?【TODO:待补充】
    • Realistic Domains (RD) :现实领域。即现实数据,不是模拟数据,可能会出现噪声等信息
    • Nonstationarity (NS) :非平稳性,即不稳定的MDP过程。在这个数据集中,Agent可能会遇到传感器故障、执行器退化或奖励函数更新的情况,导致随时间变化的MDP中的扰动(例如,泵的效率随时间下降)
  • D4RL和RL Unplugged环境

离线强化学习的分类

说明:目前没有非常官方的离线强化学习分类方法,本文的分类方法来源于论文A Survey on Offline Reinforcement Learning: Taxonomy, Review, and Open Problems(2022,最新更新2024年8月,持续更新)

  • 本文按照修改类型分类(Modification Types),即按照离线强化学习方法在标准多步AC(Multi-step Actor Critic)的基础上做了哪些修改(注:修改类型的对照基线模型是标准Multi-step AC方法),可以将修改类型分成以下几类
  • 各种修改类型的一些图示
  • 不同算法的修改类型归属(同一个算法可能同时使用多个修改类型)

Policy Constraints

  • 策略约束分为直接策略约束(Direct Policy Constriants)和隐式策略约束(Implicit Policy Constraints)两种
  • 直接策略约束方法 :学习策略 \(\pi_\theta\) 的同时估计行为策略 \(\pi_\beta\),同时限制策略 \(\pi_\theta\) 贴近行为策略 \(\pi_\beta\)
    $$
    \begin{align}
    J(\theta) &= \mathbb{E}_{s\sim d^{\pi_\theta}, a\sim\pi_\theta(\cdot\vert s)}[Q^{\pi}(s,a)] \\
    \text{s.t.} &\ D(\pi_\theta(\cdot\vert s), \hat{\pi}_\beta(\cdot\vert s)) \le \epsilon
    \end{align}
    $$
    • 其中D表示某个距离评估指标,一般常用KL散度
    • 需要直接评估行为策略,如果行为策略评估不准确,这种方式效果会比较差
  • 隐式策略约束方法 :不显示的估计行为策略 \(\pi_\beta\),只依赖于样本(行为策略 \(\pi_\beta\) 收集来的样本),通过修改目标函数来隐式的约束策略 \(\pi_\theta\),实现不评估行为策略 \(\pi_\beta\) 的同时对策略 \(\pi_\theta\) 施加约束
    • 首先通过策略提升推导得到如下目标函数:
      $$
      \begin{align}
      \mathcal{L}(\pi,\lambda) &= \mathbb{E}_{s\sim d^{\pi_\beta}(\cdot)}[\mathbb{E}_{ a\sim\pi(\cdot\vert s)}[\hat{A}^{\pi}(s,a)] + \lambda (\epsilon - D_{\text{KL}}(\pi(\cdot\vert s), \hat{\pi}_\beta(\cdot\vert s)))]
      \end{align}
      $$
    • 然后进一步求解优化问题的解如下:
      $$ \pi^*(a|s) = \pi_\beta(a|s)exp(\frac{1}{\lambda}\hat{A}^{\pi}(s,a)) $$
    • 进一步地,最小化 \(\pi_\theta\) 和 \(\pi^*\) 的KL散度,可以最终推导得到如下的目标函数:
      $$ J(\theta) = \mathbb{E}_{(s,a) \sim \mathcal{D}} \log \pi_\theta(a|s)exp(\frac{1}{\lambda}\hat{A}^{\pi}(s,a)) $$
    • 以上方式相当于一种加权极大似然估计(Weighted Maximum Likelihood),根据以上公式,我们不再需要显示的学习行为策略 \(\pi_\beta\),可以直接从 \((s,a)\) 中学习到最优策略
  • 策略约束有两种形式,一种是Distribution Matching Constraints(也称为Distribution Constraints),一种是Support Matching Constraints(也称为Support Constraints)
    • Distribution Constraints:指限制 \(\pi_\theta\) 和 \(\pi_\beta\) 这两个策略的分布足够接近
    • Support Constraints:不要求 \(\pi_\theta\) 和 \(\pi_\beta\) 这两个策略的分布足够接近,只需要限制从 \(\pi_\theta\) 采样的动作在 \(\pi_\beta\) 采样动作的支持集里面即可(支持集即 \(\pi_\beta(a|s)\) 分配了正概率的所有动作 \(a\) 的集合)

Importance Sampling

  • 通过重要性采样实现
    $$
    \mathbb{E}_{\tau\sim \pi_\beta}\Big[ w_{0:H}\sum_{t=0}^H\nabla_\theta\gamma^t \log \pi_\theta(a_t|s_t)\hat{Q}^{\pi}(s_t,a_t) \Big]
    $$
    • 其中 \(w_{0:H}\) 是重要性权重 \(w_t = \frac{\pi(a_t|s_t)}{\hat{\pi}_\beta(a_t|s_t)}\) 的乘积

Regularization

  • 基本思路是在优化目标上增加一个正则项,分为策略正则(Policy Regularization)和值正则(Values Regularization)
  • 策略正则(Policy Regularization)
    $$ J(\theta) = \mathbb{E}_{(s,a) \sim \mathcal{D}} [\hat{Q}^{\pi_\theta}(s_t,a_t)] + \mathcal{R}(\theta) $$
  • 值正则(Values Regularization)
    $$ J(\phi) = \mathbb{E}_{(s,a,s’) \sim \mathcal{D}}\Big[(r(s,a) + \gamma \mathbb{E}_{a’\sim\pi_{\text{off}}(\cdot|s)}[Q_\phi^\pi(s’,a’)] - Q_\phi^\pi(s,a))^2\Big] + \mathcal{R}(\phi) $$

Uncertainty Estimation

  • 基本思路是在保守(Conservative)RL和原始(Native)RL上做一些Trade off,基于我们对泛化能力的信任程度不同,可以选择不同程度的保守策略,对策略做不同程度的放松;这里的不确定性评估可以是对策略、值和模型的评估
    $$ J(\theta) = \mathbb{E}_{(s,a) \sim \mathcal{D}}\Big[ \mathbb{E}_{Q^\pi\sim\mathcal{P}_{\mathcal{D}}(\cdot)} [Q^\pi(s,a)] - \alpha U_{\mathcal{P}_{\mathcal{D}}} (\mathcal{P}_{\mathcal{D}}(\cdot)) \Big] $$
    • \(\mathcal{P}_{\mathcal{D}}(Q^\pi)\) 表示在数据集 \(\mathcal{D}\) 中,Q函数的分布
    • 其中 \(U_{\mathcal{P}_{\mathcal{D}}}(\cdot)\) 是对 \(\mathcal{P}_{\mathcal{D}}\) 的不确定性评估(这里的 \(\mathcal{P}_{\mathcal{D}}\) 是下角标)
      • 理解:在对数据集 \(\mathcal{D}\) 上对某个变量进行不确定性评估,这个变量也是与数据集相关的,所以 \(U_{\mathcal{P}_{\mathcal{D}}} (\mathcal{P}_{\mathcal{D}}(\cdot))\) 中数据集出现了两次

Model-based

  • 首先通过标准的监督学习回归方法构建模型预测状态转移概率 \(T_{\psi_T}(s_{t+1}|s_t,a_t)\) 和奖励函数 \(r_{\psi_r}(s_t,a_t)\)
  • 使用学习到的状态转移概率和奖励函数模型作为真实环境的一个代理,与策略交互
  • 在数据覆盖面广(探索足够充分)时,Model-based方法效果比较容易学习
  • Model-based方法也可以用到Online RL场景,在Offline RL场景中,由于无法与环境交互,所以无法修正一些错误的预估值,一种解决这个问题的方法是采用保守策略,类似于Uncertainty Estimation思想对奖励函数进行修正
    $$ \tilde{r}_{\psi_r}(s,a) = r_{\psi_r}(s,a) + \lambda U_r(s,a) $$
    • \(U_r(s,a)\) 表示不确定度,在数据集中出现过的 \((s,a)\),对应的不确定性较小,没有出现过的不确定性较大

One-step

  • 多步策略评估和策略提升出现OOD问题的原因是需要对策略 \(\pi_{\text{off}}\) 做策略评估,在选择动作评估目标Q值时,不可避免的容易出现 \(\pi_\beta\) 没有见过的动作
  • One-step方法的思想:先对策略 \(\pi_\beta\) 做策略评估得到准确的 \(Q^{\pi_\beta}(s,a)\),此时不会出现OOD,因为 \(a’\) 都是来自 \(\pi_\beta\) 的;接着从 \(Q^{\pi_\beta}(s,a)\) 中进行一步策略提取,找到最优策略
  • 在One-step的方法下,不需要担心OOD问题,因为我们从不需要访问OOD的状态动作对

Imitation Learning

  • 模仿学习是一类学习方法,这类方法的思路是通过模仿行为策略的行为来实现策略学习
  • 离线强化学习中的模仿学习常见的有行为克隆(Behavior Clone,BC)及其改进版本
  • BC方法要求行为策略是专家策略,BC的基本目标如下:
    $$ J(\theta) D(\pi_\theta(\cdot\vert s), \hat{\pi}_\beta(\cdot\vert s)) $$
    • \(D(\cdot,\cdot)\) 是f-divergence,可以是交叉熵等
    • 其他说明:行为策略本身比较优质时BC能学到较好的效果
  • 改进版本不要求行为策略是专家策略,常常通过丢弃劣质动作或者对高收益动作进行加权实现
    • 动作挑选 :通过Q值或者一些启发式方法识别劣质和优质的动作
    • 动作加权 :设计特殊的目标函数,让策略决策到高收益动作的概率更高,比如AWR的优化目标
    • 条件策略(Conditional Policy) :学习一个条件生成网络和条件策略网络,条件生成网络可在给定轨迹 \(\tau\) 下生成条件,条件策略网络会根据给定条件和状态来生成策略,该策略的目标就是使得决策能够最终生成给定轨迹 \(\tau\) ?【TODO:待补充】

Trajectory optimization

  • 直接建模状态动作的联合分布,即轨迹分布,然后在这些分布里面规划出优质的轨迹,轨迹里面就包含了动作
  • 给定任意的 \(s_0\),先试探性规划多步,找到最优轨迹,然后选择最优轨迹上的动作决策一步即可(注意,为了避免误差累计,一次规划仅进行一次决策)

Off-policy Evaluation (OPE)

  • 目标:给定待评估策略 \(\pi\) 和评估数据集 \(\mathcal{D}_e\),希望定义一个可以评估效果的OPE目标 \(\hat{J}(\pi)\)
  • 方法包含Model-based,Importance Sampling和Fit Q Evaluation
  • 为什么需要OPE?
    • 现实世界中,许多领域里面,直接使用真实环境预测去交互以评估策略效果是危险且成本高昂(包括时间成本和资源成本)的,而OPE提供了一个不需要与环境交互就可以评估策略效果的方案(虽然有时候不是很准确)
    • OPE可以用作超参数的选择

Model-based Approach

  • 类似Model-based强化学习方法,通过标准的监督学习回归方法构建模型预测状态转移概率 \(T_{\psi_T}(s_{t+1}|s_t,a_t)\) 和奖励函数 \(r_{\psi_r}(s_t,a_t)\)
  • 评估结果为:
    $$\hat{J}(\pi) = \mathbb{E}_{\tau\sim p_{\phi_T}(\cdot)}\Big[ \sum_{t=0}^H\gamma^t r_{\psi_r}(s_t,a_t) \Big]$$
    • 其中 \(p_{\phi_T}(\cdot)\) 表示按照策略 \(\pi\) 在环境 \(T_{\psi_T}(s_{t+1}|s_t,a_t)\) 中采样得到的trajectory分布

Importance Sampling (IS)

  • 利用重要性采样的性质完成评估
    $$\hat{J}(\pi) = \mathbb{E}_{\tau\sim p_{\hat{\pi}_\beta(\cdot)}}\Big[ w_{0:H}\sum_{t=0}^H\gamma^t r(s_t,a_t) \Big]$$
    • 其中 \( w_{i:j}\) 是重要性采样的权重乘积
      $$ w_{i:j} = \frac{\prod_{t=i}^j\pi(a_t|s_t)}{\prod_{t=i}^j\hat{\pi}_\beta(a_t|s_t)} $$
    • 其中每一项 \(w_t = \frac{\pi(a_t|s_t)}{\hat{\pi}_\beta(a_t|s_t)}\),实际上 \( w_{i:j}\) 也可以写为:
      $$ w_{i:j} = \prod_{t=i}^j w_t = \prod_{t=i}^j\frac{\pi(a_t|s_t)}{\hat{\pi}_\beta(a_t|s_t)} $$

Fit Q Evaluation (FQE)

  • 先使用策略评估方法学习一个Q值 \(Q_\phi^\pi\) (最小化贝尔曼误差即可),然后在数据集上评估该Q值的累计值
    $$ \hat{J}(\pi) = \mathbb{E}_{(s,a)\sim \mathcal{D}_e}[Q_\phi^\pi(s,a)] $$

离线强化学习方法补充

AWR(Advantage-Weighted Regression)

  • 参考链接:ADVANTAGE-WEIGHTED REGRESSION: SIMPLE AND SCALABLE OFF-POLICY REINFORCEMENT LEARNING, UC Berkeley, arXiv 2019
  • AWR 训练流程

BCQ

CQL

IQL

BRAC(Behavior-Regularized Actor-Critic)

  • 参考链接:(BRAC)Behavior Regularized Offline Reinforcement Learning, arXiv 2019, CMU & Google
  • BRAC 训练流程

XQL(Extreme Q-Learning)

  • 参考链接:Extreme Q-Learning: MaxEnt RL without entropy, ICLR 2023, Google
  • 也称为 \(\mathcal{X}\)-QL
  • XQL 训练流程

SQL(Sparse Q-Learning)

  • 参考链接:[待确认]
  • 参考链接:Sparse Q-learning with Mirror Descent, University of Massachusetts, arXiv 2012
  • 参考链接:SPARSE Q-LEARNING: OFFLINE REINFORCEMENT LEARNING WITH IMPLICIT VALUE REGULARIZATION, Offline RL Workshop 2023

RWR(Reward-Weighted Regression)

  • 参考链接:待确认

EDAC(Ensemble Diversity Actor-Critic)

  • 参考链接:待确认

AWAC(Advantage-Weighted Actor-Critic)

  • 参考链接:AWAC: Accelerating Online Reinforcement Learning with Offline Datasets, UC Berkeley, arXiv 2020
  • AWAC 整体方案介绍(Offline + Online 的训练方式)
  • AWAC 训练流程

Cal-QL(Calibrated Q-Learning)

  • 参考链接:Cal-QL: Calibrated Offline RL Pre-Training for Efficient Online Fine-Tuning, UC Berkely & Stanford University, NeurIPS 2023
  • Cal-QL 训练流程

RS——SENet

  • 参考链接
    • 原始论文:Squeeze-and-Excitation Networks

整体说明

  • SENet(Squeeze-and-Excitation Network)是一种用于图像识别等领域的神经网络架构,通过显式地建模通道之间的相互依赖关系,自适应地调整特征通道的重要性 ,从而提高模型的性能
  • 在CV领域爆火后,近年来,SENet也被广泛应用于推荐系统中
  • 一句话说明:SENet可以给与不同通道不同的权重,从而实现图片的重构(不同通道被乘以不同权重)

SENet整体结构

  • 整体结构图如下:
  • Squeeze层(\(\mathbf{F}_{sq}\)):
    • 输入 :特征图\(U \in \mathbb{R}^{H \times W \times C}\),其中\(H\)、\(W\)、\(C\)分别表示特征图的高度、宽度和通道数
    • 输出 :一个长度为\(C\)的向量\(z \in \mathbb{R}^{C}\)
    • 公式 :\(z_c = \mathbf{F}_{sq}(U_c)=\frac{1}{H\times W}\sum_{i=1}^{H}\sum_{j=1}^{W}U_c(i,j)\),即对每个通道的特征图进行全局平均池化,将二维的特征图压缩成一个实数,得到通道的全局统计信息
  • Excitation层((\(\mathbf{F}_{ex}\))):
    • 输入 :Squeeze层输出的向量\(z\)
    • 输出 :与输入特征图通道数相同的权重向量\(s \in \mathbb{R}^{C}\),用于表示每个通道的重要性
    • 公式 :\(s = \mathbf{F}_{ex}(z, W)= \sigma(g(z, W))=\sigma(W_2\delta(W_1z))\),其中\(\sigma\)是sigmoid函数,\(\delta\)是ReLU函数,\(W_1 \in \mathbb{R}^{\frac{C}{r} \times C}\)和\(W_2 \in \mathbb{R}^{C \times \frac{C}{r} }\)是两个全连接层的权重矩阵,\(r\)是一个缩减比例超参数,用于控制中间神经元的数量,减少模型复杂度
  • Scale层((\(\mathbf{F}_{scale}\))):也称为Reweight
    • 输入 :原始特征图\(U\)和Excitation层输出的权重向量\(s\)
    • 输出 :经过通道加权后的特征图\(\tilde{U} \in \mathbb{R}^{H \times W \times C}\)
    • 公式 :\(\tilde{U}_c = \mathbf{F}_{scale}(U_c, s_c)=s_c \cdot U_c\),即将权重向量\(s\)与原始特征图\(U\)的每个通道对应相乘,实现对特征图的自适应加权
  • 注:\(\mathbf{F}_{tr}\) 是一个转换操作,跟SENet没有直接关系,在 CV 里面就是一个普通的卷积神经网络

在CV领域的使用

  • 图像分类 :SENet可以嵌入到各种经典的图像分类网络中,如ResNet、Inception等,通过对特征通道的自适应加权 ,能够更好地捕捉图像中的重要特征 ,抑制图片中的无关特征 ,从而提高分类准确率

  • 目标检测 :在目标检测任务中,SENet有助于模型更准确地定位和识别目标物体。它可以增强与目标相关的特征通道 ,使模型对目标的细节和特征更加敏感,提高检测的精度和召回率

  • 语义分割 :对于语义分割任务,SENet能够帮助模型更好地理解图像中的语义信息,通过调整通道权重 ,突出不同语义类别对应的特征 ,从而更精确地分割出不同的物体和区域

  • 在 Inception 模块中的嵌入方式

  • 在 Residual 模块中的嵌入方式


在推荐系统中的使用

  • 特征加权 :在推荐系统中,将用户和物品的特征类比为图像中的特征通道。SENet可以学习不同特征的重要性权重,对用户行为特征、物品属性特征等进行自适应加权,强调对推荐结果有重要影响的特征,提高推荐的准确性
    • 理解:可以将SENet应用于用户的嵌入向量、物品的嵌入向量或者深度神经网络中的隐藏层输出,SENet可以对不同特征给与不同的权重(这里的特征和CV中的通道类似),相当于是一种特征重要性抽取器
    • 举例:假设输入 \(N \times d\) 维特征矩阵 ,有 \(N\) 个特征,每个特征是 \(d\) 维的 Embedding,则:
      • Squeeze层 :将每个特征 Embedding 从 \(d\) 维度降低到 1 维标量,输出 \(N\) 维向量
      • Excitation层 :用一个MLP将 \(N\) 维向量先压缩到 \(\frac{N}{r}\) 维再扩展为 \(N\) 维
      • Scale层(Re-weight) :将 \(N\) 维向量作为权重对原始 \(N \times d\) 维的特征矩阵进行加权,输出 \(N \times d\) 维特征矩阵 ,此时每个特征都有自己的个性化权重
    • SENet的本质是对输入 Embedding 做 field-wise 加权(这里认为每个特征就是不同的 field)
  • 注意力机制 :类似于在CV领域中捕捉图像中的重要信息,SENet在推荐系统中可以作为一种注意力机制,聚焦于用户和物品的关键特征,从而更好地建模用户与物品之间的交互关系,为用户提供更个性化的推荐

ML——参数化模型与非参数化模型

本文介绍非参数化模型(Non-parametric models)和参数化模型(Parametric models)


参数化模型

定义

参数化模型指的是那些依赖于固定数量参数的模型,这些参数可以通过训练数据学习得到。参数化模型的特点是一旦参数确定,模型的复杂度也就固定了

举例

参数化模型的例子包括线性回归、逻辑回归、支持向量机(SVM)和神经网络等

非参数化模型

定义

非参数化模型则不对模型形式做出严格的假设,也不依赖于固定数量的参数。这类模型通常具有更大的灵活性,可以根据数据的复杂性来适应模型的复杂度

举例

非参数化模型的例子包括高斯过程回归、决策树、k-最近邻(k-NN)算法、核方法以及各种基于模型的集成方法如随机森林和提升树(Boosting Trees)等

其他说明

  • 尽管神经网络可以拥有大量的参数,使得它们具有高度的灵活性和强大的表达能力,但这些特点并不使其成为非参数化模型。非参数化模型指的是不对模型形式做出严格假设的模型,其参数的数量和模型的复杂度可以随着数据量的增加而增加。因此,即使神经网络在实际应用中可以非常复杂,它们仍然是基于固定参数数量的参数化模型

  • 神经网络越来越复杂,现在参数量已经非常大,实际上可以看做是非参数模型?

DL——迁移学习-元学习-联邦学习

迁移学习-元学习-联邦学习对比

  • 迁移学习侧重于知识从源任务到目标任务的迁移
  • 元学习侧重于快速适应新任务的能力
  • 联邦学习则侧重于在数据隐私保护的前提下进行分布式学习

迁移学习

  • 迁移学习(Transfer Learning)允许模型在一个任务上学习得到的知识应用到另一个不同但相关的任务上。这种方法特别适用于目标任务的数据量不足时。在迁移学习中,通常有一个源域(source domain)和一个目标域(target domain),模型首先在源域上进行训练,然后将学到的特征或参数迁移到目标域以提高学习效率和性能
  • 参考博客: https://blog.csdn.net/dakenz/article/details/85954548

元学习

  • 元学习(Meta-Learning),又称为“学会学习”,是指模型不仅学习如何处理具体的任务,而且学习如何从经验中快速适应和学习新任务的过程。元学习特别关注于当面对新任务时,如何利用已有的知识来加速学习过程。元学习的一个典型应用是通过少量的样本(例如,少样本学习)快速适应新任务
  • 参考链接:【李宏毅-元学习】少样本&元学习Meta Learning_MAML最新机器学习课程!!!
  • 以常见的元学习方法 MAML 的流程为例:
    • 任务采样 :从任务集合中随机选择一批任务,每个任务都包含训练集和验证集
      • 训练集用于更新模型的参数,验证集用于评估模型的泛化性能
    • 任务内优化(内循环) :对于每个采样到的任务,首先使用当前的初始化参数进行梯度更新
      • 通过这些更新,让模型在任务训练集上的表现逐步改善,快速适应当前任务的特定要求
    • 元优化(外循环) :在完成任务内优化后,使用更新后的模型参数在其他任务的验证集上计算损失
      • 然后,基于多个任务的验证集损失,对初始化参数进行更新
      • 外循环的梯度更新是通过任务验证集上的损失进行的,目的是优化整个模型的初始参数 ,使其能在多个任务上快速学习
  • 总结:MAML 的目标是在元训练阶段,通过在多个任务上进行训练和优化 ,找到一个良好的初始参数 ,使得当模型遇到新任务时 ,只需利用新任务的少量数据进行几次快速梯度更新,就能迅速适应新任务,达到较好的性能

联邦学习

  • 联邦学习(Federated Learning)是一种分布式机器学习范式,它允许多个参与者在保持数据隐私和数据本地化的前提下共同构建机器学习模型。在联邦学习中,数据不需要集中存储或处理,而是在各个参与者的本地进行训练,只有模型的更新(如参数)在参与者之间共享。这种方式可以解决数据孤岛问题,同时保护用户隐私。

DL——重参数化技巧

  • 参考链接:
    • 漫谈重参数:从正态分布到Gumbel Softmax
    • 重参数化技巧(Gumbel-Softmax)
    • 通俗易懂地理解Gumbel Softmax

重参数化解决的问题

  • 问题 :假设用NN建模一个分布,比如正太分布可以表达为 \(\mathcal{N}(\mu_\theta,\sigma_\theta)\),此时如果直接从NN建模的分布中采样,由于采样动作是离散的,那么这个采样结果不包含NN分布的梯度信息的,NN反向传播时无法传播回去,也无法实现对参数 \(\theta\) 的更新
  • 重参数化技巧 :通过一些技巧设计采样方式,使得采样过程可导,让采样结果包含NN分布的梯度信息(即实现既可按照NN分布采样 ,又可回传梯度信息)

重参数化的基本思想

  • 不能梯度回传的本质原因是因为采样过程是一种选择动作,这种选择动作本身没有梯度信息,把采样过程挪到计算图之外
  • 用形式来表示,将 \(z \sim f(\theta)\) 构建为形如 \(z = g(\theta, \epsilon), \epsilon \sim p\) 的形式(其中p是与参数无关的某个分布,比如高斯分布)

连续变量分布采样的重参数化

  • 以正太分布为例,原始NN分布采样形式:
    $$ z \sim \mathcal{N}(\mu_\theta,\sigma_\theta) $$
  • 重参数技巧采样:
    $$
    \begin{align}
    \epsilon \sim \mathcal{N}(0,1) \\
    z = \mu_\theta + \sigma_\theta \cdot \epsilon
    \end{align}
    $$

离散变量分布采样的重参数化

以下内容主要参考自重参数化技巧(Gumbel-Softmax)以及其中的回复讨论

原版 softmax(原始问题):

1
2
3
logits = model(x)
probs = softmax(logits)
r = torch.multinomial(probs, num_samples)
  • 采到的 r 都是整数 ID,后面可以用 r 去查 embedding table。缺点是采样这一步把计算图弄断了

Gumbel-Max Trick:

1
2
3
4
5
6
7
8
def sample_gumbel(shape, eps=1e-20, tens_type=torch.FloatTensor):
"""Sample from Gumbel(0, 1)"""
U = Variable(tens_type(*shape).uniform_(), requires_grad=False)
return -torch.log(-torch.log(U + eps) + eps)

logits = model(x)
g = sample_gumbel(logits.size())
r = torch.argmax(logits + g)
  • 采到的 r 都是整数 ID,后面可以用 r 去查 embedding table,计算图连起来了,但 argmax 仍不可导
  • 为什么一定要用sample_gumbel分布而不是其他分布?
    • 因为只有使用gumbel分布采样才能保证与原始softmax后的多项式分布采样完全等价,即 argmax(logits + Gumbel随机变量)与多项式分布采样严格等价 ,相关证明见:漫谈重参数:从正态分布到Gumbel Softmax
  • Gumbel分布的具体定义是什么?
    • 一般Gumbel分布的PDF和CDF:
      $$
      \begin{align}
      \text{PDF}: \quad f(x;\mu,\beta) = e^{-(z+e^{-z})},\quad z=\frac{x-\mu}{\beta} \\
      \text{CDF}: \quad F(x;\mu,\beta) = e^{-e^{-z}}, \quad z=\frac{x-\mu}{\beta}
      \end{align}
      $$
      • \(\mu\) 是位置参数(location parameter)
      • \(\beta\) 是尺度参数(scale parameter)
    • 标准Gumbel分布中, \(\mu=0,\ \beta=1\),此时有 \(z=x\)
      $$
      \begin{align}
      \text{PDF}: \quad f(x;\mu,\beta) = e^{-(x+e^{-x})} \\
      \text{CDF}: \quad F(x;\mu,\beta) = e^{-e^{-x}}
      \end{align}
      $$
  • 在这个场景中,我们使用标准Gumbel分布即可
  • 采样标准Gumbel分布时,可以直接使用逆变换采样(Inverse Transform Sampling) :
    • 先按照均匀分布采样: \(u = \mathcal{U}(0,1)\)
    • 对Gumbel分布原始CDF取逆Gumbel分布采样结果: \(z = -ln(-ln(u))\)

Gumbel-Softmax Trick:

1
2
3
logits = model(x)
g = sample_gumbel(logits.size())
r = F.softmax(logits + g)
  • 采到的 r 都是概率分布,后面可以用 r 把 embedding table 里的各个条目加权平均混合起来,假装是一个单词拿去用。虽然计算图可导了,但是训练和推断不一致!训练时模型见到的都是各个 word embedding 的混合,而非独立的 word embedding!推断时则使用的是独立的 word embedding!

Gumbel-Softmax Trick + Straight-Though Estimator:

1
2
3
4
5
logits = model(x)
g = sample_gumbel(logits.size())
r = F.softmax(logits + g)
r_hard = torch.argmax(r)
r = (r_hard - r).detach() + r
  • 采到的 r 都是整数 ID,后面可以用 r 去查 embedding table
  • 前向传播使用 r_hard 获得独立的单词,反向传播使用 r(即 softmax 的结果)的梯度。一切都很完美
  • Straight-Through Estimator 的意思是说,如果你遇到某一层不可导,你就当它的梯度是 identity,直接把梯度漏下去,即假定当前层的梯度为1
  • 实际上此时正向传播和反向传播面对的公式也不一样
    • 正向传播时得到的是r_hard
    • 反向传播时,由于(r_hard - r).detach()使得梯度为0,所以回传的实际是r的反向梯度

argmax动作的梯度回传

  • argmax操作的形式:
    $$
    \begin{align}
    i^* &= \mathop{\arg\max}_i (\vec{x}) \\
    \text{where} \quad \vec{x}=&(x_1, x_2, \cdots, x_n), \quad x_i = f(\theta)_i
    \end{align}
    $$
    • 注:以上argmax的写法不严谨,严谨的是 \(i^* = \mathop{argmax}_i x_i, \ x_i \in \vec{x}\)
  • 近似形式:
    $$
    \begin{equation}
    \mathop{\arg\max}_i (\vec{x}) \approx \sum_{i=1}^n i\times \text{softmax}(\vec{x})_i
    \end{equation}
    $$
  • argmax本质也可以看做一种离散采样,只是没有随机性,该采样选择使得目标值最大的离散变量
  • 详情见:函数光滑化杂谈:不可导函数的可导逼近

PyTorch——CrossEntopy-Loss和NLL-Loss的区别


整体说明

  • 交叉熵损失(Cross-Entropy Loss, CE Loss)和负对数似然损失(Negative Log-Likelihood Loss, NLL Loss)的核心区别在于 是否包含 Softmax 激活 以及 适用场景的精细化
  • PyTorch 框架中的交叉熵损失 = Softmax(或 Sigmoid)+ 负对数似然损失
  • 使用原则
    • 分类任务直接用 CE Loss(输入 logits),简单且数值稳定
    • 若已手动添加 Softmax/LogSoftmax 层,或需要自定义概率输出,再用 NLL Loss
  • NLL Loss 是“对概率的负对数”,CE Loss 是“对分布差异的衡量”,但在分类任务中(预测分布由 Softmax 生成)二者结果等价

分类任务场景符号定义

  • 模型原始输出(logits):\(z = [z_1, z_2, …, z_C]\)(\(C\) 为类别数)
  • 真实标签:\(y\)(one-hot 编码为 \(y_{one-hot} = [0, 0, …, 1, …, 0]\),或类别索引 \(y \in {0,1,…,C-1}\))
  • Softmax 激活:将 logits 映射为概率分布 \(p_i = \text{Softmax}(z)i = \frac{e^{z_i}}{\sum{j=1}^C e^{z_j}}\)(满足 \(\sum p_i = 1\))
  • 对数概率:\(\log(p_i)\)(概率越大,对数概率越接近 0;概率越小,对数概率越负)

负对数似然损失(Negative Log-Likelihood Loss, NLL Loss)

  • 负对数似然损失(NLL Loss)是 对 模型输出的概率分布 \(p\) 取对数后,取负,再根据真实标签\(y\)选择对应类别的项
    • 若标签为 one-hot 编码:
      $$ L_{\text{NLL}} = -\sum_{i=1}^C y_i \cdot \log(p_i)$$
      • 此时仅真实类别 \(y_k=1\),所以可进一步简化为
        $$ L_{\text{NLL}} = -\log(p_k) $$
        • 这也是 负对数似然损失(NLL Loss)名字的由来
    • 若标签为类别索引(本质是一个 index,one-hot 转换成向量以后与 one-hot 编码一致):直接取真实类别 \(y\) 对应的概率 \(p_y\) 的对数再取负
      $$ L_{\text{NLL}} = -\log(p_y)$$
  • 特别说明: NLL Loss 本身不包含 Softmax 步骤
    • 输入给 NLL Los 的必须是 已经经过 Softmax 激活的概率分布 \(p\) 或对数概率 \(\log(p)\))

交叉熵损失(Cross-Entropy Loss, CE Loss)

  • 交叉熵损失(CE Loss) 用于衡量 模型预测概率分布 \(p\) 与 真实标签分布 \(q\) (one-hot编码)之间的差异,公式与 NLL Loss形式完全一致 ,但隐含了“先将 logits 转为概率”的逻辑
    • 数学公式:
      $$ L_{\text{CE}} = -\sum_{i=1}^C q_i \cdot \log(p_i)$$
      • 其中: \(q\) 是真实分布
      • one-hot 时简化为
        $$ L_{\text{CE}} = -\log(p_y) $$
    • 关键区别:在深度学习框架中(如 PyTorch、TensorFlow),CE Loss 的输入是 原始 logits ,而非概率
      • 框架会内部先对 logits 做 Softmax,再计算负对数似然

CE Loss 和 NLL Loss 对比

  • 本质上,交叉熵(CE)的数学定义是
    $$ H(q,p) = -\sum q_i \log(p_i)$$
  • 而 负对数似然(NLL)是
    $$ -\sum q_i \log(p_i)$$
    • 仅当 \(p\) 是模型预测的概率分布时,因此当 \(p\) 是 Softmax 输出时,CE Loss = NLL Loss ,但二者的 计算链路 不同:
      $$ \text{CE Loss(logits)} = \text{NLL Loss(Softmax(logits))}$$

附录:为什么框架中的 CE Loss 更常用?

  • 在 PyTorch 中:
    • nn.NLLLoss():输入必须是 对数概率 (通常需先过nn.LogSoftmax(dim=1))
    • nn.CrossEntropyLoss():输入是 logits,内部等价于 nn.LogSoftmax(dim=1) + nn.NLLLoss()
  • 选择 CE Loss 的核心原因是数值稳定性 :
    • 直接计算 \(\log(\text{Softmax}(z_i))\) 时,若 \(z_i\) 很大(如 1000),\(e^{z_i}\) 会溢出(变成无穷大);而框架会用数学变换优化:
      $$ \log\left(\frac{e^{z_i}}{\sum_j e^{z_j}}\right) = z_i - \log\left(\sum_j e^{z_j}\right) $$
    • 通过减去 \(\log(\sum_j e^{z_j})\)(即 LogSumExp),避免了指数爆炸,数值更稳定
  • 补充:一种常用的等价推导(可以优化计算的方法)是
    $$
    \begin{align}
    \text{log_sofmax}(x) &= \log \frac{e^{x_{i}}}{\sum_{j=1}x_{j}} \\
    &= \log e^{x_i} - \log \sum_{j=1}x_{j} \\
    &= x_i - \log \sum_{j=1}x_{j}
    \end{align}
    $$
    • 上面的式子中,只需要计算一次 \(\log \sum_{j=1}x_{j}\) 即可(且不同维度可重用该值), 其他的都是加减法运算

使用代码示例

  • 用 CrossEntropyLoss(直接输入 logits)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import torch
    import torch.nn as nn

    # 模型输出logits(C=3类,batch_size=2)
    logits = torch.tensor([[2.0, 1.0, 0.1], [1.0, 3.0, 0.5]])
    # 真实标签(类别索引)
    labels = torch.tensor([0, 1])

    # 交叉熵损失(内部含Softmax)
    ce_loss = nn.CrossEntropyLoss()
    print(ce_loss(logits, labels)) # 输出:0.4170(数值稳定)
  • 用 NLLLoss(需先过LogSoftmax)

    1
    2
    3
    4
    5
    6
    7
    # 先将logits转为对数概率
    log_softmax = nn.LogSoftmax(dim=1)
    log_probs = log_softmax(logits) # 等价于框架内部优化后的计算

    # 负对数似然损失(输入是对数概率)
    nll_loss = nn.NLLLoss()
    print(nll_loss(log_probs, labels)) # 输出:0.4170(与CE Loss结果一致)

附录:错误示例(NLLLoss 直接输入 logits)

  • 常常犯的错:
    1
    2
    # 直接用logits输入NLLLoss(会报错或结果错误)
    print(nll_loss(logits, labels)) # 错误:logits不是对数概率,数值会异常

附录:BCE Loss

  • 二分类任务时,CE Loss 常被称为 二元交叉熵(BCE Loss) ,与 NLL Loss 的关系同样成立:

    • nn.BCEWithLogitsLoss():输入是 logits(单输出,如 \(s\)),内部先做 Sigmoid(\(\sigma(s) = 1/(1+e^{-s})\)),再计算BCE Loss:
      $$ L = -[y\log(\sigma(s)) + (1-y)\log(1-\sigma(s))]$$
    • 若手动用 Sigmoid + NLLLoss:需将标签转为 float(如 \(y \in {0.0, 1.0}\)),且输出为单通道概率,此时
      $$ L = -[y\log(p) + (1-y)\log(1-p)]$$
      • 与 BCEWithLogitsLoss 结果一致
  • BCELoss 还可以直接使用

    1
    torch.nn.BCELoss()
    • 具体操作就是实现了书上定义的二分类交叉熵定义
    • 普通样本计算公式:
      $$ loss(o,t)=-\frac{1}{n}\sum_i(t_i\log(o_i)+(1-t_i)\log(1-o_i)) $$
    • 带有权重的单个样本计算公式:
      $$ loss(o,t)=-\frac{1}{n}\sum_i w_i (t_i \log(o_i)+(1-t_i)\log(1-o_i)) $$

BCELoss vs CrossEntropyLoss 比较

  • BCELoss 对应的网络只有一个输出值
  • CrossEntropyLoss 对应的网络有两个输出值
  • 可以证明, 二分类时 BCELoss 与 CrossEntropyLoss等价
    • 证明时,将每个 CrossEntropyLoss 的计算公式中的 softmax 函数分子分母同时除以 shift(\(\text{shift} = max (x_i)\)), 即可得到为下面的定义,进一步可得到 BCELoss 的计算公式
      $$f_i(x) = \frac{e^{(x_i - \text{shift})}} { \sum^j e^{(x_j - \text{shift})}} $$

附录:相关损失函数 MultiLabelMarginLoss

  • 使用

    1
    torch.nn.MultiLabelMarginLoss()
  • 用于多标签分类的损失函数


总结

  • 一般来说直接使用 CrossEntropyLoss 即可
    • 二分类时还可以使用 nn.BCELoss
    • 二分类时使用 nn.BCELoss 的话,输入的 input和target维度都为 (n, 1) 的维度
    • 二分类时使用 CrossEntropyLoss 则输入的 input 为 (n x 2) 的维度
  • 如果使用 NLLLoss 则一定记得在输出层最后加一层 log_softmax 层
  • 注意,log 指的是以 e 为底的对数函数,而不是以10为底的
    • Mac自带的计算器中 log 是以 10 为底的,ln 才是以 e 为底的

PyTorch——Accelerate使用总结

  • 参考链接:
    • 官方文档:docs.pytorch.org/docs/stable/fsdp.html

整体说明

  • HuggingFace Accelerate 是一个轻量级库(accelerate),专为简化 PyTorch 模型在各种硬件配置上的训练和推理而设计
  • 它能自动处理分布式训练、混合精度训练等复杂设置,让开发者无需深入了解底层硬件细节,就能轻松将模型部署到单 GPU、多 GPU、TPU 甚至 CPU 集群等环境中(专注于模型逻辑和训练流程即可)
  • 安装简便,仅需一行代码:pip install accelerate
  • Accelerate 的核心功能包括下面几个
    • 自动识别可用硬件(GPU、TPU 等),并根据硬件情况优化训练配置
    • 无缝支持数据并行、模型并行等分布式训练模式 ,适配多 GPU 或集群环境
    • 可选择 FSDP 或 DeepSpeed 等底层框架,仅需简单修改启动命令即可
    • 支持 FP16、BF16 等混合精度训练 ,在减少显存占用的同时,保证模型训练精度
    • 只需对原有 PyTorch 代码进行少量修改 ,即可实现硬件加速和分布式训练
  • 一般来说仅需要两行代码改动,其他都有命令行进行配置
    • accelerator.prepare() :核心函数,用于包装模型、优化器、数据加载器等组件,自动适配分布式和混合精度设置
    • accelerator.backward() :替代传统的 loss.backward(),在分布式环境中确保梯度正确同步

HuggingFace Accelerate 使用代码示例

  • 以下是一个使用 Accelerate 进行模型训练的基础示例,训练代码(train.py)如下:
    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
    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.utils.data import Dataset, DataLoader
    from accelerate import Accelerator # 导入 Accelerator

    class DiyModel(nn.Module):
    def __init__(self):
    super().__init__()
    self.fc = nn.Linear(10, 2) # 二分类任务
    def forward(self, x):
    return self.fc(x)

    class DiyDataset(Dataset):
    def __len__(self):
    return 1000
    def __getitem__(self, idx):
    x = torch.randn(10)
    y = torch.randint(0, 2, (1,)).item() # 随机标签(0 或 1)
    return x, y

    # 混合精度配置:可通过 `Accelerator(mixed_precision="fp16")` 启用 FP16 混合精度训练,减少显存占用
    accelerator = Accelerator() # 核心代码,初始化 Accelerator

    # 以下所有定义都不涉及使用 Accelerator
    model = DiyModel()
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=1e-3)
    dataset = DiyDataset()
    dataloader = DataLoader(dataset, batch_size=32, shuffle=True)

    # 重点:使用 accelerator 包装多个组件(注:这一行会自动处理分布式和混合精度)
    model, optimizer, dataloader, criterion = accelerator.prepare(
    model, optimizer, dataloader, criterion
    )

    # 特别注意的一点不同是:训练循环时,使用 accelerator.backward()
    model.train()
    for epoch in range(3):
    total_loss = 0.0
    for x, y in dataloader:
    optimizer.zero_grad()
    outputs = model(x)
    loss = criterion(outputs, y)
    accelerator.backward(loss) # 替代 loss.backward()
    optimizer.step()
    total_loss += loss.item()
    avg_loss = total_loss / len(dataloader)
    print(f"Epoch {epoch+1}, Loss: {avg_loss:.4f}")

Accelerate 启动训练

  • 可简单通过 Accelerate 命令行工具配置训练环境并启动:

    1
    accelerate launch --num_processes=2 train.py  # 使用 2 个进程(如 2 个 GPU)
    • --num_processes:指定进程数(通常等于 GPU 数量)
    • 若使用单 GPU 或 CPU,可直接运行 python train.py,Accelerator 会自动适配环境
  • 更多启动命令参见下文


accelerate launch 命令详细说明

  • accelerate launch 是 HuggingFace Accelerate 库的核心命令,用于启动分布式训练脚本,它能自动处理多卡、多机等复杂分布式环境的配置

  • accelerate launch 的核心作用是:

    • 1)初始化分布式环境(进程组、通信后端等)
    • 2)根据参数自动选择分布式策略(数据并行/FSDP/DeepSpeed 等)
    • 3)将环境配置传递给训练脚本中的 Accelerator 实例,使其能正确处理模型、数据的分布式适配
  • accelerate launch 命令基本语法

    1
    accelerate launch [启动参数] your_script.py [脚本参数]
    • [启动参数]:控制分布式训练的配置(如使用的 GPU 数量、分布式策略等)
    • [脚本参数]:传递给你的训练脚本(your_script.py)的自定义参数(如 --epochs 10、--batch_size 32 等)

硬件与进程配置参数

  • --num_processes N:指定总进程数(通常等于参与训练的 GPU 总数)
    • 例如:--num_processes 4 表示使用 4 个 GPU
  • --num_machines N:指定机器数量(多机分布式训练时使用),默认值为 1(单机器)
  • --machine_rank N:当使用多机时,指定当前机器的序号(从 0 开始)
    • 例如:主节点用 --machine_rank 0,从节点用 --machine_rank 1
  • --main_process_ip IP地址:多机训练时,主节点的 IP 地址(供从节点连接)
  • --main_process_port 端口号:主节点的通信端口(默认 29500,需确保端口未被占用)

分布式策略选择参数

  • Accelerate 支持多种分布式策略,通过参数指定:
  • 默认策略(自动选择) :不指定任何策略时,Accelerate 会根据硬件自动选择当前硬件下的最佳策略:
    • 单卡:直接使用单进程训练
    • 多卡:默认使用 PyTorch 的 nn.DataParallel 或 DistributedDataParallel(数据并行)
  • --use_fsdp:
    • 启用 FSDP(完全分片数据并行) ,适合超大规模模型(需 PyTorch ≥ 1.11);
    • 常用搭配参数如下:
      • --fsdp_fully_shard:完全分片参数、梯度和优化器状态(最大程度节省内存)
      • --fsdp_transformer_layer_cls_to_wrap "类名":指定 Transformer 层的类名(如 GPT2 的 GPT2Layer、BERT 的 BertLayer),用于自动分片模型层
      • --fsdp_sharding_strategy 策略:分片策略,可选 FULL_SHARD(完全分片)、SHARD_GRAD_OP(梯度和优化器分片)等
  • --use_deepspeed:
    • 启用 DeepSpeed 分布式框架,支持 ZeRO 优化、混合精度等(需提前安装 deepspeed)
    • 通常需配合 DeepSpeed 配置文件使用,通过 --deepspeed 配置文件路径 指定

混合精度训练参数

  • --mixed_precision [mode]:指定混合精度策略
  • 可选模式 [mode] 为:
    • no:不使用混合精度(默认)
    • fp16:使用 FP16 混合精度
    • bf16:使用 BF16 混合精度(需 GPU 支持,如 A100、RTX 3090 等)
    • fp8:使用 FP8 混合精度(需 PyTorch ≥ 2.0 且 GPU 支持)

其他实用参数

  • --config_file 配置文件路径:通过 YAML 配置文件指定所有参数(推荐复杂场景使用),无需在命令行逐个输入
  • --debug:启用调试模式,打印详细的分布式初始化日志,便于排查问题
  • --gradient_accumulation_steps N:指定梯度累积步数(等价于在代码中设置,但通过命令行更灵活)

附录:在启动命令中使用配置文件用法(推荐)

  • 对于复杂配置(如 FSDP/DeepSpeed 细节),建议使用 YAML 配置文件,步骤如下:

  • 第一步:生成默认配置文件 :

    1
    accelerate launch --config_file accelerate_config.yaml --generate_config
    • --generate_config 表示运行后会交互式提问,自动生成配置文件
    • 注:也可以自己编辑 accelerate_config.yaml 文件
  • 第二步:示例配置文件(FSDP 场景) ::

    1
    2
    3
    4
    5
    6
    7
    8
    compute_environment: LOCAL_MACHINE  # 本地机器环境
    distributed_type: FSDP # 使用 FSDP 策略
    fsdp_config:
    fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP # 自动包装 Transformer 层
    fsdp_transformer_layer_cls_to_wrap: "GPT2Layer" # 模型层类名
    fsdp_sharding_strategy: FULL_SHARD # 完全分片
    mixed_precision: fp16 # 启用 FP16 混合精度
    num_processes: 4 # 4 个进程(4 卡)
  • 第三步:使用配置文件启动 :

    1
    accelerate launch --config_file accelerate_config.yaml your_script.py --epochs 10

附录:启动命令的常见场景示例

  • 示例一:单机器多卡基础数据并行示例

    1
    accelerate launch --num_processes 4 train.py --batch_size 32
  • 示例二:启用 FSDP 训练大模型

    1
    2
    3
    4
    5
    6
    7
    accelerate launch \
    --num_processes 4 \
    --use_fsdp \
    --fsdp_fully_shard \
    --fsdp_transformer_layer_cls_to_wrap "BertLayer" \
    --mixed_precision bf16 \
    train_bert.py
  • 示例三:启用 DeepSpeed 与 ZeRO-3 优化(其中 ds_config.json 为 DeepSpeed 配置文件,定义 ZeRO 阶段、梯度裁剪等)

    1
    2
    3
    4
    5
    accelerate launch \
    --num_processes 8 \
    --use_deepspeed \
    --deepspeed ds_config.json \
    train.py

PyTorch——compile函数的理解和使用


整体说明

  • torch.compile() 函数是 PyTorch 2.0 引入的一个重要功能,用于对模型进行编译优化,以提升训练和推理性能
    • 将 PyTorch 模型从“解释型”的逐行执行模式,转变为“编译型”的、一次性优化的执行模式
    • 当模型被编译后,它在后续的推理或训练中会运行得更快,并且使用的内存可能更少
  • torch.compile() 的核心作用是通过对模型计算图进行一系列优化(如算子融合、常量折叠、内存优化等),生成更高效的代码,从而加速模型的执行
    • 这个过程是自动的,并且大部分时间是无侵入性的,不需要修改模型的代码
  • torch.compile() 效果提升:
    • 因模型结构和硬件而异,通常对于有大量小操作(如 Transformer 模型)或对 GPU 算力要求高的模型效果更显著
    • 在大规模训练和推理场景中效果显著
  • 首次运行编译后的模型会有一定的启动延迟,用于图优化和代码生成(compile 函数是惰性执行的),但后续重复调用会更快
  • 编译过程可能会增加模型的内存占用,需根据实际情况调整

调用 torch.compile(model) 后会发生什么?

  • 具体来说,调用 torch.compile(model) 会发生以下过程:
  • 1)捕获模型计算图(Graph Capturing) :分析模型的前向传播逻辑,记录张量的操作序列和依赖关系,构建计算图表示
    • 当你第一次调用 torch.compile 编译后的模型时,它会追踪模型的前向传播过程
    • 这就像在记录模型中的每一步操作,比如矩阵乘法、卷积、激活函数等
    • PyTorch 会创建一个计算图(computational graph) ,这个图代表了模型从输入到输出的所有计算路径
    • 这个过程是惰性的,即只在第一次实际运行模型时发生(所以第一次调用比较慢,后面会比较快)
    • 计算图被捕获后,编译器会对计算图进行一系列优化(接下面)
  • 2)图优化(Graph Optimization) :包含一系列优化,例如:
    • 算子融合(Operator Fusion) : 将多个连续的小算子合并为一个大算子(如将卷积+批归一化+激活函数融合),减少 kernel 调用次数和内存读写
    • 常量折叠(Constant Folding) :计算图中固定不变的常量表达式会被预先计算,避免重复计算
    • 死代码消除(Dead Code Elimination) : 移除计算图中未被使用的节点或操作,减少不必要计算
    • 内存优化(Memory Optimization) : 优化张量的内存分配和复用,重新安排计算顺序,以减少中间结果所需的内存,从而更好地利用缓存
    • 优化后的计算图会用来生成高效的、针对特定硬件(如 GPU)的可执行代码(具体见下节)
  • 3)代码生成 :
    • 编译器将高级的 PyTorch 操作转换成底层的、更接近硬件指令的特定后端的代码(如 CPU 上的 C++/OpenMP 代码,GPU 上的 CUDA 代码)
      • 例如,它可能会将 PyTorch 的张量操作转换成 CUDA 内核
    • 支持多种后端(如 inductor、aot_eager 等),默认使用 inductor 后端(也叫做 TorchInductor 后端),它能生成高效的 GPU/CPU 代码
  • 4)返回编译后的模型 :
    • torch.compile(model) 返回一个经过包装的模型对象,其接口与原模型一致(可直接调用 forward 方法或进行训练),但内部执行的是优化后的代码

使用示例

  • 使用非常简单,仅需一行代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import torch
    import torch.nn as nn

    class DiyModel(nn.Module):
    def __init__(self):
    super().__init__()
    self.conv = nn.Conv2d(3, 32, kernel_size=3)
    self.relu = nn.ReLU()

    def forward(self, x):
    return self.relu(self.conv(x))

    model = DiyModel()
    # 编译模型
    compiled_model = torch.compile(model)

    # 使用编译后的模型(接口与原模型万全一致)
    x = torch.randn(1, 3, 224, 224)
    output = compiled_model(x)

PyTorch——einsum函数使用


整体说明:

  • torch.einsum 是 PyTorch 中一个非常强大且灵活的函数,用于执行基于爱因斯坦求和约定(Einstein summation convention)的张量运算
    • 通过这种约定,你可以简洁地表示复杂的多维数组操作,如矩阵乘法、转置、点积等,而不需要显式地编写循环
  • torch.einsum 提供了极大的灵活性来处理各种复杂的张量运算,但需要注意的是,不恰当的使用可能导致性能下降,因为它可能会隐藏潜在的优化机会(比如矩阵乘法建议直接调用矩阵乘法)
  • torch.einsum的本质就是爱因斯坦求和约定(一种爱因斯坦发表论文中提到的表达式省略写法),是矩阵乘法的一种表示
    • 比如 C = torch.einsum("m,d->nd", A,B)表示矩阵 C[m,d] = A[n]*B[d],这是个很常用的省略写法

基本用法

  • torch.einsum 的基本语法如下:

    1
    torch.einsum(equation, *operands)
    • equation:一个字符串,指定了输入张量的下标标签以及输出张量的计算方式
    • operands:可变数量的张量参数,它们将根据 equation 进行运算
  • 在 equation 参数中:

    • 每个输入张量的维度由字母标记,不同张量之间使用逗号分隔(亲测字符串中间可以随便加空格,不影响最终结果)
      • 相同字母只能表示相同维度Size,但是相同维度Size不要求必须相同字母
      • 下标数量和输入矩阵维度数量一定要对齐
    • 输出的维度是在箭头 -> 右侧指定,如果没有指定输出,则自动推断(注意,需要自动推断的场景是没有 -> 的场景,有->的场景不需要推断,->后面为空时表示输出是一个标量)
    • 推断思路是:重复下标都去掉,不重复下边按照顺序保留
      • ij,jk == ij,jk->ik
      • j,j == j,j->
      • ijd,jk == ijd,jk->idk
      • idi,jk == idi,jk->djk
  • 举例:给定两个二维张量 A 和 B,要进行矩阵乘法并求和可以写作:

    1
    torch.einsum('ij,jk->ik', A, B) # 等价于 torch.einsum('ij,jk', A, B)
    • 'ij' 表示张量 A 的维度,
    • 'jk' 表示张量 B 的维度,
    • 'ik' 表示输出张量的维度
    • 重复的下标(在这个例子中的 j)意味着沿着这些维度进行乘积和求和(后面会详细讲解)
  • 后面会有详细讲解


爱因斯坦求和约定讲解

  • 以 看图学 AI:einsum 爱因斯坦求和约定到底是怎么回事? 中的一个例子为例,torch.einsum('ij,jk->ik', A, B) 求解过程相当于下面的图片展示的形式:

  • 爱因斯坦求和约定的基本理解:

    • 对于任意的表达式,结果都等价于一个多重循环乘积(可能包含求和)的过程:
      • 输入:函数参数,字符串 -> 左边表示矩阵的输入下标
      • 输出:返回值,字符串 -> 右边表示矩阵的输出下标
      • 右边的下标有时候可以省略,此时需要自动推断
  • C = torch.einsum("ij,jk->ijk", A,B) 结果相当于下面的函数(性能上并不想当,因为下面是一种速度较慢的函数)

    1
    2
    3
    4
    for i in range(A[0]):
    for j in range(A[1]): # 也可以用 range(B[0])
    for k in range(B[1]):
    C[i][j][k] = A[i][j] * B[j][k]
  • C = torch.einsum("ij,jk->ik", A,B) 结果相当于下面的函数

    1
    2
    3
    4
    for i in range(A[0]):
    for j in range(A[1]): # 也可以用 range(B[0])
    for k in range(B[1]):
    C[i][k] += A[i][j] * B[j][k] # C[i][k]从0开始累加
  • C = torch.einsum("ii->i", A) 结果相当于下面的函数

    1
    2
    for i in range(A[0]): # 也可用A[1]
    C[i] = A[i][i]
  • 实际上,所有的表达式都可以表达成同一个形式,只要初始化结果的各个元素为0 ,然后统一使用加法即可


附录:一些简单示例

  • 矩阵乘法 :

    1
    2
    3
    A = torch.randn(3, 4)
    B = torch.randn(4, 5)
    C = torch.einsum('ij,jk->ik', A, B) # 等价于 torch.mm(A, B),C[i][k] += A[i][j] * B[j][k]
  • 向量内积 :

    1
    2
    3
    u = torch.randn(3)
    v = torch.randn(3)
    C = torch.einsum('i,i->', u, v) # 等价于 torch.dot(u, v),C += A[i] * B[i]
  • 张量转置 :

    1
    2
    A = torch.randn(2, 3, 4)
    C = torch.einsum('ijk->kji', A) # 转置张量 permute(2,1,0),C[k][j][i] += A[i][j][k]
  • 批量矩阵乘法 :

    1
    2
    3
    A = torch.randn(3, 2, 5)
    B = torch.randn(3, 5, 4)
    C = torch.einsum('bij,bjk->bik', A, B) # 对每一批次做矩阵乘法, C[b][i][k] += A[b][i][k] * B[b][k][k]
  • 乘法+求和 :

    1
    2
    3
    A = torch.randn(3, 4)
    B = torch.randn(4, 5)
    C = torch.einsum('ij,jk->', A, B) # 输出为一个标量 C += A[i][j] * B[j][k]

附录:高阶用法之省略号

  • 省略号容易误解,不建议使用!

附录:einops 包

  • 除了爱因斯坦求和(包含在 torch 包中)外,还有许多相似的爱因斯坦操作(包含在 einops 包中)

  • einops 包中的函数支持跨框架的数据格式,不仅仅是 PyTorch,比如 NumPy,TensorFlow 等

  • 最常见的有 rearrange 函数,可用于做下面的工作:

    • 对张量做更高阶的 reshape 或 view 操作
    • 对张量做 permute 或 transpose 操作
  • 特别地,爱因斯坦操作还有些对应的网络层,如 rearrange 函数对应的 einops.layers.torch.Rearrange 类可以像 nn.Linear 或 nn.ReLU 一样加入到 nn.Sequential() 中作为一个网络模块使用

  • rearrange 函数的简单示例

    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
    import torch
    import einops

    A = torch.randn(6,2,3,4)
    B = einops.rearrange(A, 'b c d e -> b (c d e)') # 等价于 A.reshape(6, -1)
    print(B.shape)
    # 输出:torch.Size([6, 24])

    A = torch.randn(6,2,3,4)
    B = A.reshape(6, -1)
    print(B.shape)
    # 输出:torch.Size([6, 24])

    A = torch.randn(6,2,3,4)
    B = einops.rearrange(A, 'b c d e -> b e d c') # 等价于 A.permute(0, 3, 2, 1)
    print(B.shape)
    # 输出:torch.Size([6, 4, 3, 2])

    A = torch.randn(6,2,3,4)
    B = A.permute(0, 3, 2, 1)
    print(B.shape)
    # 输出:torch.Size([6, 4, 3, 2])

    # 更复杂的用法
    A = torch.randn(2, 3, 9, 8)
    B = einops.rearrange(A, 'b c (d1 d2) (e1 e2) -> b c (d1 e1) d2 e2', d1=3, e1=2)
    print(B.shape)
    # 输出:torch.Size([2, 3, 6, 3, 4])
  • reduce 函数的简单示例(可选的 reduction 参数有 'sum','max','min','mean' 等):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import torch
    import einops

    A = torch.randn(6,2,3,4)
    B = einops.reduce(A, 'b c d e ->b c d', reduction='sum') # 等价于 A.sum(-1)
    print(B.shape)
    # 输出:torch.Size([6, 2, 3])

    A = torch.randn(6,2,3,4)
    B = einops.reduce(A, 'b c d e ->b c', reduction='sum') # 等价于 A.sum(-1).sum(-1)
    print(B.shape)
    # 输出:torch.Size([6, 2])

    A = torch.randn(6,2,3,4)
    B = einops.reduce(A, 'b c d e ->b c 1 1', reduction='mean') # 等价于 A.sum(-1).sum(-1)
    print(B.shape)
    # 输出:torch.Size([6, 2, 1, 1])

PyTorch——gather函数使用

  • 参考链接:
    • 一篇浅显易懂的博客:PyTorch中的高级索引方法——gather详解

gather函数形式

  • 包含 torch.gather 和 tensor.gather 两种形式,基本思路等价,他们的函数签名如下

    1
    2
    torch.gather(input, dim, index, *, sparse_grad=False, out=None)
    tensor.gather(dim, index, *, sparse_grad=False, out=None)
    • 注:在 PyTorch 的函数签名中,* 是 Python 语法中的一个特殊标记,用于表示强制关键字参数(keyword-only arguments)。这意味着在 * 之后的所有参数(如 sparse_grad 和 out)必须通过关键字(即使用参数名)来传递,而不能通过位置参数传递
  • 参数解释

    • input (Tensor): 输入张量
    • dim (int): 沿着哪个维度进行收集
    • index (LongTensor): 索引张量,包含要收集的元素的索引
    • sparse_grad (bool, 可选): 如果为True,梯度将是稀疏张量
    • out (Tensor, 可选): 输出张量(若该值不为 None,则会将返回值存储到 out 引用中,此时 out 和 返回值 是同一个对象)
  • 特别说明:gather 和普通的矩阵索引操作一样,操作支持反向传播


基本原理

  • 对于 3D 张量,gather操作可以表示为:

    1
    2
    3
    out[i][j][k] = input[index[i][j][k]][j][k]  # dim=0
    out[i][j][k] = input[i][index[i][j][k]][k] # dim=1
    out[i][j][k] = input[i][j][index[i][j][k]] # dim=2
  • 特别说明(记忆方法)

    • index[i][j][k] 用于索引 out[i][j][k]
    • dim=0 :index 指定行号(第0维) ,列号(第1维)和通道(第2维)保持不变
    • dim=1 :index 指定列号(第1维) ,行号(第0维)和通道(第2维)保持不变
    • dim=2 :index 指定通道(第2维) ,行号(第0维)和列号(第1维)保持不变
    • 特别地:输出形状 = index 形状

代码示例

  • 一维张量示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import torch

    # 基本用法
    input_tensor = torch.tensor([10, 20, 30, 40, 50])
    index_tensor = torch.tensor([0, 2, 4, 1])
    result = torch.gather(input_tensor, dim=0, index=index_tensor)
    print(result) # tensor([10, 30, 50, 20])

    # 使用tensor.gather方法
    result2 = input_tensor.gather(0, index_tensor)
    print(result2) # tensor([10, 30, 50, 20])
  • 二维张量示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # 二维张量
    input_2d = torch.tensor([[1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]])

    # 沿着dim=0收集(按行收集)
    index_2d = torch.tensor([[0, 1, 2],
    [2, 0, 1]])
    result_dim0 = torch.gather(input_2d, dim=0, index=index_2d)
    print("dim=0 结果:")
    print(result_dim0)
    # tensor([[1, 5, 9],
    # [7, 2, 6]])

    # 沿着dim=1收集(按列收集)
    index_2d_col = torch.tensor([[0, 2],
    [1, 0],
    [2, 1]])
    result_dim1 = torch.gather(input_2d, dim=1, index=index_2d_col)
    print("dim=1 结果:")
    print(result_dim1)
    # tensor([[1, 3],
    # [5, 4],
    # [9, 8]])

一些实际应用场景

  • 获取最大值,获取对应标签等实例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 场景1: 获取每行的最大值索引对应的值
    scores = torch.tensor([[0.1, 0.8, 0.3],
    [0.6, 0.2, 0.9],
    [0.4, 0.7, 0.1]])

    # 获取每行最大值的索引
    max_indices = torch.argmax(scores, dim=1, keepdim=True)
    print("最大值索引:", max_indices) # tensor([[1], [2], [1]])

    # 使用gather获取最大值
    max_values = torch.gather(scores, dim=1, index=max_indices)
    print("最大值:", max_values) # tensor([[0.8], [0.9], [0.7]])

    # 场景2: 根据标签获取对应的预测概率
    predictions = torch.tensor([[0.2, 0.3, 0.5],
    [0.1, 0.8, 0.1],
    [0.6, 0.2, 0.2]])
    labels = torch.tensor([2, 1, 0]) # 真实标签

    # 获取每个样本对应标签的预测概率
    label_probs = torch.gather(predictions, dim=1, index=labels.unsqueeze(1))
    print("标签概率:", label_probs) # tensor([[0.5], [0.8], [0.6]])

注意事项

  • 索引值必须在 [0, input.size(dim)) 范围内
  • 除了指定的dim维度外,input 和 index 的其他维度大小必须相同
  • 输出张量的形状与 index 张量相同
1…232425…66
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

659 posts
53 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4