<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Ada Lovelemon</title><description>Blog</description><link>https://adalovelemon.github.io/</link><language>en</language><item><title>officecli: 让 Claude Code 用 PPT 绘图</title><link>https://adalovelemon.github.io/posts/content/technotes/agents/officecli/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/agents/officecli/</guid><pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;天下苦绘图久矣！让 AI 帮助绘图曾经是个难题，但现在有了 &lt;strong&gt;OfficeCLI&lt;/strong&gt;，这个遥不可及的梦想终于成真了。无论是做 PPT 结构图、流程图，还是 Word 报告、Excel 表格，OfficeCLI 都能让你用代码轻松搞定。更棒的是，它还能一键注册为 Claude Code 的 MCP 服务器，让 AI 直接调用 OfficeCLI 来生成文档，完全不需要手动操作。&lt;/p&gt;
&lt;h2&gt;一、安装&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;PowerShell 一键安装&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 在PowerShell中执行
irm https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/main/install.ps1 | iex

# 验证安装
officecli --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后，就可以在命令行中使用 &lt;code&gt;officecli&lt;/code&gt; 这个强大的工具了。接下来，我们看看它是如何与 Claude Code 无缝集成的，以及一些基础的 CLI 指令示例。&lt;/p&gt;
&lt;h2&gt;二、注册到 Claude Code&lt;/h2&gt;
&lt;p&gt;OfficeCLI 提供了一个非常智能的功能：一键注册为 Claude Code 的 MCP 服务器。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 一键注册 MCP 服务器
officecli mcp claude
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行后，Claude Code 会自动获得操作 Office 文档的能力。你在对话中让 Claude &quot;做个 PPT&quot;、&quot;整理成表格&quot;、&quot;导出报告&quot;，它就能直接调用 OfficeCLI 来完成。不需要手动配置 JSON。&lt;/p&gt;
&lt;h2&gt;三、基础 CLI 指令&lt;/h2&gt;
&lt;h3&gt;创建文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 创建一个测试PPT（默认宽屏 33.87cm × 19.05cm）
officecli create test.pptx --type pptx
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;添加幻灯片&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 添加一张带标题的幻灯片，深色背景
officecli add test.pptx / --type slide --prop title=&quot;测试绘图&quot; --prop background=2C3E50
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;添加图形元素&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 在第二页添加一个带文字的矩形
officecli add test.pptx &quot;/slide[2]&quot; --type shape --prop text=&quot;Hello OfficeCLI&quot; --prop x=2cm --prop y=5cm --prop width=10cm --prop height=3cm --prop size=24pt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;支持的各种 shape 类型：&lt;code&gt;rect&lt;/code&gt;（矩形）、&lt;code&gt;roundRect&lt;/code&gt;（圆角矩形）、&lt;code&gt;ellipse&lt;/code&gt;（椭圆）、&lt;code&gt;triangle&lt;/code&gt;（三角）、&lt;code&gt;rightArrow&lt;/code&gt;（箭头）、&lt;code&gt;star5&lt;/code&gt;（五角星）等。&lt;/p&gt;
&lt;h3&gt;添加文本框&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;officecli add test.pptx &quot;/slide[2]&quot; --type textbox --prop text=&quot;这是文本框&quot; --prop x=2cm --prop y=8cm --prop width=10cm --prop height=2cm --prop size=14pt --prop bold=true --prop color=FF0000
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;添加连接线（箭头）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 直线箭头
officecli add test.pptx &quot;/slide[2]&quot; --type connector --prop shape=straight --prop x=5cm --prop y=3cm --prop width=0cm --prop height=3cm --prop color=333333 --prop tailEnd=triangle --prop lineWidth=2pt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实时预览&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 启动预览服务器（会自动打开浏览器）
officecli watch test.pptx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个功能非常实用 —— 你每次执行 &lt;code&gt;add&lt;/code&gt; / &lt;code&gt;set&lt;/code&gt; 命令后，浏览器页面会自动刷新，实时看到效果。&lt;/p&gt;
&lt;h3&gt;查看文档结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 查看所有幻灯片
officecli query test.pptx slide

# 查看所有图形
officecli query test.pptx shape

# 查看所有连接线
officecli query test.pptx connector
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;批量操作&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 通过JSON文件批量执行命令
officecli batch test.pptx --input commands.json
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、智能体调用经验&lt;/h2&gt;
&lt;p&gt;在 Claude Code 尝试使用 officecli 做了个图后，我让他做了个经验总结，便于后续智能体调用时省点 token。&lt;/p&gt;
&lt;h3&gt;核心流程&lt;/h3&gt;
&lt;p&gt;OfficeCLI 操作 PPT 的本质是三步曲：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;创建文件 → 添加元素 → 设置属性
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每添加一个元素，&lt;code&gt;officecli&lt;/code&gt; 会返回一个路径，例如 &lt;code&gt;/slide[1]/shape[@id=10000]&lt;/code&gt;，后续修改通过这个路径定位。&lt;/p&gt;
&lt;h3&gt;Skill 模板：用 OfficeCLI 画论文结构图&lt;/h3&gt;
&lt;p&gt;如果你要让 AI 用 OfficeCLI 画 PPT 结构图，直接把下面的指令框架给 AI：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;背景：你要用 officecli 生成一个 PPT 结构图
尺寸：PPT 宽 33.87cm，高 19.05cm（标准宽屏）

步骤：
1. officecli create output.pptx --type pptx    创建空白文件
2. officecli add output.pptx &quot;&quot; --type slide --prop layout=&quot;Blank&quot; --prop background=FFFFFF    加一页空白

3. 对每个图形元素使用 batch 模式（批量操作），格式为 JSON：
   {&quot;op&quot;:&quot;add&quot;,&quot;path&quot;:&quot;/slide[1]&quot;,&quot;type&quot;:&quot;shape&quot;,&quot;props&quot;:{...}}
   
   常用 props 属性速查：
   ├── 位置大小: x, y, width, height（单位cm）
   ├── 几何形状: geometry = rect | roundRect | ellipse | triangle
   ├── 颜色填充: fill = RRGGBB（hex色值）
   ├── 文本内容: text, size, color, bold, font.ea（中文字体）
   ├── 对齐方式: align = left|center|right, valign = top|middle|bottom
   └── 边距边框: margin = 0.3cm, line = &quot;RRGGBB:宽度pt&quot;

4. 添加连接线（箭头）：
   {&quot;op&quot;:&quot;add&quot;,&quot;path&quot;:&quot;/slide[1]&quot;,&quot;type&quot;:&quot;connector&quot;,&quot;props&quot;:{
     &quot;shape&quot;:&quot;straight&quot;, &quot;x&quot;:&quot;起点cm&quot;, &quot;y&quot;:&quot;起点cm&quot;,
     &quot;width&quot;:&quot;0cm&quot;, &quot;height&quot;:&quot;长度cm&quot;,
     &quot;color&quot;:&quot;RRGGBB&quot;, &quot;lineWidth&quot;:&quot;2pt&quot;, &quot;tailEnd&quot;:&quot;triangle&quot;
   }}
   注意：连接线是靠 start(x,y) → end(x+width, y+height) 定位的
   向下箭头：width=0, height=正数
   向右箭头：width=正数, height=0

5. 编辑完用 officecli save output.pptx 存盘
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;踩坑记录&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;解决方案&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;中文乱码&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;必须设置 &lt;code&gt;--prop font.ea=&quot;Microsoft YaHei&quot;&lt;/code&gt; 指定中文字体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;路径错误&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Git Bash 下路径中的 &lt;code&gt;/&lt;/code&gt; 会被转义，用 &lt;code&gt;&quot;&quot;&lt;/code&gt; 空字符串代替根路径&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;connector 方向不对&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;connector 用偏移量定位：width=0 为垂直方向，height 正值=向下，负值=向上；height=0 为水平方向，width 正值=向右，负值=向左&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;batch JSON 字段名&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;用 &lt;code&gt;op&lt;/code&gt; 代替 &lt;code&gt;add&lt;/code&gt;，用 &lt;code&gt;props&lt;/code&gt; 代替 &lt;code&gt;--prop&lt;/code&gt;，用 &lt;code&gt;type&lt;/code&gt; 代替 &lt;code&gt;--type&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;预览不刷新&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;检查 &lt;code&gt;officecli watch&lt;/code&gt; 是否仍在运行，重开即可&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Batch JSON 的完整字段格式&lt;/h3&gt;
&lt;p&gt;让智能体先生成 Batch JSON，再用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;officecli batch test.pptx --input commands.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以一键修改 PPT，好处是方便在此前调整的基础上修改 PPT 结构。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;op&quot;: &quot;add | set | remove | get | query&quot;,
  &quot;path&quot;: &quot;/slide[N]&quot;,
  &quot;type&quot;: &quot;shape | textbox | connector | slide&quot;,
  &quot;props&quot;: {
    &quot;x&quot;: &quot;位置cm&quot;,
    &quot;y&quot;: &quot;位置cm&quot;,
    &quot;width&quot;: &quot;宽度cm&quot;,
    &quot;height&quot;: &quot;高度cm&quot;,
    &quot;fill&quot;: &quot;RRGGBB&quot;,
    &quot;geometry&quot;: &quot;roundRect&quot;,
    &quot;text&quot;: &quot;内容&quot;,
    &quot;size&quot;: &quot;字号pt&quot;,
    &quot;color&quot;: &quot;RRGGBB&quot;,
    &quot;bold&quot;: true,
    &quot;font.ea&quot;: &quot;Microsoft YaHei&quot;,
    &quot;align&quot;: &quot;center&quot;,
    &quot;valign&quot;: &quot;middle&quot;,
    &quot;margin&quot;: &quot;0.3cm&quot;,
    &quot;line&quot;: &quot;RRGGBB:线宽pt&quot;,
    &quot;lineWidth&quot;: &quot;2pt&quot;,
    &quot;tailEnd&quot;: &quot;triangle&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;配色方案推荐&lt;/h3&gt;
&lt;p&gt;画技术架构图/论文结构图时，推荐这套蓝色系配色：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;深蓝（主标题）: 1A3A5C
章1（最深的蓝）: 2E86C1
章2: 3498DB
章3: 5DADE2
章4: 85C1E9
章5（最浅的蓝）: AED6F1
背景浅蓝: EBF5FB
强调色（橙色）: F39C12
强调色（红色）: E74C3C
正文深色: 2C3E50
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、视觉反馈：AI 的&quot;眼睛&quot;&lt;/h2&gt;
&lt;p&gt;做 PPT 最头疼的一点是：&lt;strong&gt;AI 自己看不到做出来的图&lt;/strong&gt;。没有视觉反馈，布局重叠、字体过小、颜色失衡这些问题，AI 完全不知道。怎么解决？&lt;/p&gt;
&lt;h3&gt;两种视觉反馈通道&lt;/h3&gt;
&lt;p&gt;根据 AI 模型的能力不同，有两种视觉反馈方案：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方案&lt;/th&gt;
&lt;th&gt;适用模型&lt;/th&gt;
&lt;th&gt;原理&lt;/th&gt;
&lt;th&gt;精度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTML 结构化分析&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;纯文本模型（如 DeepSeek）&lt;/td&gt;
&lt;td&gt;导出 HTML，读取 DOM 元素坐标来检测布局&lt;/td&gt;
&lt;td&gt;像素级精确&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;截图视觉识别&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;多模态模型（如 Claude、GPT-4o）&lt;/td&gt;
&lt;td&gt;直接看截图，像人一样判断&lt;/td&gt;
&lt;td&gt;直观但定性&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;方案一：HTML 结构化分析（纯文本模型）&lt;/h3&gt;
&lt;p&gt;纯文本模型无法&quot;看&quot;图片，但可以通过 &lt;code&gt;officecli view&lt;/code&gt; 导出 HTML，然后&lt;strong&gt;读取 DOM 元素的坐标数据&lt;/strong&gt;来理解布局。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 导出 HTML 预览
officecli view paper.pptx html &amp;gt; preview.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML 中每个元素都有精确的 CSS 坐标：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 从 HTML 中可以读到每个元素的 left, top, width, height --&amp;gt;
&amp;lt;div class=&quot;shape&quot; style=&quot;left:28.35pt;top:102.05pt;width:164.41pt;height:90.71pt;&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过这些坐标，纯文本模型可以&lt;strong&gt;像读表格一样&quot;看&quot;布局&lt;/strong&gt;，自动检测三类常见问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 重叠检测&lt;/strong&gt; — 计算两个元素的包围盒是否有交集&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;元素A底部 = topA + heightA = 102.05 + 90.71 = 192.76pt
元素B顶部 = topB = 212.6pt
间距 = 212.6 - 192.76 = 19.84pt (0.7cm) ✓ 无重叠
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 间距均匀性&lt;/strong&gt; — 检查所有同类元素的位置是否对齐&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;章1内容框 top=102.05pt, left=28.35pt
章2内容框 top=102.05pt, left=212.6pt  ← top 一致，水平对齐 ✓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 文字适配&lt;/strong&gt; — 计算文本高度是否超出容器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;内容框文本区高度 = 90.71pt - 2×9.92pt(边距) = 70.87pt
4行文字需要 = 4 × 11pt × 1.4(行距) = 61.6pt
剩余 = 9.27pt ✓ 文字能完整显示
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方案二：截图视觉识别（多模态模型）&lt;/h3&gt;
&lt;p&gt;多模态模型可以用 &lt;code&gt;officecli view&lt;/code&gt; 的截图模式，直接&quot;看到&quot; PPT 效果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 导出截图
officecli view paper.pptx screenshot --slide 1 -o slide1.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模型可以直接判断：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;颜色是否协调&lt;/li&gt;
&lt;li&gt;字体大小是否合适&lt;/li&gt;
&lt;li&gt;整体视觉平衡感&lt;/li&gt;
&lt;li&gt;装饰元素的美观度&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;迭代工作流&lt;/h3&gt;
&lt;p&gt;无论哪种方案，核心都是&lt;strong&gt;生成 → 反馈 → 调整&lt;/strong&gt;的闭环：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;创建 PPT → 导出预览(HTML/截图) → AI分析反馈 → 调整 JSON → 重新生成
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以提示让模型在每轮迭代都关注不同维度：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;第 1 轮&lt;/strong&gt;：整体布局、元素是否存在&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 2 轮&lt;/strong&gt;：间距、对齐、颜色一致性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第 3 轮&lt;/strong&gt;：细节微调、装饰元素、视觉平衡&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;关键技术指标&lt;/h3&gt;
&lt;p&gt;分析 HTML 布局时，重点关注这些 CSS 属性：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;检查要点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;top&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元素顶部位置&lt;/td&gt;
&lt;td&gt;同类元素是否一致&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;left&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元素左侧位置&lt;/td&gt;
&lt;td&gt;水平对齐情况&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;width&lt;/code&gt;/&lt;code&gt;height&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元素尺寸&lt;/td&gt;
&lt;td&gt;内容是否适配&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;padding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;内边距&lt;/td&gt;
&lt;td&gt;文字是否被裁剪&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;line-height&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;行高&lt;/td&gt;
&lt;td&gt;行数 × 行高 ≤ 内容区高度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;border-radius&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;圆角&lt;/td&gt;
&lt;td&gt;与元素尺寸比例是否协调&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这个&quot;结构化视觉反馈&quot;的方法，不仅适用于 PPT，对 Word 文档排版、Excel 图表布局同样有效。&lt;strong&gt;没有视觉模态的 AI，一样能做出视觉上合格的文档&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;OfficeCLI 最大的价值在于，&lt;strong&gt;它把 Office 文档操作变成了可编程的、AI 可调用的 API&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;以前 AI 做 PPT 的方案，是生成 Python 脚本 → 调 python-pptx 库 → 慢慢调试。现在，officecli batch 一个 JSON 搞定，还能实时预览。&lt;/p&gt;
&lt;p&gt;搭配 Claude Code 的 MCP 注册功能，直接跟 AI 说&quot;把这个分析结果做成 PPT&quot;，剩下的交给 OfficeCLI。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;相关链接：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/iOfficeAI/OfficeCLI&quot;&gt;OfficeCLI GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://python-pptx.readthedocs.io/&quot;&gt;python-pptx&lt;/a&gt;（备选方案，更灵活但更繁琐）&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>从零开始学 AI - 第三章：文本翻译、文本理解与文本生成</title><link>https://adalovelemon.github.io/posts/content/coursenotes/learn-ai-from-scratch/chapter3/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/learn-ai-from-scratch/chapter3/</guid><pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;export const images = import.meta.glob(&quot;./assets/*.{png,jpg,jpeg,webp,gif,avif,svg}&quot;, {
eager: true,
import: &quot;default&quot;,
})&lt;/p&gt;
&lt;h2&gt;语言文本的数据格式&lt;/h2&gt;
&lt;h3&gt;回顾：图像的数据格式&lt;/h3&gt;
&lt;p&gt;在前两章中，我们主要探讨了一些与图像相关的机器学习任务。图像数据通常是一个三维张量 (高度、宽度、通道)。给定一张图像 $x$，可以形式化表示为
$$
I \in \mathbb{R}^{H \times W \times C}
$$
其中 $H$ 是图像的高度，$W$ 是宽度，$C$ 是通道数。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;从矩阵堆叠的角度来看，图像就是若干张形状相同的矩阵堆叠在一起
$$
I = \begin{bmatrix}I_1 &amp;amp; I_2 &amp;amp; \cdots &amp;amp; I_C\end{bmatrix}
$$
其中 $I_i \in \mathbb{R}^{H \times W}$ 是第 $i$ 个通道的二维矩阵。每个二维矩阵都体现了各个像素位置在它这个通道上的信号强度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从向量做二维排列的角度来看，图像也可以看成是 $H \times W$ 个像素点，每个像素点是一个 $C$ 维的向量，它们沿着二维网格分布，聚集在一起
$$
I = \begin{bmatrix}p_{1,1} &amp;amp; p_{1,2} &amp;amp; \cdots &amp;amp; p_{1,W} \ p_{2,1} &amp;amp; p_{2,2} &amp;amp; \cdots &amp;amp; p_{2,W} \ \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \ p_{H,1} &amp;amp; p_{H,2} &amp;amp; \cdots &amp;amp; p_{H,W}\end{bmatrix}
$$
其中 $p_{i,j} \in \mathbb{R}^C$ 是位于第 $i$ 行、第 $j$ 列的像素点的向量表示，包含了该像素在所有通道上的信号强度。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;无论从哪个角度观察，图像数据都具有&lt;strong&gt;明确的空间结构&lt;/strong&gt;，这是其最显著的特征。&lt;/p&gt;
&lt;p&gt;在实际应用中最多的图像数据格式是三通道 RGB 格式，其中 R 表示红色通道，G 表示绿色通道，B 表示蓝色通道。每个通道的像素值通常在 0~255 之间，表示该像素在该颜色上的强度。&lt;/p&gt;
&lt;h3&gt;文本的数据格式&lt;/h3&gt;
&lt;p&gt;文本，本质上就是一段文字，在计算机中以字符串的形式存在。与图像不同，文本没有显然的空间结构——文本长度并不固定，而且每个字符也只有前一个字符和后一个字符两个邻居，而不是像图像像素点那样最多可以有八个邻居。&lt;/p&gt;
&lt;p&gt;正如语言学中为了便于研究，产生了“字母”、“词根”、“词”、“句子”等不同颗粒度的概念一样，在自然语言处理 (NLP) 中，为了便于研究和计算，文本也可以有不同颗粒度的划分方式。为了便于阐述，不论哪种颗粒度的划分形式，都把文本看作是最基本单元 &lt;strong&gt;Token (中文叫 &quot;词元&quot;)&lt;/strong&gt; 的序列。也即数学上，文本被表示成一个&lt;strong&gt;一维 Token 序列&lt;/strong&gt;
$$
T = [c_1, c_2, \cdots, c_N] \in \mathcal{C}^N
$$
其中 $N$ 是文本的长度，$\mathcal{C}$ 是文本的词汇空间，$c_i$ 是文本中的第 $i$ 个 Token。需要注意的是，由于不同任务的需要， Token 的颗粒度是可以不同的。例如，给定一句话 &lt;code&gt;T = &quot;I love AI!&quot;&lt;/code&gt;，我们可以按以下不同的方式划分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T = [&quot;I&quot;, &quot; &quot;, &quot;l&quot;, &quot;o&quot;, &quot;v&quot;, &quot;e&quot;, &quot; &quot;, &quot;A&quot;, &quot;I&quot;, &quot;!&quot;]  # 字符级划分
T = [&quot;I&quot;, &quot;love&quot;, &quot;AI!&quot;]  # 词级划分
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但不论是何种划分方式，文本数据都具有&lt;strong&gt;一维序列结构&lt;/strong&gt;，这是其本质属性。仿照图像的处理方式，让计算机分别对每个文本组成单元——也就是例如字符或者单词的 &lt;strong&gt;Token&lt;/strong&gt;——进行处理，是不是就能完成文本理解和生成的任务了呢？&lt;/p&gt;
&lt;p&gt;事实上，这很困难。这是因为直接对字符组合（单词或者字符）进行处理，会面临一个无法计算的问题——&lt;strong&gt;Token 本身是不可计算的&lt;/strong&gt;，它不像图像那样是数值的组合。我们无法直接对 Token 进行加减乘除或者内积等数学运算。&lt;/p&gt;
&lt;p&gt;举个例子，你不能拿 &lt;code&gt;&quot;l&quot;&lt;/code&gt; 加上 &lt;code&gt;&quot;o&quot;&lt;/code&gt; 期望得到 &lt;code&gt;&quot;v&quot;&lt;/code&gt;，也不能计算 &lt;code&gt;&quot;I&quot;&lt;/code&gt; 与 &lt;code&gt;&quot;AI&quot;&lt;/code&gt; 之间的欧氏距离。缺乏了数值计算，机器学习的方法也就无法直接应用了。&lt;/p&gt;
&lt;p&gt;此外，文本作为字符串本身，并非是直接切分好的 Token 序列。这里是为了阐述方便，直接默认文本是切分好了的。实际上，切分的操作需要基于一定的规则来完成，这是下面介绍的分词器的工作。&lt;/p&gt;
&lt;h3&gt;分词 (Tokenization) 与词嵌入 (Word Embedding)&lt;/h3&gt;
&lt;p&gt;既然 Token 本身不可计算，那么要让计算机处理文本，首先就必须解决可计算的核心问题，即&lt;strong&gt;如何将离散的 Token 序列转化为可供数学运算的数值形式？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;参考图像分类的最后两步，特征向量经过线性层得到了每个类别的置信分数，然后选置信分数高的为预测的类别，类别都是用离散的整数表示，但是经过字典的映射可以变为类别本身的字符串。这一步可以看作是&lt;strong&gt;从特征向量到整数再到字符串的变换过程&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那反其道而行之，倒着设计文本数据的处理流程：&lt;strong&gt;首先将 Token 转化为整数 ID，然后再通过一个可学习的查找表将整数 ID 转化为稠密的向量表示&lt;/strong&gt;。事实上，现代的自然语言处理都是这么干的。这个过程分为两个步骤：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分词 (Tokenization)&lt;/strong&gt;：将文本&lt;strong&gt;拆分&lt;/strong&gt;为 Tokens。Token 的颗粒度由分词算法决定，可能等于一个完整的词，也可能是一个词的片段 (子词)，甚至是一个单独的字符。目前主流的子词级分词算法包括 Byte Pair Encoding (BPE，字节对编码) 及其变体 Byte-level BPE (BBPE，字节级字节对编码)。它们通过统计语料中字符或字节的共现频率，迭代地合并高频相邻对，从而在词表大小和序列长度之间取得平衡。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;词嵌入 (Word Embedding)&lt;/strong&gt;：将每个 Token ID 映射为一个高维的稠密向量。这一映射通过一个可训练的嵌入矩阵实现——Token ID 作为索引，从矩阵中取出对应的向量行。在训练过程中，这些向量会通过反向传播不断调整，最终使得语义相近的 Token 在向量空间中彼此靠近。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过分词和词嵌入，我们就完成了文本数据从&lt;strong&gt;离散的符号序列&lt;/strong&gt;到&lt;strong&gt;连续的数值张量&lt;/strong&gt;的转变。最终得到的文本数据格式是一个形状为 $N \times d$ 的二维张量
$$
x = \text{Embedding}\left(\text{Tokenization}(T)\right) \in \mathbb{R}^{N \times d}
$$
其中 $N$ 是文本的长度，$d$ 是嵌入向量的维度。这就成为了神经网络模型可以直接处理的输入格式。&lt;/p&gt;
&lt;p&gt;在 NLP 术语描述中，Token 的本义是文本的最小组成单元，但是为了便于表达，有的时候 Token 也可以泛指经过分词操作之后的 Token ID 或者 Token 的嵌入向量。&lt;/p&gt;
&lt;h2&gt;分词器 (Tokenizer) 的实现——以 BPE 为例&lt;/h2&gt;
&lt;p&gt;Byte Pair Encoding (BPE) 的核心思想可以概括为：&lt;strong&gt;从字符级词表出发，通过反复合并语料中出现频率最高的相邻符号对，逐步构造出更大的子词单元&lt;/strong&gt;。定义词表 (Vocabulary) 是模型所“认识”的全部 Token 构成的有限集合，本质是一张从 Token 到整数 ID 的映射表。在 BPE 中，词表 $\mathcal{V}$ 分为子词词表 $\mathcal{V}&lt;em&gt;{\text{subword}}$ 和预定义的特殊标记（如 &lt;code&gt;&amp;lt;PAD&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;UNK&amp;gt;&lt;/code&gt;）词表 $\mathcal{S}$ 两个部分构成
$$
\mathcal{V} = \mathcal{V}&lt;/em&gt;{\text{subword}} \cup \mathcal{S}
$$&lt;/p&gt;
&lt;h3&gt;BPE 的算法原理&lt;/h3&gt;
&lt;p&gt;子词词表 $\mathcal{V}_{\text{subword}}$ 是通过以下迭代过程构建的：&lt;/p&gt;
&lt;p&gt;假设构建过程中，当前子词词表为 $\mathcal{V}_{\text{subword}}$，语料中所有相邻符号对构成的集合为 $\mathcal{P}$，定义任意相邻对 $(a, b) \in \mathcal{P}$ 在语料中的共现频次为 $\text{count}(a, b)$。BPE 的每一轮迭代执行以下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;找出频次最高的相邻对：
$$
(a^&lt;em&gt;, b^&lt;/em&gt;) = \mathop{\arg\max}_{(a, b) \in \mathcal{P}} \text{count}(a, b)
$$&lt;/li&gt;
&lt;li&gt;将 $(a^&lt;em&gt;, b^&lt;/em&gt;)$ 合并为一个新符号 $a^&lt;em&gt;b^&lt;/em&gt;$，并加入词表 $\mathcal{V}_{\text{subword}}$。&lt;/li&gt;
&lt;li&gt;在语料中将所有相邻出现的 $a^&lt;em&gt;$ 和 $b^&lt;/em&gt;$ 替换为 $a^&lt;em&gt;b^&lt;/em&gt;$，更新 $\mathcal{P}$ 中的频次统计。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;重复上述过程 $K$ 次（$K$ 为目标词表大小减去初始字符数），最终得到一个包含子词单元的词表及对应的合并规则序列。&lt;/p&gt;
&lt;h3&gt;特殊标记的设计&lt;/h3&gt;
&lt;p&gt;除了从语料中学习到的子词单元，分词器通常还需要预先定义一组&lt;strong&gt;特殊标记&lt;/strong&gt; (Special Tokens)。这些标记并不来自语料的统计合并，而是服务于模型训练和推理过程中的工程需求。常见的特殊标记包括如下这些：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;标记符号&lt;/th&gt;
&lt;th&gt;全称与含义&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;PAD&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Padding Token&lt;/td&gt;
&lt;td&gt;填充标记。由于同一批次内的文本序列长度不一，需将较短序列填充至统一长度 $L$，填充位置用 &lt;code&gt;&amp;lt;PAD&amp;gt;&lt;/code&gt; 占据，并在后续注意力计算中通过掩码忽略。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;UNK&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unknown Token&lt;/td&gt;
&lt;td&gt;未知词标记。当分词器遇到词表中不存在的字符或子词组合时，统一映射为 &lt;code&gt;&amp;lt;UNK&amp;gt;&lt;/code&gt;，防止因集外词导致的索引越界。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;BOS&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Beginning of Sequence&lt;/td&gt;
&lt;td&gt;序列起始标记。置于输入序列的首位，提示模型序列的开始。在生成任务（如文本续写、翻译解码）中常作为第一个输入 token。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;End of Sequence&lt;/td&gt;
&lt;td&gt;序列结束标记。置于序列末尾，用于指示生成过程的终止条件，模型在预测到 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; 时停止输出。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;SEP&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Separator Token&lt;/td&gt;
&lt;td&gt;分隔标记。常用于需要区分多个句子的任务（如 BERT 的下一句预测），置于两个句子之间。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;CLS&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Classification Token&lt;/td&gt;
&lt;td&gt;分类标记。通常置于序列首位，其对应的最终隐状态被用作整段文本的聚合表示，输入分类层。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;分词器在编码阶段会为每个特殊标记预留固定的整数 ID（通常从 0 开始连续分配），并在解码时将其映射回对应的标记字符串或忽略（例如 &lt;code&gt;&amp;lt;PAD&amp;gt;&lt;/code&gt; 不出现在最终输出文本中）。&lt;/p&gt;
&lt;p&gt;特殊标记的 ID 分配顺序虽无硬性规定，但常见实践是将其置于词表最前端，以保证：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;索引稳定：不同训练轮次或不同语料下，特殊标记的 ID 保持不变，方便模型权重的迁移与复用。&lt;/li&gt;
&lt;li&gt;掩码便利：&lt;code&gt;&amp;lt;PAD&amp;gt;&lt;/code&gt; 的 ID 常设为 0，使得注意力掩码可通过判断输入是否为 0 快速生成。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下代码实现中，我们显式地将 &lt;code&gt;&amp;lt;UNK&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;PAD&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;BOS&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; 作为基础特殊标记加入词表，并在后续的 &lt;code&gt;encode&lt;/code&gt; 和 &lt;code&gt;decode&lt;/code&gt; 方法中正确处理它们。&lt;/p&gt;
&lt;h3&gt;BPE 分词器的代码实现&lt;/h3&gt;
&lt;p&gt;首先用代码实现一个简单的 BPE 分词器，包含训练和编码解码功能：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import re
from collections import defaultdict, Counter
from typing import List, Tuple, Dict, Optional
from tqdm import tqdm

class BPETokenizer:
    &quot;&quot;&quot;Byte Pair Encoding (BPE) 分词器&quot;&quot;&quot;
    
    def __init__(self, vocab_size: int = 1000, unk_token: str = &quot;&amp;lt;UNK&amp;gt;&quot;):
        &quot;&quot;&quot;
        Args:
            vocab_size: 目标词表大小（包含所有基础字符和特殊标记）
            unk_token: 未知词标记
        &quot;&quot;&quot;
        self.vocab_size = vocab_size
        self.unk_token = unk_token
        self.merges: Dict[Tuple[str, str], str] = {}  # 合并规则：(a, b) -&amp;gt; ab
        self.vocab: Dict[str, int] = {}               # 词表：token -&amp;gt; id
        
    def _prepare_corpus(self, texts: List[str]) -&amp;gt; Dict[Tuple[str, ...], int]:
        &quot;&quot;&quot;将语料中的每个单词拆分为字符组元组，并利用 Counter 提前聚合词频，极大节省内存和时间&quot;&quot;&quot;
        word_freqs = Counter()
        for text in texts:
            words = re.findall(r&quot;\w+|[^\w\s]&quot;, text)
            for word in words:
                word_freqs[word] += 1
                
        # 转换为带权重字典: {(&apos;l&apos;, &apos;o&apos;, &apos;v&apos;, &apos;e&apos;, &apos;&amp;lt;/w&amp;gt;&apos;): 150}
        corpus = {}
        for word, freq in word_freqs.items():
            chars = tuple(list(word) + [&quot;&amp;lt;/w&amp;gt;&quot;])
            corpus[chars] = freq
            
        return corpus
    
    def _get_stats(self, corpus: Dict[Tuple[str, ...], int]) -&amp;gt; Dict[Tuple[str, str], int]:
        &quot;&quot;&quot;统计字典中相邻符号对的频次 (需乘上该词的全局权重频次)&quot;&quot;&quot;
        pairs = defaultdict(int)
        for word_tuple, freq in corpus.items():
            for i in range(len(word_tuple) - 1):
                pairs[(word_tuple[i], word_tuple[i + 1])] += freq
        return pairs
    
    def _merge_pair(self, corpus: Dict[Tuple[str, ...], int], pair: Tuple[str, str]) -&amp;gt; Dict[Tuple[str, ...], int]:
        &quot;&quot;&quot;在去重汇总后的字典词表上合并对，而不是重新创建一千万个列表&quot;&quot;&quot;
        a, b = pair
        new_corpus = {}
        for word_tuple, freq in corpus.items():
            # 这里如果单词里没包含待合并的字符，直接放过去
            if a not in word_tuple or b not in word_tuple:
                new_corpus[word_tuple] = freq
                continue
                
            new_word = []
            i = 0
            while i &amp;lt; len(word_tuple):
                if i &amp;lt; len(word_tuple) - 1 and word_tuple[i] == a and word_tuple[i + 1] == b:
                    new_word.append(a + b)  # 合并
                    i += 2
                else:
                    new_word.append(word_tuple[i])
                    i += 1
            new_corpus[tuple(new_word)] = freq
        return new_corpus
    
    def train(self, texts: List[str], verbose: bool = False):
        &quot;&quot;&quot;
        在给定文本上训练 BPE 分词器
        &quot;&quot;&quot;
        # 1. 准备语料：每个单词拆分为字符序列
        if verbose:
            print(&quot;正在将语料拆分为字符序列并全局聚合词频...&quot;)
            iterator = tqdm(texts, desc=&quot;预处理与聚合语料&quot;)
        else:
            iterator = texts

        corpus = self._prepare_corpus(iterator)
        if verbose:
            print(f&quot;数据聚合完毕！去重后的独立词元数量: {len(corpus)}&quot;)
        
        # 2. 初始化词表：所有基础字符（含 &amp;lt;/w&amp;gt;）
        base_vocab = set()
        for word_tuple in corpus.keys():
            base_vocab.update(word_tuple)
        
        # 将特殊标记排在词表最前面（索引 0, 1, 2, 3...）
        special_tokens = [self.unk_token, &quot;&amp;lt;PAD&amp;gt;&quot;, &quot;&amp;lt;BOS&amp;gt;&quot;, &quot;&amp;lt;EOS&amp;gt;&quot;]
        final_vocab = list(special_tokens)
        
        # 加上语料中的基础字符（去重后排序）
        base_chars = sorted([char for char in base_vocab if char not in special_tokens])
        final_vocab.extend(base_chars)
        
        self.vocab = {tok: i for i, tok in enumerate(final_vocab)}
        if verbose:
            print(f&quot;初始词表大小: {len(self.vocab)}&quot;)
        
        # 3. 迭代合并
        num_merges = self.vocab_size - len(self.vocab)
        if num_merges &amp;lt;= 0:
            print(f&quot;\n[Warning] 设定的 vocab_size ({self.vocab_size}) 小于或等于抽取出的基础字符表大小 ({len(self.vocab)})。&quot;)
            print(&quot;[Warning] 由于中文字符丰富以及特殊符号极多，基础字符数轻易破万。建议指定更大的 vocab_size (例如 20000 或 50000)！当前将跳过字符合并操作。&quot;)
            num_merges = 0
        
        if num_merges &amp;gt; 0:
            iterator = tqdm(range(num_merges), desc=&quot;训练 BPE 合并规则&quot;) if verbose else range(num_merges)
            for i in iterator:
                pairs = self._get_stats(corpus)
                if not pairs:
                    break
                best_pair = max(pairs, key=pairs.get)  # 频次最高的相邻对
                corpus = self._merge_pair(corpus, best_pair)
                
                # 记录合并规则和更新词表
                new_token = best_pair[0] + best_pair[1]
                self.merges[best_pair] = new_token
                self.vocab[new_token] = len(self.vocab)
                
                if verbose:
                    # 使用 tqdm 时，通过 set_postfix 动态更新当前进度信息，避免原先 print 导致的一直刷屏
                    if hasattr(iterator, &apos;set_postfix&apos;):
                        iterator.set_postfix({&apos;merging&apos;: f&quot;{best_pair[0]}+{best_pair[1]}&quot;, &apos;freq&apos;: pairs[best_pair]})
                    elif (i + 1) % 100 == 0:
                        print(f&quot;合并 {i + 1}/{num_merges}: {best_pair} -&amp;gt; {new_token}, 频次: {pairs[best_pair]}&quot;)
        
        if verbose:
            print(f&quot;训练完成，最终词表大小: {len(self.vocab)}&quot;)
    
    def tokenize(self, text: str) -&amp;gt; List[str]:
        &quot;&quot;&quot;将文本分词为 token 序列&quot;&quot;&quot;
        # 预处理：按单词拆分并转为字符序列（加 &amp;lt;/w&amp;gt;）
        words = re.findall(r&quot;\w+|[^\w\s]&quot;, text)
        tokens = []
        for word in words:
            chars = list(word) + [&quot;&amp;lt;/w&amp;gt;&quot;]
            # 反复应用合并规则，直到无法再合并
            while len(chars) &amp;gt; 1:
                # 找到当前序列中在合并规则里优先级最高的相邻对
                # 规则按照训练时的顺序（即频次高低）应用
                min_index = float(&quot;inf&quot;)
                target_pair = None
                for pair in self.merges:
                    for i in range(len(chars) - 1):
                        if chars[i] == pair[0] and chars[i + 1] == pair[1]:
                            # 因为规则是按频次高低顺序存储的（Python 3.7+ dict 保持插入顺序）
                            # 所以第一次遇到的就是最高优先级的
                            if i &amp;lt; min_index:
                                min_index = i
                                target_pair = pair
                if target_pair is None:
                    break
                # 合并找到的 pair
                a, b = target_pair
                i = min_index
                chars = chars[:i] + [a + b] + chars[i + 2:]
            # 去掉单词结束符，但保留为独立的 token（常规 BPE 会将 &amp;lt;/w&amp;gt; 作为词边界标记）
            # 这里直接将 &amp;lt;/w&amp;gt; 保留在 token 中，例如 &quot;love&amp;lt;/w&amp;gt;&quot; 表示单词 love
            tokens.extend(chars)
        # 处理未知字符（简单起见，此实现中基础字符已覆盖所有输入，故省略 UNK 逻辑）
        return tokens
    
    def encode(self, text: str) -&amp;gt; List[int]:
        &quot;&quot;&quot;将文本编码为 token ID 序列&quot;&quot;&quot;
        tokens = self.tokenize(text)
        return [self.vocab.get(tok, self.vocab[self.unk_token]) for tok in tokens]
    
    def decode(self, ids: List[int]) -&amp;gt; str:
        &quot;&quot;&quot;将 token ID 序列解码为原始文本&quot;&quot;&quot;
        id_to_token = {v: k for k, v in self.vocab.items()}
        tokens = [id_to_token.get(i, self.unk_token) for i in ids]
        # 拼接 tokens，移除 &amp;lt;/w&amp;gt; 并替换为空格
        text = &quot;&quot;
        for tok in tokens:
            if tok.endswith(&quot;&amp;lt;/w&amp;gt;&quot;):
                text += tok[:-4] + &quot; &quot;
            else:
                text += tok
        return text.strip()
    
    def save(self, path: str):
        &quot;&quot;&quot;保存分词器（合并规则和词表）&quot;&quot;&quot;
        import json
        with open(path, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
            json.dump({
                &quot;vocab_size&quot;: self.vocab_size,
                &quot;unk_token&quot;: self.unk_token,
                &quot;merges&quot;: {f&quot;{k[0]} {k[1]}&quot;: v for k, v in self.merges.items()},
                &quot;vocab&quot;: self.vocab
            }, f, ensure_ascii=False, indent=2)
    
    def load(self, path: str):
        &quot;&quot;&quot;加载分词器&quot;&quot;&quot;
        import json
        with open(path, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
            data = json.load(f)
        self.vocab_size = data[&quot;vocab_size&quot;]
        self.unk_token = data[&quot;unk_token&quot;]
        self.merges = {tuple(k.split()): v for k, v in data[&quot;merges&quot;].items()}
        self.vocab = data[&quot;vocab&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了训练这个 BPE 分词器，我们需要准备一些文本数据。这里选择一下几个数据集&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/brightmart/nlp_chinese_corpus?tab=readme-ov-file#2%E6%96%B0%E9%97%BB%E8%AF%AD%E6%96%99json%E7%89%88news2016zh&quot;&gt;新闻语料json版(news2016zh)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/brightmart/nlp_chinese_corpus?tab=readme-ov-file#3%E7%99%BE%E7%A7%91%E7%B1%BB%E9%97%AE%E7%AD%94json%E7%89%88baike2018qa&quot;&gt;百科类问答json版(baike2018qa)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/brightmart/nlp_chinese_corpus?tab=readme-ov-file#4%E7%A4%BE%E5%8C%BA%E9%97%AE%E7%AD%94json%E7%89%88webtext2019zh-%E5%A4%A7%E8%A7%84%E6%A8%A1%E9%AB%98%E8%B4%A8%E9%87%8F%E6%95%B0%E6%8D%AE%E9%9B%86&quot;&gt;社区问答json版(webtext2019zh)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/brightmart/nlp_chinese_corpus?tab=readme-ov-file#5%E7%BF%BB%E8%AF%91%E8%AF%AD%E6%96%99translation2019zh&quot;&gt;翻译语料(translation2019zh)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;训练的接口如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 创建并训练 BPE 分词器
tokenizer = BPETokenizer(vocab_size=args.vocab_size, unk_token=&quot;&amp;lt;UNK&amp;gt;&quot;)
tokenizer.train(corpus, verbose=True)       # corpus 是一个文本列表，例如 [&quot;I love AI!&quot;, &quot;BPE is great!&quot;]

# 测试分词器
text = &quot;I love AI!&quot;
tokens = tokenizer.tokenize(text)           # 分词列表
ids = tokenizer.encode(text)                # 编码为 token ID 列表
decoded = tokenizer.decode(ids)             # 解码回原始文本（可能与输入略有不同，取决于分词规则）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体训练代码可以参考 &lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/tree/master/chapter-03-attention-translation/codes/train_tokenizer.py&quot;&gt;&lt;code&gt;codes/train_tokenizer.py&lt;/code&gt;&lt;/a&gt;，需要注意的是这个代码会一次性加载整个语料到内存中，如果计算机内存较小，&lt;strong&gt;可能会导致 OOM (内存溢出)&lt;/strong&gt;，从而导致死机。请谨慎运行，可以选择合适的语料规模。下面是训练一个小型 BPE 分词器的命令示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python train_tokenizer.py \
    --data_root &quot;data/corpus&quot; \
    --datasets translation2019zh baike2018qa news2016zh \
    --vocab_size 5000 \
    --num_train_samples 10000 \
    --save_path &quot;models/tokenizer/mixed_bpe_tokenizer.json&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;工业级分词器训练 (HuggingFace tokenizers)&lt;/h3&gt;
&lt;p&gt;在真实的工业场景下，为了更加内存友好且高效地训练分词器，通常借助底层为 Rust 编写的高性能库 &lt;code&gt;tokenizers&lt;/code&gt; 以实现对分词器的流式训练。这个库的优点包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;效率&lt;/strong&gt;：Rust 的性能优势使得分词器训练速度大幅提升，能够处理大规模语料。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内存友好&lt;/strong&gt;：支持从数据生成器流式加载文本，避免一次性将整个语料加载到内存中导致 OOM。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能丰富&lt;/strong&gt;：提供了多种预定义的分词模型（如 BPE、WordPiece、Unigram）和灵活的特殊标记配置，满足不同任务需求。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;易用性&lt;/strong&gt;：Python 接口简洁，易于集成到现有的 NLP 工作流中，且属于 Huggingface 生态系统的一部分，与 Transformers 库无缝兼容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://huggingface.co/&quot;&gt;Huggingface&lt;/a&gt; 是一个专注于自然语言处理与机器学习的开源平台和社区，以其核心库 Transformers 最为知名，提供了大量预训练模型（如BERT、GPT等）、数据集以及模型训练、评估和部署的工具，极大降低了开发者构建和应用先进NLP模型的门槛；同时，它还通过模型Hub和Spaces等功能，促进了全球开发者之间的模型共享与协作，已成为现代AI开发不可或缺的基础设施之一。&lt;/p&gt;
&lt;p&gt;安装 Huggingface 生态系统中主要的环境依赖可以使用如下 pip 命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install transformers datasets accelerate peft evaluate tokenizers sentencepiece
pip install -U huggingface_hub  # 安装 Huggingface Hub 客户端库（可选，用于模型和数据集的上传下载）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于在中国大陆地区无法直接访问 Huggingface 的官方资源，通常的做法是使用国内的镜像网站——&lt;a href=&quot;https://hf-mirror.com/&quot;&gt;HF Mirror&lt;/a&gt;，来访问 Huggingface 的模型和数据集资源。下载数据集或者模型前，可以预先设置环境变量&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Linux / MacOS
export HF_ENDPOINT=https://hf-mirror.com

# Windows PowerShell
$env:HF_ENDPOINT = &quot;https://hf-mirror.com&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下是 &lt;code&gt;tokenizers&lt;/code&gt; 库的接口实例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from tokenizers import Tokenizer, models, pre_tokenizers, decoders, trainers

# 1. 定义底层模型为 BPE
tokenizer = Tokenizer(models.BPE(unk_token=&quot;&amp;lt;UNK&amp;gt;&quot;))

# 2. 配置预分词器与解码器 (例如使用大模型常用的 ByteLevel 字节级切分)
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
tokenizer.decoder = decoders.ByteLevel()

# 3. 配置训练器 (设置工业级词表大小与需要的特殊标记)
trainer = trainers.BpeTrainer(
    vocab_size=50000,
    show_progress=True,
    special_tokens=[&quot;&amp;lt;PAD&amp;gt;&quot;, &quot;&amp;lt;UNK&amp;gt;&quot;, &quot;&amp;lt;BOS&amp;gt;&quot;, &quot;&amp;lt;EOS&amp;gt;&quot;, &quot;&amp;lt;SEP&amp;gt;&quot;, &quot;&amp;lt;CLS&amp;gt;&quot;]
)

# 4. 极为关键的一步：从数据生成器流式(Stream)训练
# 这样能够源源不断地从硬盘加载文本进入 Rust 引擎，避免 OOM (内存溢出)
tokenizer.train_from_iterator(batch_iterator(train_ds, batch_size=20000), trainer=trainer)

# 5. 训练结束，保存词表
tokenizer.save(&quot;industrial_bpe_tokenizer.json&quot;)

# 真实的使用测试
encoded = tokenizer.encode(&quot;AI is changing the world.&quot;)
print(f&quot;Token IDs: {encoded.ids}&quot;)
print(f&quot;切分片段: {encoded.tokens}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体训练代码可以参考 &lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/tree/master/chapter-03-attention-translation/codes/train_tokenizer_hf.py&quot;&gt;&lt;code&gt;codes/train_tokenizer_hf.py&lt;/code&gt;&lt;/a&gt;，使用下列命令可以训练一个工业级的 BPE 分词器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python train_tokenizer_hf.py \
    --data_root &quot;data/corpus&quot; \
    --datasets wiki2019zh news2016zh baike2018qa webtext2019zh translation2019zh \
    --vocab_size 30000 \
    --save_path &quot;models/tokenizer/my_bert_tokenizer.json&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后续的语言模型训练我们会采用 Huggingface 提供的预训练的分词器。&lt;/p&gt;
&lt;h3&gt;分词器的结果&lt;/h3&gt;
&lt;p&gt;完成对分词器的训练后，我们会得到一个&lt;strong&gt;词汇表&lt;/strong&gt;(Vocabulary) $\mathcal{V}$ 和对文本做切分的规则 $\text{Split}$。词汇表收录了模型能够识别和处理的所有 token，并为每个 token 分配一个唯一的整数编号(ID)。形式化地，词汇表确定了一个从 token 到整数 ID 的双射映射
$$
\text{Vocab}: \mathcal{V} \rightarrow {0, 1, 2, \dots, |\mathcal{V}|-1},\text{token} \mapsto \text{ID}
$$&lt;/p&gt;
&lt;p&gt;其中 $|\mathcal{V}|$ 代表词表大小。示例如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vocab = {
    &quot;[PAD]&quot;: 0,   # 填充标记，用于将不等长序列对齐
    &quot;[UNK]&quot;: 1,   # 未知词标记，处理未收录词
    &quot;[CLS]&quot;: 2,   # 序列起始标记(常用于分类任务)
    &quot;[SEP]&quot;: 3,   # 分隔标记(常用于分隔两个句子)
    &quot;I&quot;: 4,
    &quot;love&quot;: 5,
    &quot;AI&quot;: 6,
    &quot;!&quot;: 7,
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果是使用前面实现的 &lt;code&gt;BPETokenizer&lt;/code&gt;，可以用 &lt;code&gt;tokenizer.vocab&lt;/code&gt; 属性查看训练好的词汇表内容字典。&lt;/p&gt;
&lt;p&gt;如果是使用 Hugging Face 的工业级 &lt;code&gt;tokenizers.Tokenizer&lt;/code&gt;，可以通过调用 &lt;code&gt;get_vocab()&lt;/code&gt; 等方法来操作词表内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 获取完整的词汇表字典 (Token -&amp;gt; ID)
hf_vocab = tokenizer.get_vocab()

# 获取词表大小
print(f&quot;词汇表大小: {tokenizer.get_vocab_size()}&quot;)

# 双向映射查询
print(f&quot;&apos;AI&apos; 对应的 ID: {tokenizer.token_to_id(&apos;AI&apos;)}&quot;)
print(f&quot;最开始的 ID (例如 0) 对应的 Token: {tokenizer.id_to_token(0)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有了分词器后，我们就可以将文本数据转化为整数 ID 序列了。这个过程我们用数学形式化表示为&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;分词&lt;/strong&gt;
$$
T_{\text{tokenized}} = \text{Split}(T) = [c_1, c_2, \cdots, c_N] \in \mathcal{V}^N
$$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;映射为整数 ID&lt;/strong&gt;
$$
T_{\text{id}} = \text{Tokenization}(T) = [\text{Vocab}(c_1), \text{Vocab}(c_2), \cdots, \text{Vocab}(c_N)] \in \mathbb{Z}^N
$$
其中 $\mathbb{Z}^N$ 表示整数的 N 维向量空间。每个文本中的 token 都被映射为一个整数 ID，这些 ID 就是模型可以直接处理的数值输入了。需要注意的是，分词器的设计和训练质量直接影响到后续模型的性能，因为它决定了文本数据的基本表示形式。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;词嵌入层 (Embedding Layer)&lt;/h2&gt;
&lt;p&gt;词嵌入层的作用是为了解决整数 ID 的两个缺陷：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;整数之间的大小关系(例如 5 &amp;gt; 4)并不代表任何语义上的远近或重要性差异；&lt;/li&gt;
&lt;li&gt;单个整数所能表达的信息容量极为有限。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;词嵌入层的核心是一张巨大的可训练查询表——&lt;strong&gt;嵌入矩阵&lt;/strong&gt;
$$
\mathbf{E} = \begin{bmatrix} e_0 \ e_1 \ \vdots \ e_{|\mathcal{V}|-1} \end{bmatrix}\in \mathbb{R}^{|\mathcal{V}| \times d}
$$&lt;/p&gt;
&lt;p&gt;其中 $d$ 是嵌入向量的维度(例如 512、768 或 4096)，$e_i \in \mathbb{R}^d$ 表示第 $i$ 个 token 的嵌入向量。对于输入序列 $T_{\text{id}}$ 中的每一个整数 ID $T_{\text{id}}^{(i)}$，嵌入层所做的操作就是“查表”
$$
x_i = \mathbf{E}[T_{\text{id}}^{(i)}, :] = e_{T_{\text{id}}^{(i)}} \in \mathbb{R}^d
$$
例如，ID 为 &lt;code&gt;5&lt;/code&gt; 的 token &lt;code&gt;&quot;love&quot;&lt;/code&gt; 会被映射为一个 $d$ 维的实数向量。这个向量的每一个分量在训练开始时是随机初始化的，但随着模型在任务数据上的不断训练，&lt;strong&gt;这些向量的数值会通过反向传播算法被逐步调整&lt;/strong&gt;。调整的目标是让语义上相近的词(如 &lt;code&gt;&quot;love&quot;&lt;/code&gt; 与 &lt;code&gt;&quot;like&quot;&lt;/code&gt;)在向量空间中彼此靠近，而语义无关的词则相互远离。&lt;/p&gt;
&lt;p&gt;这就是 &lt;strong&gt;词嵌入&lt;/strong&gt; 的核心思想，&lt;strong&gt;将离散的 token 映射为连续向量空间中的点，用向量之间的几何关系(距离、角度)来编码词汇之间的语义和语法关系&lt;/strong&gt;。著名的例子有
$$
\mathbf{e}&lt;em&gt;{\text{king}} - \mathbf{e}&lt;/em&gt;{\text{man}} + \mathbf{e}&lt;em&gt;{\text{woman}} \approx \mathbf{e}&lt;/em&gt;{\text{queen}}
$$&lt;/p&gt;
&lt;p&gt;通过词嵌入层，文本数据最终完成从“不可计算的符号序列”到“可微分的数值张量”的关键蜕变。整段文本被表示为一个形状为 $N \times d$ 的二维矩阵，形式化表示为
$$
x = \text{Embedding}(T_{\text{id}}) = \begin{bmatrix} e_{T_{\text{id}}^{(1)}} \ e_{T_{\text{id}}^{(2)}} \ \vdots \ e_{T_{\text{id}}^{(N)}} \end{bmatrix} \in \mathbb{R}^{N \times d}
$$&lt;/p&gt;
&lt;p&gt;词嵌入层可以在 PyTorch 中通过 &lt;code&gt;nn.Embedding&lt;/code&gt; 模块实现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;embedding_layer = nn.Embedding(num_embeddings=len(vocab), embedding_dim=d)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;文本翻译——序列到序列的建模 (seq2seq)&lt;/h2&gt;
&lt;p&gt;文本翻译是自然语言处理领域的一个经典任务，属于序列到序列 (seq2seq) 建模范畴，目标是将一种语言的文本转换为另一种语言的文本。这个任务不仅要求模型理解输入文本的语义，还需要生成符合目标语言语法和习惯的输出文本。&lt;/p&gt;
&lt;p&gt;数学上，文本翻译就是建立从一种语言的词汇空间到另一种语言的词汇空间的映射关系，完成文本序列之间的转换，即学习一个映射
$$
f_{\text{translate}} : \mathcal{C}&lt;em&gt;{\text{source}}^N \rightarrow \mathcal{C}&lt;/em&gt;{\text{target}}^M, T_{\text{source}} \mapsto T_{\text{target}}
$$
其中 $\mathcal{C}&lt;em&gt;{\text{source}}$ 和 $\mathcal{C}&lt;/em&gt;{\text{target}}$ 分别是源语言和目标语言的词汇空间，$N$ 和 $M$ 是输入和输出文本的长度；$T_{\text{source}}$ 是输入文本字符序列，$T_{\text{target}}$ 是输出文本字符序列。模型需要学习在不同语言之间捕捉语义对应关系，并生成流畅自然的翻译结果。&lt;/p&gt;
&lt;p&gt;根据前面的离散文本到连续张量的转变，我们可以将文本翻译任务转化为学习一个从源语言文本的嵌入张量到输出目标语言文本的嵌入张量的映射
$$
f&apos;&lt;em&gt;{\text{translate}} : \mathbb{R}^{N \times d} \rightarrow \mathbb{R}^{M \times d}, x&lt;/em&gt;{\text{source}} \mapsto y_{\text{target}}
$$&lt;/p&gt;
&lt;p&gt;注: 在许多文献中，嵌入维度 $d$ 都表示成 $d_{\text{model}}$ 或者 $d_{\text{emb}}$ 以便于区分。本文在公式中选择 $d$ 的简洁表示，但在实际代码中会采用 $d_{\text{model}}$ 的描述形式。&lt;/p&gt;
&lt;h3&gt;注意力机制&lt;/h3&gt;
&lt;p&gt;文本翻译实际上并非是简单词对词的翻译，需要考虑到句子中间很复杂的依赖关系。例如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;中文：他做事总是三天打鱼，两天晒网。&lt;/li&gt;
&lt;li&gt;英文直译：He always fishes for three days and dries the net for two days.&lt;/li&gt;
&lt;li&gt;英文意译：He is very inconsistent in his work.
这个翻译的例子就很好地说明了文本翻译的复杂性，英文中的 &quot;inconsistent&quot; 这个词并没有在中文原文中出现过，而是模型通过对整个句子语义的理解，推断出一个更符合英文表达习惯的翻译结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种依赖关系不是简单词与词之间的对应，也不是简单的只与当前词汇往前的几个词汇相关，而是&lt;strong&gt;每个词都可能与输入序列中的任意一个词相关&lt;/strong&gt;。因此，文本翻译需要一个能够捕捉全局依赖关系的机制。&lt;/p&gt;
&lt;p&gt;基于此，&lt;strong&gt;注意力机制 (Attention Mechanism)&lt;/strong&gt; 被提出了。它允许模型在生成每个输出词时，动态地“关注”输入序列中的不同部分，从而捕捉到全局的依赖关系。这种机制极大地提升了文本翻译的质量，使得模型能够生成更自然、更准确的翻译结果。&lt;/p&gt;
&lt;h4&gt;数学模型&lt;/h4&gt;
&lt;p&gt;注意力机制的核心思想是，&lt;strong&gt;句子中的每个词，都可以直接与句子中的所有其他词进行交互，“注意到”那些与自己相关的部分。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q、K、V 模型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;注意力机制借用了数据库检索的概念：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;查询张量(Query, $q \in \mathbb{R}^{N \times d_k}$ 或 $\mathbb{R}^{M \times d_k}$)&lt;/strong&gt;：当前正在寻找相关信息的词(我该关注谁？)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;键张量(Key, $k \in \mathbb{R}^{N \times d_k}$)&lt;/strong&gt;：被考察的词(我包含什么信息？)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;值张量(Value, $v \in \mathbb{R}^{N \times d_v}$)&lt;/strong&gt;：被考察词的实际内容(如果你关注我，我就把这段信息给你)。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从张量形状的角度看，$N$ 是输入序列的长度，$d_k$ 和 $d_v$ 是键和值的维度。这表明，对于每一个 token，我们都为它生成了一个 $d_k$ 维度的查询向量、一个 $d_k$ 维度的键向量和一个 $d_v$ 维度的值向量。&lt;/p&gt;
&lt;p&gt;根据网络构建目的的不同，注意力机制主要有两种模式，分别是&lt;strong&gt;自注意力(Self-Attention)&lt;/strong&gt; 和 &lt;strong&gt;交叉注意力(Cross-Attention)&lt;/strong&gt;。这两种注意力机制的 $q$、$k$、$v$ 特征向量的来源不同：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;对于自注意力(Self-Attention)，$q$、$k$、$v$ 都来自同一个输入序列 $x \in \mathbb{R}^{N \times d}$：
$$
q = xW_Q, \quad k = xW_K, \quad v = xW_V
$$
其中 $W_Q \in \mathbb{R}^{d \times d_k}, W_K \in \mathbb{R}^{d \times d_k}, W_V \in \mathbb{R}^{d \times d_v}$ 是模型自动学习的权重矩阵。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于交叉注意力(Cross-Attention)，$q$ 来自一个序列(例如目标语言的输入)，而 $k$ 和 $v$ 来自另一个序列(例如源语言的输入)：
$$
q = y_{\text{target}}W_Q, \quad k = x_{\text{source}}W_K, \quad v = x_{\text{source}}W_V
$$
其中 $y_{\text{target}} \in \mathbb{R}^{M \times d}$ 是目标输入序列，$x_{\text{source}} \in \mathbb{R}^{N \times d}$ 是源输入序列，$W_Q \in \mathbb{R}^{d \times d_k}, W_K \in \mathbb{R}^{d \times d_k}, W_V \in \mathbb{R}^{d \times d_v}$ 是模型自动学习的权重矩阵。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;缩放点积注意力(Scaled Dot-Product Attention)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;注意力机制的目标是为了提取出一整个序列关于某一个 token 的信息。为了衡量信息的相关性，这里采用了点积的方式。有了特征张量 $q$ 和 $k$ 后，依次计算它们各个 tokens 之间的点积。点积结果越大，说明两个 tokens 越相关，也称作注意力得分越高。&lt;/p&gt;
&lt;p&gt;单个点积的计算如下（这里把 $\mathbb{R}^{d_k}$ 的向量看作是行向量）
$$
\text{score}(q_i, k_j) = q_i k_j^\top \in \mathbb{R}
$$
其中 $q_i \in \mathbb{R}^{d_k}$ 是查询张量 $q$ 中的第 $i$ 个 token 的查询向量，$k_j \in \mathbb{R}^{d_k}$ 是键张量 $k$ 中的第 $j$ 个 token 的键向量。通过计算所有 $q_i$ 与所有 $k_j$ 的点积组合，我们可以得到一个注意力得分矩阵
$$
\text{Scores}(q, k) = qk^\top  \in \mathbb{R}^{N \times N} \quad \text{(自注意力)} \quad \text{或} \quad \text{Scores}(q, k) = qk^\top  \in \mathbb{R}^{M \times N} \quad \text{(交叉注意力)}
$$&lt;/p&gt;
&lt;p&gt;为了方便统一相似性度量的尺度，需要沿着键张量的序列长度维度 $N$ 做 Softmax 归一化，将得分转换为概率分布（每个查询 token 都会有一个概率分布），表示键张量对应序列中的每个 token 对当前查询 token 的关注程度：
$$
\text{SimilarityProbs}(q, k) = \text{Softmax}(q k^\top , \text{dim}=-1) \in \mathbb{R}^{N \times N} \text{ 或者 } \mathbb{R}^{M \times N}
$$&lt;/p&gt;
&lt;p&gt;然而，随着 $d_k$ 的增加，点积的结果可能会变得非常大，这会导致 Softmax 函数的容易出现梯度消失。为了解决这个问题，我们对点积结果进行缩放，除以 $\sqrt{d_k}$，从而得到&lt;strong&gt;缩放点积注意力&lt;/strong&gt;的计算公式
$$
\text{AttentionScores}(q, k) = \text{Softmax}\left(\frac{qk^\top }{\sqrt{d_k}}\right)
$$&lt;/p&gt;
&lt;p&gt;缩放点积注意力的本质是关于值张量 $v$ 的权重，加权求和，就是最终的注意力结果
$$
\text{Attention}(q, k, v) = \text{Softmax}\left(\frac{qk^\top }{\sqrt{d_k}}\right) v \in \mathbb{R}^{N \times d_v} \text{ 或者 }\mathbb{R}^{M \times d_v}
$$&lt;/p&gt;
&lt;h4&gt;缩放点积注意力的 PyTorch 实现&lt;/h4&gt;
&lt;p&gt;PyTorch 实现核心缩放点积注意力&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def scaled_dot_product_attention(q, k, v, mask=None):
    d_k = q.size(-1)
    # 1. 计算未缩放的分数 q * k^\top 
    scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k)
    
    # 2. 如果存在掩码，将特定位置屏蔽，从而不参与注意力计算（后续会解释这步操作）
    if mask is not None:
        scores = scores.masked_fill(mask == False, -1e9)
        
    # 3. Softmax 归一化
    attention_weights = F.softmax(scores, dim=-1)
    
    # 4. 加权求和 Value
    output = torch.matmul(attention_weights, v)
    return output, attention_weights
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/attention.png&quot;].src} width=&apos;100%&apos; alt=&apos;注意力机制示意图&apos;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;多头注意力(Multi-Head Attention)&lt;/h4&gt;
&lt;p&gt;为了让模型能从多个不同的“角度”去观察词与词间的关系，通常会将输入拆分成多个并行的“头”(Heads)来计算上述 Attention，然后将结果拼接在一起。&lt;/p&gt;
&lt;p&gt;这里的头可以理解为模型关注的不同的特征领域，每个头都有自己独立的查询、键、值的权重矩阵。通过多头注意力，模型能够捕捉到输入序列中不同层次、不同类型的依赖关系，从而提升模型的表达能力和性能。&lt;/p&gt;
&lt;p&gt;打个比方，使用多头注意力后，有的头可能专注于捕捉语法关系(如主谓宾)，有的头可能专注于捕捉语义关系(如同义词)，还有的头可能专注于捕捉长距离依赖(如跨句子关系)。通过这种方式，模型能够更全面地理解输入文本的结构和意义，从而生成更准确、更自然的翻译结果。&lt;/p&gt;
&lt;p&gt;不论是自注意力机制还是交叉注意力机制，都可以封装成下面的 MultiHeadAttention 层&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MultiHeadAttention(nn.Module):
    &quot;&quot;&quot;多头注意力模块&quot;&quot;&quot;
    def __init__(self, d_model: int, n_heads: int, dropout: float = 0.1):
        super().__init__()
        assert d_model % n_heads == 0, &quot;d_model must be divisible by n_heads&quot;
        self.d_model = d_model
        self.n_heads = n_heads
        self.d_k = d_model // n_heads

        self.W_q = nn.Linear(d_model, d_model)
        self.W_k = nn.Linear(d_model, d_model)
        self.W_v = nn.Linear(d_model, d_model)
        self.W_o = nn.Linear(d_model, d_model)
        self.dropout = nn.Dropout(dropout)

    def _split_heads(self, x):
        &quot;&quot;&quot;将最后一维拆分为 (n_heads, d_k)，并转置为 (batch, n_heads, seq_len, d_k)&quot;&quot;&quot;
        batch, seq_len, _ = x.shape
        x = x.view(batch, seq_len, self.n_heads, self.d_k)
        return x.transpose(1, 2)  # (batch, n_heads, seq_len, d_k)

    def _combine_heads(self, x):
        &quot;&quot;&quot;合并头，形状 (batch, seq_len, d_model)&quot;&quot;&quot;
        batch, n_heads, seq_len, d_k = x.shape
        x = x.transpose(1, 2).contiguous()
        return x.view(batch, seq_len, self.d_model)

    def forward(self, q, k, v, mask=None):
        &quot;&quot;&quot;
        q, k, v: (batch, seq_len_q/k/v, d_model)
        mask: 可选，形状 (batch, 1, seq_len_q, seq_len_k) 或可广播的形状
        &quot;&quot;&quot;
        # 线性变换并分头
        Q = self._split_heads(self.W_q(q))
        K = self._split_heads(self.W_k(k))
        V = self._split_heads(self.W_v(v))

        # 计算注意力
        attn_output, _ = scaled_dot_product_attention(Q, K, V, mask=mask)
        attn_output = self.dropout(attn_output)

        # 合并头并输出
        output = self._combine_heads(attn_output)
        output = self.W_o(output)
        return output
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Transformer 模型&lt;/h3&gt;
&lt;p&gt;Transformer 模型是同时集成了&lt;strong&gt;多头自注意力机制&lt;/strong&gt;和&lt;strong&gt;多头交叉注意力机制&lt;/strong&gt;的架构。它由编码器 (Encoder) 和解码器 (Decoder) 两部分组成，分别负责&lt;strong&gt;处理输入文本&lt;/strong&gt;和&lt;strong&gt;生成输出文本&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/Transformer.png&quot;].src} width=&apos;100%&apos; alt=&apos;Transformer 模型架构示意图&apos;/&amp;gt;&lt;/p&gt;
&lt;p&gt;如图所示，Transformer 的编码器由多个相同的层堆叠而成，每层包含一个多头自注意力机制和一个前馈神经网络 (Feed-Forward Network)。解码器也由多个相同的层堆叠而成，每层包含一个多头自注意力机制、一个多头交叉注意力机制和一个前馈神经网络。&lt;/p&gt;
&lt;p&gt;简单来说，编码器的经过逐层自注意力机制处理，就像图像中的全连接网络一样，感受野本身就是全局的。即使是编码器输出序列中的一个 token，它都是捕捉了输入序列中所有 token 的信息的。正因如此，编码器输出的序列被叫作&lt;strong&gt;全局上下文中间表示(Global Contextual Representation)&lt;/strong&gt;，数学符号记作 $z \in \mathbb{R}^{N \times d}$。全局上下文中间表示会在解码器中被当作键和值输入序列，用于交叉注意力的计算。&lt;/p&gt;
&lt;p&gt;编码器的序列处理是一层一层的把整个序列进行加工，这和图像卷积层的逐层加工非常类似的。但是解码器的处理就很不一样了。&lt;strong&gt;解码器并不是一次性地输出最终的翻译序列结果，而是自回归式地一个 token 一个 token 地生成翻译结果的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;自回归式的处理，意思就是给定一个半成品序列 (下标表示左闭右开) $y_{0:t} = [y_0, y_1, \ldots, y_{t-1}] \in \mathbb{R}^{t \times d}$，解码器需要结合这个半成品序列和编码器输出的全局上下文中间表示，去预测下一个 token $y_t$ 的概率分布 $p(y_{t} | y_{0:t}, z)$。根据概率分布选择最合适的 token 作为 $y_t$ 的输出 (通常是从分布中采样或者选择概率值最大的那个 token)，然后将 $y_t$ 添加到半成品序列中，重复操作，继续预测下一个 token $y_{t+1}$，直到生成完整的翻译结果。&lt;/p&gt;
&lt;p&gt;对比之下，编码器一次性就完成计算了，而解码器需要反复迭代生成结果。&lt;/p&gt;
&lt;p&gt;讲完了 Transformer 编码器和解码器的整体工作原理后，接下来需要细细探讨它的各层的具体实现。&lt;/p&gt;
&lt;h4&gt;输入序列的文本嵌入 (Text Embeddings) 和位置编码 (Positional Encoding)&lt;/h4&gt;
&lt;p&gt;Transformer 模型本身不包含任何循环或卷积结构，因此它无法天然感知序列中 token 的顺序信息，这一点从注意力结果的序列次序可交换性上可以看出来。但是，位置关系在语言建模中是相当重要的，一个简单的例子就足以体现&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文本 1： &quot;我爱自然语言处理&quot;&lt;/li&gt;
&lt;li&gt;文本 2： &quot;自然语言处理爱我&quot;
这两句话的 tokens 是完全一样的，但是它们的语义是完全不同的，这就是位置关系导致的差异。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了让模型能够利用 token 在序列中的位置关系，我们必须在输入端显式地注入位置信息。具体做法是，将 &lt;strong&gt;文本嵌入&lt;/strong&gt; 与 &lt;strong&gt;位置编码&lt;/strong&gt; 相加，作为编码器和解码器真正的输入。其中位置编码必须要能体现出不同位置的区别。&lt;/p&gt;
&lt;p&gt;数学描述上，位置编码的目标是为每个绝对位置 $pos$（$0 \le pos &amp;lt; L$，$L$ 为序列长度）生成一个 $d$ 维向量，使得模型能够区分不同位置的 token。有两种常见的设计方案：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 正余弦型位置编码 (Sinusoidal Positional Encoding)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\text{PE}(pos, 2i) &amp;amp;= \sin\left(\frac{pos}{10000^{,2i / d}}\right) \
\text{PE}(pos, 2i+1) &amp;amp;= \cos\left(\frac{pos}{10000^{,2i / d}}\right)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;其中 $\text{PE}(pos, k)$ 表示位置在 $pos$ 处的 token 的，在文本嵌入向量的第 $k$ 个维度上的位置编码。这种设计的优点包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每个位置的编码是唯一的；&lt;/li&gt;
&lt;li&gt;对于任意固定的偏移量 $k$，$\text{PE}(pos+k)$ 可以表示为 $\text{PE}(pos)$ 的线性函数，这有助于模型学习相对位置信息；&lt;/li&gt;
&lt;li&gt;值域在 $[-1, 1]$ 之间，与文本嵌入相加时不会造成数值不稳定。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最后，将文本嵌入与位置编码按元素相加，得到编码器和解码器的真正输入：
$$
x_{\text{input}} = x + \text{PE}&lt;em&gt;{x} \in \mathbb{R}^{N \times d}, \qquad
y&lt;/em&gt;{\text{input}} = y + \text{PE}_{y} \in \mathbb{R}^{M \times d}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PyTorch 实现&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class PositionalEncoding(nn.Module):
    def __init__(self, d_model: int, max_len: int = 5000):
        super().__init__()
        # 预计算所有位置的位置编码
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)  # (max_len, 1)
        
        # 计算分母项 10000^(2i/d_model)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        
        # 偶数索引使用 sin，奇数索引使用 cos
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        
        # 增加 batch 维度方便广播，形状 (1, max_len, d_model)
        self.register_buffer(&apos;pe&apos;, pe.unsqueeze(0))
    
    def forward(self, x: torch.Tensor) -&amp;gt; torch.Tensor:
        # x 形状: (batch_size, seq_len, d_model)
        return x + self.pe[:, :x.size(1), :]

# 示例：生成一个 batch 的输入嵌入并加上位置编码
batch_size, seq_len, d_model = 2, 10, 512
embedding = nn.Embedding(num_embeddings=10000, embedding_dim=d_model)
x_ids = torch.randint(0, 10000, (batch_size, seq_len))
x_emb = embedding(x_ids)                     # 文本嵌入
pos_enc = PositionalEncoding(d_model)
x_input = pos_enc(x_emb)                     # 最终输入
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 旋转位置编码 (Rotary Position Embedding, RoPE)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;正弦余弦位置编码直接将位置向量加到嵌入上，而 RoPE 采用了一种不同的思路——&lt;strong&gt;将位置信息通过旋转矩阵融入 query 和 key 向量中&lt;/strong&gt;，使得注意力分数天然依赖于 token 之间的相对位置。&lt;/p&gt;
&lt;p&gt;对于序列中第 $m$, $n$ 个位置的 token，其对应的 query 向量 $q_m$ 和 key 向量 $k_n$ 不直接加上位置编码，而是乘以一个与位置 $m$ 相关的旋转矩阵&lt;/p&gt;
&lt;p&gt;$$
q_m^{\text{Rotated}} = R_{\Theta,m} , q_m, \quad
k_n^{\text{Rotated}} = R_{\Theta,n} , k_n
$$&lt;/p&gt;
&lt;p&gt;其中 $q_m, k_n$ 是未经过位置编码的原始 query/key 向量（来自文本嵌入的线性变换），$R_{\Theta,m}$ 是旋转矩阵。旋转矩阵具有性质
$$
R_{\Theta,m}^\top R_{\Theta,n} = R_{\Theta, n-m}
$$&lt;/p&gt;
&lt;p&gt;因此，两个旋转后的向量的内积为
$$
(q_m^{{\text{Rotated}}})^\top k_n^{\text{Rotated}} = q_m^\top R_{\Theta,m}^\top R_{\Theta,n} k_n = q_m^\top R_{\Theta, n-m} k_n
$$&lt;/p&gt;
&lt;p&gt;这表明 &lt;strong&gt;注意力分数仅依赖于相对位置 $n-m$&lt;/strong&gt;，而不是绝对位置 $m$ 和 $n$，这是 RoPE 能够很好地外推到更长序列的关键。&lt;/p&gt;
&lt;p&gt;为了高效实现，RoPE 通常将 $d$ 维向量拆分成 $d/2$ 个二维子空间，每个子空间独立旋转。对于第 $i$ 个子空间（$0 \le i &amp;lt; d/2$），旋转角度为
$$
\theta_i = 10000^{-2i / d}
$$
于是位置 $m$ 的旋转矩阵为
$$
R_{\Theta,m} = \begin{bmatrix}
\cos m\theta_0 &amp;amp; -\sin m\theta_0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \cdots \
\sin m\theta_0 &amp;amp; \cos m\theta_0 &amp;amp; 0 &amp;amp; 0 &amp;amp; \cdots \
0 &amp;amp; 0 &amp;amp; \cos m\theta_1 &amp;amp; -\sin m\theta_1 &amp;amp; \cdots \
0 &amp;amp; 0 &amp;amp; \sin m\theta_1 &amp;amp; \cos m\theta_1 &amp;amp; \cdots \
\vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \ddots
\end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;实际计算时不需要构造完整的稀疏矩阵，而是利用复数乘法高效实现。将二维向量视为复数 $q_{2k} + j q_{2k+1}$，乘以旋转因子 $e^{j m \theta_k}$ 即可完成旋转。&lt;/p&gt;
&lt;p&gt;RoPE 的优点包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;相对位置依赖&lt;/strong&gt;：注意力分数直接由相对距离决定，更符合自然语言的局部性假设。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长序列外推能力&lt;/strong&gt;：训练时未见过的长度，在推理时仍能保持较好的性能，而正弦余弦编码的外推能力较弱。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不改变向量模长&lt;/strong&gt;：旋转是正交变换，不会缩放向量，保持数值稳定。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;PyTorch 实现&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def rotate_half(x):
    &quot;&quot;&quot;将输入的后一半维度旋转到前一半的负位置&quot;&quot;&quot;
    x1, x2 = x.chunk(2, dim=-1)
    return torch.cat([-x2, x1], dim=-1)

def apply_rotary_pos_emb(q, k, cos, sin):
    &quot;&quot;&quot;
    通用 RoPE 应用函数
    q, k: (batch, n_heads, seq_len, d_k)
    cos, sin: (1, 1, seq_len, d_k)
    &quot;&quot;&quot;
    # 确保 cos 和 sin 的维度与 q 匹配（截断到当前序列长度）
    seq_len = q.shape[2]
    cos = cos[:, :, :seq_len, :]
    sin = sin[:, :, :seq_len, :]

    # 旋转公式: q_rotated = (q * cos) + (rotate_half(q) * sin)
    q_embed = (q * cos) + (rotate_half(q) * sin)
    k_embed = (k * cos) + (rotate_half(k) * sin)
    return q_embed, k_embed

class Model(nn.Module):
    def __init__(self, ...):
        super().__init__()
        pass

    def get_rotary_emb(self, seq_len, device):
        &quot;&quot;&quot;
        计算旋转位置编码的 cos 和 sin 缓存向量
        Args:
            seq_len: 当前输入的序列长度
            device: 张量所在设备
        Returns:
            cos, sin: 形状为 (1, 1, seq_len, d_k) 的旋转向量
        &quot;&quot;&quot;
        t = torch.arange(seq_len, device=device).float()
        freqs = torch.outer(t, self.inv_freq)  # (seq_len, d_k/2)
        
        # 核心逻辑：这里为了配合 rotate_half 中的 chunk(2, dim=-1) 操作，
        # 我们需要构造一个对称的角度序列 [theta_0, ..., theta_{d_k/2-1}, theta_0, ..., theta_{d_k/2-1}]
        # 这样在 apply_rotary_pos_emb 中，每一对 (x1, x2) 都会乘以相同的旋转频率
        emb = torch.cat([freqs, freqs], dim=-1)         # (seq_len, d_k)
        
        # 整理形状以适配多头注意力：(batch, n_heads, seq_len, d_k)
        cos = emb.cos().view(1, 1, seq_len, self.d_k) 
        sin = emb.sin().view(1, 1, seq_len, self.d_k) 
        return cos, sin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还需注意的一点是，当引入多头注意力机制后，RoPE 位置编码的维度就不再是 $d$，而是每个头的维度 $d_{\text{head}} = d / \text{num_heads}$ 了，需要注意调整。&lt;/p&gt;
&lt;p&gt;在实际应用中，如果任务序列长度固定且较短（如机器翻译），正弦余弦编码足够；如果需要处理超长上下文或要求更好的外推性，RoPE 是更优选择。&lt;/p&gt;
&lt;h4&gt;注意力掩码 (Attention Mask)&lt;/h4&gt;
&lt;p&gt;在实际训练和推理中，我们需要控制注意力机制能看到哪些位置。掩码（Mask）是一个与注意力得分矩阵形状相同（或可广播）的布尔矩阵，用于屏蔽某些位置的注意力分数（通常将屏蔽位置设为 $-\infty$，使其 Softmax 权重为 0）。&lt;/p&gt;
&lt;p&gt;主要有两类掩码：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 填充掩码 (Padding Mask)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于批次内序列长度不一，较短的序列会用 &lt;code&gt;&amp;lt;PAD&amp;gt;&lt;/code&gt; token 填充至统一长度。这些填充位置不应参与注意力计算，否则模型会学到无意义的模式。&lt;/p&gt;
&lt;p&gt;填充掩码通常是一个形状为 &lt;code&gt;(batch_size, seq_len)&lt;/code&gt; 的布尔矩阵，其中 &lt;code&gt;True&lt;/code&gt; 表示有效 token，&lt;code&gt;False&lt;/code&gt; 表示填充 token。在计算注意力时，将填充 token 对应的键/查询位置屏蔽。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 因果掩码 (Causal Mask / Look-ahead Mask)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在解码器的自注意力中，为了保持自回归性质（预测第 $t$ 个 token 时不能看到 $t$ 及之后的 token），需要使用因果掩码。它确保位置 $i$ 只能关注到位置 $j \le i$。&lt;/p&gt;
&lt;p&gt;因果掩码是一个下三角矩阵：
$$
\text{causal_mask}_{ij} = \begin{cases}
1, &amp;amp; i \ge j \
0, &amp;amp; i &amp;lt; j
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;当与填充掩码结合时，通常将两者取逻辑与，然后在计算注意力得分前将无效位置设为 $-\infty$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PyTorch 实现&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def create_padding_mask(seq, pad_token_id=0):
    &quot;&quot;&quot;seq: (batch_size, seq_len)  token id 序列&quot;&quot;&quot;
    return (seq != pad_token_id).unsqueeze(1).unsqueeze(2)  # (batch, 1, 1, seq_len)

def create_causal_mask(size):
    &quot;&quot;&quot;生成下三角掩码，shape: (size, size)&quot;&quot;&quot;
    mask = torch.triu(torch.ones(size, size), diagonal=1).bool()  # 上三角为 True
    return ~mask   # True 表示允许关注

# 在注意力计算中合并掩码
def combine_masks(padding_mask, causal_mask):
    # padding_mask: (batch, 1, 1, seq_len_k)
    # causal_mask:   (1, seq_len_q, seq_len_k) 或 (seq_len_q, seq_len_k)
    # 输出形状 (batch, 1, seq_len_q, seq_len_k)
    if padding_mask is not None:
        mask = padding_mask &amp;amp; causal_mask.unsqueeze(0)
    else:
        mask = causal_mask
    return mask

# 在 scaled_dot_product_attention 中使用
def scaled_dot_product_attention(q, k, v, mask=None):
    d_k = q.size(-1)
    scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k)
    if mask is not None:
        # mask 中 True 表示允许关注，False 表示屏蔽
        scores = scores.masked_fill(~mask, -1e9)
    attention_weights = F.softmax(scores, dim=-1)
    return torch.matmul(attention_weights, v), attention_weights
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：在实际实现中，为了提高效率，常常将填充掩码和因果掩码预先计算好并合并为一个张量，传入注意力函数。&lt;/p&gt;
&lt;h4&gt;前馈层 (Feed-Forward Network)&lt;/h4&gt;
&lt;p&gt;在多头注意力机制之后，Transformer 的每个编码器和解码器层都包含一个全连接的前馈网络（FFN）。该网络对每个 token 的表示独立地进行相同的非线性变换，从而增强模型的表达能力。&lt;/p&gt;
&lt;p&gt;FFN 由两个线性变换和一个激活函数组成（通常使用 ReLU 或其变体 GELU）。设输入为 $x \in \mathbb{R}^{N \times d}$，则：&lt;/p&gt;
&lt;p&gt;$$
\text{FFN}(x) = \text{GELU}(x W_1 + b_1) W_2 + b_2
$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$W_1 \in \mathbb{R}^{d \times d_{\text{ff}}}$，$b_1 \in \mathbb{R}^{d_{\text{ff}}}$ 是第一个线性层的参数；&lt;/li&gt;
&lt;li&gt;$W_2 \in \mathbb{R}^{d_{\text{ff}} \times d}$，$b_2 \in \mathbb{R}^{d}$ 是第二个线性层的参数；&lt;/li&gt;
&lt;li&gt;$d_{\text{ff}}$ 是前馈层的隐藏维度，通常设为 $4d$（即 2048 当 $d=512$ 时）；&lt;/li&gt;
&lt;li&gt;GELU 是高斯误差线性单元激活函数：$\text{GELU}(x) = x \cdot \Phi(x)$，其中 $\Phi$ 是标准正态分布的累积分布函数。相比 ReLU，GELU 更平滑，常用于现代 Transformer 模型。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;值得注意的是，FFN 对序列中的每个位置独立作用（参数共享），即输出矩阵的第 $i$ 行仅依赖于输入矩阵的第 $i$ 行。这使得 FFN 能够在不破坏位置独立性的前提下，为每个 token 引入非线性变换。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PyTorch 实现&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class FeedForward(nn.Module):
    def __init__(self, d_model: int, d_ff: int = None, dropout: float = 0.1):
        super().__init__()
        if d_ff is None:
            d_ff = 4 * d_model
        self.linear1 = nn.Linear(d_model, d_ff)
        self.linear2 = nn.Linear(d_ff, d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        # x: (batch_size, seq_len, d_model)
        x = self.linear1(x)
        x = F.gelu(x)               # GELU 激活
        x = self.dropout(x)
        x = self.linear2(x)
        return x
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;残差连接与层归一化 (Add &amp;amp; Norm)&lt;/h4&gt;
&lt;p&gt;随着神经网络层数的加深，梯度消失和表示退化问题会变得严重。Transformer 采用了 &lt;strong&gt;残差连接 (Residual Connection)&lt;/strong&gt; 与 &lt;strong&gt;层归一化 (Layer Normalization)&lt;/strong&gt; 相结合的策略，使得梯度可以顺畅地流过深层网络，同时稳定训练过程。&lt;/p&gt;
&lt;p&gt;每个子层（多头注意力或前馈网络）的输出都会与该子层的输入相加，然后再进行层归一化。形式化地，对于子层 $\text{Sublayer}(\cdot)$，其输出为：&lt;/p&gt;
&lt;p&gt;$$
\text{Output} = \text{LayerNorm}(x + \text{Sublayer}(x))
$$&lt;/p&gt;
&lt;p&gt;其中 $x$ 是子层的输入。在原始 Transformer 论文中，层归一化位于残差连接之后（即 Post-LN），但后续实践（如 GPT、BERT）常采用 &lt;strong&gt;Pre-LN&lt;/strong&gt; 变体，即先归一化再进入子层，然后残差连接：$x + \text{Sublayer}(\text{LayerNorm}(x))$。Pre-LN 更有利于训练稳定性，尤其当层数很深时。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;层归一化&lt;/strong&gt; 与批归一化 (Batch Normalization) 不同，它对每个样本的每个特征维度独立计算均值和方差，并应用可学习的缩放和平移参数。对于输入 $x \in \mathbb{R}^{N \times d}$，层归一化沿最后一维（特征维度）进行：&lt;/p&gt;
&lt;p&gt;$$
\text{LayerNorm}(x) = \gamma \odot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta
$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\mu = \frac{1}{d} \sum_{j=1}^{d} x_{:,j}$（每个 token 的均值，形状为 $N$）；&lt;/li&gt;
&lt;li&gt;$\sigma^2 = \frac{1}{d} \sum_{j=1}^{d} (x_{:,j} - \mu)^2$（每个 token 的方差）；&lt;/li&gt;
&lt;li&gt;$\gamma, \beta \in \mathbb{R}^d$ 是可学习的缩放和偏移参数；&lt;/li&gt;
&lt;li&gt;$\epsilon$ 是一个小常数（如 $10^{-5}$）防止除零。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;PyTorch 实现&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class AddNorm(nn.Module):
    def __init__(self, d_model: int, dropout: float = 0.1, pre_ln: bool = True):
        super().__init__()
        self.norm = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)
        self.pre_ln = pre_ln   # True: Pre-LN, False: Post-LN

    def forward(self, x, sublayer):
        # x: 子层输入, sublayer: 子层（尚未加残差）
        if self.pre_ln:
            # Pre-LN: 先归一化，再经过子层，然后残差连接
            return x + self.dropout(sublayer(self.norm(x)))
        else:
            # Post-LN: 先子层再加残差，最后归一化
            return self.norm(x + self.dropout(sublayer(x)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际使用时，Add &amp;amp; Norm 不单独创建一个模块，通常直接封装在 编码器/解码器 层内部：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class EncoderLayer(nn.Module):
    &quot;&quot;&quot;单个编码器层（Pre-LN 风格）&quot;&quot;&quot;
    def __init__(self, d_model: int, n_heads: int, d_ff: int = None, dropout: float = 0.1):
        super().__init__()
        self.self_attn = MultiHeadAttention(d_model, n_heads, dropout)
        self.ffn = FeedForward(d_model, d_ff, dropout)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        # Pre-LN: 先归一化，再子层，然后残差
        residual = x
        x = self.norm1(x)
        x = self.self_attn(x, x, x, mask)
        x = self.dropout1(x)
        x = residual + x

        residual = x
        x = self.norm2(x)
        x = self.ffn(x)
        x = self.dropout2(x)
        x = residual + x
        return x

class DecoderLayer(nn.Module):
    &quot;&quot;&quot;单个解码器层（Pre-LN 风格）&quot;&quot;&quot;
    def __init__(self, d_model: int, n_heads: int, d_ff: int = None, dropout: float = 0.1):
        super().__init__()
        self.self_attn = MultiHeadAttention(d_model, n_heads, dropout)
        self.cross_attn = MultiHeadAttention(d_model, n_heads, dropout)
        self.ffn = FeedForward(d_model, d_ff, dropout)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)
        self.dropout3 = nn.Dropout(dropout)

    def forward(self, x, encoder_output, src_mask=None, tgt_mask=None):
        # 自注意力（带因果掩码）
        residual = x
        x = self.norm1(x)
        x = self.self_attn(x, x, x, tgt_mask)
        x = self.dropout1(x)
        x = residual + x

        # 交叉注意力（q 来自解码器，k/v 来自编码器）
        residual = x
        x = self.norm2(x)
        x = self.cross_attn(x, encoder_output, encoder_output, src_mask)
        x = self.dropout2(x)
        x = residual + x

        # 前馈网络
        residual = x
        x = self.norm3(x)
        x = self.ffn(x)
        x = self.dropout3(x)
        x = residual + x
        return x
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;动手实践：构建微型翻译机&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1. 首先完成对 Transformer 的搭建：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Transformer(nn.Module):
    &quot;&quot;&quot;完整的 Transformer 模型（用于序列到序列任务）&quot;&quot;&quot;
    def __init__(self,
                 src_vocab_size: int,
                 tgt_vocab_size: int,
                 d_model: int = 512,
                 n_heads: int = 8,
                 num_encoder_layers: int = 6,
                 num_decoder_layers: int = 6,
                 d_ff: int = 2048,
                 max_seq_len: int = 5000,
                 dropout: float = 0.1,
                 pad_token_id: int = 0):
        super().__init__()
        self.d_model = d_model
        self.pad_token_id = pad_token_id

        # 嵌入层（共享或独立，考虑到做文本翻译任务，两种语言有很大的区别，这里分开）
        self.src_embedding = Embeddings(src_vocab_size, d_model)
        self.tgt_embedding = Embeddings(tgt_vocab_size, d_model)
        self.pos_encoding = PositionalEncoding(d_model, max_seq_len)    # 这里的代码设计是 PE 模块自己内部把加法做好了，其他地方可能是需要自己手动在此处完成与 PE 的相加

        self.encoder = Encoder(num_encoder_layers, d_model, n_heads, d_ff, dropout)     # Encoder 是由若干个 EncoderLayer 堆叠而成的
        self.decoder = Decoder(num_decoder_layers, d_model, n_heads, d_ff, dropout)     # Decoder 是由若干个 DecoderLayer 堆叠而成的

        self.output_proj = nn.Linear(d_model, tgt_vocab_size)
        self.dropout = nn.Dropout(dropout)

        self._init_parameters()

    def _init_parameters(self):
        &quot;&quot;&quot;参数初始化（Xavier 均匀分布）&quot;&quot;&quot;
        for p in self.parameters():
            if p.dim() &amp;gt; 1:
                # xavier 均匀分布是从 [-sqrt(6 / (fan_in + fan_out)), sqrt(6 / (fan_in + fan_out))] 之间均匀采样，其中 fan_in = 输入维度，fan_out = 输出维度
                # 对于嵌入层，它各个维度的取值范围是 [-sqrt(3/d_model), sqrt(3/d_model)]，明显小于位置编码的取值范围 [-1, 1]，在向量范数上看来，位置编码占了主导作用
                # 因此在早期的嵌入层设计中，会给嵌入向量乘上 sqrt(d_model) 来放大嵌入向量的范数，使其与位置编码的范数保持一致
                nn.init.xavier_uniform_(p)

    def forward(self, src, tgt, src_mask=None, tgt_mask=None):
        &quot;&quot;&quot;
        src: (batch, src_seq_len) 源语言 token ID
        tgt: (batch, tgt_seq_len) 目标语言 token ID（训练时使用，需要偏移）
        src_mask: 可选，源序列的掩码（通常为填充掩码）
        tgt_mask: 可选，目标序列的掩码（通常为因果掩码 + 填充掩码）
        &quot;&quot;&quot;
        # 嵌入并添加位置编码
        src_emb = self.src_embedding(src)
        src_emb = self.pos_encoding(src_emb)
        src_emb = self.dropout(src_emb)

        tgt_emb = self.tgt_embedding(tgt)
        tgt_emb = self.pos_encoding(tgt_emb)
        tgt_emb = self.dropout(tgt_emb)

        # 编码器
        encoder_output = self.encoder(src_emb, src_mask)

        # 解码器
        decoder_output = self.decoder(tgt_emb, encoder_output, src_mask, tgt_mask)

        # 输出投影到目标词表
        logits = self.output_proj(decoder_output)  # (batch, tgt_seq_len, tgt_vocab_size)
        return logits

    def encode(self, src, src_mask=None):
        &quot;&quot;&quot;仅编码（用于推理时复用编码器输出）&quot;&quot;&quot;
        src_emb = self.src_embedding(src)
        src_emb = self.pos_encoding(src_emb)
        src_emb = self.dropout(src_emb)
        return self.encoder(src_emb, src_mask)

    def decode(self, tgt, encoder_output, src_mask=None, tgt_mask=None):
        &quot;&quot;&quot;单步解码（自回归生成时调用）&quot;&quot;&quot;
        tgt_emb = self.tgt_embedding(tgt)
        tgt_emb = self.pos_encoding(tgt_emb)
        tgt_emb = self.dropout(tgt_emb)
        return self.decoder(tgt_emb, encoder_output, src_mask, tgt_mask)

    def generate(self, src, start_token_id, end_token_id, max_len=100, device=&apos;cpu&apos;):
        &quot;&quot;&quot;
        自回归生成（简单示例，使用贪婪搜索）
        src: (batch, src_seq_len)
        返回: (batch, generated_seq_len)
        &quot;&quot;&quot;
        batch_size = src.size(0)
        src_mask = create_padding_mask(src, self.pad_token_id).to(device)
        encoder_output = self.encode(src, src_mask)

        # 初始化解码器输入：只包含起始标记
        tgt = torch.full((batch_size, 1), start_token_id, dtype=torch.long, device=device)

        for _ in range(max_len):
            # 生成目标掩码（因果掩码 + 填充掩码）
            tgt_seq_len = tgt.size(1)
            causal_mask = create_causal_mask(tgt_seq_len, device)  # (1, tgt_len, tgt_len)
            # 填充掩码：tgt 中没有填充（假设起始标记和生成 token 都不含 pad）
            # 但为了统一接口，可以生成一个全 True 的掩码
            tgt_padding_mask = torch.ones(batch_size, tgt_seq_len, dtype=torch.bool, device=device)
            tgt_padding_mask = tgt_padding_mask.unsqueeze(1).unsqueeze(2)  # (batch,1,1,seq_len)
            combined_mask = combine_masks(tgt_padding_mask, causal_mask)

            logits = self.decode(tgt, encoder_output, src_mask, combined_mask)
            next_token_logits = logits[:, -1, :]  # 取最后一个位置的输出
            next_token = torch.argmax(next_token_logits, dim=-1, keepdim=True)

            # 拼接
            tgt = torch.cat([tgt, next_token], dim=1)

            # 如果所有序列都生成了结束标记，则停止
            if (next_token == end_token_id).all():
                break

        return tgt[:, 1:]  # 去掉起始标记
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模型代码详见 &lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/tree/master/chapter-03-attention-translation/codes/models/transformer.py&quot;&gt;codes/models/transformer.py&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 数据集准备&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里选用 &lt;a href=&quot;https://github.com/brightmart/nlp_chinese_corpus?tab=readme-ov-file#5%E7%BF%BB%E8%AF%91%E8%AF%AD%E6%96%99translation2019zh&quot;&gt;translation2019zh&lt;/a&gt; 语料作为训练和测试的数据集。这个数据集包含了大量的中英文平行句对，非常适合用来训练机器翻译模型。&lt;/p&gt;
&lt;p&gt;Transformer 模型的输入需要有源语言序列 &lt;code&gt;src&lt;/code&gt; 和不完整的目标语言序列 &lt;code&gt;tgt&lt;/code&gt; 两个部分，输出是目标语言序列的下一个 token 的预测分布。但是数据集加载器在构建时一般是直接给出完整的源语言序列 &lt;code&gt;src&lt;/code&gt; 和完整的目标语言序列 &lt;code&gt;tgt&lt;/code&gt; 的，因此在训练前需要对目标语言序列 &lt;code&gt;tgt&lt;/code&gt; 进行偏移处理（shifted right），分别构建 &lt;code&gt;tgt_in&lt;/code&gt; 和 &lt;code&gt;tgt_out&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tgt_in = tgt[:, :-1]   # 目标序列的输入，去掉最后一个 token
tgt_out = tgt[:, 1:]   # 目标序列的输出，去掉第一个 token
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;训练使用的损失函数使用预测当前 token 的交叉熵损失函数即可，优化器可以选择 AdamW。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;optimizer = optim.AdamW(model.parameters(), lr=args.lr)
criterion = nn.CrossEntropyLoss(ignore_index=pad_id)    # ignore_index 用于忽略 &amp;lt;PAD&amp;gt; token 的损失计算
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体的训练循环如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for src, tgt in dataloader:
    src, tgt = src.to(device), tgt.to(device)
    tgt_in = tgt[:, :-1]    # (batch, tgt_seq_len-1)
    tgt_out = tgt[:, 1:]    # (batch, tgt_seq_len-1)

    src_mask = create_padding_mask(src, pad_id).to(device)
    tgt_mask = create_causal_mask(tgt_in.size(1), device)  # 因果掩码
    tgt_padding_mask = create_padding_mask(tgt_in, pad_id).to(device)
    combined_mask = combine_masks(tgt_padding_mask, tgt_mask)

    logits = model(src, tgt_in, src_mask, combined_mask)    # (batch, tgt_seq_len-1, tgt_vocab_size)
    loss = criterion(logits.view(-1, logits.size(-1)), tgt_out.view(-1))

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;训练部分代码详见 &lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/tree/master/chapter-03-attention-translation/codes/train_transformer.py&quot;&gt;codes/train_transformer.py&lt;/a&gt;，可以使用下列命令来运行训练：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python train_transformer.py \
    --data_root &quot;data/translation&quot; \
    --train_file &quot;translation2019zh_train.json&quot; \
    --valid_file &quot;translation2019zh_valid.json&quot; \
    --tokenizer_name &quot;bert-base-multilingual-cased&quot; \
    --batch_size 64 \
    --epochs 20 \
    --lr 0.0001 \
    --max_len 128 \
    --d_model 512 \
    --n_heads 8 \
    --num_layers 6 \
    --d_ff 2048 \
    --dropout 0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 测试模型推理阶段的能力&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在测试阶段，我们可以使用训练好的模型进行推理，生成新的翻译结果。这通常涉及到自回归生成过程，即每次生成一个 token，然后将其添加到输入中继续生成下一个 token，直到生成结束标记 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; 或达到最大长度。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;test_sentence = &quot;Hello, how are you?&quot;
src_tokens = [bos_id] + tokenizer.encode(test_sentence, add_special_tokens=False) + [eos_id]
src_tensor = torch.tensor(src_tokens, dtype=torch.long).unsqueeze(0).to(device)

with torch.no_grad():
    generated = model.generate(src_tensor, bos_id, eos_id, max_len=50, device=device)
    generated_list = generated.squeeze(0).tolist()
    # 清除特殊字符并解码
    generated_list = [idx for idx in generated_list if idx not in [pad_id, bos_id, eos_id]]
    translated = tokenizer.decode(generated_list)
    print(f&quot;模型输出预测: {translated}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了衡量模型的翻译质量，我们可以使用 BLEU 分数等指标来评估生成的翻译与参考翻译之间的相似度。它通过对比&quot;机器生成的译文&quot;与&quot;人工参考译文&quot;的词序重合程度来打分。具体来说：先统计机器译文中有多少词和短语出现在了参考答案中（比如单个词、连续两个词、三个词、四个词的搭配分别检查），然后计算这些匹配的精确率并取平均；为了防止机器&quot;只答一半&quot;也能拿高分，还加入了一个长度惩罚项——如果生成的句子比参考答案短太多，就会扣分。最终得到一个0到1之间的数值，越接近1表示翻译质量越好。举个例子：如果机器译文是&quot;我吃饭了&quot;而参考答案是&quot;我吃过了&quot;，这两个句子虽然意思相近但用词略有不同，BLEU可能会给出一个中等分数而非满分。因此BLEU不是完美指标，但它能快速给大量翻译结果量化排名，是自然语言处理领域最常用的评测标准之一。
$$
\text{BLEU} = \text{BP} \cdot \exp\left( \sum_{n=1}^{N} w_n \log p_n \right)
$$
其中 $p_n$ 是 n-gram 的精确率，$w_n$ 是 n-gram 的权重，$\text{BP}$ 是 brevity penalty，用于惩罚过短的生成结果。&lt;/p&gt;
&lt;p&gt;下面是手动实现的 BLEU 分数计算函数，供参考：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from collections import Counter
import math

def bleu_score(candidate, references, max_n=4):
    &quot;&quot;&quot;计算BLEU分数&quot;&quot;&quot;
    # 生成n-gram函数
    def ngrams(seq, n):
        return list(zip(*[seq[i:] for i in range(n)])) if len(seq) &amp;gt;= n else []
    
    # 1. 统计各级修正精确率
    precisions = []
    for n in range(1, max_n + 1):
        cand_ng = Counter(ngrams(candidate, n))
        
        # 参考取最大计数
        ref_ng = Counter()
        for ref in references:
            ref_ng.update(ngrams(ref, n))
        
        # 裁剪并求和
        match = sum(min(cand_ng[k], ref_ng.get(k, 0)) for k in cand_ng)
        precision = match / len(candidate) if candidate else 0
        precisions.append(precision)
    
    # 2. 几何平均（加平滑防止log(0)）
    log_sum = sum(math.log(p + 1e-10) for p in precisions)
    geo_mean = math.exp(log_sum / max_n)
    
    # 3. 长度惩罚BP
    ref_len = min(references, key=lambda x: abs(len(x)-len(candidate)))
    if len(candidate) &amp;gt;= len(ref_len):
        bp = 1.0
    elif len(candidate) == 0:
        bp = 0.0
    else:
        bp = math.exp(1 - len(ref_len) / len(candidate))
    
    return bp * geo_mean

# ===== 使用示例 =====
if __name__ == &quot;__main__&quot;:
    candidate = &quot;the cat sat on the mat&quot;.split()
    references = [&quot;the cat sat on the mat&quot;.split(), &quot;a cat sat on the rug&quot;.split()]
    
    print(f&quot;BLEU 分数: {bleu_score(candidate, references):.4f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以用 &lt;code&gt;nltk&lt;/code&gt; 库中的 &lt;code&gt;sentence_bleu&lt;/code&gt; 函数来计算 BLEU 分数，使用起来更方便：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from nltk.translate.bleu_score import sentence_bleu

score = sentence_bleu(
    [&quot;the cat sat on the mat&quot;.split(), &quot;a cat sat on the rug&quot;.split()],
    &quot;the cat sat&quot;.split()
)
print(f&quot;BLEU: {score:.4f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. 常见疑问&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么训练时的输入是偏移的 &lt;code&gt;tgt_in&lt;/code&gt; (正确答案，其中不含有模型预测的 token)，而不是直接把模型预测的 token 添加到 &lt;code&gt;tgt_in&lt;/code&gt; 中，作为下一次训练时的输入？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是因为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果像模型推理时使用自己生成的 token 作为下一步的输入，如果自己预测的 token 是错误的，那么后续的输入就会被污染，导致错误不断累加，训练过程不稳定，模型难以收敛；使用真实的目标序列作为输入可以保证训练过程中的输入是正确的，模型能够更快地学习到正确的映射关系。&lt;/li&gt;
&lt;li&gt;观察输入的 &lt;code&gt;tgt_in: (batch, tgt_seq_len-1)&lt;/code&gt; 和输出的 &lt;code&gt;logits: (batch, tgt_seq_len-1, tgt_vocab_size)&lt;/code&gt; 的形状，发现实际上在训练中，模型是并行地对整个序列的各种位置进行预测的，而不是自回归式的逐步生成。自回归需要迭代完成，而并行训练则可以一次性计算整个序列的损失，效率更高。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;文本理解：选词填空与文本分类&lt;/h2&gt;
&lt;p&gt;文本理解是自然语言处理中的核心任务，旨在让模型理解文本的语义内容。这里介绍两种文本理解任务，分别是&lt;strong&gt;选词填空&lt;/strong&gt;与&lt;strong&gt;文本分类&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;选词填空：要求模型根据上下文预测文本中缺失的词语，考验模型对语境的理解能力；&lt;/li&gt;
&lt;li&gt;文本分类：要求模型将文本分配到预定义的类别中，例如下一句预测、垃圾邮件分类、新闻分类、情感分析等；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;1. 选词填空&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;给定一个包含掩码标记的文本序列 $T_{\text{masked}} = [t_1, t_2, ..., t_{\text{mask}}, ..., t_N]$，这里的文本是分词后的结果，$t_i$ 表示第 $i$ 个 tokens。文本序列中的某个位置被遮蔽了（直接用 &lt;code&gt;[MASK]&lt;/code&gt; 这个特殊 token 替换了），模型需要预测该位置最可能的词语。数学上，这可以看作学习一个从上下文文本空间到目标词汇空间的映射：
$$
f_{\text{cloze}} : \mathcal{C}^N \rightarrow \mathcal{C}, \quad T_{\text{masked}} \mapsto w
$$
其中 $\mathcal{C}$ 是词汇空间，$w$ 是应该填入的 token。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 文本分类&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;给定一个文本序列 $T = [t_1, t_2, ..., t_N]$，模型需要为其预测出一个合适的类别标签 $y \in {1, 2, ..., K}$，其中 $K$ 为类别数。这个可以用映射表示为
$$
f_{\text{classify}} : \mathcal{C}^N \rightarrow {1, 2, ..., K}, \quad T \mapsto y
$$&lt;/p&gt;
&lt;h3&gt;BERT：Encoder-Only 模型&lt;/h3&gt;
&lt;p&gt;前面两种文本理解任务聚焦于单个 token 或标签层次的预测，无需生成完整文本序列，但要求模型具备强大的全局上下文理解能力。为此，&lt;strong&gt;BERT (Bidirectional Encoder Representations from Transformers)&lt;/strong&gt; 应运而生，其核心设计是&lt;strong&gt;仅采用 Transformer 的编码器部分（Encoder-only 架构）&lt;/strong&gt;，通过训练时的双向能力来捕捉深层次的语义关联。&lt;/p&gt;
&lt;h4&gt;BERT 的预训练&lt;/h4&gt;
&lt;p&gt;在实际训练中，为了让 BERT 有强大的文本理解能力，需要让 BERT 模型预先完成预训练任务。BERT 的预训练包含两大关键任务：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;双向掩码语言建模 (Masked Language Modeling, MLM)&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;本质上就是前面所讲述的选词填空。通过随机掩蔽输入文本中的部分单词（用 &lt;code&gt;[MASK]&lt;/code&gt; 替换）的方式，让模型需基于上下文去预测被掩蔽的词语。这一任务迫使模型同时学习词语左侧和右侧的上下文信息，从而获得更全面的语义表示。例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入：&quot;苹果公司今天发布了最新的 &amp;lt;MASK&amp;gt; 手机。&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模型可以同时看到 &lt;code&gt;&amp;lt;MASK&amp;gt;&lt;/code&gt; 左侧和右侧的上下文，它需要利用这些上下文信息来预测 &lt;code&gt;&amp;lt;MASK&amp;gt;&lt;/code&gt; 位置应该填入的词语（例如 &quot;iPhone&quot;）。&lt;/p&gt;
&lt;p&gt;经过一系列编码器层的处理后，模型会输出一个与输入序列长度相同的隐藏状态序列 &lt;code&gt;sequence_output: (batch_size, seq_len, d_model)&lt;/code&gt;。我们希望 &lt;code&gt;sequence_output&lt;/code&gt; 中的每个位置都和输入序列中对应位置的 token 相对应，像图像分割模型那样，对每个 token 位置的隐藏层状态都做一系列的线性层处理后，可以得到形状为 &lt;code&gt;(batch_size, seq_len, vocab_size)&lt;/code&gt; 的预测分布，其中 &lt;code&gt;vocab_size&lt;/code&gt; 是词表大小。对于 &lt;code&gt;&amp;lt;MASK&amp;gt;&lt;/code&gt; 位置的 token，我们可以直接从对应位置的预测分布中取出概率最高的词语作为模型的预测结果。&lt;/p&gt;
&lt;p&gt;训练的损失函数直接采用交叉熵损失函数即可，计算模型在 &lt;code&gt;&amp;lt;MASK&amp;gt;&lt;/code&gt; 位置的预测分布与真实标签之间的损失，并通过反向传播来优化模型参数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;下一句预测 (Next Sentence Prediction, NSP)&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;本质上属于一个二分类任务。输入连续的两个句子对，模型需判断第二个句子是否在原始文本中紧邻第一个句子（&lt;code&gt;yes&lt;/code&gt; 或 &lt;code&gt;no&lt;/code&gt;）。该任务帮助模型学习句子间的逻辑连贯性与语义关系，为后续处理涉及多句理解的复杂任务（如问答、文本分类中的长文本分析）奠定基础。&lt;/p&gt;
&lt;p&gt;在文本分类任务中，BERT 需要在输入序列的开头添加一个特殊的分类标记 &lt;code&gt;&amp;lt;CLS&amp;gt;&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;CLS&amp;gt; 这部电影非常精彩，演员的表演也很到位。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体到下一句预测的任务中，还需要在两个句子之间添加一个分隔标记 &lt;code&gt;&amp;lt;SEP&amp;gt;&lt;/code&gt;，例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;CLS&amp;gt; 这部电影非常精彩，演员的表演也很到位。 &amp;lt;SEP&amp;gt; 你觉得这部电影怎么样？ &amp;lt;SEP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个时候，得到了 &lt;code&gt;sequence_output: (batch_size, seq_len, d_model)&lt;/code&gt; 后，模型通过取出 &lt;code&gt;&amp;lt;CLS&amp;gt;&lt;/code&gt; 位置的隐藏层状态 &lt;code&gt;sequence_output[:, 0, :]&lt;/code&gt; 并进行线性变换和 Softmax 操作，就可以得到全局类别信息的预测分布了。在 NSP 任务中，这个预测分布是一个二分类分布，表示输入的第二个句子是否紧邻第一个句子。训练的损失函数亦可以选用交叉熵损失函数。尽管后续一些工作发现去除 NSP 或改用其他句子级任务效果更好，但理解 NSP 有助于掌握 BERT 的设计思想。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;多任务训练&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过上述任务的联合训练，BERT 可以获得强大的双向语义编码能力。更多有关预训练的内容，将会在后续章节中展开。&lt;/p&gt;
&lt;h4&gt;BERT 的微调&lt;/h4&gt;
&lt;p&gt;BERT 模型可以不只用于做选词填空和下一句预测，也可以做诸如情感分析、实体识别等其他的文本理解任务。然而，直接对一个没有经过预训练的 BERT 模型做这些更为高级的文本理解任务的训练，通常很难能得到一个表现良好的模型。这是因为这些任务通常需要模型具备对文本的深层次理解能力，而没有预训练的 BERT 模型在初始状态下并不具备这样的能力。&lt;/p&gt;
&lt;p&gt;因此，在实际应用中，往往需要预训练的、具有一定文本理解能力的 BERT 作为初始化的模型，然后再在这些更为高级的文本理解任务上训练，这个过程被称作&lt;strong&gt;微调 (Fine-tuning)&lt;/strong&gt;。通过微调，模型可以在预训练阶段学到的通用语言表示的基础上，进一步适应特定任务的需求，从而获得更好的性能。&lt;/p&gt;
&lt;p&gt;微调也将会在后续章节中展开介绍。&lt;/p&gt;
&lt;h4&gt;BERT 的特殊输入设计&lt;/h4&gt;
&lt;p&gt;与简单的 Transformer 编码器不同的是，BERT 的输入由三部分嵌入组成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;语言嵌入 (Word Embeddings)&lt;/strong&gt;：将输入文本中的每个 token 转换为一个固定维度的向量表示，捕捉词语的语义信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位置嵌入 (Position Embeddings)&lt;/strong&gt;：由于 Transformer 模型本身没有序列信息，位置嵌入用于为每个 token 提供位置信息，使模型能够区分不同位置的 token。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型嵌入 (Token Type Embeddings)&lt;/strong&gt;：在 BERT 的预训练任务中，输入通常是由两段文本拼接而成的，例如 &lt;code&gt;[CLS] Sentence A [SEP] Sentence B [SEP]&lt;/code&gt;，类型嵌入用于区分这两段文本中的 token，帮助模型理解它们之间的关系。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;动手实践：BERT 实现&lt;/h4&gt;
&lt;p&gt;构建一个 BERT 模型，这里依然选择 Pre-LN 的设计风格 (实际最早的 BERT 是 Post-LN 架构的)，编码器层的结构与之前介绍的 EncoderLayer 类似：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class BERTEncoderLayer(nn.Module):
    &quot;&quot;&quot;单个编码器层（Pre-LN 风格）&quot;&quot;&quot;
    def __init__(self, d_model: int, n_heads: int, d_ff: int = None, dropout: float = 0.1):
        super().__init__()
        self.self_attn = MultiHeadAttention(d_model, n_heads, dropout)
        self.ffn = FeedForward(d_model, d_ff, dropout)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)

    def forward(self, x, mask=None):
        # Pre-LN: 先归一化，再子层，然后残差
        residual = x
        x = self.norm1(x)
        x = self.self_attn(x, x, x, mask)
        x = self.dropout1(x)
        x = residual + x

        residual = x
        x = self.norm2(x)
        x = self.ffn(x)
        x = self.dropout2(x)
        x = residual + x
        return x

class BERTEncoder(nn.Module):
    &quot;&quot;&quot;编码器：由 N 个 EncoderLayer 堆叠&quot;&quot;&quot;
    def __init__(self, num_layers: int, d_model: int, n_heads: int,
                 d_ff: int = None, dropout: float = 0.1):
        super().__init__()
        self.layers = nn.ModuleList([
            BERTEncoderLayer(d_model, n_heads, d_ff, dropout)
            for _ in range(num_layers)
        ])
        self.norm = nn.LayerNorm(d_model)

    def forward(self, x, mask=None):
        for layer in self.layers:
            x = layer(x, mask)
        return self.norm(x)

class BERT(nn.Module):
    def __init__(self, vocab_size, num_classes=None, d_model=256, n_heads=8, num_layers=4, type_vocab_size=2, max_len=512):
        super().__init__()
        self.d_model = d_model
        self.word_embeddings = nn.Embedding(vocab_size, d_model)
        self.position_embeddings = nn.Embedding(max_len, d_model)
        self.token_type_embeddings = nn.Embedding(type_vocab_size, d_model)
        self.embedding_norm = nn.LayerNorm(d_model)
        self.embedding_dropout = nn.Dropout(0.1)
        
        self.encoder = BERTEncoder(num_layers, d_model, n_heads)
        
        # 1. 增强型 MLM Head
        self.mlm_head = nn.Sequential(
            nn.Linear(d_model, d_model),
            nn.GELU(),
            nn.LayerNorm(d_model),
            nn.Dropout(0.1),
            nn.Linear(d_model, vocab_size)
        )
        
        # 2. CLS Pooler (用于分类/NSP任务)
        self.pooler = nn.Sequential(
            nn.Linear(d_model, d_model),
            nn.Tanh()
        )
        self.classifier = nn.Linear(d_model, num_classes) if num_classes else None

    def forward(self, input_ids, token_type_ids=None, attention_mask=None, task=&apos;mlm&apos;):
        # input_ids: (batch, seq_len)
        # token_type_ids: (batch, seq_len)
        seq_len = input_ids.size(1)
        
        # 词嵌入 + 类型嵌入 + 位置嵌入
        x = self.word_embeddings(input_ids)
        
        if token_type_ids is None:
            token_type_ids = torch.zeros_like(input_ids)
        x = x + self.token_type_embeddings(token_type_ids)
        
        position_ids = torch.arange(seq_len, dtype=torch.long, device=input_ids.device)
        position_ids = position_ids.unsqueeze(0).expand_as(input_ids)
        x = x + self.position_embeddings(position_ids)
        
        x = self.embedding_norm(x)
        x = self.embedding_dropout(x)
        
        # attention_mask: (batch, seq_len) -&amp;gt; (batch,1,1,seq_len)
        if attention_mask is not None:
            attention_mask = attention_mask.unsqueeze(1).unsqueeze(2)
        
        sequence_output = self.encoder(x, attention_mask)  # (batch, seq_len, d_model)
        
        if task == &apos;mlm&apos;:
            return self.mlm_head(sequence_output)          # (batch, seq_len, vocab_size)
        else:  # classification / nsp
            cls_hidden = sequence_output[:, 0, :]           # 取 &amp;lt;CLS&amp;gt; 向量
            pooled_output = self.pooler(cls_hidden)         # 通过 Pooler
            return self.classifier(pooled_output)           # (batch, num_classes)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更多代码细节详见 &lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/tree/master/chapter-03-attention-translation/codes/models/bert.py&quot;&gt;codes/models/bert.py&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;训练原理：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;模型一开始是瞎猜的。但在训练时，我们会不断比对 &lt;code&gt;mask_prediction_id&lt;/code&gt; 和原句中原本的真实词，或者 &lt;code&gt;&amp;lt;CLS&amp;gt;&lt;/code&gt; token 对应的 logits 与真实标签之间的差异，计算&lt;strong&gt;交叉熵损失&lt;/strong&gt;，然后使用优化器反向传播，逐渐逼近人类的语言规律。&lt;/p&gt;
&lt;p&gt;需要注意的是，输入的 &lt;code&gt;mask&lt;/code&gt; 张量是一个 boolean 矩阵，用于指示哪些位置是 &lt;code&gt;&amp;lt;PAD&amp;gt;&lt;/code&gt;。&lt;code&gt;&amp;lt;MASK&amp;gt;&lt;/code&gt; 部分虽然是模型需要预测的目标，但它本身也是一个有效的 token，因此在 &lt;code&gt;mask&lt;/code&gt; 中应该被标记为 &lt;code&gt;True&lt;/code&gt;（即不被屏蔽）。&lt;/p&gt;
&lt;p&gt;训练部分代码详见 &lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/tree/master/chapter-03-attention-translation/codes/train_bert.py&quot;&gt;codes/train_bert.py&lt;/a&gt;，可以用下列命令一件运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python train_bert.py \
    --tokenizer_name &quot;bert-base-chinese&quot; \
    --datasets wiki news `
    --wiki_path &quot;data/corpus/wiki_zh&quot; \
    --news_path &quot;data/corpus/news2016zh_train.json&quot; \
    --tasks mlm nsp \
    --max_len 128 \
    --batch_size 32 \
    --epochs 5 \
    --lr 5e-5 \
    --d_model 768 \
    --n_heads 12 \
    --num_layers 12 \
    --log_interval 50 \
    --save_path &quot;checkpoints/bert_pretrain&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;使用 PyTorch &lt;code&gt;nn.TransformerEncoder&lt;/code&gt; 构建 BERT&lt;/h4&gt;
&lt;p&gt;PyTorch 的 &lt;code&gt;nn.TransformerEncoder&lt;/code&gt; 模块已经封装好了多层编码器的结构，我们可以直接利用它来构建 BERT 的编码器部分。下面是一个简化的 BERT 实现示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class BERT(nn.Module):
    def __init__(self, vocab_size, num_classes=None, d_model=256, n_heads=8, num_layers=4, type_vocab_size=2, max_len=512):
        super().__init__()
        self.d_model = d_model
        self.word_embeddings = nn.Embedding(vocab_size, d_model)
        self.position_embeddings = nn.Embedding(max_len, d_model)
        self.token_type_embeddings = nn.Embedding(type_vocab_size, d_model)
        self.embedding_norm = nn.LayerNorm(d_model)
        self.embedding_dropout = nn.Dropout(0.1)
        
        self.encoder = nn.TransformerEncoder(
            nn.TransformerEncoderLayer(
                d_model, n_heads, dim_feedforward=4*d_model, 
                dropout=0.1, batch_first=True, norm_first=True
            ),
            num_layers=num_layers
        )
        
        # 1. 增强型 MLM Head
        self.mlm_head = nn.Sequential(
            nn.Linear(d_model, d_model),
            nn.GELU(),
            nn.LayerNorm(d_model),
            nn.Dropout(0.1),
            nn.Linear(d_model, vocab_size)
        )
        
        # 2. CLS Pooler (用于分类/NSP任务)
        self.pooler = nn.Sequential(
            nn.Linear(d_model, d_model),
            nn.Tanh()
        )
        self.classifier = nn.Linear(d_model, num_classes) if num_classes else None

    def forward(self, input_ids, token_type_ids=None, attention_mask=None, task=&apos;mlm&apos;):
        seq_len = input_ids.size(1)
        x = self.word_embeddings(input_ids)
        if token_type_ids is None:
            token_type_ids = torch.zeros_like(input_ids)
        x = x + self.token_type_embeddings(token_type_ids)
        
        positions = torch.arange(seq_len, device=input_ids.device).unsqueeze(0)
        x = x + self.position_embeddings(positions)
        x = self.embedding_norm(x)
        x = self.embedding_dropout(x)
        
        # PyTorch TransformerEncoder 的 src_key_padding_mask: True 表示屏蔽
        # 如果你的 attention_mask 是常规的 (1=有效, 0=无效)，需要取反
        padding_mask = ~attention_mask if attention_mask is not None else None
        
        sequence_output = self.encoder(x, src_key_padding_mask=padding_mask)
        
        if task == &apos;mlm&apos;:
            return self.mlm_head(sequence_output)
        else:
            cls_hidden = sequence_output[:, 0, :]
            pooled_output = self.pooler(cls_hidden)
            return self.classifier(pooled_output)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里需要注意的是，在 &lt;code&gt;nn.TransformerEncoder&lt;/code&gt; 中，它的逻辑是认为 &lt;code&gt;mask&lt;/code&gt; 为 &lt;code&gt;1&lt;/code&gt; (或 &lt;code&gt;True&lt;/code&gt;) 的位置是需要被屏蔽的。如果你的 &lt;code&gt;attention_mask&lt;/code&gt; 定义为 &lt;code&gt;True&lt;/code&gt; 表示有效位置（非填充），那么在调用时需要对掩码进行逻辑取反处理。&lt;/p&gt;
&lt;h2&gt;文本生成&lt;/h2&gt;
&lt;p&gt;文本理解的目标是“读懂”文本，而文本生成的目标则是“写出”文本。生成任务要求模型&lt;strong&gt;自主地产生一段连贯的、符合语义的文本序列&lt;/strong&gt;，典型应用包括&lt;strong&gt;文本续写、对话生成、代码生成&lt;/strong&gt;等。&lt;/p&gt;
&lt;p&gt;与理解任务不同，生成任务采用的是&lt;strong&gt;自回归 (Autoregressive)&lt;/strong&gt; 的方式——模型根据已经生成的前缀，逐词预测下一个词。每生成一个新词，就将其追加到输入中，再继续预测下一个，直到生成结束标记或达到最大长度。&lt;/p&gt;
&lt;p&gt;数学上，给定一个初始文本序列 $T_{\text{prompt}} = [t_1, t_2, ..., t_N]$，模型需要不断预测并生成后续的 $M$ 个 tokens：
$$
f_{\text{generation}} : \mathcal{C}^N \rightarrow \mathcal{C}^M, \quad T_{\text{prompt}} \mapsto T_{\text{generated}}
$$&lt;/p&gt;
&lt;h3&gt;GPT：Decoder-only 模型&lt;/h3&gt;
&lt;p&gt;生成任务最常用的架构是 &lt;strong&gt;Decoder-only&lt;/strong&gt;（仅解码器），代表模型为 &lt;strong&gt;GPT&lt;/strong&gt; 系列。它只保留了 Transformer 的解码器部分，并去掉了编码器‑解码器的交叉注意力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么不需要编码器？&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;要回答这个问题，需要先区分两类生成任务：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;条件生成&lt;/strong&gt;（如机器翻译、文本摘要）：条件生成，简单来说，就是文本的生成还需要参考一个外部的条件指示信息，像 seq2seq 类型的任务（例如文本翻译），编码器提取的源语言序列的全局上下文中间表示就是一个典型的条件信息。给定一个&lt;strong&gt;外部的源序列&lt;/strong&gt;（源语言句子、长文档），模型必须根据这个源序列生成目标序列。这也是为什么 seq2seq 的建模任务需要使用一个&lt;strong&gt;编码器&lt;/strong&gt;将源序列压缩成中间表示，再让解码器再通过&lt;strong&gt;交叉注意力&lt;/strong&gt;从中提取信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;无条件生成&lt;/strong&gt;（如文本续写、故事生成）：模型只需要根据&lt;strong&gt;已经生成的文本前缀&lt;/strong&gt;继续往下写，没有额外的“源输入”。GPT 做的正是这一类任务——它只依赖当前已生成的 token，所有必要的信息都蕴含在这些 token 的上下文中。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果只让 GPT 做文本生成任务，那么它是不需要处理外部源序列，所以&lt;strong&gt;编码器是多余的&lt;/strong&gt;。仅用解码器，配合&lt;strong&gt;因果注意力&lt;/strong&gt;（每个 token 只能看到左侧的 token），就足以让模型学习到语言的内在规律：给定前文，预测后文。这也是为什么 GPT 是一个 Decoder-only 的架构。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心特点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;因果注意力 (Causal Attention)&lt;/strong&gt;：每个位置只能看到它左侧的 token（包括自己），不能看到右侧。这通过一个&lt;strong&gt;下三角掩码&lt;/strong&gt;实现，确保自回归生成时不会“作弊”窥探未来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位置编码&lt;/strong&gt;：通常使用可学习的位置嵌入，因为生成序列长度不固定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;训练任务&lt;/strong&gt;：语言建模 (Language Modeling) —— 给定文本序列，预测下一个 token。所有位置的损失都会被计算（不同于 BERT 只计算掩码位置）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;动手实践：Decoder-only 生成模型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下面用 PyTorch 实现一个简单的 GPT 模型，用于文本生成任务。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class GPTDecoderLayer(nn.Module):
    &quot;&quot;&quot;仅包含自注意力 + 前馈网络的解码器层&quot;&quot;&quot;
    def __init__(self, d_model: int, n_heads: int, d_ff: int = None, dropout: float = 0.1):
        super().__init__()
        self.self_attn = MultiHeadAttention(d_model, n_heads, dropout)
        self.ffn = FeedForward(d_model, d_ff, dropout)
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)

    def forward(self, x, mask=None, rotary_emb=None):
        # 自注意力 + 残差（Pre-LN）
        residual = x
        x = self.norm1(x)
        x = self.self_attn(x, x, x, mask, rotary_emb=rotary_emb)
        x = self.dropout1(x)
        x = residual + x

        # 前馈网络 + 残差
        residual = x
        x = self.norm2(x)
        x = self.ffn(x)
        x = self.dropout2(x)
        x = residual + x
        return x


class GPTDecoder(nn.Module):
    &quot;&quot;&quot;堆叠多层 GPTDecoderLayer&quot;&quot;&quot;
    def __init__(self, num_layers: int, d_model: int, n_heads: int,
                 d_ff: int = None, dropout: float = 0.1):
        super().__init__()
        self.layers = nn.ModuleList([
            GPTDecoderLayer(d_model, n_heads, d_ff, dropout)
            for _ in range(num_layers)
        ])
        self.norm = nn.LayerNorm(d_model)

    def forward(self, x, mask=None, rotary_emb=None):
        for layer in self.layers:
            x = layer(x, mask, rotary_emb=rotary_emb)
        return self.norm(x)


class GPT(nn.Module):
    &quot;&quot;&quot;
    GPT 实现：
    1. 使用 RoPE (Rotary Positional Embedding) 代替绝对位置编码
    2. 加入可学习的 Position Embedding (作为示例)
    3. 规范的权重初始化 (GPT-2 风格)
    4. Pre-LN 风格
    &quot;&quot;&quot;
    def __init__(self, vocab_size: int, d_model: int = 256, n_heads: int = 8,
                 num_layers: int = 4, max_seq_len: int = 512, dropout: float = 0.1):
        super().__init__()
        self.d_model = d_model
        self.n_heads = n_heads
        self.d_k = d_model // n_heads
        
        self.token_emb = nn.Embedding(vocab_size, d_model)
        self.pos_emb = nn.Embedding(max_seq_len, d_model)
        
        self.decoder = GPTDecoder(num_layers, d_model, n_heads, dropout=dropout)
        self.lm_head = nn.Linear(d_model, vocab_size)

        # 动态生成 RoPE 缓存 (sin/cos)
        # 注意：RoPE 实际上作用在 head dimension (d_k) 上
        self.register_buffer(&quot;inv_freq&quot;, 1.0 / (10000**(torch.arange(0, self.d_k, 2).float() / self.d_k)))

        # 初始化权重
        self.apply(self._init_weights)

    def _init_weights(self, module):
        &quot;&quot;&quot;GPT-2 风格的权重初始化&quot;&quot;&quot;
        if isinstance(module, nn.Linear):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
            if module.bias is not None:
                torch.nn.init.zeros_(module.bias)
        elif isinstance(module, nn.Embedding):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
        elif isinstance(module, nn.LayerNorm):
            torch.nn.init.zeros_(module.bias)
            torch.nn.init.ones_(module.weight)

    def get_rotary_emb(self, seq_len, device):
        &quot;&quot;&quot;计算旋转位置编码向量&quot;&quot;&quot;
        t = torch.arange(seq_len, device=device).float()
        freqs = torch.outer(t, self.inv_freq)  # (seq_len, d_k/2)
        # 旋转位置编码需要 cos(theta) 和 sin(theta) 对齐到 d_k
        # 这里使用 interleave 或 cat [freq, freq] 取决于 apply_rotary_emb 的实现
        # 我们的 apply_rotary_emb 使用了 split dim=-1，所以这里用 cat
        emb = torch.cat([freqs, freqs], dim=-1) # (seq_len, d_k)
        cos = emb.cos().view(1, 1, seq_len, self.d_k) # (1, 1, seq_len, d_k)
        sin = emb.sin().view(1, 1, seq_len, self.d_k) # (1, 1, seq_len, d_k)
        return cos, sin

    def forward(self, input_ids, attention_mask=None):
        &quot;&quot;&quot;
        input_ids: (batch, seq_len)
        attention_mask: (batch, seq_len), True 表示非 padding（可选）
        &quot;&quot;&quot;
        batch_size, seq_len = input_ids.shape
        
        # 1. 词嵌入 + 可学习位置嵌入
        tok_x = self.token_emb(input_ids)
        # 生成 [0, 1, 2, ..., seq_len-1] 并映射到 embedding 层
        pos_ids = torch.arange(seq_len, device=input_ids.device).unsqueeze(0)
        pos_x = self.pos_emb(pos_ids)           # (1, seq_len, d_model)
        x = tok_x + pos_x

        # 2. 准备 RoPE (旋转位置编码)
        cos, sin = self.get_rotary_emb(seq_len, input_ids.device)

        # 3. 生成因果掩码（下三角，True 表示允许关注）
        causal_mask = torch.tril(torch.ones(seq_len, seq_len, dtype=torch.bool, device=input_ids.device))
        causal_mask = causal_mask.unsqueeze(0).unsqueeze(0)   # (1, 1, seq_len, seq_len)

        # 4. 合并 padding mask
        if attention_mask is not None:
            # attention_mask: (batch, seq_len) -&amp;gt; (batch, 1, 1, seq_len)
            padding_mask = attention_mask.unsqueeze(1).unsqueeze(2)
            mask = padding_mask &amp;amp; causal_mask
        else:
            mask = causal_mask

        # 5. 解码器并传入 RoPE
        x = self.decoder(x, mask, (cos, sin))

        # 6. 输出层
        logits = self.lm_head(x)                # (batch, seq_len, vocab_size)
        return logits
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;训练方式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于输入文本 &lt;code&gt;&quot;I love you&quot;&lt;/code&gt;，将其作为输入，将向右偏移一位的 &lt;code&gt;&quot;love you&quot;&lt;/code&gt; 作为标签（通常用 &lt;code&gt;-100&lt;/code&gt; 忽略起始位置）。这里模型的输出和损失计算与文本翻译任务中的 Transformer 类似，损失函数选择为计算所有位置的交叉熵。&lt;/p&gt;
&lt;p&gt;具体训练代码参考 &lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/tree/master/chapter-03-attention-translation/codes/train_gpt.py&quot;&gt;codes/train_gpt.py&lt;/a&gt;，可以用下列命令一键运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python train_gpt.py \
    --datasets wiki news \
    --wiki_path &quot;data/wiki&quot; \
    --news_path &quot;data/news&quot; \
    --batch_size 4 \
    --max_len 512 \
    --d_model 768 \
    --n_heads 12 \
    --num_layers 12 \
    --save_path &quot;checkpoints/gpt&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;文本生成（推理阶段）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;GPT 完全放弃了双向互看的编码器，它是一个&lt;strong&gt;叠了非常多层的纯单向解码器&lt;/strong&gt;。
它的任务非常纯粹：&lt;strong&gt;根据前 $N$ 个词，预测第 $N+1$ 个词(Next Token Prediction)。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因此在推理阶段，模型需要根据一个初始的文本前缀（prompt）来生成后续文本。生成过程是自回归的：每次预测下一个 token 后，将其追加到输入中，再继续预测下一个 token，直到生成结束标记或达到最大长度。&lt;/p&gt;
&lt;p&gt;这里我们还引入了&lt;strong&gt;采样策略&lt;/strong&gt;，包括 Temperature 缩放、Top-K 采样和 Top-P (Nucleus) 采样，以控制生成文本的多样性和质量。下面是一个改进后的生成函数示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def top_k_top_p_filtering(logits, top_k=0, top_p=0.0, filter_value=-float(&apos;Inf&apos;)):
    &quot;&quot;&quot;抽取采样策略：Top-K 和 Nucleus (Top-P) 采样&quot;&quot;&quot;
    top_k = min(top_k, logits.size(-1))
    if top_k &amp;gt; 0:
        # 过滤掉不在 Top-K 列表中的 tokens
        indices_to_remove = logits &amp;lt; torch.topk(logits, top_k)[0][..., -1, None]
        logits[indices_to_remove] = filter_value

    if top_p &amp;gt; 0.0:
        sorted_logits, sorted_indices = torch.sort(logits, descending=True)
        cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)

        # 移除累积概率超过 top_p 的 tokens
        sorted_indices_to_remove = cumulative_probs &amp;gt; top_p
        # 将第一个超过阈值的 token 也保留下来（Shift right）
        sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
        sorted_indices_to_remove[..., 0] = 0

        # 将需要移除的索引映射回原始 logits 的位置
        for i in range(logits.size(0)):
            indices_to_remove = sorted_indices[i][sorted_indices_to_remove[i]]
            logits[i][indices_to_remove] = filter_value
    return logits


@torch.no_grad()
def generate(model, prompt_ids, max_new_tokens=50, temperature=1.0, top_k=50, top_p=0.9, eos_token_id=None):
    &quot;&quot;&quot;
    改进后的生成函数：
    1. 支持 Temperature 缩放 (控制创造性)
    2. 支持 Top-K / Top-P (Nucleus) 过滤 (防止崩坏)
    3. 支持 Batch 生成
    &quot;&quot;&quot;
    model.eval()
    generated = prompt_ids
    
    for _ in range(max_new_tokens):
        # GPT 输入长度通常有上限 (max_seq_len)
        input_ids = generated[:, -512:] 
        
        logits = model(input_ids)                        # (batch, seq_len, vocab_size)
        next_token_logits = logits[:, -1, :] / temperature
        
        # 采样过滤
        filtered_logits = top_k_top_p_filtering(next_token_logits.clone(), top_k=top_k, top_p=top_p)
        probs = F.softmax(filtered_logits, dim=-1)
        
        # 采样获取下一个 token
        next_token = torch.multinomial(probs, num_samples=1) # (batch, 1)
        
        generated = torch.cat([generated, next_token], dim=1)
        
        if eos_token_id is not None and (next_token == eos_token_id).all():
            break
            
    return generated
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;使用 PyTorch &lt;code&gt;nn.TransformerEncoder&lt;/code&gt; 构建 GPT&lt;/h4&gt;
&lt;p&gt;类似于 BERT，我们也可以利用 PyTorch 封装好的 &lt;code&gt;nn.TransformerEncoder&lt;/code&gt; 来快速搭建 GPT。由于 GPT 是 Decoder-only 架构，本质上就是一个带有&lt;strong&gt;因果掩码 (Causal Mask)&lt;/strong&gt; 的 Transformer 编码器。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class GPT(nn.Module):
    def __init__(self, vocab_size, d_model=256, n_heads=8, num_layers=4, max_len=512):
        super().__init__()
        self.d_model = d_model
        self.token_embeddings = nn.Embedding(vocab_size, d_model)
        self.position_embeddings = nn.Embedding(max_len, d_model)
        self.embedding_norm = nn.LayerNorm(d_model)
        self.embedding_dropout = nn.Dropout(0.1)
        
        # GPT 使用 TransformerEncoder 层，但配合因果掩码实现 Decoder 功能
        self.encoder = nn.TransformerEncoder(
            nn.TransformerEncoderLayer(
                d_model, n_heads, dim_feedforward=4*d_model, 
                dropout=0.1, batch_first=True, norm_first=True
            ),
            num_layers=num_layers
        )
        
        self.lm_head = nn.Linear(d_model, vocab_size)

    def forward(self, input_ids, attention_mask=None):
        seq_len = input_ids.size(1)
        
        # 1. 嵌入层
        x = self.token_embeddings(input_ids)
        positions = torch.arange(seq_len, device=input_ids.device).unsqueeze(0)
        x = x + self.position_embeddings(positions)
        x = self.embedding_norm(x)
        x = self.embedding_dropout(x)
        
        # 2. 构造因果掩码 (Causal Mask)
        # nn.TransformerEncoder 要求掩码中 True 表示屏蔽 (mask)
        causal_mask = torch.triu(torch.ones(seq_len, seq_len, device=input_ids.device), diagonal=1).bool()
        
        # 3. 编码器处理 (Padding Mask 同样 True 表示屏蔽)
        padding_mask = ~attention_mask if attention_mask is not None else None
        x = self.encoder(x, mask=causal_mask, src_key_padding_mask=padding_mask)
        
        # 4. 输出层
        return self.lm_head(x)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;这里的 GPT 是 OpenAI 的网页版 GPT 模型吗？&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;不是的。&lt;/strong&gt; 我们上面实现的 GPT 是一个&lt;strong&gt;基础的语言模型&lt;/strong&gt;，它只具备最核心的能力：&lt;strong&gt;给定上文，预测下一个词&lt;/strong&gt;（自回归生成）。而在网页上使用的 ChatGPT 或 GPT-4o 等产品，除了基础的文本生成能力外，还拥有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;指令跟随&lt;/strong&gt;：理解用户意图，按指令回答问题、写代码、总结文档等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多轮对话记忆&lt;/strong&gt;：记住对话历史，维持上下文连贯性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;搜索增强&lt;/strong&gt;：可以联网搜索最新信息（需人工开启）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工具调用&lt;/strong&gt;：调用计算器、代码解释器、外部 API 等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全与对齐&lt;/strong&gt;：拒绝有害请求，输出符合人类价值观。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些高级能力并非单纯的 Decoder-only 架构就能实现，而是在&lt;strong&gt;极大规模预训练&lt;/strong&gt;（海量文本、数千亿参数）的基础上，再经过&lt;strong&gt;指令微调&lt;/strong&gt;和&lt;strong&gt;人类反馈强化学习（RLHF）&lt;/strong&gt; 才能获得。此外，像联网搜索、记忆管理等还需要&lt;strong&gt;智能体（Agent）框架&lt;/strong&gt;的支持（这些内容将在后续章节中展开）。&lt;/p&gt;
&lt;h4&gt;那 GPT 能做 Seq2Seq 任务（比如翻译）吗？&lt;/h4&gt;
&lt;p&gt;可以，但需要特殊训练。原生 GPT 是纯粹的无条件生成模型（只根据上文续写）。若要它完成“中文→英文”翻译，需要：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在预训练阶段将翻译任务建模为“输入：请把‘你好’翻译成英文 → 输出：‘Hello’”。模型通过海量这样的例子学会翻译模式。&lt;/li&gt;
&lt;li&gt;再进行&lt;strong&gt;指令微调&lt;/strong&gt;，让模型理解用户给出的明确指令（如“Translate to English: 你好”）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;即使如此，由于 Decoder-only 架构缺少对源序列的双向编码，对于复杂的长句翻译，效果通常不如专门的 Encoder-Decoder 模型（如 Transformer 原始架构）。但对于大多数日常场景，经过充分训练的 GPT（如 GPT-4）已经展现出极强的翻译能力，甚至超越了一些专用翻译模型。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小结：我们这里的 GPT 是一个&lt;strong&gt;教学用的小型语言模型&lt;/strong&gt;，核心是理解 Decoder-only 的生成机制。而网页版 GPT 是经过千亿级参数、海量数据、多种技术栈（预训练 + 微调 + RLHF + 工具调用）打磨而成的&lt;strong&gt;产品级智能体&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Transformer 三大架构对比&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;架构类型&lt;/th&gt;
&lt;th&gt;核心模型&lt;/th&gt;
&lt;th&gt;适用任务&lt;/th&gt;
&lt;th&gt;信息流动(可见性)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;完整编码器-解码器&lt;/strong&gt; (Encoder-Decoder)&lt;/td&gt;
&lt;td&gt;原始 Transformer, T5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;中英翻译&lt;/strong&gt;、摘要生成&lt;/td&gt;
&lt;td&gt;编码器双向全可见；解码器只能看自己之前的部分+完整的编码器信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;仅编码器&lt;/strong&gt; (Encoder-Only)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;BERT&lt;/strong&gt;, RoBERTa&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;选词填空&lt;/strong&gt;、文本分类&lt;/td&gt;
&lt;td&gt;双向上下文全可见(我知道整句的话结构)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;仅解码器&lt;/strong&gt; (Decoder-Only)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;GPT&lt;/strong&gt; 系列, LLaMA&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;自回归文本生成&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;仅单向/自回归可见(我只能根据过去预测未来)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;练习部分&lt;/h2&gt;
&lt;h3&gt;任务一：图像数据上的 Transformer - Vision Transformer 模型&lt;/h3&gt;
&lt;h4&gt;背景知识&lt;/h4&gt;
&lt;p&gt;长期以来，卷积型神经网络（CNN-based）是计算机视觉的主流。自从 Transformer 模型问世并在 NLP 任务上大放异彩之后，有人好奇，能否把 Transformer 架构搬到图像处理领域中去呢？2020 年，Google 提出了 &lt;strong&gt;Vision Transformer (ViT)&lt;/strong&gt;，证明了纯 Transformer Encoder 结构在图像分类任务上可以超越长期以来的霸主卷积型神经网络。&lt;/p&gt;
&lt;p&gt;ViT 的核心思想是将图像分割成固定大小的 &lt;strong&gt;图像块 (patch)&lt;/strong&gt;，然后将每个块线性投影为向量，从而将二维的图像转换为一个个的一维图像块 patch 序列，并叠加可学习的位置编码以保留空间信息，随后输入标准 Transformer 编码器。实验证明，在 JFT-300M 等超大规模数据集上预训练后，ViT 在 ImageNet 等分类任务上能够达到甚至超越当时最先进的卷积型神经网络。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ViT 工作流程&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将输入图像 $ x \in \mathbb{R}^{H \times W \times C} $ 划分为 $ N = \frac{HW}{P^2} $ 个大小为 $ P \times P $ 的 patch。&lt;/li&gt;
&lt;li&gt;将每个 patch 展平并线性投影到维度 $ D $（称为 patch embedding）。&lt;/li&gt;
&lt;li&gt;在序列开头添加一个可学习的 &lt;code&gt;[CLS]&lt;/code&gt; token，用于最终的分类输出。&lt;/li&gt;
&lt;li&gt;加上可学习的位置编码（或正弦余弦编码）。&lt;/li&gt;
&lt;li&gt;输入 Transformer 编码器（与 BERT 相同，Encoder-only）。&lt;/li&gt;
&lt;li&gt;取 &lt;code&gt;[CLS]&lt;/code&gt; 位置的输出，经过 MLP 分类头得到类别预测。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;优势&lt;/strong&gt;：ViT 打破了 CNN 的局部归纳偏置，能够通过自注意力捕获全局依赖。在大规模数据上预训练后，表现优异。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/VisionTransformer.png&quot;].src} alt=&quot;ViT 架构图&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;任务目标&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1. 参考 &lt;a href=&quot;https://arxiv.org/abs/2010.11929&quot;&gt;Vision Transformer 的论文&lt;/a&gt;，实现一个基础的 ViT 模型。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 在 &lt;a href=&quot;https://www.image-net.org/&quot;&gt;ImageNet 数据集&lt;/a&gt;上训练 ViT，并测试该模型能否达到合理的分类准确率。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 在 Caltech101 数据集上训练 ViT，并与 ResNet-50 进行性能对比。为什么在 Caltech101 上 ViT 的表现反而不如 ResNet-50？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 卷积型神经网络真的不适合大型数据集吗？&lt;a href=&quot;https://liuzhuang13.github.io/&quot;&gt;有位年轻人&lt;/a&gt;不这么认为，并提出了 &lt;a href=&quot;https://www.image-net.org/&quot;&gt;ConvNeXt&lt;/a&gt;，阅读 ConvNeXt 的论文，分析它对卷积网络做了哪些改进，使得在大型数据集上能够与 ViT 竞争？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 既然 ViT 是 Encoder-only 架构，那么是否也可以用类似 Decoder-only 的方式来做图像生成任务呢？如果可以，应该如何设计这样的模型？&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;任务二：跨越内存墙 - FlashAttention&lt;/h3&gt;
&lt;h4&gt;背景知识&lt;/h4&gt;
&lt;h5&gt;注意力计算的瓶颈&lt;/h5&gt;
&lt;p&gt;标准 Transformer 中的缩放点积注意力计算如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算得分矩阵 $ S = QK^T \in \mathbb{R}^{N \times N} $&lt;/li&gt;
&lt;li&gt;对 $ S $ 按行做 Softmax，得到注意力权重 $ P $&lt;/li&gt;
&lt;li&gt;计算输出 $ O = PV $&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中 $ N $ 是序列长度。每一步都需要在 GPU 显存（HBM）中分配并存储 $ N \times N $ 的中间矩阵（通常为 float32/16），导致 &lt;strong&gt;显存复杂度 $O(N^2)$&lt;/strong&gt; 和 &lt;strong&gt;大量 HBM 读写&lt;/strong&gt;。当 $ N = 4096 $ 时，仅 $ S $ 就占用 64MB；当 $ N = 16384 $ 时，占用 1GB。更严重的是，频繁读写 HBM 的速度远慢于 GPU 计算速度，形成“内存墙”（Memory Wall）。&lt;/p&gt;
&lt;p&gt;在 GPU 的体系结构中，GPU 上是有一个非常快的片上高速 Cache - SRAM 的，不过它的容量非常有限（通常几十 KB 到几百 KB），远小于 HBM 的容量（GB 级）。&lt;/p&gt;
&lt;h5&gt;FlashAttention 的核心思想&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;FlashAttention&lt;/strong&gt; 就是考虑到了 SRAM 的优点和容量限制的。它通过 &lt;strong&gt;平铺（Tiling）&lt;/strong&gt; 和 &lt;strong&gt;算子融合（Kernel Fusion）&lt;/strong&gt; 加速了注意力计算：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将 $ Q, K, V $ 分成小块（例如 64×64），每次只将一小块载入 GPU 的 &lt;strong&gt;SRAM（共享内存）&lt;/strong&gt; 中计算。&lt;/li&gt;
&lt;li&gt;在 SRAM 内完成整个注意力计算（包括 Softmax），并直接累加到最终输出 $ O $ 上，&lt;strong&gt;从不写出完整的 $ N \times N $ 矩阵&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;通过 &lt;strong&gt;在线 Softmax&lt;/strong&gt; 算法，在分块计算时动态维护每个行的最大值和归一化分母，保证数学等价。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;FlashAttention 将显存复杂度降为 $ O(N) $，速度提升 2~4 倍。更多细节可参考原始论文 &lt;a href=&quot;https://arxiv.org/abs/2205.14135&quot;&gt;FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在线 Softmax 原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;假设我们要计算一行 $q_i$ 对所有 $k_j$ 的注意力。传统做法需要先拿到所有 $s_{ij} = q_i \cdot k_j$ 才能计算 Softmax。但是在线 Softmax 允许我们分块处理——每处理一个块，就根据当前块的最大值和指数和更新全局最大值和分母。&lt;/p&gt;
&lt;p&gt;对于单行，设已经处理了前 $t$ 个块，记录了当前全局最大值 $m$ 和指数和 $l$。当新块到来时，计算该块内的最大值 $m_{\text{new}}$ 和指数和 $l_{\text{new}}$（指数减去新最大值），然后合并：&lt;/p&gt;
&lt;p&gt;$$
m_{\text{global}} \leftarrow \max(m_{\text{global}}, m_{\text{new}})
$$
$$
l_{\text{global}} \leftarrow l_{\text{global}} \cdot e^{m_{\text{global}} - m_{\text{global}}^{\text{old}}} + l_{\text{new}}
$$&lt;/p&gt;
&lt;p&gt;最后，每个块的输出贡献需要乘上相应的缩放因子 $e^{m_{\text{global}} - m_{\text{global}}^{\text{old}}}$ 进行修正。实际实现时，通常同时处理多个行（一个块）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代码实现（前向）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
import math

def flash_attention_forward(Q, K, V, block_size=64):
    &quot;&quot;&quot;
    模拟 FlashAttention 的分块前向计算（不写出完整 S 矩阵）
    Q, K, V: (batch, seq_len, head_dim)
    返回: O (batch, seq_len, head_dim)
    &quot;&quot;&quot;
    batch, N, d = Q.shape
    # 初始化输出 O 和用于在线 Softmax 的辅助变量
    O = torch.zeros_like(Q)
    # 每个样本每行的最大值和指数和
    m = torch.full((batch, N), -float(&apos;inf&apos;), device=Q.device)   # 全局最大值
    l = torch.zeros((batch, N), device=Q.device)                 # 指数和
    
    # 按块遍历 K, V（列块）
    for i in range(0, N, block_size):
        k_block = K[:, i:i+block_size, :]   # (batch, block_size, d)
        v_block = V[:, i:i+block_size, :]
        # 计算当前块对所有行的分数（不分配完整矩阵，仅计算一个块）
        # Q 与 k_block 的点积，形状 (batch, N, block_size)
        scores = torch.matmul(Q, k_block.transpose(-2, -1)) / math.sqrt(d)
        # 当前块内的最大值
        m_block = scores.max(dim=-1, keepdim=True)[0]  # (batch, N, 1)
        # 更新全局最大值和指数和
        m_new = torch.maximum(m, m_block.squeeze(-1))
        # 对于每个样本的每行，需要计算修正因子
        # 原始公式：l_new = l * exp(m - m_new) + sum(exp(scores - m_new.unsqueeze(-1)))
        # 注意 m 是上一轮全局最大值，m_new 是新的全局最大值
        l = l * torch.exp(m - m_new) + (scores - m_new.unsqueeze(-1)).exp().sum(dim=-1)
        # 维护 O 的尺度对齐
        # O_new = O * exp(m - m_new) + exp(scores - m_new.unsqueeze(-1)) * v_block
        O = O * torch.exp(m - m_new).unsqueeze(-1) + torch.matmul((scores - m_new.unsqueeze(-1)).exp(), v_block)
        m = m_new
        # 当前块内的注意力权重已在上面计算并累加到 O
    
    # 最终归一化
    O = O / l.unsqueeze(-1)
    return O
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;验证&lt;/strong&gt;：与标准注意力输出比较，确保数值一致。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import time

def standard_attention(Q, K, V):
    scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(Q.size(-1))
    attn = torch.softmax(scores, dim=-1)
    return torch.matmul(attn, V)

# 随机数据测试
torch.manual_seed(42)
batch, N, d = 4, 1024, 64
Q = torch.randn(batch, N, d)
K = torch.randn(batch, N, d)
V = torch.randn(batch, N, d)

# 预热
_ = standard_attention(Q, K, V)

# 测试 Standard Attention
start_std = time.time()
for _ in range(10):
    out_std = standard_attention(Q, K, V)
print(f&quot;Standard Attention avg time: {(time.time() - start_std) / 10:.6f}s&quot;)


# 预热
_ = flash_attention_forward(Q, K, V, block_size=64)

# 测试 Flash Attention
start_flash = time.time()
for _ in range(10):
    out_flash = flash_attention_forward(Q, K, V, block_size=64)
print(f&quot;Flash Attention (Python Sim) avg time: {(time.time() - start_flash) / 10:.6f}s&quot;)

print(&quot;Max diff:&quot;, (out_flash - out_std).abs().max().item())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Standard Attention avg time: 0.003153s
Flash Attention (Python Sim) avg time: 0.023555s
Max diff: 1.6391277313232422e-07
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;自定义 PyTorch 算子&lt;/h5&gt;
&lt;p&gt;PyTorch 中提供了 &lt;code&gt;torch.autograd.Function&lt;/code&gt; 接口用于自定义可微分的算子。其中 &lt;code&gt;forward&lt;/code&gt; 方法定义前向计算，&lt;code&gt;backward&lt;/code&gt; 方法定义反向传播。这些算子可以直接在 &lt;code&gt;nn.Module&lt;/code&gt; 中调用，并参与前向传播和反向自动求导的计算图中。下面我们以一个简单的 &lt;strong&gt;逐元素乘幂&lt;/strong&gt; 为例，展示如何编写自定义算子。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class PowerFunction(torch.autograd.Function):
    @staticmethod
    def forward(ctx, x, exponent):
        ctx.save_for_backward(x)
        ctx.exponent = exponent
        return x ** exponent

    @staticmethod
    def backward(ctx, grad_output): # forward 中有多少个输出参数，这里就有多少输入参数
        x, = ctx.saved_tensors
        exponent = ctx.exponent
        grad_input = exponent * (x ** (exponent - 1)) * grad_output
        # 注意 return 的时候，在 forward 中有多少输入参数，这里就要有多少返回值，且顺序对应
        return grad_input, None   # 对 exponent 无梯度

def power(x, exponent):
    return PowerFunction.apply(x, exponent)

# 测试
x = torch.randn(3, requires_grad=True)
y = power(x, 2)
y.sum().backward()
print(torch.allclose(x.grad, 2 * x))  # 应等于 2*x，验证梯度正确性
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;C++ 扩展编写 Softmax 算子&lt;/h5&gt;
&lt;p&gt;PyTorch 允许用 C++ 编写算子并编译成动态库，通过 &lt;code&gt;torch.utils.cpp_extension&lt;/code&gt; 加载。下面演示如何实现一个 CPU 版本的 Softmax 算子。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤 1：编写 C++ 源文件 &lt;code&gt;softmax_cpu.cpp&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;torch/extension.h&amp;gt;
#include &amp;lt;vector&amp;gt;

// 前向传播的 Softmax 计算
torch::Tensor softmax_cpu_forward(torch::Tensor input) {
    auto last_dim = input.dim() - 1;
    auto max_vals = std::get&amp;lt;0&amp;gt;(input.max(last_dim, /*keepdim=*/true));
    auto exp_input = torch::exp(input - max_vals);
    auto sum_exp = exp_input.sum(last_dim, /*keepdim=*/true);
    return exp_input / sum_exp;
}

// 反向传播的梯度计算
torch::Tensor softmax_cpu_backward(torch::Tensor grad_output, torch::Tensor output) {
    auto grad_input = output * (grad_output - (output * grad_output).sum(-1, true));
    return grad_input;
}

// 封装接口
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
    m.def(&quot;softmax_forward&quot;, &amp;amp;softmax_cpu_forward, &quot;CPU Softmax Forward&quot;);
    m.def(&quot;softmax_backward&quot;, &amp;amp;softmax_cpu_backward, &quot;CPU Softmax Backward&quot;);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成上述代码的编写后，将这段 C++ 代码保存为 &lt;code&gt;softmax_cpu.cpp&lt;/code&gt;，然后编写用于编译封装的 &lt;code&gt;setup.py&lt;/code&gt; 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# setup.py
from setuptools import setup
from torch.utils.cpp_extension import BuildExtension, CppExtension

setup(
    name=&apos;softmax_cpu&apos;,
    ext_modules=[
        CppExtension(
            name=&apos;softmax_cpu&apos;, # 编译出的模块名
            sources=[&apos;softmax_cpu.cpp&apos;],
        ),
    ],
    cmdclass={
        &apos;build_ext&apos;: BuildExtension
    }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;步骤 2：编写 &lt;code&gt;setup.py&lt;/code&gt; 动态编译&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在编译之前，首先安装 &lt;code&gt;ninja&lt;/code&gt; 库，这可以加速编译过程：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install ninja
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后运行以下命令编译 C++ 扩展：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 编译 C++ 扩展，编译后的库会直接放在当前目录下，命名为 softmax_cpu.so (Linux/MacOS) 或类似 softmax_cpu.cp312-win_amd64.pyd 名称的文件 (Windows)
python setup.py build_ext --inplace

# 也可以直接安装到全局的 Python 环境中
pip install -e .
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;CUDA 扩展编写 Softmax 算子&lt;/h5&gt;
&lt;p&gt;CUDA 是 NVIDIA 提出的并行计算平台和编程模型，可以利用 GPU 的强大计算能力加速深度学习中的核心算子。与 C++ 版本的类似，CUDA 版本的 Softmax 算子需要编写 &lt;code&gt;.cu&lt;/code&gt; 文件，使用 CUDA 的线程块和共享内存来实现分块计算和在线 Softmax。可以在文件 &lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/tree/master/chapter-03-attention-translation/codes/softmax_kernels/softmax_cuda/softmax_cuda.cu&quot;&gt;&lt;code&gt;codes/softmax_kernels/softmax_cuda/softmax_cuda.cu&lt;/code&gt;&lt;/a&gt; 中查看一个简单的 CUDA 实现示例。&lt;/p&gt;
&lt;p&gt;CUDA 的编译封装和 C++ 类似，只需在 &lt;code&gt;setup.py&lt;/code&gt; 中指定 &lt;code&gt;sources&lt;/code&gt; 包含 &lt;code&gt;.cu&lt;/code&gt; 文件，并使用 &lt;code&gt;CUDAExtension&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# setup.py
from setuptools import setup
from torch.utils.cpp_extension import CUDAExtension, BuildExtension

setup(
    name=&apos;softmax_cuda&apos;,
    ext_modules=[
        CUDAExtension(
            name=&apos;softmax_cuda&apos;,
            sources=[&apos;softmax_cuda.cu&apos;],
            extra_compile_args={&apos;cxx&apos;: [&apos;-O3&apos;], &apos;nvcc&apos;: [&apos;-O3&apos;]}
        )
    ],
    cmdclass={&apos;build_ext&apos;: BuildExtension}
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其他操作和 C++ 类似，运行 &lt;code&gt;python setup.py build_ext --inplace&lt;/code&gt; 编译生成动态库。&lt;/p&gt;
&lt;h5&gt;调用 C++ 或 CUDA 算子，并封装为 &lt;code&gt;torch.autograd.Function&lt;/code&gt;&lt;/h5&gt;
&lt;p&gt;得到 &lt;code&gt;.pyd&lt;/code&gt; 或 &lt;code&gt;.so&lt;/code&gt; 文件后，我们可以在 Python 中导入编译好的算子，并通过 &lt;code&gt;torch.autograd.Function&lt;/code&gt; 显式构建一条自定义的计算图节点：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
import softmax_cpu
import softmax_cuda

class Softmax(torch.autograd.Function):
    @staticmethod
    def forward(ctx, x):
        # 兼容性处理，防止 CUDA 内存不连续
        x_contig = x.contiguous()
        if x.is_cuda:
            ctx.cuda = True
            out = softmax_cuda.softmax_forward(x_contig)
        else:
            ctx.cuda = False
            out = softmax_cpu.softmax_forward(x_contig)
        
        ctx.save_for_backward(out)
        return out
    
    @staticmethod
    def backward(ctx, grad_out):
        out = ctx.saved_tensors[0]
        grad_out_contig = grad_out.contiguous()
        
        if ctx.cuda:
            return softmax_cuda.softmax_backward(grad_out_contig, out)
        else:
            return softmax_cpu.softmax_backward(grad_out_contig, out)

# 方式1：直接调用 apply
x = torch.randn(3, 5, requires_grad=True)
y = Softmax.apply(x)

# 方式2：包装成普通函数
def my_softmax(x):
    return Softmax.apply(x)

y2 = my_softmax(x)

# 方式3：直接赋值给变量
softmax = Softmax.apply
y3 = softmax(x)

# 测试
print(&quot;输出:\n&quot;, y)
loss = y.sum()
loss.backward()
print(&quot;梯度:\n&quot;, x.grad)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;即时编译 (JIT - Just-In-Time) 加载方式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;除了提前编译，我们也可以用 &lt;code&gt;torch.utils.cpp_extension.load&lt;/code&gt; 直接在运行时编译加载源码。这种方式适合开发调试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch

# 预先定义全局模块
_cpu_module = None
_cuda_module = None

def get_cpu_module():
    global _cpu_module
    if _cpu_module is None:
        from torch.utils.cpp_extension import load
        _cpu_module = load(name=&quot;softmax_cpu&quot;, sources=[&quot;softmax_kernels/softmax_cpu/softmax_cpu.cpp&quot;], verbose=False)
    return _cpu_module

def get_cuda_module():
    global _cuda_module
    if _cuda_module is None:
        from torch.utils.cpp_extension import load
        _cuda_module = load(name=&quot;softmax_cuda&quot;, sources=[&quot;softmax_kernels/softmax_cuda/softmax_cuda.cu&quot;], verbose=False)
    return _cuda_module

class AutoSoftmaxFunction(torch.autograd.Function):
    @staticmethod
    def forward(ctx, input):
        input_contig = input.contiguous()
        if input.is_cuda:
            output = get_cuda_module().softmax_forward(input_contig)
            ctx.cuda_enabled = True
        else:
            output = get_cpu_module().softmax_forward(input_contig)
            ctx.cuda_enabled = False
        
        ctx.save_for_backward(output)
        return output
    
    @staticmethod
    def backward(ctx, grad_output):
        output = ctx.saved_tensors[0]
        grad_out_contig = grad_output.contiguous()
        
        if ctx.cuda_enabled:
            grad_input = get_cuda_module().softmax_backward(grad_out_contig, output)
        else:
            grad_input = get_cpu_module().softmax_backward(grad_out_contig, output)
        
        return grad_input
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;任务目标&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1. 思考下面的问题：Python 的 FlashAttention 实现虽然避免了分配完整的 $S$ 矩阵，但为什么运行速度反而比标准注意力慢？试着从 Python 循环、PyTorch 内核启动开销、未融合的算子的角度思考。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 尝试用 C++ 或者 CUDA 实现 FlashAttention，并封装为 PyTorch 算子。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 在一个小型的 Transformer 模型中替换标准注意力为你实现的 FlashAttention，比较训练速度和显存占用。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 使用现成的通用 FlashAttention 库&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install flash-attention
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;并在你的 Transformer 模型中调用 &lt;code&gt;torch.nn.functional.scaled_dot_product_attention&lt;/code&gt; 来使用 FlashAttention 后端，比较性能差异。&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;完整实现 FlashAttention 的正反向非常复杂，有兴趣的读者可以参考 &lt;a href=&quot;https://github.com/Dao-AILab/flash-attention&quot;&gt;flash-attention 官方实现&lt;/a&gt;。本练习只需理解原理，不要求写出完整反向。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;更多参考资料&lt;/h2&gt;
&lt;p&gt;Transformer&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1706.03762&quot;&gt;Attention Is All You Need | Transformer 原始论文&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2010.11929&quot;&gt;An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale | Vision Transformer 原始论文&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;分词器与词嵌入层&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/1947688136684574286&quot;&gt;知乎文章 | 全网最全的大模型分词器（Tokenizer）总结&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/422542949&quot;&gt;知乎文章 | 从0开始词嵌入（Word embedding）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.volcengine.com/articles/7389519179202134025&quot;&gt;一文彻底搞懂Transformer - Word Embedding（词嵌入）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;seq2seq&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zh-v2.d2l.ai/chapter_recurrent-modern/seq2seq.html&quot;&gt;动手学深度学习 | 9.7. 序列到序列学习（seq2seq）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;BERT 与 GPT&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1810.04805&quot;&gt;BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding | BERT 原始论文&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf&quot;&gt;GPT: Improving Language Understanding by Generative Pre-Training | GPT 原始论文&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/tardis/zm/art/607605399&quot;&gt;知乎文章 | Transformer两大变种：GPT和BERT的差别（易懂版）-2更&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第三章项目文件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/tree/master/chapter-03-attention-translation&quot;&gt;从零开始学 AI - 第三章&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>MarkItDown - 将多种文档转换为 Markdown 文件的工具</title><link>https://adalovelemon.github.io/posts/content/technotes/tools/markitdown/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/tools/markitdown/</guid><description>对微软开发的 MarkItDown 工具的个人使用笔记和介绍</description><pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Steps, Tabs, TabItem, Timeline, Card, CardList, Collapse, Button, Spoiler, Label } from &quot;astro-pure/user&quot;&lt;/p&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;note&quot; title=&quot;学习笔记&quot;&amp;gt;
本文是我在学习和使用 MarkItDown 过程中的一些个人笔记和总结，内容可能不够全面或者存在错误，欢迎指正和补充！希望能帮助到正在学习这个工具的朋友们。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;p&gt;众所周知大模型的文档语言用的是 Makrdown。为了便于大模型阅读各类文档，微软开发了一套可以将多种文档格式转换为 Markdown 格式 (保留标题、列表、表格、链接等) 的工具——&lt;a href=&quot;https://github.com/microsoft/markitdown&quot;&gt;MarkItDown&lt;/a&gt;。它支持的转换格式有&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PDF&lt;/li&gt;
&lt;li&gt;PowerPoint&lt;/li&gt;
&lt;li&gt;Word&lt;/li&gt;
&lt;li&gt;Excel&lt;/li&gt;
&lt;li&gt;Images (不仅提取 EXIF 元数据，还能进行 OCR 识别)&lt;/li&gt;
&lt;li&gt;Audio (不仅提取 EXIF 元数据，还能进行语音转录)&lt;/li&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;li&gt;其他基于文本的格式 (CSV, JSON, XML)&lt;/li&gt;
&lt;li&gt;ZIP 压缩包 (以及遍历其中的文件进行转换)&lt;/li&gt;
&lt;li&gt;Youtube 网络链接&lt;/li&gt;
&lt;li&gt;EPub 电子书&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;工具安装&lt;/h2&gt;
&lt;p&gt;MarkItDown 需要 Python 3.10+ 环境，可以通过下列命令一键安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone git@github.com:microsoft/markitdown.git
cd markitdown
pip install -e &apos;packages/markitdown[all]&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install &quot;git+https://github.com/microsoft/markitdown.git#subdirectory=packages/markitdown&amp;amp;egg=markitdown[all]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的是，该工具依赖 &lt;code&gt;ffmpeg&lt;/code&gt; 处理音视频文件，因此需要确保系统中已经安装了 &lt;code&gt;ffmpeg&lt;/code&gt;，并且将其添加到了环境变量中。如果没有安装，可以通过以下命令安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Linux
sudo apt update &amp;amp;&amp;amp; sudo apt install ffmpeg

# macOS (使用 Homebrew)
brew install ffmpeg

# Windows PowerShell
winget install ffmpeg
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用方法&lt;/h2&gt;
&lt;h3&gt;基础使用&lt;/h3&gt;
&lt;p&gt;命令行的使用方法如下，使用 &lt;code&gt;&amp;gt;&lt;/code&gt; 或者 &lt;code&gt;-o&lt;/code&gt; 指定输出文件路径&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;markitdown path/to/file.pdf &amp;gt; document.md
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;markitdown path/to/file.pptx -o document.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此外，还可以通过管道的方式转换文档&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Linux 或 macOS
cat path/to/file.docx | markitdown &amp;gt; document.md

# Windows PowerShell - 不推荐使用
Get-Content path/to/file.docx | markitdown &amp;gt; document.md
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;插件&lt;/h3&gt;
&lt;p&gt;MarkItDown 还支持第三方的插件，可以通过安装第三方插件来扩展其功能。下面指令列举了已安装的插件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;markitdown --list-plugins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以用这个指令启用插件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;markitdown --use-plugins path/to/file.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;markitdown-ocr&lt;/code&gt; 插件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;markitdown-ocr&lt;/code&gt; 插件为 PDF、DOCX、PPTX 和 XLSX 文件增加了 OCR 识别功能。为了使用这个插件，需要额外安装以下依赖&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install markitdown-ocr
pip install openai  # 或者其他与 OpenAI API 兼容的库
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用方法如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from markitdown import MarkItDown
from openai import OpenAI

# 转换是基于 LLM 的，因此需要提供一个 LLM 客户端实例
md = MarkItDown(
    enable_plugins=True,
    llm_client=OpenAI(),
    llm_model=&quot;gpt-4o&quot;,
)
result = md.convert(&quot;document_with_images.pdf&quot;)
print(result.text_content)

# 保存为 Markdown 文件
with open(&quot;document.md&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    f.write(result.markdown_content)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Azure 文档数据提取器&lt;/h3&gt;
&lt;p&gt;亦可以集成到这个工具中来使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;markitdown path-to-file.pdf -o document.md -d -e &quot;&amp;lt;document_intelligence_endpoint&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Python 接口&lt;/h3&gt;
&lt;p&gt;基础 Python 接口的使用方法如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from markitdown import MarkItDown

md = MarkItDown(enable_plugins=False)   # 设置为 False 来禁用插件
result = md.convert(&quot;test.xlsx&quot;)
print(result.text_content)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结合 Azure 文档数据提取器的使用方法如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from markitdown import MarkItDown

md = MarkItDown(docintel_endpoint=&quot;&amp;lt;document_intelligence_endpoint&amp;gt;&quot;)
result = md.convert(&quot;test.pdf&quot;)
print(result.text_content)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果想利用大模型的能力提取图像中的文本，可以提供 &lt;code&gt;llm_client&lt;/code&gt; 和 &lt;code&gt;llm_model&lt;/code&gt; 参数来启用 OCR 插件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from markitdown import MarkItDown
from openai import OpenAI

client = OpenAI()
md = MarkItDown(llm_client=client, llm_model=&quot;gpt-4o&quot;, llm_prompt=&quot;optional custom prompt&quot;)
result = md.convert(&quot;example.jpg&quot;)
print(result.text_content)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;MCP 工具&lt;/h2&gt;
&lt;p&gt;MarkItDown 还提供了一个 MCP (MarkItDown Content Processor) 工具，供本地模型使用。要使用，需要先安装依赖&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install markitdown-mcp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果使用 STDIO 模式运行这个 MCP 工具，运行这个指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;markitdown-mcp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果使用流式 HTTP 和 SSE 模式运行工具，需要运行这个指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;markitdown-mcp --http --host 127.0.0.1 --port 3001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更多相关信息可以参考官方文档 &lt;a href=&quot;https://github.com/microsoft/markitdown/tree/main/packages/markitdown-mcp&quot;&gt;MarkItDown MCP&lt;/a&gt;。&lt;/p&gt;
</content:encoded></item><item><title>Linux 学习笔记 4 —— 磁盘与文件系统</title><link>https://adalovelemon.github.io/posts/content/technotes/linux/linux4/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/linux/linux4/</guid><pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Steps, Tabs, TabItem, Timeline, Card, CardList, Collapse, Button, Spoiler, Label } from &quot;astro-pure/user&quot;&lt;/p&gt;
&lt;p&gt;Linux 将硬件设备抽象为文件，磁盘也不例外。在 Linux 看来，硬盘、U盘、光驱都只是位于 &lt;code&gt;/dev/&lt;/code&gt; 目录下的一个文件（例如 &lt;code&gt;/dev/sda&lt;/code&gt;）。这种设计使得操作系统可以使用统一的接口（读/写）来操作不同的硬件。&lt;/p&gt;
&lt;h2&gt;4.1 磁盘分区&lt;/h2&gt;
&lt;p&gt;磁盘分区是将物理磁盘划分为多个独立区域的过程。Linux 内核通过&lt;strong&gt;分区表&lt;/strong&gt;来记录这些边界。&lt;/p&gt;
&lt;h3&gt;4.1.1 分区表类型&lt;/h3&gt;
&lt;p&gt;目前主要有两种分区表标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MBR（Master Boot Record）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;传统的分区表，位于磁盘的最开始处&lt;/li&gt;
&lt;li&gt;限制：最多支持 4 个主分区（或 3 个主分区 + 1 个扩展分区），最大寻址 2TB 磁盘&lt;/li&gt;
&lt;li&gt;结构：包含引导代码、分区表项（4 个 16 字节的条目）和魔数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GPT（GUID Partition Table）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;现代标准，作为 UEFI 的一部分&lt;/li&gt;
&lt;li&gt;优势：支持超过 2TB 的大磁盘，支持几乎无限数量的分区（通常限制为 128 个）&lt;/li&gt;
&lt;li&gt;具有 CRC 校验保证数据完整性&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.1.2 常用分区工具&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;工具名称&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;th&gt;适用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fdisk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;交互式，功能强大，不立即写入直到确认&lt;/td&gt;
&lt;td&gt;创建、删除、修改分区表（推荐用于 MBR）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;parted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;命令即时生效，支持脚本化操作&lt;/td&gt;
&lt;td&gt;需要脚本自动化分区，或处理 GPT 磁盘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gparted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;parted&lt;/code&gt; 的图形化前端&lt;/td&gt;
&lt;td&gt;桌面环境下的可视化磁盘管理&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;4.1.3 分区操作实战&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;fdisk&lt;/code&gt; 修改磁盘 &lt;code&gt;/dev/sdd&lt;/code&gt; 的典型流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;启动交互：&lt;code&gt;fdisk /dev/sdd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;查看现状：输入 &lt;code&gt;p&lt;/code&gt; 打印当前分区表&lt;/li&gt;
&lt;li&gt;删除旧分区：输入 &lt;code&gt;d&lt;/code&gt;，选择分区号&lt;/li&gt;
&lt;li&gt;创建新分区：输入 &lt;code&gt;n&lt;/code&gt;，选择分区类型，设定起始扇区和大小（如 &lt;code&gt;+200M&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;写入磁盘：输入 &lt;code&gt;w&lt;/code&gt; 保存并退出（在此之前所有操作仅在内存中）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;Aside type=&quot;danger&quot; title=&quot;数据安全警告&quot;&amp;gt;
修改分区表会改变磁盘的几何结构，可能导致数据无法访问。在操作前请务必备份重要数据，并确保没有分区处于挂载状态。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h3&gt;4.1.4 物理几何与对齐&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CHS（Cylinder-Head-Sector）&lt;/strong&gt;：早期的磁盘寻址方式。现代磁盘使用 &lt;strong&gt;LBA（Logical Block Addressing）&lt;/strong&gt;，将扇区线性编号&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSD 对齐&lt;/strong&gt;：对于固态硬盘，分区起始位置应与物理页或块对齐（通常工具默认对齐到 1MB 或 2048 扇区），否则会导致读写性能下降&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4.2 文件系统基础&lt;/h2&gt;
&lt;p&gt;文件系统是内核与用户空间之间的抽象层，它将线性的块设备转化为树状的目录和文件结构。&lt;/p&gt;
&lt;h3&gt;4.2.1 常见文件系统类型&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ext4&lt;/strong&gt;：Linux 传统默认文件系统，稳定，支持大文件，向后兼容 ext2/3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Btrfs&lt;/strong&gt;：现代写时复制文件系统，支持快照、压缩和动态卷管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XFS&lt;/strong&gt;：高性能日志文件系统，常用于大文件服务器（如 Red Hat 默认）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vfat/exfat&lt;/strong&gt;：Windows FAT 系列的变种，常用于 U 盘和 SD 卡，兼容性好但无权限管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;iso9660&lt;/strong&gt;：光盘标准文件系统&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2.2 创建与挂载文件系统&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;创建文件系统&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mkfs&lt;/code&gt; 是一个前端工具，实际会调用具体的工具（如 &lt;code&gt;mke2fs&lt;/code&gt; 用于 ext 系列）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkfs -t ext4 /dev/sdf2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;挂载文件系统&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mount -t ext4 /dev/sdf2 /home/extra
umount /home/extra   # 卸载
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2.3 文件系统 UUID&lt;/h3&gt;
&lt;p&gt;设备名（如 &lt;code&gt;/dev/sda1&lt;/code&gt;）可能因硬件变动而改变，因此推荐使用 &lt;strong&gt;UUID&lt;/strong&gt; 来标识和挂载文件系统：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看 UUID
blkid

# 通过 UUID 挂载
mount UUID=b600fe63-d2e9-461c-a5cd-d3b373a5e1d2 /home/extra
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2.4 &lt;code&gt;/etc/fstab&lt;/code&gt; 配置文件&lt;/h3&gt;
&lt;p&gt;系统启动时自动挂载文件系统的配置表，每行 6 列：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UUID=... / ext4 errors=remount-ro 0 1
[设备标识] [挂载点] [文件系统类型] [选项] [dump备份] [fsck检查顺序]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;dump 字段：通常设为 &lt;code&gt;0&lt;/code&gt;（已废弃）&lt;/li&gt;
&lt;li&gt;fsck 字段：&lt;code&gt;0&lt;/code&gt; = 不检查；&lt;code&gt;1&lt;/code&gt; = 根分区（最先检查）；&lt;code&gt;2&lt;/code&gt; = 其他分区&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2.5 文件系统检查与修复&lt;/h3&gt;
&lt;p&gt;当系统非正常关机时，文件系统元数据可能不一致，需要在下次挂载前修复：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 交互式检查
fsck /dev/sdb1

# 自动修复
fsck -p /dev/sdb1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;Aside type=&quot;danger&quot; title=&quot;数据安全警告&quot;&amp;gt;
严禁对已挂载的文件系统运行 fsck，这会导致数据严重损坏。除非是只读挂载的根文件系统。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h2&gt;4.3 交换空间&lt;/h2&gt;
&lt;p&gt;当物理内存耗尽时，Linux 使用磁盘上的交换空间来存储不活跃的内存页。&lt;/p&gt;
&lt;h3&gt;4.3.1 交换空间类型&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;交换分区&lt;/strong&gt;：专用的磁盘分区（类型 ID 通常为 &lt;code&gt;82&lt;/code&gt;），性能略好，无需文件系统开销&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交换文件&lt;/strong&gt;：文件系统中的一个普通文件，灵活性高，无需重新分区即可调整大小&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.3.2 管理命令&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 初始化分区
mkswap /dev/sda5

# 初始化文件
dd if=/dev/zero of=/swapfile bs=1M count=100
mkswap /swapfile

# 启用/禁用
swapon /dev/sda5
swapoff /dev/sda5

# 查看状态
free -h
swapon --show
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3.3 Swap 使用策略&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传统规则&lt;/strong&gt;：Swap 大小 = 2 倍物理内存（适用于内存很小的旧时代）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现代建议&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;桌面/休眠：需要至少等于物理内存大小的 Swap，以支持休眠功能&lt;/li&gt;
&lt;li&gt;服务器：内存较大（&amp;gt;16GB）时，仅需少量 Swap（2GB-4GB）用于应急&lt;/li&gt;
&lt;li&gt;可设置 &lt;code&gt;vm.swappiness=1&lt;/code&gt;，告诉内核尽量避免使用 Swap，防止性能抖动&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4.4 文件系统内部机制&lt;/h2&gt;
&lt;h3&gt;4.4.1 Inode 结构&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inode（索引节点）&lt;/strong&gt;：存储文件的元数据（权限、所有者、时间戳、指向数据块的指针），但不包含文件名&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文件名&lt;/strong&gt;：存储在目录文件的条目中，目录文件本质上是一个将文件名映射到 Inode 号的列表&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.4.2 硬链接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;定义：多个文件名指向同一个 Inode&lt;/li&gt;
&lt;li&gt;特点：删除其中一个链接，Inode 的链接计数减 1，只有当计数为 0 时，数据块才会被真正释放&lt;/li&gt;
&lt;li&gt;限制：硬链接不能跨文件系统，也不能链接目录（防止循环引用）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.4.3 块位图&lt;/h3&gt;
&lt;p&gt;文件系统使用位图来追踪数据块的使用状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt;：块空闲&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt;：块已占用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;fsck&lt;/code&gt; 在检查文件系统时，会重新生成位图并与 Inode 中记录的使用情况进行比对。&lt;/p&gt;
&lt;h2&gt;4.5 文件系统日志机制&lt;/h2&gt;
&lt;p&gt;为什么现代文件系统（ext3/4、XFS、Btrfs）在断电后重启极快，而古老的 ext2 需要漫长的 fsck 检查？答案在于日志。&lt;/p&gt;
&lt;h3&gt;4.5.1 文件系统不一致的原因&lt;/h3&gt;
&lt;p&gt;当写入一个文件时，涉及多个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;更新 Inode（元数据）&lt;/li&gt;
&lt;li&gt;更新位图（标记块已占用）&lt;/li&gt;
&lt;li&gt;写入实际数据块&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果在这些步骤中间断电，元数据与实际数据就会不一致。&lt;/p&gt;
&lt;h3&gt;4.5.2 日志的工作流程&lt;/h3&gt;
&lt;p&gt;日志文件系统引入&quot;预写日志&quot;的概念：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;提交事务&lt;/strong&gt;：在修改实际文件系统结构之前，先将&quot;意图&quot;写入日志区&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;检查点&lt;/strong&gt;：一旦日志写入成功，系统就认为事务已提交&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行&lt;/strong&gt;：按日志中的记录去修改实际的 Inode 和位图&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;清除&lt;/strong&gt;：修改完成后，从日志中清除该记录&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;系统崩溃重启时，文件系统驱动只需读取日志区：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果有未完成的记录，直接重做&lt;/li&gt;
&lt;li&gt;如果记录已完成但标记未清除，忽略即可&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.5.3 日志模式（针对 ext4）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Journal（数据日志）&lt;/strong&gt;：元数据和文件数据都记日志，最安全但速度最慢&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ordered（有序模式，默认）&lt;/strong&gt;：只记元数据日志，但保证数据块先写入磁盘再提交元数据日志，兼顾安全与性能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Writeback（回写模式）&lt;/strong&gt;：只记元数据日志，速度最快但断电后可能出现数据不一致&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4.6 逻辑卷管理&lt;/h2&gt;
&lt;p&gt;LVM 在物理磁盘和文件系统之间增加了一层抽象，解决了传统分区&quot;一旦分配难以调整&quot;的痛点。&lt;/p&gt;
&lt;h3&gt;4.6.1 核心概念&lt;/h3&gt;
&lt;p&gt;LVM 的结构类似于&quot;存储池&quot;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PV（Physical Volume）&lt;/strong&gt;：物理卷，通常是磁盘分区（类型 &lt;code&gt;8e&lt;/code&gt;）或整块磁盘&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VG（Volume Group）&lt;/strong&gt;：卷组，由多个 PV 组成的大池子&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LV（Logical Volume）&lt;/strong&gt;：逻辑卷，从 VG 池子中切分出来的空间，相当于&quot;虚拟分区&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.6.2 LVM 实战流程&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 准备物理分区&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;fdisk&lt;/code&gt; 或 &lt;code&gt;parted&lt;/code&gt; 将磁盘分区，并将分区类型设为 &lt;code&gt;Linux LVM&lt;/code&gt;（8e）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 创建 PV 和 VG&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pvcreate /dev/sdb1 /dev/sdc1
vgcreate myvg /dev/sdb1 /dev/sdc1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 创建 LV&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 从卷组 myvg 中创建一个 10GB 的逻辑卷
lvcreate --size 10g --name mylv1 myvg
# 设备路径：/dev/myvg/mylv1 或 /dev/mapper/myvg-mylv1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. 使用 LV&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkfs.ext4 /dev/myvg/mylv1
mount /dev/myvg/mylv1 /mnt/data
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.6.3 动态调整&lt;/h3&gt;
&lt;p&gt;LVM 的最大优势是可以在不卸载的情况下调整大小（仅限支持在线调整的文件系统）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 扩展逻辑卷并自动调整文件系统
lvextend -r --size +100%FREE /dev/myvg/mylv1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4.7 设备映射器&lt;/h2&gt;
&lt;p&gt;LVM 的底层依赖于内核的&lt;strong&gt;设备映射器&lt;/strong&gt;驱动。它位于物理块设备和逻辑块设备之间。&lt;/p&gt;
&lt;h3&gt;4.7.1 核心架构&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;dm-mod&lt;/code&gt; 内核模块&lt;/strong&gt;：DM 的核心驱动&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;dmsetup&lt;/code&gt; 工具&lt;/strong&gt;：用户空间命令行工具，用于直接与内核的 DM 驱动交互&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标类型&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;linear&lt;/code&gt;：LVM 默认模式，将多个物理区域拼接成线性逻辑区域&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mirror&lt;/code&gt;：提供 RAID 1 镜像功能&lt;/li&gt;
&lt;li&gt;&lt;code&gt;snapshot&lt;/code&gt;：提供快照功能（写时复制）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crypt&lt;/code&gt;：提供透明加密（LUKS 的基础）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.7.2 工作原理&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;用户空间的 LVM 工具计算好映射关系&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;ioctl&lt;/code&gt; 系统调用将映射表写入内核的 &lt;code&gt;/dev/mapper/control&lt;/code&gt; 设备&lt;/li&gt;
&lt;li&gt;内核空间的 Device Mapper 根据映射表，将对逻辑卷的读写请求翻译为对物理卷上具体位置的读写请求&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;Aside type=&quot;tip&quot; title=&quot;映射示例&quot;&amp;gt;
假设逻辑卷 mylv 的前 100 个扇区被映射到物理设备 &lt;code&gt;/dev/sdb&lt;/code&gt; 的第 2048 个扇区。当进程读取 &lt;code&gt;/dev/mapper/myvg-mylv&lt;/code&gt; 的第 0 个扇区时，Device Mapper 会将其重定向为读取 &lt;code&gt;/dev/sdb&lt;/code&gt; 的第 2048 个扇区。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h3&gt;4.7.3 多路径技术&lt;/h3&gt;
&lt;p&gt;在企业级存储环境中，服务器到存储设备通常有多条物理连接：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;问题&lt;/strong&gt;：操作系统会看到同一个 LUN 通过不同路径出现多次&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：使用 &lt;code&gt;device-mapper-multipath&lt;/code&gt; 将多个路径聚合成一个虚拟设备&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能&lt;/strong&gt;：故障切换（一条路径断，IO 自动切换）和负载均衡（IO 在多条路径间轮询分发）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 查看多路径状态
multipath -ll
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4.8 虚拟文件系统与挂载机制&lt;/h2&gt;
&lt;p&gt;如果说文件系统是数据的存储格式，那么虚拟文件系统就是内核用来统一访问这些数据的&quot;万能翻译官&quot;。&lt;/p&gt;
&lt;h3&gt;4.8.1 VFS 的抽象层作用&lt;/h3&gt;
&lt;p&gt;Linux 支持几十种文件系统，应用程序不需要知道底层具体是哪种格式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;工作原理&lt;/strong&gt;：VFS 定义了一组通用的接口，具体的文件系统驱动负责实现这些接口&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流程&lt;/strong&gt;：&lt;code&gt;用户程序&lt;/code&gt; → &lt;code&gt;系统调用&lt;/code&gt; → &lt;code&gt;VFS&lt;/code&gt; → &lt;code&gt;具体文件系统驱动&lt;/code&gt; → &lt;code&gt;块设备驱动&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.8.2 挂载的本质&lt;/h3&gt;
&lt;p&gt;挂载是将一个文件系统及其目录树附加到现有 VFS 树中某个目录的过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;挂载点&lt;/strong&gt;：被附加的目录，一旦挂载，该目录原本的内容会被&quot;隐藏&quot;，直到卸载&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;绑定挂载&lt;/strong&gt;：允许将同一个文件系统或目录挂载到不同的位置&lt;pre&gt;&lt;code&gt;mount --bind /source/dir /target/dir
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.8.3 挂载命名空间&lt;/h3&gt;
&lt;p&gt;在现代 Linux 中，挂载不是全局唯一的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不同的进程可以看到不同的挂载点列表&lt;/li&gt;
&lt;li&gt;这是 Docker/容器技术的基础——容器内部看到的挂载点可能与宿主机完全不同，互不干扰&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4.9 磁盘配额&lt;/h2&gt;
&lt;p&gt;当多个用户共享同一个文件系统时，管理员需要限制每个用户能使用的磁盘空间。&lt;/p&gt;
&lt;h3&gt;4.9.1 配额类型&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;块限制&lt;/strong&gt;：限制用户能使用的磁盘空间总量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inode 限制&lt;/strong&gt;：限制用户能创建的文件总数（防止耗尽 Inode）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.9.2 软限制与硬限制&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;软限制&lt;/strong&gt;：警告线，超过后系统会发出警告，但允许在宽限期内继续写入&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬限制&lt;/strong&gt;：绝对红线，一旦达到立即禁止写入&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.9.3 管理工具&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;edquota&lt;/code&gt;：编辑用户配额&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repquota&lt;/code&gt;：报告配额使用情况&lt;/li&gt;
&lt;li&gt;&lt;code&gt;quotaon&lt;/code&gt; / &lt;code&gt;quotaoff&lt;/code&gt;：开启或关闭配额功能&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;Aside type=&quot;tip&quot; title=&quot;配额生效条件&quot;&amp;gt;
必须在 &lt;code&gt;/etc/fstab&lt;/code&gt; 中挂载文件系统时添加 usrquota（用户配额）或 grpquota（组配额）选项，并重新挂载后，配额才会生效。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h2&gt;4.10 磁盘 I/O 调度器&lt;/h2&gt;
&lt;p&gt;当多个进程同时请求读写磁盘时，内核通过 I/O 调度器决定处理顺序。&lt;/p&gt;
&lt;h3&gt;4.10.1 调度器的作用&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;合并请求&lt;/strong&gt;：将相邻扇区的读写请求合并，减少磁头移动&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;排序&lt;/strong&gt;：根据算法决定请求顺序&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;公平性&lt;/strong&gt;：防止某个进程独占磁盘带宽&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.10.2 常见调度算法&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;调度器名称&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;th&gt;适用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mq-deadline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保证请求在截止时间前完成，注重低延迟&lt;/td&gt;
&lt;td&gt;适合数据库等对延迟敏感的应用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bfq&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;基于预算的公平队列，保证每个进程获得公平带宽&lt;/td&gt;
&lt;td&gt;适合桌面环境，防止界面卡顿&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kyber&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;针对多队列设备优化，简单高效&lt;/td&gt;
&lt;td&gt;适合高性能 SSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;none&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不进行调度，直接下发给硬件&lt;/td&gt;
&lt;td&gt;适合自带复杂控制器的现代 NVMe SSD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;4.10.3 查看与修改&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 查看当前调度器
cat /sys/block/sda/queue/scheduler

# 临时修改
echo kyber &amp;gt; /sys/block/sda/queue/scheduler
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4.11 实战演练：综合故障排查&lt;/h2&gt;
&lt;h3&gt;4.11.1 场景：磁盘空间&quot;假性&quot;不足&lt;/h3&gt;
&lt;p&gt;用户报告无法写入文件，提示&quot;设备无空间&quot;，但 &lt;code&gt;df -h&lt;/code&gt; 显示还有剩余空间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;排查步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;检查块空间：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;df -h
# Use% 未达 100%，说明块空间未满
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;检查 Inode 空间：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;df -i
# IUse% 显示 100%，说明 Inode 耗尽
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;定位问题文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 统计各目录下的文件数
find /var -xdev -type f | cut -d &quot;/&quot; -f 3 | sort | uniq -c | sort -nr | head
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;清理不必要的小文件（如旧的缓存、会话文件）&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.11.2 场景：恢复误删但仍被进程占用的文件&lt;/h3&gt;
&lt;p&gt;如果文件被删除但进程仍持有句柄，空间不会立即释放：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;查找被删除但仍被占用的文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lsof | grep deleted
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;恢复方法（不要重启进程）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cp /proc/&amp;lt;PID&amp;gt;/fd/&amp;lt;FD&amp;gt; /tmp/recovered_file
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>SAM3 模型代码探幽</title><link>https://adalovelemon.github.io/posts/content/technotes/foundationmodels/sam/sam3/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/foundationmodels/sam/sam3/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Steps, Tabs, TabItem, Timeline, Card, CardList, Collapse, Button, Spoiler, Label } from &quot;astro-pure/user&quot;&lt;/p&gt;
&lt;p&gt;export const images = import.meta.glob(&quot;./assets/*.{png,jpg,jpeg,webp,gif,avif,svg}&quot;, {
eager: true,
import: &quot;default&quot;,
})&lt;/p&gt;
&lt;h2&gt;前序工作&lt;/h2&gt;
&lt;h3&gt;基本介绍&lt;/h3&gt;
&lt;p&gt;SAM3 (Segment Anything Model 3) 是 Meta AI 研究院开发的一款强大的图像分割模型，能够在各种图像上进行高效的分割任务。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2511.16719&quot;&gt;SAM3 论文&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/facebookresearch/sam3&quot;&gt;代码&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Okay, clone 完项目的代码后，在 &lt;a href=&quot;https://github.com/facebookresearch/sam3/blob/main/README.md&quot;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; 中可以看到 SAM3 的基本结构图。&lt;/p&gt;
&lt;h3&gt;模型结构&lt;/h3&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/sam3.png&quot;].src} alt=&quot;SAM3 Model Diagram&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;图像截取自 SAM3 论文&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/facebookresearch/sam3/blob/main/README.md&quot;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; 中是这样介绍 SAM3 的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SAM 3 is a unified foundation model for promptable segmentation in images and videos. It can detect, segment, and track objects using text or visual prompts such as points, boxes, and masks. Compared to its predecessor &lt;a href=&quot;https://github.com/facebookresearch/sam2&quot;&gt;SAM 2&lt;/a&gt;, SAM 3 introduces the ability to exhaustively segment all instances of an open-vocabulary concept specified by a short text phrase or exemplars. Unlike prior work, SAM 3 can handle a vastly larger set of open-vocabulary prompts. It achieves 75-80% of human performance on our new &lt;a href=&quot;https://github.com/facebookresearch/sam3?tab=readme-ov-file#sa-co-dataset&quot;&gt;SA-CO benchmark&lt;/a&gt; which contains 270K unique concepts, over 50 times more than existing benchmarks.&lt;/p&gt;
&lt;p&gt;This breakthrough is driven by an innovative data engine that has automatically annotated over 4 million unique concepts, creating the largest high-quality open-vocabulary segmentation dataset to date. In addition, SAM 3 introduces a new model architecture featuring a presence token that improves discrimination between closely related text prompts (e.g., “a player in white” vs. “a player in red”), as well as a decoupled detector–tracker design that minimizes task interference and scales efficiently with data.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;相较于 SAM2，SAM3 的改进主要体现在以下几个方面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;能力质变&lt;/strong&gt;：从依赖点、框的视觉提示进化为理解开放词汇文本，能自动找出画面中所有符合描述（如“穿红衣的球员”）的实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;架构解耦&lt;/strong&gt;：采用 Detector（负责理解语义）与Tracker（负责时序传播）分离的设计，共享主干网络以减少任务干扰并提升扩展性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;判别精准&lt;/strong&gt;：引入 Presence Token 机制，专门用于区分相似文本描述（如颜色、属性差异），显著提升了语义判别的准确率。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;配置环境&lt;/h3&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://github.com/facebookresearch/sam3/blob/main/README.md&quot;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; 中的说明，作如下配置（注意这个只适用于 Linux/macOS 系统）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda create -n sam3 python=3.12 -y
conda activate sam3

cd sam3
pip install torch==2.10.0 torchvision --index-url https://download.pytorch.org/whl/cu128

pip install -e .

# Additional dependencies for training and evaluation

# For running example notebooks
pip install -e &quot;.[notebooks]&quot;

# For development
pip install -e &quot;.[train,dev]&quot;

# Optional dependencies for faster inference
pip install einops ninja 
pip install flash-attn-3 --no-deps --index-url https://download.pytorch.org/whl/cu128
pip install git+https://github.com/ronghanghu/cc_torch.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python 3.12 以上版本将弃用 &lt;code&gt;pkg_resources&lt;/code&gt; 模块，而且在 Windows 版本的 Python 解释器中，这个模块已经被完全移除了，但是 SAM3 开发是在 Linux 上进行的，而且还使用了这个古老的模块，因此在 Windows 系统上直接安装 SAM3 的依赖会遇到兼容性问题。
对于 Windows 系统，建议使用 WSL 来配置环境。如果已经安装好了 UV，可以使用如下 &lt;code&gt;pyproject.toml&lt;/code&gt; 来配置环境：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[build-system]
requires = [&quot;setuptools&amp;gt;=61&quot;, &quot;wheel&quot;]
build-backend = &quot;setuptools.build_meta&quot;

[project]
name = &quot;sam_unified&quot;
version = &quot;0.1.0&quot;
description = &quot;Unified Segment Anything Model (SAM 2 &amp;amp; 3) implementation&quot;
readme = &quot;README.md&quot;
requires-python = &quot;&amp;gt;=3.12&quot;
license = {text = &quot;MIT&quot;}
authors = [
    {name = &quot;Meta AI Research&quot;}
]
classifiers = [
    &quot;Development Status :: 4 - Beta&quot;,
    &quot;Intended Audience :: Science/Research&quot;,
    &quot;License :: OSI Approved :: MIT License&quot;,
    &quot;Programming Language :: Python :: 3&quot;,
    &quot;Programming Language :: Python :: 3.12&quot;,
    &quot;Topic :: Scientific/Engineering :: Artificial Intelligence&quot;,
]
dependencies = [
    &quot;torch&amp;gt;=2.7&quot;,
    &quot;torchvision&quot;,
    &quot;timm&amp;gt;=1.0.17&quot;,
    &quot;numpy&amp;gt;=1.26,&amp;lt;2&quot;,
    &quot;tqdm&quot;,
    &quot;ftfy==6.1.1&quot;,
    &quot;regex&quot;,
    &quot;iopath&amp;gt;=0.1.10&quot;,
    &quot;typing_extensions&quot;,
    &quot;huggingface_hub&quot;,
    &quot;pillow&quot;,
    &quot;einops&quot;,
    &quot;pycocotools&quot;,
    &quot;psutil&quot;,
    &quot;triton&quot;,
    &quot;scipy&quot;,
    &quot;opencv-python&quot;,
    &quot;modelscope&quot;,
]

[project.optional-dependencies]
dev = [
    &quot;pytest&quot;,
    &quot;pytest-cov&quot;,
    &quot;black==24.2.0&quot;,
    &quot;ufmt==2.8.0&quot;,
    &quot;ruff-api==0.1.0&quot;,
    &quot;usort==1.0.2&quot;,
    &quot;gitpython==3.1.31&quot;,
    &quot;yt-dlp&quot;,
    &quot;pandas&quot;,
    &quot;opencv-python&quot;,
    &quot;pycocotools&quot;,
    &quot;numba&quot;,
    &quot;python-rapidjson&quot;,
]
notebooks = [
    &quot;matplotlib&quot;,
    &quot;jupyter&quot;,
    &quot;notebook&quot;,
    &quot;ipywidgets&quot;,
    &quot;ipycanvas&quot;,
    &quot;ipympl&quot;,
    &quot;pycocotools&quot;,
    &quot;decord&quot;,
    &quot;opencv-python&quot;,
    &quot;einops&quot;,
    &quot;scikit-image&quot;,
    &quot;scikit-learn&quot;,
]
train = [
    &quot;hydra-core&quot;,
    &quot;submitit&quot;,
    &quot;tensorboard&quot;,
    &quot;zstandard&quot;,
    &quot;scipy&quot;,
    &quot;torchmetrics&quot;,
    &quot;fvcore&quot;,
    &quot;fairscale&quot;,
    &quot;scikit-image&quot;,
    &quot;scikit-learn&quot;,
]

[tool.setuptools.packages.find]
include = [&quot;sam2*&quot;, &quot;sam3*&quot;]

[tool.uv]
index-url = &quot;https://mirrors.aliyun.com/pypi/simple/&quot;
extra-index-url = [&quot;https://download.pytorch.org/whl/cu128&quot;]
index-strategy = &quot;unsafe-best-match&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;预训练模型下载&lt;/h3&gt;
&lt;p&gt;SAM3 的模型权重不像 SAM, SAM2 那样提供了 &lt;code&gt;.pt&lt;/code&gt; 权重文件，而是必须通过 huggingface 平台获取，而且还需要提交使用申请。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/facebook/sam3&quot;&gt;SAM3 huggingface repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/docs/huggingface_hub/en/quick-start#authentication&quot;&gt;申请指导&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;考虑到申请 SAM3 的模型权重非常的容易被拒绝，这里推荐使用魔塔社区先下载模型权重，再用 huggingface 加载的方式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 确保已经安装了 modelscope
pip install modelscope

# 下载 SAM3 模型权重
modelscope download --model facebook/sam3

# 下载 SAM3.1 模型权重
modelscope download --model facebook/sam3.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下载完成后，在 modelscope 的本地 repo 文件中可以直接看到 &lt;code&gt;sam3.pt&lt;/code&gt; 和 &lt;code&gt;sam3.1_multiplex.pt&lt;/code&gt; 的权重文件的，这个时候可以直接使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;model = build_sam3_image_model(load_from_HF=False, checkpoint_path=&quot;your/path/to/sam3.pt&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;的方式来加载模型权重了，下面的接口展示也是以这个加载方式为例。需要注意的是，如果是 Windows WSL 加载模型的话，路径开头不是 Windows 格式，而是 &lt;code&gt;/mnt/d&lt;/code&gt; (D 盘) 这样的格式。&lt;/p&gt;
&lt;h2&gt;官方使用接口&lt;/h2&gt;
&lt;h3&gt;数据类型 Debug&lt;/h3&gt;
&lt;p&gt;由于 SAM3 的发布时间距今并不长，因此官方代码中仍然存在不少的 bugs。在运行官方接口之前，需要先修改 &lt;code&gt;sam3/sam3/perflib/fused.py&lt;/code&gt; 中的 &lt;code&gt;addmm_act()&lt;/code&gt; 函数:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def addmm_act(activation, linear, mat1):
    if torch.is_grad_enabled():
        raise ValueError(&quot;Expected grad to be disabled.&quot;)
    self = linear.bias.detach()
    mat2 = linear.weight.detach()
    # keep the original dtype so we can cast the result back
    out_dtype = linear.weight.dtype if hasattr(linear, &quot;weight&quot;) else mat1.dtype
    self = self.to(torch.bfloat16)
    mat1 = mat1.to(torch.bfloat16)
    mat2 = mat2.to(torch.bfloat16)
    mat1_flat = mat1.view(-1, mat1.shape[-1])
    if activation in [torch.nn.functional.relu, torch.nn.ReLU]:
        y = addmm_act_op(self, mat1_flat, mat2.t(), beta=1, alpha=1, use_gelu=False)
        return y.view(mat1.shape[:-1] + (y.shape[-1],)).to(out_dtype)
    if activation in [torch.nn.functional.gelu, torch.nn.GELU]:
        y = addmm_act_op(self, mat1_flat, mat2.t(), beta=1, alpha=1, use_gelu=True)
        return y.view(mat1.shape[:-1] + (y.shape[-1],)).to(out_dtype)
    raise ValueError(f&quot;Unexpected activation {activation}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改完成后，就可以运行下列代码了。&lt;/p&gt;
&lt;h3&gt;单张图像预测——文本提示&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import torch
from PIL import Image
from sam3.model_builder import build_sam3_image_model
from sam3.model.sam3_image_processor import Sam3Processor


# Load the model
model = build_sam3_image_model(load_from_HF=False, checkpoint_path=&quot;your/path/to/sam3.pt&quot;)
print(model.device)

processor = Sam3Processor(model)
# Load an image
image = Image.open(&quot;input.jpg&quot;)
inference_state = processor.set_image(image)
# Prompt the model with text
output = processor.set_text_prompt(state=inference_state, prompt=&quot;A man in the red.&quot;)

# Get the masks, bounding boxes, and scores
masks, boxes, scores = output[&quot;masks&quot;], output[&quot;boxes&quot;], output[&quot;scores&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出的 &lt;code&gt;output&lt;/code&gt; 是一个字典，格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;original_height&quot;: int,                 # 输入图像的原始高度 H
    &quot;original_width&quot;: int,                  # 输入图像的原始宽度 W

    # 视觉-语言骨干网络输出（SAM3VLBackbone）
    &quot;backbone_out&quot;: dict(                   # 主干网络的输出特征图
        &quot;vision_features&quot;: Tensor,          # 多尺度视觉特征图 [B, 256, 72, 72]
        &quot;vision_pos_enc&quot;: list[Tensor],     # 位置编码（多层级），len=3
        &quot;backbone_fpn&quot;: list[Tensor],       # FPN 各层输出，len=3
        &quot;sam2_backbone_out&quot;: dict | None,   # SAM2 兼容模式输出

        # 文本提示编码器输出
        &quot;language_features&quot;: Tensor,        # 文本语义特征 (seq_len, B, 256)
        &quot;language_mask&quot;: boolean Tensor,    # Attention掩码 (B, seq_len)
        &quot;language_embeds&quot;: Tensor,          # Token级嵌入 (seq_len, B, 1024)
    ),

    # 几何提示编码器输出
    &quot;geometric_prompt&quot;: Prompt Object,      # 编码后的点、框、掩码几何提示

    # 检测解码器最终输出
    &quot;masks_logits&quot;: Tensor,                 # 原始logits (N, 1, H, W)
    &quot;masks&quot;: boolean Tensor,                # 二值掩码 (N, 1, H, W)
    &quot;boxes&quot;: Tensor,                        # 边界框 (N, 4) (x1, y1, x2, y2)
    &quot;scores&quot;: Tensor,                       # 置信度分数 (N, )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;视频预测器修改&lt;/h3&gt;
&lt;p&gt;考虑到我们使用的不是 huggingface 上的模型权重，因此需要为 &lt;code&gt;sam3/sam3/model/sam3_video_predictor.py&lt;/code&gt; 添加 &lt;code&gt;load_from_HF&lt;/code&gt; 参数:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Sam3VideoPredictor(Sam3BasePredictor):
    def __init__(
        self,
        checkpoint_path=None,
        load_from_HF=True,          # 新增参数，控制是否从 huggingface 加载模型权重
        bpe_path=None,
        has_presence_token=True,
        geo_encoder_use_img_cross_attn=True,
        strict_state_dict_loading=True,
        async_loading_frames=False,
        video_loader_type=&quot;cv2&quot;,
        apply_temporal_disambiguation: bool = True,
        compile: bool = False,
    ):
        super().__init__()
        self.async_loading_frames = async_loading_frames
        self.video_loader_type = video_loader_type
        from sam3.model_builder import build_sam3_video_model

        self.model = (
            build_sam3_video_model(
                checkpoint_path=checkpoint_path,
                load_from_HF=load_from_HF,              # 传递参数控制加载方式
                bpe_path=bpe_path,
                has_presence_token=has_presence_token,
                geo_encoder_use_img_cross_attn=geo_encoder_use_img_cross_attn,
                strict_state_dict_loading=strict_state_dict_loading,
                apply_temporal_disambiguation=apply_temporal_disambiguation,
                compile=compile,
            )
            .cuda()
            .eval()
        )
    
    # 其余功能函数不做修改
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是因为 &lt;code&gt;build_sam3_video_model&lt;/code&gt; 函数中确实有 &lt;code&gt;load_from_HF&lt;/code&gt; 的参数控制，但是在 &lt;code&gt;Sam3VideoPredictor&lt;/code&gt; 的构造函数中并没有传递这个参数，因此默认是从 huggingface 加载模型权重的，这样就会导致我们无法使用本地下载的模型权重了。&lt;/p&gt;
&lt;h3&gt;结合文本提示的视频预测&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from sam3.model_builder import build_sam3_video_predictor

def video_predict(video_path: str, text_prompt: str, sam3_checkpoint_path: str = &quot;your/path/to/sam3.pt&quot;):
    # 加载模型
    video_predictor = build_sam3_video_predictor(
        load_from_HF=False, 
        checkpoint_path=sam3_checkpoint_path
    )
    
    # 开启一个 session，逐帧把视频的 RGB 像素压缩成特征图
    start_response = video_predictor.handle_request(
        request=dict(
            type=&quot;start_session&quot;,
            resource_path=video_path,
        )
    )

    # 得到 session_id，后续的所有请求都要带上这个 ID，底层才能把它们关联到同一个视频上，进行正确的时序跟踪和掩码传播
    # 模型可以同时处理不同的 sessions，每个 session 代表一个视频，互不干扰
    session_id = start_response[&quot;session_id&quot;]
    print(&quot;Session started with ID:&quot;, session_id)

    # 为当前的 session 增加文本提示，触发给定帧的 Mask 预测
    response = video_predictor.handle_request(
        request=dict(
            type=&quot;add_prompt&quot;,
            session_id=session_id,
            frame_index=0,                      # 可以是任意的帧
            text=text_prompt,
        )
    )
    # 观察初始检测到多少个与文本提示相关的对象（Mask），这些对象都会被记录，并在整个视频中被跟踪
    output = response[&quot;outputs&quot;]
    print(f&quot;The number of initial predicted objects related to the prompt \&quot;{text_prompt}\&quot; is &quot;, len(output.get(&quot;out_binary_masks&quot;, [])))

    print(&quot;---------- Starting Video Propagation ----------&quot;)
    outputs_per_frame = {}
    for stream_resp in video_predictor.handle_stream_request(
        request=dict(
            type=&quot;propagate_in_video&quot;,
            session_id=session_id,
        )
    ):
        f_idx = stream_resp[&quot;frame_index&quot;]                  # 异步执行，实际上 tqdm 的进度条和帧预测的时间进度是不同的，预测帧的操作比特征提取的操作要慢好几个时间步
        outputs_per_frame[f_idx] = stream_resp[&quot;outputs&quot;]

    return outputs_per_frame


video_path = &quot;your/path/to/video.mp4&quot;     # 或者 &quot;your/path/to/image_folder&quot; (JPG 图像文件夹或视频文件)
text_prompt = &quot;People&quot;
outputs_per_frame = video_predict(video_path, text_prompt)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出的 &lt;code&gt;outputs_per_frame&lt;/code&gt; 是一个字典套字典，键是帧索引，值是一个包含预测结果的字典。每一帧的预测结果的字典格式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    # 对象标识
    &quot;out_obj_ids&quot;: np.ndarray[int64],        # 形状: (N,)，跟踪对象的唯一ID
    
    # 置信度分数
    &quot;out_probs&quot;: np.ndarray[float32],        # 形状: (N,)，每个对象的检测置信度
    
    # 边界框（归一化坐标）
    &quot;out_boxes_xywh&quot;: np.ndarray[float32],   # 形状: (N, 4)，格式: [x_center, y_center, width, height]
                                              # ⚠️ 注意：是中心点坐标 + 宽高，且是归一化的（0-1范围）
    
    # 二值掩码
    &quot;out_binary_masks&quot;: np.ndarray[bool],    # 形状: (N, H, W)，每个对象的像素级分割掩码，这个也是最常用的预测掩码结果
    
    # 帧统计信息
    &quot;frame_stats&quot;: {
        &quot;num_obj_tracked&quot;: int,               # 当前成功跟踪的对象数量
        &quot;num_obj_dropped&quot;: int                # 丢失/被丢弃的对象数量
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;SAM3 接口文档&lt;/h2&gt;
&lt;p&gt;本文档记录了 SAM 3 (包括 SAM 3.1 Multiplex) 项目中所有的核心预测类、特征提取类、核心组件极其所有的内部方法。相较于 SAM 2，SAM 3/3.1 在架构的广度与抽象调度上进行了成倍扩充，以支持多目标（Multiplex）、视觉-语言多模态关联（Text/Vision-Language Prompts）及高并发会话（Session）。&lt;/p&gt;
&lt;h2&gt;1. 顶层分发控制器与预测机 (Predictors &amp;amp; Top-level Wrappers)&lt;/h2&gt;
&lt;h3&gt;1.1 &lt;code&gt;Sam3BasePredictor&lt;/code&gt; (&lt;code&gt;model/sam3_base_predictor.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: SAM 3 和 SAM 3.1 视频预测器的共享底层基类，提供统一的推流、请求接管与长驻内存的会话注册表（Session Manager）机制。负责会话管理（启动、重置、关闭）、请求分发（handle_request / handle_stream_request）以及通用的 add_prompt / propagate_in_video / remove_object / reset_session / close_session 方法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;底层 SAM3 模型，子类必须设置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_all_inference_states&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Dict[str, dict]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;会话ID到推理状态的映射，记录多个视频独立追踪并发状态&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化基础预测器，设置 &lt;code&gt;model&lt;/code&gt; 和 &lt;code&gt;_all_inference_states&lt;/code&gt; 为 None/空字典&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;handle_request&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;request&lt;/code&gt;: dict (&lt;code&gt;type&lt;/code&gt;: str, ...)&lt;/td&gt;
&lt;td&gt;dict 或抛出异常&lt;/td&gt;
&lt;td&gt;根据请求类型分发到对应方法（start_session, add_prompt, remove_object, reset_session, cancel_propagation, close_session）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;handle_stream_request&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;request&lt;/code&gt;: dict (&lt;code&gt;type&lt;/code&gt;: str, ...)&lt;/td&gt;
&lt;td&gt;生成器，yield dict&lt;/td&gt;
&lt;td&gt;处理流式请求，目前仅支持 &lt;code&gt;propagate_in_video&lt;/code&gt; 类型，返回帧结果流&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;start_session&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;resource_path&lt;/code&gt;: str&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;session_id&lt;/code&gt;: str 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;offload_video_to_cpu&lt;/code&gt;: bool = False&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;session_id&quot;: str}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;启动新推理会话，加载视频资源，初始化模型状态，分配唯一会话ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;session_id&lt;/code&gt;: str&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;frame_idx&lt;/code&gt;: int&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;text&lt;/code&gt;: str 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;points&lt;/code&gt;: &lt;code&gt;[P,2]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;point_labels&lt;/code&gt;: &lt;code&gt;[P]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;clear_old_points&lt;/code&gt;: bool = True&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;bounding_boxes&lt;/code&gt;: &lt;code&gt;[B,4]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;bounding_box_labels&lt;/code&gt;: &lt;code&gt;[B]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;clear_old_boxes&lt;/code&gt;: bool = True&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;output_prob_thresh&lt;/code&gt;: float = 0.5&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;obj_id&lt;/code&gt;: int 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;frame_index&quot;: int, &quot;outputs&quot;: dict}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在指定视频帧添加文本、点和/或框提示，运行单帧推理，返回临时输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remove_object&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;session_id&lt;/code&gt;: str&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;frame_idx&lt;/code&gt;: int = 0&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;obj_id&lt;/code&gt;: int = 0&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;is_user_action&lt;/code&gt;: bool = True&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;frame_index&quot;: int, &quot;outputs&quot;: dict}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从跟踪中移除对象，清理相关状态，返回更新后的掩码（可能为空）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cancel_propagation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;session_id&lt;/code&gt;: str&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;is_success&quot;: bool}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;取消正在进行的传播（如果模型支持）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;propagate_in_video&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;session_id&lt;/code&gt;: str&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;propagation_direction&lt;/code&gt;: str = &quot;both&quot;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;start_frame_idx&lt;/code&gt;: int 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;max_frame_num_to_track&lt;/code&gt;: int 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;output_prob_thresh&lt;/code&gt;: float = 0.5&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成器，yield &lt;code&gt;{&quot;frame_index&quot;: int, &quot;outputs&quot;: dict}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将已添加的提示传播到所有视频帧，支持正向、反向或双向传播&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset_session&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;session_id&lt;/code&gt;: str&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;is_success&quot;: bool}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;重置会话到初始状态，清空所有提示和跟踪结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;close_session&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;session_id&lt;/code&gt;: str&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;run_gc_collect&lt;/code&gt;: bool = True&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;is_success&quot;: bool}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;关闭会话，释放资源，可选运行垃圾回收&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_session&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;session_id&lt;/code&gt;: str&lt;/td&gt;
&lt;td&gt;dict (会话对象)&lt;/td&gt;
&lt;td&gt;内部方法，根据会话ID获取会话字典，不存在则抛出异常&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_extend_expiration_time&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;session&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;更新会话的最后使用时间，用于会话过期跟踪&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shutdown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;关闭预测器，清空所有会话&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;1.2 &lt;code&gt;Sam3MultiplexVideoPredictor&lt;/code&gt; (&lt;code&gt;model/sam3_multiplex_video_predictor.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 面向用户的 SAM 3.1 Multiplex（多路复用多目标追踪）预测容器。集成了 BF16 精度上下文和 Torch Dynamo (&lt;code&gt;torch.compile&lt;/code&gt;) 预热机制。包装了 Sam3MultiplexTrackingWithInteractivity，提供 bf16 自动转换、预热编译、会话过期管理以及从 Sam3BasePredictor 继承的 handle_request / handle_stream_request 分发 API。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sam3MultiplexTrackingWithInteractivity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;底层多路复用跟踪模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;session_expiration_sec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;td&gt;会话过期时间（秒）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;default_output_prob_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;默认输出概率阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;async_loading_frames&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否异步加载视频帧&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bf16_context&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.autocast&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;启用 bfloat16 的上下文&lt;/td&gt;
&lt;td&gt;用于 Flash Attention 内核的 bfloat16 自动转换上下文&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_all_inference_states&lt;/code&gt; (继承)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Dict[str, dict]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;来自 Sam3BasePredictor 的会话映射&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;: Sam3MultiplexTrackingWithInteractivity&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;session_expiration_sec&lt;/code&gt;: int = 1200&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;default_output_prob_thresh&lt;/code&gt;: float = 0.5&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;async_loading_frames&lt;/code&gt;: bool = True&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;warm_up&lt;/code&gt;: bool = False&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化预测器，设置模型和配置，启用 TF32 和 BF16，可选预热编译&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_extend_expiration_time&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;session&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;更新会话的最后使用时间，并存储会话过期超时时间&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;注意：此类继承自 &lt;code&gt;Sam3BasePredictor&lt;/code&gt;，因此也拥有所有基类方法（&lt;code&gt;handle_request&lt;/code&gt;、&lt;code&gt;start_session&lt;/code&gt;、&lt;code&gt;add_prompt&lt;/code&gt; 等）。&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;1.3 &lt;code&gt;Sam3VideoPredictor&lt;/code&gt; (&lt;code&gt;model/sam3_video_predictor.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 非复用（Non-multiplex）版本的基础 SAM 3 Tracker 的对外用户封装，保持了与 SAM 2 &lt;code&gt;SAM2VideoPredictor&lt;/code&gt; 极为相似的接口风格（以向后兼容单目标/精简模式追踪）。继承自 Sam3BasePredictor，负责加载 SAM3 视频模型，提供单 GPU 推理。同时包含多 GPU 扩展类 &lt;code&gt;Sam3VideoPredictorMultiGPU&lt;/code&gt; 以支持分布式推理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt; (Sam3VideoPredictor):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;通过 &lt;code&gt;build_sam3_video_model&lt;/code&gt; 构建的 SAM3 视频模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;async_loading_frames&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否异步加载视频帧&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;video_loader_type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;cv2&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;视频加载器类型（如 &quot;cv2&quot;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_all_inference_states&lt;/code&gt; (继承)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Dict[str, dict]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;来自 Sam3BasePredictor 的会话映射&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt; (Sam3VideoPredictor):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;checkpoint_path&lt;/code&gt;: str 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;bpe_path&lt;/code&gt;: str 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;has_presence_token&lt;/code&gt;: bool = True&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;geo_encoder_use_img_cross_attn&lt;/code&gt;: bool = True&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;strict_state_dict_loading&lt;/code&gt;: bool = True&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;async_loading_frames&lt;/code&gt;: bool = False&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;video_loader_type&lt;/code&gt;: str = &quot;cv2&quot;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;apply_temporal_disambiguation&lt;/code&gt;: bool = True&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;compile&lt;/code&gt;: bool = False&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化预测器，构建 SAM3 视频模型并移至 GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remove_object&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;session_id&lt;/code&gt;: str&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;frame_idx&lt;/code&gt;: int = 0&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;obj_id&lt;/code&gt;: int = 0&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;is_user_action&lt;/code&gt;: bool = True&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&quot;is_success&quot;: bool}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;移除跟踪对象（简化 API，直接调用模型的 remove_object）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_session_stats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;str&lt;/td&gt;
&lt;td&gt;获取活动会话统计信息和 GPU 内存使用情况&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_torch_and_gpu_properties&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;str&lt;/td&gt;
&lt;td&gt;获取 PyTorch 和 GPU 属性字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;类 &lt;code&gt;Sam3VideoPredictorMultiGPU&lt;/code&gt; (继承自 &lt;code&gt;Sam3VideoPredictor&lt;/code&gt;)&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gpus_to_use&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;List[int]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前 GPU 设备列表&lt;/td&gt;
&lt;td&gt;要使用的 GPU ID 列表&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rank&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从环境变量 &lt;code&gt;RANK&lt;/code&gt; 读取&lt;/td&gt;
&lt;td&gt;当前进程的排名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;world_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从环境变量 &lt;code&gt;WORLD_SIZE&lt;/code&gt; 读取&lt;/td&gt;
&lt;td&gt;进程总数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rank_str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自动生成&lt;/td&gt;
&lt;td&gt;排名和世界大小的字符串表示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cuda:{gpus_to_use[rank]}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前进程的设备&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;has_shutdown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否已关闭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;command_queues&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Dict[int, mp.Queue]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;与工作进程通信的命令队列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;result_queues&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Dict[int, mp.Queue]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;结果队列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;worker_pids&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Dict[int, int]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;工作进程的 PID&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*model_args&lt;/code&gt;, &lt;code&gt;gpus_to_use=None&lt;/code&gt;, &lt;code&gt;**model_kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化多 GPU 预测器，设置分布式环境，启动工作进程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;handle_request&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;request&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;dict&lt;/td&gt;
&lt;td&gt;分发请求到所有工作进程（主进程）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;handle_stream_request&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;request&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;生成器，yield dict&lt;/td&gt;
&lt;td&gt;分发流式请求到所有工作进程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_start_worker_processes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*model_args&lt;/code&gt;, &lt;code&gt;**model_kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;启动工作进程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_start_nccl_process_group&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化 NCCL 进程组，执行预热 all-reduce&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_find_free_port&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;查找空闲端口用于分布式通信&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_worker_process_command_loop&lt;/code&gt; (静态方法)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rank&lt;/code&gt;, &lt;code&gt;world_size&lt;/code&gt;, &lt;code&gt;command_queue&lt;/code&gt;, &lt;code&gt;result_queue&lt;/code&gt;, &lt;code&gt;model_args&lt;/code&gt;, &lt;code&gt;model_kwargs&lt;/code&gt;, &lt;code&gt;gpus_to_use&lt;/code&gt;, &lt;code&gt;parent_pid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;工作进程的命令循环，监听并执行命令&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shutdown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;关闭所有工作进程，销毁进程组&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;1.4 &lt;code&gt;Sam3Processor&lt;/code&gt; (&lt;code&gt;model/sam3_image_processor.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 面向单张/多张无关图像推理过程的封装调度机，自动处理 &lt;code&gt;torchvision.transforms.v2&lt;/code&gt; 以及基础数据状态对齐。负责图像预处理、文本/几何提示添加、前向推理及结果后处理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;底层 SAM3 模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;resolution&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1008&lt;/td&gt;
&lt;td&gt;输入图像分辨率（调整大小后的边长）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt; 或 &lt;code&gt;torch.device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;cuda&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;计算设备&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transform&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torchvision.transforms.v2.Compose&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;预处理流水线&lt;/td&gt;
&lt;td&gt;图像转换：缩放、归一化等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;confidence_threshold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;置信度阈值，用于过滤预测结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;find_stage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FindStage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;预初始化的查找阶段对象&lt;/td&gt;
&lt;td&gt;包含文本ID、图像ID等，用于前向传播&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;: nn.Module&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;resolution&lt;/code&gt;: int = 1008&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;device&lt;/code&gt;: str = &quot;cuda&quot;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;confidence_threshold&lt;/code&gt;: float = 0.5&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化处理器，设置模型、分辨率、设备及预处理流水线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;set_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;: PIL.Image 或 Tensor 或 np.ndarray&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;state&lt;/code&gt;: dict 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;设置单张图像，提取主干特征并存入状态字典&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;set_image_batch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;images&lt;/code&gt;: List[PIL.Image]&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;state&lt;/code&gt;: dict 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;设置图像批次，提取主干特征并存入状态字典&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;set_text_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prompt&lt;/code&gt;: str&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;设置文本提示，运行推理，返回更新后的状态（包含检测框、掩码、分数）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_geometric_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;box&lt;/code&gt;: List[float] ([center_x, center_y, width, height]，归一化到 [0,1])&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;label&lt;/code&gt;: bool (True 为正，False 为负)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;添加几何框提示，运行推理，返回更新后的状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset_all_prompts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;清除所有提示和结果，重置状态字典中的相关键&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;set_confidence_threshold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;threshold&lt;/code&gt;: float&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;state&lt;/code&gt;: dict 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state&lt;/code&gt;: dict 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设置置信度阈值，若提供状态则重新过滤结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_forward_grounding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;内部前向传播，调用模型的 &lt;code&gt;forward_grounding&lt;/code&gt;，处理输出并更新状态&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;2. 视频与时序追踪逻辑引擎 (Video &amp;amp; Tracking Base Models)&lt;/h2&gt;
&lt;h3&gt;2.1 &lt;code&gt;Sam3TrackerBase&lt;/code&gt; (&lt;code&gt;model/sam3_tracker_base.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: SAM 3 视频跟踪的核心基类，整合图像主干网络、Transformer 编码器模块和过去帧记忆的 Cross-Attention 系统。负责管理时序记忆、特征融合、SAM 头推理以及帧间跟踪状态维护。继承自 &lt;code&gt;torch.nn.Module&lt;/code&gt;，是所有跟踪器的基础架构。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;图像主干网络，用于提取视觉特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_feature_levels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;特征金字塔层级数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_obj_ptrs_in_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 16)&lt;/td&gt;
&lt;td&gt;编码器中最大对象指针数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_downsample&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Conv2d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Conv2d(1, 1, kernel_size=4, stride=4)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将 GT 掩码下采样到 stride 4 的卷积层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;仅编码器的 Transformer，用于融合当前帧视觉特征与过去帧记忆&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hidden_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;transformer.d_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transformer 隐藏维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maskmem_backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;记忆编码器骨干网络（如 &lt;code&gt;SimpleMaskEncoder&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mem_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hidden_dim&lt;/code&gt; 或 &lt;code&gt;maskmem_backbone.out_proj.weight.shape[0]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;记忆特征维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_maskmem&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 7)&lt;/td&gt;
&lt;td&gt;可访问的记忆数量（1 输入帧 + 6 历史帧）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maskmem_tpos_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.zeros(num_maskmem, 1, 1, mem_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;记忆的时间位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_mem_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.zeros(1, 1, hidden_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;表示无记忆嵌入的 token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_mem_pos_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.zeros(1, 1, hidden_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无记忆位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sigmoid_scale_for_mem_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20.0&lt;/td&gt;
&lt;td&gt;记忆编码器 sigmoid 缩放因子&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sigmoid_bias_for_mem_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-10.0&lt;/td&gt;
&lt;td&gt;记忆编码器 sigmoid 偏置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;non_overlap_masks_for_mem_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 False)&lt;/td&gt;
&lt;td&gt;是否在记忆编码中对掩码应用非重叠约束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory_temporal_stride_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 1)&lt;/td&gt;
&lt;td&gt;评估时记忆库的时间步长（类似 XMem 的 r 参数）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_output_in_sam&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 False)&lt;/td&gt;
&lt;td&gt;是否在初始条件帧上输出多个掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_min_pt_num&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 1)&lt;/td&gt;
&lt;td&gt;使用多掩码输出的最小点数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_max_pt_num&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 1)&lt;/td&gt;
&lt;td&gt;使用多掩码输出的最大点数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_output_for_tracking&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 False)&lt;/td&gt;
&lt;td&gt;是否在跟踪时也使用多掩码输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 1008)&lt;/td&gt;
&lt;td&gt;输入图像尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backbone_stride&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 14)&lt;/td&gt;
&lt;td&gt;图像主干输出步长&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;low_res_mask_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image_size // backbone_stride * 4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;低分辨率掩码尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input_mask_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;low_res_mask_size * 4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入掩码尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_backbone_per_frame_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 False)&lt;/td&gt;
&lt;td&gt;评估时是否逐帧前向传播主干网络以避免 OOM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;offload_output_to_cpu_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 False)&lt;/td&gt;
&lt;td&gt;评估时是否将输出卸载到 CPU 内存以避免 GPU OOM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trim_past_non_cond_mem_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 False)&lt;/td&gt;
&lt;td&gt;评估时是否修剪过去的非条件帧记忆以节省内存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sam_mask_decoder_extra_args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 None)&lt;/td&gt;
&lt;td&gt;传递给 SAM 掩码解码器的额外参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_obj_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.zeros(1, hidden_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_obj_embed_spatial&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.zeros(1, mem_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;空间记忆的无对象嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sam_prompt_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PromptEncoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;构建时创建&lt;/td&gt;
&lt;td&gt;SAM 提示编码器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sam_mask_decoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MaskDecoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;构建时创建&lt;/td&gt;
&lt;td&gt;SAM 掩码解码器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;obj_ptr_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MLP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MLP(hidden_dim, hidden_dim, hidden_dim, 3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对象指针投影层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;obj_ptr_tpos_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Linear(hidden_dim, mem_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对象指针时间位置投影层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_cond_frames_in_attn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 -1，无限制)&lt;/td&gt;
&lt;td&gt;注意力中最大条件帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;keep_first_cond_frame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 False)&lt;/td&gt;
&lt;td&gt;是否始终保留第一个条件帧&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_memory_selection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 False)&lt;/td&gt;
&lt;td&gt;是否使用记忆选择（类似 SAM2Long）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mf_threshold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 0.01)&lt;/td&gt;
&lt;td&gt;记忆选择的阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_all_components&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从参数传入 (默认 False)&lt;/td&gt;
&lt;td&gt;是否编译所有组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;device&lt;/code&gt; (property)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自动计算&lt;/td&gt;
&lt;td&gt;模型参数所在的设备&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表格&lt;/strong&gt; (传递给 &lt;code&gt;__init__&lt;/code&gt; 的配置):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;图像主干网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;仅编码器的 Transformer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maskmem_backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;记忆编码器骨干网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_maskmem&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;可访问的记忆数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1008&lt;/td&gt;
&lt;td&gt;输入图像尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backbone_stride&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;图像主干输出步长&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_cond_frames_in_attn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-1&lt;/td&gt;
&lt;td&gt;注意力中最大条件帧数（-1 表示无限制）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;keep_first_cond_frame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;是否始终保留第一个条件帧&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_output_in_sam&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;是否在初始条件帧上输出多个掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_min_pt_num&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;使用多掩码输出的最小点数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_max_pt_num&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;使用多掩码输出的最大点数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_output_for_tracking&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;是否在跟踪时也使用多掩码输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_backbone_per_frame_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;评估时是否逐帧前向传播主干网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory_temporal_stride_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;评估时记忆库的时间步长&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;offload_output_to_cpu_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;评估时是否将输出卸载到 CPU 内存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trim_past_non_cond_mem_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;评估时是否修剪过去的非条件帧记忆&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;non_overlap_masks_for_mem_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;是否在记忆编码中对掩码应用非重叠约束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_obj_ptrs_in_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;编码器中最大对象指针数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sam_mask_decoder_extra_args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;传递给 SAM 掩码解码器的额外参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_all_components&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;是否编译所有组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_memory_selection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;是否使用记忆选择&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mf_threshold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;记忆选择的阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;见上表&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化跟踪器基类，构建 SAM 头组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_tpos_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rel_pos_list&lt;/code&gt;, &lt;code&gt;device&lt;/code&gt;, &lt;code&gt;max_abs_pos=None&lt;/code&gt;, &lt;code&gt;dummy=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tensor&lt;/code&gt;: [len(rel_pos_list), mem_dim]&lt;/td&gt;
&lt;td&gt;获取时间位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_build_sam_heads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;构建 SAM 风格的提示编码器和掩码解码器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_forward_sam_heads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_features&lt;/code&gt;, &lt;code&gt;point_inputs=None&lt;/code&gt;, &lt;code&gt;mask_inputs=None&lt;/code&gt;, &lt;code&gt;high_res_features=None&lt;/code&gt;, &lt;code&gt;multimask_output=False&lt;/code&gt;, &lt;code&gt;gt_masks=None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;low_res_multimasks&lt;/code&gt;, &lt;code&gt;high_res_multimasks&lt;/code&gt;, &lt;code&gt;ious&lt;/code&gt;, &lt;code&gt;low_res_masks&lt;/code&gt;, &lt;code&gt;high_res_masks&lt;/code&gt;, &lt;code&gt;obj_ptr&lt;/code&gt;, &lt;code&gt;object_score_logits&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;前向传播 SAM 提示编码器和掩码头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_use_mask_as_output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_features&lt;/code&gt;, &lt;code&gt;high_res_features&lt;/code&gt;, &lt;code&gt;mask_inputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;low_res_masks&lt;/code&gt;, &lt;code&gt;high_res_masks&lt;/code&gt;, &lt;code&gt;ious&lt;/code&gt;, &lt;code&gt;low_res_masks&lt;/code&gt;, &lt;code&gt;high_res_masks&lt;/code&gt;, &lt;code&gt;obj_ptr&lt;/code&gt;, &lt;code&gt;object_score_logits&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;直接将二进制掩码输入转换为输出掩码 logits，绕过 SAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;: &lt;code&gt;BatchedDatapoint&lt;/code&gt;, &lt;code&gt;is_inference=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;抛出 &lt;code&gt;NotImplementedError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;抽象前向方法，需在子类中实现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;img_batch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;: 骨干网络输出&lt;/td&gt;
&lt;td&gt;获取输入批次的图像特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_prepare_backbone_features&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;backbone_out&lt;/code&gt;, &lt;code&gt;vision_feats&lt;/code&gt;, &lt;code&gt;vision_pos_embeds&lt;/code&gt;, &lt;code&gt;feat_sizes&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;准备并展平视觉特征（类似 MDETR_API）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_prepare_backbone_features_per_frame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;img_batch&lt;/code&gt;, &lt;code&gt;img_ids&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;image&lt;/code&gt;, &lt;code&gt;vision_feats&lt;/code&gt;, &lt;code&gt;vision_pos_embeds&lt;/code&gt;, &lt;code&gt;feat_sizes&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;为给定图像 ID 动态计算图像骨干特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cal_mem_score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;object_score_logits&lt;/code&gt;, &lt;code&gt;iou_score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;: 记忆分数&lt;/td&gt;
&lt;td&gt;计算当前掩码被记入历史追踪记忆池的重要程度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;frame_filter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output_dict&lt;/code&gt;, &lt;code&gt;track_in_reverse&lt;/code&gt;, &lt;code&gt;frame_idx&lt;/code&gt;, &lt;code&gt;num_frames&lt;/code&gt;, &lt;code&gt;r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;List[int]&lt;/code&gt;: 有效帧索引列表&lt;/td&gt;
&lt;td&gt;帧级时序筛选器，抛弃置信度低的信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_prepare_memory_conditioned_features&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;, &lt;code&gt;is_init_cond_frame&lt;/code&gt;, &lt;code&gt;current_vision_feats&lt;/code&gt;, &lt;code&gt;current_vision_pos_embeds&lt;/code&gt;, &lt;code&gt;feat_sizes&lt;/code&gt;, &lt;code&gt;output_dict&lt;/code&gt;, &lt;code&gt;num_frames&lt;/code&gt;, &lt;code&gt;track_in_reverse=False&lt;/code&gt;, &lt;code&gt;use_prev_mem_frame=True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tensor&lt;/code&gt;: [B, C, H, W]&lt;/td&gt;
&lt;td&gt;将当前帧的视觉特征与先前记忆融合&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_encode_new_memory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;, &lt;code&gt;current_vision_feats&lt;/code&gt;, &lt;code&gt;feat_sizes&lt;/code&gt;, &lt;code&gt;pred_masks_high_res&lt;/code&gt;, &lt;code&gt;object_score_logits&lt;/code&gt;, &lt;code&gt;is_mask_from_pts&lt;/code&gt;, &lt;code&gt;output_dict=None&lt;/code&gt;, &lt;code&gt;is_init_cond_frame=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;maskmem_features&lt;/code&gt;, &lt;code&gt;maskmem_pos_enc&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;将当前图像及其预测编码为记忆特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_tracking&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;return_dict=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;list&lt;/code&gt; 或 &lt;code&gt;dict&lt;/code&gt;: 所有帧的输出&lt;/td&gt;
&lt;td&gt;在每个帧上执行视频跟踪（并采样校正点击）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;track_step&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;, &lt;code&gt;is_init_cond_frame&lt;/code&gt;, &lt;code&gt;current_vision_feats&lt;/code&gt;, &lt;code&gt;current_vision_pos_embeds&lt;/code&gt;, &lt;code&gt;feat_sizes&lt;/code&gt;, &lt;code&gt;image&lt;/code&gt;, &lt;code&gt;point_inputs&lt;/code&gt;, &lt;code&gt;mask_inputs&lt;/code&gt;, &lt;code&gt;output_dict&lt;/code&gt;, &lt;code&gt;num_frames&lt;/code&gt;, &lt;code&gt;track_in_reverse=False&lt;/code&gt;, &lt;code&gt;run_mem_encoder=True&lt;/code&gt;, &lt;code&gt;prev_sam_mask_logits=None&lt;/code&gt;, &lt;code&gt;use_prev_mem_frame=True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;: 当前帧的输出&lt;/td&gt;
&lt;td&gt;单帧跟踪步骤，处理提示并生成掩码预测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_use_multimask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;is_init_cond_frame&lt;/code&gt;, &lt;code&gt;point_inputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;: 是否使用多掩码输出&lt;/td&gt;
&lt;td&gt;决定是否在 SAM 头中使用多掩码输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_apply_non_overlapping_constraints&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pred_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tensor&lt;/code&gt;: 应用非重叠约束后的掩码&lt;/td&gt;
&lt;td&gt;对预测掩码应用非重叠约束（在批量维度）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_compile_all_components&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;编译所有模型组件以加速推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_maybe_clone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tensor&lt;/code&gt;: 克隆或原始张量&lt;/td&gt;
&lt;td&gt;如果 &lt;code&gt;compile_all_components&lt;/code&gt; 为 True 则克隆张量&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;静态方法&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;concat_points(old_point_inputs, new_points, new_labels)&lt;/code&gt;: 将新点和标签添加到先前的点输入中（添加到末尾）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 &lt;code&gt;Sam3MultiplexTracking&lt;/code&gt; 等族系 (&lt;code&gt;sam3_multiplex_tracking.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: SAM 3.1 多路复用（Multiplex）多目标跟踪的核心实现族系，继承自 &lt;code&gt;Sam3MultiplexBase&lt;/code&gt;（进而继承 &lt;code&gt;Sam3VideoBase&lt;/code&gt;）。承担同时跟踪数十个甚至上百个交织目标的复杂管理任务，支持多GPU分布式推理、热启动延迟（hotstart）、掩码确认（masklet confirmation）、批处理后处理等高级特性。族系包含三个主要类：&lt;code&gt;Sam3MultiplexTracking&lt;/code&gt;（基础版本）、&lt;code&gt;Sam3MultiplexTrackingProd&lt;/code&gt;（生产版本，支持批处理视频）和 &lt;code&gt;Sam3MultiplexTrackingWithInteractivity&lt;/code&gt;（交互增强版本，支持用户操作历史记录与部分传播）。&lt;/p&gt;
&lt;h4&gt;2.2.1 &lt;code&gt;Sam3MultiplexTracking&lt;/code&gt; 基础类&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;继承关系&lt;/strong&gt;: &lt;code&gt;Sam3MultiplexTracking&lt;/code&gt; → &lt;code&gt;Sam3MultiplexBase&lt;/code&gt; → &lt;code&gt;Sam3VideoBase&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt; (Sam3MultiplexTracking 特有属性):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1008&lt;/td&gt;
&lt;td&gt;输入图像尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_mean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tuple&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(0.5, 0.5, 0.5)&lt;/td&gt;
&lt;td&gt;图像归一化均值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_std&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tuple&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(0.5, 0.5, 0.5)&lt;/td&gt;
&lt;td&gt;图像归一化标准差&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否编译模型加速推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;postprocess_batch_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;后处理批大小（积累多少帧后运行后处理）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TEXT_ID_FOR_TEXT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;文本提示的文本ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TEXT_ID_FOR_VISUAL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;视觉提示的文本ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TEXT_ID_FOR_GEOMETRIC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;几何提示的文本ID&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;从 Sam3MultiplexBase 继承的核心属性&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tracker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sam3MultiplexTrackerPredictor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;SAM2 跟踪器预测器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;detector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sam3MultiplexDetector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;多路复用检测器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;score_threshold_detection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;检测输出概率阈值（用于NMS和检测-跟踪匹配）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_only_det_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;仅图像输入的检测阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;det_nms_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;检测NMS的IoU阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;det_nms_use_iom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在NMS中使用IoM（交小比）而非IoU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;assoc_iou_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;检测-跟踪匹配的IoU阈值（宽松阈值，如0.1）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trk_assoc_iou_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;跟踪关联的IoU阈值（严格阈值，如0.5）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;new_det_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;检测作为新对象添加的阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hotstart_delay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;热启动延迟帧数（0表示禁用）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hotstart_unmatch_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;热启动期间未匹配帧数阈值（超过则移除对象）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hotstart_dup_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;热启动期间重叠对象帧数阈值（超过则移除对象）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;suppress_unmatched_only_within_hotstart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否仅在热启动期间抑制未匹配对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;init_trk_keep_alive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;初始跟踪保持活跃帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_trk_keep_alive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;最大跟踪保持活跃帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;min_trk_keep_alive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-4&lt;/td&gt;
&lt;td&gt;最小跟踪保持活跃帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;suppress_overlapping_based_on_recent_occlusion_threshold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;基于最近遮挡的重叠对象抑制阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allow_unoccluded_to_suppress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否允许未遮挡对象抑制遮挡对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decrease_trk_keep_alive_for_empty_masklets&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为空掩码减少跟踪保持活跃计数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;o2o_matching_masklets_enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否启用匈牙利匹配以匹配现有掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;suppress_det_close_to_boundary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否抑制边界附近的检测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fill_hole_area&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;填充空洞的面积阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sprinkle_removal_area&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;洒点移除的面积阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_num_objects&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;跨所有GPU跟踪的最大对象数（-1表示无限制）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_num_kboxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;最大关键框数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;recondition_every_nth_frame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-1&lt;/td&gt;
&lt;td&gt;每N帧重新条件化（-1表示禁用）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_iom_recondition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在重新条件化中使用IoM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iom_thresh_recondition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;重新条件化的IoM阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_thresh_recondition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;重新条件化的IoU阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;is_multiplex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为多路复用模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;running_in_prod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在生产环境（FBInfra）中运行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;masklet_confirmation_enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否启用掩码确认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;masklet_confirmation_consecutive_det_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;掩码确认所需的连续检测帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reconstruction_bbox_iou_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;重建边界框的IoU阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reconstruction_bbox_det_score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;重建边界框的检测分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reapply_no_object_pointer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为抑制对象重新应用无对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_batched_grounding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用批处理接地&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;batched_grounding_batch_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;批处理接地的批大小&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bucket_capacity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从 &lt;code&gt;tracker.multiplex_controller&lt;/code&gt; 获取&lt;/td&gt;
&lt;td&gt;多路复用桶容量（仅当 &lt;code&gt;is_multiplex=True&lt;/code&gt; 时有效）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rank&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从环境变量 &lt;code&gt;RANK&lt;/code&gt; 读取&lt;/td&gt;
&lt;td&gt;当前进程排名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;world_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从环境变量 &lt;code&gt;WORLD_SIZE&lt;/code&gt; 读取&lt;/td&gt;
&lt;td&gt;进程总数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_dist_pg_cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.distributed.ProcessGroup&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CPU进程组（惰性初始化）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_profiler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.profiler.profile&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PyTorch性能分析器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_profiling_enabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从环境变量 &lt;code&gt;ENABLE_PROFILING&lt;/code&gt; 读取&lt;/td&gt;
&lt;td&gt;是否启用性能分析&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表格&lt;/strong&gt; (传递给 &lt;code&gt;Sam3MultiplexTracking.__init__&lt;/code&gt; 的参数):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1008&lt;/td&gt;
&lt;td&gt;输入图像尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_mean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tuple&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(0.5, 0.5, 0.5)&lt;/td&gt;
&lt;td&gt;图像归一化均值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_std&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tuple&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(0.5, 0.5, 0.5)&lt;/td&gt;
&lt;td&gt;图像归一化标准差&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否编译模型加速推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;postprocess_batch_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;后处理批大小&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;传递给父类 &lt;code&gt;Sam3MultiplexBase&lt;/code&gt; 的其余参数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;注: &lt;code&gt;**kwargs&lt;/code&gt; 包含所有 &lt;code&gt;Sam3MultiplexBase&lt;/code&gt; 的初始化参数（见上表）。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt; (Sam3MultiplexTracking 核心方法):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image_size=1008&lt;/code&gt;, &lt;code&gt;image_mean=(0.5,0.5,0.5)&lt;/code&gt;, &lt;code&gt;image_std=(0.5,0.5,0.5)&lt;/code&gt;, &lt;code&gt;compile_model=False&lt;/code&gt;, &lt;code&gt;postprocess_batch_size=1&lt;/code&gt;, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化多路复用跟踪器，设置图像处理参数和编译选项&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_construct_initial_input_batch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;images&lt;/code&gt;: List[torch.Tensor]&lt;/td&gt;
&lt;td&gt;无（修改 &lt;code&gt;inference_state&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;构建初始的 &lt;code&gt;BatchedDatapoint&lt;/code&gt; 输入批次，包含图像批次、查找文本批次和查找输入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_visual_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;boxes_cxcywh&lt;/code&gt;: Tensor, &lt;code&gt;box_labels&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;boxes_cxcywh&lt;/code&gt;, &lt;code&gt;box_labels&lt;/code&gt;, &lt;code&gt;geometric_prompt&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;根据边界框和标签创建视觉提示的几何提示对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;init_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;resource_path&lt;/code&gt;: str, &lt;code&gt;offload_video_to_cpu=False&lt;/code&gt;, &lt;code&gt;async_loading_frames=False&lt;/code&gt;, &lt;code&gt;use_torchcodec=False&lt;/code&gt;, &lt;code&gt;use_cv2=False&lt;/code&gt;, &lt;code&gt;input_is_mp4=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;初始化推理状态，加载视频帧，构建输入批次，设置初始状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无（修改 &lt;code&gt;inference_state&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;重置推理状态到初始状态，清除所有提示和跟踪结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_processing_order&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;start_frame_idx&lt;/code&gt;: int 或 &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;max_frame_num_to_track&lt;/code&gt;: int 或 &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;reverse&lt;/code&gt;: bool&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;processing_order&lt;/code&gt;: range, &lt;code&gt;end_frame_idx&lt;/code&gt;: int)&lt;/td&gt;
&lt;td&gt;根据起始帧、最大跟踪帧数和方向确定处理顺序&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;propagate_in_video&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;start_frame_idx=None&lt;/code&gt;, &lt;code&gt;max_frame_num_to_track=None&lt;/code&gt;, &lt;code&gt;reverse=False&lt;/code&gt;, &lt;code&gt;output_prob_thresh=0.5&lt;/code&gt;, &lt;code&gt;compute_stability_score=False&lt;/code&gt;, &lt;code&gt;is_instance_processing=False&lt;/code&gt;, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成器，yield (&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;output&lt;/code&gt;: dict)&lt;/td&gt;
&lt;td&gt;将提示传播到视频的所有帧，支持热启动延迟和批处理后处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_run_single_frame_inference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;reverse&lt;/code&gt;: bool, &lt;code&gt;is_instance_processing=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;执行单帧推理，更新推理状态，返回对象ID到掩码/分数的映射&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_postprocess_output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;out&lt;/code&gt;: dict, &lt;code&gt;removed_obj_ids=None&lt;/code&gt;, &lt;code&gt;suppressed_obj_ids=None&lt;/code&gt;, &lt;code&gt;unconfirmed_obj_ids=None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;postprocessed_out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;后处理输出：过滤被移除/抑制/未确认的对象，生成最终掩码、分数和边界框&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_cache_frame_outputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;obj_id_to_mask&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无（修改 &lt;code&gt;inference_state[&quot;cached_frame_outputs&quot;]&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;缓存帧输出到推理状态中，供后续获取&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_compile_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;编译模型组件以加速推理（如果 &lt;code&gt;compile_model=True&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remove_object&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;obj_id&lt;/code&gt;: int, &lt;code&gt;is_user_action=True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无（修改 &lt;code&gt;inference_state&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;从跟踪中移除指定对象，清理相关状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;text=None&lt;/code&gt;, &lt;code&gt;points=None&lt;/code&gt;, &lt;code&gt;point_labels=None&lt;/code&gt;, &lt;code&gt;clear_old_points=True&lt;/code&gt;, &lt;code&gt;bounding_boxes=None&lt;/code&gt;, &lt;code&gt;bounding_box_labels=None&lt;/code&gt;, &lt;code&gt;clear_old_boxes=True&lt;/code&gt;, &lt;code&gt;output_prob_thresh=0.5&lt;/code&gt;, &lt;code&gt;obj_id=None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;output&lt;/code&gt;: dict)&lt;/td&gt;
&lt;td&gt;在指定帧添加文本、点或框提示，运行单帧推理，返回临时输出&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;从 Sam3MultiplexBase 继承的重要方法&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_det_track_one_frame(...)&lt;/code&gt;: 执行单帧检测与跟踪（SPMD方式）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_det_track_one_frame_impl(...)&lt;/code&gt;: &lt;code&gt;_det_track_one_frame&lt;/code&gt; 的具体实现&lt;/li&gt;
&lt;li&gt;&lt;code&gt;all_gather_cpu(...)&lt;/code&gt;, &lt;code&gt;all_gather_python_obj_cpu(...)&lt;/code&gt;, &lt;code&gt;broadcast_cpu(...)&lt;/code&gt;: 分布式CPU通信&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_start_profiling(...)&lt;/code&gt;, &lt;code&gt;_stop_profiling(...)&lt;/code&gt;: 性能分析控制&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2.2.2 &lt;code&gt;Sam3MultiplexTrackingProd&lt;/code&gt; 生产版本类&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;继承关系&lt;/strong&gt;: &lt;code&gt;Sam3MultiplexTrackingProd&lt;/code&gt; → &lt;code&gt;Sam3MultiplexTracking&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 支持批处理视频处理的生产版本，可处理大型视频的小批次块以管理内存或跨多个调用分布处理。通过持久化生成器状态（热启动缓冲区、移除对象ID等）在推理状态中跨生成器实例化，支持批处理处理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特有属性&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;generator_state&lt;/code&gt; (在 &lt;code&gt;inference_state&lt;/code&gt; 中): 包含 &lt;code&gt;hotstart_buffer&lt;/code&gt;, &lt;code&gt;hotstart_removed_obj_ids&lt;/code&gt;, &lt;code&gt;unconfirmed_obj_ids_per_frame&lt;/code&gt;, &lt;code&gt;postprocess_yield_list&lt;/code&gt;，用于跨批处理持久化状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;特有方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;init_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;resource_path&lt;/code&gt;: str, &lt;code&gt;offload_video_to_cpu=False&lt;/code&gt;, &lt;code&gt;async_loading_frames=False&lt;/code&gt;, &lt;code&gt;use_torchcodec=False&lt;/code&gt;, &lt;code&gt;use_cv2=False&lt;/code&gt;, &lt;code&gt;input_is_mp4=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;重写父类方法，初始化生成器状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;重写父类方法，重置生成器状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;propagate_in_video&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;start_frame_idx=None&lt;/code&gt;, &lt;code&gt;max_frame_num_to_track=None&lt;/code&gt;, &lt;code&gt;reverse=False&lt;/code&gt;, &lt;code&gt;output_prob_thresh=0.5&lt;/code&gt;, &lt;code&gt;compute_stability_score=False&lt;/code&gt;, &lt;code&gt;is_instance_processing=False&lt;/code&gt;, &lt;code&gt;is_last_batch=True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成器，yield (&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;output&lt;/code&gt;: dict)&lt;/td&gt;
&lt;td&gt;重写父类方法，支持批处理处理，&lt;code&gt;is_last_batch&lt;/code&gt; 参数控制缓冲区刷新&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;2.2.3 &lt;code&gt;Sam3MultiplexTrackingWithInteractivity&lt;/code&gt; 交互增强版本类&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;继承关系&lt;/strong&gt;: &lt;code&gt;Sam3MultiplexTrackingWithInteractivity&lt;/code&gt; → &lt;code&gt;Sam3MultiplexTracking&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 支持用户交互操作的增强版本，维护用户操作历史记录，支持部分传播（仅传播用户编辑的对象）与完整传播的智能切换，提供更细粒度的交互控制。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特有属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_prev_mem_frame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为添加点操作使用先前记忆帧&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_stateless_refinement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否启用无状态细化行为&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;refinement_detector_cond_frame_removal_window&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;120 (30*4)&lt;/td&gt;
&lt;td&gt;用户细化帧附近移除检测器条件帧的窗口大小&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;特有方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;use_prev_mem_frame=False&lt;/code&gt;, &lt;code&gt;use_stateless_refinement=False&lt;/code&gt;, &lt;code&gt;refinement_detector_cond_frame_removal_window=120&lt;/code&gt;, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化交互增强跟踪器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;init_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;resource_path&lt;/code&gt;: str, &lt;code&gt;offload_video_to_cpu=False&lt;/code&gt;, &lt;code&gt;async_loading_frames=False&lt;/code&gt;, &lt;code&gt;use_torchcodec=False&lt;/code&gt;, &lt;code&gt;use_cv2=False&lt;/code&gt;, &lt;code&gt;input_is_mp4=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;重写父类方法，初始化动作历史记录和SAM2推理状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;重写父类方法，重置动作历史记录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_init_new_sam2_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;new_sam2_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;初始化新的SAM2推理状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cancel_propagation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;取消正在进行的传播，重置模型状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fetch_and_process_single_frame_results&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;postprocessed_out&lt;/code&gt;: dict)&lt;/td&gt;
&lt;td&gt;获取并处理单帧的缓存结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;propagate_in_video&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;start_frame_idx=None&lt;/code&gt;, &lt;code&gt;max_frame_num_to_track=None&lt;/code&gt;, &lt;code&gt;reverse=False&lt;/code&gt;, &lt;code&gt;output_prob_thresh=0.5&lt;/code&gt;, &lt;code&gt;compute_stability_score=False&lt;/code&gt;, &lt;code&gt;is_instance_processing=False&lt;/code&gt;, &lt;code&gt;is_last_batch=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成器，yield (&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;output&lt;/code&gt;: dict)&lt;/td&gt;
&lt;td&gt;重写父类方法，根据动作历史智能选择传播类型（完整/部分/获取）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;parse_action_history_for_propagation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;propagation_type&lt;/code&gt;: str, &lt;code&gt;obj_ids&lt;/code&gt;: List[int])&lt;/td&gt;
&lt;td&gt;解析动作历史记录以确定传播类型和相关对象ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_action_history&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;action_type&lt;/code&gt;: str, &lt;code&gt;obj_ids&lt;/code&gt;: List[int] 或 &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;frame_idx&lt;/code&gt;: int 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;添加用户动作到历史记录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remove_object&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;obj_id&lt;/code&gt;: int, &lt;code&gt;is_user_action=True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;重写父类方法，记录移除动作到历史记录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;text=None&lt;/code&gt;, &lt;code&gt;points=None&lt;/code&gt;, &lt;code&gt;point_labels=None&lt;/code&gt;, &lt;code&gt;clear_old_points=True&lt;/code&gt;, &lt;code&gt;bounding_boxes=None&lt;/code&gt;, &lt;code&gt;bounding_box_labels=None&lt;/code&gt;, &lt;code&gt;clear_old_boxes=True&lt;/code&gt;, &lt;code&gt;output_prob_thresh=0.5&lt;/code&gt;, &lt;code&gt;obj_id=None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;output&lt;/code&gt;: dict)&lt;/td&gt;
&lt;td&gt;重写父类方法，记录提示添加动作到历史记录&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2.3 &lt;code&gt;Sam3VideoBase&lt;/code&gt; (&lt;code&gt;sam3_video_base.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: SAM 3 视频跟踪的基础类，继承自 &lt;code&gt;torch.nn.Module&lt;/code&gt;，整合检测器（&lt;code&gt;detector&lt;/code&gt;）和跟踪器（&lt;code&gt;tracker&lt;/code&gt;）两大核心组件，实现检测-跟踪匹配、热启动延迟（hotstart）、掩码确认（masklet confirmation）、非极大值抑制（NMS）、分布式推理等核心跟踪逻辑。作为预测器（Predictor）和跟踪器（Tracker）之间的中间抽象层，隔离业务逻辑与模型拓扑，提供统一的单帧推理流程 &lt;code&gt;_det_track_one_frame&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;detector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;检测器模型，负责图像特征提取与目标检测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tracker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;跟踪器模型，负责时序记忆与掩码预测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;score_threshold_detection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;检测输出概率阈值（进入NMS与检测-跟踪匹配）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;det_nms_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;检测NMS的IoU阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;assoc_iou_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;检测-跟踪匹配的IoU阈值（宽松阈值，如0.1）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trk_assoc_iou_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;跟踪关联的IoU阈值（严格阈值，如0.5）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;new_det_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;检测作为新对象添加的阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hotstart_delay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;热启动延迟帧数（0表示禁用）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hotstart_unmatch_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;热启动期间未匹配帧数阈值（超过则移除对象）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hotstart_dup_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;热启动期间重叠对象帧数阈值（超过则移除对象）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;suppress_unmatched_only_within_hotstart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否仅在热启动期间抑制未匹配对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;init_trk_keep_alive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;初始跟踪保持活跃帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_trk_keep_alive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;最大跟踪保持活跃帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;min_trk_keep_alive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-4&lt;/td&gt;
&lt;td&gt;最小跟踪保持活跃帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;suppress_overlapping_based_on_recent_occlusion_threshold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;基于最近遮挡的重叠对象抑制阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;suppress_det_close_to_boundary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否抑制边界附近的检测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decrease_trk_keep_alive_for_empty_masklets&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为空掩码减少跟踪保持活跃计数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;o2o_matching_masklets_enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否启用匈牙利匹配以匹配现有掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fill_hole_area&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;填充空洞的面积阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_num_objects&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-1&lt;/td&gt;
&lt;td&gt;跨所有GPU跟踪的最大对象数（-1表示无限制）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_obj_for_compile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自动计算&lt;/td&gt;
&lt;td&gt;为torch.compile缓存创建的对象数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;recondition_every_nth_frame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-1&lt;/td&gt;
&lt;td&gt;每N帧重新条件化（-1表示禁用）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;masklet_confirmation_enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否启用掩码确认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;masklet_confirmation_consecutive_det_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;掩码确认所需的连续检测帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reconstruction_bbox_iou_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;重建边界框的IoU阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reconstruction_bbox_det_score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;重建边界框的检测分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rank&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从环境变量 &lt;code&gt;RANK&lt;/code&gt; 读取&lt;/td&gt;
&lt;td&gt;当前进程排名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;world_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从环境变量 &lt;code&gt;WORLD_SIZE&lt;/code&gt; 读取&lt;/td&gt;
&lt;td&gt;进程总数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_dist_pg_cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.distributed.ProcessGroup&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CPU进程组（惰性初始化）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;device&lt;/code&gt; (property)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自动计算&lt;/td&gt;
&lt;td&gt;模型参数所在的设备&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表格&lt;/strong&gt; (传递给 &lt;code&gt;Sam3VideoBase.__init__&lt;/code&gt; 的配置):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;detector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;检测器模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tracker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;跟踪器模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;score_threshold_detection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;检测输出概率阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;det_nms_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;检测NMS的IoU阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;assoc_iou_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;检测-跟踪匹配的IoU阈值（宽松）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trk_assoc_iou_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;跟踪关联的IoU阈值（严格）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;new_det_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;检测作为新对象添加的阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hotstart_delay&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;热启动延迟帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hotstart_unmatch_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;热启动期间未匹配帧数阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hotstart_dup_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;热启动期间重叠对象帧数阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;suppress_unmatched_only_within_hotstart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否仅在热启动期间抑制未匹配对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;init_trk_keep_alive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;初始跟踪保持活跃帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_trk_keep_alive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;最大跟踪保持活跃帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;min_trk_keep_alive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-4&lt;/td&gt;
&lt;td&gt;最小跟踪保持活跃帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;suppress_overlapping_based_on_recent_occlusion_threshold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;基于最近遮挡的重叠对象抑制阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decrease_trk_keep_alive_for_empty_masklets&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为空掩码减少跟踪保持活跃计数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;o2o_matching_masklets_enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否启用匈牙利匹配以匹配现有掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;suppress_det_close_to_boundary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否抑制边界附近的检测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fill_hole_area&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;填充空洞的面积阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_num_objects&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-1&lt;/td&gt;
&lt;td&gt;跨所有GPU跟踪的最大对象数（-1表示无限制）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;recondition_every_nth_frame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-1&lt;/td&gt;
&lt;td&gt;每N帧重新条件化（-1表示禁用）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;masklet_confirmation_enable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否启用掩码确认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;masklet_confirmation_consecutive_det_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;掩码确认所需的连续检测帧数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reconstruction_bbox_iou_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;重建边界框的IoU阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reconstruction_bbox_det_score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;重建边界框的检测分数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;见上表&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化视频跟踪基类&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_det_track_one_frame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;num_frames&lt;/code&gt;: int, &lt;code&gt;reverse&lt;/code&gt;: bool, &lt;code&gt;input_batch&lt;/code&gt;: BatchedDatapoint, &lt;code&gt;geometric_prompt&lt;/code&gt;: Any, &lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any], &lt;code&gt;tracker_metadata_prev&lt;/code&gt;: Dict[str, Any], &lt;code&gt;feature_cache&lt;/code&gt;: Dict, &lt;code&gt;orig_vid_height&lt;/code&gt;: int, &lt;code&gt;orig_vid_width&lt;/code&gt;: int, &lt;code&gt;is_image_only&lt;/code&gt;: bool = False, &lt;code&gt;allow_new_detections&lt;/code&gt;: bool = True&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;obj_id_to_mask&lt;/code&gt;: dict, &lt;code&gt;obj_id_to_score&lt;/code&gt;: dict, &lt;code&gt;tracker_states_local_new&lt;/code&gt;: List, &lt;code&gt;tracker_metadata_new&lt;/code&gt;: dict, &lt;code&gt;frame_stats&lt;/code&gt;: dict, &lt;code&gt;tracker_obj_scores_global&lt;/code&gt;: list)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;核心方法&lt;/strong&gt;: 执行单帧检测-跟踪推理（SPMD方式），包含5个步骤：1) 骨干网络与检测；2) 跟踪器传播；3) 更新规划；4) 更新执行；5) 输出构建&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run_backbone_and_detection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;num_frames&lt;/code&gt;: int, &lt;code&gt;input_batch&lt;/code&gt;: BatchedDatapoint, &lt;code&gt;geometric_prompt&lt;/code&gt;: Any, &lt;code&gt;feature_cache&lt;/code&gt;: Dict, &lt;code&gt;reverse&lt;/code&gt;: bool, &lt;code&gt;allow_new_detections&lt;/code&gt;: bool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;det_out&lt;/code&gt;: dict (&lt;code&gt;bbox&lt;/code&gt;, &lt;code&gt;mask&lt;/code&gt;, &lt;code&gt;scores&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;运行骨干网络和检测器，提取检测结果并缓存骨干特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run_tracker_propagation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;num_frames&lt;/code&gt;: int, &lt;code&gt;reverse&lt;/code&gt;: bool, &lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any], &lt;code&gt;tracker_metadata_prev&lt;/code&gt;: Dict[str, Any]&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;tracker_low_res_masks_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_obj_scores_global&lt;/code&gt;: Tensor)&lt;/td&gt;
&lt;td&gt;传播本地跟踪器状态，收集全局掩码预测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run_tracker_update_planning_phase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;num_frames&lt;/code&gt;: int, &lt;code&gt;reverse&lt;/code&gt;: bool, &lt;code&gt;det_out&lt;/code&gt;: dict, &lt;code&gt;tracker_low_res_masks_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_obj_scores_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_metadata_prev&lt;/code&gt;: Dict[str, Any], &lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any], &lt;code&gt;is_image_only&lt;/code&gt;: bool = False&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;tracker_update_plan&lt;/code&gt;: dict, &lt;code&gt;tracker_metadata_new&lt;/code&gt;: dict)&lt;/td&gt;
&lt;td&gt;规划跟踪器更新：决定哪些对象添加/移除/重新条件化，生成更新计划&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run_tracker_update_execution_phase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;num_frames&lt;/code&gt;: int, &lt;code&gt;reverse&lt;/code&gt;: bool, &lt;code&gt;det_out&lt;/code&gt;: dict, &lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any], &lt;code&gt;tracker_update_plan&lt;/code&gt;: dict, &lt;code&gt;orig_vid_height&lt;/code&gt;: int, &lt;code&gt;orig_vid_width&lt;/code&gt;: int, &lt;code&gt;feature_cache&lt;/code&gt;: Dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tracker_states_local_new&lt;/code&gt;: List[Any]&lt;/td&gt;
&lt;td&gt;执行跟踪器更新：根据计划更新本地跟踪器状态，编码新记忆&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_suppress_detections_close_to_boundary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boxes&lt;/code&gt;: Tensor (N,4 xyxy 归一化), &lt;code&gt;margin&lt;/code&gt;: float = 0.025&lt;/td&gt;
&lt;td&gt;&lt;code&gt;keep&lt;/code&gt;: Tensor (bool)&lt;/td&gt;
&lt;td&gt;抑制图像边界附近的检测框&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_recondition_masklets&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;tracker_low_res_masks_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_obj_scores_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_metadata_prev&lt;/code&gt;: Dict[str, Any], &lt;code&gt;det_out&lt;/code&gt;: dict, &lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;reconditioned_obj_ids&lt;/code&gt;: set&lt;/td&gt;
&lt;td&gt;重新条件化掩码（当对象被遮挡后重新出现时恢复）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_suppress_overlapping_based_on_recent_occlusion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tracker_low_res_masks_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_obj_scores_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_metadata_prev&lt;/code&gt;: Dict[str, Any], &lt;code&gt;det_out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;suppressed_obj_ids&lt;/code&gt;: set&lt;/td&gt;
&lt;td&gt;基于最近遮挡历史抑制重叠对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_create_planning_metadata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tracker_metadata_prev&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;planning_metadata&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;创建用于规划阶段的元数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_post_execution_phase_hook&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any], &lt;code&gt;tracker_metadata_new&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;执行阶段后的钩子函数，可被子类覆盖&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;build_outputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;num_frames&lt;/code&gt;: int, &lt;code&gt;reverse&lt;/code&gt;: bool, &lt;code&gt;det_out&lt;/code&gt;: dict, &lt;code&gt;tracker_low_res_masks_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_obj_scores_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_metadata_prev&lt;/code&gt;: Dict[str, Any], &lt;code&gt;tracker_update_plan&lt;/code&gt;: dict, &lt;code&gt;orig_vid_height&lt;/code&gt;: int, &lt;code&gt;orig_vid_width&lt;/code&gt;: int, &lt;code&gt;reconditioned_obj_ids&lt;/code&gt;: set, &lt;code&gt;det_to_matched_trk_obj_ids&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;obj_id_to_mask&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;构建最终输出字典（对象ID到掩码的映射）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_propogate_tracker_one_frame_local_gpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any], &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;reverse&lt;/code&gt;: bool&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;obj_ids_local&lt;/code&gt;: List[int], &lt;code&gt;low_res_masks_local&lt;/code&gt;: Tensor, &lt;code&gt;obj_scores_local&lt;/code&gt;: Tensor)&lt;/td&gt;
&lt;td&gt;在本地GPU上传播跟踪器状态，获取当前帧的掩码预测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_associate_det_trk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;det_bboxes&lt;/code&gt;: Tensor, &lt;code&gt;det_masks&lt;/code&gt;: Tensor, &lt;code&gt;det_scores&lt;/code&gt;: Tensor, &lt;code&gt;trk_masks&lt;/code&gt;: Tensor, &lt;code&gt;trk_scores&lt;/code&gt;: Tensor, &lt;code&gt;trk_metadata&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;reverse&lt;/code&gt;: bool&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;det_to_matched_trk_idx&lt;/code&gt;: dict, &lt;code&gt;trk_to_matched_det_idx&lt;/code&gt;: dict, &lt;code&gt;det_scores_for_matched&lt;/code&gt;: Tensor, &lt;code&gt;det_masks_for_matched&lt;/code&gt;: Tensor, &lt;code&gt;det_bboxes_for_matched&lt;/code&gt;: Tensor)&lt;/td&gt;
&lt;td&gt;关联检测与跟踪：计算IoU匹配，返回匹配映射&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_assign_new_det_to_gpus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;new_det_num&lt;/code&gt;: int, &lt;code&gt;prev_workload_per_gpu&lt;/code&gt;: List[int]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gpu_ids&lt;/code&gt;: List[int]&lt;/td&gt;
&lt;td&gt;分配新检测对象到各GPU，实现负载均衡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_process_hotstart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;det_out&lt;/code&gt;: dict, &lt;code&gt;tracker_low_res_masks_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_obj_scores_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_metadata_prev&lt;/code&gt;: Dict[str, Any]&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;removed_obj_ids&lt;/code&gt;: set, &lt;code&gt;suppressed_obj_ids&lt;/code&gt;: set)&lt;/td&gt;
&lt;td&gt;处理热启动逻辑：移除未匹配或重叠的对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_tracker_update_memories&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any], &lt;code&gt;obj_ids&lt;/code&gt;: List[int], &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;feature_cache&lt;/code&gt;: Dict, &lt;code&gt;orig_vid_height&lt;/code&gt;: int, &lt;code&gt;orig_vid_width&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;更新跟踪器记忆：为指定对象编码新记忆&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_tracker_add_new_objects&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any], &lt;code&gt;new_det_inds&lt;/code&gt;: List[int], &lt;code&gt;det_out&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;feature_cache&lt;/code&gt;: Dict, &lt;code&gt;orig_vid_height&lt;/code&gt;: int, &lt;code&gt;orig_vid_width&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;new_obj_ids&lt;/code&gt;: List[int]&lt;/td&gt;
&lt;td&gt;添加新对象到跟踪器状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_tracker_remove_object&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any], &lt;code&gt;obj_id&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;从跟踪器状态中移除单个对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_tracker_remove_objects&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any], &lt;code&gt;obj_ids&lt;/code&gt;: List[int]&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;批量移除对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_initialize_metadata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;metadata&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;初始化跟踪器元数据（全局对象ID映射等）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;update_masklet_confirmation_status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;det_out&lt;/code&gt;: dict, &lt;code&gt;tracker_metadata_prev&lt;/code&gt;: Dict[str, Any], &lt;code&gt;tracker_update_plan&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;unconfirmed_obj_ids&lt;/code&gt;: set, &lt;code&gt;confirmed_obj_ids&lt;/code&gt;: set)&lt;/td&gt;
&lt;td&gt;更新掩码确认状态：基于连续检测匹配确认对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;: BatchedDatapoint, &lt;code&gt;is_inference&lt;/code&gt;: bool = False&lt;/td&gt;
&lt;td&gt;抛出 &lt;code&gt;NotImplementedError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;抽象前向方法，需在子类中实现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_load_checkpoint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ckpt_path&lt;/code&gt;: str, &lt;code&gt;strict&lt;/code&gt;: bool = True&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;加载检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prep_for_evaluator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;video_frames&lt;/code&gt;: List[Image], &lt;code&gt;tracking_res&lt;/code&gt;: dict, &lt;code&gt;scores_labels&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;为评估器准备数据格式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_encode_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;编码提示（占位符，需子类实现）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_drop_new_det_with_obj_limit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;new_det_fa_inds&lt;/code&gt;: np.ndarray, &lt;code&gt;det_scores_np&lt;/code&gt;: np.ndarray, &lt;code&gt;num_to_keep&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;new_det_fa_inds&lt;/code&gt;: np.ndarray&lt;/td&gt;
&lt;td&gt;根据对象限制丢弃新检测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_init_dist_pg_cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化CPU进程组&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;broadcast_python_obj_cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;python_obj_list&lt;/code&gt;: list, &lt;code&gt;src&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;广播Python对象到所有CPU进程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_objects_to_suppress_based_on_most_recently_occluded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tracker_low_res_masks_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_obj_scores_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_metadata_prev&lt;/code&gt;: Dict[str, Any], &lt;code&gt;det_out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;suppressed_obj_ids&lt;/code&gt;: set&lt;/td&gt;
&lt;td&gt;基于最近被遮挡的对象确定需要抑制的对象&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;静态方法&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_convert_to_numpy(self)&lt;/code&gt;: 将张量转换为NumPy数组。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_create_cpu_metadata(self, trk_obj_ids, det_masks)&lt;/code&gt;: 创建CPU元数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.4 &lt;code&gt;Sam3MultiplexBase&lt;/code&gt; (&lt;code&gt;sam3_multiplex_base.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: SAM 3.1 多路复用（Multiplex）跟踪的中间抽象层，继承自 &lt;code&gt;Sam3VideoBase&lt;/code&gt;，为多路复用跟踪添加特定配置和功能。引入多路复用控制器（bucket capacity）、批处理接地（batched grounding）、生产环境标志（running_in_prod）、性能分析等特性，是多路复用跟踪族系（Sam3MultiplexTracking等）的直接父类。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;继承关系&lt;/strong&gt;: &lt;code&gt;Sam3MultiplexBase&lt;/code&gt; → &lt;code&gt;Sam3VideoBase&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt; (特有属性，不包括继承的属性):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tracker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sam3MultiplexTrackerPredictor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;多路复用跟踪器预测器（类型特定）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;detector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sam3MultiplexDetector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;多路复用检测器（类型特定）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_only_det_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;仅图像输入的检测阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;det_nms_use_iom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在NMS中使用IoM（交小比）而非IoU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;is_multiplex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为多路复用模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;running_in_prod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在生产环境（FBInfra）中运行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allow_unoccluded_to_suppress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否允许未遮挡对象抑制遮挡对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sprinkle_removal_area&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;洒点移除的面积阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_num_objects&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;跨所有GPU跟踪的最大对象数（默认128）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_num_kboxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;最大关键框数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_iom_recondition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在重新条件化中使用IoM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iom_thresh_recondition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;重新条件化的IoM阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_thresh_recondition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;重新条件化的IoU阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reapply_no_object_pointer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为抑制对象重新应用无对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_batched_grounding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用批处理接地&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;batched_grounding_batch_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;批处理接地的批大小&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bucket_capacity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从 &lt;code&gt;tracker.multiplex_controller&lt;/code&gt; 获取&lt;/td&gt;
&lt;td&gt;多路复用桶容量（仅当 &lt;code&gt;is_multiplex=True&lt;/code&gt; 时有效）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_profiler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.profiler.profile&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PyTorch性能分析器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_frame_count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;帧计数（用于性能分析）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_profile_save_dir&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/tmp/profiling&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;性能分析结果保存目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_profiling_enabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从环境变量 &lt;code&gt;ENABLE_PROFILING&lt;/code&gt; 读取&lt;/td&gt;
&lt;td&gt;是否启用性能分析&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表格&lt;/strong&gt; (传递给 &lt;code&gt;Sam3MultiplexBase.__init__&lt;/code&gt; 的特有参数):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tracker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sam3MultiplexTrackerPredictor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;多路复用跟踪器预测器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;detector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sam3MultiplexDetector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;多路复用检测器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ckpt_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查点路径（传统加载方式）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sam3_ckpt_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SAM3检查点路径（新加载方式）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_only_det_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;仅图像输入的检测阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;det_nms_use_iom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在NMS中使用IoM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allow_unoccluded_to_suppress&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否允许未遮挡对象抑制遮挡对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sprinkle_removal_area&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;洒点移除的面积阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_num_objects&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;跨所有GPU跟踪的最大对象数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_num_kboxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;最大关键框数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_iom_recondition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在重新条件化中使用IoM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iom_thresh_recondition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;重新条件化的IoM阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_thresh_recondition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;重新条件化的IoU阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;is_multiplex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为多路复用模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;running_in_prod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在生产环境中运行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reapply_no_object_pointer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为抑制对象重新应用无对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_batched_grounding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用批处理接地&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;batched_grounding_batch_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;批处理接地的批大小&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;传递给父类 &lt;code&gt;Sam3VideoBase&lt;/code&gt; 的其余参数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;注: 此外还继承所有 &lt;code&gt;Sam3VideoBase&lt;/code&gt; 的初始化参数（如 &lt;code&gt;score_threshold_detection&lt;/code&gt;、&lt;code&gt;hotstart_delay&lt;/code&gt; 等）。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt; (特有方法，不包括继承的方法):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;见上表&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化多路复用基类，设置多路复用特定配置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;all_gather_cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tensor_list&lt;/code&gt;: List[Tensor], &lt;code&gt;tensor&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;在CPU进程组上执行all-gather操作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;all_gather_python_obj_cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;object_list&lt;/code&gt;: List[Any], &lt;code&gt;python_obj&lt;/code&gt;: Any&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;在CPU进程组上收集Python对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;broadcast_cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;: Tensor, &lt;code&gt;src&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;在CPU进程组上广播张量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_start_profiling&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;: 是否已启动性能分析&lt;/td&gt;
&lt;td&gt;启动PyTorch性能分析器（如果启用）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_stop_profiling&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;停止性能分析器并保存跟踪文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_det_track_one_frame_impl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;num_frames&lt;/code&gt;: int, &lt;code&gt;reverse&lt;/code&gt;: bool, &lt;code&gt;input_batch&lt;/code&gt;: BatchedDatapoint, &lt;code&gt;geometric_prompt&lt;/code&gt;: Any, &lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any], &lt;code&gt;tracker_metadata_prev&lt;/code&gt;: Dict[str, Any], &lt;code&gt;feature_cache&lt;/code&gt;: Dict, &lt;code&gt;orig_vid_height&lt;/code&gt;: int, &lt;code&gt;orig_vid_width&lt;/code&gt;: int, &lt;code&gt;is_image_only&lt;/code&gt;: bool = False&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;obj_id_to_mask&lt;/code&gt;: dict, &lt;code&gt;obj_id_to_score&lt;/code&gt;: dict, &lt;code&gt;tracker_states_local_new&lt;/code&gt;: List, &lt;code&gt;tracker_metadata_new&lt;/code&gt;: dict, &lt;code&gt;frame_stats&lt;/code&gt;: dict, &lt;code&gt;tracker_obj_scores_global&lt;/code&gt;: list)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_det_track_one_frame&lt;/code&gt; 的具体实现，包含性能分析包装&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_deepcopy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;: Any&lt;/td&gt;
&lt;td&gt;深拷贝的对象&lt;/td&gt;
&lt;td&gt;深度复制对象（用于跟踪器状态复制）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_count_buckets_in_states&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tracker_states_local&lt;/code&gt;: List[Any]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;: 桶数量&lt;/td&gt;
&lt;td&gt;计算本地跟踪器状态中的桶数量（用于多路复用容量检查）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_process_hotstart_gpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;det_out&lt;/code&gt;: dict, &lt;code&gt;tracker_low_res_masks_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_obj_scores_global&lt;/code&gt;: Tensor, &lt;code&gt;tracker_metadata_prev&lt;/code&gt;: Dict[str, Any]&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;removed_obj_ids&lt;/code&gt;: set, &lt;code&gt;suppressed_obj_ids&lt;/code&gt;: set)&lt;/td&gt;
&lt;td&gt;GPU版本的热启动处理&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;注: 此类继承所有 &lt;code&gt;Sam3VideoBase&lt;/code&gt; 的方法（如 &lt;code&gt;run_backbone_and_detection&lt;/code&gt;、&lt;code&gt;run_tracker_propagation&lt;/code&gt; 等）。&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;3. 多模态、视觉/语言大一统主干 (Backbones &amp;amp; V-L Models)&lt;/h2&gt;
&lt;h3&gt;3.1 &lt;code&gt;Sam3Image&lt;/code&gt; &amp;amp; &lt;code&gt;Sam3ImageOnVideoMultiGPU&lt;/code&gt; (&lt;code&gt;sam3_image.py&lt;/code&gt; / &lt;code&gt;multiplex_detector&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: SAM 3 多模态图像检测与分割模型，支持文本提示（&quot;Find Object&quot;）的视觉-语言联合推理。作为检测器核心，处理图像特征提取、几何提示编码、Transformer编码器-解码器推理和分割头预测，为视频跟踪提供单帧检测能力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;继承关系&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Sam3Image&lt;/code&gt; → &lt;code&gt;torch.nn.Module&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Sam3ImageOnVideoMultiGPU&lt;/code&gt; → &lt;code&gt;Sam3Image&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3.1.1 &lt;code&gt;Sam3Image&lt;/code&gt; 基类&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;类常量&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;常量名&lt;/th&gt;
&lt;th&gt;值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TEXT_ID_FOR_TEXT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;文本提示的ID，用于文本特征编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TEXT_ID_FOR_VISUAL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;视觉提示的ID，用于视觉特征编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TEXT_ID_FOR_GEOMETRIC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;几何提示的ID，用于几何特征编码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SAM3VLBackbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;视觉-语言骨干网络，提取图像和文本特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;geometry_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;输入几何编码器，编码框、点等几何提示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;Transformer模型，执行编码器-解码器推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hidden_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;transformer.d_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transformer隐藏维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_feature_levels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;特征层级数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;segmentation_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;分割头模型，预测最终掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;o2m_mask_predict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否预测O2M（一对多）掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dot_prod_scoring&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点积评分头，用于对象检测评分&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_act_checkpoint_seg_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;分割头是否使用激活检查点以节省显存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interactivity_in_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编码器中是否启用交互性&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matcher&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;匹配器，用于训练时匹配预测与目标&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_interactive_steps_val&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;验证时的交互步数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_dot_prod_scoring&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用点积评分（否则使用线性分类器）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instance_dot_prod_scoring&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实例点积评分头（当&lt;code&gt;separate_scorer_for_instance=True&lt;/code&gt;时）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;class_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;线性分类嵌入层（当&lt;code&gt;use_dot_prod_scoring=False&lt;/code&gt;时）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instance_class_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实例分类嵌入层（当&lt;code&gt;separate_scorer_for_instance=True&lt;/code&gt;时）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;supervise_joint_box_scores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否监督联合框分数（仅当使用存在性token/分数时相关）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;detach_presence_in_joint_score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;联合评分中是否分离存在性（仅当使用存在性token/分数时相关）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_instance_query&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用实例查询&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否输出多掩码（多掩码输出模式）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inst_interactive_predictor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SAM3InteractiveImagePredictor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实例交互预测器，用于实例级交互推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dac&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;transformer.decoder.dac&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否启用DAC（检测器-跟踪器协作）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;device&lt;/code&gt; (property)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自动计算&lt;/td&gt;
&lt;td&gt;模型参数所在的设备&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表格&lt;/strong&gt; (传递给 &lt;code&gt;Sam3Image.__init__&lt;/code&gt; 的参数):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SAM3VLBackbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;视觉-语言骨干网络，必须提供&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;Transformer模型，必须提供&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input_geometry_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;输入几何编码器，必须提供&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;segmentation_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;分割头模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_feature_levels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;特征层级数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;o2m_mask_predict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否预测O2M（一对多）掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dot_prod_scoring&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点积评分头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_instance_query&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用实例查询&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否输出多掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_act_checkpoint_seg_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;分割头是否使用激活检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interactivity_in_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编码器中是否启用交互性&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matcher&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;匹配器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_dot_prod_scoring&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用点积评分&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;supervise_joint_box_scores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否监督联合框分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;detach_presence_in_joint_score&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;联合评分中是否分离存在性&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;separate_scorer_for_instance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否为实例使用单独的评分器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_interactive_steps_val&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;验证时的交互步数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inst_interactive_predictor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SAM3InteractiveImagePredictor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实例交互预测器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;其他参数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;见上表初始化参数&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化SAM3图像检测器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_img_feats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt;: dict, &lt;code&gt;img_ids&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;backbone_out&lt;/code&gt;, &lt;code&gt;img_feats&lt;/code&gt;, &lt;code&gt;img_pos_embeds&lt;/code&gt;, &lt;code&gt;vis_feat_sizes&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;从骨干网络输出中提取图像特征，返回特征列表和位置嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_encode_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt;: dict, &lt;code&gt;find_input&lt;/code&gt;: FindStage, &lt;code&gt;geometric_prompt&lt;/code&gt;: Prompt, &lt;code&gt;visual_prompt_embed&lt;/code&gt;=None, &lt;code&gt;visual_prompt_mask&lt;/code&gt;=None, &lt;code&gt;encode_text&lt;/code&gt;=True, &lt;code&gt;prev_mask_pred&lt;/code&gt;=None&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;prompt&lt;/code&gt;: Tensor, &lt;code&gt;prompt_mask&lt;/code&gt;: Tensor, &lt;code&gt;backbone_out&lt;/code&gt;: dict)&lt;/td&gt;
&lt;td&gt;编码几何提示（框、点）和文本提示，生成统一的提示张量和掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_run_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt;: dict, &lt;code&gt;find_input&lt;/code&gt;: FindStage, &lt;code&gt;prompt&lt;/code&gt;: Tensor, &lt;code&gt;prompt_mask&lt;/code&gt;: Tensor, &lt;code&gt;encoder_extra_kwargs&lt;/code&gt;=None&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;backbone_out&lt;/code&gt;: dict, &lt;code&gt;encoder_out&lt;/code&gt;: dict, &lt;code&gt;feat_tuple&lt;/code&gt;: tuple)&lt;/td&gt;
&lt;td&gt;运行Transformer编码器，编码图像特征和提示，返回编码器输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_run_decoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pos_embed&lt;/code&gt;: Tensor, &lt;code&gt;memory&lt;/code&gt;: Tensor, &lt;code&gt;src_mask&lt;/code&gt;: Tensor, &lt;code&gt;out&lt;/code&gt;: dict, &lt;code&gt;prompt&lt;/code&gt;: Tensor, &lt;code&gt;prompt_mask&lt;/code&gt;: Tensor, &lt;code&gt;encoder_out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;out&lt;/code&gt;: dict, &lt;code&gt;hs&lt;/code&gt;: Tensor)&lt;/td&gt;
&lt;td&gt;运行Transformer解码器，生成对象查询、边界框和分数预测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_update_scores_and_boxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;out&lt;/code&gt;: dict, &lt;code&gt;hs&lt;/code&gt;: Tensor, &lt;code&gt;reference_boxes&lt;/code&gt;: Tensor, &lt;code&gt;prompt&lt;/code&gt;: Tensor, &lt;code&gt;prompt_mask&lt;/code&gt;: Tensor, &lt;code&gt;dec_presence_out&lt;/code&gt;=None, &lt;code&gt;is_instance_prompt&lt;/code&gt;=False&lt;/td&gt;
&lt;td&gt;无（修改&lt;code&gt;out&lt;/code&gt;字典）&lt;/td&gt;
&lt;td&gt;更新分数和边界框预测：计算分类分数和边界框坐标&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_run_segmentation_heads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;out&lt;/code&gt;: dict, &lt;code&gt;backbone_out&lt;/code&gt;: dict, &lt;code&gt;img_ids&lt;/code&gt;: Tensor, &lt;code&gt;vis_feat_sizes&lt;/code&gt;: list, &lt;code&gt;encoder_hidden_states&lt;/code&gt;: Tensor, &lt;code&gt;prompt&lt;/code&gt;: Tensor, &lt;code&gt;prompt_mask&lt;/code&gt;: Tensor, &lt;code&gt;hs&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;无（修改&lt;code&gt;out&lt;/code&gt;字典）&lt;/td&gt;
&lt;td&gt;运行分割头，预测最终掩码，添加到输出字典&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_best_mask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prev_mask_pred&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;从输出中选择最佳掩码，下采样以匹配图像分辨率&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_grounding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt;: dict, &lt;code&gt;find_input&lt;/code&gt;: FindStage, &lt;code&gt;find_target&lt;/code&gt;: Any, &lt;code&gt;geometric_prompt&lt;/code&gt;: Prompt, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;完整接地推理流程：编码提示 → 编码器 → 解码器 → 分割头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_postprocess_out&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;out&lt;/code&gt;: dict, &lt;code&gt;multimask_output&lt;/code&gt;: bool = False&lt;/td&gt;
&lt;td&gt;&lt;code&gt;out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;后处理输出：多掩码输出模式下选择最佳掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_geo_prompt_from_find_input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;find_input&lt;/code&gt;: FindStage&lt;/td&gt;
&lt;td&gt;&lt;code&gt;geometric_prompt&lt;/code&gt;: Prompt&lt;/td&gt;
&lt;td&gt;从查找输入构建初始几何提示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_dummy_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;num_prompts&lt;/code&gt;=1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;geometric_prompt&lt;/code&gt;: Prompt&lt;/td&gt;
&lt;td&gt;创建虚拟几何提示（用于无提示推理）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;input&lt;/code&gt;: BatchedDatapoint&lt;/td&gt;
&lt;td&gt;&lt;code&gt;previous_stages_out&lt;/code&gt;: SAM3Output&lt;/td&gt;
&lt;td&gt;主前向方法：处理批处理数据点，支持多步交互推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_compute_matching&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;out&lt;/code&gt;: dict, &lt;code&gt;targets&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无（修改&lt;code&gt;out&lt;/code&gt;字典）&lt;/td&gt;
&lt;td&gt;计算预测与目标之间的匹配索引&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;back_convert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;targets&lt;/code&gt;: Any&lt;/td&gt;
&lt;td&gt;&lt;code&gt;batched_targets&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;将目标转换为批处理格式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;predict_inst&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;masks&lt;/code&gt;: np.ndarray, &lt;code&gt;scores&lt;/code&gt;: np.ndarray, &lt;code&gt;logits&lt;/code&gt;: np.ndarray)&lt;/td&gt;
&lt;td&gt;实例级预测：使用实例交互预测器进行单实例推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;predict_inst_batch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;*args&lt;/code&gt;, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;masks_list&lt;/code&gt;: List[np.ndarray], &lt;code&gt;scores_list&lt;/code&gt;: List[np.ndarray], &lt;code&gt;logits_list&lt;/code&gt;: List[np.ndarray])&lt;/td&gt;
&lt;td&gt;批处理实例级预测：批量处理多个实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;device&lt;/code&gt; (property)&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;返回模型参数所在的设备&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;3.1.2 &lt;code&gt;Sam3ImageOnVideoMultiGPU&lt;/code&gt; 多GPU视频处理扩展类&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 支持多GPU视频处理的SAM3图像检测器扩展，通过分布式计算和缓存机制加速视频帧处理，实现高效的批处理推理。为&lt;code&gt;Sam3VideoBase&lt;/code&gt;提供多GPU检测支持。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rank&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从环境变量 &lt;code&gt;RANK&lt;/code&gt; 读取&lt;/td&gt;
&lt;td&gt;当前进程排名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;world_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从环境变量 &lt;code&gt;WORLD_SIZE&lt;/code&gt; 读取&lt;/td&gt;
&lt;td&gt;进程总数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;async_all_gather&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否异步聚集张量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gather_backbone_out&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自动判断&lt;/td&gt;
&lt;td&gt;是否聚集骨干网络输出（默认仅对&lt;code&gt;SAM3VLBackbone&lt;/code&gt;聚集）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;所有父类属性&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;继承自&lt;code&gt;Sam3Image&lt;/code&gt;的所有属性&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;任意&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;传递给父类&lt;code&gt;Sam3Image.__init__&lt;/code&gt;的位置参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;async_all_gather&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否异步聚集张量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gather_backbone_out&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否聚集骨干网络输出（&lt;code&gt;None&lt;/code&gt;时自动判断）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;传递给父类&lt;code&gt;Sam3Image.__init__&lt;/code&gt;的关键字参数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*args&lt;/code&gt;, &lt;code&gt;async_all_gather=True&lt;/code&gt;, &lt;code&gt;gather_backbone_out=None&lt;/code&gt;, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化多GPU图像检测器，设置分布式参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_video_grounding_multigpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt;: dict, &lt;code&gt;find_inputs&lt;/code&gt;: List[FindStage], &lt;code&gt;geometric_prompt&lt;/code&gt;: Prompt, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;num_frames&lt;/code&gt;: int, &lt;code&gt;multigpu_buffer&lt;/code&gt;: dict, &lt;code&gt;track_in_reverse&lt;/code&gt;=False, &lt;code&gt;return_sam2_backbone_feats&lt;/code&gt;=False, &lt;code&gt;run_nms&lt;/code&gt;=False, &lt;code&gt;nms_prob_thresh&lt;/code&gt;=None, &lt;code&gt;nms_iou_thresh&lt;/code&gt;=None, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;out&lt;/code&gt;: dict, &lt;code&gt;backbone_out&lt;/code&gt;: dict)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;核心方法&lt;/strong&gt;: 在多GPU环境下执行视频接地推理，使用缓存机制加速处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_build_multigpu_buffer_next_chunk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt;: dict, &lt;code&gt;find_inputs&lt;/code&gt;: List[FindStage], &lt;code&gt;geometric_prompt&lt;/code&gt;: Prompt, &lt;code&gt;frame_idx_begin&lt;/code&gt;: int, &lt;code&gt;frame_idx_end&lt;/code&gt;: int, &lt;code&gt;num_frames&lt;/code&gt;: int, &lt;code&gt;multigpu_buffer&lt;/code&gt;: dict, &lt;code&gt;run_nms&lt;/code&gt;=False, &lt;code&gt;nms_prob_thresh&lt;/code&gt;=None, &lt;code&gt;nms_iou_thresh&lt;/code&gt;=None&lt;/td&gt;
&lt;td&gt;无（修改&lt;code&gt;multigpu_buffer&lt;/code&gt;字典）&lt;/td&gt;
&lt;td&gt;构建多GPU缓冲区下一块：每个GPU计算一个帧的检测输出并聚集到所有GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_gather_tensor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;元组: (&lt;code&gt;output_list&lt;/code&gt;: List[Tensor], &lt;code&gt;handle&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;聚集张量到所有GPU：执行NCCL &lt;code&gt;all_gather&lt;/code&gt;操作，支持异步模式&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;注意：&lt;code&gt;Sam3ImageOnVideoMultiGPU&lt;/code&gt; 继承了 &lt;code&gt;Sam3Image&lt;/code&gt; 的所有方法，因此也拥有所有基类方法（&lt;code&gt;forward_grounding&lt;/code&gt;、&lt;code&gt;_encode_prompt&lt;/code&gt;、&lt;code&gt;_run_encoder&lt;/code&gt; 等）。&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;3.2 &lt;code&gt;SAM3VLBackbone&lt;/code&gt; (&lt;code&gt;vl_combiner.py&lt;/code&gt; / &lt;code&gt;vitdet.py&lt;/code&gt; / &lt;code&gt;necks.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: Visual-Language Backbone（视觉-语言骨干网络）。将视觉骨干网络（Vision Transformer，来自Hiera或VitDet，负责密集像素特征提取）与语言编码器（Text Encoder，负责理解文本提示如&quot;红色的车&quot;）组合在一起，但不进行特征融合。主要作为方便包装器，统一处理两个骨干网络的激活检查点和编译优化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;文件位置&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主类: &lt;code&gt;model/vl_combiner.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;视觉骨干: &lt;code&gt;model/necks.py&lt;/code&gt; (&lt;code&gt;Sam3DualViTDetNeck&lt;/code&gt;, &lt;code&gt;Sam3TriViTDetNeck&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;文本编码器: 外部提供（如CLIP、BERT等）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3.2.1 &lt;code&gt;SAM3VLBackbone&lt;/code&gt; 基类&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;继承关系&lt;/strong&gt;: &lt;code&gt;SAM3VLBackbone&lt;/code&gt; → &lt;code&gt;torch.nn.Module&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vision_backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sam3DualViTDetNeck&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;视觉骨干网络，提取多尺度图像特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;language_backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;语言编码器，编码文本提示和边界框&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scalp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;标量参数：丢弃最低分辨率特征的数量（如scalp=1丢弃最底层特征）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;act_ckpt_whole_vision_backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否对整个视觉骨干网络启用激活检查点（节省显存）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;act_ckpt_whole_language_backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否对整个语言骨干网络启用激活检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;visual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sam3DualViTDetNeck&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;视觉骨干网络，必须提供&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;文本编码器，必须提供&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_visual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用&lt;code&gt;torch.compile&lt;/code&gt;编译视觉骨干网络以加速推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;act_ckpt_whole_vision_backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否对整个视觉骨干网络启用激活检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;act_ckpt_whole_language_backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否对整个语言骨干网络启用激活检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scalp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;丢弃最低分辨率特征的数量&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;见上表初始化参数&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化视觉-语言骨干网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;samples&lt;/code&gt;: Tensor (图像批次), &lt;code&gt;captions&lt;/code&gt;: List[str] (标题列表), &lt;code&gt;input_boxes&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt; (输入边界框), &lt;code&gt;additional_text&lt;/code&gt;: List[str] 或 &lt;code&gt;None&lt;/code&gt; (附加文本)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;完整前向传播：调用&lt;code&gt;forward_image&lt;/code&gt;和&lt;code&gt;forward_text&lt;/code&gt;，返回包含视觉和语言特征的字典&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;samples&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;图像前向传播：提取视觉特征，支持激活检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_forward_image_no_act_ckpt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;samples&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无激活检查点的图像前向传播：实际调用视觉骨干网络，处理&lt;code&gt;scalp&lt;/code&gt;参数，返回SAM2和SAM3特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;captions&lt;/code&gt;: List[str], &lt;code&gt;input_boxes&lt;/code&gt;=None, &lt;code&gt;additional_text&lt;/code&gt;=None, &lt;code&gt;device&lt;/code&gt;=&quot;cuda&quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;文本前向传播：编码文本提示，支持激活检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_forward_text_no_ack_ckpt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;captions&lt;/code&gt;: List[str], &lt;code&gt;input_boxes&lt;/code&gt;=None, &lt;code&gt;additional_text&lt;/code&gt;=None, &lt;code&gt;device&lt;/code&gt;=&quot;cuda&quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无激活检查点的文本前向传播：实际调用语言编码器，支持附加文本编码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;输出字典结构&lt;/strong&gt; (&lt;code&gt;forward&lt;/code&gt; 方法返回):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vision_features&lt;/code&gt;: Tensor - 视觉特征（SAM3级别）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vision_pos_enc&lt;/code&gt;: List[Tensor] - 视觉位置编码&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backbone_fpn&lt;/code&gt;: List[Tensor/NestedTensor] - 多尺度骨干特征（特征金字塔）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sam2_backbone_out&lt;/code&gt;: dict 或 &lt;code&gt;None&lt;/code&gt; - SAM2骨干输出（包含&lt;code&gt;vision_features&lt;/code&gt;, &lt;code&gt;vision_pos_enc&lt;/code&gt;, &lt;code&gt;backbone_fpn&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;language_features&lt;/code&gt;: Tensor - 语言特征&lt;/li&gt;
&lt;li&gt;&lt;code&gt;language_mask&lt;/code&gt;: Tensor - 语言注意力掩码&lt;/li&gt;
&lt;li&gt;&lt;code&gt;language_embeds&lt;/code&gt;: Tensor - 编码器前的文本嵌入&lt;/li&gt;
&lt;li&gt;&lt;code&gt;additional_text_features&lt;/code&gt;: Tensor (可选) - 附加文本特征&lt;/li&gt;
&lt;li&gt;&lt;code&gt;additional_text_mask&lt;/code&gt;: Tensor (可选) - 附加文本注意力掩码&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3.2.2 &lt;code&gt;SAM3VLBackboneTri&lt;/code&gt; 三重头视觉-语言骨干网络&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;继承关系&lt;/strong&gt;: &lt;code&gt;SAM3VLBackboneTri&lt;/code&gt; → &lt;code&gt;SAM3VLBackbone&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 三重头视觉骨干网络（SAM3、交互式、传播）与文本编码器的组合。视觉骨干网络为&lt;code&gt;Sam3TriViTDetNeck&lt;/code&gt;，同时输出三种不同的视觉特征：SAM3检测特征、交互式细化特征和传播跟踪特征。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特有属性&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;继承所有父类属性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vision_backbone&lt;/code&gt;类型强制为&lt;code&gt;Sam3TriViTDetNeck&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;visual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sam3TriViTDetNeck&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;三重头视觉骨干网络，必须为&lt;code&gt;Sam3TriViTDetNeck&lt;/code&gt;类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;文本编码器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_visual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否编译视觉骨干网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scalp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;丢弃最低分辨率特征的数量&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;特有方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;samples&lt;/code&gt;: Tensor, &lt;code&gt;need_sam3_out&lt;/code&gt;=True, &lt;code&gt;need_interactive_out&lt;/code&gt;=True, &lt;code&gt;need_propagation_out&lt;/code&gt;=True&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;三重头图像前向传播：可选择性输出三种特征类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_forward_image_tri_no_act_ckpt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;samples&lt;/code&gt;: Tensor, &lt;code&gt;need_sam3_out&lt;/code&gt;=True, &lt;code&gt;need_interactive_out&lt;/code&gt;=True, &lt;code&gt;need_propagation_out&lt;/code&gt;=True&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无激活检查点的三重头图像前向传播&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;输出字典结构&lt;/strong&gt; (&lt;code&gt;forward_image&lt;/code&gt; 方法返回):
根据&lt;code&gt;need_*&lt;/code&gt;参数选择性包含以下键：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SAM3输出（当&lt;code&gt;need_sam3_out=True&lt;/code&gt;）:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vision_features&lt;/code&gt;: Tensor - SAM3视觉特征&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vision_mask&lt;/code&gt;: Tensor - SAM3视觉掩码&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vision_pos_enc&lt;/code&gt;: List[Tensor] - SAM3位置编码&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backbone_fpn&lt;/code&gt;: List[NestedTensor] - SAM3多尺度特征&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;交互式输出（当&lt;code&gt;need_interactive_out=True&lt;/code&gt;）:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;interactive&lt;/code&gt;: dict - 包含&lt;code&gt;vision_features&lt;/code&gt;, &lt;code&gt;vision_mask&lt;/code&gt;, &lt;code&gt;vision_pos_enc&lt;/code&gt;, &lt;code&gt;backbone_fpn&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;传播输出（当&lt;code&gt;need_propagation_out=True&lt;/code&gt;）:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sam2_backbone_out&lt;/code&gt;: dict - 包含&lt;code&gt;vision_features&lt;/code&gt;, &lt;code&gt;vision_mask&lt;/code&gt;, &lt;code&gt;vision_pos_enc&lt;/code&gt;, &lt;code&gt;backbone_fpn&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3.2.3 &lt;code&gt;VisionOnly&lt;/code&gt; 仅视觉骨干网络&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;继承关系&lt;/strong&gt;: &lt;code&gt;VisionOnly&lt;/code&gt; → &lt;code&gt;torch.nn.Module&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 仅视觉骨干网络的包装器，用于无语言输入的推理场景。支持分块推理（chunk-wise inference）以处理大图像，并支持编译优化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vision_backbone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;视觉骨干网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;should_compile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自动判断&lt;/td&gt;
&lt;td&gt;是否应编译模型（根据&lt;code&gt;compile_mode&lt;/code&gt;或&lt;code&gt;compile_extra_args&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编译模式（如&lt;code&gt;&quot;reduce-overhead&quot;&lt;/code&gt;, &lt;code&gt;&quot;max-autotune&quot;&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_extra_args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;传递给&lt;code&gt;torch.compile&lt;/code&gt;的额外参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compiled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否已编译&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;n_features&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;特征维度（用于创建虚拟语言特征）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_in_chunk_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;评估时是否分块前向传播&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;eval_chunk_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;评估分块大小&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;eval_cast_to_cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;评估时是否将特征转换为CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scalp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;丢弃最低分辨率特征的数量&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;visual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;视觉骨干网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;n_features&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;特征维度（必须提供）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_in_chunk_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;评估时是否分块前向传播&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;eval_chunk_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;评估分块大小&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;eval_cast_to_cpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;评估时是否将特征转换为CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scalp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;丢弃最低分辨率特征的数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编译模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_extra_args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编译额外参数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;见上表初始化参数&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化仅视觉骨干网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_compile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;编译视觉骨干网络（如果配置了编译）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;samples&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;图像前向传播：编译模型（如果需要），提取视觉特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;captions&lt;/code&gt;: List[str], &lt;code&gt;input_boxes&lt;/code&gt;=None, &lt;code&gt;additional_text&lt;/code&gt;=None, &lt;code&gt;device&lt;/code&gt;=&quot;cuda&quot;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;虚拟文本前向传播：返回零张量作为语言特征（保持接口一致性）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;3.2.4 &lt;code&gt;TriHeadVisionOnly&lt;/code&gt; 三重头仅视觉骨干网络&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;继承关系&lt;/strong&gt;: &lt;code&gt;TriHeadVisionOnly&lt;/code&gt; → &lt;code&gt;VisionOnly&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 三重头仅视觉骨干网络，视觉骨干网络为&lt;code&gt;Sam3TriViTDetNeck&lt;/code&gt;，同时输出SAM3、交互式和传播三种视觉特征。用于无语言输入但需要多类型视觉特征的场景。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特有属性&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;继承所有父类属性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vision_backbone&lt;/code&gt;类型强制为&lt;code&gt;Sam3TriViTDetNeck&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;特有方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;samples&lt;/code&gt;: Tensor, &lt;code&gt;need_sam3_out&lt;/code&gt;=True, &lt;code&gt;need_interactive_out&lt;/code&gt;=True, &lt;code&gt;need_propagation_out&lt;/code&gt;=True&lt;/td&gt;
&lt;td&gt;&lt;code&gt;output&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;三重头图像前向传播：可选择性输出三种特征类型，功能与&lt;code&gt;SAM3VLBackboneTri.forward_image&lt;/code&gt;类似但不包含语言处理&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;注意：&lt;code&gt;TriHeadVisionOnly&lt;/code&gt;的&lt;code&gt;forward_text&lt;/code&gt;方法与&lt;code&gt;VisionOnly&lt;/code&gt;相同，返回虚拟语言特征。&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;3.3 &lt;code&gt;Prompt&lt;/code&gt; 集成包装 (&lt;code&gt;geometry_encoders.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;说明&lt;/strong&gt;: &lt;code&gt;Prompt&lt;/code&gt; 是一个几何提示工具类，用于操作几何提示（框、点、掩码），遵循 PyTorch 序列优先的约定。它封装了三种类型的几何提示：边界框（boxes）、点（points）和掩码（masks），并提供了统一的接口进行拼接、克隆等操作。&lt;code&gt;MaskEncoder&lt;/code&gt; 是掩码编码器的基类，负责将输入掩码下采样并添加位置编码。&lt;code&gt;FusedMaskEncoder&lt;/code&gt; 是 &lt;code&gt;MaskEncoder&lt;/code&gt; 的子类，额外融合了图像特征。&lt;code&gt;SequenceGeometryEncoder&lt;/code&gt; 是一个完整的几何提示编码器，支持将框、点、掩码编码为统一的序列表示。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class Prompt:&lt;/code&gt;（第83行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;box_embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;边界框嵌入，形状为 &lt;code&gt;(N_boxes, B, C_box)&lt;/code&gt;，其中 &lt;code&gt;C_box=4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;point_embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点嵌入，形状为 &lt;code&gt;(N_points, B, C_point)&lt;/code&gt;，其中 &lt;code&gt;C_point=2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码嵌入，形状为 &lt;code&gt;(N_masks, B, 1, H_mask, W_mask)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;box_mask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;边界框注意力掩码，形状为 &lt;code&gt;(B, N_boxes)&lt;/code&gt;，&lt;code&gt;True&lt;/code&gt; 表示填充位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;point_mask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点注意力掩码，形状为 &lt;code&gt;(B, N_points)&lt;/code&gt;，&lt;code&gt;True&lt;/code&gt; 表示填充位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_mask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码注意力掩码，形状为 &lt;code&gt;(B, N_masks)&lt;/code&gt;，&lt;code&gt;True&lt;/code&gt; 表示填充位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;box_labels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;边界框标签（正/负），形状为 &lt;code&gt;(N_boxes, B)&lt;/code&gt;，&lt;code&gt;1&lt;/code&gt; 表示正样本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;point_labels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点标签（正/负），形状为 &lt;code&gt;(N_points, B)&lt;/code&gt;，&lt;code&gt;1&lt;/code&gt; 表示正样本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_labels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码标签（正/负），形状为 &lt;code&gt;(N_masks, B)&lt;/code&gt;，&lt;code&gt;1&lt;/code&gt; 表示正样本&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;box_embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;边界框嵌入，形状 &lt;code&gt;(N_boxes, B, C_box)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;box_mask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;边界框注意力掩码，形状 &lt;code&gt;(B, N_boxes)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;point_embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点嵌入，形状 &lt;code&gt;(N_points, B, C_point)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;point_mask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点注意力掩码，形状 &lt;code&gt;(B, N_points)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;box_labels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;边界框标签，形状 &lt;code&gt;(N_boxes, B)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;point_labels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点标签，形状 &lt;code&gt;(N_points, B)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码嵌入，形状 &lt;code&gt;(N_masks, B, 1, H_mask, W_mask)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_mask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码注意力掩码，形状 &lt;code&gt;(B, N_masks)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_labels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.Tensor&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码标签，形状 &lt;code&gt;(N_masks, B)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;append_boxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boxes&lt;/code&gt;: Tensor, &lt;code&gt;labels&lt;/code&gt;: Tensor, &lt;code&gt;mask&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;将新的边界框、标签和掩码追加到现有数据中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;append_points&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;points&lt;/code&gt;: Tensor, &lt;code&gt;labels&lt;/code&gt;: Tensor, &lt;code&gt;mask&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;将新的点、标签和掩码追加到现有数据中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;append_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;masks&lt;/code&gt;: Tensor, &lt;code&gt;labels&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;attn_mask&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;将新的掩码、标签和注意力掩码追加到现有数据中（目前仅支持单掩码）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;返回当前提示的深拷贝&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_init_seq_len_and_device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;box_embeddings&lt;/code&gt;, &lt;code&gt;point_embeddings&lt;/code&gt;, &lt;code&gt;mask_embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(box_seq_len, point_seq_len, mask_seq_len, bs, device)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;内部方法：计算序列长度、批次大小和设备&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_init_box&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;box_embeddings&lt;/code&gt;, &lt;code&gt;box_labels&lt;/code&gt;, &lt;code&gt;box_mask&lt;/code&gt;, &lt;code&gt;box_seq_len&lt;/code&gt;, &lt;code&gt;bs&lt;/code&gt;, &lt;code&gt;device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(box_embeddings, box_labels, box_mask)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;内部方法：初始化边界框相关张量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_init_point&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;point_embeddings&lt;/code&gt;, &lt;code&gt;point_labels&lt;/code&gt;, &lt;code&gt;point_mask&lt;/code&gt;, &lt;code&gt;point_seq_len&lt;/code&gt;, &lt;code&gt;bs&lt;/code&gt;, &lt;code&gt;device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(point_embeddings, point_labels, point_mask)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;内部方法：初始化点相关张量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_init_mask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mask_embeddings&lt;/code&gt;, &lt;code&gt;mask_labels&lt;/code&gt;, &lt;code&gt;mask_mask&lt;/code&gt;, &lt;code&gt;mask_seq_len&lt;/code&gt;, &lt;code&gt;bs&lt;/code&gt;, &lt;code&gt;device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(mask_embeddings, mask_labels, mask_mask)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;内部方法：初始化掩码相关张量&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class MaskEncoder(nn.Module):&lt;/code&gt;（第404行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_downsampler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码下采样模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;position_encoding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;位置编码模块&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_downsampler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;掩码下采样模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;position_encoding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;位置编码模块&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;masks&lt;/code&gt;: Tensor, &lt;code&gt;*args&lt;/code&gt;, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(masks, masks_pos)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播：对输入掩码进行下采样并添加位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class FusedMaskEncoder(MaskEncoder):&lt;/code&gt;（第425行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;（继承自 &lt;code&gt;MaskEncoder&lt;/code&gt;，新增）:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fuser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;融合模块，用于融合图像特征和掩码特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出投影模块，&lt;code&gt;nn.Identity&lt;/code&gt; 或 &lt;code&gt;nn.Conv2d&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pix_feat_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Conv2d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;图像特征投影层&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;（继承自 &lt;code&gt;MaskEncoder&lt;/code&gt;，新增）:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_downsampler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;掩码下采样模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;position_encoding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;位置编码模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fuser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;融合模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;in_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入特征维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出特征维度&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;masks&lt;/code&gt;: Tensor, &lt;code&gt;pix_feat&lt;/code&gt;: Tensor, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(x, pos)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播：融合图像特征和掩码特征，输出融合后的特征和位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class SequenceGeometryEncoder(nn.Module):&lt;/code&gt;（第470行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;模型维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pos_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;位置编码器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;encode_boxes_as_points&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否将边界框编码为两个点（左上和右下）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;roi_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ROI 对齐的尺寸（用于边界框特征池化）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;label_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;标签嵌入层，&lt;code&gt;num_labels=6&lt;/code&gt;（若 &lt;code&gt;encode_boxes_as_points=True&lt;/code&gt;）或 &lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cls_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CLS 标记嵌入层，若 &lt;code&gt;add_cls=True&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;points_direct_project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点直接投影层（坐标 → 特征）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;points_pool_project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点特征池化投影层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;points_pos_enc_project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点位置编码投影层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;boxes_direct_project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;边界框直接投影层（坐标 → 特征）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;boxes_pool_project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Conv2d&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;边界框特征池化投影层（ROI 对齐后）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;boxes_pos_enc_project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;边界框位置编码投影层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;final_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;最终投影层，若 &lt;code&gt;add_post_encode_proj=True&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;norm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.LayerNorm&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;层归一化，与 &lt;code&gt;final_proj&lt;/code&gt; 配套&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;img_pre_norm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;图像特征预归一化层（&lt;code&gt;nn.Identity&lt;/code&gt; 或 &lt;code&gt;nn.LayerNorm&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;encode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.ModuleList&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transformer 编码层列表，若 &lt;code&gt;num_layers&amp;gt;0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;encode_norm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.LayerNorm&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编码器后的层归一化，若 &lt;code&gt;encode&lt;/code&gt; 存在&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_label_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码标签嵌入层，若 &lt;code&gt;add_mask_label=True&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_mask_label&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否添加掩码标签嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MaskEncoder&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码编码器实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_act_ckpt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用激活检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;encode_boxes_as_points&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;是否将边界框编码为两个点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;points_direct_project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;是否使用直接投影编码点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;points_pool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;是否使用特征池化编码点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;points_pos_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;是否使用位置编码编码点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;boxes_direct_project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;是否使用直接投影编码边界框&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;boxes_pool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;是否使用特征池化编码边界框&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;boxes_pos_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;是否使用位置编码编码边界框&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;模型维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pos_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;位置编码器实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_layers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;Transformer 编码层数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;layer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;单层 Transformer 模块（将被复制 &lt;code&gt;num_layers&lt;/code&gt; 次）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;roi_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ROI 对齐的尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_cls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否添加 CLS 标记&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_post_encode_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否添加编码后投影层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MaskEncoder&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码编码器实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_mask_label&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否添加掩码标签嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_act_ckpt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用激活检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;geo_prompt&lt;/code&gt;: Prompt, &lt;code&gt;img_feats&lt;/code&gt;: Tensor, &lt;code&gt;img_sizes&lt;/code&gt;: List[Tuple], &lt;code&gt;img_pos_embeds&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(final_embeds, final_mask)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播：将几何提示编码为统一的序列表示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_encode_points&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;points&lt;/code&gt;: Tensor, &lt;code&gt;points_mask&lt;/code&gt;: Tensor, &lt;code&gt;points_labels&lt;/code&gt;: Tensor, &lt;code&gt;img_feats&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(points_embed, points_mask)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;内部方法：编码点提示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_encode_boxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boxes&lt;/code&gt;: Tensor, &lt;code&gt;boxes_mask&lt;/code&gt;: Tensor, &lt;code&gt;boxes_labels&lt;/code&gt;: Tensor, &lt;code&gt;img_feats&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(boxes_embed, boxes_mask)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;内部方法：编码边界框提示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_encode_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;masks&lt;/code&gt;: Tensor, &lt;code&gt;attn_mask&lt;/code&gt;: Tensor, &lt;code&gt;mask_labels&lt;/code&gt;: Tensor, &lt;code&gt;img_feats&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(masks_embed, attn_mask)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;内部方法：编码掩码提示&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;: &lt;code&gt;SequenceGeometryEncoder&lt;/code&gt; 支持三种编码方式的任意组合（直接投影、特征池化、位置编码），组合时采用简单相加。若 &lt;code&gt;encode_boxes_as_points=True&lt;/code&gt;，边界框将被转换为两个点（左上和右下）并与现有点序列拼接。&lt;/p&gt;
&lt;h2&gt;4. 解码模块与记忆增强 (Decoders &amp;amp; Memory)&lt;/h2&gt;
&lt;h3&gt;4.1 &lt;code&gt;MultiplexMaskDecoder&lt;/code&gt; (&lt;code&gt;multiplex_mask_decoder.py&lt;/code&gt; &amp;amp; &lt;code&gt;maskformer_segmentation.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;说明&lt;/strong&gt;: &lt;code&gt;MultiplexMaskDecoder&lt;/code&gt; 是一个支持多路复用（multiplex）的掩码解码器，能够同时预测多个目标的掩码。它借鉴了 MaskFormer 的思想，使用 Transformer 架构和动态多掩码选择机制。&lt;code&gt;maskformer_segmentation.py&lt;/code&gt; 提供了 &lt;code&gt;SegmentationHead&lt;/code&gt;、&lt;code&gt;PixelDecoder&lt;/code&gt;、&lt;code&gt;UniversalSegmentationHead&lt;/code&gt; 等组件，用于像素级特征解码和掩码预测。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class MultiplexMaskDecoder(nn.Module):&lt;/code&gt;（第16行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transformer 特征维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transformer 模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multiplex_count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;多路复用数量，即单个特征图中包含的目标数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_multimask_outputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每个目标输出的多掩码数量（总掩码数为 &lt;code&gt;(num_multimask_outputs+1) * multiplex_count&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_outputs_only&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否仅输出多掩码（不包含单掩码输出令牌）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decode_mask_with_shared_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用共享令牌解码掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decode_mask_attribute_with_shared_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用掩码令牌（而非独立令牌）预测 IoU 和对象分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_mask_output_per_object&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每个目标的掩码输出数（&lt;code&gt;num_multimask_outputs+1&lt;/code&gt; 或 &lt;code&gt;num_multimask_outputs&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_mask_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码令牌总数（&lt;code&gt;multiplex_count * num_mask_output_per_object&lt;/code&gt; 或 &lt;code&gt;multiplex_count&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred_obj_scores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否预测对象分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_multimask_token_for_obj_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用多掩码令牌作为对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoU 令牌嵌入层（若 &lt;code&gt;decode_mask_attribute_with_shared_tokens=False&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;obj_score_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对象分数令牌嵌入层（若 &lt;code&gt;pred_obj_scores=True&lt;/code&gt; 且 &lt;code&gt;decode_mask_attribute_with_shared_tokens=False&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码令牌嵌入层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output_upscaling&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Sequential&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出上采样模块（转置卷积 + 层归一化 + 激活）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_high_res_features&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用高分辨率特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;conv_s0&lt;/code&gt;, &lt;code&gt;conv_s1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Conv2d&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;高分辨率特征卷积层（若 &lt;code&gt;use_high_res_features=True&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output_hypernetworks_mlp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MLP&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出超网络 MLP（若 &lt;code&gt;num_multimask_outputs=0&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output_hypernetworks_mlps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.ModuleList&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出超网络 MLP 列表（每个掩码输出一个）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_prediction_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MLP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoU 预测头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred_obj_score_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt; 或 &lt;code&gt;MLP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对象分数预测头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_via_stability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否通过稳定性动态选择多掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_stability_delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;稳定性计算中的 delta 阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_stability_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;稳定性阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;Transformer 特征维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;Transformer 模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multiplex_count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;多路复用数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_multimask_outputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每个目标输出的多掩码数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;activation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Type[nn.Module]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.GELU&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;激活函数类&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_head_depth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoU 预测头的层数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_head_hidden_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoU 预测头的隐藏维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_high_res_features&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用高分辨率特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_prediction_use_sigmoid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoU 预测是否使用 sigmoid 输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_via_stability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否通过稳定性动态选择多掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_stability_delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.05&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;稳定性计算中的 delta 阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_stability_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.98&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;稳定性阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred_obj_scores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否预测对象分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred_obj_scores_mlp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对象分数预测是否使用 MLP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_multimask_token_for_obj_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用多掩码令牌作为对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decode_mask_with_shared_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用共享令牌解码掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decode_mask_attribute_with_shared_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用掩码令牌预测 IoU 和对象分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_outputs_only&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否仅输出多掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image_embeddings&lt;/code&gt;: Tensor, &lt;code&gt;image_pe&lt;/code&gt;: Tensor, &lt;code&gt;multimask_output&lt;/code&gt;: bool, &lt;code&gt;high_res_features&lt;/code&gt;: List[Tensor] 或 &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;extra_per_object_embeddings&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict[str, Tensor]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播：预测掩码、IoU 和对象分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;predict_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image_embeddings&lt;/code&gt;: Tensor, &lt;code&gt;image_pe&lt;/code&gt;: Tensor, &lt;code&gt;high_res_features&lt;/code&gt;: List[Tensor] 或 &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;extra_per_object_embeddings&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict[str, Tensor]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;核心预测方法：生成掩码、IoU 和对象分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_stability_scores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mask_logits&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;计算掩码 logits 的稳定性分数（基于上下阈值的 IoU）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_dynamic_multimask_via_stability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;all_mask_logits&lt;/code&gt;: Tensor, &lt;code&gt;all_iou_scores&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(mask_logits_out, iou_scores_out)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;动态选择机制：当单掩码输出稳定性低时，选择多掩码输出中 IoU 最高的掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;辅助类&lt;/strong&gt;: &lt;code&gt;class MLP(nn.Module):&lt;/code&gt;（第448行）—— 简单的多层感知机，用于 IoU 预测头等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class SegmentationHead(nn.Module):&lt;/code&gt;（第56行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_encoder_inputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用编码器输入（而非骨干特征）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aux_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否输出辅助掩码（多层级）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pixel_decoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PixelDecoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;像素解码器实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_dec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否跳过解码器（直接卷积预测）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_predictor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MaskPredictor&lt;/code&gt; 或 &lt;code&gt;nn.Conv2d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码预测器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;act_ckpt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用激活检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instance_keys&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;List[str]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出字典的键名列表&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hidden_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;隐藏维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upsampling_stages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;上采样阶段数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_encoder_inputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用编码器输入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aux_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否输出辅助掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_dec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否跳过解码器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pixel_decoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PixelDecoder&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;像素解码器实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;act_ckpt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用激活检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shared_conv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否共享卷积层（仅对 &lt;code&gt;PixelDecoder&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_mode_pixel_decoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;像素解码器的编译模式&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_feats&lt;/code&gt;: List[Tensor], &lt;code&gt;obj_queries&lt;/code&gt;: Tensor, &lt;code&gt;image_ids&lt;/code&gt;: Tensor, &lt;code&gt;encoder_hidden_states&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict[str, Tensor]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播：生成预测掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_embed_pixels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_feats&lt;/code&gt;: List[Tensor], &lt;code&gt;image_ids&lt;/code&gt;: Tensor, &lt;code&gt;encoder_hidden_states&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;内部方法：嵌入像素特征&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class PixelDecoder(nn.Module):&lt;/code&gt;（第184行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hidden_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;隐藏维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_upsampling_stages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;上采样阶段数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interpolation_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;插值模式（&quot;nearest&quot; 等）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;conv_layers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.ModuleList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;卷积层列表&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;norms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.ModuleList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;归一化层列表&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shared_conv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否共享卷积层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出维度&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hidden_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;隐藏维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_upsampling_stages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;上采样阶段数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interpolation_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;nearest&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;插值模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shared_conv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否共享卷积层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;编译模式（torch.compile）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_feats&lt;/code&gt;: List[Tensor]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播：解码像素特征&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class UniversalSegmentationHead(SegmentationHead):&lt;/code&gt;（第234行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;（继承自 &lt;code&gt;SegmentationHead&lt;/code&gt;，新增）:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;模型维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;presence_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LinearPresenceHead&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;存在性预测头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cross_attend_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;交叉注意力提示模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cross_attn_norm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.LayerNorm&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;交叉注意力归一化层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;semantic_seg_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Conv2d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;语义分割头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instance_seg_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Conv2d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;实例分割头&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;（继承自 &lt;code&gt;SegmentationHead&lt;/code&gt;，新增）:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hidden_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;隐藏维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upsampling_stages&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;上采样阶段数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pixel_decoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PixelDecoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;像素解码器实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aux_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否输出辅助掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_dec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否跳过解码器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;act_ckpt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用激活检查点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;presence_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否添加存在性预测头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dot_product_scorer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点积评分器（若 &lt;code&gt;presence_head=True&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cross_attend_prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;交叉注意力提示模块&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_feats&lt;/code&gt;: List[Tensor], &lt;code&gt;obj_queries&lt;/code&gt;: Tensor, &lt;code&gt;image_ids&lt;/code&gt;: Tensor, &lt;code&gt;encoder_hidden_states&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;prompt&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;prompt_mask&lt;/code&gt;: Tensor 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict[str, Tensor]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播：生成实例分割掩码、语义分割图和存在性 logits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;: &lt;code&gt;maskformer_segmentation.py&lt;/code&gt; 还包含 &lt;code&gt;LinearPresenceHead&lt;/code&gt;（第16行）和 &lt;code&gt;MaskPredictor&lt;/code&gt;（第25行）等辅助类。&lt;/p&gt;
&lt;h3&gt;4.2 &lt;code&gt;MaskDecoder&lt;/code&gt; (&lt;code&gt;sam/mask_decoder.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;说明&lt;/strong&gt;: 标准的 Transformer 掩码解码器，用于根据图像嵌入和提示嵌入预测掩码。它支持多掩码输出（每个提示最多输出4个掩码：1个主掩码 + 3个辅助掩码）和动态多掩码选择机制（基于稳定性分数）。是 SAM 系列的核心解码组件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class MaskDecoder(nn.Module):&lt;/code&gt;（第14行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transformer 特征维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transformer 模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_multimask_outputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每个提示输出的多掩码数量（默认3个辅助掩码）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoU 预测令牌嵌入层（1个令牌）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_mask_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码令牌总数（&lt;code&gt;num_multimask_outputs + 1&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码令牌嵌入层（&lt;code&gt;num_mask_tokens&lt;/code&gt; 个令牌）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred_obj_scores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否预测对象分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;obj_score_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对象分数令牌嵌入层（若 &lt;code&gt;pred_obj_scores=True&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_multimask_token_for_obj_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用多掩码令牌作为对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output_upscaling&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Sequential&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出上采样模块（转置卷积 + 层归一化 + 激活）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_high_res_features&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用高分辨率特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;conv_s0&lt;/code&gt;, &lt;code&gt;conv_s1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Conv2d&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;高分辨率特征卷积层（若 &lt;code&gt;use_high_res_features=True&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output_hypernetworks_mlps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.ModuleList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出超网络 MLP 列表（每个掩码令牌一个）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_prediction_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MLP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoU 预测头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred_obj_score_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt; 或 &lt;code&gt;MLP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对象分数预测头（若 &lt;code&gt;pred_obj_scores=True&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_via_stability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否通过稳定性动态选择多掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_stability_delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;稳定性计算中的 delta 阈值（用于上下阈值）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_stability_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;稳定性阈值（低于此值时切换到多掩码输出）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;Transformer 特征维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;Transformer 模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_multimask_outputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每个提示输出的多掩码数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;activation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Type[nn.Module]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.GELU&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;激活函数类（用于上采样模块）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_head_depth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoU 预测头的层数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_head_hidden_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoU 预测头的隐藏维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_high_res_features&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用高分辨率特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_prediction_use_sigmoid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoU 预测是否使用 sigmoid 输出（限制到 [0,1]）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_via_stability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否通过稳定性动态选择多掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_stability_delta&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.05&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;稳定性计算中的 delta 阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_stability_thresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.98&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;稳定性阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred_obj_scores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否预测对象分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred_obj_scores_mlp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对象分数预测是否使用 MLP（否则使用线性层）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_multimask_token_for_obj_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用多掩码令牌作为对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image_embeddings&lt;/code&gt;: Tensor, &lt;code&gt;image_pe&lt;/code&gt;: Tensor, &lt;code&gt;sparse_prompt_embeddings&lt;/code&gt;: Tensor, &lt;code&gt;dense_prompt_embeddings&lt;/code&gt;: Tensor, &lt;code&gt;multimask_output&lt;/code&gt;: bool, &lt;code&gt;repeat_image&lt;/code&gt;: bool, &lt;code&gt;high_res_features&lt;/code&gt;: List[Tensor] 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(masks, iou_pred, sam_tokens_out, object_score_logits)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播：根据图像嵌入和提示嵌入预测掩码、IoU、SAM令牌和对象分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;predict_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image_embeddings&lt;/code&gt;: Tensor, &lt;code&gt;image_pe&lt;/code&gt;: Tensor, &lt;code&gt;sparse_prompt_embeddings&lt;/code&gt;: Tensor, &lt;code&gt;dense_prompt_embeddings&lt;/code&gt;: Tensor, &lt;code&gt;repeat_image&lt;/code&gt;: bool, &lt;code&gt;high_res_features&lt;/code&gt;: List[Tensor] 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(masks, iou_pred, mask_tokens_out, object_score_logits)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;核心预测方法：连接令牌、运行Transformer、上采样特征、生成掩码和预测分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_stability_scores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mask_logits&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;计算掩码 logits 的稳定性分数：基于上下阈值（±delta）的 IoU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_dynamic_multimask_via_stability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;all_mask_logits&lt;/code&gt;: Tensor, &lt;code&gt;all_iou_scores&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(mask_logits_out, iou_scores_out)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;动态选择机制：当单掩码输出（令牌0）的稳定性分数低于阈值时，选择多掩码输出（令牌1~3）中 IoU 最高的掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;辅助类&lt;/strong&gt;: &lt;code&gt;class MLP(nn.Module):&lt;/code&gt;（第299行）—— 简单的多层感知机，用于 IoU 预测头等。包含 &lt;code&gt;num_layers&lt;/code&gt; 个线性层和 ReLU 激活（最后一层除外），可选 sigmoid 输出。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工作原理&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;令牌连接&lt;/strong&gt;: 将 &lt;code&gt;iou_token&lt;/code&gt;、&lt;code&gt;mask_tokens&lt;/code&gt;（以及可选的 &lt;code&gt;obj_score_token&lt;/code&gt;）与稀疏提示嵌入拼接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transformer 处理&lt;/strong&gt;: 将拼接后的令牌与图像嵌入（+密集提示嵌入）输入 Transformer。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特征上采样&lt;/strong&gt;: 通过转置卷积上采样 Transformer 输出特征。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;掩码生成&lt;/strong&gt;: 通过超网络 MLP 将掩码令牌嵌入映射到权重，与上采样特征进行矩阵乘法生成掩码 logits。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分数预测&lt;/strong&gt;: 通过 IoU 预测头（MLP）和对象分数预测头（线性层或 MLP）生成质量分数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态选择&lt;/strong&gt;: 在推理时（非训练），若 &lt;code&gt;dynamic_multimask_via_stability=True&lt;/code&gt;，则根据稳定性分数动态选择最佳掩码。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.3 &lt;code&gt;SimpleMaskEncoder&lt;/code&gt; (&lt;code&gt;model/memory.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;说明&lt;/strong&gt;: 轻量级时序记忆编码器，用于将前一帧的像素特征与掩码（分割结果）融合并压缩为记忆表示，供后续帧的 Attention 模块读取。它是多目标跟踪中记忆增强机制的核心组件，专门设计为轻量级以支持实时视频处理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class SimpleMaskEncoder(nn.Module):&lt;/code&gt;（第166行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_downsampler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SimpleMaskDownSampler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码下采样器，将掩码下采样到与像素特征相同的空间分辨率&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pix_feat_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Conv2d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;像素特征投影层（1x1卷积），用于调整像素特征的通道维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fuser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SimpleFuser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;融合器，将像素特征和下采样掩码融合的模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;position_encoding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;位置编码生成器（通常与视觉骨干共享）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Identity&lt;/code&gt; 或 &lt;code&gt;nn.Conv2d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输出投影层（1x1卷积），将融合特征投影到目标维度；如果输入输出维度相同则为恒等映射&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;输出特征维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_downsampler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SimpleMaskDownSampler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;掩码下采样器实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fuser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SimpleFuser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;融合器实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;position_encoding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;位置编码生成器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;in_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入像素特征的通道维度&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pix_feat&lt;/code&gt;: Tensor（形状 &lt;code&gt;(B, C, H, W)&lt;/code&gt;），&lt;code&gt;masks&lt;/code&gt;: Tensor（形状 &lt;code&gt;(B, M, H&apos;, W&apos;)&lt;/code&gt;），&lt;code&gt;skip_mask_sigmoid&lt;/code&gt;: bool（默认 &lt;code&gt;False&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict[str, Tensor]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播：将像素特征与掩码融合，生成记忆特征和位置编码。输出字典包含 &lt;code&gt;&quot;vision_features&quot;&lt;/code&gt;（融合特征）和 &lt;code&gt;&quot;vision_pos_enc&quot;&lt;/code&gt;（位置编码列表）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;工作原理&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;掩码预处理&lt;/strong&gt;: 如果 &lt;code&gt;skip_mask_sigmoid=False&lt;/code&gt;（默认），对掩码应用 sigmoid 激活，使其值域在 [0,1] 之间，减少与二值 GT 掩码的域差异。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;掩码下采样&lt;/strong&gt;: 通过 &lt;code&gt;SimpleMaskDownSampler&lt;/code&gt; 将掩码下采样到与像素特征相同的空间分辨率（通常下采样16倍）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;像素特征投影&lt;/strong&gt;: 通过 &lt;code&gt;pix_feat_proj&lt;/code&gt;（1x1卷积）调整像素特征的通道维度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特征融合&lt;/strong&gt;: 将投影后的像素特征与下采样掩码&lt;strong&gt;相加&lt;/strong&gt;（元素级加法）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;融合处理&lt;/strong&gt;: 通过 &lt;code&gt;fuser&lt;/code&gt;（多层 &lt;code&gt;CXBlock&lt;/code&gt; 堆叠）进行深度特征融合。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出投影&lt;/strong&gt;: 通过 &lt;code&gt;out_proj&lt;/code&gt; 将特征投影到目标维度（如果 &lt;code&gt;out_dim != in_dim&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位置编码生成&lt;/strong&gt;: 使用 &lt;code&gt;position_encoding&lt;/code&gt; 从融合特征生成位置编码。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;相关组件&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;SimpleMaskDownSampler&lt;/code&gt;&lt;/strong&gt;（第21行）: 渐进式掩码下采样器，专门设计支持多路复用（multiplex）机制，允许同时处理多个目标的掩码。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心参数&lt;/strong&gt;: &lt;code&gt;embed_dim&lt;/code&gt;（输出维度）、&lt;code&gt;kernel_size&lt;/code&gt;（卷积核大小）、&lt;code&gt;stride&lt;/code&gt;（每层步长）、&lt;code&gt;total_stride&lt;/code&gt;（总下采样倍数）、&lt;code&gt;multiplex_count&lt;/code&gt;（多路复用计数）、&lt;code&gt;interpol_size&lt;/code&gt;（可选插值尺寸）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工作原理&lt;/strong&gt;: 通过 &lt;code&gt;num_layers = log₂(total_stride) / log₂(stride)&lt;/code&gt; 层卷积堆叠实现逐级下采样，每层下采样 &lt;code&gt;stride&lt;/code&gt; 倍，通道数增加 &lt;code&gt;stride²&lt;/code&gt; 倍。支持可选的预插值以适应特定分辨率。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;CXBlock&lt;/code&gt;&lt;/strong&gt;（第90行）: ConvNeXt 风格残差块，使用深度可分离卷积和层缩放（Layer Scale）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心参数&lt;/strong&gt;: &lt;code&gt;dim&lt;/code&gt;（通道数）、&lt;code&gt;kernel_size&lt;/code&gt;（深度卷积核大小）、&lt;code&gt;drop_path&lt;/code&gt;（随机深度率）、&lt;code&gt;layer_scale_init_value&lt;/code&gt;（层缩放初始值）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结构&lt;/strong&gt;: &lt;code&gt;DwConv&lt;/code&gt; → &lt;code&gt;LayerNorm&lt;/code&gt; → &lt;code&gt;1x1 Conv（扩展4倍）&lt;/code&gt; → &lt;code&gt;GELU&lt;/code&gt; → &lt;code&gt;1x1 Conv（收缩回原维度）&lt;/code&gt; → &lt;code&gt;层缩放&lt;/code&gt; → &lt;code&gt;残差连接&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;SimpleFuser&lt;/code&gt;&lt;/strong&gt;（第148行）: 简单融合器，将多个 &lt;code&gt;CXBlock&lt;/code&gt;（或其他模块）堆叠起来。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心参数&lt;/strong&gt;: &lt;code&gt;layer&lt;/code&gt;（基础层类）、&lt;code&gt;num_layers&lt;/code&gt;（堆叠层数）、&lt;code&gt;dim&lt;/code&gt;（输入维度）、&lt;code&gt;input_projection&lt;/code&gt;（是否添加输入投影）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能&lt;/strong&gt;: 可选地添加输入投影层（1x1卷积），然后堆叠指定数量的层。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;设计特点&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;轻量化&lt;/strong&gt;: 使用深度可分离卷积（ConvNeXt块）和渐进式下采样，保持较低的计算开销。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多路复用支持&lt;/strong&gt;: &lt;code&gt;SimpleMaskDownSampler&lt;/code&gt; 的 &lt;code&gt;multiplex_count&lt;/code&gt; 参数允许同时处理多个目标的掩码，这是多目标跟踪的关键。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;域适应&lt;/strong&gt;: 通过 sigmoid 激活将二值掩码转换为连续值，减少训练与推理时的域差异。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位置编码重用&lt;/strong&gt;: 使用与视觉骨干相同的位置编码生成器，确保编码一致性。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.4 &lt;code&gt;PromptEncoder&lt;/code&gt; (&lt;code&gt;sam/prompt_encoder.py&lt;/code&gt;) &amp;amp; &lt;code&gt;PositionEmbeddingRandom&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;说明&lt;/strong&gt;: 提示编码器，用于将各种类型的提示（点、框、掩码）编码为嵌入向量，供掩码解码器使用。&lt;code&gt;PositionEmbeddingRandom&lt;/code&gt; 使用随机空间频率生成位置编码，是提示编码器的核心组件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class PromptEncoder(nn.Module):&lt;/code&gt;（第14行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;embed_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;嵌入维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input_image_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tuple[int, int]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入图像尺寸（高，宽）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_embedding_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tuple[int, int]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;图像嵌入的空间尺寸（高，宽）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pe_layer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PositionEmbeddingRandom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;位置编码层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_point_embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点嵌入数量（4个：正点、负点 + 2个框角点）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;point_embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.ModuleList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点嵌入层列表（4个 &lt;code&gt;nn.Embedding&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;not_a_point_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&quot;非点&quot;嵌入（用于填充点）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_input_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tuple[int, int]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码输入尺寸（4倍于图像嵌入尺寸）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_downscaling&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Sequential&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码下采样模块（卷积 + 层归一化 + 激活）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_mask_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&quot;无掩码&quot;嵌入（当没有掩码输入时使用）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;embed_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;嵌入维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_embedding_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tuple[int, int]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;图像嵌入的空间尺寸（高，宽）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input_image_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tuple[int, int]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;输入图像尺寸（高，宽）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_in_chans&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;掩码编码的隐藏通道数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;activation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Type[nn.Module]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.GELU&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;激活函数类（用于掩码下采样）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_dense_pe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;（形状 &lt;code&gt;1xembed_dim x H x W&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;获取密集位置编码，用于点提示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_embed_points&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;points&lt;/code&gt;: Tensor（坐标）, &lt;code&gt;labels&lt;/code&gt;: Tensor（标签）, &lt;code&gt;pad&lt;/code&gt;: bool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;（形状 &lt;code&gt;B x N x embed_dim&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;嵌入点提示：应用位置编码并加上对应的点嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_embed_boxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boxes&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;（形状 &lt;code&gt;B x 2 x embed_dim&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;嵌入框提示：将框视为两个角点，分别加上框角点嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_embed_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;masks&lt;/code&gt;: Tensor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;（形状 &lt;code&gt;B x embed_dim x H x W&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;嵌入掩码提示：通过下采样卷积网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_batch_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;points&lt;/code&gt;: 可选, &lt;code&gt;boxes&lt;/code&gt;: 可选, &lt;code&gt;masks&lt;/code&gt;: 可选&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;根据输入提示获取批次大小&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;获取模型参数所在的设备&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;points&lt;/code&gt;: 可选, &lt;code&gt;boxes&lt;/code&gt;: 可选, &lt;code&gt;masks&lt;/code&gt;: 可选&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(sparse_embeddings, dense_embeddings)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播：编码所有类型的提示，返回稀疏嵌入（点/框）和密集嵌入（掩码）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;工作流程&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;点编码&lt;/strong&gt;: 坐标 + 0.5（移到像素中心）→ 位置编码 → 根据标签（-1,0,1,2,3）加上对应的点嵌入。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;框编码&lt;/strong&gt;: 框坐标 + 0.5 → 视为两个角点 → 位置编码 → 分别加上角点嵌入（索引2和3）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;掩码编码&lt;/strong&gt;: 通过三层卷积下采样（2x2 stride）到图像嵌入尺寸的1/4，然后投影到 &lt;code&gt;embed_dim&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无提示处理&lt;/strong&gt;: 若无点/框，添加填充点；若无掩码，使用 &lt;code&gt;no_mask_embed&lt;/code&gt; 扩展为密集嵌入。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;类定义&lt;/strong&gt;: &lt;code&gt;class PositionEmbeddingRandom(nn.Module):&lt;/code&gt;（第202行）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;positional_encoding_gaussian_matrix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;（注册的缓冲区）&lt;/td&gt;
&lt;td&gt;随机高斯矩阵（2 x &lt;code&gt;num_pos_feats&lt;/code&gt;），用于生成位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;初始化参数表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_pos_feats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;位置特征数（输出维度为 &lt;code&gt;2 * num_pos_feats&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scale&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;高斯矩阵的缩放因子（若为 None 或 ≤0，则使用1.0）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;核心方法表&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_pe_encoding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;coords&lt;/code&gt;: Tensor（归一化到 [0,1]）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;（形状 &lt;code&gt;... x C&lt;/code&gt;，C=2*&lt;code&gt;num_pos_feats&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;核心编码函数：将坐标映射到正弦/余弦位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;size&lt;/code&gt;: Tuple[int, int]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;（形状 &lt;code&gt;C x H x W&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;为指定尺寸的网格生成位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_with_coords&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;coords_input&lt;/code&gt;: Tensor, &lt;code&gt;image_size&lt;/code&gt;: Tuple[int, int]&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Tensor&lt;/code&gt;（形状 &lt;code&gt;B x N x C&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;为非归一化坐标生成位置编码：先归一化到 [0,1]，再调用 &lt;code&gt;_pe_encoding&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;编码原理&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;随机频率矩阵&lt;/strong&gt;: 使用随机高斯矩阵 &lt;code&gt;M&lt;/code&gt;（2 x &lt;code&gt;num_pos_feats&lt;/code&gt;）作为频率基础。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;坐标归一化&lt;/strong&gt;: 坐标归一化到 [0,1]（网格生成）或通过图像尺寸归一化（&lt;code&gt;forward_with_coords&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线性变换&lt;/strong&gt;: &lt;code&gt;coords = 2 * coords - 1&lt;/code&gt;（映射到 [-1,1]），然后 &lt;code&gt;coords = coords @ M&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正弦/余弦编码&lt;/strong&gt;: &lt;code&gt;coords = 2 * π * coords&lt;/code&gt;，然后拼接 &lt;code&gt;sin(coords)&lt;/code&gt; 和 &lt;code&gt;cos(coords)&lt;/code&gt;，得到维度 &lt;code&gt;2 * num_pos_feats&lt;/code&gt; 的编码。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;: &lt;code&gt;PositionEmbeddingRandom&lt;/code&gt; 是 SAM 系列中广泛使用的位置编码方式，不同于传统的固定正弦编码，它使用随机频率矩阵，具有一定的泛化能力。&lt;/p&gt;
&lt;h2&gt;5. 高性能推理与支持模块 (Perf. &amp;amp; Utils)&lt;/h2&gt;
&lt;h3&gt;5.1 NMS 非极大值抑制引擎 (&lt;code&gt;sam3_multiplex_detector_utils.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;核心方法&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nms_masks(...)&lt;/code&gt; / &lt;code&gt;_nms_masks_core_batched(...)&lt;/code&gt;:
核心级 Tensor 并行打压冗余模块，采用交并比 (IoU) 或交小比 (IoM)（针对局部遮挡）与得分对数百个粗粒度 Multi-object 输出进行大批量修剪提纯，这是 SAM 3 复用追踪（Multiplex）能并行锁定大量目标不干涉重叠的基础支撑点。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.2 精度调度与编译引擎 (&lt;code&gt;sam3/perflib/*&lt;/code&gt;, &lt;code&gt;act_ckpt_utils.py&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;说明&lt;/strong&gt;: SAM 3 引入了更强大的激活检查点（Activation Checkpointing）来极大节省显存，并用 &lt;code&gt;torch.compile&lt;/code&gt; 对各个核心函数（如 Transformer 的前向传播、NMS 的并联张量分配）进行 CUDA 图融合封装，以支持其在低资源上流畅推进高并发。&lt;/p&gt;
</content:encoded></item><item><title>SAM2 模型代码探幽</title><link>https://adalovelemon.github.io/posts/content/technotes/foundationmodels/sam/sam2/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/foundationmodels/sam/sam2/</guid><pubDate>Mon, 06 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Steps, Tabs, TabItem, Timeline, Card, CardList, Collapse, Button, Spoiler, Label } from &quot;astro-pure/user&quot;&lt;/p&gt;
&lt;p&gt;export const images = import.meta.glob(&quot;./assets/*.{png,jpg,jpeg,webp,gif,avif,svg}&quot;, {
eager: true,
import: &quot;default&quot;,
})&lt;/p&gt;
&lt;h2&gt;前序工作&lt;/h2&gt;
&lt;h3&gt;基本介绍&lt;/h3&gt;
&lt;p&gt;SAM2 (Segment Anything Model 2) 是 Meta AI 研究院开发的一款强大的图像分割模型，能够在各种图像上进行高效的分割任务。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2408.00714&quot;&gt;SAM2 论文&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/facebookresearch/sam2&quot;&gt;代码&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Okay, clone 完项目的代码后，在 &lt;a href=&quot;https://github.com/facebookresearch/sam2/blob/main/README.md&quot;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; 中可以看到 SAM2 的基本结构图。&lt;/p&gt;
&lt;h3&gt;模型结构&lt;/h3&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/sam2.png&quot;].src} alt=&quot;SAM2 Model Diagram&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;图像截取自 SAM2 论文&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/facebookresearch/sam2/blob/main/README.md&quot;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; 中是这样介绍 SAM2 的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Segment Anything Model 2 (SAM 2)&lt;/strong&gt; is a foundation model towards solving promptable visual segmentation in images and videos. We extend SAM to video by considering images as a video with a single frame. The model design is a simple transformer architecture with streaming memory for real-time video processing. We build a model-in-the-loop data engine, which improves model and data via user interaction, to collect &lt;a href=&quot;https://ai.meta.com/datasets/segment-anything-video&quot;&gt;&lt;strong&gt;our SA-V dataset&lt;/strong&gt;&lt;/a&gt;, the largest video segmentation dataset to date. SAM 2 trained on our data provides strong performance across a wide range of tasks and visual domains.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;可以看出 SAM2 相较于 SAM 的主要改进在于其引入了一个流式内存（记忆）机制，使其能够更高效地处理视频数据。&lt;/p&gt;
&lt;h3&gt;配置环境&lt;/h3&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://github.com/facebookresearch/sam2/blob/main/README.md&quot;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; 中的说明，作如下配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda create -n sam2 python=3.12 -y
conda activate sam2

cd sam2
pip install torch==2.5.1 torchvision==0.20.1 --index-url https://download.pytorch.org/whl/cu124
pip install -e .
pip install -e &quot;.[notebooks]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;预训练模型下载&lt;/h3&gt;
&lt;p&gt;官方的预训练模型有 SAM2 和 SAM2.1 两个版本，其中 SAM 2.1 是 SAM 2 的全面升级版，在训练的时候引入了额外的数据增强技术，因此 SAM 2.1 更加适合处理复杂场景（遮挡、小物体、低光照）。下面两个是官方提供的预训练模型的性能对比表格：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Size (M)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Speed (FPS)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;SA-V test (J&amp;amp;F)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;MOSE val (J&amp;amp;F)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;LVOS v2 (J&amp;amp;F)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sam2.1_hiera_tiny &amp;lt;br /&amp;gt; (&lt;a href=&quot;sam2/configs/sam2.1/sam2.1_hiera_t.yaml&quot;&gt;config&lt;/a&gt;, &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_tiny.pt&quot;&gt;checkpoint&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;38.9&lt;/td&gt;
&lt;td&gt;91.2&lt;/td&gt;
&lt;td&gt;76.5&lt;/td&gt;
&lt;td&gt;71.8&lt;/td&gt;
&lt;td&gt;77.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sam2.1_hiera_small &amp;lt;br /&amp;gt; (&lt;a href=&quot;sam2/configs/sam2.1/sam2.1_hiera_s.yaml&quot;&gt;config&lt;/a&gt;, &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_small.pt&quot;&gt;checkpoint&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;84.8&lt;/td&gt;
&lt;td&gt;76.6&lt;/td&gt;
&lt;td&gt;73.5&lt;/td&gt;
&lt;td&gt;78.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sam2.1_hiera_base_plus &amp;lt;br /&amp;gt; (&lt;a href=&quot;sam2/configs/sam2.1/sam2.1_hiera_b+.yaml&quot;&gt;config&lt;/a&gt;, &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_base_plus.pt&quot;&gt;checkpoint&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;80.8&lt;/td&gt;
&lt;td&gt;64.1&lt;/td&gt;
&lt;td&gt;78.2&lt;/td&gt;
&lt;td&gt;73.7&lt;/td&gt;
&lt;td&gt;78.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sam2.1_hiera_large &amp;lt;br /&amp;gt; (&lt;a href=&quot;sam2/configs/sam2.1/sam2.1_hiera_l.yaml&quot;&gt;config&lt;/a&gt;, &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_large.pt&quot;&gt;checkpoint&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;224.4&lt;/td&gt;
&lt;td&gt;39.5&lt;/td&gt;
&lt;td&gt;79.5&lt;/td&gt;
&lt;td&gt;74.6&lt;/td&gt;
&lt;td&gt;80.6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Model&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Size (M)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Speed (FPS)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;SA-V test (J&amp;amp;F)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;MOSE val (J&amp;amp;F)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;LVOS v2 (J&amp;amp;F)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sam2_hiera_tiny &amp;lt;br /&amp;gt; (&lt;a href=&quot;sam2/configs/sam2/sam2_hiera_t.yaml&quot;&gt;config&lt;/a&gt;, &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_tiny.pt&quot;&gt;checkpoint&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;38.9&lt;/td&gt;
&lt;td&gt;91.5&lt;/td&gt;
&lt;td&gt;75.0&lt;/td&gt;
&lt;td&gt;70.9&lt;/td&gt;
&lt;td&gt;75.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sam2_hiera_small &amp;lt;br /&amp;gt; (&lt;a href=&quot;sam2/configs/sam2/sam2_hiera_s.yaml&quot;&gt;config&lt;/a&gt;, &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_small.pt&quot;&gt;checkpoint&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;85.6&lt;/td&gt;
&lt;td&gt;74.9&lt;/td&gt;
&lt;td&gt;71.5&lt;/td&gt;
&lt;td&gt;76.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sam2_hiera_base_plus &amp;lt;br /&amp;gt; (&lt;a href=&quot;sam2/configs/sam2/sam2_hiera_b+.yaml&quot;&gt;config&lt;/a&gt;, &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_base_plus.pt&quot;&gt;checkpoint&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;80.8&lt;/td&gt;
&lt;td&gt;64.8&lt;/td&gt;
&lt;td&gt;74.7&lt;/td&gt;
&lt;td&gt;72.8&lt;/td&gt;
&lt;td&gt;75.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sam2_hiera_large &amp;lt;br /&amp;gt; (&lt;a href=&quot;sam2/configs/sam2/sam2_hiera_l.yaml&quot;&gt;config&lt;/a&gt;, &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_large.pt&quot;&gt;checkpoint&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;224.4&lt;/td&gt;
&lt;td&gt;39.7&lt;/td&gt;
&lt;td&gt;76.0&lt;/td&gt;
&lt;td&gt;74.6&lt;/td&gt;
&lt;td&gt;79.8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;官方使用接口&lt;/h2&gt;
&lt;h3&gt;模型加载&lt;/h3&gt;
&lt;p&gt;官方提供了 &lt;code&gt;build_sam2&lt;/code&gt; 函数来加载预训练模型。和 SAM 不同，这次不是用字典做映射，而是需要提供模型的配置文件路径和权重文件路径来加载模型:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from sam2.build_sam import build_sam2

ckpt_path = &quot;your/path/to/sam2.1_hiera_tiny.pt&quot;
model_cfg_path = &quot;your/path/to/sam2.1_hiera_t.yaml&quot;

sam2_model = build_sam2(model_cfg_path, ckpt_path)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;SAM2ImagePredictor&lt;/code&gt; 类&lt;/h3&gt;
&lt;h4&gt;从模型权重加载&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;SAM2ImagePredictor&lt;/code&gt; 类是 SAM2 中用于进行图像分割预测的核心类。它提供了一个简单的接口来处理输入图像和生成分割掩码。类似 SAM 中的 &lt;code&gt;SamPredictor&lt;/code&gt;，SAM2 的 &lt;code&gt;SAM2ImagePredictor&lt;/code&gt; 也接受多种类型的输入提示（点、框、掩码等），并返回分割结果:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
from sam2.sam2_image_predictor import SAM2ImagePredictor

predictor = SAM2ImagePredictor(sam2_model)

with torch.inference_mode(), torch.autocast(&quot;cuda&quot;, dtype=torch.bfloat16):
    predictor.set_image(img)    # img 是一个 np.ndarray 对象
    masks, iou_predictions, low_res_masks = predictor.predict(mask_input=None, point_coords=np.array([[1280, 720]]), point_labels=np.array([1]), multimask_output=False)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同样地：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;masks&lt;/code&gt;: 生成的分割掩码，形状为 &lt;code&gt;(num_masks, height, width)&lt;/code&gt;，每个掩码是一个二值图像，表示对应区域的分割结果。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iou_predictions&lt;/code&gt;: 每个掩码的 IoU 预测值，形状为 &lt;code&gt;(num_masks,)&lt;/code&gt;，表示每个掩码与真实分割的重叠程度。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;low_res_masks&lt;/code&gt;: 低分辨率的掩码，形状为 &lt;code&gt;(num_masks, low_res_height, low_res_width)&lt;/code&gt;，每个掩码是一个较小尺寸的掩码 logits，可以作为下一次预测的 &lt;code&gt;mask_input&lt;/code&gt; 用于迭代细化。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mask_input&lt;/code&gt;: 这是一个可选的输入参数，可以是一个低分辨率的掩码，形状为 &lt;code&gt;(low_res_height, low_res_width)&lt;/code&gt;，用于提供先前预测的掩码信息，以帮助模型进行迭代细化。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;point_coords&lt;/code&gt;: 这是一个可选的输入参数，形状为 &lt;code&gt;(num_points, 2)&lt;/code&gt;，表示用户提供的点坐标，每个点由 (x, y) 坐标组成。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;point_labels&lt;/code&gt;: 这是一个可选的输入参数，形状为 &lt;code&gt;(num_points,)&lt;/code&gt;，表示每个点的标签，通常为 1（正样本）或 0（负样本），用于指导模型进行分割。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;box&lt;/code&gt;: 这是一个可选的输入参数，形状为 &lt;code&gt;(4,)&lt;/code&gt;，表示用户提供的边界框坐标，格式为 (x_min, y_min, x_max, y_max)，用于指导模型进行分割。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;multimask_output&lt;/code&gt;: 这是一个布尔参数，表示是否返回多个掩码结果。如果设置为 &lt;code&gt;True&lt;/code&gt;，模型将返回多个分割掩码，以提供更多的分割选项；如果设置为 &lt;code&gt;False&lt;/code&gt;，模型将返回一个最佳的分割掩码。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上述代码中用到了 &lt;code&gt;torch.inference_mode()&lt;/code&gt; 和 &lt;code&gt;torch.autocast(&quot;cuda&quot;, dtype=torch.bfloat16)&lt;/code&gt; 来优化推理性能，特别是在使用 GPU 时，可以显著提高推理速度和减少内存占用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;torch.inference_mode()&lt;/code&gt;: 这是 PyTorch 中的一种上下文管理器，用于在推理阶段禁用梯度计算和其他与训练相关的功能，从而提高推理效率。相较于 &lt;code&gt;torch.no_grad()&lt;/code&gt;，&lt;code&gt;torch.inference_mode()&lt;/code&gt; 完全禁用视图追踪，且完全关闭整个 autograd 系统，因此它的内存开销更低，是对推理性能的极致优化。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bfloat16&lt;/code&gt; (BF16) 和 &lt;code&gt;float16&lt;/code&gt; (FP16) 都是半精度浮点数格式，但它们在表示范围和精度方面有所不同。BF16 具有与 FP32 相同的指数位数（8 位），但只有 7 位的尾数，这使得它能够表示更大的数值范围，同时在某些情况下提供更好的数值稳定性。FP16 则具有 5 位的指数和 10 位的尾数，适用于需要更高精度的场景，但可能会遇到数值溢出或下溢的问题。BF16 相较于 FP16 在深度学习中的应用更广泛，因为数值范围比数值精度对模型推理结果的影响更大。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;从 huggingface 上加载&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;SAM2ImagePredictor&lt;/code&gt; 类还提供了一个 &lt;code&gt;from_pretrained&lt;/code&gt; 的类方法，可以直接从 Hugging Face 上加载预训练的 SAM2 模型权重，简化了模型加载的过程:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
from sam2.sam2_image_predictor import SAM2ImagePredictor

predictor = SAM2ImagePredictor.from_pretrained(&quot;facebook/sam2-hiera-large&quot;)

with torch.inference_mode(), torch.autocast(&quot;cuda&quot;, dtype=torch.bfloat16):
    predictor.set_image(img)
    masks, iou_predictions, low_res_masks = predictor.predict(mask_input=None, point_coords=np.array([[1280, 720]]), point_labels=np.array([1]), multimask_output=False)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;build_sam2_video_predictor&lt;/code&gt; 函数&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;build_sam2_video_predictor&lt;/code&gt; 函数是 SAM2 中用于构建视频分割预测器的函数。它接受一个预训练的 SAM2 模型作为输入，并返回一个 &lt;code&gt;SAM2VideoPredictor&lt;/code&gt; 对象，该对象可以用于处理视频数据并生成分割结果:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
import numpy as np
import cv2
from sam2.build_sam import build_sam2_video_predictor

# 1. 模型权重和配置路径
ckpt_path = &quot;your/path/to/sam2.1_hiera_tiny.pt&quot;
model_cfg_path = &quot;your/path/to/sam2.1_hiera_t.yaml&quot;

# 2. 构建视频预测器
predictor = build_sam2_video_predictor(model_cfg_path, ckpt_path)

# 3. 初始化推理状态（直接传入 MP4 视频路径）
video_path = r&quot;your/video/path.mp4&quot;
with torch.inference_mode(), torch.autocast(&quot;cuda&quot;, dtype=torch.bfloat16):
    inference_state = predictor.init_state(
        video_path=video_path,
        offload_video_to_cpu=False,  # False: 帧加载到 GPU, True: 帧加载到 CPU 节省显存
    )

# 4. 添加 prompt（例如：在第 0 帧添加一个正样本点）
ann_frame_idx = 0  # 交互的帧索引
ann_obj_id = 1     # 物体 ID（任意唯一整数）

# 添加一个正样本点击 (x, y) = (210, 350)，label=1 表示正样本
points = np.array([[210, 350]], dtype=np.float32)
labels = np.array([1], np.int32)

_, out_obj_ids, out_mask_logits = predictor.add_new_points_or_box(
    inference_state=inference_state,
    frame_idx=ann_frame_idx,
    obj_id=ann_obj_id,
    points=points,
    labels=labels,
)

# 5. 传播到整个视频
video_segments = {}
for out_frame_idx, out_obj_ids, out_mask_logits in predictor.propagate_in_video(inference_state):
    video_segments[out_frame_idx] = {
        out_obj_id: (out_mask_logits[i] &amp;gt; 0.0).cpu().numpy()
        for i, out_obj_id in enumerate(out_obj_ids)
    }

print(f&quot;处理完成！共处理 {len(video_segments)} 帧&quot;)

# 6. 可视化并保存视频
output_video_path = &quot;your/output/video/path.mp4&quot;
cap = cv2.VideoCapture(video_path)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)

fourcc = cv2.VideoWriter_fourcc(*&apos;mp4v&apos;)
out = cv2.VideoWriter(output_video_path, fourcc, fps, (width, height))

frame_idx = 0
while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
    
    if frame_idx in video_segments:
        # 获取当前帧的所有掩码
        for obj_id, mask in video_segments[frame_idx].items():
            # mask 的形状通常是 (1, H, W) 或 (H, W)
            mask = mask.squeeze()
            # 创建一个彩色叠加层 (例如 蓝色)
            color_mask = np.zeros_like(frame, dtype=np.uint8)
            color_mask[mask] = [255, 0, 0] # BGR: Blue
            # 叠加到原图
            frame = cv2.addWeighted(frame, 1.0, color_mask, 0.5, 0)
            
            # 可选：绘制掩码轮廓
            contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            cv2.drawContours(frame, contours, -1, (255, 255, 255), 2)

    out.write(frame)
    frame_idx += 1

cap.release()
out.release()
print(f&quot;可视化视频已保存至: {output_video_path}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从视频的结果可以看出，SAM2 的分割是追踪 prompts 所提示的物体的，因此在视频中同一物体的分割结果是连续的，且能够适应物体的形变和运动。例如，如果下一帧中这个物体消失了，可能会导致后续所有帧中都没有分割内容了。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;SAM2VideoPredictor&lt;/code&gt; 类&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SAM2VideoPredictor&lt;/code&gt; 类就是前面 &lt;code&gt;build_sam2_video_predictor&lt;/code&gt; 函数返回的对象，它提供了一个接口来处理视频数据并生成分割结果。它的使用方式与前面介绍的 &lt;code&gt;SAM2ImagePredictor&lt;/code&gt; 类类似，但它专门针对视频数据进行了优化，能够处理视频中的连续帧，并且支持在视频中添加交互式提示（如点、框等）来指导分割过程。&lt;/p&gt;
&lt;p&gt;很遗憾地是，官方给出的接口并不能像 &lt;code&gt;SAM2ImagePredictor&lt;/code&gt; 那样直接自行从模型权重加载，而是需要通过 &lt;code&gt;build_sam2_video_predictor&lt;/code&gt; 函数来构建视频预测器对象。&lt;/p&gt;
&lt;p&gt;但是，官方提供了使用 hugginface 权重加载的方式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
from sam2.sam2_video_predictor import SAM2VideoPredictor

predictor = SAM2VideoPredictor.from_pretrained(&quot;facebook/sam2-hiera-large&quot;)

with torch.inference_mode(), torch.autocast(&quot;cuda&quot;, dtype=torch.bfloat16):
    state = predictor.init_state(&amp;lt;your_video&amp;gt;)

    # add new prompts and instantly get the output on the same frame
    frame_idx, object_ids, masks = predictor.add_new_points_or_box(
            state, 
            inference_state=inference_state,
            frame_idx=ann_frame_idx,
            obj_id=ann_obj_id,
            points=points,
            labels=labels
        )

    # propagate the prompts to get masklets throughout the video
    for frame_idx, object_ids, masks in predictor.propagate_in_video(state):
        video_segments[out_frame_idx] = {
            out_obj_id: (out_mask_logits[i] &amp;gt; 0.0).cpu().numpy()
            for i, out_obj_id in enumerate(out_obj_ids)
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;SAM2AutomaticMaskGenerator&lt;/code&gt; 类&lt;/h3&gt;
&lt;p&gt;类似 SAM 的 &lt;code&gt;SAMAutomaticMaskGenerator&lt;/code&gt;，&lt;code&gt;SAM2AutomaticMaskGenerator&lt;/code&gt; 是 SAM2 提供的全自动实例分割工具，无需任何人工提示，即可在单张图像上自动检测并分割出所有可能的物体对象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from sam2.build_sam import build_sam2
from sam2.automatic_mask_generator import SAM2AutomaticMaskGenerator

# 加载模型
sam2_model = build_sam2(&quot;configs/sam2.1/sam2.1_hiera_l.yaml&quot;, &quot;checkpoints/sam2.1_hiera_large.pt&quot;)

# 初始化自动掩码生成器
mask_generator = SAM2AutomaticMaskGenerator(sam2_model)

# 生成所有掩码
masks = mask_generator.generate(image)

# masks 是一个列表，每个元素包含 segmentation, bbox, area, predicted_iou 等字段
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;SAM2 接口文档&lt;/h2&gt;
&lt;p&gt;本文档基于 SAM2 模型部分源码，逐项记录 11 个核心类：&lt;code&gt;SAM2Base&lt;/code&gt;、&lt;code&gt;SAM2VideoPredictor&lt;/code&gt;、&lt;code&gt;SAM2ImagePredictor&lt;/code&gt;、&lt;code&gt;PositionEmbeddingSine&lt;/code&gt;、&lt;code&gt;MemoryEncoder&lt;/code&gt;、&lt;code&gt;MemoryAttentionLayer&lt;/code&gt;、&lt;code&gt;MemoryAttention&lt;/code&gt;、&lt;code&gt;ImageEncoder&lt;/code&gt;、&lt;code&gt;MaskDecoder&lt;/code&gt;、&lt;code&gt;PromptEncoder&lt;/code&gt;、&lt;code&gt;Hiera&lt;/code&gt;。
包含：关键属性、全部方法（含 &lt;code&gt;_&lt;/code&gt; 私有方法）、每个方法的职责与核心输入输出，以及详细的形状说明。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;SAM2Base&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件路径&lt;/strong&gt;: &lt;code&gt;sam2/modeling/sam2_base.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: SAM2 模型的基础类，负责图像编码、记忆融合、SAM 头预测和记忆编码的核心流程。它集成了图像主干网络、记忆注意力机制、记忆编码器以及 SAM 的提示编码器和掩码解码器，是视频目标跟踪的核心模型。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;图像主干网络，提取多尺度视觉特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory_attention&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;记忆注意力模块，融合当前帧特征与历史记忆&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;记忆编码器，将当前预测掩码编码为记忆特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_maskmem&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;可访问的记忆数量（1个输入帧 + 6个历史帧）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;输入图像尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backbone_stride&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;图像主干输出步长&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_high_res_features_in_sam&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在 SAM 解码器中使用高分辨率特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_feature_levels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1 或 3&lt;/td&gt;
&lt;td&gt;特征金字塔层数（高分辨率时为3）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_obj_ptrs_in_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在编码器中交叉注意力对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_obj_ptrs_in_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;编码器中最大对象指针数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maskmem_tpos_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(num_maskmem,1,1,mem_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;记忆的时间位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_mem_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(1,1,hidden_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无记忆嵌入（用于第一帧）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_mem_pos_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(1,1,hidden_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无记忆位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;directly_add_no_mem_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否直接将无记忆嵌入加到特征上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sam_prompt_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PromptEncoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;SAM 提示编码器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sam_mask_decoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MaskDecoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;SAM 掩码解码器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;obj_ptr_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Identity&lt;/code&gt; 或线性层&lt;/td&gt;
&lt;td&gt;对象指针投影层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;obj_ptr_tpos_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Identity&lt;/code&gt; 或线性层&lt;/td&gt;
&lt;td&gt;对象指针时间位置编码投影&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hidden_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;图像编码器 neck.d_model&lt;/td&gt;
&lt;td&gt;隐藏层维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mem_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hidden_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;记忆特征维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sigmoid_scale_for_mem_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;记忆编码前掩码 sigmoid 的缩放因子&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sigmoid_bias_for_mem_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;记忆编码前掩码 sigmoid 的偏置因子&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;binarize_mask_from_pts_for_mem_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否对点提示产生的掩码进行二值化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_mask_input_as_output_without_sam&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否直接使用输入掩码作为输出（跳过 SAM）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_cond_frames_in_attn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-1&lt;/td&gt;
&lt;td&gt;注意力中最大条件帧数（-1 表示无限制）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_output_in_sam&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在 SAM 中输出多掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_min_pt_num&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;使用多掩码的最小点数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_max_pt_num&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;使用多掩码的最大点数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multimask_output_for_tracking&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跟踪时是否使用多掩码输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_multimask_token_for_obj_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用多掩码 token 生成对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_prediction_use_sigmoid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用 sigmoid 限制 IoU 预测到 [0,1]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory_temporal_stride_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;评估时记忆库的时间步长&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;non_overlap_masks_for_mem_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;记忆编码时是否应用非重叠约束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_tpos_enc_to_obj_ptrs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否给对象指针添加时间位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;proj_tpos_enc_in_obj_ptrs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否投影对象指针的时间位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_signed_tpos_enc_to_obj_ptrs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用有符号时间位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;only_obj_ptrs_in_the_past_for_eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;评估时是否只关注过去的对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred_obj_scores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否预测对象出现分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred_obj_scores_mlp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用 MLP 预测对象分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fixed_no_obj_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用固定的无对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;soft_no_obj_ptr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用软无对象指针（混合）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_mlp_for_obj_ptr_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否使用 MLP 投影对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_obj_embed_spatial&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Parameter&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;空间无对象嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sam_mask_decoder_extra_args&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SAM 掩码解码器额外参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compile_image_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否编译图像编码器&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;参见上方属性列表（共 46 个参数）&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化整个模型，构建图像主干、记忆注意力、记忆编码器、SAM 头部等组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;device&lt;/code&gt; (property)&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;返回模型参数所在的设备&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*args, **kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NotImplementedError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;占位方法，提示使用 &lt;code&gt;SAM2VideoPredictor&lt;/code&gt; 进行推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_build_sam_heads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;构建 SAM 提示编码器和掩码解码器，初始化对象指针投影层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_forward_sam_heads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_features&lt;/code&gt;: &lt;code&gt;[B, C, H, W]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;point_inputs&lt;/code&gt;: dict (&lt;code&gt;point_coords&lt;/code&gt;: &lt;code&gt;[B, P, 2]&lt;/code&gt;, &lt;code&gt;point_labels&lt;/code&gt;: &lt;code&gt;[B, P]&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;mask_inputs&lt;/code&gt;: &lt;code&gt;[B, 1, H*16, W*16]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;high_res_features&lt;/code&gt;: list(&lt;code&gt;[B, C, 4*H, 4*W]&lt;/code&gt;, &lt;code&gt;[B, C, 2*H, 2*W]&lt;/code&gt;) 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;multimask_output&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;low_res_multimasks&lt;/code&gt;: &lt;code&gt;[B, M, H*4, W*4]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;high_res_multimasks&lt;/code&gt;: &lt;code&gt;[B, M, H*16, W*16]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;ious&lt;/code&gt;: &lt;code&gt;[B, M]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;low_res_masks&lt;/code&gt;: &lt;code&gt;[B, 1, H*4, W*4]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;high_res_masks&lt;/code&gt;: &lt;code&gt;[B, 1, H*16, W*16]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;obj_ptr&lt;/code&gt;: &lt;code&gt;[B, C]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;object_score_logits&lt;/code&gt;: &lt;code&gt;[B, 1]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播 SAM 提示编码器和掩码解码器，生成掩码预测、IoU 估计和对象指针&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_use_mask_as_output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_features&lt;/code&gt;: &lt;code&gt;[B, C, H, W]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;high_res_features&lt;/code&gt;: 同上或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;mask_inputs&lt;/code&gt;: &lt;code&gt;[B, 1, H*16, W*16]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同 &lt;code&gt;_forward_sam_heads&lt;/code&gt; 的返回值&lt;/td&gt;
&lt;td&gt;直接将二值掩码输入转换为输出掩码 logits（跳过 SAM 解码器）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;img_batch&lt;/code&gt;: &lt;code&gt;[B, 3, image_size, image_size]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt;: dict (&lt;code&gt;vision_features&lt;/code&gt;, &lt;code&gt;vision_pos_enc&lt;/code&gt;, &lt;code&gt;backbone_fpn&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;运行图像编码器，提取多尺度视觉特征和位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_prepare_backbone_features&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt; (copy), &lt;code&gt;vision_feats&lt;/code&gt;: list(&lt;code&gt;[HW, B, C]&lt;/code&gt;), &lt;code&gt;vision_pos_embeds&lt;/code&gt;: list(&lt;code&gt;[HW, B, C]&lt;/code&gt;), &lt;code&gt;feat_sizes&lt;/code&gt;: list(&lt;code&gt;(H, W)&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;整理多尺度特征和位置编码，展平为 Transformer 使用的格式（HW x B x C）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_prepare_memory_conditioned_features&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: &lt;code&gt;int&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;is_init_cond_frame&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;current_vision_feats&lt;/code&gt;: list(&lt;code&gt;[HW, B, C]&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;current_vision_pos_embeds&lt;/code&gt;: list(&lt;code&gt;[HW, B, C]&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;feat_sizes&lt;/code&gt;: list(&lt;code&gt;(H, W)&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;output_dict&lt;/code&gt;: dict&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;num_frames&lt;/code&gt;: &lt;code&gt;int&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;track_in_reverse&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pix_feat&lt;/code&gt;: &lt;code&gt;[B, C, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;核心记忆融合：拼接条件/非条件记忆 + 可选对象指针，通过 &lt;code&gt;memory_attention&lt;/code&gt; 得到当前帧融合特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_encode_new_memory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;current_vision_feats&lt;/code&gt;: list(&lt;code&gt;[HW, B, C]&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;feat_sizes&lt;/code&gt;: list(&lt;code&gt;(H, W)&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;pred_masks_high_res&lt;/code&gt;: &lt;code&gt;[B, 1, H*16, W*16]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;object_score_logits&lt;/code&gt;: &lt;code&gt;[B, 1]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;is_mask_from_pts&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;maskmem_features&lt;/code&gt;: &lt;code&gt;[B, C, H, W]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;maskmem_pos_enc&lt;/code&gt;: list(&lt;code&gt;[1, B, C, H, W]&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;将当前帧预测掩码与视觉特征编码为记忆特征和位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_track_step&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: &lt;code&gt;int&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;is_init_cond_frame&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;current_vision_feats&lt;/code&gt;: list(&lt;code&gt;[HW, B, C]&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;current_vision_pos_embeds&lt;/code&gt;: list(&lt;code&gt;[HW, B, C]&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;feat_sizes&lt;/code&gt;: list(&lt;code&gt;(H, W)&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;point_inputs&lt;/code&gt;: dict 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;mask_inputs&lt;/code&gt;: &lt;code&gt;[B, 1, H*16, W*16]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;output_dict&lt;/code&gt;: dict&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;num_frames&lt;/code&gt;: &lt;code&gt;int&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;track_in_reverse&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;prev_sam_mask_logits&lt;/code&gt;: &lt;code&gt;[B, 1, H*4, W*4]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;current_out&lt;/code&gt;: dict (&lt;code&gt;point_inputs&lt;/code&gt;, &lt;code&gt;mask_inputs&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;sam_outputs&lt;/code&gt;: 同 &lt;code&gt;_forward_sam_heads&lt;/code&gt; 返回值&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;high_res_features&lt;/code&gt;: list 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;pix_feat&lt;/code&gt;: &lt;code&gt;[B, C, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;单帧内部跟踪流程：准备高分辨率特征 → 记忆融合 → SAM 头预测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_encode_memory_in_output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;current_vision_feats&lt;/code&gt;: list(&lt;code&gt;[HW, B, C]&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;feat_sizes&lt;/code&gt;: list(&lt;code&gt;(H, W)&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;point_inputs&lt;/code&gt;: dict 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;run_mem_encoder&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;high_res_masks&lt;/code&gt;: &lt;code&gt;[B, 1, H*16, W*16]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;object_score_logits&lt;/code&gt;: &lt;code&gt;[B, 1]&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;current_out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无（修改 &lt;code&gt;current_out&lt;/code&gt;，添加 &lt;code&gt;maskmem_features&lt;/code&gt; 和 &lt;code&gt;maskmem_pos_enc&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;将记忆编码器的输出写入 &lt;code&gt;current_out&lt;/code&gt; 字典&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;track_step&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frame_idx&lt;/code&gt;: &lt;code&gt;int&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;is_init_cond_frame&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;current_vision_feats&lt;/code&gt;: list(&lt;code&gt;[HW, B, C]&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;current_vision_pos_embeds&lt;/code&gt;: list(&lt;code&gt;[HW, B, C]&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;feat_sizes&lt;/code&gt;: list(&lt;code&gt;(H, W)&lt;/code&gt;)&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;point_inputs&lt;/code&gt;: dict 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;mask_inputs&lt;/code&gt;: &lt;code&gt;[B, 1, H*16, W*16]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;output_dict&lt;/code&gt;: dict&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;num_frames&lt;/code&gt;: &lt;code&gt;int&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;track_in_reverse&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;run_mem_encoder&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;prev_sam_mask_logits&lt;/code&gt;: &lt;code&gt;[B, 1, H*4, W*4]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;current_out&lt;/code&gt;: dict (&lt;code&gt;pred_masks&lt;/code&gt;, &lt;code&gt;pred_masks_high_res&lt;/code&gt;, &lt;code&gt;obj_ptr&lt;/code&gt;, &lt;code&gt;object_score_logits&lt;/code&gt;, &lt;code&gt;maskmem_features&lt;/code&gt;, &lt;code&gt;maskmem_pos_enc&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;单帧公开跟踪入口，组合 &lt;code&gt;_track_step&lt;/code&gt; + &lt;code&gt;_encode_memory_in_output&lt;/code&gt;，写入预测掩码、对象指针等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_use_multimask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;is_init_cond_frame&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;point_inputs&lt;/code&gt;: dict 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;multimask_output&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;根据交互点数和配置判断是否启用多掩码输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_apply_non_overlapping_constraints&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pred_masks&lt;/code&gt;: &lt;code&gt;[B, 1, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pred_masks&lt;/code&gt;: &lt;code&gt;[B, 1, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对预测掩码应用非重叠约束：同像素只保留最高分对象，抑制对象间重叠&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;code&gt;SAM2VideoPredictor&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件路径&lt;/strong&gt;: &lt;code&gt;sam2/sam2_video_predictor.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 继承自 &lt;code&gt;SAM2Base&lt;/code&gt;，负责视频推理的状态管理、用户交互处理和视频传播。它管理推理状态（图像缓存、对象映射、每对象输出字典），提供添加点/框/掩码提示、传播跟踪、清除提示等功能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fill_hole_area&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;填充掩码中孔洞的面积阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;non_overlap_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否对输出对象掩码应用非重叠约束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clear_non_cond_mem_around_input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否清除输入帧周围的非条件记忆（避免旧信息干扰）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_all_frames_to_correct_as_cond&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否将所有接收校正点击的帧添加到条件帧列表&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fill_hole_area=0&lt;/code&gt;, &lt;code&gt;non_overlap_masks=False&lt;/code&gt;, &lt;code&gt;clear_non_cond_mem_around_input=False&lt;/code&gt;, &lt;code&gt;add_all_frames_to_correct_as_cond=False&lt;/code&gt;, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化视频预测器，设置填充孔洞、非重叠掩码、清除记忆等选项&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;init_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;video_path&lt;/code&gt;: str&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;offload_video_to_cpu=False&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;offload_state_to_cpu=False&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;async_loading_frames=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;初始化推理状态：加载视频帧、创建对象映射、缓存特征等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;from_pretrained&lt;/code&gt; (classmethod)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;model_id&lt;/code&gt;: str, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SAM2VideoPredictor&lt;/code&gt; 实例&lt;/td&gt;
&lt;td&gt;从预训练模型加载配置和权重创建预测器实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_obj_id_to_idx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;obj_id&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;obj_idx&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;将外部对象 ID 转换为内部索引，必要时创建新对象槽位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_obj_idx_to_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;obj_idx&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;obj_id&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;将内部索引转换为外部对象 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_obj_num&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;返回当前对象数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_new_points_or_box&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;obj_id&lt;/code&gt;: int, &lt;code&gt;point_coords&lt;/code&gt;: &lt;code&gt;[P, 2]&lt;/code&gt; 或 &lt;code&gt;[[x1,y1,x2,y2]]&lt;/code&gt;, &lt;code&gt;point_labels&lt;/code&gt;: &lt;code&gt;[P]&lt;/code&gt;, &lt;code&gt;is_box=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;video_res_masks&lt;/code&gt;: &lt;code&gt;[1, H_vid, W_vid]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在指定帧添加点或框提示，运行单帧推理，写入临时输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_new_points&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*args, **kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同 &lt;code&gt;add_new_points_or_box&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;兼容旧接口，调用 &lt;code&gt;add_new_points_or_box&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_new_mask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;obj_id&lt;/code&gt;: int, &lt;code&gt;mask&lt;/code&gt;: &lt;code&gt;[H_vid, W_vid]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;video_res_masks&lt;/code&gt;: &lt;code&gt;[1, H_vid, W_vid]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在指定帧添加二值掩码作为提示，更新临时输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_orig_video_res_output&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;any_res_masks&lt;/code&gt;: &lt;code&gt;[B, 1, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;video_res_masks&lt;/code&gt;: &lt;code&gt;[B, 1, H_vid, W_vid]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将掩码上采样回原始视频尺寸，并可施加非重叠约束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_consolidate_temp_output_across_obj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;is_cond&lt;/code&gt;: bool, &lt;code&gt;consolidate_at_video_res=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;consolidated_masks&lt;/code&gt;: &lt;code&gt;[1, H, W]&lt;/code&gt; 或 &lt;code&gt;[1, H_vid, W_vid]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;合并多对象临时输出到统一张量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;propagate_in_video_preflight&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无（修改 &lt;code&gt;inference_state&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;传播前整理：合并临时输出、必要时补跑记忆编码器、一致性校验&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;propagate_in_video&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;start_frame_idx=None&lt;/code&gt;, &lt;code&gt;max_frame_num_to_track=None&lt;/code&gt;, &lt;code&gt;reverse=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成器返回 &lt;code&gt;(frame_idx, obj_ids, video_res_masks)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;视频时序传播主循环，逐帧产生跟踪结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clear_all_prompts_in_frame&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;obj_id&lt;/code&gt;: int, &lt;code&gt;need_output=True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;video_res_masks&lt;/code&gt;: &lt;code&gt;[1, H_vid, W_vid]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;清理某对象某帧的所有提示，并返回更新后的掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset_state&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;全量重置推理状态和对象映射&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_reset_tracking_results&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;仅清空跟踪内容，不清理对象映射&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_image_feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;batch_size&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;&lt;code&gt;backbone_out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;取缓存或计算当前帧特征，并扩展到对象 batch 维&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_run_single_frame_inference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;obj_id&lt;/code&gt;: int, &lt;code&gt;point_inputs=None&lt;/code&gt;, &lt;code&gt;mask_inputs=None&lt;/code&gt;, &lt;code&gt;prev_sam_mask_logits=None&lt;/code&gt;, &lt;code&gt;run_mem_encoder=True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;current_out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;单帧打包推理，返回紧凑输出结构 + GPU 掩码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_run_memory_encoder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int, &lt;code&gt;obj_id&lt;/code&gt;: int, &lt;code&gt;high_res_masks&lt;/code&gt;: &lt;code&gt;[1, H*16, W*16]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无（更新 &lt;code&gt;inference_state&lt;/code&gt; 中的记忆特征）&lt;/td&gt;
&lt;td&gt;在给定高分辨率掩码下重新计算记忆特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_maskmem_pos_enc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;current_out&lt;/code&gt;: dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;maskmem_pos_enc&lt;/code&gt;: list(&lt;code&gt;[1, B, C, H, W]&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;缓存并按 batch 扩展记忆位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remove_object&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;obj_id&lt;/code&gt;: int, &lt;code&gt;strict=False&lt;/code&gt;, &lt;code&gt;need_output=True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;video_res_masks&lt;/code&gt;: &lt;code&gt;[1, H_vid, W_vid]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除对象并重映射所有 per-object 容器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_clear_non_cond_mem_around_input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inference_state&lt;/code&gt;: dict, &lt;code&gt;frame_idx&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;清除输入帧附近的非条件记忆，避免旧信息干扰&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;code&gt;SAM2ImagePredictor&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件路径&lt;/strong&gt;: &lt;code&gt;sam2/sam2_image_predictor.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 用于单张图像的交互式分割预测器。它缓存图像嵌入，支持点、框、掩码提示，并返回分割掩码和 IoU 分数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SAM2Base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;底层 SAM2 模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_transforms&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ResizeLongestSide&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;图像预处理变换&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_is_image_set&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否已设置图像&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_features&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;缓存的图像嵌入和高分辨率特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_orig_hw&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tuple&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;原始图像高度和宽度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_is_batch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否处于批处理模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_threshold&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;掩码二值化阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_bb_feat_sizes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;主干特征尺寸列表&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;: &lt;code&gt;SAM2Base&lt;/code&gt;, &lt;code&gt;mask_threshold=0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化预测器，设置模型和掩码阈值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;from_pretrained&lt;/code&gt; (classmethod)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;model_id&lt;/code&gt;: str, &lt;code&gt;**kwargs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SAM2ImagePredictor&lt;/code&gt; 实例&lt;/td&gt;
&lt;td&gt;从预训练模型加载配置和权重创建预测器实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;set_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;: &lt;code&gt;[H, W, 3]&lt;/code&gt; numpy array&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;单图编码并缓存嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;set_image_batch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image_list&lt;/code&gt;: list of &lt;code&gt;[H, W, 3]&lt;/code&gt; numpy arrays&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;多图编码并进入批处理模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;predict_batch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;point_coords=None&lt;/code&gt;, &lt;code&gt;point_labels=None&lt;/code&gt;, &lt;code&gt;box=None&lt;/code&gt;, &lt;code&gt;mask_logits=None&lt;/code&gt;, &lt;code&gt;normalize_coords=True&lt;/code&gt;, &lt;code&gt;img_idx=-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;all_masks&lt;/code&gt;: list of &lt;code&gt;[B, 1, H, W]&lt;/code&gt;, &lt;code&gt;all_ious&lt;/code&gt;: list of &lt;code&gt;[B, 1]&lt;/code&gt;, &lt;code&gt;all_low_res_masks&lt;/code&gt;: list of &lt;code&gt;[B, 1, H//4, W//4]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对已缓存的批处理图像进行提示推理，返回所有图像的掩码和 IoU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;predict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;point_coords=None&lt;/code&gt;, &lt;code&gt;point_labels=None&lt;/code&gt;, &lt;code&gt;box=None&lt;/code&gt;, &lt;code&gt;mask_logits=None&lt;/code&gt;, &lt;code&gt;multimask_output=False&lt;/code&gt;, &lt;code&gt;return_logits=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;masks&lt;/code&gt;: &lt;code&gt;[1, H, W]&lt;/code&gt;, &lt;code&gt;ious&lt;/code&gt;: &lt;code&gt;[1]&lt;/code&gt;, &lt;code&gt;low_res_logits&lt;/code&gt;: &lt;code&gt;[1, 256, 256]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;单图提示推理（numpy 输入/输出封装）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_prep_prompts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;point_coords&lt;/code&gt;, &lt;code&gt;point_labels&lt;/code&gt;, &lt;code&gt;box&lt;/code&gt;, &lt;code&gt;mask_logits&lt;/code&gt;, &lt;code&gt;normalize_coords&lt;/code&gt;, &lt;code&gt;img_idx=-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;point_coords&lt;/code&gt;: &lt;code&gt;[B, P, 2]&lt;/code&gt;, &lt;code&gt;point_labels&lt;/code&gt;: &lt;code&gt;[B, P]&lt;/code&gt;, &lt;code&gt;box&lt;/code&gt;: &lt;code&gt;[B, 4]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;mask_logits&lt;/code&gt;: &lt;code&gt;[B, 1, 256, 256]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;提示标准化与张量化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_predict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;point_coords&lt;/code&gt;, &lt;code&gt;point_labels&lt;/code&gt;, &lt;code&gt;box&lt;/code&gt;, &lt;code&gt;mask_logits&lt;/code&gt;, &lt;code&gt;multimask_output&lt;/code&gt;, &lt;code&gt;return_logits&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;masks&lt;/code&gt;: &lt;code&gt;[B, 1, H, W]&lt;/code&gt;, &lt;code&gt;ious&lt;/code&gt;: &lt;code&gt;[B, 1]&lt;/code&gt;, &lt;code&gt;low_res_logits&lt;/code&gt;: &lt;code&gt;[B, 1, 256, 256]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;核心 torch 推理路径：提示编码 + 掩码解码 + 后处理上采样&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_image_embedding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image_embed&lt;/code&gt;: &lt;code&gt;[1, C, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;返回缓存的图像嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;device&lt;/code&gt; (property)&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;torch.device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;返回模型设备&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reset_predictor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;重置预测器状态，清空缓存&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;code&gt;PositionEmbeddingSine&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件路径&lt;/strong&gt;: &lt;code&gt;sam2/modeling/position_encoding.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 生成正弦位置编码，用于图像特征的位置嵌入。支持对点、框的编码，以及缓存的 2D 位置编码图。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_pos_feats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;位置特征维度（每个方向 x/y 各一半）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;temperature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10000.0&lt;/td&gt;
&lt;td&gt;正弦函数的温度参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;normalize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否归一化坐标到 [0,1]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scale&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2 * pi&lt;/td&gt;
&lt;td&gt;缩放因子&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;dict&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;缓存的位置编码图&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;num_pos_feats=64&lt;/code&gt;, &lt;code&gt;temperature=10000.0&lt;/code&gt;, &lt;code&gt;normalize=False&lt;/code&gt;, &lt;code&gt;scale=2*pi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化正弦位置编码器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_encode_xy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;: &lt;code&gt;[B, N]&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;: &lt;code&gt;[B, N]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pe&lt;/code&gt;: &lt;code&gt;[B, N, num_pos_feats*2]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对归一化坐标进行 sin/cos 编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;encode_boxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;: &lt;code&gt;[B, N]&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;: &lt;code&gt;[B, N]&lt;/code&gt;, &lt;code&gt;w&lt;/code&gt;: &lt;code&gt;[B, N]&lt;/code&gt;, &lt;code&gt;h&lt;/code&gt;: &lt;code&gt;[B, N]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pe&lt;/code&gt;: &lt;code&gt;[B, N, num_pos_feats*2]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;框中心/尺寸编码（中心坐标 + 宽高）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;encode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同 &lt;code&gt;encode_boxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同 &lt;code&gt;encode_boxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;encode_boxes&lt;/code&gt; 的别名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;encode_points&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;: &lt;code&gt;[B, N]&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;: &lt;code&gt;[B, N]&lt;/code&gt;, &lt;code&gt;labels&lt;/code&gt;: &lt;code&gt;[B, N]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pe&lt;/code&gt;: &lt;code&gt;[B, N, num_pos_feats*2]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;点坐标编码，根据标签（正/负）调整相位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_pe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;B&lt;/code&gt;: int, &lt;code&gt;device&lt;/code&gt;: torch.device, &lt;code&gt;*cache_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pe&lt;/code&gt;: &lt;code&gt;[B, num_pos_feats*2, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;生成或命中缓存的 2D 位置编码图&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;: &lt;code&gt;[B, C, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pe&lt;/code&gt;: &lt;code&gt;[B, num_pos_feats*2, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;为输入特征图生成位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;code&gt;MemoryEncoder&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件路径&lt;/strong&gt;: &lt;code&gt;sam2/modeling/memory_encoder.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 将视觉特征和预测掩码融合编码为记忆特征。包含掩码下采样器、特征投影、融合模块和位置编码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_downsampler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MaskDownSampler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;掩码下采样器（卷积层）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pix_feat_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Conv2d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;视觉特征投影层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fuser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Fuser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;融合模块（多个 CXBlock）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;position_encoding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PositionEmbeddingSine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;位置编码器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;out_proj&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Conv2d&lt;/code&gt; 或 &lt;code&gt;nn.Identity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;输出投影层（可选压缩通道）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mask_downsampler&lt;/code&gt;, &lt;code&gt;pix_feat_proj&lt;/code&gt;, &lt;code&gt;fuser&lt;/code&gt;, &lt;code&gt;position_encoding&lt;/code&gt;, &lt;code&gt;out_proj=None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化记忆编码器组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pix_feat&lt;/code&gt;: &lt;code&gt;[B, C, H, W]&lt;/code&gt;, &lt;code&gt;masks&lt;/code&gt;: &lt;code&gt;[B, 1, H*16, W*16]&lt;/code&gt;, &lt;code&gt;skip_mask_sigmoid=False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;: &lt;code&gt;{&quot;vision_features&quot;: x, &quot;vision_pos_enc&quot;: [pos]}&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;x&lt;/code&gt;: &lt;code&gt;[B, C&apos;, H, W]&lt;/code&gt;, &lt;code&gt;pos&lt;/code&gt;: &lt;code&gt;[1, B, C&apos;, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码下采样 + 与视觉特征融合 + fuser + 位置编码，输出记忆特征和位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;code&gt;MemoryAttentionLayer&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件路径&lt;/strong&gt;: &lt;code&gt;sam2/modeling/memory_attention.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 记忆注意力层，包含自注意力和交叉注意力（图像到记忆），以及前馈网络。用于在 &lt;code&gt;MemoryAttention&lt;/code&gt; 中堆叠。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;self_attn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;自注意力层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cross_attn_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;交叉注意力层（图像到记忆）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;linear1&lt;/code&gt;, &lt;code&gt;linear2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Linear&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;前馈网络线性层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;norm1&lt;/code&gt;, &lt;code&gt;norm2&lt;/code&gt;, &lt;code&gt;norm3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.LayerNorm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;层归一化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dropout&lt;/code&gt;, &lt;code&gt;dropout1&lt;/code&gt;, &lt;code&gt;dropout2&lt;/code&gt;, &lt;code&gt;dropout3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Dropout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;Dropout 层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;activation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ReLU&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;激活函数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pos_enc_at_input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在输入时添加位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;batch_first&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否 batch 维度在前&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d_model&lt;/code&gt;, &lt;code&gt;nhead&lt;/code&gt;, &lt;code&gt;dim_feedforward=2048&lt;/code&gt;, &lt;code&gt;dropout=0.1&lt;/code&gt;, &lt;code&gt;activation=nn.ReLU&lt;/code&gt;, &lt;code&gt;layer_norm_eps=1e-5&lt;/code&gt;, &lt;code&gt;batch_first=False&lt;/code&gt;, &lt;code&gt;pos_enc_at_input=False&lt;/code&gt;, &lt;code&gt;num_k_exclude_rope=0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化注意力层组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_forward_sa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tgt&lt;/code&gt;: &lt;code&gt;[seq_len, B, C]&lt;/code&gt;, &lt;code&gt;query_pos&lt;/code&gt;: &lt;code&gt;[seq_len, B, C]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tgt2&lt;/code&gt;: &lt;code&gt;[seq_len, B, C]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自注意力前向传播&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_forward_ca&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tgt&lt;/code&gt;: &lt;code&gt;[seq_len, B, C]&lt;/code&gt;, &lt;code&gt;memory&lt;/code&gt;: &lt;code&gt;[mem_len, B, C]&lt;/code&gt;, &lt;code&gt;query_pos&lt;/code&gt;: &lt;code&gt;[seq_len, B, C]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;pos&lt;/code&gt;: &lt;code&gt;[mem_len, B, C]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;, &lt;code&gt;num_k_exclude_rope=0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tgt2&lt;/code&gt;: &lt;code&gt;[seq_len, B, C]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;交叉注意力前向传播（图像到记忆）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;curr&lt;/code&gt;: &lt;code&gt;[seq_len, B, C]&lt;/code&gt;, &lt;code&gt;curr_pos&lt;/code&gt;: &lt;code&gt;[seq_len, B, C]&lt;/code&gt;, &lt;code&gt;memory&lt;/code&gt;: &lt;code&gt;[mem_len, B, C]&lt;/code&gt;, &lt;code&gt;memory_pos&lt;/code&gt;: &lt;code&gt;[mem_len, B, C]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;curr&lt;/code&gt;: &lt;code&gt;[seq_len, B, C]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;完整的层前向：自注意力 → 交叉注意力 → FFN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;code&gt;MemoryAttention&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件路径&lt;/strong&gt;: &lt;code&gt;sam2/modeling/memory_attention.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 记忆注意力模块，堆叠多个 &lt;code&gt;MemoryAttentionLayer&lt;/code&gt;，实现自注意力与交叉注意力的多层融合。支持位置编码在输入时添加、batch_first/seq_first 格式转换，以及 RoPE 注意力中的对象指针 token 排除。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;模型维度（特征通道数）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;layers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.ModuleList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;堆叠的 &lt;code&gt;MemoryAttentionLayer&lt;/code&gt; 实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_layers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;层数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;norm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.LayerNorm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;层归一化，应用于最后一层输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pos_enc_at_input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否在输入时添加位置编码（乘以 0.1）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;batch_first&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入/输出是否为 batch 维度在前（否则为 seq 维度在前）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d_model&lt;/code&gt;: int, &lt;code&gt;pos_enc_at_input&lt;/code&gt;: bool, &lt;code&gt;layer&lt;/code&gt;: nn.Module, &lt;code&gt;num_layers&lt;/code&gt;: int, &lt;code&gt;batch_first&lt;/code&gt;: bool = True&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化记忆注意力模块，克隆指定层并设置归一化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;curr&lt;/code&gt;: &lt;code&gt;[seq_len, B, C]&lt;/code&gt; 或 &lt;code&gt;[B, seq_len, C]&lt;/code&gt;（根据 &lt;code&gt;batch_first&lt;/code&gt;）&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;memory&lt;/code&gt;: 同 &lt;code&gt;curr&lt;/code&gt; 形状&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;curr_pos&lt;/code&gt;: 同 &lt;code&gt;curr&lt;/code&gt; 形状或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;memory_pos&lt;/code&gt;: 同 &lt;code&gt;memory&lt;/code&gt; 形状或 &lt;code&gt;None&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;&lt;code&gt;num_obj_ptr_tokens&lt;/code&gt;: int = 0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;normed_output&lt;/code&gt;: 同 &lt;code&gt;curr&lt;/code&gt; 形状&lt;/td&gt;
&lt;td&gt;前向传播：可选输入位置编码 → 格式统一为 seq_first → 逐层自注意力+交叉注意力 → 层归一化 → 格式还原&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;code&gt;ImageEncoder&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件路径&lt;/strong&gt;: &lt;code&gt;sam2/modeling/backbones/image_encoder.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 图像编码器，包含主干网络（如 Hiera）、FPN 颈部（多尺度特征融合）和可选的 scalp（最高分辨率特征）。输出多尺度视觉特征和位置编码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;trunk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Module&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;主干网络（如 Hiera）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;neck&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FpnNeck&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;FPN 颈部，生成多尺度特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scalp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;是否保留最高分辨率特征（0 表示不保留）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;trunk&lt;/code&gt;, &lt;code&gt;neck&lt;/code&gt;, &lt;code&gt;scalp=0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化图像编码器组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sample&lt;/code&gt;: &lt;code&gt;[B, 3, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dict&lt;/code&gt;: &lt;code&gt;{&quot;vision_features&quot;: list, &quot;vision_pos_enc&quot;: list, &quot;backbone_fpn&quot;: list}&lt;/code&gt;&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;特征形状：&lt;code&gt;[B, C, H//s, W//s]&lt;/code&gt; (s 为各层步长)&lt;/td&gt;
&lt;td&gt;前向传播：主干提取特征 → FPN 颈部多尺度融合 → 添加位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;code&gt;MaskDecoder&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件路径&lt;/strong&gt;: &lt;code&gt;sam2/modeling/sam/mask_decoder.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: SAM 掩码解码器，基于 Transformer 和超网络生成掩码预测。支持多掩码输出、IoU 预测和对象分数预测。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;Transformer 维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TwoWayTransformer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;双向 Transformer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_multimask_outputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;多掩码输出数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(1, transformer_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IoU token 嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(num_mask_tokens, transformer_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码 token 嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_mask_tokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4（1 单掩码 + 3 多掩码）&lt;/td&gt;
&lt;td&gt;掩码 token 数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output_upscaling&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Sequential&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;输出上采样模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;output_hypernetworks_mlps&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.ModuleList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;超网络 MLP（每个掩码 token 一个）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iou_prediction_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MLP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;IoU 预测头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;obj_score_token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对象分数 token（如果 &lt;code&gt;pred_obj_scores=True&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pred_obj_score_head&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MLP&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;对象分数预测头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;conv_s0&lt;/code&gt;, &lt;code&gt;conv_s1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Conv2d&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;高分辨率特征卷积层（如果 &lt;code&gt;use_high_res_features=True&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dynamic_multimask_*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;多个参数&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;动态多掩码稳定性策略相关参数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;num_multimask_outputs=3&lt;/code&gt;, &lt;code&gt;transformer=None&lt;/code&gt;, &lt;code&gt;transformer_dim=256&lt;/code&gt;, &lt;code&gt;iou_head_depth=3&lt;/code&gt;, &lt;code&gt;iou_head_hidden_dim=256&lt;/code&gt;, &lt;code&gt;use_high_res_features=False&lt;/code&gt;, &lt;code&gt;iou_prediction_use_sigmoid=False&lt;/code&gt;, &lt;code&gt;pred_obj_scores=False&lt;/code&gt;, &lt;code&gt;pred_obj_scores_mlp=False&lt;/code&gt;, &lt;code&gt;use_multimask_token_for_obj_ptr=False&lt;/code&gt;, &lt;code&gt;dynamic_multimask_stability_score_thresh=0.95&lt;/code&gt;, &lt;code&gt;dynamic_multimask_iou_thresh=0.9&lt;/code&gt;, &lt;code&gt;dynamic_multimask_min_stability_score=0.9&lt;/code&gt;, &lt;code&gt;dynamic_multimask_max_output_masks=3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化掩码解码器，设置 Transformer、token、预测头等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image_embeddings&lt;/code&gt;: &lt;code&gt;[B, C, H, W]&lt;/code&gt;, &lt;code&gt;image_pe&lt;/code&gt;: &lt;code&gt;[B, C, H, W]&lt;/code&gt;, &lt;code&gt;sparse_prompt_embeddings&lt;/code&gt;: &lt;code&gt;[B, num_prompts, C]&lt;/code&gt;, &lt;code&gt;dense_prompt_embeddings&lt;/code&gt;: &lt;code&gt;[B, C, H, W]&lt;/code&gt;, &lt;code&gt;multimask_output=False&lt;/code&gt;, &lt;code&gt;repeat_image=False&lt;/code&gt;, &lt;code&gt;high_res_features=None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;masks&lt;/code&gt;: &lt;code&gt;[B, num_masks, H*4, W*4]&lt;/code&gt;, &lt;code&gt;ious&lt;/code&gt;: &lt;code&gt;[B, num_masks]&lt;/code&gt;, &lt;code&gt;output_tokens&lt;/code&gt;: &lt;code&gt;[B, num_masks, C]&lt;/code&gt;, &lt;code&gt;object_score_logits&lt;/code&gt;: &lt;code&gt;[B, 1]&lt;/code&gt; 或 &lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;包装 &lt;code&gt;predict_masks&lt;/code&gt;，根据配置选择单/多掩码输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;predict_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;同 &lt;code&gt;forward&lt;/code&gt; 参数&lt;/td&gt;
&lt;td&gt;同 &lt;code&gt;forward&lt;/code&gt; 返回值&lt;/td&gt;
&lt;td&gt;核心预测流程：token 拼接 → transformer → 超网络生成掩码 → IoU/对象分数预测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_stability_scores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mask_logits&lt;/code&gt;: &lt;code&gt;[B, num_masks, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;scores&lt;/code&gt;: &lt;code&gt;[B, num_masks]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;计算掩码稳定性分数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_dynamic_multimask_via_stability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;all_mask_logits&lt;/code&gt;: &lt;code&gt;[B, num_masks, H, W]&lt;/code&gt;, &lt;code&gt;all_iou_scores&lt;/code&gt;: &lt;code&gt;[B, num_masks]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;selected_mask_logits&lt;/code&gt;: &lt;code&gt;[B, M&apos;, H, W]&lt;/code&gt;, &lt;code&gt;selected_iou_scores&lt;/code&gt;: &lt;code&gt;[B, M&apos;]&lt;/code&gt; (M&apos; ≤ max_output_masks)&lt;/td&gt;
&lt;td&gt;通过稳定性分数动态选择多掩码输出&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;code&gt;PromptEncoder&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件路径&lt;/strong&gt;: &lt;code&gt;sam2/modeling/sam/prompt_encoder.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: SAM 提示编码器，将点、框、掩码提示编码为稀疏嵌入和稠密嵌入。包含点嵌入、框嵌入、掩码下采样和位置编码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;embed_dim&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;嵌入维度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input_image_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tuple&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(1024, 1024)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入图像尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image_embedding_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tuple&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(64, 64)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;图像嵌入尺寸（主干输出）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pe_layer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PositionEmbeddingSine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;位置编码层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_point_embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;点嵌入数量（正点、负点、框左上、框右下）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;point_embeddings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.ModuleList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;点嵌入层列表&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;not_a_point_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(1, embed_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&quot;非点&quot;嵌入（用于填充）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_input_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tuple&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(256, 256)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;掩码输入尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mask_downscaling&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Sequential&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;掩码下采样模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no_mask_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Embedding&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(1, embed_dim)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&quot;无掩码&quot;嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;embed_dim&lt;/code&gt;, &lt;code&gt;image_embedding_size&lt;/code&gt;, &lt;code&gt;input_image_size&lt;/code&gt;, &lt;code&gt;mask_in_chans=16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化提示编码器组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_dense_pe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dense_pe&lt;/code&gt;: &lt;code&gt;[1, embed_dim, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;返回稠密位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_embed_points&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;points&lt;/code&gt;: &lt;code&gt;[B, P, 2]&lt;/code&gt;, &lt;code&gt;labels&lt;/code&gt;: &lt;code&gt;[B, P]&lt;/code&gt;, &lt;code&gt;pad&lt;/code&gt;: &lt;code&gt;bool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;point_embeddings&lt;/code&gt;: &lt;code&gt;[B, P, embed_dim]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;嵌入点坐标和标签&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_embed_boxes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boxes&lt;/code&gt;: &lt;code&gt;[B, 4]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;box_embeddings&lt;/code&gt;: &lt;code&gt;[B, 2, embed_dim]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;嵌入框坐标（左上、右下）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_embed_masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;masks&lt;/code&gt;: &lt;code&gt;[B, 1, mask_input_size[0], mask_input_size[1]]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mask_embeddings&lt;/code&gt;: &lt;code&gt;[B, embed_dim, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;嵌入掩码提示（下采样 + 卷积）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_batch_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;points&lt;/code&gt;, &lt;code&gt;boxes&lt;/code&gt;, &lt;code&gt;masks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;batch_size&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;从输入中推断批大小&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;device&lt;/code&gt;: torch.device&lt;/td&gt;
&lt;td&gt;返回模型设备&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;points=None&lt;/code&gt;, &lt;code&gt;boxes=None&lt;/code&gt;, &lt;code&gt;masks=None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sparse_embeddings&lt;/code&gt;: &lt;code&gt;[B, num_prompts, embed_dim]&lt;/code&gt;, &lt;code&gt;dense_embeddings&lt;/code&gt;: &lt;code&gt;[B, embed_dim, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前向传播：编码所有提示类型，返回稀疏和稠密嵌入&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;code&gt;Hiera&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;文件路径&lt;/strong&gt;: &lt;code&gt;sam2/modeling/backbones/hieradet.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;类作用&lt;/strong&gt;: 分层视觉 Transformer 主干网络，支持多尺度注意力、窗口划分和全局注意力块。用于提取图像的多尺度特征。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;默认值&lt;/th&gt;
&lt;th&gt;作用描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;window_spec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tuple of tuples&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;各阶段的窗口尺寸规格&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q_stride&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tuple&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;各阶段的查询步长&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stage_ends&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;各阶段结束的块索引&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q_pool_blocks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;进行查询池化的块索引&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;return_interm_layers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;是否返回中间层特征&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;patch_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PatchEmbed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;图像块嵌入层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;global_att_blocks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;全局注意力块索引&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;window_pos_embed_bkg_spatial_size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tuple&lt;/td&gt;
&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;窗口位置编码背景空间尺寸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pos_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(1, C, H, W)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;全局位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pos_embed_window&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.Parameter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(1, C, win_H, win_W)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;窗口位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;blocks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nn.ModuleList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;多尺度块列表&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;channel_list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;各阶段输出通道数列表&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;方法表格&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名&lt;/th&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;返回值&lt;/th&gt;
&lt;th&gt;方法作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;window_spec&lt;/code&gt;, &lt;code&gt;q_stride&lt;/code&gt;, &lt;code&gt;stage_ends&lt;/code&gt;, &lt;code&gt;q_pool_blocks&lt;/code&gt;, &lt;code&gt;return_interm_layers=False&lt;/code&gt;, &lt;code&gt;patch_embed=None&lt;/code&gt;, &lt;code&gt;global_att_blocks=()&lt;/code&gt;, &lt;code&gt;window_pos_embed_bkg_spatial_size=None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;初始化 Hiera 主干网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_get_pos_embed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hw&lt;/code&gt;: tuple&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pos_embed&lt;/code&gt;: &lt;code&gt;[1, C, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;根据空间尺寸获取位置编码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;forward&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;: &lt;code&gt;[B, 3, H, W]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;out&lt;/code&gt;: dict 或 list of features&lt;/td&gt;
&lt;td&gt;前向传播：提取多尺度特征，可选返回中间层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_layer_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;layer_name&lt;/code&gt;: str&lt;/td&gt;
&lt;td&gt;&lt;code&gt;layer_id&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;根据层名获取层索引&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_num_layers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;num_layers&lt;/code&gt;: int&lt;/td&gt;
&lt;td&gt;返回总层数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;形状与执行路径总结&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;图像编码&lt;/strong&gt;: &lt;code&gt;ImageEncoder.forward&lt;/code&gt; -&amp;gt; 多尺度特征 + 位置编码
&lt;ul&gt;
&lt;li&gt;输入: &lt;code&gt;[B, 3, H, W]&lt;/code&gt; -&amp;gt; 输出: 多尺度特征列表 &lt;code&gt;[B, C, H//s, W//s]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记忆融合&lt;/strong&gt;: &lt;code&gt;SAM2Base._prepare_memory_conditioned_features&lt;/code&gt; -&amp;gt; &lt;code&gt;MemoryAttention.forward&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;输入: 当前特征 &lt;code&gt;[HW, B, C]&lt;/code&gt; + 记忆 -&amp;gt; 输出: 融合特征 &lt;code&gt;[B, C, H, W]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;掩码预测&lt;/strong&gt;: &lt;code&gt;SAM2Base._forward_sam_heads&lt;/code&gt; -&amp;gt; &lt;code&gt;PromptEncoder.forward&lt;/code&gt; + &lt;code&gt;MaskDecoder.forward/predict_masks&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;输入: 特征 &lt;code&gt;[B, C, H, W]&lt;/code&gt; + 提示 -&amp;gt; 输出: 掩码 &lt;code&gt;[B, M, H*4, W*4]&lt;/code&gt;, IoU &lt;code&gt;[B, M]&lt;/code&gt;, 对象指针 &lt;code&gt;[B, C]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记忆写回&lt;/strong&gt;: &lt;code&gt;SAM2Base._encode_new_memory&lt;/code&gt; -&amp;gt; &lt;code&gt;MemoryEncoder.forward&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;输入: 特征 &lt;code&gt;[B, C, H, W]&lt;/code&gt; + 掩码 &lt;code&gt;[B, 1, H*16, W*16]&lt;/code&gt; -&amp;gt; 输出: 记忆特征 &lt;code&gt;[B, C, H, W]&lt;/code&gt;, 位置编码 &lt;code&gt;[1, B, C, H, W]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;附录：SAM2 核心张量类型详解&lt;/h2&gt;
&lt;p&gt;在 SAM2 的 Transformer 架构中，存在多种功能的 Token，它们共同完成了从“视觉感知”到“时序记忆”的闭环。&lt;/p&gt;
&lt;h3&gt;1. Vision Tokens (视觉特征 Token 向量)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;来源&lt;/strong&gt;: &lt;code&gt;ImageEncoder&lt;/code&gt; 输出，代表图像空间网格上的特征采样。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本质&lt;/strong&gt;: 一个 &lt;strong&gt;$C$ 维特征向量&lt;/strong&gt;，代表图像在该坐标点的纹理、颜色等高维信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;形状&lt;/strong&gt;: 常态下为序列 &lt;code&gt;[HW, B, C]&lt;/code&gt;。其中每个 $1 \times C$ 的向量即为一个 Token。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特性&lt;/strong&gt;: 携带 &lt;strong&gt;2D RoPE&lt;/strong&gt;。在 &lt;code&gt;MemoryAttention&lt;/code&gt; 中作为 &lt;strong&gt;Query 向量&lt;/strong&gt; 参与点积运算。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. Prompt Tokens (提示 Token 向量)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;来源&lt;/strong&gt;: &lt;code&gt;PromptEncoder&lt;/code&gt; 将用户交互位置编码至嵌入空间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本质&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;点/框 Token&lt;/strong&gt;: 为每个交互点生成一个 &lt;strong&gt;$C$ 维位置向量&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;掩码 Token&lt;/strong&gt;: 掩码下采样后，每个网格单元也是一个 &lt;strong&gt;$C$ 维描述向量&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用&lt;/strong&gt;: 在计算相似度时，通过向量空间中的距离来吸引或排斥特定的 Vision Tokens。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Memory Tokens (记忆 Token 向量)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;来源&lt;/strong&gt;: &lt;code&gt;MemoryEncoder&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本质&lt;/strong&gt;: 经过掩码加权后的 &lt;strong&gt;$C$ 维视觉记忆向量&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用&lt;/strong&gt;: 存储物体历史状态的向量集合。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位置&lt;/strong&gt;: 在 &lt;code&gt;MemoryAttention&lt;/code&gt; 中作为 &lt;strong&gt;Key/Value 向量库&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. Object Pointers (对象指针向量)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;来源&lt;/strong&gt;: &lt;code&gt;MaskDecoder&lt;/code&gt; 的输出 Token。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本质&lt;/strong&gt;: 对整帧目标的 &lt;strong&gt;$C$ 维语义降维向量&lt;/strong&gt;。它将整帧物体的信息通过 Attention Pooling 压缩进几个点向量中（通常 16x256）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用&lt;/strong&gt;: 作为目标的“向量指纹”，用于跨帧的最高层匹配。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特殊处理&lt;/strong&gt;: 拼接到序列末尾参与矩阵乘法，但不带空间含义。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. Decoder Queries (解码器查询向量)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;注意&lt;/strong&gt;: 这些在严谨术语中更接近 &lt;strong&gt;Queries&lt;/strong&gt; 或 &lt;strong&gt;Task Embeddings&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;来源&lt;/strong&gt;: &lt;code&gt;MaskDecoder&lt;/code&gt; 的初始可学习 Embedding。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本质&lt;/strong&gt;: 它们是进入双向 Transformer 的“初始占位符”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;包含&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mask Queries&lt;/strong&gt;: 并不是最终掩码，而是通过注意力机制搜集特征，最终其向量值与特征图做点积来生成掩码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IoU Query&lt;/strong&gt;: 负责汇聚整组特征，最后输入 MLP 回归出一个标量分数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object Score Query&lt;/strong&gt;: 辅助判断物体是否在当前帧消失。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>从零开始学 AI - 第二章：图像分割</title><link>https://adalovelemon.github.io/posts/content/coursenotes/learn-ai-from-scratch/chapter2/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/learn-ai-from-scratch/chapter2/</guid><pubDate>Sun, 05 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;export const images = import.meta.glob(&quot;./assets/*.{png,jpg,jpeg,webp,gif,avif,svg}&quot;, {
eager: true,
import: &quot;default&quot;,
})&lt;/p&gt;
&lt;h2&gt;图像分割任务的范式&lt;/h2&gt;
&lt;p&gt;图像分割（Image Segmentation）是计算机视觉中比图像分类更为精细的任务。如果说图像分类回答的是“这张图里有什么”，那么图像分割则要回答“它们在哪里”以及“每个像素属于哪个对象”。其核心目标是将输入图像的每一个像素分配到一个特定的语义类别或实例标识中，从而实现对图像内容的像素级理解。&lt;/p&gt;
&lt;p&gt;给定一个输入图像 $x \in \mathbb{R}^{H \times W \times C}$，其中 $H$、$W$ 和 $C$ 分别表示图像的高度、宽度和通道数。假设总共有 $K$ 个类别。图像分割任务的目标是学习出一个从图像 $x$ 到像素标签 $M$ 的映射函数
$$
f_{\text{segment}}: \mathbb{R}^{H \times W \times C} \rightarrow {1, 2, ..., K}^{H \times W}
$$
其中输出 $M \in {1, 2, ..., K}^{H \times W}$ 是一个与输入图像空间尺寸相同的像素标签，称作&lt;strong&gt;掩码 (Mask)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;从掩码 $M$ 的结构上看，对于图像中的每一个像素位置 $(h, w)$，它都反映了该像素位置对应的一个类别标签 $M_{h,w}$。相较于图像分类，图像分割是一种更为精细化的图像识别任务。图像分割的输出不再是一个单一的标量值，而是一个高密度的空间分布图。这意味着模型不仅要识别物体的内容，还要精确地刻画物体的几何形状和边界。&lt;/p&gt;
&lt;p&gt;根据不同的应用需求，图像分割任务主要演化为三种不同的范式：&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/team.png&quot;].src} alt=&quot;原始输入图像&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;原始输入图像，摘自 https://zhuanlan.zhihu.com/p/368904941&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;h3&gt;1. 语义分割 (Semantic Segmentation)&lt;/h3&gt;
&lt;p&gt;语义分割是最基础的分割形式。它的目标是为图像中的每个像素分配一个预定义的语义类别标签，但&lt;strong&gt;不区分同一类别的不同个体&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;输入&lt;/strong&gt;: 图像 $x$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出&lt;/strong&gt;: 语义标签图 $S \in {1, 2, ..., K}^{H \times W}$，其中 $K$ 为类别总数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;: “只分种类，不分个体”。如果图像中有两个人，语义分割会将这两个人的所有像素都标记为“人”这一类别的标签，而不会区分“人A”和“人B”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用&lt;/strong&gt;: 自动驾驶中的道路/车道线检测、医学影像中的器官区域提取。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例说明&lt;/strong&gt;:
假设有一个包含“汽车”和“行人”的图像。在语义分割任务中，无论图像中有多少辆车，所有车的像素都会被赋予同一个类别ID（例如 ID=3）；同样，所有行人的像素都会被赋予另一个类别ID（例如 ID=5）。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;像素坐标 (h, w)&lt;/th&gt;
&lt;th&gt;真实语义&lt;/th&gt;
&lt;th&gt;语义分割输出&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;(10, 20)&lt;/td&gt;
&lt;td&gt;汽车&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(10, 21)&lt;/td&gt;
&lt;td&gt;汽车&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(10, 22)&lt;/td&gt;
&lt;td&gt;行人&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(10, 23)&lt;/td&gt;
&lt;td&gt;行人&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;注意：模型无法通过输出区分这是两辆不同的车，还是两个不同的人。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/semantic-segmentation.png&quot;].src} alt=&quot;语义分割结果&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;语义分割结果示例，摘自 https://zhuanlan.zhihu.com/p/368904941&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;h3&gt;2. 实例分割 (Instance Segmentation)&lt;/h3&gt;
&lt;p&gt;实例分割在语义分割的基础上进一步升级，它不仅要求识别像素的语义类别，还要求&lt;strong&gt;区分同一类别下的不同个体实例&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;输入&lt;/strong&gt;: 图像 $x$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出&lt;/strong&gt;: 实例掩码图 $I \in {0, 1, ..., N}^{H \times W}$，其中 $N$ 是该图像中检测到的物体实例总数（通常包括背景0）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;: “既分种类，又分个体”。每个独立的物体实例都被赋予一个唯一的ID。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用&lt;/strong&gt;: 机器人抓取（需要知道具体抓哪一辆车）、密集人群计数、视频分析中的目标跟踪。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例说明&lt;/strong&gt;:
继续上面的例子，如果有两辆汽车和两个人。实例分割不仅会告诉你是“汽车”和“行人”，还会给第一辆车分配ID=3，第二辆车分配ID=4；第一个人分配ID=5，第二个人分配ID=6。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;像素坐标 (h, w)&lt;/th&gt;
&lt;th&gt;真实语义&lt;/th&gt;
&lt;th&gt;实例分割输出&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;(10, 20)&lt;/td&gt;
&lt;td&gt;汽车 (左)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(10, 21)&lt;/td&gt;
&lt;td&gt;汽车 (左)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(10, 22)&lt;/td&gt;
&lt;td&gt;汽车 (右)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(10, 23)&lt;/td&gt;
&lt;td&gt;汽车 (右)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(10, 24)&lt;/td&gt;
&lt;td&gt;行人 (前)&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(10, 25)&lt;/td&gt;
&lt;td&gt;行人 (后)&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;注意：模型能够清晰地将两辆相邻的汽车区分开来。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/instance-segmentation.png&quot;].src} alt=&quot;实例分割结果&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;实例分割结果示例，摘自 https://zhuanlan.zhihu.com/p/368904941&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;h3&gt;3. 全景分割 (Panoptic Segmentation)&lt;/h3&gt;
&lt;p&gt;全景分割是近年来提出的统一框架，旨在将上述两种任务合二为一。它试图同时完成语义分割和实例分割的任务，并根据物体的属性进行智能处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于“可数物体”（Countable Things，如人、车、动物），执行&lt;strong&gt;实例分割&lt;/strong&gt;，区分个体。&lt;/li&gt;
&lt;li&gt;对于“不可数物体”（Stuff，如天空、草地、道路），执行&lt;strong&gt;语义分割&lt;/strong&gt;，不区分个体。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种范式解决了传统方法中难以平衡“区分个体”和“覆盖背景”的问题，是目前最接近人类视觉理解的分割范式。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/panoptic-segmentation.png&quot;].src} alt=&quot;全景分割结果&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;全景分割结果示例，摘自 https://zhuanlan.zhihu.com/p/368904941&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;h2&gt;图像分割的评估指标&lt;/h2&gt;
&lt;p&gt;为了衡量图像分割模型的性能，我们通常使用&lt;strong&gt;平均交并比 (mean Intersection over Union, mIoU)&lt;/strong&gt; 作为核心评估指标。IoU衡量了预测区域与真实标注区域的重叠程度：
$$
\text{IoU}_c = \frac{\text{Prediction}_c \cap \text{GroundTruth}_c}{\text{Prediction}_c \cup \text{GroundTruth}_c}
$$
其中 $c$ 代表某个特定的类别。mIoU则是所有类别IoU的平均值。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;真实标签\模型预测&lt;/th&gt;
&lt;th&gt;预测为“汽车”&lt;/th&gt;
&lt;th&gt;预测为“非汽车”&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;真实为“汽车”&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TP (真阳性)&lt;/td&gt;
&lt;td&gt;FN (假阴性)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;真实为“非汽车”&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FP (假阳性)&lt;/td&gt;
&lt;td&gt;TN (真阴性)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;对于类别 $c$，其IoU计算如下：
$$
\text{IoU}_c = \frac{\text{预测区域} \cap \text{真实区域}}{\text{预测区域} \cup \text{真实区域}} = \frac{TP_c}{TP_c + FP_c + FN_c}
$$
如果一个模型在测试集上对“汽车”类别的预测结果如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;真正例 (TP): 80 (正确预测为汽车的像素数)&lt;/li&gt;
&lt;li&gt;假正例 (FP): 20 (被误判为汽车的背景像素数)&lt;/li&gt;
&lt;li&gt;假负例 (FN): 10 (被漏掉的真实汽车像素数)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;则该类别的 IoU 为：
$$
\text{IoU}_{\text{car}} = \frac{80}{80 + 20 + 10} = \frac{80}{110} \approx 0.727
$$&lt;/p&gt;
&lt;p&gt;mIoU即为所有类别IoU值的算术平均，反映了模型整体的分割能力。数值越接近1，表示分割精度越高。
$$
\text{mIoU} = \frac{1}{N} \sum_{i=1}^{N} \text{IoU}_i
$$&lt;/p&gt;
&lt;h2&gt;图像分割的损失函数&lt;/h2&gt;
&lt;p&gt;对于图像分割，我们希望模型在每个像素上的预测与真实掩码尽可能一致。既然预测掩码本质上是给每个像素位置做一个分类任务，那显然可以仿照着图像分类，用交叉熵损失作为任务的损失函数。因此，分割任务的损失函数可以看作是分类损失在空间维度上的自然扩展。&lt;/p&gt;
&lt;p&gt;考虑到每一个像素位置都有一个交叉熵损失，那就有一个交叉熵的矩阵了。我们可以对这个矩阵进行求和或者求平均，得到一个标量损失值，因为不论是哪种方式，当求和或求平均的损失值最小时，所有的交叉熵损失也都是最小的 (交叉熵是非负的)。一般地，选择每个像素的交叉熵的平均值作为图像分割的交叉熵损失函数：
$$
\mathcal{L}&lt;em&gt;{CE} = -\frac{1}{H \times W} \sum&lt;/em&gt;{h=1}^{H} \sum_{w=1}^{W} \sum_{c=1}^{K} y_{h,w,c} \log(\hat{y}&lt;em&gt;{h,w,c})
$$
其中 $y&lt;/em&gt;{h,w,c}$ 是 one‑hot 编码的真实标签（若像素 $(h,w)$ 属于类别 $c$ 则为1，否则0），$\hat{y}_{h,w,c}$ 是模型对该像素属于类别 $c$ 的预测概率（通常由 Softmax 输出）。&lt;/p&gt;
&lt;p&gt;然而，在实际应用中，如果图像中背景占据了绝大部分位置，换句话讲就是类别信息极度不平衡的情况下，倘若只使用交叉熵损失，模型会陷入到&lt;strong&gt;将所有像素预测为背景&lt;/strong&gt;的局面。这是因为交叉熵的求和或者平均操作忽视了占少数像素区域的类别的重要性，导致模型在训练过程中被大量的主导类别像素如背景像素主导，从而无法有效学习到前景类别的特征。&lt;/p&gt;
&lt;p&gt;为了克服类别不平衡问题，分割任务中常引入&lt;strong&gt;基于区域重叠&lt;/strong&gt;的损失函数，其中最典型的是 &lt;strong&gt;Dice Loss&lt;/strong&gt;。它直接优化预测区域与真实区域的相似度，而不是逐像素的独立分类。&lt;/p&gt;
&lt;p&gt;Dice 系数（Dice coefficient）是一种集合相似度度量，定义如下
$$
\text{Dice} = \frac{2 \cdot |\text{Pred} \cap \text{GT}|}{|\text{Pred}| + |\text{GT}|}
$$
其中 Pred 和 GT 分别表示预测的前景像素集合和真实前景像素集合。$|\cdot|$ 表示集合中像素的数量。Dice 系数的值域为 $[0,1]$，越接近1表示两个区域重叠越好。相应地，&lt;strong&gt;Dice Loss&lt;/strong&gt; 被定义为
$$
\mathcal{L}&lt;em&gt;{Dice}^{\text{Original}} = 1 - \text{Dice}
$$
为了将其应用于神经网络（需要可微的表达式），我们通常使用概率化的形式，以保证梯度可以传播。令 $p&lt;/em&gt;{h,w}$ 为模型预测像素 $(h,w)$ 属于前景的概率（对于二分类，Sigmoid 输出），$g_{h,w} \in {0,1}$ 为真实标签，则：&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}&lt;em&gt;{Dice} = 1 - \frac{2 \sum&lt;/em&gt;{h,w} p_{h,w} g_{h,w}}{\sum_{h,w} p_{h,w} + \sum_{h,w} g_{h,w} + \epsilon}
$$&lt;/p&gt;
&lt;p&gt;其中 $\epsilon$ 是一个很小的常数（如 $10^{-5}$），用于避免分母为零。对于多类别分割，通常对每个类别分别计算 Dice Loss 然后取平均。&lt;/p&gt;
&lt;p&gt;观察 Dice Loss 的分母项，它同时考虑了预测区域的大小和真实区域的大小。如果模型将所有像素预测为背景（即所有 $p_{h,w} \approx 0$），分子为0，分母为 $0 + |GT|$，此时 $\mathcal{L}_{Dice}=1$（最大损失）；如果模型只预测出很少的前景像素，分子很小，分母中预测区域也很小，导致 Dice 系数较低。换句话说，&lt;strong&gt;Dice Loss 强制模型关注前景区域，无论它多么小&lt;/strong&gt;。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;逐像素交叉熵 (CE)&lt;/th&gt;
&lt;th&gt;Dice Loss&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;优化粒度&lt;/td&gt;
&lt;td&gt;每个像素独立&lt;/td&gt;
&lt;td&gt;整个前景区域&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;对类别不平衡的鲁棒性&lt;/td&gt;
&lt;td&gt;差（易被背景主导）&lt;/td&gt;
&lt;td&gt;好（聚焦前景）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;梯度平滑度&lt;/td&gt;
&lt;td&gt;稳定&lt;/td&gt;
&lt;td&gt;可能在小目标上震荡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适用场景&lt;/td&gt;
&lt;td&gt;类别相对平衡的分割&lt;/td&gt;
&lt;td&gt;前景占比极小的分割（如病灶、缺陷）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;多类别扩展&lt;/td&gt;
&lt;td&gt;天然支持&lt;/td&gt;
&lt;td&gt;需逐类计算后平均&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;实践中的常见组合&lt;/strong&gt;：由于 CE 和 Dice 各有优劣，许多分割模型将两者结合使用&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L} = \lambda \cdot \mathcal{L}&lt;em&gt;{CE} + (1-\lambda) \cdot \mathcal{L}&lt;/em&gt;{Dice}
$$&lt;/p&gt;
&lt;p&gt;其中 $\lambda$ 是平衡超参数，通常取 $0.5$ 或根据验证集调整。这种组合既能利用 CE 的稳定收敛性，又能借助 Dice 提升小目标的分割效果，成为许多现代分割模型的默认选择。&lt;/p&gt;
&lt;h2&gt;图像分割任务的技术发展&lt;/h2&gt;
&lt;p&gt;图像分割的核心挑战在于如何同时捕捉图像的&lt;strong&gt;全局语义信息&lt;/strong&gt;（理解“是什么”）和&lt;strong&gt;局部空间细节&lt;/strong&gt;（理解“在哪里”）。它对模型理解语义的能力提出了更高的要求，同时也对模型的计算效率和优化稳定性提出了更高的挑战。&lt;/p&gt;
&lt;p&gt;图像分割技术的发展并非一蹴而就，而是经历了一个从“手工特征工程”到“端到端深度学习”，再到“架构创新与深层网络优化”的漫长演变过程。&lt;/p&gt;
&lt;h3&gt;朴素的想法：基于像素差异的传统分割&lt;/h3&gt;
&lt;p&gt;在深度学习爆发之前，计算机视觉中的分割任务主要依赖于&lt;strong&gt;低层视觉特征&lt;/strong&gt;和&lt;strong&gt;手工设计的规则&lt;/strong&gt;。这些方法的核心理念非常朴素：&lt;strong&gt;“相似的像素属于同一个区域”&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基于聚类的方法 (Clustering)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;早期的算法如 &lt;a href=&quot;https://www.semanticscholar.org/paper/Some-methods-for-classification-and-analysis-of-MacQueen/ac8ab51a86f1a9ae74dd0e4576d1a019f5e654ed&quot;&gt;&lt;strong&gt;K-Means 聚类&lt;/strong&gt;&lt;/a&gt; 或 &lt;a href=&quot;https://ieeexplore.ieee.org/document/1000236&quot;&gt;&lt;strong&gt;Mean Shift&lt;/strong&gt;&lt;/a&gt;，将图像中的每个像素视为一个高维空间中的点（由颜色、纹理等特征组成）。算法试图将这些点划分为 $K$ 个簇，使得同一簇内的像素相似度最大，不同簇之间的差异最大。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数学形式&lt;/strong&gt;: 最小化簇内平方误差 $\sum_{i=1}^K \sum_{x \in C_i} ||x - \mu_i||^2$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;局限性&lt;/strong&gt;: 这种方法忽略了像素的空间邻域信息。如果两个物体颜色相似但位置相距较远，它们可能会被错误地归为一类；反之，如果物体内部颜色有渐变，会被错误地切分成多个区域。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;基于边缘检测的方法 (Edge Detection)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;另一种思路是先检测物体的边界，再填充区域。经典的算子如 &lt;strong&gt;Sobel&lt;/strong&gt;, &lt;strong&gt;Canny&lt;/strong&gt; 通过计算图像梯度的幅值来寻找亮度剧烈变化的地方。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数学形式&lt;/strong&gt;: Sobel 算子在 $x$ 方向的梯度近似为 $G_x = I * \begin{bmatrix} -1 &amp;amp; 0 &amp;amp; 1 \ -2 &amp;amp; 0 &amp;amp; 2 \ -1 &amp;amp; 0 &amp;amp; 1 \end{bmatrix}$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;局限性&lt;/strong&gt;: 边缘检测往往只能得到不连续的细线，难以形成封闭的区域。此外，噪声极易产生虚假边缘，且无法处理纹理复杂但边缘模糊的场景（如云朵、草地）。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;: 传统方法严重依赖人工设计的特征（Feature Engineering），缺乏对高层语义的理解。当面对复杂的自然场景时，这些基于局部像素差异的规则往往显得力不从心。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;范式转变：全卷积型网络 (FCSN) 的诞生&lt;/h3&gt;
&lt;p&gt;随着卷积神经网络（CNN）在图像分类任务上的巨大成功，研究人员开始思考：&lt;strong&gt;能否将分类网络改造为分割网络？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;传统的 CNN（如 VGG, GoogLeNet）最后通常接有全连接层（Fully Connected Layers），这要求输入图像的尺寸必须是固定的（例如 $224 \times 224$），且输出是一个标量类别概率。这种结构天然不适合处理任意尺寸的图像，也无法输出像素级的标签图。&lt;/p&gt;
&lt;p&gt;既然 CNN 输出的就是具有二维结构的特征图，那么去掉全连接层是不是就可以用于输出像素级的标签图呢？这个革命性的想法——&lt;strong&gt;移除所有全连接层，用卷积层替代它们&lt;/strong&gt;——是可行的，我们姑且称这类网络为&lt;strong&gt;全卷积型网络 (Fully Convolution Style Networks, FCSN)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;注意这里的 FCSN 和&lt;a href=&quot;https://arxiv.org/abs/1411.4038&quot;&gt;一般文献中提到的全卷积网络 (FCN)&lt;/a&gt; 是有区别的，本文的 FCSN 指的是一种类型的网络架构模式，不是一个具体的模型名称，而一般文献中的 FCN 是指 Long 等人提出的第一个全卷积网络模型 (&lt;strong&gt;FCN 里面是有特征降采样操作的，在此区分&lt;/strong&gt;)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心思想&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;全卷积化&lt;/strong&gt;: 将原本的全连接层替换为 $1 \times 1$ 或者 $3 \times 3, \text{padding} = 1$，步长为 1 的卷积层。这样，无论输入图像尺寸如何变化，网络都能输出一个特征图（Feature Map），而不是一个固定长度的向量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无池化操作&lt;/strong&gt;: 不使用任何池化层（如 Max Pooling），以保持特征图的空间分辨率不变。这样，输出的特征图与输入图像具有相同的空间尺寸。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;$$
\text{Output Mask} \in \mathbb{R}^{H \times W \times K}
$$&lt;/p&gt;
&lt;p&gt;虽然 FCSN 有效的达到了用神经网络做分割的目的，但它生成的分割结果往往比较粗糙，边界模糊。这是因为深层的特征图虽然包含了丰富的语义信息（“是什么”），却丢失了精细的空间细节（“在哪里”）。&lt;/p&gt;
&lt;h3&gt;深层网络的困境：梯度消失与残差网络 (ResNet)&lt;/h3&gt;
&lt;h4&gt;网络越深，看到的“世界”越丰富&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;浅层网络&lt;/strong&gt;（如早期的 CNN）是只能识别图像中的&lt;strong&gt;低级特征&lt;/strong&gt;的——比如&lt;strong&gt;颜色&lt;/strong&gt;、&lt;strong&gt;边缘&lt;/strong&gt;、&lt;strong&gt;简单的纹理&lt;/strong&gt;；而&lt;strong&gt;深层网络&lt;/strong&gt;通过层层堆叠，能够&lt;strong&gt;将低级特征组合成高级抽象&lt;/strong&gt;——如&lt;strong&gt;局部形状&lt;/strong&gt;、&lt;strong&gt;物体部件&lt;/strong&gt;，最后识别出&lt;strong&gt;完整的物体&lt;/strong&gt;及其&lt;strong&gt;上下文关系&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这就好比人类视觉系统一样：从视网膜接收光信号，经过大脑皮层逐步处理，才能最终识别出&quot;这是一个人正在跑步&quot;这样复杂的场景。正是因为这种信息处理的性质，在图像分割、图像识别等&lt;strong&gt;需要关注语义理解&lt;/strong&gt;的任务中，&lt;strong&gt;需要深层网络&lt;/strong&gt;的强语义理解能力来判断图像的内容信息的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;📐 分析:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;根据&lt;strong&gt;数据处理不等式 (Data Processing Inequality)&lt;/strong&gt;，当信号经过多级非线性变换后，原始信息与输出的互信息是非递增的
$$
I(X; Z_0) \ge I(X; Z_1) \ge I(X; Z_2) \ge \dots \ge I(X; Z_L)
$$
其中 $X$ 是原始输入图像，$Z_k$ 是第 $k$ 层的特征输出。这意味着，&lt;strong&gt;网络越深，从输入保留的细节信息越少&lt;/strong&gt;。由于通道数固定或减小的限制，深层网络必然对信息进行&lt;strong&gt;压缩与筛选&lt;/strong&gt;，只保留最关键的特征表示。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;那么深层压缩保留的关键信息是什么？&lt;/strong&gt; 答案是&lt;strong&gt;全局的语义信息&lt;/strong&gt;。为了理解这一点，需要明白什么是感受野，然后理解感受野为何会扩大。感受野是指网络中某个神经元能够“看到”的输入图像区域的大小。由于卷积等操作的本质是对上一层特征的局部加权求和，而下一层再对这些求和结果做同样的操作——这意味着每一层的神经元不仅接收本层的局部邻域，还会通过上层的间接依赖&quot;追溯&quot;到更远的前向输入范围。这种&lt;strong&gt;层级累积效应&lt;/strong&gt;使得感受野随深度逐层扩展：浅层神经元可能只看几十像素的局部区域，但深层神经元经过多次叠加，其信息追溯可以覆盖整个输入张量。&lt;strong&gt;无论是非全连接网络（如卷积神经网络）的感受野随深度累积扩大，还是全连接网络从一开始就覆盖整个输入，到深层时神经元的计算都已基于完整的全局输入空间进行&lt;/strong&gt;。因此，深层的信息压缩不是局部特征的叠加，而是在全局张量层面上提取跨区域、抽象化的语义模式——这就是浅层特征更多的是关于图像像素层级的信息 (如边界定位)，而深层特征属于语义层级的根本原因。&lt;/p&gt;
&lt;h4&gt;梯度消失问题&lt;/h4&gt;
&lt;p&gt;但是，在尝试构建更深的网络以提取更强语义特征时，研究人员发现了一个严重的瓶颈——&lt;strong&gt;梯度消失问题 (Gradient Vanishing Problem)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在反向传播过程中，梯度需要通过链式法则逐层传递。对于深层网络，梯度是多层导数的连乘：
$$
\frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial y_L} \cdot \frac{\partial y_L}{\partial y_{L-1}} \cdots \frac{\partial y_2}{\partial y_1} \cdot \frac{\partial y_1}{\partial w_1}
$$
如果每一层的激活函数导数（如 Sigmoid 或 Tanh）都小于 1，那么连乘的结果会指数级衰减，趋近于 0。这导致浅层网络的参数几乎得不到更新，网络无法训练。&lt;/p&gt;
&lt;h4&gt;残差学习 (Residual Learning)&lt;/h4&gt;
&lt;p&gt;2015 年，He et al. 提出的 &lt;a href=&quot;(https://arxiv.org/abs/1512.03385)&quot;&gt;&lt;strong&gt;ResNet&lt;/strong&gt;&lt;/a&gt; 巧妙地解决了这一问题。其核心思想不再是直接学习目标映射 $H(x)$，而是学习残差 $F(x) = H(x) - x$。&lt;/p&gt;
&lt;p&gt;通过引入&lt;strong&gt;跳跃连接 (Skip Connection)&lt;/strong&gt;，网络结构变为：
$$
y = F(x, {W_i}) + x
$$
在反向传播时，梯度可以直接通过加法操作流向浅层：
$$
\frac{\partial L}{\partial x} = \frac{\partial L}{\partial y} \cdot \left( \frac{\partial F}{\partial x} + 1 \right) = \frac{\partial L}{\partial y} + \frac{\partial L}{\partial y} \cdot \frac{\partial F}{\partial x}
$$
那个关键的 &lt;strong&gt;&quot;$+1$&quot;&lt;/strong&gt; 保证了梯度至少可以无损地传回浅层，即使深层的导数部分很小，也不会导致梯度完全消失。这使得训练数百甚至上千层的网络成为可能。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/resnet.png&quot;].src} alt=&quot;ResNet 残差块&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ResNet 残差块&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch.nn as nn

class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, in_channels, out_channels, stride=1):
        super(BasicBlock, self).__init__()
        # 主路径
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, 
                               stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, 
                               stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)

        # 跳跃连接 (Shortcut)
        self.shortcut = nn.Sequential()
        if stride != 1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=1, 
                          stride=stride, bias=False),
                nn.BatchNorm2d(out_channels)
            )

    def forward(self, x):
        identity = x
        
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        # 关键步骤：跳跃连接相加
        out += self.shortcut(identity)
        out = self.relu(out)

        return out
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ResNet 的提出使得训练更深的神经网络成为可能，它在&lt;strong&gt;图像分类&lt;/strong&gt;任务上大放异彩，成功超越了当时的所有模型。但是残差学习的想法远不局限在图像分类领域，其广泛应用为后续更复杂的分割网络（如 DeepLab 系列、Mask R-CNN）也提供了强大的骨干网络（Backbone），使其能够提取极其丰富的深层语义特征。&lt;/p&gt;
&lt;h3&gt;从 FCSN 到 U-Net：解决深度网络的两难困境&lt;/h3&gt;
&lt;h4&gt;加深 FCSN 的困难&lt;/h4&gt;
&lt;p&gt;FCSN 架构虽然开创了语义分割的先河，但也暴露出了明显的短板：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优化难题&lt;/strong&gt;：为了获得更强的语义理解能力，网络层数需要加深。但网络越深，反向传播时的梯度信号就越弱，导致训练越来越不稳定，甚至难以收敛。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源瓶颈&lt;/strong&gt;：如果在深层网络中强行保持与原图一致的高分辨率特征，计算量和显存占用将呈指数级上升，普通显卡根本无法承载。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如何破局？考虑到既然图像分割任务既需要有&lt;strong&gt;精细空间定位能力来画出准确的边界&lt;/strong&gt;，也要有&lt;strong&gt;高度抽象的语义理解能力&lt;/strong&gt;，那么将浅层特征和深层特征进行有效融合是构建图像分割模型的关键。一个直观的解决方案是结合 ResNet 的&lt;strong&gt;残差连接&lt;/strong&gt;（解决梯度消失的同时融合深层和浅层特征）与&lt;strong&gt;金字塔网络结构&lt;/strong&gt;（解决计算开销）。这里的金字塔网络结构，如下图所示，形式上先压缩特征图大小，再逐步恢复原图分辨率。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/pyramid.png&quot;].src} alt=&quot;特征金字塔结构&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;残差 + 金字塔结构带来的问题&lt;/h4&gt;
&lt;p&gt;然而，直接将这套组合拳应用到图像分割时，我们遇到了两个棘手的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;维度不兼容&lt;/strong&gt;：标准的 ResNet 残差连接&lt;strong&gt;要求“残差连接”两端的张量维度完全一致&lt;/strong&gt;。但在金字塔结构中，我们需要频繁地进行下采样和上采样，这直接破坏了维度匹配的前提。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;融合机制的错位&lt;/strong&gt;：残差连接的核心逻辑是 $F(x) = H(x) - x$，即让网络仅学习输入与目标之间的“残差”。这种机制更&lt;strong&gt;适用于分类&lt;/strong&gt;等任务，却&lt;strong&gt;不适用于分割&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;细节丢失风险&lt;/strong&gt;：分割任务需要像素级的精确还原。较深层的特征经过多次下采样后，虽然包含了丰富的语义信息（如&quot;这是汽车&quot;），但&lt;strong&gt;空间分辨率的下降导致物体边界的精确位置信息被&quot;压缩&quot;&lt;/strong&gt;；而浅层特征保留了原始图像的空间细节。如果使用 Add 操作强行融合两者，&lt;strong&gt;深层特征的数值可能会干扰浅层的高频细节&lt;/strong&gt;，导致模型难以学会精确的边界定位。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数值冲突&lt;/strong&gt;：不同阶段的特征图数值分布差异巨大，强行相加往往导致信息混淆甚至梯度震荡。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;UNet 的方案——对称结构与跳跃连接&lt;/h4&gt;
&lt;p&gt;UNet 使用对称结构和跳跃连接（Skip Connection）巧妙地解决了上述问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;模型结构上，U-Net 的框架像一个字母&quot;U&quot;，由两部分组成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;收缩路径 (Contracting Path / Encoder)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过重复应用两个 $3 \times 3$、填充为 $1$ 的卷积，每个卷积后接 BatchNorm 和 ReLU 激活，然后使用 $2 \times 2$ 的最大池化进行下采样。&lt;/li&gt;
&lt;li&gt;每下采样一次，通道数翻倍，特征图尺寸减半。这一步提取了越来越抽象的语义特征。&lt;/li&gt;
&lt;li&gt;我们把收缩路径这部分的网络称为&lt;strong&gt;编码器 (Encoder)&lt;/strong&gt;，它负责从输入图像中提取多层次的特征表示。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;扩展路径 (Expansive Path / Decoder)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目的是恢复特征图的空间分辨率，以便进行像素级预测。&lt;/li&gt;
&lt;li&gt;每一步先进行 $2 \times 2$ 的上采样（&lt;a href=&quot;https://zh.d2l.ai/chapter_computer-vision/transposed-conv.html&quot;&gt;&lt;strong&gt;转置卷积&lt;/strong&gt;&lt;/a&gt;实现），将特征图尺寸扩大一倍，通道数减半。&lt;/li&gt;
&lt;li&gt;然后与收缩路径中对应层级的特征图进行&lt;strong&gt;拼接 (Concatenation)&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;最后经过两个 $3 \times 3$ 卷积，每个卷积后接 BatchNorm 和 ReLU。&lt;/li&gt;
&lt;li&gt;我们把扩展路径这部分的网络称为&lt;strong&gt;解码器 (Decoder)&lt;/strong&gt;，它负责将编码器提取的语义特征与空间细节融合，最终输出与输入图像同尺寸的分割掩码。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;特征连接上，U-Net 最终选择了用 &lt;strong&gt;Concat (拼接)&lt;/strong&gt; 去跳跃连接浅层和深层的特征。这种方式不强制使用 Add 注入浅层特征，这样既保留了原始空间细节，又让后续网络能够自主学习如何融合“物体是什么”与“物体在哪里”，从而便于实现高精度的像素级分割。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;编码器&lt;/strong&gt;保留了高分辨率的空间细节（如边缘、纹理），但语义信息较弱。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解码器&lt;/strong&gt;拥有强语义信息，但空间分辨率低。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拼接操作&lt;/strong&gt;将编码器的空间细节直接注入解码器，使得模型在恢复分辨率的同时，能够利用这些细节来精确定位物体边界。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/unet.png&quot;].src} alt=&quot;U-Net 结构图&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;U-Net 结构图&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;U-Net 代码实现&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class DoubleConv(nn.Module):
    &quot;&quot;&quot;两次卷积 + BN + ReLU&quot;&quot;&quot;
    def __init__(self, in_ch, out_ch):
        super(DoubleConv, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(in_ch, out_ch, 3, padding=1),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_ch, out_ch, 3, padding=1),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True)
        )

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

class UNet(nn.Module):
    def __init__(self, n_classes=2, base_filters=64):
        super(UNet, self).__init__()
        
        # --- 编码器 (Encoder) ---
        self.enc1 = DoubleConv(3, base_filters)      # 3 -&amp;gt; 64
        self.pool1 = nn.MaxPool2d(2)
        
        self.enc2 = DoubleConv(base_filters, base_filters * 2) # 64 -&amp;gt; 128
        self.pool2 = nn.MaxPool2d(2)
        
        self.enc3 = DoubleConv(base_filters * 2, base_filters * 4) # 128 -&amp;gt; 256
        self.pool3 = nn.MaxPool2d(2)
        
        self.enc4 = DoubleConv(base_filters * 4, base_filters * 8) # 256 -&amp;gt; 512
        
        # --- 瓶颈层 (Bottleneck) ---
        self.bottleneck = DoubleConv(base_filters * 8, base_filters * 16)
        
        # --- 解码器 (Decoder) ---
        self.upconv4 = nn.ConvTranspose2d(base_filters * 16, base_filters * 8, 2, stride=2)
        self.dec4 = DoubleConv(base_filters * 16, base_filters * 8) # 注意：这里通道数是 8+8=16
        
        self.upconv3 = nn.ConvTranspose2d(base_filters * 8, base_filters * 4, 2, stride=2)
        self.dec3 = DoubleConv(base_filters * 8, base_filters * 4)
        
        self.upconv2 = nn.ConvTranspose2d(base_filters * 4, base_filters * 2, 2, stride=2)
        self.dec2 = DoubleConv(base_filters * 4, base_filters * 2)
        
        self.upconv1 = nn.ConvTranspose2d(base_filters * 2, base_filters, 2, stride=2)
        self.dec1 = DoubleConv(base_filters * 2, base_filters)
        
        self.out = nn.Conv2d(base_filters, n_classes, 1)

    def forward(self, x):
        # 编码器前向
        e1 = self.enc1(x)
        e2 = self.enc2(self.pool1(e1))
        e3 = self.enc3(self.pool2(e2))
        e4 = self.enc4(self.pool3(e3))
        
        # 瓶颈
        b = self.bottleneck(self.pool3(e4))
        
        # 解码器前向 (包含跳跃连接)
        d4 = self.upconv4(b)
        # 拼接：上采样后的特征 + 对应的编码器特征
        d4 = torch.cat((e4, d4), dim=1) 
        d4 = self.dec4(d4)
        
        # 同理继续上采样和拼接
        d3 = self.upconv3(d4)
        d3 = torch.cat((e3, d3), dim=1)
        d3 = self.dec3(d3)
        
        d2 = self.upconv2(d3)
        d2 = torch.cat((e2, d2), dim=1)
        d2 = self.dec2(d2)
        
        d1 = self.upconv1(d2)
        d1 = torch.cat((e1, d1), dim=1)
        d1 = self.dec1(d1)
        
        return self.out(d1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;在 Pascal VOC 2012 上做语义分割&lt;/h2&gt;
&lt;p&gt;Pascal VOC 2012 是计算机视觉领域最具影响力的基准数据集之一，由英国牛津大学和剑桥大学联合发布，广泛用于评估图像分类、目标检测及语义分割等算法性能。该数据集包含约 11,000 张标注图像，涵盖 20 个常见物体类别和 10 个人体动作类别，其数据结构清晰完整，包括原始 RGB 图像（JPEGImages）、目标检测标注文件（Annotations/）、任务划分文件（ImageSets/）以及语义分割掩码（SegmentationClass/）。在语义分割任务中，通常使用 &lt;code&gt;ImageSets/Segmentation/train.txt&lt;/code&gt; 与 &lt;code&gt;val.txt&lt;/code&gt; 作为官方划分，并将像素值 &lt;code&gt;255&lt;/code&gt; 视为忽略标签（ignore index）。作为像素级分割的重要试验平台，VOC 2012 自发布以来一直是经典 benchmark；不过需要注意的是，官方测试集真实标签并未公开，通常需要通过在线评测服务器提交结果，因此教学与研究中常使用训练集与验证集进行离线评估。&lt;/p&gt;
&lt;h3&gt;数据集下载&lt;/h3&gt;
&lt;p&gt;VOC 2012 数据集可以从官方链接下载：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://thor.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar&quot;&gt;VOC 2012 数据集下载链接&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下载并解压后，会得到如下的文件结构&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;VOCDEVKIT
└─VOC2012
    ├─Annotations
    ├─ImageSets
    │  ├─Action
    │  ├─Layout
    │  ├─Main
    │  └─Segmentation
    ├─JPEGImages
    ├─SegmentationClass
    └─SegmentationObject
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Annotations/&lt;/code&gt;: 包含每张图像的 XML 格式标注文件，主要用于目标检测任务。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ImageSets/&lt;/code&gt;: 包含不同任务的训练/验证/测试集划分文件。语义分割任务通常使用 &lt;code&gt;Segmentation/train.txt&lt;/code&gt; 与 &lt;code&gt;Segmentation/val.txt&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JPEGImages/&lt;/code&gt;: 包含原始 RGB 图像。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SegmentationClass/&lt;/code&gt;: 包含语义分割的掩码图像，每个像素的值对应一个类别 ID。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SegmentationObject/&lt;/code&gt;: 包含实例分割的掩码图像，每个像素的值对应一个实例 ID。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;构建自定义数据集类——&lt;code&gt;torch.utils.data.Dataset&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;在实际项目中，我们很少能直接使用 PyTorch 内置的标准数据集。学会如何将自己的图像数据加载到模型中，是迈向实战的关键一步。PyTorch 提供了 &lt;code&gt;torch.utils.data.Dataset&lt;/code&gt; 和 &lt;code&gt;DataLoader&lt;/code&gt; 两个核心工具来高效地处理数据。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Dataset&lt;/code&gt; 是一个抽象类，它定义了数据集的接口。要创建自己的数据集，只需继承这个类并实现两个核心方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__len__(self)&lt;/code&gt;: 返回数据集的总样本数量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;__getitem__(self, idx)&lt;/code&gt;: 根据给定的索引 &lt;code&gt;idx&lt;/code&gt;，从数据集中读取并返回一个样本（通常是图像和对应的标签）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种设计将数据加载逻辑与模型训练循环解耦，使代码更加模块化和清晰。下面给出一个教学版示例；在本章工程代码中，我们进一步将图像与掩码的几何变换做了同步处理，并将预处理流程内置在数据集中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
import numpy as np
from PIL import Image
import torch
from torch.utils.data import Dataset
from torchvision import transforms

class PascalVOC2012Dataset(Dataset):
    &quot;&quot;&quot;极简版 Pascal VOC 2012 数据集
    仅包含基础的 Resize 和 Normalize 操作。
    &quot;&quot;&quot;
    
    CLASSES = [
        &apos;background&apos;, &apos;aeroplane&apos;, &apos;bicycle&apos;, &apos;bird&apos;, &apos;boat&apos;, &apos;bottle&apos;, 
        &apos;bus&apos;, &apos;car&apos;, &apos;cat&apos;, &apos;chair&apos;, &apos;cow&apos;, &apos;diningtable&apos;, &apos;dog&apos;, 
        &apos;horse&apos;, &apos;motorbike&apos;, &apos;person&apos;, &apos;pottedplant&apos;, &apos;sheep&apos;, &apos;sofa&apos;, 
        &apos;train&apos;, &apos;tvmonitor&apos;
    ]
    
    def __init__(self, root_dir, image_set=&apos;train&apos;, image_size=(512, 512)):
        &quot;&quot;&quot;
        Args:
            root_dir (str): VOCdevkit/VOC2012 的根目录路径
            image_set (str): &apos;train&apos;, &apos;val&apos;, 或 &apos;trainval&apos;
            image_size (tuple): 输出图像大小 (H, W)
        &quot;&quot;&quot;
        self.root_dir = root_dir
        self.image_size = image_size
        self.image_ids = self._load_split_file(image_set)
        
        # 基础预处理：转为张量并标准化
        self.transform = transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                 std=[0.229, 0.224, 0.225])
        ])

    def _load_split_file(self, split):
        &quot;&quot;&quot;读取图片ID列表&quot;&quot;&quot;
        split_path = os.path.join(self.root_dir, &apos;ImageSets&apos;, &apos;Segmentation&apos;, f&apos;{split}.txt&apos;)
        with open(split_path, &apos;r&apos;) as f:
            lines = f.readlines()
        return [line.strip() for line in lines]

    def __len__(self):
        return len(self.image_ids)

    def __getitem__(self, idx):
        img_id = self.image_ids[idx]
        
        # 1. 加载图像
        img_path = os.path.join(self.root_dir, &apos;JPEGImages&apos;, f&apos;{img_id}.jpg&apos;)
        image = Image.open(img_path).convert(&apos;RGB&apos;)
        
        # 2. 加载掩码 (Segmentation Mask)
        mask_path = os.path.join(self.root_dir, &apos;SegmentationClass&apos;, f&apos;{img_id}.png&apos;)
        mask = Image.open(mask_path).convert(&apos;P&apos;) # &apos;P&apos; 表示调色板模式
        
        # 3. 统一调整大小 (Resize)
        # 使用 BILINEAR 插值调整图像，NEAREST 邻近插值调整掩码
        image = transforms.functional.resize(image, self.image_size, 
                                            interpolation=transforms.InterpolationMode.BILINEAR)
        mask = transforms.functional.resize(mask, self.image_size, 
                                           interpolation=transforms.InterpolationMode.NEAREST)
        
        # 4. 转换与归一化
        image = self.transform(image)
        mask = torch.as_tensor(np.array(mask), dtype=torch.long) # 转为 LongTensor
        
        return {&apos;image&apos;: image, &apos;target&apos;: mask, &apos;image_id&apos;: img_id}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到数据集对象后，我们就可以使用 &lt;code&gt;DataLoader&lt;/code&gt; 来创建一个可迭代的数据加载器，支持批处理、打乱数据和多线程加载等功能。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from torch.utils.data import DataLoader

dataset = PascalVOC2012Dataset(root_dir=&apos;VOCdevkit/VOC2012&apos;, image_set=&apos;train&apos;)
dataloader = DataLoader(dataset, batch_size=4, shuffle=True, num_workers=2)
for batch in dataloader:
    images = batch[&apos;image&apos;]  # 形状: [B, C, H, W]
    targets = batch[&apos;target&apos;] # 形状: [B, H, W]
    image_ids = batch[&apos;image_id&apos;]
    # 在这里可以进行训练循环的前向传播等操作
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;训练效果&lt;/h3&gt;
&lt;p&gt;经过训练以后，会发现 UNet 模型在 Pascal VOC 2012 上的表现其实&lt;strong&gt;非常有限&lt;/strong&gt;，mIoU 只能达到 6% 左右。这是因为 UNet 本身是为做细胞图像分割任务而设计的，UNet 提出的时候最初的数据集 &lt;strong&gt;ISBI 2012 细胞追踪挑战赛&lt;/strong&gt; 的图像分辨率较低，且场景并不复杂。&lt;/p&gt;
&lt;p&gt;图像分割本身是一个困难的任务，而 UNet 作为一个基础的分割网络，缺乏足够的语义理解能力来处理 VOC 2012 中复杂的场景和多样的物体类别。为了提升性能，可以使用更强大的骨干网络（如 ResNet101、ResNeXt101）来替换 UNet 中的编码器部分，或者引入注意力机制（如 SE 模块、CBAM 模块）来增强特征表达能力。此外，数据增强、损失函数设计（如 Dice Loss、Focal Loss）以及训练策略（如学习率调度、预训练权重微调）等方面的改进也能显著提升模型在 VOC 2012 上的分割性能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;教学部分的代码可以在 &lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/tree/master/chapter-02-custom-dataset-segmentation/codes&quot;&gt;&lt;code&gt;codes/&lt;/code&gt;&lt;/a&gt; 中找到&lt;/strong&gt;，可以使用下面的指令来训练模型&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python main.py \
  --epochs 40 \                # 训练总轮数：模型将遍历整个数据集 40 次
  --batch-size 2 \             # 批大小：每次迭代处理 2 张图片（显存受限时的常见设置）
  --num-workers 0 \            # 数据加载线程数：0 表示在主线程中加载数据（便于调试，无多进程）
  --base-filters 32 \          # 基础通道数：网络第一层的卷积核数量（后续层通常以此倍增）
  --norm-type gn \             # 归一化类型：使用 Group Normalization (分组归一化)，常用于小 batch size
  --image-size 512 \           # 图像尺寸：输入图片将被缩放或裁剪为 512x512
  --use-dice \                 # 启用 Dice 损失：开启 Dice Loss，常用于分割任务
  --dice-weight 0.2            # Dice 损失权重：总损失 = CE_Loss + Dice_Loss * 0.2
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;练习部分——在 Caltech101 上训练 ResNet、目标检测与小样本语义分割&lt;/h2&gt;
&lt;h3&gt;任务一：在 Caltech101 上训练 ResNet 完成图像分类&lt;/h3&gt;
&lt;h4&gt;背景知识&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;数据集介绍&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Caltech-101&lt;/strong&gt; 是计算机视觉领域最经典且广泛使用的图像分类基准数据集之一，由加州理工学院（Caltech）于 2003 年发布。该数据集包含 &lt;strong&gt;102 个类别&lt;/strong&gt; 的图像，其中 &lt;strong&gt;101个 目标类别&lt;/strong&gt;（如各类动物、车辆、乐器等）和 &lt;strong&gt;1 个背景类别&lt;/strong&gt;。每个目标类别下大约有 40到800张不等的图像，&lt;strong&gt;总计约 9,000 多张图片&lt;/strong&gt;，涵盖了物体在大小、姿态、光照、背景和遮挡等方面的巨大变化，旨在测试算法对复杂真实场景的识别能力。&lt;/p&gt;
&lt;p&gt;由于其数据标注规范、类别丰富且具有挑战性，Caltech-101 长期以来被作为评估图像分类、目标检测及视觉定位（Visual Grounding）等模型性能的“标准试金石”。尽管随着更大数据集（如ImageNet）的出现，其地位有所变化，但它依然是研究小样本学习、细粒度分类和多模态任务中不可或缺的参考基准。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://data.caltech.edu/records/mzrjq-6wc02?spm=5176.28103460.0.0.29b87551uHMDJD&quot;&gt;Caltech101 数据集下载链接&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;模型介绍&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;ResNet 有多种不同参数量大小的模型，可以参考以下表格&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模型名称&lt;/th&gt;
&lt;th&gt;层数&lt;/th&gt;
&lt;th&gt;参数量（百万）&lt;/th&gt;
&lt;th&gt;适用场景&lt;/th&gt;
&lt;th&gt;huggingface 模型&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ResNet-18&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;11.7&lt;/td&gt;
&lt;td&gt;资源受限、小型数据集&lt;/td&gt;
&lt;td&gt;https://huggingface.co/microsoft/resnet-18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ResNet-34&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;21.8&lt;/td&gt;
&lt;td&gt;中等资源、较大数据集&lt;/td&gt;
&lt;td&gt;https://huggingface.co/microsoft/resnet-34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ResNet-50&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;25.6&lt;/td&gt;
&lt;td&gt;大型数据集、需要更强特征提取能力&lt;/td&gt;
&lt;td&gt;https://huggingface.co/microsoft/resnet-50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ResNet-101&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;44.5&lt;/td&gt;
&lt;td&gt;大型数据集、追求更高性能&lt;/td&gt;
&lt;td&gt;https://huggingface.co/microsoft/resnet-101&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ResNet-152&lt;/td&gt;
&lt;td&gt;152&lt;/td&gt;
&lt;td&gt;60.2&lt;/td&gt;
&lt;td&gt;大型数据集、追求极致性能&lt;/td&gt;
&lt;td&gt;https://huggingface.co/microsoft/resnet-152&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;数据增强&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;数据增强是提升模型泛化能力的关键手段，尤其在数据量有限的情况下更显重要。常见的数据增强方法包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;几何变换&lt;/strong&gt;：如随机裁剪、水平翻转、旋转等，可以增加数据的多样性，帮助模型学习到更鲁棒的特征。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;颜色变换&lt;/strong&gt;：如随机调整亮度、对比度、饱和度等，可以模拟不同的光照条件，增强模型对颜色变化的适应能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;噪声添加&lt;/strong&gt;：如高斯噪声、椒盐噪声等，可以提高模型对输入数据中的噪声的鲁棒性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cutout&lt;/strong&gt;：随机遮挡图像的一部分，迫使模型关注图像的其他区域，提升模型的泛化能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mixup&lt;/strong&gt;：将两张图像及其标签进行线性混合，生成新的训练样本，可以有效缓解过拟合问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 PyTorch 中，可以使用 &lt;code&gt;torchvision.transforms&lt;/code&gt; 模块轻松实现这些数据增强方法。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from torchvision import transforms

data_transforms = transforms.Compose([
    transforms.RandomResizedCrop(224),  # 随机裁剪并调整大小
    transforms.RandomHorizontalFlip(),  # 随机水平翻转
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),  # 随机调整颜色
    transforms.Mixup(alpha=0.2),  # Mixup 数据增强
    transforms.RandomErasing(),  # Cutout 数据增强
    transforms.ToTensor(),  
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些数据增强方法可以单独使用，也可以组合使用，根据具体任务和数据集的特点进行选择和调整，以达到最佳的训练效果。&lt;/p&gt;
&lt;h4&gt;要求&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;你需要实现一个自定义的 &lt;code&gt;Dataset&lt;/code&gt; 类来加载 Caltech101 数据集，并使用数据增强的方法扩展训练数据。&lt;/li&gt;
&lt;li&gt;选择某一个参数量的 ResNet 模型，用代码实现它，并在 Caltech101 上进行训练和评估。&lt;/li&gt;
&lt;li&gt;你需要记录训练过程中的损失和准确率，并在验证集上评估模型性能。&lt;/li&gt;
&lt;li&gt;(可选) 对比不同参数量大小的 ResNet 模型在 Caltech101 上的表现，分析模型复杂度与性能之间的关系。&lt;/li&gt;
&lt;li&gt;调用 HuggingFace 上预训练的 ResNet 模型在 Caltech101 训练集上进行微调，在验证集上评估性能，并与自己训练的模型进行性能对比。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;任务二：目标检测任务&lt;/h3&gt;
&lt;h4&gt;背景知识&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;目标检测任务介绍&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;目标检测是计算机视觉中的一项核心任务，它的目标是让机器能够：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;识别&lt;/strong&gt;图像中有哪些物体 (分类)；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定位&lt;/strong&gt;这些物体在图像中的具体位置 (通常用矩形边界框 Bounding Box 表示)。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;给定一个输入图像 $x \in \mathbb{R}^{H \times W \times C}$，其中 $H$、$W$ 和 $C$ 分别表示图像的高度、宽度和通道数。假设总共有 $K$ 个类别。目标检测任务的目标是学习出一个从图像 $x$ 到&lt;strong&gt;边界框序列及其对应类别&lt;/strong&gt;的映射函数：
$$
f_{\text{detect}}: x \mapsto \mathcal{B} = {(b_1, c_1), (b_2, c_2), \dots, (b_N, c_N)}
$$
其中输出 $\mathcal{B}$ 是一个包含 $N$ 个检测结果的集合（$N$ 为不定值，取决于图像中物体的数量），且满足以下条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;边界框定义&lt;/strong&gt;：每个 $b_i \in \mathbb{R}^4$ 表示第 $i$ 个物体的位置坐标。通常采用中心点-宽高形式 $b_i = [x_c, y_c, w, h]$ 或左上角-右下角形式 $b_i = [x_{min}, y_{min}, x_{max}, y_{max}]$，其数值范围需与图像尺寸 $(H, W)$ 对齐。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类别标签&lt;/strong&gt;：每个 $c_i \in {0, 1, \dots, K}$ 表示第 $i$ 个物体的类别索引（通常 $0$ 代表背景类 Background）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非重复性约束&lt;/strong&gt;：对于任意 $i \neq j$，物体 $i$ 和物体 $j$ 在空间上不应存在严重的重叠（即IoU低于阈值），且每个预测框必须对应图像中的真实物体。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/object-detection.png&quot;].src} alt=&quot;目标检测示例&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;目标检测结果示例，摘自 https://zhuanlan.zhihu.com/p/368904941&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;YOLO 系列模型介绍&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;YOLO（You Only Look Once）是计算机视觉领域最具影响力的实时目标检测算法系列。其核心突破在于将检测任务转化为单一的回归问题，摒弃了传统两阶段方法中耗时的“候选区域生成”步骤，仅需一次前向传播即可同时预测物体的位置和类别。这种设计赋予了YOLO极高的推理速度，使其能够轻松达到每秒几十帧甚至上百帧，完美适配自动驾驶、视频监控等对实时性要求严苛的场景，成为连接学术研究与工业落地的桥梁。&lt;/p&gt;
&lt;p&gt;YOLOv3 发布于 2018 年，是该系列中承前启后的经典之作，也是初学者理解目标检测原理的最佳入门版本。它引入了&lt;strong&gt;特征金字塔（FPN）&lt;strong&gt;和&lt;/strong&gt;多尺度预测&lt;/strong&gt;机制，利用 Darknet-53 骨干网络在三个不同分辨率的特征图上分别检测大、中、小物体，显著提升了小目标检测能力。此外，YOLOv3采用了经典的**锚框（Anchor Box）**机制来辅助定位。尽管架构相对现代版本略显陈旧，但其逻辑清晰、结构完整，是学习如何从零搭建神经网络和编写损失函数的理想教材。&lt;/p&gt;
&lt;p&gt;截至 2024 年底，&lt;strong&gt;YOLOv11&lt;/strong&gt;（Ultralytics发布）是目前工业界最主流的最新版本，它在保持轻量级的同时通过改进的注意力机制进一步提升了精度；学术界则已出现引入纯注意力机制的&lt;strong&gt;YOLOv12&lt;/strong&gt;。对于开发者，无需手动搭建模型，直接使用&lt;code&gt;ultralytics&lt;/code&gt;库调用预训练模型只需几行代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from ultralytics import YOLO
# 加载预训练模型 (n=微小, s=小, m=中等)
model = YOLO(&quot;yolov11n.pt&quot;) 
# 直接进行推理
results = model.predict(source=&quot;image.jpg&quot;, save=True)
# 如需微调，只需一行代码
# results = model.train(data=&quot;data.yaml&quot;, epochs=50)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;YOLO v3 从零搭建&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ayooshkathuria/YOLO_v3_tutorial_from_scratch&quot;&gt;ayooshkathuria/YOLO_v3_tutorial_from_scratch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.paperspace.com/how-to-implement-a-yolo-object-detector-in-pytorch/&quot;&gt;How to implement a YOLO (v3) object detector from scratch in PyTorch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jiqizhixin.com/articles/2018-04-23-3&quot;&gt;YOLOv3 目标检测 PyTorch 从零搭建&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;ultralytics&lt;/code&gt; 官方仓库和手册&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ultralytics/ultralytics&quot;&gt;ultralytics/ultralytics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.ultralytics.com/zh/models/yolov11/&quot;&gt;Ultralytics 支持的模型&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;要求&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;思考目标检测任务的损失函数应该是什么样的？交叉熵或者 Dice Loss 还适合这个任务吗？你需要自己设计一个适合目标检测的损失函数，并在训练过程中使用它来优化模型。&lt;/li&gt;
&lt;li&gt;参考从零搭建 YOLOv3 的教程，理解其核心组件，并在自行设计的基于 Pascal VOC 数据集的数据加载器上进行训练，并使用 mAP（mean Average Precision）指标进行评估模型性能。&lt;/li&gt;
&lt;li&gt;直接使用 &lt;code&gt;ultralytics&lt;/code&gt; 库调用预训练的 YOLOv11 模型，在 Pascal VOC 数据集上进行微调，并评估其性能，比较与自己搭建的模型在 mAP 上的差异。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;任务三：理解小样本语义分割任务&lt;/h3&gt;
&lt;h4&gt;背景知识&lt;/h4&gt;
&lt;p&gt;小样本语义分割（Few-Shot Semantic Segmentation）是语义分割任务的扩展，旨在让模型能够在仅有少量标注样本的情况下，学习到新的类别并进行像素级的分割预测。这一任务具有重要的实际意义，因为在许多应用场景中，获取大量标注数据既昂贵又耗时。&lt;/p&gt;
&lt;h4&gt;要求&lt;/h4&gt;
&lt;p&gt;调研小样本语义分割领域的研究发展历程，弄清楚从领域开始到最新的研究成果中，各个方法流派的核心思想和代表性工作，并形成一份调研报告。&lt;/p&gt;
&lt;h2&gt;更多参考资料&lt;/h2&gt;
&lt;p&gt;图像分割与目标检测&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/368904941&quot;&gt;知乎文章 | 【图解AI】什么是语义分割、实例分割、全景分割&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/696216736&quot;&gt;知乎文章 | 图像分割概述 - 语义分割、实例分割、全景分割、一键抠图（FCN, U-Net,Mask R-CNN,UPSNet）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/6994833730&quot;&gt;知乎文章 | 一文读懂计算机视觉「目标检测」的基本原理和主流模型&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;聚类&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.semanticscholar.org/paper/Some-methods-for-classification-and-analysis-of-MacQueen/ac8ab51a86f1a9ae74dd0e4576d1a019f5e654ed&quot;&gt;KMeans 论文 | Some Methods for Classification and Analysis of Multivariate Observations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ieeexplore.ieee.org/document/1000236&quot;&gt;Mean Shift 论文 | Mean shift: a robust approach toward feature space analysis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/679458317&quot;&gt;知乎文章 | 超详细！聚类算法总结及对比！&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;边缘检测&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/59640437&quot;&gt;知乎文章 | 数字图像处理:边缘检测(Edge detection)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ultralytics.com/zh/blog/edge-detection-in-image-processing-explained#29r5SkjK&quot;&gt;理解图像处理中的边缘检测&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ResNet 模型以及扩展&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/1512.03385&quot;&gt;ResNet 原文 | Deep Residual Learning for Image Recognition&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zh.d2l.ai/chapter_convolutional-modern/resnet.html&quot;&gt;动手学深度学习 | 7.6. 残差网络（ResNet）&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://pytorch.ac.cn/hub/pytorch_vision_resnet/&quot;&gt;PyTorch 文档 | ResNet&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/1889079285102380738&quot;&gt;知乎文章 | 何恺明ResNet（残差网络）——彻底改变深度神经网络的训练方式&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/1608.06993&quot;&gt;DenseNet 论文 | Densely Connected Convolutional Networks&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zh-v2.d2l.ai/chapter_convolutional-modern/densenet.html&quot;&gt;动手学深度学习 | 7.7. 稠密连接网络（DenseNet）&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/37189203&quot;&gt;知乎文章 | DenseNet：比ResNet更优的CNN模型&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/1611.05431&quot;&gt;ResNeXt 论文 | Aggregated Residual Transformations for Deep Neural Networks&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://pytorch.org/hub/pytorch_vision_resnext/&quot;&gt;PyTorch 文档 | ResNeXt &lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/32913695&quot;&gt;知乎文章 | 深度学习——分类之ResNeXt&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;转置卷积&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zh.d2l.ai/chapter_computer-vision/transposed-conv.html&quot;&gt;动手学深度学习 | 13.10. 转置卷积&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/549164774&quot;&gt;知乎文章 | 卷积操作总结（二）—— 转置卷积（transposed convolution）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pytorch.ac.cn/docs/stable/generated/torch.nn.ConvTranspose2d.html&quot;&gt;PyTorch 文档 | ConvTranspose2d&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;数据增强&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pytorch.ac.cn/vision/0.22/transforms.html&quot;&gt;PyTorch 文档 | 图像转换和增强&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>SAM 模型代码探幽</title><link>https://adalovelemon.github.io/posts/content/technotes/foundationmodels/sam/sam/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/foundationmodels/sam/sam/</guid><pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Steps, Tabs, TabItem, Timeline, Card, CardList, Collapse, Button, Spoiler, Label } from &quot;astro-pure/user&quot;&lt;/p&gt;
&lt;p&gt;export const images = import.meta.glob(&quot;./assets/*.{png,jpg,jpeg,webp,gif,avif,svg}&quot;, {
eager: true,
import: &quot;default&quot;,
})&lt;/p&gt;
&lt;h2&gt;前序工作&lt;/h2&gt;
&lt;h3&gt;基本介绍&lt;/h3&gt;
&lt;p&gt;SAM (Segment Anything Model) 是 Meta AI 研究院开发的一款强大的图像分割模型，能够在各种图像上进行高效的分割任务。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2304.02643&quot;&gt;SAM 论文&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/facebookresearch/segment-anything&quot;&gt;代码&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Okay, clone 完项目的代码后，在 &lt;a href=&quot;https://github.com/facebookresearch/segment-anything/blob/main/README.md&quot;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; 中可以看到 SAM 的基本结构图。&lt;/p&gt;
&lt;h3&gt;模型结构&lt;/h3&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/sam.png&quot;].src} alt=&quot;SAM Model Diagram&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;图像截取自 SAM 论文&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/facebookresearch/segment-anything/blob/main/README.md&quot;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; 中是这样介绍 SAM 的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Segment Anything Model (SAM) produces high quality object masks from &lt;strong&gt;input prompts&lt;/strong&gt; such as &lt;strong&gt;points&lt;/strong&gt; or &lt;strong&gt;boxes&lt;/strong&gt;, and it can be used to generate masks for all objects in an image. It has been trained on a dataset of 11 million images and 1.1 billion masks, and has strong zero-shot performance on a variety of segmentation tasks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;可以看出 SAM 的厉害之处就在于它可以把多种不同结构的输入 prompt 作为提示用于分割，显然，多结构信息融合是 SAM 最关键的部分，这也是接下来需要重点探秘的内容。&lt;/p&gt;
&lt;h3&gt;配置环境&lt;/h3&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://github.com/facebookresearch/segment-anything/blob/main/README.md&quot;&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; 中的说明，作如下配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda create -n sam python=3.10 -y
conda activate sam

cd segment-anything
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install opencv-python pycocotools matplotlib onnxruntime onnx
pip install -e .
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;预训练模型下载&lt;/h3&gt;
&lt;p&gt;本篇博客的内容不是探索如何训练 SAM 模型，因此不下载数据集&lt;a href=&quot;https://ai.facebook.com/datasets/segment-anything-downloads/&quot;&gt;SA-1B&lt;/a&gt;。模型权重这里选择使用 &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth&quot;&gt;&lt;code&gt;ViT-B SAM model&lt;/code&gt;&lt;/a&gt;，当然也可选择下载其他大小的模型权重。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vit_h&lt;/code&gt;: &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth&quot;&gt;ViT-H SAM model.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vit_l&lt;/code&gt;: &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything/sam_vit_l_0b3195.pth&quot;&gt;ViT-L SAM model.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vit_b&lt;/code&gt;: &lt;a href=&quot;https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth&quot;&gt;ViT-B SAM model.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;官方接口使用&lt;/h2&gt;
&lt;h3&gt;模型加载&lt;/h3&gt;
&lt;p&gt;官方使用 &lt;code&gt;sam_model_registry&lt;/code&gt; 来加载模型，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from segment_anything import sam_model_registry

sam = sam_model_registry[&quot;vit_b&quot;](checkpoint=&quot;path/to/sam_vit_b_01ec64.pth&quot;)
sam.to(device=&quot;cuda&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的 &lt;code&gt;sam_model_registry&lt;/code&gt; 是一个字典，包含了不同大小的 SAM 模型构造函数，可以根据需要选择加载不同大小的模型:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sam_model_registry = {
    &quot;default&quot;: build_sam_vit_h,
    &quot;vit_h&quot;: build_sam_vit_h,
    &quot;vit_l&quot;: build_sam_vit_l,
    &quot;vit_b&quot;: build_sam_vit_b,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如，&lt;code&gt;build_sam_vit_b&lt;/code&gt; 的定义如下，它直接将编码器深度、嵌入维度、注意力头数等参数都限定好了，只需要传入 &lt;code&gt;checkpoint&lt;/code&gt; 就可以了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def build_sam_vit_b(checkpoint=None):
    return _build_sam(
        encoder_embed_dim=768,
        encoder_depth=12,
        encoder_num_heads=12,
        encoder_global_attn_indexes=[2, 5, 8, 11],
        checkpoint=checkpoint,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;tip&quot; title=&quot;模型构造字典&quot;&amp;gt;
如果想打造一个供用户使用的多种 size 的模型接口，可以仿照 &lt;code&gt;sam_model_registry&lt;/code&gt; 的设计，用一个字典来实现。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;p&gt;最后加载出来的模型是一个以 &lt;code&gt;torch.nn.Module&lt;/code&gt; 为基类的 &lt;code&gt;segment_anything.modeling.sam.Sam&lt;/code&gt; 类对象，是一个神经网络模型。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;SamAutomaticMaskGenerator&lt;/code&gt; 类&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SamAutomaticMaskGenerator&lt;/code&gt; 是 SAM 模型的一个包装，用于自动生成图像的分割掩码。它提供了一个简单的接口，可以直接输入图像并获得对应的分割结果。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from segment_anything import SamAutomaticMaskGenerator

# Given an image object - image: np.ndarray
mask_generator = SamAutomaticMaskGenerator(sam)
masks = mask_generator.generate(image)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mask_generator&lt;/code&gt; 生成出来的就是一列表的预测掩码，每个掩码都是一个字典，包含了掩码的二值化结果、掩码的边界框、掩码的面积等信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[
    {
        &quot;segmentation&quot;: np.ndarray,   # 二值化的掩码
        &quot;area&quot;: int,                  # 掩码的面积，包含的像素总数
        &quot;bbox&quot;: [x_min, y_min, width, height],  # 掩码的边界框，COCO 格式
        &quot;predicted_iou&quot;: float,     # 预测的 IoU 分数，模型对自己生成的这个掩码质量的自信度，范围 0~1，有时因为浮点运算导致数值可能超过 1
        &quot;point_coords&quot;: np.ndarray, # 输入点坐标（如果有的话）
        &quot;stability_score&quot;: float,   # 稳定性分数，SAM 会对掩码进行多次扰动（如随机旋转、缩放或添加噪声），然后重新预测；如果多次预测的结果高度一致，则稳定性高
        &quot;crop_box&quot;: [x_min, y_min, x_max, y_max],  # 裁剪框，为了节省显存和加速推理，SAM 在处理大图像时，会将图像切分成多个小块（Crop），分别对每个小块进行推理；用以表明该掩码是在原图的哪个区域被裁剪出来计算得到的
    },
    ...
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过 alpha-blending 的方式，将掩码可视化在原图上&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alpha = 0.5
mask_all = np.zeros_like(img, dtype=np.uint8)
for i, mask in enumerate(masks):
    color_rnd = np.random.randint(0, 255, size=3, dtype=np.uint8)
    mask_all += (mask[&quot;segmentation&quot;][:, :, None] * color_rnd).astype(np.uint8) * 255

foreground = mask_all.astype(np.float32)
background = img.astype(np.float32)
result = cv2.addWeighted(foreground, alpha, background, 1 - alpha, 0)
img = np.clip(result, 0, 255).astype(np.uint8)

Image.fromarray(img).save(&quot;result.png&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/generator_result.png&quot;].src} alt=&quot;Avatar Result&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;我的 Avatar 的分割结果&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;SamPredictor&lt;/code&gt; 类&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SamPredictor&lt;/code&gt; 是 SAM 模型的另一个包装类，提供了更灵活的接口，可以接受不同类型的输入提示（如点、框等）来生成分割掩码。它允许用户根据具体需求进行更细粒度的控制。它的输出不同于 &lt;code&gt;SamAutomaticMaskGenerator&lt;/code&gt;，它返回的是拆开出来的三个变量：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;masks&lt;/code&gt;: 预测的掩码，形状为 &lt;code&gt;(num_masks, height, width)&lt;/code&gt;，每个掩码都是一个二值化的二维数组，表示图像中对应位置是否属于该掩码。当 &lt;code&gt;multimask_output=True&lt;/code&gt; 时，&lt;code&gt;num_masks&lt;/code&gt; 的值为 3，表示每个输入提示会生成三个不同的掩码；当 &lt;code&gt;multimask_output=False&lt;/code&gt; 时，&lt;code&gt;num_masks&lt;/code&gt; 的值为 1，表示每个输入提示只生成一个掩码。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iou_predictions&lt;/code&gt;: 预测的 IoU 分数，形状为 &lt;code&gt;(num_masks,)&lt;/code&gt;，每个分数表示模型对对应掩码质量的自信度。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;low_res_masks&lt;/code&gt;: 低分辨率的掩码，形状为 &lt;code&gt;(num_masks, low_res_height, low_res_width)&lt;/code&gt;，每个掩码是一个较小尺寸的掩码 logits，可以作为下一次预测的 &lt;code&gt;mask_input&lt;/code&gt; 用于迭代细化。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;from segment_anything import SamPredictor

predictor = SamPredictor(sam)
predictor.set_image(img)    # img 是一个 np.ndarray 对象

masks, iou_predictions, low_res_masks = predictor.predict(mask_input=None, point_coords=np.array([[100, 100]]), point_labels=np.array([1]), box=np.array([50, 50, 150, 150]), multimask_output=False)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;代码结构&lt;/h2&gt;
&lt;p&gt;SAM 的核心代码在 &lt;code&gt;segmentation_anything/&lt;/code&gt; 目录下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;segment_anything/
├── __init__.py
├── automatic_mask_generator.py
├── build_sam.py
├── modeling
│   ├── __init__.py
│   ├── common.py
│   ├── image_encoder.py
│   ├── mask_decoder.py
│   ├── prompt_encoder.py
│   ├── sam.py
│   └── transformer.py
├── predictor.py
└── utils
    ├── __init__.py
    ├── amg.py
    ├── onnx.py
    └── transforms.py
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;模型定义&lt;/h3&gt;
&lt;p&gt;SAM 模型的定义在 &lt;code&gt;modeling/sam.py&lt;/code&gt; 中，核心类是 &lt;code&gt;Sam&lt;/code&gt;，它继承自 &lt;code&gt;torch.nn.Module&lt;/code&gt;，包含了图像编码器 &lt;code&gt;image_encoder&lt;/code&gt;、提示编码器 &lt;code&gt;prompt_encoder&lt;/code&gt; 和掩码解码器 &lt;code&gt;mask_decoder&lt;/code&gt; 三个主要组件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Sam(nn.Module):
    mask_threshold: float = 0.0
    image_format: str = &quot;RGB&quot;

    def __init__(
        self,
        image_encoder: ImageEncoderViT,
        prompt_encoder: PromptEncoder,
        mask_decoder: MaskDecoder,
        pixel_mean: List[float] = [123.675, 116.28, 103.53],
        pixel_std: List[float] = [58.395, 57.12, 57.375],
    ) -&amp;gt; None:
        &quot;&quot;&quot;
        SAM predicts object masks from an image and input prompts.

        Arguments:
          image_encoder (ImageEncoderViT): The backbone used to encode the
            image into image embeddings that allow for efficient mask prediction.
          prompt_encoder (PromptEncoder): Encodes various types of input prompts.
          mask_decoder (MaskDecoder): Predicts masks from the image embeddings
            and encoded prompts.
          pixel_mean (list(float)): Mean values for normalizing pixels in the input image.
          pixel_std (list(float)): Std values for normalizing pixels in the input image.
        &quot;&quot;&quot;
        super().__init__()
        self.image_encoder = image_encoder
        self.prompt_encoder = prompt_encoder
        self.mask_decoder = mask_decoder
        self.register_buffer(&quot;pixel_mean&quot;, torch.Tensor(pixel_mean).view(-1, 1, 1), False)
        self.register_buffer(&quot;pixel_std&quot;, torch.Tensor(pixel_std).view(-1, 1, 1), False)

    @property
    def device(self) -&amp;gt; Any:
        return self.pixel_mean.device

    @torch.no_grad()
    def forward(
        self,
        batched_input: List[Dict[str, Any]],
        multimask_output: bool,
    ) -&amp;gt; List[Dict[str, torch.Tensor]]:
        &quot;&quot;&quot;
        Predicts masks end-to-end from provided images and prompts.
        If prompts are not known in advance, using SamPredictor is
        recommended over calling the model directly.

        Arguments:
          batched_input (list(dict)): A list over input images, each a
            dictionary with the following keys. A prompt key can be
            excluded if it is not present.
              &apos;image&apos;: The image as a torch tensor in 3xHxW format,
                already transformed for input to the model.
              &apos;original_size&apos;: (tuple(int, int)) The original size of
                the image before transformation, as (H, W).
              &apos;point_coords&apos;: (torch.Tensor) Batched point prompts for
                this image, with shape BxNx2. Already transformed to the
                input frame of the model.
              &apos;point_labels&apos;: (torch.Tensor) Batched labels for point prompts,
                with shape BxN.
              &apos;boxes&apos;: (torch.Tensor) Batched box inputs, with shape Bx4.
                Already transformed to the input frame of the model.
              &apos;mask_inputs&apos;: (torch.Tensor) Batched mask inputs to the model,
                in the form Bx1xHxW.
          multimask_output (bool): Whether the model should predict multiple
            disambiguating masks, or return a single mask.

        Returns:
          (list(dict)): A list over input images, where each element is
            as dictionary with the following keys.
              &apos;masks&apos;: (torch.Tensor) Batched binary mask predictions,
                with shape BxCxHxW, where B is the number of input prompts,
                C is determined by multimask_output, and (H, W) is the
                original size of the image.
              &apos;iou_predictions&apos;: (torch.Tensor) The model&apos;s predictions
                of mask quality, in shape BxC.
              &apos;low_res_logits&apos;: (torch.Tensor) Low resolution logits with
                shape BxCxHxW, where H=W=256. Can be passed as mask input
                to subsequent iterations of prediction.
        &quot;&quot;&quot;
        input_images = torch.stack([self.preprocess(x[&quot;image&quot;]) for x in batched_input], dim=0)
        image_embeddings = self.image_encoder(input_images)

        outputs = []
        for image_record, curr_embedding in zip(batched_input, image_embeddings):
            if &quot;point_coords&quot; in image_record:
                points = (image_record[&quot;point_coords&quot;], image_record[&quot;point_labels&quot;])
            else:
                points = None
            sparse_embeddings, dense_embeddings = self.prompt_encoder(
                points=points,
                boxes=image_record.get(&quot;boxes&quot;, None),
                masks=image_record.get(&quot;mask_inputs&quot;, None),
            )
            low_res_masks, iou_predictions = self.mask_decoder(
                image_embeddings=curr_embedding.unsqueeze(0),
                image_pe=self.prompt_encoder.get_dense_pe(),
                sparse_prompt_embeddings=sparse_embeddings,
                dense_prompt_embeddings=dense_embeddings,
                multimask_output=multimask_output,
            )
            masks = self.postprocess_masks(
                low_res_masks,
                input_size=image_record[&quot;image&quot;].shape[-2:],
                original_size=image_record[&quot;original_size&quot;],
            )
            masks = masks &amp;gt; self.mask_threshold
            outputs.append(
                {
                    &quot;masks&quot;: masks,
                    &quot;iou_predictions&quot;: iou_predictions,
                    &quot;low_res_logits&quot;: low_res_masks,
                }
            )
        return outputs

    def postprocess_masks(
        self,
        masks: torch.Tensor,
        input_size: Tuple[int, ...],
        original_size: Tuple[int, ...],
    ) -&amp;gt; torch.Tensor:
        &quot;&quot;&quot;
        Remove padding and upscale masks to the original image size.

        Arguments:
          masks (torch.Tensor): Batched masks from the mask_decoder,
            in BxCxHxW format.
          input_size (tuple(int, int)): The size of the image input to the
            model, in (H, W) format. Used to remove padding.
          original_size (tuple(int, int)): The original size of the image
            before resizing for input to the model, in (H, W) format.

        Returns:
          (torch.Tensor): Batched masks in BxCxHxW format, where (H, W)
            is given by original_size.
        &quot;&quot;&quot;
        masks = F.interpolate(
            masks,
            (self.image_encoder.img_size, self.image_encoder.img_size),
            mode=&quot;bilinear&quot;,
            align_corners=False,
        )
        masks = masks[..., : input_size[0], : input_size[1]]
        masks = F.interpolate(masks, original_size, mode=&quot;bilinear&quot;, align_corners=False)
        return masks

    def preprocess(self, x: torch.Tensor) -&amp;gt; torch.Tensor:
        &quot;&quot;&quot;Normalize pixel values and pad to a square input.&quot;&quot;&quot;
        # Normalize colors
        x = (x - self.pixel_mean) / self.pixel_std

        # Pad
        h, w = x.shape[-2:]
        padh = self.image_encoder.img_size - h
        padw = self.image_encoder.img_size - w
        x = F.pad(x, (0, padw, 0, padh))
        return x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据代码，在前向传播时，SAM 会首先将输入的图像做一个 Normalize 和填充的预处理，然后通过 &lt;code&gt;image_encoder&lt;/code&gt; 得到图像的特征表示 &lt;code&gt;image_embeddings&lt;/code&gt;，接着对于每张图像，根据输入的提示（点、框、掩码）通过 &lt;code&gt;prompt_encoder&lt;/code&gt; 得到稀疏和密集的提示嵌入，最后将图像特征和提示嵌入一起输入到 &lt;code&gt;mask_decoder&lt;/code&gt; 中，得到低分辨率的掩码 logits 和对应的 IoU 预测分数。最后通过 &lt;code&gt;postprocess_masks&lt;/code&gt; 将低分辨率的掩码进行上采样和去除填充，得到最终的掩码输出。&lt;/p&gt;
&lt;h3&gt;图像编码器&lt;/h3&gt;
&lt;p&gt;图像编码器在 &lt;code&gt;modeling/image_encoder.py&lt;/code&gt; 中定义，核心类是 &lt;code&gt;ImageEncoderViT&lt;/code&gt;，它基于 Vision Transformer (ViT) 架构实现。它将输入图像切分成固定大小的块（patches），并通过多个 Transformer 编码器层来提取图像的特征表示。编码器的输出是一个包含了图像全局信息的特征 tokens 向量序列，可以用于后续的掩码解码过程。&lt;/p&gt;
&lt;h3&gt;提示编码器&lt;/h3&gt;
&lt;p&gt;提示编码器位于 &lt;code&gt;modeling/prompt_encoder.py&lt;/code&gt; 中，核心类是 &lt;code&gt;PromptEncoder&lt;/code&gt;，它负责将输入的提示（如点、框、掩码）编码成适合与图像特征融合的嵌入表示。提示编码器包含了多个子模块来处理不同类型的提示输入，例如点提示会被编码成稀疏的嵌入，而框和掩码提示则会被编码成密集的嵌入。这些嵌入将被送入掩码解码器中，与图像特征进行融合，以生成最终的分割掩码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class PromptEncoder(nn.Module):
    def __init__(
        self,
        embed_dim: int,
        image_embedding_size: Tuple[int, int],
        input_image_size: Tuple[int, int],
        mask_in_chans: int,
        activation: Type[nn.Module] = nn.GELU,
    ) -&amp;gt; None:
        &quot;&quot;&quot;
        Encodes prompts for input to SAM&apos;s mask decoder.

        Arguments:
          embed_dim (int): The prompts&apos; embedding dimension
          image_embedding_size (tuple(int, int)): The spatial size of the
            image embedding, as (H, W).
          input_image_size (int): The padded size of the image as input
            to the image encoder, as (H, W).
          mask_in_chans (int): The number of hidden channels used for
            encoding input masks.
          activation (nn.Module): The activation to use when encoding
            input masks.
        &quot;&quot;&quot;
        super().__init__()
        self.embed_dim = embed_dim
        self.input_image_size = input_image_size
        self.image_embedding_size = image_embedding_size
        self.pe_layer = PositionEmbeddingRandom(embed_dim // 2)

        self.num_point_embeddings: int = 4  # pos/neg point + 2 box corners
        point_embeddings = [nn.Embedding(1, embed_dim) for i in range(self.num_point_embeddings)]
        self.point_embeddings = nn.ModuleList(point_embeddings)
        self.not_a_point_embed = nn.Embedding(1, embed_dim)

        self.mask_input_size = (4 * image_embedding_size[0], 4 * image_embedding_size[1])
        self.mask_downscaling = nn.Sequential(
            nn.Conv2d(1, mask_in_chans // 4, kernel_size=2, stride=2),
            LayerNorm2d(mask_in_chans // 4),
            activation(),
            nn.Conv2d(mask_in_chans // 4, mask_in_chans, kernel_size=2, stride=2),
            LayerNorm2d(mask_in_chans),
            activation(),
            nn.Conv2d(mask_in_chans, embed_dim, kernel_size=1),
        )
        self.no_mask_embed = nn.Embedding(1, embed_dim)

    def get_dense_pe(self) -&amp;gt; torch.Tensor:
        &quot;&quot;&quot;
        Returns the positional encoding used to encode point prompts,
        applied to a dense set of points the shape of the image encoding.

        Returns:
          torch.Tensor: Positional encoding with shape
            1x(embed_dim)x(embedding_h)x(embedding_w)
        &quot;&quot;&quot;
        return self.pe_layer(self.image_embedding_size).unsqueeze(0)

    def _embed_points(
        self,
        points: torch.Tensor,
        labels: torch.Tensor,
        pad: bool,
    ) -&amp;gt; torch.Tensor:
        &quot;&quot;&quot;Embeds point prompts.&quot;&quot;&quot;
        points = points + 0.5  # Shift to center of pixel
        if pad:
            padding_point = torch.zeros((points.shape[0], 1, 2), device=points.device)
            padding_label = -torch.ones((labels.shape[0], 1), device=labels.device)
            points = torch.cat([points, padding_point], dim=1)
            labels = torch.cat([labels, padding_label], dim=1)
        point_embedding = self.pe_layer.forward_with_coords(points, self.input_image_size)
        point_embedding[labels == -1] = 0.0
        point_embedding[labels == -1] += self.not_a_point_embed.weight
        point_embedding[labels == 0] += self.point_embeddings[0].weight
        point_embedding[labels == 1] += self.point_embeddings[1].weight
        return point_embedding

    def _embed_boxes(self, boxes: torch.Tensor) -&amp;gt; torch.Tensor:
        &quot;&quot;&quot;Embeds box prompts.&quot;&quot;&quot;
        boxes = boxes + 0.5  # Shift to center of pixel
        coords = boxes.reshape(-1, 2, 2)
        corner_embedding = self.pe_layer.forward_with_coords(coords, self.input_image_size)
        corner_embedding[:, 0, :] += self.point_embeddings[2].weight
        corner_embedding[:, 1, :] += self.point_embeddings[3].weight
        return corner_embedding

    def _embed_masks(self, masks: torch.Tensor) -&amp;gt; torch.Tensor:
        &quot;&quot;&quot;Embeds mask inputs.&quot;&quot;&quot;
        mask_embedding = self.mask_downscaling(masks)
        return mask_embedding

    def _get_batch_size(
        self,
        points: Optional[Tuple[torch.Tensor, torch.Tensor]],
        boxes: Optional[torch.Tensor],
        masks: Optional[torch.Tensor],
    ) -&amp;gt; int:
        &quot;&quot;&quot;
        Gets the batch size of the output given the batch size of the input prompts.
        &quot;&quot;&quot;
        if points is not None:
            return points[0].shape[0]
        elif boxes is not None:
            return boxes.shape[0]
        elif masks is not None:
            return masks.shape[0]
        else:
            return 1

    def _get_device(self) -&amp;gt; torch.device:
        return self.point_embeddings[0].weight.device

    def forward(
        self,
        points: Optional[Tuple[torch.Tensor, torch.Tensor]],
        boxes: Optional[torch.Tensor],
        masks: Optional[torch.Tensor],
    ) -&amp;gt; Tuple[torch.Tensor, torch.Tensor]:
        &quot;&quot;&quot;
        Embeds different types of prompts, returning both sparse and dense
        embeddings.

        Arguments:
          points (tuple(torch.Tensor, torch.Tensor) or none): point coordinates
            and labels to embed.
          boxes (torch.Tensor or none): boxes to embed
          masks (torch.Tensor or none): masks to embed

        Returns:
          torch.Tensor: sparse embeddings for the points and boxes, with shape
            BxNx(embed_dim), where N is determined by the number of input points
            and boxes.
          torch.Tensor: dense embeddings for the masks, in the shape
            Bx(embed_dim)x(embed_H)x(embed_W)
        &quot;&quot;&quot;
        bs = self._get_batch_size(points, boxes, masks)
        sparse_embeddings = torch.empty((bs, 0, self.embed_dim), device=self._get_device())
        if points is not None:
            coords, labels = points
            point_embeddings = self._embed_points(coords, labels, pad=(boxes is None))
            sparse_embeddings = torch.cat([sparse_embeddings, point_embeddings], dim=1)
        if boxes is not None:
            box_embeddings = self._embed_boxes(boxes)
            sparse_embeddings = torch.cat([sparse_embeddings, box_embeddings], dim=1)

        if masks is not None:
            dense_embeddings = self._embed_masks(masks)
        else:
            dense_embeddings = self.no_mask_embed.weight.reshape(1, -1, 1, 1).expand(
                bs, -1, self.image_embedding_size[0], self.image_embedding_size[1]
            )

        return sparse_embeddings, dense_embeddings
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;稀疏嵌入来源于点提示和框提示，而密集嵌入则来源于掩码提示。对于不同类型的提示输入，SAM 采用了不同的编码方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于点提示输入，&lt;code&gt;_embed_points&lt;/code&gt; 方法会首先将点坐标进行一个 0.5 的偏移，使其位于像素中心，然后通过位置编码器 &lt;code&gt;pe_layer&lt;/code&gt; 将点坐标转换成嵌入表示。根据点的标签（正点、负点或无效点），会分别添加不同的嵌入向量来区分不同类型的点提示。&lt;/li&gt;
&lt;li&gt;对于框提示输入，&lt;code&gt;_embed_boxes&lt;/code&gt; 方法会将框的坐标进行类似的偏移处理，然后通过位置编码器将框的四个角的坐标转换成嵌入表示。每个角的嵌入会添加不同的向量来区分框提示。&lt;/li&gt;
&lt;li&gt;当同时存在点提示和框提示时，点提示会被填充（pad）到与框提示相同的数量，以确保稀疏嵌入的维度一致。&lt;/li&gt;
&lt;li&gt;对于掩码提示输入，&lt;code&gt;_embed_masks&lt;/code&gt; 方法会将输入的掩码通过一个卷积神经网络进行下采样和特征提取，得到一个密集的嵌入表示。&lt;/li&gt;
&lt;li&gt;当不存在掩码提示时，&lt;code&gt;dense_embeddings&lt;/code&gt; 会被设置为一个固定的嵌入向量，表示没有掩码提示的情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;掩码解码器&lt;/h3&gt;
&lt;p&gt;掩码解码器在 &lt;code&gt;modeling/mask_decoder.py&lt;/code&gt; 中定义，核心类是 &lt;code&gt;MaskDecoder&lt;/code&gt;，它负责将图像特征和提示嵌入融合起来，生成最终的分割掩码。掩码解码器包含了多个 Transformer 解码器层，通过自注意力机制和交叉注意力机制来融合图像特征和提示嵌入，从而生成高质量的分割掩码。解码器的输出是一个低分辨率的掩码 logits，可以通过后续的上采样操作得到最终的掩码输出。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MaskDecoder(nn.Module):
    def __init__(
        self,
        *,
        transformer_dim: int,
        transformer: nn.Module,
        num_multimask_outputs: int = 3,
        activation: Type[nn.Module] = nn.GELU,
        iou_head_depth: int = 3,
        iou_head_hidden_dim: int = 256,
    ) -&amp;gt; None:
        &quot;&quot;&quot;
        Predicts masks given an image and prompt embeddings, using a
        transformer architecture.

        Arguments:
          transformer_dim (int): the channel dimension of the transformer
          transformer (nn.Module): the transformer used to predict masks
          num_multimask_outputs (int): the number of masks to predict
            when disambiguating masks
          activation (nn.Module): the type of activation to use when
            upscaling masks
          iou_head_depth (int): the depth of the MLP used to predict
            mask quality
          iou_head_hidden_dim (int): the hidden dimension of the MLP
            used to predict mask quality
        &quot;&quot;&quot;
        super().__init__()
        self.transformer_dim = transformer_dim
        self.transformer = transformer

        self.num_multimask_outputs = num_multimask_outputs

        self.iou_token = nn.Embedding(1, transformer_dim)
        self.num_mask_tokens = num_multimask_outputs + 1
        self.mask_tokens = nn.Embedding(self.num_mask_tokens, transformer_dim)

        self.output_upscaling = nn.Sequential(
            nn.ConvTranspose2d(transformer_dim, transformer_dim // 4, kernel_size=2, stride=2),
            LayerNorm2d(transformer_dim // 4),
            activation(),
            nn.ConvTranspose2d(transformer_dim // 4, transformer_dim // 8, kernel_size=2, stride=2),
            activation(),
        )
        self.output_hypernetworks_mlps = nn.ModuleList(
            [
                MLP(transformer_dim, transformer_dim, transformer_dim // 8, 3)
                for i in range(self.num_mask_tokens)
            ]
        )

        self.iou_prediction_head = MLP(
            transformer_dim, iou_head_hidden_dim, self.num_mask_tokens, iou_head_depth
        )

    def forward(
        self,
        image_embeddings: torch.Tensor,
        image_pe: torch.Tensor,
        sparse_prompt_embeddings: torch.Tensor,
        dense_prompt_embeddings: torch.Tensor,
        multimask_output: bool,
    ) -&amp;gt; Tuple[torch.Tensor, torch.Tensor]:
        &quot;&quot;&quot;
        Predict masks given image and prompt embeddings.

        Arguments:
          image_embeddings (torch.Tensor): the embeddings from the image encoder
          image_pe (torch.Tensor): positional encoding with the shape of image_embeddings
          sparse_prompt_embeddings (torch.Tensor): the embeddings of the points and boxes
          dense_prompt_embeddings (torch.Tensor): the embeddings of the mask inputs
          multimask_output (bool): Whether to return multiple masks or a single
            mask.

        Returns:
          torch.Tensor: batched predicted masks
          torch.Tensor: batched predictions of mask quality
        &quot;&quot;&quot;
        masks, iou_pred = self.predict_masks(
            image_embeddings=image_embeddings,
            image_pe=image_pe,
            sparse_prompt_embeddings=sparse_prompt_embeddings,
            dense_prompt_embeddings=dense_prompt_embeddings,
        )

        # Select the correct mask or masks for output
        if multimask_output:
            mask_slice = slice(1, None)
        else:
            mask_slice = slice(0, 1)
        masks = masks[:, mask_slice, :, :]
        iou_pred = iou_pred[:, mask_slice]

        # Prepare output
        return masks, iou_pred

    def predict_masks(
        self,
        image_embeddings: torch.Tensor,
        image_pe: torch.Tensor,
        sparse_prompt_embeddings: torch.Tensor,
        dense_prompt_embeddings: torch.Tensor,
    ) -&amp;gt; Tuple[torch.Tensor, torch.Tensor]:
        &quot;&quot;&quot;Predicts masks. See &apos;forward&apos; for more details.&quot;&quot;&quot;
        # Concatenate output tokens
        output_tokens = torch.cat([self.iou_token.weight, self.mask_tokens.weight], dim=0)
        output_tokens = output_tokens.unsqueeze(0).expand(sparse_prompt_embeddings.size(0), -1, -1)
        tokens = torch.cat((output_tokens, sparse_prompt_embeddings), dim=1)

        # Expand per-image data in batch direction to be per-mask
        src = torch.repeat_interleave(image_embeddings, tokens.shape[0], dim=0)
        src = src + dense_prompt_embeddings
        pos_src = torch.repeat_interleave(image_pe, tokens.shape[0], dim=0)
        b, c, h, w = src.shape

        # Run the transformer
        hs, src = self.transformer(src, pos_src, tokens)
        iou_token_out = hs[:, 0, :]
        mask_tokens_out = hs[:, 1 : (1 + self.num_mask_tokens), :]

        # Upscale mask embeddings and predict masks using the mask tokens
        src = src.transpose(1, 2).view(b, c, h, w)
        upscaled_embedding = self.output_upscaling(src)
        hyper_in_list: List[torch.Tensor] = []
        for i in range(self.num_mask_tokens):
            hyper_in_list.append(self.output_hypernetworks_mlps[i](mask_tokens_out[:, i, :]))
        hyper_in = torch.stack(hyper_in_list, dim=1)
        b, c, h, w = upscaled_embedding.shape
        masks = (hyper_in @ upscaled_embedding.view(b, c, h * w)).view(b, -1, h, w)

        # Generate mask quality predictions
        iou_pred = self.iou_prediction_head(iou_token_out)

        return masks, iou_pred
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将提示嵌入和图像嵌入融合的最关键的一步是交叉注意力机制&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hs, src = self.transformer(src, pos_src, tokens)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，&lt;code&gt;src&lt;/code&gt; 是图像特征加上密集提示嵌入的结果，作为交叉注意力的 K、V 来源，&lt;code&gt;pos_src&lt;/code&gt; 是图像特征的位置信息，&lt;code&gt;tokens&lt;/code&gt; 是稀疏提示嵌入和iou tokens、mask tokens 的拼接，是 Q 的来源。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;SAM 模型的设计非常巧妙，通过将图像编码器、提示编码器和掩码解码器三个模块进行有效的组合，实现了对多种输入提示的高效融合，从而能够在各种图像上进行高质量的分割任务。&lt;/p&gt;
</content:encoded></item><item><title>Linux 学习笔记 3 —— 核心命令与系统管理</title><link>https://adalovelemon.github.io/posts/content/technotes/linux/linux3/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/linux/linux3/</guid><pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Steps, Tabs, TabItem, Timeline, Card, CardList, Collapse, Button, Spoiler, Label } from &quot;astro-pure/user&quot;&lt;/p&gt;
&lt;p&gt;Linux系统的一个核心设计哲学是&quot;一切皆文件&quot;，这一理念在设备管理上体现得淋漓尽致。本章将带你深入了解Linux如何将硬件设备抽象为文件，以及如何与这些设备进行高效、安全的交互。&lt;/p&gt;
&lt;h2&gt;3.1 设备文件&lt;/h2&gt;
&lt;p&gt;Linux内核将硬件设备抽象为特殊的文件，这些文件通常位于&lt;code&gt;/dev&lt;/code&gt;目录下，被称为&lt;strong&gt;设备节点&lt;/strong&gt;或&lt;strong&gt;设备文件&lt;/strong&gt;。这种设计使得用户空间程序可以通过标准的文件操作（如&lt;code&gt;open()&lt;/code&gt;、&lt;code&gt;read()&lt;/code&gt;、&lt;code&gt;write()&lt;/code&gt;、&lt;code&gt;close()&lt;/code&gt;）来与硬件设备进行交互，大大简化了设备编程的复杂性。&lt;/p&gt;
&lt;h3&gt;3.1.1 设备文件类型&lt;/h3&gt;
&lt;p&gt;当你使用&lt;code&gt;ls -l&lt;/code&gt;命令查看&lt;code&gt;/dev&lt;/code&gt;目录时，会发现文件名前的第一个字符标识了设备的类型：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls -l /dev | head -20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;常见的设备文件类型包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;b&lt;/code&gt; - 块设备 (Block device)&lt;/strong&gt;：以固定大小的数据块（通常是512字节或4KB）进行读写，支持随机访问。硬盘、SSD、USB存储设备都属于块设备。例如&lt;code&gt;/dev/sda&lt;/code&gt;、&lt;code&gt;/dev/nvme0n1&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;c&lt;/code&gt; - 字符设备 (Character device)&lt;/strong&gt;：处理字符流，通常不支持随机访问，数据按顺序读写。键盘、鼠标、串口、终端都属于字符设备。例如&lt;code&gt;/dev/tty1&lt;/code&gt;、&lt;code&gt;/dev/input/mouse0&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;p&lt;/code&gt; - 管道设备 (Pipe device)&lt;/strong&gt;：用于进程间通信（IPC），数据单向流动。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;s&lt;/code&gt; - 套接字设备 (Socket device)&lt;/strong&gt;：用于网络通信或特定进程间通信。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.1.2 主次设备号&lt;/h3&gt;
&lt;p&gt;在&lt;code&gt;ls -l&lt;/code&gt;的输出中，除了文件类型标识符，第5列和第6列的数字分别代表&lt;strong&gt;主设备号&lt;/strong&gt;（Major number）和&lt;strong&gt;次设备号&lt;/strong&gt;（Minor number）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls -l /dev/sda
# brw-rw---- 1 root disk 8, 0 Jan 15 10:30 /dev/sda
#                        ↑  ↑
#                      主设备号 次设备号
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;主设备号&lt;/strong&gt;：标识设备对应的驱动程序。相同类型的设备通常共享同一个主设备号。例如，所有SCSI磁盘的主设备号都是8。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;次设备号&lt;/strong&gt;：标识具体的设备实例。例如，&lt;code&gt;/dev/sda&lt;/code&gt;的次设备号是0，&lt;code&gt;/dev/sdb&lt;/code&gt;的次设备号是16。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了&lt;code&gt;ls -l&lt;/code&gt;，你还可以直接查看&lt;code&gt;sysfs&lt;/code&gt;中的设备号文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /sys/block/sda/dev
# 输出: 8:0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.1.3 并非所有设备都有文件&lt;/h3&gt;
&lt;p&gt;值得注意的是，并非所有硬件设备都有对应的&lt;code&gt;/dev&lt;/code&gt;文件。例如，网络接口通常没有设备文件，而是通过套接字（sockets）API与内核交互。这种设计反映了不同设备的访问模式差异。&lt;/p&gt;
&lt;h2&gt;3.2 sysfs 设备路径&lt;/h2&gt;
&lt;p&gt;虽然&lt;code&gt;/dev&lt;/code&gt;目录提供了一种简单直观的设备访问方式，但设备名（如&lt;code&gt;/dev/sda&lt;/code&gt;）可能在系统重启或硬件配置改变后发生变化。为了提供一个基于硬件属性的、稳定的设备视图，Linux内核引入了&lt;code&gt;sysfs&lt;/code&gt;文件系统。&lt;/p&gt;
&lt;h3&gt;3.2.1 sysfs 的作用&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sysfs&lt;/code&gt;是一个虚拟文件系统，通常挂载在&lt;code&gt;/sys&lt;/code&gt;目录下。它将内核中的设备、驱动、总线等对象以文件和目录的形式暴露给用户空间，形成了一个层次化的设备树。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sysfs&lt;/code&gt;中的文件和目录主要供程序读取，虽然某些文件可以写入（用于配置设备），但&lt;strong&gt;不要随意手动修改&lt;/strong&gt;，除非你完全清楚自己在做什么。错误的修改可能导致系统不稳定。&lt;/p&gt;
&lt;h3&gt;3.2.2 sysfs 路径结构&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sysfs&lt;/code&gt;的路径详细描述了设备的物理连接关系。例如，一个SATA硬盘&lt;code&gt;/dev/sda&lt;/code&gt;在&lt;code&gt;sysfs&lt;/code&gt;中的完整路径可能是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/sys/devices/pci0000:00/0000:00:17.0/ata3/host0/target0:0:0/0:0:0:0/block/sda
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个路径告诉我们：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设备连接在PCI总线的&lt;code&gt;0000:00:17.0&lt;/code&gt;控制器上&lt;/li&gt;
&lt;li&gt;通过ATA协议连接&lt;/li&gt;
&lt;li&gt;是第3个ATA主机上的第1个目标设备&lt;/li&gt;
&lt;li&gt;最终在&lt;code&gt;block&lt;/code&gt;子系统下表现为&lt;code&gt;sda&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2.3 使用 udevadm 查询设备信息&lt;/h3&gt;
&lt;p&gt;当在&lt;code&gt;/dev&lt;/code&gt;中难以找到设备的&lt;code&gt;sysfs&lt;/code&gt;位置时，&lt;code&gt;udevadm&lt;/code&gt;命令是一个强大的工具：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看设备的所有属性
udevadm info --query=all --name=/dev/sda

# 查看设备的sysfs路径
udevadm info --query=path --name=/dev/sda

# 以树状结构显示设备属性
udevadm info --attribute-walk --name=/dev/sda
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.3 dd 命令与设备&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dd&lt;/code&gt;（data duplicator）是一个功能强大的命令行工具，用于在文件和设备之间复制数据。它在处理块设备和字符设备时特别有用。&lt;/p&gt;
&lt;h3&gt;3.3.1 基本语法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;dd if=input_file of=output_file [options]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3.2 常用选项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;if&lt;/code&gt;：指定输入文件（默认为标准输入）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;of&lt;/code&gt;：指定输出文件（默认为标准输出）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bs&lt;/code&gt;：指定读写块的大小（例如&lt;code&gt;bs=1024&lt;/code&gt;或&lt;code&gt;bs=1M&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;count&lt;/code&gt;：指定要复制的块的总数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;skip&lt;/code&gt;：在复制前跳过输入文件的指定块数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;seek&lt;/code&gt;：在写入前跳过输出文件的指定块数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3.3 实用示例&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dd&lt;/code&gt;命令非常强大，但也非常危险。&lt;strong&gt;错误的参数可能导致数据永久丢失&lt;/strong&gt;。在执行任何&lt;code&gt;dd&lt;/code&gt;命令前，请务必仔细检查&lt;code&gt;if&lt;/code&gt;和&lt;code&gt;of&lt;/code&gt;参数，确保它们指向正确的设备。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;创建空白文件：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 创建一个100MB的空白文件
dd if=/dev/zero of=testfile bs=1M count=100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;备份MBR（主引导记录）：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 备份第一块磁盘的前512字节（MBR）
dd if=/dev/sda of=mbr_backup.img bs=512 count=1

# 恢复MBR
dd if=mbr_backup.img of=/dev/sda bs=512 count=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;创建可启动USB：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 将ISO镜像写入USB设备（假设USB设备是/dev/sdb）
dd if=ubuntu.iso of=/dev/sdb bs=4M status=progress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;测试磁盘读写速度：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 测试写入速度
dd if=/dev/zero of=testfile bs=1G count=1 oflag=direct

# 测试读取速度
dd if=testfile of=/dev/null bs=1G iflag=direct
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.4 常见设备命名汇总&lt;/h2&gt;
&lt;p&gt;在Linux系统中，不同类型的设备遵循不同的命名规则。了解这些规则对于系统管理和故障排查至关重要。&lt;/p&gt;
&lt;h3&gt;3.4.1 硬盘：&lt;code&gt;/dev/sd*&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;现代Linux系统中的大多数硬盘（包括SATA、SCSI、USB存储）都使用&lt;code&gt;sd&lt;/code&gt;前缀（SCSI Disk）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;命名规则：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/dev/sda&lt;/code&gt;：第一块磁盘&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/dev/sdb&lt;/code&gt;：第二块磁盘&lt;/li&gt;
&lt;li&gt;分区命名：&lt;code&gt;/dev/sda1&lt;/code&gt;、&lt;code&gt;/dev/sda2&lt;/code&gt;等&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;设备名变动问题：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设备名的分配顺序取决于内核加载驱动的顺序。如果硬件配置改变（如拔掉一个USB设备），设备名可能会发生变化，导致&lt;code&gt;/etc/fstab&lt;/code&gt;挂载失败。&lt;/p&gt;
&lt;h3&gt;3.4.2 虚拟磁盘：&lt;code&gt;/dev/xvd*&lt;/code&gt;, &lt;code&gt;/dev/vd*&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;在虚拟机环境中，磁盘设备通常使用不同的命名约定：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Xen虚拟化&lt;/strong&gt;：&lt;code&gt;/dev/xvda&lt;/code&gt;, &lt;code&gt;/dev/xvdb&lt;/code&gt;等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KVM/QEMU&lt;/strong&gt;：&lt;code&gt;/dev/vda&lt;/code&gt;, &lt;code&gt;/dev/vdb&lt;/code&gt;等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS EC2&lt;/strong&gt;：&lt;code&gt;/dev/xvda&lt;/code&gt;, &lt;code&gt;/dev/xvdb&lt;/code&gt;等&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.4.3 非易失性内存：&lt;code&gt;/dev/nvme*&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;NVMe（Non-Volatile Memory Express）是一种高性能的存储协议，专为SSD设计。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;命名规则：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/dev/nvme0n1&lt;/code&gt;：第一个NVMe控制器上的第一个命名空间&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/dev/nvme0n1p1&lt;/code&gt;：第一个NVMe控制器上第一个命名空间的第一个分区&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 查看NVMe设备信息
nvme list
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4.4 设备映射器：&lt;code&gt;/dev/dm-*&lt;/code&gt;, &lt;code&gt;/dev/mapper/*&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;设备映射器（Device Mapper）是Linux内核的一个框架，用于支持逻辑卷管理（LVM）、磁盘加密（LUKS）等高级功能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;命名规则：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/dev/dm-0&lt;/code&gt;, &lt;code&gt;/dev/dm-1&lt;/code&gt;：内核分配的设备映射器设备&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/dev/mapper/vg00-lv_root&lt;/code&gt;：LVM逻辑卷的符号链接&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 查看设备映射器设备
dmsetup ls
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4.5 光驱：&lt;code&gt;/dev/sr*&lt;/code&gt;, &lt;code&gt;/dev/hd*&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;光驱设备的命名取决于接口类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SCSI/SATA光驱&lt;/strong&gt;：&lt;code&gt;/dev/sr0&lt;/code&gt;, &lt;code&gt;/dev/sr1&lt;/code&gt;等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;旧式PATA光驱&lt;/strong&gt;：&lt;code&gt;/dev/hdc&lt;/code&gt;, &lt;code&gt;/dev/hdd&lt;/code&gt;等&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 挂载光盘
mount /dev/sr0 /mnt/cdrom

# 弹出光盘
eject /dev/sr0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4.6 终端：&lt;code&gt;/dev/tty*&lt;/code&gt;, &lt;code&gt;/dev/pts/*&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;终端设备用于字符输入输出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;虚拟控制台&lt;/strong&gt;：&lt;code&gt;/dev/tty1&lt;/code&gt;, &lt;code&gt;/dev/tty2&lt;/code&gt;等（通过&lt;code&gt;ALT&lt;/code&gt;+&lt;code&gt;F1&lt;/code&gt;-&lt;code&gt;F6&lt;/code&gt;切换）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;伪终端&lt;/strong&gt;：&lt;code&gt;/dev/pts/0&lt;/code&gt;, &lt;code&gt;/dev/pts/1&lt;/code&gt;等（用于SSH、终端模拟器）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;当前终端&lt;/strong&gt;：&lt;code&gt;/dev/tty&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 查看当前终端
tty
# 输出: /dev/pts/0

# 向其他终端发送消息
echo &quot;Hello from $(whoami)&quot; &amp;gt; /dev/pts/1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4.7 网络接口&lt;/h3&gt;
&lt;p&gt;网络接口是Linux中一个特例，它们&lt;strong&gt;没有&lt;/strong&gt;对应的&lt;code&gt;/dev&lt;/code&gt;设备文件。这是因为网络数据包的传输不遵循简单的字节流模型，而是通过更复杂的套接字（sockets）API进行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;查看网络接口：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 现代方法
ip link show

# 传统方法（可能需要安装net-tools）
ifconfig
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;接口命名：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传统命名&lt;/strong&gt;：&lt;code&gt;eth0&lt;/code&gt;（以太网）、&lt;code&gt;wlan0&lt;/code&gt;（无线网）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可预测网络接口名&lt;/strong&gt;：&lt;code&gt;enp0s3&lt;/code&gt;（PCI总线上的以太网卡）、&lt;code&gt;wlp2s0&lt;/code&gt;（PCI总线上的无线网卡）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.5 设备名持久化&lt;/h2&gt;
&lt;p&gt;由于内核按发现顺序分配设备名（如&lt;code&gt;sda&lt;/code&gt;, &lt;code&gt;sdb&lt;/code&gt;），硬件配置的微小变化都可能导致设备名错乱，进而破坏依赖于设备名的系统配置（如&lt;code&gt;/etc/fstab&lt;/code&gt;）。为了解决这个问题，Linux提供了两种主要的持久化方案。&lt;/p&gt;
&lt;h3&gt;3.5.1 UUID 和 Label&lt;/h3&gt;
&lt;p&gt;这是最常用的方法。每个文件系统在创建时都会被赋予一个&lt;strong&gt;UUID&lt;/strong&gt;（全局唯一标识符）和一个可选的&lt;strong&gt;Label&lt;/strong&gt;（标签）。无论设备在哪个端口，其UUID和Label都不会改变。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;查找UUID和Label：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用blkid命令（需要root权限）
sudo blkid /dev/sda1

# 输出示例: 
# /dev/sda1: UUID=&quot;a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8&quot; TYPE=&quot;ext4&quot; LABEL=&quot;MyData&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;在 /etc/fstab 中使用：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用UUID挂载
UUID=a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8 /mnt/data ext4 defaults 0 2

# 或者使用Label挂载
LABEL=MyData /mnt/data ext4 defaults 0 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/etc/fstab&lt;/code&gt;中的每一行包含6个字段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设备标识（UUID、LABEL或设备名）&lt;/li&gt;
&lt;li&gt;挂载点&lt;/li&gt;
&lt;li&gt;文件系统类型&lt;/li&gt;
&lt;li&gt;挂载选项&lt;/li&gt;
&lt;li&gt;dump备份标志（0=不备份）&lt;/li&gt;
&lt;li&gt;fsck检查顺序（0=不检查）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3.5.2 udev 规则&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;udev&lt;/code&gt;是Linux的设备管理器，它在内核检测到设备时动态地在&lt;code&gt;/dev&lt;/code&gt;目录下创建和删除设备节点。你可以编写自定义的&lt;code&gt;udev&lt;/code&gt;规则来为特定设备创建固定的符号链接。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;规则文件位置：&lt;/strong&gt; &lt;code&gt;/etc/udev/rules.d/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;规则文件命名：&lt;/strong&gt; 通常以数字开头（如&lt;code&gt;99-custom.rules&lt;/code&gt;），数字决定规则执行的优先级。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;规则示例：&lt;/strong&gt; 为一个特定序列号的USB硬盘创建固定链接&lt;code&gt;/dev/my_usb_disk&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 首先，获取设备的唯一属性
udevadm info --attribute-walk --name=/dev/sdb

# 然后，在/etc/udev/rules.d/99-my-usb.rules中添加规则
SUBSYSTEM==&quot;block&quot;, ATTRS{serial}==&quot;ABC123456789&quot;, SYMLINK+=&quot;my_usb_disk&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重新插拔设备后，&lt;code&gt;/dev/my_usb_disk&lt;/code&gt;就会指向该设备。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;规则文件名必须以&lt;code&gt;.rules&lt;/code&gt;结尾&lt;/li&gt;
&lt;li&gt;规则中的属性值必须完全匹配（区分大小写）&lt;/li&gt;
&lt;li&gt;修改规则后，需要重新加载udev规则：&lt;code&gt;sudo udevadm control --reload-rules&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;测试规则：&lt;code&gt;sudo udevadm test $(udevadm info -q path -n /dev/sdb)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;3.6 内存设备&lt;/h2&gt;
&lt;p&gt;Linux将内存也抽象为设备文件，位于&lt;code&gt;/dev&lt;/code&gt;目录下。这些特殊的设备文件提供了对系统内存和随机数生成器的访问。&lt;/p&gt;
&lt;h3&gt;3.6.1 /dev/mem&lt;/h3&gt;
&lt;p&gt;代表系统的主物理内存。直接读写此设备极其危险，通常只被内核调试工具使用。&lt;/p&gt;
&lt;p&gt;直接读写&lt;code&gt;/dev/mem&lt;/code&gt;可以访问任意物理内存地址，包括内核空间。&lt;strong&gt;错误的操作可能导致系统崩溃或安全漏洞&lt;/strong&gt;。除非你是内核开发者或在进行底层调试，否则不要使用这个设备。&lt;/p&gt;
&lt;h3&gt;3.6.2 /dev/null&lt;/h3&gt;
&lt;p&gt;一个特殊的&quot;黑洞&quot;设备。任何写入它的数据都会被丢弃，任何从它读取的操作都会立即返回EOF（文件结束）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实用示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 丢弃命令的标准输出
some_command &amp;gt; /dev/null

# 丢弃命令的错误输出
some_command 2&amp;gt; /dev/null

# 丢弃命令的所有输出
some_command &amp;amp;&amp;gt; /dev/null

# 作为空输入源
cat /dev/null &amp;gt; logfile  # 清空文件
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.6.3 /dev/zero&lt;/h3&gt;
&lt;p&gt;一个无限的数据源，每次读取都会返回空字节（&lt;code&gt;\0&lt;/code&gt;）。常用于创建指定大小的空白文件或作为数据流的输入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实用示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 创建一个100MB的空白文件
dd if=/dev/zero of=blank_file bs=1M count=100

# 作为压缩测试的输入源
dd if=/dev/zero bs=1M count=100 | gzip &amp;gt; test.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.6.4 /dev/random 和 /dev/urandom&lt;/h3&gt;
&lt;p&gt;提供加密安全的随机数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/dev/random&lt;/code&gt;&lt;/strong&gt;：真随机数生成器，基于环境噪声（如键盘敲击、鼠标移动）收集熵。当熵池耗尽时会阻塞，等待新的熵。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/dev/urandom&lt;/code&gt;&lt;/strong&gt;：伪随机数生成器，使用密码学安全的算法生成随机数。永不阻塞，对于绝大多数应用（包括密钥生成）已足够安全。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;实用示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 生成一个32字节的随机密码（推荐使用urandom）
head -c 32 /dev/urandom | base64

# 生成SSH密钥
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N &quot;$(head -c 32 /dev/urandom | base64 | head -c 20)&quot;

# 安全地擦除磁盘（使用随机数据覆盖）
dd if=/dev/urandom of=/dev/sdb bs=1M status=progress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于现代Linux系统（内核版本≥4.8），&lt;code&gt;/dev/urandom&lt;/code&gt;在系统启动后就已经有足够的熵，&lt;strong&gt;推荐优先使用&lt;code&gt;/dev/urandom&lt;/code&gt;&lt;/strong&gt;。只有在极少数对随机性要求极高的场景（如生成根CA证书）时，才考虑使用&lt;code&gt;/dev/random&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;3.7 显示设备&lt;/h2&gt;
&lt;p&gt;显示设备负责将图形输出到屏幕。Linux内核通过帧缓冲区（framebuffer）设备&lt;code&gt;/dev/fb*&lt;/code&gt;提供对显存的原始访问，但用户空间的应用程序通常通过更高级的图形子系统（如X Window System, Wayland）与之交互。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;查看帧缓冲设备：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls /dev/fb*
# 通常只有一个 /dev/fb0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;获取帧缓冲信息：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看帧缓冲的详细信息
cat /sys/class/graphics/fb0/name
cat /sys/class/graphics/fb0/virtual_size
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.8 CPU 与设备&lt;/h2&gt;
&lt;p&gt;CPU本身也被视为一种设备。&lt;code&gt;/proc/cpuinfo&lt;/code&gt;文件提供了关于系统CPU的详细信息。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;查看CPU信息：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看完整的CPU信息
cat /proc/cpuinfo

# 只查看处理器型号
cat /proc/cpuinfo | grep &quot;model name&quot; | uniq

# 查看CPU核心数
nproc

# 查看CPU架构
uname -m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;实用示例：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 获取CPU核心数（用于并行任务）
CORES=$(nproc)
echo &quot;系统有 $CORES 个CPU核心&quot;

# 根据核心数设置make的并行编译
make -j$((CORES + 1))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/proc/cpuinfo&lt;/code&gt;中的每个processor块代表一个逻辑CPU核心。在超线程CPU上，物理核心数通常是逻辑核心数的一半。你可以通过&lt;code&gt;cpu cores&lt;/code&gt;字段查看每个物理CPU的核心数。&lt;/p&gt;
&lt;h2&gt;3.9 监视设备活动&lt;/h2&gt;
&lt;p&gt;了解设备当前的活动状态对于系统性能调优至关重要。Linux提供了多种工具来监视设备活动。&lt;/p&gt;
&lt;h3&gt;3.9.1 iostat&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;iostat&lt;/code&gt;报告CPU使用率和I/O设备（主要是磁盘）的统计信息。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;安装和使用：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 安装sysstat包（如果未安装）
sudo apt install sysstat  # Debian/Ubuntu
sudo yum install sysstat  # RHEL/CentOS

# 每2秒报告一次磁盘I/O
iostat -x 2

# 只查看磁盘统计
iostat -d 2

# 查看特定设备
iostat -x /dev/sda 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;输出字段解释：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rrqm/s&lt;/code&gt;, &lt;code&gt;wrqm/s&lt;/code&gt;：每秒合并的读/写请求数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r/s&lt;/code&gt;, &lt;code&gt;w/s&lt;/code&gt;：每秒完成的读/写请求数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rkB/s&lt;/code&gt;, &lt;code&gt;wkB/s&lt;/code&gt;：每秒读取/写入的千字节数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;await&lt;/code&gt;：I/O请求的平均等待时间（毫秒）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%util&lt;/code&gt;：设备的利用率（接近100%表示设备饱和）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.9.2 iotop&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;iotop&lt;/code&gt;类似于&lt;code&gt;top&lt;/code&gt;命令，但专门用于监视磁盘I/O。它可以显示哪些进程正在大量读写磁盘。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;安装和使用：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 安装iotop
sudo apt install iotop  # Debian/Ubuntu
sudo yum install iotop  # RHEL/CentOS

# 以root身份运行
sudo iotop

# 只显示有I/O活动的进程
sudo iotop -o

# 按I/O排序
sudo iotop -o -d 2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.9.3 其他实用工具&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;dstat：&lt;/strong&gt; 一个全能的系统资源统计工具，可以同时监视CPU、内存、磁盘、网络等。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 安装dstat
sudo apt install dstat

# 监视所有资源
dstat -tamdng 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;vmstat：&lt;/strong&gt; 报告虚拟内存统计信息，包括进程、内存、分页、块I/O、陷阱和CPU活动。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 每2秒报告一次
vmstat 2

# 只查看块设备活动
vmstat -D
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.10 实战演练&lt;/h2&gt;
&lt;p&gt;理论知识需要通过实践来巩固。以下是一些实用的练习，帮助你更好地理解和掌握本章内容。&lt;/p&gt;
&lt;h3&gt;3.10.1 探索你的设备&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 1. 列出所有块设备
lsblk

# 2. 查看详细的设备信息
sudo blkid

# 3. 探索sysfs中的设备树
ls -la /sys/block/

# 4. 查看特定设备的sysfs属性
cat /sys/block/sda/queue/scheduler
cat /sys/block/sda/device/model
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.10.2 创建和测试设备&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 1. 创建一个100MB的测试文件
dd if=/dev/zero of=test.img bs=1M count=100

# 2. 将测试文件格式化为ext4文件系统
mkfs.ext4 test.img

# 3. 挂载测试文件
sudo mkdir /mnt/test
sudo mount -o loop test.img /mnt/test

# 4. 在挂载点创建一些文件
echo &quot;Hello, World!&quot; &amp;gt; /mnt/test/hello.txt
ls -l /mnt/test/

# 5. 卸载并清理
sudo umount /mnt/test
sudo rm -rf /mnt/test
rm test.img
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.10.3 监控磁盘I/O&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 1. 在一个终端中运行iotop
sudo iotop -o

# 2. 在另一个终端中执行磁盘密集型操作
dd if=/dev/zero of=/tmp/largefile bs=1M count=1000

# 3. 观察iotop中的I/O活动
# 你应该能看到dd进程的I/O速率

# 4. 清理测试文件
rm /tmp/largefile
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.10.4 使用UUID挂载设备&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 1. 查找设备的UUID
sudo blkid /dev/sdb1

# 2. 创建挂载点
sudo mkdir /mnt/data

# 3. 临时使用UUID挂载
sudo mount UUID=&quot;your-uuid-here&quot; /mnt/data

# 4. 永久配置（需要root权限）
# 编辑 /etc/fstab，添加一行：
# UUID=your-uuid-here /mnt/data ext4 defaults 0 2

# 5. 测试fstab配置
sudo mount -a
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;在虚拟机或测试环境中练习&lt;/strong&gt;，避免误操作影响生产系统&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;备份重要数据&lt;/strong&gt;，特别是在使用&lt;code&gt;dd&lt;/code&gt;命令时&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;仔细检查设备名&lt;/strong&gt;，确保操作的是正确的设备&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用sudo时要格外小心&lt;/strong&gt;，确认命令的每个参数&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>从零开始学 AI - 第一章：图像分类</title><link>https://adalovelemon.github.io/posts/content/coursenotes/learn-ai-from-scratch/chapter1/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/learn-ai-from-scratch/chapter1/</guid><pubDate>Mon, 30 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;export const images = import.meta.glob(&quot;./assets/*.{png,jpg,jpeg,webp,gif,avif,svg}&quot;, {
eager: true,
import: &quot;default&quot;,
})&lt;/p&gt;
&lt;h2&gt;图像分类任务的范式&lt;/h2&gt;
&lt;p&gt;图像分类是计算机视觉中的基本任务之一，目标是将输入的图像分配到预定义的类别中。给定一个图像 $x \in \mathbb{R}^{H \times W \times C}$，其中 $H$、$W$ 和 $C$ 分别表示图像的高度、宽度和通道数，图像分类的目标是学习出一个图像到类别的映射
$$
f_{\text{classify}}: \mathbb{R}^{H \times W \times C} \rightarrow {1, 2, ..., K}
$$
其中 $K$ 是类别的数量，数字 $i$ 表示第 $i$ 个类别。这个映射可以是人工设计的特征提取器 (feature extractor) 加上数据驱动的 (data-driven) 机器学习分类器，也可以是端到端 (end-to-end) 训练的深度神经网络。&lt;/p&gt;
&lt;p&gt;在图像分类的任务中，类别标签的名称对分类结果并无实质影响，影响分类结果的核心实际上是各类别所表征的概念及其在数据空间中的分布形态。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;例如，在一个包含猫、狗和鸟的图像分类任务中，为了方便计算机的计算，通常会给每个类别名称字符串分配一个数字标签，例如类别 1 可能代表猫，类别 2 代表狗，类别 3 代表鸟，用名称字符串还是用数字标签，分类器模型本质上都是一样的。毕竟，建立一个从数字到名称的字典，或者从名称到数字的字典，两种模式下学习到的分类器模型就一模一样了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为了衡量图像分类模型的性能，我们通常使用准确率 (accuracy) 作为评估指标。准确率定义为模型输出的正确分类的样本数占输入的总样本数的比例
$$
\text{Accuracy} = \frac{\text{Number of Correct Predictions}}{\text{Total Number of Predictions}}
$$
例如，在一个只有 A, B 两个类别的分类任务中，如果一个模型在 100 张测试图像中，模型预测的结果如下表所示&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;真实标签\模型预测&lt;/th&gt;
&lt;th&gt;预测为 A 类别&lt;/th&gt;
&lt;th&gt;预测为 B 类别&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;真实为 A 类别&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;真实为 B 类别&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;那么模型的准确率可以计算为
$$
\text{Accuracy} = \frac{45 + 40}{100} = 0.85
$$&lt;/p&gt;
&lt;h2&gt;使用 conda 配置虚拟环境&lt;/h2&gt;
&lt;p&gt;Anaconda 是一个流行的 Python 发行版，提供了包管理和环境管理功能。使用 Anaconda 可以方便地创建和管理虚拟环境，避免不同项目之间的依赖冲突。&lt;/p&gt;
&lt;h3&gt;安装 Miniconda&lt;/h3&gt;
&lt;p&gt;Miniconda 是 Anaconda 的一个轻量级版本，只包含最基本的包管理功能，适合需要自定义环境的用户。对于中国大陆的用户，可以从&lt;a href=&quot;https://s3.jcloud.sjtu.edu.cn/899a892efef34b1b944a19981040f55b-oss01/anaconda/miniconda/mirror_clone_list.html&quot;&gt;上海交大镜像站&lt;/a&gt;选择适合自己操作系统的 Miniconda 安装包进行下载和安装。&lt;/p&gt;
&lt;p&gt;下面以 Windows 操作系统，x86_64 架构为例，介绍如何安装 Miniconda。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先访问镜像网站，使用 cltr + f 搜索 &quot;miniconda3-latest&quot;。&lt;code&gt;Miniconda3-latest-*&lt;/code&gt; 表示的是最新的 Miniconda 发布版本。这里需要区分 Miniconda 和 Miniconda3，Miniconda3 表示的是基于 Python3 开发的包管理器，Miniconda 则是基于 Python2 开发的。
&amp;lt;img src={images[&quot;./assets/miniconda.png&quot;].src} alt=&quot;Miniconda&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;Miniconda 下载页面示例&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;点击 &lt;code&gt;Miniconda3-latest-Windows-x86_64.exe&lt;/code&gt; 进行下载，下载完成后运行安装程序，按照提示完成安装。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;完成安装后，为了方便在终端中使用 conda 命令，需要将 conda 文件所在路径添加到系统环境变量 &lt;code&gt;&quot;PATH&quot;&lt;/code&gt; 中，这个可以在 Windows 系统的环境变量设置中完成。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;打开命令提示符 (cmd)，输入 &lt;code&gt;conda --version&lt;/code&gt; 来验证 conda 是否安装成功。如果显示 conda 的版本号，说明安装成功。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;创建虚拟环境&lt;/h3&gt;
&lt;p&gt;使用 conda 创建一个新的虚拟环境，可以使用以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda create -n myenv python=3.12
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据照提示输入 &lt;code&gt;y&lt;/code&gt; 来确认创建环境。创建完成后，可以使用以下命令来激活环境：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda activate myenv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;激活环境后，命令提示符会显示当前环境的名称，例如 &lt;code&gt;(myenv) C:\Users\Username&amp;gt;&lt;/code&gt;，这表示当前已经进入了 &lt;code&gt;myenv&lt;/code&gt; 环境。&lt;/p&gt;
&lt;p&gt;更多关于 conda 的使用，可以参考官方文档 &lt;a href=&quot;https://docs.conda.io/projects/conda/en/latest/index.html&quot;&gt;Conda Documentation&lt;/a&gt; 或我的博客 &lt;a href=&quot;https://adalovelemon.github.io/blog/posts/content/technotes/cmd-lines/cmd-lines/#conda--pip-%E7%B3%BB%E5%88%97&quot;&gt;常用计算机指令/conda &amp;amp; pip 系列&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;查看 NVIDIA GPU 信息与 CUDA 版本&lt;/h3&gt;
&lt;p&gt;在 Windows 系统中，可以使用以下命令来查看 NVIDIA GPU 的信息和 CUDA 版本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nvidia-smi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个命令会显示当前系统中安装的 NVIDIA GPU 的型号、驱动版本、CUDA 版本以及 GPU 的使用情况等信息。如果命令提示显示 &lt;code&gt;nvidia-smi&lt;/code&gt; 不是内部或外部命令，说明系统中没有安装 NVIDIA GPU 驱动或者环境变量没有正确配置。&lt;/p&gt;
&lt;p&gt;使用 NVIDIA GPU 训练深度学习模型比使用 CPU 更快，因为 GPU 具有更高的并行计算能力，能够同时处理大量的计算任务。对于图像分类等计算密集型任务，使用 GPU 可以显著缩短训练时间，提高模型的迭代效率。&lt;/p&gt;
&lt;h3&gt;安装常见的 Python 包&lt;/h3&gt;
&lt;p&gt;在激活的虚拟环境中，可以使用 conda 或 pip 来安装所需的 Python 包。例如，可以用 conda 安装 PyTorch：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda install pytorch torchvision torchaudio cudatoolkit=12.1 -c pytorch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个命令会安装 torch、torchvision、torchaudio 以及与 CUDA 12.1 兼容的 CUDA 工具包。根据自己的系统和 CUDA 版本，可以调整命令中的 CUDA 版本号。&lt;/p&gt;
&lt;p&gt;当然也可以使用 pip 来安装 PyTorch，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 安装适配 CUDA 12.1 版本的最新 PyTorch 版本
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

# 如果你的电脑没有 NVIDIA GPU，可以安装最新的 CPU 版本的 PyTorch
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过通常官方的 pip 和 conda 源在中国大陆访问速度较慢，可以更换为国内的镜像源，例如阿里云的 PyPI 镜像源，下载速度会快很多：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install numpy matplotlib scikit-learn -i https://pypi.aliyun.com/simple
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里建议是使用 pytorch 官方的源来安装 cuda 版本的 PyTorch，因为非官方源可能不提供适合当前 Python 版本的、适合当前 CUDA 版本的、适合当前操作系统的 PyTorch 版本。&lt;/p&gt;
&lt;p&gt;为了检测 PyTorch 是否安装成功以及是否能够使用 GPU，可以创建 Python 脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 保存为 test_pytorch.py
import torch

print(f&quot;PyTorch version: {torch.__version__}&quot;)
print(f&quot;CUDA available: {torch.cuda.is_available()}&quot;)
if torch.cuda.is_available():
    print(f&quot;CUDA version: {torch.version.cuda}&quot;)
    print(f&quot;GPU device: {torch.cuda.get_device_name(0)}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并在终端中，在 &lt;code&gt;test_pytorch.py&lt;/code&gt; 所在目录下输入以下命令来运行它&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python test_pytorch.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果显示和下面类似的内容，说明 PyTorch 安装成功并且能够使用 GPU 进行计算了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PyTorch version: 2.5.1+cu121
CUDA available: True
CUDA version: 12.1
GPU device: NVIDIA GeForce RTX 4060 Laptop GPU
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果安装的是 CPU 版本的 PyTorch，或者系统中没有 NVIDIA GPU，那么输出会显示 PyTorch 安装好了但是 CUDA 不可用，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PyTorch version: 2.8.0+cpu
CUDA available: False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成以上步骤后，就成功配置好了 PyTorch 的开发环境，可以开始进行图像分类的实验了。&lt;/p&gt;
&lt;h2&gt;使用 PyTorch 构建神经网络并训练手写数字分类模型&lt;/h2&gt;
&lt;p&gt;安装了这么久，现在该来介绍这些包的作用都是什么了。在深度学习与数据科学生态中，NumPy 是构建高效数值计算的基石，提供了多维数组操作的核心能力；PyTorch (torch) 则是基于 NumPy 理念演进的现代深度学习框架，通过动态计算图和自动微分机制，让神经网络的设计与 GPU 加速训练变得灵活而高效；Matplotlib 作为强大的可视化库，能将枯燥的数据转化为直观的图表，帮助开发者洞察模型训练过程与结果分布；而 Scikit-learn 则填补了传统机器学习算法的空白，提供了一套简洁统一的接口来处理分类、回归及聚类任务，四者相辅相成，共同构成了从数据处理、模型构建到结果分析的一站式开发闭环。这四个包是后续章节中构建神经网络、训练模型、评估性能以及可视化结果的基础工具，掌握它们的使用是进行深度学习实验的第一步。&lt;/p&gt;
&lt;h3&gt;全连接网络&lt;/h3&gt;
&lt;p&gt;全连接神经网络，也叫多层感知机模型 (Multi-Layer Perceptron, MLP)，是最基础的神经网络结构，每一层的神经元都与下一层的所有神经元相连。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/MLP.png&quot;].src} alt=&quot;MLP结构&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;全连接神经网络的结构示例&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;在 PyTorch 中，可以使用 &lt;code&gt;nn.Linear&lt;/code&gt; 和激活函数来构建网络层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nn.Linear(in_features, out_features)&lt;/code&gt;：线性变换层&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nn.ReLU()&lt;/code&gt;：修正线性单元激活函数，引入非线性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nn.Softmax(dim)&lt;/code&gt;：Softmax 激活函数，用于把模型输出的实数域 $\mathbb{R}$ 内的置信度分数转换为概率分布，&lt;code&gt;dim&lt;/code&gt; 参数指定了进行 Softmax 计算的维度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全连接神经网络的可学习参数存在于每一层的权重矩阵和偏置向量，这些参数通过训练数据进行优化，以使模型能够更好地拟合数据分布并进行准确的分类。可以用&lt;code&gt;nn.Linear.weight&lt;/code&gt; 和 &lt;code&gt;nn.Linear.bias&lt;/code&gt; 参数来访问每一层的权重和偏置参数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一个简单的 MLP 实现&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
from torch import nn

class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        # 定义网络层
        self.fc1 = nn.Linear(28 * 28, 128)  # 输入层 (784) -&amp;gt; 第一隐藏层 (128)
        self.fc2 = nn.Linear(128, 64)     # 第一隐藏层 (128) -&amp;gt; 第二隐藏层 (64)
        self.fc3 = nn.Linear(64, 10)      # 第二隐藏层 (64) -&amp;gt; 输出层 (10)
        self.relu = nn.ReLU()             # 激活函数

    def forward(self, x):
        # 定义数据前向传播的路径
        x = x.view(-1, 28 * 28)   # 将输入图片展平为向量
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        x = self.fc3(x)         # 输出层这里不需要加 Softmax，因为后续的损失函数内部（如 CrossEntropyLoss）会自动应用 Softmax
        return x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个网络包含了一个输入层、两个隐藏层和一个输出层，适用于处理长度和宽度均为 28 的灰度图像（如 MNIST 数据集）。输入层将 28×28 的图像展平为一个 784 维的向量，经过两层全连接和 ReLU 激活函数后，输出一个长度为 10 的向量，表示对 10 个类别的预测分数。&lt;/p&gt;
&lt;h3&gt;MNIST 数据集简介&lt;/h3&gt;
&lt;p&gt;MNIST 是一个手写数字数据集，包含 60,000 个训练样本和 10,000 个测试样本，每张图片大小为 28×28 像素。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;训练代码实现&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
import torch.optim as optim
from torchvision import datasets, transforms

# 数据预处理
transform = transforms.Compose([
    transforms.ToTensor(),  # 将 PIL 图像或 NumPy 数组转换为 PyTorch 张量，并将像素值归一化到 [0, 1] 范围
    transforms.Normalize((0.1307,), (0.3081,))  # 对 MNIST 数据集进行标准化处理，均值为 0.1307，标准差为 0.3081
])

# 加载数据
train_dataset = datasets.MNIST(&apos;./data&apos;, train=True, download=True, transform=transform)    # 使用 MNIST 数据集的训练部分，下载数据并应用预处理
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)  # 创建数据加载器，其中 batch_size 是每个批次的样本数量，shuffle=True 表示在每个 epoch 结束后打乱数据顺序

# 模型、损失函数、优化器
model = SimpleNet()                 # 实例化模型
criterion = nn.CrossEntropyLoss()   # 交叉熵损失函数适用于多分类问题
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)    # 随机梯度下降优化器

# 训练循环 - 10 个 epoch
for epoch in range(10):
    total_num, correct_num = 0, 0
    for batch_idx, (data, target) in enumerate(train_loader):   # 遍历训练数据
        optimizer.zero_grad()   # 清空梯度
        output = model(data)    # 前向传播计算输出
        loss = criterion(output, target)    # 计算损失
        loss.backward()         # 反向传播计算梯度
        optimizer.step()        # 优化器更新模型参数
    
        with torch.no_grad():   # 在评估模型性能时不需要计算梯度
            total_num += target.size(0)   # 累加总样本数
            correct_num += (output.argmax(dim=1) == target).sum().item()   # 累加正确分类的样本数

    acc = correct_num / total_num   # 计算当前批次的准确率
    print(f&apos;Epoch {epoch+1}, Loss: {loss.item():.4f}, Accuracy: {acc:.4f}&apos;)   # 打印每个 epoch 的损失和准确率
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;训练完成后，SimpleNet 模型在 MNIST 数据集上的准确率大致在 0.97 左右，这表明模型已经能够较好地学习到手写数字的特征并进行分类了。&lt;/p&gt;
&lt;h3&gt;张量形状（Shape）的重要性&lt;/h3&gt;
&lt;p&gt;在神经网络前向传播中，每一步的张量形状必须匹配，否则会出现 &lt;code&gt;RuntimeError: size mismatch&lt;/code&gt; 错误。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常见错误排查方式&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;查看张量形状&lt;/strong&gt;：&lt;code&gt;print(x.shape)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;检查层输入输出维度&lt;/strong&gt;：确保 &lt;code&gt;nn.Linear&lt;/code&gt; 的 &lt;code&gt;in_features&lt;/code&gt; 与上一层的输出匹配&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;展平操作&lt;/strong&gt;：全连接层需要一维输入，使用 &lt;code&gt;x.view(-1, 28*28)&lt;/code&gt; 将图像展平&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;调试示例&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 在 forward 方法中添加调试打印
def forward(self, x):
    print(f&quot;Input shape: {x.shape}&quot;)  # 应为 [batch_size, 1, 28, 28]
    x = x.view(-1, 28*28)
    print(f&quot;After view shape: {x.shape}&quot;)  # 应为 [batch_size, 784]
    x = self.relu(self.fc1(x))
    print(f&quot;After fc1 shape: {x.shape}&quot;)  # 应为 [batch_size, 128]
    x = self.relu(self.fc2(x))
    print(f&quot;After fc2 shape: {x.shape}&quot;)  # 应为 [batch_size, 64]
    x = self.fc3(x)
    print(f&quot;After fc3 shape: {x.shape}&quot;)  # 应为 [batch_size, 10]
    return x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;教学部分的代码可以在 &lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/blob/master/chapter-01-pytorch-intro/codes/example.py&quot;&gt;&lt;code&gt;example.py&lt;/code&gt;&lt;/a&gt; 中找到&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;损失函数、反向传播和优化器&lt;/h2&gt;
&lt;p&gt;在上一节的训练循环中，我们使用了三行核心代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;loss = criterion(output, target)   # 计算损失
loss.backward()                    # 反向传播
optimizer.step()                   # 更新参数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这三行代码对应了深度学习训练的三个核心步骤：&lt;strong&gt;评估误差&lt;/strong&gt;、&lt;strong&gt;计算梯度&lt;/strong&gt;、&lt;strong&gt;更新参数&lt;/strong&gt;。下面分别展开讲解。&lt;/p&gt;
&lt;h3&gt;损失函数 (Loss Function)&lt;/h3&gt;
&lt;p&gt;损失函数衡量的是模型预测值与真实标签之间的不一致程度，是一个&lt;strong&gt;标量值&lt;/strong&gt; (可以比较大小)。在构建损失函数时，我们期望当损失最小时，模型的性能应当是最大化的，因此损失函数的构建是有讲究的，不是随意构造的。训练模型的目标就是让损失值尽可能小。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;交叉熵损失函数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于分类问题（如 MNIST 中的 10 个数字），最常用的损失函数是 &lt;strong&gt;交叉熵损失 (Cross-Entropy Loss)&lt;/strong&gt;。交叉熵损失可以衡量模型输出的概率分布与真实标签之间的差异，换句话说，交叉熵取最小值 0 当且仅当模型的预测概率分布完全匹配真实标签的 one‑hot 分布。&lt;/p&gt;
&lt;p&gt;模型最后一层输出的是对每个类别的“得分”（logits），经过 Softmax 函数后变成概率分布 $(\hat{y}_1, \hat{y}&lt;em&gt;2, ..., \hat{y}&lt;em&gt;K)$，其中 $\sum&lt;/em&gt;{k=1}^{K} \hat{y}&lt;em&gt;k = 1$，$\hat{y}&lt;em&gt;i$ 表示模型对第 $i$ 个类别的预测概率。真实标签是一个 one‑hot 向量，例如对于数字 3，$ y = [0,0,0,1,0,...,0] $。交叉熵的计算公式为
$$
\mathcal{L}&lt;/em&gt;{CE} = -\sum&lt;/em&gt;{k=1}^{K} y_k \log(\hat{y}&lt;em&gt;k)
$$
因为只有一个 $ y_k = 1 $，其余为 0，所以上式等价于
$$
\mathcal{L}&lt;/em&gt;{CE} = -\log(\hat{y}&lt;/em&gt;{\text{true}})
$$
即模型对正确类别的预测概率越高，损失越小。例如，若模型对正确类别的预测概率为 0.99，则 $-\log(0.99) \approx 0.01$；若只有 0.1，则损失约为 2.30，惩罚显著增大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在 PyTorch 中的使用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nn.CrossEntropyLoss&lt;/code&gt; 内部自动完成了 Softmax + 负对数似然计算，因此模型的最后一层不需要额外加 Softmax 激活函数，直接输出原始分数（logits）即可。它的输入形状通常为 &lt;code&gt;(batch_size, num_classes)&lt;/code&gt;，目标形状为 &lt;code&gt;(batch_size,)&lt;/code&gt;（包含每个样本的真实类别索引）。&lt;/p&gt;
&lt;h3&gt;反向传播 (Backpropagation)&lt;/h3&gt;
&lt;p&gt;有了损失值，我们需要知道&lt;strong&gt;每个模型参数对损失的贡献程度&lt;/strong&gt;，即损失关于参数的偏导数（梯度）。对于包含数百万参数的深度网络，直接解析求导不可行，数值方法求导数的结果既不精准，也不好求。为了精确求出损失函数对参数的梯度，反向传播算法被提出，利用链式法则高效地计算所有梯度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;链式法则的简单例子&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;假设网络为 $ y = f(g(x)) $，则梯度
$$
\frac{\partial y}{\partial x} = \frac{\partial f}{\partial g} \cdot \frac{\partial g}{\partial x}
$$
在神经网络中，损失 $ L $ 关于第 $ l $ 层参数 $ W^{(l)} $ 的梯度可以表示为
$$
\frac{\partial L}{\partial W^{(l)}} = \frac{\partial L}{\partial \text{out}^{(l)}} \cdot \frac{\partial \text{out}^{(l)}}{\partial W^{(l)}}
$$
其中 $ \text{out}^{(l)} $ 是第 $ l $ 层的输出。反向传播从输出层开始，逐层向前计算梯度，并将梯度值存储在每一层参数的 &lt;code&gt;.grad&lt;/code&gt; 属性中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PyTorch 的自动微分&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;PyTorch 通过构建&lt;strong&gt;计算图&lt;/strong&gt;来记录张量上的所有操作。当在张量上调用 &lt;code&gt;.backward()&lt;/code&gt; 时，它会沿着计算图反向传播，自动计算出所有 &lt;code&gt;requires_grad=True&lt;/code&gt; 的张量的梯度。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x = torch.tensor([1.0, 2.0], requires_grad=True)
y = (x ** 2).sum()
y.backward()
print(x.grad)   # 输出 tensor([2., 4.])，即 dy/dx = 2x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在我们的训练代码中，&lt;code&gt;loss.backward()&lt;/code&gt; 会计算模型中所有可学习参数（&lt;code&gt;fc1.weight&lt;/code&gt;, &lt;code&gt;fc1.bias&lt;/code&gt; 等）的梯度，并累加到各自的 &lt;code&gt;.grad&lt;/code&gt; 属性中。&lt;/p&gt;
&lt;h3&gt;优化器 (Optimizer)&lt;/h3&gt;
&lt;p&gt;优化器根据计算出的梯度来更新模型参数，使得损失值逐渐下降。最基本的更新规则是&lt;strong&gt;随机梯度下降 (Stochastic Gradient Descent, SGD)&lt;/strong&gt;
$$
\theta_{t+1} = \theta_t - \eta \cdot \nabla L(\theta_t)
$$
其中 $ \theta $ 是参数，$ \eta $ 是学习率，$ \nabla L(\theta_t) $ 是当前损失关于参数的梯度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SGD 的改进变体&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在实际训练中，原始 SGD 可能存在收敛慢、震荡等问题，因此出现了多种改进算法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;优化器&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;th&gt;适用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SGD + Momentum&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;引入动量项，加速收敛并减少震荡&lt;/td&gt;
&lt;td&gt;大多数任务的基础选择&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Adam&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;自适应学习率，对超参数较鲁棒，收敛快&lt;/td&gt;
&lt;td&gt;大规模数据/复杂网络，但泛化性有时不如 SGD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AdamW&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;修正了 Adam 的权重衰减实现，更稳定&lt;/td&gt;
&lt;td&gt;当前大参数量的主流模型的首选&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;在之前的 MNIST 示例中，我们使用了 &lt;code&gt;optim.SGD(model.parameters(), lr=0.01, momentum=0.9)&lt;/code&gt;。这里的 &lt;code&gt;momentum=0.9&lt;/code&gt; 表示使用了动量 SGD，可以理解为给参数更新加入了一个“惯性”，帮助模型跨越局部极小点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化器使用流程&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;清零梯度&lt;/strong&gt;：&lt;code&gt;optimizer.zero_grad()&lt;/code&gt;&lt;br /&gt;
因为梯度是累加的，每个 batch 前必须清零，否则会累积上个 batch 的梯度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;反向传播&lt;/strong&gt;：&lt;code&gt;loss.backward()&lt;/code&gt;&lt;br /&gt;
计算当前 batch 下的梯度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数更新&lt;/strong&gt;：&lt;code&gt;optimizer.step()&lt;/code&gt;&lt;br /&gt;
根据优化算法更新参数。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;练习——搭建 LeNet-5 模型并训练 CIFAR-10 数据集&lt;/h2&gt;
&lt;h3&gt;背景知识&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;卷积神经网络&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;卷积神经网络 (Convolutional Neural Network, CNN) 是专为处理图像数据而设计的深度学习架构，其核心灵感源自生物视觉系统对局部信息的感知机制。不同于传统的全连接网络，CNN 利用卷积核 (Filter) 在输入图像上滑动并执行局部加权求和的卷积操作，生成特征图 (Feature Map)，从而高效地提取从边缘、纹理到复杂语义的层次化特征；同时，通过引入池化层 (Pooling Layer) 进行下采样，不仅大幅降低了计算复杂度与参数量，还赋予了模型关键的平移不变性，使其能够忽略物体位置的变化而稳定识别目标。这种“局部连接”、“权值共享”与“层次化特征提取”的结合，让 CNN 成功解决了高维图像数据的特征表示难题，成为计算机视觉领域最经典且强大的基石模型。&lt;/p&gt;
&lt;p&gt;卷积层 (Convolutional Layer) 是卷积神经网络提取特征的核心组件，也就是实现利用卷积核在输入图像上滑动并执行局部加权求和的操作的部分。给定一张图像 $x \in \mathbb{R}^{H \times W \times C}$，和一个卷积核 $w \in \mathbb{R}^{h \times w \times C}$，以及偏置向量 $b \in \mathbb{R}^{C&apos;}$，经过卷积操作之后得到的特征图像的数学化表示为
$$
y = w * x + b \in \mathbb{R}^{H&apos; \times W&apos; \times C&apos;},
$$
$$
y[i] = \sum_{c=1}^{C} \sum_{j=1}^{h} \sum_{k=1}^{w} w[j, k, c] \cdot x[i+j-1, i+k-1, c] + b[i] \quad \text{for } i = 1, 2, ..., C&apos;
$$
其中 $*$ 表示卷积操作，$+$ 操作用到了&lt;a href=&quot;https://docs.pytorch.org/docs/stable/notes/broadcasting.html&quot;&gt;广播机制 (Broadcast)&lt;/a&gt;，$H&apos;$、$W&apos;$ 和 $C&apos;$ 分别表示输出特征图的高度、宽度和通道数。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/conv.png&quot;].src} alt=&quot;Conv 结构&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;卷积计算示例，其中偏置向量被设置为零&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;此外，为了提升提取特征的能力，卷积层通常会使用多个卷积核来提取不同类型的特征，这些卷积核的输出会被堆叠在一起形成多通道的特征图。卷积层还可以通过调整卷积核的大小、步长 (stride) 和填充 (padding) 来控制输出特征图的尺寸和感受野大小，从而更好地适应不同规模和复杂度的图像数据。&lt;/p&gt;
&lt;p&gt;在 PyTorch 中，上述卷积可以用二维卷积模块 &lt;code&gt;nn.Conv2d&lt;/code&gt; 来实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conv_layer = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5, stride=1, padding=0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这表明该卷积层接受 3 通道的输入图像，使用 6 个卷积核 (也就是输出的通道数)，每个卷积核的大小为 5×5，步长为 1，且不使用填充。&lt;/p&gt;
&lt;p&gt;当指定卷积层的输入通道数、输出通道数和卷积核大小后，给定输入图像的尺寸，卷积层的输出特征图的尺寸可以通过以下公式计算：
$$
\begin{aligned}
H&apos; &amp;amp;= \left\lfloor \frac{H + 2P - h}{S} \right\rfloor + 1 \
W&apos; &amp;amp;= \left\lfloor \frac{W + 2P - w}{S} \right\rfloor + 1 \
C&apos; &amp;amp;= \text{输出通道数}
\end{aligned}
$$
其中 $H$、$W$ 和 $C$ 分别表示输入图像的高度、宽度和通道数，$P$ 表示填充大小，$h$ 和 $w$ 表示卷积核的高度和宽度，$S$ 表示步长。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;池化层&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;池化层是卷积神经网络中的另一个重要组件，主要用于对特征图进行下采样，从而减少特征图的尺寸和参数量，同时增强模型的平移不变性。池化层通过在输入特征图上滑动一个固定大小的窗口，并在窗口内执行某种聚合操作（如最大值或平均值）来生成输出特征图。例如，最大池化层 (Max Pooling) 会在每个窗口内取最大值作为输出，而平均池化层 (Average Pooling) 则会计算窗口内所有值的平均值作为输出。池化层的输出尺寸可以通过以下公式计算：
$$
\begin{aligned}
H&apos; &amp;amp;= \left\lfloor \frac{H + 2P - h}{S} \right\rfloor + 1 \
W&apos; &amp;amp;= \left\lfloor \frac{W + 2P - w}{S} \right\rfloor + 1 \
C&apos; &amp;amp;= \text{输出通道数}
\end{aligned}
$$
其中 $H$、$W$ 和 $C$ 分别表示输入特征图的高度、宽度和通道数，$h$ 和 $w$ 表示池化窗口的高度和宽度，$S$ 表示步长，$P$ 表示填充大小。其实计算公式和卷积层的输出尺寸计算公式是一样的，因为池化层本质上也是一种特殊的卷积操作，只不过它的卷积核是固定的，并且没有可学习的参数。一般池化的步长会设置为和池化窗口的大小相同，这样就不会有重叠的窗口，从而实现对特征图的有效下采样。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/pooling.png&quot;].src} alt=&quot;池化计算示例&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;池化计算示例&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LeNet-5 模型结构&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ieeexplore.ieee.org/document/726791&quot;&gt;LeNet-5&lt;/a&gt; 是一个经典的卷积神经网络，由 Yann LeCun 提出，主要用于手写数字识别。这里将这个经典模型稍加修改，使之适配 CIFAR-10 数据集的输入尺寸和通道数。改进后的 LeNet-5 模型结构如图所示。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/LeNet.png&quot;].src} alt=&quot;LeNet-5 结构&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;center&amp;gt;改进版 LeNet-5&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;该模型的具体结构可以用如下 PyTorch 代码来描述：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;卷积层: &lt;code&gt;nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;池化层: &lt;code&gt;nn.MaxPool2d(kernel_size, stride)&lt;/code&gt; 或者 &lt;code&gt;nn.AvgPool2d(kernel_size, stride)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;展平操作: &lt;code&gt;x.view(-1, num_features)&lt;/code&gt; 或者 &lt;code&gt;nn.Flatten()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;CIFAR-10 数据集&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CIFAR-10 数据集包含 10 个类别的彩色图像：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车&lt;/li&gt;
&lt;li&gt;每张图片大小为 32×32 像素，3 个颜色通道&lt;/li&gt;
&lt;li&gt;训练集：50,000 张图像&lt;/li&gt;
&lt;li&gt;测试集：10,000 张图像&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;练习任务&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;任务一：根据网络结构图搭建 LeNet-5 模型&lt;/li&gt;
&lt;li&gt;任务二：加载 CIFAR-10 数据集。这里使用 PyTorch 的 &lt;code&gt;torchvision.datasets.CIFAR10&lt;/code&gt; 加载数据集，并进行适当的数据增强和归一化。&lt;/li&gt;
&lt;li&gt;任务三：训练与评估
&lt;ul&gt;
&lt;li&gt;使用训练集训练 LeNet-5 模型&lt;/li&gt;
&lt;li&gt;在测试集上评估模型性能&lt;/li&gt;
&lt;li&gt;记录训练过程中的损失和准确率&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;练习部分的代码可以在 &lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/blob/master/chapter-01-pytorch-intro/codes/exercise.py&quot;&gt;&lt;code&gt;exercise.py&lt;/code&gt;&lt;/a&gt; 中找到&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;更多参考资料&lt;/h2&gt;
&lt;p&gt;深度学习基础&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://adalovelemon.github.io/blog/posts/content/coursenotes/aisys/chapter1/#%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E6%98%AF%E4%BB%80%E4%B9%88&quot;&gt;AI-Sys 1 深度学习基础&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;图像分类任务&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cs231n.github.io/classification/&quot;&gt;cs231n - Image Classification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cs231n.github.io/linear-classify/&quot;&gt;cs231n - Linear Classification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;卷积部分&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cs231n.github.io/convolutional-networks/&quot;&gt;cs231n - Convolutional Neural Networks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://adalovelemon.github.io/blog/posts/content/coursenotes/aisys/chapter2/#%E5%BC%A0%E9%87%8F%E8%AE%A1%E7%AE%97&quot;&gt;张量计算&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/561991816&quot;&gt;【综述】一文读懂卷积神经网络(CNN)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/272008168/answer/1953397720069960041&quot;&gt;信号与系统里的卷积怎么理解？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;优化器部分&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cs231n.github.io/optimization-1/&quot;&gt;cs231n - Optimization 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cs231n.github.io/optimization-2/&quot;&gt;cs231n - Optimization 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/40378224&quot;&gt;Back Propagation（梯度反向传播）实例讲解&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/580645925&quot;&gt;梯度下降详解（主观理解+推导证明+例题）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;损失函数部分&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/26486223&quot;&gt;信息熵&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/638725320&quot;&gt;交叉熵损失函数（Cross Entropy Loss）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;更多神经网络架构&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://proceedings.neurips.cc/paper_files/paper/2012/file/c399862d3b9d6b76c8436e924a68c45b-Paper.pdf&quot;&gt;AlexNet&lt;/a&gt;, &lt;a href=&quot;https://proceedings.neurips.cc/paper_files/paper/2012/file/c399862d3b9d6b76c8436e924a68c45b-Paper.pdf&quot;&gt;VGGNet&lt;/a&gt;, &lt;a href=&quot;https://arxiv.org/abs/1409.4842&quot;&gt;GoogLeNet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;仓库地址&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/AdaLovelemon/learn-AI-from-scratch/tree/master/chapter-01-pytorch-intro&quot;&gt;AdaLovelemon/learn-AI-from-scratch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Linux 学习笔记 2 —— 核心命令与系统管理</title><link>https://adalovelemon.github.io/posts/content/technotes/linux/linux2/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/linux/linux2/</guid><pubDate>Mon, 23 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Steps, Tabs, TabItem, Timeline, Card, CardList, Collapse, Button, Spoiler, Label } from &quot;astro-pure/user&quot;&lt;/p&gt;
&lt;p&gt;你可能觉得 Linux 命令枯燥，但想象一下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你想批量重命名 1000 张照片，图形界面要点点点半小时，终端只要一行命令。&lt;/li&gt;
&lt;li&gt;服务器出问题了，没有图形界面，你只能靠命令行救命。&lt;/li&gt;
&lt;li&gt;如果你想自动化备份、部署、监控，那么Shell 脚本是运维的基石。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本章将告诉你 Linux 的核心命令和系统管理技巧，让你从“命令小白”变成“命令达人”。&lt;/p&gt;
&lt;h2&gt;2.1 文本处理：&lt;code&gt;grep&lt;/code&gt; 和 &lt;code&gt;less&lt;/code&gt;&lt;/h2&gt;
&lt;h3&gt;2.1.1 🔍 &lt;code&gt;grep&lt;/code&gt;：在茫茫文海中找针&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;grep&lt;/code&gt; 不只是搜索，它是你的“文本雷达”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基础用法&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 在 /etc/passwd 中找包含 &quot;root&quot; 的行
grep root /etc/passwd

# 忽略大小写搜索（-i）
grep -i error /var/log/syslog

# 反向匹配：找不包含 &quot;bash&quot; 的行（-v）
grep -v bash /etc/passwd

# 显示行号（-n）
grep -n &quot;TODO&quot; project.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;正则表达式进阶&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 找邮箱地址（扩展正则 -E 或 egrep）
egrep &apos;[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}&apos; contacts.txt

# 找以 &quot;Error&quot; 开头且以 &quot;failed&quot; 结尾的行
grep &apos;^Error.*failed$&apos; app.log

# 找包含数字的行
grep &apos;[0-9]&apos; data.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;note&quot; title=&quot;实战技巧&quot;&amp;gt;
组合 &lt;code&gt;grep&lt;/code&gt; 和 &lt;code&gt;less&lt;/code&gt; 看大文件
&lt;code&gt;bash     grep -i &quot;critical&quot; /var/log/syslog | less     &lt;/code&gt;
这样你可以一页页翻着看匹配结果，不会刷屏。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h3&gt;2.1.2 📄 &lt;code&gt;less&lt;/code&gt;：分页查看大文件的正确姿势&lt;/h3&gt;
&lt;p&gt;别再用 &lt;code&gt;cat&lt;/code&gt; 看大文件了！&lt;code&gt;less&lt;/code&gt; 让你从容浏览。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常用操作&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;less /var/log/auth.log
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;键盘快捷键&lt;/td&gt;
&lt;td&gt;功能&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;空格/Page Down&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;下一页&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;b/Page Up&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;上一页&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/keyword&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;向前搜索（输完按回车）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;?keyword&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;向后搜索&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;下一个匹配项&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;上一个匹配项&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;退出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;g&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到文件头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;G&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到文件尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;帮助手册&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;caution&quot; title=&quot;注意&quot;&amp;gt;
&lt;code&gt;less&lt;/code&gt; 默认不加载整个文件到内存，所以打开 GB 级日志也不会卡死。但如果你用 &lt;code&gt;cat&lt;/code&gt;，可能会把终端撑爆！
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h3&gt;2.1.3 📊 &lt;code&gt;sort&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;tail&lt;/code&gt;：数据整理组合拳&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;排序 (&lt;code&gt;sort&lt;/code&gt;)&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 字母顺序排序
sort names.txt

# 数值排序（-n），否则 &quot;10&quot; 会排在 &quot;2&quot; 前面！
sort -n scores.txt

# 逆序（-r）
sort -r data.txt

# 去重后排序（-u）
sort -u duplicates.txt

# 按第2列排序（-k2）
sort -k2 students.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;取头尾 (&lt;code&gt;head&lt;/code&gt;, &lt;code&gt;tail&lt;/code&gt;)&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 前10行（默认）
head /etc/passwd

# 前5行
head -n 5 /etc/passwd

# 后10行（默认）
tail /var/log/syslog

# 后5行
tail -n 5 /var/log/syslog

# 从第100行开始到最后
tail -n +100 huge_file.txt

# 实时监控日志（-f），按 Ctrl+C 停止
tail -f /var/log/apache2/access.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;note&quot; title=&quot;组合技&quot;&amp;gt;
找出出现频率最高的10个IP地址：
&lt;code&gt;bash     awk &apos;{print $1}&apos; access.log | sort | uniq -c | sort -nr | head -10     &lt;/code&gt;
解释：&lt;code&gt;awk&lt;/code&gt; 提取第一列（IP）→ &lt;code&gt;sort&lt;/code&gt; 排序 → &lt;code&gt;uniq -c&lt;/code&gt; 计数 → &lt;code&gt;sort -nr&lt;/code&gt; 按数量逆序 → &lt;code&gt;head&lt;/code&gt; 取前10。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h2&gt;2.2 文件查找：&lt;code&gt;find&lt;/code&gt; vs &lt;code&gt;locate&lt;/code&gt;&lt;/h2&gt;
&lt;h3&gt;2.2.1 🔎 &lt;code&gt;find&lt;/code&gt;：实时递归搜索（强大但慢）&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;find&lt;/code&gt; 是瑞士军刀，功能多到爆炸。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基础语法&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find [路径] [条件] [动作]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;常用示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 在当前目录及子目录找所有 .log 文件
find . -name &quot;*.log&quot;

# 找大于100M的文件（-size +100M）
find /home -size +100M

# 找最近7天内修改过的文件（-mtime -7）
find . -mtime -7

# 找用户 john 的所有文件
find /home -user john

# 找可执行文件（-type f -executable）
find /usr/bin -type f -executable

# 找到后删除（小心！先不加 -delete 确认一下）
find ./temp -name &quot;*.tmp&quot; -delete

# 找到后执行命令（-exec）
find . -name &quot;*.jpg&quot; -exec mv {} ~/pictures/ \;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;caution&quot; title=&quot;注意&quot;&amp;gt;
&lt;strong&gt;血泪教训&lt;/strong&gt;：&lt;code&gt;find . -name &quot;*.log&quot; -delete&lt;/code&gt; 会直接删文件！建议先用 &lt;code&gt;find . -name &quot;*.log&quot;&lt;/code&gt; 看看结果，再加 &lt;code&gt;-delete&lt;/code&gt;。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h3&gt;2.2.2 🚀 &lt;code&gt;locate&lt;/code&gt;：基于数据库的快速查找&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;locate&lt;/code&gt; 快如闪电，但数据可能不是最新的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 快速找含 &quot;passwd&quot; 的文件
locate passwd

# 只统计数量（-c）
locate -c nginx

# 忽略大小写（-i）
locate -i ReadMe

# 更新数据库（需要 sudo）
sudo updatedb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;tip&quot; title=&quot;怎么选择？&quot;&amp;gt;
- 刚创建/删除的文件 → 用 &lt;code&gt;find&lt;/code&gt;
- 找已知文件名，追求速度 → 用 &lt;code&gt;locate&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h2&gt;2.3 环境变量与 PATH：让系统认识你的命令&lt;/h2&gt;
&lt;h3&gt;2.3.1 🌍 什么是环境变量？&lt;/h3&gt;
&lt;p&gt;环境变量是 shell 的“全局设置”，比如 &lt;code&gt;$HOME&lt;/code&gt; 指向你的家目录，&lt;code&gt;$USER&lt;/code&gt; 是当前用户名。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;查看与设置&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看所有环境变量
env
# 或
printenv

# 查看单个变量
echo $HOME
echo $PATH

# 设置临时变量（仅当前 shell 有效）
MY_VAR=&quot;hello&quot;
echo $MY_VAR

# 导出为环境变量（子进程也能看到）
export MY_VAR=&quot;hello&quot;
bash  # 启动新 shell
echo $MY_VAR  # 仍能输出 &quot;hello&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;caution&quot; title=&quot;常见错误&quot;&amp;gt;
赋值时不能有空格！
&lt;code&gt;bash     MY_VAR=hello    # ✅ 正确     MY_VAR = hello  # ❌ 错误！shell 会把 &quot;MY_VAR&quot; 当成命令     &lt;/code&gt;
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h3&gt;2.3.2 🛣️ PATH：命令搜索路径&lt;/h3&gt;
&lt;p&gt;当你输入 &lt;code&gt;ls&lt;/code&gt;，shell 怎么知道去哪找它？答案是 &lt;code&gt;$PATH&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看当前 PATH
echo $PATH
# 输出类似：/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

# 临时添加自定义目录到 PATH（优先查找）
export PATH=/my/scripts:$PATH

# 添加到末尾
export PATH=$PATH:/my/scripts

# 永久生效：加到 ~/.bashrc 或 ~/.profile
echo &apos;export PATH=/my/scripts:$PATH&apos; &amp;gt;&amp;gt; ~/.bashrc
source ~/.bashrc  # 立即生效
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;danger&quot; title=&quot;作死警告&quot;&amp;gt;
&lt;code&gt;bash     PATH=/my/scripts  # ❌ 完了！原有路径全丢了，ls, cd 都失效！     &lt;/code&gt;
&lt;strong&gt;补救&lt;/strong&gt;：关闭终端重开，或手动恢复：
&lt;code&gt;bash     export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin     &lt;/code&gt;
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h2&gt;2.4 权限管理：chmod, chown, umask&lt;/h2&gt;
&lt;h3&gt;2.4.1 🔐 读懂 &lt;code&gt;ls -l&lt;/code&gt; 输出&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-rw-r--r-- 1 alice developers 2048 Mar 23 10:00 secret.txt
↑↑↑↑↑↑↑↑↑↑
│││││││││└─ 文件名
││││││││└─── 修改时间
│││││││└──── 文件大小
││││││└───── 所属组 (developers)
│││││└────── 所有者 (alice)
│││└───────── 其他人权限 (r--)
││└────────── 所属组权限 (r--)
│└─────────── 所有者权限 (rw-)
└──────────── 文件类型 (- = 普通文件, d = 目录, l = 链接)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;权限含义&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;r&lt;/code&gt; (read) = 4&lt;/li&gt;
&lt;li&gt;&lt;code&gt;w&lt;/code&gt; (write) = 2&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x&lt;/code&gt; (execute) = 1&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-&lt;/code&gt; = 无权限&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对目录而言，&lt;code&gt;x&lt;/code&gt; 表示“可进入”。&lt;/p&gt;
&lt;h3&gt;2.4.2 🛠️ &lt;code&gt;chmod&lt;/code&gt;：修改权限&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;方法一：数字模式（八进制）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 所有者: rwx(7), 组: r-x(5), 其他人: r-x(5)
chmod 755 script.sh

# 所有者: rw-(6), 组: r--(4), 其他人: r--(4) → 标准文件权限
chmod 644 document.txt

# 只有自己能读写执行
chmod 700 private_key.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;方法二：符号模式（更直观）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 给所有人加执行权限
chmod +x script.sh

# 给所有者加写权限
chmod u+w file.txt

# 移除其他人的读权限
chmod o-r secret.txt

# 给组和所有人加读权限
chmod go+r data.txt

# 递归修改目录及内部所有文件
chmod -R 755 my_folder/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;note&quot; title=&quot;常用权限速查表&quot;&amp;gt;
| 场景             | 命令              | 说明                          |
|--|--|--|
| 可执行脚本       | &lt;code&gt;chmod 755 xxx&lt;/code&gt;   | 自己可改，别人可运行          |
| 普通文档         | &lt;code&gt;chmod 644 xxx&lt;/code&gt;   | 自己可改，别人只读            |
| 私钥/配置文件    | &lt;code&gt;chmod 600 xxx&lt;/code&gt;   | 仅自己可读写                  |
| 私人目录         | &lt;code&gt;chmod 700 xxx&lt;/code&gt;   | 仅自己可进入和操作            |
| 公共共享目录     | &lt;code&gt;chmod 777 xxx&lt;/code&gt;   | ⚠️ 极度危险！任何人可删改     |
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h3&gt;2.4.3 🎭 &lt;code&gt;umask&lt;/code&gt;：控制新建文件的默认权限&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;umask&lt;/code&gt; 是“权限掩码”，决定新建文件时&lt;strong&gt;去掉&lt;/strong&gt;哪些权限。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看当前 umask
umask
# 通常输出 022 或 002

# 设置 umask（影响后续新建文件）
umask 022   # 新建文件默认 644，目录 755
umask 077   # 新建文件默认 600，目录 700（更安全）

# 永久生效：加到 ~/.bashrc
echo &apos;umask 022&apos; &amp;gt;&amp;gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;计算逻辑&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件最大权限 = 666 (rw-rw-rw-)&lt;/li&gt;
&lt;li&gt;目录最大权限 = 777 (rwxrwxrwx)&lt;/li&gt;
&lt;li&gt;实际权限 = 最大权限 - umask&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例：&lt;code&gt;umask 022&lt;/code&gt; → 文件权限 = &lt;code&gt;666 - 022 = 644&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;2.5 进程管理：ps, kill, 作业控制&lt;/h2&gt;
&lt;h3&gt;2.5.1 👁️ &lt;code&gt;ps&lt;/code&gt;：查看进程&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 当前终端的进程
ps

# 所有属于你的进程
ps x

# 所有系统进程（包括别人的）
ps aux

# 详细信息（CPU, 内存）
ps u

# 完整命令行（不换行）
ps w

# 组合拳：最常用
ps aux | grep chrome

# 查看当前 shell 的 PID 和详情
ps u $$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键字段解释&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PID&lt;/code&gt;: 进程ID&lt;/li&gt;
&lt;li&gt;&lt;code&gt;STAT&lt;/code&gt;: 状态（S=睡眠, R=运行, Z=僵尸, T=停止）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%CPU&lt;/code&gt;, &lt;code&gt;%MEM&lt;/code&gt;: 资源占用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;COMMAND&lt;/code&gt;: 启动命令&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.5.2 ☠️ &lt;code&gt;kill&lt;/code&gt;：终止进程&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 礼貌终止（发送 SIGTERM）
kill 1234

# 强制杀死（SIGKILL，慎用！）
kill -9 1234
# 或
kill -KILL 1234

# 暂停进程（冻结）
kill -STOP 1234

# 恢复被暂停的进程
kill -CONT 1234

# 查看所有信号
kill -l
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;caution&quot; title=&quot;何时用 &lt;code&gt;kill -9&lt;/code&gt;?&quot;&amp;gt;
只在进程完全卡死、正常 &lt;code&gt;kill&lt;/code&gt; 无效时使用。否则可能导致数据丢失！
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h3&gt;2.5.3 🎪 作业控制：前台/后台切换&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 运行一个长任务
ping google.com

# 按 Ctrl+Z 挂起（暂停）
# 输出：[1]+  Stopped    ping google.com

# 查看当前 shell 的作业
jobs

# 把作业放到后台继续运行
bg %1

# 把作业拉回前台
fg %1

# 直接在后台启动命令
sleep 100 &amp;amp;
# 输出：[2] 5678
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;tip&quot; title=&quot;实用场景&quot;&amp;gt;
编译大项目时，按 &lt;code&gt;Ctrl+Z&lt;/code&gt; 挂起，先去吃饭，回来再 &lt;code&gt;fg&lt;/code&gt; 继续。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h2&gt;2.6 重定向与管道：Unix 哲学的精髓&lt;/h2&gt;
&lt;h3&gt;2.6.1 🔄 管道 &lt;code&gt;|&lt;/code&gt;：连接命令&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 取前10行，转大写
head /proc/cpuinfo | tr a-z A-Z

# 找 Chrome 进程
ps aux | grep chrome

# 排序 + 去重计数
cat words.txt | sort | uniq -c

# 复杂组合：找日志中出现最多的IP
awk &apos;{print $1}&apos; access.log | sort | uniq -c | sort -nr | head -5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;tip&quot; title=&quot;Unix 哲学&quot;&amp;gt;
每个程序只做一件事，做好它；用管道组合它们。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h3&gt;2.6.2 📥📤 重定向：控制输入输出&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;标准输出 (&lt;code&gt;stdout&lt;/code&gt;, 文件描述符 1)&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 覆盖写入
echo &quot;hello&quot; &amp;gt; file.txt

# 追加写入
echo &quot;world&quot; &amp;gt;&amp;gt; file.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;标准错误 (&lt;code&gt;stderr&lt;/code&gt;, 文件描述符 2)&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 错误单独存到 err.log
ls /nonexistent 2&amp;gt; err.log

# stdout 和 stderr 合并到 all.log
ls /nonexistent &amp;gt; all.log 2&amp;gt;&amp;amp;1
# 或（Bash 4+ 简写）
ls /nonexistent &amp;amp;&amp;gt; all.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;标准输入 (&lt;code&gt;stdin&lt;/code&gt;, 文件描述符 0)&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 从文件读入（较少用，因为命令通常直接接受文件名）
sort &amp;lt; unsorted.txt &amp;gt; sorted.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.7 归档与压缩：tar, gzip, zip&lt;/h2&gt;
&lt;h3&gt;2.7.1 📦 &lt;code&gt;tar&lt;/code&gt;：打包工具&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 创建归档（c=create, v=verbose, f=file）
tar cvf backup.tar file1 dir1/

# 查看内容（t=test/list）
tar tvf backup.tar

# 解压（x=extract）
tar xvf backup.tar

# 打包并 gzip 压缩（z=gzip）
tar czvf backup.tar.gz file1 dir1/

# 解压 .tar.gz
tar xzvf backup.tar.gz

# 打包并 bzip2 压缩（j=bzip2）
tar cjvf backup.tar.bz2 file1/

# 解压 .tar.bz2
tar xjvf backup.tar.bz2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;note&quot; title=&quot;记忆口诀&quot;&amp;gt;
- &lt;code&gt;cvf&lt;/code&gt; = &lt;strong&gt;C&lt;/strong&gt;reate, &lt;strong&gt;V&lt;/strong&gt;erbose, &lt;strong&gt;F&lt;/strong&gt;ile
- &lt;code&gt;tvf&lt;/code&gt; = &lt;strong&gt;T&lt;/strong&gt;est/List, &lt;strong&gt;V&lt;/strong&gt;erbose, &lt;strong&gt;F&lt;/strong&gt;ile
- &lt;code&gt;xvf&lt;/code&gt; = E&lt;strong&gt;x&lt;/strong&gt;tract, &lt;strong&gt;V&lt;/strong&gt;erbose, &lt;strong&gt;F&lt;/strong&gt;ile
- &lt;code&gt;czvf&lt;/code&gt; = &lt;strong&gt;C&lt;/strong&gt;reate, &lt;strong&gt;Z&lt;/strong&gt;ip, &lt;strong&gt;V&lt;/strong&gt;erbose, &lt;strong&gt;F&lt;/strong&gt;ile
- &lt;code&gt;xzvf&lt;/code&gt; = E&lt;strong&gt;x&lt;/strong&gt;tract, Un&lt;strong&gt;z&lt;/strong&gt;ip, &lt;strong&gt;V&lt;/strong&gt;erbose, &lt;strong&gt;F&lt;/strong&gt;ile&lt;/p&gt;
&lt;p&gt;&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h3&gt;2.7.2 🗜️ 其他压缩工具&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# gzip（最常见）
gzip file.txt        # 生成 file.txt.gz
gunzip file.txt.gz
zcat file.txt.gz     # 直接查看，不解压

# bzip2（压缩率更高）
bzip2 file.txt       # 生成 file.txt.bz2
bunzip2 file.txt.bz2

# xz（最高压缩率）
xz file.txt          # 生成 file.txt.xz
unxz file.txt.xz

# zip（Windows 兼容）
zip archive.zip file1 dir1/
unzip archive.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.8 超级用户：sudo 的正确用法&lt;/h2&gt;
&lt;h3&gt;2.8.1 👑 为什么不用 root 登录？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;❌ 无操作审计&lt;/li&gt;
&lt;li&gt;❌ 容易误删系统文件&lt;/li&gt;
&lt;li&gt;❌ 失去正常用户环境&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ &lt;strong&gt;正确做法&lt;/strong&gt;：用 &lt;code&gt;sudo&lt;/code&gt; 临时提权。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 以 root 身份编辑配置文件
sudo vipw

# 重启服务
sudo systemctl restart nginx

# 启动交互式 root shell（谨慎！）
sudo -i

# 以其他用户身份运行命令
sudo -u postgres psql
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.8.2 📜 配置 sudoers（必须用 &lt;code&gt;visudo&lt;/code&gt;！）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 安全编辑 /etc/sudoers
sudo visudo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;示例配置&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 允许用户 alice 免密码执行所有命令
alice ALL=(ALL) NOPASSWD: ALL

# 允许 admins 组执行特定命令
%admins ALL=(ALL) /usr/bin/systemctl, /usr/bin/apt

# 允许 root 模拟任何用户
root ALL=(ALL) ALL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;danger&quot; title=&quot;血泪警告&quot;&amp;gt;
- 绝对不要直接用 &lt;code&gt;vim /etc/sudoers&lt;/code&gt;！语法错误会导致无法使用 &lt;code&gt;sudo&lt;/code&gt;！
- 修改前备份：&lt;code&gt;cp /etc/sudoers /etc/sudoers.bak&lt;/code&gt;
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h2&gt;2.9 Linux 目录结构：FHS 标准速查&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;目录&lt;/th&gt;
&lt;th&gt;用途说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;根目录，一切从这里开始&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/bin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;基本命令（ls, cp, bash 等）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/dev&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;设备文件（硬盘、键盘等）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/etc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;系统配置文件（passwd, fstab, network 等）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/home&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用户家目录（/home/alice, /home/bob）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/lib&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;共享库文件（.so）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/proc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;虚拟文件系统，提供进程和内核信息（如 /proc/cpuinfo）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/root&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;root 用户的家目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/sbin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;系统管理员命令（fdisk, ifconfig 等）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/tmp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;临时文件（重启后可能清空）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/usr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用户程序和数据（类似 Windows 的 Program Files）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/usr/bin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;用户级命令（gcc, python, vim 等）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/usr/local&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;手动安装的软件（避免覆盖系统包）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/var&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;可变数据（日志 /var/log，邮件 /var/mail，缓存 /var/cache）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/run&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;运行时数据（进程 ID、socket 等，重启清空）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/sys&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;系统与设备接口（虚拟文件系统，可调内核参数）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;note&quot; title=&quot;探索命令&quot;&amp;gt;
```bash
# 查看磁盘使用
df -h&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看目录大小
du -sh /var/log/*

# 查看内核版本
uname -r

# 查看当前进程打开的文件
ls /proc/self/fd
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h2&gt;2.10 特殊字符与命令行编辑&lt;/h2&gt;
&lt;p&gt;下面是一些常见的特殊字符和命令行编辑快捷键：&lt;/p&gt;
&lt;h3&gt;2.10.1 🎭 特殊字符速查表&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字符&lt;/th&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;star&lt;/td&gt;
&lt;td&gt;通配符&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;question&lt;/td&gt;
&lt;td&gt;单字符通配&lt;/td&gt;
&lt;td&gt;&lt;code&gt;file?.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`&lt;/td&gt;
&lt;td&gt;`&lt;/td&gt;
&lt;td&gt;pipe&lt;/td&gt;
&lt;td&gt;管道&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;greater&lt;/td&gt;
&lt;td&gt;输出重定向（覆盖）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;echo hi &amp;gt; file&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;append&lt;/td&gt;
&lt;td&gt;输出重定向（追加）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;echo hi &amp;gt;&amp;gt; file&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;less&lt;/td&gt;
&lt;td&gt;输入重定向&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sort &amp;lt; file&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ampersand&lt;/td&gt;
&lt;td&gt;后台运行&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sleep 100 &amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;semicolon&lt;/td&gt;
&lt;td&gt;命令分隔&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cd /tmp; ls&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AND&lt;/td&gt;
&lt;td&gt;前一个成功才执行后一个&lt;/td&gt;
&lt;td&gt;&lt;code&gt;make &amp;amp;&amp;amp; make install&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;`&lt;/td&gt;
&lt;td&gt;OR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;dollar&lt;/td&gt;
&lt;td&gt;变量引用&lt;/td&gt;
&lt;td&gt;&lt;code&gt;echo $HOME&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;`&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;backtick&lt;/td&gt;
&lt;td&gt;命令替换（旧式）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;echo `date`&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;subshell&lt;/td&gt;
&lt;td&gt;命令替换（新式）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;echo $(date)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;single&lt;/td&gt;
&lt;td&gt;字面量（禁止展开）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;echo &apos;$HOME&apos;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;double&lt;/td&gt;
&lt;td&gt;半字面量（允许变量展开）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;echo &quot;$HOME&quot;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;backslash&lt;/td&gt;
&lt;td&gt;转义&lt;/td&gt;
&lt;td&gt;&lt;code&gt;echo \&quot;hello\&quot;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tilde&lt;/td&gt;
&lt;td&gt;家目录快捷方式&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cd ~&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;hash&lt;/td&gt;
&lt;td&gt;注释&lt;/td&gt;
&lt;td&gt;&lt;code&gt;# This is a comment&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2.10.2 ⌨️ 命令行编辑快捷键（Bash Emacs 模式）&lt;/h3&gt;
&lt;p&gt;下面是一些常用的命令行编辑快捷键，能让你在终端里快到飞起来：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;快捷键&lt;/th&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到行首&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到行尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;光标左移（同←）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+F&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;光标右移（同→）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+P&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;上一条命令（同↑）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;下一条命令（同↓）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除前一个单词&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+U&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除从光标到行首&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+K&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除从光标到行尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+Y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;粘贴被删内容（yank）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+L&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;清屏（同 &lt;code&gt;clear&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;反向搜索历史命令&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+G&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;取消搜索&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自动补全&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;退出当前 shell（空输入时）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;终止当前命令&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+Z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;挂起当前命令&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;2.11 获取帮助：man, info, --help&lt;/h2&gt;
&lt;p&gt;可以通过 &lt;code&gt;man&lt;/code&gt; 查看命令的手册页，了解它的用法和选项。&lt;code&gt;info&lt;/code&gt; 提供更详细的文档，适合 GNU 工具。&lt;code&gt;--help&lt;/code&gt; 则给出简短的使用说明。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看命令手册
man ls
man 5 passwd      # 第5节：文件格式
man 2 open        # 第2节：系统调用

# 关键字搜索
man -k copy       # 找含 &quot;copy&quot; 的命令
apropos copy      # 同上

# GNU 风格简短帮助
ls --help
grep --help

# 更详细的 GNU 文档
info coreutils
info grep | less  # 管道给 less 阅读

# 在线文档（很多软件在 /usr/share/doc/）
ls /usr/share/doc/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.12 实战练习题&lt;/h2&gt;
&lt;h3&gt;练习1：权限实验&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 1. 创建一个文件，权限设为只有自己可读写
touch secret.txt
chmod 600 secret.txt
ls -l secret.txt

# 2. 创建一个脚本，让自己和别人都能执行
echo &apos;#!/bin/bash\necho &quot;Hello&quot;&apos; &amp;gt; greet.sh
chmod 755 greet.sh
./greet.sh

# 3. 尝试用别人身份读取 secret.txt（会失败）
sudo -u nobody cat secret.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;练习2：管道组合&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 找出 /var/log 中最大的5个文件
du -ah /var/log | sort -rh | head -5

# 统计当前登录用户数
who | wc -l

# 找最近修改的10个文件
find . -type f -mtime -1 | head -10
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;练习3：归档备份&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 备份家目录到 /backup 文件夹
mkdir -p /backup
tar czvf /backup/home_backup_$(date +%F).tar.gz ~/

# 验证备份
tar tzvf /backup/home_backup_*.tar.gz | head -20
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;练习4：进程管理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 启动一个后台进程
sleep 300 &amp;amp;

# 查看它的 PID
jobs -l

# 挂起它（先 fg 再 Ctrl+Z）
fg %1
# 按 Ctrl+Z

# 恢复后台
bg %1

# 杀死它
kill %1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.13 常见错误与解决方案&lt;/h2&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;caution&quot; title=&quot;常见错误与解决方案&quot;&amp;gt;
| 错误信息                  | 可能原因                     | 解决方案                          |
|--|--|--|
| &lt;code&gt;Permission denied&lt;/code&gt;       | 权限不足                     | 检查 &lt;code&gt;ls -l&lt;/code&gt;，用 &lt;code&gt;chmod&lt;/code&gt; 或 &lt;code&gt;sudo&lt;/code&gt; |
| &lt;code&gt;No such file or directory&lt;/code&gt; | 路径拼错或文件不存在         | 用 &lt;code&gt;Tab&lt;/code&gt; 补全，&lt;code&gt;find&lt;/code&gt; 搜索        |
| &lt;code&gt;Command not found&lt;/code&gt;       | 命令不在 PATH 中或未安装     | 检查 &lt;code&gt;echo $PATH&lt;/code&gt;，安装包         |
| &lt;code&gt;No space left on device&lt;/code&gt; | 磁盘满了                     | &lt;code&gt;df -h&lt;/code&gt; 查看，清理 &lt;code&gt;/tmp&lt;/code&gt; 或日志  |
| &lt;code&gt;Too many open files&lt;/code&gt;     | 文件描述符耗尽               | &lt;code&gt;ulimit -n&lt;/code&gt; 查看，关闭多余文件    |
| &lt;code&gt;Segmentation fault&lt;/code&gt;      | 程序 bug（访问非法内存）     | 更新软件或报告 bug                |
| &lt;code&gt;Read-only file system&lt;/code&gt;   | 文件系统只读（可能硬件故障） | 重启或检查 &lt;code&gt;dmesg&lt;/code&gt;                |
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>Linux 学习笔记 1 —— 系统架构与核心机制</title><link>https://adalovelemon.github.io/posts/content/technotes/linux/linux1/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/linux/linux1/</guid><pubDate>Sun, 22 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside, Steps, Tabs, TabItem, Timeline, Card, CardList, Collapse, Button, Spoiler, Label } from &quot;astro-pure/user&quot;&lt;/p&gt;
&lt;h2&gt;1.1 Linux 系统的三层抽象模型&lt;/h2&gt;
&lt;p&gt;Linux 系统可以分成三个主要层级：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;硬件层 (Hardware)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最底层，包括 处理器 (CPU)、主内存 (RAM) 、磁盘、网络接口等物理设备。这是所有计算的基础。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;内核层 (Kernel)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;中间层，是操作系统的核心。它负责管理硬件资源，并为上层程序提供统一接口。内核运行在“内核模式”，拥有最高权限，能直接访问所有硬件和内存。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用户空间层 (User Space)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最顶层，是我们日常使用的程序所在的地方，比如浏览器、终端、编辑器等。这些程序运行在“用户模式”，权限受限，不能随便碰硬件或别人的数据。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;tip&quot; title=&quot;Linux 系统的结构&quot;&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[User Processes] → Graphical UI, Servers, Shell
      ↓
[Linux Kernel] → System Calls, Process Mgmt, Memory Mgmt, Device Drivers
      ↓
[Hardware] → CPU, RAM, Disks, Network Ports
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h2&gt;1.2 主内存 (Main Memory) ——系统的“工作台”&lt;/h2&gt;
&lt;p&gt;主内存就是电脑里的 RAM，它是所有正在运行的程序和数据的临时存放地。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有进程 (包括内核线程如 &lt;code&gt;kthreadd&lt;/code&gt;、&lt;code&gt;kblockd&lt;/code&gt;) 都住在内存里。&lt;/li&gt;
&lt;li&gt;CPU 不从硬盘读指令，而是从内存里取指令和数据。&lt;/li&gt;
&lt;li&gt;输入输出设备的数据也要先经过内存再传给 CPU。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.3 内核的四大职责&lt;/h2&gt;
&lt;p&gt;内核不是干活的工人，它是调度员 + 安全员 + 翻译官 + 管理员。它的核心任务围绕内存展开，具体分为四个方面：&lt;/p&gt;
&lt;h3&gt;1.3.1 进程管理 (Process Management)&lt;/h3&gt;
&lt;p&gt;进程管理的职责是决定哪个进程能用 CPU、什么时候用、用多久。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多任务是怎么实现的？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;即使只有一个 CPU 核心，也能同时跑多个程序 (比如边看网页边听音乐) ，这靠的是“时间片轮转” + “上下文切换”。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个进程被分配一小段时间 (time slice) ，比如几毫秒。&lt;/li&gt;
&lt;li&gt;时间到了，CPU 中断当前进程，保存它的状态 (寄存器、内存指针等) ，然后加载下一个进程的状态继续执行。&lt;/li&gt;
&lt;li&gt;这个过程叫 &lt;strong&gt;context switch&lt;/strong&gt;，由内核完成。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;✅ 可以在终端里做个尝试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看当前有哪些进程在运行
ps aux | head -n 5

# 观察 CPU 使用情况 (按 q 退出) 
top

# 创建一个后台进程模拟占用 CPU
yes &amp;gt; /dev/null &amp;amp;

# 终端显示
# Hello@OmniLinux:~$ yes &amp;gt; /dev/null &amp;amp;
# [1] 2596
# 就表明成功创建后台进程，且 yes 进程的 PID 是 2596

# 再看 top，你会发现有一个 yes 进程占用了接近 100% 的 CPU
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当你输入 &lt;code&gt;kill 2596&lt;/code&gt; 终止 &lt;code&gt;yes&lt;/code&gt; 时，内核就会做一次 context switch，把 CPU 控制权交还给 shell。&lt;/p&gt;
&lt;h3&gt;1.3.2 内存管理 (Memory Management)&lt;/h3&gt;
&lt;p&gt;内存管理需要确保每个进程都有属于自己的内存区域，互不干扰；此外，它还要支持共享内存、虚拟内存等功能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键规则：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内核有自己的私有内存区，用户进程进不去。&lt;/li&gt;
&lt;li&gt;每个用户进程有独立内存段。&lt;/li&gt;
&lt;li&gt;用户进程之间默认不能互相访问对方内存。&lt;/li&gt;
&lt;li&gt;允许某些内存只读 (比如代码段) 。&lt;/li&gt;
&lt;li&gt;可以用磁盘当“扩展内存” (swap space) ，这就是虚拟内存。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;虚拟内存 &amp;amp; MMU&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;现代 CPU 有个叫 &lt;strong&gt;MMU (Memory Management Unit)&lt;/strong&gt; 的部件，它让每个进程以为自己独占整台机器的内存。&lt;/p&gt;
&lt;p&gt;实际上，MMU 会把进程看到的“虚拟地址”翻译成真实的“物理地址”。这个映射关系保存在一个叫 &lt;strong&gt;页表 (page table)&lt;/strong&gt; 的数据结构里。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看系统总内存和已用内存
free -h

# 查看某个进程的内存使用详情 (替换 &amp;lt;pid&amp;gt; 为真实 PID) 
cat /proc/&amp;lt;pid&amp;gt;/status | grep Vm

# 查看交换分区使用情况
swapon --show

# 手动触发一次 swap 写入 (谨慎操作！) 
echo 3 &amp;gt; /proc/sys/vm/drop_caches
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;caution&quot; title=&quot;警告&quot;&amp;gt;
不要随意修改 &lt;code&gt;/proc/sys/vm/*&lt;/code&gt; 下的参数，除非你想知道后果！
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h3&gt;1.3.3 设备驱动管理 (Device Drivers and Management)&lt;/h3&gt;
&lt;p&gt;硬件千差万别，但内核要给用户提供统一的接口。比如不同品牌的网卡，对用户来说都是“网络设备”，可以通过相同的命令配置。&lt;/p&gt;
&lt;p&gt;设备驱动通常以内核模块形式存在，它们运行在内核态，因为直接操作硬件很危险 (比如误关电源会导致死机) 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 列出当前加载的内核模块
lsmod

# 查看某个模块的信息 (比如 ext4 文件系统驱动) 
modinfo ext4

# 动态加载/卸载模块 (需要 root 权限) 
sudo modprobe usb_storage     # 加载 USB 存储驱动
sudo rmmod usb_storage        # 卸载

# 查看硬件设备列表
lspci         # PCI 设备
lsusb         # USB 设备
lshw          # 更详细的硬件信息 (需安装 lshw) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3.4. 系统调用与支持 (System Calls and Support)&lt;/h3&gt;
&lt;p&gt;用户程序想做事 (比如打开文件、创建进程) ，必须通过“系统调用”请求内核帮忙。&lt;/p&gt;
&lt;p&gt;两个最重要的系统调用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fork()&lt;/code&gt;：复制当前进程，生成一个几乎一模一样的子进程。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exec()&lt;/code&gt;：用新程序替换当前进程的内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;tip&quot; title=&quot;典型流程，以执行 &lt;code&gt;ls&lt;/code&gt; 为例 &quot;&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shell → fork() → 得到一份 shell 副本
      ↓
    copy of shell → exec(ls) → 变成 ls 程序并运行
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;p&gt;实践代码:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用 strace 跟踪系统调用 (非常有用！) 
strace ls

# 输出片段示例：
execve(&quot;/bin/ls&quot;, [&quot;ls&quot;], ...) = 0
openat(AT_FDCWD, &quot;/etc/ld.so.cache&quot;, O_RDONLY|O_CLOEXEC) = 3
read(3, &quot;...&quot;, 832)                   = 832
...
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;自己写个小程序测试 fork/exec (C 语言)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        execlp(&quot;date&quot;, &quot;date&quot;, NULL);  // 执行 date 命令
    } else {
        // 父进程
        wait(NULL);  // 等待子进程结束
        printf(&quot;Child finished.\n&quot;);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;编译运行：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;gcc -o test_fork test_fork.c
./test_fork
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你会看到日期输出后打印 “Child finished.” —— 这就是 fork+exec 的经典用法！&lt;/p&gt;
&lt;h2&gt;1.4 伪设备 (Pseudodevices) ——软件模拟的“假硬件”&lt;/h2&gt;
&lt;p&gt;有些功能不适合做成真正的硬件，但又希望像设备一样被访问，于是就有了“伪设备”。&lt;/p&gt;
&lt;p&gt;下面是些著名的例子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/dev/random&lt;/code&gt; 和 &lt;code&gt;/dev/urandom&lt;/code&gt;：随机数生成器&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/dev/null&lt;/code&gt;：黑洞，写入即丢弃&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/dev/zero&lt;/code&gt;：无限零流&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/dev/tty&lt;/code&gt;：当前终端&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;虽然它们看起来像设备文件，其实是内核里的软件实现。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 从 /dev/urandom 读取 10 字节随机数据
dd if=/dev/urandom of=random.bin bs=1 count=10

# 把任意内容扔进 /dev/null (相当于删除) 
echo &quot;hello&quot; &amp;gt; /dev/null

# 生成一个 1MB 的全零文件
dd if=/dev/zero of=zeros.bin bs=1M count=1

# 查看当前终端设备名
tty
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;caution&quot; title=&quot;注意&quot;&amp;gt;
访问伪设备也需要系统调用 (如 open/read/write) ，所以无法完全避开内核交互。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;h2&gt;1.5 用户空间 (User Space) ——我们的活动舞台&lt;/h2&gt;
&lt;p&gt;用户空间是指所有非内核程序的集合，也叫 “userland”。这里包含了你每天打交道的东西：图形界面、Web 浏览器、命令行工具、服务器软件等等。&lt;/p&gt;
&lt;p&gt;根据功能复杂度，用户空间组件大致可分为三层：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;**顶层 (应用层) **：直接面向用户的程序，如 Firefox、VSCode、Steam。&lt;/li&gt;
&lt;li&gt;**中间层 (服务层) **：支撑应用的后台服务，如 DNS 缓存、数据库、邮件服务器。&lt;/li&gt;
&lt;li&gt;**底层 (基础服务层) **：靠近内核的小型实用程序，如网络配置、日志记录、通信总线。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;tip&quot; title=&quot;三层组件的依赖关系&quot;&amp;gt;
&lt;code&gt;    User Interface ←→ Web Browser         ↓             ↓     Name Caching Server         ↓     Network Config ←→ Communication Bus ←→ Diagnostic Logging    &lt;/code&gt;
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;caution&quot; title=&quot;注意&quot;&amp;gt;
现实中没有严格界限。比如 Nginx 既是 Web 服务器 (高层) ，也可能作为反向代理处理低层路由逻辑。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;p&gt;✅ 操作示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看系统中运行的所有用户进程
ps -ef | grep -v &quot;$$&quot; | grep -v &quot;PID&quot;

# 查看某个进程所属的服务层级 (粗略判断) 
systemctl status nginx    # 如果是 systemd 管理的，可能是服务层
htop                      # 可视化查看进程树和资源占用

# 查看日志服务是否运行
journalctl -u rsyslog --since today   # systemd 日志
tail -f /var/log/syslog               # 传统 syslog 日志
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1.6 用户与权限体系 —— 安全的第一道防线&lt;/h2&gt;
&lt;p&gt;Linux 是多用户系统，每个用户都有唯一的 ID (UID) ，而不是用户名。内核只认 UID，不认识 “alice” 或 “bob”。&lt;/p&gt;
&lt;h3&gt;核心概念：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;普通用户&lt;/strong&gt;：只能操作自己的文件和进程。&lt;/li&gt;
&lt;li&gt;**root 用户 (UID=0) **：超级管理员，可以做任何事情 (包括删库跑路 😱) 。&lt;/li&gt;
&lt;li&gt;**组 (Group) **：一组用户的集合，用于批量授权文件访问权限。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;danger&quot; title=&quot;重要提醒&quot;&amp;gt;
尽量避免长期使用 root！很多操作可以用 &lt;code&gt;sudo&lt;/code&gt; 替代。
&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;p&gt;✅ 操作示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看当前用户是谁
whoami

# 查看当前用户的 UID/GID
id

# 切换到 root (需要密码) 
sudo su -

# 或者临时以 root 身份执行一条命令
sudo rm -rf /tmp/testdir

# 创建新用户
sudo useradd -m alice
sudo passwd alice

# 将用户加入某个组
sudo usermod -aG sudo alice   # 加入 sudo 组，获得提权能力

# 查看某个文件的属主和权限
ls -l /home/alice/.bashrc
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;附录：常用诊断命令速查表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;目的&lt;/th&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;查看进程&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ps&lt;/code&gt;, &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;htop&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查看内存&lt;/td&gt;
&lt;td&gt;&lt;code&gt;free&lt;/code&gt;, &lt;code&gt;vmstat&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查看磁盘&lt;/td&gt;
&lt;td&gt;&lt;code&gt;df&lt;/code&gt;, &lt;code&gt;du&lt;/code&gt;, &lt;code&gt;iostat&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查看网络&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ip&lt;/code&gt;, &lt;code&gt;ss&lt;/code&gt;, &lt;code&gt;netstat&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查看系统调用&lt;/td&gt;
&lt;td&gt;&lt;code&gt;strace&lt;/code&gt;, &lt;code&gt;ltrace&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查看内核模块&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lsmod&lt;/code&gt;, &lt;code&gt;modinfo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查看硬件&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lspci&lt;/code&gt;, &lt;code&gt;lsusb&lt;/code&gt;, &lt;code&gt;lshw&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查看日志&lt;/td&gt;
&lt;td&gt;&lt;code&gt;journalctl&lt;/code&gt;, &lt;code&gt;dmesg&lt;/code&gt;, &lt;code&gt;tail&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>Foundational Models 论文阅读合集 1</title><link>https://adalovelemon.github.io/posts/content/paperreading/foundations/foundai1/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/paperreading/foundations/foundai1/</guid><pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Paper } from &quot;astro-pure/user&quot;
import { Aside, Steps, Tabs, TabItem, Timeline, Card, CardList, Collapse, Button, Spoiler, Label } from &quot;astro-pure/user&quot;&lt;/p&gt;
&lt;p&gt;export const images = import.meta.glob(&quot;./assets/FoundAI-1/*.{png,jpg,jpeg,webp,gif,avif,svg}&quot;, {
eager: true,
import: &quot;default&quot;,
})&lt;/p&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; &lt;em&gt;/}
{/&lt;/em&gt; &amp;lt;Aside type=&quot;caution&quot; title=&quot;注意&quot;&amp;gt;
注意：本篇Post所列举文章当前没有全部阅读完，本篇博客后续会持续更新。
&amp;lt;/Aside&amp;gt; */}&lt;/p&gt;
&lt;h1&gt;3D-LLaVA&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;3D-LLaVA: Towards Generalist 3D LMMs with Omni Superpoint Transformer&quot;
arxiv=&quot;2501.01163v2&quot;
code=&quot;https://github.com/djiajunustc/3D-LLaVA&quot;
rating=&quot;3&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;“超点压缩 + 2D蒸馏”让纯点云直接变身LLM可理解的视觉Token，实现了无需多视图、端到端的“所说即所割”。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/3D-LLaVA-1.png&quot;].src} alt=&quot;3D-LLaVA&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/3D-LLaVA-2.png&quot;].src} alt=&quot;3D-LLaVA&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/3D-LLaVA-3.png&quot;].src} alt=&quot;3D-LLaVA&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;3D-LLaVA 旨在解决现有 3D 多模态模型流程繁琐、功能割裂的痛点，提出了一种极简的一体化架构。&lt;/strong&gt; 它摒弃了复杂的多视图预处理和独立编码器，仅以点云为输入，通过核心的 &lt;strong&gt;Omni Superpoint Transformer (OST)&lt;/strong&gt; 统一承担视觉特征选择、提示编码与掩码解码三大功能，实现了从 3D 感知到语言推理的端到端贯通。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;其核心创新在于“超点表示”与“混合预训练”策略的深度融合。&lt;/strong&gt; 模型先将体素聚类为语义超点以压缩计算量，再利用距离自适应注意力机制增强几何感知；同时，通过联合实例分割任务与 2D-to-3D 知识蒸馏，将强大的 2D 语义先验注入 3D 空间，使生成的视觉 Token $Z_V$ 既能被大语言模型理解，又能精准反推 3D 掩码，真正实现了“所说即所割”。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;实验结果证明该方案在五大基准数据集上全面刷新 SOTA，且兼具高效性与通用性。&lt;/strong&gt; 3D-LLaVA 在仅需点云输入的情况下，于 3D 问答 (ScanQA) 和指代分割 (Multi3DRefer) 任务中分别取得 92.6% CiDEr 和 42.7% mIoU 的优异成绩，显著超越依赖复杂多视图特征的竞品；它不仅统一了对话、描述与分割任务，更验证了无需额外模块即可实现高精度 3D 具身智能的可行性。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;SGL&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;A Stitch in Time Saves Nine: Small VLM is a Precise Guidance for Accelerating Large VLMs&quot;
arxiv=&quot;2412.03324v2&quot;
code=&quot;https://github.com/NUS-HPC-AI-Lab/SGL&quot;
rating=&quot;3&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;当小模型和大模型的输出高度相似时，可以用小模型来指导或者代替大模型。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/SGL-1.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;img src={images[&quot;./assets/FoundAI-1/SGL-2.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当视觉编码器从高分辨率图中提取得到大量的视觉 tokens 作为输入时，会导致 VLMs 面临巨大的推理效率挑战。此前的工作通常从减少视觉 tokens 的角度来解决这一问题。该思路主要分为两个大类：
&lt;ul&gt;
&lt;li&gt;训练类方法需要额外的训练成本。&lt;/li&gt;
&lt;li&gt;无训练方法考虑做 tokens 的融合或者剪枝，但是简单的融合机制或者剪枝可能带来严重的性能下降。&lt;/li&gt;
&lt;li&gt;近期的方法是无训练方法的一个分支——利用 VLMs 的中间层注意力图来评估 token 重要性并动态剪枝。这类方法中，仅通过单层的注意力图不足以准确识别关键的视觉 tokens，而对整体的注意力图做聚合又导致推理开销的增加。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;基于以上背景，本文提出了 SGL 方法，通过引入一个小型的 VLMs 模型并利用它的全局注意力图(由多个注意力图聚合而来)来指导视觉 tokens 剪枝。这里是利用到了实验验证的结论——&lt;strong&gt;小型 VLMs 的全局注意力图和大型 VLMs 的全局注意力图是十分接近的&lt;/strong&gt;。SGL 有两个模块：
&lt;ul&gt;
&lt;li&gt;SGP 模块首先让一个小型 VLM 运行给定的输入，聚合该小型模型的所有头的注意力图(包括 prefilling 阶段 prompt 对 visual tokens 的注意力和解码阶段 generated tokens 对 visual tokens 的关注)，计算出每个视觉 token 的综合得分，然后将得分低的 token 剪掉。&lt;/li&gt;
&lt;li&gt;SGL 模块的设计则是考虑到既然小型的 VLM 都已经推理一次了，那么推理阶段的结果就不要浪费了。计算小型模型输出结果的决策分数 $S$(置信度 $S_{\text{confidence}}$ (模型对自己输出的信任程度)和一致性分数 $S_{\text{consistency}}$ (剪枝前后输出结果的比对) 的平均值)；如果决策分数超过某个阈值，则直接使用小模型的输出结果，否则才使用大模型(输入裁剪后的 tokens)的输出结果。
$$
\begin{aligned}
S_{\text{confidence}} &amp;amp;= \left[\prod_{i=1}^{N_G} P \left(x_G^{(i)} | \text{LM}&lt;em&gt;S \left(x_I, x_T, x_G^{(1:i-1)} \right) \right) \right]^{\frac{1}{N_G}}, \
S&lt;/em&gt;{\text{consistency}} &amp;amp;= \prod_{i=1}^{N_G} P \left(x_G^{(i)} | \text{LM}&lt;em&gt;S \left(\text{Prune}(x_I), x_T, x_G^{(1:i-1)} \right) \right) \
S &amp;amp;= \frac{1}{2} (S&lt;/em&gt;{\text{confidence}} + S_{\text{consistency}})
\end{aligned}
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;可以实现，在视觉token保留率仅为 9% 的情况下(即剪掉了91%的token)，SGL方法仍能保持大模型原始性能的 89%-96%。&lt;strong&gt;非常工程化的一项工作。&lt;/strong&gt;
&amp;lt;/Paper&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;A Few Heads For Visual Grounding&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;Your Large Vision-Language Model Only Needs A Few Attention Heads For Visual Grounding&quot;
arxiv=&quot;2503.06287v1&quot;
paper_url=&quot;&quot;
code=&quot;&quot;
rating=&quot;3&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;预训练模型的注意力机制中可能已隐含多种下游任务的能力，通过设计合适的量化准则分析并提取特定注意力头，可实现免微调的任务迁移。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/Only-Needs-1.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/Only-Needs-2.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;现有利用 VLM 做 visual grounding 的方法大多需要在视觉定位数据集上微调模型，或添加额外的定位组件(如边界框生成器、分割解码器)，这无疑增加了模型复杂度和训练成本。本篇论文发现，冻结的原始 VLM 模型其实已经隐含了 grounding 的能力，可以实现无需任何微调即可用于 visual grounding。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了筛选合适的注意力头，论文提出了&lt;strong&gt;注意力总和&lt;/strong&gt;和&lt;strong&gt;空间熵&lt;/strong&gt;两个量化准则。需要注意的是，本文中的所有注意力图计算&lt;strong&gt;都是基于最后一个 text token 对所有 image tokens 的注意力图&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(1) 注意力总和准则: 最后一个文本 token 对所有图像 token 的注意力总和，其中 $l, h$ 分别表示头的层数序号和头数序号，$a^{l, h}$ 是展平的注意力图 $A^{l, h}$：
$$
S_{\text{img}}^{l, h} = \sum_{i=1}^{N_I} a_{i}^{l, h}
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(2) 空间熵准则: 筛选注意力分布集中而非分散的头，通过计算注意力图的空间熵值，识别低空间熵的头。其中 ${C_i}_{i=1}^{N_C}$ 根据 8 邻域原则发现的二值化的注意力图中的值为 1 的连通区域集合，二值化的注意力图是将注意力图中值高于平均值的位置置为 1，其他位置置为 0 得到的。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
\begin{aligned}
P(C_i) &amp;amp;= \frac{|C_i|}{\sum_{i=1}^{N_C} |C_i|}, \
H(A^{l, h}) &amp;amp;= -\sum_{i=1}^{N_C} P(C_i) \log P(C_i)
\end{aligned}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;联合筛选：最终定位头需同时满足 $S_{\text{img}}$ 高于阈值，且自身的 $H(A^{l, h})$ 排名属于最低的前若干个头。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在多个数据集的指代理解和指代分割任务上，该方法性能与需要微调的方法相当或接近，显著优于其他免训练方法。定位头在同一模型架构内具有较好通用性，跨任务跨数据集均有效，但不同架构模型需重新筛选定位头。核心结论是冻结 VLM 内部已隐含视觉定位能力，正确提取注意力图即可实现有效定位。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;评分说明&lt;/strong&gt;：本文单看论文思路确实是一篇清晰且简单有效的工作，可以评到 4 分，但是考虑到代码没有开源，实验结果可复现性未知，暂时评到 3 分。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;Words or Vision&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;Words or Vision: Do Vision-Language Models Have Blind Faith in Text?&quot;
arxiv=&quot;2503.02199v1&quot;
paper_url=&quot;&quot;
code=&quot;https://github.com/d-ailin/blind-faith-in-text&quot;
rating=&quot;4&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;当视觉与文本发生冲突时，模型往往因训练数据中纯文本的压倒性优势而‘盲目信文’，唯有通过构建显式的模态冲突样本进行针对性微调，才能教会模型真正‘眼见为实’。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;随着视觉-语言模型(VLMs)在复杂多模态任务中的广泛应用，模型经常面临视觉信息与文本输入不一致的场景(如钓鱼网站中的虚假品牌描述)。然而，现有研究多关注视觉中心任务，忽视了模型在处理跨模态冲突时的行为。本文指出，当前主流VLMs普遍存在“文本盲目信任”(Blind Faith in Text)现象：当图像内容与伴随的文本信息发生冲突或文本具有误导性时，模型倾向于忽略视觉证据而过度依赖文本，这不仅导致性能大幅下降，更带来了严重的安全隐患。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了解决这一问题，作者首先从理论层面提出解释，认为训练数据中纯文本数据量远大于多模态数据量($N \gg M$)，导致模型在优化过程中更倾向于最小化纯文本损失，从而形成对文本的偏好；同时，实验发现输入序列中文本Token位于图像Token之前也会加剧这种偏见。针对此，论文提出了两种缓解方案：一是简单的指令提示(效果有限)；二是基于监督微调(SFT)的改进方法，通过构建包含“匹配”、“冲突(损坏)”和“无关”三种文本变体的混合数据集，并保留部分纯文本数据以维持语言能力，对模型进行针对性微调，教会模型在模态冲突时正确权衡视觉与文本信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实验结果显示，未经调整的开放权重模型(如LLaVA、Qwen)在面对误导性文本时，准确率下降幅度高达50%-64%，且文本偏好率(TPR)普遍超过50%；相比之下，部分专有模型(如Claude)表现较为稳健。经过提出的SFT方法微调后，模型性能显著提升：例如LLaVA-NeXT-7B在冲突条件下的准确率从28.69%跃升至71.25%，宏观平均准确率也大幅提高，成功克服了盲目信任文本的缺陷。此外，研究还发现仅用少量(各1,000样本)冲突数据即可有效纠正模型行为，证明了该方法的样本高效性，同时也验证了保留纯文本数据对于防止模型走向“完全拒绝文本”这一极端的重要性。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;AnyAttack&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;AnyAttack: Towards Large-scale Self-supervised Adversarial Attacks on Vision-language Models&quot;
arxiv=&quot;2410.05346v3&quot;
paper_url=&quot;&quot;
code=&quot;https://jiamingzhang94.github.io/anyattack/&quot;
rating=&quot;4&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;利用多架构代理模型的‘对抗共识’来过滤过拟合噪声，是突破黑盒商业大模型防御壁垒的关键捷径。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/AnyAttack-1.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;现有的针对视觉 - 语言模型(VLMs)的对抗攻击方法主要依赖于有监督学习，需要特定的目标标签或图像进行训练，这导致其扩展性差、灵活性低，难以利用海量无标签数据，且生成的对抗样本往往缺乏跨模型和跨任务的迁移能力。随着多模态大模型的广泛应用，这种局限性使得现有攻击手段无法充分揭示模型在开放世界中的深层安全漏洞，亟需一种能够大规模自监督训练、具备高度泛化能力且能实现“任意图像到任意输出”攻击的新型框架。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;论文提出了 AnyAttack，这是首个将“预训练 + 微调”范式引入有目标对抗攻击的自监督框架。其核心是训练一个&lt;strong&gt;噪声生成器&lt;/strong&gt;，采用两阶段策略：首先在 LAION-400M 等大规模无标签数据集上进行自监督预训练，利用对比学习损失，&lt;strong&gt;强制生成器将无关随机图像加上生成的噪声后，在特征空间中对齐到目标原始图像&lt;/strong&gt;，从而学习通用的对抗噪声模式；随后在特定下游任务数据上进行微调，通过引入双向对比损失和辅助代理模型，进一步适配具体任务并提升黑盒迁移能力。该方法巧妙地将图像内容与对抗噪声解耦，实现了无需目标标签的大规模高效训练。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实验结果表明，AnyAttack 在多个开源 VLMs(如 CLIP, BLIP2, MiniGPT-4)的图像检索、分类和描述任务上，攻击成功率显著优于现有的最先进方法(提升约 15%-18%)。更令人震惊的是其强大的黑盒迁移能力，在完全未知的商业闭源模型(如 GPT-4o, Gemini, Claude Sonnet)上，攻击成功率达到了 30%-40%，而基线方法几乎失效。此外，该方法在保持高攻击性的同时，具备推理速度快、内存占用低的优势，证明了其作为通用对抗攻击基础模型的巨大潜力和现实威胁。
&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;ViLex&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;Visual Lexicon: Rich Image Features in Language Space&quot;
arxiv=&quot;2412.06774v1&quot;
paper_url=&quot;&quot;
code=&quot;&quot;
rating=&quot;5&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;尝试将多模态数据编码至预训练大模型的“语义接口空间”(如文本嵌入层)而非传统的潜空间，利用冻结生成器的强大先验作为“语义蒸馏器”，可迫使编码器同时习得高保真重建能力与强判别性理解特征。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/ViLex-1.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;img src={images[&quot;./assets/FoundAI-1/ViLex-2.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;这篇论文直击计算机视觉领域长期存在的“理解”与“重建”难以兼得的痛点。传统的视觉模型往往顾此失彼：擅长语义理解的模型(如 CLIP)在生成图像时会丢失纹理和风格细节，而擅长高保真重建的模型(如 VAE)又缺乏深层的语义表达能力。现有的图像反转技术通常需要对每个图像进行耗时的微调，且无法灵活地将视觉内容与自然语言自由组合。作者的核心目标是寻找一种统一的图像表示法，既能像文本一样具备强大的语义组合性，又能保留丰富的视觉细节，从而同时胜任高质量的图像生成和复杂的视觉理解任务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了解决上述矛盾，作者提出了 ViLex(Visual Lexicon)范式，其核心创新在于将图像直接编码到预训练文本到图像(T2I)扩散模型的“文本词汇空间”中。该方法采用自监督的自动编码器架构：使用基于ViT的视觉编码器将图像映射为一系列连续的“视觉 tokens”，这些 tokens 在维度上与冻结的 T2I 模型(如Imagen)的文本嵌入对齐。训练过程中，扩散模型及其文本编码器完全保持冻结，仅通过最小化图像重建损失来训练视觉编码器。此外，作者引入了巧妙的 “TailDrop” 策略，即在训练时随机丢弃序列末尾的部分 tokens，迫使前面的 tokens 学习更核心的语义信息，从而实现从单 tokens 语义概括到多 tokens 细节还原的动态适应。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实验结果显示，ViLex在生成和理解两个维度均取得了突破性进展。在图像生成方面，即使仅使用1个视觉 tokens ，其重建质量也优于现有的离散化方法；当使用全部 75 个 tokens 时，能够完美还原图像的风格、布局和细微纹理。更重要的是，它实现了零样本的多模态生成，用户可以直接将视觉 tokens 插入自然语言提示中(例如“[视觉 tokens]加上梵高风格”)，无需任何微调即可实现物体的重语境化和风格迁移。在视觉理解方面，将ViLex编码器集成到视觉 - 语言模型(如 PaliGemma)中，在15个基准测试上全面超越了强大的 SigLIP 基线，证明了其学到的特征具有极高的语义密度和判别力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;纵观全文，有几个看似“反直觉”的设计实则蕴含深意。首先，作者放弃直接适用 Visual Encoder 生成条件指导向量的路线，而选择从文本空间多绕一次的实现，是为了利用语言模型的通用接口和语义先验，让图像能像单词一样被大模型理解和组合，这是实现“文图无缝融合”的关键。其次，“TailDrop” 策略看似人为制造信息瓶颈，实则是通过课程学习的方式强制模型进行信息分层，确保前几个 tokens 承载核心语义，后几个 tokens 补充高频细节，从而赋予模型灵活的推理能力。最后，仅靠重建损失且冻结解码器就能获得 SOTA 的理解能力，揭示了“生成即理解”的深层逻辑：要驱动强大的冻结扩散模型精确重建，编码器必须提取出最本质的语义特征，这种高强度的重建约束实际上充当了极佳的语义蒸馏过程。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &quot;note&quot;, &quot;tip&quot;, &quot;caution&quot;, &quot;danger&quot; */}
&amp;lt;Aside type=&quot;tip&quot; title=&quot;Title&quot;&amp;gt;
背景知识:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- T2I 的 pipeline 本质上就是一个 **Text Encoder** (Transformer Encoder) 加上 **Latent Diffusion Model**。其中 Text Encoder 的作用就是利用自注意力机制将离散文本的**嵌入向量** ($N \times C_{\text{embed}}$) 转化为富含上下文的空间感知向量 ($N \times C$) 作为指导 Diffusion Model 生成的条件向量。如果没有 Text Encoder，直接把文本序列的嵌入向量作为条件指导向量输入给 Diffusion Model，由于这些嵌入向量表征的是单词的本意，并不含有如位置关系之类的上下文信息，这样训练出来的 T2I 模型很可能会在多义词 (如 `&apos;bank&apos;` 既是银行也是河岸) 上感到困惑。 所以这里必须要有一个 Encoder 来对输入的 tokens 做上下文的语义增强。

- **ViLex** 的创新其实是在于模仿
  ```
  Text Token Embeddings --&amp;gt; Text Encoder --&amp;gt; Contextualized Text Features --&amp;gt; Diffusion Model
  ```
  这条路线。它训练一个视觉编码器强行**对齐并模仿** Text Encoder 的输出分布，将图像直接“翻译”为扩散模型能听懂的**伪文本语义向量**，从而无需修改模型架构，即可让原本仅服务于文本的扩散模型无缝具备强大的图像条件生成与多模态融合能力。
  ```
  Image --&amp;gt; Visual Encoder --&amp;gt; Pseudo-Textual Visual Tokens --&amp;gt; Text Encoder --&amp;gt; Contextualized Visual Features --&amp;gt; Diffusion Model
  ```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/Aside&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;VisionArena&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;VisionArena: 230K Real World User-VLM Conversations with Preference Labels&quot;
arxiv=&quot;2412.08687v3&quot;
paper_url=&quot;&quot;
huggingface=&quot;https://huggingface.co/lmarena-ai&quot;
rating=&quot;3&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;VisionArena 是一个基于 23 万条真实用户交互构建的多模态评估基准，能有效捕捉模型在开放场景下的实际表现与人类偏好，尤其适用于发现并缓解“风格优于实质”的评估偏差及微调高对齐度的视觉语言模型。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/VisionArena-1.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;随着视觉语言模型(VLMs)的快速发展，现有的评估基准大多依赖于静态的、有标准答案的单轮任务(如图像描述或选择题)，这种方式无法有效捕捉真实世界中用户与VLM交互的开放性、多轮性和主观偏好。这种评估方式与模型的实际应用场景存在巨大脱节，导致我们难以准确衡量VLM在复杂、动态的真实环境中的表现和人类对其的真实满意度。因此，社区亟需一个能够反映 authentic user-VLM interactions(真实的用户-VLM互动)的大规模数据集和评估框架，以推动VLM技术向更符合人类期望的方向发展。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了解决上述问题，作者提出了 &lt;strong&gt;VisionArena&lt;/strong&gt;，一个包含23万条真实世界用户-VLM对话的数据集。该数据集通过构建一个类似Chatbot Arena的平台收集而来，主要包含三个部分：(1) &lt;strong&gt;VisionArena-Chat&lt;/strong&gt; (200k条)，记录用户与指定模型的直接对话；(2) &lt;strong&gt;VisionArena-Battle&lt;/strong&gt; (30k条)，记录用户在匿名“对战模式”下对两个模型回答的偏好投票；(3) &lt;strong&gt;VisionArena-Bench&lt;/strong&gt; (500个提示词)，一个用于自动化评估的离线基准。在技术细节上，数据收集后经过了严格的清洗流程，包括使用自动化工具过滤不安全内容(NSFW)、移除个人身份信息(PII)，并对非英语对话进行了筛选。为了分析用户意图，作者采用BERTopic框架对海量对话进行主题建模和聚类，从而识别出STEM、OCR、创意写作等核心问题类别。此外，他们还利用Bradley-Terry模型处理对战数据中的偏好投票，以生成可靠的模型排行榜。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VisionArena的分析与应用得出了多项重要成果。首先，数据分析揭示了当前VLM的优势与短板，例如模型在处理开放式的幽默和创意写作时高度依赖回复风格，而在需要复杂空间推理的任务上则普遍表现不佳。其次，其衍生的离线基准 VisionArena-Bench 被证明非常有效，它与在线Chatbot Arena官方排行榜的Spearman相关性高达97.3%，表明其能低成本、高效率地预测模型在真实用户中的排名。最后，论文展示了该数据集的巨大实用价值：仅在VisionArena-Chat数据的一个子集上进行微调，就使得Llama-3.2-11B基础模型在MMMU和WildVision等多个权威基准测试上取得了显著提升，性能甚至超越了参数量更大的模型，充分证明了VisionArena数据对于训练高性能、符合人类偏好的VLM具有关键作用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;IP-CLIP&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;Vision-Language Model IP Protection via Prompt-based Learning&quot;
arxiv=&quot;2503.02393v1&quot;
paper_url=&quot;&quot;
code=&quot;https://github.com/LyWang12/IP-CLIP&quot;
rating=&quot;2&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;利用对抗攻击的思想，通过训练一个动态 Prompt 生成器，使其在非授权域自动合成具有强误导性的对抗指令，诱导冻结的大模型产生定向决策崩塌，从而实现低成本的知识产权锁定。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/IP-CLIP-1.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;随着视觉 - 语言模型(如 CLIP)商业价值飙升，高昂的训练成本使其面临严重的知识产权侵权风险，特别是攻击者容易将授权模型非法迁移到未付费的特定数据域(如不同风格或场景)中使用；然而，现有的保护方法往往需要全量微调巨大模型导致成本过高，或缺乏对多模态语义的有效利用，难以在低开销下实现精准的“域锁定”。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;论文提出 &lt;strong&gt;IP-CLIP&lt;/strong&gt; 框架，采用参数高效的提示学习(Prompt-based Learning)策略，在冻结 CLIP 主干网络的前提下，仅通过训练轻量级的 &lt;strong&gt;IP-Prompt&lt;/strong&gt; 模块来实现保护；该模块包含一个风格增强分支，利用特征库和注意力机制提取图像的域风格特征，生成特定的“域 Token”拼接到文本提示中，引导模型在授权域正常分类，而在检测到非授权域风格时故意输出错误预测或大幅降低性能。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在多个跨域数据集(如 Office-Home, DomainNet)上的实验表明，IP-CLIP 能以极低的计算成本(仅更新极少参数)，在几乎不损失授权域性能(准确率下降 &amp;lt; 1%)的同时，使模型在非授权域上的准确率显著崩塌(大幅下降至随机水平)；其综合保护指标优于现有非迁移学习基线，证明了该方法能有效防止模型被非法跨域滥用，且具备部署灵活性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这篇文章的想法就是对于输入的图片，用一个 prompt 生成器去根据输入图片是否是授权的内容，来选择是增强文本 prompt 以增强类别识别，还是生成一个误导性的 prompt 来欺骗模型，从而导致分类识别失败。利用误导性 prompt 确实是一个经典且不错的方法，但是任务本身的价值并不大，因为它的保护仅仅只是在图像识别上。不过，类似的操作可以移植到风格迁移的那种图像条件生成上去，在这个使用场景下，对授权保护的需求可能会更大一些。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;VideoEspresso&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;VideoEspresso: A Large-Scale Chain-of-Thought Dataset for Fine-Grained Video Reasoning via Core Frame Selection&quot;
arxiv=&quot;2411.14794v1&quot;
paper_url=&quot;&quot;
code=&quot;https://github.com/hshjerry/VideoEspresso&quot;
rating=&quot;4&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/VideoEspresso-1.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;img src={images[&quot;./assets/FoundAI-1/VideoEspresso-2.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;br&amp;gt;&amp;lt;/br&amp;gt;
&amp;lt;img src={images[&quot;./assets/FoundAI-1/VideoEspresso-3.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;本文工作主要分为两个部分，分别是构建 VideoEspresso 视频QA数据集，和提出 Hybrid LVLMs Collaboration 视频推理框架两个部分。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;VideoEspresso 视频QA数据集&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;构建背景&lt;/strong&gt;：针对现有视频问答数据集依赖昂贵人工标注导致粒度粗糙，或自动构建方法因处理全量帧而充满冗余信息、缺乏细粒度时空逻辑的问题，急需一种能保留关键细节且包含中间推理步骤的大规模高质量数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构建过程&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;冗余去除&lt;/strong&gt;：利用轻量级模型，按照视频变化快慢自适应采样，为采样的每一帧生成帧描述 (Caption) 并计算语义相似度，过滤高度相似帧，提取高信息密度的“核心帧”序列。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;问答生成&lt;/strong&gt;：将核心帧按时间顺序每15帧划分为连续组，以在保持短时序逻辑连贯的同时避免长上下文引发的模型幻觉，进而将这些分组输入GPT-4o生成需多帧协同推理的复杂问答对并经由二次验证过滤。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多模态思维链标注&lt;/strong&gt;：引导大模型提取关键物体及其时空位置(边界框+帧索引)，构建包含“文本证据+空间坐标+时间索引”的完整推理链。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;效果&lt;/strong&gt;：构建了包含20万+高质量样本、覆盖14个细分推理任务的数据集；基于该数据集训练的模型在客观准确率上超越了GPT-4o等最强基线，且显著提升了推理的逻辑性和事实准确性。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hybrid LVLMs Collaboration 视频推理框架&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;构建背景&lt;/strong&gt;：为解决直接输入全量视频帧导致计算成本高昂(显存和FLOPs巨大)且冗余信息干扰模型注意力，以及传统端到端模型容易产生幻觉、缺乏可解释性证据的问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;具体细节&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心帧选择器&lt;/strong&gt;：设计了一个由微小LVLM和LLM组成的轻量级插件，动态筛选出与问题最相关的极少数核心帧(平均仅2.36帧)，大幅降低输入负载。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两阶段微调策略&lt;/strong&gt;：对推理大模型进行分步训练，第一阶段强制模型学习从核心帧中定位并生成“多模态证据”，第二阶段基于生成的证据进行最终答案推理，实现思维过程的显式化。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：在保持甚至提升推理准确率(平均34.1%，超越GPT-4o 7.7%)的同时，将输入帧数减少至基准方法的1.8%，计算量(FLOPs)降低至14.74%，显存占用显著减少，实现了高效能与高精度的统一。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;VDocRAG&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;VDocRAG: Retrieval-Augmented Generation over Visually-Rich Documents&quot;
arxiv=&quot;2504.09795v1&quot;
paper_url=&quot;&quot;
code=&quot;https://vdocrag.github.io/&quot;
rating=&quot;4&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;图像是统一的载体，单个 &amp;lt;EOS&amp;gt; token 足够概括文档&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/VDocRAG-1.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;
&amp;lt;img src={images[&quot;./assets/FoundAI-1/VDocRAG-2.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本文先构建了一个 OpenDocVQA 数据集，用于提供一个标准的视觉 RAG 基准。给定含有 $N$ 个文档的图片(将 pdf 等文档统一转换成图片)集合 ${\mathcal{I}&lt;em&gt;i}&lt;/em&gt;{i=1}^N$ 和一个给定的问题 $Q$，OpenDocVQA 要求 RAG 大模型能够通过参考最相关的 $k$ 个文档图像，输出针对问题 $Q$ 的回答 $A$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;基于 OpenDocVQA 数据集，本文训练出了一个视觉检索模型 VDocRAG。VDocRAG 主要有两个模块，分别是检索部分 VDocRetriever 和回答部分 VDocGenerator。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VDocRetriever 首先将文档图像动态切分为多个 $336 \times 336$ 大小的 Patches，经 ViT 编码与投影层映射为 LLM 维度的视觉 Token 序列(一个文档是一个 token 序列)，并在末尾附加 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; 标记后输入模型，利用预训练阶段学到的自注意力机制(RCR/RCG任务)，强制将分散在所有 Patch 中的复杂视觉语义信息“蒸馏”并压缩至最后一个 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; token 的隐藏状态，从而得到紧凑的文档向量 $h_d$；与此同时，对于用户问题 $Q$，系统将其先分词为文本序列并在末尾同样附加 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; 标记后输入同一模型，通过前向传播让 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; token 捕获整个问题的完整语义意图，提取其隐藏状态作为问题向量 $h_q$，最终通过计算 $h_q$ 与 $h_d$ 的余弦相似度进行最大内积搜索(Maximum Inner Product Search)，从海量文档库中精准检索出 Top-$k$ 个最相关的视觉文档($N$ 个文档就对应 $N$ 个 $h_d$)。&lt;/li&gt;
&lt;li&gt;使用 Topk 筛选出最相关的 $k$ 个文档后，VDocGenerator 就会将筛选出的文档和问题 $Q$ 共同输入给一个 VLM，让它生成对问题的回答。这里，筛选出的文档同样也是经过了 ViT 编码并投影到 LLM 维度的视觉 Token 序列，直接作为输入与问题文本一起输入给 VLM 进行端到端的生成。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;显然，检索的核心在于模型对 $h_d$ 和 $h_q$ 的构造能力上。作者在 VDocRetriever 的预训练阶段设计了两个自监督任务(RCR和RCG)，强制模型将整张文档图像的所有视觉信息“蒸馏”并压缩进唯一的 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; token 中，使其成为该文档的紧凑语义向量。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RCR (基于检索的对比学习)：构建具备强判别力的语义索引&lt;/strong&gt;。RCR 任务旨在解决“找得准”的问题，它通过最小化以下对比损失函数，强制文档图像的 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; 向量 ($\mathbf{h}&lt;em&gt;d$) 与其对应的 OCR 文本向量 ($\mathbf{h}&lt;em&gt;o$) 在特征空间中高度对齐，同时最大化与其他无关文档的距离：
$$
\mathcal{L}&lt;/em&gt;{\text{RCR}} = -\log \frac {\exp(\text{sim}(\mathbf {h}&lt;/em&gt;{\text {o}}, \mathbf {h}&lt;em&gt;{\text {d}^+})/\tau )} {\sum &lt;em&gt;{i\in \mathcal {B}} \exp(\text{sim}(\mathbf {h}&lt;/em&gt;{\text {o}}, \mathbf {h}&lt;/em&gt;{\text {d}&lt;em&gt;i})/\tau )}
$$
其中 $\mathbf{h}&lt;/em&gt;{\text{d}^+}$ 为正样本文档向量，$\mathbf{h}_{\text{d}_i}$ 为批次内的负样本。该公式迫使模型学习到一个&lt;strong&gt;高区分度&lt;/strong&gt;的嵌入空间：只有当 $h_d$ 准确捕捉到文档的核心语义且与查询意图匹配时，分子项才会占主导；反之，若 $h_d$ 包含噪声或偏离主题，分母中的负样本项将拉低分数。这确保了最终生成的 $h_d$ 是一个纯净、高效的检索键值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RCG (基于生成的信息蒸馏)：确保向量包含完整的细粒度细节&lt;/strong&gt;。RCG 任务旨在解决“记得全”的问题，它设计了一种特殊的生成约束，屏蔽掉所有原始图像 Patch 的注意力，强制模型仅依赖 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; 向量来重构后续文本序列，其损失函数定义为：
$$
\mathcal{L}&lt;em&gt;{\text{RCG}} = -\frac {1}{L} \sum &lt;em&gt;{i=1}^{L} \log p(y_i|y&lt;/em&gt;{&amp;lt;i}, \text{&amp;lt;EOS&amp;gt;})
$$
这里的条件概率 $p(y_i|\dots)$ 意味着第 $i$ 个字符的预测完全依赖于历史文本 $y&lt;/em&gt;{&amp;lt;i}$ 和文档摘要向量 $\mathbf{h}_d$ (即 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt;)，而&lt;strong&gt;无法直接看到具体的图像块&lt;/strong&gt;。这一机制构成了严苛的&lt;strong&gt;信息瓶颈测试&lt;/strong&gt;：如果 $\mathbf{h}_d$ 丢失了任何关键细节(如表格数字、特定词汇或布局关系)，模型就无法以高概率生成正确的 $y_i$，导致 Loss 急剧上升。因此，该任务倒逼模型将所有分散的视觉信息无损地“压缩”进单一的 $h_d$ 中，保证了检索向量的信息完备性。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;模型的效果&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;检索性能&lt;/strong&gt;：在零样本(Zero-shot)设置下，显著优于BM25、CLIP及现有的视觉文档检索基线模型(如DSE, VisRAG-Ret)，特别是在非文本主导的文档中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成性能&lt;/strong&gt;：在InfoVQA, DUDE, ChartQA等多个基准上大幅超越基于文本的RAG。实验证明，即使使用相同的基座模型，仅改变输入形式(图像vs文本)，VDocRAG也能取得更高分数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;效率优势&lt;/strong&gt;：由于省去了耗时的OCR预处理步骤，其整体推理速度比传统文本RAG快约&lt;strong&gt;69%&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;鲁棒性&lt;/strong&gt;：在处理手写体模糊、表格错位等OCR容易出错的场景时，表现出更强的鲁棒性和准确性。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;HyperCLIP&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;Understanding Fine-tuning CLIP for Open-vocabulary Semantic Segmentation in Hyperbolic Space&quot;
arxiv=&quot;&quot;
paper_url=&quot;https://openaccess.thecvf.com/content/CVPR2025/papers/Peng_Understanding_Fine-tuning_CLIP_for_Open-vocabulary_Semantic_Segmentation_in_Hyperbolic_Space_CVPR_2025_paper.pdf&quot;
code=&quot;https://github.com/SJTU-DeepVisionLab/HyperCLIP&quot;
rating=&quot;3&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;利用双曲空间天然的层次表示能力，通过简单缩放变换调整CLIP文本嵌入的双曲半径，实现高效的开放词汇分割。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/HyperCLIP.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;开放词汇语义分割任务要求模型能够基于任意文本描述对图像像素进行分类，突破预定义类别限制。CLIP作为强大的视觉-语言基础模型在此任务中表现突出，但存在一个关键矛盾：传统观点认为冻结CLIP文本编码器能保留其泛化能力，而最新研究却发现同时微调文本和图像编码器能显著提升性能。作者从层次对齐角度解释这一现象，发现微调过程中图像嵌入从图像级转变为像素级，这种层次结构的转变需要相应的文本嵌入调整来实现有效对齐。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;作者提出了HyperCLIP——一种基于双曲几何的参数高效微调策略。双曲空间是一种具有负常曲率的非欧几里得几何空间，特别适合表示层次结构数据。在Poincaré球模型中，双曲空间定义为
$$
\mathbb{D}_c^n = {x \in \mathbb{R}^n: \sqrt{c}|x| &amp;lt; 1}
$$
其中 $c &amp;gt; 0$ 是曲率参数。双曲空间中的距离度量为：
$$
d_c(x,y) = \frac{2}{\sqrt{c}}\tanh^{-1}\left(\sqrt{c}|\ominus_c x \oplus_c y|\right)
$$
其中 $\ominus_c$ 和 $\oplus_c$ 分别表示双曲减法和加法操作。点到原点的双曲半径(即层次深度)可表示为：
$$
r_c(x) = \frac{1}{\sqrt{c}}\tanh^{-1}(\sqrt{c}|x|)
$$&lt;/p&gt;
&lt;p&gt;作者通过块对角缩放矩阵和Möbius矩阵乘法操作，调整CLIP文本嵌入的双曲半径，使其更好地与像素级视觉特征的层次结构对齐。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实验表明，HyperCLIP在三个基准测试上取得了开放词汇语义分割的最先进性能，仅需微调约4%的CLIP参数(5.6M参数)。研究发现，调整后的文本嵌入双曲半径从初始值7.5减小到约6.0时性能最佳，这验证了层次对齐的重要性。该方法还展现出良好的通用性，在开放词汇目标检测和全景分割任务上同样表现优异。值得注意的是，不同数据集上最优的双曲半径相对固定，这表明分割任务所需的语义粒度可能可以用双曲半径来量化。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;What&apos;s in the Image?&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;What&apos;s in the Image? A Deep-Dive into the Vision of Vision Language Models&quot;
arxiv=&quot;2411.17491v1&quot;
paper_url=&quot;&quot;
code=&quot;https://vision-of-vlm.github.io/&quot;
rating=&quot;3&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;VLMs 用查询文本的 tokens 记住整张图的大意，靠生成的 tokens 从图像 tokens 中寻找着具体细节来回答问题。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/Whats-in-the-image.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;视觉语言模型(Vision Language Models, VLMs)近年来在理解复杂视觉内容方面展现出卓越能力，但其内部处理视觉信息的机制仍不明确。尽管VLMs已被广泛应用于机器人、医疗影像分析、自动驾驶等领域，但它们通常被视为&quot;黑盒&quot;工具，缺乏对其内部视觉处理机制的理解。这种理解对于提高模型透明度、效率和在高风险应用中的可信度至关重要。本文旨在通过实证分析方法，深入探究VLMs如何处理和利用视觉信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;作者通过&quot;注意力敲除&quot;(attention knockout)技术，限制不同层对视觉信息的访问，来揭示模型内部工作机制。研究聚焦于三个核心问题：查询文本 tokens 在视觉信息处理中的作用、不同层在视觉-语言知识传递中的贡献，以及模型如何检索细粒度的视觉属性和对象细节。为验证这些发现，论文提出了两种新型评估协议：基于LLM的评估协议用于比较原始输出与修改后输出的一致性，以及利用现成的对象分割工具进行空间定位的自动评估。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;研究得出了三个关键发现：(1) VLMs将高层次图像信息压缩到查询文本 tokens 中，即使阻断生成 tokens 直接访问图像 tokens，模型仍能产生描述性响应；(2) 视觉到语言的知识传递主要由中间层(约占所有层的25%)完成，而早期和晚期层贡献微乎其微；(3) 细粒度视觉属性和对象细节是通过生成 tokens 直接从图像 tokens 中以空间局部化方式提取的。基于这些发现，论文展示了&quot;Image Re-prompting&quot;技术，使用比完整上下文小20倍的压缩上下文，在视觉问答任务中仍能达到96%的性能。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;VladVA&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;VladVA: Discriminative Fine-tuning of LVLMs&quot;
arxiv=&quot;2412.04378v3&quot;
paper_url=&quot;&quot;
code=&quot;&quot;
rating=&quot;2&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;用长短描述分工训练——短描述学判别，长描述学组合，让LVLM既精准又懂语言结构。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/VladVA.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;当前视觉-语言模型(VLMs)如CLIP虽在零样本任务中表现出色，但存在“词袋行为”和组合性理解不足的问题。而大型视觉-语言模型(LVLMs)虽具备强大的生成与推理能力，却因自回归训练方式&lt;strong&gt;难以直接用于判别任务&lt;/strong&gt;(如图文检索)。近期工作E5-V尝试通过纯文本对比学习将LVLM转为判别模型，但忽略了图像-文本联合优化的潜力。本文旨在弥合这一鸿沟，提出一种新框架，将生成式LVLM转化为兼具强判别能力和组合性理解的模型，从而实现针对给定图像和给定文本的特定性判别(相似性计算)或生成(问答)任务。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VladVA 通过&lt;strong&gt;分粒度混合训练策略&lt;/strong&gt;将判别性与组合性统一：针对&lt;strong&gt;短描述&lt;/strong&gt;(&amp;lt; 30 tokens)，采用&lt;strong&gt;对比损失&lt;/strong&gt; $\mathcal{L}&lt;em&gt;c$，强制图像嵌入 $\mathbf{e}&lt;em&gt;v$ 与文本摘要嵌入 $\mathbf{e}&lt;em&gt;t$ 在余弦相似度空间中对齐，构建高区分度的全局判别向量；针对&lt;strong&gt;长描述&lt;/strong&gt;(30–500 tokens)，则引入&lt;strong&gt;自回归交叉熵损失&lt;/strong&gt; $\mathcal{L}&lt;/em&gt;{CE}$，迫使模型在预测序列时显式关联视觉细节与复杂语言结构，从而习得细粒度组合推理能力。具体损失函数的公式如下：
$$
\mathcal{L}&lt;em&gt;c = \frac{1}{b}\sum&lt;/em&gt;{k=1}^{b}\left(-\log\frac{\exp(s&lt;/em&gt;{k,k}^v)}{\sum_j\exp(s&lt;/em&gt;{k,j}^v)} - \log\frac{\exp(s_{k,k}^t)}{\sum_j\exp(s_{j,k}^t)}\right)
$$
$$
\mathcal{L}&lt;em&gt;{CE} = \sum&lt;/em&gt;{i=1}^{L}\log p_\theta(u_i|x_v, x_p^v, x_q^{\text{long},&amp;lt;i})
$$
在&lt;strong&gt;推理阶段&lt;/strong&gt;，用户输入一张图像 $x_v$ 和一段指令 $q$(或仅图像)：模型首先提取图像特征 $\mathbf{e}&lt;em&gt;v$，并通过软提示将其转化为具有强判别性的“摘要Token”向量 $\mathbf{z}&lt;/em&gt;{img}$(复用 $\mathcal{L}&lt;em&gt;c$ 的语义对齐能力)；随后根据任务类型分路执行——若为&lt;strong&gt;检索/分类&lt;/strong&gt;，直接计算查询文本与 $\mathbf{z}&lt;/em&gt;{img}$ 的余弦相似度输出匹配结果；若为&lt;strong&gt;生成/问答&lt;/strong&gt;，则将 $\mathbf{z}&lt;em&gt;{img}$ 作为前缀输入LLM，利用 $\mathcal{L}&lt;/em&gt;{CE}$ 习得的组合推理能力逐词预测生成包含详细视觉细节的自然语言描述，从而实现“一图多模态”的统一输出。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VladVA在多个基准上显著超越现有模型：在Flickr30K、COCO和nocaps的图文检索任务中，R@1指标比同规模SOTA模型提升4.7–7.0%，甚至超过参数量更大的EVA-CLIP(18B)。在组合性测试集SugarCrepe上，其在关系替换、属性添加等子任务上提升高达15%，尤其在“对象交换”任务(直接衡量词袋行为)上表现突出。消融实验证明，对比损失、自回归损失及参数高效组件均不可或缺。此外，模型在保持判别性能的同时，仍保留较强的生成能力。
&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Narrating the Video&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;Narrating the Video: Boosting Text-Video Retrieval via Comprehensive Utilization of Frame-Level Captions&quot;
arxiv=&quot;2503.05186v4&quot;
paper_url=&quot;&quot;
code=&quot;https://multimodal-understanding-group.github.io/NarVid/&quot;
rating=&quot;3&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;在视频-文本检索中，利用帧级叙述捕捉时间变化信息并结合自适应过滤机制，能有效提升检索精度并减少生成模型幻觉带来的干扰。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/NarVid.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在视频-文本检索(Text-Video Retrieval)领域，现有的主流方法通常依赖于CLIP等视觉语言预训练模型。然而，这些方法面临着两个主要挑战：一是&lt;strong&gt;跨模态鸿沟&lt;/strong&gt;，即如何有效对齐异构的视觉与语言数据；二是&lt;strong&gt;时间信息捕捉不足&lt;/strong&gt;。为了弥合鸿沟，现有研究尝试引入生成的辅助字幕(Caption)，但大多采用单一的“视频级字幕”(Video-Level Caption)。这种方法往往只能概括关键场景，容易丢失视频中随时间变化的丰富细节(如连续动作和局部属性)。此外，生成模型固有的“幻觉”问题可能导致字幕包含错误信息，从而误导检索过程，导致性能下降。因此，论文旨在寻找一种能捕捉时序丰富信息且能过滤错误干扰的新框架。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了解决上述问题，论文提出了 &lt;strong&gt;NarVid (Narrating the Video)&lt;/strong&gt; 框架。该框架的核心创新在于利用&lt;strong&gt;帧级字幕 (Frame-Level Captions)&lt;/strong&gt; 构成的 &lt;strong&gt;“叙述(Narration)”&lt;/strong&gt;，并将其贯穿于检索全过程。具体方法包含四个关键模块：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;视频-叙述交互(Cross-Modal Interaction)：&lt;/strong&gt; 利用 Co-Attention 机制，让视频帧特征与对应的帧级字幕特征互相增强，并通过时间块(Temporal Block)捕捉时序依赖。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询感知自适应过滤(Query-Aware Adaptive Filtering)：&lt;/strong&gt; 针对生成模型可能产生的幻觉，提出了一种基于核采样(Nucleus Sampling)的过滤机制。该机制根据查询文本(Query)的相似度，动态筛选掉无关或错误的帧/字幕特征，而非固定选取Top-k帧。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;双模态匹配(Dual-Modal Matching)：&lt;/strong&gt; 在计算相似度时，不仅计算“查询-视频”的相似度，还额外计算“查询-叙述”的相似度，并将两者融合，利用叙述中的丰富语义作为补充。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨视图难负样本损失(Cross-View Hard Negative Loss)：&lt;/strong&gt; 在训练阶段，结合“查询-视频”和“查询-叙述”两个视角来定义难负样本(Hard Negatives)，促使模型学习更具判别性的特征。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;NarVid 框架在四个主流基准数据集(MSR-VTT, MSVD, VATEX, DiDeMo)上均取得了&lt;strong&gt;State-of-the-Art (SOTA)&lt;/strong&gt; 的成绩。在最具代表性的 MSR-VTT 数据集上，NarVid 的 Text-to-Video R@1 指标达到了 &lt;strong&gt;52.7%&lt;/strong&gt;，显著优于基线模型 CLIP4Clip (44.5%) 和其他现有方法(如 Cap4Video 的 49.3%)。消融实验进一步证明了该方法的有效性：引入“叙述匹配”模块使 R@1 提升了 4.1%，而“自适应过滤”机制被证实能有效剔除无关信息，显著提升检索精度。实验结果充分验证了利用帧级叙述捕捉时间变化信息以及过滤错误特征的策略，能有效解决视频检索中的跨模态鸿沟问题。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;Explaining Domain Shifts in Language&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;Explaining Domain Shifts in Language: Concept erasing for Interpretable Image Classification&quot;
arxiv=&quot;2503.18483v1&quot;
paper_url=&quot;&quot;
code=&quot;https://github.com/joeyz0z/LanCE&quot;
rating=&quot;3&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;如果想让AI既有人类能看懂的解释力，又能像人类一样举一反三(跨域泛化)，不妨试试用“语言”去指挥视觉模型，把那些靠不住的“表面特征”给抹掉。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/LanCE.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;现有的概念瓶颈模型(CBMs)虽然通过人类可理解的概念(如颜色、纹理、形状)提升了模型的可解释性，但在面对未见过的视觉域(Out-of-Distribution, OOD)时，其泛化能力往往较差。论文指出，核心痛点在于这些模型倾向于依赖&lt;strong&gt;域特异性概念&lt;/strong&gt;(例如训练数据中苹果通常是“红色”且具有“蜡质光泽”)。当测试数据发生域偏移(如从真实照片变为卡通或素描)时，这些特定特征消失，导致模型性能大幅下降。因此，如何在不牺牲可解释性的前提下，消除这些域特异性概念对分类决策的负面影响，是本文旨在解决的关键问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了解决上述问题，作者提出了一种名为&lt;strong&gt;语言引导概念擦除(Language-guided Concept-Erasing, LanCE)&lt;strong&gt;的框架。该方法的核心创新在于利用&lt;/strong&gt;大语言模型(LLM)&lt;strong&gt;和&lt;/strong&gt;视觉语言模型(VLM)&lt;strong&gt;来辅助训练。具体而言，论文利用LLM(如GPT-3.5)生成大量未见域的描述符(如“素描”、“雕塑”、“3D模型”)，并利用VLM(如CLIP)计算这些描述符在视觉空间中的差异方向。基于此，论文设计了一个即插即用的&lt;/strong&gt;域描述符正交性(DDO)损失函数&lt;/strong&gt;。该损失函数强制分类器的权重与模拟出的域特异性概念激活值保持正交，从而在不改变模型架构和增加推理成本的情况下，有效地“擦除”了有害的概念关联，迫使模型仅依赖域共享的通用特征(如物体形状)进行判断。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了验证LanCE的有效性，研究在7个基准数据集上进行了评估，其中包括4个标准基准和3个新构建的极具挑战性的基准(涵盖从真实照片到卡通、雕塑及3D渲染的跨域场景)。实验结果表明，引入DDO正则化项后，模型在保持原始域(In-Distribution)分类精度几乎不变的同时，显著提升了在未见域(OOD)上的泛化能力。与现有的SOTA概念模型相比，LanCE在OOD准确率上平均提升了约3个百分点。此外，定性分析显示，经过LanCE训练的模型，其关注点确实从“颜色”、“背景”等干扰特征转移回了物体的核心形状特征，证明了该方法在提升鲁棒性和可解释性方面的双重优势。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;PATHEVAL&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;Evaluating Vision-Language Models as Evaluators in Path Planning&quot;
arxiv=&quot;2411.18711v4&quot;
paper_url=&quot;&quot;
code=&quot;https://github.com/MohamedAghzal/PathEval&quot;
huggingface=&quot;https://huggingface.co/datasets/maghzal/PathEval&quot;
rating=&quot;4&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;VLM的逻辑大脑很聪明，但眼睛（视觉编码器）看不清细节，若想用它做规划评价，得先给它配一副能看清“毫米级”差异的“眼镜”。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;尽管大语言模型(LLMs)和视觉语言模型(VLMs)展现出强大的推理能力，但它们在&lt;strong&gt;端到端的长程路径规划(End-to-End Path Planning)&lt;strong&gt;中表现不佳。这促使研究者思考一种新的范式：既然模型难以直接生成完美的路径，能否将其作为&lt;/strong&gt;“评价者”(Evaluator)&lt;strong&gt;或“批评者”(Critic)来评估传统算法生成的路径质量？这一思路基于“评价比生成更容易”的直觉。然而，现有的研究缺乏对VLMs在复杂路径规划场景下作为评价者能力的系统评估，特别是缺乏对&lt;/strong&gt;低级视觉感知&lt;/strong&gt;(如精确的距离、角度、间隙感知)与&lt;strong&gt;高级常识推理&lt;/strong&gt;相结合的考验。因此，作者旨在探究VLMs是否具备同时处理精细视觉细节和复杂语义指令的能力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了验证上述问题，作者提出了 &lt;strong&gt;PATHEVAL&lt;/strong&gt;，这是一个可控且可扩展的基准测试。该基准包含14,550个任务，基于1,150个不同的复杂环境(如迷宫、波浪形障碍等)和15种决策场景(如消防车需最大化覆盖面积、大型卡车需最小化急转弯等)构建。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;任务设计：&lt;/strong&gt; VLM 需要在给定场景描述的前提下，比较两条路径(Path 1 和 Path 2)的图像(2D或3D)，并选出更符合场景约束的路径。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;核心挑战：&lt;/strong&gt; 成功完成任务需要模型具备三种能力：&lt;strong&gt;属性抽象&lt;/strong&gt;(从文本中理解什么是好路径)、&lt;strong&gt;低级感知&lt;/strong&gt;(从图像中精确提取路径的几何特征，如最小间距、平滑度、急转弯数量)、&lt;strong&gt;信息整合&lt;/strong&gt;(结合视觉感知和文本需求做出决策)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实验评估了包括 GPT-4o 和多种开源模型(如 LLaVA, Qwen2-VL)在内的 9 个 SOTA VLMs，得出了以下关键结论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;视觉瓶颈(Visual Bottleneck)：&lt;/strong&gt; 模型在任务中的失败主要归因于&lt;strong&gt;视觉感知能力的不足&lt;/strong&gt;，而非语言推理能力。当研究者直接提供路径的数值特征(绕过视觉)时，模型准确率大幅提升(如 Qwen2-VL 从 50.2% 升至 74.2%)；但在仅看图像时，模型难以分辨细微的几何差异。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型表现差异：&lt;/strong&gt; &lt;strong&gt;GPT-4o&lt;/strong&gt; 是唯一显著优于随机猜测的模型，但在 3D 图像中仍受视觉错觉困扰；大多数&lt;strong&gt;开源模型&lt;/strong&gt;(如 LLaVA)表现接近随机水平(~50%)，且存在严重的&lt;strong&gt;幻觉&lt;/strong&gt;(Hallucination)，即编造视觉细节来支持其错误的选择。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;微调的局限性：&lt;/strong&gt; 实验发现，简单的端到端微调无法解决视觉编码器(Vision Encoder)的感知缺陷。论文指出，需要针对特定任务对视觉编码器进行专门的判别性适应(Discriminative Adaptation)，才能让 VLM 成为有效的路径评价者。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;CoT-VLA&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;CoT-VLA: Visual Chain-of-Thought Reasoning for Vision-Language-Action Models&quot;
arxiv=&quot;2503.22020v1&quot;
paper_url=&quot;&quot;
code=&quot;https://cot-vla.github.io/&quot;
rating=&quot;4&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;如果你想让机器人做复杂的家务，先让它在“脑海”里想象出做完后的样子（Visual CoT），再动手，这比直接瞎指挥要靠谱得多。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/CoT-VLA.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;现有的视觉-语言-动作模型(Vision-Language-Action Models, VLAs)虽然通过利用大规模预训练的视觉-语言模型(VLMs)和机器人演示数据，在学习通用的传感器运动控制方面展现出了巨大潜力，但它们存在明显的局限性。目前的主流 VLA 方法(如 RT-1, OpenVLA 等)主要侧重于从观测(Observation)到动作(Action)的&lt;strong&gt;直接映射(Direct Input-Output Mapping)&lt;/strong&gt;。这种范式缺乏中间的推理步骤，导致模型在处理需要复杂规划的长程任务时，缺乏时间上的推理和规划能力。此外，现有的 VLA 通常仅依赖带有动作标注的机器人演示数据，无法有效利用互联网上海量的、仅包含视频和语言描述的“无动作视频数据”(Action-less Videos)，这限制了其视觉理解能力的进一步提升。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了解决上述问题，论文提出了 &lt;strong&gt;CoT-VLA&lt;/strong&gt;，这是一种引入了显式&lt;strong&gt;视觉思维链(Visual Chain-of-Thought, Visual CoT)&lt;strong&gt;推理的 VLA 模型。该方法的核心理念是让模型在行动前先进行“视觉思考”：模型不再直接预测动作，而是首先自回归地生成一张&lt;/strong&gt;子目标图像(Subgoal Image)&lt;/strong&gt;，作为中间推理步骤，代表对未来状态的像素级规划；随后，模型基于当前观测和生成的子目标图像，生成一段短的动作序列(Action Chunk)来实现该目标。在架构设计上，CoT-VLA 基于能够理解和生成图像/文本的统一多模态基础模型 &lt;strong&gt;VILA-U&lt;/strong&gt;，并引入了&lt;strong&gt;混合注意力机制(Hybrid Attention)&lt;/strong&gt;——对图像/文本生成使用因果注意力，对动作预测使用全注意力，以支持并行动作解码。这种方法不仅解锁了利用 EPIC-KITCHENS 等无动作视频数据进行预训练的能力，还通过视觉推理增强了任务的可解释性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CoT-VLA 在模拟环境和真实世界的机器人操作任务中均取得了卓越的表现，证明了视觉思维链的有效性。在 &lt;strong&gt;LIBERO 模拟基准测试&lt;/strong&gt;中，CoT-VLA 的成功率达到了 &lt;strong&gt;81.13%&lt;/strong&gt;(Spatial 套件)，显著优于之前的 SOTA 模型 OpenVLA。在 &lt;strong&gt;真实世界实验&lt;/strong&gt;中，无论是 Bridge-V2 平台还是仅有少量演示数据的 Franka-Tabletop 设置，CoT-VLA 都展现出了更强的泛化能力。特别是在 Franka-Tabletop 的长程任务中，其成功率达到了 &lt;strong&gt;69.0%&lt;/strong&gt;，比 OpenVLA 高出了约 15%。消融实验证实，引入视觉思维链(Visual CoT)和混合注意力机制是性能提升的关键因素，且利用无动作视频数据进行预训练能显著提高模型在下游任务中的适应能力。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;CoLLM&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;CoLLM: A Large Language Model for Composed Image Retrieval&quot;
arxiv=&quot;2503.19910v1&quot;
paper_url=&quot;&quot;
code=&quot;https://collm-cvpr25.github.io/&quot;
rating=&quot;3&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;CoLLM用LLM合成数据+插值法巧解CIR痛点，证明方法巧思比堆参数更有效&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/CoLLM-1.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/CoLLM-2.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;论文指出，当前的CIR研究面临四大核心挑战，导致难以在实际场景中广泛应用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据获取极其困难(Data Scarcity)：&lt;/strong&gt; 监督学习需要大量高质量的三元组数据(参考图、修改文本、目标图)。人工标注这些数据既昂贵又耗时，导致现有的CIR数据集规模小、覆盖域有限。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;现有替代方案的局限性：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;零样本方法：&lt;/strong&gt; 依赖现成的图文对(Image-Caption Pairs)，缺乏针对CIR任务的特定联合嵌入学习，效果受限。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合成数据集：&lt;/strong&gt; 现有的合成数据(如LaSCo, WebCoVR)往往规模不足，且生成的修改文本生硬、缺乏多样性，无法模拟真实的人类查询习惯。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型理解能力不足(Model Limitation)：&lt;/strong&gt; 现有的CIR模型通常使用浅层Transformer或简单的线性插值来融合图像和文本，缺乏对复杂语义关系的深度理解。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;评估基准存在歧义(Evaluation Noise)：&lt;/strong&gt; 主流基准(如CIRR, Fashion-IQ)中存在大量歧义样本(即一个查询可能对应多个正确的答案)，这导致模型评估结果不可靠，难以反映模型的真实性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了解决上述问题，论文提出了 &lt;strong&gt;CoLLM&lt;/strong&gt; 框架，这是一个基于大语言模型(LLM)的CIR解决方案，包含三个核心创新点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基于图文对的动态三元组合成(Triplet Synthesis)：&lt;/strong&gt;
CoLLM 不依赖人工标注的CIR三元组，而是利用海量现成的图文对进行训练。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;图像端：&lt;/strong&gt; 利用球面线性插值(Slerp)在批次内找到最近邻图像，合成“参考图像嵌入”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文本端：&lt;/strong&gt; 利用预定义的模板(如“将A改为B”)，结合原图和邻近图的描述，自动生成自然的“修改文本”。&lt;/li&gt;
&lt;li&gt;这一策略使得模型可以在没有人工CIR数据的情况下进行监督训练。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;基于LLM的深度查询组合(LLM-based Query Composition)：&lt;/strong&gt;
与传统方法使用浅层网络不同，CoLLM 利用预训练的 &lt;strong&gt;LLM(或专门的嵌入模型 LLEM)&lt;/strong&gt; 来处理多模态查询。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模型将合成的参考图像嵌入和修改文本同时输入LLM。&lt;/li&gt;
&lt;li&gt;利用LLM强大的世界知识和语义理解能力，深度挖掘视觉与语言之间的复杂关系，生成更精准的联合嵌入向量。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;构建高质量数据集与基准(MTCIR &amp;amp; Refined Benchmarks)：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MTCIR数据集：&lt;/strong&gt; 论文利用LLM生成了包含 &lt;strong&gt;340万&lt;/strong&gt; 图像对和 &lt;strong&gt;1770万&lt;/strong&gt; 修改文本的合成数据集。该数据集的特点是包含真实图像，并为每对图像提供多条简短、自然的修改文本(平均每对5.18条)，覆盖多种属性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;精修基准测试集：&lt;/strong&gt; 利用Claude 3 Sonnet对现有的CIRR和Fashion-IQ验证集进行清洗，剔除歧义样本并重写模糊的文本，从而提供更可靠的评估环境。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;论文在多个标准CIR基准上进行了广泛实验，结果证明了CoLLM的优越性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SOTA 性能：&lt;/strong&gt; CoLLM 在 CIRCO、CIRR 和 Fashion-IQ 等多个基准测试中均取得了 &lt;strong&gt;State-of-the-Art (SOTA)&lt;/strong&gt; 的成绩。即使在不使用任何CIR三元组进行预训练的情况下，其表现也优于许多现有的零样本方法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据集有效性验证：&lt;/strong&gt; 当使用论文提出的 &lt;strong&gt;MTCIR&lt;/strong&gt; 数据集进行微调时，模型性能大幅提升。例如在CIRR数据集上，Recall@1 指标显著优于 MagicLens 和 CoVR2 等现有最佳模型，证明了合成数据的质量和有效性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;消融实验结论：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LLEM优于LLM：&lt;/strong&gt; 实验发现，专门用于检索任务微调的嵌入模型(如 E5-Mistral)比通用的生成式LLM表现更好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;评估基准的改进：&lt;/strong&gt; 在经过清洗的“精修基准”上，CoLLM 依然保持领先，且模型排名在清洗前后保持一致，验证了新基准的可靠性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据质量为王：&lt;/strong&gt; 尽管 MTCIR 的数据量级在合成数据中并非最大，但因其高质量和多样性，训练出的模型效果远超基于其他合成数据集训练的模型。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;Beyond Sight&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;Beyond Sight: Towards Cognitive Alignment in LVLM via Enriched Visual Knowledge&quot;
arxiv=&quot;2411.16824v1&quot;
paper_url=&quot;&quot;
code=&quot;&quot;
rating=&quot;4&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;不要盲目堆砌数据，要通过“实体增强”的监督让视觉特征去主动适配语言模型的认知框架，用高质量的“多粒度对齐”打通视觉与语言的关联。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/BeyondSight.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;论文首先提出了一个核心疑问：“看见(Seeing)是否意味着知道(Knowing)？”研究者发现，即使是最先进的模型(如GPT-4o、Qwen2-VL)，在面对标志性建筑图片时往往无法识别，尽管它们在纯文本问答中能准确描述该建筑。这揭示了**“认知不对齐”(Cognitive Misalignment)**这一根本问题：视觉编码器(VE)提取的特征与大语言模型(LLM)的认知框架之间存在断层。作者通过构建多粒度地标数据集(MGLD)进行分析，发现单纯增加数据量无效，反而会引入“VE-Unknown”数据(即视觉特征模糊、缺乏区分度的数据)，这些数据阻碍了视觉与语言模态的有效整合。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了解决上述问题，论文提出了&lt;strong&gt;实体增强认知对齐(Entity-Enhanced Cognitive Alignment, EECA)&lt;strong&gt;框架，旨在让视觉Token“模仿”VE-Known的特征，从而打开LVLM的“认知之眼”。该方法包含两个核心部分：首先，设计了一个&lt;/strong&gt;双分支视觉架构&lt;/strong&gt;，结合高分辨率(HR)和低分辨率(LR)特征，利用HR分支捕捉丰富的细节信息；其次，引入了&lt;strong&gt;多粒度监督机制&lt;/strong&gt;，包含“实体感知对比损失(Entity-Aware Contrastive Loss)”和“分层分类损失(Hierarchical Classification Loss)”。前者强制高分辨率Token与LLM嵌入空间中的实体特征对齐，后者则通过层级标签建立跨类别的区分能力，从而确保视觉Token既丰富又具有判别力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实验结果验证了EECA在提升LVLM认知对齐方面的有效性。在地标识别任务中，EECA不仅在VE-Known数据上表现出色，更显著提升了模型对原本难以识别的VE-Unknown数据的理解能力。研究显示，&lt;strong&gt;数据质量远胜于数量&lt;/strong&gt;：仅使用&lt;strong&gt;25k&lt;/strong&gt;数据训练的EECA模型，其性能就能达到甚至超越使用&lt;strong&gt;125k&lt;/strong&gt;基线数据训练的模型水平。此外，消融实验证明，引入实体感知监督和分层分类损失是性能提升的关键因素，该方法在不同类型的视觉数据上均展现出了卓越的鲁棒性和泛化能力。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
&lt;h1&gt;AnySat&lt;/h1&gt;
&lt;p&gt;&amp;lt;Paper
title=&quot;AnySat: One Earth Observation Model for Many Resolutions, Scales, and Modalities&quot;
arxiv=&quot;2412.14123v3&quot;
paper_url=&quot;&quot;
code=&quot;https://github.com/gastruc/AnySat&quot;
rating=&quot;4&quot;
ratingIcon=&quot;🍋&quot;
tip=&quot;AnySat通过Scale-Adaptive编码与JEPA架构的巧妙结合，成功打破了遥感多模态与多分辨率的壁垒，是迈向真正通用地球观测基础模型的关键一步。&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/AnySight-1.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;img src={images[&quot;./assets/FoundAI-1/AnySight-2.png&quot;].src} alt=&quot;image&quot; style=&quot;width:100%;&quot; /&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;遥感(Earth Observation, EO)数据与自然图像(如RGB照片)存在本质差异，表现出极高的异构性。现有的EO基础模型通常针对单一特定数据集(如仅Sentinel-2)进行训练，往往要求固定的输入配置(如特定的分辨率、波段数或模态组合)。这种刚性设计导致模型无法直接应用于不同传感器配置的数据，一旦遇到分辨率或模态不同的场景，往往需要重新训练或进行复杂的调整，这违背了基础模型应具备的通用性和灵活性。遥感数据在空间分辨率(从0.2米到250米)、时间频率(单时相到时间序列)和光谱模态(光学、雷达等)上存在巨大差异，因此亟需一种能够无缝集成多源异构数据的统一架构。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了解决上述问题，论文提出了 &lt;strong&gt;AnySat&lt;/strong&gt;，这是一种基于**联合嵌入预测架构(JEPA)&lt;strong&gt;和&lt;/strong&gt;尺度自适应块编码(Scale-Adaptive Patch Encoding)**的通用地球观测模型。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;尺度自适应编码(核心创新)&lt;/strong&gt;：AnySat不直接处理原始Patch，而是将其细分为固定物理尺寸的Sub-patches。这一机制使得模型能够处理任意大小的输入Patch和任意的空间分辨率，而无需改变网络参数，实现了参数在不同分辨率下的共享。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JEPA自监督学习&lt;/strong&gt;：不同于传统的掩码自编码器(MAE)试图在像素级重建图像，AnySat采用JEPA框架，在特征空间而非像素空间进行预测。它利用一个“学生网络”预测被遮挡区域的特征，并使其逼近由“教师网络”(通过EMA更新)提取的目标特征。这种方法避开了多模态数据在像素级重建的困难，直接学习语义一致的高层特征。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多模态融合&lt;/strong&gt;：通过Modality-Combiner网络，利用交叉注意力机制将不同模态(如SAR和光学)在同一空间位置的特征进行融合。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AnySat在包含GeoPlex内部测试集和6个外部数据集的广泛评估中，展现了卓越的泛化能力和SOTA(State-of-the-Art)性能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多任务SOTA表现&lt;/strong&gt;：在9个下游任务(包括土地覆盖分类、树种识别、作物分类、洪水监测和变化检测等)中，AnySat均达到了最佳性能。例如，在TreeSatAI-TS上加权F1分数提升了0.9，在PASTIS-HD上分割mIoU提升了0.2。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;强大的泛化能力&lt;/strong&gt;：AnySat成功应用于&lt;strong&gt;未参与训练&lt;/strong&gt;的传感器配置。例如，尽管训练数据中没有HLS(Harmonized Landsat-Sentinel)数据，AnySat通过微调新投影层，其在HLS Burn Scar检测任务上的表现超越了在海量HLS数据上训练的同类模型(如Prithvi)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高效性与线性探测&lt;/strong&gt;：得益于JEPA学习到的高质量特征，AnySat在仅使用线性探测(Linear Probing，即冻结主干网络仅训练线性分类器)的情况下，也能在Sen1Floods11等数据集上取得极具竞争力的结果，证明了其特征表达的鲁棒性。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;/Paper&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>大模型技术知识点 3</title><link>https://adalovelemon.github.io/posts/content/technotes/llm-misc/llm-misc3/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/llm-misc/llm-misc3/</guid><pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;大模型的一些应知知识点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;16. SFT 训练到什么程度，才适合做 RL？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SFT 训练的是模型补充文本的基础能力 (token-level)，RL 训练的是模型在特定任务上的表现能力 (sequence-level)。&lt;/li&gt;
&lt;li&gt;RL 的训练需要有正向奖励，当模型本身的基础能力不足时，模型可能只能得到负向反馈，导致 RL 训练效果不佳。&lt;/li&gt;
&lt;li&gt;RL 的好处在于不需要大量人工标注的数据，只需要给出人类的偏好反馈即可进行训练。&lt;/li&gt;
&lt;li&gt;当模型具有稳定的指令跟随能力和较好的生成质量时，就可以考虑使用 RL 进行进一步优化。&lt;/li&gt;
&lt;li&gt;如果模型对一个 prompt 采样出来的回答 reward 分布已经相对均匀 (不是都是低分负反馈)，同时接下来的训练目标是在多个候选答案中选优，而不是让模型的回答遵循固定的格式，当算力允许时 (RL 通常需要 2~4 倍 SFT 的算力)，就可以考虑使用 RL 进行训练。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;17. 在什么场景下，传统的 RAG 已经不够用了，必须要用上 GraphRAG?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传统 RAG (Vector-based):&lt;/strong&gt; 擅长&lt;strong&gt;语义相似度匹配&lt;/strong&gt;。比如问“苹果公司的财报”，它能找到包含“苹果”、“财报”、“收入”等词义相近的片段。
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;痛点:&lt;/em&gt; 它是“断章取义”的。如果答案分散在三篇不同的文档里，需要通过逻辑串联才能得出，传统 RAG 往往只能返回这三个片段，让大模型去“猜”关系，容易幻觉或遗漏。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GraphRAG (Knowledge Graph-based):&lt;/strong&gt; 擅长&lt;strong&gt;结构化推理和全局理解&lt;/strong&gt;。它先构建图谱（实体+关系），检索时不仅看语义，还走“路径”。
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;优势:&lt;/em&gt; 它能发现“隐藏连接”。比如 A 公司投资了 B，B 公司的 CEO 是 C，C 曾就读于 D 大学。传统 RAG 很难一次性把这条链找全，但 GraphRAG 可以通过图遍历直接锁定这条路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当应用场景出现以下特征时，传统 RAG 通常不够用，&lt;strong&gt;必须&lt;/strong&gt;引入 GraphRAG：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;场景一：多跳推理查询 (Multi-hop Reasoning)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;问题示例:&lt;/strong&gt; “哪些由‘红杉资本’投资的‘人工智能’公司，其创始人在‘斯坦福大学’读过书，且该公司在 2024 年有过并购行为？”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传统 RAG 表现:&lt;/strong&gt; 可能会分别搜到“红杉投资列表”、“斯坦福校友名单”、“2024 并购新闻”，但很难将这三者精准交集在一起，因为它不知道这些碎片属于同一个实体链条。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GraphRAG 表现:&lt;/strong&gt; 直接在图谱上执行多跳查询（Red Sequoia -&amp;gt; invests -&amp;gt; AI Company -&amp;gt; founded by -&amp;gt; Person -&amp;gt; educated at -&amp;gt; Stanford AND Company -&amp;gt; acquired -&amp;gt; 2024）。这是图数据库的强项。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;场景二：全局性/宏观总结查询 (Global Understanding)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;问题示例:&lt;/strong&gt; “这份包含 10,000 份警方笔录的数据集中，主要的犯罪团伙结构是怎样的？他们之间的核心联系纽带是什么？”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传统 RAG 表现:&lt;/strong&gt; 只能返回几个最相似的笔录片段。它无法回答“整体结构”或“主要趋势”，因为它没有全局视图（Global View）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GraphRAG 表现:&lt;/strong&gt; 利用你提到的&lt;strong&gt;局部 Summary (Community Summaries)&lt;/strong&gt; 机制。GraphRAG 会将图谱聚类成不同的社区（Community），为每个社区生成摘要。回答宏观问题时，它直接调用这些高层级的摘要，而不是去读原始的 10,000 个片段。这是 Microsoft GraphRAG 论文中最核心的创新点之一。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;场景三：实体消歧与精确关系查找&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;问题示例:&lt;/strong&gt; “马斯克 (Elon Musk) 和 马斯克 (Justine Musk) 现在的关系状态及其共同涉及的商业实体有哪些？”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传统 RAG 表现:&lt;/strong&gt; 容易混淆同名实体，或者因为文本中只写了“马斯克”而无法区分指代谁，导致检索出大量无关信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GraphRAG 表现:&lt;/strong&gt; 在构建图谱阶段就已经完成了&lt;strong&gt;实体解析 (Entity Resolution)&lt;/strong&gt;，明确了两个不同的节点。查询时直接定位到特定节点及其边，精度极高。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;场景四：动态更新与溯源要求极高的场景&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;问题示例:&lt;/strong&gt; 金融风控、法律案件分析、医疗药物相互作用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需求:&lt;/strong&gt; 不仅需要答案，还需要清晰的&lt;strong&gt;证据链&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GraphRAG 表现:&lt;/strong&gt; 可以清晰地展示推理路径（Path），告诉用户：“结论是基于 A-&amp;gt;B-&amp;gt;C 这条关系链得出的”，可信度远高于传统 RAG 的“基于相似度匹配的几段文字”。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;虽然 GraphRAG 在上述场景无敌，但&lt;strong&gt;并不是所有场景都要用它&lt;/strong&gt;，因为它的成本较高：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;构建成本高:&lt;/strong&gt; 需要额外的 LLM 调用次数来抽取实体、关系和生成社区摘要。索引构建时间比传统 RAG 慢得多（可能是几十倍）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实时性挑战:&lt;/strong&gt; 如果数据每秒都在变，维护一个实时的知识图谱比更新向量索引要复杂得多（尽管增量更新技术正在进步）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简单问答杀鸡用牛刀:&lt;/strong&gt; 如果用户只是问“文档里关于‘休假政策’是怎么说的？”，传统 RAG 响应更快、成本更低，效果也足够好。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;目前业界的主流架构往往是 &lt;strong&gt;Hybrid RAG (混合模式)&lt;/strong&gt;：同时保留向量索引和图索引，根据问题的复杂度路由到不同的检索路径，或者将两者的结果进行重排序 (Re-ranking)，以兼顾成本和效果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;18. 为什么大部分神经网路模型都有升维降维操作？为什么升维通常是升到原来维度的 4 倍？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;升维的本质是为了&lt;strong&gt;增强模型的自由度和表达能力&lt;/strong&gt;。通过将特征投影到更高维的空间，模型可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;学习更复杂的特征组合&lt;/li&gt;
&lt;li&gt;捕捉更丰富的非线性关系&lt;/li&gt;
&lt;li&gt;类似于传统 MLP 中扩展隐藏层以增强模型容量的思路&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;降维的目的&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;保持残差连接的维度一致性&lt;/strong&gt;：Transformer 中有残差连接（Residual Connection），输入和输出维度必须相同才能相加&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;控制计算复杂度&lt;/strong&gt;：避免维度爆炸，让模型可以堆叠更多层&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;信息压缩与提炼&lt;/strong&gt;：将高维空间中学习到的特征重新投影回原始空间，供下一层使用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为什么通常是 4 倍？&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;原因&lt;/th&gt;
&lt;th&gt;解释&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;激活函数导致的秩损失&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;使用 ReLU/GELU 等激活函数时，部分神经元会输出 0，导致矩阵&lt;strong&gt;降秩&lt;/strong&gt;。研究表明，为了保证降维后矩阵秩不低于输入维度，中间维度需要至少是输入的 2 倍以上，4 倍是经验上的安全值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;表达力与效率的平衡&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 倍可能不够充分，8 倍计算成本太高，4 倍是在&lt;strong&gt;模型容量&lt;/strong&gt;和&lt;strong&gt;计算开销&lt;/strong&gt;之间的经验最优解&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;历史沿袭&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;原始 Transformer 论文（Attention Is All You Need, 2017）设定为 4 倍，后续模型（BERT、GPT 系列等）沿用这一设计，成为行业惯例&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;值得注意的是，&lt;strong&gt;4 倍并不是绝对标准&lt;/strong&gt;，最新模型有不同设计：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模型&lt;/th&gt;
&lt;th&gt;FFN 维度比例&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;原始 Transformer&lt;/td&gt;
&lt;td&gt;4×&lt;/td&gt;
&lt;td&gt;确立基本架构&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BERT&lt;/td&gt;
&lt;td&gt;4×&lt;/td&gt;
&lt;td&gt;使用 GELU 替代 ReLU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLaMA&lt;/td&gt;
&lt;td&gt;8/3 × 2 (约 5.3×)&lt;/td&gt;
&lt;td&gt;使用 SwiGLU 激活函数，更高效的维度利用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4&lt;/td&gt;
&lt;td&gt;MoE 架构&lt;/td&gt;
&lt;td&gt;多个专家网络，极大增加容量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;某些高效模型&lt;/td&gt;
&lt;td&gt;2×~3×&lt;/td&gt;
&lt;td&gt;在资源受限场景下压缩维度&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>变分法基础</title><link>https://adalovelemon.github.io/posts/content/coursenotes/mathslab/variationalcalculus/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/mathslab/variationalcalculus/</guid><pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;变分学是研究泛函极值（以及更一般的临界值）的一个数学分支，在机器学习、物理学等领域有广泛的应用。&lt;/p&gt;
&lt;h1&gt;1. 泛函&lt;/h1&gt;
&lt;h2&gt;1.1 泛函的定义&lt;/h2&gt;
&lt;p&gt;在数学中，&lt;strong&gt;泛函&lt;/strong&gt;（Functional）是指定义域为某个函数空间，而值域为数域（如实数域或复数域）的映射。更具体地说，设 $M$ 是一个函数空间（例如 $C^k(S, \mathbb{R})$，表示定义在集合 $S$ 上的 $k$ 阶连续可微实值函数的全体），定义泛函是一个从 $M$ 到 $\mathbb{R}$ 或 $\mathbb{C}$ 的映射
$$
I: M \to \mathbb{R} \quad \text{or} \quad I: M \to \mathbb{C}
$$
即对每个 $u \in M$，$I[u]$ 是一个实数或复数。泛函的自变量是“函数”，而不是通常意义上的数。&lt;/p&gt;
&lt;p&gt;例如，设 $\Omega \subset \mathbb{R}^n$ 是一个有界开集，$x_0 \in \Omega$ 是一个固定点，$F \in C(\bar\Omega), M = C^1(\bar\Omega)$，其中 $\bar\Omega$ 是 $\Omega$ 的闭包，则
$$
I_1[u] = \max_{x \in \bar\Omega} |u(x)|
$$
$$
I_2[u] = u(x_0)
$$
$$
I_3[u] = \int_\Omega \left[|\nabla u(x)|^2 - F(u(x))\right] dx
$$
都是泛函。这里需要注意的是，泛函不是复合函数。例如，设 $f$ 是一个定义在实数域 $\mathbb{R}$ 上的一元函数，
$$
I_4[u] = f(u(x))
$$
不是泛函，因为结果不是一个数，而是一个关于 $x$ 的函数，泛函的值是不应该依赖于 $x$ 的！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fourier 变换不是泛函&lt;/strong&gt;，虽然形式上看，$\mathcal{F}[u(t)]$ 确实也有一个方括号，但是它输出的是一个函数，所以不是泛函。从本质上讲，Fourier 变换是一个算子（Operator），它是定义在函数空间上的映射，输入一个函数，输出一个函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.2 变分学中泛函的形式&lt;/h2&gt;
&lt;p&gt;在变分学中，泛函的取值一般是实数。给定一个三变量函数 $L(t, u, p) \in C^1(\bar\Omega \times \mathbb{R}^n \times \mathbb{R}^n, \mathbb{R})$，变分学主要研究如下形式的泛函:
$$
I[u] = \int_\Omega L(x, u(x), \nabla u(x))dx,
$$
其中 $M$ 是连续可微函数类 $C^1(\bar\Omega, \mathbb{R})$ 的子集合，或者某种广义可微函数类的子集合。&lt;/p&gt;
&lt;h2&gt;1.3 一些例子&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;最速降线&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在垂直平面上给定两点 $A = (x_1, y_1)$ 和 $B = (x_2, y_2)$，其中 $x_1 &amp;lt; x_2, y_1 &amp;gt; y_2$. 一个值点沿着一条连接这两点的光滑曲线仅凭借重力下滑。设初速度为零，问沿怎样的一条曲线滑行时间最短？&lt;/p&gt;
&lt;p&gt;设 $u \in C^1[x_1, x_2], {(x, u(x)) | x \in [x_1, x_2], u(x_i) = y_i, i = 1, 2}$ 是连接 $A, B$ 的一条曲线。因为有
$$
\begin{cases}
\frac{1}{2}mv^2 = mgh, \
v = \frac{ds}{dt}
\end{cases}
$$
所以
$$
v = \sqrt{2g(y_1 - u(x))}.
$$
以及
$$
dt = \frac{ds}{v} = \sqrt{\frac{1 + |u&apos;(x)|^2}{2g(y_1 - u(x))}}
$$
因此滑行时间为
$$
T = \int_{x_1}^{x_2} \sqrt{\frac{1 + |u&apos;(x)|^2}{2g(y_1 - u(x))}} dx.
$$
令
$$
M = {u \in C^1[x_1, x_2] | u(x_i) = y_i, i = 1, 2}
$$
则映射
$$
I: M \to \mathbb{R}, I[u] = \int_{x_1}^{x_2} \sqrt{\frac{1 + |u&apos;(x)|^2}{2g(y_1 - u(x))}} dx
$$
是一个泛函。问题转化为在 $M$ 中求 $u$ 以使得 $I$ 最小。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;极小曲面&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在空间 $\mathbb{R}^3$ 中给定一条 Jordan 曲线 $\Gamma$，能否找到一个盘状的曲面 $S$ 张在 $\Gamma$ 上使其面积达到最小值？
首先定义从单位圆 $D$ 到 $\mathbb{R}^3$ 的参数化映射（曲面函数）
$$
Z: \bar D \to \mathbb{R}^3, (u, v) \mapsto \vec{r}(u, v) = (x(u, v), y(u, v), z(u, v)),
$$
其中 $D \subset \mathbb{R}^2$ 是单位圆 $u^2 + v^2 \le 1, \bar D$ 为 $D$ 的闭包。需要注意的是，这里区分了 $S$ 和 $Z$ 是因为 $S$ 是一个几何对象，而 $Z$ 是一个函数对象，$Z$ 的值域是 $S$，但 $Z$ 不是 $S$。因此我们可以把 $S$ 看成是 $Z$ 的值域，或者说 $Z$ 是 $S$ 的一个参数化，这个关系描述为
$$
S = Z(\bar D) \subset \mathbb{R}^3,
$$
也就是 $S$ 是 $Z$ 的值域。
$$\vec{r}(u, v) = (x(u, v), y(u, v), z(u, v)) \Longleftrightarrow
\begin{cases}
x = x(u, v), \
y = y(u, v), \
z = z(u, v).
\end{cases}
$$
曲线的面积为
$$
A(Z) = \int_D |Z_u \times Z_v| dudv = \int_D \sqrt{(x_uy_v - x_vy_u)^2 + (x_uz_v - x_vz_u)^2 + (y_uz_v - y_vz_u)^2} dudv.
$$
面积 $A(Z)$ 就是关于曲面函数 $Z$ 的一个泛函。&lt;/p&gt;
&lt;p&gt;这个泛函需要满足边界条件 $Z|&lt;em&gt;{\partial D}$ 与 $\Gamma$ 同胚。转换为标准的泛函模型,
$$
M = {Z \in C^1(\bar D, \mathbb{R}^3) |,, Z|&lt;/em&gt;{\partial D} \simeq \Gamma}
$$
然后求在这个函数集合上的 $A(Z)$ 的极小值。&lt;/p&gt;
&lt;p&gt;这里的有几个符号需要澄清&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\Gamma = \partial S$ 表示曲面 $S$ 的边界是 $\Gamma$。&lt;/li&gt;
&lt;li&gt;$Z|_{\partial D} \simeq \Gamma$ 表示 $Z$ 在边界 $\partial D$ 上的取值集合与 $\Gamma$ 同胚。、&lt;/li&gt;
&lt;li&gt;$Z|_{\partial D} \simeq \Gamma$ 这个同胚关系是由同胚映射 $Z$ 推导出来的，因为 $\bar D$ 与 $S$ 是同胚的，所以 $\partial D$ 与 $\Gamma$ 也是同胚的。&lt;/li&gt;
&lt;li&gt;同胚就是指两个集合之间存在一个双射，并且这个双射和它的逆都是连续的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;图像分割&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在一张图片中查明人像的边缘。设此图形占有平面区域 $\Omega \subset \mathbb{R}^2$，用函数 $g: \Omega \to \mathbb{R}$ 表示这张图片（信号强度）。我们需要寻求另一个函数 $u: \Omega \to \mathbb{R}$，使之在人像的边缘处与原图像尽量吻合，且在其余出尽量不要有多余的响应。为了描写像的边缘，引入具有有限一维 Hausdorff 测度的闭子集合 $K \subset \bar\Omega, H^1(K) &amp;lt; \infty$，其中 $H^1(K)$ 是 $K$ 的一维 Hausdorff 测度。定义
$$
I(K, u) = \underbrace{\int_{\Omega / K} |\nabla u|^2 dx dy}&lt;em&gt;{\text{Smoothing Term}} + \mu \underbrace{\int&lt;/em&gt;{\Omega / K} |u - g|^2 dxdy}&lt;em&gt;{\text{Data Fitting Term}} + \underbrace{\lambda H^1(K)}&lt;/em&gt;{\text{Edge Length Penalty Term}}
$$
其中 $\lambda, \mu &amp;gt; 0$ 是权系数。这里的 $I$ 不仅依赖于函数 $u$，而且还依赖于闭子集 $K$，这个集合可以看作是一个特征函数
$$
\chi_K(x) = \begin{cases}1, \quad x \in K, \
0, \quad x \in \Omega/K
\end{cases}
$$
因此 $I$ 也是一个泛函，且依赖于 $\chi_K$ 和 $u$。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$H^1(K)$ 是 $K$ 的一维 Hausdorff 测度，这里就是 $K$ 的曲线长度。&lt;/li&gt;
&lt;li&gt;平滑项的作用是减少发生强度的突变。&lt;/li&gt;
&lt;li&gt;$K$ 在这里表示人像的边缘区域，考虑到边缘区域未知，因此它也是一个变化量。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 泛函的极值与一阶变分&lt;/h1&gt;
&lt;h2&gt;2.1 直觉动机&lt;/h2&gt;
&lt;p&gt;函数的导数（梯度）刻画的是在给定点 $x_0$ 处，对自变量 $x$ 给予微小扰动时对应函数 $f(x)$ 的函数值变化情况。极值点处的导数（梯度）对应是零，也就是说在极值点处，施加微小扰动，函数值是稳定且几乎不变化的。&lt;/p&gt;
&lt;p&gt;参考函数极值的必要条件，我们对函数 $u_0(t)$ 施加其邻域空间 (是&lt;strong&gt;函数空间&lt;/strong&gt;) 中的微小扰动 $\varepsilon \varphi(t)$, 然后观察泛函 $I[u]$ 的变化情况。如果泛函关于扰动量大小 $\varepsilon$ 的变化率 $\lim_{\varepsilon \to 0} \frac{I[u_0 + \varepsilon\varphi] - I[u_0]}{\varepsilon} = 0$，则说明 $I[u]$ 在 $u_0$ 附近是稳定的，$u_0$ 有可能就是泛函 $I[u]$ 的一个“极值点”。&lt;/p&gt;
&lt;h2&gt;2.2 泛函极值的定义（一元函数的例子）&lt;/h2&gt;
&lt;p&gt;给定两个集合 $J \subset \mathbb{R}$，$\Omega \subset \mathbb{R}$，和一个连续可微函数 $L(t, u, p) \in C^1(J \times \Omega \times \mathbb{R}, \mathbb{R})$。设泛函为
$$
I[u] = \int_J L(t, u(t), \dot u(t))dt,
$$
其中 $u \in M$，$M = C^1(J, \Omega)$ 是泛函 $I[u]$ 的定义域。&lt;/p&gt;
&lt;p&gt;给定一个函数 $u_0 \in M$。若存在 $u_0$ 的一个邻域 $U \subset M$，使得
$$
I[u] \ge I[u_0], \quad \forall u \in U,
$$
则称 $u_0$ 是 $I[u]$ 的一个极小值点。类似地，当满足条件
$$
I[u] \le I[u_0], \quad \forall u \in U,
$$
则称 $u_0$ 是 $I[u]$ 的一个极大值点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;函数空间是必须由&lt;strong&gt;函数的定义域&lt;/strong&gt; $J$ 和&lt;strong&gt;函数的值域&lt;/strong&gt; $\Omega$ 共同确定的，这意味着函数 $u_0$ 邻域 $U$ 中的全体函数必须具有与 $u_0$ 相同的定义域和相同的值域。&lt;/li&gt;
&lt;li&gt;符号规范上，$J$ 表征的是时间的集合，$\Omega$ 表征的是状态的集合。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.3 一阶变分的定义&lt;/h2&gt;
&lt;p&gt;继续挖掘泛函 $I[u]$ 对扰动大小 $\varepsilon$ 的变化率。由于 $L \in C^1(J \times \Omega \times \mathbb{R}, \mathbb{R})$ 且 $\varphi \in C^1(J, \mathbb{R})$，因此可以交换求导与积分:
$$
\begin{aligned}
&amp;amp;\lim_{\varepsilon \to 0} \frac{I[u_0 + \varepsilon\varphi] - I[u_0]}{\varepsilon} \
=\ &amp;amp;\frac{\partial I[u_0 + \varepsilon\varphi]}{\partial \varepsilon}\Big|&lt;em&gt;{\varepsilon = 0} \
=\ &amp;amp; \frac{\partial}{\partial \varepsilon} \int_J L(t, u_0 + \varepsilon\varphi, \dot u_0 + \varepsilon\dot\varphi) dt\Big|&lt;/em&gt;{\varepsilon = 0} \
=\ &amp;amp;\int_J \left[L_{u}(t, u_0(t), \dot u_0(t))\varphi(t) + L_{p}(t, u_0(t), \dot u_0(t))\dot\varphi(t)\right] dt
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;计算中，发现这个变化率本身的数值与 $\varepsilon$ 的取值是无关的，它是一个关于 $\varepsilon$ 的线性项的系数。既然与 $\varepsilon$ 的数值无关，考虑到扰动项本身由 $\varepsilon$ 和 $\varphi$ 两部分构成，因此变化率 $\lim_{\varepsilon \to 0} \frac{I[u_0 + \varepsilon\varphi] - I[u_0]}{\varepsilon}$ 就可以认为是由 $\varphi$ 部分引起的、消除了 $\varepsilon \to 0$ 尺度影响的，归一化了的量。&lt;/p&gt;
&lt;p&gt;我们定义这个量为一阶变分，记作 $\delta I[u_0, \varphi]$，意即由扰动函数 $\varphi$ 引起的、在 $u_0$ 处的泛函 $I$ 的一阶变化量:
$$
\boxed{
\delta I[u_0, \varphi] = \lim_{\varepsilon \to 0} \frac{I[u_0 + \varepsilon\varphi] - I[u_0]}{\varepsilon} = \frac{\partial I[u_0 + \varepsilon\varphi]}{\partial \varepsilon}\Big|&lt;em&gt;{\varepsilon = 0}
}
$$
具体的计算公式为
$$
\boxed{
\delta I[u_0, \varphi] = \int_J \left[L&lt;/em&gt;{u}(t, u_0(t), \dot u_0(t))\varphi(t) + L_{p}(t, u_0(t), \dot u_0(t))\dot\varphi(t)\right] dt
}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\varphi$ 的值域不一定是 $\Omega$，作为扰动函数，其取值可以是 $R / \Omega$ 中的任意数值。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.4 泛函极值的必要条件&lt;/h2&gt;
&lt;p&gt;直觉告诉我们，极值点处的一阶变分应该为零。下面我们通过从极值点的定义出发，推导出这个结论。&lt;/p&gt;
&lt;p&gt;这里我们首先取 $U = {u \in M \big| \Vert u - u_0\Vert &amp;lt; \delta}$ (范数的定义可以自行选取)。目标是对任意的 $\varphi \in C^1(J, \mathbb{R})$，都能存在 $\varepsilon(\varphi) &amp;gt; 0$，使得当 $0 &amp;lt; |\varepsilon| &amp;lt; \varepsilon(\varphi)$ 时，有
$$
\Vert (u_0 + \varepsilon\varphi) - u_0\Vert = |\varepsilon| \cdot \Vert\varphi\Vert &amp;lt; \delta
$$
从而满足 $u_0 + \varepsilon\varphi \in U$ 的条件。事实上，取 $\varepsilon(\varphi) = \frac{\delta}{\Vert\varphi\Vert + 1}$ (注意 $\varphi \ne 0$ 时 $\Vert\varphi\Vert &amp;gt; 0$) 就可以实现这个目标。&lt;/p&gt;
&lt;p&gt;将 $u = u_0 + \varepsilon\varphi$ 代入到 $I[u] \ge I[u_0]$ 中，从而得到极小值点的充要条件:
$$
I[u_0 + \varepsilon \varphi] \ge I[u_0], \quad \forall \varphi \in C^1(J, \mathbb{R}), \exists \varepsilon(\varphi) &amp;gt; 0, 0 &amp;lt; |\varepsilon| &amp;lt; \varepsilon(\varphi).
$$&lt;/p&gt;
&lt;p&gt;这个式子告诉我们不管是什么样的函数扰动，都可以通过构造恰到好处的 $\varepsilon$ 实现 $I[u_0 + \varepsilon\varphi] - I[u_0] \ge 0$。当 $\varepsilon &amp;gt; 0$ 时,
$$
\delta I[u_0, \varphi] = \lim_{\varepsilon \to 0} \frac{I[u_0 + \varepsilon\varphi] - I[u_0]}{\varepsilon} \ge 0, \quad \forall \varphi \in C^1(J, \mathbb{R}),
$$
但是当 $\varepsilon &amp;lt; 0$ 时,
$$
\delta I[u_0, \varphi] = \lim_{\varepsilon \to 0} \frac{I[u_0 + \varepsilon\varphi] - I[u_0]}{\varepsilon} \le 0, \quad \forall \varphi \in C^1(J, \mathbb{R}).
$$
由此得出结论，当 $u_0$ 是 $I[u]$ 的一个极小值点时，泛函的一阶变分 $\delta I[u_0, \varphi]$ 必须为零；类似地，当 $u_0$ 是 $I[u]$ 的一个极大值点时，这个结果同样成立。于是我们得出了泛函极值点的必要条件：
$$
\boxed{
\delta I[u_0, \varphi] = 0, \quad \forall \varphi \in C^1(J, \mathbb{R})
}
$$&lt;/p&gt;
&lt;h2&gt;2.5 Euler-Lagrange 方程&lt;/h2&gt;
&lt;p&gt;令一阶变分 $\delta I[u_0, \varphi] = 0$ 可以缩小极值点的搜索范围，但是该方程的求解涉及到复杂的积分方程。为了在贴近实际应用场景的同时简化求解过程，对给定的函数空间 $M$ 做一些边界取值的约束。&lt;/p&gt;
&lt;p&gt;设泛函的定义域为
$$
M = {u \in C^1(J, \Omega) \big| u(t_0) = a, u(t_1) = b}
$$
其中 $J = [t_0, t_1]$ 是一个时间区间，$\Omega \subset \mathbb{R}$ 是一个状态空间，$a, b \in \Omega$ 是两个常数值。此外，为了保证经过扰动后的函数仍然属于 $M$，通常限定 $\varphi \in C_0^1(J, \mathbb{R})$，也即 $\varphi(t_0) = \varphi(t_1) = 0$。&lt;/p&gt;
&lt;p&gt;利用分部积分法，可以简化一阶变分
$$
\begin{aligned}
\delta I(u_0, \varphi) &amp;amp;= \int_J \left[L_{u}(t, u_0(t), \dot u_0(t))\varphi(t) + L_{p}(t, u_0(t), \dot u_0(t))\dot\varphi(t)\right] dt \
&amp;amp;= \int_J L_{u}(t, u_0(t), \dot u_0(t))\varphi(t)dt + L_p(t, u_0(t), \dot u_0(t))\varphi(t)\Big|&lt;em&gt;{t_0}^{t_1} - \int_J \frac{d}{dt}L&lt;/em&gt;{p}(t, u_0(t), \dot u_0(t))\cdot\varphi(t) dt, \
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;由于 $\varphi(t_0) = 0, \varphi(t_1) = 0$，因此
$$
\delta I(u_0, \varphi) = \int_J \left[L_{u}(t, u_0(t), \dot u_0(t)) - \frac{d}{dt}L_{p}(t, u_0(t), \dot u_0(t))\right]\varphi(t) dt.
$$&lt;/p&gt;
&lt;p&gt;由于 $\varphi$ 是任意选取的，得到
$$
\boxed{
L_{u}(t, u_0(t), \dot u_0(t)) - \frac{d}{dt}L_{p}(t, u_0(t), \dot u_0(t)) = 0, \quad \forall t \in J.
}
$$
这个微分方程就是 Euler-Lagrange 方程，它是寻找泛函极值的必要条件。也可以写成下面的形式
$$
\boxed{
\frac{\partial L}{\partial u} - \frac{d}{dt}\left(\frac{\partial L}{\partial \dot u}\right) = 0
}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Euler-Lagrange 可以推广到 Lagrange 函数 $L$ 依赖更高阶的函数导数的情况，例如含三阶导数的情况
$$
\frac{\partial L}{\partial u} - \frac{d}{dt}\left(\frac{\partial L}{\partial u^{(1)}}\right) + \frac{d^2}{dt^2}\left(\frac{\partial L}{\partial u^{(2)}}\right) - \frac{d^3}{dt^3}\left(\frac{\partial L}{\partial u^{(3)}}\right) = 0
$$&lt;/li&gt;
&lt;li&gt;Euler-Lagrange 方程在分析力学中有重要应用，通过构造物理系统的 Lagrange 函数 $L = K - P$，其中 $K$ 是系统的动能，$P$ 是系统的势能，可以通过求解 Euler-Lagrange 方程来得到系统的运动方程。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.6 向量值函数的情况&lt;/h2&gt;
&lt;p&gt;现对函数 $u$ 是向量值函数的情况做推广。设 $u: J \to \Omega$ 是一个 $n$ 维向量值一元函数，其中 $J = [t_0, t_1]$ 是一个时间区间，$\Omega \subset \mathbb{R}^n$ 是一个状态空间。设 $L(t, u, p) \in C^1(J \times \Omega \times \mathbb{R}^{n}, \mathbb{R})$，则泛函为
$$
I[u] = \int_J L(t, u(t), \dot u(t)) dt,
$$
对应的一阶变分在计算中需要对 $u$ 的每一个分量进行求偏导，得到
$$
\begin{aligned}
\delta I[u_0, \varphi] &amp;amp;= \int_J \left[L_{u}(t, u_0(t), \dot u_0(t))\cdot\varphi(t) + L_{p}(t, u_0(t), \dot u_0(t))\cdot\dot\varphi(t)\right] dt \
&amp;amp;= \int_J \sum_{i=1}^n \left[L_{u_i}(t, u_0(t), \dot u_0(t))\varphi_i(t) + L_{p_i}(t, u_0(t), \dot u_0(t))\dot\varphi_i(t)\right] dt \
&amp;amp;= \int_J \sum_{i=1}^n \left[L_{u_i}(t, u_0(t), \dot u_0(t)) - \frac{d}{dt}L_{p_i}(t, u_0(t), \dot u_0(t))\right]\varphi_i(t) dt
\end{aligned}
$$
由于每一个 $\varphi_i$ 都是任意选取的，因此得到的是 $n$ 个 Euler-Lagrange 方程构成的方程组:
$$
\boxed{
\frac{\partial L}{\partial u_i} - \frac{d}{dt}\left(\frac{\partial L}{\partial \dot u_i}\right) = 0, \quad i = 1, 2, \cdots, n.
}
$$&lt;/p&gt;
&lt;h1&gt;3. 一阶变分导数&lt;/h1&gt;
&lt;h2&gt;3.1 变分导数的定义&lt;/h2&gt;
&lt;p&gt;设有泛函 $I[u]: M \to \mathbb{R}$，形式为
$$
I[u] = \int_J L(t, u(t), \dot u(t)) dt,
$$
定义该泛函 $I[u]$ 对&lt;strong&gt;无约束函数&lt;/strong&gt; $u(t)$ 的&lt;strong&gt;一阶变分导数&lt;/strong&gt; (也称&lt;strong&gt;泛函导数&lt;/strong&gt;) $\frac{\delta I[u]}{\delta u}$ 是满足如下变分公式的函数:
$$
\delta I[u, \varphi] = \int_J \frac{\delta I[u]}{\delta u} \cdot \varphi(t) dt
$$
其中 $\varphi(t)$ 是变分空间内的函数扰动。&lt;/p&gt;
&lt;h2&gt;3.2 变分导数的计算&lt;/h2&gt;
&lt;p&gt;当 $J$ 为一个区间，且 $\varphi(t)$ 在区间 $J$ 的端点处取值为零时，可以得到泛函导数的具体计算公式为
$$
\frac{\delta I[u]}{\delta u} = \frac{\partial L}{\partial u} - \frac{d}{dt}\left(\frac{\partial L}{\partial \dot u}\right)
$$
其中 $L(t, u, p)$ 是泛函的 Lagrange 函数，$\frac{\partial L}{\partial \dot u} = L_p$，$\frac{\partial L}{\partial u} = L_u$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;求解泛函导数通常有两种方法&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义法：计算出 $\delta I[u, \varphi]$ 并比较泛函导数的定义从而得到&lt;/li&gt;
&lt;li&gt;Lagrange 函数法：判别出 Lagrange 函数的形式，从而直接套用泛函导数的计算公式 (对 $\varphi(t)$ 有限制条件)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;泛函导数 $\frac{\delta I[u]}{\delta u}$ 是一个关于 $t$ 的函数，其定义域为 $J$；&lt;/li&gt;
&lt;li&gt;上面泛函导数的定义和计算是针对函数变量 $u(t)$ &lt;strong&gt;无约束&lt;/strong&gt; 的情况定义的；&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.3 约束函数的情况&lt;/h2&gt;
&lt;p&gt;当函数变量 $u(t)$ 有约束条件时，不能直接套用上述的定义和计算公式求得泛函导数。此时需要将约束问题通过 Lagrange 乘子法转化为等价的无约束情况，然后才可以使用相关的定义:&lt;/p&gt;
&lt;p&gt;设 $I[u] = \int_J F(t, u(t), \dot u(t)) dt$，约束条件为点态等式约束 $g(t, u, \dot u) = 0$，点态不等式约束 $h(t, u, \dot u) \leq 0$ 和积分态约束 $V[u] = \int_J v(t, u(t), \dot u(t)) dt = 0$。 引入 Lagrange 乘子 $\lambda_1(t), \lambda_2(t), \lambda_3$，构造新的 Lagrange 函数
$$
\begin{aligned}
L(t, u, p) = F(t, u, p) + \lambda_1(t) g(t, u, p) + \lambda_2(t) h(t, u, p) + \lambda_3 v(t, u, p)
\end{aligned}
$$
其中点态不等式约束的乘子函数 $\lambda_2(t) \ge 0$，且满足互补松弛条件 $\lambda_2(t) h(t, u, p) = 0$。&lt;/p&gt;
&lt;p&gt;然后新的泛函是
$$
\tilde I[u] = \int_J L(t, u(t), \dot u(t)) dt = \int_J \left[F(t, u(t), \dot u(t)) + \lambda_1 g(t, u(t), \dot u(t)) + \lambda_2 h(t, u(t), \dot u(t)) + \lambda_3 v(t, u(t), \dot u(t))\right] dt
$$
从而可以通过求解 $\tilde I[u]$ 的泛函导数来得到满足约束条件的泛函导数。&lt;/p&gt;
&lt;h2&gt;3.4 例子——微分熵的泛函导数&lt;/h2&gt;
&lt;p&gt;设 $p(x)$ 是一个概率密度函数，定义微分熵为
$$
h[p] = -\int_{a}^{b} p(x) \log p(x) dx
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;错误的想法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;发现 &quot;$L$&quot; 函数为 $L(x, p, p&apos;) = -p(x) \log p(x)$，带入泛函导数的计算公式，得到
$$
\begin{aligned}
\frac{\delta h[p]}{\delta p} &amp;amp;= \frac{\partial L}{\partial p} - \frac{d}{dx}\left(\frac{\partial L}{\partial p&apos;}\right) \
&amp;amp;= -\log p(x) - 1
\end{aligned}
$$
我们接下来用一阶变分的公式来验证
$$
\begin{aligned}
\delta h[p, \pi] &amp;amp;= \frac{\partial }{\partial \varepsilon} h[p + \varepsilon \pi]\Big|&lt;em&gt;{\varepsilon = 0} \
&amp;amp;= \int&lt;/em&gt;{a}^{b} \frac{\partial }{\partial \varepsilon} \left[-(p(x) + \varepsilon \pi(x)) \log(p(x) + \varepsilon \pi(x))\right] \big|&lt;em&gt;{\varepsilon = 0} dx \
&amp;amp;= \int&lt;/em&gt;{a}^{b} \left[-\pi(x) \log p(x) - \pi(x)\right] dx \
&amp;amp;= \int_{a}^{b} \left[-\log p(x) - 1\right] \pi(x) dx \
\end{aligned}
$$
对比发现变分导数是符合定义公式的。&lt;/p&gt;
&lt;p&gt;但是我们令变分导数 $\frac{\delta h[p]}{\delta p} = 0$，发现得到的分布是 $p(x) = e^{-1}$，这显然是不对的，因为 $p(x)$ 连归一化条件都不满足。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;正确的解法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在微分熵的例子中，约束条件是 $p(x)$ 是一个概率密度函数，需要满足积分态约束 $\int_{a}^{b} p(x) dx = 1$ 和点态不等式约束 $p(x) \ge 0$。引入 Lagrange 乘子 $\lambda_1(x), \lambda_2$，得到真正的 Lagrange 函数为
$$
L(x, p, p&apos;) = -p(x) \log p(x) + \lambda_1(x) p(x) + \lambda_2 p(x)
$$
求泛函导数为
$$
\begin{aligned}
\frac{\delta h[p]}{\delta p} &amp;amp;= \frac{\partial L}{\partial p} - \frac{d}{dx}\left(\frac{\partial L}{\partial p&apos;}\right) \
&amp;amp;= -\log p(x) - 1 + \lambda_1(x) + \lambda_2
\end{aligned}
$$
由此解出
$$
p(x) = e^{\lambda_1(x) + \lambda_2 - 1}
$$
由于指数函数的非负性，根据互补松弛条件，得到 $\lambda_1(x) = 0$&lt;/p&gt;
&lt;p&gt;再代入归一化条件 $\int_{a}^{b} p(x) dx = 1$，得到
$$
\int_{a}^{b} e^{\lambda_2 - 1} dx = 1 \Rightarrow e^{\lambda_2 - 1}(b - a) = 1 \Rightarrow \lambda_2 = 1 + \log(b - a)
$$&lt;/p&gt;
&lt;p&gt;从而得到微分熵的极大值点为
$$
p(x) = \frac{1}{b - a}
$$
验证了均匀分布是无矩约束情况下的微分熵的极大值点这个结论。&lt;/p&gt;
&lt;p&gt;事实上，当去掉点态不等式约束 $p(x) \ge 0$，仅保留积分态约束 $\int_{a}^{b} p(x) dx = 1$ 时，得到的结果也是 $p(x) = \frac{1}{b - a}$。&lt;/p&gt;
&lt;h1&gt;附录&lt;/h1&gt;
&lt;h2&gt;A. 符号表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;符号&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$C^1(S, R)$&lt;/td&gt;
&lt;td&gt;定义在函数定义域为 $S$, 函数值域为 $R$ 上的一阶连续可微&lt;strong&gt;函数空间&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$C_0^1([t_0, t_1], R)$&lt;/td&gt;
&lt;td&gt;定义在函数定义域为 $S$, 函数值域为 $R$ 上的一阶连续可微&lt;strong&gt;函数空间&lt;/strong&gt;，且在区间端点处函数值为零&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$I[u]$&lt;/td&gt;
&lt;td&gt;泛函，定义在函数空间上的映射&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$u_0$&lt;/td&gt;
&lt;td&gt;泛函的极值点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$\varphi$&lt;/td&gt;
&lt;td&gt;变分函数，表示对 $u_0$ 的微小扰动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$\delta I(u_0, \varphi)$&lt;/td&gt;
&lt;td&gt;泛函 $I$ 的一阶变分，表示 $I$ 在 $u_0$ 处对 $\varphi$ 的变化率&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$L(t, u, p)$&lt;/td&gt;
&lt;td&gt;拉格朗日函数，定义在时间 $t$、函数值 $u$ 和导数 $p$ 上的函数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$\frac{\delta I[u]}{\delta u}$&lt;/td&gt;
&lt;td&gt;泛函 $I$ 对函数 $u$ 的一阶变分导数 (泛函导数)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;参考文献&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;[1] &lt;a href=&quot;https://book.douban.com/subject/6535578/&quot;&gt;张恭庆. 变分学讲义 [M]. 北京: 北京大学出版社, 2005.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[2] &lt;a href=&quot;https://books.google.com.hk/books?hl=en&amp;amp;lr=&amp;amp;id=zNDdVFZalSAC&amp;amp;oi=fnd&amp;amp;pg=PR4&amp;amp;dq=Methodus+Inveniendi+Lineas+Curvas+Maximi+Minive+Proprietate+Gaudentes,+Sive+Solutio+Problematis+Isoperimetrici+Latissimo+Sensu+Accepti&amp;amp;ots=FohyhQ4FqF&amp;amp;sig=wBlB2x6k-od0LJWARwR6vuo2Ffk&amp;amp;redir_esc=y#v=onepage&amp;amp;q&amp;amp;f=false&quot;&gt;Euler L. Methodus Inveniendi Lineas Curvas Maximi Minive Proprietate Gaudentes, Sive Solutio Problematis Isoperimetrici Latissimo Sensu Accepti [M]. Lausanne: Bousquet, 1744.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[3] Lagrange J.-L. Essay on a New Method for Determining the Maxima and Minima of Indefinite Integral Formulae [J]. Miscellanea Taurinensia, 1760, 2: 179–252.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>FlowRL - Matching Reward Distributions for LLM Reasoning</title><link>https://adalovelemon.github.io/posts/content/paperreading/rl/flowrl/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/paperreading/rl/flowrl/</guid><pubDate>Mon, 09 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;FlowRL: Matching Reward Distributions for LLM Reasoning&lt;/h1&gt;
&lt;h2&gt;1. Motivation &amp;amp; Problem Definition&lt;/h2&gt;
&lt;h3&gt;1.1 Background and Limitations of Traditional RL in LLM Reasoning&lt;/h3&gt;
&lt;p&gt;Large language model (LLM) reasoning is typically formulated as a &lt;strong&gt;conditional generation problem&lt;/strong&gt;: given a question $\mathbf{x} \in \mathcal{X}$, a policy model $\pi_{\theta}(\mathbf{y}|\mathbf{x})$ generates an answer $\mathbf{y} \in \mathcal{Y}$. The quality of the answer is evaluated by a task-specific reward signal $r(\mathbf{x}, \mathbf{y})$. In reasoning tasks, the reward is usually &lt;strong&gt;sparse and terminal&lt;/strong&gt; (e.g., correctness of the final answer), which means &lt;strong&gt;we consider one-step reward instead of returns&lt;/strong&gt; (i.e., discounted sum of rewards over time steps).&lt;/p&gt;
&lt;p&gt;Existing reinforcement learning (RL) methods for LLMs—such as REINFORCE, PPO, and GRPO—adopt a &lt;strong&gt;reward‑maximization&lt;/strong&gt; objective:
$$
\max_{\theta} \mathbb{E}&lt;em&gt;{\mathbf{y} \sim \pi&lt;/em&gt;{\theta}(\cdot|\mathbf{x})}\bigl[ r(\mathbf{x}, \mathbf{y}) \bigr].
$$
However, this approach tends to &lt;strong&gt;over‑fit the dominant reward mode&lt;/strong&gt;, leading to &lt;strong&gt;mode collapse&lt;/strong&gt; and a lack of diversity in generated reasoning paths. For complex reasoning tasks (e.g., long‑chain mathematical proofs or code generation), capturing a &lt;strong&gt;diverse set of valid solutions&lt;/strong&gt; is crucial for generalization.&lt;/p&gt;
&lt;h3&gt;1.2 Core Idea of FlowRL&lt;/h3&gt;
&lt;p&gt;FlowRL shifts the paradigm from &lt;strong&gt;reward maximization&lt;/strong&gt; to &lt;strong&gt;reward‑distribution matching&lt;/strong&gt;. Instead of only pushing the policy toward the highest‑reward answers, FlowRL encourages the policy to &lt;strong&gt;generate answers with probabilities proportional to their exponentiated rewards&lt;/strong&gt;. Formally, the target is:
$$
\pi_{\theta}(\mathbf{y}|\mathbf{x}) \propto \exp!\bigl(\beta , r(\mathbf{x}, \mathbf{y})\bigr),
$$
where $\beta &amp;gt; 0$ is a temperature parameter. This &lt;strong&gt;Boltzmann‑type distribution&lt;/strong&gt; naturally balances exploitation (high‑reward answers) and exploration (lower‑reward but still valid answers).&lt;/p&gt;
&lt;h2&gt;2. Theoretical Framework: From Reverse KLD to Trajectory Balance&lt;/h2&gt;
&lt;h3&gt;2.1 Distribution Matching via Reverse KL Divergence&lt;/h3&gt;
&lt;p&gt;To align the policy with the desired Boltzmann distribution, we minimize the &lt;strong&gt;reverse Kullback–Leibler (KL) divergence&lt;/strong&gt; between the policy $\pi_{\theta}$ and a normalized target distribution $\tilde{\pi}$. Because the partition function of the Boltzmann distribution is intractable, we introduce a &lt;strong&gt;learnable partition function&lt;/strong&gt; $Z_{\phi}(\mathbf{x})$:&lt;/p&gt;
&lt;p&gt;$$
\tilde{\pi}(\mathbf{y}|\mathbf{x}) = \frac{\exp!\bigl(\beta , r(\mathbf{x}, \mathbf{y})\bigr)}{Z_{\phi}(\mathbf{x})}.
$$&lt;/p&gt;
&lt;p&gt;The reverse KL divergence is:
$$
\mathbb{D}&lt;em&gt;{\text{KL}}!\bigl(\pi&lt;/em&gt;{\theta} \parallel \tilde{\pi}\bigr)
= \mathbb{E}&lt;em&gt;{\mathbf{y} \sim \pi&lt;/em&gt;{\theta}(\cdot|\mathbf{x})}
\Bigl[ \log \pi_{\theta}(\mathbf{y}|\mathbf{x}) - \log \tilde{\pi}(\mathbf{y}|\mathbf{x}) \Bigr].
$$&lt;/p&gt;
&lt;p&gt;Substituting $\tilde{\pi}$ gives:
$$
\mathbb{D}&lt;em&gt;{\text{KL}} = \mathbb{E}&lt;/em&gt;{\mathbf{y} \sim \pi_{\theta}(\cdot|\mathbf{x})}
\Bigl[ \log \pi_{\theta}(\mathbf{y}|\mathbf{x}) - \beta , r(\mathbf{x}, \mathbf{y}) + \log Z_{\phi}(\mathbf{x}) \Bigr].
$$&lt;/p&gt;
&lt;h3&gt;2.2 From Flow Equation to Trajectory Balance: Gradient Equivalence&lt;/h3&gt;
&lt;p&gt;In GFlowNets, the &lt;strong&gt;trajectory balance&lt;/strong&gt; condition emerges from the more fundamental &lt;strong&gt;flow conservation&lt;/strong&gt; principle. Consider the generation of a complete response $\mathbf{y} = (y_1, \dots, y_T)$ given a prompt $\mathbf{x}$ as a trajectory in a directed acyclic graph. Let $F(s)$ denote the &lt;em&gt;flow&lt;/em&gt; (probability mass) at state $s$. The forward policy $\pi_\theta$ (our generation model) determines the transition probabilities. For a complete trajectory, the &lt;strong&gt;flow equation&lt;/strong&gt; states that the probability flow from the initial state $s_0$ (empty response) to the terminal state $s_T = \mathbf{y}$ must satisfy:&lt;/p&gt;
&lt;p&gt;$$
F(s_0) \cdot \prod_{t=1}^{T} \pi_\theta(y_t \mid \mathbf{y}&lt;em&gt;{&amp;lt;t}, \mathbf{x}) = R(\mathbf{x}, \mathbf{y}) \cdot \prod&lt;/em&gt;{t=1}^{T} P_B(y_{t-1} \mid \mathbf{y}_{\le t}, \mathbf{x}),
$$&lt;/p&gt;
&lt;p&gt;where $R(\mathbf{x},\mathbf{y})$ is the reward associated with the terminal state, and $P_B$ is a &lt;em&gt;backward policy&lt;/em&gt; (often chosen as uniform or a simple fixed distribution). The initial flow $F(s_0)$ is exactly the partition function $Z_\phi(\mathbf{x})$, as it represents the total probability mass injected into the system. In FlowRL, the reward is defined as the exponentiated, reference‑model‑adjusted reward:&lt;/p&gt;
&lt;p&gt;$$
R(\mathbf{x}, \mathbf{y}) = \exp!\bigl(\beta , r(\mathbf{x}, \mathbf{y})\bigr) \cdot \pi_{\text{ref}}(\mathbf{y} \mid \mathbf{x}).
$$&lt;/p&gt;
&lt;p&gt;Moreover, for simplicity (and following common practice in GFlowNets for sequence generation), the backward policy is taken to be uniform, so that $\prod_{t} P_B(\cdot) = \text{constant}$. Ignoring this constant (since it can be absorbed into the learned partition function), we obtain the simplified &lt;strong&gt;trajectory‑balance equation&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;$$
Z_\phi(\mathbf{x}) \cdot \pi_\theta(\mathbf{y} \mid \mathbf{x}) = \exp!\bigl(\beta , r(\mathbf{x}, \mathbf{y})\bigr) \cdot \pi_{\text{ref}}(\mathbf{y} \mid \mathbf{x}). \tag{TB‑eq}
$$&lt;/p&gt;
&lt;p&gt;Taking logarithms on both sides gives the linear constraint:&lt;/p&gt;
&lt;p&gt;$$
\log Z_\phi(\mathbf{x}) + \log \pi_\theta(\mathbf{y} \mid \mathbf{x}) = \beta , r(\mathbf{x}, \mathbf{y}) + \log \pi_{\text{ref}}(\mathbf{y} \mid \mathbf{x}). \tag{log‑TB}
$$&lt;/p&gt;
&lt;p&gt;Since this equality cannot hold for every possible trajectory during training, we turn it into a squared‑error objective, the &lt;strong&gt;trajectory‑balance loss&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}&lt;em&gt;{\text{TB}}(\mathbf{x}, \mathbf{y}; \theta, \phi) = \Bigl( \log Z&lt;/em&gt;\phi(\mathbf{x}) + \log \pi_\theta(\mathbf{y} \mid \mathbf{x}) - \beta , r(\mathbf{x}, \mathbf{y}) - \log \pi_{\text{ref}}(\mathbf{y} \mid \mathbf{x}) \Bigr)^2.
$$&lt;/p&gt;
&lt;p&gt;Now we demonstrate that minimizing this loss is &lt;strong&gt;gradient‑equivalent&lt;/strong&gt; to minimizing the reverse KL divergence between $\pi_\theta$ and the target distribution $\tilde{\pi}(\mathbf{y} \mid \mathbf{x}) = \exp(\beta r(\mathbf{x},\mathbf{y})) \pi_{\text{ref}}(\mathbf{y} \mid \mathbf{x}) / Z_\phi(\mathbf{x})$.&lt;/p&gt;
&lt;p&gt;First, compute the gradient of $\mathbb{D}_{\text{KL}}$ with respect to the policy parameters $\theta$:&lt;/p&gt;
&lt;p&gt;$$
\nabla_{\theta} \mathbb{D}&lt;em&gt;{\text{KL}}
= \mathbb{E}&lt;/em&gt;{\mathbf{y} \sim \pi_\theta(\cdot \mid \mathbf{x})}
\Bigl[
\nabla_{\theta} \log \pi_\theta(\mathbf{y} \mid \mathbf{x})
; \bigl( \log \pi_\theta(\mathbf{y} \mid \mathbf{x}) - \beta , r(\mathbf{x}, \mathbf{y}) - \log \pi_{\text{ref}}(\mathbf{y} \mid \mathbf{x}) + \log Z_\phi(\mathbf{x}) \bigr)
\Bigr].
$$&lt;/p&gt;
&lt;p&gt;Next, compute the gradient of the trajectory‑balance loss. Note that $\mathcal{L}&lt;em&gt;{\text{TB}}$ is an expectation over the same distribution $\pi&lt;/em&gt;\theta$ (or an importance‑weighted version thereof when using off‑policy data). For on‑policy sampling, we have:&lt;/p&gt;
&lt;p&gt;$$
\nabla_{\theta} \mathcal{L}&lt;em&gt;{\text{TB}}
= \nabla&lt;/em&gt;{\theta} ,
\mathbb{E}&lt;em&gt;{\mathbf{y} \sim \pi&lt;/em&gt;\theta(\cdot \mid \mathbf{x})}
\Bigl[
\bigl( \log Z_\phi(\mathbf{x}) + \log \pi_\theta(\mathbf{y} \mid \mathbf{x}) - \beta , r(\mathbf{x}, \mathbf{y}) - \log \pi_{\text{ref}}(\mathbf{y} \mid \mathbf{x}) \bigr)^2
\Bigr].
$$&lt;/p&gt;
&lt;p&gt;Applying the log‑derivative trick, we obtain:&lt;/p&gt;
&lt;p&gt;$$
\nabla_{\theta} \mathcal{L}&lt;em&gt;{\text{TB}}
= 2 ,
\mathbb{E}&lt;/em&gt;{\mathbf{y} \sim \pi_\theta(\cdot \mid \mathbf{x})}
\Bigl[
\nabla_{\theta} \log \pi_\theta(\mathbf{y} \mid \mathbf{x})
; \bigl( \log Z_\phi(\mathbf{x}) + \log \pi_\theta(\mathbf{y} \mid \mathbf{x}) - \beta , r(\mathbf{x}, \mathbf{y}) - \log \pi_{\text{ref}}(\mathbf{y} \mid \mathbf{x}) \bigr)
\Bigr].
$$&lt;/p&gt;
&lt;p&gt;Comparing KL‑grad and TB‑grad, the two gradients are &lt;strong&gt;proportional&lt;/strong&gt; (differing only by a constant factor of 2). Therefore, &lt;strong&gt;minimizing the trajectory‑balance loss yields the same gradient direction as minimizing the reverse KL divergence&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Practical implications:&lt;/strong&gt; This equivalence provides a robust surrogate objective. The squared‑error formulation is numerically stable, allows simultaneous optimization of both $\theta$ (policy) and $\phi$ (partition function), and naturally accommodates off‑policy data through importance sampling. Moreover, it directly enforces the flow‑balance equation, which is the cornerstone of the GFlowNet framework.&lt;/p&gt;
&lt;h3&gt;2.3 Incorporating Reference Model and Length Normalization&lt;/h3&gt;
&lt;p&gt;In practice, two modifications are essential for stable training on long reasoning chains:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reference‑model regularization&lt;/strong&gt;&lt;br /&gt;
To prevent the policy from deviating too far from the original pre‑trained model, the target distribution is augmented with a reference policy $\pi_{\text{ref}}$:
$$
\tilde{\pi}(\mathbf{y}|\mathbf{x}) = \frac{\exp!\bigl(\beta , r(\mathbf{x}, \mathbf{y})\bigr) ; \pi_{\text{ref}}(\mathbf{y}|\mathbf{x})}{Z_{\phi}(\mathbf{x})}.
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Length normalization&lt;/strong&gt;&lt;br /&gt;
Because $\log \pi_{\theta}(\mathbf{y}|\mathbf{x}) = \sum_{t=1}^{|\mathbf{y}|} \log \pi_{\theta}(y_t|\mathbf{y}&lt;em&gt;{&amp;lt;t},\mathbf{x})$, the loss can grow with sequence length, causing gradient explosion. FlowRL normalizes the log‑probabilities by the response length $|\mathbf{y}|$:
$$
\frac{1}{|\mathbf{y}|} \log \pi&lt;/em&gt;{\theta}(\mathbf{y}|\mathbf{x}),
\quad \frac{1}{|\mathbf{y}|} \log \pi_{\text{ref}}(\mathbf{y}|\mathbf{x}).
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Importance sampling for off‑policy correction&lt;/strong&gt;&lt;br /&gt;
To reuse rollouts collected from an older policy $\pi_{\theta_{\text{old}}}$, FlowRL employs clipped importance weights:
$$
w = \text{clip}!\left( \frac{\pi_{\theta}(\mathbf{y}|\mathbf{x})}{\pi_{\theta_{\text{old}}}(\mathbf{y}|\mathbf{x})},; 1-\epsilon,; 1+\epsilon \right)^{\text{detach}}.
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2.4 Final FlowRL Objective&lt;/h3&gt;
&lt;p&gt;Combining the above ingredients, the complete FlowRL loss becomes:
$$
\mathcal{L}&lt;em&gt;{\text{FlowRL}} = w \cdot \Bigl(
\log Z&lt;/em&gt;{\phi}(\mathbf{x})&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;\frac{1}{|\mathbf{y}|}\log \pi_{\theta}(\mathbf{y}|\mathbf{x})&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;\beta , \hat{r}(\mathbf{x},\mathbf{y})&lt;/li&gt;
&lt;li&gt;\frac{1}{|\mathbf{y}|}\log \pi_{\text{ref}}(\mathbf{y}|\mathbf{x})
\Bigr)^2,
$$
where $\hat{r}$ is the group‑normalized reward (e.g., $\hat{r}_i = (r_i - \text{mean}(\mathbf{r}))/\text{std}(\mathbf{r})$).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. Implementation Details&lt;/h2&gt;
&lt;h3&gt;3.1 Partition Function Network $Z_{\phi}$&lt;/h3&gt;
&lt;p&gt;The partition function is parameterized by a small neural network (a 3‑layer MLP) that &lt;strong&gt;estimates $\log Z_{\phi}(\mathbf{x})$&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Input&lt;/strong&gt;: The hidden‑state representation of the prompt $\mathbf{x}$.&lt;br /&gt;
Specifically, we take the &lt;strong&gt;mean of the last‑layer hidden states&lt;/strong&gt; of the language model over the prompt tokens, yielding a fixed‑dimensional vector $\mathbf{h}_{\mathbf{x}} \in \mathbb{R}^{d}$.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Architecture&lt;/strong&gt;:&lt;br /&gt;
$$
\mathbf{h}^{(1)} = \text{GELU}\bigl( \mathbf{W}&lt;em&gt;1 \mathbf{h}&lt;/em&gt;{\mathbf{x}} + \mathbf{b}_1 \bigr), \quad
\mathbf{h}^{(2)} = \text{GELU}\bigl( \mathbf{W}_2 \mathbf{h}^{(1)} + \mathbf{b}&lt;em&gt;2 \bigr), \quad
\log Z&lt;/em&gt;{\phi}(\mathbf{x}) = \mathbf{W}_3 \mathbf{h}^{(2)} + b_3,
$$
where the hidden dimensions match the base LLM’s hidden size.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Training&lt;/strong&gt;: The parameters $\phi$ are updated jointly with $\theta$ using the same optimizer (e.g., Adam).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2 Simplified Python Code Snippet&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import torch
import torch.nn as nn

class PartitionFunction(nn.Module):
    &quot;&quot;&quot;3-layer MLP to estimate log Z_phi(x).&quot;&quot;&quot;
    def __init__(self, hidden_dim):
        super().__init__()
        self.mlp = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim),
            nn.GELU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.GELU(),
            nn.Linear(hidden_dim, 1)
        )
    
    def forward(self, prompt_embeddings):
        # prompt_embeddings: [batch_size, seq_len, hidden_dim]
        prompt_repr = prompt_embeddings.mean(dim=1)          # [batch_size, hidden_dim]
        log_Z = self.mlp(prompt_repr).squeeze(-1)           # [batch_size]
        return log_Z

def compute_flowrl_loss(prompt, response, reward, policy_model, ref_model, Z_phi, beta=15.0):
    &quot;&quot;&quot;
    Compute FlowRL loss with length normalization and importance sampling.
    &quot;&quot;&quot;
    # Get prompt embeddings from the policy model
    with torch.no_grad():
        prompt_outputs = policy_model(prompt, output_hidden_states=True)
        prompt_embeddings = prompt_outputs.hidden_states[-1]  # [batch, seq_len, hidden]
    
    # Estimate log partition function
    log_Z = Z_phi(prompt_embeddings)                         # [batch]
    
    # Log probabilities from policy and reference model
    log_pi = policy_model.get_log_prob(response, prompt)     # [batch]
    log_ref = ref_model.get_log_prob(response, prompt)       # [batch]
    
    # Length normalization
    lengths = response.lengths.float()                       # [batch]
    norm_log_pi = log_pi / lengths
    norm_log_ref = log_ref / lengths
    
    # Group‑normalized reward (pre‑computed)
    norm_reward = reward                                     # [batch]
    
    # Importance weight (detached)
    old_log_pi = ...  # from stored rollouts
    imp_ratio = (log_pi - old_log_pi).exp().detach()
    w = torch.clamp(imp_ratio, 1-0.2, 1+0.2)
    
    # Trajectory‑balance term
    tb_term = log_Z + norm_log_pi - beta * norm_reward - norm_log_ref
    loss = w * (tb_term ** 2)
    
    return loss.mean()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. Why Does FlowRL Work? Key Insights&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Distribution Matching vs. Reward Maximization&lt;/strong&gt;&lt;br /&gt;
FlowRL explicitly encourages the policy to &lt;strong&gt;cover multiple high‑reward modes&lt;/strong&gt; instead of collapsing to a single peak. This is crucial for reasoning tasks where diverse solution strategies exist.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Boltzmann Distribution as a Soft Exploration Mechanism&lt;/strong&gt;&lt;br /&gt;
The target $\pi \propto \exp(\beta r)$ provides a &lt;strong&gt;continuous trade‑off&lt;/strong&gt; between exploitation and exploration, analogous to a “softened” ε‑greedy strategy but more principled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trajectory Balance as a Stable Optimization Proxy&lt;/strong&gt;&lt;br /&gt;
The squared‑error loss derived from the flow‑balance condition is numerically more stable than direct policy‑gradient estimation, especially for long sequences.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GFlowNets as a Conceptual Bridge&lt;/strong&gt;&lt;br /&gt;
Although the core idea is distribution matching, the GFlowNets framework offers an intuitive &lt;strong&gt;flow‑based analogy&lt;/strong&gt; and a theoretically grounded optimization objective (trajectory balance).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Practical Adaptations for LLMs&lt;/strong&gt;&lt;br /&gt;
Length normalization and reference‑model regularization are critical engineering adaptations that make distribution matching feasible for real‑world LLM reasoning tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;5. Conclusion&lt;/h2&gt;
&lt;p&gt;FlowRL re‑frames LLM reinforcement learning as a &lt;strong&gt;reward‑distribution matching&lt;/strong&gt; problem. By minimizing the reverse KL divergence between the policy and a Boltzmann‑type target distribution—implemented via a learnable partition function and optimized through a trajectory‑balance loss—FlowRL achieves &lt;strong&gt;superior diversity and generalization&lt;/strong&gt; in mathematical and code‑reasoning tasks. The method’s success stems from its principled departure from pure reward maximization, coupled with practical adaptations for training large language models on long reasoning chains.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2509.15207&quot;&gt;FlowRL: Matching Reward Distributions for LLM Reasoning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>常用计算机指令</title><link>https://adalovelemon.github.io/posts/content/technotes/cmd-lines/cmd-lines/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/cmd-lines/cmd-lines/</guid><pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;记录一些常用的计算机指令&lt;/p&gt;
&lt;h2&gt;uv 系列&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 安装 &lt;code&gt;uv&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Linux 系统下安装 &lt;code&gt;uv&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# via official installer (recommended)
curl -LsSf https://astral.sh/uv/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows 系统下安装 &lt;code&gt;uv&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# via official installer (recommended)
irm https://astral.sh/uv/install.ps1 | iex
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后，重启终端，即可使用 &lt;code&gt;uv&lt;/code&gt; 了。可以通过以下命令验证安装是否成功&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 使用 &lt;code&gt;uv&lt;/code&gt; 创建虚拟环境&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用默认参数创建虚拟环境，默认情况下 &lt;code&gt;uv&lt;/code&gt; 会使用系统的默认 Python 解释器来创建虚拟环境:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Default Python Interpreter
uv venv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果希望指定 Python 版本，可以使用 &lt;code&gt;--python&lt;/code&gt; 参数，&lt;code&gt;uv&lt;/code&gt; 会在系统中首先检索已安装的，对应版本的 Python 可执行文件 (要求该文件在系统环境变量 &lt;code&gt;PATH&lt;/code&gt; 中可以被检索到):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Specify Python Version
uv venv --python 3.10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此外，还可以通过直接指定 Python 解释器的文件路径来创建虚拟环境:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Specify Python Interpreter Path
uv venv --python /opt/data/private/usr/python3.10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果不清楚到底有哪些可用的且当前系统已经安装的 Python 解释器，可以使用 &lt;code&gt;uv python list&lt;/code&gt; 来查看:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ uv python list

cpython-3.9.18      /usr/bin/python3.9
cpython-3.10.14     /opt/homebrew/bin/python3.10
cpython-3.11.9      /usr/bin/python3.11
cpython-3.12.2      /home/user/.local/bin/python3.12
cpython-3.13.0      &amp;lt;download available&amp;gt;
cpython-3.14.0a5    &amp;lt;download available&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前四行是已经安装在系统上的 Python 解释器，同时指定了它们的路径。&lt;/li&gt;
&lt;li&gt;后两行则是未安装，但 &lt;code&gt;uv&lt;/code&gt; 支持直接下载安装（比如最新的 3.13 正式版或 3.14 预览版）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 使用 &lt;code&gt;uv&lt;/code&gt; 下载并安装 Python 解释器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可以使用 &lt;code&gt;uv python install &amp;lt;version&amp;gt;&lt;/code&gt; 来下载安装这些&quot;可下载&quot;的版本:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv python install 3.13.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些下载的 Python 解释器默认会存储在 &lt;code&gt;uv&lt;/code&gt; 的缓存目录中:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Linux: ~/.cache/uv/python/
Windows: %LOCALAPPDATA%\uv\python\
macOS: ~/Library/Caches/uv/python/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果不希望 &lt;code&gt;uv&lt;/code&gt; 将 Python 解释器安装在这些默认的缓存目录中，可以通过指定环境变量 &lt;code&gt;UV_PYTHON_DIR&lt;/code&gt; 来自定义存储路径。&lt;/p&gt;
&lt;p&gt;卸载某个通过 &lt;code&gt;uv&lt;/code&gt; 下载并安装的 Python 版本，可以使用 &lt;code&gt;uv python uninstall &amp;lt;version&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv python uninstall 3.13.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面是一个完整的安装 Python 3.13 虚拟环境的例子:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Download and install Python 3.13
uv python install 3.13

# Create a virtual environment using Python 3.13
uv venv --python 3.13

# Activate the virtual environment
## If you use bash (Linux or macOS)
source .venv/bin/activate

## If you use Powershell (Windows)
.venv\Scripts\activate

# Verify the Python version in the virtual environment
python --version  # Should Display 3.13.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. 使用 &lt;code&gt;uv pip&lt;/code&gt; 管理虚拟环境依赖&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt; 的依赖管理功能与 &lt;code&gt;pip&lt;/code&gt; 几乎完全相同，可以直接使用 &lt;code&gt;uv pip&lt;/code&gt; 来安装、卸载、列出依赖包:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Install numpy package
uv pip install numpy

# Install dependencies from requirements.txt
uv pip install -r requirements.txt

# Uninstall a package
uv pip uninstall numpy

# List installed packages
uv pip list

# Upgrade a package
uv pip install --upgrade numpy

# Show package information
uv pip show numpy

# Install local project in editable mode
uv pip install -e .

# Specify source index URL
uv pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大部分 &lt;code&gt;pip&lt;/code&gt; 支持的命令和参数，&lt;code&gt;uv pip&lt;/code&gt; 都支持，可以直接使用 &lt;code&gt;uv pip&lt;/code&gt; 来替代 &lt;code&gt;pip&lt;/code&gt;，除了极少数的命令如 &lt;code&gt;pip cache dir&lt;/code&gt;、&lt;code&gt;pip cache purge&lt;/code&gt; 等是 &lt;code&gt;uv pip&lt;/code&gt; 不支持的 (因为 &lt;code&gt;uv&lt;/code&gt; 用自己的缓存系统)。&lt;/p&gt;
&lt;p&gt;在处理速度上，&lt;code&gt;uv pip&lt;/code&gt; (Rust 开发) 比 &lt;code&gt;pip&lt;/code&gt; (Python 开发) 要快很多。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. &lt;code&gt;.toml&lt;/code&gt; 配置文件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt; 有自己的配置文件格式 TOML，可以通过配置文件来设置 &lt;code&gt;uv&lt;/code&gt; 的默认行为，从而避免每次使用命令行参数来指定选项。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;文件&lt;/th&gt;
&lt;th&gt;主要用途&lt;/th&gt;
&lt;th&gt;是否与 &lt;code&gt;uv&lt;/code&gt; 相关&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pyproject.toml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;定义 Python 项目本身：&amp;lt;br&amp;gt;• 项目元数据（名称、版本、作者）&amp;lt;br&amp;gt;• 依赖（&lt;code&gt;dependencies&lt;/code&gt;）&amp;lt;br&amp;gt;• 构建方式（&lt;code&gt;build-backend&lt;/code&gt;）&amp;lt;br&amp;gt;• 各工具的配置（包括 &lt;code&gt;uv&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;✅ 是&amp;lt;br&amp;gt;→ &lt;code&gt;uv&lt;/code&gt; 会读取其中的 &lt;code&gt;[project]&lt;/code&gt; 和 &lt;code&gt;[tool.uv]&lt;/code&gt; 部分&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv.toml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;专门配置 &lt;code&gt;uv&lt;/code&gt; 工具的行为：&amp;lt;br&amp;gt;• 默认 Python 版本&amp;lt;br&amp;gt;• 镜像源（index URL）&amp;lt;br&amp;gt;• 缓存目录等&lt;/td&gt;
&lt;td&gt;✅ 是&amp;lt;br&amp;gt;→ 仅由 &lt;code&gt;uv&lt;/code&gt; 使用，其他工具忽略&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; 文件可以用于配置整个 Python 项目，通常结合使用 &lt;code&gt;uv sync&lt;/code&gt; 就可以实现一键配置环境 (同步依赖) 的目的:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv sync
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;文件路径设置:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt; 会按照以下顺序查找 &lt;code&gt;uv.toml&lt;/code&gt; 配置文件:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;项目根目录 (当前工作目录)，如 &lt;code&gt;pyproject.toml&lt;/code&gt; 或 &lt;code&gt;.uv.toml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用户主目录下的 &lt;code&gt;.uv.toml&lt;/code&gt; 文件
&lt;ul&gt;
&lt;li&gt;Linux / macOS: &lt;code&gt;~/.config/uv/uv.toml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Windows: &lt;code&gt;%APPDATA%\uv\uv.toml&lt;/code&gt; 或 &lt;code&gt;C:\Users\&amp;lt;User&amp;gt;\AppData\Roaming\uv\uv.toml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;环境变量指定的配置文件路径 (通过 &lt;code&gt;UV_CONFIG_FILE&lt;/code&gt; 环境变量指定)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;而对于 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件，&lt;code&gt;uv&lt;/code&gt; 只会在项目根目录下查找该文件，一般这个文件也只存在于项目的根目录中。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个典型的 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件示例如下:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# pyproject.toml

[build-system]
requires = [&quot;hatchling&quot;]
build-backend = &quot;hatchling.build&quot;

[project]
name = &quot;my-awesome-app&quot;
dynamic = [&quot;version&quot;]  # Version generated via Git tags
description = &quot;A modern Python application using uv and Ruff&quot;
readme = &quot;README.md&quot;
authors = [{ name = &quot;Your Name&quot;, email = &quot;you@example.com&quot; }]
license = { text = &quot;MIT&quot; }
requires-python = &quot;&amp;gt;=3.10&quot;
classifiers = [
    &quot;License :: OSI Approved :: MIT License&quot;,
    &quot;Programming Language :: Python :: 3&quot;,
    &quot;Programming Language :: Python :: 3.10&quot;,
    &quot;Programming Language :: Python :: 3.11&quot;,
    &quot;Programming Language :: Python :: 3.12&quot;,
]

# Running dependencies
dependencies = [
    &quot;httpx&amp;gt;=0.27.0&quot;,
    &quot;typer&amp;gt;=0.12.0&quot;,
    &quot;rich&amp;gt;=13.0.0&quot;,
]

# Optional dependencies (users can install as needed)
[project.optional-dependencies]
dev = [
    &quot;pytest&amp;gt;=8.0&quot;,
    &quot;ruff&amp;gt;=0.6.0&quot;,
    &quot;mypy&amp;gt;=1.10&quot;,
]
web = [&quot;fastapi&amp;gt;=0.110&quot;, &quot;uvicorn&amp;gt;=0.29&quot;]

# Command line entry points
[project.scripts]
myapp = &quot;myapp.cli:app&quot;

# Project URLs
[project.urls]
Homepage = &quot;https://github.com/yourname/my-awesome-app&quot;
Repository = &quot;https://github.com/yourname/my-awesome-app&quot;
Documentation = &quot;https://my-awesome-app.readthedocs.io&quot;

# ========================
# 工具配置区 [tool.*]
# ========================

# uv configurations
[tool.uv]
# Default groups to sync (used by `uv sync`)
default-groups = [&quot;dev&quot;]

# Dependency groups (replacing requirements-dev.txt)
[dependency-groups]
dev = [
    &quot;my-awesome-app[dev]&quot;,  # Includes own dev dependencies
    &quot;pytest-cov&quot;,
    &quot;pre-commit&quot;,
]

# Ruff configuration
[tool.ruff]
line-length = 88
target-version = &quot;py310&quot;
fix = true

[tool.ruff.lint]
select = [&quot;E&quot;, &quot;F&quot;, &quot;I&quot;, &quot;UP&quot;]  # pycodestyle, pyflakes, isort, pyupgrade

# pytest configuration
[tool.pytest.ini_options]
minversion = &quot;7.0&quot;
addopts = &quot;--strict-markers -v&quot;
testpaths = [&quot;tests&quot;]

# mypy configuration
[tool.mypy]
python_version = &quot;3.10&quot;
warn_return_any = true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个典型的 &lt;code&gt;uv.toml&lt;/code&gt; 文件示例则如下:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# uv.toml

# Default Python version to use (when running uv venv or uv pip install)
python = &quot;3.11&quot;

# PyPI mirror source (Tsinghua mirror recommended in China)
index-url = &quot;https://pypi.tuna.tsinghua.edu.cn/simple&quot;

# Extra index URLs (optional)
extra-index-urls = [
    &quot;https://pypi.org/simple&quot;,
]

# Virtual environment settings
venv = { 
    name = &quot;.venv&quot;,               # Virtual environment folder name
    system-site-packages = false  # Do not inherit system packages
}

# pip installation behavior
pip = {
    # Upgrade strategy: eager or only-if-needed (default)
    upgrade-strategy = &quot;only-if-needed&quot;,
    
    # Whether to show progress bar
    progress-bar = &quot;off&quot;,  # Options: &quot;on&quot;, &quot;off&quot;, &quot;ascii&quot;
}

# Cache directory (usually no need to change)
cache-dir = &quot;～/.cache/uv&quot;

# Timeout setting (seconds)
timeout = 30

# Whether to allow pre-release versions
prereleases = false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用场景对比&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;th&gt;读取哪个文件？&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uv.toml&lt;/code&gt; → &lt;code&gt;python = &quot;3.11&quot;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv pip install -r requirements.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;uv.toml&lt;/code&gt; → 使用清华源&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uv sync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; → 安装 &lt;code&gt;[dependency-groups]&lt;/code&gt; 中的 &lt;code&gt;dev&lt;/code&gt; 组&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pip install .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; → 构建并安装项目&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;6. 使用 uv 运行脚本&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果没有激活虚拟环境，也可以直接使用 &lt;code&gt;uv run&lt;/code&gt; 来运行脚本:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv run python my_script.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然也可以像其他虚拟环境一样，激活后直接使用 &lt;code&gt;python my_script.py&lt;/code&gt; 来运行脚本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source .venv/bin/activate  # Linux or macOS
# .venv\Scripts\activate   # Windows
python my_script.py
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;conda &amp;amp; pip 系列&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 安装conda&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh
bash ~/miniconda.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 使用conda安装pytorch&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. conda删除环境&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda env remove -n your_env_name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. conda创建环境&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda create -n your_env_name python=3.8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果想指定环境的具体路径，可以使用以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;conda create -p /path/to/your/env python=3.8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;5. pip 换源&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;6. pip cache 跳过&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;临时跳过 &lt;code&gt;pip cache&lt;/code&gt; 进行安装&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install --no-cache-dir -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;7. 切换 pip cache&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# /mnt/data is the pip cache directory
mkdir -p /mnt/data/pip_cache
export PIP_CACHE_DIR=/mnt/data/pip_cache
pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;数据集下载预处理系列&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 使用wget下载数据集&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget &amp;lt;url&amp;gt; -O &amp;lt;filename&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，这里使用的&lt;code&gt;-O&lt;/code&gt;与&lt;code&gt;-o&lt;/code&gt;是不同的，&lt;code&gt;-O&lt;/code&gt;表示将下载的文件重命名为指定的文件名，而&lt;code&gt;-o&lt;/code&gt;表示将下载的日志输出到指定的文件中。
例如，使用以下命令下载数据集并将其重命名为&lt;code&gt;dataset.zip&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget https://example.com/dataset.zip -O dataset.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你想将下载的日志输出到&lt;code&gt;log.txt&lt;/code&gt;文件中，可以使用以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget https://example.com/dataset.zip -o log.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你想将下载的日志输出到&lt;code&gt;log.txt&lt;/code&gt;文件中，并将下载的文件重命名为&lt;code&gt;dataset.zip&lt;/code&gt;，可以使用以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget https://example.com/dataset.zip -O dataset.zip -o log.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 使用gdown下载数据集&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install gdown
gdown --id &amp;lt;id-num&amp;gt; --output dataset.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如，对于链接为 https://drive.google.com/file/d/1TOg7BZE1XsJ7l2VzMoqFRAETk7OLcv75/view?usp=sharing 的Google Driver 文件（在此引用这个文件链接🔗，对文件所有者表达感谢），其对应的id号为 id: 1TOg7BZE1XsJ7l2VzMoqFRAETk7OLcv75&lt;/p&gt;
&lt;p&gt;于是，可以使用以下命令下载该文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gdown --id 1TOg7BZE1XsJ7l2VzMoqFRAETk7OLcv75 --output dataset.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果想要一次性下载某个文件夹中的全部文件，可以用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gdown --folder YOUR_FOLDER_ID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如, 链接为 https://drive.google.com/drive/folders/1TK2yio7I8Dtab3m8IFZaXUiU-H3IFSmU?usp=drive_link，具体指令为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gdown --folder 1TK2yio7I8Dtab3m8IFZaXUiU-H3IFSmU
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 使用modelscope下载数据&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install modelscope
modelscope download --dataset &amp;lt;dataset_repo&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. git大型文件存储（LFS）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git lfs install
(used to download large files)
sudo apt-get update
sudo apt-get install git-lfs
# Initialize Git LFS
git lfs install
# Clone the repository
git clone https://huggingface.co/ZiyuG/Image-Generation-CoT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;5. 使用huggingface_hub&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from huggingface_hub import snapshot_download
snapshot_download(repo_id=&apos;ZiyuG/Image-Generation-CoT&apos;, local_dir=&apos;./&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;6. 使用datasets下载数据集&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from datasets import load_dataset

# Specify the cache directory
dataset = load_dataset(&quot;imdb&quot;, cache_dir=&quot;/path/to/your/cache/directory&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;7. Linux解压文件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;unzip命令解压zip文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unzip filename.zip -d /path/to/directory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;unrar命令解压rar文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unrar x filename.rar /path/to/directory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;tar命令解压tar文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar -xvf filename.tar -C /path/to/directory
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;编译运行系列&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. torchrun启动指令&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;torchrun --nproc_per_node=2 my_script.py --arg1 value1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. CUDA编译器编译CUDA程序并运行&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CUDA编译器编译CUDA程序并运行（Windows）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Compile CUDA program on Windows
nvcc -ccbin &quot;D:\Program_Files\Visual Studio\BuildTools\VC\Tools\MSVC\14.43.34808\bin\Hostx64\x64\cl.exe&quot; -o 
# Run CUDA program
out.exe try.cu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用CUDA编译器编译CUDA程序并运行（Linux）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Compile CUDA program on Linux
nvcc -ccbin g++ -o x x.cu	# compilation
# Run CUDA program
./x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 使用g++编译C++程序并运行&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;g++编译C++程序并运行（Windows）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ test.cpp -o test.exe
.\test.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;g++编译C++程序并运行（Linux）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g++ test.cpp -o test
./test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. 使用clang编译C++程序并运行&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clang++ test.cpp -o test
./test
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;终端控制系列&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. tmux代理终端&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# create new session	
tmux new -s name    # create a new session with name
tmux ls             # list all sessions
tmux attach -t name  # attach to the session with name  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;退出tmux会话只需要按下&lt;code&gt;Ctrl + b&lt;/code&gt;，然后按下&lt;code&gt;d&lt;/code&gt;键即可。&lt;/p&gt;
&lt;h2&gt;服务器登录与数据传输系列&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 使用ssh连接远程服务器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可以使用ssh实现一个服务器连接另外一个服务器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh username@remote_server_ip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 使用scp传输文件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果需要传输文件，可以使用scp命令。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scp username@remote_server_ip:/path/to/remote/file /path/to/local/directory
scp -r username@remote_server_ip:/path/to/remote/file /path/to/local/directory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-r&lt;/code&gt;是递归的意思，表示传输整个目录。&lt;/p&gt;
&lt;p&gt;需要注意的是上面的指令是从远程服务器传输到本地，如果需要从本地传输到远程服务器，可以将命令中的前后两部分调换位置。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;scp /path/to/local/file username@remote_server_ip:/path/to/remote/directory
scp -r /path/to/local/directory username@remote_server_ip:/path/to/remote/directory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 使用sftp传输文件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;也可以使用sftp命令传输文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sftp username@remote_server_ip
sftp&amp;gt; put /path/to/local/file /path/to/remote/directory
sftp&amp;gt; get /path/to/remote/file /path/to/local/directory
sftp&amp;gt; exit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果需要密码，命令输入后会自动提示输入密码。&lt;/p&gt;
</content:encoded></item><item><title>t-SNE</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/aialgorithms/t-sne/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/aialgorithms/t-sne/</guid><pubDate>Sat, 27 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The core objective of &lt;strong&gt;t-SNE (t-distributed Stochastic Neighbor Embedding)&lt;/strong&gt; is to &lt;strong&gt;reduce the dimensionality&lt;/strong&gt; of high-dimensional data while &lt;strong&gt;preserving local neighborhood structure&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;More precisely, t-SNE tries to make “who is close to whom” in high-dimensional space look similar in the low-dimensional embedding. It is primarily a &lt;strong&gt;visualization&lt;/strong&gt; method (2D/3D), not a general-purpose dimensionality reduction for downstream metrics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What t-SNE preserves (and what it doesn’t)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Preserves: local neighborhoods / nearest-neighbor relationships.&lt;/li&gt;
&lt;li&gt;Does not promise: global distances, cluster sizes, or meaningful absolute axes.&lt;/li&gt;
&lt;li&gt;Typical outcome: points form visually separated islands, but &lt;strong&gt;inter-island distances are not reliable&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. High-Dimensional Similarities $p_{j\mid i}$&lt;/h2&gt;
&lt;p&gt;Let the dataset be $\mathcal{X} = {x_i}_{i=1}^N$, with $x_i \in \mathbb{R}^D$.&lt;/p&gt;
&lt;p&gt;For each center point $x_i$, t-SNE defines a conditional probability that $x_j$ is a neighbor of $x_i$ using a Gaussian kernel with a &lt;strong&gt;point-specific bandwidth&lt;/strong&gt; $\sigma_i$:&lt;/p&gt;
&lt;p&gt;$$
p_{j\mid i} = \frac{\exp\left(-\frac{\lVert x_i - x_j \rVert^2}{2\sigma_i^2}\right)}{\sum_{k \neq i} \exp\left(-\frac{\lVert x_i - x_k \rVert^2}{2\sigma_i^2}\right)},\quad p_{i\mid i}=0.
$$&lt;/p&gt;
&lt;p&gt;Why per-point $\sigma_i$? Because data density varies across the space; a single global kernel width often fails.&lt;/p&gt;
&lt;h3&gt;Perplexity and choosing $\sigma_i$&lt;/h3&gt;
&lt;p&gt;Instead of picking $\sigma_i$ directly, t-SNE chooses it so that the conditional distribution $P_i = {p_{j\mid i}}_{j\neq i}$ has a target &lt;strong&gt;perplexity&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;$$
\mathrm{Perp}(P_i) = 2^{H(P_i)},\quad H(P_i) = -\sum_{j \neq i} p_{j\mid i},\log_2 p_{j\mid i}.
$$&lt;/p&gt;
&lt;p&gt;Intuition: perplexity is an “effective number of neighbors”. In practice, $\sigma_i$ is found by &lt;strong&gt;binary search&lt;/strong&gt; to match the desired perplexity.&lt;/p&gt;
&lt;h3&gt;Symmetrization $p_{ij}$&lt;/h3&gt;
&lt;p&gt;t-SNE uses a symmetric joint distribution over pairs:&lt;/p&gt;
&lt;p&gt;$$
p_{ij} = \frac{p_{j\mid i} + p_{i\mid j}}{2N},\quad p_{ii}=0,\quad \sum_{i\neq j} p_{ij}=1.
$$&lt;/p&gt;
&lt;h2&gt;2. Low-Dimensional Similarities $q_{ij}$&lt;/h2&gt;
&lt;p&gt;Let $y_i \in \mathbb{R}^d$ be the low-dimensional embedding ($d=2$ or $3$). Instead of a Gaussian, t-SNE uses a heavy-tailed Student-$t$ distribution (with 1 degree of freedom, i.e. Cauchy):&lt;/p&gt;
&lt;p&gt;$$
q_{ij} = \frac{\left(1 + \lVert y_i - y_j \rVert^2\right)^{-1}}{\sum_{k \neq l} \left(1 + \lVert y_k - y_l \rVert^2\right)^{-1}},\quad q_{ii}=0.
$$&lt;/p&gt;
&lt;p&gt;This choice addresses the &lt;strong&gt;crowding problem&lt;/strong&gt;: in low dimensions, many moderately-close points compete for limited area. Heavy tails allow moderately distant points to stay separated without forcing everyone into the center.&lt;/p&gt;
&lt;h2&gt;3. Objective: KL Divergence $\mathrm{KL}(P,|,Q)$&lt;/h2&gt;
&lt;p&gt;t-SNE fits the embedding by minimizing:&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}(Y) = \mathrm{KL}(P,|,Q) = \sum_{i\neq j} p_{ij} \log\frac{p_{ij}}{q_{ij}}.
$$&lt;/p&gt;
&lt;p&gt;Important asymmetry: $\mathrm{KL}(P,|,Q)$ heavily penalizes when a high-probability neighbor in $P$ is far apart in $Q$ (i.e. it prioritizes local neighbor preservation).&lt;/p&gt;
&lt;h2&gt;4. Gradient (Attractive vs Repulsive Forces)&lt;/h2&gt;
&lt;p&gt;The gradient w.r.t. a point $y_i$ has a clean “forces” form:&lt;/p&gt;
&lt;p&gt;$$
\frac{\partial \mathcal{L}}{\partial y_i}
= 4\sum_{j\neq i} (p_{ij}-q_{ij}),\frac{(y_i-y_j)}{1+\lVert y_i-y_j\rVert^2}.
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Attractive term&lt;/strong&gt;: $p_{ij}$ pulls close neighbors together.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repulsive term&lt;/strong&gt;: $q_{ij}$ pushes points apart (preventing collapse).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. The Algorithm (Practical t-SNE)&lt;/h2&gt;
&lt;p&gt;In practice, t-SNE is optimized by gradient descent with momentum and a few well-known tricks.&lt;/p&gt;
&lt;h3&gt;Step-by-step&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;(Optional but recommended) Preprocess $X$: standardize features; often apply PCA to 30–50 dims.&lt;/li&gt;
&lt;li&gt;Compute approximate kNN graph (for speed) and distances.&lt;/li&gt;
&lt;li&gt;For each $i$, binary search $\sigma_i$ to match the chosen perplexity, yielding $p_{j\mid i}$.&lt;/li&gt;
&lt;li&gt;Symmetrize to get $p_{ij}$.&lt;/li&gt;
&lt;li&gt;Initialize $y_i$ (random or PCA).&lt;/li&gt;
&lt;li&gt;Optimize $\mathcal{L}(Y)$ with gradient descent.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Two common training tricks&lt;/h3&gt;
&lt;h4&gt;(a) Early exaggeration&lt;/h4&gt;
&lt;p&gt;For an initial phase, replace $p_{ij}$ with $\alpha p_{ij}$, where $\alpha&amp;gt;1$ (often 4–12). This encourages clusters to separate early before fine local fitting.&lt;/p&gt;
&lt;h4&gt;(b) Learning rate and momentum&lt;/h4&gt;
&lt;p&gt;Learning rate too small: points may clump and move slowly.
Learning rate too large: embedding may “explode” and become unstable.&lt;/p&gt;
&lt;h2&gt;6. Complexity and Accelerations&lt;/h2&gt;
&lt;p&gt;Naive t-SNE uses all pairwise interactions and costs $O(N^2)$ in memory/time.&lt;/p&gt;
&lt;p&gt;Common accelerations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Barnes–Hut t-SNE&lt;/strong&gt;: approximates repulsive forces with a quadtree/octree, typically $O(N\log N)$ (mostly for 2D/3D).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FIt-SNE / FFT-based&lt;/strong&gt;: uses interpolation + FFT for faster repulsive force computation, often near $O(N)$ in practice.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Approximate neighbors&lt;/strong&gt;: compute $p_{ij}$ using kNN only (sparse $P$), which is crucial for large $N$.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7. Engineering Notes (Hyperparameters + Pitfalls)&lt;/h2&gt;
&lt;h3&gt;Recommended preprocessing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Standardize features (mean 0, variance 1) unless distances already meaningful.&lt;/li&gt;
&lt;li&gt;PCA to 30–50 dims often improves stability and speed (and denoises).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Perplexity: how to pick&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Small (5–30): focuses on very local structure; more fragmented islands.&lt;/li&gt;
&lt;li&gt;Medium (30–100): smoother global neighborhood; fewer islands.&lt;/li&gt;
&lt;li&gt;Rule of thumb: perplexity should be smaller than $N/3$ and large enough to capture the neighborhood scale you care about.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Common failure modes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Interpreting island distances as meaningful global geometry.&lt;/li&gt;
&lt;li&gt;Interpreting island size/density as real density (t-SNE distorts densities).&lt;/li&gt;
&lt;li&gt;Comparing two different runs without fixing randomness: different seeds can produce different layouts.&lt;/li&gt;
&lt;li&gt;Applying t-SNE directly to raw pixels or unnormalized features; PCA/standardization typically helps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;About “new points” (out-of-sample)&lt;/h3&gt;
&lt;p&gt;Classic t-SNE is &lt;strong&gt;non-parametric&lt;/strong&gt;: it optimizes ${y_i}$ for the training set only.
If you need to embed new samples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;parametric t-SNE&lt;/strong&gt; (learn a neural net to predict $y$).&lt;/li&gt;
&lt;li&gt;Or use libraries that support adding points approximately (e.g. openTSNE).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;8. Minimal Code Examples (Python)&lt;/h2&gt;
&lt;h3&gt;scikit-learn&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import numpy as np
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler

# X: [N, D]
X = ...
X = StandardScaler().fit_transform(X)

Z = TSNE(
    # Depending on your sklearn version, this may be `max_iter` instead of `n_iter`.
    n_components=2,
    perplexity=30,
    learning_rate=&quot;auto&quot;,
    init=&quot;pca&quot;,
    max_iter=1000,
    random_state=42,
).fit_transform(X)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;init=&quot;pca&quot;&lt;/code&gt; usually improves stability.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;learning_rate=&quot;auto&quot;&lt;/code&gt; is often a good default in recent sklearn.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;openTSNE (often faster / more flexible)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from openTSNE import TSNE
from sklearn.decomposition import PCA

X = ...
X_50 = PCA(n_components=50, random_state=42).fit_transform(X)

embedding = TSNE(
    n_components=2,
    perplexity=30,
    initialization=&quot;pca&quot;,
    random_state=42,
).fit(X_50)

Z = embedding.view(np.ndarray)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;9. Relationship to Related Methods (Quick Map)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SNE&lt;/strong&gt;: uses Gaussian in both spaces; suffers more from crowding.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;t-SNE&lt;/strong&gt;: fixes crowding via Student-$t$ in low-dim.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UMAP&lt;/strong&gt;: often preserves more global structure, faster for big datasets, supports transform (out-of-sample) more naturally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PCA&lt;/strong&gt;: linear; preserves global variance directions; good baseline and a common pre-step for t-SNE.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Self-Distillation</title><link>https://adalovelemon.github.io/posts/content/paperreading/foundations/old/selfdistillation/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/paperreading/foundations/old/selfdistillation/</guid><pubDate>Sat, 27 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Self-Distillation (Motivation &amp;amp; Definition)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Self-distillation can be read as: &lt;strong&gt;the model teaches itself&lt;/strong&gt;. During training we maintain a &lt;em&gt;student&lt;/em&gt; and a &lt;em&gt;teacher&lt;/em&gt;; the teacher is not an externally trained model but is usually an exponential moving average (EMA / momentum encoder) of the student parameters.&lt;/li&gt;
&lt;li&gt;Compared to classical knowledge distillation, there are no human labels and no separate pretrained teacher. The supervision signal comes from the idea that “different augmented views of the same image should agree.”&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Emerging Properties in Self-Supervised Vision Transformers (DINOv1, 2021)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Paper: https://arxiv.org/abs/2104.14294&lt;/li&gt;
&lt;li&gt;Codes: https://github.com/facebookresearch/dino&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Overview&lt;/h3&gt;
&lt;p&gt;This paper proposes DINO, a self-distillation framework with no labels, to pretrain ViTs. Besides the fact that the DINO method works quite well on this kind of architecture, there are also two interesting properties emerging from the learned features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;DINO features can be &lt;strong&gt;naturally applied for semantic segmentation&lt;/strong&gt; (without fine-tuning):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Just segment scenes with a nearest neighbor between consecutive frames can lead to competitive results on DAVIS-2017 benchmark;&lt;/li&gt;
&lt;li&gt;Different attention heads capture different semantic concepts (object parts, objects, background, etc).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DINO features also &lt;strong&gt;excel at classification tasks&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linear probing on ImageNet achieves 80.1% top-1 accuracy (ViT-B/8);&lt;/li&gt;
&lt;li&gt;k-NN classification (k=20) on ImageNet achieves 74.5% top-1 accuracy (ViT-S/16).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Self-Distillation means the DINO method involves two models—a student model and a teacher model, and both of the model share the same architecure but different parameters. Given a batch of input images $x \in \mathbb{R}^{B \times 3 \times H \times W}$, apply two different random transformations on it to get two views $x_s$ and $x_t$.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;For the student model, after being feed with $x_s$, it will output features $z_s \in \mathbb{R}^{B\times d}$; then apply a Softmax operation with temperature $\tau_s$ on $z_s$&apos;s feature dimension to get a student probability distribution $p_s$.
$$
p_s = \text{Softmax}\left(\frac{z_s}{\tau_s}, \text{dim}=1\right)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For the teacher model, after output features $z_t \in \mathbb{R}^{B\times d}$ when given $x_t$, firstly center $z_t$ by subtracting a mean vector $c \in \mathbb{R}^{d}$ (dynamically updated during training), then apply the same Softmax operation with temperature $\tau_t$ to get the teacher probability distribution $p_t$.
$$
\begin{aligned}
p_t = \text{Softmax}\left(\frac{z_t - c}{\tau_t}, \text{dim}=1\right) \
c \leftarrow m \cdot c + (1-m) \cdot \frac{1}{B} \sum_{i=1}^{B} z_t^{(i)}
\end{aligned}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, we want the two distributions to be as close as possible, so we minimize the cross-entropy loss between them:
$$
\min_{\theta_s} \text{CE}(p_t, p_s) = -\frac{1}{B}\sum_{i=1}^B\sum_{j=1}^d (p_t^{(ij)} \cdot \log p_s^{(ij)})
$$&lt;/p&gt;
&lt;p&gt;Note that only the student model is updated by back-propagation; the teacher model&apos;s parameters are updated by an exponential moving average (EMA) of the student model&apos;s parameters:
$$
\theta_t \leftarrow \lambda \cdot \theta_t + (1-\lambda) \cdot \theta_s
$$&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/dino_arch.png&quot; alt=&quot;DINO Framework&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# gs, gt: student and teacher
# C: center buffer
# tps, tpt: student/teacher temperature
# m_net, m_center: EMA momentum for network and center

gt.params = gs.params
for x in loader:
    x_1, x_2 = augment_1(x), augment_2(x)  # random augmentations

    z_s1, z_s2 = gs(x_1), gs(x_2)          # student forward
    z_t1, z_t2 = gt(x_1), gt(x_2)          # teacher forward

    p_s1, p_s2 = softmax(z_s1 / tps), softmax(z_s2 / tps)  # student prob
    p_t1, p_t2 = softmax((z_t1 - C) / tpt), softmax((z_t2 - C) / tpt)  # teacher prob

    loss = 0.5 * (CE(p_t1, p_s2) + CE(p_t2, p_s1))  # cross-entropy loss
    loss.backward()

    gs_optimizer.zero_grad()
    gs_optimizer.step()  # update student

    # update teacher by EMA
    for p_s, p_t in zip(gs.params, gt.params):
        p_t.data = m_net * p_t.data + (1 - m_net) * p_s.data
    
    # update center
    C = m_center * C + (1 - m_center) * mean([z_t1, z_t2])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Technical Details&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Random Augmentation Strategy&lt;/strong&gt;&lt;br /&gt;
Two major types of augmentations are applied here:
&lt;ul&gt;
&lt;li&gt;BYOL&apos;s augmentation: color jittering, Gaussian blur and solarization;&lt;/li&gt;
&lt;li&gt;Multi-crop: each input image $x$ is transformed into 2 global views $x_1^g, x_2^g$ (224x224, covering most of the image) and 6–10 local crops (96x96, randomly sampled patches), constructing a set of views $V = {x_1^g, x_2^g, x_1^l, \ldots, x_n^l}$. The teacher only processes the global views, while the student processes all views (global + local). This design encourages the student to learn consistent representations across scales and promotes fine-grained semantic understanding—critical for downstream dense prediction tasks like segmentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Actual loss function:
$$
\min_{\theta_s} \sum_{x\in{x_1^g, x_2^g}}\sum_{x&apos;\in V, x&apos; \ne x} \text{CE}\left(p_t(x), p_s(x&apos;)\right)
$$&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Centering to Prevent Collapse&lt;/strong&gt;&lt;br /&gt;
While there are multiple stablizing methods including constrastive loss, clustering constraints, predictor and batch normalizations, only a centering and sharpening of the momentum teacher outputs (via using a low value of $\tau_t$) is enough to avoid model collapse—a degenerate solution where all inputs map to the same feature.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Centering avoids the collapse induced by a dominant dimension, but encourages a uniform distribution.&lt;/li&gt;
&lt;li&gt;Sharpening prevents the model from outputting uniform distributions across all dimensions, while allowing some dimensions to dominate.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;[CLS] token&lt;/strong&gt;&lt;br /&gt;
An extra learnable token is prepended to the sequence, which acts to aggregate global information from the entire sequence. Although [CLS] token is primarily designed for supervised classification tasks, considering by adding a aggregative token can obtain similar benefits, we continue to use this name in DINO. A projection head is applied to the [CLS] token output to produce a global representation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Attention Maps for Different Heads&lt;/strong&gt;
Different attention heads in the pretrained ViT capture different semantic concepts. For example, some heads focus on object parts, while others attend to whole objects or background regions. This diversity in attention patterns allows the model to learn rich and varied representations that are useful for downstream tasks like segmentation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;assets/head_attn.png&quot; alt=&quot;DINO Attention Maps&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Writing Tricks&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Major Issue&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Information Loss&lt;/em&gt;: Supervised pretraining usually reduces the rich visual information in an image to a single concept (label).&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Unlabeled Scales Up&lt;/em&gt;: Supervised pretraining requires large labeled datasets, which does not scale well with the increasing number of images.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Motivation&lt;/strong&gt;: Self-supervised works well on NLP tasks, can be transferred to vision tasks?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Key Design&lt;/strong&gt;: Like knowledge distillation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;iBOT: Image BERT Pre-Training with Online Tokenizer&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Paper: https://arxiv.org/abs/2111.07832&lt;/li&gt;
&lt;li&gt;One-liner: extends DINO-style self-distillation from mostly global/CLS supervision to &lt;strong&gt;token-level&lt;/strong&gt; targets, and combines it with masked image modeling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;How to think about it&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DINO is closer to “make whole-image representations consistent across views”, while iBOT also “makes patch/token predictions consistent”.&lt;/li&gt;
&lt;li&gt;In practice, iBOT often yields better fine-grained features (more friendly to detection/segmentation).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;DINOv2: Learning Robust Visual Features Without Any Labels&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Paper: https://arxiv.org/abs/2304.07193&lt;/li&gt;
&lt;li&gt;One-liner: scales the DINO/iBOT-style self-distillation recipe and refines training details to obtain very strong frozen features.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Main upgrades (reproduction-oriented)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Larger/cleaner data and improved training recipe (augmentations, regularization, optimization hyperparameters).&lt;/li&gt;
&lt;li&gt;Uses token-level training signals (aligned with iBOT) plus more stable training details.&lt;/li&gt;
&lt;li&gt;The resulting ViT features transfer well to classification/retrieval/segmentation/depth, acting as a “general-purpose vision backbone”.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>On-Policy Distillation</title><link>https://adalovelemon.github.io/posts/content/paperreading/foundations/old/onpolicydistillation/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/paperreading/foundations/old/onpolicydistillation/</guid><pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Current Dilemma for LLM&lt;/h2&gt;
&lt;p&gt;Currently, &lt;em&gt;large models&lt;/em&gt; are &lt;em&gt;post‑trained via RLHF&lt;/em&gt;, making them &lt;em&gt;powerful&lt;/em&gt; but &lt;em&gt;expensive to train and deploy&lt;/em&gt;, while &lt;em&gt;smaller models&lt;/em&gt; are usually &lt;em&gt;fine‑tuned with SFT or KD&lt;/em&gt; methods and are &lt;em&gt;easier to deploy and adapt&lt;/em&gt; but often &lt;em&gt;lack the performance&lt;/em&gt; of larger models.&lt;/p&gt;
&lt;p&gt;Another notable point is that, compared to large models, smaller models sufficiently trained often outperform larger, general models in their own domains. Also, there are some benefits to using smaller models:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Edge Deployment&lt;/strong&gt;: Smaller models can be deployed on edge devices locally with limited computational resources, enabling real-time inference and reducing latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accelerated Updations&lt;/strong&gt;: Smaller models can be updated and fine-tuned more quickly, allowing for faster iteration cycles and adaptation to new data or tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Information Security&lt;/strong&gt;: Deploying smaller models locally can avoid potential data leakage, enhancing data privacy and security by minimizing the need to transmit sensitive information to external servers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thus, how to combine the advantages and build a smaller model that is both powerful and efficient to deploy becomes a true question.&lt;/p&gt;
&lt;h2&gt;What is On‑Policy Distillation?&lt;/h2&gt;
&lt;p&gt;In a nutshell, on‑policy distillation &lt;em&gt;collects states from the student’s distribution&lt;/em&gt;, &lt;em&gt;labels those states with the teacher’s outputs&lt;/em&gt;, &lt;em&gt;supervises the student on states it actually visits&lt;/em&gt;, &lt;em&gt;aligns the student’s decision boundary with its induced distribution&lt;/em&gt;, and &lt;em&gt;reduces distribution shift and improves robustness&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Why on-policy?&lt;/h3&gt;
&lt;p&gt;Traditional RLHF/RLAIF assigns a scalar reward to each student rollout based on human or model preferences, rather than step-by-step corrections. In contrast, on-policy approaches &lt;em&gt;instruct the student model to mimic the teacher&apos;s behavior on the student&apos;s own sampled trajectories&lt;/em&gt;, enabling more direct learning of the correct modes.&lt;/p&gt;
&lt;p&gt;Consider a scenario where the student must complete a task independently.The student might fail early and ultimately provide a wrong answer. Off-policy means we simply supervise the student by enforcing it to answer like teacher’s output, it could never know why its own response is wrong, and never knows where it actually fails from. In the contrast, on-policy allows the teacher to point out the mistakes along the student&apos;s own reasoning path, and guide the student to correct those mistakes step by step. This way, the student can learn faster and grasp the correct reasoning process.&lt;/p&gt;
&lt;h3&gt;Why distillation?&lt;/h3&gt;
&lt;p&gt;Here, distillation is a concept different from reinforcement learning. &lt;em&gt;RL frameworks&lt;/em&gt; have a main drawback that they &lt;em&gt;provide very sparse feedback&lt;/em&gt;. In RL, no matter how long or detailed the model’s output is (i.e., how many tokens it generates), it only receives one scalar reward as feedback for the entire output. This makes the learning signal extremely information-poor (sparse) and coarse compared to methods like supervised learning or distillation, which provide per-token guidance.&lt;/p&gt;
&lt;p&gt;To better understand the sparsity of RL, imagine you do your homework and the next day the teacher only gives you a boolean score (tick or cross) at the end, without any hints or corrections along the way. To improve, you’d have to redo the entire assignment and guess which part was wrong—a frustrating and inefficient process. If you instead get feedback on each question or step, you can learn and improve much faster.&lt;/p&gt;
&lt;p&gt;This is the key advantage of distillation: it provides dense, token-level, informative feedback at every step, allowing the student to learn more effectively. The student model will try to match the output distribution of a teacher model. This dense supervision not only accelerates learning but also enables smaller models to acquire nuanced reasoning patterns that would be nearly impossible to learn from sparse rewards alone.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Sampling&lt;/th&gt;
&lt;th&gt;Reward signal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SFT&lt;/td&gt;
&lt;td&gt;off-policy&lt;/td&gt;
&lt;td&gt;dense&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RL&lt;/td&gt;
&lt;td&gt;on-policy&lt;/td&gt;
&lt;td&gt;sparse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPD&lt;/td&gt;
&lt;td&gt;on-policy&lt;/td&gt;
&lt;td&gt;dense&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Unlike DPO—which still relies on preference pairs and implicit reward assumptions—OPD requires only a fixed teacher and standard supervised learning.&lt;/p&gt;
&lt;h3&gt;Formal objective&lt;/h3&gt;
&lt;p&gt;Let $\pi_\theta$ denote the student policy parameterized by $\theta$ and $\pi_0$ the (fixed) teacher policy. The canonical on‑policy distillation objective is
$$
\text{RKLD}(\theta) = \mathbb{E}&lt;em&gt;{\mathbf{x} \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=1}^{T-1} \log \pi_\theta(x_{t+1} | x_{\leq t}) - \log \pi_0(x_{t+1} | x_{\leq t}) \right]
$$
where we choose the per-token reverse KLD for each token conditioned on the same prior trajectory. When the student continues to imitate teacher&apos;s behavior, the RKLD loss will diminish to zero.&lt;/p&gt;
&lt;h3&gt;Workflow&lt;/h3&gt;
&lt;p&gt;OPD operates as an iterative loop: the student continuously generates new trajectories, which are immediately labeled by the teacher and used to update itself—enabling rapid adaptation to its own evolving behavior.&lt;/p&gt;
&lt;h2&gt;Experiments&lt;/h2&gt;
&lt;p&gt;The following are the listed experiments displayed in the original &lt;a href=&quot;#references&quot;&gt;blog&lt;/a&gt;:&lt;/p&gt;
&lt;h3&gt;1. Math Reasoning (AIME&apos;24 Benchmark)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Starting from a Qwen3-8B model first fine-tuned on 400K teacher-generated samples (achieving ~60% AIME accuracy), subsequent training with:
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Continued SFT (off-policy distillation)&lt;/em&gt;: Requires ~2M samples to improve from 60% to 70% accuracy.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;RL (e.g., as in Qwen3 Technical Report)&lt;/em&gt;: Achieves 67.6% at a cost of &lt;em&gt;17,920 GPU-hours&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;On-Policy Distillation&lt;/em&gt;: Reaches &lt;em&gt;&amp;gt;70% accuracy in just ~150 training steps (~77K samples)&lt;/em&gt;, costing only &lt;em&gt;~1,800 GPU-hours&lt;/em&gt;—&lt;em&gt;9–30× more compute-efficient&lt;/em&gt; than RL.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The efficiency gain stems from avoiding reward modeling and policy gradient estimation—OPD uses only standard supervised learning on teacher-labeled student outputs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. Personalization &amp;amp; Continual Learning (Internal Assistant)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;After domain fine-tuning on internal docs, the model suffers &lt;em&gt;catastrophic forgetting&lt;/em&gt;: instruction-following score (IF-eval) drops from 85% to 45–79%.&lt;/li&gt;
&lt;li&gt;Applying &lt;em&gt;On-Policy Distillation&lt;/em&gt; (using the original Qwen3-8B as teacher) on chat data:
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Recovers IF-eval to 83%&lt;/em&gt;,&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Preserves domain knowledge&lt;/em&gt; (internal QA stays at ~41%).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Demonstrates strong capability for &lt;em&gt;continual learning without forgetting&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Single-Example Generalization&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Trained on &lt;em&gt;only one AIME problem&lt;/em&gt; (5,120 sampled trajectories), the student achieves &lt;em&gt;~50% zero-shot accuracy on the full AIME&apos;24 test set&lt;/em&gt;—demonstrating strong out-of-distribution generalization.&lt;/li&gt;
&lt;li&gt;On-Policy Distillation still &lt;em&gt;approaches the teacher&apos;s overall AIME&apos;24 performance&lt;/em&gt;,&lt;/li&gt;
&lt;li&gt;Suggests it learns &lt;em&gt;general reasoning strategies&lt;/em&gt;, not just memorization.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;On-Policy Distillation delivers &lt;strong&gt;superior performance, sample efficiency, and continual learning ability&lt;/strong&gt; compared to SFT and RL. By providing &lt;strong&gt;dense, token-level supervision on the student&apos;s own trajectories&lt;/strong&gt;, it enables small models to closely mimic expert teachers—at a fraction of the cost.&lt;/p&gt;
&lt;p&gt;This suggests that with the right training paradigm, &lt;strong&gt;small models can match or exceed large models in their domains&lt;/strong&gt;—making scale less critical than alignment and feedback quality.&lt;/p&gt;
&lt;h2&gt;REFERENCES&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://thinkingmachines.ai/blog/on-policy-distillation/&quot;&gt;Lu, K., et al. &quot;On-Policy Distillation.&quot; Thinking Machines, 2024. https://thinkingmachines.ai/blog/on-policy-distillation/&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Fourier and Wavelets for Deep Learning</title><link>https://adalovelemon.github.io/posts/content/coursenotes/mathslab/frequency/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/mathslab/frequency/</guid><pubDate>Mon, 08 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. 为什么需要小波：从傅里叶的局限开始&lt;/h2&gt;
&lt;p&gt;令 $f\in L^2(\mathbb{R})$。傅里叶变换（在 $L^2$ 意义下）把信号表示为全局正弦基的叠加：&lt;/p&gt;
&lt;p&gt;$$
\hat f(\omega) = \int_{-\infty}^{\infty} f(t) e^{-i\omega t},dt.
$$&lt;/p&gt;
&lt;p&gt;它非常适合回答“有哪些频率成分”，但它天然是&lt;strong&gt;全局积分&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 $f$ 在某个时刻出现瞬态/突变（非平稳），$\hat f$ 仍是全局混合的；&lt;/li&gt;
&lt;li&gt;“频率在哪里发生”这个问题，傅里叶域本身不回答。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;短时傅里叶（STFT）通过窗函数 $g(t)$ 提供局部化：&lt;/p&gt;
&lt;p&gt;$$
\mathrm{STFT}_g f(\tau,\omega)=\int f(t),\overline{g(t-\tau)},e^{-i\omega t},dt.
$$&lt;/p&gt;
&lt;p&gt;但 STFT 的窗宽是固定的：同一把尺子同时量低频与高频，总会不合适。&lt;/p&gt;
&lt;p&gt;小波的核心动机是：&lt;strong&gt;让“窗”随频率缩放&lt;/strong&gt;。高频用短窗（好定位），低频用长窗（好分辨）。这正是多分辨率（multi-resolution）的来源。&lt;/p&gt;
&lt;h2&gt;2. 连续小波变换（CWT）：严格的数学定义&lt;/h2&gt;
&lt;h3&gt;2.1 母小波、伸缩与平移&lt;/h3&gt;
&lt;p&gt;选取一个母小波（mother wavelet）$\psi\in L^2(\mathbb{R})$。对尺度 $a\in\mathbb{R}\setminus{0}$ 和位置 $b\in\mathbb{R}$，定义一族函数：&lt;/p&gt;
&lt;p&gt;$$
\psi_{a,b}(t)=\frac{1}{\sqrt{|a|}},\psi\left(\frac{t-b}{a}\right).
$$&lt;/p&gt;
&lt;p&gt;其中 $\frac{1}{\sqrt{|a|}}$ 保证 $|\psi_{a,b}|_2=|\psi|_2$（能量归一）。&lt;/p&gt;
&lt;h3&gt;2.2 CWT 的定义（内积/相关）&lt;/h3&gt;
&lt;p&gt;连续小波变换定义为&lt;/p&gt;
&lt;p&gt;$$
\mathcal{W}&lt;em&gt;\psi f(a,b) = \langle f,\psi&lt;/em&gt;{a,b}\rangle = \int_{-\infty}^{\infty} f(t),\overline{\psi_{a,b}(t)},dt.
$$&lt;/p&gt;
&lt;p&gt;可以把 $\mathcal{W}_\psi f(a,b)$ 理解为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在位置 $b$ 附近，信号与“尺度为 $a$ 的模板”$\psi_{a,b}$ 的相似度；&lt;/li&gt;
&lt;li&gt;相似度越大，说明在该位置确实存在该尺度的结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 可容许性（admissibility）与可逆性&lt;/h3&gt;
&lt;p&gt;要让 CWT 可逆，需要母小波满足可容许性条件：&lt;/p&gt;
&lt;p&gt;$$
C_\psi = \int_0^{\infty} \frac{|\hat\psi(\omega)|^2}{\omega},d\omega &amp;lt; \infty.
$$&lt;/p&gt;
&lt;p&gt;一个常见的充要特征是 &lt;strong&gt;零均值&lt;/strong&gt;（zero mean）：&lt;/p&gt;
&lt;p&gt;$$
\int \psi(t),dt = 0 \quad \Longleftrightarrow \quad \hat\psi(0)=0.
$$&lt;/p&gt;
&lt;p&gt;零均值意味着小波是“带通/高通”的：它对常数（直流分量）不响应。&lt;/p&gt;
&lt;p&gt;在合适的函数空间假设下，有反演公式（给出思想即可）：&lt;/p&gt;
&lt;p&gt;$$
f(t) = \frac{1}{C_\psi}\int_{-\infty}^{\infty}\int_0^{\infty}
\mathcal{W}&lt;em&gt;\psi f(a,b),\psi&lt;/em&gt;{a,b}(t),\frac{da,db}{a^2}.
$$&lt;/p&gt;
&lt;h3&gt;2.4 能量与内积的观点&lt;/h3&gt;
&lt;p&gt;CWT 是在一族缩放平移的函数上做“投影系数”。当“在小波域做损失/相似度”时，本质上就是在另一个坐标系统里度量能量与相关。&lt;/p&gt;
&lt;h2&gt;3. 小波的原理：尺度对应频率，多分辨率来自缩放&lt;/h2&gt;
&lt;p&gt;尺度 $a$ 与频率大致呈反比。直觉上：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$|a|$ 越大，$\psi_{a,b}$ 越“宽”，更像低频模板；&lt;/li&gt;
&lt;li&gt;$|a|$ 越小，$\psi_{a,b}$ 越“窄”，更像高频模板。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此 CWT 系数 $\mathcal{W}_\psi f(a,b)$ 是一种“时-尺度（time-scale）”表示，也常被视为时频联合表示。&lt;/p&gt;
&lt;p&gt;这也是小波常被用于非平稳信号：突变/边缘在小尺度（高频）会产生显著响应，同时其发生位置 $b$ 也被保留下来。&lt;/p&gt;
&lt;h2&gt;4. 小波的关键性质（选小波时真正关心的东西）&lt;/h2&gt;
&lt;p&gt;不同小波家族（Haar、Daubechies、Symlets、Coiflets、双正交 CDF 等）差异主要体现在以下性质上。&lt;/p&gt;
&lt;h3&gt;4.1 零均值与带通特性&lt;/h3&gt;
&lt;p&gt;零均值 $\int \psi(t)dt=0$ 使得小波更像“差分/导数”算子：对缓慢变化的趋势不敏感，但对变化（边缘、纹理、瞬态）敏感。&lt;/p&gt;
&lt;h3&gt;4.2 消失矩（vanishing moments）：为何小波适合稀疏表示&lt;/h3&gt;
&lt;p&gt;若 $\psi$ 满足&lt;/p&gt;
&lt;p&gt;$$
\int t^k\psi(t),dt = 0,\quad k=0,1,\dots,p-1,
$$&lt;/p&gt;
&lt;p&gt;则称它有 $p$ 阶消失矩。含义是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;小波对低阶多项式趋势“正交”，因此对平滑区域响应很弱；&lt;/li&gt;
&lt;li&gt;对分段光滑信号/自然图像，小波系数往往高度稀疏（大多接近 0，少数显著系数集中在边缘/纹理处）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是压缩（JPEG2000）与去噪（阈值化）的数学原因之一。&lt;/p&gt;
&lt;h3&gt;4.3 正交 / 双正交：能量是否保留、重构是否稳定&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;正交小波&lt;/strong&gt;：变换可看作正交矩阵（在离散有限维情形），保内积与范数；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;双正交小波&lt;/strong&gt;：分析与合成使用不同基（更灵活，比如线性相位），但不再严格保内积。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.4 紧支撑、正则性与边界效应&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;紧支撑&lt;/strong&gt;决定局部性与计算量；&lt;/li&gt;
&lt;li&gt;**正则性（光滑性）**影响频谱泄漏与重构质量；&lt;/li&gt;
&lt;li&gt;实际实现必须处理边界延拓（periodic / symmetric / zero-pad），这会影响是否严格正交。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. 离散小波变换（DWT）：多分辨率分析（MRA）与滤波器组&lt;/h2&gt;
&lt;p&gt;深度学习里常用的是 DWT / FWT（fast wavelet transform），它来自 MRA。&lt;/p&gt;
&lt;h3&gt;5.1 多分辨率分析的结构&lt;/h3&gt;
&lt;p&gt;MRA 给出一串嵌套子空间：&lt;/p&gt;
&lt;p&gt;$$
\cdots \subset V_{j+1}\subset V_j\subset V_{j-1}\subset \cdots \subset L^2(\mathbb{R}),
$$&lt;/p&gt;
&lt;p&gt;并且存在细节空间 $W_j$ 使得&lt;/p&gt;
&lt;p&gt;$$
V_{j-1} = V_j \oplus W_j.
$$&lt;/p&gt;
&lt;p&gt;直觉上：$V_j$ 表示“分辨率为 $2^{-j}$ 的近似”，$W_j$ 表示“从更粗到更细需要补的细节”。&lt;/p&gt;
&lt;h3&gt;5.2 尺度函数与两尺度方程&lt;/h3&gt;
&lt;p&gt;用尺度函数（scaling function）$\varphi$ 生成 $V_j$，它满足两尺度关系&lt;/p&gt;
&lt;p&gt;$$
\varphi(t) = \sqrt{2}\sum_{n} h[n],\varphi(2t-n),
$$&lt;/p&gt;
&lt;p&gt;小波函数满足&lt;/p&gt;
&lt;p&gt;$$
\psi(t) = \sqrt{2}\sum_{n} g[n],\varphi(2t-n).
$$&lt;/p&gt;
&lt;p&gt;这里 $h[n]$ 是低通滤波器，$g[n]$ 是高通滤波器。&lt;/p&gt;
&lt;h3&gt;5.3 Mallat 算法：分析/合成滤波器组&lt;/h3&gt;
&lt;p&gt;对离散信号 $x[n]$，一级分解通常写成&lt;/p&gt;
&lt;p&gt;$$
a_1[k] = \sum_n h[n-2k]x[n],\quad d_1[k] = \sum_n g[n-2k]x[n].
$$&lt;/p&gt;
&lt;p&gt;也就是：低通/高通卷积后再下采样（$\downarrow 2$）。多层分解对 $a_1$ 继续重复。&lt;/p&gt;
&lt;p&gt;如果使用正交小波并选取匹配的合成滤波器，则可实现&lt;strong&gt;完美重构&lt;/strong&gt;（perfect reconstruction）。&lt;/p&gt;
&lt;h3&gt;5.4 2D 图像：四个子带的意义&lt;/h3&gt;
&lt;p&gt;对二维图像做可分离 DWT，一层得到四个子带：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LL&lt;/strong&gt;：低频近似（整体结构/光照/大形状）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LH&lt;/strong&gt;：水平细节（对“水平边缘/竖向变化”敏感，取决于具体的约定）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HL&lt;/strong&gt;：垂直细节&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HH&lt;/strong&gt;：对角细节（高频纹理）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;更重要的理解是：它们对应不同方向的带通滤波响应。&lt;/p&gt;
&lt;h2&gt;6. 小波的物理意义：局部带通滤波 + 局部差分&lt;/h2&gt;
&lt;p&gt;从信号处理角度，小波系数就是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在某一尺度上，用一个“带通滤波器”去扫过信号；&lt;/li&gt;
&lt;li&gt;系数的大小表示该位置是否存在该尺度的振荡/边缘/纹理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;零均值 + 消失矩让它类似“局部差分/局部导数”。因此在图像里：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;边缘位置会在高频子带产生大系数；&lt;/li&gt;
&lt;li&gt;平滑区域系数接近 0。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也是为什么小波看起来很像“多尺度的边缘检测器”。&lt;/p&gt;
&lt;h2&gt;7. 小波在特征提取中的作用&lt;/h2&gt;
&lt;p&gt;把“系数 = 局部尺度-方向响应”这句话吃透，就能理解它在特征提取中的价值。&lt;/p&gt;
&lt;h3&gt;7.1 稀疏化与去噪：把“结构”从“噪声”里分离&lt;/h3&gt;
&lt;p&gt;对分段光滑信号，噪声往往在高频子带里均匀铺开，而真实结构对应少量大系数。典型做法是对子带做软阈值：&lt;/p&gt;
&lt;p&gt;$$
ilde w = \mathrm{sign}(w)\max(|w|-\lambda,0).
$$&lt;/p&gt;
&lt;p&gt;这是一种很直接的“特征提纯”。&lt;/p&gt;
&lt;h3&gt;7.2 多尺度边缘与纹理：比单尺度算子更稳定&lt;/h3&gt;
&lt;p&gt;Sobel/Laplacian 是单尺度；小波给出一组尺度。很多视觉结构（文字笔画、纹理颗粒、轮廓）本质是多尺度的，小波系数把它们自然地分开。&lt;/p&gt;
&lt;h3&gt;7.3 可解释的频带解耦：低频=内容，高频=细节&lt;/h3&gt;
&lt;p&gt;在生成/压缩/蒸馏里经常使用“低频对齐语义，高频对齐纹理”的说法。这里“低频/高频”不是口号，而是通过 LL vs (LH,HL,HH) 子带有了明确操作对象。&lt;/p&gt;
&lt;h3&gt;7.4 小波散射（Wavelet Scattering）：把小波当作固定的特征提取前端&lt;/h3&gt;
&lt;p&gt;如果希望在理论上更“稳”，散射网络用固定小波滤波器组 + 非线性 + 平均池化构造对平移/形变稳定的表征。它强调的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用小波获得多尺度局部响应；&lt;/li&gt;
&lt;li&gt;用非线性把能量折叠到可用特征上；&lt;/li&gt;
&lt;li&gt;用平均聚合得到稳定性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（这部分可以作为“把小波引入深度学习”最干净的数学入口。）&lt;/p&gt;
&lt;h2&gt;8. 小波与深度学习：在网络里到底扮演什么角色&lt;/h2&gt;
&lt;p&gt;这里不把小波当“频域花活”，而是明确它提供的能力。&lt;/p&gt;
&lt;h3&gt;8.1 作为结构化下采样（替代 pooling/stride）&lt;/h3&gt;
&lt;p&gt;用 DWT 替代 stride/pooling 的关键收益：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不是简单丢弃信息，而是把信息分流到不同子带；&lt;/li&gt;
&lt;li&gt;网络可以选择保留/强调某些频带。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.2 作为归纳偏置：让网络天然具备多尺度与方向选择性&lt;/h3&gt;
&lt;p&gt;卷积当然也能学到类似滤波器，但小波提供了一个“开局就合理”的基底：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;低层特征更容易对齐到边缘/纹理；&lt;/li&gt;
&lt;li&gt;对小数据或强噪声场景更稳。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8.3 作为可解释的对齐/蒸馏/损失空间&lt;/h3&gt;
&lt;p&gt;可以在每个子带上分别设计损失：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对 LL 强约束（结构/语义）；&lt;/li&gt;
&lt;li&gt;对 HH/LH/HL 软约束（纹理/边缘），或者反过来强调细节。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这比“在傅里叶幅度谱上做全局约束”更容易保持空间局部性。&lt;/p&gt;
&lt;h2&gt;9. 小波域 vs 空域：余弦相似度何时相等？&lt;/h2&gt;
&lt;p&gt;余弦相似度定义为&lt;/p&gt;
&lt;p&gt;$$
\mathrm{CosSim}(f,g)=\frac{\langle f,g\rangle}{|f|_2,|g|_2}.
$$&lt;/p&gt;
&lt;p&gt;如果离散小波变换可以表示为一个正交矩阵 $\mathbf{W}\in\mathbb{R}^{n\times n}$（这需要：正交小波 + 全系数拼接 + 合适的边界条件，使其整体是正交变换），则&lt;/p&gt;
&lt;p&gt;$$
\langle \mathbf{W}f,\mathbf{W}g\rangle = \langle f,g\rangle,\quad |\mathbf{W}f|_2=|f|_2,
$$&lt;/p&gt;
&lt;p&gt;从而 $\mathrm{CosSim}(\mathbf{W}f,\mathbf{W}g)=\mathrm{CosSim}(f,g)$。&lt;/p&gt;
&lt;p&gt;实践中常见导致“不相等”的原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用双正交小波（整体不再正交）；&lt;/li&gt;
&lt;li&gt;只取部分子带（例如只拿 LL），这是投影/降维，不可能保内积；&lt;/li&gt;
&lt;li&gt;边界延拓导致严格正交性被破坏；&lt;/li&gt;
&lt;li&gt;分子带分别算相似度再加权求和：那已经是另一个度量。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;10. 在 PyTorch 里怎么把 DWT 用起来&lt;/h2&gt;
&lt;p&gt;默认输入是图像特征图 $x\in\mathbb{R}^{B\times C\times H\times W}$。&lt;/p&gt;
&lt;h3&gt;10.1 一层 2D DWT 的张量形状&lt;/h3&gt;
&lt;p&gt;最常见实现会输出四个子带，每个子带空间尺寸减半：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$LL, LH, HL, HH \in \mathbb{R}^{B\times C\times \frac{H}{2}\times \frac{W}{2}}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有两种常见组织方式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;返回四个张量&lt;/strong&gt;（更清晰）：&lt;code&gt;return ll, lh, hl, hh&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;在通道维拼接&lt;/strong&gt;（更方便接卷积）：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;$$
\mathrm{cat}(LL,LH,HL,HH)\in\mathbb{R}^{B\times (4C)\times \frac{H}{2}\times \frac{W}{2}}.
$$&lt;/p&gt;
&lt;p&gt;对应的逆变换（iDWT）把这些子带重构回 $\mathbb{R}^{B\times C\times H\times W}$。&lt;/p&gt;
&lt;h3&gt;10.2 一个可跑的实现思路：分离滤波 + stride=2&lt;/h3&gt;
&lt;p&gt;工程上可以把 2D DWT 看成“对行/列分别做 1D 分解”。对于 Haar 小波：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;低通 $h=[\tfrac{1}{\sqrt{2}},\tfrac{1}{\sqrt{2}}]$&lt;/li&gt;
&lt;li&gt;高通 $g=[\tfrac{1}{\sqrt{2}},-\tfrac{1}{\sqrt{2}}]$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一次 2D 分解等价于：先在宽度方向做低/高通并下采样，再在高度方向再做一次。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
import torch.nn.functional as F

def dwt2_haar(x: torch.Tensor):
	# x: [B, C, H, W], H/W 必须是偶数（或者自己做 padding）
	B, C, H, W = x.shape
	assert H % 2 == 0 and W % 2 == 0

	# 先按宽度做 1D Haar
	x0 = x[..., 0::2]  # [B,C,H,W/2]
	x1 = x[..., 1::2]  # [B,C,H,W/2]
	lo = (x0 + x1) / (2 ** 0.5)
	hi = (x0 - x1) / (2 ** 0.5)

	# 再按高度做 1D Haar
	lo0 = lo[:, :, 0::2, :]
	lo1 = lo[:, :, 1::2, :]
	hi0 = hi[:, :, 0::2, :]
	hi1 = hi[:, :, 1::2, :]

	ll = (lo0 + lo1) / (2 ** 0.5)
	lh = (lo0 - lo1) / (2 ** 0.5)
	hl = (hi0 + hi1) / (2 ** 0.5)
	hh = (hi0 - hi1) / (2 ** 0.5)
	return ll, lh, hl, hh

def pack_subbands(ll, lh, hl, hh):
	# -&amp;gt; [B, 4C, H/2, W/2]
	return torch.cat([ll, lh, hl, hh], dim=1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段 Haar 的实现优点是：不用显式卷积核，速度快、形状直观；缺点是：只覆盖 Haar，小波家族换了就要改。&lt;/p&gt;
&lt;h3&gt;10.3 边界处理（padding）是“工程坑点”&lt;/h3&gt;
&lt;p&gt;理论里我们常假设无限长信号或周期延拓；工程里必须选一种边界方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;periodic&lt;/code&gt;：适合周期信号，但图像边缘可能引入接缝；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;symmetric/reflect&lt;/code&gt;：视觉任务里更常见；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;zero-pad&lt;/code&gt;：简单但会引入边缘伪影。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意：边界处理会影响“整体是否正交”，进而影响在第 9 节讨论的“相似度是否严格不变”。&lt;/p&gt;
&lt;h3&gt;10.4 DWT 替代下采样时，后续网络怎么接&lt;/h3&gt;
&lt;p&gt;常见两种接法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;保留所有子带&lt;/strong&gt;：把 &lt;code&gt;[B,4C,H/2,W/2]&lt;/code&gt; 送入后续卷积，让网络自己学会用哪些频带。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;只保留 LL&lt;/strong&gt;：等价于一种“更结构化的低通下采样”，但明确丢掉了高频细节（更像传统金字塔）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;经验上：如果是做分割/超分/生成，保留高频子带更有意义；如果做分类且数据噪声大，先只用 LL 有时更稳。&lt;/p&gt;
&lt;h3&gt;10.5 小波域损失/蒸馏：怎么写得可控&lt;/h3&gt;
&lt;p&gt;假设对教师/学生特征分别做 DWT 得到子带 $S_b, T_b$，一个可控的对齐损失是&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}=\sum_{b\in{LL,LH,HL,HH}} \alpha_b,|S_b-T_b|_1.
$$&lt;/p&gt;
&lt;p&gt;工程上建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从 $\alpha_{LL}$ 大、$\alpha_{HH}$ 小开始（先对齐结构，再对齐纹理）；&lt;/li&gt;
&lt;li&gt;或者按任务反过来（例如去噪/细节增强）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;10.6 常见检查清单&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;形状：&lt;code&gt;H,W&lt;/code&gt; 是否为偶数；多层 DWT 时是否一直满足。&lt;/li&gt;
&lt;li&gt;归一化：不同实现对滤波器系数/能量归一可能不同，导致系数幅值尺度变化。&lt;/li&gt;
&lt;li&gt;子带约定：&lt;code&gt;LH/HL&lt;/code&gt; 的方向含义在不同库/实现里可能互换，做可视化检查最靠谱。&lt;/li&gt;
&lt;li&gt;反传：若把 DWT 当网络层，确保实现是可微的（上面的 Haar 版本天然可微）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;11. 小结：一句话抓住小波的价值&lt;/h2&gt;
&lt;p&gt;小波不是“另一个频域”，而是一种&lt;strong&gt;可局部化、可多尺度、可方向分解&lt;/strong&gt;的表示方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数学上：它通过缩放/平移的一族函数对信号做内积投影；&lt;/li&gt;
&lt;li&gt;原理上：尺度随频率自适应，天然适配非平稳结构；&lt;/li&gt;
&lt;li&gt;性质上：零均值与消失矩带来稀疏性与“像导数一样”的敏感性；&lt;/li&gt;
&lt;li&gt;在特征提取中：它把“结构（低频）/细节（高频）/方向（子带）/尺度（层级）”变成可操作的分量。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>流模型 Appendix</title><link>https://adalovelemon.github.io/posts/content/coursenotes/generativeai/flowmodels/appendix/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/generativeai/flowmodels/appendix/</guid><pubDate>Tue, 14 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;附录&lt;/p&gt;
&lt;h2&gt;A. 一些定理&lt;/h2&gt;
&lt;h3&gt;A.1 Liouville 方程&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;连续性方程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;连续性方程是概率质量守恒的微分形式，描述了概率密度函数随时间的变化。设 $p_t(x)$ 是在时间 $t$ 时刻位置 $x$ 处的概率密度，$u_t(x)$ 是位置 $x$ 处的速度场，则连续性方程为
$$
\boxed{
\frac{\partial p_t(x)}{\partial t} + \nabla \cdot J_t(x) = 0
}
$$
其中 $J_t(x) = u_t(x) p_t(x)$ 是概率流函数。连续性方程也就是 Liouville 方程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推导&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设 $V$ 是空间中的一个体积区域，$\partial V$ 是其边界。$P_V(t) = \int_V p_t(x) dx$ 是该体积区域内的概率质量。根据概率质量守恒 (流出的是负的流入的)，有
$$
\frac{d}{dt} P_V(t) = -\int_{\partial V} J_t(x)^\top n , dS
$$
其中 $n$ 是边界的外侧法向量，代入
$$
\frac{d}{dt} P_V(t) = \frac{d}{dt} \int_V p_t(x) dx
$$
和散度定理
$$
\int_{\partial V} J_t(x)^\top n , dS = \int_V \nabla \cdot J_t(x) , dx
$$
得到
$$
\frac{d}{dt} \int_V p_t(x) dx = -\int_V \nabla \cdot J_t(x) , dx
$$
交换微分和积分 (假设 $p_t(x)$ 足够光滑)，有
$$
\int_V \frac{\partial p_t(x)}{\partial t} dx = -\int_V \nabla \cdot J_t(x) , dx
$$
由于 $V$ 是任意的，得到 Liouville 方程
$$
\frac{\partial p_t(x)}{\partial t} + \nabla \cdot (u_t(x) p_t(x)) = 0
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;性质&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;偏微分方程解的唯一性：当 $u_t(x)$ 和初值条件 $p_0(x)$ 是确定的时候，方程的解 $p_t(x)$ 是唯一的。&lt;/li&gt;
&lt;li&gt;$p_t(x)$ 确定，$u_t(x)$ 不唯一：给定一族中间分布 ${p_t(x)}_{t\in[0,1]}$，可以有无数个不同的速度场 $u_t(x)$ 满足 Liouville 方程。因为方程只给出了速度场的散度约束，而没有给出完整的速度场信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;A.2 边缘性定理&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定理内容&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;边缘性定理 (Marginalization Theorem) 的数学形式为
$$
\boxed{
u^{target}(x_t, t) = \int u^{target}(x_t, t | z) p(z | x_t) dz = \mathbb{E}_{z \sim p(z | x_t)}\left[u^{target}(x_t, t | z)\right]
}
$$
它表明边缘速度场 $u^{target}(x_t, t)$ 可以通过条件速度场 $u^{target}(x_t, t | z)$ 的加权平均来表示。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推导&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(i) 边缘分布与 Liouville 方程&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;设 $z \sim p_{data}(z)$ 是一个真实样本，根据
$$
p_t(x_t) = \int p_t(x_t | z)p_{data}(z)dz
$$
对时间 $t$ 求偏导 (这里假设 $p_t(x_t | z)$ 连续可导)，得到
$$
\frac{\partial p_t(x_t)}{\partial t} = \int \frac{\partial p_t(x_t | z)}{\partial t} p_{data}(z) dz
$$&lt;/p&gt;
&lt;p&gt;将条件分布的 Liouville 方程
$$
\frac{\partial p_t(x_t | z)}{\partial t} + \nabla_x \cdot \left[p_t(x_t | z) u^{target}(x_t, t | z)\right] = 0
$$
和边缘分布的 Liouville 方程
$$
\frac{\partial p_t(x_t)}{\partial t} + \nabla_x \cdot \left[p_t(x_t) u^{target}(x_t, t)\right] = 0
$$
代入到上式中，得到
$$
-\nabla_x \cdot \left[p_t(x_t) u^{target}(x_t, t)\right] = \int -\nabla_x \cdot \left[p_t(x_t | z) u^{target}(x_t, t | z)\right] p_{data}(z) dz
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(ii) 交换&lt;/em&gt; $\int_z$ &lt;em&gt;与&lt;/em&gt; $\nabla_x\cdot$&lt;/p&gt;
&lt;p&gt;由于是对 $z$ 求积分而对 $x$ 取散度，且假设 $p_t(x_t|z)$ 与 $u^{target}(x_t,t|z)$ 对 $x_t$ 连续可导，根据 Leibniz 积分微分交换定理，可以交换积分与散度算子
$$
\nabla_x \cdot \left[p_t(x_t) u^{target}(x_t, t)\right] = \nabla_x \cdot \int p_t(x_t | z) u^{target}(x_t, t | z) p_{data}(z) dz
$$
现在，得到了散度相同的等式。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(iii) 去掉&lt;/em&gt; $\nabla_x \cdot$&lt;/p&gt;
&lt;p&gt;令
$$
A(x) = p_t(x) u^{target}(x, t)
$$
$$
B(x) = \int p_t(x | z) u^{target}(x, t | z) p_{data}(z) dz
$$&lt;/p&gt;
&lt;p&gt;现在形式转换为
$$
\nabla_x \cdot A(x) = \nabla_x \cdot B(x)
$$&lt;/p&gt;
&lt;p&gt;根据 Helmholtz 分解定理，任何足够光滑且衰减足够快的向量场都可以唯一地分解为无旋场（梯度场）和无散场之和。因为 $\nabla \cdot A(x) = \nabla \cdot B(x)$，所以它们的差 $A(x) - B(x)$ 是一个无散场，即
$$
A(x) = B(x) + \nabla \times F(x) + C
$$
其中 $\nabla \times F(x)$ 是无散场，$C$ 是常数场。&lt;/p&gt;
&lt;p&gt;假设&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;边界条件：当 $|x| \to \infty$ 时，$p_t(x) \to 0$，且 $u^{target}(x, t)$ 有界。&lt;/li&gt;
&lt;li&gt;概率流的物理意义：$p_t(x)u^{target}(x, t)$ 表示概率流密度，在无穷远处应为零。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在这些假设下，由于 $p_t(x) \to 0$，我们有 $A(x) \to 0$，$B(x) \to 0$，当 $|x| \to \infty$。因此常数项 $C = 0$。对于无散项 $\nabla \times F(x)$，如果它在无穷远处不为零，会导致非零的概率流穿过无穷远边界，这违反概率守恒。因此 $\nabla \times F(x) = 0$。&lt;/p&gt;
&lt;p&gt;于是我们得到
$$
A(x) = B(x)
$$
即
$$
p_t(x_t) u^{target}(x_t, t) = \int p_t(x_t | z) u^{target}(x_t, t | z) p_{data}(z) dz
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(iv) 贝叶斯定理&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;由贝叶斯定理，得到
$$
u^{target}(x_t, t) = \int u^{target}(x_t, t | z) p(z | x_t) dz = \mathbb{E}_{z \sim p(z | x_t)}\left[u^{target}(x_t, t | z)\right]
$$&lt;/p&gt;
&lt;h3&gt;A.3 概率流等价定理&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;定理内容&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设 ${p_t(x)}&lt;em&gt;{t\in[0, 1]}$ 是一族连续可微的概率分布，且 $p_0(x) = p&lt;/em&gt;{prior}(x)$，$p_1(x) = p_{data}(x)$。假设存在一个光滑的速度场 $u_t(x)$ 满足 Liouville 方程
$$
\frac{\partial p_t(x)}{\partial t} + \nabla_x \cdot \left[p_t(x) u_t(x)\right] = 0
$$
其中，速度场对应有一个常微分方程 (ODE)
$$
\boxed{
\frac{dX_t}{dt} = u_t(X_t), \quad X_0 \sim p_{prior}(x_0), \quad t \in [0, 1]
}
$$
则存在一个随机微分方程 (SDE)
$$
\boxed{
dX_t = \left[u_t(X_t) + \frac{\sigma_t^2}{2}\nabla_{X_t} \log p_t(X_t)\right]dt + \sigma_t dW_t, \quad X_0 \sim p_{prior}(x_0), \quad t \in [0, 1]
}
$$
其边缘分布 ${p_t(x)}_{t\in[0, 1]}$ 与 Liouville 方程给出的一致。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(i) 符号表示&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;设 $x_t$ 满足服从概率路径 ${p_t(x_t)}&lt;em&gt;{t\in[0, 1]}$，假设同时有 ODE
$$
\frac{dx_t}{dt} = u_t(x_t), \quad x_0 \sim p&lt;/em&gt;{prior}(x_0), \quad t \in [0, 1]
$$
和 SDE
$$
dx_t = v_t(x_t)dt + \sigma_t dW_t, \quad x_0 \sim p_{prior}(x_0), \quad t \in [0, 1]
$$
对应这条概率路径。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(ii) Liouville 方程与 Fokker-Planck 方程联立&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;根据 Liouville 方程 和 Fokker-Planck 方程，分别有
$$
\frac{\partial p_t(x)}{\partial t} = -\nabla_x \cdot \left[p_t(x) u_t(x)\right]
$$
$$
\frac{\partial p_t(x)}{\partial t} = -\nabla_x \cdot \left[p_t(x) v_t(x)\right] + \frac{1}{2}\nabla_x^2 : (\sigma_t\sigma_t^\top p_t(x))
$$
其中 $\nabla_x^2 :\ \  = \sum_{i=1}^{d}\sum_{j=1}^{d} \frac{\partial^2}{\partial x_i \partial x_j}$ 表示对矩阵的双重散度操作, $D_t = \sigma_t\sigma_t^\top \in \mathbb{R}^{d \times d}$ 是扩散张量。联立方程，得到
$$
\nabla_x \cdot \left[p_t(x) u_t(x)\right] = \nabla_x \cdot \left[p_t(x) v_t(x)\right] - \frac{1}{2}\nabla_x^2 : (D_t p_t(x))
$$
整理得到
$$
\nabla_x \cdot \left[p_t(x) (u_t(x) - v_t(x))\right] = - \frac{1}{2}\nabla_x^2 : (D_t p_t(x))
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(iii) 计算&lt;/em&gt; $\nabla_x^2 : (D_t p_t(x))$&lt;/p&gt;
&lt;p&gt;展开
$$
\nabla_x^2 : (D_t p_t(x))
$$
得到
$$
\nabla_x^2 : (D_t p_t(x)) = \nabla_x \cdot \left[\nabla_x \cdot \left(D_t p_t(x)\right)\right] = \nabla_x \cdot \left[\left(\nabla_x \cdot D_t\right) p_t(x) + D_t\nabla_x p_t(x)\right]
$$
其中，矩阵的一阶散度
$$
\nabla_x\cdot (D_t p_t(x)) = \begin{bmatrix} \sum_{i=1}^{d}\frac{\partial}{\partial x_i}D_{i1}, &amp;amp; \sum_{i=1}^{d}\frac{\partial}{\partial x_i}D_{i2}, &amp;amp; \cdots, &amp;amp; \sum_{i=1}^{d}\frac{\partial}{\partial x_i}D_{id}\end{bmatrix}^\top
$$
是一个 $d$ 维列向量。推导中用到了矩阵数乘形式的散度展开公式 $\nabla \cdot \left(Ap\right) = (\nabla \cdot A) p + A \nabla p$。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(iv) 代入并取特殊情况&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;将上式代入，得到
$$
\nabla_x \cdot \left[p_t(x) (u_t(x) - v_t(x))\right] = - \frac{1}{2}\nabla_x \cdot \left[\left(\nabla_x \cdot D_t\right) p_t(x) + D_t\nabla_x p_t(x)\right]
$$
很显然，当我们直接取
$$
p_t(x) (u_t(x) - v_t(x)) = - \frac{1}{2}\left[\left(\nabla_x \cdot D_t\right) p_t(x) + D_t\nabla_x p_t(x)\right]
$$
时，上面的散度等式成立。由此化简，得到
$$
\boxed{
v_t(x) = u_t(x) + \frac{1}{2}\nabla_x \cdot D_t + \frac{1}{2}D_t\nabla_x \log p_t(x)
}
$$
如果 $D_t$ 是关于 $x$ 的变量，则 $\nabla_x \cdot D_t$ 是一个 $d$ 维列向量。否则，$\nabla_x \cdot D_t = 0$，也即
$$
\boxed{
v_t(x) = u_t(x) + \frac{1}{2}D_t\nabla_x \log p_t(x)
}
$$
这是更常见的一种形式。特别地，当 $D_t = \sigma_t^2 I_d$ 时，得到
$$
\boxed{
v_t(x) = u_t(x) + \frac{\sigma_t^2}{2}\nabla_x \log p_t(x)
}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;另一种表达形式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;概率流等价定理还有另一种公式形式。设有 SDE
$$
dX_t = u_t(X_t)dt + \sigma_t dW_t, \quad X_0 \sim p_{prior}(x_0), \quad t \in [0, 1]
$$
其等价的 ODE 为
$$
\boxed{
\frac{dX_t}{dt} = u_t(X_t) - \frac{\sigma_t^2}{2}\nabla_{X_t} \log p_t(X_t), \quad X_0 \sim p_{prior}(x_0), \quad t \in [0, 1]
}
$$&lt;/p&gt;
&lt;h2&gt;B. 伊藤积分&lt;/h2&gt;
&lt;h3&gt;B.1 定义&lt;/h3&gt;
&lt;p&gt;伊藤积分是对随机过程进行的积分，定义为
$$
\int_0^t X_s dW_s = \lim_{\Vert \Delta \Vert \to 0} \sum_{i=0}^{n-1} X_{t_i} (W_{t_{i+1}} - W_{t_i})
$$
其中 $X_s$ 是一个适应过程 (即 $X_s$ 的值仅仅依赖于 $W_s$ 在时间 $s$ 之前的值)，$W_s$ 则是标准的维纳过程。&lt;/p&gt;
&lt;p&gt;伊藤积分不是普通的黎曼积分或勒贝格积分，因为布朗运动 $W_t$ 的路径几乎处处不可微，且具有无限变差，因此需要特殊的构造方法。&lt;/p&gt;
&lt;h3&gt;B.2 性质&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;零均值:&lt;/strong&gt; 因为维纳过程的增量是独立的，且均值为零，因此伊藤积分的期望值为零。
$$
\mathbb{E}\left[\int_0^t X_s dW_s\right] = 0
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;伊藤等距:&lt;/strong&gt; 可用于计算方差。
$$
\mathbb{E}\left[(\int_0^t X_s dW_s)^2\right] = \mathbb{E}\left[\int_0^t X_s^2 ds\right]
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;B.3 伊藤公式&lt;/h3&gt;
&lt;p&gt;设 $X_t$ 是一个伊藤过程，即满足以下 SDE 方程
$$
dX_t = \mu_t dt + \sigma_t dW_t
$$
其中 $\mu_t$ 是漂移项，$\sigma_t$ 是扩散系数， $W_t$ 是标准维纳过程。&lt;/p&gt;
&lt;p&gt;若 $f(t, X_t)$ 是一个关于 $t$ 和 $X_t$ 的二次可微函数，则
$$
df(X_t, t) = \frac{\partial f}{\partial t}dt + \frac{\partial f}{\partial X_t}dX_t + \underbrace{\frac{1}{2} \frac{\partial^2 f}{\partial x^2} d(X_t)^2}_{\text{二阶项}}
$$&lt;/p&gt;
&lt;p&gt;根据维纳过程的性质，有
$$
d(X_t)^2 = (\mu_t dt + \sigma_t dW_t)^2 = \sigma_t^2 dt
$$
这是因为&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$dW_t^2 = dt$，即维纳过程的增量平方等于时间增量。&lt;/li&gt;
&lt;li&gt;$dW_t dt = 0$，即维纳过程的增量与时间增量的乘积为零。&lt;/li&gt;
&lt;li&gt;$dW_t^2 = 0$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，最终的伊藤公式为
$$
\boxed{df(X_t, t) = \left(\frac{\partial f}{\partial t} + \mu_t \frac{\partial f}{\partial X_t} + \frac{1}{2} \sigma_t^2 \frac{\partial^2 f}{\partial X_t^2}\right) dt + \sigma_t \frac{\partial f}{\partial X_t} dW_t}
$$&lt;/p&gt;
&lt;h3&gt;B.4 伊藤公式的例子&lt;/h3&gt;
&lt;h4&gt;B.4.1 计算 $I_t = \int_0^t e^{-\theta(t - s)} dW_s$ 的微分&lt;/h4&gt;
&lt;p&gt;设 $I_t = \int_0^t e^{-\theta(t - s)} dW_s$，我们想要计算 $dI_t$。&lt;/p&gt;
&lt;p&gt;首先，我们可以将 $I_t$ 重写为
$$
I_t = e^{-\theta t} \int_0^t e^{\theta s} dW_s
$$&lt;/p&gt;
&lt;p&gt;设 $f(t, Y_t) = e^{-\theta t} Y_t$，其中 $Y_t = \int_0^t e^{\theta s} dW_s$。&lt;/p&gt;
&lt;p&gt;根据伊藤积分的性质，$Y_t$ 满足
$$
dY_t = e^{\theta t} dW_t
$$&lt;/p&gt;
&lt;p&gt;现在应用伊藤公式到 $f(t, Y_t) = e^{-\theta t} Y_t$&lt;/p&gt;
&lt;p&gt;$$
\frac{\partial f}{\partial t} = -\theta e^{-\theta t} Y_t, \quad \frac{\partial f}{\partial Y_t} = e^{-\theta t}, \quad \frac{\partial^2 f}{\partial Y_t^2} = 0
$$&lt;/p&gt;
&lt;p&gt;由于 $dY_t = e^{\theta t} dW_t$，我们有 $\mu_t = 0$，$\sigma_t = e^{\theta t}$。&lt;/p&gt;
&lt;p&gt;应用伊藤公式
$$
dI_t = df(t, Y_t) = \frac{\partial f}{\partial t} dt + \frac{\partial f}{\partial Y_t} dY_t
$$
$$
= -\theta e^{-\theta t} Y_t dt + e^{-\theta t} \cdot e^{\theta t} dW_t
$$
$$
= -\theta e^{-\theta t} \int_0^t e^{\theta s} dW_s , dt + dW_t
$$&lt;/p&gt;
&lt;p&gt;因此
$$
\boxed{dI_t = -\theta I_t dt + dW_t}
$$&lt;/p&gt;
&lt;p&gt;这表明 $I_t$ 本身也满足一个简单的 SDE，这是 OU 过程积分项的一个重要性质。&lt;/p&gt;
&lt;h2&gt;C. 方程求解过程&lt;/h2&gt;
&lt;h3&gt;C.1 条件速度场的解析形式&lt;/h3&gt;
&lt;p&gt;我们直接从已知方程出发
$$
\frac{(x_t-\mu_t(z))^\top \dot{\mu_t}(z)}{\sigma_t^2} + \frac{|x_t-\mu_t(z)|^2 \dot{\sigma_t}}{\sigma_t^3} - \frac{\dot{\sigma_t} d}{\sigma_t} = \frac{(x_t-\mu_t(z))^\top u^{target}(x_t, t|z)}{\sigma_t^2} - \nabla_{x_t} \cdot u^{target}(x_t, t|z)
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;齐次方程分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;考虑对应的齐次方程
$$
\frac{(x_t-\mu_t(z))^\top}{\sigma_t^2}u_h - \nabla_{x_t} \cdot u_h = 0
$$&lt;/p&gt;
&lt;p&gt;令 $y = x_t - \mu_t(z)$，方程变为
$$
\frac{y^\top}{\sigma_t^2}u_h(y) - \nabla_y \cdot u_h(y) = 0
$$&lt;/p&gt;
&lt;p&gt;在加权 Sobolev 空间 $L^2(\mathbb{R}^d, p_t(y)dy)$ 中分析，其中 $p_t(y) = \frac{1}{(2\pi\sigma_t^2)^{d/2}} \exp\left(-\frac{|y|^2}{2\sigma_t^2}\right)$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;正交分解论证&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;将 $u_h(y)$ 分解为径向部分和切向部分
$$
u_h(y) = u_h^{\parallel}(y) + u_h^{\perp}(y)
$$
其中 $u_h^{\parallel}(y)$ 与 $y$ 平行，$u_h^{\perp}(y)$ 与 $y$ 垂直。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;径向部分分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设 $u_h^{\parallel}(y) = f(|y|) \frac{y}{|y|}$，代入齐次方程&lt;/p&gt;
&lt;p&gt;第一项
$$
\frac{y^\top}{\sigma_t^2} u_h^{\parallel} = \frac{|y| f(|y|)}{\sigma_t^2}
$$&lt;/p&gt;
&lt;p&gt;散度项
$$
\nabla_y \cdot u_h^{\parallel} = \nabla_y \cdot \left(f(|y|) \frac{y}{|y|}\right) = f&apos;(|y|) + \frac{d-1}{|y|} f(|y|)
$$&lt;/p&gt;
&lt;p&gt;齐次方程变为
$$
\frac{|y| f(|y|)}{\sigma_t^2} - \left[f&apos;(|y|) + \frac{d-1}{|y|} f(|y|)\right] = 0
$$&lt;/p&gt;
&lt;p&gt;整理得
$$
f&apos;(|y|) + \left[\frac{d-1}{|y|} - \frac{|y|}{\sigma_t^2}\right] f(|y|) = 0
$$&lt;/p&gt;
&lt;p&gt;解此一阶线性ODE，得到
$$
f(|y|) = C |y|^{1-d} \exp\left(\frac{|y|^2}{2\sigma_t^2}\right)
$$&lt;/p&gt;
&lt;p&gt;但在 $L^2(\mathbb{R}^d, p_t(y)dy)$ 空间中，该解不满足可积性条件
$$
\int_{\mathbb{R}^d} |u_h^{\parallel}(y)|^2 p_t(y) dy \propto \int_0^\infty |y|^{2-2d} \exp\left(\frac{|y|^2}{\sigma_t^2}\right) \exp\left(-\frac{|y|^2}{2\sigma_t^2}\right) |y|^{d-1} d|y|
$$
$$ = \int_0^\infty |y|^{1-d} \exp\left(\frac{|y|^2}{2\sigma_t^2}\right) d|y| = \infty
$$&lt;/p&gt;
&lt;p&gt;因此 $f(|y|) \equiv 0$，即 $u_h^{\parallel}(y) \equiv 0$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;切向部分分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;现在考虑 $u_h^{\perp}(y)$，满足 $y^\top u_h^{\perp}(y) = 0$。&lt;/p&gt;
&lt;p&gt;齐次方程简化为
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;\nabla_y \cdot u_h^{\perp}(y) = 0
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;考虑任意光滑紧支撑标量函数 $\phi(y)$，由散度定理
$$
\int_{\mathbb{R}^d} \phi(y) \nabla_y \cdot u_h^{\perp}(y) dy = - \int_{\mathbb{R}^d} \nabla_y \phi(y) \cdot u_h^{\perp}(y) dy = 0
$$&lt;/p&gt;
&lt;p&gt;这意味着 $u_h^{\perp}$ 在分布意义下无散度。但在 $L^2(\mathbb{R}^d, p_t(y)dy)$ 空间中，满足 $y^\top u_h^{\perp} = 0$ 且 $\nabla_y \cdot u_h^{\perp} = 0$ 的非零向量场必须具有特定的调和形式。通过傅里叶分析或考虑加权空间中的Hodge分解，可以证明在合理的边界条件下（在无穷远处衰减足够快），唯一的解是 $u_h^{\perp}(y) \equiv 0$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;唯一性结论&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因此，在加权 $L^2$ 空间中，齐次方程只有零解。根据线性微分方程理论，原非齐次方程如果有解，则解是唯一的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;构造特解&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;既然解唯一，我们通过待定系数法构造特解。假设解具有形式
$$
u^{target}(x_t, t|z) = A(t)(x_t - \mu_t(z)) + b(t)
$$&lt;/p&gt;
&lt;p&gt;计算散度项
$$
\nabla_{x_t} \cdot u^{target} = A(t)d
$$&lt;/p&gt;
&lt;p&gt;代入方程左边
$$
\frac{A(t)|x_t-\mu_t(z)|^2}{\sigma_t^2} + \frac{(x_t-\mu_t(z))^\top b(t)}{\sigma_t^2} - A(t)d
$$&lt;/p&gt;
&lt;p&gt;与右边比较系数&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$|x_t-\mu_t(z)|^2$ 系数：$\frac{A(t)}{\sigma_t^2} = \frac{\dot{\sigma_t}}{\sigma_t^3} \Rightarrow A(t) = \frac{\dot{\sigma_t}}{\sigma_t}$&lt;/li&gt;
&lt;li&gt;$(x_t-\mu_t(z))$ 系数：$\frac{b(t)}{\sigma_t^2} = \frac{\dot{\mu_t}(z)}{\sigma_t^2} \Rightarrow b(t) = \dot{\mu_t}(z)$&lt;/li&gt;
&lt;li&gt;常数项验证：$-A(t)d = -\frac{\dot{\sigma_t} d}{\sigma_t}$ 成立&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此得到唯一解
$$
u^{target}(x_t, t|z) = \frac{\dot{\sigma_t}}{\sigma_t}(x_t - \mu_t(z)) + \dot{\mu_t}(z)
$$&lt;/p&gt;
&lt;h3&gt;C.2 Langevin 动力学的稳态分布&lt;/h3&gt;
&lt;p&gt;直接从稳态 Fokker-Planck 方程开始
$$
\nabla_x \cdot \left[p_{ss}(x) \nabla_x U(x)\right] + kT \Delta_x p_{ss}(x) = 0
$$
我们可以将其重写为
$$
\nabla_x \cdot \left[p_{ss}(x) \nabla_x U(x) + kT \nabla_x p_{ss}(x)\right] = 0
$$
这表明括号内的概率流向量场 $J(x)$ 是无散度的。在热力学平衡状态下，我们有更强的条件，即细致平衡 (detailed balance)，这意味着净概率流在每一点都为零
$$
J(x) = p_{ss}(x) \nabla_x U(x) + kT \nabla_x p_{ss}(x) = 0
$$
整理上式，得到
$$
kT \nabla_x p_{ss}(x) = -p_{ss}(x) \nabla_x U(x)
$$
$$
\frac{\nabla_x p_{ss}(x)}{p_{ss}(x)} = -\frac{1}{kT} \nabla_x U(x)
$$
利用 $\nabla_x \log f(x) = \frac{\nabla_x f(x)}{f(x)}$，我们有
$$
\nabla_x \log p_{ss}(x) = -\frac{1}{kT} \nabla_x U(x)
$$
对 $x$ 积分，得到
$$
\log p_{ss}(x) = -\frac{U(x)}{kT} + C&apos;
$$
其中 $C&apos;$ 是积分常数。两边取指数，得到
$$
p_{ss}(x) = e^{C&apos;} e^{-\frac{U(x)}{kT}}
$$
这是一个未归一化的概率分布。为了使其成为一个有效的概率密度函数，我们需要对其进行归一化，即 $\int p_{ss}(x) dx = 1$。
$$
\int e^{C&apos;} e^{-\frac{U(x)}{kT}} dx = 1 \implies e^{C&apos;} = \frac{1}{\int e^{-\frac{U(x)}{kT}} dx}
$$
令配分函数 $Z = \int e^{-\frac{U(x)}{kT}} dx$，则 $e^{C&apos;} = \frac{1}{Z}$。&lt;/p&gt;
&lt;p&gt;因此，Langevin 动力学的稳态分布是玻尔兹曼分布
$$
\boxed{
p_{ss}(x) = \frac{1}{Z} e^{-\frac{U(x)}{kT}}
}
$$&lt;/p&gt;
</content:encoded></item><item><title>流模型 Chapter4——基于分数的生成模型 (Score-based Generative Model)</title><link>https://adalovelemon.github.io/posts/content/coursenotes/generativeai/flowmodels/chapter4/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/generativeai/flowmodels/chapter4/</guid><pubDate>Sun, 12 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;朗之万去噪分数匹配 (SMLD) 和连续时间 Score-based 模型 (VP-SDE / VE-SDE) 是两种基于分数匹配的生成模型方法。它们通过学习数据分布的分数函数（即对数概率密度的梯度）来生成新样本。&lt;/p&gt;
&lt;h2&gt;4.1 朗之万去噪分数匹配 (SMLD) —— 静态 Score Matching&lt;/h2&gt;
&lt;h3&gt;4.1.1 模型概述&lt;/h3&gt;
&lt;p&gt;朗之万去噪分数匹配 (Denoising Score Matching with Langevin Dynamics, SMLD) 由 &lt;strong&gt;Y. Song and S. Ermon (2019)&lt;/strong&gt; 提出，是一种基于分数匹配的生成模型。其核心思想是通过在不同噪声水平下学习数据分布的分数函数，结合 Langevin 动力学 (Langevin Dynamics) 进行采样，从而生成高质量样本。&lt;/p&gt;
&lt;h3&gt;4.1.2 启发源——粒子运动的 Langevin 动力学&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Langevin 方程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Langevin 方程是描述一个粒子在势能场中受随机热扰动的动力学方程。设粒子位置为 $x_t \in \mathbb{R}^d$，受到来自势能 $U(x_t)$ 的确定性力 $-\nabla_{x_t} U(x_t)$ 和来自热随机噪声的扰动 $\sqrt{2kT}dw_t$ 的影响，其 Langevin 动力学方程为
$$
dx_t = -\nabla_{x_t} U(x_t) dt + \sqrt{2kT}dw_t
$$
其中 $dw_t$ 是标准布朗运动增量，$k$ 是 Boltzmann 常数，$T$ 是系统温度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Langevin 动力学的稳态分布&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Langevin 动力学方程定义了粒子在各个时刻位置的概率分布 $p_t(x)$，其演化由 Fokker-Planck 方程描述
$$
\frac{\partial p_t(x)}{\partial t} = \nabla_x \cdot \left[p_t(x) \nabla_x U(x)\right] + kT \Delta_x p_t(x)
$$
其中 $\Delta_x p_t(x) = \nabla_x \cdot \nabla_x p_t(x)$ 用到了拉普拉斯算子表示。假设该动力学过程存在稳态分布 $p_{ss}(x) = \lim_{t \to \infty}p_t(x)$，在 $t \to \infty$ 时可以达到，则必定满足
$$
\frac{\partial p_t(x)}{\partial t} = 0 \quad \Rightarrow \quad \nabla_x \cdot \left[p_{ss}(x) \nabla_x U(x)\right] + kT \Delta_x p_{ss}(x) = 0
$$
此偏微分方程的解是著名的 &lt;strong&gt;Boltzmann-Gibbs 分布&lt;/strong&gt;
$$
\boxed{
p_{ss}(x) = \frac{1}{Z} \exp(-\frac{1}{kT}U(x))
}
$$
其中 $Z = \int \exp(-\frac{1}{kT}U(x)) dx$ 是归一化常数。这个解的得出可以通过代入验证。具体过程详见&lt;a href=&quot;../appendix/#c2-langevin-%E5%8A%A8%E5%8A%9B%E5%AD%A6%E7%9A%84%E7%A8%B3%E6%80%81%E5%88%86%E5%B8%83&quot;&gt;附录 C.2&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;Boltzmann-Gibbs 分布表明，粒子在势能场 $U(x)$ 中的稳态分布与势能成指数关系，低势能区域对应高概率密度。通过调节温度 $T$，可以控制分布的平滑程度。&lt;/p&gt;
&lt;h3&gt;4.1.3 数学形式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;能量函数与概率分布&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在概率能量模型中，数据分布 $p_{data}(x)$ 通常表示为一个基于势能函数 $U_{data}(x)$ 的形式
$$
p_{data}(x) = \frac{1}{Z} \exp(-U_{data}(x))
$$
其中 $Z = \int \exp(-U_{data}(x)) dx$ 是归一化常数。势能函数 $U_{data}(x)$ 衡量数据点 $x$ 的“非典型性”，低能量对应高概率。例如，Boltzmann-Gibbs 分布就是这种形式的一个例子。&lt;/p&gt;
&lt;p&gt;通过对数梯度变换，可以得到数据分布的对数概率密度的梯度为
$$
\nabla_x \log p_{data}(x) = -\nabla_x U_{data}(x)
$$
象征着数据分布的“力场”，指向概率密度增加的方向。我们把这个梯度定义为&lt;strong&gt;分数函数&lt;/strong&gt; (score function)，写作
$$
s_{data}(x) = \nabla_x \log p_{data}(x) = -\nabla_x U_{data}(x)
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分数函数形式的 Langevin 动力学&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;受到概率能量模型的启发，如果可以准确估计出数据分布的分数函数 $s_{data}(x) = \nabla_x \log p_{data}(x)$，就相当于得到了势能函数 $U_{data}(x)$ 对应的保守力。根据 Langevin 动力学方程，最终方程对应的边缘概率分布 $p_t(x_t)$ 是会收敛到数据分布 $p_{data}(x)$ 的。这样，通过 Langevin SDE 采样，即可实现从先验分布 $p_{prior}(x)$ 到数据分布 $p_{data}(x)$ 的转换。&lt;/p&gt;
&lt;p&gt;替换 $-\nabla_x U(x)$ 为 $\nabla_x \log p_{data}(x)$，得到分数形式的 Langevin 动力学
$$
dx_t = \frac{\eta_t}{2} \nabla_{x_t}\log p_{data}(x_t) dt + \sqrt{\eta_t} dw_t, \quad t = 1, \ldots, T
$$
其中 $\eta_t$ 是时间相关的步长参数，控制扩散和梯度更新的权重。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;去噪分数匹配与目标函数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(i) 初始目标函数&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;分数匹配的核心在于如何估计数据分布的分数函数 $s_{data}(x) = \nabla_x \log p_{data}(x)$。假设我们用一个参数化的神经网络模型 $s_\theta(x)$ 来近似分数函数。我们可以通过最小化如下分数匹配目标函数来训练模型
$$
\mathcal{L}&lt;em&gt;{SM}(\theta) = \mathbb{E}&lt;/em&gt;{x \sim p_{data}(x)} \left| s_\theta(x) - \nabla_x \log p_{data}(x) \right|^2
$$
然而，直接计算 $\nabla_x \log p_{data}(x)$ 通常是不可行的，因为 $p_{data}(x)$ 的形式未知且难以估计。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(ii) 去噪分数匹配&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;为了解决这个问题，SMLD 引入了&lt;strong&gt;去噪分数匹配&lt;/strong&gt; (Denoising Score Matching, DSM) 的概念。对原始数据 $z \sim p_{data}(z)$ (替换之前的符号 $x$ 为 $z$ 以示区分) 添加高斯噪声，得到带噪数据
$$
x_\sigma = z + \sigma\epsilon，\quad \epsilon \sim \mathcal{N}(0, I_d)
$$
其中 $\sigma$ 是噪声标准差，控制噪声水平。由此得到带噪数据的条件分布为
$$
q_\sigma(x_\sigma | z) = \mathcal{N}(x_\sigma; z, \sigma^2 I_d) = \frac{1}{\sqrt{2\pi}}\exp\left[-\frac{(x_\sigma - z)^2}{2\sigma^2}\right]
$$
通过积分，得到带噪数据的边缘分布为
$$
q_\sigma(x_\sigma) = \int q_\sigma(x_\sigma | z) p_{data}(z) dz
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(iii) 去噪分数匹配与条件去噪分数匹配的等价性&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;展开边缘含噪数据分布
$$
\begin{aligned}
\nabla_{x_\sigma}\log q_\sigma(x_\sigma) &amp;amp;= \frac{\nabla_{x_\sigma}\int q_\sigma(x_\sigma | z)p_{data}(z)dz}{q_\sigma(x_\sigma)} \
&amp;amp;= \frac{\int \nabla_{x_\sigma}q_\sigma(x_\sigma | z)p_{data}(z)dz}{q_\sigma(x_\sigma)} \
&amp;amp;= \int \nabla_{x_\sigma}\log q_\sigma(x_\sigma | z) p(z | x_\sigma)dz \
&amp;amp;= \mathbb{E}&lt;em&gt;{z \sim p(z | x&lt;/em&gt;\sigma)}\left[\nabla_{x_\sigma}\log q_\sigma(x_\sigma | z)\right]
\end{aligned}
$$
得到 Tweedie-Stein 公式
$$
\nabla_{x_\sigma}\log q_\sigma(x_\sigma) = \mathbb{E}&lt;em&gt;{z \sim p(z | x&lt;/em&gt;\sigma)}\left[\nabla_{x_\sigma}\log q_\sigma(x_\sigma | z)\right]
$$&lt;/p&gt;
&lt;p&gt;设去噪分数匹配的目标函数为
$$
\mathcal{L}&lt;em&gt;{DSM}(\theta; \sigma) = \mathbb{E}&lt;/em&gt;{x_\sigma \sim q_\sigma(x_\sigma)}\left[\frac{1}{2}\left\Vert s_\theta(x_\sigma) - \nabla_{x_\sigma}\log q_\sigma(x_\sigma)\right\Vert^2\right]
$$
展开可以得到
$$
\mathcal{L}&lt;em&gt;{DSM}(\theta; \sigma) = \frac{1}{2}\left(\mathbb{E}&lt;/em&gt;{x_\sigma}\left\Vert s_\theta(x_\sigma)\right\Vert^2 + \mathbb{E}&lt;em&gt;{x&lt;/em&gt;\sigma}\left\Vert \nabla_{x_\sigma}\log q_\sigma(x_\sigma)\right\Vert^2 - 2\mathbb{E}&lt;em&gt;{x&lt;/em&gt;\sigma} \left[s_\theta(x_\sigma)^\top \nabla_{x_\sigma}\log q_\sigma(x_\sigma)\right]\right)
$$&lt;/p&gt;
&lt;p&gt;再定义条件去噪分数匹配的目标函数为
$$
\mathcal{L}&lt;em&gt;{CDSM}(\theta; \sigma) = \mathbb{E}&lt;/em&gt;{z\sim p_{data}(z), x_\sigma \sim q_\sigma(x_\sigma | z)}\left[\frac{1}{2}\left\Vert s_\theta(x_\sigma) - \nabla_{x_\sigma}\log q_\sigma(x_\sigma | z) \right\Vert^2 \right]
$$
展开同样可以得到
$$
\begin{aligned}
\mathcal{L}&lt;em&gt;{CDSM}(\theta; \sigma) &amp;amp;= \mathbb{E}&lt;/em&gt;{x_\sigma\sim q_\sigma(x_\sigma), z \sim p(z | x_\sigma)}\left[\frac{1}{2}\left\Vert s_\theta(x_\sigma) - \nabla_{x_\sigma}\log q_\sigma(x_\sigma | z) \right\Vert^2 \right] \
&amp;amp;= \frac{1}{2}\left(\mathbb{E}&lt;em&gt;{x&lt;/em&gt;\sigma}\left\Vert s_\theta(x_\sigma)\right\Vert^2 + \mathbb{E}&lt;em&gt;{x&lt;/em&gt;\sigma, z}\left\Vert \nabla_{x_\sigma}\log q_\sigma(x_\sigma | z)\right\Vert^2 - 2\mathbb{E}&lt;em&gt;{x&lt;/em&gt;\sigma, z} \left[s_\theta(x_\sigma)^\top \nabla_{x_\sigma}\log q_\sigma(x_\sigma | z)\right]\right)
\end{aligned}
$$
因为
$$
\mathbb{E}&lt;em&gt;{x&lt;/em&gt;\sigma, z} \left[s_\theta(x_\sigma)^\top \nabla_{x_\sigma}\log q_\sigma(x_\sigma | z)\right] = \mathbb{E}&lt;em&gt;{x&lt;/em&gt;\sigma} \left[s_\theta(x_\sigma)^\top \mathbb{E}&lt;em&gt;{z \sim p(z | x&lt;/em&gt;\sigma)}\left[\nabla_{x_\sigma}\log q_\sigma(x_\sigma | z)\right]\right] = \mathbb{E}&lt;em&gt;{x&lt;/em&gt;\sigma}\left[s_\theta(x_\sigma)^\top \nabla_{x_\sigma}\log q_\sigma(x_\sigma)\right]
$$
且
$$
\mathbb{E}&lt;em&gt;{x&lt;/em&gt;\sigma, z}\left\Vert \nabla_{x_\sigma}\log q_\sigma(x_\sigma | z)\right\Vert^2 - \mathbb{E}&lt;em&gt;{x&lt;/em&gt;\sigma}\left\Vert \nabla_{x_\sigma}\log q_\sigma(x_\sigma)\right\Vert^2 = \text{const}
$$
由此可知，去噪分数匹配的目标函数 $\mathcal{L}&lt;em&gt;{DSM}(\theta; \sigma)$ 和条件去噪分数匹配的目标函数 $\mathcal{L}&lt;/em&gt;{CDSM}(\theta; \sigma)$ 在优化上是等价的
$$
\mathcal{L}&lt;em&gt;{DSM}(\theta; \sigma) = \mathcal{L}&lt;/em&gt;{CDSM}(\theta; \sigma) + \text{const}
$$
因此，我们可以选择更易计算的条件去噪分数匹配目标函数 $\mathcal{L}_{CDSM}(\theta; \sigma)$ 进行优化。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(iv) 去噪分数匹配的渐进收敛性&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;当噪声水平 $\sigma \to 0$ 时，带噪数据分布 $q_\sigma(x_\sigma)$ 渐进收敛到真实数据分布 $p_{data}(x)$。此时，去噪分数匹配目标函数 $\mathcal{L}&lt;em&gt;{DSM}(\theta; \sigma)$ 也渐进收敛到原始的分数匹配目标函数 $\mathcal{L}&lt;/em&gt;{SM}(\theta)$。由此带来的是，含噪分布的分数函数 $s_\theta(x_\sigma)$ 也会逐渐逼近真实数据分布的分数函数 $\nabla_{x_\sigma}\log p_{data}(x_\sigma)$
$$
\lim_{\sigma \to 0} \mathcal{L}&lt;em&gt;{DSM}(\theta; \sigma) = \mathcal{L}&lt;/em&gt;{SM}(\theta) + \text{const}
$$
$$
\lim_{\sigma \to 0} s_\theta(x_\sigma) = \nabla_{x}\log p_{data}(x)
$$&lt;/p&gt;
&lt;p&gt;因此用去噪分数匹配训练得到的模型 $s_\theta(x_\sigma)$，在 $\sigma$ 足够小时，可以很好地近似真实数据分布的分数函数。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(v) 最终的目标函数&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在实际应用中，我们通常会在多个噪声水平 ${\sigma_i}&lt;em&gt;{i=1}^L$ 下训练模型，一方面是为了增强模型的鲁棒性和泛化能力，另一方面是为了实现渐进性。经过适当计算，可以得到
$$
\nabla&lt;/em&gt;{x_\sigma} \log q_\sigma(x_\sigma | z) = \frac{z - x_\sigma}{\sigma^2} = -\frac{\epsilon}{\sigma}
$$&lt;/p&gt;
&lt;p&gt;为了便于模型学习，将 $\sigma$ 作为条件输入给神经网络。于是，最终的目标函数为
$$
\boxed{
\begin{aligned}
\mathcal{L}&lt;em&gt;{DSM}&apos;(\theta) &amp;amp;= \mathbb{E}&lt;/em&gt;{\sigma \sim p(\sigma), z \sim p_{data}(z), x_\sigma \sim q_\sigma(x_\sigma | z)}\left[\lambda(\sigma) \left\Vert s_\theta(x_\sigma, \sigma) - \nabla_{x_\sigma}\log q_\sigma(x_\sigma | z) \right\Vert^2\right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{\sigma \sim p(\sigma), z \sim p&lt;/em&gt;{data}(z), \epsilon \sim \mathcal{N}(0, I_d)}\left[\lambda(\sigma)\left\Vert s_\theta(z + \sigma\epsilon, \sigma) + \frac{\epsilon}{\sigma}\right\Vert^2\right]
\end{aligned}
}
$$
其中 $\lambda(\sigma)$ 是噪声水平 $\sigma$ 的权重函数，通常取 $\lambda(\sigma) = \sigma^2$。&lt;/p&gt;
&lt;h3&gt;4.1.4 训练过程与推理过程&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;训练过程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SMLD 的训练过程旨在学习一个能够估计不同噪声水平下数据分布分数函数的神经网络 $s_\theta(x, \sigma)$。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;数据与噪声&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;从真实数据分布 $p_{data}(z)$ 中采样一个批次的数据 ${z_i}$。&lt;/li&gt;
&lt;li&gt;定义一个噪声水平的几何序列 ${\sigma_i}&lt;em&gt;{i=1}^L$，其中 $0 &amp;lt; \sigma_L &amp;lt; \sigma&lt;/em&gt;{L-1} &amp;lt; \dots &amp;lt; \sigma_2 &amp;lt; \sigma_1$。$\sigma_1$ 足够大，使得加噪后的数据分布接近高斯分布；$\sigma_L$ 足够小，使得加噪后的数据分布接近真实数据分布。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加噪与目标计算&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;对于每个数据点 $z$，从 ${\sigma_i}_{i=1}^L$ 中随机选择一个噪声水平 $\sigma$。&lt;/li&gt;
&lt;li&gt;从标准正态分布 $\mathcal{N}(0, I_d)$ 中采样一个噪声向量 $\epsilon$。&lt;/li&gt;
&lt;li&gt;生成带噪数据 $x_\sigma = z + \sigma\epsilon$。&lt;/li&gt;
&lt;li&gt;此时，我们知道真实的分数是 $\nabla_{x_\sigma}\log q_\sigma(x_\sigma | z) = -\frac{\epsilon}{\sigma}$。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型优化&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;将带噪数据 $x_\sigma$ 和噪声水平 $\sigma$ 输入到分数模型 $s_\theta(x_\sigma, \sigma)$ 中。&lt;/li&gt;
&lt;li&gt;计算损失函数，即模型预测分数与真实分数之间的加权均方误差：
$$
\mathcal{L}&lt;em&gt;{DSM}&apos;(\theta) = \frac{1}{L}\sum&lt;/em&gt;{i=1}^L \sigma_i^2 \mathbb{E}&lt;em&gt;{z \sim p&lt;/em&gt;{data}(z), \epsilon \sim \mathcal{N}(0, I_d)}\left[\left\Vert s_\theta(z + \sigma_i\epsilon, \sigma_i) + \frac{\epsilon}{\sigma_i}\right\Vert^2\right]
$$&lt;/li&gt;
&lt;li&gt;更新模型参数 $\theta$ 以最小化该损失。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;推理过程 (退火 Langevin 动力学采样)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;训练完成后，模型 $s_\theta(x, \sigma)$ 可以用来生成新样本。这个过程通过模拟一个从高噪声状态向低噪声状态“退火”的 Langevin 动力学过程来实现。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;初始化&lt;/strong&gt;：从一个简单的先验分布（如均匀分布或高斯分布）中采样初始样本 $x_L$。这个初始样本可以看作是处在最高噪声水平 $\sigma_1$ 下的随机状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;退火采样循环&lt;/strong&gt;：按照预设的噪声水平序列 ${\sigma_1, \sigma_2, \ldots, \sigma_L}$ 从高噪声水平到低噪声水平进行迭代。对于每一个噪声水平 $\sigma_i$：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Langevin 动力学更新&lt;/strong&gt;：执行 $K$ 步 Langevin MCMC 更新。在第 $k$ 步 ($k=1, \dots, K$)：
$$
x_{i}^{(k)} = x_{i}^{(k-1)} + \alpha_i s_\theta(x_{i}^{(k-1)}, \sigma_i) + \sqrt{2\alpha_i} \xi_k
$$
其中：
&lt;ul&gt;
&lt;li&gt;$x_{i}^{(k-1)}$ 是上一步的样本（对于 $k=1$，它是在上一个噪声水平 $\sigma_{i-1}$ 结束时得到的样本，即 $x_{i}^{(0)} = x_{i-1}^{(K)}$）。&lt;/li&gt;
&lt;li&gt;$s_\theta(x_{i}^{(k-1)}, \sigma_i)$ 是模型在当前样本和噪声水平下预测的分数。&lt;/li&gt;
&lt;li&gt;$\alpha_i$ 是与 $\sigma_i$ 相关的步长，通常设置为 $\alpha_i \propto \sigma_i^2$。&lt;/li&gt;
&lt;li&gt;$\xi_k \sim \mathcal{N}(0, I_d)$ 是一个随机高斯噪声，用于维持随机性。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传递样本&lt;/strong&gt;：完成 $K$ 步更新后，将最终得到的样本 $x_K$ 作为下一个更低噪声水平 $\sigma_{i+1}$ 的初始样本。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出样本&lt;/strong&gt;：当完成所有噪声水平的迭代后（即在最低噪声水平 $\sigma_L$ 下完成 $K$ 步更新），最终得到的样本即为生成的结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;退火过程的直观解释&lt;/strong&gt;：在高噪声水平下，数据分布平滑且简单，Langevin 动力学可以轻松探索整个样本空间，避免陷入局部最优。随着噪声水平的逐步降低，样本被逐渐“雕琢”和“精炼”，以匹配真实数据分布中更精细的结构和特征，最终生成高质量、高保真度的样本。&lt;/p&gt;
&lt;h2&gt;4.2 基于 SDE 的分数生成模型 (SGM) —— 动态 Score Matching&lt;/h2&gt;
&lt;h3&gt;4.2.1 模型概述&lt;/h3&gt;
&lt;p&gt;基于 SDE 的分数生成模型 (Score-based Generative Model through Stochastic Differential Equations, SGM) 由 &lt;strong&gt;Y. Song et al. (2021)&lt;/strong&gt; 提出，是对朗之万去噪分数匹配 (SMLD) 的扩展，同时整合了 DDPM。其核心思想是通过构建一个扩散过程的随机微分方程 (SDE)，使得数据分布可以通过该 SDE 从一个复杂的目标数据分布逐渐转变为简单的先验分布。通过 Anderson 定理反转该 SDE，从而实现从先验分布到目标数据分布的采样。&lt;/p&gt;
&lt;h3&gt;4.2.2 求解目标&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;逆向 SDE&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;根据 Anderson 定理，对于一个给定的正向 SDE ($t$ 从 0 变到 1，对应从 $p_{data}(x_0)$ 到 $p_{prior}(x_1)$)
$$
dx_t = f(x_t, t)dt + g(t)dW_t, \quad x_0 \sim p_{data}(x_0), \quad t \in [0, 1]
$$&lt;/p&gt;
&lt;p&gt;而它对应的逆向 SDE ($t$ 从 1 变到 0 的形式，对应从 $p_{prior}(x_1)$ 到 $p_{data}(x_0)$) 为
$$
dx_t = \left[f(x_t, t) - g(t)^2 \nabla_{x_t} \log p_t(x_t)\right] (-dt) + g(t)d\bar{W}&lt;em&gt;t, \quad x_1 \sim p&lt;/em&gt;{prior}(x_1), \quad t \in [0, 1]
$$
其中 $d\bar{W}&lt;em&gt;t$ 是逆向时间的维纳过程增量。式中取 $-dt$ 是因为时间反转，不过部分文献中为了公式美观也可能会直接写成
$$
dx_t = \left[f(x_t, t) - g(t)^2 \nabla&lt;/em&gt;{x_t} \log p_t(x_t)\right] dt + g(t)d\bar{W}&lt;em&gt;t, \quad x_1 \sim p&lt;/em&gt;{prior}(x_1), \quad t \in [0, 1]
$$
相当于其中的 $dt &amp;lt; 0$。不过不管哪种形式，其对应的概率路径 ${p_t(x)}_{t\in[0, 1]}$ 以及方向是一致的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;已知条件与目标&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 SGM 中，正向 SDE 的形式是我们预先定义好的。换句话说，$f(x_t, t)$ 和 $g(t)$ 是已知的。那么，既然用逆向 SDE 就可以实现从先验分布 $p_{prior}(x_1)$ 到目标数据分布 $p_{data}(x_0)$ 的采样，显然的，问题就转变成了只要能&lt;strong&gt;估计出&lt;/strong&gt; $\nabla_{x_t} \log p_t(x_t)$ &lt;strong&gt;就可以实现采样&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;用一个参数化的神经网络 $s_\theta(x_t, t)$ 来近似 $\nabla_{x_t} \log p_t(x_t)$，那么逆向 SDE 就变成了
$$
dx_t = \left[f(x_t, t) - g(t)^2 s_\theta(x_t, t)\right] dt + g(t)d\bar{W}&lt;em&gt;t, \quad x_1 \sim p&lt;/em&gt;{prior}(x_1), \quad t \in [0, 1]
$$
对应的损失函数为
$$
\mathcal{L}&lt;em&gt;{SM}(\theta) = \mathbb{E}&lt;/em&gt;{t \sim \mathcal{U}(0, 1), x_t \sim p_t(x_t)}\left[\lambda(t) \left| s_\theta(x_t, t) - \nabla_{x_t} \log p_t(x_t) \right|^2 \right]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;条件分数匹配&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同样的道理，我们不知道 $\nabla_{x_t} \log p_t(x_t)$ 的形式，因此我们可以使用条件分数匹配 (Conditional Score Matching) 的方法来优化目标函数
$$
\mathcal{L}&lt;em&gt;{CSM}(\theta) = \mathbb{E}&lt;/em&gt;{t \sim \mathcal{U}(0, 1), z \sim p_{data}(z), x_t \sim p_t(x_t | z)} \left[\lambda(t) \left| s_\theta(x_t, t) - \nabla_{x_t} \log p_t(x_t | z) \right|^2 \right]
$$&lt;/p&gt;
&lt;p&gt;同样的方式可以证明 $\mathcal{L}&lt;em&gt;{SM}(\theta)$ 与 $\mathcal{L}&lt;/em&gt;{CSM}(\theta)$ 在优化上是等价的。这部分证明可以直接参考 SMLD 中的去噪分数匹配与条件去噪分数匹配的等价性部分，因为只要替换掉下标，两个证明过程是一模一样的。&lt;/p&gt;
&lt;p&gt;于是最终用于训练的目标函数为
$$
\boxed{
\begin{aligned}
\mathcal{L}&lt;em&gt;{CSM}(\theta) &amp;amp;= \mathbb{E}&lt;/em&gt;{t \sim \mathcal{U}(0, 1), z \sim p_{data}(z), x_t \sim p_t(x_t | z)}\left[\lambda(t) \left| s_\theta(x_t, t) - \nabla_{x_t} \log p_t(x_t | z) \right|^2 \right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{t \sim \mathcal{U}(0, 1), z \sim p&lt;/em&gt;{data}(z), \epsilon \sim \mathcal{N}(0, I_d)}\left[\lambda(t) \left| s_\theta(z + \sigma_t \epsilon, t) + \frac{\epsilon}{\sigma_t} \right|^2 \right]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;\end{aligned}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}
$$
其中 $\lambda(t)$ 是时间相关的权重函数，通常取 $\lambda(t) = g(t)^2$。损失函数中要求 $\nabla_{x_t} \log p_t(x_t | z)$ 是已知的，如果取为条件高斯路径 $p_t(x_t | z) = \mathcal{N}(z, \sigma_t^2 I_d)$，那么 $\nabla_{x_t} \log p_t(x_t | z) = -\frac{\epsilon}{\sigma_t}$。&lt;/p&gt;
&lt;p&gt;训练出参数化的分数函数 $s_\theta(x_t, t)$ 后，就可以通过逆向 SDE 来实现从先验分布 $p_{prior}(x_1)$ 到目标数据分布 $p_{data}(x_0)$ 的采样。过程类似 Flow Matching、SMLD 等中的过程，这里不加赘述。&lt;/p&gt;
&lt;h3&gt;4.2.3 用概率流 ODE 加速 SDE 采样过程&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;从 ODE 到 SDE&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;考虑一个 ODE，形式为
$$
\frac{dx_t}{dt} = u^{target}(x_t, t), \quad x_0 \sim p_{prior}(x_0), \quad t \in [0, 1]
$$&lt;/p&gt;
&lt;p&gt;通过 Liouville 方程，我们知道该 ODE 对应了唯一的一条概率路径 ${p_t(x)}_{t\in[0, 1]}$
$$
\frac{\partial p_t(x)}{\partial t} = -\nabla_x \cdot \left[p_t(x) u^{target}(x, t)\right]
$$&lt;/p&gt;
&lt;p&gt;事实上，存在无数的 SDE 可以对应有同样的概率路径 ${p_t(x)}_{t\in[0, 1]}$。例如，一个简单等价 SDE，其形式如下&lt;/p&gt;
&lt;p&gt;$$
dx_t = \left[u^{target}(x_t, t) + \frac{\sigma_t^2}{2}\nabla_{x_t} \log p_t(x_t, t)\right]dt + \sigma_t dW_t, \quad x_0 \sim p_{prior}(x_0), \quad t \in [0, 1]
$$&lt;/p&gt;
&lt;p&gt;相关等价性证明可参考&lt;a href=&quot;../appendix/#a3-%E6%A6%82%E7%8E%87%E6%B5%81%E7%AD%89%E4%BB%B7%E5%AE%9A%E7%90%86&quot;&gt;附录 A.3&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;利用 ODE 加速采样&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;根据上述等价性，给定一个 SDE
$$
dx_t = f(x_t, t)dt + g(t)dW_t, \quad x_0 \sim p_{data}(x_0), \quad t \in [0, 1]
$$
我们可以用它对应的概率流 ODE
$$
dx_t = \left[f(x_t, t) - \frac{1}{2}g(t)^2 \nabla_{x_t} \log p_t(x_t), \quad x_0 \sim p_{data}(x_0), \quad t \in [0, 1]\right]dt
$$
简化并加速采样过程。因为 ODE 的采样过程是确定性的，不需要像 SDE 那样每一步都添加随机噪声，从而大大减少了采样步骤。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;采样速度更快：由于不需要每一步都添加随机噪声，ODE 的采样过程通常比 SDE 更快。&lt;/li&gt;
&lt;li&gt;结果更稳定：ODE 的确定性使得每次采样结果更加一致，减少了随机性带来的波动。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;可能牺牲多样性：由于缺乏随机性，ODE 采样可能会减少生成样本的多样性。&lt;/li&gt;
&lt;li&gt;降低的质量：同样是由于缺乏随机性，ODE 采样可能会导致生成样本的质量略有下降。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4.3 扩散与流模型的大一统&lt;/h2&gt;
&lt;h3&gt;4.3.1 分数匹配生成模型 (SGM) 与 DPPM 的统一性&lt;/h3&gt;
&lt;p&gt;当选择条件概率路径为条件高斯分布时
$$
p_t(x_t | z) = \mathcal{N}(\alpha_t z, \beta_t^2 I_d)
$$
$$
x_t = \alpha_t z + \beta_t \epsilon, \quad \epsilon \sim \mathcal{N}(0, I_d)
$$
SGM 的损失函数为
$$
\mathcal{L}&lt;em&gt;{SGM}(\theta) = \mathbb{E}&lt;/em&gt;{t \sim \mathcal{U}(0, 1), z \sim p_{data}(z), \epsilon \sim \mathcal{N}(0, I_d)}\left[\lambda(t) \left| s_\theta(\alpha_t z + \beta_t \epsilon, t) + \frac{\epsilon}{\beta_t} \right|^2 \right]
$$
DDPM 的损失函数为
$$
\mathcal{L}&lt;em&gt;{DDPM}(\theta) = \mathbb{E}&lt;/em&gt;{t \sim \text{Uniform}(1,T), x_0 \sim q(x_0), \epsilon \sim \mathcal{N}(0,I_d)} \left[ \left| \epsilon_\theta(\alpha_t z + \beta_t \epsilon, t) - \epsilon\right|^2 \right]
$$
这两个损失函数是等价的，只需要令
$$
\boxed{
s_\theta(x_t, t) = -\frac{1}{\beta_t} \epsilon_\theta(x_t, t), \quad \lambda(t) = \beta_t^2
}
$$
注意这里 DDPM 的条件概率路径是原始论文中的扩展版本，并不是写错了。&lt;/p&gt;
&lt;h3&gt;4.3.2 分数匹配生成模型 (SGM) 与 Flow Matching 的统一性&lt;/h3&gt;
&lt;p&gt;依然是在条件高斯路径下
$$
p_t(x_t | z) = \mathcal{N}(\alpha_t z, \beta_t^2 I_d)
$$
流匹配的向量场 $u_t(x)$ 让粒子沿着 ODE
$$
dx_t = u_t(x_t) dt, \quad x_0 \sim p_{data}(x_0), \quad t \in [0, 1]
$$
分数匹配则定义了
$$
s_t(x | z) = \nabla_x \log p_t(x | z) = -\frac{x - \alpha_t z}{\beta_t^2}
$$
根据条件向量场的公式
$$
u_t(x | z) = \dot\alpha_t z + \frac{\dot\beta_t}{\beta_t} (x - \alpha_t z) = \frac{\dot\alpha_t}{\alpha_t} x + \left(\beta_t^2\frac{\dot\alpha_t}{\alpha_t} - \dot\beta_t\beta_t\right) s_t(x | z)&lt;br /&gt;
$$
根据边缘性定理
$$
u_t(x) = \int u_t(x | z) p(z | x) dz = \mathbb{E}&lt;em&gt;{z \sim p(z | x)}\left[u_t(x | z)\right]
$$
有
$$
\begin{aligned}
u_t(x) &amp;amp;= \int u_t(x | z) \frac{p_t(x | z)p&lt;/em&gt;{data}(z)}{p_t(x)} dz \
&amp;amp;= \int \left[\frac{\dot\alpha_t}{\alpha_t} x + \left(\beta_t^2\frac{\dot\alpha_t}{\alpha_t} - \dot\beta_t\beta_t\right) \nabla_x \log p_t(x | z)\right] \frac{p_t(x | z)p_{data}(z)}{p_t(x)} dz \
&amp;amp;= \frac{\dot\alpha_t}{\alpha_t} x + \left(\beta_t^2\frac{\dot\alpha_t}{\alpha_t} - \dot\beta_t\beta_t \right)\nabla_x \log p_t(x)
\end{aligned}
$$
这里用到了交换积分 $\int_z$ 与求梯度 $\nabla_x$
$$
\int \nabla_x p_t(x | z)p_{data}(z) dz = \nabla_x \int p_t(x | z)p_{data}(z) dz = \nabla_x p_t(x)
$$
由此得出
$$
\boxed{
u_t(x) = \frac{\dot\alpha_t}{\alpha_t} x + \left(\beta_t^2\frac{\dot\alpha_t}{\alpha_t} - \dot\beta_t\beta_t \right) s_t(x)
}
$$
对应学习的网络也满足
$$
\boxed{
u_\theta(x, t) = \frac{\dot\alpha_t}{\alpha_t} x + \left(\beta_t^2\frac{\dot\alpha_t}{\alpha_t} - \dot\beta_t\beta_t \right) s_\theta(x, t)
}
$$
换句话说，对应的 ODE 也可以写作
$$
dx_t = \left[\frac{\dot\alpha_t}{\alpha_t} x + \left(\beta_t^2\frac{\dot\alpha_t}{\alpha_t} - \dot\beta_t\beta_t \right) s_\theta(x, t)\right] dt, \quad x_0 \sim p_{data}(x_0), \quad t \in [0, 1]
$$
利用 ODE 与 SDE 的等价性 (见&lt;a href=&quot;../appendix/#a3-%E6%A6%82%E7%8E%87%E6%B5%81%E7%AD%89%E4%BB%B7%E5%AE%9A%E7%90%86&quot;&gt;附录 A.3&lt;/a&gt;)，可以得到与 SGM 对应的 SDE 形式
$$
dx_t = \left[\frac{\dot\alpha_t}{\alpha_t} x + \left(\beta_t^2\frac{\dot\alpha_t}{\alpha_t} - \dot\beta_t\beta_t + \frac{\sigma_t^2}{2}\right) s_\theta(x, t)\right] dt + \sigma_t dw_t, \quad x_0 \sim p_{data}(x_0), \quad t \in [0, 1]
$$
其中 $\sigma_t$ 是任意大于 0 的扩散系数。这就完成了 SGM 与 Flow Matching 的统一。&lt;/p&gt;
&lt;h3&gt;4.3.3 模型间的差异&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;既然 SGM 与 DDPM、Flow Matching 都是等价的，为什么它们实际上的表现差异很大？&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Schedule 参数不同&lt;/strong&gt;：实际上差异大的原因主要在于选取的 schedule 参数是不同的，换句话说，高斯条件概率路径 $p_t(x_t | z) = \mathcal{N}(\alpha_t z, \beta_t^2 I_d)$ 中 $\alpha_t, \beta_t$ 的选取不同导致了最终的表现差异。DDPM 选取的是线性 schedule 或余弦 schedule，SGM 选取的是例如指数增长的非线性 schedule，Flow Matching 中选取的是最优传输路径。因此实际使用中，三者的表现不同。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;采样策略不同&lt;/strong&gt;：即使理论路径相同，实际采样需要离散化，步数少或者步长不合理会造成性能差异。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;训练函数不同&lt;/strong&gt;：训练函数不同对神经网络的影响也很大，尤其是当神经网络容量有限时。这是因为网络本身具有 inductive bias，$s_t(x)$, $u_t(x)$, $\epsilon$ 各有不同的分布特性，网络可能更适合拟合某一种。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>流模型 Chapter3——流匹配模型 (Flow Matching)</title><link>https://adalovelemon.github.io/posts/content/coursenotes/generativeai/flowmodels/chapter3/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/generativeai/flowmodels/chapter3/</guid><pubDate>Thu, 09 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;流匹配模型&lt;/p&gt;
&lt;h2&gt;3.1 流匹配的概念&lt;/h2&gt;
&lt;p&gt;流匹配 (Flow Matching) 是一个通过&lt;strong&gt;学习概率流 (Probability Flow ODE)&lt;/strong&gt; 来实现从噪声分布到数据分布的连续生成过程的方法。相比扩散模型 (Diffusion Models) 需要定义一个随机过程 (SDE) 并通过采样训练，流匹配模型直接学习确定性 ODE 的速度场 (velocity field)。&lt;/p&gt;
&lt;h3&gt;3.1.1 可学习的动力学模型&lt;/h3&gt;
&lt;p&gt;给定一个先验分布 $p_{prior}(x)$ 和一个真实数据分布 $p_{data}(x)$，其中对于真实数据分布 $p_{data}(x)$ 我们只能得到服从于它的样本，但是得不到分布的概率密度函数，而先验分布 $p_{prior}(x)$ 我们是知道概率密度函数的。流匹配需要实现从先验分布到真实数据分布的转换。&lt;/p&gt;
&lt;p&gt;正如 DDPM 模型中所做的那样，可以设计一套转换的动力学过程，来完成这一任务。在 DDPM 中，前向过程和逆向过程的动力学过程都是具有解析形式的。考虑到神经网络具有强大的拟合能力，是否可以考虑使用神经网络来学习得到一个合适的速度场呢？答案是肯定的。&lt;/p&gt;
&lt;h3&gt;3.1.2 数学形式&lt;/h3&gt;
&lt;p&gt;流匹配模型假定存在有速度场 $u^{target}(x_t, t)$，使得样本的演化遵循
$$
\frac{dx_t}{dt} = u^{target}(x_t, t), \quad t \in [0, 1]
$$
并且保证它诱导的中间分布 $p_t(x)$ 满足边界条件
$$
x_0 \sim p_0(x_0) = p_{prior}(x_0), \quad x_1 \sim p_1(x_1) = p_{data}(x_1)
$$&lt;/p&gt;
&lt;p&gt;根据先前的分析，这样定义的速度场 $u^{target}(x_t, t)$ 和它所诱导出的中间概率分布族 ${p_t(x)}_{t\in[0,1]}$ 满足 Liouville 方程
$$
\frac{\partial p_t(x)}{\partial t} + \nabla_x \cdot \left[p_t(x) u^{target}(x, t)\right] = 0
$$&lt;/p&gt;
&lt;p&gt;那么只要求得了速度场 $u^{target}(x_t, t)$，就可以通过 ODE 的动力学过程实现从 $p_{prior}(x)$ 到 $p_{data}(x)$ 的转换。实际上，速度场 $u^{target}(x_t, t)$ 并不一定存在解析形式，因此往往是采用神经网络学习的方式来得到这样的满足条件的速度场的，参数化为 $u_\theta(x_t, t)$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;满足条件的速度场是否唯一？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;答案是&lt;strong&gt;不唯一&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;多种概率转移路径&lt;/strong&gt;: 在连续时间动力学系统中，两端分布的约束并不足以唯一确定中间的演化过程。换句话说，&lt;strong&gt;存在无数种不同的中间分布族&lt;/strong&gt; ${p_t(x)}&lt;em&gt;{t\in[0,1]}$ &lt;strong&gt;可以实现从&lt;/strong&gt; $p&lt;/em&gt;{prior}(x)$ &lt;strong&gt;到&lt;/strong&gt; $p_{data}(x)$ &lt;strong&gt;的转换&lt;/strong&gt;。即有无数条可能的概率路径 (probability trajectories) 连接起这两个分布。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Liouville 方程对速度场的约束不足&lt;/strong&gt;: Liouville 方程只给出了速度场的散度约束，而没有给出完整的速度场信息。即使确定了中间分布族 ${p_t(x)}_{t\in[0, 1]}$，对应的速度场也可能有无数个。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3.1.3 初始目标函数&lt;/h3&gt;
&lt;p&gt;直觉上，我们希望让模型预测的速度场 $u_\theta(x_t, t)$ 接近真实的速度场 $u^{target}(x_t, t)$。这里假设我们知道在每个时间点 $t$ 下，样本 $x_t$ 的真实速度场 $u^{target}(x_t, t)$，虽然它的闭式解未必存在。使用边缘流匹配回归损失函数
$$
\mathcal{L}&lt;em&gt;{FM}(\theta) = \mathbb{E}&lt;/em&gt;{t\sim \text{Uniform}[0, 1], x_t \sim p_t(x_t)} \left[ \left\Vert u_\theta(x_t, t) - u^{target}(x_t, t) \right\Vert^2 \right]
$$
其中，$u^{target}(x_t, t)$ 满足 Liouville 方程 $\frac{\partial p_t(x)}{\partial t} + \nabla_x \cdot \left[p_t(x) u^{target}(x_t, t)\right] = 0$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;边缘流匹配损失函数的困难&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$u^{target}(x_t, t)$ &lt;strong&gt;未知&lt;/strong&gt;: 实际应用中，我们不知道 $u^{target}(x_t, t)$ (否则就不需要学习了)&lt;/li&gt;
&lt;li&gt;$p_t(x_t)$ &lt;strong&gt;设计上的困难&lt;/strong&gt;: 直接设计出合适的、有闭式形式的，且拟合出的速度场有强泛化性能的 $p_t(x_t)$ 并不容易。即使设计有闭式的 $p_t(x_t)$，也不能保证直接求出对应 $u^{target}(x, t)$ 有闭式解。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;综上考虑，需要转换这个目标函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果 $p_t(x)$ 已知，能否用 Liouville 方程替代作为损失函数用于拟合 $u^{target}(x, t)$ 呢？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;理论上可以，但非常困难&lt;/strong&gt;：在这个条件下， Liouville 方程本身不能唯一确定 $u^{target}(x, t)$，只能确定其散度 $\nabla_x \cdot (p_t(x) u^{target}(x, t))$。换言之，Liouville 方程是&quot;弱监督&quot;信号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工程上计算难度大&lt;/strong&gt;: 计算 $\nabla_x\cdot \left[p_t(x) u_\theta(x, t)\right]$ 在数据高维时难度大。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.2 条件概率路径&lt;/h2&gt;
&lt;h3&gt;3.2.1 边缘性定理&lt;/h3&gt;
&lt;p&gt;边缘性定理 (Marginalization Theorem) 的数学形式为
$$
\boxed{
u^{target}(x_t, t) = \int u^{target}(x_t, t | z) p(z | x_t) dz = \mathbb{E}_{z \sim p(z | x_t)}\left[u^{target}(x_t, t | z)\right]
}
$$
它表明边缘速度场 $u^{target}(x_t, t)$ 可以通过条件速度场 $u^{target}(x_t, t | z)$ 的加权平均来表示。它的推导过程详见&lt;a href=&quot;../appendix/#a2-%E8%BE%B9%E7%BC%98%E6%80%A7%E5%AE%9A%E7%90%86&quot;&gt;附录 A.2&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;3.2.2 条件流匹配目标函数&lt;/h3&gt;
&lt;p&gt;引入条件流匹配目标函数 (Conditional Flow Matching Objective)
$$
\mathcal{L}&lt;em&gt;{CFM}(\theta) = \mathbb{E}&lt;/em&gt;{t \sim \text{Uniform}[0, 1], z \sim p_{data}(z), x_t \sim p_t(x_t | z)} \left[ \left\Vert u_\theta(x_t, t) - u^{target}(x_t, t | z) \right\Vert^2 \right]
$$&lt;/p&gt;
&lt;p&gt;可以证明，条件流匹配目标函数 $\mathcal{L}&lt;em&gt;{CFM}(\theta)$ 与边缘流匹配目标函数 $\mathcal{L}&lt;/em&gt;{FM}(\theta)$ 在最优解上是等价的。这意味着，通过最小化 $\mathcal{L}&lt;em&gt;{CFM}(\theta)$，我们可以找到一个神经网络 $u&lt;/em&gt;\theta(x_t, t)$，它能够有效地逼近真实的边缘速度场 $u^{target}(x_t, t)$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;:
首先，我们将 $\mathcal{L}&lt;em&gt;{CFM}(\theta)$ 的期望形式展开
$$
\begin{aligned}
\mathcal{L}&lt;/em&gt;{CFM}(\theta) &amp;amp;= \mathbb{E}&lt;em&gt;{t \sim \text{Uniform}[0, 1], z \sim p&lt;/em&gt;{data}(z), x_t \sim p_t(x_t | z)} \left[ \left\Vert u_\theta(x_t, t) - u^{target}(x_t, t | z) \right\Vert^2 \right] \
&amp;amp;= \int_0^1 \int p_{data}(z) \int p_t(x_t | z) \left\Vert u_\theta(x_t, t) - u^{target}(x_t, t | z) \right\Vert^2 dx_t dz dt
\end{aligned}
$$
根据贝叶斯定理，有 $p_t(x_t, z) = p_t(x_t | z) p_{data}(z) = p(z | x_t) p_t(x_t)$。代入上式可得
$$
\begin{aligned}
\mathcal{L}&lt;em&gt;{CFM}(\theta) &amp;amp;= \int_0^1 \int p_t(x_t) \int p(z | x_t) \left\Vert u&lt;/em&gt;\theta(x_t, t) - u^{target}(x_t, t | z) \right\Vert^2 dz dx_t dt \
&amp;amp;= \int_0^1 \int p_t(x_t) \mathbb{E}&lt;em&gt;{z \sim p(z|x_t)} \left[ \left\Vert u&lt;/em&gt;\theta(x_t, t) - u^{target}(x_t, t | z) \right\Vert^2 \right] dx_t dt \
&amp;amp;= \mathbb{E}&lt;em&gt;{t \sim \text{Uniform}[0, 1], x_t \sim p_t(x_t)} \left[ \mathbb{E}&lt;/em&gt;{z \sim p(z|x_t)} \left[ \left\Vert u_\theta(x_t, t) - u^{target}(x_t, t | z) \right\Vert^2 \right] \right]
\end{aligned}
$$
对于内层的期望，我们可以将其展开
$$
\begin{aligned}
&amp;amp;\mathbb{E}&lt;em&gt;{z \sim p(z|x_t)} \left[ \left\Vert u&lt;/em&gt;\theta(x_t, t) - u^{target}(x_t, t | z) \right\Vert^2 \right] \
=\ &amp;amp;\mathbb{E}&lt;em&gt;{z \sim p(z|x_t)} \left[ \left\Vert (u&lt;/em&gt;\theta(x_t, t) - \mathbb{E}&lt;em&gt;{z \sim p(z|x_t)}[u^{target}(x_t, t | z)]) + (\mathbb{E}&lt;/em&gt;{z \sim p(z|x_t)}[u^{target}(x_t, t | z)] - u^{target}(x_t, t | z)) \right\Vert^2 \right] \
=\ &amp;amp;\left\Vert u_\theta(x_t, t) - \mathbb{E}&lt;em&gt;{z \sim p(z|x_t)}[u^{target}(x_t, t | z)] \right\Vert^2 + \mathbb{E}&lt;/em&gt;{z \sim p(z|x_t)} \left[ \left\Vert \mathbb{E}&lt;em&gt;{z \sim p(z|x_t)}[u^{target}(x_t, t | z)] - u^{target}(x_t, t | z) \right\Vert^2 \right] + 2 (u&lt;/em&gt;\theta(x_t, t) - \mathbb{E}&lt;em&gt;{z \sim p(z|x_t)}[u^{target}(x_t, t | z)])^\top \underbrace{\mathbb{E}&lt;/em&gt;{z \sim p(z|x_t)} \left[(\mathbb{E}&lt;em&gt;{z \sim p(z|x_t)}[u^{target}(x_t, t | z)] - u^{target}(x_t, t | z)) \right]}&lt;/em&gt;{0} \
=\ &amp;amp;\left\Vert u_\theta(x_t, t) - \mathbb{E}&lt;em&gt;{z \sim p(z|x_t)}[u^{target}(x_t, t | z)] \right\Vert^2 + \text{Var}&lt;/em&gt;{z \sim p(z|x_t)} (u^{target}(x_t, t | z)) \
=\ &amp;amp;\left\Vert u_\theta(x_t, t) - u^{target}(x_t, t) \right\Vert^2 + \text{Var}&lt;em&gt;{z \sim p(z|x_t)} (u^{target}(x_t, t | z))
\end{aligned}
$$
其中，$u&lt;/em&gt;\theta(x_t, t)$ 和 $\mathbb{E}&lt;em&gt;{z \sim p(z|x_t)}[u^{target}(x_t, t | z)]$ 相对于 $z$ 是常数，因此第一项前面的期望符号去掉。此外，根据边缘性定理，$u^{target}(x_t, t) = \mathbb{E}&lt;/em&gt;{z \sim p(z | x_t)}\left[u^{target}(x_t, t | z)\right]$，替换 $\mathbb{E}&lt;em&gt;{z \sim p(z | x_t)}\left[u^{target}(x_t, t | z)\right]$。因此，$\mathcal{L}&lt;/em&gt;{CFM}(\theta)$ 可以重写为
$$
\mathcal{L}&lt;em&gt;{CFM}(\theta) = \underbrace{\mathbb{E}&lt;/em&gt;{t \sim \text{Uniform}[0, 1], x_t \sim p_t(x_t)} \left[ \left\Vert u_\theta(x_t, t) - u^{target}(x_t, t) \right\Vert^2 \right]}&lt;em&gt;{\mathcal{L}&lt;/em&gt;{FM}(\theta)} + \underbrace{\mathbb{E}&lt;em&gt;{t \sim \text{Uniform}[0, 1], x_t \sim p_t(x_t)} \left[ \text{Var}&lt;/em&gt;{z \sim p(z|x_t)} (u^{target}(x_t, t | z)) \right]}&lt;em&gt;{\text{与} \theta \text{无关的项}}
$$
上式表明，$\mathcal{L}&lt;/em&gt;{CFM}(\theta)$ 等于 $\mathcal{L}&lt;em&gt;{FM}(\theta)$ 加上一个与模型参数 $\theta$ 无关的方差项。因此，&lt;strong&gt;最小化&lt;/strong&gt; $\mathcal{L}&lt;/em&gt;{CFM}(\theta)$ &lt;strong&gt;等价于最小化&lt;/strong&gt; $\mathcal{L}_{FM}(\theta)$。&lt;/p&gt;
&lt;p&gt;这个转换的优势在于，$\mathcal{L}_{CFM}$ 的计算不需要边缘分布 $p_t(x_t)$ 或边缘速度场 $u^{target}(x_t, t)$，而是依赖于条件概率路径 $p_t(x_t|z)$ 和条件速度场 $u^{target}(x_t, t|z)$，这两者都可以被设计成简单的、&lt;strong&gt;具有解析表达式&lt;/strong&gt;的形式，从而使得训练变得可行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;既然有了条件速度场的&lt;/strong&gt; $u^{target}(x_t, t | z)$ &lt;strong&gt;解析形式，那直接用解析形式生成数据不就行了？为什么还需要训练神经网络？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;条件速度场需要确定的真实数据点&lt;/strong&gt;: 条件速度场 $u^{target}(x_t, t | z)$ 是基于特定的真实数据点 $z$ 定义的。它的作用相当于是为数据的演化提供了一个方向信息。然而，推理过程中，我们不会有真实的数据点 $z$ 可供使用，因此无法直接利用条件速度场来生成数据。&lt;/li&gt;
&lt;li&gt;$u^{target}(x_t, t) = \mathbb{E}&lt;em&gt;{z \sim p(z | x_t)}\left[u^{target}(x_t, t | z)\right]$ &lt;strong&gt;的一种加权理解&lt;/strong&gt;: 边缘速度场实际上相当于是对所有可能的真实数据点 $z$ 的条件速度场的加权平均。这个加权过程隐含了对数据分布的整体理解，而不仅仅是单个数据点的演化方向。让 $u&lt;/em&gt;\theta(x_t, t)$ 去拟合这个加权平均，可以让模型学会把握数据的整体模式。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2.3 条件概率路径与条件速度场&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;为什么需要条件概率路径？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;直接用设置有简单闭式表达式的边际概率路径 (marginal probability path) ${p_t(x)}&lt;em&gt;{t\in[0,1]}$ 训练其对应的速度场 $v&lt;/em&gt;\theta(x, t)$ 是非常困难的。为了解决这个问题，流匹配模型引入了&lt;strong&gt;条件概率路径 (conditional probability path)&lt;/strong&gt; 的概念。其核心思想是，不直接对整个分布的演化进行建模，而是为每一对噪声样本 $x_0 \sim p_0(x)$ 和数据样本 $x_1 \sim p_1(x)$ 定义一个简单的、确定的路径。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;条件概率的边界条件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设 $z \sim p_{data}(z)$ 来自于真实数据分布。为了实现 $p_0(x_0) = p_{prior}(x_0), p_1(x_1) = p_{data}(x_1)$，条件概率族 ${p_t(x_t | z)}_{t\in[0,1]}$ 同样需要满足一定的边界条件。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先验条件分布：$p_0(x_0 | z) = p_0(x_0)$，即 $x_0$ 与 $z$ 独立&lt;/li&gt;
&lt;li&gt;数据条件分布：$p_1(x_1 | z) = \mathcal{N}(x_1 | z, \sigma_1^2 I_d)$，其中 $\sigma_1$ 是很小的正数，用于将分布紧紧包围在数据点 $z$ 附近。事实上，当 $\sigma_1 \to 0$ 时，就是 Dirac Delta 分布 $p_1(x_1 | z) = \delta_z(x_1) = \begin{cases}\infty, &amp;amp; x_1 = z \ 0, &amp;amp; x_1 \neq z\end{cases}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;条件概率路径的设计&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了便于实现，将条件概率 $p_t(x_t | z)$ 设置为一个简单的、具有闭式表达式的路径族
$$
p_t(x_t | z) = \mathcal{N}(x_t | \mu_t(z), \sigma_t^2 I_d)
$$
这个形式就将条件概率路径的设计转换成了对均值函数 $\mu_t(z)$ 和标准差函数 $\sigma_t$ 的设计。现在我们只需要保证 $\mu_t(z)$ 和 $\sigma_t$ 满足边界条件就可以了
$$
\mu_0(z) = 0, \quad \sigma_0 = 1, \quad \mu_1(z) = z, \quad \sigma_1 \to 0
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;条件速度场&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;根据条件概率路径的定义，可以计算出对应的条件速度场。条件概率 $p_t(x_t | z)$ 和条件速度场 $u^{target}(x_t, t | z)$ 满足条件 Liouville 方程：
$$
\frac{\partial p_t(x | z)}{\partial t} + \nabla_x \cdot \left[p_t(x | z) u^{target}(x, t | z)\right] = 0
$$
当条件概率路径被设计为高斯分布 $p_t(x_t | z) = \mathcal{N}(x_t | \mu_t(z), \sigma_t^2 I_d)$ 时，可以推导出对应的条件速度场具有一个简单的解析形式：
$$
\boxed{
u^{target}(x_t, t | z) = \dot{\mu_t}(z) + \frac{\dot{\sigma_t}}{\sigma_t} (x_t - \mu_t(z))
}
$$
其中 $\dot{\mu_t}(z) = \frac{\partial \mu_t(z)}{\partial t}$，$\dot{\sigma_t} = \frac{d\sigma_t}{dt}$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推导过程&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(i) 对数概率的推导&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;对于高斯分布 $p_t(x_t | z) = \mathcal{N}(x_t | \mu_t(z), \sigma_t^2 I_d)$，其对数概率为
$$
\log p_t(x_t|z) = -\frac{|x_t - \mu_t(z)|^2}{2\sigma_t^2} - \frac{d}{2}\log(2\pi\sigma_t^2)
$$
对时间 $t$ 求导，得到
$$
\frac{\partial \log p_t(x_t|z)}{\partial t} = \frac{(x_t-\mu_t(z))^\top \dot{\mu}_t(z)}{\sigma_t^2} + \frac{|x_t-\mu_t(z)|^2 \dot{\sigma}_t}{\sigma_t^3} - \frac{\dot{\sigma}_t d}{\sigma_t}
$$
其中 $\dot{\mu_t}(z) = \frac{\partial\mu_t(z)}{\partial t}$，$\dot{\sigma}_t = \frac{d\sigma_t}{dt}$。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(ii) Liouville 方程的对数形式&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;另一方面，将 Liouville 方程两边除以 $p_t$ 得到
$$
\begin{aligned}
\frac{\partial \log p_t(x_t | z)}{\partial t} &amp;amp;= -\frac{1}{p_t(x_t | z)}\nabla_{x_t} \cdot \left[p_t(x_t | z) u^{target}(x_t, t | z)\right] \
&amp;amp;= -[\nabla_{x_t} \log p_t(x_t | z)] ^\top u^{target}(x_t, t | z) - \nabla_{x_t} \cdot u^{target}(x_t, t | z)
\end{aligned}
$$
其中有利用性质，$\nabla\cdot (a\vec{v}) = a \nabla\cdot \vec{v} + \vec{v} \cdot \nabla a$。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(iii) 联立求解条件速度场&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;对于高斯分布
$$
\nabla_{x_t} \log p_t(x_t|z) = -\frac{x_t-\mu_t}{\sigma_t^2}
$$
代入上式
$$
\frac{\partial \log p_t(x_t|z)}{\partial t} = \frac{(x_t-\mu_t(z))^\top u^{target}(x_t, t|z)}{\sigma_t^2} - \nabla_{x_t} \cdot u^{target}(x_t, t|z)
$$
将两个 $\frac{\partial \log p_t}{\partial t}$ 的表达式联立，得到关于 $u^{target}(x_t, t|z)$ 的方程
$$
\boxed{
\frac{(x_t-\mu_t(z))^\top \dot{\mu_t}(z)}{\sigma_t^2} + \frac{|x_t-\mu_t(z)|^2 \dot{\sigma_t}}{\sigma_t^3} - \frac{\dot{\sigma_t} d}{\sigma_t} = \frac{(x_t-\mu_t(z))^\top u^{target}(x_t, t|z)}{\sigma_t^2} - \nabla_{x_t} \cdot u^{target}(x_t, t|z)
}
$$
可以发现当 $u^{target}(x_t, t|z)$ 取以下形式时方程成立
$$
u^{target}(x_t, t | z) = \dot{\mu_t}(z) + \frac{\dot{\sigma_t}}{\sigma_t} (x_t - \mu_t(z))
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(iv) 代入验证&lt;/em&gt;
$$
\begin{aligned}
\nabla_x \cdot u^{target}(x_t, t|z) &amp;amp;= \nabla_x \cdot \left( \dot{\mu_t}(z) + \frac{\dot{\sigma_t}}{\sigma_t} (x_t - \mu_t(z)) \right) = \frac{\dot{\sigma_t}}{\sigma_t}d \
\frac{(x_t-\mu_t(z))^\top}{\sigma_t^2} u^{target} &amp;amp;= \frac{(x_t-\mu_t(z))^\top}{\sigma_t^2} \left( \dot{\mu_t}(z) + \frac{\dot{\sigma_t}}{\sigma_t} (x_t - \mu_t(z)) \right) = \frac{(x_t-\mu_t(z))^\top \dot{\mu_t}(z)}{\sigma_t^2} + \frac{|x_t-\mu_t(z)|^2 \dot{\sigma_t}}{\sigma_t^3}
\end{aligned}
$$
将这两项相减，正好得到 $\frac{\partial \log p_t(x_t|z)}{\partial t}$ 的表达式，证明了该速度场形式的正确性。关于方程的求解，可以参考&lt;a href=&quot;../appendix/#c1-%E6%9D%A1%E4%BB%B6%E9%80%9F%E5%BA%A6%E5%9C%BA%E7%9A%84%E8%A7%A3%E6%9E%90%E5%BD%A2%E5%BC%8F&quot;&gt;附录 C.1&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;这个结果非常重要，因为它表明，我们可以设计出简单的均值函数 $\mu_t(z)$ 和标准差函数 $\sigma_t$，得到一个具有闭式解的条件速度场 $u^{target}(x_t, t | z)$。这使得条件流匹配的目标函数 $\mathcal{L}_{CFM}(\theta)$ 变得完全可计算。&lt;/p&gt;
&lt;h2&gt;3.3 一些设计案例&lt;/h2&gt;
&lt;h3&gt;3.3.1 VP 过程的条件向量场 (Variance Preserving Conditional Vector Field)&lt;/h3&gt;
&lt;p&gt;VP 过程是一种常见的扩散过程，它的特点是在前向过程 ($p_{data}(x_0) \to p_{noise}(x_1)$) 中，噪声的方差 $\sigma_t, (t \in [0, 1])$ 随着时间的推移而增加，且满足 $\sigma_0 = 0$，$\sigma_1 &amp;lt; 1$。VP 过程对应的逆过程 ($p_{noise}(x_1)\to p_{data}(x_0)$) 的概率路径为&lt;/p&gt;
&lt;p&gt;$$
p_t(x_t | z) = \mathcal{N}(x_t; \alpha_t z, (1 - \alpha_t^2) I_d), \quad z \sim p_{data}(z)
$$
其中 $\alpha_t = e^{-\frac{T(t)}{2}}$，$T(t) = \int_0^t \beta(s)ds$，$\beta(t)$ 是一个非负的、连续的噪声尺度函数。这个条件概率路径的均值和标准差分别为 $\mu_t(z) = \alpha_t z, \sigma_t = \sqrt{1 - \alpha_t^2}$，代入可以算得条件概率场为
$$
u^{target}(x_t, t | z) = \frac{\dot\alpha_t}{1 - \alpha_t^2} (z - \alpha_t x_t)
$$&lt;/p&gt;
&lt;h3&gt;3.3.2 VE 过程的条件向量场 (Variance Exploding Conditional Vector Field)&lt;/h3&gt;
&lt;p&gt;VE 过程是另一种常见的扩散过程，它的特点是在前向过程 ($p_{data}(x_0) \to p_{noise}(x_1)$) 中，噪声的方差 $\sigma_t, (t \in [0, 1])$ 随着时间的推移而增加，且满足 $\sigma_0 = 0$，$\sigma_1 \gg 1$。VE 过程对应的逆过程 ($p_{noise}(x_1)\to p_{data}(x_0)$) 的条件概率路径为
$$
p_t(x_t | z) = \mathcal{N}(x_t; z, \sigma_t^2 I_d), \quad z \sim p_{data}(z)
$$
因此，VE 过程的均值和标准差分别为 $\mu_t(z) = z, \sigma_t$，代入可以算得条件概率场为
$$
u^{target}(x_t, t | z) = \frac{\dot\sigma_t}{\sigma_t} (x_t - z)
$$&lt;/p&gt;
&lt;h3&gt;3.3.3 最优传输条件向量场 (Optimal Transport Conditional Vector Field)&lt;/h3&gt;
&lt;p&gt;最优传输 (Optimal Transport, OT) 过程是一种特殊的概率路径，它通过线性插值的方式连接起噪声分布和数据分布。OT 过程对应的条件概率路径为 ($p_{noise} \to p_{data}$)
$$
p_t(x_t | z) = \mathcal{N}(x_t; tz, (1 - t)^2 I_d), \quad z \sim p_{data}(z)
$$
因此，OT 过程的均值和标准差分别为 $\mu_t(z) = tz, \sigma_t = 1 - t$，代入可以算得条件概率场为
$$
u^{target}(x_t, t | z) = \frac{z - x_t}{1 - t}
$$&lt;/p&gt;
&lt;p&gt;可以发现，OT 过程的路径相较于 VP 和 VE 过程更加直接和线性，这使得它在某些应用中具有更好的性能。&lt;/p&gt;
&lt;h2&gt;3.4 训练及推理算法&lt;/h2&gt;
&lt;h3&gt;3.4.1 训练算法&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=5&amp;gt;Algorithm Flow Matching Training Procedure&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;训练数据集 $p_{data}(z)$，神经网络 $u_\theta(x_t, t)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;训练好的模型参数 $\theta^*$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;for&lt;/strong&gt; 数据集 $p_{data}$ 中的每个 mini-batch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;   取 mini-batch 中的样本 $z$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;   随机采样一个时间 $t \sim \text{Uniform}[0, 1]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   随机采集一个噪声 $\epsilon \sim \mathcal{N}(0, I_d)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;   计算噪声样本 $x_t = tz + (1 - t) \epsilon$ （通用形式为 $x_t = \mu_t(z) + \sigma_t \epsilon \sim p_t(x_t \vert z) \sim p_t(x_t \vert z)$）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;   计算条件速度场 $u^{target}(x_t, t \vert z) = \frac{z - x_t}{1 - t}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;   计算损失 $\mathcal{L}&lt;em&gt;{CFM}(\theta) = \left\Vert u&lt;/em&gt;\theta(x_t, t) - u^{target}(x_t, t \vert z) \right\Vert^2$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;   反向传播，更新参数 $\theta$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.4.2 推理算法&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=5&amp;gt;Algorithm Flow Matching Sampling Procedure&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;训练好的模型参数 $\theta^*$，先验分布 $p_{prior}(x_0)$，采样步数 $T$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;生成样本 $x_1 \sim p_{data}(x_1)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化 $x_0 \sim p_{prior}(x_0)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;设置时间步长 $\Delta t = \frac{1}{T}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;for&lt;/strong&gt; $i = 0, 1, \ldots, T - 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   计算当前时间 $t_i = \frac{i}{T}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;   计算速度场 $u_\theta(x_{t_i}, t_i)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;   更新样本 $x_{t_{i+1}} = x_{t_i} + u_\theta(x_{t_i}, t_i) \Delta t$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;返回 $x_1 = x_{t_{T-1}}$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>流模型 Chapter2——去噪扩散概率模型（DDPM）</title><link>https://adalovelemon.github.io/posts/content/coursenotes/generativeai/flowmodels/chapter2/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/generativeai/flowmodels/chapter2/</guid><pubDate>Tue, 07 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;DDPM（Denoising Diffusion Probabilistic Models）是一种基于扩散过程的生成模型。它通过逐步向数据中添加噪声来模拟数据的生成过程，然后学习一个逆向过程来去除噪声，从而生成新的数据样本。&lt;/p&gt;
&lt;h2&gt;2.1 扩散模型的正向过程&lt;/h2&gt;
&lt;h3&gt;2.1.1 扩散过程&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;物理启发&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;扩散过程是一种概率方法，它将&lt;strong&gt;复杂的数据分布&lt;/strong&gt;（例如图像）&lt;strong&gt;转换为一个更简单的分布&lt;/strong&gt;（例如高斯噪声）。受物理学概念的启发，该过程&lt;strong&gt;逐步向原始数据中注入少量噪声&lt;/strong&gt;，最终得到一个标准高斯分布 $\mathcal{N}(0, I_d)$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数学形式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数学上，给定一个数据分布 $q_0(x_0)$，我们在每一步逐步添加噪声 $\sqrt{\beta_t}\epsilon_t \sim \mathcal{N}(0, \beta_t I_d)$，其中我们将 $t$ 步后的新数据分布表示为 $q_t(x_t)$。这个过程可以用以下方程描述：&lt;/p&gt;
&lt;p&gt;$$
x_t = w_t x_{t-1} + \sqrt{\beta_t}\epsilon_t, \quad \epsilon_t\sim \mathcal{N}(0, I_d), \quad t= 1, 2 ,\ldots, T
$$
其中 $0 \lt w_t \le 1$，$\beta_t$ 是一个小的正常数，控制每一步添加的噪声量。&lt;/p&gt;
&lt;h3&gt;2.1.2 $w_t$ 的选取&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;稳态均值&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最终，我们希望达到一个状态，使得数据分布 $q_T(x_T)$ 近似于一个标准高斯分布 $\mathcal{N}(0, I_d)$。这对 $w_t$ 的选取提出了要求。当 $T \rightarrow \infty$ 时，变量 $x_T$ 的期望变为&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
E(x_T) &amp;amp;= E(w_T x_{T-1}) + \sqrt{\beta_T}E(\epsilon_T) = w_{T} E(x_{T-1}) \
&amp;amp;= w_Tw_{T-1} E(x_{T-2}) = \cdots = \prod_{t=1}^T w_t E(x_{0}) \le w_{max}^T E(x_{0})
\end{aligned}
$$
只要 $w_{max} = \max{w_t}_{t=1}^T &amp;lt; 1$，当 $T \rightarrow \infty$ 时，$x_T$ 的期望将指数级收敛到 0。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;稳态方差&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;至于方差，有
$$
\begin{aligned}
Var(x_T) &amp;amp;= Var(w_T x_{T-1}) + Var(\sqrt{\beta_T}\epsilon_T) \
&amp;amp;= w_T^2 Var(x_{T-1}) + \beta_T I_d \
&amp;amp;= w_T^2w_{T-1}^2 Var(x_{T-2}) + \beta_T I_d + w_T^2 \beta_{T-1} I_d \
&amp;amp;= \cdots \
&amp;amp;= \prod_{t=1}^T w_t^2 Var(x_{0}) + \beta_T I_d + w_T^2 \beta_{T-1} I_d + \cdots + w_T^2w_{T-1}^2 \cdots w_2^2 \beta_1 I_d
\end{aligned}
$$
这里，如果我们令 $w_t = \sqrt{1 - \beta_t}$，可以发现方差满足
$$
\begin{aligned}
Var(x_T) &amp;amp;= \prod_{t=1}^T (1 - \beta_t) Var(x_{0}) + \beta_T I_d + (1 - \beta_T) \beta_{T-1} I_d + \cdots + (1 - \beta_T)(1 - \beta_{T-1}) \cdots (1 - \beta_2) \beta_1 I_d\
&amp;amp;= \prod_{t=1}^T (1 - \beta_t) Var(x_{0}) + (1 - (1 - \beta_T) + (1 - \beta_T)\beta_{T-1} + \cdots + (1 - \beta_T)(1 - \beta_{T-1})\cdots(1 - \beta_2)) \beta_1 I_d\
&amp;amp;= \prod_{t=1}^T (1 - \beta_t) Var(x_{0}) + (1 - (1 - \beta_T)(1 - \beta_{T-1}) + (1 - \beta_T)(1 - \beta_{T-1})\beta_{T-2} + \cdots + (1 - \beta_T)(1 - \beta_{T-1})\cdots(1 - \beta_2)) \beta_1 I_d\
&amp;amp;= \prod_{t=1}^T (1 - \beta_t) Var(x_{0}) + [1 - \prod_{t=1}^T (1 - \beta_t)] I_d \
&amp;amp;\le \alpha_{max}^T Var(x_{0}) + [1 - \alpha_{min}^T] I_d
\end{aligned}
$$
只要 $\alpha_{max} = \max{1 - \beta_t}&lt;em&gt;{t=1}^T &amp;lt; 1$ 且 $0 &amp;lt; \alpha&lt;/em&gt;{min} = \min{1 - \beta_t}_{t=1}^T &amp;lt; 1$，当 $T \rightarrow \infty$ 时，$x_T$ 的方差将收敛到 1。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参数选取&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因此，从以上分析可以看出，&lt;strong&gt;为了达到最终的标准高斯分布&lt;/strong&gt;，我们可以选择参数
$$
w_t = \sqrt{1 - \beta_t}
$$
其中 $\beta_t$ 满足 $0 &amp;lt; \beta_t &amp;lt; 1$ 以确保 $w_t &amp;lt; 1$ 和 $1 - \beta_t &amp;lt; 1$。&lt;/p&gt;
&lt;h3&gt;2.1.3 初始分布假设&lt;/h3&gt;
&lt;p&gt;现在我们将扩散过程定义如下
$$
x_t = \sqrt{1 - \beta_t}x_{t-1} + \sqrt{\beta_t}\epsilon_t, \quad \epsilon_t\sim \mathcal{N}(0, I_d), \quad t= 1, 2 ,\ldots, T
$$&lt;/p&gt;
&lt;p&gt;根据前面的分析，我们证明了 $x_T$ 的均值和方差均符合标准高斯分布的要求。但是为了实现最终分布 $q_T(x_T)$ 收敛到 $\mathcal{N}(0, I_d)$，我们还需要做一个合理的假设。&lt;/p&gt;
&lt;p&gt;这里，我们假设&lt;strong&gt;初始数据分布 $q_0(x_0)$ 是一个高斯分布&lt;/strong&gt;，并且 $\epsilon_t$ 与 $x_t$ &lt;strong&gt;独立&lt;/strong&gt;。在这种条件下，根据高斯分布的性质，我们可以证明每一步的分布 $q_t(x_t)$ 也是一个高斯分布，这就可以保证最终 $q_T(x_T)$ 收敛到 $\mathcal{N}(0, I_d)$。&lt;/p&gt;
&lt;h3&gt;2.1.4 前向 Markovian 链&lt;/h3&gt;
&lt;p&gt;扩散过程是一步步分布变化的过程，我们可以用以下形式表示每一个时间步的分布&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
q(x_t | x_{t-1}) &amp;amp;= \mathcal{N}(x_t; \sqrt{1 - \beta_t}x_{t-1}, \beta_tI) \
q_t(x_t) &amp;amp;= \int q(x_t | x_{t-1}) q(x_{t-1} | x_{t-2}) \dots q(x_0) dx_{t-1}dx_{t-2}\dots dx_1dx_0 \
&amp;amp;= \int q(x_t, x_{t-1}, \dots, x_1, x_0) dx_{t-1}dx_{t-2}\dots dx_1dx_0 \
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;仔细观察，可以看出这个过程是一个马尔可夫链。当前变量 $x_t$ 只依赖于前一个变量 $x_{t-1}$ 和噪声 $\epsilon_t$，而不依赖于其他更早的历史状态。&lt;/p&gt;
&lt;p&gt;由上式，我们可以得到初始数据分布 $q_0(x_0)$ 和 $q_t(x_t)$ 的直接关系式，直接给出从 $x_0$ 到 $x_t$ 的单步转换。&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
q(x_t|x_0) &amp;amp;= \mathcal{N}(x_t; \sqrt{\bar\alpha_t} x_0, (1 - \bar\alpha_t)I_d) \
q_t(x_t) &amp;amp;= \int q(x_t | x_0) q_0(x_0) dx_0
\end{aligned}
$$
其中 $\alpha_t = 1 - \beta_t$ 且 $\bar\alpha_t = \prod_{s=1}^t\alpha_s$。&lt;/p&gt;
&lt;p&gt;现在，我们完成了扩散过程的建模。然而，不幸的是，我们需要很长的时间步长 $T$ 才能达到最终的高斯分布，计算时间成本大。为了加快收敛速度，直观上我们可以将 $\beta_t$ 设置得更大，更接近 1，这样每一步添加的噪声就更大。但事实上，我们并不能将 $\beta_t$ 设置得过大，否则数据分布 $q_t(x_t)$ 会与原始数据分布 $q_0(x_0)$ 偏离太多，这将使逆向扩散过程很难学习。&lt;/p&gt;
&lt;h2&gt;2.2 扩散模型的逆向过程&lt;/h2&gt;
&lt;h3&gt;2.2.1 逆向 Markovian 链&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;逆向过程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在去噪扩散概率模型（DDPM）中，逆向过程旨在将纯高斯噪声 $x_T$ 转换回真实的数据样本 $x_0$。从概念上讲，这是前向（加噪）过程的逆过程，同样被建模为一个马尔可夫链，一次一个时间步地逐步完成去噪。通过学习，我们就可以使用逆向过程逐步还原出服从真实数据分布 $q_0(x_0)$ 的样本。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数学形式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用数学语言来说，我们将以当前步状态 $x_t$ 为条件，前一步状态 $x_{t-1}$ 的&lt;strong&gt;条件分布&lt;/strong&gt;建模为
$$
\begin{aligned}
p_{\theta_t}(x_{t-1}|x_t) &amp;amp;= \mathcal{N}(x_{t-1}; \mu_{\theta_t}(x_t, t), \Sigma_{\theta_t}(x_t, t)) \
p_{\theta_t, \dots, \theta_T}(x_{t-1}) &amp;amp;= \int p_{\theta_t}(x_{t-1} | x_{t}) p_{\theta_{t+1}}(x_{t} | x_{t+1}) \dots p_{\theta_T}(x_{T-1}|x_T)p(x_T) dx_{t}dx_{t+1}\dots dx_T \
\end{aligned}
$$
其中我们使用 $p_{\theta_t}(x_{t-1}|x_t)$ 来表示学习到的条件分布。$\mu_{\theta_t}$ 和 $\Sigma_{\theta_t}$ 是均值和协方差，由一个参数为 $\theta_t$ 的神经网络模型参数化表示。由于神经网络具有强大的函数逼近能力，可以肯定这样的单步逆向分布是可以通过学习得到的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;简化假设&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了使问题易于处理，我们对每一步 $t$ 使用相同的参数 $\theta$，而不是 $T$ 个不同的参数 $\theta_t$。因此，逆向过程可以表示为 $p_\theta(x_{t-1} | x_t)$。
$$
\begin{aligned}
p_{\theta}(x_{t-1}|x_t) &amp;amp;= \mathcal{N}(x_{t-1}; \mu_{\theta}(x_t, t), \Sigma_{\theta}(x_t, t)) \
p_{\theta}(x_{t-1}) &amp;amp;= \int p_{\theta}(x_{t-1} | x_{t}) p_{\theta}(x_{t} | x_{t+1}) \dots p_{\theta}(x_{T-1}|x_T)p_\theta(x_T) dx_{t}dx_{t+1}\dots dx_T \
\end{aligned}
$$
其中 $p(x_T) = \mathcal{N}(0, I_d)$。&lt;/p&gt;
&lt;h3&gt;2.2.2 初始的目标函数：ELBO&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;从KLD开始&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;作为一个生成模型，DDPM 的最终目标是通过 $p_\theta(x_0)$ 来近似 $q_0(x_0)$。因此，我们使用 KL 散度来衡量 $q_0(x_0)$ 和 $p_\theta(x_0)$ 之间的差异，并试图最小化它。
$$
\min_\theta \mathbb{D}&lt;em&gt;\mathrm{{KL}}[q_0(x_0) | p&lt;/em&gt;\theta(x_0)] = \mathbb{E}&lt;em&gt;{x_0 \sim q_0(x_0)} \left[\log q_0(x_0) - \log p&lt;/em&gt;\theta(x_0) \right]
$$&lt;/p&gt;
&lt;p&gt;然而，直接优化这个目标是棘手的，因为它&lt;strong&gt;需要计算边缘分布&lt;/strong&gt; $p_\theta(x_0)$，这&lt;strong&gt;涉及到对所有可能的噪声路径进行积分&lt;/strong&gt;。为了解决这个问题，我们展开 KL 散度，并注意到由于 $\log q_0(x_0)$ 与 $\theta$ 无关
$$
\min_\theta \mathbb{D}&lt;em&gt;\mathrm{KL}[q_0(x_0) | p&lt;/em&gt;\theta(x_0)] = \min_\theta \mathbb{E}&lt;em&gt;{x_0 \sim q_0(x_0)} \left[\log q_0(x_0) - \log p&lt;/em&gt;\theta(x_0) \right] = \max_\theta \mathbb{E}&lt;em&gt;{x_0 \sim q_0(x_0)} \left[\log p&lt;/em&gt;\theta(x_0)\right]
$$&lt;/p&gt;
&lt;p&gt;使用 Jensen 不等式推导出一个可处理的下界
$$
\begin{aligned}
\log p_\theta(x_0) &amp;amp;= \log \int p_\theta(x_0, x_1, \dots, x_T) dx_1\dots dx_T \
&amp;amp;= \log \int \frac{p_\theta(x_0, x_1, \dots, x_T)}{q(x_1, \dots, x_T | x_0)}q(x_1, \dots, x_T | x_0) dx_1\dots dx_T \
&amp;amp;= \log \mathbb{E}&lt;em&gt;{x_1, \dots, x_T \sim q(x_1, \dots, x_T|x_0)} \left[ \frac{p&lt;/em&gt;\theta(x_0, x_1, \dots, x_T)}{q(x_1, \dots, x_T | x_0)} \right] \
&amp;amp;\geq \mathbb{E}&lt;em&gt;{x_1, \dots, x_T \sim q(x_1, \dots, x_T|x_0)} \left[ \log \frac{p&lt;/em&gt;\theta(x_0, x_1, \dots, x_T)}{q(x_1, \dots, x_T | x_0)}\right]
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;得到 ELBO&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;定义
$$
\mathcal{L}&lt;em&gt;{\text{ELBO}} =  \mathbb{E}&lt;/em&gt;{x_0, x_1, \dots, x_T \sim q(x_0, x_1, \dots, x_T)} \left[ \log \frac{p_\theta(x_0, x_1, \dots, x_T)}{q(x_1, \dots, x_T | x_0)}\right]
$$
通过最大化&lt;strong&gt;证据下界（ELBO）&lt;/strong&gt; $\mathcal{L}&lt;em&gt;{\text{ELBO}}$，我们可以间接优化 $q_0(x_0)$ 和 $p&lt;/em&gt;\theta(x_0)$ 之间的 KL 散度&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\max_\theta \mathcal{L}&lt;em&gt;{\text{ELBO}} &amp;amp;= \mathbb{E}&lt;/em&gt;{q(x_0, x_1, \dots, x_T)} \left[ \log \frac{p_\theta(x_0, x_1, \dots, x_T)}{q(x_1, \dots, x_T | x_0)}\right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{q(x_0, x_1, \dots, x_T)} \left[ \log \frac{p(x_T) \prod&lt;/em&gt;{t=1}^T p_\theta(x_{t-1}|x_t)}{\prod_{t=1}^T q(x_t | x_{t-1})}\right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{q(x_0, x_1, \dots, x_T)} \left[ \log p(x_T) + \sum&lt;/em&gt;{t=1}^T \log p_\theta(x_{t-1}|x_t) - \sum_{t=1}^T \log q(x_t | x_{t-1})\right]
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;现在我们使用一个关键技巧，通过应用缩放和分组来重新排列各项。关键步骤是使用后验分布 $q(x_{t-1}|x_t, x_0)$ 重写求和项。&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\mathcal{L}&lt;em&gt;{\text{ELBO}} &amp;amp;= \mathbb{E}&lt;/em&gt;{q(x_0, x_1, \dots, x_T)} \left[ \log p(x_T) + \sum_{t=1}^T \log p_\theta(x_{t-1}|x_t) - \sum_{t=1}^T \log q(x_t | x_{t-1})\right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{q(x_0, x_1, \dots, x_T)} \left[ \log p(x_T) + \log p&lt;/em&gt;\theta(x_0|x_1) + \sum_{t=2}^T \log p_\theta(x_{t-1}|x_t) - \log q(x_1 | x_0) - \sum_{t=2}^T \log q(x_t | x_{t-1})\right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{q(x_0, x_1, \dots, x_T)} \left[ \log p(x_T) + \log p&lt;/em&gt;\theta(x_0|x_1) + \sum_{t=2}^T \log p_\theta(x_{t-1}|x_t) - \log q(x_1 | x_0) - \sum_{t=2}^T \log \frac{q(x_{t-1} | x_t, x_0) q(x_t | x_0)}{q(x_{t-1} | x_0)}\right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{q(x_0, x_1)} [\log p&lt;/em&gt;\theta(x_0|x_1)] - \mathbb{E}&lt;em&gt;{q(x_0, x_1)} [\log q(x_1 | x_0)] + \mathbb{E}&lt;/em&gt;{\mathcal{N}(0, I_d)} [\log p(x_T)] + \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x&lt;/em&gt;{t-1}, x_t)} [\log p_\theta(x_{t-1}|x_t)] - \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x&lt;/em&gt;{t-1}, x_t, x_0)} [\log q(x_{t-1} | x_t, x_0)] - \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x&lt;/em&gt;{0}, x_t)} [\log q(x_t | x_0)] + \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x_0, x&lt;/em&gt;{t-1})} [\log q(x_{t-1} | x_0)] \
&amp;amp;= \mathbb{E}&lt;em&gt;{q(x_0, x_1)} [\log p&lt;/em&gt;\theta(x_0|x_1)] - \mathbb{E}&lt;em&gt;{q(x_0, x_1)} [\log q(x_1 | x_0)] + \mathbb{E}&lt;/em&gt;{\mathcal{N}(0, I_d)} [\log p(x_T)] + \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x&lt;/em&gt;{t-1}, x_t)} [\log p_\theta(x_{t-1}|x_t)] - \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x&lt;/em&gt;{t-1}, x_t, x_0)} [\log q(x_{t-1} | x_t, x_0)] - \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x&lt;/em&gt;{0}, x_t)} [\log q(x_t | x_0)] + \sum_{t=1}^{T-1} \mathbb{E}&lt;em&gt;{q(x_0, x&lt;/em&gt;{t})} [\log q(x_{t} | x_0)] \
&amp;amp;= \mathbb{E}&lt;em&gt;{q(x_0, x_1)} [\log p&lt;/em&gt;\theta(x_0|x_1)] - \mathbb{E}&lt;em&gt;{q(x_0, x_1)} [\log q(x_1 | x_0)] + \mathbb{E}&lt;/em&gt;{\mathcal{N}(0, I_d)} [\log p(x_T)] + \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x&lt;/em&gt;{t-1}, x_t)} [\log p_\theta(x_{t-1}|x_t)] - \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x&lt;/em&gt;{t-1}, x_t, x_0)} [\log q(x_{t-1} | x_t, x_0)] -\mathbb{E}&lt;em&gt;{q(x_0, x_T)} [\log q(x_T | x_0)] + \mathbb{E}&lt;/em&gt;{q(x_0, x_1)} [\log q(x_1 | x_0)] \
&amp;amp;= \mathbb{E}&lt;em&gt;{q(x_0, x_1)} [\log p&lt;/em&gt;\theta(x_0|x_1)] + \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x_0, x&lt;/em&gt;{t-1}, x_t)} \left[\log \frac{p_\theta(x_{t-1}|x_t)}{q(x_{t-1} | x_t, x_0)}\right] + \mathbb{E}_{q(x_0, x_T)} \left[\log \frac{p(x_T)}{q(x_T | x_0)}\right] \&lt;/p&gt;
&lt;p&gt;&amp;amp;= \mathbb{E}&lt;em&gt;{q(x_0, x_1)} [\log p&lt;/em&gt;\theta(x_0|x_1)] - \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x_0, x_t)} \left[\mathbb{D}&lt;/em&gt;{\text{KL}}[q(x_{t-1}|x_t, x_0) | p_\theta(x_{t-1}|x_t)]\right] - \mathbb{E}&lt;em&gt;{q(x_0)} \left[\mathbb{D}&lt;/em&gt;{\text{KL}}[q(x_T|x_0) | p(x_T)]\right]
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;这里目标函数的分解揭示了以下的物理意义&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;重构项&lt;/strong&gt;: $\mathbb{E}&lt;em&gt;{q(x_0, x_1)} [\log p&lt;/em&gt;\theta(x_0|x_1)]$ - 给定 $x_1$ 重构 $x_0$ 的对数似然函数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;去噪项&lt;/strong&gt;: $\sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x_0, x_t)} [\mathbb{D}&lt;/em&gt;{\text{KL}}(q(x_{t-1}|x_t, x_0) | p_\theta(x_{t-1}|x_t))]$ - 真实后验与学习到的先验之间的 KL 散度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;先验匹配项&lt;/strong&gt;: $\mathbb{E}&lt;em&gt;{q(x_0)} [\mathbb{D}&lt;/em&gt;{\text{KL}}(q(x_T|x_0) | p(x_T))]$ - 确保最终分布与标准高斯分布匹配。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;$$
\begin{aligned}
\mathcal{L}&lt;em&gt;{\text{ELBO}} &amp;amp;= \mathbb{E}&lt;/em&gt;{q(x_0, x_1)} [\log p_\theta(x_0|x_1)] - \sum_{t=2}^T \mathbb{E}&lt;em&gt;{q(x_0, x_t)} \left[\mathbb{D}&lt;/em&gt;{\text{KL}}[q(x_{t-1}|x_t, x_0) | p_\theta(x_{t-1}|x_t)]\right] - \mathbb{E}&lt;em&gt;{q(x_0)} \left[\mathbb{D}&lt;/em&gt;{\text{KL}}[q(x_T|x_0) | p(x_T)]\right]
\end{aligned}
$$&lt;/p&gt;
&lt;h3&gt;2.2.3 为什么使用真实后验？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题：为什么我们使用包含 $x_0$ 的真实后验&lt;/strong&gt; $q(x_{t-1}|x_t, x_0)$，&lt;strong&gt;而不是标准的逆向条件分布&lt;/strong&gt; $q(x_{t-1}|x_t)$？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;答：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(i) 损失函数&lt;/strong&gt;：在 ELBO 中，我们用于逆向过程的分布是真实后验 $q(x_{t-1}|x_t, x_0)$，而不是逆向条件分布 $q(x_{t-1}|x_t)$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(ii) 闭式表达式&lt;/strong&gt;：因为真实后验
$$
q(x_{t-1}|x_t, x_0) = \mathcal{N}\left(x_{t-1}; \tilde{\mu}&lt;em&gt;t(x_t, x_0), \tilde{\beta}&lt;em&gt;t I_d\right)
$$
具有已知的、解析的均值和方差。相比之下，逆向条件分布
$$
q(x&lt;/em&gt;{t-1}\mid x_t) = \int q(x&lt;/em&gt;{t-1}\mid x_t,x_0),q(x_0\mid x_t),dx_0
$$
没有闭式解，因此无法明确写出。&lt;/p&gt;
&lt;p&gt;使用真实后验 $q(x_{t-1}|x_t, x_0)$ 作为我们学习的目标 $p_\theta(x_{t-1}|x_t)$ 是一个非常聪明的选择。虽然 $p_\theta$ 在去噪时无法获取真实的 $x_0$，但通过在训练时让它模仿以 $x_0$ 为条件的“理想”去噪步骤，模型就能学会如何有效地逆转扩散过程。本质上，我们给了模型一个“答案”，让它学习如何从带噪的 $x_t$ 中猜出这个答案。&lt;/p&gt;
&lt;h3&gt;2.2.4 真实后验的推导&lt;/h3&gt;
&lt;p&gt;最后，我们需要推导&lt;strong&gt;真实后验&lt;/strong&gt; $q(x_{t-1}|x_t, x_0)$ 的方程。根据贝叶斯定理
$$
q(x_{t-1}|x_t, x_0) = \frac{q(x_t|x_{t-1}, x_0) q(x_{t-1}|x_0)}{q(x_t|x_0)}
$$&lt;/p&gt;
&lt;p&gt;由于前向过程的马尔可夫性质，$x_t$ 仅依赖于 $x_{t-1}$ 而不直接依赖于 $x_0$
$$
q(x_t|x_{t-1}, x_0) = q(x_t|x_{t-1})
$$&lt;/p&gt;
&lt;p&gt;因此
$$
q(x_{t-1}|x_t, x_0) = \frac{q(x_t|x_{t-1}) q(x_{t-1}|x_0)}{q(x_t|x_0)}
$$&lt;/p&gt;
&lt;p&gt;在前向过程中，我们已知&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$q(x_t|x_{t-1}) = \mathcal{N}(x_t; \sqrt{\alpha_t} x_{t-1}, \beta_t I_d)$&lt;/li&gt;
&lt;li&gt;$q(x_{t-1}|x_0) = \mathcal{N}(x_{t-1}; \sqrt{\bar\alpha_{t-1}} x_0, (1-\bar\alpha_{t-1}) I_d)$&lt;/li&gt;
&lt;li&gt;$q(x_t|x_0) = \mathcal{N}(x_t; \sqrt{\bar\alpha_t} x_0, (1-\bar\alpha_t) I_d)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;取对数并关注涉及 $x_{t-1}$ 的项
$$
\begin{aligned}
\log q(x_{t-1}|x_t, x_0) &amp;amp;= \log q(x_t|x_{t-1}) + \log q(x_{t-1}|x_0) - \log q(x_t|x_0) + \text{const}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;展开高斯项&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\log q(x_t|x_{t-1}) &amp;amp;= -\frac{1}{2\beta_t} |x_t - \sqrt{\alpha_t} x_{t-1}|^2 + \text{const} \
\log q(x_{t-1}|x_0) &amp;amp;= -\frac{1}{2(1-\bar\alpha_{t-1})} |x_{t-1} - \sqrt{\bar\alpha_{t-1}} x_0|^2 + \text{const} \
\log q(x_t|x_0) &amp;amp;= -\frac{1}{2(1-\bar\alpha_t)} |x_t - \sqrt{\bar\alpha_t} x_0|^2 + \text{const}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;展开二次项并收集 $x_{t-1}^2$、$x_{t-1}$ 和常数的系数&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(i) 对于&lt;/em&gt; $x_{t-1}^2$ &lt;em&gt;的系数&lt;/em&gt;
$$
-\frac{\alpha_t}{2\beta_t} - \frac{1}{2(1-\bar\alpha_{t-1})} = -\frac{1}{2} \left( \frac{\alpha_t}{\beta_t} + \frac{1}{1-\bar\alpha_{t-1}} \right)
$$&lt;/p&gt;
&lt;p&gt;由于 $\beta_t = 1 - \alpha_t$ 和 $\bar\alpha_t = \alpha_t \bar\alpha_{t-1}$：
$$
\frac{\alpha_t}{\beta_t} + \frac{1}{1-\bar\alpha_{t-1}} = \frac{\alpha_t}{1-\alpha_t} + \frac{1}{1-\bar\alpha_{t-1}} = \frac{1-\bar\alpha_t}{\beta_t(1-\bar\alpha_{t-1})}
$$&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(ii) 对于&lt;/em&gt; $x_{t-1}$ &lt;em&gt;的系数&lt;/em&gt;
$$
\frac{\sqrt{\alpha_t} x_t}{\beta_t} + \frac{\sqrt{\bar\alpha_{t-1}} x_0}{1-\bar\alpha_{t-1}}
$$&lt;/p&gt;
&lt;p&gt;配方后，我们得到一个高斯分布，其均值和方差为&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方差：&lt;/strong&gt;
$$
\tilde{\beta}&lt;em&gt;t = \frac{1-\bar\alpha&lt;/em&gt;{t-1}}{1-\bar\alpha_t} \beta_t
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;均值：&lt;/strong&gt;
$$
\tilde{\mu}&lt;em&gt;t(x_t, x_0) = \frac{\sqrt{\alpha_t}(1-\bar\alpha&lt;/em&gt;{t-1})}{1-\bar\alpha_t} x_t + \frac{\sqrt{\bar\alpha_{t-1}}\beta_t}{1-\bar\alpha_t} x_0
$$&lt;/p&gt;
&lt;p&gt;因此，真实后验分布的闭式表达式为
$$
q(x_{t-1}|x_t, x_0) = \mathcal{N}\left(x_{t-1}; \tilde{\mu}_t(x_t, x_0), \tilde{\beta}_t I_d\right)
$$&lt;/p&gt;
&lt;p&gt;其中
$$
\begin{aligned}
\tilde{\mu}&lt;em&gt;t(x_t, x_0) &amp;amp;= \frac{\sqrt{\alpha_t}(1-\bar\alpha&lt;/em&gt;{t-1})}{1-\bar\alpha_t} x_t + \frac{\sqrt{\bar\alpha_{t-1}}\beta_t}{1-\bar\alpha_t} x_0 \
\tilde{\beta}&lt;em&gt;t &amp;amp;= \frac{1-\bar\alpha&lt;/em&gt;{t-1}}{1-\bar\alpha_t} \beta_t
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;这个闭式表达式至关重要，因为它允许我们解析地计算 ELBO 中的 KL 散度，而无需通过采样来估计。&lt;/p&gt;
&lt;h3&gt;2.2.5 重参数化技巧&lt;/h3&gt;
&lt;p&gt;ELBO 公式提供了理论基础，但由于复杂的 KL 散度项，直接优化仍然具有挑战性。DDPM 的关键洞见是使用&lt;strong&gt;重参数化技巧&lt;/strong&gt;，将去噪目标转换为一个简单的噪声预测任务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;噪声预测&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们可以&lt;strong&gt;重参数化逆向分布&lt;/strong&gt; $p_\theta(x_{t-1}|x_t)$ 的均值 $\mu_\theta(x_t, t)$，使其预测在前向过程中添加的噪声 $\epsilon$，而不是直接学习均值。&lt;/p&gt;
&lt;p&gt;回想一下，给定 $x_t$ 和 $x_0$，我们可以将真实后验 $q(x_{t-1}|x_t, x_0)$ 的均值表示为
$$
\tilde \mu_t(x_t, x_0) = \frac{\sqrt{\alpha_t}(1 - \bar\alpha_{t-1})}{1 - \bar\alpha_t} x_t + \frac{\sqrt{\bar\alpha_{t-1}}\beta_t}{1 - \bar\alpha_t} x_0
$$&lt;/p&gt;
&lt;p&gt;我们可以将前向过程方程 $x_0 = \frac{x_t - \sqrt{1 - \bar\alpha_t}\epsilon}{\sqrt{\bar\alpha_t}}$ 代入后验均值中&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\tilde \mu_t(x_t, x_0) &amp;amp;= \frac{\sqrt{\alpha_t}(1 - \bar\alpha_{t-1})}{1 - \bar\alpha_t} x_t + \frac{\sqrt{\bar\alpha_{t-1}}\beta_t}{1 - \bar\alpha_t} \cdot \frac{x_t - \sqrt{1 - \bar\alpha_t}\epsilon}{\sqrt{\bar\alpha_t}} \
&amp;amp;= \frac{1}{\sqrt{\alpha_t}} \left( x_t - \frac{\beta_t}{\sqrt{1 - \bar\alpha_t}} \epsilon \right)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;这表明我们可以&lt;strong&gt;将我们学习到的均值参数化&lt;/strong&gt;为
$$
\mu_\theta(x_t, t) = \frac{1}{\sqrt{\alpha_t}} \left( x_t - \frac{\beta_t}{\sqrt{1 - \bar\alpha_t}} \epsilon_\theta(x_t, t) \right)
$$
其中 $\epsilon_\theta(x_t, t)$ 是一个预测噪声的神经网络。由此，逆向扩散过程可以表示为
$$
p_\theta(x_{t-1}|x_t) = \mathcal{N}\left(x_{t-1}; \frac{1}{\sqrt{\alpha_t}} \left( x_t - \frac{\beta_t}{\sqrt{1 - \bar\alpha_t}} \epsilon_\theta(x_t, t) \right), \tilde{\beta}_t I_d\right)
$$
这里我们直接使用从真实后验配置的方差 $\tilde{\beta}&lt;em&gt;t = \frac{1-\bar\alpha&lt;/em&gt;{t-1}}{1-\bar\alpha_t} \beta_t$。&lt;/p&gt;
&lt;h3&gt;2.2.6 简化的损失函数&lt;/h3&gt;
&lt;p&gt;通过这种参数化，去噪项中复杂的 KL 散度可以被简化。在我们的例子中，两个分布具有相同的协方差矩阵 $\tilde{\beta}_t I_d$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$q(x_{t-1}|x_t, x_0) = \mathcal{N}(x_{t-1}; \tilde{\mu}_t(x_t, x_0), \tilde{\beta}_t I_d)$&lt;/li&gt;
&lt;li&gt;$p_\theta(x_{t-1}|x_t) = \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t), \tilde{\beta}_t I_d)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于 $\Sigma_1 = \Sigma_2 = \tilde{\beta}_t I_d$，我们有&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\log \frac{|\Sigma_2|}{|\Sigma_1|} = 0$&lt;/li&gt;
&lt;li&gt;$\text{tr}(\Sigma_2^{-1}\Sigma_1) = d$ (其中 $d$ 是维度)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，
$$
\mathbb{D}&lt;em&gt;{\text{KL}}[q(x&lt;/em&gt;{t-1}|x_t, x_0) | p_\theta(x_{t-1}|x_t)] = \frac{1}{2\tilde{\beta}_t} \left| \tilde{\mu}&lt;em&gt;t(x_t, x_0) - \mu&lt;/em&gt;\theta(x_t, t) \right|^2 = \frac{1}{2\tilde{\beta}&lt;em&gt;t} \cdot \frac{\beta_t^2}{\alpha_t(1 - \bar\alpha_t)} \left| \epsilon - \epsilon&lt;/em&gt;\theta(x_t, t) \right|^2
$$&lt;/p&gt;
&lt;p&gt;将 $\tilde{\beta}&lt;em&gt;t = \frac{1-\bar\alpha&lt;/em&gt;{t-1}}{1-\bar\alpha_t} \beta_t$ 代入
$$
\begin{aligned}
\mathbb{D}&lt;em&gt;{\text{KL}}[q(x&lt;/em&gt;{t-1}|x_t, x_0) | p_\theta(x_{t-1}|x_t)] &amp;amp;= \frac{\beta_t^2}{2 \cdot \frac{1-\bar\alpha_{t-1}}{1-\bar\alpha_t} \beta_t \cdot \alpha_t(1 - \bar\alpha_t)} \left| \epsilon - \epsilon_\theta(x_t, t) \right|^2 \
&amp;amp;= \frac{\beta_t^2 \cdot (1-\bar\alpha_t)}{2\beta_t \cdot (1-\bar\alpha_{t-1}) \cdot \alpha_t(1 - \bar\alpha_t)} \left| \epsilon - \epsilon_\theta(x_t, t) \right|^2 \
&amp;amp;= \frac{\beta_t}{2(1-\bar\alpha_{t-1}) \alpha_t} \left| \epsilon - \epsilon_\theta(x_t, t) \right|^2
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;这给了我们加权的噪声预测损失。&lt;strong&gt;经过大量的实验，DDPM 的作者发现，去掉这个加权项（将其设置为常数）实际上能提高性能，从而得到简单的损失函数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}&lt;em&gt;{\text{simple}} = \mathbb{E}&lt;/em&gt;{t \sim \text{Uniform}(1,T), x_0 \sim q(x_0), \epsilon \sim \mathcal{N}(0,I_d)} \left[ \left| \epsilon - \epsilon_\theta(\sqrt{\bar\alpha_t} x_0 + \sqrt{1 - \bar\alpha_t} \epsilon, t) \right|^2 \right]
$$
其中 $x_t = \sqrt{\bar\alpha_t} x_0 + \sqrt{1 - \bar\alpha_t} \epsilon$。&lt;/p&gt;
&lt;p&gt;这个公式将复杂的变分优化问题转化为一个直接的去噪任务：&lt;strong&gt;给定一个带噪图像 $x_t$ 和时间步 $t$，预测被添加的噪声 $\epsilon$&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;2.2.7 训练算法&lt;/h3&gt;
&lt;p&gt;得到简化的损失函数后，训练过程也就变得异常简单了&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;采样&lt;/strong&gt;一个随机时间步 $t \sim \text{Uniform}(1, T)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;采样&lt;/strong&gt;噪声 $\epsilon \sim \mathcal{N}(0, I_d)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创建&lt;/strong&gt;带噪图像 $x_t = \sqrt{\bar\alpha_t} x_0 + \sqrt{1 - \bar\alpha_t} \epsilon$&lt;/li&gt;
&lt;li&gt;使用神经网络&lt;strong&gt;预测&lt;/strong&gt;噪声 $\epsilon_\theta(x_t, t)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算&lt;/strong&gt;损失 $\left| \epsilon - \epsilon_\theta(x_t, t) \right|^2$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种优雅的简化解释了为什么 DDPM 既有理论上的原则性（通过 ELBO 推导），又在实践中有效（通过简单的噪声预测目标）。&lt;/p&gt;
&lt;h2&gt;2.3 DDPM 的 SDE&lt;/h2&gt;
&lt;h3&gt;2.3.1 前向过程的 SDE&lt;/h3&gt;
&lt;p&gt;已知离散表达式中扩散过程的公式为
$$
x_i = \sqrt{1 - \beta_i} x_{i-1} + \sqrt{\beta_i} \epsilon_i, \ \epsilon_i \sim \mathcal{N}(0, I_d), \quad i = 1, \cdots, T
$$
令 $\tilde x_t = x_i, \tilde x_{t - \frac{1}{T}} = x_{i-1}, \tilde \epsilon_t = \epsilon_i, \tilde\beta_t = T\beta_i$，由此得到
$$
\tilde x_t = \sqrt{1 - \frac{1}{T}\tilde\beta_t}\tilde x_{t-\frac{1}{T}} + \sqrt{\frac{1}{T}\tilde\beta_t}\tilde\epsilon_t, \ \epsilon_t \sim \mathcal{N}(0, I_d), \quad t = \frac{1}{T}, \frac{2}{T}, \cdots, 1
$$&lt;/p&gt;
&lt;p&gt;当 $\tilde\beta_t \to 0$ 时，使用泰勒展开，有
$$
\tilde x_t = (1 - \frac{1}{2T}\tilde\beta_t)\tilde x_{t-\frac{1}{T}} + \sqrt{\frac{1}{T}\tilde\beta_t}\tilde\epsilon_t
$$&lt;/p&gt;
&lt;p&gt;现在我们记 $\Delta t = \frac{1}{T}$，得到
$$
\tilde x_t = (1 - \frac{\Delta t}{2}\tilde\beta_t)\tilde x_{t-\Delta t} + \sqrt{\Delta t\tilde\beta_t}\tilde\epsilon_t
$$
用 $dw = \epsilon\sqrt{dt}$ 替换后一项，其中 $dw$ 是维纳过程，并令 $\Delta t \to 0$ (即 $T \to \infty$)，有
$$
\begin{aligned}
&amp;amp;\tilde x_t - \tilde x_{t-\Delta t} =  - \frac{\Delta t}{2}\tilde\beta_t\tilde x_{t-\Delta t} + \sqrt{\Delta t\tilde\beta_t}\tilde\epsilon_t \
&amp;amp;\Delta \tilde x_t = - \frac{\Delta t}{2}\tilde\beta_t\tilde x_{t-\Delta t} + \sqrt{\tilde\beta_t}d\tilde w \
\end{aligned}
$$
更换符号，得到最终前向扩散过程的 SDE 为
$$
\boxed{
dx_t = - \frac{\beta_t}{2} x_{t}dt + \sqrt{\beta_t}dw \
}
$$&lt;/p&gt;
&lt;h3&gt;2.3.2 逆向过程的 SDE&lt;/h3&gt;
&lt;p&gt;根据 Anderson 定理，可以得到逆向过程的 SDE 为
$$
\boxed{
dx_t = \left[- \frac{\beta_t}{2} x_{t} - \beta_t \nabla_{x_t} \log p_t(x_t)\right]dt + \sqrt{\beta_t}d\bar w \
}
$$&lt;/p&gt;
&lt;h3&gt;2.3.3 连续化 DDPM&lt;/h3&gt;
&lt;p&gt;当采用 DDPM 连续化后的 SDE 进行建模时，可以采用后续章节中介绍的动态分数匹配模型方法，利用神经网络 $s_\theta(x_t, t)$对 $\nabla_x \log p_t(x_t)$ 进行建模，从而利用模拟逆向 SDE 的方式进行采样。
$$
\frac{dx_t}{dt} = - \frac{\beta_t}{2} x_{t} - \beta_t s_\theta(x_t, t) + \sqrt{\beta_t}d\bar w
$$&lt;/p&gt;
</content:encoded></item><item><title>流模型 Chapter1——基础概念</title><link>https://adalovelemon.github.io/posts/content/coursenotes/generativeai/flowmodels/chapter1/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/generativeai/flowmodels/chapter1/</guid><pubDate>Tue, 30 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;流模型的基础概念&lt;/p&gt;
&lt;h2&gt;1.1 流模型与扩散模型&lt;/h2&gt;
&lt;h3&gt;1.1.1 定义&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;流模型:&lt;/strong&gt; 流模型是指通过常微分方程(ODE)描述物质在空间中随时间演化的确定性动力学模型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扩散模型:&lt;/strong&gt; 扩散模型是通过随机微分方程(SDE)描述物质在空间中随机扩散的模型，考虑了确定性漂移和随机波动。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.1.2 数学描述&lt;/h3&gt;
&lt;p&gt;流模型和扩散模型都包含一个确定性的速度向量场分量。区别在于流模型仅包含该分量，而扩散模型还包含一个随机扩散分量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;速度向量场&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设有速度向量场 $u_t(x)$，定义为
$$
u: \mathbb{R}^d \times \mathbb{R} \rightarrow \mathbb{R}^d, \quad (x, t) \mapsto u_t(x)
$$
其中 $d$ 是空间维度，$t$ 是时间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方程形式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于流模型，通常有以下形式的ODE方程
$$
dX_t = u_t(X_t) dt, \quad X_0 = x_0
$$&lt;/p&gt;
&lt;p&gt;对于扩散模型，通常有以下形式的SDE方程
$$
dX_t = u_t(X_t) dt + \sigma_t dW_t, \quad X_0 = x_0
$$
其中 $\sigma_t \in \mathbb{R}^{d \times k}$ 是扩散系数矩阵，$dW_t \in \mathbb{R}^k$ 是标准维纳过程，$u_t(\cdot)$ 是速度场，$x_0$ 是初始位置，$X_t$ 是在时间 $t$ 的位置随机向量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;轨迹&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个初始位置 $x_0$，一条轨迹是指在速度向量场 $u_t$ 作用下，粒子从 $x_0$ 开始随时间演化的路径。一条轨迹对应于一个从特定的初始位置 $x_0$ 出发的ODE或SDE解。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;流函数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;流函数(或称为流映射) $\psi_t(x_0)$ 给出了从初始位置 $x_0$ (这里 $x_0$ 是函数的自变量)出发，在时间 $t$ 时的位置，它包含了所有可能轨迹的信息。对于流模型，流函数满足
$$
\frac{d}{dt} \psi_t(x_0) = u_t(\psi_t(x_0)), \quad \psi_0(x_0) = x_0
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;维纳过程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;维纳过程是一个重要的随机过程，通常用来描述随机波动。它可以被定义为一个连续时间的随机过程 $W_t$，满足以下性质：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$W_0 = 0$，即初始位置为零。&lt;/li&gt;
&lt;li&gt;$W_t$ 具有独立增量，即对于任意时间点 $0 \leq t_1 &amp;lt; t_2 &amp;lt; \cdots &amp;lt; t_n$，增量 $W_{t_2}-W_{t_1}, W_{t_3}-W_{t_2}, \ldots, W_{t_n}-W_{t_{n-1}}$ 相互独立。&lt;/li&gt;
&lt;li&gt;$W_t$ 的增量服从正态分布，即 $W_t - W_s \sim \mathcal{N}(0, t-s)$，对于 $t &amp;gt; s$。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;纯扩散过程（无漂移）的动力学方程为
$$
dX_t = \sigma_t dW_t
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$dW_t$ 是什么？如何在数学公式中计算？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$dW_t$ 是维纳过程的无穷小增量，表示在时间 $t$ 的瞬时变化。严格数学上，$dW_t$ 是随机微分方程中的随机积分记号。满足
$$
\mathbb{E}[dW_t] = 0, \quad \text{Var}(dW_t) = dt
$$
即
$$
dW_t = \epsilon \sqrt{dt}, \quad \epsilon \sim \mathcal{N}(0, I)
$$&lt;/p&gt;
&lt;p&gt;在数值计算中（如Euler-Maruyama方法），$dW_t$ 通过离散化近似为
$$
\Delta W_t = \epsilon \sqrt{\Delta t}, \quad \epsilon \sim \mathcal{N}(0, I)
$$
其中 $\Delta t$ 是时间步长，$\epsilon$ 是标准正态分布的随机向量。&lt;/p&gt;
&lt;h2&gt;1.2 概率分布函数的演化&lt;/h2&gt;
&lt;h3&gt;1.2.1 随机过程&lt;/h3&gt;
&lt;p&gt;假设有若干粒子在动力学方程 $dX_t = u_t(X_t) dt + \sigma_t dW_t$ 的作用下演化，且这些粒子在初始时刻 $t = 0$ 的位置服从初始分布 $x_0 \sim p_0(x_0)$。随着时间的推移，这些粒子的位置分布会发生变化，形成一个时间依赖的概率分布 $p_t(x)$。&lt;/p&gt;
&lt;p&gt;考虑其中一个粒子的运动轨迹 $X_t$。根据上述分析，位置 $X_t$ 是一个随机变量，其分布由初始分布 $p_0(x_0)$ 和动力学方程共同决定。也就是说，$X_t \sim p_t(x_t)$ 是一个随机过程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;概率流函数 (Probability Flow)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;定义概率流函数为
$$
J_t(x) = u_t(x) p_t(x)
$$
其中 $u_t(x) \in \mathbb{R}^d$ 是速度场，$p_t(x) \in \mathbb{R}$ 是时间 $t$ 的概率分布。&lt;/p&gt;
&lt;p&gt;需要注意的是，概率流函数 $J_t(x)$ 是一个向量值函数，其每个分量表示在位置 $x$ 处，单位时间内通过该位置的概率质量流动的速率。与前述的流函数 $\psi_t(x_0)$ 不同。&lt;/p&gt;
&lt;h3&gt;1.2.2 概率分布的演化方程&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Liouville 方程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ODE 方程的概率分布 $p_t(x)$ 的演化可以通过偏微分方程来描述。对于满足动力学方程
$$
dX_t = u_t(X_t) dt
$$
的流模型，其概率分布 $p_t(x)$ 满足 Liouville 方程
$$
\frac{\partial p_t(x)}{\partial t} + \nabla_x \cdot (u_t(x) p_t(x)) = 0
$$
其中 $\nabla_x \cdot = \frac{\partial}{\partial x_1} + \frac{\partial}{\partial x_2} + \cdots + \frac{\partial}{\partial x_d}$ 表示散度算子。Liouville 的推导见 &lt;a href=&quot;../appendix/#a1-liouville-%E6%96%B9%E7%A8%8B%E7%9A%84%E6%8E%A8%E5%AF%BC&quot;&gt;附录 A.1&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;引入概率流函数 $J_t(x) = u_t(x) p_t(x)$，Liouville 方程可以改写为
$$
\frac{\partial p_t(x)}{\partial t} + \nabla_x \cdot J_t(x) = 0
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fokker-Planck 方程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Fokker-Planck 方程描述了随机过程的概率分布随时间的演化。对于满足 SDE
$$
dX_t = u_t(X_t) dt + \sigma_t dW_t
$$
的扩散模型，Fokker-Planck 方程可以表示为
$$
\frac{\partial p_t(x)}{\partial t} = -\nabla_x \cdot (p_t(x) u_t(x)) + \frac{1}{2}\nabla_x^2 : (\sigma_t\sigma_t^\top p_t(x))
$$
其中 $\nabla_x^2 :\ \  = \sum_{i=1}^{d}\sum_{j=1}^{d} \frac{\partial^2}{\partial x_i \partial x_j}$ 表示对矩阵的双重散度操作, $D_t = \sigma_t\sigma_t^\top \in \mathbb{R}^{d \times d}$ 是扩散张量。同理，Fokker-Planck 方程可以类似 Liouville 方程的推导过程得到。&lt;/p&gt;
&lt;p&gt;引入概率流函数 $J_t(x) = u_t(x) p_t(x)$，Fokker-Planck 方程可以改写为
$$
\frac{\partial p_t(x)}{\partial t} = -\nabla_x \cdot J_t(x) + \frac{1}{2}\nabla_x^2 : (D_t^\top p_t(x))
$$&lt;/p&gt;
&lt;h3&gt;1.2.3 概率分布演化——一个简单的例子&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;分布的设置&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设初始分布为标准高斯分布 $p_{init}(x_0) = \mathcal{N}(0, I_d)$，目标分布为 $p_{data}(x_1) = \delta_z, z \in \mathbb{R}^d$，时间 $t \in [0, 1]$。目标分布设置为 Dirac delta 分布表示我们的目标是为了将所有的初始分布的点都汇聚到 $z$ 这个点上。&lt;/p&gt;
&lt;p&gt;这里我们人为地构造演化分布形式，采用 $z$ 构造高斯分布
$$
p_t(x_t) = \mathcal{N}(x_t; \alpha_t z, \beta_t^2 I_d)
$$
这个公式也可以改写为重参数化的形式
$$
x_t = \alpha_t z + \beta_t \epsilon_t, \quad \epsilon_t \sim \mathcal{N}(0, I_d)
$$
其中 $\alpha_t$ 和 $\beta_t$ 是调度参数。&lt;/p&gt;
&lt;p&gt;需要注意的是，人为构造的演化分布 $p_t$ 需要满足
$$
\begin{cases}
p_0(x_0) = p_{init}(x_0) \
p_1(x_1) = p_{data}(x_1)
\end{cases}
$$
因此
$$
\begin{align*}
p_0(x_0) &amp;amp;= \mathcal{N}(0, I_d) = \mathcal{N}(\alpha_0 z, \beta_0^2 I_d) \implies \alpha_0 = 0, \beta_0 = 1 \
p_1(x_1) &amp;amp;= \mathcal{N}(\alpha_1 z, \beta_1^2 I_d) = \delta_z \implies \alpha_1 = 1, \beta_1 = 0
\end{align*}
$$&lt;/p&gt;
&lt;p&gt;于是我们可以设置调度参数为
$$
\alpha_t = t, \quad \beta_t = 1 - t
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可视化结果&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;演化结果如下图所示
&lt;img src=&quot;assets/distribution_evolution.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;动力学方程的推导&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;根据
$$
x_t = \alpha_t z + \beta_t \epsilon_t, \quad \epsilon_t \sim \mathcal{N}(0, I_d)
$$
对时间 $t$ 求导 (注意到 $\epsilon_t$ 不可导)，得到
$$
dx_t = \frac{d\alpha_t}{dt} z , dt + \frac{d\beta_t}{dt} \epsilon_t , dt
$$
代入 $\epsilon_t = \frac{x_t - \alpha_t z}{\beta_t}$，得到
$$
dx_t = \frac{z - x_t}{\beta_t}, dt
$$
即
$$
\boxed{
dx_t = \frac{z - x_t}{1 - t}, dt
}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Liouville 方程的验证&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(a) 计算概率分布函数对时间的导数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
p_t(x) = \mathcal{N}(x; \alpha_t z, \beta_t^2 I_d) = \frac{1}{(2\pi)^{d/2}\beta_t^d} \exp\left(-\frac{|x - \alpha_t z|^2}{2\beta_t^2}\right)
$$
取对数方便计算导数
$$
\log p_t(x) = -\frac{d}{2}\log(2\pi) - d\log \beta_t - \frac{|x - \alpha_t z|^2}{2\beta_t^2}
$$
对时间 $t$ 求导，得到
$$
\frac{\partial \log p_t(x)}{\partial t} = -d\frac{1}{\beta_t}\frac{d\beta_t}{dt} + \frac{(x - \alpha_t z)^\top}{\beta_t^2}\left(\frac{d\alpha_t}{dt} z + \frac{d\beta_t}{dt}\frac{x - \alpha_t z}{\beta_t}\right)
$$
因此
$$
\frac{\partial p_t(x)}{\partial t} = p_t(x)\left[\frac{d}{1 - t} - \frac{\left\Vert x - tz\right\Vert^2}{(1 - t)^{3}} + \frac{(x - tz)^\top z}{(1 - t)^2}\right]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(b) 计算散度项&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;概率流函数为
$$
J_t(x) = u_t(x) p_t(x) = \frac{z - x}{1 - t} p_t(x)
$$
根据 $\nabla\cdot (a \mathbf{v}) = a \nabla \cdot \mathbf{v} + \mathbf{v} \cdot \nabla a$ 计算散度
$$
\nabla_x \cdot J_t(x) = p_t(x) u_t(x) \cdot \nabla_x \log p_t(x) + \nabla_x \cdot u_t(x) p_t(x)
$$
其中
$$
\nabla_x \log p_t(x) = -\frac{x - \alpha_t z}{\beta_t^2} = -\frac{x - tz}{(1 - t)^2}
$$
而 $u_t(x) = \frac{z - x}{1 - t} = \begin{bmatrix} \frac{z_0 - x_0}{1 - t}, \frac{z_1 - x_1}{1 - t}, \cdots, \frac{z_d - x_d}{1 - t}\end{bmatrix}$，因此 $\nabla_x\cdot u_t(x) = \frac{d}{t - 1}$
于是
$$
\nabla_x \cdot J_t(x) = p_t(x)\left[-\frac{(x - tz)^\top (z - x)}{(1 - t)^3}-\frac{d}{1 - t}\right]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(c) 两项相加&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;将 (a) 和 (b) 两项相加，得到
$$
\frac{\partial p_t(x)}{\partial t} + \nabla_x \cdot J_t(x) = 0
$$
从而验证了 Liouville 方程。&lt;/p&gt;
&lt;h2&gt;1.3 动力学方程的基本求解方法&lt;/h2&gt;
&lt;h3&gt;1.3.1 流模型的求解&lt;/h3&gt;
&lt;p&gt;流模型的求解涉及到 ODE 求解。部分 ODE 存在解析解，但大部分 ODE 需要数值解法。常用的数值解法就是欧拉方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class EulerMethod:
    def __init__(self, ode_u_func):
        self.u = ode_u_func

    def step(self, xt, t, dt):
        # $x_{t+1} = x_t + u_t(x_t) dt$
        xt_new = xt + self.u(t, xt) * dt
        return xt_new
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3.2 维纳过程的模拟&lt;/h3&gt;
&lt;p&gt;由于扩散过程的动力 SDE 可以分解为 ODE + 维纳过程 的形式，因此我们只要能求解出维纳过程，就可以求解出扩散过程的动力学方程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;离散化&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;形式上，我们看似可以用积分的方法求解维纳过程
$$
W_t = \int_0^t dW_s = \int_0^t \epsilon_s \sqrt{ds}
$$
但是事实上，只有左侧的公式是正确的 ($W_t = \int_0^t dW_s$ 叫做&lt;strong&gt;伊藤积分&lt;/strong&gt;，见 &lt;a href=&quot;../appendix/#b-%E4%BC%8A%E8%97%A4%E7%A7%AF%E5%88%86&quot;&gt;附录 B&lt;/a&gt;)，右侧的积分是无法积出来的。最起码的 $\sqrt{ds}$ 这个无穷小量的平方根对应的积分是没有定义的。
$$
W_t \neq \int_0^t \epsilon_s \sqrt{ds}
$$
正确的做法是通过离散极限和的形式得到 $W_t$ 的值
$$
W_t = \int_0^t dW_s = \lim_{\Vert \Delta \Vert \to 0} \sum_{i=0}^{n-1} \epsilon_{t_i} \sqrt{\Delta t_i}, \quad \epsilon_{t_i} \overset{\text{i.i.d.}}{\sim} \mathcal{N}(0, I)
$$
其中 $\Delta t_i = t_i - t_{i-1} &amp;gt; 0, \quad \Delta = \max_{i=1}^n \Delta t_i$ 是时间步长的最大值，$t_i$ 是离散化后的时间点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么求解 $W_t$ 时，只有离散化求和的方法？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为维纳过程的路径虽然连续，但是并不光滑。其运动路径处处不可导的性质决定了我们无法用积分的方式直接求解。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/wiener_process.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数值解法——欧拉-丸山方法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;欧拉-丸山方法是一种常用的数值解法，可用于求解维纳过程的动力学方程。其基本思想是将连续时间的随机过程离散化为一系列小时间步长的近似。具体做法是将时间区间 $[0, T]$ 划分为 $N$ 个小时间步长 $\Delta t = \frac{T}{N}$，然后在每个时间步长上近似计算维纳过程的增量。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class EulerMaruyamaMethod4Wiener:
    def __init__(self, wiener_sigma_func):
        self.sigma = wiener_sigma_func

    def sample_eps(self, x_size):
        return torch.randn(x_size)

    def step(self, xt, t, dt):
        # $x_{t+1} = x_t + \sigma_t \epsilon_t \sqrt{dt}$
        eps = self.sample_eps(xt.shape)
        sigma_t = self.sigma(t)
        xt_new = xt + sigma_t * eps * torch.sqrt(dt)
        return xt_new
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3.3 扩散模型的求解&lt;/h3&gt;
&lt;p&gt;扩散模型的求解可以看作是流模型的扩展。由于扩散模型包含了随机性，因此需要在流模型的基础上加入维纳过程的模拟。我们依然采用数值解法来求解扩散模型的动力学方程。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class EulerMaruyamaMethod:
    def __init__(self, sde_u_func, sde_sigma_func):
        self.u = sde_u_func
        self.sigma = sde_sigma_func
    
    def sample_eps(self, x_size):
        return torch.randn(x_size)
    
    def step(self, xt, t, dt):
        # $x_{t+1} = x_t + u_t(x_t) dt + \sigma_t \epsilon_t \sqrt{dt}$
        eps = self.sample_eps(xt.shape)
        u_t = self.u(t, xt)
        sigma_t = self.sigma(t)
        xt_new = xt + u_t * dt + sigma_t * eps * torch.sqrt(dt)
        return xt_new
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3.4 经典案例&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Ornstein-Uhlenbeck (OU) 过程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ornstein-Uhlenbeck 过程是一个经典的扩散模型，常用于描述随机振动和物理系统中的噪声。其动力学方程为
$$
dX_t = \theta (\mu - X_t) dt + \sigma dW_t, \quad X_0 = x_0
$$
其中 $\theta$ 是衰减系数，$\mu$ 是均值，$\sigma$ 是扩散系数。OU 过程的解可以通过欧拉-丸山方法进行数值模拟。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class OrnsteinUhlenbeckProcess:
    def __init__(self, theta, mu, sigma):
        self.theta = theta
        self.mu = mu
        self.sigma = sigma

    def step(self, xt, t, dt):
        # $x_{t+1} = x_t + \theta (\mu - x_t) dt + \sigma dW_t$
        eps = torch.randn(xt.shape)
        drift = self.theta * (self.mu - xt) * dt
        diffusion = self.sigma * eps * torch.sqrt(dt)
        xt_new = xt + drift + diffusion
        return xt_new
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OU 过程的解析解是
$$
X_t = \psi_t(x_0) = \mu + (x_0 - \mu) e^{-\theta t} + \sigma \int_0^t e^{-\theta (t-s)} dW_s
$$&lt;/p&gt;
&lt;p&gt;这一点可以通过 $\frac{d}{dt}\psi_t(x_0)$ 来验证。
$$
\frac{d}{dt} \psi_t(x_0) = -\theta (x_0 - \mu) e^{-\theta t} + \sigma e^{-\theta t} dW_t
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Langevin 动力学&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Langevin 动力学是一个特殊的 OU 过程，通常用于模拟粒子在流体中的运动。其动力学方程为
$$
dX_t = \frac{1}{2}\sigma^2 \nabla \log p(X_t) dt + \sigma dW_t, \quad X_0 = x_0
$$&lt;/p&gt;
&lt;p&gt;可以证明，当满足
$$
p(x) = \mathcal{N}(0, \frac{\sigma^2}{2\theta})
$$&lt;/p&gt;
&lt;p&gt;Langevin 动力学的解与 OU 过程的解是等价的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于 Langevin 动力学方程
$$
dX_t = \frac{1}{2}\sigma^2 \nabla \log p(X_t) dt + \sigma dW_t
$$&lt;/p&gt;
&lt;p&gt;当 $p(x) = \mathcal{N}(0, \frac{\sigma^2}{2\theta})$ 时，有
$$
\log p(x) = -\frac{\theta}{\sigma^2} x^2 + C
$$&lt;/p&gt;
&lt;p&gt;因此
$$
\nabla \log p(x) = -\frac{2\theta}{\sigma^2} x
$$&lt;/p&gt;
&lt;p&gt;将此代入 Langevin 动力学方程
$$
dX_t = \frac{1}{2}\sigma^2 \cdot \left(-\frac{2\theta}{\sigma^2} X_t\right) dt + \sigma dW_t
$$
$$
= -\theta X_t dt + \sigma dW_t
$$&lt;/p&gt;
&lt;p&gt;这正是 OU 过程的动力学方程（当 $\mu = 0$ 时）
$$
dX_t = \theta(0 - X_t) dt + \sigma dW_t = -\theta X_t dt + \sigma dW_t
$$&lt;/p&gt;
&lt;p&gt;因此，当目标分布为零均值高斯分布 $\mathcal{N}(0, \frac{\sigma^2}{2\theta})$ 时，Langevin 动力学等价于均值为零的 OU 过程。&lt;/p&gt;
&lt;h2&gt;1.4 逆向动力学方程&lt;/h2&gt;
&lt;p&gt;在生成模型中，我们通常先定义一个从数据分布 $p_0$ 到先验分布 $p_T$ 的前向动力学过程（时间从 $0$ 到 $T$），然后学习并求解其对应的逆向动力学过程（时间从 $T$ 到 $0$），从而实现从先验分布中采样生成数据。&lt;/p&gt;
&lt;h3&gt;1.4.1 逆向 ODE&lt;/h3&gt;
&lt;p&gt;对于流模型的前向动力学方程
$$
dX_t = u_t(X_t) dt, \quad X_0 \sim p_0
$$
其对应的逆向动力学方程为
$$
d\bar{X}&lt;em&gt;t = -u&lt;/em&gt;{T-t}(\bar{X}_t) dt, \quad \bar{X}_0 \sim p_T
$$
这里 $\bar{X}&lt;em&gt;t$ 表示在逆向时间 $t \in [0, T]$ 下的粒子状态，它与前向过程的关系是 $\bar{X}&lt;em&gt;t = X&lt;/em&gt;{T-t}$。注意，逆向过程的速度场是前向速度场的时间反转形式 $-u&lt;/em&gt;{T-t}$。&lt;/p&gt;
&lt;h3&gt;1.4.2 逆向 SDE (Anderson 定理)&lt;/h3&gt;
&lt;p&gt;对于扩散模型的前向动力学方程
$$
dX_t = u_t(X_t) dt + \sigma_t dW_t, \quad X_0 \sim p_0
$$
根据 Anderson 定理，其对应的逆向 SDE 为
$$
d\bar{X}&lt;em&gt;t = \left[-u&lt;/em&gt;{T-t}(\bar{X}&lt;em&gt;t) + \sigma&lt;/em&gt;{T-t}^2 \nabla \log p_{T-t}(\bar{X}&lt;em&gt;t)\right] dt + \sigma&lt;/em&gt;{T-t} d\bar{W}_t, \quad \bar{X}_0 \sim p_T
$$
其中 $\bar{W}_t$ 是一个标准维纳过程。这个方程描述了从 $t=0$ 到 $t=T$ 的逆向过程，等价于从 $p_T$ 演化到 $p_0$。&lt;/p&gt;
&lt;p&gt;在实际应用中，我们通常直接写出从 $T$ 到 $0$ 的反向积分形式，令 $t$ 从 $T$ 走回 $0$
$$
dX_t = \left[u_t(X_t) - \sigma_t^2 \nabla \log p_t(X_t)\right] (-dt) + \sigma_t d\bar{W}_t
$$
这里的 $(-dt)$ 表示时间是反向流逝的。在数值求解时，这对应于一个负的时间步长。&lt;/p&gt;
&lt;h2&gt;1.5 流模型与扩散模型作为生成模型&lt;/h2&gt;
&lt;p&gt;流模型与扩散模型作为一类强大的生成模型，其核心思想是利用连续时间动力学来描述数据分布的演化。目标是先构建出从复杂数据分布到简单先验分布的动力学过程，然后通过学习它的逆向动力学模型实现对复杂数据分布的建模。通常设置 $t = 0$ 时的分布为目标数据分布 $p_{data}$，$t = T$ 时的分布为简单的先验分布 $p_T$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;动力学模型与分布变换&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个变换过程由一个常微分方程（ODE）或随机微分方程（SDE）定义的动力学模型所驱动。该模型的核心是用神经网络学习出一个时间依赖的速度场（或漂移场）$u_t(x)$，它精确地指引了数据点在分布空间中演化的方向和速率。当神经网络完全学习到从简单先验分布到复杂数据分布的速度场后，数据点在该速度场的引导下，能够逐渐从噪声状态“流动”到真实数据分布。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;生成过程：基于采样的动力学&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数据生成的过程本质上是一个采样过程&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;采样先验&lt;/strong&gt;: 从简单的先验分布 $p_T$ 中随机采样一个点 $x_T$。这个点可以看作是编码在潜在空间中的信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;求解下一时刻状态&lt;/strong&gt;: 将 $x_T$ 作为初始条件，通过求解学习到的逆向ODE或SDE（从时间 $T$ 到 $0$），模拟该点在速度场 $u_t(x)$ 的引导下“去噪”或“结构化”的轨迹。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成样本&lt;/strong&gt;: 最终在时间 $t=0$ 得到的状态 $x_0$ 就是一个近似服从原始数据分布 $p_{\text{data}}$ 的新生成样本。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过这种方式，模型能够将先验分布中的随机性，通过确定性的、可学习的动力学路径，转化为具有复杂结构的数据。&lt;/p&gt;
</content:encoded></item><item><title>大模型技术知识点 2</title><link>https://adalovelemon.github.io/posts/content/technotes/llm-misc/llm-misc2/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/llm-misc/llm-misc2/</guid><pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;大模型的一些应知知识点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;10. 如果 GPT 做生成前也需要把输入语句转为嵌入向量序列，那么 Encoder + Decoder 模型和 GPT 的区别是什么？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先，需要明白两个东西&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;虽然嵌入模型很像一个 Encoder (实现了 token 序号序列到 d 维向量的映射)，但是实际上它并不是 Encoder！&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;我们通常把 Embedding 层之后的向量空间叫做嵌入空间 (Emebdding Space)，而把 Encoder、Decoder 通常操作的向量空间叫做表征空间 (Representation Space)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在文本领域，Embedding 模型通常只是薄薄的一层 Embedding 层，没有多余的线性层或注意力层。GPT 在生成文本时，使用的是 Masked Self Attention (没有 Cross Attention)，就是把输入的 Context + Prompt 作为 K、Q、V 序列使用，然后预测新的 token；把新预测的 token 替换最前面的一个 添加到前面的输入序列的末尾后，再重复上面的操作，直至达到目标长度或者遇到 &lt;code&gt;[EOS]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;而 Encoder + Decoder 模型则不同了。它的范式是先将输入的文本经过 Embedding 后得到嵌入向量序列，然后把该嵌入向量经过 Encoder 处理后，将得到的上下文中间表示向量序列作为 Cross Attention K、V 传给 Decoder 使用；Decoder 的 Cross Attention 的 Q 则是嵌入向量经过 Self Attention 后得到的上下文中间表示。Decoder 最终也是做 next token prediction，只不过新的 token 只会加入到 Q 序列中，K、V 序列则保持不变。&lt;/p&gt;
&lt;p&gt;二者的架构不同(有无 Encoder, 用不用 Cross Attention)，自回归中新的 token 加到哪里(K, Q, V 都加入或者只加入 Q 中)这便是全部的差异。&lt;/p&gt;
&lt;p&gt;需要指出的是，在多模态视觉部分，ViT patch projection / CNN stem 相对应于 Embedding 模型，是先把像素映射成了 patch tokens， ViT backbone / CNN feature extractor 是直接把原始图像映射到了表征空间中，是 Encoder 模型。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;11. 为什么要有位置编码？什么是正弦型位置编码？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Transformer 本身是基于自注意力（Self-Attention）的，它没有循环结构（不像 RNN）也没有卷积结构（不像 CNN）。这意味着它看到的序列输入（词嵌入向量）本身是“无序”的。如果没有位置编码，模型就分不清 “I love you” 和 “You love I”。所以我们要给每个 token 附加上位置信息。&lt;/p&gt;
&lt;p&gt;在 Vaswani 的文章中，正弦型位置编码被首次提出。
$$
\begin{aligned}
\text{PE}(x, 2n) &amp;amp;= \sin\left(\frac{x}{10000^\frac{2n}{d}}\right) \
\text{PE}(x, 2n + 1) &amp;amp;= \cos\left(\frac{x}{10000^\frac{2n}{d}}\right)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;其中 $x = 0, 1, \ldots, L-1$ 是位置，$n = 0, 1, \ldots, d/2-1$ 是维度索引，$d$ 是总维度。这样每个位置会映射成一组不同频率的正弦/余弦波。&lt;/p&gt;
&lt;p&gt;例如，我们有一个输入 embedding $X = \begin{bmatrix}\begin{bmatrix}\begin{bmatrix}1 &amp;amp; 2\end{bmatrix} \ \begin{bmatrix}3 &amp;amp; 4\end{bmatrix} \ \begin{bmatrix}5 &amp;amp; 6\end{bmatrix}\end{bmatrix}\end{bmatrix}$，形状为 $(B, L, D) = (1, 3, 2)$，现在计算位置编码
$$
\begin{aligned}
\text{PE}(0) &amp;amp;= \begin{bmatrix}\sin(0/10000^{0/2}) &amp;amp; \cos(0/10000^{1/2})\end{bmatrix} = \begin{bmatrix}0 &amp;amp; 1\end{bmatrix} \
\text{PE}(1) &amp;amp;= \begin{bmatrix}\sin(1/10000^{0/2}) &amp;amp; \cos(1/10000^{1/2})\end{bmatrix} \approx \begin{bmatrix}0.8415 &amp;amp; 0.99995\end{bmatrix} \
\text{PE}(2) &amp;amp;= \begin{bmatrix}\sin(2/10000^{0/2}) &amp;amp; \cos(2/10000^{1/2})\end{bmatrix} \approx \begin{bmatrix}0.9093 &amp;amp; 0.9998\end{bmatrix}
\end{aligned}
$$
组合成位置编码矩阵
$$
\text{PE} = \begin{bmatrix}\begin{bmatrix}0 &amp;amp; 1\end{bmatrix} \ \begin{bmatrix}0.8415 &amp;amp; 0.99995\end{bmatrix} \ \begin{bmatrix}0.9093 &amp;amp; 0.9998\end{bmatrix}\end{bmatrix}
$$
然后广播相加
$$
X + \text{PE} = \begin{bmatrix}\begin{bmatrix}1 &amp;amp; 2\end{bmatrix} \ \begin{bmatrix}3 &amp;amp; 4\end{bmatrix} \ \begin{bmatrix}5 &amp;amp; 6\end{bmatrix}\end{bmatrix} + \begin{bmatrix}\begin{bmatrix}0 &amp;amp; 1\end{bmatrix} \ \begin{bmatrix}0.8415 &amp;amp; 0.99995\end{bmatrix} \ \begin{bmatrix}0.9093 &amp;amp; 0.9998\end{bmatrix}\end{bmatrix} = \begin{bmatrix}\begin{bmatrix}1 &amp;amp; 3\end{bmatrix} \ \begin{bmatrix}3.8415 &amp;amp; 4.99995\end{bmatrix} \ \begin{bmatrix}5.9093 &amp;amp; 6.9998\end{bmatrix}\end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;12. 为什么选用正弦型位置编码？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;正弦型位置编码有几个性质&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;频率唯一且单调变化&lt;/strong&gt;: 特征维度下标 $n$ 越大，频率越低，位置嵌入变化越慢；反之，频率越高，位置嵌入变化越快。这种设计使得模型更倾向于在 $n$ 较小的维度上捕捉局部信息，在 $n$ 较大的维度上捕捉全局信息（含有频率分析中低频对应于全局信息， 高频对应于局部信息的思想）。仿佛每一个维度上都有一个在序列长度上振荡的波。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;相对位置关系&lt;/strong&gt;: $\sin(x + k) = \sin(x)\cos(k) + \cos(x)\sin(k)$，这意味着位移 $k$ 处的编码其实是位置 $x$ 处编码的线性组合，模型只要习得 $\cos(k)$ 和 $\sin(k)$ 的值，就能推断出相对位置关系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;初等函数性质&lt;/strong&gt;: 正弦和余弦函数是连续可微、对称光滑的，且可以在 $\mathbb{R}$ 上都有定义。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;13. RoPE (Rotary Position Embedding) 是什么？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;二维示例&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;RoPE 初见论文 &lt;a href=&quot;https://arxiv.org/pdf/2104.09864&quot;&gt;ReFormer: Enhanced Transformer with Rotary Position Embedding&lt;/a&gt;.传统的 Transformer 使用绝对位置编码（如正弦编码）或学习式的位置编码，这种位置编码方式在处理&lt;strong&gt;长序列&lt;/strong&gt;时的外推性较差。模型只在训练时见过的序列长度 (如2048) 上表现良好，对于灭见过的更长的序列 (如4096)，性能就会显著下降。&lt;/p&gt;
&lt;p&gt;RoPE的想法就是&lt;strong&gt;通过绝对编码的形式，实现相对位置编码的效果&lt;/strong&gt;。注意力机制的核心时内积，如果能确保在计算内积时，只依赖于两个 token 之间的相对位置 $m - n$，而不是它们的绝对位置 $m$ 和 $n$，那么模型就能更好地泛化到没见过的序列长度上。&lt;/p&gt;
&lt;p&gt;设有一个词嵌入向量 $x_m = \left[x_m^{(1)}, x_m^{(2)}\right]$ 在位置 $m$ 处。用&lt;strong&gt;旋转矩阵&lt;/strong&gt;将其编码为一个包含位置信息的向量 $f(x_m, m)$。设旋转矩阵为 $R_\varphi = \begin{bmatrix}\cos\varphi &amp;amp; -\sin\varphi\ \sin\varphi &amp;amp; \cos\varphi\end{bmatrix}$，其中 $\varphi$ 是旋转角度。在实际编码中，先预定义一个基础角度 $\theta$，并设置位置 $m$ 处使用的旋转角度为 $m\theta$，用旋转矩阵进行编码，得到编码后的向量为
$$
f(x_m, m) = R_{m\theta} = \begin{bmatrix}\cos m\theta &amp;amp; -\sin m\theta\ \sin m\theta &amp;amp; \cos m\theta\end{bmatrix}\begin{bmatrix}x_m^{(1)} \ x_m^{(2)}\end{bmatrix} = \begin{bmatrix}x_m^{(1)}\cos m\theta - x_m^{(2)}\sin m\theta \ x_m^{(1)}\sin m\theta + x_m^{(2)}\cos m\theta\end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;下面验证其位置相对性，位置 $m$ 和 $n$ 处的编码向量的内积为
$$
\left&amp;lt;f(x_m, m), f(x_n, n)\right&amp;gt; = (R_{m\theta}x_m)^\top(R_{n\theta}x_n) = x_m^\top R_{m\theta}^\top R_{n\theta}x_n = x_m^\top R_{(n-m)\theta}x_n
$$
利用旋转矩阵的正交矩阵性质，可以发现最终的内积只与相对位置有关，而与绝对位置无关。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;高维推广&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;真实的词向量都是高维向量，为了推广二维的旋转编码，可以考虑将高维向量分成 $\frac{d}{2}$ 个组，每一个组对应一个二维平面，然后在每一个平面分别应用旋转。不过我们会为不同的平面分配不同的基础旋转角度 $\theta_i$，通常的设置是 $\theta_i$ 会随着维度增加而减小，如
$$
\theta_i = 10000^{-\frac{2(i-1)}{d}}
$$
其中 $i$ 为维度对的索引。根据矩阵分块原理，可以类似得到位置编码的相对性性质。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实际应用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在实际应用中，旋转位置编码并非作用于词嵌入向量上，而是作用于 query 向量和 key 向量上，这是因为&lt;/p&gt;
&lt;p&gt;$$
\hat q_m = f(q_m, m), \quad \hat k_n = f(k_n, n)
$$
然后计算注意力分数
$$
\hat q_m^\top \hat k_n = (R_{\Theta, m}^d q_m)^\top (R_{\Theta, n}^d k_n) = q_m^\top R_{\Theta, n - m}^d k_n
$$
这样的设计可以保证 Value 向量中没有因为含有位置编码而引入不必要的信息，且保证了位置编码只在注意力计算时发挥重要作用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;14. 传统 Attention 存在哪些问题？Attention 优化方向有哪些？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主要问题&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;复杂度与带宽瓶颈&lt;/strong&gt;: 全连接注意力时间/显存均为 $O(L^2)$，prefill 峰值显存高；decode 阶段受内存带宽限制显著。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KV Cache 膨胀&lt;/strong&gt;: 随上下文线性增长，多头重复存储 K/V，长对话吞吐急剧下降。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长上下文退化&lt;/strong&gt;: 远距信息衰减，注意力偏近邻；训练长度外推差，易出现“看不见远处”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数值稳定性&lt;/strong&gt;: QK 点积过大导致 softmax 饱和；outlier token 使梯度不稳。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算冗余&lt;/strong&gt;: 无信息 token、冗余注意力头、跨模态交叉注意力 $O(L_\text{text}·L_\text{vision})$ 过大。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工程痛点&lt;/strong&gt;: mask/padding 浪费、内核非 IO 友好、内存碎片、批量与缓存调度复杂。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;优化方向 (方法论)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;稀疏与结构化注意力&lt;/strong&gt;: 滑窗/扩张/块稀疏、局部+少量全局锚点、LSH/路由式注意力；在不显著掉点下将复杂度降至近线性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;低秩/核近似&lt;/strong&gt;: Linformer、Performer、Nyström、CosFormer、线性注意力；以特征映射或投影逼近 QK^T。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归与状态空间&lt;/strong&gt;: RWKV、S4/SSM、Hyena、RetNet、Mamba；用可并行的隐式卷积/有状态单元建模长依赖。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;检索与记忆&lt;/strong&gt;: RAG/kNN-LM、Memory/Global tokens、Compressive/Streaming Transformer、滑动窗+锚点摘要；以检索代替全局全连接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位置与外推&lt;/strong&gt;: RoPE 缩放与 NTK-aware 插值、ALiBi、XPOS、YaRN、分块相对位置；提升长序列外推稳定性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正交稳健化&lt;/strong&gt;: QK 归一化/中心化、logit 裁剪、温度与缩放、pre/post-norm、熵正则/DropAttention 减少塌缩。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;优化方向 (系统与内核)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FlashAttention 系列与内存高效注意力&lt;/strong&gt;: tile 化计算+在线 softmax，消除 O(L^2) 中间显存，显著提高带宽利用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KV Cache 工程&lt;/strong&gt;: MQA/GQA 共享 K/V、Paged KV/块化缓存、滑动裁剪或淘汰、分段重计算、RoPE 融合旋转、按通道 8/4/2-bit 量化。&lt;/li&gt;
&lt;li&gt;推理加速：连续批处理、前缀复用、prefill/decode 分离、推测/多 token 解码、块并行调度、遮罩缓存。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算图与布局&lt;/strong&gt;: 融合 QKV+RoPE+softmax、避免 padding、head/seq 友好布局、CUDA Graph、张量/流水并行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨模态&lt;/strong&gt;: 下采样/聚合视觉或语音 token、瓶颈潜变量（如 Perceiver IO）、分层交叉注意力。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;实践建议&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;训练&lt;/strong&gt;: 混合长度课程+FlashAttention，启用 NTK-aware RoPE/ALiBi，监控长距离对齐指标。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;推理&lt;/strong&gt;: GQA+Paged KV+KV 4bit 量化，滑窗注意力+少量全局 token，启用 FlashAttention2/3 与连续批处理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;超长上下文(&lt;/strong&gt;$\ &amp;gt;32k$&lt;strong&gt;)&lt;/strong&gt;: 优先检索/分块摘要，其次再做 RoPE 缩放或位置插值扩窗。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;15. 解释 PPO, DPO, 和 KTO&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(i) PPO (近端策略优化)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;PPO 是 OpenAI 在 2017 年提出的一种强化学习算法，它成为了之后多年训练大语言模型（如 ChatGPT 的早期版本）的核心算法之一。PPO 属于&lt;strong&gt;策略梯度&lt;/strong&gt;方法。其核心思想是&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;智能体&lt;/strong&gt;有一个&lt;strong&gt;策略&lt;/strong&gt;，这个策略根据当前状态（对话上下文）输出一个动作（下一个词）。&lt;/li&gt;
&lt;li&gt;智能体与环境（用户）交互，生成一段完整的对话（或回答）。&lt;/li&gt;
&lt;li&gt;根据一个&lt;strong&gt;奖励模型&lt;/strong&gt;对生成的回答进行打分，得到奖励信号。&lt;/li&gt;
&lt;li&gt;算法根据这个奖励信号来更新策略，使得策略未来更倾向于生成能获得高奖励的动作（词序列）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PPO 的关键创新在于解决了传统策略梯度方法(如 TRPO)中&lt;strong&gt;训练不稳定&lt;/strong&gt;和&lt;strong&gt;步长难以选择&lt;/strong&gt;的问题。它通过引入一个“近端”约束，确保新策略不会偏离旧策略太远，从而避免因一次激进的更新而导致策略崩溃。&lt;/p&gt;
&lt;p&gt;PPO 的核心是它的目标函数。最流行的形式是 PPO-Clip
$$
L^{CLIP}(\theta) = \mathbb{E}_{t} \left[ \min\left( r_t(\theta) A_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right]
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\theta$：策略网络的参数。&lt;/li&gt;
&lt;li&gt;$r_t(\theta)$：&lt;strong&gt;概率比&lt;/strong&gt;，$ r_t(\theta) = \frac{\pi_{\theta}(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)} $。表示新策略选择某个动作的概率与旧策略概率的比值。&lt;/li&gt;
&lt;li&gt;$A_t$：&lt;strong&gt;优势函数&lt;/strong&gt;，表示在状态 $s_t$ 下选择动作 $a_t$ 比平均情况好多少。它由奖励模型计算得出。&lt;/li&gt;
&lt;li&gt;$\epsilon$：一个超参数（如 0.2），用于限制 clipping 的范围。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;公式内涵&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心思想&lt;/strong&gt;：如果某个动作的优势 $A_t$ 是正的（好动作），我们就希望增加选择这个动作的概率，即让 $r_t(\theta) &amp;gt; 1$。反之，如果 $A_t$ 是负的（坏动作），我们就希望减小其概率，即让 $r_t(\theta) &amp;lt; 1$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clip 函数的作用&lt;/strong&gt;：它防止 $r_t(\theta)$ 变得过大或过小。例如，如果一个动作的优势很大，没有 clip 的话，$r_t(\theta)$ 可能会变得极大，导致更新步长过大。Clip 函数将其限制在 $[1-\epsilon, 1+\epsilon]$ 范围内，从而确保更新是稳定、小幅度的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;在 LLM 中的应用流程（RLHF 阶段 3）：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;有一个初始模型（经过 SFT 微调）。&lt;/li&gt;
&lt;li&gt;有一个训练好的奖励模型。&lt;/li&gt;
&lt;li&gt;使用 PPO 算法，以初始模型为起点，用奖励模型作为奖励信号来优化策略（LLM），同时加入 KL 散度惩罚项，防止模型偏离初始模型太远而生成无意义的乱码。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;优缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：稳定、通用、效果好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：流程复杂，需要同时维护四个模型（待优化的策略模型、参考模型、奖励模型、价值模型），计算成本高且难以调试。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;(ii) DPO (直接偏好优化)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;DPO 是 2024 年提出的一种更直接的方法，它完全绕过了奖励模型的训练和复杂的 PPO 循环，直接将偏好数据用于优化语言模型。DPO 的核心洞察是一个巧妙的数学变换&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;与其先训练一个奖励模型，再用强化学习算法去优化策略以最大化奖励，不如直接定义一个在偏好数据上最优的策略应该满足的数学关系（基于 Bradley-Terry 模型），然后将这个关系作为目标函数来直接优化策略本身。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;简单来说，DPO 将&lt;strong&gt;强化学习问题&lt;/strong&gt;转化为了一个简单的&lt;strong&gt;监督学习问题&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;偏好概率模型（Bradley-Terry）&lt;/strong&gt;：
假设我们有一对回答，一个是被偏好的 $y_w$，一个是被拒绝的 $y_l$。奖励模型 $r^&lt;em&gt;$ 给出奖励的概率为：
$$
P(y_w \succ y_l | x) = \frac{\exp(r^&lt;/em&gt;(x, y_w))}{\exp(r^&lt;em&gt;(x, y_w)) + \exp(r^&lt;/em&gt;(x, y_l))} = \sigma(r^&lt;em&gt;(x, y_w) - r^&lt;/em&gt;(x, y_l))
$$
其中 $\sigma$ 是 sigmoid 函数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DPO 的关键变换&lt;/strong&gt;：
在最优策略 $\pi^&lt;em&gt;$ 和最优奖励函数 $r^&lt;/em&gt;$ 之间存在一个明确的关系（通过 RL 的目标函数推导得出）：$ r^&lt;em&gt;(x, y) = \beta \log \frac{\pi^&lt;/em&gt;(y|x)}{\pi_{ref}(y|x)} + ... $。将这个关系代入上面的 Bradley-Terry 模型，可以发现&lt;strong&gt;奖励函数 $r^*$ 被消去了&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DPO 目标函数&lt;/strong&gt;：
经过推导，可以得到一个只依赖于策略 $\pi_{\theta}$ 和参考模型 $\pi_{ref}$ 的目标函数
$$
L_{DPO}(\pi_{\theta}; \pi_{ref}) = -\mathbb{E}&lt;em&gt;{(x, y_w, y_l) \sim D} \left[ \log \sigma \left( \beta \log \frac{\pi&lt;/em&gt;{\theta}(y_w|x)}{\pi_{ref}(y_w|x)} - \beta \log \frac{\pi_{\theta}(y_l|x)}{\pi_{ref}(y_l|x)} \right) \right]
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;$\pi_{\theta}$：我们要优化的策略（LLM）。&lt;/li&gt;
&lt;li&gt;$\pi_{ref}$：参考模型（通常是 SFT 后的模型，在 DPO 中&lt;strong&gt;固定不变&lt;/strong&gt;）。&lt;/li&gt;
&lt;li&gt;$\beta$：控制偏离参考模型程度的超参数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;公式内涵&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目标是最小化 $L_{DPO}$，也就是最大化 log 里面的概率。&lt;/li&gt;
&lt;li&gt;它本质上是在最大化偏好回答 $y_w$ 和拒绝回答 $y_l$ 的&lt;strong&gt;对数概率比&lt;/strong&gt;。模型被鼓励去增大 $y_w$ 相对于 $\pi_{ref}$ 的概率，同时减小 $y_l$ 的概率。&lt;/li&gt;
&lt;li&gt;$\beta$ 和 $\pi_{ref}$ 共同起到了 PPO 中 KL 惩罚项的作用，防止模型过度优化而“走火入魔”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;优缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：极其简单，训练稳定，不需要奖励模型，计算效率高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：严重依赖于高质量的偏好对数据。理论上假设偏好完全由 Bradley-Terry 模型生成，可能与现实有偏差。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;(iii) KTO (Kahneman-Tversky 优化)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;KTO 可以看作是 DPO 的进一步简化和发展。它认为收集严格的偏好对（A 比 B 好）成本高昂，而判断一个回答是“好”还是“坏”则容易得多。KTO 的灵感来源于行为经济学中的“前景理论”。它不再使用偏好对 $(y_w, y_l)$，而是使用单个样本的&lt;strong&gt;期望值&lt;/strong&gt;标签。对于每个提示 $x$ 和回答 $y$，我们只需要一个人类标注者判断这个回答是“可取的”还是“不可取的”。KTO 的目标是让模型生成的回答的&lt;strong&gt;期望值&lt;/strong&gt;尽可能高，同时避免生成期望值低的结果。&lt;/p&gt;
&lt;p&gt;KTO 定义了一个基于“价值”的目标函数。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;价值函数&lt;/strong&gt;：
首先，它定义了一个样本 $(x, y)$ 的“价值”，类似于 DPO 中的奖励：
$$
v_{KTO}(x, y; \beta) = \beta \log \frac{\pi_{\theta}(y|x)}{\pi_{ref}(y|x)}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;目标函数&lt;/strong&gt;：
然后，根据样本的标签（可取/不可取）分别定义损失函数：
$$
L_{KTO}(\pi_{\theta}, \pi_{ref}) = \mathbb{E}&lt;em&gt;{x,y \sim D} \left[ w&lt;/em&gt;{y} \left( 1 - v_{KTO}(x, y; \beta) \right) \right]
$$
但更常见的表述是使用 sigmoid 函数的一个变体，使其对不可取样本的惩罚更鲁棒
$$
L_{KTO}(\theta) = \mathbb{E}&lt;em&gt;{x, y \sim D} \left[ \lambda&lt;/em&gt;{y} \cdot \sigma\left( v_{KTO}(x, y; \beta) - z_{ref} \right) \right]
$$
其中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 $y$ 是&lt;strong&gt;可取的&lt;/strong&gt;，我们希望 $v_{KTO}$ 越大越好，因此损失函数是 $\sigma(z_{ref} - v_{KTO})$，即价值低时损失大。
&lt;ul&gt;
&lt;li&gt;如果 $y$ 是&lt;strong&gt;不可取的&lt;/strong&gt;，我们希望 $v_{KTO}$ 越小越好，因此损失函数是 $\sigma(v_{KTO} - z_{ref})$，即价值高时损失大。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;$z_{ref}$ 是一个参考点，通常设为模型在一批数据上计算出的价值的均值。&lt;/li&gt;
&lt;li&gt;$\lambda_{y}$ 是权重，用于平衡两类样本。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;公式内涵&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它直接拉高“好回答”的价值，压低“坏回答”的价值。&lt;/li&gt;
&lt;li&gt;由于不需要严格的配对，数据收集成本大大降低，并且可以充分利用那些只有一个回答的数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;优缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：数据需求更简单、成本更低；对部分有噪声的标签更鲁棒。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：相比 DPO，可能损失了一些严格的偏好比较信息，理论上的最优性保证不如 DPO 严格。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;总结对比&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;PPO&lt;/th&gt;
&lt;th&gt;DPO&lt;/th&gt;
&lt;th&gt;KTO&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;核心思想&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;使用RL，在奖励模型指导下&lt;strong&gt;稳定地&lt;/strong&gt;更新策略。&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;直接&lt;/strong&gt;优化策略，使其符合偏好对的概率模型。&lt;/td&gt;
&lt;td&gt;直接优化策略，使“好回答”价值高，“坏回答”价值低。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;数据格式&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;通过模型采样，由奖励模型打分。&lt;/td&gt;
&lt;td&gt;偏好对 $(x, y_w, y_l)$&lt;/td&gt;
&lt;td&gt;单样本标签 $(x, y)$，标注为“好/坏”。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;数学基础&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;策略梯度理论 + 近端优化（Clip）。&lt;/td&gt;
&lt;td&gt;布拉德利-特里偏好模型 + 奖励-策略的隐式映射。&lt;/td&gt;
&lt;td&gt;价值函数最大化 + 前景理论启发。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;流程复杂度&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;高&lt;/strong&gt;，需要多个模型，流程复杂。&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;低&lt;/strong&gt;，类似于带参考模型的监督微调。&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;低&lt;/strong&gt;，与 DPO 类似，甚至更简单。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;优势&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;非常通用、成熟，在复杂任务上潜力大。&lt;/td&gt;
&lt;td&gt;简单、稳定、高效，已成为当前主流。&lt;/td&gt;
&lt;td&gt;数据获取成本极低，更实用，性能与DPO相当。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;劣势&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;复杂、难以调试、计算成本高。&lt;/td&gt;
&lt;td&gt;依赖高质量的偏好对数据。&lt;/td&gt;
&lt;td&gt;损失了部分偏好信息，理论新颖度最高。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;演进趋势&lt;/strong&gt;：PPO -&amp;gt; DPO -&amp;gt; KTO 体现了整个领域向&lt;strong&gt;更简单、更稳定、数据效率更高&lt;/strong&gt;的方向发展，目标都是让语言模型更好地与人类偏好对齐。&lt;/p&gt;
</content:encoded></item><item><title>大模型技术知识点 1</title><link>https://adalovelemon.github.io/posts/content/technotes/llm-misc/llm-misc1/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/llm-misc/llm-misc1/</guid><pubDate>Thu, 28 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;大模型的一些应知知识点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 什么是 KV-Cache？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;KV-Cache 即是 Key-Value Cache, 是在大模型&lt;strong&gt;推理&lt;/strong&gt;阶段所使用的，减少重复计算量的技术。具体而言，在自回归模型逐步生成每个 token 的过程中，&lt;strong&gt;缓存每个 Transformer Decoder 层的在每个时间步&lt;/strong&gt; $t$ &lt;strong&gt;的 Key 和 Value 矩阵&lt;/strong&gt; (形状为&lt;code&gt;(B, H, L, C)&lt;/code&gt;), &lt;strong&gt;从而避免在生成新 token 时重复计算之前时间步的 Key 和 Value&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在给出具体的量化分析前，先给出一些结论&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;形状为 $m\times p$ 的矩阵与 $p\times n$ 的矩阵相乘，乘法计算次数为 $m \cdot p \cdot n$&lt;/li&gt;
&lt;li&gt;形状为 &lt;code&gt;(B, H, L, C)&lt;/code&gt; 的张量与 &lt;code&gt;(B, H, C, T)&lt;/code&gt; 的张量相乘，乘法计算次数为 $B \cdot H \cdot C \cdot L \cdot T$ (&lt;code&gt;B&lt;/code&gt;, &lt;code&gt;H&lt;/code&gt; 相当于就是一个 chunk 大小的数乘因子)&lt;/li&gt;
&lt;li&gt;计算 $K, Q, V$ 三个投影矩阵的复杂度为 $3 \cdot B \cdot L \cdot D^2$，其中 $D = H \cdot C$ 是嵌入维度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假设&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$N$：Decoder 层数，$B$：batch&lt;/li&gt;
&lt;li&gt;$H$：注意力头数，$C$：每头维度, $D = H \cdot C$：嵌入维度&lt;/li&gt;
&lt;li&gt;$L$：当前时间步（同时上文信息的长度为 $L-1$），$T$：目标总长度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是使用 KV-Cache 相较于不适用 KV-Cache 而言，少了很大一部分的 $1, 2, \ldots, L-1$ tokens 的 key 和 value 的投影计算，省下的计算量为
$$
\sum_{t=1}^{L} N \cdot \underbrace{2 \cdot B \cdot D^2 \cdot (t-1)}_{\text{Projection Matrix Multiply}} = B \cdot N \cdot D^2 \cdot (L-1) \cdot L = O(B \cdot N \cdot D^2 \cdot L^2)
$$&lt;/p&gt;
&lt;p&gt;这个量级的计算量是非常可观的，尤其是当 $L$ 很大时。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 为什么当前的大模型的结构都是 Decoder-only? Encoder 还需要吗?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decoder-only 在处理生成任务上表现出色。当前大模型的核心任务在于&lt;strong&gt;生成&lt;/strong&gt;，该任务可以用&lt;strong&gt;自回归范式&lt;/strong&gt;实现，且 Decoder 的 Masked Attention 机制能完美契合自回归范式，因此大部分大模型都采用 Decoder-only 结构。&lt;/li&gt;
&lt;li&gt;相较于 Encoder-Decoder 结构，Decoder-only 结构更为简洁高效和易于添加 Plug-ins。Encoder-Decoder 结构需要同时训练和维护两个独立的网络，且Encoder不易于更换，而 Decoder-only 结构只需一个网络，且能更换 Encoder，减少了模型的复杂度和计算资源的消耗，并提高了灵活性。&lt;/li&gt;
&lt;li&gt;此外，当前的通用范式是让 Decoder 专注于在 &lt;strong&gt;Embedding&lt;/strong&gt; 空间中做自回归生成，而&lt;strong&gt;让不同模态、不同功能的 Encoder 专注于将输入信息映射到 Embedding 空间中&lt;/strong&gt;，这样的分工使得模型更易于扩展和适应不同任务。&lt;/li&gt;
&lt;li&gt;例如当前的 embedding 模型都是 Encoder-only 结构，如 CLIP、DINOv2、SAM 等等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 为什么大模型的参数量都是 7B、13B、70B、175B 这种数？如何通过模型参数容量计算出显存开销？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;(a) 大模型的参数容量通常选择 7B、13B、70B、175B 这种数，是因为这些数字背后对应的 embedding 维度和层数基本上是 1024、2048、4096、8192 这种 2 的幂次方的倍数，这些数字在计算机系统中更易于处理和优化。&lt;/p&gt;
&lt;p&gt;(b) 模型参数量大小与&lt;strong&gt;将模型放入显存中&lt;/strong&gt;的开销之间的关系可以通过以下公式计算
$$
\text{模型放入显存开销 (GiB)} = \frac{\text{参数量 (B)} \times 10^9 \times \text{每个参数的字节数 (Bytes)}}{1024^3} \approx \text{参数量 (B)} \times \text{每个参数的字节数 (Bytes)}
$$
需要注意的是单位, $\text{B}$ 表示 &lt;strong&gt;十亿&lt;/strong&gt; ($1 \text{Billion} = 10^9$), $\text{Bytes}$ 表示&lt;strong&gt;字节&lt;/strong&gt; (Byte), $1024^3$ 表示&lt;strong&gt;每 GiB 的字节数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如，$7\text{B}$ 的模型，fp16 精度下 (每个参数 2 Bytes)，放入显存的开销为 $7 \text{B} \times 2 \text{Bytes} \approx 14 \text{GiB}$。&lt;/p&gt;
&lt;p&gt;(c) 实际的显存开销远不止这些。当前向推理时，除了存储模型参数外，还需要存储中间激活值 (activations)、梯度 (gradients) 以及优化器状态 (optimizer states) 等等，这些都会占用额外的显存空间。当反向传播时，显存的开销需要至少乘个 2 (因为梯度的存储大小近似等于参数大小)，所以实际显存开销会更大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 大模型部署框架对比&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;框架名称&lt;/th&gt;
&lt;th&gt;核心定位&lt;/th&gt;
&lt;th&gt;主要优势&lt;/th&gt;
&lt;th&gt;典型适用场景&lt;/th&gt;
&lt;th&gt;硬件倾向性&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;vLLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;高吞吐量推理先锋&lt;/td&gt;
&lt;td&gt;PagedAttention技术, 高吞吐量, 广泛兼容HuggingFace模型&lt;/td&gt;
&lt;td&gt;高并发在线API服务、长文本生成&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA GPU&lt;/strong&gt; (Linux环境最佳)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TensorRT-LLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;NVIDIA硬件极致优化&lt;/td&gt;
&lt;td&gt;TensorRT深度优化、算子融合、量化引擎, 极致低延迟&lt;/td&gt;
&lt;td&gt;NVIDIA GPU环境，企业级部署，对延迟敏感的应用&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA GPU专属&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SGLang&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;复杂工作流与结构化生成专家&lt;/td&gt;
&lt;td&gt;RadixAttention, 推测解码, 结构化输出原生支持&lt;/td&gt;
&lt;td&gt;多轮对话、Agent智能体、复杂生成逻辑&lt;/td&gt;
&lt;td&gt;支持多硬件平台 (NVIDIA/AMD/国产芯片)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LMDeploy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;全能型高性能推理伙伴&lt;/td&gt;
&lt;td&gt;Turbomind引擎, W4A16量化, 动态批处理，低延迟推理(50ms级别)&lt;/td&gt;
&lt;td&gt;实时应用（如对话系统）、边缘设备部署&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA GPU&lt;/strong&gt;优化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DeepSpeed-MII&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;资源受限环境的救星&lt;/td&gt;
&lt;td&gt;ZeRO-Inference, 动态张量切片, 量化推理 (如FP6量化), CPU卸载, NVMe扩展&lt;/td&gt;
&lt;td&gt;有限资源下的模型部署，支持超长上下文 (128K)&lt;/td&gt;
&lt;td&gt;支持多种硬件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ollama&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;个人开发者的模型管家&lt;/td&gt;
&lt;td&gt;极简本地部署、开箱即用, 内置模型市场，支持GGUF量化格式&lt;/td&gt;
&lt;td&gt;个人开发者本地测试、快速原型验证&lt;/td&gt;
&lt;td&gt;跨平台 (Win/macOS/Linux) , CPU/GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;llama.cpp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;边缘设备与老旧硬件的福音&lt;/td&gt;
&lt;td&gt;CPU推理优化, AVX2/NEON指令集加速, 支持CUDA/OpenCL, 内存占用低&lt;/td&gt;
&lt;td&gt;资源受限环境，边缘设备，纯CPU环境&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;CPU优先&lt;/strong&gt; (支持GPU加速)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TGI (Text Generation Inference)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;企业级稳定服务的守护者&lt;/td&gt;
&lt;td&gt;服务稳定性（内置健康检查、自动故障转移），多GPU扩展（Tensor并行和流水线并行），安全合规&lt;/td&gt;
&lt;td&gt;企业级API服务，AWS SageMaker推理服务，银行智能客服系统&lt;/td&gt;
&lt;td&gt;多硬件平台支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenLLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;多适配器与异构硬件的灵活派&lt;/td&gt;
&lt;td&gt;异构硬件支持（TPU/GPU/CPU混合部署），自定义适配器（LoRA插件热加载）&lt;/td&gt;
&lt;td&gt;云服务提供商（混合云部署），需要连接多个适配器到同一模型的场景&lt;/td&gt;
&lt;td&gt;异构硬件支持&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;5. 为什么现在的大模型都采用 left padding?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这与&lt;strong&gt;推理效率&lt;/strong&gt;有关。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Right padding（右填充）：&lt;/strong&gt; 真实文本在左边，填充在右边。如果一批样本长度差异大，就得等最长的那个跑完，短的输入在后面浪费算力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Left padding（左填充）：&lt;/strong&gt; 真实文本在右边，填充在左边。这样在自回归解码时，大家的最后一个 token（最新预测位置）都能对齐，方便 batch 推理 + KV cache 的并行利用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;尤其是在 vLLM、PagedAttention 这类高效推理框架里，Left Padding 可以显著减少显存和计算的浪费。
所以几乎所有新出的 LLM（Qwen、LLaMA-3、Mistral…）都强制用 Left Padding。&lt;/p&gt;
&lt;p&gt;e.g. 假设有三条句子，长度不一样：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;I like cats&quot; → 3 个 token&lt;/li&gt;
&lt;li&gt;&quot;You love&quot; → 2 个 token&lt;/li&gt;
&lt;li&gt;&quot;They&quot; → 1 个 token&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么 right padding 会是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;I like cats &amp;lt;pad&amp;gt;  (3 tokens + 1 pad)
You love &amp;lt;pad&amp;gt; &amp;lt;pad&amp;gt; (2 tokens + 2 pads)
They &amp;lt;pad&amp;gt; &amp;lt;pad&amp;gt; &amp;lt;pad&amp;gt; (1 token + 3 pads)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而 left padding 会是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;pad&amp;gt; I like cats (1 pad + 3 tokens)
&amp;lt;pad&amp;gt; &amp;lt;pad&amp;gt; You love (2 pads + 2 tokens)
&amp;lt;pad&amp;gt; &amp;lt;pad&amp;gt; &amp;lt;pad&amp;gt; They (3 pads + 1 token)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在推理时，由于最后一个 token（预测点）不对齐，第一句的预测点在 cats 后，第二句在 love 后，第三句在 They 后。这样 batch 推理的时候，就会发生不同步。&lt;/p&gt;
&lt;p&gt;这样的&lt;strong&gt;预测点&lt;/strong&gt;不同步会导致，在自回归推理阶段，每个样本的需要预测的 token 的位置都不一致，无法高效并行计算，造成资源浪费。设想一下，如果同一批中每个句子的上文长度不一致，还能并行推理吗？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. Llama 的技术关键点，以及 Llama 1/2/3 的异同?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LLaMA 技术的关键点&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;LLaMA 系列的设计思路是, &lt;strong&gt;用高效的训练方法 + 更干净的数据集，让相对较小的参数量也能获得较强性能&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;纯 Decoder-only 架构&lt;/strong&gt;：和 GPT 类似，适合自回归生成。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分组查询注意力（Grouped-Query Attention, GQA）&lt;/strong&gt;：降低注意力计算成本，同时保持上下文理解能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;旋转位置编码（RoPE）&lt;/strong&gt;：比传统的绝对/相对位置编码更灵活，支持更长上下文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高效训练策略&lt;/strong&gt;：比如用更多干净的英文/多语种数据，分阶段预训练。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开放权重&lt;/strong&gt;：Meta 的策略是开放参数（不像 GPT 封闭），带动了研究和应用生态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;LLaMA 1 / 2 / 3 的异同&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;📌 LLaMA 1（2023年2月）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;规模&lt;/strong&gt;：7B, 13B, 33B, 65B。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;：开源权重，引爆了开源社区；模型小但性能不俗。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;限制&lt;/strong&gt;：数据主要是英文，缺乏对齐/安全性训练；推理生态还不成熟。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;📌 LLaMA 2（2023年7月）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;规模&lt;/strong&gt;：7B, 13B, 70B。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;改进点&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;数据更大更干净（≈2T token），多语言比例增加。&lt;/li&gt;
&lt;li&gt;训练上下文更长（4K token）。&lt;/li&gt;
&lt;li&gt;引入 &lt;strong&gt;安全性与指令对齐&lt;/strong&gt;（RLHF、SFT）。&lt;/li&gt;
&lt;li&gt;推理时更友好，性能比 LLaMA 1 提升明显。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;影响&lt;/strong&gt;：成为开源界事实标准（很多公司直接基于它二次开发）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;📌 LLaMA 3（2024年4月）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;规模&lt;/strong&gt;：8B, 70B。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;改进点&lt;/strong&gt;（Meta 公布的 roadmap）：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;训练数据规模更大&lt;/strong&gt;（数十万亿 token），多模态扩展。&lt;/li&gt;
&lt;li&gt;上下文长度更长（有望到 8K / 32K）。&lt;/li&gt;
&lt;li&gt;更强的指令对齐，安全性更好。&lt;/li&gt;
&lt;li&gt;重点发力在 &lt;strong&gt;开放生态 + 商用许可&lt;/strong&gt;，和 OpenAI/Anthropic 抗衡。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;7. Transformer 中 FFN 层的作用是什么?&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;注意力（Multi-Head Self-Attention）&lt;/strong&gt;：负责&quot;看别人&quot;，让 token 之间能互相交流信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;前馈网络（Feed-Forward Network, FFN）&lt;/strong&gt;：负责&quot;自己加工&quot;，对每个位置单独做非线性变换。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;FFN 其实就是一个 &lt;strong&gt;位置独立的两层 MLP&lt;/strong&gt;，形式一般是&lt;/p&gt;
&lt;p&gt;$$
FFN(x) = W_2 , \sigma(W_1 x + b_1) + b_2
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$W_1$ 把维度从 $d$ 扩展到更高维（比如 $4d$），增加表达能力。&lt;/li&gt;
&lt;li&gt;激活函数（如 ReLU、GELU）引入 &lt;strong&gt;非线性&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;$W_2$ 再把它投回原来的维度 $d$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;简单来说&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Attn&lt;/strong&gt;：负责“信息交换”，像开会时大家互相讨论。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;FFN&lt;/strong&gt;：负责“个人思考”，每个 token 在吸收别人意见后，还要自己再加工一下。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果只有注意力，模型会“看别人”，但缺乏强的非线性映射能力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;加上 FFN，模型才具备更丰富的 &lt;strong&gt;特征变换能力&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Attention 抓关系，FFN 做加工。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;8. Transformer中为什么FFN要做一个bottleneck的结构?&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;增加非线性表达能力&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果直接用一个 $\mathbb{R}^{d_{model}} \to \mathbb{R}^{d_{model}}$ 的线性层，即 $W x + b$，它本质上就是个线性变换 + 偏置，不会引入新的表达力（和 attention 层没什么区别）。通过 &lt;strong&gt;升维 → 激活函数 → 降维&lt;/strong&gt;，模型就能学习到复杂的、稀疏的非线性特征。&lt;/p&gt;
&lt;p&gt;这跟 &lt;strong&gt;MLP 的隐层作用&lt;/strong&gt;是一样的，只不过这里强制输入输出要保持和 embedding 对齐，所以中间必须走一个“绕路”。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;低成本地提升容量&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;假设输入输出维度 $d_{model}=1024$。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果直接堆叠几个 $1024 \to 1024$ 的线性层，参数量和计算量都大，但每层表达力有限。&lt;/li&gt;
&lt;li&gt;用 bottleneck：先升到 $d_{ff}=4096$，再压回来。这样参数量差不多，但模型能在更高维空间里做非线性组合，然后再压缩到低维。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;和注意力机制互补&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自注意力&lt;/strong&gt;：负责 &lt;strong&gt;跨 token&lt;/strong&gt; 的交互（&quot;谁和谁相关&quot;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FFN&lt;/strong&gt;：负责 &lt;strong&gt;单 token 内部的特征变换&lt;/strong&gt;（&quot;这个 token 自身怎么变化&quot;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;瓶颈结构让每个 token 在局部维度空间内先&quot;扩展信息&quot;，再&quot;压缩&quot;，从而增强单 token 的表示能力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9. 为什么 BERT 的三个输入 Embedding 是直接相加的？为什么不是拼接？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Token Embedding、Position Embedding 和 Segment Embedding(表示这个词属于哪一段（A 或 B），用于区分句对任务) 这三种 Embedding 直接相加，而不是拼接，主要有以下几个原因&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;同维度&lt;/strong&gt;：三个 embedding 的维度是一致的，且不会增加维度大小。而拼接会导致维度增加，需要额外处理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;简化计算&lt;/strong&gt;：相加操作比拼接更简单，计算量更小，有利于提高模型的训练和推理效率。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;线性可分性&lt;/strong&gt;：BERT 的 Transformer encoder 是堆叠的 &lt;strong&gt;线性&lt;/strong&gt; + 注意力 + LayerNorm + FFN。加法 embedding 实质上在 embedding 层做了一个 线性叠加。后续的注意力和 FFN 会自动学习如何权衡三种信息（位置、类型、语义）。类似于给网络提供了 三个通道的输入，网络可以自己决定如何使用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>大模型中的强化学习</title><link>https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/llmrl/rl4llm/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/llmrl/rl4llm/</guid><pubDate>Mon, 25 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;大模型的预训练（next-token prediction）在形式上可以看作一种行为克隆（Behavioral Cloning, BC）—— 给定输入 $x$（上下文），最大化正确输出 $y$ 的似然。&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}&lt;em&gt;{BC} = -\mathbb{E}&lt;/em&gt;{(x,y) \sim D} [\log P(y|x)]
$$&lt;/p&gt;
&lt;p&gt;其中，$D$ 是训练数据集，$x$ 是输入，$y$ 是目标输出。通过最小化该损失，模型学到语言建模与世界知识。&lt;/p&gt;
&lt;p&gt;为了提升模型在下游任务中的可用性与对齐能力，还需要后训练（post-training）。后训练常见两条主线：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;监督微调（SFT）&lt;/strong&gt;：用高质量示范数据，让模型学会指令遵循与回答风格。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;偏好对齐（preference alignment）&lt;/strong&gt;：让模型在“多个候选回答”里更倾向人类偏好的那个。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SFT 目标函数仍是最大似然：&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}&lt;em&gt;{SFT} = -\mathbb{E}&lt;/em&gt;{(x,y) \sim D_{SFT}} [\log P(y|x)]
$$&lt;/p&gt;
&lt;p&gt;工程上常见的完整链路是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;预训练 → base 模型&lt;/li&gt;
&lt;li&gt;SFT → 参考/起点策略 $\pi_{\mathrm{ref}}$&lt;/li&gt;
&lt;li&gt;学偏好（训练奖励模型 RM，或直接用偏好优化）&lt;/li&gt;
&lt;li&gt;用 RL（PPO/GRPO 等）进一步对齐&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;为什么使用 RL 做后训练&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;验证比生成更便宜&lt;/strong&gt;：人类更容易判断“哪个更好”，而不是写出最优答案&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标更贴近真实需求&lt;/strong&gt;：很多指标不可微/不可监督（有用性、安全性、工具成功率、通过率等）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;序列决策与延迟回报&lt;/strong&gt;：回答是一个 trajectory，奖励往往在末端给出（典型 credit assignment 问题）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;探索与超越示范&lt;/strong&gt;：不只拟合示范分布，而是围绕奖励探索更优策略&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一句话总结：SFT 在拟合 $P(y|x)$，而 RL 允许你显式优化你真正关心的 $R(x,y)$。&lt;/p&gt;
&lt;h2&gt;RL-LLM 建模&lt;/h2&gt;
&lt;h3&gt;状态、动作与确定性转移&lt;/h3&gt;
&lt;p&gt;强化学习问题可以抽象为 MDP。对 LLM 来说，这个 MDP 很“干净”：状态就是已生成的 token 序列，动作是下一个 token。&lt;/p&gt;
&lt;p&gt;初始状态 $s_0$ 可视为一个确定性的 Dirac 分布：输入 prompt 就是初始状态（实现中通常有 &lt;code&gt;&amp;lt;BOS&amp;gt;&lt;/code&gt;）：&lt;/p&gt;
&lt;p&gt;$$
s_0 = \text{&amp;lt;BOS&amp;gt;} \oplus \mathrm{prompt}.
$$&lt;/p&gt;
&lt;p&gt;模型是自回归策略：&lt;/p&gt;
&lt;p&gt;$$
a_t \sim \pi_\theta(a_t\mid s_t),\quad s_{t+1} = s_t \oplus a_t.
$$&lt;/p&gt;
&lt;p&gt;对应的确定性动力学为&lt;/p&gt;
&lt;p&gt;$$
p(s_{t+1}\mid s_t,a_t)=\delta(s_{t+1}=s_t\oplus a_t).
$$&lt;/p&gt;
&lt;p&gt;当模型输出 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; 或达到最大长度，该幕结束。&lt;/p&gt;
&lt;p&gt;将一次生成过程记作轨迹 $\tau=(s_0,a_1,\dots,a_T)$。策略的序列概率可分解为&lt;/p&gt;
&lt;p&gt;$$
\pi_\theta(\mathbf{a}|s_0)=\prod_{t=1}^{T}\pi_\theta(a_t|s_t),\quad s_t=s_0\oplus \mathbf{a}_{1:t-1}.
$$&lt;/p&gt;
&lt;h3&gt;回报与目标&lt;/h3&gt;
&lt;p&gt;最一般地，回报是逐步奖励之和：&lt;/p&gt;
&lt;p&gt;$$
R(\tau)=\sum_{t=1}^{T}\gamma^{t-1} r_t.
$$&lt;/p&gt;
&lt;p&gt;训练目标为&lt;/p&gt;
&lt;p&gt;$$
J(\theta)=\mathbb{E}&lt;em&gt;{s_0\sim D,;\tau\sim\pi&lt;/em&gt;\theta(\cdot\mid s_0)}[R(\tau)].
$$&lt;/p&gt;
&lt;p&gt;在 LLM 对齐里，$r_t$ 往往是&lt;strong&gt;末端奖励&lt;/strong&gt;（例如 RM 打分、验证器通过/不通过），也可能是&lt;strong&gt;过程奖励&lt;/strong&gt;（例如 PRM 给每一步 reasoning 打分）。&lt;/p&gt;
&lt;h3&gt;KL 正则：让 RL 不“跑偏”&lt;/h3&gt;
&lt;p&gt;直接最大化奖励容易导致奖励过优化（reward hacking），因此常引入参考策略 $\pi_{\mathrm{ref}}$ 做 KL 约束。&lt;/p&gt;
&lt;p&gt;常见写法是无约束正则化目标：&lt;/p&gt;
&lt;p&gt;$$
\max_\theta;\mathbb{E}[R(\tau)]-\beta,\mathbb{E}\left[D_{\mathrm{KL}}\big(\pi_\theta(\cdot|s),|,\pi_{\mathrm{ref}}(\cdot|s)\big)\right].
$$&lt;/p&gt;
&lt;p&gt;实现中常用“采样 KL”的逐 token 近似：&lt;/p&gt;
&lt;p&gt;$$
\mathrm{KL}&lt;em&gt;{\mathrm{sample}}(\tau)\approx\sum&lt;/em&gt;{t=1}^{T}\left(\log\pi_\theta(a_t|s_t)-\log\pi_{\mathrm{ref}}(a_t|s_t)\right).
$$&lt;/p&gt;
&lt;p&gt;并把 KL 惩罚并入奖励做 shaping：&lt;/p&gt;
&lt;p&gt;$$
ilde r_t = r_t - \beta\left(\log\pi_\theta(a_t|s_t)-\log\pi_{\mathrm{ref}}(a_t|s_t)\right).
$$&lt;/p&gt;
&lt;h3&gt;长度偏置（实践里很常见）&lt;/h3&gt;
&lt;p&gt;当奖励只在末端给出时，回答长度会显著影响优化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模型可能为了“覆盖更多得分点”而变得啰嗦&lt;/li&gt;
&lt;li&gt;或者为了避免犯错而过早 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见处理：长度归一化、对 &lt;code&gt;&amp;lt;EOS&amp;gt;&lt;/code&gt; 的额外奖励/惩罚、或在 RM 中显式惩罚冗长/跑题。&lt;/p&gt;
&lt;h2&gt;奖励模型&lt;/h2&gt;
&lt;p&gt;奖励函数/奖励模型决定了“对齐到底对齐什么”。常见类别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RLVR（验证奖励）&lt;/strong&gt;：数学/编程题用 verifier 直接判对错（0/1 或更细粒度）。优点是客观，缺点是稀疏与可投机。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PRM/过程奖励&lt;/strong&gt;：给 reasoning 中间步骤打分，缓解稀疏奖励与 credit assignment。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RLHF（人类偏好）&lt;/strong&gt;：人类在多候选里做比较，训练 RM 或直接偏好优化。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RLAIF（AI 偏好）&lt;/strong&gt;：用强模型代替人类偏好标注，规模大但可能引入偏差。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GRM（生成式反馈）&lt;/strong&gt;：用生成式 LLM 给反馈/评分，本质是一种可扩展的自动评估信号。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;稀疏奖励问题很关键：奖励往往只能在序列结束时给出，这会导致智能体更容易修改末尾而忽视中间推理过程。这也是 PRM 被提出的重要动机。&lt;/p&gt;
&lt;h3&gt;RLHF 奖励模型（RM）的经典训练：Bradley–Terry&lt;/h3&gt;
&lt;p&gt;偏好数据通常是 $(x,y^+,y^-)$：同一 prompt $x$ 下，人类更喜欢 $y^+$ 胜过 $y^-$。&lt;/p&gt;
&lt;p&gt;令奖励模型输出标量分数 $r_\phi(x,y)$，用 Bradley–Terry（BT）定义偏好概率：&lt;/p&gt;
&lt;p&gt;$$
P(y^+\succ y^-\mid x)=\sigma\big(r_\phi(x,y^+)-r_\phi(x,y^-)\big).
$$&lt;/p&gt;
&lt;p&gt;最大似然训练对应的损失为&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}&lt;em&gt;{\mathrm{RM}}(\phi)=-\mathbb{E}\left[\log\sigma\big(r&lt;/em&gt;\phi(x,y^+)-r_\phi(x,y^-)\big)\right].
$$&lt;/p&gt;
&lt;p&gt;训练好 RM 后，常用 $R(x,y)=r_\phi(x,y)$ 作为末端奖励（再加 KL shaping）。&lt;/p&gt;
&lt;h3&gt;奖励模型的常见坑&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;reward hacking&lt;/strong&gt;：策略学会利用 RM 的漏洞（模板、重复、虚假自信）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分布偏移&lt;/strong&gt;：RL 采样把策略推到 RM 训练分布外，RM 可靠性下降&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;尺度漂移&lt;/strong&gt;：RM 分数尺度变化会导致训练不稳定（需要归一化与 KL 控制）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多目标冲突&lt;/strong&gt;：有用性/安全性/简洁性等可能互相矛盾，单一标量奖励难以表达&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;具体的 RL 算法&lt;/h2&gt;
&lt;h3&gt;REINFORCE（策略梯度基础）&lt;/h3&gt;
&lt;p&gt;对数导数技巧给出&lt;/p&gt;
&lt;p&gt;$$
\nabla_\theta J(\theta)=\mathbb{E}&lt;em&gt;{\tau\sim\pi&lt;/em&gt;\theta}\left[R(\tau)\nabla_\theta\log\pi_\theta(\tau)\right].
$$&lt;/p&gt;
&lt;p&gt;而&lt;/p&gt;
&lt;p&gt;$$
\log\pi_\theta(\tau)=\sum_{t=1}^{T}\log\pi_\theta(a_t|s_t),
$$&lt;/p&gt;
&lt;p&gt;所以实现上就是对每个 token 的 logprob 做加权。&lt;/p&gt;
&lt;p&gt;为了降低方差，引入 baseline $b(s_t)$（常取 value model $V(s_t)$）：&lt;/p&gt;
&lt;p&gt;$$
\nabla_\theta J(\theta)\approx \frac{1}{G}\sum_{i=1}^{G}\sum_{t=1}^{T_i}\big(\hat G_{i,t}-b(s_{i,t})\big),\nabla_\theta\log\pi_\theta(a_{i,t}|s_{i,t}).
$$&lt;/p&gt;
&lt;p&gt;其中 $\hat G_{i,t}$ 是从 $t$ 时刻往后的折扣回报。&lt;/p&gt;
&lt;h3&gt;Actor-Critic（PPO/RLHF 的常见基座）&lt;/h3&gt;
&lt;p&gt;工业里更常用 Actor-Critic：训练一个 value model $V_\psi(s_t)$ 预测回报，并构造优势&lt;/p&gt;
&lt;p&gt;$$
A_t = \hat G_t - V_\psi(s_t).
$$&lt;/p&gt;
&lt;p&gt;优势可以用 GAE（Generalized Advantage Estimation）平衡偏差与方差。&lt;/p&gt;
&lt;h3&gt;PPO（RLHF 最常见的在线 RL）&lt;/h3&gt;
&lt;p&gt;PPO 维护采样时的旧策略 $\pi_{\mathrm{old}}$，通过 clipping 限制更新幅度。序列级 ratio 可写为&lt;/p&gt;
&lt;p&gt;$$
\frac{\pi_\theta(\mathbf{a}|s_0)}{\pi_{\mathrm{old}}(\mathbf{a}|s_0)}=\exp\left(\sum_{t=1}^{T}\big(\log\pi_\theta(a_t|s_t)-\log\pi_{\mathrm{old}}(a_t|s_t)\big)\right).
$$&lt;/p&gt;
&lt;p&gt;实践中常用 token 级 PPO（每个 token 一个 ratio），更稳定且并行友好。原始 PPO surrogate 仍可写为&lt;/p&gt;
&lt;p&gt;$$
J(\theta)=\mathbb{E}\left[\min\left(r(\theta)A,;\mathrm{clip}(r(\theta),1-\epsilon,1+\epsilon)A\right)\right].
$$&lt;/p&gt;
&lt;p&gt;完整训练一般还包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;value loss（拟合 $V_\psi$，常带 value clipping）&lt;/li&gt;
&lt;li&gt;entropy bonus（避免过早塌缩）&lt;/li&gt;
&lt;li&gt;KL penalty / early stop（把策略限制在 $\pi_{\mathrm{ref}}$ 附近）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;GRPO（Group Relative Policy Optimization）&lt;/h3&gt;
&lt;p&gt;GRPO 可以理解为“没有 critic 的相对优势策略优化”：对同一 prompt 采样一组回答（大小 $G$），组内归一化得到相对优势 $A_i$，再用策略梯度更新。&lt;/p&gt;
&lt;p&gt;组内优势一个常见构造是&lt;/p&gt;
&lt;p&gt;$$
A_i=\frac{R_i-\mu_R}{\sigma_R+\epsilon},\quad \mu_R=\frac{1}{G}\sum_{j=1}^{G}R_j.
$$&lt;/p&gt;
&lt;p&gt;然后用&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}&lt;em&gt;{\mathrm{GRPO}}=-\mathbb{E}\left[\sum&lt;/em&gt;{i=1}^{G}\sum_{t=1}^{T_i}A_i\log\pi_\theta(a_{i,t}|s_{i,t})\right]+\beta,\mathbb{E}[\mathrm{KL}]
$$&lt;/p&gt;
&lt;p&gt;来更新策略。直觉是：同一个 prompt 里“相对更好”的回答被提升概率，“相对更差”的被压低。&lt;/p&gt;
&lt;h3&gt;DPO（Direct Preference Optimization，修正公式）&lt;/h3&gt;
&lt;p&gt;DPO 是直接用偏好数据优化策略的方法之一，避免了在线采样与训练 RM。&lt;/p&gt;
&lt;p&gt;给定 $(x,y^+,y^-)$，定义&lt;/p&gt;
&lt;p&gt;$$
\Delta_\theta=\log\pi_\theta(y^+|x)-\log\pi_\theta(y^-|x),\quad
\Delta_{\mathrm{ref}}=\log\pi_{\mathrm{ref}}(y^+|x)-\log\pi_{\mathrm{ref}}(y^-|x).
$$&lt;/p&gt;
&lt;p&gt;经典 DPO loss 为&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}&lt;em&gt;{\mathrm{DPO}}(\theta)=-\mathbb{E}\left[\log\sigma\left(\beta(\Delta&lt;/em&gt;\theta-\Delta_{\mathrm{ref}})\right)\right].
$$&lt;/p&gt;
&lt;p&gt;这里 $\beta$ 控制偏好强度（也可理解为温度/正则强度）。&lt;/p&gt;
&lt;h2&gt;工程实践：训练时常见关键细节&lt;/h2&gt;
&lt;h3&gt;1) Online RL（PPO/GRPO）的数据流&lt;/h3&gt;
&lt;p&gt;一个标准循环：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从 prompt 数据集采样一批 $x$&lt;/li&gt;
&lt;li&gt;用当前策略采样回答 $y$（通常每个 prompt 采样多条）&lt;/li&gt;
&lt;li&gt;计算奖励：RM/验证器/规则/工具成功率等&lt;/li&gt;
&lt;li&gt;计算参考模型 logprob（得到 KL shaping）&lt;/li&gt;
&lt;li&gt;构造优势：critic（PPO）或组内相对优势（GRPO）&lt;/li&gt;
&lt;li&gt;反向传播更新 actor（以及可选的 critic/value model）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2) KL controller（非常实用）&lt;/h3&gt;
&lt;p&gt;固定 $\beta$ 常常不稳。常见做法是设定目标 KL（例如每 token KL 在某范围），动态调节 $\beta$：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;KL 过大：增大 $\beta$&lt;/li&gt;
&lt;li&gt;KL 过小：减小 $\beta$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3) Reward/Advantage 归一化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;reward scale 直接决定梯度尺度，通常做 running mean/std 归一化&lt;/li&gt;
&lt;li&gt;advantage 常做 batch 标准化（均值 0 方差 1）提升稳定性&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4) 监控指标（不监控基本必炸）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;平均 reward、reward 方差&lt;/li&gt;
&lt;li&gt;平均 KL / 每 token KL&lt;/li&gt;
&lt;li&gt;回答长度分布（是否越来越啰嗦/越来越短）&lt;/li&gt;
&lt;li&gt;拒答率、安全触发率&lt;/li&gt;
&lt;li&gt;离线任务集：正确率、事实性、毒性&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5) 常见失败模式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;“刷格式/刷模板”：RM 偏好被投机&lt;/li&gt;
&lt;li&gt;“啰嗦”：为了覆盖得分点而变长&lt;/li&gt;
&lt;li&gt;“自信胡编”：用看起来像的内容骗过偏好模型&lt;/li&gt;
&lt;li&gt;“过拟合验证器”：RLVR 场景下出现投机取巧&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>贪心算法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/greedy/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/greedy/</guid><pubDate>Sat, 23 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;贪心算法（Greedy Algorithm）是一种在每一步选择中都采取在当前状态下最好或最优（即最有利）的选择，从而希望导致结果是全局最好或最优的算法。贪心算法在某些问题上能够得到最优解，但并不适用于所有问题。&lt;/p&gt;
&lt;h1&gt;零钱兑换: 一个简单的贪心例子&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/coin-change/description/&quot;&gt;LeetCode: 322. 零钱兑换&lt;/a&gt; 给定 $n$ 种硬币，第 $i$ 种硬币的面值为 $coins[i - 1]$ ，目标金额为 $amount$ ，每种硬币可以重复选取，问能够凑出目标金额的最少硬币数量。如果无法凑出目标金额，则返回 −1 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定目标金额，贪心地选择不大于它且最接近它的硬币面值，直到目标金额为 0 或无法继续选择。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    int coinChange(vector&amp;lt;int&amp;gt;&amp;amp; coins, int amount) {
        sort(coins.begin(), coins.end(), greater&amp;lt;int&amp;gt;());
        int cnt = 0;
        for (int coin : coins) {
            while (amount &amp;gt;= coin) {
                amount -= coin;
                cnt++;
            }
        }
        return amount == 0 ? cnt : -1;        
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们给一些例子&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;coins = [1, 2, 5], amount = 11&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;选择 5，amount = 6，cnt = 1&lt;/li&gt;
&lt;li&gt;选择 5，amount = 1，cnt = 2&lt;/li&gt;
&lt;li&gt;选择 1，amount = 0，cnt = 3&lt;/li&gt;
&lt;li&gt;返回 3，结果正确 ✅&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;coins = [1, 3, 4], amount = 6&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;选择 4，amount = 2，cnt = 1&lt;/li&gt;
&lt;li&gt;选择 1，amount = 1，cnt = 2&lt;/li&gt;
&lt;li&gt;选择 1，amount = 0，cnt = 3&lt;/li&gt;
&lt;li&gt;返回 3，结果错误 ❌ (正确答案是 2，选择两个 3)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以看出，贪心算法并不总是能得到最优解。在零钱兑换这个例子中，只有满足是规范币制的情况下，贪心算法才能得到最优解。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;规范币制:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;充分条件1： 可整除链。每个大币值都是前一个币值的整数倍。例如 $[1, 2, 4, 8, 16]$。&lt;/li&gt;
&lt;li&gt;充分条件2： 等比数列。币值按固定比例增长, $[1, b, b^2, \ldots]$。&lt;/li&gt;
&lt;li&gt;充分条件3： 超递增序列。每个币值都大于前面所有币值之和, $[1, 2, 4, 8, 16]$。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;组合优化问题中的贪心算法&lt;/h1&gt;
&lt;p&gt;组合优化问题通常涉及在有限的离散选项中选择一个子集，以最大化或最小化某个目标函数。贪心算法在解决某些组合优化问题时表现出色，尤其是当问题满足&lt;strong&gt;贪心选择性质&lt;/strong&gt;和&lt;strong&gt;最优子结构性质&lt;/strong&gt;时。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;贪心选择性质&lt;/strong&gt;：局部最优选择可以导致全局最优解。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;设实例 $I$ 的可行解集合为 $\mathcal{F}(I)\subseteq 2^{E(I)}$，目标为最小化 $f_I(S)$。设 $C(I)$ 为可选原子决策集合，$\text{Feasible}(I,a)$ 判定在当前前缀下选择 $a\in C(I)$ 的可行性，$R(I,a)$ 为将 $a$ 固定后的约化子问题，$\oplus$ 为解的合成算子。若存在评分函数 $s_I:C(I)\to\mathbb{R}$，使得存在某个最优解 $S^&lt;em&gt;\in\mathcal{F}(I)$ 与某个 $a^&lt;/em&gt;\in C(I)$ 满足
$$
a^* \in \argmin_{a\in C(I),,\text{Feasible}(I,a)} s_I(a),
$$
且存在 $S&apos;\in \mathcal{F}(R(I,a^&lt;em&gt;))$ 使 $S^&lt;/em&gt;=a^*\oplus S&apos;$，则称 $I$ 在评分 $s_I$ 下具有贪心选择性质。换言之，至少有一个最优解的首个决策等于贪心选择。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;最优子结构性质&lt;/strong&gt;：问题的最优解包含其子问题的最优解。
$$
\text{OPT}(I)=\min_{S\in\mathcal{F}(I)} f_I(S)
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;若存在约化算子 $R$、合成算子 $\oplus$ 与合并函数 $\text{Combine}$，使对一切实例 $I$ 有
$$
\text{OPT}(I)=\min_{a\in C(I),,\text{Feasible}(I,a)}, \text{Combine}\big(I,a,,\text{OPT}(R(I,a))\big)
$$
且若 $S^*=a\oplus T$ 是 $I$ 的某个最优解分解，则 $T$ 必为子问题 $R(I,a)$ 的最优解，即
$$
T\in \argmin_{S\in \mathcal{F}(R(I,a))} f_{R(I,a)}(S)
$$
则称该问题具有最优子结构性质。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;上述的两个性质定义了经典贪心算法的适用范围。需要注意的是，在经典贪心算法中，&lt;strong&gt;局部最优选择往往是一经确定就不再修改的&lt;/strong&gt;（这与前述例子中的贪心策略不同，不过确实有 &lt;em&gt;“带反悔的贪心算法”&lt;/em&gt; 的变种）。&lt;/p&gt;
&lt;p&gt;通常而言，证明一个问题可以用贪心算法解决，往往需要先证明该问题满足贪心选择性质和最优子结构性质。这并不是一件容易的事，因此对于工程领域而言，往往记住有哪些经典的贪心算法适用问题就足够了。&lt;/p&gt;
&lt;h2&gt;最大切分乘积&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/integer-break/description/&quot;&gt;LeetCode: 343. 整数拆分&lt;/a&gt; 给定一个正整数 $n$ ，将其切分为 $k$ 个正整数的和 ($k \ge 2$)，求切分后所有整数的乘积的最大值是多少。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设将数 $n$ 切分为 $k$ 个正整数 $n_1, n_2, \ldots, n_k$ 的和，记为
$$
n = \sum_{i=1}^{k} n_i
$$
目标是最大化
$$
P = \prod_{i=1}^{k} n_i
$$
根据均值不等式
$$
\frac{n}{k} = \frac{1}{k}\sum_{i=1}^{k} n_i \geq \sqrt[k]{\prod_{i=1}^{k} n_i} = \sqrt[k]{P}
$$
等号成立当且仅当 $n_1 = n_2 = \ldots = n_k$ 时成立。因此，为了最大化乘积 $P$，我们应尽量使各个切分的整数相等。&lt;/p&gt;
&lt;p&gt;假设将 $n$ 用 $x$ 为因子等分成 $k$ 份，即 $n = kx$，则乘积为
$$
f(x) = x^k = x^{\frac{n}{x}} = e^{\frac{n}{x} \ln x}
$$
为了最大化 $f(x)$，直接对 $g(x) = \frac{1}{x} \ln x$ 求导
$$
g&apos;(x) = \frac{1 - \ln x}{x^2}
$$
显然，当 $x = e$ 时，$g&apos;(x) = 0$，函数 $g(x)$ 在 $x = e$ 处取得最大值。&lt;/p&gt;
&lt;p&gt;考虑到切分因子必须为整数，我们选择最接近 $e$ 的整数，即 $2$ 和 $3$。通过比较可以发现，使用 $3$ 作为切分因子通常能获得更大的乘积。
$$
\begin{align*}
f(3) = 3^{\frac{n}{3}} &amp;gt; f(2) = 2^{\frac{n}{2}}
\end{align*}
$$&lt;/p&gt;
&lt;p&gt;因此，贪心策略为&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;尽可能多地使用 $3$ 进行切分。&lt;/li&gt;
&lt;li&gt;当剩余部分为 $2$ 时 ($n \text{ mod } 3 = 2$)，直接使用 $2$。&lt;/li&gt;
&lt;li&gt;当剩余部分为 $1$ 时 ($n \text{ mod } 3 = 1$)，将一个 $3$ 与这个 $1$ 合并然后拆分为 $2 + 2$，因为 $3 \times 1 &amp;lt; 2 \times 2$。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    int integerBreak(int n) {
        if (n &amp;lt;= 3) return n - 1;
        int prod = 1;
        while (n &amp;gt; 4) {
            // 当 n % 3 == 1 时，循环最终会停在 n == 4
            // 此时不再拆分 3，而是直接乘以 4
            // 符合下面的代码
            prod *= 3;
            n -= 3;
        } 
        prod *= n;
        return prod;
    }
};

// 或者直接使用数学公式
class Solution {
public:
    int integerBreak(int n) {
        if (n &amp;lt;= 3) return n - 1;
        int a = n / 3, b = n % 3;
        if (b == 0) return pow(3, a);
        if (b == 1) return pow(3, a - 1) * 4;
        return pow(3, a) * 2;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;分数背包&lt;/h2&gt;
&lt;p&gt;给定 $n$ 个物品，和一个容量为 $capacity$ 的背包。其中第 $i$ 个物品的重量为 $weight[𝑖 − 1]$，价值为 $value[𝑖 − 1]$。每个物品只能选择一次，但可以选择物品的一部分，价值根据选择的重量比例计算，求在限定背包容量下背包中物品的最大价值。&lt;/p&gt;
&lt;p&gt;注意这里是分数背包，即每个物品不用全部装入背包，可以选择部分装入。例如，面前有 10kg 的黄金，5kg 的白银，背包容量为 7kg，可以选择装入 5kg 黄金和 2kg 白银（分别是原有黄金和白银的 1/2 和 2/5）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;考虑&lt;strong&gt;使用最大化单位重量下的物品价值&lt;/strong&gt;的贪心策略。由于是分数背包，因此这样的贪心选择必然是最优的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;double fracKnapsack(vector&amp;lt;int&amp;gt;&amp;amp; weights, vector&amp;lt;int&amp;gt;&amp;amp; values, int capacity) {
    int n = weights.size();
    vector&amp;lt;pair&amp;lt;double, int&amp;gt;&amp;gt; vp; // (value/weight, index)
    for (int i = 0; i &amp;lt; n; i++) {
        vp.push_back({(double)values[i] / weights[i], i});
    }
    sort(vp.rbegin(), vp.rend()); // 按照单位重量价值从大到小排序
    double totalValue = 0.0;
    for (const auto&amp;amp; [ratio, idx] : vp) {
        if (capacity &amp;lt;= 0) break;
        int takeWeight = min(capacity, weights[idx]);
        totalValue += takeWeight * ratio;
        capacity -= takeWeight;
    }
    return totalValue;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最大容量问题&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.cn/problems/container-with-most-water/description/&quot;&gt;LeetCode: 11. 盛最多水的容器&lt;/a&gt; 输入一个长度为 $n$ 的数组 $height$，其中的每个元素代表一个垂直隔板的高度。数组中的任意两个隔板，以及它们之间的空间可以组成一个容器。容器的容量等于所围成的面积大小。求如何在数组中选择两个隔板，使得组成的容器的容量最大。返回最大容量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这题自然可以用穷举求解，但是采用贪心法可以缩小搜索空间。&lt;/p&gt;
&lt;p&gt;使用左右双指针，左指针 $left$ 指向数组开头，右指针 $right$ 指向数组结尾。计算当前容器的容量，并更新最大容量。然后每次向内移动较短的指针，直至两个指针相遇。在这个过程中记录最大容量。&lt;/p&gt;
&lt;p&gt;这里每次移动的是较短的指针，是因为如果移动较长的指针，容量必定是不会增加的（因为容器的有效高度由短板决定，移动长板不会增加短板的高度，只会减少宽度）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    int maxArea(vector&amp;lt;int&amp;gt;&amp;amp; height) {
        int left = 0, right = height.size() - 1;
        int max_vol = 0;
        while (left &amp;lt; right) {
            int vol = min(height[left], height[right]) * (right - left);
            max_vol = max(max_vol, vol);
            if (height[left] &amp;gt; height[right]) right--;
            else left++;
        }
        return max_vol;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;活动选择问题 (多区间调度问题)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.cn/problems/non-overlapping-intervals/description/&quot;&gt;LeetCode: 435. 无重叠区间&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode.cn/problems/maximum-length-of-pair-chain/description/&quot;&gt;LeetCode: 646. 最长数对链&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;给定若干半开区间 $[s_i, f_i)$，选出最多个互不重叠的区间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将区间按结束时间 $f$ 升序排序。&lt;/li&gt;
&lt;li&gt;依次扫描，若当前区间的开始时间 $s \geq last_end$，则选择该区间，并更新 $last_end = f$。&lt;/li&gt;
&lt;li&gt;该策略同时用于 435/646；时间复杂度 $O(n \log n)$（排序）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;正确性要点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;贪心选择性质：最早结束的区间留给后续最多的可用时间。&lt;/li&gt;
&lt;li&gt;最优子结构：选择最早结束的区间后，剩余问题在右侧子区间上同样最优。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// 435. 无重叠区间：返回最少删除数
class Solution435 {
public:
    int eraseOverlapIntervals(vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; intervals) {
        if (intervals.empty()) return 0;
        sort(intervals.begin(), intervals.end(),
             [](const auto&amp;amp; a, const auto&amp;amp; b){ return a[1] &amp;lt; b[1]; });
        int cnt = 0, lastEnd = INT_MIN;
        for (auto&amp;amp; it : intervals) {
            if (it[0] &amp;gt;= lastEnd) { // 选
                cnt++;
                lastEnd = it[1];
            }
        }
        return (int)intervals.size() - cnt; // 删除 = 总数 - 可选最大数
    }
};

// 646. 最长数对链：返回可选最大数
class Solution646 {
public:
    int findLongestChain(vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; pairs) {
        sort(pairs.begin(), pairs.end(),
             [](const auto&amp;amp; a, const auto&amp;amp; b){ return a[1] &amp;lt; b[1]; });
        int cnt = 0, lastEnd = INT_MIN;
        for (auto&amp;amp; p : pairs) {
            if (p[0] &amp;gt;= lastEnd) { // 选
                cnt++;
                lastEnd = p[1];
            }
        }
        return cnt;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;其他经典的贪心算法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Huffman 编码&lt;/strong&gt;：用于数据压缩，通过构建最优前缀码树来最小化编码长度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prim 和 Kruskal 算法&lt;/strong&gt;：用于寻找加权无向图的最小生成树。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dijkstra 算法&lt;/strong&gt;：用于寻找加权有向图中单源最短路径。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;数值优化中的贪心算法&lt;/h1&gt;
&lt;p&gt;数值优化问题通常涉及在连续空间中寻找函数的最优值。贪心算法在某些数值优化问题中也能发挥作用，尤其是在高维空间中。&lt;/p&gt;
&lt;h2&gt;逐步坐标下降优化&lt;/h2&gt;
&lt;p&gt;假设我们有一个标量值函数 $f(x), x \in \mathbb{R}^n$，$n$ 是一个非常大的数。我们想要找到 $f(x)$ 的最小值。假设这里我们不能使用梯度下降法，因为计算梯度涉及到非常复杂的微分计算，难以快速得到梯度信息（或者压根就是不可微的一个函数）。&lt;/p&gt;
&lt;p&gt;我们考虑一种简单的贪心策略：&lt;strong&gt;逐步坐标下降&lt;/strong&gt;（Coordinate Descent）。这种方法的核心思想是每次只优化一个变量，保持其他变量不变。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;逐步坐标下降算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;一个标量值函数 $f(x)$ 和初始点 $x^{(0)} \in \mathbb{R}^n$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;最小值点 $x^*$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Procedure:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化 $x = x^{(0)}$, $k = 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;while&lt;/strong&gt; 收敛条件不满足 &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;for&lt;/strong&gt; $i = 1$ to $n$ &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;     找到 $x_i$ 的最优点 $x_i^{(k)}$，求解 $x_i^{(k)} = \argmin_{x_i} f(x_1^{(k)}, x_2^{(k)}, \ldots, x_{i-1}^{(k)}, x_i, x_{i+1}^{(k-1)}, \ldots, x_n^{(k-1)})$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;   $k = k + 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;构造最终解 $x^* = (x_1^{(k)}, x_2^{(k)}, \ldots, x_n^{(k)})$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;返回最优解 $x^*$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;很显然，这种贪心的方法并不能保证全局最优解，但在某些情况下，它可以快速找到一个较好的局部最优解。&lt;/p&gt;
&lt;p&gt;可以证明，当满足 $f(x)$ 是凸函数时，逐步坐标下降法能够收敛到全局最优解。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;设 $x^*$ 是 $f(x)$ 的全局最优解，$x^{(k)}$ 是第 $k$ 次迭代的解。根据逐步坐标下降法的定义，我们有：
$$f(x^{(k+1)}) \leq f(x^{(k)})$$
因为每次迭代都选择了当前坐标的最优值。&lt;/p&gt;
&lt;p&gt;由于 $f(x)$ 是凸函数，且 $f(x)$ 在每次迭代中单调递减且有下界（因为 $f(x)$ 有最小值），根据单调有界定理，序列 ${f(x^{(k)})}$ 收敛。&lt;/p&gt;
&lt;p&gt;考虑到凸函数有且只有一个最小值，因此，逐步坐标下降法在 $f(x)$ 是凸函数的条件下，能够收敛到全局最优解。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;问题: 如果函数 $f(x)$ 不是凸函数呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在这种情况下，逐步坐标下降法可能会陷入局部最优解，无法保证找到全局最优解。这是因为非凸函数具有不止一个的极小值点。由上述迭代情况的分析，坐标下降法可以收敛到不同的局部最优解，具体取决于初始点 $x^{(0)}$ 的选择。这说明了贪心策略在非凸优化问题或其他优化问题中的局限性。&lt;/p&gt;
&lt;h1&gt;参考资料&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hello-algo.com/&quot;&gt;Krahets. Hello 算法. 2024. Hello Algo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jeffe.cs.illinois.edu/teaching/algorithms/&quot;&gt;Jeff Erickson. Algorithms. 2019. University of Illinois Urbana-Champaign&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>二叉树</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/binarytree/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/binarytree/</guid><pubDate>Wed, 30 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;二叉树是数据结构中的重要内容，其操作主要依赖于遍历思想，包括深度优先搜索（DFS）和广度优先搜索（BFS）。本笔记将围绕二叉树的常见问题，按照原理和例题的模式进行组织。&lt;/p&gt;
&lt;h2&gt;树的遍历与属性&lt;/h2&gt;
&lt;p&gt;树的遍历是解决大多数二叉树问题的基础。通过递归或迭代的方式，我们可以访问树中的每一个节点，从而检查或计算树的各种属性，如深度、节点总数、是否对称等。&lt;/p&gt;
&lt;h3&gt;深度/广度优先搜索&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;深度优先搜索 (DFS)&lt;/strong&gt;：优先访问子树，直到最深的节点，然后回溯。通常使用递归（前序、中序、后序遍历）或栈实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;广度优先搜索 (BFS)&lt;/strong&gt;：按层级顺序访问节点，从根节点开始，逐层向下。通常使用队列实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 100. 相同的树&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 判断两棵树是否相同，可以通过深度优先搜索（DFS）同时遍历两棵树。在每一步递归中，我们比较当前两个节点的值是否相等，并递归地检查它们的左右子树是否也分别相同。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if (!p &amp;amp;&amp;amp; !q) return true; // 两者都为空，相同
        if (!p || !q || p-&amp;gt;val != q-&amp;gt;val) return false; // 一个为空或值不同，不相同
        
        // 递归比较左右子树
        return isSameTree(p-&amp;gt;left, q-&amp;gt;left) &amp;amp;&amp;amp; isSameTree(p-&amp;gt;right, q-&amp;gt;right);
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 110. 平衡二叉树&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 一棵平衡二叉树的定义是：对于任意节点，其左右子树的高度差不超过 1。我们可以通过后序遍历（一种 DFS）来解决这个问题。在遍历每个节点时，我们计算其左右子树的高度，如果高度差大于 1，则该树不平衡。如果平衡，就返回当前节点的高度。使用 &lt;code&gt;-1&lt;/code&gt; 或其他哨兵值来表示不平衡状态可以提前终止递归。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    // 返回树的高度，如果不是平衡二叉树则返回 -1
    int getHeight(TreeNode* node) {
        if (!node) return 0;
        
        int leftHeight = getHeight(node-&amp;gt;left);
        if (leftHeight == -1) return -1;
        
        int rightHeight = getHeight(node-&amp;gt;right);
        if (rightHeight == -1) return -1;
        
        if (abs(leftHeight - rightHeight) &amp;gt; 1) {
            return -1;
        }
        
        return std::max(leftHeight, rightHeight) + 1;
    }

    bool isBalanced(TreeNode* root) {
        return getHeight(root) != -1;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;路径总和问题&lt;/h2&gt;
&lt;p&gt;在二叉树中，“路径”通常指从一个节点到另一个节点的序列。路径总和问题是二叉树中的一类经典问题，通常需要使用深度优先搜索来遍历所有可能的路径，并检查它们的和是否满足特定条件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 112. 路径总和&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 判断是否存在一条从根节点到叶子节点的路径，使得路径上所有节点值之和等于目标值。我们可以使用 DFS，在递归过程中，将目标值减去当前节点的值，然后继续在子树中寻找剩余的目标值。当到达叶子节点时，如果剩余的目标值恰好等于叶子节点的值，则找到了一条有效路径。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (!root) return false;
        
        // 如果是叶子节点，检查路径和是否满足条件
        if (!root-&amp;gt;left &amp;amp;&amp;amp; !root-&amp;gt;right) {
            return targetSum == root-&amp;gt;val;
        }
        
        // 递归地在左右子树中寻找
        return hasPathSum(root-&amp;gt;left, targetSum - root-&amp;gt;val) || 
               hasPathSum(root-&amp;gt;right, targetSum - root-&amp;gt;val);
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 437. 路径总和 III&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 寻找路径和等于目标值的路径数量，路径不一定需要从根节点开始或在叶子节点结束。直接的 DFS 会导致重复计算。我们可以使用“前缀和”的思想来优化。在 DFS 遍历时，我们用一个哈希表记录从根到当前节点路径上所有可能的前缀和及其出现的次数。在每个节点，我们检查 &lt;code&gt;(当前前缀和 - 目标值)&lt;/code&gt; 是否在哈希表中，如果是，则说明找到了满足条件的路径。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;unordered_map&amp;gt;

class Solution {
public:
    int pathSum(TreeNode* root, int targetSum) {
        std::unordered_map&amp;lt;long long, int&amp;gt; prefixSumCount;
        prefixSumCount[0] = 1; // 前缀和为0的路径有1条（空路径）
        return dfs(root, 0, targetSum, prefixSumCount);
    }

private:
    int dfs(TreeNode* node, long long currentSum, int targetSum, std::unordered_map&amp;lt;long long, int&amp;gt;&amp;amp; prefixSumCount) {
        if (!node) return 0;

        currentSum += node-&amp;gt;val;
        int count = 0;
        // 寻找 `currentSum - targetSum` 的前缀和
        if (prefixSumCount.count(currentSum - targetSum)) {
            count = prefixSumCount[currentSum - targetSum];
        }

        // 更新前缀和哈希表
        prefixSumCount[currentSum]++;
        
        // 递归进入左右子树
        count += dfs(node-&amp;gt;left, currentSum, targetSum, prefixSumCount);
        count += dfs(node-&amp;gt;right, currentSum, targetSum, prefixSumCount);

        // 回溯：恢复哈希表状态，以免影响兄弟分支
        prefixSumCount[currentSum]--;
        
        return count;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 124. 二叉树中的最大路径和&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 寻找任意两个节点之间路径的最大和。路径可以不经过根节点。对于每个节点，穿过它的最大路径和可能是 &lt;code&gt;左子树最大贡献 + 节点值 + 右子树最大贡献&lt;/code&gt;。我们使用 DFS 进行后序遍历，对于每个节点，计算并返回它能为“父节点”提供的最大路径贡献（即 &lt;code&gt;节点值 + max(左贡献, 右贡献)&lt;/code&gt;）。同时，用一个全局变量来更新我们目前找到的最大路径和。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;limits&amp;gt;

class Solution {
private:
    int maxSum = std::numeric_limits&amp;lt;int&amp;gt;::min();

public:
    // 返回从该节点出发向下的最大路径和
    int maxGain(TreeNode* node) {
        if (!node) return 0;

        // 递归计算左右子节点的最大贡献值
        // 如果子树贡献为负，则不选择该子树
        int leftGain = std::max(maxGain(node-&amp;gt;left), 0);
        int rightGain = std::max(maxGain(node-&amp;gt;right), 0);

        // 更新全局最大路径和
        // 路径可以穿过当前节点，连接左右子树
        int priceNewPath = node-&amp;gt;val + leftGain + rightGain;
        maxSum = std::max(maxSum, priceNewPath);

        // 返回对父节点的最大贡献值
        return node-&amp;gt;val + std::max(leftGain, rightGain);
    }

    int maxPathSum(TreeNode* root) {
        maxGain(root);
        return maxSum;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;树的结构修改&lt;/h2&gt;
&lt;p&gt;这类问题要求在原地修改树的结构，例如将二叉树转换为链表。通常需要利用特定的遍历顺序（如后序遍历）来确保在修改节点指针时不会丢失子树信息。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 114. 二叉树展开为链表&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 将二叉树原地展开为单链表，顺序为前序遍历的顺序。如果我们采用后序遍历（右-左-根）的思路，可以更方便地进行原地修改。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;递归地展开右子树。&lt;/li&gt;
&lt;li&gt;递归地展开左子树。&lt;/li&gt;
&lt;li&gt;将当前节点的右指针指向已展开的原左子树。&lt;/li&gt;
&lt;li&gt;将原左子树置空。&lt;/li&gt;
&lt;li&gt;找到原左子树展开后链表的末尾，将其右指针指向已展开的原右子树。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    void flatten(TreeNode* root) {
        if (!root) return;

        // 递归展开左右子树
        flatten(root-&amp;gt;left);
        flatten(root-&amp;gt;right);

        // 保存原有的左右子树
        TreeNode* left = root-&amp;gt;left;
        TreeNode* right = root-&amp;gt;right;

        // 将左子树置空，并将右指针指向原左子树
        root-&amp;gt;left = nullptr;
        root-&amp;gt;right = left;

        // 找到新右子树（原左子树）的末尾
        TreeNode* p = root;
        while (p-&amp;gt;right) {
            p = p-&amp;gt;right;
        }

        // 将原右子树连接到末尾
        p-&amp;gt;right = right;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最近公共祖先 (LCA)&lt;/h2&gt;
&lt;p&gt;最近公共祖先（LCA）是指在树中，离两个节点最近的共同祖先。这是树结构中的一个核心概念。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 236. 二叉树的最近公共祖先&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 使用 DFS 递归查找。从根节点开始，如果当前节点是 &lt;code&gt;p&lt;/code&gt; 或 &lt;code&gt;q&lt;/code&gt; 之一，则它就是LCA（如果另一个节点在它的子树中）。否则，递归地在左右子树中查找 &lt;code&gt;p&lt;/code&gt; 和 &lt;code&gt;q&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;p&lt;/code&gt; 和 &lt;code&gt;q&lt;/code&gt; 分别位于当前节点的左右子树中，则当前节点是 LCA。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;p&lt;/code&gt; 和 &lt;code&gt;q&lt;/code&gt; 都在左子树中，则 LCA 也在左子树中，向左递归。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;p&lt;/code&gt; 和 &lt;code&gt;q&lt;/code&gt; 都在右子树中，则 LCA 也在右子树中，向右递归。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (!root || root == p || root == q) return root;
        
        TreeNode* left = lowestCommonAncestor(root-&amp;gt;left, p, q);
        TreeNode* right = lowestCommonAncestor(root-&amp;gt;right, p, q);
        
        if (left &amp;amp;&amp;amp; right) return root; // p, q 分布在两侧
        if (left) return left;           // p, q 都在左侧
        return right;                    // p, q 都在右侧
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 235. 二叉搜索树的最近公共祖先&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 对于二叉搜索树（BST），其有序的特性可以让我们更高效地找到 LCA。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;p&lt;/code&gt; 和 &lt;code&gt;q&lt;/code&gt; 的值都小于当前节点的值，则 LCA 必定在左子树中。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;p&lt;/code&gt; 和 &lt;code&gt;q&lt;/code&gt; 的值都大于当前节点的值，则 LCA 必定在右子树中。&lt;/li&gt;
&lt;li&gt;如果当前节点的值介于 &lt;code&gt;p&lt;/code&gt; 和 &lt;code&gt;q&lt;/code&gt; 之间（或等于其中之一），则当前节点就是 LCA，因为 &lt;code&gt;p&lt;/code&gt; 和 &lt;code&gt;q&lt;/code&gt; 将会“分叉”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个性质允许我们使用迭代或递归进行一次性的查找，时间复杂度为 O(H)，其中 H 是树的高度。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode* ancestor = root;
        while (true) {
            if (p-&amp;gt;val &amp;lt; ancestor-&amp;gt;val &amp;amp;&amp;amp; q-&amp;gt;val &amp;lt; ancestor-&amp;gt;val) {
                ancestor = ancestor-&amp;gt;left;
            } else if (p-&amp;gt;val &amp;gt; ancestor-&amp;gt;val &amp;amp;&amp;amp; q-&amp;gt;val &amp;gt; ancestor-&amp;gt;val) {
                ancestor = ancestor-&amp;gt;right;
            } else {
                // p 和 q 在当前节点的两侧或其中一个是当前节点
                break;
            }
        }
        return ancestor;
    }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>KMP 算法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/kmp/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/kmp/</guid><pubDate>Wed, 30 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;KMP 算法简介&lt;/h2&gt;
&lt;p&gt;KMP算法（Knuth-Morris-Pratt算法）是一种高效的字符串匹配算法，用于&lt;strong&gt;在主文本串（Text）中快速查找模式串（p）的出现位置&lt;/strong&gt;。其核心思想是通过预处理模式串，利用已匹配的信息避免不必要的回溯，将时间复杂度从暴力匹配的 $O(m*n)$ 优化至 $O(m+n)$。&lt;/p&gt;
&lt;h2&gt;KMP 算法思想&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;避免无效匹配&lt;/strong&gt;：当匹配失败时，KMP算法不会回溯主文本串，而是利用模式串的部分匹配信息，直接跳过一些字符进行下一次匹配。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;部分匹配表 (Partial Match Table, PMT)&lt;/strong&gt;：KMP算法通过构建一个部分匹配表（也称为前缀表或失配表），记录模式串中每个位置的最长相等前后缀长度。这个表用于在匹配失败时决定模式串应该向右移动多少位。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;算法步骤&lt;/h2&gt;
&lt;h3&gt;最长公共前后缀 (Longest Prefix Suffix, LPS)&lt;/h3&gt;
&lt;p&gt;LPS 就是给定一个字符串后，其&lt;strong&gt;前缀&lt;/strong&gt;和&lt;strong&gt;后缀&lt;/strong&gt;的最长公共部分的长度 (注意这个公共部分不能是字符串本身)。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;例如，对于字符串 &lt;code&gt;&quot;CABABAC&quot;&lt;/code&gt;, &lt;code&gt;&quot;ABABAC&quot;&lt;/code&gt;和 &lt;code&gt;&quot;ABAB&quot;&lt;/code&gt;我们列个表说明它的 LPS 是什么&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字符串&lt;/th&gt;
&lt;th&gt;前缀&lt;/th&gt;
&lt;th&gt;后缀&lt;/th&gt;
&lt;th&gt;LPS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&quot;CABABAC&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[&quot;C&quot;, &quot;CA&quot;, &quot;CAB&quot;, &quot;CABA&quot;, &quot;CABAB&quot;, &quot;CABABA&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[&quot;C&quot;, &quot;AC&quot;, &quot;BAC&quot;, &quot;ABAC&quot;, &quot;BABAC&quot;, &quot;ABABAC&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&quot;ABABAC&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[&quot;A&quot;, &quot;AB&quot;, &quot;ABA&quot;, &quot;ABAB&quot;, &quot;ABABA&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[&quot;C&quot;, &quot;AC&quot;, &quot;BAC&quot;, &quot;ABAC&quot;, &quot;BABAC&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&quot;ABAB&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[&quot;A&quot;, &quot;AB&quot;, &quot;ABA&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[&quot;B&quot;, &quot;AB&quot;, &quot;BAB&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;可以发现一些规律，
&lt;ul&gt;
&lt;li&gt;一个字符串有自己的前缀列表和后缀列表 (均不包含自身)，且列表中的前一个元素一定是后一个元素的子串(更确切地说是前缀或后缀)。&lt;/li&gt;
&lt;li&gt;LPS 就是前缀列表和后缀列表的公共部分中最长的那个的长度。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;构建 LPS 数组&lt;/h3&gt;
&lt;p&gt;对于给定的模式串 &lt;code&gt;p&lt;/code&gt;，LPS 数组其实就是模式串 &lt;code&gt;p&lt;/code&gt; 的各个前缀子串 (含自身) 的 LPS。显然，由于各个前缀子串之间有包含的关系，因此 LPS 数组的构造可以用动态规划的思想来避免重复计算。&lt;/p&gt;
&lt;p&gt;初始化 &lt;code&gt;LPS[i] = 0, i = 0, 1, ..., len(p) - 1&lt;/code&gt;。使用两个指针 &lt;code&gt;i&lt;/code&gt; 和 &lt;code&gt;j&lt;/code&gt; 进行处理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;i、j 指针的更新逻辑&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;指针含义&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;当前子串: &lt;code&gt;p[0:i]&lt;/code&gt;，表示模式串 &lt;code&gt;p&lt;/code&gt; 的前 &lt;code&gt;i&lt;/code&gt; 个字符。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;p[0:j]&lt;/code&gt;: 当前子串的最长公共前后缀对应的前缀部分&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;p[i-j+1:i]&lt;/code&gt;: 当前子串的最长公共前后缀对应的后缀部分&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;i&lt;/code&gt;：遍历模式串的当前位置索引，从1开始；表示前 &lt;code&gt;i&lt;/code&gt; 个字符形成的子串的&lt;strong&gt;右侧字符&lt;/strong&gt; (注意这个右侧字符不在子串中，相当于是左闭右开)，同时也是子串 &lt;code&gt;p[i-j+1:i]&lt;/code&gt; 的右侧字符。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;j&lt;/code&gt;：当前已匹配的最长公共前后缀长度；表示前 &lt;code&gt;j&lt;/code&gt; 个字符形成的子串 &lt;code&gt;p[0:j]&lt;/code&gt; 的&lt;strong&gt;右侧字符&lt;/strong&gt; (同样注意这个右侧字符不在子串中)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;更新条件判断原理&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;动态规划设计&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不论 &lt;code&gt;p[i] == p[j]&lt;/code&gt; 是否成立，我们其实已经得到了 &lt;code&gt;p[0:j-1] == p[i-j+1:i]&lt;/code&gt; 这个先验条件的。&lt;/li&gt;
&lt;li&gt;因此可以基于这个条件减少不必要的回溯。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;失配回退条件&lt;/strong&gt; (&lt;code&gt;j &amp;gt; 0 and p[i] != p[j]&lt;/code&gt;)：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前字符不匹配且存在已匹配前缀时，利用 &lt;code&gt;lps[j-1]&lt;/code&gt; 回退到次长匹配位置&lt;/li&gt;
&lt;li&gt;避免从头开始匹配，复用已知的前后缀信息&lt;/li&gt;
&lt;li&gt;这里是 &lt;code&gt;while&lt;/code&gt; 循环，就是说要一直找到合适的匹配才行，找到合适的匹配后，就可以经过 &lt;code&gt;p[i] == p[j]&lt;/code&gt; 的条件来更新 &lt;code&gt;j&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;匹配递增条件&lt;/strong&gt; (&lt;code&gt;p[i] == p[j]&lt;/code&gt;)：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;字符匹配成功时，前后缀长度加1&lt;/li&gt;
&lt;li&gt;更新 &lt;code&gt;lps[i] = j&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;边界处理&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;j = 0&lt;/code&gt; 时无法再回退，直接设置 &lt;code&gt;lps[i] = 0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;核心机制&lt;/strong&gt;：通过 &lt;code&gt;j = lps[j-1]&lt;/code&gt; 实现智能回退，将线性扫描中的潜在二次复杂度优化为线性时间。体现有滑动窗口和动态规划的思想。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def build_lps(p):
    lps = [0] * len(p)
    j = 0   # 当前最长公共前后缀长度
    for i in range(1, len(p)):
        while j &amp;gt; 0 and p[i] != p[j]:   
            j = lps[j - 1]
        if p[i] == p[j]:
            j += 1
        lps[i] = j
    return lps
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;&quot;ABABA&quot;&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;&quot;ABACA&quot;&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;assets/lps-instance1.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/lps-instance2.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;lps[i] = j&lt;/code&gt; 记录的是模式串 &lt;code&gt;p[0:i]&lt;/code&gt; 的最长公共前后缀长度。&lt;/p&gt;
&lt;h3&gt;匹配过程&lt;/h3&gt;
&lt;p&gt;完成 LPS 数组的构建后，接下来就是使用这个 LPS 数组来进行模式串 &lt;code&gt;p&lt;/code&gt; 在主文本串 &lt;code&gt;T&lt;/code&gt; 中的匹配。&lt;/p&gt;
&lt;p&gt;这里依然采用两个指针进行匹配。我们的目标是为了实现隐式匹配子串 &lt;code&gt;T[i-j:i]&lt;/code&gt; 和模式子串 &lt;code&gt;p[0:j]&lt;/code&gt;。当处理到 &lt;code&gt;T[i]&lt;/code&gt; 和 &lt;code&gt;p[j]&lt;/code&gt; 时，我们已经得到 &lt;code&gt;T[i-j:i] == p[0:j]&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果 &lt;code&gt;T[i] == p[j]&lt;/code&gt;，说明当前字符匹配成功，继续向后匹配，令 &lt;code&gt;i += 1&lt;/code&gt; 和 &lt;code&gt;j += 1&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;j = len(p)&lt;/code&gt;，说明模式串 &lt;code&gt;p&lt;/code&gt; 完全匹配成功，此时记录匹配位置 &lt;code&gt;i - j&lt;/code&gt; (主字符串中子串的起始位置)，然后回退到最长公共前后缀位置 &lt;code&gt;j = lps[j - 1]&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;匹配失败时，我们其实已经成功匹配了 &lt;code&gt;p[0:j]&lt;/code&gt; 和 &lt;code&gt;T[i-j:i]&lt;/code&gt;，但是 &lt;code&gt;T[i] != p[j]&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LPS 数组的作用是&lt;strong&gt;当匹配失败，即发现 &lt;code&gt;T[i] != p[j]&lt;/code&gt; 时，利用 &lt;code&gt;p[0:j]&lt;/code&gt; 的 LPS 数值信息，调整 &lt;code&gt;j = lps[j - 1]&lt;/code&gt;，而不是从头开始匹配&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;j = lps[j - 1]&lt;/code&gt; 的含义:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将 &lt;code&gt;p&lt;/code&gt; 的前缀快速对齐到 &lt;code&gt;T&lt;/code&gt; 中已经匹配的后缀，直接将模式串的“前缀”对齐到当前已匹配的“后缀”位置，跳过不可能匹配的位置。&lt;code&gt;lps[j - 1]&lt;/code&gt; 中 &lt;code&gt;j-1&lt;/code&gt; 表示当前已经匹配的子串的末位，避免了重复比较。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;def kmp_search(T, p):
    lps = build_lps(p)
    i = 0  # 主文本串 T 的索引
    j = 0  # 模式串 p 的索引
    matches = []  # 存储匹配位置

    while i &amp;lt; len(T):
        if T[i] == p[j]:
            i += 1
            j += 1
            if j == len(p):  # 完全匹配
                matches.append(i - j)  # 匹配位置
                j = lps[j - 1]  # 回退到最长公共前后缀位置
        else:
            if j &amp;gt; 0:
                j = lps[j - 1]  # 回退到次长公共前后缀位置
            else:
                i += 1  # 主文本串指针向右移动

    return matches
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;assets/kmp.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;求连续出现的最大模式串次数&lt;/h2&gt;
&lt;p&gt;给定一个主文本串 &lt;code&gt;T&lt;/code&gt; 和模式串 &lt;code&gt;p&lt;/code&gt;，目标是找到模式串 &lt;code&gt;p&lt;/code&gt; 在主文本串 &lt;code&gt;T&lt;/code&gt; 中连续出现的最大次数。
可以用 KMP 算法来先找到所有匹配位置，然后统计连续出现的次数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def max_consecutive_occurrences(T, p):
    matches = kmp_search(T, p)
    if not matches:
        return 0
    
    max_cnt = 1
    curr_cnt = 1
    for i in range(1, len(matches)):
        if matches[i] == matches[i - 1] + len(p):
            curr_cnt += 1
        else:
            max_cnt = max(max_cnt, curr_cnt)
            curr_cnt = 1
    return max(max_cnt, curr_cnt)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Kronecker Product</title><link>https://adalovelemon.github.io/posts/content/coursenotes/mathslab/kroneck/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/mathslab/kroneck/</guid><pubDate>Sun, 27 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;克劳内克积（Kronecker Product）是两个矩阵的乘积，结果是一个新的矩阵，其元素是原矩阵元素的乘积。对于两个矩阵 $A$ 和 $B$，其克劳内克积记作 $A \otimes B$。常用于信号处理、图像处理等领域。也叫做张量积或直积。&lt;/p&gt;
&lt;h2&gt;定义&lt;/h2&gt;
&lt;p&gt;设 $A$ 是一个 $m \times n$ 的矩阵，$B$ 是一个 $p \times q$ 的矩阵，则它们的克劳内克积 $A \otimes B$ 是一个 $mp \times nq$ 的矩阵，其定义为
$$
A \otimes B = \begin{bmatrix} a_{11}B &amp;amp; a_{12}B &amp;amp; \cdots &amp;amp; a_{1n}B \ a_{21}B &amp;amp; a_{22}B &amp;amp; \cdots &amp;amp; a_{2n}B \ \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \ a_{m1}B &amp;amp; a_{m2}B &amp;amp; \cdots &amp;amp; a_{mn}B \end{bmatrix}
$$
其中 $a_{ij}$ 是矩阵 $A$ 的元素，$B$ 是矩阵 $B$, $a_{ij}B$ 表示将矩阵 $B$ 的每个元素都乘以 $a_{ij}$ 后形成的子块。&lt;/p&gt;
&lt;p&gt;例如，设
$$
A = \begin{bmatrix} 1 &amp;amp; 2 \ 3 &amp;amp; 4 \end{bmatrix}, \quad B = \begin{bmatrix} 0 &amp;amp; 5 \ 6 &amp;amp; 7 \end{bmatrix}
$$
则
$$
A \otimes B = \begin{bmatrix} 1 \cdot B &amp;amp; 2 \cdot B \ 3 \cdot B &amp;amp; 4 \cdot B \end{bmatrix} = \begin{bmatrix} 0 &amp;amp; 5 &amp;amp; 0 &amp;amp; 10 \ 6 &amp;amp; 7 &amp;amp; 12 &amp;amp; 14 \ 0 &amp;amp; 15 &amp;amp; 18 &amp;amp; 20 \ 18 &amp;amp; 21 &amp;amp; 24 &amp;amp; 28 \end{bmatrix}
$$&lt;/p&gt;
&lt;h2&gt;性质&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;结合律
$$(A \otimes B) \otimes C = A \otimes (B \otimes C)$$&lt;/li&gt;
&lt;li&gt;分配律
$$A \otimes (B + C) = A \otimes B + A \otimes C$$&lt;/li&gt;
&lt;li&gt;交换律 (仅当 $A$ 和 $B$ 都是方阵时成立)
$$A \otimes B = B \otimes A$$&lt;/li&gt;
&lt;li&gt;分配律
$$(A + B) \otimes C = A \otimes C + B \otimes C$$&lt;/li&gt;
&lt;li&gt;单位矩阵
$$I_m \otimes I_n = I_{mn}$$&lt;/li&gt;
&lt;li&gt;混合乘积
$$(A \otimes B)(C \otimes D) = (AC) \otimes (BD)$$&lt;/li&gt;
&lt;li&gt;转置与逆
$$(A \otimes B)^\top = A^\top \otimes B^\top$$
$$(A \otimes B)^{-1} = A^{-1} \otimes B^{-1}$$&lt;/li&gt;
&lt;li&gt;克劳内克积的迹
$$\text{tr}(A \otimes B) = \text{tr}(A) \cdot \text{tr}(B)$$&lt;/li&gt;
&lt;li&gt;克劳内克积的秩
$$\text{rank}(A \otimes B) = \text{rank}(A) \cdot \text{rank}(B)$$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;应用&lt;/h2&gt;
&lt;h3&gt;求解 Sylvester 方程&lt;/h3&gt;
&lt;p&gt;克劳内克积在求解 Sylvester 方程中非常有用。Sylvester 方程的形式为
$$
AX + XB = C
$$
其中 $A$ 和 $B$ 是已知矩阵，$C$ 是已知矩阵，$X$ 是未知矩阵。可以将其转化为向量形式
$$
(I \otimes A + B^\top \otimes I) \text{vec}(X) = \text{vec}(C)
$$
用这个形式可以使用线性代数的方法求解 $X$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;证明:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先说明，
$$
\text{vec}(X) = \begin{bmatrix} x_{11} \ x_{21} \ \vdots \ x_{m1} \ x_{12} \ x_{22} \ \vdots \ x_{m2} \ \vdots \ x_{1n} \ x_{2n} \ \vdots \ x_{mn} \end{bmatrix}
$$
即 $\text{vec}(\cdot)$ 函数是按照列优先的顺序将矩阵展开为一个向量。&lt;/p&gt;
&lt;p&gt;我们对等式两侧同时向量化，得到
$$
\begin{aligned}
&amp;amp;\text{vec}(AX + XB) = \text{vec}(C) \
\end{aligned}
$$
利用性质 $\text{vec}(AXB) = (B^\top \otimes A) \text{vec}(X)$，得到
$$
\begin{aligned}
&amp;amp;\text{vec}(AX) + \text{vec}(XB) = \text{vec}(C) \
\Rightarrow &amp;amp;(I \otimes A + B^\top \otimes I) \text{vec}(X) = \text{vec}(C)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;两边同时左乘 $(I \otimes A + B^\top \otimes I)^{-1}$，得到
$$
\text{vec}(X) = (I \otimes A + B^\top \otimes I)^{-1} \text{vec}(C)
$$
即可求解 $X$。&lt;/p&gt;
&lt;h3&gt;卷积定理&lt;/h3&gt;
&lt;p&gt;克劳内克积在信号处理中的卷积定理中也有重要应用。设 $x[n]$ 和 $h[n]$ 是两个离散时间信号，它们的卷积可以表示为
$$
y[n] = (x * h)[n] = \sum_{k=-\infty}^{\infty} x[k] h[n-k]
$$
利用克劳内克积，可以将其表示为矩阵形式
$$
\text{vec}(y) = (I \otimes h) \text{vec}(x)
$$
其中 $I$ 是单位矩阵，$\text{vec}(\cdot)$ 是将矩阵按列展开为向量的操作。&lt;/p&gt;
</content:encoded></item><item><title>Larangian Multiplier Method</title><link>https://adalovelemon.github.io/posts/content/coursenotes/mathslab/larangian/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/mathslab/larangian/</guid><pubDate>Sun, 20 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;在优化问题中，我们经常需要在满足若干约束条件的前提下，寻找目标函数的极值。典型形式是：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;amp;\min_{x\in\mathbb{R}^n} &amp;amp;&amp;amp; f(x) \
&amp;amp;\text{s.t.} &amp;amp;&amp;amp; g_i(x) = 0,\quad i=1,\dots,m.
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;直接在可行域内搜索往往困难——可行域可能非常复杂。拉格朗日乘子法提供了一种将约束&quot;合并&quot;到目标函数中的技巧，使问题转化为无约束优化，便于求解。&lt;/p&gt;
&lt;h2&gt;Lagrangian 乘子法&lt;/h2&gt;
&lt;p&gt;引入乘子（Lagrange multipliers）$\lambda = (\lambda_1,\dots,\lambda_m)$，构造&lt;strong&gt;拉格朗日函数&lt;/strong&gt;：
$$
\mathcal{L}(x,\lambda) = f(x) - \sum_{i=1}^m \lambda_i,g_i(x).
$$&lt;/p&gt;
&lt;p&gt;然后，在 $\mathbb{R}^{n+m}$ 空间中同时对 $(x,\lambda)$ 求偏导数，得到&lt;strong&gt;一阶条件&lt;/strong&gt;（驻点条件）：
$$
\begin{cases}
\nabla_x \mathcal{L}(x,\lambda) = \nabla f(x) - \sum_{i=1}^m \lambda_i,\nabla g_i(x) = 0,\
\nabla_\lambda \mathcal{L}(x,\lambda) = -\bigl(g_1(x),\dots,g_m(x)\bigr)^\top = 0.
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;简化为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$\nabla f(x) = \sum_{i=1}^m \lambda_i,\nabla g_i(x)$&lt;/li&gt;
&lt;li&gt;$g_i(x) = 0,;i=1,\dots,m$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在满足这些条件的 $(x^&lt;em&gt;, \lambda^&lt;/em&gt;)$ 处，$x^*$ 是目标函数 $f(x)$ 在约束集上的驻点（可能是极小点、极大点或鞍点）。为找到最小值点，可采用以下方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将求得的各个 $x^*$ 代入原函数 $f(x)$，计算函数值并比较，确定全局最小值点。&lt;/li&gt;
&lt;li&gt;理论上，满足约束优化问题的驻点 $(x^&lt;em&gt;, \lambda^&lt;/em&gt;)$ 必定是对偶问题 $\max_{\lambda} \min_{x} \mathcal{L}(x, \lambda)$ 的解。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;理论解释&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;直观理解&lt;/strong&gt;：乘子 $\lambda_i$ 表示第 $i$ 个约束对目标函数在最优点处的&quot;边际影响力&quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;等价性&lt;/strong&gt;：若 $f$ 与每个 $g_i$ 在解附近足够光滑，且 $\nabla g_1,\dots,\nabla g_m$ 在解处线性无关，则解必为上述驻点之一。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;必要性&lt;/strong&gt;：在约束极值点处，目标函数梯度必须在约束集的切空间内——即与切空间正交的向量（约束梯度）能线性组合出目标梯度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最优性判定&lt;/strong&gt;：通过拉格朗日函数的海森矩阵在可行方向上的正定性/负定性可判断局部极值性质。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;为什么最优解一定满足 $\nabla f(x^&lt;em&gt;) = \sum_{i=1}^m \lambda_i \nabla g_i(x^&lt;/em&gt;)$？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;assets/larangian.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个条件的另一层含义就是在 $x^&lt;em&gt;$ 处，目标函数 $f(x)$ 与约束函数 $g_i(x)$ 的线性组合后的曲线相切。设想极值点不在相切的位置取到，而在图中某处等高线与约束线相交的点。好，沿着约束曲线向前走一点，目标函数值就会继续下降，这就说明了 $x^&lt;/em&gt;$ 不是极值点。因此，必须等高线与约束线相切，才能保证 $x^*$ 是极值点。&lt;/p&gt;
&lt;h2&gt;KKT 条件（Karush–Kuhn–Tucker）&lt;/h2&gt;
&lt;p&gt;当问题含有不等式约束时，拉格朗日乘子法推广为KKT条件：&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
&amp;amp;\min_x &amp;amp;&amp;amp; f(x)\
&amp;amp;\text{s.t.} &amp;amp;&amp;amp; g_i(x) = 0,;i=1,\dots,m,\
&amp;amp;          &amp;amp;&amp;amp; h_j(x) \le 0,;j=1,\dots,p.
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;对应的KKT拉格朗日函数形式为：
$$
\mathcal{L}(x,\lambda,\mu) = f(x) - \sum_{i=1}^m \lambda_i g_i(x) + \sum_{j=1}^p \mu_j h_j(x)
$$&lt;/p&gt;
&lt;p&gt;存在乘子 $\lambda_i$（等式）与 $\mu_j\ge0$（不等式），使得：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;可行性&lt;/strong&gt;：$g_i(x^&lt;em&gt;)=0,;h_j(x^&lt;/em&gt;)\le0$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;驻点条件&lt;/strong&gt;：$\nabla f(x^&lt;em&gt;) = \sum_i \lambda_i ,\nabla g_i(x^&lt;/em&gt;) + \sum_j \mu_j ,\nabla h_j(x^*)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;互补松弛&lt;/strong&gt;：$\mu_j,h_j(x^*)=0,;j=1,\dots,p$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;乘子非负性&lt;/strong&gt;：$\mu_j\ge0$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在凸优化问题中，这些条件不仅必要，而且充分。&lt;/p&gt;
&lt;h2&gt;实例：约束最小二乘&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：&lt;br /&gt;
$$
\min_{x\in\mathbb{R}^2}; \frac{1}{2}|x|^2,\quad \text{s.t. } x_1 + 2x_2 - 3 = 0.
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;构造拉格朗日函数&lt;/strong&gt;：
$$
\mathcal{L}(x,\lambda) = \tfrac12(x_1^2 + x_2^2) - \lambda (x_1 + 2x_2 - 3).
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;求解一阶条件&lt;/strong&gt;：
$$
\begin{cases}
\nabla_{x_1}\mathcal{L} = x_1 - \lambda = 0 \quad\Rightarrow\quad x_1 = \lambda,\
\nabla_{x_2}\mathcal{L} = x_2 - 2\lambda = 0 \quad\Rightarrow\quad x_2 = 2\lambda,\
\nabla_{\lambda}\mathcal{L} = -(x_1 + 2x_2 - 3)=0 \quad\Rightarrow\quad \lambda + 2(2\lambda) - 3 = 0.
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;解得 $5\lambda = 3$，故 $\lambda = 3/5$，进而
$$
x^* = \bigl(\tfrac{3}{5},,\tfrac{6}{5}\bigr).
$$&lt;/p&gt;
&lt;p&gt;代入原函数可验证此为全局最优解。&lt;/p&gt;
&lt;h2&gt;对偶问题&lt;/h2&gt;
&lt;p&gt;考虑一般约束优化问题：
$$
\begin{aligned}
&amp;amp;\min_{x\in\mathbb{R}^n} &amp;amp;&amp;amp; f(x) \
&amp;amp;\text{s.t.} &amp;amp;&amp;amp; g_i(x) = 0,\quad i=1,\dots,m,\
&amp;amp;          &amp;amp;&amp;amp; h_j(x) \le 0,\quad j=1,\dots,p.
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;构造拉格朗日函数，引入乘子 $\lambda_i \in \mathbb{R}$ 和 $\mu_j \ge 0$：
$$
\mathcal{L}(x, \lambda, \mu) = f(x) - \sum_{i=1}^m \lambda_i g_i(x) + \sum_{j=1}^p \mu_j h_j(x)
$$&lt;/p&gt;
&lt;p&gt;注意：对不等式约束使用 $+$ 号，确保违背约束时（$h_j(x) &amp;gt; 0$），惩罚项 $\mu_j h_j(x)$ 可通过增大 $\mu_j$ 使惩罚达到 $+\infty$。&lt;/p&gt;
&lt;p&gt;定义对偶函数：
$$
q(\lambda, \mu) = \min_{x} \mathcal{L}(x, \lambda, \mu)
$$&lt;/p&gt;
&lt;p&gt;对偶函数的特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对给定的乘子 $\lambda, \mu$，$q(\lambda, \mu)$ 是拉格朗日函数关于 $x$ 的最小值&lt;/li&gt;
&lt;li&gt;若 $\mu_j &amp;lt; 0$，则约定 $q(\lambda, \mu) = -\infty$&lt;/li&gt;
&lt;li&gt;对任何满足约束的 $x^&lt;em&gt;$，若 $\mu \ge 0$，则 $q(\lambda, \mu) \leq f(x^&lt;/em&gt;)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由此定义对偶问题：
$$
\max_{\lambda, \mu} \quad q(\lambda, \mu) \quad \text{subject to } \mu_j \ge 0, ; j=1,\dots,p
$$&lt;/p&gt;
&lt;p&gt;对偶问题的最优值必定不大于原始问题的最优值，这称为&lt;strong&gt;弱对偶性&lt;/strong&gt;。在特定条件下（如凸优化问题），还可能成立&lt;strong&gt;强对偶性&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;利用对偶性求解原始问题&lt;/h3&gt;
&lt;p&gt;对偶问题不仅提供了原始问题最优值的下界，在满足强对偶性条件时，还可以用于求解原始问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;解对偶问题&lt;/strong&gt;：求解 $\max_{\lambda, \mu \geq 0} q(\lambda, \mu)$，得到最优对偶变量 $(\lambda^&lt;em&gt;, \mu^&lt;/em&gt;)$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;从对偶恢复原始解&lt;/strong&gt;：当原始问题为凸问题且满足Slater条件（存在严格可行点）时，我们可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若对偶函数 $q(\lambda^&lt;em&gt;, \mu^&lt;/em&gt;)$ 在某点 $x^&lt;em&gt;$ 取得最小值，则 $x^&lt;/em&gt;$ 即为原始问题的最优解&lt;/li&gt;
&lt;li&gt;通过求解方程 $\nabla_x \mathcal{L}(x, \lambda^&lt;em&gt;, \mu^&lt;/em&gt;) = 0$ 得到原始问题的最优解 $x^*$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;互补松弛性质&lt;/strong&gt;：在最优点处，$\mu_j^* h_j(x^*) = 0$ 提供了关于活跃约束的重要信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若 $\mu_j^* &amp;gt; 0$，则必有 $h_j(x^*) = 0$（约束紧绷）&lt;/li&gt;
&lt;li&gt;若 $h_j(x^&lt;em&gt;) &amp;lt; 0$，则必有 $\mu_j^&lt;/em&gt; = 0$（约束不起作用）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种方法在某些情况下比直接求解原始问题更有效，特别是当对偶问题结构更简单或维度更低时。&lt;/p&gt;
</content:encoded></item><item><title>矩阵向量的梯度</title><link>https://adalovelemon.github.io/posts/content/coursenotes/mathslab/matgrad/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/mathslab/matgrad/</guid><pubDate>Sun, 20 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这里介绍求解矩阵或者向量梯度的一个便捷方法。&lt;/p&gt;
&lt;h2&gt;符号定义&lt;/h2&gt;
&lt;p&gt;为了方便讨论，我们统一采用&lt;strong&gt;梯度张量&lt;/strong&gt;的形式，而不是&lt;strong&gt;Jacobian 矩阵&lt;/strong&gt;的形式，特此说明。&lt;/p&gt;
&lt;p&gt;例如，设有函数 $\mathbf{y} = \mathbf{J}(\mathbf{x})$，其中 $\mathbf{y} \in \mathbb{R}^{m\times 1}$，$\mathbf{x}\in \mathbb{R}^{n\times 1}$，则其梯度张量定义为：
$$
\nabla_\mathbf{x} \mathbf{y} = \frac{\partial \mathbf{y}}{\partial \mathbf{x}} = \begin{bmatrix}
\frac{\partial y_1}{\partial x_1} &amp;amp; \frac{\partial y_2}{\partial x_1} &amp;amp; \cdots &amp;amp; \frac{\partial y_m}{\partial x_1} \[6pt]
\frac{\partial y_1}{\partial x_2} &amp;amp; \frac{\partial y_2}{\partial x_2} &amp;amp; \cdots &amp;amp; \frac{\partial y_m}{\partial x_2} \[6pt]
\vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \[6pt]
\frac{\partial y_1}{\partial x_n} &amp;amp; \frac{\partial y_2}{\partial x_n} &amp;amp; \cdots &amp;amp; \frac{\partial y_m}{\partial x_n}
\end{bmatrix} \in \mathbb{R}^{n \times m}
$$&lt;/p&gt;
&lt;p&gt;这个形状的设定是基于对微分的考量, $d\mathbf{y} = (\frac{\partial \mathbf{y}}{\partial \mathbf{x}})^\top d\mathbf{x}$，这个形式更符合矩阵向量的点乘形式，因此梯度张量的形状是 $n\times m$ 才能正好符合这个微分公式。&lt;/p&gt;
&lt;h2&gt;链式法则&lt;/h2&gt;
&lt;p&gt;基于上述梯度张量的定义，我们可以使用链式法则来求解复合函数的梯度。&lt;/p&gt;
&lt;p&gt;例如，对于复合函数 $\mathbf{y} = \mathbf{f}(\mathbf{w}), \mathbf{w} = \mathbf{g}(\mathbf{z}), \mathbf{z} = \mathbf{h}(\mathbf{x})$，有&lt;/p&gt;
&lt;p&gt;$$
\nabla_\mathbf{x} \mathbf{y} = \nabla_\mathbf{x} \mathbf{z}\ \nabla_\mathbf{z} \mathbf{w}\ \nabla_\mathbf{w} \mathbf{y}
$$&lt;/p&gt;
&lt;p&gt;或者换成&quot;偏导&quot;符号的写法为&lt;/p&gt;
&lt;p&gt;$$
\frac{\partial \mathbf{y}}{\partial \mathbf{x}} = \frac{\partial \mathbf{z}}{\partial \mathbf{x}} \frac{\partial \mathbf{w}}{\partial \mathbf{z}}  \frac{\partial \mathbf{y}}{\partial \mathbf{w}}
$$&lt;/p&gt;
&lt;p&gt;注意，最内层函数的局部梯度要写在最左边。不妨拿个例子验证一下，设 $\mathbf{y} \in \mathbb{R}^{2\times 1}$，$\mathbf{w} \in \mathbb{R}^{3\times 1}$，$\mathbf{z} \in \mathbb{R}^{4\times 1}$，$\mathbf{x} \in \mathbb{R}^{5\times 1}$，则&lt;/p&gt;
&lt;p&gt;$$
\frac{\partial \mathbf{z}}{\partial \mathbf{x}} \in \mathbb{R}^{5\times 4},\quad
\frac{\partial \mathbf{w}}{\partial \mathbf{z}} \in \mathbb{R}^{4\times 3},\quad
\frac{\partial \mathbf{y}}{\partial \mathbf{w}} \in \mathbb{R}^{3\times 2}
$$&lt;/p&gt;
&lt;p&gt;由此 $\frac{\partial \mathbf{y}}{\partial \mathbf{x}} \in \mathbb{R}^{5\times 2}$，符合我们对梯度张量的定义。&lt;/p&gt;
&lt;h2&gt;如何求矩阵向量梯度张量&lt;/h2&gt;
&lt;p&gt;虽然通常的矩阵分析书中都给出了常见的矩阵向量梯度张量的公式，但是对于复杂的函数，直接采用向量形式推导并不是一个好方法，通常会由于不清楚某个向量的导数公式而导致无从下手。因此这里更推荐的方法是，&lt;strong&gt;大不了就变成标量形式的导数 $\frac{\partial \mathbf{y}_j}{\partial \mathbf{x}_i}$，然后根据具体的形状重新排列即可&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如，给定函数
$$
J(\theta) = -\frac{1}{N}\left[y^\top \log s + (1 - y)^\top \log (1 - s)\right]
$$
其中 $s, y \in \mathbb{R}^{N \times 1}, J(\theta) \in \mathbb{R}$，$\log(\cdot)$ 是逐元素的对数函数, $N$ 为标量常数。&lt;/p&gt;
&lt;p&gt;我们可以先计算每个元素的导数
$$
\begin{aligned}
\frac{\partial J}{\partial s_i} &amp;amp;= \frac{\partial}{\partial s_i} \left[-\frac{1}{N} \sum_{i=1}^{N} \left(y_i \log s_i + (1 - y_i) \log (1 - s_i)\right)\right] \
&amp;amp;= -\frac{1}{N} \left[\frac{y_i}{s_i} - \frac{1 - y_i}{1 - s_i}\right] \
&amp;amp;= \frac{1}{N} \frac{s_i - y_i}{s_i(1 - s_i)}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;然后将其整理成梯度张量的形式。注意到标量导数中并没有涉及到其他下标元素的值，所以这里的运算可以统统用逐元素计算表示&lt;/p&gt;
&lt;p&gt;$$
\nabla_s J = \frac{1}{N} \frac{s - y}{s \odot (1 - s)}
$$
其中 $\odot$ 表示逐元素乘法, 除法也是逐元素的。&lt;/p&gt;
&lt;h2&gt;例子：Logistic 回归的梯度&lt;/h2&gt;
&lt;p&gt;设 $h(x) = \sigma(\theta^\top x), \quad x, \theta \in \mathbb{R}^{d \times 1}$，假设有数据集 $X \in \mathbb{R}^{N\times d}, y \in \mathbb{R}^{N \times 1}$，则交叉熵损失为
$$
\begin{aligned}
J(\theta) &amp;amp;= -\frac{1}{N} \sum_{i=1}^{N} \left[y_i \log h(x_i) + (1 - y_i) \log (1 - h(x_i))\right] \
&amp;amp;= -\frac{1}{N} \left[y^\top \log h(X) + (1 - y)^\top \log (1 - h(X))\right] \
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;我们可以先计算每个元素的导数&lt;/p&gt;
&lt;p&gt;$$
\begin{align*}
\frac{\partial J}{\partial \theta_k} &amp;amp;= \frac{\partial}{\partial \theta_k} \left[-\frac{1}{N} \sum_{i=1}^{N} \left[y_i \log h(x_i) + (1 - y_i) \log (1 - h(x_i))\right]\right] \
&amp;amp;= -\frac{1}{N} \sum_{i=1}^{N} \left[y_i \frac{1}{h(x_i)} \frac{\partial h(x_i)}{\partial \theta_k} - (1 - y_i) \frac{1}{1 - h(x_i)} \frac{\partial h(x_i)}{\partial \theta_k}\right] \
&amp;amp;= -\frac{1}{N} \sum_{i=1}^{N} \left[y_i \frac{1}{h(x_i)} h(x_i)(1 - h(x_i)) x_{ik} - (1 - y_i) \frac{1}{1 - h(x_i)} h(x_i)(1 - h(x_i)) x_{ik}\right] \
&amp;amp;= -\frac{1}{N} \sum_{i=1}^{N} \left[y_i (1 - h(x_i)) x_{ik} - (1 - y_i) h(x_i) x_{ik}\right] \
&amp;amp;= -\frac{1}{N} \sum_{i=1}^{N} \left[(y_i - h(x_i)) x_{ik}\right] \
\end{align*}
$$
其中 $x_i = (X[i, :])^\top$ 是第 $i$ 个样本的特征向量，$x_{ik} = X[i, k]$ 是第 $i$ 个样本的第 $k$ 个特征。&lt;/p&gt;
&lt;p&gt;然后将其整理成梯度张量的形式，得到
$$
\begin{aligned}
\nabla_\theta J &amp;amp;= -\frac{1}{N} \sum_{i=1}^{N} (y_i - h(x_i)) \begin{bmatrix} x_{i1} \ x_{i2} \ \vdots \ x_{id}\end{bmatrix} \
&amp;amp;= -\frac{1}{N} \sum_{i=1}^{N} (y_i - h(x_i)) (X[i, :])^\top \
&amp;amp;= -\frac{1}{N} X^\top (y - h(X)) \
&amp;amp;= -\frac{1}{N} X^\top \left(y - \sigma(X\theta)\right)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;当然，这个结果也可以直接使用链式法则来推导得到，此处不赘述。&lt;/p&gt;
</content:encoded></item><item><title>C++ Misc</title><link>https://adalovelemon.github.io/posts/content/technotes/c/cpp-points/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/c/cpp-points/</guid><pubDate>Sat, 19 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;1. C++ 中的列表、字符串&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了寻找 Python 中 &lt;code&gt;list&lt;/code&gt; 和 &lt;code&gt;str&lt;/code&gt; 的对应 C++ 平替，我们使用 &lt;code&gt;vector&lt;/code&gt; 和 &lt;code&gt;string&lt;/code&gt;。在 C++ 中，&lt;code&gt;vector&lt;/code&gt; 是一个动态数组，可以存储任意类型的元素，并且可以动态调整大小。&lt;code&gt;string&lt;/code&gt; 则是一个用于处理文本的类，提供了许多方便的字符串操作方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;

vector&amp;lt;int&amp;gt; nums = {1, 2, 3, 4, 5}; // 初始化一个整数 vector 
vector&amp;lt;int&amp;gt; empty; // 空 vector
vector&amp;lt;int&amp;gt; list(nums.size()); // 创建一个与 nums 相同大小的空 vector

#include &amp;lt;string&amp;gt;
string str = &quot;Hello, World!&quot;; // 初始化一个字符串
string emptyStr; // 空字符串
string subStr = str.substr(0, 5); // 获取子字符串 &quot;Hello&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;list 与 vector 常用操作对比&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Python list&lt;/th&gt;
&lt;th&gt;C++ vector&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;len(list)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nums.size()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list[i:j]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vector&amp;lt;int&amp;gt;(nums.begin()+i, nums.begin()+j)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list.append(x)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nums.push_back(x)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list.insert(i, x)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nums.insert(nums.begin()+i, x)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;str 与 string 常用操作对比&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Python str&lt;/th&gt;
&lt;th&gt;C++ string&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;len(str)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str.size()&lt;/code&gt; 或 &lt;code&gt;str.length()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;str[i:j]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str.substr(i, j-i)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;str += &apos;c&apos;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str += &apos;c&apos;&lt;/code&gt; 或 &lt;code&gt;str.push_back(&apos;c&apos;)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;str.insert(i, &quot;text&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;str.insert(i, &quot;text&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;示例代码&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 获取长度
int vecSize = nums.size();
int strLen = str.length();

// 获取子串/子列表
vector&amp;lt;int&amp;gt; subVec(nums.begin()+1, nums.begin()+4); // 获取索引 1-3 的元素
string subString = str.substr(7, 5); // 从索引 7 开始取 5 个字符 &quot;World&quot;

// 添加元素
nums.push_back(6); // 在末尾添加元素 6
str += &quot; Welcome!&quot;; // 在字符串末尾添加文本

// 插入元素
nums.insert(nums.begin()+2, 10); // 在索引 2 处插入元素 10
str.insert(5, &quot;, C++&quot;); // 在索引 5 处插入字符串
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 创建指定大小的二维 vector&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 C++ 中，我们可以创建指定行数和列数的二维 vector，类似于 Python 中的二维列表。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;

// 创建一个 3x4 的二维 vector，初始值为 0
vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; matrix(3, vector&amp;lt;int&amp;gt;(4, 0));

// 创建一个 m x n 的二维 vector，初始值为某个特定值
int m = 5, n = 6;
vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; grid(m, vector&amp;lt;int&amp;gt;(n, -1)); // 5x6，初始值为 -1

// 创建空的二维 vector，然后动态添加行
vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; dynamicMatrix;
for (int i = 0; i &amp;lt; 3; i++) {
    dynamicMatrix.push_back(vector&amp;lt;int&amp;gt;(4, 0)); // 添加一行，包含 4 个元素，初始值为 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;访问和修改二维 vector&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 访问元素
int value = matrix[1][2]; // 获取第 1 行第 2 列的元素

// 修改元素
matrix[0][3] = 42; // 将第 0 行第 3 列设置为 42

// 获取维度信息
int rows = matrix.size(); // 行数
int cols = matrix[0].size(); // 列数（假设所有行都有相同的列数）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;与 Python 二维列表的对比&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Python&lt;/th&gt;
&lt;th&gt;C++&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matrix = [[0] * 4 for _ in range(3)]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; matrix(3, vector&amp;lt;int&amp;gt;(4, 0))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;matrix[i][j]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;matrix[i][j]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;len(matrix)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;matrix.size()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;len(matrix[0])&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;matrix[0].size()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;3. C++ 中类内函数访问&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 C++ 中，类内函数可以通过 &lt;code&gt;this&lt;/code&gt; 指针访问类的成员变量和其他成员函数。&lt;code&gt;this&lt;/code&gt; 指针指向当前对象的地址。&lt;/p&gt;
&lt;p&gt;以下是使用标准模板库实现的神经网络线性层，包含前向传播、反向传播和参数更新功能。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;random&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;numeric&amp;gt;

class LinearLayer {
private:
    std::vector&amp;lt;std::vector&amp;lt;double&amp;gt;&amp;gt; weights;  // 权重矩阵 [out_size x in_size]
    std::vector&amp;lt;double&amp;gt; bias;                  // 偏置向量 [out_size]
    std::vector&amp;lt;double&amp;gt; last_input;           // 保存最后一次的输入，用于反向传播
    
    int input_size;
    int output_size;
    double learning_rate;

public:
    // 构造函数 - 参数名与成员变量同名时使用this
    LinearLayer(int input_size, int output_size, double learning_rate = 0.01) 
        : input_size(input_size), output_size(output_size), learning_rate(learning_rate) {
        
        // 使用this指针访问成员变量
        this-&amp;gt;weights.resize(this-&amp;gt;output_size, std::vector&amp;lt;double&amp;gt;(this-&amp;gt;input_size));
        this-&amp;gt;bias.resize(this-&amp;gt;output_size);
        
        // Xavier初始化
        std::random_device rd;
        std::mt19937 gen(rd());
        double stddev = std::sqrt(2.0 / (this-&amp;gt;input_size + this-&amp;gt;output_size));
        std::normal_distribution&amp;lt;double&amp;gt; dist(0.0, stddev);
        
        // 使用this指针初始化权重
        for (int i = 0; i &amp;lt; this-&amp;gt;output_size; i++) {
            for (int j = 0; j &amp;lt; this-&amp;gt;input_size; j++) {
                this-&amp;gt;weights[i][j] = dist(gen);
            }
        }
        
        // 使用this指针初始化偏置为0
        std::fill(this-&amp;gt;bias.begin(), this-&amp;gt;bias.end(), 0.0);
    }
    
    // 前向传播
    std::vector&amp;lt;double&amp;gt; forward(const std::vector&amp;lt;double&amp;gt;&amp;amp; input) {
        if (input.size() != this-&amp;gt;input_size) {
            throw std::invalid_argument(&quot;Input size mismatch&quot;);
        }
        
        // 使用this指针保存输入用于反向传播
        this-&amp;gt;last_input = input;
        
        std::vector&amp;lt;double&amp;gt; output(this-&amp;gt;output_size, 0.0);
        
        // 使用this指针计算 output = weights * input + bias
        for (int i = 0; i &amp;lt; this-&amp;gt;output_size; i++) {
            output[i] = this-&amp;gt;bias[i];
            for (int j = 0; j &amp;lt; this-&amp;gt;input_size; j++) {
                output[i] += this-&amp;gt;weights[i][j] * input[j];
            }
        }
        
        return output;
    }
    
    // 反向传播
    std::vector&amp;lt;double&amp;gt; backward(const std::vector&amp;lt;double&amp;gt;&amp;amp; grad_output) {
        if (grad_output.size() != this-&amp;gt;output_size) {
            throw std::invalid_argument(&quot;Gradient output size mismatch&quot;);
        }
        
        // 使用this指针计算输入的梯度
        std::vector&amp;lt;double&amp;gt; grad_input(this-&amp;gt;input_size, 0.0);
        for (int j = 0; j &amp;lt; this-&amp;gt;input_size; j++) {
            for (int i = 0; i &amp;lt; this-&amp;gt;output_size; i++) {
                grad_input[j] += this-&amp;gt;weights[i][j] * grad_output[i];
            }
        }
        
        // 使用this指针更新权重和偏置
        for (int i = 0; i &amp;lt; this-&amp;gt;output_size; i++) {
            // 更新偏置
            this-&amp;gt;bias[i] -= this-&amp;gt;learning_rate * grad_output[i];
            
            // 更新权重
            for (int j = 0; j &amp;lt; this-&amp;gt;input_size; j++) {
                this-&amp;gt;weights[i][j] -= this-&amp;gt;learning_rate * grad_output[i] * this-&amp;gt;last_input[j];
            }
        }
        
        return grad_input;
    }
    
    // 设置学习率 - 参数名与成员变量同名时使用this
    void setLearningRate(double learning_rate) {
        this-&amp;gt;learning_rate = learning_rate;
    }
    
    // 获取权重 - 使用this指针返回成员变量
    const std::vector&amp;lt;std::vector&amp;lt;double&amp;gt;&amp;gt;&amp;amp; getWeights() const {
        return this-&amp;gt;weights;
    }
    
    // 获取偏置 - 使用this指针返回成员变量
    const std::vector&amp;lt;double&amp;gt;&amp;amp; getBias() const {
        return this-&amp;gt;bias;
    }
    
    // 打印层信息 - 使用this指针访问成员变量
    void printInfo() const {
        std::cout &amp;lt;&amp;lt; &quot;Linear Layer: &quot; &amp;lt;&amp;lt; this-&amp;gt;input_size &amp;lt;&amp;lt; &quot; -&amp;gt; &quot; &amp;lt;&amp;lt; this-&amp;gt;output_size 
                  &amp;lt;&amp;lt; &quot;, Learning Rate: &quot; &amp;lt;&amp;lt; this-&amp;gt;learning_rate &amp;lt;&amp;lt; std::endl;
    }
};

// 使用示例
int main() {
    // 创建一个 3输入 -&amp;gt; 2输出 的线性层
    LinearLayer layer(3, 2, 0.01);
    
    // 输入数据
    std::vector&amp;lt;double&amp;gt; input = {1.0, 2.0, 3.0};
    
    // 前向传播
    std::vector&amp;lt;double&amp;gt; output = layer.forward(input);
    
    std::cout &amp;lt;&amp;lt; &quot;Input: &quot;;
    for (double val : input) std::cout &amp;lt;&amp;lt; val &amp;lt;&amp;lt; &quot; &quot;;
    std::cout &amp;lt;&amp;lt; &quot;\nOutput: &quot;;
    for (double val : output) std::cout &amp;lt;&amp;lt; val &amp;lt;&amp;lt; &quot; &quot;;
    std::cout &amp;lt;&amp;lt; std::endl;
    
    // 模拟反向传播（假设输出梯度）
    std::vector&amp;lt;double&amp;gt; grad_output = {0.5, -0.3};
    std::vector&amp;lt;double&amp;gt; grad_input = layer.backward(grad_output);
    
    std::cout &amp;lt;&amp;lt; &quot;Gradient Input: &quot;;
    for (double val : grad_input) std::cout &amp;lt;&amp;lt; val &amp;lt;&amp;lt; &quot; &quot;;
    std::cout &amp;lt;&amp;lt; std::endl;
    
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用 this 指针的关键场景：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;setLearningRate&lt;/code&gt; 函数&lt;/strong&gt;：参数名 &lt;code&gt;learning_rate&lt;/code&gt; 与成员变量同名，必须使用 &lt;code&gt;this-&amp;gt;learning_rate&lt;/code&gt; 来区分&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构造函数中&lt;/strong&gt;：虽然使用了初始化列表，但在函数体内访问成员变量时显式使用了 &lt;code&gt;this&lt;/code&gt; 指针&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;所有成员函数&lt;/strong&gt;：显式使用 &lt;code&gt;this&lt;/code&gt; 指针访问成员变量，提高代码可读性和明确性&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种写法虽然不是必需的（除了参数名冲突的情况），但能让代码更加清晰地表明正在访问的是类的成员变量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Python 中 集合 set 与 字典 dict 的平替&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 C++ 中，集合和字典可以使用 &lt;code&gt;std::set&lt;/code&gt; 和 &lt;code&gt;std::unordered_map&lt;/code&gt; 来实现。&lt;code&gt;std::set&lt;/code&gt; 是一个不允许重复元素的容器，而 &lt;code&gt;std::map&lt;/code&gt; 是一个键值对容器，类似于 Python 的字典。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;set&amp;gt;
#include &amp;lt;map&amp;gt;

// 创建一个整数集合
std::set&amp;lt;int&amp;gt; mySet = {1, 2, 3, 4, 5};

// 创建一个空集合
std::set&amp;lt;int&amp;gt; emptySet;

// 向集合中添加元素
mySet.insert(6); // 添加元素 6

// 检查元素是否存在
bool exists = (mySet.find(3) != mySet.end()); // 检查元素 3 是否存在

// 删除元素
mySet.erase(2); // 删除元素 2   

// 获取集合大小
int setSize = mySet.size(); // 获取集合大小

// 创建一个整数到字符串的映射
std::map&amp;lt;int, std::string&amp;gt; myDict;

// 创建一个空字典
std::map&amp;lt;int, std::string&amp;gt; emptyDict;

// 向字典中添加键值对
myDict[1] = &quot;one&quot;; // 添加键 1，值 &quot;one&quot;

// 检查键是否存在
bool keyExists = (myDict.find(2) != myDict.end()); // 检查键 2 是否存在

// 获取值
std::string value = myDict[1]; // 获取键 1 对应的值

// 删除键值对
myDict.erase(1); // 删除键 1 对应的键值对

// 获取字典大小
int dictSize = myDict.size(); // 获取字典大小

// 遍历集合
for (const auto&amp;amp; elem : mySet) {
    std::cout &amp;lt;&amp;lt; elem &amp;lt;&amp;lt; &quot; &quot;; // 输出集合中的元素
}

// 遍历字典
for (const auto&amp;amp; pair : myDict) {
    std::cout &amp;lt;&amp;lt; pair.first &amp;lt;&amp;lt; &quot;: &quot; &amp;lt;&amp;lt; pair.second &amp;lt;&amp;lt; &quot; &quot;; // 输出键值对
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;th&gt;Python set&lt;/th&gt;
&lt;th&gt;C++ std::set&lt;/th&gt;
&lt;th&gt;Python dict&lt;/th&gt;
&lt;th&gt;C++ std::map&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;创建&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s = {1, 2, 3}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::set&amp;lt;int&amp;gt; s = {1, 2, 3};&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d = {&apos;a&apos;: 1, &apos;b&apos;: 2}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::map&amp;lt;std::string, int&amp;gt; m = {{&quot;a&quot;,1}, {&quot;b&quot;,2}};&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;添加元素&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s.add(4)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s.insert(4);&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d[&apos;c&apos;] = 3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;m[&quot;c&quot;] = 3;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;检查成员&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4 in s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s.find(4) != s.end()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&apos;a&apos; in d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;m.find(&quot;a&quot;) != m.end()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;删除元素&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s.remove(2)&lt;/code&gt; 或 &lt;code&gt;s.discard(2)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s.erase(2);&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;del d[&apos;b&apos;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;m.erase(&quot;b&quot;);&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;大小&lt;/td&gt;
&lt;td&gt;&lt;code&gt;len(s)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s.size()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;len(d)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;m.size()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;遍历&lt;/td&gt;
&lt;td&gt;&lt;code&gt;for x in s:&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;for (auto &amp;amp;x : s) { … }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;for k, v in d.items():&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;for (auto &amp;amp;p : m) { /* p.first, p.second */ }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;元素顺序&lt;/td&gt;
&lt;td&gt;无序&lt;/td&gt;
&lt;td&gt;自动按升序排序&lt;/td&gt;
&lt;td&gt;保持插入顺序（Python 3.7+）&lt;/td&gt;
&lt;td&gt;按 key 自动排序&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;底层实现&lt;/td&gt;
&lt;td&gt;哈希表&lt;/td&gt;
&lt;td&gt;红黑树（或平衡 BST）&lt;/td&gt;
&lt;td&gt;哈希表&lt;/td&gt;
&lt;td&gt;红黑树（或平衡 BST）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;下标访问&lt;/td&gt;
&lt;td&gt;不支持&lt;/td&gt;
&lt;td&gt;不支持&lt;/td&gt;
&lt;td&gt;支持 &lt;code&gt;d[&apos;key&apos;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;支持 &lt;code&gt;m[&quot;key&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;实现 list 向 set 的转换&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;set&amp;gt;

std::vector&amp;lt;int&amp;gt; vec = {1, 2, 3, 4, 5, 5}; // 包含重复元素的 vector
std::set&amp;lt;int&amp;gt; uniqueSet(vec.begin(), vec.end()); // 转换为 set，自动去重
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;删除容器，类似 Python 的 del&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;set&amp;gt;
std::set&amp;lt;int&amp;gt; mySet = {1, 2, 3, 4, 5};
// 删除整个 set
mySet.clear(); // 清空 set
// 删除 set 对象
mySet.~set(); // 显式调用析构函数（不推荐，通常使用 clear()）

vector&amp;lt;int&amp;gt; myVector = {1, 2, 3, 4, 5};
// 删除整个 vector
myVector.clear(); // 清空 vector
// 删除 vector 对象
myVector.~vector(); // 显式调用析构函数（不推荐，通常使用 clear()）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;5. C++ 中 map 默认值如何初始化为-1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 C++ 中，&lt;code&gt;std::map&lt;/code&gt; 的默认值可以通过在插入时指定默认值来实现。以下是一个示例，展示如何将 &lt;code&gt;std::map&lt;/code&gt; 的默认值初始化为 -1。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;map&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;iostream&amp;gt;

int main() {
    std::map&amp;lt;std::string, int&amp;gt; myMap;
    
    // 访问不存在的键时，会自动创建该键并赋值为 int 的默认值 0
    std::cout &amp;lt;&amp;lt; myMap[&quot;notExist&quot;] &amp;lt;&amp;lt; std::endl; // 输出 0
    
    // 可以使用 [] 操作符手动设置默认值
    std::string key = &quot;newKey&quot;;
    if (myMap.find(key) == myMap.end()) {
        myMap[key] = -1; // 手动设置默认值为 -1
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;6. C++ 中实现类似 Python defaultdict 的功能&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 Python 中，&lt;code&gt;defaultdict&lt;/code&gt; 允许我们为不存在的键提供默认值。C++ 中没有直接等价物，但可以通过以下方法实现类似功能：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;unordered_map&amp;gt;
#include &amp;lt;iostream&amp;gt;

// 方法 1: 使用 [] 运算符和查找
void method1() {
    std::unordered_map&amp;lt;std::string, int&amp;gt; counter;
    
    // 检查键是否存在，不存在则初始化为1
    std::string word = &quot;hello&quot;;
    if (counter.find(word) == counter.end()) {
        counter[word] = 1; // 初始值设为1
    } else {
        counter[word]++; // 存在则增加计数
    }
}

// 方法 2: 自定义一个带默认值的包装类
template&amp;lt;typename K, typename V&amp;gt;
class DefaultMap {
private:
    std::unordered_map&amp;lt;K, V&amp;gt; map;
    V defaultValue;

public:
    DefaultMap(V defaultVal) : defaultValue(defaultVal) {}
    
    V&amp;amp; operator[](const K&amp;amp; key) {
        if (map.find(key) == map.end()) {
            map[key] = defaultValue;
        }
        return map[key];
    }
    
    // 其他必要的函数...
};

// 方法 3: 使用 C++17 的 try_emplace
void method3() {
    std::unordered_map&amp;lt;std::string, int&amp;gt; counter;
    
    std::string word = &quot;hello&quot;;
    // 尝试插入默认值1，如果键不存在
    auto [it, inserted] = counter.try_emplace(word, 1);
    
    if (!inserted) {
        // 键已存在，增加计数
        it-&amp;gt;second++;
    }
}

int main() {
    // 使用自定义的DefaultMap
    DefaultMap&amp;lt;std::string, int&amp;gt; wordCount(1); // 默认值为1
    
    wordCount[&quot;apple&quot;]++;  // 第一次访问，设为1，然后+1变成2
    wordCount[&quot;banana&quot;]++; // 第一次访问，设为1，然后+1变成2
    wordCount[&quot;apple&quot;]++;  // 变成3
    
    std::cout &amp;lt;&amp;lt; &quot;apple count: &quot; &amp;lt;&amp;lt; wordCount[&quot;apple&quot;] &amp;lt;&amp;lt; std::endl;    // 输出 3
    std::cout &amp;lt;&amp;lt; &quot;banana count: &quot; &amp;lt;&amp;lt; wordCount[&quot;banana&quot;] &amp;lt;&amp;lt; std::endl;  // 输出 2
    std::cout &amp;lt;&amp;lt; &quot;cherry count: &quot; &amp;lt;&amp;lt; wordCount[&quot;cherry&quot;] &amp;lt;&amp;lt; std::endl;  // 输出 1 (默认值)
    
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些方法可以模拟 Python 的 &lt;code&gt;defaultdict(int)&lt;/code&gt; 或 &lt;code&gt;defaultdict(lambda: 1)&lt;/code&gt; 的行为，特别适用于计数器和频率统计等场景。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7. C++ 中查找 map 中某个键的值是否存在&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 C++ 中，可以使用 &lt;code&gt;std::map&lt;/code&gt; 的 &lt;code&gt;find&lt;/code&gt; 方法来检查某个键是否存在。如果键存在，&lt;code&gt;find&lt;/code&gt; 方法会返回一个指向该键值对的迭代器；如果不存在，则返回 &lt;code&gt;end()&lt;/code&gt; 迭代器。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;map&amp;lt;std::string, int&amp;gt; myMap = {{&quot;apple&quot;, 1}, {&quot;banana&quot;, 2}, {&quot;cherry&quot;, 3}};

std::string keyToFind = &quot;banana&quot;;
auto it = myMap.find(keyToFind);

if (it != myMap.end()) {
    // 键存在，输出对应的值
    std::cout &amp;lt;&amp;lt; &quot;Key: &quot; &amp;lt;&amp;lt; keyToFind &amp;lt;&amp;lt; &quot;, Value: &quot; &amp;lt;&amp;lt; it-&amp;gt;second &amp;lt;&amp;lt; std::endl;
} else {
    // 键不存在
    std::cout &amp;lt;&amp;lt; &quot;Key: &quot; &amp;lt;&amp;lt; keyToFind &amp;lt;&amp;lt; &quot; does not exist.&quot; &amp;lt;&amp;lt; std::endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;8. C++ 中的 map 如何遍历所有键值对&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 C++ 中，可以使用范围基于的 for 循环或迭代器来遍历 &lt;code&gt;std::map&lt;/code&gt; 中的所有键值对。以下是两种常用的方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;map&amp;lt;std::string, int&amp;gt; myMap = {{&quot;apple&quot;, 1}, {&quot;banana&quot;, 2}, {&quot;cherry&quot;, 3}};
// 方法 1: 使用范围基于的 for 循环
for (const auto&amp;amp; pair : myMap) {
    std::cout &amp;lt;&amp;lt; &quot;Key: &quot; &amp;lt;&amp;lt; pair.first &amp;lt;&amp;lt; &quot;, Value: &quot; &amp;lt;&amp;lt; pair.second &amp;lt;&amp;lt; std::endl;
}

// 方法 2: 使用迭代器
for (auto it = myMap.begin(); it != myMap.end(); ++it) {
    std::cout &amp;lt;&amp;lt; &quot;Key: &quot; &amp;lt;&amp;lt; it-&amp;gt;first &amp;lt;&amp;lt; &quot;, Value: &quot; &amp;lt;&amp;lt; it-&amp;gt;second &amp;lt;&amp;lt; std::endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;9. C++ 中直接返回 vector&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vector&amp;lt;int&amp;gt; createVector(int size, int initialValue) {
    if (size &amp;lt;= 0) {
        return {}; // 返回空 vector
    }
    return {initialValue}; // 返回一个包含 initialValue 的 vector
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种直接返回 vector 的方式在 C++ 中是合法的，并且会自动处理内存管理。C++11 及以后的版本支持移动语义，这使得返回大型对象（如 vector）时性能更高效。&lt;/p&gt;
&lt;p&gt;此外，C++ 会自动根据函数类型，判断 &lt;code&gt;{}&lt;/code&gt; 的返回内容和类型。例如，返回 &lt;code&gt;map&lt;/code&gt; 时，C++ 会自动推断出返回类型为 &lt;code&gt;std::map&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;map&amp;lt;int, int&amp;gt; createMap() {
    return {{1, 10}, {2, 20}, {3, 30}}; // 返回一个初始化的 map
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同理，C++ 也支持直接返回 &lt;code&gt;set&lt;/code&gt;、&lt;code&gt;string&lt;/code&gt; 等 STL 容器。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set&amp;lt;int&amp;gt; createSet() {
    return {1, 2, 3, 4, 5}; // 返回一个初始化的 set
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;10. C++ 中用多重集分解字符串&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 C++ 中，可以使用 &lt;code&gt;std::multiset&lt;/code&gt; 来存储字符串的分解结果。&lt;code&gt;std::multiset&lt;/code&gt; 允许存储重复的元素，因此适合用于分解字符串时统计每个字符的出现次数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;string input = &quot;hello world&quot;;
std::multiset&amp;lt;char&amp;gt; charSet;
for (char c : input) {
    if (c != &apos; &apos;) { // 忽略空格
        charSet.insert(c); // 插入字符到 multiset
    }
}
// 输出分解结果
for (const auto&amp;amp; c : charSet) {
    std::cout &amp;lt;&amp;lt; c &amp;lt;&amp;lt; &quot; &quot;;
}// 输出结果: d e h l l o r w
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，也可以用 &lt;code&gt;std::set&lt;/code&gt; 来存储唯一字符，但如果需要统计每个字符的出现次数，&lt;code&gt;std::multiset&lt;/code&gt; 更为合适。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;string input = &quot;hello world&quot;;
std::set&amp;lt;char&amp;gt; charSet;
for (char c : input) {
    if (c != &apos; &apos;) { // 忽略空格
        charSet.insert(c); // 插入字符到 set
    }
}
// 输出分解结果
for (const auto&amp;amp; c : charSet) {
    std::cout &amp;lt;&amp;lt; c &amp;lt;&amp;lt; &quot; &quot;;
}// 输出结果: d e h l o r w
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;11. C++ 中的对字符串排序&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;C++ 中可以使用 &lt;code&gt;std::sort&lt;/code&gt; 函数对字符串进行排序。&lt;code&gt;std::sort&lt;/code&gt; 是一个通用的排序算法，可以对任何可迭代的容器进行排序，包括字符串。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;string str = &quot;hello world&quot;;
std::sort(str.begin(), str.end()); // 对字符串进行排序
std::cout &amp;lt;&amp;lt; str &amp;lt;&amp;lt; std::endl; // 输出排序后的字符串 &quot; dehllloorw&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Do we really need encoders for generative models?</title><link>https://adalovelemon.github.io/posts/content/coursenotes/mathslab/dowereallyneedencoders/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/mathslab/dowereallyneedencoders/</guid><pubDate>Fri, 27 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In modern generative AI, encoders are commonly used during training to help models understand the context of input data. However, these encoders are often removed during inference. This raises an interesting question, &lt;strong&gt;if we train models using only decoders, can they still generate meaningful outputs?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To simplify the problem, consider an Autoencoder task, where we aim to optimize the reconstruction of input data. Given ${x_i}&lt;em&gt;{i=1}^N$, the goal is to minimize the reconstruction loss
$$
\mathcal{L} = \frac{1}{N} \sum&lt;/em&gt;{i=1}^N | x_i - f(g(x_i)) |^2
$$&lt;/p&gt;
&lt;p&gt;we use MSE here as the supervision signal, where $f$ is the decoder and $g$ is the encoder.&lt;/p&gt;
&lt;h2&gt;Ablations&lt;/h2&gt;
&lt;h3&gt;case 1: Encoder-Decoder Pair + Original Images&lt;/h3&gt;
&lt;p&gt;After proper fine-tuning, the encoder-decoder pair learns to compress the input data into a latent space and then reconstruct it back to the original space.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/ed-original.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;center&amp;gt;Reconstruction results, trained on CIFAR10 for 30 epochs&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;h3&gt;case 2: Encoder-Decoder Pair + Zero-Valued Images&lt;/h3&gt;
&lt;p&gt;Now comes another case, we employ the zero-valued images as inputs. We compare two scenarios here, one with the original images as the test inputs, while the other uses zero-valued images as the test inputs as well.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/ed-test-original.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;center&amp;gt;Reconstruction results, trained on CIFAR10 for 30 epochs, original images input&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/ed-zero.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;center&amp;gt;Reconstruction results, trained on CIFAR10 for 30 epochs, zero-valued images input&amp;lt;/center&amp;gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;Both of the results look quite similar, with the majority of the pixels valued around $0.5$, indicating a lack of meaningful information in the generated images. This suggests that the model has learned to generate a constant output, which is not very useful.&lt;/p&gt;
&lt;h3&gt;case 3: Decoder Only + Original Images&lt;/h3&gt;
&lt;p&gt;In this case, we remove the encoder and only use the decoder to generate outputs. We use one-hot class labels and Gaussian random noises as inputs to the decoder. And the reconstruction loss is still defined as the MSE between the generated outputs and the original images.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./assets/decoder-only.png&quot; alt=&quot;&quot; /&gt;
&amp;lt;center&amp;gt;Reconstruction results, trained on CIFAR10 for 30 epochs, decoder-only&amp;lt;/center&amp;gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;It could be observed that the generated images are still unable to reconstruct the given images, with most of the pixels valued around $0.5$ same as the previous case. This indicates that the decoder alone is not sufficient to generate meaningful outputs, as it lacks the context provided by the encoder, but the synthesized images looks more diverse and more similar to the original ones than the previous cases.&lt;/p&gt;
&lt;h2&gt;Analysis&lt;/h2&gt;
&lt;p&gt;The above phononemenon can be explained by the following theoretical analysis. Expand the reconstruction loss function,
$$
\mathcal{L} = \frac{1}{N} \sum_{i=1}^N | x_i - \hat x |^2 = \hat x^\top \hat x - \frac{2}{N} \sum_{i=1}^N x_i^\top \hat x + \frac{1}{N} \sum_{i=1}^N x_i^\top x_i
$$&lt;/p&gt;
&lt;p&gt;where $\hat x = f(g(x_i))$ is the reconstructed output. From this quadratic form, we could observed that without the encoder, $\hat x$ is more like a random variable, i.e., a free parameter. The optimum for $\hat x$ is &lt;strong&gt;the average of the input data&lt;/strong&gt; $\frac{1}{N} \sum_{i=1}^N x_i$. This explains why the generated outputs are mostly constant values around $0.5$ in the previous cases.&lt;/p&gt;
&lt;p&gt;However, when considered using encoder, the optimization problem becomes more constrained, which means $\hat x$ is not a free parameter anymore, and thus the average of the input data is not the optimum.&lt;/p&gt;
&lt;p&gt;This indicates that the encoder plays a crucial role in providing context and structure to the generated outputs, allowing the model to learn more meaningful representations of the input data.&lt;/p&gt;
</content:encoded></item><item><title>Reverse KL Divergence</title><link>https://adalovelemon.github.io/posts/content/coursenotes/mathslab/reversekld/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/mathslab/reversekld/</guid><pubDate>Wed, 25 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;Definitions&lt;/h3&gt;
&lt;p&gt;The KL Divergence (KLD) is defined as
$$
\mathbb{D}&lt;em&gt;{\text{KL}}[p(\cdot) || q(\cdot)] = \mathbb{E}&lt;/em&gt;{x\sim p(\cdot)}\left[\log\frac{p(x)}{q(x)}\right] = \int p(x) \log\frac{p(x)}{q(x)}dx
$$&lt;/p&gt;
&lt;p&gt;This is the standard form we typically use to measure how different two distributions are, which we call the &lt;em&gt;&lt;strong&gt;forward KLD&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;But there&apos;s another way to look at it - what if we swap the roles of $p$ and $q$?
$$
\mathbb{D}&lt;em&gt;{\text{KL}}[q(\cdot) || p(\cdot)] = \mathbb{E}&lt;/em&gt;{x\sim q(\cdot)}\left[\log\frac{q(x)}{p(x)}\right] = \int q(x) \log\frac{q(x)}{p(x)}dx
$$&lt;/p&gt;
&lt;p&gt;This gives us what we call the &lt;em&gt;&lt;strong&gt;reverse KLD&lt;/strong&gt;&lt;/em&gt;. While they might look similar, they behave quite differently in practice.&lt;/p&gt;
&lt;h3&gt;Experimental Results&lt;/h3&gt;
&lt;h4&gt;Setup&lt;/h4&gt;
&lt;p&gt;We fitted a 2-component Gaussian mixture to approximate a 3-component target distribution with peaks at positions &lt;code&gt;[-2, 0, 2]&lt;/code&gt; and weights &lt;code&gt;[0.3, 0.4, 0.3]&lt;/code&gt;. The KLD values were computed using &lt;strong&gt;Monte Carlo sampling&lt;/strong&gt; with 20,000 samples to ensure numerical accuracy, rather than numerical integration which can suffer from discretization errors.&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;/blog/resources/KLD.py&quot; download=&quot;KLD.py&quot;&amp;gt; Click to Download Experiment Script&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;h4&gt;Observed Phenomena&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Forward KLD Results:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Means&lt;/strong&gt;: &lt;code&gt;[-1.98, 1.97]&lt;/code&gt; - positioned near outer target modes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standard deviations&lt;/strong&gt;: &lt;code&gt;[0.41, 0.42]&lt;/code&gt; - wider to cover intermediate regions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weights&lt;/strong&gt;: &lt;code&gt;[0.52, 0.48]&lt;/code&gt; - balanced between components&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loss&lt;/strong&gt;: &lt;code&gt;0.0012&lt;/code&gt; - very low&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Reverse KLD Results:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Means&lt;/strong&gt;: &lt;code&gt;[-2.001, 0.010]&lt;/code&gt; - focused on the two strongest modes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standard deviations&lt;/strong&gt;: &lt;code&gt;[0.299, 0.418]&lt;/code&gt; - matching target component widths&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weights&lt;/strong&gt;: &lt;code&gt;[0.426, 0.574]&lt;/code&gt; - proportional to target mode strengths&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loss&lt;/strong&gt;: &lt;code&gt;0.3524&lt;/code&gt; - significantly higher&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;assets/KLD_sampling.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Key Behavioral Differences&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Forward KLD&lt;/strong&gt; creates a bimodal approximation that positions components to provide coverage across all target modes. The components are placed strategically to minimize the maximum approximation error across the entire distribution support.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reverse KLD&lt;/strong&gt; produces a selective approximation, focusing computational resources on the two most prominent modes (&lt;code&gt;x = -2&lt;/code&gt; and &lt;code&gt;x = 0&lt;/code&gt;) while completely ignoring the rightmost peak at &lt;code&gt;x = 2&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Forward KLD&lt;/strong&gt; uses broader components with balanced weights to ensure no region of significant target mass is left uncovered, while &lt;strong&gt;Reverse KLD&lt;/strong&gt; uses component parameters that closely match the characteristics of the selected target modes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Loss magnitude difference&lt;/strong&gt;: Forward KLD achieves much lower loss values, indicating successful coverage of the target distribution, while Reverse KLD accepts higher loss in exchange for concentrated, high-fidelity approximation of selected modes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Theoretical Explanation&lt;/h3&gt;
&lt;p&gt;The key to understanding these different behaviors lies in examining the mathematical forms and the role of the &lt;strong&gt;weighting function&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;Mathematical Analysis&lt;/h4&gt;
&lt;p&gt;Let&apos;s rewrite the two KLD formulations with emphasis on their weighting:&lt;/p&gt;
&lt;p&gt;$$
\text{Forward KLD:}\quad
\mathbb{D}&lt;em&gt;{\text{KL}}[p | q] = \int \underbrace{p(x)}&lt;/em&gt;{\text{weight}} \log\frac{p(x)}{q(x)} , dx
$$&lt;/p&gt;
&lt;p&gt;$$
\text{Reverse KLD:}\quad
\mathbb{D}&lt;em&gt;{\text{KL}}[q | p] = \int \underbrace{q(x)}&lt;/em&gt;{\text{weight}} \log\frac{q(x)}{p(x)} , dx
$$&lt;/p&gt;
&lt;p&gt;The crucial insight is that &lt;strong&gt;the first distribution in the KLD acts as the weighting function&lt;/strong&gt; for the expectation.&lt;/p&gt;
&lt;h4&gt;Penalty Mechanisms&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Forward KLD&lt;/strong&gt; is weighted by $p(x)$:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High penalty when $p(x)$ is large but $q(x)$ is small (since $\log\frac{p(x)}{q(x)} \to +\infty$)&lt;/li&gt;
&lt;li&gt;Low penalty when $p(x)$ is small, regardless of $q(x)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consequence&lt;/strong&gt;: $q$ must &quot;cover&quot; all regions where $p$ has significant mass&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Behavior&lt;/strong&gt;: Zero-avoiding, mode-averaging&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Reverse KLD&lt;/strong&gt; is weighted by $q(x)$:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;High penalty when $q(x)$ is large but $p(x)$ is small (since $\log\frac{q(x)}{p(x)} \to +\infty$)&lt;/li&gt;
&lt;li&gt;Low penalty when $q(x)$ is small, regardless of $p(x)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consequence&lt;/strong&gt;: $q$ avoids placing mass where $p$ has little mass&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Behavior&lt;/strong&gt;: Zero-forcing, mode-seeking&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Asymmetric Risk&lt;/h4&gt;
&lt;p&gt;Consider what happens when $p(x) \gg q(x)$ versus $q(x) \gg p(x)$:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Forward KLD&lt;/th&gt;
&lt;th&gt;Reverse KLD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$p(x) \gg q(x)$&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High penalty&lt;/strong&gt; ($p(x) \log\frac{p(x)}{q(x)}$ large)&lt;/td&gt;
&lt;td&gt;Low penalty ($q(x)$ small)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$q(x) \gg p(x)$&lt;/td&gt;
&lt;td&gt;Low penalty ($p(x)$ small)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High penalty&lt;/strong&gt; ($q(x) \log\frac{q(x)}{p(x)}$ large)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This asymmetry explains the observed behaviors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Forward KLD forces $q$ to &quot;stretch&quot; and cover all modes of $p$&lt;/li&gt;
&lt;li&gt;Reverse KLD forces $q$ to &quot;concentrate&quot; and avoid low-probability regions of $p$&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Information-Theoretic Perspective&lt;/h4&gt;
&lt;p&gt;From an information theory standpoint:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Forward KLD&lt;/strong&gt; measures the extra bits needed when using code $q$ to compress data from $p$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reverse KLD&lt;/strong&gt; measures the extra bits needed when using code $p$ to compress data from $q$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When optimizing $q$:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Forward KLD ensures $q$ can efficiently encode all data that $p$ might generate&lt;/li&gt;
&lt;li&gt;Reverse KLD ensures that data $q$ generates can be efficiently encoded by $p$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This fundamental asymmetry drives the dramatically different optimization behaviors observed in our experiments.&lt;/p&gt;
</content:encoded></item><item><title>Jacobian and Gradients</title><link>https://adalovelemon.github.io/posts/content/coursenotes/mathslab/jacobiangrad/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/mathslab/jacobiangrad/</guid><pubDate>Sun, 15 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;本文是对张量微分的一些思考。先从一个定理开始引入本文的内容。&lt;/p&gt;
&lt;h1&gt;总微分(Jacobian 存在性) 定理&lt;/h1&gt;
&lt;p&gt;设&lt;/p&gt;
&lt;p&gt;$$
f:\R^n;\longrightarrow;\R^m
$$&lt;/p&gt;
&lt;p&gt;在点 $x\in\R^n$ 可微（即在该点存在良好的线性近似）。那么有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;存在唯一的线性映射&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
Df(x);\colon;\R^n;\to;\R^m
$$&lt;/p&gt;
&lt;p&gt;使得当增量 $h\to0$ 时，&lt;/p&gt;
&lt;p&gt;$$
f(x+h);=;f(x);+;Df(x),h;+;o(|h|).
$$&lt;/p&gt;
&lt;p&gt;注: $Df(x)$ 在这里表示一个映射，其中输入变量是 $h \in \mathbb{R}^n$，输出变量是 $Df(x)h \in \mathbb{R}^m$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;在标准基下的矩阵表示——Jacobian&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果把 $Df(x)$ 在输入输出的标准基下写成矩阵形式，那么它就是我们常说的 &lt;strong&gt;Jacobian 矩阵&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
J_f(x)
=\begin{bmatrix}
\dfrac{\partial f_1}{\partial x_1} &amp;amp; \cdots &amp;amp; \dfrac{\partial f_1}{\partial x_n} \[6pt]
\vdots &amp;amp; \ddots &amp;amp; \vdots \[6pt]
\dfrac{\partial f_m}{\partial x_1} &amp;amp; \cdots &amp;amp; \dfrac{\partial f_m}{\partial x_n}
\end{bmatrix}
;\in;\R^{m\times n}.
$$&lt;/p&gt;
&lt;p&gt;也就是说，&lt;strong&gt;当&lt;/strong&gt; $f(x)=[f_1(x),\dots,f_m(x)]^T, \quad x=[x_1,\dots,x_n]^T$ &lt;strong&gt;时&lt;/strong&gt;，导数的“维度”就是 $\text{输出维度} m\times \text{输入维度} n$ —— 恰好能把输入的方向向量 $h\in\R^n$ 映射到输出的增量 $\Delta f\in\R^m$。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;高阶张量的导数&lt;/h1&gt;
&lt;p&gt;总微分定理可以推广到高阶张量上。设 $X \in \mathbb{R}^{a_1\times a_2\times \cdots\times a_n}$ 是一个 $n$ 阶张量，$Y \in \mathbb{R}^{b_1\times b_2\times \cdots\times b_m}$ 是一个 $m$ 阶张量。&lt;/p&gt;
&lt;p&gt;根据张量导数的本质是描述了张量 $X$ 的每一个元素对张量 $Y$ 的每一个元素的标量导数的特殊张量排列，可以得到张量 $X$ 对张量 $Y$ 的导数是一个 $(m+n)$ 阶张量，记为&lt;/p&gt;
&lt;p&gt;$$
\frac{\partial Y}{\partial X} \in \mathbb{R}^{ b_1\times b_2\times \cdots\times b_m \times a_1\times a_2\times \cdots \times a_n}
$$&lt;/p&gt;
&lt;p&gt;具体到某对元素之间的导数元素可以表示为&lt;/p&gt;
&lt;p&gt;$$\left(\frac{\partial Y}{\partial X}\right)&lt;em&gt;{j_1 \cdots j_m, i_1 \cdots i_n} = \frac{\partial Y&lt;/em&gt;{j_1 \cdots j_m}}{\partial X_{i_1 \cdots i_n}}$$&lt;/p&gt;
&lt;h1&gt;高阶张量的总微分定理&lt;/h1&gt;
&lt;p&gt;我们把张量 $X$ 和 张量 $Y$ 分别向量化重排列，得到 $\text{vec}(X) \in \mathbb{R}^{a_1 a_2 \cdots a_n}$ 和 $\text{vec}(Y) \in \mathbb{R}^{b_1 b_2\cdots b_m}$，此时应用向量的总微分定理即可得到对应的高阶张量的总微分定理，以及对应的导数的 Jacobian 矩阵形式&lt;/p&gt;
&lt;p&gt;$$
J_Y(X) = \begin{bmatrix}
\frac{\partial \text{vec}(Y)_1}{\partial \text{vec}(X)_1} &amp;amp; \frac{\partial \text{vec}(Y)_1}{\partial \text{vec}(X)_2} &amp;amp; \cdots &amp;amp; \frac{\partial \text{vec}(Y)&lt;em&gt;1}{\partial \text{vec}(X)&lt;/em&gt;{a_1 a_2 \cdots a_n}} \[6pt]
\frac{\partial \text{vec}(Y)_2}{\partial \text{vec}(X)_1} &amp;amp; \frac{\partial \text{vec}(Y)&lt;em&gt;2}{\partial \text{vec}(X)&lt;em&gt;2} &amp;amp; \cdots &amp;amp; \frac{\partial \text{vec}(Y)&lt;em&gt;2}{\partial \text{vec}(X)&lt;/em&gt;{a_1 a_2 \cdots a_n}} \[6pt]
\vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \[6pt]
\frac{\partial \text{vec}(Y)&lt;/em&gt;{b_1 b_2 \cdots b_m}}{\partial \text{vec}(X)&lt;em&gt;1} &amp;amp; \frac{\partial \text{vec}(Y)&lt;/em&gt;{b_1 b_2 \cdots b_m}}{\partial \text{vec}(X)&lt;em&gt;2} &amp;amp; \cdots &amp;amp; \frac{\partial \text{vec}(Y)&lt;/em&gt;{b_1 b_2 \cdots b_m}}{\partial \text{vec}(X)&lt;/em&gt;{a_1 a_2 \cdots a_n}}
\end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;其中 $J_Y(X) \in \mathbb{R}^{(b_1 b_2 \cdots b_m)\times (a_1 a_2 \cdots a_n)}$。&lt;/p&gt;
&lt;h2&gt;总微分表达式&lt;/h2&gt;
&lt;p&gt;根据总微分定理，当张量 $X$ 发生微小变化 $dX$ 时，张量 $Y$ 的变化为：&lt;/p&gt;
&lt;p&gt;$$
d\text{vec}(Y) = J_Y(X) \cdot d\text{vec}(X)
$$&lt;/p&gt;
&lt;p&gt;重新整形回张量形式：&lt;/p&gt;
&lt;p&gt;$$
dY = \text{reshape}\left(J_Y(X) \cdot \text{vec}(dX)\right)
$$&lt;/p&gt;
&lt;p&gt;换句话说，高阶张量的导数既可以表达成 $(m+n)$ 阶张量的形式，也可以表达成一个二阶展开矩阵的形式。这两种形式的表达都是很自然的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;二阶矩阵的自然在于行和列分别对应因变量和自变量&lt;/li&gt;
&lt;li&gt;$(m+n)$ 阶张量的自然则是在于本质上的自然排布所形成的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两种表达形式是等价的, $(m+n)$ 阶张量形式与 Jacobian 矩阵形式的关系为&lt;/p&gt;
&lt;p&gt;$$
J_Y(X) = \text{reshape}\left(\frac{\partial Y}{\partial X}\right)
$$&lt;/p&gt;
&lt;p&gt;其中 reshape 操作将 $(m+n)$ 阶张量重新排列为矩阵形式。&lt;/p&gt;
</content:encoded></item><item><title>强化学习 Chapter 7 - 强化学习与多智能体系统</title><link>https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter7/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter7/</guid><pubDate>Fri, 13 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;7.1 零和博弈&lt;/h1&gt;
&lt;p&gt;零和博弈是博弈论中的一个重要概念，也是多智能体系统中的基础理论。在零和博弈中，参与者的利益完全对立，一方的收益正好等于其他方的损失总和，因此所有参与者的收益与损失的总和为零。&lt;/p&gt;
&lt;h2&gt;7.1.1 零和博弈的特点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;利益冲突&lt;/strong&gt;：参与者之间存在完全的利益冲突，无合作可能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;总和为零&lt;/strong&gt;：所有参与者的收益之和必须等于零&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保守策略&lt;/strong&gt;：参与者倾向于采用最小化最大损失的策略&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;确定性&lt;/strong&gt;：在完美信息的情况下，理论上可以找到最优策略&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;零和博弈的典型例子包括棋类游戏（如国际象棋、围棋）、扑克牌游戏等。在这些博弈中，一方的胜利必然意味着另一方的失败，双方的目标是最大化自己的收益，同时最小化对手的收益。&lt;/p&gt;
&lt;h2&gt;7.1.2 双人零和博弈&lt;/h2&gt;
&lt;p&gt;双人零和博弈是指有两个玩家的博弈，其中一方的收益等于另一方的损失，即双方的收益总和为零。&lt;/p&gt;
&lt;h3&gt;基本概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;玩家 (Players)&lt;/strong&gt;: 博弈中有两个玩家，通常称为玩家1和玩家2&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略集 (Strategy Sets)&lt;/strong&gt;: 每个玩家有一组可供选择的策略。玩家1的策略集记为 $S_1$，玩家2的策略集记为 $S_2$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;收益 (Payoffs)&lt;/strong&gt;: 对于每一对策略组合 $(s_1, s_2)$，其中 $s_1 \in S_1, s_2 \in S_2$，玩家1获得收益 $u_1(s_1, s_2)$，玩家2获得收益 $u_2(s_1, s_2)$。在零和博弈中， $u_1(s_1, s_2) + u_2(s_1, s_2) = 0$，因此只需关注其中一个玩家的收益&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;收益矩阵 (Payoff Matrix)&lt;/strong&gt;: 假设玩家1有 $m$ 个纯策略，玩家2有 $n$ 个纯策略，则玩家1的收益可以用一个 $m \times n$ 的矩阵 $A$ 表示，其中 $a_{ij}$ 表示当玩家1选择策略 $i$ 而玩家2选择策略 $j$ 时玩家1的收益&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;混合策略 (Mixed Strategy)&lt;/strong&gt;: 玩家以特定概率分布选择纯策略的方案&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;策略函数表示&lt;/h3&gt;
&lt;p&gt;在博弈论中，我们使用 $\pi(a|s)$ 来表示策略函数，它表示在状态 $s$ 下选择动作 $a$ 的概率。需要注意的是，符号 $\pi$ 在不同上下文中有不同的含义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;作为概率分布函数&lt;/strong&gt;：$\pi(a|s)$ 表示在给定状态 $s$ 下选择特定动作 $a$ 的概率值&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作为策略向量&lt;/strong&gt;：$\pi$ 本身表示整个动作概率向量或策略，包含了所有可能动作的概率分布&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如，当玩家1采用混合策略 $\pi_1$（一个 $m$ 维概率向量），玩家2采用混合策略 $\pi_2$（一个 $n$ 维概率向量）时，玩家1的期望收益为：&lt;/p&gt;
&lt;p&gt;$$
u(\pi_1, \pi_2) = \sum_{i=1}^{m}\sum_{j=1}^{n}\pi_1(a_i|s) a_{ij} \pi_2(a_j|s) = \pi_1^T A \pi_2
$$
这里, $\pi_1, \pi_2$ 既可以指动作概率分布本身，也可以指全部可选动作对应的概率值的向量，公式中的含义是后者。&lt;/p&gt;
&lt;h2&gt;7.1.3 纳什均衡&lt;/h2&gt;
&lt;p&gt;纳什均衡是博弈论中的核心概念，由数学家约翰·纳什（John Nash）提出。在纳什均衡状态下，每个参与者都采用了最优策略，且在其他参与者策略不变的情况下，没有任何参与者能够通过单方面改变策略来获得更高的收益。&lt;/p&gt;
&lt;h3&gt;纳什均衡的特点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;稳定性&lt;/strong&gt;：一旦达到纳什均衡，任何一方单独改变策略都不会获益&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;互相制约&lt;/strong&gt;：每个参与者的策略是对其他参与者当前策略的最佳应对&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非唯一性&lt;/strong&gt;：一个博弈可能存在多个纳什均衡&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非最优性&lt;/strong&gt;：纳什均衡不一定是帕累托最优的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在双人零和博弈中，纳什均衡与最小最大策略紧密相关。当双方都采用最小最大策略时，他们正好处于纳什均衡状态。&lt;/p&gt;
&lt;h3&gt;纳什均衡的数学表示&lt;/h3&gt;
&lt;p&gt;对于双人零和博弈，如果策略对 $(\pi_1^&lt;em&gt;, \pi_2^&lt;/em&gt;)$ 满足：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对所有的 $\pi_1$，$(\pi_1^&lt;em&gt;)^\top A \pi_2^&lt;/em&gt; \geq \pi_1^T A \pi_2^*$&lt;/li&gt;
&lt;li&gt;对所有的 $\pi_2$，$(\pi_1^&lt;em&gt;)^\top A \pi_2^&lt;/em&gt; \leq (\pi_1^*)^T A \pi_2$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;则 $(\pi_1^&lt;em&gt;, \pi_2^&lt;/em&gt;)$ 是一个纳什均衡。&lt;/p&gt;
&lt;p&gt;这里 $\pi_1$ 和 $\pi_2$ 分别是玩家1和玩家2的混合策略向量，$A$ 是玩家1的收益矩阵。&lt;/p&gt;
&lt;h3&gt;最小最大定理 (Minimax Theorem)&lt;/h3&gt;
&lt;p&gt;对于有限的双人零和博弈，有
$$
\max_{\pi_1} \min_{\pi_2} \pi_1^\top A \pi_2 = \min_{\pi_2} \max_{\pi_1} \pi_1^\top A \pi_2 = v
$$
其中 $v$ 称为博弈的&lt;strong&gt;值 (value)&lt;/strong&gt;。存在最优混合策略向量 $\pi_1^&lt;em&gt;$ 和 $\pi_2^&lt;/em&gt;$，使得：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于玩家1：选择 $\pi_1^*$ 可以保证收益至少为 $v$，无论玩家2如何选择&lt;/li&gt;
&lt;li&gt;对于玩家2：选择 $\pi_2^*$ 可以保证玩家1的收益不超过 $v$，即自己的损失不超过 $v$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样的策略对 $(\pi_1^&lt;em&gt;, \pi_2^&lt;/em&gt;)$ 构成博弈的&lt;strong&gt;纳什均衡 (Nash Equilibrium)&lt;/strong&gt;，是双方的最佳应对策略。&lt;/p&gt;
&lt;h1&gt;7.2 Markov 博弈&lt;/h1&gt;
&lt;p&gt;Markov博弈，也称为随机博弈（Stochastic Games），是对零和博弈的一个重要扩展，它将马尔可夫决策过程（MDP）的思想引入到多智能体博弈环境中。与静态的零和博弈不同，Markov博弈考虑了时间序列和状态转移的动态特性。&lt;/p&gt;
&lt;h2&gt;7.2.1 Markov 博弈的一般定义&lt;/h2&gt;
&lt;p&gt;一般的多人Markov博弈可以用一个元组 $(N, S, {A_i}&lt;em&gt;{i \in N}, P, {R_i}&lt;/em&gt;{i \in N})$ 来描述：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;玩家集 $N$&lt;/strong&gt;：博弈中的所有玩家，$|N| = n$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态集 $S$&lt;/strong&gt;：博弈可能处于的所有状态的集合&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动作集 ${A_i}_{i \in N}$&lt;/strong&gt;：每个玩家 $i$ 在每个状态下的可选动作集合&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;转移概率 $P$&lt;/strong&gt;：$P(s&apos;|s, \mathbf{a})$ 表示在状态 $s$ 下，当所有玩家选择联合动作 $\mathbf{a} = (a_1, ..., a_n)$ 时，转移到状态 $s&apos;$ 的概率&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;奖励函数 ${R_i}_{i \in N}$&lt;/strong&gt;：$R_i(s, \mathbf{a})$ 表示在状态 $s$ 下执行联合动作 $\mathbf{a}$ 时玩家 $i$ 获得的即时奖励&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7.2.2 双人Markov博弈&lt;/h2&gt;
&lt;p&gt;双人Markov博弈是Markov博弈的一个重要特例，其中只有两个玩家参与。它可以用一个五元组 $(S, A_1, A_2, P, R_1, R_2)$ 来描述：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;状态集 $S$&lt;/strong&gt;：博弈可能处于的所有状态的集合&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动作集 $A_1, A_2$&lt;/strong&gt;：玩家1和玩家2在每个状态下的可选动作集合&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;转移概率 $P$&lt;/strong&gt;：$P(s&apos;|s, a_1, a_2)$ 表示在状态 $s$ 下，当玩家1选择动作 $a_1$、玩家2选择动作 $a_2$ 时，转移到状态 $s&apos;$ 的概率&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;奖励函数 $R_1, R_2$&lt;/strong&gt;：$R_i(s, a_1, a_2)$ 表示在状态 $s$ 下执行联合动作 $(a_1, a_2)$ 时玩家 $i$ 获得的即时奖励&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;双人零和Markov博弈&lt;/h3&gt;
&lt;p&gt;当双人Markov博弈满足零和条件，即 $R_1(s, a_1, a_2) + R_2(s, a_1, a_2) = 0$ 对所有状态和动作组合都成立时，就形成了&lt;strong&gt;双人零和Markov博弈&lt;/strong&gt;。在这种情况下，只需要考虑一个玩家的奖励函数即可。&lt;/p&gt;
&lt;h2&gt;7.2.3 Markov博弈的特点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动态性&lt;/strong&gt;：博弈在时间序列上展开，每个时刻的决策都会影响未来的状态&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态依赖&lt;/strong&gt;：玩家的策略不仅依赖于当前的博弈情况，还要考虑历史状态和未来可能的状态转移&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;马尔可夫性&lt;/strong&gt;：下一状态只依赖于当前状态和当前动作，与历史无关&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长期收益&lt;/strong&gt;：玩家需要考虑折扣累积奖励，而非仅仅关注即时收益&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在Markov博弈中，每个玩家的策略是一个映射 $\pi_i: S \times A_i \rightarrow [0,1]$，其中 $\pi_i(a_i|s)$ 表示在状态 $s$ 下选择动作 $a_i$ 的概率。玩家 $i$ 的目标是最大化期望累积折扣奖励&lt;/p&gt;
&lt;p&gt;$$
V_i^{\pi}(s) = \mathbb{E}\left[\sum_{t=0}^{\infty}\gamma^t R_i(s_t, \mathbf{a}_t) \mid s_0 = s\right]
$$&lt;/p&gt;
&lt;p&gt;其中 $\gamma \in [0,1)$ 是折扣因子，$\mathbf{a}_t$ 是第 $t$ 时刻的联合动作。&lt;/p&gt;
&lt;h2&gt;7.2.4 与经典博弈论的联系&lt;/h2&gt;
&lt;p&gt;Markov博弈是经典博弈论在动态环境下的自然扩展。当状态集只包含一个状态时，Markov博弈就退化为经典的静态博弈。同时，纳什均衡的概念也可以扩展到Markov博弈中，形成&lt;strong&gt;马尔可夫完美纳什均衡&lt;/strong&gt;的概念，要求在每个状态下，玩家的策略都构成该状态子博弈的纳什均衡。&lt;/p&gt;
&lt;h1&gt;7.3 Minimax Q-Learning&lt;/h1&gt;
&lt;p&gt;Minimax Q-Learning是专门为双人零和Markov博弈设计的强化学习算法，它将经典的Q-Learning扩展到多智能体对抗环境中。&lt;/p&gt;
&lt;h2&gt;7.3.1 算法作用与目标&lt;/h2&gt;
&lt;h3&gt;求解问题&lt;/h3&gt;
&lt;p&gt;Minimax Q-Learning主要用于求解&lt;strong&gt;双人零和Markov博弈&lt;/strong&gt;问题，即&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两个智能体在动态环境中进行对抗&lt;/li&gt;
&lt;li&gt;一方的收益等于另一方的损失（零和性质）&lt;/li&gt;
&lt;li&gt;环境状态按马尔可夫性质转移&lt;/li&gt;
&lt;li&gt;智能体需要在不完全信息下学习最优策略&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;算法目标&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;学习最优Q函数&lt;/strong&gt;：通过与环境交互，学习状态-动作对的价值&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无模型学习&lt;/strong&gt;：无需预先知道环境的转移概率和奖励函数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以证明，算法得到的最优策略最终会收敛到Markov完美纳什均衡策略&lt;/p&gt;
&lt;h2&gt;7.3.2 算法核心思想&lt;/h2&gt;
&lt;p&gt;Minimax Q-Learning的基本思想基于以下几个关键原理&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态分解&lt;/strong&gt;：将整个Markov博弈分解为各个状态上的独立子博弈&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最小最大原则&lt;/strong&gt;：在每个状态下应用最小最大定理求解局部最优策略&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时序差分学习&lt;/strong&gt;：结合当前奖励和未来状态价值来更新Q函数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略提取&lt;/strong&gt;：从学习到的Q函数中通过线性规划提取最优混合策略&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;7.3.3 数学公式&lt;/h2&gt;
&lt;h3&gt;Q函数更新规则&lt;/h3&gt;
&lt;p&gt;$$
Q(s, a_1, a_2) \leftarrow Q(s, a_1, a_2) + \alpha [\mathcal{R}(s, a_1, a_2) + \gamma V(s&apos;) - Q(s, a_1, a_2)]
$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\alpha \in (0,1]$ 是学习率&lt;/li&gt;
&lt;li&gt;$\gamma \in [0,1)$ 是折扣因子&lt;/li&gt;
&lt;li&gt;$s&apos;$ 是执行动作 $(a_1, a_2)$ 后的下一状态&lt;/li&gt;
&lt;li&gt;$\mathcal{R}(s, a_1, a_2)$ 是奖励函数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;状态值函数计算及其推导&lt;/h3&gt;
&lt;p&gt;状态值函数的计算是Minimax Q-Learning的核心，它基于最小最大定理在每个状态上求解局部最优解。&lt;/p&gt;
&lt;h4&gt;推导过程&lt;/h4&gt;
&lt;p&gt;在双人零和Markov博弈中，玩家1希望最大化自己的收益，而玩家2希望最小化玩家1的收益。根据博弈论中的最小最大定理，玩家1的最优策略应该是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;玩家1的目标&lt;/strong&gt;：选择策略 $\pi_1(a_1|s)$ 使得在最坏情况下（即玩家2采用最优反制策略）仍能获得最大收益&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;玩家2的目标&lt;/strong&gt;：针对玩家1的任何策略，选择能最小化玩家1收益的动作&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这可以表示为一个嵌套的优化问题
$$
V(s) = \max_{\pi_1} \min_{a_2 \in A_2} \sum_{a_1 \in A_1} \pi_1(a_1|s) Q(s, a_1, a_2)
$$&lt;/p&gt;
&lt;h4&gt;计算公式&lt;/h4&gt;
&lt;p&gt;状态值通过求解最小最大问题得到&lt;/p&gt;
&lt;p&gt;$$
V(s) = \max_{\pi_1} \min_{a_2} \sum_{a_1} \pi_1(a_1|s) Q(s, a_1, a_2)
$$&lt;/p&gt;
&lt;p&gt;等价的线性规划形式&lt;/p&gt;
&lt;p&gt;$$
\begin{align}
\max \quad &amp;amp; v \
\text{s.t.} \quad &amp;amp; \sum_{a_1} \pi_1(a_1|s) Q(s, a_1, a_2) \geq v, \quad \forall a_2 \
&amp;amp; \sum_{a_1} \pi_1(a_1|s) = 1 \
&amp;amp; \pi_1(a_1|s) \geq 0, \quad \forall a_1
\end{align}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;公式含义&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一个约束确保无论玩家2选择什么动作 $a_2$，玩家1的期望收益都至少为 $v$&lt;/li&gt;
&lt;li&gt;第二个约束确保 $\pi_1$ 是有效的概率分布&lt;/li&gt;
&lt;li&gt;第三个约束确保概率非负&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;理论保证：收敛到纳什均衡&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;收敛性定理&lt;/strong&gt;：在适当条件下（如所有状态-动作对被无限次访问），Minimax Q-Learning收敛到唯一的最优Q函数 $Q^&lt;em&gt;$，对应的策略对 $(\pi_1^&lt;/em&gt;, \pi_2^*)$ 构成该双人零和Markov博弈的&lt;strong&gt;马尔可夫完美纳什均衡&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这意味着：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;玩家1采用 $\pi_1^&lt;em&gt;$ 可以保证在最坏情况下获得博弈值 $V^&lt;/em&gt;(s)$&lt;/li&gt;
&lt;li&gt;玩家2采用 $\pi_2^&lt;em&gt;$ 可以确保玩家1的收益不超过 $V^&lt;/em&gt;(s)$&lt;/li&gt;
&lt;li&gt;任何一方单方面偏离均衡策略都不会获益&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;策略提取&lt;/h3&gt;
&lt;p&gt;从最优Q函数提取纳什均衡策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;玩家1的策略&lt;/strong&gt;：上述线性规划的最优解 $\pi_1^*(a_1|s)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;玩家2的策略&lt;/strong&gt;：$\pi_2^&lt;em&gt;(a_2|s) = \arg\min_{a_2} \max_{a_1} Q^&lt;/em&gt;(s, a_1, a_2)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;迭代收敛后，得到的 $Q^*$ 对应的策略对正是该零和博弈的马尔可夫完美&lt;strong&gt;纳什均衡&lt;/strong&gt;（Nash Equilibrium），需要注意的是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于零和博弈，最小最大值 $V^*(s)$ 唯一，因此价值函数唯一，策略对可能有多个等价解。&lt;/li&gt;
&lt;li&gt;如果博弈不是零和，对抗结构被打破，Minimax Q-学习不再保证收敛到纳什均衡；这时可改用如 Nash Q-learning、Correlated Q-learning 等方法，或考虑其他解概念（如相关均衡）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7.3.4 算法伪代码&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Minimax Q-Learning&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;环境 $env$，最大回合数 $M$，学习率 $\alpha$，折扣因子 $\gamma$，探索率 $\varepsilon$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;学习到的 Q-table $Q(s,a,b)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化 $Q(s,a,b)=0$ 对于所有状态 $s$ 和动作对 $(a,b)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;For&lt;/strong&gt; $\text{episode} = 1, \dots, M$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;   $s \leftarrow env.reset(),\ \text{done}\leftarrow\text{False}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;while&lt;/strong&gt; $\lnot\text{done}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;     以概率 $\varepsilon$ 随机选取 $a$，否则  $a \leftarrow \arg\max_{a}\min_{b}Q(s,a,b)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;     对手策略获得 $b$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;     执行 $(a,b)$，得到  $(s&apos;,r,\text{done})\leftarrow env.step(a,b)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;     计算  $V = \begin{cases} 0, &amp;amp; \text{if done}\ \max_{a&apos;}\min_{b&apos;}Q(s&apos;,a&apos;,b&apos;), &amp;amp; \text{otherwise} \end{cases}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;     更新  $Q(s,a,b)\leftarrow Q(s,a,b) + \alpha\bigl[r + \gamma V - Q(s,a,b)\bigr]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;     $s \leftarrow s&apos;$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;返回学习到的 Q-table $Q(s,a,b)$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;7.4 Nash Q-Learning&lt;/h1&gt;
&lt;p&gt;Nash Q-Learning是Minimax Q-Learning在一般和博弈（非零和博弈）中的推广，专门用于求解多智能体博弈中的纳什均衡策略。与Minimax Q-Learning不同，Nash Q-Learning不假设博弈是零和的，因此需要考虑所有参与者的收益函数。&lt;/p&gt;
&lt;h2&gt;7.4.1 算法背景与动机&lt;/h2&gt;
&lt;h3&gt;从零和到一般和博弈&lt;/h3&gt;
&lt;p&gt;在现实世界的多智能体系统中，智能体之间的关系往往不是完全对抗的零和关系，而是存在合作、竞争或混合的复杂关系。例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;交通控制&lt;/strong&gt;：不同路口的信号灯控制系统需要协调，既要考虑自身路口的效率，也要考虑整体交通流量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源分配&lt;/strong&gt;：多个用户共享网络带宽时，既存在竞争也可能通过协调获得更好的整体性能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;经济市场&lt;/strong&gt;：企业间既有竞争关系，也可能通过某种合作实现共赢&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;一般和博弈的特点&lt;/h3&gt;
&lt;p&gt;一般和博弈具有以下特征：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;非零和性&lt;/strong&gt;：所有参与者的收益总和不必为零，存在共赢或共输的可能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略耦合&lt;/strong&gt;：每个参与者的最优策略依赖于其他所有参与者的策略选择&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多重均衡&lt;/strong&gt;：通常存在多个纳什均衡，需要选择合适的均衡概念&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协调问题&lt;/strong&gt;：参与者需要在多个均衡中进行协调选择&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;基本设定&lt;/h3&gt;
&lt;p&gt;考虑 $n$ 个智能体的一般和Markov博弈，用元组 $(S, {A_i}&lt;em&gt;{i=1}^n, P, {R_i}&lt;/em&gt;{i=1}^n)$ 描述：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$S$：状态空间&lt;/li&gt;
&lt;li&gt;$A_i$：智能体 $i$ 的动作空间&lt;/li&gt;
&lt;li&gt;$P(s&apos;|s,\mathbf{a})$：状态转移概率&lt;/li&gt;
&lt;li&gt;$R_i(s,\mathbf{a})$：智能体 $i$ 的奖励函数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中 $\mathbf{a} = (a_1, a_2, ..., a_n)$ 是联合动作。&lt;/p&gt;
&lt;h2&gt;7.4.2 Nash Q-Learning算法框架&lt;/h2&gt;
&lt;h3&gt;Q函数定义&lt;/h3&gt;
&lt;p&gt;每个智能体 $i$ 维护自己的Q函数：
$$
Q_i(s, \mathbf{a}) = \mathbb{E}\left[\sum_{t=0}^{\infty}\gamma^t R_i(s_t, \mathbf{a}_t) \mid s_0 = s, \mathbf{a}_0 = \mathbf{a}\right]
$$&lt;/p&gt;
&lt;p&gt;表示在状态 $s$ 下执行联合动作 $\mathbf{a}$，然后所有智能体都遵循纳什均衡策略时，智能体 $i$ 的期望累积折扣奖励。&lt;/p&gt;
&lt;h3&gt;纳什均衡计算&lt;/h3&gt;
&lt;p&gt;在每个状态 $s$ 下，通过求解以下纳什均衡问题来计算状态值：&lt;/p&gt;
&lt;p&gt;对于每个智能体 $i$，其策略 $\pi_i^&lt;em&gt;(a_i|s)$ 应满足：
$$
\sum_{a_i} \pi_i^&lt;/em&gt;(a_i|s) Q_i(s, a_i, \mathbf{a}&lt;em&gt;{-i}) \geq \sum&lt;/em&gt;{a_i} \pi_i(a_i|s) Q_i(s, a_i, \mathbf{a}_{-i})
$$&lt;/p&gt;
&lt;p&gt;对于所有可能的策略 $\pi_i$ 和其他智能体的均衡策略组合 $\mathbf{a}_{-i}$。&lt;/p&gt;
&lt;h3&gt;状态值函数&lt;/h3&gt;
&lt;p&gt;智能体 $i$ 在状态 $s$ 的纳什均衡值为：
$$
V_i(s) = \sum_{\mathbf{a}} \left(\prod_{j=1}^n \pi_j^*(a_j|s)\right) Q_i(s, \mathbf{a})
$$&lt;/p&gt;
&lt;p&gt;其中 $\pi_j^*(a_j|s)$ 是纳什均衡策略。&lt;/p&gt;
&lt;h2&gt;7.4.3 Q函数更新规则&lt;/h2&gt;
&lt;p&gt;Nash Q-Learning的更新规则为：
$$
Q_i(s, \mathbf{a}) \leftarrow Q_i(s, \mathbf{a}) + \alpha \left[R_i(s, \mathbf{a}) + \gamma V_i(s&apos;) - Q_i(s, \mathbf{a})\right]
$$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\alpha$ 是学习率&lt;/li&gt;
&lt;li&gt;$\gamma$ 是折扣因子&lt;/li&gt;
&lt;li&gt;$s&apos;$ 是执行动作 $\mathbf{a}$ 后的下一状态&lt;/li&gt;
&lt;li&gt;$V_i(s&apos;)$ 通过求解 $s&apos;$ 状态下的纳什均衡获得&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7.4.4 算法挑战与限制&lt;/h2&gt;
&lt;h3&gt;计算复杂性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;纳什均衡求解&lt;/strong&gt;：在每次更新中都需要求解纳什均衡，这在一般情况下是NP-hard问题&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;维度诅咒&lt;/strong&gt;：联合动作空间随智能体数量指数增长，使得Q函数维度过高&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多重均衡&lt;/strong&gt;：可能存在多个纳什均衡，算法需要选择其中一个&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;收敛性问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;收敛保证有限&lt;/strong&gt;：与Minimax Q-Learning不同，Nash Q-Learning的收敛性保证较弱&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;均衡选择&lt;/strong&gt;：当存在多个纳什均衡时，算法可能收敛到不同的均衡或在均衡间振荡&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;探索-利用平衡&lt;/strong&gt;：需要更复杂的探索策略来确保充分学习&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;实际应用限制&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可扩展性差&lt;/strong&gt;：难以扩展到大量智能体的场景&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算资源需求高&lt;/strong&gt;：每步都需要求解优化问题，计算开销大&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实时性要求&lt;/strong&gt;：在需要快速决策的环境中可能不适用&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7.4.5 改进方法与变种&lt;/h2&gt;
&lt;p&gt;为了解决Nash Q-Learning的局限性，研究者提出了多种改进方法：&lt;/p&gt;
&lt;h3&gt;近似求解方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;函数逼近&lt;/strong&gt;：使用神经网络等方法近似Q函数，降低维度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;采样方法&lt;/strong&gt;：通过采样减少纳什均衡计算的复杂度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;启发式算法&lt;/strong&gt;：使用遗传算法、粒子群优化等元启发式方法近似求解&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;替代均衡概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Correlated Q-Learning&lt;/strong&gt;：使用相关均衡代替纳什均衡，计算更简单&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mean Field Games&lt;/strong&gt;：在大量智能体场景中使用平均场理论&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stackelberg均衡&lt;/strong&gt;：考虑智能体间的层次关系&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nash Q-Learning为理解多智能体强化学习中的策略交互提供了重要的理论基础，尽管在实际应用中面临诸多挑战，但它为后续更实用的多智能体学习算法发展奠定了基础。&lt;/p&gt;
</content:encoded></item><item><title>MuJoCo 基本介绍</title><link>https://adalovelemon.github.io/posts/content/coursenotes/robotics/mujoco/mujoco/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/robotics/mujoco/mujoco/</guid><pubDate>Fri, 13 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;MuJoCo介绍&lt;/p&gt;
&lt;h1&gt;概述&lt;/h1&gt;
&lt;p&gt;MuJoCo 是一个物理引擎，最初是由 Emo Todorov 开发的，后来被 DeepMind 收购。MuJoCo 的全称是 Multi-Joint dynamics with Contact，是一个用于模拟多关节系统和接触力学的物理引擎。MuJoCo 的设计目标是提供高效、准确的动力学模拟，特别适用于机器人学、计算机图形学和生物力学等领域。MuJoCo 的核心特点包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;高效的动力学计算&lt;/strong&gt;：MuJoCo 使用了一种基于约束的动力学计算方法，可以高效地处理多关节系统和接触力学。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;准确的接触模拟&lt;/strong&gt;：MuJoCo 提供了精确的接触模型，可以模拟刚体之间的接触和摩擦力学。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;灵活的建模语言&lt;/strong&gt;：MuJoCo 提供了一种灵活的建模语言，可以方便地定义多关节系统的几何形状、质量分布和动力学参数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持多种求解器&lt;/strong&gt;：MuJoCo 支持多种求解器，包括刚体动力学求解器和约束动力学求解器，可以根据需要选择合适的求解器。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;使用&lt;/h1&gt;
&lt;p&gt;官方文档：&lt;a href=&quot;https://mujoco.readthedocs.io/en/latest/index.html&quot;&gt;MuJoCo Documentation&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;模型设置&lt;/h2&gt;
&lt;p&gt;模型文件通常是一个 XML 文件，包含了模型的几何形状、质量分布、关节类型、接触参数等信息。MuJoCo 提供了一种灵活的建模语言，可以方便地定义多关节系统的几何形状、质量分布和动力学参数。模型文件以 &lt;code&gt;.xml&lt;/code&gt; 为后缀名。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;mujoco model=&quot;jumping_cube&quot;&amp;gt;
  &amp;lt;option timestep=&quot;0.01&quot;/&amp;gt;     &amp;lt;!-- timestep 是仿真时间步长，因为在计算机中仿真使用的是离散代替连续，于是就使用 0.01s 来作为更新的最小时间--&amp;gt;

  &amp;lt;asset&amp;gt;
    &amp;lt;texture name=&quot;grid&quot; type=&quot;2d&quot; builtin=&quot;checker&quot; width=&quot;512&quot; height=&quot;512&quot; rgb1=&quot;.1 .2 .3&quot; rgb2=&quot;.2 .3 .4&quot;/&amp;gt;
    &amp;lt;material name=&quot;grid&quot; texture=&quot;grid&quot; texrepeat=&quot;1 1&quot; texuniform=&quot;true&quot; reflectance=&quot;.2&quot;/&amp;gt;
    &amp;lt;material name=&quot;cube&quot; rgba=&quot;1 0.2 0.2 1&quot;/&amp;gt;
  &amp;lt;/asset&amp;gt;

  &amp;lt;default&amp;gt;
    &amp;lt;!-- 设置默认接触参数，增加弹性使跳跃更明显 --&amp;gt;
    &amp;lt;geom condim=&quot;3&quot; friction=&quot;0.8 0.1 0.1&quot; solimp=&quot;0.95 0.99 0.001&quot; solref=&quot;0.01 1&quot;/&amp;gt;
  &amp;lt;/default&amp;gt;

  &amp;lt;worldbody&amp;gt;
    &amp;lt;light diffuse=&quot;.8 .8 .8&quot; pos=&quot;0 0 3&quot; dir=&quot;0 0 -1&quot;/&amp;gt;
    &amp;lt;!-- 地板，使用平面类型更合适，平面类型是无限延伸的 --&amp;gt;
    &amp;lt;geom name=&quot;floor&quot; type=&quot;plane&quot; material=&quot;grid&quot; size=&quot;10 10 0.1&quot;/&amp;gt;
    
    &amp;lt;!-- 正方体主体 --&amp;gt;
    &amp;lt;body name=&quot;cube&quot; pos=&quot;0 0 0.5&quot;&amp;gt;
      &amp;lt;!-- 自由关节使正方体可以在空间中移动 --&amp;gt;
      &amp;lt;freejoint name=&quot;cube_joint&quot;/&amp;gt;
      &amp;lt;!-- 正方体几何体 --&amp;gt;
      &amp;lt;geom name=&quot;cube_geom&quot; type=&quot;box&quot; size=&quot;0.1 0.1 0.1&quot; material=&quot;cube&quot; mass=&quot;2&quot;/&amp;gt;

      &amp;lt;!-- 在正方体的底部添加四个用于跳跃的隐藏几何体 --&amp;gt;
      &amp;lt;geom name=&quot;jump_x_pos&quot; type=&quot;sphere&quot; size=&quot;0.02&quot; pos=&quot;0.1 0 -0.1&quot; rgba=&quot;0 1 0 0.3&quot;/&amp;gt;
      &amp;lt;geom name=&quot;jump_x_neg&quot; type=&quot;sphere&quot; size=&quot;0.02&quot; pos=&quot;-0.1 0 -0.1&quot; rgba=&quot;0 1 0 0.3&quot;/&amp;gt;
      &amp;lt;geom name=&quot;jump_y_pos&quot; type=&quot;sphere&quot; size=&quot;0.02&quot; pos=&quot;0 0.1 -0.1&quot; rgba=&quot;0 1 0 0.3&quot;/&amp;gt;
      &amp;lt;geom name=&quot;jump_y_neg&quot; type=&quot;sphere&quot; size=&quot;0.02&quot; pos=&quot;0 -0.1 -0.1&quot; rgba=&quot;0 1 0 0.3&quot;/&amp;gt;
      
      &amp;lt;!-- 添加一个用于上跳的几何体 --&amp;gt;
      &amp;lt;geom name=&quot;jump_up&quot; type=&quot;sphere&quot; size=&quot;0.02&quot; pos=&quot;0 0 -0.1&quot; rgba=&quot;0 0 1 0.3&quot;/&amp;gt;
      
      &amp;lt;!-- 添加站点作为效应器的作用点，放在cube内部 --&amp;gt;
      &amp;lt;site name=&quot;jump_x_pos&quot; pos=&quot;0.1 0 -0.1&quot; size=&quot;0.01&quot;/&amp;gt;
      &amp;lt;site name=&quot;jump_x_neg&quot; pos=&quot;-0.1 0 -0.1&quot; size=&quot;0.01&quot;/&amp;gt;
      &amp;lt;site name=&quot;jump_y_pos&quot; pos=&quot;0 0.1 -0.1&quot; size=&quot;0.01&quot;/&amp;gt;
      &amp;lt;site name=&quot;jump_y_neg&quot; pos=&quot;0 -0.1 -0.1&quot; size=&quot;0.01&quot;/&amp;gt;
      &amp;lt;site name=&quot;jump_up&quot; pos=&quot;0 0 -0.1&quot; size=&quot;0.01&quot;/&amp;gt;
    &amp;lt;/body&amp;gt;
  &amp;lt;/worldbody&amp;gt;
  
  &amp;lt;!-- 添加效应器，控制跳跃方向和力度 --&amp;gt;
  &amp;lt;actuator&amp;gt;
    &amp;lt;!-- 向各个方向施加力的效应器 --&amp;gt;
    &amp;lt;motor name=&quot;jump_right&quot; site=&quot;jump_x_pos&quot; gear=&quot;0 0 50 0 0 0&quot; ctrllimited=&quot;true&quot; ctrlrange=&quot;0 1&quot;/&amp;gt;
    &amp;lt;motor name=&quot;jump_left&quot; site=&quot;jump_x_neg&quot; gear=&quot;0 0 50 0 0 0&quot; ctrllimited=&quot;true&quot; ctrlrange=&quot;0 1&quot;/&amp;gt;
    &amp;lt;motor name=&quot;jump_forward&quot; site=&quot;jump_y_pos&quot; gear=&quot;0 0 50 0 0 0&quot; ctrllimited=&quot;true&quot; ctrlrange=&quot;0 1&quot;/&amp;gt;
    &amp;lt;motor name=&quot;jump_backward&quot; site=&quot;jump_y_neg&quot; gear=&quot;0 0 50 0 0 0&quot; ctrllimited=&quot;true&quot; ctrlrange=&quot;0 1&quot;/&amp;gt;
    &amp;lt;motor name=&quot;jump_up&quot; site=&quot;jump_up&quot; gear=&quot;0 0 100 0 0 0&quot; ctrllimited=&quot;true&quot; ctrlrange=&quot;0 1&quot;/&amp;gt;
  &amp;lt;/actuator&amp;gt;

  &amp;lt;sensor&amp;gt;
    &amp;lt;!-- 位置传感器 --&amp;gt;
    &amp;lt;framepos name=&quot;cube_pos&quot; objtype=&quot;body&quot; objname=&quot;cube&quot;/&amp;gt;
    &amp;lt;!-- 速度传感器 --&amp;gt;
    &amp;lt;framelinvel name=&quot;cube_vel&quot; objtype=&quot;body&quot; objname=&quot;cube&quot;/&amp;gt;
    &amp;lt;!-- 加速度传感器 --&amp;gt;
    &amp;lt;framelinacc name=&quot;cube_acc&quot; objtype=&quot;body&quot; objname=&quot;cube&quot;/&amp;gt;
  &amp;lt;/sensor&amp;gt;
  
  &amp;lt;!-- 添加关键帧 --&amp;gt;
  &amp;lt;keyframe&amp;gt;
    &amp;lt;!-- 初始状态 --&amp;gt;
    &amp;lt;key name=&quot;init&quot; qpos=&quot;0 0 0.5 1 0 0 0&quot; qvel=&quot;0 0 0 0 0 0&quot;/&amp;gt;
    
    &amp;lt;!-- 向右跳跃状态 --&amp;gt;
    &amp;lt;key name=&quot;jump_right&quot; qpos=&quot;0.5 0 0.7 1 0 0 0&quot; qvel=&quot;1 0 2 0 0 0&quot;/&amp;gt;
    
    &amp;lt;!-- 向左跳跃状态 --&amp;gt;
    &amp;lt;key name=&quot;jump_left&quot; qpos=&quot;-0.5 0 0.7 1 0 0 0&quot; qvel=&quot;-1 0 2 0 0 0&quot;/&amp;gt;
    
    &amp;lt;!-- 向前跳跃状态 --&amp;gt;
    &amp;lt;key name=&quot;jump_forward&quot; qpos=&quot;0 0.5 0.7 1 0 0 0&quot; qvel=&quot;0 1 2 0 0 0&quot;/&amp;gt;
    
    &amp;lt;!-- 向后跳跃状态 --&amp;gt;
    &amp;lt;key name=&quot;jump_backward&quot; qpos=&quot;0 -0.5 0.7 1 0 0 0&quot; qvel=&quot;0 -1 2 0 0 0&quot;/&amp;gt;
  &amp;lt;/keyframe&amp;gt;
&amp;lt;/mujoco&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是实现了一个简单的可跳跃正方体模型，正如模型的属性设置所显示的那样，模型有&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;timestep&lt;/code&gt;：仿真时间步长，单位是秒。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;asset&lt;/code&gt;：模型的材质和纹理设置&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&quot;texture&quot;&lt;/code&gt;：地板的纹理设置，使用了棋盘格 (&lt;code&gt;&quot;checker&quot;&lt;/code&gt;) 纹理，整个棋盘尺寸为 512x512 像素。每个格子的实际大小取决于纹理重复次数 (&lt;code&gt;texrepeat&lt;/code&gt;) 和地板几何体的尺寸。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;material&quot;&lt;/code&gt;：地板的材质设置，使用了 &lt;code&gt;&quot;grid&quot;&lt;/code&gt; 纹理，并设置了反射率 (&lt;code&gt;reflectance&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;cube&quot;&lt;/code&gt;：正方体的材质设置，使用红色 (&lt;code&gt;rgba=&quot;1 0.2 0.2 1&quot;&lt;/code&gt;)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;default&lt;/code&gt;：默认的接触参数设置，包括摩擦系数和接触力学参数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;condim&lt;/code&gt;：接触维度，设置为 3 表示三维接触, 即考虑了 x, y, z 三个方向的接触。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;friction&lt;/code&gt;：摩擦系数，设置为 0.8, 0.1, 0.1，表示在三个方向上的摩擦系数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;solimp&lt;/code&gt;：接触阻抗参数，设置为 0.95, 0.99, 0.001，表示接触刚度的非线性程度。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;solref&lt;/code&gt;：参考接触参数，设置为 0.01, 1，表示弹簧-阻尼系统的时间常数和阻尼比。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;actuator&lt;/code&gt;：模型的效应器设置，包括施加力的方向和大小。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&quot;name&quot;&lt;/code&gt;：效应器的名称，用于标识效应器。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;site&quot;&lt;/code&gt;：效应器的作用点，指定了效应器施加力的点。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;gear&quot;&lt;/code&gt;：效应器的力/力矩系数，设置了效应器施加的力和力矩大小和方向。格式为&lt;code&gt;gear=&quot;x y z a b c&quot;&lt;/code&gt;，其中：
&lt;ul&gt;
&lt;li&gt;前三个值(x y z)是线性力系数，表示在xyz三个轴向施加的力&lt;/li&gt;
&lt;li&gt;后三个值(a b c)是旋转力矩系数，表示围绕xyz轴施加的力矩&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gear=&quot;0 0 50 0 0 0&quot;&lt;/code&gt;：表示只在z轴方向施加50的线性力，没有施加力矩。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gear=&quot;0 0 100 0 0 0&quot;&lt;/code&gt;：表示只在z轴方向施加100的线性力，没有施加力矩。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;ctrllimited&quot;&lt;/code&gt;：是否限制效应器的控制范围，设置为 &lt;code&gt;true&lt;/code&gt; 表示限制。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;ctrlrange&quot;&lt;/code&gt;：效应器的控制信号范围，表示控制信号的合法值范围，例如 &lt;code&gt;ctrlrange=&quot;0 1&quot;&lt;/code&gt; 表示控制信号的范围在 0 到 1 之间。实际施加的力/力矩是控制信号乘以gear系数。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;keyframe&lt;/code&gt;：模型的关键帧设置，包括初始状态和跳跃状态。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;key&lt;/code&gt;：关键帧设置，设置了关键帧的名称 (&lt;code&gt;name&lt;/code&gt;) 和关节位置 (&lt;code&gt;qpos&lt;/code&gt;) 和关节速度 (&lt;code&gt;qvel&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;qpos&lt;/code&gt;：关节位置，对于freejoint包含7个值，例如 &lt;code&gt;qpos=&quot;0 0 0.5 1 0 0 0&quot;&lt;/code&gt; 中：
&lt;ul&gt;
&lt;li&gt;前3个值(0 0 0.5)是xyz位置坐标&lt;/li&gt;
&lt;li&gt;后4个值(1 0 0 0)是四元数表示的方向(旋转状态)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;qvel&lt;/code&gt;：关节速度，对于freejoint包含6个值，例如 &lt;code&gt;qvel=&quot;0 0 0 0 0 0&quot;&lt;/code&gt; 中：
&lt;ul&gt;
&lt;li&gt;前3个值是xyz方向的线速度&lt;/li&gt;
&lt;li&gt;后3个值是围绕xyz轴的角速度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;python接口&lt;/h2&gt;
&lt;p&gt;MuJoCo 提供了 Python 接口，可以方便地与 Python 进行交互。可以使用 &lt;code&gt;mujoco&lt;/code&gt; 模块来加载模型、运行仿真和获取传感器数据。以上面的模型为例，下面是一个简单的 Python 代码示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import mujoco
import mujoco.viewer
import numpy as np
import time
import matplotlib.pyplot as plt
from collections import defaultdict

# 加载模型
model = mujoco.MjModel.from_xml_path(&quot;jumping_cube.xml&quot;)
data = mujoco.MjData(model)

# 获取所有效应器信息
actuator_names = [model.actuator(i).name for i in range(model.nu)]
actuator_ids = np.arange(model.nu)

# 获取所有传感器信息
sensor_names = [model.sensor(i).name for i in range(model.nsensor)]
sensor_ids = {name: i for i, name in enumerate(sensor_names)}

# 简化的控制函数
def apply_control(control_dict):
    &quot;&quot;&quot;通过字典应用控制，键是效应器名称，值是控制信号&quot;&quot;&quot;
    for name, value in control_dict.items():
        if name in actuator_names:
            idx = actuator_names.index(name)
            data.ctrl[idx] = value
        else:
            print(f&quot;警告: 未找到效应器 &apos;{name}&apos;&quot;)

# 获取完整状态函数
def get_full_state():
    &quot;&quot;&quot;获取模型的完整状态，包括关节、传感器和效应器&quot;&quot;&quot;
    state = {
        # 基本状态
        &quot;time&quot;: data.time,
        &quot;qpos&quot;: data.qpos.copy(),  # 所有关节位置
        &quot;qvel&quot;: data.qvel.copy(),  # 所有关节速度
        &quot;qacc&quot;: data.qacc.copy(),  # 所有关节加速度
        
        # 传感器数据
        &quot;sensors&quot;: {name: data.sensor(name) for name in sensor_names},
        
        # 效应器状态
        &quot;ctrl&quot;: data.ctrl.copy(),  # 控制信号
        
        # 位置和方向 (对自由关节)
        &quot;pos&quot;: data.qpos[:3].copy(),            # 位置
        &quot;quat&quot;: data.qpos[3:7].copy() if len(data.qpos) &amp;gt;= 7 else None,  # 方向四元数
        
        # 接触力
        &quot;contact_forces&quot;: np.zeros((data.ncon, 6)),  # 准备接触力数据
    }
    
    # 获取接触力
    for i in range(data.ncon):
        state[&quot;contact_forces&quot;][i] = np.array([
            data.contact[i].dist,     # 接触距离
            data.contact[i].pos[0],   # 接触点X
            data.contact[i].pos[1],   # 接触点Y
            data.contact[i].pos[2],   # 接触点Z
            data.contact[i].frame[0], # 接触法向量X
            data.contact[i].frame[1]  # 接触法向量Y
            # 更多接触数据可以根据需要添加
        ])
    
    return state

# 记录历史数据的类
class StateRecorder:
    def __init__(self, max_steps):
        self.max_steps = max_steps
        self.reset()
    
    def reset(self):
        self.history = defaultdict(list)
        self.step_count = 0
    
    def record(self, state):
        if self.step_count &amp;lt; self.max_steps:
            # 只保存主要的数值数据
            for key, value in state.items():
                if isinstance(value, (np.ndarray, float, int)):
                    self.history[key].append(value)
                elif key == &quot;sensors&quot;:
                    for sensor_name, sensor_value in value.items():
                        self.history[f&quot;sensor_{sensor_name}&quot;].append(sensor_value)
            self.step_count += 1
    
    def get_array(self, key):
        &quot;&quot;&quot;将历史数据转换为numpy数组&quot;&quot;&quot;
        if key in self.history:
            return np.array(self.history[key])
        return None

# 创建记录器和预定义动作
max_steps = 5000
recorder = StateRecorder(max_steps)

# 定义测试动作序列 - 测试每个效应器
def create_test_actions():
    # 为每个效应器创建测试动作 (先激活后停用)
    actions = []
    
    # 对每个效应器单独测试
    for actuator_name in actuator_names:
        # 激活动作 (只设置当前测试的效应器为1，其余为0)
        active = {name: 1.0 if name == actuator_name else 0.0 for name in actuator_names}
        # 停用动作 (全部设为0)
        inactive = {name: 0.0 for name in actuator_names}
        
        # 添加到动作序列
        actions.append((active, f&quot;激活 {actuator_name}&quot;))
        actions.append((inactive, &quot;停用&quot;))
    
    # 添加组合动作 (例如同时激活多个效应器)
    jump_up_right = {name: 0.0 for name in actuator_names}
    jump_up_right[&quot;jump_up&quot;] = 1.0
    jump_up_right[&quot;jump_right&quot;] = 0.5
    actions.append((jump_up_right, &quot;向上右方跳跃&quot;))
    
    # 添加停止动作
    stop = {name: 0.0 for name in actuator_names}
    actions.append((stop, &quot;停止&quot;))
    
    return actions

# 为测试创建动作序列
test_actions = create_test_actions()

def plan(step):
    &quot;&quot;&quot;基于当前步骤返回要执行的动作&quot;&quot;&quot;
    # 每200步执行一个动作
    action_idx = min(step // 200, len(test_actions) - 1)
    return test_actions[action_idx]

# 运行模拟
with mujoco.viewer.launch_passive(model, data) as viewer:
    step = 0
    
    # 重置模拟
    mujoco.mj_resetData(model, data)
    recorder.reset()
    
    print(&quot;开始模拟...&quot;)
    print(f&quot;效应器: {actuator_names}&quot;)
    print(f&quot;传感器: {sensor_names}&quot;)
    
    while step &amp;lt; max_steps:
        # 获取当前动作
        control_dict, action_name = plan(step)
        
        # 应用控制
        apply_control(control_dict)
        
        # 前进模拟一步
        mujoco.mj_step(model, data)
        
        # 获取并记录完整状态
        state = get_full_state()
        recorder.record(state)
        
        # 更新视图和打印状态
        if step % 50 == 0:
            viewer.sync()
            
            # 打印摘要信息
            pos = state[&quot;pos&quot;]
            
            # 显示当前动作和位置
            print(f&quot;步骤 {step}: 动作={action_name}, 位置=[{pos[0]:.2f}, {pos[1]:.2f}, {pos[2]:.2f}]&quot;)
            
            # 如果有传感器，显示传感器数据
            if sensor_names:
                print(&quot;传感器数据:&quot;)
                for name in sensor_names:
                    print(f&quot;  {name}: {state[&apos;sensors&apos;][name]}&quot;)
            
            # 显示当前效应器输出
            print(&quot;效应器输出:&quot;)
            for i, name in enumerate(actuator_names):
                print(f&quot;  {name}: {state[&apos;ctrl&apos;][i]:.2f}&quot;)
            
            print(&quot;----&quot;)
        
        step += 1
        time.sleep(0.01)  # 实时模拟速度
    
    print(&quot;模拟完成!&quot;)

# 可视化记录的数据
def plot_simulation_results():
    &quot;&quot;&quot;绘制模拟结果&quot;&quot;&quot;
    # 创建几个基本图表
    fig, axes = plt.subplots(3, 1, figsize=(12, 15))
    
    # 绘制位置轨迹
    positions = np.array([recorder.history[&quot;pos&quot;][i] for i in range(recorder.step_count)])
    time_steps = np.arange(recorder.step_count) * model.opt.timestep
    
    axes[0].plot(time_steps, positions[:, 0], &apos;r-&apos;, label=&apos;X 位置&apos;)
    axes[0].plot(time_steps, positions[:, 1], &apos;g-&apos;, label=&apos;Y 位置&apos;)
    axes[0].plot(time_steps, positions[:, 2], &apos;b-&apos;, label=&apos;Z 位置&apos;)
    axes[0].set_title(&apos;立方体位置随时间变化&apos;)
    axes[0].set_xlabel(&apos;时间 (秒)&apos;)
    axes[0].set_ylabel(&apos;位置 (m)&apos;)
    axes[0].legend()
    axes[0].grid(True)
    
    # 绘制控制信号
    ctrl_data = np.array([recorder.history[&quot;ctrl&quot;][i] for i in range(recorder.step_count)])
    for i, name in enumerate(actuator_names):
        axes[1].plot(time_steps, ctrl_data[:, i], label=name)
    
    axes[1].set_title(&apos;控制信号随时间变化&apos;)
    axes[1].set_xlabel(&apos;时间 (秒)&apos;)
    axes[1].set_ylabel(&apos;控制信号&apos;)
    axes[1].legend()
    axes[1].grid(True)
    
    # 绘制传感器数据
    sensor_keys = [key for key in recorder.history.keys() if key.startswith(&quot;sensor_&quot;)]
    if sensor_keys:
        for key in sensor_keys:
            sensor_name = key[7:]  # 去掉&quot;sensor_&quot;前缀
            sensor_data = np.array(recorder.history[key])
            
            # 如果传感器数据是数组，只绘制第一个元素
            if len(sensor_data.shape) &amp;gt; 1:
                axes[2].plot(time_steps, sensor_data[:, 0], label=f&quot;{sensor_name}[0]&quot;)
            else:
                axes[2].plot(time_steps, sensor_data, label=sensor_name)
                
        axes[2].set_title(&apos;传感器数据随时间变化&apos;)
        axes[2].set_xlabel(&apos;时间 (秒)&apos;)
        axes[2].set_ylabel(&apos;传感器值&apos;)
        axes[2].legend()
        axes[2].grid(True)
    
    plt.tight_layout()
    plt.show()

# 绘制结果
plot_simulation_results()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效应器属性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;model.nu&lt;/code&gt;：表示模型的效应器数量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.actuator(i)&lt;/code&gt;：获取第 i 个效应器的信息。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.actuator(i).name&lt;/code&gt;：获取第 i 个效应器的名称。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.actuator(i).trnid&lt;/code&gt;：获取第 i 个效应器作用的传输元素ID。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.actuator(i).gear&lt;/code&gt;：获取第 i 个效应器的力/力矩系数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.actuator_ctrllimited[i]&lt;/code&gt;：表示第 i 个效应器是否限制控制范围。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.actuator_ctrlrange[i]&lt;/code&gt;：获取第 i 个效应器的控制信号范围。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.actuator_dyntype[i]&lt;/code&gt;：获取第 i 个效应器的动态类型。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;传感器输入&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;data.ctrl[idx] = value&lt;/code&gt;：设置效应器的控制信号。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;传感器属性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;model.nsensor&lt;/code&gt;：表示模型的传感器数量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.sensor(i)&lt;/code&gt;：获取第 i 个传感器的信息。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.sensor(i).name&lt;/code&gt;：获取第 i 个传感器的名称。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.sensor(i).type&lt;/code&gt;：获取第 i 个传感器的类型。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.sensor(i).objtype&lt;/code&gt;：获取第 i 个传感器的对象类型。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.sensor(i).objid&lt;/code&gt;：获取第 i 个传感器的对象ID。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.sensor(i).datatype&lt;/code&gt;：获取第 i 个传感器的数据类型。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.sensor(i).needstage&lt;/code&gt;：获取第 i 个传感器需要的计算阶段。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;model.sensor(i).dim&lt;/code&gt;：获取第 i 个传感器的维度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;传感器数据和接触数据&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;data.sensor(name)&lt;/code&gt;：根据名称获取传感器的数据值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data.sensordata[i:i+dim]&lt;/code&gt;：获取第 i 个传感器的数据值（需要知道传感器维度）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data.contact[i].dist&lt;/code&gt;：获取第 i 个接触点的距离。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data.contact[i].pos&lt;/code&gt;：获取第 i 个接触点的位置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data.contact[i].frame&lt;/code&gt;：获取第 i 个接触点的方向向量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data.contact[i].force&lt;/code&gt;：获取第 i 个接触点的接触力。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MuJoCo的接口过于繁杂，往往令初学者头疼不已，一般地，有如下数据访问格式，足够使用。这些数据变量类型都是numpy的ndarray类型，使用时可以直接进行numpy的操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;model = mujoco.MjModel.from_xml_path(&quot;model.xml&quot;)
data = mujoco.MjData(model)

# 核心运动学状态
position = data.qpos       # 所有关节位置。若只有一个关节，则形状为 (7,) 表示xyz位置和四元数方向 [x, y, z, qw, qx, qy, qz]
position_world = data.qpos[:3]  # 世界坐标中的位置
orientation = data.qpos[3:7]  # 四元数表示的方向

velocity = data.qvel       # 所有关节速度。若只有一个关节，则形状为 (6,) 表示xyz线速度和角速度 [vx, vy, vz, wx, wy, wz]
linear_velocity = data.qvel[:3]  # [vx, vy, vz]
angular_velocity = data.qvel[3:6]  # [wx, wy, wz]

acceleration = data.qacc   # 所有关节加速度。若只有一个关节，则形状为 (6,) 表示xyz线加速度和角加速度 [ax, ay, az, wx, wy, wz]
linear_acceleration = data.qacc[:3]  # [ax, ay, az]
angular_acceleration = data.qacc[3:6]  # [wx, wy, wz]

# 刚体状态 (通过ID访问)
body_id = model.body_name2id(&quot;cube&quot;)
position_world = data.xpos[body_id]      # 世界坐标中的位置，形状为 (3,)，表示 [x, y, z]
rotation_world = data.xmat[body_id]      # 旋转矩阵(3x3)，形状为 (9,)，表示3x3矩阵的行优先展开
quaternion_world = data.xquat[body_id]   # 四元数表示的方向，形状为 (4,)，表示 [qw, qx, qy, qz]

# 控制信号和作用力
control_signals = data.ctrl              # 效应器控制信号
external_forces = data.xfrc_applied      # 施加的外部力
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Decoding Strategies</title><link>https://adalovelemon.github.io/posts/content/technotes/llm-misc/decodingstrategies/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/llm-misc/decodingstrategies/</guid><pubDate>Tue, 03 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Decoding strategies are the &lt;em&gt;decision rules&lt;/em&gt; that turn a model’s next-token scores into actual tokens. The model provides a distribution over the vocabulary; the decoder decides how to pick the next token, how to stop, and how to trade off diversity vs. reliability.&lt;/p&gt;
&lt;p&gt;This note focuses on practical, commonly used strategies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deterministic: greedy decoding, beam search&lt;/li&gt;
&lt;li&gt;Stochastic: sampling, temperature, top-$k$, top-$p$ (nucleus)&lt;/li&gt;
&lt;li&gt;Controls and constraints: repetition penalties, no-repeat $n$-grams, stop sequences&lt;/li&gt;
&lt;li&gt;A short addendum: contrastive search and speculative decoding&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. Notation: what the model actually outputs&lt;/h2&gt;
&lt;p&gt;Let $x_{1:t}$ be the tokens generated so far. A causal language model (e.g., GPT-2-like) outputs &lt;strong&gt;logits&lt;/strong&gt; $z \in \mathbb{R}^{|V|}$ for the next token:&lt;/p&gt;
&lt;p&gt;$$
z = f_\theta(x_{1:t})
$$&lt;/p&gt;
&lt;p&gt;These logits become a probability distribution by softmax:&lt;/p&gt;
&lt;p&gt;$$
p(x_{t+1}=v\mid x_{1:t}) = \frac{\exp(z_v)}{\sum_{u\in V}\exp(z_u)}
$$&lt;/p&gt;
&lt;p&gt;Important detail:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;model.generate(...)&lt;/code&gt; returns &lt;strong&gt;token ids&lt;/strong&gt; (the chosen tokens), not logits.&lt;/li&gt;
&lt;li&gt;To get logits/scores during generation in HuggingFace, you must request them (see code below).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. A vivid example (correctly extracting logits)&lt;/h2&gt;
&lt;p&gt;We feed GPT-2 with the prompt &lt;code&gt;&quot;The cat ran after a&quot;&lt;/code&gt; and generate 4 tokens. We will print:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the chosen next token ids (what decoding decided)&lt;/li&gt;
&lt;li&gt;the logits distribution for each step (what the model predicted)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import torch
from transformers import GPT2LMHeadModel, GPT2Tokenizer

device = torch.device(&quot;cuda&quot; if torch.cuda.is_available() else &quot;cpu&quot;)
model = GPT2LMHeadModel.from_pretrained(&quot;gpt2&quot;).to(device)
tokenizer = GPT2Tokenizer.from_pretrained(&quot;gpt2&quot;)
model.eval()

prompt = &quot;The cat ran after a&quot;
input_ids = tokenizer(prompt, return_tensors=&quot;pt&quot;).input_ids.to(device)

with torch.no_grad():
    out = model.generate(
        input_ids,
        max_new_tokens=4,
        do_sample=False,  # greedy by default
        return_dict_in_generate=True,
        output_scores=True,
    )

sequences = out.sequences[0]                 # token ids (prompt + generated)
new_token_ids = sequences[-4:]               # only the 4 new tokens

print(&quot;New token ids:&quot;, new_token_ids.tolist())
print(&quot;Generated text:&quot;, tokenizer.decode(sequences, skip_special_tokens=True))

# out.scores is a list of length = max_new_tokens
# each element is logits for that step: [batch, vocab]
for step, logits in enumerate(out.scores, start=1):
    topk = torch.topk(logits[0], k=5)
    ids = topk.indices.tolist()
    vals = topk.values.tolist()
    toks = [tokenizer.decode([i]) for i in ids]
    print(f&quot;Step {step} top-5 tokens:&quot;)
    for tok, logit in zip(toks, vals):
        print(f&quot;  {tok!r:&amp;gt;10}  logit={logit:8.3f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Conceptually:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;model&lt;/em&gt; computes logits $z$.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;decoder&lt;/em&gt; turns logits into the next token (greedy, beam, sampling, ...).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. Temperature: sharpening or flattening the distribution&lt;/h2&gt;
&lt;p&gt;Before choosing a token, it is common to apply &lt;strong&gt;temperature scaling&lt;/strong&gt; $T&amp;gt;0$:&lt;/p&gt;
&lt;p&gt;$$
p_T(v \mid x_{1:t}) = \text{softmax}\left(\frac{z}{T}\right)_v
$$&lt;/p&gt;
&lt;p&gt;Effects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$T \to 0^+$: distribution becomes very peaked (approaches greedy / argmax)&lt;/li&gt;
&lt;li&gt;$T = 1$: original distribution&lt;/li&gt;
&lt;li&gt;$T &amp;gt; 1$: flatter distribution (more randomness)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In practice, temperature is one of the most important knobs for “creativity vs. correctness”.&lt;/p&gt;
&lt;h2&gt;4. Greedy decoding (deterministic)&lt;/h2&gt;
&lt;h3&gt;4.1 Definition&lt;/h3&gt;
&lt;p&gt;Greedy decoding picks the most likely next token at every step:&lt;/p&gt;
&lt;p&gt;$$
x_{t+1} = \arg\max_{v\in V} ; p(v\mid x_{1:t})
$$&lt;/p&gt;
&lt;h3&gt;4.2 Pros / cons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pros: fast, stable, often best for “exactness” tasks with strong local signal.&lt;/li&gt;
&lt;li&gt;Cons: can be repetitive; can miss globally better continuations; brittle for open-ended generation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.3 Minimal “manual greedy” code&lt;/h3&gt;
&lt;p&gt;This shows what generation is doing internally (one token at a time):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
from transformers import GPT2LMHeadModel, GPT2Tokenizer

device = torch.device(&quot;cuda&quot; if torch.cuda.is_available() else &quot;cpu&quot;)
model = GPT2LMHeadModel.from_pretrained(&quot;gpt2&quot;).to(device)
tokenizer = GPT2Tokenizer.from_pretrained(&quot;gpt2&quot;)
model.eval()

prompt = &quot;The cat ran after a&quot;
input_ids = tokenizer(prompt, return_tensors=&quot;pt&quot;).input_ids.to(device)

max_new_tokens = 4
with torch.no_grad():
    for _ in range(max_new_tokens):
        logits = model(input_ids).logits  # [batch, seq_len, vocab]
        next_logits = logits[:, -1, :]    # [batch, vocab]
        next_id = torch.argmax(next_logits, dim=-1, keepdim=True)
        input_ids = torch.cat([input_ids, next_id], dim=1)

print(tokenizer.decode(input_ids[0], skip_special_tokens=True))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. Beam search (deterministic, “global-ish”)&lt;/h2&gt;
&lt;p&gt;Greedy decoding is locally optimal. Beam search keeps multiple hypotheses.&lt;/p&gt;
&lt;h3&gt;5.1 Objective&lt;/h3&gt;
&lt;p&gt;An autoregressive model defines sequence probability:&lt;/p&gt;
&lt;p&gt;$$
P(x_{1:T}) = \prod_{t=1}^{T} P(x_t \mid x_{1:t-1})
$$&lt;/p&gt;
&lt;p&gt;It is numerically convenient to maximize log-probability:&lt;/p&gt;
&lt;p&gt;$$
\log P(x_{1:T}) = \sum_{t=1}^{T} \log P(x_t \mid x_{1:t-1})
$$&lt;/p&gt;
&lt;p&gt;Beam search approximates:&lt;/p&gt;
&lt;p&gt;$$
\arg\max_{x_{1:T}} \log P(x_{1:T})
$$&lt;/p&gt;
&lt;p&gt;by keeping only the top $B$ partial sequences at each step.&lt;/p&gt;
&lt;h3&gt;5.2 Length normalization / length penalty&lt;/h3&gt;
&lt;p&gt;Pure log-probability tends to favor shorter sequences (because it sums negative numbers). A common fix is a length penalty. One popular form (used in NMT) is:&lt;/p&gt;
&lt;p&gt;$$
ext{score}(x_{1:T}) = \frac{\log P(x_{1:T})}{\left(\frac{5+T}{5+1}\right)^\alpha}
$$&lt;/p&gt;
&lt;p&gt;where $\alpha \in [0, 2]$ controls how much we prefer longer sequences.&lt;/p&gt;
&lt;h3&gt;5.3 Pros / cons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pros: good for tasks with a “single best answer” (translation, some summarization, constrained generation).&lt;/li&gt;
&lt;li&gt;Cons: for open-ended text, can increase dullness and repetition; more compute: roughly $B$ times the work.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.4 HuggingFace usage&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;out = model.generate(
    input_ids,
    max_new_tokens=80,
    num_beams=4,
    do_sample=False,
    early_stopping=True,
    length_penalty=1.0,
)
print(tokenizer.decode(out[0], skip_special_tokens=True))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. Sampling (stochastic)&lt;/h2&gt;
&lt;p&gt;Sampling draws the next token from the probability distribution instead of always taking the max:&lt;/p&gt;
&lt;p&gt;$$
x_{t+1} \sim \text{Categorical}(p(\cdot\mid x_{1:t}))
$$&lt;/p&gt;
&lt;p&gt;Sampling is the main tool for &lt;em&gt;diversity&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;6.1 Why naive sampling can be bad&lt;/h3&gt;
&lt;p&gt;The long tail of the vocabulary contains many low-probability tokens that can derail coherence. Practical decoders therefore often &lt;em&gt;truncate&lt;/em&gt; the distribution before sampling.&lt;/p&gt;
&lt;h2&gt;7. Top-$k$ sampling&lt;/h2&gt;
&lt;p&gt;Top-$k$ sampling keeps only the $k$ most probable tokens and renormalizes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Let $S_k$ be the set of the $k$ tokens with highest probability.&lt;/li&gt;
&lt;li&gt;Define $p&apos;(v) \propto p(v)$ for $v\in S_k$, and $p&apos;(v)=0$ otherwise.&lt;/li&gt;
&lt;li&gt;Sample $x_{t+1} \sim \text{Categorical}(p&apos;)$.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Intuition:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Small $k$ behaves more like greedy.&lt;/li&gt;
&lt;li&gt;Large $k$ approaches naive sampling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HuggingFace:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;out = model.generate(
    input_ids,
    max_new_tokens=120,
    do_sample=True,
    top_k=50,
    temperature=0.8,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;8. Top-$p$ (nucleus) sampling&lt;/h2&gt;
&lt;p&gt;Top-$p$ chooses the smallest token set whose cumulative probability is at least $p$.&lt;/p&gt;
&lt;p&gt;Let tokens be sorted by probability: $p_1 \ge p_2 \ge \cdots$. Choose the smallest $m$ such that:&lt;/p&gt;
&lt;p&gt;$$
\sum_{i=1}^{m} p_i \ge p
$$&lt;/p&gt;
&lt;p&gt;Then sample from those $m$ tokens after renormalization.&lt;/p&gt;
&lt;p&gt;Why this is often better than top-$k$:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The “right” number of plausible tokens changes with context.&lt;/li&gt;
&lt;li&gt;Top-$p$ adapts the candidate set size automatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HuggingFace:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;out = model.generate(
    input_ids,
    max_new_tokens=120,
    do_sample=True,
    top_p=0.9,
    temperature=0.8,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rule of thumb: start with &lt;code&gt;top_p=0.9&lt;/code&gt; and tune temperature.&lt;/p&gt;
&lt;h2&gt;9. A single-step implementation: temperature + top-$k$ + top-$p$&lt;/h2&gt;
&lt;p&gt;The following helper illustrates the core mechanics.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch

def sample_next_token(
    logits: torch.Tensor,
    temperature: float = 1.0,
    top_k: int | None = None,
    top_p: float | None = None,
):
    # logits: [vocab]
    if temperature &amp;lt;= 0:
        raise ValueError(&quot;temperature must be &amp;gt; 0&quot;)

    logits = logits / temperature

    # Top-k truncation
    if top_k is not None and top_k &amp;gt; 0:
        values, _ = torch.topk(logits, k=min(top_k, logits.numel()))
        cutoff = values[-1]
        logits = torch.where(logits &amp;lt; cutoff, torch.tensor(float(&quot;-inf&quot;), device=logits.device), logits)

    # Convert to probs for top-p
    probs = torch.softmax(logits, dim=-1)

    if top_p is not None and 0 &amp;lt; top_p &amp;lt; 1:
        sorted_probs, sorted_idx = torch.sort(probs, descending=True)
        cdf = torch.cumsum(sorted_probs, dim=-1)
        keep = cdf &amp;lt;= top_p
        # ensure at least 1 token
        keep[..., 0] = True

        filtered = torch.zeros_like(probs)
        filtered[sorted_idx[keep]] = probs[sorted_idx[keep]]
        probs = filtered / filtered.sum()

    next_id = torch.multinomial(probs, num_samples=1)
    return next_id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is essentially what “sampling” decoders do, with a few extra details for speed and numerical stability.&lt;/p&gt;
&lt;h2&gt;10. Preventing repetition and degeneration&lt;/h2&gt;
&lt;p&gt;Many decoding failures come from &lt;em&gt;degeneration&lt;/em&gt;: loops, repeated phrases, or bland continuations.&lt;/p&gt;
&lt;h3&gt;10.1 Repetition penalty&lt;/h3&gt;
&lt;p&gt;In practice, decoders often penalize tokens already used in the context (or recently used). HuggingFace implements a common heuristic &lt;code&gt;repetition_penalty&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;out = model.generate(
    input_ids,
    max_new_tokens=160,
    do_sample=True,
    top_p=0.9,
    temperature=0.8,
    repetition_penalty=1.1,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10.2 No-repeat $n$-gram constraint&lt;/h3&gt;
&lt;p&gt;For a chosen $n$, forbid generating any $n$-gram that already appeared.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;out = model.generate(
    input_ids,
    max_new_tokens=160,
    do_sample=True,
    top_p=0.9,
    no_repeat_ngram_size=3,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a hard constraint (can improve readability, but may overconstrain and reduce fluency).&lt;/p&gt;
&lt;h3&gt;10.3 Stop sequences / EOS&lt;/h3&gt;
&lt;p&gt;Decoding also decides &lt;em&gt;when to stop&lt;/em&gt;. Common stop rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Emit an EOS token&lt;/li&gt;
&lt;li&gt;Hit &lt;code&gt;max_new_tokens&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Match a stop string (e.g., &lt;code&gt;&quot;\n\n&quot;&lt;/code&gt;, &lt;code&gt;&quot;&amp;lt;/answer&amp;gt;&quot;&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In pure HuggingFace &lt;code&gt;generate&lt;/code&gt;, stop strings require custom stopping criteria.&lt;/p&gt;
&lt;h2&gt;11. Choosing a strategy: practical guidance&lt;/h2&gt;
&lt;p&gt;There is no universally best decoder; choose based on task.&lt;/p&gt;
&lt;h3&gt;11.1 “Single correct answer” tasks&lt;/h3&gt;
&lt;p&gt;Examples: translation, extraction with strict schema, deterministic tool calls.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prefer: greedy or beam search&lt;/li&gt;
&lt;li&gt;Typical settings: &lt;code&gt;do_sample=False&lt;/code&gt;, &lt;code&gt;num_beams=1..5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add constraints: stop tokens, formatting rules, sometimes &lt;code&gt;no_repeat_ngram_size&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;11.2 “Helpful but varied” tasks&lt;/h3&gt;
&lt;p&gt;Examples: brainstorming, writing, ideation.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prefer: nucleus sampling + moderate temperature&lt;/li&gt;
&lt;li&gt;Typical settings: &lt;code&gt;top_p=0.9&lt;/code&gt;, &lt;code&gt;temperature=0.7..1.0&lt;/code&gt;, &lt;code&gt;repetition_penalty=1.05..1.2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;11.3 “Code generation”&lt;/h3&gt;
&lt;p&gt;Code is sensitive to small mistakes and needs consistency.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Often good: lower temperature, smaller nucleus&lt;/li&gt;
&lt;li&gt;Typical settings: &lt;code&gt;temperature=0.2..0.6&lt;/code&gt;, &lt;code&gt;top_p=0.8..0.95&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add: stop sequences (e.g., stop after closing triple backticks), constraints if possible&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;11.4 A quick parameter cheat sheet&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Too boring / repetitive: increase &lt;code&gt;temperature&lt;/code&gt; slightly, increase &lt;code&gt;top_p&lt;/code&gt;, add mild &lt;code&gt;repetition_penalty&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Too random / incoherent: decrease &lt;code&gt;temperature&lt;/code&gt;, decrease &lt;code&gt;top_p&lt;/code&gt;, consider greedy&lt;/li&gt;
&lt;li&gt;Too short: increase &lt;code&gt;max_new_tokens&lt;/code&gt;, consider a positive length penalty in beam search&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;12. Two modern add-ons (optional but useful)&lt;/h2&gt;
&lt;h3&gt;12.1 Contrastive search (quality vs repetition)&lt;/h3&gt;
&lt;p&gt;Contrastive search tries to balance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;high probability under the model (coherence)&lt;/li&gt;
&lt;li&gt;low similarity to recent hidden states (avoid repetition)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HuggingFace has a mode for it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;out = model.generate(
    input_ids,
    max_new_tokens=160,
    penalty_alpha=0.6,
    top_k=4,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can produce less repetitive text than greedy/beam without full stochastic sampling.&lt;/p&gt;
&lt;h3&gt;12.2 Speculative decoding (speed, not a style)&lt;/h3&gt;
&lt;p&gt;Speculative decoding uses a small “draft” model to propose multiple tokens and a larger model to verify them. This accelerates generation but does not change the &lt;em&gt;intended&lt;/em&gt; distribution if implemented correctly. It’s mostly an engineering optimization.&lt;/p&gt;
&lt;h2&gt;13. Summary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The model outputs logits; decoding decides what tokens to emit.&lt;/li&gt;
&lt;li&gt;Greedy: fastest and stable, but can be dull.&lt;/li&gt;
&lt;li&gt;Beam search: better for structured tasks; can be dull for open-ended text.&lt;/li&gt;
&lt;li&gt;Sampling (top-$k$/top-$p$ + temperature): best for diversity; needs controls to avoid incoherence.&lt;/li&gt;
&lt;li&gt;Repetition controls and stopping rules are part of decoding in practice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want, I can also add a small section comparing these methods on the same prompt with side-by-side outputs (and plots of entropy / top-$p$ set size) to make the differences visually obvious.&lt;/p&gt;
</content:encoded></item><item><title>AI-Sys 6 对抗攻击和安全隐私</title><link>https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter6/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter6/</guid><pubDate>Sun, 25 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;AI系统中的对抗攻击与安全隐私&lt;/p&gt;
&lt;h1&gt;AI安全与对抗攻击简介&lt;/h1&gt;
&lt;h2&gt;AI安全的重要性&lt;/h2&gt;
&lt;p&gt;人工智能（AI），特别是深度学习，在图像识别、自然语言处理和决策制定等多个领域取得了显著成就。然而，AI模型的安全性日益受到关注。模型可能容易受到各种攻击，导致错误的预测、隐私泄露或系统被操纵。理解这些漏洞对于构建强大且值得信赖的AI系统至关重要。&lt;/p&gt;
&lt;h2&gt;什么是对抗攻击？&lt;/h2&gt;
&lt;p&gt;对抗攻击指的是精心构造恶意输入——称为&lt;strong&gt;对抗样本&lt;/strong&gt;（adversarial examples/samples）——旨在欺骗机器学习模型。这些输入通常是通过向合法输入添加微小的、往往难以察觉的扰动来创建的。&lt;/p&gt;
&lt;p&gt;假设 $x$ 是原始输入，$y_{true}$ 是其真实标签，$f(x)$ 是模型的预测。一个对抗样本 $x_{adv}$ 的生成需要满足&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$f(x_{adv}) \ne y_{true}$ (模型错误分类 $x_{adv}$)。&lt;/li&gt;
&lt;li&gt;$x_{adv}$ 与 $x$ “接近”。这种接近度通常用 $L_p$ 范数衡量，例如 $\Vert x_{adv} - x\Vert_p \le \epsilon$，其中 $\epsilon$ 是一个很小的常数。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;扰动表示为 $\delta = x_{adv} - x$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/adv1.png&quot; alt=&quot;对抗攻击概念图&quot; /&gt;
&lt;em&gt;(概念图：一张熊猫图片，经过轻微扰动后，被模型高置信度地误分类为长臂猿。来源：Goodfellow et al., 2014)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;对抗样本的特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;难以察觉性 (Imperceptibility):&lt;/strong&gt; 扰动通常非常小，人眼难以将其与原始输入区分开来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;迁移性 (Transferability):&lt;/strong&gt; 为一个模型精心制作的对抗样本往往也能欺骗其他模型，即使这些模型具有不同的架构或在不同的数据集上训练。这对于黑盒攻击尤其重要。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;普适性 (Universality) (某些攻击):&lt;/strong&gt; 一些攻击可以找到一个单一的扰动向量，当添加到许多不同的干净输入时，会导致它们被错误分类。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;对抗攻击的类型&lt;/h1&gt;
&lt;p&gt;攻击主要根据攻击者对目标模型的了解程度进行分类：&lt;/p&gt;
&lt;h2&gt;白盒攻击 (White-Box Attacks)&lt;/h2&gt;
&lt;p&gt;攻击者完全了解目标模型，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模型架构&lt;/li&gt;
&lt;li&gt;模型参数（权重和偏置）&lt;/li&gt;
&lt;li&gt;训练数据（有时）&lt;/li&gt;
&lt;li&gt;使用的防御机制（如果有）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这使得攻击者能够计算损失函数相对于输入的梯度，这是制作有效扰动的常用技术。&lt;/p&gt;
&lt;h2&gt;黑盒攻击 (Black-Box Attacks)&lt;/h2&gt;
&lt;p&gt;攻击者对目标模型的了解有限或完全不了解。他们只能通过向模型查询输入并观察输出来进行攻击（例如，预测的标签或置信度分数）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基于查询的攻击 (Query-based Attacks):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基于分数的 (Score-based):&lt;/strong&gt; 攻击者获得预测的置信度分数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基于决策的 (Decision-based):&lt;/strong&gt; 攻击者只获得最终的预测标签（硬标签）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基于迁移的攻击 (Transfer-based Attacks):&lt;/strong&gt; 攻击者训练一个本地的替代模型，为其生成对抗样本，然后利用这些样本的迁移性来攻击目标黑盒模型。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;白盒攻击方法：快速梯度符号法 (FGSM)&lt;/h1&gt;
&lt;p&gt;FGSM 是 Goodfellow 等人（2014年）提出的一种简单而有效的一步式白盒攻击方法。&lt;/p&gt;
&lt;h2&gt;FGSM 原理&lt;/h2&gt;
&lt;p&gt;FGSM 的工作原理是沿着损失函数 $J(\theta, x, y)$ 相对于输入 $x$ 的梯度的符号方向移动输入 $x$。其思想是找到一个扰动 $\delta$ 来最大化损失，从而增加错误分类的机会。&lt;/p&gt;
&lt;p&gt;扰动 $\delta$ 计算如下：
$\delta = \epsilon \cdot \text{sign}(\nabla_x J(\theta, x, y))$&lt;/p&gt;
&lt;p&gt;对抗样本 $x_{adv}$ 则是：
$x_{adv} = x + \epsilon \cdot \text{sign}(\nabla_x J(\theta, x, y))$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$x$: 原始输入图像。&lt;/li&gt;
&lt;li&gt;$y$: $x$ 的真实标签。&lt;/li&gt;
&lt;li&gt;$\theta$: 模型参数。&lt;/li&gt;
&lt;li&gt;$J(\theta, x, y)$: 损失函数（例如，交叉熵损失）。&lt;/li&gt;
&lt;li&gt;$\nabla_x J(\theta, x, y)$: 损失函数相对于输入 $x$ 的梯度。&lt;/li&gt;
&lt;li&gt;$\text{sign()}$: 符号函数，如果输入为负则返回-1，为零则返回0，为正则返回1。&lt;/li&gt;
&lt;li&gt;$\epsilon$ (epsilon): 一个小的标量，控制扰动的大小 ($L_\infty$ 范数)。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;扰动后的图像 $x_{adv}$ 通常会被裁剪，以确保其像素值保持在有效范围内（例如，对于归一化图像为 [0, 1]）。&lt;/p&gt;
&lt;p&gt;除了FGSM，其他著名的白盒攻击方法包括&lt;/p&gt;
&lt;h3&gt;投影梯度下降 (Projected Gradient Descent, PGD)&lt;/h3&gt;
&lt;p&gt;PGD被认为是FGSM的迭代版本，也是一种更强大的攻击方法。它不是像FGSM那样只在梯度方向上迈出一大步，而是在梯度的符号方向上迭代地迈出多个小步。在每一步之后，它都会将扰动结果投影回原始输入 $x$ 周围的 $\epsilon$-球内，以确保扰动的大小始终保持在预设的界限内（例如 $L_\infty$ 范数约束），并且像素值也保持在有效范围内（如[0,1]）。
其迭代公式可以表示为：
$x_{adv}^{(0)} = x$ (或者 $x$ 加上一个微小的随机噪声以增加攻击的探索性)
$x_{adv}^{(t+1)} = \Pi_{x, \epsilon} (x_{adv}^t + \alpha \cdot \text{sign}(\nabla_x J(\theta, x_{adv}^t, y)))$
其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$x_{adv}^{(t)}$ 是第 $t$ 次迭代产生的对抗样本。&lt;/li&gt;
&lt;li&gt;$\alpha$ 是每一步的步长，通常是一个较小的值（例如 $\epsilon / \text{迭代次数}$）。&lt;/li&gt;
&lt;li&gt;$\Pi_{x, \epsilon}$ 是投影操作，它确保更新后的样本 $x_{adv}^{(t+1)}$ 仍然在以原始样本 $x$ 为中心、半径为 $\epsilon$ 的 $L_p$ 范数球内，并且其值被裁剪到有效的数据范围。
由于其迭代的特性和每步的投影约束，PGD攻击通常能找到更优的对抗扰动，从而对模型造成更大的威胁。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Carlini &amp;amp; Wagner (C&amp;amp;W) 攻击&lt;/h3&gt;
&lt;p&gt;C&amp;amp;W攻击是一类基于优化的攻击方法，以其强大的攻击效果而闻名，但相应地，其计算成本也比FGSM或PGD更高。C&amp;amp;W攻击的目标是找到最小的扰动 $\delta$，使得添加了该扰动的样本 $x + \delta$ 被模型错误分类，同时这个扰动本身尽可能小（通常用 $L_0, L_2,$ 或 $L_\infty$ 范数来衡量）。
这类攻击通常会构造一个特定的目标函数（损失函数）来进行优化。例如，在目标攻击（targeted attack）中，攻击者希望模型将输入 $x$ 错误分类为特定的目标类别 $t&apos;$；在非目标攻击（untargeted attack）中，则希望模型将其分类为除真实类别 $y$ 之外的任何类别。
C&amp;amp;W攻击中的 $L_2$ 攻击形式的优化问题可以表示为：
$\text{minimize} ||\delta||_2^2 + c \cdot f(x+\delta)$
其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$||\delta||_2^2$ 是扰动的 $L_2$ 范数的平方，目标是使其最小化。&lt;/li&gt;
&lt;li&gt;$f(x+\delta)$ 是一个惩罚项，当 $x+\delta$ 未被成功误导时，该项为正，否则为非正。例如，$f(x&apos;)$ 可以定义为 $\max(\max_{i \ne t} Z(x&apos;)_i - Z(x&apos;)_t, -\kappa)$，这里 $Z(x&apos;)$ 是模型在softmax层之前的logits输出，$t$ 是目标类别（或真实类别，取决于攻击类型），$\kappa$ (kappa)是一个置信度参数，用于控制攻击的强度（$\kappa=0$ 表示只要能改变分类即可，$\kappa &amp;gt; 0$ 则要求以更高的置信度误分类）。&lt;/li&gt;
&lt;li&gt;$c$ 是一个正常数，用于平衡扰动大小和攻击成功之间的权重。通常通过二分搜索来找到合适的 $c$ 值。
C&amp;amp;W攻击由于其优化方法，往往能找到非常难以察觉且有效的对抗样本。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;黑盒攻击方法&lt;/h2&gt;
&lt;p&gt;黑盒攻击指的是攻击者在对目标模型的内部结构、参数、训练数据甚至所使用的防御机制了解非常有限或完全不了解的情况下发起的攻击。攻击者通常只能通过向模型API发送查询请求，并观察其输出（如预测类别、置信度分数）来收集信息并实施攻击。&lt;/p&gt;
&lt;p&gt;黑盒攻击主要可以分为两大类：&lt;/p&gt;
&lt;h3&gt;基于查询的攻击 (Query-based Attacks)&lt;/h3&gt;
&lt;p&gt;这类攻击依赖于多次查询目标模型来获取信息，并逐步调整输入以生成对抗样本。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基于分数的攻击 (Score-based Attacks):&lt;/strong&gt; 攻击者可以获取模型对输入样本的预测置信度分数（例如，softmax层的输出概率）。利用这些分数，攻击者可以估计损失函数相对于输入的梯度（即使无法直接计算），或者采用其他优化策略来引导输入朝向错误分类。例如，通过迭代地对输入进行微小扰动，并观察哪个方向的扰动能最有效地降低正确类别的置信度或提高某个错误类别的置信度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基于决策的攻击 (Decision-based Attacks / Hard-label Attacks):&lt;/strong&gt; 这是更具挑战性的黑盒场景，攻击者只能获得模型最终的预测标签（硬标签），而无法得知具体的置信度分数。这类攻击通常从一个已知的被模型错误分类的样本开始（如果找不到，可能从一个随机噪声图像开始），然后逐步向目标原始图像的方向修改这个错误分类的样本，同时确保每一步的修改都保持其被错误分类的状态，并且修改的幅度尽可能小，直到生成的样本在视觉上与原始目标图像足够相似但仍被错误分类。Boundary Attack 就是这类攻击的一个典型例子。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;基于迁移的攻击 (Transfer-based Attacks)&lt;/h3&gt;
&lt;p&gt;这类攻击利用了对抗样本的一个重要特性——&lt;strong&gt;迁移性&lt;/strong&gt;。迁移性指的是为一个模型（源模型）精心制作的对抗样本，往往也能欺骗其他具有不同架构或在不同数据集上训练的模型（目标模型），即使攻击者对目标模型一无所知。
攻击步骤通常如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;训练替代模型 (Substitute Model / Proxy Model):&lt;/strong&gt; 攻击者首先收集一些数据（如果能获取到与目标模型训练数据分布相似的数据集最好，如果不能，也可以使用公开数据集，甚至通过查询目标模型来构建一个合成数据集），然后用这些数据训练一个本地的替代模型。这个替代模型在功能上尽可能地模仿目标黑盒模型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在替代模型上生成对抗样本:&lt;/strong&gt; 由于攻击者对替代模型拥有完全的白盒访问权限，他们可以使用任何白盒攻击方法（如FGSM, PGD, C&amp;amp;W等）在替代模型上生成对抗样本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;利用迁移性攻击目标模型:&lt;/strong&gt; 将在替代模型上生成的对抗样本直接输入到目标黑盒模型中。由于迁移性的存在，这些样本有很大概率也能成功欺骗目标模型。
&lt;ul&gt;
&lt;li&gt;如果攻击者拥有目标网络的部分训练数据，可以更有效地训练出相似的替代网络。&lt;/li&gt;
&lt;li&gt;如果没有训练数据，攻击者也可以通过向目标网络查询大量输入并记录其输出来构建训练集，从而训练替代网络。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基于迁移的攻击避免了对目标模型的大量查询，计算成本相对较低，但其成功率高度依赖于对抗样本的迁移性强弱以及替代模型与目标模型的相似程度。&lt;/p&gt;
&lt;h2&gt;对抗补丁 (Adversarial Patch)&lt;/h2&gt;
&lt;p&gt;传统的对抗攻击方法（如FGSM、PGD）通常需要对输入图像的每一个像素或大部分像素进行微小的修改，这种全图扰动的方式在数字世界中是可行的，但在物理世界中实现起来则非常困难且不切实际。例如，我们很难精确控制打印出来的图片上每一个像素点的微小颜色变化。&lt;/p&gt;
&lt;p&gt;为了解决这个问题，&lt;strong&gt;对抗补丁 (Adversarial Patch, DPatch)&lt;/strong&gt; 的概念被提了出来。对抗补丁是一种小尺寸、局部化但扰动强度可能很大的图像区域（即一个“补丁”）。这个补丁被精心设计，当它被添加到原始图像的任何位置（或者特定位置）时，就能有效地欺骗机器学习模型。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主要特点与优势：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;物理可实现性:&lt;/strong&gt; 对抗补丁通常具有明确的形状（如方形）和相对简单的图案，可以很容易地被打印出来，并像贴纸一样粘贴到物理物体上，从而在物理世界中实现攻击。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位置无关性 (部分情况下):&lt;/strong&gt; 理想的对抗补丁在被放置于图像中不同位置时，仍然能保持其攻击效果，这增加了其在实际应用中的鲁棒性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标明确:&lt;/strong&gt; 可以设计针对特定任务的对抗补丁，例如：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分类任务:&lt;/strong&gt; 使图像分类器将贴有补丁的物体错误分类为其他类别。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标检测任务:&lt;/strong&gt; 使目标检测器（如YOLO, Faster R-CNN）无法检测到贴有补丁的物体（隐身攻击），或者将其检测为错误的类别。例如，一个精心设计的补丁可以让检测器把“停止”标志识别成“限速”标志，或者完全忽略一个行人。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;生成方法：&lt;/strong&gt;
对抗补丁的生成也是一个优化过程。目标是找到一个补丁图案 $P$，当它被应用到一系列不同的背景图像 $X_{bg}$ 和不同的位置、角度、缩放等变换 $T$ 下时，能够最大化模型的损失函数（即导致错误分类或检测失败）。
例如，针对目标检测器的对抗补丁，其损失函数可能包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使目标物体的类别预测错误。&lt;/li&gt;
&lt;li&gt;降低目标物体的检测置信度。&lt;/li&gt;
&lt;li&gt;使边界框定位不准确。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;文献中展示了例如40x40像素的无目标补丁可以有效地攻击YOLO检测器，而20x20像素的有目标补丁可以攻击Faster R-CNN。这些补丁虽然在图像中只占很小一部分，但其扰动强度足以覆盖整个模型的决策过程。&lt;/p&gt;
&lt;p&gt;对抗补丁的出现使得对AI系统的物理世界攻击成为可能，对自动驾驶、安防监控等依赖视觉感知的AI应用构成了严重威胁。&lt;/p&gt;
&lt;h2&gt;对抗防御 (Defense)&lt;/h2&gt;
&lt;p&gt;面对日益增多的对抗攻击手段，研究和部署有效的防御策略至关重要。对抗防御的目标是增强模型在面对恶意构造的输入时的鲁棒性，或者能够检测到这些恶意输入。防御方法大致可以分为被动防御和主动防御两类。&lt;/p&gt;
&lt;h3&gt;被动防御 (Passive Defense)&lt;/h3&gt;
&lt;p&gt;被动防御策略通常不直接修改模型结构或训练过程，而是试图在模型接收输入之前对输入进行处理，或者在模型输出之后进行判断，以检测或减轻对抗样本的影响。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;输入预处理/转换 (Input Preprocessing/Transformation):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;图像平滑/滤波 (Image Smoothing/Filtering):&lt;/strong&gt; 这是最直观的一类方法。通过应用各种图像滤波器（如高斯模糊、中值滤波、均值滤波）来平滑输入图像，希望能够去除或减弱对抗扰动中包含的高频噪声。虽然这类方法简单易行，但它们也可能损害模型在干净、未受攻击图像上的性能，因为平滑操作会丢失一部分图像细节。而且，攻击者如果知道防御方使用了某种特定的滤波器，也可能设计出能绕过该滤波器的对抗样本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特征压缩 (Feature Squeezing):&lt;/strong&gt; 该方法通过减少输入数据的冗余信息来压缩特征空间，从而可能消除对抗扰动。例如，可以降低图像的颜色位深度（如从24位彩色降到8位或更低），或者对图像进行空间平滑（如局部均值平滑）。其检测机制是：比较模型对原始输入的预测和对压缩后输入的预测，如果两者之间的差异超过某个阈值，则认为原始输入可能是对抗样本。这种方法在一定程度上有效，但压缩的程度需要仔细权衡，过度压缩会严重影响正常分类的准确率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;随机调整数据尺寸与填充 (Random Resizing and Padding):&lt;/strong&gt; 在将输入图像送入模型之前，对其进行随机的尺寸缩放和随机的边缘填充。这种随机性使得攻击者难以针对一个固定的输入格式来精确地生成有效的对抗样本，因为攻击者不知道在推理时输入会经过何种变换。这增加了攻击的难度，但不能完全阻止攻击。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;其他变换：&lt;/strong&gt; 还包括JPEG压缩、总变差最小化 (Total Variation Denoising) 等，它们都试图通过某种方式“净化”输入图像。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;被动防御的一个主要缺点是，它们往往是针对已知攻击模式设计的，一旦出现新的、更复杂的攻击方法，这些防御可能就会失效。攻击者也可能通过将防御机制本身纳入攻击优化目标中来规避它们（适应性攻击）。&lt;/p&gt;
&lt;h3&gt;主动防御 (Proactive Defense)&lt;/h3&gt;
&lt;p&gt;主动防御策略旨在通过修改模型的训练过程或模型架构本身，来构建一个对对抗攻击具有内在鲁棒性的模型。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;对抗训练 (Adversarial Training):&lt;/strong&gt; 这是目前被认为最有效且研究最广泛的主动防御方法。其核心思想是“以子之矛，攻子之盾”——在模型的训练阶段，就将模型暴露在对抗样本中，迫使模型学习如何正确分类这些恶意构造的输入。
标准的对抗训练流程可以描述为一个min-max优化问题：
$\min_{\theta} \mathbb{E}&lt;em&gt;{(x,y) \sim \mathcal{D}} [\max&lt;/em&gt;{\delta \in \mathcal{S}} L(\theta, x+\delta, y)]$
这意味着，对于训练集 $\mathcal{D}$ 中的每一个样本 $(x,y)$，我们首先在允许的扰动范围 $\mathcal{S}$ 内（例如，$|\delta|_\infty \le \epsilon$）找到一个能最大化损失函数 $L$ 的扰动 $\delta^&lt;em&gt;$ (内部的max问题，即生成对抗样本)，然后用这个生成的对抗样本 $(x+\delta^&lt;/em&gt;, y)$ 来训练模型，最小化其在这些对抗样本上的损失 (外部的min问题)。
&lt;strong&gt;具体步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从训练数据中取一个批次的干净样本。&lt;/li&gt;
&lt;li&gt;对于批次中的每个干净样本，使用某种攻击算法（如FGSM，但更常用的是更强的PGD攻击）生成对应的对抗样本。这一步是“找出漏洞”。&lt;/li&gt;
&lt;li&gt;将这些生成的对抗样本（有时也混合一部分干净样本）作为一个新的训练批次，用于更新模型的参数。这一步是“把洞补起来”，即通过数据增强的方式让模型学习对抗扰动。&lt;/li&gt;
&lt;li&gt;重复以上过程。
&lt;strong&gt;挑战与考虑：&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;攻击强度与多样性：&lt;/strong&gt; 对抗训练的效果高度依赖于生成对抗样本时所用攻击的强度和多样性。如果只用弱攻击（如单步FGSM）进行训练，模型可能只是对该特定攻击鲁棒，而对其他更强的攻击（如PGD）仍然脆弱。因此，通常推荐使用PGD等强攻击进行对抗训练。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算成本：&lt;/strong&gt; 对抗训练显著增加了训练的计算开销，因为在每个训练步骤中都需要额外进行生成对抗样本的迭代优化过程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;鲁棒性与准确性的权衡：&lt;/strong&gt; 有时过度追求对抗鲁棒性可能会导致模型在干净、未受攻击的原始数据上的准确率有所下降。如何在两者之间取得平衡是一个重要问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;过拟合到特定攻击：&lt;/strong&gt; 如果对抗训练中使用的攻击类型不够多样，模型可能会“过拟合”到这些特定攻击模式。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;防御蒸馏 (Defensive Distillation):&lt;/strong&gt; 最初提出时被认为是一种有前景的防御方法。它包括两个阶段：首先训练一个教师模型，然后使用教师模型产生的“软标签”（即softmax层的输出概率，而不是硬性的0/1标签）来训练一个学生模型（通常与教师模型结构相同）。其思想是平滑模型的决策表面，使得梯度更小，从而使基于梯度的攻击更难成功。然而，后来的研究表明，防御蒸馏提供的鲁棒性主要是由于梯度掩码/混淆效应，可以被一些专门设计的攻击方法所攻破。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;梯度掩码/混淆 (Gradient Masking/Obfuscation):&lt;/strong&gt; 一些防御方法通过使得模型梯度难以获取或不可靠来抵御基于梯度的攻击。例如，通过使用不可微的激活函数、随机化操作或在推理时破坏梯度等。虽然这些方法在某些情况下能抵御一些简单的白盒攻击，但它们通常不能提供真正的鲁棒性，因为攻击者可以通过其他方式（如黑盒攻击、基于替代模型的攻击，或者专门设计来克服梯度掩码的攻击）来绕过它们。一个好的防御不应该仅仅依赖于隐藏梯度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;认证防御 (Certified Defenses / Provable Defenses):&lt;/strong&gt; 这类防御方法的目标是提供数学上可证明的鲁棒性保证。即，对于给定的输入 $x$ 和一个扰动范围 $\mathcal{S}$（例如，以 $x$ 为中心的 $L_p$ 范数球），认证防御可以证明模型在该范围内的所有点上的预测都是一致的（即不会被错误分类）。方法包括基于区间边界传播 (Interval Bound Propagation, IBP)、线性规划松弛、随机平滑 (Randomized Smoothing) 等。虽然认证防御能提供严格的保证，但目前它们通常只能在相对较小的扰动范围或针对特定类型的模型和攻击时有效，并且可能会以牺牲模型在干净数据上的准确性或增加计算复杂度为代价。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;主动防御是当前对抗鲁棒性研究的热点，研究者们在不断探索新的模型架构、训练策略和正则化方法，以期构建出更强大、更通用的鲁棒模型。&lt;/p&gt;
&lt;h2&gt;思考题&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;如何提高模型对对抗攻击的鲁棒性？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对抗训练：&lt;/strong&gt; 这是目前最主流且被证明有效的方法之一。通过在训练数据中加入对抗样本，让模型学习识别和抵抗这些扰动。关键在于使用足够强大的攻击（如PGD）来生成训练用的对抗样本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据增强：&lt;/strong&gt; 除了对抗样本，还可以使用更广泛的数据增强技术，如随机变换、噪声注入等，来提高模型的泛化能力和对微小扰动的容忍度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型正则化：&lt;/strong&gt; 采用一些正则化技术，如权重衰减、Dropout、或者针对模型Lipschitz常数的正则化，有助于平滑模型的决策边界，从而降低其对微小扰动的敏感性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;鲁棒架构设计：&lt;/strong&gt; 探索和设计本身就具有更好鲁棒性的网络结构。例如，一些研究表明，具有平滑激活函数或特定连接方式的网络可能更鲁棒。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;认证防御方法：&lt;/strong&gt; 如前所述，随机平滑、区间边界传播等方法可以为模型在一定扰动范围内的鲁棒性提供数学保证。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集成方法：&lt;/strong&gt; 组合多个不同模型或不同防御策略的输出来进行最终决策，可能会提高整体的鲁棒性，但需要小心设计以避免被更强的自适应攻击攻破。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;预处理和输入变换：&lt;/strong&gt; 虽然属于被动防御，但一些有效的输入变换（如特征压缩、随机化）可以作为鲁棒性提升的辅助手段。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;你还知道哪些其他的对抗攻击和防御方法？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;其他攻击方法：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;白盒攻击：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BIM (Basic Iterative Method) / I-FGSM (Iterative FGSM):&lt;/strong&gt; 本质上是PGD的一种形式，迭代地应用FGSM。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DeepFool:&lt;/strong&gt; 一种迭代攻击，旨在找到最小的扰动使样本越过决策边界。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSMA (Jacobian-based Saliency Map Attack):&lt;/strong&gt; 利用雅可比矩阵计算输入的显著性，每次修改对输出影响最大的少数像素，直到误分类。主要用于目标攻击，扰动通常很小但可能更集中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Universal Adversarial Perturbations (UAPs):&lt;/strong&gt; 寻找一个单一的、与输入图像无关的扰动向量，当添加到大部分不同图像上时，都能使模型产生错误分类。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;黑盒攻击：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ZOO (Zeroth Order Optimization):&lt;/strong&gt; 基于零阶优化的攻击，通过查询模型输出来估计梯度，不需要模型内部信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SPSA (Simultaneous Perturbation Stochastic Approximation):&lt;/strong&gt; 也是一种梯度估计算法，用于黑盒攻击。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boundary Attack:&lt;/strong&gt; 一种基于决策的攻击，从一个大的对抗性扰动开始，逐步减少扰动同时保持对抗性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HopSkipJumpAttack:&lt;/strong&gt; 另一种高效的基于决策的攻击方法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Natural Evolution Strategies (NES):&lt;/strong&gt; 基于进化策略的黑盒攻击。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物理世界攻击：&lt;/strong&gt; 除了对抗补丁，还有针对3D物体的对抗纹理生成、针对语音识别的对抗音频等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据投毒攻击 (Data Poisoning Attacks):&lt;/strong&gt; 在训练阶段向训练数据中注入少量精心构造的恶意样本，使得训练出的模型在特定输入（触发器）下表现异常或性能下降。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后门攻击 (Backdoor Attacks):&lt;/strong&gt; 与数据投毒类似，通过在训练数据中植入带有特定触发器（后门）的样本，使得模型在正常输入下表现正常，但在遇到包含触发器的输入时会产生攻击者预设的恶意行为。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;其他防御方法：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;梯度正则化/梯度惩罚：&lt;/strong&gt; 在损失函数中加入对输入梯度的正则项，鼓励模型学习更平滑的函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;随机平滑 (Randomized Smoothing):&lt;/strong&gt; 通过在输入中添加高斯噪声，并对多次带噪输入的预测结果进行投票，来构建一个可认证鲁棒性的分类器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基于检测的防御：&lt;/strong&gt; 训练一个额外的检测器来区分正常样本和对抗样本。或者通过分析模型内部激活值、输入重建误差等统计特性来检测异常。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型集成 (Ensemble Methods):&lt;/strong&gt; 组合多个独立训练或不同架构的模型的预测结果。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入去噪/重建：&lt;/strong&gt; 使用自动编码器等模型对输入进行去噪或重建，再送入分类器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TRADES (TRadeoff-inspired Adversarial Defense via Surrogate-loss minimization):&lt;/strong&gt; 一种改进的对抗训练方法，旨在平衡鲁棒性和标准准确率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Certified Defenses (可证明防御/认证防御) 的其他变体：&lt;/strong&gt; 例如基于抽象解释 (Abstract Interpretation) 的方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>AI-Sys 5 计算集群调度与资源管理系统</title><link>https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter5/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter5/</guid><pubDate>Sat, 24 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;计算集群调度与资源管理系统&lt;/p&gt;
&lt;h1&gt;背景&lt;/h1&gt;
&lt;p&gt;假想你是一个多卡服务器集群的所有者，你打算把你的卡租出去收费赚💴。不过，不同的用户有不同的算力和软件需求，当他们的需求完成后，会释放相应的算力资源等。该如何合理分配调度？&lt;/p&gt;
&lt;p&gt;把每个需求抽象为一个&lt;strong&gt;作业&lt;/strong&gt;，每个作业有一个&lt;strong&gt;资源需求&lt;/strong&gt;，包括算力、内存、存储等。作业的资源需求是动态变化的，可能会随着时间的推移而增加或减少。&lt;/p&gt;
&lt;p&gt;集群中的一个作业的可以表示为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;name&quot;: &quot;deep_learning_training&quot;,
    &quot;image&quot;: &quot;nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04&quot;,
    &quot;command&quot;: [&quot;python&quot;, &quot;/app/train.py&quot;],
    &quot;resources&quot;: {
        &quot;gpu&quot;: 2,
        &quot;cpu&quot;: 8,
        &quot;memory&quot;: &quot;16Gi&quot;
    },
    &quot;env&quot;: {
        &quot;BATCH_SIZE&quot;: &quot;64&quot;,
        &quot;LEARNING_RATE&quot;: &quot;0.001&quot;
    },
    &quot;volumes&quot;: [
        {
            &quot;name&quot;: &quot;data-volume&quot;,
            &quot;hostPath&quot;: &quot;/data&quot;,
            &quot;mountPath&quot;: &quot;/app/data&quot;
        },
        {
            &quot;name&quot;: &quot;model-output&quot;,
            &quot;hostPath&quot;: &quot;/outputs&quot;,
            &quot;mountPath&quot;: &quot;/app/outputs&quot;
        }
    ],
    &quot;timeout&quot;: &quot;24h&quot;,
    &quot;priority&quot;: &quot;high&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为每个作业分配资源，需要考虑如下的三个问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;如何提交作业与解决环境依赖问题？&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如何高效调度作业并分配资源？&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如何保证不同作业之间不会冲突? 例如不同的作业用了某一变量的相同名称或者同一个存储地址&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;作业、镜像与容器&lt;/h1&gt;
&lt;h2&gt;可能存在的问题&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 作业环境依赖问题&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;问题：
&lt;ul&gt;
&lt;li&gt;服务器上有没有预装好的需要的个性化环境？&lt;/li&gt;
&lt;li&gt;不同作业需要的框架，依赖和版本不同，安装繁琐且重复，如何解决？&lt;/li&gt;
&lt;li&gt;部署服务器上可能有大量重复安装的库，占用空间，如何解决？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;目标：
&lt;ul&gt;
&lt;li&gt;复用已有的环境，避免重复安装。&lt;/li&gt;
&lt;li&gt;层级化存储，避免重复存储。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 作业运行时资源隔离问题&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;问题：
&lt;ul&gt;
&lt;li&gt;集群资源被共享，如何保证不同作业之间不会冲突且不多占资源？&lt;/li&gt;
&lt;li&gt;如何能够让不同作业可以运行不同的操作系统和命名空间？&lt;/li&gt;
&lt;li&gt;如何保证隔离的同时，作业启动速度越快越好？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;目标：
&lt;ul&gt;
&lt;li&gt;资源隔离，避免冲突。&lt;/li&gt;
&lt;li&gt;资源共享，避免浪费。&lt;/li&gt;
&lt;li&gt;启动速度快，避免等待。&lt;/li&gt;
&lt;li&gt;资源使用率高，避免空闲。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;虚拟化容器技术——Docker&lt;/h2&gt;
&lt;p&gt;2013 年 Docker 公司的一个开源项目改写了云计算产业软件交付的历史进程。通过容器镜像，直接将一个应用运行所需的完整环境，即整个操作系统的文件系统也打包了进去，从而实现了“一次发布、随处运行”。&lt;/p&gt;
&lt;p&gt;Docker 容器是一个轻量级、可执行的独立软件包，包含运行某个软件所需的&lt;strong&gt;所有代码、库、依赖项和配置文件&lt;/strong&gt;。&lt;strong&gt;Docker 容器是基于镜像创建的&lt;/strong&gt;，镜像是一个只读的模板，包含了运行某个应用所需的所有文件和环境。Docker 镜像可以在不同的操作系统上运行，而不需要担心环境依赖问题。&lt;/p&gt;
&lt;p&gt;这样的设计，使得 Docker 可以用于成果交付，例如，乙方使用 torch 和 Ubuntu 编写了代码，将代码打包成 Docker 镜像，甲方只需要拉取镜像即可运行，即使甲方只有 tensorflow 和 centos。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;轻量级：&lt;/strong&gt; 一台机器上运行的多个容器可以共享操作系统内核，镜像通过文件系统分层进行构造，共享公共文件，降低磁盘用量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标准化：&lt;/strong&gt; 容器基于开放式标准，能够在主流 Linux、Windows、和云在内的任何基础设施上运行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一致的运行环境：&lt;/strong&gt; Docker 镜像提供了完整的运行时环境，不用担心平台迁移时运行环境的变化导致应用无法正常运行的情况&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;快速启动：&lt;/strong&gt; 可做到秒级、甚至毫秒级的启动时间&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隔离性：&lt;/strong&gt; 解决了公用服务器中，资源易受到其他用户的影响问题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;assets/docker2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Docker 镜像的分层原理&lt;/h3&gt;
&lt;p&gt;Docker 镜像是由多个层组成的，&lt;strong&gt;每一层都是一个只读的文件系统&lt;/strong&gt;。每一层都包含了对上一层的修改，例如添加、删除或修改文件。Docker 使用联合文件系统（Union File System）来将这些层组合在一起，形成一个完整的文件系统视图。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;典型 Linux文件系统由 bootfs 和 rootfs 两部分组成&lt;/th&gt;
&lt;th&gt;Docker将其他layer加载其上，且每层都是只读 rootfs结构&lt;/th&gt;
&lt;th&gt;相同的base镜像可以共享，只有顶层的容器层可以读写&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;assets/docker33.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/docker11.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/docker22.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;多个容器共享一份基础镜像，某个容器的修改会被限制在单个容器内，使用 &lt;strong&gt;Copy-on-Write&lt;/strong&gt; 的技术实现，即，修改现有数据会先从镜像层将数据复制到容器层，修改后的数据直接保存在容器层中，而镜像层不会改变。&lt;/p&gt;
&lt;p&gt;讲白了，镜像与容器的关系就像是一个类与对象的关系，镜像是一个类，容器是这个类的实例。&lt;strong&gt;镜像是只读的，而容器是可读可写的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
\text{容器} = \text{镜像} + \text{可读可写的层}
$$&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&apos;assets/docker3.png&apos; width=&apos;60%&apos; alt=&apos;&apos;&amp;gt;&lt;/p&gt;
&lt;h3&gt;编写 Dockerfile， 创建镜像&lt;/h3&gt;
&lt;p&gt;以下是一个具体的 Dockerfile 示例，展示了不同指令如何创建镜像层&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 第1层：基础镜像层
FROM ubuntu:20.04

# 第2层：更新软件源并安装依赖
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y \
    python3 \
    python3-pip \
    git

# 第3层：设置工作目录
WORKDIR /app
# /app 容器内部的路径，作用是创建容器的工作目录 /app

# 第4层：复制依赖文件
COPY requirements.txt .  
# requirements.txt 文件来自 Host device
# 与此 Dockerfile 在同一目录中
# 作用是将主机的 requirements.txt 拷贝到容器的 /app/requirements.txt 

# 第5层：安装Python依赖
RUN pip3 install --no-cache-dir -r requirements.txt

# 第6层：复制源代码
COPY src/ .
# src 文件夹来自 Host device
# 与此 Dockerfile 在同一目录中
# 作用是将主机的 src/ 拷贝到容器的 /app/

# 设置环境变量(不会创建新层，只是元数据)
ENV MODEL_PATH=/app/models

# 设置暴露端口(不会创建新层，只是元数据)
EXPOSE 8000
# 暴露容器内部的端口 8000
# 作用是声明容器会监听 8000 端口

# 设置启动命令(不会创建新层，只是元数据)
CMD [&quot;python3&quot;, &quot;app.py&quot;]
# 在容器的 /app 目录下执行
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;创建镜像层的指令：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FROM&lt;/code&gt;: 指定基础镜像，创建第一层&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RUN&lt;/code&gt;: 执行命令并创建新层&lt;/li&gt;
&lt;li&gt;&lt;code&gt;COPY&lt;/code&gt;/&lt;code&gt;ADD&lt;/code&gt;: 复制文件到容器并创建新层&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WORKDIR&lt;/code&gt;: 更改工作目录，创建新层&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;不创建新层的指令(仅添加元数据)：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ENV&lt;/code&gt;: 设置环境变量&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EXPOSE&lt;/code&gt;: 声明容器监听的端口&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LABEL&lt;/code&gt;: 添加元数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CMD&lt;/code&gt;: 指定容器启动时执行的命令&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ENTRYPOINT&lt;/code&gt;: 设置容器入口点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当执行&lt;code&gt;docker build&lt;/code&gt;命令时，Docker会按顺序执行Dockerfile中的每条指令，并为创建层的指令生成一个新的镜像层。这种分层存储的方式使得镜像可以被高效共享和重用。&lt;/p&gt;
&lt;p&gt;需要注意的是创建此镜像时，Dockerfile 所在目录中必须有 &lt;code&gt;requirements.txt&lt;/code&gt; 和 &lt;code&gt;src/&lt;/code&gt;，否则会导致创建出错。如果没有相应文件，需要删除对应层级构建的指令。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;项目目录/
├── Dockerfile
├── requirements.txt      # COPY requirements.txt . 会复制这个文件
├── src/                 # COPY src/ . 会复制这个文件夹
│   ├── app.py
│   ├── models/
│   └── utils/
└── 其他文件...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;构建完成后容器内部目录结构应为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/app/
├── requirements.txt      # 从主机复制过来的依赖文件
├── app.py                # 从主机的 src/app.py 复制过来的
├── models/               # 从主机的 src/models/ 复制过来的文件夹
│   └── (models 文件夹内的具体文件和子文件夹)
└── utils/                # 从主机的 src/utils/ 复制过来的文件夹
    └── (utils 文件夹内的具体文件和子文件夹)

# 其他由基础镜像 ubuntu:20.04 自带的系统目录，例如：
# /bin/
# /etc/
# /home/
# /lib/
# /opt/
# /root/
# /sbin/
# /usr/
# /var/
# ... 等等
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example: 模型训练容器&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;配置：Dockerfile&lt;/li&gt;
&lt;li&gt;生成镜像&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo docker build -t my_model_training_image .
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;运行容器&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo docker run -it --gpus all --name my_model_training_container -v /data:/app/data -v /outputs:/app/outputs my_model_training_image
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例，训练MNIST分类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
# 基于CUDA 11.8和Ubuntu 22.04的基础镜像
# 安装Python和必要的库
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y \
    python3 \
    python3-pip \
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

# 设置工作目录
WORKDIR /app

# 安装PyTorch和相关依赖
RUN pip3 install --no-cache-dir \
    torch==2.0.0+cu118 \
    torchvision==0.15.0+cu118 \
    torchaudio==2.0.0 \
    --extra-index-url https://download.pytorch.org/whl/cu118 \
    matplotlib \
    tqdm \
    tensorboard

# 复制训练脚本
COPY train.py /app/train.py

# 创建数据和输出目录
RUN mkdir -p /app/data /app/outputs

# 设置环境变量
ENV PYTHONUNBUFFERED=1
ENV BATCH_SIZE=64
ENV LEARNING_RATE=0.001
ENV NUM_EPOCHS=10

# 容器启动时执行的命令
CMD [&quot;python3&quot;, &quot;/app/train.py&quot;]

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Docker 常用命令&lt;/h3&gt;
&lt;p&gt;Docker 提供了丰富的命令行工具来管理容器和镜像。以下是一些最常用的 Docker 命令&lt;/p&gt;
&lt;h4&gt;镜像管理&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 从Docker Hub拉取镜像
docker pull [镜像名称]:[标签]
# 例如：docker pull ubuntu:20.04

# 列出本地镜像
docker images

# 删除镜像
docker rmi [镜像ID或名称]

# 构建镜像
docker build -t [镜像名称]:[标签] [Dockerfile所在目录]
# 例如：docker build -t my-app:1.0 .
# 或者自己指定 Dockerfile 文件: docker build -f Dockerfile -t train_dk_cpu .

# 为镜像添加标签
docker tag [源镜像]:[标签] [目标镜像]:[标签]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;容器管理&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 创建并启动容器
docker run [选项] [镜像名称] [命令]
# 常用选项:
# -d: 后台运行
# -p 主机端口:容器端口: 端口映射
# -v 主机目录:容器目录: 挂载卷
# --name: 指定容器名称
# --rm: 容器停止后自动删除
# --gpus all: 使用所有GPU
# -it: 进入容器 
# 例如：docker run -d -p 8080:80 --name web nginx

# 列出运行中的容器
docker ps
# 列出所有容器(包括已停止的)
docker ps -a

# 启动/停止/重启容器
docker start/stop/restart [容器ID或名称]

# 删除容器
docker rm [容器ID或名称]
# 强制删除：docker rm -f [容器ID或名称]

# 进入容器交互式终端
docker exec -it [容器ID或名称] bash
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;数据卷和网络&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 创建数据卷
docker volume create [卷名]

# 查看数据卷
docker volume ls

# 创建网络
docker network create [网络名]

# 查看网络
docker network ls
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;日志和监控&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 查看容器日志
docker logs [容器ID或名称]
# 实时查看：docker logs -f [容器ID或名称]

# 查看容器资源使用情况
docker stats [容器ID或名称]

# 查看容器详细信息
docker inspect [容器ID或名称]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;清理资源&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 删除所有已停止的容器
docker container prune

# 删除未被使用的镜像
docker image prune

# 删除未被使用的数据卷
docker volume prune

# 删除所有未使用的资源(容器、镜像、网络、卷)
docker system prune
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;高级应用&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 保存镜像为tar文件
docker save -o [文件名.tar] [镜像名称]

# 从tar文件加载镜像
docker load -i [文件名.tar]

# 查看镜像构建历史
docker history [镜像名称]

# 将容器保存为新镜像
docker commit [容器ID] [新镜像名称]:[标签]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些命令覆盖了 Docker 日常使用中的大部分场景。通过组合使用这些命令，可以方便地管理 Docker 环境下的容器和镜像。&lt;/p&gt;
&lt;h1&gt;调度性能指标&lt;/h1&gt;
&lt;p&gt;为例评价调度好坏，有如下性能指标。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;吞吐量 (Throughput)：&lt;/strong&gt; 单位时间内完成的作业数量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完工时间 (Makespan) / 平均响应时间 (Average Response Time)：&lt;/strong&gt; 作业从提交到完成的时间&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;公平性 (Fairness)：&lt;/strong&gt; 不同作业之间的资源分配是否公平&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源利用率 (Resource Utilization)：&lt;/strong&gt; 集群资源的使用效率&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务等级协议 (SLA) 满足率：&lt;/strong&gt; 满足用户需求的比例&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;公平性&lt;/h2&gt;
&lt;p&gt;为了满足公平性和资源利用率的要求，调度算法需要考虑多个因素，例如作业的优先级、资源需求、作业的运行时间等。&lt;/p&gt;
&lt;h3&gt;单资源调度——最大最小公平 (Max-Min Fairness) 算法&lt;/h3&gt;
&lt;p&gt;最大最小公平(Max-Min Fairness)算法是一种经典的资源分配策略，旨在实现&quot;公平&quot;的资源分配。其核心思想是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;优先分配给需求最小的用户&lt;/strong&gt;：首先满足资源需求最小的用户&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;均等分配剩余资源&lt;/strong&gt;：如果资源有剩余，则将剩余资源均等地分配给其他用户&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;迭代过程&lt;/strong&gt;：持续这个过程直到所有资源分配完毕或所有用户需求都得到满足&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;算法步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对所有用户按资源需求量排序&lt;/li&gt;
&lt;li&gt;从需求最小的用户开始分配资源&lt;/li&gt;
&lt;li&gt;如果满足了某用户的需求后还有剩余资源，则平均分配给剩余用户&lt;/li&gt;
&lt;li&gt;重复直到资源耗尽或所有用户都满足&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保证了资源分配的公平性&lt;/li&gt;
&lt;li&gt;避免了&quot;饥饿&quot;现象&lt;/li&gt;
&lt;li&gt;简单易实现&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可能不够高效，因为优先满足小需求可能导致整体吞吐量降低&lt;/li&gt;
&lt;li&gt;不考虑作业优先级和截止时间等因素&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;主导资源公平 (Dominant Resource Fairness) 算法&lt;/h4&gt;
&lt;p&gt;主导资源公平(Dominant Resource Fairness, DRF)算法是为多资源环境设计的公平分配策略，解决了在异构计算环境中资源分配的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本原理：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;主导资源识别&lt;/strong&gt;：对每个用户，确定其主导资源（需求占总资源比例最高的资源）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源分配&lt;/strong&gt;：基于主导资源的份额公平分配资源&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态调整&lt;/strong&gt;：随着资源使用情况变化不断调整分配&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;算法步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算每个用户对各类资源的需求占总资源的比例&lt;/li&gt;
&lt;li&gt;确定每个用户的主导资源及其比例&lt;/li&gt;
&lt;li&gt;选择主导资源份额最小的用户进行资源分配&lt;/li&gt;
&lt;li&gt;更新资源状态，重复步骤1-3&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;适用于多资源环境（如GPU、CPU、内存混合需求）&lt;/li&gt;
&lt;li&gt;满足策略无羡慕性、共享激励和Pareto效率&lt;/li&gt;
&lt;li&gt;防止资源囤积，提高整体利用率&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;计算复杂度较高&lt;/li&gt;
&lt;li&gt;对资源需求变化频繁的场景响应可能不够灵敏&lt;/li&gt;
&lt;li&gt;可能需要与其他策略结合使用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;例子&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;User A&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;需求: &lt;code&gt;&amp;lt;1 GPU, 4 RAM&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;GPU 份额: 1/9 (占总GPU)&lt;/li&gt;
&lt;li&gt;RAM 份额: 4/18 = 2/9 (占总RAM)&lt;/li&gt;
&lt;li&gt;由于  &lt;code&gt;1/9 (GPU) &amp;lt; 2/9 (RAM)&lt;/code&gt;，User A 的主导资源是 &lt;strong&gt;RAM&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User B&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;需求: &lt;code&gt;&amp;lt;3 GPU, 1 RAM&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;GPU 份额: 3/9 = 1/3 (占总GPU)&lt;/li&gt;
&lt;li&gt;RAM 份额: 1/18 (占总RAM)&lt;/li&gt;
&lt;li&gt;由于 &lt;code&gt;1/3 (GPU) &amp;gt; 1/18 (RAM)&lt;/code&gt;，User B 的主导资源是 &lt;strong&gt;GPU&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;基于最大最小公平 (max-min fairness) 的针对多资源类型 (e.g. GPU, CPU) 的调度算法&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Objective:&lt;/strong&gt;
$$ \max (x, y) \quad \text{(Maximize allocations)} $$
&lt;strong&gt;Subject to:&lt;/strong&gt;
$$ x + 3y \le 9 \quad \text{(GPU constraint)} $$
$$ 4x + y \le 18 \quad \text{(Memory constraint)} $$
$$ \frac{2x}{9} = \frac{y}{3} \quad \text{(Equalize dominant shares)} $$&lt;/p&gt;
&lt;p&gt;(其中，假设 x 是分配给User A的任务数，y 是分配给User B的任务数。总GPU为9，总RAM为18。User A每个任务需求&amp;lt;1GPU, 4RAM&amp;gt;，User B每个任务需求&amp;lt;3GPU, 1RAM&amp;gt;。User A的主导资源是RAM，其单位任务主导资源份额是4/18 = 2/9。User B的主导资源是GPU，其单位任务主导资源份额是3/9 = 1/3。因此，均衡主导份额的约束是 &lt;code&gt;(2/9) * x = (1/3) * y&lt;/code&gt;，简化后即 &lt;code&gt;2x/9 = y/3&lt;/code&gt;。)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Schedule&lt;/th&gt;
&lt;th&gt;User A res.shares&lt;/th&gt;
&lt;th&gt;User A dom.share&lt;/th&gt;
&lt;th&gt;User B res.shares&lt;/th&gt;
&lt;th&gt;User B dom.share&lt;/th&gt;
&lt;th&gt;GPU total alloc.&lt;/th&gt;
&lt;th&gt;RAM total alloc.&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User B&lt;/td&gt;
&lt;td&gt;&amp;lt;0, 0&amp;gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;3/9, 1/18&amp;gt;&lt;/td&gt;
&lt;td&gt;1/3&lt;/td&gt;
&lt;td&gt;3/9&lt;/td&gt;
&lt;td&gt;1/18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User A&lt;/td&gt;
&lt;td&gt;&amp;lt;1/9, 4/18&amp;gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2/9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;3/9, 1/18&amp;gt;&lt;/td&gt;
&lt;td&gt;1/3&lt;/td&gt;
&lt;td&gt;4/9&lt;/td&gt;
&lt;td&gt;5/18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User A&lt;/td&gt;
&lt;td&gt;&amp;lt;2/9, 8/18&amp;gt;&lt;/td&gt;
&lt;td&gt;4/9&lt;/td&gt;
&lt;td&gt;&amp;lt;3/9, 1/18&amp;gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1/3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5/9&lt;/td&gt;
&lt;td&gt;9/18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User B&lt;/td&gt;
&lt;td&gt;&amp;lt;2/9, 8/18&amp;gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4/9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;6/9, 2/18&amp;gt;&lt;/td&gt;
&lt;td&gt;2/3&lt;/td&gt;
&lt;td&gt;8/9&lt;/td&gt;
&lt;td&gt;10/18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User A&lt;/td&gt;
&lt;td&gt;&amp;lt;3/9, 12/18&amp;gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2/3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;6/9, 2/18&amp;gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2/3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;14/18&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;拓扑与亲和性 (Affinity)&lt;/h3&gt;
&lt;p&gt;在集群调度中，&lt;strong&gt;拓扑 (Topology)&lt;/strong&gt; 指的是计算资源（如CPU、GPU、内存、网络接口）的物理或逻辑组织方式，例如它们如何连接在同一个PCIe交换机、CPU插槽、节点（服务器）或机架上。&lt;strong&gt;亲和性 (Affinity)&lt;/strong&gt; 或 &lt;strong&gt;局部性 (Locality)&lt;/strong&gt; 描述了分配给单个作业的多个资源单元在物理拓扑上的接近程度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 调度的拓扑倾向性 (Sensitivity to Locality)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;多GPU作业的性能高度依赖于所分配GPU之间的亲和性。换句话说，GPU之间的通信效率受到它们物理位置和连接方式的显著影响。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;示例&lt;/strong&gt;：一个需要8个GPU的作业，如果这8个GPU都位于同一个节点（服务器）内，并且通过高速互联（如NVLink或QPI）连接，其运行速度通常会远快于这8个GPU分布在8个不同节点上，通过普通网络连接的情况。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内部服务器局部性 (Intra-server locality)&lt;/strong&gt;：如下图1所示，即使在同一服务器内部，GPU位于同一个PCIe交换机下（SamePCIeSw）的性能通常优于位于同一CPU插槽但不同PCIe交换机（SameSocket），而这又优于位于不同CPU插槽（DiffSocket）的情况。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务器间局部性 (Inter-server locality)&lt;/strong&gt;：如下图2所示，对于需要多个GPU的作业，将它们集中在少数节点（如一个节点内的4个GPU）通常比分散在多个节点（如4个节点各1个GPU）性能更好。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;img src=&quot;assets/affinity1.png&quot; alt=&quot;调度的拓扑倾向性&quot;&amp;gt;
&lt;em&gt;图注：左图为服务器内局部性对VGG16和ResNet-50性能的影响，右图为服务器间局部性对ResNet-50和InceptionV3性能的影响。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 共享异常 (Sharing Anomaly) 与拓扑忽视的代价&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果调度系统仅基于数量（例如Quota机制）分配GPU，而忽略了拓扑结构，即使为任务分配了足够数量的GPU，也可能因为GPU分布不佳（例如跨CPU Socket、跨PCIe Switch甚至跨机器）导致大量的通信开销和外部碎片，从而造成性能大幅下降。这种现象被称为“共享异常”。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;例如&lt;/strong&gt;：一个作业的多个GPU如果需要频繁跨PCIe交换机通信，可能导致50%的性能下降；如果需要跨机器通信，性能下降可能达到5倍甚至更多。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 考虑拓扑的调度策略 (如 HiveD)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了解决上述问题，先进的调度算法会考虑集群的拓扑结构和作业的亲和性需求。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;层级化资源定义 (Levels and Cells)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;层级 (Levels)&lt;/strong&gt;：将资源按拓扑关系划分为不同层级，例如：
&lt;ul&gt;
&lt;li&gt;Level 1: 单个GPU&lt;/li&gt;
&lt;li&gt;Level 2: 连接到同一PCIe交换机的GPU组&lt;/li&gt;
&lt;li&gt;Level 3: 连接到同一CPU Socket的GPU组&lt;/li&gt;
&lt;li&gt;Level 4: 整个节点（服务器）上的所有GPU&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单元 (Cell)&lt;/strong&gt;：资源分配的基本粒度，它代表了一组具有特定互联拓扑和亲和性的GPU集合。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;伙伴单元 (Buddy Cell)&lt;/strong&gt;：同一层级中的单元，可以合并或分裂以满足不同大小的资源请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;img src=&quot;assets/HiveD.png&quot; alt=&quot;HiveD调度算法层级定义与机架示意图&quot;&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分配策略&lt;/strong&gt;：调度器（如HiveD）会尽可能从能够满足作业需求的最高层级（high-level，即亲和性最好的层级）分配资源。例如，如果一个作业需要4个GPU，调度器会优先尝试在同一个Level 3（同一CPU Socket）甚至Level 4（同一节点）找到一个包含4个GPU的Cell。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Buddy Cell Allocation Algorithm (伙伴单元分配算法)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是一种管理层级化资源的具体算法，旨在高效分配和回收具有拓扑亲和性的资源单元。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;维护信息&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;绑定关系 (Binding)：单元之间的拓扑连接关系。&lt;/li&gt;
&lt;li&gt;每层级的空闲列表 (free list)：记录每个层级可用的资源单元。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分配过程 (ALLOCATECELL)&lt;/strong&gt;：
&lt;ol&gt;
&lt;li&gt;如果当前层级没有足够大的空闲单元，则尝试从更高一级（cell_level+1）分配一个更大的单元。&lt;/li&gt;
&lt;li&gt;将这个更大的单元分裂 (Split) 成多个较小的“伙伴单元 (buddies)”。&lt;/li&gt;
&lt;li&gt;将分裂后的单元加入当前层级的空闲列表。&lt;/li&gt;
&lt;li&gt;返回一个满足需求的单元。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;释放过程 (RELEASECELL)&lt;/strong&gt;：
&lt;ol&gt;
&lt;li&gt;检查被释放单元的伙伴单元是否也在空闲列表中。&lt;/li&gt;
&lt;li&gt;如果是，则将它们合并 (Merge) 成一个更高层级的单元，并递归地尝试释放这个合并后的单元。&lt;/li&gt;
&lt;li&gt;如果伙伴单元不空闲，则直接将被释放单元加入当前层级的空闲列表。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化目标与效果&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;目标：尽可能保持更多的高层级单元可用，将可用单元维持在尽可能高的层级。&lt;/li&gt;
&lt;li&gt;效果：减少GPU碎片，为需要高层级（即高亲和性）单元的作业创造更多调度机会。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;img src=&quot;assets/buddy.png&quot; alt=&quot;Buddy Cell Allocation Algorithm&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;通过综合考虑拓扑结构和作业的亲和性需求，调度系统可以显著提升集群的整体性能和资源利用率，特别是对于通信密集型的分布式训练任务。&lt;/p&gt;
&lt;h3&gt;灵活性 (flexibility)&lt;/h3&gt;
&lt;p&gt;调度的灵活性主要体现在两个方面：弹性 (elasticity) 和抢占 (preemption)。一个灵活的调度系统能够更好地适应动态变化的负载，提高资源利用率并保障服务等级协议 (SLA)。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;弹性 (elasticity)&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义：当集群有空闲资源时，调度器允许作业或队列使用超出其配额 (quota) 的资源。当集群资源紧张时，这些超额使用的资源可以被回收。&lt;/li&gt;
&lt;li&gt;目的：提高整体资源利用率。如上图“不使用抢占调度”所示，App1结束后，App2可以利用更多资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;抢占 (preemption)&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义：当集群中没有未使用的资源，且一些优先级较高或未满足需求的队列（如上图中的App2）请求新资源时，集群会从那些已经消耗了超过其配置容量的队列（如上图中的App1）中回收资源。&lt;/li&gt;
&lt;li&gt;目的：保障高优先级作业的SLA，确保关键任务及时获得资源。&lt;/li&gt;
&lt;li&gt;效果：
&lt;ul&gt;
&lt;li&gt;如上图“使用抢占调度”所示，App1与App2均享有50%的资源配额。&lt;/li&gt;
&lt;li&gt;App2（可能为高优先级或有SLA需求）能够更快结束 (T3&apos; &amp;lt; T3)，从而提高了SLA满足率。&lt;/li&gt;
&lt;li&gt;App1的完成时间虽然有所推迟 (T2&apos; &amp;gt; T2)，但由于其在初期可能已经获得了较多资源，这种变慢通常是可以接受的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;这是一种兼顾了弹性和SLA的调度方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;抢占的实现机制&lt;/h4&gt;
&lt;p&gt;不同的调度系统有不同的抢占实现方式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. HiveD中的抢占实现&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HiveD 是一种考虑拓扑和虚拟集群 (VC) 的调度器，其抢占机制设计如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;高优先级抢占低优先级&lt;/strong&gt;：允许优先级高的任务抢占优先级低的任务所占用的资源单元 (cell)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隔离低优先级单元&lt;/strong&gt;：低优先级任务的单元通常被分配到远离高优先级任务绑定（或经常使用）的物理区域（例如，尽量避免分配给低优先级任务那些容易形成“伙伴单元”的资源），以降低它们被高优先级任务抢占的几率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免在高优设备上分配低优单元&lt;/strong&gt;：高优先级作业也尽量避免在已经运行了低优先级作业的物理设备上分配新的单元，以减少潜在的干扰和抢占开销。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;虚拟私有集群 (Virtual Private Clusters, VPCs)&lt;/strong&gt;：租户（Tenant A, Tenant B）在各自的VPC视图中操作，第三方调度器基于此视图进行调度。物理集群则动态地分配和回收物理资源单元。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Hadoop YARN中的抢占实现&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hadoop YARN (Yet Another Resource Negotiator) 是大数据生态中广泛使用的资源管理系统，其抢占实现步骤如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 1: 识别可抢占容器&lt;/strong&gt;：从过度使用其队列配额的队列中，识别出可以被抢占的容器。例如，下图中Queue A配置了10个单位的资源但已使用了30个，而Queue C配置了20个单位但仍有20个单位的请求处于等待（Pending）状态。此时，App1（属于Queue A）的容器C6、C7可能被标记为可抢占。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 2: 通知Application Master&lt;/strong&gt;：通知相关作业的Application Master，告知其部分容器即将被抢占，以便Application Master可以采取相应措施（如保存状态）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 3: 强制终止容器&lt;/strong&gt;：Resource Manager 强行杀死（kill）那些被标记为抢占的容器，释放资源给等待的更高优先级或未满足需求的作业。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;抢占的代价 (Cost)&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;尤其在深度学习这类长周期、有状态的作业中，被抢占的作业当前可能只能失败。&lt;/li&gt;
&lt;li&gt;如果作业没有很好地实现检查点 (checkpoint) 机制，那么从上一个检查点到被抢占时刻之间的所有训练成果都将被丢弃，造成计算资源的浪费。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，在设计抢占策略时，需要权衡SLA保障、资源利用率以及抢占带来的开销和对作业的影响。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;参考资料&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.docker.com/get-started/&quot;&gt;Docker 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>AI-Sys 4 分布式算法与分布式训练</title><link>https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter4/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter4/</guid><pubDate>Fri, 23 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;分布式算法与分布式训练&lt;/p&gt;
&lt;h1&gt;并行计算加速定律&lt;/h1&gt;
&lt;p&gt;并行计算加速定律有两个&lt;/p&gt;
&lt;h2&gt;1. Amdahl 定律&lt;/h2&gt;
&lt;p&gt;假设程序执行总运行时间为不可并行部分与可并行部分之和， 即 $T_1 = W_{ser} + W_{par}$。设共有 $s$ 个并行执行单元，并行加速后的执行时间为，$T_p \ge W_{ser} + W_{par} / s$，这里取大于号是因为并行执行单元之间通信存在时间开销。定义加速比为
$$
\large
S_\text{latency}(s) = \frac{T_1}{T_p}\le \frac{W_{ser} + W_{par}}{W_{ser} + W_{par} / s}
$$&lt;/p&gt;
&lt;p&gt;令 $p$ 为 $T_1$ 中可并行部分的占比，则
$$
\large
S_{\text{latency}}(s) = \frac{1}{(1 - p) + \frac{p}{s}} \rightarrow \frac{1}{1 - p} (s \rightarrow \infty)
$$
此时加速比为 $1/(1 - p)$，这意味着即使 $s$ 无限大，$S_{\text{latency}}(s)$ 也不会超过 $1/(1 - p)$。&lt;/p&gt;
&lt;p&gt;$$
\large
S_{\text{latency}}(s) = \frac{1}{(1 - p) + \frac{p}{s}}
$$&lt;/p&gt;
&lt;p&gt;Amdahl 定律说明了并行计算的加速存在上限。&lt;/p&gt;
&lt;h2&gt;2. Gustafson定律&lt;/h2&gt;
&lt;p&gt;Amdahl 假设问题的规模是固定的，具有局限性。实际上随着并行度的增加，可解的问题的规模也在增加。假设在拥有 $s$ 个并行执行单元的机器上，总的执行时间为 $T_1 = W_{ser} + W_{par}$, 则在单处理器上，相同规模问题的执行时间为 $T_s = W_{ser} + sW_{par}$ , 加速比为
$$
\large
S_{\text{latency}}(s) = \frac{T_s}{T_1} = \frac{W_{ser} + sW_{par}}{W_{ser} + W_{par}} = \frac{(1 - p)T_1 + spT_1}{T_1} = 1 - p + sp
$$
这里假设的是问题的规模随着并行的增加也在增加。此时得到的加速比是关于 $s$ 的线性函数，随着 $s$ 的增加而增加。&lt;/p&gt;
&lt;p&gt;Gustafson 定律则是揭示了线性加速比的可能性，在问题的规模足够大的时候。&lt;/p&gt;
&lt;h2&gt;分布式计算的意义&lt;/h2&gt;
&lt;p&gt;深度学习训练耗时与训练数据规模、单步计算量、计算速率有关
$$
\text{深度学习训练耗时} = \frac{\text{训练数据规模} \times \text{单步计算量}}{\text{计算速率}}&lt;br /&gt;
$$
而其中计算速率则受单设备计算速率、设备数和并行效率影响
$$
\text{计算速率} = \text{单设备计算速率} \times \text{设备数} \times \text{并行效率}
$$&lt;/p&gt;
&lt;p&gt;为例降低深度学习的训练耗时，其中最容易改变的因子是&lt;strong&gt;设备数和并行效率&lt;/strong&gt;，因为其他因素要么与模型本身相关，不便于改变，要么受到 Moore 定律的限制，难以大幅提升。从此也就得出并行计算的意义所在。&lt;/p&gt;
&lt;h1&gt;分布式算法&lt;/h1&gt;
&lt;p&gt;分布式算法是指在多个处理器上运行且没有非常严格的中心控制的算法。&lt;/p&gt;
&lt;p&gt;分布式系统是指有一系列独立计算设备组成的一个在用户眼里看起来就像是一个设备的协同系统。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;并行化的方案可以分为两大类&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;算子内并行：&lt;/strong&gt; 在单个算子内部进行并行化计算，将一个算子的计算任务分解到多个处理器单元上同时执行。例如一个卷积可以使用不同的 GPU 核心共同完成计算&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;算子间并行&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;模型并行：&lt;/strong&gt; 将一个模型的各个部分拆分到不同的设备上，每个处理器并行计算自己需要处理的部分&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据并行：&lt;/strong&gt; 将相同的模型复制到多个设备上，每个设备并行处理不同的数据批次&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;算子内并行的部分在计算机体系结构部分已经阐述。下面主要讲述算子间并行&lt;/p&gt;
&lt;h2&gt;模型并行：将模型计算图划分至不同的设备上执行&lt;/h2&gt;
&lt;p&gt;&amp;lt;img src=&apos;assets/model_paral.png&apos; width=&apos;30%&apos; alt=&apos;模型并行示意图&apos;&amp;gt;&lt;/p&gt;
&lt;h3&gt;Naïve 模型并行&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 模型并行的AlexNet实现
class ModelParallelAlexNet(nn.Module):
    def __init__(self):
        super(ModelParallelAlexNet, self).__init__()
        # 第一部分放在cuda:0上
        self.features_0 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2)
        ).to(&apos;cuda:0&apos;)
        
        # 第二部分放在cuda:1上
        self.features_1 = nn.Sequential(
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        ).to(&apos;cuda:1&apos;)
        
        # 分类器部分也放在cuda:1上
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, 1000),
        ).to(&apos;cuda:1&apos;)
    
    def forward(self, x):
        # 假设输入x在cuda:0上
        x = self.features_0(x)
        # 将x从cuda:0传输到cuda:1
        x = x.to(&apos;cuda:1&apos;)
        # 继续在cuda:1上进行计算
        x = self.features_1(x)
        x = x.view(x.size(0), 256 * 6 * 6)
        x = self.classifier(x)
        return x

# 训练示例
def train_model_parallel(model, data_loader, criterion, optimizer):
    model.train()
    for inputs, labels in data_loader:
        inputs = inputs.to(&apos;cuda:0&apos;) # 将输入数据放在第一个GPU上
        labels = labels.to(&apos;cuda:1&apos;) # 将标签放在输出所在的GPU上

        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;事实上，&lt;strong&gt;该模型并行的实现方式并不一定能让训练时间减少为一半&lt;/strong&gt;，这种方法的时间开销甚至会比不并行训练的时间更长，因为在任何时刻总有一块设备处于空闲状态，但是在 GPU 之间传播张量的通信却有额外耗时开销。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&apos;assets/paral1.png&apos; width=&apos;60%&apos; alt=&apos;&apos;&amp;gt;&lt;/p&gt;
&lt;p&gt;当数据 batch 在 &lt;code&gt;cuda:0&lt;/code&gt; 时，&lt;code&gt;cuda:1&lt;/code&gt; 中尚没有数据 batch 可供运行；而当 &lt;code&gt;cuda:0&lt;/code&gt; 完成计算，数据 batch 传到 &lt;code&gt;cuda:1&lt;/code&gt; 时，&lt;code&gt;cuda:0&lt;/code&gt; 需等待这个数据 batch 运行结束后，才能有新的数据 batch 传入供 &lt;code&gt;cuda:0&lt;/code&gt; 处理。显然这种实现没有充分利用两个 GPU 的并行能力。&lt;/p&gt;
&lt;h3&gt;流水线 (Pipeline) 的实现方式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;GPipe 算法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;若想高效利用多块 GPU 间的并行能力，流水线无疑是一个解决方案。切分 batch，当前一个 split 经 &lt;code&gt;cuda:0&lt;/code&gt; 处理好后进入 &lt;code&gt;cuda:1&lt;/code&gt; 时，下一个 split 就可以进入 &lt;code&gt;cuda:0&lt;/code&gt; 运行。这种实现方法叫 Gpipe 算法。其特点是所有 splits 前向传播结束后才能进行反向传播，因此在前向传播和后向传播之间存在大量 bubbles 。&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- &lt;img src=&quot;assets/Gpipe1.png&quot; alt=&quot;Gpipe&quot; /&gt; --&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class GPipeAlexNet(nn.Module):
    def __init__(self, split_size):
        super().__init__()
        self.split_size = split_size

        self.features_0 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2)
        ).to(&apos;cuda:0&apos;)
        
        self.features_1 = nn.Sequential(
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        ).to(&apos;cuda:1&apos;)
        
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, 1000),
        ).to(&apos;cuda:1&apos;)
    
    def forward(self, x):
        splits = iter(x.split(self.split_size, dim=0))
        s_prev = self.features_0(next(splits)).to(&apos;cuda:1&apos;) # 初始化计算
        results = []

        for s_next in splits:
            s_prev = self.features_1(s_prev)
            results.append(self.classifier(s_prev.view(s_prev.size(0), -1)))

            s_prev = self.features_0(s_next).to(&apos;cuda:1&apos;)

        s_prev = self.features_1(s_prev)    # 尾部处理
        results.append(self.classifier(s_prev.view(s_prev.size(0), -1)))
        # 将所有分割结果连接起来，保持与单卡输出格式一致
        return torch.cat(results, dim=0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gpipe 算法仅首尾循环中存在 bubbles，大多数迭代过程中两块 GPU 是可以并行执行的。切分的越细这种空洞自然也会越小，但是 splits 不是越多越好，因为传输复制也是耗时的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gpipe 测试代码: &amp;lt;a href=&quot;/blog/resources/deeplearning/Gpipe.py&quot; download=&quot;Gpipe.py&quot;&amp;gt; Gpipe.py &amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;PipeDream 算法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;PipeDream 算法的特点是人为制造反向传播的 bubbles，然后在反向传播的 bubbles 中穿插计算前向计算，从而改进 GPipe 算法前向和反向传播间有大量 Bubles 的不足。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/PipeDream.png&quot; alt=&quot;PipeDream&quot; /&gt;&lt;/p&gt;
&lt;p&gt;PipeDream 与 GPipe不同，不需要等待所有前向传播完成才开始反向传播，而是采用更灵活的 1F1B (one-forward-one-backward) 调度策略，即在前向处理了一个微批次后就可以开始处理该微批次的反向传播。这种策略能更好地填满 bubbles，提升设备利用率。&lt;/p&gt;
&lt;h2&gt;数据并行: 将数据划分至不同的设备上执行&lt;/h2&gt;
&lt;p&gt;数据并行的实现方式是将数据划分至不同的设备上执行相同的计算图。每个设备上都有一份模型参数，前向传播时每个设备上都执行相同的计算图，最后将每个设备上的梯度进行聚合。然后利用聚合后的梯度更新模型。&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&apos;assets/paral2.png&apos; width=&apos;60%&apos; alt=&apos;&apos;&amp;gt;&lt;/p&gt;
&lt;p&gt;数据并行比模型并行更为高效且更加常用。&lt;/p&gt;
&lt;h3&gt;集合通信 (Collective Communication)&lt;/h3&gt;
&lt;p&gt;集合通信是分布式深度学习中多设备间协同工作的基础，指的是在多个计算节点之间同时进行的数据交换操作。不同于点对点通信(只涉及两个节点)，集合通信同时涉及多个节点共同参与的通信模式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;集合通信的主要特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多个节点同时参与通信过程&lt;/li&gt;
&lt;li&gt;通常有特定的通信模式和语义&lt;/li&gt;
&lt;li&gt;为分布式训练提供高效的数据传输方式&lt;/li&gt;
&lt;li&gt;支持跨设备、跨机器的数据同步&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在分布式深度学习中，集合通信主要用于&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;梯度聚合和参数同步&lt;/li&gt;
&lt;li&gt;模型分片和结果合并&lt;/li&gt;
&lt;li&gt;同步不同工作节点的状态&lt;/li&gt;
&lt;li&gt;实现高效的数据并行和模型并行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里介绍一些集合通信的 primitives（原语）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一对多&lt;/strong&gt;：Scatter, Broadcast&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多对一&lt;/strong&gt;：Gather，Reduce&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多对多&lt;/strong&gt;：All-Reduce，All-Gather&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;scatter&lt;/th&gt;
&lt;th&gt;broadcast&lt;/th&gt;
&lt;th&gt;gather&lt;/th&gt;
&lt;th&gt;reduce&lt;/th&gt;
&lt;th&gt;all-gather&lt;/th&gt;
&lt;th&gt;all-reduce&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;主节点把数据分发给各个节点&lt;/td&gt;
&lt;td&gt;某个节点把自身数据传递给全部节点&lt;/td&gt;
&lt;td&gt;不同设备上的数据规约运算&lt;/td&gt;
&lt;td&gt;将所有节点的数据收集到主节点上&lt;/td&gt;
&lt;td&gt;gather + broadcast&lt;/td&gt;
&lt;td&gt;reduce + broadcast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;assets/scatter.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/broadcast.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/gather.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/reduce.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/all-gather.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/all-reduce.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;assets/scatter1.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/broadcast1.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/gather1.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/reduce1.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/all-gather1.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/all-reduce1.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;gather 与 reduce 的区别&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同样是将数据从不同设备上收集到同一个设备上，但是 &lt;strong&gt;gather&lt;/strong&gt; 是收集数据，并不进行处理，但是 &lt;strong&gt;reduce&lt;/strong&gt; 则会将数据进行求和等操作。类似地，&lt;strong&gt;broadcast&lt;/strong&gt; 是将同一份数据分发到不同的设备上，而 &lt;strong&gt;scatter&lt;/strong&gt; 则是将不同的数据分发到不同的设备上。&lt;strong&gt;All-reduce&lt;/strong&gt; 相当于 &lt;strong&gt;reduce + broadcast&lt;/strong&gt;，&lt;strong&gt;All-gather&lt;/strong&gt; 相当于 &lt;strong&gt;gather + broadcast&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;PyTorch 的六种通信原语接口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch.distributed as dist

# 1. scatter
dist.scatter(tensor, scatter_list=None, src=0, group=None, async_op=False)
# 2. gather
dist.gather(tensor, gather_list=None, dst=0, group=None, async_op=False)
# 3. broadcast
dist.broadcast(tensor, src=0, group=None, async_op=False)
# 4. all_gather
dist.all_gather(tensor_list, tensor, group=None, async_op=False)
# 5. all_reduce
dist.all_reduce(tensor, op=dist.ReduceOp.SUM, group=None, async_op=False)
# 6. reduce
dist.reduce(tensor, dst=0, op=dist.ReduceOp.SUM, group=None, async_op=False)
# 7.barrier
dist.barrier(group=None, timeout=timedelta.max)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;点对点通信 (Point-to-Point Communication)&lt;/h3&gt;
&lt;p&gt;点对点通信是分布式系统中两个计算节点之间直接交换数据的通信方式。在分布式深度学习中，它用于在不同设备（如 GPU 或 机器）之间传输模型参数、梯度或中间结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只涉及两个节点：一个发送方和一个接收方&lt;/li&gt;
&lt;li&gt;通信范围局限，不像集合通信那样涉及多个节点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;实现方式：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同步 send/recv，即阻塞式通信，发送端需要等待接收端开始接收数据之后才能结束，发送命令的返回意味着接收端已执行了一定程度的接收工作。双方进程到达一个确定的同步点之后，通信才可以结束。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;def run(rank, size):
    tensor = torch.zeros(1)
    if rank == 0:
        tensor += 1
        dist.send(tensor=tensor, dst=1)
    else:
        dist.recv(tensor=tensor, src=0)
    print(f&quot;Rank {rank} has data {tensor[0]}&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;异步 send/recv，即非阻塞式通信，可以将通信和计算重叠进行。发送指令不要求操作立即执行，从发送数据区取走数据之前命令即可返回，然后在适当时机完成实际发送通信，在 &lt;code&gt;wait()&lt;/code&gt; 时可并发进行数据传输和计算。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;def run(rank, size):
    tensor = torch.zeros(1)
    req = None
    if rank == 0:
        tensor += 1
        req = dist.isend(tensor=tensor, dst=1)
        print(&apos;Rank 0 started sending&apos;)
    else:
        req = dist.irecv(tensor=tensor, src=0)
        print(&apos;Rank 1 started receiving&apos;)
    req.wait()
    print(f&quot;Rank {rank} has data {tensor[0]} after wait&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;参数服务器&lt;/h3&gt;
&lt;p&gt;参数服务器是一种分布式训练的架构，主要用于解决数据并行训练中的参数更新问题。参数服务器的基本思想是将模型参数存储在一个或多个服务器上，工作节点（worker）负责计算梯度并将其发送到参数服务器进行更新。参数服务器可以是集中式的，也可以是分布式的。参数服务器可以是 CPU，也可以是 GPU0。&lt;/p&gt;
&lt;h3&gt;All-Reduce 实现算法&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Reduce + Broadcast&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parameter Sever 作为中心节点，先全局接收所有节点的梯度，然后进行聚合计算，最后将结果 Broadcast 到所有节点。&lt;/li&gt;
&lt;li&gt;总体耗时是 $2 \times (\alpha + S / B) + N \times S \times C$ （这里的前者是通信耗时，后者是中心节点的计算耗时）&lt;/li&gt;
&lt;li&gt;其中 $\alpha$ 是两个通信节点之间&lt;strong&gt;建立连接&lt;/strong&gt;的固定延迟，$S$ 是每个节点的数据块大小，$B$ 是带宽，$N$ 是节点数，$C$ 是每个节点的每字节数据的计算耗时。&lt;/li&gt;
&lt;li&gt;该算法的缺点是中心节点的带宽成为了性能瓶颈，且通信延迟较大。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;img src=&apos;assets/all-reduce11.png&apos; width=&apos;60%&apos; alt=&apos;&apos;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 树形递归算法 (Reduce + Broadcast)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该算法的基本思想是将所有节点组织成一棵树，树的根节点是参数服务器，叶子节点是工作节点。每个节点只与其父节点和子节点进行通信。每个节点先将自己的梯度发送到父节点，然后父节点将所有子节点的梯度进行聚合计算，最后将结果 Broadcast 到所有子节点。&lt;/li&gt;
&lt;li&gt;图中实线是真实的通信，虚线只是示意，并无通信发生&lt;/li&gt;
&lt;li&gt;规避了单节点的带宽瓶颈，但是在递归过程中仍然有一半的节点没有进行 send 操作，只是在等待接收数据 (发送带宽没有被利用上)&lt;/li&gt;
&lt;li&gt;整体耗时是
$$
\log_2 [N \times (\alpha + S / B + 2 \times S \times C)] + \log_2 [N \times (\alpha + S / B)] = 2 \times \log_2 [N \times (\alpha + S / B + S \times C)]
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;img src=&apos;assets/all-reduce22.png&apos; width=&apos;60%&apos; alt=&apos;&apos;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Scatter-Reduce + All-Gather&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该算法结合了 Scatter-Reduce 和 All-Gather 两个操作来实现 All-Reduce&lt;/li&gt;
&lt;li&gt;Scatter-Reduce 阶段：首先将每个节点的数据分成 N 块（N为节点数），然后第 i 个节点负责收集所有节点的第 i 个数据块并进行聚合计算&lt;/li&gt;
&lt;li&gt;All-Gather 阶段：每个节点将自己计算得到的聚合结果广播给其他所有节点&lt;/li&gt;
&lt;li&gt;这种方法通过分散计算负载，避免了单节点瓶颈，同时每个节点只需负责部分数据的聚合&lt;/li&gt;
&lt;li&gt;总体通信时间为 $2 \times (N - 1) / N \times (\alpha + S / B)$，比直接 Reduce + Broadcast 方式更有效率&lt;/li&gt;
&lt;li&gt;该方法被广泛用于分布式深度学习框架中，是实现高效 All-Reduce 的基础&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;img src=&apos;assets/all-reduce33.png&apos; width=&apos;60%&apos; alt=&apos;Scatter-Reduce + All-Gather&apos;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Ring All-Reduce (Scatter-Reduce + All-Gather 的变种)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;采用环形拓扑结构实现通信链路。第 $k$ 个 worker（节点）会把第 $k$ 个数据发送给下一个 worker，即第 $k + 1$ 个节点，同时从前一个 worker 接收到 第 $k - 1$个数据。然后第 $k$ 个 worker 会把收到的的第 $k-1$ 个数据和自己的第 $k - 1$数据整合，再将整合的数据发送给下一个 worker。&lt;/p&gt;
&lt;p&gt;假设总共有 $N$ 个节点，那么经过 $N - 1$ 次迭代之后就完成了 &lt;strong&gt;Scatter-Reduce&lt;/strong&gt; 操作。（不是 $N$ 次，因为最后一个数据块来自第 $N - 1$ 个 worker 计算好了的）。此时，在第 $k$ 个 worker 的第 $k + 1$ 个数据块上出现 reduce 好了的数据块。&lt;/p&gt;
&lt;p&gt;此后，只需要一次 All-Gather 即可把所有的计算块收集并分发出来，从而实现 All-Reduce 操作。这里的 All-Gather 操作也是类似前述的环形操作的，对于第 $k$ 块数据，会从第 $k - 1$ 个 worker 开始，逐步往下一个设备传播 ($k-1 \rightarrow k \rightarrow k + 1 \rightarrow \dots \rightarrow k - 2$)，传播 $N - 1$ 次即实现了 All-Gather。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://andrew.gibiansky.com/blog/machine-learning/baidu-allreduce/images/ring-gpus.png&quot; alt=&quot;RingAllReduce&quot; /&gt;
&lt;img src=&quot;https://andrew.gibiansky.com/blog/machine-learning/baidu-allreduce/images/scatter-reduce-iteration-2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://andrew.gibiansky.com/blog/machine-learning/baidu-allreduce/images/scatter-reduce-iteration-done.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;性能分析: 在整个过程中，网络中每个 worker 上 send / receive 的总通信数据量均为
$$
\text{DataTransfered} = 2(N - 1) \frac{K}{N}
$$
其中，$N$ 为 worker 数量， $K$ 为每个 worker 上所有 chunk 的总数据量 （chunk数量 =  worker数量）。这里的 $K / N$ 代表单个 worker 上需要传输的一个数据块的大小。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;单个 worker 通信数据量近似独立于 worker 的数量 $N$，是 $O(K)$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每个 worker 的网络收发负载是均衡的，网络双向带宽得到充分利用&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实现代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def ring_allreduce(send, recv):
    &quot;&quot;&quot; Implementation of a true Ring-AllReduce with explicit accumulator. &quot;&quot;&quot;
    rank = dist.get_rank()
    size = dist.get_world_size()
    chunk_size = send.size(0) // size  # Divide data into chunks
    send_buff = torch.zeros(chunk_size, *send.size()[1:])  # Buffer for sending
    recv_buff = torch.zeros(chunk_size, *send.size()[1:])  # Buffer for receiving

    # Split the data into chunks
    chunks = [send[i * chunk_size:(i + 1) * chunk_size] for i in range(size)]
    accum = torch.zeros_like(send)  # Explicit accumulator

    left = (rank - 1 + size) % size  # Left neighbor
    right = (rank + 1) % size        # Right neighbor

    # Reduce-Scatter phase
    for i in range(size - 1):
        send_chunk = chunks[(rank - i + size) % size]  # Select the chunk to send
        send_buff.copy_(send_chunk)  # Copy the chunk to the send buffer
        send_req = dist.isend(send_buff, right)  # Send to the right neighbor
        dist.recv(recv_buff, left)  # Receive from the left neighbor
        send_req.wait()  # Wait for the send to complete

        # Accumulate the received chunk into `accum`
        recv_chunk_idx = (rank - i - 1 + size) % size
        accum[i * chunk_size:(i + 1) * chunk_size] += recv_buff

    # All-Gather phase
    for i in range(size - 1):
        send_chunk_idx = (rank - i - 1 + size) % size
        send_buff.copy_(chunks[send_chunk_idx])  # Copy the chunk to the send buffer
        send_req = dist.isend(send_buff, right)  # Send to the right neighbor
        dist.recv(recv_buff, left)  # Receive from the left neighbor
        send_req.wait()  # Wait for the send to complete

        # Store the received chunk in the correct position
        recv_chunk_idx = (rank - i - 2 + size) % size
        chunks[recv_chunk_idx].copy_(recv_buff)

    # Combine all chunks into the final result
    recv.copy_(torch.cat(chunks, dim=0))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PyTorch 中的 rank 间通信使用示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
import torch
import torch.distributed as dist
import torch.multiprocessing as mp

&quot;&quot;&quot;Blocking point-to-point communication.&quot;&quot;&quot;
def run_B(rank, size):
    tensor = torch.zeros(1)
    if rank == 0:
        tensor += 1
        # Send the tensor to process 1
        dist.send(tensor=tensor, dst=1)
    else:
        # Receive tensor from process 0
        dist.recv(tensor=tensor, src=0)
    print(&apos;Rank &apos;, rank, &apos; has data &apos;, tensor[0])

&quot;&quot;&quot;Non-blocking point-to-point communication.&quot;&quot;&quot;
def run_N(rank, size):
    tensor = torch.zeros(1)
    req = None
    if rank == 0:
        tensor += 1
        # Send the tensor to process 1
        req = dist.isend(tensor=tensor, dst=1)
        print(&apos;Rank 0 started sending&apos;)
    else:
        # Receive tensor from process 0
        req = dist.irecv(tensor=tensor, src=0)
        print(&apos;Rank 1 started receiving&apos;)
    req.wait()
    print(&apos;Rank &apos;, rank, &apos; has data &apos;, tensor[0])

&quot;&quot;&quot; All-Reduce example.&quot;&quot;&quot;
def run_A(rank, size):
    &quot;&quot;&quot; Simple collective communication. &quot;&quot;&quot;
    group = dist.new_group([0, 1])
    tensor = torch.ones(1) * 5
    dist.all_reduce(tensor, op=dist.ReduceOp.SUM, group=group)
    print(&apos;Rank &apos;, rank, &apos; has data &apos;, tensor[0])

def init_process(rank, size, fn, backend=&apos;gloo&apos;):
    &quot;&quot;&quot; Initialize the distributed environment. &quot;&quot;&quot;
    os.environ[&apos;MASTER_ADDR&apos;] = &apos;127.0.0.1&apos;
    os.environ[&apos;MASTER_PORT&apos;] = &apos;29500&apos;
    dist.init_process_group(backend, rank=rank, world_size=size)
    fn(rank, size)

if __name__ == &quot;__main__&quot;:
    size = 2
    processes = []
    mp.set_start_method(&quot;spawn&quot;)
    for rank in range(size):
        p = mp.Process(target=init_process, args=(rank, size, run_N))
        p.start()
        processes.append(p)

    for p in processes:
        p.join()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;数据并行的代码实践&lt;/h2&gt;
&lt;p&gt;目前已有许多成熟的分布式通信后端，实现了各类通信原语的算法，如 MPI、NCCL 和 Gloo。这些后端在分布式训练中可以进行选择。&lt;/p&gt;
&lt;h3&gt;torch.distributed 实现单机多卡分布式训练&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import torch.distributed as dist
from math import ceil
from random import Random
from torch.multiprocessing import Process
from torch.autograd import Variable
from torchvision import datasets, transforms

class DatasetSubset:
    &quot;&quot;&quot;数据集的子集包装器&quot;&quot;&quot;
    def __init__(self, parent_dataset, indices):
        self.parent_dataset = parent_dataset
        self.indices = indices
    
    def __len__(self):
        return len(self.indices)
    
    def __getitem__(self, idx):
        actual_idx = self.indices[idx]
        return self.parent_dataset[actual_idx]

class DataPartitioner(object):
    &quot;&quot;&quot; Partitions a dataset into different chuncks. &quot;&quot;&quot;
    def __init__(self, data, sizes=[0.7, 0.2, 0.1], seed=42):
        self.data = data
        self.partitions = []
        rng = Random()
        rng.seed(seed)
        data_len = len(data)
        indexes = [x for x in range(0, data_len)]
        rng.shuffle(indexes)

        for frac in sizes:
            part_len = int(frac * data_len)
            self.partitions.append(indexes[0:part_len])
            indexes = indexes[part_len:]

    def use(self, partition):
        return DatasetSubset(self.data, self.partitions[partition])

def partition_dataset():    # 数据集分割需要自行实现，dist中并未实现
    &quot;&quot;&quot; Partitioning MNIST &quot;&quot;&quot;
    dataset = datasets.MNIST(
        &apos;./data&apos;,
        train=True,
        download=True,
        transform=transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize((0.1307, ), (0.3081, ))
        ]))
    size = dist.get_world_size()
    bsz = int(128 / float(size))     # 128 / float(size)
    partition_sizes = [1.0 / size for _ in range(size)]
    partition = DataPartitioner(dataset, partition_sizes)
    partition = partition.use(dist.get_rank())
    train_set = torch.utils.data.DataLoader(
        partition, batch_size=bsz, shuffle=True)
    return train_set, bsz


def average_gradients(model):   # 同理，avg-SGD 也是 dist 中没有的
    &quot;&quot;&quot; Gradient averaging. &quot;&quot;&quot;
    world_size = float(dist.get_world_size())
    for param in model.parameters():
        if param.grad is not None:
            # 执行全局梯度求和
            dist.all_reduce(param.grad.data, op=dist.ReduceOp.SUM)
            # 计算平均梯度
            param.grad.data.div_(world_size)


def run(rank, size):
    &quot;&quot;&quot; Distributed Synchronous SGD Example &quot;&quot;&quot;
    torch.manual_seed(1234)
    train_set, bsz = partition_dataset()
    model = Net()
    model = model.cuda(rank)     # single machine, multiple GPU
    optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)

    num_batches = ceil(len(train_set.dataset) / float(bsz))
    for epoch in range(3):
        epoch_loss = 0.0
        for data, target in train_set:
            data, target = Variable(data.cuda(rank)), Variable(target.cuda(rank))
            optimizer.zero_grad()
            output = model(data)
            loss = F.nll_loss(output, target)
            epoch_loss += loss.item()   #loss.data[0]
            loss.backward()
            average_gradients(model)    # all reduce operation
            optimizer.step()
        print(&apos;Rank &apos;,
              dist.get_rank(), &apos;, epoch &apos;, epoch, &apos;: &apos;,
              epoch_loss / num_batches)


def init_processes(rank, size, fn, backend=&apos;gloo&apos;):
    &quot;&quot;&quot; Initialize the distributed environment. &quot;&quot;&quot;
    os.environ[&apos;MASTER_ADDR&apos;] = &apos;127.0.0.1&apos;
    os.environ[&apos;MASTER_PORT&apos;] = &apos;29500&apos;
    dist.init_process_group(backend, rank=rank, world_size=size)
    fn(rank, size)


if __name__ == &quot;__main__&quot;:
    size = 2
    processes = []
    for rank in range(size):
        p = Process(target=init_processes, args=(rank, size, run))
        p.start()
        processes.append(p)

    for p in processes:
        p.join()
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;完整代码: &amp;lt;a href=&quot;/blog/resources/deeplearning/dist-SGD.py&quot; download=&quot;dist-SGD.py&quot;&amp;gt; dist-SGD.py &amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;li&gt;此代码仅实现了多 worker 组网及通信原语功能&lt;/li&gt;
&lt;li&gt;但是数据集分割仍需手工编码实现&lt;/li&gt;
&lt;li&gt;All-reduce 操作仍需显式调用，因为其为数据并行算法相关的&lt;/li&gt;
&lt;li&gt;开发及运行效率低下&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;DDP (torch.nn.parallel.DistributedDataParallel)&lt;/h3&gt;
&lt;p&gt;DDP 全称为 DistributedDataParallel，是 PyTorch 提供的一种高效数据并行训练模块，用于在多进程环境下进行分布式训练。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DDP 的主要优点：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;简化分布式编程&lt;/strong&gt; - 隐藏了复杂的通信细节，用户只需要少量代码修改即可将单机程序转换为分布式程序&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高效通信&lt;/strong&gt; - 采用 Ring-AllReduce 算法实现梯度同步，避免了中心节点带宽瓶颈&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通信和计算重叠&lt;/strong&gt; - 采用梯度累积的形式，实现通信与计算的并行执行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持多种后端&lt;/strong&gt; - 兼容 NCCL、Gloo、MPI 等不同通信后端&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动梯度同步&lt;/strong&gt; - 在反向传播过程中自动完成梯度的 AllReduce 操作&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;DDP 的作用：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在多台机器或单机多卡环境中实现数据并行训练&lt;/li&gt;
&lt;li&gt;确保模型参数在各进程间的一致性&lt;/li&gt;
&lt;li&gt;优化分布式训练的性能和通信效率&lt;/li&gt;
&lt;li&gt;提供容错机制，处理进程失败等异常情况&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以下是 DDP 使用示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP

# 初始化分布式环境
dist.init_process_group(backend=&quot;nccl&quot;)
local_rank = dist.get_rank()
torch.cuda.set_device(local_rank)

# 创建模型并移至对应设备
model = Net().to(local_rank)
# 将模型封装到DDP中
ddp_model = DDP(model, device_ids=[local_rank])

# 正常训练流程
optimizer = torch.optim.SGD(ddp_model.parameters(), lr=0.01)
for data, target in train_loader:
    data, target = data.to(local_rank), target.to(local_rank)
    optimizer.zero_grad()
    output = ddp_model(data)
    loss = criterion(output, target)
    loss.backward()  # DDP自动同步梯度
    optimizer.step()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DDP 把 dist 中的 send/recv 和 all-reduce 封装起来了，用户只需要调用 DDP 的接口即可。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DDP 实践代码: &amp;lt;a href=&quot;/blog/resources/deeplearning/densenet-DDP.py&quot; download=&quot;densenet-DDP.py&quot;&amp;gt; densenet-DDP.py &amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;跨机器训练&lt;/h3&gt;
&lt;p&gt;多机器训练的需要确定一个主节点，主节点负责协调所有的工作节点。主节点的作用是分配任务、收集结果、同步参数等。主节点可以是任意一台机器，但通常选择性能最强的一台机器作为主节点。同时还需要确定从节点，从节点是指除了主节点之外的所有机器。每个从节点都需要知道主节点的地址和端口号，以便进行通信。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在多机训练中，通常使用 &lt;code&gt;tcp://&lt;/code&gt; 协议进行通信。主节点的地址和端口号需要在所有从节点上配置好，以便进行通信。可以通过环境变量 &lt;code&gt;MASTER_ADDR&lt;/code&gt; 和 &lt;code&gt;MASTER_PORT&lt;/code&gt; 来设置主节点的地址和端口号。&lt;/li&gt;
&lt;li&gt;在多机训练中，通常使用 &lt;code&gt;gloo&lt;/code&gt; 或 &lt;code&gt;nccl&lt;/code&gt; 作为通信后端。&lt;code&gt;gloo&lt;/code&gt; 是一个高性能的通信库，支持多种通信模式，如点对点通信、广播、聚合等。&lt;code&gt;nccl&lt;/code&gt; 是 NVIDIA 提供的一个高性能的通信库，专门用于 GPU 之间的通信，支持多种通信模式，如点对点通信、广播、聚合等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;不过区分主从节点不代表通信方式是使用参数服务器架构，其实实际上使用的依然可以是 Ring All-Reduce。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;主节点启动 bash 脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

# 设置主节点信息
export MASTER_ADDR=&quot;192.168.1.100&quot;   # 主节点IP地址
export MASTER_PORT=&quot;29500&quot;           # 主节点端口
export WORLD_SIZE=4                  # 总进程数(所有机器的GPU总数)
export NODE_RANK=0                   # 当前节点的rank(主节点为0)
export NPROC_PER_NODE=2              # 每个节点上的进程数(GPU数)

# 启动分布式训练
python -m torch.distributed.launch \
    --nproc_per_node=$NPROC_PER_NODE \
    --nnodes=$WORLD_SIZE \
    --node_rank=$NODE_RANK \
    --master_addr=$MASTER_ADDR \
    --master_port=$MASTER_PORT \
    train_script.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从节点启动 bash 脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

# 设置主节点信息（与主节点相同）
export MASTER_ADDR=&quot;192.168.1.100&quot;   # 主节点IP地址
export MASTER_PORT=&quot;29500&quot;           # 主节点端口
export WORLD_SIZE=4                  # 总进程数(所有机器的GPU总数)
export NODE_RANK=1                   # 当前节点的rank(从节点为非0值，每个从节点不同)
export NPROC_PER_NODE=2              # 每个节点上的进程数(GPU数)

# 启动分布式训练
python -m torch.distributed.launch \
    --nproc_per_node=$NPROC_PER_NODE \
    --nnodes=$WORLD_SIZE \
    --node_rank=$NODE_RANK \
    --master_addr=$MASTER_ADDR \
    --master_port=$MASTER_PORT \
    train_script.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果服务器上有多张网卡，需要指定使用那张网卡进行通信，可以通过设置环境变量 &lt;code&gt;NCCL_SOCKET_IFNAME&lt;/code&gt; 来指定使用的网卡。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export NCCL_SOCKET_IFNAME=eth0  # 使用eth0网卡进行通信
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当主节点启动后，主节点会等待与从节点通信的建立。如果尚未建立通信，则主节点会一直处于等待状态。可以通过设置环境变量 &lt;code&gt;NCCL_DEBUG&lt;/code&gt; 来查看通信的详细信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export NCCL_DEBUG=INFO  # 打印通信的详细信息
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;同步 barrier&lt;/h3&gt;
&lt;p&gt;不同的机器之间的硬件性能可能有差异，导致不同机器之间的训练速度不一致。为了保证训练的同步性，可以使用 &lt;code&gt;barrier&lt;/code&gt; 来实现同步。&lt;code&gt;barrier&lt;/code&gt; 的作用是阻塞所有进程，直到所有进程都到达 &lt;code&gt;barrier&lt;/code&gt; 位置。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch.distributed as dist
import torch.multiprocessing as mp
import os
import time

def run(rank, size):
    print(f&quot;Rank {rank} is waiting at barrier, now is {time.time()}&quot;)
    dist.barrier()  # 等待所有进程到达 barrier
    print(f&quot;Rank {rank} has passed barrier&quot;)
    time.sleep(1)  # 模拟训练过程
    print(f&quot;Rank {rank} has finished training, now is {time.time()}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Horovod&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&quot;Hovovod is a distributed deep learning training framework that makes it easy to take a single-GPU TensorFlow or PyTorch program and run it on many GPUs and many machines with minimal code changes.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Horovod 是 Uber 开源的一个分布式深度学习训练框架，支持 TensorFlow、Keras、PyTorch 和 MXNet 等深度学习框架。Horovod 的设计目标是使分布式训练变得简单和高效。Horovod 的核心思想是使用 Ring All-Reduce 算法来实现参数的同步更新，从而提高训练速度。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Horovod 代码: &amp;lt;a href=&quot;/blog/resources/deeplearning/horovod.py&quot; download=&quot;horovod.py&quot;&amp;gt; horovod.py &amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Horovod 尽可能的保证了多卡代码的实现上和单卡的实现类似。&lt;/p&gt;
&lt;p&gt;启动命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;horovodrun -np 4 -H localhost:4 python train.py --batch-size 64 --epochs 3 --fp16-allreduce --use-adasum
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;-np&lt;/code&gt; 表示总进程数，&lt;code&gt;-H&lt;/code&gt; 表示每台机器的进程数。&lt;code&gt;--fp16-allreduce&lt;/code&gt; 表示使用 fp16 进行 all-reduce，&lt;code&gt;--use-adasum&lt;/code&gt; 表示使用 adasum 算法进行 all-reduce。backend 默认为 MPI，支持 gloo 和 nccl。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;参考资料&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1811.06965&quot;&gt;Gpipe 论文&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.open-mpi.org/en/main/index.html&quot;&gt;MPI 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/index.html&quot;&gt;NCCL 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://horovod.readthedocs.io/en/stable/summary_include.html#gloo&quot;&gt;Gloo 文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>AI-Sys 3 神经网络模型的稀疏化与轻量化</title><link>https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter3/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter3/</guid><pubDate>Thu, 22 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;模型的稀疏化与轻量化&lt;/p&gt;
&lt;h1&gt;背景信息&lt;/h1&gt;
&lt;h2&gt;为什么需要模型稀疏化与轻量化？&lt;/h2&gt;
&lt;p&gt;随着深度学习模型规模的不断增长，模型稀疏化与轻量化变得越来越重要，一个重要的原因就是当前的模型是越做越大，因为增加模型的参数量仍然是提高精度的主要手段。然而，无限制地增长模型参数量自然不现实。&lt;strong&gt;计算资源限制&lt;/strong&gt;、&lt;strong&gt;实时性要求&lt;/strong&gt;、&lt;strong&gt;能耗消耗&lt;/strong&gt;、&lt;strong&gt;存储空间&lt;/strong&gt;和&lt;strong&gt;经济效益&lt;/strong&gt;等都要求着稀疏化与轻量化。&lt;/p&gt;
&lt;h2&gt;为什么模型可以稀疏化？&lt;/h2&gt;
&lt;p&gt;稀疏化的来源是&lt;strong&gt;数据具有高度结构化的特性&lt;/strong&gt;，结构化的本质导致了稀疏本身的产生。神经网络内部存在大量冗余信息，&lt;strong&gt;并不是所有的参数和结构都对深度神经网络的高判别性起作用&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;稀疏化的常用手段&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;权重系数&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;激活稀疏&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;梯度稀疏&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;量化&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模型轻量化设计&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;权重稀疏&lt;/h1&gt;
&lt;h2&gt;原理&lt;/h2&gt;
&lt;p&gt;神经网络的绝大部分权重参数都是&lt;code&gt;0&lt;/code&gt;值参数，且这个规律符合正态分布，对这部分参数进行稀疏化处理不会影响模型的性能。权重系数的本质是因为深度学习模型是自动提取特征的，在自动提取的特征中，存在大量冗余的不重要特征。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/sparse1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;因此可行的一种权重系数化手段是&lt;strong&gt;对进行阈值剪枝后的模型再进行训练&lt;/strong&gt;，从而得到稀疏化的网络。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;权重稀疏化的优点&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在内存开销相同的情况下，大稀疏模型通常比小密集模型的性能要更好&lt;/li&gt;
&lt;li&gt;经过剪枝之后的系数模型是要优于同参数量的非稀疏模型&lt;/li&gt;
&lt;li&gt;稀疏化后的参数可以用稀疏矩阵进行存储&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;权重系数化的三个步骤&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;训练完整模型&lt;/li&gt;
&lt;li&gt;剪枝&lt;/li&gt;
&lt;li&gt;再训练微调&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通常微调 (Fine-tuning) 后的稀疏模型性能与被剪枝参数量占比的曲线如下所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/sparse2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;具体方法&lt;/h2&gt;
&lt;h3&gt;非结构化剪枝 (unstructured pruning)&lt;/h3&gt;
&lt;p&gt;非结构化剪枝是直接减除低于阈值的权值，这种方法是一种细粒度的剪枝方法&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/sparse3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在 PyTorch 中，具体的实现方式是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;随机非结构化剪枝&lt;/strong&gt;，即随机把参数的某些值置为 0。可以使用 &lt;code&gt;torch.nn.utils.prune&lt;/code&gt; 模块来实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import torch.nn.utils.prune as prune

model = LeNet().to(device=device)

# 对 conv1 层进行剪枝
module = model.conv1
print(&quot;conv1 的参数:\n&quot;, list(module.named_parameters()))  # 查看 conv1 层有哪些参数，以及对应的参数值是什么

# 进行非结构化剪枝

# 对 weight 参数进行随机非结构化剪枝
# 即随机把参数的某些值置为 0
# amount 表示剪枝比例，即将 30% 值随机置 0
prune.random_unstructured(module, name=&apos;weight&apos;, amount=0.3) 

# named_buffers() 是 torch 中的一个用于获取模型或模块中所有注册的 buffers 及其名称的方法
# buffers 是模型中需要保存但不需要梯度更新的张量
# 这里保存的是剪枝的 mask，剪枝并不是直接把参数的值设为 0，而是通过 mask 机制把被剪枝的掩码置 0
# 这样，被剪枝的权重还可以被恢复，方法为 prune.remove()
print(&quot;weight 的 mask:\n&quot;, list(module.named_buffers()))
print(&quot;查看剪枝后的结果:\n&quot;, module.weight)    # 查看剪枝后的结果

# 查看原始权重
print(module.weight_orig)

# 恢复权重
prune.remove(module, name=&apos;weight&apos;)
print(&quot;查看恢复的结果:\n&quot;, module.weight)    # 查看恢复的结果
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;点击下载: &amp;lt;a href=&quot;/blog/resources/deeplearning/prune_unstruct1.py&quot; download=&quot;prune_unstruct1.py&quot;&amp;gt; 随机非结构化剪枝代码 &amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;L1 非结构化剪枝&lt;/strong&gt;，即根据参数的 L1 范数进行剪枝。L1 范数是指向量中每个元素的绝对值之和。L1 范数越小，表示该参数对模型的贡献越小，因此可以被剪枝。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 对 conv1 层进行剪枝
module = model.conv1
print(&quot;conv1 的参数:\n&quot;, list(module.named_parameters()))  # 查看 conv1 层有哪些参数，以及对应的参数值是什么

# 进行非结构化剪枝

# 对 bias 参数进行 L1非结构化剪枝
# 即随机把参数的某些值置为 0
# amount 表示剪枝比例，即将 30% 值随机置 0
# importance_scores 是用来指定重要性指标的接口
# 例如可以设置参数的绝对值信息作为指标
# 要求是该指标的形状必须与被剪枝的参数相同
importance_scores = module.bias.abs()
prune.l1_unstructured(module, name=&apos;bias&apos;, amount=0.3, importance_scores=importance_scores)

# named_buffers() 是 torch 中的一个用于获取模型或模块中所有注册的 buffers 及其名称的方法
# buffers 是模型中需要保存但不需要梯度更新的张量
# 这里保存的是剪枝的 mask，剪枝并不是直接把参数的值设为 0，而是通过 mask 机制把被剪枝的掩码置 0
# 这样，被剪枝的权重还可以被恢复，方法为 prune.remove()
print(&quot;bias 的 mask:\n&quot;, list(module.named_buffers()))
print(&quot;查看剪枝后的结果:\n&quot;, module.bias)    # 查看剪枝后的结果
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;点击下载: &amp;lt;a href=&quot;/blog/resources/deeplearning/prune_unstruct2.py&quot; download=&quot;prune_unstruct2.py&quot;&amp;gt; L1 非结构化剪枝代码 &amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;非结构化剪枝的特点是剪枝并不会改变参数矩阵的形状，只是变成了含有大量零值的稀疏矩阵。对应的优点是存储上可以用&lt;strong&gt;稀疏矩阵存储&lt;/strong&gt;（只存储非零元素的数值和位置即可实现存储体积的减少）和&lt;strong&gt;计算上的开销减少&lt;/strong&gt;（稀疏矩阵乘法）。&lt;/p&gt;
&lt;p&gt;英伟达 A100 GPU 的 sparse tensor core
&lt;img src=&quot;assets/nvidia-a100-sparse.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;结构化剪枝 (structured pruning)&lt;/h3&gt;
&lt;p&gt;结构化剪枝是在将大矩阵划分为若干符合内存大小的子块的前提下，直接减除低于阈值的权值块的剪枝方法。这种方法是一种粗粒度的剪枝方法，粗粒度的剪枝方法的好处是可以加快推理速度，对硬件友好，缺点是会带来精度损失。这是因为规则化限制了对不重要参数的精准剪枝。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/sparse4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;块大小对精度的影响&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/sparse5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;PyTorch 中的实现方式是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch.nn.utils.prune as prune

prune.ln_structured(module, name=&quot;weight&quot;, amount=0.5, n=2, dim=0)
print(module.weight)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结构化剪枝的一种实现方式是&lt;strong&gt;通道剪枝&lt;/strong&gt;，即将整个通道剪掉。它的自定义实现方式是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch

def index_remove(tensor, dim, index):  
    size_ = list(tensor.size())
    a = tensor.size(dim)
    b = len(index)
    new_size = a - b
    size_[dim] = new_size

    select_index = list(set(range(tensor.size(dim))) - set(index))
    new_tensor = torch.index_select(tensor, dim, torch.tensor(select_index))

    return new_tensor

kernel = torch.rand(4, 2, 3)

kernel_pruned = index_remove(kernel, 0, [0, 2])
print(kernnel_pruned)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;正则化手段&lt;/h3&gt;
&lt;p&gt;如果一般训练后模型的权重分布不符合正态分布，可以使用正则化的方式强迫权重分布符合正态分布。常用的正则化手段有 L1 正则化和 L2 正则化。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;L1 正则化&lt;/strong&gt;：L1 正则化是通过对模型的权重参数施加 L1 范数的惩罚项来实现的。
$$
L1 = \sum_{i=1}^{n} |w_i|
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;L2 正则化&lt;/strong&gt;：L2 正则化是通过对模型的权重参数施加 L2 范数的惩罚项来实现的。
$$
L2 = \sum_{i=1}^{n} w_i^2
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下载结构化稀疏后微调模型的代码 &amp;lt;a href=&quot;/blog/resources/deeplearning/prune_struct3.py&quot; download=&quot;prune_struct3.py&quot;&amp;gt; prune_struct3.py &amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;激活稀疏&lt;/h1&gt;
&lt;p&gt;神经网络的激活稀疏是指在模型推理过程中，大部分神经元的输出为零，只有少数神经元被激活。激活稀疏的来源主要是因为有激活函数的存在。这种现象在使用ReLU等非线性激活函数的网络中尤为明显，因为ReLU会将所有负值映射为零。&lt;/p&gt;
&lt;p&gt;激活稀疏产生的主要原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;非线性激活函数&lt;/strong&gt;：如ReLU、Leaky ReLU等函数天然会产生稀疏激活&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;权重初始化和更新机制&lt;/strong&gt;：导致某些神经元接收的输入总是处于激活函数的非激活区域&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正则化技术&lt;/strong&gt;：例如Dropout随机失活或L1正则化会促进激活稀疏&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结构化数据特性&lt;/strong&gt;：高维数据通常集中在低维子空间，不需要所有神经元都被激活&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;激活稀疏的优势：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;计算效率提升&lt;/strong&gt;：零激活可以跳过后续的乘法运算&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;减少过拟合&lt;/strong&gt;：类似正则化效果，降低模型的有效复杂度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提高生物学可解释性&lt;/strong&gt;：与人脑神经元的稀疏激活特性相似&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见的激活稀疏化方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;k-稀疏正则化&lt;/strong&gt;：强制每层中最多只有k个神经元激活&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;显式稀疏约束&lt;/strong&gt;：在损失函数中添加鼓励激活稀疏的惩罚项&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结构化剪枝&lt;/strong&gt;：移除训练过程中很少被激活的神经元&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;门控机制&lt;/strong&gt;：动态决定哪些神经元应该被激活&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;梯度稀疏&lt;/h1&gt;
&lt;p&gt;梯度稀疏是指在深度神经网络的训练过程中，参数更新的梯度向量中大多数元素接近于零，只有少量元素具有显著非零值。这种特性对于大规模分布式训练和优化算法的设计至关重要。例如在分布式训练任务中，多台节点需要实时交换各自梯度计算数值，当模型参数高度稀疏时，99.9% 的梯度交换是冗余的。&lt;/p&gt;
&lt;p&gt;梯度稀疏的形成原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;参数重要性不均&lt;/strong&gt;：不同参数对模型性能的贡献差异很大&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;激活稀疏的传导&lt;/strong&gt;：由于激活稀疏，许多神经元不参与反向传播，导致相应权重的梯度为零&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化过程特性&lt;/strong&gt;：随着训练进行，模型趋于收敛，大部分参数梯度逐渐变小&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;梯度消失问题&lt;/strong&gt;：特别是在深层网络中，梯度可能在反向传播过程中衰减到接近零&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;梯度稀疏的应用价值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分布式训练优化&lt;/strong&gt;：只需传输和更新非零梯度，显著降低通信开销&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算资源优化&lt;/strong&gt;：集中计算资源在重要梯度上，提高训练效率&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自适应学习率策略&lt;/strong&gt;：根据梯度稀疏度动态调整不同参数的学习率&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见的梯度稀疏化技术：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;阈值稀疏化&lt;/strong&gt;：只保留绝对值大于指定阈值的梯度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Top-k稀疏化&lt;/strong&gt;：只保留绝对值最大的k个梯度元素&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;随机稀疏化&lt;/strong&gt;：以概率方式保留梯度元素，概率与梯度大小成正比&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结构化稀疏化&lt;/strong&gt;：保持特定模式的梯度非零，如块状或分组稀疏&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;梯度稀疏、权重稀疏和激活稀疏三者相互关联，共同构成了深度学习模型稀疏性的完整画像，是模型优化和压缩的重要理论基础。&lt;/p&gt;
&lt;p&gt;一个简单的梯度稀疏化的想法是设置一个阈值，把大于阈值的梯度在节点间进行传播，而小于阈值的梯度则在本地进行积累，当超过阈值时再进行传播。这样可以减少通信开销和计算开销。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;结构/非结构剪枝&lt;/th&gt;
&lt;th&gt;动态/静态&lt;/th&gt;
&lt;th&gt;数据驱动方式&lt;/th&gt;
&lt;th&gt;训练时/推理时&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;权重稀疏&lt;/td&gt;
&lt;td&gt;非结构/结构化剪枝&lt;/td&gt;
&lt;td&gt;静态&lt;/td&gt;
&lt;td&gt;数据无关&lt;/td&gt;
&lt;td&gt;推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;激活稀疏&lt;/td&gt;
&lt;td&gt;结构化剪枝&lt;/td&gt;
&lt;td&gt;动态&lt;/td&gt;
&lt;td&gt;数据驱动&lt;/td&gt;
&lt;td&gt;推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;梯度稀疏&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;动态&lt;/td&gt;
&lt;td&gt;数据驱动&lt;/td&gt;
&lt;td&gt;训练&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;量化&lt;/h1&gt;
&lt;p&gt;量化是一种通过降低用于表示模型参数的数值精度来压缩深度学习模型的技术。它将模型参数从高精度浮点数（如FP32）转换为低精度表示（如INT8、INT4或甚至二值），从而显著减少模型大小和计算资源需求。&lt;/p&gt;
&lt;h2&gt;量化原理&lt;/h2&gt;
&lt;p&gt;量化的基本思想是在可接受的精度损失范围内，使用更少的比特表示神经网络中的权重和激活值。这一过程可以用如下公式表示&lt;/p&gt;
&lt;p&gt;$$
Q(r) = \text{round}\left(\frac{r}{S}\right) + Z
$$&lt;/p&gt;
&lt;p&gt;其中，$r$ 是原始浮点值，$Q(r)$ 是量化后的整数值，$S$ 是量化比例因子，$Z$ 是零点偏移。反量化过程则是&lt;/p&gt;
&lt;p&gt;$$
r = S \times (Q(r) - Z)
$$&lt;/p&gt;
&lt;h2&gt;量化类型&lt;/h2&gt;
&lt;h3&gt;按精度分类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FP32 → INT8&lt;/strong&gt;：将32位浮点数量化为8位整数，典型应用于推理加速&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FP32 → INT4&lt;/strong&gt;：更进一步减少到4位表示，但精度损失增加&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二值化（Binary）&lt;/strong&gt;：极端情况下参数只用1位表示（+1或-1）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;三值化（Ternary）&lt;/strong&gt;：参数使用3个值表示（-1、0、+1）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;按量化范围分类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;权重量化&lt;/strong&gt;：只对模型权重进行量化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;激活量化&lt;/strong&gt;：对神经元的激活输出进行量化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全量化&lt;/strong&gt;：对权重和激活值都进行量化&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;按量化时机分类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;训练后量化（Post-Training Quantization，PTQ）&lt;/strong&gt;：在模型训练完成后应用量化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;量化感知训练（Quantization-Aware Training，QAT）&lt;/strong&gt;：在训练过程中考虑量化效应&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;训练后量化（PTQ）&lt;/h2&gt;
&lt;p&gt;PTQ是一种简单有效的量化方法，直接应用于已训练模型，无需重新训练：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;收集校准数据（通常是训练集的小子集）&lt;/li&gt;
&lt;li&gt;确定量化参数（如比例因子和零点）&lt;/li&gt;
&lt;li&gt;转换模型权重和激活值为低精度表示&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# PyTorch中的简单PTQ示例
import torch

# 假设model是已训练好的模型
model_fp32 = model

# 设置为评估模式
model_fp32.eval()

# 准备用于量化的模型
model_int8 = torch.quantization.quantize_dynamic(
    model_fp32,  # 原模型
    {torch.nn.Linear, torch.nn.Conv2d},  # 要量化的层类型
    dtype=torch.qint8  # 量化数据类型
)

# 保存量化后的模型
torch.save(model_int8.state_dict(), &quot;quantized_model.pth&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;量化感知训练（QAT）&lt;/h2&gt;
&lt;p&gt;QAT在训练过程中模拟量化效果，使模型能够适应量化引起的精度损失：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在前向传播中模拟量化操作（计算使用量化值）&lt;/li&gt;
&lt;li&gt;在反向传播中使用原始精度梯度更新（训练正常进行）&lt;/li&gt;
&lt;li&gt;训练完成后应用真正的量化&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# PyTorch中的QAT示例
import torch.quantization

# 准备量化配置
qconfig = torch.quantization.get_default_qat_qconfig(&apos;fbgemm&apos;)
model_fp32.qconfig = qconfig

# 准备QAT
model_qat = torch.quantization.prepare_qat(model_fp32)

# 进行训练
for epoch in range(epochs):
    train_one_epoch(model_qat, criterion, optimizer, data_loader, device)
    
# 转换为量化模型
model_int8 = torch.quantization.convert(model_qat.eval(), inplace=False)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;量化的优势和挑战&lt;/h2&gt;
&lt;h3&gt;优势&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;存储效率&lt;/strong&gt;：模型大小显著减少，INT8模型比FP32模型小约75%&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;推理加速&lt;/strong&gt;：低精度运算速度更快，尤其在支持INT8计算的硬件上&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能耗降低&lt;/strong&gt;：降低算力和存储需求，减少能源消耗&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;边缘设备部署&lt;/strong&gt;：使大模型能在资源有限的设备上运行&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;挑战&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;精度损失&lt;/strong&gt;：量化不可避免地导致信息损失，尤其是在极低位宽时&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特殊层处理&lt;/strong&gt;：某些层（如Softmax）对量化特别敏感&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬件依赖&lt;/strong&gt;：不同硬件对量化算法的支持程度不同&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态范围问题&lt;/strong&gt;：处理激活值范围变化大的情况较困难&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;实际应用&lt;/h2&gt;
&lt;p&gt;量化已在各种应用中被广泛采用，特别是在移动设备和嵌入式系统中部署大型模型时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google的TensorFlow Lite使用INT8量化使模型体积减少75%&lt;/li&gt;
&lt;li&gt;PyTorch、ONNX Runtime和TensorRT等框架都提供了完善的量化工具&lt;/li&gt;
&lt;li&gt;边缘AI芯片（如Google TPU、英特尔Movidius等）专为低精度计算优化设计&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;量化技术与模型剪枝、知识蒸馏等方法结合使用，代表了高效AI部署的重要方向。&lt;/p&gt;
&lt;h1&gt;轻量化神经网络架构设计&lt;/h1&gt;
&lt;p&gt;轻量化神经网络旨在减少模型的计算量和参数量，使其能够在资源受限的设备上高效运行。&lt;/p&gt;
&lt;h2&gt;轻量化设计的原则&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;轻量化的一些概念：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FLOPs: 浮点计算次数(Floating-point Operations)，理解为计算量，可以用来衡量算法/模型是啊金的复杂度&lt;/li&gt;
&lt;li&gt;FLOPS：每秒所执行的浮点运算次数(Floating-point Operations Per Second),理解为计算速度，是一个衡量硬件性能/模型速度的指标，即一个芯片的算力。&lt;/li&gt;
&lt;li&gt;MACCs：乘加操作次数(Multiply-accumulate Operations)，MACCs大约为FLOPs的一半，将 $w[0] \times x[0]$ 是为一个乘法累加或一个MACC&lt;/li&gt;
&lt;li&gt;Params：模型参数量，单位通常为M&lt;/li&gt;
&lt;li&gt;MAC：内存访问代价（Memory Access Cost），指的是输入单个样本，模型完成一次前向传播所发生的内存交换总量，即模型的空间复杂度，单位是Byte&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;设计原则&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;降低计算复杂度&lt;/strong&gt;: 减少FLOPs和MACs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;减少参数量&lt;/strong&gt;: 降低模型大小和内存占用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬件友好&lt;/strong&gt;: 考虑实际硬件架构特性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;平衡精度和效率&lt;/strong&gt;: 在模型性能和资源消耗间取得平衡&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化网络结构&lt;/strong&gt;: 使用更高效的基本模块替代传统卷积&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;MobileNet 系列&lt;/h2&gt;
&lt;h3&gt;MobileNet v1&lt;/h3&gt;
&lt;p&gt;MobileNet v1 的核心创新是将标准卷积分解为深度可分离卷积&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;深度卷积(Depthwise Convolution)&lt;/strong&gt;: 对每个输入通道单独进行空间卷积&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逐点卷积(Pointwise Convolution)&lt;/strong&gt;: 使用1×1卷积进行通道融合&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种分解大幅降低了计算复杂度：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;标准卷积: $D_K \times D_K \times M \times N \times D_F \times D_F$&lt;/li&gt;
&lt;li&gt;深度可分离卷积: $D_K \times D_K \times M \times D_F \times D_F + M \times N \times D_F \times D_F$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中，$D_K$ 是卷积核尺寸，$M$ 是输入通道数，$N$ 是输出通道数，$D_F$ 是特征图尺寸。&lt;/p&gt;
&lt;p&gt;计算复杂度降低了约 8-9 倍，而精度损失相对较小。&lt;/p&gt;
&lt;h3&gt;MobileNet v2&lt;/h3&gt;
&lt;p&gt;MobileNet v2 引入了两个重要改进：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;倒置残差结构(Inverted Residual)&lt;/strong&gt;: 先扩展通道数，再进行卷积，最后压缩回原始维度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线性瓶颈(Linear Bottleneck)&lt;/strong&gt;: 在瓶颈层去掉了ReLU激活函数，防止特征信息丢失&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些改进使MobileNet v2在保持低计算量的同时，提高了特征表达能力。&lt;/p&gt;
&lt;h2&gt;ShuffleNet 系列&lt;/h2&gt;
&lt;h3&gt;ShuffleNet v1&lt;/h3&gt;
&lt;p&gt;ShuffleNet v1主要创新点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;组卷积(Group Convolution)&lt;/strong&gt;: 将输入通道分组进行卷积，减少计算量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通道混洗(Channel Shuffle)&lt;/strong&gt;: 在组间进行特征混合，解决组卷积造成的信息流通受限问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通道混洗操作示例：假设有9个通道分为3组，先将通道重塑为(3,3)形状，然后转置为(3,3)，最后重塑回9个通道，实现了跨组特征交流。&lt;/p&gt;
&lt;h3&gt;ShuffleNet v2&lt;/h3&gt;
&lt;p&gt;ShuffleNet v2不仅关注FLOPs，还考虑了实际运行速度的影响因素：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;内存访问成本(MAC)&lt;/strong&gt;: 提出通道数输入输出应相等的原则&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;并行度&lt;/strong&gt;: 避免过多的组卷积分支&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬件友好&lt;/strong&gt;: 减少了分支结构和碎片化操作&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;核心模块是通道分离(Channel Split)和通道混洗的组合，实现了更高效的特征提取。&lt;/p&gt;
&lt;h2&gt;其他轻量化网络架构&lt;/h2&gt;
&lt;h3&gt;SqueezeNet&lt;/h3&gt;
&lt;p&gt;通过&quot;Fire模块&quot;实现压缩，该模块首先使用1×1卷积减少通道数(&quot;squeeze&quot;)，然后并行应用1×1和3×3卷积(&quot;expand&quot;)。虽然参数量极小，但实际速度不尽如人意。&lt;/p&gt;
&lt;h3&gt;MnasNet/EfficientNet&lt;/h3&gt;
&lt;p&gt;使用神经架构搜索(NAS)自动设计轻量级网络，优化精度、延迟和计算量的平衡。EfficientNet还引入了复合缩放方法，同时缩放网络的宽度、深度和分辨率。&lt;/p&gt;
&lt;h3&gt;GhostNet&lt;/h3&gt;
&lt;p&gt;基于&quot;幽灵&quot;(Ghost)模块设计，该模块首先使用少量卷积生成内在特征，然后通过线性变换生成更多廉价特征。大幅降低计算成本的同时保持表达能力。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;参考资料&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1506.02626&quot;&gt;Han, S., Pool, J., Tran, J., &amp;amp; Dally, W. (2015). Learning both Weights and Connections for Efficient Neural Networks. NIPS 2015.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1803.03635&quot;&gt;Frankle, J., &amp;amp; Carbin, M. (2019). The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks. ICLR 2019.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1704.04861&quot;&gt;Howard, A. G., Zhu, M., Chen, B., et al. (2017). MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1801.04381&quot;&gt;Sandler, M., Howard, A., Zhu, M., Zhmoginov, A., &amp;amp; Chen, L.C. (2018). MobileNetV2: Inverted Residuals and Linear Bottlenecks. CVPR 2018.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1707.01083&quot;&gt;Zhang, X., Zhou, X., Lin, M., &amp;amp; Sun, J. (2018). ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices. CVPR 2018.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1807.11164&quot;&gt;Ma, N., Zhang, X., Zheng, H.T., &amp;amp; Sun, J. (2018). ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design. ECCV 2018.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1712.05877&quot;&gt;Jacob, B., Kligys, S., Chen, B., et al. (2018). Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference. CVPR 2018.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1510.00149&quot;&gt;Han, S., Mao, H., &amp;amp; Dally, W. J. (2016). Deep Compression: Compressing Deep Neural Networks with Pruning, Trained Quantization and Huffman Coding. ICLR 2016.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1602.07360&quot;&gt;Iandola, F.N., Han, S., Moskewicz, M.W., et al. (2016). SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and &amp;lt;0.5MB model size.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1905.11946&quot;&gt;Tan, M., &amp;amp; Le, Q.V. (2019). EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks. ICML 2019.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1911.11907&quot;&gt;Han, K., Wang, Y., Tian, Q., et al. (2020). GhostNet: More Features from Cheap Operations. CVPR 2020.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1810.12281&quot;&gt;Alizadeh, M., Behboodi, A., van Baalen, M., et al. (2019). Gradient ℓ1 Regularization for Quantization Robustness. ICLR 2019.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>机器人学基础 第五章 机械臂动力学</title><link>https://adalovelemon.github.io/posts/content/coursenotes/robotics/chapter-5/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/robotics/chapter-5/</guid><pubDate>Wed, 21 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;机械臂的动力学部分&lt;/p&gt;
&lt;h1&gt;5.1 机械臂动力学的目标&lt;/h1&gt;
&lt;p&gt;机械臂动力学的目标是在已知机械臂模型（关节设置）和各个连杆和质量分布的情况下，给定机械臂末端期望实现的位姿、速度、加速度，求在各个关节的制动器上应该施加的力或力矩。&lt;/p&gt;
&lt;h1&gt;5.2 迭代法求解动力学问题&lt;/h1&gt;
&lt;h2&gt;5.2.1 符号约定&lt;/h2&gt;
&lt;p&gt;对于连杆 $i$，有三个与之相关联的框架，分别是&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;连杆 $i$ 的在关节轴 $i$ 处的固连框架 ${i}$&lt;/li&gt;
&lt;li&gt;在连杆 $i$ 的质心 $c_i$ 处的质心固连框架 ${c_i}$，其朝向与框架 ${i}$ 一致&lt;/li&gt;
&lt;li&gt;在关节轴 $i+1$ 处的连杆 $i+1$ 的固连框架 ${i+1}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;约定连杆 $i$ 的质量为 $m_i$，相对于质心框架 ${c_i}$ 的惯性张量为 $^{c_i}I_i$; 相对于刚体自身框架，关节 $i$ 处的线速度和线加速度为 $^{i}v_i, \ ^{i}\dot v_i$，连杆 $i$ 的角速度及角加速度为 $^{i}\omega_i, \ ^{i}\dot \omega$，连杆 $i - 1$ 施加在 连杆 $i$ 上的力和力矩为 $^{i}f_i, \ ^{i}n_i$，连杆 $i$ 施加在 连杆 $i+1$ 上的力和力矩为 $^{i}f_{i+1},\ ^{i} n_{i+1}$&lt;/p&gt;
&lt;h2&gt;5.2.2 牛顿-欧拉动力学方程&lt;/h2&gt;
&lt;p&gt;对于连杆 $i$，有
$$
\begin{aligned}
\vec{f}&lt;em&gt;i - \vec{f}&lt;/em&gt;{i+1} + m_i \vec{g} &amp;amp;= m_i \dot{\vec{v}}&lt;em&gt;{c_i} \
^{c_i}\left[\vec{n}&lt;em&gt;i - \vec{n}&lt;/em&gt;{i+1} + (\vec{p}&lt;em&gt;i - \vec{p}&lt;/em&gt;{c_i}) \times \vec{f}&lt;em&gt;i - (\vec{p}&lt;/em&gt;{i+1} - \vec{p}&lt;/em&gt;{c_i} \times \vec{f}&lt;em&gt;{i+1})\right] &amp;amp;= \ ^{c_i}I&lt;/em&gt;{c_i}\ ^{c_i} \dot\omega_{c_i} + \ ^{c_i}\omega_{c_i} \times \ ^{c_i}I_{c_i}\ ^{c_i}\omega_{c_i}
\end{aligned}
$$
其中 $^{c_i}[\cdot]$ 表示将物理矢量表达成框架 ${c_i}$ 中的数学向量。&lt;/p&gt;
&lt;p&gt;由方程可知，为了求解 $f_i$ 与 $f_{i+1}$，需要先求出 $\dot{\vec{v}}&lt;em&gt;{c_i}$ 、 $\vec{\omega}&lt;/em&gt;{c_i}$ 和 $\dot{\vec{\omega}}_{c_i}$&lt;/p&gt;
&lt;h2&gt;5.2.3 前向迭代求速度&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;线加速度&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;按照上一章的速度传播结果，可以先求出关节 $i$ 处的速度 $v_i$，然后，求出连杆 $i$ 质心处的速度 $v_{c_i}$。注意质心框架 ${c_i}$ 与 框架 ${i}$ 的朝向一致，
$$
\begin{aligned}
^{c_i} v_{c_i} &amp;amp;= \ ^{i}v_{c_i} = \ ^{i}v_i + \ ^{i}\omega_i \times \ ^{i}p_{c_i}
\end{aligned}
$$
其中，$^{i}p_{c_i}$ 表示质心 $c_i$ 在 框架 ${i}$ 中的位矢的向量表达。直接对时间 $t$ 求导，可以得到线加速度为
$$
^{c_i} \dot{v}&lt;em&gt;{c_i} = \ ^{i}\dot{v}&lt;/em&gt;{c_i} = \ ^{i}\dot{v}_i + \ ^{i}\dot{\omega}&lt;em&gt;i \times \ ^{i}p&lt;/em&gt;{c_i} + \ ^{i}{\omega}&lt;em&gt;i \times (\ ^{i}{\omega}&lt;em&gt;i \times \ ^{i}p&lt;/em&gt;{c_i})
$$
**其中最后一项是对 $^{i}p&lt;/em&gt;{c_i}$ 求导的结果，这里是由于朝向发生了变化因此有一项导数。**&lt;/p&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;角速度及角加速度&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于
$$
\begin{aligned}
^{c_i} \omega_{c_i} = \ ^{i}\omega_{c_i} = \ ^{i}\omega_i
\end{aligned}
$$
有
$$
^{c_i} \dot\omega_{c_i} = \ ^{i}\dot\omega_{c_i} = \ ^{i}\dot\omega_i
$$&lt;/p&gt;
&lt;h2&gt;5.2.4 反向迭代求动力&lt;/h2&gt;
&lt;p&gt;对于连杆 $i$，假设此时 $^{i+1}f_{i+1}$ 和 $^{i+1}n_{i+1}$，有
$$
\begin{aligned}
^{i}F_i &amp;amp; = m_i \ ^{c_i}\dot v_{c_i}\quad \text{惯性力， 作用于质心} c_i\
^{i}N_i &amp;amp; = \ ^{c_i}I \ ^{i} \dot \omega_{i} + \ ^{i}\omega_i \times \ ^{c_i}I \ ^{i}\omega_i  \quad \text{惯性力矩}\
^{i}f_i &amp;amp;=\ ^{i}F_i + \ ^{i}&lt;em&gt;{i+1}R \ ^{i+1}f&lt;/em&gt;{i+1} - m_i\ ^{i}g \quad \text{牛顿第二定律}\
^{i}n_i &amp;amp;=\ ^{i}N_i + \underbrace{\ ^{i}p_{c_i} \times \ ^{i}F_i}&lt;em&gt;{N_i\text{的平移附加项}} + \ ^{i}&lt;/em&gt;{i+1}R \ ^{i+1}n_{i+1}  + \underbrace{\ ^{i}p_{i+1} \times \ ^{i}&lt;em&gt;{i+1}R \ ^{i+1}f&lt;/em&gt;{i+1}}&lt;em&gt;{n&lt;/em&gt;{i+1}\text{的平移附加项}} - \ ^{i}p_{c_i} \times m_i \ ^{i}g
\quad\text{相对于关节}i\text{处的力矩方程}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/forces2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;注意这里的重力项 $m_i\ ^{i}g $ 和 $\ ^{i}p_{c_i} \times m_i \ ^{i}g$ 需要依据问题环境来设置，有时候可以不用考虑重力项&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意重力项是被减去的！(例如，设置 $^{0}g = \begin{bmatrix} 0 \ -|g| \ 0\end{bmatrix}$ )&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;有时候还需要考虑粘滞阻力等其他外力，此时需要对方程做相应修改。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;迭代的初始条件通常是机械臂末端是自由运动的，即
$$
\begin{aligned}
^{N+1}f_{N+1} &amp;amp;= 0\
^{N+1}n_{N+1} &amp;amp;= 0
\end{aligned}
$$
这里 $N$ 是关节个数，设置为大写是为了与力矩向量 $n$ 区分。&lt;/p&gt;
&lt;h2&gt;5.2.5 关节提供的动力&lt;/h2&gt;
&lt;p&gt;关节只负责提供 Z 轴方向的动力，于是可以求出&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;棱柱关节需要提供的力 $\tau_i = \ ^{i}f_i ^\top \ ^{i}\mathbf{\hat{z}}_i$&lt;/li&gt;
&lt;li&gt;旋转关节需提供的力矩 $\tau_i = \ ^{i}n_i ^\top \ ^{i}\mathbf{\hat{z}}_i$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5.2.6 特别需要注意的地方&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;特别需要注意，这里面对速度 $v$ 和 $\omega$ 是不可以直接对向量表达求导的，这样得到的结果是错误的！！！&lt;/strong&gt; 具体的原因是在整个运动的过程中，框架也是在发生变化的，对应于&lt;strong&gt;物理学中矢量的求导是需要分矢量的标量大小和矢量方向求导&lt;/strong&gt;两个部分的。下面的例题将说明这一点。&lt;/p&gt;
&lt;p&gt;(角速度部分的 $\dot\theta$ 由于是对标量求导，所以不会存在这个问题)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;平面2R的动力学模型&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;简化质量分布，假设每个连杆的质量都集中在连杆远端的一点，分别是 $m_1$ 和 $m_2$，重力沿着 ${0}$ 框架 Y 轴负方向。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;前向运动学方程为
$$
\begin{aligned}
^{0}&lt;em&gt;{1}T &amp;amp;= \begin{bmatrix} c\theta_1 &amp;amp; -s\theta_1 &amp;amp; 0 &amp;amp; 0\ s\theta_1 &amp;amp; c\theta_1 &amp;amp;0 &amp;amp;0\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
^{1}&lt;/em&gt;{2}T &amp;amp;= \begin{bmatrix} c\theta_2 &amp;amp; -s\theta_2 &amp;amp; 0 &amp;amp; L_1\ s\theta_2 &amp;amp; c\theta_2 &amp;amp;0 &amp;amp;0\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
^{2}&lt;em&gt;{3}T &amp;amp;= \begin{bmatrix} 1 &amp;amp; 0 &amp;amp; 0 &amp;amp; L_2\ 0 &amp;amp; 1 &amp;amp;0 &amp;amp;0\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
^{0}&lt;/em&gt;{3}T(\theta_1, \theta_2) &amp;amp;= \begin{bmatrix} c(\theta_1 + \theta_2) &amp;amp; -s(\theta_1 + \theta_2) &amp;amp; 0 &amp;amp; L_1 c\theta_1 + L_2 c(\theta_1 + \theta_2)\ s(\theta_1 + \theta_2) &amp;amp; c(\theta_1 + \theta_2) &amp;amp;0 &amp;amp;L_1 s\theta_1 + L_2 s(\theta_1 + \theta_2)\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;前向速度传播的结果为 (初始条件 $^0\omega_0 = 0, \ ^0v_0 = 0$)，利用迭代公式，有
$$
\begin{aligned}
\ ^{1}\omega_1 &amp;amp;= \ ^{1}&lt;em&gt;{0}R \ ^{0}w_0 + \dot\theta_1 \ ^{1}\mathbf{\hat{z}}&lt;em&gt;1 = \begin{bmatrix}0\ 0\ \dot\theta_1\end{bmatrix}\
\ ^{2}\omega_2 &amp;amp;= \ ^{2}&lt;/em&gt;{1}R \ ^{1}w_1 + \dot\theta_2 \ ^{2}\mathbf{\hat{z}}&lt;em&gt;2 = \begin{bmatrix} 0 \ 0\ \dot\theta_1 + \dot\theta_2\end{bmatrix} \
\
\ ^{1}v_1 &amp;amp;= \ ^{1}&lt;/em&gt;{0}R(\ ^{0}v_0 + \ ^{0}\omega_0 \times \ ^{0} p_1) = 0\
\ ^{2}v_2 &amp;amp;= \ ^{2}&lt;/em&gt;{1}R(\ ^{1}v_1 + \ ^{1}\omega_1 \times \ ^{1}p_2) = \begin{bmatrix} L_1\dot\theta_1 s\theta_2 \ L_1\dot\theta_1c\theta_2 \ 0\end{bmatrix} \&lt;/p&gt;
&lt;p&gt;^{1}v_{c_1} &amp;amp;= \ ^{1}v_1 + \ ^{1}\omega_1 \times \ ^{1}p_{c_1} = \begin{bmatrix} 0\0\\dot\theta_1\end{bmatrix} \times \begin{bmatrix} L_1 \0\0\end{bmatrix} = \begin{bmatrix} 0 \ L_1\dot\theta_1 \0\end{bmatrix} \
^{2}v_{c_2} &amp;amp;= \ ^{2}v_2 + \ ^{2}\omega_2 \times \ ^{2}p_{c_2} = \begin{bmatrix} L_1\dot\theta_1 s\theta_2 \ L_1\dot\theta_1c\theta_2 \ 0\end{bmatrix} + \begin{bmatrix}0 \0\ \dot\theta_1 + \dot\theta_2\end{bmatrix} \times \begin{bmatrix} L_2 \0\0\end{bmatrix} = \begin{bmatrix} L_1\dot\theta_1 s\theta_2 \ L_1\dot\theta_1c\theta_2 + L_2 (\dot\theta_1 + \dot\theta_2) \ 0\end{bmatrix}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;求导，有
$$
\begin{aligned}
\ ^{1}\dot\omega_1 &amp;amp;= \ ^{1}&lt;em&gt;{0}R \ ^{0}\dot \omega_0 + \ ^{1}&lt;/em&gt;{0}R \ ^{0}\omega_0 \times \dot\theta_1 \ ^{1}\mathbf{\hat{z}}&lt;em&gt;1 + \ddot\theta_1 \ ^{1}\mathbf{\hat{z}}&lt;em&gt;1 = \begin{bmatrix} 0 \ 0\ \ddot\theta_1\end{bmatrix} \
\ ^{2}\dot\omega_2 &amp;amp;= \ ^{2}&lt;/em&gt;{1}R \ ^{1}\dot\omega_1 + \ ^{2}&lt;/em&gt;{1}R \ ^{1}\omega_1\times \dot\theta_2 \ ^{2}\mathbf{\hat{z}}_2 + \ddot\theta_2 \ ^{2}\mathbf{\hat{z}}_2 = \begin{bmatrix} 0 \ 0\ \ddot\theta_1 + \ddot\theta_2\end{bmatrix} \&lt;/p&gt;
&lt;p&gt;\ ^{1}\dot v_1 &amp;amp;= \ ^{1}_{0}R(\ ^{0}\dot\omega_0 \times \ ^{0}p_1 + \ ^{0}\omega_0 \times (\ ^{0}\omega_0 \times \ ^{0}p_1) + \ ^{0}\dot v_0) = 0 \&lt;/p&gt;
&lt;p&gt;\ ^{1}\dot v_{c_1} &amp;amp;= \ ^{1}\dot\omega_1 \times \ ^{1}p_{c_1} + \ ^{1}\omega_1 \times (\ ^{1}\omega_1 \times \ ^{1}p_{c_1}) + \ ^{1}\dot v_{1} = \begin{bmatrix} -L_1\dot\theta_1^2 \ L_1\ddot\theta_1\0 \end{bmatrix} \&lt;/p&gt;
&lt;p&gt;\ ^{2}\dot v_2 &amp;amp;= \ ^{2}_{1}R(\ ^{1}\dot\omega_1 \times \ ^{1}p_2 + \ ^{1}\omega_1 \times (\ ^{1}\omega_1 \times \ ^{1}p_2) + \ ^{1}\dot v_1) = \begin{bmatrix} L_1\ddot\theta_1s\theta_2 - L_1\dot\theta_1^2c\theta_2 \ L_1\ddot\theta_1c\theta_2 + L_1\dot\theta_1^2s\theta_2 \ 0\end{bmatrix} \&lt;/p&gt;
&lt;p&gt;\ ^{2}\dot v_{c_2} &amp;amp;= \ ^{2}\dot\omega_2 \times \ ^{2}p_{c_2} + \ ^{2}\omega_2 \times (\ ^{2}\omega_2 \times \ ^{2}p_{c_2}) + \ ^{2}\dot v_{2} = \begin{bmatrix}-L_2(\dot\theta_1 + \dot\theta_2)^2 + L_1\ddot\theta_1s\theta_2 - L_1\dot\theta_1^2c\theta_2 \ L_2(\ddot\theta_1 + \ddot\theta_2) + L_1\ddot\theta_1c\theta_2 + L_1\dot\theta_1^2s\theta_2 \ 0 \end{bmatrix} \
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意到一个规律可以方便记忆公式，就是如果有矢量&lt;/strong&gt; $a = \begin{bmatrix} a_x \ a_y \ a_z\end{bmatrix}$ &lt;strong&gt;是在变化的框架中表达的，那么对这个向量求导时，除了有自身在当前框架的直接导数&lt;/strong&gt; $\begin{bmatrix} \dot a_x \ \dot a_y \ \dot a_z\end{bmatrix}$ &lt;strong&gt;外，还会有框架变化导致的导数&lt;/strong&gt; $\omega \times \begin{bmatrix} a_x \ a_y \a_z\end{bmatrix} $。这个规律适用于上面 $\dot\theta_2 \ ^{2}\mathbf{\hat{z}}&lt;em&gt;2$ 的导数计算和 $\ ^{1}\omega_1 \times \ ^{1} p&lt;/em&gt;{c_1}$ 的导数计算（这里面 $\ ^{1}\omega_1$ 的大小和框架变化的导数全含在了 $\ ^{1}\dot \omega_1$ 中，$\ ^{1}p_{c_1}$ 没有大小的变化，因此只有框架变化的导数 $(\omega \times p)$）。&lt;/p&gt;
&lt;p&gt;于是计算惯性力及惯性力矩
$$
\begin{aligned}
^{1}F_1 &amp;amp;= m_1 \ ^{1}\dot v_{c_1} = \begin{bmatrix} -m_1L_1\dot\theta_1^2 \ m_1L_1\ddot\theta_1\0 \end{bmatrix} \&lt;/p&gt;
&lt;p&gt;\ ^{2}F_2 &amp;amp;= m_2 \ ^{2}\dot v_{c_2} = \begin{bmatrix}-m_2L_2(\dot\theta_1 + \dot\theta_2)^2 + m_2L_1\ddot\theta_1s\theta_2 - m_2L_1\dot\theta_1^2c\theta_2 \ m_2L_2(\ddot\theta_1 + \ddot\theta_2) + m_2L_1\ddot\theta_1c\theta_2 + m_2L_1\dot\theta_1^2s\theta_2 \ 0 \end{bmatrix} \&lt;/p&gt;
&lt;p&gt;\ ^{1}N_1 &amp;amp;= \ ^{c_1}I \ ^{1} \dot \omega_{1} + \ ^{1}\omega_1 \times \ ^{c_1}I \ ^{1}\omega_1 = 0 \
\ ^{2}N_2 &amp;amp;= \ ^{c_2}I \ ^{2} \dot \omega_{2} + \ ^{2}\omega_2 \times \ ^{c_2}I \ ^{2}\omega_2 = 0
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;带入公式，结合 $\ ^{3} f_3 = 0, \ ^{3} n_3 = 0$ 得
$$
\begin{aligned}
\ ^{2}f_2 &amp;amp;= \ ^{2}F_2 + \ ^{2}&lt;em&gt;{3}R \ ^{3}f&lt;/em&gt;{3} - m_2\ ^{2}g = \begin{bmatrix}-m_2L_2(\dot\theta_1 + \dot\theta_2)^2 + m_2L_1\ddot\theta_1s\theta_2 - m_2L_1\dot\theta_1^2c\theta_2 \ m_2L_2(\ddot\theta_1 + \ddot\theta_2) + m_2L_1\ddot\theta_1c\theta_2 + m_2L_1\dot\theta_1^2s\theta_2 \ 0 \end{bmatrix} - m_2 \ ^{2}&lt;em&gt;{0}R\begin{bmatrix} 0 \ -g \ 0\end{bmatrix} = \begin{bmatrix}-m_2L_2(\dot\theta_1 + \dot\theta_2)^2 + m_2L_1\ddot\theta_1s\theta_2 - m_2L_1\dot\theta_1^2c\theta_2 + m_2gs(\theta_1 + \theta_2) \ m_2L_2(\ddot\theta_1 + \ddot\theta_2) + m_2L_1\ddot\theta_1c\theta_2 + m_2L_1\dot\theta_1^2s\theta_2 + m_2g c(\theta_1 + \theta_2)\ 0 \end{bmatrix}\
\ ^{1}f_1 &amp;amp;= \ ^{1}F_1 + \ ^{1}&lt;/em&gt;{2}R \ ^{2}f_{2} - m_1\ ^{1}g = \begin{bmatrix} -(m_1 + m_2)(L_1 \dot\theta_1^2 - gs\theta_1) - m_2L_2(\ddot\theta_1 + \ddot\theta_2)s\theta_2 - m_2L_2(\dot\theta_1 + \dot\theta_2)^2c\theta_2 \ (m_1 + m_2)(L_1 \ddot\theta_1 + gc\theta_1) + m_2L_2(\ddot\theta_1 + \ddot\theta_2)c\theta_2 - m_2L_2(\dot\theta_1 + \dot\theta_2)^2s\theta_2 \ 0\end{bmatrix}\
\
\ ^{2}n_2 &amp;amp;= \ ^{2}N_2 + \ ^{2}p_{c_2} \times \ ^{2}F_2 + \ ^{2}&lt;em&gt;{3}R \ ^{3}n_3 + \ ^{2}p&lt;/em&gt;{3} \times \ ^{2}&lt;em&gt;{3}R \ ^{3}f_3 - \ ^{2}p&lt;/em&gt;{c_2} \times m_2 \ ^{2}g\
&amp;amp;= \begin{bmatrix} 0 \ 0 \ m_2L_2^2(\ddot\theta_1 + \ddot\theta_2) + m_2L_1L_2\ddot\theta_1c\theta_2 + m_2L_1L_2\dot\theta_1^2 s\theta_2 + m_2gL_2c(\theta_1 + \theta_2)\end{bmatrix} \&lt;/p&gt;
&lt;p&gt;\ ^{1}n_1 &amp;amp;= \ ^{1}N_1 + \ ^{1}p_{c_1} \times \ ^{1}F_1 + \ ^{1}&lt;em&gt;{2}R \ ^{2}n_2 + \ ^{1}p&lt;/em&gt;{2} \times \ ^{1}&lt;em&gt;{2}R \ ^{2}f_2 - \ ^{1}p&lt;/em&gt;{c_1} \times m_1 \ ^{1}g\
&amp;amp;= \begin{bmatrix} 0 \ 0 \ m_1L_1^2\ddot\theta_1 \end{bmatrix} +  \begin{bmatrix} 0 \ 0 \ m_2L_2^2(\ddot\theta_1 + \ddot\theta_2) + m_2L_1L_2\ddot\theta_1c\theta_2 + m_2L_1L_2\dot\theta_1^2 s\theta_2 + m_2gL_2c(\theta_1 + \theta_2)\end{bmatrix} + \begin{bmatrix} 0 \ 0\ m_2L_1^2\ddot\theta_1 + m_2gL_1c\theta_1 - m_2L_1L_2(\dot\theta_1 + \dot\theta_2)^2s\theta_2 + m_2L_1L_2(\ddot\theta_1 + \ddot\theta_2)c\theta_2\end{bmatrix} - \begin{bmatrix} 0 \ 0 \ - m_1gL_1c\theta_1\end{bmatrix} \
&amp;amp;= \begin{bmatrix} 0 \ 0 \(m_1 + m_2)L_1^2\ddot\theta_1 + m_2L_2^2(\ddot\theta_1 + \ddot\theta_2) + m_2L_1L_2\ddot\theta_1c\theta_2  + m_2L_1L_2\dot\theta_1^2s\theta_2 + m_2gL_2c(\theta_1 + \theta_2) + (m_1 + m_2)gL_1c\theta_1 - m_2L_1L_2(\dot\theta_1 + \dot\theta_2)^2s\theta_2 + m_2L_1L_2(\ddot\theta_1 + \ddot\theta_2)c\theta_2\end{bmatrix}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;以上就是迭代的结果，可以说是相当的复杂。取力矩向量在 Z 轴方向分量，即可得到关节需要提供的力矩大小。&lt;/p&gt;
&lt;p&gt;化简得到
$$
\boxed{
\begin{aligned}
\tau_1 &amp;amp;=  m_1L_1^2\ddot\theta_1 + m_2L_1^2\ddot\theta_1 +m_2L_2^2\ddot\theta_1 + m_2L_2^2\ddot\theta_2 + 2m_2L_1L_2\ddot\theta_1c\theta_2 - 2m_2L_1L_2\dot\theta_1\dot\theta_2s\theta_2 + m_2L_1L_2\ddot\theta_2c\theta_2 - m_2L_1L_2\dot\theta_2^2s\theta_2  + m_1gL_1c\theta_1 + m_2gL_1c\theta_1 + m_2gL_2c(\theta_1+\theta_2) \
\tau_2 &amp;amp;= m_2L_2^2\ddot\theta_1 + m_2L_2^2\ddot\theta_2 + m_2L_1L_2\ddot\theta_1c\theta_2  + m_2L_1L_2\dot\theta_1^2s\theta_2 + m_2gL_2c(\theta_1 + \theta_2)
\end{aligned}
}
$$&lt;/p&gt;
&lt;h1&gt;5.3 Lagrangian 方程法求解动力学问题&lt;/h1&gt;
&lt;p&gt;Lagrangian 方程是个好东西，物理系的同学们都爱用。这个方程大大简化了物理问题的分析，将物理的标准坐标系问题转换成了对任意广义坐标设置的数学问题。具体的原理部分可以自行查找资料，例如参考这个链接&lt;a href=&quot;https://zhuanlan.zhihu.com/p/1900243092348597498&quot;&gt;Lagrangian方程&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;5.3.1 广义坐标&lt;/h2&gt;
&lt;p&gt;简单来说，广义坐标就是数学中&lt;strong&gt;相互独立的自变量&lt;/strong&gt;，是衡量物体运动变化的变量。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/Lagrange.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图中的 $\varphi_1, \varphi_2$ 就是广义坐标，经典的正交坐标 $(x_A, y_A), (x_B, y_B)$ 就可以用这两个广义坐标表示
$$
\begin{cases}
x_A = L_1 s\varphi_1 \
y_A = L_1 c\varphi_1 \
x_B = L_1 s\varphi_1 + L_2 s\varphi_2 \
y_B = L_1 c\varphi_1 + L_2 c\varphi_2
\end{cases}
$$&lt;/p&gt;
&lt;h2&gt;5.3.2 Lagrangian 方程&lt;/h2&gt;
&lt;p&gt;设 $\mathcal{K}$ 为系统动能，$\mathcal{P}$ 为系统势能，定义 Lagrangian 函数为
$$
\mathcal{L} = \mathcal{K} - \mathcal{P}
$$
则有方程
$$
\boxed{
\frac{\mathrm{d}}{\mathrm{d} t}(\frac{\partial L}{\partial \dot q_k}) - \frac{\partial L}{\partial q_k} = \tau_k, \quad k = 1, \dots, n
}
$$
其中 $\tau = \begin{bmatrix} \tau_1 \ \vdots \ \tau_n\end{bmatrix}$ 为非保守广义力，$q = \begin{bmatrix} q_1 \ \vdots \ q_n\end{bmatrix}$ 为广义坐标，$\tau_k$ 是与 $q_k$ 对应的非保守广义力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;平面2R的Lagrangian解法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设广义坐标为两个关节变量 $\theta_1$ 和 $\theta_2$，则
$$
\begin{cases}
x_1 = L_1c\theta_1 \
y_1 = L_1s\theta_1 \
x_2 = L_1c\theta_1 + L_2c(\theta_1 + \theta_2) \
y_2 = L_1s\theta_1 + L_2s(\theta_1 + \theta_2)
\end{cases}
$$
对时间求导 (在绝对框架中的表达，不需要考虑框架的变化项)，得到
$$
\begin{cases}
\dot x_1 = - L_1\dot\theta_1s\theta_1  \
\dot y_1 = L_1\dot\theta_1c\theta_1 \
\dot x_2 = - L_1\dot\theta_1s\theta_1 - L_2(\dot\theta_1 + \dot\theta_2)s(\theta_1 + \theta_2) \
\dot y_2 = L_1\dot\theta_1c\theta_1 + L_2(\dot\theta_1 + \dot\theta_2)c(\theta_1 + \theta_2)
\end{cases}
$$
于是
$$
\begin{aligned}
v_1^\top v_1 &amp;amp;= \dot x_1^2 + \dot y_1^2 = L_1^2\dot\theta_1^2 \
v_2^\top v_2 &amp;amp;= \dot x_2^2 + \dot y_2^2 = L_1^2\dot\theta_1^2 + L_2^2(\dot\theta_1 + \dot\theta_2)^2 + 2L_1L_2\dot\theta_1(\dot\theta_1 + \dot\theta_2)c\theta_2
\end{aligned}
$$
由此可得动能、势能 (零势能点可以任意选取)、Lagrangian 函数分别为
$$
\begin{aligned}
\mathcal{K} &amp;amp;= \frac{1}{2}m_1v_1^\top v_1 + \frac{1}{2}m_2v_2^\top v_2 \
&amp;amp;= \frac{1}{2}(m_1 + m_2) L_1^2 \dot\theta_1^2 + \frac{1}{2}m_2L_2^2(\dot\theta_1 + \dot\theta_2)^2 + m_2L_1L_2\dot\theta_1(\dot\theta_1 + \dot\theta_2)c\theta_2 \
\
\mathcal{P} &amp;amp;= m_1gy_1 + m_2gy_2 = m_1gL_1s\theta_1 + m_2g(L_1s\theta_1 + L_2 s(\theta_1 + \theta_2)) \
\
\mathcal{L} &amp;amp;= \mathcal{K} - \mathcal{P} \
&amp;amp;= \frac{1}{2}(m_1 + m_2) L_1^2 \dot\theta_1^2 + \frac{1}{2}m_2L_2^2(\dot\theta_1 + \dot\theta_2)^2 + m_2L_1L_2\dot\theta_1(\dot\theta_1 + \dot\theta_2)c\theta_2 - m_1gL_1s\theta_1 - m_2g(L_1s\theta_1 + L_2 s(\theta_1 + \theta_2))
\end{aligned}
$$
于是，分别求出
$$
\begin{aligned}
\frac{\partial \mathcal{L}}{\partial \dot\theta_1} &amp;amp;= (m_1 + m_2) L_1^2 \dot\theta_1 + m_2 L_2^2(\dot\theta_1 + \dot\theta_2) + m_2L_1L_2(\dot\theta_1 + \dot\theta_2)c\theta_2 + m_2L_1L_2\dot\theta_1c\theta_2 \&lt;/p&gt;
&lt;p&gt;\frac{\partial \mathcal{L}}{\partial \dot\theta_2} &amp;amp;= m_2L_2^2(\dot\theta_1 + \dot\theta_2) + m_2L_1 L_2\dot\theta_1 c\theta_2 \&lt;/p&gt;
&lt;p&gt;\frac{\partial L}{\partial \theta_1} &amp;amp;= -(m_1+m_2)gL_1c\theta_1 -m_2gL_2c(\theta_1 + \theta_2) \&lt;/p&gt;
&lt;p&gt;\frac{\partial L}{\partial \theta_2} &amp;amp;= -m_2L_1L_2\dot\theta_1(\dot\theta_1 + \dot\theta_2)s\theta_2 - m_2gL_2c(\theta_1 + \theta_2)
\end{aligned}
$$
接着计算
$$
\begin{aligned}
\frac{\mathrm{d}}{\mathrm{d} t}(\frac{\partial \mathcal{L}}{\partial \dot\theta_1}) &amp;amp;= m_1L_1^2\ddot\theta_1 + m_2L_1^2\ddot\theta_1 +m_2L_2^2\ddot\theta_1 + m_2L_2^2\ddot\theta_2 + 2m_2L_1L_2\ddot\theta_1c\theta_2 - 2m_2L_1L_2\dot\theta_1\dot\theta_2s\theta_2 + m_2L_1L_2\ddot\theta_2c\theta_2 - m_2L_1L_2\dot\theta_2^2s\theta_2 \&lt;/p&gt;
&lt;p&gt;\frac{\mathrm{d}}{\mathrm{d} t}(\frac{\partial \mathcal{L}}{\partial \dot\theta_2}) &amp;amp;= m_2L_2^2(\ddot\theta_1 + \ddot\theta_2) + m_2L_1L_2\ddot\theta_1c\theta_2 - m_2L_1L_2\dot\theta_1 \dot\theta_2 s\theta_2
\end{aligned}
$$
于是得出动力学方程为
$$
\begin{aligned}
\tau_1 &amp;amp;=  m_1L_1^2\ddot\theta_1 + m_2L_1^2\ddot\theta_1 +m_2L_2^2\ddot\theta_1 + m_2L_2^2\ddot\theta_2 + 2m_2L_1L_2\ddot\theta_1c\theta_2 - 2m_2L_1L_2\dot\theta_1\dot\theta_2s\theta_2 + m_2L_1L_2\ddot\theta_2c\theta_2 - m_2L_1L_2\dot\theta_2^2s\theta_2  + m_1gL_1c\theta_1 + m_2gL_1c\theta_1 + m_2gL_2c(\theta_1 + \theta_2) \
\tau_2 &amp;amp;= m_2L_2^2\ddot\theta_1 + m_2L_2^2\ddot\theta_2 + m_2L_1L_2\ddot\theta_1c\theta_2 - m_2L_1L_2\dot\theta_1 \dot\theta_2 s\theta_2 + m_2L_1L_2\dot\theta_1(\dot\theta_1 + \dot\theta_2)s\theta_2 + m_2gL_2c(\theta_1 + \theta_2)
\end{aligned}
$$
化简得到
$$
\boxed{
\begin{aligned}
\tau_1 &amp;amp;=  m_1L_1^2\ddot\theta_1 + m_2L_1^2\ddot\theta_1 +m_2L_2^2\ddot\theta_1 + m_2L_2^2\ddot\theta_2 + 2m_2L_1L_2\ddot\theta_1c\theta_2 - 2m_2L_1L_2\dot\theta_1\dot\theta_2s\theta_2 + m_2L_1L_2\ddot\theta_2c\theta_2 - m_2L_1L_2\dot\theta_2^2s\theta_2  + m_1gL_1c\theta_1 + m_2gL_1c\theta_1 + m_2gL_2c(\theta_1+\theta_2) \
\tau_2 &amp;amp;= m_2L_2^2\ddot\theta_1 + m_2L_2^2\ddot\theta_2 + m_2L_1L_2\ddot\theta_1c\theta_2  + m_2L_1L_2\dot\theta_1^2s\theta_2 + m_2gL_2c(\theta_1 + \theta_2)
\end{aligned}
}
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;注意，动能项中含有一个 $\cos\theta_2$，千万不要因为忽略这一项的导数&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算不出错的最好办法是不要尝试去构造对称的式子，全部化成乘积相加的形式&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通过观察各项下标的差异进行比对&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>AI-Sys 2 张量计算、计算机体系结构和编译优化</title><link>https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter2/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter2/</guid><pubDate>Wed, 21 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;张量计算，计算机体系结构，编译器与编译优化&lt;/p&gt;
&lt;h1&gt;张量计算&lt;/h1&gt;
&lt;p&gt;简单地说，张量可以理解为是数字的一类阵列，例如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;零阶张量是标量&lt;/li&gt;
&lt;li&gt;一阶张量是向量 (行、列)&lt;/li&gt;
&lt;li&gt;二阶张量是矩阵 (表格)&lt;/li&gt;
&lt;li&gt;三阶张量是一种长方体排列形式的阵列 (立体)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;深度学习中的绝大部分计算都可以表示为张量计算的形式，下面给出的例子中数学公式部分将主要用矩阵形式介绍，代码部分张量的阶数视情况而定&lt;/p&gt;
&lt;h2&gt;全连接层（线性层）&lt;/h2&gt;
&lt;p&gt;设输入 $X \in \mathbb{R}^{N \times d_{in}}$，$N$ 代表样本个数，$d_{in}$ 代表每一个样本的数据通道维度。线性层可以用一个线性映射表示，设权重 $W \in \mathbb{R}^{d_{out} \times d_{in}}$，偏置 $b \in \mathbb{R}^{d_{out}}$，则线性层的输出为
$$
Y = XW^\top + b \in \mathbb{R}^{N \times d_{out}}
$$
这里 $W$ 不直接采用 $\mathbb{R}^{d_{in} \times d_{out}}$ 的形式是因为，当只有单个样本 $x \in \mathbb{R}^{d_{in}}$ 作为输入时，对应输出可以表示为
$$
y = Wx + b \in \mathbb{R}^{d_{out}}
$$
这个形式更加符合数学上的习惯。&lt;/p&gt;
&lt;p&gt;在 PyTorch 中，线性层是作用在张量 $\mathsf{X}$ 的最后一个维度上，即其权重矩阵只与最后一个维度相乘，输入张量其他维度的形状大小保持不变，得到输出 $\mathsf{Y}$&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# PyTorch 中线性层的权重形状也是按照 (out_features, in_features) 的形式存储的
linear = nn.Linear(5, 10)
print(linear.weight.shape) # weight 形状 (10, 5)

X = torch.ones(4, 3, 5, 5) # X 形状 (4, 3, 5, 5)
Y = linear(X)
print(Y.shape)  # Y 形状 (4, 3, 5, 10)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;卷积层&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://img-blog.csdnimg.cn/20200601115557625.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;设输入 $X \in \mathbb{R}^{B \times C_{in} \times H_{in} \times W_{in}}$，其中 $B$ 是批量大小，$C_{in}$ 是输入通道数，$H_{in}$ 和 $W_{in}$ 分别是输入特征图的高度和宽度。卷积核 $K \in \mathbb{R}^{C_{out} \times C_{in} \times k_h \times k_w}$，其中 $C_{out}$ 是输出通道数，$k_h$ 和 $k_w$ 分别是卷积核的高度和宽度。则二维卷积的输出 $Y \in \mathbb{R}^{B \times C_{out} \times H_{out} \times W_{out}}$ 可以表示为&lt;/p&gt;
&lt;p&gt;$$
Y_{n,c_{out},h,w} = \sum_{c_{in}=0}^{C_{in}-1} \sum_{i=0}^{k_h-1} \sum_{j=0}^{k_w-1} K_{c_{out},c_{in},i,j} \cdot X_{n,c_{in},h+i-\frac{k_h-1}{2},w+j-\frac{k_w-1}{2}}
$$&lt;/p&gt;
&lt;p&gt;上式假设使用了适当的填充（padding）使输出尺寸与输入相同。实际应用中，输出尺寸还受步长（stride）和填充（padding）参数的影响，&lt;/p&gt;
&lt;p&gt;$$
H_{out} = \lfloor \frac{H_{in} + 2 \times padding - k_h}{stride} + 1 \rfloor
$$&lt;/p&gt;
&lt;p&gt;$$
W_{out} = \lfloor \frac{W_{in} + 2 \times padding - k_w}{stride} + 1 \rfloor
$$&lt;/p&gt;
&lt;p&gt;在 PyTorch 中，卷积层可以如下使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 创建一个卷积层，输入通道3，输出通道16，卷积核尺寸3x3
conv = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)
print(conv.weight.shape)  # 形状 (16, 3, 3, 3)

X = torch.randn(32, 3, 28, 28)  # 批量大小32，3通道，28x28图像
Y = conv(X)
print(Y.shape)  # 形状 (32, 16, 28, 28)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;卷积运算也可以表示成矩阵运算的形式，通过重组输入张量，卷积计算可以被转换成矩阵乘法&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/conv1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在硬件上的卷积实现也是采用的矩阵形式，通过将输入特征图的局部感受野展开称列（如图，各列存在元素的重叠），将卷积核展开成行的形式，就能够使用高效的矩阵乘法得到卷积后的结果。（显然行列的长度不可能没有限制，因此计算是有分块的）&lt;/p&gt;
&lt;h2&gt;Attention 机制&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;assets/attn.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Attention 机制是现代深度学习模型中的关键组件，特别是在 Transformer 架构中。设输入序列 $X \in \mathbb{R}^{N \times d_{in}}$，其中 $N$ 是序列长度，$d_{in}$ 是每个元素的维度。Attention 的计算过程如下：&lt;/p&gt;
&lt;p&gt;首先，将输入 $X$ 线性映射为查询 (Query)、键 (Key) 和值 (Value) 三个矩阵：
$$
Q = XW_Q \in \mathbb{R}^{N \times d_k}, \quad K = XW_K \in \mathbb{R}^{N \times d_k}, \quad V = XW_V \in \mathbb{R}^{N \times d_v}
$$&lt;/p&gt;
&lt;p&gt;其中，$W_Q \in \mathbb{R}^{d_{in} \times d_k}$，$W_K \in \mathbb{R}^{d_{in} \times d_k}$，$W_V \in \mathbb{R}^{d_{in} \times d_v}$ 是可学习的参数矩阵。&lt;/p&gt;
&lt;p&gt;然后，计算注意力权重并应用于值矩阵：
$$
O = \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V \in \mathbb{R}^{N \times d_v}
$$&lt;/p&gt;
&lt;p&gt;其中 $\sqrt{d_k}$ 是缩放因子，用于稳定梯度。&lt;/p&gt;
&lt;p&gt;在 PyTorch 中，自注意力机制可以如下实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 实现自注意力机制
class SelfAttention(nn.Module):
    def __init__(self, embed_dim, num_heads):
        super().__init__()
        self.attention = nn.MultiheadAttention(embed_dim, num_heads)
    
    def forward(self, x):
        # x 形状: (seq_len, batch_size, embed_dim)
        attn_output, _ = self.attention(x, x, x)
        return attn_output

# 使用示例
seq_len, batch_size, embed_dim = 10, 32, 512
x = torch.randn(seq_len, batch_size, embed_dim) # 形状 (10, 32, 512)
attention = SelfAttention(embed_dim, num_heads=8)
output = attention(x)   
# 在完整的多头注意力模块实现中，还含有一个线性投影层，用于把中间的输出 O 的通道维度再转换成和输入一致的形状的输出
print(output.shape)  # 形状 (10, 32, 512)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;多头注意力机制则是将注意力机制重复多次并将结果拼接，以捕获不同子空间中的信息。&lt;/p&gt;
&lt;h1&gt;体系结构与矩阵运算&lt;/h1&gt;
&lt;h2&gt;CPU 体系结构&lt;/h2&gt;
&lt;p&gt;中央处理器（CPU）是计算机系统的核心组件，负责执行指令和处理数据。现代CPU架构通常包含以下关键部分&lt;/p&gt;
&lt;h3&gt;核心架构&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多核设计&lt;/strong&gt;：现代CPU通常拥有2-64个处理核心，每个核心可以独立执行指令&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存层次结构&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;L1缓存：最快、容量最小(几十KB)，通常分为指令缓存和数据缓存&lt;/li&gt;
&lt;li&gt;L2缓存：中等速度和容量(几百KB到几MB)&lt;/li&gt;
&lt;li&gt;L3缓存：较大容量(几MB到几十MB)，在多核间共享&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;指令流水线&lt;/strong&gt;：将指令执行拆分为多个阶段(取指、译码、执行、访存、写回等)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;数据处理能力&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SIMD指令集&lt;/strong&gt;：单指令多数据并行处理能力
&lt;ul&gt;
&lt;li&gt;x86架构：SSE、AVX、AVX2、AVX-512等&lt;/li&gt;
&lt;li&gt;ARM架构：NEON、SVE等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内存访问&lt;/strong&gt;：支持乱序执行和预取机制，减少内存访问延迟&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;CPU与矩阵计算&lt;/h3&gt;
&lt;p&gt;CPU执行矩阵计算时面临以下挑战：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内存带宽限制：大型矩阵数据传输可能成为瓶颈&lt;/li&gt;
&lt;li&gt;缓存局部性：矩阵计算需要优化内存访问模式以利用缓存&lt;/li&gt;
&lt;li&gt;并行度有限：与GPU相比，CPU的SIMD单元和核心数量较少&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;CPU核心优势&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;顺序执行效率&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;高单线程性能和复杂的分支预测&lt;/li&gt;
&lt;li&gt;大缓存设计优化顺序代码执行&lt;/li&gt;
&lt;li&gt;较低的指令延迟，适合串行任务&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;条件控制流处理&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;高效的分支预测器，减少条件判断开销&lt;/li&gt;
&lt;li&gt;支持复杂的控制逻辑和不规则代码路径&lt;/li&gt;
&lt;li&gt;适合具有大量条件判断和异常处理的算法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;混合工作负载处理&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;平衡的设计适合同时处理计算、IO和系统任务&lt;/li&gt;
&lt;li&gt;灵活性强，可处理多样化任务而非单一计算模式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些特性使CPU在需要复杂决策逻辑的AI推理任务、迭代算法开发和混合工作负载中保持优势。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/cpu1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 利用NumPy在CPU上高效执行矩阵乘法
import numpy as np
import time

# 创建两个大矩阵
A = np.random.rand(1000, 1000)
B = np.random.rand(1000, 1000)

start = time.time()
C = np.matmul(A, B)  # NumPy自动使用优化的BLAS库
end = time.time()

print(f&quot;CPU矩阵乘法耗时: {end - start:.4f}秒&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;优化的CPU库(如Intel MKL、OpenBLAS)通过充分利用SIMD指令和多核心，仍能高效处理中小规模矩阵计算。&lt;/p&gt;
&lt;p&gt;CPU 的执行流程如下图所示
&lt;img src=&quot;assets/CPU.png&quot; alt=&quot;&quot; /&gt;
其中访存步骤花费的时间相对是最多的&lt;/p&gt;
&lt;h3&gt;案例：如何在 CPU 上高效计算矩阵乘法?&lt;/h3&gt;
&lt;p&gt;矩阵乘法是张量计算的基础操作，对于两个矩阵 $A \in \mathbb{R}^{M \times K}$ 和 $B \in \mathbb{R}^{K \times N}$，其乘积 $C = A \times B$ 的计算公式为&lt;/p&gt;
&lt;p&gt;$$C_{i,j} = \sum_{k=0}^{K-1} A_{i,k} \times B_{k,j}$$&lt;/p&gt;
&lt;h3&gt;直接实现与性能瓶颈&lt;/h3&gt;
&lt;p&gt;最简单的实现方式是使用三重循环&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (int i = 0; i &amp;lt; M; i++)
    for (int j = 0; j &amp;lt; N; j++) {
        C[i][j] = 0;
        for (int k = 0; k &amp;lt; K; k++)
            C[i][j] += A[i][k] * B[k][j];
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是这种实现存在严重的性能问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;缓存不友好&lt;/strong&gt;：对矩阵B的访问是按列进行的，而在内存中数据是按行存储的&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;频繁访存&lt;/strong&gt;：每次计算都需要从内存加载数据，导致CPU大部分时间在等待数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;访存次数&lt;/strong&gt;：对于每个元素C[i][j]的计算，需要读取K个A的元素和K个B的元素&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;分块矩阵乘法优化&lt;/h3&gt;
&lt;p&gt;分块矩阵乘法的核心思想是&lt;strong&gt;减少访存操作&lt;/strong&gt;，提高缓存利用率：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 分块大小根据缓存大小选择
int BM = 64, BN = 64, BK = 64;

// 初始化C矩阵为0
for (int i = 0; i &amp;lt; M; i++)
    for (int j = 0; j &amp;lt; N; j++) 
        C[i][j] = 0;

// 分块矩阵乘法
for (int i = 0; i &amp;lt; M; i += BM)
    for (int j = 0; j &amp;lt; N; j += BN)
        for (int k = 0; k &amp;lt; K; k += BK) {
            // 计算子矩阵乘法并累加结果
            for (int i1 = i; i1 &amp;lt; min(i+BM, M); i1++)
                for (int j1 = j; j1 &amp;lt; min(j+BN, N); j1++) {
                    float temp = 0;  // 局部累加器
                    for (int k1 = k; k1 &amp;lt; min(k+BK, K); k1++)
                        temp += A[i1][k1] * B[k1][j1];
                    C[i1][j1] += temp;  // 累加到C
                }
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;性能提升原理：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;减少访存次数&lt;/strong&gt;：一次将一个块加载到缓存中，多次复用这些数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;改善局部性&lt;/strong&gt;：小块矩阵能完全放入L1/L2缓存，大幅减少缓存未命中&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算访存比优化&lt;/strong&gt;：每个缓存块加载一次后可执行多次计算操作&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;assets/matmul.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过优化，分块矩阵乘法可将访存次数从 $O(MNK)$ 降低到 $O(MNK/B)$，其中B是块大小。当B较大时，访存开销显著降低，性能可提升5-10倍。&lt;/p&gt;
&lt;p&gt;除分块外，进一步优化还包括:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;循环重排序&lt;/li&gt;
&lt;li&gt;向量化指令(SIMD)&lt;/li&gt;
&lt;li&gt;多线程并行化&lt;/li&gt;
&lt;li&gt;内存对齐和预取技术&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;GPU 体系结构&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;assets/gpu.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;相较于 CPU，GPU 更擅高密度简单暴力计算。GPU由上千个简单的核心 (core) 组成，每个 core 可以同时执行简单的加减乘除逻辑计算等任务。GPU 特点有&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;较高的计算密度&lt;/li&gt;
&lt;li&gt;计算与访存比率 较高 (相较于 CPU，可以在一起访存后进行更大规模的计算)&lt;/li&gt;
&lt;li&gt;擅长处理高度并行的计算，如图像处理，矩阵计算&lt;/li&gt;
&lt;li&gt;不擅长处理控制逻辑复杂的程序&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;GPU 执行模型&lt;/h3&gt;
&lt;p&gt;单指令多线程 (SIMT) 是GPU并行处理的核心执行模型&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原理&lt;/strong&gt;：同一指令被多个线程同时执行，但每个线程拥有独立的程序计数器和寄存器状态&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组织结构&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;线程被组织为&lt;strong&gt;线程束&lt;/strong&gt;（warp/wavefront），典型大小为32/64线程&lt;/li&gt;
&lt;li&gt;同一线程束 (warp) 内的线程执行相同指令路径时性能最优&lt;/li&gt;
&lt;li&gt;多个线程束组成&lt;strong&gt;线程块&lt;/strong&gt;（block），多个线程块组成&lt;strong&gt;网格&lt;/strong&gt;（grid）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分支处理&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;当线程束内出现分支时，不同分支路径被&lt;strong&gt;序列化执行&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;执行某分支时，不走该路径的线程被暂时屏蔽&lt;/li&gt;
&lt;li&gt;分支散布（divergence）会显著降低性能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;assets/gpu2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线程层次结构&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;线程束(Warp/Wavefront)&lt;/strong&gt;：GPU执行的基本单位，通常为32线程(NVIDIA)或64线程(AMD)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程块(Block)&lt;/strong&gt;：共享本地内存的线程集合，可包含多个warp&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网格(Grid)&lt;/strong&gt;：整个GPU计算任务，由多个块组成&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$\text{Thread} \subset \text{Warp} \subset \text{Block} \subset \text{Grid}$&lt;/p&gt;
&lt;p&gt;这种执行模型使GPU能高效处理规整的并行计算任务，如矩阵运算，但在分支条件复杂时性能下降。&lt;/p&gt;
&lt;h3&gt;GPU 访存延迟&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;assets/gpu3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;如何在 GPU 上高效计算矩阵乘法？&lt;/h3&gt;
&lt;p&gt;在GPU上高效实现矩阵乘法涉及多层次的优化策略，主要围绕着如何充分利用GPU的并行架构和内存层次结构。&lt;/p&gt;
&lt;h4&gt;基本线程映射&lt;/h4&gt;
&lt;p&gt;最简单的实现是每个线程计算结果矩阵的一个元素：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__global__ void matrixMul(float *A, float *B, float *C, int M, int N, int K) {
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;
    
    if(row &amp;lt; M &amp;amp;&amp;amp; col &amp;lt; N) {
        float sum = 0.0f;
        for (int k = 0; k &amp;lt; K; k++) {
            sum += A[row * K + k] * B[k * N + col];
        }
        C[row * N + col] = sum;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种实现存在的主要问题是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;全局内存访问频繁，存在高延迟&lt;/li&gt;
&lt;li&gt;缺乏数据复用，相同数据被重复从全局内存加载&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;共享内存优化&lt;/h4&gt;
&lt;p&gt;使用共享内存可以显著减少全局内存访问：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__global__ void matrixMulShared(float *A, float *B, float *C, int M, int N, int K) {
    __shared__ float As[TILE_SIZE][TILE_SIZE];
    __shared__ float Bs[TILE_SIZE][TILE_SIZE];
    
    int bx = blockIdx.x, by = blockIdx.y;
    int tx = threadIdx.x, ty = threadIdx.y;
    
    int row = by * TILE_SIZE + ty;
    int col = bx * TILE_SIZE + tx;
    
    float sum = 0.0f;
    
    for (int t = 0; t &amp;lt; (K-1)/TILE_SIZE + 1; t++) {
        // 协作加载A和B的tile到共享内存
        if(row &amp;lt; M &amp;amp;&amp;amp; t*TILE_SIZE+tx &amp;lt; K)
            As[ty][tx] = A[row*K + t*TILE_SIZE+tx];
        else
            As[ty][tx] = 0.0f;
            
        if(t*TILE_SIZE+ty &amp;lt; K &amp;amp;&amp;amp; col &amp;lt; N)
            Bs[ty][tx] = B[(t*TILE_SIZE+ty)*N + col];
        else
            Bs[ty][tx] = 0.0f;
            
        __syncthreads();
        
        // 计算当前tile的乘积
        for (int k = 0; k &amp;lt; TILE_SIZE; k++) {
            sum += As[ty][k] * Bs[k][tx];
        }
        __syncthreads();
    }
    
    if(row &amp;lt; M &amp;amp;&amp;amp; col &amp;lt; N)
        C[row*N + col] = sum;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Thread Block Tile 与 Warp Tile&lt;/h4&gt;
&lt;p&gt;为进一步优化性能，可以采用多级分块策略：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Thread Block Tile&lt;/strong&gt;：即整个线程块处理的矩阵子区域&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;利用共享内存在线程块内共享数据&lt;/li&gt;
&lt;li&gt;通常大小为32×32或64×64&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Warp Tile&lt;/strong&gt;：即单个warp处理的矩阵子区域&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;利用warp内线程的协作和寄存器&lt;/li&gt;
&lt;li&gt;通常大小为16×16或8×8&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种多级分块策略能够：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最大化寄存器和共享内存的数据复用&lt;/li&gt;
&lt;li&gt;减少同步开销&lt;/li&gt;
&lt;li&gt;提高指令级并行性&lt;/li&gt;
&lt;li&gt;更好地利用GPU计算资源&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;从 GEMM 到 BLAS 再到 cuBLAS&lt;/h4&gt;
&lt;p&gt;在实际应用中，通常使用高度优化的库如cuBLAS：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用cuBLAS的矩阵乘法示例
import torch

# 创建GPU上的张量
A = torch.rand(1024, 1024, device=&quot;cuda&quot;)
B = torch.rand(1024, 1024, device=&quot;cuda&quot;)

# PyTorch会自动调用cuBLAS
C = torch.matmul(A, B)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;cuBLAS内部实现了多种优化技术：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动选择最佳算法和参数&lt;/li&gt;
&lt;li&gt;利用共享内存和寄存器优化&lt;/li&gt;
&lt;li&gt;数据预取和重叠计算&lt;/li&gt;
&lt;li&gt;针对特定GPU架构的调优&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;编译器与编译优化&lt;/h1&gt;
&lt;h2&gt;传统编译器&lt;/h2&gt;
&lt;p&gt;编译器是一种计算机程序，用于将高级程序语言编写的源代码转换为计算机能够理解和执行的机器语言代码。整个从编译到装在的程序创建过程可以描述为&lt;/p&gt;
&lt;p&gt;$$
\text{高级语言代码} \xrightarrow{\text{编译器}}  \text{汇编代码} \xrightarrow{\text{汇编器}} \text{目标代码(二进制指令)} \xrightarrow{\text{链接器}} \text{可执行二进制代码} \xrightarrow{\text{装载器}} \text{在硬件上执行}
$$&lt;/p&gt;
&lt;p&gt;编译器主要负责编译部分，由&lt;strong&gt;前端&lt;/strong&gt;、&lt;strong&gt;优化器&lt;/strong&gt;和&lt;strong&gt;后端&lt;/strong&gt;三个部分组成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;前端：&lt;/strong&gt; 负责词法和语法分析，会检查源程序代码是否符合正确的语法和语义，然后将源代码转为抽象语法树&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化器：&lt;/strong&gt; 对前端输出的中间代码进行优化，使代码更加高效&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;后端：&lt;/strong&gt; 将已经优化的中间代码转化为针对各自平台的机器代码&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;传统编译器主要有两大类，分别是 &lt;strong&gt;GCC&lt;/strong&gt; 和 &lt;strong&gt;LLVM&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/compiler.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图中左侧描述的是 GCC 的编译模式，特点是 GCC 为不同的高级语言和汇编指令平台的组合分别构建一个编译链接，链接复杂度是 $O(m\times n)$；而图中右侧描述的 LLVM 编译模式的特点则是通过构建一个中间表示 IR，链接复杂度是 $O(m + n)$，实现了高级语言和汇编平台更加方便的链接和扩展。&lt;/p&gt;
&lt;p&gt;以 LLVM 为例，LLVM 的编译过程图如下所示&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/llvm.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到 C、C++ 的前端是 Clang，经过 LLVM IR 表示后，在中间的优化器部分经过多次优化（一个 pass 表示对源程序的一次完整扫描与优化处理），然后通过后端变成汇编指令&lt;/p&gt;
&lt;h2&gt;AI 编译器&lt;/h2&gt;
&lt;p&gt;深度学习的计算主要涉及前向反向传播的计算模式，这与传统计算有着很大的区别。因此，需要新的 AI 编译器优化这个计算模式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI 编译器的挑战&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不同厂商提供的专用加速芯片导致性能的可移植性称为一种刚需&lt;/li&gt;
&lt;li&gt;新的算子层出不穷，算子库的开发、维护、优化和测试工作量越来越大，需要有更加通用和自动化的能对程序进行优化和代码生成的编译器&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;AI编译器与传统编译器的区别&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;计算模型差异&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传统编译器&lt;/strong&gt;：针对通用计算，处理控制流复杂的程序，如条件、循环和函数调用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI编译器&lt;/strong&gt;：优化张量计算和数据流图，主要处理大规模并行的矩阵运算，计算模式相对固定&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优化目标&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传统编译器&lt;/strong&gt;：注重通用性能提升、代码大小和内存使用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI编译器&lt;/strong&gt;：专注于张量计算的吞吐量、内存效率和硬件利用率，如最大化计算密度和优化数据移动&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;编译流程&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传统编译器&lt;/strong&gt;：源代码→IR→机器代码，优化主要在IR级别&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI编译器&lt;/strong&gt;：计算图→高级IR→硬件特定IR→代码生成，通常有多级IR转换和更复杂的优化流水线 (IR 层级相较于传统编译器较高)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;硬件适配&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传统编译器&lt;/strong&gt;：主要针对CPU架构优化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI编译器&lt;/strong&gt;：需支持异构计算环境(GPU/TPU/NPU)，每种硬件都有不同的内存层次和计算单元&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;自动调优&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传统编译器&lt;/strong&gt;：主要使用固定的启发式规则和分析算法&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI编译器&lt;/strong&gt;：大量使用自动搜索和机器学习方法优化参数（如TVM的AutoTVM和Ansor）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;典型代表&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传统编译器&lt;/strong&gt;：GCC、LLVM、Clang等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI编译器&lt;/strong&gt;：TVM、XLA、MLIR、Glow、PyTorch 2.0(TorchScript/TorchDynamo)等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;领域知识&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传统编译器&lt;/strong&gt;：利用程序分析和系统架构知识&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI编译器&lt;/strong&gt;：需要深度学习算法、硬件加速器架构和并行计算等多领域知识&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;assets/ai_compiler.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;AI编译器实质上是结合了传统编译优化、并行计算、深度学习和硬件架构等多个领域的专门化编译系统，为深度学习工作负载提供高性能、可移植的解决方案。&lt;/p&gt;
&lt;h2&gt;前端优化&lt;/h2&gt;
&lt;p&gt;前端优化就是通过&lt;strong&gt;计算图的等价变换&lt;/strong&gt;化简计算图，从而降低计算复杂度或内存开销。
&lt;strong&gt;前端优化与硬件无关&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;通常的优化方法包括 &lt;strong&gt;算术表达式化简&lt;/strong&gt;、&lt;strong&gt;公共子表达式消除&lt;/strong&gt;、&lt;strong&gt;死代码消除&lt;/strong&gt;、&lt;strong&gt;常量折叠&lt;/strong&gt;、&lt;strong&gt;算子融合&lt;/strong&gt;、&lt;strong&gt;布局转换&lt;/strong&gt;、&lt;strong&gt;内存分配&lt;/strong&gt;等&lt;/p&gt;
&lt;h3&gt;算术表达式化简&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;通过代数运算等价变换计算图，如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$a * 0 \rightarrow 0$&lt;/li&gt;
&lt;li&gt;$a * 1 \rightarrow a$&lt;/li&gt;
&lt;li&gt;$a + 0 \rightarrow a$&lt;/li&gt;
&lt;li&gt;$\log(\exp(x) / y) \rightarrow x - \log y$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;利用交换律、结合律等规律调整图中的算子执行顺序，或者删除不必要的算子&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$(a \cdot b) \odot d + (a \cdot b) \odot c \rightarrow (a \cdot b) \odot (c + d)$&lt;/li&gt;
&lt;li&gt;e.g. $AB \odot C + AB \odot D \rightarrow AB \odot (C + D)$，其中 $\odot$ 是逐元素乘法，$AB$ 是矩阵乘法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;公共子表达式消除&lt;/h3&gt;
&lt;p&gt;目的是通过找到程序中等价的计算表达式，然后通过复用结果的方式消除其他冗余表达式的计算
$$
\begin{cases}
a = b \cdot c + g \
d = b \cdot c + e
\end{cases}
\rightarrow
\begin{cases}
tmp = b \cdot c \
a = tmp + g \
d = tmp + e
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/opti1.png&quot; alt=&quot;&quot; /&gt;
这里的数字只是算子和参数的标号，不是加7减6之类的含义&lt;/p&gt;
&lt;h3&gt;死代码消除&lt;/h3&gt;
&lt;p&gt;移除对程序执行结果没有任何影响的代码，例如一些在训练阶段会使用但在推理时不会执行的计算，可以被消除 (Dropout 操作)&lt;/p&gt;
&lt;h3&gt;常量折叠&lt;/h3&gt;
&lt;p&gt;折叠多余的常量计算，例如 &lt;code&gt;a = 320 * 200 * 32&lt;/code&gt; 直接变成 &lt;code&gt;a = 2_048_000&lt;/code&gt;。这个道理非常的显然，但是常量折叠具有较好的优化作用，因为在模型的修改调试阶段，用户可能会因为调试而编写了很多非折叠的常量，如果不折叠，这在后续的训练优化中会很消耗性能。此外，模型训练阶段时，模型的参数是变化的，而当训练结束后，在推理阶段，模型的参数可以视作一个个常量，此时可以使用常量折叠进行优化。&lt;/p&gt;
&lt;p&gt;一个具体的例子是 BatchNorm。BatchNorm操作在训练阶段的计算公式为：
$$y = \gamma \cdot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta$$&lt;/p&gt;
&lt;p&gt;其中:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$x$ 是输入特征&lt;/li&gt;
&lt;li&gt;$\mu$ 是批次均值&lt;/li&gt;
&lt;li&gt;$\sigma^2$ 是批次方差&lt;/li&gt;
&lt;li&gt;$\gamma, \beta$ 是可学习的缩放和偏移参数&lt;/li&gt;
&lt;li&gt;$\epsilon$ 是防止除零的小常数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在推理阶段，$\mu$ 和 $\sigma^2$ 通常使用训练过程中累积的移动平均值，且这些值与 $\gamma$ 和 $\beta$ 都是固定的。此时可以通过常量折叠优化：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 原始BatchNorm推理计算
def batch_norm_inference(x, gamma, beta, mean, var, epsilon=1e-5):
    return gamma * (x - mean) / torch.sqrt(var + epsilon) + beta

# 常量折叠后的计算
def batch_norm_folded(x, scale, shift):
    return x * scale + shift

# 参数折叠过程
scale = gamma / torch.sqrt(var + epsilon)
shift = beta - scale * mean

# 将原始BatchNorm替换为简单的缩放和平移操作
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种优化将BatchNorm从包含除法的复杂操作简化为单一的缩放和平移（乘加操作），减少了计算量并提高了推理效率。在实际部署中，常量折叠可以将BatchNorm层与相邻的卷积层进一步融合，完全消除BatchNorm的额外计算开销。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/bn_folding.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在高效的推理引擎实现中，这种折叠通常是自动完成的，显著提升了模型的执行速度，尤其在边缘设备上更为明显。&lt;/p&gt;
&lt;h3&gt;算子融合&lt;/h3&gt;
&lt;p&gt;将向量化的多个算子操作合并成一个算子操作。这种优化的想法是，CPU 内核的启动是有时间开销的，这样做可以减少内存的读取，提高计算密度。例如，可以将 &lt;code&gt;c = mul(a, b)&lt;/code&gt; 和 &lt;code&gt;e = add(c, d)&lt;/code&gt; 合并成 &lt;code&gt;e = mla(a, b, d)&lt;/code&gt;，从而减少一次 &lt;code&gt;c&lt;/code&gt; 写入的访存和一次 &lt;code&gt;c&lt;/code&gt; 读取的访存。&lt;/p&gt;
&lt;p&gt;另外一个具体的例子是实现矩阵乘法的自动融合，例如同时有两个张量 &lt;code&gt;x&lt;/code&gt; 和 &lt;code&gt;y&lt;/code&gt; 都需要有与张量 &lt;code&gt;z&lt;/code&gt; 相乘计算后的结果，可以把 &lt;code&gt;x&lt;/code&gt; 和 &lt;code&gt;y&lt;/code&gt; 进行拼接再与 &lt;code&gt;z&lt;/code&gt; 相乘，从而减少访存的操作和内核启动时间&lt;/p&gt;
&lt;h3&gt;布局转换&lt;/h3&gt;
&lt;p&gt;Tensor 是一个多维阵列，但是在存储中的排列存储方式确是“向量化”的形式，因此如何布局排布对计算 cache 的读取是具有影响的。假设有一个形状为 &lt;code&gt;(2, 3)&lt;/code&gt; (行编号, 列编号) 的矩阵 $X$，通常排布有&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;行优先：排布成 $[X_{1, 1:3}, X_{2, 1:3}]$ 存储&lt;/li&gt;
&lt;li&gt;列优先：排布成 $[X_{1:2, 1}, X_{1:2, 2}, X_{1:2, 3}]$ 存储&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在深度学习中，图像张量的形状有两种常用的排布方式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(B, C, H, W)&lt;/code&gt; 型：各个张量维度依次表示 (批次大小, 通道数, 高度, 宽度)，这是输入卷积层的特征图的形状。同一个通道的数据值连续排布，更适合需要对每个通道单独运算的操作，如 MaxPooling，适合 GPU、大带宽的计算模式&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(B, H, W, C)&lt;/code&gt; 型：各个张量维度依次表示 (批次大小, 高度, 宽度, 通道数)。不同通道中同一位置元素顺序存储，更适合需要跨通道计算的操作如 $1\times 1$卷积（注意$1\times 1$卷积可以用 NHWC 形式下存储的张量和线性层实现，这样比 NCHW 加卷积层的模式更高效）。这种计算模式更适合多核 CPU 执行，计算控制灵活且复杂&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;内存分配&lt;/h3&gt;
&lt;p&gt;在深度学习训练和推理过程中，内存分配与管理是影响性能和效率的关键因素。内存复用技术可以显著降低内存开销，尤其是在处理大型模型时。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用torch.cuda.memory_stats()查看当前GPU内存使用情况
import torch

# 在不优化的情况下执行操作
def unoptimized():
    x = torch.randn(10000, 10000, device=&apos;cuda&apos;)
    y = torch.randn(10000, 10000, device=&apos;cuda&apos;)
    z = x @ y  # 矩阵乘法
    return z

# 使用内存前后对比
torch.cuda.empty_cache()
before = torch.cuda.memory_allocated()
result = unoptimized()
after = torch.cuda.memory_allocated()
print(f&quot;内存增长: {(after - before) / 1024**2:.2f} MB&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里没有进行优化，导致内存增长为 390.12 MB&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;内存复用的基本原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在计算图执行过程中，不同操作产生的中间结果通常只在特定阶段有用。一旦这些中间结果被消费后，其占用的内存空间可以被释放并重新用于存储其他中间结果。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;原地操作 (Inplace Operation)&lt;/strong&gt;: 如果一块内存不再需要，且下一个操作是 element-wise 的，就可以做原地操作覆盖内存，而无需新内存。例如，PyTorch 的激活函数可以设置采用 &lt;code&gt;torch.nn.ReLU(inplace=True)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;内存共享 (Memory Sharing)&lt;/strong&gt;: 两个数据共享同一块内存空间，其中有一个数据参与计算后不再需要，后一个数据可以覆盖前一个数据，只要这两个数据的生命周期无重叠即可（不过这对反向传播是有影响的）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PyTorch中提供了多种内存优化机制&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;梯度检查点(Checkpoint)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于大型模型，可以使用梯度检查点技术在反向传播时重新计算中间激活值，而不是存储所有激活值&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch.utils.checkpoint as checkpoint

class LargeModel(torch.nn.Module):
    def __init__(self):
        super().__init__()
        # 定义一个包含多个层的大型模型
        self.layers = torch.nn.ModuleList([
            torch.nn.Linear(1024, 1024) for _ in range(10)
        ])
        
    def forward(self, x):
        for layer in self.layers:
            # 使用checkpoint包装层的前向传播
            x = checkpoint.checkpoint(layer, x)
        return x
        
# 这种方式会在反向传播时重新计算中间结果，降低内存使用但增加计算量
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;释放未使用的Tensor&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# 明确释放不再需要的张量
x = torch.randn(10000, 10000, device=&apos;cuda&apos;)
y = torch.randn(10000, 10000, device=&apos;cuda&apos;)
z = x @ y
del x, y  # 显式释放不再需要的大张量
torch.cuda.empty_cache()  # 释放缓存的未使用内存
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;使用inplace操作&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当可能时，使用原地操作可以避免额外的内存分配：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 非原地操作
x = torch.ones(1000, 1000)
x = x + 1  # 创建新张量

# 原地操作
x = torch.ones(1000, 1000)
x.add_(1)  # 直接修改x，不创建新张量
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;自动垃圾回收优化&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PyTorch 2.0引入了更智能的内存管理机制，包括通过编译优化内存使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用torch.compile优化内存使用
@torch.compile
def optimized_function(x, y):
    intermediate1 = x * 2
    intermediate2 = y * 3
    result = intermediate1 @ intermediate2
    return result

# 编译后的函数会优化内存使用模式
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;流水线并行和模型分片&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于特别大的模型，可以使用PyTorch的分布式训练工具实现模型并行化：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用torch.distributed进行模型并行示例
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel

# 初始化分布式环境
dist.init_process_group(backend=&quot;nccl&quot;)

# 将模型的不同部分分配到不同设备
model_part1 = Model1().to(f&apos;cuda:{dist.get_rank() % 2}&apos;)
model_part2 = Model2().to(f&apos;cuda:{(dist.get_rank()+1) % 2}&apos;)

# 每个设备只存储部分模型参数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过这些技术，深度学习框架可以在有限内存条件下处理更大的模型和数据集，提高训练和推理效率。&lt;/p&gt;
&lt;h2&gt;后端优化&lt;/h2&gt;
&lt;p&gt;后端优化主要关注算子节点内部的具体实现，目标是让算子的性能达到最优。常用的优化方式有&lt;strong&gt;循环展开&lt;/strong&gt;，&lt;strong&gt;循环分块&lt;/strong&gt;，&lt;strong&gt;循环融合&lt;/strong&gt;等。&lt;/p&gt;
&lt;h3&gt;循环展开&lt;/h3&gt;
&lt;p&gt;循环展开是一种通过减少循环控制开销和提高指令级并行性来优化循环执行的技术。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本原理&lt;/strong&gt;：将循环体内的代码复制多次，减少循环迭代次数，同时增加每次迭代中的计算量。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 原始循环
for (int i = 0; i &amp;lt; N; i++) {
    sum += array[i];
}

// 循环展开(展开因子为4)
for (int i = 0; i &amp;lt; N; i += 4) {
    sum += array[i];
    sum += array[i+1];
    sum += array[i+2];
    sum += array[i+3];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优化效果&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;减少循环控制开销(分支预测、条件检查)&lt;/li&gt;
&lt;li&gt;提高指令级并行性&lt;/li&gt;
&lt;li&gt;增加寄存器利用率&lt;/li&gt;
&lt;li&gt;减少内存操作的延迟影响&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在深度学习计算如卷积或矩阵乘法中，循环展开通常与向量化指令(如SIMD)结合使用，显著提高计算密度。&lt;/p&gt;
&lt;h3&gt;循环分块&lt;/h3&gt;
&lt;p&gt;循环分块（Loop Tiling）通过改变数据访问模式来提高缓存效率，对于矩阵和张量运算尤为重要。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本原理&lt;/strong&gt;：将大循环划分为小块，使每块数据能够完全放入CPU缓存中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 原始矩阵乘法
for (int i = 0; i &amp;lt; N; i++)
    for (int j = 0; j &amp;lt; N; j++) {
        float sum = 0;
        for (int k = 0; k &amp;lt; N; k++)
            sum += A[i][k] * B[k][j];
        C[i][j] = sum;
    }

// 分块优化后的矩阵乘法
for (int i0 = 0; i0 &amp;lt; N; i0 += B)
    for (int j0 = 0; j0 &amp;lt; N; j0 += B)
        for (int k0 = 0; k0 &amp;lt; N; k0 += B)
            for (int i = i0; i &amp;lt; min(i0+B, N); i++)
                for (int j = j0; j &amp;lt; min(j0+B, N); j++) {
                    float sum = (k0 == 0) ? 0 : C[i][j];
                    for (int k = k0; k &amp;lt; min(k0+B, N); k++)
                        sum += A[i][k] * B[k][j];
                    C[i][j] = sum;
                }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优化效果&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提高缓存命中率&lt;/li&gt;
&lt;li&gt;减少内存带宽需求&lt;/li&gt;
&lt;li&gt;增加数据局部性&lt;/li&gt;
&lt;li&gt;降低TLB缺失率&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;分块策略在深度学习编译器中广泛应用，特别是在GPU计算中，将数据分块到不同层次的内存(共享内存、寄存器)可以显著提升性能。&lt;/p&gt;
&lt;h3&gt;循环融合&lt;/h3&gt;
&lt;p&gt;循环融合是将多个循环合并成一个循环的优化技术，可减少循环开销并提高数据局部性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本原理&lt;/strong&gt;：当多个循环具有相同的迭代范围且无数据依赖时，可以将它们融合为一个循环。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 融合前
for (int i = 0; i &amp;lt; N; i++)
    A[i] = B[i] * scalar1;
    
for (int i = 0; i &amp;lt; N; i++)
    C[i] = A[i] + D[i];

// 融合后
for (int i = 0; i &amp;lt; N; i++) {
    A[i] = B[i] * scalar1;
    C[i] = A[i] + D[i];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优化效果&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;减少循环控制开销&lt;/li&gt;
&lt;li&gt;提高指令缓存效率&lt;/li&gt;
&lt;li&gt;减少数据在不同循环间的移动&lt;/li&gt;
&lt;li&gt;提高数据局部性和缓存命中率&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在深度学习计算中，循环融合通常用于合并element-wise操作，如将激活函数计算与前一层的线性操作融合，减少中间结果的内存访问。许多现代AI编译器（如TVM、XLA）会自动执行这类融合优化。&lt;/p&gt;
&lt;p&gt;循环融合与算子融合密切相关，但更专注于底层循环实现而非图层面的优化。在实际应用中，两者通常结合使用，先在计算图层面融合算子，再在代码生成阶段优化具体循环实现。&lt;/p&gt;
&lt;h3&gt;循环拆分&lt;/h3&gt;
&lt;h3&gt;循环拆分&lt;/h3&gt;
&lt;p&gt;循环拆分（Loop Splitting）是将单个循环拆分为多个连续执行的循环的优化技术，有助于提高执行效率和并行性。&lt;strong&gt;主要是针对含有条件分支的语句进行的优化方法。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基本原理&lt;/strong&gt;：将循环按照某种条件分成多个部分，每部分处理原循环的不同情况或执行不同的优化策略。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 原始循环（包含条件判断）
for (int i = 0; i &amp;lt; N; i++) {
    if (i &amp;lt; M)
        A[i] = B[i] * 2;
    else
        A[i] = C[i] + D[i];
}

// 拆分后
for (int i = 0; i &amp;lt; M; i++) {
    A[i] = B[i] * 2;  // 第一部分
}

for (int i = M; i &amp;lt; N; i++) {
    A[i] = C[i] + D[i];  // 第二部分
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优化效果&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;消除循环内的条件分支，减少分支预测失误&lt;/li&gt;
&lt;li&gt;简化循环体，使编译器能应用更多优化&lt;/li&gt;
&lt;li&gt;为不同循环部分应用不同的优化策略&lt;/li&gt;
&lt;li&gt;提高向量化和并行化机会&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在深度学习计算中，循环拆分常用于处理边界条件、对齐问题或特殊情况，例如将卷积操作中填充(padding)区域和非填充区域的计算分开处理，使主计算循环能够被更好地优化。这种技术也可以与循环展开、向量化等其他优化相结合，进一步提升性能。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;参考资料&lt;/em&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/microsoft/AI-System/blob/main/docs/SystemforAI-4-Computer%20architecture%20for%20Matrix%20computation.pdf&quot;&gt;微软亚洲研究院的 AI System 课程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1802.04799&quot;&gt;TVM: An Automated End-to-End Optimizing Compiler for Deep Learning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.nvidia.com/cuda/cuda-c-programming-guide/&quot;&gt;CUDA C++ Programming Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://llvm.org/&quot;&gt;The LLVM Compiler Infrastructure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2002.03794&quot;&gt;Deep Learning Compiler: A Comprehensive Survey&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1809.02697&quot;&gt;Optimizing CNN Model Inference on CPUs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.ezyang.com/2019/05/pytorch-internals/&quot;&gt;PyTorch Internals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>AI-Sys 1 深度学习基础</title><link>https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter1/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/aisys/chapter1/</guid><pubDate>Tue, 20 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;深度学习基础&lt;/p&gt;
&lt;h1&gt;深度学习是什么&lt;/h1&gt;
&lt;p&gt;深度学习是通过深度神经网络的机器学习方法。简单来说，深度学习是对一个含有可学习参数 $\theta$ 的神经网络 $f(x;\theta)$，利用梯度下降的方法优化损失函数 $L(y, \hat y)$ ，从而实现对数据集 $D = {x_i, y_i}_{i=1}^N$ 的拟合。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/dog-cat.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;下面是一个图像分类任务上的 LeNet 网络，使用 PyTorch 搭建&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
from torch import nn
import torch.nn.functional as F

class LeNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 6, 5) # 卷积层1
        self.conv2 = nn.Conv2d(6, 16, 5) # 卷积层2
        self.fc1 = nn.Linear(16 * 5 * 5, 120)   # 全连接层1
        self.fc2 = nn.Linear(120, 84)   # 全连接层2
        self.fc3 = nn.Linear(84, 10)    # 全连接层3，实现10分类
    
    def forward(self, x):
        out = F.relu(self.conv1(x))
        out = F.max_pool2d(out, 2)
        out = F.relu(self.conv2(out))
        out = out.view(out.size(0), -1) # 展平
        out = F.relu(self.fc1(out))
        out = F.relu(self.fc2(out))
        out = self.fc3(out)
        return out
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;assets/LeNet-5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;深度学习的应用的典型领域&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;计算机视觉
&lt;ul&gt;
&lt;li&gt;图像分类&lt;/li&gt;
&lt;li&gt;图像分割
&lt;ul&gt;
&lt;li&gt;语义分割&lt;/li&gt;
&lt;li&gt;实例分割&lt;/li&gt;
&lt;li&gt;全景分割&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;目标检测&lt;/li&gt;
&lt;li&gt;图像生成&lt;/li&gt;
&lt;li&gt;图像超分辨率&lt;/li&gt;
&lt;li&gt;姿态估计&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;自然语言处理
&lt;ul&gt;
&lt;li&gt;机器翻译&lt;/li&gt;
&lt;li&gt;文本分类&lt;/li&gt;
&lt;li&gt;情感分析&lt;/li&gt;
&lt;li&gt;问答系统&lt;/li&gt;
&lt;li&gt;文本生成&lt;/li&gt;
&lt;li&gt;语言模型&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;语音处理
&lt;ul&gt;
&lt;li&gt;语音识别&lt;/li&gt;
&lt;li&gt;语音合成&lt;/li&gt;
&lt;li&gt;声纹识别&lt;/li&gt;
&lt;li&gt;音乐生成&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;多模态学习
&lt;ul&gt;
&lt;li&gt;图像描述&lt;/li&gt;
&lt;li&gt;视觉问答&lt;/li&gt;
&lt;li&gt;文本到图像生成&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;深度强化学习
&lt;ul&gt;
&lt;li&gt;游戏智能体&lt;/li&gt;
&lt;li&gt;机器人控制&lt;/li&gt;
&lt;li&gt;自动驾驶&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;机器学习系统的组成部分&lt;/h1&gt;
&lt;p&gt;深度学习系统本质上仍然是机器学习系统，在定义了机器学习系统后，就可以用数据集对模型进行训练从而拟合出一个函数出来。一般地，机器学习系统的组成部分如下&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;假设空间，即模型的集合，一般会满足某种架构&lt;/li&gt;
&lt;li&gt;损失函数&lt;/li&gt;
&lt;li&gt;优化器&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如，线性模型
$$
h_\theta(\mathbf{x}) = \theta^T \mathbf{x}
$$
设$\mathbf{x} \in \mathbb{R}^n, h(\mathbf{x}) \in \mathbb{R}^k$。为了方便进行矩阵计算，设有$B$个batch，$\mathbf{X} \in \mathbb{R}^{B \times n}, \mathbf{y} \in \mathbb{R}^{B \times k}$，于是
$$
h_\theta(\mathbf{X}) = \mathbf{X} \theta
$$&lt;/p&gt;
&lt;p&gt;损失函数通常有如下形式：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;L2损失（均方误差）:
$$
L_2(\mathbf{y}, h_\theta(\mathbf{X})) = \frac{1}{B} \sum_{i=1}^B (y_i - h_\theta(x_i))^2
$$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Cross-Entropy:
$$
L_{CE} = -\frac{1}{B} \sum_{i=1}^B \sum_{j=1}^k y_{ij} \log(h_\theta(x_i)&lt;em&gt;j)
$$
其中$y&lt;/em&gt;{ij}$表示第$i$个样本的第$j$个真实标签，$h_\theta(x_i)_j$表示模型对第$i$个样本的第$j$个标签的预测值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为了使得损失函数最小，这也是我们期望模型所能做到的，即能够拟合一个函数出来，我们需要一个优化器来进行参数的更新。通常采用的方法是梯度下降法（Gradient Descent）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Gradient Descent:
$$
\theta \leftarrow \theta - \eta \nabla_\theta L(\mathbf{y}, h_\theta(\mathbf{X}))
$$
其中$\eta$为学习率，$\nabla_\theta L(\mathbf{y}, h_\theta(\mathbf{X}))$为损失函数对参数的梯度。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由于一次性计算对全部样本的损失以及对应的梯度会消耗大量的计算内存，因此我们通常采用mini-batch的方法来进行训练。即每次只计算一个batch的样本的损失和梯度。对应的梯度下降的方法为随机梯度下降（Stochastic Gradient Descent）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Stochastic Gradient Descent:
$$
\theta \leftarrow \theta - \frac{\eta}{B} \nabla_\theta \sum_{i=1}^B l(\mathbf{y}&lt;em&gt;i, h&lt;/em&gt;\theta(\mathbf{x}_i))
$$
其中$l(\mathbf{y}&lt;em&gt;i, h&lt;/em&gt;\theta(\mathbf{x}_i))$为对单个样本，即第$i$个样本的损失函数。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对于深度学习的系统，在定义好模型、损失函数和优化器后，就可以用下面的深度学习框架进行拟合了。&lt;/p&gt;
&lt;h1&gt;深度学习框架&lt;/h1&gt;
&lt;p&gt;深度学习的基本计算流程涉及到&lt;strong&gt;前向传播（计算预测结果）&lt;/strong&gt;、&lt;strong&gt;反向传播（计算损失函数梯度）&lt;/strong&gt; 和 &lt;strong&gt;优化参数&lt;/strong&gt;三个部分，因此一个成熟的深度学习框架也主要就负责这三个部分的处理，通过构建一个&lt;a href=&quot;https://zhuanlan.zhihu.com/p/145353262&quot;&gt;计算图&lt;/a&gt;实现。&lt;/p&gt;
&lt;p&gt;此外，由于深度学习的函数通常是多维张量计算，具有较高的数据并行性，因此深度学习框架也需要具有和高并行处理单元如 GPU、NPU 等有很好的接口结合。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前端：面向用户，应能够简便且灵活的定义深度神经网络模型&lt;/li&gt;
&lt;li&gt;算子：用于执行计算，如卷积算子&lt;/li&gt;
&lt;li&gt;求导：用于更新参数，需要提供自动求导的计算方式&lt;/li&gt;
&lt;li&gt;后端：需要将算子部署在不同的加速设备上&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;反向传播计算梯度是由框架自行完成的，称为​自动微分（Automatic Differentiation，AD）。这是一种通过将复杂函数分解为基本操作，并利用&lt;strong&gt;链式法则&lt;/strong&gt;来计算导数的方法。这里直接通过数值方法计算梯度是不可行的，一方面是存在数值误差问题，另一方面是计算复杂度过高。但是，数值梯度的作用也是有的，在当前的深度学习框架中，为了验证反向传播算法的正确性，框架通常还会使用数值梯度（Numerical Gradient）来自动进行验证。&lt;/p&gt;
&lt;p&gt;框架的具体的两种极端的实现方式&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.高灵活性（高复杂性）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import numpy as np
N, D = 3, 4

def mla(x: np.ndarray, y: np.ndarray, z: np.ndarray):
    # 前向传播
    a = x * y
    b = a + z
    c = np.sum(b)

    # 反向传播
    grad_c = 1.0
    grad_b = grad_c * np.one_like(b)
    grad_a = grad_b.copy()
    grad_z = grad_b.copy()
    grad_x = grad_a * y
    grad_y = grad_a * x

    return c, grad_x, grad_y, grad_z
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2.高效性（低修改性）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torchvision.models as models

x, y = load_data()
model = models.resnet152(pretrained=True)   # 模型是提前打包好的
outputs = model(x)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;一个好的深度学习框架应该可以&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提供&lt;strong&gt;灵活&lt;/strong&gt;的编程模型和接口&lt;/li&gt;
&lt;li&gt;提供&lt;strong&gt;高效&lt;/strong&gt;和可扩展性的计算能力&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;早期深度学习框架的局限&lt;/h1&gt;
&lt;h2&gt;层的实现不灵活&lt;/h2&gt;
&lt;p&gt;每当出现一个新的层时，都需要专门编写它的前向传播和反向传播的函数，限制了编程的灵活性。也就是说，早期的深度学习框架用&lt;strong&gt;基于层的方式&lt;/strong&gt;实现计算。&lt;/p&gt;
&lt;p&gt;而现代深度学习框架则是通过&lt;strong&gt;构建计算图的方式实现计算流程&lt;/strong&gt;，计算图的基本单元是一个个基础运算（加减乘除等）的门电路，灵活性更高。&lt;/p&gt;
&lt;h2&gt;优化器的实现不灵活&lt;/h2&gt;
&lt;p&gt;对于早期框架，新的优化器出现需要定义新的参数更新范式，不灵活。&lt;/p&gt;
&lt;h2&gt;简单的 “前向 + 后向” 的计算模式限制了新的训练范式&lt;/h2&gt;
&lt;p&gt;当前的神经网络的训练范式中往往会夹杂一些特定的模式，如强化学习需要与外界交互收集经验数据、对抗网络需要 Generator 和 Discriminator 交替训练，简单的 前向 + 后向 无法满足。&lt;/p&gt;
&lt;h1&gt;现代深度学习框架的特点&lt;/h1&gt;
&lt;h2&gt;计算图的实现方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;基本的数据结构是 $N$ 阶 Tensor&lt;/li&gt;
&lt;li&gt;基本的运算单元是操作子&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;操作子类别&lt;/th&gt;
&lt;th&gt;具体操作子&lt;/th&gt;
&lt;th&gt;功能描述&lt;/th&gt;
&lt;th&gt;典型应用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;数学运算操作&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;加法(Add)&lt;/td&gt;
&lt;td&gt;对两个张量执行元素级加法&lt;/td&gt;
&lt;td&gt;残差连接、特征融合&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;减法(Subtract)&lt;/td&gt;
&lt;td&gt;对两个张量执行元素级减法&lt;/td&gt;
&lt;td&gt;特征差异计算&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;乘法(Multiply)&lt;/td&gt;
&lt;td&gt;对两个张量执行元素级乘法&lt;/td&gt;
&lt;td&gt;注意力机制、门控机制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;除法(Divide)&lt;/td&gt;
&lt;td&gt;对两个张量执行元素级除法&lt;/td&gt;
&lt;td&gt;归一化计算&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;幂运算(Power)&lt;/td&gt;
&lt;td&gt;计算张量的幂&lt;/td&gt;
&lt;td&gt;特征变换&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;矩阵乘法(MatMul)&lt;/td&gt;
&lt;td&gt;执行矩阵乘法&lt;/td&gt;
&lt;td&gt;全连接层、线性变换&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;神经网络操作&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;卷积(Convolution)&lt;/td&gt;
&lt;td&gt;执行N维卷积操作&lt;/td&gt;
&lt;td&gt;CNN网络、图像处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;转置卷积(TransposedConv)&lt;/td&gt;
&lt;td&gt;执行转置卷积&lt;/td&gt;
&lt;td&gt;上采样、生成网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;最大池化(MaxPool)&lt;/td&gt;
&lt;td&gt;执行最大池化&lt;/td&gt;
&lt;td&gt;特征降维、空间不变性&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;平均池化(AvgPool)&lt;/td&gt;
&lt;td&gt;执行平均池化&lt;/td&gt;
&lt;td&gt;特征降维、平滑处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;批归一化(BatchNorm)&lt;/td&gt;
&lt;td&gt;对批次数据进行归一化&lt;/td&gt;
&lt;td&gt;稳定训练、加速收敛&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;层归一化(LayerNorm)&lt;/td&gt;
&lt;td&gt;对层数据进行归一化&lt;/td&gt;
&lt;td&gt;Transformer模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;激活函数&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ReLU&lt;/td&gt;
&lt;td&gt;线性整流函数&lt;/td&gt;
&lt;td&gt;引入非线性，防止梯度消失&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Sigmoid&lt;/td&gt;
&lt;td&gt;S形激活函数&lt;/td&gt;
&lt;td&gt;二分类输出层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Tanh&lt;/td&gt;
&lt;td&gt;双曲正切函数&lt;/td&gt;
&lt;td&gt;RNN、早期网络&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Softmax&lt;/td&gt;
&lt;td&gt;将向量归一化为概率分布&lt;/td&gt;
&lt;td&gt;多分类输出层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;GELU&lt;/td&gt;
&lt;td&gt;高斯误差线性单元&lt;/td&gt;
&lt;td&gt;Transformer模型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;形状操作&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;重塑(Reshape)&lt;/td&gt;
&lt;td&gt;改变张量形状&lt;/td&gt;
&lt;td&gt;数据预处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;转置(Transpose)&lt;/td&gt;
&lt;td&gt;交换张量维度&lt;/td&gt;
&lt;td&gt;矩阵运算准备&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;连接(Concat)&lt;/td&gt;
&lt;td&gt;沿指定维度连接张量&lt;/td&gt;
&lt;td&gt;特征融合&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;展平(Flatten)&lt;/td&gt;
&lt;td&gt;将张量展平成向量&lt;/td&gt;
&lt;td&gt;连接卷积层与全连接层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;切片(Slice)&lt;/td&gt;
&lt;td&gt;提取张量的子集&lt;/td&gt;
&lt;td&gt;特征选择&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;梯度与优化&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;梯度计算(Gradient)&lt;/td&gt;
&lt;td&gt;计算函数对输入的梯度&lt;/td&gt;
&lt;td&gt;反向传播&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;随机丢弃(Dropout)&lt;/td&gt;
&lt;td&gt;随机置零部分神经元&lt;/td&gt;
&lt;td&gt;正则化、防止过拟合&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;损失函数&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;交叉熵(CrossEntropy)&lt;/td&gt;
&lt;td&gt;计算预测与真实标签的交叉熵&lt;/td&gt;
&lt;td&gt;分类问题&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;均方误差(MSE)&lt;/td&gt;
&lt;td&gt;计算预测值与真实值的平方差&lt;/td&gt;
&lt;td&gt;回归问题&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;计算图的节点是操作子&lt;/li&gt;
&lt;li&gt;计算图的边是 Tensor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;计算图的实现方式有动态数据流图和静态数据流图两种，其中 TensorFlow 是静态数据流图， PyTorch 主要是动态数据流图（其实也有 jit 等工具可以实现静态数据流图）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;静态数据流图就是&lt;strong&gt;先定义好计算图后才执行计算&lt;/strong&gt;，优点是能全局优化、内存利用效率高，缺点是调试困难&lt;/li&gt;
&lt;li&gt;动态数据流图则是&lt;strong&gt;边定义边运行&lt;/strong&gt;，每次编译时都需要构建一个新的计算图，在运行中数据流图是变化的 (例如引入了分支指令)，优点是代码简洁灵活性好，缺点是无法全局优化&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;深度学习框架实践——PyTorch&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;神经网络模型搭建的核心在于要把输入进模型的张量和各个中间张量的形状要把握清楚&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;图像分类&lt;/h2&gt;
&lt;p&gt;图像分类是深度学习中最基础也最重要的任务之一，其目标是为输入图像分配给预定义的类别标签。这个任务广泛应用于人脸识别、医学诊断、自动驾驶等领域。图像分类的评价指标包括&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;准确率(Accuracy) $\uparrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最直观的评价指标，表示正确预测的样本数占总样本数的比例&lt;/li&gt;
&lt;li&gt;公式：$Accuracy = (TP + TN) / (TP + TN + FP + FN)$&lt;/li&gt;
&lt;li&gt;PyTorch实现：&lt;pre&gt;&lt;code&gt;accuracy = (predicted == labels).sum().item() / labels.size(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;精确率(Precision)与召回率(Recall) $\uparrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;精确率：在所有被预测为正类的样本中，真正为正类的比例&lt;/li&gt;
&lt;li&gt;召回率：在所有真正为正类的样本中，被正确预测的比例&lt;/li&gt;
&lt;li&gt;公式：
&lt;ul&gt;
&lt;li&gt;$Precision = TP / (TP + FP)$&lt;/li&gt;
&lt;li&gt;$Recall = TP / (TP + FN)$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;F1分数 $\uparrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;精确率和召回率的调和平均数&lt;/li&gt;
&lt;li&gt;公式：$F1 = 2 * (Precision * Recall) / (Precision + Recall)$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;多层感知机 (MLP) 与 MNIST&lt;/h3&gt;
&lt;p&gt;多层感知机是一种前馈神经网络，由多层全连接层组成。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MLP(nn.Module):
    def __init__(self, input_size=784, hidden_sizes=[512, 256], output_size=10):
        super(MLP, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_sizes[0])        # 隐藏层1
        self.fc2 = nn.Linear(hidden_sizes[0], hidden_sizes[1])        # 隐藏层2
        self.output_layer = nn.Linear(hidden_sizes[1], output_size)    # 输出层
        
    def forward(self, x):
        x = x.view(x.size(0), -1)
        x = self.fc1(x)
        x = torch.relu(x)
        x = self.fc2(x)
        x = torch.relu(x)
        x = self.output_layer(x)
        return x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MNIST数据集是机器学习领域中最经典和广泛使用的数据集之一，特别适合作为深度学习入门的基准测试。它包含了60,000张训练图像和10,000张测试图像，每张图像为28×28像素的手写数字（0-9）灰度图像。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;下载实践代码：&amp;lt;a href=&quot;/blog/resources/deeplearning/mlp-mnist.py&quot; download=&quot;mlp-mnist.py&quot;&amp;gt;在MNIST手写数字数据集上训练MLP分类器&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;卷积神经网络 (CNN) 与 CIFAR10&lt;/h3&gt;
&lt;p&gt;卷积神经网络是一种专门用于处理网格结构数据（如图像）的神经网络。下面是一个使用PyTorch实现的简单CNN模型，用于CIFAR-10图像分类任务。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)  # 第一个卷积层 (输入通道3, 输出通道32, 3x3卷积核)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)  # 第二个卷积层 (输入通道32, 输出通道64, 3x3卷积核)
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)  # 第三个卷积层 (输入通道64, 输出通道128, 3x3卷积核)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)  # 池化层
        self.fc1 = nn.Linear(128 * 4 * 4, 512)  # 全连接层1
        self.fc2 = nn.Linear(512, 10)  # 全连接层2 (输出层)
        self.dropout = nn.Dropout(0.5)  # Dropout层
        
    def forward(self, x):
        x = self.conv1(x)
        x = torch.relu(x)
        x = self.pool(x)
        x = self.conv2(x)
        x = torch.relu(x)
        x = self.pool(x)
        x = self.conv3(x)
        x = torch.relu(x)
        x = self.pool(x)
        x = x.view(x.size(0), -1)
        x = self.fc1(x)
        x = torch.relu(x)
        x = self.dropout(x)
        x = self.fc2(x)
        return x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CIFAR-10数据集包含10个类别的32x32彩色图像，共50,000张训练图像和10,000张测试图像。上述CNN模型包含三个卷积层和两个全连接层。每个卷积层后跟ReLU激活函数和最大池化操作，以减小特征图的空间尺寸并提取重要特征。最后，通过全连接层实现分类，并使用Dropout技术防止过拟合。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;下载实践代码： &amp;lt;a href=&quot;/blog/resources/deeplearning/cnn-cifar10.py&quot; download=&quot;cnn-cifar10.py&quot;&amp;gt;在CIFAR10上训练CNN分类器&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;图像分割&lt;/h2&gt;
&lt;p&gt;图像分割的目标是将图像划分为多个有意义的区域或对象。与图像分类不同，分割需要在像素级别上进行预测，为每个像素分配一个类别标签。图像分割在医学图像分析、自动驾驶、遥感图像处理等领域有广泛应用。根据任务的性质，图像分割可以分为以下几类&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;语义分割(Semantic Segmentation)&lt;/strong&gt;：为图像中的每个像素分配一个类别标签，但不区分同一类别的不同实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实例分割(Instance Segmentation)&lt;/strong&gt;：不仅识别像素所属的类别，还区分同一类别的不同实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全景分割(Panoptic Segmentation)&lt;/strong&gt;：结合语义和实例分割，为每个像素分配类别和实例ID。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;图像分割的评价指标有&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;像素准确率(Pixel Accuracy) $\uparrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;被正确分类的像素数量与总像素数的比值&lt;/li&gt;
&lt;li&gt;公式：$PA = \frac{\sum_{i=0}^{k}p_{ii}}{\sum_{i=0}^{k}\sum_{j=0}^{k}p_{ij}}$&lt;/li&gt;
&lt;li&gt;其中$p_{ij}$是被预测为类别$j$但实际属于类别$i$的像素数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;平均像素准确率(Mean Pixel Accuracy) $\uparrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个类别的像素准确率的平均值&lt;/li&gt;
&lt;li&gt;公式：$MPA = \frac{1}{k+1}\sum_{i=0}^{k}\frac{p_{ii}}{\sum_{j=0}^{k}p_{ij}}$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;交并比(IoU, Intersection over Union) $\uparrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;也称为Jaccard指数，衡量预测分割与真实分割的重叠度&lt;/li&gt;
&lt;li&gt;公式：$IoU = \frac{TP}{TP+FP+FN}$&lt;/li&gt;
&lt;li&gt;对于特定类别：$IoU_i = \frac{p_{ii}}{\sum_{j=0}^{k}p_{ij} + \sum_{j=0}^{k}p_{ji} - p_{ii}}$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;平均交并比(mIoU, Mean Intersection over Union) $\uparrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有类别IoU的平均值&lt;/li&gt;
&lt;li&gt;公式：$mIoU = \frac{1}{k+1}\sum_{i=0}^{k}IoU_i$&lt;/li&gt;
&lt;li&gt;是图像分割任务中最常用的评价指标之一&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dice系数(Dice Coefficient) $\uparrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;衡量两个样本集的相似度，也称为F1分数&lt;/li&gt;
&lt;li&gt;公式：$Dice = \frac{2|X \cap Y|}{|X| + |Y|} = \frac{2TP}{2TP+FP+FN}$&lt;/li&gt;
&lt;li&gt;与IoU密切相关：$Dice = \frac{2 \times IoU}{1 + IoU}$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;U-Net 与 Oxford Pet&lt;/h3&gt;
&lt;p&gt;U-Net是一种经典的用于图像分割的卷积神经网络架构，特别适合医学图像分割等任务。下面是一个使用PyTorch从头实现的U-Net模型：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 定义U-Net模块
class DoubleConv(nn.Module):
    &quot;&quot;&quot;(卷积 =&amp;gt; BN =&amp;gt; ReLU) * 2&quot;&quot;&quot;
    def __init__(self, in_channels, out_channels, mid_channels=None):
        super().__init__()
        if not mid_channels:
            mid_channels = out_channels
        self.double_conv = nn.Sequential(
            nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(mid_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        return self.double_conv(x)

class Down(nn.Module):
    &quot;&quot;&quot;下采样：最大池化 + 双卷积&quot;&quot;&quot;
    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.maxpool_conv = nn.Sequential(
            nn.MaxPool2d(2),
            DoubleConv(in_channels, out_channels)
        )

    def forward(self, x):
        return self.maxpool_conv(x)

class Up(nn.Module):
    &quot;&quot;&quot;上采样：转置卷积 + 拼接 + 双卷积&quot;&quot;&quot;
    def __init__(self, in_channels, out_channels, bilinear=True):
        super().__init__()

        # 可以选择双线性插值或转置卷积进行上采样
        if bilinear:
            self.up = nn.Upsample(scale_factor=2, mode=&apos;bilinear&apos;, align_corners=True)
            self.conv = DoubleConv(in_channels, out_channels, in_channels // 2)
        else:
            self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2)
            self.conv = DoubleConv(in_channels, out_channels)

    def forward(self, x1, x2):
        x1 = self.up(x1)
        # 处理输入size不匹配的问题
        diffY = x2.size()[2] - x1.size()[2]
        diffX = x2.size()[3] - x1.size()[3]

        x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2,
                        diffY // 2, diffY - diffY // 2])
        # 拼接两个特征图
        x = torch.cat([x2, x1], dim=1)
        return self.conv(x)

class OutConv(nn.Module):
    &quot;&quot;&quot;输出卷积层&quot;&quot;&quot;
    def __init__(self, in_channels, out_channels):
        super(OutConv, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)

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

# 完整的U-Net模型
class UNet(nn.Module):
    def __init__(self, n_channels=3, n_classes=3, bilinear=False):
        super(UNet, self).__init__()
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.bilinear = bilinear

        # 编码器部分
        self.inc = DoubleConv(n_channels, 64)
        self.down1 = Down(64, 128)
        self.down2 = Down(128, 256)
        self.down3 = Down(256, 512)
        factor = 2 if bilinear else 1
        self.down4 = Down(512, 1024 // factor)
        
        # 解码器部分
        self.up1 = Up(1024, 512 // factor, bilinear)
        self.up2 = Up(512, 256 // factor, bilinear)
        self.up3 = Up(256, 128 // factor, bilinear)
        self.up4 = Up(128, 64, bilinear)
        self.outc = OutConv(64, n_classes)

    def forward(self, x):
        # 编码路径
        x1 = self.inc(x)
        x2 = self.down1(x1)
        x3 = self.down2(x2)
        x4 = self.down3(x3)
        x5 = self.down4(x4)
        
        # 解码路径 + 跳跃连接
        x = self.up1(x5, x4)
        x = self.up2(x, x3)
        x = self.up3(x, x2)
        x = self.up4(x, x1)
        logits = self.outc(x)
        return logits
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;U-Net是一种专为医学图像分割设计的卷积神经网络架构，由Olaf Ronneberger等人在2015年提出。它的特点是具有对称的U形结构，包含一个下采样的编码路径和一个上采样的解码路径，以及在对应层之间的跳跃连接(skip connections)。这些跳跃连接能够将编码阶段的高分辨率特征直接传递到解码阶段的相应层，有效地保留空间信息，对精确分割边界非常重要。&lt;/p&gt;
&lt;p&gt;Oxford-IIIT Pet数据集包含约7,400张猫和狗的图像，来自37个不同品种。每张图像都有对应的像素级分割标注，将图像分为前景(宠物)、背景和边界三个类别。这个数据集是用于&lt;strong&gt;语义分割&lt;/strong&gt;任务的，因为它为每个像素分配类别标签(前景/背景/边界)，但不区分同一类别的不同实例。这个数据集非常适合训练和评估像U-Net这样的分割网络。数据集的难点在于目标对象(宠物)的姿势、大小和背景变化多样，为图像分割模型提供了很好的挑战。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/unet-oxford.png&quot; alt=&quot;&quot; /&gt;
图像分割是一个比较困难的任务&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;下载实践代码： &amp;lt;a href=&quot;/blog/resources/deeplearning/unet-oxfordpet.py&quot; download=&quot;unet-oxfordpet.py&quot;&amp;gt;在Oxford Pet数据集上训练Unet分割网络&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;图像生成&lt;/h2&gt;
&lt;p&gt;图像生成是一项旨在创建新的、逼真的图像的任务。它是生成式人工智能的一个重要分支，近年来随着深度学习技术的发展取得了显著进展。图像生成模型可以从随机噪声、文本描述、条件输入或已有图像创建全新的视觉内容。&lt;/p&gt;
&lt;p&gt;常见的图像生成模型包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;生成对抗网络(GANs)&lt;/strong&gt;：通过生成器和判别器的对抗训练生成逼真图像&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;变分自编码器(VAEs)&lt;/strong&gt;：学习数据的潜在分布并从中采样生成新图像&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自回归模型&lt;/strong&gt;：像素级别的条件生成，如PixelRNN和PixelCNN&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扩散模型&lt;/strong&gt;：如DDPM和Stable Diffusion，通过逐步去噪过程生成图像&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流模型&lt;/strong&gt;：如Glow，通过可逆变换学习数据分布&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;图像生成的评价指标&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;评估生成图像的质量是一个具有挑战性的问题，因为&quot;好&quot;的图像往往是主观的。然而，研究者已经开发出一系列客观指标：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inception Score (IS) $\uparrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;衡量生成图像的多样性和质量&lt;/li&gt;
&lt;li&gt;高IS分数表示生成的图像既清晰（每个图像的类别分布有较高的置信度）又多样（不同图像的类别分布各不相同）&lt;/li&gt;
&lt;li&gt;公式：$IS = \exp(\mathbb{E}_x[KL(p(y|x) || p(y))])$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fréchet Inception Distance (FID) $\downarrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;测量生成图像与真实图像分布之间的距离&lt;/li&gt;
&lt;li&gt;较低的FID表示生成的图像更接近真实分布&lt;/li&gt;
&lt;li&gt;公式：$FID = ||\mu_r - \mu_g||^2 + Tr(\Sigma_r + \Sigma_g - 2(\Sigma_r\Sigma_g)^{1/2})$&lt;/li&gt;
&lt;li&gt;其中$\mu_r, \mu_g$分别是真实和生成图像特征的均值，$\Sigma_r, \Sigma_g$是对应的协方差矩阵&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;结构相似性指数(SSIM) $\uparrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;比较两幅图像在亮度、对比度和结构上的相似程度&lt;/li&gt;
&lt;li&gt;适用于条件图像生成（如图像转换、超分辨率等）&lt;/li&gt;
&lt;li&gt;范围在[-1,1]之间，1表示完全相同&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;峰值信噪比(PSNR) $\uparrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;衡量重建图像与原始图像的对应像素差异&lt;/li&gt;
&lt;li&gt;主要用于图像重建和修复任务&lt;/li&gt;
&lt;li&gt;单位为分贝(dB)，值越高表示重建质量越好&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Learned Perceptual Image Patch Similarity (LPIPS) $\downarrow$&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基于深度特征的感知相似度度量&lt;/li&gt;
&lt;li&gt;比像素级指标更符合人类视觉感知&lt;/li&gt;
&lt;li&gt;值越低表示感知相似度越高&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;人类评估&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;人类评价者对生成图像的质量进行主观评分&lt;/li&gt;
&lt;li&gt;通常采用均值意见得分(MOS)或AB测试&lt;/li&gt;
&lt;li&gt;最直接但成本高且难以大规模实施&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;评估图像生成模型时，最好结合多个指标，因为每种指标都有其优缺点和适用场景。例如，FID在评估无条件生成模型时很流行，而PSNR和SSIM则更适合条件生成任务。&lt;/p&gt;
&lt;h3&gt;变分自编码器 与 MNIST&lt;/h3&gt;
&lt;p&gt;变分自编码器(VAE)是一种生成式模型，它可以学习数据的概率分布，并生成与原始数据相似的新样本。条件变分自编码器(CVAE)通过添加条件信息（如类标签）来控制生成过程。&lt;/p&gt;
&lt;p&gt;以下是一个使用PyTorch实现的条件变分自编码器(CVAE)，用于MNIST数字图像的条件生成。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 条件变分自编码器模型
class CVAE(nn.Module):
    def __init__(self, latent_dim, num_classes):
        super(CVAE, self).__init__()
        self.img_size = 28  # 图像大小和条件信息
        self.latent_dim = latent_dim
        self.num_classes = num_classes
        
        self.encoder = nn.Sequential( # 编码器网络
            nn.Conv2d(1 + num_classes, 32, kernel_size=3, stride=2, padding=1),  # 14x14
            nn.LeakyReLU(0.2),
            nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),  # 7x7
            nn.LeakyReLU(0.2),
            nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),  # 7x7
            nn.LeakyReLU(0.2),
            nn.Flatten()
        )
        
        self.flat_size = 128 * 7 * 7   # 计算展平后的特征大小

        self.fc_mu = nn.Linear(self.flat_size, latent_dim)  # 均值和方差预测层
        self.fc_logvar = nn.Linear(self.flat_size, latent_dim)
        
        self.decoder_input = nn.Linear(latent_dim + num_classes, 128 * 7 * 7)  # 解码器输入层
        self.decoder = nn.Sequential(  # 解码器网络
            nn.Unflatten(1, (128, 7, 7)),
            nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1),  # 14x14
            nn.LeakyReLU(0.2),
            nn.ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1),  # 28x28
            nn.LeakyReLU(0.2),
            nn.Conv2d(32, 1, kernel_size=3, stride=1, padding=1),
            nn.Sigmoid()  # 输出像素值在[0,1]之间
        )
        
    def encode(self, x, c):
        c = c.view(-1, self.num_classes, 1, 1).expand(-1, -1, self.img_size, self.img_size) # 将条件信息嵌入到输入中
        x_c = torch.cat([x, c], dim=1)  # 在通道维度上拼接
        
        h = self.encoder(x_c) # 编码器前向传播
        mu = self.fc_mu(h)
        logvar = self.fc_logvar(h)
        return mu, logvar
    
    def reparameterize(self, mu, logvar):
        std = torch.exp(0.5 * logvar) # 重参数化技巧
        eps = torch.randn_like(std)
        z = mu + eps * std
        return z
    
    def decode(self, z, c):
        z_c = torch.cat([z, c], dim=1) # 将条件信息与潜在表示拼接
        h = self.decoder_input(z_c) # 解码器前向传播
        x_recon = self.decoder(h.view(-1, 128, 7, 7))
        return x_recon
    
    def forward(self, x, c):
        c_onehot = F.one_hot(c, self.num_classes).float() # 将类标签转换为one-hot向量
        mu, logvar = self.encode(x, c_onehot) # 编码
        z = self.reparameterize(mu, logvar) # 采样潜在表示
        x_recon = self.decode(z, c_onehot) # 解码
        return x_recon, mu, logvar
    
    def sample(self, num_samples, c):
        &quot;&quot;&quot;
        给定条件c，生成样本
        c: (num_samples,) 类标签
        &quot;&quot;&quot;
        c_onehot = F.one_hot(c, self.num_classes).float()         # 将类标签转换为one-hot向量
        z = torch.randn(num_samples, self.latent_dim).to(device)  # 从标准正态分布采样潜在向量
        samples = self.decode(z, c_onehot)  # 解码生成样本
        return samples
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;插值实验&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/cvae_interpolation_3_to_7.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;生成的手写数字&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/requested_digits.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;下载实践代码： &amp;lt;a href=&quot;/blog/resources/deeplearning/vae-mnist.py&quot; download=&quot;vae-mnist.py&quot;&amp;gt;在MNIST数据集上训练VAE生成网络&amp;lt;/a&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;PyTorch使用小结&lt;/h1&gt;
&lt;h2&gt;自定义数据集&lt;/h2&gt;
&lt;p&gt;在 PyTorch 中，只需继承 &lt;code&gt;torch.utils.data.Dataset&lt;/code&gt; 并实现以下两个方法，即完成了一个数据集的定义&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__len__&lt;/code&gt;：返回样本总数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;__getitem__&lt;/code&gt;：根据索引返回单个样本（通常是 &lt;code&gt;(数据, 标签)&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
from PIL import Image
import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms

class MyImageDataset(Dataset):
    def __init__(self, img_dir, labels, transform=None):
        &quot;&quot;&quot;
        img_dir: 存放图片的文件夹路径
        labels: dict, { &apos;img1.jpg&apos;: 0, &apos;img2.jpg&apos;: 1, ... }
        transform: torchvision.transforms.Compose
        &quot;&quot;&quot;
        self.img_dir = img_dir
        self.labels = labels
        self.transform = transform
        self.filenames = list(labels.keys())

    def __len__(self):
        return len(self.filenames)

    def __getitem__(self, idx):
        fname = self.filenames[idx]
        img_path = os.path.join(self.img_dir, fname)
        image = Image.open(img_path).convert(&apos;RGB&apos;)
        label = self.labels[fname]
        if self.transform:
            image = self.transform(image)
        return image, label

# 使用方式
transform = transforms.Compose([
    transforms.Resize((128,128)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.5]*3, std=[0.5]*3),
])

# 假设有一个字典 labels_map
dataset = MyImageDataset(img_dir=&apos;data/images&apos;, labels=labels_map, transform=transform)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True, num_workers=4)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;模型训练范式&lt;/h2&gt;
&lt;p&gt;PyTorch 的模型训练范式通常是如下形式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch

def train_one_epoch(model, dataloader, optimizer, criterion, device):
    model.train()
    total_loss = 0
    for inputs, targets in dataloader:
        inputs, targets = inputs.to(device), targets.to(device)
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()
        total_loss += loss.item() * inputs.size(0)
    avg_loss = total_loss / len(dataloader.dataset)
    print(f&quot;Train Loss: {avg_loss:.4f}&quot;)
    return avg_loss

def evaluate(model, dataloader, criterion, device):
    model.eval()
    total_loss = 0
    correct = 0
    with torch.no_grad():
        for inputs, targets in dataloader:
            inputs, targets = inputs.to(device), targets.to(device)
            outputs = model(inputs)
            loss = criterion(outputs, targets)
            total_loss += loss.item() * inputs.size(0)
            pred = outputs.argmax(dim=1)
            correct += (pred == targets).sum().item()
    avg_loss = total_loss / len(dataloader.dataset)
    accuracy = correct / len(dataloader.dataset)
    print(f&quot;Val   Loss: {avg_loss:.4f}, Acc: {accuracy:.4f}&quot;)
    return avg_loss, accuracy

# 主训练循环
for epoch in range(1, epochs+1):
    train_loss = train_one_epoch(model, train_loader, optimizer, criterion, device)
    val_loss, val_acc = evaluate(model, val_loader, criterion, device)
    # 如果使用学习率调度器，可在此处 step
    # scheduler.step(val_loss)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Profiler 工具&lt;/h2&gt;
&lt;p&gt;PyTorch Profiler 是一个内置的性能分析工具，可以帮助识别模型训练和推理过程中的训练瓶颈。Profiler 会收集 GPU 或 CPU 的详细运行实践统计信息，从而提供深入的性能分析和可视化。&lt;/p&gt;
&lt;h3&gt;基本使用&lt;/h3&gt;
&lt;p&gt;经典形式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def profile(model, device, data_loader):
    dataiter = iter(data_loader)
    data, target = next(dataiter)
    data, target = data.to(device), target.to(device)
    # use_cuda=False，是只分析GPU
    with torch.autograd.profiler.profile(use_cuda=False) as prof:   
        model(data[0].reshape(1,1,28,28))
    print(prof)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更现代的API形式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from torch import profiler

def profile(model, device, data_loader):
    with profiler.profile(
        activities=[
            profiler.ProfilerActivity.CPU,
            profiler.ProfilerActivity.CUDA,   
        ],
        record_shapes=True,
        profile_memory=True,
        with_stack=True
    ) as prof:
        # 需要分析的代码
        data, target = next(iter(data_loader))
        data, target = data.to(device), target.to(device)
        model(data)
    print(prof.key_averages().table(sort_by=&apos;cuda_time_total&apos;, row_limit=10))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与训练循环集成的形式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;with torch.profiler.profile(
    activities=[
        torch.profiler.ProfilerActivity.CPU, 
        torch.profiler.ProfilerActivity.CUDA],
    schedule=torch.profiler.schedule(wait=1, warmup=1, active=3, repeat=1),
    on_trace_ready=torch.profiler.tensorboard_trace_handler(&apos;./profiler_logs&apos;),
    record_shapes=True,
    profile_memory=True,
    with_stack=True
) as prof:
    for step, (inputs, labels) in enumerate(dataloader):
        if step &amp;gt;= (1 + 1 + 3) * 1:
            break
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        prof.step()  # 必须调用，与 schedule 配合使用
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;服务器使用&lt;/h1&gt;
&lt;h2&gt;服务器登录&lt;/h2&gt;
&lt;p&gt;服务器一般可以通过 vscode 等 IDE 使用 ssh 进行连接，具体使用方式可以参考相关资料（例如&lt;a href=&quot;https://zhuanlan.zhihu.com/p/661255803&quot;&gt;知乎上的讲解&lt;/a&gt;）。除了使用 IDE 连接外，本地的终端也是可以连接服务器的。在终端中输入下列指令后&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh ada@10.184.16.88 # 服务器账号名@服务器ip地址
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即可登录服务器，连接到服务器的终端。如果账号设有密码，终端会提示输入密码。&lt;/p&gt;
&lt;h2&gt;Vim&lt;/h2&gt;
&lt;p&gt;服务器代码编辑一般是使用 vscode 等 IDE。不过当使用终端登录时，也可以使用 vim 等编辑器编辑代码。使用 Vim 打开代码脚本的指令为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim train.py    # 打开 train.py 文件
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;常用 Vim 指令&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在光标处进入插入模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在光标后进入插入模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;I&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在行首进入插入模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在行尾进入插入模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;o&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在当前行下方新开一行并进入插入模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;O&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在当前行上方新开一行并进入插入模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;Esc&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;退出插入模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;h&lt;/code&gt;/&lt;code&gt;j&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt;/&lt;code&gt;l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;光标左/下/上/右移动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到下一个单词开头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到当前/上一个单词开头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;e&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到当前/下一个单词末尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到行首&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;^&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到本行第一个非空字符&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到行尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;G&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到文件末尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;跳到文件开头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:&lt;/code&gt;数字&lt;/td&gt;
&lt;td&gt;跳到指定行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f&amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前行向右查找并跳到下一个 &lt;code&gt;&amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t&amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前行向右查找并跳到 &lt;code&gt;&amp;lt;char&amp;gt;&lt;/code&gt; 之前的位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;F&amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前行向左查找并跳到下一个 &lt;code&gt;&amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t&amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前行向左查找并跳到 &lt;code&gt;&amp;lt;char&amp;gt;&lt;/code&gt; 之后的位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cw&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除从光标到单词末尾并进入插入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除整行并进入插入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除从光标到行尾并进入插入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;r&amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;替换光标下单个字符为 &lt;code&gt;&amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;R&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;进入替换模式（Overwrite）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;u&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;撤销上一步操作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;重做（撤销的反向操作）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;重复上一次编辑操作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;复制（yank）当前行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yw&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;复制从光标到单词末尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在光标后粘贴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;P&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在光标前粘贴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除（剪切）当前行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d&amp;lt;number&amp;gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除从当前行起连续多行（如 &lt;code&gt;3dd&lt;/code&gt; 删除 3 行）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;df&amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除到并包含下一个 &lt;code&gt;&amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d$&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;删除到行尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;进入可视模式（按字符选区）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;V&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;进入可视行模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;进入可视块模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:&lt;/code&gt;s/old/new/g&lt;/td&gt;
&lt;td&gt;全文替换 &lt;code&gt;old&lt;/code&gt; 为 &lt;code&gt;new&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:%s/old/new/gc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;全文替换并交互确认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:set number&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;显示行号&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:set nonumber&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;关闭行号显示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:set hlsearch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;高亮搜索结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:set nohlsearch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;取消高亮&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:tabnew [file]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;新建选项卡并打开文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:tabnext&lt;/code&gt;/&lt;code&gt;:tabprev&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;切换到下一个/上一个选项卡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:split [file]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;水平分屏打开文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:vsplit [file]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;垂直分屏打开文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+w h/j/k/l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;在分屏间移动光标&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;qa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;开始录制宏到寄存器 &lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;停止录制宏&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;播放寄存器 &lt;code&gt;a&lt;/code&gt; 中的宏&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@@&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;重复上一个宏播放&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保存当前文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;退出 Vim&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:wq&lt;/code&gt; 或 &lt;code&gt;:x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保存并退出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ZZ&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;保存并退出（在普通模式下输入）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:q!&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;不保存强制退出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:qa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;退出所有打开的文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:qa!&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;强制退出所有，无需保存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:w filename&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将当前缓冲区另存为 &lt;code&gt;filename&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:e filename&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;打开或切换到 &lt;code&gt;filename&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;NVIDIA 环境查看&lt;/h2&gt;
&lt;p&gt;在深度学习服务器上，我们常用下面的命令来检查 GPU、驱动和 CUDA 环境是否就绪&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看 NVIDIA 驱动与 GPU 状态
nvidia-smi

# 查看 CUDA 工具包版本
nvcc --version

# 查看已安装的 PyTorch 是否支持 CUDA
python -c &quot;import torch; print(&apos;CUDA 可用：&apos;, torch.cuda.is_available())&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CUDA (Compute Unified Device Architecture) 是一种由 NVIDIA 退出的通用并行计算架构，该架构使用 GPU 解决复杂的计算问题，包含 CUDA 指令集架构 (ISA) 以及 GPU 内部的并行计算引擎。&lt;/p&gt;
&lt;h1&gt;训练过程可视化&lt;/h1&gt;
&lt;h2&gt;TensorBoard&lt;/h2&gt;
&lt;p&gt;在 PyTorch 中可以非常方便地集成 TensorBoard，以实时监控训练过程中的指标、可视化网络结构和图像。&lt;/p&gt;
&lt;h3&gt;1. 安装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pip install tensorboard
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 在训练脚本中添加 SummaryWriter&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;from torch.utils.tensorboard import SummaryWriter

# 指定日志目录
writer = SummaryWriter(log_dir=&apos;runs/exp1&apos;)

for epoch in range(1, epochs+1):
    # 训练循环
    train_loss, train_acc = train_one_epoch(...)
    val_loss, val_acc       = evaluate(...)

    # 记录标量
    writer.add_scalar(&apos;Loss/train&apos;, train_loss, epoch)
    writer.add_scalar(&apos;Loss/val&apos;,   val_loss,   epoch)
    writer.add_scalar(&apos;Acc/train&apos;,  train_acc,  epoch)
    writer.add_scalar(&apos;Acc/val&apos;,    val_acc,    epoch)

    # 可视化参数分布或梯度
    for name, param in model.named_parameters():
        writer.add_histogram(f&apos;Param/{name}&apos;, param, epoch)
        writer.add_histogram(f&apos;Grad/{name}&apos;, param.grad, epoch)

    # 第一次记录时可添加网络结构图
    if epoch == 1:
        dummy_input = torch.randn(1, 3, 224, 224).to(device)
        writer.add_graph(model, dummy_input)

# 训练结束后关闭
writer.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 启动 TensorBoard&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;tensorboard --logdir=&apos;./runs&apos; --port 6006
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在浏览器打开 http://localhost:6006 ，即可看到实时更新的训练曲线、直方图和网络结构。&lt;/p&gt;
&lt;p&gt;如果是在服务器上运行的训练过程，需要使用 端口映射 才能在本地网页查看。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 本地端口 7007 转发到服务器的 localhost:6006
ssh -L 7007:localhost:6006 ada@10.184.16.88
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在浏览器打开 http://localhost:7007 即可查看。&lt;/p&gt;
&lt;h3&gt;4. 使用 TensorBoard 可视化 Profiler 结果&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;tensorboard --logdir=./profiler_logs
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Weights &amp;amp; Biases (W&amp;amp;B)&lt;/h2&gt;
&lt;p&gt;Weigths &amp;amp; Biases（简称 W&amp;amp;B）是一款流行的实验管理与可视化平台，能够帮助你在云端实时跟踪、可视化训练指标、超参、模型权重和数据集版本，并自动生成对比报告。&lt;/p&gt;
&lt;h3&gt;1. 安装与登录&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pip install wandb
wandb login  # 首次运行后按提示粘贴 API key
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 在训练脚本中集成 W&amp;amp;B&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import wandb
from torch.utils.tensorboard import SummaryWriter

# 1 初始化一个 run，project 名称可自定义
wandb.init(project=&quot;my-deeplearning-project&quot;, config={
    &quot;epochs&quot;: epochs,
    &quot;batch_size&quot;: batch_size,
    &quot;lr&quot;: learning_rate,
    &quot;latent_dim&quot;: latent_dim,
})

# 2 自动追踪模型、梯度和超参
wandb.watch(model, log=&quot;all&quot;, log_freq=100)

for epoch in range(1, epochs+1):
    train_loss, train_acc = train_one_epoch(...)
    val_loss, val_acc     = evaluate(...)

    # 3 记录标量
    wandb.log({
        &quot;epoch&quot;: epoch,
        &quot;loss/train&quot;: train_loss,
        &quot;loss/val&quot;:   val_loss,
        &quot;acc/train&quot;:  train_acc,
        &quot;acc/val&quot;:    val_acc,
    })

# 4 保存最佳模型到 W&amp;amp;B Artifacts
torch.save(model.state_dict(), &quot;best_model.pth&quot;)
wandb.save(&quot;best_model.pth&quot;)

# 5 结束 run
wandb.finish()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 在 W&amp;amp;B 仪表盘查看&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;浏览器打开 https://wandb.ai&lt;/li&gt;
&lt;li&gt;进入你所在的 project，即可查看训练曲线、超参对比、模型参数直方图、图片和对比报告。&lt;/li&gt;
&lt;li&gt;支持自动对比多个 runs，以及与团队成员共享结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;参考资料&lt;/em&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chenzomi12.github.io/05Framework03DataFlow/03Atuodiff.html&quot;&gt;计算图与自动微分，导数的计算同样构建了数据流图&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pytorch.org/&quot;&gt;PyTorch官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.tensorflow.org/?hl=en&quot;&gt;TensorFlow官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wandb.ai/site/&quot;&gt;W &amp;amp; B 官网&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>机器人学基础 第四章 机械臂的速度与静力</title><link>https://adalovelemon.github.io/posts/content/coursenotes/robotics/chapter-4/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/robotics/chapter-4/</guid><pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;机械臂连杆的速度方程和静力分析&lt;/p&gt;
&lt;h1&gt;4.1 速度分析目标&lt;/h1&gt;
&lt;p&gt;分析机械臂连杆的速度的目标是&lt;strong&gt;求机械臂的末端及各个中间连杆的运动速度与关节速率之间的关系&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设连杆0的固连框架 ${0}$ 为参考坐标系。固连框架 ${i}$ 的速度有两种，分别是框架 ${i}$ 原点的&lt;strong&gt;线速度 $^{0}v_i = \ ^{0}V_{iorg}$&lt;/strong&gt; 和 连杆 $i$ 整个刚体的&lt;strong&gt;角速度 $^{0}\omega_i = \ ^{0}\Omega_i$&lt;/strong&gt;。有时，我们会利用在刚体自身框架下的速度向量表达，以方便进行向量的迭代计算，因此需要涉及到速度向量在不同框架下的转换。具体的方法是使用旋转矩阵进行变换，例如从框架 ${i}$ 变到框架 ${0}$ 的公式为
$$
\begin{aligned}
^{0}v_i = \ ^{0}&lt;em&gt;{i}R\ ^{i}v_i\
^{0}\omega_i = \ ^{0}&lt;/em&gt;{i}R\ ^{i}\omega_i\
\end{aligned}
$$&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;连杆的速度表达&lt;/th&gt;
&lt;th&gt;速度传播&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;assets/velocity.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/vprop.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;4.2 速度传递公式&lt;/h1&gt;
&lt;p&gt;连杆间的速度传递公式有两种形式，一种是在绝对框架 ${0}$ 表达下的迭代形式，另一种是在自身框架 ${i}$ 表达的迭代形式。&lt;/p&gt;
&lt;h2&gt;4.2.1 旋转关节&lt;/h2&gt;
&lt;p&gt;旋转关节 $i + 1$ 连接的连杆 $i$ 和连杆 $i + 1$ 之间的速度传播公式为&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自身框架形式&lt;/strong&gt;
$$
\begin{cases}
&amp;amp;^{i+1}\omega_{i+1} = \ ^{i+1}&lt;em&gt;{i}R\ ^{i}\omega_i + \dot\theta&lt;/em&gt;{i+1} \ ^{i+1} \hat{\mathbf{z}}&lt;em&gt;{i+1} \
&amp;amp;^{i+1}v&lt;/em&gt;{i+1} = \ ^{i+1}&lt;em&gt;{i}R(^{i} v_i + \ ^{i}\omega_i \times \ ^{i}p&lt;/em&gt;{i+1})
\end{cases}
$$
其中 $\hat{\mathbf{z}}_{i+1}$ 是框架 ${i+1}$ 的 Z 轴方向的单位向量。&lt;/p&gt;
&lt;p&gt;注：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;框架 ${i+1}$ 的原点其实就是连杆 $i$ 的末端，因此 $\vec{v}&lt;em&gt;{i+1} = \vec{v}&lt;em&gt;i + \vec{\omega}&lt;em&gt;i \times \vec{p}&lt;/em&gt;{i+1}$，其中 $\vec{p}&lt;/em&gt;{i+1}$ 是框架 ${i+1}$ 原点 $O&lt;/em&gt;{i+1}$ 在框架 ${i}$ 中的位矢；&lt;/li&gt;
&lt;li&gt;连杆 $i+1$ 的角速度满足 $\vec{\omega}&lt;em&gt;{i+1} = \vec{\omega}&lt;/em&gt;{i} + \dot\theta_{i+1} \hat{\mathbf{z}}&lt;em&gt;{i+1}$，因为 $\dot\theta&lt;/em&gt;{i+1}$ 是连杆 $i+1$ 相对于连杆 $i$ 的相对角速度大小，其方向沿 框架 ${i+1}$ 的 Z 轴方向，因此总的角速度用合成公式即可得到。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;绝对框架形式&lt;/strong&gt;
$$
\begin{cases}
&amp;amp;^{0}\omega_{i+1} = \ ^{0}\omega_i + \dot\theta_{i+1} \ ^{0}&lt;em&gt;{i+1}R\ ^{i+1} \hat{\mathbf{z}}&lt;/em&gt;{i+1} \
&amp;amp;^{0}v_{i+1} = \ ^{0} v_i + \ ^{0}\omega_i \times \ ^{0}&lt;em&gt;{i}R\ ^{i}p&lt;/em&gt;{i+1}
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;物理含义同上。&lt;/p&gt;
&lt;h2&gt;4.2.2 棱柱关节&lt;/h2&gt;
&lt;p&gt;棱柱关节 $i + 1$ 连接的连杆 $i$ 和连杆 $i + 1$ 之间的速度传播公式为&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自身框架形式&lt;/strong&gt;
$$
\begin{cases}
&amp;amp;^{i+1}\omega_{i+1} = \ ^{i+1}&lt;em&gt;{i}R\ ^{i}\omega_i \
&amp;amp;^{i+1}v&lt;/em&gt;{i+1} = \ ^{i+1}&lt;em&gt;{i}R(^{i} v_i + \ ^{i}\omega_i \times \ ^{i}p&lt;/em&gt;{i+1}) + \dot{d}&lt;em&gt;{i+1}\ ^{i+1}\hat{\mathbf{z}}&lt;/em&gt;{i+1}
\end{cases}
$$
其中 $\hat{\mathbf{z}}_{i+1}$ 是框架 ${i+1}$ 的 Z 轴方向的单位向量。&lt;/p&gt;
&lt;p&gt;注：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同样地，这里框架 ${i+1}$ 的原点也是连杆 $i$ ，因此 $\vec{v}&lt;em&gt;{i+1} = \vec{v}&lt;em&gt;i + \vec{\omega}&lt;em&gt;i \times \vec{p}&lt;/em&gt;{i+1} + \dot{d}&lt;/em&gt;{i+1}\hat{\mathbf{z}}&lt;/em&gt;{i+1}$，其中 $\vec{p}&lt;em&gt;{i+1}$ 是框架 ${i+1}$ 原点 $O&lt;/em&gt;{i+1}$ 在框架 ${i}$ 中的位矢，这里比旋转关节多出来的那一项是相对运动的速度；&lt;/li&gt;
&lt;li&gt;由于是旋转关节，因此没有相对的角速度部分，故连杆 $i+1$ 的角速度就是连杆 $i$ 的角速度 $\vec{\omega}&lt;em&gt;{i+1} = \vec{\omega}&lt;/em&gt;{i}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;绝对框架形式&lt;/strong&gt;
$$
\begin{cases}
&amp;amp;^{0}\omega_{i+1} = \ ^{0}\omega_i \
&amp;amp;^{0}v_{i+1} = \ ^{0} v_i + \ ^{0}\omega_i \times \ ^{0}&lt;em&gt;{i}R\ ^{i}p&lt;/em&gt;{i+1} + \dot{d}&lt;em&gt;{i+1}\ ^{0}&lt;/em&gt;{i+1} R \ ^{i+1}\hat{\mathbf{z}}_{i+1}
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;物理含义同上。&lt;/p&gt;
&lt;p&gt;根据上述的迭代公式，可以得到从连杆 1 开始的，直至最后一根连杆 $n$ 的速度结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;求平面 2R 的末端速度与关节速率的函数关系&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/planar2R.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;前向运动学方程为
$$
\begin{aligned}
^{0}&lt;em&gt;{1}T &amp;amp;= \begin{bmatrix} c\theta_1 &amp;amp; -s\theta_1 &amp;amp; 0 &amp;amp; 0\ s\theta_1 &amp;amp; c\theta_1 &amp;amp;0 &amp;amp;0\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
^{1}&lt;/em&gt;{2}T &amp;amp;= \begin{bmatrix} c\theta_2 &amp;amp; -s\theta_2 &amp;amp; 0 &amp;amp; L_1\ s\theta_2 &amp;amp; c\theta_2 &amp;amp;0 &amp;amp;0\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
^{2}&lt;em&gt;{3}T &amp;amp;= \begin{bmatrix} 1 &amp;amp; 0 &amp;amp; 0 &amp;amp; L_2\ 0 &amp;amp; 1 &amp;amp;0 &amp;amp;0\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
^{0}&lt;/em&gt;{3}T(\theta_1, \theta_2) &amp;amp;= \begin{bmatrix} c(\theta_1 + \theta_2) &amp;amp; -s(\theta_1 + \theta_2) &amp;amp; 0 &amp;amp; L_1 c\theta_1 + L_2 c(\theta_1 + \theta_2)\ s(\theta_1 + \theta_2) &amp;amp; c(\theta_1 + \theta_2) &amp;amp;0 &amp;amp;L_1 s\theta_1 + L_2 s(\theta_1 + \theta_2)\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
\end{aligned}
$$
其中，$c(\cdot)$ 和 $s(\cdot)$ 分别是余弦函数和正弦函数的缩写。&lt;/p&gt;
&lt;p&gt;已知 $^0\omega_0 = 0, \ ^0v_0 = 0$，利用迭代公式，有
$$
\begin{aligned}
\ ^{1}\omega_1 &amp;amp;= \ ^{1}&lt;em&gt;{0}R \ ^{0}w_0 + \dot\theta_1 \ ^{1}\mathbf{\hat{z}}&lt;em&gt;1 = \begin{bmatrix}0\ 0\ \dot\theta_1\end{bmatrix}\
\ ^{2}\omega_2 &amp;amp;= \ ^{2}&lt;/em&gt;{1}R \ ^{1}w_1 + \dot\theta_2 \ ^{2}\mathbf{\hat{z}}&lt;em&gt;2 = \begin{bmatrix} 0 \ 0\ \dot\theta_1 + \dot\theta_2\end{bmatrix} \
\ ^{3}\omega_3 &amp;amp;= \ ^{3}&lt;/em&gt;{2}R\ ^{2}\omega_2 = \ ^{2}\omega_2
\end{aligned}
$$
注意这里计算 $\ ^{i+1}&lt;/em&gt;{i} R$ 使用 $\ ^{i+1}&lt;em&gt;{i}R = \ ^{i}&lt;/em&gt;{i+1}R^\top$ 最方便。
$$
\begin{aligned}
\ ^{1}v_1 &amp;amp;= \ ^{1}&lt;em&gt;{0}R(\ ^{0}v_0 + \ ^{0}\omega_0 \times \ ^{0} p_1) = 0\
\ ^{2}v_2 &amp;amp;= \ ^{2}&lt;/em&gt;{1}R(\ ^{1}v_1 + \ ^{1}\omega_1 \times \ ^{1}p_2) = \begin{bmatrix} L_1\dot\theta_1 s\theta_2 \ L_1\dot\theta_1c\theta_2 \ 0\end{bmatrix} \
\ ^{3}v_3 &amp;amp;= \ ^{3}_{2}R(\ ^{2}v_2 + \ ^{2}\omega_2 \times \ ^{2}p_3) = \begin{bmatrix} L_1\dot\theta_1 s\theta_2\ L_1\dot\theta_1c\theta_2 + L_2(\dot\theta_1 + \dot\theta_2) \ 0\end{bmatrix}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;注意，这里的 $\ ^{3}v_3$ 依然需要使用旋转关节的传播公式，因为线速度实际是上一个连杆末端的速度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当从后续 Jacobian 矩阵的角度看这个问题的时候，会发现 $v$ 关于 x, y 分量的项数并不对称，其实这是因为这里的速度表达是在刚体自身框架下的表达，而不是在绝对框架 ${0}$ 下的表达形式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;实际上，将上述结果变换到框架 ${0}$ 中，得到
$$
\begin{aligned}
\ ^{0}v_3 &amp;amp;= \ ^{0}_{3}R \ ^{3}v_3 = \begin{bmatrix} c(\theta_1 + \theta_2) &amp;amp;-s(\theta_1 + \theta_2) &amp;amp;0 \ s(\theta_1 + \theta_2) &amp;amp;c(\theta_1 + \theta_2) &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;1 \end{bmatrix} \begin{bmatrix} L_1\dot\theta_1 s\theta_2\ L_1\dot\theta_1c\theta_2 + L_2(\dot\theta_1 + \dot\theta_2) \ 0\end{bmatrix}\
&amp;amp;= \begin{bmatrix} L_1 s\theta_2 c(\theta_1 + \theta_2)\dot\theta_1 - s(\theta_1 + \theta_2)(L_1\dot\theta_1c\theta_2 + L_2(\dot\theta_1 + \dot\theta_2)) \  L_1 s\theta_2 s(\theta_1 + \theta_2)\dot\theta_1 + c(\theta_1 + \theta_2)(L_1\dot\theta_1c\theta_2 + L_2(\dot\theta_1 + \dot\theta_2)) \ 0\end{bmatrix}\
&amp;amp;= \begin{bmatrix} -L_1s\theta_1\dot\theta_1 - L_2 s(\theta_1 + \theta_2)\dot\theta_1 -L_2s(\theta_1 + \theta_2)\dot\theta_2 \ L_1c\theta_1\dot\theta_1 + L_2 c(\theta_1 + \theta_2)\dot\theta_1 + L_2 c(\theta_1 + \theta_2)\dot\theta_2 \ 0\end{bmatrix}
\end{aligned}
$$&lt;/p&gt;
&lt;h1&gt;4.3 Jacobian 矩阵&lt;/h1&gt;
&lt;h2&gt;4.3.1 数学定义&lt;/h2&gt;
&lt;p&gt;Jacobian 是向量函数 $f(x) \in \mathbb{R}^m$ 对向量 $x \in \mathbb{R}^n$ 的一阶导数，其形式为一个矩阵
$$
J(x) = \frac{\partial f(x)}{\partial x} \in \mathbb{R}^{m \times n}
$$
对应微分形式为
$$
\mathrm{d}f(x) = J(x)\mathrm{d}x
$$&lt;/p&gt;
&lt;h2&gt;4.3.2 Jacobian 矩阵求速度关系&lt;/h2&gt;
&lt;p&gt;设想我们得到了末端位置 $\mathcal{X} = \begin{bmatrix} x \ y\ z\end{bmatrix}$ 对关节变量 $q = \begin{bmatrix} q_1 \ \vdots \ q_n\end{bmatrix}$ 的正向运动学关系为
$$
\mathcal{X} = T(q)
$$
那么，通过求 Jacobian 矩阵 $J(q)$，并且让微分形式同时对时间 $t$ 求导，可以得到
$$
\dot{\mathcal{X}} = J(q) \dot q
$$
这个公式即是速度关系，实现了从构形空间到笛卡尔空间的速度映射。注意，通常这里所取的向量表达框架就是绝对框架 ${0}$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;平面2R速度关系的 Jacobian 解法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;直接得到正向运动学结果
$$
\begin{aligned}
^{0}_{3}T(\theta_1, \theta_2) &amp;amp;= \begin{bmatrix} c(\theta_1 + \theta_2) &amp;amp; -s(\theta_1 + \theta_2) &amp;amp; 0 &amp;amp; L_1 c\theta_1 + L_2 c(\theta_1 + \theta_2)\ s(\theta_1 + \theta_2) &amp;amp; c(\theta_1 + \theta_2) &amp;amp;0 &amp;amp;L_1 s\theta_1 + L_2 s(\theta_1 + \theta_2)\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
\end{aligned}
$$
提取末端位置坐标，有
$$
\mathcal{X} = \begin{bmatrix} L_1 c\theta_1 + L_2 c(\theta_1 + \theta_2)\ L_1 s\theta_1 + L_2 s(\theta_1 + \theta_2)\end{bmatrix}
$$
对时间 $t$ 求导，有
$$
\dot{\mathcal{X}} = \begin{bmatrix} -L_1s\theta_1 - L_2 s(\theta_1 + \theta_2) &amp;amp;-L_2 s(\theta_1 + \theta_2)\ L_1c\theta_1 + L_2 c(\theta_1 + \theta_2) &amp;amp;L_2 c(\theta_1 + \theta_2)\end{bmatrix}\begin{bmatrix} \dot\theta_1 \ \dot\theta_2\end{bmatrix}
$$
其中，$J(\theta_1, \theta_2) = \begin{bmatrix} -L_1s\theta_1 - L_2 s(\theta_1 + \theta_2) &amp;amp;-L_2 s(\theta_1 + \theta_2)\ L_1c\theta_1 + L_2 c(\theta_1 + \theta_2) &amp;amp;L_2 c(\theta_1 + \theta_2)\end{bmatrix}$&lt;/p&gt;
&lt;p&gt;这里的运算结果和前面的使用迭代方式的运算结果是一致的。&lt;/p&gt;
&lt;h2&gt;4.3.3 关节变量的 Jacobian 贡献&lt;/h2&gt;
&lt;p&gt;相较于框架 ${0}$（默认各个变量的左上标均为$\ ^0$），考察各个关节变量对末端速度的贡献。设
$$
\begin{aligned}
\dot{\mathcal{X}} &amp;amp;= J_P(q)\dot{q} = \sum_{i=1}^n J_{P_i}(q)\dot{q}&lt;em&gt;i \
\Omega &amp;amp;= J_O(q) \dot{q} = \sum&lt;/em&gt;{i=1}^n J_{O_i}(q)\dot q_i
\end{aligned}
$$
其中，$\mathcal{X}$ 表示末端线速度， $\Omega$ 表示末端旋转角速度，线速度 Jacobian 矩阵 $J_P(q) = \begin{bmatrix} J_{P_1}(q) &amp;amp; J_{P_2}(q) &amp;amp; \dots &amp;amp; J_{P_n}(q)\end{bmatrix}$，角速度 Jacobian 矩阵 $J_O(q) = \begin{bmatrix} J_{O_1}(q) &amp;amp; J_{O_2}(q) &amp;amp;\dots &amp;amp;J_{O_n}(q)\end{bmatrix}$。&lt;/p&gt;
&lt;h3&gt;旋转关节&lt;/h3&gt;
&lt;p&gt;有
$$
\begin{aligned}
J_{P_i}(q)\dot\theta_i &amp;amp;= \dot\theta_i\mathbf{\hat{z}}&lt;em&gt;i \times (\mathcal{X} - p_i) \
J&lt;/em&gt;{O_i}(q)\dot\theta_i &amp;amp;= \mathbf{\hat{z}}&lt;em&gt;i\dot\theta_i
\end{aligned}
$$
于是
$$
\begin{aligned}
J&lt;/em&gt;{P_i}(q) &amp;amp;= \mathbf{\hat{z}}&lt;em&gt;i \times (\mathcal{X} - p_i) \
J&lt;/em&gt;{O_i}(q) &amp;amp;= \mathbf{\hat{z}}&lt;em&gt;i
\end{aligned}
$$
其中, $p_i$ 是关节 $i$ 的位置向量，也就是框架 ${i}$ 的原点 $p&lt;/em&gt;{iorg}$&lt;/p&gt;
&lt;h3&gt;棱柱关节&lt;/h3&gt;
&lt;p&gt;有
$$
\begin{aligned}
J_{P_i}(q)\dot d_i &amp;amp;= \mathbf{\hat{z}}&lt;em&gt;i \dot d_i\
J&lt;/em&gt;{O_i}(q)\dot d_i &amp;amp;= 0
\end{aligned}
$$
于是
$$
\begin{aligned}
J_{P_i}(q) &amp;amp;= \mathbf{\hat{z}}&lt;em&gt;i \
J&lt;/em&gt;{O_i}(q) &amp;amp;= 0
\end{aligned}
$$&lt;/p&gt;
&lt;h2&gt;4.3.4 运动旋量的 Jacobian&lt;/h2&gt;
&lt;p&gt;前面所使用的 Jacobian 主要是关节变量和末端线速度之间或和末端角速度之间的 Jacobian 矩阵。我们可以将线速度和角速度的 Jacobian 矩阵沿行方向拼接起来，从而得到末端运动旋量 $\xi = \begin{bmatrix} \dot{\mathcal{X}} \ \Omega\end{bmatrix}$ 关于各个关节变量的速度关系。&lt;/p&gt;
&lt;p&gt;$$
\xi = \begin{bmatrix} \dot{\mathcal{X}} \ \Omega\end{bmatrix} = J(q) \dot q = \begin{bmatrix} J_P(q) \ J_O(q)\end{bmatrix} \dot q = \begin{bmatrix} J_{P_1}(q) &amp;amp; \dots &amp;amp;J_{P_n}(q) \ J_{O_1}(q) &amp;amp; \dots &amp;amp;J_{O_n}(q)\end{bmatrix} \begin{bmatrix} \dot q_1 \ \vdots \ \dot q_n\end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;对于旋转关节，有
$$
J(q) = \begin{bmatrix} \mathbf{\hat{z}}_i \times (\mathcal{X} - p_i) \ \mathbf{\hat{z}}_i\end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;对于棱柱关节，有
$$
J(q) = \begin{bmatrix} \mathbf{\hat{z}}_i \ 0\end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;平面2R速度关系的另一种 Jacobian 解法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;前向运动学结果为
$$
\begin{aligned}
^{0}&lt;em&gt;{3}T(\theta_1, \theta_2) &amp;amp;= \begin{bmatrix} c(\theta_1 + \theta_2) &amp;amp; -s(\theta_1 + \theta_2) &amp;amp; 0 &amp;amp; L_1 c\theta_1 + L_2 c(\theta_1 + \theta_2)\ s(\theta_1 + \theta_2) &amp;amp; c(\theta_1 + \theta_2) &amp;amp;0 &amp;amp;L_1 s\theta_1 + L_2 s(\theta_1 + \theta_2)\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
\end{aligned}
$$
于是，有
$$
\begin{aligned}
\begin{bmatrix} J&lt;/em&gt;{P_1} \ J_{O_1}\end{bmatrix} &amp;amp;= \begin{bmatrix} \ ^{0}\mathbf{\hat{z}}_i \times (\ ^{0}p_3 - \ ^{0}p_1) \ \ ^{0}\mathbf{\hat{z}}_1\end{bmatrix} = \begin{bmatrix} \begin{bmatrix} 0\0\1\end{bmatrix} \times \left(\begin{bmatrix} L_1 c\theta_1 + L_2c(\theta_1 + \theta_2) \ L_1 s\theta_1 + L_2s(\theta_1 + \theta_2) \0 \end{bmatrix} - \begin{bmatrix} 0\0\0\end{bmatrix}\right) \ \begin{bmatrix} 0\0\1\end{bmatrix}\end{bmatrix} = \begin{bmatrix} -L_1 s\theta_1 - L_2 s(\theta_1 + \theta_2)\ L_1 c\theta_1 + L_2 c(\theta_1 + \theta_2) \ 0 \ 0\ 0\ 1\end{bmatrix} \&lt;/p&gt;
&lt;p&gt;\begin{bmatrix} J_{P_2} \ J_{O_2}\end{bmatrix} &amp;amp;= \begin{bmatrix} \ ^{0}\mathbf{\hat{z}}&lt;em&gt;i \times (\ ^{0}p_3 - \ ^{0}p_2) \ \ ^{0}\mathbf{\hat{z}}&lt;em&gt;2\end{bmatrix} = \begin{bmatrix} \begin{bmatrix} 0\0\1\end{bmatrix} \times \left(\begin{bmatrix} L_1 c\theta_1 + L_2c(\theta_1 + \theta_2) \ L_1 s\theta_1 + L_2s(\theta_1 + \theta_2) \0 \end{bmatrix} - \begin{bmatrix} L_1c\theta_1\L_1s\theta_1\0\end{bmatrix}\right) \ \begin{bmatrix} 0\0\1\end{bmatrix}\end{bmatrix} = \begin{bmatrix} - L_2 s(\theta_1 + \theta_2)\ L_2 c(\theta_1 + \theta_2) \ 0 \ 0\ 0\ 1\end{bmatrix} \
\end{aligned}
$$
从而得到
$$
\xi = \begin{bmatrix} \dot{\mathcal{X}} \ \Omega\end{bmatrix} = \begin{bmatrix} J&lt;/em&gt;{P_1} &amp;amp; J&lt;/em&gt;{P_2} \ J_{O_1} &amp;amp; J_{O_2} \end{bmatrix} \begin{bmatrix} \dot\theta_1 \ \dot\theta_2\end{bmatrix}
$$
$$
J = \begin{bmatrix} J_{P_1} &amp;amp; J_{P_2} \ J_{O_1} &amp;amp; J_{O_2} \end{bmatrix} = \begin{bmatrix} -L_1s\theta_1 - L_2 s(\theta_1 + \theta_2) &amp;amp;-L_2 s(\theta_1 + \theta_2)\ L_1c\theta_1 + L_2 c(\theta_1 + \theta_2) &amp;amp;L_2 c(\theta_1 + \theta_2) \ 0 &amp;amp;0 \ 0 &amp;amp;0 \ 0 &amp;amp;0 \ 1 &amp;amp; 1\end{bmatrix}
$$
化简后可以得到和之前一样的结果。&lt;/p&gt;
&lt;h2&gt;4.3.5 Jacobian 在不同框架下的切换&lt;/h2&gt;
&lt;p&gt;前面所使用的 Jacobian 矩阵都是在框架 ${0}$ 中的向量表示。同样地，Jacobian 矩阵也可以在其他框架下表达。&lt;/p&gt;
&lt;p&gt;设在框架 ${A}$ 中表达的 Jacobian 矩阵为 $^{A}J(q)$，那么其在框架 ${B}$ 中的对应表达 $^{B}J(q)$ 满足&lt;/p&gt;
&lt;p&gt;$$
^{A}J(q) = \begin{bmatrix} \ ^{A}&lt;em&gt;{B}R &amp;amp; 0 \ 0 &amp;amp; \ ^{A}&lt;/em&gt;{B}R\end{bmatrix} \ ^{B}J(q)
$$&lt;/p&gt;
&lt;p&gt;这是因为
$$
\begin{aligned}
\begin{bmatrix} ^A v \ ^A\omega\end{bmatrix} &amp;amp;= \begin{bmatrix} \ ^{A}&lt;em&gt;{B}R &amp;amp; 0 \ 0 &amp;amp; \ ^{A}&lt;/em&gt;{B}R\end{bmatrix} \begin{bmatrix} ^B v \ ^B\omega\end{bmatrix} \
^{A}\dot{\mathcal{X}} = \begin{bmatrix} ^A v \ ^A\omega\end{bmatrix} &amp;amp;= \ ^{A}J(q)\dot q \
^{B}\dot{\mathcal{X}} = \begin{bmatrix} ^B v \ ^B\omega\end{bmatrix} &amp;amp;= \ ^{B}J(q)\dot q
\end{aligned}
$$
结合在一起即可推导出上述结论。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特别需要注意的一点是 $\begin{bmatrix} ^A v \ ^A\omega\end{bmatrix}$ 与 $\begin{bmatrix} ^B v \ ^B\omega\end{bmatrix}$ 不是同一个运动旋量在不同框架下的向量表达。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;4.3.6 运动学冗余&lt;/h2&gt;
&lt;h3&gt;Jacobian 子矩阵&lt;/h3&gt;
&lt;p&gt;Jacobian 矩阵的形状不一定是方阵，在前面的例子中，Jacobian 的形状有 $2\times 2$ 的和 $6 \times 2$ 的。事实上这些矩阵都是空间 Jacobian 矩阵 $J(q) \in \mathbb{R}^{6\times n}$ 的子矩阵（其中 $n$ 为关节个数）。例如，在平面的机械臂问题中，实际上只需要 $\begin{bmatrix} \dot x \\dot y \ \omega_z\end{bmatrix}$ 三个速度变量即可完全描述机械臂的速度，而不需要剩下的三个速度变量 $\dot z, \omega_x, \omega_y$。因此，可以通过选取适当的 Jacobian 子矩阵以方便问题的求解。&lt;/p&gt;
&lt;p&gt;（这一段的讲述目的是阐明后续运动学冗余的定义中为什么 Jacobian 矩阵的形状是 $J(q) \in \mathbb{R}^{m\times n}$，其中 $m$ 未必是6）&lt;/p&gt;
&lt;h3&gt;运动学冗余的定义&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;定义:&lt;/strong&gt; 关节变量的数目 $n$ 大于描述给定任务所必须的变量数目 $m$。&lt;/li&gt;
&lt;li&gt;例如末端执行器的自由度为 $6$ 或 $3$，大于 $6$ 轴的空间机械臂和大于 $3$ 轴的平面臂都是运动学冗余的。&lt;/li&gt;
&lt;li&gt;又如若目标任务仅是实现平面定位任务（只关注末端 $x, y$ 坐标, $m = 2$），那么三轴平面臂 ($n = 3$) 也是运动学冗余的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;运动学冗余并不是不好的，冗余能够为机械臂提供灵活、多样的运动。在实际应用中，关节活动范围往往有限制，此时加入运动学冗余可以弥补活动范围限制对末端运动的影响。&lt;/p&gt;
&lt;h3&gt;存在运动学冗余时的速度逆解&lt;/h3&gt;
&lt;p&gt;求速度逆解的目标是给定末端速度 $\dot{\mathcal{X}}$ 和 Jacobian 矩阵 $J(q)$，目标是求取相应的关节速率 $\dot q$。
$$
\dot{\mathcal{X}} = J(q)\dot q, \quad \dot{\mathcal{X}} \in \mathbb{R}^m, \quad \dot q \in \mathbb{R}^n
$$
当 $m \lt n$ 时，运动学冗余会导致无穷多解的存在，此时求解方程可以用优化方法求解。例如采用正则化的最小二乘法
$$
\min_{\dot q} \mathcal{L}(\dot q, \lambda) = \frac{1}{2}\dot q^\top W \dot q + \lambda^\top (\dot{\mathcal{X}} - J(q) \dot q)
$$
有最优解
$$
\dot q = W^{-1} J^\top(JW^{-1}J^\top)^{-1}\dot{\mathcal{X}}
$$&lt;/p&gt;
&lt;h2&gt;4.3.7 奇异性&lt;/h2&gt;
&lt;p&gt;如果 Jacobian 矩阵 &lt;strong&gt;满秩&lt;/strong&gt; 且为方阵，则可以通过求逆的方法直接得到速度逆解。
$$
\dot q = J^{-1}(q)\dot{\mathcal{X}}
$$
使 Jacobian 矩阵&lt;strong&gt;不满秩&lt;/strong&gt;（注意是不满秩而不是非方阵）的构形 $q$ 也是有可能存在的，我们称这些构形为 &lt;strong&gt;奇异构形&lt;/strong&gt; 或 &lt;strong&gt;奇异点&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;边界奇异点&lt;/strong&gt;：末端执行器处于或接近工作空间边界时出现的奇异点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内部奇异点&lt;/strong&gt;：出现在远离工作空间边界的位置，通常是由&lt;strong&gt;两个或多个关节轴共线&lt;/strong&gt;或某个特定构形所致的奇异点。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果机械臂的构形位于一个奇异点，则其运动自由度减少，无论如何选择关节速率，都无法实现笛卡尔空间内某个方向或子空间的运动。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;求解方阵 Jacobian 矩阵奇异点的方法通常是采用 Jacobian 矩阵的行列式，当行列式值为0时，即对应构形为奇异点。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;如果 Jacobian 矩阵非方阵，可以用求非满秩的情况的方法求解奇异点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;虽然在奇异点附近也可以用优化方法求解速度逆解，但是 Jacobian 矩阵是否满秩对实际中末端速度实现是有影响的。在临近奇异点的区域，实现给定末端速度所需要的关节速率会激增。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;平面2R的线速度奇异点&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;根据前面的结果
$$
J(\theta_1, \theta_2) = \begin{bmatrix} -L_1s\theta_1 - L_2 s(\theta_1 + \theta_2) &amp;amp;-L_2 s(\theta_1 + \theta_2)\ L_1c\theta_1 + L_2 c(\theta_1 + \theta_2) &amp;amp;L_2 c(\theta_1 + \theta_2)\end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;令 $\det J(\theta_1, \theta_2) = 0$，可以得到
$$
\begin{aligned}
\det J &amp;amp;= -L_1L_2s\theta_1c(\theta_1 + \theta_2) - L_2^2s(\theta_1 + \theta_2)c(\theta_1 + \theta_2) + L_1L_2c\theta_1s(\theta_1 + \theta_2) + L_2^2s(\theta_1 + \theta_2)c(\theta_1 + \theta_2) \
&amp;amp;= L_1 L_2 s\theta_2
\end{aligned}
$$
当 $\theta_2 = 0 \text{ or } \pi$ 时是机械臂位于奇异点，此时，末端只能沿臂的垂直方向运动，在二维平面上损失了一个自由度。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/sigularity.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- 利用连杆自身框架描述下的 Jacobian 矩阵同样可以求解奇异点，
$$
\begin{aligned}
^{3}J(\theta_1, \theta_2) &amp;amp;= \begin{bmatrix} \ ^{3}&lt;em&gt;{0}R &amp;amp; [^{3}p&lt;/em&gt;{0org}]\ ^{3}&lt;em&gt;{0}R \ 0 &amp;amp;\ ^{3}&lt;/em&gt;{0}R\end{bmatrix} J(\theta_1, \theta_2) \
&amp;amp;= \begin{bmatrix} c(\theta_1 + \theta_2) &amp;amp;s(\theta_1 + \theta_2) &amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; L_1s\theta_2 \
-s(\theta_1 + \theta_2) &amp;amp; c(\theta_1 + \theta_2) &amp;amp; 0 &amp;amp; 0 &amp;amp;0 &amp;amp;L_1c\theta_2 + L_2 \
0 &amp;amp; 0 &amp;amp; 1 &amp;amp; L_1s\theta_1 + L_2 s(\theta_1 + \theta_2) &amp;amp; -L_1c\theta_1 - L_2 c(\theta_1 + \theta_2)  &amp;amp; 0 \
0 &amp;amp; 0&amp;amp; 0&amp;amp; c(\theta_1 + \theta_2) &amp;amp;s(\theta_1 + \theta_2) &amp;amp; 0 \
0 &amp;amp; 0 &amp;amp; 0&amp;amp; -s(\theta_1 + \theta_2) &amp;amp;c(\theta_1 + \theta_2) &amp;amp; 0 \
0 &amp;amp; 0&amp;amp; 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 1
\end{bmatrix}
\begin{bmatrix} -L_1s\theta_1 - L_2 s(\theta_1 + \theta_2) &amp;amp;-L_2 s(\theta_1 + \theta_2)\ L_1c\theta_1 + L_2 c(\theta_1 + \theta_2) &amp;amp;L_2 c(\theta_1 + \theta_2) \ 0 &amp;amp;0 \ 0 &amp;amp;0 \ 0 &amp;amp;0 \ 1 &amp;amp; 1\end{bmatrix} \&lt;/p&gt;
&lt;p&gt;&amp;amp;= \begin{bmatrix}
2L_1s_2      &amp;amp; L_1s_2 \
2(L_1c_2 + L_2)&amp;amp; L_1c_2 + 2L_2\
0&amp;amp;0\
0&amp;amp;0\
0&amp;amp;0\
1&amp;amp;1
\end{bmatrix}.
\end{aligned}
$$ --&amp;gt;&lt;/p&gt;
&lt;p&gt;从此例中也可以看出为什么在接近奇异点的区域，需要提供的关节速率会无穷大
$$
\dot \theta = J^{-1}(\theta) \dot{\mathcal{X}}
$$
$$
J^{-1}(\theta) = \frac{1}{L_1L_2s\theta_2}\begin{bmatrix} L_2c(\theta_1 + \theta_2) &amp;amp; L_2s(\theta_1 + \theta_2) \ -L_1c\theta_1 - L_2 c(\theta_1 + \theta_2) &amp;amp; -L_1s\theta_1 - L_2 s(\theta_1 + \theta_2)\end{bmatrix}
$$
这里分母上的 $s\theta_2$ 就导致了为什么关节速率会趋于无穷大。&lt;/p&gt;
&lt;h1&gt;4.4 静力学分析目标及力和力矩的传播公式&lt;/h1&gt;
&lt;p&gt;静力学分析是在机械臂相对静止或匀速状态下的受力分析。&lt;strong&gt;目标是求解，为了保证平衡条件，各个关节应该提供的力或力矩是什么。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;约定&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$f_i$ 表示连杆 $i-1$ 施加在连杆 $i$ 上的力&lt;/li&gt;
&lt;li&gt;$n_i$ 表示连杆 $i-1$ 施加在连杆 $i$ 上的力矩&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是根据连杆 $i$ 的平衡方程（忽略自重），可以得到
$$
\begin{cases}
&amp;amp;^{i}f_i - ^{i}f_{i+1} = 0\
&amp;amp;^{i}n_i - ^{i}n_{i+1} -\ ^{i}p_{i+1} \times\ ^{i}f_{i+1} = 0
\end{cases}
$$
其中 $^{i}p_{i+1} = ^{i}p_{(i+1)org}$。&lt;/p&gt;
&lt;p&gt;这里需要注意的一个点是如何得到这两个方程。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在这个系统中，连杆 $i$ 只受到 $f_i$ 和 $f_{i+1}$ 两个力的作用，因此，根据刚体的静力学分析，直接把刚体视作一个质点作力部分的分析处理，得到第一个方程即力的平衡。&lt;/li&gt;
&lt;li&gt;然而，力矩的加减法是在必须在同一参考点下才可以进行。而根据我们所设定的结果，$n_i$ 与 $n_{i+1}$ 分别是相对于关节 $i$ 和关节 $i+1$ 处的力矩（因为力 $f_i$ 和 $f_{i+1}$ 分别作用于关节 $i$ 和 关节 $i+1$ 上），是不可以直接作力矩平衡的，需要把 $n_{i+1}$ 平移到关节 $i$ 处才可以。这种力矩的平移会带来一个额外的力矩项，即 $p_{i+1} \times f_{i+1}$，此时可以得到第二个方程。(可以参考&amp;lt;a href=&quot;/blog/resources/Forces.pdf&quot; download=&quot;朗道物理学.pdf&quot;&amp;gt;朗道物理学 P112(实际是pdf文件的P125)&amp;lt;/a&amp;gt; )&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;assets/forces1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;于是传播公式为
$$
\begin{cases}
&amp;amp;^{i}f_{i+1} = \ ^{i}f_i \
&amp;amp;^{i}n_{i+1} = \ ^{i}n_i  + \ ^{i}p_{i+1} \times\ ^{i}f_{i+1}
\end{cases}
$$
注意 $f_i, n_i$ 是连杆 $i$ 提供的力和力矩，&lt;strong&gt;关节所能够提供的只能是 Z 轴方向的分量&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;旋转关节（力矩）: $\tau_i = \ ^{i} n_i^\top \ ^{i}\mathbf{\hat{z}}_i$&lt;/li&gt;
&lt;li&gt;棱柱关节（力）: $\tau_i = \ ^{i}f_i^\top \ ^{i}\mathbf{\hat{z}}_i$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面的一道例题，给出了求解静力学平衡的方法（力学的结果，通常是反向传播求解）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;平面2R，求为了保持平衡状态需要在末端施加的作用力 $\mathcal{F}$&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/planar2R2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;前向运动学结果
$$
\begin{aligned}
^{0}&lt;em&gt;{1}T &amp;amp;= \begin{bmatrix} c\theta_1 &amp;amp; -s\theta_1 &amp;amp; 0 &amp;amp; 0\ s\theta_1 &amp;amp; c\theta_1 &amp;amp;0 &amp;amp;0\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
^{1}&lt;/em&gt;{2}T &amp;amp;= \begin{bmatrix} c\theta_2 &amp;amp; -s\theta_2 &amp;amp; 0 &amp;amp; L_1\ s\theta_2 &amp;amp; c\theta_2 &amp;amp;0 &amp;amp;0\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
^{2}&lt;em&gt;{3}T &amp;amp;= \begin{bmatrix} 1 &amp;amp; 0 &amp;amp; 0 &amp;amp; L_2\ 0 &amp;amp; 1 &amp;amp;0 &amp;amp;0\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
^{0}&lt;/em&gt;{3}T(\theta_1, \theta_2) &amp;amp;= \begin{bmatrix} c(\theta_1 + \theta_2) &amp;amp; -s(\theta_1 + \theta_2) &amp;amp; 0 &amp;amp; L_1 c\theta_1 + L_2 c(\theta_1 + \theta_2)\ s(\theta_1 + \theta_2) &amp;amp; c(\theta_1 + \theta_2) &amp;amp;0 &amp;amp;L_1 s\theta_1 + L_2 s(\theta_1 + \theta_2)\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
\end{aligned}
$$
根据末端无施加的外力矩，但是有施加的外力 $\mathcal{F}$，有
$$
\begin{aligned}
^{3}\mathcal{F} = \ ^{3} f_3 = \begin{bmatrix}^{3}f_x\ ^{3}f_y \ 0\end{bmatrix}, \quad \ ^{3}n_3 = 0
\end{aligned}
$$
带入迭代公式
$$
\begin{aligned}
\ ^{2}f_2 &amp;amp;= \ ^{2}&lt;em&gt;{3}R \ ^{3}f_3 = \begin{bmatrix}^{3}f_x\ ^{3}f_y \ 0 \end{bmatrix}, \quad \ ^{2}n_2 = \ ^{2}&lt;/em&gt;{3}R \ ^{3}n_3 + \ ^{2}p_{3} \times \ ^{2}f_2 = \begin{bmatrix} 0 \ 0\ L_2 \ ^{3}f_y\end{bmatrix}\&lt;/p&gt;
&lt;p&gt;\ ^{1}f_1 &amp;amp;= \ ^{1}&lt;em&gt;{2}R \ ^{2}f_2 = \begin{bmatrix} ^{3}f_xc\theta_2 - \ ^{3}f_ys\theta_2 \ ^{3}f_x s\theta_2 + \ ^{3}f_yc\theta_2 \ 0\end{bmatrix}, \quad\ ^{1}n_1 = \ ^{1}&lt;/em&gt;{2}R \ ^{2}n_2  + \ ^{1}p_2 \times \ ^{1}f_1 = \begin{bmatrix} 0 \ 0 \ L_1^{3}f_xs\theta_2 + L_1 \ ^{3}f_yc\theta_2 + L_2 \ ^{3}f_y \end{bmatrix}
\end{aligned}
$$
注意这里的逆向传播不需要计算到连杆0。&lt;/p&gt;
&lt;p&gt;于是取沿 Z 轴方向的分量，得到关节需要提供的力矩为
$$
\begin{bmatrix} \tau_1 \ \tau_2\end{bmatrix} = \begin{bmatrix}^{1}n_1^\top \ ^{1}\mathbf{\hat{z}}_1 \ ^{2}n_2^\top \ ^{2}\mathbf{\hat{z}}_2\end{bmatrix} = \begin{bmatrix} L_1^{3}f_xs\theta_2 + L_1 \ ^{3}f_yc\theta_2 + L_2 \ ^{3}f_y \ L_2 \ ^{3}f_y\end{bmatrix} = \begin{bmatrix} L_1 s\theta_2 &amp;amp;L_1c\theta_2 + L_2 \ 0 &amp;amp; L_2\end{bmatrix}\begin{bmatrix} ^{3}f_x \ ^{3}f_y\end{bmatrix}
$$&lt;/p&gt;
&lt;h1&gt;4.4.1 力的 Jacobian 矩阵&lt;/h1&gt;
&lt;p&gt;对比上面平面 2R 力学的结果
$$
\tau = \begin{bmatrix} L_1 s\theta_2 &amp;amp;L_1c\theta_2 + L_2 \ 0 &amp;amp; L_2\end{bmatrix}\begin{bmatrix} ^{3}f_x \ ^{3}f_y\end{bmatrix}
$$
和速度的关系
$$
^{3}v_3 = \begin{bmatrix} L_1 s\theta_2 &amp;amp; 0\ L_1c\theta_2 + L_2 &amp;amp; L_2\end{bmatrix}\begin{bmatrix} \dot \theta_1 \ \dot \theta_2\end{bmatrix}
$$
发现一条规律，若 $\dot{\mathcal{X}} = J(q)\dot q$，则应该有 $\tau = J^\top(q)\mathcal{F}$。事实上，这个结论是成立的，使用虚功原理可以证明这个结论。&lt;/p&gt;
&lt;p&gt;根据虚功原理 (这个很出名，详细讲解很多，例如&lt;a href=&quot;https://zhuanlan.zhihu.com/p/417114829&quot;&gt;知乎某个讲解&lt;/a&gt;)，主动力做的虚功之和为0，因此
$$
\tau^\top\delta q = \mathcal{F}^\top \delta \mathcal{X}, \quad \forall \delta q
$$
根据 $\delta \mathcal{X} = J(q)\delta q$，带入有
$$
\tau = J^\top(q) \mathcal{F}
$$
这实现了从末端笛卡尔空间中的广义力到关节空间中的广义力的映射。&lt;/p&gt;
</content:encoded></item><item><title>随机二进制防冲突算法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/misc/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/misc/</guid><pubDate>Wed, 14 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;随机二进制防冲突算法是 RFID 系统中解决标签冲突的一种树形算法，通过随即分割冲突标签集逐步识别所有标签。算法核心是通过&lt;strong&gt;随机二进制分割冲突标签集&lt;/strong&gt;（每次冲突时将标签随机分为两个子集，形成二叉树结构），递归遍历子树直至所有叶子节点仅含单个标签，从而消除冲突并完成识别。&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;算法流程&lt;/strong&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;未识别的标签集合  $T$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;已识别的标签列表  $L$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;初始化&lt;/strong&gt;：标签生成随机二进制序列前缀（初始为空），读写器维护待处理路径栈  $S$ ，压入根节点（空路径 &lt;code&gt;&quot;&quot;&lt;/code&gt;）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;While&lt;/strong&gt; 栈  $S$  非空 &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;   弹出栈顶路径  $s$ ，读写器广播查询前缀  $s$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;If&lt;/strong&gt; 无标签响应 &lt;strong&gt;then&lt;/strong&gt;&amp;lt;br&amp;gt;    结束当前分支&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;Else if&lt;/strong&gt; 单标签响应 &lt;strong&gt;then&lt;/strong&gt;&amp;lt;br&amp;gt;    读取标签数据并加入  $L$ ，标签休眠&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;Else&lt;/strong&gt;（冲突发生）&amp;lt;br&amp;gt;    所有响应标签随机生成新比特  $b \in {0,1}$ ，更新前缀为  $s | b$ （&lt;code&gt;s0&lt;/code&gt;或&lt;code&gt;s1&lt;/code&gt;）&amp;lt;br&amp;gt;    将新路径 &lt;code&gt;s0&lt;/code&gt; 和 &lt;code&gt;s1&lt;/code&gt; 压入栈  $S$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;End While&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h3&gt;&lt;strong&gt;关键步骤解析&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;步骤1&lt;/strong&gt;：标签初始化时生成随机二进制位，读写器使用栈（深度优先）或队列（广度优先）管理路径。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;步骤3-6&lt;/strong&gt;：冲突时标签随机选择子路径（0或1），读写器递归查询子路径，直到所有路径仅含一个标签。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;路径更新&lt;/strong&gt;：标签每次冲突后动态扩展前缀（如原前缀为 &lt;code&gt;0&lt;/code&gt;，新前缀可能为 &lt;code&gt;00&lt;/code&gt; 或 &lt;code&gt;01&lt;/code&gt;）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;&lt;strong&gt;示例流程&lt;/strong&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;当前栈状态&lt;/th&gt;
&lt;th&gt;查询路径&lt;/th&gt;
&lt;th&gt;响应结果&lt;/th&gt;
&lt;th&gt;动作&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[&quot;&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;冲突&lt;/td&gt;
&lt;td&gt;压入 &lt;code&gt;&quot;0&quot;&lt;/code&gt;, &lt;code&gt;&quot;1&quot;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[&quot;1&quot;, &quot;0&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;0&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;冲突&lt;/td&gt;
&lt;td&gt;压入 &lt;code&gt;&quot;00&quot;&lt;/code&gt;, &lt;code&gt;&quot;01&quot;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[&quot;1&quot;, &quot;01&quot;, &quot;00&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;00&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;成功&lt;/td&gt;
&lt;td&gt;识别标签A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[&quot;1&quot;, &quot;01&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;01&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;成功&lt;/td&gt;
&lt;td&gt;识别标签B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[&quot;1&quot;]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;1&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;成功&lt;/td&gt;
&lt;td&gt;识别标签C&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;&lt;strong&gt;核心优势与限制&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优势&lt;/strong&gt;：随机分割避免标签ID分布不均问题，保证最坏时间复杂度为  $O(n \log n)$ 。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;限制&lt;/strong&gt;：依赖标签生成随机数能力，硬件成本略高。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>网络基础知识</title><link>https://adalovelemon.github.io/posts/content/technotes/computernetwork/net/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/technotes/computernetwork/net/</guid><pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;网络分类&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;网络的A类地址: $\underbrace{0\text{xxxxxxx}}&lt;em&gt;{\text{8bit net-id}}|\underbrace{\text{xxxxxxxxxxxxxxxxxxxxxxxx}}&lt;/em&gt;{\text{24bit host-id}}$&lt;/li&gt;
&lt;li&gt;网络的B类地址: $\underbrace{10\text{xxxxxxxxxxxxxx}}&lt;em&gt;{\text{16bit net-id}}|\underbrace{\text{xxxxxxxxxxxxxxxx}}&lt;/em&gt;{\text{16bit hostid}}$&lt;/li&gt;
&lt;li&gt;网络的C类地址: $\underbrace{110\text{xxxxxxxxxxxxxxxxxxxxx}}&lt;em&gt;{\text{24bit host-id}}|\underbrace{\text{xxxxxxxx}}&lt;/em&gt;{\text{8bit host-id}}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;IP地址使用范围&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;网络类别&lt;/th&gt;
&lt;th&gt;最大网络数&lt;/th&gt;
&lt;th&gt;第一个可用网络号&lt;/th&gt;
&lt;th&gt;最后一个可用网络号&lt;/th&gt;
&lt;th&gt;每个网络中最大的主机数&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;$126 = 2^7 - 2$&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.0.0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;126.0.0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$16,777,214 = 2^{24} - 2$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;$16,383 = 2^{14} - 1$&lt;/td&gt;
&lt;td&gt;&lt;code&gt;128.1.0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;191.255.0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$65,534 = 2^{16} - 2$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;$2,097,151 = 2^{21} - 1$&lt;/td&gt;
&lt;td&gt;&lt;code&gt;192.0.1.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;223.225.225.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$254 = 2^{8} - 2$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;为什么最大主机数减2?&lt;/strong&gt;  每个网络中主机号存在两个保留地址，分别是全0的主机号（表示网络本身不可分配给主机）和全1的主机号（表示广播地址，不可分配给主机）
&lt;ul&gt;
&lt;li&gt;例如，对于&lt;code&gt;192.168.1.0&lt;/code&gt;对应的网络号，&lt;code&gt;192.168.1.0&lt;/code&gt;是网络本身，&lt;code&gt;192.168.1.255&lt;/code&gt;是广播地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;为什么最大网络数也减2或减1?&lt;/strong&gt;  全0网络号保留为默认路由或无效地址，&lt;code&gt;127.0.0.0&lt;/code&gt;保留为环回地址（即&lt;code&gt;localhost&lt;/code&gt;），因此A类最大网络数要减2；同理，B，C类网络的全0网络号（&lt;code&gt;128.0.0.0&lt;/code&gt;, &lt;code&gt;192.0.0.0&lt;/code&gt;）也被保留，故减1&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认网关&lt;/strong&gt; 通常设置为网络的第一个主机号，若网络号为 &lt;code&gt;192.168.255.0/24&lt;/code&gt;，则默认网关一般设置为 &lt;code&gt;192.168.255.1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Autoregressive Models</title><link>https://adalovelemon.github.io/posts/content/coursenotes/generativeai/autoregressivemodels/autoregressivemodel/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/generativeai/autoregressivemodels/autoregressivemodel/</guid><pubDate>Mon, 05 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Conditional Distribution Modeling&lt;/h1&gt;
&lt;h2&gt;Mathematical Foundation&lt;/h2&gt;
&lt;h3&gt;Chain Rule Decomposition&lt;/h3&gt;
&lt;p&gt;Any joint distribution can be decomposed into a product of conditional distributions using the chain rule:
$$
p(x_1, x_2, \dots, x_n) = p(x_1)p(x_2 | x_1) p(x_3 | x_1, x_2) \dots p(x_n | x_1, \dots, x_{n-1})
$$&lt;/p&gt;
&lt;p&gt;This decomposition is particularly well-suited for &lt;strong&gt;modeling sequences of tokens&lt;/strong&gt;, where each token $x_i$ represents a &lt;strong&gt;discrete element&lt;/strong&gt; such as words, image patches, or amino acids.&lt;/p&gt;
&lt;h3&gt;Sequential Properties&lt;/h3&gt;
&lt;p&gt;While the chain rule decomposition applies universally to multivariate distributions, sequential data introduces important constraints. For sequences with &lt;strong&gt;temporal dependencies&lt;/strong&gt;, the variables are &lt;strong&gt;not interchangeable&lt;/strong&gt; due to their inherent ordering.&lt;/p&gt;
&lt;h3&gt;Extension to Grid-Structured Data&lt;/h3&gt;
&lt;p&gt;Unlike sequential data with natural temporal ordering, &lt;strong&gt;grid-structured data&lt;/strong&gt; (images, videos, spatial data) lacks inherent sequential structure. However, autoregressive modeling can still be applied by &lt;strong&gt;imposing an artificial ordering&lt;/strong&gt; on spatial elements.&lt;/p&gt;
&lt;p&gt;For images, this is achieved by &lt;strong&gt;linearizing the 2D pixel grid&lt;/strong&gt; into a 1D sequence using various strategies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Raster scan ordering&lt;/strong&gt;: left-to-right, top-to-bottom traversal&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spiral ordering&lt;/strong&gt;: starting from center or corner positions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Random permutation&lt;/strong&gt;: shuffling pixel positions randomly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After linearization, we apply the same conditional chain:
$$
p(I) = p(x_1)p(x_2|x_1)p(x_3|x_1,x_2)\cdots p(x_n|x_1,\ldots,x_{n-1})
$$&lt;/p&gt;
&lt;p&gt;where $I$ represents the image and $x_i$ denotes the $i$-th pixel in the chosen ordering.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key considerations&lt;/strong&gt; for grid-structured data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Spatial correlation&lt;/strong&gt;: neighboring pixels exhibit stronger correlations than distant ones&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dimensional mismatch&lt;/strong&gt;: natural 2D structure versus imposed 1D ordering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Order sensitivity&lt;/strong&gt;: different linearization strategies can significantly impact modeling behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Despite these challenges, autoregressive image models such as &lt;strong&gt;PixelRNN&lt;/strong&gt; and &lt;strong&gt;PixelCNN&lt;/strong&gt; have successfully demonstrated the ability to capture complex image distributions and generate high-quality samples.&lt;/p&gt;
&lt;h2&gt;Conditional Distribution Parameterization&lt;/h2&gt;
&lt;h3&gt;Naive Parameterization Approach&lt;/h3&gt;
&lt;p&gt;Each conditional distribution in the chain rule decomposition could be parameterized independently with its own parameter set $\theta_i$:&lt;/p&gt;
&lt;p&gt;$$
p_{\theta_1, \theta_2, \dots, \theta_n}(x_1, \dots, x_n) = p_{\theta_1}(x_1) p_{\theta_2}(x_2 | x_1)\dots p_{\theta_n}(x_n| x_1, \dots, x_{n-1})
$$&lt;/p&gt;
&lt;p&gt;However, this approach introduces significant computational challenges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Parameter explosion&lt;/strong&gt;: Each position requires its own parameter set, leading to $O(n)$ parameter scaling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory overhead&lt;/strong&gt;: Storing and managing separate networks for each conditional distribution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Training complexity&lt;/strong&gt;: Optimizing multiple independent parameter sets simultaneously&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generalization issues&lt;/strong&gt;: Limited parameter sharing reduces the model&apos;s ability to learn common patterns&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Weight Sharing Solution&lt;/h3&gt;
&lt;p&gt;To address these limitations, modern autoregressive models employ &lt;strong&gt;weight sharing&lt;/strong&gt;, where all conditional distributions share the same parameter set $\theta$:&lt;/p&gt;
&lt;p&gt;$$
p_\theta(x_1, \dots, x_n) = p_\theta(x_1) p_\theta(x_2 | x_1)\dots p_\theta(x_n| x_1, \dots, x_{n-1})
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benefits of weight sharing&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Parameter efficiency&lt;/strong&gt;: Fixed parameter count regardless of sequence length&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Translation invariance&lt;/strong&gt;: Model learns position-agnostic conditional patterns&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better generalization&lt;/strong&gt;: Shared parameters enable learning from all positions simultaneously&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Enables processing of variable-length sequences without architectural changes&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Autoregressive Models&lt;/h1&gt;
&lt;h2&gt;Definition and Method&lt;/h2&gt;
&lt;p&gt;The term &quot;autoregressive&quot; combines &quot;auto&quot; (self) and &quot;regression&quot; (prediction). The model uses &lt;strong&gt;its own previous outputs&lt;/strong&gt; as inputs for generating the next prediction, creating a &lt;strong&gt;self-referential chain&lt;/strong&gt; where outputs become inputs.&lt;/p&gt;
&lt;p&gt;Autoregressive models predict sequences by using &lt;strong&gt;previously generated elements&lt;/strong&gt; to predict the next one, enabling generation of sequences of arbitrary length through iterative application.&lt;/p&gt;
&lt;h2&gt;Inductive Bias&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;inductive bias&lt;/strong&gt; in autoregressive models stems from the assumption that the conditional distribution function remains &lt;strong&gt;stationary across all sequence positions&lt;/strong&gt;. The same neural network architecture and parameters $\theta$ model every conditional probability $p_\theta(x_i | x_1, \ldots, x_{i-1})$ regardless of position $i$.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key aspects of this inductive bias&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Position-invariant conditional patterns&lt;/strong&gt;: The model assumes that the mechanism for predicting the next token given previous context follows the same pattern throughout the sequence&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shared representation learning&lt;/strong&gt;: Using identical parameters across positions enables the model to learn generalizable features&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Implicit stationarity assumption&lt;/strong&gt;: The conditional relationship $p(x_t | x_{&amp;lt;t})$ is assumed consistent across time steps&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Benefits&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Efficient parameter utilization&lt;/strong&gt;: Enables learning from all sequence positions simultaneously&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generalization to variable-length sequences&lt;/strong&gt;: The same model handles sequences of any length&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transfer of learned patterns&lt;/strong&gt;: Knowledge from one position informs predictions at other positions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Limitations&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Position-specific patterns&lt;/strong&gt;: Some sequences may have position-dependent conditional structures this bias cannot capture&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Oversimplification&lt;/strong&gt;: Real-world sequences may exhibit non-stationary conditional dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Dynamic Generation Process&lt;/h2&gt;
&lt;p&gt;The autoregressive generation process creates a &lt;strong&gt;dynamic dependency chain&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: $x_1 \sim p_\theta(x_1)$
&lt;strong&gt;Step 2&lt;/strong&gt;: $x_2 \sim p_\theta(x_2 | x_1)$
&lt;strong&gt;Step 3&lt;/strong&gt;: $x_3 \sim p_\theta(x_3 | x_1, x_2)$
&lt;strong&gt;Step t&lt;/strong&gt;: $x_t \sim p_\theta(x_t | x_1, x_2, \ldots, x_{t-1})$&lt;/p&gt;
&lt;p&gt;This creates the dependency chain:
$$
\begin{align}
\text{Time step 1:} \quad &amp;amp;x_1 \
\text{Time step 2:} \quad &amp;amp;x_1 \rightarrow x_2 \
\text{Time step 3:} \quad &amp;amp;x_1, x_2 \rightarrow x_3 \
&amp;amp;\vdots \
\text{Time step t:} \quad &amp;amp;x_1, x_2, \ldots, x_{t-1} \rightarrow x_t
\end{align}
$$&lt;/p&gt;
&lt;h2&gt;Training vs. Inference&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;During Training (Teacher Forcing)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The model receives the &lt;strong&gt;entire ground truth sequence&lt;/strong&gt; as input&lt;/li&gt;
&lt;li&gt;All conditional probabilities are computed &lt;strong&gt;in parallel&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The model learns conditional distribution patterns without generating sequences step-by-step&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;During Inference (True Autoregression)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The model generates tokens &lt;strong&gt;sequentially&lt;/strong&gt;, one at a time&lt;/li&gt;
&lt;li&gt;Each new token is &lt;strong&gt;sampled from the learned distribution&lt;/strong&gt; and fed back as input&lt;/li&gt;
&lt;li&gt;Prediction errors can &lt;strong&gt;compound&lt;/strong&gt; through the generation process, leading to exposure bias&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The training phase resembles &lt;strong&gt;intensive practice on decomposition problems&lt;/strong&gt;, while the real autoregressive challenge emerges during inference when the model must generate coherent sequences using only its own predictions as context.&lt;/p&gt;
&lt;h2&gt;Limitations&lt;/h2&gt;
&lt;p&gt;Autoregressive modeling has inherent limitations for sequences with complex contextual dependencies. In natural language, a word&apos;s meaning often depends on both &lt;strong&gt;preceding and subsequent context&lt;/strong&gt;. This &lt;strong&gt;bidirectional dependency&lt;/strong&gt; means that autoregressive models, conditioning only on previous tokens, may miss crucial contextual information from future tokens.&lt;/p&gt;
&lt;p&gt;This limitation has motivated the development of bidirectional models that capture dependencies in both directions.&lt;/p&gt;
</content:encoded></item><item><title>Variational Autoencoder</title><link>https://adalovelemon.github.io/posts/content/coursenotes/generativeai/vae/vae/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/generativeai/vae/vae/</guid><pubDate>Sat, 03 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Variational Autoencoder (VAE)&lt;/h1&gt;
&lt;h2&gt;Mathematical Establishment&lt;/h2&gt;
&lt;p&gt;Assume a data generation process, where $z \sim p(z)$ represent &lt;strong&gt;latent variables&lt;/strong&gt; (certain features like size, color, position) and $x \sim p_{real}(x)$ represent &lt;strong&gt;real observed variables&lt;/strong&gt; (e.g. images, videos, texts). Our aim is to generate an observed variable &lt;strong&gt;via latent variables and a generator&lt;/strong&gt;. Here, we bridge the &lt;strong&gt;latent distribution&lt;/strong&gt; and the &lt;strong&gt;observed distribution&lt;/strong&gt; with a conditional probability function $p_\theta(x|z)$, which is a mathematical description of the generator.&lt;/p&gt;
&lt;p&gt;Thus, we establish &lt;strong&gt;an man-made observed distribution&lt;/strong&gt;
$$
p_\theta(x) = \int_{\mathcal{Z}}p(z)p_\theta(x | z)dz
$$
and we wish this distribution to match the &lt;strong&gt;real observed distribution&lt;/strong&gt; $p_{real}(x)$&lt;/p&gt;
&lt;h2&gt;Optimization Metrics&lt;/h2&gt;
&lt;p&gt;To measure the similarities between the distributions, we typically use the &lt;strong&gt;Kullback-Leibler (KL) divergence&lt;/strong&gt;, and we try to minimize the divergence&lt;/p&gt;
&lt;p&gt;$$
\theta^* = \argmin_\theta D_{KL}[p_{real}(x) | p_\theta(x)] = \int_{\mathcal{X}} p_{real}(x) \log \frac{p_{real}(x)}{p_\theta(x)} dx
$$&lt;/p&gt;
&lt;p&gt;However, this is intractable since we don&apos;t know $p_{real}(x)$ explicitly. To find feasible methods, try decompose the divergence
$$
\begin{aligned}
\theta^* &amp;amp;= \argmin_\theta D_{KL}[p_{real}(x) | p_\theta(x)]\
&amp;amp;= \argmin_\theta \int_{\mathcal{X}} -p_{real}(x) \log p_\theta(x) dx + \text{const}\
&amp;amp;= \argmax_\theta \int_{\mathcal{X}} p_{real}(x) \log p_\theta(x) dx \
&amp;amp;= \argmax_\theta \mathbb{E}&lt;em&gt;{x \sim p&lt;/em&gt;{real}(x)}[\log p_\theta(x)]
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;So instead, we could maximize the &lt;strong&gt;log-likelihood&lt;/strong&gt; of our data under the model&lt;/p&gt;
&lt;p&gt;$$
\argmax_\theta \mathbb{E}&lt;em&gt;{x \sim p&lt;/em&gt;{real}(x)}[\log p_\theta(x)]
$$&lt;/p&gt;
&lt;p&gt;Okay, now, another problem emerged. Computing $p_\theta(x) = \int p(z)p_\theta(x|z)dz$ is also intractable due to &lt;strong&gt;hardness of the integral over all possible latent variables&lt;/strong&gt;. Detailed reasons are listed as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;High-dimensional integration&lt;/strong&gt;: The latent space $z$ is typically high-dimensional, making the integral computationally expensive or impossible to evaluate analytically.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Complex posterior distribution&lt;/strong&gt;: Even if we could compute $p_\theta(x)$, we would still need to compute the posterior $p_\theta(z|x) = \frac{p(z)p_\theta(x|z)}{p_\theta(x)}$ for inference, which requires the same intractable integral in the denominator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No closed-form solution&lt;/strong&gt;: For most practical choices of $p(z)$ and $p_\theta(x|z)$ (e.g., Gaussian distributions with neural network parameterization), the integral has no closed-form solution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sampling inefficiency&lt;/strong&gt;: Monte Carlo sampling methods would require an impractical number of samples to get good estimates, especially in high dimensions where most of the probability mass is concentrated in a small region.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Variational Inference Solution&lt;/h2&gt;
&lt;p&gt;To address these computational challenges, VAE introduces &lt;strong&gt;variational inference&lt;/strong&gt; by approximating the intractable posterior $p_\theta(z|x)$ with a tractable &lt;strong&gt;variational distribution&lt;/strong&gt; $q_\phi(z|x)$ parameterized by $\phi$ (typically implemented as an encoder neural network).&lt;/p&gt;
&lt;p&gt;The key insight is to establish an exact decomposition of the log-likelihood. Starting from the log-likelihood we want to maximize&lt;/p&gt;
&lt;p&gt;$$
\log p_\theta(x) = \log \int_{\mathcal{Z}} p(z)p_\theta(x|z)dz
$$&lt;/p&gt;
&lt;p&gt;We introduce the variational distribution $q_\phi(z|x)$ and derive&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\log p_\theta(x) &amp;amp;= \log p_\theta(x) \int_{\mathcal{Z}} q_\phi(z|x) dz \
&amp;amp;= \int_{\mathcal{Z}} q_\phi(z|x) \log p_\theta(x) dz \
&amp;amp;= \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}[\log p_\theta(x)] \
&amp;amp;= \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}\left[\log \frac{p_\theta(x,z)}{p_\theta(z|x)}\right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}\left[\log \frac{p_\theta(x,z) \cdot q_\phi(z|x)}{p_\theta(z|x) \cdot q_\phi(z|x)}\right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}\left[\log \frac{p_\theta(x,z)}{q_\phi(z|x)}\right] + \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}\left[\log \frac{q_\phi(z|x)}{p_\theta(z|x)}\right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}\left[\log p(z) + \log p_\theta(x|z) - \log q_\phi(z|x)\right] + D_{KL}[q_\phi(z|x) | p_\theta(z|x)] \
&amp;amp;= \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}[\log p_\theta(x|z)] - D_{KL}[q_\phi(z|x) | p(z)] + D_{KL}[q_\phi(z|x) | p_\theta(z|x)]
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;Therefore, we have the exact decomposition&lt;/p&gt;
&lt;p&gt;$$
\log p_\theta(x) = \mathcal{L}(\theta, \phi; x) + D_{KL}[q_\phi(z|x) | p_\theta(z|x)]
$$&lt;/p&gt;
&lt;p&gt;where the &lt;strong&gt;Evidence Lower Bound (ELBO)&lt;/strong&gt; is&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}(\theta, \phi; x) = \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}[\log p_\theta(x|z)] - D_{KL}[q_\phi(z|x) | p(z)]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key insights from this decomposition&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Since $D_{KL}[q_\phi(z|x) | p_\theta(z|x)] \geq 0$, we have $\log p_\theta(x) \geq \mathcal{L}(\theta, \phi; x)$, hence ELBO is indeed a lower bound.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The equality $\log p_\theta(x) = \mathcal{L}(\theta, \phi; x)$ holds if and only if $q_\phi(z|x) = p_\theta(z|x)$, meaning the variational posterior perfectly matches the true posterior.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maximizing ELBO w.r.t. $\phi$ minimizes $D_{KL}[q_\phi(z|x) | p_\theta(z|x)]$, making $q_\phi(z|x)$ a better approximation to the true posterior.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The ELBO consists of two terms&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Reconstruction term&lt;/strong&gt;: $\mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}[\log p_\theta(x|z)]$ - encourages the decoder to reconstruct the input&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regularization term&lt;/strong&gt;: $D_{KL}[q_\phi(z|x) | p(z)]$ - keeps the learned latent distribution close to the prior&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since maximizing $\log p_\theta(x)$ is intractable, we instead maximize the tractable ELBO as a surrogate objective.
$$
\argmax_\theta \mathcal{L}(\theta, \phi; x) = \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}[\log p_\theta(x|z)] - D_{KL}[q_\phi(z|x) | p(z)]
$$&lt;/p&gt;
&lt;h2&gt;Final Optimization Objective&lt;/h2&gt;
&lt;p&gt;Therefore, the final optimization objective function is:&lt;/p&gt;
&lt;p&gt;$$
\argmax_{\theta,\phi} \mathbb{E}&lt;em&gt;{x\sim p&lt;/em&gt;{\text{real}}(x)}[\mathcal{L}(\theta, \phi; x)]
$$&lt;/p&gt;
&lt;p&gt;where the ELBO for each sample is:&lt;/p&gt;
&lt;p&gt;$$
\mathcal{L}(\theta, \phi; x) = \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}[\log p_\theta(x|z)] - D_{KL}[q_\phi(z|x) | p(z)]
$$&lt;/p&gt;
&lt;p&gt;This can be equivalently written as:&lt;/p&gt;
&lt;p&gt;$$
\argmax_{\theta,\phi} \mathbb{E}&lt;em&gt;{x\sim p&lt;/em&gt;{\text{real}}(x)}\left[\mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}[\log p_\theta(x|z)] - D_{KL}[q_\phi(z|x) | p(z)]\right]
$$&lt;/p&gt;
&lt;p&gt;or in expanded form:&lt;/p&gt;
&lt;p&gt;$$
\argmax_{\theta,\phi} \mathbb{E}&lt;em&gt;{x\sim p&lt;/em&gt;{\text{real}}(x), z \sim q_\phi(z|x)}[\log p_\theta(x|z)] - \mathbb{E}&lt;em&gt;{x\sim p&lt;/em&gt;{\text{real}}(x)}[D_{KL}[q_\phi(z|x) | p(z)]]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key points:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We optimize both $\theta$ (decoder parameters) and $\phi$ (encoder parameters) simultaneously&lt;/li&gt;
&lt;li&gt;The KL divergence term also requires expectation over $x$, since $q_\phi(z|x)$ depends on $x$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Important&lt;/strong&gt;: We assume $p(z) = \mathcal{N}(0, I)$ is a fixed standard Gaussian prior. Since this distribution is analytically known, the KL divergence $D_{KL}[q_\phi(z|x) | p(z)]$ can be computed in closed form without sampling from $z$&lt;/li&gt;
&lt;li&gt;In practice, both expectations over $p_{\text{real}}(x)$ are approximated using mini-batch sampling from the training dataset&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why no sampling over $z$ in the KL term:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The KL divergence $D_{KL}[q_\phi(z|x) | \mathcal{N}(0,I)]$ has a closed-form analytical solution&lt;/li&gt;
&lt;li&gt;For Gaussian $q_\phi(z|x) = \mathcal{N}(\mu_\phi(x), \sigma^2_\phi(x)I)$, we get:
$$
D_{KL}[q_\phi(z|x) | \mathcal{N}(0,I)] = \frac{1}{2}\sum_{j=1}^d \left(1 + \log \sigma^2_j - \mu^2_j - \sigma^2_j\right)
$$&lt;/li&gt;
&lt;li&gt;This eliminates the need for Monte Carlo sampling in the regularization term&lt;/li&gt;
&lt;li&gt;Only the reconstruction term $\mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}[\log p_\theta(x|z)]$ requires sampling (via reparameterization trick)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;VAE Architecture&lt;/h2&gt;
&lt;p&gt;The VAE consists of two neural networks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Encoder&lt;/strong&gt; $q_\phi(z|x)$: Maps input $x$ to latent distribution parameters (typically mean and variance for Gaussian)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decoder&lt;/strong&gt; $p_\theta(x|z)$: Maps latent variable $z$ back to reconstruction of $x$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The training objective becomes:
$$
\max_{\theta,\phi} \mathbb{E}&lt;em&gt;{x \sim p&lt;/em&gt;{real}(x)}[\mathcal{L}(\theta, \phi; x)]
$$&lt;/p&gt;
&lt;h3&gt;Encoder Network&lt;/h3&gt;
&lt;p&gt;The encoder typically parameterizes a diagonal Gaussian distribution:&lt;/p&gt;
&lt;p&gt;$$
q_\phi(z|x) = \mathcal{N}(z; \mu_\phi(x), \sigma^2_\phi(x)I)
$$&lt;/p&gt;
&lt;p&gt;where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\mu_\phi(x) \in \mathbb{R}^d$ is the mean vector output by the encoder&lt;/li&gt;
&lt;li&gt;$\sigma^2_\phi(x) \in \mathbb{R}^d$ is the variance vector (often parameterized as $\log \sigma^2$ for numerical stability)&lt;/li&gt;
&lt;li&gt;$d$ is the dimensionality of the latent space&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Decoder Network&lt;/h3&gt;
&lt;p&gt;The decoder defines the likelihood of the data given the latent variable&lt;/p&gt;
&lt;p&gt;$$
p_\theta(x|z) = \mathcal{N}(x; \mu_\theta(z), \sigma^2_\theta I) \quad \text{(for continuous data)}
$$&lt;/p&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;p&gt;$$
p_\theta(x|z) = \text{Bernoulli}(x; p_\theta(z)) \quad \text{(for binary data)}
$$&lt;/p&gt;
&lt;h3&gt;Reparameterization Trick&lt;/h3&gt;
&lt;p&gt;To enable backpropagation through the stochastic sampling process, VAE employs the &lt;strong&gt;reparameterization trick&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead of sampling $z \sim q_\phi(z|x)$ directly, we&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sample noise: $\epsilon \sim \mathcal{N}(0, I)$&lt;/li&gt;
&lt;li&gt;Transform deterministically: $z = \mu_\phi(x) + \sigma_\phi(x) \odot \epsilon$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This makes the sampling operation differentiable w.r.t. $\phi$.&lt;/p&gt;
&lt;h3&gt;Training Process&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Forward pass&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Encode: $(x) \rightarrow (\mu_\phi(x), \sigma_\phi(x))$&lt;/li&gt;
&lt;li&gt;Sample: $z = \mu_\phi(x) + \sigma_\phi(x) \odot \epsilon$&lt;/li&gt;
&lt;li&gt;Decode: $(z) \rightarrow \hat{x} = \mu_\theta(z)$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Loss computation&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reconstruction loss&lt;/strong&gt;: $-\log p_\theta(x|z) \approx |x - \hat{x}|^2$ (for Gaussian decoder)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KL regularization&lt;/strong&gt;: $D_{KL}[q_\phi(z|x) | p(z)]$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backpropagation&lt;/strong&gt;: Update both $\theta$ and $\phi$ using gradient descent&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Practical Implementation&lt;/h3&gt;
&lt;p&gt;The ELBO for a single sample becomes:
$$
\mathcal{L}(\theta, \phi; x) = \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x)}[\log p_\theta(x|z)] - D_{KL}[q_\phi(z|x) | \mathcal{N}(0,I)]
$$&lt;/p&gt;
&lt;p&gt;For Gaussian encoder and decoder, the KL term has a closed form:
$$
D_{KL}[q_\phi(z|x) | \mathcal{N}(0,I)] = \frac{1}{2}\sum_{j=1}^d \left(1 + \log \sigma^2_j - \mu^2_j - \sigma^2_j\right)
$$&lt;/p&gt;
&lt;h1&gt;Conditional VAE (CVAE)&lt;/h1&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;Standard VAE generates random samples from $p(z)$. &lt;strong&gt;Conditional VAE&lt;/strong&gt; allows controlled generation by conditioning on additional information $c$ (e.g., class labels, attributes).&lt;/p&gt;
&lt;h2&gt;Mathematical Framework&lt;/h2&gt;
&lt;p&gt;The conditional generative model becomes:
$$
p_\theta(x|c) = \int p(z)p_\theta(x|z,c)dz
$$&lt;/p&gt;
&lt;p&gt;Both encoder and decoder are conditioned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Conditional encoder&lt;/strong&gt;: $q_\phi(z|x,c)$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conditional decoder&lt;/strong&gt;: $p_\theta(x|z,c)$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Modified ELBO&lt;/h2&gt;
&lt;p&gt;$$
\mathcal{L}(\theta, \phi; x, c) = \mathbb{E}&lt;em&gt;{z \sim q&lt;/em&gt;\phi(z|x,c)}[\log p_\theta(x|z,c)] - D_{KL}[q_\phi(z|x,c) | p(z)]
$$&lt;/p&gt;
&lt;h2&gt;Applications&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Class-conditional generation&lt;/strong&gt;: Generate images of specific classes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Style transfer&lt;/strong&gt;: Control artistic style while preserving content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Text-to-image&lt;/strong&gt;: Generate images from textual descriptions&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Vector Quantized VAE (VQ-VAE)&lt;/h1&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;Standard VAE suffers from &lt;strong&gt;posterior collapse&lt;/strong&gt; - the latent codes may be ignored during generation. VQ-VAE addresses this with &lt;strong&gt;discrete latent representations&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Key Innovation: Vector Quantization&lt;/h2&gt;
&lt;p&gt;Instead of continuous latent variables, VQ-VAE uses a &lt;strong&gt;discrete codebook&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Codebook&lt;/strong&gt;: $\mathcal{C} = {e_k}_{k=1}^K$ where $e_k \in \mathbb{R}^d$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quantization&lt;/strong&gt;: $\text{VQ}(z) = e_k$ where $k = \argmin_j |z - e_j|$&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Architecture Changes&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Encoder: x → z_e (continuous)
Vector Quantization: z_e → z_q (discrete)
Decoder: z_q → x̂
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Training Objective&lt;/h2&gt;
&lt;p&gt;$$
\mathcal{L} = |x - x̂|^2 + |\text{sg}[z_e] - e_k|^2 + \beta|z_e - \text{sg}[e_k]|^2
$$&lt;/p&gt;
&lt;p&gt;where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reconstruction loss&lt;/strong&gt;: $|x - x̂|^2$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Codebook loss&lt;/strong&gt;: Updates codebook vectors toward encoder outputs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commitment loss&lt;/strong&gt;: Encourages encoder outputs to commit to codebook entries&lt;/li&gt;
&lt;li&gt;$\text{sg}[\cdot]$ denotes stop-gradient operation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Advantages of VQ-VAE&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;No posterior collapse&lt;/strong&gt;: Discrete codes are always used&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better reconstruction&lt;/strong&gt;: Avoids blurry outputs common in VAE&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interpretable latents&lt;/strong&gt;: Discrete codes often correspond to meaningful features&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hierarchical modeling&lt;/strong&gt;: Can be stacked for multi-scale representations&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;VQ-VAE-2&lt;/h2&gt;
&lt;p&gt;Extends VQ-VAE with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hierarchical quantization&lt;/strong&gt;: Multiple resolution levels&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PixelCNN decoder&lt;/strong&gt;: Autoregressive modeling of quantized codes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better sample quality&lt;/strong&gt;: Competitive with GANs on image generation&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Comparison Summary&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Latent Space&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VAE&lt;/td&gt;
&lt;td&gt;Continuous, Gaussian&lt;/td&gt;
&lt;td&gt;Simple, stable training&lt;/td&gt;
&lt;td&gt;Blurry outputs, posterior collapse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVAE&lt;/td&gt;
&lt;td&gt;Continuous, conditional&lt;/td&gt;
&lt;td&gt;Controllable generation&lt;/td&gt;
&lt;td&gt;Still suffers from VAE limitations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VQ-VAE&lt;/td&gt;
&lt;td&gt;Discrete, codebook&lt;/td&gt;
&lt;td&gt;Sharp outputs, no collapse&lt;/td&gt;
&lt;td&gt;More complex training, discrete optimization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Applications and Impact&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Image generation&lt;/strong&gt;: DALL-E uses VQ-VAE for tokenizing images&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audio modeling&lt;/strong&gt;: VQ-VAE for speech and music synthesis&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Representation learning&lt;/strong&gt;: Learning disentangled and interpretable features&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data compression&lt;/strong&gt;: Efficient encoding of high-dimensional data&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>人工免疫算法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/artificialimmune/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/artificialimmune/</guid><pubDate>Thu, 24 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;人工免疫计算（Artificial Immune Systems, AIS）是一类受生物免疫系统启发的智能优化与学习方法族。常见应用包括：连续/离散优化、组合优化、分类与聚类、异常检测、在线自适应控制等。&lt;/p&gt;
&lt;h1&gt;概述&lt;/h1&gt;
&lt;p&gt;生物免疫理论为人工免疫算法的建立奠定了基础。生物免疫主要有两种类型，分别是&lt;strong&gt;特异性免疫&lt;/strong&gt;和&lt;strong&gt;非特异性免疫&lt;/strong&gt;。生物免疫系统是通过自我识别、相互刺激与制约而构成了一个动态平衡的网络结构。&lt;/p&gt;
&lt;h2&gt;生物学概念&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;免疫：指机体对自身和异体识别与响应过程中产生的生物学效应的总和，正常情况下是一种维持机体循环稳定的生理性功能。生物机体识别异体抗原，对其产生免疫相应并清除，机体对自身抗原不产生免疫反应。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;抗原：指能够刺激和诱导机体的免疫系统使其产生免疫应答，并能与相应的免疫应答产物在体内或体外发生特异性反应的物质&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;抗体：指免疫系统受抗原刺激后，免疫细胞转化为浆细胞并产生能与抗原发生特异性结合的免疫球蛋白，该免疫球蛋白即为抗体&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;T 细胞和 B 细胞：是淋巴细胞的主要组成成分。 B 细胞受到抗原刺激后，可增殖分化为大量浆细胞。但是，B 细胞不能识别大多数抗原，必须借助能识别抗原的辅助性 T 细胞来辅助 B 细胞活化，产生抗体。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;免疫系统的主要功能有&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;免疫防御：机体防御病原微生物的感染&lt;/li&gt;
&lt;li&gt;免疫稳定：机体通过免疫功能经常消除那些损伤和衰老细胞以维持机体的生理平衡&lt;/li&gt;
&lt;li&gt;免疫监视：机体通过免疫功能防止或消除体内细胞在新陈代谢过程中发生突变的和异常的细胞&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;免疫系统分为&lt;strong&gt;固有免疫系统&lt;/strong&gt;和&lt;strong&gt;自适应免疫系统&lt;/strong&gt;。固有免疫系统是抵抗抗原感染的第一道防线；自适应免疫系统能够记住入侵的抗原特征，预防下一次攻击。自适应免疫系统有两个分支，&lt;strong&gt;体液免疫&lt;/strong&gt;（由 B 细胞及其产物介导）和 &lt;strong&gt;细胞免疫&lt;/strong&gt;（由 T 细胞介导）。&lt;/p&gt;
&lt;h2&gt;生物学免疫机理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;免疫识别：通过淋巴细胞上的抗原受体与抗原特异性结合来实现敌我的区分。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;免疫学习：学习的结果是免疫细胞的个体亲和度提高、群体规模扩大，并且最优个体以免疫记忆的形式得到保存。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;免疫记忆：当免疫系统初次遇到一种抗原时，淋巴细胞需要一定的时间进行调整以更好地识别抗原，并在识别结束以后以最优抗体的形式保留对该抗体的记忆信息。而在免疫系统再次遇到相同或者结构相似的抗原时，在联想记忆的作用下，其应答速度大大提高。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;克隆选择模型：免疫应答和免疫细胞的增殖在一个特定的匹配阈值之上发生。当淋巴细胞实现对抗原的识别， B 细胞被激活并增殖复制产生克隆 B 细胞，随后克隆细胞经历变异过程，产生对抗抗原具有特异性的抗体。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;个体多样性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分布式和自适应性&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;免疫应答&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;免疫系统具有两种应答方式，初次应答和二次应答。初次应答发生在免疫系统遭遇第一次遇到过的抗原并对其反应的时候。免疫系统能够学习抗原，该机制产生免疫记忆，这样为身体再次遇到同样的抗原时产生二次应答。当抗体结合一个抗原时，B 细胞受刺激产生自体克隆，成长的克隆体现一种变异机制使免疫系统具有适应性。&lt;/p&gt;
&lt;h1&gt;免疫计算&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;生物免疫系统&lt;/th&gt;
&lt;th&gt;免疫算法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;抗原&lt;/td&gt;
&lt;td&gt;优化问题&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;抗体&lt;/td&gt;
&lt;td&gt;优化问题的可行解&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;亲和度&lt;/td&gt;
&lt;td&gt;可行解的质量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;细胞活化&lt;/td&gt;
&lt;td&gt;免疫选择&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;亲和度成熟&lt;/td&gt;
&lt;td&gt;变异&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;克隆抑制&lt;/td&gt;
&lt;td&gt;克隆抑制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;动态维持平衡&lt;/td&gt;
&lt;td&gt;种群刷新&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;流程图
&lt;img src=&quot;assets/immune.png&quot; alt=&quot;immune&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先进行抗原识别，即理解待优化问题，对问题进行可行性分析，提取先验知识，构造出合适的亲和度函数，并指定各种约束条件&lt;/li&gt;
&lt;li&gt;产生初始抗体群，通过编码把问题的可行解表示成解空间中的抗体，在解的空间中随机产生一个初始种群&lt;/li&gt;
&lt;li&gt;对种群中的每一个可行解进行亲和度评价&lt;/li&gt;
&lt;li&gt;判断是否满足算法终止条件。若满足终止条件则终止算法寻优过程，输出计算结果；否则继续寻优计算&lt;/li&gt;
&lt;li&gt;计算抗体浓度和激励度&lt;/li&gt;
&lt;li&gt;进行免疫处理，包括&lt;strong&gt;免疫选择、克隆、变异和克隆抑制&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;种群刷新，以随机生成的新抗体替代种群中的激励度较低的抗体，形成新一代抗体，转至步骤3&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;免疫选择&lt;/strong&gt;：根据种群中抗体的亲和度和浓度计算结果选择优质抗体，使其活化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;克隆&lt;/strong&gt;：对活化的抗体进行克隆操作，使其发生亲和度突变&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;变异&lt;/strong&gt;：对克隆得到的副本进行变异操作，使其发生亲和度突变&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;克隆抑制&lt;/strong&gt;：对变异结果进行再选择，抑制亲和度低的抗体，保留亲和度高的变异结果&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;核心算子&lt;/h2&gt;
&lt;h3&gt;亲和度 (Affinity) 评价算子&lt;/h3&gt;
&lt;p&gt;表征免疫细胞与抗原的结合强度，类似遗传算法中的适应度。函数的输入为一个抗体（可行解），输出为亲和度评价结果。通常，&lt;strong&gt;亲和度可以直接设置为目标函数&lt;/strong&gt;（对于最大化问题，因为亲和度越大说明越匹配）&lt;/p&gt;
&lt;p&gt;除了用类似相似度的方式作为亲和度之外，也可以用距离来描述亲和度，距离越小，亲和度越大。例如，对于连续编码，可以设置为欧氏距离；对于离散编码，可以设置为海明距离。&lt;/p&gt;
&lt;h3&gt;抗体浓度评价算子&lt;/h3&gt;
&lt;p&gt;抗体的浓度表征抗体种群的多样性好坏，抗体浓度过高意味着种群中非常相似的个体大量存在，则寻优搜索会集中于可行解区间的一个区域，不利于全局优化。因此优化算法中应对浓度过高的个体进行抑制，保证个体的多样性。抗体浓度可以定义为
$$
\text{Density}(a_i) = \frac{1}{N} \sum_{j=1}^{N} \text{similarity}(a_i, a_j)
$$
其中，$N$ 为种群规模，$a_i$ 表示第 $i$ 个抗体，$\text{similarity}(a_i, a_j)$ 表示抗体 $a_i$ 和 $a_j$ 之间的&quot;相似度&quot;，可以定义为 $similarity(x, y) = \begin{cases}1 ,\quad d(x, y) &amp;lt; \delta \ 0, \quad d(x, y) \ge \delta\end{cases}$&lt;/p&gt;
&lt;h3&gt;激励度 / 活化度（Stimulation）算子&lt;/h3&gt;
&lt;p&gt;很多免疫优化会同时考虑“解的质量（亲和度）”与“种群多样性（浓度/相似性）”，用一个综合指标来决定谁被活化、谁被抑制。一个常用的线性形式是
$$
ext{Stim}(a_i) = \alpha,\widehat{\text{Aff}}(a_i) - \beta,\text{Density}(a_i)
$$
其中 $\alpha,\beta&amp;gt;0$ 为权重，$\widehat{\text{Aff}}\in[0,1]$ 表示归一化后的亲和度。&lt;/p&gt;
&lt;h3&gt;亲和度与目标函数的关系（最小化/最大化）&lt;/h3&gt;
&lt;p&gt;若原问题为&lt;strong&gt;最小化&lt;/strong&gt; $f(x)$，但算法希望“亲和度越大越好”，常见转换方式：
$$
ext{Aff}(x) = \frac{1}{1+f(x)}\quad (f(x)\ge 0)\qquad\text{或}\qquad
ext{Aff}(x)=\exp(-\gamma f(x))
$$
若 $f(x)$ 可能为负，可先平移：$f&apos;(x)=f(x)-\min(f)+\epsilon$。&lt;/p&gt;
&lt;h3&gt;免疫记忆（Memory）机制&lt;/h3&gt;
&lt;p&gt;免疫系统会保存少量“记忆抗体”（当前最优/代表性解），并以较低频率被替换。工程上常用做法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保留前 $M$ 个最优个体作为记忆库 $\mathcal{M}$&lt;/li&gt;
&lt;li&gt;每代将种群与记忆库合并排序，更新 $\mathcal{M}$&lt;/li&gt;
&lt;li&gt;抑制时优先保留记忆库以避免丢失全局最优&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;种群刷新（Repertoire / Random Insertion）&lt;/h3&gt;
&lt;p&gt;为避免早熟收敛，引入“新抗体注入”：每代用随机解替换掉激励度最低的 $r%$ 个体。
$$
\mathcal{P}\leftarrow \text{ReplaceWorst}(\mathcal{P},; \lfloor rN\rfloor; \text{random antibodies})
$$&lt;/p&gt;
&lt;h2&gt;建模与实现要点&lt;/h2&gt;
&lt;h3&gt;抗体表示（编码）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;二进制/离散编码&lt;/strong&gt;：适合组合优化、规则匹配、字符串检测（负选择）。距离可用海明距离 $d_H$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实数向量编码&lt;/strong&gt;：适合连续优化。距离可用欧氏距离 $\lVert x-y\rVert_2$、曼哈顿距离 $\lVert x-y\rVert_1$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;排列编码&lt;/strong&gt;：如 TSP。相似度可用边重合率、交换距离等。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;约束处理（可行性与惩罚函数）&lt;/h3&gt;
&lt;p&gt;对约束优化
$$
\min f(x)\quad \text{s.t.}\quad g_k(x)\le 0,; h_m(x)=0
$$
常用惩罚法把约束并入目标：
$$
F(x)= f(x)+\lambda\sum_k\max{0,g_k(x)}^p+\mu\sum_m |h_m(x)|^q
$$
然后用 $F(x)$ 构造亲和度 $\text{Aff}(x)$。&lt;/p&gt;
&lt;h2&gt;典型人工免疫算法&lt;/h2&gt;
&lt;h3&gt;1) 克隆选择算法（CLONALG / Clonal Selection）&lt;/h3&gt;
&lt;p&gt;核心思想：&lt;strong&gt;高亲和度个体克隆更多、变异更少&lt;/strong&gt;；低亲和度个体克隆更少、变异更强。&lt;/p&gt;
&lt;h4&gt;常见克隆数分配&lt;/h4&gt;
&lt;p&gt;将种群按亲和度从高到低排序，选取前 $n$ 个进行克隆。第 $i$ 名的克隆数可设为
$$
n_i = \left\lfloor \beta \frac{n}{i} \right\rfloor \quad (i=1,2,\dots,n)
$$
其中 $\beta$ 为克隆因子。&lt;/p&gt;
&lt;h4&gt;超变异（Hypermutation）强度&lt;/h4&gt;
&lt;p&gt;变异强度与亲和度负相关。对归一化亲和度 $\widehat{\text{Aff}}\in[0,1]$，一种常用设定：
$$
\sigma_i = \sigma_{\max}\exp(-\rho,\widehat{\text{Aff}}(a_i))
$$
然后对实数编码做高斯扰动：
$$
a&apos;&lt;em&gt;i = a_i + \mathcal{N}(0,\sigma_i^2 I)
$$
对二进制编码可用按位翻转概率 $p_i = p&lt;/em&gt;{\max}\exp(-\rho,\widehat{\text{Aff}}(a_i))$。&lt;/p&gt;
&lt;h4&gt;伪代码&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Input: population size N, selected n, clone factor β, refresh rate r
Initialize P with N random antibodies
Initialize memory M = ∅
repeat
  Evaluate Aff(a) for a in P
  Update memory M with best antibodies from P ∪ M
  Select top-n antibodies S from P
  C = ∅
  for i-th antibody in S (ranked):
	  clone n_i copies -&amp;gt; add to C
  Hypermutate all clones in C (mutation inversely to affinity)
  Evaluate Aff for clones; keep best clones to form P&apos;
  Suppress highly similar antibodies in P&apos; (keep diversity)
  Refresh: replace worst ⌊rN⌋ in P&apos; with random antibodies
  P = P&apos;
until stop criterion
Output: best antibody in M
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Python 示例：用 CLONALG 优化 Rastrigin（连续最小化）&lt;/h4&gt;
&lt;p&gt;下面代码演示一个“可跑通”的最小实现：亲和度用 $\exp(-\gamma f(x))$，并做简单的相似度抑制与刷新。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import numpy as np


def rastrigin(x: np.ndarray) -&amp;gt; float:
	# global minimum at x=0, f=0
	A = 10.0
	return A * x.size + np.sum(x * x - A * np.cos(2 * np.pi * x))


def clonalg_optimize(
	f,
	dim: int,
	bounds: tuple[float, float] = (-5.12, 5.12),
	N: int = 60,
	n_select: int = 12,
	beta: float = 6.0,
	sigma_max: float = 0.8,
	rho: float = 4.0,
	gamma: float = 0.08,
	refresh_rate: float = 0.15,
	suppress_delta: float = 0.25,
	iters: int = 200,
	seed: int = 0,
):
	rng = np.random.default_rng(seed)
	lo, hi = bounds

	def clip(x):
		return np.clip(x, lo, hi)

	# population: (N, dim)
	P = rng.uniform(lo, hi, size=(N, dim))
	best_x = None
	best_f = np.inf

	for _ in range(iters):
		fvals = np.array([f(x) for x in P])
		idx = np.argsort(fvals)  # minimization
		P = P[idx]
		fvals = fvals[idx]

		if fvals[0] &amp;lt; best_f:
			best_f = float(fvals[0])
			best_x = P[0].copy()

		# affinity in [0,1], larger is better
		aff = np.exp(-gamma * fvals)
		aff_norm = (aff - aff.min()) / (aff.max() - aff.min() + 1e-12)

		S = P[:n_select]
		S_aff = aff_norm[:n_select]

		# cloning
		clones = []
		for i, (x, a) in enumerate(zip(S, S_aff), start=1):
			n_i = int(np.floor(beta * n_select / i))
			if n_i &amp;lt;= 0:
				continue
			# hypermutation strength: high affinity -&amp;gt; small sigma
			sigma = sigma_max * np.exp(-rho * a)
			noise = rng.normal(0.0, sigma, size=(n_i, dim))
			clones.append(clip(x + noise))

		C = np.vstack(clones) if clones else clip(rng.uniform(lo, hi, size=(N, dim)))
		C_f = np.array([f(x) for x in C])
		# pick best N candidates from union
		U = np.vstack([P, C])
		U_f = np.concatenate([fvals, C_f])
		U_idx = np.argsort(U_f)
		P_new = U[U_idx][:N]

		# simple suppression: remove too-close individuals (euclidean)
		kept = []
		for x in P_new:
			if not kept:
				kept.append(x)
				continue
			dmin = min(np.linalg.norm(x - y) for y in kept)
			if dmin &amp;gt;= suppress_delta:
				kept.append(x)
			if len(kept) &amp;gt;= N:
				break
		# refill if suppressed too much
		while len(kept) &amp;lt; N:
			kept.append(rng.uniform(lo, hi, size=(dim,)))
		P = np.array(kept)

		# refresh worst
		k = int(np.floor(refresh_rate * N))
		if k &amp;gt; 0:
			P[-k:] = rng.uniform(lo, hi, size=(k, dim))

	return best_x, best_f


if __name__ == &quot;__main__&quot;:
	x_star, f_star = clonalg_optimize(rastrigin, dim=10, iters=300, seed=42)
	print(&quot;best f:&quot;, f_star)
	print(&quot;best x (first 5 dims):&quot;, x_star[:5])
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;2) 负选择算法（Negative Selection, NSA）——异常检测&lt;/h3&gt;
&lt;p&gt;核心思想：生成一组“探测器（detectors）”去覆盖**非自体（non-self）**区域。训练阶段确保探测器不匹配任何“自体样本”；检测阶段若探测器匹配到输入，则判为异常。&lt;/p&gt;
&lt;h4&gt;基本定义（以二进制串为例）&lt;/h4&gt;
&lt;p&gt;自体集合 $\mathcal{S}$（正常样本），探测器集合 $\mathcal{D}$。匹配规则通常基于海明距离：
$$
ext{match}(x,d)=\mathbb{I}[d_H(x,d)\le r]
$$
其中 $r$ 为匹配半径。&lt;/p&gt;
&lt;h4&gt;伪代码&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Input: self set S, detector count M, radius r
Initialize D = ∅
while |D| &amp;lt; M:
  sample random candidate d
  if ∀s∈S: d_H(d,s) &amp;gt; r:
	  add d to D
Detect(x): if ∃d∈D with d_H(d,x) ≤ r then anomaly else normal
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NSA 的难点是“覆盖率 vs 误报率”的权衡：$r$ 大则覆盖强但误报高；$r$ 小则漏报风险大。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;3) 免疫网络模型（Immune Network）——多样性维持&lt;/h3&gt;
&lt;p&gt;Jerne 的免疫网络思想强调“抗体-抗体”之间的相互促进/抑制，从而形成动态平衡。&lt;/p&gt;
&lt;p&gt;一种经典的浓度动力学形式（示意）
$$
\frac{dx_i}{dt} = c\left(\sum_j m_{ij}x_i x_j - k\sum_j s_{ij}x_i x_j\right) + \eta,\text{Aff}(a_i),x_i - d,x_i
$$
其中 $x_i$ 为第 $i$ 个抗体浓度，$m_{ij}$ 表示促进（相似性带来的刺激），$s_{ij}$ 表示抑制（过度相似导致竞争），$d$ 为自然衰减。通过网络抑制可显著提升“多峰问题”的多样性与稳态覆盖。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;4) 免疫算法在优化中的常见“抑制”实现&lt;/h3&gt;
&lt;p&gt;免疫抑制用于去除“过于相似”的冗余解。常用做法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算个体间距离矩阵 $D_{ij}=d(a_i,a_j)$（$O(N^2)$）&lt;/li&gt;
&lt;li&gt;若 $D_{ij}&amp;lt;\delta$，只保留亲和度更高者&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;工程优化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用近邻结构（如 kNN）近似抑制，降低复杂度&lt;/li&gt;
&lt;li&gt;分簇后在簇内做抑制&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参数选择与实践建议&lt;/h2&gt;
&lt;h3&gt;常用参数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;$N$：种群规模（一般 20–200，随维度增大而增大）&lt;/li&gt;
&lt;li&gt;$n$：选择并克隆的个体数（通常 $n\approx 0.1N$ 到 $0.3N$）&lt;/li&gt;
&lt;li&gt;$\beta$：克隆因子（越大开发越强，但计算更贵）&lt;/li&gt;
&lt;li&gt;$\sigma_{\max}$ / $p_{\max}$：最大变异强度（探索能力来源）&lt;/li&gt;
&lt;li&gt;$\rho$：亲和度-变异强度的耦合（越大越“精英保守”）&lt;/li&gt;
&lt;li&gt;$\delta$：抑制阈值（决定多样性维持强弱）&lt;/li&gt;
&lt;li&gt;$r$：刷新率（避免停滞；但过大会变成随机搜索）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;终止条件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;最大迭代/最大评估次数&lt;/li&gt;
&lt;li&gt;最优值若干代无改进（early stop）&lt;/li&gt;
&lt;li&gt;亲和度达到阈值或满足约束精度&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;何时 AIS 特别好用&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;目标函数多峰且易早熟（免疫抑制/网络更有优势）&lt;/li&gt;
&lt;li&gt;需要“记忆”与“在线自适应”（概念漂移、动态环境）&lt;/li&gt;
&lt;li&gt;异常检测/安全（负选择天然契合）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;常见坑&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;浓度/相似度计算 $O(N^2)$：大规模时需近似或分簇&lt;/li&gt;
&lt;li&gt;亲和度缩放不当：导致“变异强度”失效（建议用归一化或指数映射）&lt;/li&gt;
&lt;li&gt;刷新率过高：收敛困难；过低：停滞&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;与遗传算法（GA）的快速对比&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;相同点：种群搜索、选择与变异都是核心&lt;/li&gt;
&lt;li&gt;不同点：AIS 强调“克隆选择 + 亲和度成熟（超变异）+ 抑制/网络维持多样性 + 记忆机制”&lt;/li&gt;
&lt;li&gt;实务上：把 AIS 视为“更强调局部成熟与多样性抑制的种群优化框架”通常更准确&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考与延伸阅读&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;de Castro, L. N., Von Zuben, F. J. &lt;em&gt;Learning and Optimization Using the Clonal Selection Principle&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Forrest, S. et al. Negative Selection 系列工作（异常检测/入侵检测的经典起点）。&lt;/li&gt;
&lt;li&gt;Farmer, J. D., Packard, N. H., Perelson, A. S. Immune network 模型的早期经典工作。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>强化学习 Chapter 6 - 策略梯度方法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter6/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter6/</guid><pubDate>Sun, 20 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;6.1 策略方法&lt;/h1&gt;
&lt;p&gt;策略方法是强化学习中的一类重要方法。其核心思想是，直接优化参数化策略函数 $\pi_\theta(a|s)$，以最大化期望的累计折扣奖励
$$
J(\theta) = \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta}\left[\sum_{t=0}^{T} \gamma^t r_t\right]
$$
与值函数方法不同，策略方法无需显式地估计动作价值函数 $Q(s, a)$ 或状态价值函数 $V(s)$，而是基于采样得到的轨迹 $\tau = (s_0, a_0, s_1, a_1, \dots, s_T)$，利用策略梯度定理 (Policy Gradient Theorem) 计算梯度，并通过梯度上升法更新策略参数 $\theta$。该方法适用于连续动作空间，且能够自然地处理随机策略。&lt;/p&gt;
&lt;p&gt;经典的策略方法包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;REINFORCE (Monte Carlo Policy Gradient)：基于完整轨迹的蒙特卡洛梯度估计；&lt;/li&gt;
&lt;li&gt;Actor-Critic：结合策略网络 (Actor) 与值函数网络 (Critic)，以降低梯度估计方差；&lt;/li&gt;
&lt;li&gt;Trust Region Policy Optimization (TRPO)：引入信任区域约束，确保策略更新步长可控，保证训练稳定性；&lt;/li&gt;
&lt;li&gt;Proximal Policy Optimization (PPO)：通过近似 TRPO 的裁剪机制，在保持性能的同时实现更高的训练效率与鲁棒性。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;6.2 策略梯度方法&lt;/h1&gt;
&lt;h2&gt;6.2.1 直接求策略梯度面临的困难&lt;/h2&gt;
&lt;p&gt;在策略方法中，我们的目标是找到最优的策略参数 $\theta^*$，使得期望回报能够最大化
$$
\max_\theta J(\theta) = \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta}\left[\sum_{t=0}^{T} \gamma^t r_t\right]
$$
直接计算 $J(\theta)$ 的梯度
$$
\nabla_\theta J(\theta) = \frac{\partial}{\partial \theta} \int_\tau R(\tau) p_\theta(\tau) d\tau = \int_\tau R(\tau) \frac{\partial}{\partial \theta} p_\theta(\tau) d\tau
$$
其中:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$p_\theta(\tau) = p_0(s_0) \prod_{t=0}^{T-1} \pi_\theta(a_t|s_t) p(s_{t+1}|s_t, a_t)$ 是在策略 $\pi_\theta$ 下生成轨迹 $\tau$ 的概率分布&lt;/li&gt;
&lt;li&gt;$R(\tau) = \sum_{t=0}^{T} \gamma^{t} r_t$ 是轨迹 $\tau$ 的累计回报，它是与参数 $\theta$ 无关，但与轨迹 $\tau$ 相关的量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里出现了两个难以计算的困难：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;环境动态未知 (Model-free)&lt;/strong&gt;：轨迹 $\tau$ 的生成主要由环境动态变化 $p(s_{t+1}|s_t, a_t)$ 和策略 $\pi_\theta(a_t|s_t)$ 决定。环境动态通常是未知的，这意味着我们无法直接计算 $p_\theta(\tau)$ 的解析形式，也即无法计算出上式的解析解。即使能通过采样的方式估计出 $p_\theta(\tau)$ 的数值，求解它的梯度 $\frac{\partial}{\partial \theta} p_\theta(\tau)$ 也是非常困难的，因为它涉及到整个轨迹的概率分布，而不是单个状态或动作的概率分布。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;轨迹空间巨大&lt;/strong&gt;：若状态空间或动作空间是连续空间，想要遍历全部可能的轨迹 $\tau$ 是无法实现的。若状态空间和动作空间是离散空间，随着时间步数 $T$ 的增加，轨迹的数量也会呈指数级增长，这使得直接计算积分是不可行的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，无论是解析求解还是数值积分，都是不可行的。&lt;/p&gt;
&lt;h2&gt;6.2.2 分解轨迹分布的梯度 $\frac{\partial}{\partial \theta} p_\theta(\tau)$&lt;/h2&gt;
&lt;p&gt;既然环境动态与策略参数 $\theta$ 无关，那么 $\frac{\partial}{\partial \theta} p(s_{t+1} | s_t, a_t) = 0$。考虑到轨迹分布 $p_\theta(\tau)$ 是链式乘法形式的分布函数，做一个对数变换，得到
$$
\log p_\theta(\tau) = \log p_0(s_0) + \sum_{t=0}^{T-1} \log \pi_\theta(a_t|s_t) + \sum_{t=0}^{T-1} \log p(s_{t+1}|s_t, a_t)
$$
对 $\theta$ 求导，得到
$$
\frac{\partial}{\partial \theta} \log p_\theta(\tau) = \sum_{t=0}^{T-1} \frac{\partial}{\partial \theta} \log \pi_\theta(a_t|s_t)
$$
利用 Log-Derivative Trick，
$$
\frac{\partial}{\partial \theta} p_\theta(\tau) = p_\theta(\tau) \frac{\partial}{\partial \theta} \log p_\theta(\tau) = p_\theta(\tau) \sum_{t=0}^{T-1} \frac{\partial}{\partial \theta} \log \pi_\theta(a_t|s_t)
$$
这下就很简单了，我们把上式代入到 $\nabla_\theta J(\theta)$ 的表达式中，得到
$$
\nabla_\theta J(\theta) = \int_\tau R(\tau) p_\theta(\tau) \sum_{t=0}^{T-1} \frac{\partial}{\partial \theta} \log \pi_\theta(a_t|s_t) d\tau = \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ R(\tau) \sum_{t=0}^{T-1} \frac{\partial}{\partial \theta} \log \pi_\theta(a_t|s_t) \right]
$$
这里虽然 $p_\theta(\tau)$ 解析形式得不到，积分也是无法计算的，但是我们可以转化为采样的方式来估计 $\nabla_\theta J(\theta)$，这就是策略梯度方法的核心思想。&lt;/p&gt;
&lt;h2&gt;6.2.3 策略梯度定理&lt;/h2&gt;
&lt;p&gt;需要指出
$$
\boxed{
\nabla_\theta J(\theta) = \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ R(\tau) \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \right]
}
$$
并非策略梯度的最简化形式，这里面仍然有很多可以消除的项。展开轨迹 $\tau$ 的回报 $R(\tau)$，得到
$$
\begin{aligned}
\nabla_\theta J(\theta) &amp;amp;= \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \left(\sum_{k=0}^{T} \gamma^{k}r_k \right) \left(\sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \right) \right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{T-1} \sum_{k=0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t) \gamma^{k}r_k \right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \left(\sum_{k=0}^{t-1} \gamma^{k}r_k + \sum_{k=t}^{T} \gamma^{k}r_k \right) \right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \sum_{k=0}^{t-1} \gamma^{k}r_k \right] + \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \sum_{k=t}^{T} \gamma^{k}r_k \right] \
\end{aligned}
$$
展开第一个期望项，得到
$$
\mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \sum_{k=0}^{t-1} \gamma^{k}r_k \right] = \sum_{t=0}^{T-1} \sum_{k=0}^{t-1} \gamma^{k} \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ r_k \nabla_\theta \log \pi_\theta(a_t|s_t) \right]
$$
而双求和里面的那个期望实际上可以进一步简化，这里要考虑到轨迹 $\tau = (s_0, a_0, \ldots, s_T)$ 看起来是一个多维随机向量，实际上，内部变量之间存在依赖关系—— $r_k$ 的产生仅依赖于历史状态和动作 ${(s_i, a_i)}&lt;em&gt;{i=0}^k$，与未来的状态和动作是相互条件独立的:
$$
\begin{aligned}
&amp;amp;\mathbb{E}&lt;/em&gt;{\tau \sim \pi_\theta} \left[ r_k \nabla_\theta \log \pi_\theta(a_t|s_t) \right] \quad (k &amp;lt; t)\
=&amp;amp; \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ r_k \right] \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \log \pi_\theta(a_t|s_t) \right]\
=&amp;amp; \mathbb{E}&lt;em&gt;{s_0 \sim p_0(s_0), a_0 \sim \pi&lt;/em&gt;\theta(a_0 | s_0), s_1 \sim p(s_1 | s_0, a_0), \ldots, s_T \sim p(s_T | s_{T-1}, a_{T-1}), r_k \sim p(r_k | s_k, a_k)} \left[ r_k \right] \cdot \mathbb{E}&lt;em&gt;{s_0 \sim p_0(s_0), a_0 \sim \pi&lt;/em&gt;\theta(a_0 | s_0), s_1 \sim p(s_1 | s_0, a_0), \ldots, s_T \sim p(s_T | s_{T-1}, a_{T-1})} \left[\nabla_\theta \log \pi_\theta(a_t|s_t) \right]\
=&amp;amp; \mathbb{E}&lt;em&gt;{s_0 \sim p_0(s_0), a_0 \sim \pi&lt;/em&gt;\theta(a_0 | s_0), s_1 \sim p(s_1 | s_0, a_0), \ldots, s_k \sim p(s_k | s_{k-1}, a_{k-1}), a_k \sim \pi_\theta(a_k | s_k), r_k \sim p(r_k | s_k, a_k)} \left[ r_k \right] \cdot \mathbb{E}&lt;em&gt;{s_0 \sim p_0(s_0), a_0 \sim \pi&lt;/em&gt;\theta(a_0 | s_0), s_1 \sim p(s_1 | s_0, a_0), \ldots, s_t \sim p(s_t | s_{t-1}, a_{t-1}), a_t \sim \pi_\theta(a_t | s_t)} \left[ \nabla_\theta \log \pi_\theta(a_t|s_t) \right] \
=&amp;amp; \mathcal{R}(s_k, a_k) \cdot \mathbb{E}&lt;em&gt;{(s_0, a_0, \ldots, s&lt;/em&gt;{t-1}, a_{t-1}) \sim \pi_\theta, s_t \sim p(s_t | s_{t-1}, a_{t-1}), a_t \sim \pi_\theta(a_t | s_t)} \left[ \nabla_\theta \log \pi_\theta(a_t|s_t) \right]
\end{aligned}
$$
注意这里我们省略了 $r_k$ 的生成过程，$r_k$ 可以是由确定性的奖励函数 $r(s_k, a_k)$ 给出，也可以是由随机的奖励分布 $p(r_k | s_k, a_k)$ 给出，这里为了公式简化表达，将期望奖励 $r_k$ 直接表达成了一个随机的奖励函数值
$$
\mathcal{R}(s_k, a_k) = \mathbb{E}&lt;em&gt;{s_0 \sim p_0(s_0), a_0 \sim \pi&lt;/em&gt;\theta(a_0 | s_0), s_1 \sim p(s_1 | s_0, a_0), \ldots, s_k \sim p(s_k | s_{k-1}, a_{k-1}), a_k \sim \pi_\theta(a_k | s_k), r_k \sim p(r_k | s_k, a_k)} \left[ r_k \right]
$$
好，继续关注新出现的后一项期望，它是一个条件期望
$$
\begin{aligned}
&amp;amp;\mathbb{E}&lt;em&gt;{(s_0, a_0, \ldots, s&lt;/em&gt;{t-1}, a_{t-1}) \sim \pi_\theta, s_t \sim p(s_t | s_{t-1}, a_{t-1}), a_t \sim \pi_\theta(a_t | s_t)} \left[ \nabla_\theta \log \pi_\theta(a_t|s_t) \right] \
=&amp;amp; \mathbb{E}&lt;em&gt;{(s_0, a_0, \ldots, s&lt;/em&gt;{t-1}, a_{t-1}) \sim \pi_\theta} \left[ \mathbb{E}&lt;em&gt;{s_t \sim p(s_t | s&lt;/em&gt;{t-1}, a_{t-1}), a_t \sim \pi_\theta(a_t | s_t)} \left[ \nabla_\theta \log \pi_\theta(a_t|s_t) \right] \right] \
\end{aligned}
$$
由于
$$
\begin{aligned}
&amp;amp;\mathbb{E}&lt;em&gt;{s_t \sim p(s_t | s&lt;/em&gt;{t-1}, a_{t-1}), a_t \sim \pi_\theta(a_t | s_t)} \left[ \nabla_\theta \log \pi_\theta(a_t|s_t) \right] \
=&amp;amp; \iint_{s_t, a_t} p(s_t | s_{t-1}, a_{t-1}) \pi_\theta(a_t | s_t) \nabla_\theta \log \pi_\theta (a_t | s_t) ds_t da_t \
=&amp;amp; \iint_{s_t, a_t} p(s_t | s_{t-1}, a_{t-1}) \nabla_\theta \pi_\theta (a_t | s_t) ds_t da_t \
=&amp;amp; \nabla_\theta \iint_{s_t, a_t} p(s_t | s_{t-1}, a_{t-1}) \pi_\theta (a_t | s_t) ds_t da_t \
=&amp;amp; \nabla_\theta 1 \
=&amp;amp; 0
\end{aligned}
$$
逐级回代，可以得到最开始的第一个期望项结果实际上也就是 $0$，因此
$$
\nabla_\theta J(\theta) = \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \sum_{k=t}^{T} \gamma^{k}r_k \right]
$$
将 $t$ 时刻的的回报记作 $G_t$:
$$
G_t = \sum_{k=0}^{T} \gamma^{k}r_{k+t}
$$
从而得到简化的策略梯度形式，也即下面的策略梯度定理:&lt;/p&gt;
&lt;p&gt;&amp;lt;font size=3&amp;gt;&lt;strong&gt;Theorem (策略梯度定理)&lt;/strong&gt;&amp;lt;/font&amp;gt;&lt;/p&gt;
&lt;p&gt;$$
\boxed{
\nabla_\theta J(\theta) = \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \gamma^t G_t \right]
}
$$&lt;/p&gt;
&lt;h2&gt;6.2.4 策略梯度上升&lt;/h2&gt;
&lt;p&gt;拿到策略梯度后，我们就可以使用梯度上升法来更新策略参数 $\theta$，以最大化 $J(\theta)$。梯度上升的更新规则为
$$
\theta_{t+1} = \theta_t + \alpha \nabla_\theta J(\theta_t)
$$
其中$\alpha$是学习率，$J(\theta)$是目标函数。&lt;/p&gt;
&lt;h2&gt;6.2.5 两种策略梯度公式的比较&lt;/h2&gt;
&lt;p&gt;前面我们分析得到了两种策略梯度的公式
$$
\nabla_\theta J(\theta) = \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ R(\tau) \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \right]
$$
与
$$
\nabla_\theta J(\theta) = \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \gamma^t G_t \right]
$$
两个公式是等价的，且第二个公式是第一个的简化。但是在实际使用上，第二个公式使用范围更加广泛：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;学习模式对比&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;第一个公式只适用于轨迹 $\tau$ 已经完全确定的情况，也即 $R(\tau)$ 已经计算出的情况。换言之，第一个公式不能在每个时间步 $t$ 都更新参数。&lt;/li&gt;
&lt;li&gt;第二个公式能在每个时间步 $t$ 都更新参数，因为本身梯度可以看作是在每个时间步都做一次 $\theta \leftarrow \theta + \alpha \gamma^t G_t \nabla\log \pi_\theta(a_t\vert s_t)$，累计后完成整个梯度上升。这里期望下标虽是 $\tau$，但这只是简化的写法，实际上每个时间步 $t$ 用到的随机变量并没有完全覆盖 $\tau$ 中的所有变量。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;采样方差对比&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;第一个公式中所有时间步共享同一个整条轨迹的回报 $R(\tau)$，引入了大量与当前梯度无关的历史噪声和未来噪声，导致不同时间步梯度项之间强相关，方差较大。&lt;/li&gt;
&lt;li&gt;第二个公式中每个时间步使用从该步开始的累积回报 $G_t$，减少了无关变量的影响，且便于引入基线进一步降低方差，因此方差更小、估计更稳定。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6.2.6 含有基线的策略梯度&lt;/h2&gt;
&lt;p&gt;策略梯度公式中可以引入基线，在保持梯度无偏估计的同时，通过减去一个只依赖于状态的基线，降低回报 $G_t$ 的波动性，从而减小梯度估计的方差，使学习更加稳定高效。&lt;/p&gt;
&lt;p&gt;$$
\boxed{
\nabla_\theta J(\theta) = \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) \left(\gamma^t G_t - b(s_t)\right) \right]
}
$$
其中 $b(s_t)$ 是基线函数，基线函数只需要满足其值与动作无关的条件。通常选用状态价值函数 $V(s_t)$ 作为基线函数。&lt;/p&gt;
&lt;p&gt;下面验证含基线的策略梯度的无偏性。为了验证这一性质，我们只需要证明
$$
\mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) b(s_t) \right] = 0
$$
拆开条件期望
$$
\begin{aligned}
&amp;amp;\sum_{t=0}^{T-1} \mathbb{E}&lt;em&gt;{(s_0, a_0, \dots, s&lt;/em&gt;{t-1}, a_{t-1}) \sim \pi_\theta, s_t \sim p(s_t | s_{t-1}, a_{t-1}), a_t \sim \pi_\theta(a_t | s_t)} \left[ \nabla_\theta \log \pi_\theta(a_t|s_t) b(s_t) \right] \
=&amp;amp; \sum_{t=0}^{T-1} \mathbb{E}&lt;em&gt;{(s_0, a_0, \dots, s&lt;/em&gt;{t-1}, a_{t-1}) \sim \pi_\theta}\left[\mathbb{E}&lt;em&gt;{s_t \sim p(s_t | s&lt;/em&gt;{t-1}, a_{t-1})} \left[ b(s_t) \underbrace{\int_{a_t}  \pi_\theta(a_t | s_t) \nabla_\theta \log \pi_\theta(a_t|s_t) da_t}_{0} \right] \right] \
=&amp;amp; 0&lt;br /&gt;
\end{aligned}
$$
证毕。&lt;/p&gt;
&lt;h2&gt;6.2.7 REINFORCE 算法&lt;/h2&gt;
&lt;p&gt;REINFORCE 算法的本质是通过 Monte Carlo 方法来估计策略的梯度。它的基本思想是通过采样来计算每个时间步的回报，然后使用这些回报来更新策略参数，是一种分幕式的强化学习算法。&lt;/p&gt;
&lt;p&gt;REINFORCE 算法用到了将策略梯度按时间步 $t$ 分解的思路进行更新
$$
\theta \leftarrow \theta + \alpha \gamma^t G_t \nabla\log \pi_\theta(a_t\vert s_t)
$$
它的好处有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;在线更新&lt;/strong&gt;：不需要等到所有轨迹数据收集完毕再统一更新，可以在每个时间步立即更新参数，提高数据效率&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;方差降低&lt;/strong&gt;：每个时间步只使用从该步开始的回报$G_t$，而不是整条轨迹的总回报$R(\tau)$，减少了与当前动作无关的历史奖励带来的噪声&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;便于引入基线&lt;/strong&gt;：这种分解形式更容易引入状态值函数作为基线，进一步降低方差&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;REINFORCE 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;可微分参数化策略 $\pi_\theta(a \vert s)$，学习率 $\alpha &amp;gt; 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化策略参数 $\theta \in \mathbb{R}^d$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;  基于 $\pi_\theta(\cdot \vert \cdot)$ 生成一个序列 $s_0, a_0, r_0, ..., s_T, a_T, r_T$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;for&lt;/strong&gt; $t = 0, 1, ..., T$ &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;    $G \leftarrow \sum_{k=t}^{T} \gamma^{k-t}r_k$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;    $\theta \leftarrow \theta + \alpha \gamma^t G \nabla\log \pi_\theta(a_t\vert s_t)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;until&lt;/strong&gt; 收敛&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;python 伪代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def REINFORCE(env, policy, alpha, gamma, num_episodes):
    for episode in range(num_episodes):
        # 生成一个完整的轨迹
        states, actions, rewards = generate_episode(env, policy)
            
        # 计算每个时间步的回报
        G = 0
        for t in reversed(range(len(states))):
            G = rewards[t] + gamma * G
                
            # 更新策略参数
            policy.update(states[t], actions[t], alpha * G, gamma)
    
    return policy
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6.2.8 Actor-Critic&lt;/h2&gt;
&lt;p&gt;Actor-Critic (演员-评论家，AC 方法) 方法结合了策略梯度 (Actor) 和值函数近似 (Critic) 的优势，通过引入值函数作为基准 (baseline) 来降低策略梯度的方差，从而提升学习效率。其核心思想是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Actor&lt;/strong&gt;：负责根据当前策略 $\pi_\theta(a|s)$ 选择动作，并通过策略梯度更新策略参数 $\theta$；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Critic&lt;/strong&gt;：负责估计状态值函数 $V_w(s)$ 或动作值函数 $Q_w(s,a)$，并通过TD误差等目标优化值函数参数 $w$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AC 方法使用了含基线的策略梯度定理。其策略梯度可表示为：
$$
\nabla_\theta J(\theta) = \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{T-1} \nabla_\theta \log \pi_\theta(a_t|s_t) A(s_t, a_t) \right]
$$
其中 &lt;strong&gt;优势函数&lt;/strong&gt; $A(s_t, a_t) = Q(s_t, a_t) - V(s_t)$ 表示在状态 $s_t$ 下选择动作 $a_t$ 的相对优势。Critic 的任务即为估计 $A(s_t, a_t)$，常见实现方式包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;TD误差法&lt;/strong&gt;：用 $\delta_t = r_{t+1} + \gamma V_w(s_{t+1}) - V_w(s_t)$ 作为优势函数的无偏估计；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;广义优势估计（GAE）&lt;/strong&gt;：结合多步TD误差的加权平均，平衡偏差与方差。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;font size=3&amp;gt;&lt;strong&gt;Actor-Critic 算法&lt;/strong&gt;&amp;lt;/font&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;可微分策略 $\pi_\theta(a\vert s)$，值函数 $V_w(s)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;策略学习率 $\alpha_\theta$，值函数学习率 $\alpha_w$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化参数 $\theta$ 和 $w$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;  从环境中采样轨迹 $\tau = (s_0, a_0, r_1, s_1, ..., s_{T-1}, a_{T-1}, r_T)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;for&lt;/strong&gt; $t = 0, 1, ..., T-1$ &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;    计算TD误差：$\delta_t = r_{t+1} + \gamma V_w(s_{t+1}) - V_w(s_t)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;    更新Actor参数：$\theta \leftarrow \theta + \alpha_\theta \nabla_\theta \log \pi_\theta(a_t \vert s_t) \cdot \delta_t$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;    更新Critic参数：$w \leftarrow w + \alpha_w \delta_t \nabla_w V_w(s_t)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;until&lt;/strong&gt; 策略收敛&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;关键改进与变体&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 异步优势Actor-Critic（A3C）&lt;/strong&gt;
多个并行的Actor线程同时与环境交互，异步计算梯度并更新全局参数，打破了样本相关性，提升训练效率和稳定性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 优势Actor-Critic（A2C）&lt;/strong&gt;
A3C的同步版本，等待所有并行Actor完成采样后统一计算平均梯度再更新，实现更简单，训练更稳定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 确定性策略梯度（DPG）&lt;/strong&gt;
适用于连续动作空间，Actor输出确定性动作而非动作概率分布，通过链式法则计算梯度，常结合经验回放和目标网络形成 DDPG 算法。&lt;/p&gt;
&lt;h1&gt;6.3 策略改进方法&lt;/h1&gt;
&lt;h2&gt;6.3.1 步长选择的难题&lt;/h2&gt;
&lt;p&gt;前述的经典策略梯度算法 (如 REINFORCE，Actor-Critic) 的缺点是难以确定合适的步长。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果步长过大，策略的更新可能会导致直接跨越过最优点，产生较大的策略变动。而这种巨大的策略变动则有可能导致策略本身性能的退化；&lt;/li&gt;
&lt;li&gt;如果步长过小，更新会非常缓慢。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，我们需要找到能够合适的步长来更新策略参数。假设我们已经有了一个还不错的旧策略 $\pi_{\theta_0}$，我们希望利用策略梯度方法得到一个更好的新策略 $\pi_\theta$，即
$$
J(\theta) \ge J(\theta_0)
$$
现在的目标就是该如何基于旧策略 $\pi_{\theta_0}$ 改进得到新策略 $\pi_\theta$。&lt;/p&gt;
&lt;h2&gt;6.3.2 策略差异恒等式&lt;/h2&gt;
&lt;p&gt;下面推导量化两个策略的目标函数差异的两个恒等式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;恒等式一&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于给定的两个策略 $\pi_{\theta_0}$ 和 $\pi_\theta$, 存在如下恒等式
$$
\boxed{
J(\theta) = J(\theta_0) + \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{\infty} \gamma^t A_{\pi_{\theta_0}}(s_t, a_t) \right]
}
$$
其中，$\tau = (s_0, a_0, s_1, a_1, \dots)$ 是采样自策略 $\pi_\theta$ 轨迹，$A_{\pi_{\theta_0}}(s_t, a_t)$ 是策略 $\pi_{\theta_0}$ 对应的优势函数。&lt;/p&gt;
&lt;p&gt;下面给出这个恒等式的证明，注意这里的证明利用到了初始状态 $s_0$ 的概率分布与策略本身无关，是两个策略目标函数所共有的概率分布:&lt;/p&gt;
&lt;p&gt;展开
$$
A_{\pi_{\theta_0}}(s, a) =  Q_{\pi_{\theta_0}}(s, a) - V_{\pi_{\theta_0}}(s) = \mathbb{E}&lt;em&gt;{s&apos; \sim P(s&apos; |s, a)}\left[r(s, a) + \gamma V&lt;/em&gt;{\pi_{\theta_0}}(s&apos;) - V_{\pi_{\theta_0}}(s) \right]
$$
有
$$
\begin{aligned}
\mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{\infty} \gamma^t A_{\pi_{\theta_0}}(s_t, a_t) \right] &amp;amp;= \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^\infty \gamma^t [r(s_t, a_t) + \gamma V_{\pi_{\theta_0}}(s_{t+1}) - V_{\pi_{\theta_0}}(s_t)] \right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^\infty \gamma^t r(s_t, a_t)  - V_{\pi_{\theta_0}}(s_0) \right] \
&amp;amp;= \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^\infty \gamma^t r(s_t, a_t)\right] - \mathbb{E}&lt;em&gt;{s_0} \left[ V&lt;/em&gt;{\pi_{\theta_0}}(s_0) \right] \
&amp;amp;= J(\theta) - J(\theta_0)
\end{aligned}
$$
恒等式证毕。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;恒等式二&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于给定的两个策略 $\pi_{\theta_0}$ 和 $\pi_\theta$, 存在如下恒等式
$$
\boxed{
J(\theta) = J(\theta_0) + \sum_s \rho_{\pi_\theta}(s) \sum_a \pi_\theta(a|s) A_{\pi_{\theta_0}}(s, a)
}
$$
其中:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\rho_{\pi_\theta}(s) = p(s_0 = s) + \gamma p(s_1 = s | \pi_\theta) + \gamma^2 p(s_2 = s | \pi_\theta) + \cdots$ 是状态 $s$ 在策略 $\pi_\theta$ 下的&lt;strong&gt;折扣状态访问分布&lt;/strong&gt;，反映了从初始状态分布出发，按照策略 $\pi_\theta$ 与环境交互，在整个无限长轨迹中，状态 $s$ 出现的&quot;折扣期望次数&quot;；&lt;/li&gt;
&lt;li&gt;$A_{\pi_{\theta_0}}(s_t, a_t)$ 是策略 $\pi_{\theta_0}$ 对应的优势函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面基于恒等式一来证明恒等式二：
$$
\begin{align*}
J(\theta) &amp;amp;= J(\theta_0) + \mathbb{E}&lt;em&gt;{\tau \sim \pi&lt;/em&gt;\theta} \left[ \sum_{t=0}^{\infty} \gamma^t A_{\pi_{\theta_0}}(s_t, a_t) \right] \
&amp;amp;= J(\theta_0) + \mathbb{E}&lt;em&gt;{s_0 \sim p_0(s_0), a_0 \sim \pi&lt;/em&gt;\theta(a_0| s_0), s_1 \sim p(s_1|s_0, a_0), a_1 \sim \pi_\theta(a_1 | s_1), \dots} \left[ \sum_{t=0}^{\infty} \gamma^t A_{\pi_{\theta_0}}(s_t, a_t)\right] \
&amp;amp;= J(\theta_0) + \sum_{t=0}^{\infty} \sum_{s_0, s_1, s_2, \dots, s_t} \sum_{a_0, a_1, \dots, a_t}\gamma^t A_{\pi_{\theta_0}}(s_t, a_t) p(s_t, a_t, s_{t-1}, a_{t-1}, s_{t-2}, a_{t-2}, \dots, s_0, a_0) \
&amp;amp;= J(\theta_0) + \sum_{t=0}^{\infty} \sum_{s_0, s_1, s_2, \dots, s_t} \sum_{a_0, a_1, \dots, a_t}\gamma^t A_{\pi_{\theta_0}}(s_t, a_t) \left[p(s_0) \prod_{k=0}^{t-1}\pi_\theta(a_k|s_k) P(s_{k+1} | s_k, a_k) \pi_\theta(a_t | s_t)\right]\
&amp;amp;= J(\theta_0) + \sum_{t=0}^\infty \gamma^t \sum_{s_0} p(s_0) \sum_{a_0} \pi_\theta(a_0 | s_0)\sum_{s_1}p(s_1|s_0, a_0) \dots \sum_{s_t}p(s_t|s_{t-1}, a_{t-1})\sum_{a_t} \pi_\theta(a_t|s_t) A_{\pi_{\theta_0}}(s_t, a_t)\
&amp;amp;= J(\theta_0) + \sum_{t=0}^\infty \gamma^t \sum_{s_t} \underbrace{\left[ \sum_{s_0, \dots, s_{t-1}} p(s_0) \prod_{k=0}^{t-1} \pi_\theta(a_k |s_k) p(s_{k+1} | s_k, a_k)\right]}&lt;em&gt;{p(s_t = s | \pi&lt;/em&gt;\theta)} \sum_{a_t} \pi_\theta(a_t | s_t) A_{\pi_{\theta_0}}(s_t, a_t)\
&amp;amp;= J(\theta_0) + \sum_{t=0}^{\infty}\gamma^t \sum_{s} p(s_t = s | \pi_\theta) \sum_{a} \pi_\theta(a|s) A_{\pi_{\theta_0}}(s, a) \
&amp;amp;= J(\theta_0) + \sum_s \rho_{\pi_\theta}(s) \sum_a \pi_\theta(a|s) A_{\pi_{\theta_0}}(s, a)
\end{align*}
$$&lt;/p&gt;
&lt;p&gt;其中，$p(s_t = s | \pi_\theta)$ 是在策略 $\pi_\theta$ 下，在第 $t$ 时刻状态 ${s_t = s}$ 的概率。证毕。&lt;/p&gt;
&lt;h2&gt;6.3.3 TRPO 方法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;TRPO 方法的原始论文可以点此访问: &lt;a href=&quot;https://arxiv.org/abs/1502.05477&quot;&gt;TRPO&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;利用前面恒等式二的结果，只要满足
$$
\sum_s \rho_{\pi_\theta}(s) \sum_a \pi_\theta(a|s) A_{\pi_{\theta_0}}(s,a) &amp;gt; 0, \ \forall s
$$
就可以保证策略 $\pi_\theta$ 比策略 $\pi_{\theta_0}$ 更好。&lt;/p&gt;
&lt;h3&gt;6.3.3.1 重要性采样&lt;/h3&gt;
&lt;p&gt;事实上，这个期望并不好计算
$$
\mathbb{E}&lt;em&gt;{s \sim \rho&lt;/em&gt;{\pi_\theta}, a \sim \pi_\theta(a|s)} \left[ A_{\pi_{\theta_0}}(s, a)\right] = \sum_s \rho_{\pi_\theta}(s) \sum_a \pi_\theta(a|s) A_{\pi_{\theta_0}}(s, a)
$$
这是因为我们只有策略 $\pi_{\theta_0}$ ，而没有策略 $\pi_\theta$，所以我们不可能直接得到 $\rho_{\pi_\theta}(s)$。为了转换问题，我们先推导一个重要性采样定理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Theorem 重要性采样定理&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个难以采样的概率分布 $p(x)$, 可以通过利用一个容易采样的分布 $q(x)$ 来近似 $p(x)$ 的采样，利用
$$
\mathbb{E}&lt;em&gt;{x \sim p(x)}[f(x)] = \mathbb{E}&lt;/em&gt;{x \sim q(x)}\left[\frac{p(x)}{q(x)} f(x)\right]
$$&lt;/p&gt;
&lt;p&gt;证明：
$$
\begin{aligned}
\mathbb{E}&lt;em&gt;{x \sim p(x)}[f(x)] &amp;amp;= \int f(x) p(x) dx \
&amp;amp;= \int f(x) \frac{p(x)}{q(x)} q(x) dx \
&amp;amp;= \int f(x) q(x) dx \cdot \frac{p(x)}{q(x)}\
&amp;amp;= \mathbb{E}&lt;/em&gt;{x \sim q(x)}\left[\frac{p(x)}{q(x)} f(x)\right]
\end{aligned}
$$
证毕。&lt;/p&gt;
&lt;p&gt;利用重要性采样定理，可以用从分布 $\pi_{\theta_0}$ 中采样的方式得到关于分布 $\pi_\theta$ 的期望值:
$$
\mathbb{E}&lt;em&gt;{s \sim \rho&lt;/em&gt;{\pi_\theta}(s), a \sim \pi_\theta(a|s)} \left[ A_{\pi_{\theta_0}}(s, a)\right] = \mathbb{E}&lt;em&gt;{s \sim \rho&lt;/em&gt;{\pi_{\theta_0}}(s), a \sim \pi_{\theta_0}(a|s)}\left[\frac{\pi_\theta(a|s)}{\pi_{\theta_0}(a|s)} A_{\pi_{\theta_0}}(s, a)\right]
$$&lt;/p&gt;
&lt;p&gt;于是，利用重要性采样，且考虑到&lt;strong&gt;折扣状态访问分布变化不大&lt;/strong&gt;，即 $\rho_{\pi_\theta}(s) \approx \rho_{\pi_{\theta_0}}(s)$， 有
$$
\mathbb{E}&lt;em&gt;{s \sim \rho&lt;/em&gt;{\pi_{\theta_0}}(s), a \sim \pi_{\theta_0}(a|s)}\left[\frac{\rho_{\pi_\theta}(s)}{\rho_{\pi_{\theta_0}}(s)} \cdot \frac{\pi_\theta(a|s)}{\pi_{\theta_0}(a|s)} A_{\pi_{\theta_0}}(s, a)\right] \approx \mathbb{E}&lt;em&gt;{s \sim \rho&lt;/em&gt;{\pi_{\theta_0}}(s), a \sim \pi_{\theta_0}(a|s)}\left[ \frac{\pi_\theta(a|s)}{\pi_{\theta_0}(a|s)} A_{\pi_{\theta_0}}(s, a)\right]
$$&lt;/p&gt;
&lt;p&gt;这个式子正是 TRPO 的核心公式，由此，
$$
\begin{aligned}
\max_{\theta} J(\theta) &amp;amp;= J(\theta_0) + \max_{\theta}\mathbb{E}&lt;em&gt;{s \sim \rho&lt;/em&gt;{\pi_{\theta_0}}(s), a \sim \pi_{\theta_0}(a|s)}\left[\frac{\pi_\theta(a|s)}{\pi_{\theta_0}(a|s)} A_{\pi_{\theta_0}}(s, a)\right] \
&amp;amp;= J(\theta_0) + \max_{\theta}\sum_s \rho_{\pi_{\theta_0}}(s) \sum_a \pi_\theta(a|s) A_{\pi_{\theta_0}}(s, a) \
\end{aligned}
$$
&lt;strong&gt;注意第二个式子中虽然看着很像没有重要性采样，但是注意下标，实际上是暗含了重要性采样的。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;6.3.3.2 TRPO 方法的数学模型&lt;/h3&gt;
&lt;p&gt;为了满足&lt;strong&gt;折扣状态访问分布变化不大&lt;/strong&gt;的性质
$$
\rho_{\pi_\theta}(s) \approx \rho_{\pi_{\theta_0}}(s)
$$
需要将更新范围限定在一个确定的邻域内。&lt;strong&gt;Trust Region Policy Optimization (TRPO)&lt;/strong&gt; 的想法就在于此，通过约束策略更新的步长，确保新策略的性能单调提升。&lt;/p&gt;
&lt;p&gt;设 $\mathcal{N}(\theta_0) = {\theta | \Vert \theta - \theta_0 \Vert \le \Delta}$ 为 $\theta_0$ 的一个邻域。若存在一个函数 $L(\theta | \theta_0)$ 可以在邻域 $\mathcal{N}(\theta_0)$ 内近似目标函数 $J(\theta)$ ，则可以通过最大化 $L(\theta | \theta_0)$ 来更新策略参数，此时 $L(\theta | \theta_0)$ 被称作&lt;strong&gt;置信域&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;于是，优化目标为
$$
\boxed{
\begin{aligned}
&amp;amp;\max_\theta \mathbb{E}&lt;em&gt;{s \sim \rho&lt;/em&gt;{\theta_0}, a \sim \pi_{\theta_0}} \left[ \frac{\pi_\theta(a|s)}{\pi_{\theta_0}(a|s)} A_{\theta_0}(s,a) \right] \
\text{s.t. } &amp;amp;\mathbb{E}&lt;em&gt;{s \sim \rho&lt;/em&gt;{\theta_0}} \left[ D_{\text{KL}}(\pi_{\theta_0}(\cdot|s) | \pi_\theta(\cdot|s)) \right] \leq \delta
\end{aligned}
}
$$&lt;/p&gt;
&lt;p&gt;这里采用了&lt;strong&gt;Kullback-Leibler散度&lt;/strong&gt; (KL散度) 来度量新旧策略之间的差异，实际上是从分布变化的角度来衡量参数的变化。TRPO通过限制KL散度来控制策略更新的幅度，从而避免过大的策略变动。
其中，目标函数为
$$
J(\theta) = \mathbb{E}&lt;em&gt;{s \sim \rho&lt;/em&gt;{\theta_0}, a \sim \pi_{\theta_0}} \left[ \frac{\pi_\theta(a|s)}{\pi_{\theta_0}(a|s)} A_{\theta_0}(s,a) \right]
$$
目标函数的近似函数则为：
$$
L(\theta | \theta_0) = \frac{1}{n} \sum_{i=1}^{n}\left[ \frac{\pi_\theta(a_i|s_i)}{\pi_{\theta_0}(a_i|s_i)} A_{\theta_0}(s_i,a_i) \right]
$$&lt;/p&gt;
&lt;h3&gt;6.2.4 PPO 方法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;PPO 方法的原始论文可以点此访问: &lt;a href=&quot;https://arxiv.org/abs/1707.06347&quot;&gt;PPO&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TRPO 通过KL散度约束实现了策略更新的稳定性，但其具有如下几个局限性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;计算复杂度高&lt;/strong&gt;，因为需显式计算KL散度的二阶近似 (涉及 Fisher 矩阵)，难以扩展到超大规模网络。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实现复杂&lt;/strong&gt;，毕竟共轭梯度法需要定制化实现，与自动微分框架兼容性差。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;超参数敏感&lt;/strong&gt;，这是因为KL约束阈值 $\delta$ 需精细调节，否则易导致更新过保守或不稳定。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Proximal Policy Optimization (PPO)&lt;/strong&gt; 提出了一种更简洁的约束方式，通过&lt;strong&gt;裁剪目标函数 (Clipped Objective)&lt;/strong&gt; 或&lt;strong&gt;自适应 KL 惩罚 (Adaptive KL Penalty)&lt;/strong&gt;，在保证性能的前提下显著降低实现难度。&lt;/p&gt;
&lt;p&gt;PPO 针对以上 TRPO 的各个问题做了如下的改进。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;隐式约束&lt;/strong&gt;上，通过目标函数的裁剪或自适应惩罚替代显式KL约束。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;兼容性&lt;/strong&gt;上，直接使用一阶优化方法 (如 Adam)，与深度学习框架无缝集成。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高效性&lt;/strong&gt;上，无需计算二阶矩阵，适合大规模分布式训练。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PPO 主要有两种形式，分别是 &lt;strong&gt;PPO-Clip&lt;/strong&gt; 和 &lt;strong&gt;PPO-KL&lt;/strong&gt;。其区别在于对策略更新的约束方式。PPO-Clip 通过裁剪目标函数来限制策略更新幅度，而 PPO-KL 则通过自适应 KL 惩罚来实现类似效果。两者在性能上相近，但实现复杂度和超参数调节上有所不同。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;形式一：PPO-Clip (裁剪目标函数)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;目标函数为
$$
L^{\text{CLIP}}(\theta) = \mathbb{E}&lt;em&gt;{s \sim \rho&lt;/em&gt;{\theta_0}, a \sim \pi_{\theta_0}} \left[ \min\left( r_t(\theta) A_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right]
$$
其中，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;重要性采样比率&lt;/strong&gt;： $r_t(\theta) = \frac{\pi_\theta(a|s)}{\pi_{\theta_0}(a|s)}$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优势函数&lt;/strong&gt;： $A_t = A_{\pi_{\theta_0}}(s,a)$ ，通常用GAE（Generalized Advantage Estimation）估计。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;裁剪阈值&lt;/strong&gt;： $\epsilon$ （如0.1或0.2），限制  $r_t(\theta)$  的偏离幅度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里，&lt;strong&gt;裁剪机制的作用&lt;/strong&gt;是限制策略更新幅度，避免过度优化。具体来说，当&lt;strong&gt;优势为正$A_t &amp;gt; 0$&lt;strong&gt;时，鼓励动作概率提升，但限制  $r_t(\theta) \leq 1+\epsilon$ ，避免过度优化。而当&lt;/strong&gt;优势为负$A_t &amp;lt; 0$&lt;/strong&gt;，抑制动作概率下降，但限制  $r_t(\theta) \geq 1-\epsilon$ ，避免策略退化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;形式二：PPO-KL (自适应KL惩罚)&lt;/strong&gt;
它的目标函数是
$$
L^{\text{KL}}(\theta) = \mathbb{E}&lt;em&gt;{s \sim \rho&lt;/em&gt;{\theta_0}, a \sim \pi_{\theta_0}} \left[ r_t(\theta) A_t - \beta \cdot D_{\text{KL}}(\pi_{\theta_0} | \pi_\theta) \right]
$$
其中，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自适应系数 $\beta$&lt;/strong&gt;：
&lt;ol&gt;
&lt;li&gt;若当前平均KL散度  $\bar{D}&lt;em&gt;{\text{KL}} &amp;gt; \delta&lt;/em&gt;{\text{target}}$ ，增大 $\beta$（如乘1.5）。&lt;/li&gt;
&lt;li&gt;若  $\bar{D}&lt;em&gt;{\text{KL}} &amp;lt; \delta&lt;/em&gt;{\text{target}}$ ，减小 $\beta$（如乘0.5）。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标阈值&lt;/strong&gt;： $\delta_{\text{target}}$ （如0.01）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=5&amp;gt;PPO的优化目标与实现，以PPO-Clip为例&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 完整优化问题&lt;/strong&gt;
PPO-Clip的优化目标可写为：
$$
\max_\theta , \mathbb{E}&lt;em&gt;{s,a \sim \pi&lt;/em&gt;{\theta_0}} \left[ \min\left( r_t(\theta) A_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right]
$$
无显式约束，通过目标函数设计隐式限制策略更新幅度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 优势函数估计&lt;/strong&gt;
使用 &lt;strong&gt;GAE (Generalized Advantage Estimation)&lt;/strong&gt; 平衡偏差与方差：
$$
A_t^{\text{GAE}(\gamma,\lambda)} = \sum_{k=0}^{T-t} (\gamma \lambda)^k \delta_{t+k}
$$
其中  $\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)$ ，$\lambda \in [0,1]$ 控制方差-偏差权衡。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 实现步骤&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;数据收集：用旧策略 $\pi_{\theta_0}$ 与环境交互，收集轨迹数据。&lt;/li&gt;
&lt;li&gt;优势计算：基于当前价值函数 $V_\phi$ 计算GAE优势 $A_t$。&lt;/li&gt;
&lt;li&gt;目标优化：对目标函数 $L^{\text{CLIP}}$ 或 $L^{\text{KL}}$ 执行多轮小批量梯度上升。&lt;/li&gt;
&lt;li&gt;策略同步：定期将当前策略参数 $theta$ 复制给 $\theta_0$。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;PPO与TRPO的对比&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;特性&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TRPO&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;PPO&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;约束方式&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;显式KL散度约束&lt;/td&gt;
&lt;td&gt;裁剪目标函数或自适应KL惩罚&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;计算复杂度&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;高（需二阶矩阵运算）&lt;/td&gt;
&lt;td&gt;低（仅一阶优化）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;实现难度&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;复杂（共轭梯度法）&lt;/td&gt;
&lt;td&gt;简单（兼容标准优化器）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;超参数敏感性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;高（依赖 $\delta$ 选择）&lt;/td&gt;
&lt;td&gt;低（$\epsilon$ 或 $\beta$ 易调节）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;适用场景&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;理论验证或高精度控制&lt;/td&gt;
&lt;td&gt;大规模训练（如游戏、机器人）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;两种形式的 PPO 对比&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;裁剪目标的启发式设计: PPO-Clip的裁剪机制虽缺乏严格理论保证，但实验表明其效果与TRPO相当。直观解释为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;概率比偏离限制：强制策略更新在信任区域内（ $r_t(\theta) \in [1-\epsilon, 1+\epsilon]$ ）。&lt;/li&gt;
&lt;li&gt;保守更新：对优势显著的动作进行“截断”，防止过拟合。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;自适应KL惩罚的退化问题: PPO-KL虽更接近TRPO理论，但实际中易因KL散度振荡导致训练不稳定。因此，PPO-Clip成为主流实现。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>强化学习 Chapter 5 - 基于价值的深度强化学习</title><link>https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter5/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter5/</guid><pubDate>Sat, 19 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;5.1 价值函数近似&lt;/h1&gt;
&lt;h2&gt;5.1.1 问题背景&lt;/h2&gt;
&lt;p&gt;对于状态空间与动作空间均规模较小且离散的有限型问题，我们可以创建一个查询表，在表中维护价值函数，从而用于求解问题。这类问题也被称作&lt;strong&gt;表格型问题&lt;/strong&gt;。但是，当问题规模变大，或者说成为连续型问题时，维护表格的方法就不合适了。例如，下围棋和自动驾驶汽车，一个是超大的状态空间，一个是连续的状态空间，显然不适合使用表格处理。&lt;/p&gt;
&lt;p&gt;一个可行的方法是使用参数化的方式来近似价值函数 $V_\theta(s), Q_\theta(s, a)$，其中 $\theta$ 是可学习的参数，用强化学习的方法进行更新。显然，可以使用深度神经网络的方法作为价值函数的近似。这个想法解决了状态空间或动作空间规模过大的问题，适用于&lt;strong&gt;离散型或连续型问题&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;对于连续型问题，状态或动作可以用特征向量表示 $\mathbf{s} = [s_1, s_2, \dots]^\top, \mathbf{a} = [a_1, a_2, \dots]^\top $。&lt;/p&gt;
&lt;h2&gt;5.1.2 梯度下降法更新参数实现策略评估&lt;/h2&gt;
&lt;p&gt;类似深度学习，可以使用随机梯度下降法 (SGD) 来更新价值函数。目标是找到能最小化损失函数的参数 $\theta$
$$
\min_\theta J(\theta) = \mathbb{E}&lt;em&gt;\pi\left[\frac{1}{2}(V&lt;/em&gt;\pi(s) - V_\theta(s))^2 \right]
$$
更新公式为
$$
\theta \leftarrow \theta - \alpha \frac{\partial J(\theta)}{\partial \theta} = \theta + \alpha \mathbb{E}&lt;em&gt;\pi\left[(V&lt;/em&gt;\pi(s) - V_\theta(s)) \frac{\partial V_\theta(s)}{\partial \theta}\right]
$$&lt;/p&gt;
&lt;p&gt;具体的更新方式类似深度学习，用数据样本去预测全局真实梯度，进行更新。&lt;/p&gt;
&lt;p&gt;$$
\theta \leftarrow \theta - \alpha \frac{\partial \hat{J}(\theta)}{\partial \theta} = \theta + \alpha \frac{1}{N}\sum_{i=1}^N(V_\pi(s_i) - V_\theta(s_i)) \frac{\partial V_\theta(s_i)}{\partial \theta}
$$&lt;/p&gt;
&lt;p&gt;但是，强化学习中，$V_\pi(s)$ 是未知的，因此需要使用 MC 方法或 TD 方法来估计 $V_\pi(s)$。实际上，类似策略评价算法，这里真实的 $V_\pi(s)$ 就用 $r + \gamma V_\theta(s&apos;)$ (TD) 或 $G_t$ (MC) 替代了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TD 更新
$$
\theta \leftarrow \theta + \alpha \left(r + \gamma V_\theta(s&apos;) - V_\theta(s)\right) \frac{\partial V_\theta(s)}{\partial \theta}
$$&lt;/li&gt;
&lt;li&gt;MC 更新
$$
\theta \leftarrow \theta + \alpha \left(r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \cdots - V_\theta(s)\right) \frac{\partial V_\theta(s)}{\partial \theta}
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;类似地，有动作价值函数的近似和更新（以 SARSA 为例）
$$
Q_\theta(s, a) \leftarrow Q_\theta(s, a) + \alpha \left(r + \gamma Q_\theta(s&apos;, a&apos;) - Q_\theta(s, a)\right) \frac{\partial Q_\theta(s, a)}{\partial \theta}
$$&lt;/p&gt;
&lt;p&gt;可以看出，这里是使用了深度学习的方式来近似价值函数，但是策略的更新仍然是前面的方法。例如，使用贪心策略，可以不用显式地维护一个策略函数，直接用动作价值函数即可实现智能体的控制等任务。当然也可以用神经网络去拟合一个策略函数 $\pi_\theta(a|s)$，然后使用策略梯度方法来更新参数，这在后面会介绍。&lt;/p&gt;
&lt;p&gt;需要注意的一点是，&lt;strong&gt;参数近似方法和表格型方法的一大不同在于，在表格型方法中，更新了某个状态的价值函数是不会影响和改变其他状态的价值函数的，而在参数近似方法中，更新了某个状态的价值函数会影响到其他状态的价值函数，因为参数是共享的。&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;5.2 深度 Q 网络 (DQN)&lt;/h1&gt;
&lt;p&gt;深度 Q 网络 (DQN) 是一种基于深度学习的强化学习方法，使用深度神经网络来近似动作价值函数 $Q(s, a)$。DQN 的基本思想是使用深度神经网络来处理高维状态空间，并通过经验回放和目标网络等技术来提高训练的稳定性和效率。&lt;/p&gt;
&lt;p&gt;本质的强化学习方法还是 Q 学习，只是用深度神经网络来近似动作价值函数 $Q(s, a)$。&lt;/p&gt;
&lt;h2&gt;5.2.1 经验回放 (Experience Replay)&lt;/h2&gt;
&lt;p&gt;经验回放是 DQN 中的一个重要技术，它通过将智能体在环境中经历的状态、动作、奖励和下一个状态存储在一个经验池中，然后从中随机抽取小批量的经验进行训练。这样做的好处是可以&lt;strong&gt;打破数据之间的相关性&lt;/strong&gt;，提高训练的稳定性和效率。&lt;/p&gt;
&lt;p&gt;打破数据的相关性是因为，强化学习中，数据是时序相关的，$s_t, a_t$ 与 $s_{t+1}, a_{t+1}, s_{t+2}, a_{t+2}$ 有关，直接使用会导致数据之间的相关性很强，导致训练不稳定。经验回放可以将数据存储在一个经验池中，然后&lt;strong&gt;随机抽取&lt;/strong&gt;小批量的数据进行训练，这样就可以打破数据之间的相关性。&lt;/p&gt;
&lt;p&gt;经验回放池（replay buffer）中存放的是一个个的状态转移信息例如 $(s_t, a_t, r_t, s_{t+1})$，具体实现方式是使用一个 FIFO 队列来存储经验，当经验池满了之后，新的经验会覆盖最旧的经验。通常经验池的大小设置为 $10^5 \sim 10^6$。&lt;/p&gt;
&lt;p&gt;在训练时，从经验池中随机抽取小批量的经验进行训练，例如 $32$ 个经验来训练 Q 网络。&lt;/p&gt;
&lt;h3&gt;实现经验回放池&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class ReplayBuffer:
    def __init__(self, capacity):
        self.buffer = collections.deque(maxlen=capacity)

    def add(self, state, action, reward, next_state, done):
        self.buffer.append((state, action, reward, next_state, done))

    def sample(self, batch_size):
        transitions = random.sample(self.buffer, batch_size)
        state, action, reward, next_state, done = zip(*transitions) # 注意是有batch的，不可以直接展开
        return np.array(state), action, reward, np.array(next_state), done
    
    def size(self):
        return len(self.buffer)
    
    def clear(self):
        self.buffer.clear()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;优先经验回放&lt;/h3&gt;
&lt;p&gt;并非所有的经验都同样重要，可以用 TD 误差来衡量重要性。&lt;br /&gt;
$$
p_t = |\delta_t| = |r_t + \gamma \max_{a&apos;} Q(s_{t+1}, a&apos;) - Q(s_t, a_t) |
$$
然后用概率采样的方式来选择经验进行训练。&lt;/p&gt;
&lt;h2&gt;5.2.2 目标网络&lt;/h2&gt;
&lt;p&gt;Q-Learning类算法大部分都存最大化偏差问题。偏差的存在并不是一定是有害的，如果偏差是均匀的，即对所有的状态-动作的估计都有相同的偏差，那么就相当于给价值函数加上了常数，并不会影响策略选择动作。但是，通常偏差并不是均匀的，这对学习而言是有影响的。&lt;/p&gt;
&lt;p&gt;可以使用&lt;strong&gt;目标网络&lt;/strong&gt;来解决这个问题。目标网络是一个与 Q 网络结构相同的神经网络，但是参数是固定的，通常每隔 $C$ 步更新一次，更新的方式就是把 Q 网络的参数复制到目标网络中。因为维护了两个 Q 网络，因此叫 Double-DQN。设目标网络的参数为 $\theta^-$, Q 网络的参数为 $\theta$，则每隔 $C$ 步更新一次目标网络的参数为
$$
\begin{align*}
\theta^- \leftarrow \theta \text{ \quad (直接覆盖)} \
\text{或者} \quad \theta^- \leftarrow \tau \theta + (1 - \tau) \theta^- \text{ \quad (软更新)} \
\end{align*}
$$&lt;/p&gt;
&lt;p&gt;此外，&lt;strong&gt;在每个时间步中，使用 Q 网络来控制智能体和收集经验，但是使用目标网络来计算 TD 目标，更新 Q 网络的参数。&lt;/strong&gt;
$$
\begin{align*}
y_t &amp;amp;= r_t + \gamma \max_{a&apos;} Q_{\theta^-}(s_{t+1}, a&apos;) \
\theta &amp;amp;\leftarrow \theta + \alpha \left(y_t - Q_\theta(s_t, a_t)\right) \frac{\partial Q_\theta(s_t, a_t)}{\partial \theta} \
\text{若} &amp;amp;\text{更新目标网络，则} \quad \theta^- \leftarrow \theta
\end{align*}
$$&lt;/p&gt;
&lt;h2&gt;5.2.3 Double DQN&lt;/h2&gt;
&lt;p&gt;类似使用目标网络的 DQN，不过这里的更新公式有些变化
$$
\begin{align*}
y_t &amp;amp;= r_t + \gamma Q_{\theta^-}(s_{t+1}, \arg\max_{a&apos;} Q_{\theta}(s_{t+1}, a&apos;)) \
\theta &amp;amp;\leftarrow \theta + \alpha \left(y_t - Q_\theta(s_t, a_t)\right) \frac{\partial Q_\theta(s_t, a_t)}{\partial \theta} \
\text{若} &amp;amp;\text{更新目标网络，则} \quad \theta^- \leftarrow \theta
\end{align*}
$$&lt;/p&gt;
&lt;p&gt;具体算法流程为&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;收集数据，使用 Q 网络来控制智能体，存储经验到经验池中。&lt;/li&gt;
&lt;li&gt;采样经验池中的小批量数据&lt;/li&gt;
&lt;li&gt;更新网络，计算 TD 目标，使用目标网络来计算 TD 目标，更新 Q 网络的参数。&lt;/li&gt;
&lt;li&gt;每隔 $C$ 步更新目标网络的参数。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;使用目标网络的方法可以有效地减少最大化偏差问题，提高训练的稳定性和效率，但是仍然不能完全消除最大化偏差问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Double DQN 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;经验池容量 $N$，小批量大小 $B$，折扣因子 $\gamma$，学习率 $\alpha$，目标网络更新频率 $C$，探索率 $\epsilon$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;训练好的 Q 网络参数 $\theta$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化：评估网络参数 $\theta$、目标网络参数 $\theta^-=\theta$、经验池 $\mathcal{D}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;对每个回合循环:&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;  初始化状态 $s_0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;对每个时间步 $t$ 循环:&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;    以概率 $\epsilon$ 随机选择动作 $a_t$，否则 $a_t = \arg\max_a Q_\theta(s_t, a)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;    执行动作 $a_t$，观察奖励 $r_t$ 和下一个状态 $s_{t+1}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;    将经验 $(s_t, a_t, r_t, s_{t+1})$ 存入经验池 $\mathcal{D}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;    从 $\mathcal{D}$ 中随机采样大小为 $B$ 的小批量数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;对批量中的每个样本 $(s_i, a_i, r_i, s_{i+1})$ 执行:&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;      最优动作选择：$a^* = \arg\max_a Q_\theta(s_{i+1}, a)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;      计算目标：$y_i = r_i + \gamma Q_{\theta^-}(s_{i+1}, a^*)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:&lt;/td&gt;
&lt;td&gt;    计算损失：$L = \frac{1}{B}\sum_i (y_i - Q_\theta(s_i, a_i))^2$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:&lt;/td&gt;
&lt;td&gt;    使用梯度下降更新 $\theta$：$\theta \leftarrow \theta - \alpha \nabla_\theta L$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14:&lt;/td&gt;
&lt;td&gt;    每 $C$ 步更新目标网络：$\theta^- \leftarrow \theta$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15:&lt;/td&gt;
&lt;td&gt;    若 $s_{t+1}$ 为终止状态，结束当前回合&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;结束循环&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;结束循环&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;需要注意的是 Double-DQN 与 Double Q-Learning 是不同的！Double-DQN 中两个 Q 网络更新速度快慢不同，而 Double Q-Learning 中是随机选择两个 Q 表格中的一个用于计算 TD 目标，然后更新另一个 Q 表格，更新速度是相同的。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;5.2.4 Dueling DQN (对抗 DQN)&lt;/h2&gt;
&lt;p&gt;Dueling DQN 是另一种改进的 DQN 方法，它通过将 Q 函数分解为两个部分来提高训练的效率和稳定性。由于动作价值函数 $Q(s, a)$ 的均值是状态 $s$ 处的状态价值函数 $V(s)$，因此考虑将动作价值函数进行分解
$$
Q(s, a) = V(s) + A(s, a)
$$
其中 $V(s)$ 是状态价值函数，是动作价值函数的均值；$A(s, a)$ 是优势函数，表示在状态 $s$ 下，动作 $a$ 的优势。&lt;/p&gt;
&lt;p&gt;此外，对于最优策略，优势函数 $A(s, a)$ 的均值为 $0$，即 $\frac{1}{|A|}\sum_a A(s, a) = 0$。
因此可以将优势函数进行归一化处理，得到最终的 Q 函数
$$
Q(s, a) = V(s) + A(s, a) - \max_{a&apos;} A(s, a&apos;)
$$&lt;/p&gt;
&lt;p&gt;于是 Q 网络可以用两个神经网络来实现，一个用于计算状态价值函数 $V_\theta(s)$，另一个用于计算优势函数 $A_\phi(s, a)$。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;为什么要分离 $V(s)$ 和 $A(s, a)$ 呢？因为在训练时，状态价值函数 $V(s)$ 是相对稳定的，而优势函数 $A(s, a)$ 是相对不稳定的，将它们分开训练，可以提高训练的效率和稳定性。&lt;/li&gt;
&lt;li&gt;为什么要减去 $\max_{a&apos;} A(s, a&apos;)$ 呢？因为这样可以使得优势函数的均值为 $0$，从而保证 Q 函数的均值为状态价值函数 $V(s)$。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>强化学习 Chapter 4 - 时序差分学习</title><link>https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter4/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter4/</guid><pubDate>Fri, 18 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;4.1 从 DP 和 MC 到 TDL&lt;/h1&gt;
&lt;p&gt;时序差分学习 (Temporal-Difference Learning) 无疑是强化学习中最核心的方法之一，它结合了 Dynamic Programming 算法 和 Monte Carlo 算法的思想。与 MC 方法类似，TD 方法也是直接从与环境交互的经验中学习策略，而不需要环境的模型。但是，与 MC 不同的是，TD 不需要等待交互的最终结果，因为使用了&lt;strong&gt;自举法 (bootstraping)&lt;/strong&gt;，因此可以像 DP 一样基于已得到的其他状态的估计值来更新当前状态的价值函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自举法: 就是从不完全的经历样本中学习，利用一个猜测的数据去预测另一个数据&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TDL 的特点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自举法&lt;/li&gt;
&lt;li&gt;免模型&lt;/li&gt;
&lt;li&gt;直接从经历样本中学习&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法&lt;/th&gt;
&lt;th&gt;自举&lt;/th&gt;
&lt;th&gt;采样&lt;/th&gt;
&lt;th&gt;model-free&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DP&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MC&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TD&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;td&gt;✔&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;4.2 $TD(0)$ 方法&lt;/h1&gt;
&lt;h2&gt;4.2.1 价值更新&lt;/h2&gt;
&lt;p&gt;更新价值函数采用的是估计的回报 $V(s_{t + 1})$ 和采样的奖励 $r_t$，更新方法为
$$
V(s_t) \leftarrow V(s_t) + \alpha(r_t + \gamma V(s_{t+1}) - V(s_t))
$$
其中，$y_t = r_t + \gamma V(s_{t+1})$ 是TD 目标 (TD Target)，而 $\delta_t = y_t - V(s_t) = r_t + \gamma V(s_{t+1}) - V(s_t)$ 是 TD 误差 (TD Error)。&lt;/p&gt;
&lt;p&gt;对比 MC 的更新方式 $V(s_t) \leftarrow V(s_t) + \alpha (g_t - V(s_t))$，差异也只要是在于更新的信号上。MC 的误差是 $\Delta_t = g_t - V(s_t)$，实际上，MC 的误差可以由 TD 的误差表示
$$
g_t - V(s_t) = \sum_{k=t}^{T-1} \gamma^{k-t} \delta_k
$$
至于解释放在后面的 $n$ 步自举中说明。&lt;/p&gt;
&lt;h2&gt;4.2.2 策略估计&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;TD(0) 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;策略 $\pi$， 步长 $\alpha \in (0, 1]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;任意初始化 $V(s)，其中 V(\text{terminal state}) = 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;对于每幕循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;   初始化 状态 $s_0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;for&lt;/strong&gt; $t = 0, 1, 2, 3, ...$ 直至终止状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;      从策略 $\pi(a \vert s_t)$ 中 采样动作 $a_t$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;     执行动作 $a_t$，观察得到 奖励 $r_t$ 和 下一个状态 $s_{t+1}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;     更新价值 $V(s_t) \leftarrow V(s_t) + \alpha [r_t + \gamma V(s_{t+1}) - V(s_t)]$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这个算法反映出来几点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TD 可以在知道最终结果之前学习，而MC 必须直到每幕结束知道回报后才能学习&lt;/li&gt;
&lt;li&gt;TD 在没有最终结果时可以学习，因此 &lt;strong&gt;TD 方法既可以用于连续型任务，又可以用于分幕式任务&lt;/strong&gt;，而 MC 只能用于分幕式任务&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4.2.3 偏差与方差&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MC 方法是高方差、零偏差的学习方法&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;回报 $g_t = r_t + \gamma r_{t+1} + \dots + \gamma^{T-1} r_{T-1}$ 是对 $V(s_t)$ 的无偏估计，但是具有较高的方差（因为涉及到很多的随机变量，且这些随机变量之间是相互独立的，因此方差直接累加了）&lt;/li&gt;
&lt;li&gt;MC 方法有良好的收敛性能，且对于初始价值不敏感&lt;/li&gt;
&lt;li&gt;MC 没有使用 Markov 属性，因此在 非 Markov环境中更有效&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;TD 方法是低方差、但有一些偏差&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TD 目标 $y_t = r_t + \gamma V_\pi(s_{t+1})$ 也是 $V(s_t)$ 的无偏估计（前提是 $V(s_{t+1})$ 得无偏），但是方差很低&lt;/li&gt;
&lt;li&gt;TD 方法对初始价值敏感&lt;/li&gt;
&lt;li&gt;TD 使用了 Markov 属性，因此在 Markov环境中更有效&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4.3 时序差分控制&lt;/h1&gt;
&lt;p&gt;将TD方法运用到控制中，可以让智能体每做出一个动作，就对应的进行学习改进策略。时序差分控制仍然遵循广义策略迭代 (GPI) 的模式，在评估和预测的部分都使用时序差分方法。类似 MC 方法，需要在 Explore 和 Exploit 之间做出权衡，因此方法也同样分为&lt;strong&gt;on-policy&lt;/strong&gt; 和 &lt;strong&gt;off-policy&lt;/strong&gt; 两类。&lt;/p&gt;
&lt;h2&gt;4.3.1 SARSA&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;SARSA 是一个 on-policy 方法&lt;/strong&gt;，它所使用的价值函数是动作价值函数，更新方式为
$$
Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha [r_t + \gamma Q(s_{t+1}, a_{t+1}) - Q(s_t, a_t)]
$$
每当从非终止状态的 $s_t$ 出现一次转移之后，就进行上面的一次更新。如果涉及到了是终止状态，那么就把终止状态对应的动作价值函数设置为 $0$，例如，若 $s_{t+1}$ 是终止状态，那么就让 $Q(s_{t+1}, a) = 0, \ \forall a \in \mathcal{A}(s_{t+1})$。由于这个更新方式涉及到了五元组 $(s_t, a_t, r_t, s_{t+1}, a_{t+1})$，因此这个方法被命名为 SARSA，代表这个五元组的每个元素分别是 状态-动作-奖励-状态-动作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;SARSA Control 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;步长 $\alpha \in (0, 1]$, $\epsilon &amp;gt; 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;任意初始化动作价值函数 $Q(s, a)$，其中 $Q(\text{terminal state}, \cdot) = 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;对每幕数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;  初始化状态 $s_0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   使用从 $Q$ 得到的策略 (例如 $\epsilon$-greedy)，在 $s_0$ 处选择动作 $a_0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;for&lt;/strong&gt; $t = 0, 1, 2, \dots$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;     执行动作 $a_t$，观察到 $r_t, s_{t+1}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;     使用从 $Q$ 得到的策略（例如 $\epsilon$-greedy），在 $s_{t+1}$ 处选择动作 $a_{t+1}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;     $Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha[r_t + \gamma Q(s_{t+1}, a_{t+1}) - Q(s_t, a_t)]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;     若 $s_{t+1}$ 是终止状态，则终止当前幕的 &lt;strong&gt;for&lt;/strong&gt; 循环&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;4.3.2 Q-Learning&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Q-Learning 是一个 off-policy 方法&lt;/strong&gt;，其定义为
$$
Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha \left[r_t + \gamma \max_a Q(s_{t+1}, a) - Q(s_t, a_t)\right]
$$
这里，待学习的动作价值函数 $Q$ 采用了对最优动作价值函数 $Q^*$ 的直接近似作为学习目标，而与用于生成智能体决策序列轨迹的行动策略无关。它的 TD 目标是 $y_t = r_t + \gamma \max_a Q(s_{t+1}, a)$。这个迭代的方式不需要在 $t + 1$ 时刻的动作选择 $a_{t+1}$，简化了迭代过程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Q-Learning Control 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;步长 $\alpha \in (0, 1]$, $\epsilon &amp;gt; 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化动作价值函数 $Q(s, a)$，其中 $Q(\text{terminal state}, \cdot) = 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;对每幕&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;  初始化状态 $s_0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;for&lt;/strong&gt; $t = 0, 1, 2, \dots$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;     使用从 $Q$ 得到的策略 ( 例如 $\epsilon$-贪心 )，在 $s_t$ 处选择动作 $a_t$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;     执行动作 $a_t$，观察得到 $r_t, s_{t+1}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;     $Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha [r_t + \gamma \max_a Q(s_{t+1}, a) - Q(s_t, a_t)]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;    若 $s_{t+1}$ 是终止状态，则停止 &lt;strong&gt;for&lt;/strong&gt; 循环&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Q-Learning 显然是 off-policy 方法，因为它使用的动作策略和目标策略不同&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;动作策略是一个 &lt;strong&gt;ε-贪心&lt;/strong&gt; 的策略&lt;/li&gt;
&lt;li&gt;而目标策略则是 &lt;strong&gt;贪婪&lt;/strong&gt; 策略&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Q-Learning 虽然是 off-policy 方法，但是不需要对经验 $(s_t, a_t, r_t, s_{t+1})$ 做重要性采样&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;重要性采样的目的是修正不同策略之间的概率偏差&lt;/li&gt;
&lt;li&gt;Q-Learning 的更新目标是 $y_t = r_t + \gamma \max_a Q(s_{t+1}, a)$，相当于是对
$$
\mathbb{E}&lt;em&gt;{s&lt;/em&gt;{t+1} \sim p(s_{t+1} | s_t, a_t)}\left[r_t + \gamma \max_a Q(s_{t+1}, a) \right] = \sum_{s_{t+1}} p(s_{t+1} | s_t, a_t) \left[ r_t + \gamma \max_a Q(s_{t+1}, a)\right]
$$
  的 Monte Carlo 估计，可以发现这里的期望积分实际上只涉及下一个状态 $s_{t+1}$ 而不包含动作 $a_{t+1}$，那么在 当前动作 $a_t$ 和状态 $s_t$ 均已知的条件下，实际上采样只会用到环境模型，而不涉及到策略模型，有因此无需重要性采样。&lt;/li&gt;
&lt;li&gt;从公式角度也可以看出
$$
\rho_{t} = \frac{p_{\hat{\pi}^*}(s_{t+1} | s_t, a_t)}{p_\pi(s_{t+1} | s_t, a_t)} = \frac{ p(s_{t+1} | s_t, a_t)}{ p(s_{t+1} | s_t, a_t)} = 1
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4.3.3 期望 SARSA (Expected SARSA)&lt;/h2&gt;
&lt;p&gt;期望 SARSA 的更新公式非常类似 Q-Learning 或 SARSA, 就是把 Q-Learning 的动作价值最大化改为了求期望。相较于 SARSA 也就是替换了在下一步状态 $s_{t+1}$ 下一步动作 $a_{t+1}$ 的价值函数为对动作求期望的动作价值函数，因此被命名为 期望 SARSA。当引入了 &lt;strong&gt;$\epsilon$-贪心且行为策略和目标策略一致&lt;/strong&gt;时，期望 SARSA 是 &lt;strong&gt;on-policy&lt;/strong&gt; 方法。
$$
Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha \left[ r_t + \gamma \sum_a \pi(a | s_{t+1}) Q(s_{t+1}, a) - Q(s_t, a_t)\right]
$$&lt;/p&gt;
&lt;p&gt;当采用了&lt;strong&gt;探索性的行为策略&lt;/strong&gt;和&lt;strong&gt;贪心的目标策略&lt;/strong&gt;时，期望 SARSA 是 &lt;strong&gt;off-policy&lt;/strong&gt; 方法。此时的更新公式仍然为
$$
Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha \left[ r_t + \gamma \sum_a \pi(a | s_{t+1}) Q(s_{t+1}, a) - Q(s_t, a_t)\right]
$$
这里没有重要性采样的原因与 Q-Learning 中没有重要性采样的原因相同，也因此更新公式与前述公式相同。&lt;/p&gt;
&lt;p&gt;算法形式与 SARSA 或 Q-Learning 相同，只需要替换更新公式即可。&lt;/p&gt;
&lt;h2&gt;4.3.4 Double Q-Learning&lt;/h2&gt;
&lt;h3&gt;最大化正偏差问题&lt;/h3&gt;
&lt;p&gt;最大化正偏差（Maximization Positive Bias）是指在强化学习（尤其是值函数估计方法如Q-learning）中，由于使用最大化（max）操作来选择动作或估计未来回报，导致对真实期望值系统性高估的现象。最大化偏差的数学本质如下。假设动作的真实 Q 值 $Q^&lt;em&gt;$ 与估计 Q 值 $Q$ 满足：
$$
Q(s, a) = Q^&lt;/em&gt;(s, a) + \epsilon(s, a)
$$
其中 $\epsilon(s, a)$ 是独立同分布的噪声，且 $\mathbb{E}[\epsilon(s, a)] = 0$，方差为 $\sigma^2$。这里 $Q(s, a)$ 是随机变量而 $Q^*(s, a)$ 是确定量。&lt;/p&gt;
&lt;p&gt;对于下一状态 $s&apos;$，最大化操作的结果为：
$$
\max_{a&apos;} Q(s&apos;, a&apos;) = \max_{a&apos;} \left( Q^&lt;em&gt;(s&apos;, a&apos;) + \epsilon(s&apos;, a&apos;) \right)
$$
由于最大值函数是凸函数，根据&lt;strong&gt;Jensen不等式&lt;/strong&gt;：
$$
\mathbb{E}\left[ \max_{a&apos;} Q(s&apos;, a&apos;) \right] \geq \max_{a&apos;} \mathbb{E}\left[ Q(s&apos;, a&apos;) \right] = \max_{a&apos;} Q^&lt;/em&gt;(s&apos;, a&apos;)
$$
因此，$\max_{a&apos;} Q(s&apos;, a&apos;)$ 的期望会高估真实的最大Q值，导致更新目标被系统性抬高。&lt;/p&gt;
&lt;p&gt;此外，由于 TD 等方法在学习过程中使用了&lt;strong&gt;自举法&lt;/strong&gt;，即对状态 $s$ 的价值估计是基于其他状态 $s&apos;$ 之上的。如果其他的状态估计存在有最大化偏差的问题，那么对当前状态 $s$ 的估计也会相应的有最大化偏差。也就是说，自举法实际上加剧了这种正偏差。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;影响与后果&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;高估问题（Overestimation）&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Q值的持续高估可能导致策略选择次优动作。&lt;/li&gt;
&lt;li&gt;例如，在存在多个动作的情况下，某个次优动作的Q值可能因噪声被高估，导致智能体错误地偏好该动作。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;收敛稳定性下降&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;高估偏差可能使Q值更新过程震荡，尤其是在函数近似（如深度Q网络）中，可能导致训练不稳定。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.3.5 双 Q 学习消除最大化偏差&lt;/h3&gt;
&lt;p&gt;将样本分为两个集合，独立学习得到两个动作价值 $Q_1$ 和 $Q_2$，让 $Q_1$ 选择最大价值的动作，用 $Q_2$ 计算动作价值估计。然后交换 $Q_1$ 和 $Q_2$ 的角色，再进行更新。&lt;/p&gt;
&lt;p&gt;$$
Q_1(s_t, a_t) \leftarrow Q_1(s_t, a_t) + \alpha \left[ r_t + \gamma Q_2(s_{t+1}, \arg\max_a Q_1(s_{t+1}, a)) - Q_1(s_t, a_t)\right]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Double Q-Learning 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;步长 $\alpha \in (0, 1]$, $\epsilon &amp;gt; 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化两个动作价值函数 $Q_1(s, a)$ 和 $Q_2(s, a)$，其中 $Q_1(\text{terminal state}, \cdot) = Q_2(\text{terminal state}, \cdot) = 0 $&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;对每幕循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;   初始化状态 $s_0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;for&lt;/strong&gt; $t = 0, 1, 2, \dots$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;    基于 $Q_1 + Q_2$，使用 ε-贪心策略在 $s_t$ 选择动作 $a_t$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;     执行动作 $a_t$ , 观察得到 $r_t$, $s_{t+1}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;     以 $0.5$ 的概率执行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;       $Q_1(s_t, a_t) \leftarrow Q_1(s_t, a_t) + \alpha \left[ r_t + \gamma Q_2(s_{t+1}, \arg\max_aQ_1(s_{t+1}, a)) - Q_1(s_t, a_t)\right]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;    或者执行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;       $Q_1(s_t, a_t) \leftarrow Q_1(s_t, a_t) + \alpha \left[ r_t + \gamma Q_2(s_{t+1}, \arg\max_aQ_1(s_{t+1}, a)) - Q_1(s_t, a_t)\right]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;    若 $s_{t+1}$ 是终止状态，则终止当前幕的 &lt;strong&gt;for&lt;/strong&gt; 循环&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;可以看出来，双 Q 学习就是利用两个动作价值函数同时进行学习。用其中一个动作价值函数来选择最优动作，然后用另一个动作价值函数来估计这个动作的价值，再更新第一个动作价值函数的动作价值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;双Q学习可以&lt;em&gt;减弱&lt;/em&gt;最大化偏差问题（注意不是消除）&lt;/strong&gt;，因为当采用了两个动作价值函数的时候，只要 $Q_1$ 选择的动作对应的在 $Q_2$ 中的动作价值没有被高估，就不会出现最大化偏差的问题。&lt;/p&gt;
&lt;h1&gt;4.4 n 步自举法 (n-step Bootstrapping)&lt;/h1&gt;
&lt;p&gt;单独的 MC 方法或 TD 方法都不会总是最好的方法。 $n$ 步时序差分方法是这两种方法更一般的推广，在这个框架下可以更加平滑地切换这两种方法。&lt;/p&gt;
&lt;h2&gt;4.4.1 $TD (n)$ 方法&lt;/h2&gt;
&lt;p&gt;考虑在固定策略 $\pi$ 下利用多幕采样序列估计 $V_\pi$ 的情况。 Monte Carlo 方法是根据从某一状态开始到终止状态的收益序列，对这个状态的价值进行更新。而时序差分方法则只根据后面的单个即时收益，在下一个后继状态的价值估计值的基础上进行自举更新。考虑一种介于两者之间的方法——采样 $n$ 步的奖励，然后在 第 $n+1$ 步进行自举。&lt;/p&gt;
&lt;p&gt;$n$ 步回报定义为
$$
G_{t:t+n} = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \dots + \gamma^{n-1} r_{t+n-1} + \gamma^n V^{(t+n-1)} (s_{t+n})
$$
作为 $n$ 步时序差分的 TD 目标用于更新。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;$n$ 步时序差分 (TD (n)) 的更新公式为&lt;/strong&gt;
$$
V^{(t+n)}(s_t) \leftarrow V^{(t + n - 1)}(s_t) + \alpha\left[G_{t:t+n} - V^{(t + n - 1)}(s_t) \right]
$$
其中 $V^{(k)}$ 代表第 $k$ 个时间步时的价值函数，因为价值函数在每步都在迭代，且这种迭代是具有延迟效应的，即 第 $t$ 个 时间步的状态 $s_t$ 的价值函数需要等待 $n$ 个时间步才能更新。还有一个需要注意的点是，更新时所用的基线是 $V^{(t + n - 1)}$ 而不是 $V^{(t)}$, 这是因为相较于 $V^{(t)}$， $V^{(t + n - 1)}$ 更新更准确。采用后者会更加的合理，能&lt;strong&gt;减少误差&lt;/strong&gt;，使价值函数收敛到正确的预测。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/bootstrapping.png&quot; alt=&quot;n-steps bootstrapping&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;TD(n) 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;策略 $\pi$，步长 $\alpha$，正整数 $n$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;状态价值函数 $V_\pi(s)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;任意初始化状态价值函数 $V(s)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;对每幕循环:&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;  初始化并存储状态 $s_0$ (非终止状态)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;  $T \leftarrow \infty$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;for&lt;/strong&gt; $t = 0, 1, 2, \dots$ &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;if&lt;/strong&gt; $t &amp;lt; T$ &lt;strong&gt;then&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;      根据 $\pi(a_t\vert s_t)$ 采样动作 $a_t$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;      执行动作 $a_t$，观察 $r_t$ 和 $s_{t+1}$，并存储&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;      &lt;strong&gt;if&lt;/strong&gt; $s_{t+1}$ 是终止状态 &lt;strong&gt;then&lt;/strong&gt; $T \leftarrow t + 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;end if&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;    $\tau \leftarrow t - n + 1$ (当前正在更新的状态时刻)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;if&lt;/strong&gt; $\tau \geq 0$ &lt;strong&gt;then&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:&lt;/td&gt;
&lt;td&gt;      $G \leftarrow \sum_{i=\tau}^{\min(\tau + n - 1, T-1)}\gamma^{i - \tau} r_i$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14:&lt;/td&gt;
&lt;td&gt;      &lt;strong&gt;if&lt;/strong&gt; $\tau + n &amp;lt; T$ &lt;strong&gt;then&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15:&lt;/td&gt;
&lt;td&gt;        $G \leftarrow G + \gamma^n V(s_{\tau + n})$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16:&lt;/td&gt;
&lt;td&gt;      &lt;strong&gt;end if&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17:&lt;/td&gt;
&lt;td&gt;      $V(s_\tau) \leftarrow V(s_\tau) + \alpha \left[G - V(s_\tau)\right]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;end if&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;if&lt;/strong&gt; $\tau = T - 1$ &lt;strong&gt;then&lt;/strong&gt; 终止 &lt;strong&gt;for&lt;/strong&gt; 循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;end 对每幕循环&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;4.4.2 $n$ 步 SARSA&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;assets/nSarsa.png&quot; alt=&quot;nSarsa&quot; /&gt;&lt;/p&gt;
&lt;p&gt;结合 $n$ 步自举法 和 SARSA，可以得到同轨策略下的时序差分控制方法&lt;/p&gt;
&lt;p&gt;类似地，定义 $n$ 步 $Q$ 回报为
$$
G_{t:t+n} = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \dots + \gamma^{n-1} r_{t+n} + \gamma^n Q^{(t+n - 1)}(s_{t+n}, a_{t+n})
$$
于是 $n$ 步 SARSA 的更新公式为
$$
Q^{(t+n)}(s_t, a_t) \leftarrow Q^{(t+n - 1)}(s_t, a_t) + \alpha \left[ G_{t:t+n} - Q^{(t+n - 1)}(s_t, a_t)\right]
$$
符号表示与 $TD(n)$ 类似，$Q^{(k)}(s, a)$ 表示第 $k$ 步时的动作价值函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;n-step SARSA Control 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;步长 $\alpha$，正整数 $n$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;将 $\pi$ 初始化为对应 $Q$ 的 $\epsilon$-greedy 策略，或者某个固定的给定策略&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;对每幕循环:&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;  初始化并存储状态 $s_0$ (非终止状态)，并选择和保存动作 $a_0 \sim \pi(a_0 \vert s_0)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;  $T \leftarrow \infty$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;for&lt;/strong&gt; $t = 0, 1, 2, \dots$ &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;if&lt;/strong&gt; $t &amp;lt; T$ &lt;strong&gt;then&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;      执行动作 $a_t$，观察 $r_t$ 和 $s_{t+1}$，并存储&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;      &lt;strong&gt;if&lt;/strong&gt; $s_{t+1}$ 是终止状态 &lt;strong&gt;then&lt;/strong&gt; $T \leftarrow t + 1$ &lt;strong&gt;else&lt;/strong&gt; 选择并存储动作 $a_{t+1} \sim \pi(a_{t+1} \vert s_{t+1})$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;end if&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;    $\tau \leftarrow t - n + 1$ (当前正在更新的状态时刻)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;if&lt;/strong&gt; $\tau \geq 0$ &lt;strong&gt;then&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:&lt;/td&gt;
&lt;td&gt;      $G \leftarrow \sum_{i=\tau}^{\min(\tau + n - 1, T-1)}\gamma^{i - \tau} r_i$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:&lt;/td&gt;
&lt;td&gt;      &lt;strong&gt;if&lt;/strong&gt; $\tau + n &amp;lt; T$ &lt;strong&gt;then&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14:&lt;/td&gt;
&lt;td&gt;        $G \leftarrow G + \gamma^n Q(s_{\tau + n}, a_{\tau + n})$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15:&lt;/td&gt;
&lt;td&gt;      &lt;strong&gt;end if&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16:&lt;/td&gt;
&lt;td&gt;      $Q(s_\tau, a_\tau) \leftarrow Q(s_\tau, a_\tau) + \alpha \left[G - Q(s_\tau, a_\tau)\right]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17:&lt;/td&gt;
&lt;td&gt;      如果处于学习 $\pi$ 的过程中，那么需要确保 $\pi(a_\tau \vert s_\tau)$ 是基于 $Q$ 的$\epsilon$-贪心策略&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;end if&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;if&lt;/strong&gt; $\tau = T - 1$ &lt;strong&gt;then&lt;/strong&gt; 终止 &lt;strong&gt;for&lt;/strong&gt; 循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;end 对每幕循环&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;$n$ 步 SARSA 相较于 单步的 SARSA 加速了策略学习。&lt;/p&gt;
&lt;p&gt;类似地还有 $n$ 步期望 SARSA，更新公式为
$$
G_{t:t+n} = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \dots + \gamma^{n-1} r_{t+n} + \gamma^n \sum_a \pi(a|s_{t+n}) Q^{(t+n - 1)}(s_{t+n}, a)
$$
$$
Q^{(t+n)}(s_t, a_t) = Q^{(t+n - 1)}(s_t, a_t) + \alpha \left[G_{t:t+n} - Q^{(t+n-1)}(s_t, a_t) \right]
$$&lt;/p&gt;
&lt;h2&gt;4.4.3 $n$ 步离轨策略学习&lt;/h2&gt;
&lt;p&gt;类似 MC 的离轨策略学习，这里的方法是需要引入重要度重采样率的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;off-policy $TD(n)$
$$
V^{(t+n)}(s_t) = V^{(t+n - 1)}(s_t) + \alpha \rho_{t:t+n-1} \left[r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \dots + \gamma^{n-1} r_{t+n-1} + \gamma^n V^{(t+n-1)} (s_{t+n}) - V^{(t+n-1)}(s_t ) \right]
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;off-policy $n$ 步 SARSA
$$
Q^{(t+n)}(s_t, a_t) = Q^{(t+n - 1)}(s_t, a_t) + \alpha \rho_{t+1:t+n} \left[r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \dots + \gamma^{n-1} r_{t+n} + \gamma^n Q^{(t+n - 1)}(s_{t+n}, a_{t+n}) - Q^{(t+n-1)}(s_t, a_t) \right]
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;off-policy $n$ 步 期望 SARSA
$$
Q^{(t+n)}(s_t, a_t) = Q^{(t+n - 1)}(s_t, a_t) + \alpha \rho_{t+1:t+n-1} \left[ r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \dots + \gamma^{n-1} r_{t+n} + \gamma^n \sum_a \pi(a|s_{t+n}) Q^{(t+n - 1)}(s_{t+n}, a) - Q^{(t+n-1)}(s_t, a_t) \right]
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中 $\rho_{t:h} = \prod_{k=t}^{h} \frac{\pi(a_k | s_k)}{\mu(a_k | s_k)}$&lt;/p&gt;
&lt;h2&gt;4.4.4 $n$ 步树回溯&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;assets/nTree.png&quot; alt=&quot;nTree&quot; /&gt;
借鉴 Q-Learning 和 期望SARSA 中不使用重要度采样的思想，采用自举构建用于价值函数更新的目标。在回溯树中，顶端节点的价值由沿途所有的收益和底部节点的估计价值组合而成，更新量是从树的叶子节点的动作价值的估计值计算出来的，采样子步骤与期望子步骤交替进行。&lt;/p&gt;
&lt;p&gt;回报的递归公式为
$$
G_{t:t+n} = r_t + \gamma \sum_{a \ne a_{t+1}} \pi(a| s_{t+1}) Q^{(t+n-1)}(s_{t+1}, a) + \gamma \pi(a_{t+1} | s_{t+1}) G_{t+1:t+n}
$$
动作价值更新为
$$
Q^{(t+n)}(s_t, a_t) = Q^{(t+n-1)}(s_t, a_t) + \alpha \left[ G_{t:t+n} - Q^{(t+n-1)}(s_t, a_t)\right]
$$&lt;/p&gt;
</content:encoded></item><item><title>群体智能算法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/swarmintelligence/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/swarmintelligence/</guid><pubDate>Thu, 17 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Swarm 本义为昆虫群体，在自然计算中，有一类受昆虫群体行为启发的算法，称为群体智能（Swarm Intelligence）。群体中的个体行为非常简单，但是当它们一起协同工作时，能够产生复杂的行为。&lt;/p&gt;
&lt;h1&gt;概述&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;灵活性：群体可以适应随时变化的环境&lt;/li&gt;
&lt;li&gt;鲁棒性：即使部分个体失败，整个群体也能完成任务&lt;/li&gt;
&lt;li&gt;自我组织：个体的活动既不受中央控制，也不受局部管理&lt;/li&gt;
&lt;li&gt;分布式：个体之间的交互是局部的，个体之间没有全局信息&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;计算复杂度高：个体之间的交互会导致计算复杂度增加&lt;/li&gt;
&lt;li&gt;收敛速度慢：群体智能算法通常需要较长的时间才能收敛到最优解&lt;/li&gt;
&lt;li&gt;信息素均匀分配策略与路径重要性无关：对已搜索路径中的所有路段采用同样的信息素增量实际上与路段的重要性无关。&lt;/li&gt;
&lt;li&gt;容易出现停滞现象：当所有蚂蚁都选择相同的路径时，系统不再搜索更好的解。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;典型算法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;粒子群优化（Particle Swarm Optimization, PSO）：模拟鸟群觅食行为&lt;/li&gt;
&lt;li&gt;蚁群算法（Ant Colony Optimization, ACO）：模拟蚂蚁觅食行为&lt;/li&gt;
&lt;li&gt;蜂群算法（Bee Algorithm）：模拟蜜蜂觅食行为&lt;/li&gt;
&lt;li&gt;鱼群算法（Fish School Search, FSS）：模拟鱼群觅食行为&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;蚁群算法 (Ant Colony Optimization)&lt;/h1&gt;
&lt;h2&gt;算法的起源&lt;/h2&gt;
&lt;p&gt;蚁群自组织行为的双桥实验，蚁群利用遗留在往来路径上的&lt;strong&gt;具有挥发性的信息素&lt;/strong&gt;来进行通信和协调。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/ant1.png&quot; alt=&quot;ant1&quot; /&gt;
假设有一群蚂蚁，蚂蚁们从A点出发，随机选择路线ABD或ACD，这里路线ABD长度为9个时间单位而ACD的长度为18个时间单位。经过9个时间单位后，走ABD的蚂蚁到达终点，走ACD的蚂蚁刚好走到C点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/ant2.png&quot; alt=&quot;ant2&quot; /&gt;
经过18个时间单位后，走ABD的蚂蚁到达终点后得到食物又返回了起点A，而走ACD的蚂蚁刚好走到D点。&lt;strong&gt;假设蚂蚁的在单位时间内释放的信息素是个固定量&lt;/strong&gt;，这样一来，路线ABD上蚂蚁留下的信息素将是路线ACD上蚂蚁留下的信息素的两倍。很显然这有一个正反馈的过程，最终蚂蚁们都会被信息素较多的路线吸引，选择走ABD。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/ant3.png&quot; alt=&quot;ant3&quot; /&gt;
&lt;strong&gt;注意，并不是因为蚂蚁们知道哪条路线长就在哪条路上释放较少的信息素，蚂蚁单位时间释放的信息素是定值。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;蚁群算法解决 TSP 问题&lt;/h2&gt;
&lt;p&gt;上述蚁群觅食很好的体现出来了寻找食物时的最优路径选择问题，那么，借助这个思想，可以解决其他类似的优化问题。&lt;/p&gt;
&lt;p&gt;作为蚁群算法的蚂蚁个体，其运动和通信的简单规则，需要包含以下几个方面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;搜索范围: 蚂蚁会以一定的搜索半径进行搜索&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;局部环境: 蚂蚁能感知周围环境的信息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;觅食规则: 蚂蚁只在其能感知的范围内进行探索和信息素释放&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;移动规则: 蚂蚁会朝信息素最多的方向移动，并且，当周围没有信息素指引的时候，蚂蚁会按照自己原来运动的方向惯性地运动下去。如果蚂蚁发现有它已经经过的地点，则以较大概率进行避让。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;避障规则: 在蚂蚁的移动方向上有障碍物存在时，蚂蚁会随机选择另一个方向，或者按照信息素的指引继续其觅食行为。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通信规则: 信息素，信息素的挥发和蚂蚁的释放是一个正反馈的过程，信息素的浓度越高，蚂蚁就越容易选择这条路径。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;TSP 问题&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TSP 问题是一种典型的组合优化问题。给定 $n$ 个城市的集合，TSP 问题目标是寻找一条只经过各城一次的且长度最短的闭合路径。&lt;/p&gt;
&lt;p&gt;用蚁群算法可以很好的解决 TSP 问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=5&amp;gt;Algorithm 蚁群算法解决 TSP&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt; 城市拓扑图 $&amp;lt;N, E&amp;gt;$，城市个数 $n$，蚂蚁个数 $m$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt; 最优路径&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;将 $m$ 只蚂蚁随机的放到 $n$ 个城市，并且为每只蚂蚁构造一个禁忌表，记第 $k$ 只蚂蚁的禁忌表为 $tabu_k(s)$。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;设各路径上的信息素量为 $\tau_{ij}(0) = C$，$C$ 为一个较小的常数。// 该步只进行一次，后续不再执行，目的就是希望利用上一次循环的信息素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3：&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;for&lt;/strong&gt; $cnt = 0$ &lt;strong&gt;to&lt;/strong&gt; $max_iter_num$ &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt; 初始化每个蚂蚁的禁忌表的第一个元素 $tabu_k(1)$ 为它当前所在的城市。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;while&lt;/strong&gt; 每只蚂蚁的禁忌表没有满 &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;   对于每只蚂蚁，根据路径上的&lt;strong&gt;信息素&lt;/strong&gt;和&lt;strong&gt;启发式信息（两城市之间的距离）&lt;/strong&gt; 独立地选择下一个城市。具体地，在 $t$ 时刻，蚂蚁 $k$ 从城市 $i$ 转移到 城市 $j$ 的概率为$$p^k_{ij}(t) = \begin{cases}\frac{\tau_{ij}(t)^\alpha \eta_{ij}(t)^\beta}{\sum_{s \in J_k(i)}\tau_{is}(t)^\alpha \eta_{is}(t)^\beta}, &amp;amp;\quad j \in J_k(i)\ 0, &amp;amp;\quad j \notin J_k(i)\end{cases} $$ 其中下一步允许选择的城市的集合为 $J_k(i) = {1, 2, \dots, n} - tabu_k,\ \ \eta_{ij} = 1 / d_{ij}$，$d_{ij}$ 为城市 i 和城市 j 之间的距离，$\alpha$ 和 $\beta$ 是两个参数，分别表示信息素和启发式信息的权重。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7：&lt;/td&gt;
&lt;td&gt;   依据概率选出下一个城市 $j$ 后，将 $j$ 放入禁忌表 $tabu_{k}$ 中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;  &lt;strong&gt;end while&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt; 当所有蚂蚁完成一次周游后，计算所有 $m$ 只蚂蚁走过的周游长度 $L_k$，并更新当前的最优路径；&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt; 各路径上的信息素将进行更新 $$\tau_{ij}(t + 1) = (1 - \rho)\tau_{ij}(t) + \Delta\tau_{ij},\ \Delta\tau_{ij} = \sum_{k=1}^m \Delta \tau_{ij}^{(k)},\ \Delta \tau_{ij}^{(k)} = \begin{cases}\frac{Q}{L_k}, &amp;amp;\quad \text{if ant}\ k\text{ has passed edge}\ ij\text{ in this tour}  \ 0, &amp;amp;\quad \text{otherwise}\end{cases}$$ 其中$L_k$ 为蚂蚁 $k$ 在本次周游中走过的路径长度，$Q$ 是一个常数，$\rho \in (0, 1)$ 是信息素挥发速率。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11：&lt;/td&gt;
&lt;td&gt; 清空所有的禁忌表&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;return&lt;/strong&gt; 最优路径&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;典型的蚁群算法模板为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def ACO_MetaHeuristic():
    while not terminated:
        generateSolutions()
        daemonActions()     
        pheromoneUpdate()   # 更新信息素
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;信息素更新的三种设置&lt;/h2&gt;
&lt;p&gt;有三种基本信息素更新模式：&lt;strong&gt;蚁密模型（Ant‑Density）&lt;/strong&gt;、&lt;strong&gt;蚁量模型（Ant‑Quantity）&lt;strong&gt;和&lt;/strong&gt;蚁周模型（Ant‑Cycle）&lt;/strong&gt;。它们的主要区别在于信息素何时更新（每移动一步或完成整个周游后）以及更新量如何确定（常数、与边长成反比或与整条巡游长度成反比）。其中，蚁密和蚁量模型均为局部更新，前者只按常数沉积、后者根据边长沉积；而蚁周模型采用全局更新，在所有蚂蚁完成一次完整巡游后才统一沉积信息素&lt;/p&gt;
&lt;h3&gt;蚁密模型（Ant‑Density Model）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;更新时机&lt;/strong&gt;：每只蚂蚁在从城市 $i$ 移动到城市 $j$ 的瞬间，立即进行信息素更新（局部更新）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新量&lt;/strong&gt;：在边 $(i,j)$ 上沉积一个常量 $Q$，与路径长度无关。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;公式&lt;/strong&gt;：&lt;br /&gt;
$$
\tau_{ij} \leftarrow (1-\rho),\tau_{ij} + Q
$$&lt;br /&gt;
其中，$\rho\in(0,1)$ 为挥发系数，$Q$ 为常数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点与不足&lt;/strong&gt;：此模型简单易实现，信息素量与蚂蚁数量成正比，但忽略了边长对路径质量的影响，容易导致搜索过度局部化。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;蚁量模型（Ant‑Quantity Model）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;更新时机&lt;/strong&gt;：同样在每步移动时立即更新（局部更新）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新量&lt;/strong&gt;：与边长 $d_{ij}$ 成反比，在移动到 $(i,j)$ 时沉积&lt;br /&gt;
$$
\Delta\tau_{ij} = \frac{Q}{d_{ij}}
$$&lt;br /&gt;
因此较短边得到更多信息素，有助于局部搜索能力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;公式&lt;/strong&gt;：&lt;br /&gt;
$$
\tau_{ij} \leftarrow (1-\rho),\tau_{ij} + \frac{Q}{d_{ij}}
$$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;：较蚁密模型更能利用边的启发式信息，但仍为局部更新，无法综合全局路径信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;蚁周模型（Ant‑Cycle Model）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;更新时机&lt;/strong&gt;：每轮所有蚂蚁完成一次完整巡游后，才统一进行信息素更新（全局更新）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;更新量&lt;/strong&gt;：对于第 $k$ 只蚂蚁所走过的每条边 $(i,j)$，沉积&lt;br /&gt;
$$
\Delta\tau_{ij}^{(k)} =
\begin{cases}
\displaystyle \frac{Q}{L_k}, &amp;amp; \text{若蚂蚁 }k\text{ 经过边 }(i,j)\
0, &amp;amp; \text{否则}
\end{cases}
$$&lt;br /&gt;
其中 $L_k$ 为第 $k$ 只蚂蚁本次巡游的总长度，$Q$ 为常数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;整体更新公式&lt;/strong&gt;：&lt;br /&gt;
$$
\tau_{ij} \leftarrow (1-\rho),\tau_{ij}
+ \sum_{k=1}^m \Delta\tau_{ij}^{(k)}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优势&lt;/strong&gt;：利用每只蚂蚁全程信息素沉积，兼顾全局与局部，从而常被证明性能优于前两种模式，收敛质量更高。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;停滞现象&lt;/h2&gt;
&lt;p&gt;停滞现象（Stagnation behavior）：当所有蚂蚁都选择相同的路径时，系统不再搜索更好的解。原因在于较好路径上的信息素远大于其他边上的，从而使所有蚂蚁都选择该路径。&lt;/p&gt;
&lt;p&gt;因此参数 $\alpha$ 和 $\beta$ 的设置非常重要。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$\alpha$ 越大，信息素的影响越大，容易导致停滞现象。$\alpha$ 取较大值时，意味着在选择路径时，路径上的信息素非常重要；当 $\alpha$ 取较小值时，变成随机的贪婪算法。&lt;/li&gt;
&lt;li&gt;$\beta$ 越大，启发式信息的影响越大，容易导致搜索过度局部化。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;蚂蚁数量 $m \approx n$ 时，使用 ant-cycle 模型可以在最少迭代次数内找到最优解。&lt;/p&gt;
&lt;h1&gt;粒子群算法 (Particle Swarm Optimization, PSO)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;论文地址：&lt;a href=&quot;https://ieeexplore.ieee.org/document/488968&quot;&gt;Particle swarm optimization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;粒子群算法源于对鸟群捕食行为的研究。假设有一个区域，所有的鸟都不知道食物的位置，但是它们知道当前位置距离食物还有多远。PSO 算法把每个解看作一只鸟，称为 &quot;粒子 (particle)&quot;，所有的粒子都有一个适应值，每个粒子都有一个速度决定它们的飞翔方向和距离，粒子们追随当前最优粒子在解空间中的搜索。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;鸟群觅食&lt;/th&gt;
&lt;th&gt;粒子群优化算法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;鸟群&lt;/td&gt;
&lt;td&gt;搜索空间的一组有效解 (表现为种群规模 $N$)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;觅食空间&lt;/td&gt;
&lt;td&gt;问题的搜索空间 (表现为维数 $D$)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;飞行速度&lt;/td&gt;
&lt;td&gt;解的速度向量 $\mathbf{v}&lt;em&gt;i = [v&lt;/em&gt;{i1}, v_{i2}, \dots, v_{iD}]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;所在位置&lt;/td&gt;
&lt;td&gt;解的位置向量 $\mathbf{x}&lt;em&gt;i = [x&lt;/em&gt;{i1}, x_{i2}, \dots, x_{iD}]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;个体认知与群体协作&lt;/td&gt;
&lt;td&gt;每个粒子 $i$ 根据自身历史最优位置和群体的全局最优位置更新速度和位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;找到食物&lt;/td&gt;
&lt;td&gt;算法结束，输出全局最优解&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;算法流程&lt;/h2&gt;
&lt;p&gt;初始化一群随机粒子，通过迭代找到最优值。在每次迭代过程中，粒子通过跟踪 &quot;个体极值 ($pbest$)&quot; 和 &quot;全局极值 ($gbest$)&quot; 来更新自己的位置。&lt;/p&gt;
&lt;p&gt;假设在 $D$ 维搜索空间中，有 $m$ 个粒子，其中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第 $i$ 个粒子的位置矢量 $\mathbf{x}&lt;em&gt;i = [x&lt;/em&gt;{i1}, x_{i2}, \dots, x_{iD}]$，&lt;/li&gt;
&lt;li&gt;其飞行速度也是一个矢量，记为 $\mathbf{v}&lt;em&gt;i = [v&lt;/em&gt;{i1}, v_{i2}, \dots, v_{iD}]$，&lt;/li&gt;
&lt;li&gt;第 $i$ 个粒子搜索到最优的位置为 $\mathbf{p}&lt;em&gt;i = [p&lt;/em&gt;{i1}, p_{i2}, \dots, p_{iD}]$，&lt;/li&gt;
&lt;li&gt;整个粒子群搜索到的最优位置为 $\mathbf{p}&lt;em&gt;{gbest} = [p&lt;/em&gt;{gbest1}, p_{gbest2}, \dots, p_{gbestD}]$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;对于第 $i$ 个粒子，它的速度更新公式为&lt;/strong&gt;
$$
v_{id}(t+1) = \underbrace{w \cdot v_{id}(t)}&lt;em&gt;{\text{速度惯性}} + \underbrace{c_1 \cdot rand_1 \cdot (p&lt;/em&gt;{id} - x_{id}(t))}&lt;em&gt;{\text{个体经验}} + \underbrace{c_2 \cdot rand_2 \cdot (p&lt;/em&gt;{gbest,d} - x_{id}(t))}_{\text{社会趋势}}
$$&lt;/p&gt;
&lt;p&gt;其中 $v_{id}(t+1)$ 表示第 $i$ 个粒子在第 $d$ 维上的新速度，$w$ 是惯性权重，控制粒子当前速度的影响，$c_1, c_2$ 是学习因子（加速常数），分别表示个体认知和群体协作的权重，$rand_1, rand_2$ 是在 $[0, 1]$ 范围内的随机数，增加随机性，$p_{id}$ 表示第 $i$ 个粒子在第 $d$ 维上的历史最优位置，$p_{gbest,d}$ 是全局最优位置在第 $d$ 维上的值，$x_{id}(t)$ 代表第 $i$ 个粒子在第 $d$ 维上的当前位置。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;而位置更新公式为&lt;/strong&gt;
$$
x_{id}(t+1) = x_{id}(t) + v_{id}(t+1)
$$&lt;/p&gt;
&lt;p&gt;其中，$x_{id}(t+1)$ 是第 $i$ 个粒子在第 $d$ 维上的新位置，$v_{id}(t+1)$ 是第 $i$ 个粒子在第 $d$ 维上的新速度。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/pso_animation.gif&quot; alt=&quot;pso&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=5&amp;gt;Python 实现&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import numpy as np
from tqdm import tqdm

class PSO:
    def __init__(self, n_particles, n_dim, options, bounds=None):
        self.n_particles = n_particles
        self.n_dim = n_dim
        self.bounds = bounds

        self.w = options[&apos;w&apos;]   # 惯性权重
        self.c1 = options[&apos;c1&apos;] # 认知系数
        self.c2 = options[&apos;c2&apos;] # 社会系数

    def _init_swarm(self):
        # 如果有边界，在边界范围内初始化
        if self.bounds:
            min_bound, max_bound = self.bounds
            self.pos = np.random.uniform(
                low=min_bound,
                high=max_bound,
                size=(self.n_particles, self.n_dim)
            )
        
            velocity_range = np.array(max_bound) - np.array(min_bound)
            self.velocity = np.random.uniform(
                low=-velocity_range,
                high=velocity_range,
                size=(self.n_particles, self.n_dim)
            ) * 0.1 # 初始速度限制在较小范围

        else:
            # 如果没有边界，使用标准初始化
            self.pos = np.random.uniform(
                low=-1,
                high=1,
                size=(self.n_particles, self.n_dim)
            )
            self.velocity = np.random.uniform(
                low=-0.1,
                high=0.1,
                size=(self.n_particles, self.n_dim)
            )

        # 初始化每个粒子个体的最佳位置和适应度
        self.pbest_pos = self.pos.copy()
        self.pbest_cost = np.full(self.n_particles, float(&apos;inf&apos;))

        # 初始化全局最佳位置和适应度
        self.gbest_pos = np.zeros(self.n_dim)
        self.gbest_cost = float(&apos;inf&apos;)

        # 记录每次迭代的最佳适应度值
        self.cost_history = []
        self.pos_history = []

    def reset(self):
        # 重置粒子群到初始状态
        self._init_swarm()

    def optimize(self, objective_func, n_iters=100, verbose=False):
        self.reset()
        for i in tqdm(range(n_iters)):
            # 计算当前粒子群的适应度值
            curr_cost = np.array(objective_func(self.pos))

            # 更新个体最佳
            mask = curr_cost &amp;lt; self.pbest_cost
            self.pbest_pos[mask] = self.pos[mask].copy()    # 是矩阵
            self.pbest_cost[mask] = self.pbest_cost[mask]

            # 更新全局最佳
            min_cost_idx = np.argmin(curr_cost)
            if curr_cost[min_cost_idx] &amp;lt; self.gbest_cost:
                self.gbest_cost = curr_cost[min_cost_idx]
                self.gbest_pos = self.pos[min_cost_idx].copy()

            # 保存历史记录
            self.cost_history.append(self.gbest_cost)
            self.pos_history.append(self.gbest_pos.copy())

            if verbose and i % 10 == 0:
                print(f&apos;Iteration {i}: Best cost = {self.gbest_cost}&apos;)

            # 更新速度和位置
            r1 = np.random.random((self.n_particles, self.n_dim))
            r2 = np.random.random((self.n_particles, self.n_dim))

            cognitive = self.c1 * r1 * (self.pbest_pos - self.pos)
            social = self.c2 * r2 * (self.gbest_pos - self.pos)

            self.velocity = self.w * self.velocity + cognitive + social
            self.pos = self.pos + self.velocity

            # 边界处理
            if self.bounds:
                min_bound, max_bound = self.bounds
                self.pos = np.clip(self.pos, min_bound, max_bound)

                # 对于撞到边界的粒子，将其速度反向
                out_of_bounds_lower = self.pos &amp;lt;= min_bound
                out_of_bounds_upper = self.pos &amp;gt;= max_bound
                self.velocity[out_of_bounds_lower | out_of_bounds_upper] *= -0.5
        return self.gbest_cost, self.gbest_pos
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参数分析&lt;/h2&gt;
&lt;h3&gt;惯性权重 $w$&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使粒子保持自身运动惯性，符合现实个体行为的连续型&lt;/li&gt;
&lt;li&gt;较大的惯性权重 $w$ 有助于跳出局部极值，而较小的 $w$ 则有利于算法收敛&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;加速常数 (学习因子) $c_1, c_2$&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;代表个体从&lt;strong&gt;个体历史最优经验&lt;/strong&gt;和当前&lt;strong&gt;群体趋势&lt;/strong&gt;的学习权重&lt;/li&gt;
&lt;li&gt;粒子会从&lt;strong&gt;个体历史最优经验&lt;/strong&gt;和当前&lt;strong&gt;群体趋势&lt;/strong&gt;学习知识&lt;/li&gt;
&lt;li&gt;较低的学习因子 $c_1, c_2$ 允许个体在被拉回之前可以在目标区域外徘徊，而较高的学习因子则会导致粒子突然冲向目标区域（甚至越过目标区域）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;最大速度 $v_{max}$&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;最大速度可以限制粒子在一次迭代中的最大移动距离（在上面的代码中没有使用）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;特点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;属于仿生算法&lt;/li&gt;
&lt;li&gt;属于全局优化算法&lt;/li&gt;
&lt;li&gt;属于随机搜索算法&lt;/li&gt;
&lt;li&gt;隐含并行性&lt;/li&gt;
&lt;li&gt;根据个体的适配信息进行搜索，因此不要求目标函数具有可微性、连续型等&lt;/li&gt;
&lt;li&gt;对高维复杂问题，往往会遇到早熟收敛和收敛性差的缺点&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;改进&lt;/h2&gt;
&lt;p&gt;变化的惯性权重：惯性权重随迭代次数的增加而减小&lt;/p&gt;
&lt;p&gt;收敛因子模型：收敛因子模型引入了一个额外的收敛因子 $\chi$，用于调整速度更新公式。速度更新公式变为
$$
v_{id}(t+1) = \chi \cdot \left[ w \cdot v_{id}(t) + c_1 \cdot rand_1 \cdot (p_{id} - x_{id}(t)) + c_2 \cdot rand_2 \cdot (p_{gbest,d} - x_{id}(t)) \right]
$$
其中，收敛因子 $\chi = \frac{2}{\lvert 2 - \phi - \sqrt{\phi^2 - 4\phi} \rvert}, \quad \phi = c_1 + c_2, \quad \phi &amp;gt; 4$&lt;/p&gt;
&lt;h2&gt;应用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;神经网络训练：不需要使用梯度信息，多属性情况下训练结果优于 BP 算法，且训练速度非常快（但是高维空间中效果不好，因此不流行）&lt;/li&gt;
&lt;li&gt;模糊控制、机器人路径规划等&lt;/li&gt;
&lt;li&gt;组合优化&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;PSO 求解 TSP 问题&lt;/h2&gt;
&lt;h3&gt;交换子与交换序&lt;/h3&gt;
&lt;p&gt;设 $n$ 个节点的 TSP 问题的一个解序列为 $s = [a_{1}, a_{2}, \dots, a_{n}]$，定义&lt;strong&gt;交换子&lt;/strong&gt; $SO(i_1, i_2)$ 为交换解 $s$ 中的点 $a_{i_1}$ 和 $a_{i2}$，得到的新解用 $s&apos; = s + SO(i_1, i_2)$ 表示。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;例如，$s + SO(1, 2) = [a_2, a_1, a_3, \dots, a_n]$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;定义&lt;strong&gt;交换序&lt;/strong&gt;是一个或多个交换子的有序队列，记为 $SS = (SO_1, SO_2, \dots, SO_N)$，其中 $SO_i, \ i = 1, 2, \dots, N$ 是交换子，之间的顺序是有意义的，各个交换子依次作用于解上。&lt;/p&gt;
&lt;p&gt;定义交换序之间的&lt;strong&gt;合并运算&lt;/strong&gt;为 $\oplus$，有
$$
SS&apos; = SS_1 \oplus SS_2
$$
其中，$SS_1, SS_2$ 均为交换序，将 $SS&apos;$ 作用与解 $s$ 上相当于 将$SS_1$ 先作用在 $s$ 上后再用 $SS_2$ 作用在解 $s$ 上。因为作用相同，所以称 $SS&apos;$ 与 $SS_1 \oplus SS_2$ 属于同一&lt;strong&gt;等价集&lt;/strong&gt;。在交换序等价集中，拥有交换子的个数最少的交换序称为该等价集的&lt;strong&gt;基本交换序&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;PSO 更新公式&lt;/h3&gt;
&lt;p&gt;$$
\begin{cases}
v_{id}(t+1) = v_{id}(t) \oplus \alpha (p_{id}(t) - x_{id}(t)) \oplus \beta (p_{gd}(t) - x_{id}(t))\
x_{id}(t+1) = x_{id}(t) + v_{id}(t+1)&lt;/p&gt;
&lt;p&gt;\end{cases}
$$&lt;/p&gt;
&lt;p&gt;其中，$\alpha, \beta$ 是区间 $[0, 1]$ 上的随机数，$v_{id}$ 表示交换序，$x_{id}$ 表示路径序列（解）。&lt;/p&gt;
&lt;p&gt;于是可以套用之前的 PSO 算法进行求解。&lt;/p&gt;
</content:encoded></item><item><title>禁忌搜索</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/tabusearch/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/tabusearch/</guid><pubDate>Thu, 17 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;禁忌搜索是对人类大脑的记忆功能进行模仿的&lt;/p&gt;
&lt;h1&gt;概述&lt;/h1&gt;
&lt;p&gt;禁忌搜索算法的一个主要特点是算法具有&lt;strong&gt;记忆性&lt;/strong&gt;，能够对已经搜索到的解进行维护和选择性回避。算法会维护一个禁忌表，通过不断地&lt;strong&gt;加入新的禁忌对象&lt;/strong&gt;和&lt;strong&gt;解禁旧的禁忌对象&lt;/strong&gt;，实现避免重复在一个局部最优解附近进行的过多的无意义操作，从而扩大搜索空间，找到全局最优解。&lt;/p&gt;
&lt;h1&gt;局部搜索&lt;/h1&gt;
&lt;h2&gt;邻域&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在函数型优化问题中，邻域被定义为以某点为中心的距离不超过一定阈值的球体。&lt;/li&gt;
&lt;li&gt;在组合优化问题中，定义邻域映射为 $N: x\in D \rightarrow N(x) \in 2^D$，其中 $2^D$ 表示集合 $D$ 的全部子集做并集运算形成的集合，显然有 $x \in N(x)$。这里称 $N(x)$ 为 $x$ 的邻域，若 $y \in N(x)$，则 $y$ 为 $x$ 的一个邻居。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如，TSP 问题中，定义邻域映射为一个排列 $x$ 的两个元素交换 (2-opt) 后的结果。比如，若 $x = (1, 2, 3, 4)$，则 $N(x) = {(1, 2, 3, 4), (2, 1, 3, 4), (3, 2, 1, 4), (4, 2, 3, 1), (1, 3, 2, 4), (1, 4, 3, 2), (1, 2, 4, 3)}$ 共 $C_4^2 = 6$ 个元素。此外，这个定义也可以推广，比如定义排列中的 $k$ 个元素进行重新排列 (k-opt) 后的结果形成的集合为排列 $x$ 的邻域 $N(x)$.&lt;/p&gt;
&lt;h2&gt;局部搜索算法&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=5&amp;gt;Algorithm Local Search&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;问题的解空间 $D$，初始解 $x_0 \in D$，目标函数 $f$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;局部最优解 $x_{best}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;选定一个可行解 $x_0$, 记录当前的最优解为 $x_{best} = x_0, T = N(x_{best})$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;当终止条件未满足时：&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;   从 $T - {x_{best}}$ 中选择一个使目标函数值最好的解 $x&apos;$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   如果在去心邻域中找不到比当前解更好的解（即对于最大化问题，$f(x&apos;) \leq f(x_{best})$），则终止算法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;   否则，令 $x_{best} = x&apos;$，$T = N(x_{best})$，继续下一次迭代&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;返回局部最优解 $x_{best}$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一次的局部搜索算法无法保证全局最优性，特别依赖于初始点的选取和邻域的结构设置。当初始点选取的足够多时，倒也可以计算出全局最优解。&lt;/p&gt;
&lt;h1&gt;禁忌搜索&lt;/h1&gt;
&lt;p&gt;禁忌搜索的核心机制在于记忆，体现在以下几个方面&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;禁忌对象（Tabu Object，TO）&lt;/strong&gt;：禁忌表中记录的元素，可以是完整的解、解的特定属性或导致解变化的移动操作。在实际应用中，通常不会存储完整的解（因为存储开销大），而是记录解的某些关键属性或变换操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;禁忌表（Tabu List，TL）&lt;/strong&gt;：用来存放（记忆）禁忌对象的表，是禁忌搜索算法的基本前提。禁忌表有容量限制，其大小会影响存放禁忌对象的个数，从而影响算法性能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特赦准则(Aspiration Criteria)&lt;/strong&gt;：即使某个移动在禁忌表中，如果它能够带来比当前已知最优解更好的结果，也可以被接受。用于在所有对象都被禁忌时解禁性能最好的对象，或者当解禁某个对象会带来显著改进时使用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;禁忌长度&lt;/strong&gt;：每个禁忌对象在禁忌表中停留的时间长度，表示该对象在多少次迭代内不能被重复选择。禁忌长度可以是固定的，也可以是动态调整的。禁忌长度短难以跳出局部最优，紧急长度过长会增加计算时间。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=5&amp;gt;Algorithm Tabu Search&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;问题的解空间 $D$，初始解 $x_0 \in D$，目标函数 $f$，禁忌表大小 $L$，最大迭代次数 $MaxIter$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;全局最优解 $x_{best}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化当前解 $x_{current} = x_0$，全局最优解 $x_{best} = x_0$，禁忌表 $TabuList = \emptyset$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;设置迭代计数器 $iter = 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;当 $iter &amp;lt; MaxIter$ 时执行以下操作：&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   从 $N(x_{current})$ 中找出所有候选移动，构成候选集合 $C$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;   从 $C$ 中选择一个解 $x&apos;$，满足：$x&apos;$ 不在禁忌表中，或者 $x&apos;$ 满足特赦准则（$f(x&apos;) &amp;gt; f(x_{best})$）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;   更新当前解 $x_{current} = x&apos;$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;   如果 $f(x_{current}) &amp;gt; f(x_{best})$，则更新全局最优解 $x_{best} = x_{current}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;   将导致 $x_{current}$ 的移动或属性加入禁忌表，如果禁忌表长度超过 $L$，则移除最早加入的元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;   $iter = iter + 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;返回全局最优解 $x_{best}$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Tabu Search 求解 TSP 问题可视化结果
&lt;img src=&quot;assets/tabu_search_tsp.gif&quot; alt=&quot;TSP-tabu&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>强化学习 Chapter 3 - Monte Carlo 方法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter3/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter3/</guid><pubDate>Thu, 17 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;3.1 背景描述&lt;/h1&gt;
&lt;p&gt;当把强化学习的问题建模为一个马尔可夫决策过程（MDP）时，我们如果拥有一个完整的环境模型（即状态转移概率和奖励函数），那么就可以用动态规划的方法来求解最优策略。然而，现实中，我们很难能够得到一个完整的环境模型。我们往往只能通过与真实环境不断交互获得一个个的交互数据，并从中来学习到一个近似的环境模型。在这种情况下，是用动态规划方法求解最优策略一定是会有偏差的。如果能够找到一种不需要环境模型的最优策略求解方法，就可以绕开环境模型的估计，从而学习到一个最优策略。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Monte Carlo 方法就是一种 Model-free 的方法，是首个从经验中学习最优策略的方法。&lt;/strong&gt; 它的理念是通过一幕幕的交互数据（&lt;strong&gt;注意，也因此朴素的 Mote Carlo 方法只适用于分幕式任务，因为必须要有终止状态&lt;/strong&gt;），利用&lt;em&gt;价值估计可以用平均回报得到&lt;/em&gt;的思想来估计状态的价值函数。&lt;/p&gt;
&lt;h1&gt;3.2 朴素的 Monte Carlo 方法&lt;/h1&gt;
&lt;h2&gt;3.2.1 采样代替期望&lt;/h2&gt;
&lt;p&gt;假设当前有一个策略 $\pi$，我们可以通过与环境交互得到一系列的状态-动作-奖励序列，记为轨迹 $\tau = (s_0, a_0, r_1, s_1, a_1, r_2, \ldots, s_T) \sim \pi$，于是我们使用
$$
\begin{align*}
g_t &amp;amp;= r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \ldots\
&amp;amp;= r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \ldots + \gamma^{T-t} r_T
\end{align*}
$$
来表示从 $t$ 时刻开始到终止状态的回报。于是这样得到的一个轨迹的回报 $g_t$ 就是回报随机变量 $G_t$ 的一个样本。我们可以通过多次采样来估计 $G_t$ 的期望值。&lt;/p&gt;
&lt;p&gt;于是，状态价值函数的估计也就可以用如下式子完成
$$
\begin{align*}
V(s) &amp;amp;= \mathbb{E}&lt;em&gt;{\pi} [G_t | S_t = s] \
&amp;amp;\approx \frac{1}{N} \sum&lt;/em&gt;{i=1}^N g_t^{(i)}
\end{align*}
$$
这里的 $N$ 是轨迹的数量，$g_t^{(i)}$ 是第 $i$ 条轨迹下的从状态 $s$ 开始的回报。&lt;/p&gt;
&lt;h2&gt;3.2.2 迭代均值更新&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;结论 均值迭代化计算&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于一个序列 $(x_1, x_2, \dots, x_n)$，若欲计算其前缀子序列 $(x_1, \dots, x_k)\ (\forall k = 1, 2, \dots, n)$ 的均值，可以采用如下迭代方式
$$
\mu_k = \mu_{k-1} + \frac{1}{k}(x_k - \mu_{k-1})
$$
其中 $\mu_1 = x_1$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;推导：&lt;/strong&gt;
$$
\begin{align*}
\mu_k &amp;amp;= \frac{1}{k} \sum_{j=1}^{k} x_j = \frac{1}{k}(x_k + \sum_{j=1}^{k-1}x_j) \
&amp;amp;= \frac{1}{k} (x_k + (k-1)\mu_{k-1})\
&amp;amp;= \mu_{k-1} + \frac{1}{k} (x_k - \mu_{k-1})
\end{align*}
$$&lt;/p&gt;
&lt;h2&gt;3.2.3 增量式 MC 价值估计&lt;/h2&gt;
&lt;p&gt;当智能体与环境交互结束后，得到一段交互轨迹 $\tau = (s_1, a_1, r_1, \dots, s_T, a_T, r_T)$，那么可以用这段轨迹的经验数据逐步更新轨迹中出现的每一个状态 $s_t$，利用
$$
\begin{align*}
&amp;amp;N(s_t) \leftarrow N(s_T) + 1\
&amp;amp;V(s_t) \leftarrow V(s_t) + \frac{1}{N(s_t)}(g_t - V(s_t))
\end{align*}
$$
来实现价值函数的更新。这里的迭代更新方式利用了上述的结论。&lt;/p&gt;
&lt;p&gt;不过，对于非平稳问题（即环境会随时间发生变化），利用均值更新学习的效果不好。此时可以采用一个常数 $\alpha$ (类似学习率) 来代替 $1 / N(s_t)$，来跟踪一个现阶段的平均值（即学习当前时间局域内的价值）
$$
V(s_t) \leftarrow V(s_t) + \alpha (g_t  - V(s_t))
$$&lt;/p&gt;
&lt;h1&gt;3.3 基于 Monte Carlo 的强化学习&lt;/h1&gt;
&lt;h2&gt;3.3.1 策略评估&lt;/h2&gt;
&lt;p&gt;目标是在给定策略 $\pi$ 下经验片段 $\tau = (s_1, a_1, r_1, \dots, s_T, a_T, r_T)$ 中学习状态价值函数 $V_\pi(s)$，思想是用采样的经验数据的平均回报来代替有环境模型计算的期望回报。&lt;/p&gt;
&lt;p&gt;但是存在一个问题，即在一段轨迹中，&lt;strong&gt;智能体可能从开始到终止会多次访问某几个状态&lt;/strong&gt;。由此，估计的方法分为 &lt;strong&gt;首次访问MC (First visit MC)&lt;/strong&gt; 和 &lt;strong&gt;每次访问MC (Every visit MC)&lt;/strong&gt; 两种。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Monte Carlo算法对于每个状态的估计是独立的，它对于一个状态的估计并不依赖于对其他状态的估计，这与DP完全不同。这也说明了MC方法没有 &lt;em&gt;自举思想&lt;/em&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;首次访问型 MC 策略评估&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;First Visit MC 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;待评估的策略 $\pi$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;状态价值函数 $V_\pi(s)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化 $V(s) \in \mathbb R$ 和 回报 $Returns(s)$ 为空列表 对于所有的状态 $s \in \mathcal{S}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;  根据策略 $\pi$ 生成轨迹 $\tau = (s_1, a_1, r_1, \dots, s_T, a_T, r_T)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;  初始化 $G \leftarrow 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;for&lt;/strong&gt; $t = T, T-2, \dots, 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;     $G \leftarrow \gamma G + r_{t}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;     若 $s_t$ 在之前的状态 $s_0, s_1, \dots, s_{t-1}$ 中没有出现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;      将 $G$ 添加到 $Returns(s_t)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;      $V(s_t) \leftarrow average(Returns(s_t))$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;令 $V_\pi(s) = V(s),\ \forall s \in \mathcal{S}$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;可以发现，这个算法有如下细节：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;反向迭代计算回报 $G$，因为反向计算比争先计算效率更高。&lt;/li&gt;
&lt;li&gt;维护一个 $Returns(s_t)$ 的列表是因为估计状态价值不能单纯只靠一次的经验数据就可以得到&lt;/li&gt;
&lt;li&gt;首次访问，排除第二次访问的状态估计&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;每次访问型 MC 策略评估&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Every Visit MC 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;待评估的策略 $\pi$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;状态价值函数 $V_\pi(s)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化 $V(s) \in \mathbb R$ 和 回报 $Returns(s)$ 为空列表 对于所有的状态 $s \in \mathcal{S}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;  根据策略 $\pi$ 生成轨迹 $\tau = (s_1, a_1, r_1, \dots, s_T, a_T, r_T)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;  初始化 $G \leftarrow 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;for&lt;/strong&gt; $t = T, T-2, \dots, 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;     $G \leftarrow \gamma G + r_{t}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;    将 $G$ 添加到 $Returns(s_t)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;    $V(s_t) \leftarrow average(Returns(s_t))$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;令 $V_\pi(s) = V(s),\ \forall s \in \mathcal{S}$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;动作价值的估计&lt;/h3&gt;
&lt;p&gt;很遗憾，上述对状态价值函数的估计方法并不适用于动作价值函数的估计。因为，动作价值函数是关于状态和动作的二元函数，然而，在一段经验轨迹中，智能体可能从未访问过某些确定的状态-动作对，这就导致了这些状态-动作无法被估计。&lt;/p&gt;
&lt;p&gt;当然，我们也可以利用 Bellman 方程，使用状态价值函数来求解动作价值函数
$$
\begin{align*}
q_\pi(s, a) = \mathbb{E}&lt;em&gt;{s&apos;, r}[r + \gamma V&lt;/em&gt;\pi(s&apos;) | s, a]
\end{align*}
$$&lt;/p&gt;
&lt;p&gt;不过，还有其他的方法。既然我们需要保证智能体的轨迹中能访问每个状态-动作对，不妨设置一个让智能体能够从任意一个状态以任意一个动作开始的轨迹，运用这个轨迹来估计动作价值函数，这个方法称为 &lt;em&gt;Explore-Start&lt;/em&gt;. 这个方法将与策略改进放在一起使用。&lt;/p&gt;
&lt;h2&gt;3.3.2 策略改进&lt;/h2&gt;
&lt;p&gt;有了当前最新的价值函数，就可以使用贪心方法来改进当前策略。
$$
\pi&apos;(s) = \arg\max_{a\in \mathcal{A}} Q_\pi(s, a)
$$&lt;/p&gt;
&lt;h3&gt;Monte Carlo Explore-Start&lt;/h3&gt;
&lt;p&gt;同时维护一个近似策略和近似的价值函数，彼此为对方设定优化目标。从而，价值函数不断迭代逼近当前策略的真实价值函数，当前的策略也会根据当前的价值函数不断优化。这个算法框架被称作 &lt;strong&gt;广义策略迭代 GPI&lt;/strong&gt;。（实际上就是 EM 算法的思想）&lt;/p&gt;
&lt;p&gt;利用这个思想，得到如下 MC ES 算法&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Monte Carlo Explore-Start 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$\gamma$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;最优策略 $\pi$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;任意初始化策略 $\pi$ 和动作价值函数 $Q(s, a)$，为每个动作和状态创建空列表 $Returns(s, a)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;   随意选择初始状态 $s_0 \in \mathcal{S}$ 和初始动作 $a_0 \in \mathcal{A}$ // 这里不需要初始动作和状态的采样是遵循均匀分布的，但是要保证每个初始的动作和状态都有非0的概率访问到&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   从初始状态 $s_0$ 和 动作 $a_0$ 开始，利用策略 $\pi$ 生成一段轨迹 $\tau = (s_1, a_1, r_1, \dots, s_T, a_T, r_T)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5：&lt;/td&gt;
&lt;td&gt;   $G \leftarrow 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;for&lt;/strong&gt; $t = T, \dots, 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;     $G \leftarrow \gamma G + r_t$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;     若 $s_t, a_t$ 在 $s_0, a_0, \dots, s_{t-1}, a_{t-1}$ 中没有出现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;       把 $G$ 加入到 $Returns(s_t, a_t)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;       $Q(s_t, a_t) \leftarrow average(Returns(s_t, a_t))$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;       $\pi(s_t) = \arg\max_a Q(s_t, a)$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这里是 &lt;em&gt;Explore-Start&lt;/em&gt; + &lt;em&gt;First Vist&lt;/em&gt; 的策略迭代算法，需要时可修改为其他的组合。&lt;/p&gt;
&lt;p&gt;实际上这里维护一个 $Returns(s, a)$ 的列表并不高效，可以使用前述的迭代均值更新来减少内存复杂度。&lt;/p&gt;
&lt;h2&gt;3.3.3 没有试探性出发假设的 Monte Carlo 控制&lt;/h2&gt;
&lt;p&gt;如何避免很难被满足的试探性出发假设呢？唯一的一般性解决方案就是智能体可以持续不断地选择所有可能的动作。有两种方法可以保证这一点，分别是 &lt;strong&gt;同轨策略 (on-policy)&lt;/strong&gt; 和&lt;strong&gt;离轨策略 (off-policy)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Definition On-Policy&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在同轨策略种中，用于生成采样数据序列的策略和用于实际决策的待评估和改进的策略是相同的。&lt;/strong&gt; 同轨策略是在交互中学习，是自学。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Definition Off-Policy&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在离轨策略中，用于评估、改进的策略和用于生成采样数据的策略是不同的。&lt;/strong&gt; 有点类似有一个示教策略和一个学习策略，观察他人的行为进行学习。&lt;/p&gt;
&lt;h3&gt;同轨策略学习&lt;/h3&gt;
&lt;p&gt;在同轨策略方法中，一般会采用&quot;软性&quot;的策略，例如 $\epsilon$-greedy，在保证整体策略收敛到最优策略的同时，仍然以一定的概率随机选择动作。而正是这里的一定概率的随机动作选择保证了即使没有 &lt;em&gt;Explore-Start&lt;/em&gt; 的条件，智能体也能学习到所有 状态-动作 组合的价值函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;On-Policy First Visit MC Control with ε-greedy 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$\gamma$, $\epsilon$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;最优策略 $\pi$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;任意初始化策略 $\pi$ 和动作价值函数 $Q(s, a)$，为每个动作和状态创建空列表 $Returns(s, a)$，注意这里要保证策略初始化为一个 $\epsilon$-greedy 策略&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;   从初始状态 $s_0$ 和 动作 $a_0$ 开始，利用策略 $\pi$ 生成一段轨迹 $\tau = (s_1, a_1, r_1, \dots, s_T, a_T, r_T)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4：&lt;/td&gt;
&lt;td&gt;   $G \leftarrow 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;for&lt;/strong&gt; $t = T, \dots, 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;     $G \leftarrow \gamma G + r_t$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;     若 $s_t, a_t$ 在 $s_0, a_0, \dots, s_{t-1}, a_{t-1}$ 中没有出现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;       把 $G$ 加入到 $Returns(s_t, a_t)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;       $Q(s_t, a_t) \leftarrow average(Returns(s_t, a_t))$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;       更新策略 $\pi(a \vert s_t) = \begin{cases} 1 - \epsilon + \frac{\epsilon}{\vert \mathcal{A}(s_t)\vert}, &amp;amp;\quad \text{if } a = \arg\max_aQ(s_t, a) \ \frac{\epsilon}{\mathcal{A}(s_t)}, &amp;amp;\quad \text{otherwise}\end{cases} \forall a \in \mathcal{A(s_t)}$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这个方法非常类似 MC-ES 算法，区别就是一个需要能从任意的动作-状态组合出发，一个可以从固定的动作-状态组合出发但是策略必须是 $\epsilon$-贪心。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Example Problem 证明 ε-贪心策略能够改进策略&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;假设 $\pi&apos;$ 是基于 $\pi$ 用 $\epsilon$-贪心更新的。有
$$
\begin{align*}
V_\pi&apos;(s) &amp;amp;= \sum_a \pi&apos;(a | s) Q_\pi(s, a) \
&amp;amp;= \frac{\epsilon}{|\mathcal{A(s)}|} \sum_a Q_\pi(s, a) + (1 - \epsilon) \max_a Q_\pi(s, a) \
&amp;amp;\ge \frac{\epsilon}{\mathcal{A}(s)} \sum_a Q_\pi(s, a) + (1 - \epsilon) \sum_a \frac{\pi(a | s) - \epsilon / m}{ 1 - \epsilon} Q_\pi(s, a) \
&amp;amp;= \sum_a \pi(a | s) Q_\pi(s, a) = V_\pi(s)
\end{align*}
$$&lt;/p&gt;
&lt;p&gt;由此得出策略 $\pi&apos;$ 比策略 $\pi$ 好。&lt;/p&gt;
&lt;h3&gt;离轨策略学习&lt;/h3&gt;
&lt;p&gt;离轨策略的方法比同轨策略方法的&lt;strong&gt;方差要大&lt;/strong&gt;，而且&lt;strong&gt;收敛速度要慢&lt;/strong&gt;，但是它的通用性更好。这是因为采用上述的同轨策略例如 $\epsilon$-greedy 的方法时，存在一个问题。为了克服 &lt;em&gt;Explore-Start&lt;/em&gt; 的要求，采用了以 $\epsilon$ 的概率随机选择动作的技巧，。但是，&lt;strong&gt;正是这个技巧导致了当策略收敛到最优策略附近时，智能体仍然有一定的概率会选择十分愚蠢的行为，必须以较小的可能去选择非最优的动作&lt;/strong&gt;。很显然，这是不合适的，尤其是当智能体有可能会做出损害自己或他人的危险的事情的情况下。&lt;/p&gt;
&lt;p&gt;于是我们考虑使用两个策略来克服 &lt;em&gt;Explore-Start&lt;/em&gt; 的问题。让执行动作的策略 $\mu$ 去产生轨迹，然后用于评估当前的目标策略 $\pi$，然后改进目标策略 $\pi$。不过，由于两个策略不同，如果直接拿 $\mu$ 产生的轨迹数据来更新 $\pi$ 的话，很可能导致在 $\pi$ 自身需要重点关注的地方没怎么改进，反而在不重要的地方有较大的更新。&lt;/p&gt;
&lt;p&gt;因此，需要使用&lt;strong&gt;基于重要度采样的技巧&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Theorem 重要性采样&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;数学中，重要度采样的公式为
$$
\mathbb{E}&lt;em&gt;{x \sim p} [f(x)] = \int_x p(x) f(x)dx = \int_x q(x) \frac{p(x)}{q(x)} f(x) dx = \mathbb{E}&lt;/em&gt;{x\sim q} \left[\frac{p(x)}{q(x)}f(x)\right]
$$
其中，重要性权重为 $\beta(x) = \frac{p(x)}{q(x)}$&lt;/p&gt;
&lt;p&gt;根据
$$
p_\pi(a_t, s_{t + 1}, a_{t+1}, \dots, s_T | s_t) = \pi(a_t | s_t) p(s_{t+1} | s_t, a_t) \pi(a_{t+1} | s_{t+1}) \dots p(s_T | s_{T-1}, a_{T-1})
$$
构造重要性权重为
$$
\begin{align*}
\rho_{t:T-1} &amp;amp;= \frac{p_\pi(a_t, s_{t + 1}, a_{t+1}, \dots, s_T | s_t)}{p_\mu(a_t, s_{t + 1}, a_{t+1}, \dots, s_T | s_t)}  \
&amp;amp;= \frac{\pi(a_t | s_t) p(s_{t+1} | s_t, a_t) \pi(a_{t+1} | s_{t+1}) \dots p(s_T | s_{T-1}, a_{T-1})}{\mu(a_t | s_t) p(s_{t+1} | s_t, a_t) \mu(a_{t+1} | s_{t+1}) \dots p(s_T | s_{T-1}, a_{T-1})}\
&amp;amp;= \prod_{k=t}^{T-1}\frac{\pi(a_k | s_k)}{\mu(a_k |s_k)}
\end{align*}
$$
注意看，虽然 $p_\pi(a_t, s_{t + 1}, a_{t+1}, \dots, s_T | s_t)$ 需要环境模型，但是在重要性权重中，由于环境模型的概率被约去，因此实际上并不需要状态转移概率。于是
$$
V_\pi(s) = \mathbb{E}&lt;em&gt;{\tau \sim \pi} \left[G_t | s_t = s \right] = \mathbb{E}&lt;/em&gt;{\tau \sim \mu} \left[\rho_{t:T-1} G_t | s_t = s\right]
$$&lt;/p&gt;
&lt;p&gt;有两种方法可以估计 $\mathbb{E}&lt;em&gt;{\tau \sim \mu} \left[\rho&lt;/em&gt;{t:T-1} G_t | s_t = s\right]$，分别是 &lt;strong&gt;普通重要度采样&lt;/strong&gt; 和 &lt;strong&gt;加权重要度采样&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;普通重要度采样为
$$
\hat V^{\mathrm{IS}}&lt;em&gt;\pi(s)
= \frac{1}{N}\sum&lt;/em&gt;{i=1}^N \rho_{t:T(i)-1}^{(i)},G_t^{(i)},
$$
它保持了&lt;strong&gt;无偏性&lt;/strong&gt; ($\mathbb{E}[\rho G] = V_\pi$)，但是由于权重 $\rho$ 的方差极大，学习过程中仍然有剧烈抖动和发散等不稳定的情况存在。其中 $N$ 为轨迹数量。&lt;/p&gt;
&lt;p&gt;加权重要度采样为
$$
\hat V^{\mathrm{WIS}}&lt;em&gt;\pi(s)
= \sum&lt;/em&gt;{i=1}^N \frac{\rho_{t:T(i)-1}^{(i)}}{\sum_{j=1}^N \rho_{t:T(i)-1}^{(j)}},G_t^{(i)}.
$$
这种方法显著降低了方差，因为权重被整体缩放，不至于因单条轨迹的超大权重而主导估计；但归一化也带来了有偏性（估计值收敛到行为策略下的某种加权平均，而非严格的 $V_\pi$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;离轨策略的策略评估 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;策略 $\pi$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;动作价值函数 $Q_\pi(s, a)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;任意初始化 $Q(s, a) \in \mathcal{R}$，令计数器 $C(s, a) \leftarrow 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;   $\mu \leftarrow$ 任何能包括 $\pi$ 的策略 （覆盖性假设，即行为策略 $\mu$ 必须在所有状态 $s$ 下对目标策略 $\pi$ 的所有动作 $a$ 赋予非零概率，即行为策略 $\mu$ 必须&quot;覆盖&quot;目标策略 $\pi$ 的所有可能行为， $\forall s, a, \pi(a\vert s) &amp;gt; 0 \Rightarrow \mu(a \vert s) &amp;gt; 0\ $）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   根据策略 $\mu$ 生成一幕数据 $\tau = (s_1, a_1, r_1, \dots, s_T, a_T, r_T)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;   $G\leftarrow 0$ 和 $W \leftarrow 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6：&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;for&lt;/strong&gt; $ t = T, \dots, 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;     当 $W \ne 0$ 时&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;       $G \leftarrow \gamma G + r_t$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;       $C(s_t, a_t) \leftarrow C(s_t, a_t) + W$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;       $Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \frac{W}{C(s_t, a_t)}[G - Q(s_t, a_t)]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;       $W \leftarrow W \frac{\pi(a_t \vert s_t)}{\mu(a_t \vert s_t)}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:&lt;/td&gt;
&lt;td&gt;    否则，若 $W = 0$，退出循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:&lt;/td&gt;
&lt;td&gt;令 $Q_\pi(s, a) = Q(s, a),\ \forall s \in \mathcal{S}, \forall a \in \mathcal{A}$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这其中第 3 行的覆盖性假设是为了保证重要性采样比率 $\frac{\pi(a|s)}{\mu(a|s)}$ 的分母不会为 0.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;离轨策略的 Monte Carlo 控制 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$\gamma$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;最优策略 $\pi$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;任意初始化 $Q(s, a) \in \mathcal{R}$，令计数器 $C(s, a) \leftarrow 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;令策略 $\pi(s) \leftarrow \arg\max_a Q(s, a)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;  $\mu \leftarrow$ 任何 $\epsilon$-greedy 策略&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;  根据策略 $\mu$ 生成一幕数据 $\tau = (s_1, a_1, r_1, \dots, s_T, a_T, r_T)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;  $G\leftarrow 0$ 和 $W \leftarrow 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7：&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;for&lt;/strong&gt; $ t = T, \dots, 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;     当 $W \ne 0$ 时&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;       $G \leftarrow \gamma G + r_t$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;       $C(s_t, a_t) \leftarrow C(s_t, a_t) + W$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;       $Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \frac{W}{C(s_t, a_t)}[G - Q(s_t, a_t)]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:&lt;/td&gt;
&lt;td&gt;       $\pi(s_t) = \arg\max_a Q(s_t, a)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:&lt;/td&gt;
&lt;td&gt;       如果 $a_t \ne \pi(s_t)$，则退出循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14:&lt;/td&gt;
&lt;td&gt;       $W \leftarrow W \frac{1}{\mu(a_t \vert s_t)}$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;3.3.4 一些问题&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;为什么做动作选择使用的是 动作价值函数，而不是状态价值函数？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  如果基于状态价值函数选择来更新策略，公式为
$$
\pi(s) = \arg\max_{a \in \mathcal{A}(s)} \sum_{s&apos; \in \mathcal{S}} p(s&apos; | s, a) V(s&apos;)
$$
这里的公式显然需要环境模型 $p(s&apos; | s, a)$。但是如果采用动作价值函数，就不需要环境信息了
$$
\pi(s) = \arg\max_a Q(s, a)
$$
这里的策略用 $\pi(s)$ 表示是因为采用的是贪心策略，这样表示比概率更方便。&lt;/p&gt;
&lt;p&gt;注意，$Q(s, a) = \sum_{s&apos;} p(s&apos; | s, a) V(s&apos;)$。如果状态转移模型是一个确定性的模型，那么，$Q(s, a) = V(s&apos;),\ s&apos; = \text{transition}(s, a)$，那么此时用状态价值函数更新策略也是可以的。&lt;/p&gt;
</content:encoded></item><item><title>机器人学基础 第三章 逆向运动学</title><link>https://adalovelemon.github.io/posts/content/coursenotes/robotics/chapter-3/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/robotics/chapter-3/</guid><pubDate>Wed, 16 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;机械臂的逆向运动学&lt;/p&gt;
&lt;h1&gt;3.1 运动学逆解的目标&lt;/h1&gt;
&lt;p&gt;运动学逆解的目标是给定一个可能的末端位姿 $\ ^{S}_{E}T$，求出对应的各个关节变量 $q$.
$$
T^{-1}: SE(3) \rightarrow \mathbb{Q}
$$&lt;/p&gt;
&lt;p&gt;求解逆过程相比于求解正过程一般都是更加困难的一个过程（例如积分相较于微分困难），求运动学逆解也不例外。不似正向运动学，给定了关节变量的值，就一定可以确定末端位姿。求解运动学逆解，需要考虑几个问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;解是否存在&lt;/li&gt;
&lt;li&gt;解是否唯一&lt;/li&gt;
&lt;li&gt;如何求解&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;将方程 $\ ^{S}&lt;em&gt;{E}T(q) = \begin{bmatrix} r&lt;/em&gt;{11} &amp;amp; r_{12} &amp;amp; r_{13} &amp;amp;p_x \ r_{21} &amp;amp; r_{22} &amp;amp; r_{23} &amp;amp;p_y \ r_{31} &amp;amp; r_{32} &amp;amp; r_{33} &amp;amp;p_z \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix}$ 拆开，可以得到 6 个非线性超越方程（末端位姿是 $6$ 自由度的），而需要求解 $N$ 个关节变量（$N$ 通常取 $6, 4, 5$或$7$等）&lt;/p&gt;
&lt;h1&gt;3.2 解的存在性&lt;/h1&gt;
&lt;p&gt;为了解决解是否存在的问题，定义&lt;strong&gt;工作空间为机械臂的末端执行器能够到达的空间范围&lt;/strong&gt;，即
$$
\mathbb{W} = {^{S}_{E}T(q) \in SE(3)| \forall q = \begin{bmatrix} q_1 \ q_2 \\vdots \ q_n\end{bmatrix}\in \mathbb{Q}}
$$
十分自然地，可以得出解的存在性结论&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解的存在性: 只有当给定的机械臂末端位姿 $^{S}_{E}T$ 位于工作空间中时运动学逆解才存在&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/workspace.png&quot; alt=&quot;工作空间&quot; /&gt;&lt;/p&gt;
&lt;p&gt;有时我们关注的更多的是给定末端坐标然后求解对应的关节变量的情形，此时也可以定义工作空间，不过这个工作空间就变成了末端坐标的集合了。&lt;/p&gt;
&lt;h1&gt;3.3 多解性或解唯一性&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;具有&lt;strong&gt;冗余自由度&lt;/strong&gt;的机械臂 ($N &amp;gt; 6$) 的机械臂有无穷解&lt;/li&gt;
&lt;li&gt;通常对于一个给定位姿，逆解不止一个&lt;/li&gt;
&lt;li&gt;一般当末端位姿位于&lt;strong&gt;工作空间的边界上&lt;/strong&gt;时，逆解是&lt;strong&gt;唯一&lt;/strong&gt;的（例如机械臂完全伸展开）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;assets/multisolutions.png&quot; alt=&quot;multisolutions&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;3.4 求解运动学逆解&lt;/h1&gt;
&lt;p&gt;求解运动学逆解的方法主要分为解析法和数值法。需要指出的是数值法才是主流的求解逆解的方法，因为大部分机械臂很难有闭式解。不过闭式解的结果肯定是比数值法计算要快且准确的。&lt;/p&gt;
&lt;h2&gt;3.4.1 数值解法&lt;/h2&gt;
&lt;p&gt;设末端位姿 $T= \begin{bmatrix} r_{11} &amp;amp; r_{12} &amp;amp; r_{13} &amp;amp;p_x \ r_{21} &amp;amp; r_{22} &amp;amp; r_{23} &amp;amp;p_y \ r_{31} &amp;amp; r_{32} &amp;amp; r_{33} &amp;amp;p_z \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix}$ 已知，方程为
$$
T = \ ^{S}_{E}T(q)
$$&lt;/p&gt;
&lt;p&gt;可以使用 Newton 方法等数值优化方法进行方程求解（需要给定初始猜测、关节变量等的取值范围约束条件、收敛依据等）
$$
\min_{q\in\mathbb{Q}} \Vert^{S}_{E}T(q) - T\Vert^2
$$
这里不做赘述&lt;/p&gt;
&lt;h2&gt;3.4.2 解析解法&lt;/h2&gt;
&lt;p&gt;解析解法一般是通过建立与原始方程等价的代数或几何方程进行求解（一般是从中间关节处的运动学信息出发建立新的方程）&lt;/p&gt;
&lt;p&gt;下面将给出一些经典的机械臂的例子&lt;/p&gt;
&lt;h3&gt;平面 3R 机械臂&lt;/h3&gt;
&lt;p&gt;这是一个自由度为 $3$ (两个位置坐标，一个角度朝向) 的机构，其工作空间是 $SE(3)$ 的一个$3$维子空间&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/planar3R2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;根据前向运动学，可以得到其末端位姿关于三个旋转关节的旋转角度的方程为
$$
^{S}_{E}T(\theta_1, \theta_2, \theta_3) = \begin{bmatrix} \cos(\theta_1 + \theta_2 + \theta_3) &amp;amp; -\sin(\theta_1 + \theta_2 + \theta_3) &amp;amp; 0 &amp;amp; L_1 \cos\theta_1 + L_2 \cos(\theta_1 + \theta_2)+L_3 \cos(\theta_1 + \theta_2 + \theta_3) \
\sin(\theta_1 + \theta_2 + \theta_3) &amp;amp; \cos(\theta_1 + \theta_2 + \theta_3) &amp;amp; 0 &amp;amp; L_1 \sin\theta_1 + L_2 \sin(\theta_1 + \theta_2)+L_3 \sin(\theta_1 + \theta_2 + \theta_3) \
0 &amp;amp;0 &amp;amp;1 &amp;amp;0\
0 &amp;amp;0 &amp;amp;0 &amp;amp;1
\end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;假设
$$
^{S}_{E}T(\theta_1, \theta_2, \theta_3) = \begin{bmatrix} \cos\phi &amp;amp;-\sin\phi &amp;amp; 0 &amp;amp; x\
\sin\phi &amp;amp;\cos\phi &amp;amp;0 &amp;amp;y\
0 &amp;amp;0 &amp;amp;1 &amp;amp;0\
0 &amp;amp;0 &amp;amp;0 &amp;amp;1
\end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;其中 $L_1, L_2, L_3$ 均已只，则有
$$
\begin{cases}
&amp;amp;\phi = \theta_1 + \theta_2 + \theta_3\
&amp;amp;x = L_1 \cos\theta_1 + L_2 \cos(\theta_1 + \theta_2)+L_3 \cos(\theta_1 + \theta_2 + \theta_3)\
&amp;amp;y = L_1 \sin\theta_1 + L_2 \sin(\theta_1 + \theta_2)+L_3 \sin(\theta_1 + \theta_2 + \theta_3)
\end{cases}
$$&lt;/p&gt;
&lt;h4&gt;代数求解&lt;/h4&gt;
&lt;p&gt;构造
$$
\begin{cases}
&amp;amp;x&apos; = x - L_3\cos(\theta_1 + \theta_2 + \theta_3)\
&amp;amp;y&apos; = y - L_3\sin(\theta_1 + \theta_2 + \theta_3)
\end{cases}
$$
则有
$$
\begin{cases}
&amp;amp;\phi = \theta_1 + \theta_2 + \theta_3\
&amp;amp;x&apos; = L_1 \cos\theta_1 + L_2 \cos(\theta_1 + \theta_2)\
&amp;amp;y&apos; = L_1 \sin\theta_1 + L_2 \sin(\theta_1 + \theta_2)
\end{cases}
$$
于是，有
$$
x&apos;^2 + y&apos;^2 = L_1^2 + L_2^2 + 2L_1 L_2 \cos\theta_2
$$
可以求得
$$
\cos\theta_2 = \frac{x&apos;^2 + y&apos;^2 - L_1^2 - L_2^2}{2L_1 L_2} \in [-1, 1]
$$
这里需要检测 $\cos\theta_2$ 范围是否在 $[-1, 1]$ 内，&lt;strong&gt;如果不在，说明该逆解不存在&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;接着可以由此求出 $\theta_2$
$$
\begin{aligned}
\sin\theta_2 &amp;amp;= \pm\sqrt{1 - \cos\theta_2^2}\quad\text{(多解性)}\
\theta_2 &amp;amp;= \text{atan2}(\sin\theta_2, \cos\theta_2)
\end{aligned}
$$
接下来，展开 $x&apos;, y&apos;$，有
$$
\begin{cases}
&amp;amp;x&apos; = (L_1 + L_2\cos\theta_2)\cos\theta_1 - L_2\sin\theta_2\sin\theta_1\
&amp;amp;y&apos; = (L_1 + L_2\cos\theta_2)\sin\theta_1 + L_2\sin\theta_2\cos\theta_1
\end{cases}
$$
取 $r = \sqrt{(L_1 + L_2\cos\theta_2)^2 + (L_2\sin\theta_2)^2}$，并令
$$
\begin{aligned}
\cos\gamma &amp;amp;= \frac{L_1 + L_2 \cos\theta_2}{r}\
\sin\gamma &amp;amp;= \frac{L_2 \sin\theta_2}{r}
\end{aligned}
$$
则
$$
\begin{cases}
&amp;amp;x&apos; = r(\cos\gamma\cos\theta_1 - \sin\gamma\sin\theta_1) = \cos(\theta_1 + \gamma)\
&amp;amp;y&apos; = r(\cos\gamma\sin\theta_1 + \sin\gamma\cos\theta_1) = \sin(\theta_1 + \gamma)
\end{cases}
$$
由此可以求出
$$
\theta_1 = \text{atan2}(x&apos;, y&apos;) - \text{atan2}(L_2\sin\theta_2, L_1 + L_2\cos\theta_2)
$$
其中 $\gamma = \text{atan2}(L_2\sin\theta_2, L_1 + L_2\cos\theta_2)$&lt;/p&gt;
&lt;p&gt;最后可以求得 $\theta_3$
$$
\theta_3 = \phi - \theta_1 - \theta_2
$$
需要指出的是这里求解 $\theta_2$ 的代数方法的斧凿痕迹很明显，如果换用几何方法（余弦定理与角度加减）是可以很清楚的看出构造过程的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/planar3Rsolution.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>分布估计算法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/densityestimation/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/densityestimation/</guid><pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;分布估计算法，估计概率分布的一些方法&lt;/p&gt;
&lt;h1&gt;概述&lt;/h1&gt;
&lt;p&gt;分布估计算法起源于遗传算法，其基本思想是使用概率的方法描述和表示每一代群体。对比优化问题，优化问题中的每个自变量都被看做是一个随机变量，将所有的随机变量表示为一个随机向量 $X = (X_1, X_2, \dots, X_n)$，其中每个随机变量 $X_i$ 代表一个个体的某一特征。于是，&lt;strong&gt;一个群体就对应于该随机向量的一个分布&lt;/strong&gt;。在一个概率分布上进行采样，可以生成更有价值的群体和个体。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分布估计算法是一种基于种群的随即优化算法，它利用每一代种群，学习随机变量的分布，然后在学习得到的分布的基础上再生成下一代新种群，逐步迭代直至收敛。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/denc.png&quot; alt=&quot;denc&quot; /&gt;&lt;/p&gt;
&lt;p&gt;由于分布估计算法没有&lt;strong&gt;交叉&lt;/strong&gt;和&lt;strong&gt;变异&lt;/strong&gt;操作，因此通常不用基因来描述个体所包含的信息，取而代之的是变量。&lt;/p&gt;
&lt;h1&gt;算法步骤&lt;/h1&gt;
&lt;h2&gt;变量无关的分布式估计算法&lt;/h2&gt;
&lt;h3&gt;UMDA (Univariate Marginal Distribution Algorithm)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;随机产生 $N$ 个个体来组成一个初始种群，并评估初始种群中所有个体的适应度&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;按适应度从高到低的顺序对种群进行排序，并从中选取最优的 $S$ 个个体 ($S &amp;lt; N$)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分析所选出的 $S$ 个个体所包含的信息，估计其联合概率分布 $p(x)$
$$
p_{selected}(x) = \prod_{i=1}^{n} p_{selected}(x_i)
$$
      其中 $p_{selected} (x_i)$ 为边缘分布。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从构建的概率模型 $p(x)$ 中采样，得到 $N$ 个新样本，构建新种群。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;若达到终止条件就结束算法流程，否则跳转至第 2 步&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;很显然，UMDA 算法的假设是单个特征变量之间是相互独立的，和 Naive Bayes 一样，认为变量之间是独立不相关的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example: OneMax Problem&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于固定长度为 $N$ 的二进制串， OneMax 问题就是要求找到一个包含 $1$ 的个数最大的二进制串，即找到 $x = (x_1, x_2, \dots, x_n)$，$x_n \in {0, 1}$，使得 $F(x) = \sum_{i=1}^{N} x_i$ 最大化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可以用 UMDA 来求解 OneMax 问题。不妨以四维的 OneMax 为例，设种群分布的概率模型可以用一个简单的概率向量 $p = (p_1, p_2, p_3, p_4)$ 来表示描述种群分布的概率模型，其中 $p_i$ 表示取 $x_i = 1$ 的概率，$1 - p_i$ 表示取 $x_i = 0$ 的概率。&lt;/p&gt;
&lt;p&gt;1. 产生初始种群，定义初始化概率向量模型 $p = (0.5, 0.5, 0.5, 0.5)$，然后根据 $p$ 产生规模为 10 的初始种群，根据 $F(x) = x_1 + x_2 + x_3 + x_4$ 计算初始种群适应度。其中产生的种群如图&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;编号&lt;/th&gt;
&lt;th&gt;x₁&lt;/th&gt;
&lt;th&gt;x₂&lt;/th&gt;
&lt;th&gt;x₃&lt;/th&gt;
&lt;th&gt;x₄&lt;/th&gt;
&lt;th&gt;f&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;2. 按照种群的适应度从高到低进行排序。设 $S = 5$, 从种群中选出适应度较高的 5 个个体用来更新概率向量模型 $p$。更新时，令 $p_i = \frac{n_i}{S}$，其中 $n_i$ 为在选出的较优个体中 $x_i = 1$ 的个体数。若选取的个体如下表所示&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;编号&lt;/th&gt;
&lt;th&gt;原编号&lt;/th&gt;
&lt;th&gt;x₁&lt;/th&gt;
&lt;th&gt;x₂&lt;/th&gt;
&lt;th&gt;x₃&lt;/th&gt;
&lt;th&gt;x₄&lt;/th&gt;
&lt;th&gt;f&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;则可以得到更新的模型为 $p = (\frac{3}{5}, \frac{4}{5}, \frac{3}{5}, \frac{3}{5})$&lt;/p&gt;
&lt;p&gt;3. 根据更新后的概率模型 $p$ 产生新的种群，并计算适应度&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;编号&lt;/th&gt;
&lt;th&gt;x₁&lt;/th&gt;
&lt;th&gt;x₂&lt;/th&gt;
&lt;th&gt;x₃&lt;/th&gt;
&lt;th&gt;x₄&lt;/th&gt;
&lt;th&gt;f&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;4. 重复步骤 2 和 3，直到达到终止条件。最终得到结果如下表&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;进化代数&lt;/th&gt;
&lt;th&gt;概率向量&lt;/th&gt;
&lt;th&gt;种群平均适应度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;(0.5,0.5,0.5,0.5)&lt;/td&gt;
&lt;td&gt;2.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;(0.6,0.8,0.6,0.6)&lt;/td&gt;
&lt;td&gt;2.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;(0.8,1.0,0.6,0.8)&lt;/td&gt;
&lt;td&gt;2.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;(1.0,1.0,1.0,0.8)&lt;/td&gt;
&lt;td&gt;3.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;(1.0,1.0,1.0,1.0)&lt;/td&gt;
&lt;td&gt;4.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;PBIL (Population Based Incremental Learning)&lt;/h3&gt;
&lt;p&gt;PBIL (Population Based Incremental Learning) 是一种分布估计算法，它结合了遗传算法和竞争学习的思想。与UMDA类似，PBIL也通过概率向量来表示种群中变量的分布，但采用了不同的更新机制。&lt;/p&gt;
&lt;p&gt;PBIL的工作原理如下&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;增量式学习机制&lt;/strong&gt;：PBIL不是直接将概率向量更新为当前选出个体的样本分布，而是采用一种渐进式学习方式，通过学习率参数逐步向最优个体方向调整&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;竞争学习机制&lt;/strong&gt;：PBIL采用竞争学习的方式来更新概率向量。每次迭代中，PBIL会选择适应度较高的个体，并根据这些个体的信息来更新概率向量。具体来说，PBIL会将选出个体的基因信息与当前概率向量进行比较，并根据适应度的差异来调整概率向量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新概率向量&lt;/strong&gt;：PBIL通过以下公式来更新概率向量
$$
p_{t+1}(x) = (1 - \alpha) p_t(x) + \alpha \frac{1}{N} \sum_{i=1}^{N} x_t^{(i)}
$$
     其中 $p_{t+1}(x)$ 是更新后的概率向量，$p_t(x)$ 是当前的概率向量，$\alpha$ 是学习率，$N$ 是种群大小，$x_t^{(i)}$ 是第 $t$ 代中第 $i$ 个个体。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;更新步骤类似 UMDA，在此不列出代码流程。&lt;/p&gt;
&lt;h3&gt;CGA (Compact Genetic Algorithm)&lt;/h3&gt;
&lt;p&gt;CGA (Compact Genetic Algorithm) 是一种不同于 UMDA、PBIL 的分布估计算法。CGA 的种群规模很小，只产生两个个体，分别是最优个体和次优个体。CGA 通过对这两个个体进行交叉和变异来生成新的个体，并根据适应度来更新概率向量。&lt;/p&gt;
&lt;p&gt;算法步骤为&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=5&amp;gt;Algorithm &amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;问题规模 $n$, 种群规模 $N$, 学习率 $\alpha$ (通常为 $\frac{1}{N}$)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;最优解 $x_{best}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化概率向量 $p = (p_1, p_2, \dots, p_n)$，其中 $p_i = 0.5$ 对于所有 $i$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;while&lt;/strong&gt; 未达到终止条件 &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;    从概率向量 $p$ 中采样生成两个个体 $a$ 和 $b$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;    计算适应度: $f(a)$ 和 $f(b)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;    确定胜者 $w$ 和败者 $l$ (若 $f(a) &amp;gt; f(b)$ 则 $w = a$, $l = b$; 否则 $w = b$, $l = a$)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;for&lt;/strong&gt; $i = 1$ 到 $n$ &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;        &lt;strong&gt;if&lt;/strong&gt; $w_i \neq l_i$ &lt;strong&gt;then&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;            &lt;strong&gt;if&lt;/strong&gt; $w_i = 1$ &lt;strong&gt;then&lt;/strong&gt; $p_i = p_i + \alpha$ &lt;strong&gt;else&lt;/strong&gt; $p_i = p_i - \alpha$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;        &lt;strong&gt;end if&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;        将 $p_i$ 限制在 $[1/N, 1-1/N]$ 范围内，防止收敛到 0 或 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:&lt;/td&gt;
&lt;td&gt;    如有必要，更新 $x_{best}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;end while&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;return&lt;/strong&gt; $x_{best}$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;变量相关的分布式估计算法&lt;/h2&gt;
&lt;h3&gt;MIMC (Mutual Information Maximizing Input Clustering)&lt;/h3&gt;
&lt;p&gt;MIMIC (Mutual Information Maximizing Input Clustering) 是一种基于链式概率模型的分布估计算法。MIMIC 通过最大化变量之间的互信息来学习变量之间的依赖关系，从而构建一个更复杂的概率模型。&lt;/p&gt;
&lt;p&gt;考虑建模为链式概率模型，给定一个变量集合 $x = (x_1, x_2, \dots, x_n)$，它的联合分布概率密度函数可表示为
$$
p(x) = p(x_1, x_2, \dots, x_n) = p(x_1) p(x_2|x_1) p(x_3|x_1,x_2) \cdots p(x_n|x_1,x_2,\dots,x_{n-1})
$$&lt;/p&gt;
&lt;p&gt;MIMIC 假设变量依赖是链式的，即每个变量只依赖于它前面的变量。目标是找到一种变量的最优排序，使得这种依赖链对应的联合分布概率最接近真实分布。&lt;/p&gt;
&lt;p&gt;排序可以表示为
$$
\pi = {i_1, i_2, \dots, i_n}
$$&lt;/p&gt;
&lt;p&gt;对应于
$$
x_{i_1} \rightarrow x_{i_2} \rightarrow \cdots \rightarrow x_{i_n}
$$&lt;/p&gt;
&lt;p&gt;使用 KL 散度来度量真实分布和估计分布之间的差异，MIMIC 的目标是最小化以下目标函数
$$
\begin{align*}
\min_{x} D_{KL}[p(x) || p_\pi(x)] &amp;amp;= \sum_{x} p(x) \log \frac{p(x)}{p_\pi(x)} \
&amp;amp;= -H(p) + H(p, p_\pi) \
\end{align*}
$$
其中，$H(p)$ 是真实分布的熵，$H(p, p_\pi)$ 是交叉熵。&lt;/p&gt;
&lt;p&gt;MIMIC 引入了一种贪心算法来求最有排列
&lt;img src=&quot;assets/MIMIC.png&quot; alt=&quot;MIMIC&quot; /&gt;&lt;/p&gt;
&lt;p&gt;类似地，算法可以扩展到树状模型和更复杂的概率模型如贝叶斯网络。&lt;/p&gt;
&lt;h2&gt;连续变量的分布估计算法&lt;/h2&gt;
&lt;p&gt;类似地处理，还是采用 UMDA、PBIL 和 CGA 等算法，只是将离散变量替换为连续变量。例如，只需要把每个变量定义为一个一元高斯分布，然后根据分布生成新个体。即可类似地对应求解。&lt;/p&gt;
</content:encoded></item><item><title>搜索与回溯</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/searchbacktrack/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/searchbacktrack/</guid><pubDate>Sat, 12 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;搜索和回溯是算法中解决组合、路径、决策等问题的基本技术。它们通过系统地探索所有可能的解空间来找到问题的答案。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;搜索&lt;/strong&gt;：广义上包括深度优先搜索（DFS）和广度优先搜索（BFS），用于遍历或搜索树或图的节点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回溯&lt;/strong&gt;：一种通过探索所有可能的候选解来找出所有解的算法。如果发现候选解不是可行解，回溯算法会“回溯”并选择另一个候选解。它本质上是DFS的一种应用，特别用于解决约束满足问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分治&lt;/strong&gt;：将一个大问题分解为若干个相同或相似的子问题，递归地解决子问题，然后将子问题的解合并起来得到原问题的解。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记忆化搜索&lt;/strong&gt;：是自顶向下动态规划的一种形式，通过缓存子问题的解来避免重复计算，是DFS和DP的结合。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;深度优先搜索 (DFS)&lt;/h2&gt;
&lt;p&gt;深度优先搜索（DFS）是一种用于遍历或搜索树或图的算法。它从一个起始节点开始，沿着一条路径尽可能深地探索，直到到达末端，然后回溯以探索其他分支。常用于寻找路径、连通性、环检测等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 79. 单词搜索&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 在一个字符网格中寻找一个单词。我们可以从每个单元格开始，进行DFS，尝试匹配单词的每个字符。为了防止重复使用同一个单元格，我们可以在DFS递归时将当前单元格标记为已访问（例如，通过修改其值），并在回溯时恢复它。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;

class Solution {
public:
    bool exist(std::vector&amp;lt;std::vector&amp;lt;char&amp;gt;&amp;gt;&amp;amp; board, std::string word) {
        if (board.empty()) return false;
        int rows = board.size(), cols = board[0].size();
        for (int i = 0; i &amp;lt; rows; ++i) {
            for (int j = 0; j &amp;lt; cols; ++j) {
                if (dfs(board, word, i, j, 0)) {
                    return true;
                }
            }
        }
        return false;
    }
private:
    bool dfs(std::vector&amp;lt;std::vector&amp;lt;char&amp;gt;&amp;gt;&amp;amp; board, const std::string&amp;amp; word, int r, int c, int k) {
        if (k == word.length()) return true;
        int rows = board.size(), cols = board[0].size();
        if (r &amp;lt; 0 || r &amp;gt;= rows || c &amp;lt; 0 || c &amp;gt;= cols || board[r][c] != word[k]) {
            return false;
        }

        char temp = board[r][c];
        board[r][c] = &apos;#&apos;; // Mark as visited

        bool found = dfs(board, word, r + 1, c, k + 1) ||
                     dfs(board, word, r - 1, c, k + 1) ||
                     dfs(board, word, r, c + 1, k + 1) ||
                     dfs(board, word, r, c - 1, k + 1);

        board[r][c] = temp; // Backtrack
        return found;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 463. 岛屿的周长&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 计算一个由 &lt;code&gt;1&lt;/code&gt; 组成的岛屿的周长。周长由岛屿单元格与水域（&lt;code&gt;0&lt;/code&gt;）或网格边界相邻的边构成。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;遍历整个网格，找到岛屿的任意一个陆地单元格（值为 &lt;code&gt;1&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;从这个单元格开始进行DFS。&lt;/li&gt;
&lt;li&gt;对于当前单元格 &lt;code&gt;(r, c)&lt;/code&gt;，检查其四个方向（上、下、左、右）：
&lt;ul&gt;
&lt;li&gt;如果邻居是水域或超出边界，那么这条边就是周长的一部分，周长加1。&lt;/li&gt;
&lt;li&gt;如果邻居是未访问过的陆地，则递归地对该邻居进行DFS。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;为了避免重复计算，需要一个 &lt;code&gt;visited&lt;/code&gt; 集合或直接修改原数组（例如，将访问过的 &lt;code&gt;1&lt;/code&gt; 变为 &lt;code&gt;2&lt;/code&gt;）。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;

class Solution {
public:
    int islandPerimeter(std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; grid) {
        if (grid.empty()) return 0;
        int rows = grid.size(), cols = grid[0].size();
        for (int i = 0; i &amp;lt; rows; ++i) {
            for (int j = 0; j &amp;lt; cols; ++j) {
                if (grid[i][j] == 1) {
                    return dfs(grid, i, j);
                }
            }
        }
        return 0;
    }
private:
    int dfs(std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; grid, int r, int c) {
        int rows = grid.size(), cols = grid[0].size();
        if (r &amp;lt; 0 || r &amp;gt;= rows || c &amp;lt; 0 || c &amp;gt;= cols || grid[r][c] == 0) {
            return 1; // Edge contributes to perimeter
        }
        if (grid[r][c] == 2) { // Already visited
            return 0;
        }
        
        grid[r][c] = 2; // Mark as visited
        
        int perimeter = 0;
        perimeter += dfs(grid, r + 1, c);
        perimeter += dfs(grid, r - 1, c);
        perimeter += dfs(grid, r, c + 1);
        perimeter += dfs(grid, r, c - 1);
        
        return perimeter;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 529. 扫雷游戏&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 模拟扫雷游戏的点击操作。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果点击到地雷（&apos;M&apos;），游戏结束，将该位置变为 &apos;X&apos;。&lt;/li&gt;
&lt;li&gt;如果点击到空白方块（&apos;E&apos;），需要揭示它以及所有相邻的空白方块。
&lt;ul&gt;
&lt;li&gt;首先，计算点击位置周围8个方向的地雷数量。&lt;/li&gt;
&lt;li&gt;如果地雷数大于0，将该位置更新为数字，并停止扩展。&lt;/li&gt;
&lt;li&gt;如果地雷数为0，将该位置更新为 &apos;B&apos;（Blank），然后对周围8个方向的 &apos;E&apos; 递归进行DFS。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;char&amp;gt;&amp;gt; updateBoard(std::vector&amp;lt;std::vector&amp;lt;char&amp;gt;&amp;gt;&amp;amp; board, std::vector&amp;lt;int&amp;gt;&amp;amp; click) {
        int r = click[0], c = click[1];
        if (board[r][c] == &apos;M&apos;) {
            board[r][c] = &apos;X&apos;;
            return board;
        }
        dfs(board, r, c);
        return board;
    }
private:
    void dfs(std::vector&amp;lt;std::vector&amp;lt;char&amp;gt;&amp;gt;&amp;amp; board, int r, int c) {
        int rows = board.size(), cols = board[0].size();
        if (r &amp;lt; 0 || r &amp;gt;= rows || c &amp;lt; 0 || c &amp;gt;= cols || board[r][c] != &apos;E&apos;) {
            return;
        }

        int mines = countMines(board, r, c);

        if (mines &amp;gt; 0) {
            board[r][c] = &apos;0&apos; + mines;
        } else {
            board[r][c] = &apos;B&apos;;
            for (int dr = -1; dr &amp;lt;= 1; ++dr) {
                for (int dc = -1; dc &amp;lt;= 1; ++dc) {
                    if (dr == 0 &amp;amp;&amp;amp; dc == 0) continue;
                    dfs(board, r + dr, c + dc);
                }
            }
        }
    }

    int countMines(const std::vector&amp;lt;std::vector&amp;lt;char&amp;gt;&amp;gt;&amp;amp; board, int r, int c) {
        int mines = 0;
        for (int dr = -1; dr &amp;lt;= 1; ++dr) {
            for (int dc = -1; dc &amp;lt;= 1; ++dc) {
                if (dr == 0 &amp;amp;&amp;amp; dc == 0) continue;
                int nr = r + dr, nc = c + dc;
                if (nr &amp;gt;= 0 &amp;amp;&amp;amp; nr &amp;lt; board.size() &amp;amp;&amp;amp; nc &amp;gt;= 0 &amp;amp;&amp;amp; nc &amp;lt; board[0].size() &amp;amp;&amp;amp; board[nr][nc] == &apos;M&apos;) {
                    mines++;
                }
            }
        }
        return mines;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;广度优先搜索 (BFS)&lt;/h2&gt;
&lt;p&gt;广度优先搜索（BFS）也是一种图遍历算法，它从根节点开始，探索完所有邻近节点后，再逐层向外扩展。BFS 常用于寻找最短路径（在无权图中）、层序遍历等。它通常使用队列来实现。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 127. 单词接龙&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 寻找从 &lt;code&gt;beginWord&lt;/code&gt; 到 &lt;code&gt;endWord&lt;/code&gt; 的最短转换序列长度。这是一个在隐式图中寻找最短路径的问题，图的节点是单词，如果两个单词只相差一个字母，则它们之间有边。BFS 是解决无权图最短路径问题的理想选择。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将 &lt;code&gt;wordList&lt;/code&gt; 存入哈希集合以便快速查找。&lt;/li&gt;
&lt;li&gt;从 &lt;code&gt;beginWord&lt;/code&gt; 开始进行BFS，队列中存储 &lt;code&gt;(单词, 转换次数)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在每一层，生成当前单词所有可能的“邻居”（只改变一个字母），如果在 &lt;code&gt;wordList&lt;/code&gt; 中存在且未被访问过，则将其加入队列，并从 &lt;code&gt;wordList&lt;/code&gt; 中移除以防重复访问。&lt;/li&gt;
&lt;li&gt;当第一次找到 &lt;code&gt;endWord&lt;/code&gt; 时，其对应的转换次数就是最短长度。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;unordered_set&amp;gt;

class Solution {
public:
    int ladderLength(std::string beginWord, std::string endWord, std::vector&amp;lt;std::string&amp;gt;&amp;amp; wordList) {
        std::unordered_set&amp;lt;std::string&amp;gt; dict(wordList.begin(), wordList.end());
        if (dict.find(endWord) == dict.end()) return 0;

        std::queue&amp;lt;std::pair&amp;lt;std::string, int&amp;gt;&amp;gt; q;
        q.push({beginWord, 1});
        
        while (!q.empty()) {
            auto [word, len] = q.front();
            q.pop();

            if (word == endWord) return len;

            for (int i = 0; i &amp;lt; word.length(); ++i) {
                char original_char = word[i];
                for (char c = &apos;a&apos;; c &amp;lt;= &apos;z&apos;; ++c) {
                    word[i] = c;
                    if (dict.count(word)) {
                        q.push({word, len + 1});
                        dict.erase(word);
                    }
                }
                word[i] = original_char;
            }
        }
        return 0;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 126. 单词接龙 II&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 与 &quot;单词接龙&quot; 类似，但要求返回所有最短的转换序列。这需要对标准的BFS进行修改。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;双向BFS或单向BFS+记录路径&lt;/strong&gt;：标准的BFS找到最短路径长度后就停止了，但我们需要所有路径。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记录父节点&lt;/strong&gt;：在BFS过程中，当从单词 &lt;code&gt;u&lt;/code&gt; 找到一个新单词 &lt;code&gt;v&lt;/code&gt; 时，我们将 &lt;code&gt;u&lt;/code&gt; 记录为 &lt;code&gt;v&lt;/code&gt; 的一个父节点。因为可能有多条最短路径到达 &lt;code&gt;v&lt;/code&gt;，所以一个节点可以有多个父节点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分层遍历&lt;/strong&gt;：BFS天然是分层的。我们必须确保只有在当前层完全遍历完之后，才将下一层的节点标记为已访问。这样可以保证我们找到的所有到 &lt;code&gt;v&lt;/code&gt; 的路径都是最短的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回溯构建路径&lt;/strong&gt;：BFS结束后，我们从 &lt;code&gt;endWord&lt;/code&gt; 开始，使用记录的父节点关系，通过DFS回溯到 &lt;code&gt;beginWord&lt;/code&gt;，从而构建出所有最短路径。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;unordered_set&amp;gt;
#include &amp;lt;unordered_map&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;std::string&amp;gt;&amp;gt; findLadders(std::string beginWord, std::string endWord, std::vector&amp;lt;std::string&amp;gt;&amp;amp; wordList) {
        std::unordered_set&amp;lt;std::string&amp;gt; dict(wordList.begin(), wordList.end());
        if (dict.find(endWord) == dict.end()) return {};

        std::unordered_map&amp;lt;std::string, std::vector&amp;lt;std::string&amp;gt;&amp;gt; parents;
        std::queue&amp;lt;std::string&amp;gt; q;
        q.push(beginWord);
        
        std::unordered_set&amp;lt;std::string&amp;gt; visited_this_level;
        visited_this_level.insert(beginWord);

        bool found = false;

        while (!q.empty()) {
            int level_size = q.size();
            for (int i = 0; i &amp;lt; level_size; ++i) {
                std::string word = q.front();
                q.pop();

                std::string original_word = word;
                for (int j = 0; j &amp;lt; word.length(); ++j) {
                    char original_char = word[j];
                    for (char c = &apos;a&apos;; c &amp;lt;= &apos;z&apos;; ++c) {
                        word[j] = c;
                        if (dict.count(word)) {
                            if (word == endWord) found = true;
                            // Only add to queue if not visited in previous levels
                            if (dict.count(word)) { 
                                parents[word].push_back(original_word);
                                if (visited_this_level.find(word) == visited_this_level.end()) {
                                    q.push(word);
                                    visited_this_level.insert(word);
                                }
                            }
                        }
                    }
                    word[j] = original_char;
                }
            }
            // Remove visited words from dict for next levels
            for(const auto&amp;amp; w : visited_this_level) dict.erase(w);
            if (found) break;
        }

        std::vector&amp;lt;std::vector&amp;lt;std::string&amp;gt;&amp;gt; result;
        if (found) {
            std::vector&amp;lt;std::string&amp;gt; path = {endWord};
            buildPaths(endWord, beginWord, parents, path, result);
        }
        return result;
    }
private:
    void buildPaths(const std::string&amp;amp; word, const std::string&amp;amp; beginWord, 
                    const std::unordered_map&amp;lt;std::string, std::vector&amp;lt;std::string&amp;gt;&amp;gt;&amp;amp; parents, 
                    std::vector&amp;lt;std::string&amp;gt;&amp;amp; path, 
                    std::vector&amp;lt;std::vector&amp;lt;std::string&amp;gt;&amp;gt;&amp;amp; result) {
        if (word == beginWord) {
            std::vector&amp;lt;std::string&amp;gt; reversed_path = path;
            std::reverse(reversed_path.begin(), reversed_path.end());
            result.push_back(reversed_path);
            return;
        }
        if (parents.count(word)) {
            for (const std::string&amp;amp; p : parents.at(word)) {
                path.push_back(p);
                buildPaths(p, beginWord, parents, path, result);
                path.pop_back();
            }
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 365. 水壶问题&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 判断是否可以用两个容量分别为 &lt;code&gt;jug1Capacity&lt;/code&gt; 和 &lt;code&gt;jug2Capacity&lt;/code&gt; 的水壶得到 &lt;code&gt;targetCapacity&lt;/code&gt; 升水。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;数学解法（贝祖定理）&lt;/strong&gt;：问题等价于问是否存在整数 &lt;code&gt;x&lt;/code&gt; 和 &lt;code&gt;y&lt;/code&gt;，使得 &lt;code&gt;x * jug1 + y * jug2 = target&lt;/code&gt;。根据贝祖定理，这样的整数存在当且仅当 &lt;code&gt;target&lt;/code&gt; 是 &lt;code&gt;jug1&lt;/code&gt; 和 &lt;code&gt;jug2&lt;/code&gt; 的最大公约数（GCD）的倍数。当然，&lt;code&gt;target&lt;/code&gt; 也不能超过两个水壶的总容量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态搜索解法（BFS）&lt;/strong&gt;：可以将问题看作状态空间的搜索。每个状态是 &lt;code&gt;(水量1, 水量2)&lt;/code&gt; 的一个元组。从初始状态 &lt;code&gt;(0, 0)&lt;/code&gt; 开始，通过以下操作转移到新状态：
&lt;ul&gt;
&lt;li&gt;倒空任一水壶。&lt;/li&gt;
&lt;li&gt;装满任一水壶。&lt;/li&gt;
&lt;li&gt;将一个水壶的水倒入另一个，直到一个倒空或另一个倒满。
使用BFS进行搜索，如果搜索到任何一个状态 &lt;code&gt;(x, y)&lt;/code&gt; 使得 &lt;code&gt;x + y = target&lt;/code&gt;，则返回 &lt;code&gt;true&lt;/code&gt;。用一个 &lt;code&gt;visited&lt;/code&gt; 集合来避免重复状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;numeric&amp;gt; // For std::gcd in C++17
#include &amp;lt;queue&amp;gt;
#include &amp;lt;unordered_set&amp;gt;

class Solution {
public:
    // Mathematical approach
    bool canMeasureWater(int jug1Capacity, int jug2Capacity, int targetCapacity) {
        if (jug1Capacity + jug2Capacity &amp;lt; targetCapacity) {
            return false;
        }
        if (jug1Capacity == 0 || jug2Capacity == 0) {
            return targetCapacity == 0 || jug1Capacity + jug2Capacity == targetCapacity;
        }
        return targetCapacity % std::gcd(jug1Capacity, jug2Capacity) == 0;
    }
    
    // BFS approach (for demonstration)
    bool canMeasureWaterBFS(int jug1, int jug2, int target) {
        if (target &amp;gt; jug1 + jug2) return false;
        
        std::queue&amp;lt;std::pair&amp;lt;int, int&amp;gt;&amp;gt; q;
        std::unordered_set&amp;lt;long long&amp;gt; visited;

        q.push({0, 0});
        visited.insert(0);

        while (!q.empty()) {
            auto [x, y] = q.front();
            q.pop();

            if (x == target || y == target || x + y == target) {
                return true;
            }

            std::vector&amp;lt;std::pair&amp;lt;int, int&amp;gt;&amp;gt; next_states;
            next_states.push_back({jug1, y}); // Fill jug1
            next_states.push_back({x, jug2}); // Fill jug2
            next_states.push_back({0, y});    // Empty jug1
            next_states.push_back({x, 0});    // Empty jug2
            // Pour jug1 to jug2
            int pour1to2 = std::min(x, jug2 - y);
            next_states.push_back({x - pour1to2, y + pour1to2});
            // Pour jug2 to jug1
            int pour2to1 = std::min(y, jug1 - x);
            next_states.push_back({x + pour2to1, y - pour2to1});

            for (auto const&amp;amp; state : next_states) {
                long long key = (long long)state.first * (jug2 + 1) + state.second;
                if (visited.find(key) == visited.end()) {
                    q.push(state);
                    visited.insert(key);
                }
            }
        }
        return false;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1971. 寻找图中是否存在路径&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 在给定的无向图中，判断从 &lt;code&gt;source&lt;/code&gt; 到 &lt;code&gt;destination&lt;/code&gt; 是否存在一条路径。这是一个基础的图连通性问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;构建邻接表&lt;/strong&gt;：首先，根据 &lt;code&gt;edges&lt;/code&gt; 数组构建图的邻接表表示，其中 &lt;code&gt;graph[u]&lt;/code&gt; 存储所有与节点 &lt;code&gt;u&lt;/code&gt; 相邻的节点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BFS或DFS&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BFS&lt;/strong&gt;：从 &lt;code&gt;source&lt;/code&gt; 节点开始，将其加入队列。然后，逐层探索，将访问到的邻居加入队列。用一个 &lt;code&gt;visited&lt;/code&gt; 数组记录已访问的节点。如果在探索过程中遇到了 &lt;code&gt;destination&lt;/code&gt;，则说明路径存在。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DFS&lt;/strong&gt;：从 &lt;code&gt;source&lt;/code&gt; 节点开始递归。在递归函数中，访问当前节点，然后对其所有未访问的邻居进行递归调用。如果某次调用到达了 &lt;code&gt;destination&lt;/code&gt;，则返回 &lt;code&gt;true&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;

class Solution {
public:
    bool validPath(int n, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; edges, int source, int destination) {
        if (source == destination) return true;
        
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; graph(n);
        for (const auto&amp;amp; edge : edges) {
            graph[edge[0]].push_back(edge[1]);
            graph[edge[1]].push_back(edge[0]);
        }

        std::queue&amp;lt;int&amp;gt; q;
        q.push(source);
        std::vector&amp;lt;bool&amp;gt; visited(n, false);
        visited[source] = true;

        while (!q.empty()) {
            int u = q.front();
            q.pop();

            if (u == destination) return true;

            for (int v : graph[u]) {
                if (!visited[v]) {
                    visited[v] = true;
                    q.push(v);
                }
            }
        }
        return false;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 399. 除法求值&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 给定一系列变量除法等式，求解查询。我们可以将问题建模成一个图，变量是节点，&lt;code&gt;a / b = k&lt;/code&gt; 表示从 &lt;code&gt;a&lt;/code&gt; 到 &lt;code&gt;b&lt;/code&gt; 的有向边权重为 &lt;code&gt;k&lt;/code&gt;，从 &lt;code&gt;b&lt;/code&gt; 到 &lt;code&gt;a&lt;/code&gt; 的权重为 &lt;code&gt;1/k&lt;/code&gt;。查询 &lt;code&gt;c / d&lt;/code&gt; 就变成了寻找从 &lt;code&gt;c&lt;/code&gt; 到 &lt;code&gt;d&lt;/code&gt; 的路径，路径上所有权重的乘积就是答案。
对于每个查询，我们可以使用BFS（或DFS）在图上搜索路径。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;unordered_map&amp;gt;
#include &amp;lt;queue&amp;gt;

class Solution {
public:
    std::vector&amp;lt;double&amp;gt; calcEquation(std::vector&amp;lt;std::vector&amp;lt;std::string&amp;gt;&amp;gt;&amp;amp; equations, std::vector&amp;lt;double&amp;gt;&amp;amp; values, std::vector&amp;lt;std::vector&amp;lt;std::string&amp;gt;&amp;gt;&amp;amp; queries) {
        std::unordered_map&amp;lt;std::string, std::vector&amp;lt;std::pair&amp;lt;std::string, double&amp;gt;&amp;gt;&amp;gt; graph;
        for (int i = 0; i &amp;lt; equations.size(); ++i) {
            graph[equations[i][0]].push_back({equations[i][1], values[i]});
            graph[equations[i][1]].push_back({equations[i][0], 1.0 / values[i]});
        }

        std::vector&amp;lt;double&amp;gt; results;
        for (const auto&amp;amp; q : queries) {
            results.push_back(bfs(q[0], q[1], graph));
        }
        return results;
    }
private:
    double bfs(const std::string&amp;amp; start, const std::string&amp;amp; end, std::unordered_map&amp;lt;std::string, std::vector&amp;lt;std::pair&amp;lt;std::string, double&amp;gt;&amp;gt;&amp;gt;&amp;amp; graph) {
        if (graph.find(start) == graph.end() || graph.find(end) == graph.end()) {
            return -1.0;
        }

        std::queue&amp;lt;std::pair&amp;lt;std::string, double&amp;gt;&amp;gt; q;
        q.push({start, 1.0});
        std::unordered_set&amp;lt;std::string&amp;gt; visited;
        visited.insert(start);

        while (!q.empty()) {
            auto [curr_node, curr_val] = q.front();
            q.pop();

            if (curr_node == end) return curr_val;

            for (const auto&amp;amp; neighbor : graph[curr_node]) {
                if (visited.find(neighbor.first) == visited.end()) {
                    visited.insert(neighbor.first);
                    q.push({neighbor.first, curr_val * neighbor.second});
                }
            }
        }
        return -1.0;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;记忆化搜索&lt;/h2&gt;
&lt;p&gt;记忆化搜索是自顶向下动态规划的一种实现方式。它本质上是递归（DFS），但增加了一个缓存（如哈希表或数组）来存储已经计算过的子问题的解。当再次遇到相同的子问题时，直接从缓存中获取结果，从而避免重复计算。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 241. 为运算表达式设计优先级&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 给定一个包含数字和运算符的字符串，返回所有可能通过不同加括号方式计算出的结果。这是一个典型的分治问题，可以用递归解决。在 &lt;code&gt;expression&lt;/code&gt; 的每个运算符位置，我们可以将其分为左右两部分，递归计算两部分所有可能的结果，然后将两边的结果根据当前运算符进行组合。由于子表达式会被重复计算，我们可以使用记忆化来优化。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;unordered_map&amp;gt;

class Solution {
public:
    std::vector&amp;lt;int&amp;gt; diffWaysToCompute(std::string expression) {
        return dfs(expression);
    }
private:
    std::unordered_map&amp;lt;std::string, std::vector&amp;lt;int&amp;gt;&amp;gt; memo;

    std::vector&amp;lt;int&amp;gt; dfs(const std::string&amp;amp; expr) {
        if (memo.count(expr)) {
            return memo[expr];
        }

        std::vector&amp;lt;int&amp;gt; results;
        for (int i = 0; i &amp;lt; expr.length(); ++i) {
            char c = expr[i];
            if (c == &apos;+&apos; || c == &apos;-&apos; || c == &apos;*&apos;) {
                std::vector&amp;lt;int&amp;gt; left = dfs(expr.substr(0, i));
                std::vector&amp;lt;int&amp;gt; right = dfs(expr.substr(i + 1));
                for (int l : left) {
                    for (int r : right) {
                        if (c == &apos;+&apos;) results.push_back(l + r);
                        else if (c == &apos;-&apos;) results.push_back(l - r);
                        else results.push_back(l * r);
                    }
                }
            }
        }

        if (results.empty()) { // Base case: the expression is a single number
            results.push_back(std::stoi(expr));
        }

        return memo[expr] = results;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 488. 祖玛游戏&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 经典的状态压缩 + 记忆化搜索。目标是用最少的手中球消去所有桌上球。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;状态&lt;/strong&gt;：&lt;code&gt;(board_state, hand_state)&lt;/code&gt;，即桌面球的布局和手中球的状况。&lt;code&gt;board_state&lt;/code&gt; 是一个字符串，&lt;code&gt;hand_state&lt;/code&gt; 也是一个字符串（或频率图）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归/DFS&lt;/strong&gt;：定义一个函数 &lt;code&gt;solve(board, hand)&lt;/code&gt; 返回消去当前 &lt;code&gt;board&lt;/code&gt; 所需的最少球数。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基准情况&lt;/strong&gt;：如果 &lt;code&gt;board&lt;/code&gt; 为空，返回0。如果 &lt;code&gt;hand&lt;/code&gt; 为空但 &lt;code&gt;board&lt;/code&gt; 不为空，返回无穷大。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归步骤&lt;/strong&gt;：遍历 &lt;code&gt;hand&lt;/code&gt; 中的每种颜色的球。对于每种球，尝试将其插入到 &lt;code&gt;board&lt;/code&gt; 的每个可能位置（包括两个相同颜色球之间）。&lt;/li&gt;
&lt;li&gt;插入后，检查是否触发消除（连续三个或以上同色球）。如果触发，则递归处理消除后的新 &lt;code&gt;board&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;取所有成功尝试中的最小值。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记忆化&lt;/strong&gt;：使用 &lt;code&gt;map&amp;lt;pair&amp;lt;string, string&amp;gt;, int&amp;gt;&lt;/code&gt; 或类似结构来缓存 &lt;code&gt;(board, hand)&lt;/code&gt; 状态的结果，避免重复计算。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;unordered_map&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int findMinStep(std::string board, std::string hand) {
        std::unordered_map&amp;lt;char, int&amp;gt; hand_count;
        for (char c : hand) hand_count[c]++;
        
        memo.clear();
        int result = dfs(board, hand_count);
        return result &amp;gt; hand.length() ? -1 : result;
    }

private:
    std::unordered_map&amp;lt;std::string, int&amp;gt; memo;

    int dfs(std::string board, std::unordered_map&amp;lt;char, int&amp;gt;&amp;amp; hand) {
        if (board.empty()) return 0;
        
        std::string state_key = board + &quot;#&quot;;
        for(auto const&amp;amp; [key, val] : hand) {
            state_key += std::to_string(key) + std::to_string(val);
        }
        if (memo.count(state_key)) return memo[state_key];

        int min_steps = 1e9;

        for (int i = 0; i &amp;lt;= board.length(); ++i) {
            for (auto const&amp;amp; [color, count] : hand) {
                if (count &amp;gt; 0) {
                    // Optimization: only insert if it can form a group of 3
                    // or if inserting between two same-colored balls.
                    if (i &amp;gt; 0 &amp;amp;&amp;amp; i &amp;lt; board.length() &amp;amp;&amp;amp; board[i-1] == board[i] &amp;amp;&amp;amp; board[i-1] != color) continue;
                    if (i &amp;lt; board.length() &amp;amp;&amp;amp; board[i] == color) {} // ok
                    else if (i &amp;gt; 0 &amp;amp;&amp;amp; board[i-1] == color) {} // ok
                    else continue;


                    hand[color]--;
                    std::string new_board = board.substr(0, i) + color + board.substr(i);
                    
                    std::string cleaned_board = cleanup(new_board);
                    
                    int steps = dfs(cleaned_board, hand);
                    if (steps != 1e9) {
                        min_steps = std::min(min_steps, 1 + steps);
                    }
                    
                    hand[color]++; // backtrack
                }
            }
        }
        
        return memo[state_key] = min_steps;
    }

    std::string cleanup(std::string board) {
        int start = 0;
        while (start &amp;lt; board.length()) {
            int end = start;
            while (end &amp;lt; board.length() &amp;amp;&amp;amp; board[end] == board[start]) {
                end++;
            }
            if (end - start &amp;gt;= 3) {
                board.erase(start, end - start);
                start = 0; // Restart scan
            } else {
                start = end;
            }
        }
        return board;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1444. 切披萨的方案数&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 动态规划/记忆化搜索。&lt;code&gt;dp(r, c, k)&lt;/code&gt; 表示将 &lt;code&gt;(r, c)&lt;/code&gt; 右下角的披萨切成 &lt;code&gt;k&lt;/code&gt; 块（包含当前块）的方案数。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;预处理&lt;/strong&gt;：为了快速判断任意一块披萨是否含有苹果，先预计算一个二维前缀和数组 &lt;code&gt;apples[i][j]&lt;/code&gt;，表示从 &lt;code&gt;(i, j)&lt;/code&gt; 到右下角 &lt;code&gt;(rows-1, cols-1)&lt;/code&gt; 的苹果总数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态&lt;/strong&gt;：&lt;code&gt;dp(r, c, k)&lt;/code&gt; - 将由 &lt;code&gt;(r, c)&lt;/code&gt; 定义的右下角区域切成 &lt;code&gt;k&lt;/code&gt; 块的方案数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归/DFS&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基准情况&lt;/strong&gt;：如果 &lt;code&gt;k=1&lt;/code&gt;，检查当前区域是否有苹果，有则返回1，否则返回0。如果当前区域苹果数小于 &lt;code&gt;k&lt;/code&gt;，无法满足每块至少一个苹果，返回0。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归步骤&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;水平切割&lt;/strong&gt;：遍历所有可能的水平切割线 &lt;code&gt;nr&lt;/code&gt; (从 &lt;code&gt;r+1&lt;/code&gt; 到 &lt;code&gt;rows-1&lt;/code&gt;)。如果上半部分（从 &lt;code&gt;r&lt;/code&gt; 到 &lt;code&gt;nr-1&lt;/code&gt;）含有苹果，则方案数增加 &lt;code&gt;dp(nr, c, k-1)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;垂直切割&lt;/strong&gt;：遍历所有可能的垂直切割线 &lt;code&gt;nc&lt;/code&gt; (从 &lt;code&gt;c+1&lt;/code&gt; 到 &lt;code&gt;cols-1&lt;/code&gt;)。如果左半部分（从 &lt;code&gt;c&lt;/code&gt; 到 &lt;code&gt;nc-1&lt;/code&gt;）含有苹果，则方案数增加 &lt;code&gt;dp(r, nc, k-1)&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记忆化&lt;/strong&gt;：使用三维数组 &lt;code&gt;memo[r][c][k]&lt;/code&gt; 存储结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;

class Solution {
public:
    int ways(std::vector&amp;lt;std::string&amp;gt;&amp;amp; pizza, int k) {
        int rows = pizza.size(), cols = pizza[0].size();
        apples.assign(rows + 1, std::vector&amp;lt;int&amp;gt;(cols + 1, 0));
        memo.assign(rows, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;(cols, std::vector&amp;lt;int&amp;gt;(k + 1, -1)));
        
        for (int i = rows - 1; i &amp;gt;= 0; --i) {
            for (int j = cols - 1; j &amp;gt;= 0; --j) {
                apples[i][j] = (pizza[i][j] == &apos;A&apos;) + apples[i+1][j] + apples[i][j+1] - apples[i+1][j+1];
            }
        }
        
        return dfs(0, 0, k, rows, cols);
    }

private:
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; apples;
    std::vector&amp;lt;std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;gt; memo;
    int MOD = 1e9 + 7;

    int dfs(int r, int c, int k, int rows, int cols) {
        if (apples[r][c] == 0) return 0;
        if (k == 1) return 1;
        if (memo[r][c][k] != -1) return memo[r][c][k];

        long long ans = 0;
        // Horizontal cuts
        for (int nr = r + 1; nr &amp;lt; rows; ++nr) {
            if (apples[r][c] - apples[nr][c] &amp;gt; 0) { // Upper piece has apples
                ans = (ans + dfs(nr, c, k - 1, rows, cols)) % MOD;
            }
        }
        // Vertical cuts
        for (int nc = c + 1; nc &amp;lt; cols; ++nc) {
            if (apples[r][c] - apples[r][nc] &amp;gt; 0) { // Left piece has apples
                ans = (ans + dfs(r, nc, k - 1, rows, cols)) % MOD;
            }
        }
        return memo[r][c][k] = ans;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1387. 将整数按权重排序&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 计算每个整数的“权重”（变为1所需的步数），然后根据权重和原始值排序。计算权重的过程就是经典的“考拉兹猜想”问题。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;权重计算&lt;/strong&gt;：对于一个数 &lt;code&gt;x&lt;/code&gt;，如果 &lt;code&gt;x&lt;/code&gt; 是偶数，则 &lt;code&gt;x = x / 2&lt;/code&gt;；如果 &lt;code&gt;x&lt;/code&gt; 是奇数，则 &lt;code&gt;x = 3 * x + 1&lt;/code&gt;。重复此过程直到 &lt;code&gt;x=1&lt;/code&gt;，所用步数即为权重。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记忆化&lt;/strong&gt;：在计算权重的过程中，很多数字的权重会被重复计算。例如，计算 &lt;code&gt;7&lt;/code&gt; 的权重时会经过 &lt;code&gt;22 -&amp;gt; 11 -&amp;gt; 34 -&amp;gt; 17...&lt;/code&gt;，之后计算 &lt;code&gt;11&lt;/code&gt; 的权重时路径是重复的。我们可以用一个哈希表或数组 &lt;code&gt;memo&lt;/code&gt; 来存储已计算过的数字的权重。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;排序&lt;/strong&gt;：计算出 &lt;code&gt;[lo, hi]&lt;/code&gt; 区间内每个数的权重后，将 &lt;code&gt;(权重, 数值)&lt;/code&gt; 对存起来，然后自定义排序规则进行排序。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;unordered_map&amp;gt;

class Solution {
public:
    int getKth(int lo, int hi, int k) {
        std::vector&amp;lt;std::pair&amp;lt;int, int&amp;gt;&amp;gt; weighted_nums;
        for (int i = lo; i &amp;lt;= hi; ++i) {
            weighted_nums.push_back({getWeight(i), i});
        }
        
        std::sort(weighted_nums.begin(), weighted_nums.end());
        
        return weighted_nums[k - 1].second;
    }

private:
    std::unordered_map&amp;lt;int, int&amp;gt; memo;

    int getWeight(int n) {
        if (n == 1) return 0;
        if (memo.count(n)) return memo[n];
        
        if (n % 2 == 0) {
            return memo[n] = 1 + getWeight(n / 2);
        } else {
            return memo[n] = 1 + getWeight(3 * n + 1);
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 224. 基本计算器&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 实现一个支持 &lt;code&gt;+&lt;/code&gt;、&lt;code&gt;-&lt;/code&gt; 和 &lt;code&gt;()&lt;/code&gt; 的基本计算器。递归是处理括号嵌套的自然方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主循环&lt;/strong&gt;：遍历字符串。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数字&lt;/strong&gt;：解析完整的数字。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运算符&lt;/strong&gt;：&lt;code&gt;+&lt;/code&gt; 或 &lt;code&gt;-&lt;/code&gt;，更新符号 &lt;code&gt;sign&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;(&lt;/code&gt;&lt;/strong&gt;：遇到左括号，说明进入了一个新的子表达式。递归调用计算器函数来求解这个子表达式的值。递归调用会返回它处理完的表达式的结束位置，主循环从这个新位置继续。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;)&lt;/code&gt;&lt;/strong&gt;：遇到右括号，说明当前子表达式计算结束，返回结果。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;栈方法&lt;/strong&gt;：也可以用栈来处理。遇到 &lt;code&gt;(&lt;/code&gt; 时，将当前的计算结果和符号 &lt;code&gt;sign&lt;/code&gt; 入栈，然后重置结果和符号，开始计算括号内的新表达式。遇到 &lt;code&gt;)&lt;/code&gt; 时，将括号内的计算结果与栈顶的旧结果和符号合并。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;stack&amp;gt;

class Solution {
public:
    int calculate(std::string s) {
        std::stack&amp;lt;int&amp;gt; st;
        int result = 0;
        int sign = 1;
        int n = s.length();
        for (int i = 0; i &amp;lt; n; ++i) {
            char c = s[i];
            if (isdigit(c)) {
                long num = 0;
                while (i &amp;lt; n &amp;amp;&amp;amp; isdigit(s[i])) {
                    num = num * 10 + (s[i] - &apos;0&apos;);
                    i++;
                }
                i--; // Decrement i because the outer loop increments it
                result += sign * num;
            } else if (c == &apos;+&apos;) {
                sign = 1;
            } else if (c == &apos;-&apos;) {
                sign = -1;
            } else if (c == &apos;(&apos;) {
                st.push(result);
                st.push(sign);
                result = 0;
                sign = 1;
            } else if (c == &apos;)&apos;) {
                result *= st.top(); st.pop(); // sign
                result += st.top(); st.pop(); // previous result
            }
        }
        return result;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 273. 整数转换英文表示&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 将一个非负整数转换为其英文表示。这是一个分治/递归问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;分解&lt;/strong&gt;：数字可以按千位（&quot;Thousand&quot;）、百万位（&quot;Million&quot;）、十亿位（&quot;Billion&quot;）来分解。例如，&lt;code&gt;1,234,567&lt;/code&gt; 可以看作 &lt;code&gt;1 Million&lt;/code&gt; + &lt;code&gt;234 Thousand&lt;/code&gt; + &lt;code&gt;567&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;基本单元&lt;/strong&gt;：我们需要一个辅助函数，能将任何小于1000的数转换为英文。
&lt;ul&gt;
&lt;li&gt;这个函数可以进一步分解：处理百位（&lt;code&gt;n / 100&lt;/code&gt;）、十位和个位。&lt;/li&gt;
&lt;li&gt;需要预定义 &lt;code&gt;1-19&lt;/code&gt; 和 &lt;code&gt;20, 30, ..., 90&lt;/code&gt; 的英文单词。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归/主逻辑&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;从最大的单位（Billion）开始处理。&lt;/li&gt;
&lt;li&gt;取 &lt;code&gt;num / 1,000,000,000&lt;/code&gt;，递归调用转换函数处理这个部分，然后加上 &quot;Billion&quot;。&lt;/li&gt;
&lt;li&gt;对余数 &lt;code&gt;num % 1,000,000,000&lt;/code&gt; 重复此过程，处理 &quot;Million&quot;，然后 &quot;Thousand&quot;，最后是小于1000的部分。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;

class Solution {
private:
    std::vector&amp;lt;std::string&amp;gt; less_than_20 = {&quot;&quot;, &quot;One&quot;, &quot;Two&quot;, &quot;Three&quot;, &quot;Four&quot;, &quot;Five&quot;, &quot;Six&quot;, &quot;Seven&quot;, &quot;Eight&quot;, &quot;Nine&quot;, &quot;Ten&quot;, &quot;Eleven&quot;, &quot;Twelve&quot;, &quot;Thirteen&quot;, &quot;Fourteen&quot;, &quot;Fifteen&quot;, &quot;Sixteen&quot;, &quot;Seventeen&quot;, &quot;Eighteen&quot;, &quot;Nineteen&quot;};
    std::vector&amp;lt;std::string&amp;gt; tens = {&quot;&quot;, &quot;&quot;, &quot;Twenty&quot;, &quot;Thirty&quot;, &quot;Forty&quot;, &quot;Fifty&quot;, &quot;Sixty&quot;, &quot;Seventy&quot;, &quot;Eighty&quot;, &quot;Ninety&quot;};
    std::vector&amp;lt;std::string&amp;gt; thousands = {&quot;&quot;, &quot;Thousand&quot;, &quot;Million&quot;, &quot;Billion&quot;};

public:
    std::string numberToWords(int num) {
        if (num == 0) return &quot;Zero&quot;;
        
        std::string result = &quot;&quot;;
        int i = 0;
        while (num &amp;gt; 0) {
            if (num % 1000 != 0) {
                result = helper(num % 1000) + thousands[i] + &quot; &quot; + result;
            }
            num /= 1000;
            i++;
        }
        
        // Trim trailing space
        size_t endpos = result.find_last_not_of(&quot; &quot;);
        if (std::string::npos != endpos) {
            result = result.substr(0, endpos + 1);
        }
        return result;
    }

private:
    std::string helper(int n) {
        if (n == 0) return &quot;&quot;;
        if (n &amp;lt; 20) {
            return less_than_20[n] + &quot; &quot;;
        }
        if (n &amp;lt; 100) {
            return tens[n / 10] + &quot; &quot; + helper(n % 10);
        }
        return less_than_20[n / 100] + &quot; Hundred &quot; + helper(n % 100);
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 10. 正则表达式匹配&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 实现一个支持 &lt;code&gt;.&lt;/code&gt; 和 &lt;code&gt;*&lt;/code&gt; 的正则表达式匹配。&lt;code&gt;*&lt;/code&gt; 表示前面的元素可以出现零次或多次。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;递归定义&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;如果模式串 &lt;code&gt;p&lt;/code&gt; 为空，文本串 &lt;code&gt;s&lt;/code&gt; 也为空，返回真。&lt;/li&gt;
&lt;li&gt;如果模式串 &lt;code&gt;p&lt;/code&gt; 为空，文本串 &lt;code&gt;s&lt;/code&gt; 不为空，返回假。&lt;/li&gt;
&lt;li&gt;如果模式串 &lt;code&gt;p&lt;/code&gt; 的下一个字符不是 &lt;code&gt;*&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;如果文本串 &lt;code&gt;s&lt;/code&gt; 为空，返回假。&lt;/li&gt;
&lt;li&gt;如果模式串 &lt;code&gt;p&lt;/code&gt; 的第一个字符和文本串 &lt;code&gt;s&lt;/code&gt; 的第一个字符匹配，则继续递归匹配剩余部分。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果模式串 &lt;code&gt;p&lt;/code&gt; 的下一个字符是 &lt;code&gt;*&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;尝试两种情况：
&lt;ol&gt;
&lt;li&gt;将 &lt;code&gt;*&lt;/code&gt; 看作是零个前面元素的匹配，直接跳过 &lt;code&gt;*&lt;/code&gt; 和它前面的元素。&lt;/li&gt;
&lt;li&gt;如果文本串 &lt;code&gt;s&lt;/code&gt; 的第一个字符和模式串 &lt;code&gt;p&lt;/code&gt; 的第一个字符匹配，则将 &lt;code&gt;*&lt;/code&gt; 看作是一个或多个前面元素的匹配，文本串 &lt;code&gt;s&lt;/code&gt; 向后移动一个字符，模式串 &lt;code&gt;p&lt;/code&gt; 保持不变（因为 &lt;code&gt;*&lt;/code&gt; 可以匹配多个）。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;

class Solution {
public:
    bool isMatch(std::string s, std::string p) {
        return dfs(s, p, 0, 0);
    }
private:
    bool dfs(const std::string&amp;amp; s, const std::string&amp;amp; p, int i, int j) {
        if (j == p.size()) return i == s.size();
        
        bool match = (i &amp;lt; s.size() &amp;amp;&amp;amp; (s[i] == p[j] || p[j] == &apos;.&apos;));

        if (j + 1 &amp;lt; p.size() &amp;amp;&amp;amp; p[j + 1] == &apos;*&apos;) {
            return dfs(s, p, i, j + 2) || (match &amp;amp;&amp;amp; dfs(s, p, i + 1, j));
        } else {
            return match &amp;amp;&amp;amp; dfs(s, p, i + 1, j + 1);
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 91. 解码方法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 给定一个只包含数字的字符串，计算有多少种不同的解码方法。每个数字可以单独解码，也可以与下一个数字组合解码（前提是组合后的数字不超过26）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态定义&lt;/strong&gt;：&lt;code&gt;dp[i]&lt;/code&gt; 表示前 &lt;code&gt;i&lt;/code&gt; 个字符的解码方法总数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态转移&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;如果第 &lt;code&gt;i&lt;/code&gt; 个字符不为 &lt;code&gt;0&lt;/code&gt;，则 &lt;code&gt;dp[i] += dp[i-1]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果第 &lt;code&gt;i-1&lt;/code&gt; 和第 &lt;code&gt;i&lt;/code&gt; 个字符组合成的数字在 &lt;code&gt;10&lt;/code&gt; 到 &lt;code&gt;26&lt;/code&gt; 之间，则 &lt;code&gt;dp[i] += dp[i-2]&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;初始值&lt;/strong&gt;：&lt;code&gt;dp[0] = 1&lt;/code&gt;（空字符串），&lt;code&gt;dp[1]&lt;/code&gt; 根据第一个字符决定（如果不为 &lt;code&gt;0&lt;/code&gt;，则为 &lt;code&gt;1&lt;/code&gt;）。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;

class Solution {
public:
    int numDecodings(std::string s) {
        if (s.empty() || s[0] == &apos;0&apos;) return 0;
        
        int n = s.size();
        std::vector&amp;lt;int&amp;gt; dp(n + 1, 0);
        dp[0] = 1;
        dp[1] = 1;

        for (int i = 2; i &amp;lt;= n; ++i) {
            if (s[i - 1] != &apos;0&apos;) {
                dp[i] += dp[i - 1];
            }
            int two_digit = (s[i - 2] - &apos;0&apos;) * 10 + (s[i - 1] - &apos;0&apos;);
            if (two_digit &amp;gt;= 10 &amp;amp;&amp;amp; two_digit &amp;lt;= 26) {
                dp[i] += dp[i - 2];
            }
        }
        return dp[n];
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 139. 单词拆分&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 给定一个字符串和一个词典，判断字符串是否可以被拆分为若干个在词典中出现的单词。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态定义&lt;/strong&gt;：&lt;code&gt;dp[i]&lt;/code&gt; 表示前 &lt;code&gt;i&lt;/code&gt; 个字符是否可以被拆分。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态转移&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;初始化 &lt;code&gt;dp[0] = true&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;对于每个 &lt;code&gt;i&lt;/code&gt;，检查所有可能的 &lt;code&gt;j&lt;/code&gt;（&lt;code&gt;0 &amp;lt;= j &amp;lt; i&lt;/code&gt;），如果 &lt;code&gt;dp[j]&lt;/code&gt; 为真且 &lt;code&gt;s[j:i]&lt;/code&gt; 在词典中，则 &lt;code&gt;dp[i]&lt;/code&gt; 为真。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化&lt;/strong&gt;：可以用一个哈希集合存词典，以便快速查找。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;unordered_set&amp;gt;

class Solution {
public:
    bool wordBreak(std::string s, std::unordered_set&amp;lt;std::string&amp;gt;&amp;amp; wordDict) {
        int n = s.size();
        std::vector&amp;lt;bool&amp;gt; dp(n + 1, false);
        dp[0] = true;

        for (int i = 1; i &amp;lt;= n; ++i) {
            for (int j = 0; j &amp;lt; i; ++j) {
                if (dp[j] &amp;amp;&amp;amp; wordDict.count(s.substr(j, i - j))) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[n];
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 5. 最长回文子串&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 寻找给定字符串中的最长回文子串。回文串是正着读和反着读都一样的字符串。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态定义&lt;/strong&gt;：&lt;code&gt;dp[i][j]&lt;/code&gt; 表示子串 &lt;code&gt;s[i...j]&lt;/code&gt; 是否是回文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态转移&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;初始化：所有长度为1的子串都是回文，&lt;code&gt;dp[i][i] = true&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;对于长度为2的子串，&lt;code&gt;dp[i][i+1] = (s[i] == s[i+1])&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;对于长度大于2的子串，&lt;code&gt;dp[i][j] = (s[i] == s[j] &amp;amp;&amp;amp; dp[i+1][j-1])&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：遍历所有 &lt;code&gt;dp[i][j]&lt;/code&gt;，找到最长的回文子串。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;

class Solution {
public:
    std::string longestPalindrome(std::string s) {
        int n = s.size();
        if (n == 0) return &quot;&quot;;
        
        std::vector&amp;lt;std::vector&amp;lt;bool&amp;gt;&amp;gt; dp(n, std::vector&amp;lt;bool&amp;gt;(n, false));
        int max_len = 1, start = 0;

        // All substrings of length 1 are palindromic
        for (int i = 0; i &amp;lt; n; ++i) {
            dp[i][i] = true;
        }

        // Check for substrings of length 2
        for (int i = 0; i &amp;lt; n - 1; ++i) {
            if (s[i] == s[i + 1]) {
                dp[i][i + 1] = true;
                start = i;
                max_len = 2;
            }
        }

        // Check for substrings of length &amp;gt; 2
        for (int len = 3; len &amp;lt;= n; ++len) {
            for (int i = 0; i &amp;lt;= n - len; ++i) {
                int j = i + len - 1;
                if (s[i] == s[j] &amp;amp;&amp;amp; dp[i + 1][j - 1]) {
                    dp[i][j] = true;
                    start = i;
                    max_len = len;
                }
            }
        }

        return s.substr(start, max_len);
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 32. 最长有效括号&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 寻找字符串中最长的有效括号子串。有效括号指的是每个左括号都有对应的右括号，并且左右括号的顺序正确。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态定义&lt;/strong&gt;：&lt;code&gt;dp[i]&lt;/code&gt; 表示以 &lt;code&gt;s[i]&lt;/code&gt; 结尾的最长有效括号子串的长度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态转移&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;初始化 &lt;code&gt;dp[0] = 0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;遍历字符串，对于每个 &lt;code&gt;)&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;如果前一个字符是 &lt;code&gt;(&lt;/code&gt;，则 &lt;code&gt;dp[i] = dp[i-2] + 2&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果前一个字符是 &lt;code&gt;)&lt;/code&gt;，则需要找到与之匹配的左括号，更新 &lt;code&gt;dp[i]&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：&lt;code&gt;dp&lt;/code&gt; 数组中的最大值即为结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;stack&amp;gt;

class Solution {
public:
    int longestValidParentheses(std::string s) {
        int max_len = 0;
        std::vector&amp;lt;int&amp;gt; dp(s.size(), 0);
        
        for (int i = 1; i &amp;lt; s.size(); ++i) {
            if (s[i] == &apos;)&apos;) {
                if (s[i - 1] == &apos;(&apos;) {
                    dp[i] = (i &amp;gt;= 2 ? dp[i - 2] : 0) + 2;
                } else if (i - dp[i - 1] &amp;gt; 0 &amp;amp;&amp;amp; s[i - dp[i - 1] - 1] == &apos;(&apos;) {
                    dp[i] = dp[i - 1] + ((i - dp[i - 1] &amp;gt;= 2) ? dp[i - dp[i - 1] - 2] : 0) + 2;
                }
            }
            max_len = std::max(max_len, dp[i]);
        }
        
        return max_len;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 300. 最长递增子序列&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 在一个无序数组中找到一个最长的递增子序列。该问题可以通过动态规划解决。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态定义&lt;/strong&gt;：&lt;code&gt;dp[i]&lt;/code&gt; 表示以 &lt;code&gt;nums[i]&lt;/code&gt; 结尾的最长递增子序列的长度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态转移&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;初始化 &lt;code&gt;dp[i] = 1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;对于每个 &lt;code&gt;i&lt;/code&gt;，检查所有小于 &lt;code&gt;i&lt;/code&gt; 的 &lt;code&gt;j&lt;/code&gt;，如果 &lt;code&gt;nums[j] &amp;lt; nums[i]&lt;/code&gt;，则更新 &lt;code&gt;dp[i]&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：&lt;code&gt;dp&lt;/code&gt; 数组中的最大值即为结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int lengthOfLIS(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        if (nums.empty()) return 0;
        
        std::vector&amp;lt;int&amp;gt; dp(nums.size(), 1);
        int max_length = 1;

        for (int i = 1; i &amp;lt; nums.size(); ++i) {
            for (int j = 0; j &amp;lt; i; ++j) {
                if (nums[j] &amp;lt; nums[i]) {
                    dp[i] = std::max(dp[i], dp[j] + 1);
                }
            }
            max_length = std::max(max_length, dp[i]);
        }
        return max_length;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 674. 最长连续递增序列&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 寻找数组中最长的连续递增子序列。与最长递增子序列不同的是，这里要求是连续的。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态定义&lt;/strong&gt;：&lt;code&gt;dp[i]&lt;/code&gt; 表示以 &lt;code&gt;nums[i]&lt;/code&gt; 结尾的最长连续递增子序列的长度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态转移&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;初始化 &lt;code&gt;dp[0] = 1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;对于每个 &lt;code&gt;i&lt;/code&gt;，如果 &lt;code&gt;nums[i] &amp;gt; nums[i-1]&lt;/code&gt;，则 &lt;code&gt;dp[i] = dp[i-1] + 1&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果&lt;/strong&gt;：&lt;code&gt;dp&lt;/code&gt; 数组中的最大值即为结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int findLengthOfLCIS(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        if (nums.empty()) return 0;
        
        std::vector&amp;lt;int&amp;gt; dp(nums.size(), 1);
        int max_length = 1;

        for (int i = 1; i &amp;lt; nums.size(); ++i) {
            if (nums[i] &amp;gt; nums[i - 1]) {
                dp[i] = dp[i - 1] + 1;
            }
            max_length = std::max(max_length, dp[i]);
        }
        return max_length;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 53. 最大子数组和&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 寻找数组中和最大的连续子数组。暴力解法是合并数组后找中点，但时间复杂度不满足要求。分治法的思路是寻找一个合适的分割线，将两个数组都分成左右两部分，使得 &lt;code&gt;左半部分所有元素 &amp;lt;= 右半部分所有元素&lt;/code&gt;，并且 &lt;code&gt;左半部分元素个数 = 右半部分元素个数&lt;/code&gt;（或多一个）。这可以转化为在较短的数组中二分查找这个分割线的位置。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int maxSubArray(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        return maxSubArrayDivideConquer(nums, 0, nums.size() - 1);
    }
private:
    int maxSubArrayDivideConquer(const std::vector&amp;lt;int&amp;gt;&amp;amp; nums, int left, int right) {
        if (left == right) {
            return nums[left];
        }
        int mid = left + (right - left) / 2;
        
        int left_max = maxSubArrayDivideConquer(nums, left, mid);
        int right_max = maxSubArrayDivideConquer(nums, mid + 1, right);
        int cross_max = maxCrossingSum(nums, left, mid, right);
        
        return std::max({left_max, right_max, cross_max});
    }

    int maxCrossingSum(const std::vector&amp;lt;int&amp;gt;&amp;amp; nums, int left, int mid, int right) {
        int left_sum = INT_MIN;
        int sum = 0;
        for (int i = mid; i &amp;gt;= left; --i) {
            sum += nums[i];
            if (sum &amp;gt; left_sum) {
                left_sum = sum;
            }
        }
        
        int right_sum = INT_MIN;
        sum = 0;
        for (int i = mid + 1; i &amp;lt;= right; ++i) {
            sum += nums[i];
            if (sum &amp;gt; right_sum) {
                right_sum = sum;
            }
        }
        return left_sum + right_sum;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 2407. 最长递增子序列 II&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 寻找最长递增子序列，但要求相邻元素之差不超过 &lt;code&gt;k&lt;/code&gt;。这是一个动态规划问题，但由于数据范围，需要使用数据结构进行优化。线段树是实现这种优化的有效工具，其本身就蕴含了分治的思想。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DP状态&lt;/strong&gt;：&lt;code&gt;dp[v]&lt;/code&gt; 表示以数值 &lt;code&gt;v&lt;/code&gt; 结尾的满足条件的最长子序列的长度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态转移&lt;/strong&gt;：当我们处理一个新的数 &lt;code&gt;num&lt;/code&gt; 时，我们需要找到在 &lt;code&gt;[num - k, num - 1]&lt;/code&gt; 这个数值范围内的所有数 &lt;code&gt;v&lt;/code&gt; 中，&lt;code&gt;dp[v]&lt;/code&gt; 的最大值，记为 &lt;code&gt;max_prev_len&lt;/code&gt;。那么，以 &lt;code&gt;num&lt;/code&gt; 结尾的子序列长度就是 &lt;code&gt;max_prev_len + 1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线段树优化&lt;/strong&gt;：线段树用于维护数值范围上的 &lt;code&gt;dp&lt;/code&gt; 值。
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;查询&lt;/strong&gt;：对于每个 &lt;code&gt;num&lt;/code&gt;，在线段树中查询范围 &lt;code&gt;[num - k, num - 1]&lt;/code&gt; 的最大值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新&lt;/strong&gt;：计算出 &lt;code&gt;dp[num]&lt;/code&gt; 后，在线段树中更新 &lt;code&gt;num&lt;/code&gt; 位置的值。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

// Segment Tree Node
struct Node {
    int max_val;
};

class SegTree {
public:
    SegTree(int size) : n(size), tree(4 * size, {0}) {}

    void update(int index, int val) {
        update_recursive(1, 0, n - 1, index, val);
    }

    int query(int l, int r) {
        return query_recursive(1, 0, n - 1, l, r);
    }

private:
    int n;
    std::vector&amp;lt;Node&amp;gt; tree;

    void update_recursive(int node, int start, int end, int idx, int val) {
        if (start == end) {
            tree[node].max_val = val;
            return;
        }
        int mid = (start + end) / 2;
        if (start &amp;lt;= idx &amp;amp;&amp;amp; idx &amp;lt;= mid) {
            update_recursive(2 * node, start, mid, idx, val);
        } else {
            update_recursive(2 * node + 1, mid + 1, end, idx, val);
        }
        tree[node].max_val = std::max(tree[2 * node].max_val, tree[2 * node + 1].max_val);
    }

    int query_recursive(int node, int start, int end, int l, int r) {
        if (r &amp;lt; start || end &amp;lt; l || l &amp;gt; r) {
            return 0;
        }
        if (l &amp;lt;= start &amp;amp;&amp;amp; end &amp;lt;= r) {
            return tree[node].max_val;
        }
        int mid = (start + end) / 2;
        int p1 = query_recursive(2 * node, start, mid, l, r);
        int p2 = query_recursive(2 * node + 1, mid + 1, end, l, r);
        return std::max(p1, p2);
    }
};

class Solution {
public:
    int lengthOfLIS(std::vector&amp;lt;int&amp;gt;&amp;amp; nums, int k) {
        int max_val = 0;
        for(int num : nums) max_val = std::max(max_val, num);

        SegTree st(max_val + 1);
        int max_len = 0;

        for (int num : nums) {
            int prev_max_len = st.query(std::max(0, num - k), num - 1);
            int current_len = prev_max_len + 1;
            st.update(num, current_len);
            max_len = std::max(max_len, current_len);
        }
        return max_len;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;回溯 (Backtracking)&lt;/h2&gt;
&lt;p&gt;回溯是一种试探性算法，用于寻找所有（或某个）解。它通过构建解的所有可能的候选者，并逐步验证每个候选者是否满足问题的要求。
回溯法通常用于解决组合、排列、子集等问题。其基本思想是：对问题的解空间进行深度优先搜索（DFS），在搜索过程中根据当前解的状态，动态地决定是否继续向下搜索，还是进行回溯。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 22. 括号生成&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 生成所有有效的括号组合。使用回溯法，维护一个当前组合的字符串 &lt;code&gt;current&lt;/code&gt;，以及左右括号的剩余可用数量 &lt;code&gt;left&lt;/code&gt; 和 &lt;code&gt;right&lt;/code&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基准情况&lt;/strong&gt;：当 &lt;code&gt;current&lt;/code&gt; 长度达到 &lt;code&gt;2 * n&lt;/code&gt; 时，说明生成了一个完整的组合，加入结果集中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归步骤&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;left &amp;gt; 0&lt;/code&gt;，说明还有左括号可以使用，尝试添加一个左括号，并递归。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;right &amp;gt; left&lt;/code&gt;，说明当前的左括号数量少于右括号，可以添加右括号，保持组合的有效性，然后递归。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::string&amp;gt; generateParenthesis(int n) {
        std::vector&amp;lt;std::string&amp;gt; result;
        std::string current;
        backtrack(result, current, n, n);
        return result;
    }
private:
    void backtrack(std::vector&amp;lt;std::string&amp;gt;&amp;amp; result, std::string&amp;amp; current, int left, int right) {
        if (current.length() == 2 * left) {
            result.push_back(current);
            return;
        }
        if (left &amp;gt; 0) {
            current.push_back(&apos;(&apos;);
            backtrack(result, current, left - 1, right);
            current.pop_back();
        }
        if (right &amp;gt; left) {
            current.push_back(&apos;)&apos;);
            backtrack(result, current, left, right - 1);
            current.pop_back();
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 46. 全排列&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 生成数组的所有排列。使用回溯法，维护一个当前排列的数组 &lt;code&gt;current&lt;/code&gt;，以及一个布尔数组 &lt;code&gt;used&lt;/code&gt; 来标记数组元素是否被使用。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基准情况&lt;/strong&gt;：当 &lt;code&gt;current&lt;/code&gt; 长度达到 &lt;code&gt;nums&lt;/code&gt; 长度时，说明生成了一个完整的排列，加入结果集中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归步骤&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;遍历 &lt;code&gt;nums&lt;/code&gt; 中的每个元素，如果未被使用，则将其加入 &lt;code&gt;current&lt;/code&gt;，并标记为已使用。&lt;/li&gt;
&lt;li&gt;递归调用以生成下一个位置的排列。&lt;/li&gt;
&lt;li&gt;回溯时，撤销当前选择，标记为未使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; permute(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; result;
        std::vector&amp;lt;int&amp;gt; current;
        std::vector&amp;lt;bool&amp;gt; used(nums.size(), false);
        backtrack(result, current, nums, used);
        return result;
    }
private:
    void backtrack(std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; result, std::vector&amp;lt;int&amp;gt;&amp;amp; current, 
                   const std::vector&amp;lt;int&amp;gt;&amp;amp; nums, std::vector&amp;lt;bool&amp;gt;&amp;amp; used) {
        if (current.size() == nums.size()) {
            result.push_back(current);
            return;
        }
        for (int i = 0; i &amp;lt; nums.size(); ++i) {
            if (used[i]) continue;
            used[i] = true;
            current.push_back(nums[i]);
            backtrack(result, current, nums, used);
            current.pop_back();
            used[i] = false;
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 17. 电话号码的字母组合&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 给定一个电话号码的数字，返回所有可能的字母组合。每个数字对应的字母由一个映射表给出。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基准情况&lt;/strong&gt;：当 &lt;code&gt;index&lt;/code&gt; 等于数字字符串长度时，说明生成了一组完整的字母组合，加入结果集中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归步骤&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;获取当前数字对应的所有字母。&lt;/li&gt;
&lt;li&gt;遍历这些字母，将每个字母加入当前组合。&lt;/li&gt;
&lt;li&gt;递归调用以生成下一个数字的字母组合。&lt;/li&gt;
&lt;li&gt;回溯时，撤销当前字母选择。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::string&amp;gt; letterCombinations(std::string digits) {
        std::vector&amp;lt;std::string&amp;gt; result;
        if (digits.empty()) return result;
        std::string current;
        backtrack(result, current, digits, 0);
        return result;
    }
private:
    void backtrack(std::vector&amp;lt;std::string&amp;gt;&amp;amp; result, std::string&amp;amp; current, 
                   const std::string&amp;amp; digits, int index) {
        if (index == digits.length()) {
            result.push_back(current);
            return;
        }
        std::string letters = getLetters(digits[index]);
        for (char c : letters) {
            current.push_back(c);
            backtrack(result, current, digits, index + 1);
            current.pop_back();
        }
    }

    std::string getLetters(char digit) {
        switch (digit) {
            case &apos;2&apos;: return &quot;abc&quot;;
            case &apos;3&apos;: return &quot;def&quot;;
            case &apos;4&apos;: return &quot;ghi&quot;;
            case &apos;5&apos;: return &quot;jkl&quot;;
            case &apos;6&apos;: return &quot;mno&quot;;
            case &apos;7&apos;: return &quot;pqrs&quot;;
            case &apos;8&apos;: return &quot;tuv&quot;;
            case &apos;9&apos;: return &quot;wxyz&quot;;
            default: return &quot;&quot;;
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 51. N 皇后&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 在 &lt;code&gt;n * n&lt;/code&gt; 的棋盘上放置 &lt;code&gt;n&lt;/code&gt; 个皇后，使其互不攻击。皇后可以攻击同一行、同一列或同一对角线上的任意棋子。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基准情况&lt;/strong&gt;：当 &lt;code&gt;row&lt;/code&gt; 等于 &lt;code&gt;n&lt;/code&gt; 时，说明成功放置了 &lt;code&gt;n&lt;/code&gt; 个皇后，将当前棋盘布局加入结果集中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;递归步骤&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;遍历棋盘的每一列，尝试在当前行的每一列放置皇后。&lt;/li&gt;
&lt;li&gt;检查该位置是否安全（即该列和两个对角线没有其他皇后）。&lt;/li&gt;
&lt;li&gt;如果安全，则在该位置放置皇后，并递归调用以放置下一行的皇后。&lt;/li&gt;
&lt;li&gt;回溯时，撤销当前皇后放置。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;std::string&amp;gt;&amp;gt; solveNQueens(int n) {
        std::vector&amp;lt;std::vector&amp;lt;std::string&amp;gt;&amp;gt; result;
        std::vector&amp;lt;std::string&amp;gt; board(n, std::string(n, &apos;.&apos;));
        backtrack(result, board, 0, n);
        return result;
    }
private:
    void backtrack(std::vector&amp;lt;std::vector&amp;lt;std::string&amp;gt;&amp;gt;&amp;amp; result, std::vector&amp;lt;std::string&amp;gt;&amp;amp; board, 
                   int row, int n) {
        if (row == n) {
            result.push_back(board);
            return;
        }
        for (int col = 0; col &amp;lt; n; ++col) {
            if (!isSafe(board, row, col, n)) {
                continue;
            }
            board[row][col] = &apos;Q&apos;;
            backtrack(result, board, row + 1, n);
            board[row][col] = &apos;.&apos;;
        }
    }

    bool isSafe(const std::vector&amp;lt;std::string&amp;gt;&amp;amp; board, int row, int col, int n) {
        for (int i = 0; i &amp;lt; row; ++i) {
            if (board[i][col] == &apos;Q&apos;) {
                return false;
            }
            int diag1 = col - (row - i);
            int diag2 = col + (row - i);
            if (diag1 &amp;gt;= 0 &amp;amp;&amp;amp; board[i][diag1] == &apos;Q&apos;) {
                return false;
            }
            if (diag2 &amp;lt; n &amp;amp;&amp;amp; board[i][diag2] == &apos;Q&apos;) {
                return false;
            }
        }
        return true;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 52. N 皇后 II&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 与 &quot;N 皇后&quot; 问题完全相同，只是最后不要求返回棋盘布局，而是返回解的总数。因此，我们可以在回溯算法的基准情况（当 &lt;code&gt;row == n&lt;/code&gt;）中，不存储棋盘，而是简单地将计数器加一。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;

class Solution {
public:
    int totalNQueens(int n) {
        int count = 0;
        std::vector&amp;lt;bool&amp;gt; cols(n, false);
        std::vector&amp;lt;bool&amp;gt; diag1(2 * n - 1, false);
        std::vector&amp;lt;bool&amp;gt; diag2(2 * n - 1, false);
        backtrack(0, n, count, cols, diag1, diag2);
        return count;
    }
private:
    void backtrack(int row, int n, int&amp;amp; count,
                   std::vector&amp;lt;bool&amp;gt;&amp;amp; cols, std::vector&amp;lt;bool&amp;gt;&amp;amp; diag1, std::vector&amp;lt;bool&amp;gt;&amp;amp; diag2) {
        if (row == n) {
            count++;
            return;
        }
        for (int col = 0; col &amp;lt; n; ++col) {
            int d1 = row - col + n - 1;
            int d2 = row + col;
            if (cols[col] || diag1[d1] || diag2[d2]) {
                continue;
            }
            cols[col] = diag1[d1] = diag2[d2] = true;
            backtrack(row + 1, n, count, cols, diag1, diag2);
            cols[col] = diag1[d1] = diag2[d2] = false;
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 47. 全排列 II&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 生成包含重复元素数组的全排列。为了避免生成重复的排列，我们需要在回溯时进行剪枝。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;排序&lt;/strong&gt;：首先对输入数组 &lt;code&gt;nums&lt;/code&gt; 进行排序。这使得相同的元素相邻，便于剪枝。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;剪枝&lt;/strong&gt;：在回溯的循环中，如果 &lt;code&gt;i &amp;gt; start&lt;/code&gt; 并且 &lt;code&gt;nums[i] == nums[i-1]&lt;/code&gt;，则跳过 &lt;code&gt;nums[i]&lt;/code&gt;，以避免产生重复的排列。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;used&lt;/code&gt; 数组&lt;/strong&gt;：需要一个 &lt;code&gt;used&lt;/code&gt; 布尔数组来跟踪每个索引的元素是否已在当前路径中使用。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; permuteUnique(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; result;
        std::vector&amp;lt;int&amp;gt; path;
        std::vector&amp;lt;bool&amp;gt; used(nums.size(), false);
        std::sort(nums.begin(), nums.end());
        backtrack(nums, path, used, result);
        return result;
    }
private:
    void backtrack(const std::vector&amp;lt;int&amp;gt;&amp;amp; nums, std::vector&amp;lt;int&amp;gt;&amp;amp; path, std::vector&amp;lt;bool&amp;gt;&amp;amp; used, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; result) {
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        for (int i = 0; i &amp;lt; nums.size(); ++i) {
            if (used[i]) continue;
            // Pruning: if current element is same as previous, and previous was not used in this path, skip.
            if (i &amp;gt; 0 &amp;amp;&amp;amp; nums[i] == nums[i-1] &amp;amp;&amp;amp; !used[i-1]) continue;
            
            used[i] = true;
            path.push_back(nums[i]);
            backtrack(nums, path, used, result);
            path.pop_back();
            used[i] = false;
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 39. 组合总和&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 从一个无重复元素的数组中找出所有和为 &lt;code&gt;target&lt;/code&gt; 的组合，每个数字可以使用无限次。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;排序&lt;/strong&gt;：对 &lt;code&gt;candidates&lt;/code&gt; 排序可以进行一个优化剪枝：如果当前和已经大于 &lt;code&gt;target&lt;/code&gt;，后续更大的元素也不可能满足条件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回溯&lt;/strong&gt;：定义 &lt;code&gt;backtrack(start, current_sum, path)&lt;/code&gt;。
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;start&lt;/code&gt; 参数用于避免产生重复的组合（如 &lt;code&gt;[2,3]&lt;/code&gt; 和 &lt;code&gt;[3,2]&lt;/code&gt;）。在递归调用时，我们只从 &lt;code&gt;start&lt;/code&gt; 索引开始选择，而不是从0开始。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;current_sum == target&lt;/code&gt;，找到一个解。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;current_sum &amp;gt; target&lt;/code&gt;，剪枝。&lt;/li&gt;
&lt;li&gt;循环从 &lt;code&gt;start&lt;/code&gt; 到数组末尾，选择一个 &lt;code&gt;candidates[i]&lt;/code&gt;，加入路径，递归调用 &lt;code&gt;backtrack(i, ...)&lt;/code&gt;（注意是 &lt;code&gt;i&lt;/code&gt; 而不是 &lt;code&gt;i+1&lt;/code&gt;，因为数字可以重复使用）。然后回溯。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; combinationSum(std::vector&amp;lt;int&amp;gt;&amp;amp; candidates, int target) {
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; result;
        std::vector&amp;lt;int&amp;gt; path;
        std::sort(candidates.begin(), candidates.end());
        backtrack(candidates, target, 0, path, result);
        return result;
    }
private:
    void backtrack(const std::vector&amp;lt;int&amp;gt;&amp;amp; candidates, int remain, int start, std::vector&amp;lt;int&amp;gt;&amp;amp; path, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; result) {
        if (remain == 0) {
            result.push_back(path);
            return;
        }
        if (remain &amp;lt; 0) {
            return;
        }
        for (int i = start; i &amp;lt; candidates.size(); ++i) {
            path.push_back(candidates[i]);
            backtrack(candidates, remain - candidates[i], i, path, result);
            path.pop_back();
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 40. 组合总和 II&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 与 &quot;组合总和&quot; 类似，但 &lt;code&gt;candidates&lt;/code&gt; 可能包含重复数字，且每个数字只能使用一次。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;排序&lt;/strong&gt;：必须排序以处理重复。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回溯与剪枝&lt;/strong&gt;：在回溯的循环中，如果 &lt;code&gt;i &amp;gt; start&lt;/code&gt; 且 &lt;code&gt;candidates[i] == candidates[i-1]&lt;/code&gt;，则跳过 &lt;code&gt;candidates[i]&lt;/code&gt;，以避免产生重复的组合。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; combinationSum2(std::vector&amp;lt;int&amp;gt;&amp;amp; candidates, int target) {
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; result;
        std::vector&amp;lt;int&amp;gt; path;
        std::sort(candidates.begin(), candidates.end());
        backtrack(candidates, target, 0, path, result);
        return result;
    }
private:
    void backtrack(const std::vector&amp;lt;int&amp;gt;&amp;amp; candidates, int remain, int start, std::vector&amp;lt;int&amp;gt;&amp;amp; path, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; result) {
        if (remain == 0) {
            result.push_back(path);
            return;
        }
        if (remain &amp;lt; 0) return;

        for (int i = start; i &amp;lt; candidates.size(); ++i) {
            if (i &amp;gt; start &amp;amp;&amp;amp; candidates[i] == candidates[i-1]) continue; // Skip duplicates
            
            path.push_back(candidates[i]);
            backtrack(candidates, remain - candidates[i], i + 1, path, result);
            path.pop_back();
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 78. 子集&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 生成一个不含重复元素数组的所有子集（幂集）。
回溯法非常直观：对于数组中的每个元素，我们都有两种选择：将它包含在当前子集中，或者不包含。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; subsets(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; result;
        std::vector&amp;lt;int&amp;gt; path;
        backtrack(nums, 0, path, result);
        return result;
    }
private:
    void backtrack(const std::vector&amp;lt;int&amp;gt;&amp;amp; nums, int start, std::vector&amp;lt;int&amp;gt;&amp;amp; path, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; result) {
        result.push_back(path); // Add the current combination
        for (int i = start; i &amp;lt; nums.size(); ++i) {
            path.push_back(nums[i]);
            backtrack(nums, i + 1, path, result);
            path.pop_back();
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 90. 子集 II&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 生成一个可能包含重复元素数组的所有子集，且结果不能包含重复子集。
与 &quot;组合总和 II&quot; 的去重逻辑类似：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;排序&lt;/strong&gt;：必须先对 &lt;code&gt;nums&lt;/code&gt; 排序。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回溯与剪枝&lt;/strong&gt;：在回溯的循环中，如果 &lt;code&gt;i &amp;gt; start&lt;/code&gt; 且 &lt;code&gt;nums[i] == nums[i-1]&lt;/code&gt;，则跳过 &lt;code&gt;nums[i]&lt;/code&gt;，以避免产生重复的子集。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; subsetsWithDup(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; result;
        std::vector&amp;lt;int&amp;gt; path;
        std::sort(nums.begin(), nums.end());
        backtrack(nums, 0, path, result);
        return result;
    }
private:
    void backtrack(const std::vector&amp;lt;int&amp;gt;&amp;amp; nums, int start, std::vector&amp;lt;int&amp;gt;&amp;amp; path, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; result) {
        result.push_back(path);
        for (int i = start; i &amp;lt; nums.size(); ++i) {
            if (i &amp;gt; start &amp;amp;&amp;amp; nums[i] == nums[i-1]) continue; // Skip duplicates
            path.push_back(nums[i]);
            backtrack(nums, i + 1, path, result);
            path.pop_back();
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>强化学习 Chapter 2 - 动态规划方法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter2/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter2/</guid><pubDate>Thu, 10 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;2.1 Recap: Bellman 最优方程&lt;/h1&gt;
&lt;p&gt;通过求解 Bellman 最优方程，我们可以得到最优策略和最优状态价值函数。Bellman 最优方程是一个递归方程，它将当前状态的价值与下一个状态的价值联系起来。上节中我们推导得到的 Bellman 最优方程的形式如下
$$
V^&lt;em&gt;(s) = \max_{\pi} \sum_{a \in \mathcal{A(s)}} \pi(a|s) \left[ \sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V^&lt;/em&gt;(s&apos;) \right] \quad \forall s \in \mathcal{S}
$$
其中 $V^&lt;em&gt;(s)$ 是最优状态价值函数，$V(s)$ 是当前状态的价值函数，$\pi$ 是策略，$\mathcal{A(s)}$ 是状态 $s$ 的动作空间，$\mathcal{R}$ 是奖励空间，$\mathcal{S}$ 是状态空间，$p(r|s, a)$ 是在状态 $s$ 下采取动作 $a$ 时获得奖励 $r$ 的概率，$p(s&apos;|s, a)$ 是在状态 $s$ 下采取动作 $a$ 时转移到状态 $s&apos;$ 的概率。方程中，最优的状态价值函数 $V^&lt;/em&gt;(s)$ 是未知的，策略空间 $\pi \in \Pi$，动作空间 $\mathcal{A(s)}$，奖励空间 $\mathcal{R}$，状态空间 $\mathcal{S}$，环境模型 $p(r|s, a)$ 和 $p(s&apos;|s, a)$ 都是已知的。&lt;/p&gt;
&lt;h2&gt;2.1.1 优化变量的替换 $\max_\pi \rightarrow \max_a$&lt;/h2&gt;
&lt;p&gt;当环境变量 $p(r|s, a)$ 和 $p(s&apos;|s, a)$ 已知时，贪心策略 $\pi^&lt;em&gt;(s)$，一个&lt;strong&gt;确定性&lt;/strong&gt;策略，&lt;strong&gt;是一个最优策略&lt;/strong&gt;。贪心策略可以被表示为
$$
\pi^&lt;/em&gt;(a | s) = \begin{cases} 1, &amp;amp; a = a^&lt;em&gt;,\ 0, &amp;amp; a \ne a^&lt;/em&gt;, \end{cases}
$$
即贪心策略只会选取当前已知的最优动作 $a^*$，而不考虑其他动作。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;贪心策略是最优策略，但是最优策略并不一定都是贪心策略&lt;/li&gt;
&lt;li&gt;贪心策略是一个确定性策略，而最优策略可以是一个随机策略&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;推导
$$
\begin{align*}
V^&lt;em&gt;(s) &amp;amp;= \max_{\pi} \sum_{a \in \mathcal{A(s)}} \pi(a|s) \left[ \sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V^&lt;/em&gt;(s&apos;) \right] \
&amp;amp;= \max_a \left[ \sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V^&lt;em&gt;(s&apos;) \right] \
\end{align&lt;/em&gt;}
$$&lt;/p&gt;
&lt;p&gt;这是因为 $\sum_{a\in \mathcal{A(s)}}[f(a)]$ 是一个对于 $a$ 加权平均数，因为 $\sum_{a\in \mathcal{A(s)}} \pi(a|s) = 1$。令 $f(a)$ 取最大值的 $a$ 对应的权重为 1，其他的权重为 0，即可实现 $\sum_{a\in \mathcal{A(s)}}[f(a)]$ 取最大值。&lt;/p&gt;
&lt;p&gt;因此，Bellman 最优方程可以简化为（对应贪心策略）
$$
V^&lt;em&gt;(s) = \max_a \left[ \sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V^&lt;/em&gt;(s&apos;) \right] \quad \forall s \in \mathcal{S}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方程性质&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;解的存在性&lt;/li&gt;
&lt;li&gt;解的唯一性&lt;/li&gt;
&lt;li&gt;解的最优性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;相关证明可以参考可以参考 &lt;a href=&quot;https://github.com/MathFoundationRL/Book-Mathematical-Foundation-of-Reinforcement-Learning/blob/main/Book-all-in-one.pdf&quot;&gt;西湖大学赵世钰老师所出版的强化学习的数学原理的 P38-P45&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;那么，该如何求解 Bellman 最优方程呢？这一节的答案是 DP（动态规划）&lt;/p&gt;
&lt;h1&gt;2.2 动态规划方法概述&lt;/h1&gt;
&lt;p&gt;动态规划的思想是将一个复杂的问题分解为多个简单的子问题，通过求解子问题来求解原问题。一般可以用DP求解的问题具有&lt;strong&gt;最优子结构&lt;/strong&gt;和&lt;strong&gt;重叠子问题&lt;/strong&gt;的性质。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最优子结构：指一个问题的最优解包含了其子问题的最优解&lt;/li&gt;
&lt;li&gt;重叠子问题：指一个问题可以分解为多个子问题，这些子问题之间存在重叠&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;动态规划的方法假设已经有了MDP框架下的全部知识，包括状态空间 $\mathcal{S}$、动作空间 $\mathcal{A}$、奖励函数 $r(s, a)$、转移概率 $p(s&apos;|s, a)$ 等。&lt;strong&gt;需要注意这一点，因为后续的强化学习方法不用建立在这些知识都已知的基础之上的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在求解 Bellman 最优方程的问题上，DP的核心思想是 使用价值函数的 Bellman 结构搜索最优策略，换句话说，假设状态 $s$ 的价值函数 $V^&lt;em&gt;(s)$ 已知，那么可以通过迭代的方式得到其他状态 $s&apos;$ 的价值函数 $V^&lt;/em&gt;(s&apos;)$。&lt;strong&gt;在每一轮迭代的过程中， 第 $k + 1$ 轮的状态价值函数的求解需要依赖第 $k$ 轮的状态价值函数的结果。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最优子结构：对于每一个状态 $s$，它的最优价值函数 $V^&lt;em&gt;(s)$ 是由它的后继状态 $s&apos;$ 的最优价值函数 $V^&lt;/em&gt;(s&apos;)$ 计算得到的，后继的最优用于求解当前的最优。&lt;/li&gt;
&lt;li&gt;重叠子问题：对于不同的状态 $s$，它在计算 Bellman 方程时都有可能会用到同一个状态 $s&apos;$ 的价值函数 $V^*(s&apos;)$，因此可以将这些重复的计算结果存储起来，避免重复计算，从而提高效率。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2.3 Policy Iteration&lt;/h1&gt;
&lt;p&gt;策略迭代由两部分组成，分别是&lt;strong&gt;策略评估&lt;/strong&gt;和&lt;strong&gt;策略改进&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;策略评估：在给定策略的情况下，计算该策略下的价值函数 $V_\pi(s)$&lt;/li&gt;
&lt;li&gt;策略改进：在给定状态价值函数的情况下，计算最优策略 $\pi^*(s)$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.3.1 策略评估&lt;/h2&gt;
&lt;p&gt;假设已知当前策略为 $\pi$，目标是预测出在该策略下的状态价值函数 $V_\pi(s)$。基于 Bellman 期望方程，有
$$
V_\pi(s) = \sum_{a \in \mathcal{A}(s)} \pi(a|s) \left[\sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V_\pi(s&apos;) \right]
$$
该方程是一个线性方程组，向量形式为
$$
V_\pi = r_\pi + \gamma P_\pi V_\pi
$$
其中，$r_\pi$ 是期望的奖励向量，$P_\pi$ 是状态转移概率矩阵， $V_\pi$ 是状态价值函数向量。显然，当 $(I - \gamma P_\pi)$ 可逆时，方程组有唯一解 $V_\pi = (I - \gamma P_\pi)^{-1} r_\pi$。当然，也可以通过迭代的方式求解该方程组，迭代公式为
$$
V_\pi^{(k+1)}(s) = \sum_{a \in \mathcal{A}(s)} \pi(a|s) \left[\sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V_\pi^{(k)}(s&apos;) \right] \quad \forall s \in \mathcal{S}
$$
这个迭代方程的含义是方程会收敛到一个不动点 $V_\pi^{(k)}(s) = V_\pi(s)$，这里的 $V_\pi(s)$ 就是最终的状态价值函数。逐步迭代，即可完成对策略的评估。&lt;/p&gt;
&lt;h2&gt;2.3.2 策略改进&lt;/h2&gt;
&lt;p&gt;在策略评估的基础上，我们可以通过计算 Bellman 最优方程来实现策略改进。
形式为
$$
\begin{align*}
\pi^{new}(a | s) &amp;amp;= \arg\max_\pi \sum_{a \in \mathcal{A(s)}} \pi(a|s) \left[ \sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V_\pi(s&apos;) \right]  \quad \forall s \in \mathcal{S}\
\end{align*}
$$&lt;/p&gt;
&lt;p&gt;为了简化计算，可以采用贪心策略，于是策略改进的形式为
$$
\begin{align*}
\pi^{new}(a | s) &amp;amp;= \begin{cases} 1, &amp;amp; a = \arg\max_a \left[ \sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V_\pi(s&apos;) \right],\ 0, &amp;amp; a \ne \arg\max_a \left[ \sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V_\pi(s&apos;) \right] \ \end{cases} \
\end{align*}
$$
这里的计算只需一步即可完成，无需迭代。&lt;/p&gt;
&lt;h2&gt;2.3.3 完整算法&lt;/h2&gt;
&lt;p&gt;策略迭代的完整算法是一条迭代链条
$$
\pi_0 \rightarrow V_{\pi_0} \rightarrow \pi_1 \rightarrow V_{\pi_1} \rightarrow \pi_2 \rightarrow V_{\pi_2} \rightarrow \pi_3 \dots
$$&lt;/p&gt;
&lt;p&gt;策略迭代的完整算法如下&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Policy Iteration 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;策略 $\pi$，状态空间 $\mathcal{S}$，动作空间 $\mathcal{A}$，奖励函数 $r(s, a)$，转移概率 $p(s&apos;\vert s, a)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;最优策略 $\pi^* $ ，最优状态价值函数 $V^*(s)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化策略 $\pi$ 和状态价值函数 $V_\pi(s)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;     $\Delta \leftarrow 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;     &lt;strong&gt;for each&lt;/strong&gt; $s \in \mathcal{S}$ &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;       $V \leftarrow V_\pi(s)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;       $V_\pi(s) \leftarrow \sum_{a \in \mathcal{A}} \pi(a\vert s) [r(s,a) + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;\vert s,a) V_\pi(s&apos;)]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;       $\Delta \leftarrow \max(\Delta, \vert V - V_\pi(s)\vert)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;     &lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;until&lt;/strong&gt; $\Delta &amp;lt; \theta$ (策略评估收敛)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;policy_stable&lt;/strong&gt; $\leftarrow$ &lt;strong&gt;true&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;for each&lt;/strong&gt; $s \in \mathcal{S}$ &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13:&lt;/td&gt;
&lt;td&gt;     &lt;strong&gt;old_action&lt;/strong&gt; $\leftarrow$ 当前策略 $\pi(s)$ 所选的动作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14:&lt;/td&gt;
&lt;td&gt;     $\pi(s) \leftarrow \arg\max_a [r(s,a) + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;\vert s,a) V_\pi(s&apos;)]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15:&lt;/td&gt;
&lt;td&gt;     &lt;strong&gt;if&lt;/strong&gt; &lt;strong&gt;old_action&lt;/strong&gt; $\neq \pi(s)$ &lt;strong&gt;then&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16:&lt;/td&gt;
&lt;td&gt;       &lt;strong&gt;policy_stable&lt;/strong&gt; $\leftarrow$ &lt;strong&gt;false&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17:&lt;/td&gt;
&lt;td&gt;     &lt;strong&gt;end if&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;until policy_stable&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20:&lt;/td&gt;
&lt;td&gt;返回 $V_\pi$ 作为 $V^&lt;em&gt;$，$\pi$ 作为 $\pi^&lt;/em&gt;$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Example Problem: Cliff Walking&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/cliffwalking.png&quot; alt=&quot;cliff walking&quot; /&gt;&lt;/p&gt;
&lt;p&gt;智能体在悬崖上行走，目标是到达终点。智能体可以向上、下、左、右四个方向移动，每次移动的奖励为 -1，如果跌落悬崖，则返回起点，奖励为 -1， 如果到达终点，则奖励为 +1。存在障碍物，智能体不能穿过障碍物。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import numpy as np
from matplotlib.patches import Patch
import matplotlib.pyplot as plt

class GridWorld:
    def __init__(self):
        self.size = 6
        self.terminal = (5, 5)
        self.actions = [&apos;up&apos;, &apos;down&apos;, &apos;left&apos;, &apos;right&apos;]
        self.obstacles = [(1, 1), (2, 2), (2, 3), (4, 5), (5, 2)]
        self.cliffs = [(0, 3), (0, 4), (0, 5), (1, 5), (3, 0), (4, 2)]

    def is_terminal(self, state):
        return state == self.terminal or state in self.cliffs

    def get_reward(self, state):
        if state == self.terminal:
            return 1
        else:
            return -1

    def transition(self, state, action):
        if self.is_terminal(state):
            return state

        row, col = state
        new_row, new_col = row, col

        # 处理移动
        if action == &apos;up&apos;:
            new_row = max(row - 1, 0)
        elif action == &apos;down&apos;:
            new_row = min(row + 1, self.size - 1)
        elif action == &apos;left&apos;:
            new_col = max(col - 1, 0)
        elif action == &apos;right&apos;:
            new_col = min(col + 1, self.size - 1)

        # 障碍物检测
        if (new_row, new_col) in self.obstacles:
            return state

        # 悬崖处理
        if (new_row, new_col) in self.cliffs:
            return (0, 0)  # 跌落悬崖返回起点

        return (new_row, new_col)

env = GridWorld()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;策略迭代的实现如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def policy_iteration(env: GridWorld, gamma=0.9, theta=1e-6):
    V = np.zeros((env.size, env.size))
    V[env.terminal] = 1  # 初始化终点价值
    policy = np.full((env.size, env.size), None, dtype=object)  # 初始化为None, 确定性动作，非概率性

    # 仅对可行动状态初始化策略
    for i in range(env.size):
        for j in range(env.size):
            state = (i, j)
            if not env.is_terminal(state) and state not in env.obstacles:
                policy[i][j] = &apos;right&apos;  # 初始策略

    policy_improvement_iters = 0
    policy_evaluation_iters = 0

    while True:
        policy_improvement_iters += 1

        # 策略评估
        eval_iters = 0
        while True:
            eval_iters += 1
            delta = 0
            for i in range(env.size):
                for j in range(env.size):
                    state = (i, j)
                    if env.is_terminal(state) or state in env.obstacles:
                        continue

                    old_v = V[i][j]
                    action = policy[i][j]
                    next_state = env.transition(state, action)
                    reward = env.get_reward(next_state)
                    V[i, j] = gamma * V[next_state] + reward
                    delta = max(delta, abs(old_v - V[i][j]))

            if delta &amp;lt; theta:
                break
        policy_evaluation_iters += eval_iters

        # 策略改进
        policy_stable = True
        for i in range(env.size):
            for j in range(env.size):
                state = (i, j)
                # 设置特殊位置的策略为None
                if env.is_terminal(state) or state in env.obstacles:
                    policy[i][j] = None
                    continue

                old_action = policy[i][j]
                max_value = -np.inf
                best_action = old_action

                for action in env.actions:
                    next_state = env.transition(state, action)
                    reward = env.get_reward(next_state)
                    value = reward + gamma * V[next_state]

                    if value &amp;gt; max_value:
                        max_value = value
                        best_action = action

                policy[i][j] = best_action
                if old_action != best_action:
                    policy_stable = False

        if policy_stable:
            break

    print(f&quot;策略迭代: 改进次数={policy_improvement_iters} 评估总次数={policy_evaluation_iters}&quot;)
    return V, policy

def print_policy(policy):
    direction_symbols = {
        &apos;up&apos;: &apos;↑&apos;,
        &apos;down&apos;: &apos;↓&apos;,
        &apos;left&apos;: &apos;←&apos;,
        &apos;right&apos;: &apos;→&apos;,
        None: &apos;■&apos;
    }

    print(&quot;策略可视化：&quot;)
    for row in policy:
        print(&quot; &quot;.join([direction_symbols[a] for a in row]))


env = GridWorld()
print(&quot;=== 策略迭代 ===&quot;)
V_pi, policy_pi = policy_iteration(env)
print(&quot;价值函数:\n&quot;, np.round(V_pi, 2))
print_policy(policy_pi)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;2.4 Value Iteration&lt;/h1&gt;
&lt;p&gt;策略迭代的缺点是需要两次迭代，分别是策略评估和策略改进。我们可以将这两步合并为一步，称为价值迭代。价值迭代的核心思想是使用 Bellman 最优方程来直接更新状态价值函数 $V^*(s)$，而不是使用策略评估来更新状态价值函数 $V_\pi(s)$。&lt;/p&gt;
&lt;p&gt;迭代公式为
$$
V^{(k+1)}(s) = \sum_{a \in \mathcal{A}} \pi(a|s) \left[ \sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V^{(k)}(s&apos;) \right] \quad \forall s \in \mathcal{S}
$$
此公式的收敛点为最优状态价值函数 $V^*(s)$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Value Iteration 算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;状态空间 $\mathcal{S}$，动作空间 $\mathcal{A}$，奖励函数 $r(s, a)$，转移概率 $p(s&apos;\vert s, a)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;最优状态价值函数 $V^&lt;em&gt;(s)$，最优策略 $\pi^&lt;/em&gt; $&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;初始化状态价值函数 $V(s)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;   $\Delta \leftarrow 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;for each&lt;/strong&gt; $s \in \mathcal{S}$ &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;     $V \leftarrow V(s)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;     $V(s) \leftarrow \max_a [r(s,a) + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;\vert s,a) V(s&apos;)]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;     $\Delta \leftarrow \max(\Delta, \vert V - V(s)\vert)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;   &lt;strong&gt;end for&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;until&lt;/strong&gt; $\Delta &amp;lt; \theta$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;根据 $V$ 确定最优策略 $\pi^*(s) \leftarrow \arg\max_a [r(s,a) + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;\vert s,a) V(s&apos;)]$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;返回 $V$ 作为 $V^&lt;em&gt;$，$\pi^&lt;/em&gt;$ 作为最优策略&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;def value_iteration(env, gamma=0.9, theta=1e-6):
    V = np.zeros((env.size, env.size))
    V[env.terminal] = 1
    value_iters = 0

    while True:
        value_iters += 1
        delta = 0

        for i in range(env.size):
            for j in range(env.size):
                state = (i, j)
                if env.is_terminal(state) or state in env.obstacles:
                    continue

                old_v = V[i][j]
                max_value = -np.inf

                for action in env.actions:
                    next_state = env.transition(state, action)
                    reward = env.get_reward(next_state)
                    value = reward + gamma * V[next_state]
                    max_value = max(max_value, value)

                V[i][j] = max_value
                delta = max(delta, abs(old_v - V[i][j]))

        if delta &amp;lt; theta:
            break

    # 提取策略（特殊位置设为None）
    policy = np.full((env.size, env.size), None, dtype=object)
    for i in range(env.size):
        for j in range(env.size):
            state = (i, j)
            if env.is_terminal(state) or state in env.obstacles:
                continue

            max_value = -np.inf
            best_action = None

            for action in env.actions:
                next_state = env.transition(state, action)
                reward = env.get_reward(next_state)
                value = reward + V[next_state] * gamma
                if value &amp;gt; max_value:
                    max_value = value
                    best_action = action

            policy[i][j] = best_action

    print(f&quot;价值迭代: 总次数={value_iters}&quot;)
    return V, policy

def print_policy(policy):
    direction_symbols = {
        &apos;up&apos;: &apos;↑&apos;,
        &apos;down&apos;: &apos;↓&apos;,
        &apos;left&apos;: &apos;←&apos;,
        &apos;right&apos;: &apos;→&apos;,
        None: &apos;■&apos;
    }

    print(&quot;策略可视化：&quot;)
    for row in policy:
        print(&quot; &quot;.join([direction_symbols[a] for a in row]))

env = GridWorld()
print(&quot;\n=== 价值迭代 ===&quot;)
V_vi, policy_vi = value_iteration(env)
print(&quot;价值函数:\n&quot;, np.round(V_vi, 2))
print_policy(policy_vi)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;2.5 Questions&lt;/h1&gt;
&lt;h2&gt;2.5.1 在价值迭代中，为什么不需要显式地维护一个策略？&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;原始BOE的形式为
$$
V(s) = \max_\pi \sum_a\pi(a|s) [\sum_r p(r|s, a) r + \gamma \sum_{s&apos;}p(s&apos; | s, a)V(s&apos;)], \ \forall s
$$
由于采用状态价值迭代时贪心策略一定可以得到最优状态价值，($V_{t+1}(s) = \max_\pi \pi(a|s) q_t(s, a) = \max_aq_t(s, a)$, 这里的$q_t$不依赖于$\pi$, 因此可以给$q_t(s,a)$最大的对应动作$a&apos;$赋概率$\pi(a&apos;|s)=1$)因此，
价值迭代的BOE是
$$
V_{t+1}(s) = \max_a \sum_{s&apos;, r}p(s&apos;, r|s, a)[r + \gamma V_{t}(s&apos;)], \ \forall s
$$
这里对动作$a$遍历，把$\sum_a \pi(a|s)$直接变成$\max_a$是因为这里采用了贪心策略。由此可见，公式中的计算并没有显式的要求计算出每一步的最优策略$\pi_t$（实际上也不需要因为这里采用的就是贪心策略，已知），因此可以不需要显式地维护一个策略。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;由于采用了贪心策略，因此从每步的状态价值函数中提取出最优动作，可以由下式得出
$$
a^*&lt;em&gt;t(s) = \arg\max_a q_t(s,a) = \arg \max_a [\sum&lt;/em&gt;{s&apos;,r} p(s&apos;, r|s, a)[r + \gamma V_t(s&apos;)]]
$$&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;2.5.2 策略迭代和价值迭代的主要区别是什么？在什么情况下应选择使用策略迭代而不是价值迭代？&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;策略迭代分为策略评估和策略改进两个部分交替进行，收敛速度相对较慢；价值迭代则只需要不断更新状态价值函数即可，收敛速度相对较快。此外，由于策略迭代中每一步得到的状态价值函数都是基于给定策略求得的，因此是真实的有意义的状态价值函数；而价值迭代过程中的状态价值不一定满足Bellman方程（尚未收敛），因此其对应的价值函数未必具有对应于某个策略的意义。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当状态空间较小时（计算成本低），或者需要准确的中间迭代过程的策略和状态函数时，选用策略迭代更合适。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>数据结构</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/datastructure/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/datastructure/</guid><pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这篇笔记整理一些常见且“好用的特殊数据结构”，并配上&lt;strong&gt;原理讲解 + 可读性优先的 Python 实现&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;说明：二叉树与图在其它笔记里已单独展开，这里不再重复（但会提到“堆”这种常用于优先队列的结构，重点放在数组实现与接口）。&lt;/p&gt;
&lt;h2&gt;目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;HashSet（链式哈希）&lt;/li&gt;
&lt;li&gt;HashMap（链式哈希）&lt;/li&gt;
&lt;li&gt;栈 / 队列 / 双端队列（环形缓冲区）&lt;/li&gt;
&lt;li&gt;堆（优先队列，数组实现）&lt;/li&gt;
&lt;li&gt;并查集（Disjoint Set Union）&lt;/li&gt;
&lt;li&gt;Trie（前缀树）&lt;/li&gt;
&lt;li&gt;Bloom Filter（布隆过滤器）&lt;/li&gt;
&lt;li&gt;LRU Cache（哈希表 + 双向链表）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;1. HashSet：哈希表实现的集合（链式拉链法）&lt;/h1&gt;
&lt;h2&gt;1.1 原理与复杂度&lt;/h2&gt;
&lt;p&gt;哈希表的核心是把元素映射到桶（bucket）下标：&lt;/p&gt;
&lt;p&gt;$$
ext{index} = h(x)\bmod M
$$&lt;/p&gt;
&lt;p&gt;其中 $M$ 是桶数组大小。理想情况下，每个桶里元素数量很少，因此查找/插入/删除平均时间复杂度为 $O(1)$。&lt;/p&gt;
&lt;p&gt;现实中会发生哈希冲突（不同元素映射到同一个桶）。常见解决冲突方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;链式拉链（separate chaining）&lt;/strong&gt;：每个桶挂一条链表/动态数组。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开放寻址（open addressing）&lt;/strong&gt;：冲突时在桶数组里继续探测（线性探测、二次探测、双重哈希）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文用链式拉链法：实现简单、删除方便。&lt;/p&gt;
&lt;h2&gt;1.2 结构示意&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;+---------------------+
| 哈希表（桶数组）     |
+---------------------+
| 索引0 | → [A] → null
+-------+
| 索引1 | → null
+-------+
| 索引2 | → [B] → [C] → [D] → null
+-------+
| 索引3 | → null
+-------+
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1.3 代码实现（HashSet）&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class _HashSetNode:
    def __init__(self, value, next_node=None):
        self.value = value
        self.next = next_node


class HashSet:
    &quot;&quot;&quot;链式哈希实现的 Set：平均 O(1)，最坏 O(n)。&quot;&quot;&quot;

    def __init__(self, items=None, initial_capacity=8, load_factor=0.75):
        self._capacity = max(2, int(initial_capacity))
        self._buckets = [None] * self._capacity
        self._size = 0
        self._load_factor = float(load_factor)

        if items is not None:
            for item in items:
                self.add(item)

    def _index(self, item):
        # Python 的 hash 可能为负；这里转成非负更直观
        return (hash(item) &amp;amp; 0x7FFFFFFF) % self._capacity

    def _resize(self, new_capacity):
        old_buckets = self._buckets
        self._capacity = int(new_capacity)
        self._buckets = [None] * self._capacity
        self._size = 0

        for head in old_buckets:
            node = head
            while node:
                self.add(node.value)
                node = node.next

    def add(self, item):
        if self._size + 1 &amp;gt; self._capacity * self._load_factor:
            self._resize(self._capacity * 2)

        idx = self._index(item)
        node = self._buckets[idx]
        while node:
            if node.value == item:
                return
            node = node.next

        self._buckets[idx] = _HashSetNode(item, self._buckets[idx])
        self._size += 1

    def remove(self, item):
        idx = self._index(item)
        prev = None
        curr = self._buckets[idx]
        while curr:
            if curr.value == item:
                if prev is None:
                    self._buckets[idx] = curr.next
                else:
                    prev.next = curr.next
                self._size -= 1
                return
            prev, curr = curr, curr.next
        raise KeyError(f&quot;{item} not in set&quot;)

    def discard(self, item):
        try:
            self.remove(item)
        except KeyError:
            return

    def __contains__(self, item):
        idx = self._index(item)
        node = self._buckets[idx]
        while node:
            if node.value == item:
                return True
            node = node.next
        return False

    def __len__(self):
        return self._size

    def __iter__(self):
        for head in self._buckets:
            node = head
            while node:
                yield node.value
                node = node.next

    def __repr__(self):
        return f&quot;HashSet({list(self)!r})&quot;

    def union(self, other):
        result = HashSet(self)
        for x in other:
            result.add(x)
        return result

    def intersection(self, other):
        result = HashSet()
        if len(self) &amp;lt;= len(other):
            for x in self:
                if x in other:
                    result.add(x)
        else:
            for x in other:
                if x in self:
                    result.add(x)
        return result

    def difference(self, other):
        result = HashSet()
        for x in self:
            if x not in other:
                result.add(x)
        return result
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;2. HashMap：哈希表实现的字典（链式拉链法）&lt;/h1&gt;
&lt;p&gt;Set 只存 value；Map/Dict 则存 key→value 映射。实现上只要节点里存 &lt;code&gt;(key, value)&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class _HashMapNode:
    def __init__(self, key, value, next_node=None):
        self.key = key
        self.value = value
        self.next = next_node


class HashMap:
    &quot;&quot;&quot;链式哈希实现的 Map。接口风格接近 dict。&quot;&quot;&quot;

    def __init__(self, items=None, initial_capacity=8, load_factor=0.75):
        self._capacity = max(2, int(initial_capacity))
        self._buckets = [None] * self._capacity
        self._size = 0
        self._load_factor = float(load_factor)

        if items is not None:
            for k, v in items:
                self[k] = v

    def _index(self, key):
        return (hash(key) &amp;amp; 0x7FFFFFFF) % self._capacity

    def _resize(self, new_capacity):
        old = self._buckets
        self._capacity = int(new_capacity)
        self._buckets = [None] * self._capacity
        self._size = 0

        for head in old:
            node = head
            while node:
                self[node.key] = node.value
                node = node.next

    def __setitem__(self, key, value):
        if self._size + 1 &amp;gt; self._capacity * self._load_factor:
            self._resize(self._capacity * 2)

        idx = self._index(key)
        node = self._buckets[idx]
        while node:
            if node.key == key:
                node.value = value
                return
            node = node.next

        self._buckets[idx] = _HashMapNode(key, value, self._buckets[idx])
        self._size += 1

    def __getitem__(self, key):
        idx = self._index(key)
        node = self._buckets[idx]
        while node:
            if node.key == key:
                return node.value
            node = node.next
        raise KeyError(key)

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default

    def __contains__(self, key):
        idx = self._index(key)
        node = self._buckets[idx]
        while node:
            if node.key == key:
                return True
            node = node.next
        return False

    def __delitem__(self, key):
        idx = self._index(key)
        prev = None
        curr = self._buckets[idx]
        while curr:
            if curr.key == key:
                if prev is None:
                    self._buckets[idx] = curr.next
                else:
                    prev.next = curr.next
                self._size -= 1
                return
            prev, curr = curr, curr.next
        raise KeyError(key)

    def __len__(self):
        return self._size

    def keys(self):
        for head in self._buckets:
            node = head
            while node:
                yield node.key
                node = node.next

    def values(self):
        for head in self._buckets:
            node = head
            while node:
                yield node.value
                node = node.next

    def items(self):
        for head in self._buckets:
            node = head
            while node:
                yield node.key, node.value
                node = node.next

    def __iter__(self):
        return self.keys()

    def __repr__(self):
        return f&quot;HashMap({dict(self.items())!r})&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;3. 栈 / 队列 / 双端队列：环形缓冲区（Ring Buffer）&lt;/h1&gt;
&lt;h2&gt;3.1 为什么要环形缓冲区&lt;/h2&gt;
&lt;p&gt;Python 的 &lt;code&gt;list.append/pop()&lt;/code&gt; 在尾部是均摊 $O(1)$，但如果你用 &lt;code&gt;pop(0)&lt;/code&gt; 去实现队列，会退化为 $O(n)$（整体搬移）。&lt;/p&gt;
&lt;p&gt;环形缓冲区用一个数组 + 两个指针在“圆环”上移动：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;入队：写到 &lt;code&gt;tail&lt;/code&gt;，&lt;code&gt;tail = (tail + 1) % cap&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;出队：读 &lt;code&gt;head&lt;/code&gt;，&lt;code&gt;head = (head + 1) % cap&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样队列/双端队列可以稳定 $O(1)$。&lt;/p&gt;
&lt;h2&gt;3.2 Deque（双端队列）实现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Deque:
    def __init__(self, initial_capacity=8):
        self._cap = max(2, int(initial_capacity))
        self._data = [None] * self._cap
        self._head = 0
        self._tail = 0
        self._size = 0

    def __len__(self):
        return self._size

    def _grow(self):
        new_cap = self._cap * 2
        new_data = [None] * new_cap
        for i in range(self._size):
            new_data[i] = self._data[(self._head + i) % self._cap]
        self._data = new_data
        self._cap = new_cap
        self._head = 0
        self._tail = self._size

    def append(self, x):
        if self._size == self._cap:
            self._grow()
        self._data[self._tail] = x
        self._tail = (self._tail + 1) % self._cap
        self._size += 1

    def appendleft(self, x):
        if self._size == self._cap:
            self._grow()
        self._head = (self._head - 1) % self._cap
        self._data[self._head] = x
        self._size += 1

    def pop(self):
        if self._size == 0:
            raise IndexError(&quot;pop from empty deque&quot;)
        self._tail = (self._tail - 1) % self._cap
        x = self._data[self._tail]
        self._data[self._tail] = None
        self._size -= 1
        return x

    def popleft(self):
        if self._size == 0:
            raise IndexError(&quot;pop from empty deque&quot;)
        x = self._data[self._head]
        self._data[self._head] = None
        self._head = (self._head + 1) % self._cap
        self._size -= 1
        return x

    def peekleft(self):
        if self._size == 0:
            raise IndexError(&quot;peek from empty deque&quot;)
        return self._data[self._head]

    def peek(self):
        if self._size == 0:
            raise IndexError(&quot;peek from empty deque&quot;)
        return self._data[(self._tail - 1) % self._cap]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;基于 &lt;code&gt;Deque&lt;/code&gt; 可以轻松得到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;栈：只用 &lt;code&gt;append/pop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;队列：只用 &lt;code&gt;append/popleft&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;4. 堆（优先队列）：数组实现&lt;/h1&gt;
&lt;p&gt;优先队列支持：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;push(x)&lt;/code&gt;：加入元素&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pop()&lt;/code&gt;：弹出最小（或最大）元素&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常用实现是二叉堆（用数组存储，父子关系由下标计算，不需要显式树节点）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;parent = (i - 1) // 2&lt;/li&gt;
&lt;li&gt;left = 2&lt;em&gt;i + 1, right = 2&lt;/em&gt;i + 2&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class MinHeap:
    def __init__(self, items=None):
        self._a = []
        if items is not None:
            for x in items:
                self.push(x)

    def __len__(self):
        return len(self._a)

    def peek(self):
        if not self._a:
            raise IndexError(&quot;peek from empty heap&quot;)
        return self._a[0]

    def push(self, x):
        a = self._a
        a.append(x)
        i = len(a) - 1
        while i &amp;gt; 0:
            p = (i - 1) // 2
            if a[p] &amp;lt;= a[i]:
                break
            a[p], a[i] = a[i], a[p]
            i = p

    def pop(self):
        a = self._a
        if not a:
            raise IndexError(&quot;pop from empty heap&quot;)
        if len(a) == 1:
            return a.pop()
        top = a[0]
        a[0] = a.pop()
        self._sift_down(0)
        return top

    def _sift_down(self, i):
        a = self._a
        n = len(a)
        while True:
            l = 2 * i + 1
            r = l + 1
            smallest = i
            if l &amp;lt; n and a[l] &amp;lt; a[smallest]:
                smallest = l
            if r &amp;lt; n and a[r] &amp;lt; a[smallest]:
                smallest = r
            if smallest == i:
                break
            a[i], a[smallest] = a[smallest], a[i]
            i = smallest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;复杂度：&lt;code&gt;push/pop&lt;/code&gt; 均为 $O(\log n)$，&lt;code&gt;peek&lt;/code&gt; 为 $O(1)$。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;5. 并查集（Disjoint Set Union / Union-Find）&lt;/h1&gt;
&lt;p&gt;并查集用来维护“若干不相交集合”的动态合并与查询：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;find(x)&lt;/code&gt;：找代表元（根）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;union(x,y)&lt;/code&gt;：合并两个集合&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关键优化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;路径压缩&lt;/strong&gt;：find 时把路径上的点直接挂到根上&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;按秩/按大小合并&lt;/strong&gt;：小树挂大树&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class DisjointSetUnion:
    def __init__(self, n):
        self.parent = list(range(n))
        self.size = [1] * n

    def find(self, x):
        while self.parent[x] != x:
            self.parent[x] = self.parent[self.parent[x]]
            x = self.parent[x]
        return x

    def union(self, a, b):
        ra, rb = self.find(a), self.find(b)
        if ra == rb:
            return False
        if self.size[ra] &amp;lt; self.size[rb]:
            ra, rb = rb, ra
        self.parent[rb] = ra
        self.size[ra] += self.size[rb]
        return True

    def same(self, a, b):
        return self.find(a) == self.find(b)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;均摊复杂度接近 $O(1)$（更精确为 $\alpha(n)$）。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;6. Trie（前缀树）：字符串前缀查询&lt;/h1&gt;
&lt;p&gt;Trie 适合做：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;精确查词 &lt;code&gt;search(word)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;前缀查询 &lt;code&gt;starts_with(prefix)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它把字符串按字符逐层展开，查询时间与字符串长度 $L$ 成正比：$O(L)$。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False


class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        node = self.root
        for ch in word:
            node = node.children.setdefault(ch, TrieNode())
        node.is_end = True

    def search(self, word):
        node = self.root
        for ch in word:
            if ch not in node.children:
                return False
            node = node.children[ch]
        return node.is_end

    def starts_with(self, prefix):
        node = self.root
        for ch in prefix:
            if ch not in node.children:
                return False
            node = node.children[ch]
        return True
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;7. Bloom Filter：快速判“可能存在/一定不存在”&lt;/h1&gt;
&lt;p&gt;布隆过滤器用一个 bitset + $k$ 个哈希函数表示集合。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查询：如果任意一位为 0，则&lt;strong&gt;一定不存在&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;如果全部为 1，则&lt;strong&gt;可能存在&lt;/strong&gt;（有假阳性，false positive）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见工程做法是用 double hashing 从两个 hash 派生出 $k$ 个：&lt;/p&gt;
&lt;p&gt;$$
h_i(x)=h_1(x)+i\cdot h_2(x)
$$&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class BloomFilter:
    def __init__(self, m_bits, k_hashes=4):
        self.m = int(m_bits)
        self.k = int(k_hashes)
        self.bits = bytearray((self.m + 7) // 8)

    def _hashes(self, item):
        h1 = hash((item, 0)) &amp;amp; 0x7FFFFFFF
        h2 = hash((item, 1)) &amp;amp; 0x7FFFFFFF
        for i in range(self.k):
            yield (h1 + i * h2) % self.m

    def add(self, item):
        for h in self._hashes(item):
            self.bits[h &amp;gt;&amp;gt; 3] |= 1 &amp;lt;&amp;lt; (h &amp;amp; 7)

    def __contains__(self, item):
        for h in self._hashes(item):
            if (self.bits[h &amp;gt;&amp;gt; 3] &amp;gt;&amp;gt; (h &amp;amp; 7)) &amp;amp; 1 == 0:
                return False
        return True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;应用场景：大规模去重、缓存穿透防护、快速过滤（先用 Bloom 过滤，再去数据库/HashSet 做精确查询）。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;8. LRU Cache：哈希表 + 双向链表&lt;/h1&gt;
&lt;p&gt;LRU（Least Recently Used）缓存要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;get(key)&lt;/code&gt;：如果命中，把该项变“最近使用”&lt;/li&gt;
&lt;li&gt;&lt;code&gt;put(key,value)&lt;/code&gt;：如果超容量，淘汰“最久未使用”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要做到 $O(1)$：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用哈希表：key → 节点（快速定位）&lt;/li&gt;
&lt;li&gt;用双向链表：维护使用顺序（快速移动与淘汰）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class _LRUNode:
    def __init__(self, key=None, value=None):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None


class LRUCache:
    def __init__(self, capacity):
        self.capacity = int(capacity)
        self.map = {}

        # 哨兵节点：head &amp;lt;-&amp;gt; ... &amp;lt;-&amp;gt; tail
        self.head = _LRUNode()
        self.tail = _LRUNode()
        self.head.next = self.tail
        self.tail.prev = self.head

    def _remove(self, node):
        p, n = node.prev, node.next
        p.next = n
        n.prev = p

    def _add_to_front(self, node):
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node

    def get(self, key, default=None):
        if key not in self.map:
            return default
        node = self.map[key]
        self._remove(node)
        self._add_to_front(node)
        return node.value

    def put(self, key, value):
        if key in self.map:
            node = self.map[key]
            node.value = value
            self._remove(node)
            self._add_to_front(node)
            return

        node = _LRUNode(key, value)
        self.map[key] = node
        self._add_to_front(node)

        if len(self.map) &amp;gt; self.capacity:
            # 淘汰 tail.prev
            lru = self.tail.prev
            self._remove(lru)
            del self.map[lru.key]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;小结：如何选数据结构&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;需要 &lt;strong&gt;快速查存在性/去重&lt;/strong&gt;：HashSet&lt;/li&gt;
&lt;li&gt;需要 &lt;strong&gt;键值映射&lt;/strong&gt;：HashMap&lt;/li&gt;
&lt;li&gt;需要 &lt;strong&gt;FIFO / 双端操作&lt;/strong&gt;：环形 Deque&lt;/li&gt;
&lt;li&gt;需要 &lt;strong&gt;随时取最小/最大&lt;/strong&gt;：堆（优先队列）&lt;/li&gt;
&lt;li&gt;需要 &lt;strong&gt;动态连通性/集合合并&lt;/strong&gt;：并查集&lt;/li&gt;
&lt;li&gt;需要 &lt;strong&gt;前缀检索/词典&lt;/strong&gt;：Trie&lt;/li&gt;
&lt;li&gt;需要 &lt;strong&gt;快速过滤、允许假阳性&lt;/strong&gt;：Bloom Filter&lt;/li&gt;
&lt;li&gt;需要 &lt;strong&gt;缓存淘汰策略&lt;/strong&gt;：LRU Cache&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>动态规划</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/dynamicprogramming/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/dynamicprogramming/</guid><pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;动态规划&lt;/h2&gt;
&lt;p&gt;动态规划其实就是动态的调整策略，来达到最优解。它的核心思想是&lt;strong&gt;将复杂问题分解成简单子问题&lt;/strong&gt;，通过&lt;strong&gt;保存子问题的解&lt;/strong&gt;来避免重复计算。动态规划&lt;strong&gt;通常适用于具有重叠子问题和最优子结构性质&lt;/strong&gt;的问题。&lt;/p&gt;
&lt;p&gt;动态规划问题可以大致分为以下几类：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;线性DP&lt;/strong&gt;：问题状态是一维的，通常依赖于前一个或前几个状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;背包问题&lt;/strong&gt;：在一系列物品中进行选择，以在满足某些约束（如总重量或体积）的同时最大化（或最小化）总价值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;区间DP&lt;/strong&gt;：状态由一个区间 &lt;code&gt;[i, j]&lt;/code&gt; 定义，通常通过合并子区间的最优解来求解。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;序列DP&lt;/strong&gt;：在序列上进行操作，如最长子序列、子数组等问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态压缩DP&lt;/strong&gt;：当状态的维度过多但每一维的取值范围很小时，可以用一个整数的二进制位来表示状态。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下面将通过一系列经典例题来具体阐述。&lt;/p&gt;
&lt;h2&gt;基础线性DP&lt;/h2&gt;
&lt;p&gt;线性DP是最基础的动态规划类型，其状态通常定义在一个一维数组上，&lt;code&gt;dp[i]&lt;/code&gt; 的值依赖于 &lt;code&gt;dp[i-1]&lt;/code&gt;, &lt;code&gt;dp[i-2]&lt;/code&gt; 等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 70. 爬楼梯&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 假设 &lt;code&gt;dp[i]&lt;/code&gt; 是爬到第 &lt;code&gt;i&lt;/code&gt; 级台阶的方法数。要到达第 &lt;code&gt;i&lt;/code&gt; 级，可以从第 &lt;code&gt;i-1&lt;/code&gt; 级爬一步上来，也可以从第 &lt;code&gt;i-2&lt;/code&gt; 级爬两步上来。因此，状态转移方程为 &lt;code&gt;dp[i] = dp[i-1] + dp[i-2]&lt;/code&gt;，这本质上是一个斐波那契数列。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    int climbStairs(int n) {
        if (n &amp;lt;= 2) return n;
        int prev2 = 1, prev1 = 2, current;
        for (int i = 3; i &amp;lt;= n; ++i) {
            current = prev1 + prev2;
            prev2 = prev1;
            prev1 = current;
        }
        return current;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 198. 打家劫舍&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 不能抢劫相邻的房子。定义 &lt;code&gt;dp[i]&lt;/code&gt; 为抢劫到第 &lt;code&gt;i&lt;/code&gt; 家房子时能获得的最大金额。对于第 &lt;code&gt;i&lt;/code&gt; 家，我们有两个选择：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;不抢第 &lt;code&gt;i&lt;/code&gt; 家：最大金额等于抢到第 &lt;code&gt;i-1&lt;/code&gt; 家的最大金额，即 &lt;code&gt;dp[i-1]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;抢第 &lt;code&gt;i&lt;/code&gt; 家：那么就不能抢第 &lt;code&gt;i-1&lt;/code&gt; 家，最大金额为 &lt;code&gt;nums[i] + dp[i-2]&lt;/code&gt;。
状态转移方程为 &lt;code&gt;dp[i] = max(dp[i-1], nums[i] + dp[i-2])&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int rob(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        if (nums.empty()) return 0;
        int n = nums.size();
        if (n == 1) return nums[0];
        
        int prev2 = 0, prev1 = 0;
        for (int i = 0; i &amp;lt; n; ++i) {
            int current = std::max(prev1, prev2 + nums[i]);
            prev2 = prev1;
            prev1 = current;
        }
        return prev1;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;背包问题&lt;/h2&gt;
&lt;p&gt;背包问题是组合优化的经典问题，核心是在给定容量的背包和一组物品中，如何选择物品以达到价值最大化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 322. 零钱兑换 (完全背包)&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 给定不同面额的硬币和总金额，计算凑成总金额所需的最少硬币数量。这是典型的“完全背包”问题，每种硬币可以无限次使用。
定义 &lt;code&gt;dp[i]&lt;/code&gt; 为凑成金额 &lt;code&gt;i&lt;/code&gt; 所需的最少硬币数。对于金额 &lt;code&gt;i&lt;/code&gt;，它可以由 &lt;code&gt;dp[i - coin]&lt;/code&gt; 加上一枚 &lt;code&gt;coin&lt;/code&gt; 硬币得到。
状态转移方程为 &lt;code&gt;dp[i] = min(dp[i], dp[i - coin] + 1)&lt;/code&gt;，其中 &lt;code&gt;coin&lt;/code&gt; 是可用的硬币面额。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int coinChange(std::vector&amp;lt;int&amp;gt;&amp;amp; coins, int amount) {
        std::vector&amp;lt;int&amp;gt; dp(amount + 1, amount + 1);
        dp[0] = 0;
        for (int i = 1; i &amp;lt;= amount; ++i) {
            for (int coin : coins) {
                if (i &amp;gt;= coin) {
                    dp[i] = std::min(dp[i], dp[i - coin] + 1);
                }
            }
        }
        return dp[amount] &amp;gt; amount ? -1 : dp[amount];
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 416. 分割等和子集 (0/1背包)&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 判断一个数组是否可以被分割成两个和相等的子集。这个问题可以转化为：能否从数组中找到一个子集，其和等于数组总和的一半。这是一个“0/1背包”问题。
背包容量为 &lt;code&gt;target = sum / 2&lt;/code&gt;，物品为数组中的每个数字。定义 &lt;code&gt;dp[j]&lt;/code&gt; 为是否能凑成和为 &lt;code&gt;j&lt;/code&gt;。
状态转移方程为 &lt;code&gt;dp[j] = dp[j] || dp[j - num]&lt;/code&gt;。为避免重复使用同一个元素，内层循环需要从后向前遍历。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;numeric&amp;gt;

class Solution {
public:
    bool canPartition(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        int sum = std::accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2 != 0) return false;
        int target = sum / 2;
        
        std::vector&amp;lt;bool&amp;gt; dp(target + 1, false);
        dp[0] = true;
        
        for (int num : nums) {
            for (int j = target; j &amp;gt;= num; --j) {
                dp[j] = dp[j] || dp[j - num];
            }
        }
        return dp[target];
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;序列DP&lt;/h2&gt;
&lt;p&gt;序列DP处理与序列或字符串相关的问题，如最长子序列、子数组、编辑距离等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 300. 最长递增子序列&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 寻找一个序列中最长的递增子序列的长度。定义 &lt;code&gt;dp[i]&lt;/code&gt; 为以 &lt;code&gt;nums[i]&lt;/code&gt; 结尾的最长递增子序列的长度。为了计算 &lt;code&gt;dp[i]&lt;/code&gt;，我们需要遍历 &lt;code&gt;j&lt;/code&gt; 从 &lt;code&gt;0&lt;/code&gt; 到 &lt;code&gt;i-1&lt;/code&gt;，如果 &lt;code&gt;nums[i] &amp;gt; nums[j]&lt;/code&gt;，则 &lt;code&gt;dp[i]&lt;/code&gt; 可以从 &lt;code&gt;dp[j]&lt;/code&gt; 转移而来，即 &lt;code&gt;dp[i] = max(dp[i], dp[j] + 1)&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int lengthOfLIS(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        if (nums.empty()) return 0;
        std::vector&amp;lt;int&amp;gt; dp(nums.size(), 1);
        int maxLen = 1;
        for (size_t i = 1; i &amp;lt; nums.size(); ++i) {
            for (size_t j = 0; j &amp;lt; i; ++j) {
                if (nums[i] &amp;gt; nums[j]) {
                    dp[i] = std::max(dp[i], dp[j] + 1);
                }
            }
            maxLen = std::max(maxLen, dp[i]);
        }
        return maxLen;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 139. 单词拆分&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 判断一个字符串是否可以被字典中的单词拼接而成。定义 &lt;code&gt;dp[i]&lt;/code&gt; 为字符串 &lt;code&gt;s&lt;/code&gt; 的前 &lt;code&gt;i&lt;/code&gt; 个字符（&lt;code&gt;s[0...i-1]&lt;/code&gt;）是否可以被拆分。&lt;code&gt;dp[i]&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt; 的条件是：存在一个 &lt;code&gt;j &amp;lt; i&lt;/code&gt;，使得 &lt;code&gt;dp[j]&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt;，并且子串 &lt;code&gt;s[j...i-1]&lt;/code&gt; 在字典中。
状态转移方程为 &lt;code&gt;dp[i] = dp[j] &amp;amp;&amp;amp; check(s.substr(j, i-j))&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;unordered_set&amp;gt;

class Solution {
public:
    bool wordBreak(std::string s, std::vector&amp;lt;std::string&amp;gt;&amp;amp; wordDict) {
        std::unordered_set&amp;lt;std::string&amp;gt; dict(wordDict.begin(), wordDict.end());
        int n = s.length();
        std::vector&amp;lt;bool&amp;gt; dp(n + 1, false);
        dp[0] = true;

        for (int i = 1; i &amp;lt;= n; ++i) {
            for (int j = 0; j &amp;lt; i; ++j) {
                if (dp[j] &amp;amp;&amp;amp; dict.count(s.substr(j, i - j))) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[n];
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 152. 乘积最大子数组&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 由于存在负数，最大乘积可能由两个负数相乘得到。因此，在每个位置 &lt;code&gt;i&lt;/code&gt;，我们需要同时记录以 &lt;code&gt;nums[i]&lt;/code&gt; 结尾的“最大乘积”和“最小乘积”（可能为负）。
&lt;code&gt;max_dp[i] = max(nums[i], max(max_dp[i-1] * nums[i], min_dp[i-1] * nums[i]))&lt;/code&gt;
&lt;code&gt;min_dp[i] = min(nums[i], min(max_dp[i-1] * nums[i], min_dp[i-1] * nums[i]))&lt;/code&gt;
最终结果是所有 &lt;code&gt;max_dp[i]&lt;/code&gt; 中的最大值。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int maxProduct(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        if (nums.empty()) return 0;
        int maxProd = nums[0];
        int currentMax = nums[0];
        int currentMin = nums[0];

        for (size_t i = 1; i &amp;lt; nums.size(); ++i) {
            int tempMax = currentMax;
            currentMax = std::max({nums[i], currentMax * nums[i], currentMin * nums[i]});
            currentMin = std::min({nums[i], tempMax * nums[i], currentMin * nums[i]});
            maxProd = std::max(maxProd, currentMax);
        }
        return maxProd;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 32. 最长有效括号&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 寻找最长的有效（格式正确）括号子串的长度。定义 &lt;code&gt;dp[i]&lt;/code&gt; 为以 &lt;code&gt;s[i-1]&lt;/code&gt; 结尾的最长有效括号子串的长度。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;s[i-1]&lt;/code&gt; 是 &lt;code&gt;(&lt;/code&gt;，&lt;code&gt;dp[i]&lt;/code&gt; 必为 &lt;code&gt;0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;s[i-1]&lt;/code&gt; 是 &lt;code&gt;)&lt;/code&gt;，我们需要找到与之匹配的 &lt;code&gt;(&lt;/code&gt;。
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;s[i-2]&lt;/code&gt; 是 &lt;code&gt;(&lt;/code&gt;，则 &lt;code&gt;dp[i] = dp[i-2] + 2&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;s[i-2]&lt;/code&gt; 是 &lt;code&gt;)&lt;/code&gt;，并且在 &lt;code&gt;s[i - dp[i-1] - 2]&lt;/code&gt; 的位置是 &lt;code&gt;(&lt;/code&gt;，则 &lt;code&gt;dp[i] = dp[i-1] + 2 + dp[i - dp[i-1] - 2]&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int longestValidParentheses(std::string s) {
        int n = s.length();
        if (n &amp;lt; 2) return 0;
        std::vector&amp;lt;int&amp;gt; dp(n, 0);
        int maxLen = 0;

        for (int i = 1; i &amp;lt; n; ++i) {
            if (s[i] == &apos;)&apos;) {
                if (s[i - 1] == &apos;(&apos;) {
                    dp[i] = (i &amp;gt;= 2 ? dp[i - 2] : 0) + 2;
                } else if (i - dp[i - 1] &amp;gt; 0 &amp;amp;&amp;amp; s[i - dp[i - 1] - 1] == &apos;(&apos;) {
                    dp[i] = dp[i - 1] + 2 + (i - dp[i - 1] &amp;gt;= 2 ? dp[i - dp[i - 1] - 2] : 0);
                }
                maxLen = std::max(maxLen, dp[i]);
            }
        }
        return maxLen;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1027. 最长等差数列&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 寻找数组中最长的等差数列的长度。由于需要公差信息，一维DP不足以表示状态。定义 &lt;code&gt;dp[i][diff]&lt;/code&gt; 为以 &lt;code&gt;nums[i]&lt;/code&gt; 结尾、公差为 &lt;code&gt;diff&lt;/code&gt; 的等差数列的长度。
状态转移：遍历 &lt;code&gt;j&lt;/code&gt; 从 &lt;code&gt;0&lt;/code&gt; 到 &lt;code&gt;i-1&lt;/code&gt;，计算公差 &lt;code&gt;diff = nums[i] - nums[j]&lt;/code&gt;，则 &lt;code&gt;dp[i][diff] = dp[j][diff] + 1&lt;/code&gt;。
由于 &lt;code&gt;diff&lt;/code&gt; 可能为负，我们可以给它一个偏移量来作为数组索引。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;unordered_map&amp;gt;

class Solution {
public:
    int longestArithSeqLength(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        int n = nums.size();
        if (n &amp;lt;= 2) return n;
        
        std::vector&amp;lt;std::unordered_map&amp;lt;int, int&amp;gt;&amp;gt; dp(n);
        int maxLength = 0;
        
        for (int i = 1; i &amp;lt; n; ++i) {
            for (int j = 0; j &amp;lt; i; ++j) {
                int diff = nums[i] - nums[j];
                int length = 2;
                if (dp[j].count(diff)) {
                    length = dp[j][diff] + 1;
                }
                dp[i][diff] = length;
                maxLength = std::max(maxLength, length);
            }
        }
        return maxLength;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 72. 编辑距离&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 计算将一个单词转换成另一个单词所需的最少操作（插入、删除、替换）次数。定义 &lt;code&gt;dp[i][j]&lt;/code&gt; 为 &lt;code&gt;word1&lt;/code&gt; 的前 &lt;code&gt;i&lt;/code&gt; 个字符转换成 &lt;code&gt;word2&lt;/code&gt; 的前 &lt;code&gt;j&lt;/code&gt; 个字符所需的最小操作数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;word1[i-1] == word2[j-1]&lt;/code&gt;，则 &lt;code&gt;dp[i][j] = dp[i-1][j-1]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;否则，我们可以进行三种操作：
&lt;ul&gt;
&lt;li&gt;替换：&lt;code&gt;dp[i-1][j-1] + 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;删除（&lt;code&gt;word1&lt;/code&gt;）：&lt;code&gt;dp[i-1][j] + 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;插入（到 &lt;code&gt;word1&lt;/code&gt;）：&lt;code&gt;dp[i][j-1] + 1&lt;/code&gt;
取三者中的最小值。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int minDistance(std::string word1, std::string word2) {
        int m = word1.length();
        int n = word2.length();
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; dp(m + 1, std::vector&amp;lt;int&amp;gt;(n + 1));

        for (int i = 0; i &amp;lt;= m; ++i) dp[i][0] = i;
        for (int j = 0; j &amp;lt;= n; ++j) dp[0][j] = j;

        for (int i = 1; i &amp;lt;= m; ++i) {
            for (int j = 1; j &amp;lt;= n; ++j) {
                if (word1[i - 1] == word2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = std::min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
                }
            }
        }
        return dp[m][n];
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;区间DP&lt;/h2&gt;
&lt;p&gt;区间DP的状态通常定义在一个区间 &lt;code&gt;[i, j]&lt;/code&gt; 上，最终目标是求解整个区间的答案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=5&amp;gt;Example Problem Min-Cut &amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有一根长度为 &lt;code&gt;n&lt;/code&gt; 个单位的木棍，棍上从 &lt;code&gt;0&lt;/code&gt; 到 &lt;code&gt;n&lt;/code&gt; 标记了若干位置。例如，长度为 &lt;code&gt;6&lt;/code&gt; 的棍子可以标记如下：
&lt;img src=&quot;https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/statement.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;给你一个整数数组 &lt;code&gt;cuts&lt;/code&gt; ，其中 &lt;code&gt;cuts[i]&lt;/code&gt; 表示你需要将棍子切开的位置。你可以按顺序完成切割，也可以根据需要更改切割的顺序。&lt;/p&gt;
&lt;p&gt;每次切割的成本都是当前要切割的棍子的长度，切棍子的总成本是历次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍（这两根木棍的长度和就是切割前木棍的长度）。请参阅第一个示例以获得更直观的解释。&lt;/p&gt;
&lt;p&gt;返回切棍子的 最小总成本 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=3&amp;gt;Solution:&amp;lt;/font&amp;gt;&amp;lt;br&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;切割成本特点：每次切割的成本等于当前要切割木棍的长度&lt;/li&gt;
&lt;li&gt;可调整顺序：可以按任意顺序进行切割，目标是最小化总成本&lt;/li&gt;
&lt;li&gt;子问题重叠：不同切割顺序会导致相同区间的重复计算&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;状态定义为 &lt;code&gt;dp[i][j]&lt;/code&gt;， 表示从&lt;strong&gt;切点&lt;/strong&gt; &lt;code&gt;cuts[i]&lt;/code&gt; 到切点 &lt;code&gt;cuts[j]&lt;/code&gt; 之间的最小切割成本。注意，需要添加 &lt;code&gt;0&lt;/code&gt; 和 &lt;code&gt;n&lt;/code&gt; 作为边界，它们不是真正的切点。&lt;/p&gt;
&lt;p&gt;状态转移方程为
$$
dp[i][j] = \min_{k=i+1}^{j-1} (dp[i][k] + dp[k][j]) + (cuts[j] - cuts[i])
$$&lt;/p&gt;
&lt;p&gt;讲白了就是我们要实现从 &lt;code&gt;i&lt;/code&gt; 到 &lt;code&gt;j&lt;/code&gt; 的最小切割成本，首先我们要选择一个切点 &lt;code&gt;k&lt;/code&gt; 在 &lt;code&gt;(i, j)&lt;/code&gt; 之间进行切割，将木棍 &lt;code&gt;(cuts[i], cuts[j])&lt;/code&gt; 分成两段。总成本等于切割这两段的成本之和，再加上当前这次切割的成本 &lt;code&gt;cuts[j] - cuts[i]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;为什么要先排序 ?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;排序是为了确保切点按顺序排列，便于定义和计算区间。
添加 &lt;code&gt;0&lt;/code&gt; 和 &lt;code&gt;n&lt;/code&gt; 是为了表示木棍的两端，方便处理边界情况。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from typing import List

def minCost(n: int, cuts: List[int]) -&amp;gt; int:
    cuts.sort()     # 排序是为了方便计算切割成本
    cuts = [0] + cuts + [n] # 在开头和结尾添加0和n，表示棍子的两端
    m = len(cuts)
    
    # dp[i][j] 表示从 cuts[i] 到 cuts[j] 的最小切割成本
    dp = [[0] * m for _ in range(m)]
    
    # 枚举区间长度
    for length in range(2, m):
        # 枚举区间左端点
        for i in range(m - length):
            j = i + length
            dp[i][j] = float(&apos;inf&apos;)
            # 枚举区间内的切割点 k
            for k in range(i + 1, j):
                cost = dp[i][k] + dp[k][j] + cuts[j] - cuts[i]
                dp[i][j] = min(dp[i][j], cost)
    
    return dp[0][m - 1]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;DP与其他技巧&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 42. 接雨水&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 虽然此题有更优的双指针解法，但也可以用动态规划求解。对于每个位置 &lt;code&gt;i&lt;/code&gt;，它能接的雨水量取决于其左右两侧最高的柱子中的较小者，即 &lt;code&gt;min(left_max[i], right_max[i]) - height[i]&lt;/code&gt;。
我们可以用两个DP数组 &lt;code&gt;left_max&lt;/code&gt; 和 &lt;code&gt;right_max&lt;/code&gt; 分别预处理每个位置左侧和右侧的最高柱子高度。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;left_max[i] = max(left_max[i-1], height[i])&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;right_max[i]&lt;/code&gt; 从右向左计算：&lt;code&gt;right_max[i] = max(right_max[i+1], height[i])&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int trap(std::vector&amp;lt;int&amp;gt;&amp;amp; height) {
        int n = height.size();
        if (n == 0) return 0;
        
        std::vector&amp;lt;int&amp;gt; left_max(n);
        left_max[0] = height[0];
        for (int i = 1; i &amp;lt; n; ++i) {
            left_max[i] = std::max(left_max[i - 1], height[i]);
        }
        
        std::vector&amp;lt;int&amp;gt; right_max(n);
        right_max[n - 1] = height[n - 1];
        for (int i = n - 2; i &amp;gt;= 0; --i) {
            right_max[i] = std::max(right_max[i + 1], height[i]);
        }
        
        int ans = 0;
        for (int i = 0; i &amp;lt; n; ++i) {
            ans += std::min(left_max[i], right_max[i]) - height[i];
        }
        return ans;
    }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>基础算法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/basic/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/basic/</guid><pubDate>Tue, 08 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前缀和、双指针与滑动窗口&lt;/p&gt;
&lt;h2&gt;双指针&lt;/h2&gt;
&lt;p&gt;双指针是一种基础算法，通常用于求解&lt;strong&gt;一维数组类&lt;/strong&gt;问题。一般要求这类问题具有两个性质，&lt;strong&gt;单调有序性&lt;/strong&gt;和&lt;strong&gt;双指针区间扫描&lt;/strong&gt;。这类问题可以用双重循环求解，但时间复杂度为O(n^2)，而双指针的时间复杂度为O(n)。扫描的方向分为&lt;strong&gt;同向扫描&lt;/strong&gt;和&lt;strong&gt;反向扫描&lt;/strong&gt;两种。&lt;/p&gt;
&lt;h3&gt;同向扫描（快慢指针，滑动窗口）&lt;/h3&gt;
&lt;p&gt;同向扫描是指两个指针从同一方向出发，分别以不同的速度向前移动。具体的移动情况视题目要求而定。定义滑动窗口为&lt;code&gt;[i, j]&lt;/code&gt;（左闭右开符合程序员习惯），其中&lt;code&gt;i&lt;/code&gt;和&lt;code&gt;j&lt;/code&gt;分别表示窗口的左边界和右边界。&lt;code&gt;while&lt;/code&gt;循环条件是&lt;code&gt;j &amp;lt; n&lt;/code&gt;，即&lt;code&gt;j&lt;/code&gt;指针不能超过数组的长度。&lt;/p&gt;
&lt;p&gt;一般地，使用&lt;code&gt;滑动窗口&lt;/code&gt;解题是考虑到问题对象为区间（子序列、字串等），且区间具有单调性，即对于某个相关的函数&lt;code&gt;f(i, j)&lt;/code&gt;，&lt;code&gt;i&lt;/code&gt;增大时&lt;code&gt;f(i, j)&lt;/code&gt;的变化情况与&lt;code&gt;j&lt;/code&gt;的变化情况相反，例如，&lt;code&gt;i&lt;/code&gt;增大时，&lt;code&gt;f(i, j)&lt;/code&gt;减小，&lt;code&gt;j&lt;/code&gt;增大时&lt;code&gt;f(i, j)&lt;/code&gt;增大。题目的要求就是找到&lt;code&gt;f(i, j)&lt;/code&gt;符合某些条件的对应的区间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem 统计满足 K 约束的子字符串数量&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给你一个 二进制 字符串 &lt;code&gt;s&lt;/code&gt; 和一个整数 &lt;code&gt;k&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果一个 二进制字符串 &lt;strong&gt;满足以下任一条件&lt;/strong&gt;，则认为该字符串满足 &lt;code&gt;k&lt;/code&gt; 约束：&lt;/p&gt;
&lt;p&gt;字符串中 &lt;code&gt;&apos;0&apos;&lt;/code&gt; 的数量最多为 &lt;code&gt;k&lt;/code&gt;。
字符串中 &lt;code&gt;&apos;1&apos;&lt;/code&gt; 的数量最多为 &lt;code&gt;k&lt;/code&gt;。
返回一个整数，表示 &lt;code&gt;s&lt;/code&gt; 的所有满足 &lt;code&gt;k&lt;/code&gt; 约束 的子字符串的数量&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def countKConstraintSubstrings(s: str, k: int) -&amp;gt; int:
    # as because it&apos;s and, so we could use sliding window
    n = len(s)
    count = [0, 0]
    res = 0
    i = 0
    for j in range(n):  # window = s[i:j+1], len(window) = j + 1 - i
        count[int(s[j])] += 1
        while count[0] &amp;gt; k and count[1] &amp;gt; k:
            count[int(s[i])] -= 1
            i += 1
        res += j - i + 1    
        # 这里暗含了一点，就是如果长度为 j - i + 1的子字符串满足条件的话，
        # 那么减小窗口长度的长度更小的子字符串一定满足条件，从而不必要重复计算，
        # 可以直接加 j - i + 1，表示从j - i + 1到长度缩减为1的子串都满足条件（j - i + 1）个
    return res
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem 数组去重&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给你一个 非严格递增排列 的数组 &lt;code&gt;nums&lt;/code&gt; ，请你 原地 删除重复出现的元素，使每个元素 只出现一次 ，返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 &lt;code&gt;nums&lt;/code&gt; 中唯一元素的个数。&lt;/p&gt;
&lt;p&gt;考虑 &lt;code&gt;nums&lt;/code&gt; 的唯一元素的数量为 &lt;code&gt;k&lt;/code&gt; ，你需要做以下事情确保你的题解可以被通过：&lt;/p&gt;
&lt;p&gt;更改数组 &lt;code&gt;nums&lt;/code&gt; ，使 &lt;code&gt;nums&lt;/code&gt; 的前 &lt;code&gt;k&lt;/code&gt; 个元素包含唯一元素，并按照它们最初在 &lt;code&gt;nums&lt;/code&gt; 中出现的顺序排列。&lt;code&gt;nums&lt;/code&gt; 的其余元素与 &lt;code&gt;nums&lt;/code&gt; 的大小不重要。
返回 &lt;code&gt;k&lt;/code&gt; 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
def removeDuplicates(nums):
    if len(nums) == 0:
        return 0
    i = j = 1  # never from 0, because nums[0] is unique and i always point to next pos to fill
    while j &amp;lt; len(nums):
        if nums[j] != nums[j - 1]: # new land
            nums[i] = nums[j]
            i += 1
        j += 1  # j always ++
    return i
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem 环形链表&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给你一个链表的头节点 &lt;code&gt;head&lt;/code&gt; ，判断链表中是否有环。&lt;/p&gt;
&lt;p&gt;如果链表中有某个节点，可以通过连续跟踪 &lt;code&gt;next&lt;/code&gt; 指针再次到达，则链表中存在环。 为了表示给定链表中的环，评测系统内部使用整数 &lt;code&gt;pos&lt;/code&gt; 来表示链表尾连接到链表中的位置（索引从 0 开始）。注意：&lt;code&gt;pos&lt;/code&gt; 不作为参数进行传递 。仅仅是为了标识链表的实际情况。&lt;/p&gt;
&lt;p&gt;如果链表中存在环 ，则返回 &lt;code&gt;true&lt;/code&gt; 。 否则，返回 &lt;code&gt;false&lt;/code&gt; 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 这是真快慢指针
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

def hasCycle(head: ListNode):
    if not head or not head.next:
        return False
    i, j = head, head.next
    while j and j.next:
        if i == j:
            return True
        i = i.next
        j = j.next.next
    return False
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;反向扫描（左右指针，双元素）&lt;/h2&gt;
&lt;p&gt;反向扫描通常涉及两个元素的操作，而不是区间的操作。分别令左指针&lt;code&gt;i&lt;/code&gt;指向0，右指针&lt;code&gt;j&lt;/code&gt;指向&lt;code&gt;n - 1&lt;/code&gt;，注意这里是&lt;code&gt;n - 1&lt;/code&gt;，而不是&lt;code&gt;n&lt;/code&gt;, 因为这里不需要左闭右开，他不是区间。&lt;code&gt;while&lt;/code&gt;循环条件是&lt;code&gt;i &amp;lt; j&lt;/code&gt; 或 &lt;code&gt;i &amp;lt;= j&lt;/code&gt;。通常需要一维数组具有单调性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem 两数之和 1&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个整数数组 &lt;code&gt;nums&lt;/code&gt; 和一个整数目标值 &lt;code&gt;target&lt;/code&gt;，请你在该数组中找出 和为目标值 &lt;code&gt;target&lt;/code&gt;  的那 两个 整数，&lt;strong&gt;并返回这两个整数&lt;/strong&gt;。你可以假设每种输入只会对应一个答案，并且你不能使用两次相同的元素。你可以按任意顺序返回答案。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def twoSum(nums, target):
    i, j = 0, len(nums) - 1
    nums.sort() # 需要单调性
    while i &amp;lt; j:    # 两数不同
        if nums[i] + nums[j] &amp;gt; target:
            j -= 1
        elif nums[i] + nums[j] &amp;lt; target:
            i += 1
        else:
            return [nums[i], nums[j]]
    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem 两数之和 2&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个整数数组 &lt;code&gt;nums&lt;/code&gt; 和一个整数目标值 &lt;code&gt;target&lt;/code&gt;，请你在该数组中找出 和为目标值 &lt;code&gt;target&lt;/code&gt;  的那 两个 整数，&lt;strong&gt;并返回它们的数组下标&lt;/strong&gt;。你可以假设每种输入只会对应一个答案，并且你不能使用两次相同的元素。你可以按任意顺序返回答案。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from collections import defaultdict

def twoSum(nums, target):
    hash_map = defaultdict(lambda : -1) # 必须用默认字典，需要记住默认字典的调用
    for i in range(len(nums)):
        if hash_map[target - nums[i]] != -1:    # 有对应的元素
            return [hash_map[target - nums[i]], i]
        hash_map[nums[i]] = i
    return None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两题的区别是，第一题是求和为&lt;code&gt;target&lt;/code&gt;的两个数，第二题是求和为&lt;code&gt;target&lt;/code&gt;的两个数的下标。所以第一题既可以用双指针，也可以用哈希表。第二题只能用哈希表。哈希表的时间复杂度是O(n)，空间复杂度是O(n)。反向扫描的时间复杂度是O(n)，空间复杂度是O(1)。&lt;/p&gt;
&lt;h2&gt;前缀和&lt;/h2&gt;
&lt;p&gt;前缀和是一个常用的算法技巧，主要用于快速计算数组中某个区间的和。通过预处理一个前缀和数组，可以在O(1)的时间复杂度内计算任意区间的和。
前缀和数组的定义如下：
$$
\text{prefix}[i] = \sum_{j=0}^{i} \text{arr}[j]
$$
其中，&lt;code&gt;prefix[i]&lt;/code&gt;表示数组&lt;code&gt;arr&lt;/code&gt;中从下标0到i的元素之和。&lt;/p&gt;
&lt;p&gt;通常，前缀和数组的计算时间复杂度为O(n)，查询时间复杂度为O(1)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int* prefix_sum(int* arr, int n) {
    int* prefix = new int[n + 1];
    prefix[0] = 0; // 前缀和数组的第一个元素为0
    for (int i = 1; i &amp;lt;= n; i++) {
        prefix[i] = prefix[i - 1] + arr[i - 1]; // 计算前缀和
    }
    return prefix;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一般地，都会把&lt;code&gt;prefix[0]&lt;/code&gt;设为0，这样可以方便地计算从下标1到i的元素之和。那么此时&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prefix[i]&lt;/code&gt;表示从下标0到i - 1的元素之和，长度为&lt;code&gt;i&lt;/code&gt;，计算的是&lt;code&gt;arr[0]&lt;/code&gt;到&lt;code&gt;arr[i - 1]&lt;/code&gt;的和&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prefix[j] - prefix[i]&lt;/code&gt;表示从下标i到j - 1的元素之和，长度为&lt;code&gt;j - i&lt;/code&gt;，计算的是&lt;code&gt;arr[i]&lt;/code&gt;到&lt;code&gt;arr[j - 1]&lt;/code&gt;的和&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prefix[n] - prefix[i]&lt;/code&gt;表示从下标i到n - 1的元素之和，长度为&lt;code&gt;n - i&lt;/code&gt;，计算的是&lt;code&gt;arr[i]&lt;/code&gt;到&lt;code&gt;arr[n - 1]&lt;/code&gt;的和&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem 长度最小的数组&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个含有 $n$ 个正整数的数组和一个正整数 &lt;code&gt;target&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;找出该数组中满足其总和大于等于 &lt;code&gt;target&lt;/code&gt; 的长度最小的 子数组 &lt;code&gt;[nums_l, nums_l+1, ..., nums_r-1, nums_r]&lt;/code&gt; ，并返回其长度。如果不存在符合条件的子数组，返回 0 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def prefix_sum(nums):
    prefix = [0] * (len(nums) + 1)
    cum_sum = 0
    for i, num in enumerate(nums):
        cum_sum += num
        prefix[i + 1] = (cum_sum)
    return prefix

def min_sub_array_len(target, nums):
    prefix = prefix_sum(nums)
    min_len = len(nums) + 1
    i, j = 0, 1 # 双指针同向遍历
    while j &amp;lt; len(prefix):
        if prefix[j] - prefix[i] &amp;gt;= target:
            min_len = min(min_len, j - i)   # 长度是 j - i
            i += 1
        else:
            j += 1

    return min_len if min_len != len(nums) + 1 else 0
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>图算法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/graph/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/graph/</guid><pubDate>Tue, 08 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;图论是研究图（Graphs）的数学分支，是计算机科学中解决网络、依赖关系和连接性问题的核心。本笔记将围绕图论的经典算法和问题，按照原理 + 例题的模式进行组织。&lt;/p&gt;
&lt;h2&gt;拓扑排序与图遍历&lt;/h2&gt;
&lt;p&gt;拓扑排序用于有向无环图（DAG），它能给出一个所有节点满足其前驱节点都在其之前的线性序列。这在处理依赖关系（如课程安排、编译顺序）时非常有用。核心算法是 Kahn 算法（基于BFS和入度）和基于DFS的算法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 207. 课程表 &amp;amp; 210. 课程表 II&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这两个问题是拓扑排序的经典应用。要完成所有课程，课程依赖关系中不能存在环。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;课程表 (207)&lt;/strong&gt;：判断是否存在环。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;课程表 II (210)&lt;/strong&gt;：如果无环，返回一个可行的学习顺序。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kahn 算法的思路是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;构建图，并统计所有节点的入度（即有多少门先修课）。&lt;/li&gt;
&lt;li&gt;将所有入度为 0 的节点（没有先修课的课程）加入队列。&lt;/li&gt;
&lt;li&gt;当队列不为空时，出队一个节点，加入拓扑排序结果中。&lt;/li&gt;
&lt;li&gt;遍历该节点的所有邻居（依赖该课程的后续课程），将其入度减 1。如果邻居的入度变为 0，则入队。&lt;/li&gt;
&lt;li&gt;循环结束后，如果结果列表中的节点数等于总节点数，则拓扑排序成功；否则，图中存在环。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;

class Solution {
public:
    // Leetcode 210
    std::vector&amp;lt;int&amp;gt; findOrder(int numCourses, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; prerequisites) {
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; graph(numCourses);
        std::vector&amp;lt;int&amp;gt; in_degree(numCourses, 0);
        
        for (const auto&amp;amp; p : prerequisites) {
            graph[p[1]].push_back(p[0]);
            in_degree[p[0]]++;
        }
        
        std::queue&amp;lt;int&amp;gt; q;
        for (int i = 0; i &amp;lt; numCourses; ++i) {
            if (in_degree[i] == 0) {
                q.push(i);
            }
        }
        
        std::vector&amp;lt;int&amp;gt; result;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            result.push_back(u);
            
            for (int v : graph[u]) {
                in_degree[v]--;
                if (in_degree[v] == 0) {
                    q.push(v);
                }
            }
        }
        
        if (result.size() == numCourses) {
            return result;
        }
        return {}; // Leetcode 207 would return false here
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1462. 课程表 IV&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 判断课程 &lt;code&gt;u&lt;/code&gt; 是否是课程 &lt;code&gt;v&lt;/code&gt; 的先修课程，本质上是查询图中节点 &lt;code&gt;u&lt;/code&gt; 是否可达节点 &lt;code&gt;v&lt;/code&gt;。我们可以预先计算出图中所有节点对之间的可达性（传递闭包）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Floyd-Warshall&lt;/strong&gt;: 将图看作邻接矩阵 &lt;code&gt;adj&lt;/code&gt;，&lt;code&gt;adj[i][j] = true&lt;/code&gt; 如果 &lt;code&gt;i&lt;/code&gt; 是 &lt;code&gt;j&lt;/code&gt; 的直接先修。然后用 Floyd-Warshall 算法计算传递闭包：&lt;code&gt;adj[i][j] = adj[i][j] || (adj[i][k] &amp;amp;&amp;amp; adj[k][j])&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多次BFS/DFS&lt;/strong&gt;: 从每个节点 &lt;code&gt;i&lt;/code&gt; 出发，通过图遍历（BFS或DFS）找出所有它能到达的节点 &lt;code&gt;j&lt;/code&gt;，并记录下来。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    std::vector&amp;lt;bool&amp;gt; checkIfPrerequisite(int numCourses, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; prerequisites, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; queries) {
        std::vector&amp;lt;std::vector&amp;lt;bool&amp;gt;&amp;gt; is_prereq(numCourses, std::vector&amp;lt;bool&amp;gt;(numCourses, false));
        
        for (const auto&amp;amp; p : prerequisites) {
            is_prereq[p[0]][p[1]] = true;
        }
        
        // Floyd-Warshall to find all-pairs reachability
        for (int k = 0; k &amp;lt; numCourses; ++k) {
            for (int i = 0; i &amp;lt; numCourses; ++i) {
                for (int j = 0; j &amp;lt; numCourses; ++j) {
                    is_prereq[i][j] = is_prereq[i][j] || (is_prereq[i][k] &amp;amp;&amp;amp; is_prereq[k][j]);
                }
            }
        }
        
        std::vector&amp;lt;bool&amp;gt; result;
        for (const auto&amp;amp; q : queries) {
            result.push_back(is_prereq[q[0]][q[1]]);
        }
        return result;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1311. 获取你好友已观看的视频&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这是一个在图上进行限定层数广度优先搜索（BFS）的问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从你的 &lt;code&gt;id&lt;/code&gt; 开始进行 BFS，层数 &lt;code&gt;level&lt;/code&gt; 从 1 开始。&lt;/li&gt;
&lt;li&gt;当 &lt;code&gt;level&lt;/code&gt; 达到给定的 &lt;code&gt;level&lt;/code&gt; 时，收集该层所有好友观看过的视频。&lt;/li&gt;
&lt;li&gt;使用哈希表统计视频出现的频率，并按频率和视频名字典序排序。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::string&amp;gt; watchedVideosByFriends(std::vector&amp;lt;std::vector&amp;lt;std::string&amp;gt;&amp;gt;&amp;amp; watchedVideos, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; friends, int id, int level) {
        int n = friends.size();
        std::queue&amp;lt;std::pair&amp;lt;int, int&amp;gt;&amp;gt; q;
        q.push({id, 0});
        std::vector&amp;lt;bool&amp;gt; visited(n, false);
        visited[id] = true;
        
        std::vector&amp;lt;int&amp;gt; level_friends;
        
        while (!q.empty()) {
            auto [curr_id, curr_level] = q.front();
            q.pop();
            
            if (curr_level == level) {
                level_friends.push_back(curr_id);
            }
            if (curr_level &amp;gt; level) break;
            
            for (int friend_id : friends[curr_id]) {
                if (!visited[friend_id]) {
                    visited[friend_id] = true;
                    q.push({friend_id, curr_level + 1});
                }
            }
        }
        
        std::map&amp;lt;std::string, int&amp;gt; freq;
        for (int friend_id : level_friends) {
            for (const std::string&amp;amp; video : watchedVideos[friend_id]) {
                freq[video]++;
            }
        }
        
        std::vector&amp;lt;std::pair&amp;lt;int, std::string&amp;gt;&amp;gt; sorted_videos;
        for (auto const&amp;amp; [video, count] : freq) {
            sorted_videos.push_back({count, video});
        }
        
        std::sort(sorted_videos.begin(), sorted_videos.end());
        
        std::vector&amp;lt;std::string&amp;gt; result;
        for (const auto&amp;amp; p : sorted_videos) {
            result.push_back(p.second);
        }
        
        return result;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 2360. 图中的最长环&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 题目给的是一个功能图（每个节点出度为1）。在这种图中，从任何节点出发，最终要么到达一个环，要么路径结束。我们可以用DFS来遍历图，同时记录路径。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 &lt;code&gt;visited&lt;/code&gt; 数组标记已完全探索过的节点（确定不在任何未发现的环中）。&lt;/li&gt;
&lt;li&gt;对于每个未访问的节点，开始DFS。使用一个 &lt;code&gt;path_visited&lt;/code&gt; 哈希表记录当前路径上的节点及其路径长度（或时间戳）。&lt;/li&gt;
&lt;li&gt;如果在DFS中遇到一个已经在 &lt;code&gt;path_visited&lt;/code&gt; 中的节点，说明找到了一个环。环的长度为 &lt;code&gt;当前路径长度 - 首次访问该节点时的路径长度&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;DFS结束后，将当前路径上的所有节点标记到 &lt;code&gt;visited&lt;/code&gt; 中，避免重复计算。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int longestCycle(std::vector&amp;lt;int&amp;gt;&amp;amp; edges) {
        int n = edges.size();
        std::vector&amp;lt;bool&amp;gt; visited(n, false);
        int max_cycle = -1;

        for (int i = 0; i &amp;lt; n; ++i) {
            if (visited[i]) continue;
            
            std::vector&amp;lt;int&amp;gt; path_visited(n, 0);
            int dist = 0;
            int curr = i;
            
            while (curr != -1 &amp;amp;&amp;amp; !visited[curr]) {
                if (path_visited[curr] &amp;gt; 0) { // Found a cycle
                    max_cycle = std::max(max_cycle, dist - path_visited[curr] + 1);
                    break;
                }
                path_visited[curr] = dist + 1;
                dist++;
                visited[curr] = true; // Mark as visited for this path traversal
                curr = edges[curr];
            }
        }
        return max_cycle;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;图与动态规划/博弈论&lt;/h2&gt;
&lt;p&gt;许多图问题，特别是在网格或DAG上，可以利用动态规划（通常是记忆化搜索）来求解。当问题涉及两个对立玩家时，就进入了图博弈论的范畴。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1728. 猫和老鼠 II&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这是一个在网格图上的博弈论问题。我们需要确定在最优策略下，老鼠是否能赢。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态定义&lt;/strong&gt;：游戏的状态由 &lt;code&gt;(mouse_row, mouse_col, cat_row, cat_col, turn)&lt;/code&gt; 决定，其中 &lt;code&gt;turn&lt;/code&gt; 表示轮到谁（0为老鼠，1为猫）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结果定义&lt;/strong&gt;：每个状态有三种可能的结果：老鼠赢 (MOUSE_WIN), 猫赢 (CAT_WIN), 或平局/未知 (DRAW)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逆向推理 (BFS)&lt;/strong&gt;：我们从已知的终局状态开始，反向推导所有其他状态的结果。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;终局状态&lt;/strong&gt;：老鼠在食物处（老鼠赢），猫鼠同格（猫赢），猫在食物处（猫赢）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态转移&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;如果一个状态是&lt;strong&gt;老鼠的回合&lt;/strong&gt;，并且老鼠可以移动到一个&lt;strong&gt;老鼠赢&lt;/strong&gt;的状态，那么当前状态也是&lt;strong&gt;老鼠赢&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;如果一个状态是&lt;strong&gt;猫的回合&lt;/strong&gt;，并且猫可以移动到一个&lt;strong&gt;猫赢&lt;/strong&gt;的状态，那么当前状态也是&lt;strong&gt;猫赢&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;如果一个状态是&lt;strong&gt;老鼠的回合&lt;/strong&gt;，并且它所有的下一步都通向&lt;strong&gt;猫赢&lt;/strong&gt;的状态，那么当前状态是&lt;strong&gt;猫赢&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;如果一个状态是&lt;strong&gt;猫的回合&lt;/strong&gt;，并且它所有的下一步都通向&lt;strong&gt;老鼠赢&lt;/strong&gt;的状态，那么当前状态是&lt;strong&gt;老鼠赢&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拓扑排序思想&lt;/strong&gt;：我们可以用一个 &lt;code&gt;degrees&lt;/code&gt; 数组记录每个状态有多少个“未确定结果”的后续走法。当一个状态的所有后续走法都确定为对手赢时，该状态就确定为输。我们将结果确定的状态加入队列，并更新其前驱状态的 &lt;code&gt;degrees&lt;/code&gt;，这类似于拓扑排序。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;queue&amp;gt;

class Solution {
public:
    bool canMouseWin(std::vector&amp;lt;std::string&amp;gt;&amp;amp; grid, int catJump, int mouseJump) {
        int rows = grid.size(), cols = grid[0].size();
        int food_r, food_c, mouse_r, mouse_c, cat_r, cat_c;
        for (int r = 0; r &amp;lt; rows; ++r) {
            for (int c = 0; c &amp;lt; cols; ++c) {
                if (grid[r][c] == &apos;F&apos;) { food_r = r; food_c = c; }
                else if (grid[r][c] == &apos;M&apos;) { mouse_r = r; mouse_c = c; }
                else if (grid[r][c] == &apos;C&apos;) { cat_r = r; cat_c = c; }
            }
        }

        // state: [mouse_pos, cat_pos, turn], pos = r * cols + c
        // result: 0: DRAW, 1: MOUSE_WIN, 2: CAT_WIN
        int states[rows][cols][rows][cols][2];
        int degrees[rows][cols][rows][cols][2];
        std::memset(states, 0, sizeof(states));
        std::memset(degrees, 0, sizeof(degrees));

        int dr[] = {-1, 1, 0, 0}, dc[] = {0, 0, -1, 1};

        for (int mr = 0; mr &amp;lt; rows; ++mr) {
            for (int mc = 0; mc &amp;lt; cols; ++mc) {
                if (grid[mr][mc] == &apos;#&apos;) continue;
                for (int cr = 0; cr &amp;lt; rows; ++cr) {
                    for (int cc = 0; cc &amp;lt; cols; ++cc) {
                        if (grid[cr][cc] == &apos;#&apos;) continue;
                        // Mouse&apos;s turn
                        for (int i = 0; i &amp;lt; 4; ++i) {
                            for (int jump = 0; jump &amp;lt;= mouseJump; ++jump) {
                                int nmr = mr + dr[i] * jump, nmc = mc + dc[i] * jump;
                                if (nmr &amp;lt; 0 || nmr &amp;gt;= rows || nmc &amp;lt; 0 || nmc &amp;gt;= cols || grid[nmr][nmc] == &apos;#&apos;) break;
                                degrees[mr][mc][cr][cc][0]++;
                            }
                        }
                        // Cat&apos;s turn
                        for (int i = 0; i &amp;lt; 4; ++i) {
                            for (int jump = 0; jump &amp;lt;= catJump; ++jump) {
                                int ncr = cr + dr[i] * jump, ncc = cc + dc[i] * jump;
                                if (ncr &amp;lt; 0 || ncr &amp;gt;= rows || ncc &amp;lt; 0 || ncc &amp;gt;= cols || grid[ncr][ncc] == &apos;#&apos;) break;
                                degrees[mr][mc][cr][cc][1]++;
                            }
                        }
                    }
                }
            }
        }

        std::queue&amp;lt;std::tuple&amp;lt;int, int, int, int, int&amp;gt;&amp;gt; q;

        for (int r = 0; r &amp;lt; rows; ++r) {
            for (int c = 0; c &amp;lt; cols; ++c) {
                if (grid[r][c] == &apos;#&apos;) continue;
                // Terminal states
                if (r == food_r &amp;amp;&amp;amp; c == food_c) continue;
                for (int turn = 0; turn &amp;lt; 2; ++turn) {
                    // Cat wins by catching mouse
                    states[r][c][r][c][turn] = 2;
                    q.emplace(r, c, r, c, turn);
                    // Mouse wins by reaching food
                    states[food_r][food_c][r][c][turn] = 1;
                    q.emplace(food_r, food_c, r, c, turn);
                }
            }
        }

        while (!q.empty()) {
            auto [mr, mc, cr, cc, turn] = q.front();
            q.pop();
            int result = states[mr][mc][cr][cc][turn];
            int prev_turn = 1 - turn;

            if (prev_turn == 0) { // Previous turn was mouse&apos;s
                for (int i = 0; i &amp;lt; 4; ++i) {
                    for (int jump = 0; jump &amp;lt;= mouseJump; ++jump) {
                        int pmr = mr - dr[i] * jump, pmc = mc - dc[i] * jump;
                        if (pmr &amp;lt; 0 || pmr &amp;gt;= rows || pmc &amp;lt; 0 || pmc &amp;gt;= cols || grid[pmr][pmc] == &apos;#&apos;) break;
                        if (states[pmr][pmc][cr][cc][prev_turn] == 0) {
                            if (result == 1) { // Mouse found a winning move
                                states[pmr][pmc][cr][cc][prev_turn] = 1;
                                q.emplace(pmr, pmc, cr, cc, prev_turn);
                            } else { // One path to cat-win is removed
                                degrees[pmr][pmc][cr][cc][prev_turn]--;
                                if (degrees[pmr][pmc][cr][cc][prev_turn] == 0) {
                                    states[pmr][pmc][cr][cc][prev_turn] = 2;
                                    q.emplace(pmr, pmc, cr, cc, prev_turn);
                                }
                            }
                        }
                    }
                }
            } else { // Previous turn was cat&apos;s
                 for (int i = 0; i &amp;lt; 4; ++i) {
                    for (int jump = 0; jump &amp;lt;= catJump; ++jump) {
                        int pcr = cr - dr[i] * jump, pcc = cc - dc[i] * jump;
                        if (pcr &amp;lt; 0 || pcr &amp;gt;= rows || pcc &amp;lt; 0 || pcc &amp;gt;= cols || grid[pcr][pcc] == &apos;#&apos;) break;
                        if (states[mr][mc][pcr][pcc][prev_turn] == 0) {
                            if (result == 2) { // Cat found a winning move
                                states[mr][mc][pcr][pcc][prev_turn] = 2;
                                q.emplace(mr, mc, pcr, pcc, prev_turn);
                            } else {
                                degrees[mr][mc][pcr][pcc][prev_turn]--;
                                if (degrees[mr][mc][pcr][pcc][prev_turn] == 0) {
                                    states[mr][mc][pcr][pcc][prev_turn] = 1;
                                    q.emplace(mr, mc, pcr, pcc, prev_turn);
                                }
                            }
                        }
                    }
                }
            }
        }
        return states[mouse_r][mouse_c][cat_r][cat_c][0] == 1;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 329. 矩阵中的最长递增路径&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 将矩阵看作一个图，每个单元格是一个节点，如果两个相邻单元格的值满足递增关系，则存在一条有向边。问题是求图中的最长路径。这是一个DAG（因为路径必须递增，所以无环），可以用DFS + 记忆化来解决。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;定义 &lt;code&gt;dp[r][c]&lt;/code&gt; 为从单元格 &lt;code&gt;(r, c)&lt;/code&gt; 出发的最长递增路径的长度。&lt;/li&gt;
&lt;li&gt;对每个单元格 &lt;code&gt;(r, c)&lt;/code&gt; 调用DFS函数。&lt;/li&gt;
&lt;li&gt;DFS函数中，如果 &lt;code&gt;dp[r][c]&lt;/code&gt; 已计算过，直接返回。否则，探索四个方向的邻居 &lt;code&gt;(nr, nc)&lt;/code&gt;，如果 &lt;code&gt;matrix[nr][nc] &amp;gt; matrix[r][c]&lt;/code&gt;，则 &lt;code&gt;dp[r][c]&lt;/code&gt; 可以更新为 &lt;code&gt;1 + dfs(nr, nc)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;取所有方向的最大值更新 &lt;code&gt;dp[r][c]&lt;/code&gt;，并用一个全局变量记录所有 &lt;code&gt;dp&lt;/code&gt; 值中的最大值。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int longestIncreasingPath(std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; matrix) {
        if (matrix.empty()) return 0;
        int rows = matrix.size(), cols = matrix[0].size();
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; dp(rows, std::vector&amp;lt;int&amp;gt;(cols, 0));
        int max_len = 0;
        for (int i = 0; i &amp;lt; rows; ++i) {
            for (int j = 0; j &amp;lt; cols; ++j) {
                max_len = std::max(max_len, dfs(matrix, i, j, dp));
            }
        }
        return max_len;
    }

private:
    int dfs(const std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; matrix, int r, int c, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; dp) {
        if (dp[r][c] != 0) return dp[r][c];
        
        int max_path = 1;
        int rows = matrix.size(), cols = matrix[0].size();
        int dr[] = {-1, 1, 0, 0};
        int dc[] = {0, 0, -1, 1};
        
        for (int i = 0; i &amp;lt; 4; ++i) {
            int nr = r + dr[i];
            int nc = c + dc[i];
            if (nr &amp;gt;= 0 &amp;amp;&amp;amp; nr &amp;lt; rows &amp;amp;&amp;amp; nc &amp;gt;= 0 &amp;amp;&amp;amp; nc &amp;lt; cols &amp;amp;&amp;amp; matrix[nr][nc] &amp;gt; matrix[r][c]) {
                max_path = std::max(max_path, 1 + dfs(matrix, nr, nc, dp));
            }
        }
        
        return dp[r][c] = max_path;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1857. 有向图中最大颜色值&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 在一个有向图中，找到一条路径，使得路径上出现次数最多的颜色值最大。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先，使用拓扑排序检测图中是否有环。如果存在环，则可以无限次经过环上的节点，答案是无穷大（题目规定返回-1）。&lt;/li&gt;
&lt;li&gt;定义 &lt;code&gt;dp[u][c]&lt;/code&gt; 为到达节点 &lt;code&gt;u&lt;/code&gt; 的路径中，颜色 &lt;code&gt;c&lt;/code&gt; 出现的最大次数。&lt;/li&gt;
&lt;li&gt;在拓扑排序的过程中更新 &lt;code&gt;dp&lt;/code&gt; 数组。当从节点 &lt;code&gt;u&lt;/code&gt; 移动到邻居 &lt;code&gt;v&lt;/code&gt; 时：
&lt;code&gt;dp[v][c] = max(dp[v][c], dp[u][c] + (colors[v] == c))&lt;/code&gt;
其中 &lt;code&gt;colors[v] == c&lt;/code&gt; 是一个布尔表达式，如果 &lt;code&gt;v&lt;/code&gt; 的颜色是 &lt;code&gt;c&lt;/code&gt;，则为1，否则为0。&lt;/li&gt;
&lt;li&gt;在整个过程中，用一个全局变量记录所有 &lt;code&gt;dp&lt;/code&gt; 值的最大值。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int largestPathValue(std::string colors, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; edges) {
        int n = colors.length();
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; graph(n);
        std::vector&amp;lt;int&amp;gt; in_degree(n, 0);
        for (const auto&amp;amp; edge : edges) {
            graph[edge[0]].push_back(edge[1]);
            in_degree[edge[1]]++;
        }

        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; dp(n, std::vector&amp;lt;int&amp;gt;(26, 0));
        std::queue&amp;lt;int&amp;gt; q;
        for (int i = 0; i &amp;lt; n; ++i) {
            if (in_degree[i] == 0) {
                q.push(i);
            }
        }

        int max_val = 0;
        int nodes_seen = 0;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            nodes_seen++;
            
            dp[u][colors[u] - &apos;a&apos;]++;
            max_val = std::max(max_val, dp[u][colors[u] - &apos;a&apos;]);

            for (int v : graph[u]) {
                for (int c = 0; c &amp;lt; 26; ++c) {
                    dp[v][c] = std::max(dp[v][c], dp[u][c]);
                }
                in_degree[v]--;
                if (in_degree[v] == 0) {
                    q.push(v);
                }
            }
        }

        return nodes_seen == n ? max_val : -1;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;并查集 (Union-Find)&lt;/h2&gt;
&lt;p&gt;并查集是一种用于处理不相交集合的合并与查询的数据结构。它主要支持两个操作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;find(i)&lt;/code&gt;: 确定元素 &lt;code&gt;i&lt;/code&gt; 属于哪个集合。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;union(i, j)&lt;/code&gt;: 将元素 &lt;code&gt;i&lt;/code&gt; 和 &lt;code&gt;j&lt;/code&gt; 所在的集合合并。
常用于解决连通性问题，如判断图中两个节点是否连通、计算连通分量数量等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 990. 等式方程的可满足性&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这是一个并查集的经典应用。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将所有变量 &lt;code&gt;a&lt;/code&gt; 到 &lt;code&gt;z&lt;/code&gt; 看作并查集中的元素。&lt;/li&gt;
&lt;li&gt;首先遍历所有等式 &lt;code&gt;a==b&lt;/code&gt;。对于每个等式，将 &lt;code&gt;a&lt;/code&gt; 和 &lt;code&gt;b&lt;/code&gt; 所在的集合合并（&lt;code&gt;union&lt;/code&gt;）。这表示所有相等的变量都属于同一个连通分量。&lt;/li&gt;
&lt;li&gt;然后遍历所有不等式 &lt;code&gt;c!=d&lt;/code&gt;。对于每个不等式，检查 &lt;code&gt;c&lt;/code&gt; 和 &lt;code&gt;d&lt;/code&gt; 是否在同一个集合中（&lt;code&gt;find(c) == find(d)&lt;/code&gt;）。如果在，说明 &lt;code&gt;c&lt;/code&gt; 和 &lt;code&gt;d&lt;/code&gt; 被之前的等式强制要求相等，与当前不等式矛盾，返回 &lt;code&gt;false&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果所有不等式都不矛盾，返回 &lt;code&gt;true&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;

class DSU {
private:
    std::vector&amp;lt;int&amp;gt; parent;
public:
    DSU(int n) {
        parent.resize(n);
        for (int i = 0; i &amp;lt; n; ++i) parent[i] = i;
    }
    int find(int i) {
        if (parent[i] == i) return i;
        return parent[i] = find(parent[i]);
    }
    void unite(int i, int j) {
        int root_i = find(i);
        int root_j = find(j);
        if (root_i != root_j) {
            parent[root_i] = root_j;
        }
    }
};

class Solution {
public:
    bool equationsPossible(std::vector&amp;lt;std::string&amp;gt;&amp;amp; equations) {
        DSU dsu(26);
        for (const std::string&amp;amp; eq : equations) {
            if (eq[1] == &apos;=&apos;) {
                dsu.unite(eq[0] - &apos;a&apos;, eq[3] - &apos;a&apos;);
            }
        }
        for (const std::string&amp;amp; eq : equations) {
            if (eq[1] == &apos;!&apos;) {
                if (dsu.find(eq[0] - &apos;a&apos;) == dsu.find(eq[3] - &apos;a&apos;)) {
                    return false;
                }
            }
        }
        return true;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 997. 找到小镇的法官&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这是一个简单的图问题，可以通过计算节点的入度和出度来解决。小镇的法官满足两个条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;所有其他 &lt;code&gt;n-1&lt;/code&gt; 个人都信任他，意味着法官的入度是 &lt;code&gt;n-1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;他不信任任何人，意味着法官的出度是 &lt;code&gt;0&lt;/code&gt;。
我们可以遍历 &lt;code&gt;trust&lt;/code&gt; 数组，用两个数组分别记录每个人的入度和出度，然后找到满足条件的节点。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;

class Solution {
public:
    int findJudge(int n, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; trust) {
        if (n == 1) return 1;
        std::vector&amp;lt;int&amp;gt; in_degree(n + 1, 0);
        std::vector&amp;lt;int&amp;gt; out_degree(n + 1, 0);
        
        for (const auto&amp;amp; t : trust) {
            out_degree[t[0]]++;
            in_degree[t[1]]++;
        }
        
        for (int i = 1; i &amp;lt;= n; ++i) {
            if (in_degree[i] == n - 1 &amp;amp;&amp;amp; out_degree[i] == 0) {
                return i;
            }
        }
        
        return -1;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 2127. 参加会议的最多员工数&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这是一个功能图（每个节点出度为1）的问题。员工可以形成两种结构来开会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一个长度大于2的环。&lt;/li&gt;
&lt;li&gt;两个相-互喜欢的员工（一个长度为2的环），以及分别指向这两位员工的最长“手臂”链条。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们可以先用拓扑排序（Kahn算法）剥离出所有的非环部分（树枝），计算出每个节点作为链条末端时的最长链长度。然后，处理剩下的环：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于长度大于2的环，环上所有员工可以一起开会，更新最大值。&lt;/li&gt;
&lt;li&gt;对于长度为2的环（&lt;code&gt;a &amp;lt;-&amp;gt; b&lt;/code&gt;），可以参加会议的人数是 &lt;code&gt;2 + (a的最长手臂) + (b的最长手臂)&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int maximumInvitations(std::vector&amp;lt;int&amp;gt;&amp;amp; favorite) {
        int n = favorite.size();
        std::vector&amp;lt;int&amp;gt; in_degree(n, 0);
        for (int f : favorite) {
            in_degree[f]++;
        }

        std::queue&amp;lt;int&amp;gt; q;
        std::vector&amp;lt;int&amp;gt; max_depth(n, 1);
        for (int i = 0; i &amp;lt; n; ++i) {
            if (in_degree[i] == 0) {
                q.push(i);
            }
        }

        // 拓扑排序，计算非环节点的最长链
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            int v = favorite[u];
            max_depth[v] = std::max(max_depth[v], max_depth[u] + 1);
            in_degree[v]--;
            if (in_degree[v] == 0) {
                q.push(v);
            }
        }

        int max_cycle_len = 0;
        int two_person_chains_sum = 0;
        std::vector&amp;lt;bool&amp;gt; visited(n, false);

        for (int i = 0; i &amp;lt; n; ++i) {
            if (in_degree[i] &amp;gt; 0 &amp;amp;&amp;amp; !visited[i]) {
                if (favorite[favorite[i]] == i) { // 长度为2的环
                    two_person_chains_sum += max_depth[i] + max_depth[favorite[i]];
                    visited[i] = visited[favorite[i]] = true;
                } else { // 长度大于2的环
                    int curr = i;
                    int cycle_len = 0;
                    while (!visited[curr]) {
                        visited[curr] = true;
                        cycle_len++;
                        curr = favorite[curr];
                    }
                    max_cycle_len = std::max(max_cycle_len, cycle_len);
                }
            }
        }
        return std::max(max_cycle_len, two_person_chains_sum);
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;图与动态规划&lt;/h2&gt;
&lt;p&gt;许多图问题，特别是在网格或DAG上，可以利用动态规划（通常是记忆化搜索）来求解。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 329. 矩阵中的最长递增路径&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 将矩阵看作一个图，每个单元格是一个节点，如果两个相邻单元格的值满足递增关系，则存在一条有向边。问题是求图中的最长路径。这是一个DAG（因为路径必须递增，所以无环），可以用DFS + 记忆化来解决。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;定义 &lt;code&gt;dp[r][c]&lt;/code&gt; 为从单元格 &lt;code&gt;(r, c)&lt;/code&gt; 出发的最长递增路径的长度。&lt;/li&gt;
&lt;li&gt;对每个单元格 &lt;code&gt;(r, c)&lt;/code&gt; 调用DFS函数。&lt;/li&gt;
&lt;li&gt;DFS函数中，如果 &lt;code&gt;dp[r][c]&lt;/code&gt; 已计算过，直接返回。否则，探索四个方向的邻居 &lt;code&gt;(nr, nc)&lt;/code&gt;，如果 &lt;code&gt;matrix[nr][nc] &amp;gt; matrix[r][c]&lt;/code&gt;，则 &lt;code&gt;dp[r][c]&lt;/code&gt; 可以更新为 &lt;code&gt;1 + dfs(nr, nc)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;取所有方向的最大值更新 &lt;code&gt;dp[r][c]&lt;/code&gt;，并用一个全局变量记录所有 &lt;code&gt;dp&lt;/code&gt; 值中的最大值。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int longestIncreasingPath(std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; matrix) {
        if (matrix.empty()) return 0;
        int rows = matrix.size(), cols = matrix[0].size();
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; dp(rows, std::vector&amp;lt;int&amp;gt;(cols, 0));
        int max_len = 0;
        for (int i = 0; i &amp;lt; rows; ++i) {
            for (int j = 0; j &amp;lt; cols; ++j) {
                max_len = std::max(max_len, dfs(matrix, i, j, dp));
            }
        }
        return max_len;
    }

private:
    int dfs(const std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; matrix, int r, int c, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; dp) {
        if (dp[r][c] != 0) return dp[r][c];
        
        int max_path = 1;
        int rows = matrix.size(), cols = matrix[0].size();
        int dr[] = {-1, 1, 0, 0};
        int dc[] = {0, 0, -1, 1};
        
        for (int i = 0; i &amp;lt; 4; ++i) {
            int nr = r + dr[i];
            int nc = c + dc[i];
            if (nr &amp;gt;= 0 &amp;amp;&amp;amp; nr &amp;lt; rows &amp;amp;&amp;amp; nc &amp;gt;= 0 &amp;amp;&amp;amp; nc &amp;lt; cols &amp;amp;&amp;amp; matrix[nr][nc] &amp;gt; matrix[r][c]) {
                max_path = std::max(max_path, 1 + dfs(matrix, nr, nc, dp));
            }
        }
        
        return dp[r][c] = max_path;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1857. 有向图中最大颜色值&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 在一个有向图中，找到一条路径，使得路径上出现次数最多的颜色值最大。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先，使用拓扑排序检测图中是否有环。如果存在环，则可以无限次经过环上的节点，答案是无穷大（题目规定返回-1）。&lt;/li&gt;
&lt;li&gt;定义 &lt;code&gt;dp[u][c]&lt;/code&gt; 为到达节点 &lt;code&gt;u&lt;/code&gt; 的路径中，颜色 &lt;code&gt;c&lt;/code&gt; 出现的最大次数。&lt;/li&gt;
&lt;li&gt;在拓扑排序的过程中更新 &lt;code&gt;dp&lt;/code&gt; 数组。当从节点 &lt;code&gt;u&lt;/code&gt; 移动到邻居 &lt;code&gt;v&lt;/code&gt; 时：
&lt;code&gt;dp[v][c] = max(dp[v][c], dp[u][c] + (colors[v] == c))&lt;/code&gt;
其中 &lt;code&gt;colors[v] == c&lt;/code&gt; 是一个布尔表达式，如果 &lt;code&gt;v&lt;/code&gt; 的颜色是 &lt;code&gt;c&lt;/code&gt;，则为1，否则为0。&lt;/li&gt;
&lt;li&gt;在整个过程中，用一个全局变量记录所有 &lt;code&gt;dp&lt;/code&gt; 值的最大值。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int largestPathValue(std::string colors, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; edges) {
        int n = colors.length();
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; graph(n);
        std::vector&amp;lt;int&amp;gt; in_degree(n, 0);
        for (const auto&amp;amp; edge : edges) {
            graph[edge[0]].push_back(edge[1]);
            in_degree[edge[1]]++;
        }

        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; dp(n, std::vector&amp;lt;int&amp;gt;(26, 0));
        std::queue&amp;lt;int&amp;gt; q;
        for (int i = 0; i &amp;lt; n; ++i) {
            if (in_degree[i] == 0) {
                q.push(i);
            }
        }

        int max_val = 0;
        int nodes_seen = 0;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            nodes_seen++;
            
            dp[u][colors[u] - &apos;a&apos;]++;
            max_val = std::max(max_val, dp[u][colors[u] - &apos;a&apos;]);

            for (int v : graph[u]) {
                for (int c = 0; c &amp;lt; 26; ++c) {
                    dp[v][c] = std::max(dp[v][c], dp[u][c]);
                }
                in_degree[v]--;
                if (in_degree[v] == 0) {
                    q.push(v);
                }
            }
        }

        return nodes_seen == n ? max_val : -1;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;并查集 (Union-Find)&lt;/h2&gt;
&lt;p&gt;并查集是一种用于处理不相交集合的合并与查询的数据结构。它主要支持两个操作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;find(i)&lt;/code&gt;: 确定元素 &lt;code&gt;i&lt;/code&gt; 属于哪个集合。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;union(i, j)&lt;/code&gt;: 将元素 &lt;code&gt;i&lt;/code&gt; 和 &lt;code&gt;j&lt;/code&gt; 所在的集合合并。
常用于解决连通性问题，如判断图中两个节点是否连通、计算连通分量数量等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 990. 等式方程的可满足性&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这是一个并查集的经典应用。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将所有变量 &lt;code&gt;a&lt;/code&gt; 到 &lt;code&gt;z&lt;/code&gt; 看作并查集中的元素。&lt;/li&gt;
&lt;li&gt;首先遍历所有等式 &lt;code&gt;a==b&lt;/code&gt;。对于每个等式，将 &lt;code&gt;a&lt;/code&gt; 和 &lt;code&gt;b&lt;/code&gt; 所在的集合合并（&lt;code&gt;union&lt;/code&gt;）。这表示所有相等的变量都属于同一个连通分量。&lt;/li&gt;
&lt;li&gt;然后遍历所有不等式 &lt;code&gt;c!=d&lt;/code&gt;。对于每个不等式，检查 &lt;code&gt;c&lt;/code&gt; 和 &lt;code&gt;d&lt;/code&gt; 是否在同一个集合中（&lt;code&gt;find(c) == find(d)&lt;/code&gt;）。如果在，说明 &lt;code&gt;c&lt;/code&gt; 和 &lt;code&gt;d&lt;/code&gt; 被之前的等式强制要求相等，与当前不等式矛盾，返回 &lt;code&gt;false&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果所有不等式都不矛盾，返回 &lt;code&gt;true&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;

class DSU {
private:
    std::vector&amp;lt;int&amp;gt; parent;
public:
    DSU(int n) {
        parent.resize(n);
        for (int i = 0; i &amp;lt; n; ++i) parent[i] = i;
    }
    int find(int i) {
        if (parent[i] == i) return i;
        return parent[i] = find(parent[i]);
    }
    void unite(int i, int j) {
        int root_i = find(i);
        int root_j = find(j);
        if (root_i != root_j) {
            parent[root_i] = root_j;
        }
    }
};

class Solution {
public:
    bool equationsPossible(std::vector&amp;lt;std::string&amp;gt;&amp;amp; equations) {
        DSU dsu(26);
        for (const std::string&amp;amp; eq : equations) {
            if (eq[1] == &apos;=&apos;) {
                dsu.unite(eq[0] - &apos;a&apos;, eq[3] - &apos;a&apos;);
            }
        }
        for (const std::string&amp;amp; eq : equations) {
            if (eq[1] == &apos;!&apos;) {
                if (dsu.find(eq[0] - &apos;a&apos;) == dsu.find(eq[3] - &apos;a&apos;)) {
                    return false;
                }
            }
        }
        return true;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 997. 找到小镇的法官&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这是一个简单的图问题，可以通过计算节点的入度和出度来解决。小镇的法官满足两个条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;所有其他 &lt;code&gt;n-1&lt;/code&gt; 个人都信任他，意味着法官的入度是 &lt;code&gt;n-1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;他不信任任何人，意味着法官的出度是 &lt;code&gt;0&lt;/code&gt;。
我们可以遍历 &lt;code&gt;trust&lt;/code&gt; 数组，用两个数组分别记录每个人的入度和出度，然后找到满足条件的节点。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;

class Solution {
public:
    int findJudge(int n, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; trust) {
        if (n == 1) return 1;
        std::vector&amp;lt;int&amp;gt; in_degree(n + 1, 0);
        std::vector&amp;lt;int&amp;gt; out_degree(n + 1, 0);
        
        for (const auto&amp;amp; t : trust) {
            out_degree[t[0]]++;
            in_degree[t[1]]++;
        }
        
        for (int i = 1; i &amp;lt;= n; ++i) {
            if (in_degree[i] == n - 1 &amp;amp;&amp;amp; out_degree[i] == 0) {
                return i;
            }
        }
        
        return -1;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 2127. 参加会议的最多员工数&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这是一个功能图（每个节点出度为1）的问题。员工可以形成两种结构来开会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一个长度大于2的环。&lt;/li&gt;
&lt;li&gt;两个相-互喜欢的员工（一个长度为2的环），以及分别指向这两位员工的最长“手臂”链条。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们可以先用拓扑排序（Kahn算法）剥离出所有的非环部分（树枝），计算出每个节点作为链条末端时的最长链长度。然后，处理剩下的环：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于长度大于2的环，环上所有员工可以一起开会，更新最大值。&lt;/li&gt;
&lt;li&gt;对于长度为2的环（&lt;code&gt;a &amp;lt;-&amp;gt; b&lt;/code&gt;），可以参加会议的人数是 &lt;code&gt;2 + (a的最长手臂) + (b的最长手臂)&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int maximumInvitations(std::vector&amp;lt;int&amp;gt;&amp;amp; favorite) {
        int n = favorite.size();
        std::vector&amp;lt;int&amp;gt; in_degree(n, 0);
        for (int f : favorite) {
            in_degree[f]++;
        }

        std::queue&amp;lt;int&amp;gt; q;
        std::vector&amp;lt;int&amp;gt; max_depth(n, 1);
        for (int i = 0; i &amp;lt; n; ++i) {
            if (in_degree[i] == 0) {
                q.push(i);
            }
        }

        // 拓扑排序，计算非环节点的最长链
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            int v = favorite[u];
            max_depth[v] = std::max(max_depth[v], max_depth[u] + 1);
            in_degree[v]--;
            if (in_degree[v] == 0) {
                q.push(v);
            }
        }

        int max_cycle_len = 0;
        int two_person_chains_sum = 0;
        std::vector&amp;lt;bool&amp;gt; visited(n, false);

        for (int i = 0; i &amp;lt; n; ++i) {
            if (in_degree[i] &amp;gt; 0 &amp;amp;&amp;amp; !visited[i]) {
                if (favorite[favorite[i]] == i) { // 长度为2的环
                    two_person_chains_sum += max_depth[i] + max_depth[favorite[i]];
                    visited[i] = visited[favorite[i]] = true;
                } else { // 长度大于2的环
                    int curr = i;
                    int cycle_len = 0;
                    while (!visited[curr]) {
                        visited[curr] = true;
                        cycle_len++;
                        curr = favorite[curr];
                    }
                    max_cycle_len = std::max(max_cycle_len, cycle_len);
                }
            }
        }
        return std::max(max_cycle_len, two_person_chains_sum);
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;图与动态规划&lt;/h2&gt;
&lt;p&gt;许多图问题，特别是在网格或DAG上，可以利用动态规划（通常是记忆化搜索）来求解。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 329. 矩阵中的最长递增路径&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 将矩阵看作一个图，每个单元格是一个节点，如果两个相邻单元格的值满足递增关系，则存在一条有向边。问题是求图中的最长路径。这是一个DAG（因为路径必须递增，所以无环），可以用DFS + 记忆化来解决。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;定义 &lt;code&gt;dp[r][c]&lt;/code&gt; 为从单元格 &lt;code&gt;(r, c)&lt;/code&gt; 出发的最长递增路径的长度。&lt;/li&gt;
&lt;li&gt;对每个单元格 &lt;code&gt;(r, c)&lt;/code&gt; 调用DFS函数。&lt;/li&gt;
&lt;li&gt;DFS函数中，如果 &lt;code&gt;dp[r][c]&lt;/code&gt; 已计算过，直接返回。否则，探索四个方向的邻居 &lt;code&gt;(nr, nc)&lt;/code&gt;，如果 &lt;code&gt;matrix[nr][nc] &amp;gt; matrix[r][c]&lt;/code&gt;，则 &lt;code&gt;dp[r][c]&lt;/code&gt; 可以更新为 &lt;code&gt;1 + dfs(nr, nc)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;取所有方向的最大值更新 &lt;code&gt;dp[r][c]&lt;/code&gt;，并用一个全局变量记录所有 &lt;code&gt;dp&lt;/code&gt; 值中的最大值。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int longestIncreasingPath(std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; matrix) {
        if (matrix.empty()) return 0;
        int rows = matrix.size(), cols = matrix[0].size();
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; dp(rows, std::vector&amp;lt;int&amp;gt;(cols, 0));
        int max_len = 0;
        for (int i = 0; i &amp;lt; rows; ++i) {
            for (int j = 0; j &amp;lt; cols; ++j) {
                max_len = std::max(max_len, dfs(matrix, i, j, dp));
            }
        }
        return max_len;
    }

private:
    int dfs(const std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; matrix, int r, int c, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; dp) {
        if (dp[r][c] != 0) return dp[r][c];
        
        int max_path = 1;
        int rows = matrix.size(), cols = matrix[0].size();
        int dr[] = {-1, 1, 0, 0};
        int dc[] = {0, 0, -1, 1};
        
        for (int i = 0; i &amp;lt; 4; ++i) {
            int nr = r + dr[i];
            int nc = c + dc[i];
            if (nr &amp;gt;= 0 &amp;amp;&amp;amp; nr &amp;lt; rows &amp;amp;&amp;amp; nc &amp;gt;= 0 &amp;amp;&amp;amp; nc &amp;lt; cols &amp;amp;&amp;amp; matrix[nr][nc] &amp;gt; matrix[r][c]) {
                max_path = std::max(max_path, 1 + dfs(matrix, nr, nc, dp));
            }
        }
        
        return dp[r][c] = max_path;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1857. 有向图中最大颜色值&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 在一个有向图中，找到一条路径，使得路径上出现次数最多的颜色值最大。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先，使用拓扑排序检测图中是否有环。如果存在环，则可以无限次经过环上的节点，答案是无穷大（题目规定返回-1）。&lt;/li&gt;
&lt;li&gt;定义 &lt;code&gt;dp[u][c]&lt;/code&gt; 为到达节点 &lt;code&gt;u&lt;/code&gt; 的路径中，颜色 &lt;code&gt;c&lt;/code&gt; 出现的最大次数。&lt;/li&gt;
&lt;li&gt;在拓扑排序的过程中更新 &lt;code&gt;dp&lt;/code&gt; 数组。当从节点 &lt;code&gt;u&lt;/code&gt; 移动到邻居 &lt;code&gt;v&lt;/code&gt; 时：
&lt;code&gt;dp[v][c] = max(dp[v][c], dp[u][c] + (colors[v] == c))&lt;/code&gt;
其中 &lt;code&gt;colors[v] == c&lt;/code&gt; 是一个布尔表达式，如果 &lt;code&gt;v&lt;/code&gt; 的颜色是 &lt;code&gt;c&lt;/code&gt;，则为1，否则为0。&lt;/li&gt;
&lt;li&gt;在整个过程中，用一个全局变量记录所有 &lt;code&gt;dp&lt;/code&gt; 值的最大值。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int largestPathValue(std::string colors, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; edges) {
        int n = colors.length();
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; graph(n);
        std::vector&amp;lt;int&amp;gt; in_degree(n, 0);
        for (const auto&amp;amp; edge : edges) {
            graph[edge[0]].push_back(edge[1]);
            in_degree[edge[1]]++;
        }

        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; dp(n, std::vector&amp;lt;int&amp;gt;(26, 0));
        std::queue&amp;lt;int&amp;gt; q;
        for (int i = 0; i &amp;lt; n; ++i) {
            if (in_degree[i] == 0) {
                q.push(i);
            }
        }

        int max_val = 0;
        int nodes_seen = 0;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            nodes_seen++;
            
            dp[u][colors[u] - &apos;a&apos;]++;
            max_val = std::max(max_val, dp[u][colors[u] - &apos;a&apos;]);

            for (int v : graph[u]) {
                for (int c = 0; c &amp;lt; 26; ++c) {
                    dp[v][c] = std::max(dp[v][c], dp[u][c]);
                }
                in_degree[v]--;
                if (in_degree[v] == 0) {
                    q.push(v);
                }
            }
        }

        return nodes_seen == n ? max_val : -1;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1632. 矩阵转换后的秩&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这是一个结合了并查集和拓扑排序的复杂问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;分组&lt;/strong&gt;：将矩阵中所有值相同的单元格按值分组，并按值从小到大处理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理同行同列&lt;/strong&gt;：对于每个值，遍历其所有出现位置。使用并查集将同一行或同一列的单元格合并到同一个连通分量中。这意味着它们必须有相同的秩。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算组的秩&lt;/strong&gt;：对于每个连通分量（组），其秩必须大于等于该组内所有单元格所在行和列的当前最大秩。所以，组的秩 &lt;code&gt;group_rank = 1 + max(rank_row[r], rank_col[c])&lt;/code&gt; for all &lt;code&gt;(r, c)&lt;/code&gt; in the group。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新秩&lt;/strong&gt;：计算出组的秩后，用这个新秩更新该组内所有单元格的最终秩，并同时更新这些单元格所在行和列的 &lt;code&gt;rank_row&lt;/code&gt; 和 &lt;code&gt;rank_col&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;numeric&amp;gt;

class DSU_Rank {
public:
    std::vector&amp;lt;int&amp;gt; parent;
    DSU_Rank(int n) {
        parent.resize(n);
        std::iota(parent.begin(), parent.end(), 0);
    }
    int find(int i) {
        if (parent[i] == i) return i;
        return parent[i] = find(parent[i]);
    }
    void unite(int i, int j) {
        int root_i = find(i);
        int root_j = find(j);
        if (root_i != root_j) parent[root_i] = root_j;
    }
};

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; matrixRankTransform(std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; matrix) {
        int rows = matrix.size(), cols = matrix[0].size();
        std::vector&amp;lt;int&amp;gt; rank_row(rows, 0), rank_col(cols, 0);
        DSU_Rank dsu(rows * cols);

        // Step 1: Group by value
        std::vector&amp;lt;std::tuple&amp;lt;int, int, int, int&amp;gt;&amp;gt; cells;
        for (int r = 0; r &amp;lt; rows; ++r) {
            for (int c = 0; c &amp;lt; cols; ++c) {
                cells.emplace_back(matrix[r][c], r, c, r * cols + c);
            }
        }
        std::sort(cells.begin(), cells.end());

        // Step 2: Union-Find for rows and columns
        for (auto&amp;amp; [value, r, c, id] : cells) {
            if (r &amp;gt; 0 &amp;amp;&amp;amp; matrix[r - 1][c] == value) {
                dsu.unite(id, (r - 1) * cols + c);
            }
            if (c &amp;gt; 0 &amp;amp;&amp;amp; matrix[r][c - 1] == value) {
                dsu.unite(id, r * cols + c - 1);
            }
        }

        // Step 3: Calculate rank for each group
        std::map&amp;lt;int, int&amp;gt; group_rank;
        for (int r = 0; r &amp;lt; rows; ++r) {
            for (int c = 0; c &amp;lt; cols; ++c) {
                int root = dsu.find(r * cols + c);
                group_rank[root] = std::max(group_rank[root], std::max(rank_row[r], rank_col[c]));
            }
        }

        // Step 4: Assign ranks
        for (int r = 0; r &amp;lt; rows; ++r) {
            for (int c = 0; c &amp;lt; cols; ++c) {
                int root = dsu.find(r * cols + c);
                int new_rank = group_rank[root] + 1;
                rank_row[r] = rank_col[c] = new_rank;
            }
        }

        // Prepare the result
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; ans(rows, std::vector&amp;lt;int&amp;gt;(cols));
        for (int r = 0; r &amp;lt; rows; ++r) {
            for (int c = 0; c &amp;lt; cols; ++c) {
                ans[r][c] = std::max(rank_row[r], rank_col[c]);
            }
        }
        return ans;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最短路径&lt;/h2&gt;
&lt;p&gt;最短路径算法用于寻找图中两点之间成本最低的路径。&lt;/p&gt;
&lt;h3&gt;Dijkstra 算法&lt;/h3&gt;
&lt;p&gt;用于解决&lt;strong&gt;单源最短路径&lt;/strong&gt;问题，适用于&lt;strong&gt;边权非负&lt;/strong&gt;的图。它使用优先队列来贪心地选择离源点最近的未访问节点进行扩展。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1976. 到达目的地的方案数&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这是对 Dijkstra 算法的扩展。我们需要在找到最短路径的同时，计算最短路径的数量。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;定义 &lt;code&gt;dist[i]&lt;/code&gt; 为从起点到节点 &lt;code&gt;i&lt;/code&gt; 的最短距离，&lt;code&gt;ways[i]&lt;/code&gt; 为到达节点 &lt;code&gt;i&lt;/code&gt; 的最短路径数量。&lt;/li&gt;
&lt;li&gt;使用 Dijkstra 算法。当通过节点 &lt;code&gt;u&lt;/code&gt; 更新到邻居 &lt;code&gt;v&lt;/code&gt; 的距离时：
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;dist[u] + weight &amp;lt; dist[v]&lt;/code&gt;，说明找到了到 &lt;code&gt;v&lt;/code&gt; 的一条更短的路径。更新 &lt;code&gt;dist[v]&lt;/code&gt;，并且到 &lt;code&gt;v&lt;/code&gt; 的最短路径方案数等于到 &lt;code&gt;u&lt;/code&gt; 的方案数，即 &lt;code&gt;ways[v] = ways[u]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;dist[u] + weight == dist[v]&lt;/code&gt;，说明找到了另一条同样长度的最短路径。到 &lt;code&gt;v&lt;/code&gt; 的方案数增加，即 &lt;code&gt;ways[v] = ways[v] + ways[u]&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;

using ll = long long;
const ll INF = 1e18;

class Solution {
public:
    int countPaths(int n, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; roads) {
        std::vector&amp;lt;std::vector&amp;lt;std::pair&amp;lt;int, int&amp;gt;&amp;gt;&amp;gt; graph(n);
        for (const auto&amp;amp; road : roads) {
            graph[road[0]].push_back({road[1], road[2]});
            graph[road[1]].push_back({road[0], road[2]});
        }
        
        std::vector&amp;lt;ll&amp;gt; dist(n, INF);
        std::vector&amp;lt;ll&amp;gt; ways(n, 0);
        dist[0] = 0;
        ways[0] = 1;
        
        std::priority_queue&amp;lt;std::pair&amp;lt;ll, int&amp;gt;, std::vector&amp;lt;std::pair&amp;lt;ll, int&amp;gt;&amp;gt;, std::greater&amp;lt;std::pair&amp;lt;ll, int&amp;gt;&amp;gt;&amp;gt; pq;
        pq.push({0, 0}); // {distance, node}
        
        int MOD = 1e9 + 7;
        
        while (!pq.empty()) {
            auto [d, u] = pq.top();
            pq.pop();
            
            if (d &amp;gt; dist[u]) continue;
            
            for (auto&amp;amp; edge : graph[u]) {
                int v = edge.first;
                int weight = edge.second;
                if (dist[u] + weight &amp;lt; dist[v]) {
                    dist[v] = dist[u] + weight;
                    ways[v] = ways[u];
                    pq.push({dist[v], v});
                } else if (dist[u] + weight == dist[v]) {
                    ways[v] = (ways[v] + ways[u]) % MOD;
                }
            }
        }
        
        return ways[n - 1];
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 2662. 前往目标的最小代价&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这是一个在网格图上的最短路径问题，但边的权重比较特殊。从一个点到另一个相邻点的代价是1，但还可以通过 &lt;code&gt;specialRoads&lt;/code&gt; 在不相邻的点之间移动。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将起点、终点以及所有 &lt;code&gt;specialRoads&lt;/code&gt; 的端点都看作图中的节点。&lt;/li&gt;
&lt;li&gt;构建图：
&lt;ul&gt;
&lt;li&gt;任意两个节点之间的默认边权重是它们之间的曼哈顿距离。&lt;/li&gt;
&lt;li&gt;对于每条 &lt;code&gt;specialRoads&lt;/code&gt; &lt;code&gt;(x1, y1, x2, y2, cost)&lt;/code&gt;，如果 &lt;code&gt;cost&lt;/code&gt; 小于 &lt;code&gt;(x1, y1)&lt;/code&gt; 和 &lt;code&gt;(x2, y2)&lt;/code&gt; 之间的曼哈顿距离，则可以认为有一条从 &lt;code&gt;(x1, y1)&lt;/code&gt; 到 &lt;code&gt;(x2, y2)&lt;/code&gt; 的权重为 &lt;code&gt;cost&lt;/code&gt; 的有向边。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在这个构建的图上，从起点运行 Dijkstra 算法，找到到终点的最短路径。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;cmath&amp;gt;

using ll = long long;

class Solution {
public:
    int minimumCost(std::vector&amp;lt;int&amp;gt;&amp;amp; start, std::vector&amp;lt;int&amp;gt;&amp;amp; target, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; specialRoads) {
        std::map&amp;lt;std::pair&amp;lt;int, int&amp;gt;, ll&amp;gt; dist;
        dist[{start[0], start[1]}] = 0;
        
        std::priority_queue&amp;lt;std::pair&amp;lt;ll, std::pair&amp;lt;int, int&amp;gt;&amp;gt;, std::vector&amp;lt;std::pair&amp;lt;ll, std::pair&amp;lt;int, int&amp;gt;&amp;gt;&amp;gt;, std::greater&amp;lt;&amp;gt;&amp;gt; pq;
        pq.push({0, {start[0], start[1]}});

        ll min_cost = abs(start[0] - target[0]) + abs(start[1] - target[1]);

        while (!pq.empty()) {
            auto [d, p] = pq.top();
            pq.pop();
            int r = p.first, c = p.second;

            if (d &amp;gt; dist[{r, c}]) continue;

            min_cost = std::min(min_cost, d + (ll)abs(r - target[0]) + (ll)abs(c - target[1]));

            for (const auto&amp;amp; road : specialRoads) {
                int r1 = road[0], c1 = road[1], r2 = road[2], c2 = road[3], cost = road[4];
                ll new_dist = d + (ll)abs(r - r1) + (ll)abs(c - c1) + cost;
                
                if (!dist.count({r2, c2}) || new_dist &amp;lt; dist[{r2, c2}]) {
                    dist[{r2, c2}] = new_dist;
                    pq.push({new_dist, {r2, c2}});
                }
            }
        }
        return min_cost;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 3123. 最短路径中的边&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 找到所有存在于从 &lt;code&gt;start&lt;/code&gt; 到 &lt;code&gt;end&lt;/code&gt; 的某条最短路径上的边。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从 &lt;code&gt;start&lt;/code&gt; 节点运行一次 Dijkstra 算法，计算出到所有节点的最短距离 &lt;code&gt;dist_start&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;从 &lt;code&gt;end&lt;/code&gt; 节点运行一次 Dijkstra 算法（在反向图上，或者如果是无向图则在原图上），计算出到所有节点的最短距离 &lt;code&gt;dist_end&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;遍历图中的每一条边 &lt;code&gt;(u, v)&lt;/code&gt;，权重为 &lt;code&gt;w&lt;/code&gt;。如果这条边在某条最短路径上，它必须满足 &lt;code&gt;dist_start[u] + w + dist_end[v] == dist_start[end]&lt;/code&gt; (对于有向边) 或者 &lt;code&gt;dist_start[u] + w + dist_end[v] == dist_start[end]&lt;/code&gt; 或 &lt;code&gt;dist_start[v] + w + dist_end[u] == dist_start[end]&lt;/code&gt; (对于无向边)。&lt;/li&gt;
&lt;li&gt;返回所有满足条件的边。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;

using ll = long long;
const ll INF_SP = 1e18;

class Solution {
public:
    std::vector&amp;lt;bool&amp;gt; findAnswer(int n, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; edges) {
        std::vector&amp;lt;std::vector&amp;lt;std::pair&amp;lt;int, int&amp;gt;&amp;gt;&amp;gt; graph(n);
        for (const auto&amp;amp; edge : edges) {
            graph[edge[0]].push_back({edge[1], edge[2]});
            graph[edge[1]].push_back({edge[0], edge[2]});
        }

        auto dijkstra = [&amp;amp;](int start_node) {
            std::vector&amp;lt;ll&amp;gt; dist(n, INF_SP);
            dist[start_node] = 0;
            std::priority_queue&amp;lt;std::pair&amp;lt;ll, int&amp;gt;, std::vector&amp;lt;std::pair&amp;lt;ll, int&amp;gt;&amp;gt;, std::greater&amp;lt;&amp;gt;&amp;gt; pq;
            pq.push({0, start_node});
            while (!pq.empty()) {
                auto [d, u] = pq.top();
                pq.pop();
                if (d &amp;gt; dist[u]) continue;
                for (auto&amp;amp; edge : graph[u]) {
                    int v = edge.first;
                    int w = edge.second;
                    if (dist[u] + w &amp;lt; dist[v]) {
                        dist[v] = dist[u] + w;
                        pq.push({dist[v], v});
                    }
                }
            }
            return dist;
        };

        std::vector&amp;lt;ll&amp;gt; dist_start = dijkstra(0);
        std::vector&amp;lt;ll&amp;gt; dist_end = dijkstra(n - 1);

        std::vector&amp;lt;bool&amp;gt; ans(edges.size(), false);
        if (dist_start[n - 1] &amp;gt;= INF_SP) return ans;

        for (int i = 0; i &amp;lt; edges.size(); ++i) {
            int u = edges[i][0], v = edges[i][1], w = edges[i][2];
            if (dist_start[u] + w + dist_end[v] == dist_start[n - 1]) {
                ans[i] = true;
            }
            if (dist_start[v] + w + dist_end[u] == dist_start[n - 1]) {
                ans[i] = true;
            }
        }
        return ans;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Floyd-Warshall 算法&lt;/h3&gt;
&lt;p&gt;用于解决&lt;strong&gt;所有节点对之间的最短路径&lt;/strong&gt;（All-Pairs Shortest Path）问题，可以处理&lt;strong&gt;带负权边&lt;/strong&gt;的图（但不能有负权环）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 2976. 转换字符串的最小成本 I&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 题目要求计算将 &lt;code&gt;source&lt;/code&gt; 字符串转换为 &lt;code&gt;target&lt;/code&gt; 字符串的最小成本，其中字符可以相互转换，成本已知。这本质上是一个所有节点对之间的最短路径问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将 26个小写字母看作图的顶点。&lt;/li&gt;
&lt;li&gt;给定的 &lt;code&gt;original[i]&lt;/code&gt;, &lt;code&gt;changed[i]&lt;/code&gt;, &lt;code&gt;cost[i]&lt;/code&gt; 看作从 &lt;code&gt;original[i]&lt;/code&gt; 到 &lt;code&gt;changed[i]&lt;/code&gt; 的有向边，权重为 &lt;code&gt;cost[i]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;使用 Floyd-Warshall 算法计算出任意两个字符 &lt;code&gt;c1&lt;/code&gt; 和 &lt;code&gt;c2&lt;/code&gt; 之间转换的最小成本。&lt;/li&gt;
&lt;li&gt;遍历 &lt;code&gt;source&lt;/code&gt; 和 &lt;code&gt;target&lt;/code&gt; 字符串，对于每个位置 &lt;code&gt;i&lt;/code&gt;，如果 &lt;code&gt;source[i] != target[i]&lt;/code&gt;，累加从 &lt;code&gt;source[i]&lt;/code&gt; 转换到 &lt;code&gt;target[i]&lt;/code&gt; 的最小成本。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;algorithm&amp;gt;

using ll = long long;

class Solution {
public:
    long long minimumCost(std::string source, std::string target, std::vector&amp;lt;char&amp;gt;&amp;amp; original, std::vector&amp;lt;char&amp;gt;&amp;amp; changed, std::vector&amp;lt;int&amp;gt;&amp;amp; cost) {
        const ll INF = 1e12;
        std::vector&amp;lt;std::vector&amp;lt;ll&amp;gt;&amp;gt; min_cost(26, std::vector&amp;lt;ll&amp;gt;(26, INF));

        for (int i = 0; i &amp;lt; 26; ++i) min_cost[i][i] = 0;

        for (size_t i = 0; i &amp;lt; original.size(); ++i) {
            int u = original[i] - &apos;a&apos;;
            int v = changed[i] - &apos;a&apos;;
            min_cost[u][v] = std::min(min_cost[u][v], (ll)cost[i]);
        }

        // Floyd-Warshall
        for (int k = 0; k &amp;lt; 26; ++k) {
            for (int i = 0; i &amp;lt; 26; ++i) {
                for (int j = 0; j &amp;lt; 26; ++j) {
                    min_cost[i][j] = std::min(min_cost[i][j], min_cost[i][k] + min_cost[k][j]);
                }
            }
        }

        ll total_cost = 0;
        for (size_t i = 0; i &amp;lt; source.length(); ++i) {
            int u = source[i] - &apos;a&apos;;
            int v = target[i] - &apos;a&apos;;
            if (min_cost[u][v] &amp;gt;= INF) {
                return -1;
            }
            total_cost += min_cost[u][v];
        }

        return total_cost;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 2977. 转换字符串的最小成本 II&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 这个问题比 &lt;code&gt;2976&lt;/code&gt; 更复杂，因为它允许子串转换，并且转换关系可以传递（A-&amp;gt;B, B-&amp;gt;C =&amp;gt; A-&amp;gt;C）。这是一个动态规划问题，其状态转移的成本需要通过图的最短路径算法来计算。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;构建转换图&lt;/strong&gt;：将所有出现在 &lt;code&gt;original&lt;/code&gt; 和 &lt;code&gt;changed&lt;/code&gt; 中的子串作为图的节点。对于每个给定的转换 &lt;code&gt;original[i] -&amp;gt; changed[i]&lt;/code&gt;，在图中添加一条权重为 &lt;code&gt;cost[i]&lt;/code&gt; 的有向边。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算所有对最短路径&lt;/strong&gt;：在这个子串图上，使用 Floyd-Warshall 算法计算出任意两个子串节点之间转换的最小成本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DP求解&lt;/strong&gt;：定义 &lt;code&gt;dp[i]&lt;/code&gt; 为转换 &lt;code&gt;source&lt;/code&gt; 字符串的前 &lt;code&gt;i&lt;/code&gt; 个字符以匹配 &lt;code&gt;target&lt;/code&gt; 的前 &lt;code&gt;i&lt;/code&gt; 个字符所需的最小成本。
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dp[0] = 0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;状态转移：&lt;code&gt;dp[i] = min(dp[i], dp[j] + cost(source[j..i-1] -&amp;gt; target[j..i-1]))&lt;/code&gt;，其中 &lt;code&gt;j&lt;/code&gt; 从 &lt;code&gt;0&lt;/code&gt; 到 &lt;code&gt;i-1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cost(...)&lt;/code&gt; 是从步骤2中计算出的最短转换成本。如果 &lt;code&gt;source&lt;/code&gt; 和 &lt;code&gt;target&lt;/code&gt; 的子串相同，成本为0。如果无法转换，成本为无穷大。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;set&amp;gt;

using ll = long long;

class Solution {
public:
    long long minimumCost(std::string source, std::string target, std::vector&amp;lt;std::string&amp;gt;&amp;amp; original, std::vector&amp;lt;std::string&amp;gt;&amp;amp; changed, std::vector&amp;lt;int&amp;gt;&amp;amp; cost) {
        const ll INF = 1e12;
        std::map&amp;lt;std::string, int&amp;gt; node_map;
        int next_node_id = 0;

        auto get_node_id = [&amp;amp;](const std::string&amp;amp; s) {
            if (node_map.find(s) == node_map.end()) {
                node_map[s] = next_node_id++;
            }
            return node_map[s];
        };

        for (const auto&amp;amp; s : original) get_node_id(s);
        for (const auto&amp;amp; s : changed) get_node_id(s);

        int num_nodes = next_node_id;
        std::vector&amp;lt;std::vector&amp;lt;ll&amp;gt;&amp;gt; adj(num_nodes, std::vector&amp;lt;ll&amp;gt;(num_nodes, INF));
        for(int i = 0; i &amp;lt; num_nodes; ++i) adj[i][i] = 0;

        for (size_t i = 0; i &amp;lt; original.size(); ++i) {
            int u = get_node_id(original[i]);
            int v = get_node_id(changed[i]);
            adj[u][v] = std::min(adj[u][v], (ll)cost[i]);
        }

        for (int k = 0; k &amp;lt; num_nodes; ++k) {
            for (int i = 0; i &amp;lt; num_nodes; ++i) {
                for (int j = 0; j &amp;lt; num_nodes; ++j) {
                    if (adj[i][k] &amp;lt; INF &amp;amp;&amp;amp; adj[k][j] &amp;lt; INF) {
                        adj[i][j] = std::min(adj[i][j], adj[i][k] + adj[k][j]);
                    }
                }
            }
        }

        int n = source.length();
        std::vector&amp;lt;ll&amp;gt; dp(n + 1, INF);
        dp[0] = 0;

        for (int i = 0; i &amp;lt; n; ++i) {
            if (dp[i] &amp;gt;= INF) continue;
            
            // Case 1: Characters match
            if (source[i] == target[i]) {
                dp[i + 1] = std::min(dp[i + 1], dp[i]);
            }

            // Case 2: Substring conversion
            for (auto const&amp;amp; [s_orig, id_orig] : node_map) {
                int len = s_orig.length();
                if (i + len &amp;gt; n) continue;
                if (source.substr(i, len) == s_orig) {
                    for (auto const&amp;amp; [s_chg, id_chg] : node_map) {
                        if (s_chg.length() != len) continue;
                        if (target.substr(i, len) == s_chg) {
                            if (adj[id_orig][id_chg] &amp;lt; INF) {
                                dp[i + len] = std::min(dp[i + len], dp[i] + adj[id_orig][id_chg]);
                            }
                        }
                    }
                }
            }
        }

        return dp[n] &amp;gt;= INF ? -1 : dp[n];
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;最小生成树 (MST)&lt;/h2&gt;
&lt;p&gt;最小生成树是在一个连通的、带权无向图中，找到一棵包含所有顶点的树，使得树的所有边的权重之和最小。&lt;/p&gt;
&lt;h3&gt;Kruskal 算法&lt;/h3&gt;
&lt;p&gt;将所有边按权重从小到大排序，然后依次遍历边。如果一条边的两个端点不在同一个连通分量中（用并查集判断），则将这条边加入MST，并合并两个端点。&lt;/p&gt;
&lt;h3&gt;Prim 算法&lt;/h3&gt;
&lt;p&gt;从任意一个顶点开始，逐步扩展生成树。每次选择一条连接已在树中的顶点和树外顶点的、权重最小的边，并把对应的树外顶点加入树中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1584. 连接所有点的最小费用&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt; 将所有点看作图的顶点，任意两点之间的曼哈顿距离看作边的权重。问题转化为在一个完全图中寻找最小生成树。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算所有点对之间的距离，生成一个边的列表。&lt;/li&gt;
&lt;li&gt;使用 Kruskal 算法：
a.  将所有边按权重从小到大排序。
b.  初始化并查集，每个点自成一个集合。
c.  遍历排序后的边，如果边的两个端点不连通，则将该边加入MST（累加其权重），并用并查集合并两个端点。
d.  当加入的边数等于 &lt;code&gt;n-1&lt;/code&gt; 时，MST构建完成。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;numeric&amp;gt;

class DSU_MST {
private:
    std::vector&amp;lt;int&amp;gt; parent;
public:
    DSU_MST(int n) {
        parent.resize(n);
        std::iota(parent.begin(), parent.end(), 0);
    }
    int find(int i) {
        if (parent[i] == i) return i;
        return parent[i] = find(parent[i]);
    }
    bool unite(int i, int j) {
        int root_i = find(i);
        int root_j = find(j);
        if (root_i != root_j) {
            parent[root_i] = root_j;
            return true;
        }
        return false;
    }
};

struct Edge {
    int u, v, weight;
};

bool compareEdges(const Edge&amp;amp; a, const Edge&amp;amp; b) {
    return a.weight &amp;lt; b.weight;
}

class Solution {
public:
    int minCostConnectPoints(std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; points) {
        int n = points.size();
        if (n &amp;lt;= 1) return 0;
        
        std::vector&amp;lt;Edge&amp;gt; edges;
        for (int i = 0; i &amp;lt; n; ++i) {
            for (int j = i + 1; j &amp;lt; n; ++j) {
                int dist = std::abs(points[i][0] - points[j][0]) + std::abs(points[i][1] - points[j][1]);
                edges.push_back({i, j, dist});
            }
        }
        
        std::sort(edges.begin(), edges.end(), compareEdges);
        
        DSU_MST dsu(n);
        int total_cost = 0;
        int edges_count = 0;
        
        for (const auto&amp;amp; edge : edges) {
            if (dsu.unite(edge.u, edge.v)) {
                total_cost += edge.weight;
                edges_count++;
                if (edges_count == n - 1) break;
            }
        }
        
        return total_cost;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1489. 找到最小生成树里的关键边和伪关键边&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先，计算出原图的最小生成树（MST）的权重 &lt;code&gt;mst_weight&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键边&lt;/strong&gt;：一条边是关键边，如果从图中移除它后，新图的MST权重会增加（或图变得不连通）。
&lt;ul&gt;
&lt;li&gt;遍历每一条边 &lt;code&gt;e&lt;/code&gt;，暂时从图中移除它，然后计算剩余图的MST权重。如果新权重 &amp;gt; &lt;code&gt;mst_weight&lt;/code&gt;，则 &lt;code&gt;e&lt;/code&gt; 是关键边。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;伪关键边&lt;/strong&gt;：一条边是伪关键边，如果它存在于至少一个MST中，但它不是关键边。
&lt;ul&gt;
&lt;li&gt;遍历每一条边 &lt;code&gt;e&lt;/code&gt;。首先强制将 &lt;code&gt;e&lt;/code&gt; 加入生成树（通过并查集提前合并其端点），然后在此基础上运行Kruskal算法计算MST。如果得到的MST权重等于 &lt;code&gt;mst_weight&lt;/code&gt;，说明 &lt;code&gt;e&lt;/code&gt; 可以是某个MST的一部分。如果它还不是关键边，那它就是伪关键边。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;numeric&amp;gt;

class DSU_Kruskal {
public:
    std::vector&amp;lt;int&amp;gt; parent;
    int components;
    DSU_Kruskal(int n) {
        parent.resize(n);
        std::iota(parent.begin(), parent.end(), 0);
        components = n;
    }
    int find(int i) {
        if (parent[i] == i) return i;
        return parent[i] = find(parent[i]);
    }
    bool unite(int i, int j) {
        int root_i = find(i);
        int root_j = find(j);
        if (root_i != root_j) {
            parent[root_i] = root_j;
            components--;
            return true;
        }
        return false;
    }
};

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; findCriticalAndPseudoCriticalEdges(int n, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; edges) {
        for (int i = 0; i &amp;lt; edges.size(); ++i) {
            edges[i].push_back(i); // Store original index
        }
        std::sort(edges.begin(), edges.end(), [](const auto&amp;amp; a, const auto&amp;amp; b) {
            return a[2] &amp;lt; b[2];
        });

        auto get_mst_weight = [&amp;amp;](int n, const auto&amp;amp; current_edges, int excluded_idx = -1, int included_idx = -1) {
            DSU_Kruskal dsu(n);
            int weight = 0;
            int edges_count = 0;

            if (included_idx != -1) {
                weight += edges[included_idx][2];
                dsu.unite(edges[included_idx][0], edges[included_idx][1]);
                edges_count++;
            }

            for (int i = 0; i &amp;lt; current_edges.size(); ++i) {
                if (i == excluded_idx || i == included_idx) continue;
                const auto&amp;amp; edge = current_edges[i];
                if (dsu.unite(edge[0], edge[1])) {
                    weight += edge[2];
                    edges_count++;
                }
            }
            return (edges_count == n - 1) ? weight : INT_MAX;
        };

        int original_mst_weight = get_mst_weight(n, edges);
        
        std::vector&amp;lt;int&amp;gt; critical, pseudo_critical;
        for (int i = 0; i &amp;lt; edges.size(); ++i) {
            // Check for critical edge
            int weight_without = get_mst_weight(n, edges, i);
            if (weight_without &amp;gt; original_mst_weight) {
                critical.push_back(edges[i][3]);
                continue;
            }
            
            // Check for pseudo-critical edge
            int weight_with = get_mst_weight(n, edges, -1, i);
            if (weight_with == original_mst_weight) {
                pseudo_critical.push_back(edges[i][3]);
            }
        }
        
        return {critical, pseudo_critical};
    }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>模拟退火算法</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/simuannealing/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/simuannealing/</guid><pubDate>Tue, 08 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;概述&lt;/h1&gt;
&lt;p&gt;模拟退火 (simulated annealing) 算法是一种用于求解最优化问题的随机化算法。它的灵感来源于物理退火过程的启发，通过&lt;strong&gt;模拟固体加温、等温、冷却&lt;/strong&gt;等过程，结合 Metropolis 采样准则进行科学计算的一种启发式算法。模拟退火算法在解决组合优化问题、函数优化问题等方面表现出色，尤其适用于大规模复杂问题的求解。&lt;/p&gt;
&lt;p&gt;特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;相较于局部搜索算法，模拟退火算法可以在较短的时间内找到最优近似解，且不会陷入局部最优解中&lt;/li&gt;
&lt;li&gt;模拟退火算法允许任意选取初始解和随机数序列，对初始值的选取较为鲁棒&lt;/li&gt;
&lt;li&gt;模拟退火算法能应用于多种组合优化问题，如旅行商问题、背包问题等&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;算法思想&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;物理学中有原理——物质总是趋于能量最低的状态（最稳定的状态）。&lt;/strong&gt; 退火就是物质从高能态到低能态的过程。将固体加热到高温后，分子会呈现随机排列状体，然后逐步降温使之冷却，分子会逐渐趋于有序排列，从而物质达到某种稳定的低能状态。&lt;/p&gt;
&lt;p&gt;因此，不妨把函数下降求最小值的问题看作是一个物理系统的能量最小化问题。把优化的目标函数视为一个能量函数，即可通过模拟物理退火过程来求解最优化问题。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将解 $x$ 看作是物理系统的状态&lt;/li&gt;
&lt;li&gt;目标函数 $f(x)$ 即为能量函数&lt;/li&gt;
&lt;li&gt;目标函数的最优解 $x_{opt}$ 就对应于能量最低的状态&lt;/li&gt;
&lt;li&gt;设定优化的初始温度为 $T$ 的过程对应于加温过程&lt;/li&gt;
&lt;li&gt;Metropolis 采样对应于等温过程&lt;/li&gt;
&lt;li&gt;控制参数的下降对应于冷却过程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;在解空间中随机游走，使用 Metropolis 抽样准则来使解状态逐渐收敛于全局最优解。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=5&amp;gt;Algorithm Simulated Annealing&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt; Initial Temperature $T_0$, Initial state $x_0$, Objective Function $f(x)$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt; Optimal State $x_{opt}$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1: Let state $x = x_0$, optimal state $x_{opt} = x_0$ and temperature $T = T_0$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2: &lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:      &lt;strong&gt;repeat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:           Randomly generate a new neighbouring state $x_{new} = \text{Random}(x)$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:           Compute probability $\large p(x, x_{new}) = \min{e^{-\frac{f(x_{new}) - f(x)}{T}}, 1}$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:           Accept the new state at a probability of $p(x, x_{new})$, i.e. &lt;strong&gt;if&lt;/strong&gt; $\text{rand}(0, 1) &amp;lt; p(x, x_{new})$, then let $x = x_{new}$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:      &lt;strong&gt;until&lt;/strong&gt; the new state is accepted&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:      &lt;strong&gt;if&lt;/strong&gt; $f(x) &amp;lt; f(x_{opt})$, then let $x_{opt} = x$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:      Decrease the temperature $T$ according to a cooling schedule&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10: &lt;strong&gt;until&lt;/strong&gt; stopping criteria is met&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11: Return the best state $x_{opt}$ found so far&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Metropolis 采样准则&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果新解比当前解更优，则接受新解 (这也是为什么伪代码中会出现 $\min$ 和 $1$ 的原因)&lt;/li&gt;
&lt;li&gt;如果新解比当前解差，则以一定概率接受新解，概率为 $p(x, x_{new}) = e^{-\frac{f(x_{new}) - f(x)}{T}}$，其中 $T$ 是当前温度。&lt;/li&gt;
&lt;li&gt;该概率随着温度的降低而减小，最终趋近于 $0$。 体现为在高温下，可以接受与当前状态差的多的新状态，从而避免陷入局部极小值，在低温下，接受较差的新状态的概率减小，最终趋近于 $0$，即不接受新状态。&lt;/li&gt;
&lt;li&gt;物理学中的 Metropolis 采样准则是基于 Boltzmann 分布的，即粒子在温度 $T$ 时趋于热平衡的概率为
$$
p(x) = \frac{e^{-\frac{\Delta E(x)}{kT}}}{Z}
$$
     其中 $E(x)$ 是能量函数，$k$ 是玻尔兹曼常数，$Z$ 是配分函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;冷却过程&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;冷却过程是模拟退火算法的关键，决定了算法的收敛速度和最终结果的质量。&lt;/li&gt;
&lt;li&gt;冷却过程可以采用线性冷却、指数冷却、对数冷却等方式。常用的冷却方式是指数冷却，即每次迭代时将温度乘以一个小于 $1$ 的常数 $\alpha$，即 $T_{new} = \alpha T_{old}$，其中 $0 &amp;lt; \alpha &amp;lt; 1$。&lt;/li&gt;
&lt;li&gt;也可以采用线性冷却，即每次迭代时将温度减去一个常数 $C$，即 $T_{new} = T_{old} - C$，其中 $C$ 是一个小于 $T_{old}$ 的常数。&lt;/li&gt;
&lt;li&gt;冷却速率的选择会影响算法的收敛速度和最终结果的质量。冷却速率过快可能导致算法陷入局部最优解，冷却速率过慢则会导致算法收敛速度过慢。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;初始温度的选择&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始温度的选择会影响算法的收敛速度和最终结果的质量。初始温度过高可能导致算法收敛速度过快，初始温度过低则可能导致算法陷入局部最优解。&lt;/li&gt;
&lt;li&gt;初始温度的选择可以通过经验法则、试验法则等方式进行选择。常用的经验法则是将初始温度设置为目标函数值的范围，即 $T_0 = \max(f(x)) - \min(f(x))$，其中 $f(x)$ 是目标函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;def simulated_annealing(objective_function, initial_state, random_neighbor, 
                       initial_temp=100, cooling_rate=0.95, min_temp=1e-10, max_iterations=1000):
    &quot;&quot;&quot;
    模拟退火算法的通用实现
    
    参数:
    - objective_function: 目标函数，用于评估解的质量
    - initial_state: 初始解状态
    - random_neighbor: 产生随机邻居解的函数
    - initial_temp: 初始温度
    - cooling_rate: 冷却率 (0 &amp;lt; cooling_rate &amp;lt; 1)
    - min_temp: 最小温度，算法终止条件
    - max_iterations: 每个温度下的最大迭代次数
    
    返回:
    - 找到的最优解
    - 最优解对应的目标函数值
    &quot;&quot;&quot;
    current_state = initial_state
    current_energy = objective_function(current_state)
    
    best_state = current_state
    best_energy = current_energy
    
    temp = initial_temp
    
    # 存储能量变化，用于可视化
    energy_history = [current_energy]
    temp_history = [temp]
    
    # 主循环
    while temp &amp;gt; min_temp:
        for _ in range(max_iterations):
            # 生成新状态
            new_state = random_neighbor(current_state)
            new_energy = objective_function(new_state)
            
            # 计算能量变化
            delta_energy = new_energy - current_energy
            
            # Metropolis准则：接受更好的解或以一定概率接受较差的解
            if delta_energy &amp;lt; 0 or random.random() &amp;lt; math.exp(-delta_energy / temp):
                current_state = new_state
                current_energy = new_energy
                
                # 更新全局最优解
                if current_energy &amp;lt; best_energy:
                    best_state = current_state
                    best_energy = current_energy
            
            energy_history.append(current_energy)
            temp_history.append(temp)
        
        # 降温
        temp *= cooling_rate
    
    return best_state, best_energy, energy_history, temp_history

# 示例：寻找函数 f(x) = x^2 + 10*sin(x) 在区间 [-10, 10] 的最小值
def objective_function(x):
    return x**2 + 10*np.sin(x)

def random_neighbor(x, step_size=0.5):
    &quot;&quot;&quot;生成当前解附近的随机解&quot;&quot;&quot;
    return x + (random.random() - 0.5) * step_size

# 执行模拟退火算法
initial_x = random.uniform(-10, 10)
best_x, best_value, energy_history, temp_history = simulated_annealing(
    objective_function, 
    initial_x, 
    random_neighbor,
    initial_temp=100,
    cooling_rate=0.97,
    max_iterations=100
)

print(f&quot;找到的最优解 x = {best_x:.6f}&quot;)
print(f&quot;对应的函数值 f(x) = {best_value:.6f}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;实例应用&lt;/h1&gt;
&lt;h2&gt;旅行商问题 (TSP)&lt;/h2&gt;
&lt;p&gt;数学描述为，给定 $n$ 个城市和它们之间的距离 $d_{ij},\ i,j \in {1, 2, \dots, n}$，要求找到一条经过每个城市一次且仅一次的最短路径。&lt;/p&gt;
&lt;p&gt;解空间 $\mathcal{X}$ 是遍历每个城市恰好一次的回路，可以表示为 ${1, 2, \dots, n}$ 的全部循环排列。记状态为 $x = (x_1, x_2, \dots, x_n)$，其中 $x_i$ 表示第 $i$ 个城市的编号。目标函数为路径长度，即 $f(x) = d_{x_1 x_2} + d_{x_2 x_3} + \dots + d_{x_{n-1} x_n} + d_{x_n x_1}$。&lt;/p&gt;
&lt;p&gt;新解的产生可以通过&lt;strong&gt;互换操作&lt;/strong&gt;、&lt;strong&gt;插入操作&lt;/strong&gt;、&lt;strong&gt;逆序操作&lt;/strong&gt;来实现。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;互换操作：随机选择两个城市 $i$ 和 $j$，交换它们在路径中的位置。&lt;/li&gt;
&lt;li&gt;插入操作：随机选择一个城市 $i$ 和一个位置 $j$，将城市 $i$ 插入到路径中的位置 $j$。&lt;/li&gt;
&lt;li&gt;逆序操作：随机选择两个城市 $i$ 和 $j$，将它们之间的路径逆序。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;e.g. 设当前路径为 $x = (1, 2, 3, 4, 5, 6, 7, 8, 9)$，互换操作产生的新解为 $x_{new} = (1, \underline{8}, 3, 4, 5, 6, 7, \underline{2}, 9)$，插入操作产生的新解为 $x_{new} = (1, \underline{8}, 2, 3, 4, 5, 6, 7, 9)$，逆序操作产生的新解为 $x_{new} = (1, \underline{8, 7, 6, 5, 4, 3, 2}, 9)$。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import numpy as np
import random
import math
import matplotlib.pyplot as plt
from typing import List, Tuple

def generate_distance_matrix(n_cities: int, seed: int = 42) -&amp;gt; np.ndarray:
    &quot;&quot;&quot;
    生成随机城市坐标和距离矩阵
    
    参数:
    - n_cities: 城市数量
    - seed: 随机种子
    
    返回:
    - distances: 距离矩阵
    - coords: 城市坐标
    &quot;&quot;&quot;
    random.seed(seed)
    np.random.seed(seed)
    
    # 生成随机坐标
    coords = np.random.rand(n_cities, 2) * 100
    
    # 计算距离矩阵
    distances = np.zeros((n_cities, n_cities))
    for i in range(n_cities):
        for j in range(n_cities):
            if i != j:
                # 欧几里得距离
                distances[i, j] = np.sqrt(np.sum((coords[i] - coords[j])**2))
    
    return distances, coords

def calculate_path_length(path: List[int], distances: np.ndarray) -&amp;gt; float:
    &quot;&quot;&quot;
    计算路径长度
    
    参数:
    - path: 路径
    - distances: 距离矩阵
    
    返回:
    - path_length: 路径长度
    &quot;&quot;&quot;
    path_length = 0
    for i in range(len(path) - 1):
        path_length += distances[path[i], path[i+1]]
    # 加上从最后一个城市回到起点的距离
    path_length += distances[path[-1], path[0]]
    return path_length

def random_path(n_cities: int) -&amp;gt; List[int]:
    &quot;&quot;&quot;
    生成随机路径
    
    参数:
    - n_cities: 城市数量
    
    返回:
    - path: 随机路径
    &quot;&quot;&quot;
    path = list(range(n_cities))
    random.shuffle(path)
    return path

def generate_neighbor(path: List[int]) -&amp;gt; List[int]:
    &quot;&quot;&quot;
    生成邻居解（使用三种操作之一：互换、插入或逆序）
    
    参数:
    - path: 当前路径
    
    返回:
    - new_path: 新路径
    &quot;&quot;&quot;
    new_path = path.copy()
    n = len(path)
    
    # 随机选择操作类型
    operation = random.choice([&quot;swap&quot;, &quot;insert&quot;, &quot;reverse&quot;])
    
    if operation == &quot;swap&quot;:
        # 互换操作：随机选择两个城市并交换它们的位置
        i, j = random.sample(range(n), 2)
        new_path[i], new_path[j] = new_path[j], new_path[i]
    
    elif operation == &quot;insert&quot;:
        # 插入操作：随机选择一个城市，并将其移动到一个新位置
        i, j = random.sample(range(n), 2)
        city = new_path.pop(i)
        new_path.insert(j, city)
    
    else:  # operation == &quot;reverse&quot;
        # 逆序操作：随机选择两个位置，并将它们之间的路径逆序
        i, j = sorted(random.sample(range(n), 2))
        new_path[i:j+1] = reversed(new_path[i:j+1])
    
    return new_path

def simulated_annealing_tsp(distances: np.ndarray, 
                           initial_temp: float = 1000.0, 
                           cooling_rate: float = 0.995, 
                           min_temp: float = 1e-8,
                           iterations_per_temp: int = 100) -&amp;gt; Tuple[List[int], float, List[float], List[float]]:
    &quot;&quot;&quot;
    使用模拟退火算法解决旅行商问题
    
    参数:
    - distances: 城市间距离矩阵
    - initial_temp: 初始温度
    - cooling_rate: 冷却率
    - min_temp: 最小温度
    - iterations_per_temp: 每个温度下的迭代次数
    
    返回:
    - best_path: 找到的最佳路径
    - best_length: 最佳路径长度
    - length_history: 路径长度历史
    - temp_history: 温度历史
    &quot;&quot;&quot;
    n_cities = distances.shape[0]
    
    # 初始化随机路径
    current_path = random_path(n_cities)
    current_length = calculate_path_length(current_path, distances)
    
    best_path = current_path.copy()
    best_length = current_length
    
    temp = initial_temp
    
    # 存储历史数据用于可视化
    length_history = [current_length]
    temp_history = [temp]
    
    # 主循环
    while temp &amp;gt; min_temp:
        for _ in range(iterations_per_temp):
            # 生成新解
            new_path = generate_neighbor(current_path)
            new_length = calculate_path_length(new_path, distances)
            
            # 计算能量差
            delta = new_length - current_length
            
            # Metropolis准则：接受更好的解或以一定概率接受较差的解
            if delta &amp;lt; 0 or random.random() &amp;lt; math.exp(-delta / temp):
                current_path = new_path
                current_length = new_length
                
                # 更新全局最优解
                if current_length &amp;lt; best_length:
                    best_path = current_path.copy()
                    best_length = current_length
            
            length_history.append(current_length)
            temp_history.append(temp)
        
        # 降温
        temp *= cooling_rate
    
    return best_path, best_length, length_history, temp_history

def plot_tsp_solution(coords, path, title=&quot;TSP Solution&quot;):
    &quot;&quot;&quot;绘制TSP解决方案&quot;&quot;&quot;
    plt.figure(figsize=(10, 8))
    
    # 绘制城市
    plt.scatter(coords[:, 0], coords[:, 1], c=&apos;red&apos;, s=50)
    
    # 绘制路径
    for i in range(len(path)):
        j = (i + 1) % len(path)
        plt.plot([coords[path[i], 0], coords[path[j], 0]], 
                 [coords[path[i], 1], coords[path[j], 1]], &apos;b-&apos;)
    
    # 添加城市编号标签
    for i, coord in enumerate(coords):
        plt.text(coord[0] + 1, coord[1] + 1, str(i), fontsize=10)
    
    plt.title(title)
    plt.grid(True)
    plt.tight_layout()
    plt.show()

def plot_convergence(length_history, temp_history):
    &quot;&quot;&quot;绘制收敛过程&quot;&quot;&quot;
    plt.figure(figsize=(14, 6))
    
    # 绘制路径长度变化
    plt.subplot(1, 2, 1)
    plt.plot(length_history)
    plt.xlabel(&apos;迭代次数&apos;)
    plt.ylabel(&apos;路径长度&apos;)
    plt.title(&apos;路径长度随迭代变化&apos;)
    plt.grid(True)
    
    # 绘制温度变化
    plt.subplot(1, 2, 2)
    plt.plot(temp_history)
    plt.xlabel(&apos;迭代次数&apos;)
    plt.ylabel(&apos;温度&apos;)
    plt.title(&apos;温度随迭代变化&apos;)
    plt.yscale(&apos;log&apos;)
    plt.grid(True)
    
    plt.tight_layout()
    plt.show()

# 主程序
if __name__ == &quot;__main__&quot;:
    # 生成问题实例（20个城市）
    n_cities = 20
    distances, coords = generate_distance_matrix(n_cities)
    
    # 运行模拟退火算法
    initial_temp = 100.0
    cooling_rate = 0.995
    iterations_per_temp = 50
    
    print(&quot;开始求解TSP问题...&quot;)
    best_path, best_length, length_history, temp_history = simulated_annealing_tsp(
        distances, initial_temp, cooling_rate, min_temp=0.01, iterations_per_temp=iterations_per_temp
    )
    
    print(f&quot;找到的最佳路径: {best_path}&quot;)
    print(f&quot;最佳路径长度: {best_length:.2f}&quot;)
    
    # 可视化结果
    plot_tsp_solution(coords, best_path, f&quot;TSP Solution - Length: {best_length:.2f}&quot;)
    plot_convergence(length_history, temp_history)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>进化计算</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/evolcomputation/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/naturalcomputation/evolcomputation/</guid><pubDate>Tue, 08 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;概述&lt;/h1&gt;
&lt;p&gt;进化计算 (Evolutionary Computation) 是一系列启发式的搜索技术&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;遗传算法 (Genetic Algorithm, GA)&lt;/li&gt;
&lt;li&gt;遗传规划 (Genetic Programming, GP)&lt;/li&gt;
&lt;li&gt;进化策略 (Evolution Strategy, ES)&lt;/li&gt;
&lt;li&gt;进化规划 (Evolutionary Programming, EP)&lt;/li&gt;
&lt;li&gt;差分进化 (Differential Evolution, DE)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具有高鲁棒性，广泛适应性的全局优化算法。它们模拟自然选择和遗传学的原理，使用种群的概念来搜索解空间。进化计算通常用于解决复杂的优化问题，尤其是那些具有高维度、非线性或不连续性的函数。&lt;/p&gt;
&lt;h1&gt;算法思想&lt;/h1&gt;
&lt;p&gt;生物群体的生存过程遵循自然选择的原则，适者生存。进化计算模拟了这一过程，通过&lt;strong&gt;选择、交叉、变异&lt;/strong&gt;等操作来生成新的解。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;选择&lt;/strong&gt;：根据适应度函数选择优秀的个体进行繁殖。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交叉&lt;/strong&gt;：将两个或多个个体的基因组合在一起，产生新的个体。交叉操作可以是单点交叉、多点交叉或均匀交叉等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;变异&lt;/strong&gt;：对个体的基因进行随机修改，以增加种群的多样性。变异操作可以是位翻转、交换或插入等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;遗传算法&lt;/h1&gt;
&lt;p&gt;遗传算法是最早提出的进化计算方法之一，主要用于优化问题。&lt;/p&gt;
&lt;h2&gt;编码&lt;/h2&gt;
&lt;p&gt;将解空间的状态编码为位串形式的code，通常使用二进制编码、实数编码或符号编码等。&lt;/p&gt;
&lt;h3&gt;二进制编码&lt;/h3&gt;
&lt;p&gt;将解空间的每个维度用二进制位表示，适用于离散问题。若解空间的最大值为 $x_{max}$, 最小值为 $x_{min}$，则可以用 $n$ 位二进制数表示解空间中的十进制状态 $x$。
$$
x = x_{min} + \frac{x_{max} - x_{min}}{2^n - 1} \sum_{i=0}^{n-1} b_i \cdot 2^i
$$
其中 $x$ 对应的二进制数为 $b_{n-1}b_{n-2}...b_0$。&lt;/p&gt;
&lt;p&gt;对于精度为 $\delta$ 的优化问题，$n$ 的取值为 $\large n = \lceil \log_2(\frac{x_{max} - x_{min}}{\delta}) \rceil$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example: 在某个优化问题中，搜索范围为 $x \in [-1, 2]$ 精度要求为$\delta = 10^{-6}$，请问需要至少多少位二进制编码&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$$
L = \lceil \log_2(\frac{x_{max} - x_{min}}{10^{-6}}) \rceil = 21.5165 \approx 22
$$&lt;/p&gt;
&lt;h3&gt;格雷码&lt;/h3&gt;
&lt;p&gt;对于一些连续优化问题，二进制编码由于遗传算法的随即特性而使其局部搜索能力较差。为了改进这一特性，人们提出使用格雷码。&lt;/p&gt;
&lt;p&gt;首先要说明为什么二进制编码不好。不妨考虑一个简单的例子，在十进制中，&lt;code&gt;19&lt;/code&gt;和&lt;code&gt;20&lt;/code&gt;相差仅为&lt;code&gt;1&lt;/code&gt;，但是在二进制中，&lt;code&gt;19&lt;/code&gt;的二进制是&lt;code&gt;10011&lt;/code&gt;，&lt;code&gt;20&lt;/code&gt;的二进制是&lt;code&gt;10100&lt;/code&gt;，它们之间相差&lt;code&gt;3&lt;/code&gt;位。这就导致了在使用二进制的编码中，实际上如果采用每次只改变一位的方式进行变异，那么在&lt;code&gt;19&lt;/code&gt;和&lt;code&gt;20&lt;/code&gt;之间的变异就需要经过至少&lt;code&gt;3&lt;/code&gt;次变异才能到达。然而，我们的优化问题往往是针对十进制空间进行的，这种编码方式显然会导致搜索效率的降低。&lt;/p&gt;
&lt;p&gt;格雷码的特点是&lt;strong&gt;十进制中相邻的两个数只有一位不同&lt;/strong&gt;，这样在进行变异时，变异的幅度就会小很多。例如在十进制中，&lt;code&gt;19&lt;/code&gt;和&lt;code&gt;20&lt;/code&gt;的格雷码分别为&lt;code&gt;10011&lt;/code&gt;和&lt;code&gt;10010&lt;/code&gt;，它们之间只相差一位。&lt;/p&gt;
&lt;p&gt;格雷码的公式如下
$$
\begin{align*}
g_i &amp;amp;= \begin{cases} b_i &amp;amp;,i = n-1 \
b_i \oplus b_{i+1} &amp;amp;,0 \leq i &amp;lt; n-1 \end{cases}
\end{align*}
$$
其中 $g_i$ 是格雷码的第 $i$ 位，$b_i$ 是二进制编码的第 $i$ 位，$\oplus$ 表示异或操作。&lt;/p&gt;
&lt;p&gt;要从格雷码得到十进制表达，需要先把格雷码变为二进制编码，再由二进制得到十进制。&lt;/p&gt;
&lt;p&gt;$$
\begin{align*}
b_i &amp;amp;= \begin{cases} g_i &amp;amp;,i = n-1 \
b_{i+1} \oplus g_i &amp;amp;,0 \leq i &amp;lt; n-1
\end{cases}
\end{align*}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example: 把 &lt;code&gt;11&lt;/code&gt; 变为格雷码，并计算出 &lt;code&gt;1101&lt;/code&gt; 对应的十进制数&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;十进制编码 &lt;code&gt;d = 11&lt;/code&gt; ，对应的二进制编码为 &lt;code&gt;b = 1011&lt;/code&gt;， 求格雷码相当于将二进制编码自左向右求异或 （有点类似求差分的感觉），于是可以求出对应格雷码为 &lt;code&gt;g = 1110&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;格雷码为 &lt;code&gt;g = 1101&lt;/code&gt;， 先求对应二进制编码。格雷码求二进制编码类似计算前缀和，把二进制编码的前一位和格雷码中当前位异或即可求得结果。&lt;code&gt;b = 1001&lt;/code&gt;， 从而十进制编码为 &lt;code&gt;d = 9&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;assets/grayComp.jpg&quot; alt=&quot;graycodecomputation&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4位格雷码对应表&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;十进制&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;th&gt;9&lt;/th&gt;
&lt;th&gt;10&lt;/th&gt;
&lt;th&gt;11&lt;/th&gt;
&lt;th&gt;12&lt;/th&gt;
&lt;th&gt;13&lt;/th&gt;
&lt;th&gt;14&lt;/th&gt;
&lt;th&gt;15&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;二进制&lt;/td&gt;
&lt;td&gt;0000&lt;/td&gt;
&lt;td&gt;0001&lt;/td&gt;
&lt;td&gt;0010&lt;/td&gt;
&lt;td&gt;0011&lt;/td&gt;
&lt;td&gt;0100&lt;/td&gt;
&lt;td&gt;0101&lt;/td&gt;
&lt;td&gt;0110&lt;/td&gt;
&lt;td&gt;0111&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;1010&lt;/td&gt;
&lt;td&gt;1011&lt;/td&gt;
&lt;td&gt;1100&lt;/td&gt;
&lt;td&gt;1101&lt;/td&gt;
&lt;td&gt;1110&lt;/td&gt;
&lt;td&gt;1111&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;格雷码&lt;/td&gt;
&lt;td&gt;0000&lt;/td&gt;
&lt;td&gt;0001&lt;/td&gt;
&lt;td&gt;0011&lt;/td&gt;
&lt;td&gt;0010&lt;/td&gt;
&lt;td&gt;0110&lt;/td&gt;
&lt;td&gt;0111&lt;/td&gt;
&lt;td&gt;0101&lt;/td&gt;
&lt;td&gt;0100&lt;/td&gt;
&lt;td&gt;1100&lt;/td&gt;
&lt;td&gt;1101&lt;/td&gt;
&lt;td&gt;1111&lt;/td&gt;
&lt;td&gt;1110&lt;/td&gt;
&lt;td&gt;1010&lt;/td&gt;
&lt;td&gt;1011&lt;/td&gt;
&lt;td&gt;1001&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;实数编码&lt;/h3&gt;
&lt;p&gt;将解空间的每个维度直接用实数表示，适用于连续优化问题。实数编码直接使用浮点数表示解空间中的状态，个体的基因型和表现型之间没有转换过程。&lt;/p&gt;
&lt;p&gt;$$
x = (x_1, x_2, \ldots, x_n), \quad x_i \in [a_i, b_i]
$$&lt;/p&gt;
&lt;p&gt;其中 $x$ 是一个 $n$ 维向量，$x_i$ 是第 $i$ 个维度的值，$[a_i, b_i]$ 是第 $i$ 个维度的取值范围。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优势：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;表示精度更高，不受位数限制&lt;/li&gt;
&lt;li&gt;计算效率更高，避免了编码和解码过程&lt;/li&gt;
&lt;li&gt;更接近问题的自然表示&lt;/li&gt;
&lt;li&gt;便于应用特定领域的遗传算子&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;实数编码的遗传算子：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;交叉操作&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;算术交叉：$x_{new1} = \alpha x_1 + (1-\alpha)x_2$, $x_{new2} = (1-\alpha)x_1 + \alpha x_2$&lt;/li&gt;
&lt;li&gt;离散交叉：从父代基因中随机选择每个位置的值&lt;/li&gt;
&lt;li&gt;BLX-$\alpha$ 交叉：在扩展的区间内随机选择&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;变异操作&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;高斯变异：$x_{new} = x + N(0, \sigma^2)$&lt;/li&gt;
&lt;li&gt;均匀变异：在指定区间内随机替换&lt;/li&gt;
&lt;li&gt;非均匀变异：变异幅度随代数增加而减小&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;产生初始群体&lt;/h2&gt;
&lt;p&gt;采用随机方法生成若干个个体的集合，该集合称为初始种群。初始种群中个体的数量称为种群规模。种群规模越大，搜索的范围也就越广，但是每代的计算量也会增加。通常，种群规模取 $50 \sim 100$。&lt;/p&gt;
&lt;h2&gt;适应度函数&lt;/h2&gt;
&lt;p&gt;遗传算法对一个个体（也就是一个解）优劣的评价用适应度函数来表示，适应度函数值越大，解的质量越好（当然也可以反过来）。适应度函数是遗传算法进化过程的驱动力，也是自然选择的唯一标准。通常，适应度函数就设置为优化问题的目标函数。&lt;/p&gt;
&lt;h2&gt;选择操作&lt;/h2&gt;
&lt;p&gt;根据个体的适应度函数值来决定该个体的它在下一代是被淘汰还是被遗传。选择操作通常有以下几种方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;轮盘赌选择 (Roulette Wheel Selection)：根据适应度函数值的比例来选择个体，适应度函数值越高，被选择的概率越大。&lt;/li&gt;
&lt;li&gt;锦标赛选择 (Stochastic Tournament Selection)：随机选择若干个体进行比赛，适应度函数值高的个体胜出。&lt;/li&gt;
&lt;li&gt;期望值选择 (Stochastic Universal Sampling)：根据个体的适应度函数值来计算生存期望数目，生存期望数目越高，被选择的概率越大。&lt;/li&gt;
&lt;li&gt;确定性采样选择 (Deterministic Sampling)：根据个体的适应度函数值来计算生存期望数目，生存期望数目越高，被选择的概率越大。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;轮盘赌选择&lt;/h3&gt;
&lt;p&gt;将每个个体的适应度函数值计算出来，归一化成为概率值（类似 softmax 操作），然后根据概率值进行选择。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点：简单易实现，适用于大多数问题。&lt;/li&gt;
&lt;li&gt;缺点：容易导致早熟现象，即过早地收敛到局部最优解。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;计算归一化概率 $p_i = \frac{f_i}{\sum_{j=1}^{N} f_j}$，其中 $p_i$ 是第 $i$ 个个体的概率，$f_i$ 是第 $i$ 个个体的适应度函数值，$N$ 是种群规模。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设定轮盘赌区。构造一个前缀和函数 $S_0 = 0, \ S_i = \sum_{j=1}^{i} p_j$，那么区间 $[S_{i-1}, S_{i})$ 就是第 $i$ 个个体对应的区间，$i = 1, 2, \ldots, N$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在区间 $[0, 1)$ 上随机选择一个数 $r$，如果 $S_{i-1} \leq r &amp;lt; S_i$，则选择第 $i$ 个个体。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用这样一个方法，重复 $N$ 次，就可以得到 $N$ 个个体。&lt;/p&gt;
&lt;h3&gt;锦标赛选择&lt;/h3&gt;
&lt;p&gt;锦标赛选择是从种群中随机选择 $k$ 个个体进行比赛，适应度函数值最高的个体胜出（$k$ 选 1）。重复 $N$ 次，就可以得到 $N$ 个个体。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点：简单易实现，适用于大多数问题。&lt;/li&gt;
&lt;li&gt;缺点：容易导致早熟现象，即过早地收敛到局部最优解。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;期望值选择&lt;/h3&gt;
&lt;p&gt;根据每个个体在下一代群体中的生存期望值来进行随机选择运算。期望值选择的基本思想是，认为每个个体在下一代群体中的生存期望值与其适应度函数值成正比。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点：避免了早熟现象，适用于大多数问题。&lt;/li&gt;
&lt;li&gt;缺点：计算复杂度较高，适用于小规模种群。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;计算每个个体的生存期望数目 $E_i = \frac{f_i}{\sum_{j=1}^{N} f_j} \cdot N$，其中 $E_i$ 是第 $i$ 个个体的生存期望数目，$f_i$ 是第 $i$ 个个体的适应度函数值，$N$ 是种群规模。&lt;/li&gt;
&lt;li&gt;若某一个个体被选中参与交叉运算，则生存期望数目减去 $0.5$， 若某一个个体未被选中参与交叉运算，则它在下一代中的生存期望数目减去 $1$。&lt;/li&gt;
&lt;li&gt;随着选择过程的进行，若某一个个体的生存期望数小于 $0$，则它在下一代中就不再被选中。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;确定性采样选择&lt;/h3&gt;
&lt;p&gt;针对最优个体保留的不确定性，此方法按照一种确定的方式来进行选择操作。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算个体在下一代中的生存期望&lt;/li&gt;
&lt;li&gt;确定各个对应个体在下一代群体中的确定生存数目，对个体进行降序排序，顺序取前 $N$ 个个体为下一代群体。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;交叉操作&lt;/h2&gt;
&lt;p&gt;交叉是产生新个体的主要方式，从一对亲代中产生一对自带个体。通常交叉概率 $p_c \in [0.5, 1]$，交叉概率越高，产生新个体的数量就越多。交叉操作的目的是为了增加种群的多样性，决定了全局搜索能力。设群体规模为 $N$，交叉操作后产生的个体数量为 $N_c$，则有 $N_c = N \cdot p_c$, 不过一般工程上还需要把 $N_c$ 取最近的偶数。&lt;/p&gt;
&lt;p&gt;交叉操作通常有以下几种方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;单点交叉 (Single Point Crossover)：在两个父代个体中随机选择一个交叉点，将交叉点之前的部分交换，产生两个新的个体。随机性除了亲代个体的随机性外，还有微店选取的随机性。
&lt;img src=&quot;assets/pointcross.png&quot; alt=&quot;pointcross&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多点交叉 (Multi Point Crossover)：在两个父代个体中随机选择多个交叉点，将交叉点之间的部分交换，产生两个新的个体。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;均匀交叉 (Uniform Crossover)：在两个父代个体中随机选择每个基因的位置进行交换，产生两个新的个体。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;算术交叉 (Arithmetic Crossover)：在两个父代个体中随机选择一个交叉点，将交叉点之前的部分进行线性组合，产生两个新的个体。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;变异操作&lt;/h2&gt;
&lt;p&gt;变异是另一种产生新个体的方式，依据变异概率 $p_m$ 将个体编码串中的某些基因用其他基因值来替换，从而形成一个新的个体。变异操作决定了遗传算法的局部搜索能力，同时保持群体多样性。变异概率 $p_m$ 是针对单个字符而言的，即 $p_m = \frac{B}{N \cdot L}$，其中 $B$ 是变异的基因数目，$N$ 是种群规模，$L$ 是个体编码的长度。变异概率 $p_m$ 的取值范围通常为 $[0.005, 0.01]$，也可以根据具体问题进行调整。变异概率越高，产生新个体的数量就越多。&lt;/p&gt;
&lt;p&gt;注意，&lt;strong&gt;变异概率是一个敏感参数&lt;/strong&gt;，过高的变异概率会导致搜索效率降低，过低的变异概率会导致搜索陷入局部最优解。&lt;/p&gt;
&lt;p&gt;变异操作通常有以下几种方法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;位翻转变异 (Bit Flip Mutation)：对二进制编码的个体，随机选择一个基因，将其值翻转。&lt;/li&gt;
&lt;li&gt;位交换变异 (Bit Swap Mutation)：对二进制编码的个体，随机选择两个基因，将它们的值交换。&lt;/li&gt;
&lt;li&gt;位插入变异 (Bit Insert Mutation)：对二进制编码的个体，随机选择一个基因，将其值插入到另一个基因的位置。
&lt;img src=&quot;assets/mutation.png&quot; alt=&quot;mutation&quot; /&gt;&lt;/li&gt;
&lt;li&gt;高斯变异 (Gaussian Mutation)：对实数编码的个体，随机选择一个基因，将其值加上一个高斯分布的随机数。&lt;/li&gt;
&lt;li&gt;均匀变异 (Uniform Mutation)：对实数编码的个体，随机选择一个基因，将其值加上一个均匀分布的随机数。&lt;/li&gt;
&lt;li&gt;非均匀变异 (Non-uniform Mutation)：对实数编码的个体，随机选择一个基因，将其值加上一个非均匀分布的随机数。&lt;/li&gt;
&lt;li&gt;交换变异 (Swap Mutation)：对实数编码的个体，随机选择两个基因，将它们的值交换。&lt;/li&gt;
&lt;li&gt;插入变异 (Insert Mutation)：对实数编码的个体，随机选择一个基因，将其值插入到另一个基因的位置。&lt;/li&gt;
&lt;li&gt;逆转变异 (Inversion Mutation)：对实数编码的个体，随机选择两个基因，将它们之间的基因值逆转。&lt;/li&gt;
&lt;li&gt;反转变异 (Reverse Mutation)：对实数编码的个体，随机选择两个基因，将它们之间的基因值反转。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;终止条件&lt;/h2&gt;
&lt;p&gt;终止条件一般可以用&lt;strong&gt;最大迭代次数&lt;/strong&gt;、&lt;strong&gt;适应度函数值不再变化&lt;/strong&gt;、&lt;strong&gt;达到预设的精度要求&lt;/strong&gt;等条件来判断。&lt;/p&gt;
&lt;h2&gt;算法流程&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=5&amp;gt;Algorithm: 基本遗传算法&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;种群规模 $N$，染色体长度 $L$，交叉概率 $p_c$，变异概率 $p_m$，最大迭代次数 $T_{max}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;最优解 $x_{best}$ 及其适应度值 $f(x_{best})$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;初始化&lt;/strong&gt; 随机生成初始种群 $P(0) = {x_1, x_2, \ldots, x_N}$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;评估&lt;/strong&gt; 计算种群中每个个体的适应度值 $f(x_i)$，$i = 1, 2, \ldots, N$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;迭代进化&lt;/strong&gt; $t = 0$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;while&lt;/strong&gt; $t &amp;lt; T_{max}$ &lt;strong&gt;and&lt;/strong&gt; 未满足终止条件 &lt;strong&gt;do&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;选择&lt;/strong&gt; 根据个体的适应度值，使用轮盘赌或锦标赛等方法选择父代个体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;交叉&lt;/strong&gt; 按概率 $p_c$ 对选中的父代个体进行交叉操作，生成子代个体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;变异&lt;/strong&gt; 按概率 $p_m$ 对子代个体的每个基因进行变异操作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;评估&lt;/strong&gt; 计算新生成的子代个体的适应度值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9:&lt;/td&gt;
&lt;td&gt;    &lt;strong&gt;更新&lt;/strong&gt; 根据适应度值从父代和子代中选择 $N$ 个优秀个体组成新的种群 $P(t+1)$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10:&lt;/td&gt;
&lt;td&gt;    $t = t + 1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;end while&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12:&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;返回&lt;/strong&gt; 迭代过程中找到的最优个体 $x_{best}$ 及其适应度值 $f(x_{best})$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;遗传算法的理论基础&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;传统的模式理论&lt;/li&gt;
&lt;li&gt;有限状态Markov链模型&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;概述&lt;/h2&gt;
</content:encoded></item><item><title>机器人学基础 第二章 正向运动学</title><link>https://adalovelemon.github.io/posts/content/coursenotes/robotics/chapter-2/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/robotics/chapter-2/</guid><pubDate>Sun, 06 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;机械臂的正向运动学&lt;/p&gt;
&lt;h1&gt;2.1 机械臂的模型&lt;/h1&gt;
&lt;p&gt;机械臂模型通常可以简化为一系列刚体通过关节连接而成的链式结构。其中刚体被称为 &lt;strong&gt;连杆&lt;/strong&gt;，将两个连杆相连接的是&lt;strong&gt;关节&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/arm.png&quot; alt=&quot;arm&quot; /&gt;&lt;/p&gt;
&lt;p&gt;机械臂有两种结构，分别是&lt;strong&gt;开链&lt;/strong&gt;和&lt;strong&gt;闭链&lt;/strong&gt;，其中，&lt;strong&gt;开链&lt;/strong&gt;有明确的首尾，而&lt;strong&gt;闭链&lt;/strong&gt;的每一连杆都与两个及以上连杆有关节耦合&lt;/p&gt;
&lt;h1&gt;2.2 机构自由度&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;构形&lt;/strong&gt;：在任何时刻均能完全确定机器人形状的所有&lt;strong&gt;关节变量&lt;/strong&gt;值&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;机构自由度 (DoF)&lt;/strong&gt;：表达构形所需要的最少的实值关节变量的数目&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关节自由度&lt;/strong&gt;：用于描述关节所连接连杆的相对运动自由状况的数值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;常见的关节及自由度有&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;旋转关节 (Revolute joint): 1 DoF，两个连杆绕一个轴相对转动&lt;/li&gt;
&lt;li&gt;棱柱关节 (Prismatic joint): 1 DoF，两个刚体沿一直线相对平移&lt;/li&gt;
&lt;li&gt;万向节 (Universial joint): 2 DoF，可以看作两个正交的旋转关节组合而成的二自由度关节&lt;/li&gt;
&lt;li&gt;球关节 (Spherical joint): 3 DoF，允许两个连杆之间的相对自由转动&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;旋转关节&lt;/th&gt;
&lt;th&gt;棱柱关节&lt;/th&gt;
&lt;th&gt;万向节&lt;/th&gt;
&lt;th&gt;球关节&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;assets/revolute.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/prismatic.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/universal.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/spherical.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;机构的自由度可以通过 &lt;strong&gt;Grubler&lt;/strong&gt; 公式计算。设 $N$ 表示连杆的数目（&lt;strong&gt;注意要包含固定的基座&lt;/strong&gt;）， $J$ 是关节的数目，$m$ 是一个刚体的自由度（空间机构取 $6$，平面机构取 $3$），$f_i$ 表示第 $i$ 个关节提供的自由度，则可以求出机构的自由度为
$$
\text{DoF} = m(N - 1 - J) + \sum_{i=1}^J f_i
$$&lt;/p&gt;
&lt;p&gt;例如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;平面四连杆机构，$m = 3$，$N = 4$(含基座)，$J = 4$，$f_i = 1 \ \forall i$，可以求得 $\text{DoF} = 1$&lt;/li&gt;
&lt;li&gt;特殊具有重叠关节的机构， $m = 3$, $N = 8$(基座只计一次), $J = 9$ (有两个关节重叠)，$f_i = 1$，可以求得 $\text{DoF} = 3$&lt;/li&gt;
&lt;li&gt;Delta 机器人：这是一个闭链结构，上下两个平台，上平台为固定基座，同过三个臂连接下平台。每个臂通过三个旋转关节 (R) 连接上平台、一个连杆和一个平行四边形闭式链、下平台，平行四边形闭式链通过万向节 (S) 连接而成。于是 $N = 17$, $J = 21$, 9R $f_i = 1$, 12S $f_i = 2$，于是 $\text{DoF} = 3$，即下平台的末端可在空间中自由挪动&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;平面四连杆机构&lt;/th&gt;
&lt;th&gt;特殊具有重叠关节的机构&lt;/th&gt;
&lt;th&gt;Delta 机器人&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;assets/fourbarlink.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/speciallink.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/delta.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;2.3 正向运动学的目标&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;构形空间 $\mathbb{Q}$：由所有可能构形构成的取值空间，空间中任意点是一组关节变量的取值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;正向运动学的目标就是&lt;strong&gt;在给定构形的条件下（给定一组关节变量的条件下）&lt;/strong&gt;，计算求出机械臂末端的位姿情况
$$
T: \mathbb{Q} \rightarrow SE(3)
$$
其中 $T$ 可以表示机械臂的设计所形成的从&lt;strong&gt;关节变量的构形空间&lt;/strong&gt;到&lt;strong&gt;末端位姿的特殊欧几里得群（有时只考虑末端的位置坐标，此时是笛卡尔空间）&lt;/strong&gt; 的映射关系。
$$
\begin{aligned}
q &amp;amp;= [q_1, q_2, \dots, q_n]^\top \in \mathbb{Q}\
\ ^{S}&lt;em&gt;{E}T &amp;amp;= \begin{bmatrix} \ ^{S}&lt;/em&gt;{E}R(q) &amp;amp; \ ^{S}&lt;em&gt;{}p&lt;/em&gt;{Eorg}(q)\ 0 &amp;amp; 1\end{bmatrix}
\end{aligned}
$$
这里 ${E}$ 代表末端的固连框架。&lt;/p&gt;
&lt;h1&gt;2.4 机械臂的描述&lt;/h1&gt;
&lt;p&gt;这里只考虑简单模型，即使用的关节大部分是 1 DoF 的关节如旋转关节和棱柱关节。从运动学的角度，由于连杆的运动通常是绕着或沿着关节的轴方向，因此对轴的描述用&lt;strong&gt;沿轴线方向的直线&lt;/strong&gt;即可。连杆则是确定两个相邻关节相对位置关系的一个刚体。&lt;/p&gt;
&lt;p&gt;定义从固定基座开始的&lt;strong&gt;连杆编号&lt;/strong&gt;和&lt;strong&gt;关节编号&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;固定基座编为&lt;strong&gt;连杆0&lt;/strong&gt;，第一个运动连杆是&lt;strong&gt;连杆1&lt;/strong&gt;，往后由此类推&lt;/li&gt;
&lt;li&gt;连接&lt;strong&gt;连杆0&lt;/strong&gt;和&lt;strong&gt;连杆1&lt;/strong&gt;的关节是&lt;strong&gt;关节1&lt;/strong&gt;，连接&lt;strong&gt;连杆1&lt;/strong&gt;和&lt;strong&gt;连杆2&lt;/strong&gt;的关节式&lt;strong&gt;关节2&lt;/strong&gt;，由此类推&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.4.1 连杆的参量描述&lt;/h2&gt;
&lt;p&gt;一个连杆的运动学特性可以由两个参数描述&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;连杆长度 (link length) $a_{i-1}$: 两个相邻关节轴的&lt;strong&gt;公垂线&lt;/strong&gt;长度，方向由是从 &lt;strong&gt;关节轴$i-1$&lt;/strong&gt; 到 &lt;strong&gt;关节轴$i$&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;连杆扭曲角 (link twist) $\alpha_{i-1}$: 在以两个关节轴的公垂线为法线的平面上，两个关节轴投影的夹角（实际上可直接定义为两个轴线的&lt;strong&gt;异面角&lt;/strong&gt;），方向是从 &lt;strong&gt;关节轴$i-1$&lt;/strong&gt; 转到 &lt;strong&gt;关节轴$i$&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.4.2 关节的参量描述&lt;/h2&gt;
&lt;p&gt;一个关节的运动学特性也可以用两个参数描述&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;连杆偏移量 (link offset) $d_i$: 在公共关节轴上，相邻两个连杆长度公垂线之间的距离，方向从 &lt;strong&gt;连杆$i-1$的公垂线&lt;/strong&gt; 到 &lt;strong&gt;连杆$i$的公垂线&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;关节角 (joint angle) $\theta_i$: 相邻两个连杆（的长度公垂线）绕公共关节轴旋转的夹角，方向从 &lt;strong&gt;连杆$i-1$&lt;/strong&gt; 到 &lt;strong&gt;连杆$i$&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于旋转关节，$\theta_i$ 是关节变量；对于棱柱关节，$d_i$ 是关节变量&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;连杆描述&lt;/th&gt;
&lt;th&gt;关节描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;assets/link.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/joints.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;2.4.3 DH 参数&lt;/h2&gt;
&lt;p&gt;上述的四个运动学参数即是 DH 参数，描述一个运动链，只需要给出全部的四变量即可
$$
a_{i-1},\quad \alpha_{i-1},\quad d_i, \quad \theta_i
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不过由于起始的基座作为连杆0是特殊情况，不存在关节轴0，因此不考虑 $d_0$ 和 $\theta_0$&lt;/li&gt;
&lt;li&gt;如果关节1是旋转关节，则通常有 $\alpha_0$ = 0, $a_0 = 0$&lt;/li&gt;
&lt;li&gt;同样地，由于最后一根 连杆 $n$ 只有一个相连的关节，因此不存在 关节$n+1$，所以不必考虑 $a_n$ 和 $\alpha_n$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如，对于平面 $3R$ 机械臂，有如下的 DH 参数列表&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;$i$&lt;/th&gt;
&lt;th&gt;$\alpha_i$&lt;/th&gt;
&lt;th&gt;$a_i$&lt;/th&gt;
&lt;th&gt;$d_i$&lt;/th&gt;
&lt;th&gt;$\theta_i$&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;$0$&lt;/td&gt;
&lt;td&gt;$0$&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;$0$&lt;/td&gt;
&lt;td&gt;$L_1$&lt;/td&gt;
&lt;td&gt;$0$&lt;/td&gt;
&lt;td&gt;$\theta_1$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;$0$&lt;/td&gt;
&lt;td&gt;$L_2$&lt;/td&gt;
&lt;td&gt;$0$&lt;/td&gt;
&lt;td&gt;$\theta_2$&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;$0$&lt;/td&gt;
&lt;td&gt;$\theta_3$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;assets/planar3R.png&quot; alt=&quot;平面3R&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;2.4.4 机械臂连杆固连框架&lt;/h2&gt;
&lt;p&gt;为了更方便地描述机械臂的坐标运算性质，定义 连杆 $i$ 的刚体固连框架为框架 ${i}$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;框架 ${i}$ 的 Z 轴与关节轴 $i$ 轴线方向重合&lt;/li&gt;
&lt;li&gt;框架 ${i}$ 的原点位于关节轴公垂线 $a_i$ 与关节轴 $i$ 的交点处&lt;/li&gt;
&lt;li&gt;框架 ${i}$ 的 X 轴沿连杆关节轴公垂线 $a_i$ 方向（关节轴 $i$ 到 关节轴 $i+1$）&lt;/li&gt;
&lt;li&gt;框架 ${i}$ 的 Y 轴方向由右手定则确定&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意框架的选取未必唯一&lt;/strong&gt;，例如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Z 轴方向有两个可选&lt;/li&gt;
&lt;li&gt;若关节轴相交，X 轴的方向可选&lt;/li&gt;
&lt;li&gt;若关节轴平行，原点位置可选&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;assets/frames.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在定义了刚体固连框架的标准后，可以得到求解 DH 参数的一个坐标系方法
$$
\begin{aligned}
a_i &amp;amp;= \text{Proj}&lt;em&gt;{X_i}\overline{Z_i Z&lt;/em&gt;{i+1}}\quad\text{沿着框架 {i}的X轴 }，\text{框架{i}Z轴到框架{i+1}Z轴的距离} \
\alpha_i &amp;amp;= \text{Axis}&lt;em&gt;{X_i}\text{ ang}({Z_i, Z&lt;/em&gt;{i+1}})\quad\text{绕着框架 {i}的X轴 }，\text{框架{i}Z轴到框架{i+1}Z轴的角度} \
d_i &amp;amp;= \text{Proj}&lt;em&gt;{Z_i}\overline{X&lt;/em&gt;{i-1} X_{i}}\quad\text{沿着框架 {i}的Z轴 }，\text{框架{i-1}X轴到框架{i}X轴的距离} \
\theta_i &amp;amp;= \text{Axis}&lt;em&gt;{Z_i}\text{ ang}({X&lt;/em&gt;{i-1}, X_{i}})\quad\text{绕着框架 {i}的Z轴 }，\text{框架{i-1}X轴到框架{i}X轴的角度} \
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;连杆0上固连框架&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为了便于描述，定义 连杆0 的框架 ${0}$ 就是基座的固定框架 ${B}$。&lt;/li&gt;
&lt;li&gt;此外，定义框架 ${1}$ 在关节1 的变量取0时，与框架 ${0}$ 重合&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;旋转关节的框架 ${1}$ 与 ${0}$&lt;/th&gt;
&lt;th&gt;棱柱关节的框架 ${1}$ 与 ${0}$&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src=&quot;assets/frame-R.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&quot;assets/frame-P.png&quot; alt=&quot;&quot; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;2.5 连杆变换&lt;/h1&gt;
&lt;h2&gt;2.5.1 齐次变换矩阵&lt;/h2&gt;
&lt;p&gt;相邻连杆固连框架之间的齐次变换 $^{i-1}_{i}T$ 可以看作由如下过程依次执行后得到的齐次变换&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将框架 ${i-1}$ 绕其 X 轴旋转 $\alpha_i$&lt;/li&gt;
&lt;li&gt;然后将新框架沿其X轴平移 $a_i$&lt;/li&gt;
&lt;li&gt;然后再将新框架绕其 Z 轴旋转 $\theta_i$&lt;/li&gt;
&lt;li&gt;再将新框架沿其 Z 轴平移 $d_i$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从而得到框架 ${i}$。基于上述过程，可以得到齐次变换矩阵为
$$
\begin{aligned}
\ ^{i-1}&lt;em&gt;{i}T &amp;amp;= R&lt;/em&gt;{X}(\alpha_{i-1}) D_{X}(a_{i-1}) R_{Z}(\theta_i) D_{Z}(d_i)\
&amp;amp;= \begin{bmatrix} 1 &amp;amp; 0 &amp;amp; 0 &amp;amp; a_{i-1}\ 0 &amp;amp; \cos\alpha_{i-1} &amp;amp; -\sin\alpha_{i-1} &amp;amp; 0\ 0 &amp;amp; \sin\alpha_{i-1} &amp;amp; \cos\alpha_{i-1} &amp;amp; 0 \ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1  \end{bmatrix} \begin{bmatrix} \cos\theta_{i} &amp;amp; -\sin\theta_{i} &amp;amp;0 &amp;amp;0\ \sin\theta_{i} &amp;amp; \cos\theta_{i} &amp;amp;0 &amp;amp;0\ 0 &amp;amp;0 &amp;amp;1 &amp;amp;d_i\ 0 &amp;amp;0 &amp;amp;0 &amp;amp;1\end{bmatrix} \
&amp;amp;= \begin{bmatrix}\cos\theta_{i} &amp;amp; -\sin\theta_{i} &amp;amp;0 &amp;amp;a_{i-1}\
\sin\theta_{i}\cos\alpha_{i-1} &amp;amp; \cos\theta_{i}\cos\alpha_{i-1} &amp;amp;-\sin\alpha_{i-1} &amp;amp;-\sin\alpha_{i-1}d_i\
\sin\theta_{i}\sin\alpha_{i-1} &amp;amp; \cos\theta_{i}\sin\alpha_{i-1} &amp;amp;\cos\alpha_{i-1} &amp;amp;\cos\alpha_{i-1}d_i\
0 &amp;amp;0 &amp;amp;0 &amp;amp; 1
\end{bmatrix}
\end{aligned}
$$&lt;/p&gt;
&lt;h2&gt;2.5.2 框架约束关系&lt;/h2&gt;
&lt;p&gt;需要注意的是，这样设定的框架关系是要满足一定的约束关系的&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;垂线关系：框架 ${i}$ 的 Z 轴必须与 框架 ${i-1}$ 的 X 轴垂直
$$
Z_i \cdot X_{i-1} = 0
$$&lt;/li&gt;
&lt;li&gt;原点关系：框架 ${i}$ 的原点 $O_i$ 必须位于 框架 ${i-1}$ 的X轴 $X_{i-1}$ 所在直线上，即
$$
O_{i-1} = a_{i-1} X_{i-1} + d_i Z_i
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.5.3 构形到末端位姿的映射&lt;/h2&gt;
&lt;p&gt;利用上述求得的齐次变换矩阵 $\ ^{i-1}&lt;em&gt;{i}T$，可以求得最后一个框架位姿为
$$
\ ^{0}&lt;/em&gt;{n}T(q) = \ ^{0}&lt;em&gt;{1}T(q_1) \ ^{1}&lt;/em&gt;{2}T(q_2) \dots \ ^{n-1}&lt;em&gt;{n}T(q_n)
$$
其中，$q_i = \begin{cases}\theta_i, &amp;amp;\text{revolute}\ d_i, &amp;amp;\text{prismatic} \end{cases}$ 是关节$i$的变量。引入最后一个框架 ${n}$ 到 末端框架 ${E}$ 的齐次变换 $\ ^{n}&lt;/em&gt;{E}T$，得到
$$
\ ^{S}&lt;em&gt;{E}T = \ ^{S}&lt;/em&gt;{0}T \ ^{0}&lt;em&gt;{n}T \ ^{n}&lt;/em&gt;{E}T
$$
这里空间框架 ${S}$ 常作为框架 ${0}$.&lt;/p&gt;
</content:encoded></item><item><title>强化学习 Chapter 1 - 基本定义和基础概念</title><link>https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter1/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/reinforcementlearning/basicrl/chapter1/</guid><pubDate>Sun, 06 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Markov决策过程（MDP）是强化学习的基础模型，是一种通过与环境交互从而学习一个策略，实现最大化累积奖励的理论框架。MDP的数学模型由状态空间、动作空间、转移概率和奖励函数组成。时至今日，MDP已经成为强化学习的标准模型，被广泛应用于各个领域，包括机器人控制、游戏AI和自动驾驶等。&lt;/p&gt;
&lt;h1&gt;1.1 基本定义&lt;/h1&gt;
&lt;h2&gt;1.1.1 智能体、环境与策略&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;智能体 (agent)&lt;/strong&gt;: 通过与环境交互来学习和优化策略的实体。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环境 (environment)&lt;/strong&gt;: 智能体所处的外部世界，智能体通过与环境交互来获取信息和奖励。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态 (state)&lt;/strong&gt; $s_t$: 环境在某一时刻的具体情况。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动作 (action)&lt;/strong&gt; $a_t$: 智能体在某一状态下可以采取的操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;策略 (policy)&lt;/strong&gt; $\pi(a|s)$: 智能体在某一状态下选择动作的概率分布，通常用一个函数表示。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;奖励 (reward)&lt;/strong&gt; $r(s, a)$: 智能体在某一状态下采取某一动作后，环境反馈给智能体的即时奖励。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具体来说，在每个时刻 $t$，智能体会和环境进行交互，即通过观察当前所在的环境状态 $s_t$，选择一个动作 $a_t$，得到环境对于状态 $s_t$ 和动作 $a_t$ 的奖励 $r_t$，之后智能体会转移到下一个状态 $s_{t+1}$。&lt;/p&gt;
&lt;h2&gt;1.1.2 轨迹、动作选择与状态转移&lt;/h2&gt;
&lt;p&gt;上述过程逐步迭代进行，可以得到一个轨迹 $\tau = (s_0, a_0, r_0, s_1, a_1, r_1, \dots)$，其中 $s_t$ 是在时刻 $t$ 的状态，$a_t$ 是在时刻 $t$ 采取的动作，$r_t$ 是在时刻 $t$ 得到的奖励。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;轨迹 (trajectory)&lt;/strong&gt; $\tau$: 智能体与环境交互的序列，包括状态、动作和奖励等信息，表示为 $\tau = (s_0, a_0, r_0, s_1, a_1, r_1, \dots)$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于在每个时刻 $t$，智能体都有可能有多个动作可供选择，而且每个动作导致的状态转移和奖励也可能不同，因此在每个时刻 $t$，智能体的动作选择、状态转移和奖励都可以用随机过程来表示。为了便于分析，整个决策过程使用 Markov 决策过程（MDP）来建模。 MDP的 Markov性体现在以下几个方面&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;状态转移概率&lt;/strong&gt; $p(s_{t+1} | s_t, a_t)$: 在时刻 $t$，智能体在状态 $s_t$ 下采取动作 $a_t$ 后，转移到下一个状态 $s_{t+1}$ 的概率，这里的历史依赖性被简化为只依赖于当前状态和动作。此外，在通常情况下，我们认为环境是定常的而非时变的，此时动作转移概率也可以是定常的。那么动作转移概率可以去掉时间下标 $t$，表示为 $p(s&apos;|s, a)$，表示在状态 $s$ 下采取动作 $a$ 后转移到状态 $s&apos;$ 的概率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动作选择概率&lt;/strong&gt; $p(a_t | s_t) = \pi(a_t|s_t)$: 在状态 $s_t$ 下，智能体选择动作 $a_t$ 的概率分布，也就是策略函数。策略可以是确定性的，也可以是随机的。动作的选择不依赖于历史状态和动作，只依赖于当前状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;奖励概率&lt;/strong&gt; $p(r_t | s_t, a_t)$: 在状态 $s_t$ 下，智能体采取动作 $a_t$ 后，环境反馈给智能体的奖励。奖励是一个随机变量，表示为 $r_t \sim p(r_t | s_t, a_t)$。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;需要注意的是，状态转移概率和奖励概率本质是环境的模型，而非智能体的属性。只有策略也就是动作选择概率是智能体的属性。&lt;/strong&gt; 换言之，只要状态转移概率和奖励概率确定，一个环境模型也就确定了；而一个动作策略函数确定，一个智能体也就确定了。&lt;/p&gt;
&lt;h2&gt;1.1.3 分幕式任务&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;终止状态 (terminal state)&lt;/strong&gt;: 智能体与环境交互的过程中的一个特殊状态，表示任务的结束。终止状态通常是一个吸收状态，智能体在达到终止状态后，不会再进行任何动作和奖励的反馈。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;非终止状态 (non-terminal state)&lt;/strong&gt;: 智能体与环境交互的过程中的普通状态，表示任务的继续进行。非终止状态通常是一个非吸收状态，智能体在达到非终止状态后，可以继续进行动作和奖励的反馈。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;幕 (episode)&lt;/strong&gt;: 智能体与环境交互的完整过程，从初始状态到&lt;strong&gt;终止状态&lt;/strong&gt;（每幕的最后一个状态必须是终结状态）的序列，表示为 $\tau = (s_0, a_0, r_0, s_1, a_1, r_1, \dots, s_T)$，其中 $T$ 是终止时刻。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分幕式任务 (episodic task)&lt;/strong&gt;: 智能体与环境交互的任务是有限的，智能体在每个幕中都要完成一个特定的目标。分幕式任务通常有一个明确的终止状态，智能体在达到终止状态后，任务结束。不同幕之间是独立的，下一幕的初始状态与上一幕的终止状态无关。例如，下象棋，打掼蛋等游戏，每一局游戏就是单独的一幕。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;非分幕式任务 (continuing task)&lt;/strong&gt;: 智能体与环境交互的任务是无限的，智能体在每个时刻都要完成一个特定的目标。非分幕式任务通常没有明确的终止状态，智能体在每个时刻都要继续与环境交互。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.1.4 回报与价值函数&lt;/h2&gt;
&lt;p&gt;MDP 的目标是最大化智能体在整个决策过程中的累积奖励。累积奖励可以用回报（return）来表示，回报是从当前时刻开始的未来奖励的加权和。回报的计算方式通常是将未来的奖励进行折扣处理，以便更好地平衡当前奖励和未来奖励的重要性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;折扣因子&lt;/strong&gt; $\gamma$: 用于平衡当前奖励和未来奖励的重要性，通常取值在 $[0, 1]$ 之间。$\gamma$ 越大，表示未来奖励越重要，智能体也会相应地更加目光长远；$\gamma$ 越小，表示当前奖励越重要，智能体也会相应地更加目光短浅。折扣因子 $\gamma$ 的取值通常是一个小于 1 的正数，表示未来奖励的衰减程度。$\gamma = 0$ 表示只考虑当前奖励，$\gamma = 1$ 表示考虑所有未来奖励。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回报 (return)&lt;/strong&gt; $G_t$: 从时刻 $t$ 开始的回报，通常表示为
$$
\begin{align*}
G_t &amp;amp;= \sum_{l=0}^{\infty} \gamma^l r_{t + l}\
&amp;amp;= r_t + \gamma r_{t + 1} + \gamma^2 r_{t + 2} + \dots \
&amp;amp;= r_t + \gamma G_{t + 1} \
\end{align*}
$$
其中 $r_{t + l}$ 是在时刻 $t + l$ 的奖励。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于分幕式任务，我们可以把终止状态后续的奖励视为 0，且智能体在原状态保持不变（这只是为了统一公式，便于把分幕式任务看作是持续性任务，不过把下面的表达式也可以视作对分幕式任务的回报定义），此时回报表示为从初始状态开始，到终止状态结束的折扣奖励和，表示为
$$
\begin{align*}
G_t &amp;amp;= \sum_{l=0}^{T - t} \gamma^l r_{t + l}\
&amp;amp;= r_t + \gamma r_{t + 1} + \gamma^2 r_{t + 2} + \dots + \gamma^{T - t} r_T \
&amp;amp;= r_t + \gamma G_{t + 1} \
\end{align*}
$$
其中 $T$ 是终止时刻。&lt;/p&gt;
&lt;p&gt;很显然，这些奖励都是随机变量，因此回报 $G_t$ 也是一个随机变量。为了便于分析，我们需要使用期望来分析回报。不过，从不同的初始状态出发，智能体得到的回报通常是不同的，所以我们会使用价值函数来描述这种不同状态或状态-动作下的期望回报。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;状态价值函数&lt;/strong&gt; $V_\pi(s)$: 在状态 $s_t = s$ 下，智能体在策略 $\pi$ 下的预期回报（关于 $s_t = s$ 的条件期望），表示为&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;$$
V_\pi(s) = \mathbb{E}&lt;em&gt;\pi[G_t | s_t = s] = \mathbb{E}&lt;/em&gt;{a_t \sim \pi(a_t | s) , s_{t+1} \sim p(s_{t+1} | s, a_t), r_t \sim p(r_t | s, a_t), a_{t+1} \sim \pi(a_{t+1} | s_{t+1}), \dots} [\sum_{l=0}^{\infty}\gamma^l r_{t + l}]
$$
上式对任意的状态 $s$ 均满足，因为在 $t$ 时刻，智能体可能处于状态空间中的任意一个状态。此外，此式对任意时刻 $t$ 均满足，如果环境模型是非时变的。下标 $\pi$ 是为了体现这个期望是与策略 $\pi$ 有关的，策略不同，得到的期望可能不同。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;动作价值函数&lt;/strong&gt; $Q_\pi(s, a)$: 在状态 $s_t = s$ 下采取动作 $a_t = a$ 后，智能体在策略 $\pi$ 下的预期回报，表示为
$$
Q_\pi(s, a) = \mathbb{E}&lt;em&gt;\pi[G_t | s_t = s, a_t = a] = \mathbb{E}&lt;/em&gt;{s_{t+1} \sim p(s_{t+1} | s, a), r_t \sim p(r_t | s, a), a_{t+1} \sim \pi(a_{t+1} | s_{t+1}), \dots} [\sum_{l=0}^{\infty}\gamma^l r_{t + l}]
$$
上式对任意的状态 $s$ 和动作 $a$ 均满足，因为在 $t$ 时刻，智能体可能处于状态空间中的任意一个状态，并且可能采取动作空间中的任意一个动作。同样地，此式对任意时刻 $t$ 均满足，如果环境模型是非时变的。下标 $\pi$ 是为了体现这个期望是与策略 $\pi$ 有关的，策略不同，得到的期望可能不同。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;优势函数&lt;/strong&gt; $A_\pi(s_t, a_t)$: 在状态 $s_t$ 下，采取动作 $a_t$ 的优势程度，表示为
$$
A_\pi(s, a) = Q_\pi(s, a) - V_\pi(s)
$$
优势函数的作用是用来衡量某个动作相对于其他动作的优势程度。优势函数可以用来指导智能体在选择动作时，优先选择那些具有较高优势的动作，从而提高学习效率。优势函数的值越大，表示该动作相对于其他动作的优势程度越高，智能体在选择动作时应该优先考虑该动作。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是因为状态价值函数是动作价值函数关于动作的期望，即
$$
V_\pi(s) = \sum_{a \in \mathcal{A}(s)} \pi(a|s) Q_\pi(s, a)
$$
其中 $\mathcal{A}(s)$ 是状态 $s$ 的动作空间。于是优势函数可以看作零均值化的动作价值函数。&lt;/p&gt;
&lt;p&gt;需要注意的是，&lt;strong&gt;当环境模型和智能体策略确定时，状态价值函数 $V_\pi(s)$ 和 动作价值函数 $Q_\pi(s, a)$ 是确定的&lt;/strong&gt;，体现出价值函数是策略的函数，这点可以从定义中得到。价值函数会满足 Bellman 期望方程，通过求解方程可以得到价值函数的唯一解，从而说明价值函数是确定的。&lt;/p&gt;
&lt;h1&gt;1.2 Bellman期望方程&lt;/h1&gt;
&lt;p&gt;Bellman期望方程（Bellman方程）是强化学习中的一个重要概念，它描述了在给定策略下，价值函数在不同时刻的值之间的关系。Bellman方程可以用来递归地计算状态价值函数和动作价值函数，从而实现动态规划。&lt;/p&gt;
&lt;p&gt;在给定策略 $\pi$ 下，状态价值函数的Bellman期望方程为
$$
V_\pi(s) = \mathbb{E}&lt;em&gt;\pi[r_t + \gamma V&lt;/em&gt;\pi(s_{t+1}) | s_t = s] = \mathbb{E}&lt;em&gt;{a_t \sim \pi(a_t | s), s&lt;/em&gt;{t+1} \sim p(s_{t+1} | s, a_t), r_t \sim p(r_t | s, a_t)} [r_t + \gamma V_\pi(s_{t+1})]
$$&lt;/p&gt;
&lt;p&gt;其展开形式为
$$
V_\pi(s) = \sum_{a \in \mathcal{A}(s)} \pi(a|s) \left[ \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) \gamma V_\pi(s&apos;)  + \sum_{r \in \mathcal{R}} p(r|s, a) r \right]
$$
其中 $s&apos;$ 是 $s_{t+1}$ 的可能取值，$a$ 是 $a_t$ 的可能取值。&lt;/p&gt;
&lt;p&gt;在给定策略 $\pi$ 下，动作价值函数的Bellman期望方程为
$$
Q_\pi(s, a) = \mathbb{E}&lt;em&gt;\pi[r_t + \gamma V&lt;/em&gt;\pi(s_{t+1}) | s_t = s, a_t = a] = \mathbb{E}&lt;em&gt;{s&lt;/em&gt;{t+1} \sim p(s_{t+1} | s, a), r_t \sim p(r_t | s, a)} [r(s, a) + \gamma V_\pi(s_{t+1})]
$$&lt;/p&gt;
&lt;p&gt;其展开形式为
$$
Q_\pi(s, a) =  \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) \gamma V_\pi(s&apos;)  + \sum_{r \in \mathcal{R}} p(r|s, a) r
$$
其中 $s&apos;$ 是 $s_{t+1}$ 的可能取值。&lt;/p&gt;
&lt;p&gt;关于 Bellman 方程的推导过程及解存在的证明，可以参考 &lt;a href=&quot;https://github.com/MathFoundationRL/Book-Mathematical-Foundation-of-Reinforcement-Learning/blob/main/Book-all-in-one.pdf&quot;&gt;西湖大学赵世钰老师所出版的强化学习的数学原理的 P22-P33&lt;/a&gt;。&lt;/p&gt;
&lt;h1&gt;1.3 Bellman最优方程&lt;/h1&gt;
&lt;p&gt;既然强化学习的目标是最大化累积奖励，那么我们就需要找到一个最优策略 $\pi^*$，使得在所有可能的策略中，智能体在每个状态下的预期回报最大。那如何评价一个策略比另一个策略好呢？基于前面所讨论的价值函数是策略的函数这一个点，我们可以使用价值函数来进行比较。&lt;/p&gt;
&lt;p&gt;如果一个策略 $\pi_1$ 在所有状态下的价值函数都大于另一个策略 $\pi_2$ 的价值函数，即
$$
V_{\pi_1}(s) \geq V_{\pi_2}(s), \forall s \in \mathcal{S}
$$
那么我们就可以说策略 $\pi_1$ 优于策略 $\pi_2$。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;最优策略 (optimal policy)&lt;/strong&gt; $\pi^*$: 在所有可能的策略中，能够使得智能体在每个状态下的预期回报最大的策略。最优策略是强化学习的目标。最优策略往往不止一个，但是它们的共性是对应的价值函数是相同的，即最优价值函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最优状态价值函数 (optimal state value function)&lt;/strong&gt; $V^&lt;em&gt;(s)$: 在状态 $s$ 下，智能体在最优策略 $\pi^&lt;/em&gt;$ 下的预期回报，表示为
$$
V^*(s) = \max_\pi V_\pi(s) = \max_\pi \mathbb{E}&lt;em&gt;\pi[G_t | s_t=s] = \max&lt;/em&gt;\pi \mathbb{E}&lt;em&gt;{a_t \sim \pi(a_t | s), s&lt;/em&gt;{t+1} \sim p(s_{t+1} | s, a_t), r_t \sim p(r_t | s, a_t), a_{t+1} \sim \pi(a_{t+1} | s_{t+1}), \dots} [\sum_{l=0}^{\infty}\gamma^l r_{t + l}]
$$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最优动作价值函数 (optimal action value function)&lt;/strong&gt; $Q^&lt;em&gt;(s, a)$: 在状态 $s$ 下采取动作 $a$ 后，智能体在最优策略 $\pi^&lt;/em&gt;$ 下的预期回报，表示为
$$
Q^*(s, a) = \max_\pi Q_\pi(s, a) = \max_\pi \mathbb{E}&lt;em&gt;\pi[G_t | s_t=s, a_t=a] = \max&lt;/em&gt;\pi \mathbb{E}&lt;em&gt;{s&lt;/em&gt;{t+1} \sim p(s_{t+1} | s, a), r_t \sim p(r_t | s, a), a_{t+1} \sim \pi(a_{t+1} | s_{t+1}), \dots} [\sum_{l=0}^{\infty}\gamma^l r_{t + l}]
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bellman最优方程就是求解最优策略的方程，它描述了在最优策略下，价值函数自身的关系。可以说，所有的强化学习算法都是在求解这个方程，因为求解 Bellman 最优方程，即可求得最优价值函数，利用最优价值函数，我们就可以得到最优策略。&lt;/p&gt;
&lt;p&gt;利用以上对最优策略和最有价值函数的定义，可以推导出如下方程
$$
\begin{align*}
V^&lt;em&gt;(s) &amp;amp;= \max_\pi V_\pi(s) \
&amp;amp;= \max_\pi \mathbb{E}&lt;em&gt;\pi[r + \gamma V&lt;/em&gt;\pi(s&apos;) | s]\
&amp;amp;= \max_\pi\mathbb{E}&lt;em&gt;{a \sim \pi(a|s)}\left[\mathbb{E}&lt;/em&gt;{r \sim p(r | s, a)} [r] + \gamma \mathbb{E}&lt;em&gt;{s&apos; \sim p(s&apos; | s, a)} [V&lt;/em&gt;\pi(s&apos;)]\right] \
&amp;amp;= \max_\pi \sum_{a \in \mathcal{A}(s)} \pi(a|s) \left[ \sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma\sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V_\pi(s&apos;) \right] \
&amp;amp;= \max_\pi \sum_{a \in \mathcal{A}(s)} \pi(a|s) \left[ \sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma\sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V^&lt;/em&gt;(s&apos;) \right]
\end{align*}
$$
注意在最后一步推导中，我们将 $V_\pi(s&apos;)$ 替换为 $V^&lt;em&gt;(s&apos;)$，这是因为如果目标是最大化 $V_\pi(s)$，那么根据 $V^&lt;/em&gt;(s&apos;) \ge V_\pi(s&apos;)$, 且环境模型与 $s&apos;$ 状态下的状态价值函数没有关系，而且动作 $a$ 和状态 $s$ 此时已经确定，于是有
$$
\sum_{r\in \mathcal{R}} p(r|s, a) r + \gamma\sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V^&lt;em&gt;(s&apos;) \ge \sum_{r\in \mathcal{R}} p(r|s, a) r + \gamma\sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V_\pi(s&apos;)
$$
而且，$V^&lt;/em&gt;(s&apos;)$也是诸多被优化的价值函数中的一个，是方程右侧可以取得的，所以我们可以将 $V_\pi(s&apos;)$ 替换为 $V^&lt;em&gt;(s&apos;)$。得到的方程就是状态价值函数的 Bellman 最优方程
$$
V^&lt;/em&gt;(s) = \max_{\pi} \sum_{a \in \mathcal{A(s)}} \pi(a|s) \left[ \sum_{r \in \mathcal{R}} p(r|s, a) r + \gamma \sum_{s&apos; \in \mathcal{S}} p(s&apos;|s, a) V^*(s&apos;) \right] \quad \forall s \in \mathcal{S}
$$&lt;/p&gt;
</content:encoded></item><item><title>位运算</title><link>https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/bitwiseop/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/algorithms/computertalgorithms/bitwiseop/</guid><pubDate>Thu, 03 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;位运算系列，记录一些模板题&lt;/p&gt;
&lt;h2&gt;位运算加法&lt;/h2&gt;
&lt;p&gt;位运算加法是指仅仅使用位运算符来实现加法操作，实现位运算加法只需要使用按位与、按位异或和位移运算符。
类似十进制的加法，我们可以将两个数相加分为两步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算进位：使用按位与运算符&lt;code&gt;&amp;amp;&lt;/code&gt;，然后将结果左移一位。&lt;/li&gt;
&lt;li&gt;计算和：使用按位异或运算符&lt;code&gt;^&lt;/code&gt;，数学符号表示为$\oplus$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;即
$$
\text{a + b} = (a \oplus b) + (a\ &amp;amp;\ b) &amp;lt;&amp;lt; 1
$$
换个角度看，$a \oplus b$ 与 $(a\ &amp;amp;\ b) &amp;lt;&amp;lt; 1 = 2(a\ &amp;amp;\ b)$相加就相当于是新的两个数相加，因此我们把两数相加转换成了新的两个数相加。不过，由于新的加数 $(a \ &amp;amp;\ b) &amp;lt;&amp;lt; 1$ 是由进位得来的，而进位是有限的，所以我们只需要不断地进行这个过程，直到进位为0为止即可完成等价转换，从而完成计算。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int add(int a, int b) {
    while (b != 0) {
        int carry = a &amp;amp; b; // 计算进位
        a = a ^ b; // 计算和
        b = carry &amp;lt;&amp;lt; 1; // 将进位左移一位
    }
    return a;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem 最靓数&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;定义一个数$B$为集合$A = {A_i}_{i=1}^N$的最靓数，如果它满足对于集合$A$中的所有数，有$A_i \oplus B = A_i + B$，则称$B$为集合$A$的最靓数。现在请找到对于给定的集合$A$，它的最小正数最靓数$B$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分析：&lt;/strong&gt; 将加法拆开，得
$$
A + B = A \oplus B + 2(A\ &amp;amp;\ B) = A \oplus B
$$
即有
$$
A\ &amp;amp;\ B = 0
$$
说明只要找到对于集合$A$中所有数按位与均为0数即为最靓数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

int main() {
    int N;
    scanf(&quot;%d&quot;, &amp;amp;N);
    int pos = 0;
    int mask = 0;   // mask用于记录查看对于所有的数Ai, 是否某位上有数是有1存在的
    for (int i = 0; i &amp;lt; N; i++) {
        int Ai;
        scanf(&quot;%d&quot;, &amp;amp;Ai);
        mask |= Ai; // 计算按位或
    }

    while (mask) {
        mask &amp;gt;&amp;gt;= 1; // 右移一位
        pos++; // 计算二进制位数
    }
    int ans = 1 &amp;lt;&amp;lt; pos; // 计算最靓数
    printf(&quot;%d\n&quot;, ans);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;位运算除法&lt;/h2&gt;
&lt;p&gt;位运算除法是指使用位运算来模拟除法操作，主要利用左移（&lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;）和减法。其核心思想类似于二进制的竖式除法。我们可以通过不断将除数左移，来快速逼近被除数，从而加速计算过程。&lt;/p&gt;
&lt;p&gt;例如，要计算 &lt;code&gt;dividend / divisor&lt;/code&gt;，我们可以找到最大的 &lt;code&gt;k&lt;/code&gt; 使得 &lt;code&gt;divisor &amp;lt;&amp;lt; k &amp;lt;= dividend&lt;/code&gt;。这意味着结果中包含 &lt;code&gt;1 &amp;lt;&amp;lt; k&lt;/code&gt;。然后，我们将 &lt;code&gt;dividend&lt;/code&gt; 减去 &lt;code&gt;divisor &amp;lt;&amp;lt; k&lt;/code&gt;，并对余下的部分重复此过程，直到 &lt;code&gt;dividend &amp;lt; divisor&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;为了处理负数，我们可以先将被除数和除数都转换为正数，并记录下最终结果的符号。需要特别注意整数溢出的边界情况，尤其是当被除数为 &lt;code&gt;INT_MIN&lt;/code&gt; 时。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 29. 两数相除&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定两个整数，被除数 &lt;code&gt;dividend&lt;/code&gt; 和除数 &lt;code&gt;divisor&lt;/code&gt;。将两数相除，要求不使用乘法、除法和 mod 运算符。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分析：&lt;/strong&gt;
根据上述原理，我们可以将被除数和除数都转为负数（或正数）进行计算，这样可以避免 &lt;code&gt;INT_MIN&lt;/code&gt; 转为正数时溢出的问题。这里我们选择转为负数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    int divide(int dividend, int divisor) {
        if (dividend == INT_MIN &amp;amp;&amp;amp; divisor == -1) {
            return INT_MAX;
        }
        
        long dvd = labs(dividend), dvs = labs(divisor), ans = 0;
        int sign = (dividend &amp;gt; 0) ^ (divisor &amp;gt; 0) ? -1 : 1;
        
        while (dvd &amp;gt;= dvs) {
            long temp = dvs, m = 1;
            while (temp &amp;lt;&amp;lt; 1 &amp;lt;= dvd) {
                temp &amp;lt;&amp;lt;= 1;
                m &amp;lt;&amp;lt;= 1;
            }
            dvd -= temp;
            ans += m;
        }
        
        return sign * ans;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;枚举子集&lt;/h2&gt;
&lt;p&gt;位运算是用来枚举子集的强大工具。对于一个包含 &lt;code&gt;n&lt;/code&gt; 个元素的集合，总共有 $2^n$ 个子集。我们可以用一个 &lt;code&gt;n&lt;/code&gt; 位的二进制数来表示一个子集，其中第 &lt;code&gt;i&lt;/code&gt; 位为 &lt;code&gt;1&lt;/code&gt; 表示集合中第 &lt;code&gt;i&lt;/code&gt; 个元素被选中，为 &lt;code&gt;0&lt;/code&gt; 则表示未被选中。&lt;/p&gt;
&lt;p&gt;通过从 &lt;code&gt;0&lt;/code&gt;（二进制 &lt;code&gt;00...0&lt;/code&gt;）到 $2^n - 1$（二进制 &lt;code&gt;11...1&lt;/code&gt;）进行遍历，我们就可以生成所有可能的子集。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 78. 子集&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给你一个整数数组 &lt;code&gt;nums&lt;/code&gt; ，数组中的元素 &lt;strong&gt;互不相同&lt;/strong&gt; 。返回该数组所有可能的子集（幂集）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分析：&lt;/strong&gt;
数组 &lt;code&gt;nums&lt;/code&gt; 的长度为 &lt;code&gt;n&lt;/code&gt;。我们可以遍历从 &lt;code&gt;0&lt;/code&gt; 到 $2^n - 1$ 的所有整数 &lt;code&gt;i&lt;/code&gt;。对于每个 &lt;code&gt;i&lt;/code&gt;，我们检查它的二进制表示。如果 &lt;code&gt;i&lt;/code&gt; 的第 &lt;code&gt;j&lt;/code&gt; 位是 &lt;code&gt;1&lt;/code&gt;，我们就将 &lt;code&gt;nums[j]&lt;/code&gt; 添加到当前子集中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;cmath&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; subsets(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        int n = nums.size();
        int num_subsets = 1 &amp;lt;&amp;lt; n; // 2^n
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; result;

        for (int i = 0; i &amp;lt; num_subsets; ++i) {
            std::vector&amp;lt;int&amp;gt; subset;
            for (int j = 0; j &amp;lt; n; ++j) {
                // 检查 i 的第 j 位是否为 1
                if ((i &amp;gt;&amp;gt; j) &amp;amp; 1) {
                    subset.push_back(nums[j]);
                }
            }
            result.push_back(subset);
        }
        return result;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 90. 子集 II&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给你一个整数数组 &lt;code&gt;nums&lt;/code&gt; ，其中可能包含重复元素，请你返回该数组所有可能的子集（幂集）。解集 &lt;strong&gt;不能&lt;/strong&gt; 包含重复的子集。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分析：&lt;/strong&gt;
当存在重复元素时，直接使用位运算枚举会导致重复子集。例如，对于 &lt;code&gt;[1, 2, 2]&lt;/code&gt;，&lt;code&gt;{1, 2}&lt;/code&gt; 会被生成两次。为了解决这个问题，我们可以先对数组排序。然后，在枚举时增加一个约束：如果 &lt;code&gt;nums[j]&lt;/code&gt; 与 &lt;code&gt;nums[j-1]&lt;/code&gt; 相同，并且 &lt;code&gt;nums[j-1]&lt;/code&gt; 未被选择（即 &lt;code&gt;i&lt;/code&gt; 的第 &lt;code&gt;j-1&lt;/code&gt; 位为 &lt;code&gt;0&lt;/code&gt;），那么我们跳过 &lt;code&gt;nums[j]&lt;/code&gt;。这确保了对于重复元素，我们只在它们的前一个重复元素被选中的情况下才考虑当前元素，从而避免了重复。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;cmath&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; subsetsWithDup(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        std::sort(nums.begin(), nums.end());
        int n = nums.size();
        int num_subsets = 1 &amp;lt;&amp;lt; n;
        std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; result;

        for (int i = 0; i &amp;lt; num_subsets; ++i) {
            std::vector&amp;lt;int&amp;gt; subset;
            bool skip = false;
            for (int j = 0; j &amp;lt; n; ++j) {
                // 检查 i 的第 j 位是否为 1
                if ((i &amp;gt;&amp;gt; j) &amp;amp; 1) {
                    // 如果当前元素和前一个元素相同，但前一个元素未被选择，则跳过
                    if (j &amp;gt; 0 &amp;amp;&amp;amp; nums[j] == nums[j - 1] &amp;amp;&amp;amp; !((i &amp;gt;&amp;gt; (j - 1)) &amp;amp; 1)) {
                        skip = true;
                        break;
                    }
                    subset.push_back(nums[j]);
                }
            }
            if (!skip) {
                result.push_back(subset);
            }
        }
        return result;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;按位与和公共前缀&lt;/h2&gt;
&lt;p&gt;当计算一个连续范围 &lt;code&gt;[left, right]&lt;/code&gt; 内所有数字的按位与时，结果实际上是这个范围内所有数字二进制表示的“公共前缀”。任何在 &lt;code&gt;left&lt;/code&gt; 和 &lt;code&gt;right&lt;/code&gt; 的二进制表示中不同的位，在范围内的某个数字中都会经历从 &lt;code&gt;0&lt;/code&gt; 到 &lt;code&gt;1&lt;/code&gt; 或从 &lt;code&gt;1&lt;/code&gt; 到 &lt;code&gt;0&lt;/code&gt; 的变化，这导致按位与操作后该位变为 &lt;code&gt;0&lt;/code&gt;。因此，我们只需要找到 &lt;code&gt;left&lt;/code&gt; 和 &lt;code&gt;right&lt;/code&gt; 的二进制公共前缀。&lt;/p&gt;
&lt;p&gt;一个简单的方法是不断将 &lt;code&gt;left&lt;/code&gt; 和 &lt;code&gt;right&lt;/code&gt; 右移，直到它们相等。此时得到的值就是它们的公共前缀。记录下右移的次数，然后将结果左移相同的次数，即可得到最终答案。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 201. 数字范围按位与&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给你两个整数 &lt;code&gt;left&lt;/code&gt; 和 &lt;code&gt;right&lt;/code&gt; ，表示区间 &lt;code&gt;[left, right]&lt;/code&gt; ，返回此区间内所有数字 &lt;strong&gt;按位与&lt;/strong&gt; 的结果（包含 &lt;code&gt;left&lt;/code&gt; 、 &lt;code&gt;right&lt;/code&gt; 端点）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分析：&lt;/strong&gt;
如上所述，我们寻找 &lt;code&gt;left&lt;/code&gt; 和 &lt;code&gt;right&lt;/code&gt; 的二进制公共前缀。通过不断右移 &lt;code&gt;left&lt;/code&gt; 和 &lt;code&gt;right&lt;/code&gt; 直到它们相等，我们就能找到这个前缀。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
public:
    int rangeBitwiseAnd(int left, int right) {
        int shift = 0;
        // 找到 left 和 right 的公共前缀
        while (left &amp;lt; right) {
            left &amp;gt;&amp;gt;= 1;
            right &amp;gt;&amp;gt;= 1;
            shift++;
        }
        // 将公共前缀左移，得到结果
        return left &amp;lt;&amp;lt; shift;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;按位统计汉明距离&lt;/h2&gt;
&lt;p&gt;汉明距离是指两个等长字符串（或二进制数）之间对应位置上不同字符（或位）的数量。要计算一个数组中所有数对的汉明距离总和，逐对比较的效率很低。&lt;/p&gt;
&lt;p&gt;一个更高效的方法是按位独立计算。对于整数的每一个二进制位（例如，从第 0 位到第 31 位），我们统计数组中有多少个数在该位上是 &lt;code&gt;1&lt;/code&gt;（记为 &lt;code&gt;k&lt;/code&gt;），以及多少个数是 &lt;code&gt;0&lt;/code&gt;（记为 &lt;code&gt;n-k&lt;/code&gt;，其中 &lt;code&gt;n&lt;/code&gt; 是数组长度）。在这一位上，&lt;code&gt;k&lt;/code&gt; 个 &lt;code&gt;1&lt;/code&gt; 和 &lt;code&gt;n-k&lt;/code&gt; 个 &lt;code&gt;0&lt;/code&gt; 之间可以组成 &lt;code&gt;k * (n-k)&lt;/code&gt; 个数对，它们在这一位上的值不同。因此，这一位对汉明距离总和的贡献就是 &lt;code&gt;k * (n-k)&lt;/code&gt;。将所有位的贡献相加，即可得到最终结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 477. 汉明距离总和&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给定一个整数数组 &lt;code&gt;nums&lt;/code&gt;，计算并返回 &lt;code&gt;nums&lt;/code&gt; 中所有元素之间汉明距离的总和。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分析：&lt;/strong&gt;
我们遍历从 0 到 31 的每一位。对于每一位 &lt;code&gt;i&lt;/code&gt;，我们统计 &lt;code&gt;nums&lt;/code&gt; 中所有数字在该位上为 &lt;code&gt;1&lt;/code&gt; 的数量 &lt;code&gt;count&lt;/code&gt;。然后，根据公式 &lt;code&gt;count * (n - count)&lt;/code&gt; 计算该位对总距离的贡献，并累加到最终结果中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;

class Solution {
public:
    int totalHammingDistance(std::vector&amp;lt;int&amp;gt;&amp;amp; nums) {
        int n = nums.size();
        int total_distance = 0;
        for (int i = 0; i &amp;lt; 32; ++i) {
            int count = 0;
            for (int num : nums) {
                if ((num &amp;gt;&amp;gt; i) &amp;amp; 1) {
                    count++;
                }
            }
            total_distance += count * (n - count);
        }
        return total_distance;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;状态压缩动态规划&lt;/h2&gt;
&lt;p&gt;状态压缩（State Compression）是一种利用位运算来表示和处理状态的动态规划技巧，常用于解决组合优化问题，特别是当状态的维度较小（通常不超过 20）时。我们可以用一个整数的二进制位来紧凑地表示一个集合或一个排列的状态。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;Example Problem Leetcode 1494. 并行课程 II&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;给你一个整数 &lt;code&gt;n&lt;/code&gt; 表示某所大学的课程数，从 &lt;code&gt;1&lt;/code&gt; 到 &lt;code&gt;n&lt;/code&gt; ，以及一个数组 &lt;code&gt;dependencies&lt;/code&gt; ，其中 &lt;code&gt;dependencies[i] = [xi, yi]&lt;/code&gt; 表示课程 &lt;code&gt;xi&lt;/code&gt; 必须在课程 &lt;code&gt;yi&lt;/code&gt; 之前完成。同时给你一个整数 &lt;code&gt;k&lt;/code&gt;。在一个学期中，你 &lt;strong&gt;最多&lt;/strong&gt; 可以同时修 &lt;code&gt;k&lt;/code&gt; 门课程。请你返回完成所有课程所需要的 &lt;strong&gt;最少&lt;/strong&gt; 学期数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分析：&lt;/strong&gt;
这是一个典型的状态压缩 DP 问题。我们可以用一个 &lt;code&gt;n&lt;/code&gt; 位的二进制数 &lt;code&gt;mask&lt;/code&gt; 来表示课程的完成状态，&lt;code&gt;dp[mask]&lt;/code&gt; 表示完成 &lt;code&gt;mask&lt;/code&gt; 所代表的课程集合所需的最少学期数。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;状态定义&lt;/strong&gt;：&lt;code&gt;dp[mask]&lt;/code&gt; 是完成 &lt;code&gt;mask&lt;/code&gt; 状态所需的最少学期数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;预处理&lt;/strong&gt;：&lt;code&gt;prereq[i]&lt;/code&gt; 是一个位掩码，表示课程 &lt;code&gt;i&lt;/code&gt; 的所有先修课程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态转移&lt;/strong&gt;：从当前状态 &lt;code&gt;mask&lt;/code&gt;，我们找出所有可以上的课（即先修课都已完成）。在这些可选课程中，我们选择不超过 &lt;code&gt;k&lt;/code&gt; 门来上，形成下一个状态 &lt;code&gt;next_mask&lt;/code&gt;。状态转移方程为 &lt;code&gt;dp[next_mask] = min(dp[next_mask], dp[mask] + 1)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化&lt;/strong&gt;：为了避免重复计算组合，我们只考虑可选课程的子集进行转移。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

class Solution {
public:
    int minNumberOfSemesters(int n, std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt;&amp;amp; dependencies, int k) {
        std::vector&amp;lt;int&amp;gt; prereq(n, 0);
        for (const auto&amp;amp; dep : dependencies) {
            prereq[dep[1] - 1] |= 1 &amp;lt;&amp;lt; (dep[0] - 1);
        }

        std::vector&amp;lt;int&amp;gt; dp(1 &amp;lt;&amp;lt; n, n + 1);
        dp[0] = 0;

        for (int mask = 0; mask &amp;lt; (1 &amp;lt;&amp;lt; n); ++mask) {
            if (dp[mask] &amp;gt; n) continue;

            int can_take = 0;
            for (int i = 0; i &amp;lt; n; ++i) {
                // 如果课程 i 未上过，并且其先修课都已完成
                if (!((mask &amp;gt;&amp;gt; i) &amp;amp; 1) &amp;amp;&amp;amp; (mask &amp;amp; prereq[i]) == prereq[i]) {
                    can_take |= 1 &amp;lt;&amp;lt; i;
                }
            }

            // 枚举 can_take 的所有子集
            for (int submask = can_take; submask &amp;gt; 0; submask = (submask - 1) &amp;amp; can_take) {
                if (__builtin_popcount(submask) &amp;lt;= k) {
                    int next_mask = mask | submask;
                    dp[next_mask] = std::min(dp[next_mask], dp[mask] + 1);
                }
            }
        }

        return dp[(1 &amp;lt;&amp;lt; n) - 1];
    }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>机器人学基础 第一章 刚体运动</title><link>https://adalovelemon.github.io/posts/content/coursenotes/robotics/chapter-1/</link><guid isPermaLink="true">https://adalovelemon.github.io/posts/content/coursenotes/robotics/chapter-1/</guid><pubDate>Fri, 28 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;机械臂是由一个个的刚体连接而成。为了分析机器人的运动，首先需要一个刚体的运动学模型。通过将一个参考框架固连在刚体上，就可以用描述这个框架位姿的 $4 \times 4$ 矩阵来描述刚体的运动。&lt;/p&gt;
&lt;h1&gt;1.1 位姿&lt;/h1&gt;
&lt;h2&gt;1.1.1 刚体固连框架&lt;/h2&gt;
&lt;p&gt;在质点运动学中，为了描述质点的运动需要定义一个参考的固定坐标系。同样地，在刚体运动学中也需要定义一个&lt;strong&gt;固定的空间参考框架&lt;/strong&gt;（框架的含义其实就是坐标系）。定义这个框架为 ${S}$, 作为空间中的参考坐标系。&lt;/p&gt;
&lt;p&gt;定义&lt;strong&gt;与刚体固连的框架&lt;/strong&gt;为 ${B}$, 作为刚体位姿描述的参考框架。根据几何学中的框架变换公式，刚体固连框架 ${B}$ 相对于空间参考框架 ${S}$ 的位姿可以用一个 $4 \times 4$ 矩阵来描述
$$
\ ^{S}&lt;em&gt;{B}T = \begin{bmatrix}
\ ^{S}&lt;/em&gt;{B} R &amp;amp; \ ^{S} p_{Borg} \
0 &amp;amp; 1
\end{bmatrix}
$$
其中 $\ ^{S}&lt;em&gt;{B} R$ 是 $3 \times 3$ 的旋转矩阵，描述了刚体固连框架 ${B}$ 相对于空间参考框架 ${S}$ 的朝向旋转关系。$\ ^{S} p&lt;/em&gt;{Borg}$ 是 $3 \times 1$ 的列向量，描述了刚体固连框架 ${B}$ 相对于空间参考框架 ${S}$ 的平移关系。&lt;/p&gt;
&lt;p&gt;这里的具体的变换关系是，假设有一个与空间参考框架 ${S}$ 重合的框架，此框架先通过一个旋转矩阵 $\ ^{S}&lt;em&gt;{B} R$ 旋转到一个特定的朝向，然后此框架的原点再根据向量 $\ ^{S} p&lt;/em&gt;{Borg}$ 平移到一个位置，通过上述两次变换得到的这个新框架就是刚体固连框架 ${B}$。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/body-frame.png&quot; alt=&quot;frame&quot; /&gt;&lt;/p&gt;
&lt;p&gt;需要注意的是，这里框架的旋转与普通质点的旋转不同。形象化的表述可以是框架的三个坐标轴同时绕一个过空间参考框架 ${S}$ 的旋转轴做相同角度大小的旋转，从而得到新的三个坐标轴。&lt;/p&gt;
&lt;h2&gt;1.1.2 齐次坐标变换&lt;/h2&gt;
&lt;p&gt;上文说到，刚体的绝对位姿可以用框架之间的旋转加平移得到，这个旋转加平移的数学描述采用了 $4 \times 4$ 的形式（其实刚体的位姿采用旋转矩阵 $\ ^{S}&lt;em&gt;{B}R$ 和 平移向量 $\ ^{S}p&lt;/em&gt;{Borg}$ 描述也是可以的，采用 $4 \times 4$ 矩阵是因为有更好的运算性质）。在其他学科如计算机视觉中，这类 $4 \times 4$ 矩阵有一个特殊的名称，叫&lt;strong&gt;齐次变换矩阵&lt;/strong&gt;，“齐次”的含义源于对坐标的变换是齐次形式的。&lt;/p&gt;
&lt;p&gt;设空间中有两个参考坐标系 ${A}$ 和 ${B}$，两个参考坐标系之间的相对位置关系为$\ ^{A}&lt;em&gt;{B} R$ 和 $\ ^{A}p&lt;/em&gt;{Borg}$。假设空间中还有第三个点 $q$，它在 ${A}$ 系下的坐标描述为 $\ ^{A}&lt;em&gt;{}q$，在 ${B}$ 系中的坐标描述为 $\ ^{B}&lt;/em&gt;{}q$，则有变换关系为
$$
\ ^{A}&lt;em&gt;{}q = \ ^{A}&lt;/em&gt;{B} R \ ^{B}&lt;em&gt;{}q + \ ^{A}&lt;/em&gt;{}p_{Borg}
$$
这个公式就是在&lt;strong&gt;不同参考坐标系下坐标的变换公式&lt;/strong&gt;。注意不同的参考坐标系的坐标轴单位矢量是不同的，因此这个公式是&lt;strong&gt;包含了线性变换&lt;/strong&gt;的，而&lt;strong&gt;不是单纯的物理相对位矢变换&lt;/strong&gt;。纯粹的&lt;strong&gt;物理相对位矢变换&lt;/strong&gt;是纯矢量的计算，不涉及向量坐标分解的运算，其公式为
$$
\ ^{A}&lt;em&gt;{}\vec{q} = \ ^{A}&lt;/em&gt;{}\overrightarrow{p_{Borg}} + \ ^{B}_{}\vec{q}
$$&lt;/p&gt;
&lt;p&gt;对坐标变换公式进行改写，使用点 $q$ 的齐次坐标 $\bar{q} = \begin{bmatrix}q \ 1\end{bmatrix}$，并且令 $\ ^{A}&lt;em&gt;{B}T = \begin{bmatrix}\ ^{A}&lt;/em&gt;{B} R &amp;amp; \ ^{A}&lt;em&gt;{}p&lt;/em&gt;{Borg}\ 0 &amp;amp; 1 \end{bmatrix}$，则有
$$
\ ^{A}&lt;em&gt;{}\bar{q} = \ ^{A}&lt;/em&gt;{B}T \ ^{B}_{}\bar{q}
$$
这个矩阵的形式与描述刚体位姿的 $4 \times 4$ 矩阵形式一致。&lt;/p&gt;
&lt;h2&gt;1.1.3 坐标系切换&lt;/h2&gt;
&lt;h3&gt;点的坐标切换&lt;/h3&gt;
&lt;p&gt;根据上面的推导，不难发现描述刚体位姿的 $4 \times 4$ 矩阵其实是一个齐次变换矩阵，更确切地说，是描述从空间坐标系 ${S}$ 下描述的坐标到 刚体坐标系 ${B}$ 下坐标描述的变换，即对于空间中的点 $q$，它在空间坐标系 ${S}$ 和 刚体坐标系 ${B}$ 下的坐标之间的关系为
$$
\ ^{S}&lt;em&gt;{}\bar{q} = \ ^{S}&lt;/em&gt;{B}T \ ^{B}_{}\bar{q}
$$
这个公式具有重要价值。试想假设我需要规划机械臂上的某一个点的运动，那就在机械臂上装一个摄像头或者传感器，可以得到在机械臂的某个连杆的坐标系下的这个点的坐标。但是很显然规划在空间绝对坐标系中进行会更加方便，因此，通过刚体位姿的描述矩阵可以将这个点的坐标变换到空间坐标系中，用于算法的规划。&lt;/p&gt;
&lt;h3&gt;刚体位姿切换&lt;/h3&gt;
&lt;p&gt;刚体的位姿不同于点的坐标，空间中点有 3 个自由度，而刚体有 6 个自由度，刚体比点多出的那三个自由度正是&lt;strong&gt;刚体具有方向性&lt;/strong&gt;，旋转带来的 3 个自由度。如前文所述，描述刚体的位姿用的也是 $4 \times 4$ 矩阵 $\ ^{S}_{B}T$，那么是否存在类似于点的坐标切换的齐次变换公式呢？&lt;/p&gt;
&lt;p&gt;答案是肯定的。首先，利用性质
$$
\ ^{S}&lt;em&gt;{B} R = \begin{bmatrix} \ ^{S}&lt;/em&gt;{}\mathbf{\hat{x}}&lt;em&gt;B &amp;amp; \ ^{S}&lt;/em&gt;{}\mathbf{\hat{y}}&lt;em&gt;B &amp;amp; \ ^{S}&lt;/em&gt;{}\mathbf{\hat{z}}&lt;em&gt;B \end{bmatrix}
$$
其中, $\ ^{S}&lt;/em&gt;{}\mathbf{\hat{x}}&lt;em&gt;B, \ ^{S}&lt;/em&gt;{}\mathbf{\hat{y}}&lt;em&gt;B, \ ^{S}&lt;/em&gt;{}\mathbf{\hat{z}}_B$ 分别是沿刚体固连框架 ${B}$ 坐标轴的三个单位向量（注意是&lt;strong&gt;三个自由单位向量&lt;/strong&gt;，内积为1，因此在变换中不用考虑平移变换）在空间绝对框架 ${S}$ 中的表示。因此，对刚体的位姿在不同框架下的描述变换可以用对这三个点的坐标的变换来实现。&lt;/p&gt;
&lt;p&gt;假设空间中有另外一个绝对参考框架 ${S&apos;}$，有绝对参考框架之间的变换关系
$$
\ ^{S}&lt;em&gt;{S&apos;}T = \begin{bmatrix} \ ^{S}&lt;/em&gt;{S&apos;} R &amp;amp;\ ^{S}&lt;em&gt;{}p&lt;/em&gt;{S&apos;org} \ 0 &amp;amp; 1\end{bmatrix}
$$
那么有，
$$
\begin{bmatrix} \ ^{S}&lt;em&gt;{}\mathbf{\hat{x}}&lt;em&gt;B &amp;amp; \ ^{S}&lt;/em&gt;{}\mathbf{\hat{y}}&lt;em&gt;B &amp;amp; \ ^{S}&lt;/em&gt;{}\mathbf{\hat{z}}&lt;em&gt;B \end{bmatrix} = \ ^{S}&lt;/em&gt;{S&apos;} R \begin{bmatrix} \ ^{S&apos;}&lt;/em&gt;{}\mathbf{\hat{x}}&lt;em&gt;B &amp;amp; \ ^{S&apos;}&lt;/em&gt;{}\mathbf{\hat{y}}&lt;em&gt;B &amp;amp; \ ^{S&apos;}&lt;/em&gt;{}\mathbf{\hat{z}}&lt;em&gt;B \end{bmatrix}
$$
即
$$
\ ^{S}&lt;/em&gt;{B} R = \ ^{S}&lt;em&gt;{S&apos;}R \ ^{S&apos;}&lt;/em&gt;{B}R
$$&lt;/p&gt;
&lt;p&gt;现在再考虑刚体固连框架 ${B}$ 的坐标原点的变换情况，有
$$
\ ^{S}&lt;em&gt;{}p&lt;/em&gt;{Borg} = \ ^{S}&lt;em&gt;{S&apos;}R \ ^{S&apos;}&lt;/em&gt;{} p_{Borg} + \ ^{S}&lt;em&gt;{}p&lt;/em&gt;{S&apos;org}
$$&lt;/p&gt;
&lt;p&gt;综合以上式子，可以得到
$$
\begin{aligned}
\begin{bmatrix} \ ^{S}&lt;em&gt;{B} R &amp;amp;\ ^{S}&lt;/em&gt;{}p_{Borg} \ 0 &amp;amp; 1\end{bmatrix} =
\begin{bmatrix} \ ^{S}&lt;em&gt;{S&apos;} R &amp;amp;\ ^{S}&lt;/em&gt;{}p_{S&apos;org} \ 0 &amp;amp; 1\end{bmatrix}\begin{bmatrix} \ ^{S&apos;}&lt;em&gt;{B} R &amp;amp;\ ^{S&apos;}&lt;/em&gt;{}p_{Borg} \ 0 &amp;amp; 1\end{bmatrix}
\end{aligned}
$$
即
$$
\ ^{S}&lt;em&gt;{B} T = \ ^{S}&lt;/em&gt;{S&apos;} T \ ^{S&apos;}_{B}T
$$
这个式子和点的坐标齐次变换相似，是在不同框架下刚体位姿的变换。&lt;/p&gt;
&lt;h1&gt;1.2 速度&lt;/h1&gt;
&lt;p&gt;得到了刚体的位姿描述和变换公式后，接下的问题就是研究速度的表达和变换公式了。&lt;/p&gt;
&lt;h2&gt;1.2.1 刚体速度的表示&lt;/h2&gt;
&lt;p&gt;物理学中，速度定义为位矢对是时间的一阶导数。在坐标系的描述下，质点的速度可以分解为沿坐标轴的三个速度分量。对于刚体而言，刚体的位姿是用 $4 \times 4$ 矩阵描述的，其中旋转矩阵 $\ ^{S}&lt;em&gt;{B} R$ 和平移分量（也就是框架的原点坐标）$\ ^{S}&lt;/em&gt;{} p_{Borg}$ 可能是关于时间的变量，具有导数。&lt;/p&gt;
&lt;p&gt;需要注意的是，旋转矩阵 $\ ^{S}_{B}R$ 并不简单的属于 $\mathbb{R}^{3\times 3}$ 矩阵集合，更准确的描述是旋转矩阵属于&lt;strong&gt;特殊正交群&lt;/strong&gt; $SO(3)$，对旋转矩阵的加法不具有封闭性，因此对导数运算也不具有封闭性（因为导数涉及到减法操作）。不过旋转矩阵依然是可以对时间求导的，为了更好的描述导数，定义与导数相关的切空间为李代数 $so(3)$，这是一个由反对称矩阵 $A^\top = -A$ 所构成的集合。&lt;/p&gt;
&lt;h2&gt;1.2.2 旋转矩阵对时间的导数&lt;/h2&gt;
&lt;p&gt;利用 $\ ^{S}&lt;em&gt;{B}R \ ^{S}&lt;/em&gt;{B}R^\top = 1$, 对时间求导，有
$$
\ ^{S}&lt;em&gt;{B}\dot{R}\ ^{S}&lt;/em&gt;{B}R^\top + \ ^{S}&lt;em&gt;{B}R \ ^{S}&lt;/em&gt;{B}\dot{R}^\top = 0
$$
整理得，
$$
\ ^{S}&lt;em&gt;{B}\dot{R} \ ^{S}&lt;/em&gt;{B}R^\top  = - (\ ^{S}&lt;em&gt;{B}\dot{R} \ ^{S}&lt;/em&gt;{B}R^\top )^\top
$$
定义
$$
[\ ^{S}&lt;em&gt;{}\Omega_B] = \ ^{S}&lt;/em&gt;{B}\dot{R} \ ^{S}&lt;em&gt;{B}R^\top
$$
则
$$
\ ^{S}&lt;/em&gt;{B}\dot{R} = [\ ^{S}&lt;em&gt;{}\Omega_B]\ ^{S}&lt;/em&gt;{B}R
$$&lt;/p&gt;
&lt;p&gt;现在，通过导数的原始定义来推导出 $[\ ^{S}&lt;em&gt;{}\Omega_B]$ 的物理含义。
$$
\begin{aligned}
\ ^{S}&lt;/em&gt;{B}\dot{R} &amp;amp;= \lim_{\Delta t\rightarrow 0} \frac{R(t + \Delta t) - R(t)}{\Delta t} = \lim_{\Delta t \rightarrow 0}\frac{R_{\hat{\omega}}(\Delta \theta(t))\ R(t) - R(t)}{\Delta t}\
&amp;amp;= \lim_{\Delta t \rightarrow 0}\frac{R_{\hat{\omega}}(\Delta\theta(t)) - I}{\Delta t}R(t) \
&amp;amp;= \left(\lim_{\Delta t \rightarrow 0}\frac{1}{\Delta t}\begin{bmatrix} 0 &amp;amp; -\hat\omega_z\Delta\theta &amp;amp; \hat\omega_y\Delta\theta \ \hat\omega_z\Delta\theta &amp;amp; 0 &amp;amp; -\hat\omega_x\Delta\theta \ -\hat\omega_y\Delta\theta &amp;amp; \hat\omega_x\Delta\theta &amp;amp; 0 \end{bmatrix}\right)R(t)\
&amp;amp;= \begin{bmatrix} 0 &amp;amp; -\hat\omega_z\dot{\theta} &amp;amp; \hat\omega_y\dot{\theta} \ \hat\omega_z\dot{\theta} &amp;amp; 0 &amp;amp; -\hat\omega_x\dot{\theta} \ -\hat\omega_y\dot{\theta} &amp;amp; \hat\omega_x\dot{\theta} &amp;amp; 0 \end{bmatrix}R(t)\
&amp;amp;= [\hat\omega\dot{\theta}]R(t)
\end{aligned}
$$
注意这个推到中运用到了如下性质&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;乘积公式 $R(t + \Delta t) = R_{\hat\omega}(\Delta \theta) R(t)$，其中 $\hat\omega$ 是旋转轴方向的单位向量，$\Delta\theta$ 是旋转角度。&lt;/li&gt;
&lt;li&gt;Rodrigues 公式 $R_{\hat\omega}(\Delta \theta) = I + \sin(\Delta\theta)[\hat\omega] + (1 - \cos(\Delta\theta))[\hat\omega]^2$，其中 $[\hat\omega]$ 是 $\hat\omega$ 的反对称矩阵形式。可以得到
$$
R_{\hat\omega}(\Delta \theta) = \begin{bmatrix} 1 &amp;amp; -\hat\omega_z\Delta\theta &amp;amp; \hat\omega_y\Delta\theta \ \hat\omega_z\Delta\theta &amp;amp; 1 &amp;amp; -\hat\omega_x\Delta\theta \ -\hat\omega_y\Delta\theta &amp;amp; \hat\omega_x\Delta\theta &amp;amp; 1 \end{bmatrix}
$$
利用上述的公式，可以得到
$$
\ ^{S}&lt;em&gt;{B} R = [\hat\omega_B \dot{\theta}] \ ^{S}&lt;/em&gt;{B} R = [\ ^{S}&lt;em&gt;{}\Omega_B]\ ^{S}&lt;/em&gt;{B}R
$$
从而，得到 $[\ ^{S}&lt;em&gt;{}\Omega_B] = [\hat\omega_B \dot\theta]$，即
$$
\ ^{S}&lt;/em&gt;{} \Omega_B = \hat\omega_B \dot{\theta}
$$
物理含义是 $\ ^{S}&lt;em&gt;{}\Omega_B$ 就是在&lt;strong&gt;绝对参考框架下刚体的角速度&lt;/strong&gt;（角速度方向和角速度大小）。由此我们求得了旋转矩阵对时间的导数，即
$$
\ ^{S}&lt;/em&gt;{B}\dot{R} = [\ ^{S}&lt;em&gt;{}\Omega_B]\ ^{S}&lt;/em&gt;{B}R = [\hat\omega_B \dot{\theta}] \ ^{S}_{B}R
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.2.3 旋转矩阵的指数公式&lt;/h2&gt;
&lt;p&gt;对上式变形，得到
$$
\frac{d}{dt}\ ^{S}_{B}R = [\hat{\omega}&lt;em&gt;B \dot{\theta}] \ ^{S}&lt;/em&gt;{B}R = [\hat{\omega}&lt;em&gt;B] \ ^{S}&lt;/em&gt;{B}R \ \frac{d\theta}{dt} \
$$&lt;/p&gt;
&lt;p&gt;于是
$$
\frac{d}{d\theta}\ ^{S}&lt;em&gt;{B}R(\theta) = [\hat{\omega}&lt;em&gt;B] \ ^{S}&lt;/em&gt;{B}R
$$
这是指数微分方程的形式，当 $[\hat\omega_B]$ 对 $\theta$ 是常数时，求解可得
$$
\ ^{S}&lt;/em&gt;{B} R(\theta) = e^{[\hat{\omega}_B]\theta}
$$
这便是旋转矩阵的指数公式。&lt;/p&gt;
&lt;h2&gt;1.2.4 平移向量对时间的导数&lt;/h2&gt;
&lt;p&gt;平移向量 $\ ^{S} p_{Borg} $ 对时间的导数可看作刚体固连框架 ${B}$ 的原点坐标的速度向量
$$
\ ^{S} V_{Borg} = \frac{d}{dt} \ ^{S}&lt;em&gt;{} p&lt;/em&gt;{Borg} = \begin{bmatrix} \dot{x}&lt;em&gt;{Borg} \\dot{y}&lt;/em&gt;{Borg} \\dot{z}_{Borg} \end{bmatrix}
$$&lt;/p&gt;
&lt;p&gt;这里介绍以下速度相关的符号表示&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$\ ^{A}&lt;em&gt;{} V&lt;/em&gt;{q}$：点 $q$ 相对于框架 ${A}$ 的速度（相对于框架 ${A}$ 求导），且用框架 ${A}$ 表达向量（理解为向量 $^{A}_{}\vec{V}_q$ 用 $\begin{bmatrix} \mathbf{\hat{x}}_A &amp;amp; \mathbf{\hat{y}}_A &amp;amp;\mathbf{\hat{z}}&lt;em&gt;A\end{bmatrix} \ ^{A}V_q = \ ^{A}V&lt;/em&gt;{qx}\mathbf{\hat{x}}&lt;em&gt;A + \ ^{A}V&lt;/em&gt;{qy}\mathbf{\hat{y}}&lt;em&gt;A + \ ^{A}V&lt;/em&gt;{qz}\mathbf{\hat{z}}_A$ 表示）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$\ ^{B}(^{A}&lt;em&gt;{} V&lt;/em&gt;{q})$：$\ ^{B}(^{A}&lt;em&gt;{} V&lt;/em&gt;{q}) = \ ^{B}&lt;em&gt;{A}R \ ^{A}&lt;/em&gt;{} V_{q}$，即点 $q$ 相对于框架 ${A}$ 的速度（相对于框架 ${A}$ 求导），但用框架 ${B}$ 表达向量（理解为向量 $^{A}_{}\vec{V}_q$ 用 $\begin{bmatrix} \mathbf{\hat{x}}_B &amp;amp; \mathbf{\hat{y}}_B &amp;amp;\mathbf{\hat{z}}_B\end{bmatrix} \ ^{B}(^{A}V_q) = \ ^{B}(^{A}V_q)_x\mathbf{\hat{x}}_A + \ ^{B}(^{A}V_q)_y\mathbf{\hat{y}}_A + \ ^{B}(^{A}V_q)_z\mathbf{\hat{z}}_A$ 表示）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$v_q$：$v_q = \ ^{S} V_{q}$，即点 $q$ 相对于绝对参考坐标系 ${S}$ 且在 ${S}$ 中的速度表达&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$^{A}v_q$：$^{A}v_q =\ ^{A}(\ ^{S} V_{q})$，即点 $q$ 相对于绝对参考坐标系 ${S}$ 但在 ${A}$ 中的速度表达&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;类似地，角速度也有相关的符号规范&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;$\ ^{A}&lt;em&gt;{} \Omega&lt;/em&gt;{q}$：点 $q$ 相对于框架 ${A}$ 的角速度（相对于框架 ${A}$ 求导），且用框架 ${A}$ 表达向量（理解为向量 $^{A}_{}\vec{\Omega}_q$ 用 $\begin{bmatrix} \mathbf{\hat{x}}_A &amp;amp; \mathbf{\hat{y}}_A &amp;amp;\mathbf{\hat{z}}&lt;em&gt;A\end{bmatrix} \ ^{A}\Omega_q = \ ^{A}\Omega&lt;/em&gt;{qx}\mathbf{\hat{x}}&lt;em&gt;A + \ ^{A}\Omega&lt;/em&gt;{qy}\mathbf{\hat{y}}&lt;em&gt;A + \ ^{A}\Omega&lt;/em&gt;{qz}\mathbf{\hat{z}}_A$ 表示）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$\ ^{B}(^{A}&lt;em&gt;{} \Omega&lt;/em&gt;{q})$：$\ ^{B}(^{A}&lt;em&gt;{} \Omega&lt;/em&gt;{q}) = \ ^{B}&lt;em&gt;{A}R \ ^{A}&lt;/em&gt;{} \Omega_{q}$，即点 $q$ 相对于框架 ${A}$ 的角速度（相对于框架 ${A}$ 求导），但用框架 ${B}$ 表达向量（理解为向量 $^{A}_{}\vec{\Omega}_q$ 用 $\begin{bmatrix} \mathbf{\hat{x}}_B &amp;amp; \mathbf{\hat{y}}_B &amp;amp;\mathbf{\hat{z}}_B\end{bmatrix} \ ^{B}(^{A}\Omega_q) = \ ^{B}(^{A}\Omega_q)_x\mathbf{\hat{x}}_A + \ ^{B}(^{A}\Omega_q)_y\mathbf{\hat{y}}_A + \ ^{B}(^{A}\Omega_q)_z\mathbf{\hat{z}}_A$ 表示）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$\omega_q$：$\omega_q = \ ^{S} \Omega_{q}$，即点 $q$ 相对于绝对参考坐标系 ${S}$ 且在 ${S}$ 中的角速度表达&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$^{A}\omega_q$：$^{A}\omega_q =\ ^{A}(\ ^{S} \Omega_{q})$，即点 $q$ 相对于绝对参考坐标系 ${S}$ 但在 ${A}$ 中的角速度表达&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.2.5 运动旋量&lt;/h2&gt;
&lt;p&gt;为了统一旋转矩阵和平移向量的导数，考虑直接对 $4 \times 4$ 齐次矩阵求导。
$$
\ ^{S}&lt;em&gt;{B}\dot{T}(t) = \begin{bmatrix} \ ^{S}&lt;/em&gt;{B} \dot{R}(t) &amp;amp; \ ^{S}&lt;em&gt;{}\dot{p}&lt;/em&gt;{Borg}(t) \ 0 &amp;amp; 0\end{bmatrix}
$$
左乘齐次矩阵的逆
$$
\begin{aligned}
\ ^{S}&lt;em&gt;{B}T^{-1} \ ^{S}&lt;/em&gt;{B}\dot{T} &amp;amp;= \begin{bmatrix} \ ^{S}&lt;em&gt;{B} R^\top &amp;amp; -\ ^{S}&lt;/em&gt;{B} R^\top \ ^{S}&lt;em&gt;{}p&lt;/em&gt;{Borg} \ 0 &amp;amp; 1\end{bmatrix} \begin{bmatrix} \ ^{S}&lt;em&gt;{B} \dot{R} &amp;amp; \ ^{S}&lt;/em&gt;{}\dot{p}&lt;em&gt;{Borg} \ 0 &amp;amp; 0\end{bmatrix} \
&amp;amp;= \begin{bmatrix} \ ^{S}&lt;/em&gt;{B} R^\top \ ^{S}&lt;em&gt;{B} \dot{R} &amp;amp; \ ^{S}&lt;/em&gt;{B}R^\top \ ^{S}\dot{p}&lt;em&gt;{Borg}  \ 0 &amp;amp; 0\end{bmatrix}\
&amp;amp;= \begin{bmatrix} [\ ^{B}&lt;/em&gt;{S}R\ ^{S}&lt;em&gt;{}\Omega_B] &amp;amp; \ ^{B}&lt;/em&gt;{S}R \ ^{S}V_{Borg}  \ 0 &amp;amp; 0\end{bmatrix}\
&amp;amp;= \begin{bmatrix} [\ ^{B} \Omega_B] &amp;amp; \ \ ^{B}V_{Borg}  \ 0 &amp;amp; 0\end{bmatrix} \in se(3)\
\end{aligned}
$$
右乘齐次矩阵的逆
$$
\begin{aligned}
\ ^{S}&lt;em&gt;{B}\dot{T} \ ^{S}&lt;/em&gt;{B}T^{-1} &amp;amp;= \begin{bmatrix} \ ^{S}&lt;em&gt;{B} \dot{R} &amp;amp; \ ^{S}&lt;/em&gt;{}\dot{p}&lt;em&gt;{Borg} \ 0 &amp;amp; 0\end{bmatrix} \begin{bmatrix} \ ^{S}&lt;/em&gt;{B} R^\top &amp;amp; -\ ^{S}&lt;em&gt;{B} R^\top \ ^{S}&lt;/em&gt;{}p_{Borg} \ 0 &amp;amp; 1\end{bmatrix}\
&amp;amp;= \begin{bmatrix} \ ^{S}&lt;em&gt;{B} \dot{R} \ ^{S}&lt;/em&gt;{B} R^\top &amp;amp; \ ^{S} \dot{p}&lt;em&gt;{Borg} - \ ^{S}&lt;/em&gt;{B} \dot{R} \ ^{S}&lt;em&gt;{B} R^\top \ ^{S}&lt;/em&gt;{}p_{Borg} \ 0 &amp;amp; 0\end{bmatrix}\
&amp;amp;= \begin{bmatrix} [\ ^{S} \dot{\Omega}&lt;em&gt;B] &amp;amp; \ ^{S} V&lt;/em&gt;{Borg} - [^{S}\Omega_B] \ ^{S}&lt;em&gt;{}p&lt;/em&gt;{Borg} \ 0 &amp;amp; 0\end{bmatrix} \in se(3)
\end{aligned}
$$
定义
$$
[^{B}\xi_B] = \begin{bmatrix} [\ ^{B} \Omega_B] &amp;amp; \ ^{B}V_{Borg} \ 0 &amp;amp; 0\end{bmatrix} = \ ^{S}&lt;em&gt;{B}T^{-1} \ ^{S}&lt;/em&gt;{B}\dot{T} \in se(3)
$$
和
$$
[^{S}\xi_B] = \begin{bmatrix} [\ ^{S} \dot{\Omega}&lt;em&gt;B] &amp;amp; \ ^{S} V&lt;/em&gt;{Borg} - [^{S}\Omega_B] \ ^{S}&lt;em&gt;{}p&lt;/em&gt;{Borg} \ 0 &amp;amp; 0\end{bmatrix} = \ ^{S}&lt;em&gt;{B}\dot{T} \ ^{S}&lt;/em&gt;{B}T^{-1} \in se(3)
$$
对应有
$$
\begin{aligned}
\ ^{B}&lt;em&gt;{}\xi_B &amp;amp;= \begin{bmatrix} \ ^{B}V&lt;/em&gt;{Borg} \ \ ^{B}&lt;em&gt;{}\Omega_B \end{bmatrix} \in \mathbb{R}^6\
\ ^{S}&lt;/em&gt;{}\xi_B &amp;amp;= \begin{bmatrix} \ ^{S}V_{Borg} - [\ ^{S}&lt;em&gt;{}\Omega_B] \ ^{S}&lt;/em&gt;{}p_{Borg} \ \ ^{S}&lt;em&gt;{}\Omega_B \end{bmatrix} \in \mathbb{R}^6
\end{aligned}
$$
称这两个向量为&lt;strong&gt;运动旋量&lt;/strong&gt;。具体地，称 $\ ^{B}&lt;/em&gt;{}\xi_B$ 为&lt;strong&gt;刚体运动旋量&lt;/strong&gt;，称 $\ ^{S}_{}\xi_B$ 为&lt;strong&gt;空间运动旋量&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一般地，如果一个 $6$ 维列向量 $\xi$ 的前三维分量是线速度，后三维分量是角速度，则这个向量对应于一个刚体的运动旋量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;螺旋轴——单位旋量&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;类似旋转角速度 $\Omega = \hat\omega \dot\theta$ 有旋转轴的单位向量 $\hat\omega$，运动旋量也可以有单位向量的表示，我们称这个单位向量为&lt;strong&gt;螺旋轴&lt;/strong&gt;，形式为
$$
S = \begin{bmatrix} v \ \omega\end{bmatrix} \in \mathbb{R}^6
$$
其中，$S$ 分为两种情况（只有两种）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1&lt;/strong&gt;. $S = \begin{bmatrix} \hat v \ 0\end{bmatrix}$，即 $\Vert \hat v \Vert = 1$，$\omega = 0$，此时的螺旋运动是不旋转只平移的形式。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2&lt;/strong&gt;. $S = \begin{bmatrix} v \ \hat\omega\end{bmatrix}$，即 $\Vert \hat\omega \Vert = 1$，此时的螺旋运动是绕着螺旋轴螺旋前进的形式。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当给定螺旋速度 $\dot\theta$ 和 螺旋轴 $\hat\omega$ 后，可以得到运动旋量表示为
$$
\xi = S\dot\theta = \begin{cases} \begin{bmatrix} \hat v \ 0\end{bmatrix} \dot\theta, &amp;amp;\quad \text{此时}\dot\theta\text{表征线速度大小} \\ \begin{bmatrix} v \ \hat\omega\end{bmatrix} \dot\theta &amp;amp;\quad\text{此时}\dot\theta\text{表征角速度大小} \end{cases}
$$&lt;/p&gt;
&lt;p&gt;之所以划分为两种情况而不直接统一为 $S = \frac{\xi}{\Vert\xi\Vert}$ 的形式，是因为这种归一化的形式并不具有物理意义，线速度和角速度的量纲不同，不能直接归一化。于是根据角速度是否为 0 对螺旋轴进行划分。通用的螺旋运动形式是第 2 种情况，边界情况是第 1 种。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;assets/screw.png&quot; alt=&quot;screw&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1.2.6 点的速度变换&lt;/h2&gt;
&lt;h3&gt;旋转矩阵和平移向量形式&lt;/h3&gt;
&lt;p&gt;设有任意的框架 ${A}$ 和 ${B}$，对
$$
^{A} q = \ ^{A}&lt;em&gt;{B}R \ ^{B} q + \ ^{A}&lt;/em&gt;{}p_{Borg}
$$
求导，得到
$$
^{A} V_q = \ ^{A}&lt;em&gt;{B}R \ ^{B}V_q + [^{A}\Omega_B] \ ^{A}&lt;/em&gt;{B}R \ ^{B} q +\ ^{A}V_{Borg}
$$
这就是点 $q$ 在框架 ${A}$ 下的速度表达式。&lt;/p&gt;
&lt;h3&gt;旋量形式&lt;/h3&gt;
&lt;p&gt;设刚体上有一个&lt;strong&gt;固定&lt;/strong&gt;点 $q$，根据变换关系
$$
\ ^{S} \bar{q}(t) = \ ^{S}&lt;em&gt;{B}T(t) \ ^{B} \bar{q}(t)
$$
有
$$
\begin{aligned}
\ ^{S}\bar{V}&lt;em&gt;q = \ ^{S}&lt;/em&gt;{B} \dot{T} \ ^{S}&lt;/em&gt;{B}T^{-1} \ ^{S}\bar{q} = [^{S} \xi_B] \ ^{S} \bar{q}
\end{aligned}
$$
即可以用刚体的&lt;strong&gt;空间运动旋量&lt;/strong&gt;与空间坐标系下的点 $q$ 的坐标表示点 $q$ 在空间坐标系下的速度。本质上，继续推导，可以得到
$$
\ ^{S}V_q = [^{S}\Omega_B] \ ^{S}q - [^{S}\Omega_B] \ ^{S}&lt;em&gt;{}p&lt;/em&gt;{Borg} + \ ^{S}V_{Borg} = \ ^{S}\Omega_B \times (\ ^{S}q - \ ^{S}&lt;em&gt;{}p&lt;/em&gt;{Borg}) + \ ^{S}V_{Borg}
$$
符合物理中的运动学公式。&lt;/p&gt;
&lt;p&gt;同理，可以得到刚体上&lt;strong&gt;固定&lt;/strong&gt;点 $q$ 在刚体固连框架下的速度为
$$
\ ^{B}(^{S}\bar{V}&lt;em&gt;q) = \ ^{S}&lt;/em&gt;{B}T^{-1}\ ^{S}\bar{V}&lt;em&gt;q = \ ^{S}&lt;/em&gt;{B}T^{-1}\ ^{S}_{B} \dot{T} \ ^{B}\bar{q} = [^{B}\xi_B] \ ^{B}\bar{q}
$$
即可以用刚体的&lt;strong&gt;空间运动旋量&lt;/strong&gt;与刚体固连框架下的点 $q$ 的坐标求解点 $q$ 在刚体固连框架下的绝对速度。&lt;/p&gt;
&lt;p&gt;观察公式 $S$ 和 $\xi$ 的表达式，可以发现齐次坐标和三维坐标的相似性。将运动旋量与齐次坐标进行叉乘运算，可以得到点的速度，仿佛质点在随刚体旋转。也因此，叉乘坐标的向量被称作&lt;strong&gt;运动旋量&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;1.2.7 伴随矩阵——刚体速度变换&lt;/h2&gt;
&lt;p&gt;定义齐次变换矩阵 $T = \begin{bmatrix} R &amp;amp; p \ 0 &amp;amp; 1\end{bmatrix}$ 的伴随矩阵为
$$&lt;/p&gt;
&lt;p&gt;\text{Ad}_{T} = \begin{bmatrix} R &amp;amp; \ [p] R \ 0 &amp;amp; R\end{bmatrix}&lt;/p&gt;
&lt;p&gt;$$&lt;/p&gt;
&lt;p&gt;伴随矩阵的作用是对旋量做变换，设有运动旋量 $\xi = \begin{bmatrix} v \ \omega\end{bmatrix}$，则有
$$&lt;/p&gt;
&lt;p&gt;[\text{Ad}_T \xi] = T [\xi] T^{-1} \stackrel{\text{def}}{=} [\xi&apos;] \in se(3)&lt;/p&gt;
&lt;p&gt;$$
其中
$$&lt;/p&gt;
&lt;p&gt;\xi&apos; = \text{Ad}_T \xi&lt;/p&gt;
&lt;p&gt;$$
下面将说明上面公式的细节部分。
$$
\begin{aligned}
\text{Ad}_T \xi &amp;amp;= \begin{bmatrix} R &amp;amp; \ [p] R \ 0 &amp;amp; R\end{bmatrix} \begin{bmatrix} v \ \omega\end{bmatrix} = \begin{bmatrix} Rv + [p]R\omega \ R\omega\end{bmatrix} = \begin{bmatrix} v&apos; \ \omega&apos;\end{bmatrix} \in \mathbb{R}^6 \
[\text{Ad}_T \xi] &amp;amp;= \begin{bmatrix} [\omega&apos;] &amp;amp;  v&apos; \ 0 &amp;amp; 0\end{bmatrix} = \begin{bmatrix}  [R\omega] &amp;amp; R v + [p]R\omega \ 0 &amp;amp; 0\end{bmatrix}  \in se(3)\
T[\xi]T^{-1} &amp;amp;= \begin{bmatrix} R &amp;amp; p \ 0 &amp;amp; 1\end{bmatrix} \begin{bmatrix}  [\omega] &amp;amp; v \ 0 &amp;amp; 0\end{bmatrix} \begin{bmatrix} R^\top &amp;amp; -R^\top p \ 0 &amp;amp; 1\end{bmatrix}\
&amp;amp;= \begin{bmatrix} R[\omega]R^\top &amp;amp; -R[\omega]R^\top p + Rv \ 0 &amp;amp; 0\end{bmatrix} = \begin{bmatrix} [R\omega] &amp;amp; Rv + [p]R\omega \ 0 &amp;amp; 0\end{bmatrix} \in se(3)
\end{aligned}
$$
其中用到了以下性质&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$R[\omega]R^\top = [R\omega]$，即对反对称矩阵的正交特征变换&lt;/li&gt;
&lt;li&gt;反对称矩阵的加法性质 $[\omega_1] + [\omega_2] = [\omega_1 + \omega_2]$，即对反对称矩阵的加法具有封闭性&lt;/li&gt;
&lt;li&gt;反对称矩阵与叉乘的联系 $[\omega]v = \omega \times v$&lt;/li&gt;
&lt;li&gt;反对称矩阵对数乘封闭性 $k[\omega] = [k\omega]$，这里用到的是 $-[R\omega p] = [-R\omega p]$&lt;/li&gt;
&lt;li&gt;叉乘的反交换性 $[a]b = -[b]a$，这里用到的是 $[p]R\omega = -[R\omega]p$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是证明出了 $[\text{Ad}_T \xi] = T[\xi]T^{-1}$，即伴随矩阵的作用是对运动旋量做变换。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;在不同框架下的描述&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;设从框架 ${A}$ 到 框架 ${B}$ 的变换矩阵为 $\ ^{A}&lt;em&gt;{B}T = \begin{bmatrix} \ ^{A}&lt;/em&gt;{B}R &amp;amp; \ ^{A}&lt;em&gt;{}p&lt;/em&gt;{Borg} \ 0 &amp;amp; 1\end{bmatrix}$，在框架 ${B}$ 下某个旋量 $\xi$ 的向量表示为 $^{B}\xi$，那么它在框架 ${A}$ 下的向量表示为
$$&lt;/p&gt;
&lt;p&gt;\ ^{A}\xi = \text{Ad}&lt;em&gt;{^{A}&lt;/em&gt;{B}T} \ ^{B}\xi&lt;/p&gt;
&lt;p&gt;$$&lt;/p&gt;
&lt;p&gt;由于螺旋轴的本质是归一化后的运动旋量，因此也可以用上述公式变换
$$&lt;/p&gt;
&lt;p&gt;^{A}\hat{S} = \text{Ad}&lt;em&gt;{^{A}&lt;/em&gt;{B}T} \ ^{B}\hat{S}&lt;/p&gt;
&lt;p&gt;$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;重要变换性质&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在刚体固连框架 ${B}$ 中描述的刚体相对于绝对框架 ${S}$ 的运动旋量（即刚体运动旋量）$^{B}\xi_B$ 与在绝对框架 ${S}$ 中描述的刚体相对于绝对框架 ${S}$ 的运动旋量（即空间运动旋量）$^{S}\xi_B$ 之间的关系为
$$&lt;/p&gt;
&lt;p&gt;\ ^{S}\xi_B = \text{Ad}&lt;em&gt;{^{S}&lt;/em&gt;{B}T} \ ^{B}\xi_B&lt;/p&gt;
&lt;p&gt;$$&lt;/p&gt;
&lt;p&gt;其中，$\text{Ad}&lt;em&gt;{^{S}&lt;/em&gt;{B}T} = \begin{bmatrix} \ ^{S}&lt;em&gt;{B}R &amp;amp; \ [\ ^{S}&lt;/em&gt;{B}p_{Borg}] \ ^{S}&lt;em&gt;{B}R \ 0 &amp;amp; \ ^{S}&lt;/em&gt;{B}R\end{bmatrix}$ 是伴随矩阵。这个公式可以由下式得到
$$
[^{S}\xi_B] = \ ^{S}&lt;em&gt;{B}\dot{T}\ ^{S}&lt;/em&gt;{B}T^{-1} = \ ^{S}&lt;em&gt;{B}T\ ^{S}&lt;/em&gt;{B}T^{-1} \ ^{S}&lt;em&gt;{B}\dot{T}\ ^{S}&lt;/em&gt;{B}T^{-1} = \ ^{S}&lt;em&gt;{B}T\ [^{B}\xi_B]\ ^{S}&lt;/em&gt;{B}T^{-1} = [\text{Ad}&lt;em&gt;{^{S}&lt;/em&gt;{B}T} \ ^{B}\xi_B]
$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;lt;font size=4&amp;gt;伴随矩阵的性质&amp;lt;/font&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;伴随矩阵的逆
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;\text{Ad}&lt;em&gt;T^{-1} = \begin{bmatrix} R^\top &amp;amp; -R^\top [p] \ 0 &amp;amp; R^\top\end{bmatrix} = \text{Ad}&lt;/em&gt;{T^{-1}}&lt;/p&gt;
&lt;p&gt;$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;结合律，利用结合律可以推导出伴随矩阵的逆性质
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;\text{Ad}&lt;em&gt;{T_1} \text{Ad}&lt;/em&gt;{T_2} = \text{Ad}_{T_1 T_2}&lt;/p&gt;
&lt;p&gt;$$&lt;/p&gt;
&lt;h1&gt;1.3 加速度&lt;/h1&gt;
&lt;p&gt;加速度的符号表示与速度的标准相同，$^{A}(^{B} \dot V)$ 等含义在此不予赘述。&lt;/p&gt;
&lt;h2&gt;1.3.1 线加速度&lt;/h2&gt;
&lt;p&gt;设有框架 ${A}$ 和框架 ${B}$ 和 点 $q$，对速度变换公式求导，有
$$
\begin{aligned}
^{A} \dot V_q &amp;amp;= \left([^{A}\dot\Omega_B]\ ^{A}&lt;em&gt;{B}R \ ^{B}q + [^{A}\Omega_B][^{A}\Omega_B]\ ^{A}&lt;/em&gt;{B}R \ ^{B}q + [^{A}\Omega_B] \ ^{A}&lt;em&gt;{B}R \ ^{B}V_q\right) + \left([^{A}\Omega_B] \ ^{A}&lt;/em&gt;{B}R \ ^{B}V_q + \ ^{A}&lt;em&gt;{B}R\ ^{B}\dot V_q\right) + \ ^{A}\dot V&lt;/em&gt;{Borg}\
&amp;amp;= \ ^{A}\dot V_{Borg} + \ ^{A}&lt;em&gt;{B}R \ ^{B}\dot V_q + \underbrace{2[^{A}\Omega_B] \ ^{A}&lt;/em&gt;{B}R \ ^{B}V_q}&lt;em&gt;{Coriolis} + \underbrace{[^{A}\dot\Omega_B] \ ^{A}&lt;/em&gt;{B}R \ ^{B}q}&lt;em&gt;{Euler} + \underbrace{[^{A}\Omega_B][^{A}\Omega_B]\ ^{A}&lt;/em&gt;{B}R \ ^{B}q}_{Centrifugal}
\end{aligned}
$$&lt;/p&gt;
&lt;h2&gt;1.3.2 角加速度&lt;/h2&gt;
&lt;p&gt;对于角加速度，利用
$$
^{S}\Omega_B = \ ^{S}\Omega_A + \ ^{S}&lt;em&gt;{A}R\ ^{A}\Omega_B
$$
对时间求导，得到
$$
\begin{aligned}
^{S}\dot\Omega_B &amp;amp;= \ ^{S}\dot\Omega_A + [^{S}\Omega_A]\ ^{S}&lt;/em&gt;{A}R\ ^{A}\Omega_B + \ ^{S}_{A}R\ ^{A}\dot\Omega_B
\end{aligned}
$$&lt;/p&gt;
&lt;h1&gt;1.4 力&lt;/h1&gt;
&lt;p&gt;力 $f$ 和力矩 $n$ 可以结合在一起统一表达为一个六维的广义力 $\mathcal{F} = \begin{bmatrix} f \ f\end{bmatrix}$，其中 $f, n \in \mathbb{R}^3$。我们称这个广义力叫&lt;strong&gt;力旋量 (Wrench)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;力旋量也有在不同框架下的切换公式，假设同一个力旋量在框架 ${A}$ 中表示为 $^{A}\mathcal{F}$，在框架 ${B}$ 中表示为 $^{B}\mathcal{F}$，则有
$$&lt;/p&gt;
&lt;p&gt;^{B}\mathcal{F} = \text{Ad}&lt;em&gt;{^{A}&lt;/em&gt;{B}T}^\top \ ^{A}\mathcal{F}&lt;/p&gt;
&lt;p&gt;$$
这个公式可以从做功的角度推导出，在不同框架下的力旋量表达虽然不同，但是其做功应是相同的，即
$$
\begin{aligned}
^{B}\mathcal{F}^\top \ ^{B}\xi &amp;amp;= ^{A}\mathcal{F}^\top \ ^{A}\xi \
\ ^{A}\xi &amp;amp;= \text{Ad}&lt;em&gt;{^{A}&lt;/em&gt;{B}T} \ ^{B}\xi
\end{aligned}
$$
从而推导出上式。&lt;/p&gt;
&lt;h1&gt;1.5 质量与惯量&lt;/h1&gt;
&lt;h2&gt;1.5.1 经典的标量描述方法&lt;/h2&gt;
&lt;p&gt;刚体，不同于质点，通常在空间中会占据一定的空间，因此不能只以一个点的位矢完全概括它的位置。假设刚体的密度分布为 $\rho$, $\vec{p}$ 为空间中点的位矢，则可以通过积分的方式得到刚体的质量，并求出相应的质心位置
$$
\begin{aligned}
m &amp;amp;= \int \rho \mathrm{d}V \
\vec{p}_c &amp;amp;= \frac{1}{m}\int \vec{p}\rho \mathrm{d}V
\end{aligned}
$$
质点的动力学模型仅仅通过质量即可完整描述（牛顿第二定律），但是刚体不行，因为刚体还需要考虑自身转动，因此刚体还有一个用于描述转动动力学模型的参量叫转动惯量
$$&lt;/p&gt;
&lt;p&gt;J = \int r^2\rho\mathrm{d}V&lt;/p&gt;
&lt;p&gt;$$
其中 $r$ 是位于 $\vec{p}$ 处的质点到旋转轴的距离。&lt;/p&gt;
&lt;p&gt;有了质量 $m$ 和转动惯量 $J$ 之后，就可以相应地对刚体做动力学分析了。通常的分析方程有牛顿第二定律、欧拉动力学方程和动能定理等
$$
\begin{cases}
&amp;amp;m\ddot{\vec{p_c}} = \sum \vec{f}&lt;em&gt;{\text{ext}} \
&amp;amp;J\dot{\vec{\omega}} + \vec{\omega} \times (J\vec{\omega}) = \sum \vec{\tau}&lt;/em&gt;{\text{ext}} \
&amp;amp;\mathcal{K} = \frac{1}{2}m\dot{\vec{p}}&lt;em&gt;c\cdot \dot{\vec{p}}&lt;em&gt;c + \frac{1}{2}J\vec{\omega}\cdot \vec{\omega}
\end{cases}
$$
其中 $\sum \vec{f}&lt;/em&gt;{\mathrm{ext}}$ 为外力合力，$\sum \vec{\tau}&lt;/em&gt;{\mathrm{ext}}$ 为关于质心的外力矩合力。&lt;/p&gt;
&lt;p&gt;注意这里所使用的质量和转动惯量均是标量的形式。&lt;/p&gt;
&lt;h2&gt;1.5.2 张量形式的描述方法&lt;/h2&gt;
&lt;p&gt;在三维空间中，标量形式的转动惯量只能描述&lt;strong&gt;绕一个特定轴&lt;/strong&gt;的旋转，而无法全面反映刚体在不同方向上的转动特性。因此引入了&lt;strong&gt;惯性张量&lt;/strong&gt;这个二阶张量以方便问题的分析。&lt;/p&gt;
&lt;p&gt;定义相对于框架 ${A}$ 的惯性张量为
$$&lt;/p&gt;
&lt;p&gt;^{A}I = \int [^{A}p]^\top[^{A}p]\mathrm{d}m&lt;/p&gt;
&lt;p&gt;$$
注意这里的位矢 $^{A}p$ 是相对于绝对框架的位置坐标，在刚体运动的过程中往往是随时间变化的，也就是说，惯性张量对时间的导数通常不为0。只有当参考框架取为质心框架 ${c}$ 时，惯性参量才是不随时间变化的。&lt;/p&gt;
&lt;h2&gt;1.5.3 惯性张量在不同参考框架下的切换&lt;/h2&gt;
&lt;p&gt;设惯性张量 $I$ 在框架 ${A}$ 中的表达为 $^{A}I$，在框架 ${B}$ 中的表达为 $^{B}I$，则根据角动量的框架切换公式和角速度的切换公式
$$
\begin{aligned}
^{A}L &amp;amp;= \ ^{A}I \ ^{A}\omega\
^{B}L &amp;amp;= \ ^{B}I \ ^{B}\omega\
^{A}L &amp;amp;= \ ^{A}&lt;em&gt;{B}R \ ^{B}L \
^{A}\omega &amp;amp;= \ ^{A}&lt;/em&gt;{B}R \ ^{B}\omega \
\end{aligned}
$$
有
$$
\begin{aligned}
\ ^{A}I \ ^{A}\omega = \ ^{A}&lt;em&gt;{B}R\ ^{B}I \ ^{B}\omega = \ ^{A}&lt;/em&gt;{B}R\ ^{B}I \ ^{B}_{A}R\ ^{A}\omega
\end{aligned}
$$
于是
$$&lt;/p&gt;
&lt;p&gt;\ ^{A}I = \ ^{A}&lt;em&gt;{B}R\ ^{B}I \ ^{A}&lt;/em&gt;{B}R^\top&lt;/p&gt;
&lt;p&gt;$$&lt;/p&gt;
&lt;h1&gt;1.6 能量与角动量&lt;/h1&gt;
&lt;h2&gt;1.6.1 动能&lt;/h2&gt;
&lt;p&gt;在框架 ${A}$ 中，定义刚体 $B$ 绕任意轴做纯转动的动能为
$$&lt;/p&gt;
&lt;p&gt;^{A}\mathcal{K}_R = \frac{1}{2} \int\ ^{A}v^\top\ ^{A} v\ \mathrm{d}m&lt;/p&gt;
&lt;p&gt;$$
其中速度向量 $^{A}v = \ ^{A}V_{\mathrm{d}m}$ 表示在框架 ${A}$ 下质元 $\mathrm{d}m$ 的三维速度向量。展开动能，有
$$
\begin{aligned}
^{A}\mathcal{K}_R &amp;amp;= \frac{1}{2} \int\ ^{A} v^\top\ ^{A} v\ \mathrm{d}m\
&amp;amp;= \frac{1}{2} \int(^{A}\omega \times \ ^{A}p)^\top(^{A}\omega \times\ ^{A} p) \mathrm{d}m\
&amp;amp;= \frac{1}{2} \int\ ^{A}\omega^\top[^{A}p]^\top [^{A}p]\ ^{A} \omega\ \mathrm{d}m\
&amp;amp;= \frac{1}{2}\ ^{A} \omega^\top\ ^{A}I\ ^{A} \omega
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;于是得到转动动能表示为
$$&lt;/p&gt;
&lt;p&gt;^{A}\mathcal{K}_R = \frac{1}{2}\ ^{A} \omega^\top\ ^{A}I\ ^{A} \omega&lt;/p&gt;
&lt;p&gt;$$
其中，$^{A}\omega = \ ^{A}\Omega_B$ 是刚体 $B$ 的转动角速度。&lt;/p&gt;
&lt;p&gt;结合刚体平动动能，可以得到刚体运动的全部动能为
$$&lt;/p&gt;
&lt;p&gt;^{A}\mathcal{K} = \frac{1}{2}m\ ^{A}\dot{p}_c^\top\ ^{A}\dot{p}_c + \frac{1}{2}\ ^{A} \omega^\top\ ^{A}I\ ^{A} \omega&lt;/p&gt;
&lt;p&gt;$$&lt;/p&gt;
&lt;h2&gt;1.6.2 角动量&lt;/h2&gt;
&lt;p&gt;同样地，角动量也有惯性张量的表达形式。根据定义，在框架 ${A}$ 中，刚体 $B$ 的角动量表达为
$$&lt;/p&gt;
&lt;p&gt;^{A}h = \int\ ^{A}p \times \ ^{A}v\ \mathrm{d}m&lt;/p&gt;
&lt;p&gt;$$
展开有
$$
\begin{aligned}
^{A}h &amp;amp;= \int\ ^{A}p \times \ ^{A}v\ \mathrm{d}m \
&amp;amp;= \int \ ^{A}p \times (^{A}\omega \times \ ^{A}p)\ \mathrm{d}m \
&amp;amp;= -\int \ ^{A}p \times (^{A}p \times\ ^{A}\omega)\ \mathrm{d}m \
&amp;amp;= -\int [^{A}p][^{A}p]\ ^{A}\omega \ \mathrm{d}m\
&amp;amp;= \int [^{A}p]^\top[^{A}p]\ \mathrm{d}m \ ^{A}\omega \
&amp;amp;= \ ^{A}I \ ^{A}\omega
\end{aligned}
$$
这里用到了反对称矩阵的性质 $[^{A}p]^\top = -[^{A}p]$；其中 $^{A}\omega$ 是刚体的转动角速度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;惯性主轴&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;需要注意的事情是，从上面公式可以看出，角动量 $^{A}h$ 的方向并不一定和角速度 $^{A}\omega$ 的方向相同，只有当满足下面方程时，二者的方向才相同
$$&lt;/p&gt;
&lt;p&gt;^{A}I \ ^{A}\omega = k \ ^{A}\omega&lt;/p&gt;
&lt;p&gt;$$
我们称满足这个特征向量关系的轴线为惯性主轴。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;欧拉方程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;刚体所受合力矩是其角动量的变化率
$$
\begin{aligned}
^{A} \tau &amp;amp;= \frac{\mathrm{d}\ ^{A}L}{\mathrm{d} t} = \frac{\mathrm{d}}{\mathrm{d} t}(^{A}I\ ^{A}\omega) = \frac{\mathrm{d}}{\mathrm{d} t}(\ ^{A}&lt;em&gt;{c}R\ ^{c}I\ \ ^{A}&lt;/em&gt;{c}R^\top \ ^{A}\omega) \
&amp;amp;= \ ^{A}&lt;em&gt;{c}\dot R \ ^{c}I \ ^{A}&lt;/em&gt;{c}R^\top\ ^{A}\omega +  \ ^{A}&lt;em&gt;{c} R \ ^{c}I \ ^{A}&lt;/em&gt;{c}\dot R^\top\ ^{A}\omega + \ ^{A}&lt;em&gt;{c}R \ ^{c}I \ ^{A}&lt;/em&gt;{c}R^\top\ ^{A}\dot \omega \
&amp;amp;= [^{A}\omega]\ ^{A}&lt;em&gt;{c}R \ ^{c}I \ ^{A}&lt;/em&gt;{c}R^\top\ ^{A}\omega + \ ^{A}&lt;em&gt;{c}R \ ^{c}I \ ^{A}&lt;/em&gt;{c}R^\top[^{A}\omega]^\top\ ^{A}\omega + \ ^{A}&lt;em&gt;{c}R \ ^{c}I \ ^{A}&lt;/em&gt;{c}R^\top\ ^{A}\dot \omega \
&amp;amp;= \ ^{A}\omega \times\ ^{A}I \ ^{A}\omega + \ ^{A}I \ ^{A}\dot\omega
\end{aligned}
$$
这里的推导利用到了质心框架下的惯性张量不随时间变化的性质。于是欧拉方程为
$$&lt;/p&gt;
&lt;p&gt;^{A}\tau = \ ^{A}\omega \times\ ^{A}I \ ^{A}\omega + \ ^{A}I \ ^{A}\dot\omega&lt;/p&gt;
&lt;p&gt;$$
其中 ${A}$ 为任意框架。&lt;/p&gt;
</content:encoded></item></channel></rss>