<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆</title>
	<atom:link href="https://blog.gmem.cc/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Wed, 06 May 2026 13:28:33 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>人工智能理论知识 - 智能体</title>
		<link>https://blog.gmem.cc/ai-knowledge-quick-ref-5</link>
		<comments>https://blog.gmem.cc/ai-knowledge-quick-ref-5#comments</comments>
		<pubDate>Wed, 15 Apr 2026 21:47:13 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[AI]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=42155</guid>
		<description><![CDATA[<p>这一篇处理模型之外的系统层问题，包括上下文工程、Harness Engineering、检索增强生成（RAG）与智能体。前一篇讲的是模型本体及其训练和推理，这一篇转向“模型如何被放进真实系统里工作”：上下文如何组织，工具与知识库如何接入，验证闭环如何建立，以及多步推理与多智能体协作如何落地。 上下文工程（Context Engineering） 从提示词工程到上下文工程 提示词工程（Prompt Engineering）最早强调的是“怎么写一句更有效的话”，关注点集中在措辞、顺序、示例和限制条件。随着大模型应用从单轮问答扩展到智能体（AI Agent）、RAG、多工具调用与长会话系统，工程重点逐渐转向上下文工程（Context Engineering）：真正决定效果的，不再只是某一句提示词本身，而是系统提示、用户输入、示例、检索结果、对话历史、工具返回、结构化状态与输出约束如何被整体组织成一次模型调用。 因此，上下文工程可以看作比提示词工程更大的外层概念。提示词仍然重要，但它只是上下文中的一个组件；在生产系统里，更关键的问题通常是“哪些信息应该进入上下文、以什么顺序进入、保留多久、如何压缩、何时替换、如何约束输出”。这一变化反映的不是术语时尚，而是应用形态已经从“写一句 prompt”演进为“设计一套输入装配系统”。 上下文学习 上下文学习（In-context Learning）指模型在不更新参数的前提下，仅凭当前输入中的任务说明、示例与约束完成新任务。它依赖的是上下文中的条件信息，而不是额外微调。 零样本（Zero-shot） 零样本（Zero-shot）是在不给示例的情况下，直接通过任务描述让模型完成目标。例如“判断以下影评是正面还是负面，只输出标签”。它的优点是成本低、迁移快；缺点是任务边界一旦含糊，模型更容易自由发挥。 少样本（Few-shot） 少样本（Few-shot）是在提示词中附带少量示例，让模型在当前上下文里归纳输入输出模式。它特别适合标签定义不够直观、格式要求严格或任务带有领域习惯的场景。示例的价值不只是“给答案”，更是显式定义任务边界、异常情况与输出风格。 提示词结构 一个可维护的提示词通常由若干功能块组合而成，而不是一段散乱文本。把这些块拆开，能够显著提高复用性、可调试性与一致性。 角色定位 角色定位（Role）定义模型在本次任务中的身份与职责边界，例如“你是严谨的法律信息抽取器”或“你是面向儿童解释科学概念的讲解者”。它的作用是设定默认行为策略，而不是替代具体任务指令。 <a class="read-more" href="https://blog.gmem.cc/ai-knowledge-quick-ref-5">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-5">人工智能理论知识 - 智能体</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>这一篇处理模型之外的系统层问题，包括上下文工程、Harness Engineering、检索增强生成（RAG）与智能体。前一篇讲的是模型本体及其训练和推理，这一篇转向“模型如何被放进真实系统里工作”：上下文如何组织，工具与知识库如何接入，验证闭环如何建立，以及多步推理与多智能体协作如何落地。</p>
<div class="blog_h1"><span class="graybg">上下文工程（Context Engineering）</span></div>
<div class="blog_h2"><span class="graybg">从提示词工程到上下文工程</span></div>
<p>提示词工程（Prompt Engineering）最早强调的是“怎么写一句更有效的话”，关注点集中在措辞、顺序、示例和限制条件。随着大模型应用从单轮问答扩展到智能体（AI Agent）、RAG、多工具调用与长会话系统，工程重点逐渐转向上下文工程（Context Engineering）：真正决定效果的，不再只是某一句提示词本身，而是<span style="background-color: #c0c0c0;">系统提示、用户输入、示例、检索结果、对话历史、工具返回、结构化状态与输出约束如何被整体组织成一次模型调用</span>。</p>
<p>因此，上下文工程可以看作比提示词工程更大的外层概念。提示词仍然重要，但它只是上下文中的一个组件；在生产系统里，更关键的问题通常是“哪些信息应该进入上下文、以什么顺序进入、保留多久、如何压缩、何时替换、如何约束输出”。这一变化反映的不是术语时尚，而是应用形态已经从“写一句 prompt”演进为“设计一套输入装配系统”。</p>
<div class="blog_h2"><span class="graybg">上下文学习</span></div>
<p>上下文学习（In-context Learning）指模型在不更新参数的前提下，仅凭当前输入中的任务说明、示例与约束完成新任务。它依赖的是上下文中的条件信息，而不是额外微调。</p>
<div class="blog_h3"><span class="graybg">零样本（Zero-shot）</span></div>
<p>零样本（Zero-shot）是在不给示例的情况下，直接通过任务描述让模型完成目标。例如“判断以下影评是正面还是负面，只输出标签”。它的优点是成本低、迁移快；缺点是任务边界一旦含糊，模型更容易自由发挥。</p>
<div class="blog_h3"><span class="graybg">少样本（Few-shot）</span></div>
<p>少样本（Few-shot）是在提示词中附带少量示例，让模型在当前上下文里归纳输入输出模式。它特别适合标签定义不够直观、格式要求严格或任务带有领域习惯的场景。示例的价值不只是“给答案”，更是显式定义任务边界、异常情况与输出风格。</p>
<div class="blog_h2"><span class="graybg">提示词结构</span></div>
<p>一个可维护的提示词通常由若干功能块组合而成，而不是一段散乱文本。把这些块拆开，能够显著提高复用性、可调试性与一致性。</p>
<div class="blog_h3"><span class="graybg">角色定位</span></div>
<p>角色定位（Role）定义模型在本次任务中的身份与职责边界，例如“你是严谨的法律信息抽取器”或“你是面向儿童解释科学概念的讲解者”。它的作用是设定默认行为策略，而不是替代具体任务指令。</p>
<div class="blog_h3"><span class="graybg">指令</span></div>
<p>指令（Instruction）直接说明模型要完成什么任务，是提示词中最核心的控制块。好的指令应当明确目标、操作步骤、禁止事项与完成标准，避免把多个含糊目标混在同一层表达里。</p>
<div class="blog_h3"><span class="graybg">上下文</span></div>
<p>上下文（Context）提供完成任务所需的背景信息，例如产品文档、会话历史、用户约束、检索片段或前一步工具返回。它回答的是“模型基于什么信息工作”，而不是“模型该怎么工作”。</p>
<div class="blog_h3"><span class="graybg">输出格式</span></div>
<p>输出格式（Output Format）规定结果应该长什么样，例如纯文本、项目符号、JSON、表格或固定字段。格式要求越明确，后续系统越容易解析、验证与链接到其它模块。</p>
<div class="blog_h3"><span class="graybg">受众</span></div>
<p>受众（Audience）决定解释深度、术语密度与默认背景知识。例如 ELI5（Explain Like I'm 5）指面向完全没有背景知识的读者，需要更浅白的表达；面向资深工程师时，则应保留专业术语、边界条件与实现细节。</p>
<div class="blog_h3"><span class="graybg">语气</span></div>
<p>语气（Tone）控制表达风格，例如正式、简洁、鼓励式、审慎式或客服式。它影响的是话语风格和风险表达方式，但不应改变事实本身或任务判断标准。</p>
<div class="blog_h3"><span class="graybg">数据</span></div>
<p>数据（Data）指与任务本身直接相关的材料，例如待分类文本、待总结文档、用户表单、日志片段、表格记录或检索回来的证据。很多提示词失败，根源在于真正决定任务结果的数据并没有进入上下文。</p>
<div class="blog_h2"><span class="graybg">上下文构建</span></div>
<div class="blog_h3"><span class="graybg">系统提示（System Prompt）</span></div>
<p>系统提示（System Prompt）是整个上下文的最高层行为约束，通常用于定义角色、原则、边界和长期稳定规则。它应该稳定、简短且高信号，承载通用策略，而不适合塞入频繁变化的任务细节。</p>
<div class="blog_h3"><span class="graybg">对话历史与记忆</span></div>
<p>多数大模型 API 是无状态（Stateless）的：模型在一次调用中只“看到”你发送的输入（Prompt），不会自动记住上一次调用的对话内容。因此对话应用通常需要把对话历史（Conversation History）连同当前用户输入一起发给模型。</p>
<p>这会带来两个直接后果：</p>
<ul>
<li>token 占用会随着历史增长而上升（直到触达上下文窗口上限）。</li>
<li>并非“发得越多越好”：冗余历史会稀释注意力、提高成本，并可能引入过时或冲突信息。</li>
</ul>
<p>工程上常见的记忆（Memory）分层是：</p>
<ul>
<li>短期记忆（Short-term Memory）：保留最近 N 轮原文对话，确保局部连贯。</li>
<li>工作记忆（Working Memory）：把对话状态压缩成结构化摘要，例如用户偏好、已确认事实与当前任务约束。</li>
<li>长期记忆（Long-term Memory）：把历史片段写入外部存储（向量库/数据库），需要时检索回填。</li>
</ul>
<div class="blog_h3"><span class="graybg">工具调用上下文</span></div>
<p>工具调用上下文（Tool Context）指模型在调用搜索、数据库、代码执行器或业务 API 后得到的中间结果。它的关键不只是“把结果贴回模型”，而是把工具返回整理成模型真正可用的状态：保留必要字段、去掉噪声、标明来源与时间，并避免把原始日志整段塞进上下文。</p>
<div class="blog_h3"><span class="graybg">渐进式披露</span></div>
<p>渐进式披露（Progressive Disclosure）强调：不要在一开始把所有文档、规则、工具说明和历史对话一次性塞进上下文，而应只注入当前步骤真正需要的那一层信息。模型先看到最小可行上下文，只有当任务推进到下一层时，才继续展开更具体的约束、领域知识或执行细节。</p>
<p>这样做的原因不只是节省 token，更是为了保持推理质量。多组工程实践都观察到，上下文利用率一旦超过大约 40%，模型就可能从“聚焦求解”进入“信息过载”状态，开始出现幻觉、循环、格式错误或低质量实现。渐进式披露的目标，就是让 Agent 长时间停留在这个甜蜜区，而不是被无关材料拖进噪声区。</p>
<div class="blog_h2"><span class="graybg">上下文窗口管理</span></div>
<div class="blog_h3"><span class="graybg">Token 预算</span></div>
<p>Token 预算（Token Budget）是把上下文窗口（Context Window）当作一种稀缺资源来管理：一次请求的输入 token 与输出 token 都要计入模型的最大长度限制，并直接影响延迟与成本。</p>
<p>经验做法是把 prompt 切成可控的几块：系统提示尽量短且稳定；对话历史只保留近期原文；把长期信息通过检索（Retrieval）按需注入。</p>
<div class="blog_h3"><span class="graybg">压缩与摘要</span></div>
<p>压缩与摘要（Compression &amp; Summarization）的核心，是把信息从逐字转录（Transcript）变成可被模型继续使用的状态（State），而不是单纯把文本“变短”。常见策略：</p>
<ul>
<li>滚动摘要（Rolling Summary）：每轮对话后更新一段固定长度的当前状态。</li>
<li>层级摘要（Hierarchical Summarization）：长文先分块总结，再总结摘要。</li>
<li>结构化记忆（Structured Memory）：用 JSON 或表格保存关键字段（偏好、约束、已决策项），避免自然语言摘要漂移。</li>
</ul>
<p>摘要要可验证：优先保留可操作事实（约束、数值、名称、决策），少写主观评价。</p>
<div class="blog_h3"><span class="graybg">长文本处理策略</span></div>
<p>当最新一次对话依赖很久以前的信息时，更可靠的方法是检索增强：把历史切成片段并建立索引（向量索引、关键词索引、主题标签），在每次请求前先检索与当前问题最相关的片段，再把这些片段注入上下文。无限追加历史通常会带来更高噪声和更差上下文利用率。</p>
<p>在当前智能体开发里，最主流的组合通常是：<span style="background-color: #c0c0c0;">滑动窗口 + 滚动摘要 + 向量检索（RAG）</span>。早期的外部记忆网络（Memory Networks / Neural Turing Machine）更多是研究范式，工程落地上更常见的是向量数据库与检索管线。</p>
<div class="blog_h2"><span class="graybg">结构化输出</span></div>
<div class="blog_h3"><span class="graybg">JSON Schema 约束</span></div>
<p>JSON Schema 约束把“输出应该长什么样”前置为机器可验证的结构定义，例如字段名、字段类型、必填项、枚举值与嵌套关系。它的价值在于把格式控制从“模型尽量照做”提升到“系统可以检查对不对”，从而显著降低后处理复杂度。</p>
<div class="blog_h3"><span class="graybg">函数调用（Function Calling）</span></div>
<p>函数调用（Function Calling）把模型输出从自由文本转成“选择哪个工具、填写哪些参数”的结构化决策。它的工作方式是让模型先生成一个可被程序消费的调用意图，再由外部系统负责权限校验、真实执行与结果回填。</p>
<div class="blog_h2"><span class="graybg">高级提示词策略</span></div>
<p>这里讨论的高级提示词策略，指的是仅通过提示词设计改变模型求解过程，而不借助额外框架、搜索控制器或推理编排器。它们本质上仍属于上下文工程：通过改变输入结构，让模型在一次或少数几次调用中显式展开中间推理步骤。</p>
<div class="blog_h3"><span class="graybg">思维链（Chain-of-Thought）</span></div>
<p>思维链（Chain-of-Thought, CoT）的核心做法是：在提示词中明确要求模型先分步分析，再给出最终答案。例如，可以把任务写成“先列出关键事实，再逐步推理，最后只输出结论”。若任务本身较复杂，也可以在 few-shot 示例里直接展示“问题 ➡ 分步推理 ➡ 最终答案”的格式，让模型在上下文中模仿这种求解模式。</p>
<p>在纯提示词使用方式下，CoT 的关键在于把推理过程结构化地写进输出要求，而不是迷信某一句固定咒语。例如，可以把输出格式限定为“步骤 1 / 步骤 2 / 结论”，或要求模型先检查条件、再排除候选、最后汇总结论。这样做通常有利于多步算术、条件判断、规则推导与长链依赖任务；但对简单任务、严格结构化输出任务或成本敏感场景，强行要求展开思维链反而会增加 token 开销与噪声。</p>
<div class="blog_h3"><span class="graybg">思维树（Tree of Thoughts）</span></div>
<p>思维树（Tree of Thoughts, ToT）可以看作思维链的分支化版本：提示词不只要求模型给出一条线性推理路径，而是要求它先生成多个候选思路，再比较这些思路，最后选择更优的一条继续展开。在不借助框架的前提下，这种策略仍可以通过提示词实现，例如要求模型“先给出三个可能方案，分别说明优缺点，再选择最合理的方案给出最终答案”。</p>
<p>纯提示词版 ToT 的本质，是把“分支生成 + 分支比较 + 继续展开”都压进一次或少数几次模型调用里。它适合开放式规划、方案比较、复杂写作提纲和策略搜索这类任务，因为这类任务往往不存在唯一直接路径。代价同样明显：提示词更长，输出更长，模型也更容易在分支之间漂移。因此，ToT 更适合高价值、需要比较多个候选方案的任务，而不适合作为所有请求的默认模式。</p>
<div class="blog_h1"><span class="graybg">Harness Engineering</span></div>
<p>Harness Engineering（驾驭工程）研究的是：当模型推理与生成能力已经足够强时，决定 Agent 能否稳定完成复杂任务的，往往是模型外围的整套工作系统。Harness 指模型之外的所有代码、配置与执行逻辑——工具接口、状态管理、上下文装配、架构约束、验证机制、回滚策略与持续清理，全部属于这一层。裸模型只能接收输入、输出文本；只有当 Harness 为它提供状态、工具、约束和反馈回路后，它才能成为一个持续干活的 Agent。</p>
<p>目前 Harness Engineering 落地最广、讨论最充分的场景仍然是 Coding Agent，因此本章中的很多方法首先来自代码生成、调试、验证、跨会话交接和多 Agent 编排的工程实践。但它并不局限于写代码。凡是 Agent 需要长期持有状态、调用外部工具、在约束下执行任务、接受反馈并持续纠偏的场景，都可以应用同样的方法论，例如研究 Agent、数据分析 Agent、运维自动化 Agent、业务流程 Agent 以及多模态交互系统。随着 Agent 从“生成回答”走向“持续执行任务”，Harness Engineering 也会从 Coding Agent 的经验集合，扩展为更一般的 Agent 系统工程。</p>
<p>下文将沿着“为什么 Agent 会失效 ➡ 如何搭建控制面 ➡ 如何长期治理系统 ➡ 工程师角色如何变化”这条主线展开。</p>
<div class="blog_h2"><span class="graybg">三层模型和五大支柱</span></div>
<p>Prompt Engineering（提示词工程）、Context Engineering（上下文工程）与 Harness Engineering 构成了层层外扩的系统模型：前者决定如何表达任务，中间层决定模型能看到什么，最外层决定整个系统如何执行、纠偏与长期维持质量。</p>
<div class="blog_h3"><span class="graybg">三层模型</span></div>
<div class="blog_h4"><span class="graybg">提示词工程</span></div>
<p>提示词工程回答的是“如何把任务说清楚”。它关注指令措辞、角色设定、Few-shot 示例、输出格式和推理引导，目标是在单次调用里把任务表达得足够明确，让模型更容易走向期望行为。</p>
<div class="blog_h4"><span class="graybg">上下文工程</span></div>
<p>上下文工程回答的是“模型应该看到什么”。它关注记忆注入、检索回填、窗口压缩、状态摘要和工具返回整理，目标是控制输入信息的相关性与密度，使模型在有限上下文里获得完成当前任务所需的关键材料。</p>
<div class="blog_h4"><span class="graybg">Harness Engineering</span></div>
<p>Harness Engineering 回答的是“模型在什么系统里工作”。它处理的不再只是输入文本，而是工具接口、状态持久化、架构边界、验证回路、错误恢复、执行顺序与持续清理。到了这一层，关注点已经从“如何让模型回答得更好”转向“如何让 Agent 在真实系统中稳定完成工作”。</p>
<p>Harness 并不等同于某一句系统提示，也不等同于某个框架的名称。框架（Framework）、运行时（Runtime）和工具链都可以参与实现 Harness，但 Harness 这个词强调的是<span style="background-color: #c0c0c0;">模型外围整套工作系统</span>，而不是其中任何一个单独组件。</p>
<div class="blog_h3"><span class="graybg">五大支柱</span></div>
<p>从系统设计视角，一个可用的 harness 通常由五类承重件组成：上下文管理（Context Management）、工具编排（Tool Orchestration）、安全护栏（Safety Guardrails）、反馈回路（Feedback Loops）与可读性/可观测性（Legibility / Observability）。前两者解决“Agent 知道什么、能做什么”，中间两者解决“何时纠偏、如何避免越界”，最后一类解决“系统当前到底发生了什么”。</p>
<div class="blog_h4"><span class="graybg">上下文管理</span></div>
<p>上下文管理决定 Agent 在每一步究竟能看到什么信息。它的目标是把系统提示、短期记忆、长期知识、工具返回和任务状态按层组织，让模型始终工作在信息足够但不过载的区间，而不是简单堆砌更多材料。</p>
<div class="blog_h4"><span class="graybg">工具编排</span></div>
<p>工具编排决定 Agent 能做什么，以及如何把动作接回推理链路。搜索、数据库、代码执行器、浏览器自动化和业务 API 只有在权限、输入输出格式、调用顺序与结果回填都被设计清楚后，才会从“外挂能力”变成稳定的执行系统。</p>
<div class="blog_h4"><span class="graybg">安全护栏</span></div>
<p>安全护栏负责限制 Agent 的越界空间。它包括写权限边界、架构约束、审批节点、沙箱、敏感操作限制与结构化状态约束，作用是把“模型可能做错事”转化为“系统不允许它在关键位置随意犯错”。</p>
<div class="blog_h4"><span class="graybg">反馈回路</span></div>
<p>反馈回路负责告诉 Agent 当前结果究竟对不对。测试、lint、evaluator、Sprint Contract、健康检查和人工验收都属于这一层。没有反馈回路，Agent 只能凭语言流畅性误判成功；有了反馈回路，系统才能把错误重新送回执行链路中修复。</p>
<div class="blog_h4"><span class="graybg">可读性与可观测性</span></div>
<p>可读性与可观测性负责让 Agent 看见系统真实状态。UI、日志、指标、追踪、截图、DOM 快照和运行时事件，都是 Agent 判断“系统发生了什么”的依据。若系统对 Agent 不可见，很多问题即使存在，也只能靠盲目试错去碰。</p>
<p>Harness 的成熟度，本质上就是这五类控制面被工程化到什么程度。后文的各个部分，基本都可以看作对这五类支柱的展开：先看 Agent 为什么会系统性失效，再分别讨论上下文、编排、知识、约束、可读性、验证与熵控制如何把这些失效压回系统边界之内。</p>
<div class="blog_h2"><span class="graybg">长运行 Agent 的系统性失效</span></div>
<p>长运行 Agent（Long-Running Agent）必须跨多个上下文窗口持续工作，每个新会话在启动时对前一会话发生的事情没有任何记忆。即使使用最强的前沿模型，如果只给一个高级别的提示词，Agent 也无法稳定构建出生产质量的复杂系统。实践观察到四类典型失效：</p>
<ul>
<li><span style="background-color: #c0c0c0;">过早宣告完成（Premature Victory Declaration）</span>：在项目进行到一定阶段后，Agent 环视已完成的工作，宣告整个项目已经完成，忽略仍未实现的功能。</li>
<li><span style="background-color: #c0c0c0;">脏状态遗留（Dirty State Handoff）</span>：会话结束时遗留未修复的 bug 或未记录的进度，下一个会话必须先花大量 token 恢复工作环境，而不是推进新功能。</li>
<li><span style="background-color: #c0c0c0;">伪完成标记（False Completion）</span>：Agent 完成代码改动并运行单元测试或 curl 命令后，在没有进行端到端验证的情况下把功能标记为已完成，而该功能实际上并不能正常工作。</li>
<li><span style="background-color: #c0c0c0;">上下文焦虑（Context Anxiety）</span>：部分模型在上下文窗口填充到一定程度后，会主动收尾、提前结束任务——即便任务尚未完成，这种过早的"善后行为"会导致中途截断的工作状态。</li>
</ul>
<p>这四类失效都不能靠"换一个更好的模型"自动解决。它们是系统性问题，需要 Harness 层面的工程设计来对抗。</p>
<div class="blog_h3"><span class="graybg">失败驱动的 Harness 演化</span></div>
<p>Harness 是通过失败持续生长的控制层。一个重要的工程判断是：当 Agent 在某类任务上反复失败时，应先把失败当作 Harness 缺口的定位信号。失败可能意味着工具接口缺失、文档不可达、架构边界不清、状态交接不足，或验收标准仍停留在模糊自然语言层面。</p>
<p>因此，Harness Engineering 的关键动作是把失败外化成新的系统组件：增加脚本、补充文档、收紧 lint 规则、拆出更明确的 Contract、补上 evaluator 或可观测性接入。每次失败若都能回写为新的约束与支撑物，Agent 系统就会逐步从“靠经验驱动”演化为“靠机制驱动”。</p>
<div class="blog_h2"><span class="graybg">上下文管理策略</span></div>
<div class="blog_h3"><span class="graybg">上下文焦虑与两种应对策略</span></div>
<p>上下文焦虑（Context Anxiety）是指模型在感知到上下文窗口即将耗尽时，主动把任务包装成"已完成"状态——即便还有大量工作未做。Anthropic 在 Claude Sonnet 4.5 上观察到这一现象尤为明显。</p>
<p>应对上下文耗尽有两种互相对立的策略：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">策略</td>
<td style="text-align: center;">机制</td>
<td style="text-align: center;">保留连续性</td>
<td style="text-align: center;">消除上下文焦虑</td>
<td style="text-align: center;">代价</td>
</tr>
</thead>
<tbody>
<tr>
<td>压缩（Compaction）</td>
<td>将早期对话摘要替换原文，同一 Agent 继续运行</td>
<td>是</td>
<td>否（焦虑可能持续）</td>
<td>摘要信息损失</td>
</tr>
<tr>
<td>重置（Context Reset）</td>
<td>清空上下文，新 Agent 从结构化交接物（Handoff Artifact）重启</td>
<td>否（全新起点）</td>
<td>是</td>
<td>需要构造高质量交接物；增加编排复杂度与延迟</td>
</tr>
</tbody>
</table>
<p>对于 Sonnet 4.5，仅靠 Compaction 不足以支撑长任务，Context Reset 成为 Harness 设计的必要组件。随着 Opus 4.6 的推出，其长上下文检索能力显著提升、上下文焦虑基本消除，Context Reset 可以从 Harness 中移除——这正是 Harness 组件应随模型能力动态调整的典型示例。</p>
<p>一个很有价值的经验规律是：上下文窗口并不是“越满越好”。多组实践都观察到，随着无关文档、工具说明和历史对话不断堆积，Agent 的推理质量会先升后降。工程上可以把这理解为上下文利用率的“甜蜜区间”：模型需要足够信息才能稳定工作，但一旦被冗余材料淹没，就会出现幻觉、循环、格式错误或低质量实现。Harness 的任务不是把更多 token 塞进去，而是让 Agent 在任意时刻只看到当前步骤真正需要的信息。</p>
<div class="blog_h3"><span class="graybg">跨会话状态传递</span></div>
<p>长运行 Agent 在会话之间传递状态，不能依赖模型的内部记忆，必须依靠外化的持久化制品（Persistent Artifacts）。实践中形成了一套固定的状态传递套件：</p>
<ul>
<li><span style="background-color: #c0c0c0;">进度文件（Progress File）</span>：纯文本日志，记录每个会话完成了什么、遇到了什么问题、下一步是什么。每个会话开始时读取，结束时更新。</li>
<li><span style="background-color: #c0c0c0;">功能列表文件（Feature List）</span>：结构化的功能需求清单，初始全部标记为未完成，Agent 逐项实现并通过测试后方可标记通过。使用 JSON 而非 Markdown——实验发现模型不当覆写或修改 JSON 文件的概率远低于 Markdown 文件。</li>
<li><span style="background-color: #c0c0c0;">启动脚本（init.sh）</span>：由初始化代理预先写好的环境恢复入口，负责安装依赖、启动必要服务并打印访问入口。后续 fresh session 不再重新猜测项目如何运行，而是把它作为标准恢复脚本，在需要时检查并调用。</li>
<li><span style="background-color: #c0c0c0;">Git 历史</span>：每个会话以描述性的 commit message 结束。Git 历史既是进度时间线，也是恢复机制——Agent 可以通过 <pre class="crayon-plain-tag">git revert</pre> 从错误改动中恢复。</li>
</ul>
<p>这套设计借鉴的是人类软件工程师的轮班交接实践：进度文件对应交班笔记，git commit 对应有记录的工作移交，init.sh 对应标准化的环境搭建步骤，功能列表对应待办看板。</p>
<p>当单个 Agent 已经能够跨会话持续推进后，下一个问题就不再是“如何记住过去”，而是“如何把复杂任务拆给更合适的执行者”。这正是多 Agent 架构出现的背景。</p>
<div class="blog_h2"><span class="graybg">多 Agent 架构模式</span></div>
<div class="blog_h3"><span class="graybg">GAN 启发的 Generator–Evaluator 结构</span></div>
<p>让单个 Agent 对自己的输出进行评估会产生<span style="background-color: #c0c0c0;">自评偏差（Self-Evaluation Bias）</span>：即使输出质量明显一般，模型也倾向于给出积极评价。这一问题在主观任务上尤为突出，在可验证任务上同样存在。</p>
<p>一个有效的应对结构来自生成对抗网络（Generative Adversarial Network, GAN）的启发：将生成者和评估者分离成两个独立 Agent。</p>
<pre class="crayon-plain-tag">[Generator Agent] &larr;── feedback ──[Evaluator Agent]
        │                                 │
        └──────── output artifact ────────┘</pre>
<p>关键发现：调教一个独立的评估者使其保持怀疑态度，远比让生成者对自己的输出保持批判性更为可行。评估者仍然是 LLM，仍然有宽容倾向，但针对评估者进行专项提示调优的效果可以稳定收敛，而自评调优往往无效。</p>
<div class="blog_h3"><span class="graybg">Planner–Generator–Evaluator 三 Agent 架构</span></div>
<p>在 Generator–Evaluator 基础上增加一个 Planner Agent，构成完整的三 Agent 架构：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center; width: 15%;">Agent</td>
<td style="text-align: center; width: 20%;">输入</td>
<td style="text-align: center; width: 20%;">输出</td>
<td style="text-align: center;">设计要点</td>
</tr>
</thead>
<tbody>
<tr>
<td>Planner</td>
<td>1–4 句用户提示</td>
<td>完整产品规格书</td>
<td>只关注产品上下文与高层技术设计，不指定实现细节（过度具体的规格会将错误级联到下游）；被提示在规格中寻找机会融入 AI 功能</td>
</tr>
<tr>
<td>Generator</td>
<td>产品规格 + Sprint Contract</td>
<td>可运行代码</td>
<td>按 Sprint 增量构建（早期模型）或持续运行（强模型）；完成 Sprint 后先自评，再交 Evaluator 审查；拥有版本控制权限</td>
</tr>
<tr>
<td>Evaluator</td>
<td>运行中的应用 + Sprint Contract 验收标准</td>
<td>通过/失败判定 + 具体可行的问题报告</td>
<td>通过 Playwright MCP 实际操作运行中的应用；设有硬性阈值，任一标准未达到则 Sprint 失败并返回修复</td>
</tr>
</tbody>
</table>
<p>Agent 之间的通信完全基于文件：一个 Agent 写文件，另一个读文件并响应。这种方式简单、可追溯、不依赖框架内部状态。</p>
<div class="blog_h3"><span class="graybg">初始化 Agent 与编码 Agent 的分离</span></div>
<p>对于需要跨多个上下文窗口持续工作的场景，一种实用模式是将第一次会话与后续会话用不同的提示驱动：</p>
<ul>
<li><span style="background-color: #c0c0c0;">初始化 Agent（Initializer Agent）</span>：仅在第一个会话运行，负责构建整套脚手架：根据项目规格生成 init.sh、功能列表 JSON、初始 git commit 和进度文件，把“项目如何启动、功能如何追踪、状态如何恢复”一次性外化成可执行制品。</li>
<li><span style="background-color: #c0c0c0;">编码 Agent（Coding Agent）</span>：从第二个会话起运行，先读取进度文件、功能列表和最近的 git 历史，再按需调用 init.sh 恢复环境，每次只实现一个功能，并以"干净状态（Clean State）"结束会话。</li>
</ul>
<p>两个"Agent"在技术上是同一套系统提示和工具集，区别仅在于初始用户提示不同——这是一种提示策略，而非架构分离。</p>
<p>这种设计的关键，在于把“如何把项目跑起来”从高 token 成本的推断问题，转成低成本、可重复、可审计的脚本执行问题，而不是简单多一个 shell 脚本。后续每个 fresh session 的标准启动序列因此可以稳定下来：先用 <pre class="crayon-plain-tag">pwd</pre> 与最近的 <pre class="crayon-plain-tag">git log</pre> 确认当前工作状态，读取进度文件和功能列表定位下一项任务，再检查并在需要时调用 <pre class="crayon-plain-tag">init.sh</pre> 恢复依赖、服务和访问入口。模型不再反复猜测包管理器、启动顺序、端口和运行命令，上下文预算因此可以集中到真正的实现与验证上。</p>
<div class="blog_h3"><span class="graybg">Sprint Contract：预协议的完成定义</span></div>
<p>Sprint Contract 是 Generator 与 Evaluator 在任何代码被写下之前，对"这一阶段完成的标准"达成共识的协议制品：Generator 提出将要构建什么以及如何验证成功，Evaluator 审查并确认，双方迭代直到达成一致。其目的是弥合高层用户故事与可测试实现之间的鸿沟，防止模糊需求在实现阶段演变为争议。</p>
<p>任务拆分解决了“谁来做”，但还没有解决“知识从哪里来、如何保持最新”。一旦 Agent 工作跨越多个会话、多个角色和多个代码域，知识组织方式本身就会成为 Harness 的一部分。</p>
<div class="blog_h2"><span class="graybg">知识库工程（Repository as System of Record）</span></div>
<div class="blog_h3"><span class="graybg">给 Agent 地图而非手册</span></div>
<p>“给 Agent 地图而非手册”讨论的是知识入口（Knowledge Entry Point）的设计原则：在大型、持续演化的代码库中，Agent 需要的是一套能够把它稳定引导到正确信息源的导航结构，而不是一份试图囊括所有细节的单体说明书。知识库工程的关键目标是让上下文可定位、可更新、可裁剪，而不是一次性塞满。</p>
<p>因此，这里的首要挑战是"如何让 Agent 在需要时找到正确信息"。"一个巨型 AGENTS.md 文件"的方案会失败，原因是多方面的：</p>
<ul>
<li>上下文是稀缺资源。一个巨大的指令文件会挤占任务本身、代码和相关文档的空间，导致 Agent 要么遗漏关键约束，要么对错误的目标进行优化。</li>
<li>过多的指导等于没有指导。当所有内容都被标记为"重要"时，Agent 会退化成局部模式匹配，而不是有目的地导航。</li>
<li>单体文档会迅速腐烂。人类停止维护，Agent 无法分辨哪些规则仍然有效，文件变成一个充满过期规则的吸引力陷阱。</li>
</ul>
<p>有效的替代方案是：<span style="background-color: #c0c0c0;">AGENTS.md 充当目录（Table of Contents），而非百科全书</span>。一份约 100 行的精简 AGENTS.md 作为上下文的入口，通过指针将 Agent 引导至结构化 docs/ 目录中的具体知识源。</p>
<p>AGENTS.md 还有一个更重要的角色：它不应是一次写完后长期冻结的静态文档，而应是失败驱动的活反馈循环。每当 Agent 因为命令使用错误、目录理解偏差、架构约束遗漏或工具接入方式不清而出错，最直接的修复方式往往就是把这类经验回写进 AGENTS.md 或其指向文档。这样，文档不再只是说明书，而是把历史失败压缩成未来会话可直接继承的系统记忆。</p>
<pre class="crayon-plain-tag">AGENTS.md            &larr; 约100行，地图角色
ARCHITECTURE.md
docs/
├── design-docs/
│   ├── index.md
│   └── core-beliefs.md
├── exec-plans/
│   ├── active/
│   └── completed/
├── generated/
│   └── db-schema.md
├── product-specs/
│   └── index.md
├── references/
│   ├── design-system-reference-llms.txt
│   └── ...
├── DESIGN.md
├── FRONTEND.md
├── PLANS.md
└── QUALITY_SCORE.md</pre>
<p>这种结构实现了渐进披露（Progressive Disclosure）：Agent 从一个小而稳定的入口开始，被引导到它需要的具体知识，而不是在启动时被所有信息淹没。</p>
<div class="blog_h3"><span class="graybg">组织级 Golden Path</span></div>
<p>当团队反复构建相似类型的系统时，Harness 不应只停留在单项目经验，而应上升为组织级的服务模板（Service Template）或黄金路径（Golden Path）。现实中的软件形态并不是无限多样的，常见项目通常集中在少数几类技术拓扑：前端应用、后端服务、数据流水线、内部工具。若能把每类拓扑常用的目录结构、启动脚本、验证流水线、可观测性接入、架构约束和 Agent 指令打包成模板，新项目就不必从零设计 Harness。</p>
<p>这类模板的价值不只是“脚手架复用”，更在于组织把高频工程判断沉淀成标准化控制面。团队成员在真实项目中学到的约束、回滚经验和验证方法，可以持续回流到模板；模板更新后，又会反过来提升后续所有项目的默认质量。Harness 一旦进入这个阶段，便不再只是某个工程师的个人技巧，而是组织级生产力资产。</p>
<div class="blog_h3"><span class="graybg">Agent 可见性边界</span></div>
<p>从 Agent 的视角来看，任何它在运行时无法在上下文中访问的知识，实际上不存在。存在于 Google Docs、Slack 线程或人脑中的决策，对 Agent 来说与从未发生过没有区别。唯一对 Agent 有效的知识是代码库中版本化的制品：代码、Markdown、Schema、可执行计划。</p>
<p>这一约束倒逼团队把越来越多的上下文推入代码库：对齐团队架构决策的 Slack 讨论、产品原则、工程规范，都需要以 Agent 可读的形式在仓库中存在。这不仅是文档实践，更是系统设计约束。</p>
<div class="blog_h3"><span class="graybg">机械化知识维护</span></div>
<p>知识库的有效性需要机械化强制执行，而不能依靠人工自律。专用的 linter 和 CI 任务验证知识库是否最新、交叉引用是否完整、结构是否正确。一个周期性运行的"文档园丁 Agent（doc-gardening agent）"扫描文档与实际代码行为之间的偏差，发现过期或不一致的内容后自动开启修复 Pull Request。</p>
<p>知识工程解决的是“找得到信息”，架构约束工程解决的是“即使知道该做什么，也不能随意乱做”。两者结合，Agent 才既有方向感，也有边界感。</p>
<div class="blog_h2"><span class="graybg">架构约束工程</span></div>
<div class="blog_h3"><span class="graybg">分层领域架构</span></div>
<p>Agent 在具有严格边界和可预测结构的环境中工作效果最好。在全 Agent 生成的代码库中，这一原则需要在工程层面提前落地：等到有数百名工程师时再引入架构规则，通常已经太晚了——而在 Agent 驱动的代码库中，混乱会以比人类团队快得多的速度蔓延。</p>
<p>一种行之有效的结构是<span style="background-color: #c0c0c0;">分层领域架构（Layered Domain Architecture）</span>：每个业务域被划分为固定的层集合，依赖方向被严格验证，仅允许有限的跨层边。例如，在一个业务域内部，代码只能沿固定顺序向前依赖（Types → Config → Repo → Service → Runtime → UI）；跨切面关注点（认证、连接器、遥测、特性标志）通过单一显式接口进入。其他所有依赖方向都被机械地禁止。</p>
<div class="blog_h3"><span class="graybg">机械化强制执行</span></div>
<p>架构规则通过定制 linter 和结构测试强制执行，而不是依赖文档和代码审查中的人工判断。关键实践：</p>
<ul>
<li>linter 的错误信息被设计成直接包含修复指令，从而将约束违反转化为 Agent 可消费的上下文。</li>
<li>在对人类团队而言显得迂腐的细粒度规则，对 Agent 来说是乘数效应：一旦编码，立即在所有代码上生效，无需逐一审查。</li>
<li>约束划定边界，边界内部的实现方式允许 Agent 自由选择——这类似于大型平台工程组织的管理模式：集中强制边界，局部授权自治。</li>
</ul>
<p>这套约束还包括"品味不变量（Taste Invariants）"：结构化日志、Schema 与类型命名规范、文件大小限制、平台特定的可靠性要求。这些规则用静态分析强制执行，将人类工程师的审美判断固化成可机器检查的规则。</p>
<div class="blog_h3"><span class="graybg">结构化状态与写权限约束</span></div>
<p>对 Agent 可以修改的内容施加精确的写权限，是防止状态腐蚀的重要手段。典型模式：功能列表文件中，Agent 只允许修改 <pre class="crayon-plain-tag">passes</pre> 字段，不得删除或改写任何测试条目。这种狭义写权限用强措辞的指令约束来传达："删除或编辑测试是不可接受的行为，这会导致功能遗漏或引入 bug。"</p>
<p>此外，在数据边界处解析数据形状（而不是 YOLO 式地推断数据结构），使用类型化 SDK 或 Schema 验证库，是保持 Agent 可以安全推理的代码库形态的基础约束。</p>
<p>边界清晰之后，系统仍然需要对 Agent “可见”。否则即使约束被写得很严，Agent 也只能在黑箱里试错，无法高效定位运行时问题。</p>
<div class="blog_h2"><span class="graybg">Agent 可读性（Legibility）</span></div>
<p>Agent 可读性（Agent Legibility）指的是：系统的 UI、日志、状态和指标对 Agent 来说是否足够可见、可解析、可操作，从而使模型能够直接观察应用行为、复现问题并验证修复结果。它关注的是系统是否为推理提供了足够清晰的外部反馈面。</p>
<p>随着代码吞吐量提升，人工 QA 容量会成为瓶颈。此时，关键路径不再是增加人力，而是让应用自身的 UI、日志和指标对 Agent 直接可读，使 Agent 能够自主复现 bug、验证修复并对应用行为进行推理。</p>
<div class="blog_h3"><span class="graybg">应用可读性</span></div>
<p>使应用对 Agent 可读的关键手段：</p>
<ul>
<li><span style="background-color: #c0c0c0;">每个 git worktree 独立启动一个应用实例</span>：Agent 可以针对自己的变更启动隔离的应用版本，互不干扰。</li>
<li><span style="background-color: #c0c0c0;">Chrome DevTools Protocol（CDP）接入</span>：将 CDP 接入 Agent 运行时，并封装操作 DOM 快照、截图和页面导航的技能，使 Agent 能够驱动浏览器、复现 bug 并验证修复，而不依赖人工截图传递。</li>
<li><span style="background-color: #c0c0c0;">Playwright/Puppeteer MCP</span>：在多 Agent 架构中，评估 Agent 使用浏览器自动化工具实际操作运行中的应用，而不是静态分析代码。这使得"应用看起来工作"与"应用实际可用"之间的差距得以被发现。需注意：浏览器原生弹窗（alert modal）对这类工具不可见，这是已知盲区。</li>
</ul>
<div class="blog_h3"><span class="graybg">可观测性栈</span></div>
<p>将完整的可观测性栈暴露给 Agent，使其可以查询日志、指标和追踪数据，是将模糊性能目标转化为 Agent 可执行任务的关键。每个 worktree 配备临时的本地可观测性栈（完成后自动销毁），Agent 可通过 LogQL 查询日志、PromQL 查询指标、TraceQL 查询链路追踪。</p>
<p>有了这种接入能力，"确保服务启动在 800ms 以内完成"或"这四条关键用户路径中没有 span 超过两秒"这类提示就变得可执行——Agent 可以直接查询数据、定位问题根源并实施修复，而不需要人工解读日志然后描述问题。</p>
<p>不过，看得见系统状态还不等于能正确判断任务是否完成。可读性回答的是“发生了什么”，验证闭环回答的是“这样是否算成功”。</p>
<div class="blog_h2"><span class="graybg">验证闭环</span></div>
<div class="blog_h3"><span class="graybg">主观评判的量化</span></div>
<p>对于没有二元正确性检验的任务（如界面设计、用户体验），需要将主观判断转化为具体的、可评分的标准，才能构建有效的验证闭环。一套已验证有效的前端设计评分框架包含四个维度：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">维度</td>
<td style="text-align: center;">评估内容</td>
<td style="text-align: center;">权重</td>
</tr>
</thead>
<tbody>
<tr>
<td>Design Quality（设计质量）</td>
<td>配色、字体、布局、视觉意象是否融合为一个有清晰气质和独特身份的整体，而非各部分的简单堆砌</td>
<td>高</td>
</tr>
<tr>
<td>Originality（原创性）</td>
<td>设计决策是否经过定制，还是使用了模板布局、库默认值、AI 生成惯例模式（紫色渐变白色卡片、未经修改的 stock 组件）</td>
<td>高</td>
</tr>
<tr>
<td>Craft（工艺）</td>
<td>字体层级、间距一致性、颜色对比度等技术执行质量</td>
<td>低（模型默认已做得较好）</td>
</tr>
<tr>
<td>Functionality（功能性）</td>
<td>用户能否理解界面功能、找到主要操作、完成任务而无需猜测</td>
<td>低（模型默认已做得较好）</td>
</tr>
</tbody>
</table>
<p>重要发现：评估者提示中的措辞本身（如"最好的设计是博物馆级别的"）会直接影响生成器的输出风格——不仅仅在评估反馈阶段生效，在第一次生成时就已经开始引导模型远离通用默认值。评估者需通过 Few-shot 示例加详细评分解析进行校准，以保持判断一致性并减少分数漂移。</p>
<div class="blog_h3"><span class="graybg">端到端测试与健康检查门控</span></div>
<p>单元测试和 curl 命令验证的是代码片段，而不是用户体验。端到端测试的原则是：<span style="background-color: #c0c0c0;">像人类用户一样测试</span>——通过浏览器自动化工具实际操作运行中的应用，才能发现"代码逻辑看似正确但功能整体不可用"的问题。</p>
<p>实施健康检查门控（Health-Check Gate）：在每个会话开始时，Agent 必须先运行基础端到端功能测试，确认应用处于正常工作状态，再开始实现新功能。如果发现应用处于损坏状态，应先修复，否则新功能会在损坏的基础上叠加，问题会更难追溯。</p>
<p>干净状态（Clean State）的定义：每个会话结束时，代码库应处于可合并到主分支的状态——无重大 bug，代码有序且有文档，后续开发者（或 Agent）可以直接开始新功能，而无需先清理遗留的乱局。</p>
<div class="blog_h3"><span class="graybg">功能验证缺口</span></div>
<p>Harness 案例里常见一个容易被忽略的盲点：系统也许已经很好地约束了内部质量，却未必充分验证了外部行为是否真的符合产品意图。目录结构、架构 lint、单元测试、Clean State 和持续回收都很重要，但这些机制首先保证的是内部一致性，而不是最终功能一定可用。</p>
<p>因此，成熟的 Harness 必须把<span style="background-color: #c0c0c0;">功能验证（Functionality Verification）</span>视为独立承重件，而不是默认附属物。真正可靠的系统需要对“用户是否真的能完成任务”建立单独的验证回路，例如任务级验收标准、浏览器自动化、独立评估 Agent、人工 spot check，或基于运行时信号的行为检查。能持续写代码只是起点；能持续验证行为，才是 Harness 真正闭环的标志。</p>
<p>即使单次任务已经具备了约束、可读性和验证，长期运行后的系统仍会累积漂移。Harness 因此不仅要管每一轮执行是否正确，还要管整个代码库是否在时间尺度上持续失真。</p>
<div class="blog_h2"><span class="graybg">熵控制与垃圾回收</span></div>
<p>Agent 会复制代码库中已存在的模式——即使这些模式并不理想。随着时间推移，全 Agent 生成的代码库会发生不可避免的漂移：重复工具函数扩散、文档与代码行为不同步、架构规则被逐渐侵蚀。早期的应对方式是每周花费 20% 的团队时间手动清理，但这完全不可扩展。</p>
<p>有效的替代方案是将"黄金原则（Golden Principles）"直接编码进代码库，并建立周期性的自动化垃圾回收流程：</p>
<ul>
<li>在代码库中明确记录有主见的、机械可检查的原则（如：优先使用共享工具包而非手写 helper；必须在边界处解析数据形状，不得基于猜测的结构构建逻辑）。</li>
<li>周期性运行后台 Agent 任务，扫描原则违反、更新质量评分、开启针对性重构 Pull Request。</li>
<li>大多数这类 PR 可以在一分钟内完成审查并自动合并。</li>
</ul>
<p>技术债务与高息贷款相似：持续小额偿还几乎总是优于积累后集中清算。人类工程师的品味判断被捕获一次，随后在每一行代码上持续强制执行——这是 Agent 驱动开发模式下特有的杠杆效应。</p>
<p>但这些组件本身并不是一成不变的。任何垃圾回收策略、验证门控或多 Agent 编排，背后都隐含着对当前模型能力的判断；一旦模型变化，Harness 的承重结构也需要重新评估。</p>
<div class="blog_h2"><span class="graybg">Harness 随模型演化（Load-Bearing Analysis）</span></div>
<p>Harness 中的每一个组件都编码了一个假设：模型目前无法独立完成这件事，所以需要这层脚手架。这些假设有两个失效原因：</p>
<ul>
<li>假设从一开始就是错的——模型其实能做到，但工程师没有测试验证就加了脚手架。</li>
<li>假设因模型能力提升而过时——昨天需要 Sprint 分解的任务，今天的模型可以持续完成而不失去连贯性。</li>
</ul>
<p>因此，评估哪些 Harness 组件是真正的<span style="background-color: #c0c0c0;">承重结构（Load-Bearing Components）</span>——每当新模型发布时，剥除不再有效的部分，添加利用新能力的新部分——是 Harness 工程师持续的工作职责。</p>
<p>一个典型的演化轨迹：Claude Sonnet 4.5 需要 Context Reset + Sprint 分解 + 完整三 Agent 架构；Opus 4.5 在三 Agent 架构下表现更好，Sprint 结构依然必要；Opus 4.6 消除了上下文焦虑，Generator 可以持续运行超过两小时而无需 Sprint 分解，Evaluator 从"始终必要"变为"在任务超出模型原生能力时才有价值"。这套 Harness 对于 Sonnet 4.5 来说恰到好处，对于 Opus 4.6 来说则是过度设计。</p>
<p>一个重要的元原则：<span style="background-color: #c0c0c0;">随着模型能力提升，有趣的 Harness 组合空间不会缩小，而是移动</span>。工程师的工作不是等待模型强大到不需要 Harness，而是持续找到下一个新颖的组合。</p>
<p>一旦系统的主要难点从“写出代码”转移到“设计并维护这套控制面”，工程师的职责也会随之改变。</p>
<div class="blog_h2"><span class="graybg">工程师角色的演变</span></div>
<p>在 Harness Engineering 语境下，工程师的工作重心发生了系统性上移。原本直接产出代码的工作，转变为设计 Agent 工作的环境、指定意图与验收标准、构建使 Agent 能可靠工作的反馈闭环，以及将人类判断和品味持续编码进系统。</p>
<p>核心原则可以概括为一句话：<span style="background-color: #c0c0c0;">Humans steer, agents execute</span>。这意味着人类在更高的抽象层次上工作：确定优先级、将用户反馈转化为验收标准、验证结果。当 Agent 遇到障碍时，工程师更应追问"缺少什么能力？如何让这个需求变得对 Agent 可理解且可强制执行？"</p>
<p>随着代码吞吐量的提升，许多传统工程规范变得适得其反。PR 在高吞吐系统中应该是短命的；测试 flakiness 有时更适合通过后续补丁修复而非阻塞合并；纠错的成本极低，等待的成本极高。这与低吞吐人工团队的工作假设完全相反。</p>
<p>不过，这些方法目前验证得最充分的，仍然主要是绿地项目或受控实验环境。一旦进入历史包袱深重的老系统，Harness 的建设顺序与成本结构都会发生变化。</p>
<div class="blog_h2"><span class="graybg">棕地改造的难题</span></div>
<p>当前公开的成功 Harness 案例大多发生在绿地项目、受控实验或可以从零搭脚手架的环境中。真正更难的问题，是如何把 Harness Engineering 引入一个已经运行多年、缺少架构边界、测试质量不稳定、文档残缺且历史包袱沉重的棕地代码库。这里最大的风险不是 Agent 不会写代码，而是现有系统本身已经缺乏足够清晰的控制面，Agent 一旦接手，只会更快复制并放大原有混乱。</p>
<p>因此，棕地改造不能照搬绿地模板，通常需要渐进式引入：先补最关键的边界约束，再建立最小可用的知识入口、验证门控和可观测性接入，最后才考虑多 Agent 编排或更高自治级别。换言之，Harness 在棕地场景里的首要任务不是提升速度，而是先把代码库变成一个对 Agent 可理解、可约束、可验证的环境。</p>
<div class="blog_h2"><span class="graybg">参考案例</span></div>
<p>OpenAI Codex 内部实验（2026 年 2 月）：三名工程师在约五个月内，通过零人工手写代码的流程，构建了一个拥有内部日常用户和外部 alpha 测试用户的内部 beta 产品。代码库规模约一百万行，覆盖应用逻辑、测试、CI 配置、文档、可观测性和内部工具。约 1500 个 PR 被合并，平均吞吐为每名工程师每天 3.5 个 PR，且随团队扩张吞吐还在提升。核心投入集中在：精简的 AGENTS.md 目录结构、分层领域架构 + 机械化 linter、应用 + 可观测性对 Agent 的完整可读性、黄金原则与周期性垃圾回收 Agent。</p>
<p>Anthropic 复古游戏编辑器对比实验（2026 年）：使用 Claude Opus 4.5，对同一个 prompt "构建一个 2D 复古游戏制作工具"进行两种模式的比较。单 Agent 单次运行：20 分钟，花费 9 美元，结果是布局浪费空间、核心玩法无法运行。完整三 Agent Harness（Planner + Generator + Evaluator，含 Sprint Contract）：6 小时，花费 200 美元，结果是 Planner 将一句 prompt 扩展为横跨 10 个 Sprint 的 16 项功能规格，游戏核心机制正常运行，并额外实现了精灵动画、AI 辅助关卡设计等超出原始 prompt 的功能。成本约为单次运行的 22 倍，但输出质量远超。</p>
<p>Anthropic 浏览器 DAW 实验（2026 年）：使用 Claude Opus 4.6（已移除 Sprint 分解），通过 prompt "构建一个基于 Web Audio API 的全功能浏览器 DAW"，三轮构建 + QA 循环总计 3 小时 50 分钟，花费 124.70 美元。最终产物包含可用的编排视图、混音台和传输控制；内置 AI Agent 可通过自然语言提示设置节拍/调性、演奏旋律、建立鼓轨、调节混音和添加混响，完成一段完整的短曲创作。</p>
<div class="blog_h1"><span class="graybg">检索增强生成（RAG）</span></div>
<p>检索增强生成（Retrieval-Augmented Generation, RAG）把“外部知识”通过检索在推理时注入到上下文里：先检索，再生成。它解决的不是模型能力问题，而是信息可得性与可控性问题：把知识放在可更新的外部存储里，而不是指望模型参数记住一切。</p>
<p>真正落地到生产后，RAG 很快会从“模型 + 向量库”的玩具结构，变成一套完整的知识处理系统。一个更接近现实的抽象是：<span style="background-color: #c0c0c0;">知识源接入、文档获取、分块、增强、索引、召回、融合、重排、上下文回拼、生成</span>。其中任何一步做得粗糙，最终回答质量都会明显退化。</p>
<p>从工程视角看，RAG 的核心是把知识库建成一个可持续演化的外部记忆系统，而不是简单把文本切块后丢进向量数据库。这里通常至少会区分三层对象：知识库（Base）负责来源配置、召回策略与索引策略；文档（Document）负责内容文件、语言、状态与分块配置；块（Chunk）负责可检索的最小语义单元及其摘要、问题、向量、命中统计等元数据。再往上，很多系统还会加一个目录（Catalog）层，把知识按业务树、产品线、权限域或租户边界组织起来。这种分层的意义在于：<span style="background-color: #c0c0c0;">RAG 检索的不是“一个文本文件夹”，而是一个带治理、带生命周期、带配置继承的知识对象系统</span>。</p>
<div class="blog_h2"><span class="graybg">生产 RAG 的系统视角</span></div>
<p>一个成熟的知识库系统通常会显式区分知识源（Source）与检索目标（Target）。知识源回答“内容从哪里来”，例如上传文件、网页抓取、代码仓库、目录扫描；检索目标回答“内容最终由谁提供召回能力”，可以是系统自建索引，也可以是外部知识平台。这样设计的好处是把“接入来源”与“服务出口”解耦：同一套文档采集和治理流程，可以接到不同的检索后端，而不用把采集逻辑和向量库实现绑死。</p>
<p>另一个关键决策是把知识处理做成异步流水线，而不是同步上传即就绪。现实中的知识库往往需要经历扫描、下载、解析、分块、摘要生成、问题生成、向量化、索引构建等多个阶段。把这些阶段拆开，一方面是因为它们耗时、依赖不同资源且失败模式不同；另一方面是为了支持恢复、重试、并发控制与增量更新。于是，生产 RAG 往往天然带有一个状态机：知识库有自己的状态，文档有自己的状态，块也有自己的状态。这个状态机的作用是把长链路处理过程显式化，使每个阶段都能被观察、重跑和局部修复。</p>
<p>教程中的“上传文档 ➡ embedding ➡ 检索 ➡ 回答”只覆盖了最小闭环。真实系统里的知识库会持续新增、修改、下线、重建索引，并不断调整切分方式和召回参数。因此，RAG 更接近“搜索系统 + 知识处理中台 + 生成模型”的组合，而不是一次性的离线脚本。</p>
<div class="blog_h2"><span class="graybg">分块策略（Chunking）</span></div>
<p>分块（Chunking）决定了“检索的最小单位”。块太大：召回相关信息但携带大量噪声；块太小：召回片段零散，缺上下文导致生成不稳。分块应与文档结构、问题类型、上下文窗口预算一起设计。</p>
<div class="blog_h3"><span class="graybg">固定大小分块</span></div>
<p>固定大小分块（Fixed-size Chunking）按 token/字符数切片，简单稳定，适合结构较弱的纯文本与大规模离线构建。常配合重叠窗口（Overlap）避免跨边界信息断裂。</p>
<div class="blog_h3"><span class="graybg">语义分块</span></div>
<p>语义分块（Semantic Chunking）用段落/标题/语义边界切分，目标是让一个 chunk 自洽（Self-contained）。它通常更适合技术文档与带层级结构的内容，但需要更复杂的解析与规则。</p>
<div class="blog_h3"><span class="graybg">递归分块</span></div>
<p>递归分块（Recursive Chunking）先按大结构切（章节/标题），再对子块继续切（段落/句子/固定大小），兼顾结构与长度约束，是很多工程实现的折中方案。</p>
<p>生产系统里的分块通常远比这三类更细。除了固定大小、递归和语义切分，还会按 Markdown、HTML、PDF、代码、表格、JSON、LaTeX、句子、段落、滑动窗口等内容类型选择不同 chunker。这背后的设计判断非常重要：<span style="background-color: #c0c0c0;">分块不是一个通用算法参数，而是内容理解策略的一部分</span>。代码和 API 文档更适合按语法结构或标题层级切；表格和 PDF 更需要保留版面边界；Markdown/HTML 则需要保留层次结构，甚至形成父子 chunk 树。</p>
<p>这类设计会直接改变召回质量。若把所有内容都按固定长度切块，标题、层级、页面边界、代码块和表格结构都会被抹平；若系统保存 chunk 的父子关系、边界分隔符、正文起止位置、重叠前后文与结构元数据，那么检索命中的就不再只是孤立片段，而是文档结构中的一个明确位置。此时的 RAG 更接近结构化文档检索。</p>
<p>Agentic Chunking 进一步把 LLM 引入切分阶段，由模型判断哪些内容应属于同一语义单元。它在复杂文档上可能带来更强的语义完整性，但代价同样明显：构建成本高、可重复性差、调试难度大，提示词质量会直接影响索引结果。因此它更适合作为高价值内容的增强型切分，而不适合作为默认的全量基础设施。</p>
<div class="blog_h2"><span class="graybg">向量数据库</span></div>
<p>向量数据库（Vector Database）存储每个 chunk 的向量表示（Embedding Vector）及其元数据（Metadata），支持近似最近邻（Approximate Nearest Neighbor, ANN）检索。向量并不“包含全文”，它是语义相似度的索引键；检索结果仍然需要回源拿到原文片段。</p>
<p>工程上需要区分“向量索引（Vector Index）”与“向量数据库（Vector Database）”：前者强调 ANN 检索算法与数据结构；后者强调持久化、增量更新、元数据过滤（Metadata Filtering）、分片/副本与多租户（Multi-tenancy）等系统能力。</p>
<p>生产知识库通常会同时维护多个索引平面：chunk 正文索引、chunk 摘要索引、chunk 问题索引，必要时还有文档摘要索引。它们对应的是不同的检索语义：正文索引擅长直接召回原文；摘要索引更适合长文压缩后的主题级匹配；问题索引则把 chunk 改写成更贴近用户查询的可检索意图。因此，生产 RAG 的索引设计本质上是“同一内容的多种检索视图”。</p>
<p>双后端架构也是常见现实。高质量知识库常把 Milvus/Qdrant 一类向量系统与 Elasticsearch/OpenSearch 一类全文检索系统并行使用：前者负责稠密向量、稀疏向量与 ANN 检索，后者负责 BM25、字段过滤和业务搜索。这种组合对应的是信号分工：向量擅长语义匹配，词项系统擅长精确关键词与过滤。真正的难点随之转移到一致性和融合上：索引命名要对齐，更新要同步，删除要清理，跨系统分数不能直接比较。</p>
<div class="blog_h3"><span class="graybg">实现形态与选型</span></div>
<p>向量检索组件通常有三种部署形态：向量索引库（如 FAISS/ScaNN）偏“库”；向量数据库（如 Milvus/Qdrant/Weaviate）偏“服务”；全文检索引擎（如 Elasticsearch/OpenSearch）则以 BM25 为核心并逐步补齐向量检索能力。许多生产系统采用“双引擎”组合：<span style="background-color: #c0c0c0;">向量库负责高性能 ANN，全文检索负责复杂过滤与关键词召回</span>，最终在融合/重排阶段统一排序。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">形态</td>
<td style="text-align: center;">代表实现</td>
<td style="text-align: center;">优势</td>
<td style="text-align: center;">局限</td>
<td style="text-align: center;">适用场景</td>
</tr>
</thead>
<tbody>
<tr>
<td>向量索引库（In-process Vector Index）</td>
<td>FAISS / ScaNN</td>
<td>单机性能极强；集成简单；易做离线批构建</td>
<td>分布式/多租户/权限/运维能力弱；元数据过滤能力有限</td>
<td>原型验证；单机检索；离线候选生成</td>
</tr>
<tr>
<td>向量数据库（Vector Database Service）</td>
<td>Milvus / Qdrant / Weaviate</td>
<td>持久化 + CRUD；支持元数据过滤；更易做水平扩展</td>
<td>需要运维；延迟/吞吐取决于索引与集群配置</td>
<td>生产 RAG；增量更新频繁；需要过滤/权限</td>
</tr>
<tr>
<td>全文检索 + 向量检索（Hybrid Search Engine）</td>
<td>Elasticsearch / OpenSearch</td>
<td>BM25 + 过滤能力强；生态成熟；适合业务检索</td>
<td>向量能力与性能细节高度依赖具体版本/配置；向量索引形态可选项较少</td>
<td>强关键词依赖；复杂过滤/字段检索；混合检索一体化</td>
</tr>
<tr>
<td>组合架构（Vector DB + Search Engine）</td>
<td>Milvus/Qdrant + ES</td>
<td>各取所长；向量与词项召回分别优化；融合/重排可控</td>
<td>系统更复杂；需要去重、打分对齐与一致性策略</td>
<td>对检索质量要求高，且有复杂过滤/排序逻辑</td>
</tr>
<tr>
<td>托管服务（Managed Service）</td>
<td>Pinecone / 各云厂商向量服务</td>
<td>运维成本低；弹性扩缩容；通常带权限与监控</td>
<td>成本较高；可控性与部署形态受限</td>
<td>快速上线；团队缺乏检索基础设施经验</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">ANN 索引与过滤</span></div>
<p>ANN 索引（ANN Index）在“速度、召回率（Recall）与内存”之间权衡。检索侧常见痛点是“向量相似度可算，但业务过滤很难做快”：元数据过滤会破坏 ANN 的近邻结构，使得系统需要在“先过滤再向量检索”和“先向量检索再过滤”之间做策略选择。</p>
<p>在生产知识库里，过滤通常不是可选项，而是第一等公民。目录树（Catalog Tree）、启用状态、文档类型、租户边界、语言、内容类型、业务域前缀，都可能成为过滤条件。也正因为如此，许多系统会把 catalog 前缀过滤、enabled 标志、source_type/source_id 等字段同时写入 Milvus 和 ES，两边都能先做过滤再做召回。否则，召回质量再高，也可能把“本不该返回的内容”混进上下文。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">索引/策略</td>
<td style="text-align: center;">核心思路</td>
<td style="text-align: center;">优点</td>
<td style="text-align: center;">代价</td>
</tr>
</thead>
<tbody>
<tr>
<td>Flat（Exact）</td>
<td>全量计算相似度</td>
<td>精确；实现最简单</td>
<td>规模上去后延迟/成本不可接受</td>
</tr>
<tr>
<td>HNSW</td>
<td>分层小世界图近邻搜索</td>
<td>高召回、低延迟；对在线增量友好</td>
<td>内存占用较高；过滤条件复杂时性能波动</td>
</tr>
<tr>
<td>IVF / IVF-PQ</td>
<td>先粗聚类（倒排），再在桶内搜索；PQ 进一步压缩</td>
<td>更省内存；适合大规模离线构建</td>
<td>更新成本高；召回/延迟依赖参数调优</td>
</tr>
<tr>
<td>DiskANN / Hybrid Memory</td>
<td>把索引/向量放到 SSD，内存放导航结构</td>
<td>降低内存压力；可支持更大规模</td>
<td>I/O 成为瓶颈；工程复杂度更高</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">检索策略</span></div>
<div class="blog_h3"><span class="graybg">稠密检索（向量相似度）</span></div>
<p>稠密检索（Dense Retrieval）用向量相似度（如余弦相似度或内积）做 top-k 召回，擅长语义匹配与同义改写，但可能漏掉精确关键词（如错误码、版本号）。</p>
<p>它还有一个常被低估的前提：embedding 模型的训练分布必须与目标领域足够接近。若把主要基于互联网通用语料训练出来的嵌入模型，直接拿去检索法律、医疗、专利或企业内部术语密集的文本，向量空间中的“相似”往往就不再等于业务上的“相关”，召回质量会明显下降。此时更稳妥的做法通常是引入领域微调 embedding、混合检索，或让 sparse / full-text 路径共同兜底。</p>
<div class="blog_h3"><span class="graybg">全文检索（BM25 / ES）</span></div>
<p>全文检索（Lexical Retrieval）以 BM25 等词项匹配为核心，擅长精确匹配与关键词召回。它对拼写、专有名词、数字更敏感，但对语义改写不够鲁棒。</p>
<div class="blog_h3"><span class="graybg">稀疏检索（Sparse Retrieval / SPLADE）</span></div>
<p>稀疏检索（Sparse Retrieval）用“稀疏向量”表示文本：向量维度对应词项（或子词），权重表示该词项对匹配的贡献。与 BM25 相比，稀疏检索的权重来自模型学习而非纯统计；与稠密检索相比，它保留了词项级可解释性与对罕见词/数字的敏感性。</p>
<p>典型路线包括 SPLADE（Sparse Lexical and Expansion Model）：通过学习得到“词项扩展（Lexical Expansion）”，让语义相关但不共词的文本仍能在词项空间相遇。工程上，稀疏检索常作为混合检索的一路召回信号，而不是单独替代 BM25。</p>
<div class="blog_h3"><span class="graybg">混合检索</span></div>
<p>混合检索（Hybrid Retrieval）把稠密检索与全文检索结合：先分别召回，再融合排序（如加权、去重、学习排序）。这是目前最稳健的通用策略之一。</p>
<p>在更完整的知识库系统里，混合检索不只是“dense + BM25”两路，而可能是 <span style="background-color: #c0c0c0;">chunk / summary / question</span> 三类检索目标与 <span style="background-color: #c0c0c0;">dense / sparse / fulltext</span> 三类检索路径的组合矩阵。换句话说，生产 RAG 召回的是多个“信号通道”，而不是单一相似度函数。它的优势是覆盖面更强：原文命中、主题命中、问句改写命中可以互补；但代价也很直接：路径暴涨、分数更不可比、日志更难读、回归测试更复杂。</p>
<p>这类系统往往还会按知识库能力做“有效方法裁剪”。例如，用户请求 fullhybrid，但某个知识库只建了 dense 和 sparse 索引，那么实际就只能退化成 hybrid；某个库没有开启 question recall，就不该在该路径上浪费资源。这个细节反映出一个成熟的 RAG 判断：<span style="background-color: #c0c0c0;">检索策略不是全局写死的，而是知识库级配置与请求级策略共同决定的</span>。</p>
<div class="blog_h4"><span class="graybg">分数融合（Score Fusion）</span></div>
<p>混合检索的难点不在“多路召回”，而在“分数不可比（Incomparable Scores）”：BM25 分数、余弦相似度/内积分数、稀疏向量分数往往不在同一尺度，不能直接相加。常见融合策略：</p>
<ul>
<li>归一化后加权：对各路分数做 min-max / z-score 等归一化，再做加权求和或加权乘积。</li>
<li>基于排序的融合：不依赖原始分数尺度，例如 RRF（Reciprocal Rank Fusion）：<span displaypfx="inline-" class="mathjax-container">\(\mathrm{score}(d)=\sum_{s}\frac{1}{k+\mathrm{rank}_s(d)}\)</span>。</li>
<li>学习排序（Learning to Rank）：把各路分数与特征（BM25、embedding sim、字段匹配、长度等）作为特征，训练一个排序模型做融合。</li>
</ul>
<p>生产系统通常更偏爱基于排序的融合，而不是直接拼原始分数。这是因为 ES 的 <span displaypfx="inline-" class="mathjax-container">\(_score\)</span>、Milvus 稠密距离、Milvus 稀疏 BM25 分数往往来自完全不同的评分体系。RRF 或加权 RRF 的价值就在于鲁棒：它不要求各后端分数可比，只要求各路径给出相对顺序。若再往上追求精度，则会在粗召回后接一个模型重排器，对融合后的候选做统一判断。</p>
<div class="blog_h3"><span class="graybg">相关性阈值</span></div>
<p>相关性阈值（Relevance Threshold）决定的是：召回结果里哪些片段值得继续送给模型，哪些片段应该被直接丢弃。很多 RAG 系统的问题并不是“完全没召回到东西”，而是把大量边缘相关、低置信度甚至明显错误的片段一并塞进上下文，结果让生成阶段被噪声牵着走。</p>
<p>因此，top-k 本身通常不够；工程上还需要结合相似度阈值、分数差值阈值或最小证据数量规则共同判断。阈值设得过低，会把噪声片段混进上下文；阈值设得过高，又可能让系统在真实有答案时错误地进入“未检索到结果”分支。成熟系统往往会把阈值当作知识库级和请求级都可调的参数，并配合离线标注集或在线反馈持续校准。</p>
<div class="blog_h2"><span class="graybg">查询改写</span></div>
<p>查询改写（Query Rewriting）解决的问题是：用户真正表达的检索意图，经常并不等于用户字面上输入的那句话。尤其当用户提问冗长、上下文依赖强、代词很多、问题里混有解释性语句而不是检索关键词时，直接拿原问题去召回，往往不如先把问题改写成更适合检索的形式。</p>
<p>查询改写的核心是把问题重新组织成更适合索引命中的检索表达，例如补全省略主语、显式写出实体名、拆开复合问题、提取关键词、或把对话上下文中的隐含约束展开成可检索文本。</p>
<div class="blog_h3"><span class="graybg">多查询 RAG</span></div>
<p>多查询 RAG（Multi-Query RAG）会先把一个原始问题改写成多个语义相近但表述不同的检索查询，再分别召回并合并结果。它的动机很直接：单一路径的 query 很容易受措辞影响，而多个改写版本可以覆盖不同的关键词、别名、问题角度与表达方式，从而提高召回覆盖率。</p>
<div class="blog_h3"><span class="graybg">多跳 RAG</span></div>
<p>多跳 RAG（Multi-hop RAG）适合那些答案依赖多段证据链的查询。它通常不会指望一次检索就拿到最终答案，而是先检索第一跳证据，再根据第一跳结果生成下一跳 query，继续检索后续证据。于是，检索过程本身就变成了一条“检索 ➡ 读证据 ➡ 改写下一问 ➡ 再检索”的链式推理流程。</p>
<div class="blog_h3"><span class="graybg">智能体 RAG</span></div>
<p>智能体 RAG（Agentic RAG）则把查询改写提升为一个可反复决策的过程：模型不只改写一次 query，而是根据当前召回质量、证据缺口、工具反馈与上下文状态，决定是否继续搜索、换一种问法、切换知识库、触发重排，或干脆停止检索进入回答阶段。它的重点已经不只是“改写 query”，而是让检索成为 Agent 控制流的一部分。</p>
<div class="blog_h2"><span class="graybg">重排序（Reranking）</span></div>
<p>重排序（Reranking）在“召回”（Recall）之后提升“相关性排序”（Precision@k）：用更强但更慢的模型对候选片段重新打分。</p>
<p>成熟 RAG 系统里的 rerank 还常分成两层。第一层是每个知识库内部的局部重排：对该库自己的候选做加权、RRF 或模型精排。第二层是多知识库结果合并后的全局重排：当查询同时打到多个知识库时，再在总候选集合上做一次统一排序。这样做的动机很现实：单库内部的分数可比较，并不代表跨库结果也天然可比较；若不做第二层，全局 top-k 往往会被某个打分体系更“激进”的知识库垄断。</p>
<div class="blog_h3"><span class="graybg">Cross-Encoder Reranker</span></div>
<p>Cross-Encoder Reranker 把 query 与候选 chunk 拼接后输入同一个编码器，用注意力做细粒度匹配，相关性通常更高，但计算更慢，适合对 top-k（例如 50→10）做精排。</p>
<p>典型输入形式是 <pre class="crayon-plain-tag">[CLS] query [SEP] doc [SEP]</pre>（或对应模型的特殊 token）。在 BERT 类模型中，token type embeddings（也称 segment embeddings）可用于区分两段文本；随后 Transformer 的全连接自注意力允许 query token 与 doc token 在每一层发生深度交互，这正是 cross-encoder “cross” 的本质。</p>
<p>打分通常取 <span displaypfx="inline-" class="mathjax-container">\([\mathrm{CLS}]\)</span> 位置的最终隐向量 <span displaypfx="inline-" class="mathjax-container">\(h_{\mathrm{CLS}}\)</span> 过一个线性层得到相关性分数（或二分类 logits）。与双编码器（Bi-Encoder / Dual Encoder）相比，cross-encoder 更准但无法离线预计算候选向量，因此在线成本更高。</p>
<div class="blog_h3"><span class="graybg">LLM Reranker</span></div>
<p>LLM Reranker 用大模型直接判断相关性或做对比选择，能融合更复杂的语义与任务约束，但成本更高且需要防止“自信乱判”。工程上常用它做少量候选的最终筛选。</p>
<div class="blog_h3"><span class="graybg">生成式重排序（Generative Reranking）</span></div>
<p>生成式重排序（Generative Reranking）把“相关性打分”转写为生成任务：把 query 与 doc 拼接输入解码器（Decoder-only）或编码器-解码器（Encoder–Decoder），让模型生成一个标签 token（例如 <pre class="crayon-plain-tag">Yes/No</pre>）或一个短评分文本，并用生成概率作为分数。</p>
<p>常见的点式打分形式是二分类 log-odds：令 <span displaypfx="inline-" class="mathjax-container">\(y\in\{0,1\}\)</span> 表示是否相关，则</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{score}(q,d)=\log p_\theta(y=1\mid q,d)-\log p_\theta(y=0\mid q,d)\]</span>
<p>相比传统 BERT 类 cross-encoder，生成式 reranker 往往更擅长处理长文本匹配、复杂约束与隐含推理；工程上常用“轻量级 LLM + 领域数据微调”来获得显著增益。</p>
<div class="blog_h3"><span class="graybg">列表式重排序（Listwise Reranking）</span></div>
<p>列表式重排序（Listwise Reranking）把多个候选 doc 一次性送入模型，在同一个上下文窗口中共同建模“相对顺序”，而不是对每个（query, doc）独立跑一次 forward。直觉上，它让候选之间在 self-attention 中“同台竞争”，从而提升排序一致性，并降低多次推理带来的总开销。</p>
<p>实现上常见做法是：把候选 doc 按截断长度拼接成一个 list，要求模型输出每个 doc 的分数或名次；也有实现会在末尾使用一个专门的“汇聚 token”对每个 doc 读取表示。Listwise 方法的工程约束更强：候选数、每段 doc 的截断长度与模型上下文窗口会直接决定可吞吐的 top-k。</p>
<div class="blog_h3"><span class="graybg">训练目标与 Hard Negatives</span></div>
<p>精排训练的难点是“区分相似但不相关”：需要硬负样本（Hard Negatives）来逼迫模型学习细粒度差异。硬负样本通常来自第一阶段召回：对同一 query，取检索 top-k 中不相关的候选作为 negatives（也可混入 BM25 negatives、in-batch negatives、或对抗式 negatives）。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">目标</td>
<td style="text-align: center;">形式</td>
<td style="text-align: center;">直觉</td>
<td style="text-align: center;">常见风险</td>
</tr>
</thead>
<tbody>
<tr>
<td>点式（Pointwise）</td>
<td>学习 <span displaypfx="inline-" class="mathjax-container">\(s(q,d)\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(p(y=1\mid q,d)\)</span></td>
<td>把相关性当作二分类/回归</td>
<td>分数标定（Calibration）难；对“相对顺序”监督较弱</td>
</tr>
<tr>
<td>对式（Pairwise）</td>
<td>学习 <span displaypfx="inline-" class="mathjax-container">\(s(q,d^+)>s(q,d^-)\)</span></td>
<td>直接优化排序边际（Margin）</td>
<td>负样本质量决定上限；采样偏差可能放大</td>
</tr>
<tr>
<td>列表式（Listwise）</td>
<td>对候选列表联合优化（如 softmax over list）</td>
<td>更贴近 NDCG/MRR 等排序指标</td>
<td>计算与实现复杂；受上下文窗口约束</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">蒸馏（Knowledge Distillation）</span></div>
<p>蒸馏（Knowledge Distillation）把一个强但慢的教师模型（Teacher）产生的打分/偏好，转移给一个更快的学生模型（Student）。在重排序里，蒸馏最常见的目标不是“压缩参数”，而是<span style="background-color: #c0c0c0;">把 LLM 级别的相关性判断能力下放到可规模化的 reranker</span>，使线上能承载更大的 top-k、更高的 QPS 或更低的 P99。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">Teacher → Student</td>
<td style="text-align: center;">监督信号</td>
<td style="text-align: center;">常见损失</td>
<td style="text-align: center;">收益</td>
<td style="text-align: center;">主要风险</td>
</tr>
</thead>
<tbody>
<tr>
<td>LLM / 生成式 reranker → Cross-Encoder</td>
<td>软分数（log-odds / graded）或偏好对</td>
<td>回归（MSE）/ 对式（pairwise logistic）</td>
<td>显著降成本；保留较强语义与推理能力</td>
<td>学生上限受教师约束；教师偏差会被复制</td>
</tr>
<tr>
<td>Cross-Encoder → Bi-Encoder</td>
<td>相对顺序/分数</td>
<td>对比学习（InfoNCE）/ 蒸馏排序边际</td>
<td>把精排能力“下放”到召回，提高召回质量</td>
<td>需要大量 hard negatives；对领域漂移敏感</td>
</tr>
<tr>
<td>LLM → 数据标注（作为训练集构造器）</td>
<td>弱标注标签 + 解释/证据</td>
<td>按目标任务训练（点式/对式/列表式）</td>
<td>快速构造大规模领域数据；迭代快</td>
<td>噪声标注；需抽样人工复核与在线 A/B 验证</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">返回内容要组织完整证据上下文</span></div>
<p>RAG 检索阶段的最终产物并不一定只是“命中的那一小段 chunk”。很多高质量系统会把返回内容模式做成显式策略：只返回命中块正文、返回去掉 overlap 的核心正文、返回命中块加邻近上下文、或按文档中的位置范围把相关块递归拼回更完整的片段。这样做的原因是：模型生成真正需要的是<span style="background-color: #c0c0c0;">足够完整且边界清晰的证据上下文</span>，而不是孤立句子。</p>
<p>这里的关键判断是：检索命中的是索引单元，但喂给模型的应是生成单元。索引单元倾向短小、便于召回；生成单元则要兼顾上下文完整性、token 预算和可引用性。若两者完全绑定，系统通常会在召回精度与生成可读性之间来回拉扯。</p>
<div class="blog_h2"><span class="graybg">从项目设计能看到的几条经验</span></div>
<p>第一，RAG 的上限不只由 embedding 模型决定，而是由“分块质量、索引视图、融合策略、回拼策略、状态治理”共同决定。把注意力只放在换一个更强 embedding 上，通常抓不住主矛盾。</p>
<p>第二，知识库应当被理解为带状态机的外部知识资产，而不是静态文件集合。只有显式区分 base、document、chunk，并让它们有自己的生命周期，系统才可能支持增量更新、失败恢复、重建和审计。</p>
<p>第三，生产 RAG 常常天然走向双后端和多路径召回。这个方向的收益是更稳健的覆盖率，代价是系统复杂度、索引一致性成本和观测难度都会明显上升。因此它适合高价值知识库，不一定适合所有团队的第一版系统。</p>
<p>第四，把摘要和问题也视作可检索对象，是一个非常务实的设计。它等于承认“原文表述”与“用户提问方式”经常不一致，于是用知识增强知识本身的可检索性。但它也会带来额外索引、额外 embedding 成本与额外一致性问题，因此只有在召回质量确实成为瓶颈时，这种多视图索引才值得引入。</p>
<p>第五，外部知识平台同步并不等于“复制一份数据就结束”。一旦系统允许一部分知识由外部平台托管、另一部分知识由内部系统自建索引，那么“谁是事实源、谁负责更新、谁负责删除、谁负责召回”就必须在架构上说清楚。否则最容易出现的不是召回不到，而是召回出重复、过期或跨系统不一致的内容。</p>
<div class="blog_h3"><span class="graybg">主流选型对比</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">阶段</td>
<td style="text-align: center;">常见架构</td>
<td style="text-align: center;">代表实现</td>
<td style="text-align: center;">核心逻辑</td>
<td style="text-align: center;">速度</td>
<td style="text-align: center;">精度</td>
</tr>
</thead>
<tbody>
<tr>
<td>召回</td>
<td>双编码器（Bi-Encoder）</td>
<td>BGE-Embedding<br />text-embedding-3</td>
<td>向量相似度</td>
<td>极快</td>
<td>中</td>
</tr>
<tr>
<td>精排（稳健基线）</td>
<td>交叉编码器（Cross-Encoder）</td>
<td>BAAI<br />bge-reranker-v2-m3</td>
<td><span displaypfx="inline-" class="mathjax-container">\([q;d]\)</span> 深度交互</td>
<td>中</td>
<td>高</td>
</tr>
<tr>
<td>精排（顶配）</td>
<td>生成式 / 列表式（LLM-based）</td>
<td>BGE-Reranker-v2-Gemma<br />Jina-Reranker-v3</td>
<td>生成式打分或 listwise 竞争</td>
<td>较慢</td>
<td>极高</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">生成与融合</span></div>
<p>生成与融合（Generation &amp; Fusion）的关键在于：把检索到的证据（Evidence）以可控格式注入 prompt，并要求模型“基于证据回答”。常见做法包括：</p>
<ul>
<li>Stuffing：把 top-k 片段直接拼接进上下文（最简单，最易超预算）。</li>
<li>Map-Reduce：先对各片段分别提炼要点，再汇总生成最终答案（更稳，成本更高）。</li>
<li>Refine：先用少量证据生成初稿，再逐片段增量修订（适合长证据链）。</li>
</ul>
<div class="blog_h1"><span class="graybg">智能体（Agent）</span></div>
<p>检索增强生成（RAG）解决的是“模型缺少外部知识”的问题；智能体（Agent）解决的是“模型如何在真实环境中持续完成任务”的问题。一个真正可用的 Agent 不再是单次 prompt 的包装，而是一个带有状态（State）、工具（Tools）、记忆（Memory）与控制流（Control Flow）的运行时系统。它能够观察环境、决定下一步动作、调用外部能力、根据反馈修正策略，并在多轮交互中维持任务连续性。</p>
<div class="blog_h2"><span class="graybg">核心概念</span></div>
<p>从工程视角看，Agent 的本质是“以大模型为决策核心的软件系统”。模型负责理解、推理与生成；外层系统负责注入上下文、执行动作、保存状态、施加约束，并把环境反馈重新送回模型。只有这几层协同起来，模型才会从一次性回答器变成可持续工作的执行体。</p>
<div class="blog_h3"><span class="graybg">计划-执行</span></div>
<p>计划-执行（Plan-Execute）是智能体最常见的控制流之一：先把“用户目标”分解为一组可操作步骤（计划），再逐步调用工具/模型完成这些步骤（执行），并把中间结果写回状态（State）。当环境反馈与计划假设不一致时，触发再计划（Replan）。工程上，它的价值是把一次长输出变成可观测（Observable）、可重试（Retryable）、可中断（Interruptible）的多步过程。</p>
<div class="blog_h3"><span class="graybg">感知-推理-行动循环</span></div>
<p>感知-推理-行动（Perception-Reasoning-Action）循环是 Agent 的基本闭环。感知阶段读取环境状态，例如用户输入、网页 DOM、数据库返回值、日志、文件内容或工具回执；推理阶段根据当前目标、约束和历史状态决定下一步策略；行动阶段把决策落实为具体操作，例如搜索、调用 API、写文件、点击界面或向下游智能体发消息；随后再读取新反馈，进入下一轮循环。这个闭环的关键不在“会不会思考”，而在“每一轮是否拿到了正确观测、是否执行了正确动作、是否把结果正确写回状态”。</p>
<p>因此，Agent 设计首先是状态机（State Machine）设计。很多失败并不是模型推理能力不足，而是观测不完整、动作不可验证、状态写回混乱，导致下一轮推理建立在错误世界模型之上。一个好的 Agent runtime 会显式记录当前目标、最近动作、动作结果、异常信息与下一步计划，从而让每一轮循环都建立在稳定状态上。</p>
<div class="blog_h3"><span class="graybg">工具使用（Tool Use）</span></div>
<p>工具使用（Tool Use）让语言模型突破“只会生成文本”的边界，把外部系统接成自己的感官与执行器。工具既可以是只读型能力，例如搜索、数据库查询、文件读取；也可以是动作型能力，例如发送请求、执行 shell、操作浏览器、写入代码仓库。模型本身不直接执行这些动作，而是输出结构化调用意图，再由外层系统负责真正执行、捕获结果并返回。</p>
<p>工程上，Tool Use 的难点不在“把工具接上”，而在“把工具定义清楚”。一个好的工具接口需要明确输入 schema、输出 schema、权限边界、失败语义和重试策略。工具定义越模糊，模型越容易产生参数幻觉（Argument Hallucination）、错误调用和无效循环；工具定义越稳定，模型越容易学会把工具当作程序原语来组合。</p>
<div class="blog_h3"><span class="graybg">记忆系统</span></div>
<p>记忆系统（Memory System）负责解决跨步骤与跨会话连续性问题。没有记忆的模型每次调用都像“重新开机”；有记忆的 Agent 才能累积上下文、复用历史决策、根据过去错误调整行为。记忆并不等同于“把更多 token 塞进上下文窗口”，而是要把不同时间尺度、不同重要性的状态放进不同存储层，再按需检索。</p>
<div class="blog_h4"><span class="graybg">短期记忆（上下文窗口）</span></div>
<p>短期记忆（Short-term Memory）通常由上下文窗口（Context Window）承载，保存当前任务最活跃的信息：系统指令、最近几轮对话、正在执行的计划、刚拿到的工具结果、当前工作草稿等。它的优势是读取成本低、和当前推理绑定最紧；它的限制也同样明显：容量昂贵、注意力会稀释、信息越多不代表效果越好。</p>
<p>因此，短期记忆的核心工作是“裁剪”。优秀的 Agent 会把原始轨迹压缩成摘要（Summary）、状态（State）和待办项（Next Actions），只保留当前决策真正需要的变量。短期记忆承担的是工作记忆（Working Memory）的职责：它服务当前这一轮推理，而不是承担长期知识库的角色。</p>
<div class="blog_h4"><span class="graybg">长期记忆（外部存储）</span></div>
<p>长期记忆（Long-term Memory）存放在上下文窗口之外，常见载体包括向量数据库、关系数据库、对象存储、文档库、日志系统、代码仓库、任务看板与事件流。它保存的是“现在不必全部加载，但未来可能需要回忆”的信息，例如用户偏好、历史案例、项目规范、过去失败总结、已验证的事实与中间产物。</p>
<p>长期记忆要解决三个问题：写什么、怎么检索、何时遗忘。写入过多会让存储退化为噪声池；只做向量相似度检索又容易漏掉结构化约束与时间关系。成熟做法通常会把结构化状态、全文检索、向量召回与人工定义的索引结合起来，让 Agent 既能“语义回忆”，也能“精确定位”。</p>
<div class="blog_h4"><span class="graybg">记忆类型：情景 / 语义 / 程序</span></div>
<p>目前主流 Agent 设计常借用认知科学中的三类记忆划分：情景记忆（Episodic Memory）、语义记忆（Semantic Memory）与程序记忆（Procedural Memory）。这三类记忆对应的是“发生过什么”“知道什么”“怎么做”。把它们混在一个存储里，检索和写入策略很快就会失控；分层建模则更容易设计稳定的读写规则。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">记忆类型</td>
<td style="text-align: center;">保存内容</td>
<td style="text-align: center;">典型载体</td>
<td style="text-align: center;">工程价值</td>
</tr>
</thead>
<tbody>
<tr>
<td>情景记忆（Episodic）</td>
<td>具体事件、行动轨迹、失败案例、会话历史</td>
<td>任务日志、轨迹摘要、审计记录</td>
<td>支持回放、复盘、反思与重试</td>
</tr>
<tr>
<td>语义记忆（Semantic）</td>
<td>稳定知识、规则、事实、领域概念</td>
<td>知识库、文档索引、结构化事实表</td>
<td>支持检索、问答、约束判断</td>
</tr>
<tr>
<td>程序记忆（Procedural）</td>
<td>工作流程、技能脚本、工具使用模式</td>
<td>系统提示、工具说明、可复用 playbook</td>
<td>把“会做事”沉淀为稳定操作习惯</td>
</tr>
</tbody>
</table>
<p>其中，程序记忆对工程系统尤其关键。很多所谓“Agent 经验”最终都会沉淀成程序记忆，例如某个 API 的调用顺序、某类错误的排查流程、某个项目的提交流程。Harness Engineering 本质上就是把这些隐性经验逐步外化成可持续复用的程序记忆。</p>
<div class="blog_h2"><span class="graybg">推理与规划</span></div>
<p>推理（Reasoning）解决的是“当前这一步怎么想”；规划（Planning）解决的是“整个任务怎么走”。现代 Agent 往往同时需要二者：局部步骤要有足够的推理精度，长期任务要有阶段化计划与中途重规划能力。近两年的研究基本沿着四条路线收敛：交错式思考与行动、反思式自我修正、显式规划后求解，以及基于搜索的多路径探索。</p>
<div class="blog_h3"><span class="graybg">ReAct</span></div>
<p>ReAct（Reason + Act）是当前 Agent 范式最有影响力的起点之一。它把“思考轨迹”和“动作轨迹”交错起来：模型先给出一小步 reasoning，再发起 action，读取 observation 后继续下一步 reasoning。与只做链式思维（Chain-of-Thought）的做法相比，ReAct 的优势在于每一步都能接触外部环境，因此能在事实不确定、需要探索、需要工具辅助的任务上持续修正路径。</p>
<p>ReAct 的核心价值，在于让推理过程与环境反馈形成闭环，而不是单纯让模型“想得更多”。它也有明显代价：若没有停机条件、步数上限和工具约束，轨迹很容易冗长、循环或过度调用工具。因此，ReAct 更像 Agent 控制流的原型范式，生产系统通常会在其外层再加计划、验证、预算控制与错误恢复机制。</p>
<div class="blog_h3"><span class="graybg">Reflection（自我反思）</span></div>
<p>Reflection（自我反思）把“错误”变成下一轮决策的输入。其基本思想是：一次执行失败后，不只返回原始错误信号，还额外生成一段高信息密度的经验总结，说明失败原因、已验证无效的路径、下一次应当修改的策略。这样，模型获得的不是一个抽象的 reward，而是一段可以直接进入上下文的文字化梯度。</p>
<p>从研究谱系看，Reflexion 将这种思路系统化为 verbal reinforcement learning：把环境反馈转写成文本反思，并存入 episodic memory，供后续回合检索。工程上，这类方法通常以“执行后 Critic”“失败总结卡片”“经验回放摘要”等形式落地。它最适合高代价试错任务，例如代码修复、网页操作、长任务代理；前提是系统能可靠地区分“失败是策略问题”还是“失败只是偶然噪声”。</p>
<div class="blog_h3"><span class="graybg">Plan-and-Solve</span></div>
<p>Plan-and-Solve 的思想很直接：先显式列出求解计划，再根据计划执行。它针对的是零样本链式思维常见的 missing-step 问题，即模型直接开始求解时容易跳步骤、漏约束或在中途漂移。把“先规划、后执行”做成两个阶段后，模型的注意力会先聚焦在任务分解，再聚焦在逐步完成，从而显著降低长任务中的结构性遗漏。</p>
<p>这一思路在工程系统里几乎已经成为默认模式。很多 Coding Agent、Research Agent 与 Workflow Agent 都把 planner 和 executor 拆成不同阶段，甚至拆成不同角色。原因很简单：计划是一种低成本、高杠杆的中间产物。它既能让人类在写代码前审查方向，也能让系统在执行中持续对照“当前动作是否仍服务于原始目标”。</p>
<div class="blog_h3"><span class="graybg">MCTS（蒙特卡洛树搜索）</span></div>
<p>MCTS（Monte Carlo Tree Search，蒙特卡洛树搜索）把 Agent 的决策过程从“单路径生成”升级为“多路径探索”。系统不再只让模型给出一个下一步动作，而是展开多个候选分支，对不同轨迹进行模拟、评估和回传分值，再把计算预算集中到更有希望的分支上。对于需要长程依赖、路径选择、试探性探索的任务，这种显式搜索比单次贪心生成更稳健。</p>
<p>Language Agent Tree Search（LATS）是这一路线的代表性工作，它把 MCTS 与语言模型结合，让模型同时承担候选动作生成、状态评估和自我反思等角色。代价同样明确：搜索带来更高 token 成本、更复杂的状态管理和更重的工程编排。因此，MCTS 更适合高价值、高不确定性任务，例如复杂推理、博弈式决策、困难编程与网页任务，而不是每个简单问答都默认启用的通用套路。</p>
<div class="blog_h2"><span class="graybg">工具与行动</span></div>
<p>如果说推理模块决定“应该做什么”，那么工具层决定“究竟能做到什么”。当前主流 Agent 平台基本收敛出几类高频动作接口：结构化函数调用、受控代码执行、Web 检索、图形界面操作，以及把外部工具和资源标准化接入的协议层。它们共同构成了 Agent 的执行面。</p>
<div class="blog_h3"><span class="graybg">Function Calling</span></div>
<p>Function Calling（函数调用）是最基础、最通用的 Tool Use 形式。模型输出的不再是自然语言描述，而是一个满足 schema 的调用请求，例如函数名、参数对象、调用意图；应用侧接收到请求后真正执行代码，并把结果作为 tool result 返回。这样，模型负责“决定调用什么”，应用负责“保证执行正确”。</p>
<p>Function Calling 的真正意义是把自由文本接口收窄为结构化程序接口。只要参数 schema 足够清晰，模型就能稳定学会把外部系统当成可组合原语。这也是今天大部分 Agent 框架的底层抽象：上层看起来是“智能体会用工具”，底层通常仍是“模型在做结构化函数选择与参数填充”。</p>
<div class="blog_h3"><span class="graybg">Code Interpreter</span></div>
<p>Code Interpreter（代码解释器）让模型在受控沙箱中执行代码，从而把“语言推理”转换为“可验证计算”。它特别适合数据清洗、表格处理、统计分析、绘图、文件转换、轻量脚本自动化与需要精确数值结果的任务。与纯文本推理相比，Code Interpreter 的优势在于中间状态可执行、可复现、可检查。</p>
<p>这类能力本质上是把 Python 或其他运行时变成模型的外部工作台。模型负责提出程序，沙箱负责执行，系统再把 stdout、文件产物、异常栈和图像反馈给模型。只要隔离、资源限制和文件边界设置得当，Code Interpreter 往往是让 Agent “从会解释变成会计算”的最直接跃迁。</p>
<div class="blog_h3"><span class="graybg">Web 搜索</span></div>
<p>Web 搜索（Web Search）解决的是模型知识静态化问题。基础模型参数中固化的是训练时刻之前的分布，而 Agent 经常需要回答最新价格、最新政策、最新产品文档、最新论文或当前网页状态。把搜索能力接入后，模型可以先检索，再基于来源生成回答，并在必要时附带引用。</p>
<p>工程上，Web 搜索不是“把搜索结果贴给模型”这么简单。系统还需要处理查询重写、来源筛选、去重、可信度排序、片段抽取与多源融合。对于需要最新信息的任务，搜索应当被视为一等能力，而不是可有可无的外挂；否则 Agent 的错误会表现为“推理看起来正确，但事实已经过时”。</p>
<div class="blog_h3"><span class="graybg">Computer Use</span></div>
<p>Computer Use（计算机使用）让 Agent 直接在图形界面层面操作软件与网页，例如看截图、定位元素、移动鼠标、键入内容、滚动页面与读取屏幕变化。它的价值在于：当目标系统没有现成 API，或者真正业务流程本来就发生在浏览器/桌面界面中时，Agent 仍然可以像人类操作员一样完成任务。</p>
<p>这类能力也是最脆弱的一类动作接口。界面结构变化、网络延迟、弹窗遮挡、验证码、权限确认和安全风险都会放大失败概率。因此，Computer Use 应被视为“最后一层兼容性接口”：没有 API 时才使用，并始终运行在隔离环境中，为高风险动作保留人工确认与回滚机制。</p>
<div class="blog_h3"><span class="graybg">MCP（模型上下文协议）</span></div>
<p>MCP（Model Context Protocol，模型上下文协议）试图把“给模型接工具与上下文”这件事标准化。它定义了一套客户端-服务端协议，让宿主应用可以连接多个 MCP server，并以统一方式暴露工具（Tools）、资源（Resources）与提示模板（Prompts）。这样，模型不必为每个外部系统重新学习私有接线方式，应用也不必为每种模型重复造一套集成层。</p>
<p>在工程语义上，MCP 解决的是“工具可移植性”和“上下文供给标准化”。一个搜索 server、一个数据库 server、一个设计文档 server，只要遵循同一协议，就能被不同 Agent host 复用。当前生态已经把 MCP 从“工具协议”扩展成“上下文协议”：除了调用动作，也强调让模型按需读取结构化资源，而不是把所有材料一次性塞进 prompt。</p>
<p>截至 2026 年 3 月，MCP 官方稳定协议版本为 2025-11-25，2026 路线图正在推进会话扩展（session handling）、Server Cards、Registry 与 agent-to-agent 协作能力。对应用开发者而言，更重要的不是版本号本身，而是它标志着一件事：Agent 运行时正在从各家私有 Tool API，逐步走向可协商、可复用、可观测的开放接口层。</p>
<div class="blog_h2"><span class="graybg">多智能体系统</span></div>
<p>多智能体系统（Multi-agent System）指的是：把一个复杂任务分解给多个具有不同职责或不同上下文边界的 agent，由它们通过委派、协作、验证和状态传递共同完成目标。它关注的核心是如何把规划、执行、审查和并行探索写成一个可控的协作结构。</p>
<p>是否拆成多个 agent，取决于任务是否存在天然的角色分工、上下文隔离需求、并行空间与独立验证价值。很多简单任务用一个强单体 agent 加好工具即可；只有当单一上下文开始拥塞、单角色难以兼顾规划与执行、或多个分支可以并行推进时，多智能体系统才真正体现优势。</p>
<div class="blog_h3"><span class="graybg">角色分工</span></div>
<p>角色分工（Role Decomposition）指的是：在多智能体系统中，为不同 agent 分配稳定且边界清晰的职责，使每个 agent 只处理自己最应该承担的那一类决策与操作。它的作用是把复杂任务拆成多个可管理的视角与责任域，降低单一上下文同时兼顾规划、执行和审查的负担。</p>
<p>最常见的基本三角是 Planner、Executor 与 Critic：Planner 负责分解目标，Executor 负责实际操作，Critic 负责验证与挑错。这个结构的本质是在系统内部显式制造不同视角，避免同一个上下文同时承担规划、执行与审查，最终让错误在更早阶段暴露出来。</p>
<div class="blog_h4"><span class="graybg">Planner</span></div>
<p>Planner 负责把模糊目标转成结构化任务图，例如目标拆解、依赖关系、优先级、完成标准与失败回退条件。一个好的 Planner 不直接沉迷于实现细节，而是尽快产出可被执行层消费的中间表示，例如任务列表、阶段计划、验收清单或执行 DAG。这样，后续 agent 的上下文会更短、更稳定。</p>
<div class="blog_h4"><span class="graybg">Executor</span></div>
<p>Executor 负责把计划变成具体动作：调用 API、写代码、运行测试、检索资料、修改文件、操作界面。它通常需要最严格的权限控制，因为真正改变外部世界的是执行层，而不是规划层。把 Executor 的工具范围、写入范围和预算边界限定清楚，往往比继续优化模型 prompt 更能提升整体可靠性。</p>
<div class="blog_h4"><span class="graybg">Critic</span></div>
<p>Critic 负责验证“结果是否正确”。它可以检查事实一致性、运行测试、比对输出格式、审查架构约束、回放浏览器流程，或要求执行层补充证据。Critic 的价值在于给系统引入独立的否决权：没有独立验证的多智能体系统，本质上仍然只是把同一种错误复制了几遍。</p>
<div class="blog_h3"><span class="graybg">协作模式</span></div>
<p>角色定义清楚之后，下一步是决定这些角色如何协作。常见模式主要有顺序执行、并行执行与层级委派。三者区别不在名称，而在状态如何传递、谁拥有控制权、失败后如何收敛，以及系统是否允许多个 agent 同时写入同一世界状态。</p>
<div class="blog_h4"><span class="graybg">顺序执行</span></div>
<p>顺序执行（Sequential Execution）是最稳的模式：上一步完成并产出明确结果后，下一步才开始。它最适合依赖链强、可验证点清晰的流程，例如“先规划，再实现，再审查，再修复”。优点是状态简单、问题定位容易；缺点是吞吐量受限，前一环的错误会整体阻塞后续流程。</p>
<div class="blog_h4"><span class="graybg">并行执行</span></div>
<p>并行执行（Parallel Execution）用于把独立子任务同时推进，例如多个资料检索、多个候选方案探索、多个代码模块并行实现。它能显著提高吞吐，但前提是任务切分足够干净，或者系统拥有足够好的合并策略。并行化的真正难点在于如何避免它们争抢同一上下文、重复劳动或互相覆盖结果。</p>
<div class="blog_h4"><span class="graybg">层级委派</span></div>
<p>层级委派（Hierarchical Delegation）由一个上层 supervisor 或 manager 统一掌控目标和全局状态，再把子任务委派给不同 specialist。它适合长任务和复杂流程，因为全局控制权集中在上层，局部上下文压力则下沉到子 agent。代价是 supervisor 很容易成为瓶颈，因此需要明确委派粒度、回收机制与升级路径，避免系统退化成“一个总管在不停转述消息”。</p>
<div class="blog_h2"><span class="graybg">主流框架</span></div>
<p>Agent Framework 解决的是“如何把模型能力组织成长期可运行的软件系统”。主流框架的差异主要体现在三个层面：控制流表达方式、状态管理方式，以及多智能体协作的原生支持程度。选型时最重要的是它是否契合你的系统拓扑：你需要图式工作流、事件驱动编排、还是平台托管的工具执行与追踪能力。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">框架/形态</td>
<td style="text-align: center;">控制流模型</td>
<td style="text-align: center;">强项</td>
<td style="text-align: center;">代价</td>
<td style="text-align: center;">适用</td>
</tr>
</thead>
<tbody>
<tr>
<td>LangChain / LangGraph</td>
<td>链式/图式（Graph）</td>
<td>组件化；易组合 RAG、工具与记忆；LangGraph 适合复杂状态机</td>
<td>抽象层较多；需要明确工程边界</td>
<td>生产 RAG/Agent pipeline</td>
</tr>
<tr>
<td>AutoGen</td>
<td>事件驱动多智能体（Event-driven）</td>
<td>消息传递清晰；适合团队式协作与研究</td>
<td>系统设计自由度高；需要自定治理边界</td>
<td>多智能体实验；复杂协作流程</td>
</tr>
<tr>
<td>CrewAI</td>
<td>角色 + 任务流水线</td>
<td>任务编排直观；适合“岗位分工”式流程</td>
<td>可控性取决于框架提供的扩展点</td>
<td>面向业务流程的多角色 Agent</td>
</tr>
<tr>
<td>OpenClaw</td>
<td>自托管 Gateway + Agent runtime</td>
<td>多渠道接入、会话路由、插件与设备节点整合强</td>
<td>更像入口与运行时基础设施，不是最轻量的 Python 编排库</td>
<td>本地优先、多渠道助手、长期在线 Agent 网关</td>
</tr>
<tr>
<td>Hermes Agent</td>
<td>自治运行时（Autonomous Runtime）</td>
<td>内建 learning loop、skills、memory、profiles 与 delegation</td>
<td>系统较重；要真正发挥优势需要接受其运行时哲学</td>
<td>长期运行、自我积累、强工具使用的个人/团队 Agent</td>
</tr>
<tr>
<td>OpenAI Responses API / Agents SDK</td>
<td>平台托管（Managed）</td>
<td>内建工具、Tracing、Handoff 与托管能力完善</td>
<td>更依赖平台抽象；深度定制时需额外设计 runtime</td>
<td>快速构建生产 Agent；希望复用官方工具栈</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">LangChain / LangGraph</span></div>
<p>LangChain 偏组件化抽象，LangGraph 偏图式控制流。前者适合把模型、检索器、工具、记忆等模块快速拼接起来；后者适合把复杂 Agent 流程显式建模为图（Graph），把循环、分支、检查点和人机介入点写成状态转移。对于生产系统，LangGraph 的意义尤其明显：它让“Agent 不是一段 prompt，而是一个状态机”这件事可以被直接编码。</p>
<p>在多智能体方面，LangGraph 当前更强调基于工具调用（tool-calling）的 supervisor 模式，而不是把所有 agent 都包装成自由对话体。原因很实际：当协作关系被写成显式图结构后，上下文边界、路由条件和失败恢复都会更好控制。</p>
<div class="blog_h3"><span class="graybg">AutoGen</span></div>
<p>AutoGen 的长处在于把多智能体协作建模为显式消息系统。不同 agent 可以作为独立节点收发消息、触发工具、交换中间状态，因此很适合研究“团队式智能体”以及需要复杂路由的实验性系统。新版 AutoGen 也更强调事件驱动（Event-driven）与可扩展运行时，而不只是几个人工角色互相聊天。</p>
<p>它的代价也非常明确：自由度越高，系统边界越需要开发者自己画清楚。权限、可观测性、记忆持久化与异常恢复如果没有在框架外补齐，多智能体对话很容易演化成难以调试的消息洪流。</p>
<div class="blog_h3"><span class="graybg">CrewAI</span></div>
<p>CrewAI 把多智能体系统表达为“角色（Role）+ 任务（Task）+ 工具（Tool）”的业务编排语言，适合把组织分工直接映射到系统设计中。它的上手成本较低，尤其适合内容生成、运营流程、分析流水线这类天然按岗位拆分的任务。</p>
<p>这类框架的优势是表达直观，代价是底层控制流往往被隐藏得更深。只要任务依赖、重试策略、共享状态和权限边界开始复杂化，就需要确认框架是否允许你把隐含运行时重新显式化。</p>
<div class="blog_h3"><span class="graybg">OpenAI Responses API / Agents SDK</span></div>
<p>从当前官方工具栈看，OpenAI 已经把 Responses API 作为构建 agent-like application 的统一接口，并以 Agents SDK 提供更轻量的编排、handoff、guardrail 与 tracing 能力。这一路线的核心价值是把常见基础设施平台化：函数调用、Web 搜索、Computer Use、Code Interpreter、Tracing 与多智能体 handoff 都可以沿着同一套接口组织。</p>
<p>托管式能力的优点是默认路径短、集成成本低、官方工具之间协同更顺滑；缺点是当业务开始需要深度定制的状态机、自定义持久化策略或异构执行环境时，应用仍然需要在 SDK 之上补一层自己的 runtime。换句话说，托管平台减少的是“起步成本”，而不是彻底消灭 Agent 工程本身。</p>
<div class="blog_h3"><span class="graybg">OpenClaw</span></div>
<p>OpenClaw 更适合理解为<span style="background-color: #c0c0c0;">本地优先的 Agent Gateway 与运行时基础设施</span>，而不是单纯的 prompt 编排库。它的核心不是“怎样写一条 Agent workflow”，而是“怎样把一个长期在线的 Agent 安全地接到 Discord、Slack、Telegram、WhatsApp、iMessage 这类渠道上，并用统一 Gateway 管理会话、路由、插件、节点设备和工具能力”。</p>
<p>这一路线的长处在于入口治理和系统完整性。若需求是“我需要一个可长期运行、跨多个聊天入口、会话持续存在、支持插件和设备节点的个人或团队助手”，OpenClaw 的抽象更贴近真实系统边界。它的重心不是研究型多智能体消息编排，而是把<span style="background-color: #c0c0c0;">渠道、会话、路由、插件和运行时</span>做成一个统一控制平面。</p>
<div class="blog_h3"><span class="graybg">Hermes Agent</span></div>
<p>Hermes Agent 则更像<span style="background-color: #c0c0c0;">学习型 Agent Runtime</span>。它不是简单包装单一模型 API 的聊天壳，而是试图把长期记忆（Memory）、技能（Skills）、子 Agent 委派（Delegation）、工具调用、批处理、浏览器自动化、语音模式和消息网关放进一个统一自治运行时里。其最鲜明的卖点，是官方强调的 built-in learning loop：Agent 会把经验沉淀成可复用技能，并跨会话持续积累。</p>
<p>因此，Hermes 更适合那类“希望 Agent 长期运行、逐步形成操作习惯、沉淀程序性知识并持续复用”的场景。它的重点不是先把多渠道入口打通，而是让 Agent 在运行中越来越像一个有历史、有技能库、有工作记忆的长期执行体。</p>
<p>OpenClaw 与 Hermes 对比：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">维度</td>
<td style="text-align: center;">OpenClaw</td>
<td style="text-align: center;">Hermes Agent</td>
</tr>
</thead>
<tbody>
<tr>
<td>核心定位</td>
<td>自托管 Gateway / 渠道路由 / Agent 接入层</td>
<td>长期运行、自我积累的自治 Agent 运行时</td>
</tr>
<tr>
<td>更强的一侧</td>
<td>多渠道入口、会话与路由治理、插件与节点生态</td>
<td>记忆、技能沉淀、学习闭环、工具执行与多 profile 隔离</td>
</tr>
<tr>
<td>抽象重心</td>
<td>“把 Agent 接到哪里、如何长期在线、如何统一调度”</td>
<td>“让 Agent 如何长期工作、持续复用经验、并逐步变强”</td>
</tr>
<tr>
<td>更像什么</td>
<td>Agent 基础设施与控制平面</td>
<td>Agent 大脑与执行运行时</td>
</tr>
<tr>
<td>典型适用</td>
<td>个人 AI 助手、多聊天平台入口、设备联动</td>
<td>编码助手、研究助手、长期自动化执行体</td>
</tr>
</tbody>
</table>
<p>这两者并不是简单替代关系。一个偏“入口与治理”，一个偏“执行与成长”。如果系统目标是搭建长期在线、多入口、可治理的助手平台，OpenClaw 更贴近问题本身；如果目标是让 Agent 在长期运行中积累技能、形成风格、复用经验，Hermes 的抽象更自然。现实工程里，两类能力往往最终会逐步靠拢，但在当前阶段，它们仍代表了两条不同的主线。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-5">人工智能理论知识 - 智能体</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/ai-knowledge-quick-ref-5/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>人工智能理论知识 - Transformers和大模型</title>
		<link>https://blog.gmem.cc/ai-knowledge-quick-ref-4</link>
		<comments>https://blog.gmem.cc/ai-knowledge-quick-ref-4#comments</comments>
		<pubDate>Wed, 15 Apr 2026 17:45:34 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[AI]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=42151</guid>
		<description><![CDATA[<p>这一篇聚焦现代大模型主线，内容从 Transformer 架构出发，延伸到语言模型、多模态模型、预训练与微调，以及推理阶段优化。前一篇已经建立了神经网络与训练机制，这一篇继续回答现代基础模型是如何堆叠出来、如何适配任务、又如何在部署侧提升吞吐、延迟与显存效率；最后一篇将转入上下文工程、RAG 与 Agent 系统。 Transformers 概述 Transformer 是现代大模型最核心的统一架构。它最初被提出用于序列到序列（Sequence-to-Sequence）任务，但很快演化成大语言模型（Large Language Model, LLM）、视觉 Transformer、多模态模型以及各类基础模型（Foundation Model）的共同骨架。它之所以重要，不只是因为“效果好”，更因为它提供了一种高度模块化、可并行扩展、易于堆叠放大的建模方式：输入被表示成一串 token，对这些 token 的关系建模主要依赖注意力（Attention），而每一层又通过前馈网络（Feed-Forward Network, FFN / MLP）继续做非线性变换与特征重组。 <a class="read-more" href="https://blog.gmem.cc/ai-knowledge-quick-ref-4">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-4">人工智能理论知识 - Transformers和大模型</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>这一篇聚焦现代大模型主线，内容从 Transformer 架构出发，延伸到语言模型、多模态模型、预训练与微调，以及推理阶段优化。前一篇已经建立了神经网络与训练机制，这一篇继续回答现代基础模型是如何堆叠出来、如何适配任务、又如何在部署侧提升吞吐、延迟与显存效率；最后一篇将转入上下文工程、RAG 与 Agent 系统。</p>
<div class="blog_h1"><span class="graybg">Transformers</span></div>
<div class="blog_h2"><span class="graybg">概述</span></div>
<p>Transformer 是现代大模型最核心的统一架构。它最初被提出用于序列到序列（Sequence-to-Sequence）任务，但很快演化成大语言模型（Large Language Model, LLM）、视觉 Transformer、多模态模型以及各类基础模型（Foundation Model）的共同骨架。它之所以重要，不只是因为“效果好”，更因为它提供了一种高度模块化、可并行扩展、易于堆叠放大的建模方式：输入被表示成一串 token，对这些 token 的关系建模主要依赖注意力（Attention），而每一层又通过前馈网络（Feed-Forward Network, FFN / MLP）继续做非线性变换与特征重组。</p>
<p>从工程角度看，Transformer 的成功来自三件事的结合：第一，注意力机制让模型能直接建模长距离依赖，而不必像循环网络那样逐步传递状态；第二，层与层之间结构统一，非常适合在 GPU / TPU 上做大规模并行训练；第三，模型规模可以沿着层数、隐藏维度、注意力头数、词表大小与训练数据量持续扩展，于是它天然适合作为“可放大”的通用架构。</p>
<p>因此，理解 Transformer 不应只停留在“注意力公式怎么写”，还要把它看成一条完整的信息处理流水线：token 如何变成向量，向量如何在注意力里彼此通信，MLP 如何重组和放大模式，残差流（Residual Stream）如何把各层计算串接起来，最后这些中间表示又如何被任务头（Task Head）读出，变成分类结果、生成 token 或其他下游输出。<a href="https://blog.gmem.cc/wp-content/uploads/2026/03/transformers.webp"><img class="alignnone size-large wp-image-41705" src="https://blog.gmem.cc/wp-content/uploads/2026/03/transformers.webp" alt="transformers" width="1" height="1" /></a></p>
<p>&nbsp;</p>
<div class="blog_h3"><span class="graybg">整体架构</span></div>
<p>Transformer 的“基本计算单元”是一个 Transformer block：把注意力子层（Attention Sublayer）与前馈子层（FFN Sublayer）串联起来，并在每个子层外包一层残差连接（Residual Connection）与归一化（Normalization）。注意力子层的输出不是最终预测，而是作为中间表示继续送入 FFN 与下一层 Transformer block，逐层构建更抽象的特征。</p>
<p>典型层结构（概念上）可以写成：</p>
<span displaypfx="" class="mathjax-container">\[H'=\mathrm{Add\&amp;Norm}(H,\ \mathrm{Attention}(H)),\quad H^{\text{next}}=\mathrm{Add\&amp;Norm}(H',\ \mathrm{FFN}(H'))\]</span>
<p>这条式子描述的是一个 Transformer block 内部最核心的两步。这里 <span displaypfx="inline-" class="mathjax-container">\(H\)</span> 表示进入当前层的隐藏状态矩阵（Hidden States），形状通常是 <span displaypfx="inline-" class="mathjax-container">\(L\;\times d_{\text{model}}\)</span>： <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 是序列长度， <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span> 是每个 token 的隐藏维度。 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Attention}(H)\)</span> 表示注意力子层对整段序列做一次“彼此通信”后的结果：每个 token 会结合其他位置的信息，得到新的上下文化表示。</p>
<p>第一步 <span displaypfx="inline-" class="mathjax-container">\(H'=\mathrm{Add\&amp;Norm}(H,\ \mathrm{Attention}(H))\)</span> 中，<span style="background-color: #c0c0c0;">Add</span> 表示残差相加：把原输入 <span displaypfx="inline-" class="mathjax-container">\(H\)</span> 与注意力输出相加；<span style="background-color: #c0c0c0;">Norm</span> 表示再做归一化（通常是 LayerNorm）。残差的作用是保留原始信息并让梯度更容易穿过深层网络，归一化的作用是让数值尺度更稳定。经过这一步后，得到的 <span displaypfx="inline-" class="mathjax-container">\(H'\)</span> 可以理解为“已经完成一次上下文交互”的中间表示。</p>
<p>第二步 <span displaypfx="inline-" class="mathjax-container">\(H^{\text{next}}=\mathrm{Add\&amp;Norm}(H',\ \mathrm{FFN}(H'))\)</span> 则把 <span displaypfx="inline-" class="mathjax-container">\(H'\)</span> 送入前馈网络（Feed-Forward Network, FFN）。FFN 不负责 token 之间的信息交换，而是对每个位置的向量分别做非线性变换与特征重组。它更像是在每个 token 内部重新编码：放大有用模式、抑制无关模式，并把低层线索组合成更抽象的表示。再经过一次“残差相加 + 归一化”后，输出 <span displaypfx="inline-" class="mathjax-container">\(H^{\text{next}}\)</span>，作为下一层 Transformer block 的输入。</p>
<p>因此，这个公式的阅读顺序可以概括为：先让 token 之间通过注意力交换信息，再让每个 token 自己通过 FFN 重组特征。多层堆叠之后，模型就会沿着这条路径逐层把原始输入变成越来越适合任务头读取的高层表示。</p>
<p>这里还需要区分 Pre-LN（Pre-LayerNorm）与 Post-LN（Post-LayerNorm）。它们的区别在于 <span style="background-color: #c0c0c0;">LayerNorm 放在子层计算之前，还是放在残差相加之后</span>。</p>
<p>若是 Post-LN，概念上更接近前面那条写法：先做子层计算，再与输入做残差相加，最后归一化。例如注意力子层可写成 <span displaypfx="inline-" class="mathjax-container">\(H'=\mathrm{LN}(H+\mathrm{Attention}(H))\)</span>。若是 Pre-LN，则顺序改成“先归一化，再做子层计算，再走残差”：注意力子层更接近 <span displaypfx="inline-" class="mathjax-container">\(H'=H+\mathrm{Attention}(\mathrm{LN}(H))\)</span>，FFN 子层同理。</p>
<p>两者表达的功能主线相同：信息都要经过注意力与 FFN，再靠残差流向后传递。差异主要体现在训练动力学（Training Dynamics）上。Post-LN 更贴近原始 Transformer 论文的写法，直观上像“每次子层更新完，再把结果规范一下”；Pre-LN 则让梯度更容易沿残差路径稳定传播，因此在很深的大模型里更常见。工程实现会在 Pre-LN / Post-LN 之间选择，这会影响训练稳定性、学习率可用范围以及深层可训练性，但不会改变我们对 block 主流程的理解：<span style="background-color: #c0c0c0;">注意力负责跨 token 交互，FFN 负责单 token 特征重组，残差负责让信息与梯度顺畅穿层流动</span>。</p>
<p>Transformer 这个名字源自 “Attention Is All You Need” 论文：模型不再依赖循环结构来处理序列，而是通过注意力把序列表示不断变换（Transform）为更适合预测的表征。</p>
<p>Transformer 的参数（Parameters）不是单一矩阵，而是一组可学习张量的集合，主要包括嵌入（Embedding）、注意力投影（Attention Projections）、前馈网络（FFN）以及归一化的缩放/平移参数等。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">参数组</td>
<td style="text-align: center;">符号</td>
<td style="width: 35%; text-align: center;">典型形状（Typical Shape）</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>Token Embedding</td>
<td><span displaypfx="inline-" class="mathjax-container">\(E\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^{V\;\times d_{\text{model}}}\)</span></td>
<td>词表大小 <span displaypfx="inline-" class="mathjax-container">\(V\)</span>；常与输出头权重共享（Weight Tying）。</td>
</tr>
<tr>
<td>位置嵌入（Learned）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(P\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^{L_{\max}\;\times d_{\text{model}}}\)</span></td>
<td>仅当使用可学习绝对位置嵌入时存在；正弦位置编码无此参数。</td>
</tr>
<tr>
<td>注意力投影</td>
<td><span displaypfx="inline-" class="mathjax-container">\(W_Q,W_K,W_V\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^{d_{\text{model}}\;\times d_{\text{model}}}\)</span></td>
<td>实现上常把多头合并成一次线性投影，等价于 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^{d_{\text{model}}\;\times (H d_k)}\)</span>。</td>
</tr>
<tr>
<td>注意力输出投影</td>
<td><span displaypfx="inline-" class="mathjax-container">\(W_O\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^{d_{\text{model}}\;\times d_{\text{model}}}\)</span></td>
<td>对拼接后的多头输出做线性混合；并非 <span displaypfx="inline-" class="mathjax-container">\(H\;\times d_v\;\times d_{\text{model}}\)</span> 的三维张量。</td>
</tr>
<tr>
<td>FFN</td>
<td><span displaypfx="inline-" class="mathjax-container">\(W_1,W_2\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(W_1\in\mathbb{R}^{d_{\text{model}}\;\times d_{\text{ff}}},\ W_2\in\mathbb{R}^{d_{\text{ff}}\;\times d_{\text{model}}}\)</span></td>
<td>通常 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{ff}}\gg d_{\text{model}}\)</span>。</td>
</tr>
<tr>
<td>LayerNorm</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\gamma,\beta\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^{d_{\text{model}}}\)</span></td>
<td>每个 LayerNorm 有一组缩放与平移参数。</td>
</tr>
<tr>
<td>输出头（LM Head）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(W_{\text{vocab}},b\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(W_{\text{vocab}}\in\mathbb{R}^{d_{\text{model}}\;\times V}\)</span></td>
<td>把隐藏状态映射为词表 logits；常与 <span displaypfx="inline-" class="mathjax-container">\(E\)</span> 共享权重。</td>
</tr>
</tbody>
</table>
<p>不同 Transformer 变体在维度设置上差异很大。下面列的是几类典型公开模型的常见配置，既包括中等尺寸的主流模型，也包括 2025 到 2026 年仍处前沿位置的开源大模型。它们的共同点在于：即使是“中等尺寸”的主流模型，隐藏维度、层数、头数和 FFN 宽度也已经足够大；而到了开源前沿模型阶段，参数扩展往往不再只靠加深层数，而是同时叠加更宽的隐藏维度、更大的 FFN、MoE（Mixture of Experts）和更激进的注意力/KV 设计，因此模型内部表示天然是高维、分布式且跨层叠加的。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">架构类型</td>
<td style="text-align: center;">参数规模</td>
<td style="text-align: center;">层数</td>
<td style="text-align: center;">隐藏维度 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span></td>
<td style="text-align: center;">注意力头数</td>
<td style="text-align: center;">KV 头数</td>
<td style="text-align: center;">FFN / Intermediate 维度</td>
</tr>
</thead>
<tbody>
<tr>
<td>BERT-base</td>
<td>Encoder-only</td>
<td>110M 级（dense）</td>
<td>12</td>
<td>768</td>
<td>12</td>
<td>12</td>
<td>3072</td>
</tr>
<tr>
<td>GPT-2 Small</td>
<td>Decoder-only</td>
<td>124M 级（dense）</td>
<td>12</td>
<td>768</td>
<td>12</td>
<td>12</td>
<td>3072</td>
</tr>
<tr>
<td>Mistral 7B</td>
<td>Decoder-only</td>
<td>7B 级（dense）</td>
<td>32</td>
<td>4096</td>
<td>32</td>
<td>8</td>
<td>14336</td>
</tr>
<tr>
<td>Llama 3.1 8B</td>
<td>Decoder-only</td>
<td>8B 级（dense）</td>
<td>32</td>
<td>4096</td>
<td>32</td>
<td>8</td>
<td>14336</td>
</tr>
<tr>
<td>Qwen2.5 7B</td>
<td>Decoder-only</td>
<td>7B 级（dense）</td>
<td>28</td>
<td>3584</td>
<td>28</td>
<td>4</td>
<td>18944</td>
</tr>
<tr>
<td>Qwen3-235B-A22B</td>
<td>Decoder-only + MoE</td>
<td>235B 总参 / 22B 激活</td>
<td>94</td>
<td>4096</td>
<td>64</td>
<td>4</td>
<td>12288（dense）/ 1536（per-expert）</td>
</tr>
<tr>
<td>DeepSeek-V3 系列</td>
<td>Decoder-only + MoE + MLA</td>
<td>671B 总参 / 37B 激活</td>
<td>61</td>
<td>7168</td>
<td>128</td>
<td>128</td>
<td>18432（shared）/ 2048（per-expert）</td>
</tr>
</tbody>
</table>
<p>这张表也说明了一个很重要的趋势。到 2026 年，开源前沿模型已经不再沿着“单纯加深层数”这一条路线演化，而是出现了明显分化：Qwen3-235B-A22B 把层数推到 94 层，同时保持相对克制的隐藏维度，并通过 128 个专家、每 token 激活 8 个专家来放大总容量；DeepSeek-V3 系列则维持 61 层，但把隐藏维度提升到 7168，并叠加 DeepSeekMoE 与 MLA（Multi-head Latent Attention）来同时优化容量与推理成本。也就是说，前沿模型的“强”并不只表现为更深，而更多表现为<span style="background-color: #c0c0c0;">深度、宽度、专家稀疏性与注意力工程的联合扩展</span>。</p>
<p>对闭源顶级模型的层数，外界通常拿不到可靠公开配置，因此只能做工程上的区间推断。若它们仍以 Transformer block 为主体，那么从公开开源前沿模型的尺度看，显式层数大概率仍落在<span style="background-color: #c0c0c0;">数十层到一百多层</span>这一带，而不是简单增长到几百层甚至上千层；更常见的扩展手段，是增大隐藏维度、放大 FFN、引入 MoE、延长上下文、增加训练 token，或在同等层数下叠加稀疏注意力、递归计算与工具链调用。因此，对 GPT、Claude、Gemini 这类闭源顶级模型，更稳妥的判断不是“它们一定有多少层”，而是“它们很可能已经处在百层级上下、并辅以更复杂的宽度与稀疏化设计”。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/transformers-dims.png"><img class="alignnone size-large wp-image-41435" src="https://blog.gmem.cc/wp-content/uploads/2026/03/transformers-dims.png" alt="transformers-dims" width="1" height="1" /></a>Transformer 的不可解释性（Lack of Interpretability）不是由某一个参数“导致”，而是由整体机制共同产生：表示是分布式（Distributed）且高维的，多层叠加的非线性变换把因果链条变长；注意力权重可视化能提供线索，但它不是完整解释。</p>
<div class="blog_h3"><span class="graybg">知识如何存储</span></div>
<p>大模型中的知识通常以分布式表示（Distributed Representation）的形式分散在大量参数里，而不是由某一个感知机单独存储一条事实。单个单元更像一个局部特征探测器（Feature Detector）：它只对某种模式敏感；许多单元级联后，网络才能把低层简单模式组合成高层抽象概念。模型规模越大、层数越深、参数越多，可被编码的模式组合也越丰富，这正是大模型具备强表达能力与“知识容量”的原因之一。</p>
<p>对于 Transformer 这样的模型，知识并不是以“一层一个概念、一神经元一事实”的方式整齐排布，而更像是沿着残差流（Residual Stream）在多层之间不断被提取、重组、放大和读出。注意力层更擅长在上下文中定位相关信息、建立 token 之间的依赖；MLP 层则更像参数化的模式变换器或记忆单元，会把某些已经被触发的模式映射成更强的语义方向，再写回主表示中。</p>
<p>从经验上看，这种知识分布有一些常见规律。较低层往往更接近词形、局部模式与浅层统计相关性；中间层更容易出现实体属性、关系模式和事实联想的组合；较高层则更接近任务相关读出，也就是更接近“最后怎样把内部表示变成具体输出”的阶段，例如下一 token 预测、答案选择或标签判别。但这更像统计趋势，而不是严格分工：同一类知识往往会跨多个层段冗余存在，并通过许多参数共同表达。</p>
<p>因此，更准确的理解不是“第几层存了什么知识”，而是“不同层在知识处理流水线里承担了什么功能”。有的层更偏检索线索，有的层更偏关系组合，有的层更偏把结果变成可供输出头使用的表示。单个 MLP 模块有时可以表现出类似键值记忆（Key-Value Memory）的行为，但真正稳定的知识通常仍然是跨层、跨参数、跨方向分布的。也正因为这种分布式编码，大模型既能表现出较强的知识容量，也会显得难以直接解释和精确定位。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/transformers-knowledge.png"><img class="alignnone size-full wp-image-41449" src="https://blog.gmem.cc/wp-content/uploads/2026/03/transformers-knowledge.png" alt="transformers-knowledge" width="848" height="1264" /></a></p>
<div class="blog_h3"><span class="graybg">编码器-解码器</span></div>
<p>编码器-解码器（Encoder–Decoder）结构对应经典 Seq2Seq：编码器先对输入序列做双向自注意力（Bidirectional Self-Attention）编码，即编码器里的每个 token 都可以直接看到源序列中的其他 token，不使用因果掩码（Causal Mask），因此更擅长形成充分的上下文化输入表示；随后解码器在自回归生成（Autoregressive Generation）时，一边做因果自注意力（Causal Self-Attention），只看已经生成的前缀，一边通过交叉注意力（Cross-Attention）读取编码器输出。于是，编码器负责把“输入内容本身”编码清楚，解码器负责在“已生成前缀 + 编码器语义表示”条件下逐步生成输出。典型用于机器翻译、摘要、问答等“输入到输出”的条件生成任务（Conditional Generation），代表模型如 T5、BART。</p>
<div class="blog_h3"><span class="graybg">仅编码器</span></div>
<p>仅编码器（Encoder-only）结构使用双向自注意力（Bidirectional Self-Attention）：位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 可以看见所有位置（不做因果屏蔽）。它更擅长做“理解与表示”（Representation Learning），常见预训练目标是掩码语言建模（Masked Language Modeling, MLM）：把输入里部分 token 替换为 <span displaypfx="inline-" class="mathjax-container">\([\mathrm{MASK}]\)</span>，训练模型根据上下文预测被遮住的 token。代表模型如 BERT、RoBERTa；ELECTRA 则用“替换检测（Replaced Token Detection）”作为预训练任务，但架构仍是 Encoder-only。</p>
<p>注意“掩码（Mask）”在这里指的是 <span style="background-color: #c0c0c0;">MLM 的 token masking</span>，不是自回归解码里的因果 attention mask。</p>
<div class="blog_h3"><span class="graybg">仅解码器</span></div>
<p>仅解码器（Decoder-only）结构使用因果自注意力（Causal Self-Attention）：位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 只能看见 <span displaypfx="inline-" class="mathjax-container">\(j\le i\)</span> 的历史 token，通过三角形掩码避免“偷看未来”。它天然对应自回归语言建模（Causal Language Modeling, CLM）：最大化 <span displaypfx="inline-" class="mathjax-container">\(\prod_t p(x_t|x_{&lt;t})\)</span>。代表模型如 GPT 系列、LLaMA、Qwen。</p>
<p>这里的“掩码（Mask）”指的是 <span style="background-color: #c0c0c0;">attention 里的因果屏蔽</span>，与 MLM 的 <span displaypfx="inline-" class="mathjax-container">\([\mathrm{MASK}]\)</span> token 概念不同。</p>
<div class="blog_h3"><span class="graybg">预填充</span></div>
<p>对生成式 Transformer，尤其是 Decoder-only 模型以及 Encoder–Decoder 中的解码器侧，推理过程通常可分成两个阶段：预填充（Prefill）与解码（Decode）。Prefill 先把整段已知提示词（Prompt）一次性送入模型，计算每一层的隐藏状态，并把各层的 Key / Value 写入 KV Cache；Decode 则在此基础上逐步生成新 token，每一步只新增一个位置，再与历史缓存做注意力计算。</p>
<p>Prefill 阶段虽然仍然使用因果掩码（Causal Mask），但因为整段 prompt 在进入模型时已经全部已知，所以输入处理过程中所有 token 仍可并行处理：同一层里的 Query / Key / Value 投影、矩阵乘法以及 masked attention 都可以一次性并行完成。因果掩码只负责限制“当前位置不能看未来位置”，并不会把已知 prompt 重新变回按时间步串行处理。</p>
<p>生成阶段则通常是串行的。因为每一个新输出 token 都要作为前缀的一部分，参与下一个 token 的预测，所以 token 与 token 之间存在真实的自回归依赖，不能像 Prefill 那样沿序列长度整段并行展开。此时系统仍然可以利用 batch 并行、head 并行、张量并行、专家并行和内核并行，但在“生成顺序”这一维上通常必须逐步推进。这也是为什么长 prompt 场景下常说系统先经历一次计算密集（Compute-bound）的 Prefill，而进入连续生成后，瓶颈又经常转向 KV Cache 读取、显存带宽与调度开销主导的 Decode。</p>
<p>这个两阶段视角非常重要，因为后续很多工程优化都直接对应其中一个阶段：FlashAttention 对长序列 Prefill 的收益通常最显著；KV Cache、GQA / MQA、Paged Attention、Prompt Caching 与 Speculative Decoding 等，则更多是在优化 Decode 或同时兼顾两者。理解了 Prefill 与 Decode 的分工，再看 Transformer 推理优化时，许多“为什么这里快、那里慢”的现象就会变得自然。</p>
<div class="blog_h4"><span class="graybg">主流 Decoder-only 细节差异（选型表）</span></div>
<p>Decoder-only 成为主流之后，架构创新集中在“稳定性、KV Cache 成本与训练效率”三个轴：归一化/激活影响深层训练稳定性；注意力侧的 KV 结构决定长上下文推理成本；FFN 稠密/稀疏（MoE）与训练目标改造影响单位算力的有效学习信号。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">技术点</td>
<td style="text-align: center;">常见选项</td>
<td style="text-align: center;">动机</td>
<td style="text-align: center;">影响</td>
</tr>
</thead>
<tbody>
<tr>
<td>归一化（Normalization）</td>
<td>LayerNorm / RMSNorm</td>
<td>提升深层训练稳定性</td>
<td>RMSNorm 省掉去均值，算子更简单；实际表现依赖整体配方</td>
</tr>
<tr>
<td>激活/FFN（Activation/FFN）</td>
<td>GELU / SwiGLU / GLU 变体</td>
<td>门控提升表达力与稳定性</td>
<td>通常带来更好效果，但实现与吞吐会受内核支持影响</td>
</tr>
<tr>
<td>KV Cache 压力</td>
<td>MHA / GQA / MQA</td>
<td>减少 KV heads，降低显存与带宽</td>
<td>长上下文收益显著；可能牺牲部分表示自由度</td>
</tr>
<tr>
<td>KV 压缩（Latent KV）</td>
<td>低秩/潜变量压缩（如把 KV 投影到低维潜空间）</td>
<td>进一步压缩 KV Cache</td>
<td>上下文长度与并发能力提升，但架构更复杂、实现更依赖细节</td>
</tr>
<tr>
<td>FFN 稠密 vs 稀疏</td>
<td>Dense / MoE</td>
<td>用稀疏激活扩大参数容量</td>
<td>训练更复杂（路由/负载均衡）；推理吞吐依赖专家并行与缓存</td>
</tr>
<tr>
<td>预训练目标</td>
<td>Next-token / Multi-token Prediction（MTP）</td>
<td>提升单位 token 的监督信号密度</td>
<td>MTP 可能提高训练效率，但会改变解码对齐与训练配方</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">输入处理</span></div>
<p>Transformer 并不直接处理原始字符串。文本进入模型之前，必须先经过一条输入处理流水线：文本规范化、切分为 token、映射为 token id，再查表转成向量表示。只有完成这一步，后续的注意力、FFN 和位置编码才有可计算的离散输入。输入处理决定了模型“看见世界的最小单位”是什么，因此它不仅影响参数规模与推理效率，也会影响稀有词覆盖、跨语言能力、长度利用率以及生成结果的边界质量。</p>
<div class="blog_h3"><span class="graybg">Tokenization</span></div>
<p>Tokenization 的核心任务是把连续文本切分成模型可处理的离散符号序列。这个离散化过程看似只是“切词”，实际上定义了词表（Vocabulary）、序列长度、未知词处理方式以及字符到语义表示的映射粒度。若切得过粗，词表会过大、稀有词泛化差；若切得过细，序列会变长、计算成本升高。因此，现代语言模型通常采用子词分词（Subword Tokenization）：用有限词表在“整词”和“字符”之间取得平衡。</p>
<p>分词并不只是训练前的一道预处理工序，它深度参与了模型能力边界的形成。相同一句文本，换一种 tokenizer，模型看到的 token 序列长度、常见片段分布、数字和符号的切分方式都会变化，进而影响上下文利用率、训练效率、长文本成本、代码与多语言表现，甚至影响困惑度（Perplexity）等指标的可比性。也正因为如此，跨模型比较时，若 tokenizer 不同，很多“每 token 指标”都不能直接横向解读。</p>
<p>此外，现代 tokenizer 通常不只负责“切分”，还负责一组配套约定：例如保留哪些特殊 token（Special Tokens），如何处理大小写、空格、换行、标点、表情与 Unicode 字符，以及遇到词表里没有的片段时如何回退。像 <pre class="crayon-plain-tag">[UNK]</pre> 这样的未知词标记（Unknown Token）就是早期整词分词里常见的退路：当输入片段不在词表中时，直接映射成一个统一的“未知”符号。它的问题是信息损失很大，不同未知词都会塌缩成同一个 token。子词分词与字节分词之所以重要，一个核心原因就是它们大幅减少了对 <pre class="crayon-plain-tag">[UNK]</pre> 的依赖。</p>
<p>从风格上看，分词大致可以分为四类。第一类是整词分词（Word-level Tokenization）：把单词当作基本单位，优点是语义直观，缺点是词表会迅速膨胀，且对未登录词（Out-of-Vocabulary, OOV）非常敏感。第二类是字符分词（Character-level Tokenization）：把每个字符都当作 token，几乎没有 OOV 问题，但序列会显著变长，模型需要自己学习更多组合关系。第三类是子词分词（Subword Tokenization）：用常见片段构成词表，让高频词保持完整、低频词拆成片段，这是现代 NLP 最主流的折中路线。第四类是字节分词（Byte-level Tokenization）：直接在字节层处理输入，覆盖能力最强，跨语言和特殊符号最稳，但序列通常更长，对模型容量和训练配方要求更高。</p>
<p>因此，不同分词风格的本质取舍是：<span style="background-color: #c0c0c0;">词表越大，单个 token 的语义通常越完整，但 OOV 与稀疏性越严重；词表越小，覆盖越稳，但序列越长、建模负担越重</span>。现代大模型之所以大量采用 BPE、WordPiece、SentencePiece 或 byte-level BPE，本质上都是在这条权衡曲线上寻找更合适的工程平衡点。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">分词风格</td>
<td style="text-align: center;">基本单位</td>
<td style="text-align: center;">主要优点</td>
<td style="text-align: center;">主要代价</td>
<td style="text-align: center;">常见场景</td>
</tr>
</thead>
<tbody>
<tr>
<td>整词分词</td>
<td>单词</td>
<td>语义直观；序列较短</td>
<td>词表膨胀；OOV 严重</td>
<td>早期 NLP；规则较强的封闭词表任务</td>
</tr>
<tr>
<td>字符分词</td>
<td>字符</td>
<td>覆盖稳定；几乎无 OOV</td>
<td>序列长；组合学习负担大</td>
<td>鲁棒输入建模；字符级任务</td>
</tr>
<tr>
<td>子词分词</td>
<td>高频片段 / 子词</td>
<td>词表与序列长度折中较好</td>
<td>切分方式影响语义边界</td>
<td>BERT、T5、LLaMA 等主流文本模型</td>
</tr>
<tr>
<td>字节分词</td>
<td>字节</td>
<td>覆盖最强；特殊符号与多语言稳健</td>
<td>序列更长；训练成本更高</td>
<td>byte-level BPE、多语言与噪声文本</td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">BPE</span></div>
<p>BPE（Byte Pair Encoding）从字符（或字节）开始，通过统计合并高频相邻符号对（Pair Merge）逐步构建子词（Subword）词表。它的核心收益是用有限词表覆盖开放词汇：常见词被合并成整体，罕见词被拆成更小片段，减少 <span displaypfx="inline-" class="mathjax-container">\([\mathrm{UNK}]\)</span>。</p>
<p>BPE 的直觉可以概括为“把最常一起出现的片段逐步固化成一个 token”。例如，若训练语料里 <pre class="crayon-plain-tag">t</pre> 和 <pre class="crayon-plain-tag">h</pre> 经常相邻，就可能先合并成 <pre class="crayon-plain-tag">th</pre>；若 <pre class="crayon-plain-tag">th</pre> 与 <pre class="crayon-plain-tag">e</pre> 又高频共现，就可能继续合并成 <pre class="crayon-plain-tag">the</pre>。经过大量合并之后，词表里会同时存在完整高频词、常见词根、后缀、数字片段和标点组合。这样一来，模型既能用短序列表达常见模式，又不必为每个罕见词都预留独立词条。</p>
<p>从工程谱系上看，GPT 家族总体属于 BPE 路线的延伸：早期 GPT / GPT-2 风格 tokenizer 采用 byte-level BPE，把文本先映射到字节层，再做 BPE 合并；这种设计能更稳地覆盖任意 Unicode 文本、空格和特殊符号。对 OpenAI 当前模型生态而言，官方开发工具链中程序化分词通常使用 tiktoken；它对应的是面向具体模型的 encoding 体系，但核心思想仍然是 BPE 家族的子词压缩与高覆盖率路线。对开发者来说，更重要的实践结论是：<span style="background-color: #c0c0c0;">GPT 并不是“按词”切分，而是按 BPE 家族 tokenizer 切成子词或字节片段</span>；同一个自然语言单词，可能被切成一个 token，也可能被切成多个 token，取决于它在词表中的合并状态。</p>
<div class="blog_h4"><span class="graybg">WordPiece</span></div>
<p>WordPiece 与 BPE 同属子词分词（Subword Tokenization），但合并准则更偏向最大化语言模型似然（Likelihood）。BERT 系列常用 WordPiece，因此会看到以 <pre class="crayon-plain-tag">##</pre> 标记的子词前缀（如 <pre class="crayon-plain-tag">play</pre> + <pre class="crayon-plain-tag">##ing</pre>）。</p>
<div class="blog_h4"><span class="graybg">SentencePiece</span></div>
<p>SentencePiece 是一种分词器（Tokenizer）训练与推理框架（常见算法包括 BPE 与 Unigram LM）。它可以直接在原始文本上训练（不依赖空格分词），因此在多语言与无空格语言（如中文、日文）上更常用；LLaMA 等模型的 tokenizer 通常基于 SentencePiece。</p>
<div class="blog_h3"><span class="graybg">Token Embedding</span></div>
<p>Token Embedding 的核心是一个可训练的嵌入表（Embedding Table，也常被称为嵌入矩阵（Embedding Matrix））：</p>
<span displaypfx="" class="mathjax-container">\[E\in\mathbb{R}^{V\;\times d_{\text{model}}}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 是词表大小（Vocabulary Size），每一行对应一个 token 的向量表示。给定输入 token id 序列 <span displaypfx="inline-" class="mathjax-container">\((t_1,\dots,t_L)\)</span>，查表得到输入嵌入序列（Embedding Output）：</p>
<span displaypfx="" class="mathjax-container">\[X=\begin{bmatrix}E_{t_1}\\ \vdots\\ E_{t_L}\end{bmatrix}\in\mathbb{R}^{L\;\times d_{\text{model}}}\]</span>
<p>一些材料会把 <span displaypfx="inline-" class="mathjax-container">\(E\)</span>（参数表）和 <span displaypfx="inline-" class="mathjax-container">\(X\)</span>（某次输入的嵌入结果）都叫“嵌入矩阵”，容易混淆。区分的一个简单方式是：<span style="background-color: #c0c0c0;">E 是全词表参数，X 是当前输入的嵌入输出</span>。</p>
<p>在语言模型里，这张输入嵌入表常与输出处理中的语言模型头（LM Head）共享参数，即权重共享（Weight Tying）。这里先记住这一点即可；它的具体计算方式与工程含义放在后面的“输出处理”中展开。</p>
<div class="blog_h2"><span class="graybg">位置编码</span></div>
<p>位置编码（Positional Encoding）解决一个根本问题：注意力机制本身对输入顺序是置换不变（Permutation-Invariant）的，如果不显式注入位置信息，模型无法区分“AB”和“BA”。因此需要把“位置”以某种方式编码进每个 token 的表示。</p>
<div class="blog_h3"><span class="graybg">绝对位置编码</span></div>
<p>绝对位置编码（Absolute Positional Encoding）最常见的做法之一是学习一个位置嵌入表（Position Embedding Table）：</p>
<span displaypfx="" class="mathjax-container">\[P\in\mathbb{R}^{L_{\max}\;\times d_{\text{model}}}\]</span>
<p><span displaypfx="inline-" class="mathjax-container">\(L_{\max}\)</span> 是模型支持的最大位置索引数量（Maximum Position Index）。对长度为 <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 的输入序列，取 <span displaypfx="inline-" class="mathjax-container">\(P_{0:L}\)</span>（或 <span displaypfx="inline-" class="mathjax-container">\(P_{1:L}\)</span>，取决于实现）得到当前序列的位置嵌入矩阵 <span displaypfx="inline-" class="mathjax-container">\(P_{\text{seq}}\in\mathbb{R}^{L\;\times d_{\text{model}}}\)</span>。</p>
<p>Transformer 通常用逐元素相加把 token 嵌入与位置嵌入融合：</p>
<span displaypfx="" class="mathjax-container">\[H^{(0)} = X + P_{\text{seq}}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(H^{(0)}\)</span> 仍然是 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span> 维向量序列，不是“位置标量”。位置是否用一个标量并不重要；重要的是这种表示能让后续的线性层与注意力计算利用位置关系。高维位置向量提供了更丰富的可学习空间。</p>
<p>“相加会不会把信息混在一起、无法区分？”这个直觉常见，但对表示学习而言关键不是可逆性，而是可用性：模型不需要从 <span displaypfx="inline-" class="mathjax-container">\(H^{(0)}\)</span> 精确还原 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(P_{\text{seq}}\)</span>，只需要用它们的组合完成预测。并且在高维空间里，模型可以把“语义”和“位置”分配到近似正交（Approximately Orthogonal）的方向，使得线性变换能有效解耦。</p>
<p>一个二维玩具例子：令 token 向量 <span displaypfx="inline-" class="mathjax-container">\(x=(1,0)\)</span>，位置向量 <span displaypfx="inline-" class="mathjax-container">\(p=(0,0.1)\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(h=x+p=(1,0.1)\)</span>。如果模型的某个线性读出只看第二维（例如乘以 <span displaypfx="inline-" class="mathjax-container">\((0,10)\)</span>），就能强烈感知位置而几乎不受语义影响。真实模型在上千维空间里有更大的自由度（Degree of Freedom, DOF）。</p>
<p>把位置“拼接”（Concatenation）到额外维度也能工作，但它会改变隐藏维度，影响后续层形状与参数规模；而加法保持 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span> 不变，是一种参数与工程都更稳定的设计选择。</p>
<div class="blog_h4"><span class="graybg">正弦位置编码（Sinusoidal Positional Encoding）</span></div>
<p>另一类绝对位置编码是正弦位置编码（Sinusoidal Positional Encoding），它不引入可学习参数，而是用不同频率的正弦/余弦把位置 <span displaypfx="inline-" class="mathjax-container">\(\text{pos}\)</span> 映射为向量（原始 Transformer 的设计）：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{PE}(\text{pos},2i)=\sin\!\left(\frac{\text{pos}}{10000^{2i/d_{\text{model}}}}\right),\quad \mathrm{PE}(\text{pos},2i+1)=\cos\!\left(\frac{\text{pos}}{10000^{2i/d_{\text{model}}}}\right)\]</span>
<p>为什么要成对使用 <span displaypfx="inline-" class="mathjax-container">\(\sin\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\cos\)</span>（而不是全用 <span displaypfx="inline-" class="mathjax-container">\(\sin\)</span>）？因为对同一频率而言，<span displaypfx="inline-" class="mathjax-container">\((\sin\phi,\cos\phi)\)</span> 组成一个二维正交基（Orthogonal Basis），位置平移 <span displaypfx="inline-" class="mathjax-container">\(\phi\mapsto \phi+\Delta\)</span> 等价于二维平面上的旋转（Rotation）：</p>
<span displaypfx="" class="mathjax-container">\[\begin{bmatrix}\sin(\phi+\Delta)\\ \cos(\phi+\Delta)\end{bmatrix}=\begin{bmatrix}\cos\Delta &amp; \sin\Delta\\ -\sin\Delta &amp; \cos\Delta\end{bmatrix}\begin{bmatrix}\sin\phi\\ \cos\phi\end{bmatrix}\]</span>
<p>这让“相对位移”变成一个固定的线性变换，从而更容易被后续线性层和点积注意力利用；如果只用 <span displaypfx="inline-" class="mathjax-container">\(\sin\)</span>，相位信息会丢失，平移不再能用线性变换稳定表达。</p>
<p>若只取一个最小的 4 维例子，即 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}=4\)</span>，那么位置编码就会具体化成：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{PE}(\text{pos})=\big[\sin(\text{pos}),\ \cos(\text{pos}),\ \sin(\text{pos}/100),\ \cos(\text{pos}/100)\big]\]</span>
<p>这时每个位置都不再是“一个编号”，而是一个 4 维向量。前两维变化很快，负责较短尺度的位置区分；后两维变化很慢，负责较长尺度的位置区分。例如 <span displaypfx="inline-" class="mathjax-container">\(\text{pos}=0\)</span> 时编码是 <span displaypfx="inline-" class="mathjax-container">\([0,1,0,1]\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\text{pos}=1\)</span> 时约为 <span displaypfx="inline-" class="mathjax-container">\([0.84,0.54,0.01,1.00]\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\text{pos}=2\)</span> 时约为 <span displaypfx="inline-" class="mathjax-container">\([0.91,-0.42,0.02,1.00]\)</span>。因此，不同位置会同时在多种频率刻度上留下痕迹，而不是只靠一个单调递增的数字区分。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/sinusoidal-positional-encoding-4d.png"><img class="alignnone size-full" src="https://blog.gmem.cc/wp-content/uploads/2026/03/sinusoidal-positional-encoding-4d.png" alt="sinusoidal-positional-encoding-4d" width="2322" height="1354" /></a></p>
<p>上图把这个 4 维例子拆成三种读法。左侧热力图直接列出位置 <span displaypfx="inline-" class="mathjax-container">\(0\sim 7\)</span> 在四个维度上的编码值；右上角把前两维 <span displaypfx="inline-" class="mathjax-container">\((\sin(\text{pos}),\cos(\text{pos}))\)</span> 直接当成二维平面坐标，因此可以把它理解成：<span style="background-color: #c0c0c0;">位置每增加一点，平面上的点就沿圆周往前走一步</span>；右下角则把快频对与慢频对分开画出。图里第 3、4 维之所以先前看起来几乎是平的，不是因为它们不变，而是因为在标准公式里它们对应更低频率：在 <span displaypfx="inline-" class="mathjax-container">\(\text{pos}=0\sim 12\)</span> 这样很短的区间上， <span displaypfx="inline-" class="mathjax-container">\(\sin(\text{pos}/100)\)</span> 只从 0 变化到约 0.12， <span displaypfx="inline-" class="mathjax-container">\(\cos(\text{pos}/100)\)</span> 只从 1 下降到约 0.99，必须单独放大才容易看见变化。</p>
<p>模型利用这套编码的方式，可以直接理解成“拿多把不同刻度的尺子同时量位置关系”。同一对 token 的距离，在高频维度上会表现成较快的相位差，在低频维度上会表现成较慢的相位差；于是模型看到的就不是一个孤立的位置编号，而是一组跨多个尺度同时变化的模式。对于很近的 token，高频维度会给出很敏感的区分；对于距离更远的 token，低频维度仍然能保留稳定变化，不会太快绕回去。注意力层随后并不是逐维人工判读这些数值，而是在训练中学会：某些相位差组合通常意味着“相邻修饰”“短程依赖”，另一些更慢变化的组合更像“跨句呼应”或“长程对应”。这里并不存在一个必须被显式恢复出来的“角度标量”或“距离标量”。只要位置变化能够稳定地改变表示与点积结果，后续线性层和注意力头就可以把这种差异当作可利用特征。正弦位置编码的作用正是在于把位置关系改写成一组可被模型利用的周期信号，让模型自己在不同频率上学会读出距离与相对顺序。</p>
<div class="blog_h3"><span class="graybg">相对位置编码</span></div>
<p>相对位置编码（Relative Positional Encoding）不直接编码“绝对索引”，而是让注意力更显式地依赖 token 之间的相对距离 <span displaypfx="inline-" class="mathjax-container">\(i-j\)</span>。典型做法是在注意力打分里加入相对位置偏置（Relative Position Bias）：</p>
<span displaypfx="" class="mathjax-container">\[\alpha_{ij}\propto \exp\!\left(\frac{q_i k_j^\top}{\sqrt{d_k}} + b_{i-j}\right)\]</span>
<p>这条式子描述的是：位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 的 query 去看位置 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 的 key 时，未归一化注意力权重会受到两部分共同决定。第一部分 <span displaypfx="inline-" class="mathjax-container">\(\frac{q_i k_j^\top}{\sqrt{d_k}}\)</span> 是标准内容相关性打分：其中 <span displaypfx="inline-" class="mathjax-container">\(q_i\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个位置的查询向量（Query Vector），<span displaypfx="inline-" class="mathjax-container">\(k_j\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个位置的键向量（Key Vector），二者点积 <span displaypfx="inline-" class="mathjax-container">\(q_i k_j^\top\)</span> 衡量“位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 当前想找的信息，与位置 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 持有的信息是否匹配”；<span displaypfx="inline-" class="mathjax-container">\(d_k\)</span> 是 key/query 的维度，除以 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{d_k}\)</span> 是为了控制数值尺度，避免维度增大后 softmax 过早饱和。</p>
<p>第二部分 <span displaypfx="inline-" class="mathjax-container">\(b_{i-j}\)</span> 是只由相对距离决定的偏置项（Bias Term）。若 <span displaypfx="inline-" class="mathjax-container">\(i-j=1\)</span>，表示当前 token 正在看它左边紧邻的位置；若 <span displaypfx="inline-" class="mathjax-container">\(i-j=10\)</span>，表示它正在看更远的上文。这个偏置可以通过查表得到：给每一种相对距离，或给若干距离分桶（bucket）后的区间，各分配一个可学习标量；也可以由一个小网络根据 <span displaypfx="inline-" class="mathjax-container">\(i-j\)</span> 生成。它的作用是把“距离本身是否重要”直接加进打分，而不必完全依赖内容向量自己去隐式学出这种规律。</p>
<p>式子左边的 <span displaypfx="inline-" class="mathjax-container">\(\alpha_{ij}\)</span> 表示位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 对位置 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 的注意力权重；这里写成 <span displaypfx="inline-" class="mathjax-container">\(\propto\)</span> 而不是等号，是因为右边还只是指数化前的未归一化权重。真正的注意力概率还要在固定 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 后，对所有 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 一起做 softmax 归一化：</p>
<span displaypfx="" class="mathjax-container">\[\alpha_{ij}=\frac{\exp\!\left(\frac{q_i k_j^\top}{\sqrt{d_k}} + b_{i-j}\right)}{\sum_{j'}\exp\!\left(\frac{q_i k_{j'}^\top}{\sqrt{d_k}} + b_{i-j'}\right)}\]</span>
<p>因此，相对位置编码的含义可以概括为：注意力不只比较“内容是否匹配”，还显式比较“这个位置离我有多远”。很多语言现象更依赖相对距离而不是绝对序号，例如局部搭配、邻近修饰、长程指代和句法依赖，因此把 <span displaypfx="inline-" class="mathjax-container">\(i-j\)</span> 直接写进打分，往往比单纯依赖绝对位置索引更贴近任务结构。</p>
<div class="blog_h3"><span class="graybg">RoPE</span></div>
<p>RoPE（Rotary Position Embedding）把位置信息以“旋转”的方式注入到 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span>/<span displaypfx="inline-" class="mathjax-container">\(K\)</span> 中。若按实数矩阵来写，就是把向量的每两维视为一个二维平面，再用角度与位置成正比的旋转矩阵作用在这两维上。对第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个二维分量，令 <span displaypfx="inline-" class="mathjax-container">\(\theta_{m,i}\)</span> 表示位置 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 在该（每两维一个）频段上的旋转角，则</p>
<span displaypfx="" class="mathjax-container">\[\begin{bmatrix}x'_{2i}\\ x'_{2i+1}\end{bmatrix}=\begin{bmatrix}\cos\theta_{m,i} &amp; -\sin\theta_{m,i}\\ \sin\theta_{m,i} &amp; \cos\theta_{m,i}\end{bmatrix}\begin{bmatrix}x_{2i}\\ x_{2i+1}\end{bmatrix}\]</span>
<p>实现上，RoPE 不是“只把当前 query 旋转一下”，而是<span style="background-color: #c0c0c0;">对每个位置的 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 都各自按该位置做旋转</span>；随后不同位置之间再做点积匹配。这样一来，位置 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 的 <span displaypfx="inline-" class="mathjax-container">\(Q_m\)</span> 和位置 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 的 <span displaypfx="inline-" class="mathjax-container">\(K_n\)</span> 在相遇时，二者各自携带的位置相位就会共同决定匹配结果。通常只有 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span>/<span displaypfx="inline-" class="mathjax-container">\(K\)</span> 参与这种旋转， <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 不旋转，因为位置信息的关键作用点在“如何计算注意力权重”，而不是在“被加权汇总的内容值”本身。</p>
<p>上述矩阵式在实现上是正确的，但从理解角度看仍然偏“机械”。更直接的方式是用复数视角（Complex Perspective）：把每两维 <span displaypfx="inline-" class="mathjax-container">\((x_{2i},x_{2i+1})\)</span> 看成一个复数</p>
<span displaypfx="" class="mathjax-container">\[z_i = x_{2i} + \mathrm{i}x_{2i+1}\]</span>
<p>于是 RoPE 的位置注入就可以写成一个极其紧凑的式子：</p>
<span displaypfx="" class="mathjax-container">\[z_i' = z_i \, e^{\mathrm{i} m \theta_i}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 是位置索引， <span displaypfx="inline-" class="mathjax-container">\(\theta_i\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个频段的基础角速度， <span displaypfx="inline-" class="mathjax-container">\(e^{\mathrm{i} m \theta_i}\)</span> 表示“在复平面上旋转 <span displaypfx="inline-" class="mathjax-container">\(m\theta_i\)</span> 角”。这时 RoPE 的直觉就变得很清楚：<span style="background-color: #c0c0c0;">同一个向量本身不变，变化的是它在不同位置上附带的相位（phase）</span>。位置越靠后，相位就继续往前转。</p>
<p>这种写法的关键价值在于：相对位置会自然地从乘法里浮现出来。若位置 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 的 query 与位置 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 的 key 都经过旋转，则它们的匹配项可写成</p>
<span displaypfx="" class="mathjax-container">\[q_m^{(i)} e^{\mathrm{i} m \theta_i}\cdot \overline{k_n^{(i)} e^{\mathrm{i} n \theta_i}} = q_m^{(i)} \overline{k_n^{(i)}} e^{\mathrm{i}(m-n)\theta_i}\]</span>
<p>这里上划线表示复共轭（Complex Conjugate）。前文“二维向量的复数表示”已经给出同一条基本关系：二维点积可以写成复共轭乘积的实部，因此把二维块写成复数后，位置相位会直接进入匹配项。最重要的结果是指数项里只剩下 <span displaypfx="inline-" class="mathjax-container">\((m-n)\theta_i\)</span>：绝对位置 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 被自动折叠成了相对位移 <span displaypfx="inline-" class="mathjax-container">\(m-n\)</span>。因此，RoPE 并不是先写出绝对位置、再额外补一个相对位置项，而是通过“给每个位置乘一个相位因子”的方式，让相对位移直接出现在注意力匹配里。</p>
<p>若用一句更通俗的话概括，RoPE 做的事情是：<span style="background-color: #c0c0c0;">给每个位置的 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span>/<span displaypfx="inline-" class="mathjax-container">\(K\)</span> 都拧上一点角度；两个位置一做点积，位置差就会体现在匹配分数里</span>。矩阵形式更像工程实现的展开式，复数形式更接近它的数学本质。模型并不需要在内部先还原出一个单独的“角度值”再决定如何注意；它只需要利用这种旋转所造成的分数差异与模式差异。只要某类相位关系稳定对应某类局部依赖、顺序关系或长程对应，训练过程就会把这些模式吸收到注意力头和后续层的参数里。也正因为这种“相对位移直接进入匹配”的结构，RoPE 在 Decoder-only 大模型中成为主流选择（例如 LLaMA 系列）。</p>
<div class="blog_h4"><span class="graybg">RoPE 长度外推（Length Extrapolation）</span></div>
<p>RoPE 的旋转角随位置线性增长。若训练阶段最大长度为 <span displaypfx="inline-" class="mathjax-container">\(L_{\text{train}}\)</span>，推理时直接扩展到 <span displaypfx="inline-" class="mathjax-container">\(L_{\text{test}}\gg L_{\text{train}}\)</span>，部分频段会出现“过快旋转”：模型开始在比训练时更长得多的位置区间上继续累积相位，而这些大角度相位组合在训练中几乎没有见过。结果是远距离 token 之间的相对相位关系超出训练分布，注意力更容易退化为近邻偏好，长上下文检索与推理准确率下降。</p>
<p>典型评测是大海捞针（Needle in a Haystack）：在很长的上下文中埋入一条关键信息（needle），要求模型在指定问题下准确复述该信息。常见现象是针落在开头/结尾时表现更好，但针落在中间位置时准确率显著下降；这通常与位置编码外推、注意力实现细节与 KV Cache 行为共同相关。</p>
<p>工程上常见的 RoPE 外推改造包括：</p>
<ul>
<li>位置插值（Position Interpolation, PI）：把推理位置按比例压缩回训练范围，相当于把 RoPE 角速度整体放慢。</li>
<li>NTK-aware 缩放（NTK-aware Scaling）：按“有效核宽度”视角调整频率谱，缓和远距离相对位移失真。</li>
<li>YaRN：对不同频段做分段/渐变缩放，尽量同时保住短程精度与长程外推。</li>
</ul>
<p>以 PI 为例，一个常用写法等价于把 RoPE 的位置 <span displaypfx="inline-" class="mathjax-container">\(\text{pos}\)</span> 映射为 <span displaypfx="inline-" class="mathjax-container">\(\text{pos}'=\text{pos}/s\)</span>（<span displaypfx="inline-" class="mathjax-container">\(s=L_{\text{test}}/L_{\text{train}}\)</span>），从而把角度压回训练范围：</p>
<span displaypfx="" class="mathjax-container">\[\theta'_{\text{pos},i}=\frac{\text{pos}/s}{\text{base}^{2i/d}},\quad s=\frac{L_{\text{test}}}{L_{\text{train}}}\]</span>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">方法</td>
<td style="text-align: center;">是否需要再训练</td>
<td style="text-align: center;">核心超参</td>
<td style="text-align: center;">优势</td>
<td style="text-align: center;">风险/备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>PI</td>
<td>建议配合长上下文继续预训练/微调</td>
<td>缩放因子 <span displaypfx="inline-" class="mathjax-container">\(s\)</span></td>
<td>实现简单；可在保持短程行为的同时扩展长度</td>
<td>若只做推理时改造，可能出现分布错配；需用 needle 测试验证“中间段”能力</td>
</tr>
<tr>
<td>NTK-aware scaling</td>
<td>可仅推理侧启用；配合微调更稳</td>
<td>频谱/基数缩放规则</td>
<td>对远距离更平滑；常用于把“可用上下文”拉长</td>
<td>不同实现差异大；需关注与 KV Cache、GQA/MQA 等工程优化的耦合</td>
</tr>
<tr>
<td>YaRN</td>
<td>通常建议配合继续预训练</td>
<td>分段/渐变缩放参数</td>
<td>兼顾短程精度与长程外推；对 needle 中段退化更友好</td>
<td>超参更多；需要系统评测（含不同位置、不同检索难度）</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">ALiBi</span></div>
<p>ALiBi（Attention with Linear Biases）直接在注意力 logits 上加一个与距离线性相关的偏置，而不改变表示维度，也不引入位置向量：</p>
<span displaypfx="" class="mathjax-container">\[\alpha_{ij}\propto \exp\!\left(\frac{q_i k_j^\top}{\sqrt{d_k}} - m\cdot (i-j)\right),\quad j\le i\]</span>
<p>其中斜率 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 可按 head 设置。直觉上它鼓励模型更关注近邻 token，同时具备较好的长度外推（Length Extrapolation）行为。</p>
<div class="blog_h2"><span class="graybg">注意力机制</span></div>
<p>注意力机制（Attention Mechanism）是序列模型中的动态信息选择机制。对于一个由多个 token 构成的输入序列，模型不会把所有上下文位置等量混合，而是会针对当前正在计算的位置，动态判断哪些位置更相关、相关程度有多大，以及这些位置的信息应当如何组合成新的表示。这个过程本质上是一个与输入内容相关的加权汇聚：当前位置先形成查询信号，再在上下文中寻找与之匹配的位置，最后把这些位置承载的信息按权重聚合回来。</p>
<p>这种设计改变了传统序列建模的信息传递路径。循环结构主要依赖状态沿时间步逐步传递，卷积结构主要依赖固定大小的局部感受野，而注意力机制允许任意两个位置直接建立联系，并且联系强度由内容决定而不是由距离预先写死。长距离依赖（Long-Range Dependency）因此可以被更直接地建模：一个 token 可以立刻读取很远处但与当前语义高度相关的信息，而不必等待信息穿过很长的递归链条或许多层局部卷积。</p>
<p>Transformer 将注意力机制置于核心位置。自注意力（Self-Attention）让同一序列内部的各个 token 相互读取；交叉注意力（Cross-Attention）让一个序列读取另一个序列的表示；因果注意力（Causal Attention）则通过掩码限制当前位置只能访问过去的信息，从而支撑自回归生成（Autoregressive Generation）。这些形式都遵循同一条主线：先计算相关性分数，再把分数归一化为权重，最后对承载内容的向量做加权求和。其最经典、最常见的数学形式就是缩放点积注意力（Scaled Dot-Product Attention）。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/self-attention-mechanism.png"><img class="alignnone size-full wp-image-41471" src="https://blog.gmem.cc/wp-content/uploads/2026/03/self-attention-mechanism.png" alt="self-attention-mechanism" width="1920" height="1080" /></a></p>
<div class="blog_h3"><span class="graybg">Scaled Dot-Product Attention</span></div>
<p>自注意力（Self-Attention）中，输入表示 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 通过三组参数投影为：</p>
<span displaypfx="" class="mathjax-container">\[Q=XW_Q,\quad K=XW_K,\quad V=XW_V\]</span>
<p>注意力输出为：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Attention}(Q,K,V)=\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(W_Q,W_K,W_V\)</span>（以及多头里的 <span displaypfx="inline-" class="mathjax-container">\(W_O\)</span>）都是模型参数（Parameters），训练的目标不是“生成这些矩阵”，而是在损失函数（Loss）下通过梯度下降（Gradient Descent）把它们优化到能完成任务的取值。</p>
<p>把公式按 token 展开更清楚。设当前只看一个注意力头，序列长度为 <span displaypfx="inline-" class="mathjax-container">\(L\)</span>。对第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 token，模型先取出它的查询向量 <span displaypfx="inline-" class="mathjax-container">\(q_i\in\mathbb{R}^{d_k}\)</span>，再与序列中每个位置 <span displaypfx="inline-" class="mathjax-container">\(j=1,\dots,L\)</span> 的键向量 <span displaypfx="inline-" class="mathjax-container">\(k_j\in\mathbb{R}^{d_k}\)</span> 做点积，得到一个标量打分：</p>
<span displaypfx="" class="mathjax-container">\[s_{ij}=q_i k_j^\top\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(d_k\)</span> 表示每个头里 Key / Query 向量的维度，也就是 <span displaypfx="inline-" class="mathjax-container">\(q_i\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(k_j\)</span> 的长度。它之所以记作 <span displaypfx="inline-" class="mathjax-container">\(d_k\)</span>，是因为这个维度首先由 Key 空间定义；而 Query 必须与 Key 处在同样维度里，才能做点积匹配。因此 <span displaypfx="inline-" class="mathjax-container">\(q_i\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(k_j\)</span> 的长度通常相同。Value 向量的维度记作 <span displaypfx="inline-" class="mathjax-container">\(d_v\)</span>；实践中常见设置是 <span displaypfx="inline-" class="mathjax-container">\(d_v=d_k\)</span>，但这不是数学上的硬要求。</p>
<p>接着，对第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 query 的整行打分做缩放和 softmax，得到一组对所有位置的注意力权重：</p>
<span displaypfx="" class="mathjax-container">\[\alpha_{ij}=\mathrm{softmax}_j\!\left(\frac{s_{ij}}{\sqrt{d_k}}\right)=\frac{\exp\!\left(s_{ij}/\sqrt{d_k}\right)}{\sum_{t=1}^{L}\exp\!\left(s_{it}/\sqrt{d_k}\right)}\]</span>
<p>因此，对固定的 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 来说， <span displaypfx="inline-" class="mathjax-container">\(\alpha_{i1},\dots,\alpha_{iL}\)</span> 构成一个标量概率分布：它们都非负，且总和为 1。这个分布回答的是“第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 token 应该从整段序列的哪些位置读取多少信息”。</p>
<p>输出 <span displaypfx="inline-" class="mathjax-container">\(o_i\)</span> 则是一个向量（Vector），由所有 Value 向量 <span displaypfx="inline-" class="mathjax-container">\(v_j\in\mathbb{R}^{d_v}\)</span> 按权重加权求和得到：</p>
<span displaypfx="" class="mathjax-container">\[o_i=\sum_{j=1}^{L}\alpha_{ij} v_j\]</span>
<p>把这一步画成示意图会更直观：固定第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 query 后，先得到一组对各位置 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 的注意力权重；再用这些权重去加权汇总对应的 Value 向量；右侧输出向量的每一维，都是左侧各个 Value 向量对应维度的加权和。</p>
<p>查询向量（Query）与键向量（Key）负责“匹配打分”；值向量（Value）承载被聚合的信息内容。把注意力看作“内容寻址（Content-based Addressing）”：先用 <span displaypfx="inline-" class="mathjax-container">\(QK^\top\)</span> 计算“应该看谁”，再用权重对 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 做加权求和得到“看到了什么”。</p>
<p>缩放因子 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{d_k}\)</span> 的作用是控制数值尺度。由于 <span displaypfx="inline-" class="mathjax-container">\(q_i\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(k_j\)</span> 的点积是 <span displaypfx="inline-" class="mathjax-container">\(d_k\)</span> 个乘积项的求和，若各维分量方差相近，则点积分数的方差通常会随着 <span displaypfx="inline-" class="mathjax-container">\(d_k\)</span> 增长。维度一大， <span displaypfx="inline-" class="mathjax-container">\(s_{ij}\)</span> 的绝对值就更容易变大，softmax 会更快进入饱和区：某几个位置的权重接近 1，其余位置接近 0，梯度也会变小。除以 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{d_k}\)</span> 后，分数尺度被拉回更稳定的范围，不同 head 维度设置下的 softmax 行为会更可控。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/attention-softmax-saturation.png"><img class="alignnone size-full wp-image-41453" src="https://blog.gmem.cc/wp-content/uploads/2026/03/attention-softmax-saturation.png" alt="attention-softmax-saturation" width="1920" height="1112" /></a></p>
<p>理论上可以让寻址与内容共用投影（例如令 <span displaypfx="inline-" class="mathjax-container">\(V=K\)</span> 或直接取 <span displaypfx="inline-" class="mathjax-container">\(V=X\)</span>），但实践中通常把 Q/K 与 V 分开，是为了让“打分空间”和“内容表示空间”解耦，提升表示能力与训练稳定性。</p>
<p>一个极简数值例子：若某个 Query 与 3 个 Key 的相似度（未缩放）为 <span displaypfx="inline-" class="mathjax-container">\([2,1,0]\)</span>，softmax 权重大约是 <span displaypfx="inline-" class="mathjax-container">\([0.665,0.245,0.090]\)</span>，输出就是把三个 Value 按这个比例加权求和。</p>
<div class="blog_h3"><span class="graybg">Masked Attention</span></div>
<p>Masked Attention（因果注意力 / Causal Attention）在自回归（Autoregressive）生成中使用：通过掩码（Mask）禁止位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 看到未来位置 <span displaypfx="inline-" class="mathjax-container">\(j&gt;i\)</span>。实现上通常是在 softmax 前把被禁止位置的打分加上一个极小值（如 <span displaypfx="inline-" class="mathjax-container">\(-\infty\)</span>）。</p>
<p>若把未加掩码的打分矩阵记为 <span displaypfx="inline-" class="mathjax-container">\(S=\frac{QK^\top}{\sqrt{d_k}}\in\mathbb{R}^{L\times L}\)</span>，则因果掩码可写成一个上三角被屏蔽的矩阵 <span displaypfx="inline-" class="mathjax-container">\(M\)</span>。以 <span displaypfx="inline-" class="mathjax-container">\(L=4\)</span> 为例：</p>
<span displaypfx="" class="mathjax-container">\[M=\begin{bmatrix} 0 &amp; -\infty &amp; -\infty &amp; -\infty\\ 0 &amp; 0 &amp; -\infty &amp; -\infty\\ 0 &amp; 0 &amp; 0 &amp; -\infty\\ 0 &amp; 0 &amp; 0 &amp; 0 \end{bmatrix}\]</span>
<p>然后在 softmax 之前做逐元素相加：</p>
<span displaypfx="" class="mathjax-container">\[P=\mathrm{softmax}(S+M)\]</span>
<p>这里主对角线及其左下区域为 0，表示当前位置及其历史位置允许被访问；右上区域为 <span displaypfx="inline-" class="mathjax-container">\(-\infty\)</span>，表示未来位置被强制屏蔽。softmax 之后，这些位置的权重会变成 0，因此第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行只能在 <span displaypfx="inline-" class="mathjax-container">\(j\le i\)</span> 的范围内分配概率。</p>
<p>从矩阵形状看，这就是一个<span style="background-color: #c0c0c0;">保留下三角、屏蔽上三角</span>的结构。它保证了解码器在位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 计算注意力时，只能读取已经出现的 token，而不能偷看未来 token。</p>
<p>注意力机制在训练阶段和推理阶段都会执行。区别在于：训练时通常一次性输入整段序列（Teacher Forcing）并使用因果掩码；推理时逐 token 解码，并结合 KV Cache 避免重复计算历史。</p>
<div class="blog_h3"><span class="graybg">Cross-Attention</span></div>
<p>交叉注意力（Cross-Attention）让一个序列“去读另一个序列”。在 Encoder–Decoder Transformer 里：解码器当前状态提供 Query，编码器输出提供 Key/Value。若编码器输出为 <span displaypfx="inline-" class="mathjax-container">\(H_{\text{src}}\in\mathbb{R}^{L_{\text{src}}\times d}\)</span>，解码器输入为 <span displaypfx="inline-" class="mathjax-container">\(H_{\text{tgt}}\in\mathbb{R}^{L_{\text{tgt}}\times d}\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[Q=H_{\text{tgt}}W_Q,\quad K=H_{\text{src}}W_K,\quad V=H_{\text{src}}W_V\]</span>
<span displaypfx="" class="mathjax-container">\[\mathrm{CrossAttn}(H_{\text{tgt}},H_{\text{src}})=\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V\]</span>
<p>它与自注意力（Self-Attention）的区别仅在于 <span displaypfx="inline-" class="mathjax-container">\(K,V\)</span> 来自“别的序列”，因此能把“源序列信息”按需注入到“目标序列生成”中。</p>
<p>在交叉编码器（Cross-Encoder）语境里，很多实现并不显式写 cross-attention：它们把两段文本拼接成一个序列，用全连接自注意力直接建模跨序列交互；从效果上看等价于“允许任意 token 互相注意”。</p>
<p>Decoder-only 架构本身没有 cross-attention 子层；只有在做 Seq2Seq（有 encoder 输出）或显式引入外部记忆（Memory）时，才会在解码器里加入 cross-attention。</p>
<div class="blog_h3"><span class="graybg">Multi-Head Attention</span></div>
<p>多头注意力（Multi-Head Attention）把注意力拆成 <span displaypfx="inline-" class="mathjax-container">\(H\)</span> 个头（Heads），每个头在不同的子空间里独立做一次注意力，然后在特征维度上拼接（Concatenation）并用输出矩阵混合：</p>
<span displaypfx="" class="mathjax-container">\[\text{head}_h=\mathrm{Attention}(XW_Q^{(h)},XW_K^{(h)},XW_V^{(h)})\]</span>
<span displaypfx="" class="mathjax-container">\[\mathrm{MultiHead}(X)=\mathrm{Concat}(\text{head}_1,\dots,\text{head}_H)W_O\]</span>
<p>若 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span> 固定，常见做法是每个头的维度 <span displaypfx="inline-" class="mathjax-container">\(d_k=d_v=d_{\text{model}}/H\)</span>，拼接后回到 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span>。多头的收益来自“并行关注不同关系”：有的头偏向局部邻域，有的头偏向长程依赖，有的头学到语法/实体指代等不同模式。</p>
<p>这种分工在标准训练里通常不是人工指定的，而是优化过程中的自发结果。每个头都拥有各自独立的 <span displaypfx="inline-" class="mathjax-container">\(W_Q^{(h)},W_K^{(h)},W_V^{(h)}\)</span> 参数，因此即使输入相同，它们也会把表示投影到不同子空间里，形成不同的匹配规则。随机初始化首先打破了头与头之间的对称性；随后，损失函数只约束“多头合起来的整体输出”是否有利于完成任务，而不要求每个头承担同一种功能。在这种条件下，若多个头完全重复，整体表示效率往往偏低；优化更容易把不同头推向不同关系模式，于是逐渐出现局部邻近、长程依赖、分隔符、指代、句法边界等不同偏好。</p>
<p>这种功能分化并不是严格保证。实际模型里常能观察到部分头高度相似，部分头贡献很小，甚至剪掉后性能几乎不变。若希望更强地控制不同头学习不同东西，就需要额外机制，例如对不同头加入多样性正则（Diversity Regularization）、局部窗口约束、特定监督信号，或在训练后做 head pruning / head specialization 分析。</p>
<div class="blog_h3"><span class="graybg">GQA/MQA</span></div>
<p>从多头注意力再往下走一步，就会遇到一个非常重要的工程分叉：Query 头数与 Key/Value 头数是否必须一一对应。标准 MHA 默认每个 Query head 都有自己独立的 Key/Value head；而在长上下文大模型里，工程重点往往会进一步转向<span style="background-color: #c0c0c0;">KV Cache 到底有多大、带宽到底有多贵</span>。GQA 与 MQA 的区别，正是在回答这个问题。</p>
<div class="blog_h4"><span class="graybg">GQA</span></div>
<p>GQA（Grouped Query Attention）用“更少的 KV 头”服务“更多的 Query 头”。它的核心做法是：多个 Query heads 共享同一组 Key/Value heads，从而显著降低 KV Cache 的显存与带宽压力。若仍记 Query 头数为 <span displaypfx="inline-" class="mathjax-container">\(n_q\)</span>，KV 头数为 <span displaypfx="inline-" class="mathjax-container">\(n_{\text{kv}}\)</span>，则 GQA 满足 <span displaypfx="inline-" class="mathjax-container">\(1&lt;n_{\text{kv}}\ll n_q\)</span>。</p>
<p>对比标准多头注意力，GQA 的本质是把“每个 head 都独立维护一套 KV”改成“若干 Query head 结成一组，共享一套 KV”。注意力仍然按 head 计算，但 KV 表示不再完全一一对应，而是以组为单位共享。在长上下文推理中，它带来的收益往往比对算力的节省更关键：KV Cache 与内存带宽近似按 <span displaypfx="inline-" class="mathjax-container">\(n_{\text{kv}}/n_q\)</span> 比例下降。</p>
<p>这也是为什么很多现代大模型把 GQA 当作默认配置。它在表达能力与缓存成本之间提供了一个很实用的折中：比 MHA 便宜得多，但通常又比最极端的共享方案更稳。</p>
<div class="blog_h4"><span class="graybg">MQA</span></div>
<p>MQA（Multi-Query Attention）可以看作 GQA 的极端情形，即 <span displaypfx="inline-" class="mathjax-container">\(n_{\text{kv}}=1\)</span>。这意味着全部 Query heads 共用同一套 Key / Value 表示。于是，KV Cache 被进一步压到最小，推理阶段的显存和带宽压力也达到最强压缩。</p>
<p>它的优点非常明确：在超长上下文和高并发解码场景下，MQA 往往是最省缓存的一类头部组织方式。缺点也同样明确：表示自由度下降得最厉害，不同 Query heads 看到的 Key/Value 空间过于相似，因此更容易带来质量损失。工程上通常会用更大的模型维度、更多 Query heads、或更强的 FFN 来补偿这种压缩。</p>
<p>因此，三者的关系可以概括成一条连续谱：<span style="background-color: #c0c0c0;">MHA 表达最自由、成本最高；MQA 成本最低、共享最强；GQA 则处在两者之间，提供最常用的工程折中</span>。进一步降低 KV Cache 成本的路线，还包括 Latent KV / MLA 一类潜空间压缩，以及 TurboQuant 一类面向内积保真的 KV 量化压缩。它们优化的仍然是长上下文推理里的 KV 存储与带宽，只是已经不再停留在“头数共享”这一层。</p>
<div class="blog_h3"><span class="graybg">线性注意力</span></div>
<p>线性注意力（Linear Attention）是在注意力层内部改写计算顺序，使代价不再显式依赖完整的 <span displaypfx="inline-" class="mathjax-container">\(L\times L\)</span> 注意力矩阵。标准 softmax 注意力可写为：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Attn}(Q,K,V)=\mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(Q\in\mathbb{R}^{L\times d_k}\)</span> 是整段序列的 query 矩阵， <span displaypfx="inline-" class="mathjax-container">\(K\in\mathbb{R}^{L\times d_k}\)</span> 是 key 矩阵， <span displaypfx="inline-" class="mathjax-container">\(V\in\mathbb{R}^{L\times d_v}\)</span> 是 value 矩阵； <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 是序列长度， <span displaypfx="inline-" class="mathjax-container">\(d_k\)</span> 是 query / key 维度， <span displaypfx="inline-" class="mathjax-container">\(d_v\)</span> 是 value 维度。矩阵 <span displaypfx="inline-" class="mathjax-container">\(QK^\top\in\mathbb{R}^{L\times L}\)</span> 的第 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 项就是 <span displaypfx="inline-" class="mathjax-container">\(q_i^\top k_j\)</span>，表示第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个位置对第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个位置的原始匹配分数。也就是说，标准注意力需要让每个 query 与每个 key 两两打分，因此时间与显存压力通常都是 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{O}(L^2)\)</span> 级别。线性注意力的目标，就是把这种“先算完整两两相互作用，再做归一化”的流程，改写成“先把历史信息压缩成一组可累计统计量，再让每个 query 去读取这些统计量”。</p>
<p>标准注意力里，第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个位置的输出可以写成</p>
<span displaypfx="" class="mathjax-container">\[o_i=\sum_{j=1}^{L}\alpha_{ij}v_j,\qquad \alpha_{ij}=\frac{\exp(q_i^\top k_j/\sqrt{d_k})}{\sum_{\ell=1}^{L}\exp(q_i^\top k_\ell/\sqrt{d_k})}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(q_i\in\mathbb{R}^{d_k}\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个位置的 query 向量，表示“当前位置想找什么信息”； <span displaypfx="inline-" class="mathjax-container">\(k_j\in\mathbb{R}^{d_k}\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个位置的 key 向量，表示“这个历史位置提供什么匹配线索”； <span displaypfx="inline-" class="mathjax-container">\(v_j\in\mathbb{R}^{d_v}\)</span> 是同一位置真正承载内容的 value 向量； <span displaypfx="inline-" class="mathjax-container">\(\alpha_{ij}\)</span> 是归一化后的注意力权重，表示第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 query 最终给第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个位置分了多少注意力； <span displaypfx="inline-" class="mathjax-container">\(o_i\)</span> 则是当前位置输出。于是 softmax 注意力的本质很明确：先用 <span displaypfx="inline-" class="mathjax-container">\(q_i\)</span> 和所有 <span displaypfx="inline-" class="mathjax-container">\(k_j\)</span> 算相似度，再用这些权重去加权求和所有 <span displaypfx="inline-" class="mathjax-container">\(v_j\)</span>。</p>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(\phi\)</span> 是<span style="background-color: #c0c0c0;">特征映射（Feature Map）</span>或<span style="background-color: #c0c0c0;">核特征映射（Kernel Feature Map）</span>。它把原本位于 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^{d_k}\)</span> 的 query / key 向量，映射到另一个通常更容易做内积分解的空间 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^{m}\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\phi:\mathbb{R}^{d_k}\rightarrow\mathbb{R}^{m}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 是映射后的特征维度。直观上， <span displaypfx="inline-" class="mathjax-container">\(\phi\)</span> 的作用是把原来的向量变成另一种表示，使得原本难以直接拆开的相似度，能够改写成内积形式。</p>
<p>若某种相似度函数 <span displaypfx="inline-" class="mathjax-container">\(\kappa(q,k)\)</span> 可以写成</p>
<span displaypfx="" class="mathjax-container">\[\kappa(q,k)=\phi(q)^\top \phi(k)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\kappa(q,k)\)</span> 是 query 与 key 的相似度函数；右边写成 <span displaypfx="inline-" class="mathjax-container">\(\phi(q)^\top \phi(k)\)</span> 之后，相似度就被拆成了“只依赖 query 的一项”和“只依赖 key 的一项”的内积形式。这样一来，注意力里“query 与所有 key 的逐一比较”就能被拆成两部分：一部分只和 key/value 有关，另一部分只和当前 query 有关。对单个 query <span displaypfx="inline-" class="mathjax-container">\(q_i\)</span> 而言，若记</p>
<span displaypfx="" class="mathjax-container">\[S=\sum_{j=1}^{L}\phi(k_j)v_j^\top,\qquad z=\sum_{j=1}^{L}\phi(k_j)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(S\in\mathbb{R}^{m\times d_v}\)</span> 是把整段历史中“key 的特征表示”和“对应的 value 内容”绑定后累计起来得到的矩阵，可以理解为一份带内容的历史摘要； <span displaypfx="inline-" class="mathjax-container">\(z\in\mathbb{R}^{m}\)</span> 只累计 key 的特征表示，用来充当归一化分母。则输出可改写为</p>
<span displaypfx="" class="mathjax-container">\[o_i\approx\frac{\phi(q_i)^\top S}{\phi(q_i)^\top z}\]</span>
<p>其中分子 <span displaypfx="inline-" class="mathjax-container">\(\phi(q_i)^\top S\)</span> 表示“当前 query 去读取带内容的历史摘要”，分母 <span displaypfx="inline-" class="mathjax-container">\(\phi(q_i)^\top z\)</span> 用来做归一化，防止输出尺度失控。这一步是线性化的核心。因为 <span displaypfx="inline-" class="mathjax-container">\(S\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 可以沿着序列顺序递推累计，不需要先显式构造完整的 <span displaypfx="inline-" class="mathjax-container">\(L\times L\)</span> 打分矩阵。于是复杂度从“所有 token 两两交互”转成“每个 token 更新一次全局统计量并读取一次统计量”，在很多实现里就能接近 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{O}(L)\)</span> 的序列复杂度。</p>
<p>为什么这里可以做近似，关键在于 softmax 注意力本身也可以被看成一种核化相似度。softmax 的未归一化权重本质上依赖 <span displaypfx="inline-" class="mathjax-container">\(\exp(q^\top k/\sqrt{d_k})\)</span>；如果能找到某个 <span displaypfx="inline-" class="mathjax-container">\(\phi\)</span>，使得</p>
<span displaypfx="" class="mathjax-container">\[\exp\!\left(\frac{q^\top k}{\sqrt{d_k}}\right)\approx \phi(q)^\top \phi(k)\]</span>
<p>就能用可分解的内积结构近似原来的指数核。不同线性注意力方法对 <span displaypfx="inline-" class="mathjax-container">\(\phi\)</span> 的选择并不相同：有的方法用显式正特征映射，例如 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{ELU}(x)+1\)</span> 之类的正值变换；有的方法用随机特征（Random Features）去逼近 softmax kernel；还有一些方法更进一步，直接放弃“精确逼近 softmax”，转而定义一种新的核化注意力族。于是“线性注意力”在数学上并不总是同一条公式，而是一大类可分解注意力路线。</p>
<p>“什么样的核函数能够做这件事”可以分成两类理解。第一类是<span style="background-color: #c0c0c0;">可显式近似 softmax 的核</span>。例如 Performer 使用 FAVOR+（Fast Attention Via Positive Orthogonal Random Features）路线，用随机特征把指数核 <span displaypfx="inline-" class="mathjax-container">\(\exp(q^\top k)\)</span> 近似成有限维内积 <span displaypfx="inline-" class="mathjax-container">\(\phi(q)^\top\phi(k)\)</span>。这里的 <span displaypfx="inline-" class="mathjax-container">\(\phi\)</span> 不再是一个随手指定的非线性，而是专门为逼近指数核构造出来的随机特征映射。</p>
<p>第二类是<span style="background-color: #c0c0c0;">直接改用另一种正核</span>。例如早期 Linear Transformer 一类方法常取 <span displaypfx="inline-" class="mathjax-container">\(\phi(x)=\mathrm{ELU}(x)+1\)</span> 或其他保持正值的映射。这样做的重点不在于把 softmax 严格逼近得多么精确，而在于构造一个满足非负性、可递推累计、数值上相对稳定的核化注意力形式。此时模型学到的已经不是“softmax 的近似实现”，而更接近“另一种可线性化的注意力定义”。</p>
<div class="blog_h4"><span class="graybg">一个最小例子</span></div>
<p>设序列里只有两个历史位置，当前位置的 query 为 <span displaypfx="inline-" class="mathjax-container">\(q\)</span>，历史 key / value 分别为 <span displaypfx="inline-" class="mathjax-container">\((k_1,v_1)\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\((k_2,v_2)\)</span>。标准注意力会先显式算出两个分数：</p>
<span displaypfx="" class="mathjax-container">\[s_1=q^\top k_1,\qquad s_2=q^\top k_2\]</span>
<p>再做 softmax：</p>
<span displaypfx="" class="mathjax-container">\[\alpha_1=\frac{e^{s_1}}{e^{s_1}+e^{s_2}},\qquad \alpha_2=\frac{e^{s_2}}{e^{s_1}+e^{s_2}}\]</span>
<p>最后输出</p>
<span displaypfx="" class="mathjax-container">\[o=\alpha_1 v_1+\alpha_2 v_2\]</span>
<p>线性注意力则尝试把这一步改写成“先对历史做摘要，再让 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 读取摘要”。若有某个 <span displaypfx="inline-" class="mathjax-container">\(\phi\)</span> 使得 <span displaypfx="inline-" class="mathjax-container">\(e^{q^\top k}\approx \phi(q)^\top\phi(k)\)</span>，那么先预计算</p>
<span displaypfx="" class="mathjax-container">\[S=\phi(k_1)v_1^\top+\phi(k_2)v_2^\top,\qquad z=\phi(k_1)+\phi(k_2)\]</span>
<p>然后输出近似为</p>
<span displaypfx="" class="mathjax-container">\[o\approx\frac{\phi(q)^\top S}{\phi(q)^\top z}\]</span>
<p>这里的关键变化很明确：原公式需要先单独算 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 对 <span displaypfx="inline-" class="mathjax-container">\(k_1\)</span>、<span displaypfx="inline-" class="mathjax-container">\(k_2\)</span> 的两个打分；而线性化后的公式把历史两项先合成 <span displaypfx="inline-" class="mathjax-container">\(S\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(z\)</span>，后续任何新 query 都直接读取这个摘要即可。把“两项历史”换成“<span displaypfx="inline-" class="mathjax-container">\(L\)</span> 项历史”时，这种先摘要、后读取的结构才真正体现出线性复杂度优势。</p>
<p>从直觉上看，标准注意力像是“每个 query 都把整张历史表逐行翻一遍，再决定该关注谁”；线性注意力则像是“先把历史 key/value 在特征空间里压缩成一个可累计的摘要，再由每个 query 去读取这份摘要”。前者保留了最细粒度的两两比较，后者牺牲了一部分精细交互，换取长序列下更低的显存与更高的吞吐。</p>
<p>一个典型形式（省略实现细节）是：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Attn}(Q,K,V)\approx \frac{\phi(Q)\big(\phi(K)^\top V\big)}{\phi(Q)\big(\phi(K)^\top \mathbf{1}\big)}\]</span>
<p>线性注意力更适合“极长序列下的吞吐/显存”目标，但它往往需要在表示、数值稳定性与效果之间做取舍；在通用 LLM 上，主流路径仍然是“精确注意力 + 更好的内核 + 更强的位置/缓存工程”，线性注意力更多作为特定场景或混合架构的选项。</p>
<div class="blog_h4"><span class="graybg">flash-linear-attention</span></div>
<p><pre class="crayon-plain-tag">flash-linear-attention</pre> 常缩写为 <pre class="crayon-plain-tag">fla</pre>。它更适合被理解为<span style="background-color: #c0c0c0;">线性注意力、递推更新与部分状态空间模块的高性能实现库</span>，而不是一种独立的新模型家族。它提供的是面向 GPU 前向路径的高效 kernel：通过融合、分块、块内累计和更紧凑的中间状态组织，降低长序列计算中的访存与中间张量开销。</p>
<p>从运行阶段看，fla 的影响并不局限于训练。只要模型在推理时仍然调用同一套由 fla 实现的层或算子，推理阶段也会直接受益，因为推理本质上只是在执行前向传播（Forward Pass）。这意味着它通常会同时带来两类收益：第一，延迟下降，因为前向 kernel 更快；第二，显存占用下降，因为融合实现往往减少了中间张量写回与额外缓存。序列越长，这类收益通常越明显。</p>
<p>不过，fla 的收益并不是无条件出现。若模型本身根本没有使用 fla 支持的模块，而只是标准 Transformer 加普通 PyTorch attention，那么安装 fla 不会自动带来变化；若部署时已经把模型导出到另一套不支持相应 kernel 的执行引擎，例如某些 ONNX / 图编译推理链路，原本的加速路径也可能丢失；若运行环境是 CPU，或 GPU / Triton / CUDA 条件不满足，系统通常会回退到普通实现，此时收益会显著减弱甚至完全消失。</p>
<p>它与 FlashAttention 的关系也需要分清。两者都属于“前向路径加速会同时惠及训练与推理”的工程优化，但作用对象并不相同：FlashAttention 优化的是<span style="background-color: #c0c0c0;">精确 softmax 注意力</span>的实现，数学结果保持不变；fla 更常服务于<span style="background-color: #c0c0c0;">线性注意力、递推或 SSM 风格模块</span>的高效实现。因此，是否影响推理，关键不在于“训练还是推理”，而在于<span style="background-color: #c0c0c0;">推理时走的是否仍是那条由 fla 支撑的前向计算路径</span>。</p>
<div class="blog_h3"><span class="graybg">稀疏注意力</span></div>
<p>标准密集自注意力（Dense Self-Attention）会让每个位置与所有可见位置计算打分，因此在长度 <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 上通常带来 <span displaypfx="inline-" class="mathjax-container">\(O(L^2)\)</span> 级别的注意力矩阵与计算压力。稀疏注意力（Sparse Attention）的核心思路，就是预先限制“每个 token 允许看哪些位置”，只保留一部分连接，从而把长上下文建模的代价降下来。</p>
<p>稀疏注意力是一类注意力连接模式的总称。它可以是局部窗口（Local Window）、块状稀疏（Block Sparse）、跨步连接（Strided Pattern）、少量全局 token（Global Tokens），也可以是这些模式的组合。Longformer、BigBird 这类长序列模型，都属于这条路线的经典代表。它保留 softmax 注意力的基本定义，同时把原本“谁都能看谁”的全连接关系改成一个更受约束的稀疏图。</p>
<p>从 2026 年的工程现实看，稀疏注意力仍然重要，但它已经不是通用旗舰语言模型的默认路线。它更常出现在长文档理解、超长上下文、显存/带宽受限，或专门强调长序列效率的模型中；而很多主流通用基座仍然更常采用密集因果注意力，再叠加 GQA、KV Cache、FlashAttention、KV 压缩等优化。这是因为稀疏模式虽然更省，但也会直接限制单层里可建立的依赖范围，训练与实现复杂度通常更高。</p>
<div class="blog_h3"><span class="graybg">滑动窗口注意力</span></div>
<p>滑动窗口注意力（Sliding Window Attention）是稀疏注意力里最常见、也最工程化的一种形式：位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 只看距离自己最近的一段窗口，例如前面 <span displaypfx="inline-" class="mathjax-container">\(w\)</span> 个 token。这样单层注意力的代价就从“与整段长度线性增长的每行宽度”，压缩成“与固定窗口宽度相关”的局部计算。</p>
<p>它的优点是非常直接：局部模式、邻近依赖和短程语义通常仍能被稳定捕捉，而长上下文成本显著下降。代价是，两个相距很远的位置无法在同一层里直接交互，只能依靠多层传播，或额外引入全局层、全局 token、周期性全注意力层等机制来弥补。因此很多实际架构会采用“局部层 + 少量全局层”的混合设计，而不是把所有层都做成纯局部窗口。</p>
<p>到 2026 年，滑动窗口注意力仍然被部分主流模型持续使用，尤其是在长上下文或高性价比路线中；例如 Mistral 一类模型会显式采用 Sliding Window Attention，Gemma 2/3 一类模型也会在 local / global hybrid 结构中交替使用局部注意力层。但它并不是所有主流模型的统一默认配置。更准确的说法是：<span style="background-color: #c0c0c0;">通用“稀疏注意力”并非当代旗舰模型的普遍默认架构，而“滑动窗口注意力”则仍是今天主流工程实践里一条活跃的局部注意力路线</span>。一旦局部窗口再与全局 token、周期性全注意力层或其他远程连接模式联合使用，它就进入了混合注意力的范畴。</p>
<div class="blog_h3"><span class="graybg">混合注意力</span></div>
<p>混合注意力（Hybrid Attention）是一类设计思想：在同一个模型、同一层或同一组层里，同时组合两种或多种不同的注意力模式，让它们分别承担不同职责。可被混合的对象很多，例如<span style="background-color: #c0c0c0;">局部窗口与全局连接</span>、<span style="background-color: #c0c0c0;">稠密与稀疏</span>、<span style="background-color: #c0c0c0;">未压缩与压缩表示</span>、<span style="background-color: #c0c0c0;">不同层采用不同注意力机制</span>。它的核心目标始终一致：在不牺牲太多表达能力的前提下，同时兼顾局部细节、长程依赖和计算成本。</p>
<p>之所以需要“混合”，是因为单一注意力模式往往只能在一个方向上做到极致。纯密集注意力最通用，但长上下文成本太高；纯局部窗口注意力便宜，但单层看不到远处；纯压缩注意力能把上下文做得很长，但容易丢掉近邻精细结构。因此现代模型越来越多地采用“让不同注意力模式协作分工”的路线：局部部分负责细节保真，全局部分负责长距离背景，稀疏部分负责把计算集中在最重要的位置，压缩部分负责控制 KV Cache 和 FLOPs。</p>
<p>从实现方式看，混合注意力至少有三种常见形态。第一种是<span style="background-color: #c0c0c0;">同层混合</span>：在同一层里并行放入两条注意力分支，再把结果合并，例如局部窗口分支加全局分支。第二种是<span style="background-color: #c0c0c0;">按层交替</span>：不同层使用不同的注意力模式，例如一层偏局部，一层偏全局。第三种是<span style="background-color: #c0c0c0;">主干 + 补充分支</span>：主体注意力负责主要读写，同时再加一个窗口分支、全局 token 分支或压缩块分支补足缺失的信息路径。前面的滑动窗口路线，以及后文的 DeepSeek V4 `CSA / HCA`，都可以放在这条“混合注意力”主线上理解。</p>
<div class="blog_h4"><span class="graybg">长上下文路线</span></div>
<p>这里需要把两类经常被混在一起的优化明确分开。第一类是<span style="background-color: #c0c0c0;">架构级注意力创新</span>：它直接改写注意力层内部的参数化方式、KV 表示形式或 block 级计算路径，例如 MLA（Multi-head Latent Attention）以及 DeepSeek V4 的 CSA / HCA（Compressed Sparse Attention / Heavily Compressed Attention）。第二类是<span style="background-color: #c0c0c0;">推理期运行时优化</span>：它不改变模型训练时学到的注意力结构，而是在部署阶段降低显存、带宽或调度开销，例如 Paged Attention、Prompt Caching、KV 量化等。</p>
<p>这一区分很重要，因为 MLA、CSA、HCA 都属于主干网络内部的注意力设计。它们直接定义了模型在训练时看到的注意力几何结构：哪些历史信息会先被压缩，哪些块会被稀疏选中，局部窗口与压缩块如何共同参与注意力，Query 与压缩 KV 在什么空间里交互，这些都属于主干网络本身的前向计算路径。DeepSeek V4 也把 Hybrid Attention Architecture、mHC 和 Muon 并列为架构与优化升级，并明确说明该系列模型是在这些设计下完成超大规模预训练的。这说明 `CSA / HCA` 从一开始就是<span style="background-color: #c0c0c0;">训练期与推理期共同生效</span>的主干设计，而不是训练完以后再附加的后处理层。</p>
<p>更具体地说，MLA 把“每个 token 的 KV 应该以什么潜空间形式被缓存和恢复”写进了注意力层；CSA / HCA 则把“历史序列该怎样在序列轴上压缩、检索与混合”写进了注意力层。模型在预训练阶段就必须适应这种信息流，因此它们会同时影响表示学习、优化稳定性、长程依赖建模和最终推理成本。与之相对，KV 量化和 Paged Attention 更接近部署侧技术：即使它们也会显著影响可用上下文和吞吐，它们通常不要求模型从头按该结构重新预训练。</p>
<p>其中 DeepSeek V4 的 `CSA + HCA` 应被明确看作一种<span style="background-color: #c0c0c0;">混合注意力机制</span>。它把 CSA 这种“压缩后做稀疏重点检索”的模式，与 HCA 这种“重压缩后做全局粗读”的模式组合起来，并额外保留滑动窗口分支补足局部细节。因此它同时具备“局部 / 全局”“稀疏 / 稠密”“轻压缩 / 重压缩”三层混合特征。也正因为如此，把 `CSA / HCA` 只理解成 KV Cache 压缩技巧是不够的；更准确的定位是：它首先是一种混合注意力架构，其次才带来显著的缓存和 FLOPs 压缩收益。</p>
<p>因此，从文章结构看，DeepSeek 这一类注意力创新应当先在“注意力机制”这里出现，作为<span style="background-color: #c0c0c0;">混合注意力的长上下文分支</span>做总引；而后文“KV Cache 压缩”再展开它们为何能显著降低缓存和 FLOPs、各自内部具体有哪些组件。这样章节关系才准确：前者回答“这是不是注意力机制本身的创新”，后者回答“这种创新最终把成本压到了哪里”。</p>
<div class="blog_h3"><span class="graybg">KV Cache</span></div>
<p>KV Cache（Key-Value Cache）是自回归（Autoregressive）解码的关键工程优化：生成到第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步时，历史 token 的 Key/Value 已经在前序计算中得到；缓存它们可以避免每一步都重算整段历史的 K/V。</p>
<p>形式上，单层注意力在序列长度为 <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 时需要缓存：</p>
<span displaypfx="" class="mathjax-container">\[K,V\in\mathbb{R}^{L\times n_{\text{kv}}\times d_k}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(n_{\text{kv}}\)</span> 是 KV 头数量（对标准多头注意力通常等于头数；对 GQA/MQA 通常更小），<span displaypfx="inline-" class="mathjax-container">\(d_k\)</span> 是每个 head 的维度。忽略实现细节（对齐、分块、paged layout）时，KV Cache 的显存规模近似线性增长：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Mem}_{\mathrm{KV}}\approx 2\cdot N_{\text{layers}}\cdot B\cdot L\cdot n_{\text{kv}}\cdot d_k\cdot \text{bytes}\]</span>
<p>这里前面的 2 来自同时缓存 K 与 V；<span displaypfx="inline-" class="mathjax-container">\(B\)</span> 是并发请求（batch）数；<span displaypfx="inline-" class="mathjax-container">\(\text{bytes}\)</span> 是每元素字节数（FP16/BF16 为 2）。因此 KV Cache 常成为长上下文与高并发推理的显存瓶颈。</p>
<p>KV Cache 的典型优化方向包括：</p>
<ul>
<li>减少 <span displaypfx="inline-" class="mathjax-container">\(n_{\text{kv}}\)</span>，例如使用 GQA / MQA。</li>
<li>压缩 KV，例如 KV 量化、低秩表示、选择性缓存。</li>
<li>改进分配与复用，例如 Paged Attention、前缀缓存（Prompt Caching）。</li>
</ul>
<div class="blog_h3"><span class="graybg">FlashAttention</span></div>
<p>FlashAttention 是一种对标准注意力（Standard Attention）的高性能精确实现：它不改变 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{softmax}(QK^\top/\sqrt{d_k})V\)</span> 这个数学结果，而是通过分块（Tiling）、融合计算与在线 softmax，减少大规模中间矩阵在 HBM 与片上存储之间的来回搬运。因此，它首先是一种<span style="background-color: #c0c0c0;">注意力算子实现优化</span>，而不是新的模型结构。应用阶段上，训练与推理都可以使用 FlashAttention；在推理里，它最典型地加速的是预填充（Prefill）阶段，因为这时需要对整段输入做完整注意力计算，序列长、 <span displaypfx="inline-" class="mathjax-container">\(QK^\top\)</span> 代价高，FlashAttention 的收益最明显。到了逐 token 解码（Decode）阶段，单步 query 很短，瓶颈更常转向 KV Cache 读取、采样与调度，此时仍可使用面向解码优化的 Flash-Decoding / FlashAttention 变体，但收益模式已不同于预填充阶段。</p>
<p>从软件栈位置看，FlashAttention 可以放在<span style="background-color: #c0c0c0;">内核级别（Kernel-level）/ 后端级别（Backend-level）</span>来理解：上层框架仍然调用“注意力”这个算子，但底层并不一定走朴素的矩阵乘法 + softmax + 再乘 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 三步显式实现，而是改由高度融合的 GPU kernel 完成。是否真的启用 FlashAttention，取决于框架版本、后端实现、数据类型、head 维度、掩码形式以及硬件架构是否匹配。工程上常见支持平台是 NVIDIA 的 Ampere / Ada / Hopper，以及 AMD ROCm 生态中的部分高端 GPU；若硬件或后端条件不满足，框架通常会自动回退到 memory-efficient attention、cuDNN attention 或更普通的数学实现。</p>
<div class="blog_h4"><span class="graybg">为什么需要 FlashAttention</span></div>
<p>FlashAttention要解决的是标准注意力（Standard Attention）在长序列上的 <span style="background-color: #c0c0c0;">中间张量 IO 成本</span> 过高。标准缩放点积注意力（Scaled Dot-Product Attention）可写为：</p>
<span displaypfx="" class="mathjax-container">\[S=\frac{QK^\top}{\sqrt{d_k}},\quad P=\mathrm{softmax}(S),\quad O=PV\]</span>
<p>其中：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(Q\in\mathbb{R}^{N\times d_k}\)</span>：查询矩阵（Query Matrix），<span displaypfx="inline-" class="mathjax-container">\(N\)</span> 是序列长度， <span displaypfx="inline-" class="mathjax-container">\(d_k\)</span> 是每个 head 的查询/键维度。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(K\in\mathbb{R}^{N\times d_k}\)</span>：键矩阵（Key Matrix），与 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 做点积打分。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(V\in\mathbb{R}^{N\times d_v}\)</span>：值矩阵（Value Matrix），<span displaypfx="inline-" class="mathjax-container">\(d_v\)</span> 是每个 head 的值维度。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(S\in\mathbb{R}^{N\times N}\)</span>：注意力分数矩阵（Score Matrix），其中 <span displaypfx="inline-" class="mathjax-container">\(S_{ij}\)</span> 表示第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 query 对第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个 key 的未归一化打分。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(P\in\mathbb{R}^{N\times N}\)</span>：softmax 归一化后的注意力权重矩阵（Attention Probability Matrix）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(O\in\mathbb{R}^{N\times d_v}\)</span>：最终输出矩阵（Output Matrix）。</li>
</ul>
<p>问题集中在 <span displaypfx="inline-" class="mathjax-container">\(S\)</span> 和很多实现中的 <span displaypfx="inline-" class="mathjax-container">\(P\)</span>：它们都是 <span displaypfx="inline-" class="mathjax-container">\(N\times N\)</span> 规模。序列一长，中间矩阵就会迅速膨胀。计算复杂度依然是 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{O}(N^2)\)</span> 级别，但在 GPU 上更先撞上的往往不是算力上限，而是高带宽显存（High Bandwidth Memory, HBM）与片上共享内存 / SRAM（Static Random Access Memory, SRAM）之间的数据搬运成本。</p>
<p>传统实现通常经历三步：先算出整个 <span displaypfx="inline-" class="mathjax-container">\(S=QK^\top\)</span> 并写回显存；再把它读出来做 softmax，得到 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 并再次写回；最后再把 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 读出来与 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 相乘得到 <span displaypfx="inline-" class="mathjax-container">\(O\)</span>。这意味着真正拖慢速度的往往不是矩阵乘法本身，而是对 <span displaypfx="inline-" class="mathjax-container">\(N^2\)</span> 中间结果的反复显式物化（Materialization）与反复搬运。</p>
<p>一个直接类比是流水线工厂。普通注意力像“先把全部半成品都堆进仓库，再统一拿出来做下一道工序”；仓库本身就成了瓶颈。FlashAttention 则像“边加工边流转”的流水线：中间块只在车间里短暂停留，不建立巨大的中间仓库。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>FlashAttention 的核心可以压缩成一句话：<span style="background-color: #c0c0c0;">分块（Tiling）+ 在线 softmax（Online Softmax）+ 融合输出（Fused Output Accumulation）</span>。</p>
<p>它并不改变注意力的数学目标，仍然精确计算同一个 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{softmax}(QK^\top/\sqrt{d_k})V\)</span>；它改变的是计算顺序与中间结果的存储方式。具体来说，FlashAttention 不再把整个 <span displaypfx="inline-" class="mathjax-container">\(N\times N\)</span> 的注意力矩阵一次性算完并落到 HBM 中，而是把 <span displaypfx="inline-" class="mathjax-container">\(Q,K,V\)</span> 切成若干小块（tiles），每次只在 SRAM 中处理一小块分数、归一化和输出累加。</p>
<p>设查询块（query tile）为 <span displaypfx="inline-" class="mathjax-container">\(Q_i\in\mathbb{R}^{B_q\times d_k}\)</span>，键块和值块分别为 <span displaypfx="inline-" class="mathjax-container">\(K_j\in\mathbb{R}^{B_k\times d_k}\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(V_j\in\mathbb{R}^{B_k\times d_v}\)</span>。这里 <span displaypfx="inline-" class="mathjax-container">\(B_q\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(B_k\)</span> 是 tile 大小，远小于完整序列长度 <span displaypfx="inline-" class="mathjax-container">\(N\)</span>。FlashAttention 每次只把这样的局部块搬进 SRAM，在块内完成当前 query tile 对当前 key/value tile 的全部贡献计算。</p>
<div class="blog_h4"><span class="graybg">数学本质：块级注意力与在线 softmax</span></div>
<p>对第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 query 块和第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个 key/value 块，先计算块级分数矩阵：</p>
<span displaypfx="" class="mathjax-container">\[S_{ij}=\frac{Q_iK_j^\top}{\sqrt{d_k}},\qquad S_{ij}\in\mathbb{R}^{B_q\times B_k}\]</span>
<p>其中：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(S_{ij}\)</span>：当前块内的注意力打分矩阵。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(S_{ij}[r,c]\)</span>：query 块中第 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 行与 key 块中第 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 行的打分。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\sqrt{d_k}\)</span>：缩放因子，用于抑制点积随维度增长而导致的 softmax 饱和。</li>
</ul>
<p>难点在于 softmax 的分母依赖整行所有 key：对一个 query 而言，必须把它对所有位置的打分都考虑进去，才能完成归一化。FlashAttention 的关键突破是：不必先看到整行全部元素，再做 softmax；可以用在线算法维护“到目前为止的最大值、分母和分子累加量”，随着块不断读入而精确更新。</p>
<p>对当前 query 块 <span displaypfx="inline-" class="mathjax-container">\(Q_i\)</span>，FlashAttention 维护三个按行统计的状态：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{m}_i\in\mathbb{R}^{B_q},\qquad \boldsymbol{\ell}_i\in\mathbb{R}^{B_q},\qquad R_i\in\mathbb{R}^{B_q\times d_v}\]</span>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{m}_i\)</span>：每个 query 行到目前为止见过的最大分数（row-wise running max）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\ell}_i\)</span>：每个 query 行当前的 softmax 分母累加量。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(R_i\)</span>：每个 query 行对输出向量的未归一化加权和（unnormalized weighted sum）。</li>
</ul>
<p>初始时可设：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{m}_i=-\infty,\qquad \boldsymbol{\ell}_i=\mathbf{0},\qquad R_i=0\]</span>
<p>当读入第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个块时，先求该块每一行的局部最大值：</p>
<span displaypfx="" class="mathjax-container">\[\tilde{\mathbf{m}}_{ij}=\mathrm{rowmax}(S_{ij})\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{rowmax}(\cdot)\)</span> 表示对矩阵每一行取最大值，因此输出是长度为 <span displaypfx="inline-" class="mathjax-container">\(B_q\)</span> 的向量。再把旧最大值和当前块最大值合并成新的全局参考点：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{m}_i^{\mathrm{new}}=\max\!\left(\mathbf{m}_i,\tilde{\mathbf{m}}_{ij}\right)\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(\max\)</span> 是逐元素最大值（element-wise max），因为每个 query 行都维护自己的 softmax 参考值。</p>
<p>接着把当前块的指数项按新参考点重写：</p>
<span displaypfx="" class="mathjax-container">\[P_{ij}=\exp\!\left(S_{ij}-\mathbf{m}_i^{\mathrm{new}}\mathbf{1}^\top\right)\]</span>
<p>其中：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(P_{ij}\in\mathbb{R}^{B_q\times B_k}\)</span>：当前块中按新最大值平移后的指数权重。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{1}\in\mathbb{R}^{B_k}\)</span>：全 1 向量，用于把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{m}_i^{\mathrm{new}}\)</span> 广播到块内每一列。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\exp(\cdot)\)</span>：逐元素指数函数。</li>
</ul>
<p>然后更新分母累加量：</p>
<span displaypfx="" class="mathjax-container">\[\boldsymbol{\ell}_i^{\mathrm{new}}=\exp\!\left(\mathbf{m}_i-\mathbf{m}_i^{\mathrm{new}}\right)\odot \boldsymbol{\ell}_i+\mathrm{rowsum}(P_{ij})\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\odot\)</span> 表示逐元素乘法， <span displaypfx="inline-" class="mathjax-container">\(\mathrm{rowsum}(P_{ij})\)</span> 表示对 <span displaypfx="inline-" class="mathjax-container">\(P_{ij}\)</span> 每一行求和。这个式子的含义是：旧块已经累积的分母，先因为参考最大值改变而按 <span displaypfx="inline-" class="mathjax-container">\(\exp(\mathbf{m}_i-\mathbf{m}_i^{\mathrm{new}})\)</span> 重新缩放，再加上当前块的新贡献。</p>
<p>再更新输出分子的累加量：</p>
<span displaypfx="" class="mathjax-container">\[R_i^{\mathrm{new}}=\mathrm{Diag}\!\left(\exp\!\left(\mathbf{m}_i-\mathbf{m}_i^{\mathrm{new}}\right)\right)R_i+P_{ij}V_j\]</span>
<p>其中：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathrm{Diag}(\cdot)\)</span>：把向量放到对角线上形成对角矩阵，用于按行缩放 <span displaypfx="inline-" class="mathjax-container">\(R_i\)</span>。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(P_{ij}V_j\in\mathbb{R}^{B_q\times d_v}\)</span>：当前块对输出的新增贡献。</li>
</ul>
<p>所有 <span displaypfx="inline-" class="mathjax-container">\(K_j,V_j\)</span> 块处理完之后，当前 query 块的最终输出为：</p>
<span displaypfx="" class="mathjax-container">\[O_i=\mathrm{Diag}\!\left((\boldsymbol{\ell}_i)^{-1}\right)R_i\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\((\boldsymbol{\ell}_i)^{-1}\)</span> 表示对向量每个元素取倒数，作用是把“未归一化加权和”除以 softmax 分母，从而得到真正的注意力输出。</p>
<div class="blog_h4"><span class="graybg">为什么这仍然是精确 softmax</span></div>
<p>FlashAttention 通过一种保持数值等价的累计方式精确计算 softmax。设某一行已经处理过的旧分数集合为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{A}\)</span>，其旧最大值为 <span displaypfx="inline-" class="mathjax-container">\(m_{\mathrm{old}}\)</span>，旧分母为：</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{old}}=\sum_{x\in\mathcal{A}} e^{x-m_{\mathrm{old}}}\]</span>
<p>新读入一块分数集合 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{B}\)</span> 后，若新的全局最大值变成 <span displaypfx="inline-" class="mathjax-container">\(m_{\mathrm{new}}\)</span>，则旧部分相对于新参考点的分母贡献恰好变成：</p>
<span displaypfx="" class="mathjax-container">\[\sum_{x\in\mathcal{A}} e^{x-m_{\mathrm{new}}}=e^{m_{\mathrm{old}}-m_{\mathrm{new}}}\sum_{x\in\mathcal{A}} e^{x-m_{\mathrm{old}}}=e^{m_{\mathrm{old}}-m_{\mathrm{new}}}\ell_{\mathrm{old}}\]</span>
<p>这正是在线更新公式里那一项缩放因子的来源。分子累加量 <span displaypfx="inline-" class="mathjax-container">\(R_i\)</span> 也是同样的道理：旧部分先按新参考点缩放，再加上新块贡献。因此块级处理结束后得到的 <span displaypfx="inline-" class="mathjax-container">\(O_i\)</span> 与一次性对整行做 softmax 再乘 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 的结果完全一致。</p>
<div class="blog_h4"><span class="graybg">与普通 Attention 的区别</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">维度</td>
<td style="text-align: center;">普通 Attention</td>
<td style="text-align: center;">FlashAttention</td>
</tr>
</thead>
<tbody>
<tr>
<td>数学目标</td>
<td>计算 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{softmax}(QK^\top/\sqrt{d_k})V\)</span></td>
<td>计算同一个精确结果，不改目标函数</td>
</tr>
<tr>
<td>中间矩阵</td>
<td>常显式存 <span displaypfx="inline-" class="mathjax-container">\(S\)</span>，很多实现还显式存 <span displaypfx="inline-" class="mathjax-container">\(P\)</span></td>
<td>不显式存完整 <span displaypfx="inline-" class="mathjax-container">\(N\times N\)</span> 矩阵，只保留 tile 级临时块与行级累加状态</td>
</tr>
<tr>
<td>计算顺序</td>
<td>先全部算完分数，再整体 softmax，再乘 <span displaypfx="inline-" class="mathjax-container">\(V\)</span></td>
<td>边读块边更新 softmax，边把当前块对输出的贡献累加进去</td>
</tr>
<tr>
<td>显存特征</td>
<td>中间激活常呈 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{O}(N^2)\)</span> 增长</td>
<td>额外中间存储近似降到 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{O}(N)\)</span> 级别</td>
</tr>
<tr>
<td>性能瓶颈</td>
<td>更容易受 HBM 读写限制，属于强 memory-bound 场景</td>
<td>显著减少 HBM 往返，更接近 compute-bound</td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">为什么它会快</span></div>
<p>FlashAttention 的速度优势主要来自 IO 模式优化，而不是渐近计算复杂度下降。它的核心收益主要来自四点：</p>
<ul>
<li>减少 HBM 访问：不再反复把 <span displaypfx="inline-" class="mathjax-container">\(S\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 这类 <span displaypfx="inline-" class="mathjax-container">\(N^2\)</span> 中间张量写回、读回。</li>
<li>提升 SRAM 复用：一个 tile 被搬进片上后，会在同一块内连续完成分数计算、归一化和输出累加。</li>
<li>算子融合（Kernel Fusion）：原本分散的 <span displaypfx="inline-" class="mathjax-container">\(QK^\top\)</span>、softmax、<span displaypfx="inline-" class="mathjax-container">\(PV\)</span> 被压成一条更短的数据通路。</li>
<li>数值稳定：在线 softmax 仍然使用减最大值（max trick），避免指数溢出，也避免了“先大矩阵 softmax 再回写”带来的额外数值压力。</li>
</ul>
<p>因此，FlashAttention 的本质是减少无效搬运，而不是改变注意力本身的数学定义。从硬件视角看，它把一个明显受内存带宽制约的算子，改造成更能吃满矩阵乘法单元和 Tensor Core 的实现。</p>
<div class="blog_h4"><span class="graybg">复杂度与工程直觉</span></div>
<p>复杂度上需要严格区分“算了多少”和“存了多少”。FlashAttention 与普通注意力在算术复杂度上仍然同阶，因为每个 query 与每个 key 的交互并没有消失：</p>
<span displaypfx="" class="mathjax-container">\[\text{FLOPs: }\mathcal{O}(N^2d_k)\quad\text{vs.}\quad \mathcal{O}(N^2d_k)\]</span>
<p>但中间激活的显存复杂度发生了根本变化。若只看注意力算子额外需要保留的中间结果，则：</p>
<span displaypfx="" class="mathjax-container">\[\text{普通 Attention: }\mathcal{O}(N^2),\qquad \text{FlashAttention: }\mathcal{O}(N)\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{O}(N)\)</span> 指的是按行维护的 softmax 统计量与输出累加量；tile 临时块的大小由 <span displaypfx="inline-" class="mathjax-container">\(B_q,B_k\)</span> 控制，不随完整序列平方增长。工程直觉可以概括为：<span style="background-color: #c0c0c0;">算力阶数没变，但仓库规模从平方级中间仓库变成了线性级流水线缓存</span>。</p>
<div class="blog_h4"><span class="graybg">一个极简伪代码</span></div>
<pre class="crayon-plain-tag">for each query tile Q_i:
    m_i = -inf
    l_i = 0
    R_i = 0
    for each key/value tile (K_j, V_j):
        S_ij = Q_i K_j^T / sqrt(d_k)
        m_new = max(m_i, rowmax(S_ij))
        P_ij = exp(S_ij - m_new)
        l_i = exp(m_i - m_new) * l_i + rowsum(P_ij)
        R_i = diag(exp(m_i - m_new)) * R_i + P_ij * V_j
        m_i = m_new
    O_i = diag(1 / l_i) * R_i</pre>
<p>这段伪代码对应的正是“边看块、边归一化、边输出”的流水线结构。与普通实现相比，最大的变化不是公式，而是调度顺序。</p>
<div class="blog_h4"><span class="graybg">反向传播（Backward）：为什么也能不存 <span displaypfx="inline-" class="mathjax-container">\(N^2\)</span></span></div>
<p>FlashAttention 的关键价值不仅在前向传播（Forward Pass），也在反向传播（Backward Pass）。训练时真正吃显存的不只是前向输出，还包括为了求梯度而保留的中间激活。如果 backward 仍然要求把完整的 <span displaypfx="inline-" class="mathjax-container">\(S\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 存下来，那么前向省出来的显存会被反向阶段重新吃掉。因此，FlashAttention backward 的核心原则与前向一致：<span style="background-color: #c0c0c0;">不保存 <span displaypfx="inline-" class="mathjax-container">\(N\times N\)</span> 注意力矩阵，而是在 backward 中按块重算（recompute）它们</span>。</p>
<p>设前向定义为：</p>
<span displaypfx="" class="mathjax-container">\[S=\frac{QK^\top}{\sqrt{d_k}},\qquad P=\mathrm{softmax}(S),\qquad O=PV\]</span>
<p>设损失函数为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}\)</span>，并记上游传回的输出梯度为：</p>
<span displaypfx="" class="mathjax-container">\[G=\frac{\partial \mathcal{L}}{\partial O},\qquad G\in\mathbb{R}^{N\times d_v}\]</span>
<p>这里：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}\)</span>：整个模型的标量损失（scalar loss）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(G\)</span>：损失对注意力输出 <span displaypfx="inline-" class="mathjax-container">\(O\)</span> 的梯度，也就是 backward 进入注意力层时收到的上游信号。</li>
</ul>
<p>普通 attention 的 backward 可以按链式法则拆成四步。先对 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 求梯度：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial \mathcal{L}}{\partial V}=P^\top G\]</span>
<p>这个式子表示：某个 value 向量 <span displaypfx="inline-" class="mathjax-container">\(v_j\)</span> 对多少个 query 产生了贡献，就会按相应注意力权重 <span displaypfx="inline-" class="mathjax-container">\(P_{ij}\)</span> 把这些上游梯度累加回来。</p>
<p>再对概率矩阵 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 求梯度：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial \mathcal{L}}{\partial P}=GV^\top\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial P}\in\mathbb{R}^{N\times N}\)</span> 的第 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 项表示：若第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 列的注意力权重略有变化，会怎样影响损失。</p>
<p>关键一步是 softmax 的梯度。对第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行，记 <span displaypfx="inline-" class="mathjax-container">\(p_i\)</span> 为第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行概率向量， <span displaypfx="inline-" class="mathjax-container">\(g_i^P\)</span> 为 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial P}\)</span> 的第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行，则：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial \mathcal{L}}{\partial s_i}=p_i\odot \left(g_i^P-\delta_i\mathbf{1}\right),\qquad \delta_i=\sum_{j=1}^{N} g_{ij}^P p_{ij}\]</span>
<p>其中：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(s_i\)</span>：分数矩阵 <span displaypfx="inline-" class="mathjax-container">\(S\)</span> 的第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\odot\)</span>：逐元素乘法。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\delta_i\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行 softmax Jacobian 压缩后的标量项，用来扣掉“整行归一化”带来的耦合影响。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{1}\in\mathbb{R}^{N}\)</span>：全 1 向量。</li>
</ul>
<p>把所有行拼起来，可写成矩阵形式：</p>
<span displaypfx="" class="mathjax-container">\[D=\mathrm{rowsum}\!\left(\frac{\partial \mathcal{L}}{\partial P}\odot P\right),\qquad \frac{\partial \mathcal{L}}{\partial S}=P\odot \left(\frac{\partial \mathcal{L}}{\partial P}-D\mathbf{1}^\top\right)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(D\in\mathbb{R}^{N}\)</span> 是逐行标量向量，第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个分量就是 <span displaypfx="inline-" class="mathjax-container">\(\delta_i\)</span>。最后再通过 <span displaypfx="inline-" class="mathjax-container">\(S=QK^\top/\sqrt{d_k}\)</span> 回传到 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(K\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial \mathcal{L}}{\partial Q}=\frac{\partial \mathcal{L}}{\partial S}\frac{K}{\sqrt{d_k}},\qquad \frac{\partial \mathcal{L}}{\partial K}=\left(\frac{\partial \mathcal{L}}{\partial S}\right)^\top\frac{Q}{\sqrt{d_k}}\]</span>
<p>若直接照这些公式实现，最大问题是：看起来必须先拿到完整的 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial P}\)</span>，而它们又都是 <span displaypfx="inline-" class="mathjax-container">\(N\times N\)</span>。FlashAttention backward 的突破在于，真正必须永久保存的量远比这少。</p>
<p>第一，前向阶段只需保存每一行的 log-sum-exp 统计量（Log-Sum-Exp Statistics），而不必保存整张 <span displaypfx="inline-" class="mathjax-container">\(P\)</span>。若前向某一行的最大值为 <span displaypfx="inline-" class="mathjax-container">\(m_i\)</span>，归一化因子为 <span displaypfx="inline-" class="mathjax-container">\(\ell_i\)</span>，则可存：</p>
<span displaypfx="" class="mathjax-container">\[L_i=m_i+\log \ell_i=\log\sum_{j=1}^{N} e^{S_{ij}}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(L_i\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行 softmax 分母的对数。只要 backward 时重新算出某个块的分数 <span displaypfx="inline-" class="mathjax-container">\(S_{ij}\)</span>，就可以把该块的概率精确重建为：</p>
<span displaypfx="" class="mathjax-container">\[P_{ij}=\exp\!\left(S_{ij}-L_i\mathbf{1}^\top\right)\]</span>
<p>这说明 backward 不需要读取前向保存下来的整张 <span displaypfx="inline-" class="mathjax-container">\(P\)</span>；它只需要 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span>、<span displaypfx="inline-" class="mathjax-container">\(K\)</span>、行级统计量 <span displaypfx="inline-" class="mathjax-container">\(L_i\)</span>，就能按块把局部概率重新算出来。</p>
<p>第二，softmax backward 中的行级标量 <span displaypfx="inline-" class="mathjax-container">\(\delta_i\)</span> 也可以不通过整张 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial P}\)</span> 显式求和，而是用一个更紧凑的等价式：</p>
<span displaypfx="" class="mathjax-container">\[\delta_i=\sum_{j=1}^{N} g_{ij}^P p_{ij}=g_i^\top o_i\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(g_i\)</span> 是上游梯度矩阵 <span displaypfx="inline-" class="mathjax-container">\(G\)</span> 的第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行， <span displaypfx="inline-" class="mathjax-container">\(o_i\)</span> 是前向输出 <span displaypfx="inline-" class="mathjax-container">\(O\)</span> 的第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行。这个恒等式来自：</p>
<span displaypfx="" class="mathjax-container">\[g_i^P=g_iV^\top,\qquad o_i=p_iV\]</span>
<p>于是：</p>
<span displaypfx="" class="mathjax-container">\[\sum_{j=1}^{N} g_{ij}^P p_{ij}=\sum_{j=1}^{N}(g_i v_j^\top)p_{ij}=g_i\left(\sum_{j=1}^{N}p_{ij}v_j\right)^\top=g_i o_i^\top\]</span>
<p>这一步非常关键，因为它说明 softmax backward 所需的行级校正项 <span displaypfx="inline-" class="mathjax-container">\(\delta_i\)</span>，可以直接由前向输出 <span displaypfx="inline-" class="mathjax-container">\(O\)</span> 和上游梯度 <span displaypfx="inline-" class="mathjax-container">\(G\)</span> 得到，而不需要显式展开整个 <span displaypfx="inline-" class="mathjax-container">\(N\times N\)</span> 概率矩阵。</p>
<p>因此，FlashAttention backward 的块级流程可以概括为：</p>
<ol>
<li>读取一个 query tile <span displaypfx="inline-" class="mathjax-container">\(Q_i\)</span>、对应输出 tile <span displaypfx="inline-" class="mathjax-container">\(O_i\)</span>、上游梯度 tile <span displaypfx="inline-" class="mathjax-container">\(G_i\)</span>，以及该 tile 的行级统计量 <span displaypfx="inline-" class="mathjax-container">\(L_i\)</span>。</li>
<li>逐块读取 <span displaypfx="inline-" class="mathjax-container">\(K_j,V_j\)</span>，重算当前块分数 <span displaypfx="inline-" class="mathjax-container">\(S_{ij}=Q_iK_j^\top/\sqrt{d_k}\)</span>。</li>
<li>由 <span displaypfx="inline-" class="mathjax-container">\(L_i\)</span> 重建当前块概率 <span displaypfx="inline-" class="mathjax-container">\(P_{ij}=\exp(S_{ij}-L_i\mathbf{1}^\top)\)</span>。</li>
<li>用 <span displaypfx="inline-" class="mathjax-container">\(G_iV_j^\top\)</span> 得到当前块的 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial P_{ij}}\)</span>，再结合 <span displaypfx="inline-" class="mathjax-container">\(\delta_i=g_i^\top o_i\)</span> 计算当前块的 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial S_{ij}}\)</span>。</li>
<li>把该块对 <span displaypfx="inline-" class="mathjax-container">\(dQ_i\)</span>、<span displaypfx="inline-" class="mathjax-container">\(dK_j\)</span>、<span displaypfx="inline-" class="mathjax-container">\(dV_j\)</span> 的贡献直接累加到输出梯度中。</li>
</ol>
<p>写成块级公式，就是：</p>
<span displaypfx="" class="mathjax-container">\[dV_j \mathrel{+}= P_{ij}^\top G_i\]</span>
<span displaypfx="" class="mathjax-container">\[dP_{ij}=G_iV_j^\top\]</span>
<span displaypfx="" class="mathjax-container">\[dS_{ij}=P_{ij}\odot \left(dP_{ij}-\delta_i\mathbf{1}^\top\right)\]</span>
<span displaypfx="" class="mathjax-container">\[dQ_i \mathrel{+}= dS_{ij}\frac{K_j}{\sqrt{d_k}},\qquad dK_j \mathrel{+}= dS_{ij}^\top\frac{Q_i}{\sqrt{d_k}}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(dQ_i,dK_j,dV_j\)</span> 分别表示当前块对 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial Q}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial K}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial V}\)</span> 的局部累加贡献；符号 <span displaypfx="inline-" class="mathjax-container">\(\mathrel{+}=\)</span> 表示“把当前块的贡献继续累加到已有梯度里”，而不是一次性覆盖赋值。</p>
<p>这个设计的代价是：backward 需要重算部分前向中的块级分数和概率，因此算术量会比“全存中间矩阵”的朴素实现略多；但现代 GPU 上，额外矩阵乘法通常比反复读写 <span displaypfx="inline-" class="mathjax-container">\(N^2\)</span> HBM 张量便宜得多。于是 FlashAttention backward 的工程哲学可以概括为：<span style="background-color: #c0c0c0;">用少量重算换大幅节省显存与 IO</span>。</p>
<p>一个直观类比是：普通 backward 像把前向每一道工序的全部半成品都堆满仓库，等回头算梯度时再逐件取出来；FlashAttention backward 则更像保留每条流水线的关键账本和最终产物，真正需要某段中间细节时，再按原流程快速重演一小段。仓库变小了，流水线也更连贯。</p>
<div class="blog_h4"><span class="graybg">FlashAttention v1：算法层优化</span></div>
<p>FlashAttention v1 的核心贡献在于算法层：它首先把“注意力必须显式存下 <span displaypfx="inline-" class="mathjax-container">\(N\times N\)</span> 矩阵”这一默认前提打破，给出了一种精确、稳定、块级流式的注意力实现。v1 的关键词是 <span style="background-color: #c0c0c0;">memory optimization</span>：让注意力从“被中间矩阵拖慢”转向“更像一个流式矩阵核”。</p>
<p>在这个阶段，最重要的是先证明：不物化注意力矩阵，仍然可以精确完成前向与反向计算，并把显存墙显著后移。GPU 利用率优化反而是后续问题。它首先解决的是“能不能这样算”的问题。</p>
<div class="blog_h4"><span class="graybg">FlashAttention v2：并行层优化</span></div>
<p>FlashAttention v2 保留了 v1 的数学等价性与在线 softmax 思路，但把优化重点从“省内存”推进到“把 GPU 吃满”。它关注的是并行工作划分（Work Partitioning）：如何把 query 块、head 维度、batch 维度和线程块（Thread Block）组织得更均匀，让更多流式多处理器（Streaming Multiprocessor, SM）同时处于忙碌状态。</p>
<p>v1 的一个现实限制是：虽然显存访问已经大幅减少，但某些场景下并行粒度仍然偏粗，导致 GPU 占用率（Occupancy）不够高。v2 因此重写了 kernel 调度策略，让同一个大任务能够拆给更多线程块并行处理，同时尽量减少线程同步（Synchronization）带来的停顿。</p>
<p>从本质上看，v2 做的不是“新的注意力公式”，而是“同一公式在 GPU 上的更优任务分发”。如果说 v1 的问题是“别把中间矩阵落盘”，那么 v2 的问题就是“别让 GPU 的很多 SM 闲着”。</p>
<p>这也是 v2 在反向传播（Backward Pass）上价值很高的原因。前向只解决一半问题；训练吞吐还取决于 backward kernel 能否在不恢复 <span displaypfx="inline-" class="mathjax-container">\(N^2\)</span> 显存占用的前提下保持高并行度。v2 在这一点上比 v1 更成熟，因此更适合作为训练时的高性能默认实现。</p>
<div class="blog_h4"><span class="graybg">FlashAttention v3：硬件协同优化</span></div>
<p>FlashAttention v3 的重点进一步从并行层推进到硬件协同设计（Hardware Co-design），尤其针对 NVIDIA Hopper / H100 这类新一代 GPU。它不再只关心“块怎么切、线程怎么分”，而是进一步追问：<span style="background-color: #c0c0c0;">数据加载、矩阵计算、结果写回能否形成异步流水线</span>。</p>
<p>v3 的几个代表性关键词包括：</p>
<ul>
<li>异步流水线（Asynchronous Pipeline）：加载下一块数据时，当前块已经在计算，从而重叠 load 与 compute。</li>
<li>Warp 专职分工（Warp Specialization）：不同 warp 分别负责搬运、计算、写回，减少彼此等待。</li>
<li>Tensor Core 深度利用：tile 尺寸与数据流更贴近 Tensor Core 最擅长的矩阵乘法路径。</li>
<li>更适合低精度数据类型：如 FP16、BF16，以及面向新硬件的 FP8 路径。</li>
</ul>
<p>如果把 v1 看成“算法上不建大仓库”，把 v2 看成“让更多工人同时开工”，那么 v3 更像“把整座工厂变成不停顿的装配线”：搬运、计算、写回三条流水同时进行，尽量让每一级硬件资源都不空转。</p>
<div class="blog_h4"><span class="graybg">版本演进总结</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">版本</td>
<td style="text-align: center;">主要优化层次</td>
<td style="text-align: center;">核心目标</td>
<td style="text-align: center;">本质关键词</td>
</tr>
</thead>
<tbody>
<tr>
<td>v1</td>
<td>算法层</td>
<td>避免 <span displaypfx="inline-" class="mathjax-container">\(N^2\)</span> 中间矩阵物化</td>
<td>分块、在线 softmax、融合计算</td>
</tr>
<tr>
<td>v2</td>
<td>并行层</td>
<td>提高 Occupancy，减少同步，提升训练吞吐</td>
<td>更细粒度 work partitioning</td>
</tr>
<tr>
<td>v3</td>
<td>硬件层</td>
<td>让 load / compute / store 深度重叠</td>
<td>异步流水线、warp specialization、Tensor Core 对齐</td>
</tr>
</tbody>
</table>
<p>因此，FlashAttention 的演进可以概括为三层推进：v1 解决“能否不存矩阵”、v2 解决“如何把 GPU 跑满”、v3 解决“如何贴着新硬件的数据通路跑”。三代版本的数学目标完全一致，差异主要体现在实现层面对 IO、并行性与硬件流水的挖掘深度。</p>
<div class="blog_h3"><span class="graybg">状态空间模型（State Space Model, SSM / Mamba）</span></div>
<p>状态空间模型（State Space Model, SSM）用“隐状态递推（State Recurrence）”建模序列：每步用一个小状态 <span displaypfx="inline-" class="mathjax-container">\(s_t\)</span> 累积历史信息，避免显式构造 <span displaypfx="inline-" class="mathjax-container">\(L\times L\)</span> 注意力矩阵。经典线性 SSM 的抽象形式是：</p>
<span displaypfx="" class="mathjax-container">\[s_{t+1}=As_t+Bx_t,\quad y_t=Cs_t+Dx_t\]</span>
<p>近年的 Mamba 等结构可理解为在此基础上引入输入依赖的选择性/门控机制（Selective / Input-dependent Dynamics），使得模型在保持线性复杂度的同时具备更强的表征能力。工程上，SSM 的优势通常体现在长序列吞吐与显存；代价是“按内容随机访问历史”的能力不如注意力直观，因此在需要强检索/对齐的任务上常见的是混合架构或与注意力模块组合使用。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">路线</td>
<td style="text-align: center;">序列复杂度</td>
<td style="text-align: center;">显存瓶颈</td>
<td style="text-align: center;">强项</td>
<td style="text-align: center;">典型代价</td>
</tr>
</thead>
<tbody>
<tr>
<td>精确注意力（FlashAttention 等）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathcal{O}(L^2)\)</span></td>
<td>注意力中间张量 + KV Cache</td>
<td>强检索/对齐；通用能力稳健</td>
<td>长上下文成本陡增；需要大量工程优化（GQA/分页/缓存）</td>
</tr>
<tr>
<td>线性注意力</td>
<td>近似 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{O}(L)\)</span></td>
<td>缓存布局与数值稳定性</td>
<td>极长序列吞吐/显存友好</td>
<td>近似误差；需要专门核函数/特征映射设计</td>
</tr>
<tr>
<td>SSM / Mamba</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathcal{O}(L)\)</span></td>
<td>状态与算子实现</td>
<td>长序列吞吐；流式友好</td>
<td>随机访问历史不如注意力直观；常需混合架构补齐能力</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">前馈网络（FFN）</span></div>
<div class="blog_h3"><span class="graybg">MLP</span></div>
<p>Transformer 层里的前馈网络（Feed-Forward Network, FFN）本质上就是一个位置前馈（Position-wise）MLP：它对每个 token 的向量独立作用，不在序列维度做混合（序列维度的混合由注意力完成）。典型形式是两层线性变换加非线性：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{FFN}(x)=\sigma(xW_1+b_1)W_2+b_2\]</span>
<p>这里为了贴近工程实现，把单个 token 表示写成行向量 <span displaypfx="inline-" class="mathjax-container">\(x\in\mathbb{R}^{1\;\times d_{\text{model}}}\)</span>。若中间宽度为 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{ff}}\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(W_1\in\mathbb{R}^{d_{\text{model}}\;\times d_{\text{ff}}}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(W_2\in\mathbb{R}^{d_{\text{ff}}\;\times d_{\text{model}}}\)</span>，因此这层先把 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span> 维表示升到更宽的 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{ff}}\)</span> 维，再投回原宽度。很多教材把它叫“MLP 模块”，强调的是它在每层里与注意力并列构成 Transformer block 的两大子层。</p>
<div class="blog_h4"><span class="graybg">经典 FFN：先升维，再降维</span></div>
<p>“升维（Up-Projection）”指的是用线性投影把输入映射到更高维的特征空间。新维度不是补零得到的，也不是旧特征的简单复制。若</p>
<span displaypfx="" class="mathjax-container">\[h_{\text{up}}=xW_1+b_1\in\mathbb{R}^{1\;\times d_{\text{ff}}}\]</span>
<p>则第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个中间维度满足</p>
<span displaypfx="" class="mathjax-container">\[(h_{\text{up}})_j=\sum_{i=1}^{d_{\text{model}}}x_i(W_1)_{ij}+b_{1j}\]</span>
<p>这意味着：升出来的每一维都在重新组合输入特征。有些维度更像“检测某种局部模式”，有些维度更像“混合多种语义线索”，中间宽度越大，可供模型学习的组合方式就越多。随后，非线性函数 <span displaypfx="inline-" class="mathjax-container">\(\sigma\)</span>（常见如 GELU / ReLU）对这些组合结果做逐元素变换，把线性组合提升为非线性特征。</p>
<p>“降维（Down-Projection）”也不是简单删掉多余维度，而是再做一次线性组合：</p>
<span displaypfx="" class="mathjax-container">\[h_{\text{down}}=\sigma(h_{\text{up}})W_2+b_2\in\mathbb{R}^{1\;\times d_{\text{model}}}\]</span>
<p>因此，经典 FFN 的结构可以概括成：<span style="background-color: #c0c0c0;">先在更宽的特征空间里生成大量候选特征，再把有用的那部分重新组合回模型主宽度</span>。这就是“升维—非线性—降维”的真正含义。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/ffn-glu-structure-canvas.png"><img class="alignnone size-full wp-image-41679" src="https://blog.gmem.cc/wp-content/uploads/2026/03/ffn-glu-structure-canvas.png" alt="ffn-glu-structure-canvas" width="1920" height="1080" /></a></p>
<div class="blog_h4"><span class="graybg">门控 FFN：以 SwiGLU 为例</span></div>
<p>很多现代大模型会用门控线性单元（Gated Linear Unit, GLU）的变体替代“Linear → 激活 → Linear”的经典 FFN，例如 SwiGLU（Swish-Gated Linear Unit）：把中间层拆成两路并行投影，再做逐元素门控：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{SwiGLU}(x)=\Big(\mathrm{SiLU}(xW_1)\odot (xW_3)\Big)W_2\]</span>
<p>若仍按行向量写法，则 <span displaypfx="inline-" class="mathjax-container">\(W_1,W_3\in\mathbb{R}^{d_{\text{model}}\;\times d_{\text{ff}}}\)</span>， <span displaypfx="inline-" class="mathjax-container">\(W_2\in\mathbb{R}^{d_{\text{ff}}\;\times d_{\text{model}}}\)</span>。这里 <span displaypfx="inline-" class="mathjax-container">\(W_1\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(W_3\)</span> 都是“升维投影”，但角色不同： <span displaypfx="inline-" class="mathjax-container">\(xW_1\)</span> 经过 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{SiLU}\)</span> 后形成门控分支（gate branch），决定每个中间维度应当放大、通过还是抑制； <span displaypfx="inline-" class="mathjax-container">\(xW_3\)</span> 则形成内容分支（value / candidate branch），携带候选特征本身。两者做逐元素乘法 <span displaypfx="inline-" class="mathjax-container">\(\odot\)</span> 后，得到“被门控筛选过的中间表示”，最后再由 <span displaypfx="inline-" class="mathjax-container">\(W_2\)</span> 投回 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span>。</p>
<p>因此， <span displaypfx="inline-" class="mathjax-container">\(W_3\)</span> 不是额外多出来的“神秘矩阵”，而是门控 FFN 里的第二条并行升维支路。没有它，模型只有“激活后的门”，却没有“真正被门控制的候选内容”；有了它，FFN 才能表达“哪些特征值得通过、哪些特征应被压制”这一层选择机制。</p>
<p>这种“哪些维度打开、哪些维度抑制”的规则并不是人工写死的，而是通过训练从数据中学出来的。对第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个中间维度，门控值可以写成 <span displaypfx="inline-" class="mathjax-container">\(g_j=\mathrm{SiLU}((xW_1)_j)\)</span>，候选内容写成 <span displaypfx="inline-" class="mathjax-container">\(c_j=(xW_3)_j\)</span>，二者相乘后该维输出为 <span displaypfx="inline-" class="mathjax-container">\(m_j=g_j c_j\)</span>。若某类输入模式下，让这个维度更大能够降低最终损失，则反向传播会推动 <span displaypfx="inline-" class="mathjax-container">\(W_1\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(W_3\)</span> 把对应的 <span displaypfx="inline-" class="mathjax-container">\(g_j\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(c_j\)</span> 调到更有利的方向；若某个维度会带来噪声、干扰或错误特征，则梯度会推动该维在这类输入上变小，于是门控值逐渐靠近 0，内容即使存在也难以通过。</p>
<p>因此，门控学习到的不是一个离散的“开 / 关开关”，而是一组随输入变化的连续缩放系数。某些维度在数学推理样本上可能长期被放大，在闲聊样本上则被压弱；某些维度对代码括号、缩进、关键字组合更敏感，另一些维度则更偏向实体关系或长距离语义线索。门控 FFN 的本质，是让模型在更宽的中间空间里先生成大量候选特征，再由可学习的输入相关门控决定哪些特征应该被保留、哪些应被抑制。</p>
<p>门控（Gating）让 FFN 具备“按特征选择通过 / 抑制”的能力，在相近参数规模下常带来更好的效果与训练稳定性。与经典两层 FFN 相比，门控 FFN 并不是单纯“多一层”，而是把中间表示拆成“控制信号”和“候选内容”两路，再在中间宽空间里完成细粒度筛选。</p>
<p>从能力角度看，增大 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{ff}}\)</span> 会增加中间表征的自由度（Degree of Freedom, DOF）与参数量，使模型能构造更丰富的非线性特征；但“维度更高”不等于“信息一定更多”，它提供的是可学习的表示空间与容量（Capacity），是否有效取决于数据与训练目标。</p>
<div class="blog_h3"><span class="graybg">Mixture of Experts（MoE）</span></div>
<p>MoE（Mixture of Experts）把 FFN 子层替换成“多个专家网络（Experts）+ 路由器（Router/Gate）”：对每个 token，路由器只激活少数几个专家（Top-k），因此计算量近似不随专家总数线性增长，但参数容量可以大幅增加。</p>
<p>一种常见形式（概念表达）是：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{MoE}(x)=\sum_{e\in\mathrm{TopK}(x)} p_e(x)\,\mathrm{Expert}_e(x),\quad p(x)=\mathrm{softmax}(W_g x)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(p_e(x)\)</span> 是路由概率，专家通常就是不同参数的 FFN。与稠密 FFN 的区别在于：稠密 FFN 对每个 token 都执行同一套参数；MoE 则先由路由器决定“这个 token 该送去哪些专家”，再只计算被选中的少数几个专家。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/moe.png"><img class="alignnone size-full wp-image-41675" src="https://blog.gmem.cc/wp-content/uploads/2026/03/moe.png" alt="moe" width="1024" height="1024" /></a></p>
<p>专家的差异化（Specialization）来源于<span style="background-color: #c0c0c0;">路由选择、梯度暴露和训练约束共同塑造的长期分工</span>。随机初始化只负责打破完全对称；真正让专家“越学越不一样”的，是后续每个专家持续处理不同 token 子分布，并在这些子分布上反复累积参数更新。</p>
<p>这个过程通常由以下几类机制共同推动：</p>
<ul>
<li><span style="background-color: #c0c0c0;">稀疏路由</span>：每个 token 前向时通常只进入 top-k 个专家，因此反向传播时，也只有被选中的专家接收到该 token 的主要梯度。不同专家长期看到的训练样本分布因此不再相同，参数更新方向也随之分化。</li>
<li><span style="background-color: #c0c0c0;">路由—能力自增强</span>：路由器先按当前表示给专家打分；某个专家一旦更常处理一类模式，就会在这类模式上进一步拟合得更好；下一轮遇到相似 token 时，路由器又更容易把它们送回这个专家。久而久之，专家会演化成代码型、数学型、长句法型或领域词汇型等不同处理器。</li>
<li><span style="background-color: #c0c0c0;">负载均衡损失</span>：若完全放任训练，路由器容易把大量 token 都送往少数“热门专家”，其余专家几乎得不到梯度。负载均衡（Load Balancing）辅助损失会惩罚这种失衡，推动更多专家获得稳定训练信号，从而保留分工空间，而不是塌缩成少数几个超忙专家。</li>
<li><span style="background-color: #c0c0c0;">容量限制</span>：工程实现常给每个专家设置每个 batch 最多接收多少 token 的上限。热门专家一旦满载，后续 token 就必须改道到其他专家。这相当于在训练期强行制造“分流”，避免所有高频模式都被同一专家垄断。</li>
<li><span style="background-color: #c0c0c0;">路由噪声与探索</span>：训练早期常在路由分数上加入噪声（Noisy Gating / Jitter）或采用更平滑的选择策略，使模型不会过早把某些专家永久冷启动掉。它的作用类似探索机制：先让更多专家接触不同 token，后续再由训练结果把分工逐步固化。</li>
<li><span style="background-color: #c0c0c0;">Top-k 竞争结构</span>：当多个专家为同一 token 竞争有限的 top-k 名额时，路由器天然在做离散化分配。专家之间并不是同时都拿到完整梯度，而是在竞争中各自吸附不同区域的输入分布。这比稠密加权平均更容易形成明确边界。</li>
<li><span style="background-color: #c0c0c0;">专家参数独立</span>：每个专家有自己独立的 FFN 权重，因此一旦早期路由稍有偏向，后续参数更新就会沿不同轨迹不断放大差异。若专家共享大部分参数，仅保留极少差异分支，则这种专门化能力会明显减弱。</li>
<li><span style="background-color: #c0c0c0;">数据分布本身的可分性</span>：训练语料若天然包含代码、自然语言、表格、数学推导、多语种等明显子分布，专家更容易形成稳定分工；若数据分布高度均匀、模式差异很弱，则专家专门化也会更弱，更接近“多份相似 FFN”。</li>
</ul>
<p>这些机制叠加后，MoE 中“每个专家学不同东西”就不再只是参数副本的偶然漂移，而是带有明确结构约束的分工过程。与多头注意力主要依赖独立参数的自发分化不同，MoE 额外利用<span style="background-color: #c0c0c0;">显式路由、稀疏梯度、负载约束与容量分流</span>来持续放大专家之间的功能差异。</p>
<p>MoE 结构本身不必然引入随机性。若路由使用确定性的 top-k，且推理使用确定性算子，则同一输入在同一权重下输出应是确定的。训练阶段常见的随机性主要来自 dropout、路由噪声（Noisy Gating）以及硬件/并行计算的非确定性；这些会影响训练轨迹，但不等价于“模型本质随机”。</p>
<div class="blog_h2"><span class="graybg">归一化</span></div>
<div class="blog_h3"><span class="graybg">Layer Normalization</span></div>
<p>层归一化（Layer Normalization, LayerNorm）在每个 token 的特征维度上做归一化（Normalization），与 BatchNorm 不同，它不依赖 batch 统计量，因此更适合变长序列与自回归推理。对向量 <span displaypfx="inline-" class="mathjax-container">\(x\in\mathbb{R}^{d_{\text{model}}}\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{LN}(x)=\gamma\odot \frac{x-\mu}{\sqrt{\sigma^2+\epsilon}}+\beta,\quad \mu=\frac{1}{d}\sum_{i=1}^{d}x_i,\ \sigma^2=\frac{1}{d}\sum_{i=1}^{d}(x_i-\mu)^2\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\gamma,\beta\)</span> 是可学习的缩放与平移参数（Learnable Scale/Shift），<span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 是一个很小的正数，用于数值稳定性（Numerical Stability）。归一化本质上要除以标准差或均方根；若当前 token 的各维几乎相同，分母就可能非常接近 0，进而导致输出或梯度被异常放大，甚至出现 NaN / Inf。加入 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 相当于给分母设置一个下界。它通常很小，只在“方差或 RMS 过小”时介入；若 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 取值过大，则会把分母中的真实尺度差异压平，使归一化变弱，模型对幅值变化的敏感度下降。</p>
<p>当前主流的 Transformer 几乎都采用“token 内部归一化”，而不是跨 batch 的 BatchNorm：经典 Transformer、BERT、ViT 这一路架构以 LayerNorm 为主；许多更新的 Decoder-only 大模型则把每个残差块写成 Pre-Norm 结构，并进一步用 RMSNorm 取代标准 LayerNorm。</p>
<p>Pre-LN（Pre-LayerNorm）指先做归一化，再进入 Attention 或 MLP 子层，最后与残差分支相加；其典型形式可写为：</p>
<span displaypfx="" class="mathjax-container">\[y=x+\mathrm{Sublayer}(\mathrm{LN}(x))\]</span>
<p>这种写法把归一化放进残差支路内部，有利于维持深层网络中的梯度流稳定。结合后文残差连接的分析来看，Pre-LN 的一个直接优势是：梯度更容易沿着 <span displaypfx="inline-" class="mathjax-container">\(x\to x+\cdots\)</span> 这条恒等主路向后传播，而不会在进入子层之前就先经历一次“相加后再归一化”的整体重标定。与之对应，Post-LN 会写成 <span displaypfx="inline-" class="mathjax-container">\(y=\mathrm{LN}(x+\mathrm{Sublayer}(x))\)</span>；它在早期 Transformer 中出现较多，但随着层数、上下文窗口和参数规模持续增大，Pre-LN 在大模型训练中更常见。</p>
<p>BatchNorm 很少出现在 Transformer 主干中的原因，是它要求当前表示依赖同一 batch 里其他样本的统计量。对于序列模型，这会带来几个直接问题：</p>
<ul>
<li>变长序列和 padding 会污染 batch 统计。</li>
<li>训练与推理使用的统计规则不同，自回归逐 token 生成时尤其不自然。</li>
<li>大模型训练常依赖小 batch、梯度累积和跨设备切分，batch 统计噪声更大。</li>
</ul>
<p>LayerNorm / RMSNorm 则完全避免了这些问题，因为每个 token 的归一化只依赖其自身特征。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/pre-post-norm.png"><img class="alignnone size-full wp-image-41691" src="https://blog.gmem.cc/wp-content/uploads/2026/03/pre-post-norm.png" alt="pre-post-norm" width="1024" height="1024" /></a></p>
<div class="blog_h3"><span class="graybg">RMS Normalization</span></div>
<p>RMSNorm（Root Mean Square Normalization）与 LayerNorm 的相同点是：都在每个 token 的特征维度上做归一化；不同点是 RMSNorm <span style="background-color: #c0c0c0;">不做去均值</span>，只按均方根（RMS）缩放：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{RMSNorm}(x)=\gamma\odot \frac{x}{\sqrt{\frac{1}{d}\sum_{i=1}^{d}x_i^2+\epsilon}}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\gamma\in\mathbb{R}^{d}\)</span> 是可学习缩放参数（通常不需要 <span displaypfx="inline-" class="mathjax-container">\(\beta\)</span>），<span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 则是数值稳定项（Numerical Stability Term）。当 RMS 很小时，若没有 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span>，分母会过小，微小噪声也可能被异常放大；若 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 过大，又会把不同 token 之间本应存在的尺度差异压平，使归一化变弱。RMSNorm 省掉了均值计算，算子更简单，因此在许多 Decoder-only 大模型中被广泛采用（例如 LLaMA 系列）。</p>
<p>RMSNorm 之所以使用均方根，而不是直接对各维做算术平均，是因为这里要刻画的是<span style="background-color: #c0c0c0;">向量整体有多大</span>，而不是“各维带符号求平均后的中心位置”。若直接用 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{d}\sum_i x_i\)</span>，正负分量会彼此抵消：例如 <span displaypfx="inline-" class="mathjax-container">\((10,-10)\)</span> 的算术平均是 0，但这个向量的整体幅值显然并不小。均方根先平方再平均，保留了各维对整体能量的贡献，又与二范数只差一个 <span displaypfx="inline-" class="mathjax-container">\(\sqrt d\)</span> 的常数因子，因此很适合用来刻画表示的整体尺度。</p>
<p>这也是 RMSNorm 即使不做去均值，仍然常常有效的原因。对 Transformer 主干而言，更核心的问题通常不是“特征均值是否恰好为 0”，而是<span style="background-color: #c0c0c0;">表示的整体尺度能否在深层网络中保持稳定</span>。残差流里真正容易失控的，往往是表示向量的整体幅值在层与层之间持续放大或缩小，进而影响梯度传播、残差叠加与数值稳定。RMSNorm 保留了各维之间的相对方向与相对比例，只对整体大小做统一缩放，因此不会反复改写表示基线；对深层 Transformer 来说，这种“只管尺度、不强行去中心”的处理往往已经足够，而且算子更轻，更适合大规模训练与推理。也正因为如此，归一化与残差连接通常总是一起出现：前者负责稳定尺度，后者负责保留主通路。</p>
<div class="blog_h2"><span class="graybg">残差连接</span></div>
<p>残差连接（Residual Connection）把子层输出与输入做逐元素相加：</p>
<span displaypfx="" class="mathjax-container">\[y=x+\mathrm{Sublayer}(x)\]</span>
<p>它不改变主表示的维度（Dimension）——前提是 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Sublayer}(x)\)</span> 形状相同。这个写法的核心价值，不是“把两份向量简单相加”，而是把深层网络的每一层改写成：<span style="background-color: #c0c0c0;">在已有表示上追加一小步修正，而不是每层都彻底重写整份表示</span>。</p>
<p>若没有残差，网络某一层必须直接学出从输入到输出的完整映射；有了残差后，子层只需学习增量项 <span displaypfx="inline-" class="mathjax-container">\(\Delta(x)=\mathrm{Sublayer}(x)\)</span>。当最优行为接近恒等映射时，学习“加多少修正”通常比学习“整层重新变换成什么样”更容易。这也是残差连接与恒等映射（Identity Mapping）关系紧密的原因：主通路默认保留原信息，子层负责在其上叠加必要变化。</p>
<p>从优化角度看，残差连接直接改变了梯度传播路径。若把一层的输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 看成 <span displaypfx="inline-" class="mathjax-container">\(d\)</span> 维向量，输出写成 <span displaypfx="inline-" class="mathjax-container">\(y=x+f(x)\)</span>，那么这里的求导就不再是标量对标量的导数，而是<span style="background-color: #c0c0c0;">向量对向量的 Jacobian 矩阵</span>。</p>
<p>先看最简单的恒等映射 <span displaypfx="inline-" class="mathjax-container">\(g(x)=x\)</span>。若 <span displaypfx="inline-" class="mathjax-container">\(x=(x_1,\dots,x_d)^\top\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(g_i(x)=x_i\)</span>。它的 Jacobian 第 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 个元素是</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial g_i}{\partial x_j}=\frac{\partial x_i}{\partial x_j}=\begin{cases}1,&amp; i=j\\0,&amp; i\ne j\end{cases}\]</span>
<p>因此，向量对自身的导数不是数字 1，而是恒等矩阵：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial x}{\partial x}=I\]</span>
<p>再对残差块 <span displaypfx="inline-" class="mathjax-container">\(y=x+f(x)\)</span> 求导，就得到</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial y}{\partial x}=\frac{\partial x}{\partial x}+\frac{\partial f(x)}{\partial x}=I+\frac{\partial f(x)}{\partial x}\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(I\)</span> 正对应那条“把输入原样传过去”的恒等分支。它并不表示整层没有维度之间的交互，而只表示：在这条直连路径上，每一维对自身的导数是 1、对其他维的导数是 0。真正的维度混合、特征重组与 token 间交互，仍然由 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial f(x)}{\partial x}\)</span> 负责。含义是：即使子层 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span> 的局部 Jacobian 很小、很噪，或训练初期还没有学好，梯度仍然可以沿着这条恒等路径直接穿过该层，而不必完全依赖 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial f(x)}{\partial x}\)</span>。深层网络因此更不容易出现梯度迅速衰减，训练也更稳定。</p>
<p>从表示角度看，残差连接建立了一条贯穿全网的主通道，这正是前面多次出现的残差流（Residual Stream）。在 Transformer 中，注意力子层负责跨 token 交换信息，MLP / FFN 负责对单个 token 做非线性重组，而它们的输出都不是“另起炉灶”的新表示，而是写回这条主通道。于是每一层都更像是在同一块工作记忆上持续读写：有的层补充局部依赖，有的层补充长程关系，有的层强化事实模式或语法结构。</p>
<p>残差连接还有一个很重要的工程意义：它允许模型在“保留已有信息”和“注入新特征”之间取得平衡。若某层子层输出很弱，网络行为就更接近恒等传递；若某层确实学到了有价值的新模式， <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span> 就会沿某些表示方向显著写回主通道。后续层不需要把两部分精确拆开，只需要继续利用这个叠加后的结果即可，因为后续线性映射、注意力和归一化会在新的坐标方向上重新组织这些信息。</p>
<p>从反向传播（Backpropagation）的角度看，残差连接的价值同样直接。若没有残差，深层网络中的梯度必须连续穿过许多子层 Jacobian，相当于做多次矩阵连乘；当这些局部导数长期偏小，梯度就容易逐层衰减，出现梯度消失（Vanishing Gradient）；当它们长期偏大，又可能造成梯度爆炸（Exploding Gradient）。加入残差后，每一层的局部导数从 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial f(x)}{\partial x}\)</span> 变成了 <span displaypfx="inline-" class="mathjax-container">\(I+\frac{\partial f(x)}{\partial x}\)</span>，于是梯度不再只能依赖子层本身，而始终保留了一条沿恒等分支传播的主路径。可以把它概括成一句话：<span style="background-color: #c0c0c0;">前向传播时保留原信息，反向传播时保留主梯度通路</span>。这正是残差连接能显著缓解深层网络优化困难的根本原因。</p>
<p>这也是为什么残差连接几乎成为现代深网络的标准部件。对于非常深的模型，真正困难的并不是“单层表达能力不够”，而是层数增加后，前向信息更容易被后续变换不断改写，反向梯度也更容易在长链路中衰减或失稳。残差连接用一条恒等主路同时缓解了这两个问题：前向上保留原信息，反向上保留主梯度通路。因此，ResNet、Transformer、扩散模型乃至许多大型序列模型，都会把它作为主干结构的一部分。</p>
<div class="blog_h3"><span class="graybg">流形约束超连接（mHC）</span></div>
<p>标准残差连接默认只有一条主残差流：每层都在同一条表示通道上执行 <span displaypfx="inline-" class="mathjax-container">\(x\mapsto x+f(x)\)</span>。Hyper-Connections（HC）则把这条单通道残差流扩展成多通道残差流，使不同子流之间可以在层与层之间发生受控混合。若把第 <span displaypfx="inline-" class="mathjax-container">\(l\)</span> 层的多路残差状态记为 <span displaypfx="inline-" class="mathjax-container">\(X_l\in\mathbb{R}^{n_{\text{hc}}\times d}\)</span>，则 HC 可以抽象写成</p>
<span displaypfx="" class="mathjax-container">\[X_{l+1}=B_lX_l+C_lF_l(A_lX_l)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(A_l\)</span> 负责把多路残差流混合后送入子层 <span displaypfx="inline-" class="mathjax-container">\(F_l\)</span>， <span displaypfx="inline-" class="mathjax-container">\(C_l\)</span> 负责把子层输出写回多路残差流， <span displaypfx="inline-" class="mathjax-container">\(B_l\)</span> 则负责更新“残差主路本身如何在多路之间流动”。与标准残差相比，这相当于把单车道恒等高速路扩展成多车道互通系统：信息不再只能沿一条固定车道直行，而可以在若干并行残差流之间重新分配与汇合。</p>
<p>这类设计的收益是表达力增强。不同残差子流可以承担不同功能，有的更偏局部模式，有的更偏全局抽象，有的更像中间缓存或专家路由；层间的线性混合再把这些信息重新编排。但问题也随之出现：一旦 <span displaypfx="inline-" class="mathjax-container">\(B_l\)</span> 这类残差映射矩阵完全自由学习，原本标准残差中那条稳定的恒等主路就可能被破坏。于是多路残差虽然更灵活，却可能丢掉残差连接最宝贵的性质，即<span style="background-color: #c0c0c0;">深层堆叠时对信号传播稳定性的保障</span>。</p>
<p>DeepSeek 在这一点上引入了 mHC（Manifold-Constrained Hyper-Connections）。它的核心不是否定 Hyper-Connections，而是把其中最关键的残差映射 <span displaypfx="inline-" class="mathjax-container">\(B_l\)</span> 约束到一个具备稳定性的矩阵流形上。具体地，mHC 要求 <span displaypfx="inline-" class="mathjax-container">\(B_l\)</span> 位于双随机矩阵（Doubly Stochastic Matrix）构成的集合中：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{M}_{\text{DS}}=\{B\in\mathbb{R}^{n\times n}\mid B\mathbf{1}=\mathbf{1},\ \mathbf{1}^{\top}B=\mathbf{1}^{\top},\ B_{ij}\ge 0\}\]</span>
<p>这个约束意味着 <span displaypfx="inline-" class="mathjax-container">\(B_l\)</span> 的每一行和每一列都和为 1，而且所有元素非负。它不再允许残差主路做任意线性变换，而只能做一种“总量守恒的流间混合”。从矩阵角度看，这相当于要求残差信息在多条流之间重新分配时，既不能凭空放大，也不能通过正负抵消把主信号抹掉。</p>
<p>这类约束有三层直接好处。第一，双随机矩阵的谱范数满足 <span displaypfx="inline-" class="mathjax-container">\(\|B_l\|_2\le 1\)</span>，因此残差主路是非扩张的（Non-expansive），有助于抑制深层堆叠中的信号爆炸。第二，双随机矩阵在矩阵乘法下保持封闭；也就是说，多层残差主路连续相乘后，整体仍然保持同类结构，因此稳定性不会在深度方向上迅速丢失。第三，双随机矩阵构成所谓的 Birkhoff polytope，它等价于置换矩阵集合的凸包。这给出了非常清晰的几何解释：<span style="background-color: #c0c0c0;">mHC 中的残差混合，本质上是在若干“重排残差子流”的方式之间做加权平均</span>，而不是任意扭曲整条残差高速路。</p>
<p>若把标准残差看成只有一条车道，因此主路映射固定为 <span displaypfx="inline-" class="mathjax-container">\(1\)</span>；那么 mHC 的意义就是把系统扩展到多车道，但所有匝道与分流规则都必须满足“车流守恒、不会凭空放大、也不会相互抵消”的交通约束。这样一来，模型既获得了多路残差混合带来的表达力，又保住了恒等映射思想强调的稳定主路。这也是它与普通可学习残差混合最根本的区别。</p>
<p>在参数化与实现上，mHC 会先学习一个一般实矩阵，再通过类似 Sinkhorn-Knopp 的投影步骤把它拉回双随机矩阵流形，从而确保约束在训练过程中始终成立。DeepSeek V4 把这一路径用于大规模 Transformer 主干，实质上是在回答一个非常具体的问题：当模型层数、上下文长度和 MoE 结构都继续扩张时，残差连接如何在保留稳定性的前提下容纳更复杂的信息路由。mHC 给出的答案是：<span style="background-color: #c0c0c0;">残差连接仍然是主干，但主干不必永远只有一条线；只要多路混合被限制在合适的几何约束内，残差主路依然可以稳定</span>。</p>
<p>放回 DeepSeek V4 的整体结构里看，这种“多路残差混合”并不是只在层尾出现一次。图中的 Pre-Block Mixing、Residual Mixing 与 Post-Block Mixing，可以理解为 mHC 在 block 内不同位置的具体落地：进入注意力前先混一次，把多路残差流整理成当前子层更适合读取的输入；注意力后在残差主路上再混一次，决定新信息如何写回；进入 MoE 前再混一次，把表示重新组织成更适合专家路由与前馈扩展的形态。也就是说，mHC 并不只是给残差连接“加一个约束矩阵”，而是在<span style="background-color: #c0c0c0;">每个 block 的读入、写回和子层衔接处</span>共同维护多路残差流的稳定传播。</p>
<div class="blog_h2"><span class="graybg">输出处理</span></div>
<p>Transformer 主干（Backbone）本身的直接产物通常不是“类别”或“文字”，而是一组上下文化隐藏状态（Contextual Hidden States）。也就是说，在一次标准前向计算里，模型会先处理当前输入序列中的所有 token，把每个位置都编码成上下文化表示；若输入序列长度为 <span displaypfx="inline-" class="mathjax-container">\(T\)</span>，模型宽度为 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span>，经过最后一层后常得到：</p>
<span displaypfx="" class="mathjax-container">\[H^{(L)}\in\mathbb{R}^{T\;\times d_{\text{model}}}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 是 Transformer 层数，因此 <span displaypfx="inline-" class="mathjax-container">\(H^{(L)}\)</span> 表示“经过第 <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 层之后得到的隐藏状态矩阵”； <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 是当前序列的 token 数，所以这个矩阵一共有 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 行，每一行对应一个位置。若把第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个位置那一行单独记作 <span displaypfx="inline-" class="mathjax-container">\(h_t^{(L)}\)</span>，它表示的就是：第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个 token 在通过全部 <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 层、吸收了上下文信息之后得到的最终向量表示。输出处理（Output Processing）的任务，就是把这组隐藏状态映射到具体任务所需的输出空间：可以是词表概率、类别分数、序列标签、起止位置分数，或回归数值。这里常说的读出（Readout），指的就是：<span style="background-color: #c0c0c0;">主干网络先形成内部表示，再由最后的输出层把这种表示转换成任务空间里的可解释结果</span>。更准确地说，Transformer 主干先产生整段序列的隐藏表示，再由具体任务的输出层决定如何读取这些表示：有的任务会逐位置读出，有的任务只取某个聚合位置；生成任务则在当前前缀对应的隐藏状态基础上，继续决定下一个 token 的输出。</p>
<div class="blog_h3"><span class="graybg">从隐藏状态到输出空间</span></div>
<p>最常见的输出处理是在线性读出（Linear Readout）层中，把 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span> 维隐藏状态投影到目标维度 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{out}}\)</span>。若按 token 逐位置读出，可写成：</p>
<span displaypfx="" class="mathjax-container">\[Z=HW_{\text{out}}+\mathbf{1}b^\top\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(H\in\mathbb{R}^{T\;\times d_{\text{model}}}\)</span> 是最后一层隐藏状态； <span displaypfx="inline-" class="mathjax-container">\(W_{\text{out}}\in\mathbb{R}^{d_{\text{model}}\;\times d_{\text{out}}}\)</span> 是输出投影矩阵； <span displaypfx="inline-" class="mathjax-container">\(b\in\mathbb{R}^{d_{\text{out}}}\)</span> 是偏置； <span displaypfx="inline-" class="mathjax-container">\(\mathbf{1}\in\mathbb{R}^{T}\)</span> 是全 1 列向量，用来把同一个偏置加到每个位置； <span displaypfx="inline-" class="mathjax-container">\(Z\in\mathbb{R}^{T\;\times d_{\text{out}}}\)</span> 则是每个位置对应的输出分数。若任务是词表预测，则 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{out}}=V\)</span>， <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 是词表大小；若任务是 token 分类，则 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{out}}=C\)</span>， <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 是标签类别数。</p>
<p>并非所有任务都对每个 token 独立读出。序列分类常从整段序列中先取一个聚合表示，再做线性映射。例如 BERT 类模型常使用 <span displaypfx="inline-" class="mathjax-container">\([\mathrm{CLS}]\)</span> 位置的隐藏状态 <span displaypfx="inline-" class="mathjax-container">\(h_{\mathrm{CLS}}\)</span>，再输出：</p>
<span displaypfx="" class="mathjax-container">\[z=h_{\mathrm{CLS}}W_c+b_c\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(z\in\mathbb{R}^{C}\)</span> 是整句的类别 logits。跨度抽取（Span Extraction）任务则常对每个位置分别给出“作为起点”和“作为终点”的分数；序列到序列任务中，解码器则对每个时间步读出一个词表分布。</p>
<div class="blog_h3"><span class="graybg">语言模型中的输出处理</span></div>
<p>在 Decoder-only 或 Encoder-Decoder 的生成端，输出处理通常还包含最后一次归一化层（如 LayerNorm 或 RMSNorm）以及语言模型头（Language Modeling Head, LM Head）。概念上可写成：</p>
<span displaypfx="" class="mathjax-container">\[\tilde H=\mathrm{Norm}(H^{(L)}),\qquad Z=\tilde H W_{\mathrm{vocab}}+\mathbf{1}b^\top\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\tilde H\in\mathbb{R}^{T\;\times d_{\text{model}}}\)</span> 是最终归一化后的隐藏状态； <span displaypfx="inline-" class="mathjax-container">\(W_{\mathrm{vocab}}\in\mathbb{R}^{d_{\text{model}}\;\times V}\)</span> 是词表投影矩阵； <span displaypfx="inline-" class="mathjax-container">\(Z\in\mathbb{R}^{T\;\times V}\)</span> 是每个位置对整个词表的 logits。矩阵第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 行 <span displaypfx="inline-" class="mathjax-container">\(z_t\in\mathbb{R}^{V}\)</span> 描述的是：当模型已经看到当前位置之前允许访问的上下文后，当前位置对每个候选 token 的偏好分数。</p>
<p>这里最后再做一次归一化，并不是多余的重复，而是把“主干内部的表示空间”整理成更适合词表读出的数值形态。Transformer 主干中的隐藏状态一路沿着残差流（Residual Stream）传播，虽然语义信息已经形成，但向量整体尺度仍可能随着层数、上下文和激活模式发生波动。若直接把 <span displaypfx="inline-" class="mathjax-container">\(H^{(L)}\)</span> 送入 <span displaypfx="inline-" class="mathjax-container">\(W_{\mathrm{vocab}}\)</span>，这些尺度变化会被直接放大到 logits 上，使 softmax 有时过尖、有时过平，输出分布与梯度都更难稳定。</p>
<p>最后一次归一化的作用，是在进入词表空间之前先把隐藏状态重新放回一个稳定坐标系里：一方面减弱“幅值忽大忽小”对 logits 的直接干扰，另一方面让 LM Head 更专注于“当前表示朝哪个语义方向更接近某个 token”，而不是过度依赖向量长度本身。换言之，主干网络负责把内容表示出来，最后的归一化负责把这种内容整理到一个尺度可控、便于读出的状态，再交给 <span displaypfx="inline-" class="mathjax-container">\(W_{\mathrm{vocab}}\)</span> 做最终投影。这也是许多现代 Decoder-only 大模型会在输出头前保留一层 LayerNorm 或 RMSNorm 的原因。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/lmhead.png"><img class="alignnone size-large wp-image-41727" src="https://blog.gmem.cc/wp-content/uploads/2026/03/lmhead.png" alt="lmhead" width="710" height="710" /></a></p>
<div class="blog_h4"><span class="graybg">权重共享（Weight Tying）</span></div>
<p>语言模型里常把输入嵌入表 <span displaypfx="inline-" class="mathjax-container">\(E\in\mathbb{R}^{V\;\times d_{\text{model}}}\)</span> 与输出头权重绑定为同一组参数。若不共享，输出头通常写成 <span displaypfx="inline-" class="mathjax-container">\(W_{\mathrm{vocab}}\in\mathbb{R}^{d_{\text{model}}\;\times V}\)</span>；若共享，则直接令</p>
<span displaypfx="" class="mathjax-container">\[W_{\mathrm{vocab}}=E^\top\]</span>
<p>这不是把“两份数据塞进一个矩阵”，而是让同一个参数矩阵在前向计算的两个位置重复使用：输入阶段按 token id 取出 <span displaypfx="inline-" class="mathjax-container">\(E\)</span> 的某一行作为该 token 的嵌入；输出阶段则把隐藏状态 <span displaypfx="inline-" class="mathjax-container">\(h\)</span> 与所有 token 向量做点积，得到整张词表的 logits：</p>
<span displaypfx="" class="mathjax-container">\[z=hE^\top+b\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(z\in\mathbb{R}^{V}\)</span>，第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个分量 <span displaypfx="inline-" class="mathjax-container">\(z_i=h\cdot E_i+b_i\)</span> 表示当前隐藏状态 <span displaypfx="inline-" class="mathjax-container">\(h\)</span> 与第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 token 向量 <span displaypfx="inline-" class="mathjax-container">\(E_i\)</span> 的匹配分数。输入嵌入回答“这个 token 进来时长什么样”，输出头回答“当前语境最像词表里的哪个 token”；Weight Tying 让这两种词向量语义共用同一个坐标系。</p>
<p>这里共享的是参数。训练时，这张矩阵同时接收两类梯度：一类来自输入查表路径，更新当前 batch 真正出现过的 token 行；另一类来自输出 softmax 路径，推动隐藏状态与目标 token 更接近、与竞争 token 拉开。自动求导会把这两部分梯度加到同一份参数上，形成联合更新。因此它通常能减少参数量、增强输入与输出语义空间的一致性，并起到一定正则化（Regularization）作用。</p>
<p>只有在输入嵌入维度与输出读出维度一致时，这种共享才最直接。若模型在读出前额外引入了投影层，使输出维度不再等于 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span>，则需要先做维度变换，或不共享。Weight Tying 因而是常见做法，但不是所有架构都必须采用的硬规则。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/lm-output-head.png"><img class="alignnone size-full wp-image-41479" src="https://blog.gmem.cc/wp-content/uploads/2026/03/lm-output-head.png" alt="lm-output-head" width="1920" height="1080" /></a></p>
<div class="blog_h3"><span class="graybg">从分数到最终结果</span></div>
<p>输出处理的最后一步，是把 logits 变成任务可用的结果。训练时，很多损失函数会直接接收 logits，例如交叉熵损失（Cross-Entropy Loss）内部会把 softmax 与负对数似然（Negative Log-Likelihood）合并计算，以提高数值稳定性。推理时，则通常再做显式后处理：分类任务对 logits 做 softmax 或 sigmoid 得到概率；序列标注任务可在 logits 之上接 CRF 解码；生成任务则对词表 logits 做 softmax 后，再通过贪心搜索（Greedy Decoding）、束搜索（Beam Search）、Top-k 采样或 Top-p 采样等策略选择下一个 token。</p>
<p>以生成任务为例，若当前位置的词表 logits 为 <span displaypfx="inline-" class="mathjax-container">\(z_t\in\mathbb{R}^{V}\)</span>，则先得到条件分布：</p>
<span displaypfx="" class="mathjax-container">\[p(x_{t+1}=i\mid x_{\le t})=\frac{e^{z_{t,i}}}{\sum_{j=1}^{V}e^{z_{t,j}}}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 是词表大小， <span displaypfx="inline-" class="mathjax-container">\(z_{t,i}\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个位置对第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个候选 token 的 logit， <span displaypfx="inline-" class="mathjax-container">\(p(x_{t+1}=i\mid x_{\le t})\)</span> 则是在当前前缀 <span displaypfx="inline-" class="mathjax-container">\(x_{\le t}\)</span> 下，下一个 token 取第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个词的概率。解码策略的区别，不在于 logits 或 softmax 公式不同，而在于：<span style="background-color: #c0c0c0;">拿到这组概率之后，究竟用什么规则选出真正输出的 token</span>。</p>
<div class="blog_h4"><span class="graybg">贪心搜索</span></div>
<p>贪心搜索（Greedy Decoding）是最直接的策略：每一步都选当前概率最大的那个 token。写成公式，就是</p>
<span displaypfx="" class="mathjax-container">\[x_{t+1}=\arg\max_{i} \ p(x_{t+1}=i\mid x_{\le t})\]</span>
<p>它的优点是速度快、实现简单、结果确定；缺点是过于短视。因为它每一步都只看“眼前概率最高”，而不考虑“当前稍差一点、但后续整体更优”的路径。于是贪心搜索很容易陷入局部最优：第一步看起来最稳的选择，不一定能导向整句概率最好的结果。</p>
<p>直觉上，它像每到路口都选眼前最宽的一条路，而不回头评估整条路线是否更通畅。因此贪心适合需要稳定、低延迟输出的场景，但在开放生成任务里往往较保守，也更容易重复。</p>
<div class="blog_h4"><span class="graybg">束搜索</span></div>
<p>束搜索（Beam Search）是在每一步同时保留多个高分候选前缀，而不是像贪心那样只保留 1 条路径。设束宽（Beam Width）为 <span displaypfx="inline-" class="mathjax-container">\(B\)</span>，则在第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步，算法会维护 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 条当前最优候选序列；每条序列再向外扩展多个 token，最后从所有扩展结果中重新筛出新的 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 条最高分路径继续前进。</p>
<p>若一条候选序列为 <span displaypfx="inline-" class="mathjax-container">\(x_{1:T}\)</span>，其常见打分方式是对数概率和：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{score}(x_{1:T})=\sum_{t=1}^{T}\log p(x_t\mid x_{&lt;t})\]</span>
<p>因为概率连乘会非常小，所以实现里通常比较对数概率之和，而不是直接比较概率乘积。有时还会加长度惩罚（Length Penalty），避免模型系统性偏爱过短序列。</p>
<p>束搜索的优点是全局性比贪心更强，常用于机器翻译、摘要等更强调整体序列质量的任务；缺点是计算量更高，而且它本质上仍是“找高分路径”的搜索，不会主动引入随机性，因此输出可能仍然偏保守、偏模板化。</p>
<div class="blog_h4"><span class="graybg">Top-k 采样</span></div>
<p>Top-k 采样（Top-k Sampling）先把概率最高的 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个 token 保留下来，其余 token 概率全部截断为 0，然后在这 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个候选里重新归一化并随机采样。设保留下来的候选集合为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{K}_k\)</span>，则采样分布可写成：</p>
<span displaypfx="" class="mathjax-container">\[p_k(i)= \begin{cases} \frac{p_i}{\sum_{j\in \mathcal{K}_k}p_j}, &amp; i\in \mathcal{K}_k\\ 0, &amp; i\notin \mathcal{K}_k \end{cases}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(p_i\)</span> 是 softmax 后原始概率， <span displaypfx="inline-" class="mathjax-container">\(\mathcal{K}_k\)</span> 是当前概率最高的 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个 token 集合。这样做的效果是：极小概率的长尾 token 不再参与抽样，从而降低胡言乱语或离谱跳转的风险；同时又保留了随机性，不会像贪心那样永远输出同一条路径。</p>
<p>Top-k 的关键超参数是 <span displaypfx="inline-" class="mathjax-container">\(k\)</span>。 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 太小，分布会重新变得接近贪心； <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 太大，又会把很多低质量候选放回来。它本质上是在“稳定性”和“多样性”之间做硬截断式折中。</p>
<div class="blog_h4"><span class="graybg">Top-p 采样</span></div>
<p>Top-p 采样（Top-p Sampling, Nucleus Sampling）不固定保留多少个 token，而是先按概率从高到低排序，再取最小的前缀集合 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{N}_p\)</span>，使其累计概率至少达到阈值 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\sum_{i\in \mathcal{N}_p} p_i \ge p\]</span>
<p>然后只在这个“概率核心区”里重新归一化并随机采样。与 Top-k 相比，Top-p 的保留集合大小是动态变化的：如果当前分布非常尖锐，可能只需要少数几个 token 就能覆盖 90% 或 95% 的概率质量；如果当前分布较平，保留下来的 token 数量就会自动增多。</p>
<p>这种自适应机制更贴合语言生成的实际状态：有些位置模型非常确定，例如固定短语或语法闭合，此时候选空间本来就应很小；有些位置模型不那么确定，例如开放内容展开，此时候选空间应更大。Top-p 因而通常比固定的 Top-k 更灵活，也是现代大模型推理中非常常见的采样策略。</p>
<div class="blog_h4"><span class="graybg">温度与策略取舍</span></div>
<p>温度（Temperature）常与上述采样策略配合使用。若把 logits <span displaypfx="inline-" class="mathjax-container">\(z_{t,i}\)</span> 除以温度 <span displaypfx="inline-" class="mathjax-container">\(\tau\)</span> 后再做 softmax，则有：</p>
<span displaypfx="" class="mathjax-container">\[p_\tau(i)=\frac{e^{z_{t,i}/\tau}}{\sum_{j=1}^{V}e^{z_{t,j}/\tau}}\]</span>
<p>当 <span displaypfx="inline-" class="mathjax-container">\(\tau&lt;1\)</span> 时，分布会变尖，模型更保守；当 <span displaypfx="inline-" class="mathjax-container">\(\tau&gt;1\)</span> 时，分布会变平，采样更发散。于是，解码策略的工程取舍可以概括为：</p>
<ul>
<li>贪心搜索：最快、最稳定，但最短视。</li>
<li>束搜索：更重视整句高分路径，但计算更贵、表达更保守。</li>
<li>Top-k 采样：固定候选数，简单直接，易于控制长尾噪声。</li>
<li>Top-p 采样：候选数自适应，通常更自然、更适合开放生成。</li>
</ul>
<p>当温度设为 <span displaypfx="inline-" class="mathjax-container">\(0\)</span> 时，工程语境里通常就是把系统推向“只取当前最大概率 token”的极限，也就是接近贪心解码（Greedy Decoding）。若模型权重固定、算子实现固定、数值计算路径也完全固定，这种解码理论上应当是确定的；同一输入会得到同一输出。但在线推理系统里，结果仍可能出现轻微不一致，因为真实部署环境常包含非确定性来源（Non-determinism），例如并行归约时浮点加法顺序不同、不同硬件或 kernel 实现带来微小数值差异，以及服务端动态调度、缓存策略或批处理拼接方式改变了底层执行路径。</p>
<p>因此，<span displaypfx="inline-" class="mathjax-container">\(\text{temperature}=0\)</span> 只能说明“解码策略本身不再主动引入随机采样”，并不自动等于“整条推理链路绝对确定”。若业务确实需要强确定性，工程上通常还要同时满足几件事：禁用 Top-k、Top-p 等采样选项，固定随机种子（Seed）（若框架支持），并尽量启用确定性计算（Deterministic Kernels）或等价的确定性执行模式。换句话说，温度控制的是<span style="background-color: #c0c0c0;">分布如何被取样</span>，而强确定性还取决于<span style="background-color: #c0c0c0;">数值计算路径是否也被锁死</span>。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/temperature-softmax-distribution.png"><img class="alignnone size-full wp-image-41737" src="https://blog.gmem.cc/wp-content/uploads/2026/03/temperature-softmax-distribution.png" alt="temperature-softmax-distribution" width="1920" height="1785" /></a></p>
<div class="blog_h4"><span class="graybg">重复惩罚与频率控制</span></div>
<p>仅靠解码策略本身，往往还不足以避免模型进入重复、啰嗦或机械回环的状态。例如开放生成时，模型可能连续输出相同短语，或不断在几个近义表达之间打转。工程上因此常在 logits 层再加入一类后处理规则：对已经出现过的 token 施加惩罚，从而改变下一步的候选分布。</p>
<p>最常见的一类是重复惩罚（Repetition Penalty）。它的思想很直接：若某个 token 已经在当前上下文中出现过，就下调它再次被选中的倾向。实现细节在不同框架里略有差异，一种常见写法是对已出现 token 的 logit <span displaypfx="inline-" class="mathjax-container">\(z_i\)</span> 施加按符号分段的缩放：</p>
<span displaypfx="" class="mathjax-container">\[z_i'= \begin{cases} z_i / r, &amp; z_i&gt;0\\ z_i \cdot r, &amp; z_i\le 0 \end{cases},\qquad r&gt;1\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 是重复惩罚系数。这样处理的目的，是在不破坏 logit 正负号语义的前提下，整体压低“已经出现过的 token 再次被选中”的优势。 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 越大，惩罚越强；过大则可能把正常重复也压掉，使输出变得生硬。</p>
<p>另一类常见控制项是 presence penalty（出现惩罚）与 frequency penalty（频次惩罚）。它们的共同目标是抑制重复，但力度来源不同：前者只关心“出现过没有”，后者关心“已经出现了多少次”。若原始 logit 为 <span displaypfx="inline-" class="mathjax-container">\(z_i\)</span>，token <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 在当前已生成文本中的出现次数为 <span displaypfx="inline-" class="mathjax-container">\(c_i\)</span>，则一个常见抽象写法是：</p>
<span displaypfx="" class="mathjax-container">\[z_i' = z_i - \lambda_{\mathrm{pres}}\mathbf{1}[c_i&gt;0] - \lambda_{\mathrm{freq}}c_i\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\lambda_{\mathrm{pres}}\)</span> 是 presence penalty 系数， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{1}[c_i&gt;0]\)</span> 是指示函数：只要 token <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 出现过至少一次，就减去一个固定惩罚； <span displaypfx="inline-" class="mathjax-container">\(\lambda_{\mathrm{freq}}\)</span> 是 frequency penalty 系数， <span displaypfx="inline-" class="mathjax-container">\(c_i\)</span> 越大，惩罚越强。因此：</p>
<ul>
<li>presence penalty 更像“出现过就提醒一次”，主要鼓励模型换新词、开新话题。</li>
<li>frequency penalty 更像“出现越多罚越重”，主要抑制机械重复和啰嗦堆叠。</li>
</ul>
<p>三者的作用位置都在 softmax 之前：先修改 logits，再重新归一化成概率。它们不是在训练阶段改变模型参数，而是在推理阶段临时改写候选分布。因此，它们更像输出控制器（Output Controller），而不是模型能力本身的一部分。</p>
<p>工程上，这些惩罚项通常与温度、Top-k、Top-p 一起调节。若目标是严谨、稳定、少跑偏的回答，常采用较低温度并只施加较轻的重复控制；若目标是创意写作或开放发散，则可能提高温度，同时保留较温和的 presence penalty，鼓励内容展开但避免原句循环。它们解决的不是“模型知不知道答案”，而是<span style="background-color: #c0c0c0;">模型在已知概率分布下，最终说话风格如何被约束</span>。</p>
<p>因此，Transformer 的“输出”需要分成两个层次理解。主干网络输出的是高维隐藏表示；真正面向任务的可解释结果，来自这些隐藏表示经过归一化、线性读出、概率映射与解码后的最终读出。输出处理连接了通用表示学习与具体任务目标，是 Transformer 从“会表示”走向“会预测、会生成、会决策”的最后一跳。</p>
<div class="blog_h1"><span class="graybg">语言模型</span></div>
<p>语言模型（Language Model）是对自然语言序列概率分布进行建模的模型。给定一段上下文，它学习并估计后续词元（token）或整个序列出现的概率，从而捕捉语言中的词法、语法、语义以及长程依赖结构。现代语言模型通常由参数化神经网络实现，其本质是把语言规律压缩进一个可计算的概率模型中。</p>
<div class="blog_h2"><span class="graybg">语言模型基础知识</span></div>
<div class="blog_h3"><span class="graybg">序列概率视角</span></div>
<p>从严格定义看，语言模型处理的对象是有顺序的 token 序列，而不是无序词集合。若把一句话写成 <span displaypfx="inline-" class="mathjax-container">\(x_1,x_2,\dots,x_T\)</span>，语言模型的核心任务就是为这段序列分配概率；等价地，它也可以被看成在每一步根据已有上下文估计下一个 token 的条件概率。</p>
<span displaypfx="" class="mathjax-container">\[p(x_1,\dots,x_T)=\prod_{t=1}^{T}p(x_t\mid x_1,\dots,x_{t-1})\]</span>
<p>这一定义说明了为什么语言模型天然关心词序、上下文依赖与条件生成。无论后续采用 n-gram 统计模型还是 Transformer，本质上都在近似这类序列概率分布。</p>
<div class="blog_h3"><span class="graybg">分词（Tokenization）</span></div>
<p>这里的分词（Tokenization）指的是：把原始文本切分成模型实际处理的 token 序列，并据此映射到词表（Vocabulary）中的离散 id。它服务于语言模型建模本身，决定模型看到的基本单位是什么。</p>
<p>这与全文检索（Full-text Retrieval）里的“分词”不是同一个概念。检索系统里的分词更强调索引构建、倒排表匹配与查询召回；语言模型里的 tokenization 更强调如何把文本编码成适合训练与推理的离散序列。一个 token 不一定等于自然语言里的“词”，它也可能是子词、单字、标点、空格片段，甚至字节。更具体的 tokenizer 类型与工程差异，见 Transformers 部分的 <pre class="crayon-plain-tag">Tokenization</pre> 小节。</p>
<div class="blog_h3"><span class="graybg">词袋模型（Bag of Words, BoW）</span></div>
<p>词袋模型（Bag of Words, BoW）本质上只做一件事：统计一段文本里各个词出现了多少次，或只记录它是否出现。它把文本表示成词表上的计数向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{c}\in\mathbb{R}^{|\mathcal{V}|}\)</span>，其中每一维对应某个词的出现次数。除了这些词频统计之外，BoW 不保留任何顺序信息，也不建模句法结构、上下文依赖或条件概率。</p>
<p>因此，BoW 严格说并不是语言模型，而是一种早期文本表示方法。它常与朴素贝叶斯（Naive Bayes）、逻辑回归（Logistic Regression）或 TF-IDF 一起用于文本分类、检索和主题分析。它的重要性在于：它展示了“先把文本映射成向量，再交给下游模型处理”的经典思路；但由于它仅仅统计词是否出现以及出现次数，无法区分“我喜欢你”和“你喜欢我”这类序列差异，也不能承担现代语言模型那种条件生成任务。</p>
<div class="blog_h3"><span class="graybg">词嵌入（Word Embedding）</span></div>
<p>词嵌入（Word Embedding）把每个词映射到一个低维稠密向量。与 BoW 的计数统计不同，词嵌入不再把每个词看成彼此独立的离散符号，而是让共现模式或语义相近的词在向量空间里彼此接近。Word2Vec、GloVe 和 FastText 都属于这一类方法。</p>
<p>经典词嵌入通常是静态的：同一个词在任何上下文里共享同一个向量。以 <pre class="crayon-plain-tag">bank</pre> 为例，在 <pre class="crayon-plain-tag">open a bank account</pre> 中它指银行，在 <pre class="crayon-plain-tag">sit on the river bank</pre> 中它指河岸；传统词嵌入一般仍会给它同一组参数，因此无法在表示层直接区分这两种词义。这也是后来上下文化表示（Contextual Representation）变得重要的原因。</p>
<div class="blog_h3"><span class="graybg">句子嵌入（Sentence Embedding）</span></div>
<p>句子嵌入（Sentence Embedding）进一步把整句或整段文本表示为一个向量。它关注的不再是单词层面的局部语义，而是整个输入的综合语义，常用于分类、检索、匹配与聚类。</p>
<p>历史上，基于循环神经网络（Recurrent Neural Network, RNN）的序列到序列模型（Sequence-to-Sequence, Seq2Seq）曾经采用“先编码成一个固定长度向量，再由解码器生成输出”的路径。Sutskever、Vinyals 和 Le 在 2014 年提出的 Seq2Seq 工作，就用深层 LSTM 把输入序列压缩为固定维度向量；这种单向量压缩在长句上容易形成信息瓶颈，而 RNN 本身的串行计算方式也限制了并行效率，并使长程依赖建模变得困难。Bahdanau、Cho 和 Bengio 在 2014 年提出的注意力机制（Attention Mechanism）开始缓解这一问题：解码器在每一步都能直接参考输入序列的不同位置，而不必把整句信息全部压缩进单一向量中。</p>
<p>真正的结构转折点来自 2017 年的 <span class="lang:none">Attention Is All You Need</span>。这篇论文提出了 Transformer 架构，用自注意力（Self-Attention）替代 RNN 的递归路径，使模型更擅长并行训练，也更有效地建模长距离依赖。当前主流句子嵌入方法，通常都建立在 Transformer 之上：无论是编码单句得到表示的 Encoder-only 模型，还是用于检索的双编码器（Bi-Encoder）结构，如 SBERT、E5、BGE 和 text-embedding 系列，都属于这一路线的延伸。</p>
<div class="blog_h3"><span class="graybg">稠密向量（Dense Vector）</span></div>
<p>从表示形式看，无论词嵌入还是句子嵌入，本质上都属于稠密向量：用较低维的实值向量承载词、句子或文档的信息。向量的每一维不再直接对应某个具体词，而是由训练过程自动学习得到；因此，模型可以把共现模式、语义相似性以及部分上下文规律压缩进连续向量空间。</p>
<p>这也是后文嵌入模型（Embedding Model）、Word2Vec、Sentence-BERT 和 text-embedding 系列的共同基础。它们的差异在于：表示对象是词、句子还是文档，训练目标是预测上下文、对比学习还是任务特化微调；但核心思想一致，都是让语义结构在向量空间中变得可计算。</p>
<p>上述内容回答的是“文本如何被表示”。回到语言模型本身，还需要进一步区分模型究竟在学什么、输出什么、内部结构如何组织，以及它在工程系统中承担什么角色。</p>
<div class="blog_h2"><span class="graybg">语言模型分类</span></div>
<p>理解语言模型时，至少需要区分四个互相独立但彼此关联的维度：第一，模型在预训练时学的是什么；第二，模型最终主要输出什么；第三，模型内部的信息流结构如何组织；第四，模型在工程上是通用基座、指令对齐模型，还是任务特定模型。它们回答的是四个不同问题，因此一个模型完全可以同时拥有多重身份。例如，BERT 可以同时被描述为“掩码语言模型（Masked Language Model, MLM）+ 表示模型（Representation Model）+ Encoder-only 模型”；GPT / Qwen / LLaMA 则通常是“自回归语言模型（Autoregressive Language Model）+ 生成模型（Generative Model）+ Decoder-only 模型”。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">分类维度</td>
<td style="text-align: center;">它回答的问题</td>
<td style="text-align: center;">典型类别</td>
</tr>
</thead>
<tbody>
<tr>
<td>按预训练目标</td>
<td>模型在预训练阶段究竟被要求预测什么</td>
<td>掩码语言模型、自回归语言模型、替换检测、去噪重建</td>
</tr>
<tr>
<td>按输出与用途</td>
<td>模型最终主要产出向量、表示还是可直接生成的文本</td>
<td>嵌入模型、表示模型、生成模型</td>
</tr>
<tr>
<td>按架构信息流</td>
<td>模型内部如何读取上下文、如何组织编码与生成</td>
<td>Encoder-only、Decoder-only、Encoder–Decoder</td>
</tr>
<tr>
<td>按工程形态</td>
<td>模型在实际系统里扮演什么角色</td>
<td>通用预训练基座、指令对齐模型、任务特定模型</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">按预训练目标分类</span></div>
<p>按预训练目标分类，关注的是模型在大规模无标注文本上被要求完成什么自监督任务。这一维决定了模型最初学会的信息组织方式，但不直接等价于它最终能做什么任务。最经典的两类是掩码语言模型与自回归语言模型。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类别</td>
<td style="text-align: center;">核心训练目标</td>
<td style="text-align: center;">上下文可见性</td>
<td style="text-align: center;">典型模型</td>
<td style="text-align: center;">更常见优势</td>
</tr>
</thead>
<tbody>
<tr>
<td>掩码语言模型（MLM）</td>
<td>遮住部分 token，再根据其余上下文恢复被遮住内容</td>
<td>通常可双向看左右文</td>
<td>BERT、RoBERTa、DeBERTa</td>
<td>表示学习强；适合理解、分类、匹配、序列标注</td>
</tr>
<tr>
<td>自回归语言模型（CLM / ARLM）</td>
<td>根据前文预测下一个 token</td>
<td>因果约束，只看历史上下文</td>
<td>GPT、LLaMA、Qwen、Mistral</td>
<td>生成自然；统一接口强；适合对话、续写、代码生成</td>
</tr>
</tbody>
</table>
<p>两者的差异首先体现在条件概率分解方式上。自回归语言模型直接建模整段文本的联合概率：</p>
<span displaypfx="" class="mathjax-container">\[p(x_1,\dots,x_T)=\prod_{t=1}^{T}p(x_t\mid x_{&lt;t})\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个 token， <span displaypfx="inline-" class="mathjax-container">\(x_{&lt;t}\)</span> 表示它之前所有 token；模型在第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步输出的是“下一个 token 的词表分布”。掩码语言模型则不是按固定顺序展开整句，而是在输入中随机挑出若干位置 <span displaypfx="inline-" class="mathjax-container">\(M\)</span> 做遮蔽，训练目标可写成：</p>
<span displaypfx="" class="mathjax-container">\[\max \sum_{i\in M}\log p(x_i\mid x_{\setminus M})\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(M\)</span> 是被遮住的位置集合， <span displaypfx="inline-" class="mathjax-container">\(x_{\setminus M}\)</span> 表示其余未遮住 token。它学到的是“给定上下文，如何恢复缺失信息”，而不是“如何一步步把整段文本续写出来”。因此，MLM 天然更偏表示学习；ARLM 天然更偏生成建模。</p>
<p>这一维并不只有两类。ELECTRA 的替换检测（Replaced Token Detection, RTD）不直接恢复 mask，而是判断 token 是否被替换；T5、BART 的去噪重建（Denoising Reconstruction）则通过破坏输入再让模型恢复原文。因此，“掩码 vs 自回归”是最核心的一条主线，但不是全部可能性。</p>
<div class="blog_h3"><span class="graybg">按输出与用途分类</span></div>
<p>按输出与用途分类，关注的是模型最终主要产出什么，以及这些产出在工程系统里被如何使用。这里最容易混淆的是“嵌入模型”和“表示模型”。两者都能产出向量，但优化目标和默认使用方式并不相同。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类别</td>
<td style="text-align: center;">主要输出</td>
<td style="text-align: center;">优化重点</td>
<td style="text-align: center;">典型用途</td>
<td style="text-align: center;">典型代表</td>
</tr>
</thead>
<tbody>
<tr>
<td>嵌入模型（Embedding Model）</td>
<td>固定维度向量</td>
<td>让语义相近样本在向量空间里更近</td>
<td>检索、聚类、召回、语义匹配</td>
<td>Word2Vec、SBERT、BGE、E5、text-embedding 系列</td>
</tr>
<tr>
<td>表示模型（Representation Model）</td>
<td>上下文化隐藏表示</td>
<td>学到可迁移的中间表示，再交给任务头读出</td>
<td>分类、序列标注、匹配、判别式 NLU</td>
<td>BERT、RoBERTa、DeBERTa、ModernBERT</td>
</tr>
<tr>
<td>生成模型（Generative Model）</td>
<td>逐步生成的 token 分布与文本序列</td>
<td>最大化生成质量、上下文延续性与指令跟随能力</td>
<td>对话、写作、摘要、翻译、代码生成、结构化输出</td>
<td>GPT、Qwen、LLaMA、T5、BART</td>
</tr>
</tbody>
</table>
<p>嵌入模型的关键特征是：它的向量空间本身就是最终产品。用户真正拿来用的是向量之间的距离、余弦相似度或最近邻结构。表示模型则更像通用特征提取器：它输出的隐藏状态通常还要再接一个任务头（Task Head）或额外池化层，才能变成分类分数、序列标签或其他任务结果。生成模型的最终输出则是一个条件词表分布，经过解码后形成文本或结构化序列。</p>
<p>同一底座模型有时可以被改造成不同用途。例如，BERT 原本是表示模型，但经过对比学习和专门池化后可以变成句向量嵌入模型；Decoder-only 大模型原本是生成模型，但也可以通过取隐藏状态做 embedding。不过从默认训练目标与最强项看，这三类仍然应当区分。</p>
<div class="blog_h3"><span class="graybg">按架构与信息流分类</span></div>
<p>按架构分类，关注的是模型内部如何读取上下文，以及输入和输出是如何在网络中流动的。这一维对应的是结构设计，而不是预训练目标本身。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">架构类别</td>
<td style="text-align: center;">信息流特征</td>
<td style="text-align: center;">注意力方式</td>
<td style="text-align: center;">典型任务</td>
<td style="text-align: center;">典型代表</td>
</tr>
</thead>
<tbody>
<tr>
<td>Encoder-only</td>
<td>把输入编码成上下文化表示，不直接负责逐步生成</td>
<td>通常是双向自注意力</td>
<td>分类、检索、匹配、序列标注</td>
<td>BERT、RoBERTa、DeBERTa、ELECTRA</td>
</tr>
<tr>
<td>Decoder-only</td>
<td>按时间步自回归地产生输出</td>
<td>因果自注意力</td>
<td>对话、续写、代码生成、开放式问答</td>
<td>GPT、LLaMA、Qwen、Mistral、DeepSeek</td>
</tr>
<tr>
<td>Encoder–Decoder</td>
<td>先编码输入，再由解码器条件生成输出</td>
<td>编码器双向自注意力 + 解码器因果自注意力 + 交叉注意力</td>
<td>翻译、摘要、改写、条件生成</td>
<td>T5、BART</td>
</tr>
</tbody>
</table>
<p>这一维与前两维经常联动，但不是一一对应。Encoder-only 模型常与 MLM 或 RTD 结合，Decoder-only 模型常与自回归目标结合，Encoder–Decoder 模型则常与去噪或条件生成目标结合；但它们分别回答的是“结构长什么样”和“训练时学什么”的两个不同问题。</p>
<div class="blog_h3"><span class="graybg">按工程形态分类</span></div>
<p>在工程落地中，还需要区分模型处于哪种产品化形态。通用预训练基座（Base Model）强调语言知识与可迁移能力；指令对齐模型（Instruction-tuned Model）强调遵循人类指令、对话风格与格式约束；任务特定模型（Task-specific Model）则围绕某个明确监督目标继续微调，并常配合专门任务头工作。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">工程形态</td>
<td style="text-align: center;">核心特点</td>
<td style="text-align: center;">适合场景</td>
<td style="text-align: center;">典型例子</td>
</tr>
</thead>
<tbody>
<tr>
<td>通用预训练基座</td>
<td>保留通用语言知识，强调可迁移性与再训练空间</td>
<td>继续预训练、SFT、LoRA、蒸馏</td>
<td>BERT base、LLaMA base、Qwen base</td>
</tr>
<tr>
<td>指令对齐模型</td>
<td>通过指令微调与偏好优化提升对话和任务遵循能力</td>
<td>问答助手、Agent、工具调用、结构化生成</td>
<td>ChatGPT 类、Qwen-Instruct、LLaMA-Instruct</td>
</tr>
<tr>
<td>任务特定模型</td>
<td>围绕明确任务输出继续微调，并常接专门任务头</td>
<td>NER、分类、匹配、排序、信息抽取</td>
<td>DeBERTa-CRF、BERT 分类器、LLM + PEFT</td>
</tr>
</tbody>
</table>
<p>把四个维度合在一起看，模型的定位会变得清晰得多：</p>
<ul>
<li>BERT：更接近 MLM + 表示模型 + Encoder-only + 常作为任务特定模型底座。</li>
<li>SBERT 或 BGE：更接近 表示 / 对比学习 + 嵌入模型 + 常为双编码检索结构。</li>
<li>GPT / Qwen / LLaMA：更接近 自回归 + 生成模型 + Decoder-only + 常见指令对齐形态。</li>
<li>T5 / BART：更接近 去噪或 text-to-text 目标 + 生成模型 + Encoder–Decoder。</li>
</ul>
<div class="blog_h2"><span class="graybg">主流表示型语言模型</span></div>
<p>到 2026 年，表示型语言模型（Representation Model）的工程格局已经明显分成两条主线。第一条是以 Encoder-only 为核心的判别式表示模型，主要服务于分类、自然语言推断（Natural Language Inference, NLI）、命名实体识别（Named Entity Recognition, NER）、抽取式问答等理解任务；第二条是专门为句向量、检索、聚类和召回设计的嵌入模型。前者仍沿着 BERT 家族演化，后者则越来越多地直接采用 BGE、E5、Qwen3-Embedding、jina-embeddings 这类专门路线。因此，讨论“更好的表示模型”时，必须先区分目标到底是任务头微调，还是直接产出高质量向量表示。</p>
<div class="blog_h3"><span class="graybg">BERT</span></div>
<p>BERT（Bidirectional Encoder Representations from Transformers）是典型的 Encoder-only 模型：用双向注意力做表示学习，预训练目标以掩码语言建模（Masked Language Modeling, MLM）为主。原始 BERT 还包含下一句预测（Next Sentence Prediction, NSP）任务：输入通常写成句段 A [SEP] 句段 B，模型需要根据最终的 <pre class="crayon-plain-tag">[CLS]</pre> 表示判断 B 是否真的是 A 在原语料中的下一句，而不是随机抽来的另一句。与 Word2Vec 常见的负采样训练不同，BERT 的核心预训练是在被遮住的位置上直接做词表预测；而 NSP 则为句级判别额外提供了一条监督路径。输入序列开头通常会加入一个特殊的 <pre class="crayon-plain-tag">[CLS]</pre> token，用来聚合整段输入的信息；经过编码后，这个位置的输出隐藏状态常被当作整个序列的语义摘要，并接到分类头上用于文本分类、自然语言推断（NLI）等下游任务。</p>
<p>但这里必须把两件事分开。<pre class="crayon-plain-tag">[CLS]</pre> 很适合做<span style="background-color: #c0c0c0;">任务读出位置（readout position）</span>，却不天然等于“最好的通用句向量”。它之所以能被拿来接分类头，首先是因为它在结构上位于序列最前面，经过每一层双向自注意力后，都可以从整句其它 token 汇聚信息；其次是因为原始 BERT 的预训练里确实给过它专门监督：在下一句预测（Next Sentence Prediction, NSP）任务中，最终的分类就是直接读 <pre class="crayon-plain-tag">[CLS]</pre> 的输出隐藏状态，再接一个二分类头。也就是说，<pre class="crayon-plain-tag">[CLS]</pre> 从一开始就被当成“适合给任务头读取”的位置来训练。正因为如此，原始 BERT 的 <pre class="crayon-plain-tag">[CLS]</pre> 既是首位置隐藏状态，也是被句级判别任务直接塑形过的读出接口。</p>
<p>不过，<pre class="crayon-plain-tag">[CLS]</pre> 的训练目标并不是“把整句压缩成一个适合做余弦相似度的几何向量”。在 MLM（Masked Language Modeling）中，直接受监督的是被 mask 的 token 位置，而不是整句的句向量质量；<pre class="crayon-plain-tag">[CLS]</pre> 只会通过多层自注意力间接参与这些预测。在 NSP 里，它学到的是“这一对句子是否连续”这种特定判别目标，而不是“语义相近的句子在向量空间中应彼此靠近”。因此，<pre class="crayon-plain-tag">[CLS]</pre> 更像是为分类器准备的汇总接口，而不是为检索、聚类或最近邻搜索专门对齐过的句表示。</p>
<p>这也是它不能自然代替显式池化（Explicit Pooling）的根本原因。显式池化会把所有 token 的隐藏状态用平均、最大值或加权汇聚的方式整合成句向量，例如平均池化可写成</p>
<span displaypfx="" class="mathjax-container">\[e(x)=\frac{1}{n}\sum_{i=1}^{n} h_i\]</span>
<p>这里每个 token 的最终表示都会直接进入句向量构造过程；而 <pre class="crayon-plain-tag">[CLS]</pre> 路线则把整句压缩任务隐含地交给某一个特殊位置去完成，相当于要求模型把所有句级信息都写入单个状态向量中。对分类任务，这种单点读出通常足够，因为后面还有任务头继续适配；但对通用句嵌入，这种“单位置承担全部汇总”的方式往往不如显式池化稳定，也更容易受到预训练目标偏置的影响。</p>
<p>从几何上看，这个差异会进一步表现为表示各向异性（Anisotropy）：原始 BERT 的 <pre class="crayon-plain-tag">[CLS]</pre> 向量常常集中在高维空间的少数主方向上，不同句子的向量分布会显得过于拥挤，余弦相似度缺乏足够区分度。显式池化本身并不能自动解决所有问题，但它至少把“句向量由哪些 token 共同构成”这件事写成了可控、透明的操作；一旦再叠加 Sentence-BERT 这类句对监督或对比学习目标，模型就会直接围绕池化后的句向量去优化距离结构，而不是依赖 <pre class="crayon-plain-tag">[CLS]</pre> 在预训练阶段顺带形成的间接汇总能力。</p>
<p>BERT 以及其他表示型语言模型通常先在海量通用语料上做预训练，从而学到词法模式、句法结构、语义关系以及一定程度的世界知识。正因为这些知识不是为某一个具体任务单独学习出来的，它们非常适合作为通用特征提取器（General-purpose Feature Extractor）：在迁移学习框架下，只需接上分类头、序列标注头或匹配头，并在目标任务数据上继续微调，就可以把通用表示快速适配到具体自然语言处理任务。</p>
<p>BERT系列“是否支持中文”关键在词表与分词器（Tokenizer）。英文 BERT-base 的 WordPiece 词表主要覆盖英文子词；对中文文本可能会大量落到 <span displaypfx="inline-" class="mathjax-container">\([\mathrm{UNK}]\)</span> 或被切成极碎片段，效果通常不理想。要做中文任务更常用中文 BERT、mBERT（multilingual BERT）或以 SentencePiece 为主的多语模型。</p>
<div class="blog_h3"><span class="graybg">RoBERTa</span></div>
<p>RoBERTa（Robustly Optimized BERT Approach）延续 BERT 的 Encoder-only 架构，但通过更大规模数据与训练配方改进（例如更长训练、更大 batch、动态 masking、移除或弱化 NSP 等）显著提升表示质量。它的工程意义在于：<span style="background-color: #c0c0c0;">在不改变基本架构的前提下，训练细节足以带来可观收益</span>。</p>
<p>RoBERTa 证明了一个重要事实：Encoder-only 模型的性能上限，不完全取决于“是否换了新架构”，训练数据规模、batch 策略、masking 方式和目标设计本身就足以显著改变表示质量。</p>
<div class="blog_h3"><span class="graybg">chinese-roberta-wwm-ext</span></div>
<p><pre class="crayon-plain-tag">chinese-roberta-wwm-ext</pre> 是哈工大-讯飞联合实验室（HFL）推出的中文 RoBERTa 路线基座之一，定位上属于典型的 BERT-base 量级 Encoder-only 中文判别式底座。它延续了中文 BERT 全词掩码（Whole Word Masking, WWM）路线，并采用 RoBERTa 风格的训练配方改进，例如去掉 NSP、延长训练与强化 MLM 训练过程。它与后续的 Chinese MacBERT、Chinese ELECTRA 等中文预训练模型位于同一条中文 Encoder 演化谱系中。</p>
<p>从工程历史看，它是 2020 年前后到 2022 年中文 NLU 的事实标准之一：社区验证充分，微调范式成熟，适配文本分类、句对匹配、自然语言推断（Natural Language Inference, NLI）与命名实体识别（Named Entity Recognition, NER）都非常稳定。到 2026 年，这个模型的优势仍然清晰存在：中文语料预训练充分、生态成熟、部署经验丰富、对中小规模监督数据通常相当稳健。它特别适合作为<span style="background-color: #c0c0c0;">中文闭集理解任务的经典强基线</span>，用于给新模型或新训练策略提供可复现、低风险的比较参考。</p>
<p>它的局限也同样明确。架构主体仍然停留在 BERT-base 时代：上下文长度通常围绕 512 token，长文档处理能力有限；注意力与推理路径没有吸收长上下文时代的高效实现；分词与词表路线也仍属于较早一代中文 Encoder 设计。因此，若把它放到 2025-2026 年的主流表示模型谱系里，更准确的定位是<span style="background-color: #c0c0c0;">成熟、稳定、中文友好的经典底座</span>，而不是长上下文或现代高吞吐判别式编码器的前沿代表。</p>
<p>替代方案的选择应按目标分层。若目标仍是中文单语分类、NER、句对匹配，且优先级是稳定微调与成熟生态，那么继续使用 <pre class="crayon-plain-tag">chinese-roberta-wwm-ext</pre> 完全合理；若希望在保持中文专训路线的同时提高整体效果，HFL 自己后续的 MacBERT 往往是更自然的同谱系升级方向。若目标转向更强的跨语言迁移、更新的判别式结构或更大的英文生态复用，则 DeBERTa-V3 / mDeBERTa-V3 一类底座通常更接近 2026 年的主流高质量选择。若任务明显受制于长输入、长文档分类或现代推理吞吐，则 ModernBERT 这类原生 8K 上下文的现代编码器会更有吸引力；但这类模型主要按英文与代码语料设计，直接替换到中文任务上并不自动保证优于中文专训底座，最终仍需以具体任务与中文语料上的微调结果为准。</p>
<div class="blog_h4"><span class="graybg">更好的替代方案（按场景分层）</span></div>
<p>围绕 <pre class="crayon-plain-tag">chinese-roberta-wwm-ext</pre> 做替换时，更有效的比较方式不是笼统追求“更新的模型”，而是先判断替换发生在同一参数量级，还是直接切换到更大规模 encoder。两类替换的收益来源、工程代价和失败模式并不相同。</p>
<div class="blog_h4"><span class="graybg">同级别直接替换</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">优势</td>
<td style="text-align: center;">劣势</td>
</tr>
</thead>
<tbody>
<tr>
<td>Chinese ModernBERT-base</td>
<td>22 层、词级 BPE 词表、RoPE、8192 上下文、现代 bf16 推理路径</td>
<td>在某些 MIL 架构中文任务里，直接替换 base encoder 的收益可能并不明显；瓶颈未必在 encoder 本身</td>
</tr>
<tr>
<td>MacBERT（hfl/chinese-macbert-base）</td>
<td>用近义词替代 [MASK]，缓解预训练与微调之间的目标落差；是中文 BERT / RoBERTa 谱系里最自然的升级点之一</td>
<td>上下文长度通常仍限于 512 token；长文档场景帮助有限</td>
</tr>
<tr>
<td>PERT（hfl/chinese-pert-base）</td>
<td>基于排列式预训练，对语序敏感任务有潜在优势</td>
<td>社区采用度、教程与工程生态都弱于 RoBERTa / MacBERT</td>
</tr>
<tr>
<td>Chinese ELECTRA（hfl/chinese-electra-180g-base）</td>
<td>判别式预训练效率高，在分类与 NER 任务上常有竞争力</td>
<td>长文本仍受 512 左右上下文限制；与中文 RoBERTa 相比迁移收益取决于具体任务</td>
</tr>
</tbody>
</table>
<p>这一层的替换更像是在相近预算内调整预训练目标、词表与工程实现。收益通常来自更合适的中文训练配方或更现代的编码实现，而不是简单的“参数更多”。若现有系统已经有成熟的特征聚合层、样本构造策略或 MIL 结构，encoder 升级未必自动转化成同幅度任务收益。</p>
<div class="blog_h4"><span class="graybg">更大规模 Encoder</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">规模</td>
<td style="text-align: center;">更适合的场景</td>
</tr>
</thead>
<tbody>
<tr>
<td>hfl/chinese-roberta-wwm-ext-large</td>
<td>约 325M，24 层</td>
<td>推理资源允许、且任务确实受语义表达上限约束时，直接切到 large 版往往比横向换同级 base 更容易获得稳定增益</td>
</tr>
<tr>
<td>Chinese ModernBERT-large</td>
<td>约 395M，更深且原生长上下文</td>
<td>长上下文分类、长文档理解、需要更强语义建模且能接受显著更高推理成本的场景</td>
</tr>
</tbody>
</table>
<p>若任务真正受限于表达容量而不是训练配方，扩大 encoder 往往比在多个 base 模型之间横向切换更容易带来可见提升。代价也非常直接：显存、延迟、吞吐和微调稳定性压力都会同步上升。因此 large 路线适合作为“资源换上限”的升级，而不是默认替代。</p>
<div class="blog_h3"><span class="graybg">ELECTRA</span></div>
<p>ELECTRA（Efficiently Learning an Encoder that Classifies Token Replacements Accurately）仍属于 Encoder-only 模型，但它不再让主模型只做“把被遮住的词猜出来”的掩码语言建模（Masked Language Modeling, MLM）。它先用一个较小的生成器替换部分 token，再让主模型判断每个位置上的 token 是原词还是替换词，这就是替换检测（Replaced Token Detection, RTD）。这种训练方式让模型几乎在每个 token 上都获得监督信号，因此在相近预训练预算下通常比纯 MLM 更高效。</p>
<div class="blog_h3"><span class="graybg">DeBERTa / DeBERTa-V3</span></div>
<p>DeBERTa（Decoding-enhanced BERT with Disentangled Attention）可以看作对 BERT / RoBERTa 的一次结构级强化。它仍然是典型的判别式 Encoder-only 语言模型，因此特别适合文本分类、自然语言推断（Natural Language Inference, NLI）、命名实体识别（Named Entity Recognition, NER）和情感分析等理解类任务；但它不满足于只靠“更大数据、更长训练”来变强，而是直接对注意力机制本身做了精细改造。</p>
<p>它最核心的突破是解耦注意力（Disentangled Attention）。在传统 BERT 中，词的内容信息与位置信息通常会较早融合；DeBERTa 则把“这个 token 是什么”和“这个 token 在哪里”分开处理。对位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 的注意力打分，可直观写成三部分：</p>
<span displaypfx="" class="mathjax-container">\[A_{i,j}=\underbrace{C_iC_j^\top}_{\text{内容-内容}}+\underbrace{C_iP_{j|i}^\top}_{\text{内容-相对位置}}+\underbrace{P_{i|j}C_j^\top}_{\text{相对位置-内容}}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(C_i\)</span> 表示第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个位置的内容表示（Content Representation），<span displaypfx="inline-" class="mathjax-container">\(P_{j|i}\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(P_{i|j}\)</span> 表示相对位置表示（Relative Position Representation）。这个拆分的意义在于：模型不必把“词义”和“位置”混成一个整体再去匹配，而可以更精细地学习“哪个内容会关注什么位置关系”。对于短语修饰、依存关系和实体边界判定这类任务，这种改造往往更有利。</p>
<p>DeBERTa 的第二个关键设计是增强型掩码解码器（Enhanced Mask Decoder, EMD）。它的动机是：相对位置固然重要，但在掩码预测时，绝对位置有时也能提供额外信息。因此 DeBERTa 在接近输出端的位置再次显式注入绝对位置信息，让模型在做 MLM 预测时同时利用相对与绝对位置信号。这相当于在保持主体编码过程解耦的前提下，在最后的“读答案”阶段补上一层位置提醒。</p>
<p>从工程结果看，DeBERTa 长期是判别式 NLU 的强基线之一。它的重要性不只在于“分数更高”，还在于它说明了一个事实：<span style="background-color: #c0c0c0;">Encoder-only 模型的上限不只是由数据和算力决定，内容表示、位置表示和解码方式本身的结构设计也会显著影响表示质量</span>。后续的 DeBERTa-V3 又把替换检测（RTD）这类更高效的预训练目标引入进来，进一步改善了训练效率与效果，使其在很多精细理解任务里依然保持很强竞争力。</p>
<div class="blog_h3"><span class="graybg">ModernBERT</span></div>
<p>ModernBERT 代表的是 BERT 风格双向编码器在 2025 年之后的一次现代化重写。它仍然属于 Encoder-only，但不再停留在 2018 年 BERT 的上下文长度、注意力实现和推理路径上，而是把长上下文支持与现代高效实现直接纳入底座设计。其官方模型卡给出的关键信息包括：预训练规模达到 2T tokens，原生上下文长度扩展到 8192 token，并引入 RoPE、本地-全局交替注意力（Local-Global Alternating Attention）、unpadding 和 Flash Attention 等现代工程机制。</p>
<p>与原始 BERT 还有一个重要区别在于 <pre class="crayon-plain-tag">[CLS]</pre> 的默认训练来源。ModernBERT 官方定位是纯粹的掩码语言模型（Masked Language Model, MLM）：预训练时默认存在的是 MLM head，而不是像原始 BERT 那样再额外叠加 NSP 这种句级二分类目标。这意味着 ModernBERT 的 <pre class="crayon-plain-tag">[CLS]</pre> 主要是作为首位置隐藏状态在 MLM 路线下间接形成的可读出表示，而不是被预训练阶段的句级分类任务直接塑形过的“现成分类接口”。因此，ModernBERT 当然仍然适合做分类，但更准确的理解应是：它提供了一个现代化、长上下文、可高效微调的编码底座；真正的分类头通常仍要在下游任务里显式接上并训练出来。</p>
<p>这类改造的意义非常直接。传统 BERT 家族最舒服的输入长度通常仍停留在几百 token 到一两千 token 量级，而 ModernBERT 这一路则把长文档分类、长文本检索、代码检索和大段上下文理解一并纳入可用范围。于是，在英文或以英文 / 代码为主的判别式任务里，ModernBERT 往往比 DeBERTa-V3 更接近 2026 年的默认新基线；DeBERTa-V3 仍然是高质量经典强基线，但 ModernBERT 明显更符合当前对长上下文与吞吐效率的要求。</p>
<p>它的交替局部-全局注意力也进一步改变了人们对 <pre class="crayon-plain-tag">[CLS]</pre> 的直觉。经典 BERT 可以把 <pre class="crayon-plain-tag">[CLS]</pre> 看成每层都在做全局汇总的位置；ModernBERT 则只在周期性的全局层里进行真正的全局信息混合，局部层更强调效率与长序列处理能力。因此，把 ModernBERT 的 <pre class="crayon-plain-tag">[CLS]</pre> 直接理解成“天然全局句向量”会更加不准确；它更像一个可被下游读取的首位置表示，而不是默认就已经为通用句嵌入优化好的几何向量。</p>
<div class="blog_h3"><span class="graybg">多语表示模型</span></div>
<p>多语表示模型的选型逻辑与英文单语场景并不完全相同。XLM-R（XLM-RoBERTa）仍然是经典多语 Encoder-only 基线之一：其预训练覆盖 100 种语言，核心价值是把多语文本映射到共享表示空间，再用于序列分类、序列标注和问答等下游任务。mDeBERTa-V3 则把 DeBERTa-V3 的结构与训练目标迁移到多语场景，在跨语言自然语言推断等零样本迁移任务上，相比 XLM-R-base 给出更强的官方基线结果。因此，若目标是稳定、成熟、适合任务头微调的多语编码器，XLM-R 与 mDeBERTa-V3 依然是 2026 年非常现实的主流选择。</p>
<p>更前沿的一支则沿着 ModernBERT 的方向继续推进。2025 年发布的 mmBERT 直接把 ModernBERT 的现代编码器路线扩展到大规模多语场景，官方介绍强调其训练覆盖 3T 以上 token 和 1800 多种语言，并把它定位为首个在性能和速度上同时明显超过 XLM-R 的新一代多语编码器。这说明多语表示学习也正在从“经典 XLM-R 世代”向“ModernBERT 世代”迁移，只是就生态成熟度与工程复用性而言，XLM-R / mDeBERTa-V3 仍然更稳，mmBERT 更像下一阶段的高端新底座。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">架构</td>
<td style="text-align: center;">预训练目标</td>
<td style="text-align: center;">特点</td>
<td style="text-align: center;">常见用途</td>
</tr>
</thead>
<tbody>
<tr>
<td>BERT</td>
<td>Encoder-only</td>
<td>MLM（+ NSP 变体）</td>
<td>经典通用基线；生态成熟</td>
<td>分类、匹配、序列标注</td>
</tr>
<tr>
<td>RoBERTa</td>
<td>Encoder-only</td>
<td>MLM</td>
<td>更强训练配方；经典英文 NLU 强基线</td>
<td>理解类任务强基线</td>
</tr>
<tr>
<td>chinese-roberta-wwm-ext</td>
<td>中文 Encoder-only</td>
<td>MLM（WWM；RoBERTa 风格训练）</td>
<td>经典中文 NLU 强基线；生态成熟；微调稳定</td>
<td>中文分类、句对任务、NER、NLI</td>
</tr>
<tr>
<td>ELECTRA</td>
<td>Encoder-only</td>
<td>替换检测（Replaced Token Detection）</td>
<td>训练效率高；对小算力友好</td>
<td>理解类任务、低成本预训练</td>
</tr>
<tr>
<td>DeBERTa-V3</td>
<td>Encoder-only</td>
<td>MLM / RTD</td>
<td>解耦注意力 + 更高效预训练；经典高精度 NLU 底座</td>
<td>高精度 NLU、NER、文本分类</td>
</tr>
<tr>
<td>ModernBERT</td>
<td>Encoder-only</td>
<td>MLM</td>
<td>2T tokens 预训练、8192 上下文、现代高效实现</td>
<td>长文档分类、长文本理解、代码检索、现代英文编码任务</td>
</tr>
<tr>
<td>XLM-R</td>
<td>多语 Encoder-only</td>
<td>MLM</td>
<td>100 语言经典共享表示空间；多语生态成熟</td>
<td>多语分类、NER、问答、跨语言迁移</td>
</tr>
<tr>
<td>mDeBERTa-V3</td>
<td>多语 Encoder-only</td>
<td>MLM / RTD</td>
<td>DeBERTa-V3 的多语延伸；零样本迁移更强</td>
<td>多语 NLU、多语分类、跨语言推断</td>
</tr>
<tr>
<td>Qwen3-Embedding / BGE-M3 / multilingual-e5 / jina-embeddings-v3</td>
<td>专用表示模型</td>
<td>对比学习 / 指令化检索 / 检索特化目标</td>
<td>不以任务头分类为首要目标，而是直接优化向量空间质量</td>
<td>语义检索、聚类、召回、RAG、跨语言匹配</td>
</tr>
</tbody>
</table>
<p>因此，到 2026 年若任务是分类、序列标注、NLI 或抽取式问答，主流候选通常已经变成 DeBERTa-V3、ModernBERT，以及多语场景下的 XLM-R / mDeBERTa-V3。若任务明确是中文单语理解，<pre class="crayon-plain-tag">chinese-roberta-wwm-ext</pre> 仍然是值得保留的经典强基线，只是它在架构代际上已经偏老，更适合承担“稳健基准”而不是“前沿默认底座”的角色。若任务本质上是检索、聚类、向量召回或 RAG，则应直接转向后文的主流嵌入模型，而不是继续把通用 Encoder-only 分类底座当作最优句向量来源。</p>
<div class="blog_h2"><span class="graybg">主流生成式语言模型</span></div>
<div class="blog_h3"><span class="graybg">GPT 系列</span></div>
<p>GPT 系列是典型 Decoder-only：以 CLM（自回归 next-token）为主目标，把各种任务统一为“续写”。工程上其优势来自统一接口与强 in-context learning；代价是推理成本高（尤其长上下文与高并发场景）。</p>
<div class="blog_h3"><span class="graybg">LLaMA</span></div>
<p>LLaMA 系列是开源 Decoder-only 基座的重要代表，强调稳定的训练配方与生态可用性（tokenizer、推理支持、微调社区）。它常作为“可控、可复现”的研究/工程基线，用于 SFT、LoRA、RAG 与本地部署。</p>
<div class="blog_h3"><span class="graybg">Qwen</span></div>
<p>Qwen 系列同属开源 Decoder-only 生态，中文与多语言能力通常更受关注；在工具调用、代码与多模态等方向也有较多衍生版本。工程上，它常被用作中文业务与多语言场景的基座候选。</p>
<div class="blog_h3"><span class="graybg">Mistral</span></div>
<p>Mistral 系列强调“更高性价比的推理与训练”：通过架构与工程优化在相近成本下获得更强的生成质量与吞吐表现，常见于高并发推理与轻量级部署场景。</p>
<div class="blog_h3"><span class="graybg">DeepSeek</span></div>
<p>DeepSeek 系列更强调训练与推理效率的极致：在注意力 KV 压缩、稀疏结构（MoE）与训练目标（如 Multi-Token Prediction）等方向探索更激进的工程取舍，目标是在同等算力预算下提升有效容量与上下文能力。</p>
<p>若从 block 级结构看，DeepSeek V4 的主干并不是“普通 Transformer 换一个注意力模块”这么简单，而是把三条路线绑在同一层里协同工作：<span style="background-color: #c0c0c0;">CSA / HCA 负责注意力，mHC 负责多路残差混合，DeepSeekMoE 负责前馈容量扩展</span>。一层的阅读顺序可以概括为：输入 token 先进入 embedding；随后经过 mHC 的 Pre-Block Mixing，把多路残差流混合后送入当前 block；注意力子层再按层使用 CSA 或 HCA；其输出经过 Residual Mixing 写回残差主路；接着通过 Post-Block Mixing 送入 DeepSeekMoE；MoE 输出再写回主干，进入下一层。换句话说，V4 的每个 Transformer block 内部实际上是“<span style="background-color: #c0c0c0;">混合注意力 + 流形约束残差 + 稀疏 MoE</span>”的三件套，而不是单一算子主导全层行为。</p>
<p>这也解释了 CSA 与 HCA “如何交替堆叠”这个问题。它们并不是游离在模型外部的长上下文外挂，而是 block 内部的注意力实现位。某一层装配的是 CSA，这一层就更偏向“压缩后召回重点块”；某一层装配的是 HCA，这一层就更偏向“极粗粒度地扫全局背景”。而同一层前后的 mHC mixing 与 DeepSeekMoE 保持不变，于是模型能够在<span style="background-color: #c0c0c0;">同一主干框架</span>里，让不同层承担不同的阅读粒度，同时继续依靠统一的残差主路和稀疏专家容量维持深层表达。</p>
<p>在训练端，DeepSeek V4 也不是只在最后接一个普通 LM Head。其顶层除标准语言模型损失外，还叠加了多 token 预测（Multi-Token Prediction, MTP）这一辅助监督信号。于是从整体上看，V4 的设计是四层联动的：注意力层解决百万上下文，mHC 解决深层稳定性，MoE 解决有效容量，MTP 解决训练信号密度。也正因为这几部分是一起设计的，DeepSeek V4 不能被简单归类成“只是 MLA 的延伸”或“只是更大的 MoE 模型”。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">家族</td>
<td style="text-align: center;">定位</td>
<td style="text-align: center;">常见优势</td>
<td style="text-align: center;">常见代价</td>
</tr>
</thead>
<tbody>
<tr>
<td>LLaMA</td>
<td>稳健开源基座</td>
<td>生态成熟；微调与推理支持广</td>
<td>配置与版本较多，需选对上下文/推理配方</td>
</tr>
<tr>
<td>Qwen</td>
<td>多语言/中文友好基座</td>
<td>中文场景覆盖好；衍生模型多</td>
<td>需关注 tokenizer/指令对齐数据分布</td>
</tr>
<tr>
<td>Mistral</td>
<td>高性价比推理</td>
<td>吞吐与质量兼顾；工程落地友好</td>
<td>不同版本/配方差异会影响最佳实践</td>
</tr>
<tr>
<td>DeepSeek</td>
<td>效率优先（MoE/压缩）</td>
<td>在算力/显存约束下追求更强能力</td>
<td>架构复杂度更高；推理与部署依赖实现细节</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">主流嵌入模型</span></div>
<div class="blog_h3"><span class="graybg">Word2Vec（CBOW / Skip-gram）</span></div>
<p>Word2Vec 用一个简单的自监督目标（Self-supervised Objective）学习词向量（Word Embeddings）：监督信号来自文本的共现上下文（Context），而不是人工标签。</p>
<p>它可以理解为学习两个嵌入表：输入词向量 <span displaypfx="inline-" class="mathjax-container">\(V\in\mathbb{R}^{|{\cal V}|\times d}\)</span> 与输出词向量 <span displaypfx="inline-" class="mathjax-container">\(U\in\mathbb{R}^{|{\cal V}|\times d}\)</span>（<span displaypfx="inline-" class="mathjax-container">\(|{\cal V}|\)</span> 为词表大小）。训练结束后，常用 <span displaypfx="inline-" class="mathjax-container">\(V\)</span>（或 <span displaypfx="inline-" class="mathjax-container">\((V+U)/2\)</span>）作为词向量。</p>
<p>Word2Vec 有两种经典训练方式：CBOW（Continuous Bag of Words）根据上下文预测中心词，Skip-gram 则根据中心词预测上下文。二者的预测方向相反，但都利用局部共现关系学习词向量。下面以 Skip-gram 为例说明它最核心的训练机制。</p>
<p>Word2Vec 的样本不是人工标注出来的，而是通过<span style="background-color: #c0c0c0;">滑动窗口（Sliding Window）</span>在语料上自动生成。设窗口半径为 <span displaypfx="inline-" class="mathjax-container">\(m\)</span>，当滑动窗口扫到位置 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 时，中心词 <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span> 会与它左右 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 个位置内的上下文词组成正样本对；越靠近句首句尾，可用上下文自然会变少。对单个中心词，Skip-gram 的局部目标可写成：</p>
<span displaypfx="" class="mathjax-container">\[\sum_{-m\le j\le m,\ j\ne 0}\log p(x_{t+j}\mid x_t)\]</span>
<p>这意味着：窗口每向前滑动一步，模型就把“中心词与邻近词共同出现”当作新的监督信号。CBOW 则反过来，把窗口内多个上下文词聚合起来预测中心词；但两者的训练样本都来自同一套滑动窗口机制。</p>
<p>在 Skip-gram 中，给定中心词（center word）<span displaypfx="inline-" class="mathjax-container">\(w_c\)</span>，预测窗口内的上下文词（context word）<span displaypfx="inline-" class="mathjax-container">\(w_o\)</span>。若用 full softmax：</p>
<span displaypfx="" class="mathjax-container">\[p(w_o|w_c)=\frac{\exp\left(u_{w_o}^\top v_{w_c}\right)}{\sum_{w\in{\cal V}}\exp\left(u_{w}^\top v_{w_c}\right)}\]</span>
<p>如果保留这个完整 softmax，模型会被迫在整个词表上做归一化比较；真实上下文概率升高时，其他词的概率就必须相应下降。但如果改成更便宜的“只学习哪些词对是真的”而又只提供正样本，模型就会立刻出现投机空间：它完全可以把几乎所有词对都打成高分，因为目标里从来没有人告诉它哪些配对是假的。换句话说，<span style="background-color: #c0c0c0;">只基于正样本训练一个二分类式共现目标，最容易学到的不是语义结构，而是“永远预测真”</span>。</p>
<p>因此，实践里常用负采样（Negative Sampling）：对每个正样本对 <span displaypfx="inline-" class="mathjax-container">\((w_c,w_o)\)</span>，再随机采样 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个噪声词 <span displaypfx="inline-" class="mathjax-container">\(w_k\)</span>，把 <span displaypfx="inline-" class="mathjax-container">\((w_c,w_k)\)</span> 当作负样本，并最大化</p>
<span displaypfx="" class="mathjax-container">\[\log\sigma(u_{w_o}^\top v_{w_c})+\sum_{k=1}^{K}\log\sigma(-u_{w_k}^\top v_{w_c})\]</span>
<p>第一项要求真实共现词对的内积更大，第二项要求随机噪声词对的内积更小。这样模型学到的就不再是“所有词都彼此相似”，而是“哪些词在局部上下文里更可能共同出现”。这里的“模型参数”主要就是这些词向量；模型输入通常是 token id（或 one-hot）经查表得到的 <span displaypfx="inline-" class="mathjax-container">\(v_{w_c}\)</span>。</p>
<p>Word2Vec 的负样本通常也不需要过度精心设计。经典做法就是按词频分布的 <span displaypfx="inline-" class="mathjax-container">\(0.75\)</span> 次方做随机采样，让高频词仍然更常被抽到，但不会垄断全部负样本。它的核心目标不是构造“最难反例”，而是持续给模型提供足够多的噪声对照，让真实共现和随机拼接之间产生可学习的区分。后来的对比学习常会显式挖掘 hard negatives，但在经典 Word2Vec 里，简单而稳定的随机负采样通常已经足够有效。</p>
<div class="blog_h3"><span class="graybg">GloVe</span></div>
<p>GloVe（Global Vectors for Word Representation）用全局共现统计学习词向量：目标是让词向量的内积拟合共现概率（或其对数）。与 Word2Vec 相比，它更强调全局统计的一致性；但二者都属于“静态词向量”（Static Embedding），无法像 Transformer 那样根据上下文动态改变词义表示。</p>
<div class="blog_h3"><span class="graybg">Sentence-BERT</span></div>
<p>Sentence-BERT（SBERT）是文本嵌入（Text Embedding）中的经典双编码器（Bi-Encoder / Dual Encoder）范式。它与后面的 text-embedding 系列并不是两种不同任务；二者都把文本映射为向量，用于相似度计算、检索、聚类与召回。区别主要在于：SBERT 更像一条开源方法路线，而 text-embedding 系列更像近年的通用嵌入模型或 API 产品家族。</p>
<div class="blog_h4"><span class="graybg">交叉编码（Cross-Encoder）</span></div>
<p>在 SBERT 之前，句子嵌入任务通常沿用<span style="background-color: #c0c0c0;">交叉编码器（Cross-Encoder）+ BERT</span> 的范式实现相似度建模：把两个句子同时输入 Transformer 网络，常见形式是将句子 A 与句子 B 拼接成单个序列，中间用分隔符隔开，然后在原始 BERT 顶部增加分类头或回归头，直接输出这一对句子的相似度分数。这种架构的优势在于两个句子的 token 可以在同一次自注意力计算中充分交互，因此非常擅长做细粒度匹配判断；但它输出的是“句对分数”，而不是两个可独立复用的句向量。</p>
<p>这会直接带来大规模计算问题。若要在一个包含 10000 个句子的集合中找出相似度最高的匹配对，交叉编码器原则上需要对几乎所有句对分别做一次联合编码，计算量约为 <span displaypfx="inline-" class="mathjax-container">\(\frac{10000\times 9999}{2}=49{,}995{,}000\)</span> 次前向比较。也就是说，问题规模从“编码 10000 个句子”膨胀成了“编码近五千万个句对”。由于每个候选句子都必须与其他句子重新拼接、重新过一遍 BERT，这类方法几乎无法承担大规模语义检索、聚类或召回的第一阶段计算。SBERT 的关键突破，正是在保留 BERT 语义建模能力的同时，把句子表示改造成可预先编码、可缓存、可直接做余弦相似度比较的独立向量。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/cross-encoder.jpg"><img class="alignnone size-full wp-image-41927" src="https://blog.gmem.cc/wp-content/uploads/2026/03/cross-encoder.jpg" alt="cross-encoder" width="1024" height="1024" /></a></p>
<div class="blog_h4"><span class="graybg">双编码（Bi-Encoder）与孪生网络</span></div>
<p>因此，SBERT 的核心价值首先体现在计算结构的改变上：它把原本“对句对打分”的问题，改写成“分别编码句子，再比较向量距离”的问题。这样一来，候选句子可以预先编码并缓存，检索阶段只需要做向量相似度计算，而不必让每一对候选都重新经过一次完整的 BERT 联合编码。</p>
<p>它与 Cross-Encoder 的关键差异在于计算结构：</p>
<ul>
<li>双编码器：两个输入分别编码，可离线预计算候选向量，适合大规模检索（ANN）。</li>
<li>交叉编码器（Cross-Encoder）：把两个输入拼接后一起编码，匹配更精细但无法离线索引，适合重排序（Reranking）。</li>
</ul>
<p>在结构上，SBERT 的核心是<span style="background-color: #c0c0c0;">孪生架构（Siamese Architecture）</span>：两侧使用一模一样的编码器，参数完全共享，但分别接收一个句子作为输入。训练时，句子对会分别经过这两个共享权重的编码塔，各自得到固定维度的句向量，再基于余弦相似度、三元组损失（Triplet Loss）或对比损失（Contrastive Loss）优化距离关系。共享权重保证了两个句子被投射到同一个表示空间中，因此向量之间的距离才具有可比较性。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/sbert.png"><img class="alignnone size-full wp-image-41879" src="https://blog.gmem.cc/wp-content/uploads/2026/03/sbert.png" alt="sbert" width="1280" height="1280" /></a></p>
<div class="blog_h4"><span class="graybg">训练方式</span></div>
<p>SBERT 的训练过程可以拆成两个步骤。第一步是<span style="background-color: #c0c0c0;">把每个句子单独编码成向量</span>。设句子 <span displaypfx="inline-" class="mathjax-container">\(x=(t_1,\dots,t_n)\)</span>，经过共享编码器后得到逐 token 的隐藏状态 <span displaypfx="inline-" class="mathjax-container">\(H=[h_1,\dots,h_n]\)</span>；随后用池化（Pooling）把它压缩成句向量 <span displaypfx="inline-" class="mathjax-container">\(e(x)\in\mathbb{R}^d\)</span>。经典实现最常用平均池化：</p>
<span displaypfx="" class="mathjax-container">\[e(x)=\frac{1}{n}\sum_{i=1}^{n} h_i\]</span>
<p>有些实现还会继续做 L2 归一化，得到 <span displaypfx="inline-" class="mathjax-container">\(\hat e(x)=e(x)/\|e(x)\|_2\)</span>，以便后续直接用余弦相似度比较方向。这里的关键点是：左右两侧并不是两套不同模型，而是同一组参数共享的编码器分别处理句子 A 和句子 B，最终得到 <span displaypfx="inline-" class="mathjax-container">\(\hat e_a\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\hat e_b\)</span> 两个可独立复用的句向量。</p>
<p>训练语料的形状也必须与损失函数匹配。最常见的几类数据形式如下：</p>
<ul>
<li><span style="background-color: #c0c0c0;">带连续分数的句对</span>：形如 <span displaypfx="inline-" class="mathjax-container">\((x_a,x_b,y)\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 是 0 到 1、或 0 到 5 再归一化后的语义相似度分数。这类数据最适合 STS（Semantic Textual Similarity）任务，监督信号不是“是不是同类”，而是“到底有多像”。</li>
<li><span style="background-color: #c0c0c0;">二元正负句对</span>：形如 <span displaypfx="inline-" class="mathjax-container">\((x_a,x_b,y)\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(y\in\{0,1\}\)</span>。这里 <span displaypfx="inline-" class="mathjax-container">\(y=1\)</span> 表示复述句、同义问法、相关 query-document 或其它正样本对；<span displaypfx="inline-" class="mathjax-container">\(y=0\)</span> 表示语义无关、错误匹配或人工拒绝的负样本对。</li>
<li><span style="background-color: #c0c0c0;">检索式正配对</span>：形如 <span displaypfx="inline-" class="mathjax-container">\((q,p)\)</span>，只显式给出 query 与其正确匹配的正样本，不单独列出负样本。训练时，通常把同一 batch 中其它 <span displaypfx="inline-" class="mathjax-container">\(p_j\)</span> 当作 <span displaypfx="inline-" class="mathjax-container">\(q_i\)</span> 的负例，这就是批内负样本（In-batch Negatives）的基本数据组织方式。</li>
<li><span style="background-color: #c0c0c0;">三元组</span>：形如 <span displaypfx="inline-" class="mathjax-container">\((a,p,n)\)</span>，其中锚点 <span displaypfx="inline-" class="mathjax-container">\(a\)</span> 与正样本 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 应靠近，而与负样本 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 应拉开距离。这类数据天然适合排序与检索，因为它直接表达了“哪个比哪个更相关”。</li>
</ul>
<p>因此，SBERT 训练并不要求数据必须带精确分数；它既可以吃连续相似度打分，也可以吃正负标签，甚至只需要 query-positive 配对或三元组。真正关键的是，训练样本必须能清楚告诉模型：哪些句子应该靠近，哪些句子应该远离，以及这种关系是绝对打分还是相对排序。</p>
<p>第二步是<span style="background-color: #c0c0c0;">根据标注关系定义损失</span>。若训练数据给的是连续相似度分数，例如 0 到 1 之间的语义相似度标签 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>，最直接的做法是先计算两向量的余弦相似度</p>
<span displaypfx="" class="mathjax-container">\[s(\hat e_a,\hat e_b)=\cos(\hat e_a,\hat e_b)=\frac{\hat e_a^\top \hat e_b}{\|\hat e_a\|_2\|\hat e_b\|_2}\]</span>
<p>再让模型输出的相似度逼近人工标签，例如最简单的回归式目标可以写成</p>
<span displaypfx="" class="mathjax-container">\[L=(s(\hat e_a,\hat e_b)-y)^2\]</span>
<p>这时，相似句子的标签 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 更高，损失会推动两向量余弦相似度上升；不相似句子的标签更低，损失则推动相似度下降。原始 SBERT 在 STS（Semantic Textual Similarity）类任务上，常用的正是这种“句对回归到相似度分数”的训练方式。</p>
<p>若训练数据只有二元标签，即“相似”或“不相似”，则更常见的是对比式损失（Contrastive Loss）。设距离 <span displaypfx="inline-" class="mathjax-container">\(d(\hat e_a,\hat e_b)\)</span> 可以取欧氏距离，也可以取 <span displaypfx="inline-" class="mathjax-container">\(1-\cos(\hat e_a,\hat e_b)\)</span>，则典型形式为</p>
<span displaypfx="" class="mathjax-container">\[L=y\cdot d(\hat e_a,\hat e_b)^2+(1-y)\cdot \max(0,m-d(\hat e_a,\hat e_b))^2\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(y=1\)</span> 表示正样本对，损失会逼迫距离变小；<span displaypfx="inline-" class="mathjax-container">\(y=0\)</span> 表示负样本对，损失会要求两句至少相隔一个 margin <span displaypfx="inline-" class="mathjax-container">\(m\)</span>。这就把“相似句子拉近，不相似句子推远”写成了显式几何约束。</p>
<p>若任务更接近检索或召回，现代实践更常用批内负样本（In-batch Negatives）或多负样本排序损失（Multiple Negatives Ranking Loss）。设一个 batch 中第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个查询句子的正样本是 <span displaypfx="inline-" class="mathjax-container">\(p_i\)</span>，其余 <span displaypfx="inline-" class="mathjax-container">\(p_j\)</span> 都视为负例，则可写成</p>
<span displaypfx="" class="mathjax-container">\[L_i=-\log \frac{\exp(s(\hat e_{q_i},\hat e_{p_i})/\tau)}{\sum_{j=1}^{B}\exp(s(\hat e_{q_i},\hat e_{p_j})/\tau)}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\tau\)</span> 是温度（Temperature）参数。这个目标并不需要显式为每个查询单独准备大量负样本，而是直接把同一 batch 里的其它正样本当作自己的负样本；于是优化方向很清楚：正确配对的句子相似度必须高于 batch 中所有错误配对。许多现代 embedding 模型，包括大量 SBERT 派生模型，都是沿着这条路线训练出来的。</p>
<p>另一类经典形式是三元组损失（Triplet Loss）。它不再只看一对句子，而是同时给出锚点 <span displaypfx="inline-" class="mathjax-container">\(a\)</span>、正样本 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 和负样本 <span displaypfx="inline-" class="mathjax-container">\(n\)</span>，要求锚点与正样本的距离小于锚点与负样本的距离，且至少留出一个 margin：</p>
<span displaypfx="" class="mathjax-container">\[L=\max\bigl(0,\ d(\hat e_a,\hat e_p)-d(\hat e_a,\hat e_n)+m\bigr)\]</span>
<p>它表达的仍然是同一个原则：相似句子靠近，不相似句子远离；只是监督信号从“单对标签”变成了“相对排序关系”。</p>
<p>前文已经提到，原始 BERT 的 <pre class="crayon-plain-tag">[CLS]</pre> 表示更适合接分类头，而不是直接充当高质量通用句向量；同样地，若未经句向量任务优化就简单平均最后一层表示，效果通常也不理想。SBERT 的关键改动，正是在共享权重的双编码训练框架中，把池化和句对监督显式写入训练过程，使生成出来的 <span displaypfx="inline-" class="mathjax-container">\(e(x)\)</span> 不再只是“顺手拿来的中间表示”，而是被专门优化成适合做余弦相似度、最近邻检索与聚类的句向量。其改进原因之一，正是缓解了原始 BERT 表示的各向异性（Anisotropy）问题：向量不再高度挤在高维空间的少数方向上，余弦相似度也因此更有区分度。</p>
<p>这里也需要区分 Sentence-BERT 与 sentence-transformers。前者更严格地指 2019 年提出的 SBERT 方法路线：在 BERT、RoBERTa 等编码器基础上，通过孪生网络（Siamese Network）/三元组网络（Triplet Network）或后续对比式训练，把原本不适合作为通用句向量的表示空间改造成更适合相似度计算的句向量空间。后者则主要指围绕这一路线发展出来的开源框架与模型生态，用于统一训练、评测、发布和调用各类句向量模型。因此，sentence-transformers 不是与 SBERT 并列的另一类基础模型，而更像 SBERT 方法在工程上的延伸与集合。</p>
<p>从工程角度看，SBERT 仍然有明确实用价值：它特别适合私有化部署、领域微调、本地低延迟语义检索，以及“双编码器召回 + Cross-Encoder 重排”的两阶段检索流水线。它不是被现代 embedding 模型淘汰，而是在开源可控、可微调、可离线部署这些约束下依然非常常用。</p>
<div class="blog_h3"><span class="graybg">text-embedding 系列</span></div>
<p>如果把 SBERT 看作“如何训练和使用句向量”的经典范式，那么 text-embedding 系列代表的就是近年的通用嵌入模型实现。BGE、E5、GTE、OpenAI 的 text-embedding、Cohere Embed 等，本质上都属于同一任务族：生成可用于相似度、检索、聚类、召回的向量表示。它们的主要差异不在“是否属于 embedding”，而在训练数据、模型规模、多语言能力、上下文长度、向量维度压缩策略，以及是开源模型还是托管 API 服务。</p>
<p>通用嵌入模型（General-purpose Embedding Model）的目标通常是“语义相似近、语义无关远”，因此天然适配检索与聚类。也可以把嵌入模型微调成“任务特化嵌入”（Task-specialized Embedding）：用监督标签构造正/负样本对（同类为正、异类为负），用对比学习目标把同类拉近、异类推远，然后用最近邻/类原型（Prototype）实现分类。</p>
<p>与“表示模型 + 分类头（Representation Model + Classifier Head）”相比，二者取舍通常是：</p>
<ul>
<li>特化嵌入：推理时只算一次向量 + 相似度，便于大规模检索/多标签扩展；但输出是距离分数，概率校准与细粒度判别能力通常不如专门的分类头。</li>
<li>分类头微调：直接最小化分类损失，闭集分类效果与可解释的概率输出更强；但对大规模候选检索不友好，且不同任务往往需要不同 head。</li>
</ul>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类别</td>
<td style="text-align: center;">代表模型/服务</td>
<td style="text-align: center;">特点</td>
<td style="text-align: center;">适用场景</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>开源通用嵌入（General-purpose）</td>
<td>BGE / E5 / GTE</td>
<td>部署可控；可做领域微调</td>
<td>私有化 RAG；向量检索；聚类</td>
<td>选型关注多语言、长度与 license</td>
</tr>
<tr>
<td>商用 API 嵌入</td>
<td>text-embedding-3 / Cohere Embed</td>
<td>效果稳定；无需运维</td>
<td>快速上线；跨团队复用</td>
<td>成本与数据合规是主约束</td>
</tr>
<tr>
<td>领域特化嵌入（Task-specialized）</td>
<td>对比学习微调后的 embedding</td>
<td>对业务分布拟合更强</td>
<td>垂直领域检索；闭集分类</td>
<td>需要高质量正/负样本构造</td>
</tr>
<tr>
<td>多向量/late interaction</td>
<td>ColBERT 类</td>
<td>token-level 匹配更细</td>
<td>高精度检索；精排候选压缩</td>
<td>索引与存储成本更高</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">主流 Encoder–Decoder 模型</span></div>
<div class="blog_h3"><span class="graybg">T5</span></div>
<p>T5（Text-to-Text Transfer Transformer）是典型 Encoder–Decoder：把所有任务统一为“文本到文本”的生成问题（text-to-text）。它既可用于摘要、翻译等生成任务，也可用于分类，此时模型直接生成类别名对应的 token 序列。</p>
<p>在预训练阶段，T5采用的是掩码式去噪目标（Denoising Objective），但它不是只遮住单个 token 再逐个恢复，而是会对连续的 token span 做遮掩（Span Corruption）。输入中的若干片段会被替换为哨兵标记（Sentinel Token），模型则在解码端按顺序生成这些被遮住的片段。这样的训练方式既保留了“根据上下文恢复缺失内容”的掩码语言建模思想，又让模型从一开始就以 Encoder–Decoder 的生成方式学习条件重建。</p>
<p>在后续适配阶段，T5 延续了统一的 text-to-text 框架：翻译、摘要、问答、分类等任务都写成文本输入到文本输出的形式。沿着这条路线继续发展后，研究者又引入了更大规模的多任务指令微调（Instruction Tuning）：把大量带自然语言任务描述的监督任务混合起来训练，迫使模型学习“读懂任务说明，再按说明生成答案”。FLAN-T5 就是这一路线的代表，即在 T5 底座之上经过 FLAN 指令微调得到的系列模型；它相比原始 T5 更强调零样本（Zero-shot）和少样本（Few-shot）泛化能力。</p>
<div class="blog_h3"><span class="graybg">BART</span></div>
<p>BART（Bidirectional and Auto-Regressive Transformers）同属 Encoder–Decoder，但预训练更强调去噪自编码（Denoising Autoencoding）：对输入做扰动（mask、shuffle、delete 等），让模型恢复原文。它在摘要、生成式改写与条件生成任务上常用作强基线。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">架构</td>
<td style="text-align: center;">预训练直觉</td>
<td style="text-align: center;">强项</td>
</tr>
</thead>
<tbody>
<tr>
<td>T5</td>
<td>Encoder–Decoder</td>
<td>统一 text-to-text</td>
<td>任务统一；文本生成与分类都自然</td>
</tr>
<tr>
<td>BART</td>
<td>Encoder–Decoder</td>
<td>去噪重建</td>
<td>摘要与生成式改写强基线</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">任务特定语言模型</span></div>
<div class="blog_h3"><span class="graybg">概述</span></div>
<p>任务特定语言模型（Task-specific Language Model）指在通用预训练模型基础上，围绕某个明确监督任务附加任务头（Task Head）并继续微调的模型。常见任务包括句段级分类、token 级序列标注、文本匹配、排序与信息抽取。这里“句段级”指分类对象是一段独立文本，可以是单句，也可以是能够整体输入模型的段落。工程上它并不是一类全新架构，而是“预训练主干 + 任务头 + 对应损失函数”的组合：同样是 BERT、DeBERTa、T5 或大语言模型（Large Language Model, LLM），接什么头、优化什么目标，决定了它最终服务什么任务。</p>
<p>对 BERT 类 Encoder-only 模型，多分类通常采用“句向量 + 线性分类头（Linear Classification Head）”的形式。设句子表示为 <span displaypfx="inline-" class="mathjax-container">\(h\in\mathbb{R}^{d}\)</span>，类别数为 <span displaypfx="inline-" class="mathjax-container">\(K\)</span>，则分类头输出 logits：</p>
<span displaypfx="" class="mathjax-container">\[z=Wh+b,\quad W\in\mathbb{R}^{K\times d},\quad b\in\mathbb{R}^{K}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(h\)</span> 是主干模型抽取出的整句语义表示，常取自 <pre class="crayon-plain-tag">[CLS]</pre> 对应隐藏状态或池化结果；<span displaypfx="inline-" class="mathjax-container">\(W\)</span> 把 <span displaypfx="inline-" class="mathjax-container">\(d\)</span> 维表示映射到 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个类别；<span displaypfx="inline-" class="mathjax-container">\(b\)</span> 是偏置项；<span displaypfx="inline-" class="mathjax-container">\(z_k\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 类的原始分数（logit）。若需要概率，可再经过 softmax：</p>
<span displaypfx="" class="mathjax-container">\[p(y=k|x)=\frac{e^{z_k}}{\sum_{j=1}^{K}e^{z_j}}\]</span>
<p>训练时通常直接把 logits <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 输入交叉熵（Cross-Entropy）损失，softmax 与对数运算一般由损失函数内部完成，以获得更好的数值稳定性；推理时若需要概率分布、阈值决策或置信度排序，再显式做 softmax。实践中还常在分类头前加入 Dropout，以降低小样本场景下的过拟合风险。</p>
<p>命名实体识别（Named Entity Recognition, NER）等 token 级任务的输出结构不同。设序列长度为 <span displaypfx="inline-" class="mathjax-container">\(T\)</span>，第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个 token 的隐藏状态为 <span displaypfx="inline-" class="mathjax-container">\(h_t\)</span>，则每个位置的标签打分可写成：</p>
<span displaypfx="" class="mathjax-container">\[z_t=Wh_t+b,\quad t=1,\dots,T\]</span>
<p>此时分类头不再输出“整句一个类别”，而是为每个 token 输出一组 BIO / BIOES 标签 logits。若任务更强调标签转移的一致性，还可在 token 分类头后叠加条件随机场（Conditional Random Field, CRF），显式约束标签序列的合法转移。</p>
<p>因此，任务头的输出形式始终取决于任务本身：句子分类输出 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^{K}\)</span> 上的一组 logits，token 分类为每个位置输出一组 logits，匹配/排序任务常输出单个相关性分数，生成任务则在词表维度输出下一 token 的 logits。主干负责提供中间表示（Intermediate Representation），任务头负责把这种表示投影成可直接优化的任务空间。</p>
<p>到 2026 年，任务特定语言模型的工程选型已经稳定分化。对高并发、闭集标签、边界清晰且标注数据相对充足的文本分类与 NER，DeBERTa、ModernBERT 一类 Encoder-only 模型仍然具有极高性价比：延迟低、吞吐高、概率校准更稳、部署成本更可控。若任务明确是中文单语理解，<pre class="crayon-plain-tag">chinese-roberta-wwm-ext</pre> 仍然是值得保留的经典稳健基线，MacBERT 则通常是更自然的同路线升级点。这里的输入并不只限于单句；传统 Encoder-only 模型常见有效长度大约在 512 tokens，而较新的长上下文 Encoder-only 模型已经普遍扩展到 8K tokens，因此数百字的段落在 2026 年通常也仍属于可直接处理的范围。对样本极少、语义规则复杂、输出结构开放，或需要“理解 + 生成”一体化的任务，LLM 配合参数高效微调（Parameter-Efficient Fine-Tuning, PEFT）通常更有优势。</p>
<p>在这类 LLM 任务里，LoRA 仍然是默认起点：它最适合指令跟随、风格迁移、格式约束、轻量领域适配等大多数常规需求。若显存是第一约束，QLoRA 往往是最自然的落点；若任务要求更接近全参数微调的表达力，例如深领域知识吸收、复杂推理、困难边界判别或更强的稳定性，则可优先考虑 DoRA 或 Q-DoRA。全参数微调仍然保留在少数高门槛场景：领域迁移极深、训练数据极大，或需要改动词表、上下文长度、位置编码等底层结构时，它仍然提供最高上限。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">任务形态</td>
<td style="text-align: center;">更常见方案</td>
<td style="text-align: center;">主要原因</td>
<td style="text-align: center;">典型模型</td>
</tr>
</thead>
<tbody>
<tr>
<td>闭集文本分类</td>
<td>Encoder-only + 分类头</td>
<td>判别边界清晰；推理便宜；概率输出稳定</td>
<td>BERT / RoBERTa / DeBERTa / ModernBERT</td>
</tr>
<tr>
<td>标准 NER / 序列标注</td>
<td>Encoder-only + token head（可叠加 CRF）</td>
<td>天然适配 token 级标签；边界学习直接</td>
<td>BERT-CRF / DeBERTa-CRF</td>
</tr>
<tr>
<td>低资源复杂分类</td>
<td>LLM + LoRA / QLoRA</td>
<td>预训练知识丰富；少样本泛化强；显存门槛低</td>
<td>Qwen / Llama / Gemma + LoRA</td>
</tr>
<tr>
<td>复杂结构抽取 / JSON 输出</td>
<td>LLM + PEFT 或指令微调</td>
<td>可同时完成理解、抽取、归纳与结构化生成</td>
<td>Qwen / Llama / Mistral 系列</td>
</tr>
<tr>
<td>高难推理 / 深领域适配</td>
<td>LLM + DoRA / Q-DoRA</td>
<td>比普通 LoRA 更接近全参数微调；适合高质量知识注入</td>
<td>Qwen / Llama + DoRA</td>
</tr>
<tr>
<td>极致吞吐线上服务</td>
<td>LLM 标注或蒸馏，Encoder-only 上线</td>
<td>兼顾数据质量、速度与运维成本</td>
<td>LLM 教师 + DeBERTa 学生</td>
</tr>
<tr>
<td>超大规模深度迁移 / 底层结构改造</td>
<td>全参数微调</td>
<td>需要改动表示空间本身，低秩适配容量不足</td>
<td>领域基座继续训练或全量 SFT</td>
</tr>
</tbody>
</table>
<p>工业系统中也常采用混合路线：先用强 LLM 做数据清洗、弱标注、难例发现或标签体系归并，再把任务蒸馏到更轻的 Encoder-only 模型上线。这种做法利用了 LLM 的语义泛化能力，也保留了小模型在延迟、吞吐与稳定性上的优势。</p>
<div class="blog_h3"><span class="graybg">基于嵌入的推荐系统</span></div>
<p>推荐系统也经常以“任务特定模型”的方式落地，尤其是在召回（Recall）阶段。若每首歌曲都能通过歌词、标题、风格标签、歌手简介或多模态信息编码成一个向量 <span displaypfx="inline-" class="mathjax-container">\(e_i\)</span>，那么系统就可以把用户已经选择、收藏或反复播放的歌曲向量聚合成一个用户兴趣表示 <span displaypfx="inline-" class="mathjax-container">\(u\)</span>。一个最常见的做法是把若干已选歌曲的嵌入做平均或加权平均：</p>
<span displaypfx="" class="mathjax-container">\[u=\frac{1}{N}\sum_{i=1}^{N} e_i\]</span>
<p>随后，对候选歌曲 <span displaypfx="inline-" class="mathjax-container">\(s_j\)</span> 计算它与用户兴趣向量的相似度，例如余弦相似度（Cosine Similarity）：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{score}(u,s_j)=\cos(u,e_j)\]</span>
<p>得分越高，说明该歌曲与用户已选择歌曲在嵌入空间里越接近，也就越可能在风格、主题、情绪或语义上相似。于是，如果用户最近连续选择了几首节奏轻快、独立流行、以失恋叙事为主题的歌曲，系统就会优先召回在向量空间中靠近这些歌曲的其它曲目，而不是只依赖“同歌手”或“同标签”的硬规则。</p>
<p>这类推荐的关键不是显式分类，而是<span style="background-color: #c0c0c0;">把“用户喜欢什么”和“歌曲像什么”统一表示到同一嵌入空间，再用最近邻搜索完成相似歌曲推荐</span>。工程上它通常对应双塔模型（Two-Tower Model）或文本/多模态嵌入模型：一侧编码用户历史，一侧编码候选歌曲，训练时用点击、收藏、完整播放等行为构造正样本，再配合负采样或对比学习把用户喜欢的歌曲拉近、不感兴趣的歌曲推远。这样得到的推荐结果，本质上是基于语义相关性而不是基于精确关键词匹配。</p>
<div class="blog_h3"><span class="graybg">基于表示模型的分类</span></div>
<p>一条常见路线是把 BERT、RoBERTa、DeBERTa 这类表示模型（Representation Model）当作固定特征提取器（Feature Extractor）使用：先用预训练基座把输入文本编码成一个向量表示，再在其上训练一个轻量分类器，而不继续更新基座模型参数。这种做法的重点不是“让语言模型重新学习任务”，而是直接利用其已经学到的通用语义表示。</p>
<p>工程上，一个典型流程是：先冻结（Freeze）BERT 系列底座的全部参数，只保留前向编码功能；然后对每条输入文本提取句级表示 <span displaypfx="inline-" class="mathjax-container">\(h\in\mathbb{R}^{d}\)</span>，这个表示可以取自 <pre class="crayon-plain-tag">[CLS]</pre> 位置，也可以取池化后的整句向量；最后在 <span displaypfx="inline-" class="mathjax-container">\(h\)</span> 之上训练一个分类器。若用逻辑回归（Logistic Regression）或等价的线性 softmax 分类头，则可写成：</p>
<span displaypfx="" class="mathjax-container">\[z=Wh+b,\quad p(y=k\mid x)=\frac{e^{z_k}}{\sum_{j=1}^{K}e^{z_j}}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(W\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 是新训练的分类器参数，而 BERT 底座参数保持不变。这样做的优点是训练成本低、显存压力小、过拟合风险更可控，也便于把同一个表示底座复用到多个小任务上。代价是分类边界完全依赖预训练表示的可分性：如果任务与通用语料差距较大，或标签语义非常细，冻结底座通常不如继续微调主干模型灵活。</p>
<div class="blog_h3"><span class="graybg">基于嵌入模型的分类</span></div>
<p>与此相近的还有基于嵌入模型（Embedding Model）的分类。它的做法更直接：先用 SBERT、BGE、E5、GTE 或 text-embedding 一类嵌入模型，把整段文本映射成一个句向量 <span displaypfx="inline-" class="mathjax-container">\(e(x)\in\mathbb{R}^{d}\)</span>；然后直接在这个嵌入向量后面接一个分类器，例如逻辑回归、线性分类器或一个很小的 MLP，学习从嵌入空间到类别空间的映射。</p>
<p>若使用最简单的线性分类器，这条路线在形式上与前面的表示模型分类并没有本质区别，仍然可以写成</p>
<span displaypfx="" class="mathjax-container">\[z=We(x)+b\]</span>
<p>不同之处不在“后面接不接分类器”，而在于前面的底座向量是怎么来的：表示模型通常是为理解类任务设计的上下文化表示；嵌入模型则更强调在向量空间中保持语义距离结构，使相似文本彼此接近、不相似文本彼此远离。因此，基于嵌入模型的分类与基于表示模型的分类，主要区别在底座优化目标而不是分类头形式。表示模型更偏向判别式理解，输出表示通常更适合直接服务闭集分类、序列标注和细粒度 NLU；嵌入模型更偏向语义检索与相似度结构，优势在于跨任务复用、向量检索兼容性和“分类 + 召回”一体化。</p>
<p>工程上可以把两者概括成一句话：<span style="background-color: #c0c0c0;">表示模型分类强调“先得到适合判别的表示，再做分类”；嵌入模型分类强调“先得到适合度量的向量，再在其上学习分类边界”</span>。如果任务是标准闭集分类、标签边界清晰、追求最高分类精度，BERT 一类表示模型通常更自然；如果系统本身已经以 embedding 为中心，例如既要分类又要相似检索、聚类、召回或原型匹配，那么直接在嵌入模型后接分类器会更统一，也更容易复用同一套向量基础设施。</p>
<div class="blog_h3"><span class="graybg">基于嵌入模型的聚类</span></div>
<p>嵌入模型（Embedding Model）的另一条典型用途是文本聚类（Text Clustering）。代表性方法如 BERTopic：先用句向量模型把文档映射到嵌入空间，再做降维与聚类，最后从每个簇中抽取代表词或主题词。这里真正决定簇结构的并不只有嵌入模型；降维模型与聚类模型本身仍然是经典机器学习方法。</p>
<p>一个通用流程通常分成三步：</p>
<ol>
<li>使用嵌入模型将文档转换为向量表示，例如把每篇文档编码为 <span displaypfx="inline-" class="mathjax-container">\(e(x_i)\in\mathbb{R}^{d}\)</span>。</li>
<li>使用降维模型把高维向量压缩到更适合聚类的低维空间。</li>
<li>使用聚类模型在降维后的表示上得到簇标签，或进一步识别离群点。</li>
</ol>
<p>降维阶段的典型选型是 PCA（Principal Component Analysis）或 UMAP（Uniform Manifold Approximation and Projection）。PCA 是线性方法，适合作为快速、稳定的基线；UMAP 更擅长保留非线性邻域关系与整体簇结构，因此在文本聚类里往往更常见。工程上常把 UMAP 先降到 5 到 10 维，作为后续聚类的默认起点；这个范围通常已经足以保留主要结构，同时明显降低噪声与计算成本。</p>
<p>UMAP 的一些常见设置也会直接影响聚类形状。<pre class="crayon-plain-tag">min_dist</pre> 控制低维空间中点与点允许靠得多近；若把它设为 0，低维表示通常会形成更紧密的簇。距离度量常设为 <pre class="crayon-plain-tag">cosine</pre>，因为文本表示在高维空间中更常体现为方向相似性；无论是高维稀疏词向量还是高维稠密嵌入，欧氏距离都容易出现判别力下降的问题。</p>
<p>聚类阶段则常见 K-means 或 HDBSCAN。K-means 适合簇数大致已知、簇形状相对规则的场景；HDBSCAN 更适合密度不均、簇形状复杂，或希望显式识别“不属于任何簇”的离群文档。BERTopic 之所以常见，正是因为它把“嵌入模型 + UMAP + HDBSCAN + 主题表示”这条工程链路封装成了一个相对稳定的默认方案。</p>
<div class="blog_h3"><span class="graybg">基于嵌入模型的主题建模</span></div>
<p>基于嵌入模型的主题建模（Topic Modeling）与上一节的聚类路线一脉相承：先把文档映射为向量，再做降维与聚类；不同之处在于，这里还要继续回答“每个簇到底在谈什么”。因此，在聚类结果之上还需要增加一个<span style="background-color: #c0c0c0;">主题提取（Topic Extraction）</span>步骤，为每个簇生成一组能概括其内容的主题关键词。</p>
<p>这一步的典型做法是 c-TF-IDF（class-based TF-IDF）。它不是对单篇文档计算 TF-IDF，而是先把同一簇里的所有文档拼接成一个“类别文档”，再统计词在该簇中的相对频率，并结合它在其它簇中的区分度计算权重。于是，一个词若在当前簇中频繁出现、但在其它簇中并不常见，它的 c-TF-IDF 权重就会更高；反之，那些在所有簇里都常见的泛化词，其权重会被压低。这样提取出来的关键词，描述的是“这个簇相对于其它簇最有代表性的内容”，而不是“整个语料里最常见的词”。</p>
<p>从工程流程看，这条路线可以写成四步：先用嵌入模型得到文档向量，再用降维与聚类得到簇，随后用向量化器统计每个簇中的词项分布，最后用 c-TF-IDF 为每个簇生成主题词。BERTopic 的核心价值就在于，它把“嵌入模型 + UMAP + HDBSCAN + c-TF-IDF”串成了一个统一框架，因此既保留了 embedding 在语义空间中的表达能力，也保留了词袋统计在主题解释上的可读性。</p>
<p>不过，c-TF-IDF 产出的关键词顺序仍然主要依赖词频统计与类间区分度，语义相关性未必总是最优。于是 BERTopic 又提供了主题表示微调（Representation Tuning）机制：先用 c-TF-IDF 生成候选关键词，再对这些候选词做重新排序。这里最常见的表示模型之一是 KeyBERTInspired。它会先利用 c-TF-IDF 为每个主题挑出一组代表性文档，把这些代表文档聚合成该主题的语义表示，再用与文档编码相同的嵌入模型去计算“候选关键词与主题表示”的语义相似度，最后按相似度重排关键词顺序。在实践中，这种表示方式通常还能进一步压低停用词在最终主题表示中的占比，使主题词列表更干净。</p>
<p>因此，KeyBERTInspired 并不是重新做一遍聚类，也不是替代 c-TF-IDF；它更像是在 c-TF-IDF 给出的候选集之上增加一层语义重排序。这样做的结果通常是：靠前的主题词更连贯、停用词和噪声词更少，主题标签也更接近人类对“这个簇在讲什么”的直觉。对 BERTopic 而言，这一步属于主题表示优化，而不是主题发现本身。</p>
<p>即便经过上述处理，主题关键词之间仍可能存在明显冗余，例如多个高频近义词反复出现在同一主题里。此时还可以进一步使用最大边际相关性（Maximal Marginal Relevance, MMR）做关键词多样化：它在选择下一个关键词时，同时考虑“该词与主题表示有多相关”以及“该词与已经选中的关键词有多相似”，从而找到一组彼此具有差异性、但仍然和目标文档或主题表示保持相关的关键词。于是，MMR 的作用不是提升聚类质量，而是让最终主题表示更分散、更少重复，也更适合人工阅读与命名。</p>
<p>在此基础上，还可以再走一步：把已经得到的一组主题关键词交给生成模型（Generative Model），例如 FLAN-T5，让模型基于这些关键词生成一个更短、更自然的主题标签或一小段摘要式说明。这样做并不改变主题发现本身，而是把“关键词列表”进一步压缩成更适合展示给用户阅读的主题名称。</p>
<div class="blog_h3"><span class="graybg">基于嵌入模型的零样本分类</span></div>
<p>嵌入模型还可以直接用于零样本分类（Zero-shot Classification）。做法是不训练额外分类器，而是先把每个候选类别改写成自然语言标签描述，再把输入文本与这些标签描述同时编码到同一嵌入空间中，通过相似度完成类别判断。若影评任务只有“正面”和“负面”两类，就可以构造两个标签文本，例如“这是一条正面影评”和“这是一条负面影评”，然后分别计算影评向量与两个标签向量的余弦相似度：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{score}_k=\cos(e(x),e(t_k))\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 是待分类影评，<span displaypfx="inline-" class="mathjax-container">\(t_k\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个类别对应的标签描述。若 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{score}_{\text{positive}}\)</span> 高于 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{score}_{\text{negative}}\)</span>，系统就把该影评判为正面评价。这个过程本质上是<span style="background-color: #c0c0c0;">把分类问题改写为“文本与标签描述谁更接近”的相似度匹配问题</span>。</p>
<p>这条路线的优点是部署快、几乎不需要任务专用标注数据，也不必重新训练分类头；只要标签语义写得足够清晰，就可以立刻在新任务上工作。它的代价同样直接：由于模型并未使用该任务的监督信号显式学习分类边界，零样本分类的准确率通常低于有标注数据时训练出的监督式分类器，尤其在标签定义细、类别边界接近、文本包含反讽或领域术语时更明显。</p>
<div class="blog_h3"><span class="graybg">基于生成模型分类</span></div>
<p>另一条更轻量的工程路线是基于生成模型（Generative Model）做分类，即直接调用大语言模型（Large Language Model, LLM），通过提示词工程（Prompt Engineering）把分类任务表述为指令，例如要求模型在读完一段文本后只输出“正面”或“负面”。这种做法本质上是把分类看作条件生成：模型先理解输入，再生成最符合提示约束的类别标签。这里的“生成模型”不只包括 Decoder-only LLM；像 T5 这样的 Encoder–Decoder 模型，虽然结构上属于编解码器，也同样可以在不给额外训练的情况下，直接通过输入提示词完成分类，因此在使用方式上也可以视作这一类路线。</p>
<p>它的优点是上手快、改标签体系方便、对少样本或零样本任务尤其灵活；局限是输出稳定性、延迟和成本通常不如专门训练的分类器，而且类别边界是否清晰、提示词是否严格，会明显影响结果一致性。因此，这条路线更适合作为快速原型、弱标注工具或低频复杂任务的分类接口，而不是高吞吐、强约束场景下的默认方案。</p>
<div class="blog_h1"><span class="graybg">多模态模型</span></div>
<p>多模态模型（Multimodal Model）的主线，不是“把图像、文本、音频都接进来”这么简单，而是<span style="background-color: #c0c0c0;">不同模态如何进入同一套表示、同一套推理链路、同一套训练目标</span>。从技术演进看，这条路线大致经历了四步：先有稳定的单模态编码器，再有跨模态对齐模型，然后发展出“视觉编码器 + 大语言模型”的连接式系统，最后才逐步走向原生多模态（Native Multimodality）。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">阶段</td>
<td style="text-align: center;">核心做法</td>
<td style="text-align: center;">代表能力</td>
<td style="text-align: center;">主要局限</td>
</tr>
</thead>
<tbody>
<tr>
<td>单模态编码器</td>
<td>分别把图像、文本、音频编码成各自表示</td>
<td>分类、检索、单模态理解</td>
<td>模态之间没有统一语义空间</td>
</tr>
<tr>
<td>对齐式多模态</td>
<td>用对比学习把不同模态映射到同一嵌入空间</td>
<td>图文检索、零样本分类</td>
<td>生成与复杂推理能力有限</td>
</tr>
<tr>
<td>连接式 VLM</td>
<td>视觉编码器输出特征，经连接器送入 LLM</td>
<td>看图问答、视觉对话、OCR 推理</td>
<td>融合发生在后端，仍有模态鸿沟</td>
</tr>
<tr>
<td>原生多模态</td>
<td>多模态尽量共享表示、主干与训练过程</td>
<td>更细粒度的跨模态理解与生成</td>
<td>训练成本、数据对齐和系统复杂度都更高</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">视觉编码器</span></div>
<div class="blog_h3"><span class="graybg">ViT</span></div>
<p>ViT（Vision Transformer）本身不是多模态模型，因为它只处理图像，不直接处理文本、语音或视频。它更准确的定位是视觉编码器（Visual Encoder）或视觉骨干网络（Backbone）：把原始像素转换成可供下游检索、分类或跨模态建模使用的视觉表示。</p>
<p>ViT 的关键做法是把图像切成固定大小的 patch，再把每个 patch 视作一个视觉 token。每个 patch 先被展平，再经线性投影映射到统一维度的向量空间，最后叠加位置编码送入 Transformer。这样，二维图像就被改写成了一维 token 序列，Transformer 可以像处理文本序列那样处理图像局部块之间的关系。</p>
<p>因此，ViT 的意义不在于“它已经实现了多模态”，而在于它为后续的多模态系统提供了一个稳定、强大的图像侧前端。无论是 CLIP、BLIP-2 还是 LLaVA，一类很常见的路径都是：先让 ViT 之类的视觉编码器把图像变成高层特征，再考虑如何与文本模型对齐或融合。</p>
<div class="blog_h2"><span class="graybg">多模态对齐</span></div>
<div class="blog_h3"><span class="graybg">CLIP 与 OpenCLIP</span></div>
<p>第一代真正有代表性的多模态突破，不是“直接生成一切”，而是先把模态对齐。CLIP（Contrastive Language-Image Pre-training）的核心做法是：图像编码器与文本编码器分别输出向量，再通过对比学习（Contrastive Learning）让匹配的图文对更接近、不匹配的图文对更远离。这样，图像与文本就被压到同一嵌入空间中。</p>
<p>这种路线的价值非常直接。只要图像和文本已经进入同一向量空间，就可以做图文检索、文本检索图片、图片检索文本，以及把类别标签写成自然语言描述来完成零样本分类（Zero-shot Classification）。OpenCLIP 则把这条路线做成了可复现、可扩展的开源基础设施，因此在研究和工程里都比原始 CLIP 更常作为起点。</p>
<div class="blog_h3"><span class="graybg">对齐解决了什么，没解决什么</span></div>
<p>对齐式多模态的贡献是打通<span style="background-color: #c0c0c0;">跨模态语义空间</span>。模型开始能够理解“这张图和这段文字在说同一件事”，而不再只是各自做各自的单模态任务。但它的能力边界也很清楚：对齐不等于复杂推理，更不等于自由生成。双编码器模型很擅长相似度匹配，却不天然擅长长链推理、细粒度问答和多轮对话。</p>
<p>因此，对齐式系统更像是在给多模态建模打地基。它先解决“不同模态能否进入同一个语义坐标系”这个问题，再把更复杂的生成与推理问题留给下一阶段的系统去解决。</p>
<div class="blog_h2"><span class="graybg">视觉-语言模型</span></div>
<p>在多模态系统的下一阶段，重点从“把图文对齐”转向“让大语言模型真正看见图像并围绕图像说话”。这就形成了视觉-语言模型（Vision-Language Model, VLM）的主流范式：视觉编码器负责看图，语言模型负责生成，中间再用一个连接器（Connector / Projector）把二者接起来。</p>
<div class="blog_h3"><span class="graybg">BLIP-2</span></div>
<p>BLIP-2 的方法论非常有代表性。它不从零联合训练整个多模态系统，而是尽量复用两边已经训练好的单模态模型：视觉侧使用预训练视觉编码器，语言侧使用预训练大语言模型（Large Language Model, LLM），中间只训练一个较轻量的桥接模块。这样做的目的，是在保留两侧成熟能力的前提下，用较小成本建立跨模态接口。</p>
<p>这个桥接模块在论文中的标准名称是 Q-Former（Querying Transformer）。它通过一组可学习的 query，从视觉编码器输出的图像特征中抽取最适合语言模型消费的信息，再把这些压缩后的视觉表示投影到 LLM 的输入空间。它本质上解决的是“图像特征如何变成语言模型能理解的前缀表示”这个接口问题。</p>
<div class="blog_h3"><span class="graybg">LLaVA</span></div>
<p>LLaVA（Large Language and Vision Assistant）把这条路线进一步推向“视觉对话”与“视觉指令遵循”。它通常采用视觉编码器提取图像特征，再通过投影层把视觉特征接入语言模型，然后用视觉指令微调（Visual Instruction Tuning）训练模型按图像与文本指令联合回答问题。这样，模型不再只是做相似度匹配，而是可以围绕图像展开问答、描述、解释与推理。</p>
<p>这类系统已经比 CLIP 更接近通用助手，但从结构上看，它仍然属于<span style="background-color: #c0c0c0;">连接式多模态</span>：图像先由视觉模型单独处理，文本再由语言模型主导生成，融合主要发生在后期接口层，而不是从一开始就在统一主干里共同建模。</p>
<div class="blog_h2"><span class="graybg">原生多模态</span></div>
<p>原生多模态（Native Multimodality）讨论的是<span style="background-color: #c0c0c0;">多模态是否在模型内部形成统一的信息流</span>。判断重点不在“支持几种输入”，而在输入表示、时间对齐、主干推理、输出链路和训练过程是否都尽量摆脱“先拆开、后拼接”的流水线。当前前沿系统已经把这个问题分成两条路线：一条偏理解与推理，例如 Gemini 与 Qwen Omni；另一条偏生成，例如 Seedance 这类原生音视频生成模型。</p>
<div class="blog_h3"><span class="graybg">什么情况下才算“原生”</span></div>
<p>工程上，原生多模态通常不是一个非黑即白的标签，而是一组连续特征。系统越是在模型内部统一处理模态，越接近原生；越依赖外部桥接、先转文本再推理、先出文本再外接语音或视频模块，越接近连接式系统。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">层面</td>
<td style="text-align: center;">连接式系统</td>
<td style="text-align: center;">更原生的系统</td>
</tr>
</thead>
<tbody>
<tr>
<td>输入表示</td>
<td>图像、语音、视频先各自编码，再压缩成少量前缀特征送入 LLM</td>
<td>多模态 token 或 embedding 直接进入统一上下文，尽量减少外部转译</td>
</tr>
<tr>
<td>时间与空间对齐</td>
<td>对齐更多依赖外部模块或后处理</td>
<td>在模型内部显式建模时序、位置和跨模态同步关系</td>
</tr>
<tr>
<td>主干推理</td>
<td>视觉负责“看”，LLM 负责“说”，中间靠连接器传递摘要</td>
<td>不同模态共享更大的隐藏状态流或统一主干，跨模态约束在中间层持续传播</td>
</tr>
<tr>
<td>输出链路</td>
<td>文本作为中心中介，再外接 TTS、配音或视频模块</td>
<td>直接输出语音 codec、视觉 latent 或联合音视频表示</td>
</tr>
<tr>
<td>训练方式</td>
<td>先训单模态，再训桥接层</td>
<td>更多采用联合训练、端到端微调或统一后训练流程</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">统一表示与统一时间轴</span></div>
<p>原生多模态的第一层难点，是把不同模态改写成模型能共同消费的内部对象。图像相对容易，因为静态图像只需要处理二维空间；音频和视频更难，因为它们天然带有时间轴。模型既要知道“看到了什么”，也要知道“什么时候发生”，还要知道多个模态之间是否同步。</p>
<p>因此，当前更先进的系统不只做“把图像变成 token”这一步，还会显式处理跨模态时间对齐。例如在实时音视频交互场景中，视频帧和音频片段必须在同一时间轴上组织，否则模型很难稳定理解口型、说话节奏、环境声音与画面事件之间的对应关系。原生多模态在这里解决的已经不是简单的图文对齐，而是<span style="background-color: #c0c0c0;">跨模态时空对齐</span>。</p>
<div class="blog_h3"><span class="graybg">统一主干与输出链路</span></div>
<p>更进一步的区别出现在主干网络和输出端。连接式 VLM 往往已经能把图像看懂，但输出通常仍然以文本为中心；若要说话，就再接一个语音模块；若要生成视频，再接另一套生成器。原生多模态希望把这条链路收进同一系统，让“理解图像”“围绕图像推理”“输出文本”“输出语音”“生成视频”之间的中间状态尽量共享。</p>
<p>这一步对语音和视频尤其关键。文本生成更多是离散 token 的自回归；自然语音需要兼顾内容、音色、韵律和实时延迟；视频生成则同时要控制空间结构、时间连续性、镜头运动和角色一致性。也正因为如此，当前真正具有代表性的原生系统，往往在输出链路上引入更专门的设计，而不是把一切都压成单一路径。</p>
<div class="blog_h3"><span class="graybg">当前代表模型怎么做</span></div>
<p>从公开资料看，当前最值得拿来理解原生多模态的代表，大致分成三类：理解优先的长上下文 omni 模型、端到端语音交互模型，以及原生音视频联合生成模型。生成侧里，Seedance 2.0 代表了技术报告完整、控制能力非常强的闭源工业路线；HappyHorse 1.0 则代表了近期受到高度关注的开源路线。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">代表模型</td>
<td style="text-align: center;">更接近哪类原生能力</td>
<td style="text-align: center;">值得关注的架构点</td>
<td style="text-align: center;">说明了什么</td>
</tr>
</thead>
<tbody>
<tr>
<td>Gemini 1.5 / 2.5</td>
<td>理解与推理优先</td>
<td>多模态 MoE、超长上下文、视频与音频直接进入同一推理上下文</td>
<td>原生多模态可以首先体现在统一感知与统一推理上，而不必先追求所有模态都直接生成</td>
</tr>
<tr>
<td>Qwen2.5-Omni / Qwen3-Omni</td>
<td>实时 omni 交互</td>
<td>交错音视频序列、时间对齐位置编码、Thinker-Talker、流式语音生成</td>
<td>原生多模态的难点会从“看见图像”转向“实时理解并自然说出来”</td>
</tr>
<tr>
<td>Seedance 2.0 / HappyHorse 1.0</td>
<td>原生音视频联合生成</td>
<td>前者强调统一多模态参考与编辑控制；后者强调统一 Transformer、联合音视频生成与开源部署</td>
<td>生成侧的原生多模态不仅要出画面，还要同时控制声音、时序同步、镜头连续性与工程可用性</td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">Gemini 系列：理解优先的原生多模态</span></div>
<p>Gemini 路线的代表意义，在于它把“原生”首先落在<span style="background-color: #c0c0c0;">统一感知与统一推理</span>上。Gemini 1.5 技术报告已经把它定义为高效的多模态 MoE 系统，强调可以在百万级上下文中处理长文档、音频和长视频；Gemini 2.5 则进一步把思维链式推理、长上下文和多模态理解合到同一代模型中。这条路线说明：原生多模态并不只等于“模型会出语音或会生成视频”，更重要的是不同模态能否在同一个长上下文里被一起检索、比较、归纳和推理。</p>
<div class="blog_h4"><span class="graybg">Qwen Omni：端到端实时交互</span></div>
<p>Qwen2.5-Omni 和 Qwen3-Omni 展示了另一种更彻底的做法：系统不仅统一感知文本、图像、音频和视频，还直接生成实时语音响应。其关键难点不在“多接几种输入”，而在音视频时间对齐与输出干扰控制。Qwen2.5-Omni 采用交错组织的音视频序列与时间对齐位置编码 TMRoPE，再用 Thinker-Talker 把“负责推理的语言主干”和“负责流式说话的语音输出链路”拆开但端到端协同；Qwen3-Omni 则进一步把这一路线升级到 MoE，并围绕首包延迟和 codec 级语音生成做了更强的流式优化。</p>
<div class="blog_h4"><span class="graybg">Seedance 2.0 与 HappyHorse：生成优先的原生多模态</span></div>
<p>生成侧的原生多模态，目标已经不是“看图回答问题”，而是直接联合生成视频与音频。ByteDance 在 2026 年 2 月正式发布的 Seedance 2.0，把这一方向推进到更完整的工业化形态：统一的多模态音视频联合生成架构，同时支持文本、图像、音频和视频输入，并把参考、编辑、扩展、镜头控制与音视频同步放进同一系统。它解决的重点，是<span style="background-color: #c0c0c0;">如何让复杂控制信号真正进入视频生成主干</span>，而不是只靠提示词描述一个大概方向。</p>
<p>HappyHorse 1.0 则体现了另一条值得关注的路线：更强调开源可部署与统一 Transformer 架构。公开模型页把它描述为 15B 参数、统一自注意力 Transformer、联合生成视频与同步音频，并突出 1080p、7 语种 lip-sync 和较快推理。和 Seedance 2.0 相比，它当前公开的技术透明度明显更低，缺少完整论文级技术报告，但它的重要性在于：<span style="background-color: #c0c0c0;">原生音视频生成已经不再只是少数闭源大厂的能力，开源路线也开始逼近前沿体验</span>。</p>
<p>因此，这两类模型放在一起看，技术含义很清楚。Seedance 2.0 更像“高控制度、强参考编辑、面向专业生产工作流”的路线；HappyHorse 更像“统一架构、快速推理、可开源落地”的路线。它们共同说明，视频生成已经从单纯比较单帧画质，转向比较<span style="background-color: #c0c0c0;">时序稳定性、音视频同步、镜头叙事、可控性与部署形态</span>。</p>
<div class="blog_h3"><span class="graybg">为什么原生多模态更难</span></div>
<p>原生多模态的训练与部署成本明显高于连接式系统，原因并不神秘：它要处理的问题本来就更多。连接式 VLM 主要解决“视觉特征如何喂给 LLM”；原生系统还要额外处理时序同步、多模态采样率差异、输出链路异构、流式延迟、模态之间的训练平衡，以及更复杂的安全过滤。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">难点</td>
<td style="text-align: center;">为什么更难</td>
</tr>
</thead>
<tbody>
<tr>
<td>token / 序列爆炸</td>
<td>视频帧、语音片段和高分辨率图像会迅速放大上下文长度，训练和推理的注意力成本明显高于纯文本</td>
</tr>
<tr>
<td>跨模态同步</td>
<td>模型需要在内部保持“画面发生了什么”和“声音何时出现”之间的稳定对应，而不是事后补对齐</td>
</tr>
<tr>
<td>输出异构</td>
<td>文本、语音 codec、视觉 latent 的统计结构不同，统一输出链路远比单一文本解码复杂</td>
</tr>
<tr>
<td>训练配比</td>
<td>文本数据规模通常远大于高质量音视频数据，若配比失衡，模型会重新退回“文本中心”的偏态系统</td>
</tr>
<tr>
<td>服务复杂度</td>
<td>实时语音和视频生成要求更低延迟、更稳定的缓存和更强的流式调度，远重于普通文本 API</td>
</tr>
</tbody>
</table>
<p>这也是为什么连接式 VLM 仍然长期存在。它更容易复用成熟视觉编码器与 LLM，训练成本更低，部署链路更稳定。原生多模态代表的是能力上限更高的方向，而不是所有场景都必须立即切换到的唯一范式。</p>
<div class="blog_h2"><span class="graybg">图像与视频生成模型</span></div>
<p>图像与视频生成模型可以看作多模态生成的一条重要分支：输入通常是文本、图像或更复杂的条件信号，输出是图像或视频。它们处理的不是“模型看懂了什么”，而是“模型如何把条件约束转成可感知内容”。因此，它们和视觉-语言问答系统同属多模态，但核心目标不同。</p>
<div class="blog_h3"><span class="graybg">DALL-E / Imagen</span></div>
<p>DALL-E 与 Imagen 都属于文本到图像（Text-to-Image, T2I）生成路线的代表。工程上更关心的是文本-图像对齐、提示词控制、风格一致性与安全过滤，而不是单一的像素级指标。它们的共同点是：都把文本条件作为生成控制信号，让图像生成过程服从语言约束。</p>
<div class="blog_h3"><span class="graybg">Stable Diffusion</span></div>
<p>Stable Diffusion 则代表开源扩散模型（Diffusion Model）路线。它通常在潜空间（Latent Space）中进行扩散与去噪，再把潜变量解码回图像。它的工程意义尤其大，因为这条路线不仅支持文本生成图像，还容易叠加 LoRA、ControlNet、风格微调与本地部署，因此形成了非常完整的开源工作流生态。</p>
<div class="blog_h3"><span class="graybg">视频生成模型</span></div>
<p>视频生成比图像生成多了一个真正困难的维度：时间。模型不只要把单帧画出来，还要保证人物身份、动作轨迹、镜头运动、物理连续性和叙事节奏在多帧之间保持稳定。因此，视频生成天然更接近原生多模态问题，因为它几乎总要同时处理空间、时间，进一步还可能处理语音、音乐和环境音。</p>
<p>这一方向的前沿已经从“无声文本生视频”继续推进到<span style="background-color: #c0c0c0;">音视频联合生成</span>。Seedance 2.0 已经把文本、图像、音频和视频都纳入同一个参考与编辑框架，并支持多段素材混合输入、多镜头音视频输出和更稳定的复杂动作场景；这意味着视频模型开始更像“可编排的生成引擎”，而不只是一次性吐出一段短片。HappyHorse 1.0 则把另一条路线推到台前：以统一 Transformer 和开源权重为卖点，把文本/图片到视频、同步音频和多语言 lip-sync 打包成更容易部署的开源系统。</p>
<p>这两条路线各有强调。前者更重<span style="background-color: #c0c0c0;">多模态参考、编辑与专业创作控制</span>，后者更重<span style="background-color: #c0c0c0;">统一架构、开源可用性与推理效率</span>。但它们都在说明同一件事：视频生成的比较标准已经从“画得像不像”升级成“是否能稳定控制时间、镜头、声音和角色一致性”。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类别</td>
<td style="text-align: center;">代表</td>
<td style="text-align: center;">优势</td>
<td style="text-align: center;">常见用途</td>
</tr>
</thead>
<tbody>
<tr>
<td>商业 T2I</td>
<td>DALL-E / Imagen</td>
<td>效果稳定；产品化成熟</td>
<td>通用创作、商业生成、提示词控制</td>
</tr>
<tr>
<td>开源扩散</td>
<td>Stable Diffusion</td>
<td>可本地部署；可深度定制</td>
<td>LoRA、ControlNet、工作流编排、私有化部署</td>
</tr>
<tr>
<td>视频 / 音视频生成</td>
<td>Seedance 2.0、HappyHorse 1.0</td>
<td>同时建模时序、镜头、同步音频，以及更强的控制或部署能力</td>
<td>文本生视频、图像生视频、参考驱动编辑、原生音视频生成</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">语音模型</span></div>
<p>语音也是多模态体系中的重要一支。它一方面可以单独形成语音识别（ASR）和语音合成（TTS）任务，另一方面也可以作为更大多模态系统中的输入或输出模态，与文本和视觉共同构成统一交互链路。</p>
<div class="blog_h3"><span class="graybg">语音识别（ASR）</span></div>
<p>自动语音识别（Automatic Speech Recognition, ASR）把音频转成文本。Whisper 是这一方向的代表之一。它本身更接近“语音到文本”的专用模型，而不是完整的原生多模态基础模型；但它在工程上非常重要，因为许多语音助手和语音 Agent 系统的第一步，仍然是先把音频稳定转写为文本，再进入后续语言推理链路。</p>
<div class="blog_h3"><span class="graybg">语音合成（TTS）</span></div>
<p>语音合成（Text-to-Speech, TTS）则反过来把文本转成音频波形。工程关注点通常包括音色一致性（Voice Consistency）、韵律（Prosody）、延迟控制与多说话人能力。对多模态系统而言，TTS 的意义不只是“朗读文本”，而是把文本侧推理结果重新投射到语音模态，使系统具备完整的语音交互闭环。</p>
<div class="blog_h3"><span class="graybg">从语音流水线到统一语音-语言模型</span></div>
<p>传统语音系统更像流水线：ASR 先把音频转写成文本，语言模型基于文本做推理，最后 TTS 再把文本回答合成为语音。原生多模态路线则倾向于减少这种层层转译，尽量让语音中的情绪、停顿、说话风格和实时交互信号直接进入统一推理链路。也正因为如此，语音逐步从“单独外接模块”变成原生多模态系统中的一等模态。</p>
<div class="blog_h1"><span class="graybg">预训练与微调</span></div>
<p>对生成式大语言模型（Large Language Model, LLM）而言，训练通常不是一步完成，而是一个逐层收紧目标的三阶段过程。第一阶段是预训练（Pretraining）：让模型“学会语言”，掌握通用世界知识、语言统计规律与基础生成能力。第二阶段是监督微调（Supervised Fine-Tuning, SFT）：让模型“学会按指令做事”，把通用生成能力收束到更明确的任务格式、回复风格和指令遵循能力上。第三阶段是偏好对齐（Preference Alignment）：让模型“生成得更好”，在多个候选答案中更稳定地偏向人类认为更有帮助、更安全、更相关的输出。</p>
<p>这三步也对应了三种不同的模型状态。只做完预训练的模型通常称为基础模型（Base Model）：它拥有语言能力，但并不天然理解“用户现在真正想要什么”。经过监督微调后，模型开始具备指令理解与任务对齐能力，这一步常被称为指令微调（Instruction Tuning）。再往后，通过偏好对齐把模型行为进一步向人类目标、价值约束和回答偏好靠拢，这一步才构成更完整的对齐（Alignment）过程。当前主流通用助手模型，基本都建立在这条“预训练 → SFT → 偏好对齐”的标准范式之上。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">阶段</td>
<td style="text-align: center;">核心目标</td>
<td style="text-align: center;">主要训练信号</td>
<td style="text-align: center;">产出能力</td>
</tr>
</thead>
<tbody>
<tr>
<td>预训练（Pretraining）</td>
<td>学会语言与通用知识</td>
<td>大规模通用语料上的自监督目标，如 next-token prediction</td>
<td>基础语言建模能力，形成 Base Model</td>
</tr>
<tr>
<td>监督微调（SFT）</td>
<td>学会遵循指令与任务格式</td>
<td>高质量指令-回答、任务输入-输出配对数据</td>
<td>更稳定的指令理解、格式控制与领域适配能力</td>
</tr>
<tr>
<td>偏好对齐（Preference Alignment）</td>
<td>让输出更符合人类偏好</td>
<td>偏好排序、奖励模型（Reward Model）、RLHF / DPO 等对齐信号</td>
<td>更有帮助、更安全、更符合人类判断的回答</td>
</tr>
</tbody>
</table>
<p>从工程视角看，微调的本质不是重新训练一个模型，而是在保留预训练通用能力的前提下，对模型进行二次开发。领域适配、风格约束、指令遵循、安全边界和用户体验优化，几乎都发生在预训练之后。也正因为如此，微调并不是可有可无的附加步骤，而是把“会说话的模型”变成“可用的产品模型”的关键环节。</p>
<div class="blog_h2"><span class="graybg">模型训练经验法则</span></div>
<p>模型训练并不是所有因素等权叠加的黑箱过程。不同决策对结果的影响层级并不相同：有些因素决定性能上限，有些因素决定能否稳定逼近这个上限，还有些因素更多只影响复现实验时的波动。把这些层级看清，比机械扩大超参数搜索空间更重要。</p>
<div class="blog_h3"><span class="graybg">先看什么最决定结果</span></div>
<p>一个足够稳定的工程经验是：<span style="background-color: #c0c0c0;">数据定义与数据质量决定上限，训练目标决定模型究竟被要求学什么，模型架构决定它擅长表达什么，超参数主要决定训练能否稳定地逼近该上限，随机种子则决定同一方案在局部范围内会波动多少</span>。这五类因素都重要，但重要性的层级并不相同。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">因素</td>
<td style="text-align: center;">主要作用</td>
<td style="text-align: center;">更像决定上限还是逼近速度</td>
<td style="text-align: center;">典型失败形式</td>
</tr>
</thead>
<tbody>
<tr>
<td>数据质量与标注定义</td>
<td>决定模型到底能学到什么，以及标签边界是否自洽</td>
<td>更接近决定上限</td>
<td>噪声标签、分布偏移、长尾缺失、数据泄露使所有后续优化失真</td>
</tr>
<tr>
<td>训练目标 / 损失函数</td>
<td>决定模型被奖励什么、惩罚什么</td>
<td>同时影响上限与训练方向</td>
<td>目标错配导致模型学会与业务目标不一致的行为</td>
</tr>
<tr>
<td>模型架构</td>
<td>决定模型的归纳偏置、容量与可表达结构</td>
<td>更接近决定可达到的能力形态</td>
<td>用不合适的结构处理任务，容量再大也会吃亏</td>
</tr>
<tr>
<td>超参数与优化配置</td>
<td>决定训练是否稳定、是否高效收敛</td>
<td>更接近逼近速度与稳定性</td>
<td>学习率、batch、正则化失衡导致发散、欠收敛或过拟合</td>
</tr>
<tr>
<td>随机种子与运行噪声</td>
<td>决定实验波动与边缘差异能否复现</td>
<td>主要影响方差，不直接创造新能力</td>
<td>小数据任务里不同 seed 结果差异过大，误判方案优劣</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">数据优先于调参</span></div>
<p>在大多数真实任务里，数据问题比超参数问题更早构成瓶颈。标注标准不一致、负样本定义含混、类别长尾覆盖不足、训练集与线上分布偏移、重复样本过多、低信息密度样本占比过高，这些问题会直接抬低性能天花板。此时即使继续精细搜索学习率、dropout 或 warmup 步数，得到的往往也只是对现有缺陷数据的更精致拟合。</p>
<p>因此，训练停滞时最先应排查的通常不是“还有没有更神秘的超参数组合”，而是以下四件事：监督信号是否可信，训练数据是否覆盖真实决策边界，验证集是否真的代表部署分布，以及坏例是否能被归纳成明确的数据缺口。若这些环节存在结构性问题，数据集工程（Dataset Engineering）通常比额外的超参数搜索更有价值。</p>
<div class="blog_h3"><span class="graybg">传统模型与 Transformer 的影响力分布</span></div>
<p>不同模型家族里，各因素的影响力分布并不完全一样。传统机器学习模型更依赖显式特征与模型假设是否匹配；Transformer 一类现代深度模型则更依赖数据规模、训练目标与训练配方是否成熟。两者的优化重点不能混成一套。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">因素</td>
<td style="text-align: center;">传统机器学习 / 早期深度模型</td>
<td style="text-align: center;">Transformer / 大模型体系</td>
</tr>
</thead>
<tbody>
<tr>
<td>数据</td>
<td>高质量特征与标签定义最关键；样本量常比模型复杂度更早成为瓶颈</td>
<td>数据规模、质量、清洗、去重与配比直接决定基座能力分布，影响通常最大</td>
</tr>
<tr>
<td>架构</td>
<td>模型假设差异很大，线性模型、树模型、核方法之间的选型影响巨大</td>
<td>同一家族成熟配方下，架构差异仍重要，但往往弱于数据分布与训练目标的主导作用</td>
</tr>
<tr>
<td>损失函数 / 目标函数</td>
<td>对分类、排序、回归、异常检测等任务边界影响直接，错配会立刻拉低效果</td>
<td>预训练目标、对齐目标和辅助损失的设计会深刻塑造能力形态，影响非常大</td>
</tr>
<tr>
<td>超参数</td>
<td>常更敏感，尤其是 SVM、Boosting、早期 RNN / CNN 等体系</td>
<td>现代 Transformer 在成熟优化器、残差连接和归一化配方下通常更宽容，但学习率、batch、warmup、weight decay 仍然是高杠杆参数</td>
</tr>
<tr>
<td>随机种子</td>
<td>在小数据、非凸训练或高方差集成场景中影响明显</td>
<td>大规模预训练中单次 seed 影响相对被平均，但小样本微调、PEFT 和 few-shot 任务中仍可能显著波动</td>
</tr>
</tbody>
</table>
<p>对传统模型而言，常见主线是“先把特征、标签定义和模型假设对齐，再调超参数”；对 Transformer 而言，常见主线更接近“先把数据、目标和基座路线做对，再用一套成熟训练配方稳定落地”。因此，现代大模型工程里最常见的成功路径，并不是超参数盲搜，而是先建立可信基线，再围绕数据与目标做高价值迭代。</p>
<div class="blog_h3"><span class="graybg">训练过程监控</span></div>
<p>训练是否健康，不能只看“loss 最后降到了多少”。真正有经验的训练监控，关注的是<span style="background-color: #c0c0c0;">曲线形态、不同指标之间是否彼此一致，以及这些指标是否在给出同一种信号</span>。例如训练损失下降但验证损失持续恶化，说明问题往往不在“模型没学到”，而在泛化；学习率正常但梯度范数突然飙升，则更像数值稳定性问题。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">监控项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>训练损失（Training Loss）<br />常见名称：<pre class="crayon-plain-tag">loss</pre>、<pre class="crayon-plain-tag">train_loss</pre></td>
<td>反映模型在当前训练批次或当前 epoch 上的拟合误差。它通常应随训练推进整体下降，但短期抖动是正常现象。若长期不降，常见原因是学习率过低、数据管线异常、标签错误或目标函数设置不当。</td>
</tr>
<tr>
<td>验证损失（Validation Loss）<br />常见名称：<pre class="crayon-plain-tag">val_loss</pre>、<pre class="crayon-plain-tag">eval_loss</pre></td>
<td>反映模型在未参与参数更新的数据上的误差，是观察泛化状态的核心指标之一。训练损失下降而验证损失持续升高，通常意味着过拟合、分布不一致，或训练目标与评估目标之间存在错配。</td>
</tr>
<tr>
<td>验证指标（Validation Metric）<br />常见名称：<pre class="crayon-plain-tag">val_accuracy</pre>、<pre class="crayon-plain-tag">eval_f1</pre>、<pre class="crayon-plain-tag">val_auc</pre>、<pre class="crayon-plain-tag">eval_bleu</pre></td>
<td>用于直接衡量任务目标是否改善。分类任务常看 Accuracy、F1、AUC，生成任务常看 BLEU、ROUGE、Win Rate 或人工偏好指标。它与验证损失应结合解读，因为 loss 变差而任务指标稳定，往往意味着概率校准恶化而非决策边界崩溃。</td>
</tr>
<tr>
<td>Token 级准确率（Token-level Accuracy）<br />常见名称：<pre class="crayon-plain-tag">token_accuracy</pre>、<pre class="crayon-plain-tag">eval_token_acc</pre>、<pre class="crayon-plain-tag">eval_mean_token_accuracy</pre></td>
<td>衡量验证集上 token 级别的预测正确率，常用于自回归语言模型、CoT 生成任务和结构化生成任务。它比整序列 loss 更贴近“模型是否持续生成更多正确 token”，尤其适合观察答案 token 或关键推理片段是否在变好；但它仍然不能完全替代任务级正确率，因为不同等价表达、长度差异与标点格式都可能影响统计结果。</td>
</tr>
<tr>
<td>学习率（Learning Rate）<br />常见名称：<pre class="crayon-plain-tag">lr</pre>、<pre class="crayon-plain-tag">learning_rate</pre></td>
<td>表示当前优化步长，是判断训练是否按预期执行 warmup、衰减或重启调度的关键参考。许多“训练突然变坏”的问题，本质上不是模型结构问题，而是学习率曲线与预期不符，例如 warmup 未生效、scheduler 配错或恢复训练时学习率状态丢失。</td>
</tr>
<tr>
<td>梯度范数（Gradient Norm）<br />常见名称：<pre class="crayon-plain-tag">grad_norm</pre>、<pre class="crayon-plain-tag">gradient_norm</pre></td>
<td>表示当前一步所有可训练参数梯度向量的 L2 范数，也就是“这一步模型想更新多大幅度”。它稳定在合理范围通常说明训练平稳；若突然飙到很大，常提示梯度爆炸、异常 batch 或数值不稳定；若长期极小，则可能是梯度消失、学习率过低或大部分参数几乎没有被更新。</td>
</tr>
<tr>
<td>参数范数（Parameter Norm / Weight Norm）<br />常见名称：<pre class="crayon-plain-tag">param_norm</pre>、<pre class="crayon-plain-tag">weight_norm</pre></td>
<td>反映模型权重整体尺度。它不像梯度范数那样高频用于日常看板，但在长训练或大模型微调中很有用。若参数范数异常膨胀，常提示权重衰减过弱、优化不稳或 logit scale 正在失控。</td>
</tr>
<tr>
<td>步耗时（Step Time / Iteration Time）<br />常见名称：<pre class="crayon-plain-tag">step_time</pre>、<pre class="crayon-plain-tag">iter_time</pre>、<pre class="crayon-plain-tag">time_per_step</pre></td>
<td>表示每一步训练耗时，是判断吞吐和系统瓶颈的重要指标。若步耗时突然上升，常见原因包括数据加载阻塞、远程存储抖动、NCCL 通信问题、动态 padding 失控或某类样本序列长度突然变长。</td>
</tr>
<tr>
<td>吞吐量（Throughput）<br />常见名称：<pre class="crayon-plain-tag">samples_per_second</pre>、<pre class="crayon-plain-tag">tokens_per_second</pre>、<pre class="crayon-plain-tag">it/s</pre></td>
<td>反映单位时间内处理的数据量，是评估训练效率和成本的核心指标。吞吐下降而 loss 曲线形态不变，通常说明问题在系统层；吞吐下降且梯度统计异常，则可能是训练图、batch 组成或显存压力同时发生了变化。</td>
</tr>
<tr>
<td>显存占用（GPU Memory Usage）<br />常见名称：<pre class="crayon-plain-tag">gpu_memory</pre>、<pre class="crayon-plain-tag">memory_allocated</pre>、<pre class="crayon-plain-tag">max_memory_reserved</pre></td>
<td>用于观察 batch size、序列长度、激活检查点、KV 缓存或 mixed precision 设置是否按预期生效。显存占用逐步爬升且不回落，往往提示内存泄漏、缓存未释放、日志持有张量引用，或某些框架回调在累积中间状态。</td>
</tr>
<tr>
<td>数值稳定性信号（Numerical Stability）<br />常见名称：<pre class="crayon-plain-tag">loss_scale</pre>、<pre class="crayon-plain-tag">overflow</pre>、<pre class="crayon-plain-tag">nan_count</pre></td>
<td>在混合精度训练里尤其重要。若频繁出现 overflow、loss_scale 持续下降，或日志中出现 NaN/Inf，问题通常来自学习率过高、梯度异常、算子数值不稳或输入样本存在极端值。它们往往是训练真正崩坏之前最早出现的预警信号。</td>
</tr>
</tbody>
</table>
<p>其中，<span style="background-color: #c0c0c0;">grad_norm</span> 常被误读成“某层梯度大小”或“loss 的导数值”。更准确地说，它是当前这一步所有可训练参数梯度拼成一个大向量后的 L2 范数，因此表示的是<span style="background-color: #c0c0c0;">整体更新冲动的强弱</span>。若训练初期较大、随后回落并稳定在适中区间，通常是健康现象；若突然从稳定区间跃升到极大值，则应优先排查异常 batch、学习率、混合精度溢出与梯度裁剪是否失效。</p>
<p>一条实用经验是：不要孤立解读单个监控项。训练健康与否，通常要联立观察“训练损失、验证损失、验证指标、学习率、梯度范数、吞吐和显存”这几条曲线。只有当这些信号彼此一致时，结论才可靠；若它们相互矛盾，真正的问题往往就藏在这种矛盾里。</p>
<div class="blog_h3"><span class="graybg">Epoch 数与训练步数如何判断</span></div>
<p>一个极常见的问题是：如果训练集已经很大，例如有 200 万条样本，是否 1 个 epoch 就一定够了。答案是否定的。<span style="background-color: #c0c0c0;">epoch 数从来不是脱离任务、模型容量、batch size 和学习率调度单独成立的“固定真理”</span>。数据量很大时，重复看同一批数据的边际收益确实会下降；但这并不自动意味着“只看一遍就已经充分收敛”。真正该判断的，不是样本数本身，而是模型在当前 step 数、当前噪声水平和当前优化配方下，是否已经把这批数据中的稳定规律吸收进去。</p>
<p>从优化角度看，epoch 表示“把整个训练集完整扫过几遍”，而训练真正发生更新的基本单位是 step。若训练集大小为 <span displaypfx="inline-" class="mathjax-container">\(N\)</span>，全局 batch size 为 <span displaypfx="inline-" class="mathjax-container">\(B\)</span>，则每个 epoch 的更新步数近似为</p>
<span displaypfx="" class="mathjax-container">\[\text{steps per epoch}\approx \left\lceil \frac{N}{B}\right\rceil\]</span>
<p>这条式子解释了为什么“同样是 1 个 epoch”，训练强度可能完全不同。若 <span displaypfx="inline-" class="mathjax-container">\(N=2{,}000{,}000\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(B=2{,}048\)</span>，那么 1 个 epoch 约等于 977 步；若 <span displaypfx="inline-" class="mathjax-container">\(B=128\)</span>，则 1 个 epoch 已经接近 15625 步。前者可能连 warmup 和衰减都还没完全展开，后者却已经给了优化器大量迭代机会。因此，真正需要关注的是<span style="background-color: #c0c0c0;">总更新步数是否足够、学习率曲线是否有足够空间完成收敛</span>，而不是孤立地看 epoch 数字本身。</p>
<div class="blog_h4"><span class="graybg">什么时候 1 个 epoch 可能已经够了</span></div>
<p>当数据量相对模型容量已经非常充足，或者任务本身建立在强预训练基座之上时，1 个 epoch 确实可能足够。最典型的例子是大语言模型预训练和大模型微调。预训练阶段若数据规模巨大，重复多次扫同一语料的收益会迅速递减，甚至更容易把模型推向记忆高频样本而不是继续扩大有效覆盖；微调阶段若基座已经很强，而 200 万样本又足够多，1 到 2 个 epoch 往往已经能把任务信号写进模型。</p>
<p>这种情形之所以成立，不是因为“大数据天然只需要 1 个 epoch”，而是因为模型在第一轮扫描中就已经见到了足够多的新模式，额外重复的边际知识增量开始下降。换一个表述，真正够用的不是“1 个 epoch”这个数字，而是“这 1 个 epoch 已经包含足够多有效、不同且信息密度高的更新”。</p>
<div class="blog_h4"><span class="graybg">什么时候多几个 epoch 仍然有必要</span></div>
<p>相反，即使样本数已经达到百万级，多 epoch 仍然可能有必要。第一种情形是模型容量很大、任务本身又不只是表面分类，而需要模型充分吸收细粒度规律；此时 1 个 epoch 可能只是让模型“看过”，还未真正“消化”。第二种情形是 batch size 很大，导致每个 epoch 的 step 数偏少，学习率调度甚至还没完全展开，训练就已经结束。第三种情形是数据噪声较大或任务边界本身较模糊，多几轮迭代有助于模型在不同 mini-batch 组合中逐步稳定参数，而不是过度受一次随机采样影响。</p>
<p>从优化器视角看，多 epoch 的价值不只是“重复看数据”，还包括让随机梯度下降在更多 mini-batch 路径上反复修正参数。一次 epoch 中，每个样本通常只参与一次梯度更新，但 SGD 的更新方向本身带噪声；多轮迭代使优化器有机会在不同 batch 组合下持续磨平这种噪声，更接近一个稳定解。</p>
<div class="blog_h4"><span class="graybg">真正的权衡：利用率与过拟合</span></div>
<p>多跑 epoch 的好处是提高数据利用率，坏处则是重复过度后更容易过拟合。这里的过拟合并不一定表现为“训练损失不再下降”，更常见的是训练损失持续变好，而验证损失、验证指标或校准状态开始恶化。对去重不充分的数据，这个问题会更严重：重复样本本身已经降低了有效样本量，再叠加多 epoch，就会把模型进一步推向记忆。</p>
<p>因此，epoch 数的合理区间通常不是越大越好，而是存在一个任务相关的甜蜜点。预训练里这个甜蜜点往往很靠前，因为数据足够大、重复收益低；中小模型从头训练时甜蜜点通常更靠后，因为模型需要更多轮次才能把有限数据中的模式真正吃透；大模型微调和 SFT 则常介于二者之间。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">场景</td>
<td style="text-align: center;">更常见的 epoch 区间</td>
<td style="text-align: center;">为什么</td>
</tr>
</thead>
<tbody>
<tr>
<td>大规模预训练</td>
<td>常接近 1，或不足 1 到 2</td>
<td>数据覆盖极广，重复收益下降快，更看重新 token 覆盖而非反复回看</td>
</tr>
<tr>
<td>大模型监督微调 / 指令微调</td>
<td>常见 1 到 3</td>
<td>基座能力已强，过多重复更容易带来风格过拟合、遗忘或行为退化</td>
</tr>
<tr>
<td>分类 / NER / 中等规模任务微调</td>
<td>常见 2 到 5</td>
<td>需要多轮收敛，但又必须严防验证集开始转坏</td>
</tr>
<tr>
<td>中小模型从头训练</td>
<td>常明显高于 5，甚至更多</td>
<td>模型需要靠多轮迭代逐步建立表示，单轮往往不够吸收规律</td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">最后仍然要看验证曲线</span></div>
<p>所有经验法则最终都应回到验证集信号。若训练损失持续下降，而验证损失 <span displaypfx="inline-" class="mathjax-container">\(\texttt{val\_loss}\)</span> / <span displaypfx="inline-" class="mathjax-container">\(\texttt{eval\_loss}\)</span> 不再下降甚至开始上升，就说明继续增加 epoch 很可能只是在拟合训练集细节。相反，若训练到 1 个 epoch 时验证损失和验证指标仍在稳定改善，那么因为“样本很多”就强行提前停止，反而可能让模型欠收敛。</p>
<pre class="crayon-plain-tag">Epoch 1: train_loss=2.1  val_loss=2.3
Epoch 2: train_loss=1.8  val_loss=2.0
Epoch 3: train_loss=1.5  val_loss=1.9
Epoch 4: train_loss=1.2  val_loss=2.0</pre>
<p>这类曲线就是最典型的判断依据。前 3 个 epoch 中，训练损失与验证损失同步下降，说明模型既在学习训练集，也在改善泛化；到第 4 个 epoch，虽然 <pre class="crayon-plain-tag">train_loss</pre> 还在继续下降，但 <pre class="crayon-plain-tag">val_loss</pre> 已经从 1.9 回升到 2.0，信号已经转向“继续记住训练细节，而不是继续学到可泛化规律”。这时更合理的做法通常是在第 3 个 epoch 附近 early stop，或回滚到验证集表现最好的 checkpoint。</p>
<p>上文已经系统列过训练监控项，这里只强调一个更容易被误用的原则：<span style="background-color: #c0c0c0;">early stopping 的判断不能死板地绑定单一指标</span>。停止准则必须跟任务目标、模型架构和损失函数对齐。对 BERT 一类 Encoder-only 模型的二分类任务，若最终部署关心的是固定阈值下的分类正确率、召回率或 F1，而不是输出概率本身是否校准，那么 checkpoint 选择往往应优先盯住 <pre class="crayon-plain-tag">val_f1</pre> 或 <pre class="crayon-plain-tag">val_acc</pre>，而不能仅凭 <pre class="crayon-plain-tag">val_loss</pre> 上升就立刻停止。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">Epoch</td>
<td style="text-align: center;">train_loss</td>
<td style="text-align: center;">val_loss</td>
<td style="text-align: center;">val_acc</td>
<td style="text-align: center;">val_f1</td>
<td style="text-align: center;">解读</td>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>0.4106</td>
<td>0.3122</td>
<td>87.1%</td>
<td>0.8994</td>
<td>首个可用 checkpoint</td>
</tr>
<tr>
<td>2</td>
<td>0.2878</td>
<td>0.2461</td>
<td>90.8%</td>
<td>0.9250</td>
<td>验证指标同步改善</td>
</tr>
<tr>
<td>3</td>
<td>0.1914</td>
<td>0.3646</td>
<td>90.0%</td>
<td>0.9165</td>
<td><pre class="crayon-plain-tag">val_loss</pre> 反弹，但任务指标只是小幅震荡</td>
</tr>
<tr>
<td>4</td>
<td>0.1059</td>
<td>0.4341</td>
<td>91.4%</td>
<td>0.9314</td>
<td><pre class="crayon-plain-tag">val_f1</pre> 创新高，应保存</td>
</tr>
</tbody>
</table>
<p>这类曲线在二分类里并不少见。若损失函数采用二元交叉熵（Binary Cross-Entropy, BCE）或交叉熵（Cross-Entropy），那么 <pre class="crayon-plain-tag">val_loss</pre> 衡量的是<span style="background-color: #c0c0c0;">概率分布与标签的一致性</span>，对“模型是否越来越自信”极其敏感；而 <pre class="crayon-plain-tag">val_f1</pre> 衡量的是阈值后的离散决策结果。模型进入后期时，logit 可能变得更极端，导致概率校准变差、损失上升，但只要决策边界仍在朝正确方向移动，F1 仍然可能继续提升。</p>
<p>因此，在这类 BERT 二分类任务中，更合理的策略通常是：把 early stopping 的耐心值绑定在 <pre class="crayon-plain-tag">val_f1</pre>、<pre class="crayon-plain-tag">val_acc</pre> 或业务主指标上，而把 <pre class="crayon-plain-tag">val_loss</pre> 主要当作校准与过度自信的辅助信号。只有当任务需要直接使用概率分数做路由、排序、拒识或温度缩放校准时，<pre class="crayon-plain-tag">val_loss</pre> 才应重新上升为主导停止准则。</p>
<p>在 CoT（Chain-of-Thought）生成任务里，这个问题会更明显。更常见的情况是 <pre class="crayon-plain-tag">eval_loss</pre> 覆盖的是整条推理链与答案 token 的交叉熵；而推理链本身往往存在大量等价表达，写法稍有变化就会改变 loss，却未必伤害最终答案质量。于是就会出现这样一种典型背离：<pre class="crayon-plain-tag">eval_loss</pre> 在后期微涨，但 <pre class="crayon-plain-tag">token_accuracy</pre>、答案正确率或下游分类指标仍继续上升。例如在某次早停patience=1的训练任务中：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">最佳 <pre class="crayon-plain-tag">eval_loss</pre></td>
<td style="text-align: center;">后续现象</td>
<td style="text-align: center;">更合理的解读</td>
</tr>
</thead>
<tbody>
<tr>
<td>0.8B / 2B / 4B / 9B 同组 CoT 训练</td>
<td>普遍在 epoch 3 附近达到最低</td>
<td>epoch 4 时 <pre class="crayon-plain-tag">eval_loss</pre> 只微涨 0.01 到 0.02，但 <pre class="crayon-plain-tag">token_accuracy</pre> 仍继续上升</td>
<td>更像早停准则过紧，或停止指标与任务目标错配，而不是已经进入明显过拟合</td>
</tr>
</tbody>
</table>
<p>这类现象尤其容易误伤大模型或较晚才兑现能力的配置。若 9B 模型的绝对 <pre class="crayon-plain-tag">eval_loss</pre> 在所有尺寸中最低，却因为 <pre class="crayon-plain-tag">patience=1</pre> 且过度依赖 <pre class="crayon-plain-tag">eval_loss</pre> 而被提前截断，那么“9B 没赢 4B”并不能直接推出“9B 欠容量”。更稳妥的解释，往往是停止准则先砍掉了它后续 1 到 2 个 epoch 的收益。</p>
<p>因此，还要把三个容易混淆的判断分开。<span style="background-color: #c0c0c0;">欠拟合（Underfitting）</span> 是现象层描述，表示训练集和验证集都做不好；<span style="background-color: #c0c0c0;">欠容量（Undercapacity）</span> 是成因层判断，表示模型或可训练适配器的表达能力确实不够，导致训练误差下不去；而<span style="background-color: #c0c0c0;">停止准则误判</span> 则是监控层问题，表示模型本来还在朝任务目标变好，却被不合适的指标提前停掉。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">状态</td>
<td style="text-align: center;">常见信号</td>
<td style="text-align: center;">更像该怎么处理</td>
</tr>
</thead>
<tbody>
<tr>
<td>欠拟合</td>
<td>训练集和验证集指标都偏低，二者差距不大；继续训练短时间内也上不去</td>
<td>优先查训练是否充分、特征是否有效、目标是否合理</td>
</tr>
<tr>
<td>欠容量</td>
<td>训练 loss 本身下不去，训练指标长期受限；增大模型尺寸、LoRA rank 或可训练模块后，训练集与验证集一起改善</td>
<td>增加表达能力，或放宽可训练参数写入范围</td>
</tr>
<tr>
<td>停止准则误判</td>
<td><pre class="crayon-plain-tag">eval_loss</pre> 微涨，但 <pre class="crayon-plain-tag">token_accuracy</pre>、F1 或下游任务指标仍继续上升</td>
<td>放宽 <pre class="crayon-plain-tag">patience</pre>，并把 early stopping 绑定到真正代表任务成败的指标</td>
</tr>
</tbody>
</table>
<p>因此，更可靠的工程结论不是“200 万数据就跑 1 个 epoch”或“必须至少跑 3 个 epoch”，而是：先根据任务类型给出一个合理初值，再把停止准则绑定到真正代表任务成败的验证信号上。换句话说，<span style="background-color: #c0c0c0;">epoch 只是外层计数单位，停止条件必须服务于任务目标，而不是机械服从某一条曲线</span>。</p>
<div class="blog_h3"><span class="graybg">超参数的正确投入边界</span></div>
<p>超参数仍然重要，但它们最主要的作用通常是把一个合理方案从“训不动”推到“训得稳”，再从“训得稳”推到“训得更高效”，而不是凭空创造数据与目标之外的新能力。学习率、batch size、warmup、weight decay、dropout、梯度裁剪与调度策略都属于高杠杆参数，因为它们直接影响优化轨迹；但一旦进入成熟配方附近，继续做大规模穷举搜索，边际收益往往迅速下降。</p>
<p>这也是为什么工程上更强调<span style="background-color: #c0c0c0;">经验调参（Empirical Baselines）</span>。先从社区已验证的默认范围起步，再围绕少数真正高杠杆参数做小范围、可解释的搜索，通常比无约束网格搜索更高效，也更不容易把验证集偶然性误判成模型能力提升。若一个方案必须依赖极其苛刻的超参数才能成立，它通常也更难在新数据、新种子和新硬件上稳定复现。</p>
<div class="blog_h3"><span class="graybg">随机种子、运气与实验纪律</span></div>
<p>随机种子不是主导因素，但也绝不是噪声到可以忽略的细节。初始化、数据打乱顺序、负样本采样、dropout 路径和混合精度下的数值非确定性，都会让两次训练出现差异。数据越少、任务越难、可训练参数越多，这种波动通常越明显。小样本微调、few-shot 提示学习、PEFT 与不平衡分类任务，都经常出现“只换一个 seed，指标就有明显起伏”的现象。</p>
<p>因此，实验纪律至少应包括三点：第一，重要结论不要基于单次运行下判断；第二，对关键方案记录 seed、数据切分、依赖版本和硬件环境；第三，若不同 seed 方差已经接近方案之间的提升幅度，就不应把这点差异解读为方法突破。工程上真正可信的改进，应当在多次运行、不同切分或更接近真实分布的验证下仍能站住。</p>
<div class="blog_h3"><span class="graybg">一条实用顺序</span></div>
<p>若把训练优化压缩成一条实用顺序，通常是：先校正任务定义与评估指标，再修数据分布与标签质量，然后确定模型家族与训练目标，接着使用成熟经验配方建立基线，最后才对少数关键超参数做有节制的搜索。这个顺序并不保证一步到位，但它能最大限度避免把大量算力消耗在低回报环节上。</p>
<div class="blog_h2"><span class="graybg">预训练（Pre-training）</span></div>
<div class="blog_h3"><span class="graybg">数据收集、清洗与配比</span></div>
<p>预训练首先是一条数据管线（Data Pipeline）。对大语言模型而言，模型最终学到什么、遗漏什么、偏向什么，首先取决于它看到了哪些语料，以及这些语料在进入训练前经历了怎样的筛选、清洗与混合。训练配方当然重要，但在大规模预训练里，<span style="background-color: #c0c0c0;">数据分布本身往往比单个优化技巧更具决定性</span>。</p>
<p>第一步通常是收集训练数据。来源往往包括公开网页、百科、书籍、论文、新闻、论坛、代码仓库、问答站点、对话语料以及经过授权的专有文本。不同来源的作用并不相同：网页和百科提供广覆盖的语言统计与世界知识，代码语料强化程序生成与形式化模式，论文和书籍提升长程结构与知识密度，对话数据则更贴近后续助手形态。预训练阶段谈“知识注入”，最底层的载体首先就是这些原始语料源。</p>
<p>第二步是数据清洗（Data Cleaning）。原始互联网语料通常充满模板页、导航栏、广告、乱码、截断文本、语言混杂、低信息密度页面和大规模重复内容，直接拿来训练只会把噪声写进模型。常见清洗动作包括：语言识别、文本抽取、HTML / Markdown 噪声剥离、异常字符过滤、长度过滤、文档质量评分、敏感内容过滤，以及近重复或完全重复文档去除。它的目的不是把数据“洗得越干净越好”，而是把明显无价值、重复或高风险的部分尽量挡在训练集之外。</p>
<p>第三步是数据去重与质量过滤。对现代大模型来说，重复数据并不只是浪费 token 预算，还会放大训练分布中的头部模式，使模型更容易过拟合少数高频模板、降低有效数据多样性，并污染后续评测。于是，工程上通常既要做文档级去重，也要做段落级、片段级甚至近似语义去重；同时配合质量分类器、启发式规则或小模型过滤，把低信息密度、机器生成垃圾、SEO 内容农场和错误密集文本压低占比。</p>
<p>第四步是数据配比（Data Mixture）。预训练通常不是把所有清洗后的数据简单拼接后均匀抽样，而是会显式控制不同来源、语言、领域和模态的采样比例。原因在于：不同语料的规模差异极大，若完全按原始数量采样，网页噪声和头部来源往往会淹没更高质量但规模更小的数据，例如书籍、论文和代码。数据配比的本质，是决定模型应该把多少训练预算分配给广覆盖、多少分配给高质量、多少分配给特定能力方向。</p>
<p>这种配比通常带来直接的能力权衡。代码比例升高，模型的程序生成和形式化推理往往更强，但自然语言对话风格未必同步变好；高质量书面语比例升高，模型的行文稳定性和知识密度往往改善，但口语互动和开放域覆盖可能下降；多语比例升高，则跨语言泛化更强，但单语极致性能未必最优。因此，数据配比并不是纯粹的数据工程细节，而是预训练目标函数之外最重要的能力分配器之一。</p>
<p>第五步才是把整理后的语料送入真正的训练阶段。也正因为前面已经做过收集、清洗、去重和配比，后面的“初期训练、中期训练、退火训练”才有明确的数据基础：初期通常强调大规模广覆盖混合，中后期再逐步提高高质量数据、特定能力语料或长上下文样本的权重。换句话说，阶段化训练并不是独立于数据工程存在的，它本身就是建立在<span style="background-color: #c0c0c0;">先构造可控数据分布，再按阶段调整采样分布</span>这一前提之上。</p>
<div class="blog_h3"><span class="graybg">阶段化训练与知识注入</span></div>
<p>现代大语言模型的预训练，通常不是把同一种数据、同一种上下文长度和同一组优化超参数一路跑到结束，而更接近一种分阶段课程学习（Curriculum Training）。所谓“知识注入”，本质上也不是把某条事实单独写入参数，而是通过逐步调整数据分布、上下文长度、学习率和训练目标，让模型先建立通用语言统计骨架，再吸收更高质量、更长程或更专业的模式。</p>
<p>工程上常见的三段式可以概括为：</p>
<ul>
<li><span style="background-color: #c0c0c0;">初期训练</span>。这一阶段通常以海量、多样、相对较短的上下文为主，重点是尽快建立词法、句法、语义组合、事实共现与基础推理的统计骨架。之所以大量使用短上下文，是因为在标准注意力下，序列长度增加会显著抬高训练成本；在固定算力预算下，较短序列通常能换来更多 token 更新和更稳定的早期收敛。</li>
<li><span style="background-color: #c0c0c0;">中期训练（Mid-training）</span>。当模型已经具备基本语言能力后，训练重点会从“广覆盖”逐步转向“高价值分布塑形”。这一阶段更常看到更严格过滤的高质量语料、代码、推理数据、专业领域语料，或逐步扩展的上下文长度。它的作用不是重学语言本身，而是把模型的能力重心推向更有用的区域，例如更强的代码能力、更稳的长程依赖、更贴近目标领域的表达分布。</li>
<li><span style="background-color: #c0c0c0;">退火训练（Annealing Phase）</span>。这是预训练后段的精修阶段，通常伴随更小的学习率、更保守的更新幅度，以及更精选、更低噪声的数据混合。它的目标不是再靠大步更新去扩张知识覆盖面，而是收束参数、压低噪声影响、强化高质量模式，并把模型最终的能力形态稳定下来。很多现代配方会把更专业或更高质量的数据留到这一阶段，以获得更好的下游表现。</li>
</ul>
<p>从“注入什么知识”的角度看，这三段关注的重点并不相同。初期训练主要注入广覆盖的语言统计、世界常识共现和通用结构先验；中期训练主要注入能力相关的分布偏好，例如代码、推理、长文档和领域语料；退火训练则更像把高价值知识和高质量行为模式做最后收束，使模型从“已经学会很多”走向“把重要能力学得更稳”。</p>
<p>长上下文能力也常在这一框架下被放到中后期处理。原因并不神秘：长上下文训练既昂贵，又更容易让优化目标与数据工程复杂化；如果在模型尚未建立稳定短程语言骨架时就大规模拉长序列，单位算力的有效学习信号往往并不划算。因此，很多训练配方会先用短上下文把基础能力打牢，再在中后段逐步扩展到更长上下文，或者单独追加一段上下文扩展训练。</p>
<p>因此，预训练阶段谈“知识注入”时，更准确的理解不是一次性灌入，而是<span style="background-color: #c0c0c0;">按训练阶段逐步改变模型看到的分布与约束条件</span>：先学会语言，再学会更有价值的语言分布，最后把这些能力收束成一个更稳定的基座模型。</p>
<div class="blog_h3"><span class="graybg">自回归语言建模（CLM）</span></div>
<p>自回归语言建模（Causal Language Modeling, CLM）把文本建模为从左到右的条件概率连乘：给定前缀 <span displaypfx="inline-" class="mathjax-container">\(x_{&lt;t}\)</span> 预测下一个 token <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span>。训练目标是最小化 next-token 交叉熵：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{\mathrm{CLM}}(\theta)=-\sum_{t=1}^{L}\log p_\theta(x_t\mid x_{&lt;t})\]</span>
<p>CLM 与 Decoder-only 架构天然匹配：因果 attention mask 保证模型只能看见历史 token，避免训练-推理不一致。绝大多数通用生成式大模型都以 CLM 为主目标。</p>
<div class="blog_h4"><span class="graybg">Multi-Token Prediction（MTP）</span></div>
<p>多 token 预测（Multi-Token Prediction, MTP）是在 CLM 基础上的“监督信号加密”：除了预测 <span displaypfx="inline-" class="mathjax-container">\(x_{t+1}\)</span>，还额外让模型在同一隐藏状态上预测更远的未来 token（例如 <span displaypfx="inline-" class="mathjax-container">\(x_{t+2}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(x_{t+3}\)</span>），从而在相同序列长度下产生更多训练信号。一个抽象写法是：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{\mathrm{MTP}}(\theta)=-\sum_{t=1}^{L}\sum_{j=1}^{K}\log p_\theta(x_{t+j}\mid x_{&lt;t})\]</span>
<p>MTP 通常作为辅助损失（Auxiliary Loss）提升训练效率或长程规划能力；但推理阶段是否能“真正一次生成多个 token”取决于解码与验证策略，不能简单由训练目标推出。</p>
<div class="blog_h3"><span class="graybg">掩码语言建模（MLM）</span></div>
<p>掩码语言建模（Masked Language Modeling, MLM）随机遮住输入中的一部分 token（替换为 <span displaypfx="inline-" class="mathjax-container">\([\mathrm{MASK}]\)</span> 或其他扰动），训练模型用双向上下文预测被遮住位置的 token。它是 Encoder-only 表示模型（如 BERT 系列）的典型预训练目标：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{\mathrm{MLM}}(\theta)=-\sum_{t\in \mathcal{M}}\log p_\theta(x_t\mid x_{\setminus \mathcal{M}})\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{M}\)</span> 是被 mask 的位置集合。MLM 的优势是能学到更强的双向表示，但它与生成式解码不天然一致，因此“理解类任务”更常用 MLM 预训练模型，“生成类任务”更常用 CLM。</p>
<div class="blog_h3"><span class="graybg">对比学习预训练</span></div>
<p>对比学习预训练（Contrastive Pre-training）把“相似样本拉近、非相似样本推远”作为核心目标。它广泛用于句向量/图像-文本对齐等场景：例如 CLIP 用图像编码器与文本编码器产生表示，对匹配对最大化相似度；Sentence-BERT 等句向量模型也常用对比目标训练。</p>
<p>典型形式是 InfoNCE：对 batch 内正对（Positive Pair）与负对（Negative Pair）做 softmax，对每个 query 只奖励其匹配的 key：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}=-\sum_{i}\log \frac{\exp(\mathrm{sim}(q_i,k_i)/\tau)}{\sum_{j}\exp(\mathrm{sim}(q_i,k_j)/\tau)}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\tau\)</span> 是温度（Temperature），<span displaypfx="inline-" class="mathjax-container">\(\mathrm{sim}\)</span> 常用余弦相似度或内积。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">目标</td>
<td style="text-align: center;">代表架构</td>
<td style="text-align: center;">擅长</td>
<td style="text-align: center;">典型下游</td>
</tr>
</thead>
<tbody>
<tr>
<td>CLM</td>
<td>Decoder-only</td>
<td>生成（Generation）</td>
<td>对话、写作、代码生成</td>
</tr>
<tr>
<td>MLM</td>
<td>Encoder-only</td>
<td>表示学习（Representation）</td>
<td>分类、匹配、序列标注、reranking</td>
</tr>
<tr>
<td>对比学习</td>
<td>Dual Encoder / 多塔</td>
<td>对齐与检索（Alignment/Retrieval）</td>
<td>Embedding、图文检索、聚类</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">继续预训练（Continual Pre-training）</span></div>
<div class="blog_h3"><span class="graybg">领域适配</span></div>
<p>继续预训练（Continual Pre-training）位于“通用预训练”与“下游微调”之间。它不直接把模型改造成某个具体任务的分类器或助手，而是先用目标领域的大规模无标注语料，对已经完成通用预训练的模型再训练一段时间，让参数分布、词汇统计、上下文共现和知识重心向目标领域迁移。对 Encoder-only 模型，这一步通常仍以掩码语言建模（Masked Language Modeling, MLM）为主；对 Decoder-only 生成模型，则通常继续做自回归语言建模（Autoregressive Language Modeling）。</p>
<p>它的核心价值是<span style="background-color: #c0c0c0;">先让底座学会“这个领域怎样说话”，再让它学会“这个任务怎样输出”</span>。若直接拿通用模型去做医疗、法律、金融、代码仓库、企业内部知识库等垂直场景的监督微调，模型往往会同时面对两类落差：一类是领域词汇和表达方式本身就不熟，另一类是下游任务的标签或指令又要求它立即做出稳定判断。继续预训练先处理前一类问题，把底座拉近目标域分布，后续监督微调只需要处理任务映射，训练通常会更稳定，也更节省标注数据。</p>
<p>因此，继续预训练本质上是一种领域自适应预训练（Domain-Adaptive Pretraining, DAPT）。如果继续预训练的语料不仅来自某个大领域，而是更进一步贴近最终任务的输入分布，例如只使用某个具体产品线、某类工单、某种法律文书或某一学科论文语料，那么它也可以被视作任务自适应预训练（Task-Adaptive Pretraining, TAPT）。两者的区别不在训练算法，而在语料与最终应用的距离：DAPT 更强调“进入这个领域”，TAPT 更强调“贴近这个任务”。</p>
<p>领域适配能带来的收益通常体现在四个层面。第一，模型会更熟悉目标域词汇和短语共现，例如医学缩写、金融术语、企业内部专有名词、代码 API 与日志模式。第二，模型对目标域上下文的概率分布会重新校准，原本罕见的搭配在该领域里会变成高频结构。第三，后续监督微调需要学习的东西会减少，因为模型不必一边补语言常识、一边学任务映射。第四，在低标注数据场景下，继续预训练常常比一上来就重监督微调更稳，因为它先利用了最容易获得的大规模原始文本。</p>
<p>继续预训练最适合三类场景：</p>
<ul>
<li><span style="background-color: #c0c0c0;">领域语料很多、标注很少</span>。这是最经典的适用条件，因为继续预训练最能利用大规模无标注文本，而不要求先构造高成本监督数据。</li>
<li>目标文本分布与通用互联网语料差异极大。例如长文档、半结构化记录、专业术语密集文本、代码与自然语言混合语料；这类差异首先是语言分布差异，而不是标签定义差异。</li>
<li>模型需要吸收的变化更接近“知识与表达分布迁移”，而不是单纯“输出标签变了”或“格式要求变了”。后者通常更适合直接做监督微调或 PEFT。</li>
</ul>
<p>从训练流程看，更合理的顺序通常是：先完成通用预训练，再做领域继续预训练，最后再进入监督微调、参数高效微调或偏好对齐。原因很简单：继续预训练改变的是基座对语言分布和知识结构的建模，而监督微调改变的是输出行为。先做基座适配，再做行为适配，优化目标更清晰，也更符合迁移学习的层次结构。</p>
<div class="blog_h3"><span class="graybg">灾难性遗忘问题</span></div>
<p>继续预训练的主要风险，是灾难性遗忘（Catastrophic Forgetting）。它指的是模型在吸收新分布时，原来在通用语料上学到的能力被明显冲掉：例如领域内术语理解变强了，但通用语言理解、跨领域泛化、常识问答、原始格式鲁棒性或多语言能力反而下降。这个问题并不是继续预训练独有的，但在“新语料分布很窄、训练步数又较长”时尤其容易出现。</p>
<p>其根本原因在于参数共享。神经网络并不会为“旧知识”和“新知识”自动分出两套互不干扰的存储区；当优化器持续在窄领域语料上更新同一组权重时，原先支持通用能力的参数方向会被新的梯度不断改写。如果新领域文本的语言风格、词频结构和任务偏置都高度集中，模型就会把有限参数容量优先分配给当前最常见的模式，从而牺牲原本更广的覆盖面。</p>
<p>灾难性遗忘最常见的外在表现包括：继续预训练阶段训练损失持续下降，但回到通用基准或旧任务验证集上时指标明显退化；模型在目标领域内更流畅，却在开放域输入上变得更僵硬、更偏模板化；对窄领域高频术语反应更强，但对跨域问题的泛化能力下降。这些现象都说明模型不是单纯“学到了更多”，而是在重新分配有限表示能力。</p>
<p>缓解灾难性遗忘有几条经典路线：</p>
<ul>
<li><span style="background-color: #c0c0c0;">控制继续预训练强度</span>。包括减少训练步数、降低学习率、使用更保守的 warmup 与衰减策略，避免模型在窄分布上过度漂移。</li>
<li><span style="background-color: #c0c0c0;">混合语料训练</span>。在领域语料之外保留一定比例的通用语料，让模型在吸收新分布的同时持续回顾旧分布。</li>
<li><span style="background-color: #c0c0c0;">参数隔离</span>。例如只对部分层做继续预训练，或采用 Adapter、LoRA 这类参数高效路径，把领域偏移写进新增参数，而不是完全重写主干。</li>
<li><span style="background-color: #c0c0c0;">保留旧能力验证</span>。继续预训练不应只看领域损失，还应并行跟踪若干通用验证集，否则模型退化往往到很晚才会被发现。</li>
</ul>
<p>因此，继续预训练并不是“领域语料越多、训练越久越好”。更准确的目标应当是：在尽量少破坏通用能力的前提下，把模型的统计重心向目标领域移动。它追求的不是把模型变成只会某个领域的专家，而是在通用底座之上增加一层更贴近目标分布的适配。工程上真正好的继续预训练，通常表现为<span style="background-color: #c0c0c0;">领域内显著增益、领域外可控退化，甚至几乎无退化</span>，而不是单纯把领域损失压到最低。</p>
<div class="blog_h2"><span class="graybg">监督微调（SFT）</span></div>
<p>监督微调（Supervised Fine-Tuning, SFT）用“输入 ➡ 期望输出”的监督数据继续训练预训练模型，使其在特定分布上更符合目标行为。对自回归语言模型（Autoregressive LM）而言，SFT 仍然是 next-token 交叉熵：给定提示词 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 与目标回复 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>，最小化</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{\mathrm{SFT}}(\theta)=-\sum_{t}\log \pi_\theta\!\left(y_t\mid x,y_{&lt;t}\right)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\pi_\theta\)</span> 表示由参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 决定的模型条件概率分布，也就是“在给定前缀条件下，模型对下一个 token 的预测分布”；<span displaypfx="inline-" class="mathjax-container">\(\pi_\theta\!\left(y_t\mid x,y_{&lt;t}\right)\)</span> 就表示模型在看到提示词 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 和目标回复此前各 token <span displaypfx="inline-" class="mathjax-container">\(y_{&lt;t}\)</span> 时，对当前位置正确 token <span displaypfx="inline-" class="mathjax-container">\(y_t\)</span> 赋予的概率。</p>
<p>训练通常使用教师强制（Teacher Forcing）：把 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 拼接作为输入，但只在目标回复 token 上计算损失（对提示词部分做标签掩码，label masking），避免模型被迫“复述提示词”。</p>
<p>SFT 的必要性，来自基础模型（Base Model）与“可用助手模型”之间的行为差异。基础模型的原始目标只是预测下一个 token，因此它本质上擅长的是<span style="background-color: #c0c0c0;">续写（Completion）</span>，而不是理解人类正在发出什么指令。给它一句 “The car is”，它自然会继续补全常见续文；给它一个问题 “What is 1+1?”，它在没有对齐之前也完全可能把这当成一段待续写文本，而不是一个必须回答的任务。SFT 的作用，就是把这种“见到前缀就续写”的行为，重塑成“读懂输入意图，再输出目标答案”的行为。</p>
<div class="blog_h3"><span class="graybg">全量微调</span></div>
<p>全量微调（Full Fine-tuning）更新模型的全部参数，表达能力最强，但训练成本高、对数据规模与分布漂移更敏感，也更容易出现灾难性遗忘（Catastrophic Forgetting）。它与预训练在优化形式上并没有本质断裂，区别主要在于数据：预训练依赖海量无标注通用语料，全量微调则依赖规模更小但质量更高、目标更明确的标注数据集。正因为所有参数都会被更新，它在特定任务上的性能上限通常最高，但显存、训练时间和权重存储成本也最高；每做一次完整微调，本质上都在生成一个完整的新模型副本。</p>
<div class="blog_h3"><span class="graybg">部分参数微调</span></div>
<p>部分参数微调（Partial Fine-tuning / Selective Fine-tuning）处在全量微调与参数高效微调（PEFT）之间。它的基本做法不是给模型增加新的适配器参数，而是<span style="background-color: #c0c0c0;">只解冻原模型中一部分已有参数</span>，其余参数保持冻结。这样做的直接收益是显存占用更低、训练更快、过拟合风险更可控；代价则是可调空间受限，性能上限通常低于全量微调。</p>
<p>这类方法的核心思想是：并不是每个任务都需要改写整套参数。若下游变化主要集中在输出读出方式、输入符号分布或某一局部计算结构，那么只更新最相关的一小部分参数，往往就足以完成适配。它与后文的 LoRA、Adapter 有一个重要区别：部分参数微调优化的是<span style="background-color: #c0c0c0;">原模型内部已经存在的参数子集</span>；PEFT 则更常通过新增低秩矩阵、瓶颈层或软提示，把任务偏移写进额外参数。</p>
<div class="blog_h4"><span class="graybg">输出层微调</span></div>
<p>输出层微调（Output-layer Fine-tuning）只更新模型最靠近输出读出的部分，例如语言建模头（LM Head）、分类头（Classification Head）、奖励头（Reward Head），或最后少数几层与任务头直接相连的参数，而把主体 Transformer 基本冻结。它最适合“底层表示已经足够好，但最终读出方式需要重塑”的场景。</p>
<p>对表示模型，这通常意味着冻结编码器主体，只训练顶层分类器；对生成模型或对齐流程，则常见于基于已有 SFT 模型训练奖励模型：保留主干表示层，移除原 LM Head，换成输出单一分数的奖励头，再主要围绕这个输出读出层继续训练。它的优势是参数量极小、训练稳定、成本最低；局限在于它基本不改变主干内部表示，因此当任务真正需要重排中间语义结构时，单靠输出层往往不够。</p>
<div class="blog_h4"><span class="graybg">输入层微调</span></div>
<p>输入层微调（Input-layer Fine-tuning）主要更新输入嵌入相关参数，例如 token embedding、位置嵌入，或与新词表、新符号、新模态入口直接相连的输入投影层，而冻结大部分主体网络。它适合输入分布变化显著、但主体推理与表示能力仍然可复用的场景，例如新增领域术语、扩展专有 token、接入特殊控制符，或需要让模型先学会“看懂新输入”。</p>
<p>这条路线在词表扩展与领域符号接入时尤其有价值。因为很多变化并不在“模型不会推理”，而在“模型还没有为这些新符号建立合适入口”。此时先调输入层，可以把新 token 映射到已有表示空间附近，减少一开始就全模型漂移的风险。在一些更重的训练配方里，也会先单独训练输入嵌入，再逐步解冻更深层参数做融合；但其本质始终是先处理<span style="background-color: #c0c0c0;">输入接口适配</span>，再决定是否需要更深层的结构性更新。</p>
<div class="blog_h4"><span class="graybg">局部结构微调</span></div>
<p>局部结构微调（Local-structure Fine-tuning）只选择模型内部某些特定结构或参数类型来更新，例如仅训练偏置项的 BitFit、仅训练归一化参数的 LayerNorm Tuning、只解冻注意力层参数的 Attention Tuning，或只解冻最后若干层的局部 block。它的共同点是：参数选择不是按“输入端 / 输出端”划分，而是按<span style="background-color: #c0c0c0;">网络内部哪类结构最可能承载任务偏移</span>来划分。</p>
<p>这类方法适合算力极其受限、数据量较小，或已经对任务偏移位置有较强先验的场景。例如，若任务主要要求重新标定特征尺度或阈值边界，LayerNorm Tuning 可能就足够；若任务更多是在改变“关注哪里、聚合哪些信号”，只调注意力层可能比盲目放开全部层更高效；若只是希望用极低成本给模型一点任务校正能力，BitFit 这类只训偏置的方案也有现实价值。它们的上限通常不如更强的 PEFT，但在轻量实验、消融研究和极端资源约束环境中依然很有意义。</p>
<div class="blog_h4"><span class="graybg">BitFit</span></div>
<p>BitFit 的做法极端简单：冻结几乎所有权重矩阵，只训练偏置项（Bias Terms）。若某一层的线性变换写成 <span displaypfx="inline-" class="mathjax-container">\(h'=Wh+b\)</span>，BitFit 只更新其中的 <span displaypfx="inline-" class="mathjax-container">\(b\)</span>，而把 <span displaypfx="inline-" class="mathjax-container">\(W\)</span> 保持不动。它的参数量因此极小，通常只占全模型参数的很小一部分。</p>
<p>它背后的核心假设是：对不少下游任务而言，预训练模型已经学到了足够强的表示空间与主要变换方向，任务适配真正需要的，未必是重写整块权重矩阵，而可能只是<span style="background-color: #c0c0c0;">调整各层激活的平移、阈值和默认响应水平</span>。从这个角度看，BitFit 更像是在重新标定网络内部各单元的“触发基线”，而不是重建新的特征子空间。</p>
<p>这也解释了它为什么在一些小数据分类、文本匹配或轻量行为校正任务里常常表现得比直觉预期更强：如果任务边界与预训练表示已经高度接近，那么改变少量偏置，就足以让原本已经存在的特征更容易被激活，或更容易跨过最终判别阈值。反过来，当任务需要新的知识写入、复杂结构重排或明显不同的推理路径时，BitFit 往往会很快触到容量上限，因为它几乎无法改变表示之间的主导交互方向。</p>
<div class="blog_h4"><span class="graybg">LayerNorm Tuning</span></div>
<p>LayerNorm Tuning 只更新归一化层中的可学习缩放与偏移参数，典型写法可记为：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{LN}(h)=\gamma\odot \frac{h-\mu}{\sqrt{\sigma^2+\epsilon}}+\beta\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\beta\)</span> 就是主要可训练对象，而主体权重矩阵保持冻结。它的参数量同样很小，但比 BitFit 更直接作用于每层隐藏状态的尺度（Scale）与中心（Shift）。</p>
<p>它背后的假设是：很多任务偏移并不需要创造全新的特征，只需要重新调节已有特征在各层中的相对幅度与数值范围。因为 Transformer 中大量残差路径都会经过归一化，LayerNorm 参数对信息流的“放大 / 压低 / 重新居中”有全局影响。于是，只调归一化参数，就可能在不改写主干矩阵的前提下，系统性地改变哪些特征更容易穿过后续层并主导输出。</p>
<p>这类方法尤其适合已经拥有较强基座、但需要重新校准风格、阈值、稳定性或局部行为边界的场景。它通常比 BitFit 稍强，因为它直接控制每层表示的尺度结构；但它仍然主要是在<span style="background-color: #c0c0c0;">重标定现有表示</span>，而不是构造新的复杂变换，因此面对大幅领域迁移或新知识注入时，上限仍然有限。</p>
<div class="blog_h4"><span class="graybg">Attention Tuning</span></div>
<p>Attention Tuning 只解冻注意力模块中的参数，例如 <span displaypfx="inline-" class="mathjax-container">\(W_Q,W_K,W_V,W_O\)</span>，而继续冻结 FFN / MLP 与其他大部分结构。它的核心判断是：不少任务真正需要改变的，不是模型是否拥有某些知识，而是模型在当前任务中<span style="background-color: #c0c0c0;">应该关注哪些 token、怎样聚合远近信息、如何在上下文中分配注意力</span>。</p>
<p>它背后的假设比 BitFit 更强，也更结构化：预训练模型中的知识与模式大体已经存在，任务适配更多是在改变“信息路由（Information Routing）”而不是“知识存储（Knowledge Storage）”。如果这个假设成立，只调整注意力层就能显著改变模型的上下文读取方式，例如更关注结尾句、否定词、实体关系、长程依赖或某些格式锚点，而无需重写 FFN 中更重的参数块。</p>
<p>这也是为什么 Attention Tuning 在行为调整类任务上常有不错的性价比：它比 BitFit 和 LayerNorm Tuning 拥有更强的表示重排能力，又比全量微调和大范围 PEFT 更轻。但它的边界也很清楚。若任务核心是注入新事实、学习新术语本体、补足模型原本缺失的知识映射，仅靠改变注意力路由通常不够，因为许多稳定知识关联最终仍要落在 FFN / MLP 所承载的表示重编码里。</p>
<p>从整体上看，部分参数微调提供的是一种<span style="background-color: #c0c0c0;">选择性解冻原参数</span>的思路：输出层微调优先改读出，输入层微调优先改入口，局部结构微调优先改网络内部某个被认为最关键的子结构。若这些选择性更新已经足够，就没有必要进入更重的全量微调；若它们的容量仍然不够，下一步才更自然地转向后文的 LoRA、Adapter、Prefix Tuning 这类参数高效微调路线。</p>
<div class="blog_h3"><span class="graybg">指令微调（Instruction Tuning）</span></div>
<p>指令微调（Instruction Tuning）是 SFT 的一种数据组织方式：把任务描述（Instruction）显式写进输入，使模型学习“读懂指令并按指令输出”。典型样本是三元组：指令（instruction）、输入（input，可为空）、输出（output）。</p>
<pre class="crayon-plain-tag">{&quot;instruction&quot;:&quot;回答以下问题&quot;,&quot;input&quot;:&quot;世界上最高的山是什么？&quot;,&quot;output&quot;:&quot;珠穆朗玛峰。&quot;}</pre>
<p>对话/FAQ 场景更常用多轮消息格式（Chat Format），把 role（system/user/assistant）显式编码进序列；训练时同样只在 assistant 角色对应的目标 token 上计算损失。</p>
<pre class="crayon-plain-tag">{&quot;messages&quot;:[
  {&quot;role&quot;:&quot;system&quot;,&quot;content&quot;:&quot;你是一个严谨的技术助手。&quot;},
  {&quot;role&quot;:&quot;user&quot;,&quot;content&quot;:&quot;解释什么是交叉熵损失。&quot;},
  {&quot;role&quot;:&quot;assistant&quot;,&quot;content&quot;:&quot;交叉熵损失用于衡量预测分布与真实分布的差异&hellip;&hellip;&quot;}
]}</pre>
<p>数据规模没有统一答案，但工程上最关键的是质量（Quality）与覆盖（Coverage）。常见实践：</p>
<ul>
<li>领域 SFT：从 <span displaypfx="inline-" class="mathjax-container">\(10^3\sim10^5\)</span> 级别的高质量样本起步，先跑通指标与错误分析，再扩充数据与任务覆盖。</li>
<li>通用指令微调：更常见的是 <span displaypfx="inline-" class="mathjax-container">\(10^5\sim10^6+\)</span> 的多任务指令样本，用多样性换泛化。</li>
<li>偏好对齐数据：比较对（Preference Pairs）常在 <span displaypfx="inline-" class="mathjax-container">\(10^4\sim10^6\)</span> 级别，且对标注一致性要求更高。</li>
</ul>
<div class="blog_h3"><span class="graybg">拒绝采样微调（Rejection Sampling Fine-Tuning）</span></div>
<p>拒绝采样微调（Rejection Sampling Fine-Tuning）本质上仍然属于监督微调路线。这里讨论的是 rejection sampling fine-tuning 这一路线，而不是近年某些语境里也会写成 RFT 的 reinforcement fine-tuning。它的核心做法是：监督数据不再完全来自人工直接编写，而是先由模型生成多个候选，再通过规则、验证器（Verifier）、奖励模型（Reward Model）或人工筛选，只保留其中最优或通过阈值的样本，最后把这些“被接受”的输出重新写回监督数据集，再按普通 SFT 的方式继续训练。</p>
<p>若把提示词记为 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，候选回答记为 <span displaypfx="inline-" class="mathjax-container">\(\{y^{(k)}\}_{k=1}^{K}\sim \pi_{\mathrm{old}}(\cdot|x)\)</span>，评分函数记为 <span displaypfx="inline-" class="mathjax-container">\(s(x,y)\)</span>，那么拒绝采样微调通常先从这组候选中选出满足 <span displaypfx="inline-" class="mathjax-container">\(s(x,y)\ge \tau\)</span> 的回答，或直接取最高分回答 <span displaypfx="inline-" class="mathjax-container">\(y^\star=\arg\max_k s(x,y^{(k)})\)</span>，再把 <span displaypfx="inline-" class="mathjax-container">\((x,y^\star)\)</span> 当作新的监督样本。后续优化目标并没有变成策略梯度或显式偏好损失，仍然是标准的 next-token 交叉熵。</p>
<p>因此，它可以被理解为一种<span style="background-color: #c0c0c0;">先筛选、再监督</span>的微调方式。与普通 SFT 相比，它利用模型自身采样与外部评分器把“哪种输出更好”这层信息先转成更高质量的目标答案；与 DPO、PPO 这类偏好优化相比，它并不直接学习候选之间的相对排序关系，而是把筛选结果硬化成新的监督标签。工程上，它经常处在普通 SFT 与显式偏好优化之间，既比纯手工 SFT 更能利用自动评估信号，又比完整 RLHF 或 DPO 更容易复用现有监督训练栈。</p>
<p>这条路线尤其适合<span style="background-color: #c0c0c0;">存在较强可验证信号</span>的任务，例如数学推导、代码生成、结构化输出、工具调用轨迹筛选，以及能用单元测试、规则校验、解析器或外部判分器稳定判断好坏的场景。因为一旦评分器足够可靠，拒绝采样就能把“生成多个候选、只留下正确或更优的那个”直接转化为高质量训练样本。</p>
<p>它的边界同样明确。若评分器本身噪声很大，或任务质量强依赖开放式偏好、语气细节、多维安全判断，那么把复杂偏好硬压成“通过 / 不通过”很容易损失信息；筛选过严还会让训练分布变得过窄，导致模型只会复现少数高分写法而削弱多样性。因此，拒绝采样微调更像一种<span style="background-color: #c0c0c0;">高质量数据再蒸馏</span>手段，而不是偏好对齐的终极替代品。</p>
<div class="blog_h3"><span class="graybg">聊天微调（Chat Fine-tuning）</span></div>
<p>聊天微调（Chat Fine-tuning）强调多轮对话一致性：除单轮问答外，还需要覆盖上下文承接、拒答策略、工具调用格式、长对话记忆等。它通常仍是 SFT，只是数据分布更贴近真实对话。</p>
<div class="blog_h2"><span class="graybg">参数高效微调（PEFT）</span></div>
<p>参数高效微调（Parameter-Efficient Fine-Tuning, PEFT）把“微调”从“更新全部参数”变成“冻结基座（Base Model），只训练一小部分新增参数”。它仍然是当前大模型落地的主流选择之一：成本更低、训练更快、便于为不同任务保存多份轻量适配（例如每个业务一套 LoRA）。PEFT 并不只包含 LoRA、Adapter、IA3 这类“在模型内部加参数”的方法，也包含 Prefix Tuning、Prompt Tuning 这类<span style="background-color: #c0c0c0;">软提示（Soft Prompt）</span>路线；它们的共同点不是结构长得像不像，而是都遵循“冻结大部分预训练参数，只训练极小任务参数”这一基本范式。</p>
<div class="blog_h3"><span class="graybg">Adapter</span></div>
<p>Adapter 是<span style="background-color: #c0c0c0;">插在每个 Transformer block 内部的一条很窄的可训练残差支路</span>。设某一层原本输出为 <span displaypfx="inline-" class="mathjax-container">\(h\in\mathbb{R}^{d}\)</span>，Adapter 会先把它投影到一个远小于 <span displaypfx="inline-" class="mathjax-container">\(d\)</span> 的瓶颈维度 <span displaypfx="inline-" class="mathjax-container">\(r\)</span>，经过非线性后再投影回原维度，再以残差形式加回主干：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Adapter}(h)=W_{\mathrm{up}}\;\sigma\!\left(W_{\mathrm{down}}h+b_{\mathrm{down}}\right)+b_{\mathrm{up}},\quad h'=h+\mathrm{Adapter}(h)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(W_{\mathrm{down}}\in\mathbb{R}^{r\times d}\)</span> 负责降维， <span displaypfx="inline-" class="mathjax-container">\(W_{\mathrm{up}}\in\mathbb{R}^{d\times r}\)</span> 负责升维，且通常有 <span displaypfx="inline-" class="mathjax-container">\(r\ll d\)</span>。这就是所谓的瓶颈结构（Bottleneck Structure）：主干隐藏维度也许是几千，而 Adapter 内部只开放几十到几百维的可训练通道，因此新增参数量远小于全量微调。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/adapter-structure-canvas.jpg"><img class="alignnone size-full wp-image-41967" src="https://blog.gmem.cc/wp-content/uploads/2026/03/adapter-structure-canvas.jpg" alt="adapter-structure-canvas" width="1438" height="877" /></a></p>
<p> 它通常插在自注意力子层或前馈网络子层之后，也就是“原子层输出 + LayerNorm / 残差”附近的位置。最常见的做法是在每个 block 中放两处：一处跟在 attention 输出之后，另一处跟在 FFN 输出之后。这样设计的直觉很直接：主干模型仍负责保留通用语言能力，而 Adapter 只学习<span style="background-color: #c0c0c0;">相对于原模型的任务特定偏移</span>。由于它是加法残差支路，模型一开始可以非常接近原始基座；随着训练推进，Adapter 再逐步学会把主干表示往当前任务需要的方向轻推一把。</p>
<p>这也是为什么 Adapter 常被初始化为近似恒等映射：例如让升维层初始非常小，使 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Adapter}(h)\approx 0\)</span>。这样做的效果是，训练初期模型几乎等同于原始基座，不会因为新增模块而立刻破坏预训练表示；随后再通过反向传播逐步放大这条残差分支，让它承担领域偏移、标签边界重塑或任务路由修正。与“直接改写主干权重”相比，这种路径更稳定，也更容易控制灾难性遗忘。</p>
<p>从参数量角度看，单个 Adapter 的主要新增参数就是两次线性映射，大约是 <span displaypfx="inline-" class="mathjax-container">\(2dr\)</span> 量级，而不是全层的 <span displaypfx="inline-" class="mathjax-container">\(d\times d\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(d\times d_{\mathrm{ff}}\)</span> 量级。因此，只要瓶颈维度 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 足够小，就能在保持表达力的同时，把训练显存、优化器状态和存储成本压到很低。这一点与 LoRA 的“低秩更新”在精神上相近，但它们的结构并不相同：Adapter 是显式新增一条小型 MLP 支路，LoRA 则是在原线性层内部参数化一个低秩增量。</p>
<p>工业实践里，Adapter 通常不是只挂在单个层上，而是分布式地插入到所有 Transformer block 中，从而让每一层都具备任务适配能力。它的一个重要工程优势是“插拔式（Plug-and-play）”任务切换：同一个基座模型可以加载不同任务的 Adapter 包，在情感分析、NER、检索重排等任务之间快速切换，而不必为每个任务都保存一整份完整模型。这也是 Adapter 在多任务部署和组织内模型复用场景中一直很有吸引力的原因。</p>
<div class="blog_h3"><span class="graybg">LoRA</span></div>
<p>LoRA（Low-Rank Adaptation）不改动原权重 <span displaypfx="inline-" class="mathjax-container">\(W\)</span>，而是学习一个低秩增量 <span displaypfx="inline-" class="mathjax-container">\(\Delta W\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[W' = W + \Delta W,\quad \Delta W = BA,\quad B\in\mathbb{R}^{d_{\text{out}}\times r},\ A\in\mathbb{R}^{r\times d_{\text{in}}},\ r\ll \min(d_{\text{in}},d_{\text{out}})\]</span>
<p>由于 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 很小，可训练参数与优化器状态显著减少。实践里常把 LoRA 加在注意力投影（如 <span displaypfx="inline-" class="mathjax-container">\(W_Q,W_K,W_V,W_O\)</span>）和/或 FFN 上。</p>
<p>截至 2026 年，LoRA 仍然是大语言模型参数高效微调里最常见的默认方案。原因并不神秘：它与主流 Transformer 线性层天然兼容，适配器权重体积小，便于为不同任务单独保存、热切换与合并；同时它对训练框架、推理框架和量化框架的兼容性也最成熟。因此，工程上常把 LoRA 看成 PEFT 的基线接口：后续很多方法，本质上都是在 LoRA 的参数化、量化方式或更新几何上继续细化。</p>
<div class="blog_h4"><span class="graybg">A、B 矩阵的区别</span></div>
<p>在上面的记号里， <span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{r\times d_{\text{in}}}\)</span> 负责把原始输入方向投影到一个 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 维低秩子空间， <span displaypfx="inline-" class="mathjax-container">\(B\in\mathbb{R}^{d_{\text{out}}\times r}\)</span> 再把这个低维表示映射回输出空间。因此，对输入向量 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 而言，LoRA 增量的作用顺序是：</p>
<span displaypfx="" class="mathjax-container">\[\Delta y=\Delta Wx=BAx\]</span>
<p>也就是说，先由 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 做“进低秩空间”的投影，再由 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 做“回原输出空间”的回投。直觉上， <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 更像在问“哪些组合方向值得被拿出来单独调”， <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 则更像在问“这些低维方向最终该怎样广播回原模型的输出通道”。</p>
<p>不同实现里，A、B 的命名和矩阵形状有时会看起来对调，这是因为有的库按数学乘法顺序命名，有的库按代码中参数张量的存储顺序命名。但概念并没有变：总有一个矩阵负责把高维输入压到低秩子空间，另一个矩阵负责把低秩更新再映射回原空间。只要抓住“先降到 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 维，再升回原维度”这一点，就不会被不同实现的符号差异干扰。</p>
<p>LoRA 的经典初始化也依赖这两个矩阵的不同角色。常见做法是让其中一个矩阵采用小随机初始化，而另一个矩阵初始化为 0；在当前这组记号下，更常见的叙述是让 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 随机初始化、 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 零初始化，于是训练开始时有：</p>
<span displaypfx="" class="mathjax-container">\[\Delta W = BA = 0\]</span>
<p>这样做有两个直接好处。第一，训练初始时模型输出与原基座完全一致，不会因为 LoRA 分支突然注入随机扰动而破坏预训练能力。第二，参数不会陷入完全对称的零状态：若两边都初始化为 0，梯度传播会受阻；若两边都随机初始化，训练一开始又会平白给模型加上不必要的噪声。采用“单边随机、单边为零”的非对称初始化，既保证了初始增量为零，又保留了可学习性，这是 LoRA 训练稳定性的关键细节之一。</p>
<p>从梯度角度看，这个设计也有非常直接的理由。若记损失对增量矩阵的梯度为 <span displaypfx="inline-" class="mathjax-container">\(G=\frac{\partial \mathcal{L}}{\partial \Delta W}\)</span>，则有：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial \mathcal{L}}{\partial B}=GA^\top,\qquad \frac{\partial \mathcal{L}}{\partial A}=B^\top G\]</span>
<p>因此，当 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 随机、 <span displaypfx="inline-" class="mathjax-container">\(B=0\)</span> 时，训练开始的第一步通常会先更新 <span displaypfx="inline-" class="mathjax-container">\(B\)</span>，因为 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial B}=GA^\top\)</span> 一般不为 0；而 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial A}=B^\top G=0\)</span>，所以 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 会在后续几步中随着 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 脱离零状态后再开始获得梯度。也正因为如此，LoRA 的非对称初始化并不会“学不起来”，它只是让学习过程以一种更平稳的方式启动。</p>
<div class="blog_h4"><span class="graybg">主要参数</span></div>
<p>LoRA 真正需要重点理解的超参数并不多，但每一个都在控制不同维度的权衡：容量、增量强度、挂载范围、正则化和训练稳定性。它们之间并不是简单的“越大越好”关系。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">参数</td>
<td style="text-align: center;">控制对象</td>
<td style="text-align: center;">理论含义与实践影响</td>
</tr>
</thead>
<tbody>
<tr>
<td>rank <span displaypfx="inline-" class="mathjax-container">\(r\)</span></td>
<td>低秩子空间维度</td>
<td>决定 <span displaypfx="inline-" class="mathjax-container">\(\Delta W\)</span> 最多能沿多少个独立方向修改原权重。 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 越小，参数越省、正则效应越强，但容量也越受限； <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 越大，表达力更强，却更容易抬高显存、训练成本与过拟合风险。</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span></td>
<td>增量缩放强度</td>
<td>通常与 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 一起通过 <span displaypfx="inline-" class="mathjax-container">\(\frac{\alpha}{r}\)</span> 作用在 LoRA 分支上。它控制的是“已经学到的低秩方向到底以多大幅度影响原模型”，因此更接近幅值旋钮，而不是容量旋钮。调大 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 不能替代更高的 <span displaypfx="inline-" class="mathjax-container">\(r\)</span>；它只能放大已有方向，而不会创造新方向。</td>
</tr>
<tr>
<td>target_modules</td>
<td>挂载位置</td>
<td>决定 LoRA 写入模型的哪一部分。只挂注意力投影时，参数最省、更偏行为与路由调整；把 MLP / FFN 一并纳入时，容量更强，也更适合知识关系与复杂边界适配，但训练更重。</td>
</tr>
<tr>
<td>lora_dropout</td>
<td>适配器正则化</td>
<td>主要用于抑制小数据或高重复语料上的过拟合。它并不改变 LoRA 的基本结构，而是在训练时降低低秩分支对局部样本模式的过度依赖。数据量很小时更有价值；数据充分且任务稳定时常保持较低甚至关闭。</td>
</tr>
<tr>
<td>学习率</td>
<td>优化步长</td>
<td>虽然学习率不是 LoRA 独有参数，但 LoRA 对学习率通常比全参数微调更敏感。因为可训练参数很少、每一步更新更集中，学习率过高时更容易直接把行为边界推歪；而 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 较大时，这种不稳定会被进一步放大。</td>
</tr>
</tbody>
</table>
<p>这几个参数里，最容易被混淆的是 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span>。 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 控制的是“允许模型沿多少个方向改”， <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 控制的是“这些方向的改动最终放大到多强”。前者对应容量，后者对应强度。增加 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 会改变可表达子空间本身；增加 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 只是把已存在的低秩更新放大。</p>
<p>从经验上看，LoRA 的调参顺序通常也应当遵循这个逻辑：先确定挂载哪些模块、需要多大 rank 才能容纳任务偏移，再去调节 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 与学习率，让训练稳定落在合适幅度上。若一开始就只靠提高 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 去追求效果，得到的往往不是更强的表达力，而是更剧烈的扰动。</p>
<p>LoRA 挂在哪些线性层上，并不是纯工程细节，而与希望改变模型的哪一部分能力直接相关。若目标更偏风格迁移、格式控制、对话行为调整或路由方式改变，注意力投影层上的 LoRA 往往已经能带来明显效果；但若目标是注入新的领域术语、事实关联、实体属性映射或专业概念之间的稳定关系，FFN / MLP 往往更关键。原因在于：Transformer 里的 MLP 常被视为知识写入与模式重编码的重要位置，因此很多“新知识”最终要落到这些大规模前馈权重所张成的表示子空间里。</p>
<p>这也是为什么在不少实践中，LoRA 不只挂在 <span displaypfx="inline-" class="mathjax-container">\(W_Q,W_K,W_V,W_O\)</span> 上，还会同时挂在 FFN 的线性层上，甚至在资源允许时为 FFN 分配更高的秩（Rank）。低秩更新的本质是在原有权重空间附近增加一个受限的可训练子空间；如果希望修改的是知识关联本身，而不仅是信息流动方式，那么只改注意力层常常不够，需要让 MLP / FFN 也获得足够的适配容量。沿着这条思路发展的变体，如 DoRA（Weight-Decomposed Low-Rank Adaptation），本质上也是在不做全参数微调的前提下，给参数更新更强的表达能力。</p>
<p>这里的低秩假设作用于微调增量 <span displaypfx="inline-" class="mathjax-container">\(\Delta W\)</span>，而不是作用于预训练知识本身的存储方式。预训练模型中的知识通常以分布式表示（Distributed Representation）的方式编码在大量参数 <span displaypfx="inline-" class="mathjax-container">\(W\)</span> 里；LoRA 近似的是“为了适配当前任务，需要沿哪些方向改动这些参数”。前者讨论的是 <span displaypfx="inline-" class="mathjax-container">\(W\)</span> 如何承载知识，后者讨论的是 <span displaypfx="inline-" class="mathjax-container">\(\Delta W\)</span> 如何改变模型行为与输出边界。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/lora.png"><img class="alignnone size-full wp-image-41973" src="https://blog.gmem.cc/wp-content/uploads/2026/03/lora.png" alt="lora" width="1024" height="1024" /></a></p>
<p>很多微调任务并不要求模型写入大规模新知识，而是要求它重新组织已有表示：调整回答风格、强化指令跟随、遵守输出格式、放大某些线索、抑制另一些线索。这类任务的有效更新方向往往集中在少数子空间中，小秩 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 就足以带来明显收益。若任务要求模型稳定吸纳新的领域本体（Ontology）、术语体系、事实关系或复杂规则，适配增量的有效维度通常会上升，低秩近似就更容易成为容量瓶颈。</p>
<p>分布式存储也不意味着所有参数同等重要。即便在全参数微调（Full Fine-tuning）中，显著变化的往往也是少数关键方向；LoRA 的核心假设是，这些方向可以被一个较低维的子空间有效覆盖。当任务迁移幅度较小，这个假设通常成立；当关键更新分散在许多彼此独立的方向上，就需要更高的秩、更广的挂载范围，尤其是让 FFN / MLP 参与适配，必要时再转向 DoRA 或全参数微调。工程上，这体现为一条清晰的权衡：LoRA 优先优化效率，全参数微调提供更高上限；二者对应的是不同任务内在维度（Intrinsic Dimension）下的不同最优解。</p>
<div class="blog_h4"><span class="graybg">LoRA 合并</span></div>
<p>LoRA 的一个工程优势是“可合并（Mergeable）”：推理前可把增量权重并入基座权重，从而不引入额外前向分支。对单个 LoRA，合并后得到的有效权重就是 <span displaypfx="inline-" class="mathjax-container">\(W_{\text{merged}}=W+\Delta W\)</span>（实践中常包含缩放系数）。</p>
<p>当存在多份 LoRA（多任务/多领域适配）时，最简单的合并是对增量做加权和：</p>
<span displaypfx="" class="mathjax-container">\[W' = W + \sum_{j=1}^{M}\lambda_j\,\Delta W^{(j)}\]</span>
<p>这种“线性缝合”实现简单，但容易出现任务干扰（Interference）：不同 LoRA 在同一参数子空间里叠加，可能让模型对多个任务都变差。若你需要同时服务多个领域，更稳健的方案往往是“运行时选择哪一份 LoRA”或引入路由（Routing）机制，而不是把它们永久混成一份权重。</p>
<div class="blog_h4"><span class="graybg">LoRA 缩放</span></div>
<p>LoRA 的低秩增量在工程实现里通常写成带缩放的形式，而不是直接写成 <span displaypfx="inline-" class="mathjax-container">\(\Delta W=BA\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\Delta W=\frac{\alpha}{r}BA\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 是秩（Rank），决定低秩子空间的维度；<span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 是缩放系数（Scaling Factor），决定这条增量支路最终以多大强度作用于原权重。把 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 放在一起，不是书写习惯，而是为了在改变 rank 时尽量保持更新量的数值尺度处于可控范围。若只增大 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 而不做归一化，低秩分支的整体幅度往往也会随之增大，使不同配置之间难以直接比较。</p>
<p>因此， <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 控制的是“LoRA 支路有多强”， <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 控制的是“LoRA 支路能沿多少个方向改动权重”。前者更接近幅值控制，后者更接近容量控制。单纯调大 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span>，只是放大既有低秩方向的影响；单纯调大 <span displaypfx="inline-" class="mathjax-container">\(r\)</span>，则是在扩大可表达的更新子空间。</p>
<p>缩放在合并时同样不会消失。真正并回基座的不是裸 <span displaypfx="inline-" class="mathjax-container">\(BA\)</span>，而是已经乘上系数后的有效增量，因此合并后的权重应写成：</p>
<span displaypfx="" class="mathjax-container">\[W_{\text{merged}}=W+\frac{\alpha}{r}BA\]</span>
<p>工程上，较小数据集与较轻任务迁移通常更适合温和缩放，因为此时更重要的是在保留基座先验的同时做局部修正；当任务迁移更深、希望 LoRA 更积极地重写行为边界或知识关联时，才会提高 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 或放宽 <span displaypfx="inline-" class="mathjax-container">\(r\)</span>。它本质上是在调节“基座保守性”与“任务增量强度”之间的平衡。</p>
<div class="blog_h3"><span class="graybg">DoRA</span></div>
<p>DoRA（Weight-Decomposed Low-Rank Adaptation）的核心不是把 LoRA 的两个低秩矩阵 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 再拆一层，而是先把原始权重 <span displaypfx="inline-" class="mathjax-container">\(W\)</span> 按“幅值（Magnitude）+ 方向（Direction）”重写，再只让低秩更新作用在方向部分。对第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个输出通道，也就是权重矩阵的第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 列，可先写成：</p>
<span displaypfx="" class="mathjax-container">\[W_{:,j}=\left\|W_{:,j}\right\|_2\cdot \frac{W_{:,j}}{\left\|W_{:,j}\right\|_2}=m_j\,\hat v_j,\quad \left\|\hat v_j\right\|_2=1\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(\frac{W_{:,j}}{\left\|W_{:,j}\right\|_2}\)</span> 就是“把一个向量除以自己的 <span displaypfx="inline-" class="mathjax-container">\(\ell_2\)</span> 范数（L2 Norm）”。这样得到的新向量长度恰好等于 1，因此它不再携带原来的大小信息，只保留方向信息，也就是单位方向向量（Unit Direction Vector）。</p>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(m_j\)</span> 是一个标量，表示这一列权重的整体长度； <span displaypfx="inline-" class="mathjax-container">\(\hat v_j\)</span> 是单位向量，表示这一列“指向哪里”。这一步只是重参数化（Reparameterization）：没有改动原模型，只是把每一列从“一个普通向量”改写成“长度 × 单位方向”。</p>
<p>DoRA 的更新写法通常记为：</p>
<span displaypfx="" class="mathjax-container">\[W'_{:,j}=m'_j\frac{V_{:,j}+\Delta V_{:,j}}{\left\|V_{:,j}+\Delta V_{:,j}\right\|_2},\quad \Delta V=BA\]</span>
<p>理解这条式子的关键，在于分清谁在控制方向，谁在控制大小。分式里的 <span displaypfx="inline-" class="mathjax-container">\(V_{:,j}+\Delta V_{:,j}\)</span> 先经过 <span displaypfx="inline-" class="mathjax-container">\(\ell_2\)</span> 归一化，因此无论 <span displaypfx="inline-" class="mathjax-container">\(\Delta V\)</span> 本身把这个向量拉长还是压短，归一化之后保留下来的都只有<span style="background-color: #c0c0c0;">方向信息</span>。换句话说，LoRA 产生的低秩增量 <span displaypfx="inline-" class="mathjax-container">\(BA\)</span> 在这里主要决定“这一列朝哪个方向偏转”，而不会直接把这一列的范数放大或缩小，因为范数已经被分母除掉了。</p>
<p>真正决定输出通道大小的是前面的标量 <span displaypfx="inline-" class="mathjax-container">\(m'_j\)</span>。如果把 <span displaypfx="inline-" class="mathjax-container">\(m'_j\)</span> 固定住，那么更新后的列向量范数始终满足 <span displaypfx="inline-" class="mathjax-container">\(\left\|W'_{:,j}\right\|_2=m'_j\)</span>，此时低秩更新确实只在改方向、不改大小；如果把 <span displaypfx="inline-" class="mathjax-container">\(m'_j\)</span> 设为可学习参数，那么 DoRA 就是在<span style="background-color: #c0c0c0;">两个通道里分别学习</span>：低秩分支 <span displaypfx="inline-" class="mathjax-container">\(\Delta V\)</span> 负责方向修正，标量 <span displaypfx="inline-" class="mathjax-container">\(m'_j\)</span> 负责幅值修正。无论是哪一种，方向与大小都不再像原始 LoRA 那样纠缠在同一个增量矩阵里。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/dora.png"><img class="alignnone size-full wp-image-41985" src="https://blog.gmem.cc/wp-content/uploads/2026/03/dora.png" alt="dora" width="1024" height="1024" /></a></p>
<p>这也是 DoRA 比原始 LoRA 更接近全参数微调的原因之一。普通 LoRA 直接对 <span displaypfx="inline-" class="mathjax-container">\(W\)</span> 加一个低秩增量 <span displaypfx="inline-" class="mathjax-container">\(\Delta W\)</span>，因此“方向变化”和“范数变化”混在同一个更新里；DoRA 则把这两件事显式拆开，使优化器可以分别决定“该往哪里转”与“该放大多少”。当任务需要更深地改写领域知识、重塑复杂判别边界或吸收更稳定的专业概念关系时，这种解耦往往更有表达力；代价则是额外的参数、归一化计算与实现复杂度。</p>
<div class="blog_h3"><span class="graybg">QLoRA</span></div>
<p>QLoRA 在 LoRA 基础上进一步把基座权重量化（Quantize）到低比特（常见 4-bit），以极小显存加载大模型；训练时仍只更新 LoRA 参数。它把“能不能放得下”从硬约束变成可控工程问题，是许多个人/小团队微调 7B/13B 的关键技术路径之一。</p>
<p>其核心思路是：冻结量化后的基座权重，只在前向/反向计算时对它们做解量化（Dequantize），而真正需要学习的仍是低秩增量。一个简化写法是：</p>
<span displaypfx="" class="mathjax-container">\[Y=X\,\mathrm{Dequant}(W_q)+\frac{\alpha}{r}XBA,\quad W_q=\mathrm{Quant}(W)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 是输入激活， <span displaypfx="inline-" class="mathjax-container">\(W_q\)</span> 是量化后冻结的基座权重， <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Dequant}(W_q)\)</span> 表示把低比特权重恢复到计算精度后的近似值， <span displaypfx="inline-" class="mathjax-container">\(\frac{\alpha}{r}BA\)</span> 是 LoRA 分支， <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 是缩放系数（Scaling Factor），用来控制低秩增量对原模型的影响强度。</p>
<p>这里的“解量化”不是把模型永久还原回全精度权重再训练，而是指：权重在显存或存储中仍以低比特形式保存，只是在一次具体矩阵乘法发生时，临时把对应块恢复成计算所需的近似浮点值，再与输入激活相乘。也就是说，量化主要解决的是<span style="background-color: #c0c0c0;">存储与显存占用</span>，而解量化解决的是<span style="background-color: #c0c0c0;">如何让这些低比特权重仍然参与正常线性计算</span>。</p>
<p>因此，所谓“解量化计算路径”指的是：训练框架是否知道如何从低比特权重 <span displaypfx="inline-" class="mathjax-container">\(W_q\)</span> 出发，在前向过程中正确恢复近似浮点表示、完成矩阵乘法，并在反向传播时把梯度只传给 LoRA 分支而不是错误地写回量化权重本体。若这条路径存在，那么量化基座虽然被冻结，仍然可以作为可计算的主干参与训练；若这条路径不存在，量化权重就只是某种压缩后的静态文件，能用于推理加载，却不能自然地嵌入 QLoRA 的训练图中。</p>
<p>这里的前提不是“训练者必须先拿到一份原始全精度基座，再亲手把它量化”，而是必须拥有一份<span style="background-color: #c0c0c0;">训练兼容的冻结量化基座</span>。如果基座本身已经是可被训练框架直接加载、解量化并挂接 PEFT 的 4-bit / 8-bit 版本，那么它完全可以直接作为 QLoRA 起点；但如果它只是面向推理部署的静态量化模型，例如某些只强调推理速度或离线压缩格式的 checkpoint，那么它往往并不适合作为 QLoRA 的训练底座。决定因素不在于“是不是量化过”，而在于这种量化形式是否仍然保留了训练时所需的解量化计算路径与 PEFT 兼容性。</p>
<p>QLoRA 的关键不只是“4-bit”，而是分块量化（Block-wise Quantization）：权重不会用一套全局刻度统一压缩，而是被划分成许多小块，每块各自保存缩放因子。若第 <span displaypfx="inline-" class="mathjax-container">\(g\)</span> 个块的量化码为 <span displaypfx="inline-" class="mathjax-container">\(\hat{w}^{(g)}\)</span>，对应缩放因子为 <span displaypfx="inline-" class="mathjax-container">\(s_g\)</span>，则可抽象写成：</p>
<span displaypfx="" class="mathjax-container">\[w^{(g)}\approx s_g\,\hat{w}^{(g)}\]</span>
<p>这个“分块”通常不是按语义结构切分，而是按固定块大小（例如若干连续权重为一组）直接切开。原因很简单：连续切块最容易实现，也最适合 GPU 并行。对每一个块，系统会单独估计一个比例尺 <span displaypfx="inline-" class="mathjax-container">\(s_g\)</span>，再用这个块自己的尺度去压缩和还原权重。于是更完整的写法通常是：</p>
<span displaypfx="" class="mathjax-container">\[w^{(g)}\approx s_g\cdot Q\!\left(\frac{w^{(g)}}{s_g}\right)\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(Q(\cdot)\)</span> 表示把归一化后的数值映射到低比特量化码。也就是说，比例尺 <span displaypfx="inline-" class="mathjax-container">\(s_g\)</span> 的作用是先把第 <span displaypfx="inline-" class="mathjax-container">\(g\)</span> 个块拉到一个统一的局部数值范围，再交给 4-bit 码本处理；反量化时再乘回 <span displaypfx="inline-" class="mathjax-container">\(s_g\)</span>。在最朴素的实现里， <span displaypfx="inline-" class="mathjax-container">\(s_g\)</span> 可以由该块的最大绝对值、均方根，或其他稳健统计量导出，本质都是在问同一个问题：<span style="background-color: #c0c0c0;">这一小块权重大致处在什么量级上</span>。</p>
<p>一个极小的数值例子能说明为什么需要逐块比例尺。假设某一块权重大致落在 <span displaypfx="inline-" class="mathjax-container">\([-0.1,0.1]\)</span>，另一块却落在 <span displaypfx="inline-" class="mathjax-container">\([-3,3]\)</span>。如果整个张量只共享一个全局比例尺，那么为了容纳大块的幅度，小块里的许多细微差异都会被压扁，量化后落到相同的低比特值；而若分别为这两块设置 <span displaypfx="inline-" class="mathjax-container">\(s_1\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(s_2\)</span>，两块都能在自己的局部范围里充分利用有限的 4-bit 表示能力。也正因为如此，分块量化比全局共用一个缩放因子保留了更多有效信息，尤其更能缓解离群值（Outlier）对整体刻度的污染。</p>
<p>NF4（Normalized Float 4）进一步改进的不是“是否分块”，而是块内映射到哪一套 4-bit 码本。普通均匀量化更像是把一个区间机械地分成若干等宽小段；NF4 则利用很多 Transformer 权重在局部块内常呈现零中心、近似正态分布的事实，预先设计一套更贴近这种分布的离散代表值。于是块内每个权重更接近写成：</p>
<span displaypfx="" class="mathjax-container">\[w_i^{(g)}\approx s_g\cdot c_{q_i}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(c_{q_i}\)</span> 是 NF4 码本中的代表值， <span displaypfx="inline-" class="mathjax-container">\(q_i\)</span> 是对应的 4-bit 索引。它不是假设所有权重必须严格落在同一个固定区间内，而是先按块做尺度归一化，再用更贴近正态权重分布的码本去逼近这些局部值。</p>
<p>双重量化（Double Quantization）与分页优化器（Paged Optimizer）则是在这套分块量化之上继续做工程压缩。前者的思路是：每个块都要保存自己的 <span displaypfx="inline-" class="mathjax-container">\(s_g\)</span> 或相关元数据，这些量的数量虽然远少于权重数，但它们的精度通常更高，而且每个因子只服务一个较小的权重块，因此把这部分成本均摊回“每个参数”之后，并不总能忽略。举例说，若一个缩放因子用 32 bit 保存、对应一个 64 权重的块，那么仅缩放因子这一项就相当于给每个参数额外分摊了 <span displaypfx="inline-" class="mathjax-container">\(32/64=0.5\)</span> bit；在 4-bit 权重场景里，这已经不是一个可以随手忽略的附加成本。双重量化做的，就是把这些缩放因子或相关元数据再压一层，继续降低这笔“元数据税”。后者则借鉴操作系统的分页思想，把优化器状态按页在 GPU 与 CPU 内存之间调度，从而避免 Adam 一类优化器把峰值显存推得过高。它们都不改变 LoRA 的学习目标，也不改变 QLoRA 的基本数学形式，而是在工程层面继续压低“把大模型微调跑起来”的资源门槛。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/qlora.png"><img class="alignnone size-full wp-image-42007" src="https://blog.gmem.cc/wp-content/uploads/2026/03/qlora.png" alt="qlora" width="1024" height="1024" /></a></p>
<div class="blog_h3"><span class="graybg">Q-DoRA</span></div>
<p>Q-DoRA 可以看作 QLoRA 与 DoRA 的组合：基座仍采用低比特量化以节省显存，但更新形式不再是普通 LoRA，而是“量化基座 + 方向/尺度解耦”的 DoRA 结构。一个简化表达是：</p>
<span displaypfx="" class="mathjax-container">\[W'_{:,j}=m_j\frac{\mathrm{Dequant}(W_{q,:,j})+\Delta V_{:,j}}{\left\|\mathrm{Dequant}(W_{q,:,j})+\Delta V_{:,j}\right\|_2},\quad \Delta V=BA\]</span>
<p>它的工程含义很直接：用 QLoRA 解决“显存放不下”的问题，用 DoRA 缓解“低秩更新表达力不够”的问题。若资源非常紧、任务主要是格式控制与轻量指令对齐，普通 QLoRA 往往已经足够；若任务更偏逻辑推理增强、专业知识注入、复杂边界判别或高质量垂直领域适配，Q-DoRA 往往是更稳妥的折中。对应代价是训练更慢、实现更复杂，且并非所有推理栈都像原始 LoRA 那样原生支持。</p>
<div class="blog_h3"><span class="graybg">LoRA-MoE</span></div>
<p>LoRA-MoE 可以理解为“适配器级的 MoE”：不把 FFN 变成稀疏专家，而是保留基座不变，准备多份 LoRA 作为“领域专家”，再用一个路由器（Router）按请求/句子/甚至 token 选择或加权组合这些 LoRA。直觉上，它用极小的可训练参数，为同一个基座提供多域能力，同时避免把所有任务硬合并到一份权重里。</p>
<p>一种抽象表达是把输出写成“基座 + 适配器混合”：</p>
<span displaypfx="" class="mathjax-container">\[h' = f_{\text{base}}(h) + \sum_{e\in \mathcal{E}} g_e(x)\,f_{\text{lora},e}(h),\quad \sum_e g_e(x)=1\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(g_e(x)\)</span> 是路由权重，可以来自显式分类器（域识别）、检索到的任务标签，或一个可训练的 gating 网络。工程上，LoRA-MoE 的关键不在公式，而在路由与评测：你需要定义“什么输入该走哪套 LoRA”，并防止路由错误导致质量抖动。</p>
<p>截至 2026 年，LoRA-MoE 的实际地位更接近<span style="background-color: #c0c0c0;">高级可选架构</span>，而不是参数高效微调里的默认主流基线。它已经形成了一条持续演进的方法线，说明“多 LoRA + 路由”并非概念玩具；但在更常见的工业部署里，成熟默认方案仍然往往是“单基座 + 多个独立 LoRA 适配器”，按请求或租户切换，而不是把路由器永久并入模型主干。原因并不神秘：LoRA-MoE 除了要训练适配器本身，还要额外处理路由质量、专家利用不均、冷专家几乎不被激活、线上可观测性以及请求分布变化带来的稳定性问题。只有当任务确实需要<span style="background-color: #c0c0c0;">在同一个运行图里动态融合多域能力</span>，而不是简单地在不同 LoRA 之间切换时，LoRA-MoE 的额外复杂度才更值得支付。</p>
<div class="blog_h3"><span class="graybg">多 LoRA 热切换与共享基座</span></div>
<p>比 LoRA-MoE 更常见、也更容易落地的方案，是<span style="background-color: #c0c0c0;">多个独立 LoRA 共享同一个基座模型</span>。这里的共享不是把多份 LoRA 合并成一套永久权重，而是让基座参数在 GPU 中只保留一份；不同请求到来时，再按请求绑定对应的适配器。这样做的直接收益是：显存里最重的那部分参数不需要为每个任务重复存一遍，而任务差异主要体现在额外加载的轻量增量权重上。</p>
<p>从推理执行角度看，这种“热切换”并不意味着每来一个请求就重新加载整个模型。更常见的做法是：基座常驻显存，LoRA 适配器按需驻留在 GPU 或 CPU 侧缓存中；请求只需声明“当前使用哪一份适配器”，调度器就会在对应层上把这一份 LoRA 增量接入当前 forward。若某个适配器近期很少被访问，它可以被换出；当请求再次到来时，再从本地盘、对象存储或 Hub 拉回。于是系统真正管理的是<span style="background-color: #c0c0c0;">适配器缓存与调度</span>，而不是整模型重载。</p>
<p>这条路线之所以在 2026 年更主流，是因为它把“多域能力”问题拆成了两个更容易控制的子问题：第一，训练阶段各自产出独立 LoRA，任务之间天然隔离；第二，推理阶段只做选择和缓存，不必额外训练路由器，也不会把多个领域永久混到同一组权重里。代价主要落在工程侧：适配器的 rank、目标模块集合、张量并行配置与基座版本必须兼容；同时服务系统还要决定 GPU 能同时保留多少份 LoRA、超出容量时按什么策略驱逐，以及批内是否允许不同请求混用不同适配器。</p>
<p>截至 2026 年，这已经不是纸面方案，而是主流高吞吐推理框架的标准能力之一。vLLM 支持按请求选择 LoRA，既可以在服务启动时预注册，也支持通过运行时 API 与解析插件动态加载；SGLang 支持同一批次中的不同序列绑定不同 LoRA，并提供适配器加载、驱逐、后端 kernel 与批内 LoRA 数量控制；Hugging Face TGI 也支持在启动时加载多份 LoRA 并在请求中指定 adapter；TensorRT-LLM 则已经提供多 LoRA 推理示例与运行时请求绑定接口。换句话说，<span style="background-color: #c0c0c0;">多 LoRA 共享基座</span>在今天更像是一种成熟的服务形态，而不是实验性质的技巧。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">方案</td>
<td style="text-align: center;">参数/存储</td>
<td style="text-align: center;">推理开销</td>
<td style="text-align: center;">多域能力</td>
<td style="text-align: center;">主要风险</td>
</tr>
</thead>
<tbody>
<tr>
<td>多 LoRA 合并</td>
<td>单份权重</td>
<td>最低（一次 forward）</td>
<td>不稳定（易相互干扰）</td>
<td>合并策略难；回滚困难</td>
</tr>
<tr>
<td>LoRA-MoE（路由）</td>
<td>多份 LoRA + 路由器</td>
<td>低~中（取决于是否多专家叠加）</td>
<td>强（可按域选择）</td>
<td>路由错误；线上一致性与可观测性要求更高</td>
</tr>
<tr>
<td>全量 MoE（FFN 专家）</td>
<td>多专家权重</td>
<td>中（Top-k 专家计算）</td>
<td>强（容量大）</td>
<td>训练与部署复杂；负载均衡与稳定性</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">基于 Prompt 的微调</span></div>
<p>与 LoRA、Adapter 这类“直接改模型内部参数化”的路线不同，基于 Prompt 的微调把任务适配写在输入条件上。它的核心不是重写主干权重，而是构造一小段能够引导模型行为的任务条件，让模型在保持基座冻结的前提下，沿着这段条件生成更符合目标任务的输出。</p>
<p>这里需要先把两类 Prompt 区分开。硬提示（Hard Prompt）是人工编写的离散文本提示，本质上属于提示工程（Prompt Engineering），而不是参数高效微调；软提示（Soft Prompt）则是一组可训练的连续向量，通常可以看成“不对应真实词表 token 的虚拟 token embedding”。前者没有训练参数，可解释性强但搜索空间受限；后者进入连续空间后更容易通过梯度优化找到有效解，因此才构成 Prompt Tuning、Prefix Tuning、P-Tuning、P-Tuning v2 这一路软提示微调家族。</p>
<p>从机制上看，软提示路线的共同点是：人为构造或学习一小段任务向量，把它们拼接到原始输入或注意力状态中，再让这些额外向量参与模型的注意力计算，从而影响后续真实 token 的生成和判别。它的工程优势非常明确：主干参数无需为每个任务复制一份，多任务场景下只需切换不同的 Prompt 参数即可。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">路线</td>
<td style="text-align: center;">作用位置</td>
<td style="text-align: center;">可训练参数量</td>
<td style="text-align: center;">主要优点</td>
<td style="text-align: center;">主要边界</td>
</tr>
</thead>
<tbody>
<tr>
<td>硬提示</td>
<td>输入文本</td>
<td>0</td>
<td>可读、可解释、适合快速验证</td>
<td>离散搜索困难，效果上限受人工设计限制</td>
</tr>
<tr>
<td>Prompt Tuning</td>
<td>输入层</td>
<td>极少</td>
<td>最轻、最易多任务切换</td>
<td>只影响输入端，表达力最弱</td>
</tr>
<tr>
<td>Prefix Tuning</td>
<td>各层注意力</td>
<td>很少</td>
<td>比纯输入层软提示更强，能在每层引导注意力</td>
<td>实现更复杂，与模型结构耦合更深</td>
</tr>
<tr>
<td>LoRA / QLoRA</td>
<td>模型内部线性层</td>
<td>较少</td>
<td>效果更稳、更通用</td>
<td>需要改写模型参数化与训练图</td>
</tr>
</tbody>
</table>
<p>因此，硬提示、软提示与 LoRA 的差异，并不只是“参数多少”，而是任务条件被写入模型的层次不同。硬提示只改自然语言输入；Prompt Tuning 把任务条件写进输入嵌入；Prefix Tuning 把任务条件送进每层注意力；LoRA 则直接改写模型内部线性映射的参数化。条件写得越深，通常表达力越强，但实现和系统复杂度也越高。</p>
<div class="blog_h3"><span class="graybg">Prefix Tuning</span></div>
<p>Prefix Tuning 属于软提示类 PEFT。它学习的不是自然语言前缀文本，而是一组连续可训练向量（Continuous Prefix），并把这组向量作为每一层注意力里的额外 Key / Value 注入。若某层原本的注意力键值对为 <span displaypfx="inline-" class="mathjax-container">\(K,V\)</span>，则 Prefix Tuning 可以理解为把它们扩展成 <span displaypfx="inline-" class="mathjax-container">\([K_{\text{prefix}};K]\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\([V_{\text{prefix}};V]\)</span>，让后续 token 在每一层都能访问这段任务特定“前缀记忆”。</p>
<p>它的关键不只是“在输入前加几个向量”，而是<span style="background-color: #c0c0c0;">在所有层分别注入前缀状态</span>。不同层的前缀通常并不共享；每一层都有自己的 prefix 参数，因为浅层和深层承担的表示功能并不相同。于是，Prefix Tuning 更像是在每一层都额外挂上一小段可学习上下文，让模型在整条前向路径中持续感知任务条件，而不是只在输入口看一眼提示后就完全交给主干自行传播。</p>
<p>若前缀长度记为 <span displaypfx="inline-" class="mathjax-container">\(m\)</span>、模型隐藏维度记为 <span displaypfx="inline-" class="mathjax-container">\(d\)</span>、层数记为 <span displaypfx="inline-" class="mathjax-container">\(L\)</span>，那么最粗略的参数量量级可以理解为 <span displaypfx="inline-" class="mathjax-container">\(O(Lmd)\)</span>。这也是它为什么通常比全量微调和 LoRA 更轻，但又明显重于单纯输入层 Prompt Tuning：它的参数量来自“每层各有一小段前缀”，而不是只在输入层保存一组虚拟 token。</p>
<div class="blog_h4"><span class="graybg">训练稳定性与重参数化</span></div>
<p>直接把前缀向量当作自由参数去优化，并不总是最稳定。因为这些向量一开始就要进入每层注意力，如果初始化过于随意，训练前期很容易让注意力分布出现较大抖动。为此，Prefix Tuning 的经典实现常引入一层小型重参数化网络，例如用一个 MLP 先把较低维或更结构化的中间表示映射成真正送入各层的 prefix Key / Value。</p>
<p>这种做法的本质不是给 Prefix Tuning 增加永久推理负担，而是把训练阶段的优化空间改造成更平滑、更容易收敛的形式。训练完成后，这个 MLP 生成出的前缀状态通常可以被直接缓存或固化，推理时未必需要继续保留完整重参数化模块。因此，它更像一种<span style="background-color: #c0c0c0;">训练期稳定化技巧</span>，而不是 Prefix Tuning 必须背负的长期结构成本。</p>
<div class="blog_h4"><span class="graybg">适用性评估</span></div>
<p>它与 Prompt Tuning 的差别不在于“前缀长短”，而在于注入位置。Prompt Tuning 只在输入嵌入层增加一小段软提示；Prefix Tuning 则把任务参数直接送进每层注意力，因此它通常更有表达力，也更接近“在每层引导模型如何读写上下文”。代价是实现更复杂，模型结构耦合更深，训练与推理栈也更需要原生支持。</p>
<p>到 2026 年，Prefix Tuning 仍然是成立且标准的 PEFT 方法，但它在主流大语言模型指令微调里的存在感已经明显弱于 LoRA。它最有价值的场景通常是：希望极小参数量地控制条件生成行为、使用 Encoder-Decoder 或较经典的条件生成架构，或者研究上需要把“任务条件”明确写进每层注意力。若任务是当代 Decoder-only LLM 的通用指令对齐、风格迁移或领域适配，LoRA / QLoRA 往往仍是默认起点：更稳、更通用、推理框架支持也更成熟。</p>
<div class="blog_h3"><span class="graybg">Prompt Tuning</span></div>
<p>Prompt Tuning（软提示/Soft Prompt）同样属于 PEFT，但它比 Prefix Tuning 更轻：只在输入嵌入层前面拼接一小段可训练“虚拟 token embedding”，而不改动 Transformer 内部层的参数结构。设输入嵌入序列为 <span displaypfx="inline-" class="mathjax-container">\(E(x)\)</span>，软提示为 <span displaypfx="inline-" class="mathjax-container">\(P\in\mathbb{R}^{m\times d}\)</span>，则模型实际看到的是拼接后的序列 <span displaypfx="inline-" class="mathjax-container">\([P;E(x)]\)</span>。训练时更新的只有 <span displaypfx="inline-" class="mathjax-container">\(P\)</span>，基座参数保持冻结。</p>
<p>它的核心假设是：对于某些任务，模型原本的能力已经足够，真正缺少的只是一个足够好的“任务启动条件”。如果这组输入层虚拟 token 能把模型推到合适的工作点，后面的冻结主干就能沿着原有能力完成任务。因此，Prompt Tuning 在参数量上往往可以做到比 LoRA 还小一个量级，尤其适合“大量轻任务共享同一基座”的场景。</p>
<div class="blog_h4"><span class="graybg">与 Prefix Tuning 的区别</span></div>
<p>Prompt Tuning 与 Prefix Tuning 的根本区别，不在于二者都用了虚拟 token，而在于任务条件写入的深度不同。Prompt Tuning 只在输入层插入软提示，后续所有层看到的都是这段输入在主干网络中自然传播后的结果；Prefix Tuning 则直接在每一层注意力中附加前缀状态，使任务条件持续存在于整条注意力链路中。前者最轻，后者更强。</p>
<p>也正因为如此，Prompt Tuning 在超大模型上有时会随着基座规模增大而变得更有效，因为大模型本身已经足够强，输入端的一点点软条件就足以触发所需能力；而在中小模型或需要强行为控制的任务上，它往往不如 Prefix Tuning、LoRA 稳定。</p>
<div class="blog_h4"><span class="graybg">家族扩展与适用性</span></div>
<p>围绕这一路线还发展出 P-Tuning、P-Tuning v2 等变体。它们的共同目标，都是让软提示不只停留在“输入前拼一小段向量”这么简单，而是通过更强的参数化或更深层的注入方式，提高在理解类任务和较小模型上的表现。若把家族关系压缩来看：Prompt Tuning 是最轻的输入层软提示；Prefix Tuning 把软提示推进到各层注意力；P-Tuning / P-Tuning v2 则在“如何生成这些提示、提示该注入多深”上继续增强。</p>
<p>到 2026 年，Prompt Tuning 仍然实用，但更像<span style="background-color: #c0c0c0;">轻量特化选项</span>而不是主流默认路线：当目标是极小参数、海量任务复用、低存储部署或 prompt-adapter 风格服务时，它仍有现实价值；当目标是指令遵循、复杂格式约束、长对话行为修正或稳定领域适配时，LoRA / QLoRA 往往更稳妥，Prefix Tuning 也通常比纯输入层软提示更有表达力。</p>
<div class="blog_h2"><span class="graybg">微调技术选型</span></div>
<p>前面列出的全量微调、部分参数微调、Prompt 系软提示、Adapter、LoRA、QLoRA 并不是互相替代的“流行名词清单”，而是针对不同约束条件的不同最优解。真正决定选型的，通常不是单一维度上的“效果最好”，而是四个问题同时成立时的交集：样本量够不够、GPU 预算有多紧、任务到底是在改行为还是改知识、上线时更看重单任务极致效果还是多任务复用与切换。</p>
<p>样本量是第一道分界线。数据很少时，更应优先考虑冻结主干参数的路线，例如 Prompt Tuning、Prefix Tuning、LoRA、QLoRA 或更轻的部分参数微调。原因并不只是“省显存”，而是冻结主干更容易保留预训练先验，降低小数据把模型硬拉向局部模式的风险。数据足够大、分布足够稳定、目标能力又确实需要深度改写时，全量微调才更值得支付它的高成本，因为只有在这种条件下，放开全部参数带来的表达上限才真正有机会被利用。</p>
<p>GPU 资源是第二道分界线。若显存非常紧，QLoRA 往往是生成模型微调的现实起点；若任务更轻、希望一个基座承载大量小任务，Prompt Tuning 或 Prefix Tuning 的存储优势会更突出；若 GPU 充裕且追求最强任务特化，上限仍然在全量微调一侧。换句话说，量化 LoRA 解决的是“放不放得下”，LoRA 解决的是“如何低成本改行为”，而全量微调解决的是“是否要把整套模型一起重写”。</p>
<p>任务性质决定第三道分界线。若变化主要发生在输入接口，例如新增专有 token、符号或特殊控制标记，输入层微调与软提示通常比全模型更新更自然；若变化主要体现在最终读出或评分方式，输出层微调往往就够；若目标是稳定调整指令遵循、格式约束、风格边界、多轮行为或一般性领域适配，LoRA / QLoRA 通常是默认解；若真正需要吸收大量新知识、重构深层表示、改变词表、上下文长度或位置编码等底层设定，则继续预训练或全量微调才更匹配问题本质。</p>
<p>推理形态决定第四道分界线。多任务在线服务最看重“一个基座 + 多个轻量增量”时，LoRA 及其热切换形态通常最实用；Prompt Tuning 与 Prefix Tuning 也具备同样的任务切换优势，只是主流推理框架与工业实践对 LoRA 的支持更成熟。Adapter 虽然同样具备插拔式优点，但它会在前向路径里保留额外计算分支，因此在当代大模型场景里通常不再是默认首选。若目标是最低推理延迟，合并后的 LoRA 与单体全量微调模型通常更占优；若目标是海量任务共享一个基座、频繁热切换，则运行时加载轻量适配器更灵活。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">微调技术</td>
<td style="text-align: center;">何时优先考虑</td>
<td style="text-align: center;">主要优点</td>
<td style="text-align: center;">主要代价或边界</td>
</tr>
</thead>
<tbody>
<tr>
<td>全量微调</td>
<td>GPU 资源充足、样本量充足、任务需要深度改写模型能力</td>
<td>表达上限最高，最适合深领域迁移与强任务特化</td>
<td>显存、时间和存储成本最高，也最容易削弱通用泛化</td>
</tr>
<tr>
<td>部分参数微调</td>
<td>只需要改特定层或特定结构，或现有框架不便直接接入 PEFT</td>
<td>选择性强，能用较低成本试探“真正该改哪里”</td>
<td>容量有限，往往更像折中方案而不是通用默认解</td>
</tr>
<tr>
<td>Prompt Tuning / Prefix Tuning</td>
<td>样本较少、任务很多、希望极小增量复用同一基座</td>
<td>参数极少，保留主干泛化，适合多任务轻量切换</td>
<td>表达力通常弱于 LoRA；Prefix 实现更复杂，Prompt 在复杂行为控制上更弱</td>
</tr>
<tr>
<td>Adapter</td>
<td>需要显式模块化、任务插拔或特定架构兼容路径</td>
<td>结构清晰、任务隔离好、便于组织内复用</td>
<td>前向路径保留额外分支，在大模型场景里主流度已弱于 LoRA</td>
</tr>
<tr>
<td>LoRA</td>
<td>通用生成模型微调、多任务适配、需要效果与效率平衡</td>
<td>效果稳、生态成熟、可合并、可热切换，是当前主流默认基线</td>
<td>仍需选择 rank、挂载位置与训练稳定性权衡；深知识注入时可能容量不足</td>
</tr>
<tr>
<td>QLoRA / 量化 LoRA</td>
<td>GPU 资源非常有限，但仍需要微调较大生成模型</td>
<td>显著降低显存门槛，让 7B / 13B 级模型微调更可落地</td>
<td>训练链路更复杂；若任务要求极强表达力，最终仍可能需要更重路线</td>
</tr>
</tbody>
</table>
<p>因此，微调技术选型可以压缩成一条很实际的经验顺序：先判断是否根本不该训练参数，而应优先做参数外优化；若需要训练，再判断任务是否只是轻量行为适配，此时 LoRA / QLoRA 往往是默认起点；若样本极少且强调多任务极致轻量切换，软提示路线才更有吸引力；若任务要求深度改写底座知识或结构，再考虑继续预训练与全量微调。只有当这些路线都无法满足目标时，才值得继续向更重、更贵的训练方式推进。</p>
<p>再往前走一步，就是下一节的偏好对齐问题：如果模型已经学会了任务本身，却仍然不会在多个可行回答中稳定偏向人类真正想要的那个，那么问题就不再只是“选哪种微调技术”，而是要不要进入奖励模型、DPO、PPO、GRPO 这一层相对偏好优化。</p>
<div class="blog_h2"><span class="graybg">强化学习对齐</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">路线</td>
<td style="text-align: center;">直接监督信号来自哪里</td>
<td style="text-align: center;">参考模型的作用</td>
<td style="text-align: center;">如何防止偏离 SFT 太远</td>
</tr>
</thead>
<tbody>
<tr>
<td>RLHF + PPO</td>
<td>奖励模型输出的 reward；奖励模型本身来自人类偏好数据</td>
<td>作为参考策略 <span displaypfx="inline-" class="mathjax-container">\(\pi_{\mathrm{ref}}\)</span>，通常是 SFT 模型的冻结副本</td>
<td>显式加入 KL 项，把当前策略锚定在参考分布附近</td>
</tr>
<tr>
<td>DPO</td>
<td>显式偏好对 <span displaypfx="inline-" class="mathjax-container">\((x,y_w,y_l)\)</span></td>
<td>作为锚点，比较“当前模型相对参考模型是否更偏向好答案、远离差答案”</td>
<td>不单独写 KL 惩罚项，但在损失中隐式约束模型不要脱离参考模型过远</td>
</tr>
<tr>
<td>GRPO</td>
<td>同一 prompt 下一组回答的评分、排序或规则反馈</td>
<td>常作为参考策略或 KL 正则锚点；具体是否使用取决于实现</td>
<td>通过组内相对优势更新，必要时再叠加参考模型 KL 约束</td>
</tr>
</tbody>
</table>
<p>强化学习对齐（RL-based Alignment）更准确的说法是“偏好对齐（Preference Alignment）”：用偏好信号把模型输出推向“更符合人类/评审标准”的区域。监督微调（SFT）解决的是“模型是否会按指令作答”，偏好对齐解决的则是“在多个看似都能回答问题的候选答案中，模型是否会稳定偏向更有帮助、更安全、更符合人类预期的那个”。两者并不重复，而是前后衔接的两层约束。</p>
<p>监督微调本身当然可以承担一部分对齐工作。只要训练数据里显式包含拒答样例、安全边界、好坏答案对照、批判依据（Critique Rationale）、自我修正链路，模型就能通过有监督学习吸收相当一部分“什么回答风格更合适、什么回答应当避免”的行为模式。很多现代对齐流程的第一步，本来就是把这些规则先写进 SFT 数据，再让模型学会基础行为边界。</p>
<p>但 SFT 的学习目标本质上仍然是<span style="background-color: #c0c0c0;">给定输入，去拟合某个目标输出</span>。若把提示词记为 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>、参考答案记为 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>，那么它优化的是 <span displaypfx="inline-" class="mathjax-container">\(\log \pi_\phi(y|x)\)</span> 这一类似然目标。即使数据里同时给出“好答案、坏答案、批判依据”，SFT 也主要是在学习如何复现这些文本本身，而不是直接学习“在多个候选回答之间，哪个应该被稳定偏好”。这意味着它更擅长教会模型<span style="background-color: #c0c0c0;">怎样说</span>，却不天然等价于教会模型<span style="background-color: #c0c0c0;">怎样在多个可行答案里做排序</span>。</p>
<p>拒绝采样微调可以看作这两层之间的一条中间路线。它先让模型对同一提示词生成多个候选，再借助验证器、规则或奖励信号只保留最好的一部分，把筛选结果重新写回监督数据，再继续做 SFT。这样做比纯手工监督更能利用自动评估，但仍然没有显式保留“胜者为何优于败者”的相对排序结构，因此它更像<span style="background-color: #c0c0c0;">把偏好信号先离散化成高质量目标答案，再交给监督微调吸收</span>。</p>
<p>偏好对齐之所以需要单独成层，关键就在这里。很多真实问题并不存在唯一标准答案，而是存在一组“都不算错、但质量不同”的候选回答：有的更完整，有的更安全，有的更符合语气预期，有的虽然事实没错却明显不够有帮助。把这种问题压成单一参考答案做 SFT，模型容易学到某一种写法，却未必真正学到“为什么 A 应优于 B”。批判依据也有同样的局限：模型可以学会输出一段像样的批判文本，但这并不自动保证它在自由生成时，会稳定地把这些批判原则内化为回答排序规则。</p>
<p>因此，今天更准确的技术分层是：SFT 可以完成<span style="background-color: #c0c0c0;">基础行为对齐</span>，而偏好优化负责处理<span style="background-color: #c0c0c0;">相对偏好排序</span>。前者让模型学会回答、学会拒答、学会遵守基本格式；后者让模型在多个都看似合理的答案之间，更稳定地偏向人类真正想要的那个。也正因为如此，现代对齐并不必然等于“强化学习”本身：PPO / RLHF 是一条路线，DPO、ORPO 等把偏好直接写进损失的路线则是另一条路线。它们解决的是同一个问题，只是优化手段不同。</p>
<p>这一层之所以重要，还因为大模型的质量并不能被单一指标完整刻画。古德哈特定律（Goodhart's Law）指出：一旦某个指标变成优化目标，它往往就不再是一个好的指标。放到模型对齐里，这意味着如果只盯住某个狭窄基准分数，模型很可能学会迎合该指标，却牺牲真正的可用性、稳健性与安全性。偏好对齐因此更强调相对排序、多维度评审与参考模型约束，而不是把“好答案”简化成单一静态分数。</p>
<p>从工程上看，主流路线分为两类：</p>
<ul>
<li>显式奖励：先训练奖励模型（Reward Model），再用 PPO 等策略优化算法做 RLHF。</li>
<li>直接偏好：不显式训练奖励模型，直接用偏好对优化策略（例如 DPO）。</li>
</ul>
<div class="blog_h3"><span class="graybg">奖励模型（Reward Model）</span></div>
<p>奖励模型（Reward Model, RM）把（提示词 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，回复 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>）映射为一个标量分数 <span displaypfx="inline-" class="mathjax-container">\(r_\theta(x,y)\in\mathbb{R}\)</span>。它的职责不是生成答案，而是充当自动评审器：输入“问题 + 回答”，输出一个可比较的质量信号，用来近似人类对回答质量的偏好判断。</p>
<div class="blog_h4"><span class="graybg">偏好数据</span></div>
<p>奖励模型的训练数据通常是成对偏好数据，而不是单条样本加绝对分数。对同一提示词 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，先让模型生成多个候选回答，再由人工标注员或更强的评审模型选择其中更优的一条，形成三元组 <span displaypfx="inline-" class="mathjax-container">\((x,y_w,y_l)\)</span>：其中 <span displaypfx="inline-" class="mathjax-container">\(y_w\)</span> 是被接受的回答（chosen response），<span displaypfx="inline-" class="mathjax-container">\(y_l\)</span> 是被拒绝的回答（rejected response）。</p>
<p>这种二选一偏好标注通常比直接打 1 到 5 分更稳定。原因并不复杂：绝对分数依赖个人尺度，主观漂移大；相对偏好只要求判断 A 和 B 谁更好，一致性通常更高，标注成本也更低。因此，现代偏好对齐流程更常见的监督信号是排序关系，而不是绝对评分。</p>
<div class="blog_h4"><span class="graybg">模型形态</span></div>
<p>奖励模型通常以已经完成 SFT 的语言模型为骨干（Backbone）进行改造，而不是从零开始训练：保留主体 Transformer 表示层，移除原先用于生成下一个 token 的语言建模头（LM Head），换成一个输出单一标量的质量头（Reward Head）。这样做的好处是，奖励模型继承了 SFT 模型对指令与回答结构的理解，只需要继续学习“哪种回答更好”这一层偏好判断。</p>
<p>对一对“胜/负”样本 <span displaypfx="inline-" class="mathjax-container">\((x,y_w,y_l)\)</span>，常用 Bradley–Terry / Logistic 形式把分数差转为偏好概率：</p>
<span displaypfx="" class="mathjax-container">\[\Pr(y_w \succ y_l\mid x)=\sigma\!\left(r_\theta(x,y_w)-r_\theta(x,y_l)\right)\]</span>
<p>并用对数损失训练：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{\mathrm{RM}}(\theta)=-\log \sigma\!\left(r_\theta(x,y_w)-r_\theta(x,y_l)\right)\]</span>
<p>关键点：sigmoid 用在“分数差”上，而不是对每个 <span displaypfx="inline-" class="mathjax-container">\(r_\theta(x,y)\)</span> 再套一层 sigmoid。分数本身不需要限制在 <span displaypfx="inline-" class="mathjax-container">\([0,1]\)</span>；概率来自差值的 logistic 映射。</p>
<p>参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 表示奖励模型的参数集合（Parameter Set），包含全部权重矩阵与偏置项，并非单一标量。</p>
<p>单调性视角：因为 <span displaypfx="inline-" class="mathjax-container">\(\sigma\)</span> 单调递增且 <span displaypfx="inline-" class="mathjax-container">\(-\log(\cdot)\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span> 上单调递减，所以 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}_{\mathrm{RM}}\)</span> 对分数差 <span displaypfx="inline-" class="mathjax-container">\(\Delta r=r_\theta(x,y_w)-r_\theta(x,y_l)\)</span> 单调递减。训练会推动 <span displaypfx="inline-" class="mathjax-container">\(\Delta r\)</span> 变大，从而把胜者分数推高、败者分数压低。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/reward.png"><img class="alignnone size-full wp-image-42015" src="https://blog.gmem.cc/wp-content/uploads/2026/03/reward.png" alt="reward" width="1024" height="1024" /></a></p>
<p>&nbsp;</p>
<div class="blog_h3"><span class="graybg">RLHF</span></div>
<p>RLHF（Reinforcement Learning from Human Feedback）把整个偏好对齐流程拆成三段：先收集偏好数据，再训练奖励模型，最后用奖励模型为主语言模型提供优化信号。它的核心贡献不在于“使用了强化学习”本身，而在于把原本昂贵、缓慢、难以规模化的人类评审，转化成可以自动计算的奖励分数。</p>
<p>在 RLHF 里，语言生成被改写为一个强化学习问题：token 是动作（Action），策略是语言模型 <span displaypfx="inline-" class="mathjax-container">\(\pi_\phi(y|x)\)</span>，奖励来自 <span displaypfx="inline-" class="mathjax-container">\(r_\theta(x,y)\)</span>。早期最经典、也最具代表性的做法，是以 PPO（Proximal Policy Optimization）作为策略更新算法，用奖励模型给出的分数提高高质量回答的生成概率，同时压低低质量回答的概率。常见目标写成：</p>
<span displaypfx="" class="mathjax-container">\[\max_{\phi}\ \mathbb{E}_{y\sim \pi_\phi(\cdot|x)}\big[r_\theta(x,y)\big]-\beta\,D_{\mathrm{KL}}\!\left(\pi_\phi(\cdot|x)\,\|\,\pi_{\mathrm{ref}}(\cdot|x)\right)\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(\pi_{\mathrm{ref}}\)</span> 通常不是额外训练出来的一套神秘模型，而就是<span style="background-color: #c0c0c0;">PPO 开始前那一版 SFT 模型的冻结副本</span>。标准顺序通常是：先从预训练基座得到 SFT 模型，再把这份 SFT checkpoint 复制成两路，一路继续作为可训练策略 <span displaypfx="inline-" class="mathjax-container">\(\pi_\phi\)</span>，一路冻结为参考模型 <span displaypfx="inline-" class="mathjax-container">\(\pi_{\mathrm{ref}}\)</span>。因此并不存在“先有参考模型还是先有策略模型”的循环依赖；二者都来自同一个 SFT 起点，只是一个继续更新，一个保持不动。</p>
<p>式子里的 KL 项与 KL 散度（Kullback–Leibler Divergence）是直接对应的关系：这里所谓的“KL 项”，就是目标函数中的 <span displaypfx="inline-" class="mathjax-container">\(\beta\,D_{\mathrm{KL}}(\pi_\phi\|\pi_{\mathrm{ref}})\)</span> 这一项，只不过前面再乘了一个权重系数 <span displaypfx="inline-" class="mathjax-container">\(\beta\)</span>。它的作用不是做额外评测，而是作为正则约束，把当前策略锚定在 SFT 参考分布附近，防止模型为了讨好奖励模型而偏离过远，出现奖励黑客（Reward Hacking）、语言退化或能力崩塌。PPO 在这类任务里长期被广泛采用，原因正是它对策略更新幅度加了“护栏”，能在追求更高奖励和保持原有能力之间维持相对稳定的折中。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/rlhf.png"><img class="alignnone size-full wp-image-42019" src="https://blog.gmem.cc/wp-content/uploads/2026/03/rlhf.png" alt="rlhf" width="1024" height="1024" /></a></p>
<div class="blog_h4"><span class="graybg">PPO</span></div>
<p>PPO（Proximal Policy Optimization）之所以长期是 RLHF 的默认优化器，不是因为它在理论上最优，而是因为它足够稳。普通策略梯度（Policy Gradient）的问题在于：一旦某次更新把策略推得过远，语言模型就可能突然偏离原有分布，表现为回复风格失真、可读性下降、奖励黑客，甚至整体能力退化。PPO 的核心改进就是限制“这一步最多改多少”，让策略朝高奖励方向移动，但每次只允许小步修正。</p>
<p>它最经典的形式是 clipped objective。若当前策略与旧策略的概率比记为 <span displaypfx="inline-" class="mathjax-container">\(\rho_t=\frac{\pi_\phi(a_t|s_t)}{\pi_{\phi_{\mathrm{old}}}(a_t|s_t)}\)</span>，对应优势函数（Advantage Function）为 <span displaypfx="inline-" class="mathjax-container">\(A_t\)</span>，则目标可写成：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{\mathrm{PPO}}(\phi)=\mathbb{E}_t\left[\min\left(\rho_t A_t,\ \mathrm{clip}(\rho_t,1-\epsilon,1+\epsilon)A_t\right)\right]\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 是更新护栏宽度。若某次更新让概率比偏离 1 太远，clip 就会截断继续放大的收益，阻止模型为了追求更高奖励而走得过猛。对 LLM 而言，这个机制尤其重要，因为语言模型的输出分布非常高维，只要少量 token 的条件概率被过度放大，就可能连锁改变整段回答的行为模式。</p>
<p>放到 RLHF 流程里，PPO 的完整闭环通常是：先用当前策略对同一提示词采样若干回答，再用奖励模型评分，并结合参考模型的 KL 惩罚构造最终回报；随后估计优势 <span displaypfx="inline-" class="mathjax-container">\(A_t\)</span>，再用 clipped objective 更新策略。也正因为这里同时牵涉采样、奖励模型、参考模型、旧策略快照与优势估计，PPO 路线的工程链条明显比 DPO 更长，训练成本和调参复杂度也更高。</p>
<p>工业实践中，奖励往往也不是单一维度。一个典型做法是分别训练“有用性（Helpfulness）”与“安全性（Safety）”奖励模型，再按加权和形成总奖励，例如 <span displaypfx="inline-" class="mathjax-container">\(R_{\mathrm{total}}=\alpha R_{\mathrm{helpful}}+\beta R_{\mathrm{safety}}\)</span>。这样可以显式控制不同对齐目标之间的权重，而不是把所有偏好都压缩进一个不可分解的单一评分器里。</p>
<div class="blog_h3"><span class="graybg">非典型算法形态</span></div>
<p>到了大模型对齐阶段，很多方法在问题定义上仍然属于强化学习，但在算法形态上已经明显偏离经典强化学习教材中的标准样子。原因并不神秘：LLM 对齐面对的是一种极其特殊的决策问题。若强行套用经典 RL 映射，可以把状态（State）理解为“当前提示词 + 已生成 token 序列”，把动作（Action）理解为“从巨大词表中选择下一个 token”，把奖励（Reward）理解为“整段回答生成完之后得到的评分”，把环境（Environment）理解为模型自身的自回归生成过程。这个映射在概念上成立，但工程代价极高。</p>
<p>困难主要集中在三点。第一，动作空间极大：每一步都要在成千上万个 token 中做选择。第二，奖励高度延迟：很多时候只有整段回答生成完毕后，才能得到一个总体评分，时间信用分配（Temporal Credit Assignment）比经典控制任务更棘手。第三，策略优化链条极重：若完整采用 PPO 式 RLHF，训练时往往要同时维护可训练策略、旧策略快照、参考模型、奖励模型，很多实现里还要有价值网络（Value / Critic），显存、吞吐和稳定性压力都非常大。</p>
<p>因此，大模型对齐逐渐出现了一条清晰演化路径：<span style="background-color: #c0c0c0;">保留强化学习的问题定义，重写强化学习的求解形态</span>。所谓“保留问题定义”，指的是目标仍然来自评估性反馈（Evaluative Feedback）而不是逐步给定的标准答案；模型仍然要学会在多个候选回答之间偏向更优者。所谓“重写求解形态”，指的是不再机械照搬经典 Actor-Critic 或全流程在线 RL，而是通过数学消元、离线偏好优化、组内相对比较、弱化价值网络等方式，把问题改写成更适合大模型训练的目标。</p>
<p>从这个视角看，DPO 与 GRPO 都不是对强化学习问题本身的放弃，而是对经典算法形态的工程重构。DPO 走得更远：它把“奖励建模 + 策略优化”折叠成一个静态偏好对损失，形式上已经非常接近监督学习或对比学习。GRPO 则保留了“采样 - 评分 - 更新”的策略优化闭环，但用组内相对优势替代显式价值网络，属于一种极简化的策略优化路线。前者更像对 RL 目标的解析化改写，后者更像对 PPO 结构的裁剪与瘦身。</p>
<div class="blog_h3"><span class="graybg">DPO</span></div>
<p>DPO（Direct Preference Optimization）是这种“非典型 RL 形态”里最典型的一类：它直接用偏好对 <span displaypfx="inline-" class="mathjax-container">\((x,y_w,y_l)\)</span> 优化策略，不显式训练奖励模型，也不需要 PPO rollout。它的出发点很明确：既然手里已经有“哪个回答更好”的偏好数据，那么没有必要再额外训练一个奖励模型，再把奖励模型嵌入完整强化学习闭环；可以直接把偏好关系作用到策略本身。</p>
<p>DPO 仍然保留一个冻结的参考模型 <span displaypfx="inline-" class="mathjax-container">\(\pi_{\mathrm{ref}}\)</span> 作为锚点，并同时比较当前可训练模型与参考模型在“被接受回答”和“被拒绝回答”上的相对概率。这里的概率不是单个 token 的局部值，而是整条回答在 token 级对数概率上的汇总，因此<span style="background-color: #c0c0c0;">本质上是在比较“当前模型是否比参考模型更偏向好答案、同时更远离差答案”</span>。典型目标写成：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{\mathrm{DPO}}(\phi)=-\log \sigma\!\Big(\beta\big[\log\pi_\phi(y_w|x)-\log\pi_\phi(y_l|x)-\log\pi_{\mathrm{ref}}(y_w|x)+\log\pi_{\mathrm{ref}}(y_l|x)\big]\Big)\]</span>
<p>直觉上，DPO 直接增大“胜者相对败者”的对数概率优势（log-odds margin），同时用参考模型作为锚点。它把 RLHF 中“奖励建模 + PPO 优化”的两步折叠成一步有监督式偏好优化，因此工程更轻、训练更稳定，也更容易复用现有 SFT 训练栈。正因为这一点，DPO 已经成为许多中小团队进行偏好对齐时的默认起点。</p>
<p>从经典 RL 的角度看，DPO 最“不像强化学习”的地方在于：它几乎拿掉了在线探索、环境交互和显式优势估计这些传统组件，直接在离线偏好数据上优化策略。这也是为什么它在形式上看起来更像监督学习；但它解决的仍然是评估性反馈下的偏好优化问题，而不是普通的标签拟合问题。</p>
<p>DPO 训练并不是要把“好样本相对差样本的概率优势”硬拉到某个固定阈值才算结束。它优化的是整个偏好数据集上的相对排序损失，而不是某个预设的绝对 margin。对容易区分的样本，胜者优势会很快变大，梯度也随之减弱；对天然模糊、偏好边界不清的样本，这个优势不可能无限扩大。若继续强行训练，模型更可能开始过拟合偏好数据、放大表面写法差异，甚至损害生成分布稳定性。因此，DPO 的停止标准本质上仍然是<span style="background-color: #c0c0c0;">验证集偏好指标、生成质量与分布稳定性是否已经饱和</span>，而不是“margin 必须大于某个固定常数”。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/dpo.png"><img class="alignnone size-full wp-image-42031" src="https://blog.gmem.cc/wp-content/uploads/2026/03/dpo.png" alt="dpo" width="1024" height="1024" /></a></p>
<div class="blog_h3"><span class="graybg">GRPO</span></div>
<p>GRPO（Group Relative Policy Optimization）比 DPO 更接近经典策略优化，但它同样属于“非典型 RL 形态”：对同一个提示词 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 采样一组候选回复 <span displaypfx="inline-" class="mathjax-container">\(\{y_k\}_{k=1}^{K}\)</span>，用奖励/偏好信号在组内排序或打分，再把“相对好坏”转成策略梯度更新。它的核心动机是：用 <span style="background-color: #c0c0c0;">组内比较</span> 构造优势（Advantage）或基线（Baseline），从而减少对显式价值网络（Critic / Value Function）的依赖。</p>
<p>一种常见的做法是把组内奖励做标准化得到相对优势：</p>
<span displaypfx="" class="mathjax-container">\[A_k=\frac{r_k-\mu_r}{\sigma_r+\epsilon},\quad \mu_r=\frac{1}{K}\sum_{k=1}^{K}r_k,\ \sigma_r^2=\frac{1}{K}\sum_{k=1}^{K}(r_k-\mu_r)^2\]</span>
<p>并用 PPO 风格的 clipped objective 更新策略（仍然可带 KL 正则到参考模型）：</p>
<span displaypfx="" class="mathjax-container">\[\max_{\phi}\ \mathbb{E}\Big[\min\big(\rho_k A_k,\ \mathrm{clip}(\rho_k,1-\epsilon,1+\epsilon)\,A_k\big)\Big]-\beta D_{\mathrm{KL}}\!\left(\pi_\phi\,\|\,\pi_{\mathrm{ref}}\right),\quad \rho_k=\frac{\pi_\phi(y_k|x)}{\pi_{\phi_{\mathrm{old}}}(y_k|x)}\]</span>
<p>真正决定“奖惩”的不是某个回答的绝对分数，而是它在同组候选中的相对位置。若某个回答的组内奖励 <span displaypfx="inline-" class="mathjax-container">\(r_k\)</span> 高于这一组的平均水平 <span displaypfx="inline-" class="mathjax-container">\(\mu_r\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(A_k&gt;0\)</span>，训练会提高模型再次生成这类回答的概率；若某个回答低于组内平均水平，则 <span displaypfx="inline-" class="mathjax-container">\(A_k&lt;0\)</span>，训练会压低模型对这类回答的偏好。换句话说，GRPO 奖励的是“在同一个 prompt 下，比同组其他回答更好的样本”，惩罚的是“在同一个 prompt 下，相对更差的样本”，而不是单独给每个回答设一个全局固定门槛。</p>
<p>从优化角度看，这种“压制”并不是额外加一个独立惩罚按钮，而是让该回答在目标函数里贡献<span style="background-color: #c0c0c0;">负优势（Negative Advantage）</span>更新。结果上，它等价于让这类回答对应的策略概率逐步下降：模型以后再采样到相似回答时，会更倾向于远离它，而不是继续强化它。</p>
<p>因此，GRPO 与经典 PPO 的最大差别，并不在于“还有没有策略梯度”，而在于它把传统 Actor-Critic 结构大幅裁剪了。经典 PPO 往往依赖一个显式价值网络去估计 baseline，以降低方差；GRPO 则直接用同组候选回答之间的相对分数生成基线，把“谁比平均更好、谁比平均更差”写进组内统计量。这使它在大模型场景下明显更省显存，也更容易扩展到规则打分、程序验证和裁判模型评分等复杂奖励来源。</p>
<p>这种机制尤其适合答案质量强依赖上下文、很难用一个全局绝对分数刻画的场景。对某些 prompt，70 分的回答可能已经是组内最优，应当被正向强化；对另一些 prompt，80 分的回答仍可能只是组内倒数，应当被压制。GRPO 的核心就在于：目标模型并不是因为“达到某个统一分数线”而得到奖励，而是因为它在<span style="background-color: #c0c0c0;">同题候选集合里相对更优</span>而受到正向更新。</p>
<p>GRPO 仍然需要某种奖励/偏好信号（显式奖励模型、规则打分、对比标注等）；它改变的是“如何用这些信号构造稳定的更新”，而不是免除奖励来源本身。把几条路线放在一起看，会更清楚：RLHF + PPO 强调显式奖励建模与稳定策略更新，DPO 强调跳过奖励模型后的直接偏好优化，GRPO 则强调用组内相对比较构造更稳定的优势信号。它们共享同一个目标，只是在“偏好信号如何表达、如何被模型消化”这件事上采取了不同工程路径。</p>
<div class="blog_h2"><span class="graybg">参数外优化</span></div>
<p>参数外优化（Parameter-free Optimization）指的是：在不更新模型权重的前提下，通过改写提示词（Prompt）、输出约束、示例组织、工具说明、工作流与评估闭环来提升系统表现。它优化的对象不是模型内部参数，而是模型与任务之间的接口层。</p>
<p>这条路线之所以重要，是因为很多任务的瓶颈并不在模型“不会”，而在任务描述不够精确、输出约束不够严格、示例覆盖不足，或评估回路设计不清。此时，更合理的第一步往往不是立刻进入 SFT、LoRA 或 DPO，而是先做参数外优化：不改模型参数，只改模型使用方式。</p>
<div class="blog_h3"><span class="graybg">自动化 Prompt 优化（APO）</span></div>
<p>自动化 Prompt 优化（Automatic Prompt Optimization, APO）的核心思想是：把人工反复修改 Prompt 的经验循环，改写成一个自动化搜索与评估过程。它不更新模型权重，而是把 Prompt 本身当作待优化对象，再用验证集上的错误信号驱动 Prompt 迭代。换句话说，APO 优化的不是模型内部参数，而是模型与任务之间的接口描述。</p>
<p>这条路线之所以重要，是因为很多任务的误差并不来自“模型完全不会”，而来自任务定义不够精确、输出约束不够严格、规则优先级表达不清，或者 few-shot 示例没有覆盖真正的边界情况。对这些问题，先做 Prompt 优化通常比直接微调更便宜，也更容易定位问题来源。</p>
<div class="blog_h4"><span class="graybg">基本流程</span></div>
<p>APO 可以概括为一个闭环迭代过程。</p>
<ol>
<li>从当前 Prompt 出发，在固定验证集上运行模型，得到一轮可量化的表现。</li>
<li>收集错误样本，重点关注 false positive、false negative 以及模型在边界样本上的失误模式。</li>
<li>把这些错误样本与当前 Prompt 一起交给一个更强的语言模型，要求它分析当前 Prompt 的缺陷，例如规则表述含糊、优先级冲突、示例覆盖不足、输出格式不稳定，或对某些语义模式缺乏约束。</li>
<li>基于这些分析生成若干候选 Prompt。候选改动可以包括：重写系统提示、重排规则顺序、增加约束语句、补充反例、加入 few-shot 示例，或强化输出格式说明。</li>
<li>用同一个验证集重新评估这些候选 Prompt，并按预先定义的指标选出当前最优版本。</li>
<li>重复这一过程，直到验证集指标收敛，或进一步修改已经不能带来稳定收益。</li>
</ol>
<p>这个闭环的本质是“用验证集驱动 Prompt 搜索”。人工调 Prompt 时，工程师通常也是先看错例，再猜原因，再改写提示词，再重新评估；APO 只是把这条经验流程交给模型辅助完成，并把迭代过程系统化。</p>
<div class="blog_h4"><span class="graybg">APO 依赖什么</span></div>
<p>要让 APO 真正有效，至少要具备三样东西：</p>
<ol>
<li>高质量验证集：它不一定需要极大规模，但必须足够准确，且覆盖关键边界情况。</li>
<li>清晰的评价指标：分类任务通常可以直接使用 Accuracy、Precision、Recall、F1；抽取、排序、生成任务则需要对应的可重复评价标准。</li>
<li>被优化对象必须足够明确：例如系统提示词、用户提示模板、few-shot 示例集、输出格式约束或工具调用说明。</li>
</ol>
<p>如果缺少这三者中的任意一个，APO 都很容易退化成“让模型随意重写 Prompt”，最终只是在做风格漂移，而不是真正基于验证信号优化任务表现。因此，APO 的核心不是“会不会改 Prompt”，而是“能否把 Prompt 修改纳入可验证的实验闭环”。</p>
<div class="blog_h4"><span class="graybg">验证信号质量</span></div>
<p>参数外优化对验证信号质量极其敏感。因为它并不通过海量训练样本去平均噪声，而是直接把验证集上的错误模式反向写入 Prompt 结构，所以一旦验证集标签本身含糊、冲突或混入大量低置信样本，优化器就很容易朝错误方向改写规则。高质量、边界清晰的验证集通常能显著提升 APO 的稳定性；相反，模糊数据会让优化过程更像是在追逐评测噪声，而不是纠正模型真实缺陷。</p>
<div class="blog_h4"><span class="graybg">与微调的区别</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">维度</td>
<td style="text-align: center;">微调</td>
<td style="text-align: center;">APO</td>
</tr>
</thead>
<tbody>
<tr>
<td>优化对象</td>
<td>模型权重</td>
<td>Prompt、示例、输出约束</td>
</tr>
<tr>
<td>资源需求</td>
<td>通常需要训练框架、显存和数据管线</td>
<td>通常只需要验证集与可调用的模型</td>
</tr>
<tr>
<td>成本</td>
<td>高</td>
<td>低</td>
</tr>
<tr>
<td>可解释性</td>
<td>较弱，行为被写入权重</td>
<td>强，Prompt 变更可直接审查</td>
</tr>
<tr>
<td>性能上限</td>
<td>更高，可把规则和模式内化进模型</td>
<td>受限于基座模型自身能力</td>
</tr>
<tr>
<td>更适合</td>
<td>数据充足、任务长期稳定、性能要求高</td>
<td>快速迭代、规则频繁变化、数据量有限</td>
</tr>
</tbody>
</table>
<p>因此，APO 和微调并不是互相替代的关系，而更像两层不同成本的优化手段。若模型本身已经具备足够能力，但任务接口还没有调顺，先做 APO 往往收益最高；若 Prompt 已经被压到比较成熟，但模型仍然持续犯系统性错误，才更适合进入微调阶段。</p>
<div class="blog_h4"><span class="graybg">适用边界</span></div>
<p>APO 最适合规则可文本化、评估指标清晰、且基座模型本身已经具备足够语义理解能力的任务。例如分类、抽取、轻量结构化生成、审核规则执行、问答格式控制和工具调用提示约束，往往都能从 APO 中获益。尤其当任务规则经常变化、人工需要频繁更新 Prompt 时，APO 的价值会非常明显。</p>
<p>它的边界同样清晰。若任务需要模型学习新的领域知识、记住稳定事实、吸收大量风格样本，或长期存在系统性能力缺口，那么单纯修改 Prompt 的收益通常很快见顶。此时，Prompt 优化可以继续作为上层控制手段存在，但性能提升的主战场已经会转移到微调与继续预训练。</p>
<div class="blog_h3"><span class="graybg">多分支 Prompt 优化（AMPO）</span></div>
<p>AMPO（Automatic Multi-Branched Prompt Optimization）可以看作 APO 的结构化升级版。普通 APO 往往默认“只有一条主 Prompt 流程”，优化方式主要是改写文字、调整规则顺序或补充示例；AMPO 则进一步把 Prompt 看作一种可演化的决策结构。它的目标不只是把单条 Prompt 写得更好，而是让 Prompt 从单流程逐步生长为多分支结构，使模型在面对不同输入模式时，能够沿着更合适的子路径完成判断。</p>
<p>这种思路来自一个很强的人类专家直觉：复杂任务往往并不是靠一条统一规则解决，而是靠“先识别情形，再走对应流程”。因此，AMPO 的真正创新不只是自动改 Prompt，而是把 Prompt 优化从“文案修订”提升成“结构搜索”。在这种框架下，提示词已经不只是几句话，而更像一个树状决策蓝图。</p>
<div class="blog_h4"><span class="graybg">为什么需要多分支</span></div>
<p>当任务内部同时包含多种错误模式时，单分支 Prompt 很容易不断堆叠例外说明，最后变成冗长、脆弱且难以维护的规则串。AMPO 的判断是：如果不同错误模式对应的是不同处理逻辑，就不应强行把它们揉进同一段线性指令，而应允许 Prompt 显式分叉。这样做有三个直接收益。第一，结构更清晰：每个分支各自负责一类模式，规则可解释性更强。第二，复杂场景适应性更好：模型不必在一条过长指令中硬找适用规则，而是先定位情形，再走对应分支。第三，后续维护更容易：新增模式时可以局部增补或重构特定分支，而不必重写整个 Prompt。</p>
<div class="blog_h4"><span class="graybg">三大模块</span></div>
<p>AMPO 的核心流程可以分成三个协同模块：模式识别（Pattern Recognition）、分支调整（Branch Adjustment）和分支剪枝（Branch Pruning）。这三个模块合在一起，构成了一个从错误样本出发、逐步生长并控制复杂度的优化回路。</p>
<p><span style="background-color: #c0c0c0;">模式识别</span>负责把零散坏例归纳成少数根因模式。它通常不是直接把所有错误样本原样塞进一个大 Prompt，而是采用角色分工：一个分析器（Analyzer）逐个解释失败原因，另一个总结器（Summarizer）把这些解释压缩成更高层的模式，并给模式分配重要性。这样做的关键价值在于：优化目标从“修这几个具体坏例”转成“修这一类错误背后的共同规则缺口”。</p>
<p><span style="background-color: #c0c0c0;">分支调整</span>负责决定 Prompt 结构该如何变化。这里最重要的决策不是“改不改”，而是“深化已有分支，还是新增分支”。若新模式与某个已有分支高度相关，只需补充约束或细节，那么更合理的做法是深化；若新模式与现有逻辑明显不同，继续堆进原分支只会制造冲突，就应当拓宽，新增一个独立子分支。也正因为如此，AMPO 优化的不是词句表面，而是 Prompt 的控制流结构。</p>
<p><span style="background-color: #c0c0c0;">分支剪枝</span>负责抑制过拟合。多分支优化的自然风险是：随着迭代次数增加，Prompt 可能长出大量只服务于少数训练样本的局部规则，最终在未知数据上退化。AMPO 因此显式引入两层剪枝：</p>
<ol>
<li>预剪枝（Pre-pruning）：相当于基于独立验证集的早停机制。若新增分支不再带来稳定收益，就停止继续扩张。</li>
<li>后剪枝（Post-pruning）：要求优化器在输出最终 Prompt 前重新审视各分支，删掉不必要、过于具体或明显带有训练集记忆痕迹的规则。</li>
</ol>
<div class="blog_h4"><span class="graybg">坏例抽样</span></div>
<p>AMPO 的一个很有代表性的设计，是每轮并不分析大量失败样本，而是只抽取很少量的代表性坏例，例如固定 <span displaypfx="inline-" class="mathjax-container">\(K=5\)</span>。这不是随意取值，而是一种典型的小样本归纳策略：在模式识别任务里，最前面的少数高信息量样本往往已经足以揭示一类错误的共性，而继续增加样本数量，边际信息收益会快速下降。对具备较强归纳能力的 LLM 来说，少量围绕同一主题的坏例通常已经足够提炼出根本模式。</p>
<p>更重要的是，AMPO 并不要求 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 随总坏例数量成比例扩大。固定的小 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 让每轮优化的成本更可控，也避免分析器被大量重复、低信息量坏例淹没。参数外优化的关键不是“看尽可能多的错误”，而是“看足够有代表性的错误”。</p>
<div class="blog_h4"><span class="graybg">是否需要失败输出</span></div>
<p>AMPO 还提供了一个很重要的方法论提醒：分析错误原因时，并不总是必须把模型当轮生成的失败输出显式提供给分析器。很多时候，输入、当前 Prompt、正确标签以及“这条样本被判错了”这一事实，已经足以让分析器定位规则缺失。因为优化器真正需要回答的问题不是“模型具体说错了什么句子”，而是“当前指令为什么会把模型引向这类错误”。当失败输出本身噪声较大、措辞随机性较强时，强行把它加入分析，反而可能干扰模式归纳。</p>
<p>换言之，参数外优化的关注重点应当放在<span style="background-color: #c0c0c0;">规则缺口</span>而不是<span style="background-color: #c0c0c0;">错误表面</span>上。一个高质量分析器更像在做“指令失效诊断”，而不是对错误回答做逐字复盘。</p>
<div class="blog_h4"><span class="graybg">优化器与执行器解耦</span></div>
<p>AMPO 还强化了一个现实而重要的工程模式：用于分析与重写 Prompt 的优化器模型，并不必与实际执行任务的目标模型相同。前者更强调总结、重写和结构设计能力，后者则更关注线上推理成本、延迟、稳定性和部署约束。把“谁负责优化 Prompt”和“谁负责执行 Prompt”解耦，往往能在成本与效果之间得到更好的组合。</p>
<div class="blog_h4"><span class="graybg">实验启示</span></div>
<p>从论文记录呈现的实验关注点看，AMPO 主要验证了三件事。第一，优化效率：多分支结构在复杂任务上往往能更快探索到高质量 Prompt，而不是在单分支上做无穷尽的局部修补。第二，收敛性：随着迭代推进，Prompt 结构会逐步稳定下来，说明这种优化并非纯随机搜索。第三，消融结果：模式识别、分支调整和分支剪枝都不是装饰性组件，拿掉任一模块，性能与稳定性都会受到影响。与普通 APO 相比，AMPO 的价值并不只是“Prompt 更长”，而是 Prompt 结构更像一个经过错误模式驱动后生长出来的决策树。</p>
<p>因此，参数外优化不应只被理解成“自动改几个词”。从 APO 到 AMPO，它实际上形成了一条清晰的演进路径：先把 Prompt 当作可搜索的文本接口，再把 Prompt 当作可演化的结构接口。前者已经足以覆盖大量规则型任务，后者则更适合复杂、异质、边界模式较多的任务。</p>
<div class="blog_h2"><span class="graybg">分布式训练</span></div>
<p>分布式训练（Distributed Training）处理的核心问题是：当单张 GPU 已经无法同时满足<span style="background-color: #c0c0c0;">吞吐量、显存容量、模型规模或上下文长度</span>要求时，训练过程应如何拆分到多张卡乃至多台机器上执行。它覆盖一组并行策略，分别对应不同的设备组织方式。不同策略的差别，主要体现在四个对象如何被分配：数据（data）、模型参数（parameters）、梯度（gradients）和优化器状态（optimizer states）。</p>
<p>工程上最常见的切分方向有两类。第一类是<span style="background-color: #c0c0c0;">数据并行</span>：每张卡保留完整模型，但处理不同批次数据，再通过通信把梯度同步。第二类是<span style="background-color: #c0c0c0;">状态分片</span>：参数、梯度或优化器状态本身也被拆到多张卡上，以换取更高的显存上限。前者首先解决训练速度与中等规模显存压力，后者进一步解决“模型单卡放不下”或“长上下文导致激活太重”的问题。</p>
<div class="blog_h3"><span class="graybg">常见策略总览</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">策略</td>
<td style="text-align: center;">数据如何分</td>
<td style="text-align: center;">模型状态如何放置</td>
<td style="text-align: center;">典型通信</td>
<td style="text-align: center;">最适合的场景</td>
</tr>
</thead>
<tbody>
<tr>
<td>DDP</td>
<td>每张 GPU 读取不同 mini-batch</td>
<td>每张 GPU 保留完整模型、完整梯度与完整优化器状态</td>
<td>AllReduce 同步梯度</td>
<td>模型单卡放得下，主要目标是提升吞吐与缩短训练时间</td>
</tr>
<tr>
<td>DataParallel (DP)</td>
<td>主卡切分 batch 后分发到其他 GPU</td>
<td>前向复制模型，主卡负责聚合输出与梯度</td>
<td>主卡 gather / scatter</td>
<td>原型验证、老代码兼容；现代训练中通常不再是首选</td>
</tr>
<tr>
<td>FSDP</td>
<td>每张 GPU 处理不同 mini-batch</td>
<td>参数、梯度、优化器状态按 shard 分布到多卡</td>
<td>AllGather + ReduceScatter</td>
<td>模型或长上下文训练已接近或超过单卡显存上限</td>
</tr>
<tr>
<td>DeepSpeed ZeRO</td>
<td>通常也是数据并行</td>
<td>按 Stage 逐步切分优化器状态、梯度与参数</td>
<td>AllGather + ReduceScatter + 参数协调</td>
<td>超大模型、多机多卡、显存非常紧张的训练任务</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">分布式数据并行（Distributed Data Parallel, DDP）</span></div>
<p>DDP 是 PyTorch 最标准的多 GPU 训练策略。它的基本机制很直接：<span style="background-color: #c0c0c0;">每张 GPU 持有一份完整模型副本，各自处理不同数据，各自完成前向与反向传播，然后把梯度做 AllReduce 平均，最后用同一份平均梯度同步更新参数</span>。由于每张卡的参数初值一致、梯度同步后也一致，因此各副本在每一步更新后仍保持相同权重。</p>
<p>若以 4 卡训练为例，每张 GPU 的本地 batch size 为 4，那么单个 step 中实际处理的样本数就是 16。更一般地，全局批大小满足 <span displaypfx="inline-" class="mathjax-container">\(B_{\mathrm{global}} = B_{\mathrm{local}} \times N_{\mathrm{gpu}} \times N_{\mathrm{accum}}\)</span>。其中 <span displaypfx="inline-" class="mathjax-container">\(N_{\mathrm{accum}}\)</span> 是梯度累积（Gradient Accumulation）步数。这个公式决定了训练稳定性、学习率缩放和吞吐估算，是理解 DDP 的第一条工程量纲。</p>
<p>DDP 最适合模型本体仍能放进单卡，但单卡训练已经过慢或单卡 batch 太小的场景。例如一个三亿参数量级的 Encoder，序列长度达到 4096 时，单卡既可能吞吐不足，也可能无法同时容纳更大的 batch。DDP 将这类压力分摊到多卡后，每张卡只需处理自己的本地 batch，整体吞吐通常接近线性增长，同时又保持实现逻辑相对简单。因此，在“模型放得下，但训练想更快、更稳”这一档场景里，DDP 仍然是默认起点。</p>
<div class="blog_h3"><span class="graybg">DataParallel (DP)</span></div>
<p>DataParallel 是更早一代的单进程多 GPU 封装。它同样试图做数据并行，但实现方式与 DDP 不同：主进程通常位于 GPU 0，负责把一个 batch 切分后分发到其他 GPU，前向结果和反向梯度也要回到主卡聚合。这个结构会带来两个直接后果。第一，GPU 0 的负担显著更重，容易形成瓶颈；第二，单进程多线程模型更容易受到 Python 调度和通信组织方式的影响。</p>
<p>因此，DP 的优势主要只剩“接入非常简单”。一旦进入正式训练，尤其是多卡 batch 较大、模型通信频繁或训练周期较长时，DDP 几乎总是更稳、更快，也更接近现代 PyTorch 的标准实践。工程上可以把 DP 理解为历史过渡方案：适合快速原型与兼容旧代码，不适合作为持续训练体系的默认选择。</p>
<div class="blog_h3"><span class="graybg">完全分片数据并行（Fully Sharded Data Parallel, FSDP）</span></div>
<p>FSDP 仍然属于数据并行范式，但它把“每卡完整持有模型状态”这件事拆开了。参数、梯度和优化器状态会被分片（shard）到不同 GPU 上；在某一层真正参与计算之前，系统才临时执行 AllGather 把这一层所需参数拼回；反向传播结束后，再通过 ReduceScatter 等通信把梯度重新切回各自分片。其核心收益是显存占用大幅下降，因为大部分时间里，每张卡只保存全模型状态的一部分。</p>
<p>这种收益伴随更复杂的通信与调度开销。FSDP 的目标从来不是“同样模型训练得更快”，而是让<span style="background-color: #c0c0c0;">原本单卡放不下的模型，或原本因为长上下文而极难训练的配置，进入可训练区间</span>。当模型参数规模、激活开销或优化器状态已经逼近单卡上限时，FSDP 往往比单纯增加 DDP 卡数更有效，因为后者只扩展数据吞吐，并不缩小单卡必须持有的模型状态。</p>
<div class="blog_h3"><span class="graybg">DeepSpeed ZeRO</span></div>
<p>ZeRO（Zero Redundancy Optimizer）是 DeepSpeed 最核心的一组大模型训练技术。它的思想与 FSDP 接近，目标都是减少多卡间重复保存的状态，但切分粒度与工程形态更体系化。常见 Stage 可按三步理解：Stage 1 切分优化器状态，Stage 2 再切分梯度，Stage 3 进一步切分模型参数。随着 Stage 提升，单卡显存压力持续下降，但通信和实现复杂度也会同步上升。</p>
<p>ZeRO 的价值在大模型时代尤为明显，因为优化器状态和梯度本身就可能比参数还占显存。对数十亿参数模型、超长上下文训练或多机多卡集群而言，单纯复制完整状态几乎不可持续，ZeRO 这类分片机制就成为前提能力。DeepSpeed 在此基础上还会继续叠加通信优化、内存卸载（offload）、流水并行等工程能力，因此它通常更适合训练规模继续上升、需要复杂集群优化的团队环境。</p>
<div class="blog_h3"><span class="graybg">与常见框架的关系</span></div>
<p>分布式训练策略和高层框架并不处在同一抽象层。DDP、FSDP、ZeRO 描述的是“参数与梯度如何跨设备组织”；Accelerate、Lightning、Transformers Trainer、Keras 3、MMEngine 描述的则更多是“如何把训练循环、设备启动、日志、checkpoint 与策略配置组织起来”。前者是并行机制，后者是工程入口。</p>
<div class="blog_h4"><span class="graybg">PyTorch</span></div>
<p>PyTorch 是这一组策略最常见的底座。DDP 与 FSDP 都属于 PyTorch 原生分布式能力，训练脚本最终仍然依赖其自动求导、张量通信和优化器更新。对需要精细控制训练循环的工程团队而言，直接基于 PyTorch 编写分布式训练脚本，通常拥有最高灵活度。</p>
<div class="blog_h4"><span class="graybg">Accelerate</span></div>
<p>Accelerate 的定位是多设备执行抽象。它将 DDP、FSDP 与 ZeRO 等策略接入到更统一的设备管理接口中，同时封装设备发现、进程启动、混合精度与梯度同步。其核心价值在于：同一份以 PyTorch 为中心的训练代码，可以较平滑地从单卡扩展到多卡，再扩展到 FSDP 或 DeepSpeed，而不必大规模重写训练循环。</p>
<div class="blog_h4"><span class="graybg">PyTorch Lightning</span></div>
<p>Lightning 把分布式训练放进更规范的训练引擎里。开发者主要定义模型、优化器和 step 逻辑，Trainer 再根据配置选择 DDP、FSDP、DeepSpeed 等 strategy。它适合希望减少样板代码、统一日志与 checkpoint 管理，同时保留 PyTorch 生态兼容性的团队。其代价是抽象层更高，定制极复杂训练细节时需要理解 Lightning 的生命周期约束。</p>
<div class="blog_h4"><span class="graybg">Transformers Trainer</span></div>
<p>Transformers 的 Trainer 更偏“模型生态入口”。当任务建立在 Hugging Face 模型、tokenizer 与数据集流水线之上时，Trainer 可以通过 Accelerate 或相关配置接入 DDP、FSDP 与 DeepSpeed。它特别适合标准微调、标准评测与模型复现实验；一旦训练流程开始高度定制，开发者通常会逐步回到原生 PyTorch 或 Accelerate 层。</p>
<div class="blog_h4"><span class="graybg">Keras 3</span></div>
<p>Keras 3 也提供分布式 API，但其分布式能力更依赖所选后端。若后端是 TensorFlow，实际执行往往落在 TensorFlow 的分布式策略；若后端切到 JAX 或 PyTorch，则并行细节又会由各自后端负责。它适合追求统一高层建模接口、同时希望在不同后端间迁移的场景，但在超大模型训练里，社区主流实践仍然更多集中在 PyTorch + FSDP / DeepSpeed 这条路线。</p>
<div class="blog_h4"><span class="graybg">MMEngine / OpenMMLab</span></div>
<p>MMEngine 更像视觉任务中的训练控制台。它能够把 Runner、Hook、配置系统和分布式启动流程组织起来，使检测、分割、姿态估计等复杂视觉实验更容易批量管理。其分布式能力本质上仍然建立在底层框架之上，通常不会取代 DDP / FSDP 这类并行机制本身，而是把它们纳入统一工程范式中。</p>
<div class="blog_h2"><span class="graybg">基座模型选择</span></div>
<p>在进入具体训练场景之前，通常应先判断任务应该建立在什么类型的基座模型之上。这个选择会直接决定后续数据形态、训练目标、推理链路与上线成本。很多项目的真正分水岭并不在 LoRA、QLoRA、DPO 这些技术细节，而在于一开始是否选对了模型范式：到底应当使用 BERT 一类表示模型（Representation Model），还是使用 Decoder-only 生成模型（Generative Model）。</p>
<div class="blog_h3"><span class="graybg">表示模型适合什么</span></div>
<p>BERT、RoBERTa、DeBERTa 一类 Encoder-only 模型，最擅长的是把输入压缩成稳定表示，再围绕固定目标做判别。它们特别适合闭集分类（Closed-set Classification）、文本匹配、检索、序列标注、重排序，以及“输入充分、标签空间明确、输出形式固定”的任务。只要目标可以被表述为“给这段输入打一个标签”或“判断这两段文本是否匹配”，表示模型通常都是更高效、更便宜、也更容易评估的选择。</p>
<p>这里需要明确一点：BERT 并不是完全没有顺序信息。它同样通过位置编码（Positional Encoding）与自注意力看到序列先后关系，因此能区分“先发生什么、后发生什么”。真正的限制不在“看不到顺序”，而在于它通常把整段输入压缩为一个判别表示，再直接映射到标签空间。对于需要显式执行规则、动态权衡多段证据、并把局部冲突统一到最终结论上的任务，这种一次性判别路径往往不够灵活，也缺乏可解释的中间推理结构。</p>
<div class="blog_h3"><span class="graybg">生成模型适合什么</span></div>
<p>生成式大语言模型的优势出现在另一类任务中：任务目标本身包含开放式语义判断、复杂规则执行、跨轮次状态整合，或者需要先形成中间结论，再决定最终输出。这类任务往往不只是“看完文本后打标签”，而是要求模型先判断哪些证据重要、哪些证据只是过程噪声，再决定最终答案。对这类问题，生成模型更容易通过指令约束、上下文推理和多步语义整合完成任务，因此更适合作为基座。</p>
<p>例如，在对话满意度判定中，若规则是“过程中的波折、局部负面情绪视为过程成本，只要最终方案被接受、问题收尾清晰，就优先按已解决处理”，任务本质上就不再是简单情感分类。模型需要区分中间波折与最终状态，识别“收尾是否清晰”，并对冲突信号做优先级排序。这里最困难的部分不是识别负面词，而是执行一条带有<span style="background-color: #c0c0c0;">结尾优先、过程降权</span>结构的规则。生成模型在这种场景下通常更稳，因为它更容易在长上下文中整合多段证据，并按指令执行“先看结尾，再回看过程”的判断逻辑。</p>
<div class="blog_h3"><span class="graybg">BERT 类模型的边界</span></div>
<p>只要任务满足以下条件，BERT 类模型通常仍然足够胜任：</p>
<ul>
<li>输出空间固定且较小，例如满意 / 不满意、风险 / 非风险、升级 / 不升级。</li>
<li>决定标签的证据主要是局部可见、模式稳定的文本特征，而不是依赖复杂跨轮推理。</li>
<li>规则可以被充分体现在标注数据里，使模型通过监督学习稳定内化这种判别边界。</li>
</ul>
<p>典型例子包括情感分类、意图分类、FAQ 匹配、实体识别、工单主题归类，以及大量结构清晰的客服路由任务。</p>
<p>若任务开始依赖以下能力，BERT 类模型的风险就会显著上升：</p>
<ul>
<li>需要对长对话做结尾优先的全局判断。</li>
<li>需要把中间负面情绪降权，但又不能完全忽略。</li>
<li>需要区分“问题解决了但过程不愉快”与“问题根本没解决”。</li>
<li>需要持续引入新规则，并要求模型在推理时可控地遵循这些规则。</li>
</ul>
<p>此时，即使通过多段编码、层级聚合、结尾加权或级联分类等工程技巧勉强构造出一个系统，上限通常也受限于任务本身的推理复杂度，而且系统维护成本往往会迅速上升。</p>
<div class="blog_h3"><span class="graybg">选择准则</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">任务特征</td>
<td style="text-align: center;">更合适的基座</td>
<td style="text-align: center;">原因</td>
</tr>
</thead>
<tbody>
<tr>
<td>固定标签分类、匹配、检索、序列标注</td>
<td>BERT 类表示模型</td>
<td>判别目标清晰，推理链短，部署成本低，训练与评估都更直接</td>
</tr>
<tr>
<td>需要开放式回答、格式生成、工具调用、复杂指令遵循</td>
<td>生成式模型</td>
<td>输出本身就是生成任务，Encoder-only 模型不适合作为主干</td>
</tr>
<tr>
<td>多轮对话总结、结尾优先判断、冲突证据加权、规则动态注入</td>
<td>生成式模型</td>
<td>需要跨轮整合与规则执行，往往不能稳定压缩成一次性判别</td>
</tr>
<tr>
<td>局部模式强、业务规则稳定、标注数据充分的分类任务</td>
<td>BERT 类表示模型</td>
<td>表示模型更省资源，延迟更低，也更适合大规模批量预测</td>
</tr>
</tbody>
</table>
<p>因此，基座模型选择的真正问题不是“哪个模型更先进”，而是“任务本质上是在做判别，还是在做规则驱动的语义决策”。前者优先考虑 BERT 一类表示模型，后者通常应直接进入生成式模型范式。把这一步判断做对，后面的训练场景、微调路径和评估方式才会自然收敛。</p>
<div class="blog_h2"><span class="graybg">训练场景</span></div>
<div class="blog_h3"><span class="graybg">小数据集微调</span></div>
<p>小数据集微调（Small-data Fine-tuning）讨论的不是“有没有数据”，而是“可用于更新模型权重的有效监督信号是否足够”。到了 2026 年，这个问题已经出现一个很值得重视的经验转向：在小数据场景里，微调容量并不是越大越好。很多情况下，越轻量的适配反而越稳，尤其当数据本身同时带有长尾、噪声、分布偏移和验证波动时更是如此。</p>
<div class="blog_h4"><span class="graybg">核心判断</span></div>
<p>小数据微调最容易误判的一点，是把“任务复杂”直接等同于“应该开放更多可训练参数”。在大多数小样本任务里，真正需要学习的往往不是重写整套表示空间，而只是沿少数方向对基座模型做任务相关偏移。更大的可训练子空间确实提高了拟合训练集的能力，但也更容易把头部模式、局部模板、错标样本和偶然噪声一起写进参数更新，最终损害泛化。</p>
<p>因此，小样本适配的默认逻辑通常不是“先开大再收缩”，而是<span style="background-color: #c0c0c0;">先用更强约束保护基座模型，再按验证集证据逐步放大更新空间</span>。以 LoRA 为例，低 rank 更新 <span displaypfx="inline-" class="mathjax-container">\(\Delta W = AB\)</span> 的价值不仅是省显存，更是把参数更新限制在一个低维子空间中，使模型更难直接记忆训练集表面模式。这也是为什么在低资源任务里，参数效率与泛化能力往往不是对立关系，而是同一套约束机制的两个结果。</p>
<div class="blog_h4"><span class="graybg">微调什么地方</span></div>
<p>小数据集微调不仅要决定“调多大”，还要决定“调哪里”。这个问题通常有两个维度：第一，更新哪些层；第二，更新每层里的哪些矩阵。它们共同决定模型最终学到的是局部表面模式，还是更稳定的高层语义偏移。</p>
<p>从层位分工看，Transformer 各层的注意力并不是完全同质的。浅层通常更偏局部句法、词性与相邻词关系，通用性最强，因此往往不应轻易扰动；中层开始形成更复杂的语义组合，处理跨句指代、因果联系和较长范围的信息整合；深层则更接近任务特定的高层语义，情感、意图、分类边界和最终决策信号通常更多集中在这里。对于情感判断、满意度判断、意图识别等高层语义任务，优先从中深层，尤其是后部层开始微调，通常更符合信号分布。</p>
<p>从矩阵类型看，小数据行为调整任务通常应先从注意力侧开始，而不是默认同时打开 FFN。Q 决定“向哪里发起关注”，V 决定“实际取出什么信息”，因此它们往往最直接影响模型如何组织证据与整合线索；K 和 O 也会影响结果，但通常不是最低成本起点。若任务主要是在已有知识之上重排注意力优先级、加强长程依赖或改变决策依据，先调 Q、V 往往最稳。只有当验证集持续显示模型确实缺少领域知识写入或表示重编码能力时，再逐步把 FFN、K 或 O 纳入更新范围更合适。</p>
<div class="blog_h4"><span class="graybg">主要风险来源</span></div>
<p>小数据场景的核心矛盾是<span style="background-color: #c0c0c0;">有效监督信号稀疏，模型容量却仍然巨大</span>。如果数据高度重复、标签边界模糊、头部模式占据绝大多数样本，或者训练集与真实线上分布存在偏移，那么模型面对的就不是“少量但可靠的规律”，而是“少量规律 + 大量重复与噪声”。这时，训练往往不是训练不动，而是下降得过快、记忆得过深。</p>
<p>其中最容易被低估的是长尾问题。头部样本会主导梯度方向，新增容量也最容易先被头部模式吸收；结果是总体指标继续上涨，尾部类别、边界样本和罕见情形却未必同步改善，甚至可能恶化。平均 Accuracy 往往会掩盖这种退化，因此小数据微调若不单独观察尾部表现，很容易把“更会处理常见样本”误判成“整体更强”。</p>
<p>实践中，以下信号通常说明当前瓶颈不在容量不足，而在数据质量、长尾覆盖、验证设计或规则表达：</p>
<ul>
<li>训练损失快速下降而验证集停滞。这通常说明模型正在高效记忆训练集，却没有学到可迁移的判别规律，额外容量只会让这种记忆更彻底。</li>
<li>总体指标改善但尾部类别恶化。这说明新增容量主要被头部模式吸收，模型对高频样本更熟练，却以牺牲罕见场景为代价换取平均分提升。</li>
<li>不同随机种子之间波动很大。这意味着当前结果对初始化、数据划分或训练噪声过于敏感，说明监督信号本身不稳定。这里的不稳定，通常就来自数据分布不均、标签质量不足或验证集设计过小，例如头部样本占绝大多数、尾部样本只出现几次，或同类边界样本在不同标注员之间标准并不一致。继续放大可训练空间通常只会把这种不确定性进一步放大。</li>
<li>新增参数带来的提升只集中在头部模式。这表明模型并没有真正学到更普适的决策边界，而是在更深地贴合样本最密集的局部区域。</li>
<li>模型开始出现明显的风格漂移与任务外退化。这往往意味着局部小数据已经开始覆盖预训练先验，模型虽然在当前任务里更贴近训练分布，却损伤了原本更广泛的泛化能力。</li>
</ul>
<div class="blog_h4"><span class="graybg">2026 年的实践建议</span></div>
<p>小数据场景下，更稳妥的默认策略是按证据逐步放开可训练空间：</p>
<ol>
<li>把轻量适配作为默认起点。若使用 LoRA / QLoRA，应优先把它理解为控制更新容量的手段，而不只是省显存工具。</li>
<li>先尝试只调后半层的 Q、V，或更保守地只调最后三分之一层。它的参数量最小，过拟合风险也最低，适合监督极少、验证集波动较大的情况。</li>
<li>若验证集显示模型对长程结构或中层语义组合仍然适配不足，再比较全层 Q、V 的极低 rank 配置。这样做的出发点是：任务信号不一定只存在于最深层，中层也可能承担一部分长程整合与语义组合；给所有层一点点可调空间，有时比只改最后几层更容易保持泛化。</li>
<li>只有当这些方案仍然显示出明确的知识注入或表示重编码瓶颈，再把 K、O 或 FFN 逐步放开。</li>
<li>把验证集当作主导信号，而不是训练损失。小数据场景下，训练集拟合速度通常极快，真正有意义的是验证集是否持续改善，以及尾部样本是否同步获益。</li>
<li>把参数选择和数据问题一起看。若长尾、噪声和分布偏移没有处理好，继续增加微调容量往往只会更快过拟合这些问题。</li>
</ol>
<p>这条顺序的核心是把可训练空间按证据逐步展开，避免在一开始就把过大的自由度交给少量数据，而不是追求一次命中最优结构。更成熟的理解方式，是把小数据集微调视为<span style="background-color: #c0c0c0;">受强约束的增量适配</span>，而不是缩小版的大规模微调。</p>
<div class="blog_h3"><span class="graybg">训练嵌入模型</span></div>
<div class="blog_h4"><span class="graybg">简介</span></div>
<p>嵌入模型（Embedding Model）并不天然等于“通用语义相似度模型”。它可以围绕特定目标进行训练，使向量空间优先保留某一类任务真正关心的判别信号。例如在情感分类（Sentiment Classification）场景里，模型更关心“正面 / 负面 / 中性”的倾向是否一致，而不一定关心两段文本在主题或措辞上是否高度相似。于是，训练得到的嵌入空间可能会把“物流很快，体验很好”和“包装一般，但整体满意”拉得较近，因为它们在情感方向上同属正向；反过来，即使两条评论都在谈“物流”，只要情感倾向相反，也可能被推向更远位置。</p>
<p>从更抽象的角度看，无论目标是情感、相关性、意图、风险、偏好还是图文匹配，嵌入模型始终都在学习一件事：让<span style="background-color: #c0c0c0;">与当前任务定义下“相关”的文档特征在向量空间中更接近，让“不相关”或“应被区分”的特征更远离</span>。区别只在于“相关”的定义来自哪里。通用 embedding 把相关性主要定义为语义相似；任务特化 embedding 则把相关性改写为某个业务目标下的等价关系，例如同一标签、同一情感、同一用户意图、同一风险等级，或“查询与正确答案匹配”。</p>
<p>正因为如此，嵌入训练与对比学习（Contrastive Learning）天然契合。只要能够构造正样本对与负样本对，就可以把任务目标转写成“哪些样本应靠近、哪些样本应分开”的几何约束。监督标签、点击行为、人工偏好、检索点击日志、FAQ 配对、复述句、图文配对，最终都可以落回这一范式：通过对比式目标把任务真正关心的结构写进表示空间。这样得到的 embedding 既可以直接用于最近邻检索、聚类和召回，也可以作为下游分类器或 reranker 的输入表示。Sentence-BERT 就是文本领域最常见的一条对比式嵌入技术路线之一，前文已经展开其结构，这里只把它当作训练范式的代表。</p>
<p>这里尤其要强调<span style="background-color: #c0c0c0;">替代选项（Alternative Option）</span>的重要性。对嵌入训练而言，替代选项本质上就是负样本：模型不只要知道“什么应该靠近什么”，还要知道“它为什么不是另一个看起来也很像的东西”。如果没有负样本，模型最容易学到的是一组宽泛、正确但区分度不足的共有特征；只有把相似但不同的替代选项放进训练过程，表示空间的边界才会真正被压实。</p>
<p>例如，如果想教模型理解“马”这一概念，只告诉它“有嘴巴、有鼻子、四条腿、长尾巴”，这些特征当然不算错，但它们并不能有效区分马和斑马，因为斑马同样满足这些描述。真正有区分度的，反而是“有没有条纹”、更接近哪种奔跑方式、整体体态和纹理模式这类能把两者分开的特征。把“马”和“斑马”作为相互竞争的替代选项放进对比训练后，模型才会被迫降低那些共有特征的权重，转而提升真正决定分类边界的特征权重。这也是为什么高质量负样本往往比继续堆更多正样本更能提升 embedding 的判别力。</p>
<div class="blog_h4"><span class="graybg">训练方式</span></div>
<p>如果把特定目标的 embedding 训练落到一个可执行流程，通常可以分成六步。</p>
<p>但无论流程写得多完整，训练或微调 embedding 模型的主要难点始终都不在 Trainer 本身，而在数据。可用数据不仅要足够大，质量门槛也很高；正例对通常相对容易收集，例如复述句、点击匹配、NLI 蕴含对、FAQ 问答对，但真正困难的是构造高质量的难负例，因为它们既要足够接近真实混淆项，又不能把本该相关的样本误标为负例。</p>
<ol>
<li>构造对比样本。最常见的起点是自然语言推断（Natural Language Inference, NLI）类句对数据，因为它天然提供“哪些句子应该更近、哪些句子应该更远”的监督信号。以蕴含（Entailment）关系作为正例、以矛盾（Contradiction）关系作为负例，是非常常见的做法；中立（Neutral）样本则可按任务目标决定是作为弱负例还是直接舍弃。工程实践里，经常直接使用 SNLI、MNLI 或二者合并后的 AllNLI。GLUE（General Language Understanding Evaluation）则更适合作为上层参照系：它汇总了九个语言理解任务，可用于分析模型在句对理解、推断和相似度相关任务上的整体表现，但并不是把九个任务原样全部转成对比样本。</li>
<li>定义评估器。训练过程不能只看训练损失，还需要一套稳定的验证指标。最常见的选择是 STS-B（Semantic Textual Similarity Benchmark）：它由人工标注句子对相似度，原始标签通常位于 1 到 5 的区间，适合评估句向量是否学到了连续的语义距离。若需要更全面的外部评估，则可以进一步使用 MTEB（Massive Text Embedding Benchmark）一类综合基准，它覆盖多类嵌入任务与大量数据集，能更系统地检查模型在检索、聚类、分类和语义匹配等场景中的迁移能力。</li>
<li>选择基座模型。特定目标的嵌入训练通常从一个现成的 Encoder-only Transformer 开始，例如 microsoft/mpnet-base、BERT、RoBERTa 或其领域变体。若任务更接近通用句向量，也可以直接从已经具备较强句向量能力的基座继续微调；若任务明显偏领域化，则优先选择语料分布更接近业务场景的编码器。</li>
<li>调用 sentence-transformers 进行训练。它提供了从数据集封装、池化、损失函数到 Trainer 的完整流水线。默认情况下，模型参数并不会被自动冻结，整个编码器都会参与更新；虽然也可以手动冻结底层若干层以节省显存或降低训练不稳定性，但对 embedding 任务而言，表示空间往往需要全层共同调整，因此在资源允许时，全量解冻通常比只训练顶部少数层更容易得到更好的句向量。</li>
<li>设置超参数。最关键的超参数通常是训练轮次（Epochs）、批次大小（Batch Size）和学习率预热（Learning Rate Warmup）。批次大小会直接影响 in-batch negatives 的数量，因此不仅关系到吞吐，也关系到对比学习的难度；训练轮次决定模型能否真正把目标关系写入向量空间；预热则用于降低训练初期的梯度震荡，避免刚开始就把预训练表示空间破坏掉。</li>
<li>选择损失函数。若目标是得到高质量 embedding，一般不建议把 SoftmaxLoss 当作默认选项，因为它更偏向“把当前任务做成分类”，而不是直接优化向量空间的几何结构。若手里有连续相似度分数，常用余弦相似度相关目标，例如 CosineSimilarityLoss；若任务是检索、召回或通用句向量训练，MultipleNegativesRankingLoss 往往是默认优先尝试的方案之一，因为它会把同一 batch 中其他样本自然当作负例，直接优化“正确匹配更近、错误匹配更远”的排序关系。不过它并不是所有任务上的统一最优解，最终仍取决于数据格式、负样本质量和任务目标。</li>
</ol>
<p>这六步背后的主线始终一致：先定义“相关”和“不相关”，再把这种关系写进向量空间。任务标签、蕴含关系、点击行为和人工偏好只是构造这种几何关系的不同来源；一旦正负样本定义清楚，训练目标就会自然收敛到对比学习的框架里。</p>
<p>在这条主线里，<span style="background-color: #c0c0c0;">难负例（Hard Negatives）</span> 往往能够显著提升嵌入模型的判别力。随机负例通常太容易，模型很快就能把它们推远；真正能继续塑造决策边界的，往往是那些“表面上很像、但在任务定义下并不相关”的样本。对检索、匹配和语义区分任务而言，负例越接近真实混淆项，模型越容易学到更有区分度的表示。</p>
<p>一个常见的负例收集流程如下：</p>
<ol>
<li>获取简单负例（Easy Negatives）。最直接的方法是从训练集里随机采样文档或句子，和当前 query/anchor 拼成负样本对。这一步成本最低，适合快速建立基础对比信号，但训练到中后期往往会变得过于容易。</li>
<li>获取半难负例（Semi-hard Negatives）。可以先用一个预训练 embedding 模型遍历训练集，为每个样本召回一批“看起来较相似”的候选，再排除真实正例、重复样本和语义等价样本，把剩余候选作为半难负例。这类负例已经靠近当前表示空间的边界，通常比随机负例更能提升检索质量。</li>
<li>获取难负例（Hard Negatives）。更强的做法是人工构造，或借助数据合成（Data Synthesis）生成高混淆样本。例如为同一个 query 人工编写“主题相关但答案错误”的文档，或利用大模型生成与正例高度相似但标签相反、结论错误、实体错配的文本。这样的负例最有训练价值，但也最容易混入假负例（False Negatives），因此质量控制必须更严格。</li>
</ol>
<p>难负例并不是越难越好。若负例实际上与正例同样合理，或者只是标注遗漏导致的“假负例”，模型就会被迫把本应接近的样本推远，反而损害 embedding 空间的结构。真正有效的 hard negative，是对模型足够难、但在任务定义下又明确应该分开的样本。</p>
<div class="blog_h3"><span class="graybg">特定目标嵌入微调</span></div>
<p>从头训练嵌入模型（Embedding Model）通常不是多数团队的首选路径。原因并不神秘：它既需要大规模高质量句对或 query-document 数据，也需要持续的负例挖掘、评估基准建设和较长训练周期；若多语言还要兼顾长文本、跨语言检索和任务泛化，成本会进一步抬升。对绝大多数工程团队而言，更高效的做法是从一个已经具备稳定表示空间的基座继续微调（Fine-tuning），把现有 embedding 几何结构朝业务目标方向“推一小步”，而不是从零发明一整个向量空间。</p>
<p>这也是为什么 embedding 项目的真正瓶颈往往不在“有没有训练框架”或“能不能跑通微调”，而在于能否拿到足够多、足够干净、又足够贴近业务分布的数据。正例对开发相对直接，难的是负例设计，尤其是高价值 hard negatives：它们决定模型能否学会区分那些最容易混淆、最接近真实线上错误的样本。</p>
<p>Sentence-BERT（SBERT）路线的实用价值就在这里体现得很明显：它并不要求必须从头构建一个新的 embedding 模型，而是允许直接以现有 SentenceTransformer 模型或预训练编码器为基础继续训练。这样做的收益有两个。第一，预训练阶段已经学到大量通用语言结构，微调只需要重塑与当前任务最相关的距离关系。第二，训练资源会集中花在“任务适配”而不是“重新学习基本语言知识”上，因此更适合企业内部检索、垂直领域分类、多语言 RAG 和跨语言匹配这类目标明确的场景。</p>
<p>实际选基座时，不应只看单一榜单名次，而应同时看四个因素：语言覆盖、上下文长度、是否指令化（Instruction-aware）、以及微调成本。榜单可以帮助筛掉明显过时的模型，但真正决定工程效果的，往往是“它是否与你的数据形态和训练预算匹配”。截至 2026 年 3 月，下面几类开源基座尤其值得优先考虑，尤其是在多语言场景中。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">基座</td>
<td style="text-align: center;">多语言能力</td>
<td style="text-align: center;">核心特点</td>
<td style="text-align: center;">更适合的微调目标</td>
</tr>
</thead>
<tbody>
<tr>
<td>Qwen3-Embedding-8B / 4B / 0.6B</td>
<td>100+ 语言</td>
<td>当前公开多语言榜单前列的强基座；支持指令感知、最长 32K 上下文、可自定义输出维度</td>
<td>多语言检索、跨语言召回、长文档检索、代码与自然语言混合语料</td>
</tr>
<tr>
<td>BGE-M3</td>
<td>100+ 语言</td>
<td>同时支持 dense / sparse / multi-vector；最长 8192 token；对混合检索和 RAG 结构非常友好</td>
<td>多语言 RAG、混合检索、长文档场景、需要兼容 BM25 风格稀疏信号的系统</td>
</tr>
<tr>
<td>multilingual-e5-large-instruct</td>
<td>94 语言</td>
<td>指令式 embedding 路线成熟；query 端显式带任务描述；体量相对可控</td>
<td>任务定义清晰的检索、问答召回、跨语言语义匹配</td>
</tr>
<tr>
<td>jina-embeddings-v3</td>
<td>多语言；重点调优 30 种语言</td>
<td>8192 token 长上下文；内置任务 LoRA 适配器；支持 Matryoshka 截断维度</td>
<td>一套基座服务多任务、需要分类 / 检索 / text-matching 共用底座的系统</td>
</tr>
</tbody>
</table>
<p>若强调“当前最强的开源多语言底座”，Qwen3-Embedding 系列是首先应试的对象；若强调“检索形态复杂、需要 dense + sparse + rerank 协同”，BGE-M3 的工程灵活性仍然非常突出；若更在意成熟的指令式 query-document 训练范式，multilingual-E5-large-instruct 依然是很稳的起点；若希望在同一基座上兼顾多任务并降低任务切换成本，jina-embeddings-v3 的任务适配设计更有吸引力。</p>
<p>因此，特定目标的嵌入微调并不是“随便挑一个 embedding 模型然后继续训”。更合理的顺序是：先根据任务选择合适的基座拓扑，再设计正负样本与评估器，最后用微调把表示空间朝业务目标压缩。对今天的大多数团队来说，真正的竞争力很少来自“从零训练一个全新 embedding 模型”，而更多来自“是否用合适的底座，把微调目标、负例设计和评测体系做对”。</p>
<div class="blog_h3"><span class="graybg">基于少量数据的嵌入微调</span></div>
<p>在标注数据非常有限的场景里，增强型 SBERT（Augmented SBERT）提供了一条经典而务实的路径：用少量高质量标注，扩展出一套足够大的嵌入训练集。它利用的是双编码器（Bi-Encoder）与交叉编码器（Cross-Encoder）的互补性：双编码器推理快、适合检索，但通常需要较多训练数据；交叉编码器推理慢，却能在句对打分上提供更高精度。因此，可以先让交叉编码器学会目标任务，再利用它为大量未标注句对生成伪标签，最后反过来训练一个可高效部署的 SBERT。</p>
<p>这个方法的关键不在于引入全新的模型结构，而在于重新组织数据生产流程。少量人工标注但可靠的数据，构成黄金数据集（Gold Dataset）；由交叉编码器离线打标生成的大规模伪标签数据，构成白银数据集（Silver Dataset）。黄金数据集负责提供可信监督，白银数据集负责放大覆盖面。两者组合后，就能把“标注稀缺”的问题转化成“高精度慢模型辅助生成训练信号”的问题。</p>
<p>因此，增强型 SBERT 本质上是一种<span style="background-color: #c0c0c0;">低数据场景下的数据增强（Data Augmentation）与知识蒸馏（Knowledge Distillation）</span>策略：先用少量黄金数据把交叉编码器调准，再让交叉编码器把自己的句对判断能力迁移给双编码器。最终得到的不是一个更慢的模型，而是一个依然可以做大规模向量检索、但在目标任务上明显更强的嵌入模型。</p>
<div class="blog_h4"><span class="graybg">核心流程</span></div>
<p>增强型 SBERT 的整体流程可以概括为四步。</p>
<ol>
<li>先用少量黄金数据集微调交叉编码器。这里的黄金数据通常规模不大，但标签质量高，足以让交叉编码器学会“在当前任务里什么样的句对应该更相似，什么样的句对应该更远”。</li>
<li>再生成一批新的候选句子对。这一步既可以来自额外的未标注语料，也可以从现有语料中重新组合样本，目的是构造一个远大于黄金数据集的候选池。</li>
<li>然后让已经微调好的交叉编码器为这些候选句子对打分，生成白银数据集。这里的标签不是人工真值，而是高精度模型给出的伪标签，因此质量通常高于简单启发式规则，却又远比全人工标注便宜。</li>
<li>最后用“黄金数据集 + 白银数据集”一起训练双编码器。这样训练出来的 SBERT 保留了双编码器可预编码、可缓存、可做向量检索的速度优势，同时通过白银数据学到了更多与目标任务一致的距离关系。</li>
</ol>
<div class="blog_h4"><span class="graybg">白银数据集如何构造</span></div>
<p>白银数据集的质量，决定了增强型 SBERT 能否真正成立。若手里本来就有大量未标注句对或 query-document 数据，最直接的做法就是把它们交给交叉编码器离线标注，再转成伪标签训练集。这是最标准也最稳定的路径，因为候选样本来自真实语料分布，白银数据更接近真实任务。</p>
<p>如果没有现成的大规模未标注句对，也可以从现有黄金数据出发构造更多候选样本。例如把不同句子的前半部分与后半部分重新组合，或把不同 query 与 candidate 文档重新配对，生成新的候选对。但纯随机组合通常会制造过多明显不相似的负例，导致数据分布过于偏斜，模型学到的主要是“轻松区分非常不像的样本”，而不是处理真正困难的边界。</p>
<p>因此，更有效的做法通常是先用一个预训练 embedding 模型做粗检索：为每个句子或 query 召回若干看起来较相似的候选，再把这些候选送给交叉编码器做精标。这样生成的白银数据会包含更多“高混淆但仍可判别”的样本，训练价值明显高于随机拼接。换句话说，预训练 embedding 在这里不负责给出最终标签，而是负责提高候选样本的质量；真正的伪标签仍然由交叉编码器给出。</p>
<div class="blog_h4"><span class="graybg">为什么适合少量数据微调</span></div>
<p>增强型 SBERT 的价值，在低资源场景下尤其明显。少量黄金数据本身往往不足以直接把 SBERT 微调到理想状态，因为双编码器更依赖足够多的成对训练信号去塑造向量空间；但这同一小批黄金数据，往往已经足够把交叉编码器调成一个“能较准打分”的教师模型。之后，只要有额外候选样本，教师模型就能持续扩充白银数据集，从而把训练信号放大很多倍。</p>
<p>这也是它与普通监督微调的根本差别：普通微调直接把少量标注样本喂给双编码器；增强型 SBERT 则多加了一层“教师打标”环节，用交叉编码器把少量高质量监督扩展成大量可用监督。因此，它特别适合文本匹配、语义检索、问答匹配、句子对排序等 pairwise sentence scoring 任务。</p>
<div class="blog_h4"><span class="graybg">边界与适用条件</span></div>
<p>增强型 SBERT 并不是“数据越少越万能”。它仍然要求黄金数据足够准确，否则交叉编码器会先学歪，再把错误批量复制到白银数据里。它也要求候选样本池与真实任务分布足够接近，否则伪标签再多，也只是把错误分布放大。更关键的是，白银数据集并不能替代最终评估：真正决定模型是否可用的，仍应是人工标注的黄金验证集与测试集。</p>
<p>因此，这条路线最适合的场景不是“完全没有数据”，而是“有少量高质量标注、但不足以直接训练出强双编码器”。在这种情况下，增强型 SBERT 提供了一条非常自然的过渡路径：先用高精度慢模型吸收黄金数据，再把这种判断能力蒸馏成一个推理高效的嵌入模型。</p>
<div class="blog_h3"><span class="graybg">无监督嵌入模型训练</span></div>
<div class="blog_h4"><span class="graybg">过渡背景</span></div>
<p>增强型 SBERT 已经把监督数据需求压得很低，但它仍然依赖少量黄金数据去微调交叉编码器。再往前推进一步，现实里还存在更苛刻的场景：没有人工标注句对，甚至没有可靠的点击日志、排序日志或问答配对数据。此时，嵌入模型训练只能转向无监督学习（Unsupervised Learning）或更准确地说，自监督学习（Self-supervision）：训练信号不再来自显式标签，而来自原始语料自身的结构、扰动视图（Augmented Views）或重建目标。</p>
<p>因此，无监督嵌入模型训练处理的是“零标注”条件下的表示学习问题。它并不是放弃监督，而是把监督信号改写为模型可以从原始文本中自动构造出的约束：哪些表示应该在扰动前后保持一致，哪些句子应被视为同一语义对象的不同视图，哪些带噪输入必须恢复到原始句子。这类方法尤其适合冷启动和领域适配，因为垂直领域最容易获得的往往不是标签，而是大量原始文本。</p>
<div class="blog_h4"><span class="graybg">主流路线</span></div>
<p>无监督嵌入训练的核心目标仍然是学习一个有判别力的句向量空间，只是实现方式不同。当前常见路线可以概括为四类。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">方法</td>
<td style="text-align: center;">全称</td>
<td style="text-align: center;">核心原理</td>
<td style="text-align: center;">特点</td>
</tr>
</thead>
<tbody>
<tr>
<td>SimCSE</td>
<td>Simple Contrastive Learning of Sentence Embeddings</td>
<td>把同一句子在不同 dropout 下得到的表示视为正例，用 batch 内其他句子作负例做对比学习</td>
<td>结构极简，是无监督句嵌入的经典标杆路线</td>
</tr>
<tr>
<td>CT</td>
<td>Contrastive Tension</td>
<td>通过对比张力机制重新调整预训练表示，使语义相近样本更聚合、无关样本更分离</td>
<td>强调对预训练表示的语义重调</td>
</tr>
<tr>
<td>TSDAE</td>
<td>Transformer-based Sequential Denoising Auto-Encoder</td>
<td>先破坏原句，再要求模型从带噪输入重建原句，逼迫编码器学习句级语义压缩</td>
<td>在无监督训练和领域适配中都非常强，是这一节的重点</td>
</tr>
<tr>
<td>GPL</td>
<td>Generative Pseudo-Labeling</td>
<td>从无标签语料出发自动生成 query 与伪标签，再训练 dense retriever</td>
<td>更接近弱监督，但非常适合无人工标注的检索场景</td>
</tr>
</tbody>
</table>
<p>这些方法的共同点是：都在尝试从原始文本中自动制造训练信号；差别在于信号是来自对比、重建，还是生成式伪标签。无监督训练真正的难点依然是数据，只不过难点从“标注是否充足”转移成“自监督信号是否有效”。如果正样本过于容易、负样本过于随机，模型学到的往往只是表面相似性；若扰动方式破坏了核心语义，模型又会被迫学习错误的不变性。</p>
<div class="blog_h4"><span class="graybg">TSDAE</span></div>
<p>TSDAE（Transformer-based Sequential Denoising Auto-Encoder）是无监督句嵌入中非常重要的一条路线，尤其适合领域适配（Domain Adaptation）。它的核心思想可以概括为“破坏 - 重建”：先对原始句子加噪，例如随机删除部分词、打乱局部结构或施加其他轻度破坏；再让模型从这个带噪版本重建原始句子。模型若想完成这项任务，就不能只记住局部 token，而必须把句子的整体语义压缩到编码表示中。</p>
<p>TSDAE 与掩码语言建模（Masked Language Modeling, MLM）的差别也很关键。MLM 主要学习“根据上下文补出被遮住的词”，重点仍然是词级预测；TSDAE 学习的是“从受损输入恢复整个句子”，目标天然更偏句级语义表示。因此，TSDAE 训练出来的编码器更容易直接拿来生成句嵌入，而不必再从词级预测目标里间接提炼句向量。</p>
<p>这条路线在垂直领域里尤其有效。原因是领域适配最常见的现实条件正是：有大量目标域原始文本，但缺乏成对标注数据。TSDAE 可以先在这些无标签文本上训练一个更贴近目标领域分布的编码器，把通用嵌入空间向领域语义挪动；随后若再获得少量黄金数据，就可以继续用常规 SBERT 或增强型 SBERT 做最后一步任务对齐。</p>
<div class="blog_h4"><span class="graybg">领域适配闭环</span></div>
<p>从工程角度看，最有价值的是把“监督训练”“少样本微调”“无监督训练”看成一条连续路线，而不是彼此割裂。标注充足时，直接做监督训练；标注很少时，用增强型 SBERT 放大黄金数据；标注几乎为零但有大量原始语料时，先做 TSDAE 或类似无监督适配，再在后续叠加少量监督微调。这样就形成了一个完整闭环：覆盖有监督、低监督到零监督三种数据条件。</p>
<p>因此，无监督嵌入训练并不天然优于监督微调。它的价值在于为 embedding 模型打地基，并在领域迁移时先把表示空间校正到目标语料分布附近。若后续能够获得少量高质量黄金数据，再叠加监督微调，通常会比单独依赖无监督训练更稳，也更接近真实业务目标。</p>
<div class="blog_h3"><span class="graybg">表示模型继续预训练</span></div>
<p>表示模型继续预训练（Continued Pretraining）处理的是这样一种典型情况：基座表示模型已经在通用开放语料上完成预训练，例如互联网文本、维基百科、新闻语料或大规模网页数据，因此具备稳定的通用语言能力；但它并不天然掌握特定领域知识，例如医学术语、金融表述、法律条文、企业内部缩写和业务语境。此时，问题往往不在于模型“不会语言”，而在于模型虽然懂通用语言，却还不够懂目标领域。</p>
<p>这正是继续预训练的切入点。传统 BERT 路线通常只有两阶段：先在通用语料上做预训练，再直接在下游分类或序列标注任务上微调。继续预训练在两者之间插入了一个新的中间层，形成<span style="background-color: #c0c0c0;">通用预训练 → 领域继续预训练 → 下游任务微调</span>的三阶段流程。它的目标不是立刻输出分类结果，而是先用目标领域的无标注文本，重新校正编码器的词汇分布、上下文统计与语义偏好，让表示空间先贴近领域，再去做具体任务。</p>
<div class="blog_h4"><span class="graybg">为什么需要继续预训练</span></div>
<p>通用预训练模型对“movie”“doctor”“interest”“appeal”这类词的理解，默认来自开放语料中的统计分布；一旦进入垂直场景，这些词的含义和搭配方式可能会明显变化。医学文本中的 drug、lesion、metastasis，金融文本中的 guidance、yield、hedging，法律文本中的 plaintiff、statute、liability，都承载着更窄、更稳定、更专业的语境。如果直接拿通用模型去做下游微调，模型往往会在专业术语、长尾表达和领域共现关系上吃亏。</p>
<p>继续预训练的价值就在于：它允许模型先用领域无标注数据补上这层知识，再进入任务微调阶段。因此它本质上是一种领域自适应预训练（Domain-Adaptive Pretraining, DAPT）策略。医疗领域的 BioBERT、金融领域的 FinBERT、法律领域的 LawBERT，本质上都属于这一路线的不同落地版本。</p>
<div class="blog_h4"><span class="graybg">核心训练任务</span></div>
<p>对 BERT 一类 Encoder-only 表示模型而言，继续预训练最经典的目标仍然是掩码语言建模（Masked Language Modeling, MLM）。它可以理解成一种受控的“完形填空”：随机选择输入序列中约 15% 的 token 作为预测目标，其中 80% 替换成 [MASK]，10% 替换成随机 token，剩余 10% 保持不变，但仍要求模型预测原始 token。这样做的目的，是迫使模型根据双向上下文恢复被遮蔽的信息，从而继续学习领域语料中的词汇、搭配和上下文统计。</p>
<p>在继续预训练场景中，MLM 的关键价值不在于“学会猜词”本身，而在于让词向量与上下文表示持续向领域分布靠拢。通用 BERT 看到 What a horrible [MASK]! 时，可能只学到通用情绪词与常见名词；若继续在影评语料上做 MLM，它会更容易把 horror、ending、premise、performance 这类领域表达织进表示空间。医学、金融、法律乃至企业内部文档都是同样的道理。</p>
<div class="blog_h4"><span class="graybg">训练流程</span></div>
<p>如果把表示模型继续预训练落到一个可执行的理论流程，通常可以分成六步。</p>
<ol>
<li>确定基础模型。起点通常是一个已经完成通用预训练的表示模型，例如 BERT、RoBERTa、DeBERTa 或其领域相近变体。这里不需要从头训练模型，而是直接继承已有语言能力。</li>
<li>收集目标领域的无标注语料。继续预训练最重要的资源不是标签，而是高质量领域文本。它既可以来自公开领域语料，例如 PubMed 医学文献、金融新闻、法律判例，也可以来自企业内部数据，例如业务文档、客服对话、知识库与工单记录。</li>
<li>完成分词与语料预处理。继续预训练阶段通常不再保留下游标签，只保留原始文本并转换成模型输入序列。此时最重要的是保证文本清洗、截断策略、特殊符号处理与分词器保持一致，因为模型更新的是对领域文本分布的内部表示，而不是标签映射。</li>
<li>选择掩码策略。最基础的是词元掩码（Token Masking），即对子词粒度随机掩码；若希望模型更充分学习完整术语和专业表达，也可以使用整词掩码（Whole Word Masking, WWM），让一个完整单词的所有子词同时被遮蔽。整词掩码通常训练更难、收敛更慢，但在领域术语密集的场景下更有价值。</li>
<li>执行继续预训练。用领域无标注语料继续运行 MLM 目标，让模型参数在不丢失通用语言能力的前提下，逐步适配目标领域。这个阶段更新的不是分类头，而是整个编码器本身，因此它更像“重塑表示空间”，而不是“学习某个具体任务标签”。</li>
<li>切换到下游微调。等继续预训练完成后，再把更新后的表示模型接到具体任务上，例如文本分类、语义搜索、命名实体识别（Named Entity Recognition, NER）或关系抽取。此时，下游微调面对的已经不是通用基座，而是一个更懂目标领域语境的表示模型。</li>
</ol>
<div class="blog_h4"><span class="graybg">企业级场景</span></div>
<p>继续预训练在企业内部尤其有价值。很多企业并不缺文本，而是缺可公开复用的标注数据。客服对话、知识库、工单、合同、操作手册、会议纪要、内部 wiki，这些数据天然带有组织级语境和术语。如果直接把通用模型拿去做企业任务微调，模型经常会在缩写、术语和内部表述上显得迟钝；但若先用这些无标注内部数据继续预训练，再做客服主题分类、语义搜索或实体识别，下游效果通常会明显更稳。</p>
<p>因此，继续预训练最适合的不是“领域知识已经充分内化到通用模型”的场景，而是“目标领域有大量原始文本，但通用模型对其语境仍然陌生”的场景。它本身不是终点，而是一个连接通用语言能力与领域任务能力的中间适配层。</p>
<div class="blog_h3"><span class="graybg">分类目标表示模型微调</span></div>
<p>除了直接训练 embedding 模型，另一条非常常见的路线是围绕分类目标（Classification Objective）微调表示模型（Representation Model）。这类方法的基本结构是：以预训练编码器作为基座，在顶层接一个专用分类头（Classification Head），然后用分类损失共同优化表示与决策边界。它适用于情感分类、主题分类、风险识别、意图分类等闭集标签任务，目标不是学习一个通用距离空间，而是让表示尽可能服务于当前分类边界。</p>
<p>在这种设定下，基座模型参数既可以冻结（Freeze），也可以与分类头一起更新。冻结时，训练只发生在分类头，优点是显存占用小、训练稳定、对小数据集更保守；缺点是表示空间几乎不变，模型只能在既有语义表征上学习一个浅层决策边界。若不冻结，则分类头与基座参数会在训练中<span style="background-color: #c0c0c0;">协同进化</span>：分类头不断把梯度传回编码器，编码器又持续调整自己的表示方式去配合分类目标，最终得到更贴近任务边界的内部表征。这通常能带来更高上限，但也更依赖数据质量、学习率设置和正则化控制。</p>
<p>从经验上看，把基座模型全部冻结后，分类微调效果通常会明显受限，尤其当任务分布与预训练语料存在偏移时更是如此。资源受限时，部分解冻（Partial Unfreezing）是一种常见折中：只更新分类头往往太弱，而全量解冻又可能超出显存或训练预算。在某些具体实验里，只解冻少数几个 Transformer 模块就已经能得到足够好的结果。对文本分类而言，更常见、更合理的做法通常是优先解冻靠后的高层模块，因为后层表示更接近任务语义与决策边界；前层更偏向词法与局部句法特征，保留冻结状态往往问题不大。若任务与预训练域差异很大，或输入风格明显特殊，再考虑进一步向下解冻更多层。换句话说，部分解冻的关键不在于固定解冻“哪 N 层”，而在于把有限资源优先用在最可能影响任务边界的高层表示上。</p>
<div class="blog_h4"><span class="graybg">训练流程</span></div>
<p>以烂番茄影评数据集（Rotten Tomatoes Movie Review Dataset）的情感分类为例，分类目标表示模型微调通常可以拆成六步。</p>
<ol>
<li>选择任务与数据集。烂番茄影评数据集是二分类情感任务：输入是一段影评文本，输出是正面或负面标签。它非常适合说明“表示模型 + 分类头”这一路线，因为情感边界往往依赖整体语义与局部措辞共同决定。</li>
<li>加载数据并完成划分。最基本的划分是训练集（Training Set）与测试集（Test Set）；若训练流程中还需要调超参数或做早停，则还应额外保留验证集（Validation Set）。这里的重点不是机械切分比例，而是保证测试集不参与模型选择，从而让最终分类指标具有解释价值。</li>
<li>加载基座模型与分词器（Tokenizer）。基座通常选择预训练 Transformer 编码器，例如 BERT、RoBERTa、DeBERTa 或更轻量的蒸馏版本；分词器负责把原始文本转成 token 序列、attention mask 以及模型可接收的输入张量。模型与 tokenizer 必须配套，因为词表、特殊 token 和预训练时的文本规范共同决定了输入表示。</li>
<li>进行分词与样本编码。文本在进入模型前需要完成截断（Truncation）、编码与必要的长度控制。这个阶段的目标不是简单把字符串变成整数，而是把“原始语言序列”转换成“可被编码器稳定处理的张量化输入”。序列最大长度、是否保留句首句尾、以及是否对长文本做裁剪策略，都会直接影响分类效果。</li>
<li>构建专用数据整理器（Data Collator）。它负责把长度不一的样本动态组织成批次（Batch），例如按当前 batch 的最长序列做填充（Padding），并同步构建 attention mask，保证同一批次内张量形状一致。更进一步，数据整理器也可以承载轻量数据增强策略，例如随机裁剪、句段保留或噪声注入；但它的最基本职责仍然是稳定、高效地完成批次构造，而不是单纯“把数据拼起来”。</li>
<li>定义评估指标函数。分类目标的训练不仅需要损失函数，还需要一组与业务目标对齐的评估指标。最基本的是 Accuracy；若类别不平衡或更关心误报 / 漏报，则通常还要同时看 Precision、Recall 与 F1。对情感分类这类任务，宏平均 F1（Macro-F1）往往比单独 Accuracy 更能反映模型是否真正学到了稳定的标签边界。</li>
</ol>
<p>这种训练路线与前文 embedding 微调的根本区别在于优化目标。embedding 微调强调“让相关样本在向量空间中更近，让无关样本更远”；分类目标微调强调“让表示空间直接服务于当前标签决策”。前者更适合检索、聚类、匹配与召回，后者更适合闭集判别任务。实际工程中，两条路线并不冲突：很多系统会先训练或微调一个较强的 embedding / encoder 基座，再在其上叠加分类头完成特定标签任务。</p>
<div class="blog_h3"><span class="graybg">少量样本分类目标微调</span></div>
<p>少量样本分类目标微调（Few-shot Classification Fine-tuning）处理的是另一类很常见的现实约束：标签体系已经明确，但每个类别只有极少数标注样本，往往只有 8、16、32 或几十个例子。这类场景下，直接按常规监督流程全量微调一个分类模型，很容易过拟合到表面措辞或偶然噪声；真正有效的方法通常不是“把普通微调硬做小”，而是换用对低样本更友好的训练机制。</p>
<p>少样本分类本质上仍然属于监督学习，只是把“每类有大量标注样本”的常规设定，收缩成“每类只有极少量高质量样本”的稀缺设定。它特别适合标注成本高、样本获取慢、但标签体系明确的任务，例如小众领域文本分类、垂直领域情感分析、专业工单归类或内部知识标签识别。少样本方法的核心，不是让模型凭空学会分类，而是尽可能榨出每一条标注样本中的监督信号。</p>
<div class="blog_h4"><span class="graybg">SetFit</span></div>
<p>SetFit（Sentence Transformer Fine-tuning）是少样本文本分类里最实用的一条路线之一。它基于 sentence-transformers 生态构建，但并不直接把少量样本喂给一个普通分类器，而是先把这些样本改写成大量句子对训练信号，再用两阶段流程完成分类。它的核心价值在于：仅靠极少量标注样本，就能让嵌入模型学到任务相关的类别结构，随后再用一个很轻量的分类头完成判别。</p>
<p>SetFit 的完整流程可以概括为三步：</p>
<ol>
<li>采样训练数据。先基于少量原始标注样本构造句子对：同一类别下任意两个文本组成正例，不同类别下的文本组成负例。这样一来，即使每类只有 2 到 8 条样本，也能通过类内 / 类间组合迅速扩展出大量训练对。少样本的关键不再只是“原始样本有多少”，而变成“能否从这些样本中构造出足够有判别力的相似 / 不相似关系”。</li>
<li>微调嵌入模型。利用这些正负句子对，对预训练的 Sentence Transformer 做对比学习微调。正例要求模型把同类文本的句向量拉近，负例要求模型把异类文本推远。这个阶段优化的是表示模型本身，也就是 SetFit 的 body。</li>
<li>训练分类器。等嵌入模型被调到更适配当前任务后，再用这些高质量句向量作为特征，训练一个轻量分类头（head），例如逻辑回归、线性分类器或其他简单监督分类器。最终推理时，文本先被编码成嵌入，再由分类头输出类别概率。</li>
</ol>
<p>这套设计的本质是两阶段训练：第一阶段先让嵌入空间学会“同类靠近、异类远离”；第二阶段再在这个已经整理过的表示空间里学习分类边界。相比直接对大模型做全参数分类微调，这种路线对小样本更稳定，也更节省资源。</p>
<p>SetFit 的优势主要体现在四个方面。第一，少样本效率高：每类只需极少数标注样本，就可能逼近常规大数据分类微调的效果。第二，无需提示词：它不像提示式 few-shot 方法那样依赖 prompt 或 verbalizer 设计。第三，训练成本低：大部分计算都集中在句向量微调和轻量分类头训练上。第四，数据利用率高：通过句子对采样，有限标注被最大化放大。</p>
<p>以烂番茄影评（Rotten Tomatoes）这类二分类情感任务为例，SetFit 的典型做法是先按类别均衡抽取极少量样本，例如每类 16 条；之后通过正负配对把几十条原始样本扩展成上千个训练对，再完成对比学习与分类头训练。这个例子最能说明 SetFit 的关键巧思：真正被放大的不是“文本本身”，而是文本之间的监督关系。</p>
<p>SetFit 也有边界。它最适合短文本、句子级、闭集标签明确的分类任务；若类别语义高度重叠、任务严重依赖复杂推理，或标签本身更像开放式生成目标，那么单纯依赖句向量空间分离的办法未必最优。在这些场景里，提示式 few-shot 或更强的生成式模型可能更有优势。</p>
<div class="blog_h4"><span class="graybg">提示式 Few-shot 微调</span></div>
<p>另一大类主流方案是提示式（Prompt-based）few-shot 微调，其代表方法包括 PET（Pattern-Exploiting Training）和 LM-BFF（Better Few-shot Fine-tuning of Language Models）。它们不再直接把分类任务看成“输入文本 → 标签 id”，而是把任务改写成 cloze 风格或自然语言提示，让预训练语言模型去预测标签词（label words）或完成模式匹配。PET 的特点是利用 prompt 把少样本监督对齐到语言模型原本更擅长的预训练目标，并进一步用少量标注模型为未标注样本分配软标签；LM-BFF 则把 prompt 搜索、label word 选择和 demonstration 设计系统化，以提高 few-shot 稳定性。</p>
<p>这类方法在标签语义清晰、prompt 设计得当时非常强，但工程代价通常高于 SetFit。它们更依赖 prompt 模板、verbalizer 质量以及不同随机种子的稳定性，迁移到新任务时也往往需要额外搜索和调参。换句话说，提示式 few-shot 的上限很高，但工程摩擦也更大。</p>
<div class="blog_h4"><span class="graybg">参数高效微调</span></div>
<p>第三类主流路线是参数高效微调（Parameter-Efficient Fine-Tuning, PEFT），例如 LoRA、IA3、Prefix Tuning、Prompt Tuning 等。它们的共同点是：大部分预训练参数保持冻结，只训练一小部分新增参数或适配器参数，从而显著降低显存与存储成本。这类方法更直接解决“模型太大，如何降低微调成本”的问题，因此在大基座模型上尤其有价值。</p>
<p>PEFT 与 SetFit 的关注点并不相同。SetFit 解决的是“样本太少，如何更高效地榨出监督信号”；PEFT 更直接解决“模型太大，如何降低微调成本”。在少样本分类里，二者并不互斥：完全可以把少样本策略与参数高效策略叠加。例如，当基座模型较大、显存非常紧张时，可以优先采用 LoRA / IA3 这类方法；若样本极少且更看重训练稳定性与部署成本，则 SetFit 往往是更直接的起点。</p>
<div class="blog_h4"><span class="graybg">如何选型</span></div>
<p>如果任务是典型的短文本或句子级闭集分类，且每类只有极少样本，SetFit 往往是首选起点，因为它训练快、对样本效率高、工程上也最直接。若任务标签本身具有很强自然语言语义，且团队愿意投入 prompt 设计与搜索成本，PET / LM-BFF 这类提示式 few-shot 往往有更高上限。若主要矛盾不是样本太少，而是模型太大、显存和部署预算太紧，则应优先考虑 LoRA、IA3 或其他 PEFT 方案。实际系统中，最稳妥的做法通常不是先争论“哪条路线绝对最好”，而是先判断当前瓶颈究竟是样本、算力，还是 prompt 工程复杂度。</p>
<div class="blog_h3"><span class="graybg">生成模型高效微调</span></div>
<p>生成模型高效微调（Parameter-Efficient Fine-Tuning for Generative Models）面向的是 Decoder-only 大语言模型（Large Language Model, LLM）的指令对齐与领域适配场景。它处理的不是“如何训练一个分类器”或“如何训练一个检索向量空间”，而是如何在显存、训练时间和存储预算都有限的条件下，让基座生成模型学会遵循指令、稳定输出目标格式，并吸收特定领域的表达习惯。对绝大多数 7B、13B 乃至更大规模的开源生成模型而言，基于 QLoRA 的 PEFT 已经成为默认起点。</p>
<div class="blog_h4"><span class="graybg">适用场景</span></div>
<p>这一路线最适合三类任务。第一类是指令微调（Instruction Tuning），即让基座模型从“擅长续写”转向“稳定遵循用户指令”；第二类是风格或格式约束，例如客服回复风格、结构化 JSON 输出、企业内部答复模板；第三类是轻量领域适配，例如让模型更熟悉某个组织、行业或产品线的术语与常见问答模式。若主要瓶颈是显存不足，QLoRA 往往优于全量微调；若目标是深度改写模型底层知识、继续预训练大规模领域语料，或修改上下文长度、词表与位置编码等底层结构，则继续预训练或全参数微调仍然更合适。</p>
<div class="blog_h4"><span class="graybg">训练流程</span></div>
<p>从理论上看，基于 QLoRA 的生成模型高效微调通常遵循六步流程。</p>
<ol>
<li>确定基座模型与任务目标。基座一般选择已经具备稳定因果语言建模能力的生成模型，例如 Llama、Qwen、Mistral 或 TinyLlama 一类架构；任务目标则通常是监督微调（Supervised Fine-Tuning, SFT）意义上的指令跟随，而不是从头学习语言能力。</li>
<li>构造高质量指令数据。训练样本通常采用<span style="background-color: #c0c0c0;">instruction / input / output</span>三段式结构，或更一般的多轮对话消息结构。这里最关键的不是样本数量本身，而是格式与质量：用户角色、助手角色、轮次边界、结束标记、系统提示词都必须稳定一致。若基座模型已经绑定特定 chat template，就应沿用同一模板组织训练语料；否则模型学到的首先不是任务能力，而是混乱的对话边界。实际工程里，过滤后的 UltraChat 风格指令对话之所以常被拿来做示例，核心原因也正在于此：它提供了相对稳定、结构清晰的监督信号。</li>
<li>以低比特方式加载基座。QLoRA 的“Q”来自量化（Quantization）：基座权重以 4-bit 形式存储，常见配置包括 NF4、双重量化以及 BF16 / FP16 计算精度。这样做的目的不是改变模型结构，而是把静态权重占用压到足够低，使更大模型能够在有限显存中完成微调。NF4 尤其适合这一场景，因为它针对 Transformer 权重常见的零中心、近似正态分布做了量化设计。</li>
<li>挂接 LoRA 适配器。QLoRA 的训练对象不是量化后的基座权重，而是附加在目标投影层上的低秩适配器参数。实践中通常会把 LoRA 挂到注意力层的 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> / <span displaypfx="inline-" class="mathjax-container">\(k\)</span> / <span displaypfx="inline-" class="mathjax-container">\(v\)</span> / <span displaypfx="inline-" class="mathjax-container">\(o\)</span> 投影上，必要时再扩展到 MLP 的 up / down / gate 投影。这样得到的是“冻结量化基座 + 可训练低秩分支”的结构：原模型保留通用语言能力，增量参数专门负责吸收任务相关行为。</li>
<li>执行监督微调。训练过程本质上仍然是自回归下一个 token 预测，只是训练语料已经被改写为指令遵循格式。由于显存约束依然存在，单卡 batch size 往往较小，因此通常依靠梯度累积（Gradient Accumulation）来获得更合理的等效批大小；学习率调度常采用 warmup 后接 cosine 衰减；优化器常配合分页优化器以压低峰值显存；最大序列长度则决定模型一次看到多少上下文，也直接决定训练成本。</li>
<li>导出训练产物。训练完成后，最常见的保存形式有两种：一种是只保存 LoRA 适配器，部署时与同一基座模型组合使用，这最适合多任务、可插拔部署；另一种是把适配器权重合并回基座，得到单体模型，便于独立推理与分发。前者更省存储、更灵活，后者更接近传统“一个模型直接上线”的部署习惯。</li>
</ol>
<div class="blog_h4"><span class="graybg">关键超参数</span></div>
<p>QLoRA 的表现很大程度上由少数关键超参数决定。它们分别约束适配器容量、优化稳定性、上下文覆盖范围与显存预算。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">超参数</td>
<td style="text-align: center;">控制对象</td>
<td style="text-align: center;">实践含义</td>
</tr>
</thead>
<tbody>
<tr>
<td>LoRA rank <span displaypfx="inline-" class="mathjax-container">\(r\)</span></td>
<td>低秩适配器的容量</td>
<td><span displaypfx="inline-" class="mathjax-container">\(r\)</span> 越大，适配器表达力越强，但显存与训练成本也越高；过小容易欠拟合，过大则削弱 PEFT 的资源优势。</td>
</tr>
<tr>
<td>lora_alpha</td>
<td>LoRA 更新量的缩放强度</td>
<td>它决定适配器增量对原模型输出的影响幅度，通常与 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 配合设置；过大容易训练不稳，过小则适配不足。</td>
</tr>
<tr>
<td>target_modules</td>
<td>LoRA 挂接位置</td>
<td>只覆盖 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> / <span displaypfx="inline-" class="mathjax-container">\(v\)</span> 投影时最省资源；同时覆盖注意力与 MLP 投影时，通常有更高上限，但训练更重。</td>
</tr>
<tr>
<td>lora_dropout</td>
<td>适配器分支正则化</td>
<td>小数据集或高重复训练语料更容易过拟合，适度 dropout 有助于稳定；数据充足时则通常保持较低取值。</td>
</tr>
<tr>
<td>学习率（Learning Rate）</td>
<td>参数更新步长</td>
<td>QLoRA 只训练少量适配器参数，因此学习率通常可以高于全参数微调；但过高仍会导致输出风格漂移、格式崩溃或损失震荡。</td>
</tr>
<tr>
<td>批大小与梯度累积</td>
<td>等效 batch 规模</td>
<td>单卡显存通常只允许很小的 per-device batch size，因此需要靠梯度累积换取更稳定的优化轨迹。真正重要的是等效 batch，而不是单步看到多少样本。</td>
</tr>
<tr>
<td>最大序列长度（Max Sequence Length）</td>
<td>单样本上下文覆盖范围</td>
<td>序列越长，训练成本增长越快；过短又会截断多轮对话、长指令或结构化输出。它是质量与成本之间最直接的杠杆之一。</td>
</tr>
<tr>
<td>计算精度与优化器</td>
<td>数值稳定性与显存占用</td>
<td>支持时优先使用 BF16；否则常退回 FP16。分页 AdamW 一类优化器更适合量化微调，因为它们能显著压低优化器状态带来的峰值显存。</td>
</tr>
<tr>
<td>学习率调度器（Scheduler）</td>
<td>训练初期稳定性与后期收敛</td>
<td>cosine 衰减配合短 warmup 是常见默认配置：前期避免梯度过猛，后期逐步收敛到更稳的解。</td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">适用边界</span></div>
<p>QLoRA 的核心优势是把“生成模型微调”从高门槛算力工程，压缩成可在有限资源上反复迭代的日常流程。因此，只要任务主要是指令跟随、格式控制、轻量领域适配或特定风格注入，它通常都应作为第一选择。它的边界同样明确：当任务需要深度改写基座知识、吸收大规模新领域语料、重构模型底层能力或逼近全参数微调的极限上限时，QLoRA 更适合作为基线而不是终点。此时更合理的路线通常是继续预训练、Q-DoRA，或直接转向更重的全参数微调。</p>
<div class="blog_h3"><span class="graybg">生成模型拒绝采样微调</span></div>
<p>生成模型拒绝采样微调处理的是这样一类场景：模型已经具备基本生成能力，甚至已经完成一轮 SFT，但仍希望进一步提高答案正确率、格式稳定性或可验证任务表现；同时，团队又不希望立刻进入完整 RLHF、PPO 或 DPO 训练链路。此时，最自然的做法往往不是直接改写优化目标，而是先让模型对同一提示词生成多个候选，再通过外部评分器筛选出最优答案，把它重新写回 SFT 数据，再继续监督训练。</p>
<p>从训练形态上看，它通常遵循五步流程。</p>
<ol>
<li>确定起点模型。起点通常是 Base Model 经过一轮指令微调后的模型，因为拒绝采样依赖“先能生成基本可读候选”这一前提；若模型连任务格式都不稳定，后续筛选只会浪费大量采样预算。</li>
<li>为每个提示词生成多个候选。生成阶段的目标不是一次命中标准答案，而是提供一个足够有区分度的候选池，因此通常会适度提高采样多样性，让模型在同一问题上给出若干不同解法、表述或结构。</li>
<li>使用外部评分器做筛选。评分器可以是规则校验、可执行验证、单元测试、格式解析器、奖励模型、人类打分，或它们的组合。只要能较稳定地区分“明显更好”和“明显更差”，就足以支撑拒绝采样式数据构造。</li>
<li>把通过筛选的样本回写成监督数据。最常见的做法是只保留每个提示词下得分最高或达到阈值的候选，把它们整理成新的 prompt-response 数据集。这样做之后，后续训练仍然是标准 SFT，而不是显式偏好优化。</li>
<li>按监督目标继续训练，并周期性重新采样。随着模型能力提升，旧一轮采样得到的高分样本可能不再代表新的最优边界，因此很多实践会多轮迭代：采样、筛选、回写、再训练，再用更新后的模型继续采样。</li>
</ol>
<p>这条路线的关键超参数主要有五类：每个提示词生成多少候选、采样温度与 top-p 等多样性控制、接受阈值或保留比例、评分器本身的一致性与噪声水平，以及每轮回写数据占原始 SFT 数据的比例。它们共同决定一个核心权衡：候选越多、筛选越严格，样本平均质量可能越高，但成本也越高，且更容易把训练分布压窄；候选太少或筛选过松，则回写数据与原始 SFT 的差异不够明显，改进幅度往往有限。</p>
<p>与 DPO 相比，拒绝采样微调不会直接保留“胜者优于败者”这层相对关系，而是只把胜者留下来，因此信息利用率更低，但训练链路更简单；与 PPO 相比，它没有显式策略优化与在线回报建模，因此更容易稳定落地。工程上，它特别适合<span style="background-color: #c0c0c0;">答案是否正确较容易验证</span>的任务，而对开放式偏好、帮助性、安全性与语气细粒度排序，更常需要再叠加 DPO、RLHF 或其他偏好优化方法。</p>
<div class="blog_h3"><span class="graybg">生成模型直接偏好调优</span></div>
<p>生成模型直接偏好调优（Direct Preference Tuning for Generative Models）处理的是生成模型训练中的下一层目标：模型已经通过监督微调（SFT）学会了基本的指令跟随，但在多个可行回答之间，仍未必稳定偏向人类真正想要的输出。此时，训练重点不再是“把任务教给模型”，而是“把偏好写进模型”。在当前工程实践里，DPO（Direct Preference Optimization）是最典型、也最实用的直接偏好调优方案。</p>
<p>它之所以称为“直接”，就在于它绕开了“先训练奖励模型，再用 PPO 做强化学习”的显式 RLHF 流程，直接用偏好数据本身更新策略。对资源受限、希望复用现有 SFT 训练栈的团队而言，这通常是比传统 RLHF 更轻、更稳的选择。</p>
<div class="blog_h4"><span class="graybg">适用前提</span></div>
<p>直接偏好调优几乎总是建立在一个已经完成 SFT 的模型之上，而不是直接从 Base Model 开始。原因很直接：偏好数据表达的是“在两个都还算合理的回答之间，哪个更好”；如果模型连基本指令都还不会遵循，那么偏好优化得到的首先不是更好的对齐，而是更混乱的行为。因此，标准路径通常是<span style="background-color: #c0c0c0;">Base Model → SFT / QLoRA SFT → DPO</span>。若显存非常紧，DPO 阶段也仍然可以继续沿用 LoRA / QLoRA 这一类参数高效微调方案。</p>
<div class="blog_h4"><span class="graybg">训练流程</span></div>
<p>从工程与理论结合的角度看，生成模型直接偏好调优通常可以拆成六步。</p>
<ol>
<li>确定起点模型。直接偏好调优的起点通常不是原始基座，而是已经完成指令微调的模型。若前一阶段使用的是 LoRA / QLoRA，则这里有两种常见路径：要么先把 SFT 适配器合并回模型，再继续挂接新的 DPO 适配器；要么保留 SFT 结果作为起始状态，在其之上继续叠加偏好调优适配器。无论采用哪条路径，本质都一样：DPO 学习的是“在 SFT 行为之上继续排序优化”，而不是重新学习指令能力。</li>
<li>构造偏好数据集。训练样本的基本形态是三元组：提示词（prompt）、被接受回答（chosen）、被拒绝回答（rejected）。它要求两条回答都与同一个提示词对应，并且都具备一定可读性，否则优化信号会退化成“学会排除明显坏答案”，而不是学习细粒度偏好边界。高质量 DPO 数据的关键，不只是 chosen 比 rejected 更好，还在于两者足够接近、足够可混淆，这样模型才会被迫学习真正决定人类偏好的因素，例如信息完整性、语气、格式遵循、安全边界与事实可靠性。</li>
<li>建立参考模型。DPO 不显式训练奖励模型，但仍然需要一个冻结的参考模型（Reference Model）作为锚点。这个参考模型通常就是 SFT 后的模型快照，它定义了“原本模型认为哪些回答更可能”。训练模型的目标不是盲目提高 chosen 的概率，而是相对于参考模型，进一步提高 chosen 的相对优势，并压低 rejected 的相对优势，从而避免模型在优化偏好时过度漂移。</li>
<li>配置可训练参数。若采用 PEFT 路线，训练对象通常仍是 LoRA 适配器，而不是全参数更新。此时需要重新决定 DPO 阶段的 LoRA 容量与挂载范围。实践中，一个常见选择是让 DPO LoRA 覆盖注意力投影与 MLP 投影，因为偏好优化往往既涉及回答内容选择，也涉及风格、结构和安全边界的重排。若只覆盖极少数层，偏好容量可能不足；若覆盖过广，则训练成本与过拟合风险都会上升。</li>
<li>执行直接偏好优化。DPO 训练时会同时比较当前模型和参考模型在 chosen / rejected 两条回答上的条件概率。优化目标可以概括为：让当前模型比参考模型更偏向 chosen，同时更远离 rejected。与 PPO 不同，这一过程不需要 rollout、奖励建模或价值函数估计，因此训练流程更接近“带特殊损失函数的 SFT”，也更容易保持数值稳定。</li>
<li>保存与合并训练产物。若使用 LoRA / QLoRA，训练完成后最常见的产物仍然是 DPO 适配器，而不是完整模型。部署时可以只加载适配器与基座组合使用；若希望得到单体模型，也可以按顺序把 SFT 适配器与 DPO 适配器依次合并。这个顺序很重要，因为偏好调优是在指令能力之上继续修正输出排序，若跳过 SFT 阶段直接只保留 DPO 适配器，模型通常不会得到预期行为。</li>
</ol>
<div class="blog_h4"><span class="graybg">重要参数</span></div>
<p>直接偏好调优的关键超参数主要决定四件事：偏好优化强度、可训练容量、序列覆盖范围，以及在有限显存下能否稳定收敛。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">参数</td>
<td style="text-align: center;">控制对象</td>
<td style="text-align: center;">实践含义</td>
</tr>
</thead>
<tbody>
<tr>
<td>beta</td>
<td>DPO 偏好强度 / 正则化强度</td>
<td>它决定 chosen 相对 rejected 的概率优势要被放大到什么程度。取值过大，模型容易偏离参考模型过快；取值过小，偏好信号又会过弱。工程上常把它理解为“偏好更新的力度旋钮”。</td>
</tr>
<tr>
<td>学习率（Learning Rate）</td>
<td>更新步长</td>
<td>DPO 阶段通常比早期 SFT 更接近“行为微调”而非“能力学习”，因此学习率往往需要更保守。学习率过高时，最先损坏的往往不是困惑度，而是回复风格、格式一致性与安全边界。</td>
</tr>
<tr>
<td>批大小与梯度累积</td>
<td>等效 batch 规模</td>
<td>DPO 每个样本都包含 prompt、chosen、rejected 三部分，显存压力通常高于普通单输出 SFT，因此更依赖小 batch 配合梯度累积来换取稳定训练。</td>
</tr>
<tr>
<td>max_prompt_length</td>
<td>提示词截断长度</td>
<td>它控制参考上下文保留多少信息。过短会丢失任务条件，过长则显著抬高显存与计算成本。</td>
</tr>
<tr>
<td>max_length</td>
<td>整体序列长度上限</td>
<td>它决定 prompt 与回答总共能占多少 token。对多轮对话、长解释和结构化输出任务而言，这个参数直接影响偏好信号能否覆盖完整回答。</td>
</tr>
<tr>
<td>warmup_ratio</td>
<td>训练初期学习率预热比例</td>
<td>偏好训练通常数据量小、梯度信号陡峭，预热有助于避免一开始就把模型推离参考分布。</td>
</tr>
<tr>
<td>优化器与精度</td>
<td>显存与数值稳定性</td>
<td>若沿用 QLoRA 路线，分页 AdamW、混合精度与梯度检查点通常仍是默认组合，它们解决的是“偏好训练能否在消费级显存下跑稳”的问题，而不是损失函数本身的问题。</td>
</tr>
<tr>
<td>LoRA 的 <span displaypfx="inline-" class="mathjax-container">\(r\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span>、target_modules、dropout</td>
<td>可训练容量与挂载位置</td>
<td>这些参数控制 DPO 阶段到底允许模型改动多大子空间。偏好差异若主要体现在语气、格式和细粒度行为边界上，低秩适配器通常足够；若 chosen / rejected 差异深度依赖复杂知识、推理链条或长程一致性，则更高秩或更广覆盖范围往往更稳。</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">推理阶段优化</span></div>
<p>推理优化（Inference Optimization）目标是在质量约束下同时降低延迟（Latency）、显存（Memory）和成本（Cost）。主线方法包括量化（Quantization）、KV Cache 优化、推测解码（Speculative Decoding）和连续批处理（Continuous Batching）。</p>
<div class="blog_h2"><span class="graybg">量化</span></div>
<p>量化（Quantization）通过降低数值精度减少带宽与显存占用。常见路径：FP16/BF16 到 INT8/INT4/FP8。大模型推理最常见的是权重量化（Weight-only Quantization），因为它实现简单、收益稳定；进一步的激活量化（Activation Quantization）能带来更大的加速空间，但对硬件与校准（Calibration）更敏感。</p>
<p>常见概念：</p>
<ul>
<li>PTQ（Post-Training Quantization）：训练后量化，依赖少量校准数据估计尺度（Scale）与零点（Zero-point）。</li>
<li>QAT（Quantization-Aware Training）：训练时模拟量化误差，通常精度更好但成本更高。</li>
<li>按粒度：per-tensor / per-channel / group-wise，粒度越细通常越准但开销更高。</li>
</ul>
<ul>
<li>INT8：8-bit 整数量化，精度与兼容性通常最稳，常作为权重量化的保守起点。</li>
<li>INT4：4-bit 整数量化，压缩率更高，但更依赖量化算法、分组方式与校准质量。</li>
<li>FP8：8-bit 浮点格式，动态范围通常优于 INT8，更适合高端硬件上的高吞吐推理与训练。</li>
<li>GGML：最早的本地 CPU/GPU 推理张量库与算子生态，强调轻量、本地部署与量化推理。</li>
<li>GGUF：建立在 GGML 生态上的统一模型文件格式，用于封装权重、词表、量化元数据和模型配置，已成为 llama.cpp 一类本地推理工具的主流分发格式。</li>
</ul>
<div class="blog_h2"><span class="graybg">KV Cache 缓存</span></div>
<p>KV 缓存（Key-Value Cache, KV Cache）用于避免重复计算历史 token 的 Key/Value。在解码器（Decoder-only）模型中，生成第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个 token 时需要与前 <span displaypfx="inline-" class="mathjax-container">\(t-1\)</span> 个位置做注意力；若不缓存，每一步都会重复算一遍历史的 K/V，代价极高。</p>
<p>代价分解常用“预填充（Prefill）+ 解码（Decode）”来理解：Prefill 处理提示词（Prompt）并写入 KV；Decode 每生成一个 token 只需计算新 token 的 Q/K/V，并与缓存做一次注意力。这把每步成本从“重算整段历史”降为“读取缓存并做一次加权求和”。</p>
<p>KV Cache 的主要代价是显存：缓存规模与层数、头数、上下文长度线性增长，因此优化会围绕缓存布局（如 Paged Attention）、压缩（如 KV 量化）与复用策略展开。</p>
<p>粗略的量级估算：若每层缓存张量形状近似为 <span displaypfx="inline-" class="mathjax-container">\(K,V\in\mathbb{R}^{L\times n_{\text{kv}}\times d_k}\)</span>，则单 batch 的显存规模约为</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Mem}_{\mathrm{KV}}\approx 2\cdot N_{\text{layers}}\cdot B\cdot L\cdot n_{\text{kv}}\cdot d_k\cdot \text{bytes}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(n_{\text{kv}}\)</span> 在 GQA/MQA 中显著小于 Query 头数，因此 <span style="background-color: #c0c0c0;">GQA/MQA 往往是降低 KV Cache 的一阶有效手段</span>；进一步的手段包括 KV 量化、Paged KV（按块管理与回收）、以及对重复前缀做复用/持久化（Prompt Caching）。</p>
<div class="blog_h3"><span class="graybg">Paged Attention</span></div>
<p>Paged Attention 是一种面向推理阶段的 KV Cache 分页管理与访问机制。它把逻辑上连续的一段 KV 序列拆成固定大小的块（Blocks / Pages），再用块表把请求看到的连续上下文，映射到物理上未必连续的显存块，从而支持动态分配、回收与复用。</p>
<p>它解决的重点不是“如何把 KV Cache 压得更小”，而是“如何把不断增长的 KV Cache 更高效地组织、分配和访问”。</p>
<p>这种设计的收益主要有三点。第一，减少显存碎片（Fragmentation）：请求长度不断变化时，不再需要为每条序列预留一整段连续大内存。第二，提升动态批处理与并发调度效率：不同请求可以共享统一的块分配与回收机制，更容易在在线服务里做 token 级调度。第三，和前缀复用天然兼容：当某段前缀已经生成过，对应的 KV blocks 可以直接挂到新请求的块表上，而不必搬移整段缓存。</p>
<p>因此，Paged Attention 的本质是<span style="background-color: #c0c0c0;">KV Cache 的分页管理与访问优化</span>，而不是新的注意力数学形式。它不改变注意力结果本身，改变的是推理引擎如何在显存里存放和调度这些历史状态。也正因为如此，它通常与连续批处理（Continuous Batching）、Prompt Caching 和块级回收策略一起出现，是高吞吐推理引擎里的基础设施级组件。</p>
<div class="blog_h4"><span class="graybg">为什么连续预留显存会失效</span></div>
<p>若沿用传统的连续分配思路，系统通常需要为每条请求预留一大段连续显存，用来容纳“未来可能继续增长”的 KV Cache。但生成长度在服务开始时并不可知：有的请求很快结束，有的请求会持续生成很长文本。于是，预留得太保守会频繁扩容甚至失败，预留得太激进又会浪费大量显存。</p>
<p>这会同时带来两类碎片。内部碎片（Internal Fragmentation）来自“已经预留但尚未使用”的那部分连续空间；外部碎片（External Fragmentation）则来自请求不断进入和退出后，显存里出现许多零散空洞。即使总空闲显存仍然足够，也可能因为拿不到一整段足够长的连续区域，而无法容纳新的长请求。Paged Attention 的出发点正是消除这种“连续大块内存”假设。</p>
<div class="blog_h4"><span class="graybg">块表是如何工作的</span></div>
<p>Paged Attention 借鉴了虚拟内存（Virtual Memory）的分页思想。推理引擎先把可用于 KV Cache 的显存切成大量固定大小的物理块（Physical Blocks），每个块只容纳固定数量 token 的 K/V。对单条序列而言，模型逻辑上仍然看到一段从位置 1 到位置 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 的连续上下文；但在物理层，这些 token 对应的 KV 可能分散存放在许多互不相邻的块里。</p>
<p>块表（Block Table）负责维护这种映射关系：逻辑上的第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个块，对应显存中的哪一个物理块。当当前尾块写满后，系统只需从空闲池中再取一个新块，把它挂到块表末尾即可，并不要求新块和前一块在物理地址上相邻。这样一来，序列增长就从“申请更长的一整段连续空间”变成了“追加一个固定大小的新块”。</p>
<p>这种布局把碎片问题压到很小的范围内。内部浪费通常只会出现在最后一个尚未写满的尾块中，而不会沿整条序列累计；外部碎片则因为不再要求大块连续空间而大幅缓解。工程上真正被频繁分配和回收的，不再是“整条请求的整段缓存”，而是细粒度、等尺寸的块。</p>
<div class="blog_h4"><span class="graybg">注意力访问路径如何改变</span></div>
<p>在 Paged Attention 中，注意力访问路径（Access Path）指的是：当前 query 如何找到并读取历史 token 对应的 Key / Value，再完成注意力计算。它改变的是这条访问路径，而不是注意力公式本身。对当前 query 而言，模型仍然需要与全部历史 token 的 Key / Value 交互；不同之处在于，这些历史状态不再通过一块连续显存读取，而是由 kernel 按块表顺序逐块定位、聚合并完成注意力计算。逻辑上的序列连续性由块表保证，物理上的离散布局由运行时屏蔽。</p>
<p>因此，Paged Attention 更准确地说是一种<span style="background-color: #c0c0c0;">分页式 KV 访问内核</span>。它要求注意力实现能够接受“逻辑连续、物理离散”的缓存布局，并在 kernel 内高效完成地址映射、块级遍历和结果聚合。也正因为如此，Paged Attention 并不是简单的内存分配器技巧，而是需要推理 runtime 与注意力 kernel 协同设计的系统级能力。</p>
<div class="blog_h4"><span class="graybg">前缀共享与写时复制</span></div>
<p>分页布局还自然支持前缀共享（Prefix Sharing）。当多个请求拥有相同前缀时，它们可以在块表层共同指向同一组前缀块，而不必各自复制一份完整的前缀 KV。这种共享对系统提示词固定、多轮追问、束搜索（Beam Search）或树状探索都很重要，因为这些场景往往存在大段公共前缀。</p>
<p>一旦不同请求在后续 token 上开始分叉，系统再为各自分配新的尾部块即可；已经共享的旧块保持只读并可继续复用。这就是写时复制（Copy-on-Write）的典型思想：真正发生差异时才复制，未分叉前尽量共享。后文的 Prompt Caching 可以看作这种能力在“跨请求、跨时间窗口前缀复用”上的工程化延伸。</p>
<div class="blog_h3"><span class="graybg">Prompt Caching（前缀缓存）</span></div>
<p>Prompt Caching（前缀缓存）缓存的是“提示词前缀的 KV Cache”，目标是避免在多轮对话或重复前缀场景下反复做 Prefill：当新请求的开头 token 序列与某个已缓存前缀完全一致时，推理引擎可以直接复用这段前缀的 KV blocks，只对新增 token 做增量计算。</p>
<p>它直接优化两个指标：</p>
<ul>
<li>首 token 延迟（Time To First Token, TTFT）：减少或跳过重复前缀的 Prefill。</li>
<li>成本：若推理服务对缓存命中（Cache Hit）的前缀按更低费率计费，则输入 token 成本显著下降。</li>
</ul>
<p>Prompt Caching 的工程前提是“字节级一致（Exact Prefix Match）”：通常要求 tokenizer 后的 token 序列完全一致，任何空格/标点/系统提示词差异都会导致 cache miss。因此它更适合“系统提示词固定 + 文档前缀固定 + 多次追问”的产品形态，而不是随意变化的自由对话。</p>
<p>这类缓存的生命周期通常不是固定 TTL，而更像一种<span style="background-color: #c0c0c0;">受显存压力驱动的短生命周期缓存</span>。最常见的形态是直接驻留在 GPU 显存里，只要显存充足就保留，一旦新请求挤压缓存池，就按 LRU 等策略优先淘汰不活跃前缀。也有系统会把不活跃的 KV blocks 下沉到 CPU 内存形成分层缓存，以换取更长的可复用时间；但完整 KV Cache 很少作为常规路径直接落盘长期持久化，因为它体积大、回读慢、反序列化开销高，往往不如重新做一次 Prefill 划算。</p>
<div class="blog_h3"><span class="graybg">多租户隔离（Multi-tenancy Isolation）</span></div>
<p>在共享推理后端中，Prompt Caching 必须严格做多租户隔离（Multi-tenancy Isolation）：缓存命中不能只依赖“前缀文本哈希”，还需要把租户/用户身份与模型版本纳入缓存键（Cache Key），避免跨用户“串台”与侧信道泄露。典型复合键（Composite Key）包括：</p>
<ul>
<li>租户 ID（Org/User ID）</li>
<li>模型版本（Model/Weights Version）</li>
<li>前缀 token 哈希（Prefix Hash）与长度等元数据</li>
</ul>
<p>缓存生命周期通常很短：KV Cache 占用显存，后端会用 LRU（Least Recently Used）等策略在压力下淘汰缓存；因此“隔天再聊成本回到原价”是常见现象。若业务需要跨小时/跨天复用长前缀，工程上一般转向两类手段：长效上下文缓存（Context Caching，若服务支持）或 RAG/摘要把长前缀变成可检索的外部状态。</p>
<div class="blog_h2"><span class="graybg">KV Cache 压缩</span></div>
<p>KV Cache 压缩（KV Cache Compression）讨论的是：在不显著破坏注意力行为的前提下，把每个 token 需要缓存的 Key / Value 表示存得更小。它的直接目标不是训练提速，而是长上下文推理时的显存占用、带宽压力和并发能力。因此，从<span style="background-color: #c0c0c0;">优化对象</span>看，Latent KV、MLA、KV 量化、TurboQuant 都属于推理阶段优化；只是从<span style="background-color: #c0c0c0;">实现方式</span>看，它们并不全是推理后处理技巧，有些方法需要在模型架构和训练阶段就内建进去。</p>
<div class="blog_h3"><span class="graybg">Latent KV / MLA</span></div>
<p>一条路线是潜空间压缩（Latent-space Compression）。它不是直接缓存完整维度的 Key / Value，而是先把每个 token 的 KV 投影到更低维的潜空间（Latent Space），缓存潜变量；当需要参与注意力计算时，再由模型内部结构把潜变量还原成用于打分和聚合的表示。这类方法常被概括为 Latent KV，典型代表就是 MLA（Multi-head Latent Attention）。</p>
<p>这类方法的本质是一种<span style="background-color: #c0c0c0;">架构级的推理友好设计</span>。它服务的是推理阶段的 KV 成本问题，但并不是像 KV 量化那样在现成模型外部直接套一个压缩器；模型通常需要在训练时就学会如何在潜空间里存储和恢复有效的注意力信息。因此，它应被放在推理优化里讨论，但不能误解成“任何现有模型都可无缝加上的后处理插件”。</p>
<p>相对于 GQA/MQA 只是在“头数”上减少缓存，Latent KV / MLA 更进一步，直接压缩每个 token 的 KV 表示维度。收益通常体现为更长上下文、更高并发和更低带宽占用；代价则是算子更复杂、实现更依赖内核与数值稳定性，而且表示压缩本身会改变模型的内部信息流，因此往往需要专门的训练配方与模型容量补偿。</p>
<div class="blog_h3"><span class="graybg">DeepSeek V4 的 CSA 与 HCA</span></div>
<p>DeepSeek V4 在 KV Cache 压缩上的推进，不再只压缩“每个 token 的 KV 向量有多宽”，而是进一步压缩“历史序列里需要保留多少条 KV 条目”。这条路线的代表就是混合注意力（Hybrid Attention）中的 CSA（Compressed Sparse Attention）与 HCA（Heavily Compressed Attention）。如果说 MLA 关注的是<span style="background-color: #c0c0c0;">单条缓存的表示维度</span>，那么 CSA / HCA 关注的就是<span style="background-color: #c0c0c0;">整段历史在序列轴上的压缩与检索</span>。</p>
<p>设历史序列中已有 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个 token 的 KV 条目，普通注意力会让当前位置的 Query 直接面对这 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 条历史记录。CSA / HCA 的共同思路是：先把相邻若干条 KV 条目聚合成压缩块，再让 Query 对这些压缩块做注意力。若每 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 个 token 压成一个块，则序列级缓存长度大致从 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 降到 <span displaypfx="inline-" class="mathjax-container">\(\frac{n}{m}\)</span>；若压缩率更大，长度还会进一步下降。这时压缩块可抽象写成</p>
<span displaypfx="" class="mathjax-container">\[\tilde c_i=\sum_{j=mi}^{m(i+1)-1}\alpha_{ij}c_j,\qquad \sum_j \alpha_{ij}=1,\ \alpha_{ij}\ge 0\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(c_j\)</span> 表示原始 KV 条目， <span displaypfx="inline-" class="mathjax-container">\(\tilde c_i\)</span> 表示第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个压缩块，权重 <span displaypfx="inline-" class="mathjax-container">\(\alpha_{ij}\)</span> 由模型学习得到。这个式子表达的不是简单平均，而是<span style="background-color: #c0c0c0;">带位置偏置与内容权重的可学习块聚合</span>：一个压缩块内部，哪些 token 更该代表这一段历史，由模型自己决定。</p>
<p>若按组件拆开看，CSA 基本可以分成三步。第一步是 <span style="background-color: #c0c0c0;">Token-Level Compressor</span>：把每 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 个 token 压成一个压缩块。第二步是 <span style="background-color: #c0c0c0;">Lightning Indexer</span>：不给每个 Query 暴力遍历所有压缩块，而是先用一个更轻的索引器对压缩块打粗分，再做 top-k 选择。第三步是 <span style="background-color: #c0c0c0;">Shared KV MQA</span>：被选中的压缩块不再分开维护多组 Key / Value，而是以共享 KV 的 Multi-Query Attention 方式参与核心注意力。图里的“先压缩、再挑重点、最后精读”正对应这三层结构。</p>
<p>CSA 的关键在于“先压缩，再稀疏检索”。它先把历史 KV 压成较细粒度的压缩块，然后再为当前 Query 构造一个轻量索引器，对这些压缩块打分，只保留最相关的 top-k 块参与核心注意力。若把被选中的块集合记为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{S}_t\)</span>，则当前位置 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 的核心注意力可写成</p>
<span displaypfx="" class="mathjax-container">\[o_t=\mathrm{Attn}\bigl(q_t,\{\tilde c_i\}_{i\in \mathcal{S}_t}\bigr),\qquad \mathcal{S}_t=\mathrm{TopK}\bigl(\mathrm{score}(q_t,\tilde c_i),k\bigr)\]</span>
<p>这意味着 CSA 不再让 Query 面对全部历史，而是先在压缩后的历史摘要中做一次粗检索，再只对最值得关注的若干块做精算。它本质上是一种“压缩后的稀疏注意力”：既利用块压缩降低历史长度，又利用 top-k 选择避免在所有压缩块上做全量注意力。这里的 Lightning Indexer 之所以重要，是因为它把“找哪些块值得看”这件事从昂贵的全量注意力里剥离出来，先用一个更便宜的门禁系统完成粗召回。</p>
<p>HCA 的设计更激进。它同样先做块压缩，但压缩率 <span displaypfx="inline-" class="mathjax-container">\(m'\)</span> 更高，而且不再对压缩块做 top-k 稀疏选择，而是直接在这些<span style="background-color: #c0c0c0;">高度压缩后的全局摘要</span>上做稠密注意力。若把 HCA 的压缩块记为 <span displaypfx="inline-" class="mathjax-container">\(\hat c_i\)</span>，则它的核心形式更接近</p>
<span displaypfx="" class="mathjax-container">\[o_t=\mathrm{Attn}\bigl(q_t,\{\hat c_i\}_{i=1}^{n/m'}\bigr),\qquad m' \gg m\]</span>
<p>因此，HCA 牺牲的是块内细节，换来的是一种非常便宜的全局视野：每次注意力看到的不是细粒度历史，而是“高度概括过的整段上下文骨架”。它和 CSA 最大的结构差异有两点。第一，HCA 的压缩更重，例如官方配置里常见 <span displaypfx="inline-" class="mathjax-container">\(m=4\)</span> 对应 CSA，而 <span displaypfx="inline-" class="mathjax-container">\(m'=128\)</span> 对应 HCA。第二，HCA 没有 Lightning Indexer，不做 top-k 挑块，而是直接在这些高度压缩后的块上做稠密注意力。因此，CSA 更像“压缩后再精确召回”，HCA 更像“极粗粒度的全局扫视”。二者混合使用，形成的就是一条<span style="background-color: #c0c0c0;">局部更细、全局更粗、但总体成本受控</span>的长上下文建模路径。</p>
<p>从直观上看，可以把它类比成查阅一本极长的技术手册。MLA 做的是“把每一页字缩得更紧一些，但页数不变”；CSA 做的是“先把相邻几页整理成摘要，再从这些摘要里只抽最相关的若干段精读”；HCA 做的是“先把整本书压成更粗的章节提要，再快速通览全部章节提要”。三者都在降成本，但压缩的对象并不相同。</p>
<p>DeepSeek V4 还在 CSA / HCA 外围加入了一组保证可用性的细节。其一，压缩后的 Query 与 KV 条目在进入核心注意力前还会再做一次 RMSNorm，以控制 attention logits 的数值尺度；其二，只对最后一部分维度施加部分旋转位置编码（Partial RoPE），避免压缩后的表示完全丢失相对位置信息；其三，额外保留一条滑动窗口注意力分支，让最近若干 token 仍以未压缩形式参与计算，从而补回块压缩天然会削弱的局部精细依赖。于是整个混合注意力的真实行为并不是“只看压缩摘要”，而是<span style="background-color: #c0c0c0;">远处靠压缩块，近处靠局部窗口；全局靠 HCA，重点靠 CSA</span>。</p>
<p>这张图里“精读 + 略读”这个比喻还有一个很关键但容易被忽略的实现含义：DeepSeek V4 不是在同一层里把两种注意力简单相加，而是把它们按层<span style="background-color: #c0c0c0;">交替堆叠（interleaved）</span>。官方给出的 Pro 配置是 61 层，其中前两层先用 HCA，后续层再让 CSA 与 HCA 交替出现。这样做的好处是，不需要让每一层同时承担“精细检索”和“全局粗读”两套昂贵职责，而是让不同层在长程建模里分工：有些层负责重点块召回，有些层负责全局背景扫描。</p>
<p>从技术谱系上看，CSA / HCA 也说明 KV Cache 优化已经分成两条正交路线：一条是 MLA 这类<span style="background-color: #c0c0c0;">表示维压缩</span>，压缩每个 token 自身的 KV 宽度；另一条是 CSA / HCA 这类<span style="background-color: #c0c0c0;">序列维压缩</span>，压缩需要保留的历史条目数量。前者更像“把每张卡片做薄”，后者更像“把很多卡片归并成索引块”。它们解决的是同一个 KV Cache 瓶颈，但发力点不同。</p>
<div class="blog_h3"><span class="graybg">KV 量化</span></div>
<p>KV 量化（KV Quantization）指的是：在保留 Key / Value 语义角色不变的前提下，把 KV Cache 从 FP16 / BF16 等较高精度压缩到更低 bit 的数值格式，以减少存储开销与读取带宽。它服务的仍然是原始注意力结构，只是把缓存表示改成更节省显存和带宽的低比特形式。</p>
<p>这条路线不依赖潜空间替换，而是保留原始 Key / Value 的语义角色，直接压缩缓存张量。这类方法更接近传统意义上的推理后处理：对现有模型更友好，工程接入成本通常低于 Latent KV / MLA，但量化误差是否会放大到注意力分布中，是成败关键。</p>
<div class="blog_h3"><span class="graybg">TurboQuant</span></div>
<p>TurboQuant 是一种面向 KV Cache 的内积保真量化（Inner-product-preserving Quantization）方法。它属于 KV 量化路线，但优化目标并不只是在坐标层面重建原向量，而是尽量保住注意力计算真正依赖的内积关系。</p>
<p>这也是它与普通低比特量化的关键区别。对 Key 而言，最重要的不是逐坐标把原向量还原得多么精确，而是尽量保住 <span displaypfx="inline-" class="mathjax-container">\(q\cdot k\)</span> 这类内积关系，因为注意力分数本身就是由这些内积驱动的。换句话说，TurboQuant 优先保护的是<span style="background-color: #c0c0c0;">注意力几何结构</span>，而不只是原始向量外形。</p>
<div class="blog_h4"><span class="graybg">为什么普通 KV 量化不够</span></div>
<p>如果只从存储角度看，KV Cache 似乎只是把一串浮点数改成更低 bit 的数字格式；但注意力并不是逐坐标读取这些数，而是先计算</p>
<span displaypfx="" class="mathjax-container">\[s_i=\frac{q\cdot k_i}{\sqrt{d_k}},\quad \alpha_i=\mathrm{softmax}(s_i)\]</span>
<p>然后再用 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span> 去聚合对应的 <span displaypfx="inline-" class="mathjax-container">\(v_i\)</span>。这意味着，Key 上很小的量化误差，一旦改变了 <span displaypfx="inline-" class="mathjax-container">\(q\cdot k_i\)</span> 的相对大小，就可能在 softmax 之后被放大成明显不同的注意力分布。也正因为如此，普通“逐坐标尽量还原”的量化器未必就能得到好的注意力质量，因为它优化的是向量外形，而注意力真正依赖的是内积排序与相对间隔。</p>
<p>从这个角度看，TurboQuant 更像一种<span style="background-color: #c0c0c0;">面向在线内积估计的向量量化</span>，而不是普通的低比特存储。它特别适合 KV Cache 这类在线场景：缓存必须边生成边写入，解码时又要反复读取并参与 <span displaypfx="inline-" class="mathjax-container">\(QK^\top\)</span> 计算，因此量化器不能只在离线重建误差上表现好，还必须在在线注意力分数上尽量稳定。</p>
<div class="blog_h4"><span class="graybg">核心直觉</span></div>
<p>TurboQuant 的核心直觉可以概括为一句话：<span style="background-color: #c0c0c0;">先把向量变成更容易量化的形状，再用极低成本修补量化后最关键的内积偏差</span>。如果把原始向量直接送进低比特量化器，少数高能量坐标、异常值和不均匀分布往往会主导误差；而注意力最敏感的又不是“某一维少了多少”，而是“整体内积关系是否还成立”。因此，TurboQuant 并不把压缩过程当作一次简单的数值离散化，而是分成了主压缩与内积校正两个层次。</p>
<div class="blog_h4"><span class="graybg">两阶段结构</span></div>
<p>它的整体结构可以理解为两阶段。第一阶段负责主量化（Main Quantization）：先对向量施加随机旋转（Random Rotation），再执行高质量的低比特量化。第二阶段负责残差校正（Residual Correction）：不再追求把每个坐标补得更精确，而是针对第一阶段留下的残差，额外附加一个极轻量的编码，用来降低量化后内积估计的系统偏差。这样一来，TurboQuant 的目标就从“压缩向量”推进成了“压缩后仍尽量保住注意力分数”。</p>
<p>若用概念来源去理解，这条路线可以看成“PolarQuant 风格的主量化 + QJL 风格的残差校正”的组合。前者解决“怎么把向量本体存得足够省且失真足够低”，后者解决“怎么让压缩后内积估计不要系统性跑偏”。这也是为什么 TurboQuant 看上去像一个量化方法，实际上却比普通低比特缓存更接近一个<span style="background-color: #c0c0c0;">面向注意力运算的压缩管线</span>。</p>
<div class="blog_h4"><span class="graybg">为什么先做随机旋转</span></div>
<p>随机旋转的作用是改善向量分布的可量化性，而不是“制造随机性”。原始 KV 向量往往各维统计特性差异很大，有的维度能量特别集中，有的维度接近冗余；直接低比特量化时，少数尖峰维度会迫使量化器把刻度对齐到这些极端值，结果是大量普通维度的有效分辨率被浪费掉。随机正交旋转之后，向量能量会更均匀地摊到各个方向上，各维统计特性更接近，量化器就更容易在固定 bit 预算下稳定工作。</p>
<p>更直观地说，随机旋转做的是“先把难量化的尖峰分布打散，再做统一压缩”。这一步并不改变向量之间真正的几何关系，却显著改变了逐坐标量化时面对的数值形态。因此，它服务的不是语义本身，而是后续低比特编码的数值条件。</p>
<div class="blog_h4"><span class="graybg">为什么还需要残差校正</span></div>
<p>如果只有第一阶段，TurboQuant 仍然只是一个“更会量化向量”的方法，还不能真正保证注意力分数稳定。问题在于：即使主量化已经把总体失真压得很低，内积估计仍可能存在系统偏差，而 softmax 对这种偏差非常敏感。于是第二阶段不再继续追求全面重建，而是把预算集中用在“修正内积”这件事上。</p>
<p>这种设计背后的判断非常重要：在注意力里，全面重建所有坐标并不是最高优先级，优先级更高的是让“谁该被关注、关注强度大概多大”不要被量化误差改写。也正因为如此，TurboQuant 的第二阶段看起来只加了很轻的一层编码，却能显著改变注意力保真度，因为它修补的是最承重的误差，而不是平均分摊误差。</p>
<div class="blog_h4"><span class="graybg">如何理解它的收益</span></div>
<p>TurboQuant 的收益并不主要体现在“把某个向量压缩得多漂亮”，而体现在<span style="background-color: #c0c0c0;">相同显存下能缓存更长上下文，或在相同上下文下支持更高并发</span>。它直接作用于解码阶段最贵的那部分状态，即各层持续增长的 KV Cache。对长上下文推理而言，这通常比单步算子优化更承重，因为一旦缓存存不下，系统就会被迫降低 batch、缩短上下文，或转向更慢的外部交换路径。</p>
<p>但这类收益能否真正转化为吞吐提升，还取决于实现细节。若压缩后每一步都要做很重的解码、解包和额外访存，理论上的存储优势就可能被运行时开销抵消。因此，TurboQuant 不只是一个“理论上更省”的方法，它是否好用还取决于推理引擎是否能把旋转、量化、残差校正与注意力计算做成足够紧的执行路径。</p>
<div class="blog_h4"><span class="graybg">与 Latent KV / MLA 的关系</span></div>
<p>因此，TurboQuant 与 Latent KV / MLA 的差异非常明确。TurboQuant 是<span style="background-color: #c0c0c0;">量化压缩</span>，核心是把同一个 KV 向量存得更省，同时尽量保住注意力内积；Latent KV / MLA 是<span style="background-color: #c0c0c0;">潜空间压缩</span>，核心是先换成更低维的内部表示再缓存。前者更接近推理栈里的压缩器，后者更接近模型架构层面的推理优化设计。它们都属于 KV Cache 压缩，但技术路线和接入条件并不相同。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">场景</td>
<td style="text-align: center;">策略</td>
<td style="text-align: center;">成本</td>
<td style="text-align: center;">延迟</td>
<td style="text-align: center;">风险/约束</td>
</tr>
</thead>
<tbody>
<tr>
<td>连续多轮对话（分钟级）</td>
<td>Prompt Caching</td>
<td>低（命中时前缀“打折”）</td>
<td>低（TTFT 改善明显）</td>
<td>要求前缀严格一致；缓存易被淘汰</td>
</tr>
<tr>
<td>跨小时/跨天复用长文档前缀</td>
<td>Context Caching / 预热</td>
<td>中（通常含存储费）</td>
<td>中-低</td>
<td>依赖服务能力；需要明确缓存生命周期与权限</td>
</tr>
<tr>
<td>大规模文档问答</td>
<td>RAG（检索增强生成）</td>
<td>低且稳定</td>
<td>稳定</td>
<td>需要索引构建、召回/重排与证据注入治理</td>
</tr>
<tr>
<td>长对话状态维护</td>
<td>滚动摘要（Rolling Summary）</td>
<td>低</td>
<td>稳定</td>
<td>摘要漂移与可验证性问题；需结构化约束</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Speculative Decoding</span></div>
<p>推测解码（Speculative Decoding）用一个更小更快的草稿模型（Draft Model）一次提出多个候选 token，再由大模型（Target Model）并行验证并接受其中尽可能长的前缀。若接受率高，就能显著减少大模型需要执行的解码步数；若接受率低，则会退化并产生额外验证开销。实际收益取决于草稿模型速度、分布匹配程度与实现细节。</p>
<div class="blog_h2"><span class="graybg">批处理</span></div>
<p>批处理（Batching）讨论的是：如何把多个请求或多个 token 步骤拼到同一轮计算里，以提高硬件利用率。它本质上是在吞吐（Throughput）、单请求延迟（Latency）和调度复杂度之间做权衡。推理系统里常见的批处理并不只有一种，固定批处理、动态批处理和连续批处理服务的是不同流量形态。</p>
<div class="blog_h3"><span class="graybg">固定批处理（Static Batching）</span></div>
<p>固定批处理（Static Batching）是最传统的做法：先攒够固定数量的请求，再统一组成一个 batch 执行。它的优点是实现简单、执行路径稳定、硬件利用方式容易预测，因此很适合离线推理、批量评测、Embedding 批处理或输入长度相近的后台任务。</p>
<p>它的问题同样直接。第一，等待时间明显：如果系统必须凑满 batch 才发车，单请求延迟会被批量收集时间拉长。第二，padding 浪费常常很大：当同一批次中的序列长度差异明显时，短序列必须补到与长序列对齐，等于用无效 token 占用了算力。也正因为如此，固定批处理通常更适合“任务离线、流量可控、长度相近”的环境，而不适合作为通用在线 LLM 服务的主调度方式。</p>
<div class="blog_h3"><span class="graybg">动态批处理（Dynamic Batching）</span></div>
<p>动态批处理（Dynamic Batching）是在固定批处理上的现实改进。系统不再死等“凑满固定 batch”，而是在一个较短时间窗口内，把相近时间到达的请求尽量拼进同一批次里。这样做的好处是：既能保留一部分并行执行收益，又能避免固定批处理带来的长时间等待。</p>
<p>它适合中等并发的在线推理服务，尤其是在请求长度相对可控、生成长度不算太长的场景里表现不错。但它仍然主要以“请求”为单位成批，而不是以“token 步骤”为单位重组，因此当不同请求的生成过程拉得很长、长度分化越来越大时，批次内部的同步等待和 padding 问题依然存在。换句话说，动态批处理改善了固定批处理的僵硬性，但还没有真正解决 LLM 自回归解码里的异步性问题。</p>
<div class="blog_h3"><span class="graybg">连续批处理（Continuous Batching）</span></div>
<p>连续批处理（Continuous Batching）面向在线服务：请求不断到达、序列长度各不相同。与“固定 batch + padding”相比，连续批处理按 token 粒度调度，把不同请求的下一步解码拼到同一个批次里，从而提升吞吐并减少 padding 浪费。它不再把“一整条请求”视为不可拆分的调度单位，而是把系统里所有活跃序列都当作可持续重组的解码状态。</p>
<p>这类方法之所以重要，是因为 LLM 解码阶段天然是异步的：有的请求很快结束，有的请求仍在长输出，有的请求刚刚进入系统。若继续用请求级批处理，GPU 经常会被“最慢那几个序列”拖住；而连续批处理允许系统在每一步把已经完成的序列移出，再把新请求或其他活跃序列的下一步补进来，使 batch 始终维持较高利用率。</p>
<div class="blog_h4"><span class="graybg">为什么静态批处理不够</span></div>
<p>静态批处理的问题并不只是“padding 多一点”这么简单，而是它把一整条请求当成不可拆分的运行单元。假设同一批里有四个请求，其中三个很快生成结束，另一个却还要继续生成很长一段文本，那么 GPU 在后续很多步里实际上只能继续为这一个长请求服务，原先空出来的位置却不能被新请求及时填补。这样造成的不是数学错误，而是硬件空转：算力被“批次里最慢的那个请求”绑住了。</p>
<p>因此，连续批处理真正改变的不是 batch 的大小，而是<span style="background-color: #c0c0c0;">调度颗粒度</span>。传统静态批处理按“请求”调度，连续批处理按“下一 token 的解码步”调度。只要某个请求结束、被取消、或暂时被抢占，调度器就可以立刻把它从运行队列中移出，再用等待队列里的新请求补位。</p>
<div class="blog_h4"><span class="graybg">vLLM 到底在连续什么</span></div>
<p>在 vLLM 这类系统里，所谓“连续”指的是：调度器会以 decode 迭代为粒度，持续重组当前运行中的请求集合，而不是一次性固定一个 batch 直到所有请求全部结束。连续批处理的“连续”，本质上是运行队列和解码步的持续流动。</p>
<p>具体来说，调度器会在几乎每一次 decode 迭代之后重新审视当前运行集合。若当前正在运行的请求集合记为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{R}_t\)</span>，每个请求在第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 轮各生成一个新 token，那么在进入第 <span displaypfx="inline-" class="mathjax-container">\(t+1\)</span> 轮之前，系统就会检查：哪些请求已经生成到结束符、达到最大长度或被上层中断；哪些等待中的新请求应被拉入运行队列。于是，运行中的 batch 并不是一次性固定下来的，而是在 decode 过程中持续流动。</p>
<p>这也是为什么连续批处理本质上更像<span style="background-color: #c0c0c0;">token 级流水调度</span>，而不是传统意义上的“把若干完整请求捆成一批”。高吞吐来自这种持续补位机制：只要有位置空出来，调度器就尝试让新的工作立刻进来，从而保持 GPU 上活跃序列数尽量稳定。</p>
<div class="blog_h4"><span class="graybg">Prefill 与 Decode 如何混排</span></div>
<p>连续批处理的工程难点在于，新进来的请求并不处在和老请求相同的计算阶段。老请求通常已经进入逐 token 解码（Decode），而新请求刚到达时还需要先完成整段提示词的预填充（Prefill）。这两个阶段的计算特征并不相同：Prefill 更接近长序列的大矩阵计算，往往更偏计算密集；Decode 则更依赖 KV Cache 读取与单步增量计算，往往更偏带宽与调度密集。</p>
<p>高吞吐推理引擎的关键能力之一，就是能把这两类工作纳入同一套调度体系，而不是强制等所有 Prefill 做完才统一进入 Decode。于是，新请求在进入系统后会先经历一次 Prefill，把提示词对应的 KV 写入缓存；随后它就能在下一个调度周期加入解码队列，和其他正在生成的请求一起按 token 粒度推进。工程实现上是否在同一次前向里混合执行 Prefill 与 Decode，取决于具体 runtime 的算子与调度设计；但从系统抽象看，它们必须被统一编排，否则连续批处理就无法真正发挥价值。</p>
<div class="blog_h4"><span class="graybg">为什么必须配合分页式 KV 管理</span></div>
<p>连续批处理之所以直到近年的高吞吐推理引擎才真正成熟，一个核心原因就在 KV Cache 管理。若每条请求都必须占据一整段预留好的连续显存，那么请求不断进出时，显存会迅速碎片化：短请求结束后留下很多小空洞，长请求却可能因为拿不到足够长的一段连续内存而无法被调度进去。此时即使调度逻辑想连续补位，底层显存布局也会把它卡死。</p>
<p>这正是 Paged Attention / Paged KV 发挥作用的地方。通过把 KV Cache 切成固定大小的块，再用块表去描述逻辑上的连续序列，系统就不再要求“每个请求必须拥有一整段连续显存”。于是，块可以被细粒度分配、回收、复用和共享，连续批处理才真正有了工程基础。也正因为如此，在现代 LLM 服务栈里，Paged KV 与 Continuous Batching 几乎总是成对出现：前者解决“缓存如何活着”，后者解决“调度如何持续流动”。</p>
<p>它的实现难点也最高。连续批处理要和 KV Cache 管理、Paged Attention、块分配/回收、抢占策略和流式输出协同设计，还必须在吞吐与 P99 延迟之间做持续权衡。也正因为如此，它通常不是单独存在的一项小优化，而是高吞吐推理引擎的核心调度机制。</p>
<div class="blog_h2"><span class="graybg">推理框架</span></div>
<p>推理框架（Inference Serving Stack）把“模型权重 + 推理图 + 调度策略”封装成可部署的服务。选型时优先看三件事：是否支持你的模型与精度（Compatibility）、是否能把 KV Cache 与批处理调度做对（Scheduler + KV Management）、以及在你的硬件上能否稳定达到目标吞吐/延迟（Performance Envelope）。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">框架</td>
<td style="text-align: center;">定位</td>
<td style="text-align: center;">强项</td>
<td style="text-align: center;">代价/约束</td>
<td style="text-align: center;">适用场景</td>
</tr>
</thead>
<tbody>
<tr>
<td>vLLM</td>
<td>生产级高吞吐推理</td>
<td>Paged KV + 连续批处理；并发与吞吐强</td>
<td>部署与调优门槛更高；更依赖 CUDA 与服务化环境</td>
<td>多租户在线服务；RAG/Agent 高并发；企业 API</td>
</tr>
<tr>
<td>TensorRT-LLM</td>
<td>NVIDIA 推理加速栈</td>
<td>内核与图级优化；低延迟上限高</td>
<td>构建/调参成本高；硬件绑定强</td>
<td>对延迟敏感的核心服务；固定模型部署</td>
</tr>
<tr>
<td>TGI（Text Generation Inference）</td>
<td>通用推理服务</td>
<td>生态成熟；易集成；支持常见部署形态</td>
<td>极致吞吐/显存利用率取决于具体模型与配置</td>
<td>快速上线；标准化 HuggingFace 模型服务</td>
</tr>
<tr>
<td>SGLang</td>
<td>面向 LLM 应用的推理与编排</td>
<td>更贴近应用侧的执行模型；对复杂推理/工具调用友好</td>
<td>需要接受其编排抽象；生态仍在快速演进</td>
<td>复杂 Agent/RAG pipeline；结构化推理任务</td>
</tr>
<tr>
<td>llama.cpp（GGUF）</td>
<td>本地/边缘推理</td>
<td>CPU/小 GPU 友好；量化生态完善；分发简单</td>
<td>吞吐与模型规模受硬件限制；在线并发能力有限</td>
<td>个人/离线实验；边缘设备；小规模服务</td>
</tr>
<tr>
<td>Ollama</td>
<td>本地运行与分发工具</td>
<td>安装简单；模型拉取、管理与切换顺手</td>
<td>更偏单机体验而非高并发服务性能</td>
<td>个人原型验证；本地开发；小规模离线使用</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Ollama 与 vLLM</span></div>
<p>如果只看最常见的部署分叉，Ollama 与 vLLM 实际上分别代表了两种完全不同的目标函数。Ollama 解决的是“如何让个人开发者或小团队在本地几乎零门槛地把模型跑起来”；vLLM 解决的则是“如何让同一份模型在服务端硬件上稳定承载更多请求，并把 KV Cache、批处理和显存调度做到足够高效”。前者更像本地模型工厂，后者更像生产环境推理引擎。</p>
<p>部署体验上的差异最直接。Ollama 把模型下载、量化版本管理、本地 API 与交互入口统一封装起来，目标是尽量减少环境配置摩擦；vLLM 则仍然更像一个服务端运行时，需要围绕 Python / CUDA / PyTorch 版本、模型路径、并发参数、显存预算和服务启动方式做明确配置。二者服务的优化目标不同：Ollama 优先优化可用性，vLLM 优先优化服务效率。</p>
<p>性能侧的分界点通常出现在并发与显存管理。低并发、单人本地交互时，Ollama 往往已经足够顺畅，而且对量化模型和消费级显卡更友好；当请求数上升、上下文变长、需要更稳定地榨出 GPU 吞吐时，vLLM 的 PagedAttention、连续批处理（Continuous Batching）和更系统化的 KV 管理优势就会明显体现出来。前一节所说那种按 token 步骤持续补位的调度机制，正是 vLLM 一类高吞吐引擎的核心能力之一。换句话说，Ollama 更偏“把一次请求跑得足够方便”，vLLM 更偏“把很多请求一起跑得足够经济”。</p>
<p>因此，可以把这条选型规则压缩成一句话：<span style="background-color: #c0c0c0;">本地开发、个人使用、边缘设备与隐私优先场景更偏向 Ollama；企业服务、高并发 API、多租户平台和多卡扩展场景更偏向 vLLM</span>。若一个项目未来很可能从“个人原型”走向“在线服务”，最常见的工程路径也往往是先用 Ollama 快速验证模型行为，再迁移到 vLLM 一类更强的服务端推理栈。</p>
<div class="blog_h3"><span class="graybg">vLLM</span></div>
<p>vLLM 是面向大模型推理的高吞吐推理引擎（Inference Engine），核心目标是在显存受限的情况下提升并发与吞吐。它的代表性设计是 PagedAttention：把 KV Cache 按固定大小切成块（Blocks），用块表（Block Table）把“逻辑连续的序列”映射到“物理上不必连续的显存块”。</p>
<p>这样做的收益是：按需分配 KV、降低内存碎片（Fragmentation），并支持前缀共享（Prefix Sharing）：当多个请求共享同一段前缀（例如相同系统提示词/相同文档前缀）时，可以复用同一份缓存块，从而节省显存并降低 Prefill 成本。</p>
<p>vLLM 的真正价值并不只在某个单独算子，而在于它把<span style="background-color: #c0c0c0;">KV Cache 管理、连续批处理、调度和服务接口</span>统一成了面向高并发场景的整体系统。具体到调度层，它做的正是前文展开的那种 token 级连续批处理：每一轮 decode 之后重审运行队列，把已经结束的序列移出，再让等待中的新请求完成 Prefill 并尽快补入后续 decode 周期。对企业级 API、RAG 服务、多轮聊天机器人或多租户平台而言，请求通常不会整齐地同时开始和结束；连续批处理能让系统在 decode 过程中持续吸纳新请求，而不是死板地按静态 batch 切分，这正是它在生产环境里更占优势的原因。</p>
<p>它的边界也很清楚。vLLM 本身不是给普通用户做交互体验优化的工具，而是给工程团队做吞吐、延迟、监控和资源利用率优化的 runtime。于是它更适合有明确服务化目标的团队：需要 API 暴露、监控指标、批处理调优、多 GPU 并行和较强可观测性时，vLLM 往往比本地导向工具更自然；但在单机小显存、低并发、快速原型阶段，它未必是最省力的第一选择。</p>
<div class="blog_h3"><span class="graybg">TensorRT-LLM</span></div>
<p>TensorRT-LLM 是面向 NVIDIA GPU 的大模型推理加速栈，重点在于高效算子（Kernel）与图级优化（Graph-level Optimization）：通过更强的算子融合、更贴近硬件的精度与布局选择（例如 FP16/BF16/FP8、weight-only quantization），把通用 Transformer 计算编译成更高吞吐、更低延迟的推理执行计划。</p>
<p>它更偏“部署与性能工程”路径：需要围绕目标 GPU、精度与 batch/seq 长度做构建与调优；换来的是更稳定的延迟与更高的吞吐上限。</p>
<div class="blog_h3"><span class="graybg">Ollama</span></div>
<p>Ollama 是面向本地开发的模型运行与分发工具：提供统一的模型拉取、量化版本管理与本地推理接口，降低“把模型跑起来”的工程摩擦。它更适合个人/小团队在本机进行原型验证与离线实验；若追求高并发在线服务，通常会使用更专业的推理引擎与服务编排。</p>
<p>它的核心价值并不在于把吞吐推到极限，而在于把本地使用路径做得足够短：模型名称、下载、版本切换、交互入口和本地 API 都被统一包装起来。对个人学习、隐私敏感的小规模应用、本地插件开发或“先验证模型行为再说”的探索阶段，这种低门槛通常比极致性能更重要。</p>
<p>也正因为如此，Ollama 对消费级机器往往更友好。单卡、小显存、量化模型、本地命令行交互，这些都是它的舒适区；但一旦目标转向多租户、高并发、细粒度监控和多卡线性扩展，它就不再是最自然的中心组件。更准确地说，Ollama 优化的是<span style="background-color: #c0c0c0;">单机可用性与本地开发体验</span>，而不是生产环境下的系统吞吐极限。</p>
<div class="blog_h3"><span class="graybg">TGI（Text Generation Inference）</span></div>
<p>TGI（Text Generation Inference）是面向 HuggingFace 模型生态的通用推理服务：提供标准化的 HTTP/gRPC 接口与常见的批处理/流式输出能力，适合把“能跑的模型”快速变成“可用的服务”。它的价值在于工程整合与稳定性，而不是把每个场景都推到极致性能。</p>
<div class="blog_h3"><span class="graybg">SGLang</span></div>
<p>SGLang 强调“把推理当作可编排的程序执行”：当应用需要多步生成、结构化输出、工具调用或复杂控制流时，推理框架不仅要快，还要能把控制逻辑表达清楚并与 KV Cache 调度协同。它更像是服务端的“推理 DSL + runtime”。</p>
<div class="blog_h3"><span class="graybg">llama.cpp（GGUF）</span></div>
<p>llama.cpp 是一套本地/边缘推理 runtime：围绕 GGUF/量化权重与 CPU/GPU 混合执行做了大量工程优化。它在“把模型放到普通机器上跑起来”这一目标上极具性价比，但并不追求云端多租户场景下的极限吞吐。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-4">人工智能理论知识 - Transformers和大模型</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/ai-knowledge-quick-ref-4/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>人工智能理论知识 - 主要应用领域</title>
		<link>https://blog.gmem.cc/ai-knowledge-quick-ref-3</link>
		<comments>https://blog.gmem.cc/ai-knowledge-quick-ref-3#comments</comments>
		<pubDate>Wed, 15 Apr 2026 14:55:43 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[AI]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=42147</guid>
		<description><![CDATA[<p>这一篇从任务视角进入现代 AI 的几个核心应用方向，重点讨论自然语言处理、计算机视觉、语音和音频处理、搜索/推荐/广告预估，以及时序建模和时间序列。前一篇已经建立了机器学习、神经网络与深度学习的基本方法，这一篇继续回答这些方法究竟被拿来解决哪些真实任务、每类任务的输入输出结构是什么、经典路线如何演进到今天的主流方案；再往后才会进入 Transformer、大模型与系统层落地。 自然语言处理 自然语言处理（Natural Language Processing, NLP）研究的是：如何让机器表示、理解、检索、生成和操作人类语言。它处理的对象既可以是词、短语、句子、段落，也可以是整篇文档、对话、多轮交互与语音转写后的文本。这个方向覆盖的任务非常广，从最基础的分词、句法分析和语言建模，到信息抽取、问答、翻译、摘要、对话和语义推理，都属于 NLP 的核心版图。 下面会按照不同任务逐一展开，说明每类任务到底在解决什么问题、输入输出通常长什么样、为什么它在实际系统中重要，以及它与其他 NLP 任务之间如何衔接。 任务范式总览 任务范式 代表任务 核心输入与输出 在系统中的典型作用 表示与匹配 词嵌入、文本相似度、文本聚类、文本分类 输入文本，输出向量、相似分数或类别标签 <a class="read-more" href="https://blog.gmem.cc/ai-knowledge-quick-ref-3">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-3">人工智能理论知识 - 主要应用领域</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>这一篇从任务视角进入现代 AI 的几个核心应用方向，重点讨论自然语言处理、计算机视觉、语音和音频处理、搜索/推荐/广告预估，以及时序建模和时间序列。前一篇已经建立了机器学习、神经网络与深度学习的基本方法，这一篇继续回答这些方法究竟被拿来解决哪些真实任务、每类任务的输入输出结构是什么、经典路线如何演进到今天的主流方案；再往后才会进入 Transformer、大模型与系统层落地。</p>
<div class="blog_h1"><span class="graybg">自然语言处理</span></div>
<p>自然语言处理（Natural Language Processing, NLP）研究的是：如何让机器表示、理解、检索、生成和操作人类语言。它处理的对象既可以是词、短语、句子、段落，也可以是整篇文档、对话、多轮交互与语音转写后的文本。这个方向覆盖的任务非常广，从最基础的分词、句法分析和语言建模，到信息抽取、问答、翻译、摘要、对话和语义推理，都属于 NLP 的核心版图。</p>
<p>下面会按照不同任务逐一展开，说明每类任务到底在解决什么问题、输入输出通常长什么样、为什么它在实际系统中重要，以及它与其他 NLP 任务之间如何衔接。</p>
<div class="blog_h3"><span class="graybg">任务范式总览</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">任务范式</td>
<td style="text-align: center;">代表任务</td>
<td style="text-align: center;">核心输入与输出</td>
<td style="text-align: center;">在系统中的典型作用</td>
</tr>
</thead>
<tbody>
<tr>
<td>表示与匹配</td>
<td>词嵌入、文本相似度、文本聚类、文本分类</td>
<td>输入文本，输出向量、相似分数或类别标签</td>
<td>承担召回、去重、聚类、路由与基础语义匹配</td>
</tr>
<tr>
<td>结构化分析</td>
<td>词法分析、句法分析、语义分析、信息抽取、实体链接、关系预测</td>
<td>输入文本，输出边界、树、图、实体、关系或标准化知识单元</td>
<td>把自然语言转成可检索、可落库、可推理的结构</td>
</tr>
<tr>
<td>生成与编辑</td>
<td>机器翻译、语法纠错、文本摘要、语言模型</td>
<td>输入源文本或上下文，输出新文本或修订文本</td>
<td>承担生成、改写、压缩、续写和条件编辑</td>
</tr>
<tr>
<td>理解与验证</td>
<td>自然语言推理、问答、指代消解、时间处理</td>
<td>输入问题、证据或篇章上下文，输出判断、答案或篇章级解释</td>
<td>承担证据验证、答案生成、篇章一致性和时间线组织</td>
</tr>
<tr>
<td>交互与多模态入口</td>
<td>对话、语音识别、词汇正规化</td>
<td>输入口语、噪声文本或多轮上下文，输出标准文本、状态或回复</td>
<td>承担用户入口清洗、会话控制和语音到语言的桥接</td>
</tr>
</tbody>
</table>
<p>从方法论上看，整章内容大致沿着“表示与匹配 - 结构化分析 - 生成与编辑 - 理解与验证 - 交互入口”这几条主线展开。不同任务的表面形式差异很大，但它们在实际系统里往往会被串成同一条流水线：先表示和召回，再抽取和验证，最后生成、编辑或交互输出。</p>
<div class="blog_h2"><span class="graybg">词嵌入</span></div>
<p>词嵌入（Word Embedding）处理的核心问题是：怎样把离散 token 映射成连续向量，使语义与语法关系在向量空间中变得可计算。若把词表记为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{V}\)</span>，词嵌入本质上是在学习一个映射 <span displaypfx="inline-" class="mathjax-container">\(e:\mathcal{V}\rightarrow\mathbb{R}^d\)</span>。这个映射一旦建立，模型就不再把单词当成纯符号，而是能在向量空间里比较相似性、组合上下文并进行梯度优化。</p>
<p>早期主线是<span style="background-color: #c0c0c0;">语境无关（Context-free）</span>词向量。Word2Vec 用 CBOW / Skip-gram 从局部上下文预测词，GloVe 直接分解全局共现统计，fastText 则进一步把子词（Subword）信息纳入表示，因此对未登录词和形态丰富语言更稳。这一路线在 2013 到 2018 年左右几乎是 NLP 标配，但它的根本局限也很明显：同一个词在不同句子里的词义会被压成同一个静态向量。</p>
<p>随后进入<span style="background-color: #c0c0c0;">语境相关（Contextual）</span>阶段。ELMo 通过双向语言模型让词表示依赖上下文；Flair 使用字符级语言模型表示 token；BERT、XLNet 进一步把上下文表示能力提升到 Transformer 时代。到了这一步，“词嵌入”不再是单独训练一张静态 embedding 表，而是更接近“由编码器按当前上下文即时生成表示”。</p>
<p>到 2026 年，真正主流的并不是单独讨论“词向量”，而是讨论<span style="background-color: #c0c0c0;">专用嵌入模型（Embedding Model）</span>。语义检索和相似度系统常直接使用 E5、BGE、BGE-M3、jina-embeddings、Qwen3-Embedding 这类专门为句子、段落和文档向量优化的模型，而不是把 Word2Vec 或 GloVe 直接塞进系统中。静态词向量仍有价值，但更多出现在教学、轻量模型、资源受限场景，或作为某些传统系统的初始化手段。</p>
<div class="blog_h3"><span class="graybg">语境无关词嵌入如何被使用</span></div>
<p>Word2Vec、GloVe、fastText 这条线之所以重要，不只是因为它们“比 one-hot 更好”，而是因为它们第一次让大量 NLP 任务可以在<span style="background-color: #c0c0c0;">稠密连续向量空间</span>上工作。文本分类会把词向量做平均、卷积或循环编码；序列标注会把词向量喂给 BiLSTM-CRF；信息检索和相似度任务会把句子表示建立在词向量聚合之上。fastText 进一步把子词纳入建模，因此在拼写变体多、形态变化强的语言里格外有用。</p>
<p>这一路线今天并未完全消失，但已经从“系统主干”退到“轻量基线和历史里程碑”。原因很简单：它只能给同一个词一个固定向量，无法区分多义词在不同上下文里的语义角色。对现代问答、信息抽取、检索和对话系统而言，这个缺陷通常过大。</p>
<div class="blog_h3"><span class="graybg">语境相关表示如何改变下游任务</span></div>
<p>ELMo、Flair、BERT、XLNet 这一代方法真正改变的是：表示不再先验固定，而是由整句上下文共同决定。命名实体识别、词性标注、自然语言推理、阅读理解和文本分类，都因此从“人工特征 + 浅层模型”快速转向“预训练编码器 + 任务头微调”。导图里把 ELMo、Flair、BERT、XLNet 放在词嵌入一侧是成立的，因为在很多下游系统里，它们首先承担的正是<span style="background-color: #c0c0c0;">表示生成器</span>的角色。</p>
<p>不过，到了这一阶段，“词嵌入”这个词本身已经有些偏窄。更准确的说法通常是 token 表示、上下文化表示或编码器隐藏状态。也正因为如此，后续很多任务文献虽然不再反复强调 embedding，却仍然本质上建立在更强的表示学习之上。</p>
<div class="blog_h3"><span class="graybg">当代嵌入模型在系统里怎么落地</span></div>
<p>现代嵌入模型通常直接服务于检索、聚类、重排前召回、RAG 和文本去重。最常见的用法是：先用 bi-encoder 把查询和文档独立编码为向量，再做余弦相似度或向量索引召回；若系统还要求更高精度，则在召回后再叠加 cross-encoder 重排。BGE-M3 这类模型甚至把密集检索、稀疏检索和多向量检索统一进同一模型接口；Qwen3-Embedding 一类新模型则更强调多语言与通用语义检索能力。</p>
<p>因此，今天的嵌入模型已经不只是“词表示技术”，而是很多 NLP 系统的第一层基础设施。它们决定了召回质量、聚类几何结构、RAG 证据覆盖率，以及后续大模型究竟能看到哪些上下文。</p>
<div class="blog_h2"><span class="graybg">文本语义相似度</span></div>
<p>文本语义相似度（Text Semantic Similarity）研究的是：给定两段文本 <span displaypfx="inline-" class="mathjax-container">\(x_1,x_2\)</span>，怎样判断它们在语义上有多接近。它既可以是回归问题，例如输出 <span displaypfx="inline-" class="mathjax-container">\(s\in[0,1]\)</span> 的相似分数；也可以是分类问题，例如判断是否复述（Paraphrase）、是否语义重复、是否属于同一意图。</p>
<p>这一任务在搜索、推荐、问答召回、重复问题合并、知识库去重和检索增强生成（RAG）中极其关键。真正落地时，很多系统并不直接做复杂推理，而是先问一句：<span style="background-color: #c0c0c0;">库里哪段文本和当前查询最像</span>。这个“像不像”的基础设施，就是文本相似度模型。</p>
<p>思维导图里提到的 MwAN、DIIN 更偏向句对建模和复述判别时代的代表，它们强调多路注意力、交互式特征融合；GenSen、XLNet 则代表“用更强预训练表示做句对判断”的阶段。这些模型在各自时代都很重要，但今天的主流已经明显转向两条路线：第一条是<span style="background-color: #c0c0c0;">双编码器（Bi-Encoder）</span>嵌入模型，先把文本各自编码成向量，再做余弦相似度或向量检索；第二条是<span style="background-color: #c0c0c0;">交叉编码器（Cross-Encoder）</span>重排模型，把两段文本拼接后联合打分。前者适合大规模召回，后者适合小候选集精排。</p>
<p>因此，现代系统往往把“文本相似度”做成两阶段：先用嵌入模型快速召回，再用更重的交叉编码器或 LLM 重新判断。单纯依赖早期句对网络，今天已经不是主流最优解。</p>
<div class="blog_h3"><span class="graybg">复述判别与句对匹配</span></div>
<p>复述判别（Paraphrase Identification）是文本相似度里最经典的一支。它要求模型回答的不是“像不像一个主题”，而是“这两句话是否表达了同一个命题”。MwAN、DIIN 这类模型在这一阶段很有代表性，因为它们会显式建模词对词、短语对短语的交互，再把这些交互特征汇总成最终判断。它们很适合中小规模句对任务，但在超大语料召回里代价太高。</p>
<div class="blog_h3"><span class="graybg">大规模检索为什么偏爱双编码器</span></div>
<p>若候选库有百万到十亿级文档，系统不可能把查询与每篇文档都拼接后送进重模型。因此，工程上几乎都会先用双编码器把文档离线编码入库，再在向量索引里做近似最近邻搜索。这个阶段的重点是<span style="background-color: #c0c0c0;">召回率、吞吐、延迟和可扩展性</span>，而不是单对文本判得多细。</p>
<p>这也是为什么文本相似度在现代 NLP 中经常和嵌入模型、向量数据库、RAG 绑在一起讨论。它早已不是孤立 benchmark，而是搜索与知识系统的底层检索能力。</p>
<div class="blog_h3"><span class="graybg">精排与最终判定</span></div>
<p>召回之后，系统通常会把前几十到前几百个候选交给交叉编码器、NLI 模型或 LLM 精排。这里的目标已经从“高覆盖找候选”切换成“在小候选集内做高精度判断”。因此，一个成熟的相似度系统常常同时包含两种模型：前端轻、后端重；前端负责广撒网，后端负责最后一锤定音。</p>
<div class="blog_h3"><span class="graybg">Sentence-BERT、SimCSE 与多向量检索</span></div>
<p>如果把现代文本相似度的演进再往前推一步，Sentence-BERT（SBERT）几乎是必须点名的关键节点。它把 BERT 从“适合句对联合编码的模型”改造成“适合独立产出句向量的模型”，直接推动了句向量检索的大规模实用化。随后，SimCSE 进一步说明：仅靠对比学习和 dropout 增广，也能把句向量空间训得非常有判别力。</p>
<p>再往后的检索系统则开始出现多向量路线，例如 ColBERT 这类 late interaction 方法。它不再把整段文本压成单一向量，而是保留更细粒度的 token 级匹配信号，在召回和精度之间取得另一种折中。这说明文本相似度并没有收敛到单一范式，而是在“单向量快召回”和“多向量细匹配”之间持续演化。</p>
<div class="blog_h2"><span class="graybg">自然语言推理</span></div>
<p>自然语言推理（Natural Language Inference, NLI）要求模型判断一对文本之间的逻辑关系。给定前提句 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 和假设句 <span displaypfx="inline-" class="mathjax-container">\(h\)</span>，模型通常输出三类标签之一：蕴含（Entailment）、矛盾（Contradiction）或中立（Neutral）。这比普通文本分类更接近“句间逻辑判断”，因此长期被视为语言理解能力的重要基准。</p>
<p>NLI 在事实核验、法律文本比对、问答验证、检索精排、对话一致性检查等场景里都有直接用途。很多系统表面上做的是“相似度”或“问答”，底层真正需要的却是更强的蕴含判断。例如“文章是否支持这个结论”“这段证据是否足以回答用户问题”，本质上都更接近 NLI。</p>
<p>思维导图里把 XLNet 和 Transformer LM 放在这一节，是预训练 Transformer 主导 NLI benchmark 的典型阶段。今天的主流仍然是强预训练编码器或指令微调模型，但模型代际已经更新到 RoBERTa、DeBERTa、ModernBERT、T5、以及大量 instruction-tuned LLM。旧的 NLI 竞赛模型并没有失效，但如果目标是工程实用，通常更倾向于：<span style="background-color: #c0c0c0;">编码器微调做高吞吐分类，LLM 做复杂解释型推理或低资源迁移</span>。</p>
<div class="blog_h3"><span class="graybg">NLI 在系统里的典型角色</span></div>
<p>NLI 在工程上很少以“单独上线一个 NLI 产品”的形式存在，更常见的是作为校验模块。事实核验会用它判断证据是否支持结论；检索问答会用它判断候选段落是否真正回答了问题；多轮对话会用它检查当前回复是否与历史事实冲突。也就是说，NLI 经常承担的是<span style="background-color: #c0c0c0;">语义一致性过滤器</span>的角色。</p>
<div class="blog_h2"><span class="graybg">机器翻译</span></div>
<p>机器翻译（Machine Translation, MT）研究的是：给定源语言句子 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，生成目标语言句子 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>，同时尽量保持语义、语气、术语和语法结构的一致性。它是 NLP 中最成熟、最系统化的任务之一，因此也最能看清整个领域从“规则和统计”走向“深度学习与基础模型”的完整演进。</p>
<div class="blog_h3"><span class="graybg">传统统计机器翻译</span></div>
<p>传统主线是统计机器翻译（Statistical MT, SMT），尤其是短语级机器翻译（Phrase-based SMT）。思维导图中提到的 IBM1 到 IBM4、双语对齐、调序模型、短语抽取、语言模型、柱搜索（Beam Search），正是这条路线的核心流水线。IBM1 到 IBM4 负责从双语平行语料中学习词对齐；短语抽取把对齐好的词组织成更长的短语映射；调序模型解决语序差异；语言模型保证目标句流畅；柱搜索则在巨大候选空间里找近似最优译文。</p>
<p>这条路线今天已经不再是工业主流，但并没有“毫无价值”。它仍然是理解对齐、解码、译文流畅性与可解释错误分析的最好教材。在低资源、强术语控制、可解释对齐分析等特定场景中，SMT 思想依然有借鉴意义。</p>
<div class="blog_h3"><span class="graybg">神经机器翻译</span></div>
<p>深度学习阶段先由 Seq2Seq 带动。最早的 Seq2Seq 往往由 RNN / LSTM 编码器-解码器构成，随后加入注意力机制；ConvS2S 则尝试用卷积并行建模序列；真正改写主线的是 Transformer。到今天，RNN-Seq2Seq 和 ConvS2S 更像历史里程碑，而不是大规模翻译系统的默认选择。</p>
<p>现代翻译系统的主流已经是 Transformer、mT5 / ByT5 这类文本到文本模型，以及大规模多语言翻译基础模型。例如 NLLB-200 把高质量多语翻译推进到 200 语言量级，MADLAD-400 则代表了更大语种覆盖和大规模多语言翻译训练的方向。Beam Search 仍然没有过时，它在神经机器翻译里依然是常见解码器，只是位置从“统计解码核心”变成了“神经生成阶段的搜索策略”。</p>
<p>因此，今天谈机器翻译时，IBM 对齐和短语 SMT 应归入<span style="background-color: #c0c0c0;">历史主线与基础知识</span>；真正要落地时，应优先理解 Transformer、多语言预训练、术语约束解码、领域自适应和评测体系。</p>
<div class="blog_h3"><span class="graybg">统计翻译流水线到底在做什么</span></div>
<p>IBM1 到 IBM4 的价值在于把翻译拆成了一条清晰流水线：先做词对齐，再做短语抽取，再做调序，再用语言模型保证目标句自然，最后靠 Beam Search 在候选空间中搜索高分译文。它之所以曾长期主导工业界，是因为每个环节都能单独分析、替换和调优。缺点同样明显：误差会在流水线中逐级累积，而且很多语言现象需要跨环节协同才能解释。</p>
<div class="blog_h3"><span class="graybg">神经机器翻译如何实际使用</span></div>
<p>Seq2Seq、ConvS2S、Transformer 的核心变化，是把原来分散在多个模块中的对齐、重排序和流畅性建模，尽可能收拢进一个端到端神经模型中。RNN-Seq2Seq 解决了“从输入序列生成输出序列”的基本范式，注意力解决了固定长度表示瓶颈，ConvS2S 用卷积换取更强并行性，而 Transformer 最终在长程依赖与训练效率之间取得了最好平衡，因此成为主流。</p>
<div class="blog_h3"><span class="graybg">今天翻译系统真正关注什么</span></div>
<p>2026 年的机器翻译系统，重点通常已经不是“能不能翻”，而是<span style="background-color: #c0c0c0;">低资源语言覆盖、术语一致性、长文档上下文、领域迁移和评测对齐</span>。这也是 NLLB-200、MADLAD-400 一类多语言模型重要的原因。与此同时，传统 Quality Estimation、术语词典约束、人工后编辑和文档级一致性控制并没有消失，反而经常作为神经翻译系统的外围组件继续存在。</p>
<div class="blog_h2"><span class="graybg">词汇正规化</span></div>
<p>词汇正规化（Lexical Normalization）处理的是“非标准写法如何映射回标准写法”的问题。它常见于社交媒体文本、OCR 错误、ASR 转写、用户口语化输入、拼写变体和历史文本处理。典型输入不是一句已经很干净的标准语言，而是像缩写、错别字、方言拼写、口语连写、拼音混杂或英文网络俚语这样的噪声文本。</p>
<p>导图里的 MoNoise、联合词性标注与正规化、基于音节的方法，代表的是社交媒体和噪声文本时代的经典路线。它们强调候选生成、上下文判别、词性线索和发音相似性，尤其适合拼写波动很大的文本。到今天，这类方法仍有现实价值，但多数已不再是最强主线。</p>
<p>现代正规化越来越多地使用字符级、字节级或文本到文本模型，例如 ByT5、mT5、T5 系列编辑模型，以及指令微调 LLM。原因很直接：正规化本质上就是一个局部条件生成或编辑问题，而字节级 / 字符级模型对噪声拼写、形态变化和未登录词更稳。导图里的“基于音节的方法”在特定语言和口语输入里仍有帮助，但已经不是通用主干。</p>
<div class="blog_h3"><span class="graybg">候选生成与判别式正规化</span></div>
<p>传统词汇正规化一般分成两步：先生成若干可能的标准写法候选，再结合上下文做判别。MoNoise、联合词性标注与正规化、发音相似或音节相似方法都属于这一范式。它们的优势是每一步都较可控，容易接入词典、发音规则和领域知识；缺点则是候选覆盖与上下文建模容易彼此割裂。</p>
<div class="blog_h3"><span class="graybg">生成式正规化</span></div>
<p>文本到文本模型把正规化直接视为序列编辑任务：输入噪声文本，输出标准化文本。这样做的收益是模型可以在一个统一框架里同时处理缩写展开、错拼纠正、形态恢复和局部重写。代价则是输出稳定性更依赖训练数据与解码约束，因此很多生产系统仍会在生成模型外面包一层词典校验或格式规则。</p>
<div class="blog_h4"><span class="graybg">正规化与拼写纠错、GEC 的边界</span></div>
<p>词汇正规化和拼写纠错、语法纠错相邻，但边界并不相同。拼写纠错通常更关心“这个词是不是打错了”；GEC 更关心句法、搭配、时态等整句层面的错误；词汇正规化则更强调把<span style="background-color: #c0c0c0;">非标准表达映射成标准表达</span>，哪怕原写法对人类完全可理解。例如社交媒体缩写、方言拼写、网络俚语、口语化省略和代码混写，都可能不是“拼错”，却仍然需要正规化。</p>
<p>也正因为如此，正规化常被放在更前的文本清洗层。它的目标不是提升文法优美度，而是把后续分类、解析、抽取和检索所依赖的输入分布拉回训练数据熟悉的范围。</p>
<div class="blog_h4"><span class="graybg">字节级模型为什么特别适合正规化</span></div>
<p>ByT5 一类字节级模型在这个任务上很自然，因为正规化问题经常发生在字符粒度甚至字节粒度。用户可能少打一两个字母、把多个词连写、夹杂表情或外文字符，也可能用语音近似拼写一个词。若模型从词级开始建模，很多噪声在入口就已经被打碎；而字节级或字符级模型可以直接在更细粒度上学习“从噪声表面形态到标准形式”的映射。</p>
<p>这也是为什么词汇正规化在近年经常与多语言字节级模型绑定在一起讨论。它们不仅对未登录词更稳，也更适合社交媒体、OCR 和 ASR 这类高噪声输入。</p>
<div class="blog_h4"><span class="graybg">正规化在系统中的收益与风险</span></div>
<p>正规化最直接的收益，是改善下游系统对噪声文本的稳健性。社交媒体情感分析、客服意图识别、OCR 后信息抽取和口语转写后的结构化解析，往往都会因为输入更标准而显著受益。但这个步骤也有风险：如果系统把本来有语用价值的变体都强行“洗平”，就可能丢掉风格、方言、情绪强度甚至身份信号。</p>
<p>因此，现代系统越来越强调<span style="background-color: #c0c0c0;">任务依赖的选择性正规化</span>。面向检索、分类和解析时，正规化通常应更积极；面向风格分析、作者识别、对话个性保留时，则未必应该把所有非标准写法都抹平。是否正规化、正规化到哪一步，本身就是上游任务设计的一部分。</p>
<div class="blog_h4"><span class="graybg">一个典型流水线例子</span></div>
<p>以“ASR 转写文本做客服工单分类”为例，原始输入里常会出现同音字、口语省略、数字口语化和夹杂英文品牌名。若系统直接把这类文本送进分类器，模型往往会把噪声当成类别特征，导致跨渠道泛化能力很差。更稳的做法通常是：先做轻量正规化，把明显的 ASR 噪声、缩写和常见别名拉回标准表达，再进入分类、检索或抽取模块。</p>
<p>但这一步也不能无限增强。例如用户情绪、方言身份和话语风格本身可能就是业务信号。若把“气死我了真的服了”一律改写成过度标准化文本，情绪强度和用户画像信息就会被抹掉。因此，正规化最好是<span style="background-color: #c0c0c0;">按任务定制的局部编辑</span>，而不是无条件全量改写。</p>
<div class="blog_h2"><span class="graybg">语言模型</span></div>
<p>语言模型（Language Model, LM）回答的是一个看似简单、实际上极其基础的问题：给定前文，下一段语言出现的概率分布是什么。若用序列 <span displaypfx="inline-" class="mathjax-container">\(x_1,\dots,x_T\)</span> 表示一句话，自回归语言模型通常写成</p>
<span displaypfx="" class="mathjax-container">\[p(x_1,\dots,x_T)=\prod_{t=1}^{T}p(x_t\mid x_{&lt;t})\]</span>
<p>这个定义之所以关键，是因为它把“理解语言”和“预测语言”统一到了一个概率框架里。模型若想把下一个 token 预测准，就被迫学习词法、句法、搭配、常识和某些推理结构。</p>
<p>传统语言模型以 n 元语法（n-gram）和 Pitman-Yor 一类平滑概率模型为主，强调局部上下文频率统计。它们今天在主流大模型系统里已经不是核心，但仍然是理解平滑、稀疏性、解码和语言先验的基础。深度学习阶段先后出现 AWD-LSTM、Gated CNN、Transformer-XL 等路线，其中 AWD-LSTM 曾是强语言建模基线，Transformer-XL 用分段记忆缓解上下文长度限制。</p>
<p>到 2026 年，主流显然已经是 Decoder-only Transformer 大语言模型。AWD-LSTM、Gated CNN、Transformer-XL 不应被当成“当前最先进路线”，但它们分别代表了循环网络时代、卷积语言模型时代和长上下文过渡阶段。理解这些模型的意义，在于看清当代 LLM 为什么几乎统一收敛到“大规模预训练 Transformer + 指令对齐 + 外部工具”这一组合。</p>
<div class="blog_h3"><span class="graybg">统计语言模型怎么被使用</span></div>
<p>n-gram 语言模型在历史上是很多系统的“流畅性后验”。语音识别会用它约束转写结果，统计机器翻译会用它筛掉不自然译文，拼写纠错会用它判断哪种候选更像真实语言。Pitman-Yor 这一类方法的重要性则在于它把长尾词汇与平滑问题处理得更系统，因此在语言建模理论史上占据一席之地。</p>
<div class="blog_h3"><span class="graybg">神经语言模型过渡阶段留下了什么</span></div>
<p>AWD-LSTM、Gated CNN、Transformer-XL 虽然已不是主流终局，但它们分别回答了三个关键问题：循环网络如何把语言模型训稳，卷积是否能替代循环建模局部上下文，以及长上下文能否在不完全重算历史的情况下持续扩展。很多今天看似理所当然的工程细节，例如记忆缓存、长上下文分段、激活正则化，都是在这一阶段被系统打磨出来的。</p>
<div class="blog_h3"><span class="graybg">大语言模型在 NLP 版图里的位置</span></div>
<p>现代 Decoder-only 语言模型不再只是一个“下一个词预测器”，而是很多 NLP 任务的统一接口。翻译、摘要、问答、信息抽取、文本分类、对话和代码生成，都可以被改写成条件生成问题。这并不意味着传统任务边界消失，而是意味着许多任务开始共享同一种底座与同一套 token 级概率建模机制。</p>
<div class="blog_h2"><span class="graybg">信息抽取</span></div>
<p>信息抽取（Information Extraction, IE）研究的是：如何把自然语言中的非结构化描述，转成实体、关系、事件、属性和值等结构化结果。它并不要求模型生成长文本，而是要求模型从文本中<span style="background-color: #c0c0c0;">抽取可落库、可检索、可计算的知识单元</span>。命名实体识别、关系抽取、事件抽取、三元组抽取和名词短语规范化都属于这一大类。</p>
<p>导图里提到的信息抽取分支包括命名实体、三元组抽取和名词短语规范化；三元组抽取下又提到依存句法和知识库方向，以及 BERT、CNN、RNN 这些不同阶段的编码器。这些都属实，但今天如果只把 IE 理解成“先做一个 BERT 分类器”，就太窄了。</p>
<p>当前主流至少有三条。第一条是<span style="background-color: #c0c0c0;">任务专用抽取头</span>，例如 span-based NER、relation extraction、event extraction。第二条是<span style="background-color: #c0c0c0;">统一抽取框架</span>，代表如 UIE（Universal Information Extraction），尝试用统一的文本到结构生成范式处理多类 IE 任务。第三条是<span style="background-color: #c0c0c0;">LLM 指令抽取</span>，即把 schema、字段定义和约束写进 prompt，让模型直接按模板产出结构化结果。前两条更适合高精度、可评测的工程场景，第三条更适合快速扩 schema 和弱监督启动。</p>
<p>因此，CNN / RNN 抽取器和单一依存规则法更适合作为历史阶段代表；现代信息抽取的主线已经明显转向“统一 schema + 强编码器 / LLM + 结构约束 + 后处理校验”。</p>
<div class="blog_h3"><span class="graybg">命名实体识别（NER）</span></div>
<p>命名实体识别（Named Entity Recognition, NER）是信息抽取里最基础也最关键的一步。它回答的是：文本里的哪一段 span 是人名、组织名、地名、产品名、时间、金额或领域实体。早期规则系统依赖有限状态自动机、词典和角色规则；后续进入序列标注阶段，常见 IOB、BIOES、IOBES 等标签体系，配合 CRF、BiLSTM-CRF、BERT-CRF 做解码。</p>
<p>再往后，NER 明显从“给每个 token 打标签”走向“直接对实体边界和内部结构建模”。Global Pointer 这一类 span-based 架构直接给起止边界打分，适合嵌套实体和高效率抽取；W²NER 这类 relation/grid-based 架构则显式预测词间结构关系，更适合复杂实体结构和不连续实体。两者都属于现代深度学习 NER 架构，但它们已经不再是传统概率图模型那一路，而是建立在预训练编码器上的神经抽取头。</p>
<p>如果把定义写得更形式化一些，NER 输出的并不是单点标签，而是实体集合</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{Y}=\{(s_k,e_k,c_k)\}_{k=1}^{m}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(s_k\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(e_k\)</span> 表示第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个实体的起止位置， <span displaypfx="inline-" class="mathjax-container">\(c_k\)</span> 表示实体类型。这个写法直接揭示了 NER 的本质：它最终是一个<span style="background-color: #c0c0c0;">区间抽取 + 类型判定</span>问题，而不是纯粹的逐 token 分类问题。</p>
<div class="blog_h4"><span class="graybg">它们在系统里的位置</span></div>
<p>Global Pointer、W²NER 不是 BERT、RoBERTa、ModernBERT 这种通用编码器（Backbone / Encoder），也不是 HMM、CRF 那类经典概率图模型；它们更准确的定位是<span style="background-color: #c0c0c0;">接在编码器之上的 NER 专用任务头或结构化解码架构</span>。系统链路通常可以概括为：</p>
<span displaypfx="" class="mathjax-container">\[\text{Text}\rightarrow \text{Tokenizer}\rightarrow \text{Encoder}\rightarrow \text{NER Head}\rightarrow \text{Entities}\]</span>
<p>其中编码器负责把原始文本转成上下文化表示 <span displaypfx="inline-" class="mathjax-container">\(H=[h_1,\dots,h_n]\)</span>，而 NER head 决定如何把这些表示转成实体集合。若任务头选的是逐 token 分类，输出就更接近 BIO 标签；若任务头选的是 span 打分或词间关系建模，输出就更接近实体区间本身。Global Pointer 和 W²NER 的差别，主要就体现在“实体应该怎样从隐藏表示中被读出来”这一层。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">层级</td>
<td style="text-align: center;">主要作用</td>
<td style="text-align: center;">典型例子</td>
</tr>
</thead>
<tbody>
<tr>
<td>编码器（Backbone）</td>
<td>把文本编码成上下文化向量表示</td>
<td>BERT、RoBERTa、ModernBERT、BiLSTM</td>
</tr>
<tr>
<td>任务头（Task Head）</td>
<td>把隐藏表示转成实体边界和类型预测</td>
<td>Token Classification、CRF、Global Pointer、W²NER</td>
</tr>
<tr>
<td>输出形式</td>
<td>决定模型最终返回 BIO 标签还是实体 span 集合</td>
<td>B-ORG / I-ORG / O，或 <span displaypfx="inline-" class="mathjax-container">\((s,e,c)\)</span></td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">从序列标注到 span 建模</span></div>
<p>把 NER 当作序列标注有一个很自然的优点：实现简单，训练直接，和 token 级监督天然对齐。但它也有明显边界。第一，实体在语义上是一个整体区间，而序列标注把问题拆成逐位置判断，再依赖标签体系和解码规则把局部标签拼回实体，这会引入“局部预测正确但整体边界不稳”的误差。第二，当数据里存在嵌套实体（Nested Entity）时，例如较长实体内部还包含较短实体，单层 BIO 标注就会开始变得别扭。第三，若实体跨度很长，模型需要通过一串连续标签间接表达“这一整段属于同一个实体”，而不是直接对整个 span 打分。</p>
<p>因此，深度学习时代的很多 NER 方法开始直接面向 span 建模。核心想法是：与其先预测每个 token 的局部标签，再回头拼边界，不如直接让模型回答“从第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个位置到第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个位置，是否构成某一类实体”。这样，实体边界就从隐式结构变成了显式建模对象。Global Pointer 正是这条路线里非常典型的方法。</p>
<div class="blog_h4"><span class="graybg">Global Pointer</span></div>
<p>Global Pointer 的核心思想是：把每一种实体类型都看成一个“起点 - 终点匹配问题”。给定编码器输出 <span displaypfx="inline-" class="mathjax-container">\(H=[h_1,\dots,h_n]\)</span>，模型先把每个位置投影成适合做起点查询和终点键值匹配的向量，再对每种实体类型的每一对位置 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 计算一个 span 分数。若这个分数足够高，就认为“从 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 到 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 的片段属于该类型实体”。</p>
<p>一种简化写法是：</p>
<span displaypfx="" class="mathjax-container">\[q_i^{(c)}=W_q^{(c)}h_i,\qquad k_j^{(c)}=W_k^{(c)}h_j\]</span>
<span displaypfx="" class="mathjax-container">\[\mathrm{score}_c(i,j)=\big(q_i^{(c)}\big)^\top k_j^{(c)},\qquad i\le j\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 表示实体类型， <span displaypfx="inline-" class="mathjax-container">\(q_i^{(c)}\)</span> 表示位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 作为该类实体起点时的表示， <span displaypfx="inline-" class="mathjax-container">\(k_j^{(c)}\)</span> 表示位置 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 作为终点时的表示。很多实现会进一步加入 RoPE（Rotary Position Embedding）一类位置编码，使相对位置信息直接进入打分过程。</p>
<p>这个结构的关键优势有三点。第一，它<span style="background-color: #c0c0c0;">天然输出 span</span>，而不是先输出 BIO 标签再回拼实体，因此边界目标与任务定义更一致。第二，它对嵌套实体非常自然：同一个起点可以与多个终点形成不同 span，不同类型之间也互不冲突。第三，它对长实体更友好，因为模型直接给 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 这一对边界打分，不必靠一长串内部标签层层传递“这是同一个实体”的信号。</p>
<p>从工程角度看，Global Pointer 也是很受欢迎的一类 NER 头。它的实现相对简洁，主要开销来自构造每种类型的 <span displaypfx="inline-" class="mathjax-container">\(n\times n\)</span> 打分矩阵，训练与推理都比较直接。它因此特别适合需要嵌套实体支持、但又希望结构尽量简单的场景。</p>
<div class="blog_h4"><span class="graybg">W²NER</span></div>
<p>W²NER（通常写作 W2NER）走的是另一条路线：它把 NER 视为<span style="background-color: #c0c0c0;">词与词之间关系的二维建模问题</span>。与其直接给一个 span 打分，它更关心“两个位置之间是什么关系”，再通过这些局部关系把完整实体组装出来。也就是说，模型不是直接回答“ <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 是不是实体”，而是先在一个 <span displaypfx="inline-" class="mathjax-container">\(n\times n\)</span> 的词对关系网格上预测关系标签。</p>
<p>它常见地使用两类关系标签：</p>
<ul>
<li><span style="background-color: #c0c0c0;">NNW（Next-Neighboring-Word）</span>：表示一个实体内部相邻词之间的连接关系。</li>
<li><span style="background-color: #c0c0c0;">THW-{type}（Tail-Head-Word）</span>：表示某个实体的尾词和头词之间闭合成一个特定类型实体。</li>
</ul>
<p>在这个框架里，模型先构造词对表示，再用 biaffine、条件层归一化（Conditional Layer Normalization, CLN）、二维卷积（2D Convolution）等模块在整个关系网格上做局部模式建模。最终，实体不是由一次 span 打分直接得出，而是由一条词间关系链和一个闭合关系共同定义出来。</p>
<p>这条思路的最大价值在于表达力更强。因为模型显式建模的是词与词之间的结构关系，所以它不仅能处理嵌套实体，还更容易扩展到不连续实体（Discontinuous Entity）或更复杂的局部结构。代价也同样明显：它的关系网格更重，中间表示更大，计算和显存开销通常高于较轻量的 span 打分方法；同时，二维卷积式建模也让工程实现和调参复杂度明显上升。</p>
<div class="blog_h4"><span class="graybg">Global Pointer 与 W²NER 的分工</span></div>
<p>二者都属于深度学习时代的 NER 架构，但它们的设计哲学并不相同。Global Pointer 更像“直接给候选实体区间打分”；W²NER 更像“先预测实体内部关系，再把实体结构拼出来”。前者更直接、更轻量，后者结构表达更强。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">维度</td>
<td style="text-align: center;">Global Pointer</td>
<td style="text-align: center;">W²NER</td>
</tr>
</thead>
<tbody>
<tr>
<td>基本单位</td>
<td>实体 span <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span></td>
<td>词对关系 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span></td>
</tr>
<tr>
<td>核心问题</td>
<td>这个起点和终点能否构成某类实体</td>
<td>这两个词之间是什么结构关系</td>
</tr>
<tr>
<td>嵌套实体</td>
<td>天然支持</td>
<td>天然支持</td>
</tr>
<tr>
<td>不连续实体</td>
<td>通常不作为强项</td>
<td>更容易扩展支持</td>
</tr>
<tr>
<td>长 span 处理</td>
<td>更直接，因为直接做边界对打分</td>
<td>更依赖关系网格中的结构传播</td>
</tr>
<tr>
<td>工程复杂度</td>
<td>较低</td>
<td>较高</td>
</tr>
<tr>
<td>更像什么</td>
<td>span-based / pointer-based 抽取头</td>
<td>grid-based / relation-based 抽取架构</td>
</tr>
</tbody>
</table>
<p>因此，若任务重点是常规实体抽取、嵌套实体支持和较好的工程效率，Global Pointer 往往是更干净的设计；若任务存在复杂实体结构、关系链式表达或不连续实体，W²NER 一类 relation/grid 方法会更有吸引力。它们没有谁在所有场景中绝对更强，差异主要来自任务结构本身。</p>
<div class="blog_h3"><span class="graybg">关系抽取、三元组抽取与事件抽取</span></div>
<p>关系抽取（Relation Extraction）要求模型在识别实体之后，进一步判断实体之间存在哪一种关系；三元组抽取则把结果组织成 <span displaypfx="inline-" class="mathjax-container">\((h,r,t)\)</span> 形式。传统路线常结合依存句法、模板规则或 CNN / RNN 编码器做句级分类；BERT 时代之后，则更常见 span pairing、table filling、pointer 网络或联合抽取框架。</p>
<p>事件抽取又再往前一步，不只抽“谁和谁有什么关系”，还要抽“发生了什么事件、谁是论元、时间地点在哪里”。这也是为什么现代 IE 很强调统一 schema 和结构约束：不同子任务虽然形式不同，但本质上都在把自然语言映射成结构化记录。</p>
<div class="blog_h3"><span class="graybg">名词短语规范化与实体规范化</span></div>
<p>导图中提到的名词短语规范化（Noun Phrase Normalization），本质上是在解决“表面写法不同，但是否应归并到同一个标准概念”这一问题。它和实体链接相邻，但更偏向文本内部或领域词表内部的规范化，而不一定要求链接到外部百科实体。医学术语归一、商品名称归并、日志字段规范化，都属于这一类。</p>
<p>这条路线在现代系统里常和 NER、EL、术语词典、向量检索放在一起做。原因很直接：只有把抽出的 mention 进一步规范化，后续统计、检索和知识入库才真正可用。</p>
<div class="blog_h3"><span class="graybg">统一信息抽取与 LLM 抽取</span></div>
<p>UIE 这类统一框架的重要性，在于它不再把 NER、RE、事件抽取完全拆成彼此独立的数据格式和训练头，而是尝试用统一的结构化生成接口覆盖多类 IE 任务。这样做的收益是 schema 扩展更容易、任务迁移更自然；代价则是解码与评测都更复杂。</p>
<p>LLM 指令抽取则把这一路线继续推向更灵活的方向。做法通常是把字段定义、输出 JSON schema、约束规则和示例直接写进 prompt，让模型输出结构化结果。它的优势是启动快、跨 schema 成本低；缺点是稳定性、边界一致性和可验证性通常不如专用抽取模型。因此，高要求生产系统常采用“LLM 负责快速泛化，专用抽取器负责高精度主干”的混合模式。</p>
<div class="blog_h3"><span class="graybg">信息抽取任务如何串成系统</span></div>
<p>在真实系统里，NER、实体规范化、关系抽取、事件抽取和实体链接很少孤立运行。一个更完整的链路通常是：先找出 mention，再把 mention 归一到标准实体或术语，再判断实体之间的关系或事件角色，最后把结果写入知识库、检索索引或下游规则系统。也就是说，信息抽取真正交付的不是若干分散标签，而是一套可以被数据库、搜索和推理系统消费的结构化记录。</p>
<p>这也是为什么 IE 的误差会级联传播。上游 NER 边界一旦偏掉，后续关系抽取、实体链接和事件论元识别都会被拖偏；反过来，若系统能利用知识库约束、schema 校验和下游一致性反馈，上游抽取结果也能被部分纠正。因此，现代 IE 越来越像一条带反馈的结构化流水线，而不是几个彼此隔离的小任务。</p>
<div class="blog_h2"><span class="graybg">问答</span></div>
<p>问答（Question Answering, QA）研究的是：给定问题 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 与可能的知识来源，如何输出正确答案 <span displaypfx="inline-" class="mathjax-container">\(a\)</span>。真正的差异不在“有没有问题和答案”，而在于答案来自哪里、是否允许外部知识、是否需要长文检索、是否要跨文档推理。</p>
<p>导图把 QA 分成基于知识、基于检索、阅读理解和完形填空四类，这是非常经典的划分。基于知识的问答常围绕知识图谱和实体关系做推理，过去会用 Gated Graph Neural Networks、Bidirectional Attentive Memory Networks 一类结构；基于检索的问答强调先找证据再回答，Denoising QA、DecaProp 属于早期深阅读器时代；阅读理解里，Gated-Attention Reader、AoA Reader、XLNet 代表的是抽取式 / 阅读器模型的不同阶段；完形填空则更接近“填空推理”基准。</p>
<p>到 2026 年，问答的真正主线已经是<span style="background-color: #c0c0c0;">检索增强生成（RAG）</span>与<span style="background-color: #c0c0c0;">长上下文问答</span>。很多系统不再把“问答模型”独立训练成单一 reader，而是用检索器找证据，再交给 LLM 生成和引用答案。知识图谱问答依旧存在，但不再是唯一核心路线；阅读理解型模型也仍有价值，但更多转化成“作为专用 reader 或 reranker 的组件”。因此，传统 Reader 模型并没有完全过时，但它们今天更像 QA 系统里的模块，而不是系统本体。</p>
<div class="blog_h3"><span class="graybg">知识库问答</span></div>
<p>知识库问答（Knowledge-base QA, KBQA）通常要求模型把自然语言问题映射到知识图谱查询路径、逻辑形式或候选实体关系组合。Gated Graph Neural Networks、记忆网络一类模型之所以重要，是因为它们代表了“在图结构上做推理”的阶段。这条路线今天仍有价值，尤其在高精度企业知识库、医疗知识库和金融知识图谱中。</p>
<div class="blog_h3"><span class="graybg">阅读理解式问答</span></div>
<p>抽取式阅读理解模型解决的是：答案就在给定段落中，系统只需找出正确 span。AoA Reader、Gated-Attention Reader、XLNet 阅读器等都属于这一路。它们在 benchmark 上曾非常强，也塑造了“question + passage -&gt; answer span”这一经典范式。今天它们更多承担 reader、reranker 或 teacher 的角色，而不是完整 QA 系统本体。</p>
<div class="blog_h3"><span class="graybg">RAG 与长上下文问答</span></div>
<p>现代问答系统的核心通常是“先证据，后答案”。检索器负责召回，重排器负责压缩候选，LLM 负责综合、归纳、生成并引用证据。若上下文窗口足够长，还可以把多段证据一起送入模型完成跨文档回答。这使 QA 从“单段抽取”升级为“检索、证据组织、推理、生成”一条完整链路。</p>
<div class="blog_h3"><span class="graybg">完形填空与 Reader 时代的意义</span></div>
<p>完形填空（Cloze-style QA）和早期阅读器模型之所以在发展史上重要，不只是因为它们是早期 benchmark，而是因为它们迫使模型显式学习“从上下文恢复缺失信息”。Reading Strategies Model、Hidden Coherence Model 这类方法今天已经明显不是主流系统，但它们代表了阅读理解任务从浅层词匹配走向深层语义建模的过渡阶段。</p>
<div class="blog_h3"><span class="graybg">问答为什么会依赖其他 NLP 任务</span></div>
<p>问答系统表面上像一个单独任务，实际上经常站在多项 NLP 能力的交汇点上。检索负责把相关证据找出来，信息抽取负责把文档中的实体、事件和结构化事实整理清楚，NLI 或 reranker 负责判断哪些证据真的支持答案，摘要或生成模块再把证据组织成最终回答。若缺少这些中间环节，系统很容易要么答非所问，要么生成看似流畅但缺乏依据的答案。</p>
<p>因此，现代 QA 更适合被理解为一个<span style="background-color: #c0c0c0;">任务编排层</span>。它并不总是靠某个单独 reader 或 LLM 一步完成，而是依赖检索、抽取、排序、验证和生成协同工作。也正因为如此，问答的上限通常不只取决于生成模型本身，还取决于前面各层证据组织得是否足够干净。</p>
<div class="blog_h2"><span class="graybg">实体链接</span></div>
<p>实体链接（Entity Linking, EL）处理的是：文本里出现的某个实体提及（Mention），究竟对应知识库中的哪一个真实实体。它的难点不在于发现“这里提到了一个名字”，而在于做消歧（Disambiguation）。例如“Jordan”可能是国家、姓氏或篮球运动员；“Apple”可能是水果，也可能是公司。</p>
<p>导图里提到 DeepType、ELDEN、联合实体识别与消歧、WAT，代表了实体消歧和端到端 EL 的几个典型阶段。WAT 这类 Wikipedia-based linking 工具曾经非常实用，但若从今天的主流看，它已经更像传统成熟系统，而不是最前沿方案。</p>
<p>当前实体链接的强主线是<span style="background-color: #c0c0c0;">检索 + 精排</span>。BLINK 这类系统先用 bi-encoder 在向量空间检索候选实体，再用 cross-encoder 细排；GENRE 这类方法则把链接问题转成“生成实体名称或唯一标识”的 constrained generation。再往前一步，很多 LLM 应用会把 EL 与检索、知识库调用和 schema 约束整合起来。也就是说，纯规则或纯局部分类式 EL 今天已经明显偏旧，现代 EL 更像“候选召回 + 全局上下文消歧 + 知识库约束”的组合系统。</p>
<div class="blog_h3"><span class="graybg">实体链接的两阶段结构</span></div>
<p>实体链接几乎总是分成两步。第一步是候选生成（Candidate Generation），也就是先把可能对应的实体缩到几十个甚至几个；第二步是实体消歧（Entity Disambiguation），利用上下文、局部语义和知识库结构做最后判断。若没有第一步，搜索空间太大；若没有第二步，系统很容易只凭表面字符串做错决策。</p>
<div class="blog_h3"><span class="graybg">生成式实体链接为什么出现</span></div>
<p>GENRE 一类方法出现后，实体链接开始从“多分类 / 排序问题”扩展成“受约束生成问题”。模型不再只在固定候选里二选一，而是直接生成实体名称、页面标题或唯一标识，再由解码约束保证输出合法。这种方法在开放域和超大知识库场景下很有吸引力，但它通常仍需要检索、别名库和知识库约束配合，不能把它理解成彻底摆脱候选集。</p>
<div class="blog_h3"><span class="graybg">实体链接最常见的失败模式</span></div>
<p>实体链接里最典型的错误，并不是模型完全不理解上下文，而是<span style="background-color: #c0c0c0;">候选集一开始就召回错了</span>。如果候选生成阶段没有把正确实体放进候选池，后面的精排器再强也无从挽回。别名缺失、缩写歧义、跨语言拼写差异和长尾实体冷启动，都是这一层最常见的来源。</p>
<p>另一类错误发生在上下文消歧上。系统看到了正确候选，却没有真正理解当前语境中的实体角色。例如“Apple 发布财报”与“苹果富含果胶”共享同一个表面字符串，但上下文语义完全不同。也正因为如此，实体链接的误差分析通常必须把召回失败与消歧失败拆开看，否则很难判断问题究竟出在检索、别名库还是上下文建模。</p>
<div class="blog_h3"><span class="graybg">实体链接在知识系统中的位置</span></div>
<p>实体链接通常是知识系统的入口对齐层。信息抽取负责把 mention 找出来，实体链接负责把 mention 对到知识库中的唯一实体，后续关系推理、知识图谱查询和事实聚合才能真正建立在同一个对象上。若没有这一层，系统很容易把同一实体的不同别名拆成多份记录，或把不同实体错误合并。</p>
<p>因此，EL 很少单独作为终点任务交付，更常作为检索、知识库、问答和推荐系统里的基础设施存在。它把开放文本里的表面字符串，转换成数据库里可被唯一索引的实体标识，这正是知识系统能稳定扩展的前提。</p>
<div class="blog_h2"><span class="graybg">关系预测</span></div>
<p>关系预测（Relation Prediction）在导图语境里更接近知识图谱补全（Knowledge Graph Completion）或链路预测（Link Prediction）：给定头实体 <span displaypfx="inline-" class="mathjax-container">\(h\)</span>、关系 <span displaypfx="inline-" class="mathjax-container">\(r\)</span>、尾实体 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 中的部分信息，预测缺失边是否存在，或哪一个关系最合理。</p>
<p>TransE、ConvE、ConvKB、KBAT 都是这一方向的典型模型。TransE 用平移假设 <span displaypfx="inline-" class="mathjax-container">\(h+r\approx t\)</span> 建模关系，是最经典的知识图谱嵌入基线；ConvE、ConvKB 用卷积操作增强三元组交互；KBAT 则引入图注意力。到今天，这些模型仍然经常作为 benchmark baseline 出现，但已经不能被视为“当前知识推理系统的全部”。</p>
<p>现代路线至少多了两层扩展。第一层是更强的知识图谱嵌入与图神经网络；第二层是把 KG 与 LLM 结合，用文本语义、路径推理和外部知识共同预测缺失关系。也就是说，导图里这些模型没有错，但它们更像<span style="background-color: #c0c0c0;">经典 KGE 主线</span>，而不是 2026 年全部主流。</p>
<div class="blog_h3"><span class="graybg">经典 KGE 模型如何分工</span></div>
<p>TransE 代表最早的几何平移建模思想，结构简单、解释直接；ConvE、ConvKB 试图用更强的非线性交互提升表达力；KBAT 则把图结构邻域显式纳入建模。它们的共同前提是：知识已经以三元组形式存在，模型的任务是从结构中补全缺失边，而不是直接从原始自然语言中抽知识。</p>
<p>若把这条线再补完整，DistMult、ComplEx、RotatE 也是必须知道的代表模型。DistMult 用双线性打分，是最经典的张量分解式基线之一；ComplEx 通过复数空间解决对称关系表达不足的问题；RotatE 则用复平面旋转刻画关系变换。这几类模型一起构成了知识图谱嵌入从“平移”到“更强代数结构建模”的主干。</p>
<div class="blog_h3"><span class="graybg">今天为什么要把文本语义重新接进来</span></div>
<p>现代知识系统很少只依赖图结构本身，因为大量关系证据其实散落在原始文本、文档描述和外部检索结果中。于是，关系预测开始越来越像“知识图谱结构 + 文本语义 + 检索证据 + LLM 推断”的混合问题。传统 KGE 在封闭图谱 benchmark 上仍有价值，但面对开放世界知识更新时，单靠静态嵌入往往不够。</p>
<div class="blog_h3"><span class="graybg">关系预测最容易踩的坑</span></div>
<p>关系预测最常见的误判之一，是把<span style="background-color: #c0c0c0;">图中共现</span>误当成<span style="background-color: #c0c0c0;">真实关系</span>。某些实体因为在图谱中高频出现，会让模型学到“它似乎和很多东西都能连上”，从而在评测集上看起来不错，但一到开放世界或冷启动实体就迅速失真。静态图嵌入在这一点上尤其明显，因为它们更擅长记住结构统计，而不一定真的理解关系语义。</p>
<p>另一个常见问题是封闭图谱假设过强。很多 benchmark 默认实体和关系集合是固定的，但真实业务里知识会持续增长，文本证据也会不断出现。若系统只依赖既有图结构，而不把文档语义、别名变化和新事实注入进来，就会在知识更新场景里很快退化。</p>
<div class="blog_h3"><span class="graybg">关系预测如何接到上游文本</span></div>
<p>关系预测与关系抽取并不是同一件事，但在现代知识系统里两者越来越紧密。关系抽取从原始文本中发现候选事实，关系预测则在知识图谱结构和文本证据上判断哪些边应被补全、强化或修正。若把两者结合起来，系统就不再只是“从文本抽三元组”，而是能够持续把文本世界的新增事实并入图谱世界。</p>
<p>因此，关系预测在今天越来越像一个知识融合层：上游接文本抽取、实体链接和检索证据，下游接图谱补全、问答和推荐。它的价值不只在 benchmark 上补对几条边，而在于让知识图谱随着外部世界变化持续更新。</p>
<div class="blog_h2"><span class="graybg">语法纠错</span></div>
<p>语法纠错（Grammatical Error Correction, GEC）要求模型把含有语法、拼写、搭配、时态或词法错误的句子，改写为更符合标准书面语言的版本。它本质上是一个“受约束文本编辑”任务，而不是开放式生成任务：模型既要改对，又不能无故改动本来正确的部分。</p>
<p>导图里的 Copy-Augmented Transformer、CNN Seq2Seq + Quality Estimation、Transformer，都代表了从“序列到序列改写”走向“更受控编辑”的过渡阶段。尤其是 copy 机制，在 GEC 中很自然，因为绝大多数 token 其实应该原样保留。</p>
<p>今天的主流更偏向 T5、mT5、BART 这类 encoder-decoder 文本编辑模型，以及 LLM 驱动的纠错器。CNN Seq2Seq 已明显过时，更多作为历史阶段代表；纯通用 Transformer 也通常不会直接裸用，而是会配合差分标注、最小编辑目标、错误类型建模或后验置信度校准。若系统要求可解释和高精度，还会加入语言规则、置信度过滤和多候选 rerank。</p>
<div class="blog_h3"><span class="graybg">编辑任务为什么不同于自由生成</span></div>
<p>GEC 和摘要、开放式写作不同，它的最优输出通常离输入很近。也正因为如此，copy 机制、最小编辑距离目标、差分标注和受控解码在这一任务里格外重要。一个好的纠错器不仅要会改错，还要会<span style="background-color: #c0c0c0;">尽量少改</span>，否则很容易把本来正确的表达也改坏。</p>
<div class="blog_h3"><span class="graybg">今天的纠错系统如何落地</span></div>
<p>现代 GEC 系统通常会把生成模型与后验筛选结合起来：模型先生成候选改写，再结合语言规则、质量估计、置信度阈值或多候选 rerank 选出最稳的版本。若场景对可解释性要求高，例如教育批改，还会额外输出错误类型和修改依据，而不是只给最终改写句。</p>
<div class="blog_h3"><span class="graybg">语法纠错的核心难点是少改</span></div>
<p>GEC 的核心难点并不只是“识别错误”，而是控制修改边界。很多模型在训练后会出现过度修改（Over-correction）：它们把风格差异、可接受变体甚至作者有意保留的表达都改成自己更熟悉的形式。这样得到的句子可能更像标准语料，却不一定更符合用户真正需要。</p>
<p>因此，GEC 的高质量系统往往必须额外监控两类指标：一类是错误纠正率，另一类是无错句保持率。若只盯着改对多少错句，而不管原本正确句子被误改多少，系统在真实使用中往往会显得“很积极，但不可信”。</p>
<div class="blog_h3"><span class="graybg">语法纠错在系统里的真实用法</span></div>
<p>语法纠错在教育批改、写作辅助、客服质检和文本清洗里都很常见，但不同场景的目标并不相同。教育场景需要错误类型和解释，写作辅助更强调流畅与风格建议，客服质检可能只关心是否满足模板规范，训练数据清洗则更看重低风险批量修复。也正因为如此，同样叫 GEC，系统输出可以是改写句、差分标注、错误标签或多候选建议，而不一定只有一种形式。</p>
<p>这也解释了为什么很多系统不会让 GEC 模型直接“改完即落地”。更稳的做法通常是：模型给出候选修改，规则与置信度模块筛掉高风险改写，再由用户确认或下游流程决定是否接受。换句话说，现代 GEC 更像受控编辑器，而不是完全自动重写器。</p>
<div class="blog_h2"><span class="graybg">对话</span></div>
<p>对话（Dialogue）不是单一任务，而是一组彼此耦合的子任务集合。导图中列出的对话行为分类、对话状态跟踪、检索式聊天机器人、生成式聊天机器人、意图分类、槽填充，本质上共同构成了传统对话系统的技术栈。</p>
<p>若是任务型对话（Task-Oriented Dialogue），系统通常先做意图分类（Intent Classification）和槽填充（Slot Filling），再通过对话状态跟踪（Dialogue State Tracking, DST）维护用户目标。Neural Belief Tracker、自注意力 DST、CRF-ASN、BiLSTM-CRF 都是这一代方法的代表。若是开放域聊天，历史上往往分检索式与生成式：Poly-encoder、BERT、ELMo 更偏检索匹配；TransferTransfo、Seq2Seq 更偏生成。</p>
<p>到 2026 年，对话系统的中心已经显著转向<span style="background-color: #c0c0c0;">LLM 驱动的统一对话代理</span>。这并不意味着意图分类、槽填充和 DST 失效了，而是它们经常被吸收到更大的系统里，作为工具、状态缓存、结构化约束或评测子项存在。传统模块化对话系统仍然适用于高可控、高合规、流程稳定的客服和交易场景；而开放式、多工具、多轮任务对话则几乎都在向“LLM + memory + retrieval + tool use”的路线收敛。</p>
<div class="blog_h3"><span class="graybg">任务型对话系统</span></div>
<p>任务型对话的关键不是“聊得自然”，而是<span style="background-color: #c0c0c0;">把用户目标稳定推进到可执行状态</span>。因此它天然重视意图分类、槽填充、DST、策略学习和接口调用。导图里的 CRF-ASN、BiLSTM-CRF、Neural Belief Tracker 等模型，正对应传统任务型对话系统的典型子模块。今天在强流程、高合规场景里，这条路线依然有效。</p>
<div class="blog_h3"><span class="graybg">开放域与 Agent 化对话</span></div>
<p>开放域聊天和多工具对话则更强调上下文理解、长期记忆、外部检索和工具协作。这里的核心不再只是单轮回复质量，而是多轮一致性、任务分解、工具调用正确性和用户状态持续跟踪。也正因为如此，现代对话系统越来越像一个由 LLM 驱动、但周围包裹记忆、检索、函数调用和安全控制的系统，而不是一个单独的“聊天模型”。</p>
<div class="blog_h2"><span class="graybg">指代消解</span></div>
<p>指代消解（Reference Resolution）处理的是：文本中的代词、名词短语或省略表达，究竟指向哪个先行项。更狭义的共指消解（Coreference Resolution）关注同一实体在文档中的多次提及，例如“OpenAI 发布了新模型。它……”，其中“它”指回“OpenAI”或“新模型”，就需要结合上下文判断。</p>
<p>导图中把它分成共指、回指、所指，按方法又区分规则系统和统计方法，这是经典教科书式划分。规则系统里，Hobbs 算法和知识库规则曾经非常重要；统计阶段则经历了 Mention Pair、Mention Ranking、Entity-Mention 等一系列建模范式。后来深度学习又把问题统一成端到端 span 推断，例如 end-to-end neural coreference 与后续 SpanBERT 路线。</p>
<p>今天规则法已经不是主流最优，但在小数据、强格式文本和高可解释场景里仍有生命力。真正的主线是<span style="background-color: #c0c0c0;">端到端 span 模型 + 预训练编码器</span>，以及近两年的 LLM 辅助共指消解。值得注意的是，LLM 虽然能做不少指代任务，但在长文档一致性和幻觉控制上并不天然完美，所以共指消解并没有因为 LLM 出现而“自动解决”。</p>
<div class="blog_h3"><span class="graybg">规则与统计范式</span></div>
<p>规则法之所以在早期有效，是因为很多指代现象确实和句法位置、性数一致、语义类别约束强相关。统计阶段的 Mention Pair、Mention Ranking、Entity-Mention 则逐步把问题从“写规则”转成“学习哪些候选更像同一实体”。这几代方法虽然已不是前沿，但仍然决定了今天端到端 span 模型的评价方式和候选组织逻辑。</p>
<div class="blog_h3"><span class="graybg">长文一致性仍然是难点</span></div>
<p>现代大模型能在短上下文里做不少共指判断，但长文档、多实体交织和跨段落所指漂移依然容易出错。也正因为如此，共指消解至今仍是很多文档理解、法律文本分析和长篇问答系统的重要中间模块，而不是一个已经“被 LLM 顺手解决”的旧任务。</p>
<div class="blog_h2"><span class="graybg">时间处理</span></div>
<p>时间处理（Temporal Processing）研究的是：如何从文本中识别时间表达、恢复时间值、推断事件先后关系，以及估计文档的成文时间。它不仅关心“2026 年 4 月”这种显式时间实体，也关心“上周五”“三个月后”“发布前一天”这类相对、模糊或依赖上下文的时间表达。</p>
<p>导图里提到文档时间标记和时间提取两个方向，这是很好的切分。NeuralDater、BurstySimDater 属于文档时间推断阶段的代表；时间提取则通常包括时间实体识别、时间实体链接 / 归一化、时间表达合成和时间值补全。这里真正困难的地方往往不是识别出“这是一个时间片段”，而是把它映射成统一、可计算的时间值。</p>
<p>现代时间处理仍然保留很强的<span style="background-color: #c0c0c0;">规则 + 学习混合</span>特征。原因是时间正规化对格式和日历知识高度敏感，纯神经网络不一定比规则安全；但时间识别、文档定年和跨句时间推理又越来越依赖强编码器与 LLM。因此，这个方向并没有被某个单一大模型彻底替代，而是长期保持 hybrid 风格。</p>
<div class="blog_h3"><span class="graybg">时间识别与时间归一化</span></div>
<p>时间处理至少包含两层不同问题。第一层是识别：找出文本中哪些片段在表达时间；第二层是归一化：把“明天下午”“上个季度”“发布前三天”这类表达映射成统一时间值或时间区间。第一层更像实体识别，第二层则更依赖日历规则、基准时间和上下文推断。</p>
<div class="blog_h3"><span class="graybg">文档定年与时间推理</span></div>
<p>NeuralDater、BurstySimDater 这类方法关注的是整个文档大概写于何时，或者文中事件时间如何组织。这类任务常需要把局部时间表达、主题词汇、事件背景和文档风格一起纳入判断，因此比简单时间实体识别更接近高层语义推断。</p>
<div class="blog_h3"><span class="graybg">规则系统为什么仍然重要</span></div>
<p>时间正规化至今仍大量依赖 HeidelTime、SUTime 一类规则系统，原因不是这个方向“还不够现代”，而是时间表达本身高度格式化、依赖日历知识，并且错误代价很高。学习模型擅长召回和上下文推断，规则系统擅长最后一步标准化，这正是时间处理长期保持 hybrid 风格的根本原因。</p>
<div class="blog_h4"><span class="graybg">TIMEX3、ISO-TimeML 与 SCATE</span></div>
<p>时间处理里一个经常被忽略的重点是：模型最后到底要把时间表示成什么。TIMEX3 和 ISO-TimeML 提供的是经典标准化框架，它们让系统能够把“2024 年底”“两周后”“此前一天”这类表达写成统一注释格式，便于不同系统共享数据与评测。HeidelTime 之所以长期有生命力，也和它紧贴 TIMEX3 规范有关。</p>
<p>不过，传统 TIMEX3 表示在面对组合式、事件相对型和多片段时间表达时会显得偏紧。近年的时间正规化研究开始更多借助<span style="background-color: #c0c0c0;">SCATE 这类更接近语义组合的表示</span>，把时间表达理解成可执行的语义构造，而不是只生成一个表面规范字符串。这条线本质上让时间正规化越来越像语义解析任务，而不只是模式匹配。</p>
<div class="blog_h4"><span class="graybg">时间关系抽取与事件排序</span></div>
<p>时间处理不只关心单个时间片段，还关心事件之间的先后、重叠和包含关系。例如“签约后一个月交付”“开庭前提交证据”“发烧三天后住院”，真正决定业务含义的往往是事件与时间、事件与事件之间的相对次序。因此，Temporal Relation Extraction 和事件时间排序常与时间实体识别共同组成一套系统，而不是互相独立。</p>
<p>这类任务比时间实体识别更难，因为它需要跨句、跨段落乃至跨文档整合证据。模型既要知道哪些词是事件，又要知道它们如何被时间信号词、时态、语气和篇章结构共同约束。也正因为如此，时间关系推理往往是时间处理里最接近高层语义理解的一块。</p>
<div class="blog_h4"><span class="graybg">时间处理在系统里如何落地</span></div>
<p>时间处理在实际系统里常出现在时间线构建、医疗病程整理、法律事实排序、新闻事件追踪、文档检索过滤和时效问答里。很多系统真正需要的并不是“找出一个时间 mention”，而是把它绑定到统一时间轴上，再与事件记录、数据库字段或搜索索引对齐。这里一旦时间归一化错了，后续检索、排序和分析都会系统性偏移。</p>
<p>因此，现代时间系统常采用分层结构：前段用规则与模型联合做时间识别和归一化，中段做事件-时间对齐与关系推理，后段再把结果注入检索、问答或知识库模块。LLM 可以提高覆盖和解释能力，但最终落库的时间值仍然往往需要规则或可执行语义层做最后校验。</p>
<div class="blog_h4"><span class="graybg">时间处理最常见的错误类型</span></div>
<p>时间处理里的典型错误，往往不是“完全没识别出时间”，而是<span style="background-color: #c0c0c0;">识别对了 mention，却归一化错了值</span>。例如“下周一”必须相对于文档创建时间解析；“第三季度”必须结合业务日历；“术后三天”甚至需要先找出手术事件发生点。模型若把这些表达直接映射成表面日期字符串，而没有绑定正确参考系，最终结果会在时间线上整体漂移。</p>
<p>另一类常见错误发生在事件排序上。句子里同时出现多个事件、多个时间锚点和跨句照应时，系统很容易把“复诊后三天再次住院”理解成“住院后三天复诊”。这类错误在医疗、法律和新闻场景里代价很高，因此时间处理的误差分析通常必须同时检查 mention 检出、归一化和关系推理三层，而不能只看实体识别 F1。</p>
<div class="blog_h2"><span class="graybg">词法分析</span></div>
<p>词法分析（Lexical Analysis）处理的是句子进入更高层语义与句法建模之前的基础语言单位处理。它关心的通常不是整句含义，而是文本如何被切分、归一、标注成更适合后续处理的符号序列。导图中列出的分词、词干提取、词形还原、词性标注、命名实体识别，确实常在传统 NLP 流程图里被放在这一级。</p>
<div class="blog_h3"><span class="graybg">分词</span></div>
<p>分词（Tokenization / Word Segmentation）在中文和英文里的问题形态并不相同。英文天然有空格，因此基础 tokenization 常从空格和规则切分开始，再处理标点、缩写和复合词；中文没有显式词边界，因此需要额外决定“哪些字应该组成一个词”。中文分词的经典路线包括：基于词典的 Trie / DAT / AC 自动机，配合正向最长、逆向最长、双向最长等扫描规则；基于统计的 n 元语法 + 最短路径 / N 最短路径；以及基于深度学习的序列标注。</p>
<p>到 2026 年，深度学习时代讨论“分词”时，很多场景已经转化成了<span style="background-color: #c0c0c0;">子词切分（Subword Tokenization）</span>问题。英文里，BPE、WordPiece、Unigram Language Model 和 SentencePiece 比传统有限状态切分更重要；中文里，大模型通常直接采用统一 tokenizer，而不再把传统中文分词作为唯一入口。也就是说，传统中文分词并没有消失，但在大模型系统里，它不再是唯一中枢。</p>
<p>英文侧若回到更传统的 NLP 管线，还会见到有限状态自动机（Finite State Automaton, FSA）和规则分词器。它们在今天的大模型训练里不再是主角，但在工业文本清洗、词法分析器和需要强规则控制的场景里仍然常见。</p>
<div class="blog_h4"><span class="graybg">中文分词的三条路线</span></div>
<p>中文分词之所以长期是独立问题，是因为汉语书写天然不提供稳定词边界，而很多后续任务又需要比“单字”更高一级的词汇单位。基于词典的方法本质上是在字符串流上做高效匹配，Trie、双数组字典树（DAT）和 AC 自动机解决的是“如何快速找到候选词”；正向最长、逆向最长、双向最长解决的是“当词典里有多个可匹配长度时如何选边界”。这一路线速度快、实现稳、易于接入领域词典，但对新词和上下文歧义无能为力。</p>
<p>统计分词把切分看成路径搜索或序列概率最大化问题。做法通常是先构造词图，再用 n 元语法、最短路径或 N 最短路径寻找最优切分。与纯词典法相比，它能利用上下文偏好解决部分歧义，例如“研究生命起源”应切成“研究 / 生命 / 起源”还是“研究生 / 命 / 起源”。深度学习路线则进一步把问题改写成字级序列标注或 span 预测，让模型直接学习“字与字之间是否应该断开”。到了预训练模型时代，中文分词甚至可以被吸收到更大的表示学习框架中，不再必须先手工做干净切分。</p>
<div class="blog_h4"><span class="graybg">英文 tokenization 与子词切分</span></div>
<p>英文表面上“有空格就够了”，但真正进入模型前仍需解决大量边角问题，例如缩写、连字符、URL、数字、标点、特殊符号、大小写和多语言混杂。传统规则切分器和 FSA 在这一步很强，因为它们能精细控制清洗行为；但在现代预训练系统里，真正决定词法入口的往往是子词算法。BPE、WordPiece、SentencePiece 和 Unigram Language Model 的核心目标都不是还原语言学上的“真实词”，而是在词表大小、未登录词鲁棒性和序列长度之间做工程折中。</p>
<p>因此，今天很多 NLP 模型的第一步已经不是“先分词再建模”，而是“按 tokenizer 规则把文本编码成 token 序列”。这也是为什么传统中文分词、英文规则切分和现代 tokenizer 不能混为一谈。前两者更偏语言学和预处理，后者则直接决定模型输入空间本身。</p>
<div class="blog_h3"><span class="graybg">词干提取、词形还原与词性标注</span></div>
<p>词干提取（Stemming）和词形还原（Lemmatization）都在解决“不同表面形态如何映射到更稳定的词汇单位”这一问题。波特词干算法（Porter Stemmer）属于典型规则式词干提取；词形还原则更依赖词典和词法知识，例如把 <pre class="crayon-plain-tag">went</pre> 还原成 <pre class="crayon-plain-tag">go</pre>。词干提取更粗糙，词形还原更语言学化。</p>
<p>词性标注（Part-of-Speech Tagging, POS）则是给每个 token 赋予语法类别，例如名词、动词、形容词。早期系统常用 HMM、CRF；深度学习阶段出现了 Meta-BiLSTM、Flair 等模型；今天多数高质量 POS 系统已经建立在强预训练编码器之上。需要注意的是，POS 在大模型时代没有“消失”，只是很少再作为独立明星任务出现，而更多是下游结构化分析的一部分。</p>
<p>至于 NER，虽然很多传统课程把它挂在词法分析旁边，但从任务本质看，它更适合归入信息抽取，因此本文主体也放在信息抽取一侧理解。</p>
<div class="blog_h4"><span class="graybg">词干提取与词形还原的区别</span></div>
<p>词干提取和词形还原经常被混用，但它们解决的问题并不完全相同。词干提取追求的是把多个形态变体压缩成可聚合的表面核心，因此允许输出一个并非真实单词的“词干”；词形还原追求的则是恢复词典意义上的规范词元，因此通常需要结合词性和词法规则。信息检索时代大量使用 stemming，是因为它可以提高召回；而高质量语言分析更偏好 lemmatization，因为它保留了更强的语言学一致性。</p>
<div class="blog_h4"><span class="graybg">词性标注如何进入现代系统</span></div>
<p>在现代 NLP 管线里，POS 很少再单独成为最终产品，却经常作为隐式中间结构存在。句法分析器会利用词类信息稳定局部结构判断；信息抽取和关系抽取在某些低资源场景下也会把 POS 作为辅助特征；语法纠错与教学反馈系统则仍然需要显式输出词性标签。HMM 和 CRF 代表的是“局部发射 + 转移约束”的经典序列标注时代，BiLSTM、Flair 和预训练编码器代表的是“上下文表示更强，显式特征工程更少”的阶段。今天若只谈 POS 标签集而不谈编码器，往往已经不够贴近实际系统。</p>
<div class="blog_h3"><span class="graybg">词法分析在现代 NLP 里的角色</span></div>
<p>传统 NLP 往往把词法分析看成严格前置流水线：先分词、再词形还原、再词性标注、再进入句法和语义模块。现代大模型系统中，这种刚性的前后级联已经明显减弱，因为大量词法信息会被预训练编码器隐式吸收。但词法分析并没有消失，它只是从“每个系统都要显式暴露的一连串步骤”变成了“在 tokenizer、预处理器、结构化分析器和数据清洗器里各自承担不同角色”的基础层。</p>
<p>真正落地时，是否显式做词法分析，取决于任务边界。如果目标是训练一个通用编码器，往往只需要稳定 tokenizer；如果目标是做中文检索、领域规则抽取、教育批改、法律文书结构化或高质量句法分析，显式的分词、词形还原和 POS 仍然会显著影响系统上限。也正因为如此，词法分析不能被简单理解成“旧时代遗产”，它依然是许多高精度系统的入口控制层。</p>
<div class="blog_h2"><span class="graybg">句法分析</span></div>
<p>句法分析（Syntactic Parsing）研究的是：一个句子的结构应该怎样被组织起来。它通常分成短语结构分析（Constituency Parsing）和依存句法分析（Dependency Parsing）两大主线。前者更关心句子如何被递归切成词组、短语和从句；后者更关心词与词之间谁依附谁、主谓宾修饰关系怎样组织。</p>
<div class="blog_h3"><span class="graybg">短语结构分析</span></div>
<p>短语结构分析的经典路线是概率上下文无关文法（PCFG）与 CKY 动态规划。PCFG 让规则带概率，CKY 负责在句子上做高效 chart parsing。之后出现了判别式和深度学习阶段，例如递归神经网络语法器、自注意力编码器语法器，以及 Self-Attentive Encoder + ELMo 这类代表模型。今天若追求高精度，强预训练编码器和自注意力句法器显然比纯 PCFG 更常见，但 PCFG 和 CKY 仍然是理解语法解析的基础。</p>
<p>短语结构树的价值，在于它直接保留了“短语由哪些更小成分构成”的层级信息。例如名词短语、动词短语、从句边界和附着位置，在 constituency tree 中都能被显式表达。因此，它在语言学分析、句法驱动生成、教育语法反馈和某些高精度语义解析任务里仍然很重要。PCFG 解决的是“在文法规则给定时如何估计一棵树的概率”，而 CKY 解决的是“如何在指数级候选树空间里做可计算的动态规划搜索”。</p>
<p>深度学习时代的 constituency parser 则把“树结构评分”交给神经编码器完成。递归神经网络语法器强调沿树组合表示，自注意力语法器则更擅长利用全局上下文。到了预训练模型阶段，parser 往往不再自己从零学习词法和局部组合，而是直接站在强编码器隐藏状态之上做 span 打分或 chart 打分。这意味着 PCFG 仍然是理论骨架，但现代高精度系统的打分函数已经明显神经化。</p>
<div class="blog_h3"><span class="graybg">依存句法分析</span></div>
<p>依存分析的经典划分是<span style="background-color: #c0c0c0;">基于图（Graph-based）</span>与<span style="background-color: #c0c0c0;">基于转移（Transition-based）</span>。图方法把整棵依存树看成全局优化问题，典型算法包括项目树场景下的 Eisner 算法，以及非项目树场景下的最大生成树与 Chu-Liu-Edmonds；转移方法则通过 Arc-Standard、Arc-Eager、Arc-Swift 一类转移动作逐步构树。静态规范（Static Oracle）和动态规范（Dynamic Oracle）则属于训练策略，不应与解码算法混成一层。</p>
<p>深度学习阶段最有代表性的路线是<span style="background-color: #c0c0c0;">Biaffine Parser</span>。它用强编码器得到词表示，再通过 biaffine 打分 head-dependent 关系。到今天，预训练编码器 + biaffine head 依然是句法分析的强主线。导图里部分“Eisner / BiAffine / 生成式”关系有些混杂，实际应区分：Eisner 和 Chu-Liu-Edmonds 是解码算法，Biaffine 是神经打分头，二者解决的问题不同。</p>
<div class="blog_h4"><span class="graybg">图方法与转移方法各自擅长什么</span></div>
<p>图方法的优势在于全局最优视角。模型先为候选弧 <span displaypfx="inline-" class="mathjax-container">\((h,m)\)</span> 或带标签弧打分，再在全句范围内搜索满足树约束的最高分结构。这使它在整体一致性上通常更稳，也更容易与强打分头结合。转移方法则更像在线决策过程：解析器维护栈、缓冲区和部分弧集合，通过一连串动作逐步把句子变成依存树。它的优势在于速度和增量构树自然，特别适合流式或局部决策分析。</p>
<p>Arc-Standard、Arc-Eager、Arc-Swift 这些名称说的不是不同损失函数，而是不同的构树动作系统。Arc-Standard 更偏保守、构树动作整洁；Arc-Eager 允许更早地建立依存关系；Arc-Swift 进一步通过更大跨度动作减少决策步数。静态规范与动态规范则回答另一个问题：训练时，到底用“唯一标准动作序列”监督，还是允许模型在偏离正确路径后仍获得合理指导。把这些概念拆开，依存句法的结构才会清晰。</p>
<div class="blog_h4"><span class="graybg">Biaffine parser 为什么成为主线</span></div>
<p>Biaffine parser 的关键不在于“用了一个新层”，而在于它把依存弧打分写成了适合现代编码器的参数化双线性形式。编码器先为每个词产生上下文化表示，再把“作为 head 的表示”和“作为 dependent 的表示”分别投影，最后用 biaffine 分数建模它们之间的方向性关系。这样一来，句法分析的难点就被集中到两个地方：一是编码器是否真的学到了句子结构线索，二是解码算法能否把局部弧分数组装成合法整树。</p>
<p>这也是为什么“Eisner 很强”或“Biaffine 很强”这种说法都不完整。前者是结构约束下的搜索器，后者是局部打分器；真正高性能 parser 是二者与预训练编码器共同组成的系统，而不是某一个名字单独决定一切。</p>
<div class="blog_h3"><span class="graybg">句法分析在现代系统中的作用</span></div>
<p>虽然大模型可以在不显式输出句法树的情况下完成很多任务，但句法分析并没有失去价值。信息抽取会用依存结构缩短实体关系路径，文本纠错会用句法边界限制改写范围，语义角色标注和 AMR 解析也常把句法作为辅助约束。对于法律、医学、教育和语言学场景，显式句法树仍然提供了预训练模型隐表示难以直接替代的可审计结构。</p>
<p>因此，今天的句法分析更像一个<span style="background-color: #c0c0c0;">高价值结构化中间件</span>。它不一定每次都站在系统最前台，却仍然在需要可解释结构、精细边界控制和复杂语义映射的任务里发挥作用。</p>
<div class="blog_h2"><span class="graybg">语义分析</span></div>
<p>语义分析（Semantic Analysis）讨论的是：句子到底表达了什么意义，而不仅仅是它在词法或句法层面长什么样。导图把这一章拆成词义消歧、语义角色标注、语义依存分析、抽象语义表示、问句转 SQL，这种拆法很合理，因为它们正好对应从局部词义到全句结构语义，再到可执行结构表示的几条主线。</p>
<div class="blog_h3"><span class="graybg">词义消歧与语义角色标注</span></div>
<p>词义消歧（Word Sense Disambiguation, WSD）要求模型判断一个词在当前上下文里究竟是哪一个义项。传统监督法常用互信息、贝叶斯、最大熵；无监督方法包括 ShotgunWSD、MCS Estimation；知识库方法则依赖词典定义、Yarowsky 路线或结构树信息。深度学习阶段出现了 Att-BiLSTM、ELMo 等模型。今天 WSD 仍是一个真实问题，但它已不像早年那样作为独立核心 benchmark 被频繁单独强调，因为强预训练模型在很多下游任务里已经隐式承担了部分消歧工作。</p>
<p>语义角色标注（Semantic Role Labeling, SRL）则关注“谁对谁做了什么”。早期方法常依赖短语结构树、依存句法树或语块边界；现代方法更多使用预训练编码器、span 预测和联合谓词-论元学习。换句话说，语法信息仍然重要，但已经不再总是以手工管线方式显式注入。</p>
<div class="blog_h4"><span class="graybg">词义消歧的三种知识来源</span></div>
<p>WSD 的本质是把一个词面形式映射到正确义项，因此它天然会围绕三类信息展开。第一类是监督数据，也就是人工标注过的“这个上下文里是哪个 sense”；第二类是分布信息，即同一义项在什么上下文里更常出现；第三类是外部词典或知识库，把定义、同义词、上位词和例句作为先验。互信息、贝叶斯、最大熵、Yarowsky、ShotgunWSD 这些方法虽然分属不同年代，但都在尝试回答同一件事：上下文中哪些证据最能决定义项。</p>
<p>强预训练模型出现后，WSD 的讨论方式发生了变化。模型不一定显式输出 WordNet sense id，但在阅读理解、问答、检索和翻译中已经隐式完成了大量消歧工作。这也是为什么 WSD 在现代论文里不再像早期那样处于舞台中央，却依然是很多错误分析中的核心因素。</p>
<div class="blog_h4"><span class="graybg">语义角色标注如何连接语法与事件</span></div>
<p>SRL 的目标不是画出句法树，而是明确事件语义里的参与者结构。例如一个谓词对应的施事、受事、工具、地点和时间，往往比单纯主谓宾更贴近任务语义。早期系统严重依赖句法树，因为句法边界提供了天然候选论元；现代系统虽然可以用预训练编码器直接做 span 分类或 token 级角色标注，但语法与谓词框架知识仍然经常以隐式或显式方式进入模型。</p>
<p>也正因为如此，SRL 长期被视为连接“句法结构”和“事件语义”的中间层。问答、信息抽取、事件抽取和摘要若想真正理解谁做了什么，往往都会从 SRL 式表示中获益。</p>
<div class="blog_h3"><span class="graybg">语义依存、AMR 与问句转 SQL</span></div>
<p>语义依存分析（Semantic Dependency Parsing）试图构建比句法依存更贴近语义关系的图结构。导图里提到基于图的 BiAffine + BERT 和基于转移的 list-based Arc-Eager + Tree RNN，这恰好对应两大经典路线。当前主流仍然是强编码器配合图结构预测，传统转移式语义依存已明显不如图方法常见。</p>
<p>抽象语义表示（Abstract Meaning Representation, AMR）把句子语义编码成图。早期的 JAMR、CAMR、AMREager、Sequence-to-Graph Transducer 都是重要里程碑；而现在更强的路线已经扩展到 seq2seq AMR 解析器与 LLM 微调。旧方法不能说“没用”，但更适合理解 AMR 发展史，而不是视为今天唯一主干。</p>
<p>问句转 SQL（Text-to-SQL）则属于可执行语义解析。最早系统多用语义分析器和 Seq2Seq；后续出现了 schema-aware parser、RAT-SQL、PICARD 等更强系统；到 2025 到 2026 年，LLM-based Text-to-SQL 已经成为最活跃主线，重点转向 schema linking、约束解码、工具调用和数据库执行反馈。导图里只写 Seq2Seq 和语义分析器，历史上没错，但今天显然不够覆盖主流。</p>
<div class="blog_h4"><span class="graybg">语义依存与 AMR 的区别</span></div>
<p>语义依存分析和 AMR 都在追求“比句法更贴近意义的结构”，但它们不是同一种任务。语义依存通常仍紧贴原句 token，把语义关系建在词级节点之间；AMR 则进一步走向概念图表示，允许一个句子被抽象成与表面词序不完全同构的语义图。前者更像“在句法树上做语义增强”，后者更像“把句子翻译成概念图语言”。这也是为什么 AMR 解析往往更接近图生成或 seq2seq 任务，而语义依存更接近结构预测任务。</p>
<div class="blog_h4"><span class="graybg">Text-to-SQL 为什么是语义分析的硬任务</span></div>
<p>Text-to-SQL 之所以难，不只是因为要生成 SQL 语法，而是因为模型必须同时解决自然语言理解、数据库模式理解、字段对齐和执行约束。用户问“上季度销量最高的华东区域产品前三名”，系统不仅要理解聚合、排序、时间范围和地域过滤，还要知道这些语义应该落到数据库里哪些表和列上。这就是 schema linking 的核心难点。</p>
<p>Seq2Seq 是最早把问题端到端写成“自然语言到 SQL”的统一框架，但它对复杂 schema 的对齐能力有限。RAT-SQL 这类模型通过关系感知编码把库表结构显式纳入建模，PICARD 则把 SQL 语法约束直接并入解码过程，减少非法输出。到了 LLM 时代，模型在少样本泛化上更强，但执行一致性、表结构歧义和高风险 SQL 约束仍然需要工具化约束与数据库反馈闭环。</p>
<div class="blog_h3"><span class="graybg">语义分析在现代 NLP 里的位置</span></div>
<p>如果说词法分析解决的是“语言单位怎样切出来”，句法分析解决的是“结构怎样组织起来”，那么语义分析解决的就是“这些结构最终表达了什么”。它的任务边界天然最宽，因此也最容易被强预训练模型“部分吸收”。但被吸收不等于问题消失。WSD、SRL、AMR、Text-to-SQL、语义依存这些任务仍然在要求模型输出显式语义结构，而不是只在隐空间里“懂了”。</p>
<p>在工程实践里，语义分析经常出现在高价值接口层。知识问答需要把自然语言转成可执行查询，信息抽取需要把句中角色和事件结构抽出来，企业数据助手需要把口语问题映射成数据库操作，法律和医疗系统需要可审计的语义关系。这些场景共同说明：显式语义结构并没有因为 LLM 普及而过时，反而在高风险、高精度应用里变得更重要。</p>
<div class="blog_h2"><span class="graybg">文本聚类</span></div>
<p>文本聚类（Text Clustering）要求在没有明确标签的情况下，把语义相近的文本自动分到同一组。它本质上是无监督学习问题，因此关键不只是聚类算法本身，还包括文本如何表示。导图里提到“聚类算法”和“特征提取”两支，这是正确的，因为向量表示常常比具体聚类器更决定上限。</p>
<p>传统阶段，文本通常先做分词、停用词过滤、特征选择，再用词袋或 TF-IDF 表示，随后接 K-Means、重复二分法、DBSCAN、层次聚类。聚类数量常借助 Gap Statistic、平方误差和（WCSS）或轮廓系数（Silhouette）估计。今天这些方法仍然有用，尤其在轻量场景和解释性要求较高时。</p>
<p>现代路线则越来越多地使用句向量或文档向量，再在嵌入空间聚类。例如“embedding + UMAP + HDBSCAN + topic labeling”已经成为短文本发现主题的常见工作流。严格说这已经和主题模型发生了交叉，因此很多 2026 年的“文本聚类系统”本质上更像嵌入驱动的语义分群，而不是单纯稀疏向量上的 K-Means。</p>
<div class="blog_h3"><span class="graybg">表示往往比聚类器更重要</span></div>
<p>文本聚类里最关键的变化，是“难点从聚类算法本身前移到了表示学习”。在 TF-IDF 时代，K-Means、层次聚类和 DBSCAN 的差别固然重要，但更大限制通常来自稀疏词袋无法稳定表达同义改写、长距离语义相似和短文本语义压缩。进入嵌入时代后，只要文档向量本身足够好，很多看起来普通的聚类器也能得到比旧系统更合理的簇结构。</p>
<p>这也是为什么现代文本聚类讨论越来越频繁地和 embedding model 绑在一起。今天若只列 K-Means、DBSCAN、层次聚类而不讨论向量表示，往往已经抓不住真正决定效果的主因。</p>
<div class="blog_h4"><span class="graybg">K-Means、DBSCAN、层次聚类的差异</span></div>
<p>K-Means 假设簇大致呈球状，适合簇规模相近、聚类数可预设、向量空间较规整的场景；DBSCAN 更强调基于密度发现簇，不要求预先指定类别数，也更能识别噪声点，但对高维空间和参数敏感；层次聚类则保留了簇的多层结构，适合做探索式分析和可视化解释。没有任何一个聚类器能在所有文本几何结构下都占优，因此算法选择必须与表示空间形状一起考虑。</p>
<div class="blog_h3"><span class="graybg">嵌入聚类与 BERTopic 工作流</span></div>
<p>现代文本聚类里非常常见的一条工作流是：先用句向量模型把文本编码到稠密语义空间，再通过 UMAP 等降维方法整理局部几何结构，随后用 HDBSCAN 一类密度聚类器发现簇，最后再为每个簇生成关键词或标签。BERTopic 之所以流行，就是因为它把这条流程工程化了：前面用 embedding 捕捉语义相似，后面再用 c-TF-IDF 为每个簇生成可解释主题词。</p>
<p>这一流程说明，现代文本聚类不再只是“给每个点分个组”，而是同时追求<span style="background-color: #c0c0c0;">语义几何结构、噪声点处理和簇级解释</span>。也正因为如此，聚类与主题发现的边界越来越模糊，很多系统最终交付的其实是“簇 + 标签 + 代表文档”的完整探索结果。</p>
<div class="blog_h3"><span class="graybg">聚类结果如何评估</span></div>
<p>文本聚类的困难之一，在于它没有监督学习那样直接清晰的正确标签。轮廓系数、Davies-Bouldin、Calinski-Harabasz 这类内部指标可以评价簇内紧致度和簇间分离度，但它们未必等价于“语义上真有意义”。如果手头有少量人工标签，还可以用 ARI、NMI 等外部指标做辅助评估；若没有标签，很多时候必须结合人工审阅、代表样本查看和下游任务收益一起判断。</p>
<p>因此，聚类系统的交付往往不应该只有一个分数。更有价值的输出通常是：每个簇的规模、关键词、代表文本、离群点比例，以及这一聚类结果能否真正支持运营分析、主题发现、知识整理或数据清洗。</p>
<div class="blog_h3"><span class="graybg">文本聚类在系统中的作用</span></div>
<p>文本聚类常被用于工单归类探索、舆情主题发现、搜索日志归并、问答库清洗、异常文本发现和训练数据审计。它最大的价值并不总是“最终分类”，而是帮助人先看清数据里自然形成了哪些模式。因此，在很多真实系统里，文本聚类不是终点模型，而是建标签体系、做数据治理和发现长尾问题的上游分析工具。</p>
<p>进入 LLM 时代后，聚类之后再用大模型生成簇标签、总结簇差异和抽取代表问题，也成为越来越自然的工作流。此时，聚类负责组织结构，LLM 负责生成解释，两者结合往往比单独依赖其中一方更稳。</p>
<div class="blog_h4"><span class="graybg">文本聚类最容易出现的误判</span></div>
<p>文本聚类最常见的误判有两类。第一类是<span style="background-color: #c0c0c0;">表面词重合但语义不同</span>，例如都出现“退款”但一个在投诉物流、另一个在讨论账单系统；若表示学习不够强，系统会把它们错误合并。第二类是<span style="background-color: #c0c0c0;">语义相近但表面词差异大</span>，例如“登录不上去”“账号一直转圈”“验证码过期”其实都属于访问失败主题，但 TF-IDF 类表示可能把它们拆散。</p>
<p>还有一个经常被忽略的问题是簇规模不平衡。真实业务数据里往往存在一个超大通用簇和大量细小长尾簇，如果仍然机械使用固定 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 的 K-Means，就容易把长尾问题淹没进大簇中。因此，文本聚类的失败模式往往不是算法“算错了”，而是表示空间、聚类假设和业务目标本来就没有对齐。</p>
<div class="blog_h2"><span class="graybg">文本分类</span></div>
<p>文本分类（Text Classification）要求把文本映射到预定义标签空间 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{C}\)</span>，例如新闻主题、垃圾邮件、风险等级、工单类别、法务标签或用户意图。它是 NLP 里最基础、最常落地的任务之一，因为大量业务问题最后都能被写成“这段文本属于哪一类”。</p>
<p>传统流水线是：分词、停用词过滤、特征选择、词袋向量，再接线性分类器。导图里提到的卡方检验和互信息，是很典型的特征选择方法。这个路线今天并没有完全消失，在小数据、强解释、低资源和高吞吐场景里仍然实用。</p>
<p>深度学习阶段经历了 ULMFiT、BERT、XLNet 等代表模型；今天主流则是预训练编码器微调、参数高效微调（PEFT），以及在少样本条件下直接使用 instruction-tuned LLM 做 zero-shot / few-shot 分类。也就是说，BERT 和 XLNet 仍然是应知应会的主线，但若追求最新工程效果，通常还要把 LLM 分类器、检索增强分类或多标签约束一起纳入考虑。</p>
<div class="blog_h3"><span class="graybg">传统稀疏特征分类</span></div>
<p>传统文本分类之所以长期有效，是因为很多业务标签本来就和词项分布高度相关。垃圾邮件识别、主题分类、工单路由、舆情监控，这类任务即使用 TF-IDF + 线性模型也往往有很强基线。它的优势在于训练快、解释清楚、部署轻；劣势则是对同义改写、跨领域迁移和长上下文语义组合能力有限。</p>
<div class="blog_h3"><span class="graybg">预训练编码器与 LLM 分类</span></div>
<p>BERT、XLNet、ModernBERT、T5 等模型让文本分类从“词项匹配”升级成“上下文语义判别”。在高质量标注数据充足时，编码器微调通常仍然是吞吐和精度都很稳的方案；在标注数据很少、标签定义变化频繁时，LLM 的 zero-shot / few-shot 分类则更灵活。很多系统最终会结合两者：先用 LLM 帮忙定义标签边界或生成弱标注，再训练专用分类器承接高吞吐上线。</p>
<div class="blog_h2"><span class="graybg">情感分析</span></div>
<p>情感分析（Sentiment Analysis）本质上是文本分类的一个重要子集，但它关注的不是主题归属，而是文本的主观极性、态度和情绪取向，例如正面、负面、中性，或更细粒度的愤怒、喜悦、失望、讽刺等。</p>
<p>从技术上看，情感分析既可以直接复用文本分类框架，也可能比普通分类更难。原因在于情感经常依赖否定、反讽、上下文立场、领域词义漂移和目标对象。例如“这家餐厅排队两小时，菜倒是很普通”与“这家餐厅普通得让我想哭”在词面上都不激烈，但情绪极性不同。</p>
<p>因此，现代情感分析通常会区分句级情感、方面级情感（Aspect-based Sentiment Analysis, ABSA）和多模态情感。导图里把它视作文本分类子集是合理的，但若真正展开工程实践，它常常值得单独建模，而不是简单看作“普通分类任务换个标签名”。</p>
<div class="blog_h3"><span class="graybg">句级情感与方面级情感</span></div>
<p>句级情感分析回答的是“这段话整体偏正面还是负面”；方面级情感分析则更细，要求模型回答“用户对哪个方面持什么态度”。例如一条评论可能同时包含“物流很快，但客服态度很差”，此时整体情感并不等于对每个方面的情感。ABSA 之所以长期是独立方向，正是因为它需要同时建模目标对象与情绪极性。</p>
<div class="blog_h3"><span class="graybg">情感分析为什么经常比普通分类难</span></div>
<p>情感表达高度依赖语境、立场对象和修辞。否定、让步、反讽、双关和领域词义变化都会让简单关键词方法迅速失效。因此，现代情感系统往往不仅做分类，还会显式建模目标实体、方面词、情绪触发词和上下文关系。也正因为如此，它虽然可归入文本分类，但在实际工程里常常需要专门的数据设计与误差分析。</p>
<div class="blog_h2"><span class="graybg">文本摘要</span></div>
<p>文本摘要（Text Summarization）要求模型在尽量保留关键信息的前提下，把长文本压缩成更短、可读性更高的表示。它通常分为抽取式（Extractive）和生成式（Abstractive）两类：前者从原文中挑句子或片段，后者重新组织语言生成新摘要。</p>
<p>导图里的抽取式部分分得很完整：无监督的 TextRank、LexRank，监督学习的二分类、序列标注、回归排序，以及深度学习模型。这里的旧模型名里有一些已经明显不再是主流，例如某些早期 RNN 摘要器、GAN 摘要器和特定数据集专用网络，今天更多属于阶段性代表。真正仍有持续影响力的是“抽取式排序 / 句子选择”这一大思路本身。</p>
<p>生成式摘要的现代主线已经非常清晰：BART、PEGASUS、T5、LongT5、指令微调 LLM 与长上下文模型。GAN、KIGN、DCA 一类旧生成摘要器现在基本不是主流第一选择。若是新闻摘要、会议纪要、长文档摘要或多文档摘要，今天几乎都会优先考虑强 encoder-decoder 模型或长上下文 LLM，再配合事实一致性与引用校验。</p>
<div class="blog_h3"><span class="graybg">抽取式摘要</span></div>
<p>抽取式摘要的核心不是“重新写”，而是“选得准”。TextRank、LexRank 用图排序估计句子重要性，监督式方法则把句子选择写成排序或分类问题。它的优势在于事实忠实度通常更高，因为输出直接来自原文；缺点是可读性和压缩灵活性有限，尤其在需要跨句融合时更受约束。</p>
<div class="blog_h3"><span class="graybg">生成式摘要</span></div>
<p>生成式摘要追求的是重写能力：模型可以融合多句信息、压缩冗余并重组表达。代价是事实漂移风险更高，因此现代摘要系统往往会把事实一致性校验、引用对齐和长文切块策略放到与模型本身同等重要的位置。旧式 GAN 摘要器在今天已明显偏历史阶段，真正主流已经转向 BART / T5 / PEGASUS / 长上下文 LLM 这一线。</p>
<div class="blog_h3"><span class="graybg">Pointer-Generator 与长文档摘要</span></div>
<p>如果把生成式摘要的发展再补一块，Pointer-Generator Network 是很关键的过渡模型。它把“从词表生成新词”和“从原文复制关键信息”结合起来，显著缓解了早期生成式摘要中事实词汇漂移严重的问题。今天它已不是最强主线，但在摘要发展史上是从纯 Seq2Seq 走向更可控生成的重要桥梁。</p>
<p>到了长文档摘要阶段，问题又不只在生成能力，而在于上下文预算与文档结构利用。LongT5、分块摘要、层次摘要和长上下文 LLM 的出现，正是在回答“当输入不再是一篇短新闻，而是一整份报告、会议记录或多文档材料时，摘要系统该怎样组织证据”。</p>
<div class="blog_h2"><span class="graybg">主题模型</span></div>
<p>主题模型（Topic Modeling）试图在没有显式标签的前提下，发现文档集合中的潜在主题结构。经典问题不是“这篇文章属于哪一个给定类别”，而是“这些文档内部自然长出了哪些主题簇，它们各自由哪些高频语义词汇构成”。</p>
<p>概率模型主线包括潜在语义分析（Latent Semantic Analysis, LSA）和隐狄利克雷分配（Latent Dirichlet Allocation, LDA）。LSA 通过奇异值分解（SVD）在词-文档矩阵中寻找主方向；LDA 则用层次贝叶斯生成过程，把文档看作主题混合、把主题看作词分布，并常借助吉布斯采样或变分推断求解。LDA 到今天仍是最经典的可解释主题模型之一，但已经不再代表全部前沿。</p>
<p>深度学习阶段出现了自动编码器、受限玻尔兹曼机（RBM）、语义哈希等路线；而近年的强主线则明显转向<span style="background-color: #c0c0c0;">嵌入驱动主题建模</span>，例如 BERTopic 通过 Transformer embedding、聚类和 class-based TF-IDF 组织主题。也就是说，LSA / LDA 仍然是基础，BERTopic 和各种 neural topic model 则更贴近 2026 年的常用实践。</p>
<div class="blog_h3"><span class="graybg">概率主题模型</span></div>
<p>LSA 和 LDA 的价值主要在于可解释性和理论清晰度。LDA 尤其适合回答“一个文档由哪些主题混合而成、每个主题又由哪些词分布构成”这类问题，因此在社会科学、数字人文和需要审计的分析任务里仍然常见。它的问题在于对短文本、语义改写和深层语义相近但词面不同的文本不够敏感。</p>
<div class="blog_h3"><span class="graybg">嵌入驱动主题建模</span></div>
<p>BERTopic 一类方法把现代嵌入模型直接引入主题发现流程。文档先被编码到语义空间，再用聚类找到簇，最后用 c-TF-IDF 或关键词抽取给每个簇生成可解释标签。这种路线在短文本、跨领域语料和语义近义表达丰富的场景里更强，也正因为如此，它已经成为很多现代主题发现系统的默认起点。</p>
<div class="blog_h1"><span class="graybg">计算机视觉</span></div>
<p>计算机视觉（Computer Vision, CV）处理的是：如何把像素、视频帧和图像区域转成可计算的语义结构。它和 NLP 的差别，不只是输入模态不同，更在于视觉任务天然要处理<span style="background-color: #c0c0c0;">空间结构、局部纹理、尺度变化、遮挡关系和几何一致性</span>。因此，同样是“识别一个对象”，视觉里往往还要回答它在哪、轮廓是什么、动作是什么、与其他对象如何组合。</p>
<p>从任务视角看，视觉系统的输出粒度可以从粗到细展开：最粗的是整张图的类别，进一步是图中的目标框，再进一步是像素级分割、实例级分割、关键点和三维结构。也正因为如此，计算机视觉不是一个单一任务，而是一整组围绕“从视觉信号恢复结构化世界表示”的任务簇。</p>
<div class="blog_h2"><span class="graybg">图像分类</span></div>
<p>图像分类（Image Classification）要求模型判断整张图主要属于哪个类别。它是视觉中最基础的任务之一，因为它回答的是最粗粒度的语义问题：这张图里最重要的是什么。LeNet、AlexNet、VGG、ResNet、EfficientNet 到 Vision Transformer（ViT），都曾把图像分类作为核心 benchmark。</p>
<p>分类任务看似简单，实际上承担了视觉表示学习的“预训练入口”角色。大量检测、分割、检索和多模态系统，最早都建立在分类 backbone 上。到了基础模型阶段，图像分类不再只是终点任务，更像是视觉表示质量的最小检验：若连整图语义都难以稳定提取，更细粒度任务通常也很难做好。</p>
<p>从业务视角看，图像分类通常对应商品识别、质检分档、医学影像初筛、内容审核、遥感地物分类等“整图判断”任务。它的难点并不只在类别数目，还在于细粒度差异、类间相似、类内变化、多标签共存和开放集识别。也正因为如此，现代分类系统经常会区分单标签分类、多标签分类、细粒度分类和开放词汇分类，而不是把所有问题都压成一个 softmax。</p>
<p>在技术路线上，经典 CNN 通过局部卷积和层级感受野提取纹理到语义的递进表示，ViT 则把图像切成 patch 后用自注意力建模全局关系。到了开放词汇阶段，CLIP 一类视觉-语言预训练模型进一步把“图像分类”扩展成“图文对齐后的零样本识别”，这使分类任务从固定标签预测走向可迁移视觉语义理解。</p>
<div class="blog_h3"><span class="graybg">经典 backbone：从 CNN 到 ViT</span></div>
<p>图像分类最核心的技术演进，首先体现在特征提取骨干的变化上。LeNet、AlexNet、VGG 代表卷积网络早期阶段；ResNet 通过残差连接解决深层网络难训练问题，长期成为最重要的视觉 backbone 之一；EfficientNet 则强调在宽度、深度和分辨率之间联合缩放。进入 Transformer 阶段后，ViT、Swin Transformer 等模型把视觉 token 化处理，显著提高了大规模预训练的可扩展性。</p>
<p>今天若是做常规分类任务，CNN 并没有被彻底淘汰。中小数据集、边缘设备和低延迟场景里，卷积模型仍然有很强实用价值；而在大规模预训练、跨任务迁移和统一多模态建模中，Transformer 与视觉-语言编码器已经成为更强主线。</p>
<div class="blog_h3"><span class="graybg">细粒度、多标签与开放词汇分类</span></div>
<p>很多真实分类问题并不是“每张图只有一个互斥标签”。商品图可能同时包含品类、材质、风格和品牌；医学影像可能同时出现多种征象；遥感图像也可能包含多类地物。这就把问题从普通单标签分类扩展为多标签分类、层级分类和细粒度分类。</p>
<p>细粒度分类尤其依赖局部判别区域和部件级差异，例如鸟类品种、车型版本、工业缺陷等级。这里常见的技术包括注意力机制、局部区域对齐、度量学习和 part-based 建模。它们的目标不是把整图压成一个粗语义向量，而是显式利用“哪些局部细节区分了高度相似的类别”。</p>
<div class="blog_h3"><span class="graybg">2026 主线：视觉-语言预训练与开放世界识别</span></div>
<p>到 2026 年，图像分类最重要的变化已经不是再刷新固定封闭标签集上的 top-1，而是把分类能力嵌入更一般的视觉-语言表征中。SigLIP 2 一类视觉-语言编码器在 2025 年继续强化了零样本分类、检索以及对下游定位和稠密预测的迁移能力，说明现代分类 backbone 正在向统一视觉语义编码器收敛。传统只针对 ImageNet 风格封闭分类优化的模型仍然重要，但已不再代表全部前沿方向。</p>
<p>因此，这一任务今天更应理解为视觉基础表示的入口，而不是孤立终点。分类模型既服务于直接判别任务，也服务于检索、检测、分割和多模态理解等更复杂任务的初始化与特征抽取。</p>
<div class="blog_h2"><span class="graybg">目标检测</span></div>
<p>目标检测（Object Detection）要求模型同时回答两件事：图里有什么，以及它们各自在哪里。输出通常是边界框（Bounding Box）加类别标签，因此它比图像分类多了一层空间定位问题。Faster R-CNN、YOLO、SSD、RetinaNet 和 DETR 系列，分别代表了两阶段检测、单阶段检测和集合预测检测几条主线。</p>
<p>检测任务在工业中极其重要，因为很多真实系统并不满足“整张图只有一个核心对象”。安防、自动驾驶、零售盘点、工业检测、仓储机器人和内容审核，真正需要的往往是多目标定位而不是整图分类。检测系统的难点也集中在这里：小目标、密集目标、遮挡、长尾类别和实时延迟约束会同时出现。</p>
<p>从方法演进看，两阶段检测先生成候选区域，再做分类和框回归，精度通常较高；单阶段检测直接在特征图上输出类别和框，更强调速度；DETR 一类集合预测方法则把检测写成对象 query 到目标集合的匹配问题，减少了手工 anchor 设计。三条路线长期并存，本质上对应的是不同的精度、延迟和工程复杂度权衡。</p>
<p>检测系统的评估也比分类复杂得多。它不仅看类别是否正确，还看定位框是否足够贴近目标，因此会同时受到 IoU 阈值、召回率、误检率和延迟指标影响。很多业务里，漏掉一个小目标的代价远高于多报几个框，因此真正上线时往往要围绕类别阈值、NMS、误报成本和业务容忍度做细致调参。</p>
<div class="blog_h3"><span class="graybg">三条经典技术主线</span></div>
<p>两阶段检测以 Faster R-CNN 为代表，先提出候选区域，再在候选上做分类和框回归，长期是高精度场景的重要方案。单阶段检测以 YOLO、SSD、RetinaNet 为代表，直接在特征图上预测目标位置和类别，结构更适合实时部署。DETR 系列则把检测改写为集合预测问题，用 bipartite matching 统一训练和解码流程，显著减少手工 anchor 设计和启发式后处理。</p>
<p>这些路线都没有被彻底淘汰。实时视频监控、边缘设备和工业相机仍然大量使用 YOLO 系谱；强调极致精度的离线分析系统依然偏好更重的高精度检测器；而 DETR 路线在统一建模和与多模态接口兼容方面具有更好扩展性。</p>
<div class="blog_h3"><span class="graybg">开放词汇检测与 grounding</span></div>
<p>检测领域这几年的关键变化，是任务正从封闭标签检测走向开放词汇检测（Open-Vocabulary Detection）和文本驱动 grounding。用户不再只能让系统识别预定义的几十或几百类，而是可以直接用自然语言描述目标，例如“穿红衣服的人”“灭火器”“桌上的蓝色马克杯”。</p>
<p>这一方向的代表工作包括 OWLv2、Grounding DINO 以及后续将视觉-语言模型知识迁移到检测头的路线。它们共同利用图文预训练带来的开放语义空间，使检测器不再完全依赖人工框标注类别表。对于长尾类别、机器人感知和通用视觉助手，这条线已经成为 2026 年最重要的检测前沿之一。</p>
<div class="blog_h3"><span class="graybg">2026 主线：从闭集检测器到通用定位接口</span></div>
<p>当前检测系统越来越像“通用定位接口”。一方面，开放词汇检测通过视觉-语言预训练显著扩展了类别空间；另一方面，统一视觉基础模型正在让检测与 grounding、分割、跟踪和问答共用更接近的表示。闭集检测器仍然在高可靠工业任务中占据主流，但从研究与平台能力建设的角度看，检测已经不再只是框回归问题，而是在向“语言可指定、跨任务可迁移的通用视觉定位”演进。</p>
<div class="blog_h2"><span class="graybg">语义分割</span></div>
<p>语义分割（Semantic Segmentation）要求对每个像素赋予语义类别，也就是把“这是什么”细化为“图上每个位置分别属于什么语义区域”。它的输出不是几个框，而是一张像素级标签图。FCN、U-Net、DeepLab 和后来的 SegFormer 等模型，都是这一方向的重要代表。</p>
<p>分割之所以重要，是因为很多任务需要的不只是粗框，而是区域级理解。医学影像要求精确勾出病灶区域，遥感要求识别道路、水体和建筑边界，自动驾驶要求区分车道、路缘、行人区域。只做检测，很多边界与面积信息会丢失；做语义分割，系统才真正具备了像素级空间理解能力。</p>
<p>语义分割的核心难点在于同时兼顾局部边界和全局语义。模型既要识别大范围上下文，知道一块区域为什么属于道路而不是阴影，又要保住细边界，不把车道线、病灶边缘和文本轮廓抹平。因此 encoder-decoder、空洞卷积、多尺度特征融合和跳跃连接会长期出现在分割模型里。</p>
<p>从分辨率操作的角度看，分割几乎就是“下采样与上采样如何配合”的代表任务。编码器一侧不断下采样，把原图压成更小但语义更强的特征图；解码器一侧再逐步上采样，把粗语义恢复到像素级输出网格。若只有下采样，模型会失去边界细节；若只有上采样而缺少浅层跳跃连接，输出往往会显得轮廓模糊、边缘发虚。U-Net、FPN 和各类 decoder head 长期有效，本质上都在解决这个矛盾。</p>
<p>从业务形态看，分割任务常常不是“只有一个统一标签图”这么简单。很多系统还要进一步输出面积占比、轮廓长度、区域拓扑或边界置信度，因为真正的下游问题通常是测量、监控和决策，而不仅仅是把像素染色。也正因为如此，分割系统在工程上常与后处理、连通域分析和几何约束一起部署。</p>
<div class="blog_h3"><span class="graybg">经典分割架构：FCN、U-Net、DeepLab</span></div>
<p>FCN 首次系统性地把分类 CNN 改造成像素级预测网络，奠定了现代语义分割的基本框架。U-Net 强调编码器与解码器之间的跳跃连接，在医学影像等数据量较小、边界极重要的任务中影响尤其深远。DeepLab 系列则通过空洞卷积与多尺度上下文模块强化大感受野建模，长期是高质量语义分割的重要主线。</p>
<p>这些模型今天依然值得保留，因为它们不是简单的历史名词，而是三种非常稳定的设计思想：全卷积密集预测、编码器-解码器重建细节、多尺度上下文聚合。后续很多 Transformer 分割模型，实际上也仍在复用这些思想，只是把编码器与全局建模机制换成了新的骨架。</p>
<div class="blog_h3"><span class="graybg">Promptable segmentation 与 foundation model</span></div>
<p>分割方向在 2023 年以后出现了明显结构变化。SAM 把“给定点、框或粗提示，分割出目标”做成了通用接口；SAM 2 又把这件事扩展到了图像与视频统一的 promptable segmentation，并通过流式记忆机制支持实时视频分割。到 2025 至 2026 年，SAM 3 一类工作开始把任务进一步推进到 concept-level segmentation，即不只是响应点和框，还能响应短语和概念描述。</p>
<p>这意味着，语义分割的前沿不再只是在固定标签集上追求 mIoU，而是在向“可提示、可泛化、可与语言接口对接”的方向移动。传统监督分割仍然是自动驾驶、遥感和医疗的主力，但 foundation model 正在重新定义分割任务的接口形式。</p>
<div class="blog_h3"><span class="graybg">2026 主线：监督分割仍强，通用分割接口正在形成</span></div>
<p>到 2026 年，最合理的判断不是“传统分割被 SAM 取代”，而是分割出现了双主线。业务里对固定类别、严格标注和高可靠边界要求极高的任务，仍然主要依赖专门监督模型；而交互式标注、跨域迁移、开放概念分割和下游多模态系统，则越来越依赖 promptable segmentation 与视觉基础模型。两条路线会并存很长时间。</p>
<div class="blog_h2"><span class="graybg">实例分割与全景理解</span></div>
<p>语义分割能告诉系统“哪些像素属于人”，但无法区分“这是第一个人还是第二个人”。实例分割（Instance Segmentation）正是在此基础上继续细化：它要求系统为每个对象实例分别给出独立掩码。Mask R-CNN 是这条主线最经典的代表之一。</p>
<p>再往前一步，全景分割（Panoptic Segmentation）试图把语义分割和实例分割统一起来：既要标出可数目标的实例，也要标出天空、道路、墙面这类不可数背景区域。它更接近机器最终需要的场景理解形式，因为真实世界本来就同时包含“一个个对象”和“连续背景材质”。</p>
<p>实例分割的业务场景通常集中在“需要知道每个对象边界且对象彼此相邻”的问题上，例如工业零件计数、仓储抓取、细胞分析、零售货架盘点和自动驾驶障碍物理解。这里检测框通常不够，因为框会把相邻实例混在一起；语义分割也不够，因为它不区分具体个体。实例级掩码正是为了补上这个粒度缺口。</p>
<p>全景理解进一步强调“一个场景应由对象与背景共同组成统一表示”。这让视觉系统更接近机器人和自动驾驶真正需要的世界模型，因为决策系统既关心“前方有几辆车”，也关心“当前位置是不是可通行区域”。从这个角度看，全景任务不是简单把两个 benchmark 拼起来，而是在逼近更完整的场景语义表达。</p>
<div class="blog_h3"><span class="graybg">实例分割：检测头上长出 mask</span></div>
<p>实例分割早期最有代表性的路线，是在检测器基础上增加掩码预测分支。Mask R-CNN 正是这一思路的经典实现：先定位对象，再在 RoI 内预测像素级掩码。这种设计的好处是充分继承成熟检测器的定位能力，因此在实例级边界抽取上非常稳定。</p>
<p>后续也出现了 query-based 实例分割与 one-stage 掩码方法，使实例分割逐渐从“检测的附属任务”走向更统一的目标集合预测框架。但无论结构如何变化，核心问题始终不变：同类目标必须被分开，且边界要足够精细到能支撑下游计数、抓取和测量。</p>
<div class="blog_h3"><span class="graybg">全景分割与统一场景表示</span></div>
<p>全景分割要求模型同时处理 things 与 stuff 两类视觉实体。things 指可数对象，例如人、车、箱子；stuff 指不可数背景区域，例如道路、天空、草地。把这两类输出统一到一张场景图里，是场景理解比普通分割更进一步的地方。</p>
<p>这类任务对于自动驾驶、机器人和数字孪生系统尤其关键，因为它们的决策依赖既包括“对象实例”，也包括“环境区域”。因此，全景理解不仅是一个 benchmark，而是在逼近下游系统真正使用的环境表征形式。</p>
<div class="blog_h3"><span class="graybg">2026 主线：开放词汇与通用掩码预测</span></div>
<p>实例与全景方向当前也在向开放词汇和 promptable mask prediction 收敛。Open-Vocabulary SAM、基于 CLIP/SAM 组合的开放概念实例分割，以及更通用的 VLM-based detection and segmentation 路线，都在说明：实例分割不再局限于封闭标签表，而是开始与开放语义空间打通。对需要长尾对象识别的真实系统而言，这一变化比单纯再提升几个 benchmark 点数更重要。</p>
<div class="blog_h2"><span class="graybg">关键点检测与姿态估计</span></div>
<p>关键点检测（Keypoint Detection）与姿态估计（Pose Estimation）要求模型输出对象内部的结构点，例如人体关节、面部特征点、手部骨架或工业零件的定位基准点。它关心的不是“一个人在哪”，而是“这个人的身体结构如何展开”。</p>
<p>这类任务广泛出现在动作分析、手势交互、AR/VR、运动捕捉、驾驶员监测和人机交互系统中。其难点在于，模型必须处理视角变化、遮挡、多人交叠和快速运动；输出形式也常从单帧关键点扩展到时序姿态轨迹，因此它天然和视频理解、跟踪以及三维重建相连。</p>
<p>从方法上看，关键点估计常分为 top-down 和 bottom-up 两类。前者先检测对象，再在每个对象内部预测关键点，精度通常更高；后者直接在整图上定位关键点并做分组，更适合多人密集场景。进一步往三维姿态和多视角重建发展后，问题又会从二维热力图预测扩展到几何约束和跨相机一致性。</p>
<p>这类任务的系统价值在于，它输出的是结构化运动信息而不是静态类别标签。一个动作识别系统若能显式利用骨架轨迹，就更容易理解“举手”“跌倒”“挥拍”这类与姿态变化强相关的事件。因此，姿态估计常被当作上游中间表示，为行为分析、康复评估、体育分析和机器人模仿学习服务。</p>
<div class="blog_h3"><span class="graybg">二维姿态：top-down 与 bottom-up</span></div>
<p>二维人体姿态估计长期围绕两条路线展开。top-down 先检测人框，再在框内预测关键点，精度高但计算成本随人数增长；bottom-up 直接在整图上预测关键点和连接关系，对多人密集场景更友好。两者并无绝对替代关系，而是服务于不同密度与实时性约束。</p>
<p>关键点回归本身也有两类常见范式：直接坐标回归和热力图回归。热力图方法通过空间概率分布表达不确定性，长期更稳定；直接回归则在轻量模型和端侧部署里更有吸引力。</p>
<div class="blog_h3"><span class="graybg">三维姿态、手部与面部关键点</span></div>
<p>一旦任务从二维扩展到三维，问题就不再只是“点落在哪个像素”，而是“这个结构在空间里如何展开”。三维姿态需要结合相机几何、多视角一致性或人体先验约束；手部与面部关键点则更强调局部高精度和细粒度遮挡处理。这些任务常常共享骨干网络，但损失函数、后处理与先验建模差异很大。</p>
<p>也正因为如此，姿态估计不是一个单独小分支，而是连接视觉理解、动作建模、AR 交互和三维重建的重要桥梁任务。</p>
<div class="blog_h3"><span class="graybg">2026 主线：关键点是中间表示，不只是终端输出</span></div>
<p>到 2026 年，关键点和姿态估计越来越多地作为更大系统的中间表示使用。例如动作分析、手势控制、体育分析和机器人模仿学习，往往不是最终关心关键点本身，而是关心由关键点轨迹编码出的结构化运动模式。因此，这个方向的价值正在从“单独 benchmark”转向“为下游时空理解提供可解释结构”。</p>
<div class="blog_h2"><span class="graybg">OCR 与文档视觉</span></div>
<p>OCR（Optical Character Recognition）研究的是：如何从图像或扫描文档中检测文本区域并识别字符内容。传统 OCR 曾高度依赖版面规则、字符切分和语言后处理；深度学习阶段则逐步收敛到“文本检测 + 文本识别 + 版面理解”的组合系统。</p>
<p>今天的文档视觉早已不止是“把图片里的字读出来”。发票解析、表单理解、版面分析、图文混排文档检索、票据审计和知识入库，都要求系统同时理解文字内容、阅读顺序、表格结构和视觉布局。也正因为如此，OCR 现在通常更适合放在“文档智能（Document Intelligence）”这条更宽的任务线上理解。</p>
<p>从任务分解看，文档视觉通常包含文字检测、文字识别、版面分析、表格结构恢复、字段抽取和文档问答等子问题。很多错误并不来自字符本身认不出来，而是来自阅读顺序错乱、跨栏合并失败、表格单元格边界识别不准，或者字段和值没有正确对齐。因此，文档视觉的关键不是孤立字符识别，而是二维布局与语义结构的联合建模。</p>
<p>现代文档模型也已经从“先 OCR，再单独做 NLP”逐渐走向联合编码。LayoutLM、Donut 以及更广义的文档多模态模型，都会把文本内容、位置坐标和视觉 patch 一起输入模型，使系统直接学习“这个词出现在什么区域、与哪些框相邻、在版面上承担什么角色”。这条路线尤其适合票据理解、合同解析和复杂表单抽取。</p>
<div class="blog_h3"><span class="graybg">OCR 三层任务：检测、识别、结构理解</span></div>
<p>把 OCR 讲清楚，至少要分三层。第一层是文本检测，解决“文字在哪”；第二层是文本识别，解决“写了什么”；第三层是结构理解，解决“这些文字在文档中如何组织”。很多系统只把前两层叫 OCR，但在现代业务里，第三层往往才决定系统是否真正可用。</p>
<p>例如表格解析、表单抽取和合同理解，核心难点并不只是字符识别，而是单元格之间、标题与字段之间、段落与图表之间的关系建模。也正因为如此，文档视觉必须显式利用二维布局与阅读顺序。</p>
<div class="blog_h3"><span class="graybg">从 pipeline 到端到端文档 VLM</span></div>
<p>传统 OCR pipeline 把检测、识别、版面分析和信息抽取分成多个模块，优点是可解释、可替换、可局部优化；缺点是误差会在模块之间层层传递。2024 至 2026 年，文档智能明显转向端到端 Vision-Language Model。Florence-2 展示了统一 prompt-based 视觉任务接口；Ocean-OCR、PaddleOCR-VL 等模型则进一步表明，文档解析可以在一个较统一的视觉-语言框架中同时处理文本、表格、公式和图表。</p>
<p>这条线的价值在于，它不再把文档理解视为“先 OCR，再做 NLP”的串行流水线，而是直接把页面理解成多模态结构预测问题。</p>
<div class="blog_h3"><span class="graybg">2026 主线：文档智能正在快速统一</span></div>
<p>到 2026 年，文档视觉最明显的趋势是统一化和多语言化。PaddleOCR-VL 把文档解析压缩到一个相对紧凑的 VLM 中，并覆盖上百种语言与复杂元素；Ocean-OCR 则强调 MLLM 也可以具备专业 OCR 能力。纯规则版面系统和传统 OCR 仍然在很多生产场景中长期存在，但前沿已经明显从“单点字符识别最优”转向“端到端文档理解最优”。</p>
<div class="blog_h2"><span class="graybg">人脸识别与生物特征验证</span></div>
<p>人脸识别（Face Recognition）和生物特征验证（Biometric Verification）关注的不是通用类别，而是身份一致性。它们更常把输入映射到嵌入空间（Embedding Space），再通过距离判断“是不是同一个人”。FaceNet、ArcFace 一类方法的核心价值，就是把“看起来像不像”转成可度量的向量相似度。</p>
<p>这类系统的工程要求通常比普通分类更苛刻，因为它面对的是开放集（Open-set）识别：系统不只是要在固定几类中选一个，而是要应对大量从未见过的身份、跨设备成像差异、伪造攻击和极低误识率约束。手机终端中的 Face ID、本地门禁、支付风控和公共安全，都属于这一任务线的典型落地场景。</p>
<p>人脸任务通常至少区分验证、识别、检索和聚类四种形式。验证回答“两张脸是否同一人”，识别回答“这是谁”，检索回答“库里最像的是谁”，聚类则回答“哪些样本应归为同一身份”。它们共享嵌入表示，但阈值设置、误报代价和评估协议并不相同，因此不能被简单视为同一个分类问题。</p>
<p>现代生物特征系统还必须显式处理活体检测、呈现攻击和隐私合规。也就是说，真正的系统目标不是把相似脸分开这么简单，而是在低误识率、低拒识率和高安全性之间维持平衡。对于终端解锁、支付认证和门禁系统而言，伪造攻击防御往往和识别精度同等重要。</p>
<div class="blog_h3"><span class="graybg">核心范式：embedding、margin loss 与开放集验证</span></div>
<p>人脸识别最关键的技术范式，是把人脸映射到判别性嵌入空间，再用距离或相似度进行验证和检索。FaceNet 用 triplet loss 推动同人更近、异人更远；ArcFace、CosFace 等 margin-based loss 则进一步强化了类间可分性，长期成为现代人脸系统的核心训练范式。</p>
<p>这与普通闭集分类有本质不同。人脸系统最终面对的是开放集身份空间，因此训练阶段的分类器通常只是学习判别 embedding 的工具，而不是上线时真正的输出接口。</p>
<div class="blog_h3"><span class="graybg">安全链路：活体检测、反欺骗与校准</span></div>
<p>生物特征验证从来不是“相似度大于阈值”这么简单。真实系统必须同时处理照片翻拍、视频回放、面具攻击、跨设备成像差异和环境光变化。于是，活体检测、呈现攻击检测、分布外样本拒识和阈值校准都会成为完整链路的一部分。</p>
<p>从工程意义看，一个极高精度但无法抵御攻击的人脸模型并不真正可用。安全性、误受理率和误拒识率共同定义了人脸系统的质量。</p>
<div class="blog_h3"><span class="graybg">2026 主线：foundation model 可迁移，但任务专用适配仍然关键</span></div>
<p>近两年这一方向的一个重要发现是，foundation model 并不会自动在 face recognition 上直接最优。FRoundation 的结论显示，通用 foundation model 原始状态往往不如专门训练的人脸模型，但在数据有限场景下，通过适当 fine-tuning 可以取得很有竞争力的结果。这说明人脸识别虽然受益于大规模预训练，但任务专用数据分布、损失设计和安全约束仍然不可替代。</p>
<p>因此，2026 年的人脸识别主线不是“完全交给通用视觉基础模型”，而是“以 foundation model 为起点，再做任务专用适配、安全增强与阈值校准”。</p>
<div class="blog_h2"><span class="graybg">视频理解与行为识别</span></div>
<p>视频理解（Video Understanding）把视觉任务从静态图像推进到时序视觉信号。它研究的是：一段视频里发生了什么，哪些对象在如何运动，动作何时开始和结束，事件之间怎样衔接。动作识别（Action Recognition）、时序定位、视频问答、视频检索和多目标跟踪，都是这一方向的重要子任务。</p>
<p>与图像相比，视频多了一条时间轴，因此模型不仅要理解空间结构，还要处理运动模式与跨帧一致性。早期方法用双流网络、3D CNN、时序池化等方式捕捉动态信息；后续主线逐步转向视频 Transformer、时空注意力和视频基础模型。视频任务的难点通常来自长时依赖、帧冗余、计算成本和标注昂贵。</p>
<p>从业务角度看，视频理解既包括“整段视频是什么行为”这类粗粒度任务，也包括“异常事件何时开始”“某个动作在哪一秒出现”“一个对象在多帧中如何持续跟踪”这类细粒度任务。安防巡检、赛事分析、交通监控、内容审核和短视频理解，对时间粒度的要求完全不同，因此模型设计也会在 clip-level 表示与 frame-level 定位之间切换。</p>
<p>视频基础模型的出现，使这条线越来越接近多模态理解。系统不再只预测动作标签，还会回答视频问答、生成描述、根据文本检索视频片段，甚至结合音频和字幕做统一事件理解。也正因为如此，视频理解正在从“纯视觉动作分类”扩展成“时空多模态语义建模”。</p>
<div class="blog_h3"><span class="graybg">经典路线：双流、3D CNN 与时空 Transformer</span></div>
<p>视频理解最早的重要路线，是通过双流网络分别建模 RGB 外观和光流运动信息；随后 3D CNN 把时间维直接并入卷积核，形成统一时空特征提取。进入 Transformer 阶段后，模型逐渐转向时空 token 和注意力机制，能更灵活处理长时依赖和跨帧关系。</p>
<p>这些路线的差别，不只是网络结构不同，而是对“视频中什么最重要”的假设不同。双流强调显式运动线索，3D CNN 强调局部时空模式，Transformer 则更强调长距离依赖与统一建模能力。</p>
<div class="blog_h3"><span class="graybg">任务拆分：动作识别、时序定位、视频问答</span></div>
<p>视频理解不是单一 benchmark。动作识别关注整段 clip 的主行为标签；时序动作定位关注行为开始和结束时间；视频问答则要求把视频内容转成可推理的语义表示；视频检索与视频字幕生成又分别强调检索匹配和自然语言生成。因此，一个“视频模型”并不能自动覆盖所有子任务。</p>
<p>理解这一点很重要，因为不同任务需要的时间粒度完全不同。短动作分类可能只需几十帧；长视频事件理解则可能需要跨分钟甚至更长时间的多模态上下文。</p>
<div class="blog_h3"><span class="graybg">2026 主线：video foundation model 与多模态统一理解</span></div>
<p>到 2026 年，视频理解最重要的研究变化，是从“专门视频架构”转向“视频基础模型与多模态统一理解”。近期综述表明，视频 foundation model 已覆盖十余类视频任务，而一个值得注意的结论是：纯图像 foundation model 经过适配后，在不少视频任务上依然很强；更进一步，融合图像、视频、音频和文本的 universal model 在视频任务上往往更有优势。这说明视频理解的真正上限，越来越依赖跨模态统一建模，而不只是视频专用编码器本身。</p>
<div class="blog_h2"><span class="graybg">图像生成与视觉编辑</span></div>
<p>视觉生成任务不再问“图里是什么”，而是问“能否生成一张符合条件的新图”。它包括图像生成、图像修复、超分辨率、风格迁移、图像到图像翻译和受控编辑。GAN 曾长期是这条线的主角，随后扩散模型（Diffusion Model）把高质量、可控生成推到新的主线位置。</p>
<p>在系统层面，视觉生成的价值不只在创作，还在于数据增强、设计辅助、内容编辑、工业缺陷模拟和交互式视觉工具。它与传统视觉理解任务的关系也越来越紧：现代视觉系统往往同时需要“看懂图像”和“按条件修改图像”，这正是视觉基础模型不断走向统一接口的重要原因。</p>
<p>这类任务内部也有明显分工。无条件生成追求逼真样本分布，条件生成强调按文本、草图、分割图或参考图控制输出，图像编辑则要求在“保留原图主体结构”和“精确修改局部属性”之间取得平衡。ControlNet、Inpainting、Image-to-Image、超分辨率和局部重绘，本质上都在回答“怎样把生成能力约束到用户真正需要的方向上”。</p>
<p>从业务落地看，视觉生成已经不只是内容创作工具。商品图生成、广告素材改写、设计草图上色、工业仿真数据合成、医学图像增强和隐私保护合成数据，都会使用生成模型。但这条线也天然伴随可控性、版权、水印、事实一致性和安全审核问题，因此真正可上线的生成系统通常需要严格的内容约束与后处理机制。</p>
<div class="blog_h3"><span class="graybg">技术演进：GAN 到 diffusion，再到 DiT</span></div>
<p>GAN 阶段的核心贡献，是第一次把高保真图像生成推到可用水平，并带动了图像翻译、超分辨率和风格迁移等大量任务。扩散模型随后成为主线，因为它在样本质量、训练稳定性和条件控制方面展现出更强优势。再往后，Diffusion Transformer（DiT）把扩散生成与 Transformer 骨架结合，使大规模预训练与统一多模态接口更自然地接轨。</p>
<p>因此，GAN 在今天更接近历史里程碑和部分特定场景工具，而 diffusion / DiT 已经成为生成与编辑的默认主干。若只把视觉生成理解成 GAN，就已经落后于当前主线。</p>
<div class="blog_h3"><span class="graybg">编辑比生成更难：控制、保持与一致性</span></div>
<p>图像编辑任务看似只是“生成模型 + 指令”，实际上比无条件生成更难。系统必须同时满足三件事：正确理解编辑指令、只修改该修改的局部、保持人物身份与场景结构不被无关破坏。也正因为如此，视觉编辑长期围绕 inpainting、mask-guided editing、ControlNet、reference-guided editing 和 instruction-based editing 展开。</p>
<p>一个编辑模型是否优秀，关键不只是生成好不好看，而是编辑是否精准、保真是否稳定、多轮编辑是否可控。这些要求比单次文本到图像生成严格得多。</p>
<div class="blog_h3"><span class="graybg">2026 主线：instruction editing 与大规模 DiT</span></div>
<p>到 2026 年，视觉生成的前沿重点已经明显转向“高质量基础生成模型之上的可控编辑”。2024 至 2025 年的综述表明，扩散编辑已形成完整技术谱系；而 2025 年的 In-Context Edit 则进一步展示，大规模 DiT 可以通过 in-context editing 和极少参数更新，在指令编辑上取得很强效果。这意味着当前竞争焦点已不只是能不能生成，而是怎样以更少数据、更少参数、更高控制精度完成编辑。</p>
<p>在工程层面，这也解释了为什么生成模型越来越像通用视觉接口的一部分：它们不仅负责“造图”，还要负责按自然语言、草图、参考图和结构约束进行交互式视觉操作。</p>
<div class="blog_h1"><span class="graybg">语音和音频处理</span></div>
<p>语音和音频处理研究的是：如何从连续波形中恢复语言内容、说话人信息、环境事件和声学结构。它和 NLP 的关系很紧，但本体并不属于 NLP，因为这里首先要处理的是声学信号本身，而不是离散 token 序列。语音系统真正面对的输入是采样点、频谱和时频结构，因此它天然要解决噪声、混响、口音、重叠说话和实时延迟等问题。</p>
<p>从任务角度看，这一方向可以分为语言内容恢复、说话人建模、声学净化、事件识别和声音生成几条主线。它们共享的底层问题是：连续信号怎样被编码成更高层的结构表示；但最终输出可以是文字、声纹向量、增强后的波形、事件标签或新的合成音频。</p>
<p>在这个方向里，上采样与下采样首先表现为采样率（Sampling Rate）和时间分辨率的变化。原始波形可能从 <span displaypfx="inline-" class="mathjax-container">\(48\,\text{kHz}\)</span> 降到 <span displaypfx="inline-" class="mathjax-container">\(16\,\text{kHz}\)</span> 以降低计算量，也可能在声码器（Vocoder）阶段把较粗的声学表示重新上采样为高分辨率波形。进一步看，卷积前端、梅尔频谱（Mel Spectrogram）和 Conformer 编码器也常沿时间轴做下采样，把长音频压缩成更短的帧级表示；语音合成与音频生成系统则经常需要沿时间轴逐级上采样，把低帧率表示还原成连续可播放的声音。</p>
<div class="blog_h2"><span class="graybg">语音识别</span></div>
<p>语音识别（Automatic Speech Recognition, ASR）研究的是：如何把连续语音信号转写为离散文字序列。它在系统角色上和 NLP 强相关，因为很多对话、语音助手、会议纪要和多模态系统，都会先把语音变成文本，再交给后续 NLP 模块处理。</p>
<div class="blog_h3"><span class="graybg">传统 ASR</span></div>
<p>传统 ASR 往往由声学模型、发音词典和语言模型构成。前端会用傅里叶变换、倒谱均值归一化（CMVN）、声道长度归一化（VTLN）等技术提取和校正声学特征；后端则常用 HMM 和 n 元语法做解码。这条路线今天不再是最热前沿，但在工业和教学中仍非常重要，因为它清楚展示了声学、发音和语言约束如何组合。</p>
<div class="blog_h3"><span class="graybg">深度学习与基础模型时代</span></div>
<p>深度学习阶段先经历 CNN、LSTM-HMM、神经网络语言模型，再逐渐过渡到端到端 ASR。当前主线包括 CTC、Attention、RNN-T、Conformer、自监督预训练（如 wav2vec 2.0）以及大规模弱监督语音模型（如 Whisper）。Conformer 和 RNN-T 在实时 ASR 中非常强，Whisper 则代表了“超大规模弱监督、多语言、零样本鲁棒转写”的基础模型方向。</p>
<p>因此，导图里的 CNN、LSTM-HMM、神经网络语言模型都应该保留，因为它们是历史关键节点；但若要说明今天真正主流，就必须把<span style="background-color: #c0c0c0;">端到端 Conformer / RNN-T、预训练声学表示和 Whisper 类基础模型</span>补进来。传统发音词典在纯端到端系统里不再总是必需，但在高精度行业语音和混合系统中仍然经常保留。</p>
<div class="blog_h3"><span class="graybg">CTC、Attention 与 Transducer 的分工</span></div>
<p>端到端 ASR 并不是单一做法。CTC（Connectionist Temporal Classification）擅长在未显式对齐的条件下学习声学到文本的对齐关系，结构简洁、训练稳定；Attention Encoder-Decoder 更强调直接条件生成，在离线高质量转写中很强；RNN-T 和其后续变体则更适合流式实时识别，因为它们能够边听边输出。理解这三条线的差别，有助于看清为什么实时语音助手、会议转写和离线高精度转录常常不会使用同一套 ASR 架构。</p>
<div class="blog_h3"><span class="graybg">自监督声学表示与基础模型</span></div>
<p>wav2vec 2.0、HuBERT、后续 Conformer 化声学编码器，以及 Whisper 这一类大规模弱监督模型，共同推动了 ASR 从“任务专用声学模型”走向“可迁移语音基础模型”。前者强调先学通用声学表示，再在少量标注数据上微调；后者则进一步把海量弱标注语音直接并入训练，使模型具备更强的多语言和噪声鲁棒性。</p>
<div class="blog_h4"><span class="graybg">传统 ASR 三件套各自负责什么</span></div>
<p>传统 ASR 的声学模型、发音词典和语言模型并不是三个松散部件，而是明确分工的系统结构。声学模型负责把连续声学特征映射成更接近音素或状态的概率；发音词典负责把单词拆成音素序列，承担“声音如何对应词”的桥梁；语言模型负责在候选词序列里偏向更自然、更可能的组合。只有三者联合起来，系统才能把含噪连续波形稳定转写成文本。这也是为什么传统 ASR 的可解释性很强，错误能清晰地归因到声学混淆、发音歧义或语言先验不足。</p>
<div class="blog_h4"><span class="graybg">从混合系统到端到端系统</span></div>
<p>LSTM-HMM、CNN-HMM 这类混合系统处在一个很关键的过渡位置。它们保留了 HMM 解码框架，但用神经网络替代传统 GMM 声学建模，从而显著提升了表示能力。再往后，CTC、Attention Encoder-Decoder 和 Transducer 才真正把“声学建模、对齐和文本生成”逐步并入统一神经模型。这个演进过程说明，端到端 ASR 并不是突然出现的，而是在混合系统长期积累之上完成的架构收敛。</p>
<div class="blog_h4"><span class="graybg">流式识别与离线识别</span></div>
<p>ASR 在工程上必须区分流式（Streaming）与离线（Offline）两类场景。语音助手、实时字幕、车载交互要求模型边听边出结果，因此偏爱 RNN-T、流式 Conformer 或 chunk-based 架构；会议纪要、媒体转录、长音频归档则更看重最终转写质量，可以使用更重的双向编码器、Attention 解码器或 Whisper 一类离线大模型。两者的最优架构往往不同，因为延迟约束会直接改变模型可利用的上下文范围。</p>
<div class="blog_h4"><span class="graybg">语音基础模型如何进入实际系统</span></div>
<p>wav2vec 2.0、HuBERT 和 Whisper 这类模型的重要性，不只是 benchmark 指标更高，而是它们改变了数据依赖方式。自监督模型先从海量无标注语音中学通用表征，再在少量标注语音上微调，因此特别适合低资源语种、口音多样或标注昂贵场景；Whisper 这类弱监督大模型则进一步通过海量带噪转写数据获得多语言鲁棒性，使零样本迁移成为现实。</p>
<p>在现代语音系统里，这些基础模型通常不会孤立存在。它们常与语音活动检测、说话人分离、热词偏置、标点恢复、术语后处理和下游 LLM 理解模块一起工作。也就是说，ASR 的输出虽然是一串文本，但真正可用的语音系统往往是一整条“音频前处理 + 识别 + 后处理 + 语言理解”的复合流水线。</p>
<div class="blog_h3"><span class="graybg">2026 主线：Speech LLM 与可靠性问题</span></div>
<p>到 2025 至 2026 年，语音理解已经明显从“纯 ASR 模型”延伸到 Speech LLM 与更广义的 Spoken Language Model。新的主线不只是把音频转成文字，而是希望模型直接利用语音中的韵律、情感、重音和非语言线索完成理解、对话和推理。因此，ASR 正在从独立前端模块，逐步变成更大语音智能系统中的一个子能力。</p>
<p>同时，基础 ASR 模型也暴露出新的工程问题。Whisper 及其变体在多语言、弱监督和离线转写上表现很强，但流式延迟、资源占用和幻觉转写风险也越来越被正视。2026 年的实际系统设计里，识别精度已经不是唯一目标，可靠性、拒答能力、术语稳定性和部署成本同样是主线约束。</p>
<div class="blog_h2"><span class="graybg">语音合成</span></div>
<p>语音合成（Text-to-Speech, TTS）研究的是：如何把文字、音素序列或更高层语义表示生成为自然、可懂、可控的语音波形。它不只是“把文字念出来”，还要决定韵律、停顿、语速、情感和说话人风格，因此本质上同时涉及语言规划和声学生成。</p>
<p>传统 TTS 常由前端文本分析、声学模型和声码器组成；深度学习阶段则经历 Tacotron、FastSpeech、WaveNet、HiFi-GAN 以及后来的语音生成基础模型。今天的主流重点已经从“是否能发声”转向“是否像真人、是否稳定、是否可控”，因此零样本音色克隆、情感控制、多语种一致性和低延迟推理成为关键工程问题。</p>
<p>从任务拆解看，TTS 通常包含文本规范化、音素或发音表示生成、韵律建模、声学特征生成和神经声码器合成几个阶段。很多听感问题并不来自音色本身，而来自重音落错、停顿不自然、数字和专名读法错误，或者长句韵律塌陷。因此，一个高质量 TTS 系统既是生成模型，也是语言与发音规则系统。</p>
<p>近年的语音生成基础模型把 TTS 推向了更强的说话人可迁移与风格控制能力。系统可以只用几秒参考音频完成音色克隆，也可以按情感、语速和场景风格控制输出。这让 TTS 从“固定播报器”逐渐走向“可编排声音接口”，广泛进入客服、车载助手、有声内容、无障碍辅助和数字人系统。</p>
<div class="blog_h3"><span class="graybg">经典路线：声学模型与神经声码器</span></div>
<p>TTS 的经典深度学习路线可以拆成两部分：上游声学模型负责把文本或音素映射到梅尔频谱等中间表示，下游神经声码器负责把这些中间表示还原为最终波形。Tacotron 推动了端到端语音合成的普及，FastSpeech 通过显式时长建模提高了稳定性与速度，WaveNet、WaveRNN、HiFi-GAN 等声码器则决定了最终听感质量。</p>
<p>这条路线今天仍然非常重要，因为它清楚表达了 TTS 的系统分工：语言侧负责发音和韵律，声学侧负责音色与波形细节。很多商用系统即使已经采用 foundation model，也仍然在内部保留类似分层结构。</p>
<div class="blog_h3"><span class="graybg">零样本克隆、可控 TTS 与语音 foundation model</span></div>
<p>近年的 TTS 竞争重点，已经从“自然度够不够”转向“控制力够不够”。零样本音色克隆要求系统只看极少参考音频就能复现说话人音色；可控 TTS 要求系统显式调节情感、语速、重音、风格和语种切换；更进一步，Metis、Spark-TTS 一类工作开始把语音生成视为更统一的 foundation model 问题，不再局限于单一 TTS 任务。</p>
<p>这类模型通常依赖语音 codec token、LLM 式自回归建模或更统一的离散 token 生成接口，使文本到语音、语音到语音转换、说话人迁移和语音编辑之间开始共享更接近的生成骨架。</p>
<div class="blog_h3"><span class="graybg">2026 主线：可控性、评测与安全</span></div>
<p>到 2026 年，TTS 的关键问题已经不只是 MOS 还够不够高，而是系统是否真的可控、可评估、可安全上线。近年的综述已经把 controllable TTS 单独作为主题展开，说明情感、音色、风格和自然语言指令控制正在成为主线；与此同时，零样本 voice cloning 的快速进展也让深度伪造、授权与责任评测成为必须正视的问题。</p>
<p>因此，现代 TTS 既是生成任务，也是安全任务。一个高质量系统除了自然度，还必须处理身份授权、滥用检测、可解释评测和风格失真控制。</p>
<div class="blog_h2"><span class="graybg">说话人识别与声纹验证</span></div>
<p>说话人识别（Speaker Identification）和声纹验证（Speaker Verification）关注的是“这是谁在说话”，而不是“他说了什么”。系统通常会把语音片段映射到声纹嵌入空间，再比较向量距离或相似度。x-vector、ECAPA-TDNN 及其后续嵌入模型长期是这一方向的重要技术基线。</p>
<p>这类任务广泛用于身份认证、会议分轨、通话质检和说话人聚类。其难点来自跨设备录音条件、背景噪声、短时语音片段和伪造攻击。也正因为如此，现代声纹系统往往要把活体检测、反欺骗和跨域校准一起纳入，而不是只做一个单纯分类器。</p>
<p>从业务形式上看，说话人任务通常还包括说话人分离与说话人日志（Diarization），即回答“谁在什么时候说话”。会议纪要、法庭记录和客服通话分析，不仅需要转写内容，还需要把不同说话人的发言切分出来。于是声纹嵌入、分段、聚类和 ASR 往往会在同一条流水线上协同工作。</p>
<p>声纹系统的工程难点在于阈值极度敏感。开放场景里，误受理和误拒识的代价往往都很高，因此系统常需要按设备、场景和语种做校准，并配合活体检测、回声抑制和前端增强模块。也就是说，声纹验证不是孤立的 embedding 相似度问题，而是完整语音安全链路的一部分。</p>
<div class="blog_h3"><span class="graybg">经典范式：x-vector 到 ECAPA-TDNN</span></div>
<p>现代说话人验证的核心范式，是先把变长语音片段压缩成固定长度 speaker embedding，再在 embedding 空间里做相似度判别。x-vector 奠定了这一主流范式；ECAPA-TDNN 则通过更强的通道注意力、层间聚合和多尺度时序建模，长期成为说话人验证的强基线。</p>
<p>这类模型之所以稳定，是因为它们把“谁在说话”从具体内容中相对剥离出来。系统不需要逐词理解语义，只需提取稳定的声道与说话习惯特征。</p>
<div class="blog_h3"><span class="graybg">说话人验证、分离与 diarization</span></div>
<p>声纹任务不能只理解为“比两个 embedding 像不像”。会议、客服和法庭记录里更常见的问题是 diarization，即在长音频中标出“谁在什么时候说话”。这要求系统把分段、嵌入、聚类、重叠说话处理和 ASR 联合起来，而不仅是做单对单验证。</p>
<p>因此，说话人技术的真正系统形态通常是：前端做语音活动检测与可能的分离，中间做说话人嵌入与聚类，后端把说话人标签与转写结果绑定到时间轴上。</p>
<div class="blog_h3"><span class="graybg">2026 主线：跨语种泛化、反欺骗与安全评测</span></div>
<p>近两年的一个重要趋势，是声纹系统越来越强调跨语种和跨场景泛化，而不只是在单一英文数据集上刷分。已有工作专门分析了语言相似性对跨语种 non-target speaker verification 的影响，说明说话人验证并不会天然摆脱语言分布变化的干扰。与此同时，语音深度伪造和 replay attack 使反欺骗、活体检测和校准评测进一步前置。</p>
<p>这意味着，2026 年的声纹主线是“embedding + 安全链路”，而不是“孤立的验证模型”。</p>
<div class="blog_h2"><span class="graybg">语音增强、分离与降噪</span></div>
<p>语音增强（Speech Enhancement）和语音分离（Speech Separation）处理的是“听不清”和“同时有人说话”这两类基础问题。增强任务试图从带噪、带混响语音中恢复更清晰的目标说话声；分离任务则试图把多个重叠说话源拆开。它们往往是 ASR、会议系统和远场交互的前置模块。</p>
<p>传统方法多依赖频谱减法、维纳滤波和统计掩码估计；深度学习阶段则出现了时频掩码网络、Conv-TasNet、DPRNN、SepFormer 等路线。今天的主流趋势是把增强、分离和下游识别协同优化，因为“增强后波形听起来更好”并不一定等于“识别效果一定更好”。</p>
<p>增强、分离和降噪虽然经常一起出现，但目标并不完全相同。增强更强调提升目标语音可懂度与清晰度，分离更强调把多个源拆开，降噪则是更广义地去除背景干扰。不同任务的最优损失函数和评价指标也不同，听感质量、字错率改善和说话人保持度往往不能被同一个指标完全覆盖。</p>
<p>在真实系统中，这类模块常用于会议转写、耳机通话、远场麦克风阵列、车载交互和工业录音清洗。阵列波束形成、回声消除、语音活动检测和后续 ASR 通常会一起部署，因为单独追求“更干净的音频”并不足够，系统最终还是要服务于识别、理解或通信质量目标。</p>
<div class="blog_h3"><span class="graybg">技术演进：时频掩码到时域分离</span></div>
<p>传统增强和分离长期在频谱域操作，依赖噪声估计和掩码预测；深度学习阶段的一个关键变化，是模型开始直接在时域或更接近原始波形的表示上操作。Conv-TasNet、DPRNN、SepFormer 一类方法推动了语音分离对“鸡尾酒会问题”的建模能力显著提升，也让增强与分离的边界变得更接近。</p>
<p>这条演进的意义在于，系统不再只是“对频谱做滤波”，而是能够更完整地重建源语音结构和相位信息。</p>
<div class="blog_h3"><span class="graybg">系统视角：前处理服务下游目标</span></div>
<p>增强和分离在工程上几乎从不作为独立终点。会议转写、远场交互、助听设备和车载系统最终关心的是字错率、通话质量或用户可懂度，而不是某个离线音频指标单独更好。因此，现代系统越来越强调端到端协同：前处理模块必须为后续 ASR、声纹或通信链路服务。</p>
<p>也正因为如此，增强模型的评测也在发生变化。参考信号可得时可以用传统失真指标；真实场景里则越来越需要无参考评价和下游任务驱动评价。</p>
<div class="blog_h3"><span class="graybg">2026 主线：通用增强与无参考评估</span></div>
<p>2024 至 2025 年的 URGENT 挑战和近期分离综述都说明，这一方向正在从“单失真、单数据集优化”走向“通用增强与分离”。语言依赖、多失真类型、采样率变化和真实噪声条件都被放进统一评测框架中。同时，ReFESS-QI 一类工作表明，无参考语音分离评估正在成为必要能力，因为真实混合音频往往根本没有干净真值可用。</p>
<p>这意味着，2026 年这条线真正难的不是模型再堆多深，而是怎样获得跨场景稳健性，以及怎样在没有参考音频时仍然可靠评估系统质量。</p>
<div class="blog_h2"><span class="graybg">关键词检测与语音唤醒</span></div>
<p>关键词检测（Keyword Spotting, KWS）与语音唤醒（Wake Word Detection）要求系统在持续监听中及时发现特定短语，例如“嘿 Siri”或“OK Google”。它和完整语音识别不同，因为它更强调低延迟、低功耗和极低误唤醒率，而不是完整转写整句内容。</p>
<p>这类任务通常部署在端侧设备上，因此模型容量、推理稳定性和噪声鲁棒性比离线精度更关键。很多系统会先用轻量 KWS 模型做前门，再在唤醒后启用更重的 ASR 与语言理解模块，这样可以在功耗、实时性和体验之间取得平衡。</p>
<p>KWS 的技术核心是“在非常长的负样本背景中可靠抓到极短目标片段”。因此它尤其关注误唤醒率、漏唤醒率、持续监听功耗和设备端鲁棒性。家庭环境、车载环境和会议室环境的噪声模式差别极大，一个实验室里效果不错的唤醒词模型，到了真实空间里往往要重新做前端和阈值设计。</p>
<p>很多现代端侧系统还会把 KWS、VAD 和小型流式 ASR 串成分层触发结构。第一层只判断“有没有唤醒迹象”，第二层再确认短语内容，第三层才启动完整理解链路。这种分层架构的价值不在模型更复杂，而在于它能把低功耗常驻监听与较重的后端推理隔离开来。</p>
<div class="blog_h3"><span class="graybg">小模型、端侧部署与 TinyML</span></div>
<p>KWS 的第一原则不是追求最大模型精度，而是在极低功耗和极小内存预算下稳定工作。也正因为如此，小模型设计、量化、蒸馏、特征压缩和 TinyML 部署始终是这一方向的核心问题。近年的 small-footprint KWS 综述进一步把这一点说得很清楚：端侧约束不是附加条件，而是任务定义本身的一部分。</p>
<p>这使 KWS 与很多其他音频任务不同。它的最佳方案常常不是最强大的 foundation model，而是经过蒸馏和硬件共设计后的轻量系统。</p>
<div class="blog_h3"><span class="graybg">鲁棒性、公平性与测试时适配</span></div>
<p>唤醒词任务的另一个关键难点，是环境变化会迅速破坏模型稳定性。家居噪声、口音差异、麦克风位置变化和设备回声，都可能显著拉高误唤醒率。AdaKWS 一类工作开始把 test-time adaptation 引入 KWS，说明鲁棒性问题已经不再只是训练集扩增能完全解决的。</p>
<p>与此同时，公平性也开始成为明确问题。2026 年已有工作直接讨论 wake word detection 的 demographic bias，说明唤醒系统不能只在平均指标上看起来不错，而必须关注不同性别、年龄和口音人群之间的性能差异。</p>
<div class="blog_h3"><span class="graybg">2026 主线：分层触发链路与设备友好优化</span></div>
<p>到 2026 年，KWS 的主线已经非常清楚：第一，继续做更小、更省电的前门模型；第二，用分层触发把唤醒检测与后端 ASR / LLM 解耦；第三，把鲁棒性、公平性和设备端时延纳入同一目标函数。它不是一个“简化版 ASR”，而是端侧语音交互的系统门卫。</p>
<div class="blog_h2"><span class="graybg">音频事件检测与环境声音理解</span></div>
<p>音频事件检测（Audio Event Detection）研究的是：一段声音里发生了什么事件，例如玻璃破碎、警报、犬吠、机器异常、咳嗽或交通噪声。它关心的不是语音语言内容，而是声学场景和事件类别，因此更接近“听觉版分类与检测”任务。</p>
<p>这类任务常用于安防监测、工业巡检、智能家居、车载感知和医疗辅助。技术上，系统既可以做整段分类，也可以做时间轴上的事件定位。难点在于背景噪声复杂、事件持续时间差异大、同类事件跨场景变化强，因此它常常需要时频表示、弱监督标注和多实例学习一起配合。</p>
<p>从任务层级看，环境声音理解通常包含声学场景分类、事件检测、事件分段和多标签识别。一个录音片段里可能同时存在雨声、引擎声和人声，也可能只有一次短暂的撞击事件，因此系统既要建模整体背景，也要捕捉瞬时异常。频谱图、对数梅尔特征、卷积编码器和音频 Transformer 是这里最常见的技术骨架。</p>
<p>这类任务近年来也越来越依赖预训练音频表示。大规模音频基础模型可以先学习通用声音模式，再迁移到工业异常、医疗咳嗽检测或野外生物声监测等小数据任务。与语音识别类似，这条线的收益不只在指标更高，还在于跨任务迁移能力显著增强。</p>
<div class="blog_h3"><span class="graybg">经典设定：闭集事件检测与弱标签学习</span></div>
<p>音频事件检测长期默认闭集设定，即系统只检测固定类别表中的声音事件。由于精确逐帧标注昂贵，很多工作会结合弱标签、多实例学习和伪标签传播，让模型在只有片段级标签或部分强标签时仍能学习时间定位能力。这也是为什么 SED 一直比普通音频分类更难。</p>
<p>因为真实音频场景中事件经常重叠、边界模糊、类别长尾，单一 hard label 很难完整描述声音世界。</p>
<div class="blog_h3"><span class="graybg">开放词汇声音理解与音频语言模型</span></div>
<p>近两年的关键变化，是音频事件检测开始明显向开放词汇和音频-文本建模移动。Audio-Language Model 综述已经把这一趋势系统化：自然语言监督比固定标签更适合描述复杂真实音频。具体到 SED，近期工作开始直接使用文本查询、机器生成 caption 或多模态 query 来指导事件检测，这让“Detect Any Sound”一类开放词汇设定成为现实方向。</p>
<p>这与开放词汇视觉检测的变化非常相似，说明环境声音理解也在从闭集分类器转向语言可查询的通用音频理解接口。</p>
<div class="blog_h3"><span class="graybg">2026 主线：从固定标签检测到 open-vocabulary audio understanding</span></div>
<p>到 2026 年，环境声音理解的前沿已经不再只是固定 10 类、20 类事件的检测分数，而是开始关心零样本迁移、开放类别、文本查询、噪声鲁棒性与推理能力。Large Audio-Language Model 的兴起也使评测本身成为问题：不仅要测分类和定位，还要测知识、推理、安全与可信度。因此，这条线正在迅速向“通用音频理解”靠拢。</p>
<div class="blog_h2"><span class="graybg">音乐与音频生成</span></div>
<p>音乐与音频生成任务不再问“声音里发生了什么”，而是问“能否生成新的声音内容”。它包括音乐生成、伴奏生成、风格迁移、语音克隆、音效生成和音频修复。技术路线既可以从符号音乐入手，也可以直接在波形或潜空间中生成连续音频。</p>
<p>这条线的难点与图像生成不同，因为音频天然是时序信号，长程一致性、节奏稳定性和细节纹理都必须同时处理。也正因为如此，音乐和音频生成通常要在“局部音色质量”和“长时间结构组织”之间做复杂权衡。</p>
<p>从表示形式上看，音乐生成至少有两条大路线。符号音乐生成在音符、和弦和节拍层面建模，更容易控制结构与编曲；直接音频生成则面向波形或压缩后的音频 token，更强调真实音色和制作质感。两者常常结合使用，因为结构控制和最终听感通常不由同一层表示单独决定。</p>
<p>音频生成在业务上覆盖背景音乐、配音、游戏音效、影视后期、交互式创作和数据扩增，但其约束比图像更苛刻。轻微的节奏崩坏、爆音和相位异常就会明显破坏体验，因此真实系统常把生成模型与后处理、母带质量控制和版权检测一起使用。</p>
<div class="blog_h3"><span class="graybg">符号音乐与直接音频生成</span></div>
<p>音乐生成最基本的技术分裂，是生成符号序列还是直接生成音频。符号音乐方法更容易控制和弦、节拍、结构和编曲，因此在作曲辅助和可编辑场景中很重要；直接音频生成则更接近最终可听结果，适合生成完整音色、演唱与制作细节。</p>
<p>很多现代系统实际上会组合两层表示：先在高层决定结构，再在低层合成真实声音。这与 TTS 中“语言规划 + 波形生成”的分层逻辑非常相似。</p>
<div class="blog_h3"><span class="graybg">foundation model、长时结构与多模态控制</span></div>
<p>近年的音乐生成已经明显 foundation model 化。YuE、ACE-Step、AudioX 一类工作表明，文本到音乐、长时歌词到歌曲、跨模态控制和多任务音频生成正在逐渐被统一到更一般的音频 token 生成框架中。这里最大的难点不是局部音色，而是长时间结构是否仍然连贯、歌词和旋律是否对齐、风格控制是否稳定。</p>
<p>这也解释了为什么音乐生成研究越来越重视长时一致性、多模态条件和统一评测，而不只是若干秒短音频的主观试听。</p>
<div class="blog_h3"><span class="graybg">2026 主线：评测体系落后于生成能力</span></div>
<p>音乐与音频生成在 2025 至 2026 年的一个突出问题，是生成能力上涨很快，但评测体系明显滞后。新的综述和 benchmark 工作都在反复强调，客观指标与人类偏好之间常常并不一致，长程结构、情感表达、创造性和版权风险也很难被单一自动指标捕捉。因此，这一方向未来不只是更强生成模型的竞赛，也是在补齐可靠评测、可控生成与合规机制。</p>
<div class="blog_h1"><span class="graybg">搜索/推荐/广告预估</span></div>
<p>搜索、推荐和广告预估处理的是同一类工业问题：在海量候选文档、内容、商品或广告中，如何为当前用户挑出最值得展示的少数结果。三者都不只是做一个静态分类器，而是在解决<span style="background-color: #c0c0c0;">召回、排序、价值预估、约束满足与在线策略优化</span>共同存在的决策链。差别主要在于用户意图的来源不同：搜索由 query 显式表达意图，推荐更多依赖历史行为和上下文推断意图，广告则还要把商业出价、预算和拍卖机制纳入同一系统。</p>
<p>从系统结构看，这三个方向本质上都可以概括为“<span style="background-color: #c0c0c0;">理解需求 - 生成候选 - 精排重排 - 在约束下完成最终展示</span>”。搜索系统需要理解 query 与文档相关性，推荐系统需要理解用户兴趣与内容匹配，广告系统则要在相关性与商业价值之间做动态平衡。因此，三者虽然业务目标不同，但底层技术长期共享向量召回、学习排序、重排、多目标优化、校准与策略控制等一整套方法栈。</p>
<div class="blog_h2"><span class="graybg">搜索系统</span></div>
<p>搜索（Search / Information Retrieval）处理的是：用户带着一个明确问题、需求或目标词串进入系统，系统如何在海量语料里找出最相关的结果。它与推荐最核心的区别在于，推荐更多是在“没有明确 query 时”推断用户想看什么；搜索则是围绕显式 query 展开，因此 Query Understanding、文档匹配和排序相关性成为系统主轴。</p>
<p>一个现代搜索系统通常至少分为四层：查询理解、候选召回、排序重排和结果组织。查询理解负责把用户输入转成可计算的检索意图，例如分词、纠错、改写、意图识别、实体识别、同义词扩展和 query embedding；召回层负责从超大文档库里先捞出一批高潜候选；排序层进一步判断哪些结果更相关、更权威、更新或更满足业务目标；结果组织则决定最终页上的展示方式，例如直答、摘要片段、高亮、聚合卡片和多路结果混排。</p>
<p>搜索是 AI 应用的重要方向，因为它天然要求模型同时理解语言、语义匹配、用户行为反馈和系统约束。一个好搜索系统需要判断文档是否真正回答了 query、是否符合当前上下文、是否应该优先展示某类垂直结果，并在高并发、低延迟场景下稳定输出；关键词命中只是其中最基础的一层。</p>
<div class="blog_h3"><span class="graybg">查询理解、倒排索引与语义召回</span></div>
<p>传统搜索的第一性原理是倒排索引（Inverted Index）与词项匹配。文档被切成词项并建索引，query 到来后先查哪些文档包含这些词，再用 TF-IDF、BM25 一类相关性函数做初步排序。这一路线极其重要，因为它速度快、可解释、对精确词面匹配非常强，直到今天仍是大多数搜索系统的底座。</p>
<p>但词面匹配并不能充分表达语义等价、长尾表达和上下文含义，因此现代搜索几乎都会加入语义检索。做法通常是把 query 与 document 编码成 embedding，在向量空间中做近似最近邻（ANN）搜索，从而召回词面不同但语义接近的候选。双塔编码器、Sentence-BERT、dense retrieval、dual encoder reranker 等路线，本质上都在把“相关性”从字符串共现扩展到表示空间邻近。</p>
<p>真实工业系统通常不会在“词法检索”和“语义检索”之间二选一，而是采用<span style="background-color: #c0c0c0;">混合召回（Hybrid Retrieval）</span>。倒排索引负责高精度命中精确词项、品牌词、数字和稀有实体，向量召回负责补充同义改写、语义近邻和长尾表达。这样做的原因很直接：纯词法系统容易漏掉语义等价表达，纯向量系统又可能牺牲可控性、可解释性和硬约束匹配能力。混合召回把两者的强项叠加起来，是近年搜索系统的主流工程形态。</p>
<div class="blog_h3"><span class="graybg">排序、重排与生成式搜索</span></div>
<p>搜索排序（Learning to Rank）要解决的是：候选已经召回出来后，哪些结果应该出现在最前面。这里的相关性不再只是“像不像”，而是更细粒度的综合判断，包括主题匹配、答案性、权威性、时效性、点击后满意度和位置偏差。经典做法包括 pointwise、pairwise、listwise 三类学习排序方法；模型上则经历了从手工特征 + GBDT/LambdaMART，到 BERT cross-encoder、late interaction 和多阶段 reranker 的演化。</p>
<p>重排层通常会用更贵但更准的模型，对前几十或前几百个候选做深度语义交互。Cross-Encoder 把 query 和 document 拼成一段输入，让 Transformer 直接建模逐 token 交互，因此相关性判断通常更强；代价是吞吐低，无法在全库上直接跑。这就形成了搜索里的经典多阶段结构：第一阶段用倒排或双塔快速召回，第二阶段用更强的交互模型精排，必要时再加业务规则、多样性和去重层做最终重排。</p>
<p>生成式搜索则是在此基础上的进一步演化。大语言模型（Large Language Model, LLM）可以参与 query 改写、答案抽取、结果摘要、Multi-Hop 检索和 Retrieval-Augmented Generation（RAG），让搜索从“返回文档列表”扩展到“在文档支持下生成结构化回答”。与此同时，检索系统的重要性反而更高了：只要召回文档不对、排序证据不稳，生成答案就会连带失真。因此生成式搜索的主线应理解为检索、排序与生成的协同工作。</p>
<div class="blog_h3"><span class="graybg">搜索、推荐与广告的统一视角</span></div>
<p>从系统抽象看，搜索、推荐和广告其实都可以写成“从超大候选集合中做检索与排序”的问题。搜索的显式意图最强，目标是回答 query；推荐的隐式意图更强，目标是预测用户此刻最可能消费什么；广告则在相关性之外额外叠加出价、预算、转化价值和拍卖机制。也就是说，三者的差异首先是目标函数不同，其次才是模型形态不同。</p>
<p>这也是为什么很多底层技术会反复跨领域复用。ANN 检索既能做搜索语义召回，也能做推荐召回；学习排序既能用于网页搜索，也能用于推荐 feed 和广告竞价；校准、多目标优化和重排，三者同样都离不开。放在同一章里理解，能更清楚地看到它们共享的是一条“retrieve, rank, optimize under constraints”的统一工程主线。</p>
<div class="blog_h2"><span class="graybg">推荐召回</span></div>
<p>召回（Retrieval / Matching）要解决的是：在数百万到数十亿候选里，先快速筛出一小批“值得进一步细排”的候选集合。它的核心目标是保证高潜力内容不要在第一层就被漏掉，而把最终精细排序留给后续层完成。协同过滤、双塔模型（Two-Tower Model）、向量检索和图召回是这一层最常见的路线。</p>
<p>召回系统的关键点在于“广而不乱”。候选太少会漏掉好内容，候选太多又会把后续排序层拖垮。也正因为如此，现代推荐系统常会并行运行多路召回：基于兴趣相似、基于内容相似、基于热门趋势、基于图邻接和基于规则保底，然后再统一交给后续排序层整合。</p>
<p>从系统角色看，召回层更像一个高速过滤器。它通常要求毫秒级响应和极高吞吐，因此模型结构必须足够轻，且能够与 ANN 检索、倒排索引或图搜索深度耦合。双塔模型之所以长期流行，就是因为它把用户和物品分别编码成向量，使“离线建库 + 在线近邻搜索”成为可能。</p>
<p>召回问题的真实难点还在于覆盖长尾与新内容。若系统只依赖历史点击共现，高质量但尚未被看见的新内容几乎无法进入候选池。因此很多工业系统会显式保留探索召回、内容理解召回和规则保底召回，让召回层先具备基本多样性，再把精细判断交给排序层完成。</p>
<div class="blog_h3"><span class="graybg">经典召回：协同过滤、双塔与 ANN</span></div>
<p>推荐召回最经典的工业范式，是把“候选空间极大”转化为“近邻检索问题”。早期协同过滤和矩阵分解通过用户-物品共现学习潜在表示；深度学习阶段的双塔模型则把用户和物品分别编码到向量空间中，再通过近似最近邻（ANN）索引实现高速检索。这条路线之所以长期有效，是因为它在吞吐、延迟和可扩展性之间取得了非常稳定的平衡。</p>
<p>即便到了 foundation model 时代，双塔也没有失去价值。因为只要线上系统仍要在毫秒级从海量库中筛候选，向量化检索就是最自然的工程接口。</p>
<div class="blog_h3"><span class="graybg">图召回、多路召回与内容召回</span></div>
<p>真实推荐系统很少只依赖单一路召回。图召回利用用户、物品、作者、主题和社交关系之间的高阶连接，适合发现协同结构；内容召回依赖文本、图像和多模态特征，适合新内容与长尾内容；热门召回和规则保底则保证系统不会过度塌缩到窄兴趣空间。多路召回的本质，是在召回阶段先把“覆盖率”做出来，再让排序去做精细取舍。</p>
<p>也正因为如此，召回阶段的关键指标从来不只是离线精度，而是覆盖、去重、多样性、冷启动能力与后续排序承压能力的综合平衡。</p>
<div class="blog_h3"><span class="graybg">2026 主线：图基础模型、生成式召回与跨域泛化</span></div>
<p>到 2025 至 2026 年，召回领域出现了两个明显方向。第一是图基础模型（Graph Foundation Model）与图-语言融合模型开始进入推荐，试图把用户物品图结构与文本语义统一进更可迁移的表示；第二是生成式推荐开始把召回理解成“从统一语义空间生成或选择下一个候选”的问题，而不只是 ID 近邻搜索。对于跨域推荐和冷启动，这类路线尤其有吸引力。</p>
<p>不过，工程上最现实的判断仍然是：生成式和 foundation model 更像是在增强召回表示与跨域泛化，而不是短期内完全取代高吞吐双塔检索。</p>
<div class="blog_h2"><span class="graybg">排序与重排</span></div>
<p>排序（Ranking）关心的是：在召回出来的候选里，哪几个应该排在最前面。它通常是推荐系统最直接影响用户体验的一层，因为最终展示顺序几乎完全由这里决定。经典路线从逻辑回归、GBDT、Wide&amp;Deep、DeepFM 一直到多任务排序网络和 Transformer 式序列排序器，基本都在这条线上演进。</p>
<p>重排（Reranking）则是更靠后的精细优化层，它会在较小候选集合上显式考虑位置偏差、多样性、去重、探索、业务约束和列表级目标。随着系统复杂度提升，排序越来越接近“在整个列表层面组织一个满足多约束的展示结果”，而不再只是给每个候选打一个孤立分数。</p>
<p>排序建模既可以是 pointwise，也可以是 pairwise 或 listwise。前者把每个候选单独打分，工程实现最直接；后两者更强调“谁应该排在谁前面”或“整个列表质量如何”。在真实系统里，这几类方法常常混合出现，因为训练目标、线上排序器和最终业务指标未必天然一致。</p>
<p>重排层的重要性在于，它第一次显式面对列表级体验问题。连续出现相似商品、同质短视频或重复广告，会明显伤害用户感受，即便每个单项分数都不低。因此，重排往往会把去重、多样性、公平曝光、商业约束和探索策略统一纳入，而不再把推荐看成若干独立样本的排序问题。</p>
<div class="blog_h3"><span class="graybg">经典排序：LR、GBDT、Wide&amp;Deep、DeepFM</span></div>
<p>排序层的工业主线非常清晰。逻辑回归和 GBDT 奠定了大规模点击预估与排序的可解释基线；Wide&amp;Deep、DeepFM、DCN 等模型把稀疏 ID 特征、交叉特征和深度表示结合起来，长期成为 CTR/CVR 排序系统的中坚方案。这些模型之所以常青，不是因为它们最时髦，而是因为它们稳定、易训、易扩展、易与生产特征系统对接。</p>
<p>因此，很多平台即使在探索 Transformer 和 LLM 方案，核心线上排序器仍然大量保留这条经典工业栈。</p>
<div class="blog_h3"><span class="graybg">pointwise、pairwise、listwise 与重排</span></div>
<p>排序目标的差异会直接影响模型学到的偏好。pointwise 训练关注单个样本的点击或转化概率；pairwise 强调相对顺序；listwise 则试图让整个列表质量与用户体验更一致。近期的一些工作，如 Hierarchical Group-wise Ranking，也在继续尝试把更强的排序损失并入 CTR/CVR 训练，以缩小“分类目标”和“排序目标”之间的错位。</p>
<p>重排层则把问题从“给每个候选打分”进一步推进到“把这份列表组织成用户更愿意看的样子”。这里多样性、去重和位置偏差往往比单点概率本身更重要。</p>
<div class="blog_h3"><span class="graybg">2026 主线：生成式排序与大模型适配仍在早期</span></div>
<p>2025 年以后，推荐排序的研究焦点之一，是如何把更大的 Transformer 或 LLM 式模型真正用于判别式排序任务。近期工作已经开始探索用生成式预训练支撑 CTR/CVR 等判别任务，缓解大模型在稀疏推荐数据上容易过拟合的问题。同时，也有框架尝试把预训练语言模型适配到工业级推荐排序。</p>
<p>但到 2026 年，最稳妥的判断仍然是：大模型在推荐排序里更像增强器和研究前沿，而不是已经全面替代传统深度排序器的成熟工业标准。</p>
<div class="blog_h2"><span class="graybg">用户表征与序列兴趣建模</span></div>
<p>推荐系统长期的一条主线，是怎样把用户历史行为压缩成可更新的兴趣表示。早期方法更依赖统计特征和聚合特征；后续出现了 DIN、DIEN、BST、SASRec、Transformer4Rec 一类模型，把点击序列、停留序列和会话上下文直接做时序建模。</p>
<p>这类任务的难点在于兴趣并不是静态向量，而是会漂移、分裂和受上下文触发。用户昨天在看婴儿车，未必意味着长期兴趣都转向母婴；用户今晚在搜酒店，也可能只是一次性出行需求。因此现代推荐中的用户表征，越来越强调短期兴趣、长期偏好、场景上下文和跨域行为的联合建模。</p>
<p>从特征来源看，用户表征通常同时吸收 ID 特征、内容特征、行为序列、社交图关系和实时上下文。现代系统很少再满足于“一个静态 user embedding”，而是会根据当前页面、当前时刻、当前候选集和最近行为重新计算条件化兴趣表示。这也是序列模型和注意力机制在推荐中持续重要的根本原因。</p>
<p>用户兴趣建模的另一个核心问题是可迁移性与可更新性。大型平台常希望同一用户表示同时服务首页推荐、广告、搜索和通知触达，但不同场景对兴趣的时间尺度和噪声容忍度完全不同。因此，统一用户表征与场景专属表征之间的分工，是推荐架构设计中的长期主题。</p>
<div class="blog_h3"><span class="graybg">从静态 embedding 到序列兴趣建模</span></div>
<p>用户表征的演进，核心是从“一个用户一个静态向量”走向“一个用户在当前上下文下的动态兴趣状态”。DIN 强调目标感知的局部兴趣激活，DIEN 引入兴趣演化，BST、SASRec、Transformer4Rec 等模型则更明确地把用户行为当作序列来建模。这一演进反映的不是模型时髦程度，而是对用户兴趣本质的认识更精细了。</p>
<p>因为兴趣本身具有时序性、阶段性和条件触发性，静态 embedding 很难长期承担全部表达任务。</p>
<div class="blog_h3"><span class="graybg">多行为、多模态与统一用户画像</span></div>
<p>现代平台记录的用户行为远不止点击。曝光、停留、收藏、加购、购买、分享和跳出都在表达不同强度的偏好，因此多行为推荐成为明确方向。2025 年的多行为推荐综述已经把这一点系统化：输入建模、编码方式和训练策略都会因为行为类型不同而改变。与此同时，文本、图像和视频内容也在进入用户表征，使兴趣建模不再只是 ID 共现问题。</p>
<p>这意味着，用户表征越来越像“行为图 + 内容语义 + 场景上下文”的联合编码器，而不是单一行为序列模型。</p>
<div class="blog_h3"><span class="graybg">2026 主线：foundation model 化，但 ID 问题仍未消失</span></div>
<p>当前一个非常明显的前沿，是尝试用 foundation model 处理序列推荐与跨域推荐。例如 RecGPT 试图摆脱传统 ID-only 表征，直接从文本特征构建 item 表示，以获得更强的零样本和跨域泛化能力。这条路线对冷启动尤其有吸引力，因为它正面回答了“新物品没有交互历史怎么办”。</p>
<p>但到 2026 年，ID 信号仍然没有消失。更现实的趋势是：强表征模型越来越多地同时吸收 ID、文本、图像和图结构，而不是简单宣布某一类信号过时。</p>
<div class="blog_h2"><span class="graybg">点击率、转化率与价值预估</span></div>
<p>点击率预估（CTR Prediction）、转化率预估（CVR Prediction）和价值预估，本质上是在回答“展示一个候选之后，用户后续会发生什么行为”。它们是排序与广告系统最核心的监督信号来源，因为展示决策最终都要落到点击、加购、购买、停留和长期价值上。</p>
<p>这类预估任务看似只是二分类或回归，实际上有大量特有难点：样本选择偏差、曝光偏差、延迟反馈、极端正负不平衡和归因问题。也正因为如此，推荐领域的模型设计往往不只围绕结构本身，还要围绕校准（Calibration）、样本构造、负采样和反事实纠偏展开。</p>
<p>CTR、CVR 和价值预估在链路中的分工也不同。CTR 更接近“用户是否愿意点开”，CVR 更接近“点开之后是否会转化”，价值预估则进一步把价格、利润、时长或长期收益纳入。很多工业系统会显式构造多阶段漏斗模型，例如先预估点击，再在点击条件下预估转化，避免把不同阶段的数据偏差混在一起。</p>
<p>此外，这类模型常常不仅要“排得对”，还要“概率可信”。如果输出分数被用于出价、预算分配或自动决策阈值，校准误差就会直接变成业务损失。因此，推荐和广告系统会比普通分类任务更重视概率校准、延迟标签回填和线上 A/B 实验中的稳定性监控。</p>
<div class="blog_h3"><span class="graybg">漏斗建模：CTR、CVR、价值预估各自负责什么</span></div>
<p>CTR、CVR 和价值预估本质上对应用户行为漏斗的不同层级。CTR 关注是否点开，CVR 关注点击后是否完成转化，价值预估则把金额、利润、时长或长期收益继续纳入。把这些任务硬合并成单一标签往往会损失信息，因此工业系统更常采用分层建模、多任务学习或条件概率分解。</p>
<p>这也是为什么推荐预估模型通常不只是一个二分类器，而是一组围绕行为漏斗设计的联合模型。</p>
<div class="blog_h3"><span class="graybg">偏差、校准与反事实纠偏</span></div>
<p>预估任务最棘手的部分，不在网络结构本身，而在数据是被旧策略选择出来的。曝光偏差、样本选择偏差、延迟反馈和极端不平衡，都会让模型学到“日志里的相关性”而不是真正的用户倾向。因此，propensity modeling、IPS、Doubly Robust、负采样设计和概率校准在这条线上都非常关键。</p>
<p>特别是在 CVR 与广告出价场景里，校准问题会直接影响业务收益。近期关于不确定性校准和 propensity calibration 的工作，正是在补这一块基础设施。</p>
<div class="blog_h3"><span class="graybg">2026 主线：大模型预训练开始进入判别式预估</span></div>
<p>一个值得注意的新变化，是更大的 Transformer 和生成式预训练开始被拿来增强判别式推荐任务。2025 年已有工作专门研究如何用 generative pretraining 支撑 CTR/CVR 这类 discriminative recommendation，核心动机是缓解数据稀疏条件下大模型直接训练容易过拟合的问题。这说明生成式方法在推荐里并不只服务于对话或内容生成，也开始反过来增强传统点击预估。</p>
<div class="blog_h2"><span class="graybg">多目标优化与约束推荐</span></div>
<p>真实推荐系统很少只优化单一目标。用户体验希望提高满意度和留存，商业侧希望提高 GMV、广告收入和库存周转，平台治理又要求控制重复曝光、内容安全和公平性。因此，多目标优化（Multi-objective Optimization）几乎是推荐系统的常态，而不是例外。</p>
<p>工程上常见做法包括多任务学习、加权打分、约束重排和列表级优化。关键难点不在于“写几个损失函数”，而在于不同目标天然冲突：极致点击不一定带来长期留存，极致广告收益不一定带来用户满意，极致个性化又可能牺牲内容多样性。因此，推荐系统真正难的是怎样在多目标之间维持稳定平衡。</p>
<p>从优化视角看，多目标问题通常可以写成显式加权，也可以写成主目标加约束。前者更容易工程落地，后者更容易表达“某些指标绝不能跌破底线”。例如广告收入可以提升，但内容安全和用户投诉率必须受控；点击可以提高，但重复率和生态多样性不能失衡。推荐系统的策略层，往往就是在把这类业务规则转成可执行优化问题。</p>
<p>这一层还常与因果推断和反事实评估结合。因为当系统策略改变后，数据分布本身也会随之改变，模型很容易把“被展示过”误当成“本来就更优”。因此，多目标推荐不是纯粹的监督学习扩展，而是包含反馈回路与策略干预效应的动态优化问题。</p>
<div class="blog_h3"><span class="graybg">多任务学习、加权打分与约束优化</span></div>
<p>多目标推荐最常见的三种工程形式，是多任务学习、显式加权打分和约束优化。多任务学习适合共享表征并联合学习多个行为信号；加权打分最易上线，便于快速调节业务策略；约束优化则更适合表达“收入可以提升，但投诉率或重复率不能越线”这类硬约束。三者经常同时存在，而不是彼此替代。</p>
<p>因此，多目标优化的难点从来不只是模型结构，而是目标之间的可交易性和组织层面的策略选择。</p>
<div class="blog_h3"><span class="graybg">列表级目标与生成式推荐</span></div>
<p>当目标从单项点击扩展到多样性、留存、公平性和生态健康时，列表级优化会越来越重要。近期关于 generative AI 时代多目标推荐的综述已经明确指出，推荐系统的目标早已超过单纯 accuracy，生成式模型与 agent 式推荐为联合优化提供了新的接口。这类方法试图不只给单个 item 打分，而是直接组织更符合全局目标的候选列表。</p>
<p>不过，这条路线也带来了可控性和评估复杂度问题，因为生成式策略更容易把目标耦合到黑箱决策中。</p>
<div class="blog_h3"><span class="graybg">2026 主线：从精度最优转向系统目标最优</span></div>
<p>到 2026 年，一个非常明确的变化是：推荐系统越来越少把单一精度指标当作总目标，而越来越多把满意度、收入、多样性、公平性和长期价值放到统一框架里看。这并不意味着准确率不重要，而是准确率已经被降级为更大系统目标中的一部分。</p>
<div class="blog_h2"><span class="graybg">冷启动、探索与长期价值</span></div>
<p>冷启动（Cold Start）处理的是“新用户、新物品和新广告缺少历史数据”这一问题。若系统只依赖既有交互统计，就会天然偏向老内容和老用户画像，新内容几乎没有被看见的机会。这正是推荐系统必须引入探索（Exploration）的根本原因。</p>
<p>进一步看，很多收益并不会在一次展示后立刻体现。例如一个内容今天点击一般，却可能显著提升用户长期留存；一个广告今天看似收益高，却可能长期伤害平台体验。这使推荐系统天然和强化学习、因果推断、bandit 方法以及长期价值建模相连。也正因为如此，“短期点击最优”通常不是推荐系统真正的终局目标。</p>
<p>冷启动的解决方式通常包括内容特征建模、跨域迁移、元学习式快速适配和显式探索流量分配。新物品至少要先被正确表征，系统才能知道它可能与哪些用户匹配；新用户至少要通过少量交互快速建立初始画像。于是，内容理解模型、用户 onboarding 问卷、短序列兴趣建模和 bandit 探索会共同参与冷启动链路。</p>
<p>长期价值问题则要求系统从“这次曝光赚了多少”扩展到“这次曝光对未来行为有什么影响”。停留、留存、复访、满意度和生态健康都属于长期信号。真正成熟的推荐系统不会只看一跳指标，而会通过长期奖励建模、因果归因和阶段性约束，避免系统陷入只追逐短期反馈的局部最优。</p>
<div class="blog_h3"><span class="graybg">冷启动：内容特征、混合策略与跨域迁移</span></div>
<p>冷启动之所以难，是因为传统协同过滤几乎完全依赖历史交互，一旦没有历史就会失明。因此，冷启动系统通常必须引入内容特征、用户属性、知识图谱、跨域行为和显式探索流量。2025 年关于 cold-start recommendation 的综述也表明，LLM 与更强内容理解能力正在显著改变这条线，因为它们能更好利用文本描述、商品元数据和跨域语义先验。</p>
<p>这意味着，冷启动的现代解法越来越偏向“用内容和语义补足交互稀缺”，而不是只等待日志积累。</p>
<div class="blog_h3"><span class="graybg">探索、bandit 与长期价值</span></div>
<p>探索和长期价值是同一类问题的两个侧面。若系统永远只推最确定会点的内容，就很难发现新偏好，也很难获得长期收益信号。因此，bandit、强化学习和因果推断会在这条线上持续出现。它们共同回答的是：怎样在当前收益与未来学习收益之间做平衡。</p>
<p>长期价值建模也要求系统重新定义 reward，不再只看单次点击，而是把留存、满意度、复访和生态健康纳入目标。</p>
<div class="blog_h3"><span class="graybg">2026 主线：foundation model 解决一部分冷启动，不解决全部</span></div>
<p>foundation model 和生成式推荐确实在改进冷启动，尤其是在文本充分、跨域丰富的场景里更明显；但它们并没有让探索问题消失。因为即便模型能更好理解新物品语义，它仍然需要真实用户反馈来校准平台特定偏好和长期效应。到 2026 年，更现实的路线仍然是“强内容表征 + 探索机制 + 长期价值建模”的组合，而不是幻想单一大模型自动解决冷启动。</p>
<div class="blog_h2"><span class="graybg">广告投放、竞价与预算控制</span></div>
<p>广告系统与内容推荐共享很多基础技术，但它额外引入了出价（Bidding）、预算（Budget）、归因（Attribution）和流量约束，因此更接近“机器学习 + 在线市场机制”联合系统。模型不只要判断用户会不会点，还要判断这一展示值多少钱、是否值得消耗预算、是否应当在当前流量时段出价更积极。</p>
<p>因此，广告预估的真正系统目标通常是“预估 + 排序 + 竞价 + 节奏控制（Pacing）”的联合优化。CTR、CVR、eCPM、ROI、LTV 和预算完成率不会天然一致，模型稍微偏一点，系统收益和广告主体验就会同时受影响。这也是为什么广告系统常常比普通推荐系统更强调校准、稳定性和策略解释。</p>
<p>在广告拍卖链路中，模型分数通常会直接进入排序或出价公式，例如用 pCTR、pCVR、预估价值与 bid 共同形成最终竞争分数。因此，广告预估模型并不是一个孤立分类器，而是市场机制的一部分。分数稍有偏差，就可能导致高价值广告被低估、预算过早打满，或者后段流量投不出去。</p>
<p>预算控制与节奏控制进一步把问题变成动态资源分配。系统不仅要知道“这次值得不值得投”，还要知道“今天剩余预算应当怎样随时间分配”“不同流量时段应不应提高或压低出价”。因此，广告系统通常需要把预测模型、竞价策略、预算 pacing 和归因反馈放在统一控制闭环中理解。</p>
<div class="blog_h3"><span class="graybg">广告排序与普通推荐排序的差异</span></div>
<p>广告排序和内容推荐虽然共享很多建模技术，但目标函数有本质区别。广告系统必须同时考虑用户点击、转化、广告主 ROI、平台收入和预算约束，因此排序分数往往直接进入拍卖机制。也就是说，广告模型输出的不是纯粹的“喜欢概率”，而是影响真实市场分配的 scoring signal。</p>
<p>这就是为什么广告系统会比普通推荐更敏感于校准误差、延迟标签和竞价机制变化。</p>
<div class="blog_h3"><span class="graybg">autobidding、auction design 与 pacing</span></div>
<p>近年的广告研究明显转向 autobidding 和 auction design。相关综述已经把这一方向单独系统化，说明自动出价正在成为广告生态核心接口。与此同时，预算 pacing 也不再只是一个老工程细节，而是整个投放系统的关键控制器，因为预算花太快、太慢或分布不均都会直接影响收益和广告主体验。</p>
<p>近期的基准和方法工作也说明，这一方向越来越强调离线 benchmark、约束优化和策略可评估性，而不只是在线黑箱调参。</p>
<div class="blog_h3"><span class="graybg">2026 主线：大模型开始进入广告，但闭环约束更重</span></div>
<p>到 2025 至 2026 年，广告系统也出现了明显的大模型化趋势。例如面向广告推荐与 auction scoring 的大规模训练基础设施、面向 auto-bidding 的生成式模型，以及专门为广告推荐设计的 foundation model 适配框架，都说明这一领域正在吸收 foundation model 能力。</p>
<p>但广告场景比内容推荐更难放任黑箱模型自由发挥，因为预算、ROI、CPC、节奏控制和拍卖公平性都是硬约束。也正因为如此，广告系统的大模型化大概率会长期呈现“强模型 + 强约束 + 强校准”的形态。</p>
<div class="blog_h1"><span class="graybg">时序建模和时间序列</span></div>
<p>时序建模和时间序列任务处理的是：当数据按时间顺序展开时，模型如何利用过去信息理解当前状态并预测未来变化。它和一般表格学习的根本差别在于，样本之间不是互相独立同分布的点，而是被趋势、季节性、周期性、突变和滞后关系串起来的动态过程。</p>
<p>这类任务广泛出现在金融行情、传感器监控、设备运维、能源负荷、交通流量、用户行为序列和医学监测中。它们共同要求模型尊重顺序、处理非平稳性，并面对一个长期存在的现实问题：未来的数据分布很可能和过去并不完全相同。</p>
<p>在时序任务里，上采样与下采样通常不再表现为空间分辨率变化，而是表现为<span style="background-color: #c0c0c0;">时间粒度变化</span>。把秒级序列聚合成分钟级、把分钟级聚合成小时级，是典型下采样；把缺失区间插值补回更细时间轴、把低频预测映射回更高频输出，则是典型上采样。多尺度时序模型、patch 化建模、层级卷积和部分状态空间模型，经常都在不同时间分辨率之间切换，以便同时看到短期波动与长期趋势。</p>
<div class="blog_h2"><span class="graybg">时序预测</span></div>
<p>时序预测（Time Series Forecasting）要求模型根据历史观测预测未来值。它是最经典的时间序列任务，典型输出可以是未来一个点、多个时间步，或一个概率区间。ARIMA、指数平滑、状态空间模型代表传统统计路线；LSTM、TCN、Temporal Fusion Transformer、PatchTST 等则代表深度学习阶段的重要主线。</p>
<p>预测任务真正难的地方，不只是拟合历史曲线，而是处理趋势变化、节假日扰动、外部变量和概念漂移。也正因为如此，现代预测系统越来越强调多变量建模、概率预测和不确定性估计，而不是只输出一个单点数值。</p>
<p>从业务角度看，预测任务既可能是短期滚动预测，也可能是中长期规划预测。库存调度、能源调峰、产能规划和交通调度，对预测 horizon 和误差容忍度的要求完全不同。因此，单步预测、多步直接预测、递归预测和概率区间预测会在不同场景下分别占优。</p>
<p>现代时序预测还越来越重视全局模型与局部模型的分工。过去很多系统为每条序列单独建模；今天则更常见用一个共享模型同时学习成千上万条相关序列，再用个体特征或静态协变量进行条件化。这种“跨序列共享统计强度”的思路，是深度时序模型相对传统逐序列建模的重要优势之一。</p>
<div class="blog_h3"><span class="graybg">经典路线：统计模型与状态空间模型</span></div>
<p>时序预测的第一条主线，是统计建模。ARIMA、指数平滑、卡尔曼滤波与更一般的状态空间模型，依靠对趋势、季节性、噪声和滞后结构的显式假设来做预测。它们的优势在于参数可解释、数据需求小、在短数据和规则业务场景里很稳；不足在于面对复杂非线性、多变量交互和跨序列共享时，表达能力有限。</p>
<p>即便到了 2026 年，这条线也没有过时。很多高价值场景依然把统计模型作为强基线、集成组件或可解释 fallback，而不是被完全替代。</p>
<div class="blog_h3"><span class="graybg">深度学习路线：从 RNN 到 PatchTST 与 TFT</span></div>
<p>深度学习阶段把时序预测从“单序列局部拟合”推进到“跨序列共享表示”。LSTM 和 TCN 先解决了非线性时序依赖与长程建模问题；Temporal Fusion Transformer（TFT）进一步把静态协变量、已知未来特征和注意力机制统一到一个框架里；PatchTST 则把时间序列切成 patch 后再建模，显著提升了长窗口预测能力。</p>
<p>这些模型之所以成为主线，不只是因为精度更高，而是因为它们更适合现代工业数据形态：多变量、长历史、跨实体共享和复杂外部特征。</p>
<div class="blog_h3"><span class="graybg">2026 主线：foundation model 与 zero-shot forecasting</span></div>
<p>近两年的最大变化，是 forecasting 已经明显 foundation model 化。TimesFM 把 patched decoder 风格带入 zero-shot 预测；Chronos 通过量化和 tokenization 把数值序列转成类似语言建模问题；Timer-XL、Moirai-MoE、Moirai 2.0 则继续把统一预测、长上下文和大规模预训练推进到更强的泛化范式。到了 2025 年后，这条线已经不再只是“能不能 zero-shot”，而是在比较不同 foundation model 的概率预测、效率和跨域泛化能力。</p>
<p>更进一步，Chronos-2 已开始明确处理多变量和带协变量 forecasting，说明时间序列 foundation model 正在从单变量 zero-shot 走向更接近真实业务的统一预测接口。</p>
<div class="blog_h2"><span class="graybg">时序分类</span></div>
<p>时序分类（Time Series Classification）研究的是：给定一段时间序列，它属于哪种状态、设备类型、故障模式或行为类别。例如心电序列诊断、设备振动模式分类、行为轨迹识别和金融形态识别，都属于这一方向。</p>
<p>和普通分类不同，时序分类的判别依据不只是特征值大小，还包括随时间展开的模式形状、局部子序列、相位偏移和持续时间。传统路线常用动态时间规整（DTW）和距离度量；现代路线则更依赖 CNN、RNN、TCN 和 Transformer 编码器把整段序列压成判别性表示。</p>
<p>这类任务的关键难点在于时间对齐并不总是稳定。同一种故障或生理模式，可能只是出现得更早、更晚、更快或更慢；若模型只盯逐点位置，很容易把本质相同的模式判成不同类别。这也是 shapelet、DTW 和时序卷积长期重要的原因，它们都在试图捕捉“形状相似”而非“逐时刻完全重合”。</p>
<p>很多时序分类系统还会把原始序列与频域特征、统计量和事件级特征结合起来。例如心电诊断往往同时使用波形形状、心率变异和频谱信息；工业故障诊断则会结合振动信号的时域和频域表示。也就是说，时序分类并不排斥特征工程，而是更强调表征与序列结构的联合设计。</p>
<div class="blog_h3"><span class="graybg">经典路线：DTW、shapelet 与特征工程</span></div>
<p>时序分类最早的强基线并不是深网，而是距离度量与判别子序列。DTW 通过弹性对齐处理速度和相位差异，shapelet 则显式寻找最有区分力的局部片段。这些方法的价值在于，它们直接针对时序分类最核心的问题发力：模式可能相同，但发生得更早、更晚、更长或更短。</p>
<p>因此，即使深度学习已经成为主流，DTW、shapelet 和频域/统计特征工程仍然是理解时序分类问题结构的重要参照系。</p>
<div class="blog_h3"><span class="graybg">深度表示：CNN、Transformer 与多视角特征</span></div>
<p>现代时序分类更多依赖可学习表示。CNN 擅长抽取局部形状模式，RNN 擅长序列状态传递，Transformer 则更容易统一长程依赖与多变量交互。很多系统还会把时域、频域、事件级特征与原始序列共同编码，因为在医疗、设备诊断和生理监测中，单一视角往往不足以稳定区分类别。</p>
<p>也正因为如此，时序分类越来越像“多视角表征学习”问题，而不只是把一串数字塞进分类头。</p>
<div class="blog_h3"><span class="graybg">2026 主线：foundation model 开始进入分类，但任务差异仍大</span></div>
<p>Foundation model for time series 的综述已经把 classification 作为重要下游任务之一，但这一方向和 forecasting 不同，尚未形成像 TimesFM 或 Chronos 那样一统天下的标准接口。更现实的趋势是：预训练时序 backbone 正在成为通用特征提取器，而具体分类任务仍然强依赖领域先验、数据规整程度和标签定义。对于 irregular time series，PYRREGULAR 与 Time-IMM 这类基准也在提醒一个事实：真实分类数据远比标准 benchmark 更脏、更稀疏、更异步。</p>
<div class="blog_h2"><span class="graybg">时序异常检测</span></div>
<p>时序异常检测（Time Series Anomaly Detection）要求系统发现时间轴上“不应该出现”的模式，例如设备突发故障、传感器漂移、交易异常、网络流量攻击或病理生理异常。它和静态异常检测不同，因为异常可能表现为单点突刺，也可能表现为趋势漂移、周期破坏或跨变量联动失衡。</p>
<p>这类任务通常需要同时建模局部短时异常和长期基线变化。也正因为如此，很多方法会把预测误差、重建误差、密度估计和变点检测结合起来，而不是只依赖一种分数。实际系统里，异常检测还必须控制误报，因为一个高召回但误报极多的报警系统通常无法真正落地。</p>
<p>异常检测的另一个难点是标注稀缺且异常定义随业务变化。很多系统几乎拿不到完整异常标签，只能依赖正常样本建模、弱监督标签或人工审核回流。因此，重建式自编码器、预测式模型、对比学习表示和在线阈值更新会同时出现，目的都是在标注极少的条件下维持可用性。</p>
<p>从业务流程看，异常检测从来不是“算出一个分数”就结束。系统通常还需要解释异常来源、聚合同类告警、控制告警风暴，并把检测结果回写给运维或业务规则引擎。一个无法解释、无法抑制重复噪声的异常模型，即便离线分数再高，也很难长期部署。</p>
<div class="blog_h3"><span class="graybg">经典思路：预测误差、重建误差与变点检测</span></div>
<p>时序异常检测最经典的三条主线，是预测式、重建式和变点检测式。预测式方法假设正常模式可预测，异常会导致未来偏离；重建式方法假设正常模式可压缩重构，异常重建困难；变点检测则直接寻找统计分布发生突变的时刻。三者各有盲点，因此工业系统往往不会只押一类分数。</p>
<p>例如某些异常不是突发尖峰，而是缓慢漂移；某些复杂正常模式本身难预测，容易被误判为异常。正是这些失败模式使单一方法很难长期独立承担生产告警。</p>
<div class="blog_h3"><span class="graybg">标注稀缺、误报控制与业务闭环</span></div>
<p>异常检测最麻烦的地方，是异常标签天然稀少、定义动态变化，而且误报成本往往极高。一个报警系统若每天触发数千条低质量告警，最终通常会被业务团队忽略。因此，检测模块之外还必须有告警聚合、相似事件去重、优先级分层和人工反馈回流机制。</p>
<p>也就是说，异常检测从一开始就不是纯模型问题，而是模型、规则和运维流程共同构成的系统问题。</p>
<div class="blog_h3"><span class="graybg">2026 主线：zero-shot TSAD 与 foundation model 的目标错位</span></div>
<p>近年的 foundation model 综述已经把 anomaly detection 纳入时间序列 FM 任务图谱，但这一方向也暴露出明显困难。Towards Foundation Models for Zero-Shot Time Series Anomaly Detection 指出，许多 TSAD foundation model 仍然依赖 reconstruction-style pretraining，而这会造成目标错位：模型可能把复杂正常模式当异常，也可能忽略微弱异常。TimeRCD 一类路线正是在试图修正这种预训练与检测目标之间的不匹配。</p>
<p>因此，2026 年的关键问题不是“能否把 anomaly 也塞进 foundation model”，而是“预训练目标是否真正服务异常检测”。</p>
<div class="blog_h2"><span class="graybg">序列分割与事件检测</span></div>
<p>有些时序任务关心的不是未来值，也不是整段类别，而是“某个事件在什么时候开始、什么时候结束”。这类任务可以概括为序列分割（Segmentation）与事件检测（Event Detection），例如设备工作阶段切换、睡眠阶段划分、故障开始点定位和行为片段检测。</p>
<p>它和视频时序定位很相似，本质上都是在一条时间轴上找结构边界。模型既可以做逐时刻标签预测，也可以直接回归事件区间。难点在于边界通常模糊、标注不稳定，而且不同事件持续时间可能相差很大，因此它常常需要局部感受野和长时上下文同时存在。</p>
<p>从方法上看，这类任务既可以视作序列标注，也可以视作变点检测或区间提议问题。前者强调逐时刻分类，适合边界较密的任务；后者更强调直接发现状态切换点或事件片段，适合阶段结构明显的场景。不同业务会在“时间分辨率”和“边界解释性”之间做不同取舍。</p>
<p>事件检测类任务通常还面临标签定义主观的问题。一个故障究竟从轻微波动开始算，还是从明确超阈值开始算，不同专家的标准可能不同。因此，序列分割系统往往需要和规则、人工审阅和后验平滑结合，而不是完全依赖端到端模型一次性裁决。</p>
<div class="blog_h3"><span class="graybg">逐时刻标注、区间提议与变点检测</span></div>
<p>序列分割与事件检测至少有三种常见建模视角：逐时刻标注、区间提议和变点检测。逐时刻标注适合边界较密、类别较清晰的任务；区间提议更像视频时序定位，适合持续事件；变点检测则更强调统计结构何时发生变化。这三类方法在业务里常常混用，因为真实事件既有类别属性，也有边界和持续时间属性。</p>
<p>这也是为什么事件检测很难只靠一个分类头解决，它天然带有定位结构。</p>
<div class="blog_h3"><span class="graybg">边界不确定性与因果变化</span></div>
<p>序列边界常常带有人为主观性。一个故障、睡眠阶段或生理事件究竟从哪个采样点开始，本身就可能存在专家分歧。因此，边界建模越来越重视后验平滑、不确定性和结构性解释。近期的 causal discovery-driven change point detection 也说明，在多变量场景里，变化不仅是“数值变了”，还可能是变量间因果关系变了。</p>
<p>这让 change point detection 从简单阈值问题，逐步推进到更接近结构变化分析。</p>
<div class="blog_h3"><span class="graybg">2026 主线：从边界定位走向结构变化理解</span></div>
<p>到 2026 年，序列分割和事件检测的一个明显趋势，是从“找出边界”走向“理解边界意味着什么”。在工业、医学和气候应用里，系统越来越被要求说明变化的来源、涉及哪些变量、是暂时波动还是机制改变。因此，这一方向正在与 anomaly detection、causal modeling 和根因分析更紧密地连起来。</p>
<div class="blog_h2"><span class="graybg">缺失值补全与插值</span></div>
<p>真实时间序列几乎不可能完全规整。传感器会掉点，日志会延迟，医学监测会中断，用户序列会稀疏。因此，缺失值补全（Imputation）与插值（Interpolation）本身就是独立的重要任务，而不是简单预处理步骤。</p>
<p>补全任务的难点在于，缺失机制本身可能带有信息。例如某设备在高负荷时更容易丢包，这时“缺失”本身就是异常信号。也正因为如此，现代时序补全越来越强调显式建模观测掩码、采样不规则性和跨变量依赖，而不是机械地做线性插值。</p>
<p>在技术路线上，简单插值适合短缺口和平稳信号，但在长缺失段、多变量强耦合或不规则采样场景中往往不够。卡尔曼滤波、Gaussian Process、BRITS 一类循环补全模型，以及基于注意力的不规则时间编码方法，都是为了更好地利用时间间隔和跨变量关联。</p>
<p>补全质量对下游影响极大。若补全结果被继续输入预测、诊断或告警系统，错误的插值不仅会模糊真实异常，还可能制造虚假模式。因此，成熟系统通常会同时输出补全值和不确定性，甚至保留“哪些点原本缺失”的掩码供下游模型继续使用。</p>
<div class="blog_h3"><span class="graybg">经典路线：插值、滤波与概率过程</span></div>
<p>补全问题最早依赖线性插值、样条插值、卡尔曼滤波和 Gaussian Process 等统计工具。它们在短缺口、平稳数据和噪声较轻场景中依然很强，因为这些方法天然带有平滑性或状态转移假设，且不需要大规模训练数据。</p>
<p>这条路线今天仍然不可忽视，尤其是在数据量小、需要不确定性解释或采样机制较规则的应用里。</p>
<div class="blog_h3"><span class="graybg">深度补全：BRITS、不规则采样与掩码建模</span></div>
<p>深度学习把补全推进到更复杂的场景。BRITS 等模型通过双向递归和显式掩码，把缺失值本身纳入状态更新；后续基于注意力和连续时间编码的方法，则更适合不规则采样和长缺失段。这里最关键的变化是：模型不再把缺失当作纯粹的空洞，而是把“缺失发生在何时、持续多久、与哪些变量同时缺失”都当作信息来源。</p>
<p>这正是补全与普通插值的分界线。现代补全更接近结构推断，而不是单纯光滑曲线。</p>
<div class="blog_h3"><span class="graybg">2026 主线：真实世界 irregularity 成为中心问题</span></div>
<p>Time-IMM 和 PYRREGULAR 这类基准都在强调一个现实：很多时间序列并不是规则采样、单模态、偶尔丢点，而是从生成机制上就存在异步、多模态和大面积缺失。也正因为如此，2026 年的补全主线已经明显从“规则表格上的插值技巧”转向“真实世界 irregular multivariate time series 的统一建模”。在这类场景里，补全、预测和分类之间的边界也开始模糊，因为系统经常需要一边补一边推理。</p>
<div class="blog_h2"><span class="graybg">多变量时序与时空预测</span></div>
<p>很多真实系统不是单变量序列，而是一组彼此耦合的时间变量。电网负荷、交通路网、工业产线和天气监测，都要求模型同时处理多变量依赖和空间关系。这时问题会从普通时序预测扩展成多变量时序（Multivariate Time Series）甚至时空预测（Spatio-temporal Forecasting）。</p>
<p>一旦进入这类场景，模型就不仅要问“昨天如何影响今天”，还要问“邻近节点、相连设备或上下游系统如何共同影响当前点位”。这也是为什么图神经网络、时空 Transformer 和图时序混合模型会在这一方向持续出现，因为单纯把每条序列独立建模，往往会遗漏系统级联动结构。</p>
<p>时空预测的核心在于同时建模两种依赖：时间上的滞后与空间上的传播。交通拥堵会沿路网扩散，电力负荷会在网络中传导，城市传感器也存在显著的空间相关。于是，邻接图、地理距离、拓扑结构和外部事件就会与时间编码一起进入模型，而不再只是普通特征列。</p>
<p>这类系统通常还要处理尺度不一致问题。某些变量以分钟为粒度变化，某些外部因素以小时或天为粒度变化；某些节点高度同步，某些节点几乎独立。真正高质量的时空模型，不只是堆叠一个 GNN 和一个 Transformer，而是要明确哪些依赖值得共享、哪些依赖必须局部化建模。</p>
<div class="blog_h3"><span class="graybg">经典路线：VAR、图建模与时空神经网络</span></div>
<p>多变量时序最早的重要工具包括 VAR 和更一般的多变量状态空间模型，它们擅长刻画变量之间的线性滞后依赖。进入深度学习阶段后，图神经网络、时空卷积和时空 Transformer 成为主线，因为它们更适合同时建模时间依赖与空间拓扑传播。交通、气象和电网等任务尤其受益于这种结构建模。</p>
<p>这里的核心思想非常清晰：节点之间不是独立序列，而是处在传播网络中。</p>
<div class="blog_h3"><span class="graybg">图结构、传播机制与外部变量</span></div>
<p>时空预测真正困难的地方，在于图并不总是静态的。交通路网会受到事故和施工影响，电网负荷会被天气和节假日扰动，工业节点关系也可能因工况变化而改变。因此，现代时空模型越来越强调动态图结构、外部变量融合和多尺度传播，而不是假设一个固定邻接矩阵就足够。</p>
<p>也正因为如此，时空预测和普通 multivariate forecasting 的界限越来越取决于“变量间关系是否需要显式结构建模”。</p>
<div class="blog_h3"><span class="graybg">2026 主线：foundation model 与 world-model 化倾向</span></div>
<p>2025 年的 foundation model 综述已经把 multivariate 与 spatiotemporal tasks 明确纳入统一时间序列 FM 版图；而 2025 年的 Spatiotemporal Forecasting as Planning 进一步把时空预测推进到 generative world model 与 model-based reinforcement learning 框架。这说明当前前沿不再满足于“给出一个最优点预测”，而是开始探索如何生成多种可能未来、支持规划和非可微目标优化。</p>
<p>换句话说，时空预测正在从被动预测器，逐步向可用于决策和模拟的环境模型演进。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-3">人工智能理论知识 - 主要应用领域</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/ai-knowledge-quick-ref-3/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>人工智能理论知识 - 算法和机器学习</title>
		<link>https://blog.gmem.cc/ai-knowledge-quick-ref-2</link>
		<comments>https://blog.gmem.cc/ai-knowledge-quick-ref-2#comments</comments>
		<pubDate>Wed, 15 Apr 2026 13:41:23 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[AI]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=42295</guid>
		<description><![CDATA[<p>这一篇从常用算法进入机器学习基础概念、经典机器学习与神经网络，重点讨论“模型如何被构造、训练、评估与正则化”。前一篇给出了数学语言，这一篇开始进入真正的建模问题：数据怎样表示，损失怎样定义，优化怎样推进，不同模型家族各自擅长什么；再往后才会过渡到 Transformer 与大语言模型。 常用算法 基础数据结构和算法 这一节处理的核心问题是：当面对搜索、更新、统计、调度、最短路径、依赖分析或训练流水线等任务时，数据应该怎样组织，操作应该怎样执行，才能既正确又高效。数据结构（Data Structure）决定“数据在内存里如何表示”，算法（Algorithm）决定“在这种表示上如何完成查询、插入、删除、遍历、排序与优化”。很多系统性能问题，本质上不是算力不足，而是底层组织方式与操作方式不匹配。 可以把它理解成“仓库布局与搬运规则”的组合：同样一批货物，若排成连续货架、串成链式节点、组织成树状目录，或连接成路网，后续的查找、插入、合并与运输成本会完全不同。现代 AI 工程虽然把注意力集中在模型上，但数据加载器、特征流水线、参数缓存、向量检索、计算图调度、图学习和索引系统，最终都建立在这些基础结构之上。 结构 / 算法 核心能力 典型复杂度 常见场景 数组 / 动态数组 按下标随机访问；顺序扫描效率高 访问 ；中间插入/删除 <a class="read-more" href="https://blog.gmem.cc/ai-knowledge-quick-ref-2">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-2">人工智能理论知识 - 算法和机器学习</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>这一篇从常用算法进入机器学习基础概念、经典机器学习与神经网络，重点讨论“模型如何被构造、训练、评估与正则化”。前一篇给出了数学语言，这一篇开始进入真正的建模问题：数据怎样表示，损失怎样定义，优化怎样推进，不同模型家族各自擅长什么；再往后才会过渡到 Transformer 与大语言模型。</p>
<div class="blog_h1"><span class="graybg">常用算法</span></div>
<div class="blog_h2"><span class="graybg">基础数据结构和算法</span></div>
<p>这一节处理的核心问题是：当面对搜索、更新、统计、调度、最短路径、依赖分析或训练流水线等任务时，数据应该怎样组织，操作应该怎样执行，才能既正确又高效。数据结构（Data Structure）决定“数据在内存里如何表示”，算法（Algorithm）决定“在这种表示上如何完成查询、插入、删除、遍历、排序与优化”。很多系统性能问题，本质上不是算力不足，而是底层组织方式与操作方式不匹配。</p>
<p>可以把它理解成“仓库布局与搬运规则”的组合：同样一批货物，若排成连续货架、串成链式节点、组织成树状目录，或连接成路网，后续的查找、插入、合并与运输成本会完全不同。现代 AI 工程虽然把注意力集中在模型上，但数据加载器、特征流水线、参数缓存、向量检索、计算图调度、图学习和索引系统，最终都建立在这些基础结构之上。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">结构 / 算法</td>
<td style="text-align: center;">核心能力</td>
<td style="text-align: center;">典型复杂度</td>
<td style="text-align: center;">常见场景</td>
</tr>
</thead>
<tbody>
<tr>
<td>数组 / 动态数组</td>
<td>按下标随机访问；顺序扫描效率高</td>
<td>访问 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span>；中间插入/删除 <span displaypfx="inline-" class="mathjax-container">\(O(n)\)</span></td>
<td>张量、批数据、embedding、排序、滑动窗口</td>
</tr>
<tr>
<td>链表</td>
<td>已知位置后插入/删除代价低</td>
<td>局部插删 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span>；查找 <span displaypfx="inline-" class="mathjax-container">\(O(n)\)</span></td>
<td>LRU、任务拼接、频繁重排的序列</td>
</tr>
<tr>
<td>栈（Stack）</td>
<td>后进先出（LIFO）</td>
<td>push / pop / top 均为 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span></td>
<td>递归展开、表达式解析、单调栈</td>
</tr>
<tr>
<td>队列（Queue）</td>
<td>先进先出（FIFO）</td>
<td>enqueue / dequeue 均为 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span></td>
<td>BFS、任务队列、流式缓冲</td>
</tr>
<tr>
<td>哈希表（Hash Table）</td>
<td>按键快速索引</td>
<td>平均查找/插入/删除 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span></td>
<td>字典、词表、缓存、去重</td>
</tr>
<tr>
<td>Bloom Filter</td>
<td>近似集合成员查询</td>
<td>插入/查询均为 <span displaypfx="inline-" class="mathjax-container">\(O(k)\)</span></td>
<td>缓存预检查、去重预过滤、存储层键存在性判断</td>
</tr>
<tr>
<td>树（Tree）</td>
<td>表达层次关系与有序结构</td>
<td>平衡查找常为 <span displaypfx="inline-" class="mathjax-container">\(O(\log n)\)</span></td>
<td>索引、优先队列、前缀匹配、规则分裂</td>
</tr>
<tr>
<td>图（Graph）</td>
<td>表达任意对象之间的关系</td>
<td>遍历通常为 <span displaypfx="inline-" class="mathjax-container">\(O(|V|+|E|)\)</span></td>
<td>社交网络、知识图谱、路线规划、依赖分析</td>
</tr>
</tbody>
</table>
<p>复杂度表只给出渐近上界，不能直接替代工程判断。真实系统还要同时考虑缓存友好性（Cache Locality）、常数项、并发开销、内存占用和实现复杂度。例如链表在理论上支持常数时间插入，但它对 CPU 缓存并不友好；数组在理论上中间插入较慢，但顺序扫描极快，因此在现代硬件上经常更有优势。</p>
<div class="blog_h3"><span class="graybg">数组与动态数组</span></div>
<p>数组（Array）处理的核心问题是：当元素类型一致、数量可以按顺序编号时，如何支持最低成本的随机访问与批量扫描。它的关键性质是<span style="background-color: #c0c0c0;">连续内存（Contiguous Memory）</span>。若每个元素大小为 <span displaypfx="inline-" class="mathjax-container">\(s\)</span>，首地址为 <span displaypfx="inline-" class="mathjax-container">\(\text{base}\)</span>，则第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个元素地址为</p>
<span displaypfx="" class="mathjax-container">\[\text{addr}(a_i)=\text{base}+i\cdot s\]</span>
<p>这个式子说明数组访问为何是 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span>：位置可以直接计算，不需要沿指针逐步跳转。矩阵、张量、mini-batch、时间序列缓存、本地特征块和 embedding 表中的一行，本质上都依赖这种“地址可算”的结构。</p>
<p>数组的代价也非常明确：若在中间插入或删除元素，后面的元素必须整体搬移，因此复杂度通常是 <span displaypfx="inline-" class="mathjax-container">\(O(n)\)</span>。这意味着数组适合“读多写少、顺序稳定”的任务，不适合“在任意位置频繁插入”的任务。</p>
<p>动态数组（Dynamic Array）是在数组上的工程扩展：容量不足时申请更大的连续空间，把原有元素整体拷贝过去，再继续追加。一次扩容代价很高，但若容量按倍数增长，则追加操作的均摊（Amortized）复杂度仍可视为 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span>。Python 的 list、C++ 的 vector、Java 的 ArrayList 都遵循这一思想。</p>
<p>直觉上，数组像按编号排好的货架：拿第 137 件货非常快，但若要把一件货塞进中间，后面整排货物都要整体后移。</p>
<div class="blog_h3"><span class="graybg">链表</span></div>
<p>链表（Linked List）处理的是另一类问题：当序列顺序经常变化时，能否避免数组那样的大规模搬移。链表不要求连续内存，而是让每个节点（Node）保存数据和指向下一个节点的指针（Pointer）；双向链表（Doubly Linked List）还会额外保存前驱指针。</p>
<p>若已经拿到某个节点的位置，那么在其前后插入或删除节点只需要调整局部指针，代价通常是 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span>。但链表无法像数组那样通过下标直接定位第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个元素，查找往往必须从头逐个走过去，因此通常是 <span displaypfx="inline-" class="mathjax-container">\(O(n)\)</span>。</p>
<p>链表适合做“结构改动频繁、定位方式不是按下标而是按已有节点句柄”的任务。例如 LRU 缓存中，经常需要把刚访问的元素移到头部；若配合哈希表记录节点位置，链表就能高效完成重排。</p>
<p>链表像一串用绳子串起来的标签。改顺序很方便，但想直接摸到第 500 个标签，就只能沿着绳子一个个数过去。</p>
<div class="blog_h3"><span class="graybg">栈（Stack）</span></div>
<p>栈（Stack）定义的是一种<span style="background-color: #c0c0c0;">后进先出（Last In First Out, LIFO）</span>的访问约束。它处理的问题不是“怎样存更多数据”，而是“怎样强制最近进入的状态最先退出”。典型操作包括入栈 push、出栈 pop 与查看栈顶 top，它们都发生在同一端，因此实现代价通常是 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span>。</p>
<p>函数调用栈、递归回溯、表达式求值、括号匹配、深度优先搜索中的显式状态保存，都依赖这种结构。其本质是把“尚未处理完的上下文”按嵌套顺序压起来，等内部任务结束后再按相反顺序恢复。</p>
<p>单调栈（Monotonic Stack）是栈在算法中的重要变体。它通过维护一个单调递增或单调递减的栈，把“下一个更大元素”“柱状图最大矩形”等问题从 <span displaypfx="inline-" class="mathjax-container">\(O(n^2)\)</span> 降到 <span displaypfx="inline-" class="mathjax-container">\(O(n)\)</span>。原因在于每个元素最多入栈和出栈各一次。</p>
<div class="blog_h3"><span class="graybg">队列（Queue）</span></div>
<p>队列（Queue）定义的是<span style="background-color: #c0c0c0;">先进先出（First In First Out, FIFO）</span>的访问约束。进入得早的元素先被处理，后来进入的元素排在尾部等待。它适合表达“任务排队、波前扩张、按到达顺序消费”的过程。</p>
<p>广度优先搜索（Breadth-First Search, BFS）之所以使用队列，正是因为 BFS 要按距离层层扩展：先处理距离起点为 1 的节点，再处理距离为 2 的节点。这个“分层推进”机制与 FIFO 完全一致。</p>
<p>循环队列（Circular Queue）通过把底层数组首尾相连，可以避免频繁搬移；双端队列（Deque）则允许两端都做插入和删除，因此能够支持滑动窗口最值、0-1 BFS 等更复杂的算法模式。</p>
<div class="blog_h3"><span class="graybg">哈希表（Hash Table）</span></div>
<p>哈希表（Hash Table）处理的核心问题是：当数据按“键（Key）”组织，而不是按位置组织时，如何快速找到对应的值（Value）。其思想是先通过哈希函数（Hash Function）把键映射成一个整数，再把这个整数映射到桶（Bucket）或槽位（Slot）上。</p>
<p>若哈希函数分布均匀，且装载因子（Load Factor）控制合理，则查找、插入和删除的平均复杂度都可接近 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span>。这正是词表映射、去重、缓存索引、参数名字典和特征 ID 映射大量采用哈希表的原因。</p>
<p>哈希表的难点在冲突（Collision）处理。多个键可能映射到同一位置，常见解决方案包括链地址法（Separate Chaining）和开放定址法（Open Addressing）。因此“哈希表平均 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span>”并不意味着永远常数时间，它依赖于哈希函数质量、负载控制和冲突处理策略。</p>
<div class="blog_h4"><span class="graybg">Bloom Filter（布隆过滤器）</span></div>
<p>Bloom Filter 本质上属于<span style="background-color: #c0c0c0;">概率型数据结构（Probabilistic Data Structure）</span>，更准确地说，是一种近似集合成员查询结构（Approximate Membership Query, AMQ）。它解决的问题并不是“把元素精确存下来”，而是“用极小内存快速判断某元素是否可能出现过”。因此它通常作为哈希表、数据库索引或缓存系统之前的一层预过滤结构。</p>
<p>Bloom Filter 由一个长度为 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 的比特数组 <span displaypfx="inline-" class="mathjax-container">\(B\in\{0,1\}^m\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个哈希函数 <span displaypfx="inline-" class="mathjax-container">\(h_1,\dots,h_k\)</span> 构成，其中每个哈希函数都把元素 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 映射到区间 <span displaypfx="inline-" class="mathjax-container">\(\{0,1,\dots,m-1\}\)</span> 中的一个位置。插入元素 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 时，执行</p>
<span displaypfx="" class="mathjax-container">\[B[h_1(x)]=B[h_2(x)]=\cdots=B[h_k(x)]=1\]</span>
<p>查询元素 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 时，检查 <span displaypfx="inline-" class="mathjax-container">\(B[h_1(x)],\dots,B[h_k(x)]\)</span>。只要其中至少有一个位置为 0，就可以断定 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 一定不在集合中；若这些位置全部为 1，则只能说明 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 可能在集合中。</p>
<p>这一定义直接带来 Bloom Filter 最重要的判定性质：<span style="background-color: #c0c0c0;">它允许假阳性（False Positive），但不允许假阴性（False Negative）</span>。原因在于，不同元素可能把同一批 bit 位置反复置为 1，于是一个从未插入过的元素也可能“碰巧”命中全 1；但只要某个位置仍为 0，就说明没有任何已插入元素覆盖过这条哈希路径，因此该元素一定不存在。</p>
<p>设一共插入了 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个元素，则某个 bit 在所有插入结束后仍为 0 的概率近似为 <span displaypfx="inline-" class="mathjax-container">\(\left(1-\frac{1}{m}\right)^{kn}\approx e^{-kn/m}\)</span>。于是查询一个未出现元素时， <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个位置恰好都为 1 的假阳性概率近似为</p>
<span displaypfx="" class="mathjax-container">\[p\approx \left(1-e^{-kn/m}\right)^k\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 是 bit 数组长度， <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 是已插入元素数， <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 是哈希函数数目， <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 是假阳性概率。这个公式揭示了 Bloom Filter 的基本权衡： <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 越大，冲突越少； <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 越大，数组越接近被“染满”； <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 太小会降低区分能力，太大则会过度占满 bit 位。固定 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 时，常见的近似最优选择是</p>
<span displaypfx="" class="mathjax-container">\[k\approx \frac{m}{n}\ln 2\]</span>
<p>直觉上，Bloom Filter 像一排共享的指示灯。每来一个元素，就按亮若干盏灯；查询时，只要对应灯中有一盏没亮，就可以确认它从未出现过。若全部亮着，也只能说明“这些灯曾被某些元素点亮过”，却不能保证就是当前这个元素点亮的。</p>
<p>Bloom Filter 最适合用于“先快速排除绝大多数不存在项，再把少量可疑项交给精确结构复核”的场景。例如缓存系统可先判断某个 key 是否可能在缓存中，若 Bloom Filter 直接给出“不在”，就可以避免无意义回源；LSM-Tree 存储系统可用它判断某个键是否可能存在于某个 SSTable；爬虫去重、黑名单预过滤、向量检索候选预筛都大量使用这一思想。</p>
<p>Bloom Filter 的边界也很明确。第一，它不保存原始元素，因此不能枚举集合内容，也不能像哈希表那样返回关联值。第二，标准 Bloom Filter 不支持安全删除，因为把某个 bit 清零可能误伤其他元素留下的痕迹；若确实需要删除，通常要改用计数 Bloom Filter（Counting Bloom Filter）。第三，当假阳性代价非常高、系统需要完全精确的成员判断时，应优先使用哈希表、B 树或其他精确索引结构。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/bloom.jpg"><img class="alignnone size-full wp-image-41339" src="https://blog.gmem.cc/wp-content/uploads/2026/03/bloom.jpg" alt="bloom" width="1024" height="559" /></a></p>
<div class="blog_h3"><span class="graybg">树（Tree）</span></div>
<p>树（Tree）处理的是“层次结构”和“递归划分”问题。树中的节点之间具有父子关系，除了根节点（Root）外，每个节点都有唯一父节点。它天然适合表达目录层级、决策分裂、区间划分、优先级组织与前缀共享。</p>
<p>树之所以重要，在于它把原本线性的搜索空间组织成递归结构，使很多操作能通过“向左还是向右”“进入哪个子树”逐步缩小问题规模。若每次都能把候选空间缩小到原来的一半，复杂度就会从线性级下降到对数级。</p>
<div class="blog_h4"><span class="graybg">二叉树与遍历</span></div>
<p>二叉树（Binary Tree）规定每个节点至多有两个孩子。前序遍历（Preorder）、中序遍历（Inorder）、后序遍历（Postorder）和层序遍历（Level-order）分别对应不同的信息读取顺序：前序适合序列化结构，中序适合读取二叉搜索树中的有序键，后序适合先处理子问题再合并，层序适合按深度观察整体形状。</p>
<div class="blog_h4"><span class="graybg">二叉搜索树与平衡树</span></div>
<p>二叉搜索树（Binary Search Tree, BST）在每个节点上保持“左子树键值更小、右子树键值更大”的顺序约束，因此查找、插入和删除都可以沿着比较路径进行。若树高度为 <span displaypfx="inline-" class="mathjax-container">\(h\)</span>，这些操作的复杂度一般与 <span displaypfx="inline-" class="mathjax-container">\(O(h)\)</span> 成正比。</p>
<p>问题在于普通 BST 在极端情况下会退化成链表，此时 <span displaypfx="inline-" class="mathjax-container">\(h=n\)</span>。平衡树（Balanced Tree）如 AVL 树、红黑树（Red-Black Tree）通过旋转（Rotation）维护高度受控，使 <span displaypfx="inline-" class="mathjax-container">\(h=O(\log n)\)</span>，从而把查找、插入和删除稳定在对数复杂度。数据库索引和有序映射容器大量依赖这一思想。</p>
<div class="blog_h4"><span class="graybg">堆与优先队列</span></div>
<p>堆（Heap）维护的是局部顺序，而不是整棵树的全局有序性：在最小堆（Min-Heap）中，每个父节点都不大于子节点，因此根节点始终是全局最小值；最大堆（Max-Heap）则相反。它通常用数组实现，父子下标关系可以直接计算。</p>
<p>堆最适合实现优先队列（Priority Queue）：每次都要快速取出当前最重要、最小或最大的元素时，插入和弹出都只需 <span displaypfx="inline-" class="mathjax-container">\(O(\log n)\)</span>。Dijkstra、A* 搜索、任务调度、Top-K 维护和流式中位数都大量依赖优先队列。</p>
<div class="blog_h4"><span class="graybg">Trie 与前缀结构</span></div>
<p>Trie 树（Prefix Tree）把字符串按前缀共享组织起来。若插入单词集合 <span displaypfx="inline-" class="mathjax-container">\(\{w_1,\dots,w_m\}\)</span>，公共前缀只存一次，因此“是否存在某个前缀”“以某前缀开头的词有多少”都可以沿字符路径直接完成。</p>
<p>Trie 特别适合词典匹配、自动补全、敏感词过滤和子词切分。它牺牲了一部分空间，换来按字符长度而非按词典规模进行搜索的能力。</p>
<div class="blog_h3"><span class="graybg">图（Graph）</span></div>
<p>图（Graph）处理的是最一般的关系结构。若顶点集合为 <span displaypfx="inline-" class="mathjax-container">\(V\)</span>，边集合为 <span displaypfx="inline-" class="mathjax-container">\(E\)</span>，则图可写成 <span displaypfx="inline-" class="mathjax-container">\(G=(V,E)\)</span>。树本质上是图的一个特殊子类，但图允许环、允许多条连接、允许方向和权重，因此能表达社交关系、知识链接、网页跳转、道路网络、依赖图与神经网络计算图。</p>
<p>图的常见表示方式有邻接矩阵（Adjacency Matrix）和邻接表（Adjacency List）。前者适合稠密图，能 <span displaypfx="inline-" class="mathjax-container">\(O(1)\)</span> 判断两点是否相连；后者适合稀疏图，空间复杂度更低，遍历邻居更高效。</p>
<div class="blog_h4"><span class="graybg">BFS 与 DFS</span></div>
<p>广度优先搜索（BFS）与深度优先搜索（DFS）是图遍历的两种基本组织方式。BFS 使用队列按层推进，适合无权最短路、层次扩展与最少步数问题；DFS 使用递归或显式栈沿一条路径尽量走深，适合回溯、环检测、拓扑排序、强连通分量与树形动态规划。</p>
<p>对邻接表表示的图，两者的时间复杂度通常都是 <span displaypfx="inline-" class="mathjax-container">\(O(|V|+|E|)\)</span>。区别不在渐近复杂度，而在访问顺序：BFS 保证按距离层层扩展，DFS 更擅长描述“先深入、后回退”的结构性问题。</p>
<div class="blog_h4"><span class="graybg">最短路径</span></div>
<p>最短路径（Shortest Path）问题处理的是：从起点到终点，总代价最小的路径是什么。若图无权，BFS 就能得到边数最少的路径；若边权非负，常用 Dijkstra 算法。它每次从优先队列中取出当前距离估计最小的顶点，并尝试松弛（Relax）相邻边。</p>
<p>Dijkstra 的核心更新为</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{dist}[v]=\min\big(\mathrm{dist}[v],\mathrm{dist}[u]+w(u,v)\big)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{dist}[u]\)</span> 是当前已知的从源点到 <span displaypfx="inline-" class="mathjax-container">\(u\)</span> 的最短距离估计， <span displaypfx="inline-" class="mathjax-container">\(w(u,v)\)</span> 是边权。这个公式表达的是最短路的本质：若“先到 <span displaypfx="inline-" class="mathjax-container">\(u\)</span>，再走到 <span displaypfx="inline-" class="mathjax-container">\(v\)</span>”更便宜，就更新对 <span displaypfx="inline-" class="mathjax-container">\(v\)</span> 的距离认知。</p>
<div class="blog_h4"><span class="graybg">拓扑排序</span></div>
<p>拓扑排序（Topological Sort）处理的是有向无环图（Directed Acyclic Graph, DAG）中的依赖顺序。若边 <span displaypfx="inline-" class="mathjax-container">\(u\to v\)</span> 表示“<span displaypfx="inline-" class="mathjax-container">\(u\)</span> 必须先于 <span displaypfx="inline-" class="mathjax-container">\(v\)</span>”，那么拓扑序就是一种满足全部先后约束的线性排列。</p>
<p>课程先修关系、编译依赖、工作流调度、神经网络计算图执行次序，本质上都属于这一问题。拓扑排序的价值不只是“排出一个顺序”，而是把依赖图转成一条能够实际执行的流水线。</p>
<div class="blog_h4"><span class="graybg">最小生成树（Minimum Spanning Tree, MST）</span></div>
<p>最小生成树（Minimum Spanning Tree, MST）处理的是这样的问题：给定一个连通无向带权图 <span displaypfx="inline-" class="mathjax-container">\(G=(V,E)\)</span>，需要从边集合 <span displaypfx="inline-" class="mathjax-container">\(E\)</span> 中选出一部分边，把所有顶点连成一个整体，同时不产生环，并使总权重最小。若把所有生成树的集合记为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{T}(G)\)</span>，则标准形式可以写成</p>
<span displaypfx="" class="mathjax-container">\[T^*=\arg\min_{T\in \mathcal{T}(G)}\sum_{e\in T} w(e)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(T^*\)</span> 是最优生成树；<span displaypfx="inline-" class="mathjax-container">\(w(e)\)</span> 是边 <span displaypfx="inline-" class="mathjax-container">\(e\)</span> 的权重；<span displaypfx="inline-" class="mathjax-container">\(\sum_{e\in T} w(e)\)</span> 表示树中全部边的总代价。这里的“生成树”有三个同时成立的约束：第一， <span displaypfx="inline-" class="mathjax-container">\(T\subseteq E\)</span>；第二，图在边集 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 下必须连通；第三， <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 不能含环，因此边数必然满足 <span displaypfx="inline-" class="mathjax-container">\(|T|=|V|-1\)</span>。</p>
<p>这个定义明确了 MST 不是“找一棵看上去便宜的树”，而是在<span style="background-color: #c0c0c0;">所有能够覆盖全部顶点的无环连通方案</span>中做全局最小化。只强调“连通”会多出冗余边，只强调“边权小”又可能导致图不连通；MST 同时满足这两个条件。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/mst-graph.png"><img class="alignnone size-full wp-image-41335" src="https://blog.gmem.cc/wp-content/uploads/2026/03/mst-graph.png" alt="mst-graph" width="6242" height="2905" /></a></p>
<p>直觉上，可以把 MST 理解成“以最低总造价把一组城市接通，但不修多余的回路”。如果形成了环，说明这条网络中至少有一条边是重复支出；如果某些城市没有接入，说明方案根本不可用。MST 就是在“全覆盖”和“最低成本”之间取得最紧的平衡。</p>
<p>MST 成立的核心理论基础是<span style="background-color: #c0c0c0;">割性质（Cut Property）</span>：把顶点集切成两个不相交部分后，跨越这个切分的最小权边，一定存在于某棵最小生成树中。这个性质的含义是：局部最便宜的“安全边（Safe Edge）”可以被逐步加入，而不会破坏全局最优性。Prim 与 Kruskal 虽然组织方式不同，但本质上都在不断选择这样的安全边。</p>
<p>Prim 算法的思路是“从一个起点向外生长一棵树”。设当前已经纳入树中的顶点集合为 <span displaypfx="inline-" class="mathjax-container">\(S\)</span>，则 Prim 每一步都在所有满足 <span displaypfx="inline-" class="mathjax-container">\(u\in S,\ v\notin S\)</span> 的边中，选择权重最小的一条，把新顶点接入当前树。这个过程像不断把新城市接入已经建好的主干网，因此特别适合用优先队列维护“当前边界上最便宜的边”。若图用邻接表存储并配合二叉堆实现优先队列，时间复杂度通常为 <span displaypfx="inline-" class="mathjax-container">\(O(|E|\log |V|)\)</span>。</p>
<p>Kruskal 算法的思路是“按全图范围从便宜到昂贵依次选边”。它先对所有边按权重升序排序，然后从小到大扫描：若当前边连接的是两个不同连通块，就把它加入结果；若会在当前结构中形成环，就跳过。为了高效判断“两个端点是否已经连通”，Kruskal 通常配合并查集（Disjoint Set Union, DSU）。排序代价主导总复杂度，因此复杂度通常写成 <span displaypfx="inline-" class="mathjax-container">\(O(|E|\log |E|)\)</span>，与 <span displaypfx="inline-" class="mathjax-container">\(O(|E|\log |V|)\)</span> 在数量级上接近。</p>
<p>两种算法解决的是同一个优化问题，但适合的工程语境不同。Prim 更像“从局部网络不断扩张”，适合稠密图或从某个核心节点逐步向外建设的场景；Kruskal 更像“全局看所有候选边，再逐一合并连通块”，在边集天然可排序、图较稀疏时实现尤其直接。</p>
<p>一个最小例子可以把公式和过程连起来。设四个顶点 <span displaypfx="inline-" class="mathjax-container">\(A,B,C,D\)</span>，边权为： <span displaypfx="inline-" class="mathjax-container">\(w(A,B)=1\)</span>， <span displaypfx="inline-" class="mathjax-container">\(w(B,C)=2\)</span>， <span displaypfx="inline-" class="mathjax-container">\(w(A,C)=4\)</span>， <span displaypfx="inline-" class="mathjax-container">\(w(B,D)=3\)</span>， <span displaypfx="inline-" class="mathjax-container">\(w(C,D)=5\)</span>。Kruskal 会先按边权排序： <span displaypfx="inline-" class="mathjax-container">\((A,B),(B,C),(B,D),(A,C),(C,D)\)</span>。前 3 条边分别把 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(B\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(C\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(D\)</span> 连起来，此时已经得到 <span displaypfx="inline-" class="mathjax-container">\(|V|-1=3\)</span> 条边，且图连通无环，于是生成树为</p>
<span displaypfx="" class="mathjax-container">\[T=\{(A,B),(B,C),(B,D)\}\]</span>
<p>其总代价为</p>
<span displaypfx="" class="mathjax-container">\[\sum_{e\in T}w(e)=1+2+3=6\]</span>
<p>若改选边集 <span displaypfx="inline-" class="mathjax-container">\(\{(A,B),(A,C),(B,D)\}\)</span>，总代价是 <span displaypfx="inline-" class="mathjax-container">\(1+4+3=8\)</span>；若再加入 <span displaypfx="inline-" class="mathjax-container">\((B,C)\)</span>，虽然成本局部看不高，但边数会超过 <span displaypfx="inline-" class="mathjax-container">\(|V|-1\)</span> 并形成环，因此不再是树。这个例子把“最低成本”“连通”“无环”三项约束如何同时生效展示得很清楚。</p>
<p>MST 常见于网络布线、电力传输、骨架路网设计、图像分割、聚类和图压缩。层次聚类中的单链接（Single Linkage）就可以通过图的最小生成树来理解：先把点看成顶点，把样本间距离看成边权，再在 MST 上剪断最长的若干条边，就得到若干连通簇。这也是为什么 MST 不只是图论题型，而是很多数据分析与机器学习方法的底层结构。</p>
<p>MST 也有明确边界。它只适用于<span style="background-color: #c0c0c0;">无向、连通、带权</span>图上的“全连通最低总成本”问题；若任务要求的是“从源点到其余点的最短路”，应使用最短路径算法；若图有方向，目标就不再是普通 MST，而会进入最小树形图（Minimum Arborescence）等更复杂的问题。</p>
<div class="blog_h2"><span class="graybg">动态规划（Dynamic Programming, DP）</span></div>
<div class="blog_h3"><span class="graybg">背景和问题定义</span></div>
<p>动态规划（Dynamic Programming, DP）处理的是这样一类问题：目标是求一个全局最优值、最优路径，或所有路径的总和，但如果直接把所有可能性全部枚举出来，计算量会迅速爆炸。它常见于序列决策、路径规划、字符串匹配、图搜索，以及隐马尔可夫模型（HMM）、条件随机场（CRF）这类结构化预测模型。</p>
<p>这类问题通常有两个共同特征。第一，<span style="background-color: #c0c0c0;">重叠子问题（Overlapping Subproblems）</span>：同一个中间子问题会被反复计算。第二，<span style="background-color: #c0c0c0;">最优子结构（Optimal Substructure）</span>：大问题的最优解可以由小问题的最优解递推得到。例如，在长度为 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 的序列上，若每一步有 <span displaypfx="inline-" class="mathjax-container">\(|\mathcal{S}|\)</span> 个可能状态，直接枚举所有状态路径往往需要考虑 <span displaypfx="inline-" class="mathjax-container">\(|\mathcal{S}|^T\)</span> 条候选路径；当 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 稍大时，这种暴力方法几乎不可用。</p>
<div class="blog_h3"><span class="graybg">核心思想</span></div>
<p>动态规划的核心在于：<span style="background-color: #c0c0c0;">先定义能够代表子问题的状态，再写出状态之间的递推关系，并把已经算过的结果缓存下来复用</span>。因此，它本质上是一种计算组织方式，而不是某个固定公式。</p>
<p>一个直观比喻是出差换乘。设想要从起点出发，经过很多站点，最终到达目的地。暴力法会把“到达每一站的所有走法”全部记下来；动态规划不会这样做。它只会为每个中间站保留一份最有价值的摘要，例如“到达这个站的最低成本”或“到达这个站的最大得分”。当继续前往下一站时，系统只需要查这份账本，而不必回头展开所有历史路径。</p>
<p>因此，动态规划通常包含四个步骤：定义状态、定义边界条件、写出转移方程、确定计算顺序。状态定义决定“中间结果要存什么”；边界条件决定“第一步从哪里开始”；转移方程决定“当前结果如何从更小问题得到”；计算顺序则保证所有依赖项在使用前已经计算完毕。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/dp.jpg"><img class="alignnone size-full wp-image-41269" src="https://blog.gmem.cc/wp-content/uploads/2026/03/dp.jpg" alt="dp" width="1408" height="768" /></a></p>
<div class="blog_h3"><span class="graybg">为什么局部次优前缀不会漏掉全局最优</span></div>
<p>动态规划能够丢弃大量“暂时看起来不够好”的前缀路径，前提是<span style="background-color: #c0c0c0;">状态（State）已经完整刻画了未来决策所需的全部信息</span>。一旦这个条件成立，到达同一状态的两条前缀路径，未来能够接上的可行后缀集合完全相同，因此只需要保留其中更优的那一条。</p>
<p>设两条前缀路径都到达同一状态 <span displaypfx="inline-" class="mathjax-container">\(s\)</span>，其当前累计代价分别为 <span displaypfx="inline-" class="mathjax-container">\(f_1(s)\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(f_2(s)\)</span>，且 <span displaypfx="inline-" class="mathjax-container">\(f_1(s)\le f_2(s)\)</span>。若从状态 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 出发，后续任意可行决策产生的附加代价记为 <span displaypfx="inline-" class="mathjax-container">\(g(s)\)</span>，并且这个附加代价只由当前状态决定，而不再依赖此前的完整历史，则有</p>
<span displaypfx="" class="mathjax-container">\[f_1(s)+g(s)\le f_2(s)+g(s)\]</span>
<p>这个不等式表明：在同一状态上，较差的前缀会被较优前缀完全支配（Dominated）。无论后面接哪一段后缀路径，较差前缀都不可能反超。因此 Bellman 最优性原理允许动态规划只保留“到达该状态的最优值”，而不必保留全部历史路径。</p>
<p>所谓“一个当前次优的路径，后来却通向全局最优”，本质上对应另一种情形：这条路径与当前更优路径虽然看起来到达了同一个位置，但它们对未来的可行动作并不相同。此时它们实际上并不属于同一个状态，而是状态定义缺失了关键信息。</p>
<p>一个典型例子是带资源约束的路径规划。若状态只写成当前位置 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span>，那么两条到达同一格子的路径会被合并；但若其中一条还保留一次传送机会，另一条已经把传送用掉，则它们未来的决策空间显然不同。正确的状态应扩展为 <span displaypfx="inline-" class="mathjax-container">\((i,j,\mathrm{used})\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\((i,j,\mathrm{fuel})\)</span> 这类更完整的形式。只有在状态把“剩余资源、上一步动作、已使用预算、是否持仓”等会影响未来的因素都编码进去后，动态规划的剪枝才是安全的。</p>
<p>因此，动态规划处理“局部次优可能导向全局最优”的方式，不是保留所有看起来有潜力的路径，而是通过<span style="background-color: #c0c0c0;">正确设计状态，使真正会影响未来的差异体现在不同状态上</span>。同一状态内部只保留最优前缀；不同状态之间分别递推。动态规划的正确性，最终依赖的正是这一点：未来只依赖当前状态，而不依赖通向当前状态的完整历史。</p>
<div class="blog_h3"><span class="graybg">公式和详细解释</span></div>
<p>若记 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 为阶段或时间步， <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 为当前状态，一个非常典型的动态规划写法是：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{DP}[t,s]=\max_{s'\in \mathrm{Prev}(s)}\left(\mathrm{DP}[t-1,s']+\mathrm{score}(s',s,t)\right)\]</span>
<p>这条式子表达的是：要得到“第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步处于状态 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 时的最优值”，不必重新枚举所有完整路径，而是只需查看所有能够转移到 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 的前驱状态 <span displaypfx="inline-" class="mathjax-container">\(s'\)</span>，并在它们已有的最优值基础上，加上这一步的局部得分，再从中取最大。</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathrm{DP}[t,s]\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步、状态为 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 的最优子问题值。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathrm{Prev}(s)\)</span>：所有可以转移到状态 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 的前驱状态集合。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathrm{score}(s',s,t)\)</span>：从 <span displaypfx="inline-" class="mathjax-container">\(s'\)</span> 转移到 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 时，在第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步新增的局部得分或代价。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\max\)</span>：表示当前任务要找“最好的一条路径”。若任务目标是最小代价，则可改为 <span displaypfx="inline-" class="mathjax-container">\(\min\)</span>；若任务目标是把所有路径概率加总，则可改为 <span displaypfx="inline-" class="mathjax-container">\(\sum\)</span>。</li>
</ul>
<p>边界条件通常写成：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{DP}[1,s]=\mathrm{init}(s)+\mathrm{local}(s,1)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{init}(s)\)</span> 表示序列从状态 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 开始的初始代价或初始分数， <span displaypfx="inline-" class="mathjax-container">\(\mathrm{local}(s,1)\)</span> 表示第一步在该状态产生的局部贡献。没有这个起点，后续递推就无从展开。</p>
<p>动态规划真正带来的收益来自复杂度压缩。以一阶序列模型为例，若每一步有 <span displaypfx="inline-" class="mathjax-container">\(|\mathcal{S}|\)</span> 个候选状态、总长度为 <span displaypfx="inline-" class="mathjax-container">\(T\)</span>，暴力枚举往往需要考虑 <span displaypfx="inline-" class="mathjax-container">\(|\mathcal{S}|^T\)</span> 条完整路径；而若采用“时间步 + 当前状态”的动态规划状态定义，则通常只需在每个时间步枚举所有前驱状态，计算复杂度可以降为 <span displaypfx="inline-" class="mathjax-container">\(O(T|\mathcal{S}|^2)\)</span>。这种从指数级到多项式级的下降，正是动态规划在序列模型中不可替代的原因。</p>
<p>若任务不仅要求最优值，还要求恢复最优路径，则通常还会额外保存“当前最优值来自哪个前驱状态”的回溯信息（Backpointer）。这意味着动态规划不仅能回答“最优值是多少”，还能回答“这条最优路径具体怎么走”。</p>
<p>更重要的是，动态规划并不只对应一种运算。对于最优路径问题，递推中的核心运算往往是 <span displaypfx="inline-" class="mathjax-container">\(\max\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(\min\)</span>；对于总概率、配分函数这类问题，核心运算则是 <span displaypfx="inline-" class="mathjax-container">\(\sum\)</span>。这也是为什么 HMM 的维特比算法、前向算法，以及 CRF 的前向后向算法，虽然目标不同，但都属于动态规划。</p>
<div class="blog_h3"><span class="graybg">应用实例</span></div>
<p>在 HMM 中，动态规划最典型地体现在两类问题上。第一类是维特比算法（Viterbi Algorithm）：它要求“给定观测序列后，哪一条隐藏状态路径最可能”，因此递推中的核心运算是 <span displaypfx="inline-" class="mathjax-container">\(\max\)</span>。第二类是前向算法（Forward Algorithm）：它要求“所有隐藏状态路径合起来，总概率是多少”，因此递推中的核心运算是 <span displaypfx="inline-" class="mathjax-container">\(\sum\)</span>。两者使用的是同一张状态网格，只是“每一步如何聚合前驱信息”不同。</p>
<p>在 CRF 中，动态规划同样是核心计算工具。训练时，需要对所有可能标签路径做归一化，这对应配分函数（Partition Function）的计算；解码时，需要找得分最高的那条标签路径，这对应最优路径搜索。在线性链 CRF 中，这两件事都可以通过“时间步 + 当前标签”的动态规划状态来高效完成，否则若直接枚举所有标签序列，计算量会随序列长度呈指数增长。</p>
<p>因此，在机器学习语境里，动态规划可以概括为：<span style="background-color: #c0c0c0;">把原本必须整体枚举的结构化问题，改写成一系列局部状态上的递推计算，并通过缓存中间结果把重复计算消掉</span>。一旦看到“序列路径很多、局部决策可递推、同类子问题会重复出现”这三个信号，通常就应该优先考虑动态规划。</p>
<div class="blog_h4"><span class="graybg">例子 1：编辑距离（Edit Distance）</span></div>
<p>设字符串 <span displaypfx="inline-" class="mathjax-container">\(A=a_1a_2\dots a_m\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(B=b_1b_2\dots b_n\)</span>。编辑距离要回答的问题是：至少经过多少次插入、删除、替换，才能把 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 变成 <span displaypfx="inline-" class="mathjax-container">\(B\)</span>。若直接枚举所有编辑序列，可能性会指数增长；但若定义 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{DP}[i,j]\)</span> 表示“把 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的前 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个字符变成 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 的前 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个字符所需的最小编辑次数”，问题就能递推解决。</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{DP}[i,j]=\min\begin{cases}\mathrm{DP}[i-1,j]+1\\ \mathrm{DP}[i,j-1]+1\\ \mathrm{DP}[i-1,j-1]+\mathbf{1}(a_i\ne b_j)\end{cases}\]</span>
<p>这里三项分别对应：删除 <span displaypfx="inline-" class="mathjax-container">\(a_i\)</span>、插入 <span displaypfx="inline-" class="mathjax-container">\(b_j\)</span>、或把 <span displaypfx="inline-" class="mathjax-container">\(a_i\)</span> 替换成 <span displaypfx="inline-" class="mathjax-container">\(b_j\)</span>（若本来相同，则替换代价为 0）。边界条件是 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{DP}[0,j]=j\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathrm{DP}[i,0]=i\)</span>，因为空串变成长度为 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 的串需要做 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 次插入，反之需要做 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 次删除。这个例子非常典型地体现了动态规划：状态是前缀长度，转移是三种编辑操作，目标是求最小总代价。</p>
<div class="blog_h4"><span class="graybg">例子 2：网格最短路径（Grid Shortest Path）</span></div>
<p>设一个 <span displaypfx="inline-" class="mathjax-container">\(m\times n\)</span> 网格，每个格子 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 都有进入代价 <span displaypfx="inline-" class="mathjax-container">\(w_{i,j}\)</span>，只能向右或向下移动。问题是：从左上角走到右下角的最小总代价是多少。若定义 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{DP}[i,j]\)</span> 表示“到达格子 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 的最小总代价”，则递推很直接：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{DP}[i,j]=w_{i,j}+\min\big(\mathrm{DP}[i-1,j],\mathrm{DP}[i,j-1]\big)\]</span>
<p>因为到达 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 只有两种可能：从上方 <span displaypfx="inline-" class="mathjax-container">\((i-1,j)\)</span> 走下来，或从左侧 <span displaypfx="inline-" class="mathjax-container">\((i,j-1)\)</span> 走过来。边界条件是第一行与第一列只能沿单一路径累计。这个例子说明，动态规划并不局限于字符串或序列模型；只要问题具有“局部来源有限、全局目标可递推”的结构，就可以用同样的思路求解。</p>
<div class="blog_h2"><span class="graybg">贪心算法（Greedy Algorithm）</span></div>
<div class="blog_h3"><span class="graybg">背景和问题定义</span></div>
<p>贪心算法（Greedy Algorithm）处理的是这样一类问题：希望快速构造一个全局可行解，并且每一步都只做当前看来最优的局部选择，而不回头修改已经作出的决定。它广泛出现在排序、调度、压缩、近似优化，以及许多机器学习训练与推断流程中。</p>
<div class="blog_h3"><span class="graybg">核心思想</span></div>
<p>贪心的核心假设是：<span style="background-color: #c0c0c0;">当前最好的局部选择，能够导向全局最优，或至少导向足够好的近似解</span>。它像走山路时每一步都先选眼前最高、最稳的落脚点，而不是先把整座山的所有路径都完全规划出来。贪心的优势是快、简单、容易实现；风险是局部最优未必等于全局最优。</p>
<div class="blog_h3"><span class="graybg">公式和详细解释</span></div>
<p>若记第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步可选动作集合为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{A}_t\)</span>，一个抽象的贪心选择可写为：</p>
<span displaypfx="" class="mathjax-container">\[a_t^*=\arg\max_{a\in\mathcal{A}_t}\ \mathrm{score}(a\mid \text{current state})\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{score}(a\mid \text{current state})\)</span> 是当前状态下动作 <span displaypfx="inline-" class="mathjax-container">\(a\)</span> 的局部收益；<span displaypfx="inline-" class="mathjax-container">\(\arg\max\)</span> 表示从所有可选动作里挑出得分最高的那个。贪心算法关心的是“眼下哪一步最好”，而不是“未来所有步骤联合起来后哪条完整路径最好”。</p>
<p>因此，贪心方法是否正确，取决于问题本身是否满足贪心选择性质（Greedy-choice Property）。如果这个性质成立，局部最优就能拼成全局最优；如果不成立，贪心通常只能作为启发式方法或近似算法。</p>
<div class="blog_h3"><span class="graybg">应用实例</span></div>
<p>决策树训练就是一个典型例子。每个节点都不会提前规划整棵树的全局最优结构，而是只在当前节点上选择信息增益、基尼下降或误差下降最大的切分。这个过程本质上就是贪心：<span style="background-color: #c0c0c0;">每一步都先把当前最值得切的地方切开</span>。它训练快、解释性强，但也正因为是局部选择，单棵树通常不是全局最优树结构。</p>
<div class="blog_h2"><span class="graybg">分治算法（Divide and Conquer）</span></div>
<div class="blog_h3"><span class="graybg">背景和问题定义</span></div>
<p>分治算法（Divide and Conquer）处理的是“大问题可以被拆成若干个同结构小问题”的场景。它广泛出现在排序、搜索、矩阵运算、索引构建，以及大规模数据处理与并行计算中。</p>
<div class="blog_h3"><span class="graybg">核心思想</span></div>
<p>分治的思想可以概括为三步：<span style="background-color: #c0c0c0;">分解（Divide）— 递归求解（Conquer）— 合并（Combine）</span>。它像整理一大堆文档时，先按主题拆成若干小堆，再分别处理，最后再合并成有序结果。与动态规划不同，分治更强调“子问题相互独立”，而不是“子问题结果需要反复复用”。</p>
<div class="blog_h3"><span class="graybg">公式和详细解释</span></div>
<p>分治算法的时间复杂度常写成递推式：</p>
<span displaypfx="" class="mathjax-container">\[T(n)=aT\left(\frac{n}{b}\right)+f(n)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 是问题规模；<span displaypfx="inline-" class="mathjax-container">\(a\)</span> 表示被拆成多少个子问题；<span displaypfx="inline-" class="mathjax-container">\(n/b\)</span> 是每个子问题的规模；<span displaypfx="inline-" class="mathjax-container">\(f(n)\)</span> 是“分解 + 合并”本身的额外代价。这个式子不告诉我们具体怎么做，但它准确描述了分治算法的结构骨架。</p>
<p>例如，归并排序（Merge Sort）把长度为 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 的数组分成两个规模约为 <span displaypfx="inline-" class="mathjax-container">\(n/2\)</span> 的子数组，递归排好序后再线性合并，因此它的复杂度递推就是 <span displaypfx="inline-" class="mathjax-container">\(T(n)=2T(n/2)+O(n)\)</span>。</p>
<div class="blog_h3"><span class="graybg">应用实例</span></div>
<p>在机器学习工程中，分治思想常见于大规模近邻索引构建。例如构建 kd-tree 时，算法会按某个维度把样本集递归切成两半，再在左右子集上继续构树。这样得到的层次化空间划分，能显著加速后续的近邻搜索。它的本质并不是“学习一个模型”，而是通过递归拆分把原本需要全表扫描的搜索过程组织得更高效。</p>
<div class="blog_h2"><span class="graybg">图搜索与最短路径</span></div>
<div class="blog_h3"><span class="graybg">背景和问题定义</span></div>
<p>许多软件与机器学习问题都可以抽象成图（Graph）：节点（Node）表示状态、样本、词、网页或知识实体，边（Edge）表示转移、相似性、依赖关系或可达关系。图搜索与最短路径算法要回答的问题是：如何从起点高效找到目标节点，或找到总代价最小的一条路径。</p>
<div class="blog_h3"><span class="graybg">核心思想</span></div>
<p>图搜索的核心是“沿着边扩展状态空间，但尽量避免无意义的重复探索”。无权图最短路径常用广度优先搜索（BFS），因为它按层扩展，第一次到达目标通常就是步数最少的路径；带非负权图常用 Dijkstra，因为它总是优先扩展当前总代价最小的候选节点。</p>
<div class="blog_h3"><span class="graybg">公式和详细解释</span></div>
<p>带权最短路径算法里的基本更新步骤通常写成“松弛（Relaxation）”：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{dist}(v)=\min\big(\mathrm{dist}(v),\ \mathrm{dist}(u)+w(u,v)\big)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{dist}(u)\)</span> 是当前已知从起点到节点 <span displaypfx="inline-" class="mathjax-container">\(u\)</span> 的最小代价， <span displaypfx="inline-" class="mathjax-container">\(w(u,v)\)</span> 是边 <span displaypfx="inline-" class="mathjax-container">\(u\rightarrow v\)</span> 的权重， <span displaypfx="inline-" class="mathjax-container">\(\mathrm{dist}(u)+w(u,v)\)</span> 则是“先到 <span displaypfx="inline-" class="mathjax-container">\(u\)</span> 再走到 <span displaypfx="inline-" class="mathjax-container">\(v\)</span>”这条新候选路径的总代价。若它比当前记录的 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{dist}(v)\)</span> 更小，就更新。</p>
<p>这个式子看起来和动态规划很像，原因是二者都在做“由已知子结果递推新结果”。区别在于：图搜索更强调如何选择下一个要扩展的节点，以及如何在一般图结构中避免重复访问。</p>
<div class="blog_h3"><span class="graybg">应用实例</span></div>
<p>在语音识别、机器翻译和图搜索推断中，解码过程经常会把候选状态组织成图或格（Lattice）。此时，寻找最优输出序列本质上就是图上的路径搜索问题。很多动态规划解码器也可以从“图上最优路径”的角度理解，因此图搜索是连接通用软件算法与结构化机器学习推断的重要桥梁。</p>
<div class="blog_h2"><span class="graybg">二分查找（Binary Search）</span></div>
<div class="blog_h3"><span class="graybg">背景和问题定义</span></div>
<p>二分查找（Binary Search）处理的是“搜索空间有序，或可行性判断具有单调性”的问题。它不仅用于有序数组查找，也广泛用于阈值搜索、参数调优、数值逼近和工程系统中的边界定位。</p>
<div class="blog_h3"><span class="graybg">核心思想</span></div>
<p>二分查找的核心是：<span style="background-color: #c0c0c0;">每次利用单调性砍掉一半搜索空间</span>。它像猜数字游戏：如果知道答案一定在某个区间里，而且中点左侧和右侧满足不同性质，那么每问一次都能把候选范围减半。</p>
<div class="blog_h3"><span class="graybg">公式和详细解释</span></div>
<p>若当前搜索区间为 <span displaypfx="inline-" class="mathjax-container">\([l,r]\)</span>，中点通常取：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{mid}=\left\lfloor\frac{l+r}{2}\right\rfloor\]</span>
<p>接着依据单调判定函数 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{check}(\mathrm{mid})\)</span> 缩小区间：</p>
<ul>
<li>若 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{check}(\mathrm{mid})\)</span> 为真，说明答案在左半边或恰好是中点，则令 <span displaypfx="inline-" class="mathjax-container">\(r=\mathrm{mid}\)</span>。</li>
<li>若为假，说明答案在右半边，则令 <span displaypfx="inline-" class="mathjax-container">\(l=\mathrm{mid}+1\)</span>。</li>
</ul>
<p>算法正确性的关键不在于公式本身，而在于维护区间不变式（Invariant）：在每一步更新后，真正的答案仍然留在当前区间中。</p>
<div class="blog_h3"><span class="graybg">应用实例</span></div>
<p>在机器学习里，二分查找常用于阈值定位。例如，当需要找到“使召回率至少达到某个目标值的最小分类阈值”时，只要阈值越大召回率越低这一单调关系成立，就可以在阈值区间上做二分查找，而不必逐点穷举。类似地，很多数值求根、超参数边界搜索、分位数定位问题也都可写成二分框架。</p>
<div class="blog_h2"><span class="graybg">随机采样（Random Sampling）</span></div>
<div class="blog_h3"><span class="graybg">背景和问题定义</span></div>
<p>随机采样（Random Sampling）处理的是这样一类问题：总体太大、精确计算太贵，或者目标本身就是概率性的，因此只能通过抽样近似整体行为。它是统计学习、蒙特卡洛估计、bootstrap、自助重采样、mini-batch 训练和负采样的共同基础。</p>
<div class="blog_h3"><span class="graybg">核心思想</span></div>
<p>随机采样的核心是：<span style="background-color: #c0c0c0;">不必每次都看完整总体，而是通过足够有代表性的随机子样本估计总体性质</span>。它像民意调查：不可能每天逐个询问所有人，但若抽样方式合理，少量样本也能给出相对稳定的总体估计。</p>
<div class="blog_h3"><span class="graybg">公式和详细解释</span></div>
<p>若目标是估计随机变量 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 下某个函数 <span displaypfx="inline-" class="mathjax-container">\(f(X)\)</span> 的期望 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[f(X)]\)</span>，最常见的蒙特卡洛估计写为：</p>
<span displaypfx="" class="mathjax-container">\[\hat{\mu}=\frac{1}{n}\sum_{i=1}^{n} f(x_i),\qquad x_i\sim p(x)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(x_i\sim p(x)\)</span> 表示样本 <span displaypfx="inline-" class="mathjax-container">\(x_i\)</span> 是按分布 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span> 随机抽到的； <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 是样本数； <span displaypfx="inline-" class="mathjax-container">\(\hat{\mu}\)</span> 是用样本均值近似真实期望的估计量。样本越多，估计通常越稳定，但代价也越高。</p>
<p>这一思想在机器学习里非常普遍：SGD 不是每次都在全量数据上算梯度，而是用 mini-batch 的样本均值近似全数据梯度；negative sampling 不是对所有负类都求和，而是随机抽一小部分负样本近似完整目标。</p>
<div class="blog_h3"><span class="graybg">应用实例</span></div>
<p>bootstrap 是一个很典型的例子。随机森林训练时，会对原始训练集做有放回采样，得到多份不同的 bootstrap 子集，再分别训练多棵树。这里真正起作用的不是“树”本身，而是随机采样制造了多个略有差异的数据视角，从而让集成后的模型更稳。</p>
<div class="blog_h1"><span class="graybg">机器学习基础概念</span></div>
<p>机器学习基础概念（Machine Learning Foundations）回答四类核心问题：数据从哪里来、模型在学什么、模型为什么能泛化、结果该如何评价。把这些问题分开看，会比死记算法名称更有效：学习范式决定监督信号来自哪里，假设空间与归纳偏置决定模型愿意相信什么，数据集工程决定模型实际看到了什么，模型评估决定这些学习结果是否真的能迁移到未见样本。</p>
<div class="blog_h2"><span class="graybg">假设/目标/代价/损失</span></div>
<p>这四个词描述的是同一条“训练=优化”的概念链，但位于不同层级。把层级理清后，公式与实现会自然对齐：模型 <span displaypfx="inline-" class="mathjax-container">\(f_\theta\)</span> 先给出预测，再用损失函数把预测变成数值误差，最后把误差在数据集上汇总成代价函数，并加入正则/约束得到最终的目标函数。</p>
<div class="blog_h3"><span class="graybg">假设函数（Hypothesis Function）</span></div>
<p>假设函数（Hypothesis Function）也常被直接称为模型（Model）或预测函数（Predictor），记作 <span displaypfx="inline-" class="mathjax-container">\(f_\theta\)</span>。它回答的问题是：<span style="background-color: #c0c0c0;">给定输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，模型输出什么</span>。参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 决定这条映射的具体形状。</p>
<p>线性回归（Linear Regression）的假设函数是最经典的例子：</p>
<span displaypfx="" class="mathjax-container">\[\hat y=f_\theta(x)=\mathbf{w}^\top x+b,\quad \theta=(\mathbf{w},b)\]</span>
<div class="blog_h3"><span class="graybg">目标函数（Objective Function）</span></div>
<p>目标函数（Objective Function）记作 <span displaypfx="inline-" class="mathjax-container">\(J(\theta)\)</span>，是优化器真正要优化的函数。工程上最常见、也最清晰的写法是：<span style="background-color: #c0c0c0;">目标函数 = 代价函数 + 正则化项</span>（没有正则化时可视为正则项为 0，因此 <span displaypfx="inline-" class="mathjax-container">\(J(\theta)=L(\theta)\)</span>）。</p>
<span displaypfx="" class="mathjax-container">\[J(\theta)=L(\theta)+\lambda\,\Omega(\theta)\]</span>
<p>在线性回归里，若用 L2 正则（Ridge / Weight Decay），常见目标函数可以写成：</p>
<span displaypfx="" class="mathjax-container">\[J(\theta)=L(\theta)+\lambda\|\mathbf{w}\|_2^2\]</span>
<p>把 <span displaypfx="inline-" class="mathjax-container">\(L(\theta)\)</span> 展开后，就是：</p>
<span displaypfx="" class="mathjax-container">\[J(\theta)=\frac{1}{N}\sum_{i=1}^{N}(\mathbf{w}^\top x_i+b-y_i)^2+\lambda\|\mathbf{w}\|_2^2\]</span>
<div class="blog_h3"><span class="graybg">代价/成本函数（Cost Function）</span></div>
<p>代价函数/成本函数（Cost Function）记作 <span displaypfx="inline-" class="mathjax-container">\(L(\theta)\)</span>，通常指把样本损失在训练集上做平均或求和后的整体量，也就是经验风险（Empirical Risk）。不少教材会把它直接称为训练损失（training loss），并且在不引起歧义时把它与目标函数混用。</p>
<p>在线性回归里，常用“均方误差的平均”作为代价函数：</p>
<span displaypfx="" class="mathjax-container">\[L(\theta)=\frac{1}{N}\sum_{i=1}^{N}\ell_i(\theta)\]</span>
<p>把 <span displaypfx="inline-" class="mathjax-container">\(\ell_i(\theta)\)</span> 取为平方误差后，等价写法是：</p>
<span displaypfx="" class="mathjax-container">\[L(\theta)=\frac{1}{N}\sum_{i=1}^{N}(\mathbf{w}^\top x_i+b-y_i)^2\]</span>
<div class="blog_h3"><span class="graybg">损失函数（Loss Function）</span></div>
<p>损失函数（Loss Function）记作 <span displaypfx="inline-" class="mathjax-container">\(\ell\)</span>，通常定义在单个样本上，把“预测与目标的差距”映射为一个标量。它回答的问题是：<span style="background-color: #c0c0c0;">这一条样本我错了多少</span>。</p>
<p>在线性回归里，最常见的单样本损失是平方误差：</p>
<span displaypfx="" class="mathjax-container">\[\ell_i(\theta)=\ell(\hat y_i,y_i)=(\hat y_i-y_i)^2,\quad \hat y_i=f_\theta(x_i)\]</span>
<div class="blog_h2"><span class="graybg">假设空间、容量与归纳偏置</span></div>
<p>同一份训练数据，之所以会被不同模型学出完全不同的规律，根源在于每个模型都自带一套“允许学什么、不允许学什么”的结构约束。假设空间（Hypothesis Space）、模型容量（Model Capacity）与归纳偏置（Inductive Bias）共同描述的，就是这套约束。</p>
<div class="blog_h3"><span class="graybg">假设空间</span></div>
<p>假设空间（Hypothesis Space）是模型可表达函数的集合，常记为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{H}\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{H}=\{f_\theta:\theta\in\Theta\}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(f_\theta\)</span> 是由参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 决定的预测函数， <span displaypfx="inline-" class="mathjax-container">\(\Theta\)</span> 是参数可取的范围。这个定义的关键不在于“参数有多少”，而在于<span style="background-color: #c0c0c0;">模型最终允许出现哪些映射形状</span>。例如，一元线性回归的假设空间只包含直线；二次多项式回归的假设空间包含抛物线；深度神经网络的假设空间则更大，能表示复杂得多的非线性函数。</p>
<p>因此，训练并不是在所有可能函数中盲目搜索，而是在某个特定假设空间里找一个最合适的函数。假设空间太小，真实规律可能根本装不进去；假设空间太大，模型又容易把偶然噪声也解释成模式。</p>
<div class="blog_h3"><span class="graybg">模型容量</span></div>
<p>模型容量（Model Capacity）描述的是假设空间的表达能力有多强，也就是模型能拟合多复杂规律。容量高，不代表一定更好；它只表示模型“有能力”表示复杂函数。是否真的学得好，还取决于数据量、正则化、优化过程和任务本身。</p>
<p>容量可以从多个角度理解。参数更多通常意味着容量更高，但这不是唯一标准；树的深度、核方法的核函数形式、特征维度、网络层数、隐藏维度、注意力头数，都会改变容量。工程上常用一个朴素判断：<span style="background-color: #c0c0c0;">如果模型连训练集主要结构都拟合不了，容量偏低；如果训练集几乎完美、验证集却明显变差，容量往往偏高或约束不足</span>。</p>
<p>容量与复杂度控制始终是一组平衡。表格数据上的浅层树模型可能已经足够；图像、语音、自然语言这类高度复杂任务，则通常需要更高容量的模型族。容量本身不是缺点，关键在于它是否与数据规模和任务难度匹配。</p>
<div class="blog_h4"><span class="graybg">欠容量（Undercapacity）</span></div>
<p>欠容量（Undercapacity）指模型或可训练适配器的表达能力不足，无法为当前任务提供足够大的可行函数空间。它讨论的是<span style="background-color: #c0c0c0;">模型有没有能力表示这类规律</span>，因此属于成因层概念。</p>
<p>这和欠拟合不同。欠拟合是结果层现象，表示当前训练结果不够好；欠容量只是欠拟合的一种常见原因。一个模型可能因为容量太小而欠拟合，也可能因为学习率不对、训练步数不足、输入被截断、特征表达差而欠拟合。反过来，一个高容量模型如果训练明显不充分，也会暂时呈现欠拟合外观。</p>
<p>工程上判断欠容量，常见信号包括：训练 loss 长期降不下去；训练集指标存在明显硬上限；增大模型尺寸、隐藏维度、树深、LoRA rank 或可训练模块后，训练集和验证集一起改善。若这些现象同时出现，就更像是表达能力本身不够，而不是单纯还没训练够。</p>
<div class="blog_h3"><span class="graybg">归纳偏置</span></div>
<p>归纳偏置（Inductive Bias）是模型在有限样本下从已见数据推广到未见数据时，默认采用的结构性偏好。只靠训练集上有限个点，无法唯一确定整个输入空间上的函数；模型之所以还能做出泛化判断，是因为它隐含地偏好某些解释，而排斥另一些解释。</p>
<p>把学习目标写成经验风险最小化时，这一点会更清楚：</p>
<span displaypfx="" class="mathjax-container">\[\hat f=\arg\min_{f\in\mathcal{H}}\hat R_n(f),\qquad \hat R_n(f)=\frac{1}{n}\sum_{i=1}^{n}\ell(f(x_i),y_i)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\hat R_n(f)\)</span> 是经验风险（Empirical Risk），表示函数 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 在训练集上的平均损失； <span displaypfx="inline-" class="mathjax-container">\(\hat f\)</span> 是最终选出的模型。关键约束正是 <span displaypfx="inline-" class="mathjax-container">\(f\in\mathcal{H}\)</span>：优化并不是在所有可能函数中找最优解，而是在一个被模型结构预先限制过的空间里找解。这个限制本身就是归纳偏置。</p>
<p>归纳偏置的来源很多。线性模型偏好线性关系；KNN（K-Nearest Neighbors）偏好局部相似样本给出相似输出；卷积神经网络（CNN）偏好局部连接与平移等变（Translation Equivariance）；树模型偏好分段常数的轴对齐切分；Transformer 则偏好通过注意力在 token 之间建立可变依赖。正则化、数据增强、参数共享、预训练初始化、优化器的更新轨迹，也都会进一步塑造模型的归纳偏置。</p>
<div class="blog_h2"><span class="graybg">期望风险、经验风险与结构风险</span></div>
<p>在统计学习（Statistical Learning）里，训练目标可以从三个层次来理解：期望风险（Expected Risk）描述模型在真实数据分布上的平均误差；经验风险（Empirical Risk）描述模型在有限训练集上的平均误差；结构风险（Structural Risk）则在经验风险之外，把模型复杂度一并纳入考虑。三者回答的是同一个问题的不同版本：<span style="background-color: #c0c0c0;">模型到底应该怎样才算“学得好”</span>。</p>
<div class="blog_h3"><span class="graybg">期望风险</span></div>
<p>期望风险也常被称为真实风险（True Risk）或总体风险（Population Risk）。若真实数据来自未知分布 <span displaypfx="inline-" class="mathjax-container">\(P(X,Y)\)</span>，模型为 <span displaypfx="inline-" class="mathjax-container">\(f\)</span>，单样本损失为 <span displaypfx="inline-" class="mathjax-container">\(\ell(f(x),y)\)</span>，则期望风险定义为：</p>
<span displaypfx="" class="mathjax-container">\[R(f)=\mathbb{E}_{(x,y)\sim P}\big[\ell(f(x),y)\big]\]</span>
<p>这条式子的含义很直接：把模型放到所有可能出现的真实样本上，计算平均损失。理论上，这才是机器学习真正想最小化的对象，因为泛化能力最终取决于模型在未知数据上的表现，而不是只取决于训练集上的表现。</p>
<p>困难在于，真实分布 <span displaypfx="inline-" class="mathjax-container">\(P(X,Y)\)</span> 并不可见。训练时手里只有有限样本，而没有“全体可能数据”的上帝视角。因此，期望风险通常不能被直接计算，只能被估计。</p>
<div class="blog_h3"><span class="graybg">经验风险</span></div>
<p>经验风险是用训练集对期望风险做出的现实近似。设训练集为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{D}=\{(x_i,y_i)\}_{i=1}^{n}\)</span>，则经验风险定义为：</p>
<span displaypfx="" class="mathjax-container">\[\hat R_n(f)=\frac{1}{n}\sum_{i=1}^{n}\ell\big(f(x_i),y_i\big)\]</span>
<p>它就是模型在当前这批已观测样本上的平均损失。工程上常见的 training loss，本质上反映的正是经验风险，或它的 mini-batch 近似。经验风险最小化（Empirical Risk Minimization, ERM）的训练逻辑也很朴素：既然期望风险不可直接计算，就先把训练集上的平均损失压低。</p>
<p>ERM 的关键前提是：训练集足够代表真实分布。当样本数量增加且采样足够合理时，经验风险通常会更接近期望风险；但在有限样本条件下，两者并不相等。二者之间的差距，本质上就是泛化误差（Generalization Gap）的来源。若模型只是把训练集中的偶然模式、局部噪声和标注误差也一并记住，那么 <span displaypfx="inline-" class="mathjax-container">\(\hat R_n(f)\)</span> 可以很低，而 <span displaypfx="inline-" class="mathjax-container">\(R(f)\)</span> 仍然很高，这正是过拟合（Overfitting）的典型形式。</p>
<div class="blog_h3"><span class="graybg">结构风险</span></div>
<p>结构风险（Structural Risk）是在经验风险之外，再把模型复杂度或假设空间规模纳入考虑的目标。它对应的思想是结构风险最小化（Structural Risk Minimization, SRM）：模型不仅要在训练集上拟合得好，还要避免复杂到足以随意记忆有限样本。</p>
<p>在统计学习理论的严格表述中，SRM 常写成在一族嵌套假设空间之间做选择；在工程实践里，它更常以“经验风险 + 复杂度惩罚”的形式出现，例如：</p>
<span displaypfx="" class="mathjax-container">\[J(f)=\hat R_n(f)+\lambda\,\Omega(f)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\Omega(f)\)</span> 是复杂度项（Complexity Penalty）， <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 控制数据拟合与复杂度约束之间的权衡。L2 正则化（L2 Regularization）、L1 正则化（L1 Regularization）、Weight Decay、早停（Early Stopping）以及对模型深度、宽度和树复杂度的限制，都可以看作结构风险最小化思想的具体实现。</p>
<p>因此，结构风险并不是对经验风险的否定，而是对 ERM 的补充：在有限样本条件下，<span style="background-color: #c0c0c0;">仅仅把训练误差压到最低，并不能保证模型在真实分布上表现最好</span>。模型必须同时控制复杂度，才能让“训练集上学到的规律”更有机会迁移到未见样本。</p>
<div class="blog_h3"><span class="graybg">为什么它重要</span></div>
<p>这三者共同构成了机器学习里最基本的张力。期望风险是理论上真正想优化的目标，但它不可直接见；经验风险是训练时可观测、可优化的替代量；结构风险则提醒我们，有限样本下不能把“训练集表现更好”直接等同于“真实世界表现更好”。换句话说，监督学习不仅是在找一个拟合训练集的函数，更是在有限数据和有限模型约束下，寻找一个最可能泛化的解释。</p>
<p>从这里继续往下，就会自然出现另一个问题：如果高维真实数据本身就带有强结构约束，那么经验风险为何常能在有限样本下逼近期望风险，模型又为何能够泛化到未见样本？流形假设（Manifold Hypothesis）正是对这个问题的一条几何回答：真实数据并不会任意填满整个高维空间，而是集中在某个低维、连续、受约束的结构附近。</p>
<div class="blog_h2"><span class="graybg">流形假设</span></div>
<p>流形假设（Manifold Hypothesis）给出了现代机器学习里一条极其重要的几何直觉：现实世界中有意义的数据，虽然表面上嵌在极高维空间里，但真正有效的变化自由度通常远低于表观维度。也就是说，高维观测往往不是填满整个空间，而是集中分布在一个低维流形（Low-dimensional Manifold）附近。</p>
<p>图像是最容易理解的例子。一个 <span displaypfx="inline-" class="mathjax-container">\(1024\times 1024\)</span> 的 RGB 图像在像素空间中维度极高，但自然图像并不会均匀占据这个巨大空间：物体形状、光照条件、视角变化、相机成像规律与纹理结构都受到强约束。因此，“像真实猫照片”的图像实际上只落在高维像素空间中的极小区域里。文本也类似。一个长度为 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 的 token 序列组合数极其巨大，但真正同时符合语法、语义和任务约束的文本，只占离散组合空间中很小的一部分。模型之所以能够泛化，一个重要原因正是：它并不需要学会覆盖整个高维空间，而只需要学会沿着这些低维结构建模。</p>
<div class="blog_h3"><span class="graybg">什么是流形</span></div>
<p>流形（Manifold）首先是一个几何对象。它的关键性质不是“弯不弯”，而是<span style="background-color: #c0c0c0;">局部上看起来像普通的低维欧几里得空间，整体上却可以弯曲、卷曲并嵌入到更高维空间中</span>。更正式地说，若一个集合对其上每一点，都存在一个足够小的邻域，可以用 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^d\)</span> 中的局部坐标平滑描述，那么这个集合就可以看作一个 <span displaypfx="inline-" class="mathjax-container">\(d\)</span> 维流形。</p>
<p>地球表面是最直观的例子。站在操场或街道上时，局部地面几乎是平的，可以用二维坐标定位；但从整体看，地球表面显然不是二维平面，而是嵌入三维空间中的弯曲曲面。因此，地球表面就是一个嵌在三维空间里的二维流形。对只能沿地面运动的观察者而言，真正相关的不是三维空间全部坐标，而是地表上那两个局部自由度。</p>
<p>机器学习教材里常见的瑞士卷（Swiss Roll）把这个概念进一步可视化。可以先想象一张二维纸，再把它卷进三维空间。卷起来之后，样本点在外部看来落在三维空间里，但沿着纸面定位某一点时，真正需要的仍然只是二维坐标。外在维度变高了，内在结构却没有变。这正是流形概念在机器学习里最重要的几何直觉：<span style="background-color: #c0c0c0;">数据的表观维度可以很高，但它的内在维度却可能很低</span>。</p>
<p>这类卷曲结构还带来另一个机器学习里非常关键的后果：<span style="background-color: #c0c0c0;">嵌入空间中的欧氏距离（Euclidean Distance）与流形上的测地距离（Geodesic Distance）并不一定一致</span>。两点在外部空间里看起来可能很近，因为一条直线可以直接“穿过空气”连接它们；但若真实数据只能沿流形本身变化，那么真正相关的距离应当是沿曲面或曲线走过去的那条路径长度。流形学习（Manifold Learning）之所以强调邻域图、测地近似和局部结构，正是因为外部直线距离经常不能反映数据在内在结构上的真实远近关系。</p>
<p>放到高维数据上，这个判断尤其关键。一张 <span displaypfx="inline-" class="mathjax-container">\(1000\times1000\)</span> 的灰度图像在像素空间里有一百万维，但“真实人脸图像”显然不会填满整个一百万维空间。姿态、光照、表情、年龄、拍摄距离等因素彼此耦合，使真实样本只落在高维像素空间中一个极薄、极小、受连续约束的区域附近。文本也是同样的逻辑：虽然 token 组合空间极其巨大，但真正同时满足语法、语义、上下文与任务约束的句子，只会沿着某种低维结构变化，而不会任意填满整个离散组合空间。</p>
<p>因此，在机器学习语境中谈流形，真正想表达的并不是完整搬运微分几何的全部形式系统，而是一个更直接的判断：<span style="background-color: #c0c0c0;">有意义的数据并不会随机散落在高维空间中，而是集中在某个低维、连续、受约束的结构附近</span>。后面关于自由度、主成分、隐空间、低维近似以及 LoRA 任务子空间的讨论，都是围绕这个判断展开的不同形式化视角。</p>
<div class="blog_h3"><span class="graybg">自由度</span></div>
<p>沿着前面“表观维度高、内在结构低”的判断继续往下走，就会自然落到自由度（Degrees of Freedom）这个概念上。表观维度说的是“数据在形式上有多少个坐标轴”；自由度说的是“这些数据实际上有多少种彼此独立的有效变化方式”。二者并不相同。一个对象可以嵌在极高维空间里，但真正能变化的自由度却很少。</p>
<p>例如，一张脸部图片在像素空间里有数百万维，但很多像素并不能独立随意变化：头部转向、光照强弱、表情变化、年龄纹理、拍摄距离这些因素彼此耦合，共同决定了大部分像素的联动变化。因此，“一张脸”看起来是高维数组，真正支配它变化的自由度却远小于像素总数。文本也一样。句子表面上由许多 token 组成，但语法结构、主题、语气、说话者意图与上下文约束，使它不可能在每个位置上完全独立自由地变化。</p>
<p>这也是流形假设真正重要的地方：它不是抽象地说“数据在低维流形上”，而是在说<span style="background-color: #c0c0c0;">有效自由度远少于表观维度</span>。一旦把这层理解清楚，后面关于主成分、隐空间、隐主题、低秩近似乃至 LoRA 的很多思想都会变得顺理成章，因为它们都在试图用更少的自由度，去抓住决定数据或参数变化的核心结构。</p>
<div class="blog_h3"><span class="graybg">主成分、隐空间与隐主题</span></div>
<p>一旦接受了“高维数据实际靠近低维结构”这一点，后面许多术语就会自然连起来。主成分（Principal Components）强调几何视角：在一组高维数据里，哪些方向承载了最主要的变化。隐空间（Latent Space）强调表示视角：把原始高维观测压缩到一个更低维、但仍保留关键信息的内部空间。隐主题（Latent Topics）则更偏语义视角：在文本与文档分解里，低维方向常常可以被解释为若干潜在语义因素，例如“体育”“金融”“法律”这类人类能命名的主题轴。</p>
<p>这三个词并不完全同义，但常常指向同一个底层事实：<span style="background-color: #c0c0c0;">高维观测可以通过少数主导方向或潜在因子来近似描述</span>。主成分更强调方差最大的坐标轴；隐空间更强调模型内部那间低维“房间”；隐主题则是在某些任务里，对这些低维方向做出的语义解释。它们分别对应几何、表示与语义三种语言，但共享同一条低维结构主线。</p>
<div class="blog_h3"><span class="graybg">PCA 与低维近似</span></div>
<p>PCA（Principal Component Analysis）是这条思路最经典、也最直接的算法形式。它在无监督条件下寻找方差最大的几个方向，并把数据投影到这些方向张成的低维子空间中。若数据矩阵为 <span displaypfx="inline-" class="mathjax-container">\(X\)</span>，PCA 本质上是在找一个低维线性子空间，使投影后的重建误差尽量小。在线性代数上，这与奇异值分解（SVD）直接对应：保留最大的前 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 个奇异值及其奇异向量，就得到最佳的 rank-<span displaypfx="inline-" class="mathjax-container">\(r\)</span> 近似。</p>
<p>这类思想并不只存在于经典降维里。自动编码器（Autoencoder）通过瓶颈层学习隐空间；潜在语义分析（Latent Semantic Analysis, LSA）和主题模型在文档-词矩阵里抽取潜在主题；词向量、句向量与深度表示模型把高维离散符号压缩到稠密向量空间。它们的目标函数和可解释性不同，但都默认：原始高维观测背后存在一个更低维、更有结构的变化空间。</p>
<div class="blog_h3"><span class="graybg">LoRA 与任务相关子空间</span></div>
<p>LoRA（Low-Rank Adaptation）通过把参数更新 <span displaypfx="inline-" class="mathjax-container">\(\Delta W\)</span> 限制在一个低秩子空间中，实现对大模型的参数高效微调（PEFT）。它的核心做法不是直接改写原始权重矩阵的全部自由度，而是把更新写成两个低秩矩阵的乘积：</p>
<span displaypfx="" class="mathjax-container">\[\Delta W = BA,\quad B\in\mathbb{R}^{d_{\text{out}}\times r},\ A\in\mathbb{R}^{r\times d_{\text{in}}},\ r\ll \min(d_{\text{in}},d_{\text{out}})\]</span>
<p>LoRA 应放在“低维结构”这条主线上理解，但不能把它与 PCA 直接等同。它的核心不是对输入数据做主成分分析，而是对模型参数更新施加低秩约束。</p>
<p>这个约束的含义是：模型不能在完整高维参数空间中任意改动，而只能在一个 rank-<span displaypfx="inline-" class="mathjax-container">\(r\)</span> 的低维更新子空间里移动。从思想上看，它确实与“只保留主导方向”高度相似；若事后对某个全量更新矩阵做 SVD，最佳低秩近似也会只保留最主要的奇异方向。但 LoRA 学到的并不是传统 PCA 意义上“数据方差最大”的主成分，而是<span style="background-color: #c0c0c0;">对当前任务损失下降最有用的低维更新方向</span>。前者是无监督的统计主轴，后者是由反向传播和任务目标共同决定的优化子空间。</p>
<p>因此，把 LoRA 理解为“逼迫模型只在少数主导方向上修改参数”是成立的；但这些方向更准确地说是任务相关的低维适配方向，而不是直接等同于原始数据的 PCA 主成分。也正因为如此，LoRA 与内在维度（Intrinsic Dimension）讨论天然相连：如果一个下游任务真正需要修改的有效自由度本来就不高，那么让模型只在一个低秩子空间里更新，不仅不会显著损失性能，反而会自动抑制大量无意义的噪声方向。</p>
<div class="blog_h2"><span class="graybg">学习范式</span></div>
<p>这里先按监督信号来源划分学习范式。监督学习（Supervised Learning）、无监督学习（Unsupervised Learning）、自监督学习（Self-supervised Learning）和强化学习（Reinforcement Learning）回答的是同一个问题：模型训练时的监督信号究竟来自哪里。它们属于同一分类标准，因此可以并列讨论。</p>
<div class="blog_h3"><span class="graybg">监督学习</span></div>
<p>监督学习（Supervised Learning）使用带标签的数据对 <span displaypfx="inline-" class="mathjax-container">\((x,y)\)</span> 训练模型：输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 是特征（Feature），输出 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 是目标或标签（Label）。模型学习的不是“记住答案”，而是学习一个映射 <span displaypfx="inline-" class="mathjax-container">\(f_\theta:x\to y\)</span>，使它对新样本也能给出合理预测。</p>
<p>但“学一个映射”还不够，训练时还必须回答另一个更具体的问题：<span style="background-color: #c0c0c0;">怎样才算模型学得好</span>。监督学习里最常见的回答，是在训练集上逐个比较预测与真实标签的差距，再把这些差距汇总成一个总体目标；这就导向经验风险最小化（Empirical Risk Minimization, ERM）。</p>
<p>经验风险最小化的典型目标写成：</p>
<span displaypfx="" class="mathjax-container">\[\frac{1}{N}\sum_{i=1}^{N}\ell\big(f_\theta(x_i),y_i\big)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(N\)</span> 是样本数， <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x_i)\)</span> 是模型预测， <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 是真实标签， <span displaypfx="inline-" class="mathjax-container">\(\ell\)</span> 是损失函数（Loss Function）。这条式子的含义不是抽象求和，而是：<span style="background-color: #c0c0c0;">逐个样本计算“预测错了多少”，再取平均，把平均错误压到尽可能小</span>。</p>
<p>例：垃圾邮件分类里， <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 可以是邮件文本特征， <span displaypfx="inline-" class="mathjax-container">\(y\in\{0,1\}\)</span> 表示“正常/垃圾”；房价预测里， <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 可以是面积、地段、楼龄， <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 是价格。前者是分类（Classification），后者是回归（Regression），但“有标签地学映射、再用损失函数衡量误差”这一训练逻辑完全一致。</p>
<div class="blog_h4"><span class="graybg">分类任务</span></div>
<p>分类任务（Classification）要预测的是<span style="background-color: #c0c0c0;">离散类别</span>，也就是样本属于哪一类。输出可以是一个类别 id，也可以是一组类别概率。例如二分类里常见输出 <span displaypfx="inline-" class="mathjax-container">\(P(y=1|x)\)</span>，表示“给定特征 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 时，样本属于正类的概率”。</p>
<p>垃圾邮件识别、肿瘤良恶性判断、情感分析、图像里的猫狗识别都属于分类任务。它更像“做选择题”：模型最终要在有限候选里做判断。训练时常配合交叉熵（Cross-Entropy）这类损失，因为模型不仅要选对类别，还要给正确类别足够高的置信度。</p>
<div class="blog_h4"><span class="graybg">回归任务</span></div>
<p>回归任务（Regression）要预测的是<span style="background-color: #c0c0c0;">连续数值</span>，也就是标签不是几个固定类别，而是在某个数值区间内连续变化。输出通常直接是一个实数，或一个多维连续向量。</p>
<p>房价预测、销量预测、温度预测、广告点击率中的停留时长估计都属于回归任务。它更像“做填空题”：模型不能只说“高”或“低”，而必须给出具体数值。训练时常配合均方误差（MSE）或平均绝对误差（MAE），因为关心的是预测值与真实值到底差了多少。</p>
<p>分类与回归都属于监督学习，因为它们都有标签；真正的区别在于<span style="background-color: #c0c0c0;">标签空间的形状</span>：分类的标签空间是离散集合，回归的标签空间是连续区间。这个区别会直接决定模型输出层形式、损失函数选择以及评估指标。</p>
<div class="blog_h3"><span class="graybg">弱监督学习</span></div>
<p>弱监督学习（Weakly Supervised Learning）仍然使用标签信号训练模型，但这些标签并不像标准监督学习那样完整、精确且逐样本对齐。它处理的核心情形是：<span style="background-color: #c0c0c0;">标签存在，但监督结构不够理想</span>，例如标签只存在于更粗粒度层面、标签本身含噪，或只有部分样本带标签。</p>
<p>从概念上看，弱监督并不是单一算法，而是一组监督不完备情形的总称。常见形式包括：标签不完整（incomplete supervision），即只有部分样本带标签；标签不精确（inexact supervision），即标签附着在聚合层级而不是实例层级；标签不准确（inaccurate supervision），即标签本身带噪或来自启发式规则、远程监督（Distant Supervision）与弱标注器。它与标准监督学习的边界在于：监督信号依然存在，但标签质量、粒度或覆盖度不足以直接当作“干净答案册”。</p>
<div class="blog_h4"><span class="graybg">多实例学习（Multi-Instance Learning, MIL）</span></div>
<p>多实例学习（Multi-Instance Learning, MIL）是弱监督学习中的一个经典范式。它的关键设定是：标签不附着在单个实例（instance）上，而是附着在一个由多个实例组成的包（bag）上。设训练集由若干包 <span displaypfx="inline-" class="mathjax-container">\(\{B_1,B_2,\dots,B_n\}\)</span> 构成，其中第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个包可写成</p>
<span displaypfx="" class="mathjax-container">\[B_i=\{x_{i1},x_{i2},\dots,x_{im_i}\},\qquad y_i\in\{0,1\}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(x_{ij}\)</span> 是包内第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个实例， <span displaypfx="inline-" class="mathjax-container">\(m_i\)</span> 是该包包含的实例数， <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 是包级标签。训练时只知道整个包的标签，不知道每个实例自己的标签。</p>
<p>MIL 最经典的标准假设（Standard MI Assumption）是：正包中至少存在一个正实例，负包中所有实例都为负。写成逻辑形式就是：</p>
<span displaypfx="" class="mathjax-container">\[y_i=1 \iff \exists j,\ z_{ij}=1;\qquad y_i=0 \iff \forall j,\ z_{ij}=0\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(z_{ij}\)</span> 表示实例级未知标签。这个设定特别适合两类问题：第一，真正决定结果的证据只稀疏地存在于少数关键实例中，其余实例更像背景或噪声；第二，获取实例级标签成本高，只能拿到更粗粒度的聚合标签。</p>
<p>因此，MIL 的关键不只是“把很多实例放在一起”，而是要学习一条从<span style="background-color: #c0c0c0;">实例集合到包级判断</span>的聚合规则。早期方法常用最大池化、均值池化或手工设计的聚合函数；深度学习阶段则更常引入可学习聚合，例如注意力式 MIL（Attention-based MIL），用可训练权重自动决定哪些实例对最终包标签贡献更大。这使 MIL 不只具有表达能力，也更容易给出“模型主要关注了哪些实例”的解释线索。</p>
<p>MIL 的适用性可以用一个抽象例子来理解。若一条完整客服对话被视作一个包，其中每轮发言是实例，而整体满意度评分是包级标签，那么模型面对的就是“整体有标签、逐轮无标签”的典型结构。此时，MIL 的任务不是给每轮发言都强行分配人工标签，而是在只有整体评分的前提下，学习哪些局部发言更可能决定整段会话的最终判断。</p>
<div class="blog_h3"><span class="graybg">无监督学习</span></div>
<p>无监督学习（Unsupervised Learning）只有输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，没有人工标签 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>。它的目标不是拟合“标准答案”，而是从数据中发现结构（Structure），例如聚类（Clustering）、降维（Dimensionality Reduction）、密度估计（Density Estimation）与异常检测（Anomaly Detection）。</p>
<p>一个直观类比是：监督学习像“拿着答案册做题”，无监督学习像“没有答案册，只能自己把一堆材料按相似性归类”。例如电商用户没有现成“用户类型”标签，但可以根据浏览、购买、停留时间等行为聚成“价格敏感型”“冲动购买型”“高价值复购型”等群体，用于运营分层。</p>
<p>从任务形态上看，无监督学习通常沿三条主线展开。第一条是<span style="background-color: #c0c0c0;">聚类分析</span>，目标是把样本按几何接近性、密度连通性、层次结构或图上的社区结构自动分组，典型方法包括 K-Means、层次聚类、DBSCAN、HDBSCAN，以及基于图的 Leiden / Louvain；第二条是<span style="background-color: #c0c0c0;">概率密度估计</span>，目标是刻画数据在哪些区域更常出现，从而支持异常检测、生成建模与风险评分，常见路线包括 GMM、核密度估计、One-Class SVM，以及更现代的自编码器（Autoencoder, AE）、受限玻尔兹曼机（RBM）和对抗式路线；第三条是<span style="background-color: #c0c0c0;">可视化与降维</span>，目标是在压缩表示的同时尽量保留关键结构，典型方法包括 PCA、t-SNE 与 UMAP。</p>
<p>这三条路线并不是彼此割裂的。聚类往往依赖一个合适的低维表示；异常检测常常等价于“找低密度区域”；可视化又经常被用来检查聚类是否真的形成结构、异常样本是否落在边缘地带。因此无监督学习并不只是“没有标签时随便看看数据”，而是在没有人工答案的条件下，用几何、密度与表示结构去重建数据内部秩序。</p>
<div class="blog_h3"><span class="graybg">自监督学习</span></div>
<p>自监督学习（Self-supervised Learning）介于监督与无监督之间：原始数据没有人工标签，但任务标签可以由数据本身自动构造出来。核心思想是<span style="background-color: #c0c0c0;">从数据内部制造预测任务</span>，让模型在完成这些任务的过程中学到可迁移表示（Representation）。</p>
<p>语言模型的下一个 token 预测就是最典型的自监督任务：前文是输入，后一个 token 是由原始文本自动给出的“监督信号”。图像领域里，旋转预测、遮挡恢复、不同增强视角匹配也属于同一路线。</p>
<div class="blog_h4"><span class="graybg">掩码预测</span></div>
<p>掩码预测（Masked Prediction）把输入中的一部分信息故意遮住，再要求模型恢复。例如 BERT 会把句子中的部分 token 替换成特殊标记 <span displaypfx="inline-" class="mathjax-container">\([MASK]\)</span>，模型要根据上下文预测被遮住的词。</p>
<p>类比来看，这像完形填空：你不是死记整句，而是学会根据上下文推断缺失信息。它迫使模型同时利用左侧和右侧上下文，因此特别适合编码器（Encoder）型表示学习。</p>
<div class="blog_h3"><span class="graybg">半监督学习</span></div>
<p>半监督学习（Semi-supervised Learning）位于监督学习与无监督学习之间：一小部分样本带标签，大量样本没有标签。它关心的核心问题是：如何利用未标注数据提供的结构信息，帮助少量标签发挥更大监督作用。与弱监督相比，半监督更强调“标签覆盖不足”，而不一定意味着标签本身粗糙或带噪。</p>
<p>经典路线包括 Self-Training、Co-Training、半监督 SVM、生成式方法和图半监督学习。Self-Training 会先用当前模型给未标注样本打伪标签，再把高置信样本并回训练集；Co-Training 要求样本存在两个相对独立但互补的视角，让两个模型彼此教对方；半监督 SVM 试图在利用标签的同时，把决策边界推向低密度区域；图半监督学习则利用样本相似图把少量标签沿图结构传播到邻近无标签样本。</p>
<p>主动学习（Active Learning）经常和半监督一起出现，但它的关注点略有不同：主动学习不是直接利用无标签样本训练，而是<span style="background-color: #c0c0c0;">选择最值得标注的样本去获取人工标签</span>。从数据效率角度看，半监督学习在“少标签 + 多无标签”条件下尤其重要，而主动学习解决的是“有限标注预算该花在哪些样本上”。</p>
<div class="blog_h3"><span class="graybg">强化学习</span></div>
<p>强化学习（Reinforcement Learning, RL）研究的是：智能体（Agent）在环境（Environment）中持续交互，根据奖励（Reward）学习策略（Policy），使长期累计回报（Cumulative Return）尽可能大。它关心的不是“单个输入该预测什么标签”，而是<span style="background-color: #c0c0c0;">一串连续动作最终能否带来更高的长期收益</span>。</p>
<div class="blog_h4"><span class="graybg">问题设定</span></div>
<p>强化学习的基本循环可以概括为：在时刻 <span displaypfx="inline-" class="mathjax-container">\(t\)</span>，智能体观察状态 <span displaypfx="inline-" class="mathjax-container">\(s_t\)</span>，选择动作 <span displaypfx="inline-" class="mathjax-container">\(a_t\)</span>，环境转移到新状态 <span displaypfx="inline-" class="mathjax-container">\(s_{t+1}\)</span>，并返回奖励 <span displaypfx="inline-" class="mathjax-container">\(r_{t+1}\)</span>。策略记为 <span displaypfx="inline-" class="mathjax-container">\(\pi(a\mid s)\)</span>，它回答的是“在当前状态下，动作应该怎样选”。</p>
<p>很多任务里，奖励并不会在正确动作发生的那一刻立刻显现。下棋时，一步好棋可能要十几步后才体现价值；推荐系统里，一次推荐是否合理，也要看后续点击、停留和转化。因此强化学习优化的不是单步得分，而是长期回报：</p>
<span displaypfx="" class="mathjax-container">\[J(\pi)=\mathbb{E}_{\pi}\!\left[\sum_{t=0}^{\infty}\gamma^t r_{t+1}\right]\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\gamma\in[0,1)\)</span> 是折扣因子（Discount Factor）。<span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span> 越接近 1，策略越重视长期收益；越小，策略越偏向短期收益。这个目标函数的含义很直接：在所有可能的策略中，找到那个平均下来总分最高的行为规则。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/rl.png"><img class="alignnone size-full wp-image-40981" src="https://blog.gmem.cc/wp-content/uploads/2026/03/rl.png" alt="rl" width="100%" /></a></p>
<div class="blog_h4"><span class="graybg">和监督学习的差异</span></div>
<p>监督学习通常基于带标签样本 <span displaypfx="inline-" class="mathjax-container">\((x,y)\)</span> 训练静态映射 <span displaypfx="inline-" class="mathjax-container">\(x\mapsto y\)</span>；强化学习面对的是动态环境中的序贯决策。监督学习收到的是“正确答案应当是什么”的指导性反馈，强化学习收到的是“这一步或这条轨迹好不好”的评估性反馈。前者的误差归因通常较直接，后者则需要把最终回报回溯到一串历史动作，这就是时间信用分配（Temporal Credit Assignment）的难点来源。</p>
<p>因此，强化学习的训练数据也不是静态不变的。当前策略会决定智能体之后访问哪些状态，于是数据分布本身会随着策略更新而改变。这一点使强化学习同时面对建模问题、探索问题和训练稳定性问题。</p>
<div class="blog_h4"><span class="graybg">马尔可夫决策过程（MDP）</span></div>
<p>强化学习的标准数学框架是马尔可夫决策过程（Markov Decision Process, MDP）。一个 MDP 通常写成五元组 <span displaypfx="inline-" class="mathjax-container">\((\mathcal{S},\mathcal{A},P,R,\gamma)\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{S}\)</span> 是状态空间， <span displaypfx="inline-" class="mathjax-container">\(\mathcal{A}\)</span> 是动作空间， <span displaypfx="inline-" class="mathjax-container">\(P(s'|s,a)\)</span> 是状态转移概率， <span displaypfx="inline-" class="mathjax-container">\(R(s,a)\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(R(s,a,s')\)</span> 是奖励函数， <span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span> 是折扣因子。</p>
<p>“马尔可夫”这个词的核心含义是：<span style="background-color: #c0c0c0;">如果当前状态已经把决策所需的信息概括完整，那么未来只取决于当前状态和当前动作</span>。它并不要求系统真的没有历史，而是要求当前状态已经足够代表历史中与决策相关的部分。</p>
<div class="blog_h4"><span class="graybg">价值函数与 Bellman 方程</span></div>
<p>强化学习里最重要的两个量是状态价值函数（State-value Function）和动作价值函数（Action-value Function）：</p>
<span displaypfx="" class="mathjax-container">\[V^\pi(s)=\mathbb{E}_\pi[G_t\mid s_t=s],\qquad Q^\pi(s,a)=\mathbb{E}_\pi[G_t\mid s_t=s,\ a_t=a]\]</span>
<p><span displaypfx="inline-" class="mathjax-container">\(V^\pi(s)\)</span> 描述“在状态 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 下，按策略 <span displaypfx="inline-" class="mathjax-container">\(\pi\)</span> 继续行动，长期回报大约是多少”；<span displaypfx="inline-" class="mathjax-container">\(Q^\pi(s,a)\)</span> 则更进一步，描述“在状态 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 下先做动作 <span displaypfx="inline-" class="mathjax-container">\(a\)</span>，再按策略 <span displaypfx="inline-" class="mathjax-container">\(\pi\)</span> 继续行动，长期回报大约是多少”。</p>
<p>Bellman 方程（Bellman Equation）把“长期回报”写成“即时奖励 + 下一步价值”的递归形式。对固定策略 <span displaypfx="inline-" class="mathjax-container">\(\pi\)</span>，状态价值满足：</p>
<span displaypfx="" class="mathjax-container">\[V^\pi(s)=\mathbb{E}_\pi\big[r_{t+1}+\gamma V^\pi(s_{t+1})\mid s_t=s\big]\]</span>
<p>这条式子的直觉非常重要：一个状态值多少钱，不需要把未来整条轨迹一口气全部展开，只要看“这一步先拿到多少，再加上下一状态值多少钱”。这就是动态规划思想在强化学习中的核心落点。</p>
<p>若目标是最优控制，则最优动作价值函数满足 Bellman 最优方程：</p>
<span displaypfx="" class="mathjax-container">\[Q^*(s,a)=\mathbb{E}\big[r_{t+1}+\gamma\max_{a'}Q^*(s_{t+1},a')\mid s_t=s,\ a_t=a\big]\]</span>
<p>它表达的是：当前动作的最优价值，等于这一步的即时奖励，加上下一状态里最佳后续选择的折扣价值。价值型方法、时序差分学习（Temporal-Difference Learning, TD）以及 Q-Learning，都是围绕这个递归结构展开的。</p>
<div class="blog_h4"><span class="graybg">价值方法：Q-Learning 与 DQN</span></div>
<p>价值型方法（Value-Based Methods）先估计“某个状态或状态-动作对值多少钱”，再根据价值做决策。最经典的做法是 Q-Learning。它不直接记住“这一步该做什么”，而是维护一个动作价值估计 <span displaypfx="inline-" class="mathjax-container">\(Q(s,a)\)</span>，让模型逐步学会哪些动作长期更划算。</p>
<p>Q-Learning 的标准更新写成：</p>
<span displaypfx="" class="mathjax-container">\[Q(s,a)\leftarrow Q(s,a)+\alpha\Big(r+\gamma\max_{a'}Q(s',a')-Q(s,a)\Big)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(r+\gamma\max_{a'}Q(s',a')\)</span> 是新的目标值（TD target），它把“这一步拿到的即时奖励”和“下一步开始最好还能拿到多少”拼接在一起；括号里的整体差值是 TD 误差（TD error），表示旧估计和新观察之间的偏差。Q-Learning 每见到一次新的转移样本，就用它修正一次账本。</p>
<p>当状态空间很小，例如离散网格迷宫，价值可以直接存成 Q 表；当状态变成图像、传感器序列或高维特征时，就需要用神经网络近似 <span displaypfx="inline-" class="mathjax-container">\(Q(s,a)\)</span>，这就是 DQN（Deep Q-Network）。它没有改变基本思想，只是把“查表记账”升级成“让网络来估值”。</p>
<p>SARSA（State-Action-Reward-State-Action）则代表另一种重要的 on-policy 时序差分路线。它把 Q-Learning 中的 <span displaypfx="inline-" class="mathjax-container">\(\max_{a'}Q(s',a')\)</span> 替换成策略实际下一步会采取的动作 <span displaypfx="inline-" class="mathjax-container">\(a'\)</span>，因此更新式写成</p>
<span displaypfx="" class="mathjax-container">\[Q(s,a)\leftarrow Q(s,a)+\alpha\Big(r+\gamma Q(s',a')-Q(s,a)\Big)\]</span>
<p>这一区别意味着：Q-Learning 更像“按最优后续动作记账”，SARSA 更像“按当前策略真实会怎么走来记账”。前者更激进，后者更保守；理解这一区别，有助于看清 on-policy 与 off-policy 方法的核心分野。</p>
<div class="blog_h4"><span class="graybg">策略方法：Policy Gradient、Actor-Critic 与 PPO</span></div>
<p>策略型方法（Policy-Based Methods）不先学一个价值表，而是直接参数化策略 <span displaypfx="inline-" class="mathjax-container">\(\pi_\theta(a\mid s)\)</span>。给定状态，模型直接输出动作概率分布或连续控制参数，再通过优化把高回报动作的概率提高、低回报动作的概率压低。它尤其适合连续动作空间，因为这类问题往往很难穷举所有动作再逐个估值。</p>
<p>策略梯度（Policy Gradient）的基本形式是：</p>
<span displaypfx="" class="mathjax-container">\[\nabla_\theta J(\theta)=\mathbb{E}\big[\nabla_\theta \log \pi_\theta(a_t\mid s_t)\,G_t\big]\]</span>
<p>它的含义可以概括成一句话：最终效果好的动作，以后更常做；最终效果差的动作，以后更少做。问题在于，直接用 <span displaypfx="inline-" class="mathjax-container">\(G_t\)</span> 更新通常方差很大，训练容易抖动。</p>
<p>于是就出现了 Actor-Critic。Actor 负责输出策略，也就是“怎么行动”；Critic 负责评估当前状态或动作值，也就是“这一步大概值多少”。Critic 提供更稳定的基线或优势估计，Actor 再沿着这个更平滑的信号更新策略。这样做的结果是：策略更新方向仍然由回报决定，但梯度噪声显著更可控。</p>
<p>PPO（Proximal Policy Optimization）可以看作一种工程上非常成功的 Actor-Critic 变体。它的核心思想不是改变优化方向，而是给策略更新加护栏，限制新旧策略之间的偏移幅度，避免一步改得过猛导致训练失稳。因此，PPO 在机器人控制、游戏智能体和大模型对齐里都很常见。</p>
<div class="blog_h4"><span class="graybg">探索与利用</span></div>
<p>强化学习始终要面对探索与利用（Exploration vs. Exploitation）的权衡。利用意味着优先选择当前已知回报较高的动作；探索意味着尝试那些暂时不确定、但可能更优的动作。只利用，策略可能很快卡在局部最优；只探索，又会浪费大量样本在明显不好的选择上。</p>
<p>这个矛盾在强化学习里是结构性的，因为策略会影响后续看到的数据。常见做法包括 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span>-greedy、熵正则化（Entropy Regularization）、Boltzmann exploration、上置信界（UCB）等。它们形式不同，但共同目标一致：既让模型敢于试错，又不至于长期停留在无意义的随机行动里。</p>
<div class="blog_h4"><span class="graybg">Model-Based 与 Model-Free</span></div>
<p>另一条常见划分标准是 model-based 与 model-free。二者的区别不在于是否使用神经网络，而在于<span style="background-color: #c0c0c0;">是否显式学习或利用环境动力学</span>。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类型</td>
<td style="text-align: center;">核心思路</td>
<td style="text-align: center;">优势</td>
<td style="text-align: center;">代价</td>
</tr>
</thead>
<tbody>
<tr>
<td>Model-Based RL</td>
<td>显式学习或已知状态转移与奖励模型，再据此规划或生成模拟轨迹</td>
<td>样本效率通常更高；能做前瞻规划</td>
<td>环境模型一旦学偏，规划也会被带偏；实现更复杂</td>
</tr>
<tr>
<td>Model-Free RL</td>
<td>不显式建模环境，直接从交互样本学习价值函数或策略</td>
<td>实现相对直接；适合复杂高维环境</td>
<td>样本效率通常较低；对真实交互成本更敏感</td>
</tr>
</tbody>
</table>
<p>价值型方法如 Q-Learning、DQN，策略型方法如 REINFORCE、PPO，多数都属于 model-free 路线；若先学习一个世界模型（World Model）或已知环境转移，再基于模型做搜索和规划，则属于 model-based 路线。实际系统也常把两者混合使用。</p>
<div class="blog_h4"><span class="graybg">和大模型对齐的衔接</span></div>
<p>大模型时代出现的 RLHF、PPO-based alignment、GRPO 等方法，属于强化学习思想在语言模型上的应用层。它们沿用的仍然是策略、奖励、回报、优势函数和策略优化这些通用概念，只是把环境替换成“基于 prompt、回答和偏好反馈构成的交互过程”。因此，理解通用强化学习基础之后，再进入后文的强化学习对齐，会更容易看清哪些是 RL 本体，哪些是大模型场景下的特化设计。</p>
<div class="blog_h2"><span class="graybg">统计学习</span></div>
<p>统计学习（Statistical Learning）强调从<span style="background-color: #c0c0c0;">数据由某种概率机制生成</span>这一视角理解机器学习。前面的概率论与统计已经介绍了概率、似然、MLE、MAP、边缘化与随机过程；这里不重复纯数学定义，而是把这些概念收束成机器学习里的几条主线：模型究竟在建模什么分布，隐藏变量怎样进入问题，推断为什么会变难，以及“相关”与“因果”为什么不是同一件事。</p>
<div class="blog_h3"><span class="graybg">从分布到学习问题</span></div>
<p>从统计学习视角看，很多模型的区别首先不在神经网络层数或树的深度，而在于它们选择建模哪一种概率对象。判别式模型（Discriminative Model）直接学习 <span displaypfx="inline-" class="mathjax-container">\(p(y\mid x)\)</span> 或决策边界，关心“给定输入后标签怎么判”；生成式模型（Generative Model）则更关心 <span displaypfx="inline-" class="mathjax-container">\(p(x,y)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(p(x\mid y)\)</span> 或带隐藏变量的联合分布，关心“数据是如何被生成出来的”。</p>
<p>这种划分并不等于“一个更先进、一个更落后”。判别式方法通常更直接服务于分类或回归目标；生成式方法则更容易表达不确定性、缺失变量和隐含结构。朴素贝叶斯、高斯混合模型（GMM）、隐马尔可夫模型（HMM）偏生成式；逻辑回归、支持向量机（SVM）、条件随机场（CRF）偏判别式。HMM 与 CRF 的细节放在后面的经典机器学习部分展开，这里只强调它们在统计建模立场上的差异。</p>
<div class="blog_h3"><span class="graybg">概率图模型</span></div>
<p>概率图模型（Probabilistic Graphical Model, PGM）用图结构表达随机变量之间的条件独立关系，并把高维联合分布拆成较小的局部因子。它的核心价值不是“把概率画成图”，而是把<span style="background-color: #c0c0c0;">哪些变量直接相互作用、哪些依赖可以被切断</span>写成显式结构，从而让建模、推断和解释都更清晰。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">对象</td>
<td style="text-align: center;">关注点</td>
<td style="text-align: center;">典型形式</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>贝叶斯网络（Bayesian Network）</td>
<td>有向依赖与条件独立</td>
<td>有向无环图（DAG）</td>
<td>适合表达“父节点影响子节点”的生成结构</td>
</tr>
<tr>
<td>因子图（Factor Graph）</td>
<td>联合分布如何分解为若干局部因子</td>
<td>变量节点 + 因子节点二部图</td>
<td>更强调分解结构，常作为统一表达方式</td>
</tr>
<tr>
<td>HMM / CRF</td>
<td>序列中的局部依赖</td>
<td>链式图结构</td>
<td>HMM 偏生成式，CRF 偏判别式</td>
</tr>
</tbody>
</table>
<p>贝叶斯网络适合表达“一个变量如何通过若干中间变量影响另一个变量”的有向依赖；因子图则更偏向把复杂联合分布拆成局部势函数（Factor）相乘的形式。若问题具有明显序列结构，HMM 和 CRF 就是最典型的链式概率图模型实例。它们之所以重要，不只是因为历史地位高，而是因为许多现代模型虽然实现方式更复杂，仍然在利用“局部分解 + 全局归一化/推断”这条思想主线。</p>
<div class="blog_h3"><span class="graybg">潜变量、EM 与变分推断</span></div>
<p>统计学习里很多问题之所以变难，不是因为观测变量本身太复杂，而是因为模型里存在隐藏变量（Latent Variable）<span displaypfx="inline-" class="mathjax-container">\(z\)</span>。一旦联合分布写成 <span displaypfx="inline-" class="mathjax-container">\(p(x,z)\)</span>，训练或预测往往都要面对边缘化：</p>
<span displaypfx="" class="mathjax-container">\[p(x)=\sum_z p(x,z)\quad \text{或} \quad p(x)=\int p(x,z)\,dz\]</span>
<p>当这个求和或积分无法直接算清时，推断（Inference）就成为核心问题。EM（Expectation-Maximization）适合一类带潜变量的参数估计问题：E 步先根据当前参数估计隐藏变量的后验分布或其期望统计量，M 步再在这些期望量上更新参数。GMM 的训练、HMM 的 Baum-Welch 算法，都是这一路线的经典例子。</p>
<p>若后验分布本身也难以精确求解，就需要近似推断。变分推断（Variational Inference, VI）的思路是：不用直接算真实后验 <span displaypfx="inline-" class="mathjax-container">\(p(z\mid x)\)</span>，而是选一个可计算的近似分布 <span displaypfx="inline-" class="mathjax-container">\(q(z)\)</span> 去逼近它。于是问题从“直接求后验”转化为“在一个可处理的分布族里找最接近后验的那个近似”。这一思路后来也自然延伸到了现代深度生成模型，例如变分自编码器（VAE）的训练就建立在变分推断框架之上。</p>
<div class="blog_h3"><span class="graybg">因果图与 do-calculus</span></div>
<p>统计相关性回答的是“变量经常一起变化吗”，因果推断（Causal Inference）回答的则是“如果我主动干预一个变量，另一个变量会怎样变”。这两类问题在形式上很接近，但含义完全不同。观察性条件概率 <span displaypfx="inline-" class="mathjax-container">\(p(y\mid x)\)</span> 只说明在看到 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 时， <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 通常是什么样；干预分布 <span displaypfx="inline-" class="mathjax-container">\(p(y\mid \mathrm{do}(x))\)</span> 讨论的是把 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 强行设定为某个值后， <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 会怎样变化。</p>
<p>因果图（Causal Graph）通常也写成有向无环图，但它表达的不再只是统计依赖，而是因果生成结构。混杂因素（Confounder）、中介变量（Mediator）和碰撞点（Collider）之所以重要，正是因为它们决定了哪些相关性可以被解释为因果效应，哪些只是共同原因或选择偏差造成的表象相关。do-calculus 则是一套把干预分布改写成可识别表达式的规则系统，用来判断在给定图结构下，目标因果效应是否能够从可观测分布中恢复出来。</p>
<p>在这份速查里，因果推断只保留这一层定位：它是统计学习向更强解释目标的延伸。普通监督学习通常停在“预测得准”，统计学习进一步讨论“不确定性和隐藏结构怎么处理”，而因果学习则继续追问“如果系统被主动改变，结果会不会跟着改变”。三者不是互斥关系，而是建模目标逐步增强的不同层级。</p>
<div class="blog_h2"><span class="graybg">表示学习与适配策略</span></div>
<p>与“学习范式”不同，下面这些概念不再按监督信号来源分类，而是分别回答另外几个问题：表示该怎样学、计算该怎样近似、已有知识该怎样迁移、在极少样本下又该怎样适配。因此它们更适合看成与学习范式并列的训练目标或训练策略。</p>
<div class="blog_h3"><span class="graybg">表示学习（Representation Learning）</span></div>
<p>表示学习（Representation Learning）讨论的是：如何把原始输入自动变换成更有用的特征表示，使后续任务更容易处理。它关心的不只是“最后预测对不对”，还关心模型内部是否学到了稳定、可迁移、对任务有判别力的中间表示。</p>
<p>传统特征工程（Feature Engineering）与表示学习处理的是同一个核心问题：如何把原始输入变成更适合下游任务的表示。但二者的方法论不同。特征工程主要依赖人工设计表示，例如词频、n-gram、统计量、规则特征与人工交叉特征；表示学习则强调由模型通过优化过程自动学出表示，例如 PCA、自编码器、词向量、上下文化表示以及深度网络中的隐藏状态。因此，传统手工特征本身通常不直接归入表示学习；只有当表示是通过训练自动获得时，它才更准确地属于表示学习范畴。</p>
<p>从 one-hot、BoW、词嵌入，到 BERT 的上下文化表示、Sentence-BERT 的句向量，主线始终一致：把原始符号或原始观测映射到更适合计算的表示空间。监督学习、自监督学习、对比学习都可以被用来学习表示；区别只在于监督信号来自哪里、训练目标如何设计。</p>
<div class="blog_h3"><span class="graybg">对比学习</span></div>
<p>对比学习（Contrastive Learning）通过“拉近正样本、推远负样本”学习表示。这里的关键不是类别标签本身，而是样本之间的相对关系：哪些应该相似，哪些必须区分。因此它特别适合表示学习、检索、多模态对齐和度量学习（Metric Learning）。</p>
<p>它的真正价值不只在于“学会匹配”，更在于学会<span style="background-color: #c0c0c0;">区分性特征（Discriminative Features）</span>。如果训练信号只告诉模型“这两个文本有关”，模型很容易停留在泛泛的共性描述上；而当训练持续提供“相似对”和“不相似对”，模型就被迫回答更尖锐的问题：究竟是什么让这两个文本属于同一语义区域，又是什么让它们必须分开。对比学习因此天然擅长抑制“表面上正确但没有区分度”的表示，转而强化真正决定语义边界的特征。</p>
<p>例如在商品评论表示学习里，句子“物流很快，包装也完整”和“物流很快，但东西是坏的”都包含“物流很快”这类高频表述。若模型只抓住表面词汇重叠，就可能把二者编码得非常接近；但在对比学习里，前者可能与“发货速度快、体验不错”构成正样本，后者则会与“收到商品后无法使用”“质量有问题”这类负面评价更接近。模型因此会逐步学会：真正决定语义边界的，不是共享的套话，而是“包装完整”“东西是坏的”这类改变整体语义走向的区分性片段。</p>
<p>从几何角度看，对比学习学到的是一个更有结构的向量空间。语义接近的文本会在局部形成簇（Cluster），语义无关或语义相反的文本则被推向更远位置。情感分析、语义检索、重复问句检测、意图聚类之所以能直接建立在 embedding 之上，本质上就是因为模型已经把“哪些内容应当靠近、哪些内容应当远离”编码进了空间结构，而不只是输出一个任务特定的分类分数。</p>
<p>在 NLP 中，这条路线并不是突然出现的。Word2Vec 已经体现了早期的对比式思想：真实共现词是正样本，随机采样词是负样本，模型通过区分“真实上下文”和“噪声配对”学习词向量。后来的句向量和文档向量模型，则把这种思想从词级扩展到句子级和文档级：正样本可以是复述句、问答配对、查询与相关文档，负样本则是不相关句子或困难反例（Hard Negatives）。</p>
<p>一个常见形式是 InfoNCE 损失：</p>
<span displaypfx="" class="mathjax-container">\[-\log \frac{\exp(\mathrm{sim}(z_i,z_i^+)/\tau)}{\sum_{j}\exp(\mathrm{sim}(z_i,z_j)/\tau)}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(z_i\)</span> 是当前样本表示， <span displaypfx="inline-" class="mathjax-container">\(z_i^+\)</span> 是与它匹配的正样本表示， <span displaypfx="inline-" class="mathjax-container">\(\mathrm{sim}(\cdot,\cdot)\)</span> 是相似度函数（常用余弦相似度）， <span displaypfx="inline-" class="mathjax-container">\(\tau\)</span> 是温度参数（Temperature），控制分布尖锐程度。这个目标的含义是：在一堆候选中，让正确配对拿到最高分，同时把不相关样本推远。</p>
<p>对比学习在句向量任务中的意义尤其大。交叉编码器（Cross-Encoder）把两个句子拼接后联合编码，能够做非常细的交互判断，但它直接输出的是“这一对句子有多像”，而不是可复用的独立句向量；一旦候选集合很大，计算量会迅速爆炸。双编码器（Bi-Encoder）路线则把两个文本分别编码成独立向量，再用余弦相似度或点积比较。SBERT 正是这一路线的经典代表：它通过孪生网络（Siamese Network）与对比式微调，把原本不适合作为通用句向量的 BERT 表示空间，改造成适合检索、聚类与语义匹配的 embedding 空间。</p>
<p>工程上，负样本既可以来自同一 batch 中的其他样本（In-batch Negatives），也可以来自专门构造的困难负样本（Hard Negatives）。所谓困难负样本，指的不是“完全无关”的反例，而是<span style="background-color: #c0c0c0;">在表面上很像、但语义上不应被判为同一项</span>的样本。例如检索里，与查询主题相近但并不真正回答问题的文档；句向量训练里，措辞高度相似却语义立场不同的句子；推荐里，风格相近但用户最终没有点击或转化的候选。它们之所以“困难”，正是因为模型若只依赖浅层词汇重叠、模板结构或主题相近性，很容易把这类负样本误判成正样本。</p>
<p>困难负样本的价值在于：它迫使模型放弃过于粗糙的匹配捷径，转而学习更细粒度的区分信号。随机负样本通常太容易分开，训练后期提供的梯度会迅速变弱；而困难负样本更接近真实决策边界，能持续推动表示空间学习“看起来相似但本质不同”的区别。不过它也有代价：若负样本挖掘质量不高，容易把本来就相关的样本错当成负例，形成假负样本（False Negatives），反而会伤害表示质量。因此，现代检索和 embedding 训练里，Hard Negatives 往往与 in-batch negatives、教师模型挖掘（teacher mining）或 reranker 筛选结合使用，而不是完全依赖人工拍脑袋构造。</p>
<p>CLIP、Sentence-BERT、现代检索 embedding、推荐召回模型，乃至许多 query-document dual encoder，本质上都在利用这种“正样本拉近、负样本推远”的训练逻辑。区别主要不在原理，而在样本如何构造、负样本如何选择，以及表示对象是词、句子、文档还是跨模态对。</p>
<div class="blog_h4"><span class="graybg">负采样</span></div>
<p>负采样（Negative Sampling）是与对比学习和词向量训练密切相关的一类近似策略。它的核心动机是：当候选空间极大时，没有必要每次都与所有候选比较；只保留 1 个正样本和少量负样本，就能得到足够强的判别信号。换句话说，它把原本代价高昂的“大规模归一化选择问题”，近似成若干个“真配对还是噪声配对”的二分类判断。</p>
<p>在 Word2Vec 的 Skip-gram 中，若直接对全词表做 softmax，分母需要对 <span displaypfx="inline-" class="mathjax-container">\(|{\cal V}|\)</span> 个词求和，计算代价很高。负采样则对每个正样本对 <span displaypfx="inline-" class="mathjax-container">\((w,c)\)</span> 只保留少量噪声词 <span displaypfx="inline-" class="mathjax-container">\(w_i\)</span>，并最大化：</p>
<span displaypfx="" class="mathjax-container">\[\log\sigma(v_w^\top v_c)+\sum_{i=1}^{k}\log\sigma(-v_w^\top v_{w_i})\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\sigma\)</span> 是 sigmoid 函数，第一项鼓励真实配对的内积更大，第二项鼓励噪声配对的内积更小。这样一来，计算量就从与词表大小同阶，降到与 <span displaypfx="inline-" class="mathjax-container">\(1+k\)</span> 个样本同阶。负样本也不一定完全随机：Word2Vec 常按词频的 <span displaypfx="inline-" class="mathjax-container">\(0.75\)</span> 次方采样；现代对比学习则常用 in-batch negatives 或 hard negative mining。推荐系统召回、知识图谱嵌入、句向量训练等任务里，这种思想到今天仍然非常常见。</p>
<div class="blog_h3"><span class="graybg">迁移学习</span></div>
<p>迁移学习（Transfer Learning）讨论的是：先在数据更丰富、任务更通用的源任务上学到参数或表示，再把这些知识迁移到目标任务。它不是按监督信号划分出来的独立“学习方式”，而是一种<span style="background-color: #c0c0c0;">跨任务复用知识的训练策略</span>。现代大模型先预训练、再微调，本质上就是迁移学习。</p>
<p>BERT 就是这一思路的典型例子。它通常先在大规模通用文本上做语言建模预训练，例如维基百科（Wikipedia）这类覆盖面很广的语料；模型先学到词法、句法、语义关系以及上下文表示能力。随后再把这一预训练模型迁移到具体任务上，例如情感分类、自然语言推断（NLI）、命名实体识别（NER）或文本匹配，只需接上任务头并用该任务的数据继续微调，就能把通用语言知识转化为面向目标任务的能力。</p>
<p>它与对比学习不在同一层面。对比学习回答的是“预训练阶段该用什么目标来学表示”；迁移学习回答的是“学到的表示如何迁到新任务”。两者经常配合出现：例如先在海量无标签图像上用对比学习预训练视觉编码器，再把该编码器迁移到医学影像分类、工业缺陷检测或小样本识别任务上。</p>
<div class="blog_h3"><span class="graybg">少样本学习</span></div>
<p>少样本学习（Few-shot Learning）处理的是“每个任务只有极少标注样本”时如何仍然快速泛化。它通常建立在迁移学习或预训练模型之上：模型先学到一套通用表示，再在很少示例下快速适配新任务。困难不在于单个任务本身，而在于模型必须把以往经验迁移到新任务上。直觉上，它更像“学会如何快速学习”，而不是“把一个任务彻底学透”。</p>
<div class="blog_h4"><span class="graybg">零样本（Zero-shot）</span></div>
<p>零样本（Zero-shot）指模型在目标任务上没有任何专门示例，也能凭借已有知识完成任务。大语言模型通过指令理解实现的很多能力都属于这一类。例：不给任何情感分类样例，只写“判断下面评论是正面还是负面”，模型仍可能完成分类。</p>
<div class="blog_h4"><span class="graybg">单样本（One-shot）</span></div>
<p>单样本（One-shot）指只给 1 个示例。这个示例的价值不是提供统计规律，而是告诉模型“输出格式、任务边界和你想要的判别标准”。例如先给一条“商品评论 → 正面”的例子，再让模型判断下一条评论。</p>
<div class="blog_h4"><span class="graybg">K 样本（K-shot）</span></div>
<p>K 样本（K-shot）指给每类或每任务提供 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个示例。随着 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 增大，模型更容易对任务意图和判别标准形成稳定估计。工程上，prompt 中的 few-shot 示例本质上就是在上下文窗口里做一种“临时任务适配”。</p>
<div class="blog_h4"><span class="graybg">元学习（Meta-learning / MAML）</span></div>
<p>元学习（Meta-learning）研究“让模型更快适应新任务”。MAML（Model-Agnostic Meta-Learning）的核心不是直接学一个最终答案，而是学一个好的初始化参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>，使模型只需少量梯度更新就能适配新任务。</p>
<p>MAML 的外层目标可概括为：</p>
<span displaypfx="" class="mathjax-container">\[\min_\theta \sum_{\mathcal{T}} \mathcal{L}_{\mathcal{T}}\big(\theta-\alpha\nabla_\theta \mathcal{L}_{\mathcal{T}}(\theta)\big)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{T}\)</span> 表示一个任务， <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 是内层更新步长。读法是：先用当前参数在某任务上走一步，再看更新后的参数在该任务上的表现好不好；如果“一步后就变好”，说明初始化是好的。类比来看，MAML 训练的不是“会做每道题的学生”，而是“只要老师讲一遍就能迅速举一反三的学生”。</p>
<div class="blog_h4"><span class="graybg">原型网络（Prototypical Networks）</span></div>
<p>原型网络（Prototypical Networks）把每个类别表示成嵌入空间中的一个“类中心（Prototype）”。对类别 <span displaypfx="inline-" class="mathjax-container">\(k\)</span>，其原型定义为该类支持集（Support Set）样本嵌入的平均：</p>
<span displaypfx="" class="mathjax-container">\[c_k=\frac{1}{|S_k|}\sum_{(x_i,y_i)\in S_k,\ y_i=k} f_\theta(x_i)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x_i)\)</span> 是样本的向量表示， <span displaypfx="inline-" class="mathjax-container">\(S_k\)</span> 是类别 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 的支持样本集合。分类时，把新样本映射到嵌入空间，看它离哪个原型最近。直觉上，这像“每一类先算一个代表点，新样本按离哪个代表点最近来归类”。在 few-shot 图像分类中，这种方法往往比直接训练复杂分类头更稳。</p>
<div class="blog_h2"><span class="graybg">表示聚合与池化</span></div>
<p>池化（Pooling）可以先按一句人话来理解：<span style="background-color: #c0c0c0;">把一组相邻或相关的特征，压缩成更短、更稳定、更容易继续处理的摘要</span>。它不是重新发明新特征，而是对已有特征做聚合（Aggregation）或下采样（Downsampling）。这里的下采样指：<span style="background-color: #c0c0c0;">沿某些维度减少位置数或采样点数，让表示尺寸变小、分辨率变粗</span>。例如把 <span displaypfx="inline-" class="mathjax-container">\(4\times 4\)</span> 的特征图压成 <span displaypfx="inline-" class="mathjax-container">\(2\times 2\)</span>，或把一长段序列压成更短的摘要向量，都属于下采样。</p>
<p>若把一组输入特征记为 <span displaypfx="inline-" class="mathjax-container">\(x_1,\dots,x_k\)</span>，则池化可以抽象写成</p>
<span displaypfx="" class="mathjax-container">\[y=\mathrm{Pool}(x_1,\dots,x_k)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Pool}\)</span> 可以是最大值（Max Pooling）、平均值（Average Pooling）、求和（Sum Pooling）或更复杂的加权聚合。它们做的事不同，但主线一致：<span style="background-color: #c0c0c0;">把“多个位置/多个元素的表示”变成“更少的表示”</span>。</p>
<p>池化之所以重要，是因为很多任务并不需要保留每个细节位置的完整分辨率。图像分类不一定关心边缘恰好落在第 17 个还是第 18 个像素；句子分类也不一定要求记住某个情绪词出现在第 6 个还是第 7 个 token。此时，把局部细节适度压缩，往往能提升稳定性、降低计算量，并让后续层更关注“有没有出现模式”，而不是“模式的坐标是否一模一样”。</p>
<div class="blog_h3"><span class="graybg">下采样（Downsampling）</span></div>
<p>这里单独把下采样拎出来，是因为它比“池化”更宽。池化当然是一类下采样，但下采样并不等于池化。只要一个操作会让表示在某个维度上的位置数减少、采样点变稀或分辨率变粗，它就属于下采样。最大池化、平均池化、步幅卷积（Strided Convolution）、序列 patch 化、音频降采样、时间窗口聚合，本质上都在做这件事。</p>
<p>若一维序列长度从 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 变成 <span displaypfx="inline-" class="mathjax-container">\(\lfloor T/s\rfloor\)</span>，或二维特征图从 <span displaypfx="inline-" class="mathjax-container">\(H\times W\)</span> 变成 <span displaypfx="inline-" class="mathjax-container">\(\lfloor H/s\rfloor\times \lfloor W/s\rfloor\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(s&gt;1\)</span> 是下采样倍率，那么模型面对的就不再是原来那样密集的坐标网格，而是更稀疏、更粗粒度的表示空间。收益通常有三类：计算量下降，后续层感受野相对扩大，以及模型更容易聚焦高层模式而不是微小位置抖动。</p>
<p>代价也同样明确。下采样会压缩细节，因此边界、尖峰、高频纹理和短时突发模式都可能被抹平。若信号里存在高频成分，而下采样前又没有足够平滑或低通处理，这些高频成分还会折叠成错误的低频模式，也就是混叠（Aliasing）。因此，下采样从来不是“白拿效率”，而是在<span style="background-color: #c0c0c0;">分辨率、稳定性、感受野和信息保真度之间做结构性取舍</span>。</p>
<div class="blog_h3"><span class="graybg">最常见的几种池化</span></div>
<p>最大池化（Max Pooling）保留一组特征里最强的那个响应。若某个窗口里有一个边缘、某个关键词或某个邻居信号特别强，最大池化会把它留下来。它更像在问：<span style="background-color: #c0c0c0;">这一小块区域里，最显著的模式有没有出现</span>。</p>
<p>平均池化（Average Pooling）对一组特征取平均，更强调整体趋势而不是最强局部点。它更像在问：这一块区域总体上激活强不强、语义平均水平如何。</p>
<p>求和池化（Sum Pooling）常见于图网络和集合建模，用于累积总量信息。若节点数量本身有意义，求和会把“有多少邻居/总共多强”也编码进去；平均池化则更强调归一化后的平均强度。</p>
<p>全局池化（Global Pooling）表示不再只看局部窗口，而是直接把整张特征图、整段序列或整个节点集合压成一个向量。例如全局平均池化（Global Average Pooling, GAP）会把一整个空间维度平均掉，得到“每个通道在全局上的平均响应”。自适应池化（Adaptive Pooling）则把输出尺寸预先固定，例如无论输入特征图多大，最终都压成 <span displaypfx="inline-" class="mathjax-container">\(1\times 1\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(7\times 7\)</span>。</p>
<div class="blog_h3"><span class="graybg">不同网络里的含义</span></div>
<p>在卷积神经网络（CNN）里，池化最经典的含义是<span style="background-color: #c0c0c0;">沿空间维度做局部下采样</span>。例如一张特征图经过 <span displaypfx="inline-" class="mathjax-container">\(2\times 2\)</span> 最大池化后，宽高会缩小，局部最强响应被保留下来。它的直接收益有三点：减小特征图尺寸、扩大后续层的感受野（Receptive Field）、并降低模型对小幅平移和局部扰动的敏感度。</p>
<p>在时序模型和文本模型里，池化更常表示<span style="background-color: #c0c0c0;">沿时间或序列长度维度做聚合</span>。例如把所有 token 表示做平均池化，得到整句向量；把一段音频帧表示做最大池化，得到“这一整段里最强的模式”。这里池化的重点不再是二维空间下采样，而是把变长序列压成固定长度表示，方便做分类、检索或相似度计算。</p>
<p>在图神经网络（GNN）里，池化有两层常见含义。第一层是邻域聚合（Neighborhood Aggregation）：一个节点把邻居表示做均值、求和或最大值，再更新自己；这可以理解为“节点级局部池化”。第二层是图级读出（Graph-level Readout）：把整张图的节点表示再做一次全局聚合，得到整个图的表示，用于图分类、图回归等任务。</p>
<p>在 Transformer 里，池化通常不再以“池化层”这一模块形式高频出现，但概念仍然存在。句子分类常取 <span displaypfx="inline-" class="mathjax-container">\([\mathrm{CLS}]\)</span> 位置表示，或对所有 token 做平均池化；Embedding 模型也常对最后一层隐藏状态做 mean pooling / max pooling 得到句向量。进一步看，注意力（Attention）本身也可以理解成一种<span style="background-color: #c0c0c0;">带内容依赖的加权聚合</span>：区别只在于普通池化的规则通常固定，而注意力的权重是由输入动态决定的。</p>
<div class="blog_h3"><span class="graybg">池化到底保留了什么、丢掉了什么</span></div>
<p>池化保留的是摘要信息，丢掉的是更精细的位置细节。最大池化更偏向“是否出现过显著模式”，平均池化更偏向“整体平均状态如何”，求和池化则更偏向“总量有多大”。因此，池化总带有一种 trade-off：表示更紧凑、更稳、更省算力，但精确定位能力会下降。</p>
<p>这也是为什么不同任务会选择不同聚合方式。图像分类往往欢迎一定程度的位置不敏感，因此池化很自然；语义检索希望一整句压成一个句向量，因此句级池化很常见；但像语义分割、目标检测、序列标注这类任务，输出本身依赖逐位置判断，就不能过早把位置信息池掉，否则细粒度边界会被抹平。</p>
<div class="blog_h2"><span class="graybg">泛化、过拟合与偏差—方差</span></div>
<p>机器学习更关心模型离开训练集之后能否维持稳定表现。泛化（Generalization）、过拟合（Overfitting）、欠拟合（Underfitting）与偏差—方差权衡（Bias–Variance Tradeoff）描述的，就是这件事。</p>
<div class="blog_h3"><span class="graybg">泛化</span></div>
<p>泛化（Generalization）指模型在未见样本上的表现。若训练数据与未来输入都来自同一数据分布 <span displaypfx="inline-" class="mathjax-container">\(P(X,Y)\)</span>，则模型的总体风险可写成：</p>
<span displaypfx="" class="mathjax-container">\[R(f)=\mathbb{E}_{(x,y)\sim P}\big[\ell(f(x),y)\big]\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(R(f)\)</span> 是真实风险（Population Risk），表示模型 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 在整个真实分布上的平均损失； <span displaypfx="inline-" class="mathjax-container">\(\ell(f(x),y)\)</span> 是单样本损失；期望 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}\)</span> 表示对所有可能样本做平均。训练时真正能看到的只有有限样本，因此优化的通常是经验风险 <span displaypfx="inline-" class="mathjax-container">\(\hat R_n(f)\)</span>，而不是这个理想化的总体风险。</p>
<p>训练误差与测试误差之间的差距，常称为泛化间隙（Generalization Gap）。间隙小说明模型在未见数据上比较稳定；间隙大则说明模型过度依赖训练样本中的偶然细节。</p>
<div class="blog_h3"><span class="graybg">内插与外推</span></div>
<p>内插（Interpolation）与外推（Extrapolation）描述的是：模型面对未见样本时，究竟是在<span style="background-color: #c0c0c0;">已观测范围之内补全规律</span>，还是在<span style="background-color: #c0c0c0;">已观测范围之外延伸规律</span>。二者都属于预测，但难度和风险完全不同。</p>
<p>以一维回归为例，若训练样本的输入主要落在区间 <span displaypfx="inline-" class="mathjax-container">\([a,b]\)</span> 内，那么对 <span displaypfx="inline-" class="mathjax-container">\(x\in[a,b]\)</span> 附近新样本做预测，更接近内插；对明显落在这个范围之外的 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 做预测，则更接近外推。内插依赖的是“训练数据已经覆盖了这片区域”；外推依赖的是“模型学到的规律在未见区域仍然继续成立”。后者显然要求更强。</p>
<p>在机器学习里，大多数标准泛化讨论其实都更接近内插。只要训练集与测试集近似满足独立同分布（IID）假设，测试样本通常仍然落在训练分布支持集（Support）附近，模型主要是在已知数据流形附近做平滑补全。这也是为什么现代高容量模型即使参数极多，只要训练分布覆盖充分，仍然能在测试集上表现得相当稳定。</p>
<p>外推对应的则是更困难的分布外泛化（Out-of-Distribution Generalization, OOD Generalization）。此时，测试输入不再只是训练分布中的轻微变化，而是进入了训练时很少见、甚至从未见过的区域。自动驾驶模型若只在晴天高速公路上训练，却要在暴雪、泥地和夜间乡道上决策；医学模型若主要见过成人数据，却被要求用于儿童病例；金融模型若只在平稳市场阶段训练，却要应对极端波动期，这些都属于外推问题。</p>
<p>外推之所以困难，根源在于经验风险最小化并不自动保证“规律可被安全延伸到训练分布之外”。模型完全可能在训练分布内部拟合得很好，却在一旦离开这片区域后迅速失效。因此，外推能力往往比普通测试集精度更能检验模型究竟学到了稳定结构，还是只学会了训练分布内部的高质量插值。</p>
<p>大语言模型里有一种非常典型的外推形式：长度外推（Length Extrapolation）。若模型训练时主要见到 <span displaypfx="inline-" class="mathjax-container">\(4\mathrm{K}\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(8\mathrm{K}\)</span> 上下文，却在推理时被要求处理 <span displaypfx="inline-" class="mathjax-container">\(128\mathrm{K}\)</span> 甚至更长序列，那么它面对的就不再只是“在熟悉长度范围内继续理解”，而是在训练长度之外延伸位置建模规律。RoPE 缩放、NTK-aware 调整、YaRN 等方法，本质上都是在尽量让模型把短上下文中学到的位置规律外推到更长序列上。</p>
<p>因此，可以把两者压缩成一句话：内插更像在已知区域里补全空白，外推更像拿着已总结出的规律去穿越未知边界。前者是标准机器学习评测里的常态，后者则更接近真实系统进入新环境时会遭遇的硬问题。</p>
<div class="blog_h3"><span class="graybg">过拟合</span></div>
<p>过拟合（Overfitting）指模型在训练集上持续吸收局部细节、噪声与偶然模式，但这些信息不能稳定迁移到未见样本上。它的典型外观是：训练集表现继续改善，而验证集或测试集表现停止改善、开始恶化，二者之间的泛化间隙（Generalization Gap）不断扩大。</p>
<p>过拟合描述的是一种训练现象，本身并不自动等于“模型已经不可用”。更准确的分析方式，是区分<span style="background-color: #c0c0c0;">模型到底过拟合了什么</span>。分类任务里最常见的两类退化并不完全相同：一类是决策边界本身开始贴合训练集偶然性；另一类是分类边界大体没变，但模型对既有判断越来越极端，概率校准逐步恶化。</p>
<div class="blog_h4"><span class="graybg">两种常见退化</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类型</td>
<td style="text-align: center;">退化对象</td>
<td style="text-align: center;">训练期常见信号</td>
<td style="text-align: center;">主要后果</td>
</tr>
</thead>
<tbody>
<tr>
<td>决策边界过拟合</td>
<td>模型学到只在训练集上成立的判别规则</td>
<td>训练 F1 / Accuracy 持续上升，验证 F1 / Accuracy 停滞或下降；训练 loss 很低而验证指标回落</td>
<td>真正损害分类泛化，换一批数据就更容易判错</td>
</tr>
<tr>
<td>置信度过拟合</td>
<td>模型对已有判断越来越极端，logit 持续膨胀</td>
<td>验证 F1 基本稳定，但验证 loss、Brier Score、ECE 等校准指标恶化；预测概率更频繁地逼近 0 或 1</td>
<td>硬分类结果可能不变，但概率值本身变得不可信</td>
</tr>
</tbody>
</table>
<p>前一类退化直接伤害模型的判别泛化，后一类退化主要伤害概率校准（Calibration）。因此，若系统只需要固定阈值之后的硬标签，置信度过拟合通常是次一级问题；若系统依赖概率值本身做风险分层、阈值调度、排序融合或人工兜底，置信度过拟合就会立刻变成工程问题。</p>
<div class="blog_h4"><span class="graybg">训练期间的识别信号</span></div>
<p>识别过拟合时，不能只盯着单一 loss，而要同时观察<span style="background-color: #c0c0c0;">训练集指标、验证集指标、概率分布形状与误差结构</span>。最典型的信号包括：</p>
<ul>
<li>训练 loss 持续下降，但验证 loss 在某个阶段后停止下降，随后回升。</li>
<li>训练 Accuracy / F1 继续提高，而验证 Accuracy / F1 停滞甚至下滑。这通常提示决策边界开始贴近训练集特有模式。</li>
<li>验证 F1 基本稳定，但验证 loss 明显变差。这类“指标稳、loss 崩”的组合，更接近置信度过拟合而不是判别边界崩坏。</li>
<li>logit 绝对值持续增大，softmax 或 sigmoid 输出更集中到接近 0 和 1 的两端，说明模型在继续放大自信度。</li>
<li>训练后期错误样本逐渐集中在少量硬样本，而 easy case 的置信度仍在继续极化，说明模型已经不再学新的判别规律，而是在强化已有判断的幅度。</li>
<li>不同随机种子、不同验证切分下的波动变大，说明模型开始依赖训练样本中的偶然结构。</li>
</ul>
<div class="blog_h4"><span class="graybg">常见诱因</span></div>
<p>过拟合并不只由“参数太多”导致。更常见的诱因包括：样本量不足、类别长尾、标签噪声、训练集与线上分布不一致、数据泄露、训练时间过长、正则化过弱、batch 过小导致梯度噪声放大，以及高重复语料让模型过度记忆头部模式。对深度模型而言，容量大只是风险放大器，真正决定是否过拟合的，通常是<span style="background-color: #c0c0c0;">模型自由度与有效监督信号之间是否失衡</span>。</p>
<div class="blog_h3"><span class="graybg">欠拟合</span></div>
<p>欠拟合（Underfitting）指模型连训练数据中的主要结构都没有学出来，表现为训练集和验证集都做不好。它对应的是高偏差（High Bias）状态：<span style="background-color: #c0c0c0;">模型的平均预测长期偏离真实目标</span>，也就是模型拟合能力不够，或训练过程根本还没有进入足够低误差的区域。</p>
<p>与过拟合相比，欠拟合的特征通常不是“训练和验证拉开了差距”，而是<span style="background-color: #c0c0c0;">两边都差，而且差得很一致</span>。训练集上的 loss 仍然偏高，训练 Accuracy / F1 也上不去，说明模型尚未把任务主结构写进参数中。</p>
<p>这里还要把现象与成因拆开：欠拟合强调“结果还不行”，欠容量强调“表达上限可能不够高”。若换更大模型、增加可训练参数或放宽结构限制后，训练集与验证集同步改善，说明此前更接近欠容量；若只是把训练跑满、把学习率调顺、把输入截断问题修掉后性能就明显提升，则原先更接近训练不足或优化不足。</p>
<div class="blog_h4"><span class="graybg">训练期间的识别信号</span></div>
<ul>
<li>训练 loss 和验证 loss 都较高，而且两者相差不大。</li>
<li>训练 Accuracy / F1 与验证 Accuracy / F1 同时偏低，没有形成明显的泛化间隙。</li>
<li>训练到后期时，两条曲线仍然一起缓慢下降，说明模型可能还没收敛；若提前停止训练，问题更接近“没训练够”。</li>
<li>即使继续训练较长时间，训练指标仍然明显低于任务应有上限，说明容量、特征或优化配置本身不足。</li>
<li>错误并不只集中在边界样本或少数难例，而是连大量 easy case 都无法稳定学会。</li>
</ul>
<div class="blog_h4"><span class="graybg">常见诱因</span></div>
<p>欠拟合常见于模型容量过小、特征表达弱、模型结构与任务不匹配、正则化过强、学习率设置不当、训练轮数不够、输入信息被过度截断，或任务本身需要非线性组合而模型只允许非常受限的线性表达。工程上，欠拟合也经常伪装成“模型很稳但一直不强”：曲线不震荡、训练不发散，却始终到不了可接受的性能区间。</p>
<div class="blog_h4"><span class="graybg">与过拟合的区分</span></div>
<p>一个实用判断准则是先看训练集是否已经被充分学会。若训练集指标本身就很差，优先考虑欠拟合；若训练集指标很好而验证集开始回落，优先考虑过拟合；若验证集分类指标基本不动，但验证 loss 与校准指标变差，则更接近置信度过拟合。把这三种状态区分清楚，后续的调参与正则化方向才不会混淆。</p>
<div class="blog_h3"><span class="graybg">坍缩（Collapse）</span></div>
<p>坍缩（Collapse）描述的是训练过程中的一种<span style="background-color: #c0c0c0;">退化解（Degenerate Solution）</span>：模型表面上仍在输出结果，但内部表示、预测分布或优化轨迹已经失去有效多样性，学习过程塌到某种简单、无用或几乎无信息的模式上。它和过拟合不同。过拟合仍然在“认真地区分训练样本”，只是把训练集细节学得过头；坍缩则意味着模型逐渐失去区分能力，或者训练目标退化为某种几乎不再提供有效学习信号的状态。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类型</td>
<td style="text-align: center;">典型表现</td>
<td style="text-align: center;">例子</td>
</tr>
</thead>
<tbody>
<tr>
<td>Mode Collapse</td>
<td>不管输入如何变化，输出都向少数模式收缩，预测类别或生成模式高度单一</td>
<td>分类器几乎把所有样本都判成“满意”；GAN 只会生成少数几种图像</td>
</tr>
<tr>
<td>Representation Collapse</td>
<td>编码器输出趋于相同或近似相同的向量，样本间表示几何结构被压扁</td>
<td>所有 hidden state / embedding 都高度相似，下游分类器只能依赖噪声做区分</td>
</tr>
<tr>
<td>Objective / Loss Collapse</td>
<td>训练目标迅速退化到近乎恒定的无信息状态，loss 长时间停在极低、极高或近乎不变的单一水平，梯度也可能同步衰减</td>
<td>自监督目标被模型用常数解“钻空子”；梯度消失后 loss 几乎不再变化；某些错误实现让目标函数被提前满足</td>
</tr>
</tbody>
</table>
<p>其中前两类最常见也最容易直观理解。Mode Collapse 强调<span style="background-color: #c0c0c0;">输出空间的多样性消失</span>；Representation Collapse 强调<span style="background-color: #c0c0c0;">内部表示空间的多样性消失</span>。第三类常被笼统地称作 loss collapse，但更准确的理解是“优化目标退化”或“训练目标坍缩”：loss 本身只是一个观测信号，真正的问题在于模型已经进入某种几乎不再产生有效学习内容的状态。</p>
<p>判断坍缩时，关键不是只看某一个时刻的平均 loss，而是看<span style="background-color: #c0c0c0;">输出分布、表示分布与样本间差异是否还存在</span>。若某个 epoch 中同时出现极低 loss 样本和极高 loss 样本，说明模型仍在把 easy case 与 hard case 区分开，训练信号仍有明显异质性；这更像正常训练中的难度分层，而不是已经坍缩。真正的坍缩通常会伴随更一致的退化迹象，例如预测类别快速单一化、embedding 方差急剧缩小、梯度长期接近 0，或者 loss 在大多数样本上收缩到近乎同一个无信息水平。</p>
<p>坍缩在不同任务中的诱因并不相同。对比学习（Contrastive Learning）里若缺少 stop-gradient、predictor、负样本或方差保持机制，表示空间很容易整体塌平；生成模型里，判别器与生成器失衡会诱发 mode collapse；分类任务里，极端类别不平衡、错误的损失实现、过强正则化或训练数据本身标签塌缩，都可能把模型推向低信息输出。工程上监控坍缩，通常需要同时看 loss、预测类别分布、embedding 方差、梯度范数与验证集指标，而不能只盯着单一数值曲线。</p>
<div class="blog_h3"><span class="graybg">偏差与方差</span></div>
<p>偏差（Bias）与方差（Variance）是分析泛化误差来源的经典视角。对平方损失（Squared Loss），常见分解写成：</p>
<span displaypfx="" class="mathjax-container">\[\mathbb{E}\big[(Y-\hat f(X))^2\big]=\mathrm{Bias}^2+\mathrm{Variance}+\sigma^2\]</span>
<p>左边的 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[(Y-\hat f(X))^2]\)</span> 是模型的平均平方误差； <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Bias}^2\)</span> 表示模型平均预测与真实函数之间的系统性偏离； <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Variance}\)</span> 表示模型对训练样本波动的敏感度； <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span> 是数据本身不可约的噪声（Irreducible Noise），即使模型和训练过程都完美，也无法完全消除。</p>
<p>更直白地说，<span style="background-color: #c0c0c0;">偏差看的是“模型平均预测离真实值有多远”，代表模型的拟合能力；方差看的是“换一份训练集后模型预测会抖动多大”，代表模型的稳定性</span>。偏差高通常对应欠拟合，因为模型学不到足够有效的数据规律；方差高通常对应过拟合，因为模型过度依赖某一份训练集的细节，离开训练集后泛化能力变差。</p>
<p>工程上，降低偏差常靠更强模型、更好特征和更充分训练；降低方差常靠更多数据、正则化、数据增强、早停（Early Stopping）和集成学习（Ensemble Learning）。很多建模决策，本质上都是在“预测还不够准”和“预测太不稳定”之间做权衡。下表用几类典型树模型把这种权衡具体化。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">偏差与方差的典型状态</td>
<td style="text-align: center;">为什么会这样</td>
</tr>
</thead>
<tbody>
<tr>
<td>决策树</td>
<td>高偏差 + 高方差风险并存</td>
<td>没有集成机制时，树过浅会欠拟合、偏差高；树过深又会对训练集细节极敏感、方差高，因此属于“双风险”模型</td>
</tr>
<tr>
<td>随机森林</td>
<td>低偏差 + 低方差</td>
<td>单棵深树先把偏差压低，再通过 Bagging 平均掉树与树之间的高方差</td>
</tr>
<tr>
<td>GBDT / XGBoost</td>
<td>低偏差 + 低方差</td>
<td>串行累加很多棵浅树持续降低偏差，而单棵浅树本身方差较低，再配合学习率、正则化与早停控制整体方差</td>
</tr>
</tbody>
</table>
<p>因此，偏差与方差不是抽象口号，而是在解释不同模型为什么会“学不动”或“学过头”。看树模型尤其直观：单树的问题是两头都可能出错；随机森林主要靠并行平均压方差；Boosting 家族主要靠串行纠错压偏差，再用浅树和正则化把方差稳住。</p>
<div class="blog_h2"><span class="graybg">经验风险最小化与正则化</span></div>
<p>大部分监督学习训练都可以概括成同一条主线：先定义单样本损失，再在训练集上取平均形成经验风险，最后通过优化算法把它压低。正则化（Regularization）是在这条主线之上加入额外约束，用来控制复杂度并改善泛化。</p>
<div class="blog_h3"><span class="graybg">经验风险最小化</span></div>
<p>经验风险最小化（Empirical Risk Minimization, ERM）是统计学习的基本训练原则。设训练集为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{D}=\{(x_i,y_i)\}_{i=1}^{n}\)</span>，则经验风险定义为：</p>
<span displaypfx="" class="mathjax-container">\[\hat R_n(f)=\frac{1}{n}\sum_{i=1}^{n}\ell(f(x_i),y_i)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 是样本数， <span displaypfx="inline-" class="mathjax-container">\(f(x_i)\)</span> 是模型对第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个样本的预测， <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 是真实标签， <span displaypfx="inline-" class="mathjax-container">\(\ell\)</span> 是损失函数。经验风险最小化就是在假设空间 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{H}\)</span> 中寻找让这条平均损失最小的函数：</p>
<span displaypfx="" class="mathjax-container">\[\hat f_{\mathrm{ERM}}=\arg\min_{f\in\mathcal{H}}\hat R_n(f)\]</span>
<p>这条原则覆盖范围极广。线性回归最小化的是平方误差经验风险，逻辑回归和多分类神经网络最小化的是交叉熵经验风险，序列标注模型最小化的是序列级条件对数似然。算法形式不同，骨架是一致的。</p>
<div class="blog_h3"><span class="graybg">正则化</span></div>
<p>正则化（Regularization）是在经验风险之外，再加入一个偏好“更简单、更平滑、更稳定”解的约束项。常见写法是：</p>
<span displaypfx="" class="mathjax-container">\[\hat f=\arg\min_{f\in\mathcal{H}}\hat R_n(f)+\lambda\,\Omega(f)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\Omega(f)\)</span> 是正则项（Regularizer），刻画模型复杂度； <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 是正则化强度，决定“拟合训练集”和“控制复杂度”之间的权衡。 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 越大，模型越保守；越小，模型越自由。</p>
<p>L2 正则（L2 Regularization）偏好较小权重，常写成 <span displaypfx="inline-" class="mathjax-container">\(\Omega(f)=\|\mathbf{w}\|_2^2\)</span>；L1 正则（L1 Regularization）偏好稀疏解，常写成 <span displaypfx="inline-" class="mathjax-container">\(\Omega(f)=\|\mathbf{w}\|_1\)</span>。更广义地看，早停、Dropout、数据增强、权重共享、标签平滑、参数冻结、低秩适配，都是在不同层面对模型自由度施加约束，因此都可以看作正则化思想的工程实现。</p>
<div class="blog_h2"><span class="graybg">IID 与分布偏移</span></div>
<p>机器学习中的很多训练与评估结论，都建立在一个默认前提上：训练样本与未来样本来自同一统计机制。这个前提通常写成 IID（Independent and Identically Distributed，独立同分布）假设。只要这个前提破坏，训练集表现与线上表现之间就可能出现明显断裂。</p>
<div class="blog_h3"><span class="graybg">IID 假设</span></div>
<p>设样本对 <span displaypfx="inline-" class="mathjax-container">\((x_i,y_i)\)</span> 来自某个联合分布 <span displaypfx="inline-" class="mathjax-container">\(P(X,Y)\)</span>，IID 假设写成：</p>
<span displaypfx="" class="mathjax-container">\[(x_1,y_1),\dots,(x_n,y_n)\overset{\mathrm{iid}}{\sim}P(X,Y)\]</span>
<p>这里“独立（Independent）”表示一个样本是否出现，不影响另一个样本的生成；“同分布（Identically Distributed）”表示所有样本都来自同一个联合分布 <span displaypfx="inline-" class="mathjax-container">\(P(X,Y)\)</span>。这个假设让训练集平均损失能够作为总体风险的近似，也让交叉验证、置信区间和很多泛化理论成立。</p>
<p>IID 是理想化近似，而不是自然界的铁律。时间序列、推荐系统、金融交易、医疗数据、A/B 实验日志、用户行为数据，常常都存在相关性、群组效应、时间漂移或采样偏差，因此不能机械套用 IID 设定。</p>
<div class="blog_h3"><span class="graybg">分布偏移</span></div>
<p>当训练分布与测试分布不一致时，就发生了分布偏移（Distribution Shift）：</p>
<span displaypfx="" class="mathjax-container">\[P_{\mathrm{train}}(X,Y)\neq P_{\mathrm{test}}(X,Y)\]</span>
<p>分布偏移有几种常见形式。协变量偏移（Covariate Shift）指 <span displaypfx="inline-" class="mathjax-container">\(P(X)\)</span> 变化，而 <span displaypfx="inline-" class="mathjax-container">\(P(Y|X)\)</span> 基本稳定；例如线上用户年龄结构变了，但“给定用户画像时是否点击”的规律没明显变。标签偏移（Label Shift）指 <span displaypfx="inline-" class="mathjax-container">\(P(Y)\)</span> 变化，例如欺诈率在促销期突然上升。概念漂移（Concept Drift / Concept Shift）指 <span displaypfx="inline-" class="mathjax-container">\(P(Y|X)\)</span> 本身发生变化，例如垃圾邮件发送策略升级后，原来有效的文本模式不再可靠。</p>
<p>因此，训练集、验证集与测试集的切分不能只追求随机均匀，还必须尽量模拟未来部署环境。若线上是时间推进场景，测试集就应按时间后移；若线上按用户或设备泛化，切分就应按实体隔离；若业务分布持续漂移，还需要做持续监控、重训和再校准。分布偏移不是评估里的边角问题，而是机器学习系统走向生产后的主要失效来源之一。</p>
<div class="blog_h3"><span class="graybg">OOD</span></div>
<p>OOD 是 Out-of-Distribution 的缩写，意为分布外（Out-of-Distribution）。它强调的是：<span style="background-color: #c0c0c0;">当前输入已经落到训练分布覆盖较弱、甚至根本没有覆盖的区域</span>。与一般意义上的“有点噪声”不同，OOD 更像是模型被带到了一个不熟悉的世界里。</p>
<p>例如，一个文本分类模型训练时主要见到的是规范书面语，部署后却大量遇到拼写错误、口语、英文混杂、模板化投诉和新产品名称；一个视觉模型训练时主要见到晴天白昼图像，线上却开始接收夜间、雨雪和红外图像。这类输入即使形式上仍然属于同一任务，也可能已经超出训练分布支持范围。OOD 检测与分布外泛化因此成为真实系统里的关键问题：模型不只要尽量判对，还要在“不熟悉”时知道自己不熟悉。</p>
<div class="blog_h3"><span class="graybg">数据漂移</span></div>
<p>数据漂移（Data Drift）强调的是<span style="background-color: #c0c0c0;">线上数据分布会随着时间持续变化</span>。它和 OOD 高度相关，但语境更偏工程系统：不是某一个样本偶然跑出了训练分布，而是整体数据来源、用户群体、业务流程或采集方式正在逐步改变。</p>
<p>若输入分布 <span displaypfx="inline-" class="mathjax-container">\(P(X)\)</span> 发生变化，常称为数据漂移或协变量漂移；若标签分布 <span displaypfx="inline-" class="mathjax-container">\(P(Y)\)</span> 变化，常表现为类别比例变化；若 <span displaypfx="inline-" class="mathjax-container">\(P(Y|X)\)</span> 也改变，则更接近概念漂移。现实系统里，这些变化往往同时发生。例如促销活动带来全新的用户结构，新功能改变用户行为路径，标注口径调整导致同类样本的标签规则也跟着变化。数据漂移的工程含义很直接：离线验证通过，并不意味着模型可以长期稳定在线上工作，监控、告警、回灌和重训机制必须跟上。</p>
<div class="blog_h3"><span class="graybg">鲁棒性</span></div>
<p>鲁棒性（Robustness）指模型在噪声、扰动、输入变形和分布变化下，性能是否仍能维持稳定。它关心的不只是“在标准测试集上最高能到多少分”，更关心<span style="background-color: #c0c0c0;">输入一旦变脏、变偏、变怪，模型会不会立刻失效</span>。</p>
<p>鲁棒性与 OOD、数据漂移不是同一概念，但三者紧密相关。OOD 和数据漂移描述的是输入环境发生了什么变化；鲁棒性描述的是模型面对这些变化时的承受能力。一个鲁棒性差的模型，可能在干净样本上分数很高，却会被轻微拼写错误、格式扰动、图像模糊、特征缺失或采样偏移迅速击穿。真实生产系统里，鲁棒性通常比单次 benchmark 分数更接近“模型能否长期可用”这个问题。</p>
<div class="blog_h2"><span class="graybg">数据集工程</span></div>
<p>数据集工程（Dataset Engineering）决定了模型看到什么、以什么尺度看到、又会被哪些偏差误导。很多所谓“模型问题”，根源其实是数据问题：标签噪声、分布漂移、类别极不均衡或特征泄漏，都会直接扭曲训练结果。</p>
<div class="blog_h3"><span class="graybg">黄金/白银数据集</span></div>
<p>数据集工程里常见一个实用分层：黄金数据集（Gold Dataset）与白银数据集（Silver Dataset）。黄金数据集通常指由高质量人工标注、规则严格审核或专家确认得到的小而精数据，标签噪声低，适合做最终评测、关键验证集或高价值监督信号；白银数据集则通常来自启发式规则、弱监督（Weak Supervision）、模型打标、日志回收或大规模自动清洗，规模更大、成本更低，但噪声也更高。实际工程中，常见策略不是二选一，而是用白银数据集提供覆盖面和规模，用黄金数据集提供校准、纠偏与最终可信评估。</p>
<div class="blog_h3"><span class="graybg">数据划分</span></div>
<p>数据划分的目标，是把<span style="background-color: #c0c0c0;">学参数、做模型选择、汇报最终结果</span>这三件事严格隔离开。若同一批数据既用来训练参数，又用来调超参数，最后还拿来汇报效果，评估结果通常会乐观得不真实，因为模型已经间接“看过”了答案。</p>
<p>从统计学习角度看，这三类数据分别承担三种不同职责：训练集负责让模型学习参数；验证集负责帮助人或训练流程做工程决策；测试集负责模拟真正的未知数据，给出最后一次、尽量无偏的泛化评估。三者分工清楚，模型评估才有可信度。</p>
<div class="blog_h4"><span class="graybg">训练集</span></div>
<p>训练集（Training Set）用于更新模型参数。监督学习中，训练集包含输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 与标签 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>；模型在这批样本上计算损失（Loss）、反向传播梯度（Gradient）并更新参数，因此训练集直接决定“模型学到了什么”。它回答的问题是：<span style="background-color: #c0c0c0;">在已观测样本上，模型有没有学会输入与输出之间的对应关系</span>。</p>
<p>训练集通常应占数据的大头，因为参数学习需要足够多的样本来稳定估计模式。实践中常见比例是 70% 到 80%，但这不是固定规则：若数据总量非常大，训练集比例可以更高；若数据本来就少，则往往需要把更多精力放在交叉验证（Cross Validation）而不是死守固定比例。</p>
<p>训练集上的误差通常是三者里最低的，这并不说明模型已经具有泛化能力。一个模型完全可能在训练集上表现极好，却只是记住了样本中的噪声与偶然性。训练集成绩更多反映“拟合能力”，而不是“真实上线表现”。</p>
<div class="blog_h4"><span class="graybg">验证集</span></div>
<p>验证集（Validation Set）用于模型选择（Model Selection）和超参数调优（Hyperparameter Tuning）。它不直接参与参数更新，但会影响训练流程中的关键决策，例如学习率（Learning Rate）、正则化强度、模型深度、树的数量、batch size、阈值选择，以及是否执行 Early Stopping。</p>
<p>验证集回答的问题不是“模型能不能学会”，而是：<span style="background-color: #c0c0c0;">在若干候选配置里，哪一个更可能在新数据上表现最好</span>。因此，验证集像训练过程中的“模拟考试”：它不是最终成绩单，但会决定你在训练期间如何改模型、如何调参数、何时停止训练。</p>
<p>验证集通常占总数据的 10% 到 15% 左右。若数据量很小，单独留出一份验证集的代价会较高，此时更常见的做法是使用 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 折交叉验证，让每个样本轮流充当验证数据，以减少一次随机划分带来的偶然性。交叉验证的细节放在后面的“模型评估”部分展开。</p>
<div class="blog_h4"><span class="graybg">测试集</span></div>
<p>测试集（Test Set）用于最终评估模型的泛化能力（Generalization）。它应尽量只在方案冻结之后使用：模型结构、超参数、训练策略、阈值和后处理规则都不再修改时，才在测试集上做一次最终评估。它回答的问题是：<span style="background-color: #c0c0c0;">如果把模型部署到真实世界，它在新样本上的表现大致会怎样</span>。</p>
<p>测试集通常占总数据的 10% 到 15%。它的重要性不在于比例有多大，而在于它必须保持“未参与决策”。如果开发过程中反复查看测试集结果，并据此继续改模型，那么测试集就已经被污染，不再是独立评测，而变成了另一个隐性的验证集。</p>
<p>因此，测试集更像真正的“高考卷”或“盲测集”：它的价值在于最后一次、尽量无偏的评估，而不是参与训练流程本身。</p>
<div class="blog_h4"><span class="graybg">如何划分数据集</span></div>
<p>最常见的简单划分是训练集 / 验证集 / 测试集 = 70% / 15% / 15%，或 80% / 10% / 10%。这种划分适合样本量较大、类别分布较稳定的任务，因为单次随机切分已经足以给出相对稳定的训练与评估结果。</p>
<p>当数据量较小、类别极不平衡、或者不同子群体差异明显时，划分策略就必须更谨慎。分类任务常采用分层抽样（Stratified Split），确保训练、验证、测试三部分的类别比例大致一致；时间序列任务则必须按时间顺序切分，避免未来信息泄漏到过去；用户级、设备级、病人级任务常需要按实体分组切分，防止同一实体的样本同时出现在训练集和测试集中，造成过于乐观的结果。</p>
<p>因此，“如何划分”本身就是建模的一部分。划分方式若与真实部署场景不一致，即使指标很好，也可能只是评估设定过于宽松，而不是真正泛化能力强。</p>
<div class="blog_h4"><span class="graybg">数据泄露</span></div>
<p>数据泄露（Data Leakage）指测试集或验证集中的信息以直接或间接方式进入训练过程，从而导致模型评估结果虚高。它的危险不在于“代码报错”，而在于模型会表现得看似极好，却无法在真实新数据上复现。</p>
<p>最常见的数据泄露有几类。第一类是<span style="background-color: #c0c0c0;">先对全量数据做预处理，再切分数据</span>，例如先用全量数据计算标准化均值和方差，再划分训练 / 测试集；这样测试集的信息已经进入了训练流程。第二类是<span style="background-color: #c0c0c0;">用测试集反复调参</span>，例如每改一次模型就看一次测试集成绩，直到测试集最好看为止。第三类是<span style="background-color: #c0c0c0;">特征中混入未来或标签信息</span>，例如用预测时不可能知道的字段做输入，或把目标变量的某种变形偷偷带进特征。</p>
<p>避免数据泄露的原则只有一句：<span style="background-color: #c0c0c0;">任何依赖数据分布统计量、特征构造规则、模型选择决策或阈值选择的步骤，都只能在训练集内部完成，再把同样的变换应用到验证集和测试集</span>。标准化、特征选择、缺失值填补、目标编码（Target Encoding）、降维（PCA）和重采样（Resampling）都要遵守这一原则。</p>
<p>因此，数据集划分不只是“把数据分三份”这么简单，而是整个实验设计（Experimental Design）的一部分。只有训练集、验证集、测试集的职责边界清晰，交叉验证使用得当，且数据泄露被严格控制，模型指标才具有解释价值和可复现实验意义。</p>
<div class="blog_h3"><span class="graybg">归一化与标准化</span></div>
<p>归一化（Normalization）与标准化（Standardization）都在解决“不同特征量纲和尺度差异过大”问题，但含义不同。最常见的最小-最大归一化把数据映射到固定区间：</p>
<span displaypfx="" class="mathjax-container">\[x'=\frac{x-x_{\min}}{x_{\max}-x_{\min}}\]</span>
<p>它把特征压到 <span displaypfx="inline-" class="mathjax-container">\([0,1]\)</span>，适合像像素值、比例值这类天然有上下界的量。标准化则是减去均值、再除以标准差：</p>
<span displaypfx="" class="mathjax-container">\[z=\frac{x-\mu}{\sigma}\]</span>
<p>标准化后的特征均值为 0、标准差为 1，更适合线性模型、距离模型和很多神经网络优化过程。类比来看，归一化像“把不同长度的尺子都缩到同一长度区间”；标准化像“先平移到共同中心，再按波动尺度统一单位”。</p>
<div class="blog_h3"><span class="graybg">特征工程</span></div>
<p>特征工程（Feature Engineering）是把原始数据加工成更利于模型学习的表示。它不是“多造点列”这么简单，而是在把领域知识编码进输入空间。例：时间戳可以拆成小时、星期、是否节假日；用户行为日志可以构造近 7 天点击次数、转化率、时间衰减统计；文本可以做 TF-IDF、n-gram 或实体抽取。</p>
<p>类比来看，特征工程像做菜前的备料：同样的原料，如果已经切片、去骨、配好比例，后续烹饪会顺畅得多。经典机器学习对特征工程高度依赖；深度学习则把一部分特征学习自动化了，但在表格数据、推荐、广告和风控里，特征工程仍然决定上限。</p>
<div class="blog_h3"><span class="graybg">类别不平衡处理</span></div>
<p>类别不平衡（Class Imbalance）指某些类别样本远多于另一些类别。欺诈检测、故障检测、医学筛查里最典型：正类往往极少。如果不处理，模型可能通过“永远预测多数类”获得看似不错的 Accuracy，却在关键少数类上彻底失效。</p>
<p>常见处理方法包括：重采样（过采样少数类、欠采样多数类）、类别加权（Class Weighting）、阈值调整（Threshold Tuning）和使用更合适的指标（如 Precision、Recall、PR-AUC）。例如在信用卡欺诈场景中，正类只占 0.1%，此时“全判正常”会有 99.9% Accuracy，但业务价值几乎为 0。</p>
<div class="blog_h2"><span class="graybg">超参数</span></div>
<p>超参数（Hyperparameters）是训练开始前由人或外部搜索过程设定的配置变量。它们与模型参数（Parameters）不同：模型参数如线性回归的权重、神经网络的矩阵和偏置，是通过训练数据学出来的；超参数则决定<span style="background-color: #c0c0c0;">模型该以什么结构、什么训练节奏、什么正则化强度去学习</span>。学习率、batch size、树深、dropout、LoRA rank 都属于超参数，而不是训练过程中直接被梯度更新出来的参数。</p>
<div class="blog_h3"><span class="graybg">什么是超参数</span></div>
<p>从作用层面看，超参数大致分成三类。第一类决定模型结构，例如树的最大深度、神经网络层数、隐藏维度、注意力头数；第二类决定优化过程，例如学习率、batch size、训练轮数、warmup 步数；第三类决定复杂度控制，例如正则化强度、dropout、weight decay、早停耐心值。它们共同定义了“模型允许学成什么样、以及训练过程会沿哪条轨迹逼近这个结果”。</p>
<p>因此，超参数优化并不是在调模型已经学到的权重，而是在搜索<span style="background-color: #c0c0c0;">哪一套训练配置更可能在验证集上泛化得最好</span>。这也是为什么超参数搜索天然依赖验证集，而不能依赖测试集。</p>
<div class="blog_h3"><span class="graybg">通用超参数</span></div>
<p>有些超参数跨很多模型家族都反复出现，它们更像训练流程级控制杆，而不是某个算法私有旋钮。最常见的一组可概括如下：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">超参数</td>
<td style="text-align: center;">主要控制什么</td>
<td style="text-align: center;">常见影响</td>
</tr>
</thead>
<tbody>
<tr>
<td>学习率（Learning Rate）</td>
<td>每步更新幅度</td>
<td>过大易震荡或发散，过小则收敛过慢或停在高误差区</td>
</tr>
<tr>
<td>batch size</td>
<td>每次梯度估计使用多少样本</td>
<td>影响吞吐、显存占用、梯度噪声和有效学习率范围</td>
</tr>
<tr>
<td>训练轮数 / 训练步数（Epochs / Steps）</td>
<td>训练总时长</td>
<td>过少易欠拟合，过多则更易过拟合</td>
</tr>
<tr>
<td>正则化强度（Regularization Strength）</td>
<td>复杂度惩罚有多强</td>
<td>过强会欠拟合，过弱则更易记忆训练集细节</td>
</tr>
<tr>
<td>weight decay</td>
<td>参数收缩强度</td>
<td>常用于控制神经网络权重规模与泛化</td>
</tr>
<tr>
<td>dropout</td>
<td>随机屏蔽单元的比例</td>
<td>抑制共适应，但过强会削弱表示能力</td>
</tr>
<tr>
<td>学习率调度（Scheduler）</td>
<td>训练过程中学习率如何变化</td>
<td>直接影响早期稳定性与后期收敛质量</td>
</tr>
<tr>
<td>warmup</td>
<td>前期学习率爬升过程</td>
<td>对 Transformer 和大 batch 训练尤为重要</td>
</tr>
<tr>
<td>早停耐心值（Early Stopping Patience）</td>
<td>验证集多久不提升才停止</td>
<td>影响训练预算与过拟合控制</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">模型特定超参数</span></div>
<p>另一类超参数只在特定模型家族里出现。它们往往直接对应某个算法的结构假设，因此不能简单迁移到别的模型上。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型家族</td>
<td style="text-align: center;">典型超参数</td>
<td style="text-align: center;">控制什么</td>
</tr>
</thead>
<tbody>
<tr>
<td>KNN</td>
<td><span displaypfx="inline-" class="mathjax-container">\(k\)</span>、距离度量</td>
<td>邻域大小与“相似”的定义</td>
</tr>
<tr>
<td>SVM</td>
<td><span displaypfx="inline-" class="mathjax-container">\(C\)</span>、kernel、<span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span></td>
<td>间隔惩罚与核函数形状</td>
</tr>
<tr>
<td>决策树 / 随机森林</td>
<td>max depth、min samples leaf、树数</td>
<td>树的复杂度与集成规模</td>
</tr>
<tr>
<td>Boosting / XGBoost / LightGBM</td>
<td>learning rate、树数、max depth、采样比例</td>
<td>弱学习器叠加节奏与复杂度</td>
</tr>
<tr>
<td>CNN</td>
<td>卷积核大小、通道数、stride、pooling 配置</td>
<td>局部感受野与空间降采样方式</td>
</tr>
<tr>
<td>RNN / LSTM</td>
<td>隐藏维度、层数、截断长度</td>
<td>时序记忆容量与反向传播范围</td>
</tr>
<tr>
<td>Transformer</td>
<td>层数、隐藏维度、头数、最大上下文长度</td>
<td>表示容量、并行结构与长程建模能力</td>
</tr>
<tr>
<td>PEFT / LoRA</td>
<td>rank、alpha、target modules、adapter dropout</td>
<td>低秩适配容量与写入位置</td>
</tr>
</tbody>
</table>
<p>这也是为什么“超参数”不能被理解成一张固定清单。不同模型真正敏感的旋钮并不相同。对树模型，max depth 和叶节点约束常是核心；对 Transformer，学习率、warmup、weight decay、batch 与上下文长度往往更关键；对 LoRA，rank 与挂载模块会直接决定可写入容量。</p>
<div class="blog_h3"><span class="graybg">超参数搜索</span></div>
<p>超参数搜索（Hyperparameter Search）指用验证集表现，在若干候选配置中选择更优组合。它本质上不是训练参数，而是在搜索<span style="background-color: #c0c0c0;">哪套训练配方更值得被固定下来</span>。搜索空间越大，找到更优组合的机会通常越高，但实验成本、验证集过拟合风险和复现难度也会同步上升。</p>
<div class="blog_h4"><span class="graybg">贪婪串行登山</span></div>
<p>贪婪串行登山（Greedy Sequential Hill Climbing）是一种非常实用的超参数搜索策略。它的核心规则是：<span style="background-color: #c0c0c0;">每次只调整一个超参数，在当前其余超参数固定不变的条件下，选择验证集上更优的方向走一步；确定后先固定该值，再去调下一个超参数</span>。在离散候选集上，它可以看作一种坐标式局部搜索。</p>
<p>例如先固定 dropout 和 batch size，只比较若干学习率；一旦找到当前最优学习率，就暂时锁定它，再去比较 dropout；然后再固定前两者去比较 batch size。这样做的优点是实验次数通常近似线性增长，适合“训练一次代价不低、超参数数量又不算很多”的场景。</p>
<div class="blog_h4"><span class="graybg">棘轮式锁定</span></div>
<p>贪婪串行登山常伴随一种棘轮式锁定（Ratchet-style Fixing）：某一轮一旦选定一个更优取值，就先不回头重开这个维度。这样做能显著缩小后续搜索空间，但代价也很明确：较早做出的局部最优决策，会限制后面组合空间的探索。</p>
<p>它最容易出问题的地方，是参数交互（Hyperparameter Interaction）。若两个超参数彼此强相关，例如学习率和 batch size、学习率和 warmup、LoRA rank 和 target modules，那么“在当前默认值下看起来更优”的选择，未必能和后续维度组成真正最优的整体组合。棘轮式锁定会把这类交互提前屏蔽掉。</p>
<div class="blog_h4"><span class="graybg">优点与局限</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">维度</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>优点</td>
<td>简单、可解释、实验次数少，适合作为快速锁定大方向的工程基线</td>
</tr>
<tr>
<td>局限 1</td>
<td>容易停在局部最优，因为它不会接受“短期下降、长期更优”的探索路径</td>
</tr>
<tr>
<td>局限 2</td>
<td>默认把超参数近似看成可分离维度，但现实中经常存在强交互</td>
</tr>
<tr>
<td>局限 3</td>
<td>若反复依赖同一验证集做很多轮决策，更容易把验证集偶然性误判成真实提升</td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">与其他搜索策略对比</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">策略</td>
<td style="text-align: center;">实验成本</td>
<td style="text-align: center;">能否捕捉参数交互</td>
<td style="text-align: center;">典型特点</td>
</tr>
</thead>
<tbody>
<tr>
<td>贪婪串行登山</td>
<td>较低</td>
<td>较弱</td>
<td>工程上快速、便宜、可解释，但更局部</td>
</tr>
<tr>
<td>网格搜索（Grid Search）</td>
<td>高，常随维度指数增长</td>
<td>强</td>
<td>穷举规则清楚，但高维时代很快失去性价比</td>
</tr>
<tr>
<td>随机搜索（Random Search）</td>
<td>可控</td>
<td>中等</td>
<td>在高维空间常比网格搜索更高效，是强基线</td>
</tr>
<tr>
<td>贝叶斯优化（Bayesian Optimization）</td>
<td>中等到较高</td>
<td>较强</td>
<td>利用历史试验结果自适应建议下一个点，适合昂贵实验</td>
</tr>
</tbody>
</table>
<p>因此，何时使用哪种策略，取决于训练代价与搜索空间形状。若一次训练就要数十分钟甚至数小时，且可调超参数并不多，贪婪串行登山往往已经足够作为第一轮工程方案；若参数交互明显、预算允许，随机搜索或贝叶斯优化通常更稳。无论使用哪一种方法，最关键的前提都不变：<span style="background-color: #c0c0c0;">搜索必须由验证集驱动，而测试集必须保持未参与决策</span>。</p>
<div class="blog_h2"><span class="graybg">模型评估</span></div>
<p>模型评估（Model Evaluation）回答的不是“模型能不能在训练集上做对”，而是“模型在新数据上是否可靠，以及错误代价如何”。不同任务对应的指标重点不同：分类关心类别区分，回归关心数值偏差，排序关心相对顺序。</p>
<div class="blog_h3"><span class="graybg">交叉验证</span></div>
<p>交叉验证（Cross Validation）在数据较少时特别重要。最常见的 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 折交叉验证把数据分成 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 份：每次用其中 1 份做验证、其余 <span displaypfx="inline-" class="mathjax-container">\(K-1\)</span> 份训练，循环 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 次，最后对 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个验证结果取平均。</p>
<p>它的作用像“轮流把不同一份数据拿出来当模拟考试卷”，从而降低一次随机划分带来的偶然性。对小数据集而言，单次划分可能刚好“运气好或坏”；交叉验证则给出更稳定的泛化估计。</p>
<div class="blog_h3"><span class="graybg">校准</span></div>
<p>校准（Calibration）讨论的是：<span style="background-color: #c0c0c0;">模型给出的概率值，是否真的能当概率解释</span>。分类模型不只输出“判成哪一类”，还常输出一个置信分数，例如 <span displaypfx="inline-" class="mathjax-container">\(0.9\)</span>。若一个模型在所有“预测概率约为 0.9”的样本子集上，最终真的有约 90% 预测正确，那么它就是校准良好的；若它经常把只有 60% 把握的样本说成 90%，就属于过度自信（Overconfident）。</p>
<p>二分类中，若模型输出正类概率 <span displaypfx="inline-" class="mathjax-container">\(\hat p(x)\in[0,1]\)</span>，理想校准条件可写成：</p>
<span displaypfx="" class="mathjax-container">\[P(Y=1\mid \hat p(X)=p)=p\]</span>
<p>这里左边表示：在所有预测概率等于 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 的样本中，真实为正类的条件概率；右边的 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 是模型自己报出的概率。两者相等时，概率输出就与真实频率一致。多分类场景下，常把模型最大类别概率当作置信度，并检验“报 80% 置信度的样本，是否真的大约 80% 正确”。</p>
<p>校准与准确率不是同一件事。一个模型可以分类很准，但概率不可靠；也可以概率尺度较准，但分类边界并不最优。前者常见于深层神经网络：argmax 分类结果不错，但 softmax 概率偏尖，置信度系统性偏高。涉及风险控制、医学筛查、自动驾驶、检索重排、多阶段决策时，概率是否可信往往和“分对多少”同样重要，因为阈值决策、人工复核和代价加权都依赖这个概率尺度。</p>
<p>校准的可视化工具通常是可靠性图（Reliability Diagram）。做法是把预测置信度分成若干区间，例如 <span displaypfx="inline-" class="mathjax-container">\([0.0,0.1),[0.1,0.2),\dots\)</span>，然后对每个区间分别计算平均置信度与真实准确率。若图上的点接近对角线 <span displaypfx="inline-" class="mathjax-container">\(y=x\)</span>，说明校准较好；若点普遍落在对角线下方，说明模型报得比实际更自信；若点在对角线上方，则说明模型偏保守。</p>
<p>常用数值指标是期望校准误差（Expected Calibration Error, ECE）：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{ECE}=\sum_{m=1}^{M}\frac{|B_m|}{n}\,\big|\mathrm{acc}(B_m)-\mathrm{conf}(B_m)\big|\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(M\)</span> 是置信度分箱数， <span displaypfx="inline-" class="mathjax-container">\(B_m\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 个置信度区间中的样本集合， <span displaypfx="inline-" class="mathjax-container">\(|B_m|\)</span> 是该区间样本数， <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 是总样本数， <span displaypfx="inline-" class="mathjax-container">\(\mathrm{acc}(B_m)\)</span> 是该区间的实际准确率， <span displaypfx="inline-" class="mathjax-container">\(\mathrm{conf}(B_m)\)</span> 是该区间的平均预测置信度。ECE 的含义很直接：把每个置信区间里“说得多准”和“实际多准”的差值取绝对值，再按样本占比加权平均。ECE 越小，表示整体校准越好。</p>
<p>另一类常见指标是 Brier Score。对二分类，它定义为：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Brier}=\frac{1}{n}\sum_{i=1}^{n}(\hat p_i-y_i)^2\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\hat p_i\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个样本的预测正类概率， <span displaypfx="inline-" class="mathjax-container">\(y_i\in\{0,1\}\)</span> 是真实标签。它既惩罚分类错误，也惩罚概率刻度不准，因此兼顾区分能力与概率质量。与单纯 Accuracy 不同，Brier Score 会区分“错得有多离谱”：把一个负样本报成 <span displaypfx="inline-" class="mathjax-container">\(0.51\)</span> 和报成 <span displaypfx="inline-" class="mathjax-container">\(0.99\)</span>，代价并不相同。</p>
<p>工程上最常见的后处理方法是温度缩放（Temperature Scaling）。设原始 logits 为 <span displaypfx="inline-" class="mathjax-container">\(z_i\)</span>，则缩放后的 softmax 概率写成：</p>
<span displaypfx="" class="mathjax-container">\[p_i=\frac{\exp(z_i/T)}{\sum_j \exp(z_j/T)}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(T&gt;0\)</span> 是温度参数。 <span displaypfx="inline-" class="mathjax-container">\(T&gt;1\)</span> 会把分布拉平，降低过度自信； <span displaypfx="inline-" class="mathjax-container">\(T&lt;1\)</span> 会把分布压尖，提高置信度。温度参数通常在验证集上通过最小化负对数似然（Negative Log-Likelihood, NLL）来拟合，然后固定用于测试或部署阶段。它不会改变类别排序，因此常能在几乎不影响 Accuracy 的前提下改善概率校准。</p>
<div class="blog_h3"><span class="graybg">分类指标</span></div>
<div class="blog_h4"><span class="graybg">混淆矩阵（Confusion Matrix）</span></div>
<p>分类指标通常从混淆矩阵（Confusion Matrix）出发。设正类预测结果统计为真阳性 <span displaypfx="inline-" class="mathjax-container">\(TP\)</span>、假阳性 <span displaypfx="inline-" class="mathjax-container">\(FP\)</span>、真阴性 <span displaypfx="inline-" class="mathjax-container">\(TN\)</span>、假阴性 <span displaypfx="inline-" class="mathjax-container">\(FN\)</span>。不同指标本质上是在回答不同问题：是看“总共判对多少”，还是看“判成正类时有多准”，还是看“真实正类抓到了多少”。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/confusion-matrix.png"><img class="alignnone size-full wp-image-41785" src="https://blog.gmem.cc/wp-content/uploads/2026/03/confusion-matrix.png" alt="confusion-matrix" width="1024" height="776" /></a></p>
<div class="blog_h4"><span class="graybg">Accuracy</span></div>
<p>准确率（Accuracy）定义为</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Accuracy}=\frac{TP+TN}{TP+TN+FP+FN}\]</span>
<p>它衡量“总体上判对了多少比例”，适合类别相对平衡、不同错误代价接近的场景。但在类别极不平衡时会误导：例如癌症筛查里，99% 都是阴性时，模型全判阴性也可能有 99% Accuracy，却毫无检测价值。</p>
<div class="blog_h4"><span class="graybg">Precision</span></div>
<p>精确率（Precision）定义为</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Precision}=\frac{TP}{TP+FP}\]</span>
<p>它回答的是：“所有被模型判成正类的样本里，有多少真的为正。”当误报成本很高时，Precision 特别重要。例：垃圾邮件过滤里，如果把正常邮件误判成垃圾邮件代价很高，就要关心 Precision。</p>
<div class="blog_h4"><span class="graybg">Recall</span></div>
<p>召回率（Recall）定义为</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Recall}=\frac{TP}{TP+FN}\]</span>
<p>它回答的是：“所有真实正类里，有多少被模型找出来了。”当漏报成本很高时，Recall 更关键。例：医学筛查里漏掉患者可能比多做一次复检更危险，因此 Recall 往往比 Precision 更重要。</p>
<div class="blog_h4"><span class="graybg">F1 Score</span></div>
<p>F1 值（F1 Score）是 Precision 与 Recall 的调和平均：</p>
<span displaypfx="" class="mathjax-container">\[F_1=\frac{2\cdot \mathrm{Precision}\cdot \mathrm{Recall}}{\mathrm{Precision}+\mathrm{Recall}}\]</span>
<p>之所以用调和平均而不是普通平均，是因为它会惩罚“一高一低”的不平衡情况。若一个模型 Precision 极高但 Recall 很低，它并不能拿到高 F1。F1 适合正负样本不平衡、且希望兼顾漏报与误报的场景。</p>
<div class="blog_h4"><span class="graybg">AUC-ROC</span></div>
<p>AUC-ROC 衡量模型在不同分类阈值下区分正负样本的整体能力。ROC 曲线横轴是假阳性率（False Positive Rate），纵轴是真阳性率（True Positive Rate）。AUC 是曲线下面积，范围在 <span displaypfx="inline-" class="mathjax-container">\([0,1]\)</span>；越接近 1，说明模型越能把正样本排在负样本前面。</p>
<p>它不依赖某一个固定阈值，因此适合比较“排序能力”。但在极端不平衡数据上，PR 曲线（Precision-Recall Curve）常更敏感，因为 ROC 容易被大量真阴性“冲淡”。</p>
<div class="blog_h3"><span class="graybg">回归指标</span></div>
<p>回归指标（Regression Metrics）衡量预测值与真实值之间的数值偏差。它们关注的不是“判对类别”，而是“偏差有多大、对大误差是否敏感、模型解释了多少波动”。以房价预测为例，预测 300 万和真实 320 万之间的差距，就是典型回归误差。</p>
<div class="blog_h4"><span class="graybg">MAE</span></div>
<p>平均绝对误差（Mean Absolute Error, MAE）定义为</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{MAE}=\frac{1}{N}\sum_{i=1}^{N}|\hat y_i-y_i|\]</span>
<p>它直接度量“平均差了多少个原始单位”，解释最直观。若房价单位是万元，MAE=12 就表示平均误差约 12 万元。由于使用绝对值，MAE 对离群点没有 MSE 那么敏感。</p>
<div class="blog_h4"><span class="graybg">MSE</span></div>
<p>均方误差（Mean Squared Error, MSE）定义为</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{MSE}=\frac{1}{N}\sum_{i=1}^{N}(\hat y_i-y_i)^2\]</span>
<p>平方会放大大误差，因此 MSE 对离群点更敏感。它常用于你希望“大错要被重罚”的场景。高斯噪声假设下，最小化 MSE 还对应最大似然估计，因此它不仅是工程指标，也是概率建模结果。</p>
<div class="blog_h4"><span class="graybg">RMSE</span></div>
<p>均方根误差（Root Mean Squared Error, RMSE）是</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{RMSE}=\sqrt{\mathrm{MSE}}\]</span>
<p>它保留了 MSE 对大误差更敏感的性质，同时把单位拉回原始量纲，因此更易解释。若房价 RMSE 为 20 万元，可以直接理解为“典型误差量级约 20 万元”。</p>
<div class="blog_h4"><span class="graybg">R²</span></div>
<p><span displaypfx="" class="mathjax-container">\[R^2\]</span>（决定系数，Coefficient of Determination）回答的是：<span style="background-color: #c0c0c0;">相比于最朴素的瞎猜基线，你的回归模型到底把预测提升了多少</span>。要理解它，只需要在脑子里放两条线：一条是“什么都不知道时只能猜平均值”的水平线，另一条是模型给出的预测曲线。</p>
<p>先看最朴素的基线。假设你要预测一批房子的价格，但你手里没有面积、地段、楼龄这些特征，别人却逼着你给出预测。此时最不容易挨打的办法，不是胡乱报一个数字，而是对所有房子都猜样本平均价 <span displaypfx="inline-" class="mathjax-container">\(\bar y\)</span>。在散点图上，这对应一条横向的水平线。</p>
<p>真实房价 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 会散落在这条平均线的上下。每个点到平均线的垂直距离 <span displaypfx="inline-" class="mathjax-container">\(y_i-\bar y\)</span>，就是“瞎蒙平均值”时犯下的误差。把这些误差平方后全部加起来，就得到</p>
<span displaypfx="" class="mathjax-container">\[\sum_i(y_i-\bar y)^2\]</span>
<p>这就是公式里的分母。它衡量的不是模型误差，而是这批数据本身原来就有多分散、多混乱。也可以把它理解为目标变量的<span style="background-color: #c0c0c0;">总波动</span>、总混沌程度，或者说“在完全不用特征时，世界原本有多少东西解释不了”。</p>
<p>现在再看你的模型。你训练出一个回归模型，它根据输入特征给出预测 <span displaypfx="inline-" class="mathjax-container">\(\hat y_i\)</span>。在图上，这不再是那条死板的水平线，而是一条试图穿过散点云中心的预测曲线。模型当然不可能完美，所以每个真实值 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 与预测值 <span displaypfx="inline-" class="mathjax-container">\(\hat y_i\)</span> 之间仍会有垂直误差，这个误差就是残差（Residual）。</p>
<p>把这些模型仍然没解释掉的误差平方后加起来，就得到</p>
<span displaypfx="" class="mathjax-container">\[\sum_i(\hat y_i-y_i)^2\]</span>
<p>这就是公式里的分子，也叫残差平方和（Residual Sum of Squares, RSS）。它代表模型已经尽力之后，世界上<span style="background-color: #c0c0c0;">依然残存的混沌</span>。分子越小，说明模型越贴近真实数据；分子越大，说明模型虽然复杂，但其实没把问题解释清楚。</p>
<p>于是</p>
<span displaypfx="" class="mathjax-container">\[R^2=1-\frac{\sum_i(\hat y_i-y_i)^2}{\sum_i(y_i-\bar y)^2}\]</span>
<p>这条式子就可以直接读成一句大白话：先看模型还剩下多少解释不了的波动，再除以最开始总共有多少波动，得到“模型搞不定的比例”；最后用 1 减掉它，剩下的就是<span style="background-color: #c0c0c0;">模型成功解释掉的波动比例</span>。</p>
<p>因此，若 <span displaypfx="inline-" class="mathjax-container">\(R^2=0.8\)</span>，意思不是“正确率 80%”，而是目标变量原本有 100 份波动，模型大约解释掉了其中 80 份，只剩 20 份还没解释；若 <span displaypfx="inline-" class="mathjax-container">\(R^2=0\)</span>，说明你的模型折腾半天，效果和“永远预测平均值”完全一样；若 <span displaypfx="inline-" class="mathjax-container">\(R^2&lt;0\)</span>，则表示模型比这个最朴素基线还差，常见原因是模型设错了、特征没信息，或实现上有 bug。</p>
<p>从老板视角看， <span displaypfx="inline-" class="mathjax-container">\(R^2\)</span> 的灵魂拷问其实只有一句：<span style="background-color: #c0c0c0;">相比直接拿平均值糊弄事，你这个复杂回归模型到底多解释了多少真实波动</span>。这也是为什么 <span displaypfx="inline-" class="mathjax-container">\(R^2\)</span> 特别适合回答“模型有没有真正利用特征学到东西”，但它不能替代 MAE、RMSE——因为 <span displaypfx="inline-" class="mathjax-container">\(R^2\)</span> 讲的是解释比例，而不是误差到底有多少个原始单位。</p>
<div class="blog_h2"><span class="graybg">优化算法</span></div>
<p>优化算法（Optimization Algorithms）解决的问题非常朴素：<span style="background-color: #c0c0c0;">模型参数该往哪个方向改，才能让损失函数持续下降</span>。只要训练目标能写成“最小化某个损失函数”，背后就需要一套更新参数的规则。线性回归、逻辑回归、神经网络、大语言模型训练，本质上都绕不开这个问题。</p>
<p>这里要先分清两件事。优化（Optimization）关心的是“训练损失能不能降下来”；泛化（Generalization）关心的是“模型在新样本上好不好”。一个优化器可能把训练集拟合得很好，但泛化依然一般；反过来，一个优化器如果连训练损失都压不下去，模型通常也谈不上有效。因此优化算法决定的是<span style="background-color: #c0c0c0;">你如何走向一个解</span>，而不是直接保证这个解一定最好。</p>
<div class="blog_h3"><span class="graybg">梯度下降</span></div>
<p>梯度下降（Gradient Descent）是最核心的一阶优化思想。设损失函数为 <span displaypfx="inline-" class="mathjax-container">\(L(\theta)\)</span>，参数为 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>，则梯度 <span displaypfx="inline-" class="mathjax-container">\(\nabla_\theta L(\theta)\)</span> 给出“损失上升最快的方向”。既然梯度指向上坡，那么要让损失下降，就应沿着它的反方向更新参数：</p>
<span displaypfx="" class="mathjax-container">\[\theta_{t+1}=\theta_t-\eta\,\nabla_\theta L(\theta_t)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\theta_t\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步的参数， <span displaypfx="inline-" class="mathjax-container">\(\eta\)</span> 是学习率（Learning Rate），决定每一步走多大； <span displaypfx="inline-" class="mathjax-container">\(\nabla_\theta L(\theta_t)\)</span> 是当前位置的斜率信息。学习率太大，容易一步跨过谷底甚至震荡发散；学习率太小，又会下降得极慢。它像蒙着雾下山：梯度告诉你脚下哪边更陡，学习率决定你每次迈多大步。</p>
<p>为什么很多模型不直接“解公式”，而要反复迭代？因为在深度学习里，参数维度极高，损失面又往往非凸（Non-convex），通常没有漂亮的闭式解（Closed-form Solution）。这时最现实的办法不是一次算出全局最优，而是利用局部斜率，一步一步把损失往下压。</p>
<p>当总损失是逐样本损失的平均时，梯度下降还可以写得更具体：</p>
<span displaypfx="" class="mathjax-container">\[L(\theta)=\frac{1}{N}\sum_{i=1}^{N}\ell(\theta;x_i)\]</span>
<span displaypfx="" class="mathjax-container">\[\nabla_\theta L(\theta)=\frac{1}{N}\sum_{i=1}^{N}\nabla_\theta \ell(\theta;x_i)\]</span>
<p>这也回答了“为什么可以批量喂输入”：梯度对求和是线性的，<span style="background-color: #c0c0c0;">整体梯度等于逐样本梯度的平均</span>，所以可以并行算每个样本的梯度，再求平均后统一更新参数。</p>
<p>满足这种“逐样本求和/求平均”结构的损失函数其实非常多。典型例子包括：线性回归里的均方误差（MSE）、平均绝对误差（MAE），二分类里的 logistic loss / binary cross-entropy，多分类里的交叉熵（Cross-Entropy），语言模型训练里的负对数似然（Negative Log-Likelihood, NLL）。它们都可以写成“每个样本先各自算一份损失，再在整个数据集上取平均”的形式，因此天然适合 mini-batch 训练。</p>
<p>但并不是所有目标都能严格写成这种完全独立的逐样本平均。若损失显式依赖<span style="background-color: #c0c0c0;">样本之间的相对关系</span>，情况就会复杂一些。例如排序学习里的 pairwise/listwise loss、度量学习中的 triplet loss，以及对比学习里的 InfoNCE，都会让一个样本的损失依赖同一个 batch 中的其他样本。此时虽然仍然可以按 batch 计算梯度，但它已经不是“每个样本各算各的、最后简单平均”那么干净的分解了。</p>
<p>工程上还常见另一种情况：目标函数可以写成“逐样本平均损失 + 正则项（Regularization）”。例如</p>
<span displaypfx="" class="mathjax-container">\[L(\theta)=\frac{1}{N}\sum_{i=1}^{N}\ell(\theta;x_i)+\lambda\Omega(\theta)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\Omega(\theta)\)</span> 可以是 <span displaypfx="inline-" class="mathjax-container">\(\|\theta\|_2^2\)</span> 这类参数惩罚项。前半部分仍然按样本分解，后半部分则是直接作用于参数本身，而不对应某一个单独样本。很多现代训练目标，本质上都是这两部分的组合。</p>
<p>几个常用训练量需要先分清：</p>
<ul>
<li>批大小（Batch Size）：一次更新使用多少个样本。</li>
<li>步（Step / Iteration）：一次参数更新。</li>
<li>轮（Epoch）：完整遍历一次训练集。</li>
</ul>
<p>若训练集大小为 <span displaypfx="inline-" class="mathjax-container">\(N\)</span>，batch size 为 <span displaypfx="inline-" class="mathjax-container">\(B\)</span>，则每个 epoch 的步数约为 <span displaypfx="inline-" class="mathjax-container">\(\lceil N/B\rceil\)</span>。工程里经常会说“训练了多少 step”，因为真正发生参数变化的是 step，而不是 epoch 这个更粗的计数单位。</p>
<div class="blog_h4"><span class="graybg">批量梯度下降（BGD）</span></div>
<p>批量梯度下降（Batch Gradient Descent, BGD）每次都用<span style="background-color: #c0c0c0;">整个训练集</span>来计算一次精确梯度，再更新参数。这里的 batch 指的不是现代深度学习里常说的一个 mini-batch，而是“整批训练数据”。它的优点是方向最稳定、梯度方差最小；缺点是每一步都很贵，数据一大就几乎不可用。它更适合小数据集、凸优化问题，或教材里说明“梯度下降原理”时使用。</p>
<div class="blog_h4"><span class="graybg">随机梯度下降（SGD）</span></div>
<p>随机梯度下降（Stochastic Gradient Descent, SGD）每次只用一个样本的梯度做更新。它的方向噪声很大，看起来像“跌跌撞撞地下山”，但每一步极便宜、更新极频繁，因此在数据流式到来或样本极多时很有价值。</p>
<ul>
<li>优点：更新快、内存开销小、天然适合在线学习（Online Learning）与流式数据（Streaming Data）。</li>
<li>优点：梯度噪声有时反而是好事，更容易离开鞍点（Saddle Point）和较差的局部极小。</li>
<li>缺点：轨迹抖动大，若学习率控制不好，训练容易不稳定。</li>
</ul>
<p>它像店长根据每一位新顾客的反馈立刻调价：反应很快，但也容易被个别顾客带偏。</p>
<p>适用场景：当你需要<span style="background-color: #c0c0c0;">用最新样本尽快产生参数更新</span>时，SGD（batch size=1）是最直接的选择，典型包括：</p>
<ul>
<li>在线学习（Online Learning）/流式数据（Streaming Data）：样本持续到来，要求增量更新，而不是离线反复扫全量数据。</li>
<li>分布漂移（Distribution Shift）与非平稳（Non-stationary）环境：用户偏好、市场、策略对抗等持续变化，需要快速跟踪新分布。</li>
<li>低延迟更新需求：例如广告/推荐/风控的在线校准，需要“见到一条新反馈就更新一点”。</li>
<li>资源受限或样本极大：内存无法容纳大 batch 或无法频繁计算全量梯度时，用单样本更新换取更低的每步计算与存储成本。</li>
</ul>
<p>在现代深度学习里，若使用 GPU/TPU 训练，大多数时候会用小批量梯度下降来兼顾吞吐与稳定性；纯 SGD 更常出现在在线/增量训练与部分强化学习（Reinforcement Learning, RL）设置中。</p>
<div class="blog_h4"><span class="graybg">小批量梯度下降（Mini-batch SGD）</span></div>
<p>小批量梯度下降（Mini-batch SGD）是在 BGD 与 SGD 之间折中：每次用一个 batch 的平均梯度更新。它既能利用 GPU 的并行算力，又保留一定梯度噪声，因此现代深度学习几乎都在这个范式下训练。</p>
<p>batch 太小，梯度估计噪声会很大；batch 太大，虽然每步更稳定，但显存压力更高、更新频率更低，有时还会让优化和泛化都变钝。因此 batch size 不是“越大越好”，而是吞吐、稳定性、显存和泛化之间的折中。</p>
<div class="blog_h3"><span class="graybg">动量法（Momentum）</span></div>
<p>单纯的梯度下降在“峡谷形”损失面里很容易左右来回震荡：沿陡峭方向上下摆动，沿真正有用的谷底方向前进却很慢。动量法（Momentum）的直觉是：不要只看当前这一脚的斜率，而要把过去几步的方向累积成一种“惯性”。</p>
<span displaypfx="" class="mathjax-container">\[v_{t+1}=\beta v_t+(1-\beta)g_t,\quad \theta_{t+1}=\theta_t-\eta v_{t+1}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(g_t\)</span> 是当前梯度， <span displaypfx="inline-" class="mathjax-container">\(v_t\)</span> 是累计出来的速度， <span displaypfx="inline-" class="mathjax-container">\(\beta\in[0,1)\)</span> 控制“记住过去多少信息”。 <span displaypfx="inline-" class="mathjax-container">\(\beta\)</span> 越大，方向越平滑、惯性越强；越小，则越接近普通梯度下降。类比来看，它像推一个有重量的小球下山：不会因为脚下的微小凸凹就频繁改道，而会沿长期更一致的下降方向滚动。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/momentum.jpg"><img class="alignnone size-full wp-image-41017" src="https://blog.gmem.cc/wp-content/uploads/2026/03/momentum.jpg" alt="momentum" width="100%" /></a></p>
<div class="blog_h3"><span class="graybg">AdaGrad / RMSProp</span></div>
<p>AdaGrad 与 RMSProp 属于自适应学习率（Adaptive Learning Rate）方法。它们的核心思想是：<span style="background-color: #c0c0c0;">不同参数的梯度尺度不同，不应该所有维度都用同一个固定步长</span>。历史上梯度很大的维度，后续步子要缩小；梯度稀疏或很小的维度，则可以走得更积极。</p>
<p>AdaGrad 累积历史平方梯度：</p>
<span displaypfx="" class="mathjax-container">\[s_{t+1}=s_t+g_t\odot g_t,\quad \theta_{t+1}=\theta_t-\eta\,\frac{g_t}{\sqrt{s_{t+1}}+\epsilon}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(g_t\odot g_t\)</span> 表示逐元素平方， <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 是数值稳定项。这样一来，历史上梯度一直很大的维度会被自动缩小学习率。AdaGrad 在稀疏特征（Sparse Features）任务里很有效，例如早期的文本与推荐场景；但它的问题是 <span displaypfx="inline-" class="mathjax-container">\(s_t\)</span> 只增不减，训练到后期学习率可能衰减得过头，参数几乎不再动。</p>
<p>RMSProp 用指数滑动平均替代“无限累积”，缓解这个问题：</p>
<span displaypfx="" class="mathjax-container">\[s_{t+1}=\rho s_t+(1-\rho)(g_t\odot g_t),\quad \theta_{t+1}=\theta_t-\eta\,\frac{g_t}{\sqrt{s_{t+1}}+\epsilon}\]</span>
<p>这样历史信息会逐步“遗忘”，使学习率缩放更关注近期梯度尺度。可以把 AdaGrad 理解成“终身记账”，而 RMSProp 更像“滚动记账”。</p>
<div class="blog_h3"><span class="graybg">Adam</span></div>
<p>Adam（Adaptive Moment Estimation）把动量法（Momentum）的一阶矩估计与 RMSProp 的二阶矩估计结合起来，因此它同时解决优化中的两个核心痛点：<span style="background-color: #c0c0c0;">方向感（往哪走）</span>与<span style="background-color: #c0c0c0;">节奏感（每步走多大）</span>。直觉上，可以把它理解为“带惯性的方向盘 + 自动变速箱”：方向由平均梯度决定，步长由梯度的尺度自动缩放。</p>
<div class="blog_h4"><span class="graybg">一阶矩：方向感（Momentum）</span></div>
<span displaypfx="" class="mathjax-container">\[m_{t+1}=\beta_1 m_t+(1-\beta_1)g_t\]</span>
<ul>
<li><span style="background-color: #c0c0c0;">索引约定</span>：第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 次更新先在 <span displaypfx="inline-" class="mathjax-container">\(\theta_t\)</span> 处计算 <span displaypfx="inline-" class="mathjax-container">\(g_t\)</span>，再把它并入动量得到 <span displaypfx="inline-" class="mathjax-container">\(m_{t+1}\)</span>；下标 <span displaypfx="inline-" class="mathjax-container">\(t+1\)</span> 表示“更新后状态”，不是未来信息。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(m_{t+1}\)</span>：并入 <span displaypfx="inline-" class="mathjax-container">\(g_t\)</span> 后的一阶矩（动量）估计。</li>
<li>右边第一项 <span displaypfx="inline-" class="mathjax-container">\(\beta_1 m_t\)</span>：把历史动量按系数 <span displaypfx="inline-" class="mathjax-container">\(\beta_1\)</span> 保留下来，表示“历史方向对新方向的贡献”。</li>
<li>右边第二项 <span displaypfx="inline-" class="mathjax-container">\((1-\beta_1)g_t\)</span>：把当前梯度按系数 <span displaypfx="inline-" class="mathjax-container">\(1-\beta_1\)</span> 注入动量，表示“当前观测对新方向的贡献”。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(g_t\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步的梯度，通常由当前 mini-batch 估计得到（<span displaypfx="inline-" class="mathjax-container">\(g_t=\nabla_\theta L(\theta_t)\)</span>）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(m_t\)</span>：一阶矩估计的滑动平均（动量项），可理解为“平均梯度方向”。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\beta_1\in[0,1)\)</span>：动量衰减系数，越大表示记忆越长、方向越平滑。</li>
</ul>
<p>两项系数满足 <span displaypfx="inline-" class="mathjax-container">\(\beta_1+(1-\beta_1)=1\)</span>，因此这是指数滑动平均（Exponential Moving Average, EMA，越近发生的事情，参考价值越大；越久远的事情，参考价值越小）。当 <span displaypfx="inline-" class="mathjax-container">\(m_0=0\)</span> 时，可把它展开为：</p>
<span displaypfx="" class="mathjax-container">\[m_{t+1}=(1-\beta_1)\sum_{k=0}^{t}\beta_1^{t-k}g_k\]</span>
<p>上式说明：越久远的梯度 <span displaypfx="inline-" class="mathjax-container">\(g_k\)</span> 权重按 <span displaypfx="inline-" class="mathjax-container">\(\beta_1^{t-k}\)</span> 指数衰减；经验上可把有效记忆长度理解为 <span displaypfx="inline-" class="mathjax-container">\(O\!\left(\frac{1}{1-\beta_1}\right)\)</span>。因此 <span displaypfx="inline-" class="mathjax-container">\(\beta_1\)</span> 越大，平均窗口越长，方向越平滑但响应越慢；<span displaypfx="inline-" class="mathjax-container">\(\beta_1\)</span> 越小，平均窗口越短，方向更敏捷但更易受噪声影响。</p>
<div class="blog_h4"><span class="graybg">二阶矩：节奏感（RMSProp）</span></div>
<p>RMSProp（Root Mean Square Propagation）用梯度平方的指数滑动平均（Exponential Moving Average, EMA）来估计每个参数维度的“尺度”，再用该尺度对当前梯度做逐元素归一化，从而把更新步伐控制在更稳定的量级。</p>
<span displaypfx="" class="mathjax-container">\[v_{t+1}=\beta_2 v_t+(1-\beta_2)(g_t\odot g_t),\quad \tilde g_t=\frac{g_t}{\sqrt{v_{t+1}}+\epsilon}\]</span>
<ul>
<li><span style="background-color: #c0c0c0;">索引约定</span>：第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 次更新先在 <span displaypfx="inline-" class="mathjax-container">\(\theta_t\)</span> 处计算 <span displaypfx="inline-" class="mathjax-container">\(g_t\)</span>，再用它更新得到 <span displaypfx="inline-" class="mathjax-container">\(v_{t+1}\)</span>；用 <span displaypfx="inline-" class="mathjax-container">\(v_{t+1}\)</span> 缩放 <span displaypfx="inline-" class="mathjax-container">\(g_t\)</span> 表示“用本次更新后的尺度估计做归一化”，不涉及未来信息。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(v_t\)</span>：上一轮更新结束后的二阶原点矩估计（EMA 状态，“更新前的尺度缓存”）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(v_{t+1}\)</span>：梯度的二阶原点矩（Second Raw Moment）估计，逐（梯度）维近似 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[g^2]\)</span>，刻画“历史梯度大小”的典型尺度。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(g_t\odot g_t\)</span>：当前梯度的逐元素平方（<span displaypfx="inline-" class="mathjax-container">\(\odot\)</span> 为 Hadamard 乘积），只保留幅度信息。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\sqrt{v_{t+1}}\)</span>：均方根（Root Mean Square, RMS）尺度；平方使量纲变为 <span displaypfx="inline-" class="mathjax-container">\(g^2\)</span>，开方把量纲还原到 <span displaypfx="inline-" class="mathjax-container">\(g\)</span>，从而可与 <span displaypfx="inline-" class="mathjax-container">\(g_t\)</span> 相除。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\tilde g_t\)</span>：按 RMS 尺度归一化后的梯度；分式表示逐元素相除（每个参数维度各自缩放）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\beta_2\in[0,1)\)</span>：衰减系数，控制尺度估计的记忆长度；越大表示对历史尺度更“长记忆”。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span>：数值稳定项，避免分母为 0 或过小。</li>
</ul>
<p>该缩放把“方向”和“尺度”解耦：方向仍由 <span displaypfx="inline-" class="mathjax-container">\(g_t\)</span> 给出；步长由 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{v_{t+1}}\)</span> 自适应调节。若某维长期梯度偏大，则 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{v_{t+1}}\)</span> 变大、该维更新被压小；若某维长期梯度偏小，则分母较小、该维相对步子更大。</p>
<p>若把 RMSProp 作为独立优化器使用，参数更新可写为 <span displaypfx="inline-" class="mathjax-container">\(\theta_{t+1}=\theta_t-\eta\,\tilde g_t\)</span>。在 Adam 中，同样的 RMS 缩放作用在一阶矩估计上：用 <span displaypfx="inline-" class="mathjax-container">\(\hat m_{t+1}\)</span> 替代 <span displaypfx="inline-" class="mathjax-container">\(g_t\)</span> 作为分子，并在下一节通过偏置修正得到 <span displaypfx="inline-" class="mathjax-container">\(\hat v_{t+1}\)</span> 作为分母。</p>
<div class="blog_h4"><span class="graybg">偏置修正：冷启动校正（Bias Correction）</span></div>
<p>因为 <span displaypfx="inline-" class="mathjax-container">\(m_0=v_0=0\)</span>，训练初期的滑动平均会系统性偏小（“没热起来”）。Adam 用偏置修正把它拉回合理尺度：</p>
<span displaypfx="" class="mathjax-container">\[\hat m_{t+1}=\frac{m_{t+1}}{1-\beta_1^{t+1}},\quad \hat v_{t+1}=\frac{v_{t+1}}{1-\beta_2^{t+1}}\]</span>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\hat m_{t+1},\hat v_{t+1}\)</span>：偏置修正后的估计，用来抵消初始化为 0 导致的早期偏小。</li>
<li>分母 <span displaypfx="inline-" class="mathjax-container">\(1-\beta^{t+1}\)</span>：校正“冷启动偏小”的缩放因子。由于初始化为 0，指数滑动平均在经历 <span displaypfx="inline-" class="mathjax-container">\(t+1\)</span> 次更新时只积累了约 <span displaypfx="inline-" class="mathjax-container">\(1-\beta^{t+1}\)</span> 的有效权重，因此会偏小；除以它相当于把估计值按同样比例放大回去。</li>
</ul>
<p>更严格地说：若假设 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[g_t]=\mu\)</span> 近似稳定，则由递推可得 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[m_{t+1}]=(1-\beta_1^{t+1})\mu\)</span>，因此 <span displaypfx="inline-" class="mathjax-container">\(\hat m_{t+1}=\frac{m_{t+1}}{1-\beta_1^{t+1}}\)</span> 是把它改成近似无偏估计。同理可得 <span displaypfx="inline-" class="mathjax-container">\(\hat v_{t+1}\)</span> 的修正。</p>
<div class="blog_h4"><span class="graybg">参数更新：方向 ÷ 尺度</span></div>
<span displaypfx="" class="mathjax-container">\[\theta_{t+1}=\theta_t-\eta\,\frac{\hat m_{t+1}}{\sqrt{\hat v_{t+1}}+\epsilon}\]</span>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\theta_t\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步参数（权重向量/张量）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\eta\)</span>：学习率（全局步长系数）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\sqrt{\hat v_{t+1}}\)</span>：逐元素开方；整项 <span displaypfx="inline-" class="mathjax-container">\(\frac{\hat m_{t+1}}{\sqrt{\hat v_{t+1}}+\epsilon}\)</span> 表示逐元素相除。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span>：数值稳定项，避免除以 0 或极小数。</li>
</ul>
<p>分子给方向，分母给节奏。一个极端例子能看出为什么 Adam 用 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[g^2]\)</span> 而不是方差：若某维梯度连续很多步都是 <span displaypfx="inline-" class="mathjax-container">\(g_t=10\)</span>，方差为 0 会导致除法不稳定；但 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[g^2]\approx 100\)</span> 给出稳定尺度，最终更新量级约为 <span displaypfx="inline-" class="mathjax-container">\(10/\sqrt{100}=1\)</span>（再加 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 保底）。</p>
<p>Adam 往往更易调参、早期收敛更快，因此在 Transformer、扩散模型和很多深度网络里是默认起点。但它并不是无条件最好：有些任务上，最终泛化仍可能不如精心调过的 SGD。实践中，配合权重衰减（Weight Decay）时，通常会使用 AdamW，把权重衰减从梯度自适应缩放里解耦出来，效果更稳。</p>
<div class="blog_h3"><span class="graybg">AdamW</span></div>
<p>AdamW（Adam with Decoupled Weight Decay）把权重衰减（Weight Decay）从 Adam 的自适应梯度缩放里<span style="background-color: #c0c0c0;">解耦</span>出来。原因是：在 Adam 这类自适应方法里，如果你把 L2 正则化写进损失（等价于把 <span displaypfx="inline-" class="mathjax-container">\(\lambda\theta\)</span> 加到梯度里），这个正则项也会被二阶矩 <span displaypfx="inline-" class="mathjax-container">\(\hat v_t\)</span> 的缩放影响，从而导致不同参数维度的“衰减强度”不再可控。</p>
<p>正则化本来应该是一个可控的、与梯度尺度无关的收缩力度；解耦后 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 的含义更稳定。</p>
<p>对比两种写法会更清楚：</p>
<ul>
<li>把 L2 正则化并入目标（常被口语化地称为“weight decay”，但在自适应优化器里并不等价）：先算 <span displaypfx="inline-" class="mathjax-container">\(g_t=\nabla_\theta L(\theta_t)+\lambda\theta_t\)</span>，再把这个 <span displaypfx="inline-" class="mathjax-container">\(g_t\)</span> 送入 Adam 的 <span displaypfx="inline-" class="mathjax-container">\(m_t,v_t\)</span> 与自适应步长。</li>
<li>AdamW（解耦权重衰减）：先算纯数据梯度 <span displaypfx="inline-" class="mathjax-container">\(g_t=\nabla_\theta L(\theta_t)\)</span> 并完成 Adam 的自适应更新，然后单独对参数做一次权重衰减。</li>
</ul>
<p>AdamW 的参数更新可写成：</p>
<span displaypfx="" class="mathjax-container">\[\theta_{t+1}=\theta_t-\eta\,\frac{\hat m_{t+1}}{\sqrt{\hat v_{t+1}}+\epsilon}-\eta\,\lambda\,\theta_t\]</span>
<ul>
<li>前半段 <span displaypfx="inline-" class="mathjax-container">\(-\eta\,\frac{\hat m}{\sqrt{\hat v}+\epsilon}\)</span>：Adam 的自适应梯度更新（由数据损失驱动）。</li>
<li>后半段 <span displaypfx="inline-" class="mathjax-container">\(-\eta\,\lambda\,\theta_t\)</span>：解耦的 weight decay（参数按比例收缩），其中 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 是衰减强度。</li>
</ul>
<p>其中第一项是 Adam 的自适应更新，第二项是独立的权重衰减（等价于每步把权重按比例拉向 0）。这种分离让 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 更像一个真正可解释的“收缩强度”，在 Transformer 等模型上通常更稳定、更好调。工程实现里常见做法是：对 bias 与归一化层参数（如 LayerNorm 的 <span displaypfx="inline-" class="mathjax-container">\(\gamma,\beta\)</span>）不做 weight decay，以免对尺度/偏置项造成不必要的收缩。</p>
<div class="blog_h3"><span class="graybg">Adadelta、Adamax 与 Nadam</span></div>
<p>Adadelta、Adamax、Nadam 都可以看作对前述优化器的延伸，而不是完全独立的新思想。Adadelta 试图修正 AdaGrad 学习率持续衰减的问题，用滑动窗口近似代替无限累积，并显式比较“更新量尺度”和“梯度尺度”；它在今天不再是默认主线，但在理解自适应学习率演进史时很重要。</p>
<p>Adamax 是 Adam 在 <span displaypfx="inline-" class="mathjax-container">\(L_\infty\)</span> 范数下的变体，用无穷范数尺度替代二阶矩尺度；Nadam 则把 Nesterov 动量思想引入 Adam。它们都不是 2026 年主流深度学习训练的默认首选，但属于常见优化器家族成员，因此在工程文档、旧代码或特定框架默认配置里仍会频繁出现。</p>
<div class="blog_h3"><span class="graybg">Muon</span></div>
<p>Muon 是一种面向神经网络隐藏层（Hidden Layer）权重矩阵（Weight Matrix）的优化器。它与 Adam/AdamW 的根本差异在于：AdamW 会为每个参数维度单独估计更新尺度，并对梯度/更新量做逐元素（Element-wise）归一化；Muon 则把一个二维权重张量视为一个整体，直接利用矩阵结构来塑造更新方向。因此，Muon 更像一种<span style="background-color: #c0c0c0;">矩阵感知（Matrix-aware）的优化器</span>。</p>
<p>到 2026 年，Muon 已经不只是优化器研究里的边缘尝试，而是进入了前沿大模型训练实践。DeepSeek V4 在其公开技术说明中，明确把 Muon Optimizer 列为核心训练升级之一，和混合注意力、mHC 并列。这个信号很重要：它说明在超大规模 Transformer 训练里，优化器设计已经不再只是“AdamW 一统天下”，而是开始针对<span style="background-color: #c0c0c0;">隐藏层矩阵更新的几何结构</span>做专门优化。</p>
<p>它的核心做法不是改变“沿梯度下降”这个大方向，而是改变“更新张量应该具有什么几何形状”。典型写法可以概括为：先对梯度做动量（Momentum）累积，再把这个矩阵更新做一次正交化（Orthogonalization）后处理，然后才真正更新参数：</p>
<span displaypfx="" class="mathjax-container">\[M_{t+1}=\beta M_t+(1-\beta)G_t\]</span>
<span displaypfx="" class="mathjax-container">\[\Delta W_t=\mathrm{Orth}(M_{t+1}),\quad W_{t+1}=W_t-\eta\,\Delta W_t\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(G_t\)</span> 是当前梯度矩阵， <span displaypfx="inline-" class="mathjax-container">\(M_t\)</span> 是动量缓冲， <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Orth}(\cdot)\)</span> 表示把更新矩阵变换到更“接近正交（Orthogonal）”的形状。工程实现里，这一步通常用 Newton-Schulz 迭代（Newton-Schulz Iteration）高效近似完成，因此 Muon 常被概括为“对动量更新做正交化”。</p>
<div class="blog_h4"><span class="graybg">为什么 DeepSeek V4 选择 Muon</span></div>
<p>DeepSeek V4 选择 Muon，不是因为它是一个“新奇替代品”，而是因为它同时满足了三条对超大模型训练非常现实的要求。</p>
<p>第一，Muon 与 Transformer / MoE 的参数形态高度匹配。DeepSeek V4 的主干里，大量最承重的可训练参数都来自注意力投影矩阵、MLP 线性层以及 MoE 专家里的大矩阵；而 Muon 正是把<span style="background-color: #c0c0c0;">二维权重矩阵</span>当作整体对象来优化，而不是像 AdamW 那样把每个坐标近似看成相互独立。对这种“模型大头是矩阵，且矩阵内部几何结构很重要”的体系，Muon 天然比纯逐元素缩放更对口。</p>
<p>第二，Muon 在大模型训练中的可扩展性，已经被专门论文论证过。早期 Muon 更像“小模型上效果很好”的优化器；但后续《Muon is Scalable for LLM Training》这篇论文给出了两个把它真正推到大规模训练里的关键条件：加入 weight decay，以及仔细控制每参数更新尺度（per-parameter update scale）。论文的 scaling law 结果进一步表明，在 compute-optimal 训练设定下，Muon 相对 AdamW 可达到大约 2 倍的计算效率。这意味着 Muon 对前沿团队的吸引力，不只是“理论上可能更好”，而是<span style="background-color: #c0c0c0;">同样预算下更可能更快逼近目标性能</span>。</p>
<p>第三，DeepSeek V4 的官方技术报告给出的目标非常直接：他们使用 Muon，是为了获得<span style="background-color: #c0c0c0;">更快收敛（faster convergence）</span>和<span style="background-color: #c0c0c0;">更强训练稳定性（greater training stability）</span>。这与 Muon 的优化逻辑是吻合的。Muon 并不只是“把梯度缩一缩”，而是试图把矩阵更新限制在更有结构的几何方向上，减少那些虽然逐元素看似合理、但从矩阵整体看会破坏谱结构和训练稳定性的更新。DeepSeek V4 的公开算法还进一步加入了 Nesterov 风格的动量组合、Hybrid Newton-Schulz 正交化，以及更新 RMS 重缩放，说明它们采用的并不是教科书里最原始的 Muon，而是一套为大规模训练显式工程化过的版本。</p>
<p>因此，可以把 DeepSeek V4 选择 Muon 的原因压缩成一句话：<span style="background-color: #c0c0c0;">当模型的主要学习对象本来就是大量巨型矩阵，并且训练预算、收敛速度与稳定性都成为一等约束时，矩阵感知型优化器会比纯逐元素自适应方法更有吸引力</span>。Muon 正好提供了这样一条路线。</p>
<ul>
<li>优势：对线性层/MLP/注意力里的二维权重矩阵，Muon 往往能给出更结构化的更新方向；在一些大模型训练设定下，它的训练效率与收敛速度优于 AdamW。</li>
<li>边界：Muon 不是全参数通吃的默认方案。它通常只用于隐藏层的二维参数；embedding、bias、归一化参数、输出层等非二维或语义不同的参数，实践中常继续交给 AdamW。</li>
<li>工程特征：它强调更新矩阵的谱结构（Spectral Structure），而不是单个坐标的独立缩放；因此超参数分组与参数类型划分比 AdamW 更重要。</li>
</ul>
<p>可以把 AdamW 与 Muon 的差别记成一句话：<span style="background-color: #c0c0c0;">AdamW 解决“每个参数该走多大步”，Muon 进一步关心“整个矩阵应该以什么形状移动”</span>。因此 Muon 更像是隐藏层矩阵优化的专用工具，而不是对所有参数一视同仁的通用默认项。</p>
<div class="blog_h3"><span class="graybg">学习率调度</span></div>
<p>学习率（Learning Rate）往往是最敏感的超参数之一。即使优化器相同，只要学习率设错，训练就可能完全失败。学习率调度（Learning Rate Scheduling）/退火（Annealing）的核心思想是：前期用相对大的步长快速下降，后期逐渐减小步长做精细收敛。</p>
<p>常见调度方式包括：</p>
<ul>
<li>Step decay：按 epoch 或 step 乘以固定因子衰减，简单直接。</li>
<li>Linear decay：线性下降到较小值或 0，常用于大模型训练后段。</li>
<li>Warmup + decay：先小步热身，再进入正常学习率，最后逐步衰减；对 Transformer 和大 batch 训练尤其常见。</li>
<li>余弦退火（Cosine Annealing）：将学习率从较大值逐步衰减到较小值，把优化从“高温探索”过渡到“低温收敛”，在训练后期降低梯度噪声（gradient noise）与过冲（overshoot）风险，使参数能在极小值附近稳定细化。余弦曲线在起点与终点的一阶导数为 0，避免阶梯式衰减的突变，因而衰减更平滑、后期更柔和。</li>
</ul>
<p>Warmup 为什么有用？因为训练刚开始时，参数还处在一个非常“生”的区域，梯度统计不稳定，若一上来就用很大学习率，模型容易发散。先用较小步长热身几百或几千步，再拉到目标学习率，通常更稳。</p>
<p>余弦退火的典型写法（从 <span displaypfx="inline-" class="mathjax-container">\(\eta_{\max}\)</span> 衰减到 <span displaypfx="inline-" class="mathjax-container">\(\eta_{\min}\)</span>）为：</p>
<span displaypfx="" class="mathjax-container">\[\eta(t)=\eta_{\min}+\frac{1}{2}(\eta_{\max}-\eta_{\min})\left(1+\cos\left(\pi\frac{t}{T}\right)\right)\]</span>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\eta(t)\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步使用的学习率。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\eta_{\max}\)</span> / <span displaypfx="inline-" class="mathjax-container">\(\eta_{\min}\)</span>：一个退火周期内的最大学习率与最小学习率。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(t\)</span>：当前步数（通常从 0 开始计），<span displaypfx="inline-" class="mathjax-container">\(T\)</span>：该周期的总步数。</li>
</ul>
<p>这个公式可以直接做两次代入来验证边界：当 <span displaypfx="inline-" class="mathjax-container">\(t=0\)</span> 时，<span displaypfx="inline-" class="mathjax-container">\(\cos(0)=1\)</span>，所以 <span displaypfx="inline-" class="mathjax-container">\(\eta(0)=\eta_{\max}\)</span>；当 <span displaypfx="inline-" class="mathjax-container">\(t=T\)</span> 时，<span displaypfx="inline-" class="mathjax-container">\(\cos(\pi)=-1\)</span>，所以 <span displaypfx="inline-" class="mathjax-container">\(\eta(T)=\eta_{\min}\)</span>。中间学习率按半个余弦周期平滑下降。</p>
<p>通过余弦（Cosine）提供了一个非常干净的<span style="background-color: #c0c0c0;">端点平滑（smooth endpoints）</span>性质：在起点和终点处变化率为 0。对上式求导可得</p>
<span displaypfx="" class="mathjax-container">\[\frac{d\eta}{dt}=-\frac{1}{2}(\eta_{\max}-\eta_{\min})\frac{\pi}{T}\sin\left(\pi\frac{t}{T}\right)\]</span>
<p>因此 <span displaypfx="inline-" class="mathjax-container">\(\sin(0)=\sin(\pi)=0\)</span>，学习率在周期开始与结束都不会出现突兀的“拐点”。相比之下，step decay 有不连续跳变，linear decay 在末端通常会突然到达下限并停止变化（出现不光滑的折点）。在非凸深度网络里，这种平滑退火往往更稳：前期保持较大步长便于探索，后期自然减小步长便于精细收敛。</p>
<p>工程上常见扩展是余弦重启（Cosine Annealing with Warm Restarts, SGDR）：把训练过程切成多个周期，每个周期把 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 重新从 0 开始计（并可逐周期拉长 <span displaypfx="inline-" class="mathjax-container">\(T\)</span>）。在标准 SGDR 中，<span style="background-color: #c0c0c0;">学习率在周期边界是不连续的</span>：它会在一个周期末端衰减到 <span displaypfx="inline-" class="mathjax-container">\(\eta_{\min}\)</span>，并在下一个周期起点从 <span displaypfx="inline-" class="mathjax-container">\(\eta_{\max}\)</span> 重新开始（参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 不会重置）。</p>
<p>这种“重启”的作用不是为了制造噪声，而是把优化过程从“后期小步精修”短暂切回“较大步长探索”，以应对非凸问题中的平台区（plateau）与次优盆地（suboptimal basin）。学习率拉高会增大每步更新幅度与梯度噪声（gradient noise）的有效影响，帮助轨迹跳出当前区域并探索新的吸引域；而如果当前区域确实是更稳健的平坦极小值（flat minimal），后续退火通常会把参数再次拉回并在附近更精细地收敛。工程上常见做法是把 <span displaypfx="inline-" class="mathjax-container">\(\eta_{\max}\)</span> 设在“不会破坏稳定性”的范围内；若重启瞬间仍担心不稳定，也可以在每次重启后加一个很短的 warmup，让学习率在少量步数内从较小值爬升到 <span displaypfx="inline-" class="mathjax-container">\(\eta_{\max}\)</span>。</p>
<p>没有一种调度策略对所有任务都最好。真正有效的做法是结合损失曲线、梯度稳定性、验证集表现和训练预算一起看：如果前期降不动，往往学习率偏小；如果剧烈震荡甚至发散，往往学习率偏大；如果后期长期卡在平台区，通常需要更细的衰减策略。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/sgdr.jpg"><img class="alignnone size-full wp-image-41059" src="https://blog.gmem.cc/wp-content/uploads/2026/03/sgdr.jpg" alt="sgdr" width="100%" /></a></p>
<div class="blog_h2"><span class="graybg">集成策略（Ensemble Learning）</span></div>
<p>集成学习（Ensemble Learning）的核心思想是：<span style="background-color: #c0c0c0;">不要把预测完全押在一个模型上，而是让多个模型以某种组织方式共同决定结果</span>。它背后的统计直觉并不神秘：如果多个模型的误差来源不完全相同，那么组合后的总误差通常会更小、更稳、更抗偶然扰动。</p>
<p>从误差分解视角看，集成学习最常见的两条路线分别在处理两种不同问题。Bagging 更擅长压低方差（Variance），适合“单个模型很容易被训练集波动带偏”的情形；Boosting 更擅长逐步降低偏差（Bias），适合“单个弱学习器表达力不够，但可以通过连续修正变强”的情形。树模型恰好非常适合这两条路线：单棵深树高方差，适合拿去做 Bagging；浅层回归树可作为局部纠错器，适合拿去做 Boosting。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">维度</td>
<td style="text-align: center;">Bagging</td>
<td style="text-align: center;">Boosting</td>
<td style="text-align: center;">Stacking</td>
</tr>
</thead>
<tbody>
<tr>
<td>核心目标</td>
<td>降低方差，让模型更稳</td>
<td>降低偏差，让模型更准</td>
<td>学习如何组合不同模型，让互补性显式变成一个新模型</td>
</tr>
<tr>
<td>训练方式</td>
<td>多个基模型并行独立训练</td>
<td>多个弱学习器串行依次训练</td>
<td>先训练多个基模型，再训练一个元学习器做组合</td>
</tr>
<tr>
<td>每轮关注什么</td>
<td>同一个原始任务，但训练数据子集和特征视角不同</td>
<td>前一轮没有拟合好的误差、残差或负梯度</td>
<td>不同基模型的输出在什么条件下更可信</td>
</tr>
<tr>
<td>最终聚合</td>
<td>平均或投票</td>
<td>加权累加</td>
<td>由元学习器学习组合规则</td>
</tr>
<tr>
<td>典型代表</td>
<td>随机森林</td>
<td>GBDT、XGBoost、LightGBM、CatBoost</td>
<td>多模型堆叠集成</td>
</tr>
<tr>
<td>更像什么</td>
<td>多位评委独立打分后求平均</td>
<td>接力纠错，每个人只补前面没做好的部分</td>
<td>多位专家先各自判断，再由总负责人学习何时该信谁</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Bagging</span></div>
<p>Bagging（Bootstrap Aggregating）的核心做法是对训练集进行多次自助采样（bootstrap sampling），即反复执行有放回采样（sampling with replacement）生成多个训练子集，并在这些子集上分别训练基模型，以降低方差（Variance）。由于每个模型见到的数据子集略有差异，学到的决策边界不会完全一致；最后对预测结果做平均或多数投票，可抵消一部分过拟合噪声。</p>
<p>Bagging 的关键不在“模型一定很多”，而在<span style="background-color: #c0c0c0;">人为制造模型之间的差异性</span>。如果每个基模型看到的训练数据完全相同、特征也完全相同、优化过程也完全相同，那么把它们重复训练很多次并不会带来真正互补的信息。bootstrap 采样、特征子采样、不同随机初始化，本质上都在做同一件事：让多个模型不要犯完全一样的错。</p>
<p>例：如果单棵决策树很容易被训练集中的偶然样本带偏，那么训练 100 棵在不同 bootstrap 样本上的树，再投票，通常会比只用 1 棵树稳定得多。这也是随机森林的核心直觉。</p>
<div class="blog_h3"><span class="graybg">Boosting</span></div>
<p>Boosting 的思路与 Bagging 相反：它不是“并行训练很多彼此独立的模型”，而是“串行训练一串弱学习器（Weak Learners），让后一个模型专门修正前一个模型没做好的部分”。因此它更像“老师批改作业”：每一轮都盯着上轮最容易出错的题继续强化。</p>
<p>以加法模型视角看，Boosting 逐步构造</p>
<span displaypfx="" class="mathjax-container">\[F_M(x)=\sum_{m=1}^{M}\alpha_m h_m(x)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(h_m(x)\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 个弱学习器， <span displaypfx="inline-" class="mathjax-container">\(\alpha_m\)</span> 是它的权重。公式不是为了堆符号，而是在表达：最终模型是“很多个简单模型的加权和”，每一轮都往当前模型上加一小块“修正项”。</p>
<p>这类“接力纠错”最直观的理解，是把当前模型与真实目标之间的差距看成下一轮的新任务。回归里，这个差距常直接表现为残差；更一般地，在可微损失下，它表现为损失对当前预测的负梯度。于是每一轮不再去重学整个标签，而是只学“接下来该往哪里补”。这就是 GBDT 家族能持续逼近目标函数的根本原因。</p>
<p>这里还可以再区分两种常见实现。较早的 Boosting，如 AdaBoost，更强调<span style="background-color: #c0c0c0;">把之前分错的样本权重调高</span>，让后续弱学习器更多关注难样本；GBDT 这一支则更强调<span style="background-color: #c0c0c0;">直接拟合当前损失的残差或负梯度</span>。两者都属于“串行纠错”，只是把“错误信息”传给下一轮的方式不同。</p>
<p>Boosting 也解释了为什么这类模型通常使用<span style="background-color: #c0c0c0;">较浅的小树</span>作为基学习器。因为每一棵树的职责不是独立解决全问题，而是在当前模型基础上补一个局部修正。如果每棵树都长得很深、很强，单轮就可能把训练集吃得过满，后续串行叠加更容易过拟合；若每棵树只做小而稳的修正，再配合学习率与早停，整体通常更可控。</p>
<div class="blog_h3"><span class="graybg">Stacking</span></div>
<p>Stacking（堆叠集成）不只是平均多个模型输出，而是再训练一个元学习器（Meta-Learner）去学习“什么时候该信哪个模型”。例如一个基模型擅长处理稀疏文本特征，另一个擅长处理数值特征，Stacking 可以学会在不同样本上动态加权它们。</p>
<p>它像“专家会诊 + 总负责人”：若文本模型和表格模型各有专长，元模型就负责综合判断谁在当前病例上更可信。</p>
<div class="blog_h2"><span class="graybg">机器学习编程</span></div>
<p>机器学习编程（Machine Learning Programming）处理的核心问题是：当一个模型被写成代码并真正运行起来时，<span style="background-color: #c0c0c0;">谁负责组织训练流程，谁负责表达数学操作，谁又负责在具体硬件上把这些操作算快</span>。这三层通常分别对应编程框架（Framework）、算子（Operator）和内核（Kernel）。理解这三个层次，有助于把“模型公式”与“工程实现”连接起来。</p>
<p>它们不是彼此独立的三个名词，而是一条自上而下的执行链。研究者或工程师先在框架里定义模型结构与训练流程；框架再把模型拆成一系列算子，例如矩阵乘法、卷积、归一化、激活、softmax；每个算子最终还需要落到某个硬件相关的内核实现上，才能在 CPU、GPU、TPU 或其他加速器上真正执行。因此，<span style="background-color: #c0c0c0;">框架决定开发体验，算子决定计算图语义，内核决定实际运行效率</span>。</p>
<div class="blog_h3"><span class="graybg">框架（Framework）</span></div>
<p>框架（Framework）并不是单一层次的概念。在现代机器学习工程里，至少要区分两层：一层是基础框架（Foundational Framework），负责张量（Tensor）、自动求导（Automatic Differentiation）、算子调度与底层执行；另一层是高层框架（High-level Framework），负责把某一类模型、某一类训练范式或某一类工程流程封装成更直接的接口。前者提供“地基”，后者提供“脚手架”和“现成结构”。</p>
<p>因此，PyTorch、TensorFlow、JAX 这类系统更适合放在基础框架层；而 Transformers、DeepSpeed、ModelScope、Lightning 这类工具，则更适合放在高层框架层。它们之间不是替代关系，而是典型的上下层关系：<span style="background-color: #c0c0c0;">高层框架通常建立在基础框架之上，用更强的任务抽象、更少的模板代码和更完整的工程能力，把常见训练与推理流程直接组织起来</span>。</p>
<div class="blog_h3"><span class="graybg">常见框架简介</span></div>
<p>从工程使用频率看，开发者最常接触的是一组已经按职责分层的常见框架：高层框架负责组织训练与推理流程，基础框架负责真正执行张量计算，某些系统还进一步偏向执行优化与部署。把这些名字放回分层结构里理解，比孤立记忆框架名称更清楚。</p>
<div class="blog_h4"><span class="graybg">高层框架（High-level Framework）</span></div>
<p>高层框架的核心价值在于，它们不重新发明张量计算和自动求导，而是在基础框架之上增加更强的任务语义、训练编排、模型生态或分布式能力。很多工程里，开发者日常接触最多的其实是这一层。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">高层框架</td>
<td style="text-align: center;">主要依赖的基础框架</td>
<td style="text-align: center;">核心定位</td>
<td style="text-align: center;">替你封装了什么</td>
<td style="text-align: center;">最适合的场景</td>
<td style="text-align: center;">与基础框架的关系</td>
</tr>
</thead>
<tbody>
<tr>
<td>Transformers</td>
<td>以 PyTorch 为主，也支持 TensorFlow 与 JAX / Flax</td>
<td>预训练 Transformer 模型与任务头生态</td>
<td>模型定义、权重加载、tokenizer / processor、Trainer、pipeline、任务头</td>
<td>NLP、LLM、多模态模型的微调与推理</td>
<td>通常不是自己实现底层训练系统，而是调用底层框架完成张量计算与反向传播</td>
</tr>
<tr>
<td>DeepSpeed</td>
<td>主要建立在 PyTorch 之上</td>
<td>大模型训练与推理优化框架</td>
<td>ZeRO、参数分片、优化器状态管理、分布式训练编排、推理加速</td>
<td>超大模型训练、多卡 / 多机扩展、显存受限训练</td>
<td>本质上是对 PyTorch 训练过程的增强与重写，而不是替代 PyTorch</td>
</tr>
<tr>
<td>Unsloth</td>
<td>主要建立在 PyTorch 之上，并与 Transformers、PEFT、TRL 等 Hugging Face 生态深度协同</td>
<td>面向 LLM 微调与对齐的性能导向高层框架</td>
<td>快速加载与训练配方、QLoRA / DoRA / RL 配置、量化微调、长上下文训练、导出到 GGUF / Ollama / vLLM / Hugging Face</td>
<td>单卡或少卡进行 LLM 微调、偏好对齐、消费级显卡上的高性价比实验</td>
<td>它不替代 PyTorch 或 Transformers，而是在它们之上把“高效微调 + 导出部署”这条链路进一步封装并做性能优化</td>
</tr>
<tr>
<td>ModelScope</td>
<td>主要建立在 PyTorch 之上，也兼容其他学习框架与平台能力</td>
<td>模型社区 + SDK + 训练 / 推理工作流</td>
<td>模型获取、pipeline、训练入口、评测、部署衔接、领域模型集成</td>
<td>中文生态、多模态任务、快速调用开源模型并做微调</td>
<td>更像“模型平台层 + 高层开发框架”，下层训练仍要落到基础框架执行</td>
</tr>
<tr>
<td>PyTorch Lightning</td>
<td>PyTorch</td>
<td>训练流程组织框架</td>
<td>Trainer、设备放置、日志、checkpoint、验证循环、分布式训练模板</td>
<td>希望保留 PyTorch 灵活性，同时减少训练样板代码</td>
<td>把 PyTorch 代码组织得更规范，但底层模型与梯度仍然是 PyTorch</td>
</tr>
<tr>
<td>Accelerate</td>
<td>PyTorch</td>
<td>分布式训练与多设备执行抽象</td>
<td>多 GPU / 多机启动、混合精度、设备管理、统一训练脚本适配</td>
<td>想在尽量少改代码的前提下把 PyTorch 训练扩展到分布式环境</td>
<td>不取代 PyTorch 训练代码，而是让同一份 PyTorch 代码更容易跨设备运行</td>
</tr>
<tr>
<td>Keras 3</td>
<td>可运行在 TensorFlow、JAX、PyTorch 之上，推理还可对接 OpenVINO</td>
<td>高层模型开发接口</td>
<td>Layer / Model 抽象、训练接口、callback、分布式 API、生态组件</td>
<td>需要高层建模接口且希望在多后端之间切换</td>
<td>处于基础框架之上，强调统一建模接口，而不是直接取代底层后端</td>
</tr>
<tr>
<td>Sentence Transformers</td>
<td>主要建立在 Transformers 与 PyTorch 之上</td>
<td>Embedding 与 reranker 高层框架</td>
<td>文本向量化、相似度训练、检索 / rerank 训练器、评测工具</td>
<td>语义检索、向量召回、文本匹配、reranking</td>
<td>属于面向特定任务族的高层框架，下层依赖 Transformers 与 PyTorch</td>
</tr>
<tr>
<td>MMEngine / OpenMMLab</td>
<td>主要建立在 PyTorch 之上</td>
<td>通用训练引擎与视觉算法框架底座</td>
<td>Runner、Hook、Config、数据流、训练 / 验证 / 测试流程组织</td>
<td>检测、分割、姿态估计等视觉任务</td>
<td>用统一工程抽象组织 PyTorch 训练，尤其适合复杂视觉实验体系</td>
</tr>
</tbody>
</table>
<p>这一层最容易让人产生“它自己就能训练模型”的直觉，但从执行链条看，它们大多只是把训练过程组织得更高级。以 Transformers 为例，<span style="background-color: #c0c0c0;">Trainer 可以发起训练，但底层的张量、梯度、优化器、自动求导与设备执行，通常仍然由 PyTorch 负责</span>；Lightning、Accelerate、DeepSpeed 也是同样的逻辑，只是它们封装的侧重点不同。</p>
<div class="blog_h4"><span class="graybg">Unsloth</span></div>
<p>Unsloth 最适合放在高层框架这一层理解。它并不重新定义 Tensor、自动求导（Autograd）或底层计算图，而是在 PyTorch、Transformers、PEFT、TRL 这一整套既有生态之上，把大语言模型（Large Language Model, LLM）的高频训练流程重新组织成更偏性能导向的开发体验。它解决的核心问题不是“怎样从零实现神经网络”，而是<span style="background-color: #c0c0c0;">怎样在尽可能小的显存和尽可能少的工程样板下，把 LLM 微调、对齐、导出与部署这条链路跑通</span>。</p>
<p>从开发者视角看，Unsloth 的典型工作流通常仍然是“加载 Hugging Face 模型 → 挂接 PEFT 适配器 → 用监督微调（SFT）或强化学习（RL）训练 → 导出到下游推理栈”。它的价值在于把这条链上几个最痛的环节一起压平：第一，量化微调（如 LoRA / QLoRA）与长上下文训练更容易直接落地；第二，针对 GRPO 等对齐训练给出更直接的入口；第三，把训练后的模型或适配器导出到 GGUF、Ollama、vLLM、SGLang、Hugging Face 等下游环境的路径做得更短。换言之，Unsloth 不是另起炉灶，而是把已有生态串得更紧，并对其中最贵的显存、最长的上下文和最复杂的导出环节做专项优化。</p>
<p>它与其他高层框架的边界也需要分清。Transformers 的优势在于模型家族与任务头生态最通用；Accelerate 更像多设备执行抽象；DeepSpeed 更偏分布式训练、参数分片与大规模集群优化；Unsloth 则把重心压在“单机到小规模多卡场景下，如何更快、更省显存地完成 LLM 微调与对齐”。因此，它尤其适合消费级 GPU、本地实验、小团队快速验证、Notebook 驱动的训练流程，以及以 LoRA / QLoRA / RL 为主的大模型增量训练。若任务是超大规模集群预训练或需要复杂的跨机并行策略，DeepSpeed / Megatron 一类系统通常仍然更中心；若任务是通用模型调用与标准微调，Transformers 仍然是最基础的入口。</p>
<p>工程上可以把 Unsloth 看成“面向 LLM 微调的高性能工作台”。它一端连接 Hugging Face 模型与适配器生态，另一端连接本地推理、GGUF、Ollama、vLLM、SGLang 等部署路径，中间则用一套更偏性能调优的训练封装把它们粘起来。对初学者，它降低的是上手门槛：少改几处配置就能跑通量化微调或 RL；对熟悉生态的工程师，它降低的是试验成本：同样的显存预算下，能尝试更长上下文、更复杂的对齐流程，或更快地把训练结果导出到不同推理栈。它的本质依然是高层工程抽象，而不是底层深度学习框架的替代品。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">比较维度</td>
<td style="text-align: center;">Transformers</td>
<td style="text-align: center;">DeepSpeed</td>
<td style="text-align: center;">Unsloth</td>
</tr>
</thead>
<tbody>
<tr>
<td>核心目标</td>
<td>统一模型与任务接口</td>
<td>放大训练规模与显存效率</td>
<td>压低 LLM 微调 / 对齐门槛并提升单机效率</td>
</tr>
<tr>
<td>最擅长的问题</td>
<td>模型加载、任务头、Trainer、pipeline</td>
<td>ZeRO、分布式并行、大模型集群训练</td>
<td>QLoRA、GRPO、长上下文、快速导出部署</td>
</tr>
<tr>
<td>典型使用者</td>
<td>几乎所有 NLP / LLM 开发者</td>
<td>大模型平台与多机训练团队</td>
<td>本地实验者、个人开发者、小团队 LLM 微调工程师</td>
</tr>
<tr>
<td>与 PyTorch 的关系</td>
<td>调用其张量与训练能力</td>
<td>增强其训练与并行系统</td>
<td>在其之上重组 LLM 微调与导出流程</td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">基础框架（Foundational Framework）</span></div>
<p>基础框架直接定义张量运算、计算图、自动求导、优化器、算子调度与设备执行能力。它们离硬件更近，也离“神经网络真正怎样被算出来”更近。高层框架能否存在，首先取决于这一层是否提供了足够稳定和强大的底座。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">基础框架</td>
<td style="text-align: center;">核心抽象</td>
<td style="text-align: center;">主要优势</td>
<td style="text-align: center;">最适合的场景</td>
<td style="text-align: center;">典型局限</td>
</tr>
</thead>
<tbody>
<tr>
<td>PyTorch</td>
<td>Tensor、<pre class="crayon-plain-tag">nn.Module</pre>、autograd、动态图（Dynamic Computation Graph）</td>
<td>灵活、直观、研究生态最强，训练与调试体验优秀</td>
<td>研究、论文复现、大模型训练、需要自定义训练逻辑的任务</td>
<td>如果完全手写训练循环，工程样板代码较多，部署链路常需额外工具配合</td>
</tr>
<tr>
<td>TensorFlow</td>
<td>Tensor、Layer / Model、自动求导、图执行与编译</td>
<td>训练与部署体系完整，服务化、端侧与工业链路成熟</td>
<td>企业级生产环境、需要完整训练到部署闭环的场景</td>
<td>研究阶段的编码与调试直观性通常不如 PyTorch</td>
</tr>
<tr>
<td>JAX</td>
<td>数组（Array）+ 函数变换 + XLA 编译</td>
<td>编译优化强，函数式表达清晰，适合大规模并行数值计算</td>
<td>需要强编译能力、自定义并行策略、科研数值实验的任务</td>
<td>函数式编程习惯要求更高，工程生态相对更偏高级用户</td>
</tr>
<tr>
<td>PaddlePaddle</td>
<td>Tensor、动态图 / 静态图、训练与产业工具链</td>
<td>中文生态与产业落地支持强，训练推理工具链完整</td>
<td>产业应用、教育场景、中文任务与本土化生态</td>
<td>国际社区规模与通用论文实现数量通常少于 PyTorch</td>
</tr>
</tbody>
</table>
<p>如果把机器学习工程比作建楼，那么基础框架提供的是钢筋、水泥、电路和承重结构。高层框架之所以能让开发速度显著提升，正是因为底层这些张量、梯度和算子能力已经由基础框架稳定提供。</p>
<div class="blog_h4"><span class="graybg">执行与部署系统（Execution and Deployment Stack）</span></div>
<p>除了高层框架和基础框架，还存在一类经常与“框架”混称、但职责不同的系统：执行与部署系统。它们的核心目标不是让用户更方便地写模型，而是让已经定义好的模型在特定硬件上更快、更省、更稳定地运行。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">系统</td>
<td style="text-align: center;">主要定位</td>
<td style="text-align: center;">最常见作用</td>
<td style="text-align: center;">典型场景</td>
<td style="text-align: center;">与前两层的关系</td>
</tr>
</thead>
<tbody>
<tr>
<td>ONNX Runtime</td>
<td>跨框架推理执行系统</td>
<td>加载 ONNX 图，做图优化、算子调度与多后端执行</td>
<td>统一部署、跨框架导出后的推理执行</td>
<td>位于模型定义之后，更接近运行时（Runtime）而不是训练框架</td>
</tr>
<tr>
<td>TensorRT</td>
<td>NVIDIA GPU 推理优化系统</td>
<td>图优化、层融合、量化与 kernel 自动选择</td>
<td>低延迟在线推理、高吞吐批量服务</td>
<td>通常接收上层框架导出的模型，再做更深的硬件侧优化</td>
</tr>
<tr>
<td>OpenVINO</td>
<td>Intel 硬件推理栈</td>
<td>模型转换、图优化、Intel CPU / iGPU / VPU 推理</td>
<td>Intel 服务器与边缘设备部署</td>
<td>更接近部署后端，而不是通用训练框架</td>
</tr>
<tr>
<td>TVM</td>
<td>深度学习编译栈</td>
<td>自动调优、代码生成、异构硬件适配</td>
<td>边缘部署、自定义芯片、性能工程</td>
<td>站在算子和内核之间，为不同硬件生成更优执行实现</td>
</tr>
</tbody>
</table>
<p>因此，讨论“框架”时最好先分清是哪一层：<span style="background-color: #c0c0c0;">高层框架负责把任务和流程组织起来，基础框架负责把张量与梯度真正算出来，执行与部署系统负责把已经定义好的模型在目标硬件上跑到更优</span>。这三层一旦混在一起，很多看似相近的名词就会失去边界。</p>
<div class="blog_h3"><span class="graybg">算子（Operator）</span></div>
<p>算子（Operator）是计算图（Computation Graph）的基本运算单元。它定义一个明确的数学变换：输入什么张量（Tensor）、输出什么张量、张量的形状（Shape）和数据类型（Data Type）如何变化，以及反向传播时梯度如何计算。框架层写出的模块、层、网络块，最终都会被拆解成一串更细粒度的算子。</p>
<p>从工程实现上看，算子这一层负责表达“数学语义”。例如一个线性层（Linear Layer）会被拆成 MatMul 与 Bias Add；一个自注意力（Self-Attention）模块会被拆成 Q/K/V 投影、MatMul、Scale、Mask、Softmax、再一次 MatMul、Dropout、LayerNorm 等。高层模块能否被编译优化，本质上取决于这些算子能否被识别、融合与高效执行。</p>
<p>常用算子可以分成四大类：线性代数与张量形状类、神经网络前向计算类、序列与索引操作类、训练与优化类。下面的表格按这一方式展开。</p>
<div class="blog_h4"><span class="graybg">线性代数与张量形状类</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">算子</td>
<td style="text-align: center;">核心作用</td>
<td style="text-align: center;">常见输入 / 输出</td>
<td style="text-align: center;">典型出现位置</td>
<td style="text-align: center;">实现与使用要点</td>
</tr>
</thead>
<tbody>
<tr>
<td>MatMul / GEMM</td>
<td>矩阵乘法，完成线性投影与特征混合</td>
<td>二维或更高维张量，输出新的线性组合</td>
<td>全连接层、注意力投影、MLP</td>
<td>最核心的高密度算子之一，常直接决定训练吞吐</td>
</tr>
<tr>
<td>Batch MatMul</td>
<td>批量矩阵乘法，多个矩阵对并行相乘</td>
<td>三维及以上张量</td>
<td>多头注意力（Multi-Head Attention）</td>
<td>常与转置、缩放、mask 连用</td>
</tr>
<tr>
<td>Add / Bias Add</td>
<td>逐元素加法</td>
<td>两个可广播张量</td>
<td>残差连接、偏置项、特征融合</td>
<td>常被融合到前后算子中减少访存</td>
</tr>
<tr>
<td>Sub / Mul / Div</td>
<td>逐元素减法、乘法、除法</td>
<td>逐元素张量运算</td>
<td>归一化、门控、缩放</td>
<td>广播规则必须和张量形状匹配</td>
</tr>
<tr>
<td>Scale</td>
<td>用常数或向量对张量缩放</td>
<td>输入张量与标量 / 向量</td>
<td>attention 中的 <span displaypfx="inline-" class="mathjax-container">\(1/\sqrt{d_k}\)</span> 缩放</td>
<td>经常被编译器与前后算子融合</td>
</tr>
<tr>
<td>Transpose / Permute</td>
<td>重排维度顺序</td>
<td>输入张量到相同元素、不同布局的张量</td>
<td>NCHW / NHWC 转换，多头维度重排</td>
<td>逻辑上不改值，但常改变内存访问模式</td>
</tr>
<tr>
<td>Reshape / View</td>
<td>改变张量形状而不改变元素总数</td>
<td>同样的数据，不同 shape</td>
<td>展平、分头、合并头、batch 展开</td>
<td>若内存不连续，可能触发额外复制</td>
</tr>
<tr>
<td>Expand / BroadcastTo</td>
<td>按广播规则扩展维度</td>
<td>低维张量扩展为高维张量</td>
<td>偏置广播、mask 扩展</td>
<td>逻辑扩展不一定真实复制数据</td>
</tr>
<tr>
<td>Squeeze / Unsqueeze</td>
<td>删除或插入长度为 1 的维度</td>
<td>维度数变化，数据值不变</td>
<td>batch / channel 维调整</td>
<td>常用于接口对齐和算子拼接</td>
</tr>
<tr>
<td>Concat / Stack</td>
<td>拼接多个张量</td>
<td>多个同类型张量合并为一个</td>
<td>多特征合并、多分支网络</td>
<td>Concat 沿已有维度拼接，Stack 会新增维度</td>
</tr>
<tr>
<td>Split / Chunk</td>
<td>将一个张量拆成多个子张量</td>
<td>一个张量拆成若干块</td>
<td>Q/K/V 切分、多分支路径</td>
<td>与 Concat、Stack 常成对出现</td>
</tr>
<tr>
<td>ReduceSum / ReduceMean / ReduceMax</td>
<td>沿某些维度做聚合</td>
<td>高维张量压缩成低维张量</td>
<td>池化、loss 聚合、统计量计算</td>
<td>归约（Reduction）通常对并行实现要求较高</td>
</tr>
<tr>
<td>EinSum</td>
<td>用爱因斯坦求和规则表达复合张量运算</td>
<td>多个张量到一个张量</td>
<td>复杂线性代数、注意力原型实现</td>
<td>表达力强，但实际性能往往依赖后端是否能分解优化</td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">神经网络前向计算类</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">算子</td>
<td style="text-align: center;">核心作用</td>
<td style="text-align: center;">常见输入 / 输出</td>
<td style="text-align: center;">典型出现位置</td>
<td style="text-align: center;">实现与使用要点</td>
</tr>
</thead>
<tbody>
<tr>
<td>Convolution</td>
<td>局部感受野加权求和</td>
<td>特征图与卷积核，输出新的特征图</td>
<td>CNN、视觉 backbone、语音模型</td>
<td>步幅、填充、分组卷积会显著影响性能与感受野</td>
</tr>
<tr>
<td>Depthwise / Group Convolution</td>
<td>按通道组或单通道卷积</td>
<td>特征图到特征图</td>
<td>MobileNet、轻量视觉网络</td>
<td>减少计算量，但对 kernel 实现要求更高</td>
</tr>
<tr>
<td>Pooling（Max / Avg）</td>
<td>局部下采样与聚合</td>
<td>特征图压缩为空间更小的特征图</td>
<td>CNN、时序聚合</td>
<td>降低分辨率与计算量，也带来信息损失</td>
</tr>
<tr>
<td>Adaptive Pooling</td>
<td>把输入压到固定输出尺寸</td>
<td>任意空间尺寸到固定尺寸</td>
<td>视觉分类头、全局池化</td>
<td>方便不同输入尺寸统一到下游全连接层</td>
</tr>
<tr>
<td>ReLU</td>
<td>负值截断为 0 的激活函数</td>
<td>逐元素非线性变换</td>
<td>MLP、CNN、分类头</td>
<td>实现简单，稀疏性强</td>
</tr>
<tr>
<td>GELU</td>
<td>平滑激活，保留小负值的连续变化</td>
<td>逐元素非线性变换</td>
<td>Transformer、LLM MLP</td>
<td>现代语言模型最常见的激活之一</td>
</tr>
<tr>
<td>SiLU / Swish</td>
<td>输入与 sigmoid 门控的乘积</td>
<td>逐元素非线性变换</td>
<td>高性能视觉与语言模型</td>
<td>平滑、效果稳定，常见于新型 backbone</td>
</tr>
<tr>
<td>Sigmoid</td>
<td>把实数映射到 <span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span></td>
<td>逐元素概率化</td>
<td>门控单元、二分类输出、多标签任务</td>
<td>饱和区梯度小，深层网络中通常不作主激活</td>
</tr>
<tr>
<td>Tanh</td>
<td>把实数映射到 <span displaypfx="inline-" class="mathjax-container">\((-1,1)\)</span></td>
<td>逐元素非线性变换</td>
<td>早期 RNN、门控结构</td>
<td>零中心，但同样存在饱和问题</td>
</tr>
<tr>
<td>Softmax</td>
<td>把一组分数归一化为概率分布</td>
<td>类别分数到概率</td>
<td>多分类头、attention 权重</td>
<td>常与交叉熵和 mask 配合，数值稳定性关键</td>
</tr>
<tr>
<td>LayerNorm</td>
<td>对单个样本的最后若干维做归一化</td>
<td>输入张量到同 shape 张量</td>
<td>Transformer、LLM</td>
<td>不依赖 batch 统计量，适合变长序列</td>
</tr>
<tr>
<td>BatchNorm</td>
<td>利用 batch 统计量做归一化</td>
<td>输入张量到同 shape 张量</td>
<td>CNN、视觉任务</td>
<td>训练与推理行为不同，小 batch 时效果可能下降</td>
</tr>
<tr>
<td>RMSNorm</td>
<td>基于均方根做归一化</td>
<td>输入张量到同 shape 张量</td>
<td>许多现代大语言模型</td>
<td>比 LayerNorm 更简洁，计算更轻</td>
</tr>
<tr>
<td>Attention / SDPA</td>
<td>基于相似度对值向量加权聚合</td>
<td>Q、K、V 到上下文表示</td>
<td>Transformer、跨模态模型</td>
<td>高层看是算子族，底层常映射到 FlashAttention 等 kernel</td>
</tr>
<tr>
<td>Embedding Lookup</td>
<td>根据离散索引查表取向量</td>
<td>token id 到 embedding 向量</td>
<td>NLP、推荐系统、类别特征</td>
<td>本质是参数矩阵的索引读取，不是普通 MatMul</td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">序列与索引操作类</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">算子</td>
<td style="text-align: center;">核心作用</td>
<td style="text-align: center;">常见输入 / 输出</td>
<td style="text-align: center;">典型出现位置</td>
<td style="text-align: center;">实现与使用要点</td>
</tr>
</thead>
<tbody>
<tr>
<td>Gather</td>
<td>按给定索引抽取元素或切片</td>
<td>源张量与索引张量</td>
<td>embedding、beam search、采样</td>
<td>访问模式离散，容易受内存带宽限制</td>
</tr>
<tr>
<td>Scatter / ScatterAdd</td>
<td>按索引写回或累加</td>
<td>目标张量、索引、更新值</td>
<td>图神经网络、稀疏更新</td>
<td>并发写冲突和原子操作代价常是性能瓶颈</td>
</tr>
<tr>
<td>Index Select</td>
<td>按某一维选取指定位置</td>
<td>张量与一维索引</td>
<td>子序列抽取、类别筛选</td>
<td>语义上比通用 gather 更窄，但常更清晰</td>
</tr>
<tr>
<td>Slice / Narrow</td>
<td>截取连续区间</td>
<td>大张量切出子张量</td>
<td>窗口注意力、局部特征抽取</td>
<td>若数据连续，可几乎零开销视图化</td>
</tr>
<tr>
<td>Mask Fill / Select</td>
<td>按布尔掩码选择或填充值</td>
<td>张量与 mask</td>
<td>attention mask、padding 屏蔽</td>
<td>对变长序列与非法位置处理非常关键</td>
</tr>
<tr>
<td>Where</td>
<td>按条件在两个值之间选择</td>
<td>条件张量与候选张量</td>
<td>条件计算、loss 屏蔽、数值裁剪</td>
<td>本质是逐元素条件分支</td>
</tr>
<tr>
<td>Argmax / Argmin</td>
<td>返回最大 / 最小值所在索引</td>
<td>张量到索引</td>
<td>分类预测、贪心解码</td>
<td>输出是位置而非概率，通常不可导</td>
</tr>
<tr>
<td>TopK</td>
<td>返回前 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个值及其索引</td>
<td>张量到值与索引</td>
<td>检索、beam search、采样截断</td>
<td>常与排序、候选筛选结合</td>
</tr>
<tr>
<td>Sort / Argsort</td>
<td>排序并返回顺序</td>
<td>张量到排序结果</td>
<td>排序损失、候选重排</td>
<td>复杂度高，尽量只在必要路径中使用</td>
</tr>
<tr>
<td>Pad</td>
<td>在边界补零或补指定值</td>
<td>原张量到更大张量</td>
<td>卷积前处理、batch 对齐、序列补齐</td>
<td>padding 策略会影响有效计算比例</td>
</tr>
<tr>
<td>Pack / Unpack Sequence</td>
<td>压缩或还原变长序列表示</td>
<td>变长序列与紧凑表示之间转换</td>
<td>RNN、语音与时序模型</td>
<td>用于减少 padding 带来的无效计算</td>
</tr>
<tr>
<td>Position Encoding Add</td>
<td>注入位置信息</td>
<td>token 表示与位置编码</td>
<td>Transformer、序列模型</td>
<td>绝对位置、相对位置、RoPE 在实现形式上不同</td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">训练与优化类</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">算子</td>
<td style="text-align: center;">核心作用</td>
<td style="text-align: center;">常见输入 / 输出</td>
<td style="text-align: center;">典型出现位置</td>
<td style="text-align: center;">实现与使用要点</td>
</tr>
</thead>
<tbody>
<tr>
<td>Dropout</td>
<td>训练时随机失活部分单元</td>
<td>输入张量到稀疏化后的张量</td>
<td>MLP、attention、分类头</td>
<td>训练和推理行为不同，推理阶段通常关闭</td>
</tr>
<tr>
<td>Cross-Entropy Loss</td>
<td>衡量预测分布与真实类别的差距</td>
<td>logits 与标签到标量损失</td>
<td>分类、语言模型、token 分类</td>
<td>实现中常与 log-softmax 融合提高稳定性</td>
</tr>
<tr>
<td>NLL Loss</td>
<td>负对数似然损失</td>
<td>对数概率与标签到损失</td>
<td>分类任务、序列建模</td>
<td>通常接在 log-softmax 之后</td>
</tr>
<tr>
<td>MSE Loss</td>
<td>均方误差</td>
<td>预测值与目标值到损失</td>
<td>回归、蒸馏、表示对齐</td>
<td>对异常值较敏感</td>
</tr>
<tr>
<td>L1 / SmoothL1 Loss</td>
<td>绝对误差或平滑绝对误差</td>
<td>预测值与目标值到损失</td>
<td>目标检测、鲁棒回归</td>
<td>比 MSE 对异常值更稳</td>
</tr>
<tr>
<td>KL Divergence</td>
<td>衡量两个分布之间的差异</td>
<td>两个概率分布到标量</td>
<td>知识蒸馏、VAE、分布对齐</td>
<td>输入通常需要是概率或对数概率</td>
</tr>
<tr>
<td>Backward / Gradient</td>
<td>沿计算图反向传播梯度</td>
<td>损失到各参数梯度</td>
<td>所有训练流程</td>
<td>框架自动求导的核心能力就体现在这里</td>
</tr>
<tr>
<td>Gradient Clip</td>
<td>限制梯度范数或幅值</td>
<td>梯度到裁剪后梯度</td>
<td>RNN、大模型训练</td>
<td>控制梯度爆炸，提升训练稳定性</td>
</tr>
<tr>
<td>Optimizer Step（SGD / Adam / AdamW）</td>
<td>根据梯度更新参数</td>
<td>参数、梯度、状态到新参数</td>
<td>每一步训练迭代</td>
<td>常被实现为 fused optimizer kernel 以减少开销</td>
</tr>
<tr>
<td>Weight Decay</td>
<td>对参数施加正则化收缩</td>
<td>参数到受约束更新</td>
<td>分类、语言模型、视觉模型</td>
<td>现代实现常与 AdamW 解耦</td>
</tr>
<tr>
<td>AllReduce</td>
<td>跨设备聚合梯度或统计量</td>
<td>多卡张量到同步后的张量</td>
<td>数据并行训练</td>
<td>严格说更接近通信算子，但在训练图中极常见</td>
</tr>
<tr>
<td>AllGather / ReduceScatter</td>
<td>跨设备收集或切分张量</td>
<td>多设备张量通信</td>
<td>张量并行、序列并行、ZeRO</td>
<td>大模型分布式训练不可缺少</td>
</tr>
</tbody>
</table>
<p>因此，算子层是连接“模型定义”和“底层执行”的语义中枢。看懂模型实际调用了哪些算子，基本就等于看懂了它在做哪些数学步骤，以及这些步骤能否被进一步融合和加速。</p>
<div class="blog_h3"><span class="graybg">内核（Kernel）</span></div>
<p>内核（Kernel）是算子的底层实现。它规定了某个算子如何映射到具体硬件：线程如何组织、数据如何分块、是否使用共享内存（Shared Memory）、是否调用向量化指令、是否走 Tensor Core、是否把多个小算子融合成一次执行。若说算子定义的是“做什么”，那么内核定义的就是“怎样在这台机器上把它做快”。</p>
<p>从性能工程角度看，内核层回答的是：<span style="background-color: #c0c0c0;">同一个数学算子，针对不同硬件、数据形状、精度格式和访存模式，哪种实现最优</span>。这也是为什么表面上同样是 MatMul、LayerNorm 或 Attention，不同框架和后端的速度会相差很大。</p>
<p>常见内核或内核族可以按下表理解：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">内核 / 内核族</td>
<td style="text-align: center;">主要服务的算子</td>
<td style="text-align: center;">典型硬件 / 后端</td>
<td style="text-align: center;">核心优化手段</td>
<td style="text-align: center;">最典型的收益</td>
<td style="text-align: center;">常见场景</td>
</tr>
</thead>
<tbody>
<tr>
<td>cuBLAS GEMM kernel</td>
<td>MatMul、Linear、Batch MatMul</td>
<td>NVIDIA GPU</td>
<td>tile blocking、Tensor Core、流水线、寄存器复用</td>
<td>把矩阵乘法做到接近硬件峰值吞吐</td>
<td>全连接层、attention 投影、MLP</td>
</tr>
<tr>
<td>cuDNN Convolution kernel</td>
<td>Convolution、Pooling、部分归一化与激活</td>
<td>NVIDIA GPU</td>
<td>direct convolution、im2col + GEMM、Winograd、FFT 自动选择</td>
<td>按输入形状自动切换最优卷积路径</td>
<td>CNN、视觉 backbone、语音前端</td>
</tr>
<tr>
<td>oneDNN / MKL kernel</td>
<td>MatMul、Convolution、Normalization</td>
<td>x86 CPU</td>
<td>SIMD 向量化、cache blocking、线程并行</td>
<td>提升 CPU 推理与训练效率</td>
<td>服务器 CPU 推理、无 GPU 环境</td>
</tr>
<tr>
<td>NCCL communication kernel</td>
<td>AllReduce、AllGather、ReduceScatter、Broadcast</td>
<td>NVIDIA 多 GPU</td>
<td>环形通信（Ring）、树形通信（Tree）、链路拓扑优化</td>
<td>降低多卡同步开销</td>
<td>数据并行、张量并行、大模型训练</td>
</tr>
<tr>
<td>FlashAttention kernel</td>
<td>Scaled Dot-Product Attention</td>
<td>NVIDIA GPU 及其他支持相应实现的加速器</td>
<td>分块、在线 softmax、kernel fusion、减少 HBM 访存</td>
<td>把 attention 从显存瓶颈拉回到更接近计算瓶颈</td>
<td>Transformer、LLM、长序列建模</td>
</tr>
<tr>
<td>Fused LayerNorm / RMSNorm kernel</td>
<td>LayerNorm、RMSNorm、Bias Add、Residual Add</td>
<td>GPU / CPU 后端</td>
<td>多步逐元素运算融合为一次访存</td>
<td>显著降低 memory-bound 算子的开销</td>
<td>Transformer block、LLM 推理</td>
</tr>
<tr>
<td>Fused MLP kernel</td>
<td>Bias Add、GELU / SiLU、Dropout、Residual</td>
<td>GPU</td>
<td>把连续逐元素算子合并，减少中间张量写回</td>
<td>减少 kernel launch 次数与显存读写</td>
<td>MLP block、前馈网络</td>
</tr>
<tr>
<td>Triton custom kernel</td>
<td>任意自定义算子或 fused operator</td>
<td>GPU</td>
<td>开发者手写 tile、访存布局与并行策略</td>
<td>在通用库缺少最优实现时获得定制性能</td>
<td>大模型训练、研究型性能优化、自定义融合</td>
</tr>
<tr>
<td>TensorRT generated kernel</td>
<td>部署图中的卷积、MatMul、激活、归一化、量化路径</td>
<td>NVIDIA GPU</td>
<td>图优化、层融合、低精度选择、kernel autotuning</td>
<td>显著降低推理时延并提升吞吐</td>
<td>在线推理服务、边缘推理</td>
</tr>
<tr>
<td>XLA generated kernel</td>
<td>JAX / TensorFlow 图中的可融合算子子图</td>
<td>GPU、TPU 等</td>
<td>图级融合、静态形状分析、编译生成目标代码</td>
<td>让一串算子整体下沉为更大的执行单元</td>
<td>JAX 训练、TPU 训练、编译型执行场景</td>
</tr>
<tr>
<td>TVM generated kernel</td>
<td>自定义张量表达式对应的算子</td>
<td>CPU、GPU、边缘加速器</td>
<td>自动调度、自动搜索、代码生成</td>
<td>跨异构硬件获得针对性实现</td>
<td>端侧部署、自定义芯片适配</td>
</tr>
<tr>
<td>PagedAttention / KV-cache kernel</td>
<td>增量解码中的 attention 与缓存访问</td>
<td>LLM 推理后端</td>
<td>分页管理 KV cache、优化随机访问和 batch 合并</td>
<td>提升长上下文与多请求并发推理效率</td>
<td>大语言模型在线推理</td>
</tr>
</tbody>
</table>
<p>理解内核时最重要的一点是：<span style="background-color: #c0c0c0;">一个算子并不对应唯一实现</span>。同样是卷积，可以选 direct convolution、Winograd 或 FFT；同样是 attention，可以选普通分步实现、FlashAttention 或推理场景下的 paged attention。真正的差异往往不在数学定义，而在访存方式、融合策略、并行粒度和硬件利用率上。</p>
<p>因此，内核并不是建模阶段最先暴露给用户的对象，却往往决定模型最终的吞吐、时延、显存占用、能耗和成本。模型结构决定“上限在哪里”，内核质量决定“这个上限能兑现多少”。</p>
<div class="blog_h3"><span class="graybg">三者关系</span></div>
<p>把三者串起来看，一个卷积层或注意力层的执行路径通常是这样的：开发者先在 PyTorch 或 JAX 中写出一个模块；框架把它拆成若干算子，例如 MatMul、Softmax、LayerNorm 或 Convolution；运行时再为每个算子选择对应硬件上的 kernel，例如 cuBLAS 的 GEMM kernel、cuDNN 的 convolution kernel，或 Triton 写成的自定义 fused kernel。整个训练与推理过程由框架负责调度，算子负责表达数学语义，内核负责把语义变成高性能机器代码。</p>
<p>可以用一个具体例子来理解。若在 PyTorch 中定义一个卷积层，那么：</p>
<ol>
<li>PyTorch 作为框架负责接收模块定义、组织张量、记录梯度关系并调度执行。</li>
<li>卷积（Convolution）作为算子表示“输入特征图与卷积核做局部加权求和”这一数学操作。</li>
<li>底层可能调用 cuDNN 提供的卷积 kernel，在 GPU 上以高度优化的方式完成真正计算。</li>
</ol>
<p>同样地，在 Transformer 中写一层自注意力时，框架会组织前向与反向图；MatMul、Softmax、Mask、LayerNorm 等作为算子组成计算链；而 FlashAttention、fused LayerNorm、paged attention 这类高性能实现，则属于内核或 kernel-level optimization 的范畴。</p>
<p>因此，这三个层次的关系可以概括为：<span style="background-color: #c0c0c0;">框架管理整个建模与执行流程，算子定义模型中的数学步骤，内核负责把这些步骤在具体硬件上高效落地</span>。从研究到工程落地的能力鸿沟，往往正体现在能否同时理解这三层。</p>
<div class="blog_h1"><span class="graybg">经典机器学习</span></div>
<div class="blog_h2"><span class="graybg">选型指南</span></div>
<p>经典机器学习的模型选择，本质上是在<span style="background-color: #c0c0c0;">任务形式、数据规模、特征形态、可解释性要求、训练与推断成本</span>之间做匹配。只要先判断“有没有标签”“输出是什么类型”“样本量有多大”“特征是不是稀疏或非线性”“是否需要概率或规则解释”，大多数任务都可以迅速缩小到少数几个候选模型。</p>
<p>本章中的模型并不是按“先进程度”排序，而是按建模假设来区分。线性模型假设边界或关系接近线性；树模型更擅长表格数据中的非线性交互；概率模型更强调分布解释；近邻模型依赖局部相似性；聚类与降维模型关注无监督结构；HMM（Hidden Markov Model, HMM）和条件随机场（Conditional Random Field, CRF）则处理序列标签之间存在依赖的结构化预测问题。</p>
<p>下面的表格不是概览式罗列，而是直接服务于选型。每一行都回答五个问题：<span style="background-color: #c0c0c0;">什么情况下优先选它、它最依赖什么数据条件、它能解决什么核心诉求、为什么它在该场景里合适、以及什么情况下应当换模型</span>。在大多数工程场景里，先按这些表格缩小范围，再进入具体模型细节，效率最高。</p>
<div class="blog_h3"><span class="graybg">任务类型：分类</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">优先选择条件</td>
<td style="text-align: center;">数据与特征前提</td>
<td style="text-align: center;">最主要价值</td>
<td style="text-align: center;">不建议优先使用的情况</td>
</tr>
</thead>
<tbody>
<tr>
<td>逻辑回归</td>
<td>需要稳定强基线、需要概率输出、需要解释每个特征如何影响类别</td>
<td>特征可以线性分离到一定程度；高维稀疏特征尤其合适，如 one-hot、词袋、统计特征</td>
<td>训练快、概率自然、权重可解释，适合作为第一版可上线模型</td>
<td>类别边界高度非线性、特征交互复杂且没有显式特征工程时</td>
</tr>
<tr>
<td>支持向量机（SVM）</td>
<td>样本中小规模、类别边界较清晰、希望用最大间隔提升泛化稳健性</td>
<td>特征需要做尺度统一；核 SVM 更适合中小规模，不适合超大样本</td>
<td>对边界样本建模强，在线性不可分但结构仍较规整时往往优于纯线性模型</td>
<td>数据量很大、需要快速训练与部署、或者必须输出天然概率时</td>
</tr>
<tr>
<td>决策树</td>
<td>需要把模型直接解释成规则路径，或业务天然是“按阈值分流”的形式</td>
<td>表格特征、混合数值与类别特征都可；不强依赖标准化</td>
<td>规则可视化最直接，便于和业务规则、审计规则对齐</td>
<td>追求最稳泛化性能时；单树通常方差高，容易过拟合</td>
</tr>
<tr>
<td>随机森林</td>
<td>表格分类需要稳健基线、希望少调参、担心单棵树过拟合</td>
<td>适合中等规模表格数据；对噪声、缺失和特征尺度通常更宽容</td>
<td>比单树稳定，通常先于复杂 boosting 模型给出可靠结果</td>
<td>追求表格任务的极致精度，或需要很小的模型体积时</td>
</tr>
<tr>
<td>梯度提升树（GBDT）</td>
<td>表格分类精度优先，特征中存在明显非线性与交互效应</td>
<td>适合结构化特征，不要求线性关系；通常需要一定调参</td>
<td>能逐轮修正前一轮错误，在中等规模表格任务上常是强力基线</td>
<td>需要极快训练、极少调参，或数据已经极大到串行 boosting 成本明显偏高时</td>
</tr>
<tr>
<td>XGBoost</td>
<td>工业表格分类、竞赛任务、缺失值与正则化处理都很重要</td>
<td>适合中大规模结构化数据；对特征工程和超参数较敏感，但工程支持成熟</td>
<td>精度高、鲁棒、缺失值处理成熟，是很多表格分类任务的首选之一</td>
<td>极端大规模、极度强调训练速度与内存效率时，LightGBM 往往更优先</td>
</tr>
<tr>
<td>LightGBM</td>
<td>大规模表格分类、高维稀疏特征、需要更快训练与更低内存消耗</td>
<td>适合样本量大、特征维度高的结构化任务；对类别特征和稀疏特征较友好</td>
<td>训练快、工程效率高，在工业 CTR、风控、推荐特征场景很常见</td>
<td>数据量较小且噪声较大时；叶子生长策略若不控制，容易过拟合</td>
</tr>
<tr>
<td>朴素贝叶斯</td>
<td>需要极快文本分类基线、小样本启动、希望先验证特征是否有判别力</td>
<td>最适合词袋、词频、计数类高维稀疏特征；默认接受条件独立近似</td>
<td>训练和推断都极快，在垃圾邮件、主题粗分类等任务上常有效</td>
<td>强依赖复杂特征相关性、类别边界非线性明显、或需要高精度概率校准时</td>
</tr>
<tr>
<td>K 近邻（KNN）</td>
<td>样本规模小、相似样本应有相似标签、希望不做显式训练</td>
<td>距离度量必须有意义；所有特征应标准化，且维度不能过高</td>
<td>局部模式直观，适合做小数据原型验证或相似样本检索式分类</td>
<td>高维稀疏特征、大规模数据、低延迟在线推断场景</td>
</tr>
<tr>
<td>线性判别分析（LDA）</td>
<td>有监督分类同时希望压缩特征维度，且类别统计结构较稳定</td>
<td>更适合每类近似高斯、类内协方差可估计的情况；样本数不能太少</td>
<td>把“降维”和“分类判别”结合起来，适合先压缩再分类的流程</td>
<td>类别分布非常复杂、强非高斯、强非线性时</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">任务类型：回归</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">优先选择条件</td>
<td style="text-align: center;">数据与特征前提</td>
<td style="text-align: center;">最主要价值</td>
<td style="text-align: center;">不建议优先使用的情况</td>
</tr>
</thead>
<tbody>
<tr>
<td>线性回归</td>
<td>需要连续值预测、希望建立可解释、可审计、可稳定复现的基线</td>
<td>目标与特征大致满足线性关系，或经过变换后接近线性</td>
<td>系数解释直观，便于分析“哪个因素把目标推高或拉低”</td>
<td>目标关系明显呈现复杂分段、交互或强非线性时</td>
</tr>
<tr>
<td>Lasso（L1 正则化）</td>
<td>特征很多、怀疑只有少数特征真正有效、希望模型自动做变量筛选</td>
<td>高维特征尤其适合；允许部分权重被压到 0</td>
<td>回归同时完成特征选择，适合构建更稀疏、更简洁的模型</td>
<td>大量强相关特征共同起作用时；它可能只保留其中部分特征</td>
</tr>
<tr>
<td>岭回归 / L2 正则化</td>
<td>特征共线性明显、担心普通线性回归权重不稳定</td>
<td>适合多个相关特征共同解释目标，而不希望稀疏淘汰其中一部分</td>
<td>通过收缩权重降低方差，使模型在相关特征场景下更稳定</td>
<td>首要目标是做特征筛选、希望很多系数直接变成 0 时</td>
</tr>
<tr>
<td>Elastic Net（L1 + L2 正则化）</td>
<td>既想做特征选择，又不希望在相关特征组上过于不稳定</td>
<td>高维、相关特征并存的回归任务最常见</td>
<td>综合 Lasso 与岭回归的优点，在稀疏性和稳定性之间折中</td>
<td>特征数量不多、模型目标非常简单时；其调参成本高于纯 L1 或 L2</td>
</tr>
<tr>
<td>决策树</td>
<td>目标值与输入关系近似分段函数，或业务逻辑天然围绕阈值展开</td>
<td>表格特征、混合类型特征都可；不需要严格线性假设</td>
<td>能学出“满足什么条件时预测值跳到哪个区间”的规则</td>
<td>希望预测曲线平滑、稳定，或希望泛化误差尽可能低时</td>
</tr>
<tr>
<td>随机森林</td>
<td>希望回归稳健、抗噪声、少调参，先拿到可靠效果</td>
<td>表格回归场景最常见；对特征尺度和局部异常较稳</td>
<td>综合多个树模型平均结果，通常比单树更不容易过拟合</td>
<td>要求外推能力强，或希望模型极度轻量、延迟极低时</td>
</tr>
<tr>
<td>梯度提升树（GBDT）</td>
<td>表格回归精度优先，目标和特征之间存在复杂非线性</td>
<td>适合异构表格特征；对异常值和长尾目标通常也较有韧性</td>
<td>在房价、评分、收益、风险等典型表格回归中常是强基线</td>
<td>算力非常紧、需要极低训练成本时</td>
</tr>
<tr>
<td>XGBoost / LightGBM</td>
<td>工业级表格回归、大规模特征、希望兼顾精度与工程效率</td>
<td>适合结构化数据；XGBoost 更稳健成熟，LightGBM 更强调速度与规模</td>
<td>常作为表格回归默认候选，能直接处理大量非线性和特征交互</td>
<td>数据关系本来就简单线性、并且强依赖系数解释时</td>
</tr>
<tr>
<td>K 近邻（KNN）</td>
<td>局部相似样本的目标值应当接近，希望用邻域平均直接预测</td>
<td>小数据、低维、有意义的距离度量是前提</td>
<td>局部平滑性强时实现简单有效，适合作为相似样本回归基线</td>
<td>高维、大样本、分布稀疏或在线延迟敏感时</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">任务类型：聚类</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">优先选择条件</td>
<td style="text-align: center;">数据与特征前提</td>
<td style="text-align: center;">最主要价值</td>
<td style="text-align: center;">不建议优先使用的情况</td>
</tr>
</thead>
<tbody>
<tr>
<td>K-Means</td>
<td>需要快速把样本分成若干组，并且预期每组都围绕某个均值中心展开</td>
<td>簇大致是球形或凸形；欧氏距离有意义；需要先给定 <span displaypfx="inline-" class="mathjax-container">\(K\)</span></td>
<td>速度快、实现简单，适合作为无监督分群第一选择</td>
<td>簇形状复杂、密度差异大、离群点很多或无法预先估计簇数时</td>
</tr>
<tr>
<td>层次聚类</td>
<td>不仅想得到分组结果，还想知道各组是如何逐层合并成层级结构的</td>
<td>更适合中小规模数据；需要能接受 <span displaypfx="inline-" class="mathjax-container">\(O(N^2)\)</span> 级别距离矩阵成本</td>
<td>能输出树状图，适合做群体结构分析与多粒度解释</td>
<td>数据量很大、只关心最终聚类标签、不关心层级关系时</td>
</tr>
<tr>
<td>DBSCAN</td>
<td>希望识别任意形状簇，并把稀疏孤立点单独作为噪声剔除</td>
<td>密度尺度相对统一；距离度量有意义；参数 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 邻域半径和最小点数可估计</td>
<td>不需要预设簇数，能自然处理非凸簇和离群点</td>
<td>不同区域密度差异很大时；单一密度阈值难兼顾所有簇</td>
</tr>
<tr>
<td>HDBSCAN</td>
<td>簇的密度明显不一致，希望保留 DBSCAN 的密度思想但更自适应</td>
<td>数据存在多密度结构；仍需合理距离度量</td>
<td>比 DBSCAN 更能处理“有的簇很密、有的簇较松”的真实数据</td>
<td>只需要一个快速、简单、可复现的基础分群结果时</td>
</tr>
<tr>
<td>Leiden</td>
<td>样本更适合先构成相似图，再按图上的社区结构分群；或者本身就是网络数据</td>
<td>已有图结构，或可以稳定构造 kNN / 相似图；更关心连通社区而非欧氏球形簇</td>
<td>能直接在图上优化社区划分，通常比 Louvain 更稳定，且更能避免内部断裂社区</td>
<td>原始特征空间中的欧氏距离本身就足够表达簇结构，且不希望引入构图步骤时</td>
</tr>
<tr>
<td>高斯混合模型（GMM）</td>
<td>希望得到软聚类结果，或者认为每个簇更像椭球形概率团块</td>
<td>连续特征较适合；默认每个簇可近似为一个高斯成分</td>
<td>不仅给簇标签，还给每个样本属于各簇的概率</td>
<td>簇形状非常复杂、非高斯、多峰结构难用少量高斯逼近时</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">任务类型：降维与可视化</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">优先选择条件</td>
<td style="text-align: center;">数据与特征前提</td>
<td style="text-align: center;">最主要价值</td>
<td style="text-align: center;">不建议优先使用的情况</td>
</tr>
</thead>
<tbody>
<tr>
<td>主成分分析（PCA）</td>
<td>希望做线性降维、压缩冗余特征、去噪或为下游模型降成本</td>
<td>主要结构能由少数线性主方向解释；不依赖标签</td>
<td>保留最大方差方向，是最稳健、最常用的无监督降维起点</td>
<td>真正关心的是类别判别性而不是总体方差，或数据结构强非线性时</td>
</tr>
<tr>
<td>线性判别分析（LDA）</td>
<td>有标签并希望把不同类别拉开后再做分类或可视化</td>
<td>类别标签可靠；类内散度与类间散度都可稳定估计</td>
<td>直接围绕“可分性”找投影，比 PCA 更贴近分类目标</td>
<td>没有标签、类别边界强非线性、或类别数太少导致可降维空间有限时</td>
</tr>
<tr>
<td>t-SNE</td>
<td>想把高维嵌入压到二维或三维，只为看局部邻域是否形成簇</td>
<td>更适合可视化，不适合直接做可逆特征压缩</td>
<td>局部邻域展示能力强，适合分析表征是否把相似样本聚到一起</td>
<td>需要把降维结果直接送入生产模型，或需要保留严格全局距离关系时</td>
</tr>
<tr>
<td>UMAP</td>
<td>希望做更快的大规模可视化，兼顾局部结构与部分全局连通性</td>
<td>适合高维嵌入、文本向量、图表示等复杂表征</td>
<td>通常比 t-SNE 更快，也更容易保留大体流形结构</td>
<td>需要线性可解释主方向，或需要结果完全稳定可重复到坐标级别时</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">任务类型：异常检测</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">优先选择条件</td>
<td style="text-align: center;">数据与特征前提</td>
<td style="text-align: center;">最主要价值</td>
<td style="text-align: center;">不建议优先使用的情况</td>
</tr>
</thead>
<tbody>
<tr>
<td>孤立森林（Isolation Forest）</td>
<td>通用表格异常检测，希望先得到一个稳健、扩展性好的异常分数</td>
<td>适合中大规模数据；不要求明确概率分布形式</td>
<td>通过随机切分隔离少数样本，通常是异常检测第一基线</td>
<td>异常定义依赖非常精细的局部密度差异时</td>
</tr>
<tr>
<td>局部异常因子（LOF）</td>
<td>异常并不是全局离群，而是“在本地邻域里显得稀疏”</td>
<td>距离度量必须合理；样本规模不宜过大</td>
<td>能发现那些整体位置不极端、但局部密度明显偏低的点</td>
<td>高维距离失真严重、或需要高吞吐在线检测时</td>
</tr>
<tr>
<td>单类支持向量机（One-Class SVM）</td>
<td>只有正常样本，目标是学习正常数据的封闭边界</td>
<td>特征需标准化；更适合中小规模；核方法对边界形状有帮助</td>
<td>适合“正常类定义清楚、异常类没有稳定样本”的场景</td>
<td>数据量很大、特征维度很高、参数难以稳定选择时</td>
</tr>
<tr>
<td>高斯混合模型（GMM）</td>
<td>希望把异常定义为低概率区域，并明确得到似然分数</td>
<td>连续数据更合适；分布能用若干高斯混合近似</td>
<td>异常判定有明确概率语义，适合风险评分和阈值分析</td>
<td>数据分布非常复杂、非高斯、或异常并不等价于低密度时</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">任务类型：序列标注与结构化预测</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">优先选择条件</td>
<td style="text-align: center;">数据与特征前提</td>
<td style="text-align: center;">最主要价值</td>
<td style="text-align: center;">不建议优先使用的情况</td>
</tr>
</thead>
<tbody>
<tr>
<td>隐马尔可夫模型（HMM）</td>
<td>序列较短、转移规律明显、希望在较小数据和较强先验下完成基础序列建模</td>
<td>状态转移与观测发射假设大致成立；问题适合生成式描述</td>
<td>结构清晰、推断高效，适合作为经典序列建模入门和小规模基线</td>
<td>标签强依赖全局上下文、特征复杂、需要大量判别式特征时</td>
</tr>
<tr>
<td>最大熵模型 / MEMM</td>
<td>希望直接建模条件概率，并用丰富离散特征做局部判别</td>
<td>手工特征、局部上下文和当前位置证据较强；可接受局部归一化</td>
<td>训练方便、可解释性较强，是传统 NLP 判别式序列建模的重要过渡路线</td>
<td>标签偏置（Label Bias）明显、需要整条路径全局归一化或强结构一致性时</td>
</tr>
<tr>
<td>条件随机场（CRF）</td>
<td>输出不是单点分类而是整条标签序列，并且相邻标签的合法性非常关键</td>
<td>一维链式序列最合适；上游特征或表示需要至少能提供较强局部证据</td>
<td>通过整体解码约束标签转移，使最终标签序列更一致、更符合任务结构</td>
<td>长距离语义主要由强表征模型决定、标签约束作用很弱，或任务更适合 span 建模时</td>
</tr>
<tr>
<td>结构化感知机</td>
<td>希望直接按任务评价函数更新结构化输出，强调错误驱动的大间隔修正</td>
<td>需要一个可解码的结构空间，例如序列、依存树或其他组合输出</td>
<td>训练目标直接对准“预测结构和真实结构的差异”，实现简洁、更新高效</td>
<td>需要良好概率解释、后验分数或复杂不确定性估计时</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">任务类型：密度估计与概率建模</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">优先选择条件</td>
<td style="text-align: center;">数据与特征前提</td>
<td style="text-align: center;">最主要价值</td>
<td style="text-align: center;">不建议优先使用的情况</td>
</tr>
</thead>
<tbody>
<tr>
<td>朴素贝叶斯</td>
<td>希望快速得到后验概率分类器，并接受条件独立近似</td>
<td>高维稀疏离散特征最合适，如文本词频、词出现计数</td>
<td>概率形式直接、估计简单，适合快速建模和在线系统</td>
<td>特征依赖结构复杂、需要精细表达联合分布时</td>
</tr>
<tr>
<td>高斯混合模型（GMM）</td>
<td>希望显式建模连续数据分布，或需要软聚类与密度估计统一完成</td>
<td>连续数据、多峰分布、可近似为若干高斯成分</td>
<td>每个样本都能得到各成分责任概率，解释和阈值分析都较自然</td>
<td>分布极端复杂、重尾严重、或高斯成分数难合理确定时</td>
</tr>
<tr>
<td>隐马尔可夫模型（HMM）</td>
<td>不仅要建模观测分布，还要建模隐藏状态在时间上的转移机制</td>
<td>观测是序列，且状态依赖主要体现在相邻时刻</td>
<td>把“序列生成机制”和“时序转移规律”统一到一个概率模型里</td>
<td>长程依赖很强、局部马尔可夫假设明显不成立时</td>
</tr>
</tbody>
</table>
<p>若只是要一个工程上可执行的默认起点，可以进一步压缩成如下规则：表格分类与回归优先从随机森林、GBDT、XGBoost、LightGBM 和线性模型里选；文本高维稀疏分类优先看逻辑回归和朴素贝叶斯；无监督分群先看 K-Means，再根据簇形状和噪声情况转向 DBSCAN、HDBSCAN 或 GMM；需要可视化时先区分是要线性压缩还是只要展示结构，再在 PCA、LDA、t-SNE、UMAP 中选择；涉及标签序列依赖时再进入 HMM 与 CRF。</p>
<div class="blog_h2"><span class="graybg">线性模型</span></div>
<p>线性模型（Linear Models）的核心不是“世界一定是线性的”，而是先用一个可解释、可优化、常常足够强的基线去刻画输入与输出的关系。很多复杂模型也可以看作“在线性读出层之前先做更强的特征变换”。</p>
<div class="blog_h3"><span class="graybg">线性回归</span></div>
<p>线性回归（Linear Regression）在回归任务中建模“输入特征的加权求和如何产生连续输出”。它的价值在于：<span style="background-color: #c0c0c0;">可解释</span>（权重直接对应特征影响）与<span style="background-color: #c0c0c0;">可优化</span>（凸问题，训练稳定）。</p>
<div class="blog_h4"><span class="graybg">模型与符号</span></div>
<p>给定训练集 <span displaypfx="inline-" class="mathjax-container">\(\{(\mathbf{x}_i,y_i)\}_{i=1}^{N}\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_i\in\mathbb{R}^d\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个样本的特征向量， <span displaypfx="inline-" class="mathjax-container">\(y_i\in\mathbb{R}\)</span> 是对应标签。线性回归假设单样本预测为：</p>
<span displaypfx="" class="mathjax-container">\[\hat y_i=\mathbf{w}^\top \mathbf{x}_i+b\]</span>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\hat y_i\)</span>：对 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 的预测。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\in\mathbb{R}^d\)</span>：权重向量（每个维度对应一个特征）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(b\in\mathbb{R}\)</span>：偏置（Bias），用于整体平移。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top \mathbf{x}_i\)</span>：内积（Dot Product），表示“按特征加权求和”。</li>
</ul>
<p>把全部样本写成矩阵形式。令设计矩阵（Design Matrix）<span displaypfx="inline-" class="mathjax-container">\(\mathbf{X}\in\mathbb{R}^{N\times d}\)</span> 的第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_i^\top\)</span>，标签向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\in\mathbb{R}^{N}\)</span> 的第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个分量为 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span>，全 1 向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{1}\in\mathbb{R}^{N}\)</span>。则：</p>
<span displaypfx="" class="mathjax-container">\[\hat{\mathbf{y}}=\mathbf{X}\mathbf{w}+b\mathbf{1}\]</span>
<p>直觉类比：它像“按因素打分再加总”。例如房价预测里，面积、地段评分、楼龄都可作为特征；权重正负决定影响方向，绝对值大小决定影响强弱。</p>
<div class="blog_h4"><span class="graybg">目标函数（最小二乘）</span></div>
<p>最常见的训练目标是最小化均方误差（Mean Squared Error, MSE）的总和（或平均）：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w},b}\ J_{\text{EN}}(\mathbf{w},b)=\frac{1}{N}\|\mathbf{y}-\mathbf{X}\mathbf{w}-b\mathbf{1}\|_2^2+\lambda_1\|\mathbf{w}\|_1+\lambda_2\|\mathbf{w}\|_2^2\]</span>
<p>这个目标可以拆成三部分理解：第一项 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{N}\|\mathbf{y}-\mathbf{X}\mathbf{w}-b\mathbf{1}\|_2^2\)</span> 是数据拟合误差，其中 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}-\mathbf{X}\mathbf{w}-b\mathbf{1}\)</span> 是所有样本的残差向量；第二项 <span displaypfx="inline-" class="mathjax-container">\(\lambda_1\|\mathbf{w}\|_1\)</span> 倾向把一部分权重直接压到 0；第三项 <span displaypfx="inline-" class="mathjax-container">\(\lambda_2\|\mathbf{w}\|_2^2\)</span> 倾向把权重整体缩小得更平滑。这里 <span displaypfx="inline-" class="mathjax-container">\(N\)</span> 是样本数， <span displaypfx="inline-" class="mathjax-container">\(\lambda_1,\lambda_2\)</span> 是正则化强度。若两者都取 0，就退化为普通最小二乘。</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(J(\mathbf{w},b)\)</span>：目标函数（Objective）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\|\cdot\|_2\)</span>：二范数（Euclidean Norm），向量的长度。</li>
</ul>
<p>平方项会对大残差施加更大惩罚，因此训练会优先修正“偏得很离谱”的样本。并且在高斯噪声假设下，最小二乘等价于最大似然估计（Maximum Likelihood Estimation, MLE）导出的解。</p>
<div class="blog_h4"><span class="graybg">解析解（正规方程）</span></div>
<p>把偏置吸收到特征中更方便：定义增广特征 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\mathbf{x}}_i=[\mathbf{x}_i;1]\in\mathbb{R}^{d+1}\)</span>，增广参数 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\mathbf{w}}=[\mathbf{w};b]\in\mathbb{R}^{d+1}\)</span>，增广矩阵 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\mathbf{X}}=[\mathbf{X},\mathbf{1}]\in\mathbb{R}^{N\times(d+1)}\)</span>。则 <span displaypfx="inline-" class="mathjax-container">\(\hat{\mathbf{y}}=\tilde{\mathbf{X}}\tilde{\mathbf{w}}\)</span>，目标为 <span displaypfx="inline-" class="mathjax-container">\(\min_{\tilde{\mathbf{w}}}\|\mathbf{y}-\tilde{\mathbf{X}}\tilde{\mathbf{w}}\|_2^2\)</span>。若 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\mathbf{X}}^\top\tilde{\mathbf{X}}\)</span> 可逆，则正规方程（Normal Equation）给出闭式解：</p>
<span displaypfx="" class="mathjax-container">\[\tilde{\mathbf{w}}^*=\left(\tilde{\mathbf{X}}^\top\tilde{\mathbf{X}}\right)^{-1}\tilde{\mathbf{X}}^\top\mathbf{y}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\mathbf{w}}^*\)</span> 表示最优增广参数，前 <span displaypfx="inline-" class="mathjax-container">\(d\)</span> 维对应原权重 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span>，最后一维对应偏置 <span displaypfx="inline-" class="mathjax-container">\(b\)</span>。 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\mathbf{X}}^\top\tilde{\mathbf{X}}\)</span> 汇总了特征之间的相关结构， <span displaypfx="inline-" class="mathjax-container">\(\tilde{\mathbf{X}}^\top\mathbf{y}\)</span> 汇总了特征与标签之间的相关程度，因此这个闭式解本质上是在“用整体相关关系一次性解出最优线性系数”。</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\tilde{\mathbf{X}}^\top\tilde{\mathbf{X}}\in\mathbb{R}^{(d+1)\times(d+1)}\)</span>：Gram 矩阵，度量特征之间的相关性。</li>
<li><span displaypfx="inline-" class="mathjax-container">\((\cdot)^{-1}\)</span>：矩阵逆；不可逆时通常用伪逆（Pseudo-inverse）或加正则化处理。</li>
</ul>
<p>工程实现中，直接求逆并不推荐；更稳定的做法是解线性方程组或用 QR/SVD 分解。</p>
<div class="blog_h4"><span class="graybg">实例：把公式算一遍</span></div>
<p>用一维数据拟合 <span displaypfx="inline-" class="mathjax-container">\(y\approx wx+b\)</span>。给定三点 <span displaypfx="inline-" class="mathjax-container">\((x,y)\in\{(0,1),(1,3),(2,5)\}\)</span>。构造增广矩阵与标签：</p>
<span displaypfx="" class="mathjax-container">\[\tilde{\mathbf{X}}=\begin{bmatrix}0 &amp; 1\\ 1 &amp; 1\\ 2 &amp; 1\end{bmatrix},\quad \mathbf{y}=\begin{bmatrix}1\\ 3\\ 5\end{bmatrix}\]</span>
<p>这个增广矩阵的每一行对应一个样本；第一列是原始特征 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，第二列固定为 1，用来把偏置 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 并入矩阵乘法。标签向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span> 则把三个样本的真实输出按顺序堆叠起来。</p>
<p>计算：</p>
<span displaypfx="" class="mathjax-container">\[\tilde{\mathbf{X}}^\top\tilde{\mathbf{X}}=\begin{bmatrix}5 &amp; 3\\ 3 &amp; 3\end{bmatrix},\quad \tilde{\mathbf{X}}^\top\mathbf{y}=\begin{bmatrix}13\\ 9\end{bmatrix}\]</span>
<p>左边的矩阵可以看成所有样本在“特征与偏置”两个方向上的二阶统计量： <span displaypfx="inline-" class="mathjax-container">\(5\)</span> 来自 <span displaypfx="inline-" class="mathjax-container">\(0^2+1^2+2^2\)</span>， <span displaypfx="inline-" class="mathjax-container">\(3\)</span> 来自 <span displaypfx="inline-" class="mathjax-container">\(0+1+2\)</span>，右边向量 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\mathbf{X}}^\top\mathbf{y}\)</span> 则分别对应 <span displaypfx="inline-" class="mathjax-container">\(\sum_i x_i y_i\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\sum_i y_i\)</span>。这正是正规方程所需要的汇总量。</p>
<p>解线性方程 <span displaypfx="inline-" class="mathjax-container">\(\left(\tilde{\mathbf{X}}^\top\tilde{\mathbf{X}}\right)\tilde{\mathbf{w}}=\tilde{\mathbf{X}}^\top\mathbf{y}\)</span>，即：</p>
<span displaypfx="" class="mathjax-container">\[\begin{cases}5w+3b=13\\ 3w+3b=9\end{cases}\Rightarrow w=2,\ b=1\]</span>
<p>这组方程的未知量只有两个：斜率 <span displaypfx="inline-" class="mathjax-container">\(w\)</span> 和偏置 <span displaypfx="inline-" class="mathjax-container">\(b\)</span>。解出 <span displaypfx="inline-" class="mathjax-container">\(w=2\)</span> 表示特征每增加 1，预测值增加 2；解出 <span displaypfx="inline-" class="mathjax-container">\(b=1\)</span> 表示当 <span displaypfx="inline-" class="mathjax-container">\(x=0\)</span> 时，模型基线输出为 1。</p>
<p>因此预测为 <span displaypfx="inline-" class="mathjax-container">\(\hat y=2x+1\)</span>，恰好穿过三点。这个例子展示了：正规方程把“最小化平方误差”的优化问题，转换成一个线性方程组。</p>
<div class="blog_h4"><span class="graybg">适用场景</span></div>
<ul>
<li>需要可解释的特征贡献（权重）与可校验的线性关系。</li>
<li>表格数据（Tabular）中，关系接近线性或可通过特征变换/交互项线性化。</li>
<li>作为强基线：先用线性模型定位数据问题、特征质量与噪声水平，再决定是否需要更复杂模型。</li>
<li>高维稀疏特征（如 one-hot、文本 bag-of-words）下，配合正则化可获得稳定解。</li>
</ul>
<p>当特征很多、共线性（Collinearity）强或数据量相对不足时，普通最小二乘（Ordinary Least Squares, OLS）会出现高方差：训练集拟合很好、验证集误差上升。正则化（Regularization）通过惩罚参数规模，把“拟合训练误差”与“控制模型复杂度”写进同一个目标函数。</p>
<div class="blog_h3"><span class="graybg">Lasso（L1 正则化）</span></div>
<p>Lasso 把参数惩罚写成一范数（<span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> Norm）：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w},b}\ J_{\text{lasso}}(\mathbf{w},b)=\frac{1}{N}\left\|\mathbf{y}-\mathbf{X}\mathbf{w}-b\mathbf{1}\right\|_2^2+\lambda\|\mathbf{w}\|_1\]</span>
<p>这个式子里，前半部分仍然是拟合误差，衡量预测和真实标签之间差多少；后半部分 <span displaypfx="inline-" class="mathjax-container">\(\lambda\|\mathbf{w}\|_1\)</span> 是稀疏惩罚，其中 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{w}\|_1=\sum_j |w_j|\)</span> 会优先把不重要的权重压成 0。于是 Lasso 不只是“把权重变小”，而是经常顺带完成特征选择。</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\lambda\ge 0\)</span>：正则化强度（Regularization Strength）。越大表示越强的收缩。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{w}\|_1=\sum_{j=1}^{d}|w_j|\)</span>：一范数，鼓励稀疏（Sparsity）。</li>
<li>通常不惩罚偏置 <span displaypfx="inline-" class="mathjax-container">\(b\)</span>；否则会把整体均值也一起往 0 拉。</li>
<li>Lasso 由于 <span displaypfx="inline-" class="mathjax-container">\(|\cdot|\)</span> 在 0 点不可导，一般没有像岭回归那样简洁的闭式解；常用坐标下降（Coordinate Descent）、近端梯度（Proximal Gradient）或 LARS 等算法求解。</li>
</ul>
<p>Lasso 的关键作用是让一部分权重被压到精确 0，而不是均匀缩小全部权重。它更像给参数设置了一个阈值：弱相关、贡献不足以抵消惩罚的特征，会被直接剔除。因此 Lasso 同时完成复杂度控制与特征选择（Feature Selection）。</p>
<p>几何上，在相同训练误差轮廓线下， <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 约束对应菱形/正八面体；这些尖角更容易与最优点相交在坐标轴上，因此常出现某些 <span displaypfx="inline-" class="mathjax-container">\(w_j=0\)</span> 的解。</p>
<ul>
<li>适合高维稀疏特征、希望自动筛特征的场景，例如广告、推荐、文本 one-hot 特征。</li>
<li>当特征高度相关时，Lasso 往往只保留其中少数几个，因此解的稳定性通常弱于岭回归。</li>
</ul>
<div class="blog_h3"><span class="graybg">岭回归 / L2 正则化</span></div>
<p>岭回归把参数惩罚写成二范数平方（<span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> Norm Squared）：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w},b}\ J_{\text{ridge}}(\mathbf{w},b)=\frac{1}{N}\left\|\mathbf{y}-\mathbf{X}\mathbf{w}-b\mathbf{1}\right\|_2^2+\lambda\|\mathbf{w}\|_2^2\]</span>
<p>岭回归的结构与普通线性回归相同，只是在误差项之外额外加入了 <span displaypfx="inline-" class="mathjax-container">\(\lambda\|\mathbf{w}\|_2^2\)</span>。这里 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{w}\|_2^2=\sum_j w_j^2\)</span> 会连续惩罚过大的权重，因此它更擅长缓解共线性和过拟合，而不是像 Lasso 那样主动做稀疏选择。</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\lambda\ge 0\)</span>：正则化强度。越大表示越强的收缩。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{w}\|_2^2=\sum_{j=1}^{d}w_j^2\)</span>：二范数平方，连续惩罚大权重。</li>
<li>通常不惩罚偏置 <span displaypfx="inline-" class="mathjax-container">\(b\)</span>；否则会把整体均值也一起往 0 拉。</li>
</ul>
<p>从梯度角度看，惩罚项 <span displaypfx="inline-" class="mathjax-container">\(\lambda\|\mathbf{w}\|_2^2\)</span> 对单个参数 <span displaypfx="inline-" class="mathjax-container">\(w_j\)</span> 的梯度贡献是 <span displaypfx="inline-" class="mathjax-container">\(2\lambda w_j\)</span>。这就是 <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 正则化在深度学习中被称为权重衰减（Weight Decay）的原因：它持续把权重按比例拉回 0。若取 <span displaypfx="inline-" class="mathjax-container">\(\lambda=1\)</span>，当 <span displaypfx="inline-" class="mathjax-container">\(w_j=10\)</span> 时，梯度为 <span displaypfx="inline-" class="mathjax-container">\(20\)</span>；优化器会施加强烈收缩，把这个大权重猛拉回去。当 <span displaypfx="inline-" class="mathjax-container">\(w_j=0.1\)</span> 时，梯度只有 <span displaypfx="inline-" class="mathjax-container">\(0.2\)</span>；往 0 拉的力量就很弱。也就是说，参数一旦变大， <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 惩罚立刻增强；参数已经很小时，它几乎不再干预。</p>
<p>岭回归仍是凸二次问题，并有闭式解（忽略/已中心化处理 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 的情况）：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{w}^*=\left(\mathbf{X}^\top\mathbf{X}+N\lambda\mathbf{I}\right)^{-1}\mathbf{X}^\top\mathbf{y}\]</span>
<p>与普通最小二乘相比，这里多出来的 <span displaypfx="inline-" class="mathjax-container">\(N\lambda\mathbf{I}\)</span> 是沿对角线加入的一项稳定化修正。 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{I}\)</span> 是单位矩阵，它不会改变特征之间的相对结构，但会让矩阵更容易求逆，因此在特征高度相关时更稳定。</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{I}\in\mathbb{R}^{d\times d}\)</span>：单位矩阵（Identity Matrix）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{X}^\top\mathbf{X}+N\lambda\mathbf{I}\)</span>：对角线上加了 <span displaypfx="inline-" class="mathjax-container">\(N\lambda\)</span> 的稳定项，缓解共线性导致的病态（Ill-conditioning）。</li>
</ul>
<p>用一个最小可算的例子说明 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 如何改变解。考虑无偏置的一维回归 <span displaypfx="inline-" class="mathjax-container">\(\hat y=wx\)</span>，目标为：</p>
<span displaypfx="" class="mathjax-container">\[J(w)=\sum_{i=1}^{N}(y_i-wx_i)^2+\lambda w^2\]</span>
<p>这是单变量岭回归的简化形式。前一项把所有样本的平方误差加总，后一项 <span displaypfx="inline-" class="mathjax-container">\(\lambda w^2\)</span> 惩罚过大的斜率。它清楚展示了岭回归的核心：既要求拟合数据，又限制参数不要长得太大。</p>
<p>对 <span displaypfx="inline-" class="mathjax-container">\(w\)</span> 求导并令其为 0 得：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\mathrm{d}J}{\mathrm{d}w}=-2\sum_{i=1}^{N}x_i(y_i-wx_i)+2\lambda w=0\Rightarrow w^*=\frac{\sum_{i=1}^{N}x_i y_i}{\sum_{i=1}^{N}x_i^2+\lambda}\]</span>
<p>这个结果说明岭回归解和普通最小二乘解非常接近，只是分母里多了一个 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span>。它会把估计值往 0 方向收缩： <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 越大，分母越大，得到的 <span displaypfx="inline-" class="mathjax-container">\(w^*\)</span> 就越保守。</p>
<p>可见 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 直接进入分母，把 <span displaypfx="inline-" class="mathjax-container">\(w^*\)</span> 持续拉向 0。岭回归不会像 Lasso 那样大量制造精确 0，而是把所有权重更平滑地压小，因此更像“把所有旋钮都往小一点拧”，让模型整体更保守、更稳健。</p>
<ul>
<li>适合特征高度相关、希望保留全部特征但降低方差的场景；常见于经济学、医学和一般表格数据。</li>
<li>当既希望稀疏，又希望在强相关特征间保持稳定时，Elastic Net（<span displaypfx="inline-" class="mathjax-container">\(L_1+L_2\)</span>）通常比纯 Lasso 更稳。</li>
</ul>
<div class="blog_h3"><span class="graybg">Elastic Net（L1 + L2 正则化）</span></div>
<p>Elastic Net 把 <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 惩罚合并到同一个目标中：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w},b}\ J_{\text{EN}}(\mathbf{w},b)=\frac{1}{N}\|\mathbf{y}-\mathbf{X}\mathbf{w}-b\mathbf{1}\|_2^2+\lambda_1\|\mathbf{w}\|_1+\lambda_2\|\mathbf{w}\|_2^2\]</span>
<p>它同时保留两类效应： <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 项负责产生稀疏性（Sparsity），把一部分弱特征压到 0； <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 项负责平滑收缩，在特征高度相关时提高解的稳定性。因此 Elastic Net 通常用于“既希望自动做特征选择，又不希望在强相关特征之间选得过于激进”的场景。</p>
<ul>
<li>当 <span displaypfx="inline-" class="mathjax-container">\(\lambda_2=0\)</span> 时，退化为 Lasso。</li>
<li>当 <span displaypfx="inline-" class="mathjax-container">\(\lambda_1=0\)</span> 时，退化为岭回归（Ridge Regression）。</li>
<li>当特征高度相关且维度很高时，Elastic Net 往往比纯 Lasso 更稳，也比纯岭回归更稀疏。</li>
</ul>
<div class="blog_h3"><span class="graybg">逻辑回归</span></div>
<p>逻辑回归（Logistic Regression）是二分类的标准基线：它先用线性函数产生打分，再把该打分通过 sigmoid（Logistic Function）映射到 <span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span>，从而得到条件概率模型。</p>
<div class="blog_h4"><span class="graybg">模型、概率与 logit</span></div>
<p>对二分类，令标签随机变量（Random Variable）<span displaypfx="inline-" class="mathjax-container">\(Y\in\{0,1\}\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(1\)</span> 表示正类， <span displaypfx="inline-" class="mathjax-container">\(0\)</span> 表示负类。给定输入 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 后，先定义线性打分：</p>
<span displaypfx="" class="mathjax-container">\[z=\mathbf{w}^\top\mathbf{x}+b\]</span>
<p>再定义 sigmoid 函数：</p>
<span displaypfx="" class="mathjax-container">\[\sigma(z)=\frac{1}{1+e^{-z}}\]</span>
<p>于是，在给定输入 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 的条件下，标签取正类的条件概率写成：</p>
<span displaypfx="" class="mathjax-container">\[p(Y=1\mid \mathbf{x})=\sigma(z)=\sigma(\mathbf{w}^\top\mathbf{x}+b)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\mid\)</span> 表示“在给定……条件下”；左侧 <span displaypfx="inline-" class="mathjax-container">\(Y=1\)</span> 表示事件“标签随机变量取值为正类 1”。因此这个式子表示：在输入 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 已知时，事件 <span displaypfx="inline-" class="mathjax-container">\(Y=1\)</span> 发生的概率。</p>
<p>相应地，负类概率为：</p>
<span displaypfx="" class="mathjax-container">\[p(Y=0\mid \mathbf{x})=1-\sigma(z)\]</span>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\in\mathbb{R}^d\)</span>：特征向量。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\in\mathbb{R}^d\)</span>：权重向量（Weight Vector）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(b\in\mathbb{R}\)</span>：偏置（Bias）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(z\in\mathbb{R}\)</span>：线性打分；它也是 logit（对数几率，log-odds）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\sigma(\cdot)\)</span>：把任意实数映射到 <span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span> 的非线性函数。</li>
</ul>
<p>logit 的含义来自恒等式：</p>
<span displaypfx="" class="mathjax-container">\[\log\frac{p(Y=1\mid \mathbf{x})}{1-p(Y=1\mid \mathbf{x})}=z=\mathbf{w}^\top\mathbf{x}+b\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(\frac{p(Y=1\mid \mathbf{x})}{1-p(Y=1\mid \mathbf{x})}\)</span> 称为几率（Odds），表示“正类概率与负类概率之比”；再对它取对数，就得到 logit（对数几率，log-odds）：<span displaypfx="inline-" class="mathjax-container">\(\log\frac{p}{1-p}\)</span>。因此上式的含义不是再引入一个无关的新量，而是说明：逻辑回归把<span style="background-color: #c0c0c0;">正类概率的对数几率</span>建模为输入特征的线性函数。</p>
<p>换言之，在二分类逻辑回归里， <span displaypfx="inline-" class="mathjax-container">\(z=\mathbf{w}^\top\mathbf{x}+b\)</span> 就是线性部分的原始输出值，而这个原始输出值恰好等于 logit。它本身不是概率，可以取任意实数；经过 sigmoid 之后才变成 <span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span> 内的概率。多分类情形中，softmax 之前那一组线性输出通常统称为 logits。</p>
<p>因此 <span displaypfx="inline-" class="mathjax-container">\(w_j\)</span> 可以被解释为：特征 <span displaypfx="inline-" class="mathjax-container">\(x_j\)</span> 增加一个单位，会把对数几率增加 <span displaypfx="inline-" class="mathjax-container">\(w_j\)</span>（在其他特征不变时）。这就是逻辑回归的核心可解释性。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/logistic.jpg"><img class="alignnone size-full wp-image-41171" src="https://blog.gmem.cc/wp-content/uploads/2026/03/logistic.jpg" alt="logistic" width="1216" height="874" /></a></p>
<div class="blog_h4"><span class="graybg">训练目标（NLL / Cross-Entropy）</span></div>
<p>给定训练集 <span displaypfx="inline-" class="mathjax-container">\(\{(\mathbf{x}_i,y_i)\}_{i=1}^{N}\)</span>，记 <span displaypfx="inline-" class="mathjax-container">\(p_i=\sigma(\mathbf{w}^\top\mathbf{x}_i+b)\)</span>。逻辑回归通过最大化似然（Likelihood）训练；等价地，它最小化负对数似然（Negative Log-Likelihood, NLL），也即二分类交叉熵（Binary Cross-Entropy）：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w},b}\ L(\mathbf{w},b)=-\sum_{i=1}^{N}\left(y_i\log p_i+(1-y_i)\log(1-p_i)\right)\]</span>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\log p_i\)</span>：当 <span displaypfx="inline-" class="mathjax-container">\(y_i=1\)</span> 时，鼓励 <span displaypfx="inline-" class="mathjax-container">\(p_i\)</span> 变大。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\log(1-p_i)\)</span>：当 <span displaypfx="inline-" class="mathjax-container">\(y_i=0\)</span> 时，鼓励 <span displaypfx="inline-" class="mathjax-container">\(p_i\)</span> 变小。</li>
</ul>
<p>加入 <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 正则化时，常见形式为 <span displaypfx="inline-" class="mathjax-container">\(L(\mathbf{w},b)+\lambda\|\mathbf{w}\|_2^2\)</span>（通常不惩罚 <span displaypfx="inline-" class="mathjax-container">\(b\)</span>）。该目标是凸的，因此不存在“坏局部最优”的训练不稳定问题。</p>
<div class="blog_h4"><span class="graybg">梯度：公式如何驱动参数更新</span></div>
<p>把样本堆叠成矩阵 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{X}\)</span>、标签向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span>，预测概率向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{p}\)</span>（第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个分量为 <span displaypfx="inline-" class="mathjax-container">\(p_i\)</span>）。则无正则项时梯度为：</p>
<span displaypfx="" class="mathjax-container">\[\nabla_{\mathbf{w}}L=\mathbf{X}^\top(\mathbf{p}-\mathbf{y}),\quad \frac{\partial L}{\partial b}=\mathbf{1}^\top(\mathbf{p}-\mathbf{y})\]</span>
<p>这两个梯度式子都围绕同一个误差向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{p}-\mathbf{y}\)</span> 展开：它表示“模型给出的正类概率”和“真实标签”之间的偏差。 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{X}^\top(\mathbf{p}-\mathbf{y})\)</span> 表示把这种偏差按特征方向汇总起来，从而告诉每个权重该往哪个方向改； <span displaypfx="inline-" class="mathjax-container">\(\mathbf{1}^\top(\mathbf{p}-\mathbf{y})\)</span> 则把所有偏差直接相加，用来更新偏置。</p>
<p>这两个式子揭示了训练机制：如果某个样本真实标签是 1 但模型给出小概率（<span displaypfx="inline-" class="mathjax-container">\(p_i-y_i&lt;0\)</span>），则梯度会推动 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 朝着增加该样本 logit 的方向更新；反之亦然。</p>
<div class="blog_h4"><span class="graybg">实例：单样本算概率、算损失、算一次梯度</span></div>
<p>考虑一维特征 <span displaypfx="inline-" class="mathjax-container">\(x=2\)</span>，参数 <span displaypfx="inline-" class="mathjax-container">\(w=1,b=-1\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(z=wx+b=1\)</span>，概率：</p>
<span displaypfx="" class="mathjax-container">\[p=\sigma(1)=\frac{1}{1+e^{-1}}\approx 0.731\]</span>
<p>若真实标签 <span displaypfx="inline-" class="mathjax-container">\(y=1\)</span>，该样本的负对数似然为：</p>
<span displaypfx="" class="mathjax-container">\[\ell=-\log p\approx 0.313\]</span>
<p>该样本对 <span displaypfx="inline-" class="mathjax-container">\(w\)</span> 的梯度为（单样本形式）：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial \ell}{\partial w}=(p-y)x=(0.731-1)\cdot 2\approx -0.538\]</span>
<p>用学习率 <span displaypfx="inline-" class="mathjax-container">\(\eta\)</span> 做一次梯度下降： <span displaypfx="inline-" class="mathjax-container">\(w\leftarrow w-\eta\frac{\partial\ell}{\partial w}\)</span>。由于梯度为负，更新会把 <span displaypfx="inline-" class="mathjax-container">\(w\)</span> 增大，从而增大 <span displaypfx="inline-" class="mathjax-container">\(z\)</span>、提升 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>，使模型更倾向把该样本判为正类。</p>
<div class="blog_h4"><span class="graybg">适用场景</span></div>
<ul>
<li>二分类且需要概率输出/可校准阈值（例如欺诈检测、流失预测、医学风险评分）。</li>
<li>高维稀疏特征（如 one-hot、文本 bag-of-words），逻辑回归常是强基线。</li>
<li>对可解释性、训练稳定性要求高的工程场景（可用权重做审计/特征诊断）。</li>
<li>当决策边界高度非线性且特征工程不足时，需要树模型或神经网络补上非线性。</li>
</ul>
<div class="blog_h3"><span class="graybg">支持向量机（SVM）</span></div>
<p>支持向量机（Support Vector Machine, SVM）是一类以<span style="background-color: #c0c0c0;">最大间隔分类</span>为核心原则的判别模型。对线性可分的二分类问题，它要寻找一个超平面（Hyperplane），使两类样本被正确分开，同时边界到两侧样本的最小距离尽可能大。这个最小距离对应的缓冲区称为间隔（Margin）。</p>
<p>也正因为优化目标是最大间隔，SVM 并不是“随便找一条能分开两类样本的直线/超平面”，而是要找那条对两类样本都留出最大安全缓冲区的边界。边界离两类样本都越远，模型对噪声、标注扰动与局部数据波动通常越稳。</p>
<p>从数学形式看，SVM 最终会落到二次规划（Quadratic Programming, QP）。所谓二次规划，就是：<span style="background-color: #c0c0c0;">目标函数是变量的二次函数，约束是线性等式或线性不等式</span>。SVM 的目标是最小化 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{2}\|\mathbf{w}\|_2^2\)</span>，约束则是每个样本都必须被放到正确一侧并留出至少 1 的函数间隔，因此它正好属于这一类优化问题。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/svm.jpg"><img class="alignnone size-full wp-image-41179" src="https://blog.gmem.cc/wp-content/uploads/2026/03/svm.jpg" alt="svm" width="1408" height="768" /></a></p>
<p>从结构上看，SVM 把三件事连在一起：</p>
<ol>
<li>几何：先定义“什么叫分得开”，以及“什么叫分得最稳”。</li>
<li>优化：把“最稳的边界”写成一个可求解的凸二次规划。</li>
<li>对偶：把问题改写成只依赖样本内积的形式，从而自然导出核技巧（Kernel Trick）。</li>
</ol>
<div class="blog_h4"><span class="graybg">从几何直觉到“最大间隔”</span></div>
<p>对二分类任务，设标签 <span displaypfx="inline-" class="mathjax-container">\(y_i\in\{-1,+1\}\)</span>。线性分类器先计算一个打分函数（Score Function）：</p>
<span displaypfx="" class="mathjax-container">\[f(\mathbf{x})=\mathbf{w}^\top\mathbf{x}+b\]</span>
<p>这里最好先把“函数写法”和“几何边界写法”区分清楚。像 <span displaypfx="inline-" class="mathjax-container">\(y=x\)</span> 这样的形式，强调的是“给定自变量（Independent Variable） <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，应变量（Dependent Variable） <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 如何变化”；但对分类器来说，更关键的问题不是“谁依赖谁”，而是<span style="background-color: #c0c0c0;">哪些点恰好落在边界上，以及点位于边界哪一侧</span>。因此同一条直线在几何里通常改写成隐式形式（Implicit Form） <span displaypfx="inline-" class="mathjax-container">\(x-y=0\)</span>。</p>
<p>一旦写成 <span displaypfx="inline-" class="mathjax-container">\(x-y=0\)</span>，就能直接看成二维超平面方程 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}+b=0\)</span>：令 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(x,y)^\top\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}=(1,-1)^\top\)</span>、<span displaypfx="inline-" class="mathjax-container">\(b=0\)</span>，便有 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}+b=x-y\)</span>。此时直线方向向量（Direction Vector）可以取 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}=(1,1)^\top\)</span>，因为沿这条线移动时 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 同时增加；而 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{v}=1\cdot 1+(-1)\cdot 1=0\)</span>，说明 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 与直线切向方向正交，所以它正是这条直线的法向量（Normal Vector）。更一般地，对任意边界 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}+b=0\)</span>，系数向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 都垂直于边界，因此天然决定“正侧、负侧”和距离的度量方向。</p>
<p>再用它的符号做判别：</p>
<span displaypfx="" class="mathjax-container">\[\hat y=\mathrm{sign}(f(\mathbf{x}))=\mathrm{sign}(\mathbf{w}^\top\mathbf{x}+b)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 是超平面的法向量（Normal Vector）。沿 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 方向移动， <span displaypfx="inline-" class="mathjax-container">\(f(\mathbf{x})\)</span> 会增大；沿反方向移动， <span displaypfx="inline-" class="mathjax-container">\(f(\mathbf{x})\)</span> 会减小。因此：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(f(\mathbf{x})=0\)</span>：点就在分类超平面上。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(f(\mathbf{x})&gt;0\)</span>：点落在法向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 指向的那一侧。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(f(\mathbf{x})&lt;0\)</span>：点落在另一侧。</li>
</ul>
<p>超平面把空间分成正半空间（Positive Half-space）与负半空间（Negative Half-space）；分数的正负号直接给出样本位于哪一侧。</p>
<p>令 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{H}=\{\mathbf{x}:\mathbf{w}^\top\mathbf{x}+b=0\}\)</span> 表示超平面，令单位法向量（Unit Normal Vector）为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{n}=\mathbf{w}/\|\mathbf{w}\|_2\)</span>。对样本 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_i\)</span>，记 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_{\Pi,i}\)</span> 为它在超平面 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{H}\)</span> 上的正交投影点（Orthogonal Projection），因此 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}_{\Pi,i}+b=0\)</span>，且 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_i-\mathbf{x}_{\Pi,i}\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 平行。点 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_i\)</span> 到超平面的带符号距离（Signed Distance）定义为该位移在单位法向量方向上的投影长度：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{dist}_{\pm}(\mathbf{x}_i,\mathcal{H})=\mathbf{n}^\top(\mathbf{x}_i-\mathbf{x}_{\Pi,i})=\frac{\mathbf{w}^\top\mathbf{x}_i+b}{\|\mathbf{w}\|_2}\]</span>
<p>分子 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}_i+b\)</span> 衡量点在法向量方向上偏离超平面的代数量；分母 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{w}\|_2\)</span> 对法向量做归一化，去掉“同一个超平面可写成不同倍数方程”的尺度影响；符号保留样本位于超平面哪一侧的信息。</p>
<p>带符号距离区分了超平面的两侧，但正类样本与负类样本的正确侧相反。将标签 <span displaypfx="inline-" class="mathjax-container">\(y_i\in\{-1,+1\}\)</span> 乘入后，得到几何间隔（Geometric Margin）：</p>
<span displaypfx="" class="mathjax-container">\[\gamma_i=\frac{y_i(\mathbf{w}^\top\mathbf{x}_i+b)}{\|\mathbf{w}\|_2}\]</span>
<p>几何间隔是<span style="background-color: #c0c0c0;">用标签修正后的带符号距离</span>。因此：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\gamma_i&gt;0\)</span>：样本在正确一侧，被正确分类。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\gamma_i=0\)</span>：样本正好压在边界上。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\gamma_i&lt;0\)</span>：样本落到错误一侧，被误分类。</li>
</ul>
<p>SVM 的目标是让所有训练样本中最小的那个 <span displaypfx="inline-" class="mathjax-container">\(\gamma_i\)</span> 尽可能大，即最大化“最坏样本到边界的正确方向距离”：</p>
<span displaypfx="" class="mathjax-container">\[\gamma=\min_i \gamma_i\]</span>
<p>这就是最大间隔（Maximum Margin）的含义：不是让“平均样本”离边界远，而是让最危险、最靠近边界的样本也尽量安全。</p>
<div class="blog_h4"><span class="graybg">硬间隔 SVM（Hard-margin SVM）原始问题</span></div>
<p>先看线性可分（Linearly Separable）的情形。所谓线性可分，就是存在某个 <span displaypfx="inline-" class="mathjax-container">\((\mathbf{w},b)\)</span>，使每个样本都在与自己标签一致的一侧。这件事可以统一写成：</p>
<span displaypfx="" class="mathjax-container">\[y_i(\mathbf{w}^\top\mathbf{x}_i+b)&gt;0,\quad \forall i\]</span>
<p>为什么这里是 <span displaypfx="inline-" class="mathjax-container">\(&gt;0\)</span>？因为它等价于“符号一致”：当 <span displaypfx="inline-" class="mathjax-container">\(y_i=+1\)</span> 时，要求 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}_i+b&gt;0\)</span>，即正类样本必须落在正半空间；当 <span displaypfx="inline-" class="mathjax-container">\(y_i=-1\)</span> 时，要求 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}_i+b&lt;0\)</span>，即负类样本必须落在负半空间。若只等于 0，则样本恰好压在分类边界上，不属于严格可分，因为此时它没有任何安全间隔，符号判别也处在临界点。</p>
<p>不过， <span displaypfx="inline-" class="mathjax-container">\(y_i(\mathbf{w}^\top\mathbf{x}_i+b)\)</span> 还不是几何距离，因为把 <span displaypfx="inline-" class="mathjax-container">\((\mathbf{w},b)\)</span> 同时乘以任意正常数 <span displaypfx="inline-" class="mathjax-container">\(t&gt;0\)</span>，超平面 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}+b=0\)</span> 完全不变，但这个量会整体乘上 <span displaypfx="inline-" class="mathjax-container">\(t\)</span>。因此它只是一个未归一化的间隔量，通常称为函数间隔（Functional Margin）。</p>
<p>为了消掉这个缩放自由度，SVM 采用一个标准定标：强制所有样本的最小函数间隔等于 1，也就是要求</p>
<span displaypfx="" class="mathjax-container">\[y_i(\mathbf{w}^\top\mathbf{x}_i+b)\ge 1,\quad \forall i\]</span>
<p>这条约束把两类样本同时编码进来：当 <span displaypfx="inline-" class="mathjax-container">\(y_i=+1\)</span> 时，它变成 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}_i+b\ge 1\)</span>；当 <span displaypfx="inline-" class="mathjax-container">\(y_i=-1\)</span> 时，它变成 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}_i+b\le -1\)</span>。于是分类边界两侧又出现两条平行的“间隔边界”（Margin Boundaries）：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{w}^\top\mathbf{x}+b=+1,\quad \mathbf{w}^\top\mathbf{x}+b=-1\]</span>
<p>因为平行超平面 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}+b=c_1\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}+b=c_2\)</span> 之间的距离是 <span displaypfx="inline-" class="mathjax-container">\(|c_1-c_2|/\|\mathbf{w}\|_2\)</span>，所以这两条间隔边界之间的宽度为</p>
<span displaypfx="" class="mathjax-container">\[\frac{|(+1)-(-1)|}{\|\mathbf{w}\|_2}=\frac{2}{\|\mathbf{w}\|_2}\]</span>
<p>在这个定标下，离分类边界最近的样本几何间隔恰好是</p>
<span displaypfx="" class="mathjax-container">\[\gamma=\min_i \frac{y_i(\mathbf{w}^\top\mathbf{x}_i+b)}{\|\mathbf{w}\|_2}=\frac{1}{\|\mathbf{w}\|_2}\]</span>
<p>因此，最大化几何间隔就等价于最小化 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{w}\|_2\)</span>；为了得到标准的凸二次目标，通常写成：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w},b}\ \frac{1}{2}\|\mathbf{w}\|_2^2\quad \text{s.t.}\quad y_i(\mathbf{w}^\top \mathbf{x}_i+b)\ge 1\]</span>
<p>这就是硬间隔 SVM 的原始问题（Primal Problem）。现在“二次规划”这个术语也具体了：目标函数 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{2}\|\mathbf{w}\|_2^2\)</span> 是关于参数的凸二次函数，而约束 <span displaypfx="inline-" class="mathjax-container">\(y_i(\mathbf{w}^\top \mathbf{x}_i+b)\ge 1\)</span> 对 <span displaypfx="inline-" class="mathjax-container">\((\mathbf{w},b)\)</span> 是线性的，所以这是一个凸二次规划，并且可以求到全局最优解。</p>
<div class="blog_h4"><span class="graybg">拉格朗日函数与 KKT：为什么只剩少数“支持向量”</span></div>
<p>这一段只回答一个问题：训练集中明明有很多样本，为什么最后真正决定分类边界的，往往只有少数几个点。这个结论的严格来源是 KKT 条件，但它的直观含义并不抽象：<span style="background-color: #c0c0c0;">只有那些真正把最大间隔边界“卡住”的样本，才会在最优解里留下非零权重</span>。</p>
<p>硬间隔 SVM 的原始问题是：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\boldsymbol{w},b}\frac{1}{2}\|\boldsymbol{w}\|_2^2\quad \text{s.t.}\quad y_i(\boldsymbol{w}^\top \boldsymbol{x}_i+b)\ge 1,\ \forall i\]</span>
<p>这里目标函数 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{2}\|\boldsymbol{w}\|_2^2\)</span> 想把边界做得尽量“简单”，也就是让法向量尽量短，从而把间隔做大；而每个训练样本都在提出自己的硬约束：它不仅要被分对，还必须距离边界至少有 1 个单位的函数间隔。把这两股力量写到同一个式子里，就得到拉格朗日函数（Lagrangian）：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}(\boldsymbol{w},b,\boldsymbol{\alpha})=\frac{1}{2}\|\boldsymbol{w}\|_2^2-\sum_{i=1}^{N}\alpha_i\big(y_i(\boldsymbol{w}^\top \boldsymbol{x}_i+b)-1\big),\quad \alpha_i\ge 0\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个样本对应的拉格朗日乘子（Lagrange Multiplier）。在 SVM 里，可以把它直接理解为“第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个样本对当前分类边界施加了多大压力”。压力越大，说明这个样本越在真正影响边界的位置；压力为 0，说明这个样本虽然在训练集里，但最优边界并不需要它来支撑。</p>
<p>这个问题的读法可以想成一个“边界往外推、样本往回顶”的平衡过程：</p>
<ul>
<li><span style="background-color: #c0c0c0;">模型一侧</span> 想让 <span displaypfx="inline-" class="mathjax-container">\(\|\boldsymbol{w}\|_2\)</span> 尽量小，从而把间隔尽量做大。</li>
<li><span style="background-color: #c0c0c0;">约束一侧</span> 则由每个样本通过 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span> 施加压力：谁越接近边界、越可能破坏间隔，谁就越值得保留权重。</li>
</ul>
<p>于是，离边界很远的样本会发生什么，就变得非常直观。若某个样本满足</p>
<span displaypfx="" class="mathjax-container">\[y_i(\boldsymbol{w}^\top\boldsymbol{x}_i+b)&gt;1\]</span>
<p>说明它不仅分对了，而且还有额外安全余量。这个点对“边界能否继续外推”没有形成真正阻碍，因此最优时它对应的 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span> 会被压到 0。相反，若某个样本恰好满足</p>
<span displaypfx="" class="mathjax-container">\[y_i(\boldsymbol{w}^\top\boldsymbol{x}_i+b)=1\]</span>
<p>它就正贴在间隔边界上，是“再往外推一点就会出问题”的临界点。这样的样本才有资格在最优解中保留非零权重。</p>
<p>KKT 条件（Karush–Kuhn–Tucker Conditions）把这个直觉写成严格公式。第一条来自驻点条件（Stationarity）：</p>
<span displaypfx="" class="mathjax-container">\[\nabla_{\boldsymbol{w}}\mathcal{L}=0\Rightarrow \boldsymbol{w}=\sum_{i=1}^{N}\alpha_i y_i \boldsymbol{x}_i\]</span>
<p>这条式子的含义非常重要。它说明最终的法向量 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{w}\)</span> 并不是由全部样本平均决定的，而是由训练样本的加权和决定的。这里：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}_i\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个训练样本；</li>
<li><span displaypfx="inline-" class="mathjax-container">\(y_i\alpha_i\)</span> 是它的“带符号权重”；</li>
<li>若 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i=0\)</span>，该样本就完全不会出现在 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{w}\)</span> 的表达式里。</li>
</ul>
<p>第二条关键条件是互补松弛（Complementary Slackness）：</p>
<span displaypfx="" class="mathjax-container">\[\alpha_i\big(y_i(\boldsymbol{w}^\top \boldsymbol{x}_i+b)-1\big)=0\]</span>
<p>这条式子可以直接逐项阅读：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个样本对边界施加的压力；</li>
<li><span displaypfx="inline-" class="mathjax-container">\(y_i(\boldsymbol{w}^\top \boldsymbol{x}_i+b)-1\)</span> 是该样本相对于间隔边界的“松弛量”；当它大于 0 时，说明样本在安全区里；当它等于 0 时，说明样本正好贴边。</li>
</ul>
<p>由于这两个量的乘积必须等于 0，所以只可能出现两种情况：</p>
<ul>
<li>若 <span displaypfx="inline-" class="mathjax-container">\(y_i(\boldsymbol{w}^\top \boldsymbol{x}_i+b)-1&gt;0\)</span>，说明该样本有安全余量，那么必须有 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i=0\)</span>。</li>
<li>若 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i&gt;0\)</span>，说明该样本仍在对边界施压，那么必须有 <span displaypfx="inline-" class="mathjax-container">\(y_i(\boldsymbol{w}^\top \boldsymbol{x}_i+b)=1\)</span>。</li>
</ul>
<p>这就是支持向量（Support Vector）的严格定义来源：<span style="background-color: #c0c0c0;">在硬间隔 SVM 中，只有恰好贴在间隔边界上的样本，才可能对应非零 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span>；这些样本共同决定最终超平面的位置</span>。其余样本虽然被正确分类，但因为离边界还有余量，所以在最优解里不再起作用。</p>
<p>如果继续沿着这个结论往下看，就会发现 SVM 的稀疏性完全不是偶然现象，而是 KKT 的直接产物。训练集里可以有大量“安全样本”，但最优边界只需要被少数临界样本支撑起来。名字“支持向量”说的正是这件事：这些点不是普通数据点，而是真正在几何上把边界撑住的点。</p>
<div class="blog_h4"><span class="graybg">对偶问题（Dual）：把“求边界”改写成“给样本分权重”</span></div>
<p>前面已经看到：KKT 让 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span> 变成了“谁在真正顶住边界”的刻度。但如果这里只停在 KKT，还是会留下一个疑问：<span style="background-color: #c0c0c0;">对偶问题到底是怎么从原始问题里长出来的</span>？关键动作只有一步：先把 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span> 固定住，把拉格朗日函数当成关于 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},b\)</span> 的函数来最小化；然后再回过头，只对 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 做最大化。</p>
<p>也就是说，原始问题里我们直接求“哪条边界最好”：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w},b}\ \frac{1}{2}\|\mathbf{w}\|_2^2\quad \text{s.t.}\quad y_i(\mathbf{w}^\top\mathbf{x}_i+b)\ge 1\]</span>
<p>而引入拉格朗日乘子之后，可以先看下面这个函数：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}(\mathbf{w},b,\alpha)=\frac{1}{2}\|\mathbf{w}\|_2^2-\sum_{i=1}^{N}\alpha_i\Big(y_i(\mathbf{w}^\top \mathbf{x}_i+b)-1\Big),\quad \alpha_i\ge 0\]</span>
<p>对固定的 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span>，它就是一个关于 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},b\)</span> 的凸函数。于是我们先做内层最小化：</p>
<span displaypfx="" class="mathjax-container">\[g(\alpha)=\min_{\mathbf{w},b}\ \mathcal{L}(\mathbf{w},b,\alpha)\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(g(\alpha)\)</span> 就叫对偶函数（Dual Function）。它表示：<span style="background-color: #c0c0c0;">假设每个样本的施压强度已经给定，边界那一侧最好的回应会是什么</span>。</p>
<p>这一步的好处是， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},b\)</span> 可以被显式消掉。对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 求驻点条件，有：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial \mathcal{L}}{\partial \mathbf{w}}=0\Rightarrow \mathbf{w}=\sum_{i=1}^{N}\alpha_i y_i\mathbf{x}_i\]</span>
<span displaypfx="" class="mathjax-container">\[\frac{\partial \mathcal{L}}{\partial b}=0\Rightarrow \sum_{i=1}^{N}\alpha_i y_i=0\]</span>
<p>这两条式子非常关键：第一条说明法向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 完全由训练样本线性组合出来；第二条说明正负两类样本的“总施压”必须平衡。把它们代回去，原来依赖 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},b\)</span> 的问题就变成只依赖 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 的问题：</p>
<span displaypfx="" class="mathjax-container">\[\max_{\alpha}\ \sum_{i=1}^{N}\alpha_i-\frac{1}{2}\sum_{i=1}^{N}\sum_{j=1}^{N}\alpha_i\alpha_j y_i y_j\,\mathbf{x}_i^\top\mathbf{x}_j\]</span>
<span displaypfx="" class="mathjax-container">\[\text{s.t.}\quad \alpha_i\ge 0,\quad \sum_{i=1}^{N}\alpha_i y_i=0\]</span>
<p>这就是 SVM 的对偶问题（Dual Problem）。现在可以看出它为什么叫“对偶”：它没有再直接问“<span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},b\)</span> 应该是多少”，而是改问“每个样本应该分到多大的权重 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span>，才能共同把最优边界顶出来”。</p>
<p>这样改写有两个直接收益。第一，支持向量为什么稀疏会变得一眼可见：若某个样本最终 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i^*=0\)</span>，它就自动从 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}=\sum_i \alpha_i y_i\mathbf{x}_i\)</span> 里消失。第二，对偶目标里出现的样本方式只剩内积 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_i^\top\mathbf{x}_j\)</span>，这正是后面引入核函数（Kernel）的入口。</p>
<p>把最优权重 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i^*\)</span> 求出来后，分类器可以直接写成：</p>
<span displaypfx="" class="mathjax-container">\[f(\mathbf{x})=\mathrm{sign}\left(\sum_{i=1}^{N}\alpha_i^* y_i\,\mathbf{x}_i^\top \mathbf{x}+b^*\right)\]</span>
<p>这个式子的含义很具体：新样本 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 会与训练样本做内积（Inner Product），也就是计算相似度；每个训练样本按自己的类别符号 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 和影响系数 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i^*\)</span> 投一票；最后把这些票加总，再加上偏置 <span displaypfx="inline-" class="mathjax-container">\(b^*\)</span>，看结果落在哪一侧。</p>
<p>由于绝大多数样本的 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i^*=0\)</span>，真正参与这次投票的通常只有支持向量。因此 SVM 的预测阶段常带有一个很强的稀疏性（Sparsity）：<span style="background-color: #c0c0c0;">不是所有训练样本都在持续发声，真正起作用的只是边界附近那一小部分点</span>。</p>
<p>偏置 <span displaypfx="inline-" class="mathjax-container">\(b^*\)</span> 可以用任一支持向量恢复。若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_k\)</span> 是一个支持向量，则它满足 <span displaypfx="inline-" class="mathjax-container">\(y_k(\mathbf{w}^{*\top}\mathbf{x}_k+b^*)=1\)</span>，因此</p>
<span displaypfx="" class="mathjax-container">\[b^*=y_k-\mathbf{w}^{*\top}\mathbf{x}_k\]</span>
<div class="blog_h4"><span class="graybg">核函数（Kernel）：把“线性边界”搬到更合适的空间</span></div>
<p>对偶问题真正重要的地方，不只是“把变量从 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},b\)</span> 换成了 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span>”，而是它把 SVM 的全部数据依赖压缩成了样本之间的内积（Inner Product）：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}_i^\top\mathbf{x}_j\]</span>
<p>这一步非常关键，因为它说明：<span style="background-color: #c0c0c0;">SVM 在对偶形式里并不需要直接看到样本坐标本身，它只需要知道样本彼此有多相似</span>。只要这种“相似度”还能写成某个空间里的内积，SVM 的训练与预测公式就都可以照搬。</p>
<p>于是核技巧（Kernel Trick）的引入就变得自然了。设有一个特征映射（Feature Map）<span displaypfx="inline-" class="mathjax-container">\(\phi(\mathbf{x})\)</span>，它把原始样本送到更高维、甚至无限维的特征空间（Feature Space）。如果我们真的显式去算这个高维向量，代价往往很大；但对偶形式只关心内积，因此只要能直接计算</p>
<span displaypfx="" class="mathjax-container">\[K(\mathbf{x}_i,\mathbf{x}_j)=\phi(\mathbf{x}_i)^\top\phi(\mathbf{x}_j)\]</span>
<p>就等价于“隐式地”在特征空间里做线性 SVM，而不必真的把 <span displaypfx="inline-" class="mathjax-container">\(\phi(\mathbf{x})\)</span> 写出来。这个直接在原空间里计算特征空间内积的技巧，就叫核函数（Kernel Function）或核技巧（Kernel Trick）。</p>
<p>这也解释了为什么 kernel 是从 dual 里长出来的，而不是额外拼上去的：若你还停留在原始问题里，眼前看到的仍是显式参数 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span>；而一旦进入对偶形式，表达式里只剩 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_i^\top\mathbf{x}_j\)</span>，把它替换成别的“合法相似度”就成了最自然的一步。</p>
<p>于是决策函数从</p>
<span displaypfx="" class="mathjax-container">\[f(\mathbf{x})=\mathrm{sign}\left(\sum_{i=1}^{N}\alpha_i^* y_i\,\mathbf{x}_i^\top \mathbf{x}+b^*\right)\]</span>
<p>变成</p>
<span displaypfx="" class="mathjax-container">\[f(\mathbf{x})=\mathrm{sign}\left(\sum_{i=1}^{N}\alpha_i^* y_i\,K(\mathbf{x}_i,\mathbf{x})+b^*\right)\]</span>
<p>要点是：<span style="background-color: #c0c0c0;">特征空间里仍然是线性超平面；只是映回原空间后，边界看起来变成了弯的</span>。因此 kernel 不是“把线性 SVM 换成非线性模型”，而是“先把数据换到更容易线性可分的表示里，再继续做线性 SVM”。</p>
<p>从直觉上看，核函数本质上是在重新定义“两个样本像不像”。线性核（Linear Kernel）比较原始方向是否一致；多项式核（Polynomial Kernel）强调特征之间的组合关系；RBF / Gaussian 核更强调局部邻近性，因此很容易形成局部、弯曲的决策边界。</p>
<ul>
<li>线性核（Linear Kernel）：<span displaypfx="inline-" class="mathjax-container">\(K(\mathbf{x},\mathbf{z})=\mathbf{x}^\top\mathbf{z}\)</span>。</li>
<li>多项式核（Polynomial Kernel）：<span displaypfx="inline-" class="mathjax-container">\(K(\mathbf{x},\mathbf{z})=(\mathbf{x}^\top\mathbf{z}+c)^d\)</span>。</li>
<li>RBF / Gaussian 核：<span displaypfx="inline-" class="mathjax-container">\(K(\mathbf{x},\mathbf{z})=\exp(-\gamma\|\mathbf{x}-\mathbf{z}\|_2^2)\)</span>。</li>
</ul>
<p>一个典型例子是“同心圆”二分类：在二维平面里，内圈和外圈无法用一条直线分开；但如果映射到包含半径平方等特征的空间，类别就可能被一个超平面分开。核方法的价值就在这里：<span style="background-color: #c0c0c0;">原空间里看到的是弯曲边界，特征空间里做的仍然是线性分类</span>。</p>
<div class="blog_h4"><span class="graybg">软间隔 SVM（Soft-margin SVM）：允许少量违约，但要付代价</span></div>
<p>现实数据通常含噪声、离群点（Outlier）或类别重叠。若仍然要求“所有点都必须在间隔之外”，硬间隔 SVM 很可能根本无解，或者被少数异常点强行拉歪。软间隔 SVM（Soft-margin SVM）因此引入松弛变量（Slack Variable）<span displaypfx="inline-" class="mathjax-container">\(\xi_i\ge 0\)</span>，允许个别样本违反间隔约束：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w},b,\xi}\ \frac{1}{2}\|\mathbf{w}\|_2^2+C\sum_{i=1}^{N}\xi_i\quad \text{s.t.}\quad y_i(\mathbf{w}^\top \mathbf{x}_i+b)\ge 1-\xi_i,\ \xi_i\ge 0\]</span>
<p>这个式子只表达一件事：边界仍然希望尽量大，但违约要交罚款，罚款强度由 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 决定。对单个样本， <span displaypfx="inline-" class="mathjax-container">\(\xi_i\)</span> 的含义可以直接按大小来读：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\xi_i=0\)</span>：样本分类正确，且在间隔边界上或之外。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(0&lt;\xi_i\le 1\)</span>：样本仍在正确一侧，但已经挤进了间隔内部。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\xi_i&gt;1\)</span>：样本跨过了分类边界，已经被误分。</li>
</ul>
<p><span displaypfx="inline-" class="mathjax-container">\(C\)</span> 控制的是“对违约有多敏感”：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(C\)</span> 大：更重视把训练集分对，边界更硬，对噪声也更敏感。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(C\)</span> 小：更重视大间隔和整体稳定性，允许少量训练误差。</li>
</ul>
<p>软间隔下，支持向量的范围也更宽：不仅贴着间隔边界的点重要，落在间隔内部甚至被误分的点也会直接影响最优解。对应到对偶变量，常见情形是 <span displaypfx="inline-" class="mathjax-container">\(0&lt;\alpha_i&lt;C\)</span> 的点贴在间隔上，而 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i=C\)</span> 的点往往是间隔内样本或误分类样本。</p>
<p>把松弛变量消去后，软间隔 SVM 还可以写成更常见的合页损失（Hinge Loss）形式：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w},b}\ \frac{1}{2}\|\mathbf{w}\|_2^2+C\sum_{i=1}^{N}\max\big(0,\ 1-y_i(\mathbf{w}^\top \mathbf{x}_i+b)\big)\]</span>
<p>第一项限制模型复杂度，第二项惩罚分类违约。SVM 因此可以被看作“<span style="background-color: #c0c0c0;">大间隔 + 违约惩罚</span>”的组合，而支持向量则是这两股力量平衡后仍然留在最前线的样本。</p>
<div class="blog_h2"><span class="graybg">树模型与集成方法</span></div>
<p>这一类方法处理的核心问题是：当输入与输出之间存在明显非线性、阈值效应与高阶特征交互时，线性模型往往表达力不足，但工程上仍然希望模型具备较强可解释性、对表格数据友好、并且训练稳定。树模型通过递归切分（Recursive Partitioning）把输入空间划成若干局部区域；集成方法则进一步通过 Bagging 或 Boosting 提升泛化能力与精度。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">模型数量与训练逻辑</td>
<td style="text-align: center;">集成策略 / 学习机制</td>
<td style="text-align: center;">单棵树特点</td>
<td style="text-align: center;">主要在优化什么</td>
<td style="text-align: center;">过拟合风险</td>
<td style="text-align: center;">更适合什么场景</td>
</tr>
</thead>
<tbody>
<tr>
<td>决策树</td>
<td>单棵独立模型，直接在原始数据上递归切分，没有集成过程</td>
<td>无；直接学习输入到输出的分段规则</td>
<td>树深完全由数据与约束参数决定；过深会记住噪声，过浅又会表达不足</td>
<td>没有显式“降偏差 / 降方差”分工，偏差和方差都要靠剪枝、树深和叶子约束一起控制</td>
<td>高；单树很容易把训练集中的偶然模式学进去</td>
<td>可解释性要求极高的简单任务、规则基线、集成模型的基学习器</td>
</tr>
<tr>
<td>随机森林</td>
<td>多棵树并行独立训练，树与树之间无依赖，可利用多核 CPU 并行</td>
<td>Bagging：bootstrap 采样 + 特征子采样 + 投票 / 平均</td>
<td>单棵树通常故意训练得较深，先把单树偏差压低，再依靠集成抵消高方差</td>
<td>主要降低方差，让整体模型更稳、更抗数据扰动</td>
<td>低；Bagging 和平均机制天然有正则化效果</td>
<td>中小规模表格数据、强 baseline、噪声较大且希望训练稳定的场景</td>
</tr>
<tr>
<td>GBDT / XGBoost</td>
<td>多棵树严格串行训练；后一棵树必须等前一棵树完成后，才能开始学习新的修正项</td>
<td>Boosting：拟合残差或负梯度，逐轮加权累加</td>
<td>单棵树通常较浅，只负责局部纠错；单树偏差高，但方差相对更低</td>
<td>主要降低偏差，通过接力纠错不断提升拟合能力</td>
<td>中等；若树数太多、学习率太大或树过深，容易持续把训练集吃满</td>
<td>结构化数据里追求高精度的任务，如风控、推荐、广告、竞赛</td>
</tr>
<tr>
<td>LightGBM</td>
<td>仍然属于串行 Boosting，但单棵树训练做了更激进的近似和工程优化</td>
<td>Boosting + 直方图分桶 + leaf-wise 生长 + GOSS / EFB</td>
<td>单棵树依然是浅到中等深度的纠错器，但 leaf-wise 会把最值得细分的局部继续挖深</td>
<td>继续降低偏差，同时极力优化训练速度与内存效率</td>
<td>中等偏高；若 leaf-wise 缺少足够约束，局部树会很快长深</td>
<td>大规模表格数据、高维稀疏特征、需要频繁重训的工业流水线</td>
</tr>
<tr>
<td>CatBoost</td>
<td>仍然属于串行 Boosting，但重点放在类别特征处理与训练偏移控制</td>
<td>Boosting + ordered target statistics + ordered boosting + symmetric tree</td>
<td>单棵树结构更规整，对称树推理快；模型内部原生吸收类别特征统计信息</td>
<td>继续降低偏差，同时尽量减少类别编码带来的泄露与偏移</td>
<td>中等；总体可控，但仍需学习率、树数和正则化配合</td>
<td>类别特征很多、希望少做手工编码、快速起一个强模型的表格任务</td>
</tr>
</tbody>
</table>
<p>这张表背后的逻辑可以压缩成一句话：<span style="background-color: #c0c0c0;">决策树是基础，随机森林主要靠并行平均降低方差，GBDT 家族主要靠串行纠错降低偏差，而 XGBoost、LightGBM、CatBoost 则是在 GBDT 主线上的工业级强化</span>。因此它们虽然都属于“树模型”，训练逻辑、误差控制方式和工程侧优势并不相同。</p>
<div class="blog_h3"><span class="graybg">决策树</span></div>
<p>决策树（Decision Tree）把预测过程写成一串逐层切分的规则：内部节点负责提问，边负责根据答案分流，叶节点负责输出最终结果。它的优势不在于公式复杂，而在于模型结构与业务规则天然同构：每一条从根到叶的路径，都对应一条可读的判断链。</p>
<div class="blog_h4"><span class="graybg">决策树、分类树、回归树的关系</span></div>
<p>决策树是总称；分类树（Classification Tree）与回归树（Regression Tree）是它在两类监督学习任务上的具体形式。三者共享同一种树结构，但目标变量、切分准则与叶子输出不同。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">概念</td>
<td style="text-align: center;">预测目标</td>
<td style="text-align: center;">叶子输出</td>
<td style="text-align: center;">常用切分准则</td>
</tr>
</thead>
<tbody>
<tr>
<td>决策树（Decision Tree）</td>
<td>树模型的总称</td>
<td>取决于具体任务</td>
<td>取决于具体任务</td>
</tr>
<tr>
<td>分类树（Classification Tree）</td>
<td>离散标签（如“流失/不流失”）</td>
<td>类别或类别概率</td>
<td>基尼不纯度（Gini）、熵（Entropy）、信息增益（Information Gain）</td>
</tr>
<tr>
<td>回归树（Regression Tree）</td>
<td>连续数值（如“价格”“时长”）</td>
<td>一个数值常数</td>
<td>平方误差（Squared Error）、MSE / SSE 下降</td>
</tr>
</tbody>
</table>
<p>因此不要把“决策树”和“分类树”当成并列概念。更准确的表述是：<span style="background-color: #c0c0c0;">分类树与回归树都是决策树；前者预测类别，后者预测数值</span>。</p>
<div class="blog_h4"><span class="graybg">结构与统一公式</span></div>
<p>设某个节点上落入的样本集合为 <span displaypfx="inline-" class="mathjax-container">\(S\)</span>，大小为 <span displaypfx="inline-" class="mathjax-container">\(|S|\)</span>。对某个候选切分（Split）<span displaypfx="inline-" class="mathjax-container">\(\phi\)</span>，例如“第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个特征 <span displaypfx="inline-" class="mathjax-container">\(x_j\le t\)</span>”，样本会被分成左右两个子节点：</p>
<span displaypfx="" class="mathjax-container">\[S_L(\phi)=\{(\mathbf{x}_i,y_i)\in S:\ x_{i,j}\le t\},\quad S_R(\phi)=S\setminus S_L(\phi)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(S_L\)</span> 是满足切分条件的样本集合，<span displaypfx="inline-" class="mathjax-container">\(S_R\)</span> 是剩余样本集合。树在每个节点都会尝试多个候选切分 <span displaypfx="inline-" class="mathjax-container">\(\phi\)</span>，并保留收益最大的那个。</p>
<p>为了避免分别记忆分类树与回归树的训练目标，可以先写成统一形式。设 <span displaypfx="inline-" class="mathjax-container">\(I(S)\)</span> 表示“节点 <span displaypfx="inline-" class="mathjax-container">\(S\)</span> 当前有多乱”或“在该节点上预测误差有多大”，则一次切分的收益可统一写成：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Gain}(S,\phi)=I(S)-\frac{|S_L(\phi)|}{|S|}I\!\left(S_L(\phi)\right)-\frac{|S_R(\phi)|}{|S|}I\!\left(S_R(\phi)\right)\]</span>
<p>这条式子的含义非常直接：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(I(S)\)</span>：切分前，这个节点有多混乱。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(I(S_L),I(S_R)\)</span>：切分后，左右子节点各自还有多混乱。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\frac{|S_L|}{|S|},\frac{|S_R|}{|S|}\)</span>：左右子节点占父节点样本的比例。要乘这个比例，是因为大子节点对总体误差的影响更大，小子节点不能和大子节点拥有同样权重。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathrm{Gain}(S,\phi)\)</span> 越大，说明这次切分越有效。</li>
</ul>
<p>分类树与回归树的差别，正体现在 <span displaypfx="inline-" class="mathjax-container">\(I(S)\)</span> 具体取什么。</p>
<div class="blog_h4"><span class="graybg">决策树：一个整体例子</span></div>
<p>在贷款审批（Loan Approval）场景中，决策树可以直接写成规则链。根节点先按负债收入比（Debt-to-Income Ratio）切分；若负债收入比过高，再看是否有逾期记录；若负债收入比正常，再看信用评分（Credit Score）与近 6 个月收入稳定性。最终某个叶节点可能对应“直接通过”，另一个叶节点对应“人工复核”，再另一个叶节点对应“拒绝”。这就是决策树最重要的工程价值：它不只是给出一个分数，还给出一条可追溯的判断路径。</p>
<div class="blog_h4"><span class="graybg">分类树：目标、公式与含义</span></div>
<p>分类树的目标是让每个叶节点里的标签尽量单一。若一个节点里几乎都是同一类样本，这个节点就“纯”；若各类样本混在一起，这个节点就“不纯”。</p>
<p>设类别集合为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{K}\)</span>，类别 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 在节点 <span displaypfx="inline-" class="mathjax-container">\(S\)</span> 中的占比记为：</p>
<span displaypfx="" class="mathjax-container">\[p_k(S)=\frac{\text{节点 }S\text{ 中标签为 }k\text{ 的样本数}}{|S|}\]</span>
<p>常用的不纯度（Impurity）有两种。</p>
<p>基尼不纯度（Gini Impurity）：</p>
<span displaypfx="" class="mathjax-container">\[G(S)=1-\sum_{k\in\mathcal{K}}p_k(S)^2\]</span>
<p>它可以读成“随机抽两个样本时，标签不一致的倾向有多强”。若节点里全是同一类，则某个 <span displaypfx="inline-" class="mathjax-container">\(p_k=1\)</span>、其余为 0，此时 <span displaypfx="inline-" class="mathjax-container">\(G(S)=0\)</span>，说明节点已经纯净；若二分类里两类各占一半，则 <span displaypfx="inline-" class="mathjax-container">\(G(S)=1-(0.5^2+0.5^2)=0.5\)</span>，说明混杂程度较高。</p>
<p>熵（Entropy）：</p>
<span displaypfx="" class="mathjax-container">\[H(S)=-\sum_{k\in\mathcal{K}}p_k(S)\log p_k(S)\]</span>
<p>熵衡量的是“不确定性”。若节点里全是同一类，则不需要再猜，熵为 0；若各类比例接近，说明不确定性高，熵也更大。</p>
<p>信息增益（Information Gain）就是“切分前的不确定性”减去“切分后的加权不确定性”：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{IG}(S,\phi)=H(S)-\frac{|S_L(\phi)|}{|S|}H\!\left(S_L(\phi)\right)-\frac{|S_R(\phi)|}{|S|}H\!\left(S_R(\phi)\right)\]</span>
<p>这条公式的每一部分都对应一个明确动作：</p>
<ul>
<li>第一项 <span displaypfx="inline-" class="mathjax-container">\(H(S)\)</span> 是切分前的混乱程度。</li>
<li>后两项是切分后左右子节点各自的混乱程度，并按样本占比加权求和。</li>
<li>两者相减，就是这次切分让节点“变纯”了多少。</li>
</ul>
<p>有些实现使用基尼下降而不是信息增益，本质上是同一件事：<span style="background-color: #c0c0c0;">选择那个能让子节点更纯、让标签更集中的切分</span>。</p>
<div class="blog_h4"><span class="graybg">分类树：实际例子——用户流失预警</span></div>
<p>设任务是预测“一个用户未来 30 天是否流失”。标签只有两类：流失 / 未流失，因此这是典型分类树问题。候选特征可以包括：最近 7 天登录次数、最近 30 天是否投诉、是否还有未使用优惠券、最近一次下单距今天数。</p>
<p>假设根节点先尝试切分“最近 7 天登录次数 &lt; 2”。这次切分后，左子节点里的用户大多已经很久不活跃，且流失比例显著升高；右子节点里的用户则活跃度更高、留存率更好。此时无论用基尼还是熵计算，左右节点的加权不纯度都会明显低于父节点，因此这会成为一个高质量切分。</p>
<p>继续往下，左子节点还可以再按“最近 30 天是否投诉”切分：低活跃且有投诉的用户，叶节点里可能出现“82% 最终流失”；低活跃但无投诉的用户，叶节点里可能是“61% 流失”。此时叶子不只给出类别，还可给出经验概率。工程上，这样的输出就能直接用于运营动作：高风险叶子推召回优惠券，中风险叶子推客服回访，低风险叶子不干预。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/descision-tree-1.jpg"><img class="alignnone size-full wp-image-41221" src="https://blog.gmem.cc/wp-content/uploads/2026/03/descision-tree-1.jpg" alt="descision-tree-1" width="1024" height="559" /></a></p>
<div class="blog_h4"><span class="graybg">回归树：目标、公式与含义</span></div>
<p>回归树处理的是连续数值目标，例如价格、时长、销量、能耗。它不追求“类别更纯”，而追求“同一个叶节点里的数值尽量接近”。</p>
<p>若某个叶节点 <span displaypfx="inline-" class="mathjax-container">\(S\)</span> 最终只输出一个常数 <span displaypfx="inline-" class="mathjax-container">\(c\)</span>，那么在平方误差（Squared Error）下，最优输出不是中位数，而是均值：</p>
<span displaypfx="" class="mathjax-container">\[c^*(S)=\arg\min_c\sum_{(\mathbf{x}_i,y_i)\in S}(y_i-c)^2=\frac{1}{|S|}\sum_{(\mathbf{x}_i,y_i)\in S}y_i\]</span>
<p>之所以是均值，是因为平方误差会把所有偏差向两边拉平，而均值正是使平方偏差和最小的那个常数。</p>
<p>在这个叶节点上，最小平方误差和（Sum of Squared Errors, SSE）为：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{SSE}(S)=\sum_{(\mathbf{x}_i,y_i)\in S}\left(y_i-c^*(S)\right)^2\]</span>
<p>它表示节点里所有样本值围绕叶子预测值 <span displaypfx="inline-" class="mathjax-container">\(c^*(S)\)</span> 的总波动。SSE 越大，说明这个节点里的样本值越分散，单用一个常数代表它们的效果越差。</p>
<p>因此一次切分的目标是让切分后的总误差尽量小，也可写成误差下降尽量大：</p>
<span displaypfx="" class="mathjax-container">\[\Delta(S,\phi)=\mathrm{SSE}(S)-\mathrm{SSE}\!\left(S_L(\phi)\right)-\mathrm{SSE}\!\left(S_R(\phi)\right)\]</span>
<p>若更喜欢看平均误差，也可以把它写成 MSE（Mean Squared Error）形式：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{MSE}(S)=\frac{1}{|S|}\mathrm{SSE}(S)\]</span>
<p>等价地，也可以最小化切分后的加权平均误差：<span displaypfx="" class="mathjax-container">\[\frac{|S_L|}{|S|}\mathrm{MSE}(S_L)+\frac{|S_R|}{|S|}\mathrm{MSE}(S_R)\]</span></p>
<p>两种写法完全等价：SSE 强调总误差，MSE 强调平均误差；本质都是寻找让子节点内部数值更集中的切分。</p>
<div class="blog_h4"><span class="graybg">回归树：实际例子——外卖配送时长预测</span></div>
<p>设任务是预测一笔订单从接单到送达需要多少分钟。这是连续数值目标，因此属于回归树。候选特征可以包括：配送距离、是否下雨、是否晚高峰、商家出餐速度、骑手当前手中订单数。</p>
<p>根节点可能先按“配送距离是否大于 3 公里”切分。因为近距离订单与远距离订单的时长分布差异很大，这一步通常能显著降低节点内部方差。对远距离子节点，再按“是否下雨”切分；下雨天路况更慢、波动更大。对近距离子节点，则可能按“是否处于午晚高峰”切分。</p>
<p>假设某个叶节点对应“距离 &gt; 3 公里、下雨、晚高峰”这类订单，这个叶节点里的训练样本平均送达时长是 47 分钟，那么该叶子的预测值就是 47。另一个叶节点若对应“距离 &lt; 2 公里、不下雨、非高峰”，其平均时长可能只有 18 分钟。回归树的预测逻辑不是拟合一条全局直线，而是把不同业务情境分段，再在每段内给出一个局部平均值。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/decision-tree-2.png"><img class="alignnone size-full wp-image-41227" src="https://blog.gmem.cc/wp-content/uploads/2026/03/decision-tree-2.png" alt="decision-tree-2" width="1469" height="1035" /></a></p>
<div class="blog_h4"><span class="graybg">适用场景</span></div>
<ul>
<li>需要把模型输出翻译成可审计规则，例如风控、审批、运营分层、客服分流。</li>
<li>数据以表格（Tabular）为主，且存在显著非线性、阈值效应或特征交互。</li>
<li>希望同时处理连续特征与离散特征，并保留较强可解释性。</li>
<li>注意：单棵树方差高、容易过拟合，通常需要限制最大深度、最小叶子样本数或配合集成方法。</li>
</ul>
<div class="blog_h3"><span class="graybg">随机森林</span></div>
<p>随机森林（Random Forest）是 Bagging（Bootstrap Aggregating）在树模型上的经典实现：用 bootstrap 采样生成多份训练子集，训练多棵通常偏差较低、但对训练数据扰动高度敏感（高方差）的决策树，再把它们的输出聚合，以显著降低整体方差并提升鲁棒性。</p>
<p>这里的“高方差（High Variance）”指模型的估计方差（Estimator Variance）或预测方差（Prediction Variance）：如果训练集稍有变化，单棵树学到的分裂结构与最终预测就可能明显变化。随机森林利用多棵树的平均/投票，把这种由数据扰动带来的波动相互抵消。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/random-forest.jpg"><img class="alignnone size-full wp-image-41231" src="https://blog.gmem.cc/wp-content/uploads/2026/03/random-forest.jpg" alt="random-forest" width="1287" height="816" /></a></p>
<div class="blog_h4"><span class="graybg">它和单棵树真正差在哪里</span></div>
<p>随机森林并不是“很多棵树简单叠起来”这么粗糙。单棵树的主要问题，是一旦在上层节点做出某个早期切分，后面的整条子树都会被这个局部选择锁定，因此对数据扰动极敏感。随机森林通过 bootstrap 采样与特征子采样，让每棵树在“数据视角”和“可用特征视角”上都略有不同，再把这些不同视角的判断平均起来，从而大幅削弱某一棵树早期错误切分的破坏力。</p>
<div class="blog_h4"><span class="graybg">算法与符号</span></div>
<p>给定训练集 <span displaypfx="inline-" class="mathjax-container">\(D=\{(\mathbf{x}_i,y_i)\}_{i=1}^{N}\)</span>。对 <span displaypfx="inline-" class="mathjax-container">\(m=1,\dots,M\)</span>：</p>
<ol>
<li>bootstrap 采样：从 <span displaypfx="inline-" class="mathjax-container">\(D\)</span> 有放回采样 <span displaypfx="inline-" class="mathjax-container">\(N\)</span> 次得到 <span displaypfx="inline-" class="mathjax-container">\(D_m\)</span>。所谓“有放回（Sampling with Replacement）”，是指每次抽到一个样本后，都先把它放回原数据集，再进行下一次抽样；因此同一个样本可能被重复抽中，而有些样本在这一轮里一次也没有被抽到。</li>
<li>训练一棵树 <span displaypfx="inline-" class="mathjax-container">\(T_m\)</span>：每个节点分裂时，只在随机选取的 <span displaypfx="inline-" class="mathjax-container">\(d'\)</span> 个特征上搜索最优切分（特征子采样，feature subsampling）。</li>
</ol>
<p>预测时，回归取平均，分类取多数投票：</p>
<span displaypfx="" class="mathjax-container">\[\hat y_{\text{reg}}(\mathbf{x})=\frac{1}{M}\sum_{m=1}^{M}T_m(\mathbf{x}),\quad \hat y_{\text{clf}}(\mathbf{x})=\mathrm{mode}\left(\{T_m(\mathbf{x})\}_{m=1}^{M}\right)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(T_m(\mathbf{x})\)</span> 表示第 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 棵树对样本 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 的预测。回归任务把所有树的输出做平均，以减少波动；分类任务取众数 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{mode}(\cdot)\)</span>，也就是票数最多的类别。随机森林的稳定性正来自这种“多棵树共同决定”的聚合机制。</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(M\)</span>：树的数量。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(d'\)</span>：每次分裂考虑的特征数（常见经验：分类用 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{d}\)</span>，回归用 <span displaypfx="inline-" class="mathjax-container">\(d/3\)</span>）。</li>
</ul>
<div class="blog_h4"><span class="graybg">为什么有效：方差下降与“去相关”</span></div>
<p>若单棵树预测的方差为 <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span>，不同树之间的相关系数近似为 <span displaypfx="inline-" class="mathjax-container">\(\rho\)</span>（<span displaypfx="inline-" class="mathjax-container">\(0\le\rho\le 1\)</span>），则平均后的方差近似为：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Var}\!\left(\frac{1}{M}\sum_{m=1}^{M}T_m(\mathbf{x})\right)\approx \sigma^2\left(\rho+\frac{1-\rho}{M}\right)\]</span>
<p>这个近似式子把随机森林为什么有效说得很清楚： <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span> 是单棵树自身的预测波动， <span displaypfx="inline-" class="mathjax-container">\(\rho\)</span> 是树与树之间的相关性， <span displaypfx="inline-" class="mathjax-container">\(M\)</span> 是树数。树越多， <span displaypfx="inline-" class="mathjax-container">\(\frac{1-\rho}{M}\)</span> 越小；树之间越不相似， <span displaypfx="inline-" class="mathjax-container">\(\rho\)</span> 越低，最终平均后的波动就越小。</p>
<p>因此随机森林有两条主线：</p>
<ul>
<li>增加 <span displaypfx="inline-" class="mathjax-container">\(M\)</span> 降低 <span displaypfx="inline-" class="mathjax-container">\((1-\rho)/M\)</span> 项。</li>
<li>通过 bootstrap + 特征子采样降低相关性 <span displaypfx="inline-" class="mathjax-container">\(\rho\)</span>，让集成真正“互补”。</li>
</ul>
<div class="blog_h4"><span class="graybg">OOB：不用额外验证集的误差估计</span></div>
<p>bootstrap 采样会重复抽到某些样本。对固定样本 <span displaypfx="inline-" class="mathjax-container">\(i\)</span>，一次采样中没被抽到的概率为 <span displaypfx="inline-" class="mathjax-container">\((1-\frac{1}{N})^N\approx e^{-1}\approx 0.368\)</span>。因此每棵树大约有 36.8% 的样本是袋外（Out-of-Bag, OOB）样本，可用它们评估该树对未见数据的表现，并对全森林给出近似验证误差。</p>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\((1-\frac{1}{N})\)</span> 表示“一次抽样没抽到某个固定样本”的概率，连续做 <span displaypfx="inline-" class="mathjax-container">\(N\)</span> 次后得到 <span displaypfx="inline-" class="mathjax-container">\((1-\frac{1}{N})^N\)</span>。当 <span displaypfx="inline-" class="mathjax-container">\(N\)</span> 足够大时，它逼近 <span displaypfx="inline-" class="mathjax-container">\(e^{-1}\)</span>，也就是约 36.8%。这就是为什么随机森林天然拥有一批“没参与这棵树训练”的 OOB 样本。</p>
<div class="blog_h4"><span class="graybg">适用场景</span></div>
<ul>
<li>表格数据（Tabular）的强默认基线：非线性、特征交互、缺失值与尺度不一致都较鲁棒。</li>
<li>对超参数不敏感、训练稳定；可用特征重要性（Feature Importance）做解释与特征筛查。</li>
<li>当需要极致精度时，GBDT 家族往往更强；当需要更快推理/更小模型时，线性/浅层模型更合适。</li>
</ul>
<div class="blog_h3"><span class="graybg">梯度提升树（GBDT）</span></div>
<p>梯度提升树（Gradient Boosting Decision Tree, GBDT）是一类按序叠加回归树的加法模型（Additive Model）。模型从简单的初始预测 <span displaypfx="inline-" class="mathjax-container">\(F_0\)</span> 出发，在每一轮加入一棵新树，用于修正当前模型尚未拟合好的部分，从而逐步降低训练集上的经验风险（Empirical Risk）。</p>
<p>每一轮新增的树都对应一个修正函数（Correction Function）。它不直接重新学习标签 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>，而是拟合当前模型输出与目标之间尚未被解释的差异。多轮修正连续叠加后，模型会从粗糙预测逐步逼近目标函数。</p>
<p>一个直观比喻是：GBDT 像一组按顺序接手的阅卷老师。第一位老师先给出一个粗略分数，后面的每一位老师都不重做整张卷子，只专门检查前面模型错得最明显的地方，并在这些地方补上修正意见。树一棵接一棵叠加后，最终预测会越来越接近真实值。</p>
<p>这里每棵小树都不是独立完成任务的“大模型”，而是一个局部纠错器。它关心的是当前模型还没解释好的误差：哪些样本被高估了，哪些被低估了，以及这些误差集中出现在哪些特征区域。“梯度”对应当前损失下降最快的修正方向，“提升”则表示把这些小修正持续累加成一个更强的整体模型。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/gbdt.jpg"><img class="alignnone size-full wp-image-41247" src="https://blog.gmem.cc/wp-content/uploads/2026/03/gbdt.jpg" alt="gbdt" width="1408" height="768" /></a></p>
<div class="blog_h4"><span class="graybg">目标：最小化经验风险</span></div>
<p>GBDT 背后的目标非常直接：寻找一个函数 <span displaypfx="inline-" class="mathjax-container">\(F\)</span>，使训练集上的总损失最小：</p>
<span displaypfx="" class="mathjax-container">\[ \min_F\ \sum_{i=1}^{N}\ell\big(y_i,F(\mathbf{x}_i)\big) \]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\ell(y,F(\mathbf{x}))\)</span> 是单样本损失函数，平方损失、对数损失等都可以放进来。难点在于：函数 <span displaypfx="inline-" class="mathjax-container">\(F\)</span> 不是一个普通标量参数，而是一个复杂的预测函数；如果一次性同时优化所有树的结构和叶子输出，组合空间过大，几乎不可直接求解。</p>
<p>因此 GBDT 采用前向分步加法（Forward Stagewise Additive Modeling）：不一次求整个 <span displaypfx="inline-" class="mathjax-container">\(F\)</span>，而是把它写成逐步累加的形式，只在第 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 轮新增一个修正函数 <span displaypfx="inline-" class="mathjax-container">\(f_m\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[ F_M(\mathbf{x})=F_0(\mathbf{x})+\nu\sum_{m=1}^{M}f_m(\mathbf{x}) \]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(F_M(\mathbf{x})\)</span> 表示：模型经过总共 <span displaypfx="inline-" class="mathjax-container">\(M\)</span> 轮提升后，对输入样本 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 输出的最终预测值。下标 <span displaypfx="inline-" class="mathjax-container">\(M\)</span> 表示“已经累计做了 <span displaypfx="inline-" class="mathjax-container">\(M\)</span> 次修正”，不是幂次，也不是某一棵单独的树。GBDT 的最终模型是许多轮小修正叠加后的总结果。</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span>：一个输入样本的特征向量。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(F_0(\mathbf{x})\)</span>：初始模型对样本 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 的预测，常取常数 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 使 <span displaypfx="inline-" class="mathjax-container">\(\sum_i \ell(y_i,c)\)</span> 最小。它可以理解为模型在还没有长出任何树之前给出的第一版粗略判断。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(f_m(\mathbf{x})\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 轮新增的回归树在样本 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 上给出的修正值，负责弥补当前模型尚未拟合好的部分。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\sum_{m=1}^{M}f_m(\mathbf{x})\)</span>：把前 <span displaypfx="inline-" class="mathjax-container">\(M\)</span> 轮所有修正树的输出加起来，得到总修正量。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\nu\in(0,1]\)</span>：学习率（Shrinkage），控制每一步修正只走多大。它的作用不是改变方向，而是缩小步长，使训练更稳定、泛化更好。</li>
</ul>
<p>这个公式也可以按“底稿 + 反复批改”来理解： <span displaypfx="inline-" class="mathjax-container">\(F_0(\mathbf{x})\)</span> 是第一版预测，后面的每个 <span displaypfx="inline-" class="mathjax-container">\(f_m(\mathbf{x})\)</span> 都是在已有结果上补一小笔修正，最终的 <span displaypfx="inline-" class="mathjax-container">\(F_M(\mathbf{x})\)</span> 就是经历 <span displaypfx="inline-" class="mathjax-container">\(M\)</span> 次修正后的版本。</p>
<span displaypfx="" class="mathjax-container">\[ f_m=\arg\min_f\sum_{i=1}^{N}\ell\big(y_i,F_{m-1}(\mathbf{x}_i)+f(\mathbf{x}_i)\big) \]</span>
<p>这条式子表示：在第 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 轮，模型要寻找一个新的修正函数 <span displaypfx="inline-" class="mathjax-container">\(f_m\)</span>，使它加到旧模型 <span displaypfx="inline-" class="mathjax-container">\(F_{m-1}\)</span> 上之后，训练集的总损失尽可能小。这里的 <span displaypfx="inline-" class="mathjax-container">\(\arg\min_f\)</span> 可以读作“在所有候选函数 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 里，找出那个能让目标最小的函数”。因此，求出来的不是一个数，而是当前这一轮最合适的新树。</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(F_{m-1}(\mathbf{x}_i)\)</span>：前 <span displaypfx="inline-" class="mathjax-container">\(m-1\)</span> 轮模型在第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个样本上的当前预测值。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(f(\mathbf{x}_i)\)</span>：候选新树在第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个样本上的修正值。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(F_{m-1}(\mathbf{x}_i)+f(\mathbf{x}_i)\)</span>：把新树加进去之后，这个样本的新预测值。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\ell\big(y_i,F_{m-1}(\mathbf{x}_i)+f(\mathbf{x}_i)\big)\)</span>：该样本在新预测下的损失。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\sum_{i=1}^{N}\ell(\cdot)\)</span>：把所有样本的损失加总，得到这一轮希望尽量压低的整体目标。</li>
</ul>
<p>这一轮之所以写成 <span displaypfx="inline-" class="mathjax-container">\(F_{m-1}+f\)</span>，而不是重新求一个全新的 <span displaypfx="inline-" class="mathjax-container">\(F_m\)</span>，原因在于 GBDT 采用的是逐步修正策略：旧模型已经学到的部分先保留，新树只负责补上当前还没有拟合好的误差。这样每一轮只解决一个更小的局部问题，计算上更可行，也更符合“不断纠错”的直觉。</p>
<p>第 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 轮只优化新增的修正函数 <span displaypfx="inline-" class="mathjax-container">\(f_m\)</span>，而把已有模型 <span displaypfx="inline-" class="mathjax-container">\(F_{m-1}\)</span> 视为固定量。这样做把原本难以整体求解的函数优化问题，拆成了一系列可逐步求解的局部优化问题。</p>
<div class="blog_h4"><span class="graybg">负梯度的来源</span></div>
<p>上面的子问题仍然不容易直接做，因为“最佳新树”本身还是一个复杂的函数搜索问题。GBDT 的关键近似是：在当前模型 <span displaypfx="inline-" class="mathjax-container">\(F_{m-1}\)</span> 附近，对损失做一阶展开，只看局部下降方向。</p>
<p>对单个样本 <span displaypfx="inline-" class="mathjax-container">\(i\)</span>，把新增修正记为 <span displaypfx="inline-" class="mathjax-container">\(f(\mathbf{x}_i)\)</span>，则有：</p>
<span displaypfx="" class="mathjax-container">\[ \ell\big(y_i,F_{m-1}(\mathbf{x}_i)+f(\mathbf{x}_i)\big) \approx \ell\big(y_i,F_{m-1}(\mathbf{x}_i)\big) + \frac{\partial \ell(y_i,F(\mathbf{x}_i))}{\partial F(\mathbf{x}_i)}\Big|_{F=F_{m-1}} f(\mathbf{x}_i) \]</span>
<p>一阶展开表明，新增函数 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 的最优局部方向由损失对模型输出的负梯度决定。因此第 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 轮的伪残差（Pseudo-residual）定义为：</p>
<span displaypfx="" class="mathjax-container">\[ r_i^{(m)}=-\left.\frac{\partial \ell\left(y_i,F(\mathbf{x}_i)\right)}{\partial F(\mathbf{x}_i)}\right|_{F=F_{m-1}} \]</span>
<p>接下来的动作就自然了：不用树直接拟合标签 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span>，而是用一棵回归树去拟合这些伪残差 <span displaypfx="inline-" class="mathjax-container">\(r_i^{(m)}\)</span>。这棵树学到的，就是“当前模型在输入空间不同区域里，应该往哪个方向、修正多大”的分段常数近似。</p>
<p>模型更新写成：</p>
<span displaypfx="" class="mathjax-container">\[ F_m(\mathbf{x})=F_{m-1}(\mathbf{x})+\nu f_m(\mathbf{x}) \]</span>
<p>“梯度提升”这一名称对应的正是上述更新方式：优化对象不是有限维参数向量，而是预测函数本身；每一轮更新都沿着损失在函数空间中的负梯度方向加入一个新的修正函数。</p>
<div class="blog_h4"><span class="graybg">平方损失下的残差形式</span></div>
<p>若损失取平方损失</p>
<span displaypfx="" class="mathjax-container">\[ \ell(y,F)=\frac{1}{2}(y-F)^2 \]</span>
<p>则对模型输出求导：</p>
<span displaypfx="" class="mathjax-container">\[ \frac{\partial \ell(y,F)}{\partial F}=F-y \]</span>
<p>因此第 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 轮的负梯度为：</p>
<span displaypfx="" class="mathjax-container">\[ r_i^{(m)}=-\left(F_{m-1}(\mathbf{x}_i)-y_i\right)=y_i-F_{m-1}(\mathbf{x}_i) \]</span>
<p>这正是当前预测与真实值之间的残差（Residual）。所以在回归任务里，人们常把 GBDT 说成“不断拟合残差”；这并不是另一套经验规则，而是平方损失下负梯度公式的直接结果。</p>
<div class="blog_h4"><span class="graybg">不同损失下学到的是什么</span></div>
<p>“每轮拟合残差”只在平方损失下最直观。更一般地，GBDT 拟合的是当前损失对模型输出的负梯度，因此不同任务下，每轮新树学到的对象并不完全相同。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">任务 / 损失</td>
<td style="text-align: center;">当前轮拟合的量</td>
<td style="text-align: center;">含义</td>
</tr>
</thead>
<tbody>
<tr>
<td>平方损失（MSE）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(y-F(x)\)</span></td>
<td>真实值与当前预测的残差</td>
</tr>
<tr>
<td>绝对损失（MAE）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{sign}(y-F(x))\)</span></td>
<td>只关心修正方向，不强调误差幅度；对异常值更鲁棒</td>
</tr>
<tr>
<td>二分类对数损失（Log Loss）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(y-p(x)\)</span></td>
<td>真实标签与当前预测概率之间的差</td>
</tr>
</tbody>
</table>
<p>因此，GBDT 不是被固定死在“回归残差拟合”这一种形式上，而是一套更一般的函数优化框架。平方损失下它表现为残差学习；分类任务下，它表现为不断修正当前概率估计。</p>
<div class="blog_h4"><span class="graybg">树作为局部修正器</span></div>
<p>因为树天然产生分段常数（Piecewise Constant）的局部修正。若某一轮中，一棵树把样本空间切成若干区域 <span displaypfx="inline-" class="mathjax-container">\(R_1,\dots,R_J\)</span>，那么它输出的是：</p>
<span displaypfx="" class="mathjax-container">\[ f_m(\mathbf{x})=\sum_{j=1}^{J}\gamma_j\mathbf{1}(\mathbf{x}\in R_j) \]</span>
<p>这意味着：模型不是对整个输入空间统一加一个修正，而是在每个局部区域里分别加一个常数修正 <span displaypfx="inline-" class="mathjax-container">\(\gamma_j\)</span>。这正适合处理表格数据里的阈值效应、非线性和特征交互。</p>
<div class="blog_h4"><span class="graybg">叶子输出的解析解</span></div>
<p>在平方损失下，若某个叶子覆盖的样本集合为 <span displaypfx="inline-" class="mathjax-container">\(S\)</span>，并用常数 <span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span> 拟合这一叶内的残差 <span displaypfx="inline-" class="mathjax-container">\(r_i\)</span>，则该叶子的局部目标是：</p>
<span displaypfx="" class="mathjax-container">\[ \min_{\gamma}\sum_{i\in S}(r_i-\gamma)^2 \]</span>
<p>对 <span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span> 求导并令其为 0：</p>
<span displaypfx="" class="mathjax-container">\[ \frac{d}{d\gamma}\sum_{i\in S}(r_i-\gamma)^2=-2\sum_{i\in S}(r_i-\gamma)=0 \]</span>
<span displaypfx="" class="mathjax-container">\[ \sum_{i\in S}r_i-|S|\gamma=0 \]</span>
<span displaypfx="" class="mathjax-container">\[ \gamma^*=\frac{1}{|S|}\sum_{i\in S}r_i \]</span>
<p>因此叶子输出之所以是均值，不是经验设定，而是平方损失下这个局部最小二乘问题的解析解。每一轮加入一棵树，本质上是在每个局部区域里补上一段“平均残差修正”。</p>
<div class="blog_h4"><span class="graybg">训练流程</span></div>
<ol>
<li>初始化 <span displaypfx="inline-" class="mathjax-container">\(F_0\)</span>，通常取使总体损失最小的常数模型。</li>
<li>对第 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 轮，计算所有样本的伪残差 <span displaypfx="inline-" class="mathjax-container">\(r_i^{(m)}\)</span>。</li>
<li>训练一棵回归树 <span displaypfx="inline-" class="mathjax-container">\(f_m\)</span> 去拟合 <span displaypfx="inline-" class="mathjax-container">\((\mathbf{x}_i,r_i^{(m)})\)</span>。</li>
<li>把这棵树按学习率 <span displaypfx="inline-" class="mathjax-container">\(\nu\)</span> 缩小后加到当前模型上。</li>
<li>重复多轮，直到验证集误差不再下降，或达到预设树数。</li>
</ol>
<p><span style="background-color: #c0c0c0;">GBDT 的本质，是把复杂的函数优化问题拆成许多轮局部修正，并在每一轮用一棵小树逼近当前损失下降最快的方向。</span></p>
<div class="blog_h4"><span class="graybg">适用场景</span></div>
<ul>
<li>结构化表格任务中的强模型：广告、推荐、风控、运营排序、传统特征工程场景。</li>
<li>对特征尺度、非线性、缺失值较鲁棒；能自动学习高阶交互。</li>
<li>注意：对超参数更敏感（树深、学习率、树数、采样比例）；需要用验证集早停（Early Stopping）防止过拟合。</li>
</ul>
<div class="blog_h4"><span class="graybg">为什么它在表格数据上强、在别的场景不一定强</span></div>
<p>GBDT 家族在表格数据上长期强势，并不是偶然。表格任务里常见的模式，本来就很适合树模型表达：阈值效应、离散规则、局部非线性、特征交互、缺失值路径、不同子群体的分段行为。树的分裂结构天然能把“收入高且近期投诉过的老用户”和“收入低但活跃度高的新用户”这类组合规则直接切出来，而不需要像线性模型那样依赖大量手工交叉特征。</p>
<p>但同样的归纳偏置，在高维稀疏文本、图像、语音这类输入上就未必占优。因为这些任务的有效模式通常不是少数几个阈值切分，而是分布在海量维度上的连续结构、局部平滑模式或长程依赖。此时树模型虽然能做基线，但往往不如专门面向非结构化数据的线性稀疏模型、卷积网络或 Transformer。</p>
<div class="blog_h4"><span class="graybg">优点与局限</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">维度</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>优点</td>
<td>表格数据精度高；对特征缩放不敏感；可处理非线性与高阶交互；通常还能给出特征重要性</td>
</tr>
<tr>
<td>局限 1</td>
<td>串行训练，训练速度通常慢于随机森林等并行集成</td>
</tr>
<tr>
<td>局限 2</td>
<td>对学习率、树数、树深、采样比例等超参数较敏感，容易出现“稍调不慎就欠拟合或过拟合”</td>
</tr>
<tr>
<td>局限 3</td>
<td>在超高维稀疏文本或图像等非结构化输入上，通常不是最自然的主力模型</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">XGBoost</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>XGBoost（Extreme Gradient Boosting）是在梯度提升树（Gradient Boosting Decision Tree, GBDT）基础上的工程化强化版本。它关注的是：在保持高表达能力的同时，让树的生长过程更稳定、目标函数更明确、复杂度控制更系统。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>XGBoost 仍然采用加法模型（Additive Model）：当前模型由多棵树的输出求和得到，第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 轮只学习一棵新树 <span displaypfx="inline-" class="mathjax-container">\(f_t\)</span> 作为修正项。它的关键强化点有两条：第一，把新增树的学习写成显式的带正则优化问题；第二，对该目标做二阶近似，同时利用梯度（Gradient）和海森（Hessian）信息计算叶子输出与分裂增益。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>设第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 轮预测为 <span displaypfx="inline-" class="mathjax-container">\(\tilde y_i^{(t)}=\tilde y_i^{(t-1)}+f_t(\boldsymbol{x}_i)\)</span>。XGBoost 在第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 轮优化的目标可写为：</p>
<span displaypfx="" class="mathjax-container">\[\text{Obj}^{(t)}=\sum_{i=1}^{N} \ell(y_i,\tilde y_i^{(t)})+\Omega(f_t)+\text{const}\]</span>
<p>在这个目标里， <span displaypfx="inline-" class="mathjax-container">\(\ell(y_i,\tilde y_i^{(t)})\)</span> 衡量第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个样本在第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 轮更新后的预测误差， <span displaypfx="inline-" class="mathjax-container">\(\Omega(f_t)\)</span> 惩罚新树本身的复杂度， <span displaypfx="inline-" class="mathjax-container">\(\text{const}\)</span> 则表示与当前轮新树无关的常数项。也就是说，XGBoost 每一轮都在平衡“把误差降下去”和“不要把树长得太复杂”。</p>
<p>其中正则项通常取：</p>
<span displaypfx="" class="mathjax-container">\[\Omega(f)=\gamma T+\frac{\lambda}{2}\sum_{j=1}^{T}w_j^2\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 是叶子数， <span displaypfx="inline-" class="mathjax-container">\(\gamma T\)</span> 惩罚“树长出太多叶子”， <span displaypfx="inline-" class="mathjax-container">\(\frac{\lambda}{2}\sum_j w_j^2\)</span> 惩罚“每个叶子的输出值过大”。前者控制结构复杂度，后者控制数值幅度，两者合起来让模型更稳。</p>
<p>对损失在当前预测附近做二阶泰勒展开，定义：</p>
<span displaypfx="" class="mathjax-container">\[g_i=\frac{\partial \ell(y_i,\tilde y_i)}{\partial \tilde y_i}\Big|_{\tilde y_i=\tilde y_i^{(t-1)}},\qquad h_i=\frac{\partial^2 \ell(y_i,\tilde y_i)}{\partial \tilde y_i^2}\Big|_{\tilde y_i=\tilde y_i^{(t-1)}}\]</span>
<p><span displaypfx="inline-" class="mathjax-container">\(g_i\)</span> 是一阶导数，表示“当前这个样本朝哪个方向改，损失下降最快”； <span displaypfx="inline-" class="mathjax-container">\(h_i\)</span> 是二阶导数，表示“这个方向有多陡、多稳定”。XGBoost 同时使用这两项信息，所以比只用一阶梯度的做法更精细。</p>
<p>则近似目标为：</p>
<span displaypfx="" class="mathjax-container">\[\tilde{\text{Obj}}^{(t)}\approx \sum_{i=1}^{N} \left(g_i f_t(\boldsymbol{x}_i)+\frac{1}{2}h_i f_t(\boldsymbol{x}_i)^2\right)+\Omega(f_t)\]</span>
<p>这条近似目标里， <span displaypfx="inline-" class="mathjax-container">\(f_t(\boldsymbol{x}_i)\)</span> 是新树在样本 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 上给出的修正值。线性项 <span displaypfx="inline-" class="mathjax-container">\(g_i f_t(\boldsymbol{x}_i)\)</span> 反映“沿当前方向修正是否有利”，二次项 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{2}h_i f_t(\boldsymbol{x}_i)^2\)</span> 反映“修正过大时会不会带来额外代价”。</p>
<p>若固定树结构，记落在叶子 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 的样本集合为 <span displaypfx="inline-" class="mathjax-container">\(I_j\)</span>，并定义 <span displaypfx="inline-" class="mathjax-container">\(G_j=\sum_{i\in I_j}g_i\)</span>、<span displaypfx="inline-" class="mathjax-container">\(H_j=\sum_{i\in I_j}h_i\)</span>，则该叶子的最优输出为：</p>
<span displaypfx="" class="mathjax-container">\[w_j^*=-\frac{G_j}{H_j+\lambda}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(G_j\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个叶子里所有样本一阶梯度的总和， <span displaypfx="inline-" class="mathjax-container">\(H_j\)</span> 是二阶梯度总和。分子告诉模型这个叶子整体应该往哪个方向修正，分母则用二阶信息和正则项 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 把修正幅度稳住。</p>
<p>某次分裂把父叶拆成左右两叶后的增益（Gain）为：</p>
<span displaypfx="" class="mathjax-container">\[\text{Gain}=\frac{1}{2}\left(\frac{G_L^2}{H_L+\lambda}+\frac{G_R^2}{H_R+\lambda}-\frac{G^2}{H+\lambda}\right)-\gamma\]</span>
<p>这个增益式子比较的是“父叶不分裂”和“分成左右两叶”谁更划算。 <span displaypfx="inline-" class="mathjax-container">\(G_L,H_L\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(G_R,H_R\)</span> 分别是左右子叶的梯度统计量， <span displaypfx="inline-" class="mathjax-container">\(G,H\)</span> 是父叶统计量；最后减去 <span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span>，表示每多长一个叶子都要付复杂度代价。</p>
<p>这说明 XGBoost 的分裂同时考虑“收益”和“复杂度惩罚”，并以此控制结构扩张。</p>
<div class="blog_h4"><span class="graybg">相对经典 GBDT 的强化点</span></div>
<p>XGBoost 的工程优势并不只来自“实现更快”。它把几项在工业界非常关键的能力同时做扎实了：显式复杂度正则化、二阶梯度优化、行采样与列采样、对稀疏特征的处理、缓存友好的分裂搜索，以及成熟的早停和监控接口。也正因为如此，它长期被视作结构化数据建模的稳健默认选项。</p>
<ul>
<li>正则化更明确：通过叶子数惩罚与叶子权重 L2 正则显式控制树复杂度。</li>
<li>分裂选择更精细：同时使用一阶梯度与二阶梯度，不只看“该往哪边改”，还看“这一步有多稳”。</li>
<li>随机化更充分：支持样本子采样 <span displaypfx="inline-" class="mathjax-container">\(\text{subsample}\)</span> 和列采样 <span displaypfx="inline-" class="mathjax-container">\(\text{colsample\_bytree}\)</span>，既降计算量也降过拟合。</li>
<li>工程实现更成熟：对稀疏输入、缓存访问和大规模数据训练都做了专门优化。</li>
</ul>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>用当前模型计算每个样本的一阶梯度 <span displaypfx="inline-" class="mathjax-container">\(g_i\)</span> 与二阶梯度 <span displaypfx="inline-" class="mathjax-container">\(h_i\)</span>。</li>
<li>在候选特征与候选阈值上搜索分裂增益最大的切分。</li>
<li>固定树结构后，利用 <span displaypfx="inline-" class="mathjax-container">\(w_j^*\)</span> 计算每个叶子的最优输出。</li>
<li>把新树加入现有模型，继续下一轮迭代。</li>
<li>预测时把所有树的输出累加，再映射到分类或回归结果。</li>
</ol>
<div class="blog_h4"><span class="graybg">训练控制与早停</span></div>
<p>XGBoost 在工程实践里常配合较大的树数上限和验证集早停一起使用。做法通常不是先死板地决定“到底训 300 棵还是 800 棵树”，而是给一个偏大的上限，再观察验证集误差或任务指标何时不再提升。这样，树数不再是拍脑袋设定的固定值，而变成由验证集驱动的可学习训练预算。</p>
<p>这对 Boosting 家族尤其重要，因为学习率较小时，往往需要更多轮小步修正；若学习率较大，又更容易在后期进入过拟合区域。早停在这里的作用，是把“拟合能力很强”与“不要无限继续纠错”之间的边界交给验证集来判定。</p>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在点击率预估（CTR Prediction）中，用户、广告和上下文之间往往存在复杂交互。XGBoost 可以通过逐轮加树自动学习“什么样的用户在什么时间、看到什么样的广告更容易点击”这类组合规则，因此在工业级表格任务中长期保持强竞争力。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：表格数据表现强，目标函数清晰，正则化明确，工程生态成熟。</li>
<li>局限：超参数较多；树深、学习率、树数、采样比例之间存在明显耦合。</li>
<li>适用场景：风控、广告、推荐、排序与一般结构化表格建模。</li>
</ul>
<div class="blog_h3"><span class="graybg">LightGBM</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>LightGBM 是面向大规模数据与高维稀疏特征优化的 GBDT 实现。它关注的核心问题是：当样本量和特征维度都很大时，如何降低分裂搜索的时间与内存成本，同时尽量不牺牲精度。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>LightGBM 的两个关键设计是直方图分桶（Histogram Binning）和叶子优先生长（Leaf-wise Growth）。前者把连续特征离散到有限个桶上，后者每一步都继续分裂当前收益最大的叶子，从而在相同叶子预算下更快降低训练误差。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>在优化目标上，LightGBM 与 GBDT / XGBoost 一致，仍然是逐轮加入树来降低损失。它的区别主要体现在计算方式：若某个连续特征被离散到 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 个桶，则算法只需在桶边界上搜索分裂点，而不必在所有实数取值上逐一枚举。这样，每个节点维护的是桶级梯度统计量，而非逐样本的原始实数值。</p>
<p>叶子优先生长意味着：算法始终选择当前分裂增益最大的叶子继续向下扩展。它通常比按层生长（Level-wise）更激进，因而在同样叶子数限制下更容易得到较低训练误差，但也更容易在局部区域长出很深的树。</p>
<div class="blog_h4"><span class="graybg">速度来自哪些具体机制</span></div>
<p>LightGBM 的“快”并不是单一技巧带来的，而是多项近似与工程策略叠加的结果。</p>
<ul>
<li>直方图分桶：把连续特征先离散到有限个桶，例如默认 255 或 256 个桶。这样找切分点时，复杂度从“遍历大量原始取值”变成“遍历固定桶边界”。</li>
<li>Leaf-wise 生长：每一轮只扩展当前最值得继续分裂的叶子，在相同叶子预算下往往比 level-wise 更快压低误差。</li>
<li>GOSS（Gradient-based One-Side Sampling）：优先保留梯度较大的样本，因为这些样本更能代表当前模型最需要修正的区域；对梯度较小的样本只随机保留一部分，以减少计算量。</li>
<li>EFB（Exclusive Feature Bundling）：把互斥的稀疏特征打包到同一组表示里，降低高维稀疏输入的有效维度。</li>
</ul>
<p>其中 GOSS 和 EFB 的意义非常工程化。前者服务于“大样本下不必每轮都看全量样本”，后者服务于“高维稀疏特征下不必把所有稀疏列都单独维护”。也正因为如此，LightGBM 尤其适合推荐、广告、风控这类既大规模又高度稀疏的表格任务。</p>
<p>LightGBM 也因此更依赖约束配套。leaf-wise 生长虽然更激进，但如果不同时限制 <pre class="crayon-plain-tag">max_depth</pre>、<pre class="crayon-plain-tag">num_leaves</pre>、<pre class="crayon-plain-tag">min_data_in_leaf</pre> 这类参数，局部树会很快长得过深，训练误差降得很漂亮，验证集却未必跟着受益。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>将连续特征离散到有限个桶，并统计桶级梯度信息。</li>
<li>在每个节点上基于桶统计量搜索最优切分。</li>
<li>选择全局增益最大的叶子继续分裂。</li>
<li>使用最大深度、最大叶子数、最小叶子样本数等约束抑制过拟合。</li>
<li>预测时沿树路径到达叶子并累加所有树的输出。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在大规模推荐粗排任务中，输入特征通常极高维且高度稀疏。LightGBM 能在保留较强拟合能力的同时显著缩短训练时间，因此非常适合需要频繁重训的工业流水线。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：训练快、内存占用低，对大规模稀疏特征友好。</li>
<li>局限：叶子优先策略若约束不足，局部树会过深，过拟合风险更高。</li>
<li>适用场景：超大规模表格数据、稀疏特征建模、追求训练效率的工业场景。</li>
</ul>
<div class="blog_h3"><span class="graybg">CatBoost</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>CatBoost 是另一条非常重要的 Boosting 工业路线。它关注的核心问题不是“比 XGBoost 更通用”或“比 LightGBM 更快”，而是：<span style="background-color: #c0c0c0;">当数据里有大量高基数类别特征时，如何在不引入严重数据泄露和预测偏移的前提下，把这些特征真正用好</span>。广告、推荐、电商、用户画像、商品属性等任务里，这个问题尤其关键。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>CatBoost 的两项代表性设计是有序目标统计（Ordered Target Statistics）与有序提升（Ordered Boosting）。前者处理类别特征，后者处理训练偏移。它们共同服务于同一个目标：不要让模型在训练时偷看到本不该知道的目标信息。</p>
<div class="blog_h4"><span class="graybg">类别特征为什么难</span></div>
<p>对树模型而言，类别特征的难点不只是“字符串不能直接喂进去”，更在于很多类别列的取值空间极大，例如用户 ID、商品 ID、城市、品牌、广告位、渠道来源。若简单做 one-hot 编码，维度会迅速膨胀；若直接用目标均值编码，又很容易把标签信息泄露进训练过程，导致离线效果虚高、线上泛化变差。</p>
<p>CatBoost 的思路是：用类别对应的目标统计量来表示类别，但计算某个样本的统计量时，只允许使用它之前样本的信息，而不允许看见它自己以及它之后的样本标签。这样得到的类别表示虽然仍然利用了监督信号，却显著降低了数据泄露与目标泄露风险。</p>
<div class="blog_h4"><span class="graybg">有序目标统计与有序提升</span></div>
<p>设某个类别值为 <span displaypfx="inline-" class="mathjax-container">\(c\)</span>，其编码值可以理解为“在训练顺序中，当前位置之前出现过的同类样本的目标统计量，再配合一个全局先验做平滑”。这样一来，模型在看到当前样本时，只能利用“过去”信息，不能直接偷看当前标签。</p>
<p>同样的思路也延伸到 Boosting 训练本身。传统目标编码或普通 Boosting 在训练时，常会让同一批样本之间发生微妙的信息穿透，形成预测偏移（Prediction Shift）。CatBoost 通过 ordered boosting 尽量让每一步的残差估计更接近“真正未见样本上的估计误差”，从而让训练分布和推理分布更一致。</p>
<div class="blog_h4"><span class="graybg">对称树</span></div>
<p>CatBoost 还大量使用对称树（Symmetric Tree，也常称 Oblivious Tree）：同一层的所有节点共享同一个切分规则。这种树结构比一般决策树更受约束，表达上没那么自由，但推理路径规整、计算高效，也更利于工程实现和高吞吐推断。</p>
<p>从工程角度看，CatBoost 的价值就在于把“类别特征处理”从手工特征工程里拿回模型内部。很多场景里，团队不再需要先在外部纠结 one-hot、频次编码、目标编码、平滑规则和泄露控制，而是可以把类别列直接交给模型，让训练流程自己处理这套问题。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：对类别特征支持最好，默认配置往往更稳，推理效率高。</li>
<li>局限：当类别特征优势不明显时，未必总能胜过 XGBoost 或 LightGBM；训练生态也没有前两者那么普遍。</li>
<li>适用场景：类别特征很多、ID 类特征很多、需要尽量减少手工编码工作的表格任务。</li>
</ul>
<div class="blog_h2"><span class="graybg">概率模型</span></div>
<p>这一类方法处理的核心问题是：模型不仅要给出“预测是什么”，还要明确回答“这个结果有多可信、数据是如何生成的、隐藏结构是什么”。因此它们直接建模概率分布（Probability Distribution）、隐变量（Latent Variables）或序列依赖关系，在分类、密度估计、软聚类、序列推断与不确定性表达中具有统一优势。</p>
<div class="blog_h3"><span class="graybg">朴素贝叶斯</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>朴素贝叶斯（Naive Bayes）用于分类问题。给定特征 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span>，它要估计样本属于每个类别 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 的后验概率 <span displaypfx="inline-" class="mathjax-container">\(p(y|\boldsymbol{x})\)</span>。该方法尤其适合高维稀疏、小样本或需要快速概率基线的任务。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>朴素贝叶斯的核心假设是：在给定类别 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 的条件下，各个特征条件独立（Conditional Independence）。这个假设通常并不严格成立，但它把高维联合分布的估计问题，转化为多个一维条件分布的估计问题。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>由贝叶斯公式：</p>
<span displaypfx="" class="mathjax-container">\[p(y|\boldsymbol{x})=\frac{p(\boldsymbol{x}|y)p(y)}{p(\boldsymbol{x})}\]</span>
<p>这个贝叶斯公式把后验概率拆成三部分： <span displaypfx="inline-" class="mathjax-container">\(p(y|\boldsymbol{x})\)</span> 是看到特征后属于类别 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 的概率； <span displaypfx="inline-" class="mathjax-container">\(p(\boldsymbol{x}|y)\)</span> 是该类别生成这组特征的可能性； <span displaypfx="inline-" class="mathjax-container">\(p(y)\)</span> 是类别先验； <span displaypfx="inline-" class="mathjax-container">\(p(\boldsymbol{x})\)</span> 则是对所有类别做归一化的总证据。</p>
<p>由于 <span displaypfx="inline-" class="mathjax-container">\(p(\boldsymbol{x})\)</span> 对所有类别相同，分类时只需比较：</p>
<span displaypfx="" class="mathjax-container">\[p(y|\boldsymbol{x})\propto p(y)\prod_{j=1}^{d}p(x_j|y)\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(\propto\)</span> 表示“成正比”。因为对同一个样本来说，分母 <span displaypfx="inline-" class="mathjax-container">\(p(\boldsymbol{x})\)</span> 对所有类别都相同，所以比较类别大小时只需看右边：先验 <span displaypfx="inline-" class="mathjax-container">\(p(y)\)</span> 乘上每个特征条件概率 <span displaypfx="inline-" class="mathjax-container">\(p(x_j|y)\)</span> 的连乘积。</p>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(p(y)\)</span> 是先验概率（Prior）， <span displaypfx="inline-" class="mathjax-container">\(p(x_j|y)\)</span> 是条件似然（Likelihood）。根据特征类型不同，可以得到高斯朴素贝叶斯、伯努利朴素贝叶斯、多项式朴素贝叶斯等变体。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>从训练集统计每个类别的先验概率 <span displaypfx="inline-" class="mathjax-container">\(p(y)\)</span>。</li>
<li>估计每个类别下各特征的条件分布 <span displaypfx="inline-" class="mathjax-container">\(p(x_j|y)\)</span>。</li>
<li>推断时计算各类别的对数后验分数 <span displaypfx="inline-" class="mathjax-container">\(\log p(y)+\sum_j \log p(x_j|y)\)</span>。</li>
<li>选择分数最大的类别作为预测输出。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在垃圾邮件检测中，若某些词在垃圾邮件中显著更常见，那么这些词对应的条件概率会被估得更大，从而把包含这些词的邮件判到垃圾类别。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：训练快、推断快、对高维稀疏特征友好。</li>
<li>局限：条件独立假设常被破坏；概率校准有时较弱。</li>
<li>适用场景：文本分类、垃圾邮件过滤、简单可靠的概率基线。</li>
</ul>
<div class="blog_h3"><span class="graybg">高斯混合模型（GMM）</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>高斯混合模型（Gaussian Mixture Model, GMM）处理的是“数据由多个潜在高斯成分混合生成”的建模问题。与 K-Means 只输出硬划分不同，GMM 希望估计每个样本属于各个簇的概率，并允许不同簇有不同的协方差结构。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>GMM 引入潜变量 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 表示样本来自哪一个高斯成分。整体分布由多个高斯分布按混合系数加权得到，因此每个样本对各个簇的归属是软的（Soft Assignment），并以概率形式表达。</p>
<p>一个直观比喻是：把数据想成混在一起的几类人群，只能看到每个人的外在特征，却看不到他原本属于哪一类。每一类人群都有自己的“中心位置”和“分散形状”，对应一个高斯成分；整个数据集则像这些人群按不同比例叠在一起形成的总体分布。与 K-Means 把每个样本硬塞进某一个簇不同，GMM 会给出“这个样本更像第 1 类，也有一部分像第 2 类”这样的软归属结果。</p>
<p>因此，混合系数 <span displaypfx="inline-" class="mathjax-container">\(\pi_k\)</span> 可以理解为各类人群在总体中的占比，均值 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\mu}_k\)</span> 是每类人群的中心，协方差 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\Sigma}_k\)</span> 描述该群体沿不同方向的扩散方式，而责任度 <span displaypfx="inline-" class="mathjax-container">\(\gamma_{ik}\)</span> 则表示“样本 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 有多大程度属于第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 类”。这也是 GMM 比 K-Means 更灵活的原因：它允许边界模糊，也允许簇具有不同大小、方向和椭球形状。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>对样本 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span>，GMM 的概率密度写为：</p>
<span displaypfx="" class="mathjax-container">\[p(\boldsymbol{x})=\sum_{k=1}^{K} \pi_k \mathcal{N}(\boldsymbol{x}\mid \boldsymbol{\mu}_k,\boldsymbol{\Sigma}_k)\]</span>
<p>这条式子说明 GMM 的整体密度不是由单个高斯给出，而是 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个高斯成分的加权和。 <span displaypfx="inline-" class="mathjax-container">\(\pi_k\)</span> 决定第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个成分在总体中的占比， <span displaypfx="inline-" class="mathjax-container">\(\mathcal{N}(\boldsymbol{x}\mid \boldsymbol{\mu}_k,\boldsymbol{\Sigma}_k)\)</span> 描述样本 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 在该成分下有多典型。</p>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\pi_k\)</span> 是混合系数，满足 <span displaypfx="inline-" class="mathjax-container">\(\pi_k\ge 0\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(\sum_k \pi_k=1\)</span>。给定样本后，其属于第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个成分的责任度（Responsibility）为：</p>
<span displaypfx="" class="mathjax-container">\[\gamma_{ik}=p(z_i=k|\boldsymbol{x}_i)=\frac{\pi_k \mathcal{N}(\boldsymbol{x}_i\mid \boldsymbol{\mu}_k,\boldsymbol{\Sigma}_k)}{\sum_{j=1}^{K}\pi_j \mathcal{N}(\boldsymbol{x}_i\mid \boldsymbol{\mu}_j,\boldsymbol{\Sigma}_j)}\]</span>
<p>责任度 <span displaypfx="inline-" class="mathjax-container">\(\gamma_{ik}\)</span> 是“样本 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 属于成分 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 的后验概率”。分子表示“成分 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 解释这个样本的能力”，分母则把所有成分对该样本的解释能力加总起来做归一化，因此所有 <span displaypfx="inline-" class="mathjax-container">\(\gamma_{ik}\)</span> 加起来等于 1。</p>
<p>GMM 的参数通常通过期望最大化（Expectation-Maximization, EM）求解。E 步计算责任度，M 步更新参数：</p>
<span displaypfx="" class="mathjax-container">\[N_k=\sum_{i=1}^{N}\gamma_{ik},\qquad \pi_k=\frac{N_k}{N}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(N_k\)</span> 不是成分 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 的硬计数，而是软计数：每个样本只按自己的责任度贡献一部分。于是混合系数更新为 <span displaypfx="inline-" class="mathjax-container">\(\pi_k=\frac{N_k}{N}\)</span>，表示第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个成分在总体中的相对权重。</p>
<span displaypfx="" class="mathjax-container">\[\boldsymbol{\mu}_k=\frac{1}{N_k}\sum_{i=1}^{N}\gamma_{ik}\boldsymbol{x}_i,\qquad \boldsymbol{\Sigma}_k=\frac{1}{N_k}\sum_{i=1}^{N}\gamma_{ik}(\boldsymbol{x}_i-\boldsymbol{\mu}_k)(\boldsymbol{x}_i-\boldsymbol{\mu}_k)^\top\]</span>
<p>均值更新式说明：每个样本按责任度大小对中心 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\mu}_k\)</span> 做加权贡献；协方差更新式则是在该加权中心周围统计离散程度。责任度越大，样本对该成分的中心和形状影响越大。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>初始化混合系数、均值和协方差。</li>
<li>E 步：计算每个样本对各高斯成分的责任度。</li>
<li>M 步：根据责任度更新参数。</li>
<li>重复 E / M，直到对数似然收敛。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在用户分群中，若人群天然分成多个椭球状子群体，那么 GMM 不仅能给出簇划分，还能输出“某个用户属于每个群体的概率”，这比 K-Means 的硬分配更细致。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：软聚类、概率解释清晰、能建模不同协方差形状。</li>
<li>局限：对初始化敏感；高维时协方差估计代价高。</li>
<li>适用场景：软聚类、密度估计、带概率解释的聚类分析。</li>
</ul>
<div class="blog_h3"><span class="graybg">隐马尔可夫模型（HMM）</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>隐马尔可夫模型（Hidden Markov Model, HMM）用于序列建模，尤其适合处理序列标注（Sequence Labeling）问题。序列标注的任务形式是：给定一个按时间或位置排列的输入序列，为序列中的每个位置分配一个标签。例如，在词性标注中，句子“我爱北京天安门”的每个词或字都需要对应一个词性标签；在语音识别中，一段连续声学信号需要对应到一串离散文字或音素标签。</p>
<p>HMM 是序列模型中的经典早期方法，在语音识别等领域长期具有重要地位。它的优势在于结构清晰、推断高效，能够以较低的计算成本处理序列决策；与此同时，它主要依赖局部状态转移和局部发射关系，所使用的上下文信息相对有限。</p>
<p>HMM 的建模方式是：观测序列 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}_{1:T}\)</span> 可以看到，但生成这些观测的状态序列 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{z}_{1:T}\)</span> 看不到。这里的隐藏状态通常对应词性、命名实体类别或发音状态等潜在标签。围绕这一模型，核心问题通常包括计算某段观测序列的概率、恢复最可能的隐藏状态路径，以及估计模型参数。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>HMM 假设隐藏状态满足一阶马尔可夫性（First-order Markov Property）：当前状态只依赖前一个状态；同时每个观测只依赖当前状态。一个直接的比喻是：无法直接看到远方朋友每天所处的天气，但可以持续看到他发布的活动，例如“去游泳”“去逛街”或“在家睡觉”。在这个比喻里，天气是隐藏状态 <span displaypfx="inline-" class="mathjax-container">\(z_t\)</span>，活动是观测 <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span>。HMM 的生成逻辑正是<span style="background-color: #c0c0c0;">先生成隐藏状态序列，再由每个状态发射对应观测</span>。</p>
<p>如果今天是晴天，明天仍然晴天的概率通常较高；如果今天下雨，朋友在家休息的概率通常高于去游泳。这分别对应 HMM 中的状态转移概率 <span displaypfx="inline-" class="mathjax-container">\(p(z_t|z_{t-1})\)</span> 和发射概率 <span displaypfx="inline-" class="mathjax-container">\(p(x_t|z_t)\)</span>。因此，HMM 把序列建模为一个“剧本模拟器”：先按转移规律生成一条看不见的天气轨迹，再按照每一天的天气生成看得见的活动记录。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">HMM 概念</td>
<td style="text-align: center;">比喻中的对应物</td>
<td style="text-align: center;">含义</td>
</tr>
</thead>
<tbody>
<tr>
<td>隐藏状态 <span displaypfx="inline-" class="mathjax-container">\(z_t\)</span></td>
<td>每天的天气</td>
<td>真实存在，但观察者不能直接看到。</td>
</tr>
<tr>
<td>观测 <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span></td>
<td>朋友圈里的活动</td>
<td>可以直接看到，用来反推隐藏状态。</td>
</tr>
<tr>
<td>初始分布 <span displaypfx="inline-" class="mathjax-container">\(p(z_1)\)</span></td>
<td>第一天各种天气出现的概率</td>
<td>序列从什么状态开始。</td>
</tr>
<tr>
<td>转移概率 <span displaypfx="inline-" class="mathjax-container">\(p(z_t|z_{t-1})\)</span></td>
<td>天气从今天到明天的变化规律</td>
<td>例如“晴天后仍是晴天”的概率较高。</td>
</tr>
<tr>
<td>发射概率 <span displaypfx="inline-" class="mathjax-container">\(p(x_t|z_t)\)</span></td>
<td>某种天气下出现某种活动的概率</td>
<td>例如下雨时更可能“在家睡觉”。</td>
</tr>
<tr>
<td>隐藏状态序列 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{z}_{1:T}\)</span></td>
<td>整段天气变化轨迹</td>
<td>模型希望恢复的潜在过程。</td>
</tr>
<tr>
<td>观测序列 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}_{1:T}\)</span></td>
<td>整段活动记录</td>
<td>模型的输入数据。</td>
</tr>
</tbody>
</table>
<p>这个比喻也解释了 HMM 的优势与局限。它简单、快速、可解释，因为联合概率能够拆成局部转移和局部发射的乘积，并可用动态规划高效推断。但它的条件独立假设也很强：某一天的活动只由当天的天气决定，不直接依赖前后活动。在词性标注等任务中，一个词的标签往往同时受左右上下文影响，这种假设就会限制表达能力。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>HMM 的联合分布写为：</p>
<span displaypfx="" class="mathjax-container">\[p(\boldsymbol{x}_{1:T},\boldsymbol{z}_{1:T})=p(z_1)\prod_{t=2}^{T}p(z_t|z_{t-1})\prod_{t=1}^{T}p(x_t|z_t)\]</span>
<p>这条联合分布把一整条序列拆成三部分： <span displaypfx="inline-" class="mathjax-container">\(p(z_1)\)</span> 是初始状态概率， <span displaypfx="inline-" class="mathjax-container">\(\prod_{t=2}^{T}p(z_t|z_{t-1})\)</span> 是状态转移链， <span displaypfx="inline-" class="mathjax-container">\(\prod_{t=1}^{T}p(x_t|z_t)\)</span> 是每个状态发射观测的概率。正因为有这种乘积分解，HMM 才能用动态规划高效求解。</p>
<p>若记初始分布为 <span displaypfx="inline-" class="mathjax-container">\(\pi\)</span>，转移矩阵为 <span displaypfx="inline-" class="mathjax-container">\(A\)</span>，发射分布为 <span displaypfx="inline-" class="mathjax-container">\(B\)</span>，则前向算法（Forward Algorithm）的核心量为：</p>
<span displaypfx="" class="mathjax-container">\[\alpha_t(j)=p(x_{1:t},z_t=j)\]</span>
<p><span displaypfx="inline-" class="mathjax-container">\(\alpha_t(j)\)</span> 的含义是：看到前 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个观测，同时当前时刻状态恰好是第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个状态的联合概率。它把“历史观测信息”和“当前落在哪个状态”压缩成一个可递推的中间量。</p>
<p>其递推为：</p>
<span displaypfx="" class="mathjax-container">\[\alpha_t(j)=\left[\sum_i \alpha_{t-1}(i)A_{ij}\right]B_j(x_t)\]</span>
<p>递推式可以分成两步看：先对所有上一步状态 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 求和，用 <span displaypfx="inline-" class="mathjax-container">\(A_{ij}\)</span> 累积“从 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 转到 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 的可能性”；再乘上 <span displaypfx="inline-" class="mathjax-container">\(B_j(x_t)\)</span>，表示当前状态 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 发射出观测 <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span> 的概率。</p>
<p>最可能状态路径可由维特比算法（Viterbi Algorithm）求解；参数估计通常采用 Baum-Welch 算法，即 HMM 上的 EM。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>定义状态空间、转移概率和发射概率。</li>
<li>若参数已知，用前向 / 后向算法做概率计算，用维特比算法做解码。</li>
<li>若参数未知，用 Baum-Welch 在训练集上迭代估计参数。</li>
<li>根据任务输出状态路径、序列概率或边缘状态分布。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在词性标注中，观测是单词序列，隐藏状态是词性标签。HMM 会同时利用“某个词在某种词性下更常见”的发射规律和“词性之间如何转移”的序列规律完成整体解码。</p>
<p>例如，对序列“我 / 爱 / 北京 / 天安门”，模型看到的是词本身，看不到的是背后的标签序列。HMM 会比较多种候选路径，如“代词（Pronoun）→ 动词（Verb）→ 专有名词（Proper Noun）→ 专有名词（Proper Noun）”与其他不合理组合的概率。由于“我”更容易由代词状态发射，“爱”更容易由动词状态发射，而“代词后接动词、动词后接名词性成分”又符合常见转移规律，这条标签路径就会获得更高概率。这个过程可以理解为：模型一边根据词本身猜标签，一边检查整条词性路径是否顺畅。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/hmm.jpg"><img class="alignnone size-full wp-image-41251" src="https://blog.gmem.cc/wp-content/uploads/2026/03/hmm.jpg" alt="hmm" width="1408" height="768" /></a></p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：序列结构清晰，动态规划推断高效，可解释性强。</li>
<li>局限：一阶马尔可夫假设较强，表达能力有限。</li>
<li>适用场景：基础序列标注、时间状态切换建模、中小规模序列任务。</li>
</ul>
<div class="blog_h3"><span class="graybg">最大熵模型（MaxEnt）与 MEMM</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>最大熵模型（Maximum Entropy Model, MaxEnt）处理的是：在已知若干特征约束的前提下，如何选择一个既满足这些约束、又不过度引入额外假设的条件概率模型。它在经典 NLP 中极其重要，因为很多任务的关键证据并不是来自连续向量，而是来自大量离散、稀疏、可人工设计的特征，例如当前词、前后词、词形、词性、是否出现在词典中等。</p>
<p>若只看单个位置分类，MaxEnt 的数学形式与多项逻辑回归（Multinomial Logistic Regression）本质一致：它直接建模 <span displaypfx="inline-" class="mathjax-container">\(p(y\mid x)\)</span>。当它被串到序列建模里时，最典型的扩展是最大熵马尔可夫模型（Maximum Entropy Markov Model, MEMM）：当前位置标签的条件概率不仅依赖输入特征，也依赖前一个标签，因此可用于序列标注。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>“最大熵”这个名字的含义并不是模型本身会主动变随机，而是说：在所有满足已知统计约束的分布中，选择熵（Entropy）最大的那一个，也就是额外承诺最少、最不武断的那一个。放到工程上，它导向的是指数族（Exponential Family）形式的条件模型：把各类特征函数加权求和，再通过归一化得到概率。</p>
<p>因此，MaxEnt 可以理解为<span style="background-color: #c0c0c0;">特征驱动的判别式概率模型</span>。它不像 HMM 那样去描述观测如何被生成，而是直接回答“在这些特征都成立时，这个标签有多大概率”。这使它特别适合传统 NLP 中大量规则化、稀疏化、模板化的人工特征。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>多分类最大熵模型通常写成：</p>
<span displaypfx="" class="mathjax-container">\[p(y\mid x)=\frac{\exp\left(\sum_k \lambda_k f_k(x,y)\right)}{\sum_{y'}\exp\left(\sum_k \lambda_k f_k(x,y')\right)}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(f_k(x,y)\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个特征函数，衡量“输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 与标签 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 是否满足某种模式”； <span displaypfx="inline-" class="mathjax-container">\(\lambda_k\)</span> 是对应权重。分子给出某个候选标签的未归一化分数，分母把所有标签的分数加总后做归一化，因此输出是一个真正的条件概率分布。</p>
<p>若进一步用于序列，第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个位置的 MEMM 局部条件分布可写成：</p>
<span displaypfx="" class="mathjax-container">\[p(y_t\mid y_{t-1},x,t)=\frac{\exp\left(\sum_k \lambda_k f_k(y_{t-1},y_t,x,t)\right)}{\sum_{y'}\exp\left(\sum_k \lambda_k f_k(y_{t-1},y',x,t)\right)}\]</span>
<p>这条式子说明：在 MEMM 里，当前位置标签 <span displaypfx="inline-" class="mathjax-container">\(y_t\)</span> 的概率是对“给定上一标签和当前输入特征”的局部 softmax。它保留了丰富特征表达能力，但归一化只在当前状态的出边上进行，这正是后文 CRF 要解决的标签偏置（Label Bias）来源。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>设计特征函数，把当前位置、局部上下文和标签关系编码成离散特征。</li>
<li>通过最大条件对数似然训练权重 <span displaypfx="inline-" class="mathjax-container">\(\lambda_k\)</span>，通常配合 <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 正则化。</li>
<li>若是独立分类任务，直接取条件概率最大的标签。</li>
<li>若是 MEMM 这类链式模型，则结合维特比或束搜索在局部概率之上做整条序列解码。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在传统命名实体识别中，可以为当前位置构造特征：当前词是否首字母大写、前一个词是否是头衔、后一个词是否是公司后缀、前一个标签是否已经是 <span displaypfx="inline-" class="mathjax-container">\(B\text{-}ORG\)</span>。MaxEnt 或 MEMM 会把这些证据线性加权，再输出当前位置属于组织名、人名或其他类别的条件概率。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：判别式训练、可直接接入丰富特征、概率输出直观。</li>
<li>局限：若做链式局部归一化，容易出现标签偏置；强依赖特征工程。</li>
<li>适用场景：传统 NLP 局部分类、特征模板丰富的序列标注过渡方案、理解 CRF 之前的判别式建模主线。</li>
</ul>
<div class="blog_h3"><span class="graybg">条件随机场（CRF）</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>条件随机场（Conditional Random Field, CRF）主要用于结构化预测（Structured Prediction），尤其是序列标注。它处理的问题是：在给定输入序列 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 的条件下，如何联合预测输出标签序列 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{y}\)</span>，并显式建模标签之间的依赖关系。</p>
<p>CRF 可以看作对 HMM 的进一步发展：它不再试图解释输入序列如何被生成，而是直接在给定输入的条件下预测最合理的标签结构。通过引入全局特征和更灵活的上下文依赖，CRF 缓解了 HMM 仅依赖局部独立假设所带来的信息不足。在深度学习广泛进入 NLP 之前，CRF 长期是各类标注任务中的核心方法。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>CRF 的核心是在给定观测序列的条件下，对整条标签序列进行全局评分，而不是显式描述数据生成过程。仍以天气和活动的比喻来理解：已知一整周的活动记录后，CRF 会把各种可能的天气序列都拿来比较，判断哪一种与这组活动整体最一致。它建模的是条件分布 <span displaypfx="inline-" class="mathjax-container">\(p(\boldsymbol{y}|\boldsymbol{x})\)</span>，而不是 HMM 那样的联合分布 <span displaypfx="inline-" class="mathjax-container">\(p(\boldsymbol{x},\boldsymbol{y})\)</span>。</p>
<p>这种“全局把控”体现在两个层面。第一，CRF 的打分对象是完整的标签路径，而不是每个位置互相独立的局部决策；第二，打分依据可以是灵活定义的特征函数（Feature Function）。例如，若连续三天都出现“游泳”，对应“连续晴天”的标签组合就应得到更高分；若一周中出现“滑雪”这类活动，与“夏天”一致的天气标签组合就应被显著压低。特征函数可以同时利用当前位置、前后邻域以及相邻标签之间的组合关系。</p>
<p>因此，CRF 可以视为一个<span style="background-color: #c0c0c0;">面向整条序列的打分系统</span>：输入固定，模型比较不同标签序列的相对合理性，并选择得分最高的一条。它的优势是能够充分利用复杂上下文和标签依赖，在序列标注任务中通常比 HMM 更准确；代价是训练和推断都更重，需要计算整条序列上的归一化与动态规划。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>线性链 CRF 的条件分布通常写为：</p>
<span displaypfx="" class="mathjax-container">\[p(\boldsymbol{y}|\boldsymbol{x})=\frac{1}{Z(\boldsymbol{x})}\exp\left(\sum_{t=1}^{T}\sum_k \lambda_k f_k(y_{t-1},y_t,\boldsymbol{x},t)\right)\]</span>
<p>这个式子最好分三层看。先看最里面的 <span displaypfx="inline-" class="mathjax-container">\(\sum_{t=1}^{T}\sum_k \lambda_k f_k(y_{t-1},y_t,\boldsymbol{x},t)\)</span>：它表示对整条标签序列逐位置累积特征得分。再看外面的指数 <span displaypfx="inline-" class="mathjax-container">\(\exp(\cdot)\)</span>：它把“总得分”变成一个始终为正的数，而且总得分越大，这个数就越大。最后再除以 <span displaypfx="inline-" class="mathjax-container">\(Z(\boldsymbol{x})\)</span>，把这些正数正规化成概率。因此，CRF 的计算顺序可以理解为：<span style="background-color: #c0c0c0;">先打分，再变成正权重，最后归一化成概率</span>。</p>
<p>为了看清楚 <span displaypfx="inline-" class="mathjax-container">\(Z(\boldsymbol{x})\)</span> 在做什么，可以先临时把分子记成一个“未归一化分数”：</p>
<span displaypfx="" class="mathjax-container">\[\tilde{p}(\boldsymbol{y}|\boldsymbol{x})=\exp\left(\sum_{t=1}^{T}\sum_k \lambda_k f_k(y_{t-1},y_t,\boldsymbol{x},t)\right)\]</span>
<p>这里特意加波浪号，是为了强调它<span style="background-color: #c0c0c0;">还不是概率</span>。原因很简单：把所有可能标签序列的 <span displaypfx="inline-" class="mathjax-container">\(\tilde{p}(\boldsymbol{y}|\boldsymbol{x})\)</span> 加起来，结果一般不会恰好等于 1。它只是每条路径的相对权重，表达“这条标签路径有多合理”。</p>
<p>这时配分函数（Partition Function）就出现了：</p>
<span displaypfx="" class="mathjax-container">\[Z(\boldsymbol{x})=\sum_{\boldsymbol{y}} \exp\left(\sum_{t=1}^{T}\sum_k \lambda_k f_k(y_{t-1},y_t,\boldsymbol{x},t)\right)\]</span>
<p>之所以你会觉得它“和上面那个公式一样”，是因为它确实就是<span style="background-color: #c0c0c0;">把上式分子对所有可能的标签序列整体求和</span>。上面的 <span displaypfx="inline-" class="mathjax-container">\(p(\boldsymbol{y}|\boldsymbol{x})\)</span> 针对的是某一条固定标签序列 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{y}\)</span>；而这里的 <span displaypfx="inline-" class="mathjax-container">\(Z(\boldsymbol{x})\)</span> 不是看某一条路径，而是把所有可能路径都算一遍，再全部加起来。所以它不是“另一条几乎一样的公式”，而是“分子在全标签空间上的总和”。</p>
<p>于是条件概率就变成：</p>
<span displaypfx="" class="mathjax-container">\[p(\boldsymbol{y}|\boldsymbol{x})=\frac{\tilde{p}(\boldsymbol{y}|\boldsymbol{x})}{Z(\boldsymbol{x})}\]</span>
<p>现在这个式子就容易理解了：某条路径的概率，等于“这条路径自己的权重”除以“所有路径权重的总和”。这和 softmax 的归一化逻辑完全一致，只不过 softmax 是在有限个类别上归一化，而 CRF 是在所有可能的标签序列上归一化。</p>
<p>配分函数这个名字来自统计物理，但在这里不需要物理背景也能理解：它本质上就是一个<span style="background-color: #c0c0c0;">归一化常数</span>。没有它，模型只能说“路径 A 比路径 B 更合理”；有了它，模型才能进一步说“路径 A 的概率是多少”。也正因为 <span displaypfx="inline-" class="mathjax-container">\(Z(\boldsymbol{x})\)</span> 需要把所有可能标签路径都考虑进去，CRF 训练时才必须借助动态规划，而不能暴力枚举。</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(f_k(y_{t-1},y_t,\boldsymbol{x},t)\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个特征函数，描述当前位置、相邻标签和输入之间的某种局部模式。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\lambda_k\)</span>：该特征的权重；越大表示模型越重视这个模式。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span>：固定输入序列；在讨论 <span displaypfx="inline-" class="mathjax-container">\(Z(\boldsymbol{x})\)</span> 时，它不变。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{y}\)</span>：某一条候选标签序列；计算 <span displaypfx="inline-" class="mathjax-container">\(Z(\boldsymbol{x})\)</span> 时要对所有可能的 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{y}\)</span> 求和。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\sum_{t=1}^{T}\sum_k \lambda_k f_k(\cdot)\)</span>：整条标签序列的总得分。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\exp(\cdot)\)</span>：把总得分映射成正权重，并放大高分路径与低分路径之间的差异。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(Z(\boldsymbol{x})\)</span>：所有候选标签路径未归一化权重的总和，用于把相对权重变成概率。</li>
</ul>
<p>训练时最大化条件对数似然，解码时用维特比算法寻找最优标签序列。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>定义特征函数，描述输入与标签、标签与标签之间的关系。</li>
<li>用前向后向算法计算配分函数与梯度。</li>
<li>通过梯度法优化参数 <span displaypfx="inline-" class="mathjax-container">\(\lambda_k\)</span>。</li>
<li>推断时用维特比算法输出最优标签序列。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在命名实体识别（NER）中，局部分类器可能会把某个词单独判成人名，但 CRF 会进一步考虑相邻标签是否合法，从而减少孤立的局部误判。这里“是否合法”指的不是语法合法，而是<span style="background-color: #c0c0c0;">标签序列是否符合该任务定义下允许出现的邻接模式</span>。例如，在常见的 BIO 标注体系中， <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 表示 Begin，即实体开始； <span displaypfx="inline-" class="mathjax-container">\(I\)</span> 表示 Inside，即实体内部； <span displaypfx="inline-" class="mathjax-container">\(O\)</span> 表示 Outside，即不属于任何实体。也有一些任务使用 BIOES 标注体系，其中 <span displaypfx="inline-" class="mathjax-container">\(E\)</span> 表示 End， <span displaypfx="inline-" class="mathjax-container">\(S\)</span> 表示 Single。与 BIO 相比，BIOES 会把实体结束位置和单字实体显式标出来，因此边界信息更细。于是，标签 <span displaypfx="inline-" class="mathjax-container">\(B\text{-}PER\)</span> 表示一个人名实体的开始， <span displaypfx="inline-" class="mathjax-container">\(I\text{-}PER\)</span> 表示该人名实体的内部， <span displaypfx="inline-" class="mathjax-container">\(O\)</span> 表示不属于任何实体。于是， <span displaypfx="inline-" class="mathjax-container">\(B\text{-}PER\rightarrow I\text{-}PER\)</span> 是常见且合法的相邻转移， <span displaypfx="inline-" class="mathjax-container">\(O\rightarrow B\text{-}PER\)</span> 也合法；但 <span displaypfx="inline-" class="mathjax-container">\(O\rightarrow I\text{-}PER\)</span> 通常不合法，因为一个实体内部标签不能无缘无故直接开始，前面必须先有对应的开始标签。同样， <span displaypfx="inline-" class="mathjax-container">\(B\text{-}LOC\rightarrow I\text{-}PER\)</span> 这类“实体类型突然不一致”的连接也通常应被压低分数。</p>
<p>例如，在句子“张三 在 北京 工作”中，若任务要识别人名（PER）和地点（LOC），一个合理的 BIO 标注序列可能是“张三 / 在 / 北京 / 工作”对应 <span displaypfx="inline-" class="mathjax-container">\(B\text{-}PER, O, B\text{-}LOC, O\)</span>。这里首先需要区分两件事：<span style="background-color: #c0c0c0;">某个位置更像哪一种实体类型</span>，以及<span style="background-color: #c0c0c0;">这些局部判断连起来是否构成一条合理的标签序列</span>。前者主要来自输入本身提供的证据，例如“张三”在词形上很像中文人名，“北京”本身强烈像地点名词，而“在 北京 工作”这种上下文也会继续加强“北京是地点”的判断。传统 CRF 会把这些信息写成特征函数，例如“当前词是否常见于人名词表”“当前词是否带有地名后缀”“左邻词是否是介词‘在’”“右邻词是否是动作词‘工作’”等；每个特征都会给某个候选标签加分或减分。</p>
<p>CRF 的作用是在这些局部类型证据之上，再做一次全局一致性的联合解码。换言之，实体类型 A 还是 B，并不是和 CRF 完全无关；但也不是由 CRF 凭空决定的。更准确地说，<span style="background-color: #c0c0c0;">实体类型的语义判断主要来自输入特征，CRF 负责把这些局部判断放到整条序列里统一协调</span>。例如，如果“北京”这个位置单看局部证据时，对 LOC 的分数高于 PER，那么 CRF 会倾向保留“地点”这一判断；但它还会进一步检查，当前位置前后的标签连接是否自然。如果某条候选路径把“张三”切成 <span displaypfx="inline-" class="mathjax-container">\(B\text{-}PER, O\)</span>，或者把“北京”接成 <span displaypfx="inline-" class="mathjax-container">\(B\text{-}PER\rightarrow I\text{-}LOC\)</span>，即使某个局部位置的分数不低，整条路径仍会因为边界断裂或类型转移不一致而被整体压低。于是 CRF 做的不是单点分类，而是“局部类型打分 + 全局路径约束”的联合决策。</p>
<p>从工程实现上看，这个分工在不同年代的模型里表现形式不同。在传统 CRF 中，“局部类型打分”通常来自人工设计的特征模板，例如当前词、前后词、词性、字形、是否出现在人名词典或地名词典中；CRF 再把这些手工特征组合成整条序列的全局分数。在 BiLSTM-CRF 或 BERT-CRF 这类现代模型中，局部证据不再主要依赖手工模板，而是先由 BiLSTM 或 BERT 生成上下文化表示（Contextual Representation），再由线性层给出每个位置对各标签的局部分数，最后仍由 CRF 层负责建模标签转移和整条路径解码。也就是说，上游编码器主要回答“这个位置像什么类型”，CRF 层主要回答“这些位置判断拼在一起是否构成一条最合理的标签序列”。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/crf-1.jpg"><img class="alignnone size-full wp-image-41263" src="https://blog.gmem.cc/wp-content/uploads/2026/03/crf-1.jpg" alt="crf-1" width="1408" height="768" /></a></p>
<p>类似的全局打分思想也可以推广到依存句法分析（Dependency Parsing, DEP）这类结构化任务。句子中的每个词都需要找到自己的中心词（head），模型的目标不是孤立地决定“这个词连向谁”，而是评估整棵依存树是否合理。例如，在“她 喜欢 自然语言处理”中，“喜欢”通常更可能作为中心谓词，“她”依附到“喜欢”形成主谓关系，“自然语言处理”整体依附到“喜欢”形成宾语关系。若某个局部决策把“她”错误地连到“自然语言处理”，单看两个词的局部相似度未必很低，但放到整棵树的全局结构中就会显得不协调。CRF 的价值正体现在这里：它通过全局归一化和结构约束，偏好整体验证一致的输出结构，而不是一组彼此冲突的局部最优决策。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/crf-2.jpg"><img class="alignnone size-full wp-image-41257" src="https://blog.gmem.cc/wp-content/uploads/2026/03/crf-2.jpg" alt="crf-2" width="1408" height="768" /></a></p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：适合结构化输出，能显式编码标签依赖。</li>
<li>局限：训练与推断成本高于普通独立分类器。</li>
<li>适用场景：序列标注、分词、命名实体识别等条件结构化预测任务。</li>
</ul>
<div class="blog_h3"><span class="graybg">结构化感知机（Structured Perceptron）</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>结构化感知机（Structured Perceptron）处理的是：输出本身不是一个单独类别，而是整条序列、整棵树或其他组合结构时，如何直接根据“预测结构与真实结构的差异”更新参数。它可以看作普通感知机从单点分类推广到结构化输出空间后的版本。</p>
<p>它在自然语言处理中长期用于序列标注、分词、依存句法分析等任务，因为这些问题都具有一个共同特征：模型必须在巨大的候选结构空间里找一个最优结构，而不是只在几个类别之间做选择。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>结构化感知机不要求输出概率分布，而是直接定义一个结构打分函数 <span displaypfx="inline-" class="mathjax-container">\(s(x,y)\)</span>，然后让真实结构的分数高于错误结构。若当前样本 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 上预测出的结构 <span displaypfx="inline-" class="mathjax-container">\(\hat y\)</span> 与真实结构 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 不同，就把参数朝“提高真实结构分数、降低错误结构分数”的方向更新。</p>
<p>这种做法的优点是训练目标与推断目标贴得很近。模型训练时关心的不是“校准出多漂亮的概率”，而是“最终解码出来的结构对不对”。因此它属于典型的<span style="background-color: #c0c0c0;">错误驱动（Mistake-driven）结构化学习</span>方法。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>若结构打分函数写成线性形式：</p>
<span displaypfx="" class="mathjax-container">\[s(x,y)=\mathbf{w}^\top \Phi(x,y)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\Phi(x,y)\)</span> 是输入与候选结构联合产生的特征向量，预测时做：</p>
<span displaypfx="" class="mathjax-container">\[\hat y=\arg\max_{y'\in\mathcal{Y}(x)} \mathbf{w}^\top \Phi(x,y')\]</span>
<p>若 <span displaypfx="inline-" class="mathjax-container">\(\hat y\ne y\)</span>，则一次典型更新为：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{w}\leftarrow \mathbf{w}+\Phi(x,y)-\Phi(x,\hat y)\]</span>
<p>这个更新式非常直白：真实结构出现过但预测结构没有出现的特征，会把参数往正方向推；错误结构特有的特征，则会被压低。随着训练进行，模型会逐渐提高正确结构在解码时被选中的概率，尽管它本身并不显式输出概率。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>定义联合特征 <span displaypfx="inline-" class="mathjax-container">\(\Phi(x,y)\)</span> 与结构解码器。</li>
<li>对每个样本解码出当前最优预测结构 <span displaypfx="inline-" class="mathjax-container">\(\hat y\)</span>。</li>
<li>若预测错误，则按真实结构与预测结构的特征差做更新。</li>
<li>重复多轮，并常配合参数平均（Averaged Perceptron）提升泛化稳定性。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在中文分词里，模型可以把一句话的切分结果看作一个结构。若当前参数把“自然 语言 处理”切成了错误边界，而真实答案是“自然语言 处理”，结构化感知机会直接比较这两个切分结构的特征差，把支持真实切分的特征加权抬高，把支持错误切分的特征压低。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：训练简单、更新高效、目标与最终解码结构高度一致。</li>
<li>局限：缺少概率语义，对噪声和解码误差较敏感，训练稳定性通常不如全概率模型。</li>
<li>适用场景：传统分词、句法分析、序列标注等可解码结构任务，或作为理解结构化大间隔学习的入门方法。</li>
</ul>
<div class="blog_h2"><span class="graybg">近邻模型</span></div>
<p>这一类方法处理的核心问题是：在缺少可靠全局函数形式时，是否可以直接依赖“局部相似样本通常有相似输出”这一假设完成预测。近邻模型不急于学习一个显式参数化函数，而是把相似性度量（Similarity Metric）本身作为建模中心：先找邻居，再由邻居投票、平均或加权得到结果。</p>
<div class="blog_h3"><span class="graybg">K 近邻（KNN）</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>K 近邻（K-Nearest Neighbors, KNN）用于分类与回归。给定一个待预测样本 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span>，它要根据训练集中与其最相似的样本来决定输出。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>KNN 的基本假设是局部平滑性（Local Smoothness）：在特征空间中彼此接近的样本，往往具有相近的标签或数值。它不显式学习参数化模型，训练集本身就是局部比较的参照集。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>给定距离函数 <span displaypfx="inline-" class="mathjax-container">\(d(\boldsymbol{x},\boldsymbol{x}')\)</span> 与邻居数 <span displaypfx="inline-" class="mathjax-container">\(K\)</span>，若 <span displaypfx="inline-" class="mathjax-container">\(N_K(\boldsymbol{x})\)</span> 表示 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 的 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个最近邻，则分类时可写为：</p>
<span displaypfx="" class="mathjax-container">\[\hat y=\mathrm{mode}\left(\{y_i:(\boldsymbol{x}_i,y_i)\in N_K(\boldsymbol{x})\}\right)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(N_K(\boldsymbol{x})\)</span> 是样本 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 的 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个最近邻集合，花括号中收集的是这些邻居的标签， <span displaypfx="inline-" class="mathjax-container">\(\mathrm{mode}(\cdot)\)</span> 则返回出现次数最多的类别。也就是说，KNN 分类本质上就是“看最近的邻居们大多数是谁”。</p>
<p>回归时常取邻居标签平均：</p>
<span displaypfx="" class="mathjax-container">\[\hat y=\frac{1}{K}\sum_{(\boldsymbol{x}_i,y_i)\in N_K(\boldsymbol{x})}y_i\]</span>
<p>回归版 KNN 只是把“多数投票”换成“数值平均”。 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{K}\sum\)</span> 表示把最近 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个邻居的输出做均值，因此预测值会受到局部邻域中所有数值样本的共同影响。</p>
<p>若距离使用欧氏距离，则默认所有特征尺度可比，因此标准化（Standardization）通常是必要前处理。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>训练阶段几乎不做参数学习，只保存全部训练样本。</li>
<li>推断时计算待测样本到训练样本的距离。</li>
<li>选出最近的 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个样本。</li>
<li>分类取多数投票，回归取平均或距离加权平均。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在简单的手写数字识别中，如果一张新图片与训练集中大量“3”的图像都很接近，而与“8”的图像明显更远，那么 KNN 会依据局部邻域投票把它判为 3。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：概念简单、无需复杂训练、局部非线性表达能力强。</li>
<li>局限：推断成本高；对特征尺度和无关特征敏感；维度灾难会削弱距离判别力。</li>
<li>适用场景：中小规模数据、快速基线、基于相似度的简单分类与回归。</li>
</ul>
<div class="blog_h2"><span class="graybg">聚类</span></div>
<p>聚类处理的核心问题是：在没有标签的前提下，如何仅根据样本之间的几何关系、密度结构或层次关系，把数据自动分成若干组。这里不存在唯一正确的“簇”定义：K-Means 假设簇围绕中心分布，层次聚类强调多粒度组织结构，DBSCAN / HDBSCAN 则把簇理解为高密度连通区域。因此，聚类算法的选择本质上是在选择“什么样的结构应被视为同一类”。</p>
<div class="blog_h3"><span class="graybg">K-Means</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>K-Means 处理的是无监督聚类问题：给定一组没有标签的样本，希望把它们分成 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个簇，使同一簇内样本尽量接近，不同簇之间尽量分开。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>K-Means 用“每个簇由一个中心点代表”的方式近似数据分布。算法不断重复两件事：把样本分配给最近的中心；再用簇内样本均值更新中心。它本质上是在最小化簇内平方误差。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/k-means.jpg"><img class="alignnone size-full wp-image-41287" src="https://blog.gmem.cc/wp-content/uploads/2026/03/k-means.jpg" alt="k-means" width="1024" height="559" /></a></p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>目标函数为：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\{c_k\},\{z_i\}} \sum_{i=1}^{N} \|\boldsymbol{x}_i-c_{z_i}\|_2^2\]</span>
<p>这个目标里， <span displaypfx="inline-" class="mathjax-container">\(c_k\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个簇中心， <span displaypfx="inline-" class="mathjax-container">\(z_i\)</span> 表示样本 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 被分到哪个簇， <span displaypfx="inline-" class="mathjax-container">\(\|\boldsymbol{x}_i-c_{z_i}\|_2^2\)</span> 是样本到所属簇中心的平方距离。K-Means 想做的，就是让所有样本离各自中心都尽可能近。</p>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(c_k\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个簇中心， <span displaypfx="inline-" class="mathjax-container">\(z_i\in\{1,\dots,K\}\)</span> 表示样本 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}_i\)</span> 属于哪个簇。固定簇分配时，最优中心是该簇样本均值：</p>
<span displaypfx="" class="mathjax-container">\[c_k=\frac{1}{|S_k|}\sum_{\boldsymbol{x}_i\in S_k} \boldsymbol{x}_i\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(S_k\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个簇里所有样本的集合， <span displaypfx="inline-" class="mathjax-container">\(|S_k|\)</span> 是该簇样本数。这个更新式说明簇中心就是簇内样本的算术平均，因此 K-Means 的“中心”确实是均值意义上的代表点。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>初始化 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个簇中心。</li>
<li>分配步骤：把每个样本分到最近中心。</li>
<li>更新步骤：用各簇样本均值更新中心。</li>
<li>重复迭代直到簇分配稳定或目标下降很小。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在用户分群中，若特征是消费频次与客单价，K-Means 往往会自动形成“高频低客单”“低频高客单”“中频中客单”等若干均值中心明确的群体。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：实现简单、可扩展、训练速度快。</li>
<li>局限：需要预先指定 <span displaypfx="inline-" class="mathjax-container">\(K\)</span>；对初始化与离群点敏感；不适合非凸簇。</li>
<li>适用场景：簇近似球形、需要快速聚类或作为预处理的任务。</li>
</ul>
<div class="blog_h3"><span class="graybg">层次聚类</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>层次聚类（Hierarchical Clustering）输出的是一个由粗到细的聚类层次结构，因此簇数可以在观察树状图后再决定。它适合需要观察“簇是如何逐步合并或拆分”的任务。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>凝聚式层次聚类（Agglomerative Clustering）从每个样本单独成簇开始，每一步合并当前最相近的两个簇；分裂式层次聚类则从一个大簇开始不断拆分。实践中更常见的是凝聚式版本。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/h-clustering.jpg"><img class="alignnone size-full wp-image-41293" src="https://blog.gmem.cc/wp-content/uploads/2026/03/h-clustering.jpg" alt="h-clustering" width="1024" height="559" /></a></p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>关键在于定义簇间距离。常见联接准则（Linkage Criteria）包括：</p>
<span displaypfx="" class="mathjax-container">\[d_{\text{single}}(A,B)=\min_{\boldsymbol{x}\in A,\boldsymbol{y}\in B} d(\boldsymbol{x},\boldsymbol{y})\]</span>
<p>单链距离只看两簇之间最近的那一对点，因此它很容易把“靠得最近的局部桥梁”连起来，适合发现链式结构，但也更容易被噪声点串联。</p>
<span displaypfx="" class="mathjax-container">\[d_{\text{complete}}(A,B)=\max_{\boldsymbol{x}\in A,\boldsymbol{y}\in B} d(\boldsymbol{x},\boldsymbol{y})\]</span>
<p>完全链距离只看两簇之间最远的那一对点，因此它会避免把跨度过大的簇合并到一起，更偏好紧凑、直径较小的簇。</p>
<span displaypfx="" class="mathjax-container">\[d_{\text{average}}(A,B)=\frac{1}{|A||B|}\sum_{\boldsymbol{x}\in A,\boldsymbol{y}\in B} d(\boldsymbol{x},\boldsymbol{y})\]</span>
<p>平均链则对两簇之间所有点对的距离取平均。这里 <span displaypfx="inline-" class="mathjax-container">\(|A||B|\)</span> 是点对总数，因此它不只看最近点或最远点，而是综合考虑两簇整体的平均接近程度。</p>
<p>不同联接方式对应不同簇形偏好：单链更容易形成链式簇，完全链更偏向紧凑簇，平均链则居中。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>初始化每个样本为单独一个簇。</li>
<li>计算簇间距离矩阵。</li>
<li>反复合并距离最近的两个簇，并更新距离矩阵。</li>
<li>得到树状图（Dendrogram）后，在某个高度切开即可获得聚类结果。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在文档聚类中，层次聚类不仅能区分“体育”“财经”“科技”等大类，还能进一步展示每一大类内部的细粒度层级关系。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：无需预先指定簇数；可以输出多层次聚类结构。</li>
<li>局限：计算与存储成本较高；早期合并错误通常无法回退。</li>
<li>适用场景：中小规模数据、需要树状关系解释的聚类分析。</li>
</ul>
<div class="blog_h3"><span class="graybg">DBSCAN</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>DBSCAN（Density-Based Spatial Clustering of Applications with Noise）用于识别任意形状的高密度簇，并显式发现噪声点。它尤其适合处理非球形簇与含离群点数据。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>DBSCAN 通过局部密度定义簇。若一个点周围半径 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 内有足够多的邻居，则它是核心点（Core Point）；核心点可以把周围密度可达（Density-Reachable）的样本不断扩展成一个簇。既不够密、又不属于任何核心点邻域的样本被视为噪声。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/dbscan.jpg"><img class="alignnone size-full wp-image-41305" src="https://blog.gmem.cc/wp-content/uploads/2026/03/dbscan.jpg" alt="dbscan" width="1024" height="559" /></a></p>
<p>&nbsp;</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>记 <span displaypfx="inline-" class="mathjax-container">\(N_{\epsilon}(\boldsymbol{x})\)</span> 为点 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 的 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 邻域。若：</p>
<span displaypfx="" class="mathjax-container">\[|N_{\epsilon}(\boldsymbol{x})|\ge \text{minPts}\]</span>
<p>判断核心点只需看两件事： <span displaypfx="inline-" class="mathjax-container">\(N_{\epsilon}(\boldsymbol{x})\)</span> 是点 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 在半径 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 内的邻域集合， <span displaypfx="inline-" class="mathjax-container">\(|N_{\epsilon}(\boldsymbol{x})|\)</span> 是邻域里有多少点。只要这个数量不小于 <span displaypfx="inline-" class="mathjax-container">\(\text{minPts}\)</span>，就说明该点周围密度足够高，可以作为簇扩张的核心。</p>
<p>则 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 是核心点。若某点落在某个核心点邻域中但自身并非核心点，则为边界点（Border Point）；不属于任何簇的点为噪声（Noise）。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>遍历未访问样本，计算其 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 邻域。</li>
<li>若邻居数少于 <span displaypfx="inline-" class="mathjax-container">\(\text{minPts}\)</span>，则暂记为噪声或边界候选。</li>
<li>若为核心点，则以它为起点递归扩展所有密度可达的点。</li>
<li>重复直到所有样本都被标记。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在地理位置聚类中，餐馆、商圈、交通枢纽附近的点位通常形成形状复杂的密集区域。DBSCAN 可以识别这些非凸热点，同时把零散孤立点保留为噪声。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：无需预设簇数；可识别任意形状簇；对离群点鲁棒。</li>
<li>局限：对 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(\text{minPts}\)</span> 敏感；难以同时适配不同密度簇。</li>
<li>适用场景：空间聚类、热点区域发现、非凸簇与带噪声数据。</li>
</ul>
<div class="blog_h3"><span class="graybg">HDBSCAN</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>HDBSCAN（Hierarchical DBSCAN）用于缓解 DBSCAN 的单一密度阈值问题。它面对的核心困难是：真实数据中的簇密度常常并不一致，用一组固定的 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(\text{minPts}\)</span> 很难同时兼顾所有簇。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>HDBSCAN 会在多个密度尺度上构建层次结构，再从中选出稳定簇（Stable Clusters）作为结果。这让它比 DBSCAN 更适合处理密度差异显著的数据。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/hdbscan-intuition.png"><img class="alignnone size-full wp-image-41327" src="https://blog.gmem.cc/wp-content/uploads/2026/03/hdbscan-intuition.png" alt="hdbscan-intuition" width="3045" height="12714" /></a></p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>给定参数 <span displaypfx="inline-" class="mathjax-container">\(k\)</span>，先定义核心距离（Core Distance）为点到其第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 近邻的距离，再定义互可达距离（Mutual Reachability Distance）：</p>
<span displaypfx="" class="mathjax-container">\[d_{\text{mreach},k}(\boldsymbol{x},\boldsymbol{y})=\max\big(\text{core}_k(\boldsymbol{x}),\text{core}_k(\boldsymbol{y}),d(\boldsymbol{x},\boldsymbol{y})\big)\]</span>
<p>互可达距离把三个量取最大值：点 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 的核心距离、点 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{y}\)</span> 的核心距离，以及它们之间的原始距离。这样做的效果是：低密度区域的点会被“拉远”，从而更清楚地暴露不同密度簇之间的结构边界。</p>
<p>随后算法在互可达图上构建最小生成树（Minimum Spanning Tree），再转成聚类层次，并依据簇稳定性选择最终输出。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>计算所有点的核心距离。</li>
<li>基于互可达距离构造图并求最小生成树。</li>
<li>从图中得到随密度变化的层次聚类结构。</li>
<li>在压缩树（Condensed Tree）上选择稳定簇作为结果。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在用户行为嵌入空间中，有些兴趣群体很紧密，有些较松散。HDBSCAN 可以同时保留这两类簇，而不需要强行用同一个密度阈值描述它们。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：对多密度簇更稳健，仍保留噪声识别能力。</li>
<li>局限：实现更复杂，计算与内存开销通常高于 DBSCAN。</li>
<li>适用场景：嵌入聚类、用户分群、不同密度簇共存的数据。</li>
</ul>
<div class="blog_h3"><span class="graybg">基于图的聚类与 Leiden</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>Leiden 适合这样一类聚类问题：样本之间的关系，与其说是“在欧氏空间里围着某个中心聚成一团”，不如说是“先形成一张相似关系图，再在图上出现若干连接更紧密的社区（Community）”。因此，Leiden 的标准语境不是直接对原始点云做切块，而是先得到图 <span displaypfx="inline-" class="mathjax-container">\(G=(V,E)\)</span>，再把图上的节点划分成若干社区。</p>
<p>这类方法本质上属于<span style="background-color: #c0c0c0;">基于图的聚类（Graph-based Clustering）或社区发现（Community Detection）</span>。社交网络里的“朋友圈”、引文网络里的“研究主题群”、单细胞分析中的细胞群、句向量或图像嵌入上的 kNN 图分群，都属于这一类任务。与 K-Means 假设“簇围绕均值中心分布”不同，Leiden 假设“簇对应图上内部连边更密、外部连边更稀的社区结构”。</p>
<p>从方法谱系上看，图聚类至少有三条常见路线。第一条是谱聚类（Spectral Clustering），通过图拉普拉斯矩阵的特征向量把节点映射到一个新空间，再做传统聚类；第二条是社区质量优化路线，通过优化 modularity 或 CPM 之类的目标函数直接切分图，Louvain 和 Leiden 就属于这一路线；第三条是图神经网络后的下游分群，即先学图表示，再在表示或近邻图上用 Leiden、K-Means 等算法做分群。Leiden 因此不是 GNN 本体，而是一个更基础的图聚类算法。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>Leiden 的核心思想可以压缩成一句话：<span style="background-color: #c0c0c0;">不要直接看点到中心的距离，而要看节点在图中是否形成内部紧密、外部稀疏、并且社区内部真正连通的结构</span>。它通常从一个已有分区出发，不断尝试把节点移动到更合适的社区，使社区质量目标持续上升。</p>
<p>如果只停在这里，Leiden 和 Louvain 看起来会很像。两者确实都属于“局部移动 + 社区聚合”的贪心优化路线，但 Leiden 多做了一步关键的<span style="background-color: #c0c0c0;">refinement（细化）</span>：它会在局部改进之后进一步检查社区内部是否真的连得足够好，避免出现“从目标函数上看像一个社区，但内部其实由几个弱连接甚至不连通子块拼出来”的结果。这正是 Leiden 相比 Louvain 最重要的改进。</p>
<div class="blog_h4"><span class="graybg">目标函数和详细解释</span></div>
<p>Leiden 常优化的目标包括 modularity（模块度）和 CPM（Constant Potts Model）。为了把原理讲清楚，先用最常见的 modularity 写法说明。设图的加权邻接矩阵为 <span displaypfx="inline-" class="mathjax-container">\(A_{ij}\)</span>，节点 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 的度为 <span displaypfx="inline-" class="mathjax-container">\(k_i=\sum_j A_{ij}\)</span>，图的总边权满足 <span displaypfx="inline-" class="mathjax-container">\(2m=\sum_i k_i=\sum_{i,j}A_{ij}\)</span>。若 <span displaypfx="inline-" class="mathjax-container">\(c_i\)</span> 表示节点 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 所在社区，则模块度可写为：</p>
<span displaypfx="" class="mathjax-container">\[Q=\frac{1}{2m}\sum_{i,j}\left(A_{ij}-\gamma\frac{k_i k_j}{2m}\right)\mathbf{1}[c_i=c_j]\]</span>
<p>这个式子里， <span displaypfx="inline-" class="mathjax-container">\(A_{ij}\)</span> 是节点 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 之间真实存在的边权； <span displaypfx="inline-" class="mathjax-container">\(\frac{k_i k_j}{2m}\)</span> 是一个随机零模型（Null Model）下“如果只保留节点度大小，随机连边时它们本应有多大连接强度”； <span displaypfx="inline-" class="mathjax-container">\(\mathbf{1}[c_i=c_j]\)</span> 是指示函数，表示只有当两个节点被分进同一社区时，这一对节点才会对目标函数产生贡献； <span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span> 是分辨率参数（Resolution Parameter），控制社区切得更粗还是更细。</p>
<p>因此，模块度优化本质上在回答这样一个问题：<span style="background-color: #c0c0c0;">如果把一组节点放进同一个社区，那么它们之间的真实连接强度，是否显著高于“只是因为这些节点度数大，所以随机也容易连上”的基线</span>。若答案是肯定的，把它们归到同一社区就能提高 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span>；若答案是否定的，说明这种合并只是表面热闹，并不能带来真正的社区结构收益。</p>
<p>这也是为什么 Leiden 适合处理 kNN 图、相似度图和社交图。它衡量的不是“几何中心在哪里”，而是“哪些节点在统计意义上比随机期望更紧密地相互连在一起”。如果社区内部真实连边明显超出基线，目标函数就会上升；反之，跨社区连边过多或社区内部稀薄，目标函数就会下降。</p>
<p>在一些场景里，人们更偏好 CPM，因为 modularity 存在分辨率限制（Resolution Limit）：小社区可能在大图里被吞掉。CPM 的思想是直接给同社区节点对设置固定惩罚阈值，以更直接地控制社区粒度。实践里，Leiden 常允许在 modularity 和 CPM 之间切换，但二者共享同一个高层逻辑：<span style="background-color: #c0c0c0;">把图划分成若干“内部连接收益高、外部混杂成本低”的社区</span>。</p>
<div class="blog_h4"><span class="graybg">类比理解</span></div>
<p>可以把 Leiden 想成“给城市划街区”。如果只按地图上的直线距离切分，很可能会把一条大河两岸、一个高架桥两侧、或被工业区隔开的居民区误划到一起，因为它们几何上靠得近。但如果先把城市道路、步行通路、地铁换乘、商业往来都画成图，再看哪些区域内部来往频繁、对外联系相对稀少，划出来的“街区”就更接近真实城市结构。</p>
<p>图聚类处理高维嵌入时也是同样道理。点在原空间中也许并不围绕单一中心分布，但只要它们在近邻图中高度互联，就仍然可能属于同一个社区。Leiden 做的正是这件事：它不是问“中心在哪”，而是在问“谁和谁构成了一个内部往来密切、外部联系较弱的关系团块”。</p>
<div class="blog_h4"><span class="graybg">算法流程</span></div>
<ol>
<li>先构图。若输入本身就是图，可直接使用；若输入是向量样本，通常先基于欧氏距离、余弦相似度或共享近邻构造 kNN 图，并给边赋相似权重。</li>
<li>初始化分区。最常见的起点是每个节点各自形成一个单独社区。</li>
<li>局部移动（Local Moving）。依次尝试把一个节点移动到邻居社区，只要目标函数 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 能提升，就接受该移动。</li>
<li>细化（Refinement）。对当前社区内部再做检查和重组，避免把内部连接脆弱、甚至不连通的部分勉强保留在同一社区中。</li>
<li>聚合（Aggregation）。把当前每个社区收缩成一个超节点（Supernode），构成更粗粒度的新图。</li>
<li>重复以上过程，直到目标函数不再显著提升，社区结构稳定为止。</li>
</ol>
<p>这套流程里最重要的不是“反复合并”，而是“<span style="background-color: #c0c0c0;">局部改进之后要进一步验证社区内部是否真的足够连通</span>”。Louvain 的问题恰好出在这一步缺失：它可以得到目标函数不错、但内部连通性并不理想的社区。Leiden 则通过 refinement 把这个结构性缺陷补上。</p>
<div class="blog_h4"><span class="graybg">为什么它通常比 Louvain 更可靠</span></div>
<p>Louvain 的经典优点是快，但它有一个著名弱点：社区优化过程只关心目标函数是否上升，不额外保证社区内部强连通或良好连通。因此，一个社区可能只是因为若干桥接边被贪心合并到一起，结果内部出现细长链条、弱耦合子块，甚至在某些定义下并不连通。</p>
<p>Leiden 通过 refinement 改变了这一点。它不满足于“这个合并在目标函数上看起来有利”，而要求社区内部结构也达到更合理的连通性。于是，在很多真实图上，Leiden 会比 Louvain 给出<span style="background-color: #c0c0c0;">更稳定、更细致、也更符合局部结构直觉</span>的划分。这也是为什么在单细胞分析、图嵌入分群和网络社区发现里，Leiden 已经大幅替代 Louvain 成为默认选择。</p>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在单细胞 RNA 测序中，常见流程是先把细胞表达矩阵降维到 PCA 或潜在嵌入空间，再构造 kNN 图，最后用 Leiden 分群。这里簇的含义不是“围绕某个均值中心的一团球”，而是“在细胞近邻图中形成内部高度互联的一群细胞”。这正符合细胞状态连续变化、局部流形结构明显的特点。</p>
<p>在文本或图像嵌入聚类中，也经常先用编码模型得到向量，再构造近邻图，然后用 Leiden 做社区发现。与直接在嵌入空间上跑 K-Means 相比，这条路线往往更能保留局部语义结构，尤其适合簇形复杂、边界弯曲、局部连通性比全局中心更重要的场景。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：能直接利用图结构；适合非球形、非凸簇；通常比 Louvain 更稳定，社区内部结构也更合理。</li>
<li>局限：结果依赖构图质量；分辨率参数和近邻图参数会显著影响簇粒度；不如 K-Means 那样易于用“均值中心”解释。</li>
<li>适用场景：单细胞分群、社交网络社区发现、图嵌入或句向量的近邻图聚类、推荐或用户画像中的相似图分群。</li>
</ul>
<div class="blog_h2"><span class="graybg">升维</span></div>
<p>升维（Feature Expansion / Lifting）处理的问题，与降维正好互补。降维试图把高维表示压缩到更低维空间，以减少冗余、噪声与计算量；升维则试图把原始特征映射到一个更高维的表示空间，使原本难以表达、难以分离或难以拟合的结构，在新空间里变得更容易处理。它的目标不是“把维度变大本身”，而是<span style="background-color: #c0c0c0;">通过增加表示自由度，把非线性关系改写为更容易由简单模型处理的形式</span>。</p>
<div class="blog_h3"><span class="graybg">背景和问题定义</span></div>
<p>许多经典机器学习模型本体是线性的，例如线性回归、逻辑回归、线性支持向量机（Support Vector Machine, SVM）。它们直接在原始输入空间中学习一个线性决策函数或线性预测函数。若数据关系本身高度非线性，那么模型能力可能不足。升维的思路因此是：先把输入映射为一个更高维的新表示，再在新表示上使用线性模型。模型形式仍然简单，但由于工作空间改变了，整体表达能力会显著提升。</p>
<p>更一般地，若原始输入为 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\in \mathbb{R}^d\)</span>，则升维映射可写成：</p>
<span displaypfx="" class="mathjax-container">\[\tilde{\boldsymbol{x}}=\phi(\boldsymbol{x}),\qquad \phi: \mathbb{R}^d\to \mathbb{R}^D,\qquad D&gt;d\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 是原始输入； <span displaypfx="inline-" class="mathjax-container">\(\tilde{\boldsymbol{x}}\)</span> 是升维后的新特征； <span displaypfx="inline-" class="mathjax-container">\(\phi(\boldsymbol{x})\)</span> 是特征映射； <span displaypfx="inline-" class="mathjax-container">\(D&gt;d\)</span> 表示新的表示空间维度高于原空间维度。若后续模型写成 <span displaypfx="inline-" class="mathjax-container">\(f(\boldsymbol{x})=\boldsymbol{w}^\top \tilde{\boldsymbol{x}}+b\)</span>，那么它虽然在新空间中仍然是线性的，但在原始输入 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 上通常已经对应一个非线性函数。</p>
<div class="blog_h3"><span class="graybg">核心思想</span></div>
<p>升维的直观含义，是把原来“纠缠在一起”的关系展开。例如在二维平面里，某些数据可能无法被一条直线分开；但若把 <span displaypfx="inline-" class="mathjax-container">\((x_1,x_2)\)</span> 映射成 <span displaypfx="inline-" class="mathjax-container">\((x_1,x_2,x_1^2,x_2^2,x_1x_2)\)</span> 这样的更高维特征，原本的弯曲边界就可能对应高维空间中的一个超平面。于是复杂性并没有消失，而是被转移到了特征映射 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\boldsymbol{x}}=\phi(\boldsymbol{x})\)</span> 上。</p>
<p>这也是经典机器学习里一个非常常见的套路：<span style="background-color: #c0c0c0;">先做特征构造或特征展开，再用结构简单、优化稳定的线性模型</span>。因此，升维常常并不以“升维”这个名字出现，而是以多项式特征、基函数展开、核方法、one-hot 编码、N-gram 稀疏特征、随机特征等形式出现。</p>
<div class="blog_h3"><span class="graybg">公式和详细解释</span></div>
<p>升维后的线性模型通常写成：</p>
<span displaypfx="" class="mathjax-container">\[f(\boldsymbol{x})=\boldsymbol{w}^\top \tilde{\boldsymbol{x}}+b\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\boldsymbol{x}}\)</span> 是由原始输入 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 构造出来的高维特征向量； <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{w}\in \mathbb{R}^D\)</span> 是新空间中的参数； <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 是偏置。关键点在于：模型在 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\boldsymbol{x}}\)</span> 空间里仍然是线性的，但如果 <span displaypfx="inline-" class="mathjax-container">\(\phi(\boldsymbol{x})\)</span> 含有平方项、交叉项、基函数或核映射，那么 <span displaypfx="inline-" class="mathjax-container">\(f(\boldsymbol{x})\)</span> 相对于原始输入 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 就会表现出非线性。</p>
<p>以二次多项式特征为例，若原始输入为 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}=(x_1,x_2)\)</span>，则可构造：</p>
<span displaypfx="" class="mathjax-container">\[\tilde{\boldsymbol{x}}=(x_1,x_2,x_1^2,x_2^2,x_1x_2)\]</span>
<p>此时线性模型</p>
<span displaypfx="" class="mathjax-container">\[f(\boldsymbol{x})=w_1x_1+w_2x_2+w_3x_1^2+w_4x_2^2+w_5x_1x_2+b\]</span>
<p>在参数上仍然是线性的，但在输入上已经能够表达二次曲面或二次决策边界。这正是升维最核心的数学作用：<span style="background-color: #c0c0c0;">把原空间中的非线性关系，改写成高维空间中的线性关系</span>。</p>
<div class="blog_h3"><span class="graybg">常见升维方式</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">方式</td>
<td style="text-align: center;">升维形式</td>
<td style="text-align: center;">核心作用</td>
<td style="text-align: center;">典型场景</td>
</tr>
</thead>
<tbody>
<tr>
<td>多项式特征</td>
<td>加入平方项、立方项、交叉项</td>
<td>把低阶线性模型扩展为可拟合非线性关系</td>
<td>线性回归、逻辑回归、线性分类器</td>
</tr>
<tr>
<td>基函数展开</td>
<td>高斯基、样条基、傅里叶基等</td>
<td>用一组预定义函数把局部或周期结构显式展开</td>
<td>回归、广义加性模型、核近似</td>
</tr>
<tr>
<td>核方法</td>
<td>通过核函数隐式映射到高维甚至无限维空间</td>
<td>在不显式展开特征的情况下提升可分性</td>
<td>SVM、核岭回归、核 PCA</td>
</tr>
<tr>
<td>One-hot 编码</td>
<td>把离散类别映射为高维稀疏向量</td>
<td>让类别变量进入线性模型并保持类别独立性</td>
<td>表格特征、推荐、广告、点击率预估</td>
</tr>
<tr>
<td>N-gram / 词袋</td>
<td>把文本映射为高维稀疏词项空间</td>
<td>显式展开局部共现与组合模式</td>
<td>传统文本分类、检索、朴素贝叶斯、线性 SVM</td>
</tr>
<tr>
<td>随机特征</td>
<td>用随机映射近似某些核空间</td>
<td>在显式高维表示与核方法之间做折中</td>
<td>Random Fourier Features、核近似</td>
</tr>
</tbody>
</table>
<p>其中，核方法是经典机器学习里最有代表性的升维思想。以 SVM 为例，若定义特征映射 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\boldsymbol{x}}=\phi(\boldsymbol{x})\)</span>，则线性分类器可写成 <span displaypfx="inline-" class="mathjax-container">\(f(\boldsymbol{x})=\boldsymbol{w}^\top \tilde{\boldsymbol{x}}+b\)</span>。核技巧（Kernel Trick）并不显式构造 <span displaypfx="inline-" class="mathjax-container">\(\tilde{\boldsymbol{x}}\)</span>，而是直接通过核函数</p>
<span displaypfx="" class="mathjax-container">\[K(\boldsymbol{x},\boldsymbol{x}')=\phi(\boldsymbol{x})^\top \phi(\boldsymbol{x}')\]</span>
<p>计算升维后特征空间中的内积。这样既保留了高维映射的表达力，又避免了显式展开到巨大维度的代价。也正因为如此，SVM 是很多人最先感受到“升维威力”的经典模型。</p>
<div class="blog_h3"><span class="graybg">应用实例</span></div>
<p>在圆形可分但线性不可分的数据中，原始二维平面上一条直线无法把内圈与外圈分开；但若加入半径相关的二次特征，例如 <span displaypfx="inline-" class="mathjax-container">\(x_1^2+x_2^2\)</span>，则问题可以转写为更高维空间中的线性分离。文本任务里，N-gram 稀疏特征同样是一种典型升维：原始句子是离散序列，经过词袋或 N-gram 展开后，就变成了数万维甚至更高维的稀疏向量，随后再交给逻辑回归、朴素贝叶斯或线性 SVM 处理。</p>
<p>表格任务中的类别变量处理也体现了同样思想。一个城市字段看起来只是一个离散取值，但 one-hot 编码后，它会被展开成一个高维稀疏向量，使模型能够为每个类别学习独立参数。推荐系统、广告点击率预估、工业风控中的大规模离散特征，长期都高度依赖这种升维方式。</p>
<div class="blog_h3"><span class="graybg">为什么经典机器学习更谨慎地升维</span></div>
<p>经典机器学习也会使用升维，但通常更谨慎。原因在于，维度一旦上去，过拟合风险、存储开销与计算开销都会迅速增加，这就是维度灾难（Curse of Dimensionality）的典型体现。于是经典方法常把“升维”与“控制复杂度”配套使用：一边通过特征展开提升表达能力，一边用正则化（Regularization）、特征选择（Feature Selection）或后续降维来抑制过拟合。</p>
<p>因此，在经典机器学习里，常见工作流并不是“只升维”或“只降维”，而是两者交替配合：先做有针对性的特征展开，让结构变得更容易表达；再通过正则化、筛选或压缩保留真正有效的部分。升维是在展开表达能力，降维是在压缩冗余信息，它们并不是对立操作，而是围绕表示空间做的两种互补控制。</p>
<div class="blog_h3"><span class="graybg">维度灾难</span></div>
<p>维度灾难（Curse of Dimensionality）指的是：当特征维度不断升高时，许多在低维空间中直观、有效的统计与几何规律会迅速恶化，导致数据需求、计算成本与建模难度同时上升。它不是某一个单独问题，而是一组高维效应的统称，包括样本空间体积指数级膨胀、样本变得极其稀疏、局部邻域难以稳定估计、距离与密度统计的判别力下降，以及模型更容易用复杂边界去记忆训练集。</p>
<p>其中一个最重要的现象确实是：<span style="background-color: #c0c0c0;">高维里距离往往会变得不再像低维那样有区分力</span>。直观地说，当维度很多时，样本之间的最近距离和最远距离可能越来越接近，导致“谁是真正近邻”这件事变得没那么清晰。依赖距离或局部邻域的方法，例如 KNN、聚类、核密度估计、局部异常检测等，往往会因此退化。这也是为什么高维数据上，距离度量、标准化、特征筛选和嵌入学习会变得格外重要。</p>
<p>但“高维更容易过拟合”并不只因为距离失去意义。更根本的原因是：当维度升高后，表示空间的自由度与可容纳的划分方式急剧增加，而训练样本相对于整个空间显得越来越稀疏。模型于是更容易找到一些只对训练集成立、却不能推广到新样本的偶然边界或偶然相关性。换句话说，<span style="background-color: #c0c0c0;">距离退化主要伤害的是邻域、相似度与密度估计；过拟合风险上升则更直接地来自空间稀疏化、参数自由度增加和有效样本覆盖不足</span>。</p>
<p>这也是为什么经典机器学习在做升维时往往必须同步引入约束：一方面用特征展开提升表达能力，另一方面通过正则化（Regularization）、特征选择（Feature Selection）、降维（Dimensionality Reduction）或更强的数据先验，限制模型不要把高维空间当成“背题空间”。因此，维度灾难并不是在说“高维一定不好”，而是在提醒：<span style="background-color: #c0c0c0;">维度每增加一层，模型就需要更多数据、更强归纳偏置和更谨慎的复杂度控制，才能让新增维度真正转化为有效表达能力</span>。</p>
<div class="blog_h3"><span class="graybg">和深度学习中的升维关系</span></div>
<p>深度学习中的很多操作，本质上也在做升维。Transformer 的前馈网络（Feed-Forward Network, FFN / MLP）常把 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span> 升到更大的 <span displaypfx="inline-" class="mathjax-container">\(d_{\text{ff}}\)</span>，再降回原维度；这与经典机器学习里“先展开、再用简单变换处理”的思想是一脉相承的。差别在于，经典方法里的升维往往是人工设计或固定形式的，而深度学习中的升维通常是可学习的线性投影与非线性组合。</p>
<p>因此，若从表示学习角度看，升维在机器学习中并不罕见。SVM 的核空间、逻辑回归的多项式特征、文本的 N-gram 稀疏向量、推荐系统的 one-hot 离散展开，以及 Transformer MLP 中的中间维度扩张，本质上都属于同一条主线：<span style="background-color: #c0c0c0;">把原本难以处理的关系，展开到一个更容易表达与分离的表示空间里</span>。</p>
<div class="blog_h2"><span class="graybg">降维</span></div>
<p>降维处理的核心问题是：高维数据往往包含冗余、相关性与噪声，既增加计算成本，也削弱可视化与建模稳定性。目标不是简单“删维度”，而是在压缩表示的同时尽量保留有用结构——这个“有用”可以是方差、类别可分性、局部邻域、全局流形，具体取决于所采用的方法。</p>
<div class="blog_h3"><span class="graybg">主成分分析（PCA）</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>主成分分析（Principal Component Analysis, PCA）处理的是线性降维问题：在尽量保留数据主要变化信息的前提下，把高维样本映射到更低维空间。它常用于压缩维度、去相关、可视化与噪声抑制。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/pca.jpg"><img class="alignnone size-full wp-image-41347" src="https://blog.gmem.cc/wp-content/uploads/2026/03/pca.jpg" alt="pca" width="1024" height="559" /></a></p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>PCA 用方差（Variance）近似衡量信息量。若数据在某个方向上的投影变化很大，说明该方向承载了更多结构；于是 PCA 选择能最大化投影方差的一组正交方向作为新的表示基底。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>给定中心化后的数据矩阵 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{X}_c\in\mathbb{R}^{N\times d}\)</span>，协方差矩阵为：</p>
<span displaypfx="" class="mathjax-container">\[\boldsymbol{\Sigma}=\frac{1}{N}\boldsymbol{X}_c^\top \boldsymbol{X}_c\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{X}_c\)</span> 是已经减去均值后的数据矩阵， <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{X}_c^\top \boldsymbol{X}_c\)</span> 汇总了各个特征之间如何共同变化；再除以 <span displaypfx="inline-" class="mathjax-container">\(N\)</span>，就得到平均意义下的协方差矩阵 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\Sigma}\)</span>。PCA 正是从这个矩阵里找“变化最大的方向”。</p>
<p>第一主成分对应的优化问题为：</p>
<span displaypfx="" class="mathjax-container">\[\max_{\boldsymbol{u}} \quad \boldsymbol{u}^\top \boldsymbol{\Sigma}\boldsymbol{u} \quad \text{s.t.} \quad \|\boldsymbol{u}\|_2=1\]</span>
<p><span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{u}\)</span> 是候选投影方向， <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{u}^\top \boldsymbol{\Sigma}\boldsymbol{u}\)</span> 表示数据投影到该方向后的方差，约束 <span displaypfx="inline-" class="mathjax-container">\(\|\boldsymbol{u}\|_2=1\)</span> 则防止通过把向量无限放大来虚增方差。于是这个优化问题真正寻找的是“单位长度下最能保留变化信息的方向”。</p>
<p>其解是协方差矩阵最大特征值对应的特征向量。取前 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个主成分组成矩阵 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{U}_k\)</span> 后，样本 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 的低维表示为：</p>
<span displaypfx="" class="mathjax-container">\[\boldsymbol{z}=\boldsymbol{U}_k^\top(\boldsymbol{x}-\boldsymbol{\mu})\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\mu}\)</span> 是原始数据均值， <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}-\boldsymbol{\mu}\)</span> 先把样本中心化， <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{U}_k\)</span> 由前 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个主成分方向组成，最终 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{z}\)</span> 就是样本在这组主方向上的低维坐标。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>对数据做中心化，必要时再做标准化。</li>
<li>计算协方差矩阵或直接对数据矩阵做奇异值分解（SVD）。</li>
<li>取前 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个主成分方向。</li>
<li>把数据投影到这些方向上得到低维表示。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在人脸图像压缩中，大量像素变化往往由少数全局因素驱动。PCA 可以用少量主成分保留大部分有效变化，从而显著降低特征维度。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：线性、稳定、可解释，常用作预处理和可视化。</li>
<li>局限：只能捕捉线性结构；对异常值敏感。</li>
<li>适用场景：线性降维、特征压缩、去相关与噪声过滤。</li>
</ul>
<div class="blog_h3"><span class="graybg">线性判别分析（LDA）</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>线性判别分析（Linear Discriminant Analysis, LDA）用于监督降维与分类。与 PCA 只看输入分布不同，LDA 利用类别标签寻找一个投影空间，使同类样本尽量聚集、异类样本尽量分开。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/lda.jpg"><img class="alignnone size-full wp-image-41349" src="https://blog.gmem.cc/wp-content/uploads/2026/03/lda.jpg" alt="lda" width="1024" height="559" /></a></p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>LDA 同时考虑类内散度（Within-class Scatter）和类间散度（Between-class Scatter）。好的投影方向应当让类间距离大、类内波动小，因此它优化的核心是判别性。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>定义类内散度矩阵：</p>
<span displaypfx="" class="mathjax-container">\[\boldsymbol{S}_W=\sum_{k=1}^{C}\sum_{\boldsymbol{x}_i\in \mathcal{C}_k}(\boldsymbol{x}_i-\boldsymbol{\mu}_k)(\boldsymbol{x}_i-\boldsymbol{\mu}_k)^\top\]</span>
<p>类内散度矩阵 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{S}_W\)</span> 统计的是“同一类内部有多分散”。 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{C}_k\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 类的样本集合， <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\mu}_k\)</span> 是该类均值。若类内样本围绕各自均值分布得很紧， <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{S}_W\)</span> 就会较小。</p>
<p>类间散度矩阵：</p>
<span displaypfx="" class="mathjax-container">\[\boldsymbol{S}_B=\sum_{k=1}^{C}N_k(\boldsymbol{\mu}_k-\boldsymbol{\mu})(\boldsymbol{\mu}_k-\boldsymbol{\mu})^\top\]</span>
<p>类间散度矩阵 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{S}_B\)</span> 统计的是“各类中心彼此有多分开”。其中 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\mu}\)</span> 是全局均值， <span displaypfx="inline-" class="mathjax-container">\(N_k\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 类样本数，因此样本多的大类会对类间结构贡献更大权重。</p>
<p>LDA 寻找投影向量 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{w}\)</span> 使 Fisher 判别准则最大：</p>
<span displaypfx="" class="mathjax-container">\[J(\boldsymbol{w})=\frac{\boldsymbol{w}^\top \boldsymbol{S}_B \boldsymbol{w}}{\boldsymbol{w}^\top \boldsymbol{S}_W \boldsymbol{w}}\]</span>
<p>Fisher 准则的分子衡量“投影后类间有多分开”，分母衡量“投影后类内有多混在一起”。因此 <span displaypfx="inline-" class="mathjax-container">\(J(\boldsymbol{w})\)</span> 越大，说明方向 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{w}\)</span> 越有助于把不同类别拉开、同时保持同类紧凑。</p>
<p>该问题可转化为广义特征值问题 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{S}_B\boldsymbol{w}=\lambda \boldsymbol{S}_W\boldsymbol{w}\)</span>。对 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 类问题，最多能得到 <span displaypfx="inline-" class="mathjax-container">\(C-1\)</span> 个有效判别方向。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>根据标签统计每一类的均值与全局均值。</li>
<li>构造类内散度矩阵和类间散度矩阵。</li>
<li>求解广义特征值问题，选择主要判别方向。</li>
<li>将样本投影到低维判别子空间中做分类或可视化。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在手写数字分类中，LDA 关注的是“哪些方向最有助于把不同数字分开”，因此在有标签监督时，它往往比只最大化总体方差的 PCA 更适合分类前降维。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：利用标签信息，投影方向更有判别性。</li>
<li>局限：线性假设较强；类内散度矩阵可能奇异。</li>
<li>适用场景：有标签监督的降维、分类前特征压缩、判别性可视化。</li>
</ul>
<div class="blog_h3"><span class="graybg">t-SNE</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>t-SNE（t-distributed Stochastic Neighbor Embedding）主要用于二维或三维可视化。它主要关注如何在低维图上尽量保留高维空间中的局部邻域关系。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>t-SNE 把“样本彼此接近”转写为概率相似度：在高维空间中，接近的样本应当有较大概率互为邻居；在低维嵌入中，也希望这种邻近关系继续成立。优化的目标是让两种邻域概率分布尽可能接近。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>在高维空间中，先定义邻域概率 <span displaypfx="inline-" class="mathjax-container">\(p_{ij}\)</span>；在低维空间中，对嵌入点 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{y}_i\)</span> 定义：</p>
<span displaypfx="" class="mathjax-container">\[q_{ij}=\frac{(1+\|\boldsymbol{y}_i-\boldsymbol{y}_j\|_2^2)^{-1}}{\sum_{a\ne b}(1+\|\boldsymbol{y}_a-\boldsymbol{y}_b\|_2^2)^{-1}}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{y}_i\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{y}_j\)</span> 是低维嵌入坐标，分子把距离越近的点赋予越大的相似度，分母则把所有点对的相似度加总起来做归一化，因此 <span displaypfx="inline-" class="mathjax-container">\(q_{ij}\)</span> 可以被解释成低维空间里的邻域概率。</p>
<p>t-SNE 最小化高维邻域分布与低维邻域分布之间的 KL 散度：</p>
<span displaypfx="" class="mathjax-container">\[\text{KL}(P\|Q)=\sum_{i\ne j} p_{ij} \log \frac{p_{ij}}{q_{ij}}\]</span>
<p>KL 散度衡量的是“低维邻域分布 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 与高维邻域分布 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 相差多少”。若某对样本在高维里很近，即 <span displaypfx="inline-" class="mathjax-container">\(p_{ij}\)</span> 很大，但在低维里被拉得太开， <span displaypfx="inline-" class="mathjax-container">\(q_{ij}\)</span> 就会过小，从而产生较大惩罚。</p>
<p>低维中采用重尾 Student-t 分布，主要是为了缓解拥挤问题（Crowding Problem），使远处点更容易被拉开。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>根据高维距离计算样本间的邻域概率。</li>
<li>随机初始化低维坐标。</li>
<li>计算低维相似度 <span displaypfx="inline-" class="mathjax-container">\(q_{ij}\)</span>。</li>
<li>通过梯度下降最小化 KL 散度并更新低维坐标。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在词向量或图像嵌入可视化中，t-SNE 常把语义相近的样本压到局部团簇中，从而帮助研究者直观看到表示空间里是否出现了合理分群。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：二维可视化效果强，局部邻域保持能力好。</li>
<li>局限：全局距离与簇间相对位置不稳定；对超参数和随机初始化敏感。</li>
<li>适用场景：嵌入可视化、表示质量诊断、探索性数据分析。</li>
</ul>
<div class="blog_h3"><span class="graybg">UMAP</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>UMAP（Uniform Manifold Approximation and Projection）同样用于低维可视化与非线性降维。它面对的任务与 t-SNE 类似，但更强调在保留局部结构的同时，尽量维持一定的全局几何关系，并提升速度与可扩展性。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/umap.jpg"><img class="alignnone size-full wp-image-41351" src="https://blog.gmem.cc/wp-content/uploads/2026/03/umap.jpg" alt="umap" width="1024" height="559" /></a></p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>UMAP 先在高维空间构建一个加权近邻图，把数据视为流形（Manifold）上的离散采样；随后在低维空间中寻找一张新图，使低维图与高维图的模糊连通结构尽量一致。它本质上是在匹配两张图的连通结构，而不只是在匹配两组欧氏距离。</p>
<p>这里的流形可以先从一个标准定义理解：设样本位于高维空间 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^D\)</span> 中，若它们实际上集中在某个低维集合 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{M}\subseteq\mathbb{R}^D\)</span> 附近，并且对 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{M}\)</span> 上任一点，都能在一个足够小的邻域内用 <span displaypfx="inline-" class="mathjax-container">\(d\)</span> 维坐标平滑描述，其中 <span displaypfx="inline-" class="mathjax-container">\(d\ll D\)</span>，那么这个集合就可以看作一个 <span displaypfx="inline-" class="mathjax-container">\(d\)</span> 维流形。流形的关键性质不是“它弯不弯”，而是<span style="background-color: #c0c0c0;">它在局部看起来像低维欧氏空间，在全局上则可以嵌入到更高维空间并发生弯曲、卷曲或拉伸</span>。</p>
<p>在数据分析里，这个概念表示：虽然原始特征有很多维，但样本并没有真正填满整个高维空间，而是分布在某种低维结构附近。例如一组人脸图像在像素空间里维度极高，但由姿态、光照、表情等少数潜在因素变化时，样本往往只覆盖其中一个低维子区域。UMAP 的出发点正是：若数据主要几何结构由这个低维流形决定，那么局部近邻关系比“全局欧氏直线距离”更能反映真实结构。</p>
<p>“离散采样”则表示：真实流形 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{M}\)</span> 本身是连续对象，但我们手里只有有限个样本点 <span displaypfx="inline-" class="mathjax-container">\(\{x_i\}_{i=1}^N\)</span>。UMAP 通过近邻图近似这些点在流形上的局部连通关系，再在低维空间中寻找一组坐标 <span displaypfx="inline-" class="mathjax-container">\(\{y_i\}_{i=1}^N\)</span>，使这种局部连通关系尽量被保留下来。因此它不是直接恢复整张流形，而是在有限样本层面恢复流形的邻域结构。</p>
<p>与 t-SNE 相比，UMAP 更强调“样本来自某个低维流形，并且近邻图是在近似这个流形的局部连通结构”；t-SNE 的核心对象则更偏向“高维邻域概率分布与低维邻域概率分布的匹配”。两者都重视局部关系，但 UMAP 的表述更几何化，t-SNE 的表述更概率化。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>若高维图边权为 <span displaypfx="inline-" class="mathjax-container">\(p_{ij}\)</span>，低维图边权常写为：</p>
<span displaypfx="" class="mathjax-container">\[q_{ij}=\frac{1}{1+a\|\boldsymbol{y}_i-\boldsymbol{y}_j\|_2^{2b}}\]</span>
<p>UMAP 用这个函数把低维距离转换成连通强度。 <span displaypfx="inline-" class="mathjax-container">\(a\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 控制曲线形状：距离很近时 <span displaypfx="inline-" class="mathjax-container">\(q_{ij}\)</span> 接近 1，距离变远时迅速衰减到接近 0，因此它可以把“近邻关系”转写成平滑的图边权。</p>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(a,b\)</span> 决定低维距离与连接强度的映射形状。UMAP 的优化目标通常是高维图与低维图之间的交叉熵（Cross-Entropy）：既鼓励高维中相连的点在低维中也靠近，也鼓励高维中不相连的点在低维中适当分开。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>计算近邻图，并为边赋予模糊连通权重。</li>
<li>初始化低维坐标。</li>
<li>通过随机优化最小化高维图与低维图之间的交叉熵。</li>
<li>得到二维或三维嵌入用于可视化或下游分析。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在单细胞测序、文本嵌入或推荐向量分析中，UMAP 常被用来把高维表示映射到二维平面，从而观察群体结构、类别分布与异常点。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：通常比 t-SNE 更快，较好兼顾局部与部分全局结构。</li>
<li>局限：嵌入结果仍依赖超参数与随机种子；二维距离不能机械等同于原空间距离。</li>
<li>适用场景：大规模嵌入可视化、非线性降维、聚类前的低维表示。</li>
</ul>
<div class="blog_h2"><span class="graybg">异常检测</span></div>
<p>异常检测处理的核心问题是：正常样本通常大量存在，而异常样本稀少、形态多变、甚至在训练阶段根本拿不到完整标签。模型因此不再主要学习“类别之间如何区分”，而是学习“什么算正常”以及“偏离正常结构有多严重”。不同方法对“异常”的定义并不相同：有的依赖隔离难易度，有的依赖局部密度，有的学习正常区域边界。</p>
<div class="blog_h3"><span class="graybg">什么叫异常</span></div>
<p>在业务语境里，“异常”并不等于“数值特别大”或“离均值很远”。更准确地说，异常是<span style="background-color: #c0c0c0;">相对于当前业务规则、历史模式或同类群体而言，不应当出现、很少出现、或者一旦出现就值得额外关注的样本</span>。因此异常是一个“相对概念”，必须依赖参照系：相对于谁、在哪个时间段、在什么上下文里、以什么代价衡量。</p>
<p>例如，单笔消费 5000 元在全国范围内不一定异常，但若这位用户平时只在本地便利店做几十元交易，而这次交易突然发生在异地、高风险设备、深夜时段，并伴随支付习惯突变，那么它在风控上就可能是异常。类似地，服务器 CPU 使用率 85% 在大促期间可能是正常负载，在凌晨低峰却可能意味着任务堆积；工厂传感器温度轻微升高若同时伴随振动模式变化，也可能预示故障正在形成。</p>
<p>这说明业务上的异常通常至少包含三类含义。第一类是<span style="background-color: #c0c0c0;">统计稀有</span>：样本落在低概率区域。第二类是<span style="background-color: #c0c0c0;">行为失配</span>：它与该对象自己的历史模式不一致。第三类是<span style="background-color: #c0c0c0;">群体失配</span>：它在全局上不一定极端，但相对于同类群体显著不同。异常检测算法的差异，本质上就在于它们分别更擅长刻画哪一种“失配”。</p>
<p>因此，做异常检测时首先要回答的不是“用哪种模型”，而是“业务到底把什么视为异常”。若异常意味着“明显稀少且容易与大部队分开”，隔离式方法更合适；若异常意味着“在本地邻域里显得稀疏”，应优先考虑密度比较；若正常样本边界清楚、异常样本类型杂乱，则更适合只学习正常区域。算法不是在定义业务，而是在实现业务已经确定的异常标准。</p>
<div class="blog_h3"><span class="graybg">孤立森林（Isolation Forest）</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>孤立森林（Isolation Forest）用于无监督异常检测：在没有可靠异常标签时，仅根据数据分布本身识别“容易被孤立”的异常样本。它尤其适合高维表格数据与大规模检测场景。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>孤立森林利用一个非常直接的直觉：异常点通常更稀少、更孤立，因此在随机切分下更容易被提早隔离。路径越短，越可能是异常；路径越长，越像处于正常群体内部。若把正常样本理解为“扎堆生活在高密度区域里的大群体”，那么它们往往需要经过很多次随机切割才会被单独分离出来；而那些落在边缘、稀疏区域、或与主体分布明显脱节的样本，只需少量切分就会被单独留在某个叶节点里。</p>
<p>这种思路和距离或密度方法非常不同。K 近邻（K-Nearest Neighbors, KNN）或局部异常因子（Local Outlier Factor, LOF）会显式比较“离别人有多远”或“局部密度有多低”；孤立森林则直接把异常检测改写成一个更具操作性的判据：<span style="background-color: #c0c0c0;">一个样本在随机树里平均多快会被单独隔开</span>。因此它并不先估计复杂的概率分布，也不依赖全局距离结构，而是用“隔离难易度”来近似刻画异常性。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/random-forest.png"><img class="alignnone size-full wp-image-41363" src="https://blog.gmem.cc/wp-content/uploads/2026/03/random-forest.png" alt="random-forest" width="1600" height="1308" /></a></p>
<div class="blog_h4"><span class="graybg">图示解读</span></div>
<p>图中的示意数据由两个主要正常簇和若干离散分布的异常点构成。背景等高线展示的是孤立森林学到的“异常分数地形”：颜色越深，表示该区域样本平均路径长度越长，更接近模型眼中的正常高密度区域；颜色越浅，表示样本更容易在随机切分中被提早隔离，因此更接近潜在异常区域。</p>
<p>图中圆形点对应被模型判为正常的样本，它们主要聚集在两个深色中心附近；叉号对应被模型判为异常的样本，它们更多分布在边缘、簇间空白带或浅色区域。这种可视化非常适合帮助理解孤立森林的工作方式：它并不是在画一条严格的几何边界，而是在表达“哪里更容易被随机树迅速切出来”。因此，等高线的深浅更接近一种“隔离难度地图”，而不是传统分类器意义上的硬分类边界。</p>
<div class="blog_h4"><span class="graybg">隔离树是什么</span></div>
<p>隔离树（Isolation Tree, iTree）可以理解成一棵专门为了“把样本逐步切开”而构造的随机二叉树。它与决策树（Decision Tree）在外形上相似，但目标完全不同：决策树是在找最有区分力的切分规则，隔离树则故意随机选择一个特征，再在该特征当前取值范围内随机选择一个切分点，把样本递归分到左右子节点。</p>
<p>设当前节点包含样本集合 <span displaypfx="inline-" class="mathjax-container">\(S\)</span>，随机选到的特征为第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 维，其切分阈值为 <span displaypfx="inline-" class="mathjax-container">\(\tau\)</span>，则一次切分可写成</p>
<span displaypfx="" class="mathjax-container">\[S_{\mathrm{left}}=\{\boldsymbol{x}\in S\mid x_j&lt;\tau\},\qquad S_{\mathrm{right}}=\{\boldsymbol{x}\in S\mid x_j\ge \tau\}\]</span>
<p>递归继续进行，直到某个节点只剩下 1 个样本，或所有样本在当前节点上已经无法再被有效区分。对一个本来就远离主体、所在区域又很稀疏的样本而言，随机切分往往只需要很少几步就能把它单独留在某个叶节点中；而对处在高密度正常群体内部的样本，通常要经过更多次切分才能被单独隔离。隔离树因此并不显式估计概率密度，而是把“异常”转写为“被随机切分提早单独分离”的难易度。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>对样本 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span>，记它在一棵隔离树中的路径长度为 <span displaypfx="inline-" class="mathjax-container">\(h(\boldsymbol{x})\)</span>。在多棵树上取平均路径长度 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[h(\boldsymbol{x})]\)</span> 后，异常分数定义为：</p>
<span displaypfx="" class="mathjax-container">\[s(\boldsymbol{x},n)=2^{-\mathbb{E}[h(\boldsymbol{x})]/c(n)}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(h(\boldsymbol{x})\)</span> 是样本在一棵树里被隔离所需的路径长度， <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[h(\boldsymbol{x})]\)</span> 是在整片森林中的平均值， <span displaypfx="inline-" class="mathjax-container">\(c(n)\)</span> 是针对样本规模 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 的归一化常数。路径越短，指数里的值越小，异常分数 <span displaypfx="inline-" class="mathjax-container">\(s(\boldsymbol{x},n)\)</span> 就越接近 1。</p>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(c(n)\)</span> 是平均路径长度的归一化常数，用来把不同样本规模下的路径长度放到可比较的尺度上。它常写为 <span displaypfx="inline-" class="mathjax-container">\(c(n)=2H_{n-1}-2(n-1)/n\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(H_{n-1}\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(n-1\)</span> 个调和数（Harmonic Number）。若某点明显比普通样本更早被切分隔离，则其平均路径长度更小，异常分数更接近 1。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>从训练集中随机抽取多个子样本。</li>
<li>为每个子样本构建随机隔离树。</li>
<li>对待测点计算其在所有树中的平均路径长度。</li>
<li>将平均路径长度映射为异常分数。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在交易风控中，若某条交易在金额、时间、地点、设备等多个维度上都明显偏离正常模式，它往往能在随机切分下被较早隔离出来，因此会获得更高异常分数。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：无需显式估计密度，也不依赖两两距离矩阵；因此在大规模数据上通常比基于邻域或密度的方法更高效。</li>
<li>优点：对高维表格数据较友好，对随机噪声通常也有较强鲁棒性，因为最终判断来自多棵随机树上的平均隔离行为，而不是某一次局部切分。</li>
<li>局限：对特征编码和特征尺度的业务含义仍然敏感；若异常与正常高度混叠、或者异常本身并不更容易被切开，隔离优势会下降。</li>
<li>适用场景：风控、日志异常、设备故障、指标监控等无监督异常检测，尤其适合作为高维表格场景中的强基线。</li>
</ul>
<div class="blog_h3"><span class="graybg">局部异常因子（LOF）</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>局部异常因子（Local Outlier Factor, LOF）主要解决“全局上不远，但在局部邻域中显著稀疏”的异常检测问题。当数据不同区域密度差异很大时，只看全局距离通常不够稳定。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>LOF 比较的是“这个点相对于其邻居是否更稀疏”。若一个点周围的局部密度显著低于邻居自己的局部密度，则它更像局部异常。它识别的不是“全局上最远的点”，而是<span style="background-color: #c0c0c0;">相对于自己所在局部环境显得不协调的点</span>。因此，当不同区域本来就有不同密度时，LOF 往往比只看全局距离的方法更稳。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/lof.png"><img class="alignnone size-full wp-image-41371" src="https://blog.gmem.cc/wp-content/uploads/2026/03/lof.png" alt="lof" width="1000" height="800" /></a></p>
<div class="blog_h4"><span class="graybg">图示解读</span></div>
<p>图中的实心圆点表示样本点，围绕样本点的空心圆圈大小表示该点的 LOF 分数大小。圆圈越小，说明该点的局部密度与其邻居相近，更像正常簇中的内部样本；圆圈越大，说明该点所在位置相对于邻居显得更稀疏，因此更可能是局部异常点。</p>
<p>这张图直观展示了 LOF 的核心判断方式：它并不先问“这个点离全局中心有多远”，而是先问“这个点和自己周围那一圈邻居相比，是不是显得过于稀疏”。因此，大圆圈对应的未必是全局最远的点，而更可能是那些<span style="background-color: #c0c0c0;">周围邻居仍然较密、但它自己明显脱离了局部密度水平</span>的点。</p>
<p>若把这张示意图看作两个高密度簇与少量随机噪声点的组合，则邻居数 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 决定了算法观察“局部环境”的尺度。 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 较小时，模型更敏感于非常局部的扰动； <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 较大时，密度比较更平滑，但也可能削弱对细粒度异常的敏感性。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>给定邻居数 <span displaypfx="inline-" class="mathjax-container">\(k\)</span>，设 <span displaypfx="inline-" class="mathjax-container">\(N_k(\boldsymbol{p})\)</span> 表示点 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{p}\)</span> 的 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个最近邻集合。LOF 的计算可以拆成三层：先算可达距离（Reachability Distance），再算局部可达密度（Local Reachability Density, LRD），最后比较邻居密度与自身密度，得到局部异常因子（Local Outlier Factor, LOF）。</p>
<p>先定义可达距离（Reachability Distance）：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{rd}_k(\boldsymbol{p},\boldsymbol{o})=\max\big(\text{k-distance}(\boldsymbol{o}),d(\boldsymbol{p},\boldsymbol{o})\big)\]</span>
<p>可达距离会把两个量取最大值：邻居点 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{o}\)</span> 自己的第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 邻距离，以及点对之间的真实距离。这样做可以防止极近的点对把局部密度估得过于夸张，使密度估计更稳。</p>
<p>再定义局部可达密度（Local Reachability Density, LRD）：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{lrd}_k(\boldsymbol{p})=\left(\frac{1}{|N_k(\boldsymbol{p})|}\sum_{\boldsymbol{o}\in N_k(\boldsymbol{p})} \mathrm{rd}_k(\boldsymbol{p},\boldsymbol{o})\right)^{-1}\]</span>
<p>局部可达密度 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{lrd}_k(\boldsymbol{p})\)</span> 本质上是“平均可达距离”的倒数：平均距离越小，周围越拥挤，密度越大；平均距离越大，周围越稀疏，密度越小。</p>
<p>最终的 LOF 分数为：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{LOF}_k(\boldsymbol{p})=\frac{1}{|N_k(\boldsymbol{p})|}\sum_{\boldsymbol{o}\in N_k(\boldsymbol{p})} \frac{\mathrm{lrd}_k(\boldsymbol{o})}{\mathrm{lrd}_k(\boldsymbol{p})}\]</span>
<p>LOF 分数比较的是“邻居的局部密度”和“自己本身的局部密度”。若 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{lrd}_k(\boldsymbol{p})\)</span> 明显小于邻居的密度，分数就会大于 1，说明该点相对周围环境显得更孤立。更具体地说，分子是邻居局部密度的平均水平，分母是点 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{p}\)</span> 自己的局部密度，因此这个比值本质上是在问：<span style="background-color: #c0c0c0;">你周围的人都很挤，而你自己是不是站得太空</span>。</p>
<p>结果通常可以这样解读：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathrm{LOF}_k(\boldsymbol{p})\approx 1\)</span>：该点的局部密度与邻居相近，通常属于正常样本。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathrm{LOF}_k(\boldsymbol{p})&lt;1\)</span>：该点甚至比邻居更密集，往往处于簇的核心区域。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathrm{LOF}_k(\boldsymbol{p})&gt;1\)</span>：该点局部密度低于邻居，越大越像异常点。</li>
</ul>
<p>因此，LOF 回答的是“这个点在自己的局部邻域里是不是显得过于稀疏”，而不是“这个点离全局中心远不远”。这也是它能识别局部异常、却对距离度量与邻居数 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 较敏感的根本原因。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>为每个样本找到 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个近邻。</li>
<li>计算可达距离与局部可达密度。</li>
<li>比较样本与邻居的局部密度，得到 LOF 分数。</li>
<li>按分数排序或设置阈值输出异常点。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在消费行为数据中，某一线城市用户的高消费在全国范围内未必异常，但在“同年龄、同区域、同收入”的邻域里可能明显偏离。LOF 正是通过这种局部密度比较识别这类异常。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：适合不同密度区域共存的数据，能识别“全局上不极端、但局部上明显失配”的异常。</li>
<li>优点：解释性较强，因为 LOF 分数直接来自“邻居密度 / 自身密度”的局部比较，便于回答异常是相对于谁显得异常。</li>
<li>局限：对距离度量、标准化和邻居数 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 很敏感； <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 太小会放大噪声，太大又可能抹平真正的局部异常。</li>
<li>局限：大规模近邻搜索成本较高，因此在超大数据上常需要索引加速或近似近邻方法配合。</li>
<li>适用场景：局部离群检测、消费异常、群体内部行为异常、同类用户或设备群体中的行为失配识别。</li>
</ul>
<div class="blog_h3"><span class="graybg">单类支持向量机（One-Class SVM）</span></div>
<div class="blog_h4"><span class="graybg">背景和问题定义</span></div>
<p>单类支持向量机（One-Class Support Vector Machine, One-Class SVM）用于“只有正常样本、缺少可靠异常样本”的异常检测任务。它的目标是学习一个描述正常样本区域的边界。</p>
<div class="blog_h4"><span class="graybg">核心思想</span></div>
<p>One-Class SVM 通过核映射把样本送到高维特征空间，再寻找一个把大多数正常样本与原点分开的超平面。等价地看，它学习的是一个“包住正常样本”的高维边界，边界外的点更可能是异常。</p>
<div class="blog_h4"><span class="graybg">算法公式和详细解释</span></div>
<p>一种标准形式为：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\boldsymbol{w},\rho,\boldsymbol{\xi}} \frac{1}{2}\|\boldsymbol{w}\|_2^2+\frac{1}{\nu N}\sum_{i=1}^{N} \xi_i-\rho\]</span>
<p>One-Class SVM 的目标由三部分组成： <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{2}\|\boldsymbol{w}\|_2^2\)</span> 控制边界不要太复杂， <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{\nu N}\sum_i \xi_i\)</span> 惩罚落在边界外或靠得太近的样本， <span displaypfx="inline-" class="mathjax-container">\(-\rho\)</span> 则鼓励把正常区域尽量向外推开。 <span displaypfx="inline-" class="mathjax-container">\(\nu\)</span> 越大，对违约样本的容忍度越高。</p>
<span displaypfx="" class="mathjax-container">\[\text{s.t.} \quad \boldsymbol{w}^\top \phi(\boldsymbol{x}_i) \ge \rho-\xi_i,\qquad \xi_i \ge 0\]</span>
<p>这些约束表示：样本映射到特征空间后，其投影值至少要达到阈值 <span displaypfx="inline-" class="mathjax-container">\(\rho\)</span>；若做不到，就用非负松弛变量 <span displaypfx="inline-" class="mathjax-container">\(\xi_i\)</span> 记录违约程度。于是模型允许少量样本越界，但必须为此付出代价。</p>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{w}\)</span> 是超平面法向量， <span displaypfx="inline-" class="mathjax-container">\(\rho\)</span> 是阈值， <span displaypfx="inline-" class="mathjax-container">\(\xi_i\)</span> 是松弛变量， <span displaypfx="inline-" class="mathjax-container">\(\nu\in(0,1]\)</span> 控制允许落在边界外的比例与支持向量比例。判别函数为：</p>
<span displaypfx="" class="mathjax-container">\[f(\boldsymbol{x})=\text{sign}\left(\boldsymbol{w}^\top \phi(\boldsymbol{x})-\rho\right)\]</span>
<p>判别函数先计算样本在特征空间里相对边界的位置：若 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{w}^\top \phi(\boldsymbol{x})-\rho\)</span> 为正，说明样本位于学习到的正常区域一侧；若为负，则更可能落在边界之外，被视为异常。</p>
<p>若使用核函数 <span displaypfx="inline-" class="mathjax-container">\(K(\boldsymbol{x},\boldsymbol{x}')\)</span>，则该边界可以是非线性的，因此能表达复杂的正常区域。</p>
<div class="blog_h4"><span class="graybg">训练或推断流程</span></div>
<ol>
<li>只用正常样本训练模型，选择核函数与超参数 <span displaypfx="inline-" class="mathjax-container">\(\nu\)</span>。</li>
<li>在特征空间中学习分离超平面。</li>
<li>对新样本计算判别函数值。</li>
<li>若分数低于边界阈值，则判为异常。</li>
</ol>
<div class="blog_h4"><span class="graybg">应用实例</span></div>
<p>在设备健康监控中，异常故障类型往往变化很大，难以完整收集，但正常运行数据很多。One-Class SVM 可以只基于正常样本学习边界，一旦新样本落出该区域，就触发异常告警。</p>
<div class="blog_h4"><span class="graybg">优缺点与适用场景</span></div>
<ul>
<li>优点：只依赖正常样本；核方法可表达复杂边界。</li>
<li>局限：对特征缩放和核参数敏感；大规模训练较重。</li>
<li>适用场景：设备监控、入侵检测、质量控制等“正常样本丰富、异常样本稀缺”的任务。</li>
</ul>
<div class="blog_h1"><span class="graybg">神经网络</span></div>
<div class="blog_h2"><span class="graybg">前馈神经网络</span></div>
<div class="blog_h3"><span class="graybg">感知机</span></div>
<p>感知机（Perceptron）是最早的神经元模型之一，也是现代神经网络最基本的计算原型。无论是 MLP、CNN、RNN，还是 Transformer，本质上都由大量“线性变换 + 非线性变换”的单元堆叠而成；从这个意义上说，理解感知机，就是理解大型模型最小的功能部件。</p>
<p>最原始的感知机先做线性组合，再经过一个阈值函数给出二分类输出：</p>
<span displaypfx="" class="mathjax-container">\[\hat y=\mathrm{sign}(\mathbf{w}^\top \mathbf{x}+b)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 是输入特征， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 是权重， <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 是偏置。 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top \mathbf{x}+b\)</span> 的含义是“沿着权重指定的方向对输入做加权打分”，而 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{sign}(\cdot)\)</span> 则把连续分数变成离散决策：高于阈值判为正类，低于阈值判为负类。</p>
<p>需要特别区分的是：最早的感知机确实直接做分类，但现代神经网络里的大多数“感知机式单元”并不直接输出最终类别。隐藏层单元更常见的形式是 <span displaypfx="inline-" class="mathjax-container">\(h=\phi(\mathbf{w}^\top \mathbf{x}+b)\)</span>，它们输出的是中间表示（Intermediate Representation），职责是检测局部模式、重组特征并为后续层提供更有用的表示；只有最后的任务头（Task Head）才把这些中间表示转成分类、回归或生成输出。</p>
<p>这里的中间表示（Intermediate Representation）本质上就是一组<span style="background-color: #c0c0c0;">可被后续层继续计算的数值特征</span>。它既可以是向量（Vector），也可以是矩阵（Matrix），更一般地说，它通常是张量（Tensor）。向量和矩阵都只是张量的特殊情形：若只看单个样本的 MLP 隐层输出，最常见的是向量 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{h}\in\mathbb{R}^{d}\)</span>；若看 Transformer 对整段序列的隐藏状态，常写成矩阵 <span displaypfx="inline-" class="mathjax-container">\(H\in\mathbb{R}^{L\times d}\)</span>；若再把 batch 维也带上，则会变成三维张量 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{H}\in\mathbb{R}^{B\times L\times d}\)</span>。在卷积网络（Convolutional Neural Network, CNN）里，中间表示则常是特征图张量（Feature Map Tensor） <span displaypfx="inline-" class="mathjax-container">\(\mathcal{H}\in\mathbb{R}^{C\times H\times W}\)</span>，或带 batch 的 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^{B\times C\times H\times W}\)</span>。</p>
<p>因此，“中间表示”不是某种神秘对象，它就是网络在某一层对输入所形成的内部编码。它不像最终标签那样直接面向人类语义，而是把输入重写成更适合下一层处理的坐标系。例如，文本模型中的某一层隐藏状态可能同时编码词义、上下文关系、句法位置和任务相关线索；图像模型中的某一层特征图则可能突出边缘、纹理、局部部件或更高层形状。后续层与任务头读到的，正是这些内部编码。</p>
<p>感知机的重要性不止在于历史地位，更在于今天大型模型中的知识，本质上仍然是通过这类权重结构逐层编码进去的。训练过程并不会把知识像数据库那样逐条写成显式记录，而是不断调整参数，使某些输入模式被放大、某些输入模式被抑制。于是，模型在数据中反复见到的统计规律——词与词的共现、图像局部纹理、特征之间的组合关系——都会被压缩进参数矩阵的数值结构中。</p>
<p>更准确地说，大模型中的知识通常不是“某一个感知机单独存储一条事实”，而是以<span style="background-color: #c0c0c0;">分布式表示（Distributed Representation）</span>的形式分散在大量参数里。单个单元更像一个局部特征探测器（Feature Detector）：它只对某种模式敏感；许多单元级联后，网络才能把低层简单模式组合成高层抽象概念。模型规模越大、层数越深、参数越多，可被编码的模式组合也越丰富，这正是大模型具备强表达能力与“知识容量”的原因之一。</p>
<p>感知机能学会线性可分任务，但无法处理 XOR 这类线性不可分问题，这正是多层网络出现的动机：当一个超平面不够时，就需要通过多层组合把输入空间逐步重写成更容易分开的表示。</p>
<div class="blog_h3"><span class="graybg">多层感知机（MLP）</span></div>
<p>多层感知机（Multi-Layer Perceptron, MLP）就是最典型的全连接神经网络（Fully Connected Neural Network）。它处理的核心问题是：当输入与输出之间的关系不是一个超平面就能表达时，如何通过多层可学习变换，把原始特征逐步改写成更容易完成任务的表示。它由多层线性变换与逐元素非线性激活交替组成，是最基本也最通用的前馈网络结构。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/mlp.png"><img class="alignnone size-full wp-image-41415" src="https://blog.gmem.cc/wp-content/uploads/2026/03/mlp.png" alt="mlp" width="1024" height="1024" /></a></p>
<p>单层可写成：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{h}=\phi(W\mathbf{x}+\mathbf{b})\]</span>
<p>多层堆叠后，可写为：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{h}^{(1)}=\phi(W^{(1)}\mathbf{x}+\mathbf{b}^{(1)}),\quad \mathbf{h}^{(2)}=\phi(W^{(2)}\mathbf{h}^{(1)}+\mathbf{b}^{(2)}),\quad \hat{\mathbf{y}}=W^{(3)}\mathbf{h}^{(2)}+\mathbf{b}^{(3)}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(W^{(l)}\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}^{(l)}\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(l\)</span> 层参数， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{h}^{(l)}\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(l\)</span> 层隐藏表示。所谓“逐层重写表示”，就是每一层都在回答一个更具体的问题：哪些原始模式值得保留、哪些组合值得放大、哪些方向更利于后续任务。于是，早期层往往捕捉较局部、较简单的模式，后期层则把这些模式组合成更抽象的语义结构。</p>
<p>MLP 比单层感知机强，关键不在“层数更多”本身，而在于层与层之间插入了非线性激活函数 <span displaypfx="inline-" class="mathjax-container">\(\phi\)</span>。如果没有非线性，多层线性变换满足</p>
<span displaypfx="" class="mathjax-container">\[W^{(2)}(W^{(1)}\mathbf{x}+\mathbf{b}^{(1)})+\mathbf{b}^{(2)}=\tilde W\mathbf{x}+\tilde{\mathbf{b}}\]</span>
<p>最终仍然等价于一层线性变换，表达能力不会因为堆叠而提升。只有加入非线性后，网络才能把输入空间切分、折叠、拉伸并重新组合，形成复杂的分段线性或平滑非线性决策边界。</p>
<p>从几何角度看，单层模型像“用一个超平面切一次”；而多层 MLP 则是在表示空间中反复做坐标变换和非线性折叠，把原本难分的数据逐步变成线性头也能分开的形状。文中的激活函数对比图展示的正是这个过程：不同激活函数会把同一个三层网络变成完全不同的几何变换器。</p>
<p>在现代大模型中，MLP 的作用远不只是“附属模块”。在 Transformer 里，注意力层负责在 token 之间路由信息，而 MLP / Feed-Forward Network（FFN）则负责在每个位置上做通道维度的非线性特征变换，把路由来的信息重新编码进更强的表示。因此，很多语义模式、组合规则与任务相关知识，最终都会沉淀到这些大规模参数化的 MLP 权重中。</p>
<p>更进一步说，在 Transformer 的常见解释框架里，MLP / FFN 往往被看作<span style="background-color: #c0c0c0;">事实性知识的重要载体之一</span>。一个常见直觉是把 FFN 看成参数化的“键值存储器（Key-Value Memory）”：第一层线性变换更像在检测当前输入是否匹配某种模式或概念，第二层线性变换则把与该模式相关的语义方向重新写回残差流（Residual Stream）。这里的残差流，可以理解为 Transformer 里那条贯穿各层的主表示通道：每一层注意力与 MLP 的输出，都会通过残差相加的方式写回这条主通道，再交给后续层继续处理。因此，诸如“实体—属性”“术语—定义”“模式—响应”这类较稳定的关联，常常更容易在 MLP 权重里留下痕迹。</p>
<p>但这并不意味着“一个神经元就存一条事实”。更准确的描述是：<span style="background-color: #c0c0c0;">知识通常以分布式方式存在于许多层、许多通道和许多参数方向里</span>。单个神经元有时会对某种关系或概念特别敏感，因而出现所谓“知识神经元（Knowledge Neurons）”现象；但更稳定的事实表示，通常仍然依赖一组共同激活的单元和跨层传递的表示。可以把两类子层的分工概括为：注意力更擅长“去哪里找、和谁建立联系”，MLP 更擅长“把匹配到的模式变成可供后续层使用的语义内容”。因此，说 MLP 是知识的主要载体之一是合理的；说知识只存在于 MLP、完全不在注意力里，则过于简单。</p>
<div class="blog_h2"><span class="graybg">激活函数</span></div>
<p>激活函数（Activation Function）的作用是给线性层引入非线性。如果没有激活函数，多层线性层叠起来仍然等价于一层线性变换，深度就失去了意义。</p>
<p>先给出一个实用的选型总表。它不代替后文的机制分析，但可以先回答工程上最常见的问题：某个激活函数通常应该放在输出层、隐藏层，还是特定结构里。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">激活函数</td>
<td style="text-align: center;">输出范围</td>
<td style="text-align: center;">典型适用场景</td>
<td style="text-align: center;">主要原因</td>
</tr>
</thead>
<tbody>
<tr>
<td>Sigmoid</td>
<td><span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span></td>
<td>二分类输出层；LSTM / GRU 门控</td>
<td>天然给出概率或开关强度，但隐藏层易饱和</td>
</tr>
<tr>
<td>Tanh</td>
<td><span displaypfx="inline-" class="mathjax-container">\((-1,1)\)</span></td>
<td>较浅网络；需要零中心有界激活的结构</td>
<td>比 sigmoid 更利于优化，但深层仍会饱和</td>
</tr>
<tr>
<td>ReLU</td>
<td><span displaypfx="inline-" class="mathjax-container">\([0,+\infty)\)</span></td>
<td>深层前馈网络、CNN 的默认隐藏层</td>
<td>不饱和、计算便宜、分段线性、优化稳定</td>
</tr>
<tr>
<td>Leaky ReLU</td>
<td><span displaypfx="inline-" class="mathjax-container">\((-\infty,+\infty)\)</span></td>
<td>担心 Dying ReLU 的深层隐藏层</td>
<td>保留 ReLU 优点，同时避免负半轴完全断梯度</td>
</tr>
<tr>
<td>ELU</td>
<td><span displaypfx="inline-" class="mathjax-container">\((-\alpha,+\infty)\)</span></td>
<td>希望激活更平滑、且均值更接近 0 的隐藏层</td>
<td>负区间平滑并可取负值，但计算开销高于 ReLU</td>
</tr>
<tr>
<td>GELU</td>
<td><span displaypfx="inline-" class="mathjax-container">\((-\infty,+\infty)\)</span></td>
<td>Transformer、BERT 类模型的隐藏层</td>
<td>选择性强且过渡平滑，兼顾表达能力与优化平滑性</td>
</tr>
<tr>
<td>Swish / SiLU</td>
<td><span displaypfx="inline-" class="mathjax-container">\((-\infty,+\infty)\)</span></td>
<td>现代卷积网络；部分大模型隐藏层</td>
<td>软门控、平滑、比硬截断更柔和</td>
</tr>
<tr>
<td>Softmax</td>
<td>各分量在 <span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span> 且总和为 1</td>
<td>多分类输出层；语言模型词表分布输出</td>
<td>把 logits 归一化为概率分布，不用于普通隐藏层</td>
</tr>
</tbody>
</table>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/mlp-activation-summary.png"><img class="alignnone size-full wp-image-41289" src="https://blog.gmem.cc/wp-content/uploads/2026/03/mlp-activation-summary.png" alt="mlp-activation-summary" width="1920" height="957" /></a></p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/activation-functions-grid.png"><img class="alignnone size-full wp-image-41461" src="https://blog.gmem.cc/wp-content/uploads/2026/03/activation-functions-grid.png" alt="activation-functions-grid" width="1920" height="1079" /></a></p>
<div class="blog_h3"><span class="graybg">Sigmoid</span></div>
<p>Sigmoid 把实数压到 <span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\sigma(x)=\frac{1}{1+e^{-x}}\]</span>
<p>Sigmoid 的优势在于输出天然落在 <span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span>，因此非常适合表示概率，尤其常用于二分类输出层或门控结构中。但它在隐藏层里的主要问题是饱和（saturation）：当输入绝对值较大时，函数会迅速贴近 0 或 1，此时导数接近 0，梯度在反向传播时会被不断压缩，深层网络因此容易出现梯度消失（Vanishing Gradient）。从优化角度看，这意味着前面层参数即使有误，也很难收到足够强的更新信号。</p>
<p>此外，sigmoid 的输出始终为正，不以 0 为中心，这会使后续层接收到带偏移的激活分布，通常不利于优化动态的稳定性。因此，sigmoid 今天更多保留在“需要概率解释”的输出层，或在 LSTM / GRU 等门控结构中充当开关函数，而不再是深层前馈隐藏层的默认选择。</p>
<div class="blog_h3"><span class="graybg">Tanh</span></div>
<p>Tanh 的输出范围是 <span displaypfx="inline-" class="mathjax-container">\((-1,1)\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}}\]</span>
<p>Tanh 可以看作“零中心版 sigmoid”：它同样会在大幅度输入时饱和，但输出分布位于 <span displaypfx="inline-" class="mathjax-container">\((-1,1)\)</span> 且以 0 为中心，这通常比 sigmoid 更利于优化，因为后续层接收到的激活不再整体偏向正侧。对于需要表达“正负方向”差异的隐藏表示，tanh 往往也比 sigmoid 更自然。</p>
<p>不过，tanh 并没有解决饱和带来的根本问题：当 <span displaypfx="inline-" class="mathjax-container">\(|x|\)</span> 很大时，导数仍接近 0，深层网络中的梯度传播依然会变弱。因此，在较深的前馈网络里，tanh 通常不如 ReLU 家族稳定；它更多出现在较浅网络、早期神经网络设计，或某些希望激活有界且零中心的结构中。</p>
<div class="blog_h3"><span class="graybg">ReLU</span></div>
<p>ReLU（Rectified Linear Unit）定义为</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{ReLU}(x)=\max(0,x)\]</span>
<p>ReLU 看起来几乎“过于简单”，但它之所以长期有效，关键不在公式花哨，而在于它同时满足了深层优化最需要的几条性质。第一，ReLU 在正半轴不饱和（non-saturating）：当 <span displaypfx="inline-" class="mathjax-container">\(x&gt;0\)</span> 时导数恒为 1，梯度穿过这一单元时不会像 sigmoid / tanh 那样被持续压小，因此更有利于深层网络中的梯度传播。第二，ReLU 保留了非线性，但正半轴仍是线性的，这使整个网络变成<span style="background-color: #c0c0c0;">分段线性（piecewise linear）</span>系统：表达能力足够强，同时局部优化形状又比高度弯曲的饱和函数更“规整”。第三，负半轴直接截断会带来自然的稀疏激活（sparse activation）：不是所有单元都会在每个样本上同时活跃，这通常有助于提升表示分解能力，并降低无效共适应（co-adaptation）。这里的共适应，指多个神经元在训练中形成了过强的相互依赖：某个单元之所以有效，不是因为它单独学到了稳定、可迁移的模式，而是因为它总是和另外几个特定单元“成套工作”。一旦输入分布变化，或其中某些单元没有按训练时那样响应，这种脆弱的协同关系就容易失效，从而削弱泛化能力。</p>
<p>从工程角度看，ReLU 的优势还包括计算代价极低，只需一次比较运算；这在大规模训练中会被成千上万层和数十亿次前向/反向传播放大。更深层的原因是：深度网络真正需要的不是“平滑得很漂亮”的激活函数，而是一个既能打破线性、又不会在大范围内把梯度压扁、还能让优化器容易工作的非线性。ReLU 恰好在这三点之间取得了非常实用的平衡，这就是为什么一个形式上极其朴素的函数，反而成为现代深度学习最成功的默认选择之一。它的代价也很明确：负区间梯度为 0，单元可能长期失活，这就是后面 Leaky ReLU、ELU、GELU 等变体继续改进的出发点。</p>
<div class="blog_h3"><span class="graybg">Leaky ReLU</span></div>
<p>Leaky ReLU 给负半轴保留一个很小的斜率：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{LeakyReLU}(x)=\max(\alpha x,x),\quad \alpha\ll 1\]</span>
<p>这样做是为了缓解“死亡 ReLU（Dying ReLU）”问题：若某神经元长期落在负区间，普通 ReLU 的梯度可能一直为 0，而 Leaky ReLU 仍保留一点更新信号。</p>
<p>从机制上看，Leaky ReLU 的核心改动很小，但很有针对性：它保留了 ReLU 在正半轴的不饱和与分段线性优点，同时避免把负半轴完全切断。这样即使某个单元暂时落入负区间，参数仍有机会通过非零梯度被重新拉回活跃状态。因此，Leaky ReLU 可以看作对 ReLU 的保守修正：表达风格几乎不变，但训练风险更低。</p>
<div class="blog_h3"><span class="graybg">ELU</span></div>
<p>ELU（Exponential Linear Unit）在负半轴使用指数平滑：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{ELU}(x)=\begin{cases}x,&amp;x&gt;0\\ \alpha(e^x-1),&amp;x\le 0\end{cases}\]</span>
<p>它的目标是兼顾 ReLU 的优化优势和负区间的平滑性，使激活均值更接近 0。</p>
<p>与 Leaky ReLU 相比，ELU 在负半轴不再是简单直线，而是用指数曲线平滑衰减到负饱和值。这带来两个效果：一是避免了 ReLU 在 0 点附近过于生硬的折线结构；二是允许激活出现稳定的负值，从而减轻隐藏表示整体偏正的问题。代价是计算比 ReLU 更重，且负区间在极小值处同样会逐渐饱和，因此它通常被视为“更平滑、更零中心”的 ReLU 变体，而不是彻底不同的一类激活。</p>
<div class="blog_h3"><span class="graybg">GELU</span></div>
<p>GELU（Gaussian Error Linear Unit）可理解为“按输入大小平滑地决定保留多少信号”。它不像 ReLU 那样硬截断，而是对小正值和小负值做连续、概率化的保留，因此在 0 附近更平滑。</p>
<p>这种设计的价值在于：它仍然保留了 ReLU 家族的选择性——不是所有信号都被同等对待——但又避免了硬截断带来的尖锐折点和完全失活区。于是，GELU 往往能在“表达选择性”和“优化平滑性”之间取得更好的折中，这也是它在 Transformer、BERT 及其后续大量变体中被广泛采用的重要原因。</p>
<div class="blog_h3"><span class="graybg">Swish / SiLU</span></div>
<p>Swish / SiLU 定义为</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{SiLU}(x)=x\,\sigma(x)\]</span>
<p>它是平滑、非单调的激活函数，在某些深层网络里表现优于 ReLU。其结构可以直接读成“输入值 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 乘上一个 sigmoid 门控 <span displaypfx="inline-" class="mathjax-container">\(\sigma(x)\)</span>”：当 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 很大时， <span displaypfx="inline-" class="mathjax-container">\(\sigma(x)\approx 1\)</span>，信号几乎原样通过；当 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 很小时， <span displaypfx="inline-" class="mathjax-container">\(\sigma(x)\approx 0\)</span>，信号被显著压低；在 0 附近则是连续、平滑的软过渡。</p>
<p>这种形式的价值在于：它既不像 ReLU 那样做硬截断，也不像 sigmoid 那样把输出彻底压进固定区间，而是让网络学到一种“按输入强度自适应通过多少”的软门控机制。结果是，SiLU / Swish 往往能在保持优化平滑性的同时，保留较强的表达灵活性，因此在一些现代卷积网络与大模型变体中表现良好。它可以看作介于 ReLU 家族与门控激活之间的一种折中设计。</p>
<div class="blog_h3"><span class="graybg">SELU、Softplus 与 Softsign</span></div>
<p>SELU（Scaled Exponential Linear Unit）是为自归一化网络（Self-Normalizing Neural Network）设计的激活函数。它在 ELU 的基础上再加缩放系数，目标是让层间激活均值和方差自动朝稳定区间收敛。它在特定网络设计里很有理论吸引力，但现代主流 Transformer 和大规模视觉模型并不常把它作为默认激活。</p>
<p>Softplus 定义为 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Softplus}(x)=\log(1+e^x)\)</span>，可以看作 ReLU 的平滑版；Softsign 定义为 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Softsign}(x)=\frac{x}{1+|x|}\)</span>，则是比 tanh 更缓和的有界非线性。它们在激活函数发展史上很常见，也有特定结构会采用，但在现代大模型与主流深度网络里，工程默认仍更偏向 ReLU 家族、GELU、SiLU 与门控变体。</p>
<div class="blog_h3"><span class="graybg">Softmax</span></div>
<p>Softmax 把一组实数分数（scores）映射为概率分布（Probability Distribution）。在分类与语言模型里，这组分数通常称为 logit（Logits）：它们是 softmax 之前的未归一化输出。</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{softmax}(z)_i=\frac{e^{z_i}}{\sum_{j=1}^{V} e^{z_j}}\]</span>
<p>logits 的两个关键性质：</p>
<ul>
<li>logits 不需要是概率，可以是任意实数；softmax 才把它变成 <span displaypfx="inline-" class="mathjax-container">\([0,1]\)</span> 且和为 1 的分布。</li>
<li>softmax 对整体平移不敏感：对任意常数 <span displaypfx="inline-" class="mathjax-container">\(c\)</span>，有 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{softmax}(z)=\mathrm{softmax}(z+c\mathbf{1})\)</span>。因此实现里常用 <span displaypfx="inline-" class="mathjax-container">\(z\leftarrow z-\max_i z_i\)</span> 做数值稳定（Numerical Stability）。</li>
</ul>
<p>这说明 softmax 真正关心的是各个 logit 之间的相对差值，而不是某个 logit 的绝对数值。若把所有分数同时加 10，模型对“哪一类更占优”的判断不会改变，因为指数项会在分子和分母里同时乘上 <span displaypfx="inline-" class="mathjax-container">\(e^{10}\)</span>，最终完全约掉。改变 softmax 输出的，是某个类别相对其他类别高了多少，而不是整体抬高或压低所有分数。</p>
<p>也正因为如此，logit 更适合理解为“未归一化偏好分数（unnormalized preference scores）”，而不是“概率雏形”。例如两类 logits 从 <span displaypfx="inline-" class="mathjax-container">\((1,2)\)</span> 变成 <span displaypfx="inline-" class="mathjax-container">\((101,102)\)</span>，softmax 输出完全相同；但若从 <span displaypfx="inline-" class="mathjax-container">\((1,2)\)</span> 变成 <span displaypfx="inline-" class="mathjax-container">\((1,5)\)</span>，第二类相对第一类的优势被显著拉大，概率才会明显变化。</p>
<p>在语言模型（Language Model）中，给定最后一层隐藏状态 <span displaypfx="inline-" class="mathjax-container">\(h\in\mathbb{R}^{d_{\text{model}}}\)</span>，线性输出头产生词表大小 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 的 logits： <span displaypfx="inline-" class="mathjax-container">\(z=hW_{\text{vocab}}+b\)</span>，再经 softmax 得到下一个 token 的分布。若采用权重共享（Weight Tying），则输入嵌入表 <span displaypfx="inline-" class="mathjax-container">\(E\in\mathbb{R}^{V\;\times d_{\text{model}}}\)</span> 与输出头满足 <span displaypfx="inline-" class="mathjax-container">\(W_{\text{vocab}}=E^\top\)</span>，于是可直接写成 <span displaypfx="inline-" class="mathjax-container">\(z=hE^\top+b\)</span>。</p>
<div class="blog_h4"><span class="graybg">Softmax和分类任务</span></div>
<p>在多分类任务里，这几个概念实际上是一条连续的计算链：任务头先输出 logits <span displaypfx="inline-" class="mathjax-container">\(z\in\mathbb{R}^{C}\)</span>，softmax 把它们变成条件概率 <span displaypfx="inline-" class="mathjax-container">\(p(y=i|x)\)</span>，再取真实类别 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 的负对数概率作为单样本损失，也就是负对数似然（Negative Log-Likelihood, NLL）。</p>
<span displaypfx="" class="mathjax-container">\[p(y=i|x)=\mathrm{softmax}(z)_i=\frac{e^{z_i}}{\sum_{j=1}^{C}e^{z_j}},\qquad \ell_{\mathrm{NLL}}(z,c)=-\log p(y=c|x)\]</span>
<p>把两步合起来，NLL 可以直接写成 logits 的函数：</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{NLL}}(z,c)=-\log\frac{e^{z_c}}{\sum_{j=1}^{C}e^{z_j}}=-z_c+\log\sum_{j=1}^{C}e^{z_j}\]</span>
<p>这个式子把训练目标拆成了两部分：第一项 <span displaypfx="inline-" class="mathjax-container">\(-z_c\)</span> 要求真实类别的 logit 足够大；第二项 <span displaypfx="inline-" class="mathjax-container">\(\log\sum_j e^{z_j}\)</span> 是归一化项（log-sum-exp），它把所有类别的竞争都算进去。因此训练并不是单独把正确类别分数抬高，而是要让它相对其他类别更占优势。</p>
<p>softmax 的平移不变性（Translation Invariance）在这里也能直接看见。对任意常数 <span displaypfx="inline-" class="mathjax-container">\(a\)</span>，若把所有 logits 同时改为 <span displaypfx="inline-" class="mathjax-container">\(z+a\mathbf{1}\)</span>，则 softmax 概率不变，NLL 也不变：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{softmax}(z+a\mathbf{1})_i=\mathrm{softmax}(z)_i,\qquad \ell_{\mathrm{NLL}}(z+a\mathbf{1},c)=\ell_{\mathrm{NLL}}(z,c)\]</span>
<p>因此 logits 的绝对零点没有意义，真正有意义的是类别之间的相对差值。工程实现里常把 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 先整体减去 <span displaypfx="inline-" class="mathjax-container">\(\max_i z_i\)</span>，再计算 softmax 或 log-sum-exp。这样不会改变概率与损失，但能显著降低指数溢出的风险。</p>
<div class="blog_h2"><span class="graybg">损失函数</span></div>
<p>术语区分：假设函数（Hypothesis Function）/模型 <span displaypfx="inline-" class="mathjax-container">\(f_\theta\)</span> 定义“模型在做什么映射”；样本损失（Loss Function）定义在单样本上；代价函数/成本函数（Cost Function）是把样本损失在全数据集上做平均或求和后的经验风险；目标函数（Objective Function）是优化器真正要优化的函数，最常见写法是 <span displaypfx="inline-" class="mathjax-container">\(J(\theta)=L(\theta)+\lambda\Omega(\theta)\)</span>。</p>
<div class="blog_h3"><span class="graybg">回归损失</span></div>
<div class="blog_h4"><span class="graybg">MSE</span></div>
<p>均方误差（Mean Squared Error, MSE）定义为</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{MSE}}(y,\hat y)=(\hat y-y)^2\]</span>
<p>平方的作用是让大误差被放大处罚。若一个样本错 10，另一个样本错 1，那么前者在 MSE 里不是“重 10 倍”，而是“重 100 倍”。因此 MSE 很适合你明确希望重罚大错的场景，也对应前面讲过的高斯噪声假设。</p>
<div class="blog_h4"><span class="graybg">MAE</span></div>
<p>平均绝对误差（Mean Absolute Error, MAE）对应单样本形式</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{MAE}}(y,\hat y)=|\hat y-y|\]</span>
<p>它直接度量偏差大小，对离群点更鲁棒，因为不会像平方那样把大误差急剧放大。若做房价预测，数据中存在一批价格远高于主体分布的豪宅样本时，MAE 往往比 MSE 更稳；这里的“主体分布”指的是样本中占大多数的普通住宅价格区间，而豪宅样本相对它明显偏高。这样一来，哪怕豪宅样本数量不多，它们也会在 MSE 下因为误差被平方而获得过大的影响力。</p>
<div class="blog_h4"><span class="graybg">Huber Loss</span></div>
<p>Huber Loss 结合了 MSE 与 MAE：误差小时像平方误差，误差大时像绝对误差。设阈值 <span displaypfx="inline-" class="mathjax-container">\(\delta\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[\ell_\delta(r)=\begin{cases}\frac{1}{2}r^2,&amp;|r|\le \delta\\ \delta(|r|-\frac{1}{2}\delta),&amp;|r|&gt;\delta\end{cases},\quad r=\hat y-y\]</span>
<p>这条式子的直觉是：小误差区间内保持平滑、便于优化；大误差区间内降低对离群点的过度敏感。它像“正常误差严肃处理，极端异常别让它一票否决整个模型”。</p>
<div class="blog_h3"><span class="graybg">分类损失</span></div>
<div class="blog_h4"><span class="graybg">交叉熵损失（Binary）</span></div>
<p>二分类交叉熵（Binary Cross-Entropy, BCE）用来训练输出概率 <span displaypfx="inline-" class="mathjax-container">\(p\in(0,1)\)</span> 的二分类器。这里 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 通常表示模型预测“正类”的概率， <span displaypfx="inline-" class="mathjax-container">\(y\in\{0,1\}\)</span> 是真实标签： <span displaypfx="inline-" class="mathjax-container">\(y=1\)</span> 表示正类， <span displaypfx="inline-" class="mathjax-container">\(y=0\)</span> 表示负类。</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{BCE}}(y,p)=-\Big(y\log p+(1-y)\log(1-p)\Big)\]</span>
<p>左边的 <span displaypfx="inline-" class="mathjax-container">\(\ell_{\mathrm{BCE}}(y,p)\)</span> 表示“单个样本在真实标签为 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>、模型预测正类概率为 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 时的 BCE 损失值”。也就是说，这不是新的概率，而是一个标量惩罚：预测越符合真实标签，它越小；预测越违背真实标签，它越大。</p>
<p>这条公式会根据 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 的取值自动选择应当惩罚哪一项。当 <span displaypfx="inline-" class="mathjax-container">\(y=1\)</span> 时，式中的 <span displaypfx="inline-" class="mathjax-container">\((1-y)=0\)</span>，因此第二项消失，损失化简为 <span displaypfx="inline-" class="mathjax-container">\(-\log p\)</span>；当 <span displaypfx="inline-" class="mathjax-container">\(y=0\)</span> 时，第一项中的 <span displaypfx="inline-" class="mathjax-container">\(y=0\)</span>，因此第一项消失，损失化简为 <span displaypfx="inline-" class="mathjax-container">\(-\log(1-p)\)</span>。于是它惩罚的本质就是：让真实类别对应的概率尽可能高。</p>
<p>数值例子（自然对数）：若 <span displaypfx="inline-" class="mathjax-container">\(y=1\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(p=0.9\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\ell\approx 0.105\)</span>；若 <span displaypfx="inline-" class="mathjax-container">\(p=0.1\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\ell\approx 2.303\)</span>。正确但不自信会被罚，错误且自信会被重罚。</p>
<div class="blog_h4"><span class="graybg">交叉熵损失（Categorical）</span></div>
<p>多分类交叉熵（Categorical Cross-Entropy, CE）与 softmax 通常配套使用。这里 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 是真实分布（Ground-Truth Distribution）在第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 类上的概率质量， <span displaypfx="inline-" class="mathjax-container">\(p_i\)</span> 是模型预测分布（Predicted Distribution）在第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 类上的概率。若类别总数为 <span displaypfx="inline-" class="mathjax-container">\(C\)</span>，则单样本损失写成：</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{CE}}(y,p)=-\sum_{i=1}^{C} y_i\log p_i\]</span>
<p>左边的 <span displaypfx="inline-" class="mathjax-container">\(\ell_{\mathrm{CE}}(y,p)\)</span> 表示“当真实标签分布为 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>、模型预测分布为 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 时，这个样本对应的交叉熵损失值”。其本质是：<span style="background-color: #c0c0c0;">用真实标签分布 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 作为权重，对预测分布的负对数概率 <span displaypfx="inline-" class="mathjax-container">\(-\log p_i\)</span> 做加权平均</span>。</p>
<p>当 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 是 one-hot 分布时，只有真实类别 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 那一维的权重为 1，其余维度权重都为 0，因此求和会自动塌缩成单项：</p>
<span displaypfx="" class="mathjax-container">\[\ell=-\log p_c\]</span>
<p>这正是分类任务里最常见的形式。它与最大似然估计（Maximum Likelihood Estimation, MLE）完全一致：最小化交叉熵等价于最大化真实类别的对数似然。</p>
<p>若 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 不再是 one-hot，而是一个在多个类别上分配了非零概率质量的分布，那么交叉熵就不再只读取单个类别 <span displaypfx="inline-" class="mathjax-container">\(p_c\)</span>，而是会对整条标签分布做加权。常见来源有两类。</p>
<p>第一类是软标签（Soft Label）。它指真实监督信号本身就是一个概率分布，而不是“只有一个绝对正确类别”的硬标签（Hard Label）。例如一张图像可能被标注为“70% 像猫、30% 像狐狸”，或一个样本本身就带有多标注者投票汇总后的类别分布。在这种情况下， <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 直接表示第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 类的目标概率，交叉熵自然要对所有类别一起计算。</p>
<p>第二类是教师分布蒸馏（Knowledge Distillation from Teacher Distribution）。知识蒸馏（Knowledge Distillation）的做法是：不用人工标签单独监督学生模型（Student Model），而是让学生去拟合教师模型（Teacher Model）给出的类别分布。若教师在某个样本上输出 <span displaypfx="inline-" class="mathjax-container">\(q\)</span>，学生输出 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>，则训练目标常包含 <span displaypfx="inline-" class="mathjax-container">\(-\sum_i q_i\log p_i\)</span> 这样的交叉熵或等价的 KL 散度项。它传递的不只是“哪一类是对的”，还传递“其余类别分别有多像”，因此常被称为暗知识（Dark Knowledge）。</p>
<p>这两种情况的共同点是：标签本身已经不是单点答案，而是一条分布。于是交叉熵的计算对象就不再只是“真实类别那一项”，而是整个目标分布与预测分布之间的匹配程度。Label Smoothing 也会把 one-hot 改成软分布，但它承担的是训练目标修正的角色，因此放在后文“分类任务正则化”里更清晰。</p>
<p>从信息论角度看，交叉熵的标准定义是两个分布 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 之间的</p>
<span displaypfx="" class="mathjax-container">\[H(P,Q)=-\sum_x P(x)\log Q(x)\]</span>
<p>分类里的 <span displaypfx="inline-" class="mathjax-container">\(\ell_{\mathrm{CE}}(y,p)\)</span> 与这个定义并不矛盾，它只是把信息论中的 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 分别具体化成了“单个样本对应的真实标签分布 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>”与“模型在这个样本上的预测分布 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>”。若标签是 one-hot，那么 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 退化成一个只在真实类别处取值为 1 的离散分布，于是信息论里的交叉熵自然退化成 <span displaypfx="inline-" class="mathjax-container">\(-\log p_c\)</span>。</p>
<p>进一步地，交叉熵与 KL 散度（Kullback–Leibler Divergence）的关系可以直接写成：</p>
<span displaypfx="" class="mathjax-container">\[H(P,Q)=H(P)+D_{\mathrm{KL}}(P\|Q)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(H(P)=-\sum_x P(x)\log P(x)\)</span> 是真实分布 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 自身的熵（Entropy），只由数据分布决定； <span displaypfx="inline-" class="mathjax-container">\(D_{\mathrm{KL}}(P\|Q)=\sum_x P(x)\log\frac{P(x)}{Q(x)}\)</span> 是 KL 散度，用来衡量预测分布 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 相对真实分布 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 的偏离程度。于是，在训练数据固定时， <span displaypfx="inline-" class="mathjax-container">\(H(P)\)</span> 是常数，最小化交叉熵 <span displaypfx="inline-" class="mathjax-container">\(H(P,Q)\)</span> 就等价于最小化 <span displaypfx="inline-" class="mathjax-container">\(D_{\mathrm{KL}}(P\|Q)\)</span>。</p>
<p>因此，“最小化交叉熵就是最小化 KL 散度”这句话在监督学习里通常成立，但更准确的表述是：<span style="background-color: #c0c0c0;">在真实分布固定不变时，最小化交叉熵与最小化预测分布对真实分布的 KL 偏离是等价的</span>。KL 散度确实可以理解为“与真实分布的差异或偏离”，但它不是对称距离（Symmetric Distance）：一般有 <span displaypfx="inline-" class="mathjax-container">\(D_{\mathrm{KL}}(P\|Q) \neq D_{\mathrm{KL}}(Q\|P)\)</span>，也不满足严格距离函数的三角不等式，因此更准确的名称是分布失配（distribution mismatch）或相对熵（relative entropy）。</p>
<p>若进一步把 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 写成 softmax 作用在 logits <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 上，则多分类交叉熵可直接写成：</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{CE}}(z,c)=-\log\mathrm{softmax}(z)_c=-z_c+\log\sum_{j=1}^{C}e^{z_j}\]</span>
<p>这条式子把任务头和损失函数直接接起来：线性头先输出 logits，softmax 把它们归一化为概率，交叉熵再读取真实类别的负对数概率。由于 softmax 具有平移不变性，给所有 logits 同时加上同一个常数不会改变这个损失，因此实现中通常直接从 logits 计算交叉熵（log-sum-exp 形式），并先减去 <span displaypfx="inline-" class="mathjax-container">\(\max_j z_j\)</span> 做数值稳定，而不是显式先算 softmax 再取 log。</p>
<div class="blog_h4"><span class="graybg">Focal Loss</span></div>
<p>Focal Loss 常用于类别极不平衡的分类任务，尤其是目标检测（Object Detection）这类“负样本远多于正样本”的场景。它不是简单换掉交叉熵，而是在交叉熵前再乘一个与样本难度相关的调制因子，使已经分得很对的容易样本贡献变小，把梯度预算更多留给困难样本：</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{focal}}(p_t)=-(1-p_t)^\gamma\log p_t\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(p_t\)</span> 表示“真实类别对应的预测概率”：若真实标签 <span displaypfx="inline-" class="mathjax-container">\(y=1\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(p_t=p\)</span>；若 <span displaypfx="inline-" class="mathjax-container">\(y=0\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(p_t=1-p\)</span>。因此 <span displaypfx="inline-" class="mathjax-container">\(-\log p_t\)</span> 就是普通交叉熵，而前面的 <span displaypfx="inline-" class="mathjax-container">\((1-p_t)^\gamma\)</span> 是额外加上的难度调制项。参数 <span displaypfx="inline-" class="mathjax-container">\(\gamma\ge 0\)</span> 控制聚焦强度： <span displaypfx="inline-" class="mathjax-container">\(\gamma=0\)</span> 时，Focal Loss 退化回普通交叉熵； <span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span> 越大，对容易样本的压低越明显。</p>
<p>这个机制的关键在于样本难度如何反映到权重上。若某个样本已经分得很对，例如真实类别概率 <span displaypfx="inline-" class="mathjax-container">\(p_t=0.99\)</span>，则调制因子 <span displaypfx="inline-" class="mathjax-container">\((1-p_t)^\gamma\)</span> 会非常小；以 <span displaypfx="inline-" class="mathjax-container">\(\gamma=2\)</span> 为例，权重大约只有 <span displaypfx="inline-" class="mathjax-container">\((0.01)^2=10^{-4}\)</span>，这意味着它对总损失和梯度的影响被大幅削弱。相反，若某个样本很难，例如 <span displaypfx="inline-" class="mathjax-container">\(p_t=0.2\)</span>，则权重约为 <span displaypfx="inline-" class="mathjax-container">\((0.8)^2=0.64\)</span>，其损失会被较大程度保留。于是训练过程不再被海量“早就分对的简单样本”主导，而会持续关注误分样本、边界样本和少数类样本。</p>
<p>目标检测是最典型的应用例子。以单阶段检测器（One-stage Detector）为例，一张图像上往往有成千上万个候选框（Anchors），但真正包含目标的正样本只占极少数；绝大多数候选框都是背景。若直接使用普通交叉熵，训练会被这些“背景且容易判断”的负样本淹没：它们单个损失虽小，但数量太多，累积后仍然主导梯度。Focal Loss 的作用正是把这批容易背景样本的权重压下去，让模型把更多注意力放在少数正样本、遮挡目标、边界模糊目标，以及那些看起来像目标但其实是背景的困难负样本上。这样做通常会显著改善长尾检测与前景-背景极不平衡时的训练效果。</p>
<div class="blog_h4"><span class="graybg">Hinge Loss</span></div>
<p>Hinge Loss 是 SVM 常用的分类损失：</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{hinge}}(y,f)=\max(0,1-yf),\quad y\in\{-1,+1\}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 是模型分数而不是概率。当 <span displaypfx="inline-" class="mathjax-container">\(yf\ge 1\)</span> 时，说明不仅分类正确，而且留出了足够间隔，损失为 0；当 <span displaypfx="inline-" class="mathjax-container">\(yf&lt;1\)</span> 时，就要受罚。它强调的不只是“分对”，而是“分对且留有安全距离”。</p>
<div class="blog_h3"><span class="graybg">度量学习损失</span></div>
<p>度量学习（Metric Learning）不直接预测类别，而是学习一个表示空间，让“应该相似的样本靠近，不该相似的样本拉远”。这类损失在检索、人脸识别、推荐召回和 embedding 学习中非常常见。</p>
<div class="blog_h4"><span class="graybg">Contrastive Loss</span></div>
<p>Contrastive Loss 处理样本对（pair）。若一对样本应相似，则拉近它们；若应不同，则至少推开到某个间隔 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 之外：</p>
<span displaypfx="" class="mathjax-container">\[\ell=y\,d^2+(1-y)\max(0,m-d)^2\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(d\)</span> 是两者在嵌入空间中的距离， <span displaypfx="inline-" class="mathjax-container">\(y=1\)</span> 表示正对， <span displaypfx="inline-" class="mathjax-container">\(y=0\)</span> 表示负对。它像“朋友要坐得近，陌生人至少别挤在一起”。</p>
<div class="blog_h4"><span class="graybg">Triplet Loss</span></div>
<p>Triplet Loss 使用三元组：锚点（Anchor）、正样本（Positive）、负样本（Negative）。目标是让锚点离正样本比离负样本更近至少一个 margin：</p>
<span displaypfx="" class="mathjax-container">\[\ell=\max\big(0,\ d(a,p)-d(a,n)+m\big)\]</span>
<p>这条式子表达的是一种相对排序约束，而不是绝对相似度分数。它非常适合“谁比谁更像”的任务，例如人脸验证：同一个人的两张照片应比不同人的照片更近。</p>
<div class="blog_h4"><span class="graybg">InfoNCE</span></div>
<p>InfoNCE 是现代对比学习最常见的损失之一。对一个锚点来说，它把正样本放进一堆候选里，要求模型把正样本打分最高：</p>
<span displaypfx="" class="mathjax-container">\[-\log \frac{\exp(\mathrm{sim}(z_i,z_i^+)/\tau)}{\sum_j \exp(\mathrm{sim}(z_i,z_j)/\tau)}\]</span>
<p>分子是正确配对，分母是所有候选。这个结构和 softmax 分类非常像，只不过类别不再是固定标签，而是“在一堆候选里，谁才是真正匹配的那个”。在大语言模型 embedding、图像表征学习和多模态对齐里，它几乎是标准配置。</p>
<div class="blog_h2"><span class="graybg">任务头（Task Head）</span></div>
<p>任务头（Task Head）是把主干网络（Backbone）产出的隐藏表示（Hidden Representation）映射到具体任务输出空间的模块。主干负责抽取通用特征，任务头负责把特征“读出来”并对齐到目标形式（类别、数值、序列标签、跨度、关系等）。在工程上，绝大多数“用 Transformer 做下游任务”都可以写成：<span style="background-color: #c0c0c0;">Transformer backbone + task head + task loss</span>。</p>
<div class="blog_h3"><span class="graybg">中间表示、logits 与任务头的关系</span></div>
<p>主干网络输出的隐藏表示（Hidden Representation）是任务头的输入，任务头则是把这种内部表示读成具体任务输出的最后一层或最后几层变换。若把主干输出记为 <span displaypfx="inline-" class="mathjax-container">\(h\)</span>、<span displaypfx="inline-" class="mathjax-container">\(H\)</span> 或更一般的张量 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{H}\)</span>，则任务头通常先做一次线性读出（Linear Readout）得到分数，再视任务类型决定是否接 sigmoid、softmax、CRF 解码或直接保留实数输出。</p>
<p>logits 就是在这个读出阶段最常见的中间产物。它们是<span style="background-color: #c0c0c0;">任务头输出、但尚未归一化或尚未解码的原始分数</span>。例如，多分类头常先产生 <span displaypfx="inline-" class="mathjax-container">\(z\in\mathbb{R}^{C}\)</span>，这里 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 是类别数， <span displaypfx="inline-" class="mathjax-container">\(z_c\)</span> 表示模型对第 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 类的偏好分数；softmax 之后这些分数才变成概率。对 token 分类任务，task head 产生的不是单个向量，而是一整张 logits 矩阵 <span displaypfx="inline-" class="mathjax-container">\(Z\in\mathbb{R}^{L\times C}\)</span>；对语言模型，输出则是词表 logits <span displaypfx="inline-" class="mathjax-container">\(Z\in\mathbb{R}^{L\times V}\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 是词表大小。</p>
<p>因此，关系可以概括为：<span style="background-color: #c0c0c0;">输入先被 backbone 编码成中间表示，中间表示再被 task head 读成 logits 或其他任务分数，最后再由概率映射、解码器或损失函数把这些分数变成最终预测</span>。logits 不是所有中间表示的统称，而是“距离最终任务输出只差一步”的那类任务分数；它们通常由任务头生成，而不是由主干网络中间每一层都显式生成。</p>
<div class="blog_h3"><span class="graybg">任务头输出对照表</span></div>
<p>不同任务头的差异，最核心地体现在“直接输出什么张量、这些张量后面还要经过什么处理”这两个问题上。下面这张表把常见任务头的输入形状、直接输出和后续处理并列起来，便于从工程实现角度快速对照。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">任务类型</td>
<td style="text-align: center;">任务头常见输入</td>
<td style="text-align: center;">任务头直接输出</td>
<td style="text-align: center;">后续处理</td>
</tr>
</thead>
<tbody>
<tr>
<td>二分类</td>
<td>单样本表示 <span displaypfx="inline-" class="mathjax-container">\(h\in\mathbb{R}^{d}\)</span></td>
<td>标量 logit <span displaypfx="inline-" class="mathjax-container">\(z\in\mathbb{R}\)</span>，或二维 logits <span displaypfx="inline-" class="mathjax-container">\(\mathbf{z}\in\mathbb{R}^{2}\)</span></td>
<td>sigmoid 或 softmax，得到类别概率</td>
</tr>
<tr>
<td>多分类</td>
<td>单样本表示 <span displaypfx="inline-" class="mathjax-container">\(h\in\mathbb{R}^{d}\)</span></td>
<td>类别 logits 向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{z}\in\mathbb{R}^{C}\)</span></td>
<td>softmax 后得到 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 类概率</td>
</tr>
<tr>
<td>多标签分类</td>
<td>单样本表示 <span displaypfx="inline-" class="mathjax-container">\(h\in\mathbb{R}^{d}\)</span></td>
<td>每个标签一个 logit，组成 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{z}\in\mathbb{R}^{C}\)</span></td>
<td>对每一维独立做 sigmoid，而不是在类别间做 softmax</td>
</tr>
<tr>
<td>回归</td>
<td>单样本表示 <span displaypfx="inline-" class="mathjax-container">\(h\in\mathbb{R}^{d}\)</span></td>
<td>实数或向量 <span displaypfx="inline-" class="mathjax-container">\(\hat{\mathbf{y}}\in\mathbb{R}^{m}\)</span></td>
<td>通常不做概率归一化，直接配合回归损失</td>
</tr>
<tr>
<td>Token 分类 / NER</td>
<td>序列表示 <span displaypfx="inline-" class="mathjax-container">\(H\in\mathbb{R}^{L\times d}\)</span></td>
<td>token-level logits 矩阵 <span displaypfx="inline-" class="mathjax-container">\(Z\in\mathbb{R}^{L\times C}\)</span></td>
<td>逐 token softmax，或接 CRF 做全局解码</td>
</tr>
<tr>
<td>语言模型 / 文本生成</td>
<td>序列表示 <span displaypfx="inline-" class="mathjax-container">\(H\in\mathbb{R}^{L\times d}\)</span></td>
<td>词表 logits <span displaypfx="inline-" class="mathjax-container">\(Z\in\mathbb{R}^{L\times V}\)</span></td>
<td>对每个位置在词表维做 softmax，得到 next-token 分布</td>
</tr>
<tr>
<td>跨度抽取（Span Extraction）</td>
<td>序列表示 <span displaypfx="inline-" class="mathjax-container">\(H\in\mathbb{R}^{L\times d}\)</span></td>
<td>起点 logits <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\in\mathbb{R}^{L}\)</span> 与终点 logits <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\in\mathbb{R}^{L}\)</span>，或 span 分数矩阵</td>
<td>在起止位置上做 softmax 或联合评分，输出片段边界</td>
</tr>
<tr>
<td>依存句法 / 关系抽取</td>
<td>成对表示 <span displaypfx="inline-" class="mathjax-container">\(h_i,h_j\)</span> 或序列表示 <span displaypfx="inline-" class="mathjax-container">\(H\)</span></td>
<td>边分数矩阵 <span displaypfx="inline-" class="mathjax-container">\(S\in\mathbb{R}^{L\times L}\)</span>，或关系分数张量 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{S}\in\mathbb{R}^{L\times L\times C}\)</span></td>
<td>argmax、biaffine 解码或图结构约束解码</td>
</tr>
<tr>
<td>度量学习 / 检索</td>
<td>单样本表示 <span displaypfx="inline-" class="mathjax-container">\(h\in\mathbb{R}^{d}\)</span></td>
<td>embedding 向量 <span displaypfx="inline-" class="mathjax-container">\(e\in\mathbb{R}^{d'}\)</span></td>
<td>不直接输出 logits；后续用相似度函数或对比损失比较</td>
</tr>
</tbody>
</table>
<p>这张表的关键在于区分“任务头直接输出什么”和“用户最终看到什么”。很多任务头直接输出的并不是概率，也不是标签，而是 logits、边分数、起止位置分数或 embedding。概率、标签、生成 token、依存边、异常分数等最终结果，通常还需要经过归一化、解码、阈值化或搜索过程才能得到。</p>
<div class="blog_h3"><span class="graybg">分类头</span></div>
<p>分类头（Classification Head）的核心职责，是把主干网络输出的表示 <span displaypfx="inline-" class="mathjax-container">\(h\in\mathbb{R}^{d}\)</span> 变成类别分数（Class Scores）或 logits。最常见的做法是一层线性映射：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{z}=W\mathbf{h}+\mathbf{b}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{h}\in\mathbb{R}^{d}\)</span> 是单个样本的隐藏表示， <span displaypfx="inline-" class="mathjax-container">\(W\)</span> 是任务头权重矩阵， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\)</span> 是偏置向量， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{z}\)</span> 是未归一化类别分数。分类任务的关键区别不在于“有没有线性层”，而在于：<span style="background-color: #c0c0c0;">输出空间是否互斥、每个样本允许几个标签成立、以及这些分数之后接什么归一化与损失</span>。</p>
<p>直觉上，这个线性头就是一个“可学习的读出（Readout）”：在高维表示空间里用超平面（Hyperplane）切分区域，或用线性映射把表示投影到目标坐标系。这里的“读出”指的是：主干网络先把输入编码成内部表示，而任务头再把这种内部表示转换成模型真正需要输出的量，例如类别 logits、词表 logits、回归值、span 分数或关系分数。换言之，读出不是“再提特征”，而是<span style="background-color: #c0c0c0;">把已经形成的表示翻译成任务空间中的可判定分数</span>。</p>
<div class="blog_h4"><span class="graybg">二分类</span></div>
<p>二分类（Binary Classification）要求每个样本只在两个互斥类别中选一个，例如“垃圾 / 非垃圾”“欺诈 / 正常”“阳性 / 阴性”。最常见的写法是输出一个标量 logit：</p>
<span displaypfx="" class="mathjax-container">\[z=\mathbf{w}^\top \mathbf{h}+b\]</span>
<p>然后通过 sigmoid 得到正类概率：</p>
<span displaypfx="" class="mathjax-container">\[p=P(y=1\mid x)=\sigma(z)=\frac{1}{1+e^{-z}}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 是模型对正类的原始偏好分数， <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 是正类概率，负类概率则是 <span displaypfx="inline-" class="mathjax-container">\(1-p\)</span>。训练时通常配合二分类交叉熵（Binary Cross-Entropy, BCE）。工程上也可以输出二维 logits <span displaypfx="inline-" class="mathjax-container">\(\mathbf{z}\in\mathbb{R}^{2}\)</span>，再接 softmax；但若任务确实只有两个互斥类别，单 logit + sigmoid 更常见，也更经济。</p>
<div class="blog_h4"><span class="graybg">多分类</span></div>
<p>多分类（Multi-class Classification）要求每个样本在 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 个互斥类别中恰好选一个类别，例如“猫 / 狗 / 鸟”“体育 / 财经 / 科技 / 娱乐”。此时任务头不会只输出一个标量，而是输出长度为 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 的 logits 向量：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{z}=W\mathbf{h}+\mathbf{b},\qquad W\in\mathbb{R}^{C\times d},\ \mathbf{z}\in\mathbb{R}^{C}\]</span>
<p>其中第 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 维 <span displaypfx="inline-" class="mathjax-container">\(z_c\)</span> 表示模型对第 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 个类别的原始偏好分数。由于这些类别互斥，后续通常接 softmax，把整组分数归一化成概率分布：</p>
<span displaypfx="" class="mathjax-container">\[p(y=c\mid x)=\frac{e^{z_c}}{\sum_{j=1}^{C}e^{z_j}}\]</span>
<p>这条式子的含义很直接：分子 <span displaypfx="inline-" class="mathjax-container">\(e^{z_c}\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 类的相对强度，分母 <span displaypfx="inline-" class="mathjax-container">\(\sum_{j=1}^{C}e^{z_j}\)</span> 把所有类别一起归一化，因此最终得到的 <span displaypfx="inline-" class="mathjax-container">\(p(y=c\mid x)\)</span> 落在 <span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span> 之间，且所有类别概率和为 1。也正因为总和必须为 1，多分类头天然表达的是“类间竞争”：某一类概率上升，其他类的总概率就必须下降。</p>
<p>训练时通常直接把 logits <span displaypfx="inline-" class="mathjax-container">\(\mathbf{z}\)</span> 输入多分类交叉熵（Cross-Entropy）损失，而不是手动先算 softmax 再取对数。这样做的原因是数值稳定：损失函数内部会把 softmax 与对数合并成 log-sum-exp 形式，避免指数溢出。推理时若只关心类别标签，直接取 <span displaypfx="inline-" class="mathjax-container">\(\arg\max_c z_c\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(\arg\max_c p(y=c\mid x)\)</span> 即可；若需要概率、阈值或校准，再显式使用 softmax。</p>
<div class="blog_h4"><span class="graybg">多标签分类</span></div>
<p>多标签分类（Multi-label Classification）与多分类名字相近，但任务结构完全不同。它不是“从多个类里选一个”，而是<span style="background-color: #c0c0c0;">同一个样本可以同时拥有多个标签</span>。例如一篇文章可以同时属于“AI、NLP、Transformer”，一张图片可以同时打上“室内、人物、宠物”三个标签。</p>
<p>这种任务里，标签之间不再互斥，因此不能使用 softmax。若仍用 softmax，所有标签概率会被强制归一化为和 1，相当于错误地假设“只能有一个标签成立”。多标签头通常输出 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 个 logits：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{z}=W\mathbf{h}+\mathbf{b},\qquad \mathbf{z}\in\mathbb{R}^{C}\]</span>
<p>但后续不是做一次整体 softmax，而是对每一维独立做 sigmoid：</p>
<span displaypfx="" class="mathjax-container">\[p_i=\sigma(z_i),\qquad i=1,\dots,C\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(p_i\)</span> 表示“第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个标签是否成立”的独立概率。训练时通常使用逐维二分类交叉熵，即把每个标签都当作一个独立的二分类问题，再在标签维上求和或取平均：</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{multi\mbox{-}label}}=-\sum_{i=1}^{C}\Big(y_i\log p_i+(1-y_i)\log(1-p_i)\Big)\]</span>
<p>因此，多标签头的关键不是“输出维度也叫 <span displaypfx="inline-" class="mathjax-container">\(C\)</span>”，而是<span style="background-color: #c0c0c0;">这 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 维之间不竞争，每一维都在独立回答一个 yes/no 问题</span>。推理时也不是取单个 argmax，而是对每一维做阈值判断，例如输出所有满足 <span displaypfx="inline-" class="mathjax-container">\(p_i&gt;0.5\)</span> 的标签，或按业务分别设定不同标签阈值。</p>
<div class="blog_h3"><span class="graybg">回归头</span></div>
<p>回归头（Regression Head）不负责在离散类别之间做判别，而是直接输出连续数值（Continuous Value）或连续向量。最常见的形式仍然是一层线性映射：</p>
<span displaypfx="" class="mathjax-container">\[\hat{\mathbf{y}}=W\mathbf{h}+\mathbf{b},\qquad \hat{\mathbf{y}}\in\mathbb{R}^{m}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 是回归目标维度。若 <span displaypfx="inline-" class="mathjax-container">\(m=1\)</span>，就是标量回归，例如房价预测、评分预测、温度预测；若 <span displaypfx="inline-" class="mathjax-container">\(m&gt;1\)</span>，则是多维回归，例如边界框坐标回归、姿态参数回归或多目标数值预测。</p>
<p>回归头通常不接 softmax，也不强制输出落在 <span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span>。原因很简单：回归任务关心的是数值大小本身，而不是类别概率竞争。训练时常配合均方误差（MSE）、平均绝对误差（MAE）或 Huber Loss。只有当目标值本身有明确范围约束时，才会额外接 sigmoid、tanh 或其他变换，把输出压到指定区间。</p>
<div class="blog_h3"><span class="graybg">语言模型头（LM Head）</span></div>
<p>语言模型头（Language Modeling Head, LM Head）是把隐藏表示映射回词表空间的输出头。只要任务目标是“在若干位置上对词表中的 token 做预测”，就会出现这一类头；因此它不只存在于 Decoder-only 大模型，也存在于 Encoder-only 的掩码语言模型（Masked Language Model, MLM）以及 Encoder-Decoder 的生成端。它读取主干网络在每个位置输出的隐藏状态 <span displaypfx="inline-" class="mathjax-container">\(H\in\mathbb{R}^{L\;\times d_{\text{model}}}\)</span>，并把每个位置的表示投影到整张词表空间，得到词表 logits：</p>
<span displaypfx="" class="mathjax-container">\[Z = HW_{\text{vocab}} + \mathbf{1}b^\top,\quad W_{\text{vocab}}\in\mathbb{R}^{d_{\text{model}}\;\times {V}},\ Z\in\mathbb{R}^{L\;\times {V}}\]</span>
<p>这条式子先描述整体矩阵，再自然落到单个位置。这里 <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 是序列长度，表示当前一共有多少个位置； <span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\)</span> 是隐藏维度； <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 是词表大小（Vocabulary Size）。因此，隐藏状态矩阵 <span displaypfx="inline-" class="mathjax-container">\(H\in\mathbb{R}^{L\;\times d_{\text{model}}}\)</span> 的每一行对应一个位置的表示，输出权重矩阵 <span displaypfx="inline-" class="mathjax-container">\(W_{\text{vocab}}\in\mathbb{R}^{d_{\text{model}}\;\times {V}}\)</span> 的每一列对应“词表中某个 token 作为候选答案时的读出方向”，最终得到的 <span displaypfx="inline-" class="mathjax-container">\(Z\in\mathbb{R}^{L\;\times {V}}\)</span> 就是一个“位置 <span displaypfx="inline-" class="mathjax-container">\(\times\)</span> 词表”的打分表：行表示位置，列表示候选 token。</p>
<p>把这张打分表聚焦到第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 行，就得到 <span displaypfx="inline-" class="mathjax-container">\(Z_{t,:}\in\mathbb{R}^{V}\)</span>，也就是“第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个位置对整张词表所有 token 的一整行 logits”。这里 <span displaypfx="inline-" class="mathjax-container">\(H_t\in\mathbb{R}^{d_{\text{model}}}\)</span> 表示第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个位置的隐藏状态，冒号 <span displaypfx="inline-" class="mathjax-container">\(:\)</span> 表示该行的全部列；若写成 <span displaypfx="inline-" class="mathjax-container">\(Z_{:,i}\)</span>，则表示第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 列，也就是“所有位置对第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 token 的分数”。继续缩小到单个元素 <span displaypfx="inline-" class="mathjax-container">\(Z_{t,i}\)</span>，它表示“在第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个位置，把词表中第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 token 作为下一个输出时的原始分数”。</p>
<p>把矩阵形式按单个位置、单个候选 token 展开后，打分可写成：</p>
<span displaypfx="" class="mathjax-container">\[Z_{t,i}=H_t\cdot W_{\text{vocab},:,i}+b_i\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(W_{\text{vocab},:,i}\)</span> 表示输出权重矩阵的第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 列，也就是与词表第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 token 对应的参数向量； <span displaypfx="inline-" class="mathjax-container">\(b_i\)</span> 是该 token 的偏置项。这个公式的读法是：拿第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个位置的隐藏表示 <span displaypfx="inline-" class="mathjax-container">\(H_t\)</span>，与“token <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 的读出向量”做一次点积，再加偏置，就得到该 token 在该位置的 logit。</p>
<p>LM Head 与分类头的根本区别在于输出空间。普通分类头通常只需输出 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 个类别分数；LM Head 则要在每个位置输出 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 个分数，而 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 往往达到几万甚至几十万。因此，LM Head 本质上是“逐位置的大规模多分类器”：每一步都在问“下一个 token 应该是词表中的哪一个”。</p>
<p>同一个 LM Head 公式，在不同 Transformer 架构里的使用方式并不相同。对 Encoder-only 模型，LM Head 通常服务于掩码语言建模：模型先用双向注意力得到各位置隐藏状态，再只在被遮蔽的位置上读取 <span displaypfx="inline-" class="mathjax-container">\(Z_{t,:}\)</span> 来预测原 token；这一过程通常是一次性编码，不涉及自回归生成，也没有 KV Cache 逐步增长的问题。对 Decoder-only 模型，LM Head 用于 next-token 预测：第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个位置的隐藏状态对应预测 <span displaypfx="inline-" class="mathjax-container">\(x_{t+1}\)</span>，推理时会逐步生成，因此会配合因果注意力（Causal Self-Attention）和 KV Cache。对 Encoder-Decoder 模型，LM Head 位于解码器一侧：编码器先产出源序列表示，解码器再在因果自注意力与交叉注意力（Cross-Attention）的共同作用下生成目标侧隐藏状态，最后由 LM Head 映射到词表。</p>
<p>训练时，自回归语言模型（Autoregressive Language Model）通常采用 next-token 目标：第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个位置的隐藏状态 <span displaypfx="inline-" class="mathjax-container">\(H_t\)</span> 用来预测真实的下一个 token <span displaypfx="inline-" class="mathjax-container">\(x_{t+1}\)</span>。对应损失可写成：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{\mathrm{LM}}=-\sum_{t=1}^{L-1}\log \frac{\exp(Z_{t,x_{t+1}})}{\sum_{i=1}^{V}\exp(Z_{t,i})}\]</span>
<p>这个损失公式也可以逐项拆开理解。左边的 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}_{\mathrm{LM}}\)</span> 是整段序列的语言模型损失；求和下标 <span displaypfx="inline-" class="mathjax-container">\(t=1,\dots,L-1\)</span> 表示：前 <span displaypfx="inline-" class="mathjax-container">\(L-1\)</span> 个位置都要各自预测一次下一个 token。分子里的 <span displaypfx="inline-" class="mathjax-container">\(\exp(Z_{t,x_{t+1}})\)</span> 表示“真实下一个 token 在第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个位置对应的指数化分数”；这里 <span displaypfx="inline-" class="mathjax-container">\(x_{t+1}\)</span> 是真实的下一个 token id，所以 <span displaypfx="inline-" class="mathjax-container">\(Z_{t,x_{t+1}}\)</span> 表示在位置 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 对这个真实 token 的 logit。分母 <span displaypfx="inline-" class="mathjax-container">\(\sum_{i=1}^{V}\exp(Z_{t,i})\)</span> 则把整张词表所有候选 token 的分数全部加起来做归一化。因此整个分式就是“在位置 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 预测真实下一个 token 的概率”，外面的负对数再把它变成训练损失。</p>
<p>推理时则不是直接输出标签，而是先对 <span displaypfx="inline-" class="mathjax-container">\(Z_{t,:}\)</span> 做 softmax 得到下一个 token 的条件分布。这里的 <span displaypfx="inline-" class="mathjax-container">\(Z_{t,:}\)</span> 不是一个标量，而是长度为 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 的向量，包含位置 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 对整张词表每个 token 的分数。对这整行做 softmax 后，得到的是：</p>
<span displaypfx="" class="mathjax-container">\[p(x_{t+1}=i\mid x_{\le t})=\frac{e^{Z_{t,i}}}{\sum_{j=1}^{V}e^{Z_{t,j}}},\qquad i=1,\dots,V\]</span>
<p>这条式子表示：在已经看到前缀 <span displaypfx="inline-" class="mathjax-container">\(x_{\le t}\)</span> 的条件下，下一个 token 取词表中第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个词的概率是多少。随后再配合贪心搜索（Greedy Decoding）、束搜索（Beam Search）、温度采样（Temperature Sampling）、top-k / top-p 等策略，从这组概率中选出真正生成的 token。也就是说，LM Head 负责把隐藏表示变成“词表级候选分数”，真正的文本生成还要再经过一层解码（Decoding）策略。</p>
<p>很多 LLM 还会使用权重共享（Weight Tying）：把输入嵌入表 <span displaypfx="inline-" class="mathjax-container">\(E\in\mathbb{R}^{V\;\times d_{\text{model}}}\)</span> 与输出头绑定，使 <span displaypfx="inline-" class="mathjax-container">\(W_{\text{vocab}}=E^\top\)</span>。这样一来，输入端“一个 token 的向量表示”与输出端“一个 token 作为候选答案时的原型向量”共用同一套参数空间。它通常既能减少参数量，也让输入和输出语义空间保持更强一致性。</p>
<p>从工程角度看，LM Head 往往比分类头更贵，因为它直接与词表大小 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 成正比：词表越大，最后一层的矩阵乘法、softmax 和采样都越重。因此，大模型的推理优化常会专门围绕 LM Head 展开，例如 fused softmax、采样优化、logits processor、词表裁剪或 speculative decoding 等。本质上，这些优化都在解决同一个问题：<span style="background-color: #c0c0c0;">如何更高效地从词表 logits 走到最终生成 token</span>。</p>
<div class="blog_h3"><span class="graybg">序列标注头（Token Classification）</span></div>
<p>序列标注（Sequence Labeling）/Token 分类（Token Classification）要求对每个 token 预测一个标签（例如 NER）。设主干输出 <span displaypfx="inline-" class="mathjax-container">\(H\in\mathbb{R}^{L\times d}\)</span>（长度 <span displaypfx="inline-" class="mathjax-container">\(L\)</span>，隐藏维 <span displaypfx="inline-" class="mathjax-container">\(d\)</span>），则逐 token 线性头为：</p>
<span displaypfx="" class="mathjax-container">\[Z=HW^\top+\mathbf{1}b^\top,\quad W\in\mathbb{R}^{C\times d},\ Z\in\mathbb{R}^{L\times C}\]</span>
<p>对第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个 token，用 softmax 得到 <span displaypfx="inline-" class="mathjax-container">\(p(y_t|x)\)</span> 并用逐 token 交叉熵训练。该做法把每个位置的标签看作条件独立，能工作，但会忽略标签之间的结构约束（例如 BIO 体系中不允许 <pre class="crayon-plain-tag">I-ORG</pre> 紧跟 <pre class="crayon-plain-tag">O</pre>）。</p>
<p>例：对 “Apple Inc. is in California” 的 NER（Named Entity Recognition），合理标签序列可能是 <pre class="crayon-plain-tag">B-ORG I-ORG O O B-LOC</pre>。若逐 token softmax 独立预测，模型可能输出不合法的组合；这类“结构错误”通常需要结构化任务头（Structured Head）来显式建模。</p>
<div class="blog_h4"><span class="graybg">条件随机场（CRF）</span></div>
<p>线性链条件随机场（Linear-chain Conditional Random Field, CRF）在 token 分类头上增加一个转移矩阵（Transition Matrix）<span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{C\times C}\)</span>，对整段标签序列做归一化建模。令发射分数（Emission Score）为 <span displaypfx="inline-" class="mathjax-container">\(s_t(y)=Z_{t,y}\)</span>，则序列 <span displaypfx="inline-" class="mathjax-container">\(y_{1:L}\)</span> 的总分为：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{score}(x,y)=\sum_{t=1}^{L}\Big(A_{y_{t-1},y_t}+s_t(y_t)\Big)\]</span>
<p>并定义条件概率：</p>
<span displaypfx="" class="mathjax-container">\[p(y|x)=\frac{\exp(\mathrm{score}(x,y))}{\sum_{y'}\exp(\mathrm{score}(x,y'))}\]</span>
<p>训练最小化负对数似然（Negative Log-Likelihood）；解码用 Viterbi 算法（动态规划）求 <span displaypfx="inline-" class="mathjax-container">\(\arg\max_y \mathrm{score}(x,y)\)</span>。CRF 的收益是把“标签合法性/连贯性”学进转移项，从而显著减少结构错误。</p>
<div class="blog_h3"><span class="graybg">双仿射头（Biaffine）</span></div>
<p>双仿射（Biaffine）任务头常用于“成对打分”（Pairwise Scoring），例如依存句法（Dependency Parsing）里的“当前词应依附到哪个词”，或关系抽取（Relation Extraction）里的“两个实体之间是否存在某种关系”。它的名字可以按层级直接理解：对一个变量， <span displaypfx="inline-" class="mathjax-container">\(Wx\)</span> 是线性， <span displaypfx="inline-" class="mathjax-container">\(Wx+b\)</span> 是仿射；对两个变量， <span displaypfx="inline-" class="mathjax-container">\(h_i^\top U h_j\)</span> 是双线性（Bilinear）；在这个双线性项之外再加上线性项和偏置项，就得到双仿射（Biaffine）。因此，双仿射的本质是：<span style="background-color: #c0c0c0;">既建模两个表示之间的交互，又保留各自单独的角色偏好</span>。</p>
<span displaypfx="" class="mathjax-container">\[s(i,j)=h_i^\top U h_j + w^\top [h_i;h_j] + b\]</span>
<p>这条式子可以逐项拆开读。 <span displaypfx="inline-" class="mathjax-container">\(h_i,h_j\in\mathbb{R}^{d}\)</span> 是两个待配对对象的表示向量；在依存句法里，它们可分别表示“当前词”和“候选 head”；在关系抽取里，它们可分别表示“实体 1”和“实体 2”。 <span displaypfx="inline-" class="mathjax-container">\(U\in\mathbb{R}^{d\times d}\)</span> 是双线性参数矩阵，因此 <span displaypfx="inline-" class="mathjax-container">\(h_i^\top U h_j\)</span> 建模的是二者之间的交互强度：不是只看 <span displaypfx="inline-" class="mathjax-container">\(h_i\)</span> 自己，也不是只看 <span displaypfx="inline-" class="mathjax-container">\(h_j\)</span> 自己，而是看“这两个向量放在一起是否匹配”。把它展开后就是 <span displaypfx="inline-" class="mathjax-container">\(\sum_{p=1}^{d}\sum_{q=1}^{d}(h_i)_p\,U_{pq}\,(h_j)_q\)</span>，因此 <span displaypfx="inline-" class="mathjax-container">\(U_{pq}\)</span> 可以理解为“第 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 个 dependent 特征与第 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 个 head 特征同时出现时，该给多少分”。</p>
<p>若任务需要一次输出多类关系分数，工程实现通常会为每个类别各放一张矩阵 <span displaypfx="inline-" class="mathjax-container">\(U^{(k)}\)</span>，或直接使用三阶参数张量。这样不同关系类型就能拥有不同的交互模式，而不必共用同一套 <span displaypfx="inline-" class="mathjax-container">\(U\)</span>。</p>
<p><span displaypfx="inline-" class="mathjax-container">\([h_i;h_j]\in\mathbb{R}^{2d}\)</span> 表示把两个向量直接拼接； <span displaypfx="inline-" class="mathjax-container">\(w^\top [h_i;h_j]\)</span> 是普通仿射项，可以进一步拆成“只依赖 <span displaypfx="inline-" class="mathjax-container">\(h_i\)</span> 的线性项 + 只依赖 <span displaypfx="inline-" class="mathjax-container">\(h_j\)</span> 的线性项”。它的作用是补充单边信息：即使暂时不看两者之间的乘性交互，某些 token 或实体本身也可能更像 head、更像 dependent，或更像某类关系的一端。最后的 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 是全局偏置，给整类配对提供一个基线分数。</p>
<p>具象地看， <span displaypfx="inline-" class="mathjax-container">\(h_i^\top U h_j\)</span> 像在问“这两个人之间是否搭得上”， <span displaypfx="inline-" class="mathjax-container">\(w^\top [h_i;h_j]\)</span> 像在问“这两个人各自单独看，是否本来就带某种角色倾向”。把两部分合起来，模型既能利用配对关系，也不会丢掉单个对象自身的类型线索。这正是双仿射通常比纯双线性更稳、更有表达力的原因。</p>
<p>把 <span displaypfx="inline-" class="mathjax-container">\(s(i,j)\)</span> 对所有 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 组合都算出来，就能形成一个 <span displaypfx="inline-" class="mathjax-container">\(L\times L\)</span> 的分数矩阵；对每个位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span>，再在所有候选 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 上做 <span displaypfx="inline-" class="mathjax-container">\(\arg\max\)</span> 或 softmax 分类，就能得到“它最可能依附到谁”或“它与谁最可能存在某种关系”。这种头的关键点在于：任务监督信号作用在“对”的层面，而不是单点分类。</p>
<div class="blog_h3"><span class="graybg">解析式 NLP（Analytical NLP）任务选型</span></div>
<p>解析式 NLP（Analytical NLP）指一类“结构化输出”的语言任务：输出不是一段自由文本，而是标签序列、树或图。这类任务的工程难点通常不在 backbone，而在<span style="background-color: #c0c0c0;">输出结构约束、标注成本、以及错误后果</span>（例如信息抽取用于合规/风控）。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">任务</td>
<td style="text-align: center;">输出结构</td>
<td style="text-align: center;">常见任务头</td>
<td style="text-align: center;">数据/标注成本</td>
<td style="text-align: center;">蒸馏难度</td>
</tr>
</thead>
<tbody>
<tr>
<td>NER</td>
<td>BIO 标签序列</td>
<td>Token 分类；CRF（可选）</td>
<td>中（需要一致的标注规范）</td>
<td>低~中（教师模型能稳定给出 token-level 或 span-level 标签）</td>
</tr>
<tr>
<td>DEP（Dependency Parsing）</td>
<td>树（每 token 一个 head）</td>
<td>Biaffine / 图解析器（Parser）</td>
<td>高（标注复杂；语言差异大）</td>
<td>中~高（结构约束更强；蒸馏需覆盖长尾句式）</td>
</tr>
<tr>
<td>SDP（Semantic Dependency Parsing）</td>
<td>有向图（多 head / 多边）</td>
<td>图结构头（Graph Head）</td>
<td>很高（语义标注成本高）</td>
<td>高（输出空间更大；错误更难用局部规则修正）</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">多任务与多头</span></div>
<p>同一个 backbone 可以挂多个任务头（Multi-head / Multi-task）：共享表示学习带来数据效率，但不同任务的梯度可能冲突，需要权衡损失权重（Loss Weighting）与采样策略。工程上也常见“同任务多头”：例如同时输出 token 标签与 span 边界，或同时输出检索 embedding 与分类 logits，用不同头对齐不同指标。</p>
<div class="blog_h2"><span class="graybg">反向传播</span></div>
<p>反向传播（Backpropagation）描述的是训练阶段中“如何把损失函数对输出的误差信号传回网络内部，并进一步求出各层参数梯度”的过程。理解这一章时，可以把它拆成三件事：前向传播先把值算出来，反向传播再把梯度传回去，而链式法则与 Jacobian 则给出这件事的数学形式。</p>
<div class="blog_h3"><span class="graybg">前向传播</span></div>
<p>前向传播（Forward Propagation）是训练与推理中首先发生的计算过程：给定输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 和当前参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>，按照网络定义从前到后依次计算各层输出、最终预测 <span displaypfx="inline-" class="mathjax-container">\(\hat y\)</span>，以及训练时对应的损失 <span displaypfx="inline-" class="mathjax-container">\(L\)</span>。若把网络写成函数复合</p>
<span displaypfx="" class="mathjax-container">\[h_1=f_0(x),\quad h_2=f_1(h_1),\quad \cdots,\quad h_L=f_{L-1}(h_{L-1}),\quad \hat y=g(h_L),\quad L=\ell(\hat y,y)\]</span>
<p>那么前向传播做的就是按 <span displaypfx="inline-" class="mathjax-container">\(x\to h_1\to h_2\to \cdots \to h_L\to \hat y \to L\)</span> 的顺序把这些值算出来。它回答的问题是：<span style="background-color: #c0c0c0;">在当前参数下，这个样本会被模型算成什么结果，以及这个结果与真实标签相差多大</span>。</p>
<p>前向传播的产物不仅是最终预测，还包括中间激活值（Activation）。这些中间量一方面构成模型当前这次推理的内部表示，另一方面也是后续反向传播计算梯度时必须依赖的节点。因此，训练过程总是先有前向传播，再有反向传播：前者负责算值，后者负责算这些值对参数的敏感度。</p>
<div class="blog_h3"><span class="graybg">反向传播</span></div>
<p>反向传播（Backpropagation）是深度学习里用于<span style="background-color: #c0c0c0;">高效计算梯度（Gradient）</span>的算法。训练神经网络的目标，是最小化损失函数（Loss Function）<span displaypfx="inline-" class="mathjax-container">\(L(\theta)\)</span>；而要用梯度下降（Gradient Descent）或 Adam 之类的优化器更新参数，就必须先知道每个参数对损失的影响，也就是 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial \theta}\)</span>。</p>
<p>这个需求在深层网络里会立刻变得庞大。一个模型往往包含从数万到数十亿个参数；如果对每个参数都单独做一次数值扰动，逐个估计“它变一点时损失会怎么变”，计算代价会高得无法训练。反向传播出现的直接原因，就是要把这个原本几乎不可做的问题，变成一次前向传播（Forward Pass）加一次反向传播（Backward Pass）即可完成的梯度计算流程。</p>
<p>它的来源并不神秘：神经网络本质上是许多简单函数的复合。线性层、激活函数、归一化、注意力、损失函数，都会把前一层输出当作后一层输入。只要这些局部变换可导（Differentiable）或几乎处处可导，就可以对整条复合链应用链式法则（Chain Rule）。反向传播就是把链式法则改写成一种适合计算图（Computational Graph）执行的程序：<span style="background-color: #c0c0c0;">前向时保存必要的中间结果，反向时从最终损失出发，把局部导数一层层乘回去，并把梯度分发给每个中间变量和参数</span>。</p>
<p>因此，反向传播不是另一条独立于微积分的“神经网络专用规则”，而是链式法则在复合函数上的工程化实现。它做的事情可以概括为两步：第一步，前向传播先算出各层激活值与最终损失；第二步，从 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial L}=1\)</span> 出发，把损失敏感度沿着依赖边反向传回去，依次得到 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial h_l}\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial W_l}\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial b_l}\)</span> 等量。到这一步为止，反向传播完成的是<span style="background-color: #c0c0c0;">梯度计算</span>；参数真正如何更新，则属于优化器（Optimizer）的职责。</p>
<p>最简单的梯度下降（Gradient Descent）更新写成</p>
<span displaypfx="" class="mathjax-container">\[\theta \leftarrow \theta - \eta \nabla_{\theta} L\]</span>
<p>这里更适合写梯度符号 <span displaypfx="inline-" class="mathjax-container">\(\nabla_{\theta}L\)</span>，因为 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 往往是整个参数向量或参数集合，而不是单个标量。若只讨论某一个标量参数，才可写成 <span displaypfx="inline-" class="mathjax-container">\(\theta \leftarrow \theta - \eta \frac{\partial L}{\partial \theta}\)</span>。从计算方式看，反向传播对应自动微分（Automatic Differentiation）中的反向模式（Reverse Mode AD）。它特别适合“输入参数很多、输出损失很少”的场景；而神经网络训练恰好就是这种结构：参数维度极大，但最终通常只关心一个标量损失。因此，反向传播成为现代深度学习训练的标准机制。</p>
<div class="blog_h4"><span class="graybg">一个最小手算例子</span></div>
<p>先看一个只多加一层激活函数（Activation）的最小网络。设输入 <span displaypfx="inline-" class="mathjax-container">\(x=2\)</span>，参数为权重 <span displaypfx="inline-" class="mathjax-container">\(w=1\)</span> 与偏置 <span displaypfx="inline-" class="mathjax-container">\(b=-1\)</span>，先做仿射变换（Affine Transform）</p>
<span displaypfx="" class="mathjax-container">\[z=wx+b=1\cdot 2-1=1\]</span>
<p>再经过 ReLU 激活函数</p>
<span displaypfx="" class="mathjax-container">\[a=\mathrm{ReLU}(z)=\max(0,z)=1\]</span>
<p>把激活值当作最终预测，即 <span displaypfx="inline-" class="mathjax-container">\(\hat y=a\)</span>。若真实标签 <span displaypfx="inline-" class="mathjax-container">\(y=0\)</span>，损失取平方损失（Squared Loss）</p>
<span displaypfx="" class="mathjax-container">\[L=\frac12(\hat y-y)^2=\frac12(1-0)^2=0.5\]</span>
<p>前向传播链条现在是</p>
<span displaypfx="" class="mathjax-container">\[x\ \longrightarrow\ z\ \longrightarrow\ a=\hat y\ \longrightarrow\ L\]</span>
<p>反向传播先从损失对输出的导数开始：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial \hat y}=\hat y-y=1\]</span>
<p>由于这里 <span displaypfx="inline-" class="mathjax-container">\(\hat y=a\)</span>，所以</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial a}=1\]</span>
<p>再经过激活函数这一层。因为当前 <span displaypfx="inline-" class="mathjax-container">\(z=1&gt;0\)</span>，ReLU 的导数为</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial a}{\partial z}=\mathrm{ReLU}'(z)=1\]</span>
<p>于是梯度传回仿射层输出：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial z}=\frac{\partial L}{\partial a}\cdot\frac{\partial a}{\partial z}=1\cdot 1=1\]</span>
<p>最后再看仿射层对参数的局部导数：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial z}{\partial w}=x=2,\qquad \frac{\partial z}{\partial b}=1\]</span>
<p>因此</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial w}=\frac{\partial L}{\partial z}\cdot\frac{\partial z}{\partial w}=1\cdot 2=2\]</span>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial b}=\frac{\partial L}{\partial z}\cdot\frac{\partial z}{\partial b}=1\cdot 1=1\]</span>
<p>若学习率 <span displaypfx="inline-" class="mathjax-container">\(\eta=0.1\)</span>，梯度下降更新后</p>
<span displaypfx="" class="mathjax-container">\[w\leftarrow 1-0.1\cdot 2=0.8,\qquad b\leftarrow -1-0.1\cdot 1=-1.1\]</span>
<p>这个版本比纯线性例子多了一站 <span displaypfx="inline-" class="mathjax-container">\(z\to a\)</span>，因此链式法则也多乘了一个局部导数 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial a}{\partial z}\)</span>。这正是反向传播的一般形式：<span style="background-color: #c0c0c0;">误差信号每穿过一层，就乘上这一层自己的局部导数</span>。若这里的 <span displaypfx="inline-" class="mathjax-container">\(z&lt;0\)</span>，则 ReLU 导数为 0，上游梯度也会在这一层被截断。</p>
<div class="blog_h3"><span class="graybg">链式法则与雅可比连乘</span></div>
<p>反向传播（Backpropagation）本质是链式法则（Chain Rule）在计算图（Computational Graph）上的系统化应用：把最终损失对中间变量的导数沿依赖关系向后传。</p>
<p>固定一个训练样本 <span displaypfx="inline-" class="mathjax-container">\((x,y)\)</span> 后，损失 <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 最终当然可以看成参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 的函数；训练时真正要更新的也确实是权重（Weight）。但在计算图里， <span displaypfx="inline-" class="mathjax-container">\(L(\theta)\)</span> 并不是一步直接从 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 跳到损失，而是先经过各层线性变换、激活值（Activation）和输出再到损失。因此，反向传播会先求 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial h_l}\)</span> 这类“损失对中间激活值的敏感度”，再由这些中间梯度继续求出 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial \theta_l}\)</span>。</p>
<p>Jacobian 一节已经给出局部线性化：若 <span displaypfx="inline-" class="mathjax-container">\(h_{l+1}=f_l(h_l)\)</span>，则在当前点附近有 <span displaypfx="inline-" class="mathjax-container">\(dh_{l+1}\approx J_l\,dh_l\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(J_l=\frac{\partial h_{l+1}}{\partial h_l}\)</span>。这条式子描述的是<span style="background-color: #c0c0c0;">输入扰动如何向前传到输出扰动</span>；反向传播关心的是相反方向的问题：输出端的损失敏感度如何传回输入端。</p>
<p>先看一层。由于损失 <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 是标量，若采用与前文 Jacobian 一致的分子布局（Numerator Layout），把 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial h_l}\)</span> 记成 <span displaypfx="inline-" class="mathjax-container">\(1\;\times d_l\)</span> 的行向量，则向量链式法则写成：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial h_l}=\frac{\partial L}{\partial h_{l+1}}\frac{\partial h_{l+1}}{\partial h_l}=\frac{\partial L}{\partial h_{l+1}}J_l\]</span>
<p>这条式子最好按符号逐个读。先看 <span displaypfx="inline-" class="mathjax-container">\(h_l\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(h_{l+1}\)</span>：它们分别表示第 <span displaypfx="inline-" class="mathjax-container">\(l\)</span> 层的输入表示和该层输出表示。若 <span displaypfx="inline-" class="mathjax-container">\(h_l\)</span> 有 <span displaypfx="inline-" class="mathjax-container">\(d_l\)</span> 个分量， <span displaypfx="inline-" class="mathjax-container">\(h_{l+1}\)</span> 有 <span displaypfx="inline-" class="mathjax-container">\(d_{l+1}\)</span> 个分量，那么</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial h_{l+1}}=\begin{bmatrix}\frac{\partial L}{\partial h_{l+1,1}} &amp; \frac{\partial L}{\partial h_{l+1,2}} &amp; \cdots &amp; \frac{\partial L}{\partial h_{l+1,d_{l+1}}}\end{bmatrix}\in\mathbb{R}^{1\;\times d_{l+1}}\]</span>
<p>它表示：损失 <span displaypfx="inline-" class="mathjax-container">\(L\)</span> 对第 <span displaypfx="inline-" class="mathjax-container">\(l+1\)</span> 层每个输出分量分别有多敏感。这里把它写成行向量，是因为前文采用的是分子布局（Numerator Layout）。</p>
<p>再看 Jacobian：</p>
<span displaypfx="" class="mathjax-container">\[J_l=\frac{\partial h_{l+1}}{\partial h_l}\in\mathbb{R}^{d_{l+1}\;\times d_l}\]</span>
<p>其中第 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 个元素是</p>
<span displaypfx="" class="mathjax-container">\[(J_l)_{ij}=\frac{\partial h_{l+1,i}}{\partial h_{l,j}}\]</span>
<p>意思是：第 <span displaypfx="inline-" class="mathjax-container">\(l\)</span> 层第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个输入分量变化一点，会让第 <span displaypfx="inline-" class="mathjax-container">\(l+1\)</span> 层第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个输出分量变化多少。因此，Jacobian 记录的是“输入各分量 <span displaypfx="inline-" class="mathjax-container">\(\to\)</span> 输出各分量”的局部影响表。</p>
<p>现在看乘法</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial h_l}=\frac{\partial L}{\partial h_{l+1}}J_l\]</span>
<p>左边最终应该是一个关于 <span displaypfx="inline-" class="mathjax-container">\(h_l\)</span> 各分量的梯度，所以它必须有 <span displaypfx="inline-" class="mathjax-container">\(d_l\)</span> 个分量。维度上， <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial h_{l+1}}\)</span> 是 <span displaypfx="inline-" class="mathjax-container">\(1\;\times d_{l+1}\)</span>， <span displaypfx="inline-" class="mathjax-container">\(J_l\)</span> 是 <span displaypfx="inline-" class="mathjax-container">\(d_{l+1}\;\times d_l\)</span>，两者相乘后确实得到 <span displaypfx="inline-" class="mathjax-container">\(1\;\times d_l\)</span>。</p>
<p>如果把第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个分量单独展开，这个乘法其实就是</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial h_{l,j}}=\sum_{i=1}^{d_{l+1}}\frac{\partial L}{\partial h_{l+1,i}}\frac{\partial h_{l+1,i}}{\partial h_{l,j}}\]</span>
<p>这时含义就完全显出来了：第 <span displaypfx="inline-" class="mathjax-container">\(l\)</span> 层第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个输入分量，会通过第 <span displaypfx="inline-" class="mathjax-container">\(l+1\)</span> 层的所有输出分量共同影响损失；因此要把“损失对每个输出分量的敏感度”乘上“该输出分量对当前输入分量的局部导数”，再对所有输出分量求和。矩阵乘法只是把这组求和一次性写成了紧凑形式。</p>
<p>把这条一层公式沿网络递推展开，对深度网络 <span displaypfx="inline-" class="mathjax-container">\(h_{l+1}=f_l(h_l)\)</span> 可得：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial h_0}=\frac{\partial L}{\partial h_L}J_{L-1}J_{L-2}\cdots J_0,\qquad J_l=\frac{\partial h_{l+1}}{\partial h_l}\]</span>
<p>这条公式的读法非常具体：前向传播按 <span displaypfx="inline-" class="mathjax-container">\(h_0\to h_1\to\cdots\to h_L\)</span> 计算；反向传播则从最终梯度 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial h_L}\)</span> 出发，依次乘上最后一层到第一层的 Jacobian，把敏感度一层层传回去。矩阵乘法满足结合律（Associativity），因此可以逐层累积；但不满足交换律，乘法顺序不能改，因为每个 Jacobian 都对应不同层的局部坐标变换。</p>
<p>一个最小可算例可以把这件事完全写开。设输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 是标量，第一层有两个参数 <span displaypfx="inline-" class="mathjax-container">\(w_{11},w_{12}\)</span>，输出二维隐藏表示：</p>
<span displaypfx="" class="mathjax-container">\[h_1=\begin{bmatrix}h_{1,1}\\h_{1,2}\end{bmatrix}=\begin{bmatrix}w_{11}x\\w_{12}x\end{bmatrix}\]</span>
<p>第二层有两个参数 <span displaypfx="inline-" class="mathjax-container">\(w_{21},w_{22}\)</span>，把二维隐藏表示读成一个标量预测：</p>
<span displaypfx="" class="mathjax-container">\[\hat y=w_{21}h_{1,1}+w_{22}h_{1,2}\]</span>
<p>损失取最简单的平方损失（Squared Loss）：</p>
<span displaypfx="" class="mathjax-container">\[L=\frac12(\hat y-y)^2\]</span>
<p>这里总参数一共四个，但反向传播不会直接去“猜” <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial w_{11}}\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial w_{12}}\)</span> 等结果，而是先沿着计算图写出中间量：</p>
<span displaypfx="" class="mathjax-container">\[x\ \longrightarrow\ h_1\ \longrightarrow\ \hat y\ \longrightarrow\ L\]</span>
<p>先看第二层的局部 Jacobian。因为 <span displaypfx="inline-" class="mathjax-container">\(\hat y\)</span> 是标量、 <span displaypfx="inline-" class="mathjax-container">\(h_1\in\mathbb{R}^2\)</span>，所以</p>
<span displaypfx="" class="mathjax-container">\[J_1=\frac{\partial \hat y}{\partial h_1}=\begin{bmatrix}w_{21}&amp;w_{22}\end{bmatrix}\]</span>
<p>再看第一层的局部 Jacobian。因为 <span displaypfx="inline-" class="mathjax-container">\(h_1\in\mathbb{R}^2\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 是标量，所以</p>
<span displaypfx="" class="mathjax-container">\[J_0=\frac{\partial h_1}{\partial x}=\begin{bmatrix}w_{11}\\w_{12}\end{bmatrix}\]</span>
<p>损失对最终输出的导数最容易先算：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial \hat y}=\hat y-y\]</span>
<p>于是，损失对隐藏表示的梯度由链式法则得到：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial h_1}=\frac{\partial L}{\partial \hat y}\frac{\partial \hat y}{\partial h_1}=(\hat y-y)\begin{bmatrix}w_{21}&amp;w_{22}\end{bmatrix}\]</span>
<p>这一步已经把“损失对输出的敏感度”传回到了中间激活值 <span displaypfx="inline-" class="mathjax-container">\(h_1\)</span>。继续往回乘第一层 Jacobian，就得到损失对输入的梯度：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial x}=\frac{\partial L}{\partial \hat y}J_1J_0\]</span>
<span displaypfx="" class="mathjax-container">\[=(\hat y-y)\begin{bmatrix}w_{21}&amp;w_{22}\end{bmatrix}\begin{bmatrix}w_{11}\\w_{12}\end{bmatrix}\]</span>
<span displaypfx="" class="mathjax-container">\[=(\hat y-y)(w_{21}w_{11}+w_{22}w_{12})\]</span>
<p>对参数的梯度也是同样的思路。第二层参数直接作用在 <span displaypfx="inline-" class="mathjax-container">\(\hat y\)</span> 上，因此</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial w_{21}}=\frac{\partial L}{\partial \hat y}\frac{\partial \hat y}{\partial w_{21}}=(\hat y-y)h_{1,1}\]</span>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial w_{22}}=\frac{\partial L}{\partial \hat y}\frac{\partial \hat y}{\partial w_{22}}=(\hat y-y)h_{1,2}\]</span>
<p>第一层参数不直接连到损失，而是先影响 <span displaypfx="inline-" class="mathjax-container">\(h_1\)</span>，再影响 <span displaypfx="inline-" class="mathjax-container">\(\hat y\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(L\)</span>，所以必须经过中间激活值这一站：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial w_{11}}=\frac{\partial L}{\partial h_{1,1}}\frac{\partial h_{1,1}}{\partial w_{11}}=[(\hat y-y)w_{21}]\,x\]</span>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial w_{12}}=\frac{\partial L}{\partial h_{1,2}}\frac{\partial h_{1,2}}{\partial w_{12}}=[(\hat y-y)w_{22}]\,x\]</span>
<p>若取一组具体数值 <span displaypfx="inline-" class="mathjax-container">\(x=2\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(y=1\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(w_{11}=1\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(w_{12}=-1\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(w_{21}=0.5\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(w_{22}=2\)</span>，则前向传播先得到</p>
<span displaypfx="" class="mathjax-container">\[h_1=\begin{bmatrix}2\\-2\end{bmatrix},\qquad \hat y=0.5\cdot 2+2\cdot(-2)=-3,\qquad \frac{\partial L}{\partial \hat y}=\hat y-y=-4\]</span>
<p>于是反向传播依次得到</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial h_1}=-4\begin{bmatrix}0.5&amp;2\end{bmatrix}=\begin{bmatrix}-2&amp;-8\end{bmatrix}\]</span>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial x}=\begin{bmatrix}-2&amp;-8\end{bmatrix}\begin{bmatrix}1\\-1\end{bmatrix}=6\]</span>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial w_{21}}=-4\cdot 2=-8,\qquad \frac{\partial L}{\partial w_{22}}=-4\cdot(-2)=8\]</span>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial w_{11}}=(-2)\cdot 2=-4,\qquad \frac{\partial L}{\partial w_{12}}=(-8)\cdot 2=-16\]</span>
<p>这个最小例子把反向传播的结构完整展示了出来：损失函数最终是参数的函数，但梯度并不是“绕开中间层直接对权重求”。它必须先通过 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial \hat y}\)</span>、 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial L}{\partial h_1}\)</span> 这样的中间敏感度逐步回传。激活值因此不是“额外引入的变量”，而是计算图中必经的节点。</p>
<p>若改用深度学习里更常见的列向量梯度记号 <span displaypfx="inline-" class="mathjax-container">\(\nabla_{h_l}L\in\mathbb{R}^{d_l}\)</span>，同一件事会写成</p>
<span displaypfx="" class="mathjax-container">\[\nabla_{h_l}L=J_l^\top \nabla_{h_{l+1}}L\]</span>
<p>这与上面的公式没有本质区别，只是把“左乘 Jacobian 的行向量记号”改写成了“右侧乘 Jacobian 转置的列向量记号”。工程实现里常见的 Vector-Jacobian Product，本质上就是这一步。</p>
<p>对参数的梯度也是同一个模式。若第 <span displaypfx="inline-" class="mathjax-container">\(l\)</span> 层含参数 <span displaypfx="inline-" class="mathjax-container">\(\theta_l\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial L}{\partial \theta_l}=\frac{\partial L}{\partial h_{l+1}}\frac{\partial h_{l+1}}{\partial \theta_l}\]</span>
<p>因此 backward 的核心不是“把前向再算一遍”，而是复用每一层的局部 Jacobian，把同一个上游梯度分别传给输入变量和参数。</p>
<div class="blog_h3"><span class="graybg">梯度消失与爆炸</span></div>
<p>梯度消失（Vanishing Gradients）与梯度爆炸（Exploding Gradients）通常来自连乘的数值尺度：若这些雅可比的有效增益（Effective Gain）的奇异值（Singular Values）长期小于 1，则梯度指数级衰减；长期大于 1 则指数级放大。</p>
<p>在 RNN 的 BPTT 中，这种现象尤其明显：同一递归矩阵在时间轴上重复相乘。粗略地看，若 <span displaypfx="inline-" class="mathjax-container">\(W_{hh}\)</span> 的谱半径（Spectral Radius）<span displaypfx="inline-" class="mathjax-container">\(\rho(W_{hh})\)</span> 明显大于 1，梯度更易爆炸；明显小于 1 更易消失。但这不是“只要 <span displaypfx="inline-" class="mathjax-container">\(\lambda&gt;1\)</span> 就一定爆炸”的二选一结论，因为实际还受到激活函数导数、归一化、残差/门控结构、以及数据分布的共同影响。</p>
<p>“梯度随训练慢慢变小是否正常？”在接近最优点时，梯度范数下降是预期现象；需要警惕的是训练早期就出现系统性消失（例如大量饱和激活导致导数接近 0）或出现不稳定爆炸（loss/梯度频繁变成 NaN/Inf）。</p>
<p>常见应对：</p>
<ul>
<li>梯度裁剪（Gradient Clipping）：抑制爆炸。</li>
<li>合理初始化（Xavier/He）、归一化（BatchNorm/LayerNorm）、残差连接（Residual）。</li>
<li>门控结构（Gated Units）：LSTM/GRU 通过门控缓解长程梯度问题。</li>
</ul>
<div class="blog_h2"><span class="graybg">权重初始化</span></div>
<p>权重初始化（Weight Initialization）的目标不是“随机给一个小数”，而是控制信号在层间传播的数值尺度。设第 <span displaypfx="inline-" class="mathjax-container">\(l\)</span> 层的线性部分为 <span displaypfx="inline-" class="mathjax-container">\(z^{(l)}=W^{(l)}h^{(l-1)}+b^{(l)}\)</span>。若 <span displaypfx="inline-" class="mathjax-container">\(z^{(l)}\)</span> 的二阶原点矩（Second Raw Moment）在层间持续放大，前向激活与反向梯度都会倾向爆炸；若持续衰减，则会出现梯度消失。</p>
<p>对单个神经元，写成 <span displaypfx="inline-" class="mathjax-container">\(z=\sum_{i=1}^{n}w_i x_i+b\)</span>。初始化分析里通常进一步假设 <span displaypfx="inline-" class="mathjax-container">\(w_i\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(x_i\)</span> 相互独立，且权重满足 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[w_i]=0\)</span>。这里的 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[w_i]=0\)</span> 不是在声称“训练后的权重天然应当均值为 0”，而是在分析初始化时，把权重看作从一个以 0 为中心的随机分布中抽样得到；这样做的目的，是避免网络在一开始就对某一方向产生系统性偏置，并让前向输出的均值计算更干净。基于这一初始化假设，在线性部分有</p>
<span displaypfx="" class="mathjax-container">\[\mathbb{E}[z]=0,\quad \mathbb{E}[z^2]=n\,\mathrm{Var}(w)\,\mathbb{E}[x^2]\]</span>
<p>当输入也近似零均值时， <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[x^2]\approx \mathrm{Var}(x)\)</span>，于是常写成 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(z)\approx n\,\mathrm{Var}(w)\,\mathrm{Var}(x)\)</span>。这里 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 就是 fan_in。因此初始化的核心约束可以概括为：让 <span displaypfx="inline-" class="mathjax-container">\(n\,\mathrm{Var}(w)\)</span> 保持在 1 附近。</p>
<p>更严格地说，深度初始化分析常跟踪的是 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[h^2]\)</span>，而不是所有层都精确使用方差；因为经过 ReLU 之后，激活不再零均值，此时二阶原点矩与方差不再完全相同。但在线性层与零均值近似下，两者是一致的，因此“保持方差稳定”仍是准确的工程表述。</p>
<p>两种朴素做法都会失败：若所有权重都初始化为 0，则各神经元保持完全对称，反向传播得到相同更新，网络退化为“许多拷贝的同一个单元”；若直接令 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(w)=1\)</span>，则线性输出的尺度会随层数按 fan_in 连乘，深层网络极易数值失稳。偏置（Bias）通常初始化为 0 或很小的常数；真正决定尺度的是权重矩阵的方差结构。</p>
<p>这里的 fan_in 与 fan_out 可以直接按“这层连接了多少路信号”来理解。对某个线性层中的一个输出神经元，fan_in 是流入它的输入通道数；输入通道越多，很多随机小贡献叠加后，输出就越容易变大。对某个输入神经元，fan_out 是它连接到下一层多少个输出通道；fan_out 越大，这个输入方向上的梯度在反向传播时就会被分发到更多支路。因此，fan_in 主要约束前向激活的放大量级，fan_out 主要约束反向梯度的放大量级。</p>
<p>具象地看，一个神经元像一个汇流节点：fan_in 决定有多少根水管把信号同时灌进来，fan_out 决定这股信号会被分流到多少个下游节点。初始化如果不考虑这两个连接数，网络就会在“汇流过猛”与“分流过弱”之间失衡。Xavier 正是在前向与反向之间做折中，He 则进一步把激活函数本身带来的能量损失也纳入补偿。</p>
<div class="blog_h3"><span class="graybg">正态、均匀、常数与正交初始化</span></div>
<p>导图里把初始化方法拆成正态分布、均匀分布、常数和正交四类，这种分法是成立的，因为它们回答的是两个不同层面的问题。正态分布初始化（Normal Initialization）与均匀分布初始化（Uniform Initialization）回答的是“权重样本从哪一种随机分布里抽”；常数初始化（Constant Initialization）回答的是“是否所有参数起点都相同”；正交初始化（Orthogonal Initialization）则进一步约束权重矩阵的几何结构，使不同方向在初始阶段尽量保持独立、不过度放大或压缩。</p>
<p>常数初始化最容易说明为什么会失败。若一个全连接层里所有权重都初始化为同一个常数，哪怕这个常数不是 0，各神经元在前向时看到的输入组合仍完全一致，反向时梯度也完全一致，结果就是整层神经元始终学习同一组特征，无法打破对称性。因此，偏置可以常数初始化，但权重矩阵通常不能整体常数初始化。</p>
<p>正态分布与均匀分布本身并没有绝对优劣，关键在于方差尺度是否合理。若只是“从一个随机分布里抽”，但不控制 fan_in / fan_out，网络一样会出现层间尺度失衡。因此工程实践里真正有意义的，通常不是孤立地说“用高斯”还是“用均匀”，而是说“用 Xavier 正态”“用 Xavier 均匀”“用 He 正态”“用 He 均匀”。分布族决定采样形状，方差公式决定数值稳定性。</p>
<p>正交初始化（Orthogonal Initialization）则更进一步。若 <span displaypfx="inline-" class="mathjax-container">\(W^\top W\approx I\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(WW^\top\approx I\)</span>，线性变换对向量长度的扭曲会更受控，信息在不同方向上的耦合也更弱。这对深层线性网络、RNN 的递归矩阵，以及某些残差结构尤其有价值，因为它能更好地维持奇异值谱接近 1，缓解梯度在层间或时间步之间迅速爆炸或衰减。</p>
<p>因此，这四类方法的关系可以概括为：<span style="background-color: #c0c0c0;">常数初始化主要是反例和边界条件；正态与均匀是随机采样家族；Xavier / He 是方差控制规则；正交初始化是额外的矩阵几何约束</span>。实际工程里最常见的组合是“Xavier/He + 正态或均匀采样”，以及在特定循环或深层结构中使用正交初始化。</p>
<div class="blog_h3"><span class="graybg">Xavier 初始化</span></div>
<p>Xavier 初始化（Xavier Initialization）也称 Glorot 初始化（Glorot Initialization），针对近似线性的激活工作区间设计，例如 tanh 与 sigmoid 在原点附近的局部线性区域。它的目标是同时控制前向激活与反向梯度的尺度，使它们在穿过每一层时不发生系统性放大或衰减。</p>
<p>若暂时忽略激活函数对二阶原点矩的额外缩放，则前向保持尺度不变要求 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(w)\approx 1/\mathrm{fan\_in}\)</span>；反向对应地要求 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(w)\approx 1/\mathrm{fan\_out}\)</span>。Glorot 给出的折中形式因此写成：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Var}(w)=\frac{2}{\mathrm{fan\_in}+\mathrm{fan\_out}}\]</span>
<p>常见实现包括正态版 <span displaypfx="inline-" class="mathjax-container">\(w\sim\mathcal{N}\!\left(0,\frac{2}{\mathrm{fan\_in}+\mathrm{fan\_out}}\right)\)</span>，以及均匀版 <span displaypfx="inline-" class="mathjax-container">\(w\sim U\!\left[-\sqrt{\frac{6}{\mathrm{fan\_in}+\mathrm{fan\_out}}},\sqrt{\frac{6}{\mathrm{fan\_in}+\mathrm{fan\_out}}}\right]\)</span>。</p>
<p>从直觉上看，Xavier 初始化像是在给每一层设置一个“不过度放大、也不过度压缩”的中性增益（neutral gain）。可以把一层线性变换想成一组并联的混音器：输入信号从上一层流入，经过许多权重通道混合后送到下一层。Xavier 的目标就是让这组混音器在初始状态下近似保持“总音量”不变，使信号既不会层层变得越来越吵，也不会层层变得越来越弱。</p>
<p>Xavier 的局限在于：它默认激活函数不会系统性丢失太多能量。对 ReLU 这类半波整流（Half-wave Rectification）激活，这个假设不再成立；即使线性层前后的尺度匹配，经过激活后信号的二阶原点矩仍会明显下降。</p>
<div class="blog_h3"><span class="graybg">He 初始化</span></div>
<p>He 初始化（He Initialization）也称 Kaiming 初始化（Kaiming Initialization），专门处理 ReLU 家族的整流效应。若线性输出 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 近似关于 0 对称，经过 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{ReLU}(z)=\max(0,z)\)</span> 后，大约一半的质量被截断为 0，并且</p>
<span displaypfx="" class="mathjax-container">\[\mathbb{E}[\mathrm{ReLU}(z)^2]=\frac{1}{2}\mathbb{E}[z^2]\]</span>
<p>因此，若仍使用 Xavier 量级，信号的二阶原点矩会随层数持续衰减。He 初始化通过把权重方差提高到</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Var}(w)=\frac{2}{\mathrm{fan\_in}}\]</span>
<p>来补偿这一半波损失。对应实现可写为正态版 <span displaypfx="inline-" class="mathjax-container">\(w\sim\mathcal{N}\!\left(0,\frac{2}{\mathrm{fan\_in}}\right)\)</span>，或均匀版 <span displaypfx="inline-" class="mathjax-container">\(w\sim U\!\left[-\sqrt{\frac{6}{\mathrm{fan\_in}}},\sqrt{\frac{6}{\mathrm{fan\_in}}}\right]\)</span>。</p>
<p>具象地看，ReLU 像一道只允许正值通过的闸门：一批近似对称分布的信号经过后，负半边会被直接截成 0，等于天然损失了一部分能量。He 初始化做的事情就是在闸门前把信号预先放大一些，使它通过这道“半波闸门”之后，整体尺度仍能维持在稳定区间。Xavier 假定通道基本不漏能量，He 则明确把 ReLU 的漏损补偿计入初始化方差。</p>
<p>对 Leaky ReLU/PReLU，补偿因子可推广为 <span displaypfx="inline-" class="mathjax-container">\(\frac{2}{(1+a^2)\,\mathrm{fan\_in}}\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(a\)</span> 是负半轴斜率。实践上，ReLU 及其变体默认优先使用 He 初始化；tanh/sigmoid 更适合 Xavier。归一化层与残差连接可以进一步放宽初始化的容错区间，但不能替代合理的初始尺度控制。</p>
<div class="blog_h3"><span class="graybg">大语言模型里的初始化</span></div>
<p>Transformer / 大语言模型（Large Language Model, LLM）里的初始化通常不按“整模统一套 Xavier”或“整模统一套 He”来理解，而更像一套围绕<span style="background-color: #c0c0c0;">残差流（Residual Stream）稳定性、归一化层和深度扩展</span>设计的小方差初始化配方。原因在于：LLM 的主干不是简单的“线性层 + 单一激活函数”堆叠，而是由自注意力、残差连接、LayerNorm / RMSNorm、MLP / SwiGLU 等子结构共同组成；纯粹以某个激活函数为中心推导的 Xavier / He，只能覆盖其中一部分局部直觉。</p>
<p>工程上最常见的做法是：嵌入层与线性层权重用零均值的小方差正态分布初始化，偏置置零或省略；随后依靠 LayerNorm / RMSNorm 与残差结构维持训练初期的数值稳定。BERT、GPT-2 一类经典 Transformer 常见做法是使用标准差约为 <span displaypfx="inline-" class="mathjax-container">\(0.02\)</span> 的正态或截断正态初始化；很多更现代的 Decoder-only LLM 仍延续“小方差高斯初始化”这一主线，只是在具体投影层上再叠加按隐藏维度、层数或残差分支做缩放的配方。</p>
<p>从直觉上看，LLM 初始化更像是在给一条很深的多车道主干道设置初始车流密度。若注意力里的 <span displaypfx="inline-" class="mathjax-container">\(Q/K/V/O\)</span> 投影、FFN 的升维/降维投影以及其他会把结果写回残差流的线性层初始尺度过大，残差分支会把信号越叠越猛，训练初期容易震荡；若尺度过小，几十层上百层残差块叠起来后，真正进入有效学习区的信号又会太弱。因此，现代 LLM 初始化的重点往往不是“严格选 Xavier 还是 He”，而是<span style="background-color: #c0c0c0;">让残差支路在深层网络中既能传递信息，又不会在训练一开始就把数值尺度推离稳定区</span>。</p>
<p>具体到模块分工，也可以这样理解：MLP 内部若使用 ReLU 家族激活，He 的补偿思想仍然成立；若使用 GELU、SiLU、SwiGLU 这类更平滑或带门控的激活，则实现里往往直接采用统一的小方差正态初始化，再由归一化、残差和训练配方共同保证稳定性。换言之，LLM 并没有抛弃 Xavier / He 背后的“方差守恒”原则，而是把这套原则嵌入到了更完整的 Transformer 初始化策略里。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">方法</td>
<td style="text-align: center;">核心方差</td>
<td style="text-align: center;">适用激活</td>
<td style="text-align: center;">主要问题 / 逻辑</td>
</tr>
</thead>
<tbody>
<tr>
<td>零初始化</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(w)=0\)</span></td>
<td>无</td>
<td>破坏对称性；所有神经元学到相同特征</td>
</tr>
<tr>
<td>标准正态随机</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(w)=1\)</span></td>
<td>无</td>
<td>尺度随深度快速放大；易导致爆炸</td>
</tr>
<tr>
<td>Xavier / Glorot</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{2}{\mathrm{fan\_in}+\mathrm{fan\_out}}\)</span></td>
<td>Tanh、Sigmoid</td>
<td>兼顾前向与反向尺度；假设激活近似线性</td>
</tr>
<tr>
<td>He / Kaiming</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{2}{\mathrm{fan\_in}}\)</span></td>
<td>ReLU、Leaky ReLU</td>
<td>补偿 ReLU 的半波截断；保持二阶原点矩稳定</td>
</tr>
<tr>
<td>LLM 常用小方差高斯初始化</td>
<td>常取固定小标准差，或再叠加按宽度 / 深度缩放</td>
<td>Transformer 模块整体</td>
<td>服务残差流、归一化层与深层稳定训练；不是单纯按某一种激活推导</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">正则化</span></div>
<p>正则化（Regularization）在经验风险（Empirical Risk）上增加复杂度惩罚，缓解过拟合（Overfitting）。假设函数/模型 <span displaypfx="inline-" class="mathjax-container">\(f_\theta\)</span> 决定预测；样本损失 <span displaypfx="inline-" class="mathjax-container">\(\ell\)</span> 度量单样本误差；代价函数/成本函数 <span displaypfx="inline-" class="mathjax-container">\(L(\theta)\)</span> 汇总训练集误差；目标函数 <span displaypfx="inline-" class="mathjax-container">\(J(\theta)\)</span> 则是 <span displaypfx="inline-" class="mathjax-container">\(L(\theta)\)</span> 加上正则化项： <span displaypfx="inline-" class="mathjax-container">\(J(\theta)=L(\theta)+\lambda\Omega(\theta)\)</span>。</p>
<span displaypfx="" class="mathjax-container">\[J(\theta)=\frac{1}{m}\sum_{i=1}^{m}\ell\!\left(f_{\theta}(x^{(i)}),y^{(i)}\right)+\lambda\Omega(\theta)\]</span>
<div class="blog_h3"><span class="graybg">L1 正则化（Lasso）</span></div>
<p>L1 正则化（L1 Regularization）使用 <span displaypfx="inline-" class="mathjax-container">\(\Omega(\theta)=\|\theta\|_1\)</span>。它倾向产生稀疏解（Sparsity），即一部分权重被直接压到 0，因此可同时做参数学习和特征选择（Feature Selection）。</p>
<p>从优化角度看，L1 的梯度是次梯度（Subgradient）：对单个参数 <span displaypfx="inline-" class="mathjax-container">\(\theta_k\)</span>，当 <span displaypfx="inline-" class="mathjax-container">\(\theta_k\ne 0\)</span> 时有 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial}{\partial \theta_k}\|\theta\|_1=\mathrm{sign}(\theta_k)\)</span>；在 0 点是一段区间 <span displaypfx="inline-" class="mathjax-container">\([-1,1]\)</span>。这使得优化过程更容易把小权重“推过 0”，形成精确稀疏。</p>
<p>在很多实现中，会用近端算子（Proximal Operator）给出更清晰的“压到 0”机制：对标量 <span displaypfx="inline-" class="mathjax-container">\(w\)</span>，软阈值化（Soft-Thresholding）是</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{soft}(w,\alpha)=\mathrm{sign}(w)\max(|w|-\alpha,0)\]</span>
<p>当 <span displaypfx="inline-" class="mathjax-container">\(|w|\le \alpha\)</span> 时直接变为 0。</p>
<div class="blog_h3"><span class="graybg">L2 正则化（Ridge / Weight Decay）</span></div>
<p>L2 正则化（L2 Regularization）使用 <span displaypfx="inline-" class="mathjax-container">\(\Omega(\theta)=\|\theta\|_2^2\)</span>。它倾向均匀缩小参数，但通常不会把参数精确压到 0。直观上，L1 在 0 点不可导，更容易触发“阈值化”解；L2 是光滑二次惩罚，更新更连续。</p>
<p>梯度层面，L2 会在原梯度上叠加一个“拉回原点”的项：若目标为 <span displaypfx="inline-" class="mathjax-container">\(\ell(\theta)+\lambda\|\theta\|_2^2\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\nabla_\theta=\nabla \ell(\theta)+2\lambda\theta\)</span>。工程上常把这个效果称为权重衰减（Weight Decay）：每一步都把权重按比例缩小。</p>
<p>需要区分一个常见细节：在自适应优化器（如 Adam）里，“把 <span displaypfx="inline-" class="mathjax-container">\(\lambda\|\theta\|_2^2\)</span> 加到损失里”与“直接做 weight decay”在数值上并不完全等价；因此实践中常用解耦权重衰减（Decoupled Weight Decay，典型实现是 AdamW）来获得更可控的正则化行为。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/regularization-l1-l2-geometry.png"><img class="alignnone size-full" src="https://blog.gmem.cc/wp-content/uploads/2026/03/regularization-l1-l2-geometry.png" alt="regularization-l1-l2-geometry" width="1920" height="930" /></a></p>
<p>把带惩罚的目标改写成约束形式后，几何差异会非常直观： <span displaypfx="inline-" class="mathjax-container">\(\min_\theta L(\theta)\ \mathrm{s.t.}\ \|\theta\|_1\le t\)</span> 的可行域在二维里是菱形， <span displaypfx="inline-" class="mathjax-container">\(\min_\theta L(\theta)\ \mathrm{s.t.}\ \|\theta\|_2\le t\)</span> 的可行域则是圆盘。损失等高线从外向内收缩时，第一次接触 <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 边界，往往更容易落在角点；角点恰好对应某些坐标精确等于 0。 <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 边界处处光滑，接触点通常只是把所有坐标一起缩小，而不是把其中一部分直接压成 0。</p>
<p>从一维更新机制看，这个差异也对应两种不同的收缩方式。 <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 更像连续比例收缩：权重越大，拉回原点的力越强，但通常不会在有限步内变成精确 0。 <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 则对应软阈值化（Soft-Thresholding）：一旦 <span displaypfx="inline-" class="mathjax-container">\(|w|\)</span> 小于阈值，就会被直接压成 0。因此， <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 不只是“把权重变小”，而是会主动把一部分坐标从模型中删掉，这就是稀疏性（Sparsity）的来源。</p>
<div class="blog_h3"><span class="graybg">Dropout</span></div>
<p>Dropout 通过随机屏蔽部分神经元输出，减少共适应（Co-adaptation），等价于在训练时对网络做一种随机子网络集成（Ensemble）。这里的共适应，是指若干神经元彼此形成了固定搭配：模型过度依赖它们同时出现、按特定组合共同完成判断，而不是让每个单元都学到相对独立、稳健的特征。Dropout 随机拿掉其中一部分单元后，网络不能再把能力押注在某一组固定配合上，只能把有用模式分散到更稳健的表示里。令隐藏向量为 <span displaypfx="inline-" class="mathjax-container">\(h\)</span>，mask <span displaypfx="inline-" class="mathjax-container">\(m_i\sim \mathrm{Bernoulli}(p)\)</span>，常见的 inverted dropout 写法为：</p>
<span displaypfx="" class="mathjax-container">\[\tilde h = \frac{m\odot h}{p}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 是保留概率（Keep Probability）。这样推理时可直接使用原网络（不再采样 mask），避免额外缩放。</p>
<div class="blog_h4"><span class="graybg">Word Dropout 与 Variational Dropout</span></div>
<p>在 NLP 中，Dropout 还经常有更具体的变体。Word Dropout 会在训练时随机把一部分 token 替换成未知词、占位符或空输入，从而降低模型对某些高频词项的脆弱依赖；Variational Dropout 则在 RNN 等结构中对时间维共享同一个 dropout mask，避免每个时间步都采样不同 mask 破坏时序稳定性。它们都服务于同一个目标：让模型不要过度依赖局部偶然线索。</p>
<div class="blog_h4"><span class="graybg">对抗训练</span></div>
<p>对抗训练（Adversarial Training）通过在输入或嵌入空间中加入微小、朝最坏方向设计的扰动，迫使模型在局部邻域内保持预测稳定。它并不只属于安全研究，在文本分类、序列标注和对比学习中也常被用来提升鲁棒性。对 NLP 而言，常见做法是在 embedding 上加入小扰动，再用额外损失约束模型在扰动前后保持一致或仍能正确预测。</p>
<div class="blog_h3"><span class="graybg">Batch Normalization</span></div>
<p>Batch Normalization（BatchNorm）在训练时用 mini-batch 的均值/方差做归一化，并学习缩放/平移参数。对特征维上的某个分量 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，典型形式是：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{BN}(x)=\gamma\cdot \frac{x-\mu_{\text{batch}}}{\sqrt{\sigma_{\text{batch}}^2+\epsilon}}+\beta\]</span>
<p>推理阶段通常使用训练过程累积的运行均值（Running Mean）与运行方差（Running Variance），因此训练/推理行为不同。BatchNorm 在 CNN 中极常见，因为卷积特征图在同一通道上的空间位置具有较强同质性，跨样本统计量较容易稳定；但在 Transformer 中，主流做法并不是沿 batch 维做归一化，而是使用与 batch 统计无关的 LayerNorm 或 RMSNorm。</p>
<p>原因在于 Transformer 的基本计算单元是 token 表示。序列长度常常可变，batch size 在训练与推理中也经常变化，尤其大模型训练会受到显存限制而使用较小、波动甚至分布式切分后的 micro-batch。若使用 BatchNorm，某个 token 的归一化结果会显式依赖同一 batch 中其他样本与其他位置的统计量，这会引入跨样本耦合，使训练和推理的数值语义不一致，也不利于自回归解码阶段逐 token 稳定生成。</p>
<div class="blog_h3"><span class="graybg">Layer Normalization</span></div>
<p>Layer Normalization（LayerNorm）对每个样本（或每个 token）的特征维做归一化，不依赖 batch 统计量，因此更适合变长序列与自回归推理。对向量 <span displaypfx="inline-" class="mathjax-container">\(x\in\mathbb{R}^{d}\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{LN}(x)=\gamma\odot \frac{x-\mu}{\sqrt{\sigma^2+\epsilon}}+\beta,\quad \mu=\frac{1}{d}\sum_{i=1}^{d}x_i,\ \sigma^2=\frac{1}{d}\sum_{i=1}^{d}(x_i-\mu)^2\]</span>
<p>这里的均值 <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span> 与方差 <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span> 都只在当前样本、当前 token 的特征维内部计算，因此每个 token 都能独立完成归一化。这个性质与 Transformer 的残差流（Residual Stream）非常匹配：无论 batch 如何变化、序列如何裁剪、推理时是否一次只输入一个 token，归一化规则都保持一致。</p>
<p>当前主流的 Transformer 归一化实践可以概括为两类。Encoder-only 与很多 Vision Transformer（ViT）架构通常沿用 LayerNorm；Decoder-only 大语言模型（Large Language Model, LLM）则大量采用 Pre-Norm 残差块，并进一步把 LayerNorm 简化为 RMSNorm。前者保留去均值与方差缩放，后者只保留尺度归一化，计算更轻，也更适合超大规模训练。</p>
<div class="blog_h3"><span class="graybg">Early Stopping</span></div>
<p>Early Stopping 用验证集指标监控训练过程，在泛化性能不再提升时提前停止，从而避免在训练集上继续拟合噪声。工程上常用“耐心（Patience）”：若验证集指标连续 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 次评估都未改善，则停止训练并回滚到最佳 checkpoint。它属于训练流程级的隐式正则化（Implicit Regularization）。</p>
<div class="blog_h3"><span class="graybg">分类任务正则化</span></div>
<p>分类任务正则化（Regularization for Classification Tasks）直接作用在分类训练的监督方式、样本混合方式或概率分布形状上。它的主要目标包括：缓解过度自信、减轻类别不平衡造成的梯度偏置、降低标签噪声影响，以及让决策边界在训练样本之间保持更平滑的过渡。</p>
<div class="blog_h4"><span class="graybg">Label Smoothing</span></div>
<p>Label Smoothing（标签平滑）把 one-hot 标签从“真实类为 1、其余类为 0”改写成更平滑的目标分布。对 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 分类问题，常见写法是：</p>
<span displaypfx="" class="mathjax-container">\[y_i^{\mathrm{LS}}=(1-\varepsilon)\mathbf{1}[i=c]+\frac{\varepsilon}{C}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 是真实类别， <span displaypfx="inline-" class="mathjax-container">\(\varepsilon\in(0,1)\)</span> 是平滑系数。真实类别仍占最大权重，但其他类别也会分到一小部分概率质量。训练因此不再持续奖励模型把正确类别概率推到 1、把其余类别压到 0，输出分布通常会更平滑，概率校准（Calibration）也更稳定。</p>
<p>在固定阈值或直接取 <span displaypfx="inline-" class="mathjax-container">\(\arg\max\)</span> 的分类系统里，Label Smoothing 带来的收益往往更多体现在 loss 曲线与概率可信度上，而不是直接转化为同等幅度的 F1 提升。若模型输出概率还会进入阈值调优、排序、融合、拒识或风险控制，这种校准改进的价值会更明显。</p>
<div class="blog_h4"><span class="graybg">类别重加权与重采样</span></div>
<p>类别重加权（Class Reweighting）与重采样（Resampling）主要处理类别不平衡（Class Imbalance）。其核心思想是让少数类样本在训练中获得更大的有效权重，避免优化过程被大量易分类的多数类样本主导。加权交叉熵的常见形式为：</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{wCE}}(y,p)=-\sum_{i=1}^{C}\alpha_i y_i\log p_i\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 类的损失权重。若某类样本极少，可以给它更大的 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span>，使模型在误分该类时承担更高代价。重采样则直接改变 mini-batch 的类别组成，例如过采样少数类、欠采样多数类，或用类别均衡采样器（Class-balanced Sampler）保证 batch 内标签分布更均衡。</p>
<p>这类方法的直接效果，是让决策边界不再默认偏向多数类。代价则是：过强的重加权会放大少数类中的噪声标签，过强的过采样会提高过拟合风险。因此它通常要与验证集上的 Precision / Recall / F1 一起调节，而不只盯着训练损失。</p>
<div class="blog_h4"><span class="graybg">Mixup 与 CutMix</span></div>
<p>Mixup 与 CutMix 通过构造“介于两个训练样本之间”的新样本，显式约束分类器在样本间插值区域上的行为，从而平滑决策边界。Mixup 的典型形式是：</p>
<span displaypfx="" class="mathjax-container">\[\tilde x=\lambda x_i+(1-\lambda)x_j,\qquad \tilde y=\lambda y_i+(1-\lambda)y_j\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\lambda\in[0,1]\)</span> 通常从 Beta 分布采样。输入和标签都被线性混合，于是模型被要求在 <span displaypfx="inline-" class="mathjax-container">\(x_i\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(x_j\)</span> 之间给出相应的插值预测。CutMix 则不是对整张输入做加权平均，而是把一张图像中的局部区域替换为另一张图像的对应区域，同时按被替换面积比例混合标签。</p>
<p>这类方法对图像分类尤其有效，因为它们直接惩罚“决策边界贴着训练样本走”的过拟合行为。换一个视角看，Mixup / CutMix 并不是简单的数据增强，而是在告诉模型：输入空间里两点之间的过渡区域也应当保持语义上的平滑可解释。</p>
<div class="blog_h4"><span class="graybg">置信度惩罚与熵正则</span></div>
<p>置信度惩罚（Confidence Penalty）和熵正则（Entropy Regularization）直接约束输出分布不要过早塌缩到极端尖锐形状。一个常见做法是在交叉熵之外，再加入预测分布熵的奖励项：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}=\mathcal{L}_{\mathrm{CE}}-\beta H(p),\qquad H(p)=-\sum_{i=1}^{C}p_i\log p_i\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\beta&gt;0\)</span> 控制正则强度。由于熵越大表示分布越平滑，这一项会抑制模型过快形成极端概率。它和 Label Smoothing 的方向相近，但切入点不同：Label Smoothing 改的是监督目标分布，置信度惩罚改的是模型预测分布本身。</p>
<p>两者都常用于需要更好概率校准的分类系统。相比之下，Focal Loss 更强调“把梯度预算留给困难样本”，因此它在类别不平衡或难例挖掘场景更常见；Label Smoothing 与熵正则则更偏向控制过度自信与改善概率形状。</p>
<div class="blog_h3"><span class="graybg">回归任务正则化</span></div>
<p>回归任务正则化（Regularization for Regression Tasks）除了常见的参数惩罚外，还经常直接约束预测函数 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span> 的形状。回归目标是连续值，因此“曲线是否足够平滑、是否满足单调关系、是否具有合理曲率”往往和任务正确性本身直接相关。</p>
<div class="blog_h4"><span class="graybg">平滑性正则化与样条惩罚</span></div>
<p>平滑性正则化（Smoothness Regularization）要求回归函数不要在输入空间里出现不必要的高频震荡。最典型的形式是惩罚导数，尤其是二阶导数：</p>
<span displaypfx="" class="mathjax-container">\[J(f)=\sum_{i=1}^{N}(y_i-f(x_i))^2+\lambda\int (f''(x))^2\,dx\]</span>
<p>第二项就是经典的样条平滑惩罚（Smoothing Spline Penalty）。它惩罚曲率过大，相当于抑制函数频繁弯折。 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 越大，拟合曲线越平滑； <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 越小，模型越愿意追随样本中的局部波动。很多非参数回归（Nonparametric Regression）与时间序列平滑，本质上都在做这种“数据拟合 + 曲率惩罚”的权衡。</p>
<div class="blog_h4"><span class="graybg">Total Variation 与 Fused Lasso</span></div>
<p>Total Variation（TV）正则与 Fused Lasso 适合分段平滑（Piecewise Smooth）的回归目标。它们不强求函数处处光滑，而是允许少数突变点存在，同时惩罚过多的相邻跳变。离散形式的 TV 惩罚常写成：</p>
<span displaypfx="" class="mathjax-container">\[\Omega_{\mathrm{TV}}(f)=\sum_{t=2}^{T}|f_t-f_{t-1}|\]</span>
<p>若同时对参数本身加 <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 惩罚，再对相邻参数差分加 <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 惩罚，就得到 Fused Lasso：</p>
<span displaypfx="" class="mathjax-container">\[J(\beta)=L(\beta)+\lambda_1\sum_j |\beta_j|+\lambda_2\sum_{j=2}^{d}|\beta_j-\beta_{j-1}|\]</span>
<p>这类方法非常适合信号去噪、时序回归、基因拷贝数分段估计，以及任何“整体大致平稳、局部允许少数结构突变”的问题。与样条惩罚相比，它更偏好形成平坦区段，而不是全局光滑弯曲曲线。</p>
<div class="blog_h4"><span class="graybg">单调性约束</span></div>
<p>很多回归任务天然带有单调先验：广告出价上升，曝光概率通常不应系统性下降；贷款风险特征上升，违约风险不应系统性降低；药物剂量增加，效应在一定区间内通常应单调增强。单调性约束（Monotonicity Constraint）把这种领域知识直接写进模型：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial f(x)}{\partial x_k}\ge 0\quad \text{或}\quad \frac{\partial f(x)}{\partial x_k}\le 0\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(x_k\)</span> 是某个具有明确方向含义的特征。在线性模型里，这等价于约束对应权重非负或非正；在树模型和神经网络里，则可以通过结构限制、投影步骤或软惩罚项来实现。单调约束的价值不只在提高泛化，还在于提升可解释性与业务一致性。</p>
<div class="blog_h4"><span class="graybg">凸性与曲率约束</span></div>
<p>当回归函数预期具有凸性（Convexity）、凹性（Concavity）或有限曲率时，可以继续对二阶导数施加方向性约束。例如一维凸函数满足：</p>
<span displaypfx="" class="mathjax-container">\[f''(x)\ge 0\]</span>
<p>这类约束在成本函数建模、供需曲线估计、剂量反应建模和某些经济学回归问题里非常常见。即使不要求严格凸性，也常通过曲率上界控制函数不要弯折过猛，例如惩罚 Hessian 范数或二阶差分幅度。它们的作用与平滑惩罚相近，但强调的是“弯曲方向和弯曲强度应满足领域结构”，而不只是单纯地压低高频波动。</p>
<div class="blog_h4"><span class="graybg">Lipschitz 与梯度约束</span></div>
<p>Lipschitz 约束（Lipschitz Constraint）控制的是输入微小变化会把输出放大多少。若存在常数 <span displaypfx="inline-" class="mathjax-container">\(K\)</span>，使得</p>
<span displaypfx="" class="mathjax-container">\[|f(x)-f(x')|\le K\|x-x'\|\]</span>
<p>则函数变化速度受到统一上界控制。对可导函数，一个常见做法是直接惩罚输入梯度范数：</p>
<span displaypfx="" class="mathjax-container">\[J(\theta)=L(\theta)+\lambda\,\mathbb{E}_{x}\|\nabla_x f_\theta(x)\|_2^2\]</span>
<p>这种正则化常用于需要鲁棒输出的回归系统，例如物理量估计、坐标回归和噪声敏感的传感器建模。它抑制模型对局部输入扰动过度敏感，也能缓解高维回归中出现的不稳定尖峰。</p>
<div class="blog_h4"><span class="graybg">概率回归的分布约束</span></div>
<p>概率回归（Probabilistic Regression）不仅预测均值，还会预测方差、分位数或整个条件分布。此时正则化对象不再只有均值函数，还包括分布参数之间的结构关系。例如异方差回归（Heteroscedastic Regression）中，方差参数必须保持正值；分位数回归（Quantile Regression）中，不同分位点曲线应尽量避免交叉（Quantile Crossing）。</p>
<p>若模型同时预测多个分位点 <span displaypfx="inline-" class="mathjax-container">\(\hat q_{\tau_1}(x),\hat q_{\tau_2}(x)\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(\tau_1&lt;\tau_2\)</span>，则理想上应满足：</p>
<span displaypfx="" class="mathjax-container">\[\hat q_{\tau_1}(x)\le \hat q_{\tau_2}(x)\]</span>
<p>工程上常通过排序约束、投影修正或惩罚项来维持这种分布一致性。对高斯 NLL、混合密度网络（Mixture Density Network）或生存分析模型，也会对尺度参数、危险率函数或累积分布形状加入额外约束，使预测分布既拟合数据，又保持统计上可解释、数值上稳定。</p>
<div class="blog_h1"><span class="graybg">深度学习</span></div>
<p>深度学习（Deep Learning）是以多层神经网络为核心、通过大规模数据和梯度优化自动学习表示的建模范式。若上一章讨论的是神经网络的基本部件，例如线性层、激活函数、损失函数、反向传播、初始化与正则化；这一章讨论的则是这些部件在更深层、更大规模、更强归纳偏置下，如何组合成现代模型家族，并在视觉、语音、语言、生成和图结构任务上形成方法论分水岭。</p>
<p>“深度”并不只是层数更多。更关键的变化是：模型开始把原始输入逐层改写成越来越抽象的中间表示，从边缘、纹理、局部模式，逐步组合到部件、对象、语义关系与任务决策。于是，模型能力的来源不再只是最后那一层分类器，而是整条表示变换链本身。</p>
<p>下文的卷积神经网络（CNN）、循环神经网络（RNN）、Transformer、生成模型（Generative Model）、图神经网络（GNN）和 ONNX，分别对应深度学习里几条非常重要的主线：空间局部归纳偏置、时序递推建模、自注意力驱动的通用序列建模、生成式分布学习、关系数据表示学习，以及模型部署交换格式。它们共同构成了现代深度学习的主要版图。其中 Transformer 因为后续内容体量很大，会在第 3 篇中单独展开；这里先给出它在技术演进中的位置。</p>
<div class="blog_h2"><span class="graybg">表示学习与端到端学习</span></div>
<p>在传统机器学习流程里，特征工程和预测器常常是分开的：先由人手设计特征，再把这些特征交给线性模型、树模型或核方法完成分类与回归。深度学习把这两步合并进同一个可微计算图。前面的层负责把原始输入转写为更有判别力、更有结构感的表示，后面的层负责完成具体任务，所有参数围绕同一个目标函数联合优化。这就是端到端学习（End-to-End Learning）的核心含义。</p>
<p>表示学习（Representation Learning）之所以重要，是因为感知任务真正困难的部分，往往不在“最后怎么分一下类”，而在“怎样把原始信号变成对任务友好的坐标系”。图像像素、语音波形、文本序列和图结构都高度高维、局部相关且语义分散。深度网络的价值，在于它能用层级结构自动提取适合当前任务的中间表示，而不再完全依赖人工定义纹理统计量、边界特征、语言规则或图特征模板。</p>
<p>这也解释了为什么深度学习会改变整个方法栈。过去很多系统的主要工作量放在“特征怎么造”；深度学习之后，工作重心逐渐转向“架构如何设计、数据如何构造、训练如何稳定、预训练如何迁移、部署如何落地”。从研究到工业实践，核心竞争力开始沿着表示学习能力重新分布。</p>
<div class="blog_h2"><span class="graybg">为什么深度学习成为里程碑</span></div>
<p>多层神经网络并不是 2010 年代才出现的新概念。真正的转折点在于，一系列条件在同一时期同时成熟，使深网络第一次具备了大规模可训练、可迁移、可复用的现实基础。深度学习成为里程碑，靠的不是单一公式突破，而是数据、算力、优化、架构和软件工程几条线一起闭环。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">关键条件</td>
<td style="text-align: center;">作用</td>
<td style="text-align: center;">为什么重要</td>
</tr>
</thead>
<tbody>
<tr>
<td>大规模数据</td>
<td>提供足够多样的监督信号与统计规律</td>
<td>深网络参数量大，若没有足够样本，表达能力会迅速转化为过拟合风险</td>
</tr>
<tr>
<td>GPU / 并行算力</td>
<td>把大规模矩阵乘法、卷积和反向传播变成可承受的训练过程</td>
<td>很多深层模型在理论上可定义，但在工程上长期“训不动”</td>
</tr>
<tr>
<td>优化技术成熟</td>
<td>ReLU、Xavier / He 初始化、BatchNorm、残差连接、门控结构等共同提升可训练性</td>
<td>它们解决的是梯度消失、数值失稳和深层退化等根本瓶颈</td>
</tr>
<tr>
<td>强归纳偏置的架构</td>
<td>CNN 利用空间局部性，RNN 利用时序递推，GNN 利用图邻接关系</td>
<td>深度并不自动等于有效，架构必须贴合数据结构</td>
</tr>
<tr>
<td>预训练与迁移学习</td>
<td>先在大数据上学通用表示，再迁移到下游任务</td>
<td>这使深度学习从“每个任务从零训练”转向“共享表征资产”</td>
</tr>
<tr>
<td>框架与工程生态</td>
<td>自动求导、分布式训练、模型导出和部署工具日益成熟</td>
<td>研究原型和生产系统之间的距离被显著缩短</td>
</tr>
</tbody>
</table>
<p>从方法史角度看，深度学习真正改变的是“模型从何处获得有用表示”。经典机器学习更多依赖人工抽取特征，再用较浅模型做判别；深度学习则把表示构造本身纳入训练。这个变化一旦与数据和算力结合，就会呈现出非常强的规模效应。</p>
<div class="blog_h2"><span class="graybg">关键技术演进</span></div>
<div class="blog_h3"><span class="graybg">AlexNet 与视觉模型复兴</span></div>
<p>AlexNet 是现代深度学习史上的标志性节点。它的意义不只是 ImageNet 分类精度显著提升，而是证明了一个足够深、足够宽、用 GPU 训练、配合 ReLU 和数据增强的卷积网络，能够系统性压过人工特征加浅层分类器的旧路线。视觉领域由此从“设计特征”转向“训练特征”。</p>
<p>这次转折的后果极其深远。分类只是起点，检测、分割、检索、视频理解和视觉问答很快都转向以深层 backbone 为中心的范式。很多后续工作不再从零设计整套视觉特征，而是围绕预训练卷积网络做迁移、微调和多任务扩展。</p>
<div class="blog_h3"><span class="graybg">Seq2Seq 与 Attention</span></div>
<p>在序列任务上，深度学习的关键进展并不只来自更强的 RNN / LSTM / GRU，还来自编码器-解码器（Encoder-Decoder）和注意力（Attention）机制。Seq2Seq 让模型能够把输入序列映射为输出序列，最早大规模改变了机器翻译、语音识别和摘要生成等任务；注意力则突破了“所有信息都必须挤进一个固定长度向量”的瓶颈，使解码器可以在生成每一步时，动态读取输入序列中最相关的部分。</p>
<p>这条技术线的重要性在于，它把序列建模从“单纯递推记忆”推进到“按需访问上下文”。Transformer 正是在这条线上进一步把注意力从辅助机制推到主干架构，因此 Seq2Seq 与 Attention 构成了通往下一篇 Transformer 主线的直接前史。</p>
<div class="blog_h3"><span class="graybg">Transformer</span></div>
<p>Transformer 可以看作深度学习在序列建模上的又一次架构级跃迁。它不再把循环或卷积作为主干，而是以自注意力（Self-Attention）为核心，让每个位置都能直接与其他位置建立依赖关系。这样做的结果是：长距离关系更容易建模，训练更适合并行化，模型也更容易沿着数据规模、参数规模和上下文长度继续扩展。</p>
<p>Transformer 最初在机器翻译中取得突破，随后迅速扩展到语言建模、视觉、语音、多模态和生成任务，最终成为大模型时代最核心的基础架构之一。从深度学习的全局版图看，它不是脱离前文另起炉灶的新物种，而是建立在表示学习、端到端训练、注意力机制、残差连接和大规模预训练这些深度学习主线之上的综合结果。后续第 3 篇会专门展开它的结构与演进。</p>
<div class="blog_h3"><span class="graybg">GAN 与生成式建模跃迁</span></div>
<p>深度学习早期最强的成果主要集中在识别任务，而 GAN 把研究重点大幅推进到“高质量生成”本身。它展示了深网络不仅能判断图像属于什么类别，还能学习数据分布并生成逼真的新样本。这使图像合成、风格迁移、超分辨率、图像到图像翻译等方向迅速发展，也改变了人们对“模型能力边界”的直觉。</p>
<p>GAN 之后，生成式建模继续沿着 VAE、流模型、扩散模型等路线演进。它们关注的问题已经不只是判别准确率，而是样本质量、潜空间结构、可控生成和条件生成。生成模型因此成为深度学习内部一条独立而强势的方法线，后文会单独展开。</p>
<div class="blog_h3"><span class="graybg">预训练与迁移学习</span></div>
<p>深度学习的另一个方法学跃迁，是从“每个任务都从随机初始化开始学”转向“先学通用表示，再迁移到具体任务”。在视觉里，这条线最初体现为 ImageNet 预训练 backbone；在语音和语言里，则逐步发展为更大规模的自监督预训练。迁移学习显著降低了下游任务对标注数据量的依赖，也让模型参数本身成为可复用资产。</p>
<p>这一变化与大模型时代直接相连。大语言模型并不是凭空冒出来的新物种，而是深度学习在预训练、表示共享和规模扩展三条线上持续推进后的自然结果。因此，把深度学习理解成“大模型之前的旧阶段”并不准确；更准确的说法是，大模型建立在深度学习已经完成的方法论基础之上。</p>
<div class="blog_h2"><span class="graybg">可训练深度与残差学习</span></div>
<p>深度学习真正变成“深”这件事，并不是把层数机械堆高就结束了。模型一旦变深，前向信号尺度、反向梯度传播、优化地形和参数更新路径都会迅速恶化。也就是说，网络的理论表达能力和它能否被稳定训练，根本不是同一回事。可训练深度（Trainable Depth）讨论的正是这个问题：怎样让几十层、上百层甚至更深的网络，不只是写得出来，而是真的训得动。</p>
<div class="blog_h3"><span class="graybg">深层网络为什么难训练</span></div>
<p>深层网络的困难，不能只概括成“梯度消失或爆炸”。那当然是重要问题，但并不是全部。更本质地说，随着层数增加，模型必须在一连串非线性变换里同时维持三件事：有用信息不能太快丢失，梯度不能在回传时彻底衰减或失控，优化器还要能在高维参数空间里找到稳定下降方向。哪怕某个更深模型在理论上至少不比浅模型差，训练出来的结果也可能反而更糟，这就是深层退化（Degradation）问题。</p>
<p>若把一个深层块写成 <span displaypfx="inline-" class="mathjax-container">\(H(x)\)</span>，传统堆叠要求这一组层直接学习完整映射 <span displaypfx="inline-" class="mathjax-container">\(x\mapsto H(x)\)</span>。问题在于，当最优映射本身接近恒等映射，或只需要对输入做很小修正时，让网络从零学习整张映射会非常低效。层数越多，这种“每一层都要重新写一遍答案”的负担就越重。</p>
<div class="blog_h3"><span class="graybg">ResNet</span></div>
<p>ResNet（Residual Network）对这个难题给出的回答是残差学习（Residual Learning）。它不要求若干层直接学习 <span displaypfx="inline-" class="mathjax-container">\(H(x)\)</span>，而是改学相对输入的增量 <span displaypfx="inline-" class="mathjax-container">\(F(x)=H(x)-x\)</span>，于是输出变成</p>
<span displaypfx="" class="mathjax-container">\[y=F(x)+x\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 是块输入， <span displaypfx="inline-" class="mathjax-container">\(F(x)\)</span> 是卷积、归一化和非线性组成的残差分支， <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 是块输出。这个重写非常关键，因为它把“学习完整映射”改成了“在已有表示上做局部修正”。若当前层不需要大改输入，只要让 <span displaypfx="inline-" class="mathjax-container">\(F(x)\approx 0\)</span> 即可；若需要修正，再通过残差分支逐步补上。</p>
<p>这等于给深层网络提供了一条默认可行的起点：最坏情况下，信息至少可以沿着恒等路径往后传，而不必在每一层都被迫经过强变换。ResNet 因此不是简单增加一个跳线技巧，而是在重新定义深层块应该学什么。</p>
<div class="blog_h3"><span class="graybg">残差连接为什么有效</span></div>
<p>残差连接（Residual Connection）之所以有效，可以从前向和反向两条路同时理解。前向上，主表示不再只能依赖一串层层覆盖的非线性变换，而是有一条更短、更稳定的通道把已有信息直接送到后面；这使模型更容易保留低层有用特征，不会因为层数增加而过快破坏已有表示。反向上，梯度也多了一条更直接的传播路径，因此不会被所有中间层的局部导数连续压缩。</p>
<p>若把损失记为 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}\)</span>，残差块输出为 <span displaypfx="inline-" class="mathjax-container">\(y=x+F(x)\)</span>，则对输入的梯度满足</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial \mathcal{L}}{\partial x}=\frac{\partial \mathcal{L}}{\partial y}\left(I+\frac{\partial F(x)}{\partial x}\right)\]</span>
<p>这个式子的意义是：即使残差分支 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial F(x)}{\partial x}\)</span> 在某些区域学得不理想，梯度仍然至少能通过恒等项 <span displaypfx="inline-" class="mathjax-container">\(I\)</span> 保留一条直接路径。也正因为如此，残差连接缓解的不是“模型表达能力不够”，而是深层优化本身的困难。</p>
<p>具象地看，普通深网络像要求每一层都重写一遍完整草稿；残差网络则允许每一层只在上一版稿子旁边批注修改。真正需要改动的地方写入残差，不需要改的地方直接保留原文。这个抽象后来成为深层网络设计中极其通用的模式。</p>
<div class="blog_h3"><span class="graybg">影响超出 CNN</span></div>
<p>ResNet 最早在视觉里爆发，但残差学习的影响远远超出 CNN。Transformer 的每一层都依赖残差连接把注意力子层和 MLP 子层写回主表示流；扩散模型中的 U-Net 主干大量使用残差块；很多现代语音、视频、多模态和图模型也都把 skip connection 当作默认部件。原因很简单：残差连接解决的是深层网络的通用训练稳定性，而不是某种视觉特有问题。</p>
<p>因此，在知识体系里，ResNet 一方面是 CNN 家族中的里程碑架构，另一方面又代表了一条跨架构的方法学原则。后文在卷积神经网络部分仍会把 ResNet 作为经典视觉架构展开；这里更强调它在整个深度学习版图中的地位：它让“更深的网络”第一次大规模变成了工程上可持续扩展的现实路线。</p>
<div class="blog_h2"><span class="graybg">深度学习的实际应用</span></div>
<p>深度学习之所以成为主流，不是因为它在论文基准上偶尔领先，而是因为它在大量真实任务上持续改写了系统能力边界。它最擅长的场景，通常具备三个特征：输入高维、原始信号结构复杂、手工特征难以穷尽。只要这三个条件同时出现，表示学习的优势就会迅速放大。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">应用方向</td>
<td style="text-align: center;">深度学习在做什么</td>
<td style="text-align: center;">典型模型或系统</td>
<td style="text-align: center;">关键价值</td>
</tr>
</thead>
<tbody>
<tr>
<td>图像分类、检测与分割</td>
<td>从像素中直接学习目标、边界和语义区域的层级表示</td>
<td>CNN backbone、Faster R-CNN、U-Net、Mask R-CNN</td>
<td>显著降低人工视觉特征设计需求，并统一多类视觉任务的表示基础</td>
</tr>
<tr>
<td>人脸识别与设备认证</td>
<td>学习稳定的人脸嵌入，用于身份匹配、聚类和检索</td>
<td>FaceNet、ArcFace、Face ID 一类终端系统</td>
<td>把“看起来像不像”变成可度量的特征空间距离，并兼顾鲁棒性与低误识率</td>
</tr>
<tr>
<td>语音识别与语音合成</td>
<td>把连续波形映射为音素、文本或声学表示，再进一步合成自然语音</td>
<td>Deep Speech、Conformer、Tacotron、WaveNet</td>
<td>显著提升端到端语音系统的准确率与自然度</td>
</tr>
<tr>
<td>机器翻译与序列理解</td>
<td>学习跨语言或跨序列位置的上下文依赖与对齐关系</td>
<td>Seq2Seq、Attention、Transformer</td>
<td>把规则驱动和短上下文模型推进到可扩展的端到端序列建模</td>
</tr>
<tr>
<td>医学影像与工业质检</td>
<td>从高维图像中识别微小异常、边界结构和组织模式</td>
<td>ResNet、U-Net、3D CNN</td>
<td>在噪声高、细节密、人工判读成本高的场景中放大模型辅助价值</td>
</tr>
<tr>
<td>推荐、排序与多模态检索</td>
<td>把用户、内容、上下文和行为序列编码进共享表示空间</td>
<td>Wide &amp; Deep、DeepFM、双塔检索模型</td>
<td>提升匹配能力，并支持召回、排序、粗排到精排的分层建模</td>
</tr>
</tbody>
</table>
<p>人脸识别本身毫无疑问属于深度学习的典型落地方向，而 Face ID 这类系统则更接近<span style="background-color: #c0c0c0;">“深度学习模型 + 传感器 + 活体检测 + 安全芯片 + 阈值策略”</span>的产品级集成。深度网络负责学习人脸表征与匹配规则，但真正可商用的身份认证系统，还必须处理深度信息、环境光变化、攻击对抗、设备端隐私隔离和误识率控制等工程问题。这类例子很能说明：模型往往是核心能力来源，但完整系统从来不只是一张网络结构图。</p>
<div class="blog_h2"><span class="graybg">卷积神经网络（CNN）</span></div>
<div class="blog_h3"><span class="graybg">卷积层</span></div>
<p>卷积（Convolution）在连续形式下定义为函数重叠积分：</p>
<span displaypfx="" class="mathjax-container">\[(f*g)(t)=\int_{-\infty}^{+\infty} f(\tau)\,g(t-\tau)\,d\tau\]</span>
<p>在离散二维图像中常写为：</p>
<span displaypfx="" class="mathjax-container">\[(I*K)(i,j)=\sum_m\sum_n I(i-m,j-n)\,K(m,n)\]</span>
<p>深度学习框架里多数“卷积层”实现的是互相关（Cross-Correlation）而非严格翻转核的数学卷积，但工程上沿用“卷积”命名。卷积核（Kernel）共享权重（Weight Sharing），天然利用局部性（Locality）与平移等变（Translation Equivariance）。</p>
<p>直观上，卷积层做的事情是：对每个位置取一个局部窗口，把窗口里的像素与卷积核做加权求和，得到该位置的响应。不同卷积核学习不同的局部模式（边缘、纹理、角点等）。</p>
<p>一维互相关示例：输入 <span displaypfx="inline-" class="mathjax-container">\(x=[0,0,1,1,1,0,0]\)</span>，核 <span displaypfx="inline-" class="mathjax-container">\(w=[1,0,-1]\)</span>，则在从左到右滑动时，核会对“上升沿/下降沿”给出大幅响应（本质上是比较左右两侧的差异）。这就是经典的边缘检测直觉在离散信号上的对应。</p>
<p>卷积的“系统视角”能统一理解 CNN 与信号处理：把卷积核看作响应函数（Response Kernel），输出就是对历史输入的加权累积。参见“微积分 ➡ 卷积（Convolution）”中的因果卷积与“打点滴累积药效”直觉例子。</p>
<div class="blog_h3"><span class="graybg">池化、下采样与上采样</span></div>
<p>池化（Pooling）是卷积网络里最经典的一类分辨率操作。最大池化（Max Pooling）和平均池化（Average Pooling）都会把局部窗口压缩成更小的表示，因此它们首先属于<span style="background-color: #c0c0c0;">下采样（Downsampling）</span>。把这一点说清楚之后，再讨论与之相对的上采样（Upsampling），结构才是完整的。</p>
<p>更一般地说，上采样与下采样讨论的是：<span style="background-color: #c0c0c0;">在神经网络或信号处理中，怎样改变表示的分辨率、采样密度或时间粒度</span>。图像里它通常表现为特征图宽高的变化；音频里表现为采样率变化；时间序列里则表现为时间轴被压缩或展开。它们不是视觉专用术语，而是一类跨模态的基础操作。</p>
<p>下采样的目标是把表示变粗。若二维特征图 <span displaypfx="inline-" class="mathjax-container">\(X\in\mathbb{R}^{H\times W}\)</span> 经过步幅为 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 的下采样后，输出空间尺寸通常近似变成 <span displaypfx="inline-" class="mathjax-container">\(\lfloor H/s\rfloor \times \lfloor W/s\rfloor\)</span>。最常见的方式包括最大池化（Max Pooling）、平均池化（Average Pooling）和步幅卷积（Strided Convolution）。它带来的直接收益是：计算量下降、后续层感受野扩大、模型更容易聚焦高层语义；代价则是细边界、小目标和高频细节可能被抹掉。</p>
<p>上采样的目标是把表示变细。若输入特征图大小为 <span displaypfx="inline-" class="mathjax-container">\(H\times W\)</span>，放大倍率为 <span displaypfx="inline-" class="mathjax-container">\(r\)</span>，则输出空间尺寸会变成 <span displaypfx="inline-" class="mathjax-container">\(rH\times rW\)</span>。最常见的方法包括最近邻插值（Nearest Neighbor Interpolation）、双线性插值（Bilinear Interpolation）、转置卷积（Transposed Convolution）、反池化（Unpooling）与 Pixel Shuffle。它的任务不是“凭空创造真实新信息”，而是把低分辨率表示重新投影到更细网格上，使后续模块能够输出与原输入同尺度的结果。</p>
<p>二者经常成对出现。分类骨干网络通常不断下采样，把原始像素压缩成更抽象的低分辨率语义表示；分割、超分辨率、生成模型和部分语音/时序重建任务则需要再把这些粗表示上采样回去。U-Net、FPN 和 encoder-decoder 结构长期流行，正是因为它们在回答同一个问题：<span style="background-color: #c0c0c0;">如何在压缩信息、扩大感受野的同时，仍然把输出恢复到需要的空间或时间分辨率</span>。</p>
<p>这里还要区分“降采样有信息丢失”和“升采样无法保证恢复全部细节”这两个事实。若下采样前没有做足够的低通滤波（Low-pass Filtering），高频成分会折叠成错误的低频模式，产生混叠（Aliasing）；若上采样只依赖插值，则新位置往往只是旧值的平滑填充，而不是恢复出真正的新结构。因此在工程上，很多高质量系统会把下采样后的高层语义与浅层高分辨率特征通过跳跃连接（Skip Connection）重新融合，再做上采样，以减轻细节损失。</p>
<div class="blog_h3"><span class="graybg">扩张卷积（Dilated Convolution）</span></div>
<p>扩张卷积（Dilated Convolution）处理的核心问题是：在不显著增加参数量和计算量的前提下，如何让卷积层看到更大的上下文。它通过在卷积核元素之间插入空洞（dilation gap），把原本紧密相邻的采样点拉开，从而扩大感受野（Receptive Field）。</p>
<p>若一维卷积核长度为 <span displaypfx="inline-" class="mathjax-container">\(k\)</span>、扩张率为 <span displaypfx="inline-" class="mathjax-container">\(r\)</span>，则其有效感受野长度可写成：</p>
<span displaypfx="" class="mathjax-container">\[k_{\mathrm{eff}}=k+(k-1)(r-1)\]</span>
<p>这条式子说明：当 <span displaypfx="inline-" class="mathjax-container">\(r=1\)</span> 时，它退化为普通卷积；当 <span displaypfx="inline-" class="mathjax-container">\(r&gt;1\)</span> 时，在不增加核参数个数的情况下，有效覆盖范围会迅速变大。以 <span displaypfx="inline-" class="mathjax-container">\(k=3\)</span> 为例，若 <span displaypfx="inline-" class="mathjax-container">\(r=2\)</span>，有效感受野就从 3 扩展到 5；若 <span displaypfx="inline-" class="mathjax-container">\(r=4\)</span>，则扩展到 9。</p>
<p>扩张卷积特别适合语义分割、语音建模、时间序列和需要多尺度上下文的视觉任务，因为它兼顾了两件事：一方面保留卷积的局部共享权重结构，另一方面又能在较浅层就看到更远范围的信息。它的代价是采样点变稀，若扩张率设计不当，容易出现栅格效应（Gridding Effect），也就是某些局部细节被系统性跳过。因此工程上常把不同扩张率交替堆叠，或与普通卷积、残差块结合使用。</p>
<div class="blog_h3"><span class="graybg">经典架构</span></div>
<div class="blog_h4"><span class="graybg">LeNet</span></div>
<p>LeNet 可以看作现代卷积神经网络（Convolutional Neural Network, CNN）的原型。它面对的是手写数字识别这类“局部笔画决定整体类别”的任务，因此核心思想很直接：先用卷积层提取局部边缘、角点和笔画，再用池化（Pooling）降低分辨率与局部扰动敏感性，最后把高层特征送入全连接层做分类。</p>
<p>经典 LeNet-5 的结构可以概括为“卷积 - 池化 - 卷积 - 池化 - 全连接”。前面的卷积层负责把原始像素变成越来越抽象的特征图（Feature Map），后面的全连接层负责把这些局部特征整合成类别判断。它的重要性不只是“识别数字有效”，更在于证明了三件事可以协同工作：局部感受野（Local Receptive Field）、权重共享（Weight Sharing）和层级特征提取（Hierarchical Feature Learning）。</p>
<p>卷积层里的一个典型计算是：</p>
<span displaypfx="" class="mathjax-container">\[y_{i,j}^{(k)}=\sum_{u}\sum_{v}\sum_{c} W_{u,v,c}^{(k)}\,x_{i+u,j+v,c}+b^{(k)}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 是输入图像或上一层特征图， <span displaypfx="inline-" class="mathjax-container">\(W^{(k)}\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个卷积核， <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 是输入通道索引， <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 是空间位置， <span displaypfx="inline-" class="mathjax-container">\(y_{i,j}^{(k)}\)</span> 是输出特征图在该位置上的响应。这个公式表达的就是：用同一个卷积核在整张图上滑动，寻找“哪里出现了我关心的局部模式”。</p>
<p>训练上，LeNet 已经体现出现代深度学习的基本闭环：前向传播得到类别 logits，损失函数衡量预测与真实标签的差距，反向传播把梯度传回卷积核和全连接层参数，再用梯度下降更新权重。它最典型的应用是 MNIST 手写数字识别，也常被用作理解 CNN 的第一块教学样板，因为结构短、计算图清晰、局部模式学习的直觉非常强。</p>
<div class="blog_h4"><span class="graybg">AlexNet</span></div>
<p>AlexNet 标志着深度卷积网络在大规模视觉识别上的突破。它面对的不是手写数字这种相对简单的灰度图，而是 ImageNet 级别的彩色自然图像，因此核心思想从“能提特征”进一步推进到“更深、更宽、更可训练”：增加网络容量，用 ReLU（Rectified Linear Unit）加快优化，用 Dropout 缓解过拟合，用数据增强提升泛化，再借助 GPU 让大模型真正训得动。</p>
<p>其典型结构是多层卷积堆叠后接全连接层，早期卷积核较大、步幅较大，用于迅速降采样并提取低层纹理；中后期卷积核变小，重点转向更细粒度的组合特征。AlexNet 还使用了局部响应归一化（Local Response Normalization, LRN）这一今天较少使用、但在当时有历史意义的设计。整体上，它把 CNN 从“可用于小型任务的模型”推向了“可以在大规模视觉基准上碾压传统方法的通用架构”。</p>
<p>它最有代表性的非线性是 ReLU：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{ReLU}(z)=\max(0,z)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 是线性层或卷积层的输出。与 sigmoid / tanh 相比，ReLU 在正半轴不饱和，梯度传播更直接，因此深层网络更容易优化。AlexNet 的历史意义之一，就是把“激活函数的选择会显著影响深网络可训练性”这件事变成了工程共识。</p>
<p>训练上，AlexNet 典型地结合随机裁剪、翻转、颜色扰动等数据增强，并在全连接层使用 Dropout。它的直接应用是大规模图像分类；更深远的影响则是推动了整个视觉领域转向“预训练 CNN + 迁移学习”的工作流。很多后续检测、分割与检索系统，都曾把 AlexNet 当作特征提取骨干网络（Backbone）。</p>
<div class="blog_h4"><span class="graybg">VGG</span></div>
<p>VGG 的核心思想是：用结构极其规整的小卷积核反复堆叠，把网络做深。它放弃了早期“大卷积核 + 大步幅”的粗放设计，转而几乎全程使用 <span displaypfx="inline-" class="mathjax-container">\(3\times 3\)</span> 卷积，通过增加层数来扩大感受野、提高非线性表达能力，并让整套架构在工程上更统一、更易复用。</p>
<p>VGG 的典型结构非常整齐：若干个 <span displaypfx="inline-" class="mathjax-container">\(3\times 3\)</span> 卷积层组成一个 stage，stage 之间通过池化层下采样，最后再接全连接分类头。它的重要工程思想是“深度本身就是能力来源之一”。相较于更杂糅的早期网络，VGG 的层次感非常强：浅层提边缘和纹理，中层提局部部件，高层提更完整的语义结构。</p>
<p>为什么反复使用 <span displaypfx="inline-" class="mathjax-container">\(3\times 3\)</span> 卷积有效，可以从感受野和参数量两方面理解。两个连续的 <span displaypfx="inline-" class="mathjax-container">\(3\times 3\)</span> 卷积，其有效感受野接近一个 <span displaypfx="inline-" class="mathjax-container">\(5\times 5\)</span> 卷积，但中间多了一次非线性变换，参数量通常还更少。若忽略通道数变化，单层 <span displaypfx="inline-" class="mathjax-container">\(5\times 5\)</span> 卷积大约有 25 个核参数，而两层 <span displaypfx="inline-" class="mathjax-container">\(3\times 3\)</span> 卷积一共是 18 个核参数。</p>
<p>训练上，VGG 比 AlexNet 更依赖较好的初始化、较强的正则化和更大的算力预算，因为它的参数量尤其在全连接部分非常大。它的直接应用是图像分类，但工程上更著名的是作为“通用视觉特征提取器”：在风格迁移、感知损失（Perceptual Loss）、检测与分割早期系统中，VGG 特征长期是强基线。它的代价也很明显：参数多、推理重、显存占用高，这直接推动了后续更高效架构的出现。</p>
<div class="blog_h4"><span class="graybg">ResNet</span></div>
<p>若把上一节的“残差学习”放回视觉主线里看，ResNet（Residual Network）就是它在卷积神经网络中的代表性实现。它的关键突破不是再把卷积堆得更深，而是重新设计“深层网络应该学什么”。它提出残差学习（Residual Learning）：一层或一组层不必直接学习完整映射 <span displaypfx="inline-" class="mathjax-container">\(H(x)\)</span>，而是学习相对于输入的增量 <span displaypfx="inline-" class="mathjax-container">\(F(x)=H(x)-x\)</span>。这样网络输出就写成：</p>
<span displaypfx="" class="mathjax-container">\[y=F(x)+x\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 是块输入， <span displaypfx="inline-" class="mathjax-container">\(F(x)\)</span> 是若干卷积层、归一化和激活组成的残差分支输出， <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 是该残差块的最终输出。若最优映射本身就接近恒等映射（Identity Mapping），那么让网络学习“只改一点点”通常比“从零重建整个映射”更容易优化。</p>
<p>具象地看，普通深网络像要求每一层都重新写一遍完整答案；ResNet 则允许每一层在原答案旁边写批注：需要修改的地方补上增量，不需要改的地方直接走捷径。这个“捷径连接（Skip Connection）”让梯度能沿更短路径传播，因此网络深到几十层、上百层时仍可训练。ResNet 解决的核心不是表达能力不足，而是深层优化退化（Degradation）问题：层数增加后，训练误差反而上升。</p>
<p>训练上，ResNet 通常结合 BatchNorm、较深的 stage 结构和全局平均池化（Global Average Pooling）来替代庞大的全连接头。它在图像分类上大获成功后，很快成为检测、分割、关键点、视频理解等视觉任务的主流骨干网络。更深远的影响是：残差连接后来成为 Transformer、扩散模型和许多现代深网络的标准部件，因为它本质上是在为深层优化建立稳定的信息主通路。</p>
<div class="blog_h2"><span class="graybg">循环神经网络（RNN）</span></div>
<div class="blog_h3"><span class="graybg">RNN</span></div>
<p>循环神经网络（Recurrent Neural Network, RNN）按时间步（Time Step）处理序列。第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个时刻输入是 <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span>，隐藏状态（Hidden State）递推为：</p>
<span displaypfx="" class="mathjax-container">\[h_t=\phi(W_{xh}x_t+W_{hh}h_{t-1}+b_h),\quad y_t=W_{hy}h_t+b_y\]</span>
<p><span displaypfx="inline-" class="mathjax-container">\(W_{xh}\)</span>（输入到隐层）与 <span displaypfx="inline-" class="mathjax-container">\(W_{hh}\)</span>（隐层到隐层）在所有时刻共享，这是 RNN 能在变长序列上泛化的关键。</p>
<p>把它展开（Unroll）到时间轴上会更直观：同一套参数在每个时间步重复使用，形成一个深度为 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 的计算图。这也是 RNN 训练常说的“通过时间的反向传播（Backpropagation Through Time, BPTT）”。</p>
<p>RNN 的经典难点是梯度消失/爆炸（Vanishing/Exploding Gradients）：反向传播时会反复乘以 <span displaypfx="inline-" class="mathjax-container">\(W_{hh}\)</span> 的雅可比，从而导致梯度范数指数级衰减或增长。LSTM/GRU 通过门控（Gating）与更“线性”的记忆通道缓解这一问题。</p>
<p>Seq2Seq（Sequence-to-Sequence）是任务范式，不限定具体单元。早期 Seq2Seq 常由 RNN/LSTM/GRU 的编码器-解码器（Encoder-Decoder）实现；后续被 Transformer 大规模替代。</p>
<p>“乘法 + 加法 + 非线性”为何有效：线性变换负责特征重表达，非线性激活（Nonlinearity）提供函数逼近能力，时间递推提供记忆路径，三者叠加形成高表达力。</p>
<div class="blog_h3"><span class="graybg">LSTM</span></div>
<p>LSTM（Long Short-Term Memory）是为了解决普通 RNN 难以稳定保留长程信息的问题而设计的门控循环结构。它的核心思想不是简单把隐藏状态不断往前传，而是显式维护一个记忆单元（Cell State） <span displaypfx="inline-" class="mathjax-container">\(c_t\)</span>，并用门控决定“忘掉什么、写入什么、读出什么”。这使得序列中的关键信息可以沿着一条更接近线性的通道向后传播，而不必在每个时间步都被强非线性反复改写。</p>
<p>LSTM 的典型更新写成：</p>
<span displaypfx="" class="mathjax-container">\[f_t=\sigma(W_f x_t+U_f h_{t-1}+b_f),\quad i_t=\sigma(W_i x_t+U_i h_{t-1}+b_i)\]</span>
<span displaypfx="" class="mathjax-container">\[\tilde c_t=\tanh(W_c x_t+U_c h_{t-1}+b_c),\quad c_t=f_t\odot c_{t-1}+i_t\odot \tilde c_t\]</span>
<span displaypfx="" class="mathjax-container">\[o_t=\sigma(W_o x_t+U_o h_{t-1}+b_o),\quad h_t=o_t\odot \tanh(c_t)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span> 是当前输入， <span displaypfx="inline-" class="mathjax-container">\(h_{t-1}\)</span> 是前一时刻隐藏状态， <span displaypfx="inline-" class="mathjax-container">\(c_{t-1}\)</span> 是前一时刻记忆单元； <span displaypfx="inline-" class="mathjax-container">\(f_t\)</span> 是遗忘门（Forget Gate），控制旧记忆保留多少； <span displaypfx="inline-" class="mathjax-container">\(i_t\)</span> 是输入门（Input Gate），控制当前候选记忆 <span displaypfx="inline-" class="mathjax-container">\(\tilde c_t\)</span> 写入多少； <span displaypfx="inline-" class="mathjax-container">\(o_t\)</span> 是输出门（Output Gate），控制当前记忆暴露给隐藏状态多少； <span displaypfx="inline-" class="mathjax-container">\(\sigma\)</span> 是 sigmoid，把门值压到 <span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span> 区间； <span displaypfx="inline-" class="mathjax-container">\(\odot\)</span> 是逐元素乘法。</p>
<p>具象地看，LSTM 像一条带阀门的记忆水管。普通 RNN 每走一步都把旧信息和新输入一锅重拌，长距离信息容易被冲淡；LSTM 则允许旧记忆沿主管道直接往后流，同时用三个阀门分别控制“放掉旧水”“注入新水”“输出多少”。这正是它能比普通 RNN 更稳定地记住长距离依赖的原因。</p>
<p>训练上，LSTM 仍然通过时间反向传播（BPTT）学习参数，但门控结构显著改善了梯度流。它曾长期是机器翻译、语音识别、语言建模、时间序列预测和序列标注的主力模型。在 Transformer 出现之前，大量 Seq2Seq 系统都建立在双向 LSTM 或多层 LSTM 之上；在某些中小规模时序任务里，LSTM 今天仍然是强而稳的基线。</p>
<div class="blog_h3"><span class="graybg">GRU</span></div>
<p>GRU（Gated Recurrent Unit）可以看作 LSTM 的简化版本。它保留了“用门控控制信息保留与更新”的核心思想，但把记忆单元与隐藏状态合并，不再单独维护 <span displaypfx="inline-" class="mathjax-container">\(c_t\)</span>，从而用更少参数换取更紧凑的结构与更快的训练速度。</p>
<p>GRU 的一组典型公式是：</p>
<span displaypfx="" class="mathjax-container">\[z_t=\sigma(W_z x_t+U_z h_{t-1}+b_z),\quad r_t=\sigma(W_r x_t+U_r h_{t-1}+b_r)\]</span>
<span displaypfx="" class="mathjax-container">\[\tilde h_t=\tanh(W_h x_t+U_h(r_t\odot h_{t-1})+b_h)\]</span>
<span displaypfx="" class="mathjax-container">\[h_t=(1-z_t)\odot h_{t-1}+z_t\odot \tilde h_t\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(z_t\)</span> 是更新门（Update Gate），决定旧状态保留多少、新候选状态写入多少； <span displaypfx="inline-" class="mathjax-container">\(r_t\)</span> 是重置门（Reset Gate），决定在构造候选状态 <span displaypfx="inline-" class="mathjax-container">\(\tilde h_t\)</span> 时，历史信息应当被参考到什么程度。若 <span displaypfx="inline-" class="mathjax-container">\(z_t\)</span> 很小，模型更倾向保留旧记忆；若很大，模型更倾向用新信息刷新状态。</p>
<p>从直觉上看，GRU 把 LSTM 的三道阀门压缩成两道更紧凑的控制逻辑：一方面决定“要不要更新”，另一方面决定“生成候选更新时要不要忘掉旧状态的一部分”。这让它在很多任务上能以更少参数达到与 LSTM 相近的效果，尤其适合数据量不极大、模型容量受限或推理效率敏感的场景。</p>
<p>训练上，GRU 与 LSTM 一样使用 BPTT。应用上，它常见于语音、时序预测、较轻量的编码器-解码器模型，以及很多工业界的表格时间序列任务。若需要更强的显式记忆控制，LSTM 往往更稳；若更看重结构简洁和训练效率，GRU 常是更自然的选择。</p>
<div class="blog_h3"><span class="graybg">递归神经网络与序列堆叠变体</span></div>
<p>导图里的递归神经网络（Recursive Neural Network）与循环神经网络名字相近，但建模对象不同。RNN 按时间顺序在链式序列上递推；Recursive Neural Network 则沿树结构自底向上组合表示，更适合句法树、短语树或其他层次结构输入。Tree-RNN、Matrix-Vector RNN、Syntactically-Unified RNN 都属于这一支。它们在早期句法分析、情感组合性建模和结构化语义表示中很重要，但在 2026 年已经明显不是主流通用底座，更多作为结构归纳偏置的历史代表存在。</p>
<p>与之相邻的另一条工程线，是在链式序列模型上继续堆叠和增强，例如 Stacked LSTM、LSTM-CRF、Highway Connection。Stacked LSTM 通过多层递推增加表示深度；LSTM-CRF 把序列编码器与结构化解码结合，长期是序列标注强基线；Highway Connection 则让层间信息可以部分直通，缓解深层递推网络的优化困难。它们共同代表了 Transformer 出现之前，序列建模系统如何通过<span style="background-color: #c0c0c0;">门控、堆叠与结构化解码</span>不断逼近更强表达力的那条主线。</p>
<div class="blog_h2"><span class="graybg">生成模型</span></div>
<p>生成模型（Generative Model）关注的核心问题不是“这个样本属于哪一类”，而是“数据本身是如何产生出来的”。更形式化地说，它试图学习数据分布 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span>，或条件分布 <span displaypfx="inline-" class="mathjax-container">\(p(x|c)\)</span>，从而能够采样、重建、补全、去噪或按条件生成新样本。不同生成模型的主要差别在于它们选择了不同的概率建模路径：有的学隐变量，有的学博弈过程，有的学逐步去噪，有的更偏重表示压缩。</p>
<div class="blog_h3"><span class="graybg">自编码器（AE）</span></div>
<p>自编码器（Autoencoder, AE）的核心思想是“先压缩，再重建”。它把输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 通过编码器（Encoder）映射到低维或受约束的隐表示（Latent Representation） <span displaypfx="inline-" class="mathjax-container">\(z\)</span>，再通过解码器（Decoder）把 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 重建回 <span displaypfx="inline-" class="mathjax-container">\(\hat x\)</span>。如果模型在受限瓶颈下仍能较好重建输入，就说明 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 抓住了数据中的关键结构。</p>
<p>其基本形式可以写成：</p>
<span displaypfx="" class="mathjax-container">\[z=f_{\theta}(x),\qquad \hat x=g_{\phi}(z)\]</span>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{\mathrm{AE}}=\|x-\hat x\|_2^2\quad \text{或}\quad -\sum_i x_i\log \hat x_i\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(f_{\theta}\)</span> 是编码器， <span displaypfx="inline-" class="mathjax-container">\(g_{\phi}\)</span> 是解码器， <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 是瓶颈表示， <span displaypfx="inline-" class="mathjax-container">\(\hat x\)</span> 是重建结果。若输入是连续值，常用均方误差；若输入近似二值或归一化到概率意义，常用逐维交叉熵。这个目标迫使模型学习“怎样用更紧凑的表示保存足够重建原样本的信息”。</p>
<p>自编码器本身并不天然是强生成模型，因为它主要学会的是“给定输入怎么重建自己”，而不是如何从一个规则、可采样的潜空间中稳定地产生新样本。它更适合理解为表示学习或降维模型。通过在结构或训练目标上增加限制，例如稀疏自编码器（Sparse AE）、去噪自编码器（Denoising AE）或收缩自编码器（Contractive AE），它可以学到更稳健的隐表示。</p>
<p>应用上，AE 常用于降维、异常检测、去噪、预训练和表征学习。例如在工业异常检测里，模型只用正常样本训练，推理时若某个输入无法被良好重建，重建误差就可能提示该样本偏离了正常分布。</p>
<div class="blog_h3"><span class="graybg">变分自编码器（VAE）</span></div>
<p>变分自编码器（Variational Autoencoder, VAE）是在 AE 基础上把“隐空间可采样”这件事做成概率建模的方案。它不再把编码器输出一个确定的潜向量，而是输出一个潜变量分布 <span displaypfx="inline-" class="mathjax-container">\(q_{\phi}(z|x)\)</span>；同时假设存在生成分布 <span displaypfx="inline-" class="mathjax-container">\(p_{\theta}(x|z)\)</span>。这样，模型既能重建输入，又能从一个规则的潜空间里采样新样本。</p>
<p>VAE 的核心训练目标是证据下界（Evidence Lower Bound, ELBO）：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{\mathrm{VAE}}=\mathbb{E}_{q_{\phi}(z|x)}[\log p_{\theta}(x|z)]-D_{\mathrm{KL}}\big(q_{\phi}(z|x)\,\|\,p(z)\big)\]</span>
<p>其中第一项是重建项，鼓励给定 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 时能把 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 生成回来；第二项是 KL 散度（Kullback-Leibler Divergence），把编码器输出的后验近似分布 <span displaypfx="inline-" class="mathjax-container">\(q_{\phi}(z|x)\)</span> 拉向先验分布 <span displaypfx="inline-" class="mathjax-container">\(p(z)\)</span>，通常取标准高斯 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{N}(0,I)\)</span>。这一步的作用是把隐空间整理成“规则、连续、可插值、可采样”的形状。</p>
<p>训练上的关键技巧是重参数化（Reparameterization Trick）：若直接从 <span displaypfx="inline-" class="mathjax-container">\(q_{\phi}(z|x)\)</span> 采样，梯度难以回传；VAE 通常把采样写成 <span displaypfx="inline-" class="mathjax-container">\(z=\mu+\sigma\odot \epsilon\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\sim\mathcal{N}(0,I)\)</span>，而 <span displaypfx="inline-" class="mathjax-container">\(\mu,\sigma\)</span> 由编码器输出。这样随机性被转移到与参数无关的 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 上，梯度就能顺利穿过 <span displaypfx="inline-" class="mathjax-container">\(\mu,\sigma\)</span> 回传。</p>
<p>应用上，VAE 特别适合做潜空间操作，例如插值生成、属性控制、缺失补全和概率建模。与 GAN 相比，VAE 生成结果往往更平滑、更稳定，但图像清晰度常较弱；与扩散模型相比，VAE 在采样效率上更高，但生成质量上通常不是最强路线。</p>
<div class="blog_h3"><span class="graybg">生成对抗网络（GAN）</span></div>
<p>生成对抗网络（Generative Adversarial Network, GAN）的核心思想是对抗式学习：让生成器（Generator）负责“伪造样本”，让判别器（Discriminator）负责“分辨真假”，两者在博弈中共同提高。生成器学会把随机噪声变成越来越像真实数据的样本，判别器则学会识别这些样本是否来自真实分布。</p>
<p>经典 GAN 的目标写成：</p>
<span displaypfx="" class="mathjax-container">\[\min_G\max_D\ V(D,G)=\mathbb{E}_{x\sim p_{\mathrm{data}}}[\log D(x)]+\mathbb{E}_{z\sim p(z)}[\log(1-D(G(z)))] \]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 是从先验分布中采样的随机噪声， <span displaypfx="inline-" class="mathjax-container">\(G(z)\)</span> 是生成器产生的假样本， <span displaypfx="inline-" class="mathjax-container">\(D(x)\)</span> 输出输入样本为真样本的概率。判别器希望让真实样本分数高、伪样本分数低；生成器则希望骗过判别器，让 <span displaypfx="inline-" class="mathjax-container">\(G(z)\)</span> 看起来足够真实。</p>
<p>具象地看，GAN 像“伪造者”和“鉴定师”的对抗升级。鉴定师越强，伪造者就被迫学会更精细的伪造技巧；伪造者越强，鉴定师也必须学会更细致的辨别规则。理论上，这种动态会把生成分布推向真实数据分布；工程上，它带来的最大问题是训练不稳定，常见现象包括模式崩塌（Mode Collapse）、震荡和判别器过强导致生成器梯度过弱。</p>
<p>GAN 在图像生成、图像翻译、超分辨率和风格迁移里曾经极其成功，因为它特别善于生成锐利、感知上真实的图像纹理。但它对训练技巧依赖很强，后来在大规模高保真图像生成上逐渐被扩散模型压过；即便如此，GAN 在需要低步数快速生成或特定视觉变换任务中仍然很有生命力。</p>
<div class="blog_h3"><span class="graybg">扩散模型（Diffusion）</span></div>
<p>扩散模型（Diffusion Model）的核心思想是：把“直接学会生成复杂数据”拆成“先逐步加噪，再逐步去噪”这条更稳定的路径。前向过程把真实样本一步步污染成近似高斯噪声，反向过程则训练一个神经网络学会在每一步去掉一小部分噪声。最终从纯噪声出发，经过多步反向去噪，就能逐步生成结构清晰的样本。</p>
<p>记号上，前向扩散链从真实样本 <span displaypfx="inline-" class="mathjax-container">\(x_0\)</span> 出发，依次得到 <span displaypfx="inline-" class="mathjax-container">\(x_1,x_2,\dots,x_T\)</span>。因此 <span displaypfx="inline-" class="mathjax-container">\(x_0\)</span> 表示原始数据样本， <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span> 表示第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步加噪后的样本。</p>
<p>一个常见的前向加噪过程写成：</p>
<span displaypfx="" class="mathjax-container">\[q(x_t|x_{t-1})=\mathcal{N}\!\left(x_t;\sqrt{1-\beta_t}\,x_{t-1},\beta_t I\right)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\beta_t\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步的噪声强度：它控制从 <span displaypfx="inline-" class="mathjax-container">\(x_{t-1}\)</span> 走到 <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span> 时，原信号衰减多少、随机噪声注入多少。训练时常把从 <span displaypfx="inline-" class="mathjax-container">\(x_0\)</span> 到 <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span> 的若干步合并写成</p>
<span displaypfx="" class="mathjax-container">\[x_t=\sqrt{\bar\alpha_t}\,x_0+\sqrt{1-\bar\alpha_t}\,\epsilon,\qquad \epsilon\sim\mathcal{N}(0,I)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\bar\alpha_t\)</span> 是由噪声日程（Noise Schedule）累乘得到的系数，控制到第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 步时，原始信号保留了多少、噪声混入了多少。实际训练中，网络通常不直接预测 <span displaypfx="inline-" class="mathjax-container">\(x_0\)</span>，而是预测噪声 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span>，典型损失是：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{\mathrm{diff}}=\mathbb{E}\big[\|\epsilon-\epsilon_{\theta}(x_t,t)\|_2^2\big]\]</span>
<p>这条路径之所以有效，在于“预测一步噪声”比“直接一次生成整张复杂图片”更容易优化。训练上，扩散模型相对稳定，较少出现 GAN 那种对抗不平衡；代价是采样通常需要多步迭代，推理速度较慢。应用上，扩散模型已经成为图像生成、文生图、图像编辑、超分辨率、视频生成和分子设计的重要主线。Stable Diffusion 一类系统，本质上就是把扩散过程放到了潜空间（Latent Space）里执行，以降低像素空间扩散的计算成本。</p>
<div class="blog_h2"><span class="graybg">图神经网络（GNN）</span></div>
<p>图神经网络（Graph Neural Network, GNN）处理的对象不是规则网格上的序列或图像，而是由节点（Node）和边（Edge）组成的图（Graph）。它的核心问题是：当样本之间存在不规则连接关系时，如何让一个节点的表示同时反映“自己的特征”和“邻居结构中的上下文”。因此，GNN 的基本直觉不是滑动卷积核，也不是按时间递推，而是让节点在图上传递消息、聚合邻居信息，再更新自身表示。</p>
<div class="blog_h3"><span class="graybg">图卷积网络（GCN）</span></div>
<p>图卷积网络（Graph Convolutional Network, GCN）把卷积思想推广到图结构上。它的核心思想是：一个节点的新表示，不应只由自己决定，还应由其邻居节点的表示共同决定；但这种聚合不能简单相加，而需要根据图结构做适当归一化，否则高度节点会在信息聚合中占据过大权重。</p>
<p>GCN 的经典一层更新公式写成：</p>
<span displaypfx="" class="mathjax-container">\[H^{(l+1)}=\sigma\!\left(\tilde D^{-1/2}\tilde A\tilde D^{-1/2}H^{(l)}W^{(l)}\right)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(H^{(l)}\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(l\)</span> 层所有节点的表示矩阵； <span displaypfx="inline-" class="mathjax-container">\(W^{(l)}\)</span> 是该层可学习权重； <span displaypfx="inline-" class="mathjax-container">\(\tilde A=A+I\)</span> 表示在原邻接矩阵 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 上加自环（Self-loop），让节点保留自己的信息； <span displaypfx="inline-" class="mathjax-container">\(\tilde D\)</span> 是 <span displaypfx="inline-" class="mathjax-container">\(\tilde A\)</span> 的度矩阵（Degree Matrix）； <span displaypfx="inline-" class="mathjax-container">\(\sigma\)</span> 是非线性激活。中间那项对邻居求和并按度做归一化，本质上是在做“平滑的邻居平均”。</p>
<p>具象地看，GCN 像在社交网络里更新一个人的画像：不仅看这个人自己填写的特征，还参考他一跳邻居的大致特征，再做归一化，避免“朋友特别多的人”把自己的表示稀释得过于严重。多层堆叠后，一个节点就能间接接触到两跳、三跳甚至更远范围的信息。</p>
<p>训练上，GCN 常用于节点分类、图分类和链路预测。它的经典应用包括引文网络分类、分子图预测和推荐系统图表示学习。局限也很典型：层数太深时，节点表示会越来越相似，出现过平滑（Oversmoothing）；大图上直接用全图邻接矩阵训练也会带来显存与计算压力。</p>
<div class="blog_h3"><span class="graybg">图注意力网络（GAT）</span></div>
<p>图注意力网络（Graph Attention Network, GAT）在 GCN 的“统一归一化邻居平均”之上进一步提出：不同邻居的重要性不应被预先固定，而应由模型动态学习。它的核心思想是把注意力机制引入图结构，使每个节点在聚合邻居时，能够自适应决定“更该听谁的话”。</p>
<p>一层 GAT 的典型计算是：</p>
<span displaypfx="" class="mathjax-container">\[e_{ij}=a(Wh_i,Wh_j),\qquad \alpha_{ij}=\frac{\exp(e_{ij})}{\sum_{k\in \mathcal{N}(i)}\exp(e_{ik})}\]</span>
<span displaypfx="" class="mathjax-container">\[h_i'=\sigma\!\left(\sum_{j\in \mathcal{N}(i)} \alpha_{ij}\,Wh_j\right)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(h_i\)</span> 是节点 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 的输入表示， <span displaypfx="inline-" class="mathjax-container">\(W\)</span> 是线性变换矩阵， <span displaypfx="inline-" class="mathjax-container">\(a(\cdot,\cdot)\)</span> 是注意力打分函数， <span displaypfx="inline-" class="mathjax-container">\(e_{ij}\)</span> 是节点 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 对节点 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 的未归一化重要性分数， <span displaypfx="inline-" class="mathjax-container">\(\alpha_{ij}\)</span> 是在邻居集合 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{N}(i)\)</span> 内 softmax 归一化后的注意力权重。</p>
<p>与 GCN 相比，GAT 的关键收益是更灵活：若某个邻居特别重要，模型可以给它更高权重；若某个邻居噪声较大，则可自动抑制。训练上，GAT 仍通过监督或自监督目标学习节点表示，常用多头注意力（Multi-head Attention）稳定训练。应用上，它常见于异质关系更复杂、邻居重要性差异显著的图任务，例如社交网络分析、知识图谱局部推断和分子性质预测。</p>
<div class="blog_h3"><span class="graybg">GraphSAGE</span></div>
<p>GraphSAGE（Graph Sample and Aggregate）的核心思想是把 GNN 从“转导式（Transductive）图编码”推进到“归纳式（Inductive）图表示学习”。传统 GCN 常依赖整张训练图；GraphSAGE 则强调：即使测试时出现训练中未见过的新节点，只要能拿到它的邻居特征，也应能在线生成它的表示。</p>
<p>其典型更新形式是先采样邻居，再做聚合：</p>
<span displaypfx="" class="mathjax-container">\[h_{\mathcal{N}(v)}^{(k)}=\mathrm{AGG}^{(k)}\big(\{h_u^{(k-1)}:u\in \mathcal{N}(v)\}\big)\]</span>
<span displaypfx="" class="mathjax-container">\[h_v^{(k)}=\sigma\!\left(W^{(k)}[h_v^{(k-1)}\|h_{\mathcal{N}(v)}^{(k)}]\right)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(h_v^{(k)}\)</span> 是节点 <span displaypfx="inline-" class="mathjax-container">\(v\)</span> 在第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 层的表示， <span displaypfx="inline-" class="mathjax-container">\(\mathcal{N}(v)\)</span> 是其邻居集合， <span displaypfx="inline-" class="mathjax-container">\(\mathrm{AGG}\)</span> 可以是均值、池化或 LSTM 聚合器， <span displaypfx="inline-" class="mathjax-container">\([\cdot\|\cdot]\)</span> 表示向量拼接。GraphSAGE 的关键工程点是“采样”，因为超大图中不可能每次把全部邻居完整展开。</p>
<p>具象地看，GraphSAGE 像为每个节点建立一套“从邻居摘要中构造自我画像”的规则。它不要求记住整张训练图中每个节点的专属嵌入，而是学会一套可迁移的邻域聚合函数。这正是它能处理新节点、动态图和大规模图数据的原因。</p>
<p>应用上，GraphSAGE 在推荐系统、社交网络、风控图谱和工业知识图谱中非常常见，因为这些场景经常不断出现新节点、新边，归纳式能力比单纯在固定图上做转导预测更重要。</p>
<div class="blog_h3"><span class="graybg">消息传递机制（Message Passing）</span></div>
<p>消息传递（Message Passing）不是某一个具体模型，而是理解 GNN 的统一抽象框架。无论是 GCN、GAT 还是 GraphSAGE，本质上都可以拆成两步：第一步，节点从邻居那里接收消息；第二步，把这些消息与自己的旧表示结合，更新成新的节点表示。</p>
<p>这一抽象常写成：</p>
<span displaypfx="" class="mathjax-container">\[m_v^{(l+1)}=\mathrm{AGG}\Big(\{M^{(l)}(h_v^{(l)},h_u^{(l)},e_{uv})\,:\,u\in\mathcal{N}(v)\}\Big)\]</span>
<span displaypfx="" class="mathjax-container">\[h_v^{(l+1)}=U^{(l)}\big(h_v^{(l)},m_v^{(l+1)}\big)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(h_v^{(l)}\)</span> 是节点 <span displaypfx="inline-" class="mathjax-container">\(v\)</span> 在第 <span displaypfx="inline-" class="mathjax-container">\(l\)</span> 层的表示， <span displaypfx="inline-" class="mathjax-container">\(e_{uv}\)</span> 是边特征， <span displaypfx="inline-" class="mathjax-container">\(M^{(l)}\)</span> 是消息函数，决定一条边上传递什么信息； <span displaypfx="inline-" class="mathjax-container">\(\mathrm{AGG}\)</span> 是聚合函数，如求和、均值、最大值或注意力加权和； <span displaypfx="inline-" class="mathjax-container">\(U^{(l)}\)</span> 是更新函数，把旧表示与聚合后的消息合成为新表示。</p>
<p>这个框架的重要性在于，它把看似不同的图模型放进了一套统一语言里：GCN 相当于使用归一化线性消息与均值式聚合，GAT 相当于把注意力权重写进聚合，GraphSAGE 相当于强调采样与归纳式聚合。理解了消息传递，就能把很多图模型看成“消息函数、聚合函数、更新函数”三处设计选择的不同组合。</p>
<p>训练上，消息传递式 GNN 常用于三类任务：节点级任务（例如节点分类）、边级任务（例如链路预测）、图级任务（例如分子性质预测）。它们的共同难点包括：过平滑、邻居爆炸（Neighborhood Explosion）、异质图关系复杂，以及深层堆叠后长程依赖难以稳定传播。很多现代 GNN 改进，本质上都在围绕这几个瓶颈重新设计消息传递规则。</p>
<div class="blog_h2"><span class="graybg">ONNX</span></div>
<p>ONNX（Open Neural Network Exchange，开放神经网络交换格式）是一种描述机器学习模型的开放标准。它的核心作用，不是训练模型，而是把已经定义并训练好的神经网络表示成一种<span style="background-color: #c0c0c0;">可交换、可部署、跨框架可读</span>的中间格式，使模型能够从训练环境转移到不同推理环境中运行。</p>
<div class="blog_h3"><span class="graybg">它解决什么问题</span></div>
<p>训练阶段常用 PyTorch、TensorFlow、JAX 等框架；部署阶段则常落到 ONNX Runtime、TensorRT、OpenVINO、移动端推理引擎或嵌入式执行环境。问题在于，这些系统各自拥有不同的内部表示方式、图执行器和算子实现，原生模型格式并不天然互通。ONNX 解决的正是这条链路中的“中间表示”问题。</p>
<p>可以把 ONNX 理解为模型世界里的 PDF：训练框架先把网络导出为 <pre class="crayon-plain-tag">.onnx</pre> 文件，部署侧再由相应 runtime 读取、优化并执行。它并不等于某一种具体推理引擎，而更像一个让不同框架与部署后端彼此对接的交换层。</p>
<div class="blog_h3"><span class="graybg">一个 ONNX 文件里有什么</span></div>
<p>从结构上看，ONNX 文件本质上是一份<span style="background-color: #c0c0c0;">静态计算图（Static Computation Graph）加权重参数的描述</span>。其中通常包含四类核心信息：</p>
<ul>
<li>计算图：模型由哪些算子组成，例如卷积（Conv）、矩阵乘法（MatMul）、归一化、激活、注意力等。</li>
<li>张量元信息：输入、输出与中间张量的形状（Shape）和数据类型（Data Type）。</li>
<li>参数权重：训练后得到的权重矩阵、偏置和其他可学习参数；大模型也可能采用外部权重文件。</li>
<li>算子版本信息：模型依赖的 ONNX opset 版本，以及每个算子的语义约定。</li>
</ul>
<p>这种表示方式的关键价值，是把“Python 前向代码如何写”转写成“部署时应该执行哪条算子图”。一旦导出完成，部署系统通常不再依赖训练时的 Python 类定义，而是按 ONNX 图里的算子与依赖关系直接执行。</p>
<div class="blog_h3"><span class="graybg">常见用途</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">场景</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>部署加速</td>
<td>交给 ONNX Runtime、TensorRT 等后端做图优化、算子融合、低精度执行和量化推理</td>
</tr>
<tr>
<td>跨框架部署</td>
<td>例如用 PyTorch 训练，再导出 ONNX，交由另一套推理栈加载运行</td>
</tr>
<tr>
<td>多端适配</td>
<td>同一模型可进一步转接到服务器、边缘设备、移动端或嵌入式环境，前提是目标引擎支持相应算子</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">与 PyTorch 的关系</span></div>
<p>在 PyTorch 生态里，ONNX 最常见的入口是导出。开发者通常先在 PyTorch 中定义并训练模型，再用 <pre class="crayon-plain-tag">torch.onnx.export(...)</pre> 把模型转换成 ONNX 图。这个过程的本质，是把 PyTorch 里的前向路径从“解释执行的 Python 模块”改写成“显式算子图”。</p>
<p>导出之后，部署侧执行的已不再是原始 Python 前向代码，而是 ONNX 图中的算子序列。因此，依赖复杂动态控制流、框架私有算子或运行时分支逻辑的模型，在导出时往往需要额外改写、简化或替换成更可静态化的结构。</p>
<div class="blog_h3"><span class="graybg">局限与边界</span></div>
<p>ONNX 的价值很大，但它并不是“只要导出就一定能无缝部署”的万能格式。第一，并非所有训练框架中的算子都存在完美的 ONNX 映射，复杂模型可能导出失败，或需要改写成等价但更标准的图结构。第二，不同推理引擎对 ONNX 的支持程度并不完全一致：即使同样读取 ONNX 文件，也可能只支持某些 opset 版本或某一部分算子子集。第三，ONNX 更适合表达相对稳定的前向计算图；当模型强依赖高度动态的运行逻辑时，静态导出路径会更受约束。</p>
<p>因此，ONNX 更准确的定位不是“另一种训练框架”，而是<span style="background-color: #c0c0c0;">训练环境与部署环境之间的中间表示层</span>。它的意义在于把模型从原始框架内部释放出来，交给更适合推理、优化和跨平台执行的后端系统。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-2">人工智能理论知识 - 算法和机器学习</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/ai-knowledge-quick-ref-2/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>人工智能理论知识 - 数学基础</title>
		<link>https://blog.gmem.cc/ai-knowledge-quick-ref-1</link>
		<comments>https://blog.gmem.cc/ai-knowledge-quick-ref-1#comments</comments>
		<pubDate>Wed, 15 Apr 2026 12:39:04 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Algorithm]]></category>
		<category><![CDATA[Math]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=40317</guid>
		<description><![CDATA[<p>这一篇整理 AI 所需的数学基础，包括基础数学、线性代数、微积分与概率论统计。它回答的核心问题是：模型里的向量、矩阵、导数、积分、概率分布、期望与信息量分别是什么意思，以及它们为什么会成为后续机器学习与深度学习的共同语言；后续篇章将在这些数学工具之上进入算法、学习理论与模型结构。 基础数学 代数基础 运算律与代数式 代数式（Algebraic Expression）由常数、变量与运算构成；给定变量取值后即可求值。化简（Simplification）的目标是把表达式写成更可读、更便于推导/比较的等价形式：合并同类项（Like Terms）、提取公因子（Common Factor）、展开（Expansion）与因式分解（Factorization）是最常见的操作。 运算律（Algebraic Laws）本质上是在某个数系/代数结构（例如实数域）中成立的恒等式（Identity）；它们允许在不改变值的前提下重排/重写表达式。需要区分：加法/乘法满足交换律与结合律，但减法/除法一般不满足。 性质 公式 备注 交换律（Commutativity） 不适用于 、 结合律（Associativity） 允许不改变括号结构地分组 分配律（Distributivity） 展开与提因式的核心 <a class="read-more" href="https://blog.gmem.cc/ai-knowledge-quick-ref-1">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-1">人工智能理论知识 - 数学基础</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>这一篇整理 AI 所需的数学基础，包括基础数学、线性代数、微积分与概率论统计。它回答的核心问题是：模型里的向量、矩阵、导数、积分、概率分布、期望与信息量分别是什么意思，以及它们为什么会成为后续机器学习与深度学习的共同语言；后续篇章将在这些数学工具之上进入算法、学习理论与模型结构。</p>
<div class="blog_h1"><span class="graybg">基础数学</span></div>
<div class="blog_h2"><span class="graybg">代数基础</span></div>
<div class="blog_h3"><span class="graybg">运算律与代数式</span></div>
<p>代数式（Algebraic Expression）由常数、变量与运算构成；给定变量取值后即可求值。化简（Simplification）的目标是把表达式写成更可读、更便于推导/比较的等价形式：合并同类项（Like Terms）、提取公因子（Common Factor）、展开（Expansion）与因式分解（Factorization）是最常见的操作。</p>
<p>运算律（Algebraic Laws）本质上是在某个数系/代数结构（例如实数域）中成立的恒等式（Identity）；它们允许在不改变值的前提下重排/重写表达式。需要区分：加法/乘法满足交换律与结合律，但减法/除法一般不满足。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">性质</td>
<td style="text-align: center;">公式</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>交换律（Commutativity）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a+b=b+a\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(ab=ba\)</span></td>
<td>不适用于 <span displaypfx="inline-" class="mathjax-container">\(a-b\)</span>、<span displaypfx="inline-" class="mathjax-container">\(a/b\)</span></td>
</tr>
<tr>
<td>结合律（Associativity）</td>
<td><span displaypfx="inline-" class="mathjax-container">\((a+b)+c=a+(b+c)\)</span><br /><span displaypfx="inline-" class="mathjax-container">\((ab)c=a(bc)\)</span></td>
<td>允许不改变括号结构地分组</td>
</tr>
<tr>
<td>分配律（Distributivity）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a(b+c)=ab+ac\)</span></td>
<td>展开与提因式的核心</td>
</tr>
<tr>
<td>恒等元（Identity Element）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a+0=a\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(a\cdot 1=a\)</span></td>
<td>0 与 1 在代数推导中常被隐式使用</td>
</tr>
<tr>
<td>逆元（Inverse）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a+(-a)=0\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(a\cdot a^{-1}=1\)</span></td>
<td>第二式要求 <span displaypfx="inline-" class="mathjax-container">\(a\ne 0\)</span></td>
</tr>
<tr>
<td>零乘积原则（Zero Product）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(ab=0\Rightarrow a=0\ \text{或}\ b=0\)</span></td>
<td>把方程从“乘积=0”转为“因子=0”</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">因式分解（Factorization）</span></div>
<p>因式分解（Factorization）把一个表达式改写成若干因子（Factor）的乘积。它的直接价值是：把“值为 0 / 符号变化 / 约束条件”转成对各因子的分析。与之对偶的是展开（Expansion）：把乘积写成和式。</p>
<p>对多项式（Polynomial）<span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span>，根（Root/Zero）与线性因子之间存在精确对应：若 <span displaypfx="inline-" class="mathjax-container">\(p(r)=0\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\((x-r)\)</span> 是 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span> 的因子（因子定理（Factor Theorem））。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">套路/恒等式</td>
<td style="text-align: center;">形式</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>提公因子（Common Factor）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(ax+ay=a(x+y)\)</span></td>
<td>先把最大公因子提出来</td>
</tr>
<tr>
<td>平方差（Difference of Squares）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a^2-b^2=(a-b)(a+b)\)</span></td>
<td>常用于构造可约因子</td>
</tr>
<tr>
<td>完全平方（Perfect Square）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a^2\pm 2ab+b^2=(a\pm b)^2\)</span></td>
<td>与配方（Completing the Square）等价</td>
</tr>
<tr>
<td>立方和/差（Sum/Difference of Cubes）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a^3-b^3=(a-b)(a^2+ab+b^2)\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(a^3+b^3=(a+b)(a^2-ab+b^2)\)</span></td>
<td>二次因子在实数域可能不可再分</td>
</tr>
<tr>
<td>二次式按根分解</td>
<td><span displaypfx="inline-" class="mathjax-container">\(ax^2+bx+c=a(x-r_1)(x-r_2)\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(r_1,r_2\)</span> 可为复数</td>
</tr>
<tr>
<td>分组分解（Grouping）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(ax+ay+bx+by=(a+b)(x+y)\)</span></td>
<td>目标是制造共同因子</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">多项式（Polynomial）</span></div>
<p>一元多项式（Univariate Polynomial）是形如 <span displaypfx="inline-" class="mathjax-container">\(p(x)=\sum_{k=0}^{n} a_k x^k\)</span> 的函数，其中 <span displaypfx="inline-" class="mathjax-container">\(a_k\)</span> 是系数（Coefficient），<span displaypfx="inline-" class="mathjax-container">\(n\)</span> 是次数（Degree）。多项式在实数域上处处可导、可积；在局部逼近（例如 Taylor）与特征构造中非常常用。</p>
<p>多元多项式（Multivariate Polynomial）可写成对多重指数（Multi-index）求和：若 <span displaypfx="inline-" class="mathjax-container">\(x\in\mathbb{R}^d\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[p(x)=\sum_{\alpha\in\mathbb{N}^d} c_\alpha\,x^\alpha,\quad x^\alpha=\prod_{i=1}^{d} x_i^{\alpha_i}\]</span>
<p>补充：多元二次多项式（Quadratic Polynomial）的纯二次部分在线性代数里通常称为二次型（Quadratic Form），可写成矩阵形式 <span displaypfx="inline-" class="mathjax-container">\(q(\mathbf{x})=\mathbf{x}^\top A\mathbf{x}\)</span>；其中 <span displaypfx="inline-" class="mathjax-container">\(x_ix_j\)</span>（例如 <span displaypfx="inline-" class="mathjax-container">\(xy\)</span>）是交叉项（Cross Term）。</p>
<p>在机器学习里，<span style="background-color: #c0c0c0;">多项式特征（Polynomial Features）</span>把输入映射到包含高阶项的特征空间，等价于显式构造某些核函数（Kernel）的有限维版本。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">概念</td>
<td style="text-align: center;">表述</td>
<td style="text-align: center;">用途</td>
</tr>
</thead>
<tbody>
<tr>
<td>首项/首项系数（Leading Term/Coefficient）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a_n x^n\)</span> / <span displaypfx="inline-" class="mathjax-container">\(a_n\)</span></td>
<td>决定远端增长阶与符号</td>
</tr>
<tr>
<td>余数定理（Remainder Theorem）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(p(x)=q(x)(x-a)+p(a)\)</span></td>
<td>快速计算 <span displaypfx="inline-" class="mathjax-container">\(p(a)\)</span></td>
</tr>
<tr>
<td>因子定理（Factor Theorem）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(p(a)=0\Leftrightarrow (x-a)\mid p(x)\)</span></td>
<td>把“根”与“线性因子”连接起来</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">一元二次方程（Quadratic Equation）</span></div>
<p>一元二次方程（Quadratic Equation）标准形式为 <span displaypfx="inline-" class="mathjax-container">\(ax^2+bx+c=0\)</span>（<span displaypfx="inline-" class="mathjax-container">\(a\ne 0\)</span>）。核心量是判别式（Discriminant）<span displaypfx="inline-" class="mathjax-container">\(\Delta=b^2-4ac\)</span>：它决定解的个数与类型。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">结论</td>
<td style="text-align: center;">公式</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>求根公式（Quadratic Formula）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(x=\frac{-b\pm\sqrt{\Delta}}{2a}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\Delta&lt;0\)</span> 时根为共轭复数</td>
</tr>
<tr>
<td>配方（Completing the Square）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(ax^2+bx+c=a\left(x+\frac{b}{2a}\right)^2-\frac{\Delta}{4a}\)</span></td>
<td>同时给出顶点与最值</td>
</tr>
<tr>
<td>顶点（Vertex）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(x_v=-\frac{b}{2a},\quad f(x_v)=-\frac{\Delta}{4a}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a&gt;0\)</span> 时为全局最小；<span displaypfx="inline-" class="mathjax-container">\(a&lt;0\)</span> 时为全局最大</td>
</tr>
<tr>
<td>韦达定理（Vieta）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(r_1+r_2=-\frac{b}{a},\quad r_1r_2=\frac{c}{a}\)</span></td>
<td>无需显式求根即可得到对称量</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">分式与有理式（Rational Expressions）</span></div>
<p>分式/有理式（Rational Expression）是两个多项式之比：<span displaypfx="inline-" class="mathjax-container">\(\frac{p(x)}{q(x)}\)</span>，并要求 <span displaypfx="inline-" class="mathjax-container">\(q(x)\ne 0\)</span>。任何化简都必须保留定义域（Domain）约束：约分（Cancellation）只是在允许的点上重写表达式，不会“把不可取值点变得可取”。</p>
<p>典型例子： <span displaypfx="inline-" class="mathjax-container">\(\frac{x^2-1}{x-1}=\frac{(x-1)(x+1)}{x-1}=x+1\)</span>，但仍需强调 <span displaypfx="inline-" class="mathjax-container">\(x\ne 1\)</span>。这里 <span displaypfx="inline-" class="mathjax-container">\(x=1\)</span> 是可去间断点（Removable Discontinuity）：原式无定义，而约分后的表达式在该点有值。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">操作</td>
<td style="text-align: center;">规则</td>
<td style="text-align: center;">要点</td>
</tr>
</thead>
<tbody>
<tr>
<td>加减</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{a}{b}\pm\frac{c}{d}=\frac{ad\pm bc}{bd}\)</span></td>
<td>先通分（Common Denominator）</td>
</tr>
<tr>
<td>乘除</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{a}{b}\cdot\frac{c}{d}=\frac{ac}{bd}\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\frac{a}{b}\div\frac{c}{d}=\frac{a}{b}\cdot\frac{d}{c}\)</span></td>
<td>除法要求 <span displaypfx="inline-" class="mathjax-container">\(c\ne 0\)</span></td>
</tr>
<tr>
<td>约分</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{(x-r)u(x)}{(x-r)v(x)}=\frac{u(x)}{v(x)}\)</span></td>
<td>仍需保留 <span displaypfx="inline-" class="mathjax-container">\(x\ne r\)</span></td>
</tr>
<tr>
<td>符号分析</td>
<td>把数轴按零点/极点分段</td>
<td>有理不等式常用“区间符号表”</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">绝对值与分段（Absolute Value &amp; Piecewise）</span></div>
<p>绝对值（Absolute Value）<span displaypfx="inline-" class="mathjax-container">\(|x|\)</span> 表示到 0 的距离（Distance to Zero）。它把“正负”信息丢掉，只保留大小；因此绝对值相关方程/不等式通常要通过分段（Piecewise）把符号情况拆开讨论。</p>
<span displaypfx="" class="mathjax-container">\[|x|=\begin{cases}x,&amp; x\ge 0\\ -x,&amp; x&lt;0\end{cases}\]</span>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">形式</td>
<td style="text-align: center;">等价条件</td>
<td style="text-align: center;">前提</td>
</tr>
</thead>
<tbody>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(|x|=a\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(x=a\ \text{或}\ x=-a\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a\ge 0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(|x|&lt;a\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(-a&lt;x&lt;a\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a&gt;0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(|x|\le a\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(-a\le x\le a\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a\ge 0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(|x|\ge a\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(x\le -a\ \text{或}\ x\ge a\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a\ge 0\)</span></td>
</tr>
</tbody>
</table>
<p>三角不等式（Triangle Inequality）<span displaypfx="inline-" class="mathjax-container">\(|x+y|\le |x|+|y|\)</span> 是绝对值最重要的性质之一；它把“求和后的误差”上界化为“各自误差的和”，在误差分析与泛化界推导中高频出现。</p>
<p>深度学习里常见的 ReLU、hinge loss 等都是分段函数：<span style="background-color: #c0c0c0;">分段点处通常不可导，但仍可用次梯度（Subgradient）做优化</span>。</p>
<div class="blog_h3"><span class="graybg">常用代数技巧：配方 / 换元 / 估计</span></div>
<div class="blog_h4"><span class="graybg">配方（Completing the Square）</span></div>
<p>配方（Completing the Square）把二次式改写成“平方 + 常数”，从而直接读出最值、解的结构与可行区间：</p>
<span displaypfx="" class="mathjax-container">\[ax^2+bx+c=a\left(x+\frac{b}{2a}\right)^2-\frac{b^2-4ac}{4a}\]</span>
<p>例如 <span displaypfx="inline-" class="mathjax-container">\(x^2+6x+5=(x+3)^2-4\)</span>，因此方程 <span displaypfx="inline-" class="mathjax-container">\(x^2+6x+5=0\)</span> 等价于 <span displaypfx="inline-" class="mathjax-container">\((x+3)^2=4\)</span>，解为 <span displaypfx="inline-" class="mathjax-container">\(x=-1,-5\)</span>。</p>
<div class="blog_h4"><span class="graybg">换元（Substitution）</span></div>
<p>换元（Substitution）通过引入新变量，把原问题变成更低复杂度的标准形式，尤其适用于“重复结构”。例如：</p>
<span displaypfx="" class="mathjax-container">\[x^4-5x^2+4=0,\ \text{令 }u=x^2\Rightarrow u^2-5u+4=0\]</span>
<p>解得 <span displaypfx="inline-" class="mathjax-container">\(u=1,4\)</span>，再回代得到 <span displaypfx="inline-" class="mathjax-container">\(x=\pm 1,\pm 2\)</span>。</p>
<div class="blog_h4"><span class="graybg">估计（Bounding / Estimation）</span></div>
<p>估计（Bounding/Estimation）常把表达式改写为“非负项 + 常数”，或利用单调性把复杂项夹逼到可控区间。最常见的来源是“平方非负”（<span displaypfx="inline-" class="mathjax-container">\((\cdot)^2\ge 0\)</span>）：</p>
<span displaypfx="" class="mathjax-container">\[(x-1)^2\ge 0\Rightarrow x^2+1\ge 2x,\quad (|x|-1)^2\ge 0\Rightarrow x^2+1\ge 2|x|\]</span>
<p>这类估计在证明最值、构造上界/下界、以及把损失函数改写成“凸的主项 + 可控余项”时很有效。</p>
<div class="blog_h3"><span class="graybg">数系（Number Systems）</span></div>
<p>数系（Number Systems）描述“允许使用哪些数，以及这些数上哪些运算是封闭的”。常见链条是</p>
<span displaypfx="" class="mathjax-container">\[\mathbb{N}\subset \mathbb{Z}\subset \mathbb{Q}\subset \mathbb{R}\subset \mathbb{C}\]</span>
<p>其中实数（Real Numbers）是有序（Ordered）且完备（Complete）的；复数（Complex Numbers）扩展了方程可解性（例如 <span displaypfx="inline-" class="mathjax-container">\(x^2+1=0\)</span> 在实数无解，但在复数有解）。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">数系</td>
<td style="text-align: center;">记号</td>
<td style="text-align: center;">典型元素</td>
<td style="text-align: center;">结构要点</td>
</tr>
</thead>
<tbody>
<tr>
<td>自然数（Natural Numbers）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{N}\)</span></td>
<td>0,1,2,…（是否含 0 取决于约定）</td>
<td>对加法/乘法封闭；一般不可做减法/除法</td>
</tr>
<tr>
<td>整数（Integers）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{Z}\)</span></td>
<td>…,-2,-1,0,1,2,…</td>
<td>对加减乘封闭；除法不封闭</td>
</tr>
<tr>
<td>有理数（Rational Numbers）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{Q}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(p/q\)</span>（<span displaypfx="inline-" class="mathjax-container">\(p,q\in\mathbb{Z},q\ne 0\)</span>）</td>
<td>域（Field）：非零元素存在乘法逆元</td>
</tr>
<tr>
<td>实数（Real Numbers）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}\)</span></td>
<td>包含无理数（Irrational），如 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{2},\pi\)</span></td>
<td>有序完备域；极限/连续的基础</td>
</tr>
<tr>
<td>复数（Complex Numbers）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{C}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a+bi\)</span></td>
<td>代数闭包（Algebraic Closure）；不可定义全序</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">函数基础</span></div>
<div class="blog_h3"><span class="graybg">定义域与值域</span></div>
<p>函数（Function）是映射：把输入集合中的每个元素映到一个输出。定义域（Domain）是允许的输入集合；值域（Range/Image）是实际能取到的输出集合（通常是陪域（Codomain）的子集）。常用记法：</p>
<span displaypfx="" class="mathjax-container">\[f:D\to Y,\quad x\mapsto f(x)\]</span>
<p>从表达式读定义域的常见约束：</p>
<ul>
<li>分母不为 0： <span displaypfx="inline-" class="mathjax-container">\(\frac{p(x)}{q(x)}\)</span> 要求 <span displaypfx="inline-" class="mathjax-container">\(q(x)\ne 0\)</span>。</li>
<li>偶次根非负： <span displaypfx="inline-" class="mathjax-container">\(\sqrt{g(x)}\)</span> 要求 <span displaypfx="inline-" class="mathjax-container">\(g(x)\ge 0\)</span>（实数域）。</li>
<li>对数正数： <span displaypfx="inline-" class="mathjax-container">\(\ln g(x)\)</span> 要求 <span displaypfx="inline-" class="mathjax-container">\(g(x)&gt;0\)</span>。</li>
</ul>
<p>值域分析常用“解方程 + 约束”思路：令 <span displaypfx="inline-" class="mathjax-container">\(y=f(x)\)</span>，把 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 表示成 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 并推导可行条件；若 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 在某区间单调，则可用反函数直接得到值域。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">函数</td>
<td style="text-align: center;">定义域</td>
<td style="text-align: center;">值域</td>
</tr>
</thead>
<tbody>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\sqrt{x}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(x\ge 0\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(y\ge 0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\ln x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(x&gt;0\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}\)</span></td>
</tr>
<tr>
<td>sigmoid（<span displaypfx="inline-" class="mathjax-container">\(\sigma(z)=\frac{1}{1+e^{-z}}\)</span>）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\((0,1)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\tanh z\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\((-1,1)\)</span></td>
</tr>
<tr>
<td>ReLU（<span displaypfx="inline-" class="mathjax-container">\(\max(0,z)\)</span>）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\([0,+\infty)\)</span></td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">复合函数</span></div>
<p>复合函数（Function Composition）把一个函数的输出作为另一个函数的输入：若 <span displaypfx="inline-" class="mathjax-container">\(g:D\to E\)</span>、<span displaypfx="inline-" class="mathjax-container">\(f:E\to Y\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[(f\circ g)(x)=f(g(x))\]</span>
<p>定义域必须同时满足两层约束： <span displaypfx="inline-" class="mathjax-container">\(x\in\mathrm{dom}(g)\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(g(x)\in\mathrm{dom}(f)\)</span>。例如 <span displaypfx="inline-" class="mathjax-container">\(f(x)=\sqrt{x}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(g(x)=x^2-1\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\((f\circ g)(x)=\sqrt{x^2-1}\)</span> 的定义域是 <span displaypfx="inline-" class="mathjax-container">\(|x|\ge 1\)</span>。</p>
<p>复合满足结合律（Associativity）：<span displaypfx="inline-" class="mathjax-container">\((f\circ g)\circ h=f\circ(g\circ h)\)</span>，但一般不满足交换律： <span displaypfx="inline-" class="mathjax-container">\(f\circ g\ne g\circ f\)</span>。</p>
<div class="blog_h3"><span class="graybg">反函数</span></div>
<p>反函数（Inverse Function）把映射“倒过来”。若 <span displaypfx="inline-" class="mathjax-container">\(f:D\to Y\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\(D\)</span> 上是双射（Bijection），则存在 <span displaypfx="inline-" class="mathjax-container">\(f^{-1}:\mathrm{range}(f)\to D\)</span> 满足</p>
<span displaypfx="" class="mathjax-container">\[f^{-1}(f(x))=x,\quad f(f^{-1}(y))=y\]</span>
<p>注意 <span displaypfx="inline-" class="mathjax-container">\(f^{-1}\)</span> 表示反函数，不是倒数 <span displaypfx="inline-" class="mathjax-container">\(1/f\)</span>。求反函数的常用步骤是：设 <span displaypfx="inline-" class="mathjax-container">\(y=f(x)\)</span>，交换 <span displaypfx="inline-" class="mathjax-container">\(x,y\)</span> 并解出 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>。</p>
<p>例：线性函数 <span displaypfx="inline-" class="mathjax-container">\(y=ax+b\)</span>（<span displaypfx="inline-" class="mathjax-container">\(a\ne 0\)</span>）的反函数是 <span displaypfx="inline-" class="mathjax-container">\(f^{-1}(y)=\frac{y-b}{a}\)</span>。sigmoid 的反函数是 logit：若 <span displaypfx="inline-" class="mathjax-container">\(p=\sigma(z)\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(z=\log\frac{p}{1-p}\)</span>（要求 <span displaypfx="inline-" class="mathjax-container">\(p\in(0,1)\)</span>）。</p>
<p>若函数不单调或不可一一对应（如 <span displaypfx="inline-" class="mathjax-container">\(f(x)=x^2\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}\)</span> 上），则必须限制定义域（例如限制为 <span displaypfx="inline-" class="mathjax-container">\(x\ge 0\)</span>）才能得到真正的反函数。</p>
<div class="blog_h3"><span class="graybg">奇偶性与单调性</span></div>
<p>奇偶性（Parity）描述对称性：偶函数（Even Function）满足 <span displaypfx="inline-" class="mathjax-container">\(f(-x)=f(x)\)</span>（关于 y 轴对称），奇函数（Odd Function）满足 <span displaypfx="inline-" class="mathjax-container">\(f(-x)=-f(x)\)</span>（关于原点对称）。</p>
<p>单调性（Monotonicity）描述“随输入增加，输出是否不减/不增”。在区间 <span displaypfx="inline-" class="mathjax-container">\(I\)</span> 上：</p>
<ul>
<li>单调递增（Monotone Increasing）：<span displaypfx="inline-" class="mathjax-container">\(x_1&lt;x_2\Rightarrow f(x_1)\le f(x_2)\)</span>。</li>
<li>严格递增（Strictly Increasing）：<span displaypfx="inline-" class="mathjax-container">\(x_1&lt;x_2\Rightarrow f(x_1)&lt;f(x_2)\)</span>。</li>
<li>单调递减/严格递减同理。</li>
</ul>
<p>单调函数在区间上必为单射（Injective），因此在该区间上可定义反函数。这也是为什么很多“不可逆”的函数（如 <span displaypfx="inline-" class="mathjax-container">\(x^2\)</span>）在限制到某个单调区间后就变得可逆。</p>
<div class="blog_h3"><span class="graybg">凸性与凹性</span></div>
<p>凸性（Convexity）是优化与泛化分析的核心几何性质。函数 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 在区间/凸集上是凸函数（Convex Function），当且仅当对任意 <span displaypfx="inline-" class="mathjax-container">\(x_1,x_2\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\lambda\in[0,1]\)</span> 都有</p>
<span displaypfx="" class="mathjax-container">\[f(\lambda x_1+(1-\lambda)x_2)\le \lambda f(x_1)+(1-\lambda)f(x_2)\]</span>
<p>凹函数（Concave Function）则把不等号方向反过来。几何上：凸函数“弦在图像上方”，凹函数“弦在图像下方”。</p>
<p>若 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 二阶可导，则一维判别很简单： <span displaypfx="inline-" class="mathjax-container">\(f''(x)\ge 0\)</span> 则凸， <span displaypfx="inline-" class="mathjax-container">\(f''(x)\le 0\)</span> 则凹；多变量情形把 <span displaypfx="inline-" class="mathjax-container">\(f''\)</span> 替换为 Hessian，要求其半正定/半负定。</p>
<p>典型例子： <span displaypfx="inline-" class="mathjax-container">\(x^2\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(e^x\)</span> 是凸函数；<span displaypfx="inline-" class="mathjax-container">\(\log x\)</span>（<span displaypfx="inline-" class="mathjax-container">\(x&gt;0\)</span>）是凹函数。很多经典损失（如 MSE、logistic loss）对模型输出是凸的，但对深度网络参数整体通常非凸。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/convex.jpg"><img class="alignnone size-full wp-image-40553" src="https://blog.gmem.cc/wp-content/uploads/2026/03/convex.jpg" alt="convex" width="100%" /></a></p>
<div class="blog_h2"><span class="graybg">方程与超平面</span></div>
<div class="blog_h3"><span class="graybg">线性方程（Ax + By + C = 0）</span></div>
<p>二维平面中，线性方程（Linear Equation）<span displaypfx="inline-" class="mathjax-container">\(Ax+By+C=0\)</span>（<span displaypfx="inline-" class="mathjax-container">\((A,B)\ne(0,0)\)</span>）表示一条直线（Line）。向量 <span displaypfx="inline-" class="mathjax-container">\((A,B)\)</span> 是法向量（Normal Vector）：它与直线方向垂直；常数项 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 控制沿法向量方向的平移。</p>
<p>该形式与超平面形式 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\cdot\mathbf{x}+b=0\)</span> 完全一致：只需取 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(x,y)^\top\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}=(A,B)^\top\)</span>、<span displaypfx="inline-" class="mathjax-container">\(b=C\)</span>。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">等价形式</td>
<td style="text-align: center;">表达式</td>
<td style="text-align: center;">条件/说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>斜截式（Slope-Intercept）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(y=mx+b\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(B\ne 0\)</span> 时 <span displaypfx="inline-" class="mathjax-container">\(m=-A/B,\ b=-C/B\)</span></td>
</tr>
<tr>
<td>截距（Intercepts）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(x\text{-截距}=-C/A\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(y\text{-截距}=-C/B\)</span></td>
<td>分别要求 <span displaypfx="inline-" class="mathjax-container">\(A\ne 0\)</span>、<span displaypfx="inline-" class="mathjax-container">\(B\ne 0\)</span></td>
</tr>
<tr>
<td>点法式（Point-Normal）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\cdot(\mathbf{x}-\mathbf{x}_0)=0\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_0\)</span> 是直线上一点</td>
</tr>
<tr>
<td>点到直线距离</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{dist}=\frac{|Ax_0+By_0+C|}{\sqrt{A^2+B^2}}\)</span></td>
<td>来自把点沿法向量投影到直线</td>
</tr>
</tbody>
</table>
<p>两条直线相交/平行可由法向量判断：若 <span displaypfx="inline-" class="mathjax-container">\((A_1,B_1)\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\((A_2,B_2)\)</span> 共线，则两直线平行（或重合）；否则相交，交点可由 2×2 线性方程组求解。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/linear-equation-geometry.jpg"><img class="alignnone size-full wp-image-40541" src="https://blog.gmem.cc/wp-content/uploads/2026/03/linear-equation-geometry.jpg" alt="linear-equation-geometry" width="100%" /></a></p>
<div class="blog_h3"><span class="graybg">超平面（w·x + b = 0）</span></div>
<p>超平面（Hyperplane）是高维空间中的“线性边界”。在 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^d\)</span> 中，方程</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{w}\cdot \mathbf{x}+b=0\]</span>
<p>定义一个 <span displaypfx="inline-" class="mathjax-container">\((d-1)\)</span> 维的仿射子空间（Affine Subspace）。其中 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 是法向量（Normal Vector），决定边界的朝向；<span displaypfx="inline-" class="mathjax-container">\(b\)</span> 是偏置（Bias），决定边界沿法向量方向的平移。</p>
<p>工程与论文里常写成 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top \mathbf{x}+b\)</span>：这里的转置（Transpose）符号 <span displaypfx="inline-" class="mathjax-container">\(^\top\)</span> 只是为了把列向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 变成行向量，从而与列向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 做矩阵乘法；数值上它等价于点积：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{w}^\top \mathbf{x}=\sum_{i=1}^{d} w_i x_i\]</span>
<p>例：令 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}=(2,3)^\top\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(4,5)^\top\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top \mathbf{x}=2\cdot4+3\cdot5=23\)</span>。</p>
<p>在机器学习里，线性分类器（Linear Classifier）可写成 <span displaypfx="inline-" class="mathjax-container">\(\hat y=\mathrm{sign}(\mathbf{w}^\top\mathbf{x}+b)\)</span>；逻辑回归（Logistic Regression）把它送入 sigmoid： <span displaypfx="inline-" class="mathjax-container">\(p(y=1|\mathbf{x})=\sigma(\mathbf{w}^\top\mathbf{x}+b)\)</span>。</p>
<div class="blog_h3"><span class="graybg">法向量与半空间</span></div>
<p>超平面把空间划分成两个半空间（Half-space）：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{w}\cdot\mathbf{x}+b\ge 0,\quad \mathbf{w}\cdot\mathbf{x}+b\le 0\]</span>
<p>法向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 指向“值更大”的一侧：沿 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 方向移动会增大 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\cdot\mathbf{x}+b\)</span>。这也是为什么在线性分类里，得分的正负号自然对应类别划分。</p>
<div class="blog_h3"><span class="graybg">点到超平面的距离</span></div>
<p>点 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_0\)</span> 到超平面 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\cdot\mathbf{x}+b=0\)</span> 的欧氏距离（Euclidean Distance）为：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{dist}(\mathbf{x}_0,\ \mathbf{w}\cdot\mathbf{x}+b=0)=\frac{|\mathbf{w}\cdot\mathbf{x}_0+b|}{\|\mathbf{w}\|_2}\]</span>
<p>推导直觉：把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_0\)</span> 沿法向量方向投影到超平面上；分子是“沿法向量方向的带符号位移”，除以 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{w}\|\)</span> 把它变成真实距离。</p>
<div class="blog_h3"><span class="graybg">约束函数、梯度与法向量</span></div>
<p>约束优化中的边界通常由一个定义在整个空间上的标量函数（Scalar Function）<span displaypfx="inline-" class="mathjax-container">\(g(x)\)</span> 给出，并通过等值方程 <span displaypfx="inline-" class="mathjax-container">\(g(x)=0\)</span> 表示边界本身。函数 <span displaypfx="inline-" class="mathjax-container">\(g\)</span> 是求导对象；边界则是满足该方程的点集。梯度（Gradient）算子 <span displaypfx="inline-" class="mathjax-container">\(\nabla\)</span> 作用在约束函数 <span displaypfx="inline-" class="mathjax-container">\(g\)</span> 上，由此把边界的法向几何信息编码为一个向量场。</p>
<p>线性超平面的情形最直接。边界写成</p>
<span displaypfx="" class="mathjax-container">\[g(\mathbf{x})=\mathbf{w}^\top\mathbf{x}+b=0\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(g\)</span> 是定义在整个 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^d\)</span> 上的线性函数，而边界只是它的零等值面（Zero Level Set）。由于线性函数的一阶导数处处相同，立刻得到</p>
<span displaypfx="" class="mathjax-container">\[\nabla g(\mathbf{x})=\mathbf{w}\]</span>
<p>因此，线性超平面的法向量是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span>，本质上等价于“定义该超平面的约束函数 <span displaypfx="inline-" class="mathjax-container">\(g\)</span> 的梯度等于 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span>”。</p>
<p>这一结论对一般光滑边界同样成立。设 <span displaypfx="inline-" class="mathjax-container">\(x^*\)</span> 是边界 <span displaypfx="inline-" class="mathjax-container">\(g(x)=0\)</span> 上一点，且 <span displaypfx="inline-" class="mathjax-container">\(\nabla g(x^*)\ne 0\)</span>。若 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 是该点处的切向量（Tangent Vector），则沿着 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 做无穷小移动仍停留在同一条边界上，因而 <span displaypfx="inline-" class="mathjax-container">\(g\)</span> 的一阶变化为 0：</p>
<span displaypfx="" class="mathjax-container">\[\frac{d}{d\epsilon}g(x^*+\epsilon t)\Big|_{\epsilon=0}=0\]</span>
<p>应用链式法则（Chain Rule）得到</p>
<span displaypfx="" class="mathjax-container">\[\nabla g(x^*)^\top t=0\]</span>
<p>这表示边界上的任意切向量都与 <span displaypfx="inline-" class="mathjax-container">\(\nabla g(x^*)\)</span> 正交。因此 <span displaypfx="inline-" class="mathjax-container">\(\nabla g(x^*)\)</span> 沿法向方向指向边界外侧或内侧，而不沿边界本身滑动；梯度正是边界法向量的解析表达。</p>
<p>KKT 条件中的 <span displaypfx="inline-" class="mathjax-container">\(\nabla g(x^*)\)</span> 正是以这种方式出现的。约束优化不是对“边界这个集合”求导，而是借助约束函数 <span displaypfx="inline-" class="mathjax-container">\(g\)</span> 的梯度，把边界的法向几何结构写入一阶最优性条件。驻点条件</p>
<span displaypfx="" class="mathjax-container">\[\nabla f(x^*)+\lambda^*\nabla g(x^*)=0\]</span>
<p>表达的是：在活跃边界上，目标函数剩余的下降趋势完全落在约束边界的法向空间里，并与乘子加权后的法向量达到平衡。</p>
<div class="blog_h2"><span class="graybg">基础几何（Basic Geometry）</span></div>
<p>解析几何（Analytic Geometry）、向量（Vector）、三角函数（Trigonometric Functions）以及很多 AI 中的空间直觉，都建立在更基础的几何概念上。这里先把最常用的几块地基补齐：距离、角度、弧度、比例与面积。</p>
<div class="blog_h3"><span class="graybg">点、线、角与角度单位</span></div>
<p>平面几何最基本的对象是点（Point）、线段（Segment）、直线（Line）与角（Angle）。角度描述两条射线的张开程度；常见有两种单位：</p>
<span displaypfx="" class="mathjax-container">\[360^\circ=2\pi\ \text{rad},\quad 180^\circ=\pi\ \text{rad},\quad 90^\circ=\frac{\pi}{2}\ \text{rad}\]</span>
<p>度数（Degree）更适合日常表达，弧度（Radian）更适合数学推导，因为它和圆弧长度、三角函数、导数公式天然兼容。后面遇到旋转矩阵（Rotation Matrix）、复数极坐标（Polar Form）、傅里叶分析（Fourier Analysis）时，默认几乎都使用弧度。</p>
<div class="blog_h3"><span class="graybg">勾股定理与欧氏距离</span></div>
<p>勾股定理（Pythagorean Theorem）是平面距离公式的根源。对直角三角形，若两条直角边长为 <span displaypfx="inline-" class="mathjax-container">\(a,b\)</span>，斜边长为 <span displaypfx="inline-" class="mathjax-container">\(c\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[a^2+b^2=c^2\]</span>
<p>把它应用到坐标平面，就得到两点之间的欧氏距离（Euclidean Distance）：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{dist}(P_1,P_2)=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\]</span>
<p>在高维空间里，这个公式直接推广为 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{x}-\mathbf{y}\|_2\)</span>。因此从二维几何到机器学习里的向量距离，本质上是一条连续的概念链。KNN、K-means、embedding 检索、对比学习（Contrastive Learning）都在反复使用这套“距离越小越相似”的几何直觉。</p>
<div class="blog_h3"><span class="graybg">弧度、弧长与扇形</span></div>
<p>弧度由圆弧长度直接定义：若半径为 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 的圆上有一段弧长 <span displaypfx="inline-" class="mathjax-container">\(s\)</span>，对应圆心角为 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>（弧度），则</p>
<span displaypfx="" class="mathjax-container">\[\theta=\frac{s}{r},\quad s=r\theta\]</span>
<p>对应扇形面积（Sector Area）是：</p>
<span displaypfx="" class="mathjax-container">\[A=\frac{1}{2}r^2\theta\]</span>
<p>这正是弧度“自然”的原因：一旦用弧度记角，弧长与面积公式会变得非常干净。AI 里很多周期性表示都默认使用弧度输入，例如 <span displaypfx="inline-" class="mathjax-container">\(\sin\theta\)</span> / <span displaypfx="inline-" class="mathjax-container">\(\cos\theta\)</span> 的位置编码（Positional Encoding）、旋转位置编码（RoPE）以及频域特征（Fourier Features）。</p>
<div class="blog_h3"><span class="graybg">相似、缩放与比例</span></div>
<p>相似（Similarity）指图形形状相同、大小可以不同；等价地说，对应角相等、对应边成比例。若把一个图形按比例 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 缩放，则长度变为原来的 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 倍，面积变为原来的 <span displaypfx="inline-" class="mathjax-container">\(k^2\)</span> 倍。</p>
<p>这个看似初等的事实，在 AI 图像处理中极其常见：图片 resize、本征尺度（Scale）、特征金字塔（Feature Pyramid）、多尺度检测（Multi-scale Detection）都在处理“同一对象在不同尺度下如何保持可识别性”的问题。若缩放时不保持纵横比（Aspect Ratio），就会引入几何畸变，进而影响分类、检测与分割结果。</p>
<div class="blog_h3"><span class="graybg">面积、重叠与 IoU</span></div>
<p>基础几何里，面积（Area）衡量二维区域所占的大小。矩形面积是长乘宽，圆面积是</p>
<span displaypfx="" class="mathjax-container">\[A=\pi r^2\]</span>
<p>在 AI 的目标检测（Object Detection）与实例分割（Instance Segmentation）中，一个高频几何量是交并比（Intersection over Union, IoU）：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{IoU}=\frac{\text{Intersection Area}}{\text{Union Area}}\]</span>
<p>它衡量预测框/预测区域与真实标注的重叠程度。这里用到的不是高深数学，而是最朴素的面积与重叠概念。</p>
<div class="blog_h3"><span class="graybg">基础几何和AI</span></div>
<p>若把这些内容压缩成一句话，它们在 AI 中分别承担不同角色：</p>
<ul>
<li>距离（Distance）：支撑近邻搜索、聚类、向量检索与损失函数中的相似度刻画。</li>
<li>角度（Angle）：支撑方向、夹角、余弦相似度（Cosine Similarity）与旋转直觉。</li>
<li>弧度（Radian）：支撑三角函数、周期建模、位置编码与频域表示。</li>
<li>比例与缩放（Scale）：支撑图像 resize、数据增强、特征金字塔与多尺度建模。</li>
<li>面积与重叠（Area &amp; Overlap）：支撑 IoU、检测框评估与分割质量度量。</li>
</ul>
<div class="blog_h2"><span class="graybg">解析几何（Analytic Geometry）</span></div>
<p>解析几何（Analytic Geometry）的核心做法是：选定坐标系（Coordinate System），用代数方程描述几何对象。一个几何对象可以被理解为“满足某个方程（或方程组）的所有点”的集合。</p>
<p>高中阶段最常见的两类：</p>
<ul>
<li>一次方程：直线（Line）/平面（Plane）/超平面（Hyperplane）。</li>
<li>二次方程：圆（Circle）与圆锥曲线（Conic Sections）；在三维中推广为二次曲面（Quadric Surfaces）。</li>
</ul>
<div class="blog_h3"><span class="graybg">坐标、距离与圆</span></div>
<p>在直角坐标系（Cartesian Coordinate System）中，点 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 用坐标 <span displaypfx="inline-" class="mathjax-container">\((x,y)\)</span> 表示。两点 <span displaypfx="inline-" class="mathjax-container">\(P_1(x_1,y_1)\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(P_2(x_2,y_2)\)</span> 的欧氏距离（Euclidean Distance）是：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{dist}(P_1,P_2)=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\]</span>
<p>圆（Circle）是到某个固定点距离恒定的点集；这个固定点叫圆心（Center）。若圆心为 <span displaypfx="inline-" class="mathjax-container">\((h,k)\)</span>、半径（Radius）为 <span displaypfx="inline-" class="mathjax-container">\(r\)</span>，则圆的方程是：</p>
<span displaypfx="" class="mathjax-container">\[(x-h)^2+(y-k)^2=r^2\]</span>
<p>例：方程 <span displaypfx="inline-" class="mathjax-container">\(x^2+y^2-4x+6y-12=0\)</span> 通过配方（Completing the Square）可化为 <span displaypfx="inline-" class="mathjax-container">\((x-2)^2+(y+3)^2=25\)</span>，因此它表示圆心 <span displaypfx="inline-" class="mathjax-container">\((2,-3)\)</span>、半径 <span displaypfx="inline-" class="mathjax-container">\(5\)</span> 的圆。</p>
<div class="blog_h3"><span class="graybg">圆锥曲线（Conic Sections）：二次方程的几何形状</span></div>
<p>圆锥曲线（Conic Sections）最初来自“平面截圆锥”的几何构造，但在解析几何里，它们等价于二维的二次方程曲线（Second-degree Plane Curves，方程里变量的最高次数为 2）：</p>
<span displaypfx="" class="mathjax-container">\[Ax^2+Bxy+Cy^2+Dx+Ey+F=0\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(Bxy\)</span> 是交叉项（Cross Term）。交叉项的存在通常意味着曲线的主轴（Principal Axes）与坐标轴不对齐；通过旋转坐标轴（Rotation of Axes）可以把交叉项消掉，从而得到更“标准”的形状表达。</p>
<p>而一次项 <span displaypfx="inline-" class="mathjax-container">\(Dx+Ey\)</span> 与常数项 <span displaypfx="inline-" class="mathjax-container">\(F\)</span> 扮演的是另一类角色：它们通常不改变主轴方向，而主要影响图形在平面中的<span style="background-color: #c0c0c0;">位置与尺度</span>。更具体地说，一次项往往意味着曲线的中心/顶点不在原点；在消去交叉项之后，再通过平移坐标（Translation of Axes）与配方（Completing the Square）可以把一次项吸收到平方项里。常数项则相当于改变“等号右边的阈值”，会影响曲线是否有实点、整体大小以及离原点的偏置。简言之：<span style="background-color: #c0c0c0;">交叉项主要对应旋转，一次项主要对应平移，常数项主要对应整体偏移/尺度调整</span>。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/ellipse-term-effects.png"><img class="alignnone size-full wp-image-40767" src="https://blog.gmem.cc/wp-content/uploads/2026/03/ellipse-term-effects.png" alt="ellipse-term-effects" width="100%" /></a></p>
<p>下面给出最常用的四类圆锥曲线的标准方程（Standard Form）与直观定义：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">名称</td>
<td style="text-align: center;">标准方程</td>
<td style="text-align: center;">几何定义（直观）</td>
<td style="text-align: center;">形状关键词</td>
</tr>
</thead>
<tbody>
<tr>
<td>圆（Circle）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(x^2+y^2=r^2\)</span></td>
<td>到圆心距离恒为 <span displaypfx="inline-" class="mathjax-container">\(r\)</span></td>
<td>闭合；各向同性</td>
</tr>
<tr>
<td>椭圆（Ellipse）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{x^2}{a^2}+\frac{y^2}{b^2}=1\ (a\ge b&gt;0)\)</span></td>
<td>到两个焦点（Focus）距离和为常数</td>
<td>闭合；主轴/次轴</td>
</tr>
<tr>
<td>抛物线（Parabola）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(y^2=4px\ (p&gt;0)\)</span></td>
<td>到焦点（Focus）与准线（Directrix）距离相等</td>
<td>开口；无中心</td>
</tr>
<tr>
<td>双曲线（Hyperbola）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{x^2}{a^2}-\frac{y^2}{b^2}=1\)</span></td>
<td>到两个焦点（Focus）距离差的绝对值为常数</td>
<td>两支；有渐近线（Asymptotes）</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">椭圆（Ellipse）：两焦点“距离和恒定”</span></div>
<p>椭圆（Ellipse）可以用一句话定义：平面内到两个固定点 <span displaypfx="inline-" class="mathjax-container">\(F_1,F_2\)</span> 的距离之和为常数的点的集合。若该常数为 <span displaypfx="inline-" class="mathjax-container">\(2a\)</span>，则对椭圆上任意点 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 有：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{dist}(P,F_1)+\mathrm{dist}(P,F_2)=2a\]</span>
<p>在以原点为中心、长轴沿 x 轴的标准位置下，椭圆方程是：</p>
<span displaypfx="" class="mathjax-container">\[\frac{x^2}{a^2}+\frac{y^2}{b^2}=1,\quad a\ge b&gt;0\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(a\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 分别是半长轴（Semi-major Axis）与半短轴（Semi-minor Axis）。焦距参数 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 定义为焦点到中心的距离，满足</p>
<span displaypfx="" class="mathjax-container">\[c^2=a^2-b^2\]</span>
<p>因此焦点坐标是 <span displaypfx="inline-" class="mathjax-container">\((\pm c,0)\)</span>。偏心率（Eccentricity）定义为 <span displaypfx="inline-" class="mathjax-container">\(e=c/a\)</span>，它量化“椭圆有多扁”： <span displaypfx="inline-" class="mathjax-container">\(e\in[0,1)\)</span>；当 <span displaypfx="inline-" class="mathjax-container">\(a=b\)</span> 时 <span displaypfx="inline-" class="mathjax-container">\(e=0\)</span>，椭圆退化为圆。</p>
<p>例：若 <span displaypfx="inline-" class="mathjax-container">\(a=5,b=3\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(c=4\)</span>、<span displaypfx="inline-" class="mathjax-container">\(e=0.8\)</span>，椭圆为 <span displaypfx="inline-" class="mathjax-container">\(\frac{x^2}{25}+\frac{y^2}{9}=1\)</span>，焦点为 <span displaypfx="inline-" class="mathjax-container">\((\pm 4,0)\)</span>。</p>
<div class="blog_h3"><span class="graybg">三维推广：二次曲面（Quadric Surfaces）</span></div>
<p>在三维中，圆锥曲线推广为二次曲面（Quadric Surfaces）：满足三元二次方程的点集。最一般的形式是：</p>
<span displaypfx="" class="mathjax-container">\[Ax^2+By^2+Cz^2+Dxy+Exz+Fyz+Gx+Hy+Iz+J=0\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(Dxy,Exz,Fyz\)</span> 是交叉项（Cross Term），对应“坐标轴没有对齐到曲面的主轴方向”。通过平移（Translation）与旋转（Rotation）可以把它化为标准型（Standard Form）：平移等价于把原点挪到合适的位置（通常是“中心”附近），旋转等价于把坐标轴转到主轴方向，从而一眼看出是“球/椭球/抛物面/双曲面”等哪一类。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">名称</td>
<td style="text-align: center;">典型方程（标准型）</td>
<td style="text-align: center;">直观描述</td>
</tr>
</thead>
<tbody>
<tr>
<td>球（Sphere）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(x^2+y^2+z^2=r^2\)</span></td>
<td>到中心距离恒定的点集（3D 的圆）</td>
</tr>
<tr>
<td>椭球（Ellipsoid）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{x^2}{a^2}+\frac{y^2}{b^2}+\frac{z^2}{c^2}=1\)</span></td>
<td>三个方向缩放不同的“球”；仍然闭合</td>
</tr>
<tr>
<td>椭圆抛物面（Elliptic Paraboloid）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(z=\frac{x^2}{a^2}+\frac{y^2}{b^2}\)</span></td>
<td>“碗状”；水平截面是椭圆</td>
</tr>
<tr>
<td>双曲抛物面（Hyperbolic Paraboloid）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(z=\frac{x^2}{a^2}-\frac{y^2}{b^2}\)</span></td>
<td>“马鞍形”；沿一个方向上凸、另一个方向下凹</td>
</tr>
<tr>
<td>单叶双曲面（Hyperboloid of One Sheet）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{x^2}{a^2}+\frac{y^2}{b^2}-\frac{z^2}{c^2}=1\)</span></td>
<td>连通的一张曲面；截面随方向变化</td>
</tr>
<tr>
<td>双叶双曲面（Hyperboloid of Two Sheets）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(-\frac{x^2}{a^2}-\frac{y^2}{b^2}+\frac{z^2}{c^2}=1\)</span></td>
<td>上下分离的两张曲面</td>
</tr>
</tbody>
</table>
<p>二次曲面与优化中的“二次型/曲率”是同一套数学语言：把坐标轴旋到主轴方向后，表达式会变成各轴平方项的加权和/差，从而直接暴露“碗状（局部最小）”与“鞍形（Saddle）”的结构。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/plot-quadric.png"><img class="alignnone size-full wp-image-40777" src="https://blog.gmem.cc/wp-content/uploads/2026/03/plot-quadric.png" alt="plot-quadric" width="100%" /></a></p>
<div class="blog_h2"><span class="graybg">指数函数</span></div>
<p>指数函数（Exponential Function）最常用的是自然指数 <span displaypfx="inline-" class="mathjax-container">\(e^x\)</span>。指数运算把“加法结构”映射为“乘法结构”，对数（Logarithm）作为反函数则把乘法结构拉平成加法结构。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">性质/公式</td>
<td style="text-align: center;">表达式</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>指数基本性质（Exponential Laws）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(e^{a+b}=e^a e^b\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(e^{a-b}=\frac{e^a}{e^b}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a,b\in\mathbb{R}\)</span></td>
</tr>
<tr>
<td>指数运算律（Exponent Rules）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a^0=1\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(a^m a^n=a^{m+n}\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\frac{a^m}{a^n}=a^{m-n}\)</span><br /><span displaypfx="inline-" class="mathjax-container">\((a^m)^n=a^{mn}\)</span><br /><span displaypfx="inline-" class="mathjax-container">\((ab)^n=a^n b^n\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a&gt;0,a\ne 1\)</span>；对整数指数最直接</td>
</tr>
<tr>
<td>自然常数（Euler's Number）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(e=\lim_{n\to\infty}\left(1+\frac{1}{n}\right)^n\)</span></td>
<td>极限刻画连续复利（Continuous Compounding）</td>
</tr>
<tr>
<td>微积分性质</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{d}{dx}e^x=e^x\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\frac{d}{dx}\ln x=\frac{1}{x}\)</span></td>
<td>以 <span displaypfx="inline-" class="mathjax-container">\(e\)</span> 为底时形式最简</td>
</tr>
<tr>
<td>连续增长微分方程</td>
<td><span displaypfx="inline-" class="mathjax-container">\(y'(t)=y(t),\ y(0)=1\Rightarrow y(t)=e^t\)</span></td>
<td>“增长率与当前值成正比”</td>
</tr>
<tr>
<td>与对数互逆</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\ln(e^x)=x\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(e^{\ln x}=x\)</span></td>
<td>第二式要求 <span displaypfx="inline-" class="mathjax-container">\(x&gt;0\)</span></td>
</tr>
</tbody>
</table>
<p>常用取值：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">输入</td>
<td style="text-align: center;"><span displaypfx="inline-" class="mathjax-container">\(e^x\)</span></td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(x=0\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(e^0=1\)</span></td>
<td>基准点</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(x=1\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(e^1=e\approx 2.71828\)</span></td>
<td>自然对数底</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(x=-1\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(e^{-1}=\frac{1}{e}\approx 0.36788\)</span></td>
<td>常见衰减尺度</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(x=\ln 2\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(e^{\ln 2}=2\)</span></td>
<td>对数域与线性域互换时常用</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(x=\ln 10\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(e^{\ln 10}=10\)</span></td>
<td>与 <span displaypfx="inline-" class="mathjax-container">\(\log_{10}\)</span> 换底相关</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">对数函数</span></div>
<p>对数函数（Logarithm）<span displaypfx="inline-" class="mathjax-container">\(\log x\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\(x&gt;0\)</span> 上严格单调递增（Strictly Increasing），因此 <span displaypfx="inline-" class="mathjax-container">\(-\log x\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\(x&gt;0\)</span> 上严格单调递减（Strictly Decreasing）。</p>
<p>复合后的单调性由内层决定：若 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span> 在某区间上单调递增且 <span displaypfx="inline-" class="mathjax-container">\(f(x)&gt;0\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(-\log(f(x))\)</span> 在该区间上单调递减；若 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span> 不单调，则外层单调并不能推出整体单调。</p>
<p>在损失函数里常见的形式是 <span displaypfx="inline-" class="mathjax-container">\(-\log\sigma(z)\)</span>（<span displaypfx="inline-" class="mathjax-container">\(\sigma\)</span> 为 sigmoid）。因为 sigmoid 单调递增，所以该损失对 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 单调递减：增大 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 会降低损失。需要注意的是，这只说明“对中间量 z 的单调性”；对模型参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 的单调性一般不成立，因为 <span displaypfx="inline-" class="mathjax-container">\(z=z(\theta)\)</span> 是高维非线性函数。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">性质/公式</td>
<td style="text-align: center;">表达式</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>对数运算律（Logarithm Rules）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\log(ab)=\log a+\log b\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\log\!\left(\frac{a}{b}\right)=\log a-\log b\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\log(a^k)=k\log a\)</span></td>
<td>同一底数；典型要求 <span displaypfx="inline-" class="mathjax-container">\(a&gt;0,b&gt;0\)</span></td>
</tr>
<tr>
<td>换底公式（Change of Base）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\log_a x=\frac{\ln x}{\ln a}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a&gt;0,a\ne 1,x&gt;0\)</span></td>
</tr>
<tr>
<td>与指数互逆</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a^{\log_a x}=x\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\log_a(a^x)=x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(x&gt;0\)</span></td>
</tr>
</tbody>
</table>
<p>常用取值：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">表达式</td>
<td style="text-align: center;">值</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\ln 1\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(0\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(x=1\)</span> 为基准点</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\ln e\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(1\)</span></td>
<td>自然对数的定义性质</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\ln 2\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\approx 0.6931\)</span></td>
<td>二进制相关常数</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\ln 10\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\approx 2.3026\)</span></td>
<td>十进制相关常数</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\log_{10}2\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(=\frac{\ln 2}{\ln 10}\approx 0.3010\)</span></td>
<td>工程里常用于数量级估算</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\log_2 10\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(=\frac{\ln 10}{\ln 2}\approx 3.3219\)</span></td>
<td>bit 与十进制数量级换算</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\log_2 e\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(=\frac{1}{\ln 2}\approx 1.4427\)</span></td>
<td>nats 与 bits 换算常数</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\log_{10}e\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(=\frac{1}{\ln 10}\approx 0.4343\)</span></td>
<td>自然对数与常用对数换算</td>
</tr>
</tbody>
</table>
<p>在语言模型 softmax 中，logit 经过指数再归一化：<span displaypfx="inline-" class="mathjax-container">\(\exp(\text{logit})\)</span> 把分数映射为正数权重；取 log 则把乘法结构拉平成加法结构，便于用和式写出似然与损失。</p>
<div class="blog_h2"><span class="graybg">幂函数</span></div>
<p>幂函数（Power Function）里常见的两个扩展是负指数（Negative Exponent）与分数指数（Rational Exponent）。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类型</td>
<td style="text-align: center;">公式</td>
<td style="text-align: center;">条件/备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>负指数（Negative Exponent）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a^{-n}=\frac{1}{a^n}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a\ne 0\)</span>；<span displaypfx="inline-" class="mathjax-container">\(n\)</span> 为正整数</td>
</tr>
<tr>
<td>分数指数（Rational Exponent）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a^{p/q}=\sqrt[q]{a^p}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(q&gt;0,\gcd(p,q)=1\)</span>；实数域通常要求 <span displaypfx="inline-" class="mathjax-container">\(a&gt;0\)</span></td>
</tr>
<tr>
<td>例</td>
<td><span displaypfx="inline-" class="mathjax-container">\(2^{-3}=\frac{1}{8}\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(9^{1/2}=3\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(8^{2/3}=4\)</span></td>
<td>偶次根要求被开方数非负</td>
</tr>
</tbody>
</table>
<p>常用取值：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">表达式</td>
<td style="text-align: center;">值</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(2^{-3}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(=\frac{1}{8}=0.125\)</span></td>
<td>负指数转倒数</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(10^{-3}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(=0.001\)</span></td>
<td>毫（<span displaypfx="inline-" class="mathjax-container">\(10^{-3}\)</span>）尺度</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(2^{1/2}=\sqrt{2}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\approx 1.4142\)</span></td>
<td>最常见的无理数根</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(2^{-1/2}=\frac{1}{\sqrt{2}}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\approx 0.7071\)</span></td>
<td>正交归一化、幅度缩放常用</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(10^{1/2}=\sqrt{10}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\approx 3.1623\)</span></td>
<td>对数刻度下的“半个数量级”</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(10^{-1/2}=\frac{1}{\sqrt{10}}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\approx 0.3162\)</span></td>
<td>与上式互为倒数</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">三角函数</span></div>
<div class="blog_h3"><span class="graybg">基本三角恒等式</span></div>
<p>三角函数（Trigonometric Functions）可以用单位圆（Unit Circle）定义：在圆上角度为 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 的点坐标是 <span displaypfx="inline-" class="mathjax-container">\((\cos\theta,\sin\theta)\)</span>。由此得到最基本恒等式：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类别</td>
<td style="text-align: center; width: 50%;">公式</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>基本恒等式</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sin^2\theta+\cos^2\theta=1\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\tan\theta=\frac{\sin\theta}{\cos\theta}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\cos\theta\ne 0\)</span> 时定义 <span displaypfx="inline-" class="mathjax-container">\(\tan\theta\)</span></td>
</tr>
<tr>
<td>与 <span displaypfx="inline-" class="mathjax-container">\(\tan,\cot\)</span> 相关</td>
<td><span displaypfx="inline-" class="mathjax-container">\(1+\tan^2\theta=\sec^2\theta\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(1+\cot^2\theta=\csc^2\theta\)</span></td>
<td>定义域同 <span displaypfx="inline-" class="mathjax-container">\(\tan,\cot\)</span></td>
</tr>
<tr>
<td>和差公式（Angle Addition）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sin(\alpha\pm\beta)=\sin\alpha\cos\beta\pm\cos\alpha\sin\beta\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\cos(\alpha\pm\beta)=\cos\alpha\cos\beta\mp\sin\alpha\sin\beta\)</span></td>
<td>傅里叶分析、RoPE 等直觉常用</td>
</tr>
<tr>
<td>二倍角（Double-Angle）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sin 2\theta=2\sin\theta\cos\theta\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\cos 2\theta=\cos^2\theta-\sin^2\theta=1-2\sin^2\theta=2\cos^2\theta-1\)</span></td>
<td>同一恒等式的不同等价写法</td>
</tr>
<tr>
<td>周期性（Periodicity）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sin(\theta+2\pi)=\sin\theta\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\cos(\theta+2\pi)=\cos\theta\)</span></td>
<td>一个周期（Period）为 <span displaypfx="inline-" class="mathjax-container">\(2\pi\)</span></td>
</tr>
<tr>
<td>常用极限（<span displaypfx="inline-" class="mathjax-container">\(x\to 0\)</span>）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\lim_{x\to 0}\frac{\sin x}{x}=1\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\lim_{x\to 0}\frac{\tan x}{x}=1\)</span><br /><span displaypfx="inline-" class="mathjax-container">\(\lim_{x\to 0}\frac{1-\cos x}{x^2}=\frac{1}{2}\)</span></td>
<td>推导导数与近似时高频出现</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">常用特殊角（Special Angles）</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;"><span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>（弧度）</td>
<td style="text-align: center;">角度制</td>
<td style="text-align: center;"><span displaypfx="inline-" class="mathjax-container">\(\sin\theta\)</span></td>
<td style="text-align: center;"><span displaypfx="inline-" class="mathjax-container">\(\cos\theta\)</span></td>
<td style="text-align: center;"><span displaypfx="inline-" class="mathjax-container">\(\tan\theta\)</span></td>
</tr>
</thead>
<tbody>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(0\)</span></td>
<td>0°</td>
<td><span displaypfx="inline-" class="mathjax-container">\(0\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(1\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\pi/6\)</span></td>
<td>30°</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{1}{2}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{\sqrt{3}}{2}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{1}{\sqrt{3}}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\pi/4\)</span></td>
<td>45°</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{\sqrt{2}}{2}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{\sqrt{2}}{2}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(1\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\pi/3\)</span></td>
<td>60°</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{\sqrt{3}}{2}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{1}{2}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sqrt{3}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\pi/2\)</span></td>
<td>90°</td>
<td><span displaypfx="inline-" class="mathjax-container">\(1\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(0\)</span></td>
<td>未定义（undefined）</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\pi\)</span></td>
<td>180°</td>
<td><span displaypfx="inline-" class="mathjax-container">\(0\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(-1\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(3\pi/2\)</span></td>
<td>270°</td>
<td><span displaypfx="inline-" class="mathjax-container">\(-1\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(0\)</span></td>
<td>未定义（undefined）</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(2\pi\)</span></td>
<td>360°</td>
<td><span displaypfx="inline-" class="mathjax-container">\(0\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(1\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(0\)</span></td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">欧拉公式</span></div>
<p>欧拉公式（Euler's Formula）把指数与三角函数在复数域（Complex Domain）里统一起来：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">结论</td>
<td style="text-align: center;">公式</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>欧拉公式（Euler's Formula）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(e^{i\theta}=\cos\theta+i\sin\theta\)</span></td>
<td>把“旋转”写成复指数</td>
</tr>
<tr>
<td>辐角相加对应指数相乘</td>
<td><span displaypfx="inline-" class="mathjax-container">\(e^{i\theta_1}e^{i\theta_2}=e^{i(\theta_1+\theta_2)}\)</span></td>
<td>复数乘法：模相乘、辐角相加</td>
</tr>
<tr>
<td>欧拉恒等式（Euler's Identity）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(e^{i\pi}+1=0\)</span></td>
<td>连接 <span displaypfx="inline-" class="mathjax-container">\(e,i,\pi,1,0\)</span></td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">基础函数图像</span></div>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/image-of-common-fns.jpg"><img class="alignnone size-full wp-image-40557" src="https://blog.gmem.cc/wp-content/uploads/2026/03/image-of-common-fns.jpg" alt="image-of-common-fns" width="100%" /></a></p>
<div class="blog_h2"><span class="graybg">复数</span></div>
<p>复数（Complex Number）是对实数系（Real Number System）的扩展，写作 <span displaypfx="inline-" class="mathjax-container">\(a+bi\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(a,b\in\mathbb{R}\)</span>，虚数单位（Imaginary Unit）满足 <span displaypfx="inline-" class="mathjax-container">\(i^2=-1\)</span>。几何上，复数可以表示为复平面（Complex Plane）上的点 <span displaypfx="inline-" class="mathjax-container">\((a,b)\)</span>；但更重要的是，它在二维平面上提供了一套封闭、可逆且与乘法兼容的代数结构。</p>
<p>也正因为如此，复数不能简单等同于 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^2\)</span> 里的二维向量。表面上看，二维向量 <span displaypfx="inline-" class="mathjax-container">\((a,b)\)</span> 与复数 <span displaypfx="inline-" class="mathjax-container">\(a+bi\)</span> 确实对应同一个平面点；但它们的<span style="background-color: #c0c0c0;">代数结构</span>完全不同，关键差别在于“乘法”是否自然、闭合且可逆。</p>
<p>对二维向量而言，加法非常自然，但乘法并不形成一个像实数那样稳定的数系：点乘（Inner Product）会把两个向量变成标量，叉乘（Cross Product）又会把结果带到垂直方向；因此二维向量空间本身并没有一个同时兼顾<span style="background-color: #c0c0c0;">封闭性（Closure）</span>与<span style="background-color: #c0c0c0;">可除性</span>的内建乘法。复数则不同：两个复数相乘后仍是复数，非零复数还总能做除法。这使得复平面不只是“几何上的二维平面”，而是一个可自由做加减乘除的完整数系。</p>
<p>这带来两个二维向量本身不具备的优势。第一，复数把二维旋转直接写进了乘法：若 <span displaypfx="inline-" class="mathjax-container">\(z=re^{i\theta}\)</span>，则乘以另一个复数时会自动实现“模相乘、角相加”。换句话说，<span style="background-color: #c0c0c0;">复数乘法天然就是缩放 + 旋转</span>；而若只用二维向量，通常还需要额外引入旋转矩阵。第二，复数让多项式方程的可解性闭合：例如 <span displaypfx="inline-" class="mathjax-container">\(x^2+1=0\)</span> 在实数域无解，但在复数域有解 <span displaypfx="inline-" class="mathjax-container">\(\pm i\)</span>。更深一层地，代数基本定理（Fundamental Theorem of Algebra）说明任意非常数多项式在复数域里都有根，因此复数成为代数方程的自然终点。</p>
<p>因此，二维向量更像是在描述“箭头、位移、速度、受力”的线性对象；复数则是在同一个平面上额外安装了一套兼容乘法、旋转与方程求解的代数机制。对于信号处理（Signal Processing）、交流电分析、傅里叶变换（Fourier Transform）、量子力学以及很多 AI 中的频域方法，复数都不是“二维向量的重复发明”，而是一个更强的二维代数系统。</p>
<div class="blog_h3"><span class="graybg">复数的表示：直角坐标（Rectangular Form）</span></div>
<p>复数（Complex Number）写作 <span displaypfx="inline-" class="mathjax-container">\(z=a+bi\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(a,b\in\mathbb{R}\)</span>，虚数单位（Imaginary Unit）满足 <span displaypfx="inline-" class="mathjax-container">\(i^2=-1\)</span>。把 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 视为二维平面上的点 <span displaypfx="inline-" class="mathjax-container">\((a,b)\)</span>，就得到直角坐标（Rectangular Form）。</p>
<div class="blog_h3"><span class="graybg">复数的表示：极坐标（Polar Form）</span></div>
<p>同一个点也可用极坐标（Polar Form）表示：令 <span displaypfx="inline-" class="mathjax-container">\(r=|z|\)</span> 为模（Modulus），<span displaypfx="inline-" class="mathjax-container">\(\theta=\arg(z)\)</span> 为辐角（Argument），则</p>
<span displaypfx="" class="mathjax-container">\[z=r(\cos\theta+i\sin\theta)=re^{i\theta}\]</span>
<p>两种坐标之间的转换：</p>
<span displaypfx="" class="mathjax-container">\[r=\sqrt{a^2+b^2},\quad a=r\cos\theta,\quad b=r\sin\theta\]</span>
<p><span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 通常用 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{atan2}(b,a)\)</span> 计算，并且 <span displaypfx="inline-" class="mathjax-container">\(\arg(z)\)</span> 不是唯一的：加上任意 <span displaypfx="inline-" class="mathjax-container">\(2\pi k\)</span>（<span displaypfx="inline-" class="mathjax-container">\(k\in\mathbb{Z}\)</span>）表示同一个方向。</p>
<p>例：<span displaypfx="inline-" class="mathjax-container">\(z=1+i\)</span> 的模是 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{2}\)</span>，辐角是 <span displaypfx="inline-" class="mathjax-container">\(\pi/4\)</span>，因此 <span displaypfx="inline-" class="mathjax-container">\(z=\sqrt{2}\,e^{i\pi/4}\)</span>。</p>
<p>棣莫弗公式（De Moivre's Formula）给出幂运算的快捷形式：</p>
<span displaypfx="" class="mathjax-container">\[(\cos\theta+i\sin\theta)^n=\cos(n\theta)+i\sin(n\theta)\]</span>
<div class="blog_h3"><span class="graybg">共轭与模</span></div>
<p>复数 <span displaypfx="inline-" class="mathjax-container">\(z = a + bi\)</span> 的模（Modulus）定义为 <span displaypfx="inline-" class="mathjax-container">\(|z|=\sqrt{a^2+b^2}\)</span>，表示复平面（Complex Plane）中点 <span displaypfx="inline-" class="mathjax-container">\((a,b)\)</span> 到原点的欧氏距离（Euclidean Distance）。</p>
<p>共轭（Conjugate）记作 <span displaypfx="inline-" class="mathjax-container">\(\bar z = a-bi\)</span>。几何上，它把点 <span displaypfx="inline-" class="mathjax-container">\((a,b)\)</span> 关于实轴（Real Axis）镜像到 <span displaypfx="inline-" class="mathjax-container">\((a,-b)\)</span>；数值上，它把“相位（Phase）”取反而保持“幅值（Magnitude）”不变。</p>
<p>共轭与模的核心关系是 <span displaypfx="inline-" class="mathjax-container">\(z\bar z = |z|^2\)</span>，展开即可验证：</p>
<span displaypfx="" class="mathjax-container">\[(a+bi)(a-bi)=a^2+b^2=|z|^2\]</span>
<p>这个恒等式的一个直接用途是复数除法：为了避免分母含有虚部，把分母乘以共轭进行“有理化（Rationalization）”。</p>
<span displaypfx="" class="mathjax-container">\[\frac{a+bi}{c+di}=\frac{(a+bi)(c-di)}{(c+di)(c-di)}=\frac{(a+bi)(c-di)}{c^2+d^2}\]</span>
<p>例： <span displaypfx="inline-" class="mathjax-container">\(\frac{1+2i}{3-4i}=\frac{(1+2i)(3+4i)}{3^2+4^2}=\frac{-5+10i}{25}=-\frac{1}{5}+\frac{2}{5}i\)</span>。这里分母变成实数，是因为 <span displaypfx="inline-" class="mathjax-container">\((3-4i)(3+4i)=3^2+4^2\)</span>。</p>
<div class="blog_h3"><span class="graybg">复数乘法与旋转</span></div>
<p>把复数写成极坐标（Polar Form）：<span displaypfx="inline-" class="mathjax-container">\(z=r(\cos\theta+i\sin\theta)=re^{i\theta}\)</span>。此时复数乘法的几何意义非常直接：</p>
<span displaypfx="" class="mathjax-container">\[z_1 z_2 = (r_1 e^{i\theta_1})(r_2 e^{i\theta_2}) = (r_1 r_2)e^{i(\theta_1+\theta_2)}\]</span>
<p>也就是说：<span style="background-color: #c0c0c0;">模相乘、辐角相加</span>。乘法同时完成缩放（Scaling）与旋转（Rotation）。</p>
<p>例：乘以 <span displaypfx="inline-" class="mathjax-container">\(i=e^{i\pi/2}\)</span> 会把任意复数逆时针旋转 90° 且不改变模；乘以 <span displaypfx="inline-" class="mathjax-container">\(-1=e^{i\pi}\)</span> 会旋转 180°。例如 <span displaypfx="inline-" class="mathjax-container">\((1+2i)\cdot i=-2+i\)</span>，几何上就是把点 <span displaypfx="inline-" class="mathjax-container">\((1,2)\)</span> 旋到 <span displaypfx="inline-" class="mathjax-container">\((-2,1)\)</span>。</p>
<div class="blog_h2"><span class="graybg">数列与级数</span></div>
<p>求和符号（Summation Symbol）<span displaypfx="inline-" class="mathjax-container">\(\sum\)</span> 与乘积符号（Product Symbol）<span displaypfx="inline-" class="mathjax-container">\(\prod\)</span> 是数列与级数推导里最常见的两个“聚合”记号：</p>
<span displaypfx="" class="mathjax-container">\[\sum_{i=1}^{n} a_i=a_1+a_2+\cdots+a_n,\quad \prod_{i=1}^{n} a_i=a_1\cdot a_2\cdots a_n\]</span>
<p>英文里通常把 <span displaypfx="inline-" class="mathjax-container">\(\sum\)</span> 读作 “sigma” 或 “summation”，把 <span displaypfx="inline-" class="mathjax-container">\(\prod\)</span> 读作 “capital pi” 或 “product”。</p>
<div class="blog_h3"><span class="graybg">等差数列 / 等比数列</span></div>
<p>数列（Sequence）是一列按整数下标排列的数 <span displaypfx="inline-" class="mathjax-container">\(\{a_n\}_{n\ge 1}\)</span>。最常见的两类是等差数列（Arithmetic Sequence）与等比数列（Geometric Sequence）。它们都可用“递推定义 + 通项公式 + 前 n 项和”三件套描述。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类型</td>
<td style="text-align: center;">递推定义</td>
<td style="text-align: center;">通项（<span displaypfx="inline-" class="mathjax-container">\(a_n\)</span>）</td>
<td style="width: 35%; text-align: center;">前 n 项和（<span displaypfx="inline-" class="mathjax-container">\(S_n=\sum_{k=1}^{n} a_k\)</span>）</td>
</tr>
</thead>
<tbody>
<tr>
<td>等差数列（Arithmetic）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a_{n}=a_{n-1}+d\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a_n=a_1+(n-1)d\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(S_n=\frac{n}{2}(a_1+a_n)=\frac{n}{2}\left(2a_1+(n-1)d\right)\)</span></td>
</tr>
<tr>
<td>等比数列（Geometric）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(a_{n}=ra_{n-1}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a_n=a_1 r^{n-1}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(S_n=\begin{cases}\frac{a_1(1-r^n)}{1-r},&amp; r\ne 1 \\ na_1,&amp; r=1\end{cases}\)</span></td>
</tr>
</tbody>
</table>
<p>例：若 <span displaypfx="inline-" class="mathjax-container">\(a_1=1,d=2\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(a_n=2n-1\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(S_n=n^2\)</span>。若 <span displaypfx="inline-" class="mathjax-container">\(a_1=1,r=\frac{1}{2}\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(S_n=2\left(1-2^{-n}\right)\)</span>，并随 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 增大趋近于 2。</p>
<div class="blog_h3"><span class="graybg">无穷级数与收敛</span></div>
<p>无穷级数（Infinite Series）<span displaypfx="inline-" class="mathjax-container">\(\sum_{n=1}^{\infty} a_n\)</span> 的核心对象是部分和（Partial Sum）序列 <span displaypfx="inline-" class="mathjax-container">\(S_N=\sum_{n=1}^{N} a_n\)</span>。若极限 <span displaypfx="inline-" class="mathjax-container">\(\lim_{N\to\infty} S_N\)</span> 存在且为有限值，则级数收敛（Convergence）；否则发散（Divergence）。</p>
<p>必要条件：若 <span displaypfx="inline-" class="mathjax-container">\(\sum_{n=1}^{\infty} a_n\)</span> 收敛，则必有 <span displaypfx="inline-" class="mathjax-container">\(\lim_{n\to\infty} a_n=0\)</span>。反之不成立（例如调和级数 <span displaypfx="inline-" class="mathjax-container">\(\sum 1/n\)</span> 发散）。</p>
<p>绝对收敛（Absolute Convergence）指 <span displaypfx="inline-" class="mathjax-container">\(\sum |a_n|\)</span> 收敛；绝对收敛必推出原级数收敛。仅 <span displaypfx="inline-" class="mathjax-container">\(\sum a_n\)</span> 收敛但 <span displaypfx="inline-" class="mathjax-container">\(\sum |a_n|\)</span> 发散则为条件收敛（Conditional Convergence），此时项的重排可能改变和（甚至导致发散）。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/infinite-series.jpg"><img class="alignnone size-full wp-image-40597" src="https://blog.gmem.cc/wp-content/uploads/2026/03/infinite-series.jpg" alt="infinite-series" width="100%" /></a></p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">判别法</td>
<td style="text-align: center;">条件/计算量</td>
<td style="text-align: center;">结论</td>
</tr>
</thead>
<tbody>
<tr>
<td>几何级数（Geometric Series）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sum_{n=0}^{\infty} ar^n\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(|r|&lt;1\)</span> 时收敛，且和为 <span displaypfx="inline-" class="mathjax-container">\(\frac{a}{1-r}\)</span>；<span displaypfx="inline-" class="mathjax-container">\(|r|\ge 1\)</span> 时发散</td>
</tr>
<tr>
<td>p-级数（p-series）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sum_{n=1}^{\infty}\frac{1}{n^p}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(p&gt;1\)</span> 收敛；<span displaypfx="inline-" class="mathjax-container">\(p\le 1\)</span> 发散（调和级数为 <span displaypfx="inline-" class="mathjax-container">\(p=1\)</span>）</td>
</tr>
<tr>
<td>比较判别（Comparison）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(0\le a_n\le b_n\)</span>（充分大时）</td>
<td>若 <span displaypfx="inline-" class="mathjax-container">\(\sum b_n\)</span> 收敛，则 <span displaypfx="inline-" class="mathjax-container">\(\sum a_n\)</span> 收敛；若 <span displaypfx="inline-" class="mathjax-container">\(\sum a_n\)</span> 发散，则 <span displaypfx="inline-" class="mathjax-container">\(\sum b_n\)</span> 发散</td>
</tr>
<tr>
<td>比值判别（Ratio Test）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(L=\limsup_{n\to\infty}\left|\frac{a_{n+1}}{a_n}\right|\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(L&lt;1\)</span> 绝对收敛；<span displaypfx="inline-" class="mathjax-container">\(L&gt;1\)</span>（或无穷大）发散；<span displaypfx="inline-" class="mathjax-container">\(L=1\)</span> 不定</td>
</tr>
<tr>
<td>根值判别（Root Test）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\rho=\limsup_{n\to\infty}\sqrt[n]{|a_n|}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\rho&lt;1\)</span> 绝对收敛；<span displaypfx="inline-" class="mathjax-container">\(\rho&gt;1\)</span> 发散；<span displaypfx="inline-" class="mathjax-container">\(\rho=1\)</span> 不定</td>
</tr>
<tr>
<td>交错级数（Alternating Series）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sum (-1)^{n-1}b_n\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(b_n\downarrow 0\)</span></td>
<td>收敛；截断误差满足 <span displaypfx="inline-" class="mathjax-container">\(|S-S_N|\le b_{N+1}\)</span></td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">调和级数与调和数（Harmonic Series / Harmonic Numbers）</span></div>
<p>调和级数（Harmonic Series）是 <span displaypfx="inline-" class="mathjax-container">\(\sum_{n=1}^{\infty}\frac{1}{n}\)</span>。它是最经典的“项趋于 0 但级数仍发散”的例子：虽然 <span displaypfx="inline-" class="mathjax-container">\(\lim_{n\to\infty}\frac{1}{n}=0\)</span>，但部分和会无界增长。</p>
<p>其部分和称为调和数（Harmonic Number）：</p>
<span displaypfx="" class="mathjax-container">\[H_n=\sum_{k=1}^{n}\frac{1}{k}\]</span>
<p>调和数的渐近行为与对数紧密相关：<span displaypfx="inline-" class="mathjax-container">\(H_n=\log n+\gamma+o(1)\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span> 是欧拉-马歇罗尼常数（Euler–Mascheroni constant）。因此很多“累积量随步数缓慢增长”的分析最终都会出现 <span displaypfx="inline-" class="mathjax-container">\(\log n\)</span>。</p>
<p>在 AI/优化里，调和级数最常见的用途是解释与验证学习率（Learning Rate）衰减的收敛条件。经典随机逼近（Stochastic Approximation）的一个常用充分条件是：</p>
<span displaypfx="" class="mathjax-container">\[\sum_{t=1}^{\infty}\eta_t=\infty,\quad \sum_{t=1}^{\infty}\eta_t^2&lt;\infty\]</span>
<p>取 <span displaypfx="inline-" class="mathjax-container">\(\eta_t=\frac{1}{t}\)</span> 时，第一项对应调和级数发散（保证“走得足够远”），第二项对应 <span displaypfx="inline-" class="mathjax-container">\(\sum 1/t^2\)</span> 收敛（保证噪声的累计影响有限）。很多 SGD（Stochastic Gradient Descent）及在线学习（Online Learning）的理论推导，会用这组“一个发散、一个收敛”的对比来控制偏差与方差项。</p>
<div class="blog_h3"><span class="graybg">Taylor 展开</span></div>
<p>Taylor 展开（Taylor Expansion）用多项式在局部逼近光滑函数。Taylor 定理（Taylor's Theorem）强调“有限阶近似 + 余项（Remainder）”，Taylor 级数（Taylor Series）强调“无限项级数在收敛时等于原函数”。</p>
<span displaypfx="" class="mathjax-container">\[f(x)=\sum_{k=0}^{n}\frac{f^{(k)}(a)}{k!}(x-a)^k+R_{n+1}(x)\]</span>
<p>当余项在 <span displaypfx="inline-" class="mathjax-container">\(n\to\infty\)</span> 时收敛到 0，才有 <span displaypfx="inline-" class="mathjax-container">\(f(x)=\sum_{k=0}^{\infty}\frac{f^{(k)}(a)}{k!}(x-a)^k\)</span>。并非所有光滑函数都等于其 Taylor 级数。</p>
<p>例：在 <span displaypfx="inline-" class="mathjax-container">\(a=0\)</span> 展开，<span displaypfx="inline-" class="mathjax-container">\(e^x\approx 1+x+\frac{x^2}{2}\)</span>；当 <span displaypfx="inline-" class="mathjax-container">\(|x|\)</span> 很小时，用低阶项就能得到高精度近似。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/taylor-expansion.jpg"><img class="alignnone size-full wp-image-40583" src="https://blog.gmem.cc/wp-content/uploads/2026/03/taylor-expansion.jpg" alt="taylor-expansion" width="100%" /></a></p>
<div class="blog_h2"><span class="graybg">组合数学</span></div>
<p>组合数学（Combinatorics）研究离散结构的计数、构造与存在性。它的典型问题是“有多少种可能”：当选择空间巨大时，计数结果直接决定搜索/采样的复杂度；当把计数结果归一化为概率时，就得到二项分布、超几何分布等常见模型。</p>
<div class="blog_h3"><span class="graybg">排列与组合</span></div>
<p>排列（Permutation）与组合（Combination）的区别只在一个点：<span style="background-color: #c0c0c0;">是否区分顺序</span>。把“先选哪些元素”与“再怎么排序”分开理解，可以避免大量记忆负担。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">对象</td>
<td style="text-align: center;">记号</td>
<td style="width: 30%; text-align: center;">公式</td>
<td style="text-align: center;">直觉</td>
</tr>
</thead>
<tbody>
<tr>
<td>阶乘（Factorial）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(n!\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(n!=n(n-1)\cdots 2\cdot 1,\ 0!=1\)</span></td>
<td>把 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个互异元素全部排列的方式数</td>
</tr>
<tr>
<td>排列（k-permutation）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(P(n,k)\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(P(n,k)=\frac{n!}{(n-k)!}\)</span></td>
<td>从 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个里选 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个并排序：一步一步填位置</td>
</tr>
<tr>
<td>组合（k-combination）</td>
<td><span displaypfx="inline-" class="mathjax-container">\({n \choose k}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\({n \choose k}=\frac{n!}{k!(n-k)!}\)</span></td>
<td>从 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个里选 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个，不关心顺序：先选集合</td>
</tr>
<tr>
<td>排列与组合关系</td>
<td><span displaypfx="inline-" class="mathjax-container">\(P(n,k)\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(P(n,k)={n \choose k}\,k!\)</span></td>
<td>先选 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个元素，再把它们排序</td>
</tr>
</tbody>
</table>
<p>例：从 5 个候选特征里挑 3 个做一个无序子集，有 <span displaypfx="inline-" class="mathjax-container">\({5 \choose 3}=10\)</span> 种；如果还要给这 3 个特征排“处理顺序”，则变为排列 <span displaypfx="inline-" class="mathjax-container">\(P(5,3)=5\cdot4\cdot3=60\)</span> 种。</p>
<div class="blog_h3"><span class="graybg">二项式定理</span></div>
<p>二项式定理（Binomial Theorem）描述 <span displaypfx="inline-" class="mathjax-container">\((a+b)^n\)</span> 的展开结构：</p>
<span displaypfx="" class="mathjax-container">\[(a+b)^n=\sum_{k=0}^{n}{n \choose k}a^{n-k}b^k\]</span>
<p>系数 <span displaypfx="inline-" class="mathjax-container">\({n \choose k}\)</span> 的组合解释非常直接：把 <span displaypfx="inline-" class="mathjax-container">\((a+b)^n\)</span> 看作 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个括号相乘，每一项由“从每个括号里选 <span displaypfx="inline-" class="mathjax-container">\(a\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(b\)</span>”组成。若最终选了 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 次 <span displaypfx="inline-" class="mathjax-container">\(b\)</span>，就需要从 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个位置里挑出这 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 个位置，因此系数是 <span displaypfx="inline-" class="mathjax-container">\({n \choose k}\)</span>。</p>
<p>例：<span displaypfx="inline-" class="mathjax-container">\((x+2)^4={4 \choose 0}x^4+{4 \choose 1}x^3\cdot2+{4 \choose 2}x^2\cdot2^2+{4 \choose 3}x\cdot2^3+{4 \choose 4}2^4=x^4+8x^3+24x^2+32x+16\)</span>。</p>
<p>二项式系数还满足递推（Pascal's Rule）：<span displaypfx="inline-" class="mathjax-container">\({n \choose k}={n-1 \choose k-1}+{n-1 \choose k}\)</span>。这等价于“选与不选某个固定元素”的分类讨论。</p>
<p>与概率的连接：若独立伯努利试验（Bernoulli Trial）成功概率为 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>，做 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 次恰好成功 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 次的概率是 <span displaypfx="inline-" class="mathjax-container">\({n \choose k}p^k(1-p)^{n-k}\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\({n \choose k}\)</span> 计数的是“成功出现在哪些轮次”。</p>
<div class="blog_h2"><span class="graybg">不等式</span></div>
<p>不等式（Inequality）是把“难算/难优化/难比较”的表达式替换为“可控的上界或下界”的工具。机器学习中的许多目标函数与泛化分析，本质上都在用不等式把复杂量压到可操作的形式（例如把非线性函数用线性或二次上界近似）。</p>
<div class="blog_h3"><span class="graybg">基本不等式</span></div>
<p>基本不等式（Basic Inequalities）常见来源有两类：一类来自非负量（例如 <span displaypfx="inline-" class="mathjax-container">\((\cdot)^2\ge 0\)</span>），另一类来自范数（Norm）与凸性（Convexity）的结构性质。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">不等式</td>
<td style="text-align: center;">形式</td>
<td style="text-align: center;">典型用途</td>
</tr>
</thead>
<tbody>
<tr>
<td>平方非负</td>
<td><span displaypfx="inline-" class="mathjax-container">\((a-b)^2\ge 0\Rightarrow a^2+b^2\ge 2ab\)</span></td>
<td>把乘积项 <span displaypfx="inline-" class="mathjax-container">\(ab\)</span> 转成平方项，便于求界或优化（常见于“配方”）</td>
</tr>
<tr>
<td>三角不等式（Triangle Inequality）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(|x+y|\le |x|+|y|\)</span></td>
<td>把“相加后的绝对值”上界成“绝对值之和”；用于误差传播与残差界</td>
</tr>
<tr>
<td>均值不等式（AM-GM）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{a+b}{2}\ge \sqrt{ab}\quad (a,b\ge 0)\)</span></td>
<td>在“和固定时乘积最大”或“乘积/几何平均”相关问题里给出最紧的经典界</td>
</tr>
</tbody>
</table>
<p>例（AM-GM 的“最大乘积”直觉）：在 <span displaypfx="inline-" class="mathjax-container">\(a,b\ge 0\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(a+b=10\)</span> 的约束下，乘积满足 <span displaypfx="inline-" class="mathjax-container">\(ab\le \left(\frac{a+b}{2}\right)^2=25\)</span>，并且当且仅当 <span displaypfx="inline-" class="mathjax-container">\(a=b=5\)</span> 取等号。</p>
<div class="blog_h3"><span class="graybg">Jensen 不等式</span></div>
<p>Jensen 不等式（Jensen's Inequality）回答一个很具体的问题：<span style="background-color: #c0c0c0;">“平均”与“非线性”交换顺序会发生什么</span>。它是把抽象的凸性（Convexity）变成可用结论的最常用工具之一。</p>
<p>先把术语说清楚：</p>
<ul>
<li>加权平均（Weighted Average）：给一组数 <span displaypfx="inline-" class="mathjax-container">\(x_1,\dots,x_n\)</span> 分配权重 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i\ge 0\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(\sum_i\lambda_i=1\)</span>，则加权平均是 <span displaypfx="inline-" class="mathjax-container">\(\sum_i\lambda_i x_i\)</span>（把 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i\)</span> 视为“占比”即可）。</li>
<li>凸函数（Convex Function）：函数 <span displaypfx="inline-" class="mathjax-container">\(\varphi\)</span> 若满足对任意 <span displaypfx="inline-" class="mathjax-container">\(x_1,x_2\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\lambda\in[0,1]\)</span> 都有 <span displaypfx="inline-" class="mathjax-container">\(\varphi(\lambda x_1+(1-\lambda)x_2)\le \lambda\varphi(x_1)+(1-\lambda)\varphi(x_2)\)</span>，则称 <span displaypfx="inline-" class="mathjax-container">\(\varphi\)</span> 是凸的。直观上：它“向上弯”，因此对它做平均会被“惩罚”。</li>
<li>随机变量（Random Variable）：一个在不同试验/样本上会取不同值的量；把 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 视为“每次取到的数”。</li>
<li>期望（Expectation）<span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[\cdot]\)</span>：可把它理解为“按概率加权的平均”。</li>
</ul>
<p>Jensen 的形式（离散加权平均）：若 <span displaypfx="inline-" class="mathjax-container">\(\varphi\)</span> 凸，则</p>
<span displaypfx="" class="mathjax-container">\[\varphi\!\left(\sum_i \lambda_i x_i\right)\le \sum_i \lambda_i \varphi(x_i)\]</span>
<p>等价的期望形式：对随机变量 <span displaypfx="inline-" class="mathjax-container">\(X\)</span>（且 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X]\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[\varphi(X)]\)</span> 存在），有</p>
<span displaypfx="" class="mathjax-container">\[\varphi(\mathbb{E}[X])\le \mathbb{E}[\varphi(X)]\]</span>
<p>如果 <span displaypfx="inline-" class="mathjax-container">\(\varphi\)</span> 是凹函数（Concave Function），不等号方向会反过来。</p>
<p>场景 1：凸惩罚下，“波动”本身有代价。设某个系统的代价函数是 <span displaypfx="inline-" class="mathjax-container">\(\varphi(t)=t^2\)</span>（二次惩罚，Convex Penalty），比较两种延迟（Latency）：</p>
<ul>
<li>稳定方案：每次都是 100ms，则平均代价是 <span displaypfx="inline-" class="mathjax-container">\(100^2=10000\)</span>。</li>
<li>波动方案：一半时间 50ms、一半时间 150ms（平均同样是 100ms），则平均代价是 <span displaypfx="inline-" class="mathjax-container">\(\frac{50^2+150^2}{2}=12500\)</span>。</li>
</ul>
<p>两者平均延迟一样，但二次代价更偏好稳定方案；这就是 Jensen 在 <span displaypfx="inline-" class="mathjax-container">\(\varphi(t)=t^2\)</span> 下的直接体现：<span displaypfx="inline-" class="mathjax-container">\(\left(\frac{50+150}{2}\right)^2\le \frac{50^2+150^2}{2}\)</span>。</p>
<p>场景 2：对数损失下，“偶尔很错”会被放大。分类里常用对数损失（Log Loss）<span displaypfx="inline-" class="mathjax-container">\(\varphi(p)=-\log p\)</span>（<span displaypfx="inline-" class="mathjax-container">\(p\)</span> 是正确类别的预测概率），它在 <span displaypfx="inline-" class="mathjax-container">\((0,1]\)</span> 上是凸函数。假设一个模型在同一个样本上两次输出 <span displaypfx="inline-" class="mathjax-container">\(p=0.9\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(p=0.1\)</span>（一次很自信、一次几乎反过来），则</p>
<span displaypfx="" class="mathjax-container">\[-\log\!\left(\frac{0.9+0.1}{2}\right)=-\log 0.5\approx 0.693\le \frac{-\log 0.9-\log 0.1}{2}\approx 1.204\]</span>
<p>含义：在凸损失下，预测的波动会提高平均损失；这也是许多“用不等式构造上界/下界”方法（例如把难优化的期望目标变成可算的界）背后的数学原因。</p>
<p>何时取等号：当所有 <span displaypfx="inline-" class="mathjax-container">\(x_i\)</span> 相等（没有波动），或 <span displaypfx="inline-" class="mathjax-container">\(\varphi\)</span> 在相关区间上近似线性时，不等式可取等号。</p>
<div class="blog_h3"><span class="graybg">Cauchy-Schwarz 不等式</span></div>
<p>Cauchy-Schwarz 不等式（Cauchy–Schwarz Inequality）回答另一个非常基础的问题：<span style="background-color: #c0c0c0;">两组数“对齐相乘再求和”的结果，最多能有多大</span>。它是内积（Inner Product）与范数（Norm）体系的核心约束。</p>
<p>把术语说清楚后，这个不等式就不神秘：</p>
<ul>
<li>向量（Vector）：把一组数按顺序排成列表，例如 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}=(a_1,\dots,a_n)\)</span>。</li>
<li>点积/内积（Dot Product / Inner Product）：<span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}^\top\mathbf{b}=\sum_i a_i b_i\)</span>，可理解为“两组数在同一位置上的重叠程度”。</li>
<li>L2 范数（<span displaypfx="inline-" class="mathjax-container">\(\ell_2\)</span> Norm）：<span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{a}\|_2=\sqrt{\sum_i a_i^2}\)</span>，几何上是向量长度（Length）。</li>
</ul>
<p>不等式本身是：</p>
<span displaypfx="" class="mathjax-container">\[|\mathbf{a}^\top\mathbf{b}|\le \|\mathbf{a}\|_2\,\|\mathbf{b}\|_2\]</span>
<p>几何解释：把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}^\top\mathbf{b}\)</span> 写成 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{a}\|_2\|\mathbf{b}\|_2\cos\theta\)</span>，Cauchy-Schwarz 等价于 <span displaypfx="inline-" class="mathjax-container">\(|\cos\theta|\le 1\)</span>。当且仅当两向量同向或反向（线性相关（Linearly Dependent））时取等号。</p>
<p>场景 1：为什么检索里常用“余弦相似度（Cosine Similarity）”。在向量检索中，常用点积 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{q}^\top\mathbf{d}\)</span> 衡量查询向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{q}\)</span> 与文档向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{d}\)</span> 的相似度。但点积同时受“方向”和“长度”影响：如果某个向量范数很大，即使方向一般，点积也可能很大。</p>
<p>一个最小例子：令查询 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{q}=(1,1)\)</span>，两篇候选文档向量为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{d}_1=(100,0)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{d}_2=(2,2)\)</span>。点积分别是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{q}^\top\mathbf{d}_1=100\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{q}^\top\mathbf{d}_2=4\)</span>，点积会更偏向 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{d}_1\)</span>。但如果把向量归一化到单位范数（Unit Norm）再比较，则</p>
<span displaypfx="" class="mathjax-container">\[\cos(\mathbf{q},\mathbf{d}_1)=\frac{\mathbf{q}^\top\mathbf{d}_1}{\|\mathbf{q}\|_2\|\mathbf{d}_1\|_2}=\frac{1}{\sqrt{2}},\quad \cos(\mathbf{q},\mathbf{d}_2)=\frac{\mathbf{q}^\top\mathbf{d}_2}{\|\mathbf{q}\|_2\|\mathbf{d}_2\|_2}=1\]</span>
<p>归一化后 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{d}_2\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{q}\)</span> 方向完全一致，更符合“语义对齐”的直觉。Cauchy-Schwarz 保证余弦相似度一定落在 [-1,1]，从而成为一个尺度稳定、可解释的相似度。</p>
<p>场景 2：为什么相关系数（Correlation Coefficient）不可能超过 1。把两组已中心化（Centered，均值为 0）的数据序列看成向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x},\mathbf{y}\)</span>，它们的“协方差方向”可以写成点积 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top\mathbf{y}\)</span>，而各自的尺度由 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{x}\|_2,\|\mathbf{y}\|_2\)</span> 给出。Cauchy-Schwarz 直接推出</p>
<span displaypfx="" class="mathjax-container">\[\left|\frac{\mathbf{x}^\top\mathbf{y}}{\|\mathbf{x}\|_2\|\mathbf{y}\|_2}\right|\le 1\]</span>
<p>这就是“线性相关强度最多 100%”的数学原因；取等号对应完全线性关系，即 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}=c\mathbf{x}\)</span>。</p>
<div class="blog_h2"><span class="graybg">集合论基础</span></div>
<p>集合论（Set Theory）是现代数学语言的底座：几乎所有“对象 + 结构”的定义都能归结为集合及其上的运算。工程语境里，把对象抽象为集合的好处是：边界清晰、可组合、可用代数规则推导。</p>
<div class="blog_h3"><span class="graybg">集合运算（并 / 交 / 补）</span></div>
<p>集合（Set）是元素（Element）的无序聚合。记 <span displaypfx="inline-" class="mathjax-container">\(x\in A\)</span> 表示 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 属于集合 <span displaypfx="inline-" class="mathjax-container">\(A\)</span>，记 <span displaypfx="inline-" class="mathjax-container">\(A\subseteq B\)</span> 表示子集（Subset）。常用的运算如下。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">运算</td>
<td style="text-align: center; width: 150px;">记号</td>
<td style="text-align: center;">定义</td>
<td style="text-align: center;">例子</td>
</tr>
</thead>
<tbody>
<tr>
<td>并（Union）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(A\cup B\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\{x\mid x\in A\ \text{或}\ x\in B\}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\{1,2,3\}\cup\{3,4\}=\{1,2,3,4\}\)</span></td>
</tr>
<tr>
<td>交（Intersection）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(A\cap B\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\{x\mid x\in A\ \text{且}\ x\in B\}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\{1,2,3\}\cap\{3,4\}=\{3\}\)</span></td>
</tr>
<tr>
<td>差（Difference）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(A\setminus B\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\{x\mid x\in A,\ x\notin B\}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\{1,2,3\}\setminus\{3,4\}=\{1,2\}\)</span></td>
</tr>
<tr>
<td>补（Complement）</td>
<td><span displaypfx="inline-" class="mathjax-container">\(A^c\)</span></td>
<td>相对于全集 <span displaypfx="inline-" class="mathjax-container">\(U\)</span>： <span displaypfx="inline-" class="mathjax-container">\(A^c=U\setminus A\)</span></td>
<td>若 <span displaypfx="inline-" class="mathjax-container">\(U=\{1,2,3,4\}\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\{1,2,3\}^c=\{4\}\)</span></td>
</tr>
</tbody>
</table>
<p>De Morgan 律（De Morgan's Laws）是“补运算”与“并/交”之间的互换规则：</p>
<span displaypfx="" class="mathjax-container">\[(A\cup B)^c=A^c\cap B^c,\quad (A\cap B)^c=A^c\cup B^c\]</span>
<p>计数场景常用容斥原理（Inclusion–Exclusion）：<span displaypfx="inline-" class="mathjax-container">\(|A\cup B|=|A|+|B|-|A\cap B|\)</span>。例：一个数据集里“命中规则 A”的样本有 120 个，“命中规则 B”的有 80 个，同时命中的有 30 个，则命中至少一个规则的样本数是 <span displaypfx="inline-" class="mathjax-container">\(120+80-30=170\)</span>。</p>
<div class="blog_h3"><span class="graybg">映射与关系</span></div>
<p>映射（Mapping / Function）用来描述“从输入到输出”的确定性规则。写作 <span displaypfx="inline-" class="mathjax-container">\(f:A\to B\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 是定义域（Domain），<span displaypfx="inline-" class="mathjax-container">\(B\)</span> 是陪域（Codomain）。对 <span displaypfx="inline-" class="mathjax-container">\(x\in A\)</span>，输出写作 <span displaypfx="inline-" class="mathjax-container">\(f(x)\in B\)</span>。像集（Image / Range）为 <span displaypfx="inline-" class="mathjax-container">\(f(A)=\{f(x)\mid x\in A\}\)</span>。</p>
<p>关系（Relation）是更一般的概念：它不要求“每个输入对应唯一输出”。在集合论里，一个二元关系 <span displaypfx="inline-" class="mathjax-container">\(R\)</span> 可以看成笛卡尔积（Cartesian Product）上的子集：</p>
<span displaypfx="" class="mathjax-container">\[R\subseteq A\times B,\quad (a,b)\in R\ \text{表示}\ a\ R\ b\]</span>
<p>典型例子：</p>
<ul>
<li>等价关系（Equivalence Relation）：满足自反、对称、传递。例如定义 <span displaypfx="inline-" class="mathjax-container">\(x\sim y\Leftrightarrow x-y\)</span> 能被 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 整除（同余），它把整数划分为若干等价类（Equivalence Class）。</li>
<li>偏序（Partial Order）：满足自反、反对称、传递。例如 <span displaypfx="inline-" class="mathjax-container">\(\le\)</span> 是实数上的偏序；集合的包含关系 <span displaypfx="inline-" class="mathjax-container">\(\subseteq\)</span> 也是偏序。</li>
<li>一般关系：例如“相似度大于阈值”定义了一个关系，但它通常不具备传递性，因此不是等价关系。</li>
</ul>
<p>把关系写成矩阵/邻接矩阵（Adjacency Matrix）是常用表示：若 <span displaypfx="inline-" class="mathjax-container">\(A=B=\{1,\ldots,n\}\)</span>，定义 <span displaypfx="inline-" class="mathjax-container">\(M_{ij}=1\)</span> 当且仅当 <span displaypfx="inline-" class="mathjax-container">\((i,j)\in R\)</span>。在图论（Graph Theory）与推荐/检索（Retrieval）里，这种表示会直接进入线性代数计算。</p>
<div class="blog_h1"><span class="graybg">线性代数</span></div>
<div class="blog_h2"><span class="graybg">向量</span></div>
<div class="blog_h3"><span class="graybg">向量加减法（Vector Addition / Subtraction）</span></div>
<p>在 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^n\)</span> 中，向量加法与减法按分量（Component-wise）进行。对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}=(a_1,\ldots,a_n)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}=(b_1,\ldots,b_n)\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{a}+\mathbf{b}=(a_1+b_1,\ldots,a_n+b_n),\quad \mathbf{a}-\mathbf{b}=(a_1-b_1,\ldots,a_n-b_n)\]</span>
<p>几何上，加法对应向量合成；减法 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}-\mathbf{b}\)</span> 是从 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\)</span> 指向 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\)</span> 的位移向量。</p>
<p>例：若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}=(1,2)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}=(3,-1)\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}+\mathbf{b}=(4,1)\)</span>，<span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}-\mathbf{b}=(-2,3)\)</span>。</p>
<p>在 AI 里，残差连接（Residual Connection）是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}+f(\mathbf{x})\)</span>；梯度下降（Gradient Descent）的更新可写成 <span displaypfx="inline-" class="mathjax-container">\(\theta\leftarrow\theta-\eta\nabla L(\theta)\)</span>。它们都直接依赖向量加减法。</p>
<div class="blog_h3"><span class="graybg">点积（Dot Product）</span></div>
<p>点积（Dot Product）把两个向量映射为标量（Scalar），常用于相似度、投影和方向一致性判断。对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a},\mathbf{b}\in\mathbb{R}^n\)</span>，定义为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\cdot\mathbf{b}=\sum_{i=1}^n a_i b_i\)</span>。当 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a},\mathbf{b}\ne\mathbf{0}\)</span> 时，也可写为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\cdot\mathbf{b}=\|\mathbf{a}\|\|\mathbf{b}\|\cos\theta\)</span>。</p>
<p>点积满足交换律（Commutativity）：<span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\cdot\mathbf{b}=\mathbf{b}\cdot\mathbf{a}\)</span>。若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\cdot\mathbf{b}=0\)</span>，两向量正交（Orthogonal）。</p>
<p>向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\)</span> 方向上的标量投影是 <span displaypfx="inline-" class="mathjax-container">\(\frac{\mathbf{a}\cdot\mathbf{b}}{\|\mathbf{b}\|}\)</span>。投影更常用的是向量形式（Vector Projection）：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{proj}_{\mathbf{b}}(\mathbf{a})=\frac{\mathbf{a}\cdot\mathbf{b}}{\|\mathbf{b}\|^2}\mathbf{b}\]</span>
<p>当把向量归一化到单位范数（Unit Norm）后，点积就等于余弦相似度（Cosine Similarity）：<span displaypfx="inline-" class="mathjax-container">\(\cos\theta=\mathbf{\hat a}\cdot\mathbf{\hat b}\)</span>，这也是检索与表示学习中常见的相似度度量。</p>
<p>单位向量（Unit Vector）是范数为 1 的向量，常用来“只表示方向”。对非零向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\)</span>，其单位向量是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{\hat a}=\frac{\mathbf{a}}{\|\mathbf{a}\|}\)</span>。</p>
<p>方向角（Direction Angles）/方向余弦（Direction Cosines）描述单位向量与各坐标轴的夹角：在三维中，若单位向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}=(u_x,u_y,u_z)\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(u_x=\cos\alpha\)</span>、<span displaypfx="inline-" class="mathjax-container">\(u_y=\cos\beta\)</span>、<span displaypfx="inline-" class="mathjax-container">\(u_z=\cos\gamma\)</span>，并满足 <span displaypfx="inline-" class="mathjax-container">\(\cos^2\alpha+\cos^2\beta+\cos^2\gamma=1\)</span>。</p>
<p>点积之所以会出现“乘积”，是因为它等于“被投影长度 × 参照向量长度”： <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\cdot\mathbf{b}=\|\mathbf{b}\|\cdot \mathrm{projLen}_{\mathbf{b}}(\mathbf{a})\)</span>。当两向量同向时，投影长度就是 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{a}\|\)</span>，于是点积变为 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{a}\|\|\mathbf{b}\|\)</span>。</p>
<div class="blog_h4"><span class="graybg">二维向量的复数表示</span></div>
<p>在二维空间里，向量 <span displaypfx="inline-" class="mathjax-container">\((a,b)\)</span> 可以写成复数 <span displaypfx="inline-" class="mathjax-container">\(z=a+bi\)</span>。这不是把向量“变成另一种对象”，而是给同一个二维量换一种记法：实部对应 x 轴分量，虚部对应 y 轴分量。</p>
<p>若另一向量 <span displaypfx="inline-" class="mathjax-container">\((c,d)\)</span> 写成 <span displaypfx="inline-" class="mathjax-container">\(w=c+di\)</span>，则复共轭乘积为</p>
<span displaypfx="" class="mathjax-container">\[z\bar w=(a+bi)(c-di)=(ac+bd)+i(bc-ad)\]</span>
<p>其中实部 <span displaypfx="inline-" class="mathjax-container">\(ac+bd\)</span> 恰好就是二维向量的点积：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{a}\cdot\mathbf{b}=ac+bd=\mathrm{Re}(z\bar w)\]</span>
<p>若再把它们写成极坐标形式 <span displaypfx="inline-" class="mathjax-container">\(z=r_1e^{i\theta_1}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(w=r_2e^{i\theta_2}\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{a}\cdot\mathbf{b}=\mathrm{Re}(z\bar w)=r_1r_2\cos(\theta_1-\theta_2)\]</span>
<p>这里实部的来源可以直接从指数形式读出：因为 <span displaypfx="inline-" class="mathjax-container">\(\bar w=r_2e^{-i\theta_2}\)</span>，所以 <span displaypfx="inline-" class="mathjax-container">\(z\bar w=r_1r_2e^{i(\theta_1-\theta_2)}\)</span>。再用欧拉公式 <span displaypfx="inline-" class="mathjax-container">\(e^{i\phi}=\cos\phi+i\sin\phi\)</span> 展开，就得到 <span displaypfx="inline-" class="mathjax-container">\(z\bar w=r_1r_2\big(\cos(\theta_1-\theta_2)+i\sin(\theta_1-\theta_2)\big)\)</span>；其中实部正是 <span displaypfx="inline-" class="mathjax-container">\(r_1r_2\cos(\theta_1-\theta_2)\)</span>。</p>
<p>因此，点积既可以看成分量乘加，也可以看成“复数乘积取实部”。后一种写法把<span style="background-color: #c0c0c0;">长度</span>与<span style="background-color: #c0c0c0;">相位差</span>放进同一个式子里，在位置编码、旋转表示与频域分析中尤其方便。后文 RoPE 的复数视角正是沿用这层关系：二维块先写成复数，再让相位随位置旋转。</p>
<p>例：令 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}=(3,4)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}=(4,0)\)</span>。则 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\cdot\mathbf{b}=12\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{a}\|=5\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{b}\|=4\)</span>，所以 <span displaypfx="inline-" class="mathjax-container">\(\cos\theta=\frac{12}{20}=0.6\)</span>。而 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\)</span> 方向上的标量投影是 <span displaypfx="inline-" class="mathjax-container">\(\frac{12}{4}=3\)</span>，恰好对应 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\)</span> 的 x 分量。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/vector-ops.jpg"><img class="alignnone size-full wp-image-40599" src="https://blog.gmem.cc/wp-content/uploads/2026/03/vector-ops.jpg" alt="vector-ops" width="100%" /></a></p>
<div class="blog_h3"><span class="graybg">叉积（Cross Product）</span></div>
<p>叉积（Cross Product）定义在三维空间（3D Space）。结果是同时垂直于两个输入向量的向量，大小为 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{a}\times\mathbf{b}\|=\|\mathbf{a}\|\|\mathbf{b}\|\sin\theta\)</span>，等于两向量张成平行四边形的面积。</p>
<p>计算上，若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}=(a_1,a_2,a_3)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}=(b_1,b_2,b_3)\)</span>，则：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{a}\times\mathbf{b}=(a_2b_3-a_3b_2,\ a_3b_1-a_1b_3,\ a_1b_2-a_2b_1)\]</span>
<p>例： <span displaypfx="inline-" class="mathjax-container">\((1,0,0)\times(0,1,0)=(0,0,1)\)</span>。这个例子在几何上对应两个单位正交基向量张成的“正方形面积为 1”，方向由右手定则给出。</p>
<p>叉积为零当且仅当两向量平行（Parallel/Colinear）或至少有一个为零向量：这是因为 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{a}\times\mathbf{b}\|=\|\mathbf{a}\|\|\mathbf{b}\|\sin\theta\)</span>，为 0 只能来自 <span displaypfx="inline-" class="mathjax-container">\(\sin\theta=0\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{a}\|=0\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{b}\|=0\)</span>。</p>
<p>方向由右手定则（Right-Hand Rule）确定：四指从 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\)</span> 旋向 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\)</span>，拇指方向即 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}\times\mathbf{b}\)</span> 方向。旋转“正负”不是绝对物理事实，而是由坐标系（Coordinate System）与观察方向（View Direction）约定决定。</p>
<p>力矩（Torque）可作为叉积方向的直观例子： <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\tau}=\mathbf{r}\times\mathbf{F}\)</span>。这里保留物理解释的唯一目的，是帮助理解叉积的方向性。</p>
<div class="blog_h2"><span class="graybg">基（Basis）</span></div>
<p>在 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^n\)</span> 中，一组向量 <span displaypfx="inline-" class="mathjax-container">\(\{\mathbf{b}_1,\ldots,\mathbf{b}_n\}\)</span> 若线性无关（Linearly Independent）且张成（Span）整个空间，则称为一组基（Basis）。任何向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 都能唯一表示为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=\sum_{i=1}^{n} c_i\mathbf{b}_i\)</span>；系数向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{c}=(c_1,\ldots,c_n)^\top\)</span> 就是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 在该基下的坐标（Coordinates）。</p>
<p>把基向量按列堆成矩阵 <span displaypfx="inline-" class="mathjax-container">\(B=[\mathbf{b}_1\ \cdots\ \mathbf{b}_n]\in\mathbb{R}^{n\times n}\)</span>，则坐标与原向量满足 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=B\mathbf{c}\)</span>。换基（Change of Basis）在推导里本质就是在不同 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 之间切换坐标表示。</p>
<div class="blog_h3"><span class="graybg">换基（Change of Basis）：同一个向量，不同坐标</span></div>
<p>换基（Change of Basis）指的是：在不改变几何向量本身的前提下，改用另一组基（Basis）来描述它的坐标（Coordinates）。因此，换基改变的是<span style="background-color: #c0c0c0;">表示方式</span>，不是向量对象本身。直观上，可把几何向量理解为平面/空间里的一支箭头。</p>
<p>这也是为什么“换基”不能理解成“把向量变形”。真正发生变化的是参考基（Basis），因此同一向量在不同基下的坐标数值会不同，而几何对象本身保持不变。</p>
<p>为了不混淆“向量本身”和“向量的坐标表示”，可以用一个约定把它们分开：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span>：把同一个几何向量用标准基（Standard Basis）写出来的分量列向量（数值计算里最常用的表示）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathbf{c}\)</span>：把同一个几何向量用某组新基 <span displaypfx="inline-" class="mathjax-container">\(\{\mathbf{b}_i\}\)</span> 写出来的坐标向量（Coordinate Vector），也就是“在新基上要乘的系数”。</li>
</ul>
<p>把新基向量在标准基下的分量按列组成 <span displaypfx="inline-" class="mathjax-container">\(B=[\mathbf{b}_1\ \cdots\ \mathbf{b}_n]\)</span>（可称为基矩阵（Basis Matrix）），则</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}=B\mathbf{c}\]</span>
<p>读法：右边 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{c}\)</span> 是“在新基下的坐标”，左边 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 是“在标准基下的分量”。乘上 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 就把“新基坐标”换算回“标准基分量”。</p>
<p>反过来，如果你已知标准基下的分量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span>，想求新基坐标 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{c}\)</span>，就需要解线性方程组 <span displaypfx="inline-" class="mathjax-container">\(B\mathbf{c}=\mathbf{x}\)</span>。由于基向量线性无关，矩阵 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 必可逆（Invertible），因此可写成：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{c}=B^{-1}\mathbf{x}\]</span>
<p>更一般地，若旧基矩阵为 <span displaypfx="inline-" class="mathjax-container">\(B\)</span>、新基矩阵为 <span displaypfx="inline-" class="mathjax-container">\(C\)</span>，同一几何向量满足 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=B\mathbf{c}_{B}=C\mathbf{c}_{C}\)</span>，于是坐标之间的换算是：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{c}_{C}=C^{-1}B\,\mathbf{c}_{B}\]</span>
<p>矩阵 <span displaypfx="inline-" class="mathjax-container">\(P=C^{-1}B\)</span> 常被称为换基矩阵（Change-of-basis Matrix）：它把“旧基坐标”直接映射为“新基坐标”。</p>
<p>例：在 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^2\)</span> 取 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}_1=(1,0)^\top,\ \mathbf{b}_2=(1,1)^\top\)</span>。对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(2,3)^\top\)</span>，解 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=c_1\mathbf{b}_1+c_2\mathbf{b}_2\)</span> 得 <span displaypfx="inline-" class="mathjax-container">\(c_2=3,\ c_1=-1\)</span>，因此该基下坐标为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{c}=(-1,3)^\top\)</span>。同一个几何向量，在不同基下的坐标会不同。</p>
<p>把基矩阵写出来会更便于计算： <span displaypfx="inline-" class="mathjax-container">\(B=[\mathbf{b}_1\ \mathbf{b}_2]=\begin{bmatrix}1 &amp; 1\\ 0 &amp; 1\end{bmatrix}\)</span>。此时 <span displaypfx="inline-" class="mathjax-container">\(B\mathbf{c}=\begin{bmatrix}1 &amp; 1\\ 0 &amp; 1\end{bmatrix}\begin{bmatrix}-1\\ 3\end{bmatrix}=\begin{bmatrix}2\\ 3\end{bmatrix}=\mathbf{x}\)</span>；反过来，解 <span displaypfx="inline-" class="mathjax-container">\(B\mathbf{c}=\mathbf{x}\)</span> 等价于 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{c}=B^{-1}\mathbf{x}\)</span>，因此“换基”在计算上就是解一个线性方程组。</p>
<div class="blog_h3"><span class="graybg">标准基（Standard Basis）</span></div>
<p>标准基（Standard Basis）是最常用的一组基。第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个标准基向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{e}_i\)</span> 只有第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个分量为 1，其余为 0：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{e}_1=(1,0,\ldots,0)^\top,\ \mathbf{e}_2=(0,1,0,\ldots,0)^\top,\ \ldots,\ \mathbf{e}_n=(0,\ldots,0,1)^\top\]</span>
<p>例：在 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^2\)</span> 中，<span displaypfx="inline-" class="mathjax-container">\((2,3)^\top=2\mathbf{e}_1+3\mathbf{e}_2\)</span>。把 <span displaypfx="inline-" class="mathjax-container">\(\{\mathbf{e}_i\}\)</span> 作为列组成矩阵就是单位矩阵 <span displaypfx="inline-" class="mathjax-container">\(I\)</span>，因此标准基下 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=I\mathbf{x}\)</span> 对应“坐标与分量一致”。</p>
<div class="blog_h3"><span class="graybg">正交基（Orthogonal Basis）</span></div>
<p>正交基（Orthogonal Basis）是指基向量两两正交：对 <span displaypfx="inline-" class="mathjax-container">\(i\ne j\)</span> 有 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}_i^\top\mathbf{b}_j=0\)</span>。它不要求单位长度。</p>
<p>正交基的一个关键性质是：坐标可以用投影直接算出。若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=\sum_i c_i\mathbf{b}_i\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(\{\mathbf{b}_i\}\)</span> 正交，则</p>
<span displaypfx="" class="mathjax-container">\[c_i=\frac{\mathbf{x}^\top\mathbf{b}_i}{\mathbf{b}_i^\top\mathbf{b}_i}\]</span>
<p>例：取 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}_1=(1,1)^\top,\ \mathbf{b}_2=(1,-1)^\top\)</span>，它们点积为 0。对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(2,1)^\top\)</span>，有 <span displaypfx="inline-" class="mathjax-container">\(c_1=\frac{3}{2},\ c_2=\frac{1}{2}\)</span>，因此 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=\frac{3}{2}\mathbf{b}_1+\frac{1}{2}\mathbf{b}_2\)</span>。</p>
<div class="blog_h3"><span class="graybg">正交标准基（Orthonormal Basis）</span></div>
<p>正交标准基（Orthonormal Basis）要求两两正交且每个基向量单位长度： <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{u}_i\|_2=1\)</span>，并满足 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}_i^\top\mathbf{u}_j=\delta_{ij}\)</span>（克罗内克 delta（Kronecker Delta））。</p>
<p>此时坐标就是内积/投影：若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=\sum_i c_i\mathbf{u}_i\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(c_i=\mathbf{x}^\top\mathbf{u}_i\)</span>。计算上，这等价于把向量投影到各基方向。</p>
<p>例：令 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}_1=\frac{1}{\sqrt{2}}(1,1)^\top,\ \mathbf{u}_2=\frac{1}{\sqrt{2}}(1,-1)^\top\)</span>。对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(2,1)^\top\)</span>，有 <span displaypfx="inline-" class="mathjax-container">\(c_1=\frac{3}{\sqrt{2}},\ c_2=\frac{1}{\sqrt{2}}\)</span>，于是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=c_1\mathbf{u}_1+c_2\mathbf{u}_2\)</span>。</p>
<p>把 <span displaypfx="inline-" class="mathjax-container">\(\{\mathbf{u}_i\}\)</span> 按列组成矩阵 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(Q^\top Q=I\)</span>，并且坐标变换可写成 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{c}=Q^\top\mathbf{x}\)</span>、重构为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=Q\mathbf{c}\)</span>。在 PCA（Principal Component Analysis）与 SVD 中，主方向/奇异向量就是正交标准基；投影与重构只需要转置，不需要显式求逆。</p>
<div class="blog_h2"><span class="graybg">矩阵运算</span></div>
<p>矩阵运算（Matrix Operations）是机器学习（Machine Learning）中最核心的计算骨架。前向传播、反向传播和参数更新都可以表示为矩阵与向量的组合。</p>
<div class="blog_h3"><span class="graybg">加法与广播（Addition / Broadcasting）</span></div>
<p>矩阵加法按元素（Element-wise）进行：若 <span displaypfx="inline-" class="mathjax-container">\(X,B\in\mathbb{R}^{m\times n}\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\((X+B)_{ij}=X_{ij}+B_{ij}\)</span>。减法同理。</p>
<p>在深度学习框架里常见的是广播（Broadcasting）：例如对 batch 特征 <span displaypfx="inline-" class="mathjax-container">\(X\in\mathbb{R}^{B\times d}\)</span> 与偏置 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\in\mathbb{R}^{d}\)</span>，写作 <span displaypfx="inline-" class="mathjax-container">\(Y=X+\mathbf{b}\)</span> 意味着把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\)</span> 复制到每一行后再相加。这是线性层 <span displaypfx="inline-" class="mathjax-container">\(Y=XW+\mathbf{b}\)</span> 的标准形式。</p>
<div class="blog_h4"><span class="graybg">例：矩阵加法</span></div>
<span displaypfx="" class="mathjax-container">\[X=\begin{bmatrix}1 &amp; 2\\ 3 &amp; 4\end{bmatrix},\ B=\begin{bmatrix}10 &amp; 20\\ 30 &amp; 40\end{bmatrix}\Rightarrow X+B=\begin{bmatrix}11 &amp; 22\\ 33 &amp; 44\end{bmatrix}\]</span>
<div class="blog_h4"><span class="graybg">例：广播加偏置（Bias Broadcasting）</span></div>
<p>令 <span displaypfx="inline-" class="mathjax-container">\(X\in\mathbb{R}^{2\times 3}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\in\mathbb{R}^{3}\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[X=\begin{bmatrix}1 &amp; 2 &amp; 3\\ 4 &amp; 5 &amp; 6\end{bmatrix},\ \mathbf{b}=\begin{bmatrix}10 &amp; 20 &amp; 30\end{bmatrix}\]</span>
<p>广播的语义是“把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\)</span> 复制到每一行”，因此</p>
<span displaypfx="" class="mathjax-container">\[Y=X+\mathbf{b}=\begin{bmatrix}11 &amp; 22 &amp; 33\\ 14 &amp; 25 &amp; 36\end{bmatrix}\]</span>
<p>从线性代数角度，把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\)</span> 视作行向量，则广播等价于 <span displaypfx="inline-" class="mathjax-container">\(Y=X+\mathbf{1}\mathbf{b}\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{1}\in\mathbb{R}^{B\times 1}\)</span> 是全 1 列向量。</p>
<div class="blog_h3"><span class="graybg">矩阵乘法（Matrix Multiplication）</span></div>
<p>矩阵乘法（Matrix Multiplication）在神经网络里通常对应线性层（Linear Layer）：<span displaypfx="inline-" class="mathjax-container">\(Y=XW\)</span>。从“每个输出维度是一个点积”来看更容易记：</p>
<span displaypfx="" class="mathjax-container">\[y_j=\sum_{i=1}^{d_{\text{in}}} x_i W_{ij}\]</span>
<p>在批处理（Batch）场景下，常用形状约定是 <span displaypfx="inline-" class="mathjax-container">\(X\in\mathbb{R}^{B\times d_{\text{in}}}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(W\in\mathbb{R}^{d_{\text{in}}\times d_{\text{out}}}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(Y=XW\in\mathbb{R}^{B\times d_{\text{out}}}\)</span>。矩阵乘法一般不满足交换律（Non-commutativity），形状不匹配时也无法相乘。</p>
<p>从几何（Geometry）角度看，矩阵定义了一个线性变换（Linear Transformation）：它把空间中的点/向量整体“变形”（旋转、缩放、剪切、投影等）。一种实用记法是看基向量（Basis Vectors）如何被映射：如果用列向量约定，矩阵的每一列就是某个基向量变换后的像。</p>
<p>例（二维）：标准基向量（Standard Basis Vectors）为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{e}_1=(1,0)^\top\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{e}_2=(0,1)^\top\)</span>。对</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}a &amp; c\\ b &amp; d\end{bmatrix}\]</span>
<p>有</p>
<span displaypfx="" class="mathjax-container">\[A\mathbf{e}_1=\begin{bmatrix}a\\ b\end{bmatrix},\quad A\mathbf{e}_2=\begin{bmatrix}c\\ d\end{bmatrix}\]</span>
<p>因此 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的第 1/2 列分别是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{e}_1,\mathbf{e}_2\)</span> 的像。对任意 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=x_1\mathbf{e}_1+x_2\mathbf{e}_2\)</span>，根据线性变换的线性性（Linearity）<span displaypfx="inline-" class="mathjax-container">\(A(\alpha\mathbf{u}+\beta\mathbf{v})=\alpha A\mathbf{u}+\beta A\mathbf{v}\)</span>，可得</p>
<span displaypfx="" class="mathjax-container">\[A\mathbf{x}=x_1A\mathbf{e}_1+x_2A\mathbf{e}_2=x_1\begin{bmatrix}a\\ b\end{bmatrix}+x_2\begin{bmatrix}c\\ d\end{bmatrix}\]</span>
<p>数值例子：取</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}2 &amp; 1\\ 0 &amp; 1\end{bmatrix}\]</span>
<p>则 <span displaypfx="inline-" class="mathjax-container">\(A\mathbf{e}_1=(2,0)^\top\)</span>、<span displaypfx="inline-" class="mathjax-container">\(A\mathbf{e}_2=(1,1)^\top\)</span>。若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(3,4)^\top=3\mathbf{e}_1+4\mathbf{e}_2\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[A\mathbf{x}=3A\mathbf{e}_1+4A\mathbf{e}_2=3\begin{bmatrix}2\\ 0\end{bmatrix}+4\begin{bmatrix}1\\ 1\end{bmatrix}=\begin{bmatrix}10\\ 4\end{bmatrix}\]</span>
<p>这就是“看列向量理解变换”的核心：先画出两条基向量被送到哪里，整个网格会按相同线性组合随之平移/剪切/旋转/缩放。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/matrix-trans.jpg"><img class="alignnone size-full wp-image-40629" src="https://blog.gmem.cc/wp-content/uploads/2026/03/matrix-trans.jpg" alt="matrix-trans" width="100%" /></a></p>
<p>这对应两种等价视角：</p>
<ul>
<li>变换向量（Active View）：固定坐标轴，让向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 变成 <span displaypfx="inline-" class="mathjax-container">\(A\mathbf{x}\)</span>。</li>
<li>变换坐标轴（Passive View）：固定几何点，把坐标系按 <span displaypfx="inline-" class="mathjax-container">\(A^{-1}\)</span> 变换；同一个点在新坐标系下的坐标会变化。</li>
</ul>
<p>两种视角描述的是同一个线性映射，只是“变的对象”不同。在做特征变换/白化（Whitening）/坐标变换推导时，这个区分能避免符号混乱。</p>
<p>矩阵乘法只有在内维度相等时才有定义：<span displaypfx="inline-" class="mathjax-container">\((m\times n)(n\times p)=(m\times p)\)</span>。把向量视为列向量（<span displaypfx="inline-" class="mathjax-container">\(m\times 1\)</span>）或行向量（<span displaypfx="inline-" class="mathjax-container">\(1\times m\)</span>）后，外积与点积也都可以统一为矩阵乘法的特例。</p>
<ol>
<li>
<p>一般矩阵乘法：<span displaypfx="inline-" class="mathjax-container">\((m\times n)(n\times p)=(m\times p)\)</span>。</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}1 &amp; 2 &amp; 3\\ 4 &amp; 5 &amp; 6\end{bmatrix}\in\mathbb{R}^{2\times 3},\quad B=\begin{bmatrix}7 &amp; 8\\ 9 &amp; 10\\ 11 &amp; 12\end{bmatrix}\in\mathbb{R}^{3\times 2}\]</span>
<span displaypfx="" class="mathjax-container">\[AB=\begin{bmatrix}58 &amp; 64\\ 139 &amp; 154\end{bmatrix}\in\mathbb{R}^{2\times 2}\]</span>
</li>
<li>
<p>外积（Outer Product）：<span displaypfx="inline-" class="mathjax-container">\((m\times 1)(1\times n)=(m\times n)\)</span>。</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{u}=\begin{bmatrix}1\\ 2\\ 3\end{bmatrix}\in\mathbb{R}^{3\times 1},\quad \mathbf{v}=\begin{bmatrix}4 &amp; 5\end{bmatrix}\in\mathbb{R}^{1\times 2}\]</span>
<span displaypfx="" class="mathjax-container">\[\mathbf{u}\mathbf{v}=\begin{bmatrix}4 &amp; 5\\ 8 &amp; 10\\ 12 &amp; 15\end{bmatrix}\in\mathbb{R}^{3\times 2}\]</span>
<p>这是一个秩一（Rank-1）矩阵：它把“列向量 × 行向量”变成矩阵；在低秩近似、注意力权重构造与二阶统计量里都很常见。</p>
</li>
<li>
<p>点积（Dot Product）：<span displaypfx="inline-" class="mathjax-container">\((1\times m)(m\times 1)=(1\times 1)\)</span>。</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{r}=\begin{bmatrix}1 &amp; 2 &amp; 3\end{bmatrix}\in\mathbb{R}^{1\times 3},\quad \mathbf{c}=\begin{bmatrix}4\\ 5\\ 6\end{bmatrix}\in\mathbb{R}^{3\times 1}\]</span>
<span displaypfx="" class="mathjax-container">\[\mathbf{r}\mathbf{c}=\begin{bmatrix}32\end{bmatrix}\in\mathbb{R}^{1\times 1}\equiv 32\]</span>
<p>结果是标量 <span displaypfx="inline-" class="mathjax-container">\(32\)</span>，等价于点积： <span displaypfx="inline-" class="mathjax-container">\(\mathbf{r}\mathbf{c}=\sum_{i=1}^{m} r_i c_i\)</span>。在实现里常写成 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top\mathbf{y}\)</span>。</p>
</li>
</ol>
<div class="blog_h3"><span class="graybg">仿射变换与仿射子空间</span></div>
<p>仿射（Affine）是线性（Linear）的一个自然扩展。线性变换要求 <span displaypfx="inline-" class="mathjax-container">\(f(\mathbf{x})=A\mathbf{x}\)</span>，必须把原点 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{0}\)</span> 映到原点；仿射变换则允许再加一个平移项：</p>
<span displaypfx="" class="mathjax-container">\[f(\mathbf{x})=A\mathbf{x}+\mathbf{b}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 是线性部分，负责旋转、缩放、剪切、投影等形变； <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\)</span> 是平移项（Translation / Bias），负责把整个空间整体推走一个位移。因此，仿射变换可以理解为“先做线性变换，再做平移”。</p>
<p>几何上，线性变换像在原点固定不动的前提下拉伸或扭转整张网格；仿射变换则像先把网格按 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 变形，再把整张网格连同原点一起平移到别的位置。两者最核心的区别是：<span style="background-color: #c0c0c0;">线性变换保原点，仿射变换不一定保原点</span>。</p>
<p>最简单的一维例子是 <span displaypfx="inline-" class="mathjax-container">\(f(x)=2x\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(g(x)=2x+3\)</span>。前者是线性的，因为 <span displaypfx="inline-" class="mathjax-container">\(f(0)=0\)</span>；后者是仿射的，因为它先把数轴按 2 倍拉伸，再整体平移 3 个单位，所以 <span displaypfx="inline-" class="mathjax-container">\(g(0)=3\)</span>，不再经过原点。二维里也是同样： <span displaypfx="inline-" class="mathjax-container">\(f(\mathbf{x})=\mathbf{x}\)</span> 是恒等线性变换，而 <span displaypfx="inline-" class="mathjax-container">\(g(\mathbf{x})=\mathbf{x}+\begin{bmatrix}1\\2\end{bmatrix}\)</span> 会把整张平面网格整体向右平移 1、向上平移 2。</p>
<p>仿射函数（Affine Function）在优化与机器学习里极其常见。一维情形的 <span displaypfx="inline-" class="mathjax-container">\(f(x)=ax+b\)</span> 就是最简单的仿射函数；高维里则写成 <span displaypfx="inline-" class="mathjax-container">\(f(\mathbf{x})=\mathbf{w}^\top\mathbf{x}+b\)</span>。因此，神经网络里通常口头说“线性层（Linear Layer）”，但如果带偏置 <span displaypfx="inline-" class="mathjax-container">\(b\)</span>，更严格的数学名称其实是仿射层（Affine Layer）：</p>
<span displaypfx="" class="mathjax-container">\[Y=XW+\mathbf{1}b^\top\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 是输入矩阵， <span displaypfx="inline-" class="mathjax-container">\(W\)</span> 是线性变换矩阵， <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 是偏置向量， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{1}\)</span> 是把偏置广播到每个样本上的全 1 列向量。若没有偏置，才是严格意义上的线性映射。</p>
<p>仿射还有一个重要性质：它保持仿射组合（Affine Combination）。若 <span displaypfx="inline-" class="mathjax-container">\(\sum_i \alpha_i=1\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[f\!\left(\sum_i \alpha_i \mathbf{x}_i\right)=\sum_i \alpha_i f(\mathbf{x}_i)\]</span>
<p>这意味着直线、平面、平行关系和凸组合结构在仿射变换下会被保留；因此很多几何对象在经过仿射变换后，仍然保持“像线还是线，像平面还是平面”的基本类型，但位置和方向可能改变。</p>
<p>仿射子空间（Affine Subspace）则是“线性子空间整体平移后得到的集合”。若 <span displaypfx="inline-" class="mathjax-container">\(U\)</span> 是一个线性子空间， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_0\)</span> 是空间中的某个固定点，则</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{A}=\mathbf{x}_0+U=\{\mathbf{x}_0+\mathbf{u}:\mathbf{u}\in U\}\]</span>
<p>就是一个仿射子空间。它和线性子空间的差别也在于是否经过原点：线性子空间必须包含原点，仿射子空间不必。例如二维中的直线 <span displaypfx="inline-" class="mathjax-container">\(x+y=1\)</span> 就是一个仿射子空间；它的方向部分与线性子空间 <span displaypfx="inline-" class="mathjax-container">\(x+y=0\)</span> 相同，但整条直线被平移开了，因此不经过原点。</p>
<p>这正是为什么超平面 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}^\top\mathbf{x}+b=0\)</span> 被称为仿射子空间而不是线性子空间：当 <span displaypfx="inline-" class="mathjax-container">\(b\ne 0\)</span> 时，它通常不经过原点，只是由某个线性超平面整体平移而来。优化里的等式约束 <span displaypfx="inline-" class="mathjax-container">\(h_j(x)=0\)</span> 若要求 <span displaypfx="inline-" class="mathjax-container">\(h_j\)</span> 是仿射函数，含义也正是“约束边界仍然保持平直结构，但允许存在偏置和平移”。</p>
<p>更进一步，双仿射（Biaffine）也是同一族概念的延伸。它通常在两个向量之间做一个双线性项 <span displaypfx="inline-" class="mathjax-container">\(h_i^\top U h_j\)</span>，再加上线性项和偏置项，因此既包含“两个变量之间的乘性交互”，也包含仿射偏置修正。理解了仿射，就能把“线性 / 仿射 / 双线性 / 双仿射”看成一条逐步加复杂度的函数族谱。</p>
<div class="blog_h4"><span class="graybg">线性到双仿射的公式族谱</span></div>
<p>把输出视为一个标量时，这条族谱可以写成一组并列的标准形式：</p>
<span displaypfx="" class="mathjax-container">\[f_{\mathrm{linear}}(\mathbf{x})=\mathbf{w}^\top \mathbf{x}\]</span>
<span displaypfx="" class="mathjax-container">\[f_{\mathrm{affine}}(\mathbf{x})=\mathbf{w}^\top \mathbf{x}+b\]</span>
<span displaypfx="" class="mathjax-container">\[s_{\mathrm{bilinear}}(\mathbf{x},\mathbf{z})=\mathbf{x}^\top U\mathbf{z}\]</span>
<span displaypfx="" class="mathjax-container">\[s_{\mathrm{biaffine}}(\mathbf{x},\mathbf{z})=\mathbf{x}^\top U\mathbf{z}+\mathbf{a}^\top\mathbf{x}+\mathbf{c}^\top\mathbf{z}+b\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\in\mathbb{R}^{m},\mathbf{z}\in\mathbb{R}^{n}\)</span> 是两个输入向量， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},\mathbf{a}\in\mathbb{R}^{m}\)</span>， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{c}\in\mathbb{R}^{n}\)</span>， <span displaypfx="inline-" class="mathjax-container">\(U\in\mathbb{R}^{m\times n}\)</span>， <span displaypfx="inline-" class="mathjax-container">\(b\in\mathbb{R}\)</span>。这里的 <span displaypfx="inline-" class="mathjax-container">\(U\)</span> 是双线性项的参数矩阵（Parameter Matrix）：它不作用于单个向量本身，而是给“ <span displaypfx="inline-" class="mathjax-container">\(x_p\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(z_q\)</span> 同时出现”这类二元交互分配权重。把式子展开后可写成 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top U\mathbf{z}=\sum_{p=1}^{m}\sum_{q=1}^{n}x_p\,U_{pq}\,z_q\)</span>，因此 <span displaypfx="inline-" class="mathjax-container">\(U_{pq}\)</span> 直接控制第 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 个 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 特征与第 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 个 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{z}\)</span> 特征之间的交互强度。 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a}^\top\mathbf{x}\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{c}^\top\mathbf{z}\)</span> 是单边线性项，分别描述各自独立的角色偏好； <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 是全局基线偏置。</p>
<p>把这个定义写成一个最小的 2×3 例子，会更容易直接看出“连接强度”来自哪里。令</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}=\begin{bmatrix}x_1\\x_2\end{bmatrix},\quad \mathbf{z}=\begin{bmatrix}z_1\\z_2\\z_3\end{bmatrix},\quad U=\begin{bmatrix}u_{11} &amp; u_{12} &amp; u_{13}\\u_{21} &amp; u_{22} &amp; u_{23}\end{bmatrix}\]</span>
<p>则双线性项为</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}^\top U\mathbf{z}=\begin{bmatrix}x_1 &amp; x_2\end{bmatrix}\begin{bmatrix}u_{11} &amp; u_{12} &amp; u_{13}\\u_{21} &amp; u_{22} &amp; u_{23}\end{bmatrix}\begin{bmatrix}z_1\\z_2\\z_3\end{bmatrix}\]</span>
<span displaypfx="" class="mathjax-container">\[=x_1u_{11}z_1+x_1u_{12}z_2+x_1u_{13}z_3+x_2u_{21}z_1+x_2u_{22}z_2+x_2u_{23}z_3\]</span>
<p>这一步把抽象的矩阵乘法拆成了六条显式连接： <span displaypfx="inline-" class="mathjax-container">\(u_{11}\)</span> 控制 <span displaypfx="inline-" class="mathjax-container">\(x_1\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(z_1\)</span> 的连接强度， <span displaypfx="inline-" class="mathjax-container">\(u_{23}\)</span> 控制 <span displaypfx="inline-" class="mathjax-container">\(x_2\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(z_3\)</span> 的连接强度。若某个 <span displaypfx="inline-" class="mathjax-container">\(u_{pq}\)</span> 很大且为正，只要对应的 <span displaypfx="inline-" class="mathjax-container">\(x_p\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(z_q\)</span> 同时取大值，这一对特征就会显著抬高总分；若某个 <span displaypfx="inline-" class="mathjax-container">\(u_{pq}\)</span> 为负，则说明这对特征的共同出现会压低分数。</p>
<p>在同一个例子里，双仿射只是在双线性项之外再加上单边项与偏置：</p>
<span displaypfx="" class="mathjax-container">\[s_{\mathrm{biaffine}}(\mathbf{x},\mathbf{z})=\mathbf{x}^\top U\mathbf{z}+\mathbf{a}^\top\mathbf{x}+\mathbf{c}^\top\mathbf{z}+b\]</span>
<span displaypfx="" class="mathjax-container">\[=\mathbf{x}^\top U\mathbf{z}+(a_1x_1+a_2x_2)+(c_1z_1+c_2z_2+c_3z_3)+b\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(a_1,a_2\)</span> 描述 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 一侧各特征自身的偏好， <span displaypfx="inline-" class="mathjax-container">\(c_1,c_2,c_3\)</span> 描述 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{z}\)</span> 一侧各特征自身的偏好， <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 给出无条件基线分数。于是，双线性回答的是“这两组特征配在一起有多合适”，双仿射回答的则是“它们配在一起有多合适，并且各自本身是否已经带有倾向”。</p>
<p>若输出不是一个标量分数，而是 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个关系类别或标签分数，则通常不只使用一个 <span displaypfx="inline-" class="mathjax-container">\(U\)</span>，而是为每个类别准备一张交互矩阵 <span displaypfx="inline-" class="mathjax-container">\(U^{(k)}\)</span>，或等价地把它们堆成三阶张量 <span displaypfx="inline-" class="mathjax-container">\(U\in\mathbb{R}^{K\times m\times n}\)</span>。这时每个类别都拥有自己的一套“特征两两交互”权重。</p>
<p>从函数结构看，线性与仿射的区别在于是否含偏置；双线性与双仿射的区别同样在于是否在交互项之外再加入单边项与偏置。固定 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{z}\)</span> 时， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top U\mathbf{z}\)</span> 对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 是线性的， <span displaypfx="inline-" class="mathjax-container">\(s_{\text{biaffine}}(\mathbf{x},\mathbf{z})\)</span> 对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 则是仿射的；固定 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 时，对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{z}\)</span> 也是同样的性质。这正是 “biaffine” 这个名字的数学含义。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/affine-biaffine-family.png"><img class="alignnone size-full" src="https://blog.gmem.cc/wp-content/uploads/2026/03/affine-biaffine-family.png" alt="affine-biaffine-family" width="1920" height="1203" /></a></p>
<p>图中上排用一维切片显示“是否经过原点”这一关键差异： <span displaypfx="inline-" class="mathjax-container">\(f(x)=ax\)</span> 必然经过原点，而 <span displaypfx="inline-" class="mathjax-container">\(f(x)=ax+b\)</span> 由于加入偏置项，整条直线沿输出轴发生平移。下排保持与公式族谱一致，仍写成 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top U\mathbf{z}\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top U\mathbf{z}+\mathbf{a}^\top\mathbf{x}+\mathbf{c}^\top\mathbf{z}+b\)</span>；图像本身展示的是把高维向量关系压到二维后的一个切片。纯双线性项的零等值线体现为对称的交互边界；加入单边项与偏置后，零等值线整体偏移，分数面出现倾斜与平移，表示模型不仅关心“是否匹配”，还关心两个对象各自单独的倾向。</p>
<p>在依存句法（Dependency Parsing）、关系抽取（Relation Extraction）和成对匹配（Pairwise Matching）任务中，这个结构尤其有用。纯双线性项只能表达“组合后是否相容”，双仿射则进一步允许模型学习“某个对象本身就更像 head / dependent”或“某个实体本身就更像某类关系的一端”。因此，双仿射通常既比纯仿射更能表达交互，又比纯双线性更稳定。</p>
<div class="blog_h3"><span class="graybg">转置（Transpose）</span></div>
<p>转置（Transpose）把行列互换：对 <span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{m\times n}\)</span>，其转置 <span displaypfx="inline-" class="mathjax-container">\(A^\top\in\mathbb{R}^{n\times m}\)</span> 满足 <span displaypfx="inline-" class="mathjax-container">\((A^\top)_{ij}=A_{ji}\)</span>。</p>
<p>它常用于对齐乘法形状、把点积写成矩阵乘法，以及在推导中“移动”矩阵：例如 <span displaypfx="inline-" class="mathjax-container">\((AB)^\top=B^\top A^\top\)</span>。Transformer 注意力中的 <span displaypfx="inline-" class="mathjax-container">\(QK^\top\)</span> 就是典型的“先转置再相乘”。</p>
<div class="blog_h4"><span class="graybg">例：行列互换与形状变化</span></div>
<p>令</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}1 &amp; 2 &amp; 3\\ 4 &amp; 5 &amp; 6\end{bmatrix}\in\mathbb{R}^{2\times 3}\]</span>
<p>则</p>
<span displaypfx="" class="mathjax-container">\[A^\top=\begin{bmatrix}1 &amp; 4\\ 2 &amp; 5\\ 3 &amp; 6\end{bmatrix}\in\mathbb{R}^{3\times 2}\]</span>
<p>可以直接看到：原矩阵的第 1 行变成转置后的第 1 列；因此 <span displaypfx="inline-" class="mathjax-container">\((m\times n)^\top=(n\times m)\)</span>。</p>
<div class="blog_h3"><span class="graybg">Hadamard 乘积（Hadamard Product）</span></div>
<p>Hadamard 乘积（Hadamard Product）是逐元素（Element-wise）相乘：若 <span displaypfx="inline-" class="mathjax-container">\(X,M\)</span> 形状相同，则 <span displaypfx="inline-" class="mathjax-container">\((X\odot M)_{ij}=X_{ij}M_{ij}\)</span>。</p>
<p>典型用途是掩码（Masking）与门控（Gating）。例：令 <span displaypfx="inline-" class="mathjax-container">\(M\in\{0,1\}^{B\times d}\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(X\odot M\)</span> 会把被屏蔽的特征位置直接置零；也可以用 <span displaypfx="inline-" class="mathjax-container">\(M\in[0,1]^{B\times d}\)</span> 做连续缩放。</p>
<div class="blog_h3"><span class="graybg">矩阵分解（Factorization / Decomposition）</span></div>
<p>矩阵分解（Decomposition）把矩阵写成更“易处理”的结构乘积，用于求解、降维与稳定计算。常见形式包括：</p>
<ul>
<li>SVD： <span displaypfx="inline-" class="mathjax-container">\(A=U\Sigma V^\top\)</span>，用于 PCA（Principal Component Analysis）、低秩近似与数值稳健求解。</li>
<li>QR： <span displaypfx="inline-" class="mathjax-container">\(A=QR\)</span>（<span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 正交、<span displaypfx="inline-" class="mathjax-container">\(R\)</span> 上三角），常用于最小二乘与正交化。</li>
<li>Cholesky：对对称正定（SPD）矩阵 <span displaypfx="inline-" class="mathjax-container">\(A\)</span>，有 <span displaypfx="inline-" class="mathjax-container">\(A=LL^\top\)</span>，常用于高斯模型与二次优化中的快速求解。</li>
</ul>
<div class="blog_h3"><span class="graybg">迹（Trace）</span></div>
<p>矩阵的迹（Trace）定义为对角线元素之和：对方阵 <span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{n\times n}\)</span>，</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{tr}(A)=\sum_{i=1}^{n} A_{ii}\]</span>
<p>迹在推导里常用的性质是循环不变性（Cyclic Property）：<span displaypfx="inline-" class="mathjax-container">\(\mathrm{tr}(AB)=\mathrm{tr}(BA)\)</span>（形状匹配时）。一个高频等式是 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{tr}(A^\top A)=\|A\|_F^2\)</span>，它把“平方和”写成迹，便于做矩阵微分与正则化推导。</p>
<div class="blog_h3"><span class="graybg">范数（Matrix Norms）</span></div>
<p>范数（Norm）刻画矩阵的“大小”。最常用的是 Frobenius 范数：</p>
<span displaypfx="" class="mathjax-container">\[\|A\|_F=\sqrt{\sum_{i,j}A_{ij}^2}\]</span>
<p>它等价于把矩阵按元素展平后的 <span displaypfx="inline-" class="mathjax-container">\(\ell_2\)</span> 范数，常用于权重衰减（Weight Decay）/L2 正则。另一个常见的是谱范数（Spectral Norm）<span displaypfx="inline-" class="mathjax-container">\(\|A\|_2\)</span>（最大奇异值），用于控制 Lipschitz 常数与训练稳定性（如 spectral normalization）。</p>
<div class="blog_h3"><span class="graybg">外积与秩一更新（Outer Product / Rank-1 Update）</span></div>
<p>外积（Outer Product）把两个向量映射为矩阵：对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}\in\mathbb{R}^{m}\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\in\mathbb{R}^{n}\)</span>，</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{u}\mathbf{v}^\top\in\mathbb{R}^{m\times n}\]</span>
<p>它是一个秩一（Rank-1）矩阵。外积在统计与学习中常用于构造二阶量：例如样本协方差的无偏估计可写成中心化向量的外积平均 <span displaypfx="inline-" class="mathjax-container">\(\Sigma\approx \frac{1}{N}\sum_{k=1}^{N}(\mathbf{x}_k-\bar{\mathbf{x}})(\mathbf{x}_k-\bar{\mathbf{x}})^\top\)</span>。</p>
<p>例：令 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}=\begin{bmatrix}1\\ 2\\ 3\end{bmatrix}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}=\begin{bmatrix}4\\ 5\end{bmatrix}\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{u}\mathbf{v}^\top=\begin{bmatrix}4 &amp; 5\\ 8 &amp; 10\\ 12 &amp; 15\end{bmatrix}\]</span>
<p>直观上，外积得到的矩阵每一列都是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}\)</span> 的缩放：第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 列等于 <span displaypfx="inline-" class="mathjax-container">\(v_j\mathbf{u}\)</span>。因此所有列共线，矩阵的秩最多为 1（除非 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\)</span> 为零向量）。</p>
<p>把外积视为线性算子更直接：对任意 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\in\mathbb{R}^{n}\)</span>，有 <span displaypfx="inline-" class="mathjax-container">\((\mathbf{u}\mathbf{v}^\top)\mathbf{x}=\mathbf{u}(\mathbf{v}^\top\mathbf{x})\)</span>。这表示先沿 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\)</span> 做一次投影/打分得到标量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}^\top\mathbf{x}\)</span>，再沿 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}\)</span> 方向输出。例：取 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=\begin{bmatrix}1\\ 1\end{bmatrix}\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}^\top\mathbf{x}=9\)</span>，从而 <span displaypfx="inline-" class="mathjax-container">\((\mathbf{u}\mathbf{v}^\top)\mathbf{x}=9\mathbf{u}=\begin{bmatrix}9\\ 18\\ 27\end{bmatrix}\)</span>。</p>
<p>秩一更新（Rank-1 Update）则是把矩阵写成 <span displaypfx="inline-" class="mathjax-container">\(A\leftarrow A+\mathbf{u}\mathbf{v}^\top\)</span>：只引入一个方向上的低秩结构，常用于用较低代价注入统计量/二阶近似，或在保持主结构的前提下做小幅调整。</p>
<div class="blog_h2"><span class="graybg">行列式</span></div>
<p>行列式（Determinant）把一个方阵 <span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{n\times n}\)</span> 映射为标量 <span displaypfx="inline-" class="mathjax-container">\(\det(A)\)</span>。几何上，它是线性变换对体积的缩放因子（Volume Scaling Factor）：绝对值表示缩放倍数，符号表示是否翻转取向（Orientation Flip）。</p>
<p>二维情形最直观：若</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}a &amp; b\\ c &amp; d\end{bmatrix}\]</span>
<p>则</p>
<span displaypfx="" class="mathjax-container">\[\det(A)=ad-bc\]</span>
<p>例： <span displaypfx="inline-" class="mathjax-container">\(A=\begin{bmatrix}2 &amp; 1\\ 0 &amp; 3\end{bmatrix}\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\det(A)=6\)</span>：面积被放大 6 倍。</p>
<p>关键结论：<span style="background-color: #c0c0c0;">方阵可逆（Invertible）当且仅当行列式非零</span>。当 <span displaypfx="inline-" class="mathjax-container">\(\det(A)=0\)</span> 时，变换会把体积压扁到低维（丢失信息），对应列向量线性相关（Linearly Dependent）。</p>
<p>常用性质：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\det(AB)=\det(A)\det(B)\)</span></li>
<li><span displaypfx="inline-" class="mathjax-container">\(\det(A^\top)=\det(A)\)</span></li>
<li><span displaypfx="inline-" class="mathjax-container">\(\det(I)=1\)</span></li>
</ul>
<p>若把特征值（Eigenvalues）记作 <span displaypfx="inline-" class="mathjax-container">\(\{\lambda_i\}_{i=1}^n\)</span>（按代数重数计），则 <span displaypfx="inline-" class="mathjax-container">\(\det(A)=\prod_i \lambda_i\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathrm{tr}(A)=\sum_i \lambda_i\)</span>。</p>
<div class="blog_h2"><span class="graybg">矩阵的秩</span></div>
<p>矩阵的秩（Rank）刻画“列（或行）里最多有多少个线性无关（Linearly Independent）的方向”。对 <span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{m\times n}\)</span>，秩定义为列空间（Column Space）的维数：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{rank}(A)=\dim(\mathrm{col}(A))\]</span>
<p>这一定义的直接含义是：若 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{rank}(A)=r\)</span>，那么 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的所有列向量都只能张成一个 <span displaypfx="inline-" class="mathjax-container">\(r\)</span> 维子空间，超出这个子空间的方向它完全无法表达。反过来，若某一列可以由其他列线性组合得到，它就不提供新的维度，因此不会增加秩。</p>
<p>它也等于行空间（Row Space）的维数（行秩=列秩）。把 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 看作线性映射 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^n\to\mathbb{R}^m\)</span>，秩就是输出子空间的维度：最多能输出多少个自由方向。</p>
<p>满秩（Full Rank）通常指 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{rank}(A)=\min(m,n)\)</span>。对方阵 <span displaypfx="inline-" class="mathjax-container">\(n\times n\)</span> 而言，满秩等价于可逆（也等价于 <span displaypfx="inline-" class="mathjax-container">\(\det(A)\ne 0\)</span>）。</p>
<p>线性方程组（Linear System）<span displaypfx="inline-" class="mathjax-container">\(A\mathbf{x}=\mathbf{b}\)</span> 的解与秩直接相关：设增广矩阵为 <span displaypfx="inline-" class="mathjax-container">\([A\mid \mathbf{b}]\)</span>，则</p>
<ul>
<li>若 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{rank}(A)\ne \mathrm{rank}([A\mid \mathbf{b}])\)</span>，无解。</li>
<li>若 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{rank}(A)=\mathrm{rank}([A\mid \mathbf{b}])=n\)</span>（未知数个数），唯一解。</li>
<li>若 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{rank}(A)=\mathrm{rank}([A\mid \mathbf{b}])&lt;n\)</span>，无穷多解（存在自由变量）。</li>
</ul>
<p>与 SVD 的关系非常实用：<span style="background-color: #c0c0c0;">秩等于非零奇异值（Singular Values）的个数</span>，因此在数值计算里常用“奇异值是否接近 0”判断有效秩（Numerical Rank）。</p>
<div class="blog_h2"><span class="graybg">二次型（Quadratic Form）</span></div>
<p>在高中里，一元二次函数常写成 <span displaypfx="inline-" class="mathjax-container">\(ax^2+bx+c\)</span>。把“二次”推广到多元，并且只保留二次项（没有一次项与常数项），就得到二次型（Quadratic Form）。二维里最常见的形式是：</p>
<span displaypfx="" class="mathjax-container">\[q(x,y)=ax^2+bxy+cy^2\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(bxy\)</span> 是交叉项（Cross Term）：它把不同变量“耦合在一起”。在解析几何（Analytic Geometry）里，交叉项常对应二次曲线（Conic Section）的主轴（Principal Axes）不与坐标轴对齐（图形呈旋转/倾斜）。</p>
<p>若再把一次项与常数项加回来，就得到更一般的二次多项式（Quadratic Polynomial）<span displaypfx="inline-" class="mathjax-container">\(ax^2+bxy+cy^2+dx+ey+f\)</span>。这时：交叉项 <span displaypfx="inline-" class="mathjax-container">\(bxy\)</span> 主要反映主轴旋转；一次项 <span displaypfx="inline-" class="mathjax-container">\(dx+ey\)</span> 往往表示图形的中心/顶点从原点平移出去；常数项 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 则改变整体基准值，进而影响图形的截距、大小以及是否与 <span displaypfx="inline-" class="mathjax-container">\(q(x,y)=0\)</span> 相交。只有把一次项和常数项都去掉时，我们讨论的才是纯粹的二次型。</p>
<p>下面两幅图都基于<span style="background-color: #c0c0c0;">等值线（Level Set）</span>生成：先固定常数 <span displaypfx="inline-" class="mathjax-container">\(k\)</span>，在平面上求解 <span displaypfx="inline-" class="mathjax-container">\(q(x,y)=k\)</span> 得到等值线，再把不同 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 的结果叠加，并与 <span displaypfx="inline-" class="mathjax-container">\(z=q(x,y)\)</span> 的三维曲面对应显示。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/quadratic-forms.png"><img class="alignnone size-full wp-image-40671" src="https://blog.gmem.cc/wp-content/uploads/2026/03/quadratic-forms.png" alt="quadratic-forms" width="100%" /></a></p>
<p>第一幅展示无交叉项（<span displaypfx="inline-" class="mathjax-container">\(b=0\)</span>）的典型形态：等值线与坐标轴对齐，曲面主轴方向也与坐标轴一致。</p>
<p>&nbsp;</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/quadratic-forms-cross.png"><img class="alignnone size-full wp-image-40677" src="https://blog.gmem.cc/wp-content/uploads/2026/03/quadratic-forms-cross.png" alt="quadratic-forms-cross" width="100%" /></a></p>
<p>第二幅展示含交叉项（<span displaypfx="inline-" class="mathjax-container">\(b\ne 0\)</span>）的情形：等值线整体发生旋转/倾斜，三维曲面看起来更“不规则”。</p>
<p>在线性代数里，用矩阵（Matrix）把系数组织起来更方便。令 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(x_1,\ldots,x_n)^\top\)</span>、<span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{n\times n}\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[q(\mathbf{x})=\mathbf{x}^\top A\mathbf{x}=\sum_{i=1}^{n}\sum_{j=1}^{n}A_{ij}x_i x_j\]</span>
<p>上面这条等式不是“记号游戏”，而是把矩阵乘法按分量（Component）展开后的结果。把它分两步看最清楚：</p>
<ol>
<li>先做一次矩阵-向量乘法：令 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}=A\mathbf{x}\)</span>，则第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个分量是</li>
</ol>
<span displaypfx="" class="mathjax-container">\[y_i=(A\mathbf{x})_i=\sum_{j=1}^{n}A_{ij}x_j\]</span>
<ol start="2">
<li>再做一次点积： <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top\mathbf{y}=\sum_{i=1}^{n}x_i y_i\)</span>。把第 1 步的 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 代入，就得到</li>
</ol>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}^\top A\mathbf{x}=\mathbf{x}^\top(A\mathbf{x})=\sum_{i=1}^{n}x_i\left(\sum_{j=1}^{n}A_{ij}x_j\right)=\sum_{i=1}^{n}\sum_{j=1}^{n}A_{ij}x_i x_j\]</span>
<p>这就是“双重求和（Double Summation）”的含义：每一个矩阵元素 <span displaypfx="inline-" class="mathjax-container">\(A_{ij}\)</span> 都在给二次项 <span displaypfx="inline-" class="mathjax-container">\(x_i x_j\)</span> 分配一个权重；当 <span displaypfx="inline-" class="mathjax-container">\(i=j\)</span> 时就是平方项 <span displaypfx="inline-" class="mathjax-container">\(x_i^2\)</span>，当 <span displaypfx="inline-" class="mathjax-container">\(i\ne j\)</span> 时就是交叉项 <span displaypfx="inline-" class="mathjax-container">\(x_i x_j\)</span>。</p>
<p>二维情形最直观：若希望展开后得到 <span displaypfx="inline-" class="mathjax-container">\(ax^2+bxy+cy^2\)</span>，可以取</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}a &amp; \frac{b}{2}\\ \frac{b}{2} &amp; c\end{bmatrix}\]</span>
<p>这里把向量写成 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(x,y)^\top\)</span>，按矩阵乘法展开一遍：</p>
<span displaypfx="" class="mathjax-container">\[A\mathbf{x}=\begin{bmatrix}a &amp; \frac{b}{2}\\ \frac{b}{2} &amp; c\end{bmatrix}\begin{bmatrix}x\\ y\end{bmatrix}=\begin{bmatrix}ax+\frac{b}{2}y\\ \frac{b}{2}x+cy\end{bmatrix}\]</span>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}^\top(A\mathbf{x})=\begin{bmatrix}x &amp; y\end{bmatrix}\begin{bmatrix}ax+\frac{b}{2}y\\ \frac{b}{2}x+cy\end{bmatrix}=ax^2+\frac{b}{2}xy+\frac{b}{2}yx+cy^2=ax^2+bxy+cy^2\]</span>
<p>可以看到：交叉项 <span displaypfx="inline-" class="mathjax-container">\(xy\)</span> 的系数 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 实际来自两处对称位置 <span displaypfx="inline-" class="mathjax-container">\(A_{12}\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(A_{21}\)</span> 的“合力”（各贡献一半）。这也会自然导向下一节的结论：二次型只依赖矩阵的对称部分。</p>
<p>例：取 <span displaypfx="inline-" class="mathjax-container">\(q(x,y)=5x^2-4xy+5y^2\)</span>，对应 <span displaypfx="inline-" class="mathjax-container">\(A=\begin{bmatrix}5 &amp; -2\\ -2 &amp; 5\end{bmatrix}\)</span>。这一类表达式不仅在几何里出现（椭圆（Ellipse）/双曲线（Hyperbola）），在优化与统计里也高频出现（曲率（Curvature）、距离度量（Distance Metric））。</p>
<div class="blog_h3"><span class="graybg">对称化（Symmetrization）</span></div>
<p>对任意方阵 <span displaypfx="inline-" class="mathjax-container">\(A\)</span>，二次型只依赖其对称部分：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}^\top A\mathbf{x}=\mathbf{x}^\top\left(\frac{A+A^\top}{2}\right)\mathbf{x}\]</span>
<p>这句话的意思是：无论 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的非对称部分长什么样，只要 <span displaypfx="inline-" class="mathjax-container">\(\frac{A+A^\top}{2}\)</span> 不变，二次型 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top A\mathbf{x}\)</span> 对所有 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 的取值就完全不变。</p>
<p>把 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 拆开看更直观。定义对称部分（Symmetric Part）与反对称部分（Skew-symmetric Part）：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(S=\frac{A+A^\top}{2}\)</span>，满足 <span displaypfx="inline-" class="mathjax-container">\(S=S^\top\)</span>。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(K=\frac{A-A^\top}{2}\)</span>，满足 <span displaypfx="inline-" class="mathjax-container">\(K^\top=-K\)</span>。</li>
</ul>
<p>这里“对称部分（Symmetric Part）”是一个<span style="background-color: #c0c0c0;">定义</span>：对任意方阵 <span displaypfx="inline-" class="mathjax-container">\(A\)</span>，把 <span displaypfx="inline-" class="mathjax-container">\(S=\frac{A+A^\top}{2}\)</span> 定义为它的对称部分。它与 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> <span style="background-color: #c0c0c0;">同型（同大小）</span>，并且一定是对称矩阵；它不是 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的某个“子矩阵”。</p>
<p>按元素（Entry-wise）写得更直观：对任意 <span displaypfx="inline-" class="mathjax-container">\(i,j\)</span>，</p>
<span displaypfx="" class="mathjax-container">\[S_{ij}=\frac{A_{ij}+A_{ji}}{2},\quad K_{ij}=\frac{A_{ij}-A_{ji}}{2}\]</span>
<p>也就是说：对称部分就是把每一对对称位置 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\((j,i)\)</span> 的元素取平均；反对称部分则记录它们的“差的一半”。因此 <span displaypfx="inline-" class="mathjax-container">\(A=S+K\)</span> 是把任意矩阵分解成“对称 + 反对称”的标准方式，并且这个分解是唯一的（Unique）。</p>
<p>为什么“取 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(A^\top\)</span> 的平均值”就得到对称部分？因为转置（Transpose）会把非对角元素成对交换： <span displaypfx="inline-" class="mathjax-container">\(A_{ij}\leftrightarrow A_{ji}\)</span>。把它们相加后，非对称性（即 <span displaypfx="inline-" class="mathjax-container">\(A_{ij}-A_{ji}\)</span>）会被抵消，只留下“对称的那一半”（即 <span displaypfx="inline-" class="mathjax-container">\(A_{ij}+A_{ji}\)</span>）。再除以 2，是为了把“加了两份”的量恢复到原始尺度：如果 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 本来就对称（<span displaypfx="inline-" class="mathjax-container">\(A=A^\top\)</span>），那么 <span displaypfx="inline-" class="mathjax-container">\(\frac{A+A^\top}{2}=A\)</span>，不会把矩阵放大一倍。</p>
<p>于是 <span displaypfx="inline-" class="mathjax-container">\(A=S+K\)</span>，并且</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}^\top A\mathbf{x}=\mathbf{x}^\top S\mathbf{x}+\mathbf{x}^\top K\mathbf{x}\]</span>
<p>关键点在于：对任意 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span>，都有 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top K\mathbf{x}=0\)</span>。理由很短：它是一个标量，等于它自己的转置，而</p>
<span displaypfx="" class="mathjax-container">\[(\mathbf{x}^\top K\mathbf{x})^\top=\mathbf{x}^\top K^\top \mathbf{x}=\mathbf{x}^\top(-K)\mathbf{x}=-(\mathbf{x}^\top K\mathbf{x})\]</span>
<p>一个数如果等于它的相反数，只能是 0。于是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top A\mathbf{x}=\mathbf{x}^\top S\mathbf{x}\)</span>，二次型确实只由对称部分决定。</p>
<p>二维展开能直接看到“只依赖对称部分”的具体含义。令 <span displaypfx="inline-" class="mathjax-container">\(A=\begin{bmatrix}a &amp; b\\ c &amp; d\end{bmatrix}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(x_1,x_2)^\top\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}^\top A\mathbf{x}=ax_1^2+(b+c)x_1x_2+dx_2^2\]</span>
<p>交叉项系数只出现 <span displaypfx="inline-" class="mathjax-container">\(b+c\)</span>（也就是 <span displaypfx="inline-" class="mathjax-container">\(A_{12}+A_{21}\)</span>），而差值 <span displaypfx="inline-" class="mathjax-container">\(b-c\)</span>（反对称部分）完全不会出现。</p>
<div class="blog_h4"><span class="graybg">例：两个不同矩阵，二次型完全一样</span></div>
<p>下面给一个“看得见”的数值例子。取</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}2 &amp; 4\\ -2 &amp; 4\end{bmatrix}\]</span>
<p>它显然不是对称矩阵（因为 <span displaypfx="inline-" class="mathjax-container">\(A_{12}=4\ne -2=A_{21}\)</span>）。计算它的对称部分：</p>
<span displaypfx="" class="mathjax-container">\[\frac{A+A^\top}{2}=\frac{1}{2}\left(\begin{bmatrix}2 &amp; 4\\ -2 &amp; 4\end{bmatrix}+\begin{bmatrix}2 &amp; -2\\ 4 &amp; 4\end{bmatrix}\right)=\begin{bmatrix}2 &amp; 1\\ 1 &amp; 4\end{bmatrix}=S\]</span>
<p>现在比较二次型。对任意 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(x,y)^\top\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[A\mathbf{x}=\begin{bmatrix}2x+4y\\ -2x+4y\end{bmatrix}\Rightarrow \mathbf{x}^\top A\mathbf{x}=x(2x+4y)+y(-2x+4y)=2x^2+2xy+4y^2\]</span>
<span displaypfx="" class="mathjax-container">\[S\mathbf{x}=\begin{bmatrix}2x+y\\ x+4y\end{bmatrix}\Rightarrow \mathbf{x}^\top S\mathbf{x}=x(2x+y)+y(x+4y)=2x^2+2xy+4y^2\]</span>
<p>两者对所有 <span displaypfx="inline-" class="mathjax-container">\((x,y)\)</span> 都完全相同；例如取 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(1,2)^\top\)</span>，都有 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top A\mathbf{x}=\mathbf{x}^\top S\mathbf{x}=22\)</span>。这就直观解释了“二次型只依赖对称部分”的含义：反对称的那一半怎么改，都不会改变 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top A\mathbf{x}\)</span> 的值。</p>
<p>因此讨论二次型时通常可假设 <span displaypfx="inline-" class="mathjax-container">\(A=A^\top\)</span>。这也解释了为什么二次型与对称矩阵/半正定性（Positive Semi-Definite, PSD）紧密绑定。</p>
<div class="blog_h3"><span class="graybg">二次型的标准型（Standard Form）</span></div>
<div class="blog_h4"><span class="graybg">标准型与对角标准型是什么</span></div>
<p>二次型 <span displaypfx="inline-" class="mathjax-container">\(q(\mathbf{x})=\mathbf{x}^\top A\mathbf{x}\)</span> 本身是一个几何对象；它在不同坐标系（Coordinate System）/基（Basis）下的<span style="background-color: #c0c0c0;">矩阵表示</span>会不同：同一个几何对象，用不同坐标轴/基表示时，系数矩阵 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的元素会改变。</p>
<p>这里的标准型指的是：在一类允许的坐标变换（可逆线性变量替换（Invertible Linear Change of Variables））下，把同一个二次型写成某种<span style="background-color: #c0c0c0;">约定的简化代表</span>。不同教材的约定略有差异，但最常用的目标是：把交叉项（Cross Term）消掉，露出每个坐标轴方向上的“纯平方项”。</p>
<p>把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=T\mathbf{y}\)</span> 理解成换基（Change of Basis）会更不容易出错： <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span> 是同一几何向量在新基下的坐标，矩阵 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 由新基向量在旧基下的坐标组成。因为 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 可逆，两套坐标是一一对应的，可以互相换回：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{y}=T^{-1}\mathbf{x},\quad \mathbf{x}=T\mathbf{y}\]</span>
<p>把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=T\mathbf{y}\)</span> 代入可得</p>
<span displaypfx="" class="mathjax-container">\[q(\mathbf{x})=\mathbf{x}^\top A\mathbf{x}=\mathbf{y}^\top(T^\top A T)\mathbf{y}\]</span>
<p>这里要求 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 可逆（Invertible），意味着这个变量代换不会把空间压缩到低维（不会丢维度）。因此在新坐标 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span> 下，二次型对应的系数矩阵变为 <span displaypfx="inline-" class="mathjax-container">\(T^\top A T\)</span>（这叫合同变换（Congruence Transformation））。可以把 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 理解为“旋转/缩放后的新坐标轴”在旧坐标里的表示：同一个二次型在新坐标系里的系数就由 <span displaypfx="inline-" class="mathjax-container">\(T^\top A T\)</span> 给出。标准型的目标就是选取合适的 <span displaypfx="inline-" class="mathjax-container">\(T\)</span>，把 <span displaypfx="inline-" class="mathjax-container">\(T^\top A T\)</span> 化到更简单的结构。</p>
<p>对角标准型（Diagonal Form）指把二次型写成“只有平方项、没有交叉项”的形式（也常称对角规范形（Diagonal Canonical Form））：</p>
<span displaypfx="" class="mathjax-container">\[q(\mathbf{x})=\sum_{i=1}^{n}\lambda_i y_i^2\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i\)</span> 是系数，<span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span> 是新坐标。对角标准型等价于：在新坐标系下，二次型对应的矩阵是对角矩阵（Diagonal Matrix）；“交叉项” <span displaypfx="inline-" class="mathjax-container">\(y_i y_j\)</span>（<span displaypfx="inline-" class="mathjax-container">\(i\ne j\)</span>）消失。</p>
<p>结论需要明确：标准型与原来的二次型描述的是<span style="background-color: #c0c0c0;">同一个二次型/同一组几何等值集合</span>，只是坐标系不同。给定可逆变换 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=T\mathbf{y}\)</span>，任何关于 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 的几何描述都可以无损地翻译成关于 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span> 的描述，并且可以随时“还原”回去。矩阵层面也一样：若 <span displaypfx="inline-" class="mathjax-container">\(A' = T^\top A T\)</span> 是标准型里的系数矩阵，则 <span displaypfx="inline-" class="mathjax-container">\(A=(T^{-1})^\top A' T^{-1}\)</span> 可把它变回原坐标下的表示。</p>
<p>接下来真正关心的是：<span style="background-color: #c0c0c0;">如何选 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 才能把交叉项消掉</span>。对二次型而言，一个关键简化是：二次型只依赖矩阵的对称部分，因此总可以先把 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 对称化为 <span displaypfx="inline-" class="mathjax-container">\(\frac{A+A^\top}{2}\)</span> 而不改变 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top A\mathbf{x}\)</span> 的值。于是“消交叉项”的核心问题就变成：对实对称矩阵，能否通过一次正交变基（Orthogonal Change of Basis）把它对角化（Diagonalize）。</p>
<div class="blog_h4"><span class="graybg">怎么来的：换到特征向量基消掉交叉项</span></div>
<p>对实对称矩阵，对角化与“消掉交叉项”来自同一个结构事实：它总可以通过一次正交变基（Orthogonal Change of Basis）写成对角形式。数学依据就是谱定理（Spectral Theorem，也常表述为“实对称矩阵可正交对角化（Orthogonal Diagonalization）”）。若 <span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{n\times n}\)</span> 是实对称矩阵（Real Symmetric Matrix, <span displaypfx="inline-" class="mathjax-container">\(A=A^\top\)</span>），则存在正交矩阵（Orthogonal Matrix）<span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 与实对角矩阵（Real Diagonal Matrix）<span displaypfx="inline-" class="mathjax-container">\(\Lambda\)</span> 使得</p>
<span displaypfx="" class="mathjax-container">\[A=Q\Lambda Q^\top,\quad \text{等价于}\quad Q^\top A Q=\Lambda\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(Q=[\mathbf{v}_1,\ldots,\mathbf{v}_n]\)</span> 的列向量是一组单位特征向量（Orthonormal Eigenvectors），<span displaypfx="inline-" class="mathjax-container">\(\Lambda=\mathrm{diag}(\lambda_1,\ldots,\lambda_n)\)</span> 的对角元素是对应特征值（Eigenvalues）。该定理同时包含两个常用事实：特征值都是实数；并且可以选出一组两两正交的特征向量作为基。</p>
<p>这就是你熟悉的特征值分解（Eigendecomposition / Eigenvalue Decomposition）的对称矩阵特例。</p>
<p>一般情况下，如果矩阵可对角化（Diagonalizable），可以写成 <span displaypfx="inline-" class="mathjax-container">\(A=V\Lambda V^{-1}\)</span>（或 <span displaypfx="inline-" class="mathjax-container">\(V^{-1}AV=\Lambda\)</span>），其中 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 的列是特征向量；但 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 不一定正交（Orthogonal），甚至矩阵可能不可对角化（Non-diagonalizable）。</p>
<p>对称矩阵的额外好处是：可以把 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 选成正交矩阵 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span>，因此 <span displaypfx="inline-" class="mathjax-container">\(V^{-1}=Q^{-1}=Q^\top\)</span>，分解变成数值上更稳定、几何上更直观的 <span displaypfx="inline-" class="mathjax-container">\(A=Q\Lambda Q^\top\)</span>。</p>
<p>两个最小例子能把“<span displaypfx="inline-" class="mathjax-container">\(V\)</span> 不一定正交 / 甚至不可对角化”说得更具体：</p>
<ul>
<li>
<p><span style="background-color: #c0c0c0;">例 1：可对角化，但 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 不正交</span>。取</p>
<span displaypfx="" class="mathjax-container">\[A_1=\begin{bmatrix}2 &amp; 1\\ 0 &amp; 1\end{bmatrix}\]</span>
<p>它的特征值是 <span displaypfx="inline-" class="mathjax-container">\(\lambda_1=2,\lambda_2=1\)</span>（两个不同特征值意味着在二维里一定能找到两条线性无关的特征向量，因此可对角化）。对应一组特征向量可以取</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{v}_1=\begin{bmatrix}1\\ 0\end{bmatrix},\quad \mathbf{v}_2=\begin{bmatrix}1\\ -1\end{bmatrix}\]</span>
<p>它们并不正交，因为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}_1^\top\mathbf{v}_2=1\ne 0\)</span>。把它们按列组成矩阵 <span displaypfx="inline-" class="mathjax-container">\(V=[\mathbf{v}_1\ \mathbf{v}_2]\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[V=\begin{bmatrix}1 &amp; 1\\ 0 &amp; -1\end{bmatrix},\quad \Lambda=\begin{bmatrix}2 &amp; 0\\ 0 &amp; 1\end{bmatrix},\quad V^{-1}=\begin{bmatrix}1 &amp; 1\\ 0 &amp; -1\end{bmatrix}\]</span>
<p>注意：这个例子里 <span displaypfx="inline-" class="mathjax-container">\(V^{-1}\)</span> 恰好等于 <span displaypfx="inline-" class="mathjax-container">\(V\)</span>（只是代数上的巧合），但它仍然不是正交矩阵，因为正交要求 <span displaypfx="inline-" class="mathjax-container">\(V^{-1}=V^\top\)</span>，而这里并不成立。</p>
<p>并且确实有 <span displaypfx="inline-" class="mathjax-container">\(A_1=V\Lambda V^{-1}\)</span>。这个例子说明：一般矩阵即使可对角化，特征向量也未必能选成“互相垂直的方向”。</p>
</li>
<li>
<p><span style="background-color: #c0c0c0;">例 2：不可对角化（特征值重复，但特征向量不够）</span>。取</p>
<span displaypfx="" class="mathjax-container">\[A_2=\begin{bmatrix}1 &amp; 1\\ 0 &amp; 1\end{bmatrix}\]</span>
<p>它的特征值只有 <span displaypfx="inline-" class="mathjax-container">\(\lambda=1\)</span>（在二维里重复出现）。求特征向量需要解 <span displaypfx="inline-" class="mathjax-container">\((A_2-I)\mathbf{v}=\mathbf{0}\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[A_2-I=\begin{bmatrix}0 &amp; 1\\ 0 &amp; 0\end{bmatrix}\Rightarrow (A_2-I)\begin{bmatrix}x\\ y\end{bmatrix}=\begin{bmatrix}y\\ 0\end{bmatrix}=\begin{bmatrix}0\\ 0\end{bmatrix}\Rightarrow y=0\]</span>
<p>因此所有特征向量都形如 <span displaypfx="inline-" class="mathjax-container">\((x,0)^\top\)</span>，只有 1 个线性无关方向。要写成 <span displaypfx="inline-" class="mathjax-container">\(A_2=V\Lambda V^{-1}\)</span>，矩阵 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 必须可逆（Invertible），这要求有足够多（在二维里是 2 个）线性无关特征向量作为列；该矩阵做不到，所以它不可对角化。</p>
</li>
</ul>
<p>把这个定理放回二次型就能立刻看出“交叉项为什么会消失”。令坐标变换 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}=Q^\top\mathbf{x}\)</span>（把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 在特征向量基下的坐标记作 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span>），则</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}^\top A\mathbf{x}=\mathbf{x}^\top(Q\Lambda Q^\top)\mathbf{x}=\mathbf{y}^\top\Lambda\mathbf{y}=\sum_{i=1}^{n}\lambda_i y_i^2\]</span>
<p>注意这里同时出现了两种“换坐标”的写法：线性变换里常写 <span displaypfx="inline-" class="mathjax-container">\(Q^{-1}AQ\)</span>（相似变换（Similarity Transformation）），二次型里写 <span displaypfx="inline-" class="mathjax-container">\(Q^\top A Q\)</span>（合同变换（Congruence Transformation））。对正交矩阵而言 <span displaypfx="inline-" class="mathjax-container">\(Q^{-1}=Q^\top\)</span>，所以它们在这里完全一致：同一个正交变换既给出特征值分解，也把二次型化到没有交叉项的对角标准型。</p>
<p>因此：在对称矩阵的情形，“换到特征向量基”与“把二次型旋转到主轴”是同一件事，只是用不同语言描述。</p>
<p>直观上，特征向量（Eigenvector）给出“主轴方向”（把坐标轴转到这些方向后，交叉项会消失），特征值（Eigenvalue）则是标准型里各平方项前的系数。</p>
<p>详细例子：取</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}5 &amp; -2\\ -2 &amp; 5\end{bmatrix}\]</span>
<p>它是对称矩阵，因此可正交对角化。其特征值与一组单位特征向量可以取为：</p>
<span displaypfx="" class="mathjax-container">\[\lambda_1=3,\ \mathbf{v}_1=\frac{1}{\sqrt{2}}\begin{bmatrix}1\\ 1\end{bmatrix};\quad \lambda_2=7,\ \mathbf{v}_2=\frac{1}{\sqrt{2}}\begin{bmatrix}1\\ -1\end{bmatrix}\]</span>
<p>把它们组成正交矩阵与对角矩阵：</p>
<span displaypfx="" class="mathjax-container">\[Q=[\mathbf{v}_1\ \mathbf{v}_2]=\frac{1}{\sqrt{2}}\begin{bmatrix}1 &amp; 1\\ 1 &amp; -1\end{bmatrix},\quad \Lambda=\begin{bmatrix}3 &amp; 0\\ 0 &amp; 7\end{bmatrix}\]</span>
<p>则 <span displaypfx="inline-" class="mathjax-container">\(A=Q\Lambda Q^\top\)</span>。这里 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span> 不是任意新变量，而是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 在特征向量基 <span displaypfx="inline-" class="mathjax-container">\(\{\mathbf{v}_1,\mathbf{v}_2\}\)</span> 下的坐标： <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=y_1\mathbf{v}_1+y_2\mathbf{v}_2=Q\mathbf{y}\)</span>。</p>
<p>由于 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 是正交矩阵（<span displaypfx="inline-" class="mathjax-container">\(Q^\top Q=I\)</span>），左乘 <span displaypfx="inline-" class="mathjax-container">\(Q^\top\)</span> 可得 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}=Q^\top\mathbf{x}\)</span>。这一步就是换基（Change of Basis）：把向量从标准坐标系表达改写为主轴坐标系表达。</p>
<p>因此可显式写出两组坐标关系：</p>
<span displaypfx="" class="mathjax-container">\[y_1=\frac{x_1+x_2}{\sqrt{2}},\quad y_2=\frac{x_1-x_2}{\sqrt{2}}\]</span>
<span displaypfx="" class="mathjax-container">\[x_1=\frac{y_1+y_2}{\sqrt{2}},\quad x_2=\frac{y_1-y_2}{\sqrt{2}}\]</span>
<p>代入标准型：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}^\top A\mathbf{x}=\mathbf{y}^\top\Lambda\mathbf{y}=3y_1^2+7y_2^2\]</span>
<p>把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span> 用 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 展开，可直接验证“交叉项被旋转消掉”：</p>
<span displaypfx="" class="mathjax-container">\[3y_1^2+7y_2^2=\frac{3}{2}(x_1+x_2)^2+\frac{7}{2}(x_1-x_2)^2=5x_1^2-4x_1x_2+5x_2^2\]</span>
<p>数值校验：取 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(1,2)^\top\)</span>，原式为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top A\mathbf{x}=17\)</span>；而 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}=Q^\top\mathbf{x}=\left(\frac{3}{\sqrt{2}},-\frac{1}{\sqrt{2}}\right)^\top\)</span>，代入 <span displaypfx="inline-" class="mathjax-container">\(3y_1^2+7y_2^2\)</span> 同样得到 17。</p>
<p>几何解释：正交矩阵 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 表示旋转/换基，把坐标轴对齐到“主轴方向”（特征向量）；对角矩阵 <span displaypfx="inline-" class="mathjax-container">\(\Lambda\)</span> 表示沿主轴的逐轴缩放（由特征值控制）。因此等值线在 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span> 坐标系里与轴对齐，形状由 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i\)</span> 决定。</p>
<p>若 <span displaypfx="inline-" class="mathjax-container">\(\Lambda\)</span> 中既有正特征值也有负特征值，则二次型是不定的（Indefinite）：沿某些方向 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 增大，沿另一些方向 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 减小。二维里它的等值线（Level Set）典型呈双曲线（Hyperbola）形状，优化里对应鞍点（Saddle Point）结构。例：令 <span displaypfx="inline-" class="mathjax-container">\(A=\begin{bmatrix}1 &amp; 2\\ 2 &amp; 1\end{bmatrix}\)</span>，其特征值为 3 与 -1，在某个正交坐标 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span> 下有 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^\top A\mathbf{x}=3y_1^2-y_2^2\)</span>，可取正也可取负。</p>
<p>进一步允许一般可逆线性变换（不要求是旋转）时，可把对角项缩放为 <span displaypfx="inline-" class="mathjax-container">\(+1,-1,0\)</span>（Sylvester 惯性定理（Law of Inertia））：二次型被分解为若干正平方项、负平方项与零方向，其中正/负/零项的个数在合同变换下保持不变（换言之，“正方向有几个、负方向有几个、平坦方向有几个”是坐标变换改不掉的性质）；正/负项的个数也称为签名（Signature）。在优化里，它们分别对应局部最小、鞍点与平坦方向。</p>
<div class="blog_h3"><span class="graybg">二次型与机器学习（Quadratic Form in Machine Learning）</span></div>
<p>下面这些场景看起来不同，但核心都在计算“某个方向上的能量/代价”：给一个向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\)</span>，二次型 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}^\top A\mathbf{v}\)</span> 会告诉你它在矩阵 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 定义的几何里有多大、代价有多高。</p>
<ul>
<li>平方范数（Squared <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> Norm）：<span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{x}\|_2^2=\mathbf{x}^\top I\mathbf{x}\)</span>。直白地说，它就是“向量长度的平方”，在训练里常作为最基础的“大小惩罚”。例如权重衰减（Weight Decay）把过大的参数拉回去，本质是在最小化 <span displaypfx="inline-" class="mathjax-container">\(\|\theta\|_2^2\)</span> 这种二次型。</li>
<li>最小二乘与二次损失（Least Squares / MSE）：线性回归目标 <span displaypfx="inline-" class="mathjax-container">\(\|X\mathbf{w}-\mathbf{y}\|_2^2\)</span> 展开后是关于 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 的二次型。通俗理解：模型每偏一点，代价按“平方”增长，所以大误差会被更重惩罚。它的闭式解来自正规方程（Normal Equations）<span displaypfx="inline-" class="mathjax-container">\(X^\top X\mathbf{w}=X^\top\mathbf{y}\)</span>。</li>
<li>PCA（Principal Component Analysis）：对中心化（Centering）数据，方向 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}\)</span> 上的方差是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}^\top\Sigma\mathbf{u}\)</span>。这句话的直觉是：“把数据投影到某个方向后，能展开多宽”。PCA 就是在所有单位方向里找让这个二次型最大的方向（主成分），因此主成分就是“信息最密集”的方向。</li>
<li>马氏距离（Mahalanobis Distance）与高斯负对数似然（Gaussian NLL）：核心项是 <span displaypfx="inline-" class="mathjax-container">\((\mathbf{x}-\boldsymbol{\mu})^\top\Sigma^{-1}(\mathbf{x}-\boldsymbol{\mu})\)</span>。可以把它理解成“先按数据真实尺度做校正，再测距离”：方差大的方向偏离一点不算太异常，方差小的方向偏离同样大小则更异常。异常检测（Anomaly Detection）和高斯判别模型都依赖这个量。</li>
<li>二阶近似与优化曲率（Second-order Approximation / Curvature）：在参数点附近，损失变化可写成 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{2}\Delta^\top H\Delta\)</span>。它告诉你“往哪个方向走会涨得快/慢”：特征值大表示该方向很陡，特征值小表示平坦，正负混合则是鞍点（Saddle）。这也是为什么牛顿法、预条件（Preconditioning）和学习率调度都在关心 Hessian 的谱结构。</li>
</ul>
<p>在机器学习的实现层面，二次型也常先通过可逆变量替换 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=T\mathbf{y}\)</span> 化到更易计算的表示： <span displaypfx="inline-" class="mathjax-container">\(q(\mathbf{x})=\mathbf{x}^\top A\mathbf{x}=\mathbf{y}^\top(T^\top A T)\mathbf{y}\)</span>。若 <span displaypfx="inline-" class="mathjax-container">\(A=A^\top\)</span>，原坐标有 <span displaypfx="inline-" class="mathjax-container">\(\nabla_{\mathbf{x}}q=2A\mathbf{x}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\nabla^2_{\mathbf{x}}q=2A\)</span>；在新坐标下 <span displaypfx="inline-" class="mathjax-container">\(\nabla_{\mathbf{y}}q=2A'\mathbf{y}\)</span>（<span displaypfx="inline-" class="mathjax-container">\(A'=T^\top A T\)</span>）。若进一步化到对角标准型 <span displaypfx="inline-" class="mathjax-container">\(A'=\Lambda\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial q}{\partial y_i}=2\lambda_i y_i\)</span>，逐坐标解耦，推导与实现都会更直接。</p>
<p>这里要区分“性质不变”和“数值不变”：在可逆变量替换下，正定/半正定/不定性质与正负零方向个数（惯性（Inertia））保持不变，因此局部最小/鞍点等优化结构不变；但一般合同变换 <span displaypfx="inline-" class="mathjax-container">\(T^\top A T\)</span> 不要求逐个保留特征值数值，只有正交相似变换 <span displaypfx="inline-" class="mathjax-container">\(Q^\top A Q\)</span> 才逐个保留特征值。</p>
<div class="blog_h2"><span class="graybg">对角矩阵（Diagonal Matrix）</span></div>
<p>对角矩阵（Diagonal Matrix）是只有对角线元素可能非零的方阵。写作 <span displaypfx="inline-" class="mathjax-container">\(D=\mathrm{diag}(d_1,\ldots,d_n)\)</span>，其非对角元素全为 0：</p>
<span displaypfx="" class="mathjax-container">\[D=\begin{bmatrix}d_1 &amp; 0 &amp; \cdots &amp; 0\\ 0 &amp; d_2 &amp; \cdots &amp; 0\\ \vdots &amp; \vdots &amp; \ddots &amp; \vdots\\ 0 &amp; 0 &amp; \cdots &amp; d_n\end{bmatrix}\]</span>
<p>对角矩阵乘以向量等价于“逐维缩放”：若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(x_1,\ldots,x_n)^\top\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(D\mathbf{x}=(d_1x_1,\ldots,d_nx_n)^\top\)</span>。例：令 <span displaypfx="inline-" class="mathjax-container">\(D=\mathrm{diag}(2,0.5)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(3,4)^\top\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(D\mathbf{x}=(6,2)^\top\)</span>。</p>
<p>在 AI 里，对角矩阵最常见的用途是把“逐元素缩放”写成线性算子：例如 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\odot \mathbf{s}=\mathrm{diag}(\mathbf{s})\mathbf{x}\)</span>。很多优化器的自适应学习率也可以视为对角预条件（Diagonal Preconditioner）：例如 Adam/Adagrad 里的 <span displaypfx="inline-" class="mathjax-container">\(1/\sqrt{v+\epsilon}\)</span> 本质上是按参数维度缩放梯度。</p>
<div class="blog_h3"><span class="graybg">对角化（Diagonalization）</span></div>
<p>对角化（Diagonalization）研究的不是“一个矩阵本来是不是对角矩阵”，而是<span style="background-color: #c0c0c0;">能否通过换基把它变成对角矩阵</span>。若存在可逆矩阵 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 与对角矩阵 <span displaypfx="inline-" class="mathjax-container">\(\Lambda\)</span> 使得</p>
<span displaypfx="" class="mathjax-container">\[A=P\Lambda P^{-1}\]</span>
<p>则称 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 可对角化（Diagonalizable）。这里 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 的列向量通常取为 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的一组线性无关特征向量， <span displaypfx="inline-" class="mathjax-container">\(\Lambda\)</span> 的对角线上放的是对应特征值。也就是说，对角化本质上是在寻找一个“最自然的坐标系”，使线性变换在这个坐标系里不再发生分量混合，而只剩逐坐标缩放。</p>
<p>这和前面的对角矩阵直接连在一起：对角矩阵本来就表示“各坐标轴彼此独立地缩放”；对角化则说明，很多看起来耦合很强的矩阵，只要换到合适基底，也能被还原成这种最简单的作用形式。因此，对角矩阵是结构最简单的目标形式，对角化是把一般矩阵化到这个目标形式的方法。</p>
<p>若写成特征分解的形式，这个关系更直接。设 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 有 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个线性无关特征向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}_1,\dots,\mathbf{v}_n\)</span>，对应特征值为 <span displaypfx="inline-" class="mathjax-container">\(\lambda_1,\dots,\lambda_n\)</span>。令</p>
<span displaypfx="" class="mathjax-container">\[P=[\mathbf{v}_1,\dots,\mathbf{v}_n],\quad \Lambda=\mathrm{diag}(\lambda_1,\dots,\lambda_n)\]</span>
<p>则有 <span displaypfx="inline-" class="mathjax-container">\(AP=P\Lambda\)</span>，进而得到 <span displaypfx="inline-" class="mathjax-container">\(A=P\Lambda P^{-1}\)</span>。这表明：<span style="background-color: #c0c0c0;">可对角化的核心条件，就是能否找到足够多的线性无关特征向量，把整个空间铺满</span>。</p>
<p>例：令</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}3 &amp; 1\\ 0 &amp; 2\end{bmatrix}\]</span>
<p>它的特征值为 <span displaypfx="inline-" class="mathjax-container">\(3\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(2\)</span>。对应可取特征向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}_1=(1,0)^\top\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}_2=(1,-1)^\top\)</span>。于是</p>
<span displaypfx="" class="mathjax-container">\[P=\begin{bmatrix}1 &amp; 1\\ 0 &amp; -1\end{bmatrix},\quad \Lambda=\begin{bmatrix}3 &amp; 0\\ 0 &amp; 2\end{bmatrix},\quad A=P\Lambda P^{-1}\]</span>
<p>在标准坐标下， <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 既有缩放也有“串扰”；在特征向量基下，它只是在第一维乘 3、第二维乘 2。</p>
<p>为什么需要对角化，原因有三层。第一，<span style="background-color: #c0c0c0;">计算会显著简化</span>。例如</p>
<span displaypfx="" class="mathjax-container">\[A^k=P\Lambda^k P^{-1}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\Lambda^k\)</span> 只需把每个对角元素分别升到 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 次方，即 <span displaypfx="inline-" class="mathjax-container">\(\Lambda^k=\mathrm{diag}(\lambda_1^k,\dots,\lambda_n^k)\)</span>。这让离散动力系统、马尔可夫链、线性递推、RNN 线性稳定性分析都更容易做。</p>
<p>第二，<span style="background-color: #c0c0c0;">几何结构会变透明</span>。二次型、曲率、协方差传播等问题，本质都在问“哪些方向最重要、每个方向强度是多少”。对角化以后，每个方向对应一个特征值，方向之间不再耦合。前面讲二次型标准型、正定矩阵、谱分解时反复出现的“换到主轴方向再逐维看”，本质上都属于这种思路。</p>
<p>第三，<span style="background-color: #c0c0c0;">稳定性与长期行为可以直接从特征值读出</span>。例如在线性迭代 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_{t+1}=A\mathbf{x}_t\)</span> 中，若 <span displaypfx="inline-" class="mathjax-container">\(|\lambda_i|&lt;1\)</span>，对应方向会衰减；若 <span displaypfx="inline-" class="mathjax-container">\(|\lambda_i|&gt;1\)</span>，对应方向会放大；若 <span displaypfx="inline-" class="mathjax-container">\(|\lambda_i|=1\)</span>，则处于临界状态。很多收敛性判断最终都落到这个层面。</p>
<p>对称矩阵是最理想的情形。它不仅可对角化，而且可以被正交对角化：</p>
<span displaypfx="" class="mathjax-container">\[A=Q\Lambda Q^\top\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 是正交矩阵，因此换基不会引入尺度扭曲，只是旋转坐标系。这正是 PCA、协方差分析、Hessian 主曲率分析最喜欢对称矩阵的原因。相比之下，一般矩阵即便可对角化，也可能需要非正交的 <span displaypfx="inline-" class="mathjax-container">\(P\)</span>，数值稳定性通常更差。</p>
<div class="blog_h2"><span class="graybg">单位矩阵（Identity Matrix）</span></div>
<p>单位矩阵（Identity Matrix）<span displaypfx="inline-" class="mathjax-container">\(I_n\)</span> 是对角线上全为 1、其他元素为 0 的方阵：</p>
<span displaypfx="" class="mathjax-container">\[I_n=\begin{bmatrix}1 &amp; 0 &amp; \cdots &amp; 0\\ 0 &amp; 1 &amp; \cdots &amp; 0\\ \vdots &amp; \vdots &amp; \ddots &amp; \vdots\\ 0 &amp; 0 &amp; \cdots &amp; 1\end{bmatrix}\]</span>
<p>它是矩阵乘法的单位元：对任意形状匹配的矩阵 <span displaypfx="inline-" class="mathjax-container">\(A\)</span>，有 <span displaypfx="inline-" class="mathjax-container">\(AI=IA=A\)</span>；对任意向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span>，有 <span displaypfx="inline-" class="mathjax-container">\(I\mathbf{x}=\mathbf{x}\)</span>。例：若 <span displaypfx="inline-" class="mathjax-container">\(I_2=\begin{bmatrix}1 &amp; 0\\ 0 &amp; 1\end{bmatrix}\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(I_2(3,4)^\top=(3,4)^\top\)</span>。</p>
<p>在 AI/数值计算里，<span displaypfx="inline-" class="mathjax-container">\(A+\lambda I\)</span>（<span displaypfx="inline-" class="mathjax-container">\(\lambda&gt;0\)</span>）用于改善条件数、提高可逆性与数值稳定性：例如岭回归（Ridge Regression）把 <span displaypfx="inline-" class="mathjax-container">\(X^\top X\)</span> 替换为 <span displaypfx="inline-" class="mathjax-container">\(X^\top X+\lambda I\)</span>；在高斯模型与协方差估计里常见 <span displaypfx="inline-" class="mathjax-container">\(\Sigma+\epsilon I\)</span> 来保证 Cholesky 分解可用。</p>
<div class="blog_h2"><span class="graybg">对称矩阵（Symmetric Matrix）</span></div>
<p>对称矩阵（Symmetric Matrix）是满足 <span displaypfx="inline-" class="mathjax-container">\(A=A^\top\)</span> 的实方阵，即 <span displaypfx="inline-" class="mathjax-container">\(A_{ij}=A_{ji}\)</span>。直观上，它的上三角与下三角互为镜像。</p>
<p>例： <span displaypfx="inline-" class="mathjax-container">\(\begin{bmatrix}2 &amp; 1\\ 1 &amp; 3\end{bmatrix}\)</span> 是对称矩阵；而 <span displaypfx="inline-" class="mathjax-container">\(\begin{bmatrix}2 &amp; 1\\ 0 &amp; 3\end{bmatrix}\)</span> 不是，因为非对角元素不成对相等。</p>
<p>对称矩阵拥有更“干净”的谱结构：所有特征值都是实数，并且可正交对角化（Spectral Theorem）：<span displaypfx="inline-" class="mathjax-container">\(A=Q\Lambda Q^\top\)</span>（<span displaypfx="inline-" class="mathjax-container">\(Q^\top Q=I\)</span>）。这使得很多推导都可以在旋转后的坐标系里逐维分析二次型、曲率与能量。</p>
<p>在 AI 里，对称矩阵高频出现于：</p>
<ul>
<li>协方差矩阵（Covariance Matrix）<span displaypfx="inline-" class="mathjax-container">\(\Sigma\)</span>：例如高斯模型与特征白化（Whitening）里，要求 <span displaypfx="inline-" class="mathjax-container">\(\Sigma\succeq 0\)</span>，并常用 <span displaypfx="inline-" class="mathjax-container">\(\Sigma+\epsilon I\)</span> 保证数值稳定。</li>
<li>Gram 矩阵（Gram Matrix）<span displaypfx="inline-" class="mathjax-container">\(X^\top X\)</span> 与核矩阵（Kernel Matrix）<span displaypfx="inline-" class="mathjax-container">\(K\)</span>：它们天然对称/半正定，是最小二乘、岭回归与核方法的核心对象。</li>
<li>海森矩阵（Hessian）：当目标函数二阶连续可导时，Hessian 对称；其特征值决定局部曲率，从而决定“极小/极大/鞍点”的类型与优化难度。</li>
</ul>
<div class="blog_h2"><span class="graybg">厄米矩阵（Hermitian Matrix）</span></div>
<p>厄米矩阵（Hermitian Matrix）是复数域上与对称矩阵对应的概念。若复矩阵 <span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{C}^{n\times n}\)</span> 满足</p>
<span displaypfx="" class="mathjax-container">\[A=A^\ast\]</span>
<p>则称 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 为厄米矩阵，其中 <span displaypfx="inline-" class="mathjax-container">\(A^\ast\)</span> 表示共轭转置（Conjugate Transpose）：先转置，再对每个元素取复共轭。因此，厄米矩阵满足按元素关系 <span displaypfx="inline-" class="mathjax-container">\(A_{ij}=\overline{A_{ji}}\)</span>。可以把它直接理解为<span style="background-color: #c0c0c0;">复数版本的对称矩阵</span>。</p>
<p>例： <span displaypfx="inline-" class="mathjax-container">\(\begin{bmatrix}1 &amp; 2+i\\ 2-i &amp; 3\end{bmatrix}\)</span> 是厄米矩阵，因为非对角元素互为复共轭，而对角线元素必须是实数。厄米矩阵保留了实对称矩阵最重要的好性质：特征值全为实数，并且可以被酉矩阵（Unitary Matrix）对角化。因此在复数信号处理、量子力学、复数优化与某些频域分析里，它扮演的角色与实对称矩阵在实数域中的角色完全对应。</p>
<div class="blog_h2"><span class="graybg">可逆矩阵与奇异矩阵（Invertible vs Singular）</span></div>
<p>可逆矩阵（Invertible Matrix）是存在逆矩阵的方阵：对 <span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{n\times n}\)</span>，若存在 <span displaypfx="inline-" class="mathjax-container">\(A^{-1}\)</span> 使得 <span displaypfx="inline-" class="mathjax-container">\(AA^{-1}=A^{-1}A=I\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 可逆；否则称 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 为奇异矩阵（Singular Matrix）。</p>
<p>等价判据（常用）：<span displaypfx="inline-" class="mathjax-container">\(A\)</span> 可逆 <span displaypfx="inline-" class="mathjax-container">\(\Leftrightarrow \det(A)\ne 0 \Leftrightarrow \mathrm{rank}(A)=n\)</span>（列向量线性无关）。</p>
<p>例（可逆）：令 <span displaypfx="inline-" class="mathjax-container">\(A=\begin{bmatrix}2 &amp; 1\\ 1 &amp; 1\end{bmatrix}\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\det(A)=1\)</span>，并且 <span displaypfx="inline-" class="mathjax-container">\(A^{-1}=\begin{bmatrix}1 &amp; -1\\ -1 &amp; 2\end{bmatrix}\)</span>。</p>
<p>例（奇异）：令 <span displaypfx="inline-" class="mathjax-container">\(B=\begin{bmatrix}1 &amp; 2\\ 2 &amp; 4\end{bmatrix}\)</span>，第二行是第一行的 2 倍，因此秩为 1、行列式为 0，无法求逆。对应线性方程组 <span displaypfx="inline-" class="mathjax-container">\(B\mathbf{x}=\mathbf{b}\)</span> 可能无解（例如 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}=(3,5)^\top\)</span>），也可能有无穷多解（例如 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}=(3,6)^\top\)</span>）。</p>
<p>在 AI 里，奇异性最常出现在最小二乘与协方差：当特征共线、维度远大于样本数（<span displaypfx="inline-" class="mathjax-container">\(d\gg N\)</span>）时，<span displaypfx="inline-" class="mathjax-container">\(X^\top X\)</span> 往往奇异或病态（Ill-conditioned）。常见处理是正则化（<span displaypfx="inline-" class="mathjax-container">\(X^\top X+\lambda I\)</span>）或用 SVD/QR 求解并使用伪逆（Pseudoinverse）<span displaypfx="inline-" class="mathjax-container">\(A^+\)</span>。</p>
<p>实现上通常避免显式求 <span displaypfx="inline-" class="mathjax-container">\(A^{-1}\)</span>：更稳定的做法是直接求解 <span displaypfx="inline-" class="mathjax-container">\(A\mathbf{x}=\mathbf{b}\)</span>（Solve），或用分解（Cholesky / QR / SVD）替代。</p>
<div class="blog_h2"><span class="graybg">正交矩阵（Orthogonal Matrix）</span></div>
<p>正交矩阵（Orthogonal Matrix）是满足 <span displaypfx="inline-" class="mathjax-container">\(Q^\top Q=QQ^\top=I\)</span> 的实方阵。它的列向量（或行向量）构成一组正交标准基（Orthonormal Basis），因此保持长度与点积：对任意向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 有 <span displaypfx="inline-" class="mathjax-container">\(\|Q\mathbf{x}\|_2=\|\mathbf{x}\|_2\)</span>，对任意向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{a},\mathbf{b}\)</span> 有 <span displaypfx="inline-" class="mathjax-container">\((Q\mathbf{a})^\top(Q\mathbf{b})=\mathbf{a}^\top\mathbf{b}\)</span>。</p>
<p>正交矩阵的列向量不需要“沿着坐标轴方向”。要求只有一个：列向量两两正交且都是单位向量，也就是构成一组正交标准基。标准基（<span displaypfx="inline-" class="mathjax-container">\(\mathbf{e}_1,\mathbf{e}_2,\ldots\)</span>）只是其中最常用的一组。</p>
<p>例（旋转 45°）：令</p>
<span displaypfx="" class="mathjax-container">\[Q=\frac{1}{\sqrt{2}}\begin{bmatrix}1 &amp; -1\\ 1 &amp; 1\end{bmatrix}\]</span>
<p>它的两列分别是 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{\sqrt{2}}(1,1)^\top\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{\sqrt{2}}(-1,1)^\top\)</span>，都不与坐标轴对齐，但它们正交且单位长度，因此</p>
<span displaypfx="" class="mathjax-container">\[Q^\top Q=\frac{1}{2}\begin{bmatrix}1 &amp; 1\\ -1 &amp; 1\end{bmatrix}\begin{bmatrix}1 &amp; -1\\ 1 &amp; 1\end{bmatrix}=\begin{bmatrix}1 &amp; 0\\ 0 &amp; 1\end{bmatrix}=I\]</span>
<p>例（二维旋转 90°）：令</p>
<span displaypfx="" class="mathjax-container">\[R=\begin{bmatrix}0 &amp; -1\\ 1 &amp; 0\end{bmatrix},\quad R^\top=\begin{bmatrix}0 &amp; 1\\ -1 &amp; 0\end{bmatrix}\]</span>
<p>则</p>
<span displaypfx="" class="mathjax-container">\[R^\top R=RR^\top=\begin{bmatrix}1 &amp; 0\\ 0 &amp; 1\end{bmatrix}=I\]</span>
<p>在 AI 里，正交矩阵常用于正交初始化（Orthogonal Initialization）、QR 分解与正交约束参数化；核心目的是把谱范数控制在 1 附近，改善深层网络与 RNN 的数值稳定性。</p>
<div class="blog_h2"><span class="graybg">酉矩阵（Unitary Matrix）</span></div>
<p>酉矩阵（Unitary Matrix）是复数域上的“长度保持”线性变换。对复矩阵 <span displaypfx="inline-" class="mathjax-container">\(U\in\mathbb{C}^{n\times n}\)</span>，若满足</p>
<span displaypfx="" class="mathjax-container">\[U^\ast U=UU^\ast=I\]</span>
<p>则称 <span displaypfx="inline-" class="mathjax-container">\(U\)</span> 为酉矩阵，其中 <span displaypfx="inline-" class="mathjax-container">\(U^\ast\)</span> 是共轭转置（Conjugate Transpose）。酉矩阵的列向量构成一组正交归一基（Orthonormal Basis），因此对任意向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 都有 <span displaypfx="inline-" class="mathjax-container">\(\|U\mathbf{x}\|_2=\|\mathbf{x}\|_2\)</span>。</p>
<p>实数域特例：当矩阵元素为实数时，酉矩阵退化为正交矩阵（Orthogonal Matrix），即满足 <span displaypfx="inline-" class="mathjax-container">\(Q^\top Q=QQ^\top=I\)</span>。</p>
<p>例（复数域）：令</p>
<span displaypfx="" class="mathjax-container">\[U=\begin{bmatrix}1 &amp; 0\\ 0 &amp; i\end{bmatrix},\quad U^\ast=\begin{bmatrix}1 &amp; 0\\ 0 &amp; -i\end{bmatrix}\]</span>
<p>则可直接计算：</p>
<span displaypfx="" class="mathjax-container">\[U^\ast U=\begin{bmatrix}1 &amp; 0\\ 0 &amp; -i\end{bmatrix}\begin{bmatrix}1 &amp; 0\\ 0 &amp; i\end{bmatrix}=\begin{bmatrix}1 &amp; 0\\ 0 &amp; 1\end{bmatrix}=I,\quad UU^\ast=\begin{bmatrix}1 &amp; 0\\ 0 &amp; i\end{bmatrix}\begin{bmatrix}1 &amp; 0\\ 0 &amp; -i\end{bmatrix}=I\]</span>
<p>在 AI 里，正交/酉矩阵常用于控制数值稳定性：例如正交初始化（Orthogonal Initialization）与正交/酉参数化可把谱范数压在 1 附近，缓解深层网络与 RNN 中的梯度爆炸/消失；一些长序列建模会使用 unitary/orthogonal RNN 来更好地传播长程信息。</p>
<div class="blog_h2"><span class="graybg">正定矩阵</span></div>
<p>正定矩阵（Positive Definite Matrix）把“二次型总是正”形式化。对对称矩阵（Symmetric Matrix）<span displaypfx="inline-" class="mathjax-container">\(A=A^\top\)</span>，若对任意非零向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\ne\mathbf{0}\)</span> 都有</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}^\top A\mathbf{x} &gt; 0\]</span>
<p>则称 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 正定，记作 <span displaypfx="inline-" class="mathjax-container">\(A\succ 0\)</span>。若是 <span displaypfx="inline-" class="mathjax-container">\(\ge 0\)</span> 则为半正定（Positive Semi-Definite, PSD），记作 <span displaypfx="inline-" class="mathjax-container">\(A\succeq 0\)</span>。</p>
<p>几何上，把 <span displaypfx="inline-" class="mathjax-container">\(q(x,y)=\mathbf{x}^\top A\mathbf{x}\)</span> 画成 <span displaypfx="inline-" class="mathjax-container">\(z=q(x,y)\)</span> 的三维曲面时，正定对应“向上开口的碗”（椭圆抛物面（Elliptic Paraboloid））：原点是唯一最低点，任意非零方向都往上抬升。若没有交叉项（<span displaypfx="inline-" class="mathjax-container">\(bxy\)</span> 项为 0），等值线与坐标轴对齐；若有交叉项（<span displaypfx="inline-" class="mathjax-container">\(b\ne 0\)</span>），碗的主轴会旋转，但“向上碗”的本质不变。半正定则可能出现平坦方向（Flat Direction），典型形状是槽（Trough）而非严格碗底。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/matrix-pd-psd.png"><img class="alignnone size-full wp-image-40695" src="https://blog.gmem.cc/wp-content/uploads/2026/03/matrix-pd-psd.png" alt="matrix-pd-psd" width="100%" /></a></p>
<p>从特征值（Eigenvalues）角度看，对称矩阵 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的二次型类型可直接由特征值符号判别（下述以二维 <span displaypfx="inline-" class="mathjax-container">\(\lambda_1,\lambda_2\)</span> 为例）：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\lambda_1&gt;0,\lambda_2&gt;0\)</span>：正定（Positive Definite, PD），向上开口碗。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\lambda_1&lt;0,\lambda_2&lt;0\)</span>：负定（Negative Definite, ND），向下开口碗。</li>
<li>一个为 0、另一个大于 0：半正定（Positive Semi-Definite, PSD），出现平坦方向，形状更像槽。</li>
<li>一个为 0、另一个小于 0：半负定（Negative Semi-Definite, NSD），对应“倒槽”。</li>
<li>一正一负：不定（Indefinite），对应鞍面（Saddle Surface）。</li>
</ul>
<p>因此图里“有交叉项”并不改变正负定类型；它主要改变主轴方向（旋转等值线），而“是不是碗/槽/鞍”由特征值符号决定。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/matrix-nd-nsd.png"><img class="alignnone size-full wp-image-40703" src="https://blog.gmem.cc/wp-content/uploads/2026/03/matrix-nd-nsd.png" alt="matrix-nd-nsd" width="100%" /></a></p>
<p>例：令</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}2 &amp; 1\\ 1 &amp; 2\end{bmatrix}\]</span>
<p>对任意 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(x_1,x_2)^\top\)</span>，有</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}^\top A\mathbf{x}=2x_1^2+2x_1x_2+2x_2^2=(x_1+x_2)^2+x_1^2+x_2^2&gt;0\quad (\mathbf{x}\ne\mathbf{0})\]</span>
<p>因此 <span displaypfx="inline-" class="mathjax-container">\(A\succ 0\)</span>。同时它的特征值为 3 与 1（均为正），并且存在 Cholesky 分解：</p>
<span displaypfx="" class="mathjax-container">\[A=LL^\top,\quad L=\begin{bmatrix}\sqrt{2} &amp; 0\\ \frac{1}{\sqrt{2}} &amp; \sqrt{\frac{3}{2}}\end{bmatrix}\]</span>
<p>等价刻画（常用）：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(A\succ 0\)</span> 当且仅当所有特征值 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i&gt;0\)</span>。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(A\succ 0\)</span> 当且仅当存在 Cholesky 分解 <span displaypfx="inline-" class="mathjax-container">\(A=LL^\top\)</span>（<span displaypfx="inline-" class="mathjax-container">\(L\)</span> 下三角且对角为正）。</li>
</ul>
<p>它在优化里非常关键：若函数的海森矩阵（Hessian）在某点正定，则该点是严格局部极小；若 Hessian 半正定，则函数局部凸（Locally Convex）。</p>
<div class="blog_h2"><span class="graybg">特征值与特征向量</span></div>
<p>特征值（Eigenvalue）与特征向量（Eigenvector）描述线性变换的“固有方向”：若存在非零向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\ne\mathbf{0}\)</span> 与标量 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 使得</p>
<span displaypfx="" class="mathjax-container">\[A\mathbf{v}=\lambda \mathbf{v}\]</span>
<p>则 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\)</span> 是特征向量，<span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 是对应特征值。几何上，沿特征向量方向的向量经过变换后方向不变，只被缩放（若 <span displaypfx="inline-" class="mathjax-container">\(\lambda&lt;0\)</span> 还会翻转）。</p>
<p>当矩阵可对角化（Diagonalizable）时，可写为 <span displaypfx="inline-" class="mathjax-container">\(A=V\Lambda V^{-1}\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(\Lambda\)</span> 是特征值对角矩阵，列向量 <span displaypfx="inline-" class="mathjax-container">\(V=[\mathbf{v}_1,\dots,\mathbf{v}_n]\)</span> 是特征向量。</p>
<p>对称矩阵（Symmetric Matrix）是最重要的特例：它的特征向量可取为一组正交归一基（Orthonormal Basis），因此</p>
<span displaypfx="" class="mathjax-container">\[A=Q\Lambda Q^\top,\quad Q^\top Q=I\]</span>
<p>这正是 PCA（Principal Component Analysis）等方法背后的谱分解（Spectral Decomposition）基础。</p>
<div class="blog_h3"><span class="graybg">几何直觉：哪些方向会被保留</span></div>
<p>只看公式 <span displaypfx="inline-" class="mathjax-container">\(A\mathbf{v}=\lambda \mathbf{v}\)</span> 仍然容易抽象。二维里更直观的看法是：把原点周围一个闭合形状的边界看成“很多向量的端点集合”。边界上的每个点 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\)</span> 都对应一根从原点出发的向量；施加线性变换 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 后，这个端点会被送到新位置 <span displaypfx="inline-" class="mathjax-container">\(A\mathbf{v}\)</span>，整个形状也随之被拉伸、压缩、剪切、翻折或旋转。</p>
<p>特征向量方向的判别标准正是：<span style="background-color: #c0c0c0;">变换后是否仍落在原来的那条直线上</span>。若某个非零向量满足 <span displaypfx="inline-" class="mathjax-container">\(A\mathbf{v}=\lambda\mathbf{v}\)</span>，那么 <span displaypfx="inline-" class="mathjax-container">\(A\mathbf{v}\)</span> 只是 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\)</span> 的倍数，它仍位于通过原点和 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\)</span> 的那条一维子空间上。因此，特征向量真正对应的是一个<span style="background-color: #c0c0c0;">被保留下来的方向</span>，而不是某个固定长度的箭头。</p>
<p>这里要把“方向”理解为过原点的一条直线，而不是有朝向的箭头。若 <span displaypfx="inline-" class="mathjax-container">\(\lambda&gt;0\)</span>，变换后的向量和原向量同向，只是被拉长或缩短；若 <span displaypfx="inline-" class="mathjax-container">\(\lambda&lt;0\)</span>，变换后会翻到反方向，但仍留在同一条直线上，因此它依然是特征方向；若 <span displaypfx="inline-" class="mathjax-container">\(\lambda=0\)</span>，该方向上的向量会被压到原点，这说明这条方向被完全消灭了，但它仍然对应一个特征值为 0 的特征方向。</p>
<p>很多常见变换都可以用这套语言重述：</p>
<ul>
<li><span style="background-color: #c0c0c0;">对角缩放（Diagonal Scaling）</span>：例如 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{diag}(2,0.5)\)</span>。坐标轴方向会被保留，因此 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 轴和 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 轴就是特征方向；对应特征值分别是 2 和 0.5。</li>
<li><span style="background-color: #c0c0c0;">对称耦合拉伸（Symmetric Coupled Stretch）</span>：矩阵含有非零交叉项时，保留下来的方向通常不再和原坐标轴对齐，而会旋转到主轴方向。这也是为什么对称矩阵和椭圆主轴、二次型标准型、PCA 主方向会连到一起。</li>
<li><span style="background-color: #c0c0c0;">剪切（Shear）</span>：例如 <span displaypfx="inline-" class="mathjax-container">\(\begin{bmatrix}1 &amp; 1\\ 0 &amp; 1\end{bmatrix}\)</span>。多数向量都会被“推斜”，通常只有少数方向真正保持不变；在这个例子里， <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 轴方向被保留，而绝大多数其他方向都不是特征方向。</li>
<li><span style="background-color: #c0c0c0;">投影（Projection）</span>：例如投影到 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 轴。被保留下来的子空间对应 <span displaypfx="inline-" class="mathjax-container">\(\lambda=1\)</span>，被压扁掉的方向对应 <span displaypfx="inline-" class="mathjax-container">\(\lambda=0\)</span>。</li>
<li><span style="background-color: #c0c0c0;">反射（Reflection）</span>：关于某条直线做镜像时，镜像轴本身是 <span displaypfx="inline-" class="mathjax-container">\(\lambda=1\)</span> 的特征方向；与它正交的法向方向会被翻过去，因此是 <span displaypfx="inline-" class="mathjax-container">\(\lambda=-1\)</span> 的特征方向。</li>
<li><span style="background-color: #c0c0c0;">纯旋转（Rotation）</span>：在实数平面中，只要旋转角度不是 <span displaypfx="inline-" class="mathjax-container">\(0\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(\pi\)</span>，就不存在非零向量在旋转后仍留在原来的直线上，因此没有实特征向量。二维纯旋转的特征值要到复数域里才会出现，形式是 <span displaypfx="inline-" class="mathjax-container">\(e^{\pm i\theta}\)</span>。</li>
</ul>
<p>因此，特征值和特征向量并不是“矩阵里凭空算出来的一组数字和向量”，而是在回答一个非常几何的问题：<span style="background-color: #c0c0c0;">这个线性变换到底保留了哪些一维方向，以及沿这些方向会放大、缩小、翻转还是压扁到 0</span>。后面讲特征空间、可对角化、谱分解时，本质都在把这种“被保留下来的方向结构”系统化。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/04/eigenvector-linear-maps.png"><img class="alignnone size-full wp-image-42333" src="https://blog.gmem.cc/wp-content/uploads/2026/04/eigenvector-linear-maps.png" alt="eigenvector-linear-maps" width="2089" height="1389" /></a></p>
<div class="blog_h3"><span class="graybg">特征空间（Eigenspace）</span></div>
<p>单个特征向量只是一条方向；特征空间（Eigenspace）则把<span style="background-color: #c0c0c0;">所有属于同一特征值的特征向量连同零向量一起</span>统一起来。对特征值 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span>，其特征空间定义为</p>
<span displaypfx="" class="mathjax-container">\[E_\lambda(A)=\ker(A-\lambda I)=\{\mathbf{v}\in\mathbb{R}^n\mid A\mathbf{v}=\lambda \mathbf{v}\}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\ker(A-\lambda I)\)</span> 表示核空间（Kernel / Null Space）。定义的含义很直接：凡是被 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 作用后只发生 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 倍缩放的向量，全都落在同一个特征空间里。由于核空间本身是线性子空间，因此特征空间天然是一个子空间，而不是若干零散向量的集合。</p>
<p>这件事的线性结构很重要。若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}_1,\mathbf{v}_2\in E_\lambda(A)\)</span>，且 <span displaypfx="inline-" class="mathjax-container">\(c_1,c_2\)</span> 是任意标量，则</p>
<span displaypfx="" class="mathjax-container">\[A(c_1\mathbf{v}_1+c_2\mathbf{v}_2)=c_1A\mathbf{v}_1+c_2A\mathbf{v}_2=c_1\lambda\mathbf{v}_1+c_2\lambda\mathbf{v}_2=\lambda(c_1\mathbf{v}_1+c_2\mathbf{v}_2)\]</span>
<p>因此同一特征值对应的任意线性组合，仍然属于同一个特征空间。这就是为什么当某个特征值有多个线性无关特征向量时，不会把它们看成彼此无关的点，而是整体看成一个方向簇所张成的子空间。</p>
<p>例：令</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}2 &amp; 0\\ 0 &amp; 2\end{bmatrix}=2I\]</span>
<p>则对任意向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\in\mathbb{R}^2\)</span>，都有 <span displaypfx="inline-" class="mathjax-container">\(A\mathbf{v}=2\mathbf{v}\)</span>。因此特征值 <span displaypfx="inline-" class="mathjax-container">\(\lambda=2\)</span> 的特征空间不是一条线，而是整个平面 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^2\)</span>。这说明：同一个特征值可以对应一个高维子空间。</p>
<p>再看另一个例子。若</p>
<span displaypfx="" class="mathjax-container">\[A=\begin{bmatrix}3 &amp; 1\\ 0 &amp; 3\end{bmatrix}\]</span>
<p>则唯一特征值是 <span displaypfx="inline-" class="mathjax-container">\(3\)</span>，但</p>
<span displaypfx="" class="mathjax-container">\[A-3I=\begin{bmatrix}0 &amp; 1\\ 0 &amp; 0\end{bmatrix}\]</span>
<p>它的核空间只有形如 <span displaypfx="inline-" class="mathjax-container">\((x,0)^\top\)</span> 的向量，因此特征空间是一维的，而不是二维。这意味着虽然特征值按代数重数（Algebraic Multiplicity）出现了两次，但线性无关特征向量只有一个，矩阵就不可对角化。这里暴露出的关键区别是：</p>
<ul>
<li>代数重数（Algebraic Multiplicity）：<span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 作为特征多项式根出现的次数。</li>
<li>几何重数（Geometric Multiplicity）：特征空间 <span displaypfx="inline-" class="mathjax-container">\(E_\lambda(A)\)</span> 的维数，也就是线性无关特征向量的个数。</li>
</ul>
<p>对任一特征值 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span>，总有 <span displaypfx="inline-" class="mathjax-container">\(1\le \dim E_\lambda(A)\le m_\lambda\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(m_\lambda\)</span> 表示 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 的代数重数。矩阵可对角化，当且仅当所有特征空间的维数加起来达到 <span displaypfx="inline-" class="mathjax-container">\(n\)</span>，也就是能从这些特征空间里取出一组基，铺满整个空间。</p>
<p>从几何上看，特征空间给出的不是“某一个幸运方向”，而是一整块在变换下保持不变的空间（Invariant Subspace）：若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\in E_\lambda(A)\)</span>，那么 <span displaypfx="inline-" class="mathjax-container">\(A\mathbf{v}\)</span> 仍留在这个子空间内。对称矩阵的优势再次体现在这里：不同特征值对应的特征空间彼此正交，因此整个空间可以被分解为若干互相独立的主方向子空间。这也是谱分解、PCA 和二次型主轴分析能如此清晰的根本原因。</p>
<div class="blog_h3"><span class="graybg">特征值和秩</span></div>
<p>特征值（Eigenvalue）与秩（Rank）之间的关系，核心在于 <span displaypfx="inline-" class="mathjax-container">\(\lambda=0\)</span> 这个特征值。因为</p>
<span displaypfx="" class="mathjax-container">\[A\mathbf{v}=0\cdot \mathbf{v}=\mathbf{0}\]</span>
<p>说明：<span style="background-color: #c0c0c0;">0 是特征值，当且仅当存在非零向量被 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 压到 0</span>。而这正是核空间（Kernel / Null Space）非平凡的条件，也就是矩阵丢失维度、不能满秩、不可逆的根源。</p>
<p>对方阵 <span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{n\times n}\)</span>，最稳定的一条结论是：</p>
<span displaypfx="" class="mathjax-container">\[A\ \text{满秩}\ \Longleftrightarrow\ \mathrm{rank}(A)=n\ \Longleftrightarrow\ \det(A)\ne 0\ \Longleftrightarrow\ 0\notin \sigma(A)\]</span>
<p>因此，若一个方阵满秩，那么它的所有特征值都非零；反过来，只要 0 是特征值，矩阵就一定不是满秩。这条结论并不要求矩阵可对角化，对任意方阵都成立。</p>
<p>若再加上“可对角化（Diagonalizable）”这个前提，秩与特征值个数之间就能写得更直接。设</p>
<span displaypfx="" class="mathjax-container">\[A=P\Lambda P^{-1},\quad \Lambda=\mathrm{diag}(\lambda_1,\dots,\lambda_n)\]</span>
<p>因为可逆变换 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(P^{-1}\)</span> 不会改变秩，所以</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{rank}(A)=\mathrm{rank}(\Lambda)\]</span>
<p>而对角矩阵的秩最容易读：它恰好等于非零对角元素的个数。因此在可对角化情形下，</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{rank}(A)=\#\{i\mid \lambda_i\ne 0\}\]</span>
<p>这里计数的是按代数重数（Algebraic Multiplicity）展开后的特征值个数。也就是说，<span style="background-color: #c0c0c0;">对可对角化方阵，秩就是非零特征值的数量，零特征值的数量则等于亏格（Nullity）</span>。</p>
<p>例：若</p>
<span displaypfx="" class="mathjax-container">\[\Lambda=\mathrm{diag}(5,2,0,0)\]</span>
<p>则 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{rank}(\Lambda)=2\)</span>。任何与它相似且可对角化的矩阵 <span displaypfx="inline-" class="mathjax-container">\(A=P\Lambda P^{-1}\)</span>，秩也都是 2，因为其中只有两个非零特征值。</p>
<p>这个结论在不可对角化时不能直接照搬。例：令</p>
<span displaypfx="" class="mathjax-container">\[J=\begin{bmatrix}0 &amp; 1\\ 0 &amp; 0\end{bmatrix}\]</span>
<p>它唯一的特征值是 0，但 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{rank}(J)=1\)</span>。原因在于：虽然 0 是唯一特征值，但这个矩阵含有非平凡 Jordan 块（Jordan Block），它仍然会把某些向量送到非零方向。因此“秩等于非零特征值个数”必须以可对角化为前提；对一般方阵，只能稳妥地使用“满秩当且仅当 0 不是特征值”这条判据。</p>
<p>从线性映射角度看，这组关系也很自然。非零特征值对应的特征方向会被保留下来，只是被缩放；0 特征值对应的方向会被压扁到更低维空间。矩阵秩衡量的正是“最终还能保留下来的独立方向数目”，因此它会和 0 特征值是否存在、以及存在多少，在可对角化情形下发生直接对应。</p>
<div class="blog_h2"><span class="graybg">谱、谱定理与谱范数</span></div>
<div class="blog_h3"><span class="graybg">谱（Spectrum）</span></div>
<p>矩阵的谱（Spectrum）指的是矩阵全部特征值构成的集合。对方阵 <span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{C}^{n\times n}\)</span>，常记为 <span displaypfx="inline-" class="mathjax-container">\(\sigma(A)\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\sigma(A)=\{\lambda\in\mathbb{C}\mid \det(A-\lambda I)=0\}\]</span>
<p>这里之所以会把行列式（Determinant）扯进来，是因为特征值定义本身就会自然导向这个条件。若 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 是特征值，按定义必须存在某个非零向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\ne \mathbf{0}\)</span> 使得</p>
<span displaypfx="" class="mathjax-container">\[A\mathbf{v}=\lambda \mathbf{v}\]</span>
<p>把右边移到左边，就是</p>
<span displaypfx="" class="mathjax-container">\[(A-\lambda I)\mathbf{v}=\mathbf{0}\]</span>
<p>这说明：矩阵 <span displaypfx="inline-" class="mathjax-container">\(A-\lambda I\)</span> 对应的齐次线性方程组必须有<span style="background-color: #c0c0c0;">非零解</span>。而线性代数里，一个方阵齐次方程组有非零解，当且仅当该矩阵不可逆；对方阵而言，不可逆又等价于行列式为 0。因此就得到</p>
<span displaypfx="" class="mathjax-container">\[\det(A-\lambda I)=0\]</span>
<p>也就是说，行列式在这里不是额外硬塞进来的工具，而是“特征向量存在非零解”这一要求的等价写法。解这个方程得到的多项式根，就是矩阵的全部特征值。</p>
<p>这个定义的关键不是“列出所有特征值”本身，而是把矩阵最核心的线性作用浓缩成一组标量。许多稳定性、可逆性、收敛性与几何性质，最终都可以回到谱上来判断。例：若 <span displaypfx="inline-" class="mathjax-container">\(0\in \sigma(A)\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 不可逆；若所有特征值都为正，且 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 对称，则它是正定矩阵。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/spectrum.png"><img class="alignnone size-full wp-image-42063" src="https://blog.gmem.cc/wp-content/uploads/2026/03/spectrum.png" alt="spectrum" width="1024" height="1024" /></a></p>
<p>与谱紧密相关、但不能混为一谈的另一个概念是谱半径（Spectral Radius）：</p>
<span displaypfx="" class="mathjax-container">\[\rho(A)=\max_{\lambda\in\sigma(A)}|\lambda|\]</span>
<p>它只取特征值模长中的最大者。谱是整个特征值集合，谱半径只是其中的最大模长摘要。在线性迭代、RNN 稳定性与幂法（Power Method）里，谱半径尤其常见。</p>
<div class="blog_h3"><span class="graybg">谱定理（Spectral Theorem）</span></div>
<p>谱定理（Spectral Theorem）是线性代数里最重要的结构定理之一。对实对称矩阵 <span displaypfx="inline-" class="mathjax-container">\(A=A^\top\)</span>，存在正交矩阵 <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 与实对角矩阵 <span displaypfx="inline-" class="mathjax-container">\(\Lambda\)</span>，使得</p>
<span displaypfx="" class="mathjax-container">\[A=Q\Lambda Q^\top\]</span>
<p>等价地说， <span displaypfx="inline-" class="mathjax-container">\(Q\)</span> 的列向量是一组两两正交的单位特征向量， <span displaypfx="inline-" class="mathjax-container">\(\Lambda\)</span> 的对角线上放着对应特征值。这条定理的含义非常深：对称矩阵不仅“有特征值”，而且它的全部作用都可以被解释为<span style="background-color: #c0c0c0;">先换到特征向量基，再按各坐标轴独立缩放</span>。这也是为什么二次型、协方差矩阵、Hessian、图 Laplacian 等对象一旦对称，分析就会变得异常清爽。</p>
<p>在复数域上，对应版本是厄米矩阵（Hermitian Matrix）<span displaypfx="inline-" class="mathjax-container">\(A=A^\ast\)</span> 可被酉矩阵（Unitary Matrix）对角化：</p>
<span displaypfx="" class="mathjax-container">\[A=U\Lambda U^\ast\]</span>
<p>这类“可由一组正交 / 酉基完全分解”的矩阵，在数值上更稳定、几何上更透明，也正因此成为机器学习里最重要的一类矩阵对象。</p>
<div class="blog_h3"><span class="graybg">谱范数（Spectral Norm）</span></div>
<p>谱范数（Spectral Norm）记作 <span displaypfx="inline-" class="mathjax-container">\(\|A\|_2\)</span>，定义为矩阵对单位向量能造成的最大放大倍数：</p>
<span displaypfx="" class="mathjax-container">\[\|A\|_2=\max_{\|\mathbf{x}\|_2=1}\|A\mathbf{x}\|_2\]</span>
<p>这一定义直接揭示了它的几何意义：找出所有长度为 1 的输入向量，看矩阵 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 最多能把哪一个方向拉得最长。对任意矩阵，都有</p>
<span displaypfx="" class="mathjax-container">\[\|A\|_2=\sigma_{\max}(A)=\sqrt{\lambda_{\max}(A^\top A)}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\sigma_{\max}(A)\)</span> 是最大奇异值。若 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 还是实对称矩阵，则奇异值等于特征值绝对值，于是谱范数进一步化为</p>
<span displaypfx="" class="mathjax-container">\[\|A\|_2=\max_i |\lambda_i(A)|\]</span>
<p>因此，对一般矩阵要通过奇异值来理解谱范数；对对称矩阵，则可以直接通过特征值来理解。要特别区分：谱范数与谱半径在对称 / 正规矩阵上常常一致，但对一般非正规矩阵并不总相同。</p>
<p>在 AI 中，谱范数最常用于表达“局部放大能力”与 Lipschitz 常数控制。若某层线性映射的谱范数过大，它会把某些方向的扰动显著放大，进而加重训练不稳定、梯度爆炸或对抗脆弱性；这也是谱归一化（Spectral Normalization）、正交初始化与某些鲁棒优化方法反复关注它的原因。</p>
<div class="blog_h2"><span class="graybg">奇异值分解（SVD）</span></div>
<p>奇异值分解（SVD, Singular Value Decomposition）对任意矩阵 <span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{m\times n}\)</span> 都成立：</p>
<span displaypfx="" class="mathjax-container">\[A=U\Sigma V^\top\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(U\in\mathbb{R}^{m\times m}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(V\in\mathbb{R}^{n\times n}\)</span> 是正交矩阵（Orthogonal Matrix），<span displaypfx="inline-" class="mathjax-container">\(\Sigma\in\mathbb{R}^{m\times n}\)</span> 是对角（更准确说是“对角形”）矩阵，其对角线元素 <span displaypfx="inline-" class="mathjax-container">\(\sigma_1\ge\sigma_2\ge\cdots\ge 0\)</span> 为奇异值（Singular Values）。</p>
<p>几何解释非常直接：<span style="background-color: #c0c0c0;">先用 <span displaypfx="inline-" class="mathjax-container">\(V^\top\)</span> 旋转/换基，再用 <span displaypfx="inline-" class="mathjax-container">\(\Sigma\)</span> 沿坐标轴缩放，最后用 <span displaypfx="inline-" class="mathjax-container">\(U\)</span> 再旋转</span>。因此 SVD 是“旋转-缩放-旋转”的标准分解。</p>
<p>SVD 与特征值的关系要分左右两侧一起看：</p>
<ul>
<li>右奇异向量（Right Singular Vectors）是 <span displaypfx="inline-" class="mathjax-container">\(A^\top A\)</span> 的特征向量，即 <span displaypfx="inline-" class="mathjax-container">\(A^\top A\mathbf{v}_i=\sigma_i^2\mathbf{v}_i\)</span>。</li>
<li>左奇异向量（Left Singular Vectors）是 <span displaypfx="inline-" class="mathjax-container">\(AA^\top\)</span> 的特征向量，即 <span displaypfx="inline-" class="mathjax-container">\(AA^\top\mathbf{u}_i=\sigma_i^2\mathbf{u}_i\)</span>。</li>
</ul>
<p>其中<span displaypfx="inline-" class="mathjax-container">\(\sigma_i^2\)</span> 是 <span displaypfx="inline-" class="mathjax-container">\(A^\top A\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(AA^\top\)</span> 的特征值；<span displaypfx="inline-" class="mathjax-container">\(\sigma_i\)</span> 为奇异值（即这些非零特征值的平方根）。</p>
<p>矩阵形式分别是 <span displaypfx="inline-" class="mathjax-container">\(A^\top A=V\Sigma^\top\Sigma V^\top\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(AA^\top=U\Sigma\Sigma^\top U^\top\)</span>，因此非零奇异值满足 <span displaypfx="inline-" class="mathjax-container">\(\sigma_i=\sqrt{\lambda_i(A^\top A)}=\sqrt{\lambda_i(AA^\top)}\)</span>。这也解释了为什么奇异值总是非负，而一般矩阵的特征值可以为负甚至为复数。</p>
<p>若 <span displaypfx="inline-" class="mathjax-container">\(\sigma_i&gt;0\)</span>，左右奇异向量还可互相对应： <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}_i=\frac{A\mathbf{v}_i}{\sigma_i}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}_i=\frac{A^\top\mathbf{u}_i}{\sigma_i}\)</span>。</p>
<p>特殊地，若 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 是对称矩阵，则它可正交对角化，此时奇异值等于特征值的绝对值：<span displaypfx="inline-" class="mathjax-container">\(\sigma_i=|\lambda_i(A)|\)</span>；若 <span displaypfx="inline-" class="mathjax-container">\(A\succeq 0\)</span>（半正定），则 <span displaypfx="inline-" class="mathjax-container">\(\sigma_i=\lambda_i(A)\)</span>。</p>
<p>为什么 SVD 可用于压缩与降维（Compression &amp; Dimensionality Reduction）？因为很多数据/权重矩阵在有效意义下是低秩（Low-rank）的：只有前几个 <span displaypfx="inline-" class="mathjax-container">\(\sigma_i\)</span> 很大，后面的奇异值接近 0。保留前 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 项得到秩 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 近似：</p>
<span displaypfx="" class="mathjax-container">\[A_k=U_{(:,1:k)}\Sigma_{(1:k,1:k)}V_{(:,1:k)}^\top\]</span>
<p>它在 Frobenius 范数（Frobenius Norm）与谱范数（Spectral Norm）意义下都是最优的秩 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 近似（Eckart–Young 定理）：用最少的信息保留最大的能量（由奇异值平方决定）。</p>
<p>在 PCA 中，对中心化数据矩阵 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 做 SVD： <span displaypfx="inline-" class="mathjax-container">\(X=U\Sigma V^\top\)</span>，右奇异向量 <span displaypfx="inline-" class="mathjax-container">\(V\)</span> 给出主方向（Principal Directions），奇异值刻画各方向的方差贡献（Variance Explained）。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/svd.png"><img class="alignnone size-full wp-image-42053" src="https://blog.gmem.cc/wp-content/uploads/2026/03/svd.png" alt="svd" width="1024" height="1024" /></a></p>
<div class="blog_h2"><span class="graybg">范数（L0 / L1 / L2 / L∞）</span></div>
<p>范数（Norm）刻画“向量大小”。在 AI 中，它最常出现在三类地方：距离度量（Distance Metric）、正则化（Regularization）与鲁棒性约束（Robustness Constraint）。对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\in\mathbb{R}^d\)</span>，当 <span displaypfx="inline-" class="mathjax-container">\(p\ge 1\)</span> 时 <span displaypfx="inline-" class="mathjax-container">\(L_p\)</span> 范数定义为</p>
<span displaypfx="" class="mathjax-container">\[\|\mathbf{x}\|_p=\left(\sum_{i=1}^{d}|x_i|^p\right)^{1/p},\quad p\ge 1\]</span>
<p>并且 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{x}\|_\infty=\max_i |x_i|=\lim_{p\to\infty}\|\mathbf{x}\|_p\)</span>。</p>
<div class="blog_h3"><span class="graybg">L0 “范数”（L0 “norm”）</span></div>
<p><span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{x}\|_0\)</span> 定义为非零分量的个数：</p>
<span displaypfx="" class="mathjax-container">\[\|\mathbf{x}\|_0=\#\{i\mid x_i\ne 0\}\]</span>
<p>严格来说它不是范数：例如对任意非零标量 <span displaypfx="inline-" class="mathjax-container">\(\alpha\ne 0\)</span>，有 <span displaypfx="inline-" class="mathjax-container">\(\|\alpha\mathbf{x}\|_0=\|\mathbf{x}\|_0\)</span>，不满足齐次性（Homogeneity）<span displaypfx="inline-" class="mathjax-container">\(\|\alpha\mathbf{x}\|=|\alpha|\|\mathbf{x}\|\)</span>。</p>
<p>例：若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(3,0,-1,0)\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{x}\|_0=2\)</span>。</p>
<p>在 AI 里，<span displaypfx="inline-" class="mathjax-container">\(\|\cdot\|_0\)</span> 用来表达稀疏性（Sparsity）：特征选择（Feature Selection）、压缩感知（Compressed Sensing）与网络剪枝（Pruning）常以“非零个数最少”为目标。但直接优化 <span displaypfx="inline-" class="mathjax-container">\(\|\cdot\|_0\)</span> 一般是组合优化，常用 <span displaypfx="inline-" class="mathjax-container">\(\|\cdot\|_1\)</span> 或其他可优化的替代目标近似。</p>
<div class="blog_h3"><span class="graybg">L1 范数（L1 Norm）</span></div>
<span displaypfx="" class="mathjax-container">\[\|\mathbf{x}\|_1=\sum_{i=1}^{d}|x_i|\]</span>
<p>例：若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(3,4)\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{x}\|_1=7\)</span>。几何上，二维 <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 等值线是菱形；与 <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 的圆相比，它更容易在坐标轴上产生“尖角”，对应优化时更容易把部分坐标推到 0。</p>
<p>在 AI 里，<span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 正则化是稀疏学习（Sparse Learning）的标准工具：在凸模型中会得到稀疏解（例如 Lasso）；在深度模型中也常用于诱导稀疏权重/稀疏特征、做轻量化。</p>
<div class="blog_h3"><span class="graybg">L2 范数（L2 Norm）</span></div>
<span displaypfx="" class="mathjax-container">\[\|\mathbf{x}\|_2=\sqrt{\sum_{i=1}^{d}x_i^2}\]</span>
<p>例：若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(3,4)\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{x}\|_2=5\)</span>，对应欧氏距离（Euclidean Distance）。在连续优化中，<span displaypfx="inline-" class="mathjax-container">\(\|\theta\|_2^2\)</span> 具有良好的光滑性（Smoothness），使得许多推导与数值计算更稳定。</p>
<p>在 AI 里，<span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 正则化（权重衰减（Weight Decay））会惩罚大权重、改善泛化并缓解病态问题；在二次目标里它也对应“加 <span displaypfx="inline-" class="mathjax-container">\(\lambda I\)</span>”的稳定化（例如岭回归）。</p>
<div class="blog_h3"><span class="graybg">L∞ 范数（L-infinity Norm）</span></div>
<span displaypfx="" class="mathjax-container">\[\|\mathbf{x}\|_\infty=\max_{i}|x_i|\]</span>
<p>例：若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(3,0,-1,0)\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{x}\|_\infty=3\)</span>。它度量的是“最大坐标幅度”。</p>
<p>在 AI 里，<span displaypfx="inline-" class="mathjax-container">\(L_\infty\)</span> 最常与鲁棒性相关：对抗样本（Adversarial Examples）中的 <span displaypfx="inline-" class="mathjax-container">\(L_\infty\)</span> 约束表示“每个像素的改动幅度不超过 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span>”；一些鲁棒优化与最坏情况界也会用 <span displaypfx="inline-" class="mathjax-container">\(\|\cdot\|_\infty\)</span> 表达最大误差。</p>
<div class="blog_h3"><span class="graybg">距离与正则化</span></div>
<p>由范数诱导的距离（Norm-induced Distance）写作 <span displaypfx="inline-" class="mathjax-container">\(d(\mathbf{x},\mathbf{y})=\|\mathbf{x}-\mathbf{y}\|\)</span>。常见对应关系：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(L_2\)</span>：欧氏距离（Euclidean Distance），<span displaypfx="inline-" class="mathjax-container">\(d_2(\mathbf{x},\mathbf{y})=\|\mathbf{x}-\mathbf{y}\|_2=\sqrt{\sum_{i=1}^{d}(x_i-y_i)^2}\)</span>。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(L_1\)</span>：曼哈顿距离（Manhattan Distance），<span displaypfx="inline-" class="mathjax-container">\(d_1(\mathbf{x},\mathbf{y})=\|\mathbf{x}-\mathbf{y}\|_1=\sum_{i=1}^{d}|x_i-y_i|\)</span>。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(L_\infty\)</span>：切比雪夫距离（Chebyshev Distance），<span displaypfx="inline-" class="mathjax-container">\(d_\infty(\mathbf{x},\mathbf{y})=\|\mathbf{x}-\mathbf{y}\|_\infty=\max_{i}|x_i-y_i|\)</span>。它度量的是“最坏维度”的偏差：例如 <span displaypfx="inline-" class="mathjax-container">\(d_\infty(\mathbf{x},\mathbf{y})\le \epsilon \Leftrightarrow \forall i,\ |x_i-y_i|\le \epsilon\)</span>，即每一维的误差都被同一个上界约束。直觉上，如果一次操作允许同时修改所有坐标、且每步每个坐标最多改 1，那么从 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 变到 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span> 的最少步数就是 <span displaypfx="inline-" class="mathjax-container">\(\max_i|x_i-y_i|\)</span>（更一般地，每步上限为 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 时步数为 <span displaypfx="inline-" class="mathjax-container">\(d_\infty(\mathbf{x},\mathbf{y})/\epsilon\)</span> 的向上取整）。</li>
</ul>
<p>关于 <span displaypfx="inline-" class="mathjax-container">\(L_0\)</span>：常见写法是 <span displaypfx="inline-" class="mathjax-container">\(d_0(\mathbf{x},\mathbf{y})=\|\mathbf{x}-\mathbf{y}\|_0=\#\{i\mid x_i\ne y_i\}\)</span>，它统计两向量在多少个坐标上不相等。本质上这是逐坐标“相等/不相等”的计数度量；当取值来自离散集合时，它对应哈明距离（Hamming Distance）。但 <span displaypfx="inline-" class="mathjax-container">\(\|\cdot\|_0\)</span> 严格来说不是范数，因此 <span displaypfx="inline-" class="mathjax-container">\(d_0\)</span> 不属于“由范数诱导”的距离家族。</p>
<p>在学习目标中，正则化通常写成：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\theta}\ \frac{1}{m}\sum_{i=1}^{m}\ell\!\left(f_{\theta}(x^{(i)}),y^{(i)}\right)+\lambda\Omega(\theta)\]</span>
<p>式中各成分含义如下：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>：模型参数（Parameters），优化的对象。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(m\)</span>：训练样本数（Number of Samples）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\((x^{(i)},y^{(i)})\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个样本与标签。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(f_{\theta}(x^{(i)})\)</span>：模型对第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个样本的预测。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\ell(\cdot,\cdot)\)</span>：单样本损失函数（Per-sample Loss），衡量预测与标签偏差。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\frac{1}{m}\sum_{i=1}^{m}\ell(\cdot)\)</span>：经验风险（Empirical Risk），即平均训练误差。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\Omega(\theta)\)</span>：正则项（Regularizer），约束参数复杂度。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span>：正则化系数（Regularization Strength），平衡“拟合训练数据”与“控制模型复杂度”。</li>
</ul>
<p>常见的 <span displaypfx="inline-" class="mathjax-container">\(\Omega(\theta)\)</span> 包括 <span displaypfx="inline-" class="mathjax-container">\(\|\theta\|_1\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\|\theta\|_2^2\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\|\theta\|_0\)</span>（稀疏性目标）与 <span displaypfx="inline-" class="mathjax-container">\(\|\theta\|_\infty\)</span>（最大幅度约束）。同一个思想也可写成“约束形式”（Constraint Form）：<span displaypfx="inline-" class="mathjax-container">\(\min_\theta \frac{1}{m}\sum_i \ell(\cdot)\ \text{s.t.}\ \Omega(\theta)\le c\)</span>。惩罚系数 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 与约束半径 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 在凸优化（Convex Optimization）里可通过对偶（Duality）联系起来。</p>
<div class="blog_h1"><span class="graybg">微积分</span></div>
<div class="blog_h2"><span class="graybg">极限与连续</span></div>
<div class="blog_h3"><span class="graybg">极限的定义</span></div>
<p>极限（Limit）回答的问题是：当输入“逼近”某个值时，函数输出“逼近”什么值。它关心的是趋势而不是是否刚好取到该点。</p>
<span displaypfx="" class="mathjax-container">\[\lim_{x\to a}f(x)=L\]</span>
<p>严格定义（<span displaypfx="inline-" class="mathjax-container">\(\varepsilon-\delta\)</span> 定义）可写为：</p>
<span displaypfx="" class="mathjax-container">\[\forall \varepsilon \gt 0,\ \exists \delta \gt 0,\ \text{s.t.}\ 0 \lt |x-a| \lt \delta \Rightarrow |f(x)-L| \lt \varepsilon\]</span>
<p>工程上可把它理解为：把输入控制得足够近，输出误差就能被压到任意小。</p>
<p>在 AI 里，极限直觉用于理解“收敛（Convergence）”：例如训练步长变小后，参数更新是否趋于稳定；以及损失函数在某点附近是否可被低阶展开近似。</p>
<div class="blog_h3"><span class="graybg">连续性</span></div>
<p>连续（Continuity）可理解为“函数图像没有跳断”。在点 <span displaypfx="inline-" class="mathjax-container">\(a\)</span> 处连续的三个条件是：</p>
<ol>
<li><span displaypfx="inline-" class="mathjax-container">\(f(a)\)</span> 有定义；</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\lim_{x\to a}f(x)\)</span> 存在；</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\lim_{x\to a}f(x)=f(a)\)</span>。</li>
</ol>
<p>连续是可导（Differentiable）的前提之一（但连续不必然可导）。例如 <span displaypfx="inline-" class="mathjax-container">\(|x|\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\(x=0\)</span> 连续但不可导。</p>
<p>在优化里，连续性保证“小步更新不会导致目标突变”，这也是学习率（Learning Rate）可调与训练可控的基础假设之一。</p>
<div class="blog_h3"><span class="graybg">无穷小与无穷大</span></div>
<p>无穷小（Infinitesimal）描述“趋近于 0 的量”，无穷大（Infinity）描述“无界增长”。在推导里常通过渐近记号（Asymptotic Notation）表达量级关系：</p>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(o\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(O\)</span> 不是变量，而是两种记号：小 <span displaypfx="inline-" class="mathjax-container">\(o\)</span>（little-o）表示“严格更小一个量级”，大 <span displaypfx="inline-" class="mathjax-container">\(O\)</span>（big-O）表示“至多同量级的上界”。</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(f(x)=o(g(x))\)</span>：当 <span displaypfx="inline-" class="mathjax-container">\(x\to a\)</span> 时 <span displaypfx="inline-" class="mathjax-container">\(f/g\to 0\)</span>（高阶小量，增长/衰减速度严格慢于 <span displaypfx="inline-" class="mathjax-container">\(g\)</span>）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(f(x)=O(g(x))\)</span>：存在常数 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 使 <span displaypfx="inline-" class="mathjax-container">\(|f(x)|\le C|g(x)|\)</span>（同阶或更小的上界）。</li>
</ul>
<p>例如一阶 Taylor 展开里的 <span displaypfx="inline-" class="mathjax-container">\(o(\Delta x)\)</span> 表示“比 <span displaypfx="inline-" class="mathjax-container">\(\Delta x\)</span> 更小得多”的误差项。算法分析中的时间复杂度 <span displaypfx="inline-" class="mathjax-container">\(O(Nd)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(O(N^2)\)</span> 也属于同一套量级语言。</p>
<div class="blog_h2"><span class="graybg">常见求导法则与公式</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">法则</td>
<td style="text-align: center;">公式</td>
<td style="text-align: center;">条件</td>
</tr>
</thead>
<tbody>
<tr>
<td>常数倍法则</td>
<td><span displaypfx="inline-" class="mathjax-container">\((Cu)' = C u'\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(C\)</span> 为常数</td>
</tr>
<tr>
<td>和差法则</td>
<td><span displaypfx="inline-" class="mathjax-container">\((u \pm v)' = u' \pm v'\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(u,v\)</span> 可导</td>
</tr>
<tr>
<td>乘法法则</td>
<td><span displaypfx="inline-" class="mathjax-container">\((uv)' = u'v + uv'\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(u,v\)</span> 可导</td>
</tr>
<tr>
<td>除法法则</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\left(\frac{u}{v}\right)'=\frac{u'v-uv'}{v^2}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(u,v\)</span> 可导，且 <span displaypfx="inline-" class="mathjax-container">\(v \ne 0\)</span></td>
</tr>
<tr>
<td>链式法则</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{d}{dx}f(g(x)) = f'(g(x))g'(x)\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(f,g\)</span> 可导</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">常见函数导数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">函数 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span></td>
<td style="text-align: center;">导数 <span displaypfx="inline-" class="mathjax-container">\(f'(x)\)</span></td>
<td style="text-align: center;">备注/条件</td>
</tr>
</thead>
<tbody>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(c\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(0\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(c\)</span> 为常数</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(x^n\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(n x^{n-1}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(n\)</span> 为常数；非整数 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 时需注意实数域定义域</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\sqrt{x}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{1}{2\sqrt{x}}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(x&gt;0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(e^x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(e^x\)</span></td>
<td>自然指数（Natural Exponential）</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(a^x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a^x\ln a\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(a&gt;0,a\ne 1\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\ln x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{1}{x}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(x&gt;0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\log_a x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{1}{x\ln a}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(x&gt;0,\ a&gt;0,\ a\ne 1\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\sin x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\cos x\)</span></td>
<td> </td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\cos x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(-\sin x\)</span></td>
<td> </td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\tan x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sec^2 x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\cos x\ne 0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(|x|\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{sign}(x)\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(x&gt;0\)</span> 时导数为 <span displaypfx="inline-" class="mathjax-container">\(1\)</span></p>
<p><span displaypfx="inline-" class="mathjax-container">\(x&lt;0\)</span> 时导数为 <span displaypfx="inline-" class="mathjax-container">\(-1\)</span></p>
<p>在 <span displaypfx="inline-" class="mathjax-container">\(x=0\)</span> 不可导（优化中常用次梯度 <span displaypfx="inline-" class="mathjax-container">\(g\in[-1,1]\)</span>）</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">导数（Derivative）</span></div>
<p>导数是点 <span displaypfx="inline-" class="mathjax-container">\(x_0\)</span> 处的瞬时变化率（Instantaneous Rate of Change）：</p>
<span displaypfx="" class="mathjax-container">\[f'(x_0)=\lim_{\Delta x\to 0}\frac{f(x_0+\Delta x)-f(x_0)}{\Delta x}\]</span>
<p><span displaypfx="inline-" class="mathjax-container">\(x_0\)</span> 不是“很小的数”，而是定义域中的固定位置；趋近于 0 的小量是 <span displaypfx="inline-" class="mathjax-container">\(\Delta x\)</span>（或记作 <span displaypfx="inline-" class="mathjax-container">\(dx\)</span>）。</p>
<p>一阶展开把导数解释为“线性项的系数”：当 <span displaypfx="inline-" class="mathjax-container">\(\Delta x\to 0\)</span> 时，</p>
<span displaypfx="" class="mathjax-container">\[f(x_0+\Delta x)=f(x_0)+f'(x_0)\Delta x+o(\Delta x)\]</span>
<p>可按“等于三项相加”理解：左边 <span displaypfx="inline-" class="mathjax-container">\(f(x_0+\Delta x)\)</span> 是扰动后的真实函数值；右边第一项 <span displaypfx="inline-" class="mathjax-container">\(f(x_0)\)</span> 是基点函数值，第二项 <span displaypfx="inline-" class="mathjax-container">\(f'(x_0)\Delta x\)</span> 是一阶线性近似，第三项 <span displaypfx="inline-" class="mathjax-container">\(o(\Delta x)\)</span> 是比 <span displaypfx="inline-" class="mathjax-container">\(\Delta x\)</span> 更小的高阶余项（High-order Remainder）。</p>
<div class="blog_h2"><span class="graybg">中值定理（Mean Value Theorems）</span></div>
<p>中值定理（Mean Value Theorems）是一组把“区间上的平均变化”与“某一点的瞬时变化率”连接起来的核心定理。它们在形式上不同，但共同结构是：在闭区间连续、在开区间可导，进而保证存在某个中间点 <span displaypfx="inline-" class="mathjax-container">\(c\in(a,b)\)</span> 使得斜率关系成立。</p>
<div class="blog_h3"><span class="graybg">罗尔中值定理</span></div>
<p>设 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\([a,b]\)</span> 上连续、在 <span displaypfx="inline-" class="mathjax-container">\((a,b)\)</span> 上可导，且 <span displaypfx="inline-" class="mathjax-container">\(f(a)=f(b)\)</span>。则存在 <span displaypfx="inline-" class="mathjax-container">\(c\in(a,b)\)</span> 使</p>
<span displaypfx="" class="mathjax-container">\[f'(c)=0\]</span>
<p>几何直觉：若曲线两端高度相同，那么中间至少有一点的切线是水平的。</p>
<div class="blog_h3"><span class="graybg">拉格朗日中值定理</span></div>
<p>设 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\([a,b]\)</span> 上连续、在 <span displaypfx="inline-" class="mathjax-container">\((a,b)\)</span> 上可导。则存在 <span displaypfx="inline-" class="mathjax-container">\(c\in(a,b)\)</span> 使</p>
<span displaypfx="" class="mathjax-container">\[f'(c)=\frac{f(b)-f(a)}{b-a}\]</span>
<p>几何直觉：区间割线斜率（平均变化率）等于某个点的切线斜率（瞬时变化率）。</p>
<div class="blog_h3"><span class="graybg">柯西中值定理</span></div>
<p>设 <span displaypfx="inline-" class="mathjax-container">\(f,g\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\([a,b]\)</span> 上连续、在 <span displaypfx="inline-" class="mathjax-container">\((a,b)\)</span> 上可导。则存在 <span displaypfx="inline-" class="mathjax-container">\(c\in(a,b)\)</span> 使</p>
<span displaypfx="" class="mathjax-container">\[(f(b)-f(a))g'(c)=(g(b)-g(a))f'(c)\]</span>
<p>当分母不为 0 时，可写成比值形式：</p>
<span displaypfx="" class="mathjax-container">\[\frac{f'(c)}{g'(c)}=\frac{f(b)-f(a)}{g(b)-g(a)}\]</span>
<p>它把“单函数斜率比较”推广为“两个函数变化率的比较”，是洛必达法则（L'Hospital's Rule）证明链中的关键步骤。</p>
<div class="blog_h3"><span class="graybg">三类中值定理的区别</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">定理</td>
<td style="text-align: center;">涉及函数</td>
<td style="text-align: center;">额外条件</td>
<td style="text-align: center; width: 40%;">结论形式</td>
<td style="text-align: center;">关系</td>
</tr>
</thead>
<tbody>
<tr>
<td>罗尔中值定理</td>
<td>1 个函数 <span displaypfx="inline-" class="mathjax-container">\(f\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(f(a)=f(b)\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(f'(c)=0\)</span></td>
<td>拉格朗日定理的特殊情形</td>
</tr>
<tr>
<td>拉格朗日中值定理</td>
<td>1 个函数 <span displaypfx="inline-" class="mathjax-container">\(f\)</span></td>
<td>无额外端点相等条件</td>
<td><span displaypfx="inline-" class="mathjax-container">\(f'(c)=\frac{f(b)-f(a)}{b-a}\)</span></td>
<td>柯西定理取 <span displaypfx="inline-" class="mathjax-container">\(g(x)=x\)</span> 的特例</td>
</tr>
<tr>
<td>柯西中值定理</td>
<td>2 个函数 <span displaypfx="inline-" class="mathjax-container">\(f,g\)</span></td>
<td>两个函数都满足连续+可导</td>
<td><span displaypfx="inline-" class="mathjax-container">\((f(b)-f(a))g'(c)=(g(b)-g(a))f'(c)\)</span></td>
<td>三者中最一般形式</td>
</tr>
</tbody>
</table>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/mean-value-theorems.png"><img class="alignnone size-full wp-image-40785" src="https://blog.gmem.cc/wp-content/uploads/2026/03/mean-value-theorems.png" alt="mean-value-theorems" width="100%" /></a></p>
<div class="blog_h3"><span class="graybg">中值定理和AI</span></div>
<p>在 AI 系统中，中值定理主要作为理论工具出现：工程代码很少直接“调用定理”，但大量优化、稳定性与误差分析都在使用它的核心变形，即把“两个点的函数值差”转写为“某个中间点的导数（梯度）信息”。</p>
<ul>
<li>优化收敛分析（Optimization Convergence）：训练中常关心一步更新后损失变化 <span displaypfx="inline-" class="mathjax-container">\(L(\theta+\Delta)-L(\theta)\)</span>。中值定理把它连接到中间点的梯度，进而用于学习率条件与下降性证明。</li>
<li>Lipschitz 界与鲁棒性（Lipschitz Bound &amp; Robustness）：中值定理给出“输入小扰动导致输出变化”的上界形式。若梯度有界，则输出变化可控；这正是对抗鲁棒性分析和梯度惩罚（Gradient Penalty）类方法的数学基础之一。</li>
<li>有限差分与梯度估计（Finite Difference / Gradient Estimation）：割线斜率与切线斜率之间的关系来自拉格朗日中值定理，是数值优化里差分近似、线搜索（Line Search）和误差估计的核心依据。</li>
</ul>
<p>从“纯数学”到“AI 实践”的桥梁可以一句话概括：<span style="background-color: #c0c0c0;">中值定理把宏观变化量（函数值差）变成可优化的微观信息（导数/梯度）</span>。</p>
<div class="blog_h2"><span class="graybg">偏导数（Partial Derivative）</span></div>
<p>对多元函数 <span displaypfx="inline-" class="mathjax-container">\(f:\mathbb{R}^n\to\mathbb{R}\)</span>，偏导数把“只沿某一坐标轴方向的导数”形式化：固定其余变量，只让第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个变量变化。先写成最直接的极限定义：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial f}{\partial x_i}(x_0)=\lim_{h\to 0}\frac{f(x_{0,1},\dots,x_{0,i-1},x_{0,i}+h,x_{0,i+1},\dots,x_{0,n})-f(x_0)}{h}\]</span>
<p>这里的 <span displaypfx="inline-" class="mathjax-container">\(h\)</span> 是“第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个变量的微小增量”（Increment）：只加在第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个坐标上，其他坐标保持不变。</p>
<p>在线性代数记号下，上式等价写成（更紧凑）：令 <span displaypfx="inline-" class="mathjax-container">\(e_i\)</span> 为第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个标准基向量（Standard Basis Vector），则</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial f}{\partial x_i}(x_0)=\lim_{h\to 0}\frac{f(x_0+h e_i)-f(x_0)}{h}\]</span>
<p>更常用的导数视角（计算视角）是：<span style="background-color: #c0c0c0;">把其余变量暂时当常数，把目标变量当自变量，直接套一元求导法则</span>。也就是说，极限定义负责“定义正确性”，日常计算通常用求导规则完成。</p>
<p>例如 <span displaypfx="inline-" class="mathjax-container">\(f(x,y)=x^2y\)</span>：对 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 求偏导时把 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 视作常数，得 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial f}{\partial x}=2xy\)</span>；对 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 求偏导时把 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 视作常数，得 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial f}{\partial y}=x^2\)</span>。</p>
<p>切换为极限视角，令 <span displaypfx="inline-" class="mathjax-container">\(f(x,y)=x^2y\)</span>，在点 <span displaypfx="inline-" class="mathjax-container">\((1,2)\)</span>。</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial f}{\partial x}(1,2)=\lim_{h\to 0}\frac{f(1+h,2)-f(1,2)}{h}\]</span>
<span displaypfx="" class="mathjax-container">\[=\lim_{h\to 0}\frac{2(1+h)^2-2}{h}\]</span>
<span displaypfx="" class="mathjax-container">\[=\lim_{h\to 0}(4+2h)=4\]</span>
<span displaypfx="" class="mathjax-container">\[\frac{\partial f}{\partial y}(1,2)=\lim_{h\to 0}\frac{f(1,2+h)-f(1,2)}{h}\]</span>
<span displaypfx="" class="mathjax-container">\[=\lim_{h\to 0}\frac{(2+h)-2}{h}=1\]</span>
<p>可以看到：求 <span displaypfx="inline-" class="mathjax-container">\(\partial f/\partial x\)</span> 时把 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 当常数；求 <span displaypfx="inline-" class="mathjax-container">\(\partial f/\partial y\)</span> 时把 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 当常数。这正是“其余变量固定”的具体计算含义。</p>
<p>梯度（Gradient）就是把所有偏导数按坐标收集成向量： <span displaypfx="inline-" class="mathjax-container">\(\nabla f(x)=(\partial f/\partial x_1,\dots,\partial f/\partial x_n)^\top\)</span>。</p>
<div class="blog_h2"><span class="graybg">微分（Differential）</span></div>
<p>在一元情形中，微分是“把函数增量线性化”的记号：给定一个小增量 <span displaypfx="inline-" class="mathjax-container">\(dx\)</span>，定义</p>
<span displaypfx="" class="mathjax-container">\[df\big|_{x_0}=f'(x_0)\,dx\]</span>
<p>此时真实增量满足 <span displaypfx="inline-" class="mathjax-container">\(\Delta f = df + o(dx)\)</span>。给定 <span displaypfx="inline-" class="mathjax-container">\(dx\)</span> 后， <span displaypfx="inline-" class="mathjax-container">\(df\)</span> 才对应一个确定数值。</p>
<p>例：若 <span displaypfx="inline-" class="mathjax-container">\(f(x)=x^2\)</span>，在 <span displaypfx="inline-" class="mathjax-container">\(x_0=3\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(dx=0.1\)</span> 时， <span displaypfx="inline-" class="mathjax-container">\(df=2x_0dx=0.6\)</span>，而 <span displaypfx="inline-" class="mathjax-container">\(\Delta f=f(3.1)-f(3)=0.61\)</span>。微分给出一阶近似，误差来自高阶项。</p>
<div class="blog_h2"><span class="graybg">全微分（Total Differential）</span></div>
<p>对多元函数 <span displaypfx="inline-" class="mathjax-container">\(f(x_1,\dots,x_n)\)</span>，全微分把“多方向小扰动下的一阶线性响应”写成：</p>
<span displaypfx="" class="mathjax-container">\[df=\sum_{i=1}^n \frac{\partial f}{\partial x_i}dx_i\]</span>
<p>令 <span displaypfx="inline-" class="mathjax-container">\(d\mathbf{x}=(dx_1,\dots,dx_n)^\top\)</span>，则向量形式是：</p>
<span displaypfx="" class="mathjax-container">\[df=(\nabla f(x))^\top d\mathbf{x}\]</span>
<p>若进一步把扰动分解为“方向 + 步长”： <span displaypfx="inline-" class="mathjax-container">\(d\mathbf{x}=\mathbf{u}\,ds\)</span>（<span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{u}\|_2=1\)</span>），则</p>
<span displaypfx="" class="mathjax-container">\[\frac{df}{ds}=\nabla f(x)\cdot \mathbf{u}\]</span>
<p>右侧就是方向导数（Directional Derivative）：全微分给出任意小位移的一阶近似，方向导数则把位移约束在单位方向并除去步长。</p>
<p>例：令 <span displaypfx="inline-" class="mathjax-container">\(f(x,y)=x^2y\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial f}{\partial x}=2xy\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\frac{\partial f}{\partial y}=x^2\)</span>。在点 <span displaypfx="inline-" class="mathjax-container">\((x_0,y_0)=(1,2)\)</span>，若 <span displaypfx="inline-" class="mathjax-container">\(dx=0.1\)</span>、<span displaypfx="inline-" class="mathjax-container">\(dy=-0.05\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(df=2xy\,dx+x^2dy=2\cdot1\cdot2\cdot0.1+1\cdot(-0.05)=0.35\)</span>，给出 <span displaypfx="inline-" class="mathjax-container">\(\Delta f\)</span> 的一阶近似。</p>
<div class="blog_h2"><span class="graybg">向量微积分</span></div>
<p>Nabla 算子（Nabla Operator）记作 <span displaypfx="inline-" class="mathjax-container">\(\nabla\)</span>，本质上是把偏导算子按坐标排列成的“向量形式”：</p>
<span displaypfx="" class="mathjax-container">\[\nabla=\left(\frac{\partial}{\partial x_1},\dots,\frac{\partial}{\partial x_n}\right)^\top\]</span>
<p>它本身不是一个数，而是一个算子（Operator）。作用在标量场上得到梯度（Gradient）；与向量场点乘得到散度（Divergence）；与向量场叉乘得到旋度（Curl）；对标量场再取散度得到拉普拉斯（Laplacian）。</p>
<p>符号 <span displaypfx="inline-" class="mathjax-container">\(\|\cdot\|\)</span>（双竖线）表示范数（Norm）。因此 <span displaypfx="inline-" class="mathjax-container">\(\|\nabla f(x)\|\)</span> 是梯度向量的长度；而 <span displaypfx="inline-" class="mathjax-container">\(\|\nabla\|\)</span> 作为“算子范数”在工程推导中很少直接使用，通常需要明确它作用的函数空间与范数定义。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/nabla.jpg"><img class="alignnone size-full wp-image-40609" src="https://blog.gmem.cc/wp-content/uploads/2026/03/nabla.jpg" alt="nabla" width="100%" /></a></p>
<div class="blog_h3"><span class="graybg">梯度</span></div>
<p>梯度（Gradient）把标量函数 <span displaypfx="inline-" class="mathjax-container">\(f:\mathbb{R}^n\to\mathbb{R}\)</span> 的局部变化率组织成向量：</p>
<span displaypfx="" class="mathjax-container">\[\nabla f(x)=\left(\frac{\partial f}{\partial x_1},\dots,\frac{\partial f}{\partial x_n}\right)^\top\]</span>
<p>它指向函数增长最快的方向（Steepest Ascent Direction），模长刻画该方向上的最大斜率。在优化里，梯度下降（Gradient Descent）沿 <span displaypfx="inline-" class="mathjax-container">\(-\nabla f\)</span> 走，是因为这是一阶近似下最快下降方向。</p>
<p>例： <span displaypfx="inline-" class="mathjax-container">\(f(x,y)=x^2+2y^2\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\nabla f=(2x,4y)\)</span>，在点 <span displaypfx="inline-" class="mathjax-container">\((1,1)\)</span> 处梯度为 <span displaypfx="inline-" class="mathjax-container">\((2,4)\)</span>，表示沿 y 方向的增长更陡。</p>
<p>方向导数（Directional Derivative）把“沿某个方向的变化率”写成点积：若 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}\)</span> 是单位方向向量，则</p>
<span displaypfx="" class="mathjax-container">\[D_{\mathbf{u}}f(x)=\nabla f(x)\cdot \mathbf{u}\]</span>
<p>符号先约定： <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 是当前点， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{u}\)</span> 是单位方向（<span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{u}\|_2=1\)</span>）， <span displaypfx="inline-" class="mathjax-container">\(\varepsilon\)</span> 是沿该方向的步长（可正可负的小实数）。方向导数定义为</p>
<span displaypfx="" class="mathjax-container">\[D_{\mathbf{u}}f(x)=\lim_{\varepsilon\to 0}\frac{f(x+\varepsilon\mathbf{u})-f(x)}{\varepsilon}\]</span>
<p>对 <span displaypfx="inline-" class="mathjax-container">\(f(x+\varepsilon\mathbf{u})\)</span> 做一阶 Taylor 展开：</p>
<span displaypfx="" class="mathjax-container">\[f(x+\varepsilon\mathbf{u})=f(x)+\varepsilon\,\nabla f(x)\cdot\mathbf{u}+o(\varepsilon)\]</span>
<p>代回定义式：</p>
<span displaypfx="" class="mathjax-container">\[\frac{f(x+\varepsilon\mathbf{u})-f(x)}{\varepsilon}\]</span>
<span displaypfx="" class="mathjax-container">\[=\nabla f(x)\cdot\mathbf{u}+\frac{o(\varepsilon)}{\varepsilon}\]</span>
<p>令 <span displaypfx="inline-" class="mathjax-container">\(\varepsilon\to 0\)</span>，由于 <span displaypfx="inline-" class="mathjax-container">\(o(\varepsilon)/\varepsilon\to 0\)</span>，得到 <span displaypfx="inline-" class="mathjax-container">\(D_{\mathbf{u}}f(x)=\nabla f(x)\cdot\mathbf{u}\)</span>。</p>
<div class="blog_h3"><span class="graybg">散度</span></div>
<p>场（Field）是“把空间中每个点映射到一个量”的函数。标量场（Scalar Field）<span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span> 在每个点输出一个标量；向量场（Vector Field）<span displaypfx="inline-" class="mathjax-container">\(\mathbf{F}(x)\)</span> 在每个点输出一个向量。梯度作用在标量场上，散度/旋度作用在向量场上。</p>
<p>散度（Divergence）作用在向量场 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{F}=(F_1,\dots,F_n)^\top:\mathbb{R}^n\to\mathbb{R}^n\)</span> 上。这里先把两个术语说清楚：</p>
<span displaypfx="" class="mathjax-container">\[J_{\mathbf{F}}(x)=\left[\frac{\partial F_i}{\partial x_j}\right]_{i,j=1}^n\]</span>
<p>上式是雅可比矩阵（Jacobian）：第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 列表示 <span displaypfx="inline-" class="mathjax-container">\(F_i\)</span> 对 <span displaypfx="inline-" class="mathjax-container">\(x_j\)</span> 的偏导。迹（Trace）是矩阵对角线元素之和，因此</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{tr}(J_{\mathbf{F}})=\sum_{i=1}^n \frac{\partial F_i}{\partial x_i}\]</span>
<p>而散度按定义正是这条和式，所以“散度 = Jacobian 的迹”：</p>
<span displaypfx="" class="mathjax-container">\[\nabla\cdot \mathbf{F}(x)=\mathrm{tr}(J_{\mathbf{F}}(x))=\sum_{i=1}^n \frac{\partial F_i}{\partial x_i}\]</span>
<p>几何/物理直觉：把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{F}\)</span> 想成“流体速度场（Velocity Field）”，散度衡量一个点附近是否像“源（Source）/汇（Sink）”——单位体积的净流出率。散度为正表示净流出，为负表示净流入。</p>
<p>例：二维场 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{F}(x,y)=(x,y)\)</span> 的 Jacobian 是 <span displaypfx="inline-" class="mathjax-container">\(\begin{bmatrix}1&amp;0\\0&amp;1\end{bmatrix}\)</span>，其迹为 <span displaypfx="inline-" class="mathjax-container">\(1+1=2\)</span>，所以散度也等于 2，在任意点都表现为均匀“发散”。</p>
<div class="blog_h3"><span class="graybg">旋度</span></div>
<p>旋度（Curl）衡量向量场的局部旋转（Local Rotation）。在三维中：</p>
<span displaypfx="" class="mathjax-container">\[\nabla\times \mathbf{F}=\left(\frac{\partial F_3}{\partial y}-\frac{\partial F_2}{\partial z},\ \frac{\partial F_1}{\partial z}-\frac{\partial F_3}{\partial x},\ \frac{\partial F_2}{\partial x}-\frac{\partial F_1}{\partial y}\right)\]</span>
<p>直觉上，可把它理解为“放一个很小的桨轮（Paddle Wheel）在该点附近，桨轮是否会被带着转”。保守场（Conservative Field）满足 <span displaypfx="inline-" class="mathjax-container">\(\nabla\times\mathbf{F}=\mathbf{0}\)</span>，这与“路径无关”/“存在势函数”是等价刻画。</p>
<p>例：二维旋转场 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{F}(x,y)=(-y,x,0)\)</span> 的旋度为 <span displaypfx="inline-" class="mathjax-container">\((0,0,2)\)</span>，表示绕 z 轴的均匀旋转趋势。</p>
<p>拉普拉斯算子（Laplacian）作用在标量场上，定义为梯度的散度：</p>
<span displaypfx="" class="mathjax-container">\[\nabla^2 f=\nabla\cdot(\nabla f)=\sum_{i=1}^n \frac{\partial^2 f}{\partial x_i^2}\]</span>
<p>它常出现在扩散（Diffusion）与平滑（Smoothing）问题中，也常被用作“曲率/粗糙度”的度量。例： <span displaypfx="inline-" class="mathjax-container">\(f(x,y)=x^2+y^2\)</span> 则 <span displaypfx="inline-" class="mathjax-container">\(\nabla^2 f=2+2=4\)</span>。</p>
<div class="blog_h2"><span class="graybg">Jacobian 矩阵</span></div>
<p>当函数的输出不再是一个标量，而是一个向量时，梯度这个概念就不够用了。此时需要把“每个输出分量对每个输入变量的一阶变化率”统一收集起来，这个对象就是 Jacobian 矩阵（Jacobian Matrix）。它是一阶导数在向量值函数上的自然推广，也是 Hessian 的直接前置概念。</p>
<p>设向量值函数</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{F}:\mathbb{R}^n\to\mathbb{R}^m,\qquad \mathbf{F}(\mathbf{x})=\begin{bmatrix}F_1(\mathbf{x})\\ \vdots\\ F_m(\mathbf{x})\end{bmatrix}\]</span>
<p>其中输入 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(x_1,\dots,x_n)^\top\)</span>，输出 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{F}(\mathbf{x})\)</span> 有 <span displaypfx="inline-" class="mathjax-container">\(m\)</span> 个分量。Jacobian 矩阵定义为：</p>
<span displaypfx="" class="mathjax-container">\[J_{\mathbf{F}}(\mathbf{x})=\left[\frac{\partial F_i}{\partial x_j}(\mathbf{x})\right]_{i=1,\dots,m;\,j=1,\dots,n}\]</span>
<p>按矩阵展开，就是一个 <span displaypfx="inline-" class="mathjax-container">\(m\times n\)</span> 矩阵：</p>
<span displaypfx="" class="mathjax-container">\[J_{\mathbf{F}}(\mathbf{x})= \begin{bmatrix} \frac{\partial F_1}{\partial x_1} &amp; \frac{\partial F_1}{\partial x_2} &amp; \cdots &amp; \frac{\partial F_1}{\partial x_n}\\ \frac{\partial F_2}{\partial x_1} &amp; \frac{\partial F_2}{\partial x_2} &amp; \cdots &amp; \frac{\partial F_2}{\partial x_n}\\ \vdots &amp; \vdots &amp; \ddots &amp; \vdots\\ \frac{\partial F_m}{\partial x_1} &amp; \frac{\partial F_m}{\partial x_2} &amp; \cdots &amp; \frac{\partial F_m}{\partial x_n} \end{bmatrix}\]</span>
<p>这里第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 行第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 列的元素 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial F_i}{\partial x_j}\)</span> 表示：第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个输入变量发生微小变化时，第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个输出分量会怎样一阶变化。按矩阵读法，<span style="background-color: #c0c0c0;">行对应输出分量，列对应输入变量</span>，因此 Jacobian 本质上是一张局部灵敏度表（Local Sensitivity Table）。</p>
<p>它描述的不是全局非线性结构，而是函数在某个点附近的局部线性映射：输入空间里的一个小扰动向量 <span displaypfx="inline-" class="mathjax-container">\(d\mathbf{x}\)</span> 经过 Jacobian 作用后，变成输出空间中的一阶近似扰动 <span displaypfx="inline-" class="mathjax-container">\(d\mathbf{F}\)</span>。因此，Jacobian 可以理解为“该点附近最能代表原函数的一阶线性算子”。</p>
<p>它与全微分的关系最直接。若在点 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 附近加入一个小扰动 <span displaypfx="inline-" class="mathjax-container">\(d\mathbf{x}\)</span>，则输出的一阶变化满足：</p>
<span displaypfx="" class="mathjax-container">\[d\mathbf{F}\approx J_{\mathbf{F}}(\mathbf{x})\,d\mathbf{x}\]</span>
<p>这条式子就是向量值函数的一阶线性化。也就是说，Jacobian 在局部扮演的角色，类似于一元函数里的导数：它给出“最好的线性近似”。若输出是一维，即 <span displaypfx="inline-" class="mathjax-container">\(m=1\)</span>，Jacobian 就退化成一个 <span displaypfx="inline-" class="mathjax-container">\(1\times n\)</span> 的行向量；它与梯度本质上包含同一组偏导数，只是排布约定可能不同。</p>
<p>一个二维到二维的例子最容易看清。设</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{F}(x,y)=\begin{bmatrix}x^2y\\ x+y^2\end{bmatrix}\]</span>
<p>则第一行来自分量函数 <span displaypfx="inline-" class="mathjax-container">\(F_1(x,y)=x^2y\)</span>，第二行来自 <span displaypfx="inline-" class="mathjax-container">\(F_2(x,y)=x+y^2\)</span>。逐项求偏导可得：</p>
<span displaypfx="" class="mathjax-container">\[J_{\mathbf{F}}(x,y)= \begin{bmatrix} 2xy &amp; x^2\\ 1 &amp; 2y \end{bmatrix}\]</span>
<p>例如在点 <span displaypfx="inline-" class="mathjax-container">\((1,2)\)</span>，Jacobian 为</p>
<span displaypfx="" class="mathjax-container">\[J_{\mathbf{F}}(1,2)= \begin{bmatrix} 4 &amp; 1\\ 1 &amp; 4 \end{bmatrix}\]</span>
<p>这表示：在该点附近，若输入发生微小变化 <span displaypfx="inline-" class="mathjax-container">\((dx,dy)^\top\)</span>，则输出变化近似为</p>
<span displaypfx="" class="mathjax-container">\[d\mathbf{F}\approx \begin{bmatrix} 4 &amp; 1\\ 1 &amp; 4 \end{bmatrix} \begin{bmatrix} dx\\ dy \end{bmatrix}\]</span>
<p>第一行说明输出第一分量大致按 <span displaypfx="inline-" class="mathjax-container">\(4dx+dy\)</span> 变化，第二行说明输出第二分量大致按 <span displaypfx="inline-" class="mathjax-container">\(dx+4dy\)</span> 变化。Jacobian 因而可以被理解为局部灵敏度矩阵（Sensitivity Matrix）。</p>
<p>对标量函数 <span displaypfx="inline-" class="mathjax-container">\(f:\mathbb{R}^n\to\mathbb{R}\)</span>，梯度 <span displaypfx="inline-" class="mathjax-container">\(\nabla f:\mathbb{R}^n\to\mathbb{R}^n\)</span> 本身就是一个向量值函数，因此 Hessian 可以直接看成梯度映射的 Jacobian：</p>
<span displaypfx="" class="mathjax-container">\[\nabla^2 f(\mathbf{x})=J_{\nabla f}(\mathbf{x})\]</span>
<p>这就是把 Jacobian 放在 Hessian 前面的原因：先理解“向量值函数的一阶导数如何组织成矩阵”，再看“标量函数的梯度再求一次导”，Hessian 的结构就不会显得突兀。</p>
<p>在机器学习中，Jacobian 最常出现在三类地方：</p>
<ol>
<li>反向传播（Backpropagation）：本质上是在链式法则（Chain Rule）下逐层组合这些局部 Jacobian。前一层输出的微小变化如何传到后一层，正是由对应层的 Jacobian 决定。</li>
<li>向量场分析：散度（Divergence）等于 Jacobian 的迹（Trace）。</li>
<li>生成模型、正则化与鲁棒性分析：常关心 Jacobian 的谱范数（Spectral Norm）或 Frobenius 范数，以度量局部放大效应。</li>
</ol>
<p>若输出维度和输入维度都很大，显式构造完整 Jacobian 也会很贵，因此工程上常直接计算 Jacobian-Vector Product 或 Vector-Jacobian Product，而不是把整块矩阵完全展开。</p>
<p>在神经网络语境里，Jacobian 到底“针对激活值还是针对权重”，取决于谁被当作自变量。若研究层与层之间的信号传播，常写成 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial h^{(l+1)}}{\partial h^{(l)}}\)</span>，这时它针对的是激活值（Activation）或层表示；若研究模型输出对输入的敏感度，可写成 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial y}{\partial x}\)</span>；若研究输出对参数的敏感度，也可以写成 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial y}{\partial \theta}\)</span>。因此，Jacobian 的本质是“一个向量怎样对另一个向量发生一阶变化”，并不预先限定变量一定是激活值还是权重。</p>
<div class="blog_h2"><span class="graybg">Hessian 矩阵</span></div>
<p>梯度（Gradient）回答的是“函数沿各坐标方向的一阶变化率”；Hessian 矩阵（Hessian Matrix）进一步回答的是“这些一阶变化率本身又在怎样变化”。这一点可以直接从“梯度是一个向量值函数”展开出来。对标量函数 <span displaypfx="inline-" class="mathjax-container">\(f:\mathbb{R}^n\to\mathbb{R}\)</span>，梯度写成</p>
<span displaypfx="" class="mathjax-container">\[\nabla f(\mathbf{x})=\begin{bmatrix}\frac{\partial f}{\partial x_1}\\ \frac{\partial f}{\partial x_2}\\ \vdots\\ \frac{\partial f}{\partial x_n}\end{bmatrix}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\nabla f\)</span> 已经不是标量，而是把输入向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 映射为另一个向量的函数。因此可以像对任何向量值函数那样，对它再求一次 Jacobian：</p>
<span displaypfx="" class="mathjax-container">\[J_{\nabla f}(\mathbf{x})=\frac{\partial (\nabla f)}{\partial \mathbf{x}}\]</span>
<p>把梯度各分量逐项代进去，Jacobian 的第 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 个元素就是“梯度第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个分量对输入第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个变量的偏导数”：</p>
<span displaypfx="" class="mathjax-container">\[\big[J_{\nabla f}(\mathbf{x})\big]_{ij}=\frac{\partial}{\partial x_j}\left(\frac{\partial f}{\partial x_i}\right)=\frac{\partial^2 f}{\partial x_j\partial x_i}\]</span>
<p>于是整块矩阵就是</p>
<span displaypfx="" class="mathjax-container">\[J_{\nabla f}(\mathbf{x})=\begin{bmatrix}\frac{\partial^2 f}{\partial x_1\partial x_1} &amp; \frac{\partial^2 f}{\partial x_2\partial x_1} &amp; \cdots &amp; \frac{\partial^2 f}{\partial x_n\partial x_1}\\ \frac{\partial^2 f}{\partial x_1\partial x_2} &amp; \frac{\partial^2 f}{\partial x_2\partial x_2} &amp; \cdots &amp; \frac{\partial^2 f}{\partial x_n\partial x_2}\\ \vdots &amp; \vdots &amp; \ddots &amp; \vdots\\ \frac{\partial^2 f}{\partial x_1\partial x_n} &amp; \frac{\partial^2 f}{\partial x_2\partial x_n} &amp; \cdots &amp; \frac{\partial^2 f}{\partial x_n\partial x_n}\end{bmatrix}\]</span>
<p>这正是 Hessian：</p>
<span displaypfx="" class="mathjax-container">\[\nabla^2 f(\mathbf{x})=J_{\nabla f}(\mathbf{x})\]</span>
<p>这个等式的含义很直接：Hessian 不是凭空引入的另一种矩阵，而就是“梯度这个向量场对输入的 Jacobian”。</p>
<p>直接从二阶导角度看，Hessian 是多元函数的二阶导数对象，用来描述局部曲率（Curvature）、凹凸性以及临界点附近的二阶形状。</p>
<p>设标量函数 <span displaypfx="inline-" class="mathjax-container">\(f:\mathbb{R}^n\to\mathbb{R}\)</span>，输入向量为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}=(x_1,\dots,x_n)^\top\)</span>。Hessian 矩阵记作 <span displaypfx="inline-" class="mathjax-container">\(\nabla^2 f(\mathbf{x})\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(H_f(\mathbf{x})\)</span>，定义为：</p>
<span displaypfx="" class="mathjax-container">\[\nabla^2 f(\mathbf{x})=\left[\frac{\partial^2 f}{\partial x_i\partial x_j}(\mathbf{x})\right]_{i,j=1}^{n}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 都从 <span displaypfx="inline-" class="mathjax-container">\(1\)</span> 遍历到 <span displaypfx="inline-" class="mathjax-container">\(n\)</span>，因此 Hessian 会收集所有变量对 <span displaypfx="inline-" class="mathjax-container">\((x_i,x_j)\)</span> 的二阶偏导。之所以自然形成一个 <span displaypfx="inline-" class="mathjax-container">\(n\times n\)</span> 的“全组合”矩阵，是因为一阶导数 <span displaypfx="inline-" class="mathjax-container">\(\nabla f\)</span> 本身已经有 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个分量；再对这 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个分量分别对 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个输入变量各求一次导，就得到 <span displaypfx="inline-" class="mathjax-container">\(n\times n\)</span> 个二阶偏导项。前面展开的 <span displaypfx="inline-" class="mathjax-container">\(J_{\nabla f}(\mathbf{x})\)</span> 就是这块矩阵，因此这里不再重复写一遍。</p>
<p>把梯度记为 <span displaypfx="inline-" class="mathjax-container">\(\nabla f(\mathbf{x})=(g_1,\dots,g_n)^\top\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(g_i=\frac{\partial f}{\partial x_i}\)</span>，则 Hessian 的第 <span displaypfx="inline-" class="mathjax-container">\((i,j)\)</span> 个元素可以直接写成 <span displaypfx="inline-" class="mathjax-container">\(H_{ij}=\frac{\partial g_i}{\partial x_j}\)</span>。这个记号把它的意义说得很清楚：它衡量“第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个方向上的斜率”会不会随着第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个变量的变化而改变。</p>
<p>对角线元素 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial^2 f}{\partial x_i^2}\)</span> 描述沿单一坐标方向的纯二阶曲率，也就是该方向本身的弯曲强弱；非对角元素 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial^2 f}{\partial x_i\partial x_j}\)</span> 描述不同方向之间的局部耦合（Local Coupling）。当 <span displaypfx="inline-" class="mathjax-container">\(H_{ij}=0\)</span> 时，意味着在当前点附近，变量 <span displaypfx="inline-" class="mathjax-container">\(x_j\)</span> 的微小变化不会改变第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个方向上的一阶斜率；当 <span displaypfx="inline-" class="mathjax-container">\(H_{ij}\neq 0\)</span> 时，两个方向在局部二阶结构上发生耦合。</p>
<p>这种耦合会直接反映在等高线（Contour）形状上：若非对角项接近零，局部二次近似更接近与坐标轴对齐的“正椭圆碗”；若非对角项明显非零，等高线会发生倾斜，说明改变一个变量会连带改变另一个方向上的坡度。</p>
<p>若 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 足够光滑，满足二阶混合偏导连续，则由 Clairaut / Schwarz 定理可得</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial^2 f}{\partial x_i\partial x_j}=\frac{\partial^2 f}{\partial x_j\partial x_i}\]</span>
<p>因此 Hessian 矩阵是对称矩阵（Symmetric Matrix）。这点非常重要，因为一旦 Hessian 对称，就可以用特征值（Eigenvalue）来判断局部曲率：正特征值对应向上弯，负特征值对应向下弯，正负并存则意味着不同方向上的弯曲趋势相反。</p>
<p>Hessian 的几何意义可以通过二阶 Taylor 展开看得最清楚。对点 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\)</span> 附近的微小扰动 <span displaypfx="inline-" class="mathjax-container">\(\Delta \mathbf{x}\)</span>，有：</p>
<span displaypfx="" class="mathjax-container">\[f(\mathbf{x}+\Delta\mathbf{x})\approx f(\mathbf{x})+\nabla f(\mathbf{x})^\top \Delta\mathbf{x}+\frac{1}{2}\Delta\mathbf{x}^\top \nabla^2 f(\mathbf{x})\Delta\mathbf{x}\]</span>
<p>这里第一项 <span displaypfx="inline-" class="mathjax-container">\(f(\mathbf{x})\)</span> 是当前函数值，第二项 <span displaypfx="inline-" class="mathjax-container">\(\nabla f(\mathbf{x})^\top \Delta\mathbf{x}\)</span> 是一阶线性变化，第三项 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{2}\Delta\mathbf{x}^\top \nabla^2 f(\mathbf{x})\Delta\mathbf{x}\)</span> 就是二阶曲率修正项。也就是说，梯度告诉你“朝哪边走函数会立刻增减”，Hessian 则告诉你“地形本身是碗状、山丘状，还是马鞍状”。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/hessian-locality-preview.png"><img class="alignnone size-full" src="https://blog.gmem.cc/wp-content/uploads/2026/03/hessian-locality.png" alt="hessian-locality" width="2307" height="1169" /></a></p>
<p>二维情形最直观。设</p>
<span displaypfx="" class="mathjax-container">\[f(x_1,x_2)=x_1^2+3x_1x_2+2x_2^2\]</span>
<p>先求梯度：</p>
<span displaypfx="" class="mathjax-container">\[\nabla f(x_1,x_2)=\begin{bmatrix}2x_1+3x_2\\3x_1+4x_2\end{bmatrix}\]</span>
<p>再求二阶偏导，可得：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial^2 f}{\partial x_1^2}=2,\qquad \frac{\partial^2 f}{\partial x_1\partial x_2}=3,\qquad \frac{\partial^2 f}{\partial x_2^2}=4\]</span>
<p>因此 Hessian 为：</p>
<span displaypfx="" class="mathjax-container">\[\nabla^2 f(x_1,x_2)=\begin{bmatrix}2 &amp; 3\\3 &amp; 4\end{bmatrix}\]</span>
<p>这个例子还有一个关键特征：Hessian 与 <span displaypfx="inline-" class="mathjax-container">\((x_1,x_2)\)</span> 无关，因此它说明该函数在整个空间里都是同一种二次曲面（Quadratic Surface）。对二次函数而言，Hessian 足以完整决定其曲率结构。</p>
<p>在极值判别里，Hessian 主要出现在临界点（Critical Point）附近。若某点 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}^*\)</span> 满足 <span displaypfx="inline-" class="mathjax-container">\(\nabla f(\mathbf{x}^*)=\mathbf{0}\)</span>，则：</p>
<ul>
<li>若 <span displaypfx="inline-" class="mathjax-container">\(\nabla^2 f(\mathbf{x}^*)\)</span> 正定（Positive Definite），即对任意非零向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\)</span> 都有 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}^\top \nabla^2 f(\mathbf{x}^*)\mathbf{v}&gt;0\)</span>，则该点是局部极小值（Local Minimum）。</li>
<li>若 <span displaypfx="inline-" class="mathjax-container">\(\nabla^2 f(\mathbf{x}^*)\)</span> 负定（Negative Definite），即对任意非零向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}\)</span> 都有 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{v}^\top \nabla^2 f(\mathbf{x}^*)\mathbf{v}&lt;0\)</span>，则该点是局部极大值（Local Maximum）。</li>
<li>若 Hessian 不定（Indefinite），即存在方向使二次型为正，也存在方向使其为负，则该点是鞍点（Saddle Point）。</li>
<li>若 Hessian 只有半正定、半负定，或出现零特征值，则二阶信息不足以单独判定，还需要更高阶分析或结合函数结构进一步判断。</li>
</ul>
<p>这套判据和一维情形完全一致：一维里 <span displaypfx="inline-" class="mathjax-container">\(f''(x^*)&gt;0\)</span> 表示“碗底”， <span displaypfx="inline-" class="mathjax-container">\(f''(x^*)&lt;0\)</span> 表示“山顶”；Hessian 只是把这个二阶导概念推广到了多维空间。</p>
<p>在优化中，Hessian 的意义更直接。牛顿法（Newton's Method）用它近似局部曲面，并据此选择更合理的更新方向：</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{x}_{t+1}=\mathbf{x}_t-\big(\nabla^2 f(\mathbf{x}_t)\big)^{-1}\nabla f(\mathbf{x}_t)\]</span>
<p>与只看一阶斜率的梯度下降不同，牛顿法还利用了局部曲率信息：若某个方向很陡但也弯得很厉害，步长就应更谨慎；若某个方向很平缓，更新可以相对更大。这也是为什么 Hessian 会出现在牛顿法、拟牛顿法（BFGS / L-BFGS）以及很多二阶优化分析中。</p>
<p>在机器学习里，Hessian 还常用来讨论损失面的尖锐度（Sharpness）、参数可辨识性以及训练稳定性。但深度学习模型维度极高，完整 Hessian 的存储与求逆代价非常大：若参数维度为 <span displaypfx="inline-" class="mathjax-container">\(d\)</span>，Hessian 大小就是 <span displaypfx="inline-" class="mathjax-container">\(d\times d\)</span>。因此工程上更常用 Hessian 向量积（Hessian-Vector Product）、对角近似、低秩近似，或拟牛顿方法来间接利用二阶信息，而不是显式构造整块矩阵。</p>
<p>在神经网络里，单独说 Hessian 时，默认通常指损失函数对参数向量 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 的 Hessian，即 <span displaypfx="inline-" class="mathjax-container">\(\nabla_\theta^2 L(\theta)\)</span>。原因很直接：优化真正要更新的是权重，因此人们最关心的是“损失面对参数空间的局部曲率”。当然，Hessian 也可以对输入或中间激活值来求，例如 <span displaypfx="inline-" class="mathjax-container">\(\nabla_x^2 f(x)\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(\nabla_h^2 f(h)\)</span>，它们分别描述输入空间或表示空间中的局部弯曲；只是从工程语境看，这类用法通常不是默认含义。</p>
<div class="blog_h2"><span class="graybg">积分</span></div>
<div class="blog_h3"><span class="graybg">不定积分</span></div>
<p>不定积分（Indefinite Integral）也叫反导数、原函数。是“由变化率反推原函数（Antiderivative）”：已知 <span displaypfx="inline-" class="mathjax-container">\(f\)</span>，寻找所有满足 <span displaypfx="inline-" class="mathjax-container">\(F'(x)=f(x)\)</span> 的函数 <span displaypfx="inline-" class="mathjax-container">\(F\)</span>。</p>
<p><span style="background-color: #c0c0c0;">一句话强调：不定积分就是“已知导函数（导数），求原函数”的过程。</span></p>
<span displaypfx="" class="mathjax-container">\[\int f(x)\,dx=F(x)+C\]</span>
<div class="blog_h4"><span class="graybg">从变化率反推状态</span></div>
<p>把导数看作“瞬时变化率”最容易理解积分的方向。若 <span displaypfx="inline-" class="mathjax-container">\(s(t)\)</span> 是位置、<span displaypfx="inline-" class="mathjax-container">\(v(t)\)</span> 是速度，则 <span displaypfx="inline-" class="mathjax-container">\(s'(t)=v(t)\)</span>。当只知道 <span displaypfx="inline-" class="mathjax-container">\(v(t)\)</span> 时，要恢复 <span displaypfx="inline-" class="mathjax-container">\(s(t)\)</span> 就是在做积分。</p>
<span displaypfx="" class="mathjax-container">\[s'(t)=v(t)\ \Longrightarrow\ s(t)=\int v(t)\,dt\]</span>
<div class="blog_h4"><span class="graybg">为什么必须有 +C</span></div>
<p>因为微分会“抹掉常数项”。不同函数只要相差一个常数，导数就完全一样：</p>
<span displaypfx="" class="mathjax-container">\[\frac{d}{dx}(x^2)=\frac{d}{dx}(x^2+5)=\frac{d}{dx}(x^2-100)=2x\]</span>
<p>所以当你从 <span displaypfx="inline-" class="mathjax-container">\(f(x)=2x\)</span> 反推原函数时，只能确定主体是 <span displaypfx="inline-" class="mathjax-container">\(x^2\)</span>，无法确定常数偏移。这就是积分常数（Constant of Integration）<span displaypfx="inline-" class="mathjax-container">\(C\)</span> 的来源。若再给一个初值条件（Initial Condition），例如 <span displaypfx="inline-" class="mathjax-container">\(F(x_0)=y_0\)</span>，<span displaypfx="inline-" class="mathjax-container">\(C\)</span> 才会被唯一确定。</p>
<div class="blog_h4"><span class="graybg">几何图像：一族平移曲线</span></div>
<p><span displaypfx="inline-" class="mathjax-container">\(\int f(x)\,dx\)</span> 的结果对应一族函数 <span displaypfx="inline-" class="mathjax-container">\(\{F(x)+C\mid C\in\mathbb{R}\}\)</span>。它们形状完全相同，只是沿 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 轴上下平移；在同一个 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 处，这族曲线的切线斜率都等于 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span>。</p>
<div class="blog_h4"><span class="graybg">一个最小可算例</span></div>
<span displaypfx="" class="mathjax-container">\[\int 2x\,dx=x^2+C\]</span>
<p>验算： <span displaypfx="inline-" class="mathjax-container">\(\frac{d}{dx}(x^2+C)=2x\)</span>。这一步强调的是“求导与积分互为逆过程”，但逆过程会保留一个常数不确定性。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/indefinite-integral1.png"><img class="alignnone size-full wp-image-40773" src="https://blog.gmem.cc/wp-content/uploads/2026/03/indefinite-integral1.png" alt="indefinite-integral" width="100%" /></a></p>
<div class="blog_h3"><span class="graybg">微积分基本定理（Fundamental Theorem of Calculus）</span></div>
<p>不定积分与定积分的连接点是微积分基本定理（Fundamental Theorem of Calculus）。它包含两条互补结论：</p>
<ol>
<li>若 <span displaypfx="inline-" class="mathjax-container">\(F'(x)=f(x)\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\int_a^b f(x)\,dx=F(b)-F(a)\)</span>。这告诉我们：定积分可以通过任意一个原函数在端点处相减来计算。</li>
<li>定义 <span displaypfx="inline-" class="mathjax-container">\(G(x)=\int_a^x f(t)\,dt\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(G'(x)=f(x)\)</span>。这告诉我们：把函数从 <span displaypfx="inline-" class="mathjax-container">\(a\)</span> 到 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 累积起来，得到的累积函数本身就是一个原函数。</li>
</ol>
<p><span style="background-color: #c0c0c0;">一句话强调：定积分就是原函数（不定积分）在区间两端的函数值“收尾相减”</span>，即 <span displaypfx="inline-" class="mathjax-container">\(\int_a^b f(x)\,dx=F(b)-F(a)\)</span>。</p>
<p>例： <span displaypfx="inline-" class="mathjax-container">\(f(x)=2x\)</span>，取原函数 <span displaypfx="inline-" class="mathjax-container">\(F(x)=x^2\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[\int_1^3 2x\,dx=F(3)-F(1)=9-1=8\]</span>
<div class="blog_h3"><span class="graybg">定积分</span></div>
<p>定积分（Definite Integral）可看成“带符号面积（Signed Area）”或“连续求和”。Riemann 和定义为：</p>
<span displaypfx="" class="mathjax-container">\[\int_a^b f(x)\,dx=\lim_{n\to\infty}\sum_{k=1}^{n}f(\xi_k)\Delta x_k\]</span>
<p>若上下界中出现 <span displaypfx="inline-" class="mathjax-container">\(\infty\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(-\infty\)</span>，它仍然属于定积分范畴，只是更准确地叫广义积分（Improper Integral）或广义定积分：本质上仍是在一个区间上求累积量，只是需要先把无穷区间截断，再用极限恢复。例如</p>
<span displaypfx="" class="mathjax-container">\[\int_a^{\infty} f(x)\,dx:=\lim_{R\to\infty}\int_a^R f(x)\,dx\]</span>
<p>因此“上下界无穷”不是不定积分，而是<span style="background-color: #c0c0c0;">定积分的特殊形式</span>。</p>
<p>在 AI 里，连续概率密度函数（Probability Density Function, PDF）的概率计算 <span displaypfx="inline-" class="mathjax-container">\(P(a\le X\le b)=\int_a^b p(x)\,dx\)</span> 本质上就是定积分。</p>
<div class="blog_h3"><span class="graybg">常用积分公式</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">被积函数</td>
<td style="text-align: center;">积分结果</td>
<td style="text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(x^n\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{x^{n+1}}{n+1}+C\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(n\ne -1\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{1}{x}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\ln|x|+C\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(x\ne 0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(e^x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(e^x+C\)</span></td>
<td>指数函数</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\sin x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(-\cos x+C\)</span></td>
<td>三角函数</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\cos x\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sin x+C\)</span></td>
<td>三角函数</td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{1}{1+x^2}\)</span></td>
<td><span displaypfx="inline-" class="mathjax-container">\(\arctan x+C\)</span></td>
<td>反三角函数</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">多重积分</span></div>
<p>多重积分（Multiple Integrals）是把一维积分的“连续求和”推广到更高维区域上的积分运算。它积分的对象不再只是线段上的小长度，而是平面区域上的小面积、空间区域中的小体积，或者曲面上的小面片。因此，多重积分处理的是“在二维、三维或更一般区域上，把局部量连续累加成整体量”的问题。</p>
<p>它的阅读难点通常不在计算本身，而在于符号会很快遮住几何直觉。理解的关键仍然是把“积分”看成连续求和，只不过求和对象已经从一维区间扩展到了更高维区域。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/multiple-integral.jpg"><img class="alignnone size-full wp-image-40789" src="https://blog.gmem.cc/wp-content/uploads/2026/03/multiple-integral.jpg" alt="multiple-integral" width="100%" /></a></p>
<div class="blog_h4"><span class="graybg">从一维面积到三维体积</span></div>
<p>一重积分 <span displaypfx="inline-" class="mathjax-container">\(\int f(x)\,dx\)</span> 常被理解为曲线下的面积：在 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 轴上切出很多极窄的小条，每条小条的面积近似是“高度 <span displaypfx="inline-" class="mathjax-container">\(\times\)</span> 宽度”，也就是 <span displaypfx="inline-" class="mathjax-container">\(f(x)\,dx\)</span>，再把它们全部加起来。</p>
<p>双重积分把这个想法从“一条线”推广到“一块区域”。设 <span displaypfx="inline-" class="mathjax-container">\(D\)</span> 是平面上的一块区域，曲面高度由 <span displaypfx="inline-" class="mathjax-container">\(z=f(x,y)\)</span> 给出，那么</p>
<span displaypfx="" class="mathjax-container">\[\iint_D f(x,y)\,dx\,dy\]</span>
<p>就可以理解为：在区域 <span displaypfx="inline-" class="mathjax-container">\(D\)</span> 的每个小块 <span displaypfx="inline-" class="mathjax-container">\(dx\,dy\)</span> 上立起一根很细的小柱子，柱高是 <span displaypfx="inline-" class="mathjax-container">\(f(x,y)\)</span>。这些小柱子拼起来，形成曲面下方的一整块立体体积。于是双重积分最直观的几何意义就是：<span style="background-color: #c0c0c0;">把区域上方由高度函数堆出来的整块三维体积连续累加起来</span>。</p>
<p>再往上推广，三重积分</p>
<span displaypfx="" class="mathjax-container">\[\iiint_\Omega f(x,y,z)\,dV\]</span>
<p>表示在空间区域 <span displaypfx="inline-" class="mathjax-container">\(\Omega\)</span> 内，对每个微小体积元 <span displaypfx="inline-" class="mathjax-container">\(dV\)</span> 的函数值做累加。此时它不一定是在“求一个更高维体积”，更准确地说，是在整个空间体内部对某个量做总汇总，例如总质量、总能量、总代价或总概率权重。可以把它和一重积分放在同一条理解链上来看：速度函数在时间上的累积给出位移，密度函数在空间上的累积给出质量，能量密度（Energy Density）在区域或体积上的累积给出总能量。若 <span displaypfx="inline-" class="mathjax-container">\(\rho(x,y)\)</span> 是面密度，则 <span displaypfx="inline-" class="mathjax-container">\(\iint_D \rho(x,y)\,dA\)</span> 表示区域 <span displaypfx="inline-" class="mathjax-container">\(D\)</span> 上的总质量；若 <span displaypfx="inline-" class="mathjax-container">\(\rho(x,y,z)\)</span> 是体密度，则 <span displaypfx="inline-" class="mathjax-container">\(\iiint_\Omega \rho(x,y,z)\,dV\)</span> 表示空间区域 <span displaypfx="inline-" class="mathjax-container">\(\Omega\)</span> 内的总质量。积分符号的重数，本质上对应的是累积区域的维度。</p>
<div class="blog_h4"><span class="graybg">高度函数到底表示什么</span></div>
<p>双重积分里最需要读懂的是 <span displaypfx="inline-" class="mathjax-container">\(f(x,y)\)</span>。它并不总是“真实高度”，而是一个随位置变化的量。你可以把它看成“每个点上放了多少东西”。积分的过程，就是把整块区域上的这些局部贡献全部收集起来。</p>
<ul>
<li>当 <span displaypfx="inline-" class="mathjax-container">\(f(x,y)=1\)</span> 时，每个位置的高度都恒为 1，所以积分值在数值上就等于区域 <span displaypfx="inline-" class="mathjax-container">\(D\)</span> 的面积。此时双重积分退化为“面积公式”。</li>
<li>当 <span displaypfx="inline-" class="mathjax-container">\(f(x,y)\)</span> 是密度函数（Density Function）时，积分得到的是总质量、总电荷或总概率。也就是说，函数值越大，说明该位置“堆得越多”，对总量贡献越大。</li>
<li>当 <span displaypfx="inline-" class="mathjax-container">\(f(x,y)\)</span> 表示代价、风险、热量、响应强度等场量时，积分得到的是这类量在整个区域上的累积结果。</li>
</ul>
<p>因此，多重积分做的是一件很具体的事：把无穷多个微小局部量累加成一个整体量。</p>
<div class="blog_h4"><span class="graybg">带圈的积分号是什么意思</span></div>
<p>有时会看到带圈的双积分号 ∯。这个圈表示积分域是闭合的（Closed）：不是一块敞开的平面区域，而是一个完整包起来的曲面，例如球壳表面、气泡表面或任意封闭外壳。为了把这个“带圈”符号直接看出来，下面就直接写成在闭合曲面 <span displaypfx="inline-" class="mathjax-container">\(S\)</span> 上的积分。</p>
<p style="text-align: center; font-size: 1.15em;"><span style="text-align: center; font-size: 2em;">∯</span><sub>S</sub> <span displaypfx="inline-" class="mathjax-container">\(\mathbf{F}\cdot d\mathbf{S}\)</span></p>
<p>这里的含义通常不是“求面积”，而是计算向量场（Vector Field）穿过闭合曲面的总通量（Flux）。直观地说，就是看有多少“流”从这个封闭外壳内部穿出，或者从外部流入。它和高斯散度定理（Gauss's Divergence Theorem）直接相关，因此在偏微分方程（PDE）、连续介质建模、计算流体以及物理信息神经网络（Physics-Informed Neural Networks, PINNs）中十分重要。</p>
<div class="blog_h4"><span class="graybg">多重积分和AI</span></div>
<p>在 AI 中，多重积分最常见的身份是<span style="background-color: #c0c0c0;">概率分布上的总量计算器</span>。如果 <span displaypfx="inline-" class="mathjax-container">\(p(x,y)\)</span> 是二维随机变量的概率密度，那么</p>
<span displaypfx="" class="mathjax-container">\[\iint p(x,y)\,dx\,dy=1\]</span>
<p>表示整张概率曲面下方的总体积必须等于 1。这句话的本质是：所有可能情况加起来，概率总和必须是 100%。</p>
<p>进一步，期望（Expectation）就是在这个概率地形上做加权平均。以一维为例，</p>
<span displaypfx="" class="mathjax-container">\[\mathbb{E}[X]=\int x\,p(x)\,dx\]</span>
<p>表示每个位置 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 都按其概率权重 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span> 参与平均；多维情形下则自然推广为多重积分。用几何语言说，期望就是这堆“概率质量”在各坐标方向上的重心位置。</p>
<p>这也是为什么高斯分布归一化、边缘分布（Marginal Distribution）、条件分布、变分推断（Variational Inference）、连续潜变量模型以及能量模型（Energy-Based Models）都离不开多重积分。很多时候模型真正要做的事，并不是求一个公式值，而是在高维概率空间里累计、归一化、消去变量或计算期望。</p>
<div class="blog_h2"><span class="graybg">卷积（Convolution）</span></div>
<p>卷积（Convolution）把两个函数/序列组合成第三个函数/序列，最常见的语义是：<span style="background-color: #c0c0c0;">“一个信号在另一个信号的权重/响应下做累积叠加”</span>。它同时是信号处理（Signal Processing）、系统理论（Systems Theory）与 CNN 的基础运算。</p>
<div class="blog_h3"><span class="graybg">连续卷积（Continuous Convolution）</span></div>
<p>对连续函数 <span displaypfx="inline-" class="mathjax-container">\(f(t)\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(g(t)\)</span>，卷积定义为：</p>
<span displaypfx="" class="mathjax-container">\[(f*g)(t)=\int_{-\infty}^{+\infty} f(\tau)\,g(t-\tau)\,d\tau\]</span>
<p>经典记忆法：<span style="background-color: #c0c0c0;">翻转（Flip）→ 平移（Shift）→ 相乘（Multiply）→ 积分（Integrate）</span>。其中 <span displaypfx="inline-" class="mathjax-container">\(g(t-\tau)\)</span> 这一项等价于把 <span displaypfx="inline-" class="mathjax-container">\(g\)</span> 先时间反转再按 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 平移。</p>
<div class="blog_h3"><span class="graybg">离散卷积（Discrete Convolution）</span></div>
<p>对离散序列 <span displaypfx="inline-" class="mathjax-container">\(x[n]\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(h[n]\)</span>，卷积定义为：</p>
<span displaypfx="" class="mathjax-container">\[(x*h)[n]=\sum_{k=-\infty}^{+\infty} x[k]\,h[n-k]\]</span>
<p>当序列有限长度时，上式求和区间会自然收缩为有限项。二维卷积（2D Convolution）/图像滤波可以视为把求和从一维扩展到二维索引。</p>
<div class="blog_h3"><span class="graybg">因果性与单侧卷积（Causality）</span></div>
<p>工程系统常满足因果性（Causality）：系统在时刻 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 的输出只依赖过去与当前输入，不依赖未来输入。若输入 <span displaypfx="inline-" class="mathjax-container">\(f(t)\)</span> 与系统的冲激响应（Impulse Response）<span displaypfx="inline-" class="mathjax-container">\(g(t)\)</span> 都是因果的（<span displaypfx="inline-" class="mathjax-container">\(t&lt;0\)</span> 时为 0），则卷积积分可写成单侧形式：</p>
<span displaypfx="" class="mathjax-container">\[(f*g)(t)=\int_{0}^{t} f(\tau)\,g(t-\tau)\,d\tau\]</span>
<p>这个形式更符合直觉：时刻 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 的结果由 <span displaypfx="inline-" class="mathjax-container">\([0,t]\)</span> 区间内“所有过去输入”的贡献叠加而来。</p>
<div class="blog_h3"><span class="graybg">直觉例子：打点滴的累积药效</span></div>
<p>把卷积理解成“累积药效”通常更直观。设 <span displaypfx="inline-" class="mathjax-container">\(r(t)\)</span> 是单位时间的滴注速率（Infusion Rate），<span displaypfx="inline-" class="mathjax-container">\(h(t)\)</span> 是“单位剂量在体内的药效/浓度随时间衰减曲线”（可以把它理解为冲激响应）。在时刻 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 的总药效/浓度 <span displaypfx="inline-" class="mathjax-container">\(c(t)\)</span> 可以写成：</p>
<span displaypfx="" class="mathjax-container">\[c(t)=(r*h)(t)=\int_{0}^{t} r(\tau)\,h(t-\tau)\,d\tau\]</span>
<p>解释：在过去每个时刻 <span displaypfx="inline-" class="mathjax-container">\(\tau\)</span> 滴入的一小部分药量 <span displaypfx="inline-" class="mathjax-container">\(r(\tau)\,d\tau\)</span>，到现在时刻 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 已经“经过了” <span displaypfx="inline-" class="mathjax-container">\(t-\tau\)</span> 的代谢时间，因此它的残留贡献按 <span displaypfx="inline-" class="mathjax-container">\(h(t-\tau)\)</span> 衰减；把所有过去贡献叠加，就得到当前总效果。这正是卷积“用一个响应核去累积历史”的核心语义。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/convolution.jpg"><img class="alignnone size-full wp-image-40805" src="https://blog.gmem.cc/wp-content/uploads/2026/03/convolution.jpg" alt="convolution" width="100%" /></a></p>
<div class="blog_h3"><span class="graybg">卷积与互相关（Cross-Correlation）</span></div>
<p>互相关（Cross-Correlation）与卷积的形式非常接近，但没有“核翻转”（Kernel Flip）：一维离散互相关常写成 <span displaypfx="inline-" class="mathjax-container">\(\sum_k x[k]\,w[n+k]\)</span>（不同资料索引略有差异）。深度学习框架里多数“卷积层”实现的是互相关，而不是严格数学卷积；由于卷积核参数是可学习的，翻不翻转并不会改变可表达的函数族，但在信号处理里二者语义不同，需注意约定。</p>
<div class="blog_h2"><span class="graybg">链式法则</span></div>
<p>链式法则（Chain Rule）是反向传播（Backpropagation）的核心：当计算图（Computation Graph）由一系列函数复合组成时，总导数等于沿路径的局部导数相乘。</p>
<p>在微分语言下更紧凑：若 <span displaypfx="inline-" class="mathjax-container">\(y=g(x)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(z=f(y)\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(dz=f'(y)\,dy\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(dy=g'(x)\,dx\)</span>，合并得 <span displaypfx="inline-" class="mathjax-container">\(dz=f'(g(x))g'(x)\,dx\)</span>。反向传播做的就是把这些局部“系数”从输出一路乘回输入。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/rule-of-chains.jpg"><img class="alignnone size-full wp-image-40809" src="https://blog.gmem.cc/wp-content/uploads/2026/03/rule-of-chains.jpg" alt="rule-of-chains" width="100%" /></a></p>
<div class="blog_h2"><span class="graybg">最小二乘法</span></div>
<p>最小二乘法（Least Squares）解决“拟合误差最小”的问题。给定样本矩阵 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 与目标 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span>，线性模型 <span displaypfx="inline-" class="mathjax-container">\(\hat{\mathbf{y}}=X\mathbf{w}\)</span> 的目标是：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w}}\ \|X\mathbf{w}-\mathbf{y}\|_2^2\]</span>
<p>当 <span displaypfx="inline-" class="mathjax-container">\(X^\top X\)</span> 可逆时，闭式解满足正规方程（Normal Equations）：</p>
<span displaypfx="" class="mathjax-container">\[X^\top X\mathbf{w}=X^\top\mathbf{y},\quad \mathbf{w}^*=(X^\top X)^{-1}X^\top\mathbf{y}\]</span>
<p>几何直觉： <span displaypfx="inline-" class="mathjax-container">\(X\mathbf{w}\)</span> 只能落在 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 的列空间（Column Space）中，最小二乘解就是把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{y}\)</span> 正交投影（Orthogonal Projection）到这个空间上，剩余误差向量与列空间正交。</p>
<p>在 AI/统计实践中，若 <span displaypfx="inline-" class="mathjax-container">\(X^\top X\)</span> 病态或奇异，常用岭回归（Ridge Regression）：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w}}\ \|X\mathbf{w}-\mathbf{y}\|_2^2+\lambda\|\mathbf{w}\|_2^2,\quad (X^\top X+\lambda I)\mathbf{w}=X^\top\mathbf{y}\]</span>
<p>它本质上是把二次目标做稳定化，降低方差并提升泛化。</p>
<div class="blog_h2"><span class="graybg">凸函数与凸优化</span></div>
<p>凸函数（Convex Function）是“碗状”的函数：任意两点连线上的函数值不超过端点函数值的线性插值。形式化定义：</p>
<span displaypfx="" class="mathjax-container">\[f(\lambda x_1+(1-\lambda)x_2)\le \lambda f(x_1)+(1-\lambda)f(x_2),\quad \lambda\in[0,1]\]</span>
<p>凸优化（Convex Optimization）之所以重要，是因为凸目标在凸可行域上没有“坏局部最小值”：任一局部最小值都是全局最小值。</p>
<p>若 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 二阶可导，则 Hessian 提供了最直接的局部判据：在一维中 <span displaypfx="inline-" class="mathjax-container">\(f''(x)\ge 0\)</span> 对应凸；在多维中，若对所有 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 都有 <span displaypfx="inline-" class="mathjax-container">\(\nabla^2 f(x)\succeq 0\)</span>（半正定，Positive Semidefinite），则 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 是凸函数。若处处 <span displaypfx="inline-" class="mathjax-container">\(\nabla^2 f(x)\succ 0\)</span>（正定，Positive Definite），则函数通常具有更强的严格凸性（Strict Convexity）。</p>
<p>损失函数的形状可以非常多样。需要区分两层含义：</p>
<ul>
<li>损失形式本身：例如 BCE/CE（对概率或对线性模型的 logits）是凸的，MSE 也是凸的。</li>
<li>对参数的整体目标：当把损失与深度网络的非线性参数化组合后，目标函数通常变成非凸（Non-convex），这不是“交叉熵不凸”，而是“网络映射让问题不凸”。</li>
</ul>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/loss-fn-convex.jpg"><img class="alignnone size-full wp-image-40813" src="https://blog.gmem.cc/wp-content/uploads/2026/03/loss-fn-convex.jpg" alt="loss-fn-convex" width="100%" /></a></p>
<div class="blog_h3"><span class="graybg">拉格朗日乘数法</span></div>
<p>拉格朗日乘数法（Lagrange Multiplier Method）处理约束优化（Constrained Optimization）：在满足约束的前提下最小化/最大化目标函数。常用写法是把约束写成函数形式：等式约束（Equality Constraints）<span displaypfx="inline-" class="mathjax-container">\(g_i(x)=0\)</span>，不等式约束（Inequality Constraints）<span displaypfx="inline-" class="mathjax-container">\(g_i(x)\le 0\)</span>。</p>
<p>对等式约束，构造拉格朗日函数（Lagrangian）：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}(x,\lambda)=f(x)+\sum_{i=1}^{m}\lambda_i g_i(x)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span> 是目标函数（Objective），<span displaypfx="inline-" class="mathjax-container">\(g_i(x)\)</span> 是约束函数（Constraint Function），<span displaypfx="inline-" class="mathjax-container">\(\lambda_i\)</span> 是拉格朗日乘子（Lagrange Multiplier）。新增部分 <span displaypfx="inline-" class="mathjax-container">\(\sum_i \lambda_i g_i(x)\)</span> 称为拉格朗日项（Lagrange Term），单个约束对应的项是 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i g_i(x)\)</span>。</p>
<p>在不同应用里乘子的记号可能不同：在优化理论里常写 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i\)</span>，在支持向量机（SVM）里常写 <span displaypfx="inline-" class="mathjax-container">\(\alpha_i\)</span>（每个样本约束对应一个乘子）。</p>
<p>把 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}(x,\lambda)\)</span> 看作关于原变量 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 与乘子 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 的二元函数后，鞍点（Saddle Point）结构是约束被编码进目标函数后的直接结果。 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 的角色是压低总代价， <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 的角色是放大任何尚未满足的约束，因此两者天然形成极小极大（Min-Max）结构。</p>
<p>先看等式约束 <span displaypfx="inline-" class="mathjax-container">\(g(x)=0\)</span>。这里必须先把“为什么内层是最大化”说清楚：这不是记号习惯，而是为了把约束编码成一个<span style="background-color: #c0c0c0;">可行点保留原目标、不可行点直接罚到 <span displaypfx="inline-" class="mathjax-container">\(+\infty\)</span></span> 的机制。因此，固定某个 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 后，内层写成 <span displaypfx="inline-" class="mathjax-container">\(\max_\lambda \, (f(x)+\lambda g(x))\)</span>。如果 <span displaypfx="inline-" class="mathjax-container">\(g(x)\neq 0\)</span>，由于 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 不受符号限制，最大化者总能把 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 取到与 <span displaypfx="inline-" class="mathjax-container">\(g(x)\)</span> 同号且绝对值任意大，使 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}(x,\lambda)\to\infty\)</span>；只有当 <span displaypfx="inline-" class="mathjax-container">\(g(x)=0\)</span> 时，拉格朗日项才消失，内层最大值才退化为 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span>。因此 <span displaypfx="inline-" class="mathjax-container">\(\max_\lambda \mathcal{L}(x,\lambda)\)</span> 的作用，是把所有不满足等式约束的点直接排除掉。</p>
<p>不等式约束更容易看出这种“过滤”机制。若约束是 <span displaypfx="inline-" class="mathjax-container">\(g(x)\le 0\)</span> 且乘子满足 <span displaypfx="inline-" class="mathjax-container">\(\lambda\ge 0\)</span>，那么当 <span displaypfx="inline-" class="mathjax-container">\(g(x)&gt;0\)</span> 时，最大化者会把 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 推大，使 <span displaypfx="inline-" class="mathjax-container">\(\lambda g(x)\to\infty\)</span>；当 <span displaypfx="inline-" class="mathjax-container">\(g(x)\le 0\)</span> 时，继续增大 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 只会让值变小，因此最优选择是 <span displaypfx="inline-" class="mathjax-container">\(\lambda=0\)</span>。于是 <span displaypfx="inline-" class="mathjax-container">\(\max_{\lambda\ge 0}\mathcal{L}(x,\lambda)\)</span> 对可行点返回原始代价 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span>，对不可行点返回“无限罚款”。外层再对 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 做最小化，就等价于只在可行域内最小化 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span>。</p>
<span displaypfx="" class="mathjax-container">\[\min_x\ \max_\lambda\ \mathcal{L}(x,\lambda)\]</span>
<p>几何上，这正对应马鞍形曲面：沿 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 方向切开， <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}\)</span> 像一个向上开的“碗”，因为这里在做最小化；沿 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 方向切开， <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}\)</span> 像一个向下开的“拱”，因为这里在做最大化。鞍点 <span displaypfx="inline-" class="mathjax-container">\((x^*,\lambda^*)\)</span> 的含义是：固定 <span displaypfx="inline-" class="mathjax-container">\(\lambda=\lambda^*\)</span> 时， <span displaypfx="inline-" class="mathjax-container">\(x^*\)</span> 已经不能再把值压低；固定 <span displaypfx="inline-" class="mathjax-container">\(x=x^*\)</span> 时， <span displaypfx="inline-" class="mathjax-container">\(\lambda^*\)</span> 也已经不能再把值抬高。原问题的最优解与约束的恰当作用强度，就在这个交点同时确定。</p>
<p>记号约定：上标星号（Asterisk）<span displaypfx="inline-" class="mathjax-container">\(^*\)</span> 通常表示“最优/最优点处的取值”，例如 <span displaypfx="inline-" class="mathjax-container">\(x^*\)</span> 是最优解， <span displaypfx="inline-" class="mathjax-container">\(\lambda^*\)</span> 是与之对应的最优乘子。</p>
<p>在鞍点 <span displaypfx="inline-" class="mathjax-container">\((x^*,\lambda^*)\)</span>，固定 <span displaypfx="inline-" class="mathjax-container">\(\lambda=\lambda^*\)</span> 时 <span displaypfx="inline-" class="mathjax-container">\(x^*\)</span> 使 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}\)</span> 最小；固定 <span displaypfx="inline-" class="mathjax-container">\(x=x^*\)</span> 时 <span displaypfx="inline-" class="mathjax-container">\(\lambda^*\)</span> 使 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}\)</span> 最大，可用不等式写成：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}(x^*,\lambda)\le \mathcal{L}(x^*,\lambda^*)\le \mathcal{L}(x,\lambda^*)\]</span>
<p>上面的鞍点不等式给出的是几何刻画；真正求解时，还需要把“这个点已经不能再降、也不能再升”的直观条件改写成可计算的方程。做法是检查拉格朗日函数（Lagrangian）<span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}\)</span> 对各个变量的一阶变化：若在候选点附近，沿某个允许方向还能继续把值压低（对 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>）或继续把值抬高（对 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span>），那么该点就不可能是鞍点。</p>
<p>这就是一阶必要条件（First-Order Necessary Condition）的来源：若 <span displaypfx="inline-" class="mathjax-container">\(x^*\)</span> 是最优解，则一阶（线性）变化项必须已经消失，否则还存在继续改进的方向。这里“必要”不等于“充分”：满足一阶条件只说明它有可能是最优点，不说明它一定最优；要得到充分结论，还需要二阶条件、凸性（Convexity）或其他结构。</p>
<p>无约束情形最直接。在一维中，若 <span displaypfx="inline-" class="mathjax-container">\(x^*\)</span> 是可微且不在边界上的局部最小点，则向左或向右移动都不能让函数更小，因此斜率必须满足 <span displaypfx="inline-" class="mathjax-container">\(f'(x^*)=0\)</span>；多维里把“斜率”推广为梯度（Gradient），于是条件变为 <span displaypfx="inline-" class="mathjax-container">\(\nabla f(x^*)=0\)</span>。</p>
<p>把同样的思路移到等式约束上，求解目标就从“找原函数 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 的极小点”变成“找拉格朗日函数 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}(x,\lambda)\)</span> 的鞍点”。对可导问题，一阶条件写成：</p>
<span displaypfx="" class="mathjax-container">\[\nabla_x \mathcal{L}(x^*,\lambda^*)=0,\quad g_i(x^*)=0\]</span>
<p>第一式表示：固定 <span displaypfx="inline-" class="mathjax-container">\(\lambda=\lambda^*\)</span> 后，已经找不到能继续降低 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}\)</span> 的 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 方向；第二式就是可行性（Feasibility）本身。又因为 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial \lambda_i}=g_i(x)\)</span>，所以“对乘子求偏导为 0”本质上只是把约束 <span displaypfx="inline-" class="mathjax-container">\(g_i(x)=0\)</span> 原样写回。</p>
<p>不等式约束时，乘子必须满足 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i\ge 0\)</span>，因此鞍点结构相应写成：</p>
<span displaypfx="" class="mathjax-container">\[\min_x\ \max_{\lambda\ge 0}\ \mathcal{L}(x,\lambda)\]</span>
<p>此时除了驻点条件，还必须同时满足 KKT 条件（Karush–Kuhn–Tucker Conditions）。其中最关键的是互补松弛（Complementary Slackness）<span displaypfx="inline-" class="mathjax-container">\(\lambda_i g_i(x^*)=0\)</span>：每个约束在最优点只有两种状态——要么它是紧的（Active），即 <span displaypfx="inline-" class="mathjax-container">\(g_i(x^*)=0\)</span>；要么它不紧，此时对应乘子必须为 0，表示该约束在最优点处没有实际作用。</p>
<p>例（等式约束）：最小化 <span displaypfx="inline-" class="mathjax-container">\(f(x,y)=x^2+y^2\)</span>，约束 <span displaypfx="inline-" class="mathjax-container">\(x+y=1\)</span>。拉格朗日函数：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}(x,y,\lambda)=x^2+y^2+\lambda(x+y-1)\]</span>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/plot-lagrange.png"><img class="alignnone size-full wp-image-40821" src="https://blog.gmem.cc/wp-content/uploads/2026/03/plot-lagrange.png" alt="plot-lagrange" width="100%" /></a></p>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\lambda(x+y-1)\)</span> 是拉格朗日项（Lagrange Term）：它把等式约束 <span displaypfx="inline-" class="mathjax-container">\(x+y-1=0\)</span> 以乘子加权的形式并入目标函数。</p>
<p>令偏导为零： <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial x}=2x+\lambda=0\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial y}=2y+\lambda=0\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial \lambda}=x+y-1=0\)</span>。注意 <span displaypfx="inline-" class="mathjax-container">\(\frac{\partial \mathcal{L}}{\partial \lambda}=0\)</span> 就是把约束 <span displaypfx="inline-" class="mathjax-container">\(x+y=1\)</span> 写回来；联立可得 <span displaypfx="inline-" class="mathjax-container">\(x=y=\frac{1}{2}\)</span>。几何上，这意味着最优点处目标函数等高线与约束曲线相切。</p>
<p>例（不等式约束）：最小化 <span displaypfx="inline-" class="mathjax-container">\(f(x)=x^2\)</span>，约束 <span displaypfx="inline-" class="mathjax-container">\(x\ge 1\)</span>。直觉上，无约束最小值在 <span displaypfx="inline-" class="mathjax-container">\(x=0\)</span>，但它不满足约束，所以最优点只能落在边界 <span displaypfx="inline-" class="mathjax-container">\(x=1\)</span>。</p>
<ol>
<li>把约束改写成标准形式：令 <span displaypfx="inline-" class="mathjax-container">\(g(x)=1-x\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(x\ge 1\Leftrightarrow g(x)\le 0\)</span>。</li>
<li>构造拉格朗日函数： <span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}(x,\lambda)=f(x)+\lambda g(x)=x^2+\lambda(1-x)\)</span>。其中 <span displaypfx="inline-" class="mathjax-container">\(\lambda(1-x)\)</span> 是拉格朗日项（Lagrange Term），不等式情形要求 <span displaypfx="inline-" class="mathjax-container">\(\lambda\ge 0\)</span>。</li>
<li>写出 KKT 的核心条件：可行性（Feasibility）<span displaypfx="inline-" class="mathjax-container">\(1-x\le 0\)</span>；乘子非负 <span displaypfx="inline-" class="mathjax-container">\(\lambda\ge 0\)</span>；驻点（Stationarity）<span displaypfx="inline-" class="mathjax-container">\(\frac{d\mathcal{L}}{dx}=2x-\lambda=0\)</span>；互补松弛（Complementary Slackness）<span displaypfx="inline-" class="mathjax-container">\(\lambda(1-x)=0\)</span>。</li>
<li>解：由 <span displaypfx="inline-" class="mathjax-container">\(2x-\lambda=0\)</span> 得 <span displaypfx="inline-" class="mathjax-container">\(\lambda=2x\)</span>。互补松弛要求 <span displaypfx="inline-" class="mathjax-container">\(\lambda=0\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(x=1\)</span>。若 <span displaypfx="inline-" class="mathjax-container">\(\lambda=0\)</span> 则 <span displaypfx="inline-" class="mathjax-container">\(x=0\)</span>，但违反 <span displaypfx="inline-" class="mathjax-container">\(x\ge 1\)</span>；因此 <span displaypfx="inline-" class="mathjax-container">\(x^*=1\)</span>，进而 <span displaypfx="inline-" class="mathjax-container">\(\lambda^*=2\)</span>。</li>
<li>解释：在最优点处 <span displaypfx="inline-" class="mathjax-container">\(1-x^*=0\)</span>，该约束是“紧的”（Active），互补松弛允许乘子 <span displaypfx="inline-" class="mathjax-container">\(\lambda^*&gt;0\)</span>，它刻画了该约束在最优点处对解的影响强度。如果最优点落在可行域（Feasible Set）内部（约束不紧， <span displaypfx="inline-" class="mathjax-container">\(1-x^*&lt;0\)</span>），互补松弛会强制 <span displaypfx="inline-" class="mathjax-container">\(\lambda^*=0\)</span>。</li>
</ol>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/plot-lagrange-2.png"><img class="alignnone size-full wp-image-40825" src="https://blog.gmem.cc/wp-content/uploads/2026/03/plot-lagrange-2.png" alt="plot-lagrange-2" width="100%" /></a></p>
<div class="blog_h3"><span class="graybg">对偶问题</span></div>
<p>对偶问题（Dual Problem）从原始问题（Primal Problem）导出下界函数，用于分析可行性、间隙与最优性。标准形式：</p>
<span displaypfx="" class="mathjax-container">\[\min_x\ f(x)\ \text{s.t.}\ g_i(x)\le 0,\ h_j(x)=0\]</span>
<span displaypfx="" class="mathjax-container">\[g(\lambda,\nu)=\inf_x\Big(f(x)+\sum_i \lambda_i g_i(x)+\sum_j \nu_j h_j(x)\Big),\ \lambda_i\ge 0\]</span>
<span displaypfx="" class="mathjax-container">\[\max_{\lambda,\nu}\ g(\lambda,\nu)\ \text{s.t.}\ \lambda\ge 0\]</span>
<p>把括号中的表达式记为拉格朗日函数（Lagrangian）<span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}(x,\lambda,\nu)=f(x)+\sum_i \lambda_i g_i(x)+\sum_j \nu_j h_j(x)\)</span>，则对偶函数（Dual Function）就是 <span displaypfx="inline-" class="mathjax-container">\(g(\lambda,\nu)=\inf_x \mathcal{L}(x,\lambda,\nu)\)</span>：固定乘子后先对原变量求下确界，把它“消去”，再对乘子最大化这个下界。</p>
<div class="blog_h4"><span class="graybg">例：SVM 对偶函数是怎么把变量“消掉”的</span></div>
<p>SVM 的对偶函数（Dual Function）来自一个标准过程：先写出原始问题（Primal Problem）的拉格朗日函数（Lagrangian），再对原变量取下确界（Infimum），把优化问题改写为只依赖乘子变量的形式。在线性支持向量机（Support Vector Machine, SVM）里，这个过程最清楚，因为 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\alpha}\)</span> 的出现既解释了“为什么叫对偶”，也把“把原变量消掉”变成了可逐步计算的代换。</p>
<p>考虑硬间隔（Hard-margin）线性 SVM。原始问题是：</p>
<span displaypfx="" class="mathjax-container">\[\min_{\mathbf{w},b}\ \frac{1}{2}\|\mathbf{w}\|_2^2\quad \text{s.t.}\quad y_i(\mathbf{w}^\top \mathbf{x}_i+b)-1\ge 0,\ i=1,\dots,n\]</span>
<p>把约束改写成标准不等式形式：</p>
<span displaypfx="" class="mathjax-container">\[g_i(\mathbf{w},b)=1-y_i(\mathbf{w}^\top \mathbf{x}_i+b)\le 0\]</span>
<p>于是拉格朗日函数为：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}(\mathbf{w},b,\boldsymbol{\alpha})=\frac{1}{2}\|\mathbf{w}\|_2^2+\sum_{i=1}^{n}\alpha_i\big(1-y_i(\mathbf{w}^\top \mathbf{x}_i+b)\big),\quad \alpha_i\ge 0\]</span>
<p>对偶函数（Dual Function）的定义是：固定乘子 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\alpha}\)</span>，对原变量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},b\)</span> 取下确界：</p>
<span displaypfx="" class="mathjax-container">\[g(\boldsymbol{\alpha})=\inf_{\mathbf{w},b}\mathcal{L}(\mathbf{w},b,\boldsymbol{\alpha})\]</span>
<p>这里的关键结构已经出现了：原始问题直接优化 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},b\)</span>，而对偶函数先把 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\alpha}\)</span> 固定住，把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},b\)</span> 当成待消去变量。随后得到的最大化问题，只剩乘子变量 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\alpha}\)</span>。这就是“对偶”的来源：<span style="background-color: #c0c0c0;">同一个最优化问题，被改写成了另一组变量上的伴随优化问题</span>。</p>
<p>现在把 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},b\)</span> 完整消掉。先求驻点条件（Stationarity Conditions）。对 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 求偏导：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial \mathcal{L}}{\partial \mathbf{w}}=\mathbf{w}-\sum_{i=1}^{n}\alpha_i y_i \mathbf{x}_i=\mathbf{0}\]</span>
<p>因此</p>
<span displaypfx="" class="mathjax-container">\[\mathbf{w}=\sum_{i=1}^{n}\alpha_i y_i \mathbf{x}_i\]</span>
<p>对 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 求偏导：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\partial \mathcal{L}}{\partial b}=-\sum_{i=1}^{n}\alpha_i y_i=0\]</span>
<p>因此</p>
<span displaypfx="" class="mathjax-container">\[\sum_{i=1}^{n}\alpha_i y_i=0\]</span>
<p>把这些结果代回拉格朗日函数之前，先把它展开成便于逐项代换的形式：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}(\mathbf{w},b,\boldsymbol{\alpha})=\frac{1}{2}\mathbf{w}^\top\mathbf{w}+\sum_{i=1}^{n}\alpha_i-\sum_{i=1}^{n}\alpha_i y_i\,\mathbf{w}^\top\mathbf{x}_i-b\sum_{i=1}^{n}\alpha_i y_i\]</span>
<p>第一项代入 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}=\sum_{i=1}^{n}\alpha_i y_i \mathbf{x}_i\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[\frac{1}{2}\mathbf{w}^\top\mathbf{w}=\frac{1}{2}\Big(\sum_{i=1}^{n}\alpha_i y_i \mathbf{x}_i\Big)^\top\Big(\sum_{j=1}^{n}\alpha_j y_j \mathbf{x}_j\Big)\]</span>
<span displaypfx="" class="mathjax-container">\[=\frac{1}{2}\sum_{i=1}^{n}\sum_{j=1}^{n}\alpha_i\alpha_j y_i y_j\,\mathbf{x}_i^\top\mathbf{x}_j\]</span>
<p>第二项保持不变：</p>
<span displaypfx="" class="mathjax-container">\[\sum_{i=1}^{n}\alpha_i\]</span>
<p>第三项代入同一个 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 表达式：</p>
<span displaypfx="" class="mathjax-container">\[-\sum_{i=1}^{n}\alpha_i y_i\,\mathbf{w}^\top\mathbf{x}_i=-\sum_{i=1}^{n}\alpha_i y_i\Big(\sum_{j=1}^{n}\alpha_j y_j \mathbf{x}_j\Big)^\top\mathbf{x}_i\]</span>
<span displaypfx="" class="mathjax-container">\[=-\sum_{i=1}^{n}\sum_{j=1}^{n}\alpha_i\alpha_j y_i y_j\,\mathbf{x}_j^\top\mathbf{x}_i\]</span>
<span displaypfx="" class="mathjax-container">\[=-\sum_{i=1}^{n}\sum_{j=1}^{n}\alpha_i\alpha_j y_i y_j\,\mathbf{x}_i^\top\mathbf{x}_j\]</span>
<p>最后一项利用 <span displaypfx="inline-" class="mathjax-container">\(\sum_{i=1}^{n}\alpha_i y_i=0\)</span> 直接消失：</p>
<span displaypfx="" class="mathjax-container">\[-b\sum_{i=1}^{n}\alpha_i y_i=0\]</span>
<p>因此</p>
<span displaypfx="" class="mathjax-container">\[g(\boldsymbol{\alpha})=\frac{1}{2}\sum_{i=1}^{n}\sum_{j=1}^{n}\alpha_i\alpha_j y_i y_j\,\mathbf{x}_i^\top\mathbf{x}_j+\sum_{i=1}^{n}\alpha_i-\sum_{i=1}^{n}\sum_{j=1}^{n}\alpha_i\alpha_j y_i y_j\,\mathbf{x}_i^\top\mathbf{x}_j\]</span>
<span displaypfx="" class="mathjax-container">\[=\sum_{i=1}^{n}\alpha_i-\frac{1}{2}\sum_{i=1}^{n}\sum_{j=1}^{n}\alpha_i\alpha_j y_i y_j\,\mathbf{x}_i^\top\mathbf{x}_j\]</span>
<p>于是对偶问题（Dual Problem）就是：</p>
<span displaypfx="" class="mathjax-container">\[\max_{\boldsymbol{\alpha}}\ g(\boldsymbol{\alpha})\quad \text{s.t.}\quad \alpha_i\ge 0,\ \sum_{i=1}^{n}\alpha_i y_i=0\]</span>
<p>此时“对偶”二字的含义就精确了：原始问题在参数空间里直接求 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w},b\)</span>，对偶问题则在乘子空间里求 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\alpha}\)</span>；两者来自同一个拉格朗日结构，描述的是同一个最优解的两种表示。对 SVM 而言，更重要的结构变化是：数据 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_i\)</span> 不再单独出现，而只通过内积（Inner Product）<span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}_i^\top\mathbf{x}_j\)</span> 进入目标函数，这正是核技巧（Kernel Trick）的入口。</p>
<p>若问题是凸的且满足 Slater 条件，则强对偶（Strong Duality）成立，可以用鞍点形式表达“先最小化再最大化”与“先最大化再最小化”等价：</p>
<span displaypfx="" class="mathjax-container">\[\min_x\max_{\lambda\ge 0,\nu}\mathcal{L}(x,\lambda,\nu)=\max_{\lambda\ge 0,\nu}\min_x\mathcal{L}(x,\lambda,\nu)\]</span>
<p>对偶函数之所以是“下界”，来自一个简单但关键的不等式：对任意可行解 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>（满足所有约束）与任意 <span displaypfx="inline-" class="mathjax-container">\(\lambda\ge 0\)</span>，都有</p>
<span displaypfx="" class="mathjax-container">\[f(x)\ge f(x)+\sum_i \lambda_i g_i(x)+\sum_j \nu_j h_j(x)\ge \inf_{x'}\Big(f(x')+\sum_i \lambda_i g_i(x')+\sum_j \nu_j h_j(x')\Big)=g(\lambda,\nu)\]</span>
<p>因此对任何 <span displaypfx="inline-" class="mathjax-container">\((\lambda,\nu)\)</span>，都有 <span displaypfx="inline-" class="mathjax-container">\(g(\lambda,\nu)\le p^*\)</span>（原始最优值），这就是弱对偶（Weak Duality）。</p>
<p>弱对偶（Weak Duality）总成立；也就是说，对偶问题给出的值永远不会高于原始问题的最优值。但弱对偶只保证“对偶值是下界”，并不保证这个下界恰好贴住原始最优值。若两者之间还差一截，这个差距就叫对偶间隙（Duality Gap）。</p>
<p>强对偶（Strong Duality）则更进一步：它要求原始最优值与对偶最优值完全相等，也就是对偶问题不只是给出一个保守下界，而是精确刻画了原问题的最优值。对很多凸优化问题而言，Slater 条件正是让这种“下界刚好贴住最优值”的关键保证。</p>
<p>从直观上看，Slater 条件要求可行域内部真的存在一个严格可行点。这意味着约束系统不是被边界死死卡住的退化结构，而是有真实的内部空间。可行域一旦有内部，拉格朗日乘子就更容易稳定地描述“目标函数下降趋势”和“约束反作用力”之间的平衡，因此原始问题与对偶问题之间更不容易出现缝隙。在凸优化里，这就是为什么 Slater 条件常被看作强对偶成立的重要通行证。</p>
<p>直观上，对偶变量（Dual Variables）可以看作约束的“影子价格（Shadow Price）”：如果把约束放宽一点点，最优目标值会如何变化。在很多问题中（例如支持向量机（SVM）），对偶化会把优化变量从“模型参数”转成“约束乘子”，并把数据依赖压缩为内积，从而自然导出核技巧（Kernel Trick）。</p>
<div class="blog_h4"><span class="graybg">影子价格（Shadow Price）</span></div>
<p>对偶变量（Dual Variable）常被称为影子价格（Shadow Price），因为它衡量的是：<span style="background-color: #c0c0c0;">如果把某条约束稍微放宽一点，最优目标值会改善多少</span>。这里的“价格”不是市场价格，而是“约束资源有多值钱”的边际刻度。</p>
<p>可以把约束想成一种稀缺资源。例如训练时有显存限制、预算限制、风险限制或几何边界限制；如果某条约束非常紧，那么它就像一个卡脖子的瓶颈。此时只要把这条约束稍微放宽一点，最优目标值就可能明显改善，于是它对应的对偶变量就会比较大。反过来，如果某条约束本来就很松，放宽它也几乎没有收益，那么它对应的对偶变量通常就是 0 或接近 0。</p>
<p>在数学上，这个直觉可以写成一种局部敏感度关系。若把约束写成</p>
<span displaypfx="" class="mathjax-container">\[g_i(x)\le b_i\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(b_i\)</span> 表示第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 条约束允许的“资源上限”或“边界位置”，那么对应的最优值函数可以记为 <span displaypfx="inline-" class="mathjax-container">\(p^*(b)\)</span>。在适当条件下，对偶变量 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i^*\)</span> 可以理解为最优值对 <span displaypfx="inline-" class="mathjax-container">\(b_i\)</span> 的边际变化率，也就是“把第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 条约束放宽一点，最优值会朝什么方向变化、变化多快”。</p>
<p>在最小化问题中，若某条约束对应的 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i^*\)</span> 很大，通常表示这条约束非常关键：它一旦被放宽，最优目标值会明显下降；若 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i^*=0\)</span>，则说明该约束在当前最优点并没有真正起作用，放宽它也不会立刻带来收益。这与前面的互补松弛完全一致：<span style="background-color: #c0c0c0;">只有真正卡住最优解的约束，才会拥有非零影子价格</span>。</p>
<p>因此，影子价格提供了一个非常有用的解释视角：原始变量告诉我们“最优解长什么样”，而对偶变量告诉我们“哪些约束最贵、最紧、最值得被放宽”。这也是为什么在优化理论、经济学和机器学习里，对偶变量不仅是求解工具，也是理解模型结构的重要语言。</p>
<div class="blog_h4"><span class="graybg">Slater 条件</span></div>
<p>Slater 条件（Slater's Condition）是凸优化（Convex Optimization）里最常见的正则性条件（Regularity Condition）之一。它的作用不是改变优化问题本身，而是保证对偶理论能够“工作得很干净”：在满足它时，很多凸问题会满足强对偶（Strong Duality），也就是原始问题最优值与对偶问题最优值相等；同时，KKT 条件也更容易从“必要条件”提升为判定最优性的核心条件。</p>
<p>对标准凸优化问题</p>
<span displaypfx="" class="mathjax-container">\[ \min_x f(x)\quad \text{s.t.}\quad g_i(x)\le 0,\ h_j(x)=0 \]</span>
<p>如果 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span> 和每个 <span displaypfx="inline-" class="mathjax-container">\(g_i(x)\)</span> 都是凸函数（Convex Function），每个 <span displaypfx="inline-" class="mathjax-container">\(h_j(x)\)</span> 都是仿射函数（Affine Function），那么 Slater 条件要求：<span style="background-color: #c0c0c0;">至少存在一个严格可行点（Strictly Feasible Point）</span> <span displaypfx="inline-" class="mathjax-container">\(\tilde{x}\)</span>，使得所有不等式约束都被“严格满足”，也就是</p>
<span displaypfx="" class="mathjax-container">\[ g_i(\tilde{x})&lt;0,\ \forall i,\qquad h_j(\tilde{x})=0,\ \forall j \]</span>
<p>这里“严格满足”四个字最关键。普通可行点只要求 <span displaypfx="inline-" class="mathjax-container">\(g_i(x)\le 0\)</span>，也就是允许刚好压在边界上；而 Slater 条件要求存在某个点，能让所有不等式约束都留出一点余量，也就是完全处在可行域内部，而不是贴着边界。</p>
<p>这个条件可以用一个非常直观的图像来理解。把不等式约束围成的可行域想成一个房间：</p>
<ul>
<li>如果房间内部真的有空间，存在一个点站在房间里，不碰任何墙，这就是满足 Slater 条件。</li>
<li>如果所谓“可行域”其实只是几面墙交出来的一条线、一个角、甚至一个点，根本没有真正的内部空间，那么 Slater 条件通常就不满足。</li>
</ul>
<p>因此，Slater 条件本质上是在说：<span style="background-color: #c0c0c0;">这个凸约束系统不能只是勉强拼出一个边界碎片，而要有真正的内部</span>。一旦内部存在，原始问题和对偶问题之间的间隙通常就会消失，拉格朗日乘子和 KKT 条件也会变得更稳定、更有解释力。</p>
<p>一个简单例子可以把它说清楚。考虑约束</p>
<span displaypfx="" class="mathjax-container">\[x\ge 0\]</span>
<p>写成标准形式是 <span displaypfx="inline-" class="mathjax-container">\(g(x)=-x\le 0\)</span>。这个约束满足 Slater 条件，因为取 <span displaypfx="inline-" class="mathjax-container">\(\tilde{x}=1\)</span> 时，有 <span displaypfx="inline-" class="mathjax-container">\(g(1)=-1&lt;0\)</span>。这说明可行域 <span displaypfx="inline-" class="mathjax-container">\([0,+\infty)\)</span> 不只是边界点 <span displaypfx="inline-" class="mathjax-container">\(x=0\)</span>，而是真正包含内部区域。</p>
<p>再看一个不满足 Slater 条件的例子：</p>
<span displaypfx="" class="mathjax-container">\[x^2\le 0\]</span>
<p>由于 <span displaypfx="inline-" class="mathjax-container">\(x^2\)</span> 永远不小于 0，这个约束唯一允许的点只有 <span displaypfx="inline-" class="mathjax-container">\(x=0\)</span>。可行域虽然非空，但没有任何点能让 <span displaypfx="inline-" class="mathjax-container">\(x^2&lt;0\)</span> 成立，所以不存在严格可行点，Slater 条件不成立。这样的约束系统只有边界、没有内部。</p>
<p>在机器学习常见的凸问题里，Slater 条件之所以频繁出现，是因为它几乎就是“强对偶成立的通行证”。例如在线性规划、逻辑回归的某些约束变体、支持向量机（SVM）的凸二次规划中，只要能找到一个严格可行点，就通常可以放心地从原始问题走到对偶问题，再用 KKT 条件解释最优解结构。反过来，如果没有 Slater 条件，就可能出现对偶间隙（Duality Gap），也就是对偶最优值严格小于原始最优值，此时只看对偶或只看 KKT 就不一定足够。</p>
<div class="blog_h4"><span class="graybg">概念速查</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">概念</td>
<td style="text-align: center;">它回答的问题</td>
<td style="text-align: center;">最核心的一句话</td>
<td style="text-align: center;">在这一章里的作用</td>
</tr>
</thead>
<tbody>
<tr>
<td>弱对偶（Weak Duality）</td>
<td>对偶问题和原始问题之间至少有什么关系</td>
<td>对偶最优值永远不会高于原始最优值；它天然是一个下界</td>
<td>先建立“对偶为什么有意义”的最低保证</td>
</tr>
<tr>
<td>对偶间隙（Duality Gap）</td>
<td>为什么有时对偶值还不等于原始最优值</td>
<td>如果对偶下界还没贴住原始最优值，两者之间的差就是对偶间隙</td>
<td>解释为什么“有对偶”不等于“对偶已经足够”</td>
</tr>
<tr>
<td>强对偶（Strong Duality）</td>
<td>什么时候对偶问题就足以精确刻画原问题</td>
<td>原始最优值与对偶最优值完全相等，对偶不再只是保守下界</td>
<td>为从原始问题转到对偶问题提供理论正当性</td>
</tr>
<tr>
<td>Slater 条件（Slater's Condition）</td>
<td>凸优化里什么条件有助于强对偶成立</td>
<td>只要可行域内部存在严格可行点，很多凸问题就能消除对偶间隙</td>
<td>说明为什么强对偶和 KKT 在凸问题里经常可用</td>
</tr>
<tr>
<td>影子价格（Shadow Price）</td>
<td>对偶变量到底在解释什么</td>
<td>它衡量“把某条约束放宽一点，最优值会改善多少”</td>
<td>赋予拉格朗日乘子清晰的经济 / 几何解释</td>
</tr>
<tr>
<td>KKT 条件</td>
<td>最优解在约束下必须满足哪些平衡关系</td>
<td>目标函数的下降趋势与约束施加的反作用力在最优点平衡</td>
<td>把可行性、乘子、驻点与互补松弛统一成最优性条件</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">KKT 条件</span></div>
<div class="blog_h4"><span class="graybg">背景和定义</span></div>
<p>KKT 条件（Karush–Kuhn–Tucker Conditions）讨论的是<span style="background-color: #c0c0c0;">带约束优化问题在最优点必须满足什么条件</span>。无约束优化里，常见做法是令梯度（Gradient）为 0；但一旦问题带有不等式约束或等式约束，只看 <span displaypfx="inline-" class="mathjax-container">\(\nabla f(x)=0\)</span> 就不够了，因为最优点很可能被约束“顶”在边界上，而不是落在自由空间里的普通驻点。</p>
<p>标准形式写作：</p>
<span displaypfx="" class="mathjax-container">\[\min_x f(x)\quad \text{s.t.}\quad g_i(x)\le 0,\ h_j(x)=0\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span> 是目标函数（Objective Function），表示希望最小化的量；<span displaypfx="inline-" class="mathjax-container">\(x\)</span> 是优化变量（Optimization Variable）；<span displaypfx="inline-" class="mathjax-container">\(g_i(x)\le 0\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个不等式约束（Inequality Constraint）；<span displaypfx="inline-" class="mathjax-container">\(h_j(x)=0\)</span> 是第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个等式约束（Equality Constraint）。KKT 条件给出的就是：当 <span displaypfx="inline-" class="mathjax-container">\(x^*\)</span> 真的是一个最优解时，它与对应的拉格朗日乘子（Lagrange Multipliers）之间必须满足一组相互配合的条件。</p>
<p>在凸优化（Convex Optimization）里，如果问题满足适当的正则性条件，例如 Slater 条件（Slater's Condition），KKT 条件往往不只是必要条件，还可以成为判定最优性的核心工具。在线性规划、二次规划、支持向量机（Support Vector Machine, SVM）和许多机器学习训练问题中，KKT 条件都直接参与求解过程。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/kkt-paraboloid-plane.png"><img class="alignnone size-full wp-image-41393" src="https://blog.gmem.cc/wp-content/uploads/2026/03/kkt-paraboloid-plane.png" alt="kkt-paraboloid-plane" width="2107" height="1134" /></a></p>
<div class="blog_h4"><span class="graybg">图解：四组条件如何逐条理解</span></div>
<p>上图使用的是活跃边界 <span displaypfx="inline-" class="mathjax-container">\(g(x)=1-x_1-x_2=0\)</span>。边界 <span displaypfx="inline-" class="mathjax-container">\(x_1+x_2=1\)</span> 的切向方向可取 <span displaypfx="inline-" class="mathjax-container">\((1,-1)\)</span>；与它垂直的向量都是法向量（Normal Vector），例如 <span displaypfx="inline-" class="mathjax-container">\((1,1)\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\((-1,-1)\)</span>。如果把约束写成函数 <span displaypfx="inline-" class="mathjax-container">\(g(x)=1-x_1-x_2\)</span>，那么梯度 <span displaypfx="inline-" class="mathjax-container">\(\nabla g(x)=(-1,-1)\)</span> 就是一条法向量；它指向 <span displaypfx="inline-" class="mathjax-container">\(g(x)\)</span> 增大的方向，也就是不可行侧。</p>
<p>图中的橙色箭头对应驻点条件（Stationarity）里真正参与平衡的两个向量：目标梯度 <span displaypfx="inline-" class="mathjax-container">\(\nabla f(x^*)\)</span> 与乘子加权后的约束法向量 <span displaypfx="inline-" class="mathjax-container">\(\lambda^*\nabla g(x^*)\)</span>。它们在最优点首尾相接后得到 0，于是 <span displaypfx="inline-" class="mathjax-container">\(\nabla f(x^*)+\lambda^*\nabla g(x^*)=0\)</span> 不是抽象记号，而是边界最优点上的几何平衡：</p>
<ul>
<li><span style="background-color: #c0c0c0;">原始可行性（Primal Feasibility）</span>：解必须落在可行域内。对这张图而言，可行域是半空间 <span displaypfx="inline-" class="mathjax-container">\(x_1+x_2\ge 1\)</span>，最优点 <span displaypfx="inline-" class="mathjax-container">\(x^*\)</span> 落在它的活跃边界 <span displaypfx="inline-" class="mathjax-container">\(x_1+x_2=1\)</span> 上。</li>
<li><span style="background-color: #c0c0c0;">对偶可行性（Dual Feasibility）</span>：不等式约束的乘子必须非负，即 <span displaypfx="inline-" class="mathjax-container">\(\lambda^*\ge 0\)</span>。图中的计算给出 <span displaypfx="inline-" class="mathjax-container">\(\lambda^*=0.65\)</span>，表示这条边界在最优点处确实对目标下降施加了正的“反作用”。</li>
<li><span style="background-color: #c0c0c0;">驻点条件（Stationarity）</span>：沿边界的切向方向已经不能继续下降，因此目标梯度不再含有切向分量，只能落在法向空间里。单个活跃约束时，这就变成 <span displaypfx="inline-" class="mathjax-container">\(\nabla f(x^*)=-\lambda^*\nabla g(x^*)\)</span>。图中的两根橙色箭头正是这两个大小相等、方向相反的向量。</li>
<li><span style="background-color: #c0c0c0;">互补松弛（Complementary Slackness）</span>：约束若不接触最优点，就不会产生乘子；约束一旦卡在最优点上，对应乘子才会变成正值。当前图像展示的是活跃边界情形，所以 <span displaypfx="inline-" class="mathjax-container">\(g(x^*)=0\)</span> 且 <span displaypfx="inline-" class="mathjax-container">\(\lambda^*&gt;0\)</span> 同时成立。</li>
</ul>
<p>法向量之所以关键，是因为它刻画了“离开边界最快”的方向；切向方向则刻画了“沿边界滑动”的方向。边界最优点的本质结论是：<span style="background-color: #c0c0c0;">目标函数想继续下降的那一部分趋势，已经完全被约束边界的法向作用抵消；沿边界本身则不存在进一步下降方向</span>。</p>
<div class="blog_h4"><span class="graybg">具像化描述</span></div>
<p>可以把 KKT 条件想成一个“遛狗”问题。优化目标是：狗想尽可能往远处跑；约束条件是：主人手里只有一根 5 米长的狗绳，因此狗的活动范围不能超过这条绳子的长度。不等式约束 <span displaypfx="inline-" class="mathjax-container">\(g_i(x)\le 0\)</span> 的作用，就像在说“不能超过这根绳子允许的极限”；等式约束 <span displaypfx="inline-" class="mathjax-container">\(h_j(x)=0\)</span> 则对应那些必须被精确满足的固定条件。</p>
<p>这个比喻里最重要的是区分“绳子是否真正起作用”。如果狗最后只跑到 3 米处就停下，例如已经闻到味道、找到目标，或者在那里已经达到最优状态，那么绳子仍然是松的，还留有 2 米余量。这时约束虽然存在，但它没有真正限制住解。相反，如果狗拼命往前冲，最后正好跑到 5 米极限，绳子就会被拉紧；此时狗还想继续前进，但被约束挡住了。KKT 条件刻画的正是这种“<span style="background-color: #c0c0c0;">目标继续改进的趋势，与约束施加的阻拦作用，在最优点达到平衡</span>”的状态。</p>
<p>其中最形象的一条就是互补松弛（Complementary Slackness）。它表达的是：每一个不等式约束在最优点都只有两种状态。</p>
<ul>
<li>如果某个约束正好卡在边界上，即 <span displaypfx="inline-" class="mathjax-container">\(g_i(x^*)=0\)</span>，就像狗刚好把 5 米狗绳拉到极限，此时这条绳子真的“顶住了”最优解，它对应的乘子 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i^*\)</span> 可以大于 0。</li>
<li>如果某个约束离边界还有余量，即 <span displaypfx="inline-" class="mathjax-container">\(g_i(x^*)&lt;0\)</span>，就像狗只跑到 3 米处，绳子仍然松弛，那么它对应的乘子必须是 0，也就是 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i^*=0\)</span>。</li>
</ul>
<p>因此，乘子 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i^*\)</span> 可以直观理解为绳子的<span style="background-color: #c0c0c0;">拉力</span>，也就是约束对最优解施加的压力。在优化语言里，这个量也常被解释为影子价格（Shadow Price）：如果把这条约束稍微放宽一点，最优目标值会改善多少。拉力不为 0，说明这条约束正在真正影响解；拉力为 0，说明这条约束虽然存在，但在最优点处并没有发挥作用。</p>
<div class="blog_h4"><span class="graybg">公式逐元素解释</span></div>
<p>把原问题写成统一形式后，先定义拉格朗日函数（Lagrangian）：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}(x,\lambda,\nu)=f(x)+\sum_i \lambda_i g_i(x)+\sum_j \nu_j h_j(x)\]</span>
<p>这个式子里的每个元素都有明确含义：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}\)</span>：拉格朗日函数，把目标函数和约束统一写进一个式子里。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(x\)</span>：原始变量（Primal Variable），也就是模型真正要优化的参数。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(f(x)\)</span>：目标函数，表示希望最小化的代价、损失或能量。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(g_i(x)\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个不等式约束函数；要求它满足 <span displaypfx="inline-" class="mathjax-container">\(g_i(x)\le 0\)</span>。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(h_j(x)\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个等式约束函数；要求它满足 <span displaypfx="inline-" class="mathjax-container">\(h_j(x)=0\)</span>。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\lambda_i\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个不等式约束对应的拉格朗日乘子；它衡量该约束施加的“压力”或“拉力”大小，也可理解为对应约束资源的影子价格。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\nu_j\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个等式约束对应的拉格朗日乘子。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\sum_i\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(\sum_j\)</span>：分别表示把所有不等式约束和所有等式约束的影响累加起来。</li>
</ul>
<p>在此基础上，KKT 条件通常写成四组：</p>
<span displaypfx="" class="mathjax-container">\[\text{Primal feasibility: } g_i(x^*)\le 0,\ h_j(x^*)=0\]</span>
<span displaypfx="" class="mathjax-container">\[\text{Dual feasibility: } \lambda_i^*\ge 0\]</span>
<span displaypfx="" class="mathjax-container">\[\text{Stationarity: } \nabla f(x^*)+\sum_i \lambda_i^*\nabla g_i(x^*)+\sum_j \nu_j^*\nabla h_j(x^*)=0\]</span>
<span displaypfx="" class="mathjax-container">\[\text{Complementary slackness: } \lambda_i^* g_i(x^*)=0\]</span>
<p>这四组条件可以逐条理解：</p>
<ul>
<li><span style="background-color: #c0c0c0;">原始可行性（Primal Feasibility）</span>：最优解 <span displaypfx="inline-" class="mathjax-container">\(x^*\)</span> 首先必须是合法的，不能跑到可行域之外。也就是说，所有不等式约束都要满足 <span displaypfx="inline-" class="mathjax-container">\(g_i(x^*)\le 0\)</span>，所有等式约束都要精确满足 <span displaypfx="inline-" class="mathjax-container">\(h_j(x^*)=0\)</span>。</li>
<li><span style="background-color: #c0c0c0;">对偶可行性（Dual Feasibility）</span>：不等式约束的乘子必须非负，即 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i^*\ge 0\)</span>。这保证了它们表达的是“阻止变量越界的压力”，而不是把解往不可行方向拉过去的反向力量。</li>
<li><span style="background-color: #c0c0c0;">驻点条件（Stationarity）</span>：在最优点处，目标函数的梯度 <span displaypfx="inline-" class="mathjax-container">\(\nabla f(x^*)\)</span> 与所有约束梯度加权后的合力必须平衡为 0。这里 <span displaypfx="inline-" class="mathjax-container">\(\nabla f(x^*)\)</span> 表示目标函数在 <span displaypfx="inline-" class="mathjax-container">\(x^*\)</span> 处最陡上升方向；<span displaypfx="inline-" class="mathjax-container">\(\nabla g_i(x^*)\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(\nabla h_j(x^*)\)</span> 分别表示约束边界的法向方向；乘子 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i^*\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\nu_j^*\)</span> 则控制这些方向各自施加多少“反作用力”。这条式子本质上是在说：最优点处已经不存在任何仍然可行且还能继续下降的方向。</li>
<li><span style="background-color: #c0c0c0;">互补松弛（Complementary Slackness）</span>：每个不等式约束都满足 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i^* g_i(x^*)=0\)</span>。因为这是两个量的乘积等于 0，所以只能出现两种情况：要么 <span displaypfx="inline-" class="mathjax-container">\(g_i(x^*)=0\)</span>，说明约束正好顶在边界上；要么 <span displaypfx="inline-" class="mathjax-container">\(\lambda_i^*=0\)</span>，说明这条约束在最优点没有施加压力。它精确区分了“活跃约束（Active Constraint）”和“不活跃约束（Inactive Constraint）”。</li>
</ul>
<p>一个一维例子可以把这些符号落到实处。考虑：</p>
<span displaypfx="" class="mathjax-container">\[\min_x\ (x+1)^2\quad \text{s.t.}\quad x\ge 0\]</span>
<p>把约束写成标准形式 <span displaypfx="inline-" class="mathjax-container">\(g(x)=-x\le 0\)</span>，于是拉格朗日函数是：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}(x,\lambda)=(x+1)^2+\lambda(-x)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\((x+1)^2\)</span> 是目标函数；<span displaypfx="inline-" class="mathjax-container">\(-x\le 0\)</span> 是不等式约束；<span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 是这条约束对应的乘子。KKT 条件变成：</p>
<span displaypfx="" class="mathjax-container">\[x\ge 0,\quad \lambda\ge 0,\quad 2(x+1)-\lambda=0,\quad \lambda x=0\]</span>
<p>这四项分别表示：解必须在 <span displaypfx="inline-" class="mathjax-container">\(x\ge 0\)</span> 的合法区域内；乘子必须非负；目标函数与约束施加的作用力在最优点平衡；约束要么卡住解、要么不施加压力。无约束时， <span displaypfx="inline-" class="mathjax-container">\((x+1)^2\)</span> 的最低点在 <span displaypfx="inline-" class="mathjax-container">\(x=-1\)</span>，但这不满足 <span displaypfx="inline-" class="mathjax-container">\(x\ge 0\)</span>，所以真正最优点被“推”到边界 <span displaypfx="inline-" class="mathjax-container">\(x^*=0\)</span>；再代入驻点条件 <span displaypfx="inline-" class="mathjax-container">\(2(x+1)-\lambda=0\)</span>，得到 <span displaypfx="inline-" class="mathjax-container">\(\lambda^*=2\)</span>。这正对应“边界在最优点处确实对解施加了压力”。</p>
<p>如果把目标函数换成 <span displaypfx="inline-" class="mathjax-container">\(\min_x (x-2)^2\ \text{s.t.}\ x\ge 0\)</span>，无约束最优点就是 <span displaypfx="inline-" class="mathjax-container">\(x=2\)</span>，它本来就在可行域内部，因此约束没有真正碰到最优点。此时 KKT 会给出 <span displaypfx="inline-" class="mathjax-container">\(x^*=2\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\lambda^*=0\)</span>。这就是互补松弛最直观的含义：<span style="background-color: #c0c0c0;">边界没有碰到解，压力就自动消失；边界一旦碰到解，乘子就会变成非零</span>。</p>
<div class="blog_h1"><span class="graybg">概率论与统计</span></div>
<p>概率论与统计处理两类问题：第一类是<span style="background-color: #c0c0c0;">在不确定性下如何描述事件、变量与数据生成过程</span>；第二类是<span style="background-color: #c0c0c0;">在只观察到有限样本时，如何反推总体规律与模型参数</span>。在 AI 中，分类概率、回归噪声、采样、似然训练、置信评估与后验推断都建立在这套语言之上。</p>
<div class="blog_h2"><span class="graybg">基础概念</span></div>
<p>在进入公式前，先区分几个最常混淆的基础对象。概率论不是一开始就讨论“均值”和“方差”，而是先规定<span style="background-color: #c0c0c0;">随机试验的结果空间、结果上的事件，以及把结果映射成数的随机变量</span>，之后才谈分布、期望和统计推断。</p>
<ul>
<li>样本空间（Sample Space）<span displaypfx="inline-" class="mathjax-container">\(\Omega\)</span>：一次随机试验所有可能结果的集合，其中希腊字母 <span displaypfx="inline-" class="mathjax-container">\(\Omega\)</span> 只是“全部可能结果”的记号。掷骰子时 <span displaypfx="inline-" class="mathjax-container">\(\Omega=\{1,2,3,4,5,6\}\)</span>，表示所有可能点数组成的集合。</li>
<li>事件（Event）：样本空间的子集，表示“哪些结果算作某件事发生”。例如“点数为偶数”对应集合 <span displaypfx="inline-" class="mathjax-container">\(\{2,4,6\}\)</span>；这句话的意思是，只要试验结果落在这个集合里，就说该事件发生。</li>
<li>随机变量（Random Variable）<span displaypfx="inline-" class="mathjax-container">\(X\)</span>：把随机结果映射为数的函数，可写作 <span displaypfx="inline-" class="mathjax-container">\(X:\Omega\to\mathbb{R}\)</span>。这里 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 是这个函数的名字， <span displaypfx="inline-" class="mathjax-container">\(\Omega\)</span> 是输入端的样本空间，箭头 <span displaypfx="inline-" class="mathjax-container">\(\to\)</span> 表示“映射到”， <span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}\)</span> 表示实数集合。也就是说，每个随机结果都会被 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 变成一个实数。掷骰子时最自然的随机变量就是“点数本身”；在机器学习里，输入 <span displaypfx="inline-" class="mathjax-container">\(X\)</span>、标签 <span displaypfx="inline-" class="mathjax-container">\(Y\)</span>、噪声 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 都是随机变量。</li>
<li>概率分布（Probability Distribution）：描述随机变量取不同值的概率规律。离散情形常写作 <span displaypfx="inline-" class="mathjax-container">\(P(X=x)\)</span>，意思是“随机变量 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 恰好取值 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 的概率”；连续情形常写作密度 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span>，它不是某一点的概率本身，而是概率密度函数（Probability Density Function, PDF），需要在区间上积分才得到概率。</li>
<li>期望（Expectation）<span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X]\)</span>：按概率加权的平均，回答“长期来看这个随机变量的典型水平在哪里”。这里 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}\)</span> 是 expectation 的标准记号，方括号中的 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 表示“对哪个随机变量取期望”。</li>
<li>方差（Variance）<span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(X)\)</span>：围绕期望的波动强度，回答“它通常偏离平均水平多大”。其中 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}\)</span> 是 variance 的记号，括号里的 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 表示“考察哪个随机变量的波动”。</li>
<li>参数（Parameter）与统计量（Statistic）：参数描述总体，例如高斯分布中的 <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span> 表示均值（Mean）， <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span> 表示方差（Variance）；统计量则由样本计算出来，例如 <span displaypfx="inline-" class="mathjax-container">\(\bar{x}\)</span> 表示样本均值（Sample Mean），上面的横线读作“x bar”，意思是“样本中所有 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 的平均值”。统计学习的核心任务之一，就是用统计量去估计未知参数。</li>
</ul>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/probability.jpg"><img class="alignnone size-full wp-image-40841" src="https://blog.gmem.cc/wp-content/uploads/2026/03/probability.jpg" alt="probability" width="100%" /></a></p>
<div class="blog_h2"><span class="graybg">似然和概率</span></div>
<p>概率（Probability）与似然（Likelihood）都可以写成 <span displaypfx="inline-" class="mathjax-container">\(p(x|\theta)\)</span> 这样的形式，但它们把“谁是已知、谁是待判断对象”放在不同位置。概率把参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 视为已知、把数据 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 视为随机结果；似然则把数据 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 固定下来，把参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 当作待比较对象。</p>
<p>也正因为公式形式相同，二者很容易混淆；真正的区别不在写法，而在它们回答的是相反方向的问题。条件分布 <span displaypfx="inline-" class="mathjax-container">\(p(x|\theta)\)</span> 在统计里经常同时出现在两种语境中：当参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 固定、数据 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 变化时，它表示“在这个模型下观察到不同数据的概率有多大”；当数据 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 固定、参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 变化时，它表示“哪些参数更能解释这份已经观察到的数据”。</p>
<p>因此，概率的读法是<span style="background-color: #c0c0c0;">参数已知，看数据</span>。例如抛硬币模型里，若已知正面概率 <span displaypfx="inline-" class="mathjax-container">\(\theta=0.7\)</span>，那么 10 次里出现 8 次正面的概率是</p>
<span displaypfx="" class="mathjax-container">\[P(X=8|\theta=0.7)={10 \choose 8}0.7^8 0.3^2\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\({10 \choose 8}\)</span> 表示组合数（Binomial Coefficient）：从 10 次试验里选出 8 次作为正面的不同选法一共有多少种。它负责计数“8 次正面可以出现在哪 8 个位置上”，不是额外的概率项。</p>
<p>而 <span displaypfx="inline-" class="mathjax-container">\(0.7^8 0.3^2\)</span> 来自独立重复试验的乘法法则：如果每次抛掷相互独立，且正面概率是 <span displaypfx="inline-" class="mathjax-container">\(0.7\)</span>、反面概率是 <span displaypfx="inline-" class="mathjax-container">\(0.3\)</span>，那么任意一个“8 次正面、2 次反面”的具体序列，其概率都是 8 个 <span displaypfx="inline-" class="mathjax-container">\(0.7\)</span> 与 2 个 <span displaypfx="inline-" class="mathjax-container">\(0.3\)</span> 的乘积，也就是 <span displaypfx="inline-" class="mathjax-container">\(0.7^8 0.3^2\)</span>。</p>
<p>这里被当作变量的是结果 <span displaypfx="inline-" class="mathjax-container">\(X\)</span>；问题是“给定参数，这样的数据是否常见”。这就是通常意义上的概率或概率密度。</p>
<p>似然的读法正好反过来：<span style="background-color: #c0c0c0;">数据已知，看参数</span>。假设现在已经观察到 10 次抛硬币里有 8 次正面，这组数据不再变化；真正变化的是候选参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>。于是同一个模型被改写为似然函数（Likelihood Function）：</p>
<span displaypfx="" class="mathjax-container">\[L(\theta|X=8)=P(X=8|\theta)={10 \choose 8}\theta^8(1-\theta)^2\]</span>
<p>此时 <span displaypfx="inline-" class="mathjax-container">\(L(\theta|x)\)</span> 不是“参数取某个值的概率”，而是一个关于 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 的评分函数：哪个 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 让已观察到的数据 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 更容易出现，哪个 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 的似然就更大。最大似然估计（Maximum Likelihood Estimation, MLE）做的正是这件事：寻找使 <span displaypfx="inline-" class="mathjax-container">\(L(\theta|x)\)</span> 最大的参数。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/likelihood.png"><img class="alignnone size-full wp-image-40879" src="https://blog.gmem.cc/wp-content/uploads/2026/03/likelihood.png" alt="likelihood" width="100%" /></a></p>
<p>这里有一个必须严格区分的点：<span style="background-color: #c0c0c0;">似然不是参数的概率分布</span>。对固定数据来说， <span displaypfx="inline-" class="mathjax-container">\(L(\theta|x)\)</span> 不要求对 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 积分为 1，也不能直接解释为 <span displaypfx="inline-" class="mathjax-container">\(P(\theta|x)\)</span>。只有在贝叶斯框架里，把似然与先验（Prior） <span displaypfx="inline-" class="mathjax-container">\(p(\theta)\)</span> 相乘并做归一化之后，才得到参数的后验概率（Posterior）：</p>
<span displaypfx="" class="mathjax-container">\[p(\theta|x)=\frac{p(x|\theta)p(\theta)}{p(x)}\]</span>
<p>所以可以把三者关系记成一条清晰的链： <span displaypfx="inline-" class="mathjax-container">\(p(x|\theta)\)</span> 作为“关于 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 的函数”时是概率模型；作为“关于 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 的函数”时是似然函数；再结合先验并归一化后，才变成参数的后验概率。很多机器学习教材里说“最小化交叉熵等价于最大化对数似然”，本质上就是固定数据后，在参数空间里寻找最能解释样本的模型。</p>
<div class="blog_h3"><span class="graybg">机器学习视角</span></div>
<p>在机器学习模型里，这种“同一个式子，换个视角名字就变”的现象非常常见。设模型写成 <span displaypfx="inline-" class="mathjax-container">\(p_\theta(y|x)\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 是输入或上下文， <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 是真实标签或真实 token， <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 是模型参数。</p>
<p>当参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 固定、把输出 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 当作随机变量时， <span displaypfx="inline-" class="mathjax-container">\(p_\theta(y|x)\)</span> 是概率模型：它回答“在这个模型已经定好的前提下，不同输出有多可能”。例如语言模型会对整个词表输出一个条件概率分布，其中 <span displaypfx="inline-" class="mathjax-container">\(c_t\)</span> 表示第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 个位置之前的上下文（context）， <span displaypfx="inline-" class="mathjax-container">\(y_t\)</span> 表示该位置真实出现的 token，因此 <span displaypfx="inline-" class="mathjax-container">\(p_\theta(y_t|c_t)\)</span> 就是“在上下文 <span displaypfx="inline-" class="mathjax-container">\(c_t\)</span> 下，模型给真实 token <span displaypfx="inline-" class="mathjax-container">\(y_t\)</span> 分配的概率”。</p>
<p>当观测到的 <span displaypfx="inline-" class="mathjax-container">\((x,y)\)</span> 固定、把参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 当作变量时，同一个式子 <span displaypfx="inline-" class="mathjax-container">\(p_\theta(y|x)\)</span> 就变成似然：它回答“哪些参数更能让这条已经看到的数据显得合理”。于是训练时最大化似然，就等价于最小化负对数似然：</p>
<span displaypfx="" class="mathjax-container">\[-\log p_\theta(y|x)\]</span>
<p>因此可以把这个困惑直接拆开：<span style="background-color: #c0c0c0;">固定参数看输出，它是概率；固定输出看参数，它是似然；对它取负对数后，它就是训练里使用的损失项</span>。在语言建模里，对一个具体 token 而言，给定当前模型参数后， <span displaypfx="inline-" class="mathjax-container">\(-\log p_\theta(y_t|c_t)\)</span> 既可以看成该 token 的负对数概率，也可以在训练语境下看成该 token 对参数的负对数似然；两者是同一个数值对象，只是观察角度不同。</p>
<div class="blog_h2"><span class="graybg">基本概率</span></div>
<p>概率（Probability）描述不确定事件发生的可能性。设样本空间（Sample Space）为 <span displaypfx="inline-" class="mathjax-container">\(\Omega\)</span>，事件（Event）为 <span displaypfx="inline-" class="mathjax-container">\(A\subseteq\Omega\)</span>，则概率公理要求 <span displaypfx="inline-" class="mathjax-container">\(P(A)\in[0,1]\)</span>、<span displaypfx="inline-" class="mathjax-container">\(P(\Omega)=1\)</span>，以及互斥事件可加。这里“互斥事件可加”的意思是：如果两个事件不能同时发生，即 <span displaypfx="inline-" class="mathjax-container">\(A\cap B=\{\}\)</span>，那么“<span displaypfx="inline-" class="mathjax-container">\(A\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 发生”的概率就是两者概率直接相加：</p>
<span displaypfx="" class="mathjax-container">\[P(A\cup B)=P(A)+P(B),\quad A\cap B=\{\}\]</span>
<p>之所以能直接相加，是因为两者没有重叠部分，不会重复计数。若有重叠，则不能直接相加，而要减去交集 <span displaypfx="inline-" class="mathjax-container">\(P(A\cap B)\)</span>。</p>
<p>最小例子：掷一枚公平六面骰。样本空间是 <span displaypfx="inline-" class="mathjax-container">\(\Omega=\{1,2,3,4,5,6\}\)</span>。若事件 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 表示“点数为 1”，事件 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 表示“点数为 2”，则它们互斥，因此 <span displaypfx="inline-" class="mathjax-container">\(P(A\cup B)=1/6+1/6=1/3\)</span>。再看一个非互斥例子：若事件 <span displaypfx="inline-" class="mathjax-container">\(C\)</span> 表示“点数为偶数”，则 <span displaypfx="inline-" class="mathjax-container">\(C=\{2,4,6\}\)</span>，因此 <span displaypfx="inline-" class="mathjax-container">\(P(C)=3/6=1/2\)</span>。</p>
<div class="blog_h3"><span class="graybg">联合概率</span></div>
<p>联合概率（Joint Probability）表示多个事件同时发生的概率，即 <span displaypfx="inline-" class="mathjax-container">\(P(A,B)=P(A\cap B)\)</span>。它回答的是“这些条件一起成立的可能性有多大”。</p>
<span displaypfx="" class="mathjax-container">\[P(A,B)=P(A\cap B)\]</span>
<p>仍用骰子例子：令 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 表示“点数为偶数”，<span displaypfx="inline-" class="mathjax-container">\(B\)</span> 表示“点数至少为 4”，则 <span displaypfx="inline-" class="mathjax-container">\(A\cap B=\{4,6\}\)</span>，所以 <span displaypfx="inline-" class="mathjax-container">\(P(A,B)=2/6=1/3\)</span>。在机器学习里，联合分布 <span displaypfx="inline-" class="mathjax-container">\(p(x,y)\)</span> 表示“输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 与标签 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 一起出现”的总体规律。</p>
<div class="blog_h3"><span class="graybg">独立性与乘法法则</span></div>
<p>独立（Independence）表示一个事件是否发生，不会改变另一个事件发生的概率。若事件 <span displaypfx="inline-" class="mathjax-container">\(A,B\)</span> 相互独立，则它们同时发生的概率可直接写成概率乘积：</p>
<span displaypfx="" class="mathjax-container">\[P(A\cap B)=P(A)P(B)\]</span>
<p>更一般地，若 <span displaypfx="inline-" class="mathjax-container">\(A_1,\dots,A_n\)</span> 相互独立，则</p>
<span displaypfx="" class="mathjax-container">\[P\!\left(\bigcap_{i=1}^{n}A_i\right)=\prod_{i=1}^{n}P(A_i)\]</span>
<p>例：掷一枚公平硬币并同时掷一枚公平骰子。令 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 表示“硬币为正面”，<span displaypfx="inline-" class="mathjax-container">\(B\)</span> 表示“骰子为偶数”，则 <span displaypfx="inline-" class="mathjax-container">\(P(A)=1/2\)</span>、<span displaypfx="inline-" class="mathjax-container">\(P(B)=1/2\)</span>，且二者独立，因此 <span displaypfx="inline-" class="mathjax-container">\(P(A\cap B)=1/2\times 1/2=1/4\)</span>。</p>
<p>独立时还可等价写成 <span displaypfx="inline-" class="mathjax-container">\(P(A|B)=P(A)\)</span>：知道 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 发生，并不会改变对 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的判断。</p>
<p>要注意：互斥（Mutually Exclusive）和独立（Independent）不是一回事。互斥表示不能同时发生；独立表示是否发生彼此无关。对非零概率事件而言，互斥通常意味着<span style="background-color: #c0c0c0;">不独立</span>，因为一旦知道 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 发生，就立刻知道 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 不可能发生。</p>
<div class="blog_h3"><span class="graybg">条件概率</span></div>
<p>条件概率（Conditional Probability）表示“已知 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 发生时，<span displaypfx="inline-" class="mathjax-container">\(A\)</span> 发生的概率”：</p>
<span displaypfx="" class="mathjax-container">\[P(A|B)=\frac{P(A,B)}{P(B)},\quad P(B)&gt;0\]</span>
<p>关键点不是“再算一次概率”，而是<span style="background-color: #c0c0c0;">样本空间被缩小了</span>。在上面的骰子例子中，已知 <span displaypfx="inline-" class="mathjax-container">\(B\)</span>（点数至少为 4）后，只剩 <span displaypfx="inline-" class="mathjax-container">\(\{4,5,6\}\)</span> 三种可能，其中偶数是 <span displaypfx="inline-" class="mathjax-container">\(\{4,6\}\)</span>，所以 <span displaypfx="inline-" class="mathjax-container">\(P(A|B)=2/3\)</span>，明显不同于原来的 <span displaypfx="inline-" class="mathjax-container">\(P(A)=1/2\)</span>。</p>
<p>在建模中，几乎所有监督学习目标都可写成条件概率最大化，例如分类模型学习的是 <span displaypfx="inline-" class="mathjax-container">\(P(y|x)\)</span>：给定特征 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，标签 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 的概率有多大。</p>
<div class="blog_h3"><span class="graybg">边缘概率</span></div>
<p>边缘概率（Marginal Probability）是把不关心的变量“求和/积分掉”后得到的概率：</p>
<span displaypfx="" class="mathjax-container">\[P(A)=\sum_b P(A,b),\quad p(x)=\int p(x,z)\,dz\]</span>
<p>这两个公式的意思完全一致，只是分别对应离散情形与连续情形。 <span displaypfx="inline-" class="mathjax-container">\(P(A,b)\)</span> 表示“<span displaypfx="inline-" class="mathjax-container">\(A\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 同时发生”的联合概率；若现在只关心 <span displaypfx="inline-" class="mathjax-container">\(A\)</span>，就必须把所有可能的 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 都加起来。连续情形下同理： <span displaypfx="inline-" class="mathjax-container">\(p(x,z)\)</span> 是关于 <span displaypfx="inline-" class="mathjax-container">\((x,z)\)</span> 的联合密度，若只关心 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，就把所有 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 的可能性沿该维度积分掉，得到 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span>。</p>
<p>几何上，可以把边缘化（Marginalization）理解为<span style="background-color: #c0c0c0;">把高维分布沿某个方向压扁后得到的投影</span>。设二维联合分布的两个维度分别是身高与体重，平面上的每个位置都对应一个联合概率密度 <span displaypfx="inline-" class="mathjax-container">\(p(\text{height},\text{weight})\)</span>。如果现在根本不关心体重，只想知道身高的总体分布，那么就把每个固定身高处、沿着体重方向的所有概率质量全部累加起来；累加后的结果就是该身高对应的边缘密度 <span displaypfx="inline-" class="mathjax-container">\(p(\text{height})\)</span>。</p>
<p>因此，求和符号 <span displaypfx="inline-" class="mathjax-container">\(\sum_b P(A,b)\)</span> 或积分符号 <span displaypfx="inline-" class="mathjax-container">\(\int p(x,z)\,dz\)</span> 的几何含义，不是“神秘地消掉一个变量”，而是<span style="background-color: #c0c0c0;">把那个维度上的所有可能性叠加到剩余维度上</span>。它像从侧面看一个三维物体的投影：原来的结构仍然在，但你只保留了当前关心的坐标轴信息。</p>
<p>在 AI 中，边缘概率几乎总伴随着隐藏变量（Latent Variable）。例如主题模型里 <span displaypfx="inline-" class="mathjax-container">\(z\)</span> 可以表示隐藏主题（Hidden Topic），<span displaypfx="inline-" class="mathjax-container">\(x\)</span> 表示某个词；若只关心词出现的总体概率，就要把主题变量消去，得到 <span displaypfx="inline-" class="mathjax-container">\(p(x)=\sum_z p(x,z)\)</span>。很多推断算法的难点，本质上就是这个“沿隐藏维度求和/积分”的步骤代价很高。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/marginzation.jpg"><img class="alignnone size-full wp-image-40833" src="https://blog.gmem.cc/wp-content/uploads/2026/03/marginzation.jpg" alt="marginzation" width="100%" /></a></p>
<div class="blog_h3"><span class="graybg">补事件</span></div>
<p>补事件（Complementary Event）满足</p>
<span displaypfx="" class="mathjax-container">\[P(A^c)=1-P(A)\]</span>
<p>它表示“<span displaypfx="inline-" class="mathjax-container">\(A\)</span> 不发生”的概率等于总概率 1 减去 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 发生的概率。</p>
<div class="blog_h3"><span class="graybg">全概率公式</span></div>
<p>第二，若 <span displaypfx="inline-" class="mathjax-container">\(A_1,\dots,A_n\)</span> 把样本空间划分为互斥且完备的几部分（即两两互斥，且并集为 <span displaypfx="inline-" class="mathjax-container">\(\Omega\)</span>），则全概率公式（Law of Total Probability）为</p>
<span displaypfx="" class="mathjax-container">\[P(B)=\sum_{i=1}^{n}P(B|A_i)P(A_i)\]</span>
<p>它的含义是：事件 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 的总概率，可以拆成“先落入哪一种原因 <span displaypfx="inline-" class="mathjax-container">\(A_i\)</span>，再在该原因下发生 <span displaypfx="inline-" class="mathjax-container">\(B\)</span>”的加权和。贝叶斯公式中的分母 <span displaypfx="inline-" class="mathjax-container">\(P(B)\)</span>，很多时候就是用全概率公式展开出来的。</p>
<div class="blog_h2"><span class="graybg">贝叶斯定理</span></div>
<p>贝叶斯定理（Bayes' Theorem）把“从原因到结果”的概率反转为“从结果反推原因”：</p>
<span displaypfx="" class="mathjax-container">\[P(A|B)=\frac{P(B|A)\,P(A)}{P(B)}\]</span>
<p>把式子写出来并不难，真正容易混淆的是每一项到底在说什么。下面直接用一个具体场景来解释：设 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 表示“患者真的患病”，<span displaypfx="inline-" class="mathjax-container">\(B\)</span> 表示“检测结果为阳性”。已知患病率为 1%，检测的灵敏度（Sensitivity）为 99%，假阳性率（False Positive Rate）为 5%。则：</p>
<ul>
<li>先验（Prior） <span displaypfx="inline-" class="mathjax-container">\(P(A)\)</span>：在还没看到这次检测结果前，对“此人患病”的原始判断。在这个例子里，先验就是总体患病率 <span displaypfx="inline-" class="mathjax-container">\(P(A)=0.01\)</span>。</li>
<li>似然（Likelihood） <span displaypfx="inline-" class="mathjax-container">\(P(B|A)\)</span>：如果一个人确实患病，那么检测为阳性的概率有多大。在这个例子里，检测灵敏度是 99%，因此 <span displaypfx="inline-" class="mathjax-container">\(P(B|A)=0.99\)</span>。</li>
<li>证据（Evidence） <span displaypfx="inline-" class="mathjax-container">\(P(B)\)</span>：不管一个人是否患病，检测结果为阳性在总体上出现的概率。它等于“真阳性 + 假阳性”的总和： <span displaypfx="inline-" class="mathjax-container">\(P(B)=0.99\times 0.01 + 0.05\times 0.99 = 0.0594\)</span>。</li>
<li>后验（Posterior） <span displaypfx="inline-" class="mathjax-container">\(P(A|B)\)</span>：在已经看到“检测阳性”这个证据之后，此人真正患病的更新后概率。代入上面的数值，有</li>
</ul>
<span displaypfx="" class="mathjax-container">\[P(\text{disease}|+) = \frac{0.99\times 0.01}{0.99\times 0.01 + 0.05\times 0.99} = \frac{1}{6} \approx 16.7\%\]</span>
<p>因此，贝叶斯定理描述的是一个非常具体的更新过程：<span style="background-color: #c0c0c0;">先从原始信念出发，用证据对它重新加权，再归一化，得到更新后的信念</span>。这也是为什么常把它概括成：<span style="background-color: #c0c0c0;">后验 = 似然 × 先验 ÷ 证据</span>。</p>
<p>这个例子最重要的结论是：检测阳性后，真实患病概率并不是 99%，而只有约 16.7%。原因不是检测太差，而是先验患病率本来就很低；假阳性虽然比例不高，但基数更大。换言之，<span style="background-color: #c0c0c0;">证据不会凭空决定结论，证据必须结合基线发生率一起解释</span>。在 AI 里，这就是“看到证据后更新信念”的统一公式：朴素贝叶斯分类、贝叶斯滤波、概率图模型都在做这件事。</p>
<div class="blog_h3"><span class="graybg">机器学习视角</span></div>
<p>从机器学习视角看，贝叶斯定理的价值在于它清楚地区分了<span style="background-color: #c0c0c0;">只看数据解释能力</span>与<span style="background-color: #c0c0c0;">把数据证据和先验知识合并起来</span>这两件事。前者对应似然（Likelihood）与最大似然估计（MLE），后者对应后验（Posterior）与最大后验估计（MAP）。</p>
<p>仍用上面的患病率例子。假设观测数据已经固定为“检测结果阳性”，把候选状态写成 <span displaypfx="inline-" class="mathjax-container">\(\theta\in\{\text{患病},\text{未患病}\}\)</span>。若只比较似然，则两个候选状态的评分分别是：</p>
<span displaypfx="" class="mathjax-container">\[L(\theta=\text{患病})=P(+|\text{患病})=0.99\]</span>
<span displaypfx="" class="mathjax-container">\[L(\theta=\text{未患病})=P(+|\text{未患病})=0.05\]</span>
<p>此时，按似然大小排序， <span displaypfx="inline-" class="mathjax-container">\(\theta=\text{患病}\)</span> 的确更优。这正是 MLE 式思路的核心：固定数据，寻找最能解释这份数据的候选参数或候选假设。但这里必须严格区分两件事：<span style="background-color: #c0c0c0;">“似然更大”只表示这个假设更能解释当前观测，不等于“它的后验概率就是 99%”</span>。因为似然没有把总体患病率只有 1% 这一先验事实算进去。</p>
<p>若进一步引入先验 <span displaypfx="inline-" class="mathjax-container">\(P(\text{患病})=0.01\)</span>、<span displaypfx="inline-" class="mathjax-container">\(P(\text{未患病})=0.99\)</span>，就得到后验比较：</p>
<span displaypfx="" class="mathjax-container">\[P(\text{患病}|+)\propto P(+|\text{患病})P(\text{患病})=0.99\times 0.01\]</span>
<span displaypfx="" class="mathjax-container">\[P(\text{未患病}|+)\propto P(+|\text{未患病})P(\text{未患病})=0.05\times 0.99\]</span>
<p>归一化之后， <span displaypfx="inline-" class="mathjax-container">\(P(\text{患病}|+)\approx 16.7\%\)</span>。这就是 MAP / 贝叶斯决策与单纯 MLE 的差别：后验判断不仅问“谁更能解释这份数据”，还问“谁在看到数据之前本来就更常见”。在类别极不平衡、小样本、先验知识明确或误判成本不对称的问题里，这个差别往往是决定性的。</p>
<p>朴素贝叶斯（Naive Bayes）正是把这套思路直接写成分类器。给定特征向量 <span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{x}\)</span> 和类别 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>，它比较的是后验概率</p>
<span displaypfx="" class="mathjax-container">\[p(y|\boldsymbol{x})\propto p(y)\,p(\boldsymbol{x}|y)\]</span>
<p>再在条件独立假设下展开成</p>
<span displaypfx="" class="mathjax-container">\[p(y|\boldsymbol{x})\propto p(y)\prod_{j=1}^{d}p(x_j|y)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(p(y)\)</span> 是类别先验，负责表达“哪类样本本来就更常见”； <span displaypfx="inline-" class="mathjax-container">\(p(x_j|y)\)</span> 是条件似然，负责表达“若类别固定，这个特征出现得是否合理”。训练阶段通常先从数据中估计这些概率；最简单的做法是 MLE，即直接用频数比估计先验和条件概率。若再加入拉普拉斯平滑（Laplace Smoothing）或更一般的共轭先验（Conjugate Prior），则更接近 MAP 估计。预测阶段再把先验与似然相乘并归一化，得到后验分数最高的类别。</p>
<p>因此，从机器学习角度看，贝叶斯定理并不只是概率论中的一条恒等式，而是一个完整的建模分工：<span style="background-color: #c0c0c0;">似然负责解释数据，先验负责表达归纳偏置，后验负责把两者合成为最终判断</span>。后面的 MLE、MAP 与朴素贝叶斯，只是这套分工在不同任务上的具体实现。</p>
<div class="blog_h2"><span class="graybg">概率分布</span></div>
<p>这里的“分布（Distribution）”指随机变量取值的不确定性如何在取值空间上分配。严格地说，概率分布（Probability Distribution）是一种概率测度（Probability Measure），它为每个事件 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 赋予概率 <span displaypfx="inline-" class="mathjax-container">\(P(A)\)</span>。</p>
<p>对一维实值随机变量 <span displaypfx="inline-" class="mathjax-container">\(X\)</span>，分布最常用的统一表述是累积分布函数（Cumulative Distribution Function, CDF）<span displaypfx="inline-" class="mathjax-container">\(F(x)=P(X\le x)\)</span>。离散与连续的差别，主要体现在如何从 <span displaypfx="inline-" class="mathjax-container">\(F\)</span> 得到“点上/区间上”的概率。</p>
<p>不同任务对应不同的数据分布假设（Distribution Assumption）。分布选得对，建模与推断会更稳定；分布假设错得太远，参数估计、置信区间乃至损失函数解释都会失真。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/plot_six_basic_dist.png"><img class="alignnone size-full wp-image-40921" src="https://blog.gmem.cc/wp-content/uploads/2026/03/plot_six_basic_dist.png" alt="plot_six_basic_dist" width="1848" height="1473" /></a></p>
<div class="blog_h3"><span class="graybg">概率密度/概率质量函数</span></div>
<p>若 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 是离散型随机变量，其概率质量函数（Probability Mass Function, PMF）为 <span displaypfx="inline-" class="mathjax-container">\(p(x)=P(X=x)\)</span>，并满足 <span displaypfx="inline-" class="mathjax-container">\(\sum_x p(x)=1\)</span>。</p>
<p>若 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 是连续型随机变量，其概率密度函数（Probability Density Function, PDF）为 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span>，满足 <span displaypfx="inline-" class="mathjax-container">\(p(x)\ge 0\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\int_{-\infty}^{+\infty} p(x)\,dx=1\)</span>；区间概率由积分给出：<span displaypfx="inline-" class="mathjax-container">\(P(a\le X\le b)=\int_a^b p(x)\,dx\)</span>。因此 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span> 本身不是“点上概率”。</p>
<p>两者都可写成同一 CDF 关系：连续情形 <span displaypfx="inline-" class="mathjax-container">\(F(x)=\int_{-\infty}^{x} p(t)\,dt\)</span>，离散情形 <span displaypfx="inline-" class="mathjax-container">\(F(x)=\sum_{t\le x} p(t)\)</span>。</p>
<div class="blog_h3"><span class="graybg">高斯分布（正态分布）</span></div>
<p>高斯分布（Gaussian / Normal Distribution）由均值 <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span> 与方差 <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span> 决定：</p>
<span displaypfx="" class="mathjax-container">\[p(x)=\frac{1}{\sqrt{2\pi}\sigma}\exp\!\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)\]</span>
<p>它的图像是熟悉的“钟形曲线（Bell Curve）”：离 <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span> 越近，概率密度越高；离得越远，概率密度衰减越快。大量独立小扰动叠加后常近似高斯（中心极限定理的结果），所以测量误差、回归残差、传感器噪声、嵌入向量某些方向上的统计近似都常采用高斯模型。</p>
<p>例：若成年男性身高近似服从 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{N}(170,6^2)\)</span>，则 170 cm 附近最常见，而 190 cm 属于离均值超过 3 个标准差的少见样本。回归里假设残差服从高斯，本质上是在说“误差多数小、极大误差少”。</p>
<div class="blog_h3"><span class="graybg">拉普拉斯分布</span></div>
<p>拉普拉斯分布（Laplace Distribution）也是定义在实数轴上的连续分布，常用位置参数（Location） <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span> 与尺度参数（Scale） <span displaypfx="inline-" class="mathjax-container">\(b&gt;0\)</span> 描述：</p>
<span displaypfx="" class="mathjax-container">\[p(x)=\frac{1}{2b}\exp\left(-\frac{|x-\mu|}{b}\right)\]</span>
<p>它的图像在中心点 <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span> 处更尖、尾部比高斯分布更厚（Heavier Tails）。这表示模型更偏好“大多数误差很小，但偶尔出现相对较大偏差”这一类数据形状，因此它对离群点（Outliers）的容忍度通常高于高斯模型。</p>
<p>拉普拉斯分布和绝对误差（Absolute Error）关系非常紧密：若回归残差假设服从拉普拉斯分布，那么最大似然估计会导向 <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 损失；若把参数先验设为拉普拉斯分布，最大后验估计则会导向 <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 正则。这也是它在稀疏建模（Sparse Modeling）和鲁棒回归（Robust Regression）里很常见的原因。</p>
<p>例：若某传感器大多数时刻误差接近 0，但偶尔会因为抖动或遮挡产生较大的偏差，那么用拉普拉斯分布描述误差，往往比高斯分布更贴近这种“中心尖、尾部较厚”的统计形状。</p>
<div class="blog_h3"><span class="graybg">伯努利分布</span></div>
<p>伯努利分布（Bernoulli Distribution）描述一次二元结果试验（0/1）：</p>
<span displaypfx="" class="mathjax-container">\[P(X=1)=p,\quad P(X=0)=1-p\]</span>
<p>它是二分类标签建模的最小单元，也是逻辑回归与二分类交叉熵的概率基础。若 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 表示“用户是否点击广告”，则 <span displaypfx="inline-" class="mathjax-container">\(X=1\)</span> 代表点击、<span displaypfx="inline-" class="mathjax-container">\(X=0\)</span> 代表未点击，模型输出的 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 就是点击概率。</p>
<p>伯努利变量的期望是 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X]=p\)</span>，方差是 <span displaypfx="inline-" class="mathjax-container">\(p(1-p)\)</span>。因此当 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 接近 0 或 1 时，不确定性反而更小；当 <span displaypfx="inline-" class="mathjax-container">\(p=0.5\)</span> 时，不确定性最大。</p>
<p>如果把同一个伯努利试验独立重复 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 次，并把成功次数加总，那么得到的就不是伯努利分布，而是二项分布（Binomial Distribution）。换句话说，伯努利分布描述“单次是否成功”，二项分布描述“总共成功了多少次”。</p>
<div class="blog_h3"><span class="graybg">二项分布</span></div>
<p>二项分布（Binomial Distribution）描述 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 次独立伯努利试验中的成功次数。若每次成功概率都是 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>，随机变量 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 表示总成功次数，则</p>
<span displaypfx="" class="mathjax-container">\[P(X=k)={n \choose k}p^k(1-p)^{n-k},\quad k=0,1,\dots,n\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\({n \choose k}\)</span> 表示“从 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 次试验里选出 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 次成功”的组合数；后面的 <span displaypfx="inline-" class="mathjax-container">\(p^k(1-p)^{n-k}\)</span> 表示某一种具体排列出现的概率。两者相乘，就得到“恰好成功 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 次”的总概率。</p>
<p>如果 <span displaypfx="inline-" class="mathjax-container">\(X_1,\dots,X_n\)</span> 独立同分布且 <span displaypfx="inline-" class="mathjax-container">\(X_i\sim\mathrm{Bernoulli}(p)\)</span>，那么它们的和</p>
<span displaypfx="" class="mathjax-container">\[S_n=\sum_{i=1}^{n}X_i\sim\mathrm{Binomial}(n,p)\]</span>
<p>因此，二项分布本质上就是“多个伯努利随机变量求和后的分布”。它的期望是 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X]=np\)</span>，方差是 <span displaypfx="inline-" class="mathjax-container">\(np(1-p)\)</span>。</p>
<p>例：若一枚硬币正面概率为 <span displaypfx="inline-" class="mathjax-container">\(p=0.7\)</span>，连续抛 10 次，则“正面出现几次”服从二项分布。此时恰好出现 7 次正面的概率是</p>
<span displaypfx="" class="mathjax-container">\[P(X=7)={10 \choose 7}0.7^7 0.3^3\]</span>
<div class="blog_h3"><span class="graybg">多项式分布</span></div>
<p>多项式分布（Multinomial Distribution）是“多类别计数版”的伯努利：进行 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 次独立试验，类别概率为 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{p}\)</span>，计数向量 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{k}\)</span> 的概率为</p>
<span displaypfx="" class="mathjax-container">\[P(\mathbf{k})=\frac{n!}{\prod_i k_i!}\prod_i p_i^{k_i},\quad \sum_i k_i=n\]</span>
<p>若只有一次抽样，通常写作分类分布（Categorical Distribution）：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/distribution-categorical.png"><img class="alignnone size-full wp-image-40867" src="https://blog.gmem.cc/wp-content/uploads/2026/03/distribution-categorical.png" alt="distribution-categorical" width="100%" /></a></p>
<p>若把一次抽样重复 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 次并统计每类出现次数，就得到多项式分布，语言模型里的词频计数、主题模型中的词袋（Bag of Words）都与这种计数视角一致。概率分布一节的第三个图就是多项式分布：总计抽样6次，横轴表示分类1的次数，纵轴表示分类2的次数（因为只有3分类，因此不需要绘制三维），颜色表示抽取到不同分类的次数组合的概率。</p>
<div class="blog_h3"><span class="graybg">泊松分布</span></div>
<p>泊松分布（Poisson Distribution）描述单位时间或单位空间内稀有事件的发生次数：</p>
<span displaypfx="" class="mathjax-container">\[P(X=k)=e^{-\lambda}\frac{\lambda^k}{k!},\quad k=0,1,2,\ldots\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span> 既是平均发生次数，也是方差。它适用于“事件相互独立、平均速率稳定、短时间内多次同时发生概率很小”的场景，例如单位分钟请求数、单位小时故障数、单位页面点击数。</p>
<p>例：若一个接口平均每分钟收到 3 次请求，则 <span displaypfx="inline-" class="mathjax-container">\(\lambda=3\)</span>。此时一分钟内 0 次请求的概率是 <span displaypfx="inline-" class="mathjax-container">\(e^{-3}\approx 0.05\)</span>；5 次请求的概率是 <span displaypfx="inline-" class="mathjax-container">\(e^{-3}3^5/5!\approx 0.10\)</span>。泊松分布常被用来做“到达数 / 故障数 / 事件数”建模。</p>
<div class="blog_h2"><span class="graybg">期望</span></div>
<p>期望（Expectation）是随机变量的“按概率加权的平均”，回答“长期来看这个量的典型水平在哪里”。离散情形下，它把每个可能取值按对应概率加权；连续情形下，则对概率密度做积分：</p>
<span displaypfx="" class="mathjax-container">\[\mathbb{E}[X]=\sum_x x\,p(x)\ \ (\text{或 } \int x\,p(x)\,dx)\]</span>
<p><span style="background-color: #c0c0c0;">期望和均值很像，但不是同一个概念。</span> 期望（Expectation）是总体分布层面的量，由概率模型 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span> 决定，描述“如果无限次重复抽样，平均会稳定到哪里”；均值（Mean）通常有两种语境：一是把总体的中心位置也叫“总体均值”，这时它与期望是同一个量；二是指有限样本算出来的样本均值（Sample Mean） <span displaypfx="inline-" class="mathjax-container">\(\bar{x}=\frac{1}{n}\sum_{i=1}^{n}x_i\)</span>，这是用样本近似期望的统计量。</p>
<p>因此可以把三者关系记成：<span style="background-color: #c0c0c0;">总体均值 = 期望；样本均值 = 对期望的估计</span>。例如公平骰子点数的期望是 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X]=3.5\)</span>；如果你真的掷 6 次，样本均值可能是 3、4 或 4.5，不必恰好等于 3.5，但随着试验次数增多，它会越来越接近期望。</p>
<p>期望最重要的性质之一是线性性（Linearity）：<span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[aX+b]=a\mathbb{E}[X]+b\)</span>，不要求独立。例：公平骰子点数 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 的期望是 <span displaypfx="inline-" class="mathjax-container">\((1+2+3+4+5+6)/6=3.5\)</span>；若收入模型写成 <span displaypfx="inline-" class="mathjax-container">\(Y=2X+1\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[Y]=2\times 3.5+1=8\)</span>。</p>
<div class="blog_h2"><span class="graybg">矩（Moments）</span></div>
<p>矩（Moment）是概率统计里用来概括分布形状的一组数。理论上，“第 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 阶”指对 <span displaypfx="inline-" class="mathjax-container">\(k\)</span> 次幂取期望：矩天然与期望/均值绑定。</p>
<p>常用两类定义是：</p>
<ul>
<li>原点矩（Raw Moment / Non-central Moment）：<span displaypfx="inline-" class="mathjax-container">\(m_k=\mathbb{E}[X^k]\)</span>（以 0 为参照）。</li>
<li>中心矩（Central Moment）：<span displaypfx="inline-" class="mathjax-container">\(\mu_k=\mathbb{E}\big[(X-\mathbb{E}[X])^k\big]\)</span>（以均值为参照）。</li>
</ul>
<p>直觉上可以用“跷跷板”来理解：把概率质量看作分布在数轴上的“重量”，期望/均值决定“支点放哪里”才平衡；更高阶矩描述“重量围绕支点如何分布”。</p>
<p>一阶矩（First Moment）回答“中心在哪里”：均值/期望 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X]\)</span> 给出平衡点。但<span style="background-color: #c0c0c0;">一阶矩无法刻画离散程度</span>：例如 <span displaypfx="inline-" class="mathjax-container">\(X\in\{-1,+1\}\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(X\in\{-10,+10\}\)</span> 都满足 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X]=0\)</span>，但后者离中心更远。</p>
<p>二阶量用平方偏差的期望刻画离散程度：平方保证非负、避免正负偏差相互抵消，并对远离均值的样本赋予更大权重。在统计里，这对应二阶中心矩——方差 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(X)=\mathbb{E}[(X-\mathbb{E}[X])^2]\)</span>；在力学类比里，这与转动惯量（Moment of Inertia）中按 <span displaypfx="inline-" class="mathjax-container">\(r^2\)</span> 加权的直觉一致：远处的“重量”对系统的“难摆动/难转动”贡献更大。</p>
<p>二阶原点矩与方差之间满足恒等式：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Var}(X)=\mathbb{E}\big[(X-\mathbb{E}[X])^2\big]=\mathbb{E}[X^2]-\mathbb{E}[X]^2\]</span>
<span displaypfx="" class="mathjax-container">\[\mathbb{E}[X^2]=\mathrm{Var}(X)+\mathbb{E}[X]^2\]</span>
<p>它说明二阶原点矩 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X^2]\)</span> 以 0 为参照，会把两类效应叠加在一起：一是分布整体离 0 的偏移（由均值项 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X]^2\)</span> 表示），二是围绕自身均值的离散/抖动（由方差项 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(X)\)</span> 表示）。因此 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X^2]\)</span> 变大并不等价于“波动更大”；把所有取值整体平移一个常数，方差不变，但二阶原点矩会随偏移显著改变。</p>
<p>一个最小例子可以把差异看得非常清楚。令 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 等概率取值 <span displaypfx="inline-" class="mathjax-container">\(\{99,100,101\}\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[\mathbb{E}[X]=\frac{99+100+101}{3}=100,\quad \mathbb{E}[X]^2=100^2=10000\]</span>
<span displaypfx="" class="mathjax-container">\[\mathbb{E}[X^2]=\frac{99^2+100^2+101^2}{3}=\frac{30002}{3}\approx 10000.67\]</span>
<span displaypfx="" class="mathjax-container">\[\mathrm{Var}(X)=\mathbb{E}[X^2]-\mathbb{E}[X]^2=\frac{30002}{3}-10000=\frac{2}{3}\]</span>
<p>可以直接读成一句话：这组数“离 0 很远”（所以 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X^2]\)</span> 很大），但“围绕自身均值很稳定”（所以方差很小）。</p>
<p>这也解释了 Adam 里的常见误解。Adam 说的“一阶矩/二阶矩”是把 mini-batch 梯度 <span displaypfx="inline-" class="mathjax-container">\(g\)</span> 当作随机变量，分别估计 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[g]\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[g^2]\)</span>（逐参数维度）。它用的是二阶原点矩 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[g^2]\)</span>，不是方差 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(g)\)</span>。原因很直接：如果梯度在一段时间内几乎恒定（例如每步都是 <span displaypfx="inline-" class="mathjax-container">\(g=10\)</span>），那么方差为 0，拿它做除法会造成数值灾难；但 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[g^2]=100\)</span> 给出了稳定的“绝对尺度”，使更新项 <span displaypfx="inline-" class="mathjax-container">\(\frac{\mathbb{E}[g]}{\sqrt{\mathbb{E}[g^2]}}\)</span> 仍然是有界的（再加上 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 做数值稳定）。</p>
<div class="blog_h2"><span class="graybg">方差</span></div>
<p>方差（Variance）衡量随机变量围绕均值的波动大小。它不是“偏离均值后再平均”，而是“偏离均值的平方再平均”；平方的作用是避免正负抵消，并放大大偏差：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Var}(X)=\mathbb{E}[(X-\mu)^2]\]</span>
<p>因此，方差小表示样本更集中在均值附近；方差大表示波动更强。它刻画的是“不确定性的尺度”，而不是取值本身的大小。例如两个模型的平均误差相同，方差更大的那个模型，输出往往更不稳定。</p>
<div class="blog_h2"><span class="graybg">协方差</span></div>
<p>协方差（Covariance）描述两个变量是否倾向同向变化：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Cov}(X,Y)=\mathbb{E}[(X-\mu_X)(Y-\mu_Y)]\]</span>
<p>若 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Cov}(X,Y)&gt;0\)</span>，说明 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 大时 <span displaypfx="inline-" class="mathjax-container">\(Y\)</span> 也倾向变大；若 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Cov}(X,Y)&lt;0\)</span>，说明一个变大时另一个倾向变小。直觉例子是：身高与体重常为正协方差，室外温度与暖气功率常为负协方差。</p>
<p>还要区分一点：协方差为 0 不必然意味着独立。例如令 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\(\{-1,1\}\)</span> 上等概率取值，定义 <span displaypfx="inline-" class="mathjax-container">\(Y=X^2\)</span>，则 <span displaypfx="inline-" class="mathjax-container">\(Y\)</span> 被 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 完全决定，但 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Cov}(X,Y)=0\)</span>。因此“零相关”比“独立”弱得多。</p>
<p>协方差矩阵（Covariance Matrix）<span displaypfx="inline-" class="mathjax-container">\(\Sigma\)</span> 在 AI 中极其关键：PCA、马氏距离（Mahalanobis Distance）、卡尔曼滤波与高斯模型都直接依赖它。它编码的是“各方向上的尺度”与“不同维度之间的耦合”。</p>
<div class="blog_h2"><span class="graybg">标准差（Standard Deviation）</span></div>
<p>标准差（Standard Deviation）衡量数据相对均值（Mean）的离散程度（Dispersion）。总体标准差（Population Standard Deviation）定义为：</p>
<span displaypfx="" class="mathjax-container">\[\sigma=\sqrt{\frac{1}{n}\sum_{i=1}^{n}(x_i-\mu)^2}\]</span>
<p>样本标准差（Sample Standard Deviation）使用贝塞尔校正（Bessel's Correction）：</p>
<span displaypfx="" class="mathjax-container">\[s=\sqrt{\frac{1}{n-1}\sum_{i=1}^{n}(x_i-\bar{x})^2}\]</span>
<p>先平方再平均是为了避免正负抵消并加重大偏差；最后开平方是为了把单位恢复到原始尺度。若直接用方差，单位会变成“平方单位”，解释往往不直观；标准差则可以直接说成“典型偏离均值大约多少个原始单位”。</p>
<p>例：数据 <span displaypfx="inline-" class="mathjax-container">\(\{2,4,4,4,5,5,7,9\}\)</span> 的均值是 <span displaypfx="inline-" class="mathjax-container">\(5\)</span>，总体方差是 <span displaypfx="inline-" class="mathjax-container">\(4\)</span>，标准差是 <span displaypfx="inline-" class="mathjax-container">\(2\)</span>。这意味着一个典型样本与均值的偏离量级大约是 2，而不是每个点都恰好偏 2。</p>
<p>标准差还常被用来做标准化（Standardization）。例如某考试分数 80 分，班级均值 70、标准差 5，则它的 z-score 是 <span displaypfx="inline-" class="mathjax-container">\((80-70)/5=2\)</span>，表示它比均值高 2 个标准差。近似正态分布下，经验上约有 68% 样本落在 <span displaypfx="inline-" class="mathjax-container">\(\mu\pm\sigma\)</span>，95% 落在 <span displaypfx="inline-" class="mathjax-container">\(\mu\pm2\sigma\)</span>，99.7% 落在 <span displaypfx="inline-" class="mathjax-container">\(\mu\pm3\sigma\)</span>；这就是常见的 68-95-99.7 规则。</p>
<div class="blog_h2"><span class="graybg">最大似然估计（MLE）</span></div>
<p>最大似然估计（Maximum Likelihood Estimation, MLE）先把已经观察到的数据（例如上面抛硬币10次有8次正面）固定住，再在参数空间里寻找“最能生成这批数据”的参数。设数据集为 <span displaypfx="inline-" class="mathjax-container">\(D=\{x_1,\dots,x_n\}\)</span>，参数为 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>，则定义是：</p>
<span displaypfx="" class="mathjax-container">\[\hat{\theta}_{\mathrm{MLE}}=\arg\max_{\theta}p(D|\theta)\]</span>
<p>其中<span displaypfx="inline-" class="mathjax-container">\(p(D|\theta)\)</span> 是“参数取 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 时看到整批数据 <span displaypfx="inline-" class="mathjax-container">\(D\)</span> 的概率/密度”； <span displaypfx="inline-" class="mathjax-container">\(\arg\max\)</span> 表示“找出让这个值最大的那个参数”。</p>
<p>若样本独立同分布（不相关且在同一个分布，例如抛硬币中10个独立事件。Independent and Identically Distributed, i.i.d.），联合似然可以拆成单个样本似然的乘积：</p>
<span displaypfx="" class="mathjax-container">\[p(D|\theta)=\prod_{i=1}^{n}p(x_i|\theta)\]</span>
<p>于是 MLE 常写成</p>
<span displaypfx="" class="mathjax-container">\[\hat{\theta}_{\mathrm{MLE}}=\arg\max_{\theta}\prod_{i=1}^{n}p(x_i|\theta)\]</span>
<p>实际训练几乎总是改为最大化对数似然（Log-Likelihood）：</p>
<span displaypfx="" class="mathjax-container">\[\ell(\theta)=\log p(D|\theta)=\sum_{i=1}^{n}\log p(x_i|\theta)\]</span>
<p><span style="background-color: #c0c0c0;">取对数不会改变最优解</span>，因为 <span displaypfx="inline-" class="mathjax-container">\(\log\)</span> 是单调递增函数；它只是把难处理的连乘变成易处理的求和。因此，最大化似然与最小化负对数似然（Negative Log-Likelihood, NLL）完全等价。</p>
<p>继续看抛硬币的例子。设单次结果 <span displaypfx="inline-" class="mathjax-container">\(x_i\in\{0,1\}\)</span>，其中 1 表示正面，0 表示反面。设正面概率为 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>，那么单个样本就服从伯努利分布（Bernoulli Distribution）。它的概率质量函数（Probability Mass Function, PMF，用于描述离散随机变量，直接给出概率。对应概率密度函数PDF，给出给定连续随机变量对应位置的密度，因为连续，特定点的概率必须为0，只能密度和变量范围积分得到概率）写成：</p>
<span displaypfx="" class="mathjax-container">\[p(x_i|\theta)=\theta^{x_i}(1-\theta)^{1-x_i},\quad x_i\in\{0,1\}\]</span>
<p>左边的 <span displaypfx="inline-" class="mathjax-container">\(p(\cdot|\theta)\)</span> 里的 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 是“概率分布/概率质量函数”的记号， <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 是模型参数，也就是“正面概率”。很多教材会把参数也记成 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>，写成 <span displaypfx="inline-" class="mathjax-container">\(p(x_i|p)\)</span>；这并不算错，因为参数本身就是一个概率，但两个 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 同时出现时很容易视觉混淆，所以这里改用 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 来区分“模型符号”和“参数符号”。</p>
<p>当 <span displaypfx="inline-" class="mathjax-container">\(x_i=1\)</span> 时，</p>
<span displaypfx="" class="mathjax-container">\[p(x_i=1|\theta)=\theta^1(1-\theta)^0=\theta\]</span>
<p>当 <span displaypfx="inline-" class="mathjax-container">\(x_i=0\)</span> 时，</p>
<span displaypfx="" class="mathjax-container">\[p(x_i=0|\theta)=\theta^0(1-\theta)^1=1-\theta\]</span>
<p>如果 10 次里看到 7 次正面、3 次反面，那么整批数据的似然就是：</p>
<span displaypfx="" class="mathjax-container">\[p(D|\theta)=\theta^7(1-\theta)^3\]</span>
<p>对应的对数似然是</p>
<span displaypfx="" class="mathjax-container">\[\ell(\theta)=7\log \theta+3\log(1-\theta)\]</span>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/plot-binomial-loglik.png"><img class="alignnone size-full wp-image-41091" src="https://blog.gmem.cc/wp-content/uploads/2026/03/plot-binomial-loglik.png" alt="plot-binomial-loglik" width="100%" /></a></p>
<p>我们现在要最大化似然，需要对 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 求导并令导数为 0：</p>
<span displaypfx="" class="mathjax-container">\[\frac{d\ell(\theta)}{d\theta}=\frac{7}{\theta}-\frac{3}{1-\theta}=0\Rightarrow \theta=0.7\]</span>
<p>所以 <span displaypfx="inline-" class="mathjax-container">\(\hat{\theta}_{\mathrm{MLE}}=0.7\)</span>。即“在所有候选 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 里， <span displaypfx="inline-" class="mathjax-container">\(\theta=0.7\)</span> 最能让 7 正 3 反这份数据显得不奇怪”。</p>
<div class="blog_h3"><span class="graybg">从概率建模到损失函数</span></div>
<p>在机器学习里，损失函数往往由<span style="background-color: #c0c0c0;">概率建模假设（Probabilistic Modeling Assumption）</span>诱导出来。做法是先写下观测数据在参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 下的概率模型，再把训练集的负对数似然（Negative Log-Likelihood, NLL）当作优化目标。于是，最小化损失就不再只是一个工程规定，而是等价于最大化“已观测数据在模型下出现的可能性”。</p>
<p>以最常见的回归假设为例，若模型输出写成 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)\)</span>，并假设真实标签满足</p>
<span displaypfx="" class="mathjax-container">\[y=f_\theta(x)+\varepsilon,\qquad \varepsilon\sim\mathcal{N}(0,\sigma^2)\]</span>
<p>这里的高斯分布指的不是 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 本身，也不是 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)\)</span> 本身，而是<span style="background-color: #c0c0c0;">误差项，也就是残差 <span displaypfx="inline-" class="mathjax-container">\(y-f_\theta(x)\)</span> 的分布</span>。误差总是相对于模型当前给出的预测中心 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)\)</span> 来计算：预测值附近最可能出现，偏离越远，概率越低。</p>
<p>从这个误差模型出发，可以把“误差分布”直接改写成“真实标签在给定输入下的条件分布”。因为 <span displaypfx="inline-" class="mathjax-container">\(\varepsilon\)</span> 服从均值为 0 的高斯分布，所以等价地，真实标签服从一个均值为 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)\)</span> 的高斯分布：</p>
<span displaypfx="" class="mathjax-container">\[y\,|\,x \sim \mathcal{N}(f_\theta(x),\sigma^2)\]</span>
<p>这条式子的含义是：给定输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 之后，模型并不把输出 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 看成一个确定值，而是把它看成一个<span style="background-color: #c0c0c0;">以预测值 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)\)</span> 为中心、方差为 <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span> 的高斯随机变量</span>。根据高斯分布的概率密度函数，可进一步写出条件概率密度：</p>
<span displaypfx="" class="mathjax-container">\[p_\theta(y|x)=\frac{1}{\sqrt{2\pi\sigma^2}}\exp\left(-\frac{(y-f_\theta(x))^2}{2\sigma^2}\right)\]</span>
<p>这时 <span displaypfx="inline-" class="mathjax-container">\(p_\theta(y_i|x_i)\)</span> 的含义就自然了：它评估的是在当前参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 下，训练集中已经观测到的真实标签 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span>，作为“围绕 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x_i)\)</span> 摆动的一个样本”是否足够合理。被评分的始终是<span style="background-color: #c0c0c0;">真实观测到的 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span></span>，而不是模型预测出来的 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x_i)\)</span>。<span displaypfx="inline-" class="mathjax-container">\(f_\theta(x_i)\)</span> 的作用不是直接成为被评分对象，而是作为条件分布的均值或位置参数，决定观测值最可能出现的位置。</p>
<p>这种思路并不限于高斯回归。分类任务里，条件分布不再是高斯，而会改成 Bernoulli 或 Categorical；若把噪声假设换成拉普拉斯分布（Laplacian Distribution），则导出的损失也会从平方误差变成绝对误差。更严谨地说，<span style="background-color: #c0c0c0;">损失函数是概率模型在训练集上的负对数似然写开后的结果</span>：高斯噪声导出平方误差（Squared Error），拉普拉斯噪声导出绝对误差（Absolute Error），Bernoulli 与 Categorical 分布则导出二分类或多分类交叉熵（Cross-Entropy）。</p>
<div class="blog_h3"><span class="graybg">高斯分布的MLE和最小二乘</span></div>
<p>先看监督回归（Supervised Regression）。训练集写成 <span displaypfx="inline-" class="mathjax-container">\(D=\{(x_i,y_i)\}_{i=1}^N\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(x_i\)</span> 是输入特征，<span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 是真实输出。按照上面的统一框架，这里仍然是把 <span displaypfx="inline-" class="mathjax-container">\(x_i\)</span> 当作给定条件，只为输出建模条件分布 <span displaypfx="inline-" class="mathjax-container">\(p_\theta(y|x)\)</span>，并让真实观测到的 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 在模型下尽可能不意外：</p>
<span displaypfx="" class="mathjax-container">\[\hat\theta_{\mathrm{MLE}}=\arg\max_{\theta}\prod_{i=1}^{N}p_\theta(y_i|x_i)\]</span>
<p>这里的参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 不在 <span displaypfx="inline-" class="mathjax-container">\(x_i\)</span> 里，而是出现在假设函数 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)\)</span> 里。例如线性回归（Linear Regression）可写成 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)=\mathbf{w}^\top x+b\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(\theta=(\mathbf{w},b)\)</span>。回归里常说的“高斯分布”指的不是整张训练集的 <span displaypfx="inline-" class="mathjax-container">\(y\)</span> 服从高斯分布，也不是模型预测值 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)\)</span> 本身服从高斯分布，而是指<span style="background-color: #c0c0c0;">误差项，也就是残差 <span displaypfx="inline-" class="mathjax-container">\(y_i-f_\theta(x_i)\)</span> 的分布</span>。误差总是相对于当前模型给出的中心值 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x_i)\)</span> 来计算。</p>
<p>具体假设是：</p>
<span displaypfx="" class="mathjax-container">\[y_i=f_\theta(x_i)+\varepsilon_i,\quad \varepsilon_i\sim\mathcal{N}(0,\sigma^2)\ \text{i.i.d.}\]</span>
<p>等价写成条件分布：</p>
<span displaypfx="" class="mathjax-container">\[y_i\,|\,x_i \sim \mathcal{N}(f_\theta(x_i),\sigma^2)\]</span>
<p>上式的意思是：在给定输入 <span displaypfx="inline-" class="mathjax-container">\(x_i\)</span> 的条件下，输出 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 不再被视为确定值，而被建模为随机变量，并且服从均值为 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x_i)\)</span>、方差为 <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span> 的一维高斯分布（Gaussian Distribution），当然，这里的不确定性就是模型引入的。根据高斯分布的概率密度公式，并结合 i.i.d. 假设（联合似然为逐样本似然的乘积），整批数据的似然为</p>
<span displaypfx="" class="mathjax-container">\[p(D|\theta,\sigma^2)=\prod_{i=1}^{N}\frac{1}{\sqrt{2\pi\sigma^2}}\exp\left(-\frac{(y_i-f_\theta(x_i))^2}{2\sigma^2}\right)\]</span>
<p>取负对数似然（Negative Log-Likelihood, NLL）得到训练时真正被最小化的量：</p>
<span displaypfx="" class="mathjax-container">\[-\log p(D|\theta,\sigma^2)=\frac{N}{2}\log(2\pi\sigma^2)+\frac{1}{2\sigma^2}\sum_{i=1}^{N}(y_i-f_\theta(x_i))^2\]</span>
<p>当 <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span> 视为常数时，第一项与 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 无关，因此最小化 NLL 关于 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 等价于最小化平方误差和（Sum of Squared Errors, SSE）：</p>
<span displaypfx="" class="mathjax-container">\[\hat\theta_{\mathrm{MLE}}=\arg\min_\theta\sum_{i=1}^{N}(y_i-f_\theta(x_i))^2\]</span>
<p>把残差（Residual）记为 <span displaypfx="inline-" class="mathjax-container">\(r_i=y_i-f_\theta(x_i)\)</span>，则上式就是最小化 <span displaypfx="inline-" class="mathjax-container">\(\sum_i r_i^2\)</span>。因此，在“高斯噪声 + 固定方差”的回归假设下，<span style="background-color: #c0c0c0;">最大化似然（等价最大化对数似然）就是最小化残差平方和</span>。</p>
<p>这就是“最小二乘（Least Squares）”为什么会从概率建模里自然出现；均方误差（Mean Squared Error, MSE）只是把 SSE 再除以 <span displaypfx="inline-" class="mathjax-container">\(N\)</span>，不改变最优解。</p>
<p>如果 <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span> 也未知，则 MLE 会同时估计 <span displaypfx="inline-" class="mathjax-container">\((\theta,\sigma^2)\)</span>。在固定 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 时，对 <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span> 的 MLE 为</p>
<span displaypfx="" class="mathjax-container">\[\hat\sigma^2_{\mathrm{MLE}}=\frac{1}{N}\sum_{i=1}^{N}(y_i-f_\theta(x_i))^2\]</span>
<p>注意这里是 <span displaypfx="inline-" class="mathjax-container">\(1/N\)</span>（MLE），而不是无偏估计常用的 <span displaypfx="inline-" class="mathjax-container">\(1/(N-1)\)</span>。</p>
<p>顺带一提：把回归模型退化为“常数预测” <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)\equiv \mu\)</span>，就回到“在高斯噪声下估计均值 <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span>”，其 MLE 是样本均值。这也是很多教材先从 <span displaypfx="inline-" class="mathjax-container">\(x_i=\mu+\varepsilon_i\)</span> 入手的原因：那是回归的一个最小特例（此时 <span displaypfx="inline-" class="mathjax-container">\(\theta=\mu\)</span>）。</p>
<div class="blog_h3"><span class="graybg">Bernoulli / Categorical 分布和交叉熵</span></div>
<p>分类任务与回归不同：标签通常是离散变量，因此这里使用的不是概率密度函数（Probability Density Function, PDF），而是概率质量函数（Probability Mass Function, PMF）。做最大似然估计时，最常见的做法同样是把输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 视为给定条件，只为标签建模条件分布 <span displaypfx="inline-" class="mathjax-container">\(p_\theta(y|x)\)</span>，然后让已观测标签在模型下尽可能“显得合理”。</p>
<p>先看二分类（Binary Classification）。设标签 <span displaypfx="inline-" class="mathjax-container">\(y_i\in\{0,1\}\)</span>，模型输出正类概率 <span displaypfx="inline-" class="mathjax-container">\(\pi_\theta(x_i)=p_\theta(y_i=1|x_i)\)</span>，则单个样本服从伯努利分布（Bernoulli Distribution）：</p>
<span displaypfx="" class="mathjax-container">\[p_\theta(y_i|x_i)=\pi_\theta(x_i)^{y_i}\bigl(1-\pi_\theta(x_i)\bigr)^{1-y_i}\]</span>
<p>在 i.i.d. 假设下，整批数据的似然为</p>
<span displaypfx="" class="mathjax-container">\[p(D|\theta)=\prod_{i=1}^{N}\pi_\theta(x_i)^{y_i}\bigl(1-\pi_\theta(x_i)\bigr)^{1-y_i}\]</span>
<p>取负对数似然，得到</p>
<span displaypfx="" class="mathjax-container">\[-\log p(D|\theta)=-\sum_{i=1}^{N}\Big[y_i\log \pi_\theta(x_i)+(1-y_i)\log\bigl(1-\pi_\theta(x_i)\bigr)\Big]\]</span>
<p>这正是二分类交叉熵（Binary Cross-Entropy, BCE）。因此，在“标签服从伯努利分布”的假设下，<span style="background-color: #c0c0c0;">最大化似然等价于最小化二分类交叉熵</span>。若再把 <span displaypfx="inline-" class="mathjax-container">\(\pi_\theta(x)\)</span> 写成 sigmoid 作用在 logit <span displaypfx="inline-" class="mathjax-container">\(z_\theta(x)\)</span> 上，即 <span displaypfx="inline-" class="mathjax-container">\(\pi_\theta(x)=\sigma(z_\theta(x))\)</span>，就得到工程实现里最常见的 sigmoid + BCE 训练形式。</p>
<p>再看多分类（Multiclass Classification）。设类别总数为 <span displaypfx="inline-" class="mathjax-container">\(C\)</span>，并用 one-hot 向量 <span displaypfx="inline-" class="mathjax-container">\(y_i=(y_{i1},\dots,y_{iC})\)</span> 表示真实标签，其中只有真实类别对应那一项为 1，其余为 0。若模型输出类别概率 <span displaypfx="inline-" class="mathjax-container">\(\pi_{\theta,1}(x_i),\dots,\pi_{\theta,C}(x_i)\)</span>，且满足 <span displaypfx="inline-" class="mathjax-container">\(\sum_{c=1}^{C}\pi_{\theta,c}(x_i)=1\)</span>，则单个样本服从类别分布（Categorical Distribution）：</p>
<span displaypfx="" class="mathjax-container">\[p_\theta(y_i|x_i)=\prod_{c=1}^{C}\pi_{\theta,c}(x_i)^{y_{ic}}\]</span>
<p>整批数据的似然为</p>
<span displaypfx="" class="mathjax-container">\[p(D|\theta)=\prod_{i=1}^{N}\prod_{c=1}^{C}\pi_{\theta,c}(x_i)^{y_{ic}}\]</span>
<p>取负对数似然，得到</p>
<span displaypfx="" class="mathjax-container">\[-\log p(D|\theta)=-\sum_{i=1}^{N}\sum_{c=1}^{C}y_{ic}\log \pi_{\theta,c}(x_i)\]</span>
<p>这正是多分类交叉熵（Categorical Cross-Entropy）。若标签是标准 one-hot，那么每个样本只会保留真实类别那一项，损失就退化成 <span displaypfx="inline-" class="mathjax-container">\(-\log \pi_{\theta,c^*}(x_i)\)</span>；若标签本身是软标签（Soft Label），则公式不变，只是 <span displaypfx="inline-" class="mathjax-container">\(y_{ic}\)</span> 不再只有 0 和 1，而是一个分布。</p>
<div class="blog_h3"><span class="graybg">拉普拉斯分布的MLE和绝对误差</span></div>
<p>若回归任务不再假设误差服从高斯分布，而是假设误差项 <span displaypfx="inline-" class="mathjax-container">\(\varepsilon\)</span> 服从拉普拉斯分布（Laplacian Distribution），即</p>
<span displaypfx="" class="mathjax-container">\[y_i=f_\theta(x_i)+\varepsilon_i,\qquad \varepsilon_i\sim \mathrm{Laplace}(0,b)\ \text{i.i.d.}\]</span>
<p>那么等价地，真实标签在给定输入后的条件分布可写成</p>
<span displaypfx="" class="mathjax-container">\[y_i\,|\,x_i \sim \mathrm{Laplace}(f_\theta(x_i),b)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x_i)\)</span> 是位置参数（Location Parameter），<span displaypfx="inline-" class="mathjax-container">\(b\)</span> 是尺度参数（Scale Parameter）。拉普拉斯分布的一维概率密度函数为</p>
<span displaypfx="" class="mathjax-container">\[p_\theta(y_i|x_i)=\frac{1}{2b}\exp\left(-\frac{|y_i-f_\theta(x_i)|}{b}\right)\]</span>
<p>在 i.i.d. 假设下，整批数据的似然为</p>
<span displaypfx="" class="mathjax-container">\[p(D|\theta,b)=\prod_{i=1}^{N}\frac{1}{2b}\exp\left(-\frac{|y_i-f_\theta(x_i)|}{b}\right)\]</span>
<p>取负对数似然，得到</p>
<span displaypfx="" class="mathjax-container">\[-\log p(D|\theta,b)=N\log(2b)+\frac{1}{b}\sum_{i=1}^{N}|y_i-f_\theta(x_i)|\]</span>
<p>当 <span displaypfx="inline-" class="mathjax-container">\(b\)</span> 视为常数时，第一项与 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 无关，因此最小化 NLL 关于 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 等价于最小化绝对误差和（Sum of Absolute Errors, SAE）：</p>
<span displaypfx="" class="mathjax-container">\[\hat\theta_{\mathrm{MLE}}=\arg\min_\theta\sum_{i=1}^{N}|y_i-f_\theta(x_i)|\]</span>
<p>这正是平均绝对误差（Mean Absolute Error, MAE）背后的概率来源。与高斯分布相比，拉普拉斯分布在中心更尖、尾部更厚，因此它对应的损失不再像平方误差那样强烈放大大残差，而是按线性方式惩罚偏差。也正因为如此，MAE 通常比 MSE 对离群点（Outlier）更稳健。</p>
<p>因此，高斯分布与最小二乘、拉普拉斯分布与绝对误差、Bernoulli / Categorical 分布与交叉熵，其实是同一个统计逻辑在不同任务类型下的三种展开：<span style="background-color: #c0c0c0;">先假设观测数据服从某个条件分布，再对训练集做最大似然估计；把负对数似然写开后，就得到具体的训练损失</span>。</p>
<div class="blog_h2"><span class="graybg">最大后验估计（MAP）</span></div>
<p>最大后验估计（Maximum A Posteriori, MAP）在 MLE 的“数据解释能力”之外，再加入参数的先验（Prior）信息。它选择后验概率（Posterior）最大的参数：</p>
<span displaypfx="" class="mathjax-container">\[\hat{\theta}_{\mathrm{MAP}}=\arg\max_{\theta}p(\theta|D)\]</span>
<p>根据贝叶斯定理：</p>
<span displaypfx="" class="mathjax-container">\[p(\theta|D)=\frac{p(D|\theta)p(\theta)}{p(D)}\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(p(D|\theta)\)</span> 是似然（Likelihood）， <span displaypfx="inline-" class="mathjax-container">\(p(\theta)\)</span> 是先验， <span displaypfx="inline-" class="mathjax-container">\(p(D)\)</span> 是证据（Evidence）。因为 <span displaypfx="inline-" class="mathjax-container">\(p(D)\)</span> 与参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 无关，所以在求最大值时它只是常数，可以直接忽略：</p>
<span displaypfx="" class="mathjax-container">\[\hat{\theta}_{\mathrm{MAP}}=\arg\max_{\theta}p(D|\theta)p(\theta)=\arg\max_{\theta}\big(\log p(D|\theta)+\log p(\theta)\big)\]</span>
<p>这个式子清楚地说明了 MAP 的结构： <span displaypfx="inline-" class="mathjax-container">\(\log p(D|\theta)\)</span> 负责拟合数据， <span displaypfx="inline-" class="mathjax-container">\(\log p(\theta)\)</span> 负责约束参数不要偏离先验认知。若改写成最小化形式，则有</p>
<span displaypfx="" class="mathjax-container">\[\hat{\theta}_{\mathrm{MAP}}=\arg\min_\theta\big(-\log p(D|\theta)-\log p(\theta)\big)\]</span>
<p>因此，MAP 可以理解为“负对数似然 + 先验诱导的惩罚项（Penalty）”。这也是为什么很多正则化（Regularization）能够从贝叶斯视角解释：L2 正则通常对应高斯先验，L1 正则通常对应拉普拉斯先验。若先验在可行域内与 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 无关（例如均匀先验： <span displaypfx="inline-" class="mathjax-container">\(p(\theta)=c\)</span>），则 <span displaypfx="inline-" class="mathjax-container">\(\log p(\theta)=\log c\)</span> 是关于 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 的常数；在 <span displaypfx="inline-" class="mathjax-container">\(\arg\max_\theta\)</span> 中加上或去掉这一项都不改变最优点，因此 MAP 与 MLE 给出同一组参数（若均匀先验还隐含“只在某个范围内为常数、范围外为 0”，则等价于在该范围约束下做 MLE）。</p>
<p>继续用抛硬币解释。现在参数已经统一记作 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>，因此先验也写成 <span displaypfx="inline-" class="mathjax-container">\(p(\theta)\)</span>，而不再写 <span displaypfx="inline-" class="mathjax-container">\(p(p)\)</span>。若对 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 设 Beta 分布（Beta Distribution）先验</p>
<p>Beta 分布是定义在区间 <span displaypfx="inline-" class="mathjax-container">\([0,1]\)</span> 上的连续分布，最常用来描述“某个概率值本身的不确定性”，因此非常适合作为伯努利参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 的先验。</p>
<span displaypfx="" class="mathjax-container">\[p(\theta)\propto \theta^{\alpha-1}(1-\theta)^{\beta-1},\quad 0\le \theta\le 1\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\propto\)</span> 表示“正比于”，意思是左边真正的概率密度函数还差一个归一化常数（Normalization Constant），这个常数负责保证密度在区间 <span displaypfx="inline-" class="mathjax-container">\([0,1]\)</span> 上积分为 1。对 MAP 而言，这个常数不影响最大值位置，所以通常省略不写。</p>
<p>参数 <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 和 <span displaypfx="inline-" class="mathjax-container">\(\beta\)</span> 控制分布形状： <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 越大，分布越偏向较大的 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>； <span displaypfx="inline-" class="mathjax-container">\(\beta\)</span> 越大，分布越偏向较小的 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>。例如 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Beta}(2,2)\)</span> 会把概率质量更多放在中间区域，表达“更倾向认为硬币接近公平”； <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Beta}(8,2)\)</span> 则更偏向 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 接近 1。</p>
<p>那么它表达的是：在看到数据之前，我们已经对“硬币正面概率应该落在什么范围”有一个先验偏好。</p>
<p>把这个先验与 7 正 3 反的似然 <span displaypfx="inline-" class="mathjax-container">\(p(D|\theta)=\theta^7(1-\theta)^3\)</span> 相乘，得到后验：</p>
<span displaypfx="" class="mathjax-container">\[p(\theta|D)\propto \theta^{7+\alpha-1}(1-\theta)^{3+\beta-1}\]</span>
<p>这说明后验仍然是 Beta 分布：</p>
<span displaypfx="" class="mathjax-container">\[p(\theta|D)=\mathrm{Beta}(7+\alpha,3+\beta)\]</span>
<p>众数（Mode）指的是<span style="background-color: #c0c0c0;">概率密度函数最高的那个位置</span>，也就是“这个分布最偏好的参数值”。因为 MAP 的定义本来就是寻找后验概率/后验密度最大的参数，所以当后验分布已经写出来时，MAP 估计就是这个后验分布的众数。</p>
<p>因为后验与 <span displaypfx="inline-" class="mathjax-container">\(\theta^{7+\alpha-1}(1-\theta)^{3+\beta-1}\)</span> 成正比，所以只需最大化这个函数。取对数后，等价目标变成</p>
<span displaypfx="" class="mathjax-container">\[(7+\alpha-1)\log \theta+(3+\beta-1)\log(1-\theta)\]</span>
<p>对 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 求导并令导数为 0：</p>
<span displaypfx="" class="mathjax-container">\[\frac{7+\alpha-1}{\theta}-\frac{3+\beta-1}{1-\theta}=0\]</span>
<p>移项并整理可得</p>
<span displaypfx="" class="mathjax-container">\[(7+\alpha-1)(1-\theta)=(3+\beta-1)\theta\Rightarrow \hat{\theta}_{\mathrm{MAP}}=\frac{7+\alpha-1}{10+\alpha+\beta-2}\]</span>
<p>因此，当 <span displaypfx="inline-" class="mathjax-container">\(\alpha,\beta&gt;1\)</span> 时，Beta 分布的众数（Mode）也就是 MAP 估计。</p>
<p>若取 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Beta}(2,2)\)</span> 先验，则</p>
<span displaypfx="" class="mathjax-container">\[\hat{\theta}_{\mathrm{MAP}}=\frac{7+2-1}{10+2+2-2}=\frac{8}{12}=\frac{2}{3}\]</span>
<p>它比 MLE 的 <span displaypfx="inline-" class="mathjax-container">\(0.7\)</span> 更靠近 <span displaypfx="inline-" class="mathjax-container">\(0.5\)</span>。原因很具体： <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Beta}(2,2)\)</span> 可以理解为在真实观测之外，先额外放入了“1 次正面 + 1 次反面”的温和偏好，所以最终估计不会被7正3反的样本完全带着走。样本很少时，先验的影响明显；样本很多时，数据项占主导，MAP 会逐渐逼近 MLE。</p>
<p>在机器学习里，MAP 与正则化（Regularization）本质上是同一件事的两种表述。更准确地说，<span style="background-color: #c0c0c0;">一旦你给参数指定先验，取负对数后，这个先验就会自动变成优化目标里的正则项</span>。</p>
<p>先看一般形式。MAP 最大化的是后验概率 <span displaypfx="inline-" class="mathjax-container">\(p(\theta|D)\)</span>，等价于最小化负对数后验：</p>
<span displaypfx="" class="mathjax-container">\[-\log p(\theta|D)=-\log p(D|\theta)-\log p(\theta)+\mathrm{const}\]</span>
<p>第一项 <span displaypfx="inline-" class="mathjax-container">\(-\log p(D|\theta)\)</span> 是数据拟合项，第二项 <span displaypfx="inline-" class="mathjax-container">\(-\log p(\theta)\)</span> 就是先验带来的惩罚项。因此“正则化”并不是凭空发明出来的工程技巧，而是贝叶斯先验在优化问题里的直接体现。</p>
<p>若参数向量 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 服从零均值各向同性高斯先验（Isotropic Gaussian Prior），可以写成</p>
<span displaypfx="" class="mathjax-container">\[p(\theta)\propto \exp\left(-\frac{1}{2\tau^2}\|\theta\|_2^2\right)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\tau^2\)</span> 控制先验方差：方差越小，越强地偏好参数靠近 0。对它取负对数得到</p>
<span displaypfx="" class="mathjax-container">\[-\log p(\theta)=\frac{1}{2\tau^2}\|\theta\|_2^2+\mathrm{const}\]</span>
<p>把常数 <span displaypfx="inline-" class="mathjax-container">\(\frac{1}{2\tau^2}\)</span> 记成 <span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span>，就得到熟悉的目标：</p>
<span displaypfx="" class="mathjax-container">\[-\log p(D|\theta)+\lambda\|\theta\|_2^2\]</span>
<p>这正是 <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 正则。它的作用可以理解为：先验认为“大权重不太可信”，所以优化时不仅要拟合数据，还要为过大的参数付出代价。由于高斯先验在 0 附近平滑、尾部衰减快，它通常会把参数整体压小，但不特别鼓励大量参数精确等于 0。</p>
<p>若先验改成拉普拉斯（Laplacian）分布：</p>
<span displaypfx="" class="mathjax-container">\[p(\theta)\propto \exp\big(-\lambda\|\theta\|_1\big)\]</span>
<p>则负对数先验变成</p>
<span displaypfx="" class="mathjax-container">\[-\log p(\theta)=\lambda\|\theta\|_1+\mathrm{const}\]</span>
<p>于是 MAP 等价于最小化</p>
<span displaypfx="" class="mathjax-container">\[-\log p(D|\theta)+\lambda\|\theta\|_1\]</span>
<p>这就是 <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 正则。它与 <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 的关键区别在于：拉普拉斯先验在 0 处尖得更厉害，因此更强地偏好很多参数直接变成 0，也就是产生稀疏性（Sparsity）。</p>
<p>所以可以把对应关系记成一条很清楚的链：<span style="background-color: #c0c0c0;">高斯先验对应 <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span>，拉普拉斯先验对应 <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span>；正则项的形状，本质上就是先验密度形状的负对数</span>。很多看起来像“人为添加的惩罚项”，其实都可以解释为“在做 MAP”。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/regularization-distribution-assumptions.png"><img class="alignnone size-full" src="https://blog.gmem.cc/wp-content/uploads/2026/03/regularization-distribution-assumptions.png" alt="regularization-distribution-assumptions" width="1920" height="1064" /></a></p>
<p>这里的分布假设有两个不同落点。若假设的是数据误差或残差 <span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span> 的分布，它进入的是似然 <span displaypfx="inline-" class="mathjax-container">\(p(D|\theta)\)</span>，决定数据拟合项的形状；若假设的是参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 自身的分布，它进入的是先验 <span displaypfx="inline-" class="mathjax-container">\(p(\theta)\)</span>，决定正则项的形状。前者回答“数据会怎样围绕模型波动”，后者回答“参数本身更可能落在什么区域”。</p>
<p>因此，高斯和拉普拉斯并不只用于描述数据集。高斯残差假设会把负对数似然化成平方误差，拉普拉斯残差假设会把负对数似然化成绝对误差；与此同时，零均值高斯参数先验会把负对数先验化成 <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 正则，零均值拉普拉斯参数先验会把负对数先验化成 <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 正则。它们使用的是同一类分布族，但一个作用在数据项 <span displaypfx="inline-" class="mathjax-container">\(p(D|\theta)\)</span>，一个作用在参数项 <span displaypfx="inline-" class="mathjax-container">\(p(\theta)\)</span>。</p>
<div class="blog_h2"><span class="graybg">大数定律与中心极限定理</span></div>
<div class="blog_h3"><span class="graybg">大数定律（Law of Large Numbers, LLN）</span></div>
<p>大数定律说明：当独立同分布样本越来越多时，样本平均会稳定地靠近真实期望（Expectation）。若 <span displaypfx="inline-" class="mathjax-container">\(X_1,\dots,X_n\)</span> 独立同分布，且 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X]\)</span> 存在，则</p>
<span displaypfx="" class="mathjax-container">\[\frac{1}{n}\sum_{i=1}^{n}X_i \to \mathbb{E}[X],\quad n\to\infty\]</span>
<p>这里的重点是“平均值会稳定下来”，而不是“每一次新观测都会让结果更接近真值”。它描述的是长期趋势：样本量越大，随机波动在平均过程中被不断抵消，样本平均就越不容易偏离真实期望太远。</p>
<p>最经典的例子是抛公平硬币。设正面记为 1、反面记为 0，则单次试验的期望是 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X]=0.5\)</span>。前几次抛掷时，正面比例可能是 1、0、0.67、0.25，波动很大；但抛掷次数达到几百、几千次后，样本平均 <span displaypfx="inline-" class="mathjax-container">\(\bar{X}\)</span> 会越来越稳定地靠近 0.5。大数定律回答的是“为什么频率会稳定到概率附近”。</p>
<div class="blog_h3"><span class="graybg">中心极限定理（Central Limit Theorem, CLT）</span></div>
<p>中心极限定理（Central Limit Theorem, CLT）描述的是：在独立同分布且方差有限的条件下，样本均值经过适当标准化后，会趋近于正态分布（Normal Distribution）。它给出的不是“平均值最终落到哪里”的结论，而是“平均值围绕真值波动时，波动形状如何分布”的规律。</p>
<p>因此，中心极限定理讨论的重点不是“均值会不会收敛”，而是<span style="background-color: #c0c0c0;">样本均值在真值附近如何波动，以及这种波动的分布长什么样</span>。标准写法是：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\sqrt{n}(\bar{X}-\mu)}{\sigma}\Rightarrow \mathcal{N}(0,1)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\mu=\mathbb{E}[X]\)</span> 是总体均值， <span displaypfx="inline-" class="mathjax-container">\(\sigma^2=\mathrm{Var}(X)\)</span> 是总体方差。所谓<span style="background-color: #c0c0c0;">标准化（Standardization）</span>，就是先减去均值 <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span>，把中心移到 0；再除以标准差 <span displaypfx="inline-" class="mathjax-container">\(\sigma\)</span>，把尺度统一成“多少个标准差”；再乘上 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{n}\)</span>，把样本均值随着样本量增大而缩小的波动重新放回可比较的尺度。标准化之后，不同样本量下的波动可以放到同一个参考系里比较。</p>
<p>所谓<span style="background-color: #c0c0c0;">方差有限</span>，就是随机变量的波动强度不是无限大，满足 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(X)&lt;\infty\)</span>。直观上，这意味着样本虽然会波动，但不会被极端值以“无限强”的方式主导。像伯努利分布、二项分布、泊松分布、均匀分布和高斯分布都满足这个条件；而某些重尾分布则可能不满足，因此不能直接套用最基础的 CLT 表述。</p>
<p>为什么要乘上 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{n}\)</span>？因为样本均值的波动规模大约是 <span displaypfx="inline-" class="mathjax-container">\(\sigma/\sqrt{n}\)</span>：样本越多，均值越稳定。如果不乘 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{n}\)</span>，当 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 增大时， <span displaypfx="inline-" class="mathjax-container">\(\bar{X}-\mu\)</span> 会越来越接近 0，看不出其分布形状；乘上 <span displaypfx="inline-" class="mathjax-container">\(\sqrt{n}\)</span> 后，波动被拉回到常数量级，才会显现出稳定的正态极限。</p>
<p>继续看硬币例子。设 <span displaypfx="inline-" class="mathjax-container">\(X_i\in\{0,1\}\)</span> 且正面概率为 0.5，则 <span displaypfx="inline-" class="mathjax-container">\(\mu=0.5\)</span>， <span displaypfx="inline-" class="mathjax-container">\(\sigma^2=0.25\)</span>，标准差 <span displaypfx="inline-" class="mathjax-container">\(\sigma=0.5\)</span>。CLT 说的是：</p>
<span displaypfx="" class="mathjax-container">\[\frac{\sqrt{n}(\bar{X}-0.5)}{0.5}\Rightarrow \mathcal{N}(0,1)\]</span>
<p>例如当 <span displaypfx="inline-" class="mathjax-container">\(n=100\)</span> 时，正面比例 <span displaypfx="inline-" class="mathjax-container">\(\bar{X}\)</span> 的标准差大约是 <span displaypfx="inline-" class="mathjax-container">\(0.5/\sqrt{100}=0.05\)</span>。这意味着正面比例通常会落在 <span displaypfx="inline-" class="mathjax-container">\(0.5\pm 0.1\)</span> 这一量级附近；更精确地，用正态近似可写成</p>
<span displaypfx="" class="mathjax-container">\[\bar{X}\approx \mathcal{N}\left(0.5,\frac{0.25}{100}\right)=\mathcal{N}(0.5,0.0025)\]</span>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/plot-clt.png"><img class="alignnone size-full wp-image-40929" src="https://blog.gmem.cc/wp-content/uploads/2026/03/plot-clt.png" alt="plot-clt" width="100%" /></a></p>
<p>上图是样本量分别为1, 5, 20, 100的Bernoulli(0.5)分布，分别进行了30000轮测试，绘制的（每轮）标准化后的分布，可以看到样本量足够多时，其均值倾向于向中心（未标准化前的0.5对应此伯努利分布的总体均值）靠近。</p>
<p>所以 CLT 回答的是“为什么很多统计量在样本足够大时会近似高斯”，而 LLN 回答的是“为什么样本平均会逼近真值”。前者给出分布近似，后者给出收敛结论。置信区间、显著性检验、A/B test 和 mini-batch 梯度噪声分析，主要依赖的是 CLT 提供的近似正态性。</p>
<div class="blog_h3"><span class="graybg">置信度与置信区间</span></div>
<p>置信度（Confidence Level）与置信区间（Confidence Interval）属于统计推断（Statistical Inference）中的区间估计（Interval Estimation）。它们处理的不是“样本本身长什么样”，而是<span style="background-color: #c0c0c0;">在只看到一批有限样本时，怎样给未知总体参数画出一个有覆盖保证的范围</span>。在机器学习实验、A/B test、离线评测和指标报表里，样本均值、准确率、点击率、转化率和误差率后面常跟着一个区间，这个区间就是区间估计的产物。</p>
<p>若目标是估计总体均值 <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span>，样本均值为 <span displaypfx="inline-" class="mathjax-container">\(\bar{X}\)</span>，且样本量足够大或总体近似正态，那么基于中心极限定理，可以构造近似的双侧置信区间：</p>
<span displaypfx="" class="mathjax-container">\[\bar{X}\pm z_{1-\alpha/2}\frac{\sigma}{\sqrt{n}}\]</span>
<p>若总体标准差 <span displaypfx="inline-" class="mathjax-container">\(\sigma\)</span> 未知，而样本来自正态总体或样本量适中且使用样本标准差 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 近似，则常写作：</p>
<span displaypfx="" class="mathjax-container">\[\bar{X}\pm t_{1-\alpha/2,\,n-1}\frac{s}{\sqrt{n}}\]</span>
<p>这里每个元素的含义分别是：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\bar{X}\)</span>：样本均值（Sample Mean），是当前样本对总体均值的点估计（Point Estimate）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(n\)</span>：样本量（Sample Size）。样本越多，区间通常越窄。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\sigma\)</span>：总体标准差（Population Standard Deviation）；若未知，常用样本标准差 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 替代。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\frac{\sigma}{\sqrt{n}}\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(\frac{s}{\sqrt{n}}\)</span>：标准误（Standard Error），表示样本均值本身的波动尺度，而不是单个样本点的波动。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span>：显著性水平（Significance Level）。若置信度为 95%，则 <span displaypfx="inline-" class="mathjax-container">\(\alpha=0.05\)</span>。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(z_{1-\alpha/2}\)</span>：标准正态分布的分位数（Quantile）；95% 置信度时大约是 <span displaypfx="inline-" class="mathjax-container">\(1.96\)</span>。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(t_{1-\alpha/2,\,n-1}\)</span>：Student t 分布的分位数，带有 <span displaypfx="inline-" class="mathjax-container">\(n-1\)</span> 个自由度（Degrees of Freedom），用于总体方差未知时。</li>
</ul>
<p>在这个表达式里，<span style="background-color: #c0c0c0;">置信区间</span> 指的是最终得到的区间本身，例如 <span displaypfx="inline-" class="mathjax-container">\([1.8,2.4]\)</span>；<span style="background-color: #c0c0c0;">置信度</span> 指的是构造这个区间的方法在重复抽样下的长期覆盖率，例如 95%、99%。因此，95% 置信区间的严格含义是：如果用同一种抽样方式和同一种区间构造公式反复重复实验，那么大约 95% 的区间会覆盖真实参数。</p>
<p>这一定义有一个极其重要的解释边界。总体参数 <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span> 在经典频率学派（Frequentist）统计里被视为固定但未知的常数；随机的是样本，因此随机的是区间端点。样本一旦观察完成，这次得到的区间要么覆盖 <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span>，要么不覆盖，概率意义已经不再施加在参数本身上。于是，95% 置信度描述的是<span style="background-color: #c0c0c0;">区间构造程序的长期可靠性</span>，而不是“参数有 95% 概率落在这次区间里”的后验概率表述。</p>
<p>置信度并不局限于连续分布。它的定义依赖的是“重复抽样下的覆盖率”，而不是总体分布是否连续。对伯努利分布（Bernoulli Distribution）、二项分布（Binomial Distribution）、泊松分布（Poisson Distribution）这类离散分布，同样可以对参数构造 95% 或 99% 的区间估计。例如对二项分布中的成功概率 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>，就可以根据观测到的成功次数构造 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 的置信区间；若参数或目标对象不是一维连续量，更宽泛的名称则是置信集合（Confidence Set）。</p>
<p>离散分布与连续分布的差别不在“能不能谈置信度”，而在于区间构造的细节。连续情形下，区间端点可以较平滑地调节到目标覆盖率附近；离散情形下，统计量只能取离散值，覆盖率往往无法恰好等于名义值，例如正好 95%，而常常只能做到“不低于 95%”。因此，离散分布里的精确区间常带有一定保守性（Conservativeness）：区间更宽一些，但覆盖保证更稳。这也是为什么在离散统计推断里，除了近似正态区间外，还常见 Clopper–Pearson 这类精确区间构造方法。</p>
<p>一个具体例子最容易说明这两个概念。设某模型在 100 次独立测试中的平均准确率估计为 <span displaypfx="inline-" class="mathjax-container">\(\bar{X}=0.82\)</span>，样本标准差为 <span displaypfx="inline-" class="mathjax-container">\(s=0.10\)</span>。若采用近似 95% 置信区间，可写成：</p>
<span displaypfx="" class="mathjax-container">\[0.82\pm 1.96\times \frac{0.10}{\sqrt{100}}=0.82\pm 0.0196\]</span>
<p>于是区间约为：</p>
<span displaypfx="" class="mathjax-container">\[[0.8004,\ 0.8396]\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\([0.8004,0.8396]\)</span> 是置信区间，95% 是置信度。它表示：按这种抽样和构造方式长期重复下去，约 95% 的此类区间会覆盖真实平均准确率；这次具体得到的这个区间只是其中一个实现结果。</p>
<p>区间宽度主要由三个因素共同决定：</p>
<ul>
<li>样本波动越大，即 <span displaypfx="inline-" class="mathjax-container">\(\sigma\)</span> 或 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 越大，区间越宽。</li>
<li>样本量越大，即 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 越大，标准误越小，区间越窄。</li>
<li>置信度越高，例如从 95% 提高到 99%，对应分位数更大，区间也会变宽。</li>
</ul>
<p>因此，置信区间是在样本有限时对参数估计可靠性的定量表达；置信度则是这套区间构造方法愿意给出的覆盖承诺。两者必须一起理解：只有区间没有置信度，范围没有统计保证；只有置信度没有区间，覆盖承诺也无法落到具体参数估计上。</p>
<div class="blog_h2"><span class="graybg">随机过程</span></div>
<p>前面讨论的随机变量（Random Variable）通常只对应一次不确定试验，例如抛一次硬币、测一次身高、抽取一个样本标签。随机过程（Stochastic Process）则讨论<span style="background-color: #c0c0c0;">一串彼此有关、按时间或空间索引起来的随机变量</span>。它适合描述会随时间演化的不确定系统，例如天气变化、股价波动、队列长度、用户行为序列、传感器读数，以及自然语言中的 token 序列。</p>
<p>形式上，随机过程可记为：</p>
<span displaypfx="" class="mathjax-container">\[\{X_t\}_{t\in T}\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(T\)</span> 是索引集合（Index Set），常表示时间； <span displaypfx="inline-" class="mathjax-container">\(X_t\)</span> 是时刻 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 的随机变量。若 <span displaypfx="inline-" class="mathjax-container">\(T=\{0,1,2,\dots\}\)</span>，过程是离散时间（Discrete-time）的；若 <span displaypfx="inline-" class="mathjax-container">\(T=[0,\infty)\)</span>，过程则是连续时间（Continuous-time）的。每个 <span displaypfx="inline-" class="mathjax-container">\(X_t\)</span> 都有自己的分布，但更重要的是这些随机变量之间的联合分布（Joint Distribution）与依赖结构，因为随机过程关心的不是“某一时刻单独会怎样”，而是“整个序列怎样一起变化”。</p>
<p>从结果形态看，一次随机过程的实现不再是一个点，而是一条轨迹（Trajectory）或样本路径（Sample Path）。例如，设 <span displaypfx="inline-" class="mathjax-container">\(X_t\)</span> 表示第 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 天的气温，那么一次观测得到的是一整段温度序列 <span displaypfx="inline-" class="mathjax-container">\((X_1,X_2,\dots,X_T)\)</span>；若 <span displaypfx="inline-" class="mathjax-container">\(X_t\)</span> 表示语言模型在位置 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 生成的 token，那么一次实现就是一整句文本。</p>
<p>随机过程之所以在机器学习里重要，是因为很多任务天生就是时序问题。时间序列预测关心未来值如何演化，隐马尔可夫模型（Hidden Markov Model, HMM）关心隐藏状态如何随时间转移，强化学习关心状态—动作—奖励序列怎样展开，自回归语言模型则是在建模 token 序列的联合概率。因此，随机过程可以看作“把单个随机变量扩展到整条时间轴上的概率建模语言”。</p>
<div class="blog_h3"><span class="graybg">马尔可夫性与马尔可夫过程</span></div>
<p>马尔可夫性（Markov Property）讨论的是随机过程中的“记忆如何被压缩”。它刻画这样一种情形：<span style="background-color: #c0c0c0;">如果当前状态已经把与未来演化有关的信息概括完整，那么预测下一步时就不再需要显式回看更久的历史</span>。在这种表示下，过去对未来的影响已经通过当前状态被浓缩进来了。</p>
<p>设随机过程为 <span displaypfx="inline-" class="mathjax-container">\(\{X_t\}_{t=0}^{\infty}\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(X_t\)</span> 表示时刻 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 的随机状态。若它满足一阶马尔可夫性，则有：</p>
<span displaypfx="" class="mathjax-container">\[P(X_{t+1}\mid X_t,X_{t-1},\dots,X_0)=P(X_{t+1}\mid X_t)\]</span>
<p>这条式子的含义是：在已经知道当前状态 <span displaypfx="inline-" class="mathjax-container">\(X_t\)</span> 的前提下，再额外知道更早的历史 <span displaypfx="inline-" class="mathjax-container">\(X_{t-1},\dots,X_0\)</span>，不会改变对下一时刻 <span displaypfx="inline-" class="mathjax-container">\(X_{t+1}\)</span> 的条件分布判断。左边是“条件在完整历史上的下一步分布”，右边是“条件在当前状态上的下一步分布”；两者相等，正是马尔可夫性的定义。</p>
<p>一个直接例子是天气变化。若把每天的天气记作随机变量 <span displaypfx="inline-" class="mathjax-container">\(X_t\)</span>，状态空间为 <span displaypfx="inline-" class="mathjax-container">\(\{\text{晴},\text{阴},\text{雨}\}\)</span>，那么常见建模假设是：明天是否下雨主要由今天的天气决定，而不需要把更早几天的天气逐项保留。此时“当前天气”就扮演了压缩历史信息的状态摘要。若今天是雨天，明天继续下雨的概率可以较大；若今天是晴天，明天下雨的概率可以较小。这种“只通过当前状态决定下一步分布”的结构，就是马尔可夫性。</p>
<p>满足马尔可夫性的随机过程，称为马尔可夫过程（Markov Process）或马尔可夫链（Markov Chain，离散时间、有限或可数状态时的常见名称）。因此，马尔可夫性是一个性质，马尔可夫过程是满足该性质的一类随机过程。若状态空间有限，常把一步转移概率写成转移矩阵（Transition Matrix） <span displaypfx="inline-" class="mathjax-container">\(P\)</span>，其中：</p>
<span displaypfx="" class="mathjax-container">\[P_{ij}=P(X_{t+1}=j\mid X_t=i)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(P_{ij}\)</span> 表示“当前在状态 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 时，下一步转移到状态 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 的概率”。矩阵的每一行对应当前状态固定后的下一步分布，因此每一行元素都非负，且行和为 1。</p>
<p>马尔可夫过程之所以重要，是因为它把一个潜在上非常复杂的时序依赖问题，压缩成了局部转移规律。隐马尔可夫模型（Hidden Markov Model, HMM）、马尔可夫决策过程（Markov Decision Process, MDP）、很多时间序列状态模型，以及强化学习中的环境状态转移，都建立在这一思想之上。它们的差别不在于是否使用马尔可夫性，而在于：状态是否可见、是否存在动作、是否附带奖励，以及是否只关心下一步还是关心长期回报。</p>
<p>马尔可夫性是一种建模假设，不是自然界自动保证的真理。若“当前状态”定义得不充分，历史信息就没有被真正压缩进去，此时过程在这个状态表示下就不具有马尔可夫性。例如，仅用“当前股价”预测明天走势通常不够，因为成交量、市场情绪、宏观事件等信息并未包含在状态中；但若把更完整的市场状态向量一并纳入，马尔可夫近似会更合理。因此，马尔可夫性的关键不只是公式，而是<span style="background-color: #c0c0c0;">当前状态是否足以成为过去对未来影响的充分摘要</span>。</p>
<div class="blog_h1"><span class="graybg">信息论</span></div>
<p>信息论（Information Theory）研究的是：当事件具有不确定性时，信息应该如何被度量、压缩、传输和比较。在机器学习里，它不仅是一门“通信理论”，更是理解损失函数、语言模型、表示学习和分布距离的基础语言。熵（Entropy）回答“分布本身有多不确定”，KL 散度回答“两个分布相差多少”，交叉熵回答“用模型分布描述真实分布要付出多少平均代价”。这三者共同构成后续分类损失、语言模型 NLL 和困惑度（Perplexity）的理论基础。</p>
<div class="blog_h2"><span class="graybg">信息量与编码</span></div>
<p>信息论最基本的对象不是“熵”，而是单个事件的信息量（Information Content）。若事件 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 的概率为 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span>，则它的信息量定义为：</p>
<span displaypfx="" class="mathjax-container">\[I(x)=-\log p(x)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span> 越小，说明事件越罕见、越难提前猜到，因此 <span displaypfx="inline-" class="mathjax-container">\(-\log p(x)\)</span> 越大； <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span> 越大，说明事件越常见、越不令人意外，因此信息量越小。这条定义不是任意拍脑袋得出的，它和最优编码（Optimal Coding）直接对应：概率大的事件应该分配更短的码字，概率小的事件应该分配更长的码字，而最优平均码长与 <span displaypfx="inline-" class="mathjax-container">\(-\log p(x)\)</span> 同阶。</p>
<p>对数的底决定单位。以 <span displaypfx="inline-" class="mathjax-container">\(\log_2\)</span> 为底，单位是比特（Bits）；以自然对数为底，单位是纳特（Nats）。在机器学习推导里常用自然对数，因为它和指数族分布、梯度推导、对数似然更自然地连在一起；在编码与压缩直觉里，bit 往往更直观。</p>
<p>这也解释了为什么语言模型里单个 token 的负对数概率 <span displaypfx="inline-" class="mathjax-container">\(-\log p_\theta(y_t|c_t)\)</span> 会被称为“惊奇（Surprise）”。这里 <span displaypfx="inline-" class="mathjax-container">\(c_t\)</span> 是当前位置之前的上下文， <span displaypfx="inline-" class="mathjax-container">\(y_t\)</span> 是当前位置真实出现的 token。若模型原本认为这个 token 很大概率出现，那么惊奇很小；若模型几乎没预料到它会出现，那么惊奇就很大。于是，单 token 的负对数概率就是单事件的信息量。</p>
<div class="blog_h2"><span class="graybg">熵（Entropy）</span></div>
<p>熵（Entropy）刻画的是一个概率分布整体的不确定性。对离散分布 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>：</p>
<span displaypfx="" class="mathjax-container">\[H(p)=-\sum_i p_i\log p_i\]</span>
<p>这条公式可以直接读成“信息量的期望”。因为单个事件的信息量是 <span displaypfx="inline-" class="mathjax-container">\(-\log p(x)\)</span>，而熵就是对所有事件按真实概率做加权平均后的结果。因此，熵不是针对某一个样本，而是针对整个分布：<span style="background-color: #c0c0c0;">分布越均匀，平均越难预测，熵越大；分布越尖锐，平均越容易预测，熵越小</span>。</p>
<p>例如，若 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 在 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个类别上均匀，即 <span displaypfx="inline-" class="mathjax-container">\(p_i=1/K\)</span>，则有：</p>
<span displaypfx="" class="mathjax-container">\[H(p)=\log K\]</span>
<p>这说明均匀分布的熵达到最大值，因为每个类别都一样可能，系统最难提前猜中。反过来，若某个类别概率接近 1，其余类别概率接近 0，那么熵会接近 0，因为结果几乎确定。</p>
<p>一个最简单的直觉例子是抛硬币。公平硬币的分布是 <span displaypfx="inline-" class="mathjax-container">\(p=(0.5,0.5)\)</span>，熵为 <span displaypfx="inline-" class="mathjax-container">\(-0.5\log_2 0.5-0.5\log_2 0.5=1\)</span> bit；若硬币极度偏向正面，例如 <span displaypfx="inline-" class="mathjax-container">\(p=(0.99,0.01)\)</span>，熵就会显著降低，因为结果基本可预测。也就是说，熵衡量的不是“结果有几个类别”，而是“类别概率结构到底有多分散”。</p>
<p>语言也能帮助建立对熵的直觉。以字符为单位时，汉字集合更大，单个常用字符的平均出现概率往往更低，因此 <span displaypfx="inline-" class="mathjax-container">\(-\log p(x)\)</span> 更大；同时，汉语序列里的下一字符通常也更难直接预测，所以常给人“字符级信息更密、熵更高”的直觉。对比之下，韩语的字符系统更小，序列模式也通常更规则，下一字符更容易预测，因此字符级冗余感更强、熵的直觉更低。</p>
<p>若再做一个极粗略的字符级估算：把常用汉字集合近似看作 <span displaypfx="inline-" class="mathjax-container">\(V=3000\)</span>、把韩文字母集合近似看作 <span displaypfx="inline-" class="mathjax-container">\(V=40\)</span>，并暂时假设“下一字符”在各自词表上均匀分布，则最大熵分别约为 <span displaypfx="inline-" class="mathjax-container">\(\log_2 3000\approx 11.55\)</span> bits 与 <span displaypfx="inline-" class="mathjax-container">\(\log_2 40\approx 5.32\)</span> bits。这对应的就是：词表越大、单符号平均概率越低，单位符号的潜在信息量上界越高。</p>
<div class="blog_h2"><span class="graybg">条件熵（Conditional Entropy）</span></div>
<p>条件熵（Conditional Entropy）衡量的是：在已经知道一个随机变量 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 的前提下，另一个随机变量 <span displaypfx="inline-" class="mathjax-container">\(Y\)</span> 还剩多少不确定性。对离散变量，它定义为：</p>
<span displaypfx="" class="mathjax-container">\[H(Y|X)=-\sum_{x,y}p(x,y)\log p(y|x)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(p(x,y)\)</span> 是联合分布， <span displaypfx="inline-" class="mathjax-container">\(p(y|x)\)</span> 是条件分布。与普通熵 <span displaypfx="inline-" class="mathjax-container">\(H(Y)\)</span> 相比，条件熵把“已知 <span displaypfx="inline-" class="mathjax-container">\(X\)</span>”这一额外信息纳入了计算，因此它描述的是<span style="background-color: #c0c0c0;">看到上下文之后仍然无法消除的平均不确定性</span>。</p>
<p>条件熵满足一个非常重要的链式法则（Chain Rule）：</p>
<span displaypfx="" class="mathjax-container">\[H(X,Y)=H(X)+H(Y|X)\]</span>
<p>这条式子可以直接读成：联合不确定性等于“先确定 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 需要的信息量”加上“知道 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 之后再确定 <span displaypfx="inline-" class="mathjax-container">\(Y\)</span> 还需要的信息量”。如果 <span displaypfx="inline-" class="mathjax-container">\(Y\)</span> 完全由 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 决定，那么 <span displaypfx="inline-" class="mathjax-container">\(H(Y|X)=0\)</span>；如果知道 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 对预测 <span displaypfx="inline-" class="mathjax-container">\(Y\)</span> 毫无帮助，那么 <span displaypfx="inline-" class="mathjax-container">\(H(Y|X)=H(Y)\)</span>。</p>
<p>这条概念在 AI 中非常重要。语言模型本质上就在最小化“给定上下文后的下一个 token 条件不确定性”；监督学习则在尝试让特征 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 尽可能降低标签 <span displaypfx="inline-" class="mathjax-container">\(Y\)</span> 的条件熵。一个好的表示，往往意味着：给定表示之后，目标变量剩余的不确定性更小。</p>
<div class="blog_h2"><span class="graybg">互信息（Mutual Information）</span></div>
<p>互信息（Mutual Information）衡量的是：知道 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 之后，关于 <span displaypfx="inline-" class="mathjax-container">\(Y\)</span> 的不确定性到底减少了多少。它定义为：</p>
<span displaypfx="" class="mathjax-container">\[I(X;Y)=H(Y)-H(Y|X)\]</span>
<p>同一个量还可以写成对称形式：</p>
<span displaypfx="" class="mathjax-container">\[I(X;Y)=H(X)+H(Y)-H(X,Y)\]</span>
<p>以及分布距离形式：</p>
<span displaypfx="" class="mathjax-container">\[I(X;Y)=D_{\mathrm{KL}}\bigl(p(x,y)\,\|\,p(x)p(y)\bigr)\]</span>
<p>这三种写法分别强调了三个角度。第一种写法强调“减少了多少不确定性”；第二种写法强调“联合结构比两个独立分布拼起来少了多少冗余”；第三种写法强调“联合分布与独立分布之间到底偏离了多少”。若 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(Y\)</span> 独立，则 <span displaypfx="inline-" class="mathjax-container">\(p(x,y)=p(x)p(y)\)</span>，互信息为 0；若二者强相关，互信息就大。</p>
<p>互信息在机器学习里有很强的桥梁作用。特征选择希望保留与标签互信息高的特征；表示学习希望得到对目标变量更有信息量的表示；对比学习（Contrastive Learning）很多时候都可以理解为在间接提高表示与语义目标之间的互信息。它把“这个表示到底有没有用”转成了一个可度量的问题。</p>
<div class="blog_h2"><span class="graybg">交叉熵（Cross-Entropy）</span></div>
<p>交叉熵（Cross-Entropy）衡量的是：若真实样本来自分布 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>，但预测或编码时使用分布 <span displaypfx="inline-" class="mathjax-container">\(q\)</span>，平均需要多少信息量。它定义为：</p>
<span displaypfx="" class="mathjax-container">\[H(p,q)=-\sum_i p_i\log q_i\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 是真实分布（True Distribution）或数据分布， <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 是模型分布（Model Distribution）或预测分布。与熵 <span displaypfx="inline-" class="mathjax-container">\(H(p)=-\sum_i p_i\log p_i\)</span> 相比，交叉熵把“对真实分布的自我编码”改成了“用模型分布来编码真实分布”。因此它天然会把模型偏差也算进去。</p>
<p>交叉熵与熵、KL 散度之间满足最关键的一条关系：</p>
<span displaypfx="" class="mathjax-container">\[H(p,q)=H(p)+D_{\mathrm{KL}}(p\|q)\]</span>
<p>这条式子说明：交叉熵等于“真实分布本来就不可避免的信息量”加上“模型偏离真实分布所多付出的代价”。因此，在真实分布 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 固定时，最小化交叉熵就等价于最小化 <span displaypfx="inline-" class="mathjax-container">\(D_{\mathrm{KL}}(p\|q)\)</span>。KL 散度本身的方向性和更深层意义，会在本章最后单独展开。</p>
<p>分类问题里，真实标签通常写成 one-hot 分布。若某个样本的真实类别是 <span displaypfx="inline-" class="mathjax-container">\(c^*\)</span>，则只有 <span displaypfx="inline-" class="mathjax-container">\(p_{c^*}=1\)</span>，其他类别概率都为 0。此时交叉熵立刻化简成：</p>
<span displaypfx="" class="mathjax-container">\[H(p,q)=-\log q_{c^*}\]</span>
<p>这说明分类交叉熵损失（Cross-Entropy Loss）的本质，就是惩罚模型给真实类别分配过低概率。若模型把真实类别概率压到很小，损失会急剧上升；若模型给真实类别高概率，损失就很小。因此，交叉熵本质上就是负对数似然（Negative Log-Likelihood, NLL）在分类任务上的具体展开。</p>
<p>二分类情形下，若真实标签 <span displaypfx="inline-" class="mathjax-container">\(y\in\{0,1\}\)</span>，模型输出正类概率 <span displaypfx="inline-" class="mathjax-container">\(\pi\)</span>，则交叉熵退化为：</p>
<span displaypfx="" class="mathjax-container">\[\ell_{\mathrm{BCE}}(y,\pi)=-y\log \pi-(1-y)\log(1-\pi)\]</span>
<p>多分类情形下，若模型输出 softmax 概率 <span displaypfx="inline-" class="mathjax-container">\(q_c\)</span>，则损失就是前面的 one-hot 交叉熵。于是 BCE、Categorical Cross-Entropy 和语言模型里的 token-level NLL，本质上都属于同一条信息论主线。</p>
<p>若继续使用前面的例子，真实分布 <span displaypfx="inline-" class="mathjax-container">\(p=(0.5,0.5)\)</span>，模型分布 <span displaypfx="inline-" class="mathjax-container">\(q=(0.9,0.1)\)</span>，则交叉熵为</p>
<span displaypfx="" class="mathjax-container">\[H(p,q)=-0.5\log 0.9-0.5\log 0.1\approx 1.204\ (\text{nats})\]</span>
<p>而真实熵 <span displaypfx="inline-" class="mathjax-container">\(H(p)\approx 0.693\)</span>，两者差值正好就是前面算出的 <span displaypfx="inline-" class="mathjax-container">\(D_{\mathrm{KL}}(p\|q)\approx 0.511\)</span>。图像化地看，就是交叉熵把“分布自身的不确定性”和“模型额外犯的错”加在了一起：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2026/03/plot_entropy_cross.png"><img class="alignnone size-full wp-image-40943" src="https://blog.gmem.cc/wp-content/uploads/2026/03/plot_entropy_cross.png" alt="plot_entropy_cross" width="100%" /></a></p>
<div class="blog_h2"><span class="graybg">困惑度（Perplexity）</span></div>
<p>困惑度（Perplexity, PPL）主要用于语言模型评估。它把平均交叉熵或平均 NLL 指数化，得到一个更直观的尺度，可以理解为模型在每一步预测时面对的“有效分支数（Effective Branching Factor）”。对长度为 <span displaypfx="inline-" class="mathjax-container">\(N\)</span> 的 token 序列，若模型给出的条件概率是 <span displaypfx="inline-" class="mathjax-container">\(q(x_t|x_{&lt;t})\)</span>，则平均 NLL 为：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{NLL}=-\frac{1}{N}\sum_{t=1}^{N}\log q(x_t|x_{&lt;t})\]</span>
<p>若使用自然对数，则困惑度定义为：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{PPL}=\exp(\mathrm{NLL})\]</span>
<p>因此，平均 NLL 越小，说明模型越能给真实 token 高概率，困惑度也越低；平均 NLL 越大，说明模型越“困惑”，困惑度就越高。若一个模型在某个位置总像是在若干个几乎等可能的候选之间摇摆，那么它的困惑度就高；若它常能把概率质量集中在极少数正确候选上，困惑度就低。</p>
<p>困惑度与熵的关系也很直接：若分布更均匀、不确定性更高，则平均惊奇更大，熵更高，困惑度也更高。对均匀分布而言，若有 <span displaypfx="inline-" class="mathjax-container">\(K\)</span> 个等可能结果，则困惑度恰好等于 <span displaypfx="inline-" class="mathjax-container">\(K\)</span>。这也是“有效分支数”这个直觉的来源。</p>
<p>不过，困惑度只能在相同 tokenization、相近评测语料和相同单位下比较。换了分词器、词表粒度或语料分布，PPL 的绝对值就会发生系统性变化。因此，困惑度非常适合同一模型家族内部比较，却不适合跨完全不同 tokenizer 和数据设定做简单横比。</p>
<div class="blog_h2"><span class="graybg">KL 散度（Kullback–Leibler Divergence）</span></div>
<div class="blog_h3"><span class="graybg">定义</span></div>
<p>KL 散度衡量两个概率分布之间的偏离程度。对离散分布 <span displaypfx="inline-" class="mathjax-container">\(p,q\)</span>，定义为：</p>
<span displaypfx="" class="mathjax-container">\[D_{\mathrm{KL}}(p\|q)=\sum_i p_i\log\frac{p_i}{q_i}=\mathbb{E}_{x\sim p}\!\left[\log\frac{p(x)}{q(x)}\right]\]</span>
<p>这条公式可以逐块来读。这里 <span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}_{x\sim p}[\cdot]\)</span> 表示“对 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 按照分布 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 取期望”，也就是用 <span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span> 作为权重，对每个可能事件的括号内数值做加权平均。括号里的 <span displaypfx="inline-" class="mathjax-container">\(\log \frac{p(x)}{q(x)}\)</span> 表示：对同一个事件 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，真实分布 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 给它的概率，与模型分布 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 给它的概率相比，相差了多少个对数尺度。若某个事件在 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 下概率高、但在 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 下概率低，那么这一项就为正，而且会把 KL 散度往上推；若某个事件在 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 下被高估，这一项可能变成负值，但最后整体仍会在按 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 加权平均后保持非负。</p>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 通常代表真实分布、目标分布或参考分布， <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 通常代表模型分布、近似分布或当前策略分布。把它按编码视角理解，KL 散度表示：如果样本真实来自 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>，但描述或编码时使用了按 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 设计的规则，那么平均会额外多付出多少码长。</p>
<p>这一定义非常重要，因为它把“模型到底离目标分布还有多远”变成了一个可以直接优化、直接推导的数学量。后面最大似然、交叉熵训练、变分推断、策略正则化和蒸馏，几乎都能在某个层面写成 KL 散度最小化。</p>
<div class="blog_h3"><span class="graybg">三条关键性质</span></div>
<p>第一条性质是不对称性：</p>
<span displaypfx="" class="mathjax-container">\[D_{\mathrm{KL}}(p\|q)\ne D_{\mathrm{KL}}(q\|p)\]</span>
<p>因此，KL 不是通常意义上的距离度量（Metric）。它没有“从 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 到 <span displaypfx="inline-" class="mathjax-container">\(q\)</span>”与“从 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 到 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>”完全等价的性质。方向一旦交换，优化行为就可能完全不同，这正是后面正向 KL 与反向 KL 分歧的根源。</p>
<p>第二条性质是绝对非负性：</p>
<span displaypfx="" class="mathjax-container">\[D_{\mathrm{KL}}(p\|q)\ge 0\]</span>
<p>而且只有当 <span displaypfx="inline-" class="mathjax-container">\(p=q\)</span> 时才取 0。这意味着 KL 散度确实可以被理解为“分布差异的代价”，并且这个代价不会出现负数。很多机器学习目标之所以可以被解释为“逼近目标分布”，正是因为它们最后都能写成某个非负 KL 项再加一个与参数无关的常数。</p>
<p>第三条性质与支持集（Support）直接相关。若存在某个事件 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 使得 <span displaypfx="inline-" class="mathjax-container">\(p(x)&gt;0\)</span>，但 <span displaypfx="inline-" class="mathjax-container">\(q(x)=0\)</span>，则</p>
<span displaypfx="" class="mathjax-container">\[D_{\mathrm{KL}}(p\|q)=+\infty\]</span>
<p>这意味着：只要真实分布认为某种情况会发生，而模型却把它的概率直接压成 0，正向 KL 的惩罚就会发散。这个性质会强烈约束模型不要遗漏真实分布支持集中的任何区域。也正因为如此，正向 KL 往往带有明显的“覆盖全部真实模式”的倾向。</p>
<div class="blog_h3"><span class="graybg">正向 KL 与反向 KL</span></div>
<p>由于 KL 不对称，优化 <span displaypfx="inline-" class="mathjax-container">\(D_{\mathrm{KL}}(p\|q)\)</span> 与优化 <span displaypfx="inline-" class="mathjax-container">\(D_{\mathrm{KL}}(q\|p)\)</span> 往往会导向两种完全不同的模型行为。通常把前者称为正向 KL（Forward KL），把后者称为反向 KL（Reverse KL）。</p>
<p>正向 KL：</p>
<span displaypfx="" class="mathjax-container">\[\min_q D_{\mathrm{KL}}(p\|q)\]</span>
<p>当真实分布 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 是多峰（Multi-modal）而模型 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 表达能力有限时，正向 KL 常表现出较强的<span style="background-color: #c0c0c0;">模式覆盖（Mode-covering）</span>倾向。因为若某个真实峰被 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 完全忽略，且该峰在 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 下概率不小，那么惩罚会非常大。于是模型更倾向于把多个真实峰都“罩进去”，哪怕这会让分布变宽、变平、甚至在峰与峰之间产生额外质量。</p>
<p>反向 KL：</p>
<span displaypfx="" class="mathjax-container">\[\min_q D_{\mathrm{KL}}(q\|p)\]</span>
<p>则更容易表现出<span style="background-color: #c0c0c0;">模式寻优（Mode-seeking）</span>倾向。因为现在期望是对 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 取的，模型只会为自己已经放置概率质量的区域付出代价。若它把质量集中到 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 里一个最高的峰附近，就可以避免在低概率区域“乱撒概率”；至于其他较小的峰，反向 KL 允许它忽略。于是结果通常更尖、更保守，也更容易集中在局部最优模式上。</p>
<p>这种差异可以用“求均值”与“求众数”的直觉来帮助理解。正向 KL 更像试图用一个近似分布去覆盖整个真实分布的平均形状；反向 KL 更像试图锁定真实分布里最值得相信的高概率模式。这个类比只是一种直觉，不是严格定理；真实行为还会受到模型族、参数化方式和优化过程的影响，但它非常适合帮助理解两种 KL 的整体倾向。</p>
<p>一个非常典型的具象化场景是：真实分布 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 由两个彼此分离的高斯峰组成，而模型 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 只能用一个单峰高斯去近似它。此时，若优化正向 KL，模型通常会把方差拉大、把两个峰尽量都罩住，哪怕峰与峰之间那段原本概率不高的区域也被分到一些质量；若优化反向 KL，模型则更容易直接贴到其中一个峰上，把另一个峰干脆忽略掉。这个双峰例子之所以经典，正是因为它把“模式覆盖”和“模式寻优”的差异几乎以最直观的几何方式暴露了出来。</p>
<div><a href="https://blog.gmem.cc/wp-content/uploads/2026/04/kl-forward-reverse-modes.png"><img class="alignnone size-full wp-image-42317" src="https://blog.gmem.cc/wp-content/uploads/2026/04/kl-forward-reverse-modes.png" alt="kl-forward-reverse-modes" width="1920" height="1178" /></a></div>
<div class="blog_h3"><span class="graybg">监督学习为什么经常落到正向 KL</span></div>
<p>监督学习最典型的设定是：训练样本来自某个真实数据分布 <span displaypfx="inline-" class="mathjax-container">\(p_{\text{data}}(x,y)\)</span>，模型则定义一个条件分布 <span displaypfx="inline-" class="mathjax-container">\(q_\theta(y|x)\)</span>。训练的目标，是让模型分布尽可能贴近真实条件分布。若从总体风险角度写，最常见的最大似然或交叉熵训练可以写成：</p>
<span displaypfx="" class="mathjax-container">\[\min_\theta \mathbb{E}_{(x,y)\sim p_{\text{data}}}\big[-\log q_\theta(y|x)\big]\]</span>
<p>这正是经验版交叉熵。进一步展开后，它等价于最小化：</p>
<span displaypfx="" class="mathjax-container">\[\mathbb{E}_{x\sim p_{\text{data}}}\big[D_{\mathrm{KL}}(p_{\text{data}}(y|x)\|q_\theta(y|x))\big]\]</span>
<p>再加上与参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 无关的常数项。也就是说，在监督学习中，模型通常是在用自己的预测分布去逼近真实标签条件分布，而这个逼近方向正是<span style="background-color: #c0c0c0;">正向 KL</span>。</p>
<p>这也是为什么交叉熵训练常呈现“宁可多覆盖真实模式，也不愿漏掉真实可能性”的行为。分类模型若把真实类别概率压得过低，损失会立刻升高；密度估计模型若把真实样本区域概率分配得太小，代价同样会很大。</p>
<p>均方误差（Mean Squared Error, MSE）也能放进这条链里，但需要补上分布假设：当回归任务假设 <span displaypfx="inline-" class="mathjax-container">\(y|x\)</span> 服从均值为 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)\)</span>、方差固定的高斯分布时，最小化 MSE 等价于最大化高斯似然；从分布角度看，它同样是在做一种正向 KL 逼近。于是，交叉熵、BCE 和 MSE 并不是彼此无关的损失，而是不同观测分布假设下的同一条最大似然主线。</p>
<div class="blog_h3"><span class="graybg">强化学习为什么常出现反向 KL</span></div>
<p>强化学习没有现成的“最优轨迹数据集”可直接监督，而是通过奖励函数（Reward Function）来定义哪些行为更值得偏好。若从最大熵强化学习（Maximum Entropy Reinforcement Learning）或控制即推断（Control as Inference）的视角写，最优轨迹分布可以表示为：</p>
<span displaypfx="" class="mathjax-container">\[p^*(\tau)\propto \exp\!\left(\frac{1}{\alpha}R(\tau)\right)\]</span>
<p>这里 <span displaypfx="inline-" class="mathjax-container">\(\tau\)</span> 是轨迹（Trajectory），表示从初始状态开始一路经历的状态、动作与转移序列； <span displaypfx="inline-" class="mathjax-container">\(R(\tau)\)</span> 是这条轨迹的总回报（Return）； <span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span> 是温度或熵正则系数，用来控制“更贪心地追高奖励”和“保留更多探索随机性”之间的权衡。式子里的 <span displaypfx="inline-" class="mathjax-container">\(p^*(\tau)\)</span> 不是任意轨迹分布，而是一个对高奖励轨迹赋予更高权重的目标分布。若某条轨迹奖励越高， <span displaypfx="inline-" class="mathjax-container">\(\exp(R(\tau)/\alpha)\)</span> 就越大，它在 <span displaypfx="inline-" class="mathjax-container">\(p^*(\tau)\)</span> 里的相对权重也越高。</p>
<p>当前策略记为 <span displaypfx="inline-" class="mathjax-container">\(\pi\)</span>。它表示智能体在给定状态 <span displaypfx="inline-" class="mathjax-container">\(s\)</span> 时采取动作 <span displaypfx="inline-" class="mathjax-container">\(a\)</span> 的条件分布，即 <span displaypfx="inline-" class="mathjax-container">\(\pi(a|s)\)</span>。一旦策略 <span displaypfx="inline-" class="mathjax-container">\(\pi\)</span> 固定，再结合环境的状态转移概率，整套交互过程就会诱导出一个“轨迹是怎样被生成出来”的概率分布，这个分布就记为 <span displaypfx="inline-" class="mathjax-container">\(q_\pi(\tau)\)</span>。因此 <span displaypfx="inline-" class="mathjax-container">\(q_\pi(\tau)\)</span> 的意思不是“另一个独立定义的 q”，而是<span style="background-color: #c0c0c0;">由策略 <span displaypfx="inline-" class="mathjax-container">\(\pi\)</span> 所诱导出来的轨迹分布</span>。</p>
<p>这时很多强化学习目标都可以写成：</p>
<span displaypfx="" class="mathjax-container">\[\min_\pi D_{\mathrm{KL}}\bigl(q_\pi(\tau)\|p^*(\tau)\bigr)\]</span>
<p>这里的关键点是：<span style="background-color: #c0c0c0;">谁在逼近谁，不由 KL 公式左右两侧的位置决定，而由谁是优化变量决定</span>。在监督学习里，常把真实分布记作 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>、模型分布记作 <span displaypfx="inline-" class="mathjax-container">\(q\)</span>，于是常见写法是 <span displaypfx="inline-" class="mathjax-container">\(\min_\theta D_{\mathrm{KL}}(p\|q_\theta)\)</span>；但这只是一个常见写法，不是必须遵守的“左边一定是真实、右边一定是模型”的规则。只要被调的是 <span displaypfx="inline-" class="mathjax-container">\(q_\theta\)</span>，那就是让模型分布去逼近目标分布 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>。同理，这里被调的是策略 <span displaypfx="inline-" class="mathjax-container">\(\pi\)</span>，因此真正会随训练改变的是 <span displaypfx="inline-" class="mathjax-container">\(q_\pi(\tau)\)</span>；而 <span displaypfx="inline-" class="mathjax-container">\(p^*(\tau)\)</span> 是由奖励定义出的目标分布。于是这个目标的含义仍然是：<span style="background-color: #c0c0c0;">通过调整策略，让 <span displaypfx="inline-" class="mathjax-container">\(q_\pi(\tau)\)</span> 靠近 <span displaypfx="inline-" class="mathjax-container">\(p^*(\tau)\)</span></span>。</p>
<p>式子写成 <span displaypfx="inline-" class="mathjax-container">\(D_{\mathrm{KL}}(q_\pi(\tau)\|p^*(\tau))\)</span>，表达的是<span style="background-color: #c0c0c0;">反向 KL 形式</span>。它与前面的正向 KL 在“谁是目标分布”这件事上并没有变化，变化的是<span style="background-color: #c0c0c0;">偏差如何被惩罚</span>。由于期望是对 <span displaypfx="inline-" class="mathjax-container">\(q_\pi(\tau)\)</span> 取的，策略主要会为自己当前已经赋予概率质量的轨迹负责；那些自己几乎不去的区域，即使在 <span displaypfx="inline-" class="mathjax-container">\(p^*(\tau)\)</span> 里还有一定质量，也不会像正向 KL 那样受到强烈惩罚。结果就是策略更容易把概率质量集中到少数高奖励轨迹上，而不是尽量覆盖所有还不错的行为。这正是反向 KL 常见的 mode-seeking 或“尖化”倾向。</p>
<p>这也解释了为什么很多强化学习或对齐过程具有明显的“尖化”倾向。只要某些动作或轨迹能显著提高回报，策略就会更愿意向这些高价值模式集中；低奖励区域即使仍然可能发生，也会被逐渐压低概率。这个行为与前面正向 KL 的“模式覆盖”倾向形成鲜明对照。</p>
<p>不过，这里必须保留一个边界：<span style="background-color: #c0c0c0;">并不是所有强化学习算法都可以无条件、逐字逐句地等同于“最小化反向 KL”</span>。更准确的说法是：在最大熵强化学习、策略正则化 RL、RLHF / DPO 一类现代对齐与偏好优化框架中，反向 KL 视角尤其自然、尤其有解释力；而对更传统的值函数方法、时序差分方法和某些 actor-critic 变体，这个视角仍然有帮助，但不一定就是最直接的原始定义。</p>
<div class="blog_h3"><span class="graybg">监督学习与强化学习的统一视角</span></div>
<p>从 KL 散度视角看，监督学习与强化学习都在做同一件事：让人工构造的模型分布 <span displaypfx="inline-" class="mathjax-container">\(q\)</span> 去逼近某个目标分布 <span displaypfx="inline-" class="mathjax-container">\(p\)</span>。差别不在于“一个学分布，一个不学分布”，而在于<span style="background-color: #c0c0c0;">目标分布如何定义、优化方向如何选、以及我们能否直接从目标分布采样</span>。</p>
<p>在监督学习里， <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 更接近真实数据分布或真实条件标签分布，样本可以直接观测，因此模型常落到正向 KL 或其等价目标，例如交叉熵、负对数似然和在特定噪声假设下的平方误差。模型为了不漏掉真实支持集中的区域，天然会更偏向全面覆盖。</p>
<p>在强化学习里， <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 更接近由奖励函数诱导出的最优行为分布，通常无法直接拿到完整样本，只能通过环境交互和奖励反馈间接逼近。因此很多现代 RL / 对齐目标更自然地落到反向 KL 或带 KL 正则的变体上。模型为了把概率质量集中到高回报区域，天然会更偏向高峰聚焦。</p>
<p>这一统一视角的真正价值，在于它把许多表面不同的算法放回同一个底层问题：<span style="background-color: #c0c0c0;">模型分布如何在高维空间中逼近目标分布</span>。一旦把问题写成分布逼近，很多设计选择就有了统一解释：为什么有些训练目标鼓励覆盖全部模式，为什么有些目标鼓励策略更尖锐，为什么 KL 正则能稳定训练，为什么交叉熵、蒸馏、变分推断和策略约束会反复出现。KL 散度之所以重要，正是因为它把这些分散的现象压缩到了同一条数学主线上。</p>
<div class="blog_h1"><span class="graybg">附录</span></div>
<div class="blog_h2"><span class="graybg">数学符号速查</span></div>
<p>这一表按整篇出现频率和理解价值整理最常见的数学符号。需要特别注意：<span style="background-color: #c0c0c0;">同一个符号在不同小节中可能复用</span>，例如 <span displaypfx="inline-" class="mathjax-container">\(p\)</span> 可以表示概率、分布，也可以出现在最优值或目标分布的记号里；因此阅读时必须结合上下文判断。下面的说明以本篇最常见、最核心的含义为主。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center; width: 20%;">符号</td>
<td style="text-align: center;">说明</td>
<td style="text-align: center;">示例</td>
</tr>
</thead>
<tbody>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}\)</span></td>
<td>读作 real numbers /ˈrɪəl ˈnʌmbərz/；表示实数集合。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(x\in\mathbb{R}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^n\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbb{R}^{m\times n}\)</span></td>
<td>读作 R to the n /ɑːr tə ðiː ɛn/、real m by n /ˈrɪəl ɛm baɪ ɛn/；分别表示 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 维实向量空间和 <span displaypfx="inline-" class="mathjax-container">\(m\times n\)</span> 的实矩阵空间。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\in\mathbb{R}^n\)</span>；<span displaypfx="inline-" class="mathjax-container">\(A\in\mathbb{R}^{m\times n}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{N}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbb{Z}\)</span></td>
<td>读作 natural numbers /ˈnætʃərəl ˈnʌmbərz/、integers /ˈɪntɪdʒərz/；分别表示自然数集合和整数集合。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(n\in\mathbb{N}\)</span>；<span displaypfx="inline-" class="mathjax-container">\(k\in\mathbb{Z}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{Q}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\mathbb{C}\)</span></td>
<td>读作 rational numbers /ˈræʃənəl ˈnʌmbərz/、complex numbers /ˈkɒmplɛks ˈnʌmbərz/；分别表示有理数集合和复数集合。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{Q}\subset\mathbb{R}\subset\mathbb{C}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(a,b,c\)</span></td>
<td>读作 a /eɪ/、b /biː/、c /siː/；常表示常数、系数、标量或方程参数。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(ax+b=0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(x\)</span></td>
<td>读作 ex /ɛks/；常表示自变量、输入、样本点，或随机变量的某个取值。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(f(x)=x^2\)</span>；<span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(y\)</span></td>
<td>读作 wye /waɪ/；常表示输出、标签、因变量或目标值。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(y=f(x)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(q_\theta(y|x)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(z\)</span></td>
<td>读作 zee /ziː/；常表示中间变量、复合表达式中的辅助量，或模型未归一化输出。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(z=Wx+b\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(n,m,N\)</span></td>
<td>读作 en /ɛn/、em /ɛm/、capital N /ˈkæpɪtəl ɛn/；常表示维度、样本数、类别数、矩阵大小或序列长度。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{1}{N}\sum_{i=1}^{N}x_i\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(i,j,k\)</span></td>
<td>读作 eye /aɪ/、jay /dʒeɪ/、kay /keɪ/；常作求和、矩阵、序列或类别索引。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(A_{ij}\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\sum_{i=1}^{n}x_i\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(t\)</span></td>
<td>读作 tee /tiː/；常表示时间步、位置、迭代轮次或序列下标。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(X_t\)</span>；<span displaypfx="inline-" class="mathjax-container">\(x_t\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(h\)</span></td>
<td>读作 aitch /eɪtʃ/；常表示极限中的微小增量，或构造性辅助变量。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\lim_{h\to 0}\frac{f(x+h)-f(x)}{h}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(f,g,h\)</span></td>
<td>读作 ef /ɛf/、gee /dʒiː/、aitch /eɪtʃ/；常表示函数。 <span displaypfx="inline-" class="mathjax-container">\(f\)</span> 往往是目标函数， <span displaypfx="inline-" class="mathjax-container">\(g\)</span> 常表示约束或辅助函数。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(y=f(x)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(g(x)\le 0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\varphi\)</span></td>
<td>读作 varphi /ˈvɑːrfaɪ/；常表示一般性标量函数，尤其用于凸分析、Jensen 不等式和抽象函数构造。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\varphi(\mathbb{E}[X])\le \mathbb{E}[\varphi(X)]\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbf{x},\mathbf{y},\mathbf{w},\mathbf{b}\)</span></td>
<td>读作 bold x /boʊld ɛks/ 等；黑体通常表示向量。 <span displaypfx="inline-" class="mathjax-container">\(\mathbf{w}\)</span> 常作参数向量， <span displaypfx="inline-" class="mathjax-container">\(\mathbf{b}\)</span> 常作偏置向量。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\hat y=\mathbf{w}^\top\mathbf{x}+b\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\boldsymbol{\theta}\)</span></td>
<td>读作 bold theta /boʊld ˈθeɪtə/；常表示整组模型参数或一个高维参数向量。相比单个标量参数 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span>，黑体/粗体版本强调“这是一个参数集合”。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\nabla_{\boldsymbol{\theta}}\mathcal{L}(\boldsymbol{\theta})\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(A,B,C\)</span></td>
<td>读作 capital A /ˈkæpɪtəl eɪ/ 等；大写拉丁字母常表示矩阵、集合或事件。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(A\mathbf{x}=\mathbf{b}\)</span>；<span displaypfx="inline-" class="mathjax-container">\(P(A|B)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(P(A)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(P(A|B)\)</span></td>
<td>读作 probability of A /ˌprɒbəˈbɪləti əv eɪ/、probability of A given B /ˌprɒbəˈbɪləti əv eɪ ˈɡɪvən biː/；分别表示事件 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的概率，以及在 <span displaypfx="inline-" class="mathjax-container">\(B\)</span> 已发生条件下 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的条件概率。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(P(A|B)=\frac{P(A\cap B)}{P(B)}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(A\cap B\)</span>、<span displaypfx="inline-" class="mathjax-container">\(A\cup B\)</span>、<span displaypfx="inline-" class="mathjax-container">\(A^c\)</span></td>
<td>读作 A cap B /eɪ kæp biː/、A cup B /eɪ kʌp biː/、A complement /eɪ ˈkɒmplɪmənt/；分别表示交集、并集和补集。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(P(A^c)=1-P(A)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(P(A\cup B)=P(A)+P(B)-P(A\cap B)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\subset\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\subseteq\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\notin\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\setminus\)</span></td>
<td>读作 proper subset /ˈprɒpər ˈsʌbsɛt/、subset or equal to /ˈsʌbsɛt ɔːr ˈiːkwəl tuː/、not in /nɒt ɪn/、set minus /sɛt ˈmaɪnəs/；分别表示真子集、子集、不属于和集合差。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{N}\subset\mathbb{Z}\)</span>；<span displaypfx="inline-" class="mathjax-container">\(A\subseteq B\)</span>；<span displaypfx="inline-" class="mathjax-container">\(x\notin B\)</span>；<span displaypfx="inline-" class="mathjax-container">\(A\setminus B\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(X,Y\)</span></td>
<td>读作 capital X /ˈkæpɪtəl ɛks/、capital Y /ˈkæpɪtəl waɪ/；常表示随机变量、随机向量或数据矩阵。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(P(X=x)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(I(X;Y)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(X_t\)</span></td>
<td>读作 X sub t /ɛks sʌb tiː/；表示时刻 <span displaypfx="inline-" class="mathjax-container">\(t\)</span> 的状态或随机变量取值。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(P(X_{t+1}|X_t)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(x_t\)</span>、<span displaypfx="inline-" class="mathjax-container">\(x^2\)</span>、<span displaypfx="inline-" class="mathjax-container">\(x^{(i)}\)</span></td>
<td>读作 x sub t /ɛks sʌb tiː/、x squared /ɛks skwerd/、x superscript i /ɛks ˈsuːpərskrɪpt aɪ/；下标 <span displaypfx="inline-" class="mathjax-container">\(_{}\)</span> 通常表示索引、时间步、类别或参数归属，上标 <span displaypfx="inline-" class="mathjax-container">\(^{}\)</span> 既可表示幂次，也可表示“第几个样本”“最优解”“逆”“转置”等附加标记，因此必须结合上下文判断。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(x_t\)</span>；<span displaypfx="inline-" class="mathjax-container">\(x^2\)</span>；<span displaypfx="inline-" class="mathjax-container">\(x^{(i)}\)</span>；<span displaypfx="inline-" class="mathjax-container">\(p^*(\tau)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(A^{-1}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(p,q\)</span></td>
<td>读作 pee /piː/、cue /kjuː/；在概率与信息论中常表示分布、概率质量或模型分布。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(D_{\mathrm{KL}}(p\|q)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(p_i,q_i\)</span></td>
<td>读作 p sub i /piː sʌb aɪ/、q sub i /kjuː sʌb aɪ/；表示第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个类别或事件的概率。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(H(p)=-\sum_i p_i\log p_i\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(p(x)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(q(x)\)</span></td>
<td>读作 p of x /piː əv ɛks/、q of x /kjuː əv ɛks/；表示事件 <span displaypfx="inline-" class="mathjax-container">\(x\)</span> 在不同分布下的概率或密度。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\log\frac{p(x)}{q(x)}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(p(x,y)\)</span></td>
<td>读作 p of x comma y /piː əv ɛks ˈkɑːmə waɪ/；表示联合分布。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(I(X;Y)=D_{\mathrm{KL}}(p(x,y)\|p(x)p(y))\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(p(y|x)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(q_\theta(y|x)\)</span></td>
<td>读作 p of y given x /piː əv waɪ ˈɡɪvən ɛks/；表示条件分布。带 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 的 <span displaypfx="inline-" class="mathjax-container">\(q_\theta\)</span> 常表示参数化模型分布。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(q_\theta(y|x)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(p^*(\tau)\)</span></td>
<td>读作 p star of tau /piː stɑːr əv taʊ/；常表示目标分布、最优分布或由奖励诱导出的理想轨迹分布。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(p^*(\tau)\propto \exp(R(\tau)/\alpha)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(q_\pi(\tau)\)</span></td>
<td>读作 q sub pi of tau /kjuː sʌb paɪ əv taʊ/；表示由策略 <span displaypfx="inline-" class="mathjax-container">\(\pi\)</span> 诱导出的轨迹分布。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(D_{\mathrm{KL}}(q_\pi(\tau)\|p^*(\tau))\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\theta\)</span></td>
<td>读作 theta /ˈθeɪtə/；常表示模型参数、待估计参数或高维参数向量。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\hat{\theta}\)</span></td>
<td>读作 theta hat /ˈθeɪtə hæt/；表示参数估计值，常是 MLE 或 MAP 的最优解。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\hat{\theta}_{\mathrm{MLE}}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\tilde{x}\)</span></td>
<td>读作 x tilde /ɛks ˈtɪldə/；常表示经过变换、构造出来的辅助变量，或满足特殊条件的候选点，例如严格可行点。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(g_i(\tilde{x})&lt;0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mu\)</span></td>
<td>读作 mu /mjuː/；常表示总体均值、高斯分布中心或期望位置。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(X\sim\mathcal{N}(\mu,\sigma^2)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\sigma\)</span></td>
<td>读作 sigma /ˈsɪɡmə/；常表示标准差、尺度参数或高斯宽度。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sigma=\sqrt{\mathrm{Var}(X)}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span></td>
<td>读作 sigma squared /ˈsɪɡmə skwerd/；表示方差。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(X\sim\mathcal{N}(\mu,\sigma^2)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathcal{N}(\mu,\sigma^2)\)</span></td>
<td>读作 normal of mu sigma squared /ˈnɔːrməl əv mjuː ˈsɪɡmə skwerd/；表示均值为 <span displaypfx="inline-" class="mathjax-container">\(\mu\)</span>、方差为 <span displaypfx="inline-" class="mathjax-container">\(\sigma^2\)</span> 的高斯分布（正态分布）。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(X\sim\mathcal{N}(170,6^2)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\pi\)</span></td>
<td>读作 pi /paɪ/；在概率论里常表示成功概率，在强化学习里常表示策略。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(X\sim\mathrm{Bernoulli}(\pi)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\pi(a|s)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\lambda\)</span></td>
<td>读作 lambda /ˈlæmdə/；在优化里常表示拉格朗日乘子，在 Poisson 分布里常表示事件率参数。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}(x,\lambda)=f(x)+\lambda g(x)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(X\sim\mathrm{Poisson}(\lambda)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\nu\)</span></td>
<td>读作 nu /njuː/；常表示对偶变量、等式约束乘子或辅助参数。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}(x,\lambda,\nu)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\alpha\)</span></td>
<td>读作 alpha /ˈælfə/；常表示系数、温度、熵正则强度或显著性水平。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(p^*(\tau)\propto \exp(R(\tau)/\alpha)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\beta\)</span></td>
<td>读作 beta /ˈbeɪtə/；常表示系数、权重或待估参数。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(y=\beta_0+\beta_1 x\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\eta\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\xi\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\phi\)</span></td>
<td>读作 gamma /ˈɡæmə/、eta /ˈeɪtə/、xi /ksaɪ/、phi /faɪ/；这些希腊字母常用作常数、学习率、求和采样点、角度或中间变量。具体含义取决于上下文。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(H_n=\log n+\gamma+o(1)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\theta\leftarrow\theta-\eta\nabla L(\theta)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(f(\xi_k)\Delta x_k\)</span>；<span displaypfx="inline-" class="mathjax-container">\(e^{i\phi}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\varepsilon\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span></td>
<td>读作 epsilon /ˈɛpsɪlɒn/；常表示很小的正数、误差项、扰动或容差。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(y=f(x)+\varepsilon\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\delta\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\Delta\)</span></td>
<td>读作 delta /ˈdɛltə/；小写常表示极限与连续中的邻域半径，大写常表示有限变化量。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(0&lt;|x-a|&lt;\delta\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\Delta x\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\tau\)</span></td>
<td>读作 tau /taʊ/；常表示轨迹、路径或时间展开后的完整序列。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(R(\tau)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\Omega\)</span></td>
<td>读作 omega /oʊˈmeɪɡə/；常表示样本空间、全集或事件空间。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\omega\in\Omega\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\infty\)</span></td>
<td>读作 infinity /ɪnˈfɪnəti/；表示无穷、极限发散方向，或某个量没有上界。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sum_{n=1}^{\infty}a_n\)</span>；<span displaypfx="inline-" class="mathjax-container">\(D_{\mathrm{KL}}(p\|q)=+\infty\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathcal{D}\)</span></td>
<td>读作 calligraphic D /ˌkælɪˈɡræfɪk diː/；常表示数据分布、训练分布或样本集合。</td>
<td><span displaypfx="inline-" class="mathjax-container">\((x,y)\sim\mathcal{D}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}\)</span></td>
<td>读作 calligraphic L /ˌkælɪˈɡræfɪk ɛl/；常表示损失函数或拉格朗日函数。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathcal{L}(\theta)=-\log q_\theta(y|x)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}\)</span></td>
<td>读作 expectation /ˌɛkspɛkˈteɪʃən/；表示期望，即按某个分布加权后的平均。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbb{E}[X]\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\bar{x}\)</span></td>
<td>读作 x bar /ɛks bɑːr/；常表示样本均值。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\bar{x}=\frac{1}{n}\sum_{i=1}^{n}x_i\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(X)\)</span></td>
<td>读作 variance of X /ˈvɛəriəns əv ɛks/；表示随机变量的方差。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{Var}(X)=\mathbb{E}[(X-\mathbb{E}[X])^2]\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{Cov}(X,Y)\)</span></td>
<td>读作 covariance of X and Y /koʊˈvɛəriəns əv ɛks ænd waɪ/；表示两个随机变量的协方差，用来描述它们是否倾向同向变化。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{Cov}(X,Y)=\mathbb{E}[(X-\mu_X)(Y-\mu_Y)]\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\Sigma\)</span></td>
<td>读作 capital sigma /ˈkæpɪtəl ˈsɪɡmə/；常表示协方差矩阵。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\Sigma=\mathbb{E}[(\mathbf{x}-\mu)(\mathbf{x}-\mu)^\top]\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\Lambda\)</span></td>
<td>读作 capital lambda /ˈkæpɪtəl ˈlæmdə/；在线性代数里常表示由特征值组成的对角矩阵。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(A=Q\Lambda Q^\top\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\sigma(A)\)</span></td>
<td>读作 spectrum of A /ˈspɛktrəm əv eɪ/；表示矩阵 <span displaypfx="inline-" class="mathjax-container">\(A\)</span> 的谱，也就是全部特征值组成的集合。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\rho(A)=\max_{\lambda\in\sigma(A)}|\lambda|\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(|\cdot|\)</span></td>
<td>读作 absolute value /ˈæbsəluːt ˈvæljuː/；表示绝对值或标量大小。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(|x-a|\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mid\)</span>、<span displaypfx="inline-" class="mathjax-container">\(|\)</span></td>
<td>读作 given /ˈɡɪvən/；在概率和条件分布里表示“在……条件下”。正文里 <span displaypfx="inline-" class="mathjax-container">\(P(A|B)\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(P(A\mid B)\)</span> 是同一种读法。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(P(A|B)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(p(y\mid x)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\|\cdot\|_0\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\|\cdot\|_1\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\|\cdot\|_2\)</span></td>
<td>读作 L-zero /ɛl ˈzɪəroʊ/、L-one /ɛl wʌn/、L-two /ɛl tuː/；表示不同范数或准范数。 <span displaypfx="inline-" class="mathjax-container">\(L_0\)</span> 常表示非零元素个数， <span displaypfx="inline-" class="mathjax-container">\(L_1\)</span> 表示绝对值和， <span displaypfx="inline-" class="mathjax-container">\(L_2\)</span> 表示欧氏长度。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{x}\|_2\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\|\mathbf{x}\|_1\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\cdot\)</span></td>
<td>读作 dot /dɒt/；常表示点积或普通乘法。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathbf{x}\cdot\mathbf{y}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{a}{b}\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\sqrt{x}\)</span></td>
<td>读作 a over b /eɪ ˈoʊvər biː/、square root of x /skwer ruːt əv ɛks/；分别表示分式和平方根。 <span displaypfx="inline-" class="mathjax-container">\(\frac{\cdot}{\cdot}\)</span> 在概率、梯度、归一化和注意力缩放里都非常常见， <span displaypfx="inline-" class="mathjax-container">\(\sqrt{\cdot}\)</span> 则常出现在方差、范数和 <span displaypfx="inline-" class="mathjax-container">\(1/\sqrt{d_k}\)</span> 这类缩放因子里。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{\partial f}{\partial x}\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\frac{QK^\top}{\sqrt{d_k}}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\circ\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\odot\)</span></td>
<td>读作 composition /ˌkɒmpəˈzɪʃən/、Hadamard product /ˈhædəmɑːrd ˈprɒdʌkt/；前者常表示函数复合，也可在角度里作度数记号，后者表示逐元素乘法。</td>
<td><span displaypfx="inline-" class="mathjax-container">\((f\circ g)(x)=f(g(x))\)</span>；<span displaypfx="inline-" class="mathjax-container">\(X\odot M\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(^\top\)</span></td>
<td>读作 transpose /trænˈspoʊz/；表示转置。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(A^\top\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(^{-1}\)</span></td>
<td>读作 inverse /ɪnˈvɝːs/；表示逆矩阵、逆元素或倒数。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(A^{-1}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\det\)</span></td>
<td>读作 determinant /dɪˈtɝːmɪnənt/；表示行列式。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\det(A)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{rank}\)</span></td>
<td>读作 rank /ræŋk/；表示矩阵的秩。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{rank}(A)=1\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{tr}\)</span></td>
<td>读作 trace /treɪs/；表示矩阵的迹。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\mathrm{tr}(A)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\nabla\)</span></td>
<td>读作 nabla /ˈnæblə/；表示梯度算子。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\nabla f(\mathbf{x})\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\nabla^2\)</span></td>
<td>读作 nabla squared /ˈnæblə skwerd/；表示二阶导算子，常对应 Hessian 或 Laplacian 语境。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\nabla^2 f(\mathbf{x})\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\partial\)</span></td>
<td>读作 partial /ˈpɑːrʃəl/；偏导符号。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\frac{\partial f}{\partial x}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\sum\)</span></td>
<td>读作 summation /səˈmeɪʃən/ 或 sum /sʌm/；表示求和。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sum_{i=1}^{n}x_i\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(S_n\)</span>、<span displaypfx="inline-" class="mathjax-container">\(H_n\)</span></td>
<td>读作 S sub n /ɛs sʌb ɛn/、H sub n /eɪtʃ sʌb ɛn/；常分别表示前 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 项和与第 <span displaypfx="inline-" class="mathjax-container">\(n\)</span> 个调和数。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(S_n=\sum_{k=1}^{n}a_k\)</span>；<span displaypfx="inline-" class="mathjax-container">\(H_n=\sum_{k=1}^{n}\frac{1}{k}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\prod\)</span></td>
<td>读作 product /ˈprɒdʌkt/；表示连乘。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\prod_{i=1}^{n}a_i\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\times\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\pm\)</span></td>
<td>读作 times /taɪmz/、plus or minus /plʌs ɔːr ˈmaɪnəs/；分别表示乘号，以及“正负两种可能”或围绕中心的对称区间。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(0.99\times 0.01\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\mu\pm 2\sigma\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\int\)</span></td>
<td>读作 integral /ˈɪntɪɡrəl/；表示积分。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\int_a^b f(x)\,dx\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\lim\)</span></td>
<td>读作 limit /ˈlɪmɪt/；表示极限。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\lim_{h\to 0}\frac{f(x+h)-f(x)}{h}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\rho\)</span></td>
<td>读作 rho /roʊ/；常表示半径、谱半径，或某个由极限定义出来的关键标量。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\rho(A)=\max_{\lambda\in\sigma(A)}|\lambda|\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\log\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\ln\)</span></td>
<td>读作 log /lɒɡ/、ell-en /ˌɛlˈɛn/；分别表示对数和自然对数。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(-\log q_{c^*}\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\ln x\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\exp\)</span></td>
<td>读作 exponential /ˌɛkspoʊˈnɛnʃəl/；表示指数函数，等价于以 <span displaypfx="inline-" class="mathjax-container">\(e\)</span> 为底的指数映射。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\exp(R(\tau)/\alpha)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\sin\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\cos\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\tan\)</span></td>
<td>读作 sine /saɪn/、cosine /ˈkoʊsaɪn/、tangent /ˈtændʒənt/；表示三角函数。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\sin x\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\cos x\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\min\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\max\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\arg\min\)</span></td>
<td>读作 minimum /ˈmɪnɪməm/、maximum /ˈmæksɪməm/、arg min /ɑːrɡ mɪn/；分别表示最小值、最大值和使目标最小的自变量。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\hat\theta=\arg\min_\theta \mathcal{L}(\theta)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\mapsto\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\leftarrow\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\leftrightarrow\)</span></td>
<td>读作 maps to /mæps tuː/、is updated to /ɪz ʌpˈdeɪtɪd tuː/、corresponds to each other /ˌkɒrəˈspɒndz tuː iːtʃ ˈʌðər/；分别表示映射到、赋值/更新方向，以及双向对应或互换关系。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(x\mapsto f(x)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\theta\leftarrow\theta-\eta\nabla L(\theta)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(A_{ij}\leftrightarrow A_{ji}\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\ell\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\ell(\cdot,\cdot)\)</span></td>
<td>读作 ell /ɛl/；常表示损失函数，也常出现在 <span displaypfx="inline-" class="mathjax-container">\(\ell_1\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\ell_2\)</span> 这类范数记号里。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\min_{\theta}\frac{1}{m}\sum_{i=1}^{m}\ell(f_\theta(x^{(i)}),y^{(i)})\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\in\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\sim\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\to\)</span></td>
<td>读作 in /ɪn/、distributed as /dɪˈstrɪbjuːtɪd æz/、tends to /tɛndz tuː/；分别表示“属于”“服从某分布”“趋向于”。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(x\in\mathbb{R}\)</span>；<span displaypfx="inline-" class="mathjax-container">\(X\sim\mathcal{N}(\mu,\sigma^2)\)</span>；<span displaypfx="inline-" class="mathjax-container">\(h\to 0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\forall\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\exists\)</span></td>
<td>读作 for all /fɔːr ɔːl/、there exists /ðer ɪɡˈzɪsts/；分别表示“对所有”和“存在至少一个”，常用于定义、极限和约束条件。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(\forall \varepsilon &gt; 0,\ \exists \delta &gt; 0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\Rightarrow\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\Leftrightarrow\)</span></td>
<td>读作 implies /ɪmˈplaɪz/、if and only if /ɪf ænd ˈoʊnli ɪf/；分别表示“推出”和“当且仅当”，后者说明两边是双向等价。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(ab=0\Rightarrow a=0\ \text{或}\ b=0\)</span>；<span displaypfx="inline-" class="mathjax-container">\(x\ge 1\Leftrightarrow 1-x\le 0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\le\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\ge\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\ne\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\approx\)</span></td>
<td>读作 less than or equal to /lɛs ðæn ɔːr ˈiːkwəl tuː/、greater than or equal to /ˈɡreɪtər ðæn ɔːr ˈiːkwəl tuː/、not equal to /nɒt ˈiːkwəl tuː/、approximately /əˈprɒksɪmətli/。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(g(x)\le 0\)</span>；<span displaypfx="inline-" class="mathjax-container">\(x\ne y\)</span>；<span displaypfx="inline-" class="mathjax-container">\(f(x)\approx g(x)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\ll\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\gg\)</span></td>
<td>读作 much less than /mʌtʃ lɛs ðæn/、much greater than /mʌtʃ ˈɡreɪtər ðæn/；表示数量级上的显著小于或显著大于，不只是普通不等号。在模型结构、复杂度和缓存分析里，它常用来表达“一个量相对另一个量小得多”。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(1&lt;n_{\text{kv}}\ll n_q\)</span>；<span displaypfx="inline-" class="mathjax-container">\(d_{\text{model}}\gg 1\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\propto\)</span></td>
<td>读作 proportional to /prəˈpɔːrʃənəl tuː/；表示“正比于”，通常意味着还差一个与当前变量无关的归一化常数或比例常数。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(p^*(\tau)\propto \exp(R(\tau)/\alpha)\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(\succ\)</span>、<span displaypfx="inline-" class="mathjax-container">\(\succeq\)</span></td>
<td>读作 is positive definite /ɪz ˈpɒzətɪv ˈdɛfɪnɪt/、is positive semidefinite /ɪz ˈpɒzətɪv ˌsɛmiaɪˈdɛfɪnɪt/；在线性代数和优化里分别表示正定与半正定矩阵关系。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(A\succ 0\)</span>；<span displaypfx="inline-" class="mathjax-container">\(\Sigma\succeq 0\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(H(p)\)</span>、<span displaypfx="inline-" class="mathjax-container">\(H(Y|X)\)</span></td>
<td>读作 entropy of p /ˈɛntrəpi əv piː/、conditional entropy /kənˈdɪʃənəl ˈɛntrəpi/；表示熵和条件熵。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(H(p)=-\sum_i p_i\log p_i\)</span></td>
</tr>
<tr>
<td><span displaypfx="inline-" class="mathjax-container">\(I(X;Y)\)</span></td>
<td>读作 mutual information /ˈmjuːtʃuəl ˌɪnfərˈmeɪʃən/；表示 <span displaypfx="inline-" class="mathjax-container">\(X\)</span> 与 <span displaypfx="inline-" class="mathjax-container">\(Y\)</span> 共享的信息量。</td>
<td><span displaypfx="inline-" class="mathjax-container">\(I(X;Y)=H(Y)-H(Y|X)\)</span></td>
</tr>
</tbody>
</table>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-1">人工智能理论知识 - 数学基础</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/ai-knowledge-quick-ref-1/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>人工智能理论知识 - 简介</title>
		<link>https://blog.gmem.cc/ai-knowledge-quick-ref-0</link>
		<comments>https://blog.gmem.cc/ai-knowledge-quick-ref-0#comments</comments>
		<pubDate>Wed, 15 Apr 2026 00:38:16 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[AI]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=42203</guid>
		<description><![CDATA[<p>这一篇作为整套 AI 总纲的导论，先不进入公式和具体模型细节，而是回答更根本的问题：什么叫智能，人工智能究竟在试图做什么，机器为什么能从数据中学会某些能力，为什么这个方向在近十几年才真正爆发，以及机器学习、深度学习与大语言模型之间到底是什么关系。后续第 1 篇会进入数学基础，第 2 篇进入机器学习与神经网络，第 3 篇按任务展开自然语言处理、计算机视觉、语音、搜索推荐与时序建模，第 4 篇进入 Transformer 与大模型，第 5 篇进入 RAG、上下文工程与 Agent 系统。 什么是人工智能 人工智能的核心问题 人工智能（Artificial Intelligence, <a class="read-more" href="https://blog.gmem.cc/ai-knowledge-quick-ref-0">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-0">人工智能理论知识 - 简介</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>这一篇作为整套 AI 总纲的导论，先不进入公式和具体模型细节，而是回答更根本的问题：什么叫智能，人工智能究竟在试图做什么，机器为什么能从数据中学会某些能力，为什么这个方向在近十几年才真正爆发，以及机器学习、深度学习与大语言模型之间到底是什么关系。后续第 1 篇会进入数学基础，第 2 篇进入机器学习与神经网络，第 3 篇按任务展开自然语言处理、计算机视觉、语音、搜索推荐与时序建模，第 4 篇进入 Transformer 与大模型，第 5 篇进入 RAG、上下文工程与 Agent 系统。</p>
<div class="blog_h1"><span class="graybg">什么是人工智能</span></div>
<div class="blog_h2"><span class="graybg">人工智能的核心问题</span></div>
<p>人工智能（Artificial Intelligence, AI）讨论的核心，是怎样让机器表现出某种智能行为。这里的智能行为，至少包括感知（Perception）、判断（Decision）、学习（Learning）、推理（Reasoning）与适应（Adaptation）。因此，AI 的目标不止是写一个会执行指令的程序，更是让系统能够在不完全由人工穷举规则的前提下，对复杂环境做出有效反应。</p>
<p>从工程角度看，AI 最常见的外显形式包括图像识别、语音识别、推荐系统、自动驾驶、机器翻译、问答系统、代码生成和多轮智能体。但这些表象背后要解决的是同一个更抽象的问题：<span style="background-color: #c0c0c0;">如何把真实世界中的复杂输入映射为可执行的判断与行动</span>。</p>
<div class="blog_h2"><span class="graybg">从自动化到智能</span></div>
<p>许多“看起来自动”的系统，本质上仍然只是自动化。普通自动化系统更多依赖预先写好的流程和明确规则，例如“若温度超过阈值则启动风扇”“若用户点击按钮则调用接口”。这类系统的行为边界，主要由人类工程师提前定义好。它们可以非常有用，但并不等于具备真正的学习能力。</p>
<p>智能系统的关键差别在于：它不只会执行既定步骤，还能从经验中修正自己的内部表示与决策策略。也就是说，它不仅回答“当前该做什么”，还会通过数据逐步形成“以后遇到类似情况时应该怎样判断”。这种能力一旦出现，系统行为就不再完全等价于人工编写的 if-else 规则树。</p>
<div class="blog_h2"><span class="graybg">AI、机器学习、深度学习与大模型的关系</span></div>
<p>这几个概念在日常讨论里经常被混用，但它们处在不同层级。人工智能（AI）是最大的外层概念，讨论的是“让机器表现出智能行为”这一总目标。机器学习（Machine Learning, ML）是实现 AI 的一大类方法，它强调从数据中学习规律，而不是完全依赖手工规则。深度学习（Deep Learning, DL）又是机器学习中的一个重要分支，核心是通过多层神经网络自动学习表示。大语言模型（LLM）和现代基础模型（Foundation Model）则建立在深度学习之上，是特定时代的代表性形态，而不是与深度学习并列的全新学科。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">概念</td>
<td style="text-align: center;">它回答的问题</td>
<td style="text-align: center;">与其他概念的关系</td>
</tr>
</thead>
<tbody>
<tr>
<td>人工智能（AI）</td>
<td>怎样让机器表现出感知、判断、学习、推理和行动能力</td>
<td>最大外层目标</td>
</tr>
<tr>
<td>机器学习（ML）</td>
<td>怎样让机器从数据而不是纯规则中学习映射关系</td>
<td>AI 的主要实现路线之一</td>
</tr>
<tr>
<td>深度学习（DL）</td>
<td>怎样用多层神经网络自动学习层级表示</td>
<td>ML 的一个重要分支</td>
</tr>
<tr>
<td>大语言模型 / 基础模型</td>
<td>怎样通过大规模预训练得到通用生成与迁移能力</td>
<td>建立在深度学习之上的现代主线</td>
</tr>
</tbody>
</table>
<p>因此，后续学习不应把这些词当成互相替代的流行口号，而应始终记住它们的层级关系：<span style="background-color: #c0c0c0;">AI 是目标，ML 是方法族，DL 是方法族中的核心分支，LLM 是 DL 在特定时代和特定架构下的代表形态</span>。一旦这个层级关系理顺，后面的许多概念就不会显得混乱。</p>
<div class="blog_h2"><span class="graybg">人工智能按任务如何分类</span></div>
<p>除了按“方法”来区分 AI，还可以按“任务”来区分。这个视角同样重要，因为很多名词混乱，根源并不是模型太多，而是没有先看清它到底在解决哪一类问题。若从输入输出关系与系统职责出发，当代 AI 大致可以拆成五类核心任务：感知（Perception）、预测（Prediction）、生成（Generation）、决策（Decision）与交互（Interaction）。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">任务类型</td>
<td style="text-align: center;">核心问题</td>
<td style="text-align: center;">典型输入</td>
<td style="text-align: center;">典型输出</td>
<td style="text-align: center;">典型应用</td>
</tr>
</thead>
<tbody>
<tr>
<td>感知</td>
<td>输入里有什么</td>
<td>图像、语音、视频、传感器信号</td>
<td>类别、边界、标签、结构化属性</td>
<td>图像分类、目标检测、语音识别、人脸识别</td>
</tr>
<tr>
<td>预测</td>
<td>未来会怎样，或某个量是多少</td>
<td>历史序列、用户行为、结构化特征</td>
<td>概率、分数、回归值、风险估计</td>
<td>销量预测、点击率预估、风控评分、故障预警</td>
</tr>
<tr>
<td>生成</td>
<td>在约束下生成什么内容</td>
<td>提示词、上下文、条件信号</td>
<td>文本、图像、音频、视频、代码</td>
<td>写作、对话、文生图、代码生成、语音合成</td>
</tr>
<tr>
<td>决策</td>
<td>下一步该采取什么行动</td>
<td>状态、目标、环境反馈</td>
<td>动作、策略、控制信号</td>
<td>游戏智能体、机器人控制、自动驾驶决策、调度优化</td>
</tr>
<tr>
<td>交互</td>
<td>如何在多轮过程中持续理解与响应人或环境</td>
<td>对话历史、工具结果、用户状态</td>
<td>多轮回应、调用计划、任务执行过程</td>
<td>客服 Agent、Copilot、办公助手、研究 Agent</td>
</tr>
</tbody>
</table>
<p>这几类任务并不是互相隔离的。一个自动驾驶系统就同时包含感知、预测与决策；一个大语言模型应用往往同时包含生成与交互；一个现代推荐系统既做预测，也可能接入生成式解释和对话式反馈。真正成熟的 AI 系统，通常不是只做其中一件事，而是把这些能力按流水线或闭环方式组合起来。</p>
<p>这张分类表还有一个重要作用：它提醒我们不要把“会聊天”误认为 AI 的全部，也不要把“识别图片”当成唯一的智能入口。AI 的本质不是某一种输入形式，也不是某一种热门应用，而是对不同任务类型建立稳定的输入到输出映射，并在需要时把这些映射连接成连续工作系统。后续篇章也可以按这个视角来读：经典机器学习偏预测，深度学习强化了感知与表示，生成式 AI 把生成和交互推到中心，而 Agent 系统则进一步把生成、决策与工具调用连接起来。</p>
<div class="blog_h1"><span class="graybg">什么叫智能</span></div>
<div class="blog_h2"><span class="graybg">学习能力与迁移能力</span></div>
<p>若要给“智能”一个足够实用、又不过分空泛的定义，一个很好的工作表述是：<span style="background-color: #c0c0c0;">智能 = 学习能力 + 迁移能力</span>。学习能力指系统能够从有限经验中提取规律；迁移能力指系统学到规律之后，能够在未见过但结构相近的新情境中继续做出合理判断。</p>
<p>这个定义的重要性在于，它把“记住训练样本”与“真正学会规律”区分开了。一个系统若只是把所有见过的情况硬背下来，那么它最多拥有记忆，不一定拥有智能。智能的难点不在于把过去储存起来，而在于从过去抽取出可泛化的结构。</p>
<div class="blog_h2"><span class="graybg">智能的多维结构</span></div>
<p>把智能理解成单一分数，会掩盖许多关键差异。真实系统中的智能通常至少包含几类不同能力：感知（能否从复杂输入中抽取有用信息）、表征（能否形成稳定内部概念）、记忆（能否保留历史经验）、推理（能否在已知条件上做组合与演绎）、规划（能否为目标拆解步骤）、行动（能否把判断落成可执行决策），以及沟通（能否把内部状态转换成外部可用表达）。</p>
<p>一个系统可能在其中某些方面很强，在另一些方面很弱。例如大型分类模型在感知和表征上可能很强，但不一定擅长长期规划；语言模型在表达和知识调用上很强，但若缺少外部工具和环境反馈，就未必具备可靠行动能力。因此，讨论智能不能只问“它聪不聪明”，还要问“它在哪些维度上具备能力、在哪些维度上仍有缺口”。</p>
<div class="blog_h2"><span class="graybg">从经验到概念</span></div>
<p>人类识别“苹果”这类概念时，通常是通过大量经验，在脑中逐步形成一个模糊但稳定的概念边界，而不是先掌握一组严密定义再去匹配世界。这个边界也不是几何参数、颜色阈值和纹理公式的明确列表，而是一种能够支持识别和迁移的内部表征。</p>
<p>这件事对 AI 尤其重要。它说明很多关键知识并不天然适合写成规则，而更适合通过样本驱动的表示学习形成。机器学习和深度学习之所以有效，正是因为它们允许系统用大量样本不断调整内部参数，最后形成“什么样的输入更像某个概念”的高维表示。</p>
<div class="blog_h2"><span class="graybg">泛化为什么重要</span></div>
<p>泛化（Generalization）是机器学习和人工智能中的中心概念。设训练数据来自分布 <span displaypfx="inline-" class="mathjax-container">\(\mathcal{D}_{\text{train}}\)</span>，模型在训练集上的经验风险（Empirical Risk）为</p>
<span displaypfx="" class="mathjax-container">\[\hat R(f)=\frac{1}{n}\sum_{i=1}^{n}\ell(f(x_i),y_i)\]</span>
<p>真正重要的是模型在未来未见样本上的期望风险（Expected Risk），而不只是 <span displaypfx="inline-" class="mathjax-container">\(\hat R(f)\)</span> 本身：</p>
<span displaypfx="" class="mathjax-container">\[R(f)=\mathbb{E}_{(x,y)\sim \mathcal{D}}\big[\ell(f(x),y)\big]\]</span>
<p>所谓泛化能力，本质上就是：训练中学到的规律，能否从有限样本扩展到更广泛但相关的真实世界分布。一个模型若只能在训练数据上表现良好，而离开训练分布就失效，它就更像记忆系统而不是智能系统。</p>
<p>这也是为什么 AI 讨论中总会反复出现过拟合（Overfitting）、分布偏移（Distribution Shift）、鲁棒性（Robustness）和迁移学习（Transfer Learning）这些概念。它们关心的都是同一件事：模型学到的东西，究竟是在逼近世界规律，还是只是在背训练题答案。</p>
<div class="blog_h2"><span class="graybg">专用智能与通用智能</span></div>
<p>按照任务适用范围，可以把 AI 粗略分成专用人工智能（Narrow AI）与通用人工智能（Artificial General Intelligence, AGI）。专用人工智能通常在某个任务上表现极强，例如围棋、图像分类、语音识别、广告排序或蛋白质结构预测；但它的能力边界高度依赖训练目标与任务环境，换一个问题往往就需要重新建模、重新训练甚至重写系统。</p>
<p>AGI 则要求系统具备更广泛的理解、推理、学习与迁移能力，能够跨任务、跨场景、跨知识域持续适应。这一目标远比单任务最优困难，因为它要求模型不只是对单个问题拟合得好，而是对世界结构形成更一般的内部表示。</p>
<p>因此，AlphaGo 可以击败顶级围棋选手，但这并不自动意味着它具备通用智能。它展现的是在一个定义良好、奖励明确、规则固定的任务中实现超人性能；这当然非常重要，但离“在不同领域都能自主迁移和解决问题”的 AGI 仍有明显距离。</p>
<div class="blog_h1"><span class="graybg">人工智能的早期路线</span></div>
<div class="blog_h2"><span class="graybg">符号主义与专家系统</span></div>
<p>人工智能早期最自然的思路是符号主义（Symbolicism）：既然人类能用语言、逻辑、规则和概念来描述世界，那么是否可以直接把这些规则写给机器，让机器照此推理。专家系统（Expert System）就是这种路线的代表。工程师通过知识库（Knowledge Base）、规则库（Rule Base）和推理机（Inference Engine），把领域专家的经验编码成显式规则，让机器在给定条件下自动给出结论。</p>
<p>这种方法在规则边界相对稳定、领域知识比较明确的任务上可以取得不错效果。例如早期的诊断辅助系统、规则客服、配置推荐系统，都曾从专家系统中受益。它的优势是可解释性强、行为边界清晰、局部领域内可控性高。</p>
<div class="blog_h2"><span class="graybg">为什么纯规则路线走不通</span></div>
<p>符号主义的根本局限，不在于规则无用，而在于现实世界太复杂。若想靠人工穷举规则来覆盖所有场景，很快会遇到三类问题。第一，规则组合爆炸：例外情况会越来越多，规则之间开始相互冲突。第二，感知输入难以被精确定义：光照变化、遮挡、噪声、语义歧义和上下文依赖，都会让“明确规则”变得脆弱。第三，规则迁移差：一个任务里定义好的知识，很难自然扩展到另一个任务。</p>
<p>更关键的是，世界中许多重要模式本来就不是人类能够轻易写成规则的。例如“什么样的像素组合像苹果”“什么样的句法和语义结构代表讽刺”“什么样的驾驶情境意味着危险”。人类能识别这些现象，并不意味着人类能把识别依据完整显式表达出来。</p>
<p>因此，若目标是让机器获得更强的适应性和泛化能力，系统必须学会从数据中提取模式，而不是永远依赖人类手工枚举所有知识。这就引出了机器学习路线。</p>
<div class="blog_h2"><span class="graybg">连接主义之外，还要看到统计学习</span></div>
<p>早期 AI 叙事常把历史压缩成“符号主义失败，连接主义胜利”。这个说法有一定直观性，但仍然过粗。真正推动现代 AI 成熟的，不只是神经网络路线本身，还包括统计学习（Statistical Learning）这一整套思想：经验风险最小化、泛化误差、正则化、概率建模、优化理论、核方法、集成学习和贝叶斯方法，都对今天的 AI 基础有决定性影响。</p>
<p>也就是说，现代 AI 的形成过程更接近三层视角的逐步叠加：符号视角强调显式知识与逻辑结构，统计视角强调从样本分布中估计规律，连接主义强调用大规模参数化函数学习复杂表示。今天真正有效的系统，往往同时吸收了这三条传统中的不同优点。</p>
<div class="blog_h1"><span class="graybg">机器为什么能学</span></div>
<div class="blog_h2"><span class="graybg">机器学习的基本思想</span></div>
<p>Arthur Samuel 在 1959 年对机器学习（Machine Learning）的经典定义，核心就在于一句话：<span style="background-color: #c0c0c0;">让计算机在不被显式编程的情况下获得学习能力</span>。这里“不被显式编程”不是说完全没有程序，而是说我们不再把任务规则逐条写死，而是给定数据、目标和优化机制，让系统自己去调整内部参数。</p>
<p>因此，机器学习与传统编程的分工发生了变化。传统编程更像</p>
<span displaypfx="" class="mathjax-container">\[\text{Rules} + \text{Data} \rightarrow \text{Output}\]</span>
<p>而机器学习更像</p>
<span displaypfx="" class="mathjax-container">\[\text{Data} + \text{Targets} + \text{Optimization} \rightarrow \text{Model}\]</span>
<p>人类仍然负责编写训练流程、定义损失函数、设计模型结构和评价指标，但不再手写所有领域规则；真正的映射关系由模型在数据中自动学得。</p>
<div class="blog_h2"><span class="graybg">模型究竟是什么</span></div>
<p>在最抽象的数学意义上，模型（Model）就是一个参数化函数（Parameterized Function）。给定输入 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>，模型输出预测 <span displaypfx="inline-" class="mathjax-container">\(\hat y=f_\theta(x)\)</span>，其中 <span displaypfx="inline-" class="mathjax-container">\(\theta\)</span> 表示模型参数。机器学习训练的目标，就是在庞大的参数空间里找到一组参数，使这个函数尽可能解释数据中的规律。</p>
<p>因此，“训练模型”本质上是在参数空间里搜索一个更好的函数，而不是把知识一条条写进程序。这个函数可以很简单，例如线性回归中的 <span displaypfx="inline-" class="mathjax-container">\(f_\theta(x)=w^\top x+b\)</span>；也可以极其复杂，例如拥有数十亿参数的大语言模型。复杂度不同，但本质没有变：它们都在试图逼近某个把输入映射到输出的规律。</p>
<p>这也是为什么机器学习常被表述为函数拟合（Function Approximation）。区别只在于，AI 面对的函数远比中学里的 <span displaypfx="inline-" class="mathjax-container">\(y=f(x)\)</span> 更复杂：输入可能是图片、文本、语音、视频、图结构或交互历史，输出可能是类别、数值、动作、文本序列甚至多步决策。</p>
<div class="blog_h2"><span class="graybg">训练在做什么</span></div>
<p>训练（Training）就是在反复试错中更新参数。设损失函数（Loss Function）为 <span displaypfx="inline-" class="mathjax-container">\(\ell(f_\theta(x),y)\)</span>，训练集上的目标函数通常写成</p>
<span displaypfx="" class="mathjax-container">\[J(\theta)=\frac{1}{n}\sum_{i=1}^{n}\ell(f_\theta(x_i),y_i)\]</span>
<p>优化算法会根据 <span displaypfx="inline-" class="mathjax-container">\(\nabla_\theta J(\theta)\)</span> 的方向逐步更新参数，使损失下降。对小模型而言，这可以理解为“不断试着把函数曲线调到更贴近数据”；对大模型而言，它仍然是同一件事，只是参数数量、数据规模和优化难度都被放大到了前所未有的量级。</p>
<p>因此，训练是一套规模化、可重复、可优化的搜索过程，而不是神秘的“让机器突然开窍”。机器学习的历史突破，很大程度上就是让这套搜索过程在更大数据、更复杂模型和更强硬件上变得可行。</p>
<div class="blog_h2"><span class="graybg">一个学习问题由什么构成</span></div>
<p>一个完整的学习问题，通常至少包含六个要素：输入表示 <span displaypfx="inline-" class="mathjax-container">\(x\)</span>、目标或反馈 <span displaypfx="inline-" class="mathjax-container">\(y\)</span>、模型 <span displaypfx="inline-" class="mathjax-container">\(f_\theta\)</span>、损失函数 <span displaypfx="inline-" class="mathjax-container">\(\ell\)</span>、优化算法，以及评估标准。只有把这几件事同时说清楚，问题才真正被定义完成。否则“做一个 AI 模型”这句话本身几乎没有技术含量。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">组成部分</td>
<td style="text-align: center;">它决定什么</td>
<td style="text-align: center;">典型问题</td>
</tr>
</thead>
<tbody>
<tr>
<td>输入表示</td>
<td>模型实际看见什么</td>
<td>文本是分词后 token、图像是像素还是 patch、表格特征是否标准化</td>
</tr>
<tr>
<td>目标 / 反馈</td>
<td>模型被鼓励学会什么</td>
<td>分类标签、回归值、奖励信号、对比学习正负样本</td>
</tr>
<tr>
<td>模型结构</td>
<td>函数族的表达能力与归纳偏置</td>
<td>线性模型、树模型、CNN、Transformer、MoE</td>
</tr>
<tr>
<td>损失函数</td>
<td>什么叫“预测得不好”</td>
<td>交叉熵、均方误差、对比损失、强化学习目标</td>
</tr>
<tr>
<td>优化算法</td>
<td>参数怎样被更新</td>
<td>SGD、AdamW、学习率调度、梯度裁剪</td>
</tr>
<tr>
<td>评估标准</td>
<td>模型是否真的有用</td>
<td>Accuracy、F1、AUC、BLEU、ROUGE、胜率、人工偏好</td>
</tr>
</tbody>
</table>
<p>这张表的意义在于：AI 的成败几乎从来不只由“模型结构”单独决定。很多看似是模型问题的失败，实际来自目标函数错位、输入表示粗糙、数据质量差或评估指标不对。理解这一点，后续学习才不会把注意力全部误投到“模型名字”本身。</p>
<div class="blog_h2"><span class="graybg">训练与推理的分工</span></div>
<p>训练（Training）和推理（Inference）是两个阶段。训练阶段的任务是用大量样本更新参数，让模型学到函数 <span displaypfx="inline-" class="mathjax-container">\(f_\theta\)</span>；推理阶段的任务则是在参数已经固定后，用这个函数处理新的输入。很多初学者会把“模型生成答案”与“模型学会能力”混成一件事，但这两个阶段的资源需求、系统结构和优化目标都不同。</p>
<p>训练更关注数据规模、梯度计算、参数更新和收敛稳定性；推理更关注延迟、吞吐、显存占用、服务成本与输出质量。例如一个模型可能训练非常昂贵，但推理相对便宜；也可能训练已完成，但由于上下文窗口、解码策略和缓存机制设计不佳，推理时依然很难落地。后续第 4 篇和第 5 篇会反复遇到这个区分。</p>
<div class="blog_h2"><span class="graybg">监督之外的反馈来源</span></div>
<p>视频中的直觉更偏向“给很多样本，模型就去学”。这当然是核心，但还需要补充：模型并不只从人工标注标签中学习。现代 AI 至少有三类主要反馈来源。第一类是监督学习（Supervised Learning），即直接给出正确答案或目标标签。第二类是自监督学习（Self-Supervised Learning），即从数据自身构造预测任务，例如掩码语言建模或下一个 token 预测。第三类是强化学习（Reinforcement Learning），即系统通过与环境交互，根据奖励信号优化长期行为。</p>
<p>这三类反馈机制并不是互斥关系。很多现代系统会把它们组合起来：先用自监督预训练打好通用表示，再用监督微调适配具体任务，最后再用强化学习或偏好优化调整行为。这种多阶段训练配方，正是现代大模型系统的常见做法。</p>
<div class="blog_h1"><span class="graybg">一个 AI 系统由什么组成</span></div>
<div class="blog_h2"><span class="graybg">数据、模型、目标、反馈</span></div>
<p>导论里很容易把“AI”误听成某个神奇单体，好像只要有一个模型名字，一切就自动发生了。真实系统远不是这样。一个可工作的 AI 系统，至少要把数据、模型、目标、反馈、训练、评估和部署串成一条闭环。模型当然是中心，但它只是闭环中的一个部件，而不是全部。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">部件</td>
<td style="text-align: center;">作用</td>
<td style="text-align: center;">若出问题会怎样</td>
</tr>
</thead>
<tbody>
<tr>
<td>数据</td>
<td>提供经验样本与任务边界</td>
<td>模型学不到规律，或学到偏差和噪声</td>
</tr>
<tr>
<td>模型</td>
<td>提供可学习的函数族与归纳偏置</td>
<td>表达能力不足，或虽强但很难训练与泛化</td>
</tr>
<tr>
<td>目标函数</td>
<td>定义什么叫“做得好”</td>
<td>模型可能学会与真实需求错位的行为</td>
</tr>
<tr>
<td>反馈机制</td>
<td>告诉系统怎样修正参数或策略</td>
<td>训练方向错误，优化不稳定</td>
</tr>
<tr>
<td>评估体系</td>
<td>判断模型是否真的有用</td>
<td>训练指标很好，但上线表现很差</td>
</tr>
<tr>
<td>部署系统</td>
<td>决定模型在真实环境里的延迟、成本、可靠性与安全性</td>
<td>即使模型本身很强，也无法稳定落地</td>
</tr>
</tbody>
</table>
<p>从这个角度看，AI 更像一条生产线，而不是一个神秘黑盒。后续 1 到 4 篇其实就是沿这条生产线逐步展开：先理解数学语言，再理解模型与训练，再理解基础模型，再理解系统层落地。</p>
<div class="blog_h2"><span class="graybg">数据质量与任务匹配</span></div>
<p>“海量数据”当然重要，但导论里还必须补一句更现实的话：高质量数据比单纯更多数据更重要。越接近任务结构、越干净、越有覆盖、越能提供有效反馈的数据，价值越高。大量重复、偏差严重、标注粗糙或分布失真的数据，完全可能把模型推向错误方向。现代 AI 的许多难题，其实不在模型结构本身，而在数据分布和目标构造不匹配。</p>
<p>因此，AI 工程里常常真正稀缺的不是“任意数据”，而是高质量、覆盖关键边界条件、与评估目标一致的数据。这个判断对后续所有篇章都成立，无论是在经典机器学习、深度学习还是大模型系统里。</p>
<div class="blog_h2"><span class="graybg">评估要前置到问题定义</span></div>
<p>另一个常见误区，是把评估（Evaluation）理解成模型训完之后再顺手看一眼分数。更准确的看法应该是：评估标准在问题定义阶段就已经介入了，因为它决定我们到底在优化什么。若任务真正关心的是风险控制，单纯追求 Accuracy 可能毫无意义；若任务关心排序质量，分类正确率就不是核心；若任务面向真实用户，延迟、稳定性和校准能力也会与“准确率”同等重要。</p>
<p>所以一个成熟 AI 系统的闭环顺序应当是“先定义任务与评估，再决定模型和训练”。很多失败项目的问题，不是模型不够先进，而是一开始就没有把问题定义清楚。</p>
<div class="blog_h1"><span class="graybg">人工智能系统为什么容易失败</span></div>
<div class="blog_h2"><span class="graybg">系统错误来自整条链路</span></div>
<p>AI 讨论里最容易形成的错觉，是把系统成败归结为“模型选得对不对”。真实情况通常更复杂。一个系统即使使用了当前最强的模型，只要数据分布有偏、目标函数错位、验证集设计失真、上线环境变化、工具链不稳定或输出缺乏校验，最终都可能失败。换言之，AI 的失效往往不是一个单点故障，而是整条链路中多个局部误差叠加后的结果。</p>
<p>这也是为什么成熟团队不会只问“模型用了什么”，而会同时追问：数据从哪来、标签怎么定义、评估覆盖了哪些边界、上线后的输入是否和训练时一样、系统如何做回滚、输出是否有验证闭环。模型只是核心部件，但不是唯一决定因素。</p>
<div class="blog_h2"><span class="graybg">数据偏差与标签噪声</span></div>
<p>模型学到的东西高度依赖数据，因此数据偏差（Data Bias）通常是第一类失效来源。若训练数据只覆盖了局部场景，模型就会把局部规律误当成普遍规律；若数据本身带有历史偏见，模型也可能把这些偏见一并放大。标签噪声（Label Noise）则会进一步扭曲学习目标，使模型在训练中收到错误反馈。</p>
<p>常见的数据问题包括：采样不均衡、少数群体样本不足、历史行为中本来就包含制度性偏差、弱标注体系过粗、训练集与真实线上流量不一致。很多时候，模型并不是“学坏了”，而只是忠实地学习了有问题的数据分布。这也是为什么数据治理在 AI 系统里并不是附属工作，而是能力上限本身的一部分。</p>
<div class="blog_h2"><span class="graybg">目标错位与奖励黑客</span></div>
<p>即使数据没有明显问题，系统仍可能因为目标错位（Objective Misalignment）而失败。目标错位指的是：训练中被优化的指标，并不真正等于业务或现实中想要的结果。例如一个推荐系统若只优化点击率，可能学会推送极端标题；一个客服模型若只优化“尽快结束对话”，可能学会敷衍用户；一个生成模型若只追求表面流畅，可能牺牲事实一致性。</p>
<p>在强化学习或任何带反馈优化的系统中，这种现象常被称为奖励黑客（Reward Hacking）：系统会寻找最容易提高表面指标的路径，而不一定遵守人类真正想要的行为意图。它提醒我们，定义目标函数不是形式化收尾，而是系统设计中最敏感的一步。优化器不会理解你的“真实意图”，它只会忠实优化你写进目标里的那个量。</p>
<div class="blog_h2"><span class="graybg">分布漂移与 OOD</span></div>
<p>一个模型在离线验证中表现优秀，并不保证上线后仍然有效，因为真实世界的输入分布会变化。这类现象通常称为分布漂移（Distribution Shift）或数据漂移（Data Drift）。若新输入显著偏离训练分布，模型就进入了 OOD（Out-of-Distribution，分布外）区域，此时它的输出往往会变得不稳定。</p>
<p>分布漂移可能来自季节变化、用户行为变化、设备升级、采集链路变化、业务规则调整，甚至是模型上线本身对用户行为产生的反作用。AI 系统因此不能被当作“一次训练，永久有效”的静态制品，而更像需要持续监控和更新的动态系统。后续篇章中的校准、鲁棒性、RAG、Agent 验证，本质上都和这个问题有关。</p>
<div class="blog_h2"><span class="graybg">幻觉、过度自信与校准不足</span></div>
<p>生成式模型最典型的失效形式之一，是幻觉（Hallucination）：输出看起来流畅、结构完整，甚至语气非常自信，但事实并不成立。分类模型也有对应问题，即过度自信（Overconfidence）：明明预测错了，却给出很高置信度。这说明模型输出的“分数”或“概率”不一定能直接代表真实可信度。</p>
<p>这就引出了校准（Calibration）问题。一个校准良好的系统，输出 0.8 概率时，应当大致意味着它在长期统计上约有 80% 的正确率。若模型严重失校，就会在高风险场景里造成很大问题，因为用户和上层系统会把高置信度误解为高可靠性。因此，现代 AI 不能只关注“答得像不像”，还要关注“它对自己错误的认识是否准确”。</p>
<div class="blog_h2"><span class="graybg">长链任务中的误差累积</span></div>
<p>AI 在单步任务上可能表现很好，但一旦任务变成长链流程，误差就会层层累积。第一步提取错一点，第二步就在错误前提上继续推理，第三步再去调用工具，最后整个系统可能看起来流程完整，却建立在一开始的小偏差之上。Agent、多步规划、复杂检索问答和自动化工作流，都会遇到这种问题。</p>
<p>因此，长链任务真正困难的地方，不只是“每一步都尽量强”，而是系统是否具备中途检查、状态校正、证据回填和回滚重试能力。后续第 5 篇会进一步展开这一点：很多 Agent 的核心价值，不在于某一步更聪明，而在于它能否在多步执行中持续发现并修正自己的偏差。</p>
<div class="blog_h2"><span class="graybg">离线分数好，不等于上线效果好</span></div>
<p>很多 AI 项目在实验室里看起来已经成功，真正上线后却表现平平，原因在于离线评估和真实环境之间往往存在巨大的制度差。离线数据集可能过于干净，标签定义可能与真实用户目标不一致，评测样本可能无法覆盖边界情形，而上线环境里却要面对噪声输入、恶意输入、冷启动用户、长尾场景和系统延迟约束。</p>
<p>因此，“离线 benchmark 做到多少分”只能说明模型在那个评测协议下有多强，不能自动推出它在生产环境里同样可靠。成熟系统通常需要同时做离线评估、在线 A/B 测试、回归测试、压力测试和安全测试。AI 的真正难点，往往就在从离线成绩跨到在线稳定性这一跳。</p>
<div class="blog_h2"><span class="graybg">为什么风险意识属于入门知识</span></div>
<p>这些失效模式之所以应该放在导论里，而不是留到很后面再讲，是因为它们决定了后续所有知识的阅读方式。若没有风险意识，后面的数学、模型、训练技巧和推理优化很容易被误解成“只要把分数卷高，系统自然会成功”。真实情况并不是这样。AI 学习从第一天开始，就应该把能力与边界同时纳入视野。</p>
<p>因此，导论篇不仅要回答“AI 为什么能成功”，也必须同时回答“AI 为什么会失败”。只有这两条线一起建立起来，后续对机器学习、深度学习、大模型和 Agent 的理解才不会失真。</p>
<div class="blog_h1"><span class="graybg">为什么直到近十几年才爆发</span></div>
<div class="blog_h2"><span class="graybg">数据是学习的原料</span></div>
<p>机器若要从经验中学习，首先必须“见得足够多”。互联网和数字化社会提供了前所未有的数据规模：网页文本、百科知识、社交媒体、搜索日志、点击记录、语音、图片、视频、传感器数据，几乎把大量人类行为与知识活动都转写成了可计算的数字语料。没有这些数据，模型就像只见过极少样本的人，难以形成稳定概念。</p>
<p>数据的重要性不只在量，还在覆盖范围。若训练数据太少，模型学不到稳健规律；若数据分布太窄，模型就很难泛化到复杂世界。现代 AI 的许多能力之所以能够出现，前提正是训练集规模和多样性的剧烈提升。</p>
<div class="blog_h2"><span class="graybg">算力让搜索过程成为现实</span></div>
<p>深度学习和大模型训练，本质上依赖海量矩阵乘法、卷积、注意力和梯度计算。若没有足够强的硬件，这些优化过程在工程上根本跑不动。GPU、TPU 以及后续更专业的 AI 加速器，把高度并行的张量计算变成了现实，也让“数十亿参数、数万亿 token”这类训练规模进入可操作区间。</p>
<p>因此，AI 是一个核心思想提出很早、但工程落地条件直到近十几年才真正成熟的领域。只有当<span style="background-color: #c0c0c0;">数据、算力、算法和软件基础设施</span>同时成熟，这些思想才能真正落地成具有产业影响力的系统。</p>
<div class="blog_h2"><span class="graybg">算法与工程闭环</span></div>
<p>即使有数据和硬件，若缺少有效算法，训练仍然可能失败。现代 AI 的成功同样依赖优化方法、初始化、正则化、残差连接、归一化、分布式训练、自动求导框架和部署工具链的共同成熟。真正让人工智能爆发的，不是单个孤立发明，而是整套技术生态形成了闭环。</p>
<p>换言之，AI 的发展并不是线性地“某一年突然变聪明”，而是多条技术线在同一时期交汇：数据解决“学什么”，算力解决“算得动吗”，算法解决“学得稳吗”，工程系统解决“能不能规模化复现与部署”。</p>
<div class="blog_h2"><span class="graybg">规模效应与预训练范式</span></div>
<p>近十几年 AI 爆发还有一个视频里只隐约提到、但实际上极其关键的因素：规模效应（Scaling Effect）。当模型参数、训练数据和计算预算在一定范围内同步扩大时，模型性能往往不是随机波动，而会呈现相对平滑、可预测的提升趋势。也就是说，很多能力并不是靠手工加入某个单独规则突然获得，而是在足够大的训练规模下逐步显现出来。</p>
<p>预训练（Pretraining）因此成为现代 AI 的核心范式。其基本逻辑是：先在大规模通用数据上学习通用表示或通用预测能力，再通过微调（Finetuning）、指令对齐（Instruction Tuning）或其他后训练方式适配具体任务。这个范式改变了整个行业的工作方式，因为模型不再是“每个任务单独训练一个小系统”，而更像一个可复用的能力底座。</p>
<div class="blog_h1"><span class="graybg">现代人工智能的几条主线</span></div>
<div class="blog_h2"><span class="graybg">机器学习</span></div>
<p>机器学习是现代 AI 的第一条主线。它的关键突破在于：不再完全依赖手工规则，而是让系统从数据中学习统计规律。在线性模型、树模型、支持向量机、聚类、概率模型和集成学习这些方法中，模型容量通常相对可控，特征工程的地位仍然很高，人类需要较多参与“该喂什么特征”。</p>
<p>这一阶段的 AI 已经能在很多任务上显著优于纯规则系统，例如垃圾邮件识别、信用风险评估、推荐排序和基本文本分类。但它的边界也很清楚：模型更多是在人工定义好的特征空间里工作，而不是从原始高维感知数据中自主学习层级表示。</p>
<div class="blog_h2"><span class="graybg">深度学习</span></div>
<p>深度学习（Deep Learning）把机器学习进一步推进为表示学习（Representation Learning）。系统不再严重依赖人工手工提特征，而是使用多层神经网络从原始输入中逐层学习更抽象的表示。图像中的边缘、纹理、部件与对象，语音中的音素与韵律，文本中的词义、语法和上下文关系，开始由模型内部自动形成。</p>
<p>这条主线带来了感知智能的大规模突破。图像识别、目标检测、语音识别、人脸识别、机器翻译、自动驾驶感知与 AlphaGo 这样的系统，都建立在深度学习及其扩展方法之上。它们展现了极强的专用智能，但在广泛迁移和跨任务统一上仍存在明显限制。</p>
<div class="blog_h2"><span class="graybg">强化学习</span></div>
<p>强化学习（Reinforcement Learning, RL）讨论的是另一类问题：当系统不是只做一次静态预测，而是要在环境中连续行动、不断接收反馈并优化长期收益时，该怎样学习策略（Policy）。它和监督学习最大的不同，是反馈不一定立即出现，也不一定告诉模型“正确答案是什么”；系统往往只能看到某种奖励（Reward）或惩罚，再自己推断哪些行为序列更优。</p>
<p>强化学习之所以在 AI 总纲里重要，不只是因为 AlphaGo。它代表了从“识别与预测”走向“决策与行动”的关键跨越。后来的 RLHF、RLAIF、Agent 规划、自动控制和机器人学习，都延续了这条主线。即使很多大模型系统的主体不是用 RL 从零训练出来，强化学习仍然是现代 AI 中不可绕开的基本思想之一。</p>
<div class="blog_h2"><span class="graybg">大语言模型与生成式 AI</span></div>
<p>大语言模型（Large Language Model, LLM）把 AI 推到了第三条主线：生成式 AI（Generative AI）。与很多传统系统主要做“判断题”不同，语言模型的训练目标是不断预测下一个 token。这一目标看似简单，却迫使模型在大规模文本中学习词法、句法、语义、知识、逻辑关系和风格模式，从而涌现出问答、总结、翻译、写作、代码生成与多步推理等能力。</p>
<p>Transformer 是这一阶段最关键的结构基础。通过自注意力（Self-Attention）、残差连接和大规模预训练，语言模型第一次在统一架构中同时表现出较强的通用知识调用能力、生成能力和任务迁移能力。这也是为什么后续篇章会把 Transformer 和大模型单独拿出来展开。</p>
<div class="blog_h2"><span class="graybg">几条主线的关系</span></div>
<p>这些主线不是互相替代的断裂历史，而是一条不断扩展的连续谱。机器学习提供了“从数据中学习”的基本范式；深度学习进一步把表示学习纳入模型内部；强化学习把学习目标扩展到行动与长期回报；大语言模型则把预训练、生成和通用迁移能力推到更高尺度。它们彼此叠加，而不是互相否定。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">阶段</td>
<td style="text-align: center;">核心问题</td>
<td style="text-align: center;">主要特征</td>
<td style="text-align: center;">典型代表</td>
</tr>
</thead>
<tbody>
<tr>
<td>机器学习</td>
<td>如何从数据中学习统计规律</td>
<td>特征工程重要；模型相对浅；强调监督学习与泛化</td>
<td>逻辑回归、SVM、随机森林、GBDT</td>
</tr>
<tr>
<td>深度学习</td>
<td>如何自动学习层级表示</td>
<td>多层神经网络；端到端训练；感知任务突破</td>
<td>CNN、RNN、ResNet、AlphaGo</td>
</tr>
<tr>
<td>强化学习</td>
<td>如何在行动中根据反馈优化长期策略</td>
<td>强调状态、动作、奖励和长期回报；面向决策与控制</td>
<td>Q-Learning、Policy Gradient、AlphaGo、RLHF</td>
</tr>
<tr>
<td>大语言模型</td>
<td>如何在统一架构中获得通用生成与迁移能力</td>
<td>Transformer、自监督预训练、生成式任务、规模效应</td>
<td>GPT、PaLM、Llama、Claude 类模型</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">为什么语言模型率先突破</span></div>
<div class="blog_h2"><span class="graybg">语言是世界知识的高密度压缩</span></div>
<p>语言之所以在 AGI 讨论中占据中心位置，一个重要原因是：人类大量知识本来就以文本形式被压缩和记录。科学规律、历史经验、社会规范、技术文档、小说叙事、代码、对话、数学推理，许多内容都已经被写进文字体系。对模型而言，学习语言并不只是学习词语排列，而是在学习人类如何编码世界结构。</p>
<p>例如，“所以”常常隐含因果关系，“但是”常常隐含转折，“如果……那么……”隐含条件推理，故事叙事中又包含时间、动机、行为与结果的链式结构。语言并不是世界本身，但它是人类认知世界的一种高度压缩表示。因此，在海量文本上训练下一个 token 预测器，可能间接逼迫模型学习大量世界规律。</p>
<div class="blog_h2"><span class="graybg">下一个 token 预测为什么会产生复杂能力</span></div>
<p>语言模型的训练目标表面上非常简单：给定上下文 <span displaypfx="inline-" class="mathjax-container">\(x_{1:t-1}\)</span>，预测下一个 token <span displaypfx="inline-" class="mathjax-container">\(x_t\)</span>，也就是最大化</p>
<span displaypfx="" class="mathjax-container">\[p(x_t\mid x_1,\dots,x_{t-1})\]</span>
<p>但这个目标的约束其实非常强。若模型想要准确预测法律文本中的下一句，它就必须理解法律概念和逻辑结构；若想预测一段代码的下一行，它就必须理解语法、控制流与 API 用法；若想预测一个故事的结局，它就要理解人物动机、叙事结构和常识。这使得“下一个 token 预测”虽然形式简单，内在上却会逼迫模型学习深层模式。</p>
<p>因此，大语言模型看起来像是在逐词生成，实质上是在通过这个统一目标吸收大量分布式知识表示。这也是现代生成式 AI 产生涌现能力（Emergent Capability）的关键背景之一。</p>
<div class="blog_h2"><span class="graybg">为什么语言模型率先逼近通用突破</span></div>
<p>从直觉上看，视觉似乎比语言更接近真实世界，因此很多人曾认为计算机视觉（Computer Vision, CV）才是通向通用智能的最直接道路。这个判断并不荒谬，因为视觉确实与空间、物体、运动和物理交互关系更紧。但历史上率先爆发的却是语言模型，其重要原因在于：语言数据比高质量世界交互数据更容易大规模收集、更容易统一标注、更容易压缩高层知识结构。</p>
<p>换言之，视觉更贴近世界本体，语言更贴近人类已经整理好的世界知识。前者更“原始”，后者更“高密度”。语言模型之所以先爆发，不一定说明语言比空间更根本，而更可能说明语言先在可训练性、数据规模和目标统一性上形成了更好的工程条件。</p>
<div class="blog_h1"><span class="graybg">当前 AI 的能力与边界</span></div>
<div class="blog_h2"><span class="graybg">它擅长什么</span></div>
<p>若把当代 AI 的长处概括一下，它最擅长的是：在大规模数据中提取统计规律；在高维输入中学习分布式表示；在局部定义清晰的目标上反复优化；在单次或短链任务中产生非常强的模式识别、生成和匹配能力。图像分类、语音识别、推荐排序、检索匹配、文档摘要、代码补全和多轮问答，都属于这种优势可以被直接放大的领域。</p>
<p>这些能力的共同底层，是模型非常擅长处理大规模模式压缩与重组。它可以把海量经验浓缩进参数，把看似分散的线索组合成输出，这正是现代 AI 之所以显得“聪明”的原因。</p>
<div class="blog_h2"><span class="graybg">它为什么还会犯低级错误</span></div>
<p>AI 的弱点同样有共性。第一，它学到的大多是分布规律，而不一定是人类意义上的显式因果结构。第二，它可能非常擅长局部模式匹配，但在长链规划、跨步骤一致性、外部事实校验和真实世界 grounding 上仍不稳定。第三，它的输出是否可信，很大程度上取决于训练分布、上下文、工具链和验证机制，而不是只取决于模型参数规模。</p>
<p>这也是为什么大模型会出现幻觉（Hallucination）：模型并不总是在“查询一个外部真值数据库”，它更多是在根据训练中见过的大量分布模式，生成当前看起来最合理的延续。若任务需要精确事实、长链一致性或外部环境对齐，单靠内部参数往往不够，必须依赖检索、工具调用、状态管理与验证闭环。</p>
<div class="blog_h2"><span class="graybg">会生成不等于已经理解世界</span></div>
<p>生成能力之所以容易让人误判，是因为流畅输出很像理解。一个模型能写得很像、说得很像、总结得很像，并不自动意味着它已经拥有与人类相同的世界模型。理解至少还涉及可迁移性、反事实推理、跨情境一致性、与环境交互后的自我修正能力，以及对物理和社会约束的稳定把握。</p>
<p>因此，导论里必须保留一个清醒的判断：现代 AI 已经极大突破了“模式识别”和“符号生成”的边界，但它距离稳定、统一、具身、可验证的通用智能仍有明显距离。既不能低估它已经做到的事，也不能因为生成效果惊艳就提前宣布问题已经全部解决。</p>
<div class="blog_h1"><span class="graybg">AGI 仍然悬而未决</span></div>
<div class="blog_h2"><span class="graybg">语言智能是否足够</span></div>
<p>即使语言模型取得巨大进展，是否仅靠语言就能实现 AGI，仍然存在强烈争议。一种观点认为，语言已经高度压缩了世界知识，大规模语言建模因此足以逼近通用智能；另一种观点则认为，语言只是世界的符号映射，而不是世界本身，真正的智能还需要空间感知、物理直觉、行动反馈和长期交互经验。</p>
<p>这种分歧的关键不在“语言有没有价值”，而在“语言是否足够”。如果一个系统不理解空间关系、物体恒常性、因果交互和物理约束，那么它可能仍然停留在对符号统计规律的高度拟合，而没有真正建立起可用于行动的世界模型（World Model）。</p>
<div class="blog_h2"><span class="graybg">空间智能与世界模型</span></div>
<p>空间智能（Spatial Intelligence）强调，智能体不仅要会处理符号和文本，还要能理解物体、距离、运动、遮挡、三维结构和物理一致性。对生物而言，这种能力与生存高度相关；对机器而言，它决定了系统是否能从“会说”进一步走向“会看、会做、会交互”。</p>
<p>这也是为什么近年来多模态模型、世界模型、机器人学习和具身智能（Embodied AI）重新成为 AGI 讨论中的核心方向。未来更可能出现的，不是“语言智能”和“空间智能”二选一，而是多种能力逐步汇合：语言提供高密度知识压缩，感知与行动提供对真实世界约束的接触，二者共同构成更完整的通用智能基础。</p>
<div class="blog_h1"><span class="graybg">如何阅读后续篇章</span></div>
<div class="blog_h2"><span class="graybg">这一套 AI 知识为什么这样编排</span></div>
<p>AI 学习最常见的问题，往往是层级混乱而不是资料不够。很多人一开始就直接进入模型名称、训练技巧和论文细节，但没有先回答几个最根本的问题：模型为什么存在、训练到底在优化什么、泛化为什么重要、不同阶段的 AI 方法究竟解决了什么问题。没有这层导论，后续知识就容易变成孤立名词堆。</p>
<p>因此，这套 quick reference 采用从抽象到具体的顺序：</p>
<ul>
<li>第 0 篇先回答“什么是智能、什么是模型、AI 为什么能学”。</li>
<li>第 1 篇给出数学语言：向量、矩阵、导数、概率、信息量。</li>
<li>第 2 篇进入机器学习、神经网络、训练、评估与正则化。</li>
<li>第 3 篇按任务展开自然语言处理、计算机视觉、语音、搜索推荐与时序建模。</li>
<li>第 4 篇进入 Transformer、大模型、多模态与推理优化。</li>
<li>第 5 篇进入上下文工程、RAG、Agent 与系统层落地。</li>
</ul>
<p>按这条顺序阅读，后面的每一层都会回答前一层留下的问题，而不是凭空多出一个新术语体系。这样整套内容才更接近一张完整地图，而不是若干互不连通的知识岛。</p>
<div class="blog_h2"><span class="graybg">整套 quick reference 的总地图</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">篇章</td>
<td style="text-align: center;">核心问题</td>
<td style="text-align: center;">主要内容</td>
<td style="text-align: center;">与下一篇的关系</td>
</tr>
</thead>
<tbody>
<tr>
<td>ref-0 导论</td>
<td>AI 到底在做什么，为什么机器能学，为什么它也会失败</td>
<td>智能定义、AI/ML/DL/LLM 关系、模型与训练、现代 AI 主线、能力边界、风险与失效模式</td>
<td>把概念地图搭起来，说明为什么后面必须先学数学和学习论</td>
</tr>
<tr>
<td>ref-1 数学基础</td>
<td>模型里的向量、矩阵、导数、概率和信息量到底是什么意思</td>
<td>基础数学、线性代数、微积分、概率论与统计、信息论基础</td>
<td>给后续所有模型和训练公式提供共同语言</td>
</tr>
<tr>
<td>ref-2 机器学习与神经网络</td>
<td>模型如何被构造、训练、评估、正则化，以及不同模型家族各擅长什么</td>
<td>常用算法、经典机器学习、神经网络、深度学习、训练机制、评估与任务头</td>
<td>把数学语言落实到具体建模方法，并通向 Transformer 与大模型</td>
</tr>
<tr>
<td>ref-3 任务版图</td>
<td>这些模型方法最终被拿来解决哪些任务，以及不同模态和业务方向各自关心什么</td>
<td>自然语言处理、计算机视觉、语音和音频处理、搜索/推荐/广告预估、时序建模和时间序列</td>
<td>把建模方法连接到真实任务版图，建立“方法到任务”的映射</td>
</tr>
<tr>
<td>ref-4 Transformer 与大模型</td>
<td>现代基础模型如何形成、扩展、训练、对齐和高效推理</td>
<td>Transformer、语言模型、多模态、预训练与微调、推理优化、训练监控经验法则</td>
<td>说明模型本体如何成立，再把视角转到系统装配与应用落地</td>
</tr>
<tr>
<td>ref-5 系统层与 Agent</td>
<td>模型如何被放进真实系统里持续工作</td>
<td>上下文工程、RAG、Harness Engineering、Agent、工具调用、验证闭环与多步执行</td>
<td>把前面所有模型知识落实到真实应用系统</td>
</tr>
</tbody>
</table>
<p>若把这六篇压缩成一句话：第 0 篇给出地图，第 1 篇提供语言，第 2 篇解释建模，第 3 篇展开任务版图，第 4 篇解释现代基础模型，第 5 篇解释系统如何落地。按这个顺序阅读，会比直接从某个热门模型或某篇论文切入，更容易建立完整而稳定的 AI 知识框架。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/ai-knowledge-quick-ref-0">人工智能理论知识 - 简介</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/ai-knowledge-quick-ref-0/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>多语言敏感信息检测模型训练日志</title>
		<link>https://blog.gmem.cc/pii-training-journal</link>
		<comments>https://blog.gmem.cc/pii-training-journal#comments</comments>
		<pubDate>Sun, 12 Apr 2026 02:36:39 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[AI]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=42341</guid>
		<description><![CDATA[<p>这篇文章记录一个多语言敏感信息识别项目的完整训练日志。它关注的是工程路径本身：原始 AI 合成语料如何被清洗成可训练数据，哪些增强真正提高了监督质量，哪些架构在日志和代码块场景下看起来合理却会系统性失效，以及最终为什么是分层架构加数据侧 hard sample 方案收敛下来。 任务概述 项目目标是多语言敏感信息识别——一个比通用 NER 更窄、条件更苛刻的场景。输入既包括自然语言为主的技术方案、故障报告或者聊天记录，也包括日志、代码块、连接串、配置片段和混合文本。训练数据覆盖 15 种语言，实体既包含通用 PII（个人姓名、邮箱、电话、密码等），也包含云资源标识（Instance ID、SecretId/SecretKey）、腾讯云专属标识（UIN、APPID、STAFF_ID），以及代码日志容器实体（CODE_BLOCK、LOG_BLOCK、INLINE_CODE）。系统要同时满足三个要求：多语言泛化、嵌套识别、困难边界鲁棒性。 这里的困难边界有两类。一类是 hard positive，例如值本身很弱（一个普通的数字串、一段普通文本），但上下文明确说明它就是密码、验证码或证件号；另一类是 hard negative，例如字符串格式很像 PII（长随机串、带前缀的标识符），但它只是示例值、占位符、公开测试凭据、文档样例或日志中的非敏感标识。 数据处理 原始语料 <a class="read-more" href="https://blog.gmem.cc/pii-training-journal">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/pii-training-journal">多语言敏感信息检测模型训练日志</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>这篇文章记录一个多语言敏感信息识别项目的完整训练日志。它关注的是工程路径本身：原始 AI 合成语料如何被清洗成可训练数据，哪些增强真正提高了监督质量，哪些架构在日志和代码块场景下看起来合理却会系统性失效，以及最终为什么是分层架构加数据侧 hard sample 方案收敛下来。</p>
<div class="blog_h1"><span class="graybg">任务概述</span></div>
<p>项目目标是多语言敏感信息识别——一个比通用 NER 更窄、条件更苛刻的场景。输入既包括自然语言为主的技术方案、故障报告或者聊天记录，也包括日志、代码块、连接串、配置片段和混合文本。训练数据覆盖 15 种语言，实体既包含通用 PII（个人姓名、邮箱、电话、密码等），也包含云资源标识（Instance ID、SecretId/SecretKey）、腾讯云专属标识（UIN、APPID、STAFF_ID），以及代码日志容器实体（CODE_BLOCK、LOG_BLOCK、INLINE_CODE）。系统要同时满足三个要求：多语言泛化、嵌套识别、困难边界鲁棒性。</p>
<p>这里的困难边界有两类。一类是 hard positive，例如值本身很弱（一个普通的数字串、一段普通文本），但上下文明确说明它就是密码、验证码或证件号；另一类是 hard negative，例如字符串格式很像 PII（长随机串、带前缀的标识符），但它只是示例值、占位符、公开测试凭据、文档样例或日志中的非敏感标识。</p>
<div class="blog_h1"><span class="graybg">数据处理</span></div>
<div class="blog_h2"><span class="graybg">原始语料</span></div>
<p>上游数据是大规模 LLM 合成语料。规模足够大，语言覆盖也足够广，但问题同样集中：标签本体漂移、占位符污染、演示值高频复用、模板固化严重，而且最上游只有 <span displaypfx="inline-" class="mathjax-container">\((value, type)\)</span> 对，没有字符级 span。原始语料总量为 2,603,742 条，实体总数超过 7200 万，类型却膨胀到 442 种，其中相当一部分是模型幻觉出来的错误标签。</p>
<p>这个约束直接决定了后续数据工程的设计。训练管线不在中间文件里长期保存 offset（字符偏移量），而是统一保留 <span displaypfx="inline-" class="mathjax-container">\(\{type, value\}\)</span>，在构建训练集时再通过 string matching 动态恢复 span。这样做的收益非常实际：只要保证 <span displaypfx="inline-" class="mathjax-container">\(value \in text\)</span>，就可以安全地做变长替换、前缀删除、书写变体转换和语言地域扩展，而不需要在每个中间阶段维护一条脆弱的 offset 传播链。</p>
<div class="blog_h2"><span class="graybg">清洗</span></div>
<p>清洗聚焦两个目标：</p>
<ul>
<li>标签本体收敛，把上游噪声类型压回稳定实体集合。例如删除 <pre class="crayon-plain-tag">IP</pre> 父类，只保留 <pre class="crayon-plain-tag">IPV4</pre> 和 <pre class="crayon-plain-tag">IPV6</pre>；把 <pre class="crayon-plain-tag">URL</pre> 统一改写为 <pre class="crayon-plain-tag">URI</pre>，再从 <pre class="crayon-plain-tag">URI</pre> 中拆出 <pre class="crayon-plain-tag">URN</pre>；对 <pre class="crayon-plain-tag">OTP</pre>、<pre class="crayon-plain-tag">JWT</pre>、<pre class="crayon-plain-tag">DOMAIN</pre>、<pre class="crayon-plain-tag">POSTALCODE</pre> 这类高度依赖格式的实体施加更严格的正则和上下文条件。</li>
<li>通用噪声清洗，包括占位符过滤、空值过滤、重复实体去重和异常值剔除。</li>
</ul>
<p>这一步的意义超出了数据清洗本身——它在定义模型将来到底学什么。举例来说，<pre class="crayon-plain-tag">DOMAIN</pre> 只保留裸域名，出现 <pre class="crayon-plain-tag">@</pre>、<pre class="crayon-plain-tag">://</pre> 或 <pre class="crayon-plain-tag">/</pre> 一律丢弃；<pre class="crayon-plain-tag">OTP</pre> 只接受 4 到 8 位纯数字，并要求近邻上下文出现验证码语义；<pre class="crayon-plain-tag">JWT</pre> 要同时满足长度、三段式和正则结构要求。这样得到的训练标签虽然更少，但语义边界更清楚。</p>
<div class="blog_h2"><span class="graybg">随机化</span></div>
<p>原始语料的另一个问题是示例值复用。项目内部统计显示，多类高风险实体都存在明显的 prompt-level 模板固化——同一批 LLM 生成的数据里，同一类实体反复使用极少数固定值。如果不处理，模型会记住这些值的字面内容（"遇到 AKID1234567890 就标为 SECRET_ID"），学不到它们的结构规律（"AKID 加 32 位字母数字 → SECRET_ID"）。高频值随机化的目的是迫使模型从字符串结构学习，打破对演示值的死记硬背。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center; width: 25%">类型</td>
<td style="text-align: center;">问题表现</td>
<td style="text-align: center;">随机化约束</td>
</tr>
</thead>
<tbody>
<tr>
<td>SECRET_ID</td>
<td>公开示例前缀和固定长度值大量重复</td>
<td>保留厂商前缀（AKID、AKIA、AIza、ghp_、sk-、ak-）、长度和字符集</td>
</tr>
<tr>
<td>SECRET_KEY</td>
<td>hex 或 base62 样例高频复用</td>
<td>保留长度和字符集类别（hex/base62/base64）</td>
</tr>
<tr>
<td>APPID / UIN</td>
<td>固定数字段反复出现</td>
<td>保留长度和数字结构</td>
</tr>
<tr>
<td>IPV4 / IPV6</td>
<td>公开演示地址高度集中</td>
<td>保留 private、loopback、documentation 等语义类别</td>
</tr>
<tr>
<td>CLOUD_INS_ID</td>
<td>云资源前缀模式高度重复</td>
<td>保留资源前缀（ins-、cdb-、lb-）和整体长度</td>
</tr>
<tr>
<td>UUID / URN</td>
<td>标准示例串集中</td>
<td>保留版本位或 <pre class="crayon-plain-tag">urn:<nid>:</pre> 结构</td>
</tr>
</tbody>
</table>
<p>随机化采用 record 内一致、跨 record 独立的策略。同一条记录里同一个原值无论出现在实体还是正文复述里，都必须映射到同一个新值，否则上下文一致性会被破坏。这一点对日志和代码场景尤其重要，因为敏感值经常在同一条文本里被多次复述——比如一条日志里 SecretKey 出现三次（配置、报错消息、堆栈变量），它们必须是同一个替换值。</p>
<div class="blog_h2"><span class="graybg">增强</span></div>
<p>训练集不是清洗完就直接开训的——它经过了多轮定向扩展。增强流水线覆盖国家变体扩展、locale-sensitive 重采样、银行与证件子类型补齐、云计算生命周期语料、中文书写变体、腾讯云专属增强、繁体中文转换、日志代码注入和 hard sample 生成。每个增强模块解决一个具体的覆盖缺口，单纯堆数据量没有意义。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">增强模块</td>
<td style="text-align: center;">目的</td>
<td style="text-align: center;">规模</td>
</tr>
</thead>
<tbody>
<tr>
<td>locale-sensitive 重采样</td>
<td>把相同语言拆到不同国家格式，修复电话、地址、邮编等区域依赖</td>
<td>134,141 条</td>
</tr>
<tr>
<td>银行与证件子类型补齐</td>
<td>补入 SWIFT、CVV、routing number、MRN、insurance policy 等弱覆盖子类</td>
<td>748,150 条</td>
</tr>
<tr>
<td>云计算生命周期语料</td>
<td>生成真实云运维工单、故障排查、配置片段和厂商交叉场景</td>
<td>3,926,300 条</td>
</tr>
<tr>
<td>中文书写变体</td>
<td>补齐全角数字、全角括号和中文金额写法</td>
<td>107,032 条</td>
</tr>
<tr>
<td>腾讯云专属增强</td>
<td>把特定场景中的 PERSON 替换为 STAFF_ID，补齐内部标识分布</td>
<td>112,352 条，替换 126,164 个实体</td>
</tr>
<tr>
<td>繁体中文转换</td>
<td>扩展 zh-TW、zh-HK、zh-MO 书写与本地化格式</td>
<td>209,435 条</td>
</tr>
<tr>
<td>日志代码注入</td>
<td>补齐 <pre class="crayon-plain-tag">LOG_BLOCK</pre>、<pre class="crayon-plain-tag">CODE_BLOCK</pre>、<pre class="crayon-plain-tag">INLINE_CODE</pre> 及内部嵌套 PII</td>
<td>新增 1,500,000 条，合并后总量 9,232,932 条</td>
</tr>
<tr>
<td>hard samples</td>
<td>补充 hard positive、hard negative、mixed difficulty 样本</td>
<td>合并后总量 10,455,978 条</td>
</tr>
</tbody>
</table>
<p>这里最关键的两个模块是日志代码注入和 hard samples。前者解决"代码和日志监督信号太弱"的问题——如果训练集里代码块只来自模板生成的干净示例，模型会把带 fenced marker 的模板块当成默认形态，遇到没有 fence 的裸日志就失效。后者解决"模型看过很多正样本，但没有真正看过边界附近的对照样本"的问题——一个 git commit hash 很像 SECRET_KEY，一个 k8s pod name 很像 CLOUD_INS_ID，但它们不是 PII。如果没有这两个模块，模型很容易在普通 prose 上看起来不错，但一遇到 stack trace、配置块和示例值就开始误报。</p>
<div class="blog_h2"><span class="graybg">去前缀</span></div>
<p>清洗和增强之后，70.7% 的实体值前面带有格式标记（"email: xxx"、"tel: xxx"、"密码：xxx"）。如果不处理，模型会学到最省力的捷径——"看到'email:'就标后面为 EMAIL"——遇到没有提示词的裸值就失效。去前缀阶段按类型强度分三档处理：</p>
<ul>
<li>FORMAT_STRONG（13 类：EMAIL、PHONE、IPV4 等格式极强的类型）：50% 的实体删除前缀标记，10% 重排分隔符到末尾。删除后模型必须从值本身的格式学习。</li>
<li>FORMAT_WEAK（25 类：PERSON、ORG、ADDRESS 等有上下文但非固定格式）：25% 删除前缀，10% 重排，10% 替换关键词。比例更保守，因为这些类型确实需要一定上下文。</li>
<li>CONTEXT_ONLY（28 类：DATE、MONEY、TIME 等）：不做任何修改。这些类型的值没有固有结构（"45"可以是年龄也可以是金额），去掉上下文标记后无法识别。</li>
</ul>
<p>处理后前缀比例从 78% 降到 71%，裸值比例从 12% 升到 19%。一个意外发现是去前缀的修改率在所有 15 种语言之间高度一致（84-87%），说明"类型标记 + 值"的写法是跨语言通用的模式，不是某种语言特有的。</p>
<div class="blog_h2"><span class="graybg">反偏差</span></div>
<p>数据增强完成后，cross-lingual QA 发现了一个隐蔽的偏差：中文文本中 EMAIL/DOMAIN/URI 实体的顶级域名高度集中在 <pre class="crayon-plain-tag">.cn</pre>（占比 91%+），而英文文本分散在 <pre class="crayon-plain-tag">.com/.co.uk/.ca</pre> 等多个域。这意味着模型可以不看实体结构，单凭周围文本的语言就猜出域名——中文上下文 → 预测 .cn，英文上下文 → 预测 .com。这是一条完全虚假的捷径。</p>
<p>修复方式是对 3725 万个 EMAIL/DOMAIN/URI 实体做 post-hoc 重随机化：用 80 CPU 并行扫描所有阶段的输出文件，对域名部分重新采样，打破语言→TLD 的相关性。处理后所有 TLD 的跨语言 spread 降到 15% 以下（最高 <pre class="crayon-plain-tag">.com</pre> 为 10.9%），确认偏差已消除。</p>
<div class="blog_h2"><span class="graybg">修复</span></div>
<p>这个项目后期最大的进展来自多次关键 Bug 修复——它们把数据一致性拉回可控状态，效果超过任何新模型。最典型的问题是 <pre class="crayon-plain-tag">val_not_in_text</pre>：28,591 个实体的 value 字段在对应的 text 中找不到。如果 entity.value 和 text 使用了不同的随机值，或者正文已经转换成新书写形式但实体值没有同步更新，模型学到的就是错误标注本身——它会试图在文本中找到一个根本不存在的字符串。</p>
<p>几次关键修复包括：为随机化后的数据加上最终 <pre class="crayon-plain-tag">value in text</pre> 过滤（直接丢弃 value 不在 text 中的实体）；修复中文书写变体增强中 text 和 entity.value 不同步的问题；在 anti-bias 重随机化时同步更新容器型实体内部嵌套的 <pre class="crayon-plain-tag">EMAIL</pre>、<pre class="crayon-plain-tag">DOMAIN</pre>、<pre class="crayon-plain-tag">URI</pre> 子串（例如 <pre class="crayon-plain-tag">user@old-domain.com</pre> 如果 domain 被替换了，entity value 也必须更新）；以及彻底移除中间文件中的 <pre class="crayon-plain-tag">start/end</pre>，把 span 统一留到训练集构建阶段恢复。整个数据管线的设计原则是让 bug 无法向下游复制，而非追求零 bug。</p>
<div class="blog_h1"><span class="graybg">模型训练</span></div>
<div class="blog_h2"><span class="graybg">多语言+代码日志增强+行业增强</span></div>
<p>当前真正落地并持续迭代的只有这一条模型线，而不是三条彼此独立的训练方向。它把多语言泛化、代码日志场景建模和行业专属增强合并到同一个训练系统里，目标是在一套数据分布和一组分层模型上同时解决语言差异、文本形态差异和行业实体差异。</p>
<ul>
<li>多语言部分覆盖 15 种语言。locale-sensitive 重采样额外补入 134,141 条国家格式差异样本，中文书写变体补入 107,032 条，繁体中文转换再补 209,435 条，目的是让同一实体类型在不同国家格式、不同脚本和不同标点系统下仍能保持稳定边界。</li>
<li>代码日志增强直接决定 chunker 是否可用。日志代码注入一次性补入 1,500,000 条样本，合并后训练集扩大到 9,232,932 条；再叠加 hard samples 后，总量达到 10,455,978 条。真正提升效果的不是总量，而是分布被拉回真实线上形态：代码块不再只有干净模板，日志不再只有短句示例，hard negative 也开始系统覆盖 hash、资源名、公开测试凭据和占位符。</li>
<li>行业增强主要覆盖云计算与企业内部标识。云计算生命周期语料扩到 3,926,300 条，把工单、故障排查、配置片段和跨厂商运维上下文一起拉进来；银行与证件子类型补齐 748,150 条，补上 SWIFT、CVV、MRN、insurance policy 等弱覆盖类型；腾讯云专属增强新增 112,352 条样本，并执行 126,164 次 PERSON 到 STAFF_ID 的定向替换，让内部员工标识在真实业务上下文中形成稳定分布。</li>
</ul>
<div class="blog_h3"><span class="graybg">模型架构</span></div>
<p>当前版本的训练系统拆成三层，其中 L3 已经完成设计，但尚未开始训练：</p>
<ul>
<li>L1 只做 chunk 定位，用来找到代码块和日志块的精确边界。</li>
<li>L2 只处理 prose（自然语言正文）中的 PII span。</li>
<li>L3 处理 code/log 内部的高风险实体，当前处于架构设计完成、训练未启动状态。</li>
</ul>
<p>这么拆是因为长连续区域检测、短 span 抽取和块内高频实体识别的统计结构并不相同，不应共享同一个问题框架：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">层级</td>
<td style="text-align: center;">目标</td>
<td style="text-align: center;">典型 span 长度</td>
<td style="text-align: center;">正负比</td>
<td style="text-align: center;">适合的框架</td>
</tr>
</thead>
<tbody>
<tr>
<td>L1 — Chunker</td>
<td>CODE_BLOCK / LOG_BLOCK 边界</td>
<td>50-500 tokens</td>
<td>21% 正</td>
<td>语义分割（per-token inside/outside）</td>
</tr>
<tr>
<td>L2 — Prose PII</td>
<td>32 种 PII 实体</td>
<td>1-12 tokens</td>
<td>有 chunk 的 token ~5-15%</td>
<td>span extraction（start/end scoring）</td>
</tr>
<tr>
<td>L3 — Inblock PII</td>
<td>代码/日志内的 14 种高频实体</td>
<td>1-8 tokens</td>
<td>~10-20% 有实体</td>
<td>从 L2 继续微调</td>
</tr>
</tbody>
</table>
<p>Backbone 也因此采用异构组合。L1 使用 ModernBERT（mmBERT，307M 参数），因为它原生支持 8192 token 上下文，并且预训练语料包含代码和日志，对长上下文的代码块边界检测更友好。L2 使用基于 mDeBERTa-v3 的 GLiNER（278M 参数），因为它处理的是更短、更密集的 token 级抽取任务，mDeBERTa 的 disentangled attention（解耦注意力）机制对 token 表征精度更优。L3 的设计也是从 L2 best checkpoint 继续微调 GLiNER，但训练计划尚未执行。</p>
<div class="blog_h3"><span class="graybg">L1架构演进</span></div>
<p>下面按时间顺序复盘 L1 Chunker 的四次架构迭代。这是整个项目中最波折的部分——一个"肉眼就能区分代码块和正文"的任务，用 300M 参数的预训练模型训了三天才真正解决。故事值得完整展开。</p>
<div class="blog_h4"><span class="graybg">BIO + CRF</span></div>
<p>最早的 L1 方案是经典的序列标注：mmBERT 编码器输出每个 token 的向量表示，接一个线性分类头把每个 token 分为 B（Begin，实体起始）、I（Inside，实体内部）、O（Outside，非实体）三类标签，最后用 CRF（条件随机场）层做解码，确保输出的标签序列满足 BIO 合法转移约束（比如 B 后面只能跟 I 或 O，不能跟另一个 B）。辅助损失使用 Focal Loss 放大稀疏 B-tag 的梯度。</p>
<p>训练配置是 3 个 epoch、LR 3e-5、warmup 5%，主指标是 chunk F1。训练日志显示，模型在 token 级别很快学会了 inside/outside 分类（token F1 长时间维持在 0.95 以上），但 chunk 级结果始终不稳定。best chunk F1@IoU≥0.8 停在 0.6159（对应 step 21K）；训练终止时已经跑到 step 103,150 / 304,083，LR 衰减到 1e-6 floor，loss 仍在 38K 到 45K 区间震荡。</p>
<p>这个结果揭示了 BIO+CRF 在长块检测中的结构性缺陷：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">问题</td>
<td style="text-align: center;">描述</td>
</tr>
</thead>
<tbody>
<tr>
<td>信号溺没</td>
<td>一个 500-token 的代码块，BIO 标注为 1 个 B + 498 个 I + 1 个 O。边界信号（B-tag）在 500:1 的 inside token 海洋中被稀释到几乎不可见。</td>
</tr>
<tr>
<td>CRF 容量有限</td>
<td>CRF 的转移矩阵只有 7x7 = 49 个参数（BIO 三种标签 × 2 种实体类型 + O），它只能建模相邻标签之间的转移概率。"B 之后需要跟多长的连续 I"这种长距离依赖，CRF 没有记忆机制来学习。</td>
</tr>
<tr>
<td>Viterbi 局部性</td>
<td>CRF 的解码算法（Viterbi）虽然是全局最优解码，但每步只看前一个标签。"一个 B 后面应该跟多少个 I"的决策被拆解成逐步累加的局部决策，边界误差会逐步累积。</td>
</tr>
<tr>
<td>LR 敏感</td>
<td>CRF 转移矩阵在高学习率下极不稳定——LR 在 warmup 期间偏高时，chunk F1 从 0.43 暴跌到 0.10 再弹回 0.36，而 token F1 基本不变。这说明 encoder 没退化，是 CRF 的 49 个转移参数被震坏了。</td>
</tr>
</tbody>
</table>
<p>结论：CRF 对短实体 NER 有价值（1-5 token 的实体，B-tag 比例合理，约 1:3-1:5），但当目标是 50-500 token 的代码块时，BIO 框架本身就不适合了。</p>
<div class="blog_h4"><span class="graybg">GlobalPointer + 1D U-Net</span></div>
<p>既然 BIO 不适合长 span，随后改用了 EfficientGlobalPointer——一种直接预测 <span displaypfx="inline-" class="mathjax-container">\((start, end)\)</span> 边界对的架构。GlobalPointer 的核心思想很简单：对序列中每一对 (token_i, token_j) 计算一个"这对 token 是否构成某类实体的起止点"的分数，然后选择分数高的作为预测结果。具体来说，它把 encoder 输出分成"query"和"key"两组向量，再用旋转位置编码（RoPE）注入相对位置信息，最终通过内积打分：</p>
<span displaypfx="" class="mathjax-container">\[s_c(i,j) = \mathrm{RoPE}(q_i^c)^\top \mathrm{RoPE}(k_j^c)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 是实体类型，<span displaypfx="inline-" class="mathjax-container">\(i, j\)</span> 分别是候选的起始和结束 token 位置。EfficientGlobalPointer 通过将 head_dim 分解为低维投影来避免显式构造 <span displaypfx="inline-" class="mathjax-container">\([B, T, T, C]\)</span> 的全尺寸矩阵，将内存从 <span displaypfx="inline-" class="mathjax-container">\(O(T^2 \times C)\)</span> 降到 <span displaypfx="inline-" class="mathjax-container">\(O(T \times d)\)</span>。</p>
<p>为了增强对长 span 的感知能力，我们在 encoder 和 GlobalPointer 之间插入了一个 1D U-Net 解码器做多尺度特征融合。训练用的是 GlobalPointer 标准的 Circle Loss（一种将正/负样本分数推向不同方向的 metric learning loss）。</p>
<p>训练跑了 50K steps，loss 从约 100 降到约 18，表面上看模型在学。但独立分析揭示了更糟的问题：best checkpoint 的 chunk F1@IoU≥0.8 只有 0.447（step 30K）。对模型输出做 threshold sweep 时发现，正样本分数的中位数约为 -0.36，负样本分数的中位数约为 -0.34——两者几乎完全重叠，模型根本没有学会区分正负 span。</p>
<p>根因分析指向三个结构性问题：</p>
<ul>
<li>长 span 的 start 和 end 语义耦合极弱。GlobalPointer 通过 start token 和 end token 的向量内积来判断它们是否构成同一实体。对于短实体，start 和 end 的上下文几乎相同，内积自然高；但一个 300-token 的代码块，起点和终点的上下文几乎没有可比性，内积和随机配对没有本质区别。</li>
<li>候选空间爆炸且极端稀疏。一个 512-token 窗口里，所有合法的 <span displaypfx="inline-" class="mathjax-container">\((start, end)\)</span> 对有 <span displaypfx="inline-" class="mathjax-container">\({512 \choose 2}=130816\)</span> 个，但其中平均只有 0.07 个是正样本，因为 79% 的记录根本没有代码块。正负比接近 1:1,876,114，任何 loss 在这个稀疏度下都会被海量负样本主导，模型学到的唯一安全策略就是所有 span 都给低分。</li>
<li>问题框架本身错误。GlobalPointer 解决的是"哪对 <span displaypfx="inline-" class="mathjax-container">\((start, end)\)</span> 构成一个离散实体"，这是 span extraction；代码块检测本质上是"哪些 token 落在同一连续区域内部"，这是语义分割。用 span enumeration 框架解决 segmentation 问题，数学对象一开始就不对。</li>
</ul>
<p>这个案例最有价值的教训是：代码块检测表面上像 NER，但其数学结构完全不同。NER 实体短、离散、一句话里有多个，span scoring 适合；代码块长、连续、极其稀疏，逐 token 分割才是正确框架。</p>
<div class="blog_h4"><span class="graybg">Segmentation + BCE</span></div>
<p>认清问题框架后，方案继续向 per-token segmentation 改写。对每个 token <span displaypfx="inline-" class="mathjax-container">\(x_i\)</span>，模型输出两个概率：</p>
<span displaypfx="" class="mathjax-container">\[P(y_i=\mathrm{CODE}\mid x_i), \quad P(y_i=\mathrm{LOG}\mid x_i)\]</span>
<p>它们分别用来判断当前位置是否落在代码块或日志块内部。这把 loss 的正负比从 1:1,876,114（全 span 枚举）降到了约 1:4（token 级别，21% 的 token 在 chunk 内）。同时保留 1D U-Net 做多尺度特征融合，新增一个 Boundary Head 预测起止边界位置。</p>
<p>这个框架已经是正确方向，但第一次实现仍然失败，主要卡在两个地方：</p>
<ul>
<li>BCE（Binary Cross-Entropy）在 7% 正样本占比下仍然会坍塌。BCE 对每个 token 计算交叉熵再取平均。当 93% 的 token 是负样本时，一个"全部预测为 outside"的模型能得到非常低的 loss（约 0.001），因为它在 93% 的 token 上损失为零。模型会发现，与其尝试预测正类，不如全部预测负类。这就是 majority class collapse。</li>
<li>训练评估函数存在严重 Bug。评估代码把 accuracy 错当成 token F1 报告给训练日志。在 93% 负样本的数据上，一个"全预测 O"的模型 accuracy 就有 0.93，加上一点随机波动就能显示 0.993。训练日志里的 <pre class="crayon-plain-tag">token_f1=0.993</pre> 让团队误以为模型已经基本解决了问题，实际上独立验证发现真实 F1 只有 0.196。</li>
</ul>
<p>对同一 checkpoint 做独立 precision/recall 分析：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">指标</td>
<td style="text-align: center;">训练日志报告</td>
<td style="text-align: center;">独立验证真实值</td>
</tr>
</thead>
<tbody>
<tr>
<td>Token F1</td>
<td>0.993</td>
<td>0.196</td>
</tr>
<tr>
<td>Precision</td>
<td>—（未拆分报告）</td>
<td>0.126</td>
</tr>
<tr>
<td>Recall</td>
<td>—（未拆分报告）</td>
<td>0.443</td>
</tr>
</tbody>
</table>
<p>这个阶段最大的收获是暴露了一个工程事实：错误的评估函数比错误的模型更危险，因为它会让团队长时间误以为模型在收敛，浪费 GPU 时间在空转上。</p>
<div class="blog_h4"><span class="graybg">Segmentation + Dice</span></div>
<p>最终稳定下来的 L1 方案仍然是 token segmentation，关键修改有三项：</p>
<ol>
<li>主损失从 BCE 换成 Dice Loss。</li>
<li>加入正负窗口平衡采样。</li>
<li>重写评估函数，独立报告 precision 和 recall。</li>
</ol>
<p>Dice Loss 的数学形式是：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Dice}(P,G)=\frac{2|P \cap G|+\epsilon}{|P|+|G|+\epsilon}, \quad \mathcal{L}_{dice}=1-\mathrm{Dice}(P,G)\]</span>
<p>其中 <span displaypfx="inline-" class="mathjax-container">\(P\)</span> 是模型预测为正的 token 集合，<span displaypfx="inline-" class="mathjax-container">\(G\)</span> 是 ground truth 中确实为正的 token 集合。Dice 系数衡量的是两个集合的重叠程度：分子 <span displaypfx="inline-" class="mathjax-container">\(2|P \cap G|\)</span> 是预测与真值共同覆盖的区域大小，分母 <span displaypfx="inline-" class="mathjax-container">\(|P| + |G|\)</span> 是双方总量之和。</p>
<p>关键性质：如果模型"全预测 outside"（<span displaypfx="inline-" class="mathjax-container">\(|P| = 0\)</span>），则 <span displaypfx="inline-" class="mathjax-container">\(\mathrm{Dice} = \epsilon / (0 + |G| + \epsilon) \approx 0\)</span>，loss = 1.0——这是最大惩罚。换句话说，Dice Loss 在数学上阻止了 majority class collapse：你不可能通过"什么都不预测"来获得低 loss。它只看预测区域和真实区域的重叠，不关心负类有多少——哪怕 99% 的 token 是负样本，只要正类的重叠不好，loss 就不会低。</p>
<p>作为对比，BCE 在"全预测 O"时近似为：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{BCE} \approx -\frac{1}{N}\sum_{i}\log(1-0)\cdot \mathbb{1}[y_i=0] = 0\]</span>
<p>这几乎等于零，因为负样本占 93%，模型可以安全地躲在"全预测 O"的角落里。</p>
<p>实际训练中使用的是组合损失：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L} = \mathcal{L}_{dice} + 0.5 \times \mathcal{L}_{BCE}\]</span>
<p>其中 BCE 分支的正类权重设为 10。Dice 负责阻止坍塌，加权 BCE 负责提供逐 token 的细粒度梯度，加速收敛。</p>
<p>正负窗口平衡采样通过 <pre class="crayon-plain-tag">neg_subsample=0.27</pre> 参数实现。训练数据中 79% 的记录没有任何 chunk（纯负样本），只保留 27% 的纯负记录参与训练，使正负记录比例接近 50:50。计算方式为 21% 正 / (21% 正 + 79% × 0.27 负) ≈ 50%。这确保每个 mini-batch 中都有足够的正样本产生有意义的梯度。</p>
<p>评估函数则被重写为分别报告 precision（预测为正的 token 中确实为正的比例）和 recall（ground truth 为正的 token 中被正确预测的比例），不再使用任何综合指标掩盖真实状况。</p>
<p>训练使用 512 context、2 卡 DDP、30K steps。best checkpoint 出现在 step 8K——Dice Loss 让模型在 250 步就开始真正学习正类（vs BCE 训练了 60K 步都只学负类）。最终指标如下：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">指标</td>
<td style="text-align: center;">数值</td>
<td style="text-align: center;">含义</td>
</tr>
</thead>
<tbody>
<tr>
<td>Token Precision</td>
<td>0.996</td>
<td>模型预测"在块内"的 token，99.6% 确实在块内</td>
</tr>
<tr>
<td>Token Recall</td>
<td>0.999</td>
<td>真正在块内的 token，99.9% 被正确识别</td>
</tr>
<tr>
<td>Token F1</td>
<td>0.997</td>
<td>逐 token 几乎完美</td>
</tr>
<tr>
<td>Chunk F1 @ IoU≥0.5</td>
<td>0.974</td>
<td>97% 的代码块/日志块被检出（宽松匹配）</td>
</tr>
<tr>
<td>Chunk F1 @ IoU≥0.8</td>
<td>0.961</td>
<td>96% 的块与 ground truth 高度对齐</td>
</tr>
<tr>
<td>Chunk Exact (≥0.95)</td>
<td>0.928</td>
<td>93% 的块几乎完全匹配</td>
</tr>
<tr>
<td>Precision @ IoU≥0.8</td>
<td>0.942</td>
<td>94% 的预测是正确的（低误报）</td>
</tr>
<tr>
<td>Recall @ IoU≥0.8</td>
<td>0.981</td>
<td>98% 的 ground truth 被找到（低漏检）</td>
</tr>
</tbody>
</table>
<p>后处理也非常简单：将输出概率的阈值设为 0.4（比默认的 0.5 更 aggressive，提高 recall），并过滤掉长度小于 10 个字符的预测块（消除噪声碎片）。最佳策略就是与训练目标一致的最小干预。模型本身已经学会了 chunk segmentation，后处理只是在修剪边角。</p>
<div class="blog_h4"><span class="graybg">完整路径回顾</span></div>
<p>把四次 L1 架构迭代放在一起，可以看到一条清晰的认知演进路径：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">版本</td>
<td style="text-align: center;">IoU≥0.8 F1</td>
<td style="text-align: center;">问题框架</td>
<td style="text-align: center;">失败原因</td>
<td style="text-align: center;">教训</td>
</tr>
</thead>
<tbody>
<tr>
<td>v1: BIO+CRF</td>
<td>0.62（不稳定）</td>
<td>序列标注</td>
<td>CRF 不适合长 span，LR 敏感</td>
<td>CRF 是短实体工具</td>
</tr>
<tr>
<td>v2: GlobalPointer</td>
<td>0.45</td>
<td>Span extraction</td>
<td>正负比 1:1.8M，start/end 耦合弱</td>
<td>问题框架错误</td>
</tr>
<tr>
<td>v3: Seg+BCE</td>
<td>0.20（虚假报告 0.99）</td>
<td>Per-token 分割</td>
<td>BCE majority collapse + eval bug</td>
<td>Loss 和 eval 都要验证</td>
</tr>
<tr>
<td>v4: Seg+Dice</td>
<td>0.961</td>
<td>Per-token 分割</td>
<td>—</td>
<td>正确的 loss + 正确的 eval</td>
</tr>
</tbody>
</table>
<p>从 v1 到 v4，代码量变化不大（核心改动只有 loss function 和 eval function），但认知变化巨大。最终方案的代码比 GlobalPointer 版本更简单——没有 bilinear scoring matrix，没有 RoPE，没有 circle loss，只有逐 token 二分类 + Dice + 阈值后处理。真正困难的地方集中在三个判断：</p>
<ol>
<li>识别出正确的问题框架，确认这是 segmentation 而不是 span extraction。</li>
<li>识别出 loss function 的坍塌模式，理解 BCE 允许"全预测 O"获得低 loss。</li>
<li>识别出 eval 在说谎，发现 accuracy 被伪装成 F1。</li>
</ol>
<div class="blog_h3"><span class="graybg">L2架构演进</span></div>
<p>L2 当前采用 GLiNER 路线。GLiNER 的核心特性是用自然语言文本描述实体类型——训练时模型不仅学习"哪些 token 是实体"，还学习"什么样的描述对应什么样的实体"。具体来说，它将类型标签（如"secret access key for cloud services"）和输入文本拼接后送入 mDeBERTa 编码器，然后用 bilinear scoring 对所有候选 span 打分。这意味着推理阶段可以不重训就尝试新标签——只要提供一段自然语言描述。</p>
<p>L2 负责 prose 中的 32 类 PII。选择 GLiNER 的理由在于它的自然语言标签和运行时扩类能力——对于仍在快速迭代的 PII 类型定义，能不重训就试新类型是很实际的需求。准确性方面它未必最优，但系统级集成的灵活性胜出。</p>
<p>工程代价同样很清楚。L2 在 per-device batch size 为 8 时，在 step 587 即触发 CUDA OOM（原因是 span scoring 矩阵的内存开销）；降到 4 之后才能稳定训练。更麻烦的是训练速度——GLiNER 的数据预处理需要对每个类型标签独立做 tokenize 和 span 矩阵构建，这是一个 Python GIL 下的串行循环，80 个 CPU 核只有 1 个在干活。训练速度约为 1.77 秒每步（4 GPU DDP），单轮完整评估接近 4 小时。</p>
<p>L2 第一轮训练（25K steps, 2 GPU）结束后，我们将训练扩展到 4 GPU 并把 max_steps 从 25K 改为 60K 重新启动。由于修改了训练参数（GPU 数量、max_steps），HuggingFace Trainer 重新计算了 cosine LR schedule，导致 LR 从第一轮末尾的 8.6e-6 跳回 2.15e-5。这等效于 SGDR（Stochastic Gradient Descent with Warm Restarts，Loshchilov 2017）中的 cosine warm restart——eval_loss 从 1.319 短暂跳到 2.879，但随着 LR 重新衰减，模型最终在 step 60K 收敛到 1.259（远优于 restart 前的 1.319）。这次由参数变更触发的 warm restart 客观上帮助模型跳出了局部最小值，找到了更优解。</p>
<div class="blog_h4"><span class="graybg">L2 Entity-Level 评估</span></div>
<p>对 checkpoint-55000 在 5000 条验证集上做 span-level 精确匹配评估（threshold=0.5），得到 micro 指标：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">Micro 指标</td>
<td style="text-align: center;">数值</td>
</tr>
</thead>
<tbody>
<tr>
<td>Precision</td>
<td>0.9935</td>
</tr>
<tr>
<td>Recall</td>
<td>0.7610</td>
</tr>
<tr>
<td>F1</td>
<td>0.8618</td>
</tr>
<tr>
<td>TP / FP / FN</td>
<td>34,650 / 228 / 10,881</td>
</tr>
</tbody>
</table>
<p>Precision 近乎完美（99.4%），说明模型标出来的实体几乎全是对的。Recall 偏低（76.1%），意味着约 24% 的真实实体被漏掉了。下面的逐类型分析揭示了漏检的结构性原因。</p>
<p>各实体类型完整 P/R/F1：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center; width: 25%">实体类型</td>
<td style="text-align: center;">P</td>
<td style="text-align: center;">R</td>
<td style="text-align: center;">F1</td>
<td style="text-align: center;">TP</td>
<td style="text-align: center;">FP</td>
<td style="text-align: center;">FN</td>
</tr>
</thead>
<tbody>
<tr>
<td>email address</td>
<td>1.000</td>
<td>0.975</td>
<td>0.987</td>
<td>3451</td>
<td>1</td>
<td>88</td>
</tr>
<tr>
<td>phone number</td>
<td>0.997</td>
<td>0.960</td>
<td>0.978</td>
<td>2123</td>
<td>6</td>
<td>89</td>
</tr>
<tr>
<td>postal code</td>
<td>0.993</td>
<td>0.893</td>
<td>0.941</td>
<td>997</td>
<td>7</td>
<td>119</td>
</tr>
<tr>
<td>person</td>
<td>0.996</td>
<td>0.868</td>
<td>0.928</td>
<td>3387</td>
<td>14</td>
<td>515</td>
</tr>
<tr>
<td>domain name</td>
<td>1.000</td>
<td>0.857</td>
<td>0.923</td>
<td>1739</td>
<td>0</td>
<td>291</td>
</tr>
<tr>
<td>user identification number</td>
<td>0.997</td>
<td>0.850</td>
<td>0.918</td>
<td>1725</td>
<td>5</td>
<td>305</td>
</tr>
<tr>
<td>ipv4 address</td>
<td>0.999</td>
<td>0.848</td>
<td>0.917</td>
<td>1865</td>
<td>1</td>
<td>335</td>
</tr>
<tr>
<td>application id</td>
<td>0.996</td>
<td>0.840</td>
<td>0.911</td>
<td>1424</td>
<td>6</td>
<td>271</td>
</tr>
<tr>
<td>bank account number</td>
<td>0.988</td>
<td>0.842</td>
<td>0.909</td>
<td>1412</td>
<td>17</td>
<td>265</td>
</tr>
<tr>
<td>uuid</td>
<td>1.000</td>
<td>0.818</td>
<td>0.900</td>
<td>1651</td>
<td>0</td>
<td>368</td>
</tr>
<tr>
<td>national id number</td>
<td>0.987</td>
<td>0.805</td>
<td>0.887</td>
<td>1322</td>
<td>18</td>
<td>320</td>
</tr>
<tr>
<td>identifier</td>
<td>0.984</td>
<td>0.806</td>
<td>0.887</td>
<td>946</td>
<td>15</td>
<td>227</td>
</tr>
<tr>
<td>staff id</td>
<td>0.998</td>
<td>0.770</td>
<td>0.870</td>
<td>643</td>
<td>1</td>
<td>192</td>
</tr>
<tr>
<td>monetary amount</td>
<td>0.982</td>
<td>0.757</td>
<td>0.855</td>
<td>1020</td>
<td>19</td>
<td>327</td>
</tr>
<tr>
<td>cloud instance id</td>
<td>0.999</td>
<td>0.739</td>
<td>0.850</td>
<td>1770</td>
<td>1</td>
<td>625</td>
</tr>
<tr>
<td>organization name</td>
<td>0.970</td>
<td>0.745</td>
<td>0.842</td>
<td>1470</td>
<td>46</td>
<td>504</td>
</tr>
<tr>
<td>api secret key</td>
<td>0.998</td>
<td>0.723</td>
<td>0.838</td>
<td>1002</td>
<td>2</td>
<td>384</td>
</tr>
<tr>
<td>urn</td>
<td>1.000</td>
<td>0.693</td>
<td>0.818</td>
<td>888</td>
<td>0</td>
<td>394</td>
</tr>
<tr>
<td>location</td>
<td>0.944</td>
<td>0.716</td>
<td>0.814</td>
<td>836</td>
<td>50</td>
<td>331</td>
</tr>
<tr>
<td>api secret id</td>
<td>1.000</td>
<td>0.682</td>
<td>0.811</td>
<td>824</td>
<td>0</td>
<td>385</td>
</tr>
<tr>
<td>json web token</td>
<td>0.993</td>
<td>0.674</td>
<td>0.803</td>
<td>694</td>
<td>5</td>
<td>336</td>
</tr>
<tr>
<td>one time password</td>
<td>0.989</td>
<td>0.675</td>
<td>0.803</td>
<td>370</td>
<td>4</td>
<td>178</td>
</tr>
<tr>
<td>password</td>
<td>0.996</td>
<td>0.666</td>
<td>0.798</td>
<td>827</td>
<td>3</td>
<td>415</td>
</tr>
<tr>
<td>address</td>
<td>0.995</td>
<td>0.541</td>
<td>0.701</td>
<td>766</td>
<td>4</td>
<td>649</td>
</tr>
<tr>
<td>uri</td>
<td>0.999</td>
<td>0.404</td>
<td>0.576</td>
<td>1305</td>
<td>1</td>
<td>1922</td>
</tr>
<tr>
<td>ipv6 address</td>
<td>0.990</td>
<td>0.156</td>
<td>0.269</td>
<td>193</td>
<td>2</td>
<td>1046</td>
</tr>
</tbody>
</table>
<div class="blog_h4"><span class="graybg">低 Recall 根因：架构硬限制</span></div>
<p>IPV6（R=0.156）、URI（R=0.404）、Address（R=0.541）三个类型的 recall 明显偏低。排查后确认这完全是 GLiNER 的两个架构硬限制导致的，模型本身已经达到了理论上限。</p>
<ul>
<li>max_width=12 的 span 宽度天花板。GLiNER 只枚举宽度 1 到 max_width 的候选 span，超过 12 个 token 的实体在数学上无法被预测。问题在于 tokenized_text 把标点拆成独立 token——一个标准 8 组 IPv6 地址（如 <pre class="crayon-plain-tag">2001:db8:e23a:0:0:0:0:1</pre>）被切成 8 个 hex 组 + 7 个冒号 = 15 个 token，永远超出 max_width=12 的限制。类似地，长 URI 的 <pre class="crayon-plain-tag">://</pre>、<pre class="crayon-plain-tag">/</pre>、<pre class="crayon-plain-tag">.</pre> 各占一个 token 位置，3 段以上路径的 URL 就超过 12 个 token。77% 的 IPv6 和 49% 的 URI 被这个限制卡死。</li>
<li>max_length=384 截断。mDeBERTa 的位置编码上限是 512，但 GLiNER 需要把 32 个类型标签的自然语言描述也塞进同一个序列（占约 128 个 token），留给实际文本的空间只有 384 个 token。超出的部分对模型不可见。31% 的 IPv6 和 22% 的 URI 实体落在截断区域之外。</li>
</ul>
<p>将两个限制叠加后计算理论上限，再与实际 recall 对比：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">实体类型</td>
<td style="text-align: center;">理论 Recall 上限</td>
<td style="text-align: center;">实际 Recall</td>
<td style="text-align: center;">模型效率</td>
</tr>
</thead>
<tbody>
<tr>
<td>ipv6 address</td>
<td>15.5%</td>
<td>15.6%</td>
<td>100%</td>
</tr>
<tr>
<td>uri</td>
<td>40.1%</td>
<td>40.4%</td>
<td>100%</td>
</tr>
<tr>
<td>address</td>
<td>54.4%</td>
<td>54.1%</td>
<td>99.5%</td>
</tr>
</tbody>
</table>
<p>模型找到了每一个架构允许找到的实体。Precision 全部 >0.99。这不是模型质量问题，是 span enumeration 架构对长实体的先天缺陷。</p>
<p>为了进一步验证这个结论，我们做了 threshold sweep——如果漏检是因为模型"不够自信"，那降低阈值应该能捞回一部分。结果：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">Threshold</td>
<td style="text-align: center;">Precision</td>
<td style="text-align: center;">Recall</td>
<td style="text-align: center;">F1</td>
</tr>
</thead>
<tbody>
<tr>
<td>0.1</td>
<td>0.969</td>
<td>0.764</td>
<td>0.854</td>
</tr>
<tr>
<td>0.3</td>
<td>0.988</td>
<td>0.764</td>
<td>0.861</td>
</tr>
<tr>
<td>0.5（默认）</td>
<td>0.994</td>
<td>0.762</td>
<td>0.863</td>
</tr>
<tr>
<td>0.7</td>
<td>0.997</td>
<td>0.759</td>
<td>0.862</td>
</tr>
</tbody>
</table>
<p>从 0.7 降到 0.1，recall 只涨了 0.5%（0.759→0.764），precision 降了 2.8%。F1 几乎不变。这说明模型对那 24% 漏掉的实体压根没有给出任何分数——它们不在候选空间中，连 0.1 分都没有。漏检完全由架构限制决定，调 threshold 无法改善。</p>
<p>解决方案有两条路，可以同时做：</p>
<ul>
<li>将 max_width 从 12 提到 16 重训。IPv6 覆盖率从 23% 提到接近 100%，显存增加 33%，bsz 需从 4 降到 3。</li>
<li>对 IPv6、URI、EMAIL 等高度结构化类型直接叠加正则 fallback 层。零训练成本，recall 直接到 0.95+。</li>
</ul>
<div class="blog_h4"><span class="graybg">训练速度瓶颈与离线预处理</span></div>
<p>GLiNER 的训练速度瓶颈在数据预处理阶段。每一步训练中，data_collator 需要对 batch 内每条记录做 mDeBERTa tokenize + span 矩阵构建 + label 映射，全部在 Python 主线程串行执行（GIL 限制，DataLoader 的 num_workers 无法帮上忙，因为重活在 collate_fn 里）。实测这部分占总步时间的 56%（~1.0s/1.77s）。80 个 CPU 核只有 1 个在干活。</p>
<p>解决办法是离线预处理：训练前一次性用 80 CPU 并行把所有记录的 text tokenization 和 span indices 预计算为 Arrow 文件。训练时 DataLoader 只做轻量的 label sampling + prompt 拼接 + padding。预处理 8.2M records 耗时约 10 分钟，产出约 130GB Arrow 文件。训练时 collate 时间从 ~1.0s 降到 ~0.04s，总步时间从 1.77s 降到 ~0.8s，训练速度提升约 2 倍。</p>
<div class="blog_h4"><span class="graybg">max_width=16 重训方案</span></div>
<p>max_width 从 12 提到 16，span representation 矩阵从 [B, L, 12, D] 变为 [B, L, 16, D]，显存增加 33%。在 24GB 的 4090 上，bsz 从 4 降到 3（配合 grad_accum 从 4 调到 6 保持等效 batch size）。训练速度因 bsz 降低而慢 25%，但结合离线预处理的 2 倍提速，实际训练时间反而缩短。推理时 max_width 的显存增量可以忽略（单条推理仅多 5MB）。</p>
<p>覆盖率变化预估：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">实体类型</td>
<td style="text-align: center;">max_width=12 覆盖率</td>
<td style="text-align: center;">max_width=16 覆盖率</td>
</tr>
</thead>
<tbody>
<tr>
<td>ipv6 address（15 tokens）</td>
<td>23%</td>
<td>~100%</td>
</tr>
<tr>
<td>uri（13+ tokens）</td>
<td>51%</td>
<td>~75%</td>
</tr>
<tr>
<td>address（13+ tokens）</td>
<td>55%</td>
<td>~70%</td>
</tr>
<tr>
<td>json web token（长 base64）</td>
<td>~60%</td>
<td>~85%</td>
</tr>
</tbody>
</table>
<p>选择 16 而非更大值是刻意的。max_width 越大，候选 span 数量线性增长，但正样本数不变——负正比从 1500:1（width=12）涨到 2000:1（width=16）、4000:1（width=32）。GLiNER 的 focal loss 在 2000:1 时仍能 hold 住收敛，但继续放大会导致正类梯度被海量负 span 稀释，收敛变慢甚至精度下降——和 L1 GlobalPointer 在 1:1.8M 正负比下崩溃是同一个机制。16 恰好覆盖 8 组 IPv6（15 token），超过 16 的 PII 实体极少（<3%），用正则 fallback 更合算。</p>
<div class="blog_h4"><span class="graybg">推理时滑动窗口</span></div>
<p>max_width 解决的是"单个实体太宽装不进候选空间"的问题，而 max_length=384 截断解决的是另一类问题："实体出现在文本靠后的位置，整段文本被截掉了"。技术文档和工单的典型结构是先描述问题，后面才贴具体地址、链接和配置——所以 IPv6、URI 这类实体倾向于出现在 word 384 之后的位置（31% 的 IPv6 和 22% 的 URI 落在截断区域外）。</p>
<p>滑动窗口在推理阶段可以解决这个问题：把一条长文本切成多个重叠窗口（例如 window=384, stride=256），每个窗口独立送入模型，然后合并预测结果。重叠区域内如果同一个 span 被多个窗口检出，取分数最高的。这样任何位置的实体都至少被一个窗口完整覆盖。</p>
<p>滑动窗口不需要重训，纯推理阶段改动。代价是推理时间乘以窗口数（通常 1.3-2 倍，取决于文本长度分布）。对于 84% 的记录（text ≤ 384 words），只跑一个窗口，无额外开销；只有 16% 的长文本需要多窗口。</p>
<p>值得注意的是，滑动窗口对 max_width 问题完全无效——一个 15 token 宽的 IPv6 不管在哪个窗口里都是 15 token，max_width=12 的模型看到它也无能为力。两个限制需要各自独立的解决方案：max_width=16 重训解决"实体太宽"，滑动窗口解决"实体位置太靠后"。</p>
<div class="blog_h4"><span class="graybg">Ensemble 方案构思</span></div>
<p>GLiNER 在短 span 语义实体（PERSON、ORG、PASSWORD、OTP 等需要上下文判断的类型）上表现很好，但对长实体和结构化实体有先天缺陷。一个自然的思路是引入互补架构，各自覆盖对方的盲区：</p>
<ul>
<li>GLiNER 负责语义模糊的短实体。这类实体的识别依赖上下文（"这个数字串是密码还是订单号"），GLiNER 的自然语言标签恰好提供了这种语义信号。</li>
<li>BIO token classification 模型负责长实体。BIO 对每个 token 独立打 B/I/O 标签，没有 max_width 限制，任意长度的 URI、IPv6、JWT、ADDRESS 都能覆盖。可以用 mmBERT（8192 ctx）作为 backbone，同时解决 max_length=384 截断问题。速度也远快于 GLiNER（单次 forward 15ms vs 350ms）。</li>
<li>正则 fallback 负责高度结构化的类型。IPv6、EMAIL、PHONE、UUID 等有固定格式，正则 recall 直接到 0.98+，零训练成本。</li>
</ul>
<p>合并逻辑很简单：三个来源取并集，同一 span 同一 type 被多个模块检出时保留 score 最高的，不同 type 冲突时也取高分。两个模型的强项几乎不重叠（GLiNER 强在语义判断，BIO 强在长序列覆盖），冲突很少。</p>
<p>这个双模型 ensemble 的预期效果是把 micro recall 从当前的 0.76 拉到 0.90 以上——正则层先解决结构化类型（+10%），BIO 模型再补上长实体（+5%），GLiNER 继续兜底语义类型。三者互补，各管一片。方案尚未实施，后续迭代中验证。</p>
<div class="blog_h3"><span class="graybg">现状与后续</span></div>
<p>到当前版本，系统形态已经明确：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">层</td>
<td style="text-align: center;">模型</td>
<td style="text-align: center;">状态</td>
<td style="text-align: center;">核心指标</td>
</tr>
</thead>
<tbody>
<tr>
<td>L1 — Chunker</td>
<td>mmBERT + 1D U-Net + Dice seg head</td>
<td>完成</td>
<td>Chunk F1@IoU≥0.8 = 0.961</td>
</tr>
<tr>
<td>L2 — Prose PII</td>
<td>GLiNER (mDeBERTa-v3-base)</td>
<td>完成（60K steps, 41h50m）</td>
<td>eval_loss: 2.879→1.259（持续下降至终点）</td>
</tr>
<tr>
<td>L3 — Inblock PII</td>
<td>GLiNER fine-tune from L2</td>
<td>已设计，未开训</td>
<td>—</td>
</tr>
</tbody>
</table>
<p>真正让系统跨过可用门槛的是三件事同时成立：训练数据不再被模板值污染（随机化 + val_not_in_text 修复），hard negative 进入文本本身（在数据层面制造对照，而非仅靠 loss 层面的负采样），L1 的 loss 与任务结构终于对齐（Dice Loss 阻止坍塌 + per-token 框架匹配 segmentation 语义）。</p>
<p>L2 训练已完成，60K steps 共耗时 41 小时 50 分钟。eval_loss 从参数变更引发的 2.879 高点单调下降至 1.259，最终 LR 衰减至 8.6e-10（cosine schedule 自然到底）。完整 eval_loss 曲线：2.879 → 2.234 → 1.995 → 1.749 → 1.442 → 1.259。从 2 GPU 扩展到 4 GPU 并延长 max_steps 时触发的 LR 重置，客观上起到了 warm restart 的作用——模型在跳出旧局部最小值后找到了更低的 loss basin。</p>
<p>后续计划包括：实现正则 fallback 层覆盖 15 个结构化类型；评估是否需要以 max_width=16 重训 L2，以解决 JWT、URN、password 等中长实体的 recall；在此基础上启动 L3 训练，专攻代码和日志块内部 PII。</p>
<p>这篇训练日志会持续更新。下一篇更新将包含正则层实现细节、L3 训练结果，以及系统级推理 pipeline 的延迟和吞吐量基准测试。</p>
<div class="blog_h1"><span class="graybg">关键洞察</span></div>
<div class="blog_h2"><span class="graybg">捷径学习</span></div>
<p>如果大多数样本都写成"邮箱：xxx""Phone: xxx""SecretKey=xxx"，模型就会优先学会前缀到类型的映射（"看到'邮箱：'就标后面为 EMAIL"），跳过对值本身结构的学习（"看到 xxx@yyy.zzz 格式就标为 EMAIL"）。这条捷径在常规文本上表现很好（大量文本确实有这样的前缀），但遇到没有任何提示词的裸值就完全失效。去前缀和分隔符重塑的目的是系统性削弱这条最容易被滥用的捷径，迫使模型学习值本身的特征。</p>
<div class="blog_h2"><span class="graybg">负样本设计</span></div>
<p>GLiNER 的 label-level negative sampling 只能制造 cross-class label negative（"这条文本里没有 EMAIL 标签"），不能制造 within-class hard negative（"这个看起来像 SECRET_KEY 的字符串其实是 git commit hash"）。前者让模型学会"这条文本里没有某个标签"，后者才决定模型能不能拒绝形似但非敏感的信息。真正提升 precision 的手段在文本层面：在训练数据里加入 git hash、k8s namespace、公开测试凭据、示例 token、配置占位符和文档样例，但不把它们标成 PII——让模型在文本级别学会区分。调 <pre class="crayon-plain-tag">--num-negatives</pre> 参数改变的只是 label-level 分布，对这类区分帮助有限。</p>
<div class="blog_h2"><span class="graybg">长上下文不是银弹</span></div>
<p>把训练 context 从 512 拉到 4096，并不会自动让模型更强。实验结果：512 训练 + 4096 推理得到 IoU≥0.8 = 0.963；直接用 4096 训练只得到 0.892。原因集中在以下两点：</p>
<ul>
<li>4096 context 让正负比更极端。512 的滑动窗口经过代码块时，单个窗口内正 token 可以占 80% 以上；4096 单 pass 则把整篇文档压进一个 sample，正 token 只占 20%。BCE 在 per-sample 正负比 1:4 时仍然容易被负类梯度拖向坍塌。</li>
<li>batch diversity 下降。受限于 GPU 显存，4096 context 只能用 bsz=4（vs 512 的 bsz=32）。同样 30K steps，512 训练看了 192 万篇文档的多样性，4096 训练只看了 24 万篇，多样性差 8 倍。对 segmentation 任务来说，数据多样性比完整上下文更重要。</li>
</ul>
<p>结论：512 训练 + 4096 推理是甜蜜点——训练阶段用短窗口获得大 batch 多样性，推理阶段用长 context 获得完整上下文。</p>
<div class="blog_h2"><span class="graybg">评估可信度</span></div>
<p>极端不平衡任务里，accuracy 没有解释力——一个"全预测 O"的模型在 93% 负样本数据上 accuracy 就有 0.93。precision 和 recall 必须拆开看，token 级和 chunk 级指标也必须拆开看。这个项目的一个直接教训是：如果训练日志只给出一个看起来很高的综合指标，而你没有独立验证评估代码（用完全不同的代码路径重新计算），那这个指标几乎不值得信任。我们在"token_f1=0.993"的虚假指标下浪费了超过 3 天 GPU 时间。</p>
<div class="blog_h2"><span class="graybg">灵活性的代价</span></div>
<p>GLiNER 的自然语言标签和 zero-shot 扩类能力很有吸引力（不重训就能试新类型），代价是训练速度（串行 Python 循环，80 CPU 只 1 个在干活）、评估速度（4 小时一次完整 eval）和推理并行能力（350ms/text vs BIO 的 15ms/text）。一个适合研究验证的模型，放到高吞吐生产环境会遇到完全不同的瓶颈。如果最终 entity F1 不达标，备选方案是 mDeBERTa + BIO（65 个标签的 token classification），速度快 23 倍且在固定类型集上可能更准。</p>
<div class="blog_h2"><span class="graybg">调参无法修复架构盲区</span></div>
<p>当模型对某些实体的 recall 偏低时，第一反应往往是降低检测阈值——"模型可能不够自信，放宽一点就能捞回来"。但 threshold sweep 给出了反直觉的结果：阈值从 0.7 降到 0.1，recall 只涨 0.5%，precision 降 2.8%。模型对那 24% 漏掉的实体没有给出任何分数，连 0.1 都没到。</p>
<p>原因是这些实体根本不在模型的候选空间里——要么超出了 span 宽度上限（15 token 的 IPv6 vs max_width=12），要么落在文本截断位置之后。对模型来说它们不存在，自然不可能给分。这个案例说明：在排查 recall 问题时，第一步应该确认漏检是"模型判断力不够"还是"架构看不到"。如果是后者，所有基于 threshold/loss/数据量的调参都是徒劳的。</p>
<div class="blog_h1"><span class="graybg">算法介绍</span></div>
<p>本节对项目中涉及的核心算法做完整技术说明，目标是让只有 AI 基础知识（了解 transformer、梯度下降、分类任务基本概念）的读者也能理解每个算法在做什么、为什么在这个场景下有效或无效。</p>
<div class="blog_h2"><span class="graybg">Focal Loss</span></div>
<p>在类别不平衡的分类任务中，标准交叉熵有一个隐蔽的问题：大量"简单"负样本（模型已经很确定它们是负的）产生的梯度累加起来，淹没了少量"困难"正样本的梯度。打个比方——一个班里 95 个学生已经会做题了，只有 5 个还不会，但老师的精力被 95 个人的答案平均分走，没有集中辅导那 5 个。Focal Loss（Lin et al., 2017）就是这个问题的解决方案：让"已经会做的学生"自动减少对老师注意力的占用。</p>
<p>数学形式：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{focal} = -\alpha_t (1-p_t)^{\gamma}\log p_t\]</span>
<p>其中：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(p_t\)</span>：模型对正确类别的预测概率（0 到 1 之间）。如果真实标签是"正"，<span displaypfx="inline-" class="mathjax-container">\(p_t\)</span> 就是模型预测为正的概率；如果真实标签是"负"，<span displaypfx="inline-" class="mathjax-container">\(p_t\)</span> 就是模型预测为负的概率。</li>
<li><span displaypfx="inline-" class="mathjax-container">\((1-p_t)^\gamma\)</span>：调制因子。模型越确信（<span displaypfx="inline-" class="mathjax-container">\(p_t\)</span> 接近 1），这个因子越接近 0，梯度越小——"已经学会的样本不再贡献梯度"。模型越不确信（<span displaypfx="inline-" class="mathjax-container">\(p_t\)</span> 小），因子接近 1，梯度保持正常——"困难样本继续贡献学习信号"。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span>：聚焦强度。<span displaypfx="inline-" class="mathjax-container">\(\gamma=0\)</span> 退化为标准交叉熵（不聚焦），<span displaypfx="inline-" class="mathjax-container">\(\gamma=2\)</span>（本项目使用值）表示中等聚焦，<span displaypfx="inline-" class="mathjax-container">\(\gamma=5\)</span> 表示极端聚焦（只学最难的样本）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\alpha_t\)</span>：类别权重，用来进一步提高少数类的权重（比如 B-tag 权重设为 3，O-tag 设为 1）。</li>
</ul>
<p>在这个项目中，Focal Loss 用在 BIO 方案里放大稀疏 B-tag 的梯度（B-tag 是困难少数类），以及 boundary head 的起止点预测（起止点极其稀疏）。它能缓解类别不平衡带来的梯度问题，但无法从根本上改变"长块只有 1 个 B-tag"的信号结构——只能作为辅助工具。</p>
<div class="blog_h2"><span class="graybg">CRF</span></div>
<p>CRF 是一种在序列标注任务中广泛使用的概率图模型。它的核心思想是：不仅考虑每个 token 独立的分类概率，还考虑相邻标签之间的转移概率。具体来说，CRF 维护一个转移矩阵 <span displaypfx="inline-" class="mathjax-container">\(A_{y_{i-1}, y_i}\)</span>，表示"前一个 token 标签为 <span displaypfx="inline-" class="mathjax-container">\(y_{i-1}\)</span> 时，当前 token 标签为 <span displaypfx="inline-" class="mathjax-container">\(y_i\)</span> 的额外分数"。训练时通过 forward algorithm 计算所有可能序列的分数之和（归一化常数），推理时通过 Viterbi 算法找到全局最优标签序列。</p>
<p>CRF 特别适合短实体 NER：一个 3 token 的人名（B-PER I-PER I-PER），CRF 能学到"B-PER 后面大概率跟 I-PER"这样的转移规则，帮助解决 encoder 偶尔的单 token 错误分类。但对于 500-token 的代码块，CRF 的 7x7 转移矩阵只有 49 个参数，它无法编码"开始了一个 B-CODE 后需要持续 500 个 I-CODE"这种长距离依赖——每步解码只看前一个标签，长距离决策被拆解成逐步累积的短距离决策，边界误差会放大。</p>
<p>此外，CRF 的转移矩阵在学习率偏高时极不稳定——因为它只有 49 个参数，每次更新对其影响远大于对 100M 参数 encoder 的影响。这解释了训练中观察到的"chunk F1 从 0.43 暴跌到 0.10"现象：token 分类能力没变（encoder 稳定），但 CRF 转移约束被破坏了（小参数集波动大）。</p>
<div class="blog_h2"><span class="graybg">GlobalPointer 与 Circle Loss</span></div>
<p>传统 NER 用序列标注（每个 token 打一个标签），GlobalPointer（Su et al., 2022）换了一个思路：直接对文本中所有可能的 (start, end) 位置对打分——分数高的就是实体。这相当于在一张二维表格上圈出实体，而非沿序列逐个标注。打分函数用旋转位置编码（RoPE）注入相对距离信息：</p>
<span displaypfx="" class="mathjax-container">\[s_c(i,j) = \mathrm{RoPE}(W_q h_i)^\top \mathrm{RoPE}(W_k h_j)\]</span>
<p>其中：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(s_c(i,j)\)</span>：位置 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 到位置 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 构成第 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 类实体的分数。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(h_i, h_j\)</span>：encoder 输出的 token 向量表示（768 维）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(W_q, W_k\)</span>：可学习的投影矩阵，分别把 <span displaypfx="inline-" class="mathjax-container">\(h\)</span> 投影为"查询"和"键"向量。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\mathrm{RoPE}\)</span>：旋转位置编码——给向量施加一个和位置相关的旋转，使得两个向量的内积天然包含它们之间距离的信息。距离越近内积越大。</li>
</ul>
<p>配套的 Circle Loss（Sun et al., 2020）负责训练。它的核心思想像一根弹簧：把正样本的分数往上拉（接近 1），把负样本的分数往下压（接近 0），两组分数之间拉开间距。数学形式：</p>
<span displaypfx="" class="mathjax-container">\[\mathcal{L}_{circle} = \log\left[1 + \sum_{(i,j)\in\Omega^+} e^{-\gamma \cdot s(i,j)} \cdot \sum_{(i,j)\in\Omega^-} e^{\gamma \cdot s(i,j)}\right]\]</span>
<p>其中：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(\Omega^+\)</span>：所有正 span（真正是实体的 (start, end) 对）的集合。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\Omega^-\)</span>：所有负 span（不是实体的 (start, end) 对）的集合。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\gamma\)</span>：温度参数（通常 64），控制分数区分的锐利程度。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(s(i,j)\)</span>：模型给 span (i,j) 打的分数。</li>
</ul>
<p>直觉：正样本分数越低，<span displaypfx="inline-" class="mathjax-container">\(e^{-\gamma \cdot s}\)</span> 越大，loss 越高——惩罚"该高分的给了低分"。负样本分数越高，<span displaypfx="inline-" class="mathjax-container">\(e^{\gamma \cdot s}\)</span> 越大，loss 越高——惩罚"该低分的给了高分"。当正负分数完全分开时（正 > 0、负 < 0），两项都趋近 0，loss 趋近 log(1)=0。</p>
<p>为什么在我们的场景下崩溃：当负样本数量是正样本的 187 万倍时，<span displaypfx="inline-" class="mathjax-container">\(\sum_{\Omega^-} e^{\gamma \cdot s}\)</span> 的梯度完全压倒了正样本项——模型学到的唯一安全策略是把所有分数压低（让负样本项变小），顺带也压低了正样本的分数。这就是为什么 threshold sweep 显示正负分数中位数只差 0.02 的根因。</p>
<div class="blog_h2"><span class="graybg">Dice Loss</span></div>
<p>Dice Loss 来自医学图像分割领域（Milletari et al., 2016），诞生的背景是：在 CT/MRI 扫描中找肿瘤时，前景（肿瘤）可能只占图像的 1-5%，其余 95-99% 是正常组织。标准 BCE loss 在这种极端不平衡下会让模型"摆烂"——直接预测"全是正常组织"就能得到很低的 loss。Dice Loss 解决这个问题的方式很像一个考试评分标准的改变：不再问"你做对了多少题"（accuracy），而是问"你圈出来的答案和标准答案重合了多少"（overlap）。</p>
<p>数学形式：</p>
<span displaypfx="" class="mathjax-container">\[\mathrm{Dice}(P,G)=\frac{2\sum_{i} p_i \cdot g_i + \epsilon}{\sum_{i} p_i + \sum_{i} g_i + \epsilon}, \quad \mathcal{L}_{dice}=1-\mathrm{Dice}(P,G)\]</span>
<p>其中：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(p_i \in [0,1]\)</span>：模型对第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 个 token 预测为正（在代码块内部）的概率。经过 sigmoid 激活。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(g_i \in \{0,1\}\)</span>：ground truth 标签。1 = 确实在代码块内，0 = 不在。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(2\sum p_i \cdot g_i\)</span>（分子）：预测和真值的"重叠量"。只有在 <span displaypfx="inline-" class="mathjax-container">\(g_i=1\)</span>（真正为正）的位置，<span displaypfx="inline-" class="mathjax-container">\(p_i\)</span> 才对分子有贡献。如果模型什么都不预测（全部 <span displaypfx="inline-" class="mathjax-container">\(p_i=0\)</span>），分子 = 0。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\sum p_i + \sum g_i\)</span>（分母）：预测的"体积"加上真值的"体积"。如果模型全预测为 0，<span displaypfx="inline-" class="mathjax-container">\(\sum p_i = 0\)</span>，分母只剩 <span displaypfx="inline-" class="mathjax-container">\(\sum g_i\)</span>（真实正样本的数量）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(\epsilon\)</span>：防止除零的小常数（通常取 1.0）。</li>
</ul>
<p>关键性质用一句话概括：如果模型"什么都不预测"（全部 <span displaypfx="inline-" class="mathjax-container">\(p_i=0\)</span>），Dice = <span displaypfx="inline-" class="mathjax-container">\(\epsilon / (\sum g_i + \epsilon) \approx 0\)</span>，loss = 1.0（最大惩罚）。模型不可能通过"摆烂"获得低 loss。而 BCE 在相同情况下 loss ≈ 0（因为 93% 的负样本都被"正确"分类了）。</p>
<p>通俗比喻：BCE 像一场选择题考试，100 道题 93 道答案是"否"——一个什么都选"否"的学生能拿 93 分。Dice 像一个画圈游戏——要求你在纸上圈出正确区域，评分标准是"你圈的面积和标准答案重合了多少"。什么都不圈 = 0 分。这就是为什么 Dice 能逼迫模型在严重不平衡数据上仍然学习正类。</p>
<p>项目中实际使用的是 per-type Dice（对 CODE_BLOCK 和 LOG_BLOCK 分别计算 Dice 再取平均），加上 0.5 倍的 pos-weighted BCE 作为辅助。BCE 提供逐 token 的精细梯度帮助模型学得更快，Dice 负责在全局层面阻止坍塌——两者互补。</p>
<div class="blog_h2"><span class="graybg">1D U-Net</span></div>
<p>U-Net 最初是为医学图像分割设计的编码器-解码器架构（Ronneberger et al., 2015）。它要解决的核心矛盾是：分割任务需要同时知道"大局"（这一整片区域是什么）和"细节"（精确到像素的边界在哪）。普通网络层数越深看得越远但丢失细节，层数越浅保留细节但看不到全局。U-Net 用 skip connection（跳跃连接）同时拥有两者：编码器每一层下采样后的特征直接连到解码器对应层的上采样输出上。</p>
<p>打个比方：阅读一篇混合了代码和正文的技术文档时，你需要两种视角——"缩小看"知道"第 3-8 段整体是代码块"（全局），"放大看"知道"第 3 段第 2 行的 ``` 是代码块开头"（边界细节）。U-Net 的下采样路径负责"缩小看"，上采样路径负责"放大看"，skip connection 让两种视角的信息直接对接。</p>
<p>在本项目中，我们将 U-Net 从 2D 图像领域迁移到 1D 序列领域：encoder 输出的 <span displaypfx="inline-" class="mathjax-container">\([B, T, 768]\)</span> 表示经过 1D 卷积下采样（768→384→192），在 bottleneck 处感受野覆盖整个代码块区域（"这里有一大片代码"的全局信号），然后上采样（192→384→768）并通过 skip connection 恢复精确的边界位置信息。</p>
<p>为什么这对代码块检测有价值：仅靠 encoder 的 self-attention，每个 token 的表示主要反映局部上下文（虽然理论感受野是全序列，但实际上 attention 分布集中在附近 token）。U-Net 的下采样路径通过 pooling 强制模型看到更大范围（"我处在一个代码区域的中间"），上采样路径通过 skip connection 保留精细边界（"第 127 个 token 是代码块结束的精确位置"）。这种多尺度融合比单一 token head 更适合需要同时感知"整体区域"和"精确边界"的任务。</p>
<div class="blog_h2"><span class="graybg">GLiNER</span></div>
<p>GLiNER（Zaratiana et al., 2023）是一种 zero-shot NER 模型，它的关键创新是用自然语言描述实体类型，而不是用固定的 label ID。工作流程如下：</p>
<ol>
<li>将类型描述（如 "personal email address"）和输入文本拼接：<pre class="crayon-plain-tag">[type1] [SEP] [type2] [SEP] ... [SEP] input text</pre></li>
<li>送入 mDeBERTa 编码器，得到每个 token 的表示。</li>
<li>用 bilinear scoring 对输入文本中的每个候选 span <span displaypfx="inline-" class="mathjax-container">\((i, j)\)</span> 和每个类型标签 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 打分。</li>
<li>选择分数超过阈值的 span 作为预测实体。</li>
</ol>
<p>对应的打分函数为：</p>
<span displaypfx="" class="mathjax-container">\[s(i, j, c) = h_i^T W h_j + h_c^T V h_i + h_c^T U h_j\]</span>
<p>其中：</p>
<ul>
<li><span displaypfx="inline-" class="mathjax-container">\(h_i, h_j\)</span>：文本中第 <span displaypfx="inline-" class="mathjax-container">\(i\)</span> 和第 <span displaypfx="inline-" class="mathjax-container">\(j\)</span> 个 token 的向量表示（来自 encoder 输出）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(h_c\)</span>：第 <span displaypfx="inline-" class="mathjax-container">\(c\)</span> 个类型标签的向量表示（如"personal email address"经过 encoder 编码后的向量）。</li>
<li><span displaypfx="inline-" class="mathjax-container">\(W, V, U\)</span>：可学习的投影矩阵。<span displaypfx="inline-" class="mathjax-container">\(W\)</span> 衡量 start 和 end token 之间的兼容性，<span displaypfx="inline-" class="mathjax-container">\(V\)</span> 衡量类型标签与 start token 的匹配度，<span displaypfx="inline-" class="mathjax-container">\(U\)</span> 衡量类型标签与 end token 的匹配度。</li>
</ul>
<p>直觉：这个公式同时问三个问题——"这两个 token 像是同一个实体的首尾吗"（第一项）、"这个 start token 像是这类实体的起点吗"（第二项）、"这个 end token 像是这类实体的终点吗"（第三项）。三个分数加起来越高，模型越确信这个 span 属于该类型。</p>
<p>这种设计让模型在推理时能理解任何自然语言描述的类型——因为 encoder 同时编码了类型语义和输入文本，两者在同一向量空间中交互。但代价是：每个类型都需要和输入文本一起送入 encoder，32 个类型 = 计算量乘以 32（实际通过 batch 化缓解，但仍然远慢于固定 head 的 BIO 模型）。</p>
<p>mDeBERTa（He et al., 2021）是 GLiNER 的 backbone 选择，其核心优势是 disentangled attention：将 content 和 position 信息分开计算 attention score（<span displaypfx="inline-" class="mathjax-container">\(A = A_{c2c} + A_{c2p} + A_{p2c}\)</span>），比标准 BERT 的混合 attention 在 token-level 任务上表现更好。这对 NER 很重要，因为 NER 需要精确到单个 token 的边界判断——content-position 分离让模型更好地区分"这个 token 是什么"和"这个 token 在哪"。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/pii-training-journal">多语言敏感信息检测模型训练日志</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/pii-training-journal/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>DevPod 远程开发环境搭建笔记</title>
		<link>https://blog.gmem.cc/devpod</link>
		<comments>https://blog.gmem.cc/devpod#comments</comments>
		<pubDate>Fri, 10 Apr 2026 07:22:22 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Cloud]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=42107</guid>
		<description><![CDATA[<p>DevPod 是一个开源的开发环境管理工具，支持在 Docker、K8s、SSH 主机及多种云平台上创建可复现的开发环境。本文记录在 K8s 集群上使用 DevPod 搭建远程开发环境的完整实践，涵盖持久卷策略、自定义镜像、文件同步、IDE 集成以及 GPU 接入中遇到的典型问题与解决方案。 DevPod 简介 DevPod 由 Loft Labs 开发，核心理念是将开发环境的定义与基础设施解耦。开发者通过 [crayon-69fbde1b148f6690937970-i/] 描述环境需求（基础镜像、工具链、端口），DevPod 负责在指定的 Provider <a class="read-more" href="https://blog.gmem.cc/devpod">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/devpod">DevPod 远程开发环境搭建笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>DevPod 是一个开源的开发环境管理工具，支持在 Docker、K8s、SSH 主机及多种云平台上创建可复现的开发环境。本文记录在 K8s 集群上使用 DevPod 搭建远程开发环境的完整实践，涵盖持久卷策略、自定义镜像、文件同步、IDE 集成以及 GPU 接入中遇到的典型问题与解决方案。</p>
<div class="blog_h1"><span class="graybg">DevPod 简介</span></div>
<p>DevPod 由 Loft Labs 开发，核心理念是将开发环境的定义与基础设施解耦。开发者通过 <pre class="crayon-plain-tag">devcontainer.json</pre> 描述环境需求（基础镜像、工具链、端口），DevPod 负责在指定的 <span style="background-color: #c0c0c0;">Provider</span> 上创建并管理对应的 Workspace。</p>
<p>三个核心概念：</p>
<ul>
<li>Provider：基础设施后端。内置支持 Docker、K8s、SSH，以及 AWS、GCP、Azure 等云平台。</li>
<li>Workspace：一个独立的开发环境实例，对应 Provider 上的一个容器或虚拟机。</li>
<li>devcontainer.json：遵循 Dev Container 规范的配置文件，定义镜像、生命周期钩子、端口转发等。</li>
</ul>
<p>与 GitHub Codespaces 和 Gitpod 相比，DevPod 的关键差异在于它是<span style="background-color: #c0c0c0;">客户端工具</span>——不依赖 SaaS 平台，可以对接任何你已有的基础设施。在自建 K8s 集群的场景下，这意味着完全掌控网络、存储和安全策略。</p>
<div class="blog_h1"><span class="graybg">K8s Provider 架构</span></div>
<p>选择 K8s 作为 Provider 时，DevPod 在目标集群中创建 Pod 来承载开发环境。整个配置由三个文件协同工作：</p>
<ol>
<li><pre class="crayon-plain-tag">devcontainer.json</pre>：声明基础镜像、工作目录、端口转发、生命周期命令。</li>
<li><pre class="crayon-plain-tag">pod-manifest.yaml</pre>：K8s Pod 模板，定义安全上下文、资源限制、卷挂载等 K8s 特有配置。</li>
<li>编排脚本（如 <pre class="crayon-plain-tag">devpod.sh</pre>）：封装 <pre class="crayon-plain-tag">devpod up</pre>、文件同步、环境初始化等流程，是胶水层。</li>
</ol>
<div class="blog_h2"><span class="graybg">Workspace 生命周期</span></div>
<p>典型的操作流程：</p>
<pre class="crayon-plain-tag"># 创建并启动 Workspace（在 K8s 中创建 Pod）
devpod up . --ide none --provider K8s

# 同步本地源码到远端
rsync -az --exclude='node_modules' ./project/ remote:/workspace/project/

# SSH 进入开发环境
devpod ssh my-workspace

# 停止（Pod 被删除，PVC 保留）
devpod stop my-workspace

# 彻底删除（Pod + PVC 全部清理）
devpod delete my-workspace</pre>
<p>关键行为：<pre class="crayon-plain-tag">devpod stop</pre> 删除 Pod 但保留 PVC（Persistent Volume Claim），下次 <pre class="crayon-plain-tag">devpod up</pre> 会重建 Pod 并挂回同一 PVC。这意味着工作区数据在 Pod 重建之间是持久的。</p>
<div class="blog_h2"><span class="graybg">多环境管理</span></div>
<p>通过编排脚本的参数区分环境，典型做法是为每个环境维护独立的 Pod Manifest：</p>
<pre class="crayon-plain-tag"># 编排脚本示例：按环境选择 Manifest 和磁盘大小
case "$ENV" in
  prod) MANIFEST="pod-manifest.yaml";      DISK="300Gi" ;;
  dev)  MANIFEST="pod-manifest-dev.yaml";   DISK="50Gi"  ;;
  test) MANIFEST="pod-manifest-test.yaml";  DISK="500Gi" ;;
esac

devpod up . --ide none \
  --provider K8s \
  --provider-option DISK_SIZE="$DISK" \
  --provider-option POD_MANIFEST="$MANIFEST"</pre>
<p>不同环境可以指定不同的节点选择器、资源配额和安全策略，而共享同一套 <pre class="crayon-plain-tag">devcontainer.json</pre> 和基础镜像。</p>
<div class="blog_h1"><span class="graybg">持久卷挂载策略</span></div>
<p>PVC 的挂载点选择直接决定了哪些数据能在 Pod 重建后存活。</p>
<div class="blog_h2"><span class="graybg">推荐：挂载到 $HOME</span></div>
<p>将 PVC 挂载到容器的 <pre class="crayon-plain-tag">$HOME</pre> 目录（如 <pre class="crayon-plain-tag">/root</pre>）是最省心的方案。好处是：</p>
<ul>
<li>IDE 的 Server 端（VS Code Server、Cursor Server）默认安装在 <pre class="crayon-plain-tag">~/.vscode-server</pre> 或 <pre class="crayon-plain-tag">~/.cursor-server</pre>，自动落在持久存储上。</li>
<li>工具链配置（<pre class="crayon-plain-tag">~/.nvm</pre>、<pre class="crayon-plain-tag">~/.local/bin</pre>）无需额外符号链接。</li>
<li>Shell 配置文件（<pre class="crayon-plain-tag">~/.bashrc</pre>）也是持久的，环境变量只需注入一次。</li>
</ul>
<p>如果挂载到其他路径（如 <pre class="crayon-plain-tag">/workspace</pre>），则需要为上述目录创建符号链接或在每次 Pod 启动时重新安装工具。</p>
<div class="blog_h2"><span class="graybg">目录布局示例</span></div>
<pre class="crayon-plain-tag">/root/                          # PVC 挂载点 = $HOME
├── .cursor-server/             # IDE Server + 扩展（持久）
│   ├── cli/                    # Server 二进制（可重建）
│   └── extensions/             # 已安装扩展（需保留）
├── .nvm/                       # Node.js 版本管理器（持久）
├── .local/bin/                 # kubectl 等工具（持久）
├── .bashrc                     # Shell 配置（持久）
├── Projects/
│   ├── my-project/             # 项目源码
│   └── shared-libs/            # 共享库
└── .config/                    # 各工具配置</pre>
<div class="blog_h1"><span class="graybg">常用命令</span></div>
<p>DevPod 通过命令行工具 <pre class="crayon-plain-tag">devpod</pre> 管理 Workspace 的完整生命周期。以下是日常开发中最常用的命令。</p>
<div class="blog_h2"><span class="graybg">Provider 管理</span></div>
<p>使用前需要先添加并配置 Provider：</p>
<pre class="crayon-plain-tag"># 添加 Kubernetes Provider
devpod provider add kubernetes

# 查看已配置的 Provider
devpod provider list

# 设置 Provider 选项（如命名空间、Pod Manifest 路径）
devpod provider set-options kubernetes \
  --option KUBERNETES_NAMESPACE=devpod \
  --option POD_MANIFEST=pod-manifest.yaml</pre>
<div class="blog_h2"><span class="graybg">Workspace 生命周期</span></div>
<pre class="crayon-plain-tag"># 创建并启动 Workspace
# --ide none 跳过 IDE 自动连接，适合脚本化流程
devpod up . --provider kubernetes --ide none

# 查看所有 Workspace 状态
devpod list

# SSH 进入 Workspace
devpod ssh my-workspace

# 停止 Workspace（删除 Pod，保留 PVC）
devpod stop my-workspace

# 彻底删除（Pod + PVC 全部清理）
devpod delete my-workspace</pre>
<p>关键行为：<pre class="crayon-plain-tag">stop</pre> 只删除 Pod，PVC 上的数据（IDE 扩展、工具链、源码）全部保留。下次 <pre class="crayon-plain-tag">up</pre> 会重建 Pod 并挂回同一 PVC，环境几乎瞬间恢复。</p>
<div class="blog_h2"><span class="graybg">常用 Provider 选项</span></div>
<p>Kubernetes Provider 支持通过 <pre class="crayon-plain-tag">--provider-option</pre> 传递额外参数：</p>
<pre class="crayon-plain-tag">devpod up . --provider kubernetes --ide none \
  --provider-option DISK_SIZE=100Gi \
  --provider-option POD_MANIFEST=pod-manifest-test.yaml \
  --provider-option KUBERNETES_NAMESPACE=devpod</pre>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%;">选项</td>
<td>说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>DISK_SIZE</td>
<td>PVC 容量，如 50Gi、300Gi。</td>
</tr>
<tr>
<td>POD_MANIFEST</td>
<td>自定义 Pod Manifest 文件路径。</td>
</tr>
<tr>
<td>KUBERNETES_NAMESPACE</td>
<td>Pod 创建的目标命名空间。</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">状态检查与调试</span></div>
<pre class="crayon-plain-tag"># 查看 Workspace 详细状态
devpod status my-workspace

# 直接查看底层 Pod 状态（需要 kubectl 访问同一集群）
kubectl get pod -n devpod -l app=devpod

# 查看 Pod 事件（排查启动失败）
kubectl describe pod my-workspace -n devpod</pre>
<div class="blog_h1"><span class="graybg">配置详解</span></div>
<p><pre class="crayon-plain-tag">devcontainer.json</pre> 是 Dev Container 规范的核心配置文件，定义了开发环境的镜像、生命周期钩子、端口转发、IDE 定制等一切参数。DevPod 完整支持该规范。文件通常位于 <pre class="crayon-plain-tag">.devcontainer/devcontainer.json</pre>。</p>
<p>以下是一个面向 Kubernetes 远程开发的完整示例：</p>
<pre class="crayon-plain-tag">{
  "name": "my-workspace",

  // 预装全部工具的自定义镜像，省去 onCreateCommand 等待
  "image": "registry.example.com/dev/ubuntu:22.04-tools",

  // 工具已烘焙进镜像，跳过首次创建命令
  "onCreateCommand": "true",

  // PVC 挂载到 $HOME（/root），IDE 配置和扩展天然持久化
  // workspaceMount 故意留空——DevPod v0.6.x 的 .devpodignore 存在已知 bug，
  // 大型单仓库会被全量上传。改用自定义 rsync 同步源码。
  "workspaceFolder": "/root",

  "customizations": {
    "vscode": {
      "extensions": [
        "ms-python.python",
        "ms-python.vscode-pylance",
        "ms-python.debugpy",
        "redhat.vscode-yaml",
        "ms-kubernetes-tools.vscode-kubernetes-tools"
      ],
      "settings": {
        "python.defaultInterpreterPath": "/usr/local/bin/python",
        "editor.formatOnSave": true,
        "terminal.integrated.defaultProfile.linux": "bash"
      }
    }
  },

  "forwardPorts": [8000, 8080, 5432, 6379],
  "portsAttributes": {
    "8000": { "label": "API Server" },
    "8080": { "label": "Web UI" },
    "5432": { "label": "PostgreSQL", "onAutoForward": "silent" },
    "6379": { "label": "Redis", "onAutoForward": "silent" }
  },
  "otherPortsAttributes": {
    "onAutoForward": "silent"
  }
}</pre>
<div class="blog_h2"><span class="graybg">镜像与构建</span></div>
<p>指定容器基础镜像有两种方式：直接引用镜像或通过 Dockerfile 构建。</p>
<p><pre class="crayon-plain-tag">image</pre> 字段接受任何 OCI 镜像地址（DockerHub、GHCR、私有仓库均可）。对于 Kubernetes 远程开发，推荐<span style="background-color: #c0c0c0;">预构建镜像</span>而非运行时构建——将所有开发工具烘焙进镜像可以将 Pod 启动时间从分钟级缩短到秒级。</p>
<p>如果需要在镜像基础上定制，可以使用 <pre class="crayon-plain-tag">build</pre> 字段：</p>
<pre class="crayon-plain-tag">{
  "build": {
    "dockerfile": "Dockerfile",
    "context": "..",
    "args": {
      "PYTHON_VERSION": "3.11"
    }
  }
}</pre>
<p><pre class="crayon-plain-tag">context</pre> 默认为 <pre class="crayon-plain-tag">"."</pre>（即 <pre class="crayon-plain-tag">devcontainer.json</pre> 所在目录）。设为 <pre class="crayon-plain-tag">".."</pre> 可以在 Dockerfile 中引用项目根目录的文件。</p>
<div class="blog_h2"><span class="graybg">workspaceFolder 与 workspaceMount</span></div>
<p><pre class="crayon-plain-tag">workspaceFolder</pre> 定义 IDE 连接后默认打开的目录。在 Kubernetes 场景下，建议将其设为 PVC 的挂载点（如 <pre class="crayon-plain-tag">/root</pre>），使工作区与持久存储完全对齐。</p>
<p><pre class="crayon-plain-tag">workspaceMount</pre> 控制本地源码如何挂载到容器。在本地 Docker 场景下它很有用，但在 Kubernetes 远程开发中通常<span style="background-color: #c0c0c0;">故意留空</span>。原因是 DevPod v0.6.x 存在一个已知问题（<a href="https://github.com/loft-sh/devpod/issues/1885">#1885</a>）：<pre class="crayon-plain-tag">.devpodignore</pre> 在流式上传本地仓库时被忽略，导致大型工作区（包括 venv、node_modules 等）被全量上传。更好的做法是使用自定义 rsync 脚本精确控制同步内容。</p>
<div class="blog_h2"><span class="graybg">生命周期钩子</span></div>
<p>Dev Container 规范定义了六个生命周期钩子，按以下顺序执行：</p>
<pre class="crayon-plain-tag">initializeCommand     # 在宿主机上执行（每次启动）
  ↓
onCreateCommand       # 容器首次创建后执行（仅一次）
  ↓
updateContentCommand  # 源码更新后执行（至少一次）
  ↓
postCreateCommand     # 分配给用户后执行（可访问用户密钥）
  ↓
postStartCommand      # 每次容器启动后执行
  ↓
postAttachCommand     # 每次 IDE 连接后执行</pre>
<p>每个钩子都接受三种格式：</p>
<ul>
<li>字符串：通过 <pre class="crayon-plain-tag">/bin/sh</pre> 执行。</li>
<li>数组：直接执行，不经过 shell（更安全）。</li>
<li>对象：多个命名命令<span style="background-color: #c0c0c0;">并行执行</span>，适合同时启动多个服务。</li>
</ul>
<pre class="crayon-plain-tag">{
  "postAttachCommand": {
    "api-server": "cd /root/api &amp;&amp; python -m uvicorn main:app --port 8000",
    "worker": "cd /root/worker &amp;&amp; python -m celery -A tasks worker"
  }
}</pre>
<p>实践建议：</p>
<ul>
<li>如果所有工具已烘焙进镜像，将 <pre class="crayon-plain-tag">onCreateCommand</pre> 设为 <pre class="crayon-plain-tag">"true"</pre> 跳过。</li>
<li><pre class="crayon-plain-tag">postStartCommand</pre> 适合放启动时的环境检查或服务预热。</li>
<li><pre class="crayon-plain-tag">waitFor</pre> 字段控制 IDE 在哪个阶段之后才开始连接，默认为 <pre class="crayon-plain-tag">"updateContentCommand"</pre>。</li>
</ul>
<div class="blog_h2"><span class="graybg">IDE 定制</span></div>
<p><pre class="crayon-plain-tag">customizations.vscode</pre> 下可以声明扩展和设置，IDE 连接后自动应用：</p>
<pre class="crayon-plain-tag">"customizations": {
  "vscode": {
    "extensions": [
      "ms-python.python",
      "ms-python.vscode-pylance",
      "ms-python.debugpy",
      "redhat.vscode-yaml",
      "ms-kubernetes-tools.vscode-kubernetes-tools"
    ],
    "settings": {
      "python.defaultInterpreterPath": "/usr/local/bin/python",
      "editor.formatOnSave": true,
      "terminal.integrated.defaultProfile.linux": "bash"
    }
  }
}</pre>
<p><pre class="crayon-plain-tag">extensions</pre> 中声明的扩展会在首次连接时自动安装到远端。结合 PVC 持久化，后续连接无需重复安装。<pre class="crayon-plain-tag">settings</pre> 中的配置优先级高于用户本地设置，确保团队成员使用一致的编辑器行为。</p>
<div class="blog_h2"><span class="graybg">端口转发</span></div>
<p><pre class="crayon-plain-tag">forwardPorts</pre> 声明的端口会在 IDE 连接后自动转发到本地。容器内的服务在这些端口上启动时，本地浏览器可以直接通过 <pre class="crayon-plain-tag">localhost:port</pre> 访问，无需任何手动设置。</p>
<p><pre class="crayon-plain-tag">portsAttributes</pre> 为每个端口配置显示名称和行为：</p>
<pre class="crayon-plain-tag">"forwardPorts": [8000, 8080, 5432, 6379],
"portsAttributes": {
  "8000": { "label": "API Server" },
  "8080": { "label": "Web UI", "onAutoForward": "openBrowser" },
  "5432": { "label": "PostgreSQL", "onAutoForward": "silent" },
  "6379": { "label": "Redis", "onAutoForward": "silent" }
},
"otherPortsAttributes": {
  "onAutoForward": "silent"
}</pre>
<p><pre class="crayon-plain-tag">onAutoForward</pre> 控制端口首次被检测到时的行为：<pre class="crayon-plain-tag">"notify"</pre>（默认，弹通知）、<pre class="crayon-plain-tag">"openBrowser"</pre>（自动打开浏览器）、<pre class="crayon-plain-tag">"silent"</pre>（静默转发，适合数据库等后台服务）、<pre class="crayon-plain-tag">"ignore"</pre>（完全忽略）。<pre class="crayon-plain-tag">otherPortsAttributes</pre> 为未显式配置的端口设置默认行为。</p>
<div class="blog_h2"><span class="graybg">环境变量</span></div>
<p>Dev Container 规范区分两层环境变量：</p>
<ul>
<li><pre class="crayon-plain-tag">containerEnv</pre>：设置在容器本身上，所有进程可见，容器生命周期内不变（修改需重建）。</li>
<li><pre class="crayon-plain-tag">remoteEnv</pre>：仅对 IDE 启动的进程（终端、任务、调试）可见，可以引用 <pre class="crayon-plain-tag">${containerEnv:VAR}</pre> 来扩展已有变量，修改后无需重建容器。</li>
</ul>
<pre class="crayon-plain-tag">{
  "containerEnv": {
    "PYTHONPATH": "/root/libs/common:/root/libs/shared"
  },
  "remoteEnv": {
    "PATH": "${containerEnv:PATH}:/root/.local/bin"
  }
}</pre>
<p>两个字段都支持 <pre class="crayon-plain-tag">${localEnv:VAR}</pre> 语法引用宿主机环境变量，例如 <pre class="crayon-plain-tag">${localEnv:HOME}</pre>。</p>
<div class="blog_h2"><span class="graybg">Features</span></div>
<p>Dev Container Features 是可复用的 Dockerfile 片段，以 OCI 制品形式分发。通过 <pre class="crayon-plain-tag">features</pre> 字段可以在不修改基础镜像的情况下安装额外工具：</p>
<pre class="crayon-plain-tag">{
  "features": {
    "ghcr.io/devcontainers/features/docker-in-docker:2": {},
    "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {
      "version": "latest"
    },
    "ghcr.io/devcontainers/features/node:1": {
      "version": "22"
    }
  }
}</pre>
<p>可用的 Features 列表参见 <a href="https://containers.dev/features">containers.dev/features</a>。对于 Kubernetes 远程开发，推荐将工具烘焙进基础镜像而非依赖 Features，以避免每次创建容器时的安装延迟。Features 更适合本地 Docker 场景下的快速原型搭建。</p>
<div class="blog_h2"><span class="graybg">容器行为控制</span></div>
<p>几个影响容器运行方式的字段：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%;">字段</td>
<td style="width: 20%;">默认值</td>
<td>说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>overrideCommand</td>
<td>true</td>
<td>覆盖容器默认命令为无限循环（保持容器存活）。使用自定义镜像时通常保持默认。</td>
</tr>
<tr>
<td>shutdownAction</td>
<td>stopContainer</td>
<td>IDE 关闭时的行为：stopContainer（停止容器）、none（保持运行）。K8s 场景建议 none。</td>
</tr>
<tr>
<td>init</td>
<td>false</td>
<td>使用 tini 作为 init 进程，处理僵尸进程回收。</td>
</tr>
<tr>
<td>privileged</td>
<td>false</td>
<td>特权模式。Docker 场景下通过此字段设置，K8s 场景在 Pod Manifest 中设置。</td>
</tr>
<tr>
<td>containerUser</td>
<td>root 或 Dockerfile USER</td>
<td>容器内所有操作使用的用户。</td>
</tr>
<tr>
<td>remoteUser</td>
<td>同 containerUser</td>
<td>IDE 终端和任务使用的用户，可以与 containerUser 不同。</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">预定义变量</span></div>
<p><pre class="crayon-plain-tag">devcontainer.json</pre> 的字符串值中可以使用以下预定义变量：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 40%;">变量</td>
<td>含义</td>
</tr>
</thead>
<tbody>
<tr>
<td>${localEnv:VAR_NAME}</td>
<td>宿主机环境变量，支持默认值：${localEnv:VAR:default}</td>
</tr>
<tr>
<td>${containerEnv:VAR_NAME}</td>
<td>容器环境变量（仅在 remoteEnv 中可用）</td>
</tr>
<tr>
<td>${localWorkspaceFolder}</td>
<td>宿主机上打开的工作区路径</td>
</tr>
<tr>
<td>${containerWorkspaceFolder}</td>
<td>容器内的工作区路径</td>
</tr>
<tr>
<td>${devcontainerId}</td>
<td>容器的唯一标识符，重建后保持稳定</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">镜像定制</span></div>
<p>Dev Container 的 <pre class="crayon-plain-tag">image</pre> 字段虽然可以填写任意公共镜像，但在 Kubernetes 远程开发场景下，<span style="background-color: #c0c0c0;">应当构建专用的基础镜像，将所有开发工具、语言运行时和系统库固化到镜像层中</span>。这样做的好处是：</p>
<ul>
<li>Pod 启动即可用，无需等待 <pre class="crayon-plain-tag">onCreateCommand</pre> 安装依赖。</li>
<li>环境一致性有保障——团队成员共享同一镜像，不会因安装顺序或网络问题导致环境差异。</li>
<li>Pod 重建后工具链自动恢复，不依赖外部包管理器的可用性。</li>
</ul>
<div class="blog_h2"><span class="graybg">Dockerfile 分层原则</span></div>
<p>合理的分层可以提高构建缓存命中率：变更频率低的工具放在底层，变更频率高的放在上层。每个 <pre class="crayon-plain-tag">RUN</pre> 指令末尾执行 <pre class="crayon-plain-tag">apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</pre> 减小层体积，安装时使用 <pre class="crayon-plain-tag">--no-install-recommends</pre> 避免拉入不必要的依赖。</p>
<p>以下示例构建了一个包含 Python 3.11、常用系统工具和 NVIDIA CUDA 运行时的开发镜像：</p>
<pre class="crayon-plain-tag">FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive

# Layer 1: 系统工具 + Python 3.11 + 所有 PPA（在切换默认 Python 之前添加）
RUN apt-get update &amp;&amp; \
    apt-get install -y --no-install-recommends \
      software-properties-common gnupg2 wget curl ca-certificates &amp;&amp; \
    add-apt-repository -y ppa:deadsnakes/ppa &amp;&amp; \
    add-apt-repository -y ppa:graphics-drivers/ppa &amp;&amp; \
    wget -qO /tmp/cuda-keyring.deb \
      https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb &amp;&amp; \
    dpkg -i /tmp/cuda-keyring.deb &amp;&amp; rm /tmp/cuda-keyring.deb &amp;&amp; \
    apt-get update &amp;&amp; \
    apt-get install -y --no-install-recommends \
      python3.11 python3.11-venv python3.11-dev python3-pip \
      git make vim jq postgresql-client \
      openssh-server procps iproute2 iputils-ping \
      rsync htop telnet &amp;&amp; \
    update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1 &amp;&amp; \
    update-alternatives --install /usr/bin/python  python  /usr/bin/python3.11 1 &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

# Layer 2: NVIDIA 驱动工具（nvidia-smi 等）
RUN apt-get update &amp;&amp; \
    apt-get install -y --no-install-recommends nvidia-utils-580-server &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

# Layer 3: CUDA 运行时库（独立层，便于单独更新）
RUN apt-get update &amp;&amp; \
    apt-get install -y --no-install-recommends cuda-libraries-12-8 &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*</pre>
<p>几个关键设计决策：</p>
<ul>
<li>所有 PPA 和 GPG 密钥在 Layer 1 中、<pre class="crayon-plain-tag">update-alternatives</pre> 之前添加。切换默认 Python 后，<pre class="crayon-plain-tag">add-apt-repository</pre> 会因 <pre class="crayon-plain-tag">apt_pkg</pre> 模块绑定系统原生 Python 而报错 <pre class="crayon-plain-tag">No module named 'apt_pkg'</pre>。</li>
<li>NVIDIA 驱动工具和 CUDA 库分别放在独立层中。这样更新驱动版本时只需重建 Layer 2，不影响 Layer 1 的缓存。</li>
<li>安装 <pre class="crayon-plain-tag">nvidia-utils-xxx-server</pre> 而非 <pre class="crayon-plain-tag">nvidia-utils-xxx</pre>。后者在 Ubuntu 仓库中是过渡空壳包，不包含实际的 <pre class="crayon-plain-tag">nvidia-smi</pre> 二进制。</li>
<li>选择 <pre class="crayon-plain-tag">cuda-libraries-12-8</pre>（运行时库，约 1.2 GB）而非 <pre class="crayon-plain-tag">cuda-toolkit-12-8</pre>（完整工具包，约 10 GB）。开发环境通常只需要运行时库来执行 CUDA 程序，不需要编译器和调试器。</li>
</ul>
<div class="blog_h2"><span class="graybg">镜像与 devcontainer.json 的配合</span></div>
<p>当所有工具都已烘焙进镜像后，<pre class="crayon-plain-tag">devcontainer.json</pre> 可以极大简化：</p>
<pre class="crayon-plain-tag">{
  "image": "registry.example.com/dev/ubuntu:22.04-cuda12.8",
  "onCreateCommand": "true",
  "workspaceFolder": "/root"
}</pre>
<p><pre class="crayon-plain-tag">onCreateCommand</pre> 设为 <pre class="crayon-plain-tag">"true"</pre> 表示跳过——因为没有需要在容器首次启动时安装的东西。Pod 创建后立即可用。</p>
<div class="blog_h1"><span class="graybg">Pod规格定制</span></div>
<p>Pod Manifest 是 K8s Provider 的核心配置，控制着 DevPod 无法通过 <pre class="crayon-plain-tag">devcontainer.json</pre> 表达的 K8s 原生能力。</p>
<div class="blog_h2"><span class="graybg">模板变量</span></div>
<p>DevPod 在创建 Pod 前会对 Manifest 进行模板渲染，支持以下占位符：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%;">变量</td>
<td>含义</td>
</tr>
</thead>
<tbody>
<tr>
<td>{{.WorkspaceId}}</td>
<td>Workspace 名称，用作 Pod 名和标签。</td>
</tr>
<tr>
<td>{{.Image}}</td>
<td>devcontainer.json 中声明的镜像地址。</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">安全上下文</span></div>
<p>远程开发容器通常需要比生产容器更宽松的权限。常见配置项及其用途：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%;">配置</td>
<td>用途</td>
<td>风险</td>
</tr>
</thead>
<tbody>
<tr>
<td>privileged: true</td>
<td>Docker-in-Docker、设备访问、调试工具</td>
<td>容器可访问宿主内核全部能力</td>
</tr>
<tr>
<td>SYS_ADMIN</td>
<td>mount、cgroup 操作</td>
<td>中等</td>
</tr>
<tr>
<td>SYS_PTRACE</td>
<td>strace、gdb 等调试</td>
<td>低</td>
</tr>
<tr>
<td>NET_ADMIN</td>
<td>网络调试、iptables</td>
<td>中等</td>
</tr>
<tr>
<td>hostNetwork: true</td>
<td>直接使用宿主网络栈，避免 CNI 开销</td>
<td>端口冲突、网络隔离丧失</td>
</tr>
<tr>
<td>hostPID: true</td>
<td>查看宿主进程，便于系统级调试</td>
<td>进程隔离丧失</td>
</tr>
</tbody>
</table>
<p>原则：开发环境按需放宽权限，但仍应限定在专用命名空间和节点上，避免影响生产负载。</p>
<div class="blog_h2"><span class="graybg">资源声明</span></div>
<pre class="crayon-plain-tag">resources:
  requests:
    cpu: "500m"
    memory: "1Gi"
  limits:
    cpu: "16"
    memory: "64Gi"</pre>
<p><pre class="crayon-plain-tag">requests</pre> 设低一些确保 Pod 能调度成功，<pre class="crayon-plain-tag">limits</pre> 设高一些保留突发空间。开发环境通常不会持续占满资源，但编译、测试时可能有短暂峰值。</p>
<div class="blog_h1"><span class="graybg">文件同步策略</span></div>
<div class="blog_h2"><span class="graybg">DevPod 默认同步 vs 自定义 rsync</span></div>
<p>DevPod 内置了基于 <pre class="crayon-plain-tag">devpod up</pre> 的文件同步机制，对于小型项目效果良好。但在大型多仓库工作区（数十个子项目、上百万文件）下，默认同步存在两个问题：</p>
<ul>
<li>首次同步耗时极长，且无法精细控制排除规则。</li>
<li>DevPod 会尝试上传整个 <pre class="crayon-plain-tag">workspaceFolder</pre> 内容，包括 <pre class="crayon-plain-tag">node_modules</pre>、<pre class="crayon-plain-tag">.git</pre> 等不需要的目录。</li>
</ul>
<p>解决方案是使用 <pre class="crayon-plain-tag">--ide none</pre> 启动 DevPod（跳过 IDE 自动同步），然后用自定义的 rsync 命令精确控制同步内容。</p>
<div class="blog_h2"><span class="graybg">存根目录技巧</span></div>
<p>即使使用 <pre class="crayon-plain-tag">--ide none</pre>，DevPod 仍会在 <pre class="crayon-plain-tag">devpod up</pre> 阶段尝试同步 <pre class="crayon-plain-tag">workspaceFolder</pre> 对应的本地目录。如果该目录很大，首次 up 会非常慢。一个技巧是在 up 之前临时创建一个空的存根目录来替代：</p>
<pre class="crayon-plain-tag">STUB_DIR=$(mktemp -d)
devpod up "$STUB_DIR" --ide none --provider K8s ...
rm -rf "$STUB_DIR"
# 然后用 rsync 同步真正的源码</pre>
<div class="blog_h2"><span class="graybg">rsync 实践</span></div>
<pre class="crayon-plain-tag">SSH_CMD="ssh my-workspace.devpod"

rsync -az \
  --exclude='node_modules' \
  --exclude='.git' \
  --exclude='__pycache__' \
  --exclude='venv' \
  --exclude='.venv' \
  --exclude='dist' \
  --exclude='.next' \
  --exclude='.temp' \
  --exclude='.logs' \
  --exclude='.vscode/sessions.json' \
  --copy-unsafe-links \
  ./my-project/ my-workspace.devpod:/root/Projects/my-project/</pre>
<p>关键参数说明：</p>
<ul>
<li><pre class="crayon-plain-tag">-az</pre>：归档模式 + 压缩传输。不要加 <pre class="crayon-plain-tag">--progress</pre>，大量小文件时进度输出会拖慢 SSH 管道，甚至导致 Broken pipe。</li>
<li><pre class="crayon-plain-tag">--copy-unsafe-links</pre>：将指向同步目录之外的符号链接（Symbolic Link）解引用为实际文件。在多仓库工作区中，项目间的符号链接（如共享 Skills 目录）在远端无法解析，此选项可以自动将其替换为文件副本。</li>
<li><pre class="crayon-plain-tag">--exclude</pre>：排除所有不需要同步的目录。<pre class="crayon-plain-tag">.vscode/sessions.json</pre> 会频繁变更且与远端状态冲突，应排除。</li>
</ul>
<div class="blog_h1"><span class="graybg">IDE 远程连接</span></div>
<p>VS Code 和 Cursor 的远程开发功能通过在容器内安装一个 Server 端（Remote Extension Host）来工作。IDE 通过 SSH 隧道与 Server 通信。</p>
<div class="blog_h2"><span class="graybg">Server 安装机制</span></div>
<p>IDE 的 Server 端与客户端版本严格绑定（通过 commit hash 匹配）。安装流程通常是：</p>
<ol>
<li>从本地客户端获取当前版本的 commit hash。</li>
<li>下载对应版本的 Server 二进制包。</li>
<li>通过 SSH 传输并解压到远端的 <pre class="crayon-plain-tag">~/.cursor-server/cli/servers/Stable-{commit}/</pre>。</li>
</ol>
<p>编排脚本应实现幂等的安装检查：</p>
<pre class="crayon-plain-tag">COMMIT=$(get_ide_commit_hash)
SERVER_BIN="$HOME/.cursor-server/cli/servers/Stable-$COMMIT/server/bin/code-server"

if $SSH_CMD "test -x $SERVER_BIN"; then
  echo "Server already installed"
else
  # 下载并安装 Server
  install_ide_server "$COMMIT"
fi</pre>
<div class="blog_h2"><span class="graybg">扩展持久化</span></div>
<p>IDE 扩展安装在 <pre class="crayon-plain-tag">~/.cursor-server/extensions/</pre>（或 <pre class="crayon-plain-tag">~/.vscode-server/extensions/</pre>）。当 PVC 挂载到 <pre class="crayon-plain-tag">$HOME</pre> 时，扩展天然持久。</p>
<p>一个常见的陷阱是在重新安装 Server 时误删整个 <pre class="crayon-plain-tag">~/.cursor-server</pre> 目录，导致扩展全部丢失。正确做法是<span style="background-color: #c0c0c0;">只清理 Server 二进制目录</span>：</p>
<pre class="crayon-plain-tag"># 错误：会删除扩展
rm -rf ~/.cursor-server

# 正确：只删除 Server 二进制，保留扩展
rm -rf ~/.cursor-server/cli</pre>
<div class="blog_h2"><span class="graybg">扩展批量同步</span></div>
<p>首次设置远端环境时，可以将本地已安装的扩展批量同步到远端，避免逐个从 Marketplace 下载：</p>
<pre class="crayon-plain-tag">rsync -az \
  ~/.cursor-server/extensions/ \
  my-workspace.devpod:~/.cursor-server/extensions/</pre>
<p>同步后需要检查扩展中是否有断裂的符号链接。某些扩展包含指向本地 Node.js 路径的符号链接，在远端无法解析。修复方式是用实际文件替换：</p>
<pre class="crayon-plain-tag"># 在远端查找断裂的符号链接
find ~/.cursor-server/extensions/ -type l ! -exec test -e {} \; -print

# 对每个断裂链接，用目标文件的副本替换
# （需要从本地获取原始文件）</pre>
<div class="blog_h2"><span class="graybg">首次连接缓慢</span></div>
<p>首次通过 IDE 连接远端 Workspace 时，通常需要 30 秒到数分钟。这是因为 IDE 需要：</p>
<ul>
<li>建立 SSH 隧道（DevPod 的 SSH 代理有一定开销）。</li>
<li>下载并安装 Server 端（如果尚未安装）。</li>
<li>初始化所有已安装的扩展。</li>
</ul>
<p>后续连接会快很多，因为 Server 和扩展都已在 PVC 上就绪。</p>
<div class="blog_h1"><span class="graybg">K8s 容器中的 GPU 接入</span></div>
<p>在 K8s 中使用 GPU 需要多个组件协同工作：节点上的驱动、设备插件（Device Plugin）、容器运行时钩子。任何一层配置不当都会导致容器内看不到 GPU 设备。</p>
<div class="blog_h2"><span class="graybg">NVIDIA 设备插件的工作原理</span></div>
<p>NVIDIA 提供的 <span style="background-color: #c0c0c0;">Device Plugin</span> 以 DaemonSet 形式运行在每个 GPU 节点上，向 K8s 注册 <pre class="crayon-plain-tag">nvidia.com/gpu</pre> 扩展资源。Pod 通过在 <pre class="crayon-plain-tag">resources.limits</pre> 中声明 GPU 数量来请求分配：</p>
<pre class="crayon-plain-tag">resources:
  limits:
    nvidia.com/gpu: "4"
  requests:
    nvidia.com/gpu: "4"</pre>
<p>调度器根据 <pre class="crayon-plain-tag">requests</pre> 选择有足够 GPU 的节点，设备插件负责将具体的 GPU 设备（<pre class="crayon-plain-tag">/dev/nvidia0</pre> 等）注入到容器中。</p>
<div class="blog_h2"><span class="graybg">runtimeClassName: nvidia</span></div>
<p>仅声明 GPU 资源不够。K8s 还需要知道使用哪个<span style="background-color: #c0c0c0;">容器运行时</span>来处理 GPU 设备的挂载。这通过 Pod 的 <pre class="crayon-plain-tag">runtimeClassName</pre> 字段指定：</p>
<pre class="crayon-plain-tag">spec:
  runtimeClassName: nvidia
  containers:
    - name: devpod
      # ...</pre>
<p>如果不指定 <pre class="crayon-plain-tag">runtimeClassName</pre>，即使 Pod 获得了 GPU 资源配额，容器运行时也不会调用 NVIDIA 的 prestart hook，导致 <pre class="crayon-plain-tag">/dev/nvidia*</pre> 设备节点不会出现在容器内。这是最常见的 GPU 接入失败原因之一。</p>
<div class="blog_h2"><span class="graybg">AppArmor 拦截</span></div>
<p>一个容易忽略的事实是：<pre class="crayon-plain-tag">privileged: true</pre> 并不等同于 AppArmor <pre class="crayon-plain-tag">unconfined</pre>。在启用了 AppArmor 的节点上，即使容器以特权模式运行，默认的 AppArmor profile（如 <pre class="crayon-plain-tag">cri-containerd.apparmor.d</pre>）仍然可能阻止容器访问 GPU 设备节点。</p>
<p>解决方式是在 Pod 的 <pre class="crayon-plain-tag">metadata.annotations</pre> 中显式声明 AppArmor 为 <pre class="crayon-plain-tag">unconfined</pre>：</p>
<pre class="crayon-plain-tag">metadata:
  annotations:
    container.apparmor.security.beta.K8s.io/devpod: unconfined</pre>
<p>其中 <pre class="crayon-plain-tag">devpod</pre> 是容器名称。该 annotation 需要与容器名精确匹配。</p>
<div class="blog_h2"><span class="graybg">NVIDIA_VISIBLE_DEVICES 陷阱</span></div>
<p>直觉上可能会在 Pod Manifest 中设置环境变量 <pre class="crayon-plain-tag">NVIDIA_VISIBLE_DEVICES=all</pre> 来暴露所有 GPU。然而，当与 <pre class="crayon-plain-tag">runtimeClassName: nvidia</pre> 同时使用时，<span style="background-color: #c0c0c0;">手动设置此变量会干扰设备插件的自动注入逻辑</span>。</p>
<p>NVIDIA Container Runtime 的行为是：</p>
<ul>
<li>如果 <pre class="crayon-plain-tag">NVIDIA_VISIBLE_DEVICES</pre> 由设备插件注入，运行时会根据该值精确挂载对应设备。</li>
<li>如果用户在 Manifest 中手动设置了 <pre class="crayon-plain-tag">NVIDIA_VISIBLE_DEVICES=all</pre>，该值会覆盖设备插件的注入，导致运行时在设备映射阶段产生冲突。</li>
</ul>
<p>正确做法是<span style="background-color: #c0c0c0;">不要手动设置 <pre class="crayon-plain-tag">NVIDIA_VISIBLE_DEVICES</pre></span>，让设备插件自动管理。可以保留 <pre class="crayon-plain-tag">NVIDIA_DRIVER_CAPABILITIES=all</pre> 来开放全部驱动能力（compute、utility、graphics 等）。</p>
<div class="blog_h2"><span class="graybg">容器内的 nvidia-smi</span></div>
<p><pre class="crayon-plain-tag">nvidia-smi</pre> 是验证 GPU 可用性的首选工具。在容器中安装它有一个陷阱：某些 Linux 发行版的官方仓库中，名为 <pre class="crayon-plain-tag">nvidia-utils-xxx</pre> 的包是<span style="background-color: #c0c0c0;">过渡空壳包</span>（transitional dummy package），安装后不包含实际的 <pre class="crayon-plain-tag">nvidia-smi</pre> 二进制文件。</p>
<p>以 Ubuntu 22.04 为例，正确的做法是：</p>
<ol>
<li>添加 <pre class="crayon-plain-tag">ppa:graphics-drivers/ppa</pre>。</li>
<li>安装 <pre class="crayon-plain-tag">nvidia-utils-xxx-server</pre>（注意 <pre class="crayon-plain-tag">-server</pre> 后缀），这个包包含实际的命令行工具。</li>
</ol>
<p>如果不便修改镜像，临时方案是通过 <pre class="crayon-plain-tag">hostPath</pre> 挂载宿主机的驱动库和工具到容器内：</p>
<pre class="crayon-plain-tag">volumeMounts:
  - name: host-root
    mountPath: /host
    readOnly: true
volumes:
  - name: host-root
    hostPath:
      path: /</pre>
<p>然后在容器启动后将 <pre class="crayon-plain-tag">/host/usr/lib/x86_64-linux-gnu</pre> 加入 <pre class="crayon-plain-tag">LD_LIBRARY_PATH</pre>，直接调用 <pre class="crayon-plain-tag">/host/usr/bin/nvidia-smi</pre>。这是临时手段，长期方案应将驱动工具烘焙进镜像。</p>
<div class="blog_h2"><span class="graybg">NVML Unknown Error 排查路径</span></div>
<p>当 <pre class="crayon-plain-tag">nvidia-smi</pre> 报出 <pre class="crayon-plain-tag">Failed to initialize NVML: Unknown Error</pre> 时，按以下顺序排查：</p>
<ol>
<li>AppArmor：检查 Pod annotation 是否设置为 <pre class="crayon-plain-tag">unconfined</pre>。用 <pre class="crayon-plain-tag">cat /proc/1/attr/current</pre> 确认容器实际 profile。</li>
<li>设备节点：检查 <pre class="crayon-plain-tag">ls /dev/nvidia*</pre> 是否存在。如果不存在，问题在运行时或设备插件。</li>
<li>运行时类：确认 Pod spec 中是否设置了 <pre class="crayon-plain-tag">runtimeClassName: nvidia</pre>，以及集群中是否存在对应的 RuntimeClass 资源。</li>
<li>环境变量：检查 <pre class="crayon-plain-tag">NVIDIA_VISIBLE_DEVICES</pre> 是否被手动覆盖。</li>
<li>驱动版本：确认容器内的 NVIDIA 用户态库版本与宿主机内核驱动版本兼容。</li>
</ol>
<div class="blog_h1"><span class="graybg">常见故障排查</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%;">现象</td>
<td style="width: 30%;">原因</td>
<td>解决</td>
</tr>
</thead>
<tbody>
<tr>
<td>Pod 进入 Dead / Failed 状态</td>
<td>OOM、节点问题或配置错误</td>
<td>devpod stop → 修复 Manifest → devpod up。PVC 数据不丢。</td>
</tr>
<tr>
<td>SSH exit code 255</td>
<td>Pod 未就绪或 SSH 隧道中断</td>
<td>检查 Pod 状态，等待 Running 后重试。若 Server 安装中断，手动重执行安装脚本。</td>
</tr>
<tr>
<td>rsync 报 Broken pipe</td>
<td>大量文件的进度输出压垮 SSH 管道</td>
<td>使用 rsync -az，不加 --progress 或 --info=progress2。</td>
</tr>
<tr>
<td>add-apt-repository 报 No module named 'apt_pkg'</td>
<td>默认 Python 被切换，apt_pkg 绑定旧版本</td>
<td>在 update-alternatives 之前完成所有 PPA 添加。</td>
</tr>
<tr>
<td>IDE 扩展在 Pod 重建后丢失</td>
<td>Server 重装脚本误删了 extensions 目录</td>
<td>只清理 cli/ 子目录，保留 extensions/。</td>
</tr>
<tr>
<td>nvidia-smi: command not found</td>
<td>安装了空壳过渡包</td>
<td>从 ppa:graphics-drivers/ppa 安装 nvidia-utils-xxx-server。</td>
</tr>
<tr>
<td>NVML Unknown Error</td>
<td>AppArmor / 运行时类 / 设备注入 / 环境变量</td>
<td>按 AppArmor → 设备节点 → runtimeClassName → 环境变量的顺序逐层排查。</td>
</tr>
<tr>
<td>/dev/nvidia* 不存在</td>
<td>缺少 runtimeClassName: nvidia 或设备插件未运行</td>
<td>确认 RuntimeClass 资源存在且 DaemonSet 正常运行。</td>
</tr>
</tbody>
</table>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/devpod">DevPod 远程开发环境搭建笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/devpod/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>OpenClaw学习笔记</title>
		<link>https://blog.gmem.cc/claw</link>
		<comments>https://blog.gmem.cc/claw#comments</comments>
		<pubDate>Wed, 01 Apr 2026 12:05:22 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[AI]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=41991</guid>
		<description><![CDATA[<p>四个月，343,000 颗星 2025 年 11 月 24 日，一个名为 OpenClaw 的开源项目在 GitHub 上悄然创建。四个月后，它的 Star 数突破 343,000，成为近年来增速最快的非聚合类开源项目之一，超过了 React、Vue 和 Tailwind CSS 在同等时间段内的增长速度。这条陡峭的增长曲线背后，是一个清晰的价值主张：让每个人都能在自己的设备上运行一个完全属于自己的 AI 助手。 <a class="read-more" href="https://blog.gmem.cc/claw">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/claw">OpenClaw学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">四个月，343,000 颗星</span></div>
<p>2025 年 11 月 24 日，一个名为 <span style="background-color: #c0c0c0;">OpenClaw</span> 的开源项目在 GitHub 上悄然创建。四个月后，它的 Star 数突破 343,000，成为近年来增速最快的非聚合类开源项目之一，超过了 React、Vue 和 Tailwind CSS 在同等时间段内的增长速度。这条陡峭的增长曲线背后，是一个清晰的价值主张：让每个人都能在自己的设备上运行一个完全属于自己的 AI 助手。</p>
<p>OpenClaw 的一句话定位是 <em>"Your own personal AI assistant. Any OS. Any Platform. The lobster way. "</em>。这句话的核心不在于功能描述，而在于哲学立场——<span style="background-color: #c0c0c0;">本地优先（Local-First）</span>不是一个功能选项，而是整个系统的设计前提。在 AI 助手普遍依赖云端服务的 2025 年，这个立场击中了开发者群体中日益强烈的数据主权（Data Sovereignty）诉求。</p>
<p>"The lobster way" 不只是一句口号。龙虾（Lobster）意象贯穿了整个项目：工作流编排工具命名为 Lobster，社区成员自称 lobster，甚至 GitHub 上的口号 "EXFOLIATE! EXFOLIATE!"（蜕皮！蜕皮！）暗示项目的激进重构和快速迭代哲学——龙虾通过周期性蜕壳实现生长，OpenClaw 通过频繁的破坏性变更实现架构进化。</p>
<p>从工程视角看，OpenClaw 的核心技术栈——TypeScript ESM、pnpm monorepo、230 条 Plugin SDK 导出路径、24 个消息渠道接入——构成了一个在单一代码仓库中管理 AI Agent 平台全生命周期的案例研究。无论是 Rust/Go 工具替代传统 JS 工具链（oxfmt 替代 Prettier、oxlint 替代 ESLint、tsdown 替代 webpack）、TypeScript 原生 Go 编译器预览集成、还是 Docker 多阶段构建中对 QEMU 交叉编译的优雅降级处理——这些工程细节比功能特性更能说明项目的技术深度。</p>
<p>项目采用 <span style="background-color: #c0c0c0;">MIT 协议</span>，赞助商包括 OpenAI、NVIDIA、Vercel、Blacksmith 和 Convex——一家云端 AI 公司赞助一个本地优先的开源竞争者，这本身就是值得深思的战略信号。本文基于 OpenClaw 官方仓库（<a href="https://github.com/openclaw/openclaw">github.com/openclaw/openclaw</a>）v2026.4.1 的实际代码，从仓库结构、插件架构、渠道系统、Agent 运行时、内存系统、安全模型到多端原生应用，进行完整的技术剖析。所有代码引用均来自一手数据，不依赖二手社区资料。</p>
<div class="blog_h2"><span class="graybg">四层架构概览</span></div>
<p>在深入代码之前，先建立全局视角。OpenClaw 的整体系统可以压缩为四层：</p>
<ul>
<li><span style="background-color: #c0c0c0;">Gateway（控制平面）</span>：以 WebSocket 服务（默认 ws://127.0.0.1:18789）承载会话管理、配置下发、Cron 调度、Webhook 和健康检查，同时托管 Control UI（Lit 3 + Vite）和 Canvas 宿主（A2UI）。</li>
<li><span style="background-color: #c0c0c0;">Agent / Pi Runtime（智能体运行时）</span>：基于 @mariozechner/pi-agent-core@0.64.0，以 RPC 模式运行，支持工具流（Tool Streaming）和块流（Block Streaming），接入 25+ 模型提供者，并具备 Auth 轮换与 Failover 能力。</li>
<li><span style="background-color: #c0c0c0;">Channels + Skills（渠道与技能层）</span>：覆盖 24 个消息平台，通过 Plugin SDK 的 230 条契约路径与核心交互；ClawHub 市场、before_install 安全钩子，以及 Browser、Canvas、Nodes、Cron、Sessions 等一等工具都位于这一层。</li>
<li><span style="background-color: #c0c0c0;">Memory（记忆层）</span>：由 memory-core 的 13 个子模块组成，以本地 Markdown 文件持久化、sqlite-vec 向量搜索和 LanceDB 为存储后端，承载用户可编辑偏好与长期上下文。</li>
</ul>
<p>Gateway 是唯一的控制平面入口，所有客户端（CLI、Web UI、macOS App、iOS/Android 节点）都通过 WebSocket 连接到 Gateway。Agent 运行时以 RPC 模式挂载在 Gateway 下，接收来自各渠道的消息，调用模型和工具，将结果路由回发送者所在的渠道。Skills 和 Channels 通过 Plugin SDK 的 230 条导出路径与核心交互，Memory 层则为 Agent 提供跨会话的长期记忆能力。</p>
<p>这四层的每一层都有严格的边界约束。Gateway 层通过 src/gateway/protocol/schema.ts 定义类型化 WebSocket 协议，Agent 层通过 Pi 的 RPC 接口暴露能力，Plugin SDK 是扩展与核心之间唯一合法的导入面，Memory 层通过 13 个细粒度子模块避免单体耦合。下文将逐层展开。</p>
<div class="blog_h1"><span class="graybg">项目历史与版本</span></div>
<div class="blog_h2"><span class="graybg">MoltBot → ClawdBot → OpenClaw 的命名演变</span></div>
<p>OpenClaw 并非从零开始。在当前命名之前，它先后经历了 <span style="background-color: #c0c0c0;">MoltBot</span> 和 <span style="background-color: #c0c0c0;">ClawdBot</span> 两个阶段。这段历史在代码库中留有痕迹：package.json 的 scripts 字段至今保留着 "moltbot:rpc" 命令，指向与 "openclaw:rpc" 完全相同的实现。文档域名 docs.molt.bot 也仍以 301 重定向的方式指向当前的 docs.openclaw.ai。</p>
<p>项目由奥地利开发者 <span style="background-color: #c0c0c0;">Peter Steinberger</span>（GitHub: @steipete）主导，其在仓库中拥有 14,756 个提交，远超第二贡献者（1,690 个）。Steinberger 此前以 iOS SDK 生态的贡献著称，转型为 AI Agent 平台开发者，并将高频迭代、激进重构的风格延续到了 OpenClaw 的开发中。</p>
<div class="blog_h2"><span class="graybg">日历版本号与三频道发布策略</span></div>
<p>OpenClaw 采用日历版本（Calendar Versioning）而非语义版本（Semantic Versioning）。版本格式为 vYYYY.M.D（例如 v2026.4.1），直接反映发布日期。当同一天有多个发布时，追加补丁后缀 vYYYY.M.D-N。</p>
<p>发布频道分为三层，通过 npm dist-tag 映射：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">频道</td>
<td style="text-align: center;">npm dist-tag</td>
<td style="text-align: center;">Tag 格式</td>
<td style="text-align: center;">适用场景</td>
</tr>
</thead>
<tbody>
<tr>
<td>stable</td>
<td>latest</td>
<td>vYYYY.M.D</td>
<td>生产环境，默认安装</td>
</tr>
<tr>
<td>beta</td>
<td>beta</td>
<td>vYYYY.M.D-beta.N</td>
<td>预发布验证，macOS App 可能缺席</td>
</tr>
<tr>
<td>dev</td>
<td>dev</td>
<td>main 分支头</td>
<td>开发调试，按需发布</td>
</tr>
</tbody>
</table>
<p>切换频道使用 openclaw update --channel stable|beta|dev。Beta 发布时，npm 版本号必须携带 -beta.N 后缀，不能使用不带后缀的版本号配合 --tag beta 发布，否则版本名会被消耗掉——这是仓库 AGENTS.md 中明确记录的发布守则。</p>
<div class="blog_h2"><span class="graybg">v2026.3.31：一次密集的破坏性变更</span></div>
<p>截至本文写作时，最新的 stable 版本为 v2026.3.31（2026-03-31 发布）。该版本包含 <strong>六项破坏性变更（Breaking Changes）</strong>，密度之高反映了 OpenClaw 激进的迭代风格：</p>
<ol>
<li><strong>Nodes/exec 重构</strong>：移除了 CLI 和 Agent nodes 工具中重复的 nodes.run shell 封装器，所有节点 shell 执行统一走 exec host=node 路径。节点特有能力保留在 nodes invoke 和专用的 media/location/notify 操作上。</li>
<li><strong>Plugin SDK 遗留路径弃用</strong>：弃用旧版 provider 兼容子路径以及旧版 bundled provider setup 和 channel-runtime 兼容垫片（compatibility shims），发出迁移警告。当前文档化的 openclaw/plugin-sdk/* 入口加上本地 api.ts / runtime-api.ts 桶文件是唯一的前进路径。</li>
<li><strong>插件安装安全收紧</strong>：内建的危险代码（dangerous-code）critical 级发现和安装时扫描失败现在<strong>默认拒绝</strong>（fail closed）。此前能成功安装的部分插件现在需要显式指定 --dangerously-force-unsafe-install 标志才能继续。</li>
<li><strong>Gateway 认证收紧</strong>：trusted-proxy 模式拒绝混合共享令牌配置；本地直连回退（local-direct fallback）要求使用配置的令牌，不再隐式认证同主机调用者。</li>
<li><strong>节点命令门控</strong>：节点命令在节点配对获批（node pairing approved）之前保持禁用状态。仅完成设备配对不再足以暴露声明的节点命令。</li>
<li><strong>节点事件信任面缩减</strong>：节点发起的运行（node-originated runs）现在在缩减的受信表面（reduced trusted surface）上执行，依赖更广泛 host/session 工具访问权限的通知驱动或节点触发流程可能需要调整。</li>
</ol>
<p>这种"每个版本都可能有 Breaking Changes"的策略与日历版本号的选择相呼应——既然不提供语义化的兼容性承诺，那就用发布日期明确标识每一次快照。对于使用者，官方建议锁定到特定版本号并在升级前阅读 CHANGELOG。</p>
<div class="blog_h2"><span class="graybg">v2026.3.28：xAI 深度整合与安全强化</span></div>
<p>前一个重要版本 v2026.3.28（2026-03-29 发布）同样包含多项重要变更：</p>
<ul>
<li><strong>xAI 深度整合</strong>：bundled xAI provider 迁移至 Responses API，新增原生 x_search（Grok 网页搜索工具），并在 openclaw onboard 中集成可选的 x_search 配置步骤。</li>
<li><strong>MiniMax 图像生成</strong>：新增 MiniMax image-01 模型的图像生成和图生图编辑能力，支持宽高比控制。</li>
<li><strong>Qwen 认证迁移</strong>：移除已弃用的 qwen-portal-auth OAuth 集成，迁移至 Model Studio API Key 模式。</li>
<li><strong>Plugins/hooks 审批机制</strong>：before_tool_call 钩子新增异步 requireApproval 能力，插件可以暂停工具执行并通过 Telegram 按钮、Discord 交互、/approve 命令等渠道提示用户审批。</li>
<li><strong>Microsoft Teams 升级</strong>：迁移至官方 Teams SDK，支持 1:1 对话的流式回复和 AI 标注。</li>
<li><strong>Gateway OpenAI 兼容性</strong>：新增 /v1/models 和 /v1/embeddings 端点，使 Gateway 可以被 OpenAI 兼容的第三方工具直接调用。</li>
</ul>
<div class="blog_h1"><span class="graybg">仓库结构</span></div>
<div class="blog_h2"><span class="graybg">顶层目录布局</span></div>
<p>OpenClaw 是一个 pnpm workspace monorepo，根目录的核心布局如下：</p>
<pre class="crayon-plain-tag">openclaw/
├── src/           # 核心源码
│   ├── cli/       # CLI 命令入口与进度条
│   ├── commands/  # 各子命令实现
│   ├── gateway/   # Gateway 控制平面（含 protocol/ 子目录）
│   ├── channels/  # 核心渠道实现
│   ├── routing/   # 消息路由
│   ├── plugins/   # 插件发现、加载、注册
│   ├── plugin-sdk/# 公开插件契约（唯一合法导入面）
│   ├── infra/     # 基础设施（SQLite、文件锁等）
│   └── media/     # 媒体处理管道
├── apps/
│   ├── macos/     # SwiftUI + AppKit 菜单栏应用
│   ├── ios/       # Xcode + SwiftUI
│   └── android/   # Kotlin + Gradle
├── extensions/    # 内部扩展（bundled plugin workspace tree）
├── packages/      # 共享包
├── skills/        # 内置 Skills（随 npm 包分发）
├── ui/            # Web Control UI（Lit 3 + Vite）
├── docs/          # Mintlify 文档
├── test/          # E2E 测试
└── scripts/       # 构建/发布/检查脚本（60+个）</pre>
<p>这棵目录树揭示了 OpenClaw 的工程哲学：<span style="background-color: #c0c0c0;">核心尽量薄，边界尽量硬</span>。src/ 存放所有 TypeScript 核心代码，extensions/ 存放内建扩展（bundled plugin workspace tree），apps/ 存放三个原生客户端。三者之间的导入关系是单向的：extensions/ 只能通过 openclaw/plugin-sdk/* 调用核心能力，apps/ 通过 Gateway WebSocket 协议与核心通信。任何反向依赖都会被 CI 的架构守卫脚本拦截。</p>
<div class="blog_h2"><span class="graybg">extensions/ 与 packages/ 的区分</span></div>
<p>extensions/ 是 <span style="background-color: #c0c0c0;">bundled plugin workspace tree</span>——即随 npm 包一起发布的内建扩展。Matrix、Zalo、ZaloUser、Voice Call 等渠道插件以及诊断遥测（diagnostics-otel）都存放在此。每个扩展是一个独立的 pnpm workspace 包，拥有自己的 package.json 和 openclaw.plugin.json 清单文件。扩展的运行时依赖必须声明在自身的 dependencies 中，不能添加到根 package.json（除非核心也使用了同一依赖）。而 workspace:* 在 dependencies 中是被禁止的（因为 npm install 无法解析 workspace 协议），openclaw 本身应放入 devDependencies 或 peerDependencies，运行时通过 jiti 别名解析 openclaw/plugin-sdk。</p>
<p>packages/ 存放的则是纯粹的共享库包，不含插件清单，不走插件加载管线。它们提供跨包复用的工具函数和类型定义。</p>
<div class="blog_h2"><span class="graybg">skills/ 与 docs/ 的角色</span></div>
<p>skills/ 目录存放<span style="background-color: #c0c0c0;">内置 Skills（Bundled Skills）</span>——它们随 npm 包一起分发，安装后即可使用。与 ClawHub 上的第三方 Skill 不同，内置 Skill 不需要 clawhub install，也不走 before_install 安全检查管线。每个 Skill 由一个 SKILL.md 文件描述，该文件在 Agent 运行时被注入到系统提示中。</p>
<p>docs/ 使用 <span style="background-color: #c0c0c0;">Mintlify</span> 框架构建，部署在 docs.openclaw.ai。文档内部链接采用根相对路径（如 [Config](/configuration)），不带 .md 扩展名。文档支持中文翻译，中文版位于 docs/zh-CN/，由 scripts/docs-i18n 脚本自动生成，辅以术语表 docs/.i18n/glossary.zh-CN.json 和翻译记忆 docs/.i18n/zh-CN.tm.jsonl 保证术语一致性。</p>
<div class="blog_h2"><span class="graybg">scripts/ — 60+ 个构建与运维脚本</span></div>
<p>scripts/ 目录包含 60 多个独立脚本文件，加上 package.json 中的 198 个 npm scripts 入口，构成了 OpenClaw 极其精细的构建自动化体系。脚本按用途可分为以下几类：</p>
<ul>
<li><strong>构建脚本</strong>：tsdown-build.mjs（主构建入口）、runtime-postbuild.mjs（构建后处理）、bundle-a2ui.sh（Canvas A2UI 打包）、ui.js（Web UI 构建）</li>
<li><strong>代码检查脚本</strong>：check-extension-plugin-sdk-boundary.mjs（扩展导入边界检查，三种模式）、check-plugin-extension-import-boundary.mjs（核心不得反向导入扩展内部）、check-no-pairing-store-group-auth.mjs（安全认证审计）</li>
<li><strong>发布脚本</strong>：openclaw-npm-release-check.ts（发布前校验）、plugin-npm-release-plan.ts（插件发布计划）、openclaw-npm-postpublish-verify.ts（发布后验证）</li>
<li><strong>平台脚本</strong>：package-mac-app.sh（macOS 打包）、ios-configure-signing.sh（iOS 签名）、build-release-aab.ts（Android AAB 构建）</li>
<li><strong>测试脚本</strong>：test-parallel.mjs（并行测试编排器）、test-live.mjs（真实 API Key 测试）、8 个 e2e/*.sh Docker E2E 测试场景</li>
<li><strong>运维脚本</strong>：committer（原子提交工具，取代手动 git add/commit）、restart-mac.sh（macOS Gateway 重启）、clawlog.sh（macOS 统一日志查询）</li>
</ul>
<div class="blog_h2"><span class="graybg">依赖版本：47 个运行时 + 22 个开发时</span></div>
<p>OpenClaw 的依赖控制极为精简。根 package.json 仅声明 <span style="background-color: #c0c0c0;">47 个运行时依赖</span>和 <span style="background-color: #c0c0c0;">22 个开发时依赖</span>。关键依赖的版本锁定如下：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">依赖</td>
<td style="text-align: center;">版本</td>
<td style="text-align: center;">用途</td>
</tr>
</thead>
<tbody>
<tr>
<td>@mariozechner/pi-agent-core</td>
<td>0.64.0</td>
<td>Agent 运行时核心</td>
</tr>
<tr>
<td>@agentclientprotocol/sdk</td>
<td>0.17.1</td>
<td>ACP 协议 SDK</td>
</tr>
<tr>
<td>@modelcontextprotocol/sdk</td>
<td>1.29.0</td>
<td>MCP 协议 SDK</td>
</tr>
<tr>
<td>matrix-js-sdk</td>
<td>41.3.0-rc.0</td>
<td>Matrix 渠道</td>
</tr>
<tr>
<td>playwright-core</td>
<td>1.58.2</td>
<td>浏览器控制</td>
</tr>
<tr>
<td>sqlite-vec</td>
<td>0.1.9</td>
<td>向量存储</td>
</tr>
<tr>
<td>sharp</td>
<td>^0.34.5</td>
<td>图像处理</td>
</tr>
<tr>
<td>hono</td>
<td>4.12.9</td>
<td>HTTP 框架</td>
</tr>
<tr>
<td>express</td>
<td>^5.2.1</td>
<td>兼容层</td>
</tr>
<tr>
<td>zod</td>
<td>^4.3.6</td>
<td>运行时校验</td>
</tr>
<tr>
<td>ws</td>
<td>^8.20.0</td>
<td>WebSocket</td>
</tr>
<tr>
<td>undici</td>
<td>^7.24.6</td>
<td>HTTP 客户端</td>
</tr>
</tbody>
</table>
<p>开发依赖中最值得关注的是 @typescript/native-preview@7.0.0-dev.20260331.1——这是 TypeScript 官方的 Go 语言重写版预览，OpenClaw 已将其集成为 pnpm tsgo 命令。vitest@4.1.2 搭配 @vitest/coverage-v8 提供覆盖率检测，tsdown@0.21.7 取代 webpack/rollup 作为打包器，oxfmt@0.43.0 和 oxlint@1.58.0 则分别替代 Prettier 和 ESLint。这套工具链的选型思路清晰：用 Rust/Go 编写的原生工具替换 JavaScript 编写的传统方案，以获得数量级的性能提升。</p>
<p>所有带有 pnpm.patchedDependencies 的依赖必须使用精确版本号（不允许 ^ 或 ~ 前缀），且依赖补丁需要显式审批。此外，仓库明确规定"永远不要更新 Carbon 依赖"——这是一条写入 AGENTS.md 的硬性规则。</p>
<div class="blog_h1"><span class="graybg">核心目录</span></div>
<p>上一章给出了 src/ 的一级目录骨架。本章逐一展开每个子目录的内部设计，以代码结构和依赖关系为主线，解释 OpenClaw 核心源码的工程分层。</p>
<div class="blog_h2"><span class="graybg">src/cli/ — CLI 命令入口与进度渲染</span></div>
<p>src/cli/ 是整个 OpenClaw 命令行工具的入口层。它不包含任何业务逻辑，只负责两件事：解析命令行参数并路由到 src/commands/ 中的具体实现，以及在终端渲染结构化进度反馈。</p>
<p>进度反馈的核心位于 src/cli/progress.ts。该模块同时使用两套机制：</p>
<p>第一套是 <span style="background-color: #c0c0c0;">OSC 进度序列</span>（Operating System Command Progress Sequences）。这是一组终端转义码，允许在支持 ConPTY 的 Windows Terminal、iTerm2 和部分 Linux 终端中直接在标题栏或标签页上显示百分比进度条。progress.ts 通过向 stdout 写入 \x1b]9;4;1;{percent}\x07 序列驱动操作系统级别的进度指示器，这使得即使终端窗口被最小化，用户仍可在任务栏中看到安装进度。</p>
<p>第二套是 <span style="background-color: #c0c0c0;">@clack/prompts</span>，一个轻量级的交互式终端 UI 库。OpenClaw 用它实现 onboard 向导中的步骤指示器、多选菜单和确认提示。@clack/prompts 的 spinner 与 OSC 进度可以并行工作——spinner 渲染在 stdout 的当前行，OSC 序列渲染在终端标题栏，两者互不干扰。</p>
<pre class="crayon-plain-tag">// src/cli/progress.ts 核心逻辑简化示意
import { spinner } from '@clack/prompts';

export function emitOscProgress(percent: number): void {
  process.stdout.write(`\x1b]9;4;1;${Math.round(percent)}\x07`);
}

export function clearOscProgress(): void {
  process.stdout.write(`\x1b]9;4;0;\x07`);
}

export async function withProgress(
  label: string,
  task: (update: (pct: number) =&gt; void) =&gt; Promise
): Promise {
  const s = spinner();
  s.start(label);
  const result = await task((pct) =&gt; {
    emitOscProgress(pct);
    s.message(`${label} (${pct}%)`);
  });
  clearOscProgress();
  s.stop(`${label} ✔`);
  return result;
}</pre>
<div class="blog_h2"><span class="graybg">src/commands/ — 子命令实现与 Onboard 向导</span></div>
<p>src/commands/ 中的每个文件对应一个顶级 CLI 子命令。文件命名遵循 {command}.ts 模式，如 start.ts、stop.ts、update.ts、onboard.ts、config.ts、plugin.ts。</p>
<p>其中最复杂的是 onboard.ts，即首次运行向导。Onboard 向导的执行流程为：检测系统环境（Node.js 版本、平台、包管理器）→ 选择消息渠道（Telegram/Discord/Slack 等）→ 输入渠道凭据（Bot Token 等）→ 选择 AI Provider（OpenAI/Anthropic/Ollama 等）→ 输入 Provider API Key → 写入配置文件 ~/.openclaw/config.yaml → 执行首次 npm install --omit=dev 安装所选渠道的扩展依赖。整个流程由 @clack/prompts 驱动，每个步骤都有 spinner 和进度条反馈。</p>
<div class="blog_h2"><span class="graybg">src/gateway/ — Gateway 控制平面</span></div>
<p>src/gateway/ 是 OpenClaw 的中枢。它在本地启动一个 WebSocket 服务（默认监听 ws://127.0.0.1:18789），充当所有渠道、插件、原生客户端和 Control UI 之间的 <span style="background-color: #c0c0c0;">单一控制平面</span>（Single Control Plane）。</p>
<p>目录结构大致如下：</p>
<pre class="crayon-plain-tag">src/gateway/
├── server.ts          # WebSocket 服务器生命周期
├── router.ts          # 协议消息分发
├── session.ts         # 会话管理
├── presence.ts        # 在线状态
├── config.ts          # 运行时配置 Hot-reload
├── cron.ts            # 定时任务调度
├── webhooks.ts        # 外部 webhook 接入
├── auth.ts            # 认证模型
├── health.ts          # /healthz, /readyz 端点
├── openai-compat.ts   # /v1/models, /v1/embeddings 兼容层
└── protocol/
    ├── schema.ts      # 协议 Schema 聚合入口
    └── schema/        # 按领域拆分的 Schema 定义文件
        ├── sessions.ts
        ├── nodes.ts
        ├── channels.ts
        └── ...</pre>
<p>protocol/ 子目录是 Gateway 的类型层。所有 WebSocket 消息都经由 protocol/schema.ts 聚合导出的 TypeScript 类型定义进行序列化和反序列化。schema/ 内的文件按领域组织（sessions、nodes、channels 等），每个文件导出请求/响应的 Zod Schema 或 TypeScript interface。这套 Schema 同时被用于 Swift codegen——macOS/iOS 原生应用中的 Gateway 客户端代码由构建脚本从这些 TypeScript 类型自动生成对应的 Swift struct。</p>
<p>Session 管理（session.ts）维护所有活跃会话的内存状态，包括会话 ID、关联渠道、关联 Agent、消息队列深度、最后活跃时间等。Presence（presence.ts）跟踪所有已连接客户端的在线状态，支持原生应用和 Web UI 实时显示哪些渠道处于在线。Cron（cron.ts）提供基于 cron 表达式的定时任务调度，用于周期性检查渠道连接状态和执行清理任务。Webhooks（webhooks.ts）为 Telegram webhook 模式和 Slack Events API 等需要 HTTP 回调的渠道提供端点注册。</p>
<div class="blog_h2"><span class="graybg">src/channels/ — 核心渠道实现</span></div>
<p>src/channels/ 并非一个单一目录——OpenClaw 将核心渠道的代码分散在 src/ 下的多个一级目录中。具体映射关系为：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>渠道</td>
<td>源码位置</td>
<td>底层依赖</td>
</tr>
</thead>
<tbody>
<tr>
<td>Telegram</td>
<td>src/telegram/</td>
<td>grammY</td>
</tr>
<tr>
<td>Discord</td>
<td>src/discord/</td>
<td>discord.js</td>
</tr>
<tr>
<td>Slack</td>
<td>src/slack/</td>
<td>@slack/bolt</td>
</tr>
<tr>
<td>Signal</td>
<td>src/signal/</td>
<td>signal-cli (Java 子进程)</td>
</tr>
<tr>
<td>iMessage</td>
<td>src/imessage/</td>
<td>BlueBubbles HTTP API / 原生 imsg</td>
</tr>
<tr>
<td>WhatsApp</td>
<td>src/web/</td>
<td>Baileys (WhatsApp Web 协议)</td>
</tr>
</tbody>
</table>
<p>src/channels/ 本身作为聚合层存在，定义了所有渠道必须实现的 <span style="background-color: #c0c0c0;">统一消息抽象</span>接口和路由表。每个渠道目录内部的文件结构大致对称：一个 adapter 文件负责将平台 SDK 的事件映射为统一的 inbound 消息格式，一个 sender 文件负责将统一的 outbound 格式转换回平台特定的 API 调用。</p>
<div class="blog_h2"><span class="graybg">src/routing/ — 消息路由引擎</span></div>
<p>消息路由引擎（src/routing/）是渠道系统和 Agent 运行时之间的中间层。它根据配置文件中的路由规则，将 inbound 消息分发到正确的 Agent 实例。路由维度包括：渠道类型、账号 ID、发送者 peer ID、群组 ID、消息内容匹配模式。多 Agent 场景下，路由引擎负责将不同渠道/账号/群组的消息隔离到不同 Agent 的会话中。</p>
<div class="blog_h2"><span class="graybg">src/plugins/ — 插件发现、加载与注册</span></div>
<p>src/plugins/ 是插件系统的运行时宿主，不是插件本身。它包含四个核心模块：</p>
<p><strong>Discovery</strong>：扫描 extensions/ workspace 和 ~/.openclaw/plugins/ 中已安装的 npm 包，查找包含 openclaw.plugin.json 清单文件的包。</p>
<p><strong>Manifest Validation</strong>：用 Zod Schema 严格校验 openclaw.plugin.json 的结构。清单文件中的 id、channel.id、install.npmSpec 等字段必须符合预定义的格式。</p>
<p><strong>Loader</strong>：对通过校验的插件执行动态 import()，加载其入口模块并调用约定的生命周期钩子。</p>
<p><strong>Registry</strong>：维护全局插件注册表，记录每个已加载插件的类型、状态、能力声明。Registry 支持运行时热插拔——新安装的插件可以在不重启 Gateway 的情况下被 discovery → validate → load → register。</p>
<p><strong>Contract Enforcement</strong>：通过 ESLint 规则在构建时确保插件只通过 openclaw/plugin-sdk/* 导入公开 API。任何直接引用 src/ 内部模块的插件都会在 CI 中被拦截。</p>
<div class="blog_h2"><span class="graybg">src/plugin-sdk/ — 唯一合法的插件导入面</span></div>
<p>src/plugin-sdk/ 是 OpenClaw 面向所有外部扩展的 <span style="background-color: #c0c0c0;">唯一公开 API 面</span>。package.json 的 exports 字段精确地声明了 230 条命名导出子路径，每一条都是一个稳定的契约。这 230 条子路径是插件开发的全部合法导入来源——没有例外。该目录的详细分析见下一章。</p>
<div class="blog_h2"><span class="graybg">src/infra/ — 基础设施层</span></div>
<p>src/infra/ 封装与操作系统交互的底层能力。核心组件包括：基于 <span style="background-color: #c0c0c0;">better-sqlite3</span> 的本地持久化层（存储会话历史、插件状态、用户配置等），以及基于 proper-lockfile 的文件锁机制——确保同一台机器上不会出现两个 OpenClaw Gateway 实例同时操作同一个数据目录。SQLite 数据库文件默认位于 ~/.openclaw/data/openclaw.db。</p>
<div class="blog_h2"><span class="graybg">src/media/ — 媒体处理管道</span></div>
<p>src/media/ 实现了统一的媒体处理管道（Media Processing Pipeline）。当渠道接收到图片、音频、视频或文件消息时，该管道负责：下载原始媒体 → 格式检测 → 必要时进行转码（如 Opus → WAV 用于语音转文字）→ 存储到本地缓存 → 生成引用 URL 供 Agent 使用。管道设计为可插拔的，media plugin 可以注册自定义的 processor 处理特定 MIME 类型。</p>
<div class="blog_h1"><span class="graybg">Plugin SDK</span></div>
<p>OpenClaw 的插件系统以 src/plugin-sdk/ 为核心，通过 package.json 的 exports 字段向外部暴露了 230 条精确的命名子路径。这是一个经过严格设计的 <span style="background-color: #c0c0c0;">契约体系</span>（Contract System）——它同时定义了插件能做什么和不能做什么。</p>
<div class="blog_h2"><span class="graybg">230 条导出子路径的分类与结构</span></div>
<p>package.json 的 exports 字段格式如下：</p>
<pre class="crayon-plain-tag">{
  "exports": {
    "./plugin-sdk/channel-types": "./src/plugin-sdk/channel-types.ts",
    "./plugin-sdk/channel-inbound": "./src/plugin-sdk/channel-inbound.ts",
    "./plugin-sdk/channel-reply-pipeline": "./src/plugin-sdk/channel-reply-pipeline.ts",
    "./plugin-sdk/channel-send-result": "./src/plugin-sdk/channel-send-result.ts",
    "./plugin-sdk/channel-dm-security": "./src/plugin-sdk/channel-dm-security.ts",
    "./plugin-sdk/provider-types": "./src/plugin-sdk/provider-types.ts",
    "./plugin-sdk/provider-registry": "./src/plugin-sdk/provider-registry.ts",
    "./plugin-sdk/memory-core-types": "./src/plugin-sdk/memory-core-types.ts",
    "./plugin-sdk/memory-core-store": "./src/plugin-sdk/memory-core-store.ts",
    "./plugin-sdk/plugin-manifest": "./src/plugin-sdk/plugin-manifest.ts",
    "./plugin-sdk/plugin-lifecycle": "./src/plugin-sdk/plugin-lifecycle.ts",
    "./plugin-sdk/runtime-config": "./src/plugin-sdk/runtime-config.ts",
    "./plugin-sdk/runtime-events": "./src/plugin-sdk/runtime-events.ts",
    "./plugin-sdk/media-types": "./src/plugin-sdk/media-types.ts",
    "./plugin-sdk/media-processor": "./src/plugin-sdk/media-processor.ts",
    "./plugin-sdk/speech-types": "./src/plugin-sdk/speech-types.ts",
    "./plugin-sdk/speech-engine": "./src/plugin-sdk/speech-engine.ts"
    // ... 共 230 条
  }
}</pre>
<p>这 230 条子路径按前缀可划分为以下类别：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>前缀</td>
<td>数量（约）</td>
<td>职责</td>
</tr>
</thead>
<tbody>
<tr>
<td>channel-*</td>
<td>~45</td>
<td>渠道类型定义、inbound/outbound 消息、DM 安全策略、群组行为、分块策略</td>
</tr>
<tr>
<td>provider-*</td>
<td>~35</td>
<td>AI Provider 接口、模型注册、能力声明、流式响应协议</td>
</tr>
<tr>
<td>memory-core-*</td>
<td>~20</td>
<td>内存系统核心类型、存储接口、向量索引</td>
</tr>
<tr>
<td>plugin-*</td>
<td>~25</td>
<td>插件清单格式、生命周期钩子、能力声明</td>
</tr>
<tr>
<td>runtime-*</td>
<td>~40</td>
<td>运行时配置、事件总线、日志、错误类型、会话上下文</td>
</tr>
<tr>
<td>media-*</td>
<td>~15</td>
<td>媒体类型、处理器接口、转码管道</td>
</tr>
<tr>
<td>speech-*</td>
<td>~10</td>
<td>语音识别/合成引擎接口</td>
</tr>
<tr>
<td>其他 (tool-*, skill-*, util-* 等)</td>
<td>~40</td>
<td>工具/技能插件接口、通用工具类型</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">导入边界规则：唯一合法导入面</span></div>
<p>OpenClaw 的核心架构约束是：所有外部扩展（extensions/ workspace 中的包以及第三方 npm 包）<strong>只能从 openclaw/plugin-sdk/* 导入</strong>。不允许直接引用 src/ 内部模块，不允许使用相对路径跨越包边界，不允许引用未在 exports 中声明的路径。</p>
<p>这条规则通过四条自定义 ESLint 规则在 CI 中强制执行：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>Lint 规则</td>
<td>作用</td>
</tr>
</thead>
<tbody>
<tr>
<td>lint:extensions:no-plugin-sdk-internal</td>
<td>禁止 extensions/ 中的代码导入 plugin-sdk 的内部实现文件（非 exports 声明路径）</td>
</tr>
<tr>
<td>lint:extensions:no-relative-outside-package</td>
<td>禁止 extensions/ 中的代码使用相对路径引用包外部的文件</td>
</tr>
<tr>
<td>lint:extensions:no-src-outside-plugin-sdk</td>
<td>禁止 extensions/ 中的代码直接引用 src/ 下非 plugin-sdk 的任何模块</td>
</tr>
<tr>
<td>lint:plugins:no-extension-imports</td>
<td>禁止 src/ 核心代码反向引用 extensions/ 中的模块（防止反向依赖）</td>
</tr>
</tbody>
</table>
<p>这四条规则共同构成了一个严格的 <span style="background-color: #c0c0c0;">依赖防火墙</span>：核心代码和扩展代码之间的边界是单向的、受控的、可审计的。</p>
<div class="blog_h2"><span class="graybg">五种插件类型</span></div>
<p>OpenClaw 定义了五种插件类型，每种对应 plugin-sdk 中的一组子路径：</p>
<p><strong>Channel Plugin（渠道插件）</strong>：实现一个新的消息平台适配器。必须提供 channel-inbound 和 channel-reply-pipeline 的完整实现。清单文件中必须声明 channel.id。</p>
<p><strong>Provider Plugin（Provider 插件）</strong>：接入一个新的 AI 模型提供商。需实现 provider-types 中定义的接口，包括模型列举、Chat Completion 流、Embedding 等。</p>
<p><strong>Tool Plugin（工具插件）</strong>：为 Agent 添加新的可调用工具。通过 tool-* 子路径注册工具定义，包括 JSON Schema 参数描述和执行函数。</p>
<p><strong>Skill Plugin（技能插件）</strong>：预打包的复合能力（如 "搜索网页并总结"），可包含多个 tool 的编排逻辑。</p>
<p><strong>Media Plugin（媒体插件）</strong>：注册自定义的媒体处理器，处理特定 MIME 类型的文件。</p>
<div class="blog_h2"><span class="graybg">插件清单：openclaw.plugin.json</span></div>
<p>每个插件的元数据由包根目录下的 openclaw.plugin.json 声明：</p>
<pre class="crayon-plain-tag">{
  "id": "openclaw-channel-matrix",
  "version": "2026.4.1",
  "type": "channel",
  "channel": {
    "id": "matrix",
    "displayName": "Matrix",
    "supportsGroups": true,
    "supportsDM": true
  },
  "install": {
    "npmSpec": "@openclaw/channel-matrix@latest"
  },
  "minCoreVersion": "2026.3.1",
  "entrypoint": "./dist/index.js"
}</pre>
<p>关键字段：id 是全局唯一标识符；channel.id 在 type 为 channel 时必须提供，用于路由表匹配；install.npmSpec 指定安装时使用的 npm 包标识符；minCoreVersion 声明兼容的最低 OpenClaw 核心版本。</p>
<div class="blog_h2"><span class="graybg">本地桶文件：api.ts 与 runtime-api.ts</span></div>
<p>src/plugin-sdk/ 内部有两个重要的 <span style="background-color: #c0c0c0;">桶文件</span>（Barrel File）：api.ts 和 runtime-api.ts。</p>
<p>api.ts 聚合所有纯类型导出——接口定义、类型别名、枚举等。它是编译时依赖，不包含任何运行时代码。runtime-api.ts 聚合需要运行时实现的模块——工厂函数、注册器、事件发射器等。两者的分离确保了：如果插件只需要类型信息（如纯粹的 TypeScript 类型守卫），可以仅依赖 api.ts，不会引入任何运行时代码，保持 tree-shaking 友好。</p>
<div class="blog_h2"><span class="graybg">插件安装与依赖约束</span></div>
<p>插件安装通过 npm install --omit=dev 执行，只安装生产依赖。关键约束：插件的 package.json 中禁止使用 workspace:* 协议作为 dependencies——这是因为第三方插件安装在用户机器上时不处于 monorepo workspace 上下文中，workspace:* 会解析失败。CI 中有专门的检查脚本拦截此类违规。</p>
<div class="blog_h2"><span class="graybg">遗留 Provider 兼容子路径的弃用</span></div>
<p>v2026.3.31 是一个 <span style="background-color: #c0c0c0;">Breaking Change</span> 版本。此前，plugin-sdk 中保留了一组以 provider-compat-* 为前缀的遗留子路径，用于向后兼容早期 Provider 接口。v2026.3.31 正式移除了这些路径。依赖旧接口的第三方 Provider 插件必须迁移到新的 provider-* 子路径。迁移指南位于 docs/migration/v2026.3.31-provider-compat.md。</p>
<div class="blog_h1"><span class="graybg">Gateway 架构</span></div>
<p>Gateway 是 OpenClaw 的核心运行时进程。它不是一个可选组件——所有渠道消息、Agent 调度、插件通信、原生客户端交互都经由 Gateway 路由。理解 Gateway 就是理解 OpenClaw 的运行时全貌。</p>
<div class="blog_h2"><span class="graybg">单一本地控制平面</span></div>
<p>Gateway 的设计哲学是 <span style="background-color: #c0c0c0;">Single Local Control Plane</span>——本地机器上只有一个 Gateway 实例运行，它是所有组件的通信枢纽。启动命令 openclaw start 实际上就是启动 Gateway 进程。Gateway 在 ws://127.0.0.1:18789（默认端口）上监听 WebSocket 连接，同时在同一端口提供 HTTP 端点。</p>
<p>所有组件都是 Gateway 的客户端：渠道适配器（Telegram bot、Discord bot 等）在内部通过 WebSocket 向 Gateway 报告 inbound 消息；Agent 运行时从 Gateway 接收任务并返回响应；原生应用（macOS、iOS、Android）通过 WebSocket 连接 Gateway 获取实时状态；Control UI（Web 界面）同样是一个 WebSocket 客户端。</p>
<div class="blog_h2"><span class="graybg">类型化协议：protocol/schema</span></div>
<p>Gateway 的 WebSocket 协议是完全类型化的。协议定义位于 src/gateway/protocol/schema.ts，它从 src/gateway/protocol/schema/ 目录中聚合导出所有子模块。每个子模块对应一个协议领域：</p>
<pre class="crayon-plain-tag">// src/gateway/protocol/schema/sessions.ts
import { z } from 'zod';

export const SessionPatchRequest = z.object({
  method: z.literal('sessions.patch'),
  params: z.object({
    sessionId: z.string(),
    patch: z.object({
      thinkingLevel: z.enum(['off','minimal','low','medium','high','xhigh']).optional(),
      activeAgent: z.string().optional(),
      queueMode: z.enum(['sequential','parallel']).optional(),
    }),
  }),
});

export const SessionPatchResponse = z.object({
  result: z.object({
    sessionId: z.string(),
    applied: z.record(z.unknown()),
  }),
});

// src/gateway/protocol/schema/nodes.ts
export const NodeListRequest = z.object({
  method: z.literal('node.list'),
  params: z.object({
    filter: z.object({
      type: z.enum(['channel','agent','plugin','tool']).optional(),
      status: z.enum(['online','offline','error']).optional(),
    }).optional(),
  }),
});

export const NodeDescribeRequest = z.object({
  method: z.literal('node.describe'),
  params: z.object({ nodeId: z.string() }),
});

export const NodeInvokeRequest = z.object({
  method: z.literal('node.invoke'),
  params: z.object({
    nodeId: z.string(),
    action: z.string(),
    payload: z.unknown(),
  }),
});</pre>
<p>协议采用类 JSON-RPC 的请求/响应模式。核心方法包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>方法</td>
<td>用途</td>
</tr>
</thead>
<tbody>
<tr>
<td>sessions.patch</td>
<td>修改会话参数（thinking level、活跃 agent、队列模式等）</td>
</tr>
<tr>
<td>sessions.list</td>
<td>列举所有活跃会话及其状态</td>
</tr>
<tr>
<td>node.list</td>
<td>列举所有已注册节点（渠道、Agent、插件、工具）</td>
</tr>
<tr>
<td>node.describe</td>
<td>获取指定节点的详细信息和能力声明</td>
</tr>
<tr>
<td>node.invoke</td>
<td>向指定节点发送操作指令（如要求渠道发送消息、要求 Agent 执行任务）</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Swift Codegen</span></div>
<p>macOS/iOS 原生应用需要与 Gateway 通信。为保证 TypeScript 协议定义与 Swift 客户端代码的一致性，OpenClaw 在构建流程中包含一个 <span style="background-color: #c0c0c0;">Swift codegen</span> 步骤。构建脚本解析 src/gateway/protocol/schema/ 中的 Zod Schema，自动生成对应的 Swift Codable struct 和 enum。生成的代码位于 apps/macos/Generated/ 和 apps/ios/Generated/。这意味着协议变更只需修改 TypeScript Schema，Swift 侧自动同步，不存在手动同步遗漏的风险。</p>
<div class="blog_h2"><span class="graybg">认证模型</span></div>
<p>Gateway 支持三种认证模式，按优先级排列：</p>
<p><strong>trusted-proxy</strong>：Gateway 信任来自特定代理（如 Nginx、Cloudflare Tunnel）的请求，依据代理注入的 HTTP 头进行身份识别。这是生产环境推荐模式。</p>
<p><strong>local-direct</strong>：当 WebSocket 连接来自 127.0.0.1 时，跳过认证直接授权。这是本地开发和单机部署的默认行为。</p>
<p><strong>gateway token</strong>：通过配置文件设置的静态 Token，客户端在 WebSocket 握手时通过 Authorization: Bearer 头携带。用于远程访问场景。</p>
<p>v2026.3.31 引入了一个重要的安全变更：<span style="background-color: #c0c0c0;">trusted-proxy 模式下，如果检测到多个客户端使用同一个 shared-token，Gateway 将拒绝连接</span>。此前这种 "多人共享一个 token" 的配置虽然不推荐但能工作，新版本将其升级为硬性错误。这是因为 shared-token 场景下无法区分不同用户的会话，会导致消息路由混乱。</p>
<div class="blog_h2"><span class="graybg">OpenAI 兼容端点与健康检查</span></div>
<p>Gateway 在 HTTP 层暴露了一组 <span style="background-color: #c0c0c0;">OpenAI 兼容端点</span>：</p>
<p>/v1/models：返回当前配置的所有可用模型列表，格式兼容 OpenAI List Models API。这使得任何兼容 OpenAI API 的客户端（如 Cursor、Continue 等）可以直接将 OpenClaw Gateway 作为模型提供方。</p>
<p>/v1/embeddings：提供文本向量化接口，格式兼容 OpenAI Embeddings API。后端可路由到实际配置的 Embedding Provider（OpenAI、Ollama 本地模型等）。</p>
<p>健康检查端点遵循 Kubernetes 惯例：</p>
<p>/healthz：存活探针（Liveness Probe），只要 Gateway 进程在运行就返回 200。</p>
<p>/readyz：就绪探针（Readiness Probe），只有当至少一个渠道连接成功且 Agent 运行时已初始化时才返回 200。适用于负载均衡器判断节点是否可以接收流量。</p>
<div class="blog_h2"><span class="graybg">Control UI 与 Bridge Protocol</span></div>
<p>Gateway 直接serve一个 Web 管理界面——<span style="background-color: #c0c0c0;">Control UI</span>。该 UI 使用 <span style="background-color: #c0c0c0;">Lit 3</span>（Web Components 框架）+ <span style="background-color: #c0c0c0;">Vite</span>（构建工具）开发，源码位于 ui/ 目录。构建产物在发布时嵌入 Gateway 的静态资源中，通过 HTTP 直接访问（默认 http://127.0.0.1:18789）。Control UI 本身也是一个 WebSocket 客户端，与 Gateway 保持长连接以实现实时状态更新。</p>
<p>Bridge Protocol（桥接协议）的规范文档位于 docs/gateway/bridge-protocol.md，它定义了原生应用与 Gateway 之间的通信约定——包括消息编码格式、心跳机制、重连策略、事件订阅模型。这份文档是原生应用开发者的核心参考。</p>
<div class="blog_h1"><span class="graybg">渠道系统</span></div>
<p>OpenClaw 在 v2026.4.1 中支持 24 个消息渠道。渠道系统的核心工程挑战在于：如何将 24 个各具特色、API 风格迥异的消息平台，抽象为一套统一的 inbound/outbound 消息模型，同时保留每个平台的独特能力。</p>
<div class="blog_h2"><span class="graybg">24 渠道全景</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>渠道</td>
<td>底层实现</td>
<td>类型</td>
</tr>
</thead>
<tbody>
<tr>
<td>WhatsApp</td>
<td>Baileys (WhatsApp Web 逆向协议)</td>
<td>核心渠道 (src/web/)</td>
</tr>
<tr>
<td>Telegram</td>
<td>grammY</td>
<td>核心渠道 (src/telegram/)</td>
</tr>
<tr>
<td>Slack</td>
<td>@slack/bolt</td>
<td>核心渠道 (src/slack/)</td>
</tr>
<tr>
<td>Discord</td>
<td>discord.js</td>
<td>核心渠道 (src/discord/)</td>
</tr>
<tr>
<td>Signal</td>
<td>signal-cli (Java 子进程)</td>
<td>核心渠道 (src/signal/)</td>
</tr>
<tr>
<td>BlueBubbles (iMessage)</td>
<td>BlueBubbles HTTP API</td>
<td>核心渠道 (src/imessage/)，推荐方式</td>
</tr>
<tr>
<td>iMessage (legacy imsg)</td>
<td>原生 AppleScript/osascript</td>
<td>核心渠道，已标记为遗留</td>
</tr>
<tr>
<td>Google Chat</td>
<td>Google Chat API</td>
<td>内置扩展</td>
</tr>
<tr>
<td>IRC</td>
<td>irc-framework</td>
<td>内置扩展</td>
</tr>
<tr>
<td>Microsoft Teams</td>
<td>Teams SDK (v2026.3.28 升级后版本)</td>
<td>内置扩展</td>
</tr>
<tr>
<td>Matrix</td>
<td>matrix-js-sdk + @matrix-org/crypto-wasm</td>
<td>内置扩展 (extensions/)</td>
</tr>
<tr>
<td>Feishu (飞书)</td>
<td>Feishu Open API</td>
<td>内置扩展</td>
</tr>
<tr>
<td>LINE</td>
<td>@line/bot-sdk</td>
<td>内置扩展</td>
</tr>
<tr>
<td>Mattermost</td>
<td>Mattermost REST API + WebSocket</td>
<td>内置扩展</td>
</tr>
<tr>
<td>Nextcloud Talk</td>
<td>Nextcloud Talk API</td>
<td>内置扩展</td>
</tr>
<tr>
<td>Nostr</td>
<td>nostr-tools</td>
<td>内置扩展</td>
</tr>
<tr>
<td>Synology Chat</td>
<td>Synology Chat Webhook</td>
<td>内置扩展</td>
</tr>
<tr>
<td>Tlon</td>
<td>Tlon API</td>
<td>内置扩展</td>
</tr>
<tr>
<td>Twitch</td>
<td>tmi.js</td>
<td>内置扩展</td>
</tr>
<tr>
<td>Zalo</td>
<td>Zalo Official Account API</td>
<td>内置扩展 (extensions/)</td>
</tr>
<tr>
<td>Zalo Personal</td>
<td>Zalo Personal API (ZaloUser)</td>
<td>内置扩展 (extensions/)</td>
</tr>
<tr>
<td>Voice Call</td>
<td>VoIP/SIP 集成</td>
<td>内置扩展 (extensions/)</td>
</tr>
<tr>
<td>WeChat (微信)</td>
<td>@tencent-weixin/openclaw-weixin (iLink Bot API)</td>
<td>官方合作插件</td>
</tr>
<tr>
<td>WebChat</td>
<td>Gateway 内置 WebSocket 聊天</td>
<td>核心渠道</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">渠道契约类型</span></div>
<p>渠道系统的类型契约由三个核心文件定义：</p>
<p>types.plugin.ts：面向插件开发者的公开类型。渠道插件必须实现的接口在此定义，包括 ChannelAdapter（渠道适配器）、ChannelSender（消息发送器）、ChannelConfig（渠道配置 Schema）。</p>
<p>types.core.ts：核心内部类型，不通过 plugin-sdk 导出。包含路由表条目、会话绑定关系、内部消息信封（Envelope）格式。</p>
<p>types.adapters.ts：适配器辅助类型，定义各平台 SDK 事件到统一 inbound 格式的映射接口。</p>
<div class="blog_h2"><span class="graybg">统一消息抽象</span></div>
<p>统一消息抽象是渠道系统的核心设计。它由三个 plugin-sdk 子路径定义：</p>
<p>channel-inbound：定义所有渠道 inbound 消息的统一结构。无论消息来自 WhatsApp、Telegram 还是 Discord，经过渠道适配器处理后都被转换为相同的 InboundMessage 类型。该类型包含：channelId、peerId（发送者标识）、groupId（群组标识，DM 时为 null）、content（文本/媒体/混合内容）、replyTo（引用消息 ID）、timestamp、rawEvent（平台原始事件，用于渠道特定逻辑）。</p>
<p>channel-reply-pipeline：定义 Agent 响应经过的处理管道。管道阶段包括：内容格式化（Markdown → 平台特定格式）→ 长消息分块（per-channel chunking）→ 媒体附件处理 → 平台 API 调用。</p>
<p>channel-send-result：定义消息发送结果的统一结构，包括平台返回的消息 ID、发送状态（成功/失败/部分成功）、错误信息。</p>
<div class="blog_h2"><span class="graybg">群组路由：@提及门控与回复标签</span></div>
<p>在群组场景中，Agent 默认不响应所有消息——这会导致群组中的噪声。OpenClaw 实现了 <span style="background-color: #c0c0c0;">@提及门控</span>（Mention Gating）：只有当消息中包含对 Bot 的 @mention 时，Agent 才会处理该消息。这一行为可通过配置按渠道/群组覆盖为 always 模式（响应所有消息）。</p>
<p>回复标签（Reply Tags）解决了另一个群组问题：当多条消息同时进入时，Agent 的回复需要标记其对应的原始消息。在 Telegram 中这通过 reply_to_message_id 实现，在 Discord 中通过 Message Reference 实现，在 Slack 中通过 Thread TS 实现。渠道适配器负责将统一的 replyTo 字段映射为平台特定的回复机制。</p>
<p>长消息分块（Per-channel Chunking）是另一个平台差异处理点。Telegram 单条消息上限 4096 字符，Discord 为 2000 字符，WhatsApp 约 65536 字符。channel-reply-pipeline 中的分块阶段根据目标渠道的限制，将超长 Agent 响应拆分为多条消息，同时确保代码块、Markdown 列表等结构不被截断在中间。</p>
<div class="blog_h2"><span class="graybg">DM 安全策略</span></div>
<p>私聊（DM）场景有独立的安全模型，由 channel-dm-security 子路径定义。核心是 dmPolicy 配置项，支持三种模式：</p>
<p>pairing：用户必须先发送配对码（Pairing Code）才能激活 DM 对话。配对码由 openclaw pair 命令生成，一次性使用。这是最安全的模式。</p>
<p>allowlist：只有 allowFrom 配置中列出的用户 ID/手机号才能发起 DM 对话。</p>
<p>open：任何人都可以直接发起 DM 对话。仅建议在可控环境（如内网部署）中使用。</p>
<div class="blog_h2"><span class="graybg">渠道特定亮点</span></div>
<p><strong>WhatsApp</strong>：基于 Baileys 库，使用 WhatsApp Web 协议。首次连接需要扫描 QR 码完成登录，QR 码在终端中以 ASCII art 形式渲染，同时在 Control UI 中以图片形式展示。会话凭证持久化到本地文件系统，后续启动自动恢复。</p>
<p><strong>Telegram</strong>：支持两种运行模式——长轮询（Long Polling，默认）和 <span style="background-color: #c0c0c0;">Webhook 模式</span>。Webhook 模式下，Gateway 注册一个公开 HTTPS 端点接收 Telegram 推送，延迟更低但需要公网可达的地址（通常通过 Cloudflare Tunnel 或 ngrok 实现）。grammY 框架提供了完整的 Bot API 类型安全封装。</p>
<p><strong>Discord</strong>：支持原生 Slash Commands（/ask、/image 等）和纯文本命令两种交互模式。discord.js 提供了丰富的事件模型，OpenClaw 利用其 Message Component 能力实现了交互式按钮和选择菜单。</p>
<p><strong>Microsoft Teams</strong>：v2026.3.28 版本对 Teams 集成进行了重大升级，迁移到新版 Teams SDK。新版本支持流式回复（Streaming Replies），Agent 的响应可以实时逐字显示在 Teams 对话中，并带有 AI 注释标签（AI Annotation），让用户明确知道当前回复来自 AI。</p>
<p><strong>WeChat（微信）</strong>：通过官方合作渠道实现，使用 @tencent-weixin/openclaw-weixin 包，底层接入 iLink Bot API。当前仅支持私聊消息，不支持群聊。v2.x 版本要求 OpenClaw 核心版本 ≥ 2026.3.22。</p>
<div class="blog_h1"><span class="graybg">Agent 运行时</span></div>
<p>OpenClaw 的 Agent 运行时构建在 <span style="background-color: #c0c0c0;">Pi Agent</span> 之上。这不是一个自研的 Agent 框架，而是对外部库 @mariozechner/pi-agent-core@0.64.0 和 @mariozechner/pi-ai@0.64.0 的深度集成。Pi 生态还包括 pi-coding-agent（代码生成专用 Agent）和 pi-tui（终端 UI）。</p>
<div class="blog_h2"><span class="graybg">RPC 模式：工具流与块流</span></div>
<p>Pi Agent 以 <span style="background-color: #c0c0c0;">RPC 模式</span>运行时，支持两种流式输出协议：</p>
<p><strong>Tool Streaming（工具流）</strong>：Agent 调用工具时，工具的执行过程和中间结果以流式方式返回。例如，Agent 调用搜索工具时，搜索结果的每一条都作为一个流事件推送，而不是等待所有结果返回后才一次性输出。</p>
<p><strong>Block Streaming（块流）</strong>：Agent 的文本响应以块为单位流式输出。一个 "块" 可以是一个段落、一个代码块或一个列表。块流比逐 token 流更适合消息渠道场景——渠道适配器可以在每个块完成时立即发送，而不是积累整个响应后发送，也避免了逐 token 发送导致的频繁 API 调用。</p>
<div class="blog_h2"><span class="graybg">Session 模型</span></div>
<p>OpenClaw 的 Session 模型是理解消息路由的关键。每个 Agent 维护多个独立的会话（Session），会话之间完全隔离：</p>
<p><strong>DM Session</strong>：与每个私聊用户的对话构成一个独立会话。会话由 (agentId, channelId, peerId) 三元组唯一标识。</p>
<p><strong>Group Session</strong>：每个群组一个独立会话，由 (agentId, channelId, groupId) 三元组标识。群组会话与 DM 会话完全隔离——Agent 在群组中看不到同一用户的私聊历史，反之亦然。</p>
<p>会话的激活模式（Activation Mode）控制 Agent 何时响应：mention 模式下，只有 @mention 才触发响应；always 模式下，所有消息都触发响应。DM 会话默认为 always，群组会话默认为 mention。</p>
<p>队列模式（Queue Mode）控制并发消息的处理策略：sequential 模式下，消息严格按接收顺序逐条处理；parallel 模式下，多条消息可并行处理（适用于无状态的工具调用场景）。</p>
<p>Reply-back 路由确保 Agent 的响应被发送到正确的渠道和对话。当 Agent 通过工具调用触发了跨渠道操作时（如在 Telegram 对话中要求 Agent 向 Slack 频道发送消息），reply-back 路由负责将操作结果路由回发起请求的 Telegram 对话。</p>
<div class="blog_h2"><span class="graybg">Session 工具：Agent 间协调</span></div>
<p>三个内置工具使 Agent 具备了 <span style="background-color: #c0c0c0;">跨会话/跨 Agent 协调能力</span>：</p>
<pre class="crayon-plain-tag">// sessions_list: 列举当前所有活跃会话
{
  name: 'sessions_list',
  description: 'List all active sessions with their channel, peer, and status',
  parameters: {
    filter: { type: 'object', properties: {
      channelId: { type: 'string' },
      status: { enum: ['active', 'idle', 'archived'] }
    }}
  }
}

// sessions_history: 读取指定会话的历史消息
{
  name: 'sessions_history',
  description: 'Read message history from a specific session',
  parameters: {
    sessionId: { type: 'string' },
    limit: { type: 'number', default: 50 }
  }
}

// sessions_send: 向指定会话发送消息（实现 Agent-to-Agent 通信）
{
  name: 'sessions_send',
  description: 'Send a message to a specific session (enables agent-to-agent coordination)',
  parameters: {
    sessionId: { type: 'string' },
    content: { type: 'string' }
  }
}</pre>
<p>sessions_send 是多 Agent 协调的关键。Agent A 可以通过 sessions_list 发现 Agent B 的会话，通过 sessions_send 向 Agent B 发送指令或查询，Agent B 的响应会通过 reply-back 路由返回 Agent A 的会话上下文。</p>
<div class="blog_h2"><span class="graybg">多 Agent 路由</span></div>
<p>OpenClaw 支持在同一实例中运行多个 Agent，每个 Agent 有独立的配置和会话空间。路由规则在配置文件中定义，支持按渠道、账号、peer 三个维度将 inbound 消息分发到不同 Agent：</p>
<pre class="crayon-plain-tag"># config.yaml 多 Agent 路由示例
agents:
  - id: general-assistant
    provider: openai
    model: gpt-4o
    routes:
      - channel: telegram
        account: "@mybot"
      - channel: discord
        account: "bot-token-1"

  - id: coding-helper
    provider: anthropic
    model: claude-sonnet-4-20250514
    routes:
      - channel: slack
        account: "workspace-1"
        peers: ["U12345678"]  # 仅特定用户的消息路由到此 Agent</pre>
<p>每个 Agent 拥有独立的 workspace 和 session 存储，实现完全隔离。</p>
<div class="blog_h2"><span class="graybg">Agent Workspace 与注入文件</span></div>
<p>每个 Agent 的运行时上下文由 ~/.openclaw/workspace/ 目录提供。该目录下的三个特殊文件会被自动注入到 Agent 的系统提示词中：</p>
<p>AGENTS.md：定义 Agent 的角色、行为准则和约束。这是 Agent 人格的核心定义文件。</p>
<p>SOUL.md：更细粒度的人格描述——语气、对话风格、知识领域偏好等。</p>
<p>TOOLS.md：工具使用指南，告诉 Agent 每个可用工具的使用场景和最佳实践。</p>
<p>这三个文件均为 Markdown 格式，用户可以自由编辑。修改后无需重启——Gateway 在每次会话消息处理前会检查文件的 mtime，如有变更则重新加载。</p>
<div class="blog_h2"><span class="graybg">Session 持久化、修剪与压缩</span></div>
<p>会话历史以 JSONL（JSON Lines）格式持久化到 ~/.openclaw/agents//sessions/*.jsonl。每个会话一个文件，每行一条消息记录。JSONL 格式的选择是经过考量的：它支持 append-only 写入（崩溃安全），支持按行增量读取（内存效率），且可以直接用标准文本工具检查。</p>
<p>长时间运行的会话会积累大量历史，导致 context window 溢出和延迟增加。OpenClaw 提供了两种应对机制：</p>
<p><strong>Session Pruning（会话修剪）</strong>：自动删除超过配置时间窗口（默认 7 天）的旧消息。修剪操作在会话被激活时触发，是懒惰式的。</p>
<p><strong>Session Compaction（会话压缩）</strong>：通过 /compact 命令手动触发。压缩过程调用 AI 模型将长历史总结为精简的上下文摘要，替换原始的逐条消息记录。压缩后的会话文件体积可缩减 80% 以上，同时保留关键上下文信息。</p>
<div class="blog_h2"><span class="graybg">Thinking Levels 与 Idle-stream Timeout</span></div>
<p>OpenClaw 暴露了对 AI 模型 "思考深度" 的精细控制。thinkingLevel 参数支持六个级别：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>级别</td>
<td>行为</td>
</tr>
</thead>
<tbody>
<tr>
<td>off</td>
<td>禁用扩展思考（Extended Thinking），直接生成响应</td>
</tr>
<tr>
<td>minimal</td>
<td>最小思考预算</td>
</tr>
<tr>
<td>low</td>
<td>低思考预算，适合简单任务</td>
</tr>
<tr>
<td>medium</td>
<td>中等思考预算，默认值</td>
</tr>
<tr>
<td>high</td>
<td>高思考预算，适合复杂推理</td>
</tr>
<tr>
<td>xhigh</td>
<td>极高思考预算，用于需要深度推理的场景</td>
</tr>
</tbody>
</table>
<p>Thinking Level 可以在会话级别通过 sessions.patch 协议方法动态调整，也可以在配置文件中设置全局默认值。支持扩展思考的 Provider（如 Anthropic Claude）会根据级别调整思考 token 的预算上限。</p>
<p>v2026.3.31 引入的 <span style="background-color: #c0c0c0;">idle-stream timeout</span> 解决了一个实际运维问题：当模型流（Model Stream）长时间无新 token 输出时（例如模型服务端卡住或网络中断），Agent 会一直等待而不释放会话锁，导致该会话的后续消息全部堆积。idle-stream timeout 允许配置一个超时时间（默认 120 秒），当流在指定时间内无新数据时，Agent 会主动中断流并返回部分响应或错误消息。此超时时间可在配置文件中按 Provider 调整——使用本地 Ollama 模型时可能需要更长的超时。</p>
<div class="blog_h1"><span class="graybg">内存系统</span></div>
<p>AI 助手的个性化能力取决于记忆系统的深度。OpenClaw 的内存子系统 <span style="background-color: #c0c0c0;">memory-core</span> 是整个项目中模块拆分最细致的部分，由 13 个子模块组成，全部通过 plugin-sdk 导出。设计目标明确：所有记忆数据以本地 Markdown 文件和 SQLite 数据库的形式持久化，用户可直接编辑、可 Git 版本控制、可离线运行。</p>
<div class="blog_h2"><span class="graybg">13 个子模块的职责划分</span></div>
<p>plugin-sdk 中与记忆相关的导出共 13 个路径，每个路径对应一个独立的编译单元：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">导出路径</td>
<td style="text-align: center;">职责</td>
</tr>
</thead>
<tbody>
<tr>
<td>memory-core</td>
<td>根模块，定义 MemoryStore 接口、MemoryEntry 类型、TTL 策略和序列化契约</td>
</tr>
<tr>
<td>memory-core-engine-runtime</td>
<td>引擎运行时，将内存操作绑定到当前 Agent 运行时生命周期</td>
</tr>
<tr>
<td>memory-core-host-engine-embeddings</td>
<td>嵌入引擎宿主：调度 Embedding 模型计算向量，管理批量嵌入队列</td>
</tr>
<tr>
<td>memory-core-host-engine-foundation</td>
<td>基础引擎宿主：提供 tokenizer 绑定、向量维度协商、距离度量选择</td>
</tr>
<tr>
<td>memory-core-host-engine-qmd</td>
<td>QMD（Query-Memory-Document）引擎：将用户查询与记忆文档进行语义匹配</td>
</tr>
<tr>
<td>memory-core-host-engine-storage</td>
<td>存储引擎宿主：抽象底层存储后端（SQLite、LanceDB），提供统一 CRUD</td>
</tr>
<tr>
<td>memory-core-host-multimodal</td>
<td>多模态记忆：处理图片、音频等非文本记忆条目的索引与检索</td>
</tr>
<tr>
<td>memory-core-host-query</td>
<td>查询宿主：构建语义搜索查询，合并关键词过滤与向量相似度</td>
</tr>
<tr>
<td>memory-core-host-runtime-cli</td>
<td>CLI 运行时宿主：暴露 openclaw memory search 等终端命令</td>
</tr>
<tr>
<td>memory-core-host-runtime-core</td>
<td>核心运行时宿主：记忆系统的初始化、迁移和生命周期管理</td>
</tr>
<tr>
<td>memory-core-host-runtime-files</td>
<td>文件运行时宿主：监控 Markdown 记忆文件的变更并触发重新索引</td>
</tr>
<tr>
<td>memory-core-host-secret</td>
<td>密钥宿主：管理记忆存储的加密密钥与 SecretRef 解析</td>
</tr>
<tr>
<td>memory-core-host-status</td>
<td>状态宿主：报告索引进度、向量数量、最近查询延迟等运行指标</td>
</tr>
</tbody>
</table>
<p>这种拆分方式遵循 OpenClaw 的插件架构原则：每个子模块可以被独立替换或禁用，核心系统只依赖 memory-core 根模块定义的接口，而不直接依赖任何具体存储后端。</p>
<div class="blog_h2"><span class="graybg">本地 Markdown 文件持久化</span></div>
<p>OpenClaw 的记忆系统将用户偏好和长期上下文存储为本地 Markdown 文件，默认位于 ~/.openclaw/memory/ 目录。每个记忆文件是标准 Markdown，带有 YAML front-matter 元数据：</p>
<pre class="crayon-plain-tag">---
type: preference
created: 2026-03-15T08:22:00Z
updated: 2026-04-01T14:30:00Z
tags: [coding-style, language]
---

# Coding Preferences

- Preferred language: TypeScript with strict mode
- Tab width: 2 spaces
- Always use explicit return types
- Prefer functional composition over class inheritance</pre>
<p>这种设计的核心优势在于三点：第一，用户可以直接用任何文本编辑器修改记忆内容，无需进入 OpenClaw 的界面；第二，记忆文件可以纳入 Git 版本控制，团队成员可以共享和同步偏好配置；第三，记忆内容完全离线可用，不依赖任何云端服务。memory-core-host-runtime-files 模块通过文件系统监听（fs.watch）检测 Markdown 文件的变更，自动触发重新索引流程——解析 front-matter、提取正文、计算嵌入向量、更新向量存储。</p>
<div class="blog_h2"><span class="graybg">向量存储：sqlite-vec 与 LanceDB 双后端</span></div>
<p>语义搜索依赖向量存储。OpenClaw 提供两个后端选项：</p>
<p><span style="background-color: #c0c0c0;">sqlite-vec</span>（版本 0.1.9）是默认后端。它是 SQLite 的向量搜索扩展，以 npm 依赖 sqlite-vec@0.1.9 的形式声明在 package.json 中。sqlite-vec 将向量存储为 SQLite 表中的 BLOB 列，支持精确最近邻（Exact KNN）和基于量化的近似最近邻（ANN）搜索。对于个人使用场景——通常记忆条目在数百到数千量级——sqlite-vec 的精确 KNN 已经足够高效，查询延迟在亚毫秒级别。sqlite-vec 的优势与 OpenClaw 的本地优先哲学完全一致：单文件数据库，零外部依赖，可直接备份和迁移。</p>
<p><span style="background-color: #c0c0c0;">memory-lancedb</span> 是第二个后端，同样通过 plugin-sdk 导出。LanceDB 是一个嵌入式向量数据库，底层使用 Lance 列式格式，支持 IVF-PQ 索引，适合记忆条目达到十万量级的场景。memory-core-host-engine-storage 模块通过统一的存储抽象层隔离这两个后端，上层代码无需感知底层实现差异：</p>
<pre class="crayon-plain-tag">// memory-core-host-engine-storage 抽象接口
export interface VectorStorageBackend {
  insert(entries: MemoryEntry[]): Promise;
  search(query: Float32Array, topK: number, filter?: MemoryFilter): Promise&lt;ScoredEntry[]&gt;;
  delete(ids: string[]): Promise;
  count(): Promise;
  vacuum(): Promise;
}</pre>
<div class="blog_h2"><span class="graybg">嵌入管道与语义搜索</span></div>
<p>memory-core-host-engine-embeddings 管理嵌入计算的完整管道。当记忆文件被创建或修改时，该模块执行以下流程：</p>
<ol>
<li>解析 Markdown 文件，将正文按段落分块（chunking），每块控制在 512 token 以内</li>
<li>调用当前配置的嵌入模型（Embedding Model）计算向量，默认使用提供者插件中配置的嵌入端点</li>
<li>将向量与元数据（来源文件路径、chunk 偏移、时间戳、标签）一起写入向量存储</li>
<li>维护一个增量索引：仅对变更的块重新计算嵌入，未修改的块保留原有向量</li>
</ol>
<p>memory-core-host-engine-qmd（QMD 引擎）负责查询时的语义匹配。QMD 的全称是 Query-Memory-Document，它实现一个三阶段检索流程：先对用户查询计算嵌入向量，然后在向量存储中执行近似最近邻搜索获取候选集，最后用 BM25 关键词评分对候选集重排序（Re-ranking）。memory-core-host-query 模块负责构建查询对象，将语义相似度阈值、标签过滤、时间范围等条件组合为统一的查询描述符。</p>
<p>记忆系统是 OpenClaw 个性化能力的基石。Agent 运行时在处理每一轮对话时，都会通过 memory-core-engine-runtime 检索相关记忆并注入到系统提示词中。这个过程对用户透明，但直接影响 Agent 回复的个性化程度——它知道用户偏好的编程语言、代码风格、常用工具链，甚至过去对话中建立的项目上下文。</p>
<div class="blog_h1"><span class="graybg">模型提供者</span></div>
<p>OpenClaw 的模型提供者（Model Provider）系统是其多模型支持能力的核心。通过 plugin-sdk 导出的提供者插件超过 25 个，覆盖主流商业 API、开源推理引擎和云平台网关。每个提供者是一个独立插件，遵循统一的注册、认证和模型目录协议。</p>
<div class="blog_h2"><span class="graybg">提供者插件架构</span></div>
<p>每个提供者插件由四个核心文件组成：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">文件</td>
<td style="text-align: center;">职责</td>
</tr>
</thead>
<tbody>
<tr>
<td>provider-entry.ts</td>
<td>插件入口点，注册提供者到插件注册表，声明支持的功能特性（Feature Flags）</td>
</tr>
<tr>
<td>provider-auth.ts</td>
<td>认证逻辑，实现 API Key 或 OAuth 流程</td>
</tr>
<tr>
<td>provider-catalog-shared.ts</td>
<td>模型目录，列出该提供者支持的所有模型及其能力标记（文本/图像/代码等）</td>
</tr>
<tr>
<td>provider-model-shared.ts</td>
<td>模型共享配置，定义 token 限制、定价信息、上下文窗口大小等元数据</td>
</tr>
</tbody>
</table>
<p>提供者插件通过 plugin-sdk 的导出路径注册。以 OpenAI 提供者为例，导出路径为 plugin-sdk/provider-openai，Anthropic 为 plugin-sdk/provider-anthropic，以此类推。</p>
<div class="blog_h2"><span class="graybg">完整提供者清单</span></div>
<p>截至 v2026.4.1，plugin-sdk 导出以下提供者：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">提供者</td>
<td style="text-align: center;">模型举例</td>
<td style="text-align: center;">认证方式</td>
</tr>
</thead>
<tbody>
<tr>
<td>OpenAI</td>
<td>GPT-4o, o3, o4-mini, Codex</td>
<td>API Key / OAuth</td>
</tr>
<tr>
<td>Anthropic (Claude)</td>
<td>Claude Sonnet 4, Opus 4</td>
<td>API Key / OAuth</td>
</tr>
<tr>
<td>Google (Gemini)</td>
<td>Gemini 2.5 Pro, Flash</td>
<td>API Key</td>
</tr>
<tr>
<td>DeepSeek</td>
<td>DeepSeek-V3, DeepSeek-R1</td>
<td>API Key</td>
</tr>
<tr>
<td>xAI (Grok)</td>
<td>Grok-3, Grok-3-mini</td>
<td>API Key</td>
</tr>
<tr>
<td>Ollama</td>
<td>本地部署任意 GGUF 模型</td>
<td>无（本地）</td>
</tr>
<tr>
<td>Mistral</td>
<td>Mistral Large, Codestral</td>
<td>API Key</td>
</tr>
<tr>
<td>MiniMax</td>
<td>MiniMax-Text-01, image-01</td>
<td>API Key</td>
</tr>
<tr>
<td>Moonshot（月之暗面）</td>
<td>Kimi</td>
<td>API Key</td>
</tr>
<tr>
<td>ModelStudio（通义千问）</td>
<td>Qwen-Max, Qwen-Plus</td>
<td>API Key</td>
</tr>
<tr>
<td>Qianfan（百度文心）</td>
<td>ERNIE-4.0, ERNIE-Speed</td>
<td>API Key</td>
</tr>
<tr>
<td>NVIDIA</td>
<td>Nemotron, Llama 3 NVIDIA</td>
<td>API Key</td>
</tr>
<tr>
<td>HuggingFace</td>
<td>Inference API 托管模型</td>
<td>API Token</td>
</tr>
<tr>
<td>Together</td>
<td>Llama, Mixtral 等开源模型</td>
<td>API Key</td>
</tr>
<tr>
<td>Venice</td>
<td>隐私优先推理</td>
<td>API Key</td>
</tr>
<tr>
<td>vLLM</td>
<td>自托管 vLLM 实例</td>
<td>自定义</td>
</tr>
<tr>
<td>SGLang</td>
<td>自托管 SGLang 实例</td>
<td>自定义</td>
</tr>
<tr>
<td>BytePlus（火山引擎）</td>
<td>豆包大模型</td>
<td>API Key</td>
</tr>
<tr>
<td>Cloudflare AI Gateway</td>
<td>Workers AI 代理</td>
<td>API Token</td>
</tr>
<tr>
<td>Amazon Bedrock</td>
<td>Claude on Bedrock, Titan</td>
<td>AWS IAM</td>
</tr>
<tr>
<td>Anthropic Vertex</td>
<td>Claude on Vertex AI</td>
<td>GCP Service Account</td>
</tr>
<tr>
<td>Chutes</td>
<td>GPU 推理市场</td>
<td>API Key</td>
</tr>
<tr>
<td>KiloCode</td>
<td>KiloCode 模型</td>
<td>API Key</td>
</tr>
<tr>
<td>Kimi Coding</td>
<td>Kimi 代码模型</td>
<td>API Key</td>
</tr>
<tr>
<td>OpenCode / OpenCode Go</td>
<td>开源代码推理</td>
<td>API Key</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">认证系统：双模认证与密钥管理</span></div>
<p>认证子系统由四个模块组成：provider-auth-api-key（API Key 认证）、provider-auth-login（OAuth 登录认证）、provider-auth-result（认证结果封装）和 provider-auth-runtime（运行时认证状态管理）。</p>
<p>大多数提供者支持 API Key 单一模式，但 OpenAI 和 Anthropic 等主流提供者同时支持 <span style="background-color: #c0c0c0;">OAuth 登录</span>和 API Key 两种方式。OAuth 模式下，用户通过浏览器完成授权后，OpenClaw 获取 access token 并自动管理刷新流程。这种双模设计（Auth Rotation）允许用户在免费额度用完后无缝切换到自有 API Key，反之亦然。</p>
<p><span style="background-color: #c0c0c0;">合成认证（Synthetic Auth）</span>通过 resolveSyntheticAuth 函数实现。当多个提供者共享同一底层认证（例如 Anthropic Vertex 使用 GCP 凭据，而不是 Anthropic 原生 API Key）时，合成认证将底层凭据转换为提供者期望的格式。实现位于认证运行时模块中：</p>
<pre class="crayon-plain-tag">// provider-auth-runtime 中的合成认证解析
export async function resolveSyntheticAuth(
  provider: ProviderId,
  secretStore: SecretStore
): Promise {
  const secretRef = getProviderSecretRef(provider);
  const rawCredential = await secretStore.resolve(secretRef);

  // 根据提供者类型转换凭据格式
  switch (provider) {
    case 'anthropic-vertex':
      return synthesizeVertexAuth(rawCredential as GCPServiceAccount);
    case 'amazon-bedrock':
      return synthesizeBedrockAuth(rawCredential as AWSCredentials);
    default:
      return { type: 'api-key', key: rawCredential as string };
  }
}</pre>
<p><span style="background-color: #c0c0c0;">SecretRef</span> 是 OpenClaw 的凭据引用语义。凭据不以明文存储在配置文件中，而是通过 SecretRef 引用操作系统的密钥链（macOS Keychain、Windows Credential Manager、Linux Secret Service）。SecretRef 的格式为 secretref:</p>
<p>:，运行时由 memory-core-host-secret 模块解析为实际凭据值。</p>
<div class="blog_h2"><span class="graybg">模型故障转移</span></div>
<p>模型故障转移（Model Failover）是 OpenClaw 应对 API 速率限制和服务中断的核心机制（详见 <a href="https://docs.openclaw.ai/concepts/model-failover">docs.openclaw.ai/concepts/model-failover</a>）。当主模型返回 429（Rate Limited）或 5xx 错误时，系统自动将请求路由到预配置的备选模型。故障转移配置在用户的 settings 文件中定义：</p>
<pre class="crayon-plain-tag">{
  "models": {
    "primary": "anthropic:claude-sonnet-4-20260514",
    "fallback": [
      "openai:gpt-4o",
      "google:gemini-2.5-pro"
    ],
    "failover": {
      "maxRetries": 2,
      "retryDelayMs": 1000,
      "fallbackOnRateLimit": true,
      "fallbackOnServerError": true
    }
  }
}</pre>
<p>故障转移逻辑在路由层（src/routing/）实现，对上层 Agent 运行时透明。路由层维护每个提供者的健康状态和速率限制窗口，在主模型不可用时按 fallback 列表顺序尝试备选模型。</p>
<div class="blog_h2"><span class="graybg">v2026.3.28 新增特性</span></div>
<p>v2026.3.28 版本对提供者系统引入了三项重要变更：</p>
<p><strong>xAI 迁移至 Responses API</strong>：xAI 提供者从传统的 Chat Completions API 迁移到 Responses API 格式，同时启用 x_search 原生网页搜索功能。Grok 模型可直接在对话中调用 xAI 的搜索基础设施，无需额外的工具调用层。</p>
<p><strong>MiniMax 图像生成</strong>：MiniMax 提供者新增 image-01 模型支持，通过 MiniMax 的图像生成 API 实现文生图能力。该功能作为提供者拥有的工具（Provider-owned Tool）注册，遵循 OpenClaw 的设计原则——提供者特有的工具和设置归属于提供者插件，而非核心系统。</p>
<p><strong>通义千问认证变更</strong>：Qwen 的 portal auth 模式被移除，统一切换为 Model Studio API Key 认证。这是一次破坏性变更，已有的 portal auth 用户需要手动迁移到 API Key 模式。</p>
<div class="blog_h2"><span class="graybg">GitHub Copilot 登录支持</span></div>
<p>OpenClaw 通过 plugin-sdk/github-copilot-login 和 plugin-sdk/github-copilot-token 两个导出模块支持 GitHub Copilot 账户登录。拥有 Copilot 订阅的用户可以直接使用 GitHub 账户认证，通过 Copilot 的基础设施访问底层模型（GPT-4o、Claude 等），无需单独配置每个提供者的 API Key。认证流程复用 GitHub 的 Device Flow OAuth，获取 Copilot token 后由 github-copilot-token 模块管理令牌刷新。</p>
<div class="blog_h1"><span class="graybg">ACP 协议</span></div>
<p><span style="background-color: #c0c0c0;">Agent Client Protocol（ACP）</span>是 OpenClaw 定义的有状态 Agent 会话协议。ACP 的核心思想是将 AI Agent 的交互从特定的聊天界面中解耦出来，使其可以通过任意通信渠道（Discord、iMessage、终端等）启动和管理有状态的 Agent 工作会话。项目依赖 @agentclientprotocol/sdk@0.17.1 提供协议的核心类型和客户端实现。</p>
<div class="blog_h2"><span class="graybg">ACPX：无头 CLI 工具</span></div>
<p><span style="background-color: #c0c0c0;">ACPX</span>（仓库 openclaw/acpx，1,834 stars）是 OpenClaw 的无头（Headless）ACP CLI 客户端。它允许用户从命令行创建、管理和交互 ACP 会话，无需图形界面。ACPX 的典型使用场景包括 CI/CD 管道中的 Agent 自动化、服务器端部署和脚本编排。</p>
<div class="blog_h2"><span class="graybg">ACP 渠道绑定</span></div>
<p>ACP 会话可以绑定到任意聊天渠道。通过 /acp spawn codex --bind here 命令，用户可以在当前渠道上下文中创建一个 ACP 会话。目前支持的绑定包括：</p>
<ul>
<li><strong>Discord</strong>：通过 Discord Bot 渠道绑定，ACP 会话映射到 Discord 线程</li>
<li><strong>BlueBubbles</strong>：macOS 上的 iMessage 桥接，ACP 会话通过 BlueBubbles API 接入 iMessage</li>
<li><strong>iMessage</strong>：直接 iMessage 绑定（仅 macOS/iOS 平台）</li>
</ul>
<p>ACP 的核心分层需要明确区分三个概念：<span style="background-color: #c0c0c0;">聊天表面（Chat Surface）</span>是用户交互的 UI 层，可以是 Discord 频道、终端窗口或 Web 界面；<span style="background-color: #c0c0c0;">ACP 会话（ACP Session）</span>是有状态的 Agent 交互上下文，维护对话历史、工作区状态和工具授权；<span style="background-color: #c0c0c0;">运行时工作区（Runtime Workspace）</span>是 Agent 实际执行操作的文件系统沙箱。一个聊天表面可以关联多个 ACP 会话，而每个 ACP 会话绑定到唯一的运行时工作区。</p>
<div class="blog_h2"><span class="graybg">MCP 集成与工具桥接</span></div>
<p>OpenClaw 集成了 <span style="background-color: #c0c0c0;">Model Context Protocol（MCP）</span>，依赖 @modelcontextprotocol/sdk@1.29.0。MCP 定义了 AI 模型与外部工具之间的标准通信协议，OpenClaw 通过 MCP 桥接层将外部 MCP 工具服务器暴露给 Agent 运行时。</p>
<p>v2026.3.31 版本引入了 <span style="background-color: #c0c0c0;">ACPX plugin-tools MCP 桥接</span>的关键安全变更：MCP 工具默认关闭（explicit default-off），必须在配置中显式启用。这一变更源于信任边界加固（Trust Boundary Hardening）的安全考量——外部 MCP 工具服务器可能执行任意代码，默认启用会扩大攻击面。启用配置示例：</p>
<pre class="crayon-plain-tag">{
  "mcp": {
    "servers": {
      "filesystem": {
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"],
        "enabled": true
      }
    },
    "trustPolicy": "prompt-per-tool"
  }
}</pre>
<p>trustPolicy 支持三个级别：prompt-per-tool（每次工具调用需用户确认）、prompt-once（首次确认后自动信任）和 trust-all（完全信任，仅建议在受控环境中使用）。</p>
<div class="blog_h2"><span class="graybg">OpenAI apply_patch 默认启用</span></div>
<p>对于 OpenAI 和 Codex 系列模型，OpenClaw 默认启用 apply_patch 工具。这是 OpenAI Codex 模型原生支持的代码编辑工具，直接通过 API 返回结构化的补丁指令（patch instruction），由 OpenClaw 的运行时执行文件修改。相较于让模型输出完整文件内容再做 diff，apply_patch 减少了输出 token 消耗，降低了大文件编辑时的错误率。apply_patch 的沙箱权限与 write 权限对齐——在非主会话的 Docker 沙箱中，apply_patch 的写入范围受到与普通文件写入相同的约束。</p>
<div class="blog_h2"><span class="graybg">CLI 后端插件：Claude CLI / Codex CLI / Gemini CLI</span></div>
<p>v2026.3.31 将三个主要 CLI 后端——<span style="background-color: #c0c0c0;">Claude CLI</span>、<span style="background-color: #c0c0c0;">Codex CLI</span> 和 <span style="background-color: #c0c0c0;">Gemini CLI</span>——的推理默认行为迁移到各自的 bundled plugin 中。通过 Plugin SDK 的 cli-backend 和 cli-runtime 导出路径，CLI 后端可以注册自定义的推理流、工具暴露和会话管理策略。这一迁移的意义在于解耦——核心不再硬编码 CLI 后端的行为，第三方插件可以通过相同的接口注册自定义 CLI 后端。</p>
<div class="blog_h2"><span class="graybg">ACP 与 Agent-to-Agent 通信</span></div>
<p>ACP 的深层价值体现在 <span style="background-color: #c0c0c0;">Agent-to-Agent（A2A）通信</span>能力上。OpenClaw 的 Session 工具集——sessions_list、sessions_history、sessions_send——允许一个 Agent 会话发现、查询和向另一个 Agent 会话发送消息。sessions_send 支持可选的 reply-back 模式（ping-pong 通信）和 announce 步骤，允许 Agent 之间进行结构化的协调对话。</p>
<p>在多 Agent 部署场景下（例如一个 Agent 负责客户对话，另一个 Agent 负责后端任务执行），A2A 通信避免了传统架构中需要外部消息队列的复杂性。所有通信都通过 Gateway 的 WebSocket 控制平面路由，Agent 之间共享同一个运行时基础设施但拥有隔离的会话上下文和工作区。</p>
<p>v2026.3.31 新增的 ACP 渠道绑定进一步扩展了这一能力：/acp spawn codex --bind here 可以将当前聊天表面直接绑定为 Codex 驱动的工作区，无需创建子线程。通过这种方式，用户可以在 Discord 频道中直接启动一个代码编写 Agent，Agent 的输出直接出现在对话流中。</p>
<div class="blog_h1"><span class="graybg">媒体管道</span></div>
<p>OpenClaw 的媒体处理管道位于 src/media/ 目录，负责所有非文本内容的预处理、理解和生命周期管理。通过 plugin-sdk 导出三个核心模块：media-runtime（运行时管道调度）、media-understanding（媒体内容理解接口）和 media-understanding-runtime（理解模块的运行时绑定）。另有 web-media 导出处理 Web 渠道的媒体特化逻辑。</p>
<div class="blog_h2"><span class="graybg">图像处理：sharp 管道</span></div>
<p>图像处理依赖 sharp@0.34.5——Node.js 生态中性能最高的图像处理库，底层使用 libvips。OpenClaw 使用 sharp 执行以下处理：</p>
<ul>
<li>缩放（Resize）：将用户上传的图像缩放到模型支持的最大分辨率，避免浪费 token 或超出 API 限制</li>
<li>格式转换：将 BMP、TIFF、WebP 等格式统一转换为 JPEG 或 PNG，确保所有提供者都能接收</li>
<li>元数据剥离：移除 EXIF 信息中的地理位置、设备信息等隐私数据</li>
<li>缩略图生成：为 UI 显示生成低分辨率预览</li>
</ul>
<p>文件类型检测使用 file-type@22.0.0，通过魔数（Magic Number）而非文件扩展名判断文件类型，防止恶意文件伪装。</p>
<div class="blog_h2"><span class="graybg">PDF 处理</span></div>
<p>PDF 处理依赖 pdfjs-dist@5.6.205（Mozilla PDF.js 的 npm 发行版）。处理流程包括文本提取、页面渲染为图像（用于多模态模型的视觉理解）和结构化内容解析。对于大型 PDF，OpenClaw 实现分页处理策略——仅提取与当前对话上下文相关的页面范围，而非一次性加载整个文档。</p>
<div class="blog_h2"><span class="graybg">音频与视频处理</span></div>
<p>音频和视频处理管道处理用户上传的多媒体文件或通过语音输入采集的音频流。音频处理包括格式转换（统一为 WAV/MP3）、采样率标准化和静音检测。转录（Transcription）钩子将音频输入转换为文本，集成到 Agent 的对话流程中——语音消息被自动转录后作为文本消息处理，Agent 可以选择性地以语音或文本方式回复。</p>
<p>视频处理采用关键帧提取策略：从视频中按固定间隔或场景变化检测提取关键帧，将其作为图像序列发送给多模态模型进行理解，避免处理完整视频流的高昂计算成本。</p>
<div class="blog_h2"><span class="graybg">大小限制与临时文件生命周期</span></div>
<p>每个渠道（Channel）可独立配置媒体文件的大小上限。例如 Discord 渠道的配置：</p>
<pre class="crayon-plain-tag">{
  "channels": {
    "discord": {
      "mediaMaxMb": 25
    },
    "web": {
      "mediaMaxMb": 100
    },
    "cli": {
      "mediaMaxMb": 500
    }
  }
}</pre>
<p>超过限制的文件在预处理阶段即被拒绝，不进入管道后续环节。临时文件（处理过程中的中间产物）遵循严格的生命周期管理：每个媒体处理任务创建独立的临时目录，处理完成后无论成功还是失败都会清理。media-runtime 模块维护一个临时文件注册表，进程退出时执行兜底清理（cleanup-on-exit），防止磁盘泄漏。</p>
<div class="blog_h2"><span class="graybg">媒体理解与多模态接入</span></div>
<p>media-understanding 和 media-understanding-runtime 两个 SDK 导出路径定义了媒体内容理解的接口和运行时实现。媒体理解不仅仅是格式转换——它将图像、文档和音频转化为模型可以消费的结构化输入。对于图像，理解管道提取图中的文字（OCR）、识别对象和场景；对于 PDF，它生成页面摘要和结构化的段落索引；对于音频，它输出时间戳标注的转录文本。</p>
<p>多模态理解的输出格式遵循各模型提供者的要求。OpenAI 的 GPT-4o 和 Anthropic 的 Claude Sonnet 4 接受 base64 编码的图像嵌入消息体；Google Gemini 支持更大的媒体文件通过 File API 上传后引用。media-understanding-runtime 的职责是根据当前活跃的模型提供者，选择最优的编码和传输策略。</p>
<div class="blog_h2"><span class="graybg">媒体管道的可读性阅读器</span></div>
<p>OpenClaw 集成了 @mozilla/readability@0.6.0（Mozilla 的可读性提取库）和 linkedom@0.18.12（轻量 DOM 实现），用于从网页内容中提取正文。当 Agent 使用浏览器工具访问网页时，原始 HTML 经 linkedom 解析后，Readability 算法提取核心正文内容，剥除导航栏、广告、侧边栏等噪声元素。提取后的纯文本进入 Agent 的上下文窗口，相比注入原始 HTML 大幅减少 token 消耗。</p>
<p>Markdown 渲染由 markdown-it@14.1.1 处理。Agent 输出的 Markdown 格式回复在发送到各渠道前，根据目标渠道的能力进行格式适配：Discord 原生支持 Markdown，Telegram 支持部分 Markdown 子集，WhatsApp 使用 WhatsApp 风格的文本格式化，而 SMS/iMessage 则退化为纯文本。</p>
<div class="blog_h1"><span class="graybg">语音系统</span></div>
<p>OpenClaw 的语音系统覆盖了从语音唤醒到语音合成的完整链路。plugin-sdk 导出三个语音模块：speech（公共接口）、speech-core（核心实现）和 speech-runtime（运行时绑定）。语音功能根据平台和交互模式分为四种形态。</p>
<div class="blog_h2"><span class="graybg">Voice Wake：macOS/iOS 唤醒词</span></div>
<p><span style="background-color: #c0c0c0;">Voice Wake</span>（详见 <a href="https://docs.openclaw.ai/nodes/voicewake">docs.openclaw.ai/nodes/voicewake</a>）是 macOS 和 iOS 平台的唤醒词功能。设备持续监听环境音频，检测到预设唤醒词后激活 Agent 会话。唤醒词检测在设备端本地运行，不向云端发送音频流——这与 OpenClaw 的本地优先原则一致。</p>
<p>唤醒后的消息转发通过 <span style="background-color: #c0c0c0;">VoiceWakeForwarder</span> 实现。用户语音经本地语音识别转为文本后，VoiceWakeForwarder 调用 OpenClaw 的 CLI 接口将文本传递给 Agent：</p>
<pre class="crayon-plain-tag">openclaw-mac agent --message "${text}" --thinking low</pre>
<p>VoiceWakeForwarder 的实现中需要特别处理 Shell 转义（Shell Escaping）：用户语音转录文本可能包含引号、美元符号、反引号等 Shell 特殊字符，直接拼接到命令行会导致注入风险或解析错误。转发器对文本进行严格的 Shell 转义后再传递。--thinking low 参数指示 Agent 使用低延迟思考模式，优先响应速度而非推理深度，适配语音交互对实时性的要求。</p>
<div class="blog_h2"><span class="graybg">Talk Mode：Android 持续语音</span></div>
<p><span style="background-color: #c0c0c0;">Talk Mode</span>（详见 <a href="https://docs.openclaw.ai/nodes/talk">docs.openclaw.ai/nodes/talk</a>）是 Android 平台的持续语音对话模式。与 Voice Wake 的"唤醒→单次交互"模式不同，Talk Mode 维持一个持续开放的语音通道——用户和 Agent 可以进行多轮语音对话，无需每轮重新唤醒。Talk Mode 使用 VAD（Voice Activity Detection，语音活动检测）自动判断用户发言的起止，实现自然的对话节奏。</p>
<div class="blog_h2"><span class="graybg">Push-to-Talk：macOS 覆盖层</span></div>
<p>macOS 平台还提供 <span style="background-color: #c0c0c0;">Push-to-Talk</span> 模式，以系统级覆盖层（Overlay）的形式运行。用户通过长按快捷键激活麦克风输入，松开后结束录音并发送。这种模式适合在桌面工作流中快速提问，无需切换到 OpenClaw 的窗口。覆盖层使用 AppKit 的 NSPanel 实现，设置为浮动在所有窗口之上。</p>
<div class="blog_h2"><span class="graybg">TTS：ElevenLabs 与系统回退</span></div>
<p>语音合成（TTS，Text-to-Speech）采用双层策略。首选方案是 <span style="background-color: #c0c0c0;">ElevenLabs</span> 的 API，提供高质量、低延迟、多语言的语音合成。当 ElevenLabs 不可用（网络离线或未配置 API Key）时，系统自动回退到平台原生 TTS：macOS 使用 AVSpeechSynthesizer，iOS 使用 AVSpeechSynthesizer（同一框架），Android 使用 android.speech.tts.TextToSpeech。</p>
<p>此外，OpenClaw 还集成了 node-edge-tts@1.2.10 作为第三层 TTS 后端。Edge TTS 调用 Microsoft Edge 浏览器的在线 TTS 服务，免费且支持多语言多音色，在无 ElevenLabs 订阅但有网络连接的场景下是实用的中间选项。</p>
<div class="blog_h2"><span class="graybg">Voice Call 插件与闭环测试</span></div>
<p>Voice Call 插件打包在 extensions/ 目录中，作为内置扩展随 OpenClaw 分发。它实现了完整的语音通话功能——用户可以像打电话一样与 Agent 进行实时语音对话，双向音频流通过 WebRTC 或平台原生音频框架传输。</p>
<p>语音通话的质量保证依赖 <span style="background-color: #c0c0c0;">闭环测试（Closed-Loop Testing）</span>。测试脚本通过 test:voicecall:closedloop npm script 执行，流程如下：自动生成测试文本 → TTS 合成为音频 → 音频作为输入馈送给语音通话管道 → Agent 处理并生成回复 → TTS 合成回复音频 → 转录回复音频为文本 → 对比原始文本与回复内容的语义一致性。这种端到端闭环消除了人工测试的不确定性，确保语音管道中每个环节（ASR → 推理 → TTS）都正常工作。</p>
<pre class="crayon-plain-tag"># 执行语音通话闭环测试
pnpm test:voicecall:closedloop

# 测试流程：
# 1. 生成测试 prompt
# 2. TTS 合成输入音频
# 3. 注入音频到 voice call 管道
# 4. 等待 Agent 响应
# 5. 捕获 TTS 输出音频
# 6. ASR 转录输出
# 7. 断言：输出文本与预期语义匹配</pre>
<p>整个语音系统体现了 OpenClaw 的多端一致性追求：同一个 Agent 可以通过唤醒词、持续语音、按键说话或语音通话四种方式接收语音输入，通过 ElevenLabs、Edge TTS 或系统原生 TTS 三种方式输出语音回复，所有组合在各个平台上的行为保持一致。语音能力不是一个附加功能，而是与文本渠道同等地位的一等交互模式。</p>
<p><!-- Chapter 13-17: OpenClaw 深度技术解析 --></p>
<div class="blog_h1"><span class="graybg">原生多端应用</span></div>
<p>OpenClaw 的多端战略并非简单的 WebView 包装。macOS、iOS、Android 三个原生客户端各自承担着差异化的职责：macOS 应用是开发者的本地控制台与调试中心，iOS 应用是移动端的轻量节点（Node），Android 应用则面向最广泛的设备指令族群。三者通过 Gateway WebSocket 协议统一通信，实现跨平台的节点注册、指令派发与画布同步。</p>
<div class="blog_h2"><span class="graybg">13.1 macOS 应用：菜单栏中枢</span></div>
<p>macOS 应用的源码位于 apps/macos/，采用 SwiftUI + AppKit 混合架构，以菜单栏常驻图标为交互入口。在 OpenClaw 内部词汇表中，macOS 应用的代号是 <span style="background-color: #c0c0c0;">makeup</span>（即 "mac app" 的谐音缩写）。</p>
<p>应用的核心功能涵盖以下几个层面：</p>
<p><span style="background-color: #c0c0c0;">Gateway 健康监控</span>：菜单栏图标实时反映 Gateway 进程状态，包括连接数、内存占用与心跳延迟。点击图标弹出的面板提供一键重启入口。Gateway 的重启必须通过 OpenClaw Mac 应用本身或 scripts/restart-mac.sh 脚本执行，而非手动在 tmux 中操作——后者会绕过进程监控链，导致状态不一致。</p>
<p><span style="background-color: #c0c0c0;">语音唤醒与 Push-to-Talk 悬浮层</span>：Voice Wake 持续监听唤醒词，PTT（Push-to-Talk）覆盖层以半透明悬浮窗的形式驻留桌面。两者共同构成语音交互的 macOS 原生入口。</p>
<p><span style="background-color: #c0c0c0;">WebChat 嵌入与调试工具</span>：内嵌 WebChat 视图支持与 Gateway 的实时对话，同时暴露调试面板用于查看消息流、工具调用日志与 token 消耗。</p>
<p><span style="background-color: #c0c0c0;">SSH 隧道远程控制</span>：macOS 应用可通过 SSH 隧道连接远程部署的 Gateway 实例，在本地菜单栏操控云端服务。</p>
<div class="blog_h3"><span class="graybg">13.1.1 SwiftUI 状态管理：Observation 框架</span></div>
<p>macOS 应用的状态管理已全面迁移至 Swift 5.9 引入的 <span style="background-color: #c0c0c0;">Observation 框架</span>，使用 @Observable 宏标记可观察类型，以 @Bindable 实现属性级的双向绑定。旧版 ObservableObject / @StateObject / @Published 模式已被显式弃用 — 任何残留的 legacy 用法都应迁移至新框架。Observation 框架的优势在于更细粒度的依赖追踪：SwiftUI 仅在被实际读取的属性发生变化时重新渲染视图，而非 ObservableObject 的整体通知模式。</p>
<pre class="crayon-plain-tag">// 正确：Observation 框架
@Observable
final class GatewayMonitor {
    var isConnected = false
    var latencyMs: Int = 0
    var sessionCount: Int = 0
}

// 错误：已弃用的旧模式，不要使用
// class GatewayMonitor: ObservableObject {
//     @Published var isConnected = false
// }</pre>
<div class="blog_h3"><span class="graybg">13.1.2 签名构建与 TCC 权限</span></div>
<p>macOS 应用需要签名构建才能使系统权限在重新编译后持久化。未签名的开发构建在每次 rebuild 后都会触发 TCC（Transparency, Consent, and Control）权限重置弹窗。打包脚本位于 scripts/package-mac-app.sh，负责代码签名、公证（Notarization）与 DMG 封装。</p>
<p>macOS 节点模式（Node Mode）暴露的系统能力通过 TCC 权限映射管控：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">节点命令</td>
<td style="text-align: center;">功能</td>
<td style="text-align: center;">TCC 权限</td>
</tr>
</thead>
<tbody>
<tr>
<td>system.run</td>
<td>执行本地命令，返回 stdout/stderr/exit code</td>
<td>needsScreenRecording 标志位</td>
</tr>
<tr>
<td>system.notify</td>
<td>发送用户通知</td>
<td>notifications</td>
</tr>
<tr>
<td>canvas.*</td>
<td>画布操作路由</td>
<td>screen-recording</td>
</tr>
<tr>
<td>camera.*</td>
<td>摄像头抓取</td>
<td>camera</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">13.1.3 统一日志系统</span></div>
<p>macOS 应用的日志通过 scripts/clawlog.sh 脚本统一查询，底层使用 macOS 的 Unified Logging 系统，支持按子系统（subsystem）过滤。常见操作：</p>
<pre class="crayon-plain-tag"># 实时跟踪所有 OpenClaw 子系统日志
./scripts/clawlog.sh --follow

# 按类别过滤
./scripts/clawlog.sh --category networking --tail 100

# 查看特定子系统
./scripts/clawlog.sh --subsystem ai.openclaw.gateway</pre>
<div class="blog_h2"><span class="graybg">13.2 iOS 应用：移动节点</span></div>
<p>iOS 应用源码位于 apps/ios/，是一个标准 Xcode 项目 + SwiftUI 工程。与 macOS 应用不同，iOS 应用的定位是作为 Gateway 的远程节点运行，通过 <span style="background-color: #c0c0c0;">Bonjour 设备发现</span>（Device Discovery）机制自动配对局域网内的 Gateway 实例，并通过 Gateway WebSocket 建立持久连接。</p>
<p>iOS 节点提供的核心能力：</p>
<p><span style="background-color: #c0c0c0;">Canvas Surface</span>：在 iOS 设备上渲染 Agent 驱动的画布内容，支持触摸交互。</p>
<p><span style="background-color: #c0c0c0;">Voice Wake 转发</span>：iOS 端的语音唤醒检测结果通过 WebSocket 转发至 Gateway，实现移动端的免触语音激活。</p>
<p><span style="background-color: #c0c0c0;">Talk Mode</span>：长按说话的语音交互模式，音频流直接传输至 Gateway 进行识别与处理。</p>
<p><span style="background-color: #c0c0c0;">Camera Snap/Clip</span>：支持拍照快照和短视频片段采集，供 Agent 的视觉能力使用。</p>
<p><span style="background-color: #c0c0c0;">Screen Recording</span>：通过 ReplayKit 进行屏幕录制，将录制内容作为上下文发送给 Agent。</p>
<p>iOS 应用的版本号维护在两个位置：apps/ios/Sources/Info.plist 和 apps/ios/Tests/Info.plist，关键字段为 CFBundleShortVersionString（展示版本号）和 CFBundleVersion（构建号）。发版时两个文件必须同步更新。</p>
<div class="blog_h2"><span class="graybg">13.3 Android 应用：全能设备节点</span></div>
<p>Android 应用位于 apps/android/，使用 Kotlin + Gradle 构建。与 iOS 应用相比，Android 节点暴露了更丰富的设备指令族群（Device Command Families），充分利用 Android 平台的开放性。</p>
<p>应用 UI 组织为三个主要标签页：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">标签页</td>
<td style="text-align: center;">功能</td>
</tr>
</thead>
<tbody>
<tr>
<td>Connect</td>
<td>设备配对入口，支持设置码（Setup Code）和手动输入两种方式</td>
</tr>
<tr>
<td>Chat Sessions</td>
<td>会话列表与聊天界面</td>
</tr>
<tr>
<td>Voice</td>
<td>语音交互控制面板</td>
</tr>
</tbody>
</table>
<p>Android 节点支持的设备指令族群是三端中最丰富的：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">指令族</td>
<td style="text-align: center;">能力</td>
</tr>
</thead>
<tbody>
<tr>
<td>notifications</td>
<td>读取/发送系统通知</td>
</tr>
<tr>
<td>location</td>
<td>GPS 定位与地理围栏</td>
</tr>
<tr>
<td>SMS</td>
<td>短信读取与发送</td>
</tr>
<tr>
<td>photos</td>
<td>相册访问与照片上传</td>
</tr>
<tr>
<td>contacts</td>
<td>通讯录读写</td>
</tr>
<tr>
<td>calendar</td>
<td>日历事件管理</td>
</tr>
<tr>
<td>motion</td>
<td>加速度计、陀螺仪等传感器数据</td>
</tr>
<tr>
<td>app update</td>
<td>应用自更新管理</td>
</tr>
</tbody>
</table>
<p>此外，Android 端同样支持 Canvas 渲染、摄像头采集与屏幕录制能力。</p>
<div class="blog_h3"><span class="graybg">13.3.1 Android 构建与测试命令</span></div>
<pre class="crayon-plain-tag"># 单元测试（Play Debug 变体）
./gradlew :app:testPlayDebugUnitTest

# 第三方集成测试
./gradlew :app:testThirdPartyDebugUnitTest

# Kotlin 代码风格检查
./gradlew :app:ktlintCheck :benchmark:ktlintCheck

# 发布 AAB 构建
bun apps/android/scripts/build-release-aab.ts</pre>
<p>版本信息定义在 apps/android/app/build.gradle.kts 中的 versionName（展示版本）和 versionCode（数字递增版本）。</p>
<div class="blog_h2"><span class="graybg">13.4 跨平台节点协议</span></div>
<p>三个原生应用通过统一的 <span style="background-color: #c0c0c0;">Gateway WebSocket 协议</span>与 Gateway 通信。节点相关的核心命令包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">命令</td>
<td style="text-align: center;">方向</td>
<td style="text-align: center;">功能</td>
</tr>
</thead>
<tbody>
<tr>
<td>node.list</td>
<td>Gateway → 客户端</td>
<td>枚举已连接的所有节点及其能力声明</td>
</tr>
<tr>
<td>node.describe</td>
<td>Gateway → 节点</td>
<td>查询指定节点的详细能力描述与参数 schema</td>
</tr>
<tr>
<td>node.invoke</td>
<td>Gateway → 节点</td>
<td>在指定节点上执行命令并返回结果</td>
</tr>
</tbody>
</table>
<p>macOS 平台的系统权限通过 TCC 框架管控，涵盖 screen-recording、notifications、camera、location 四项。每项权限与特定的节点命令能力绑定，应用会在首次调用时请求授权。</p>
<p>会话级别的权限提升通过 /elevated on|off 命令控制。开启后，当前会话获得完整的 bash 访问权限；关闭时回退到受限执行面。该命令是每个会话独立的，不影响其他并发会话。</p>
<div class="blog_h3"><span class="graybg">13.4.1 v2026.3.31 安全强化</span></div>
<p><span style="background-color: #c0c0c0;">v2026.3.31</span> 引入了两项与节点安全相关的破坏性变更。第一，节点命令在设备配对（Device Pairing）完成后不再自动启用 — 必须经过显式的<span style="background-color: #c0c0c0;">节点配对审批</span>（Node Pairing Approval）后，节点命令才会暴露给 Agent。设备配对仅建立了 WebSocket 连接通道，而节点配对审批确认了用户对该设备能力暴露的授权意图。</p>
<p>第二，由节点端发起的运行（Node-originated Runs）被限制在缩减的可信执行面（Reduced Trusted Surface）上。即使节点本身拥有完整能力，从节点侧主动触发的执行流只能使用预定义的安全工具子集。</p>
<p><!-- Chapter 14 --></p>
<div class="blog_h1"><span class="graybg">Live Canvas</span></div>
<p><span style="background-color: #c0c0c0;">Live Canvas</span> 是 OpenClaw Gateway 托管的 Agent 驱动可视化工作区。与传统的静态输出不同，Live Canvas 是持久性的交互画面 — Agent 可以在其上推送内容、重置状态、执行脚本、捕获快照。Canvas 的跨端渲染由原生应用（macOS、iOS SwiftUI、Android）各自实现，而画布操控逻辑通过 <span style="background-color: #c0c0c0;">A2UI</span>（Agent to UI）协议统一抽象。</p>
<div class="blog_h2"><span class="graybg">14.1 A2UI 协议与构建</span></div>
<p>A2UI 定义了 Agent 向 UI 层发送控制指令的协议规范。Canvas 宿主的 A2UI 实现位于 src/canvas-host/a2ui/ 目录。该实现会被打包为一个独立的 bundle，由 Gateway 在运行时加载并注入到 Canvas 宿主容器中。</p>
<p>bundle 的构建产物通过哈希文件 src/canvas-host/a2ui/.bundle.hash 进行版本追踪（自动生成，不应手动编辑）。构建命令有两种等价形式：</p>
<pre class="crayon-plain-tag"># 通过 pnpm script
pnpm canvas:a2ui:bundle

# 通过 shell 脚本
scripts/bundle-a2ui.sh</pre>
<p>A2UI bundle 的构建是整体 pnpm build 流水线的第一步。完整的构建管线为：</p>
<pre class="crayon-plain-tag">pnpm build
# 等价于：
# 1. pnpm canvas:a2ui:bundle
# 2. tsdown-build.mjs
# 3. runtime-postbuild.mjs</pre>
<p>A2UI 的 vendor 源码维护在 vendor/a2ui 目录，原生端的共享封装层位于 apps/shared/OpenClawKit/Tools/CanvasA2UI。</p>
<div class="blog_h3"><span class="graybg">14.1.1 跨编译注意事项</span></div>
<p>A2UI bundle 的构建在交叉编译环境下可能失败。典型场景是在 Apple Silicon 上通过 QEMU 构建 amd64 目标 — 这种情况下 A2UI 的构建步骤可能因 QEMU 对某些指令集的模拟不完整而崩溃。Dockerfile 中已对此做了防护处理：当 A2UI bundle 构建失败时，会创建一个存根（stub）文件代替，确保 Docker 镜像的整体构建不会中断。这意味着 QEMU 交叉编译产出的镜像可能不包含完整的 Canvas 功能。</p>
<div class="blog_h2"><span class="graybg">14.2 Canvas 操作原语</span></div>
<p>Canvas 的操作模型由四个核心原语组成：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">操作</td>
<td style="text-align: center;">语义</td>
<td style="text-align: center;">典型用途</td>
</tr>
</thead>
<tbody>
<tr>
<td>canvas.push</td>
<td>向 Canvas 追加内容（HTML/JS/CSS 片段）</td>
<td>增量构建 UI 界面</td>
</tr>
<tr>
<td>canvas.reset</td>
<td>清空 Canvas 并重新初始化</td>
<td>切换上下文或重置状态</td>
</tr>
<tr>
<td>canvas.eval</td>
<td>在 Canvas 上下文中执行任意 JavaScript</td>
<td>动态交互逻辑、数据可视化</td>
</tr>
<tr>
<td>canvas.snapshot</td>
<td>捕获当前 Canvas 的视觉快照</td>
<td>记录状态、生成截图反馈</td>
</tr>
</tbody>
</table>
<p>其中 canvas.eval 的安全定位需要特别说明。它被归类为 <span style="background-color: #c0c0c0;">Operator 控制面</span>（Operator Control Surface）— 意味着其安全性由部署运营方（Operator）负责，而非 OpenClaw 平台本身。Agent 通过 canvas.eval 可以执行任意 JavaScript 代码，这赋予了极大的灵活性，但也意味着 Operator 必须在自己的部署环境中建立相应的安全防线。</p>
<div class="blog_h2"><span class="graybg">14.3 Canvas 与节点模式</span></div>
<p>在多端架构中，Canvas 操作通过节点模式暴露。所有 canvas.* 调用都被路由为 node.invoke 指令发送到对应的端侧节点执行。这意味着 Agent 可以指定在特定设备（例如用户的 iPad 或 Android 手机）上渲染画布内容，实现跨设备的视觉工作流编排。</p>
<p>三个平台对 Canvas 的渲染实现各有差异：macOS 使用 WebKit 视图，iOS 使用 SwiftUI 原生视图层结合 WebKit 渲染，Android 使用 Android WebView。但上层的 A2UI 协议保证了 Agent 无需关心底层渲染差异。</p>
<div class="blog_h2"><span class="graybg">A2UI 构建管线与交叉编译</span></div>
<p>A2UI 的 bundle 文件位于 src/canvas-host/a2ui/a2ui.bundle.js，其哈希值记录在 src/canvas-host/a2ui/.bundle.hash（自动生成，不应手动编辑）。构建命令为 pnpm canvas:a2ui:bundle 或 scripts/bundle-a2ui.sh。在 pnpm build 的完整构建管线中，A2UI bundle 作为第一步执行。</p>
<p>交叉编译是一个已知痛点。在 Apple Silicon 上构建 amd64 镜像时，A2UI bundle 可能因 QEMU 模拟环境的限制而失败。Dockerfile 对此做了优雅降级处理：bundle 失败时创建一个 stub 文件（包含注释 /* A2UI bundle unavailable in this build */），并同时清理 vendor/a2ui 和 apps/shared/OpenClawKit/Tools/CanvasA2UI 目录，确保构建不会因 A2UI 不可用而中断。CI 构建在原生架构上执行，因此不受此影响。</p>
<div class="blog_h2"><span class="graybg">Canvas 的安全定位</span></div>
<p>Canvas 的安全定位是理解其设计边界的关键。canvas.eval 允许 Agent 在 Canvas 中执行任意 JavaScript 代码，这在功能上等同于一个浏览器端的 eval()。OpenClaw 将此明确归类为 <span style="background-color: #c0c0c0;">Operator 控制面</span>——与浏览器自动化工具中的脚本执行类似，安全责任在于部署者而非平台。这种定位与 OpenClaw 的单用户、本地优先架构一致：在用户自己的设备上，Agent 本就拥有与用户相当的权限。但在多租户或公开部署场景中，Operator 必须评估 Canvas eval 带来的风险并做出适当限制。</p>
<p><!-- Chapter 15 --></p>
<div class="blog_h1"><span class="graybg">Lobster</span></div>
<p><span style="background-color: #c0c0c0;">Lobster</span> 是 OpenClaw 生态中的工作流编排 Shell，独立仓库位于 openclaw/lobster（992 stars）。其定位口号是 "OpenClaw native workflow Shell" — 一个为 OpenClaw 原生设计的工作流执行环境。</p>
<div class="blog_h2"><span class="graybg">15.1 类型化 JSON 管线</span></div>
<p>Lobster 的核心抽象是<span style="background-color: #c0c0c0;">类型化 JSON 管线</span>（Typed JSON Pipelines）。与 Unix shell 的文本管道不同，Lobster 管线中流动的数据是带有类型约束的 JSON 结构。每个管线步骤声明其输入 schema 和输出 schema，Lobster 在管线组装阶段即可进行类型检查，而非等到运行时才暴露类型不匹配的问题。</p>
<p>管线架构是可组合的（Composable Pipeline Architecture）：开发者可以将 OpenClaw 的 Skills 和 Tools 链接为多步骤工作流。每个步骤可以是一个 Skill 调用、一个 Tool 执行、一段自定义逻辑，或者一个嵌套的子管线。</p>
<p>关键的流控机制是<span style="background-color: #c0c0c0;">审批门</span>（Approval Gates）。管线中可以在任意步骤之间插入审批点，执行会暂停并等待指定审批人（人类或其他 Agent）的确认后才继续推进。这对于涉及敏感操作的自动化流程至关重要 — 例如在部署管线中，代码编译步骤自动执行，但推送至生产环境之前需要人工审批。</p>
<div class="blog_h2"><span class="graybg">15.2 终端色彩调色板</span></div>
<p>Lobster 对终端输出的视觉一致性有严格要求。色彩定义集中在 src/terminal/palette.ts 模块，该模块导出一套共享的终端颜色调色板（Terminal Color Palette）。所有面向终端的输出 — 包括 onboarding 引导流程、配置提示（config prompts）与 TTY UI 输出 — 都必须引用调色板中定义的颜色常量，严禁在代码中硬编码颜色值。</p>
<pre class="crayon-plain-tag">// src/terminal/palette.ts
export const palette = {
  primary: chalk.hex('#5B8DEF'),
  success: chalk.hex('#6BCB77'),
  warning: chalk.hex('#FFD93D'),
  error:   chalk.hex('#FF6B6B'),
  muted:   chalk.gray,
  highlight: chalk.bold.white,
  // ... 更多颜色定义
} as const;</pre>
<p>这一设计确保了 Lobster 在不同终端模拟器和配色方案下的视觉一致性，同时简化了主题定制。</p>
<div class="blog_h2"><span class="graybg">15.3 Caclawphony：自主执行编排</span></div>
<p><span style="background-color: #c0c0c0;">Caclawphony</span>（仓库 openclaw/caclawphony，34 stars）是构建在 Lobster 之上的 Symphony 系统。它的核心能力是将项目级任务分解为相互隔离的自主执行单元（Isolated Autonomous Execution Runs）。每个执行单元拥有独立的上下文、工具集与沙箱环境，多个单元可以并行运行。</p>
<p>Caclawphony 适用于大规模项目重构、批量代码迁移等需要将工作分而治之的场景。项目经理（人类或 Agent）在顶层定义任务分解策略，Caclawphony 负责将其转化为可并行执行的 Lobster 管线集合。</p>
<p>Caclawphony 与 OpenClaw 主仓库的 Session 工具形成互补：sessions_send 提供 Agent 间的点对点通信，而 Caclawphony 提供的是任务级的编排框架——它关心的是"哪些工作单元需要并行/串行执行"，而非"Agent A 如何向 Agent B 发消息"。两者结合，构成了从单次对话到复杂项目执行的完整 Agent 协作栈。</p>
<div class="blog_h2"><span class="graybg">Lobster 的设计哲学</span></div>
<p>Lobster 的名称选择（龙虾）并非随意。在 OpenClaw 的概念体系中，龙虾象征着两个工程理念：其一，龙虾的螯足（claw）代表工具——Lobster 管线中的每个步骤都是一个可独立运行的工具调用；其二，龙虾的蜕壳（molt）代表版本演进——管线可以在保持外部接口不变的情况下替换内部实现。</p>
<p>Lobster 管线与 Unix 管道的另一个关键区别在于<strong>错误处理语义</strong>。Unix 管道中，上游命令的非零退出码可以被下游忽略（除非设置了 set -o pipefail）。Lobster 管线的每个步骤都必须显式声明其错误处理策略：fail-fast（任何错误立即终止整条管线）、retry（按指数退避重试）、skip（记录错误但继续执行）或 fallback（切换到备选步骤）。这种显式的错误处理语义使得 Lobster 管线在可靠性上远超 Shell 脚本。</p>
<div class="blog_h2"><span class="graybg">15.4 Lobster 与 Cron 的关系</span></div>
<p>Lobster 管线可以与 OpenClaw 的 Cron 调度系统结合，实现定时自动化。Cron 负责触发时机，Lobster 负责执行逻辑。典型应用包括：每日凌晨执行代码质量扫描管线、每周生成项目状态报告管线、在特定事件触发后延迟执行清理管线等。Cron 触发器将 Lobster 管线 ID 作为执行载荷传递，Gateway 的调度器负责在指定时间实例化管线并开始执行。</p>
<div class="blog_h1"><span class="graybg">Web UI 与浏览器控制</span></div>
<div class="blog_h2"><span class="graybg">Control UI：Lit 3 + Vite</span></div>
<p>OpenClaw 的 Web 管理界面——<span style="background-color: #c0c0c0;">Control UI</span>——直接由 Gateway 进程托管和分发，不需要独立的前端服务器。UI 源码位于 ui/ 目录，使用 <span style="background-color: #c0c0c0;">Lit 3</span>（Google 的 Web Components 库）构建，以 <span style="background-color: #c0c0c0;">Vite</span> 作为开发服务器和构建工具。构建命令为 pnpm ui:build，产出物嵌入到 Gateway 的静态资源路径中。</p>
<p>选择 Lit 而非 React/Vue/Svelte 体现了 OpenClaw 的工程偏好：Lit 基于 Web Components 标准，无需虚拟 DOM 运行时，产出的 bundle 体积极小，且与 Gateway 的原生 HTTP 服务天然兼容。Control UI 的功能覆盖会话管理、渠道状态监控、配置编辑、Skills 管理和 Agent 交互。UI 构建系统支持信号（Signals）响应式模式，通过 @lit-labs/signals@0.2.0 和 signal-utils@0.21.1 实现细粒度的 UI 更新。</p>
<p>UI 还有独立的测试流水线 pnpm test:ui，以及专门的 lint 规则 lint:ui:no-raw-window-open 防止在 UI 代码中使用原始的 window.open()（应使用框架提供的安全包装）。</p>
<div class="blog_h2"><span class="graybg">WebChat：基于 Gateway WebSocket 的对话界面</span></div>
<p><span style="background-color: #c0c0c0;">WebChat</span>（详见 <a href="https://docs.openclaw.ai/web/webchat">docs.openclaw.ai/web/webchat</a>）是 Control UI 中内嵌的对话界面，直接使用 Gateway 的 WebSocket 连接——无需独立的 WebChat 端口或额外配置。安装完 Gateway 后，用户在浏览器中访问 http://localhost:18789 即可开始与 Agent 对话。</p>
<p>WebChat 同时也是 macOS App 的内嵌 Web 视图，通过 macOS 的 WebKit 视图直接加载。这种架构复用确保了 Web 端和 macOS 端的对话体验一致。</p>
<div class="blog_h2"><span class="graybg">浏览器控制工具：Playwright + 专属 Chromium</span></div>
<p>OpenClaw 的浏览器控制工具（详见 <a href="https://docs.openclaw.ai/tools/browser">docs.openclaw.ai/tools/browser</a>）是核心工具体系中最复杂的模块之一。它使用 playwright-core@1.58.2 通过 CDP（Chrome DevTools Protocol）控制一个专属的 Chromium 实例——不是用户的日常浏览器，而是 OpenClaw 管理的独立实例，拥有独立的浏览器配置文件（Profile）。</p>
<p>浏览器控制的核心能力包括：</p>
<ul>
<li><strong>页面快照（Snapshots）</strong>：捕获页面的 DOM 状态和视觉渲染，供 Agent 分析页面内容</li>
<li><strong>结构化操作（Actions）</strong>：点击、填写表单、滚动、导航——Agent 通过结构化指令驱动浏览器，而非注入自由 JavaScript</li>
<li><strong>文件上传</strong>：Agent 可以指示浏览器在文件选择器中上传指定文件</li>
<li><strong>多 Profile 隔离</strong>：不同任务可以使用不同的浏览器配置文件，保持 Cookie 和登录状态的隔离</li>
</ul>
<p>浏览器工具的配置通过 JSON 声明：</p>
<pre class="crayon-plain-tag">{
  "browser": {
    "enabled": true,
    "color": "#FF4500"
  }
}</pre>
<p>color 参数控制浏览器窗口的标题栏颜色——这是一个细节设计，当 Agent 控制的浏览器窗口出现在屏幕上时，用户可以通过颜色快速区分它与自己的日常浏览器。</p>
<p>Docker 镜像构建时，可以通过 --build-arg OPENCLAW_INSTALL_BROWSER=1 预安装 Chromium 和 Xvfb（X Virtual Frame Buffer），增加约 300MB 镜像体积，但省去了每次容器启动时 60-90 秒的 Playwright 安装时间。这对 CI/CD 场景尤其重要。</p>
<div class="blog_h2"><span class="graybg">工具体系概览</span></div>
<p>OpenClaw 的<span style="background-color: #c0c0c0;">一等工具（First-class Tools）</span>是平台核心能力的直接延伸，区别于通过 Skills 或 MCP 接入的第三方工具。一等工具直接集成在 Gateway 和 Agent 运行时中，享有完整的安全策略和沙箱支持：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">工具</td>
<td style="text-align: center;">能力</td>
<td style="text-align: center;">文档</td>
</tr>
</thead>
<tbody>
<tr>
<td><span style="background-color: #c0c0c0;">Browser</span></td>
<td>专属 Chromium 控制、CDP 快照、结构化操作、文件上传</td>
<td><a href="https://docs.openclaw.ai/tools/browser">docs.openclaw.ai/tools/browser</a></td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;">Canvas</span></td>
<td>A2UI 驱动的可视化工作区（push/reset/eval/snapshot）</td>
<td><a href="https://docs.openclaw.ai/platforms/mac/canvas">docs.openclaw.ai/platforms/mac/canvas</a></td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;">Nodes</span></td>
<td>设备端操作：camera snap/clip、screen record、location.get、notifications</td>
<td><a href="https://docs.openclaw.ai/nodes">docs.openclaw.ai/nodes</a></td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;">Cron</span></td>
<td>定时任务调度与自动化触发</td>
<td><a href="https://docs.openclaw.ai/automation/cron-jobs">docs.openclaw.ai/automation/cron-jobs</a></td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;">Sessions</span></td>
<td>sessions_list / sessions_history / sessions_send（Agent 间通信）</td>
<td><a href="https://docs.openclaw.ai/concepts/session-tool">docs.openclaw.ai/concepts/session-tool</a></td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;">Webhooks</span></td>
<td>接收外部 HTTP 回调并触发 Agent 处理</td>
<td><a href="https://docs.openclaw.ai/automation/webhook">docs.openclaw.ai/automation/webhook</a></td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;">Gmail Pub/Sub</span></td>
<td>Gmail 邮件到达事件驱动</td>
<td><a href="https://docs.openclaw.ai/automation/gmail-pubsub">docs.openclaw.ai/automation/gmail-pubsub</a></td>
</tr>
<tr>
<td><span style="background-color: #c0c0c0;">Discord/Slack Actions</span></td>
<td>平台原生交互（斜杠命令、按钮、下拉菜单）</td>
<td>渠道文档内嵌</td>
</tr>
</tbody>
</table>
<p>在沙箱模式下，工具的可用性受到严格约束。非主会话（non-main sessions）的 Docker 沙箱中，默认<strong>允许</strong>的工具包括 bash、process、read、write、edit 以及 sessions 系列；默认<strong>禁止</strong>的工具包括 browser、canvas、nodes、cron、discord、gateway。这种白名单+黑名单的双层控制确保了多租户场景下的安全隔离。</p>
<p><!-- Chapter 16 --></p>
<div class="blog_h1"><span class="graybg">安全模型</span></div>
<p>OpenClaw 的安全模型覆盖了从消息入口到执行环境的完整链路。本章从 DM（Direct Message）配对的接入控制讲起，经过沙箱隔离的执行边界，到安全基础设施与凭证管理，系统性地拆解 OpenClaw 的安全架构。</p>
<div class="blog_h2"><span class="graybg">16.1 DM 配对与接入控制</span></div>
<p>OpenClaw 的默认 DM 安全策略（dmPolicy）设置为 <span style="background-color: #c0c0c0;">"pairing"</span>。在此模式下，任何未知发送者发起的 DM 会话都会收到一个配对码（Pairing Code），用户需要在服务器端通过 CLI 确认才能建立信任关系。</p>
<p>三种 DM 策略模式的对比：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">模式</td>
<td style="text-align: center;">行为</td>
<td style="text-align: center;">安全级别</td>
</tr>
</thead>
<tbody>
<tr>
<td>pairing（默认）</td>
<td>未知发送者收到配对码，需管理员批准</td>
<td>高</td>
</tr>
<tr>
<td>allowlist</td>
<td>仅白名单中的用户可发起 DM</td>
<td>高</td>
</tr>
<tr>
<td>open</td>
<td>接受所有 DM（需同时配置 allowFrom: "*"）</td>
<td>低</td>
</tr>
</tbody>
</table>
<p>配对审批通过 CLI 命令完成：</p>
<pre class="crayon-plain-tag">openclaw pairing approve  <code></pre>
<p>每个渠道的允许列表通过 allowFrom 字段独立配置。例如 channels.telegram.allowFrom、channels.discord.allowFrom 分别控制 Telegram 和 Discord 渠道的准入名单。</p>
<p>公开 DM 访问需要双重显式授权：dmPolicy="open" 和 allowFrom 数组中包含 "*" 通配符。仅设置其中一项不会开放公开访问 — 这是一项有意为之的双重门控设计，防止配置笔误导致意外暴露。</p>
<p>openclaw doctor 会主动检测并告警风险或错误的 DM 策略配置，包括但不限于：open 模式下缺少 allowFrom 通配符、allowlist 模式下白名单为空等异常情况。</p>
<p>历史遗留的配置键名 channels.discord.dm.policy 已迁移为 channels.discord.dmPolicy。旧格式在当前版本仍可被识别，但会触发弃用警告。</p>
<div class="blog_h2"><span class="graybg">16.2 沙箱隔离</span></div>
<p>OpenClaw 的沙箱策略通过 agents.defaults.sandbox.mode 配置。推荐的默认值为 <span style="background-color: #c0c0c0;">"non-main"</span>，意味着非主会话（群组会话、频道会话等）自动进入沙箱隔离环境。</p>
<p>这一设计基于 OpenClaw 的<span style="background-color: #c0c0c0;">单用户设计假设</span>：主会话（Main Session）的操作者是服务的所有者，拥有完整的宿主机访问权限，工具在宿主机上直接执行。而非主会话来自外部用户，每个会话在独立的 Docker 沙箱容器中执行，彼此以及与宿主机之间完全隔离。</p>
<p>沙箱内的工具可用性由白名单和黑名单双重控制：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">类别</td>
<td style="text-align: center;">工具列表</td>
</tr>
</thead>
<tbody>
<tr>
<td>沙箱白名单（允许使用）</td>
<td>bash, process, read, write, edit, sessions_list, sessions_history, sessions_send, sessions_spawn</td>
</tr>
<tr>
<td>沙箱黑名单（禁止使用）</td>
<td>browser, canvas, nodes, cron, discord, gateway</td>
</tr>
</tbody>
</table>
<p>黑名单中的工具若在沙箱会话中被调用，将返回明确的权限拒绝错误而非静默忽略。</p>
<div class="blog_h2"><span class="graybg">16.3 安全基础设施</span></div>
<p>OpenClaw 的安全策略文档维护在独立仓库 openclaw/trust（35 stars），对外发布于 trust.openclaw.ai。该仓库包含完整的威胁模型（Threat Model）文档。安全漏洞报告通过 security@openclaw.ai 接收。</p>
<div class="blog_h3"><span class="graybg">16.3.1 SSRF 防护</span></div>
<p>OpenClaw Plugin SDK 导出 ssrf-runtime 模块，供插件在发起网络请求时使用。该模块会校验目标地址，阻止对内网地址（RFC 1918）、环回地址、链路本地地址以及云元数据端点的访问，从而防范 SSRF（Server-Side Request Forgery，服务端请求伪造）攻击。所有插件的网络调用都应通过此模块路由，而非直接使用 fetch 或 http 模块。</p>
<div class="blog_h3"><span class="graybg">16.3.2 Prompt Injection 的立场</span></div>
<p>OpenClaw 将 Prompt Injection（提示注入）正式声明为<span style="background-color: #c0c0c0;">范围外</span>（Out of Scope）— 不将其视为安全漏洞。这一立场基于现实考量：当前 LLM 架构下不存在可靠的 prompt injection 防御手段，将其纳入漏洞范围只会制造虚假的安全承诺。相应地，canvas.eval 和浏览器脚本执行均被归类为 Operator 控制面，安全边界由部署方自行划定。</p>
<div class="blog_h2"><span class="graybg">16.4 插件安装安全</span></div>
<p>插件安装流程的安全管控在 v2026.3.28 至 v2026.3.31 之间经历了显著强化。</p>
<p>安装流程中的 before_install 钩子为安全扫描器提供了集成点。任何外部安全扫描工具都可以注册为 before_install 处理器，在插件代码落盘前进行检查。</p>
<p><span style="background-color: #c0c0c0;">v2026.3.31 破坏性变更</span>：内置的危险代码检测器现在对 "critical" 级别的发现默认执行<span style="background-color: #c0c0c0;">关闭失败</span>（Fail Closed）策略。此前，critical 级别的发现仅生成警告，管理员可以选择忽略。新策略下，标记为 critical 的发现会直接阻止安装。如需强制安装已标记的插件，必须使用显式的覆盖参数：</p>
<pre class="crayon-plain-tag">openclaw plugin install  --dangerously-force-unsafe-install</pre>
<p>该参数名称的冗长是有意为之 — 让每次使用都足够刻意，避免误操作。Skills 安装和 Plugins 安装都受到相同的扫描门控约束。</p>
<div class="blog_h2"><span class="graybg">16.5 Gateway 认证强化（v2026.3.31）</span></div>
<p>v2026.3.31 对 Gateway 的认证机制进行了多项收紧：</p>
<p><span style="background-color: #c0c0c0;">trusted-proxy 模式</span>拒绝混合共享令牌配置（Mixed Shared-token Configs）。如果检测到多个服务共用同一认证令牌，Gateway 将拒绝启动并报告配置冲突。</p>
<p><span style="background-color: #c0c0c0;">local-direct 回退模式</span>现在要求显式配置令牌。此前，同一主机上的连接可以隐式获得认证（Implicit Same-host Auth），这在多租户部署场景下存在风险。新版本移除了这一隐式信任，所有连接都必须提供有效令牌。</p>
<p>节点配对审批（Node Pairing Approval）成为强制前提 — 节点命令直到配对审批完成后才暴露。节点发起的运行（Node-originated Runs）被限制在缩减的可信执行面上。</p>
<div class="blog_h2"><span class="graybg">16.6 凭证管理</span></div>
<p>OpenClaw 的凭证统一存储于 ~/.openclaw/credentials/ 目录。Web 服务提供商的凭证刷新通过 openclaw login 命令重新执行 OAuth 流程。</p>
<p>Provider 插件中的密钥引用使用 <span style="background-color: #c0c0c0;">SecretRef</span> 语义 — 配置文件中仅存储密钥的引用标识符而非明文值，运行时由凭证管理器解析为实际密钥。这一设计确保配置文件可以安全地纳入版本控制。</p>
<p>关于内容安全的基本原则：永远不要提交真实的电话号码、视频文件或生产环境配置值到代码仓库中。</p>
<p><!-- Chapter 17 --></p>
<div class="blog_h1"><span class="graybg">构建与测试</span></div>
<p>OpenClaw 的构建与测试基础设施是其工程纪律的集中体现。本章逐一拆解构建工具链的选型、198 个 npm 脚本的分类、测试基础设施的架构设计以及代码质量门控的实施细节。</p>
<div class="blog_h2"><span class="graybg">17.1 构建工具链</span></div>
<p>OpenClaw 的构建工具选型刻意回避了主流的 webpack/rollup/esbuild 全家桶模式，转而采用一套更专注的工具组合：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">工具</td>
<td style="text-align: center;">版本</td>
<td style="text-align: center;">职责</td>
</tr>
</thead>
<tbody>
<tr>
<td>tsdown</td>
<td>0.21.7</td>
<td>打包器（bundler），通过 scripts/tsdown-build.mjs 驱动</td>
</tr>
<tr>
<td>TypeScript</td>
<td>6.0.2</td>
<td>类型检查</td>
</tr>
<tr>
<td>@typescript/native-preview</td>
<td>7.0.0-dev.20260331.1</td>
<td>Go 实现的 TypeScript 编译器预览版（pnpm tsgo）</td>
</tr>
<tr>
<td>oxfmt</td>
<td>0.43.0</td>
<td>代码格式化（取代 Prettier）</td>
</tr>
<tr>
<td>oxlint + oxlint-tsgolint</td>
<td>1.58.0 / 0.18.1</td>
<td>代码检查（取代 ESLint）</td>
</tr>
<tr>
<td>Bun</td>
<td>-</td>
<td>开发/测试阶段的 TypeScript 执行器</td>
</tr>
<tr>
<td>Node 22+</td>
<td>-</td>
<td>生产运行时（保持 Node + Bun 双路径兼容）</td>
</tr>
<tr>
<td>tsx</td>
<td>4.21.0</td>
<td>基于 Node 的 TypeScript 执行</td>
</tr>
<tr>
<td>jiti</td>
<td>2.6.1</td>
<td>运行时 ESM 解析（plugin-sdk 别名解析）</td>
</tr>
</tbody>
</table>
<p>几点选型要点值得展开：</p>
<p><span style="background-color: #c0c0c0;">tsdown 而非直接使用 esbuild</span>：tsdown 在 esbuild 之上提供了更高层的打包抽象，其配置文件比直接编写 esbuild 插件更简洁。构建入口是 scripts/tsdown-build.mjs。</p>
<p><span style="background-color: #c0c0c0;">@typescript/native-preview</span>：这是 TypeScript 官方的 Go 语言重写实验版本，通过 pnpm tsgo 调用。其类型检查速度比标准 TypeScript 编译器快一个数量级，OpenClaw 将其用于 CI 中的快速类型检查路径。</p>
<p><span style="background-color: #c0c0c0;">oxfmt / oxlint</span>：基于 Rust 的格式化和检查工具链，替代了传统的 Prettier + ESLint 组合。格式化命令为 pnpm format（检查）和 pnpm format:fix（自动修复），检查命令为 pnpm lint。</p>
<p><span style="background-color: #c0c0c0;">Bun + Node 双运行时</span>：开发和测试阶段使用 Bun 获得更快的启动速度（bun 、bunx ），生产部署使用 Node 22+ 确保兼容性。两条路径必须同时保持可用。</p>
<div class="blog_h3"><span class="graybg">17.1.1 构建管线与变体</span></div>
<p>完整构建管线：</p>
<pre class="crayon-plain-tag">pnpm build
# 展开为：
# 1. pnpm canvas:a2ui:bundle    → A2UI bundle 构建
# 2. scripts/tsdown-build.mjs   → 主体打包
# 3. runtime-postbuild.mjs      → 运行时后处理</pre>
<p>三种构建变体服务于不同场景：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">变体</td>
<td style="text-align: center;">命令</td>
<td style="text-align: center;">用途</td>
</tr>
</thead>
<tbody>
<tr>
<td>完整构建</td>
<td>pnpm build</td>
<td>包含 A2UI bundle + 主体 + 后处理</td>
</tr>
<tr>
<td>Docker 构建</td>
<td>pnpm build:docker</td>
<td>跳过 A2UI bundle（可能在 QEMU 下失败）</td>
</tr>
<tr>
<td>严格冒烟测试</td>
<td>pnpm build:strict-smoke</td>
<td>快速验证构建产物的基本可用性</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">17.2 198 个 npm 脚本分类</span></div>
<p>OpenClaw 的 package.json 包含 <span style="background-color: #c0c0c0;">198 个 npm 脚本</span>。这个数字并非膨胀 — 它反映了一个覆盖多平台、多渠道、多插件的工程体系所需的自动化指令密度。以下按职责分类：</p>
<div class="blog_h3"><span class="graybg">17.2.1 构建类</span></div>
<p>build、build:docker、build:plugin-sdk:dts、build:strict-smoke — 核心构建管线及其变体，加上 Plugin SDK 的类型声明生成。</p>
<div class="blog_h3"><span class="graybg">17.2.2 检查与 Lint 类（30+）</span></div>
<p>以 pnpm check 作为元检查入口，编排 tsgo、lint、format、format:check 及约 20 个特定规则检查脚本。详见 17.6 节的 CI 架构分析。</p>
<div class="blog_h3"><span class="graybg">17.2.3 测试类（40+）</span></div>
<p>测试脚本是数量最多的一组：test、test:fast、test:watch、test:coverage、test:e2e、test:live、test:gateway、test:channels、test:extensions、test:contracts，以及一系列 test:docker:* 和 test:parallels:* 脚本。</p>
<div class="blog_h3"><span class="graybg">17.2.4 发布类</span></div>
<p>release:check、release:openclaw:npm:check、release:plugins:npm:check — 发布前的版本号、changelog、npm registry 一致性校验。</p>
<div class="blog_h3"><span class="graybg">17.2.5 平台类</span></div>
<p>android:*、ios:*、ui:* — 各平台的构建、测试、lint 快捷入口。</p>
<div class="blog_h3"><span class="graybg">17.2.6 文档类</span></div>
<p>docs:check-links（死链检测）、docs:spellcheck（拼写检查）、docs:check-i18n-glossary（国际化术语表一致性）。</p>
<div class="blog_h3"><span class="graybg">17.2.7 协议类</span></div>
<p>protocol:check（协议定义一致性检查）、protocol:gen（生成 TypeScript 类型）、protocol:gen:swift（生成 Swift 类型）。</p>
<div class="blog_h2"><span class="graybg">17.3 测试基础设施</span></div>
<p>OpenClaw 使用 <span style="background-color: #c0c0c0;">Vitest 4.1.2</span> 作为测试框架，搭配 @vitest/coverage-v8 进行 V8 引擎级别的代码覆盖率采集。覆盖率门槛统一设置为 <span style="background-color: #c0c0c0;">70%</span>，覆盖 lines、branches、functions、statements 四个维度。</p>
<p>一条关键的强制规则：Vitest 的并发模式<span style="background-color: #c0c0c0;">只允许使用 forks 池</span>。threads、vmThreads、vmForks 三种模式被显式禁用。这一限制源于 OpenClaw 测试中大量涉及进程级副作用（子进程创建、文件系统操作、网络端口占用等），线程级隔离无法提供足够的隔离保障。</p>
<p>并行测试编排由 test-parallel.mjs 脚本驱动，提供三种执行配置：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">配置</td>
<td style="text-align: center;">并行度</td>
<td style="text-align: center;">用途</td>
</tr>
</thead>
<tbody>
<tr>
<td>default</td>
<td>CPU 核心数的 50%</td>
<td>日常开发，平衡速度与系统响应性</td>
</tr>
<tr>
<td>serial</td>
<td>1</td>
<td>调试失败用例，排除并发干扰</td>
</tr>
<tr>
<td>max</td>
<td>CPU 核心数的 100%</td>
<td>CI 环境，最大化吞吐</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">17.3.1 测试层级</span></div>
<p>测试体系覆盖多个层级，每个层级关注不同粒度的验证需求：</p>
<p><span style="background-color: #c0c0c0;">单元测试与集成测试</span>：pnpm test（全量运行）、pnpm test:fast（排除慢速用例）、pnpm test:watch（文件监听模式）、pnpm test:coverage（带覆盖率报告）。</p>
<p><span style="background-color: #c0c0c0;">领域测试</span>：test:channels（渠道集成）、test:extensions（扩展接口）、test:gateway（Gateway 协议）、test:e2e（端到端流程）、test:live（真实 API 对接）。</p>
<p><span style="background-color: #c0c0c0;">契约测试</span>（Contract Tests）：test:contracts:channels 和 test:contracts:plugins 分别强制渠道和插件的接口契约一致性。契约测试确保 Channel 适配器和 Plugin 遵循其声明的接口协议，防止实现漂移。</p>
<p><span style="background-color: #c0c0c0;">Docker E2E 测试</span>（8+ 场景）：在完整的 Docker 容器化环境中执行端到端验证。覆盖场景包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">场景</td>
<td style="text-align: center;">验证范围</td>
</tr>
</thead>
<tbody>
<tr>
<td>onboard</td>
<td>首次启动引导流程</td>
</tr>
<tr>
<td>plugins</td>
<td>插件安装、加载与执行</td>
</tr>
<tr>
<td>MCP channels</td>
<td>MCP 协议渠道连通性</td>
</tr>
<tr>
<td>gateway network</td>
<td>Gateway 网络拓扑与路由</td>
</tr>
<tr>
<td>OpenWebUI</td>
<td>OpenWebUI 集成</td>
</tr>
<tr>
<td>doctor-switch</td>
<td>doctor 诊断与配置切换</td>
</tr>
<tr>
<td>qr-import</td>
<td>QR 码配置导入</td>
</tr>
<tr>
<td>live models</td>
<td>真实模型端点对接</td>
</tr>
</tbody>
</table>
<p><span style="background-color: #c0c0c0;">Parallels 冒烟测试</span>：在 macOS、Windows、Linux 三个虚拟机客户端上执行冒烟测试，验证跨操作系统的基本功能可用性。</p>
<p><span style="background-color: #c0c0c0;">性能测试套件</span>：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">脚本</td>
<td style="text-align: center;">度量维度</td>
</tr>
</thead>
<tbody>
<tr>
<td>test:perf:budget</td>
<td>性能预算检查（运行时间/内存上限）</td>
</tr>
<tr>
<td>test:perf:hotspots</td>
<td>热点函数 profiling</td>
</tr>
<tr>
<td>test:perf:imports</td>
<td>模块导入耗时分析</td>
</tr>
<tr>
<td>test:startup:bench</td>
<td>启动时间基准</td>
</tr>
<tr>
<td>test:startup:memory</td>
<td>启动内存占用</td>
</tr>
</tbody>
</table>
<p><span style="background-color: #c0c0c0;">Live 测试</span>：通过设置环境变量 OPENCLAW_LIVE_TEST=1 启用，执行 pnpm test:live。这些测试使用真实的 API 密钥调用外部服务，因此不在常规 CI 中运行，而是在专用的 live 测试环境中定期执行。</p>
<div class="blog_h2"><span class="graybg">17.4 代码质量门控</span></div>
<p>OpenClaw 对代码质量的门控措施密度极高，以下逐一展开：</p>
<p><span style="background-color: #c0c0c0;">文件行数限制</span>：check:loc 脚本强制执行约 500-700 行的文件行数上限。超过上限的文件会被标记为需要拆分。这是一项软性但有强制检查的编码指南，目标是防止出现难以维护的巨型文件。</p>
<p><span style="background-color: #c0c0c0;">严格类型纪律</span>：禁止使用 @ts-nocheck，避免使用 any 类型，优先使用 unknown。在对外边界（配置文件、Webhook 载荷、CLI 输出、API 响应）处优先使用 zod@4.3.6 进行运行时 schema 校验。</p>
<p><span style="background-color: #c0c0c0;">动态导入防护</span>：构建系统会检测同一模块同时存在静态导入和动态导入的情况，并发出 INEFFECTIVE_DYNAMIC_IMPORT 警告。这种混用模式会导致 tree-shaking 失效 — 模块已经通过静态导入被打包，动态导入不会带来额外的按需加载收益，反而增加了代码理解的复杂度。</p>
<p><span style="background-color: #c0c0c0;">重复代码检测</span>：使用 jscpd@4.0.8 扫描 src/、extensions/、test/、scripts/ 目录下的代码重复。超过阈值的重复块会触发 CI 失败。</p>
<p><span style="background-color: #c0c0c0;">漂移检测</span>：一系列 check 脚本监控各类定义与实现之间的一致性：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">检查</td>
<td style="text-align: center;">检测内容</td>
</tr>
</thead>
<tbody>
<tr>
<td>canon:check</td>
<td>规范代码样式一致性</td>
</tr>
<tr>
<td>plugin-sdk:api:check</td>
<td>Plugin SDK 公开 API 漂移检测</td>
</tr>
<tr>
<td>config:docs:check</td>
<td>配置 schema 与文档的一致性</td>
</tr>
<tr>
<td>lint:plugins:plugin-sdk-subpaths-exported</td>
<td>Plugin SDK 子路径导出完整性</td>
</tr>
</tbody>
</table>
<p>此外还有 8 个以上针对特定扩展（Extension）的边界 lint 规则，确保各扩展模块不会越界访问其他扩展的内部 API。</p>
<div class="blog_h2"><span class="graybg">17.5 CI 架构</span></div>
<p>OpenClaw 的 CI 采用<span style="background-color: #c0c0c0;">两层检查体系</span>（Two-tier Check System），将本地开发门控与 CI 门控分离：</p>
<div class="blog_h3"><span class="graybg">17.5.1 第一层：pnpm check（本地开发门控）</span></div>
<p>pnpm check 是每次提交前必须通过的本地检查。其执行顺序为：</p>
<pre class="crayon-plain-tag"># pnpm check 的执行序列：
# 1. no-conflict-markers     → 检测未解决的合并冲突标记
# 2. host-env-policy:swift   → 验证 Swift 宿主环境策略
# 3. tsgo                    → Go 版 TypeScript 类型检查
# 4. lint                    → oxlint 代码检查
# 5. format                  → oxfmt 格式校验</pre>
<p>该管线为串行执行，任何步骤失败都会终止后续步骤并报告错误位置。</p>
<div class="blog_h3"><span class="graybg">17.5.2 第二层：check-additional（CI 专属门控）</span></div>
<p>check-additional 在 CI 环境中额外执行，包含架构策略和边界策略守卫。这些检查被有意排除在本地开发循环之外 — 它们通常运行较慢且依赖 CI 专有环境（如完整的 git 历史、所有分支的 diff 信息等），放入本地循环会严重拖慢开发节奏。</p>
<div class="blog_h3"><span class="graybg">17.5.3 Pre-commit 钩子与快速通道</span></div>
<p>Pre-commit 钩子由 <span style="background-color: #c0c0c0;">prek</span> 工具管理，钩子的默认行为是执行完整的 pnpm check 管线。</p>
<p>对于需要快速迭代的场景，环境变量 FAST_COMMIT=1 可以跳过 format 和 check 步骤：</p>
<pre class="crayon-plain-tag"># 跳过格式和检查（手动保证代码质量时使用）
FAST_COMMIT=1 git commit -m "wip: experimental changes"</pre>
<p>使用 FAST_COMMIT 意味着开发者自行承担代码质量责任 — CI 仍会执行完整检查，不符合要求的提交将在 CI 阶段被拦截。</p>
<div class="blog_h3"><span class="graybg">17.5.4 主分支准入标准</span></div>
<p>代码合入主分支（main）的<span style="background-color: #c0c0c0;">准入门槛</span>（Landing Bar）为：</p>
<pre class="crayon-plain-tag"># 主分支准入三件套：
pnpm check        # 类型 + lint + 格式
pnpm test          # 全量测试
pnpm build         # 构建验证（当变更涉及构建影响面时）</pre>
<p>第三项 pnpm build 是条件性的：仅在变更涉及构建影响面（build-affecting surfaces）时才要求。构建影响面的定义包括但不限于：tsdown-build.mjs 配置变更、package.json 依赖变更、tsconfig.json 变更、新增或删除模块导出等。纯粹的逻辑变更（函数实现调整、bug 修复等）不触发构建要求，以此平衡 CI 速度与安全性。</p>
<div class="blog_h1"><span class="graybg">部署方案</span></div>
<p>OpenClaw 的部署策略覆盖了从单人开发者到企业团队的全部场景。部署方式按复杂度递增排列：npm 全局安装、Docker 容器化、Ansible 编排、Nix 声明式配置、Windows 系统托盘。每种方式对应不同的运维哲学和安全模型。</p>
<div class="blog_h2"><span class="graybg">npm 全局安装（推荐方式）</span></div>
<p>对于大多数开发者而言，npm 全局安装是进入 OpenClaw 最快的路径：</p>
<pre class="crayon-plain-tag">npm install -g openclaw@latest
openclaw onboard --install-daemon</pre>
<p>第一条命令将 OpenClaw CLI 及其全部依赖安装到 Node.js 的全局 node_modules 目录。第二条命令启动交互式引导向导（Onboarding Wizard），--install-daemon 标志指示向导在流程结束后自动注册系统守护进程（Daemon）。</p>
<p>守护进程的注册方式因操作系统而异。在 macOS 上，OpenClaw 生成一个 <span style="background-color: #c0c0c0;">launchd</span> plist 文件并通过 launchctl load 注册为用户级别的 Launch Agent。plist 的关键配置如下：</p>
<pre class="crayon-plain-tag">Label
ai.openclaw.gateway
ProgramArguments

  /usr/local/bin/node
  /usr/local/lib/node_modules/openclaw/dist/gateway.js

RunAtLoad

KeepAlive</pre>
<p>在 Linux 上，OpenClaw 采用 <span style="background-color: #c0c0c0;">systemd 用户服务（user service）</span>，将单元文件写入 ~/.config/systemd/user/openclaw-gateway.service，并通过 systemctl --user enable --now openclaw-gateway 启动。这一选择的关键在于"用户级别"——无需 root 权限，服务生命周期与用户会话绑定，遵循最小权限原则。配合 loginctl enable-linger 可以实现即使用户未登录也保持运行。</p>
<div class="blog_h2"><span class="graybg">Docker 部署：四阶段构建深度剖析</span></div>
<p>Docker 部署是 OpenClaw 为生产环境和隔离场景提供的首选方案。其 Dockerfile 采用<span style="background-color: #c0c0c0;">多阶段构建（Multi-Stage Build）</span>模式，共分四个阶段，每个阶段都经过精心设计以最小化最终镜像体积。</p>
<div class="blog_h3"><span class="graybg">阶段一：ext-deps — 扩展依赖提取</span></div>
<p>第一阶段的唯一职责是从 extensions/ 目录树中提取所有 package.json 文件，同时保留其目录结构。这是一个纯文件复制阶段，不执行任何安装操作：</p>
<pre class="crayon-plain-tag">FROM node:24-bookworm AS ext-deps
WORKDIR /app
COPY extensions/ extensions/
RUN find extensions -name "package.json" -exec sh -c \
  'mkdir -p /out/$(dirname {}) &amp;&amp; cp {} /out/{}' \;</pre>
<p>这种分离的目的是利用 Docker 的层缓存机制——只有当扩展的 package.json 发生变化时，后续的依赖安装层才会失效。</p>
<div class="blog_h3"><span class="graybg">阶段二：build — 编译构建</span></div>
<p>构建阶段使用 <span style="background-color: #c0c0c0;">Bun</span> 作为 JavaScript 运行时加速依赖安装，同时以 <span style="background-color: #c0c0c0;">pnpm</span> 作为包管理器，<span style="background-color: #c0c0c0;">tsdown</span> 作为 TypeScript 编译工具：</p>
<pre class="crayon-plain-tag">FROM oven/bun:1 AS build
WORKDIR /app
COPY --from=ext-deps /out/extensions ./extensions
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
RUN cd ui &amp;&amp; pnpm run build</pre>
<p>构建产物包括两部分：dist/ 目录下的 TypeScript 编译输出（通过 tsdown 生成），以及 ui/dist/ 目录下的 Web Control UI 静态资源（通过 Vite 构建）。</p>
<div class="blog_h3"><span class="graybg">阶段三：runtime-assets — 运行时资产裁剪</span></div>
<p>第三阶段是整个构建流水线中最关键的体积优化环节：</p>
<pre class="crayon-plain-tag">FROM build AS runtime-assets
RUN pnpm prune --prod
RUN find . -name "*.d.ts" -delete \
 &amp;&amp; find . -name "*.map" -delete \
 &amp;&amp; find . -name "*.ts" ! -name "*.d.ts" -path "*/src/*" -delete</pre>
<p>首先通过 pnpm prune --prod 移除所有 devDependencies，然后逐一清除 TypeScript 声明文件（.d.ts）、Source Map 文件（.map）和源码文件。最终只保留 JavaScript 运行时代码和生产级依赖。</p>
<div class="blog_h3"><span class="graybg">阶段四：runtime — 最终运行镜像</span></div>
<p>最终阶段基于精简的 Node 24 镜像，且所有基础镜像均采用<span style="background-color: #c0c0c0;">固定 SHA256 摘要（Pinned SHA256 Digest）</span>以确保构建可复现：</p>
<pre class="crayon-plain-tag"># 默认变体
FROM node:24-bookworm@sha256:abc123... AS runtime
# 精简变体
# FROM node:24-bookworm-slim@sha256:def456... AS runtime

USER node
WORKDIR /home/node/app
COPY --from=runtime-assets --chown=node:node /app ./

HEALTHCHECK --interval=3m --timeout=10s --start-period=30s \
  CMD curl -f http://localhost:18789/healthz || exit 1

EXPOSE 18789 18790
CMD ["node", "dist/gateway.js"]</pre>
<p>OpenClaw 提供两个镜像变体（Variant），通过构建参数 OPENCLAW_VARIANT 选择：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>变体</td>
<td>基础镜像</td>
<td>特点</td>
<td>适用场景</td>
</tr>
</thead>
<tbody>
<tr>
<td>default</td>
<td>node:24-bookworm</td>
<td>包含完整 Debian 工具链，支持浏览器安装</td>
<td>需要 Playwright/浏览器渠道</td>
</tr>
<tr>
<td>slim</td>
<td>node:24-bookworm-slim</td>
<td>最小化系统库，镜像体积减小约 40%</td>
<td>纯 CLI/API 场景</td>
</tr>
</tbody>
</table>
<p>其余构建参数（Build Args）包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>参数名</td>
<td>默认值</td>
<td>说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>OPENCLAW_EXTENSIONS</td>
<td>all</td>
<td>控制构建哪些扩展，逗号分隔或 all</td>
</tr>
<tr>
<td>OPENCLAW_INSTALL_BROWSER</td>
<td>false</td>
<td>是否在镜像中预装 Chromium（Playwright）</td>
</tr>
<tr>
<td>OPENCLAW_INSTALL_DOCKER_CLI</td>
<td>false</td>
<td>是否安装 Docker CLI（用于沙箱功能）</td>
</tr>
</tbody>
</table>
<p>安全方面，最终镜像以 USER node（uid 1000）运行，杜绝 root 权限。健康检查（Health Check）配置了两个端点：/healthz 用于存活探测（Liveness Probe），/readyz 用于就绪探测（Readiness Probe），检测间隔为 3 分钟，超时 10 秒，启动宽限期 30 秒。</p>
<div class="blog_h3"><span class="graybg">docker-compose.yml：双服务架构</span></div>
<p>官方 docker-compose.yml 定义了两个服务，体现了 OpenClaw 的网关-客户端分离架构：</p>
<pre class="crayon-plain-tag">version: "3.9"
services:
  openclaw-gateway:
    image: ghcr.io/openclaw/openclaw:latest
    ports:
      - "18789:18789"
      - "18790:18790"
    volumes:
      - openclaw-data:/home/node/.openclaw
      - /var/run/docker.sock:/var/run/docker.sock
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - NET_RAW
      - NET_ADMIN
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:18789/healthz"]
      interval: 3m
      timeout: 10s
      start_period: 30s

  openclaw-cli:
    image: ghcr.io/openclaw/openclaw:latest
    command: ["node", "dist/cli.js"]
    depends_on:
      openclaw-gateway:
        condition: service_healthy
    environment:
      - OPENCLAW_GATEWAY_URL=http://openclaw-gateway:18789

volumes:
  openclaw-data:</pre>
<p>安全加固（Security Hardening）体现在三处：no-new-privileges 阻止进程通过 setuid/setgid 提权；cap_drop 丢弃 NET_RAW 和 NET_ADMIN 能力（Capability），防止原始套接字操作和网络配置篡改；Docker Socket 挂载（/var/run/docker.sock）为沙箱集成提供<span style="background-color: #c0c0c0;">Docker-in-Docker（DinD）</span>能力，允许 Agent 在隔离容器中执行命令。</p>
<p>端口映射方面，18789 是 Gateway 的 HTTP 主端口，承载 REST API、WebSocket 连接和 Web Control UI；18790 是 Bridge 端口，供外部渠道插件通过 gRPC 或 WebSocket 桥接到 Gateway。</p>
<div class="blog_h2"><span class="graybg">Ansible 部署：openclaw/openclaw-ansible</span></div>
<p>openclaw/openclaw-ansible（545 stars）提供了一套完整的 Ansible Playbook，将 OpenClaw 的 Docker 部署包装为可重复执行的基础设施代码（Infrastructure as Code）。其核心特性包括：</p>
<p><strong>Tailscale VPN 集成</strong>：Playbook 默认集成 <span style="background-color: #c0c0c0;">Tailscale</span>，Gateway 绑定到 Tailscale 虚拟网络接口而非公网接口。这意味着 OpenClaw 实例只在 Tailnet 内可达，无需暴露任何公网端口，从根本上消除了未授权访问的风险。</p>
<p><strong>UFW 防火墙配置</strong>：自动配置 <span style="background-color: #c0c0c0;">UFW（Uncomplicated Firewall）</span>规则，仅放行 SSH（22）和 Tailscale 所需端口（41641/UDP），其余入站流量全部丢弃。</p>
<p><strong>Docker 隔离</strong>：OpenClaw 运行在独立 Docker 网络中，数据卷映射到宿主机的指定路径，支持通过 Ansible 变量自定义挂载点、环境变量和资源限制。</p>
<div class="blog_h2"><span class="graybg">Nix 部署：openclaw/nix-openclaw</span></div>
<p>openclaw/nix-openclaw（611 stars）提供 Nix Flake 形式的声明式配置。对于 NixOS 用户或使用 home-manager 的开发者，这是最符合其工作流的部署方式：</p>
<pre class="crayon-plain-tag">{
  inputs.openclaw.url = "github:openclaw/nix-openclaw";
  outputs = { self, nixpkgs, openclaw }: {
    nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
      modules = [
        openclaw.nixosModules.default
        {
          services.openclaw = {
            enable = true;
            gateway.port = 18789;
            extensions = [ "discord" "telegram" "whatsapp" ];
          };
        }
      ];
    };
  };
}</pre>
<p>Nix 部署的优势在于完全的可复现性（Reproducibility）——相同的 Flake 输入必然产生相同的系统配置，消除了"在我机器上能跑"的环境差异问题。</p>
<div class="blog_h2"><span class="graybg">Windows 部署：系统托盘与 PowerToys 集成</span></div>
<p>openclaw/openclaw-windows-node（405 stars）为 Windows 提供了原生集成体验。其核心组件包括：</p>
<p><strong>系统托盘伴侣（System Tray Companion）</strong>：一个轻量级的 .NET 应用，驻留在 Windows 系统托盘。它管理 OpenClaw Gateway 进程的生命周期（启动/停止/重启），显示实时状态，并提供快捷菜单访问 Web Control UI 和日志。</p>
<p><strong>PowerToys 命令面板扩展（Command Palette Extension）</strong>：集成 Microsoft PowerToys 的命令面板（Run 插件），用户可以通过 Alt+Space 快捷键直接向 OpenClaw Agent 发送指令，无需切换到浏览器或终端窗口。</p>
<div class="blog_h2"><span class="graybg">远程 Gateway 配置（Linux）</span></div>
<p>在远程 Linux 服务器上部署 Gateway 时，安全暴露服务是核心问题。OpenClaw 提供三种模式：</p>
<p><strong>Tailscale Serve（Tailnet 内部访问）</strong>：通过 tailscale serve 将 Gateway 端口映射到 Tailscale 的 HTTPS 代理，仅 Tailnet 内的设备可访问。配合 Gateway 的 --tailscale serve 模式，自动完成证书配置和端口映射。</p>
<p><strong>Tailscale Funnel（公网 HTTPS 暴露）</strong>：tailscale funnel 模式将服务通过 Tailscale 的全球边缘网络暴露到公网，自动获取 HTTPS 证书。适用于需要外部 Webhook 回调（如 Telegram Bot）的场景。</p>
<p><strong>SSH 隧道</strong>：最传统但最灵活的方式，通过 SSH 端口转发将远程 Gateway 映射到本地：</p>
<pre class="crayon-plain-tag">ssh -L 18789:localhost:18789 -L 18790:localhost:18790 user@remote-host</pre>
<p>Gateway 的绑定模式（Bind Mode）通过 --bind 参数控制：loopback（默认）仅监听 127.0.0.1，lan 监听 0.0.0.0 以接受局域网连接。Tailscale 模式通过 --tailscale 参数设置：off（默认）、serve、funnel。</p>
<div class="blog_h2"><span class="graybg">引导向导：openclaw onboard</span></div>
<p>openclaw onboard 是 OpenClaw 的交互式初始化命令，引导用户完成从零到可用的全部配置。流程按步骤执行：</p>
<p><strong>步骤一：Gateway 配置</strong>——选择绑定模式（loopback/lan）、端口、Tailscale 模式。若检测到已有 Gateway 实例运行，则询问是否复用。</p>
<p><strong>步骤二：工作区配置</strong>——创建默认工作区目录（~/.openclaw/workspace），配置 LLM 提供商密钥（OpenAI API Key、Anthropic API Key 等），设置默认模型。</p>
<p><strong>步骤三：渠道配置</strong>——根据用户选择启用渠道（Discord Bot Token、Telegram Bot Token、WhatsApp 手机号等），验证凭据有效性。</p>
<p><strong>步骤四：Skills 配置</strong>——推荐安装热门 Skills，提示用户浏览 ClawHub 发现更多。</p>
<p>当附带 --install-daemon 标志时，向导结束后自动注册并启动系统守护进程。若出现问题，openclaw doctor 命令执行全面的健康检查：验证 Node.js 版本、检查端口占用、测试 LLM 连接、校验配置文件语法，并输出诊断报告及修复建议。</p>
<div class="blog_h1"><span class="graybg">生态全景</span></div>
<div class="blog_h2"><span class="graybg">ClawHub：官方 Skill 注册中心</span></div>
<p><span style="background-color: #c0c0c0;">ClawHub</span>（openclaw/clawhub，7,214 stars）是 OpenClaw 的官方 Skill 注册中心与分发平台。它的定位类似于 npm 之于 Node.js，或 crates.io 之于 Rust——一个集中式的包注册表，但分发的是 AI Agent 能力模块。</p>
<p>安装 Skill 只需一条命令：</p>
<pre class="crayon-plain-tag">clawhub install weather-forecast
clawhub install code-review --version 2.1.0</pre>
<p>ClawHub 的 Agent 集成能力是其核心差异化特性：当 Agent 在对话中遇到需要但当前未安装的能力时，可以自动搜索 ClawHub 并在用户确认后拉取安装。这一流程的实现路径是 Agent 调用内置的 clawhub_search 工具函数，该函数向 api.clawhub.com/v1/search 发送请求，返回匹配的 Skill 列表及其安全评级。</p>
<p>Web 端的 clawhub.com 提供可视化的市场（Marketplace）界面，支持按类别浏览、按关键词搜索、查看安装统计和社区评分。每个 Skill 页面展示其 SKILL.md 的渲染内容、依赖关系图和版本历史。</p>
<div class="blog_h2"><span class="graybg">Skills 存档与社区列表</span></div>
<p>openclaw/skills（3,622 stars）是所有 Skill 的版本存档仓库，保存了每个 Skill 每个版本的完整快照。这一设计确保即使 Skill 作者删除了某个版本，已部署的实例仍可从存档拉取。</p>
<p>VoltAgent/awesome-openclaw-skills（43,292 stars）是社区维护的精选 Skill 列表，收录超过 5,400 个经过社区验证的 Skill。其 Star 数反映了 OpenClaw Skill 生态的活跃程度——一个"awesome 列表"的 Star 数通常是其生态规模的风向标。</p>
<div class="blog_h2"><span class="graybg">Skills 系统：三层结构</span></div>
<p>OpenClaw 的 Skills 按分发方式分为三个层级：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>层级</td>
<td>存储位置</td>
<td>安装方式</td>
<td>更新策略</td>
</tr>
</thead>
<tbody>
<tr>
<td>内置 Skills（Bundled）</td>
<td>skills/ 目录，随 npm 包分发</td>
<td>自动包含</td>
<td>随 OpenClaw 版本更新</td>
</tr>
<tr>
<td>托管 Skills（Managed）</td>
<td>~/.openclaw/managed/skills/</td>
<td>clawhub install</td>
<td>clawhub update</td>
</tr>
<tr>
<td>工作区 Skills（Workspace）</td>
<td>~/.openclaw/workspace/skills//</td>
<td>手动创建</td>
<td>用户自行管理</td>
</tr>
</tbody>
</table>
<p>加载优先级从上到下递增——工作区 Skills 可以覆盖同名的托管或内置 Skill，提供最大的定制灵活性。</p>
<div class="blog_h2"><span class="graybg">Skill 开发：AgentSkills 规范</span></div>
<p>每个 Skill 由一个 SKILL.md 文件定义，这是 <span style="background-color: #c0c0c0;">AgentSkills 规范</span>的核心载体。SKILL.md 采用带有特殊 YAML Front Matter 的 Markdown 格式：</p>
<pre class="crayon-plain-tag">---
name: code-review
version: 2.1.0
description: Automated code review with multi-language support
triggers:
  - pattern: "review {file_path}"
  - pattern: "check code quality"
permissions:
  - filesystem:read
  - git:read
before_install: scripts/check-deps.sh
---

# Code Review Skill

## Instructions

You are a senior code reviewer. When triggered, analyze the provided
file for bugs, style issues, and potential improvements.

## Tools

### review_file
Analyze a single file and return findings.
- `file_path` (string, required): Path to the file to review
- `severity` (string, optional): Minimum severity to report (info|warn|error)</pre>
<p>其中 before_install 字段指定一个<span style="background-color: #c0c0c0;">安全钩子（Security Hook）</span>脚本，在安装阶段执行。该脚本用于验证系统依赖（如检查 Python 版本、确认特定二进制文件存在），并以非零退出码阻止安装。这一机制的安全意义在于：它为 Skill 作者提供了一个声明式的前置检查点，防止不兼容的 Skill 被安装到不具备运行条件的环境中。</p>
<div class="blog_h2"><span class="graybg">官方子仓库矩阵</span></div>
<p>OpenClaw 组织下维护着一系列功能各异的子仓库：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>仓库</td>
<td>Stars</td>
<td>定位</td>
</tr>
</thead>
<tbody>
<tr>
<td>openclaw/acpx</td>
<td>1,834</td>
<td>无头 ACP CLI（Agent Control Protocol 命令行客户端）</td>
</tr>
<tr>
<td>openclaw/lobster</td>
<td>992</td>
<td>工作流 Shell（Workflow Shell），交互式任务编排</td>
</tr>
<tr>
<td>openclaw/nix-openclaw</td>
<td>611</td>
<td>Nix Flake 声明式部署</td>
</tr>
<tr>
<td>openclaw/openclaw-ansible</td>
<td>545</td>
<td>Ansible Playbook 自动化部署</td>
</tr>
<tr>
<td>openclaw/openclaw-windows-node</td>
<td>405</td>
<td>Windows 系统托盘 + PowerToys 集成</td>
</tr>
<tr>
<td>openclaw/openclaw.ai</td>
<td>250</td>
<td>官方网站源码</td>
</tr>
<tr>
<td>openclaw/community</td>
<td>92</td>
<td>社区治理文档与 Discord 管理策略</td>
</tr>
<tr>
<td>openclaw/trust</td>
<td>35</td>
<td>安全策略、漏洞披露流程、审计报告</td>
</tr>
<tr>
<td>openclaw/caclawphony</td>
<td>34</td>
<td>Symphony 自主运行框架，长时间无人值守 Agent 任务</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">社区竞品与替代方案</span></div>
<p>OpenClaw 并非孤立存在。以下项目在不同维度上与其构成竞争或互补关系：</p>
<p>HKUDS/nanobot（37,216 stars）定位为"超轻量 OpenClaw 替代品"，去除了多渠道和插件系统的复杂性，聚焦于单一 CLI 场景下的极速响应。其核心卖点是亚秒级冷启动和极低内存占用，适合资源受限的边缘设备。</p>
<p>chatgpt-on-wechat/CowAgent（42,673 stars）以微信生态为核心战场，提供企业微信、公众号、小程序等深度集成。在中国市场，微信的渗透率使其成为 AI Agent 的天然入口，CowAgent 在这一垂直领域的覆盖深度超过 OpenClaw。</p>
<p>AstrBot（28,373 stars）是一个 IM 聊天机器人框架，支持 QQ、飞书、钉钉等国内主流即时通讯平台。与 OpenClaw 的平台无关性不同，AstrBot 选择深耕国内生态，提供更贴合国内开发者习惯的 API 设计。</p>
<div class="blog_h2"><span class="graybg">文档与国际化</span></div>
<p>OpenClaw 的文档基于 <span style="background-color: #c0c0c0;">Mintlify</span> 构建，部署在 docs.openclaw.ai。中文本地化版本位于 docs.openclaw.ai/zh-CN。</p>
<p>国际化（i18n）流水线的技术实现值得关注：翻译工作由 scripts/docs-i18n 脚本驱动，该脚本读取 glossary.zh-CN.json 作为术语表（Glossary），确保专有名词翻译一致（例如 "Gateway" 始终翻译为"网关"，"Skill" 保留英文）。翻译记忆（Translation Memory）存储在 zh-CN.tm.jsonl 文件中，采用 JSON Lines 格式，每行一对源文本与译文。该文件的作用类似于传统本地化工具中的 TMX 文件——当源文本发生变化时，i18n 脚本先在翻译记忆中查找完全匹配或模糊匹配，避免重复翻译已有内容。</p>
<p>社区交流的主阵地是 Discord（discord.gg/clawd），这也是获取实时开发进度和与维护者直接交流的主要渠道。</p>
<div class="blog_h1"><span class="graybg">竞品与展望</span></div>
<div class="blog_h2"><span class="graybg">全维度竞品对比</span></div>
<p>以下表格从多个维度对比 OpenClaw 与当前主流 AI Agent 框架：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>维度</td>
<td>OpenClaw</td>
<td>Manus</td>
<td>AutoGen</td>
<td>LangChain</td>
<td>OpenHands</td>
</tr>
</thead>
<tbody>
<tr>
<td>协议</td>
<td>MIT</td>
<td>闭源 SaaS</td>
<td>MIT (CC-BY-4.0 docs)</td>
<td>MIT</td>
<td>MIT</td>
</tr>
<tr>
<td>部署方式</td>
<td>本地优先 + Docker + Ansible</td>
<td>纯云端</td>
<td>本地 / 云端</td>
<td>本地 / 云端</td>
<td>Docker 容器</td>
</tr>
<tr>
<td>主语言</td>
<td>TypeScript</td>
<td>未公开</td>
<td>Python</td>
<td>Python</td>
<td>Python</td>
</tr>
<tr>
<td>多渠道</td>
<td>Discord/Telegram/WhatsApp/Slack/Web/SMS 等 15+</td>
<td>Web 唯一入口</td>
<td>无原生渠道</td>
<td>无原生渠道</td>
<td>Web UI</td>
</tr>
<tr>
<td>语音支持</td>
<td>原生 Realtime API</td>
<td>有</td>
<td>无</td>
<td>社区扩展</td>
<td>无</td>
</tr>
<tr>
<td>插件系统</td>
<td>Skills + 插件 SDK + ClawHub</td>
<td>内置工具</td>
<td>工具注册</td>
<td>工具/链/代理</td>
<td>沙箱工具</td>
</tr>
<tr>
<td>记忆系统</td>
<td>SQLite + 向量 + 知识图谱</td>
<td>云端对话历史</td>
<td>内存状态</td>
<td>多种 Memory 类型</td>
<td>对话历史</td>
</tr>
<tr>
<td>安全模型</td>
<td>三层沙箱 + 权限 DSL + 审计日志</td>
<td>平台托管</td>
<td>无内置沙箱</td>
<td>无内置沙箱</td>
<td>Docker 沙箱</td>
</tr>
<tr>
<td>社区规模</td>
<td>342K stars, 20K+ commits</td>
<td>N/A（闭源）</td>
<td>42K stars</td>
<td>105K stars</td>
<td>55K stars</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">OpenClaw 的差异化定位</span></div>
<p>从对比中可以提炼出 OpenClaw 的三个核心差异化支柱：</p>
<p><strong>本地优先（Local-First）</strong>：所有数据默认存储在用户设备上。Gateway 进程运行在本地或用户控制的服务器上，LLM API 调用从用户设备直接发出，不经过任何第三方中转服务器。这一设计对于受数据合规要求约束的企业用户尤为关键——GDPR、HIPAA 等法规对数据离境有严格限制，本地优先架构天然满足这些要求。</p>
<p><strong>多渠道原生支持（Multi-Channel Native）</strong>：不是通过适配器（Adapter）将单一接口包装成多个渠道，而是在架构层面将渠道（Channel）作为一等公民（First-Class Citizen）。每个渠道有独立的消息格式化器（Formatter）、权限模型和用户身份映射。</p>
<p><strong>MIT 全开放</strong>：不设 Enterprise 版本，不保留核心功能的闭源组件。所有功能对所有用户完全免费。这种策略在商业模式上高度依赖赞助商和社区贡献，但在信任建立上极为有效。</p>
<div class="blog_h2"><span class="graybg">赞助商策略分析</span></div>
<p>OpenAI、NVIDIA 和 Vercel 为何赞助一个本地优先的"竞争者"？这一看似矛盾的赞助关系背后是清晰的战略逻辑：</p>
<p><strong>OpenAI</strong>：OpenClaw 的每一次 Agent 调用都消耗 OpenAI 的 API Token。本地优先≠不用云模型，恰恰相反——OpenClaw 是 OpenAI API 的超级分发渠道。每个 OpenClaw 用户都是潜在的 API 付费用户，且使用频率远高于普通 ChatGPT 用户。赞助 OpenClaw 是一种<span style="background-color: #c0c0c0;">生态锁定（Ecosystem Lock-in）</span>策略：当开发者习惯了 OpenClaw + GPT-4 的工作流，切换到其他模型的成本就会显著上升。</p>
<p><strong>NVIDIA</strong>：本地推理（Local Inference）是 OpenClaw 的长期演进方向之一。当用户开始在本地运行开源 LLM 时，对 GPU 算力的需求直接转化为 NVIDIA 的硬件销售。赞助 OpenClaw 是在培育<span style="background-color: #c0c0c0;">本地推理（Local Inference）</span>的市场需求。</p>
<p><strong>Vercel</strong>：OpenClaw 的 Web Control UI、文档站点、ClawHub 市场均可部署在 Vercel 平台上。赞助开源项目是 Vercel 拓展开发者工具生态的标准动作，与其赞助 Next.js、Turborepo 的逻辑一致。</p>
<div class="blog_h2"><span class="graybg">挑战与局限性</span></div>
<p>客观评估，OpenClaw 面临以下结构性挑战：</p>
<p><strong>16,843 个待处理 Issue</strong>：截至 2026 年 4 月，仓库有超过 16,000 个开放 Issue。这一数字反映了极高的用户参与度，但同时也意味着维护团队面临巨大的分诊压力（Triage Pressure）。大量 Issue 长期无响应，社区信任可能随时间侵蚀。</p>
<p><strong>Node.js 环境门槛</strong>：对于非 JavaScript 开发者，安装和维护 Node.js 环境本身就是一个障碍。Python 生态的 AutoGen 和 LangChain 在这一点上有天然优势——Python 的安装和环境管理对数据科学家和研究人员更为友好。</p>
<p><strong>单一维护者风险（Bus Factor）</strong>：steipete 贡献了 14,756 个提交，占总提交数（20,000+）的约 73%。第二贡献者（vincentkoc，1,690 个提交）的差距巨大。这意味着项目高度依赖单一个人，其 <span style="background-color: #c0c0c0;">巴士因子（Bus Factor）</span> 接近 1——如果核心维护者因任何原因无法继续，项目存续性将受到严重威胁。</p>
<p><strong>激进的重构节奏</strong>：几乎每个版本都包含破坏性变更（Breaking Changes）。插件 API 的频繁变动导致社区 Skills 的维护成本很高——一个 Skill 可能在几周内因上游 API 变化而失效。这种"快速迭代"与"稳定平台"之间的张力是 OpenClaw 当前最大的架构风险。</p>
<div class="blog_h2"><span class="graybg">未来演进方向</span></div>
<p><strong>TypeScript 原生编译器</strong>：OpenClaw 正在跟踪 @typescript/native-preview 7.0.0-dev（基于 Go 语言实现的 TypeScript 原生编译器）。该编译器承诺 10 倍以上的编译速度提升，这对 OpenClaw 的开发体验和 CI/CD 流水线效率将产生显著影响。仓库中已有实验性分支开始适配新编译器的特性。</p>
<p><strong>插件 SDK 稳定化</strong>：当前插件系统存在多个历史遗留路径（Legacy Paths），包括已废弃但尚未删除的旧版导入方式。SDK 稳定化的核心目标是确定一个长期不变的 API 面（API Surface），将所有旧路径标记为 deprecated 并在后续版本中移除。</p>
<p><strong>微信官方集成</strong>：与腾讯的合作将带来微信渠道的官方支持，而非目前依赖第三方库的间接集成。这对中国市场的渗透率至关重要——微信的月活跃用户数超过 13 亿，官方渠道意味着更稳定的 API 和更低的封号风险。</p>
<p><strong>企业级采用路径</strong>：Ansible Playbook + Docker 容器 + 沙箱隔离的组合已为企业部署铺平道路。下一步是完善 RBAC（基于角色的访问控制）、审计日志导出（Audit Log Export）到 SIEM 系统、以及 SSO（单点登录）集成。</p>
<div class="blog_h2"><span class="graybg">终评：本地优先 AI Agent 范式的代表</span></div>
<p>OpenClaw 代表的是一种明确的技术哲学：<strong>AI Agent 应该运行在用户控制的基础设施上，数据不应离开用户的信任边界</strong>。这一立场在当前"一切皆云"的行业趋势中显得逆流而行，但正是这种逆流赋予了它独特的价值。</p>
<p>从技术实现角度看，OpenClaw 在 TypeScript 单体仓库中实现了渠道抽象、插件隔离、三层沙箱、向量记忆和跨平台原生应用等复杂功能，代码质量和架构设计的成熟度远超其仅四个月的年龄。但其单一维护者集中度、激进重构节奏和日益庞大的 Issue 积压也构成了实质性风险。</p>
<p>对于开发者而言，OpenClaw 是目前最完整的开源本地优先 AI Agent 平台，没有之一。对于企业而言，其 Docker + Ansible + 沙箱的组合提供了可审计、可隔离、可复现的部署路径。对于 AI 行业而言，它证明了"本地优先"不是一个妥协方案，而是一个可以在功能完备性上与云端 SaaS 竞品正面对抗的架构范式。</p>
<div class="blog_h1"><span class="graybg">参考资源</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td>资源</td>
<td>链接</td>
</tr>
</thead>
<tbody>
<tr>
<td>GitHub 主仓库</td>
<td><a href="https://github.com/openclaw/openclaw">github.com/openclaw/openclaw</a></td>
</tr>
<tr>
<td>官方网站</td>
<td><a href="https://openclaw.ai">openclaw.ai</a></td>
</tr>
<tr>
<td>英文文档</td>
<td><a href="https://docs.openclaw.ai">docs.openclaw.ai</a></td>
</tr>
<tr>
<td>中文文档</td>
<td><a href="https://docs.openclaw.ai/zh-CN">docs.openclaw.ai/zh-CN</a></td>
</tr>
<tr>
<td>Discord 社区</td>
<td><a href="https://discord.gg/clawd">discord.gg/clawd</a></td>
</tr>
<tr>
<td>ClawHub 市场</td>
<td><a href="https://clawhub.com">clawhub.com</a></td>
</tr>
<tr>
<td>ClawHub 源码</td>
<td><a href="https://github.com/openclaw/clawhub">github.com/openclaw/clawhub</a></td>
</tr>
<tr>
<td>Star 增长曲线</td>
<td><a href="https://star-history.com/#openclaw/openclaw">star-history.com/#openclaw/openclaw</a></td>
</tr>
<tr>
<td>DeepWiki 分析</td>
<td><a href="https://deepwiki.com/openclaw/openclaw">deepwiki.com/openclaw/openclaw</a></td>
</tr>
<tr>
<td>安全信任中心</td>
<td><a href="https://trust.openclaw.ai">trust.openclaw.ai</a></td>
</tr>
<tr>
<td>安全联络邮箱</td>
<td><a href="mailto:security@openclaw.ai">security@openclaw.ai</a></td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">官方子仓库导航</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">仓库</td>
<td style="text-align: center;">Stars</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://github.com/openclaw/openclaw">openclaw/openclaw</a></td>
<td>343,696</td>
<td>主仓库：CLI、Gateway、Agent 运行时、Plugin SDK</td>
</tr>
<tr>
<td><a href="https://github.com/openclaw/clawhub">openclaw/clawhub</a></td>
<td>7,214</td>
<td>官方 Skill 目录平台</td>
</tr>
<tr>
<td><a href="https://github.com/openclaw/skills">openclaw/skills</a></td>
<td>3,622</td>
<td>ClawHub 所有 Skill 版本归档</td>
</tr>
<tr>
<td><a href="https://github.com/openclaw/acpx">openclaw/acpx</a></td>
<td>1,834</td>
<td>无头 ACP CLI：有状态 Agent Client Protocol 会话</td>
</tr>
<tr>
<td><a href="https://github.com/openclaw/lobster">openclaw/lobster</a></td>
<td>992</td>
<td>Lobster 工作流 Shell：类型化 JSON 管线 + 审批门</td>
</tr>
<tr>
<td><a href="https://github.com/openclaw/nix-openclaw">openclaw/nix-openclaw</a></td>
<td>611</td>
<td>Nix 声明式打包支持</td>
</tr>
<tr>
<td><a href="https://github.com/openclaw/openclaw-ansible">openclaw/openclaw-ansible</a></td>
<td>545</td>
<td>Ansible 自动化部署（Tailscale + UFW + Docker）</td>
</tr>
<tr>
<td><a href="https://github.com/openclaw/openclaw-windows-node">openclaw/openclaw-windows-node</a></td>
<td>405</td>
<td>Windows 系统托盘 + PowerToys 命令面板扩展</td>
</tr>
<tr>
<td><a href="https://github.com/openclaw/openclaw.ai">openclaw/openclaw.ai</a></td>
<td>250</td>
<td>官方网站源码</td>
</tr>
<tr>
<td><a href="https://github.com/openclaw/trust">openclaw/trust</a></td>
<td>35</td>
<td>安全信任策略与威胁模型</td>
</tr>
<tr>
<td><a href="https://github.com/openclaw/caclawphony">openclaw/caclawphony</a></td>
<td>34</td>
<td>Symphony：项目任务 → 隔离自主执行</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">CLI 命令速查</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">命令</td>
<td style="text-align: center;">用途</td>
</tr>
</thead>
<tbody>
<tr>
<td>openclaw onboard</td>
<td>交互式引导安装（Gateway + 渠道 + Skills）</td>
</tr>
<tr>
<td>openclaw gateway run</td>
<td>启动 Gateway 控制平面</td>
</tr>
<tr>
<td>openclaw agent --message "..."</td>
<td>向 Agent 发送消息</td>
</tr>
<tr>
<td>openclaw channels status --probe</td>
<td>检查所有渠道连接状态</td>
</tr>
<tr>
<td>openclaw channels login</td>
<td>渠道登录（如 WhatsApp QR 扫码）</td>
</tr>
<tr>
<td>openclaw pairing approve <code></code></td>
<td>审批 DM 配对请求</td>
</tr>
<tr>
<td>openclaw doctor</td>
<td>诊断配置问题与安全风险</td>
</tr>
<tr>
<td>openclaw config set</td>
<td>修改配置项</td>
</tr>
<tr>
<td>openclaw update --channel</td>
<td>切换发布频道并更新</td>
</tr>
<tr>
<td>openclaw message send --to</td>
<td>向指定目标发送消息</td>
</tr>
<tr>
<td>openclaw gateway status</td>
<td>查看 Gateway 运行状态</td>
</tr>
<tr>
<td>openclaw nodes list</td>
<td>列出已连接的设备节点</td>
</tr>
<tr>
<td>clawhub install</td>
<td>从 ClawHub 安装 Skill</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">聊天内命令速查</span></div>
<p>以下命令可在 WhatsApp、Telegram、Slack、Discord、Teams、WebChat 等渠道的对话中直接发送：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">命令</td>
<td style="text-align: center;">功能</td>
</tr>
</thead>
<tbody>
<tr>
<td>/status</td>
<td>查看当前会话状态（模型 + token 用量 + 费用）</td>
</tr>
<tr>
<td>/new 或 /reset</td>
<td>重置会话</td>
</tr>
<tr>
<td>/compact</td>
<td>压缩会话上下文（生成摘要）</td>
</tr>
<tr>
<td>/think</td>
<td>设置思考等级：off|minimal|low|medium|high|xhigh</td>
</tr>
<tr>
<td>/verbose on|off</td>
<td>控制详细输出</td>
</tr>
<tr>
<td>/usage off|tokens|full</td>
<td>每条回复后显示用量统计</td>
</tr>
<tr>
<td>/restart</td>
<td>重启 Gateway（群组中仅 owner 可用）</td>
</tr>
<tr>
<td>/activation mention|always</td>
<td>群组激活模式切换</td>
</tr>
<tr>
<td>/elevated on|off</td>
<td>切换提升权限的 bash 访问</td>
</tr>
<tr>
<td>/approve</td>
<td>审批挂起的工具执行或插件操作</td>
</tr>
<tr>
<td>/acp spawn codex --bind here</td>
<td>在当前对话中创建 ACP 工作区</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">数据声明</span></div>
<p>本文所有技术细节均来源于以下一手数据，不依赖任何二手社区资料：</p>
<ul>
<li>GitHub API（api.github.com/repos/openclaw/openclaw）：仓库元数据、Stars/Forks/Issues 统计、贡献者排名、Release 说明</li>
<li>AGENTS.md（仓库根目录，35,263 字节）：架构规范、模块边界、构建指南、测试策略、发布守则</li>
<li>package.json（仓库根目录）：233 个 exports 条目（含 230 个 plugin-sdk 子路径）、47 个运行时依赖、22 个开发依赖、198 个 npm scripts</li>
<li>Dockerfile 与 docker-compose.yml：完整的容器化构建与部署配置</li>
<li>README.md（GitHub API base64 解码）：官方功能列表、渠道支持、安装指南、安全模型</li>
<li>GitHub Releases API（v2026.3.31、v2026.3.28 Release Notes）：破坏性变更与新功能详情</li>
</ul>
<div class="blog_h2"><span class="graybg">最小可用配置</span></div>
<p>完成 openclaw onboard 后，系统会生成一个最小配置文件 ~/.openclaw/openclaw.json。手动配置时，最小可用的 JSON 如下：</p>
<pre class="crayon-plain-tag">{
  "agent": {
    "model": "anthropic/claude-sonnet-4-6"
  }
}</pre>
<p>仅需指定一个模型即可启动 Gateway。Agent 会使用该模型进行所有推理任务。更复杂的配置可以声明多模型故障转移、渠道接入、安全策略、沙箱模式和 Skills：</p>
<pre class="crayon-plain-tag">{
  "agent": {
    "model": "openai/gpt-5.2",
    "fallbackModels": ["anthropic/claude-sonnet-4-6", "google/gemini-2.5-flash"],
    "thinkingLevel": "medium"
  },
  "agents": {
    "defaults": {
      "workspace": "~/.openclaw/workspace",
      "sandbox": {
        "mode": "non-main"
      }
    }
  },
  "channels": {
    "telegram": {
      "botToken": "123456:ABCDEF",
      "dmPolicy": "pairing",
      "allowFrom": []
    },
    "discord": {
      "token": "your-discord-bot-token",
      "dmPolicy": "pairing"
    },
    "whatsapp": {
      "allowFrom": ["+1234567890"]
    }
  },
  "browser": {
    "enabled": true
  },
  "gateway": {
    "mode": "local",
    "auth": {
      "mode": "token"
    }
  }
}</pre>
<p>完整配置参考详见 <a href="https://docs.openclaw.ai/gateway/configuration">docs.openclaw.ai/gateway/configuration</a>。配置文件支持 JSON5 格式（允许注释和尾逗号），这是一个面向人类可读性的设计选择。</p>
<div class="blog_h2"><span class="graybg">微信接入：腾讯官方插件</span></div>
<p>对于中国用户群体，微信接入是一个高优先级需求。OpenClaw 的微信支持通过腾讯官方发布的 npm 包 @tencent-weixin/openclaw-weixin 实现，基于 iLink Bot API。这是一个具有里程碑意义的集成——标志着腾讯以官方身份参与开源 AI Agent 生态。</p>
<p>安装与激活流程：</p>
<pre class="crayon-plain-tag"># 安装微信插件
openclaw plugins install "@tencent-weixin/openclaw-weixin"

# 扫码登录
openclaw channels login --channel openclaw-weixin</pre>
<p>微信集成当前仅支持私聊（Private Chat），不支持群聊。v2.x 版本要求 OpenClaw &gt;=2026.3.22。用户需要在微信客户端（我 → 设置 → 插件）中启用"微信 ClawBot 插件"——该功能由腾讯逐步灰度发布。</p>
<p>这种通过官方 npm 包而非逆向工程实现的微信接入路径，避免了 chatgpt-on-wechat 等项目面临的账号封禁风险，具有更高的稳定性和合规性。但目前的功能受限（仅私聊）也反映了腾讯在开放微信生态时的审慎态度。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/claw">OpenClaw学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/claw/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>在macOS上用Colima代替Docker</title>
		<link>https://blog.gmem.cc/colima-on-macos</link>
		<comments>https://blog.gmem.cc/colima-on-macos#comments</comments>
		<pubDate>Sun, 15 Mar 2026 03:29:02 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[IaaS]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=42245</guid>
		<description><![CDATA[<p>Colima 是 macOS 上最直接的本地容器运行时方案之一。它通过 Lima 启动一个 Linux 虚拟机，在这层 Linux 环境里运行 Docker、containerd，以及可选的 k3s Kubernetes，再把这些能力暴露给宿主机上的 [crayon-69fbde1b19686853230592-i/]、[crayon-69fbde1b1968b235445721-i/] 和其他常用客户端。本文整理 Colima 在 macOS 上的工作原理、安装方法、推荐配置、验证步骤以及几个最常见的运维注意事项。 macOS 与容器虚拟化 容器并不是“比虚拟机更轻的 <a class="read-more" href="https://blog.gmem.cc/colima-on-macos">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/colima-on-macos">在macOS上用Colima代替Docker</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>Colima 是 macOS 上最直接的本地容器运行时方案之一。它通过 Lima 启动一个 Linux 虚拟机，在这层 Linux 环境里运行 Docker、containerd，以及可选的 k3s Kubernetes，再把这些能力暴露给宿主机上的 <pre class="crayon-plain-tag">docker</pre>、<pre class="crayon-plain-tag">kubectl</pre> 和其他常用客户端。本文整理 Colima 在 macOS 上的工作原理、安装方法、推荐配置、验证步骤以及几个最常见的运维注意事项。</p>
<div class="blog_h1"><span class="graybg">macOS 与容器虚拟化</span></div>
<p>容器并不是“比虚拟机更轻的 macOS 进程”。容器依赖 Linux 内核提供的 Namespace、cgroups、OverlayFS 等机制。macOS 不提供这套 Linux 内核接口，因此在 macOS 上运行 Linux 容器时，底层一定存在一个 Linux 虚拟机。</p>
<p>这也是理解 Colima 的关键前提。它并没有绕过虚拟化，而是把虚拟化这一层做得更轻、更透明：用 Lima 管理 Linux 虚拟机，用 Colima 负责容器运行时和宿主机 CLI 的集成。</p>
<div class="blog_h1"><span class="graybg">Colima 是什么</span></div>
<p><span style="background-color: #c0c0c0;">Colima</span> 可以视为 Lima 之上的一层开发者友好封装。Lima 负责虚拟机、文件共享和端口转发，Colima 负责在虚拟机中配置容器运行时，并把它无缝接到宿主机工作流里。</p>
<p>从使用者视角看，Colima 有三个特征最重要：</p>
<ul>
<li>它在 macOS 上提供 Docker、containerd 和可选 Kubernetes 的本地运行环境。</li>
<li>它直接复用宿主机上的命令行客户端，而不是强制引入一个单独的桌面应用工作流。</li>
<li>它支持多 Profile，每个 Profile 都是一台独立虚拟机，适合把轻量 Docker 环境和启用 Kubernetes 的环境分开管理。</li>
</ul>
<div class="blog_h1"><span class="graybg">Colima 的优势</span></div>
<p>在 macOS 上做本地容器开发，问题通常不在“能不能跑起来”，而在于环境是否足够可控。Colima 的价值主要体现在三个方面：</p>
<ul>
<li>结构更清晰。宿主机 CLI、Linux 虚拟机、容器运行时三层边界明确，排障时更容易定位问题落在哪一层。</li>
<li>控制更细。CPU、内存、磁盘、架构、Kubernetes、网络和挂载方式都可以用命令行或 YAML 配置。</li>
<li>更接近工程化使用。它天然适合脚本化、Profile 隔离、CI 风格的命令流，而不是围绕 GUI 操作组织工作。</li>
</ul>
<p>如果机器上已经装有 Docker Desktop，也不一定必须先卸载。更重要的是明确当前激活的 Docker context，避免命令误连到错误的 daemon。</p>
<div class="blog_h1"><span class="graybg">安装</span></div>
<p>在 macOS 上，最简单的安装方式是 Homebrew。Docker runtime 需要宿主机安装 Docker CLI；如果要启用 Kubernetes，还需要 <pre class="crayon-plain-tag">kubectl</pre>。</p>
<pre class="crayon-plain-tag">brew install colima docker kubectl</pre>
<p>首次启动可以先使用默认配置验证链路是否正常：</p>
<pre class="crayon-plain-tag">colima start
docker run --rm hello-world
docker ps</pre>
<p>如果只想把它作为 Docker daemon 使用，这一步已经足够。若需要本地 Kubernetes，可以在启动时直接打开：</p>
<pre class="crayon-plain-tag">colima start --kubernetes
kubectl get nodes</pre>
<p>如果系统里同时存在多个 Docker daemon，先确认当前 context：</p>
<pre class="crayon-plain-tag">docker context ls
docker context use colima</pre>
<div class="blog_h1"><span class="graybg">常用配置</span></div>
<p>Colima 支持用命令行参数临时配置，也支持用 YAML 保存持久配置。日常使用中，最稳妥的入口通常是 <pre class="crayon-plain-tag">colima start --edit</pre>：它会打开当前 Profile 的配置文件并在保存后启动实例。</p>
<p>下面是一份适合本地开发的通用示例。它移除了项目私有镜像源和业务环境假设，只保留公共环境下最常见、最有价值的选项。</p>
<pre class="crayon-plain-tag"># 资源配置。默认值足以启动单个容器，但本地同时跑数据库、应用和 K3s 时通常偏小。
cpu: 4
memory: 8
disk: 100

# 创建后不可变的关键项。保持宿主机架构，使用 Docker runtime。
arch: host
runtime: docker

# 单节点 k3s。关闭默认 Traefik，避免抢占本地开发环境里的入口层职责。
kubernetes:
  enabled: true
  version: v1.35.0+k3s1
  k3sArgs:
    - --disable=traefik

# 给 VM 分配一个可从宿主机访问的地址，便于排障和直接探测。
network:
  address: true
  mode: shared

# 在较新的 macOS 上优先使用 Apple Virtualization Framework。
vmType: vz

# Apple Silicon 上运行 linux/amd64 用户态程序时启用 Rosetta。
rosetta: true

# VZ + virtiofs 是 macOS 上常见的高性能组合。
mountType: virtiofs

# 启动后自动激活 Docker / Kubernetes context。
autoActivate: true

# 给 VM 预装少量排障工具。脚本需要幂等。
provision:
  - mode: system
    script: |
      apt-get update
      apt-get install -y vim curl htop git make dnsutils net-tools iputils-ping telnet</pre>
<div class="blog_h2"><span class="graybg">配置总览</span></div>
<p>官方文档把配置分为资源、VM、运行时、网络、挂载、SSH、Provision 与环境变量几组。下表汇总当前官方模板中的主要配置项，以及文档中额外说明的 <pre class="crayon-plain-tag">rootDisk</pre>。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 24%;">配置项</td>
<td style="width: 16%;">默认值</td>
<td style="width: 44%;">说明</td>
<td style="width: 16%;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>cpu</td>
<td>2</td>
<td>分配给 VM 的 vCPU 数量。</td>
<td>资源项</td>
</tr>
<tr>
<td>memory</td>
<td>2</td>
<td>分配给 VM 的内存，单位 GiB。</td>
<td>资源项</td>
</tr>
<tr>
<td>disk</td>
<td>100</td>
<td>容器数据盘大小，单位 GiB。</td>
<td>创建后只能增大</td>
</tr>
<tr>
<td>rootDisk</td>
<td>20</td>
<td>VM 根文件系统磁盘大小，单位 GiB。</td>
<td>官方配置文档有说明</td>
</tr>
<tr>
<td>arch</td>
<td>host</td>
<td>VM 架构，可跟随宿主机，或显式指定其他架构。</td>
<td>创建后不可变</td>
</tr>
<tr>
<td>runtime</td>
<td>docker</td>
<td>容器运行时。</td>
<td>创建后不可变</td>
</tr>
<tr>
<td>modelRunner</td>
<td>docker</td>
<td>AI 模型运行器后端。</td>
<td>AI workload 配置</td>
</tr>
<tr>
<td>hostname</td>
<td>null</td>
<td>自定义 VM 主机名。</td>
<td>默认是 colima 或 colima-&lt;profile&gt;</td>
</tr>
<tr>
<td>kubernetes.enabled</td>
<td>false</td>
<td>是否启用内置 k3s。</td>
<td>Kubernetes 组</td>
</tr>
<tr>
<td>kubernetes.version</td>
<td>latest stable</td>
<td>k3s 版本，需与官方 k3s 版本号严格匹配。</td>
<td>Kubernetes 组</td>
</tr>
<tr>
<td>kubernetes.k3sArgs</td>
<td>--disable=traefik</td>
<td>传给 k3s server 的附加参数。</td>
<td>Kubernetes 组</td>
</tr>
<tr>
<td>kubernetes.port</td>
<td>0</td>
<td>Kubernetes API 监听端口，0 表示自动选取未占用端口。</td>
<td>Kubernetes 组</td>
</tr>
<tr>
<td>autoActivate</td>
<td>true</td>
<td>启动后自动激活 Docker / Kubernetes context。</td>
<td>客户端行为</td>
</tr>
<tr>
<td>network.address</td>
<td>false</td>
<td>给 VM 分配可达 IP。</td>
<td>macOS only</td>
</tr>
<tr>
<td>network.mode</td>
<td>shared</td>
<td>网络模式，官方文档列出 shared 与 bridged。</td>
<td>macOS only</td>
</tr>
<tr>
<td>network.interface</td>
<td>en0</td>
<td>bridged 模式使用的宿主机网卡。</td>
<td>仅 bridged 生效</td>
</tr>
<tr>
<td>network.preferredRoute</td>
<td>false</td>
<td>是否把分配到的 VM IP 设为优先路由。</td>
<td>需 address=true</td>
</tr>
<tr>
<td>network.dns</td>
<td>[]</td>
<td>自定义 DNS 解析器列表。</td>
<td>网络组</td>
</tr>
<tr>
<td>network.dnsHosts</td>
<td>host.docker.internal: host.lima.internal</td>
<td>内置 DNS 主机映射。</td>
<td>网络组</td>
</tr>
<tr>
<td>network.hostAddresses</td>
<td>false</td>
<td>复制宿主机 IP 到 VM，便于按指定主机地址做端口转发。</td>
<td>网络组</td>
</tr>
<tr>
<td>network.gatewayAddress</td>
<td>192.168.5.2</td>
<td>VM 网关地址，自定义时最后一个八位组必须为 2。</td>
<td>网络组</td>
</tr>
<tr>
<td>forwardAgent</td>
<td>false</td>
<td>是否转发宿主机 SSH agent。</td>
<td>SSH 组</td>
</tr>
<tr>
<td>docker</td>
<td>{}</td>
<td>直接映射到 Docker daemon.json 的配置块。</td>
<td>高级配置</td>
</tr>
<tr>
<td>vmType</td>
<td>qemu</td>
<td>虚拟化后端。</td>
<td>创建后不可变</td>
</tr>
<tr>
<td>portForwarder</td>
<td>ssh</td>
<td>端口转发实现，可选 ssh、grpc、none。</td>
<td>网络组</td>
</tr>
<tr>
<td>rosetta</td>
<td>false</td>
<td>Apple Silicon 上启用 amd64 用户态仿真。</td>
<td>需要 VZ</td>
</tr>
<tr>
<td>binfmt</td>
<td>true</td>
<td>启用外来架构二进制仿真。</td>
<td>跨架构兼容</td>
</tr>
<tr>
<td>nestedVirtualization</td>
<td>false</td>
<td>启用嵌套虚拟化。</td>
<td>需较新的 Apple Silicon 与 VZ</td>
</tr>
<tr>
<td>mountType</td>
<td>qemu 时 sshfs，vz 时 virtiofs</td>
<td>宿主机目录挂载驱动。</td>
<td>创建后不可变</td>
</tr>
<tr>
<td>mountInotify</td>
<td>false</td>
<td>向 VM 传播 inotify 文件事件。</td>
<td>实验性</td>
</tr>
<tr>
<td>cpuType</td>
<td>host</td>
<td>QEMU 的 CPU 类型。</td>
<td>QEMU only</td>
</tr>
<tr>
<td>provision</td>
<td>[]</td>
<td>启动时执行的 Provision 脚本。</td>
<td>需幂等</td>
</tr>
<tr>
<td>sshConfig</td>
<td>true</td>
<td>是否自动更新宿主机 <pre class="crayon-plain-tag">~/.ssh/config</pre>。</td>
<td>SSH 组</td>
</tr>
<tr>
<td>sshPort</td>
<td>0</td>
<td>VM SSH 服务端口，0 表示随机端口。</td>
<td>SSH 组</td>
</tr>
<tr>
<td>mounts</td>
<td>[]</td>
<td>额外挂载目录列表；设为 null 可禁用所有挂载。</td>
<td>挂载组</td>
</tr>
<tr>
<td>diskImage</td>
<td>""</td>
<td>自定义 VM 磁盘镜像路径。</td>
<td>高级配置</td>
</tr>
<tr>
<td>env</td>
<td>{}</td>
<td>注入到 VM 内部的环境变量。</td>
<td>环境变量组</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">模板与实例配置</span></div>
<p>Colima 官方文档给出了三层配置入口。第一层是 <pre class="crayon-plain-tag">colima start --edit</pre>，它直接编辑当前实例的配置文件；第二层是 <pre class="crayon-plain-tag">colima template</pre>，它修改后续新建实例继承的默认模板；第三层是环境变量，例如 <pre class="crayon-plain-tag">COLIMA_HOME</pre>、<pre class="crayon-plain-tag">COLIMA_PROFILE</pre> 和 <pre class="crayon-plain-tag">DOCKER_CONFIG</pre>，用于改变配置根目录、活动 Profile 和 Docker 客户端配置目录。</p>
<pre class="crayon-plain-tag"># 编辑当前 Profile 配置
colima start --edit

# 编辑默认模板
colima template

# 显式指定编辑器
colima start --edit --editor code
colima template --editor code</pre>
<p>配置文件路径也值得明确记住：</p>
<ul>
<li>默认 Profile：<pre class="crayon-plain-tag">~/.colima/default/colima.yaml</pre></li>
<li>命名 Profile：<pre class="crayon-plain-tag">~/.colima/&lt;profile&gt;/colima.yaml</pre></li>
<li>默认模板：<pre class="crayon-plain-tag">~/.colima/_templates/default.yaml</pre></li>
</ul>
<p>官方文档还特别指出，<span style="background-color: #c0c0c0;">arch、runtime、vmType、mountType</span> 属于创建后不可变的设置；修改这几项时，正确路径不是重启，而是删除实例并按新参数重建。</p>
<div class="blog_h1"><span class="graybg">验证</span></div>
<p>配置完成后，先检查虚拟机状态：</p>
<pre class="crayon-plain-tag">colima status</pre>
<p>如果启用了 <pre class="crayon-plain-tag">network.address</pre>，并且宿主机安装了 <pre class="crayon-plain-tag">jq</pre>，可以直接取出 VM IP：</p>
<pre class="crayon-plain-tag">export COLIMA_VM_IP=$(colima status -j | jq -r .ip_address)
echo "$COLIMA_VM_IP"
ping "$COLIMA_VM_IP"</pre>
<p>随后验证 Docker 和 Kubernetes 两条控制链是否都可用：</p>
<pre class="crayon-plain-tag">docker ps
kubectl config get-contexts
kubectl get nodes</pre>
<p>若需要进入底层虚拟机排查问题，可以直接 SSH：</p>
<pre class="crayon-plain-tag">colima ssh</pre>
<div class="blog_h1"><span class="graybg">日常运维命令</span></div>
<p>官方命令文档的结构很清晰：一个 <pre class="crayon-plain-tag">start</pre> 负责创建与启动实例，若干生命周期命令负责停启和删除，状态与连接命令负责观察和进入虚拟机，此外还有 Kubernetes、containerd、模板、升级与补全相关的辅助命令。</p>
<pre class="crayon-plain-tag"># 启动默认 Profile
colima start

# 启动并启用 Kubernetes
colima start --kubernetes

# 查看所有 Profile
colima list

# 停止当前实例
colima stop

# 删除当前实例及其容器数据
colima delete --data --force</pre>
<div class="blog_h2"><span class="graybg">命令列表</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 26%;">命令</td>
<td style="width: 20%;">典型形式</td>
<td>作用</td>
</tr>
</thead>
<tbody>
<tr>
<td>start</td>
<td>colima start [profile]</td>
<td>创建或启动指定 Profile，对大多数配置项也在这一命令上完成。</td>
</tr>
<tr>
<td>stop</td>
<td>colima stop [profile]</td>
<td>停止指定实例。</td>
</tr>
<tr>
<td>restart</td>
<td>colima restart [profile]</td>
<td>重启实例。</td>
</tr>
<tr>
<td>delete</td>
<td>colima delete [profile]</td>
<td>删除实例；可选择是否连同数据一并清理。</td>
</tr>
<tr>
<td>status</td>
<td>colima status [profile]</td>
<td>查看实例状态、运行时、架构、挂载方式、socket 路径等信息。</td>
</tr>
<tr>
<td>list</td>
<td>colima list</td>
<td>列出所有 Profile。</td>
</tr>
<tr>
<td>ssh</td>
<td>colima ssh [profile] -- command</td>
<td>进入 VM，或直接在 VM 内执行单条命令。</td>
</tr>
<tr>
<td>ssh-config</td>
<td>colima ssh-config [profile]</td>
<td>输出当前 VM 的 SSH 连接配置。</td>
</tr>
<tr>
<td>kubernetes start</td>
<td>colima kubernetes start [profile]</td>
<td>在已运行实例上启用 Kubernetes。</td>
</tr>
<tr>
<td>kubernetes stop</td>
<td>colima kubernetes stop [profile]</td>
<td>关闭 Kubernetes。</td>
</tr>
<tr>
<td>kubernetes reset</td>
<td>colima kubernetes reset [profile]</td>
<td>重置内置 Kubernetes 集群。</td>
</tr>
<tr>
<td>model run</td>
<td>colima model run &lt;model&gt;</td>
<td>运行 AI 模型。</td>
</tr>
<tr>
<td>model serve</td>
<td>colima model serve &lt;model&gt;</td>
<td>以 Web UI 方式提供模型服务。</td>
</tr>
<tr>
<td>nerdctl</td>
<td>colima nerdctl -- &lt;command&gt;</td>
<td>在 containerd runtime 下转发 nerdctl 命令。</td>
</tr>
<tr>
<td>nerdctl install</td>
<td>colima nerdctl install</td>
<td>安装独立可调用的 nerdctl。</td>
</tr>
<tr>
<td>template</td>
<td>colima template</td>
<td>生成或编辑默认配置模板。</td>
</tr>
<tr>
<td>update</td>
<td>colima update</td>
<td>更新 Colima。</td>
</tr>
<tr>
<td>prune</td>
<td>colima prune [profile]</td>
<td>清理未使用数据以释放磁盘空间。</td>
</tr>
<tr>
<td>version</td>
<td>colima version</td>
<td>输出版本信息。</td>
</tr>
<tr>
<td>completion</td>
<td>colima completion [shell]</td>
<td>生成 shell 自动补全脚本。</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">start 参数分组</span></div>
<p><pre class="crayon-plain-tag">colima start</pre> 是参数最集中的命令。官方命令文档把它拆成资源、运行时、VM、网络、挂载、Kubernetes、SSH、DNS 与配置九组。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 18%;">分组</td>
<td style="width: 32%;">参数</td>
<td>说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>资源</td>
<td>--cpus, --memory, --disk, --root-disk</td>
<td>设置 CPU、内存、数据盘和根盘容量。</td>
</tr>
<tr>
<td>运行时</td>
<td>--runtime, --activate</td>
<td>选择运行时，并决定启动后是否自动切换客户端 context。</td>
</tr>
<tr>
<td>VM</td>
<td>--arch, --vm-type, --cpu-type, --hostname, --disk-image, --vz-rosetta, --nested-virtualization, --binfmt, --foreground</td>
<td>控制架构、虚拟化后端、CPU 模型、磁盘镜像与前台运行方式。</td>
</tr>
<tr>
<td>网络</td>
<td>--network-address, --network-host-addresses, --network-mode, --network-interface, --network-preferred-route, --gateway-address, --port-forwarder</td>
<td>控制可达 IP、桥接模式、网关与端口转发实现。</td>
</tr>
<tr>
<td>挂载</td>
<td>--mount, --mount-type, --mount-inotify</td>
<td>控制宿主机目录挂载与文件事件传播。</td>
</tr>
<tr>
<td>Kubernetes</td>
<td>--kubernetes, --kubernetes-version, --k3s-arg, --k3s-listen-port</td>
<td>启用 k3s、指定版本，并传递附加参数。</td>
</tr>
<tr>
<td>SSH</td>
<td>--ssh-agent, --ssh-config, --ssh-port</td>
<td>控制 SSH agent 转发、宿主机 SSH 配置生成和端口。</td>
</tr>
<tr>
<td>DNS</td>
<td>--dns, --dns-host</td>
<td>配置 DNS 解析器与主机映射。</td>
</tr>
<tr>
<td>配置</td>
<td>--edit, --editor, --template, --save-config, --env</td>
<td>控制配置文件编辑、模板使用、参数持久化和 VM 环境变量注入。</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">其他命令参数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 24%;">命令</td>
<td style="width: 24%;">参数</td>
<td>说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>delete</td>
<td>--data, --force</td>
<td><pre class="crayon-plain-tag">--data</pre> 会连同镜像、卷等数据一起删除，<pre class="crayon-plain-tag">--force</pre> 跳过确认。</td>
</tr>
<tr>
<td>list</td>
<td>--json</td>
<td>以 JSON 形式输出所有 Profile。</td>
</tr>
<tr>
<td>ssh</td>
<td>-- command</td>
<td>不进入交互式 shell，直接在 VM 里执行单条命令。</td>
</tr>
<tr>
<td>model run / serve</td>
<td>--profile, --runner, --port</td>
<td>选择 Profile、模型运行器，以及 <pre class="crayon-plain-tag">serve</pre> 的 Web UI 端口。</td>
</tr>
<tr>
<td>completion</td>
<td>bash, zsh, fish, powershell</td>
<td>为不同 shell 生成补全脚本。</td>
</tr>
</tbody>
</table>
<p>当你修改了底层创建期参数，例如架构、运行时、虚拟机类型或挂载驱动，而变更没有按预期生效时，最常见的原因不是配置语法错误，而是这些参数属于<span style="background-color: #c0c0c0;">创建期决策</span>。这类参数通常需要删除实例后重建。</p>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">Docker context</span></div>
<p>很多“Cannot connect to the Docker daemon”问题，本质上不是 Colima 没有启动，而是本地 <pre class="crayon-plain-tag">docker</pre> CLI 仍然连着别的 context。优先检查 <pre class="crayon-plain-tag">docker context ls</pre>，再决定是否切到 <pre class="crayon-plain-tag">colima</pre>。</p>
<div class="blog_h2"><span class="graybg">镜像可见性</span></div>
<p>使用 Docker runtime 时，在同一个 Colima 实例里构建或拉取的镜像可以被 Kubernetes 直接使用。这对本地调试非常方便，因为不需要把每次本地构建都重新推到远端仓库。若切换为 containerd runtime，则镜像管理方式会随之变化，调试路径也应按 containerd 的 namespace 语义理解。</p>
<div class="blog_h2"><span class="graybg">VM IP 与端口发布</span></div>
<p><pre class="crayon-plain-tag">network.address: true</pre> 让宿主机能直接访问虚拟机 IP，这对排障很有用，但常规应用访问仍应优先通过容器端口映射完成。应用层服务应该通过 <pre class="crayon-plain-tag">-p HOST:CONTAINER</pre> 或 Kubernetes Service/Ingress 暴露，而不是把 VM IP 当成唯一访问入口。</p>
<div class="blog_h1"><span class="graybg">适用场景</span></div>
<p>如果你的目标只是“在 macOS 上拥有一个可脚本化、可重建、可启用 Kubernetes 的本地 Linux 容器环境”，Colima 基本就是最短路径。它尤其适合以下几类场景：</p>
<ul>
<li>需要在 macOS 上长期维护本地 Docker 与 K3s 开发环境。</li>
<li>希望把运行时控制逻辑写进脚本、Makefile 或项目 bootstrap 流程。</li>
<li>需要按项目拆分多个隔离环境，例如一个轻量 Docker Profile 加一个启用 Kubernetes 的 Profile。</li>
<li>需要在 Apple Silicon 上兼顾原生 ARM 开发与部分 amd64 兼容需求。</li>
</ul>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/colima-on-macos">在macOS上用Colima代替Docker</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/colima-on-macos/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
