[{"content":"","date":"2026-02-12","externalUrl":null,"permalink":"/blogs/","section":"Blog","summary":"","title":"Blog","type":"blogs"},{"content":"","date":"2026-02-12","externalUrl":null,"permalink":"/","section":"Blowfish","summary":"","title":"Blowfish","type":"page"},{"content":"","date":"2026-02-12","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"2026-02-12","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"2026-02-12","externalUrl":null,"permalink":"/tags/%E6%95%B0%E5%AD%A6/","section":"Tags","summary":"","title":"数学","type":"tags"},{"content":"","date":"2026-02-12","externalUrl":null,"permalink":"/series/%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0/","section":"Series","summary":"","title":"线性代数","type":"series"},{"content":"","date":"2026-02-12","externalUrl":null,"permalink":"/tags/%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0/","section":"Tags","summary":"","title":"线性代数","type":"tags"},{"content":" 到目前为止，我们研究的所有对象——向量子空间和线性映射——都严格地与“原点”绑定。子空间必须穿过原点，线性映射必须保持原点不变。然而，在几何和实际应用中，我们经常遇到不过原点的直线、平面，以及包含平移的变换。仿射空间和仿射映射就是将线性代数的理论拓展到这些更一般情况的框架。\n1. 仿射子空间 (Affine Subspaces) # 准确的数学定义 (Definition 2.25)\n设 \\(V\\) 是一个向量空间，\\(\\boldsymbol{x}_0 \\in V\\) 是一个特定的向量，而 \\(U \\subseteq V\\) 是一个向量子空间。那么，集合：\n$$ L = \\boldsymbol{x}_0 + U := \\{\\boldsymbol{x}_0 + \\boldsymbol{u} \\mid \\boldsymbol{u} \\in U\\} $$被称为 \\(V\\) 的一个仿射子空间 (affine subspace) 或线性流形 (linear manifold)。\n变量说明: \\(\\boldsymbol{x}_0\\) 被称为支撑点 (support point) 或平移向量 (translation vector)。 \\(U\\) 被称为方向空间 (direction space)。 直观解释\n仿射子空间就是将一个标准的向量子空间（必须过原点）进行整体平移，使其离开原点。\n如果方向空间 \\(U\\) 是一条过原点的直线，那么 \\(L = \\boldsymbol{x}_0 + U\\) 就是一条穿过点 \\(\\boldsymbol{x}_0\\) 并与 \\(U\\) 平行的直线。 如果方向空间 \\(U\\) 是一个过原点的平面，那么 \\(L = \\boldsymbol{x}_0 + U\\) 就是一个穿过点 \\(\\boldsymbol{x}_0\\) 并与 \\(U\\) 平行的平面。 核心区别：仿射子空间不一定是向量子空间，因为它通常不包含零向量 \\(\\boldsymbol{0}\\)（除非 \\(\\boldsymbol{x}_0\\) 恰好在 \\(U\\) 中，此时仿射子空间退化为向量子空间）。\nFigure 2.13，展示一条不过原点的直线作为仿射子空间的例子 参数化表示 (Parametric Equation)\n如果方向空间 \\(U\\) 的一组基是 \\(\\{\\boldsymbol{b}_1, \\dots, \\boldsymbol{b}_k\\}\\)，那么仿射子空间 \\(L\\) 中的任何一个向量 \\(\\boldsymbol{x}\\) 都可以被唯一地表示为：\n$$ \\boldsymbol{x} = \\boldsymbol{x}_0 + \\lambda_1\\boldsymbol{b}_1 + \\lambda_2\\boldsymbol{b}_2 + \\cdots + \\lambda_k\\boldsymbol{b}_k $$其中 \\(\\lambda_1, \\dots, \\lambda_k\\) 是实数参数。\n数值示例 (Example 2.26)\n直线 (Line): \\(\\mathbb{R}^n\\) 中的一维仿射子空间。由一个支撑点 \\(\\boldsymbol{x}_0\\) 和一个非零方向向量 \\(\\boldsymbol{b}_1\\) 定义。\n$$ \\boldsymbol{y} = \\boldsymbol{x}_0 + \\lambda \\boldsymbol{b}_1, \\quad \\lambda \\in \\mathbb{R} $$ 平面 (Plane): \\(\\mathbb{R}^n\\) 中的二维仿射子空间。由一个支撑点 \\(\\boldsymbol{x}_0\\) 和两个线性无关的方向向量 \\(\\boldsymbol{b}_1, \\boldsymbol{b}_2\\) 定义。\n$$ \\boldsymbol{y} = \\boldsymbol{x}_0 + \\lambda_1 \\boldsymbol{b}_1 + \\lambda_2 \\boldsymbol{b}_2, \\quad \\lambda_1, \\lambda_2 \\in \\mathbb{R} $$ 超平面 (Hyperplane): \\(\\mathbb{R}^n\\) 中的 \\((n-1)\\) 维仿射子空间。例如，\\(\\mathbb{R}^3\\) 中的平面就是超平面。\n与线性方程组的联系\n齐次方程 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{0}\\) 的解集是一个向量子空间（方向空间 \\(U\\)）。 非齐次方程 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{b}\\) (当有解时) 的解集是一个仿射子空间。它的通解 \\(\\boldsymbol{x} = \\boldsymbol{x}_p + \\boldsymbol{x}_h\\) 完美地符合了仿射子空间的定义，其中 \\(\\boldsymbol{x}_p\\) 是支撑点，\\(\\boldsymbol{x}_h\\)（即齐次解集）是方向空间。 2. 仿射映射 (Affine Mappings) # 仿射映射是在仿射空间之间的一种保持其几何结构的“良好”变换。\n准确的数学定义 (Definition 2.26)\n对于两个向量空间 \\(V, W\\)，一个线性映射 \\(\\Phi: V \\to W\\) 和一个平移向量 \\(\\boldsymbol{a} \\in W\\)，形如：\n$$ \\phi(\\boldsymbol{x}) = \\Phi(\\boldsymbol{x}) + \\boldsymbol{a} $$的映射 \\(\\phi: V \\to W\\) 被称为一个仿射映射 (affine mapping)。\n直观解释\n仿射映射可以被分解为一个线性变换（旋转、缩放、剪切等）和其后的一个平移 (translation)。\n$$ \\text{仿射变换} = \\text{线性变换} + \\text{平移} $$重要性质:\n仿射映射将直线映射为直线，将平面映射为平面。 仿射映射保持平行关系。 仿射映射的复合仍然是仿射映射。 与线性映射不同，仿射映射不一定保持原点不变。 与机器学习的联系\n在机器学习中，我们遇到的很多“线性”模型，实际上是仿射模型。\n线性回归: 一个标准的线性回归模型 \\(y = \\boldsymbol{w}^T\\boldsymbol{x} + b\\)。其中，\\(\\boldsymbol{w}^T\\boldsymbol{x}\\) 是一个线性映射部分，而偏置项（或截距）\\(b\\) 就是平移向量。因此，这是一个仿射映射。如果没有偏置项 \\(b\\)，它才是严格的线性映射。 神经网络: 神经网络中的每一层通常都由一个线性变换（权重矩阵 \\(\\boldsymbol{W}\\) 的乘法）和一个平移（偏置向量 \\(\\boldsymbol{b}\\) 的加法），然后跟一个非线性的激活函数构成。其核心部分 \\(\\boldsymbol{W}\\boldsymbol{x}+\\boldsymbol{b}\\) 就是一个仿射变换。 理解要点\n仿射的概念是将线性代数的适用范围从“严格过原点”的世界解放出来的关键。通过增加一个“平移”项，我们可以用线性代数强大的理论和工具来分析更广泛、更符合现实世界的几何对象和数据变换。\n本节知识点总结 # 仿射子空间: 一个向量子空间经过平移后得到的几何对象（如不过原点的线、面）。其结构为 \\(L = \\text{支撑点} + \\text{方向空间}\\)。 与方程组的联系: 非齐次方程组的解集是一个仿射子空间。 仿射映射: 一个线性变换后跟一个平移，其形式为 \\(\\phi(\\boldsymbol{x}) = \\boldsymbol{A}\\boldsymbol{x} + \\boldsymbol{b}\\)。 与机器学习的关系: 许多包含偏置项（bias/intercept）的机器学习模型，其本质都是仿射映射，而非严格的线性映射。 总结与思想脉络 # 至此，我们已经完成了线性代数基础理论的学习。让我们回顾一下本章的逻辑脉络：\n从一个具体问题 [线性方程组] 出发。 为了高效地表示它，引入了核心工具 [矩阵]。 为了系统地求解它，发展了 [高斯消元法]，并在此过程中发现了“解的结构”。 将解的结构抽象化，提炼出 [向量空间与子空间] 的公理化定义，这是我们理论的舞台。 为了度量这个舞台，我们引入了 [线性无关性]、[基]、[维度] 和 [秩] 等概念，它们构成了空间的“骨架”和“度量衡”。 接着，我们研究了舞台间的变换，即保持结构的 [线性映射]，并发现它与 [矩阵] 之间存在深刻的等价关系，同时揭示了 [核] 与 [像] 这两个核心子空间。 最后，通过引入“平移”，我们将理论从线性空间拓展到更普适的 [仿射空间]，使其能更好地描述现实世界的问题。 这个从“具体”到“抽象”再回归“应用”的过程，是学习线性代数的有效路径。这些基础概念，如向量、矩阵、秩、基、线性映射，将在后续学习对角化、特征值、主成分分析（PCA）以及各种机器学习算法中反复出现，是构建更高阶知识的坚实地基。\n","date":"2026-02-12","externalUrl":null,"permalink":"/blogs/math/linear-algebra/09/","section":"Blog","summary":"","title":"线性代数：仿射空间","type":"blogs"},{"content":" 在上一节，我们学会了如何判断一组向量是否“冗余”（线性相关）。现在，我们要寻找一个“恰到好处”的向量组——它既要足够强大，能够表示出整个空间的所有向量；又要足够精简，不包含任何冗余信息。这个完美的向量组，就是“基”。\n1. 生成集与基 # 生成集 (Generating Set) 与 张成空间 (Span) (Definition 2.13)\n准确的数学定义: 给定向量空间 \\(V\\) 和一组向量 \\(\\mathcal{A} = \\{\\boldsymbol{x}_1, \\dots, \\boldsymbol{x}_k\\} \\subseteq V\\)。由 \\(\\mathcal{A}\\) 中向量的所有线性组合构成的集合，被称为 \\(\\mathcal{A}\\) 的张成空间 (span)，记作 \\(\\text{span}[\\mathcal{A}]\\) 或 \\(\\text{span}[\\boldsymbol{x}_1, \\dots, \\boldsymbol{x}_k]\\)。\n如果 \\(\\text{span}[\\mathcal{A}] = V\\)，即 \\(V\\) 中的任何一个向量都可以被表示为 \\(\\mathcal{A}\\) 的线性组合，那么我们称 \\(\\mathcal{A}\\) 是 \\(V\\) 的一个生成集 (generating set)。\n直观解释:\n一个非零向量的张成空间是穿过原点和该向量的一条直线。 两个线性无关的向量的张成空间是穿过原点和这两个向量的一个平面。 生成集就是能“撑起”整个向量空间的向量集合。 基 (Basis) (Definition 2.14)\n一个生成集可能包含冗余的向量。例如，\\(\\mathbb{R}^2\\) 中任意三个不共线的向量都可以生成整个平面，但显然其中一个是多余的。我们希望找到最精简的生成集。\n准确的数学定义: 向量空间 \\(V\\) 的一个基 (Basis) 是一个线性无关的生成集。\n等价定义 (核心洞见): 对于一个向量组 \\(\\mathcal{B} \\subseteq V\\)，以下陈述是等价的：\n\\(\\mathcal{B}\\) 是 \\(V\\) 的一个基。 \\(\\mathcal{B}\\) 是一个极小生成集 (minimal generating set)：即，它是 \\(V\\) 的一个生成集，但从中移除任何一个向量后，它就不再是生成集了。 \\(\\mathcal{B}\\) 是一个极大线性无关组 (maximal linearly independent set)：即，它是线性无关的，但向其中加入任何一个 \\(V\\) 中不属于 \\(\\mathcal{B}\\) 的向量后，整个集合就变得线性相关了。 重要意义: 基为向量空间提供了一个“坐标系”。一旦选定一个基 \\(\\mathcal{B} = \\{\\boldsymbol{b}_1, \\dots, \\boldsymbol{b}_n\\}\\)，空间中的任何向量 \\(\\boldsymbol{x}\\) 都可以被唯一地表示为基向量的线性组合：\n$$ \\boldsymbol{x} = c_1\\boldsymbol{b}_1 + c_2\\boldsymbol{b}_2 + \\cdots + c_n\\boldsymbol{b}_n $$这组唯一的权值 \\((c_1, \\dots, c_n)\\) 就是向量 \\(\\boldsymbol{x}\\) 在这个基下的坐标 (coordinates)。\n数值示例\n在 \\(\\mathbb{R}^3\\) 中，最常用的基是标准基 (standard/canonical basis):\n$$ \\mathcal{B} = \\left\\{ \\boldsymbol{e}_1 = \\begin{bmatrix} 1 \\\\ 0 \\\\ 0 \\end{bmatrix}, \\boldsymbol{e}_2 = \\begin{bmatrix} 0 \\\\ 1 \\\\ 0 \\end{bmatrix}, \\boldsymbol{e}_3 = \\begin{bmatrix} 0 \\\\ 0 \\\\ 1 \\end{bmatrix} \\right\\} $$ 注意：一个向量空间的基不是唯一的。例如，\\(\\left\\{ \\begin{bmatrix} 1 \\\\ 1 \\end{bmatrix}, \\begin{bmatrix} 1 \\\\ -1 \\end{bmatrix} \\right\\}\\) 也是 \\(\\mathbb{R}^2\\) 的一组基。\n2. 维度与秩 # 维度 (Dimension)\n虽然一个向量空间的基不唯一，但所有基所包含的向量个数都是相同的。这个不变的数字，被定义为向量空间的维度 (dimension)，记作 \\(\\text{dim}(V)\\)。\n直观解释: 维度衡量了空间的“自由度”。 \\(\\text{dim}(\\mathbb{R}^n) = n\\)。 一条线的维度是1。 一个平面的维度是2。 如何寻找一个空间的基？ (Example 2.17)\n给定一组生成向量 \\(\\{\\boldsymbol{x}_1, \\dots, \\boldsymbol{x}_m\\}\\)，要找到它们张成的子空间 \\(U\\) 的一组基：\n将这些向量作为列，构建矩阵 \\(\\boldsymbol{A} = [\\boldsymbol{x}_1, \\dots, \\boldsymbol{x}_m]\\)。 对 \\(\\boldsymbol{A}\\) 进行高斯消元，得到行阶梯形矩阵 (REF)。 REF 中主元 (pivot) 所在的列，其对应的原始向量就构成了 \\(U\\) 的一组基。 秩 (Rank) (Definition in Section 2.6.2)\n秩是矩阵的一个核心属性，它将矩阵的行、列和线性变换联系在一起。\n准确的数学定义: 一个矩阵 \\(\\boldsymbol{A} \\in \\mathbb{R}^{m \\times n}\\) 的秩 (rank)，记作 \\(\\text{rk}(\\boldsymbol{A})\\)，是其线性无关的列向量的最大数目。 等价定义 (核心定理): 矩阵的列秩 (column rank, 线性无关列的数目) 恒等于其行秩 (row rank, 线性无关行的数目)。 计算方法: 对矩阵 \\(\\boldsymbol{A}\\) 进行高斯消元得到行阶梯形矩阵 (REF)，REF 中主元的个数就是 \\(\\text{rk}(\\boldsymbol{A})\\)。 数值示例 (Example 2.18)\n$$ \\boldsymbol{A} = \\begin{bmatrix} 1 \u0026 0 \u0026 1 \\\\ 0 \u0026 1 \u0026 1 \\\\ 0 \u0026 0 \u0026 0 \\end{bmatrix} $$这个矩阵已经是REF形式。它有两个主元（在第1行第1列和第2行第2列）。因此，$\\text{rk}(\\boldsymbol{A}) = 2)。\n秩的重要性质与意义\n秩告诉我们关于矩阵和线性方程组的许多关键信息：\n与子空间维度的关系: \\(\\text{rk}(\\boldsymbol{A})\\) = 矩阵列空间 (Column Space) 的维度。列空间是由A的列向量张成的子空间。 \\(\\text{rk}(\\boldsymbol{A})\\) = 矩阵行空间 (Row Space) 的维度。行空间是由A的行向量张成的子空间。 \\(\\dim(\\text{Col}(\\boldsymbol{A})) = \\dim(\\text{Row}(\\boldsymbol{A})) = \\text{rk}(\\boldsymbol{A})\\) 与可逆性的关系: 对于一个 \\(n \\times n\\) 的方块矩阵 \\(\\boldsymbol{A}\\)，它是可逆的当且仅当 \\(\\text{rk}(\\boldsymbol{A}) = n\\)。这种情况被称为满秩 (full rank)。 与线性方程组解的关系: 方程组 \\(\\boldsymbol{A}\\boldsymbol{x}=\\boldsymbol{b}\\) 有解（相容），当且仅当 \\(\\text{rk}(\\boldsymbol{A}) = \\text{rk}([\\boldsymbol{A}|\\boldsymbol{b}])\\)，即加入 \\(\\boldsymbol{b}\\) 后，空间的维度没有增加。 齐次方程 \\(\\boldsymbol{A}\\boldsymbol{x}=\\boldsymbol{0}\\) 的解空间（零空间）的维度是 \\(n - \\text{rk}(\\boldsymbol{A})\\)。这将在下一节的秩-零度定理中详细说明。 与机器学习的联系\n矩阵的秩在机器学习中代表了数据的内在维度 (intrinsic dimension)。一个低秩矩阵意味着数据中存在大量的线性相关性或冗余。\n推荐系统: 用户-物品评分矩阵通常是低秩的，因为用户的喜好不是完全随机的，而是由少数几个潜在因素（如“喜欢科幻”、“喜欢喜剧”）决定的。利用这个低秩特性进行矩阵分解是推荐算法的核心。 数据压缩: 对于图像等数据，如果其对应的矩阵是低秩的，我们就可以用更少的基向量来近似表示它，从而实现压缩。 本节知识点总结 # 生成集与张成空间: 一组向量通过线性组合能“生成”的空间。 基 (Basis): 描述向量空间的最精简的“坐标轴”——一个线性无关的生成集。 维度 (Dimension): 基中向量的个数，是衡量空间“大小”的不变量。 秩 (Rank): 矩阵中线性无关的列（或行）的数目，可以通过高斯消元后主元的个数来计算。 秩的意义: 秩揭示了矩阵所代表的变换的“维度”，并直接关联到矩阵的可逆性和方程组解的性质。在机器学习中，它常被用来衡量数据的内在复杂度和冗余度。 ","date":"2026-02-12","externalUrl":null,"permalink":"/blogs/math/linear-algebra/07/","section":"Blog","summary":"","title":"线性代数：基与秩","type":"blogs"},{"content":" 在上一节中，我们看到矩阵是表示线性方程组的紧凑工具。然而，矩阵的意义远不止于此。它们不仅可以表示线性方程组，还能代表线性映射 (linear mappings)，即空间的变换。矩阵是整个线性代数的核心，是连接具体计算与抽象理论的桥梁。\n1. 矩阵的定义 # 准确的数学定义 (Definition 2.1)\n一个矩阵 (Matrix) \\(\\boldsymbol{A}\\) 是一个由 \\(m \\times n\\) 个实数 \\(a_{ij}\\) 组成的矩形阵列，其中 \\(i\\) 代表行号 (\\(1, \\dots, m\\))，\\(j\\) 代表列号 (\\(1, \\dots, n\\))。\n$$ \\boldsymbol{A} = \\begin{bmatrix} a_{11} \u0026 a_{12} \u0026 \\cdots \u0026 a_{1n} \\\\ a_{21} \u0026 a_{22} \u0026 \\cdots \u0026 a_{2n} \\\\ \\vdots \u0026 \\vdots \u0026 \\ddots \u0026 \\vdots \\\\ a_{m1} \u0026 a_{m2} \u0026 \\cdots \u0026 a_{mn} \\end{bmatrix} , \\quad a_{ij} \\in \\mathbb{R} $$我们称 \\(\\boldsymbol{A}\\) 为一个 \\(m \\times n\\) 矩阵，记作 \\(\\boldsymbol{A} \\in \\mathbb{R}^{m \\times n}\\)。\n特殊矩阵: 行向量 (Row vector): 只有一行的矩阵 (\\(1 \\times n\\))。 列向量 (Column vector): 只有一列的矩阵 (\\(m \\times 1\\))。这是本书中向量的默认形式。 方块矩阵 (Square matrix): 行数和列数相等的矩阵 (\\(n \\times n\\))。 理解要点\n矩阵不仅仅是数字的排列，它是一个结构化的数学对象。我们可以把它看作是一组列向量的集合，也可以看作是一组行向量的集合。这种多重视角在后续学习中非常关键。\n2. 矩阵的基本运算 # 2.2.1 矩阵加法与数乘 # 这两种运算非常直观，都是按元素 (element-wise) 进行的。\n矩阵加法: 两个同维度的矩阵 \\(\\boldsymbol{A}, \\boldsymbol{B} \\in \\mathbb{R}^{m \\times n}\\) 相加，结果矩阵 \\(\\boldsymbol{C} = \\boldsymbol{A} + \\boldsymbol{B}\\) 的每个元素是对应元素的和，即 \\(c_{ij} = a_{ij} + b_{ij}\\)。 标量乘法: 矩阵 \\(\\boldsymbol{A}\\) 与标量 \\(\\lambda \\in \\mathbb{R}\\) 相乘，结果矩阵 \\(\\boldsymbol{K} = \\lambda \\boldsymbol{A}\\) 的每个元素是原元素的 \\(\\lambda\\) 倍，即 \\(k_{ij} = \\lambda a_{ij}\\)。 2.2.1 矩阵乘法 (Matrix Multiplication) # 矩阵乘法是线性代数中最核心、最不直观但最重要的运算。\n数学严谨性: 对于矩阵 \\(\\boldsymbol{A} \\in \\mathbb{R}^{m \\times n}\\) 和 \\(\\boldsymbol{B} \\in \\mathbb{R}^{n \\times k}\\)，它们的乘积为一个新矩阵 \\(\\boldsymbol{C} = \\boldsymbol{A}\\boldsymbol{B} \\in \\mathbb{R}^{m \\times k}\\)。\\(\\boldsymbol{C}\\) 中第 \\(i\\) 行、第 \\(j\\) 列的元素 \\(c_{ij}\\) 的计算方式为：\n$$ c_{ij} = \\sum_{l=1}^{n} a_{il}b_{lj} = a_{i1}b_{1j} + a_{i2}b_{2j} + \\cdots + a_{in}b_{nj} $$ 直观解释: \\(c_{ij}\\) 的值是矩阵 \\(\\boldsymbol{A}\\) 的第 \\(i\\) 个行向量与矩阵 \\(\\boldsymbol{B}\\) 的第 \\(j\\) 个列向量的点积 (dot product)。\n适用条件: 矩阵 \\(\\boldsymbol{A}\\) 和 \\(\\boldsymbol{B}\\) 能够相乘（$\\boldsymbol{A}\\boldsymbol{B}$）的唯一条件是：\\(\\boldsymbol{A}\\) 的列数必须等于 \\(\\boldsymbol{B}\\) 的行数。\n$$ \\boldsymbol{A}_{m \\times \\color{red}{n}} \\cdot \\boldsymbol{B}_{ \\color{red}{n}\\color{block}{} \\times k} = \\boldsymbol{C}_{m \\times k} $$这个“内维度必须匹配”的规则至关重要。\n数值示例\n给定 \\(\\boldsymbol{A} = \\begin{bmatrix} 1 \u0026 2 \u0026 3 \\\\ 3 \u0026 2 \u0026 1 \\end{bmatrix} \\in \\mathbb{R}^{2 \\times 3}\\) 和 \\(\\boldsymbol{B} = \\begin{bmatrix} 0 \u0026 2 \\\\ 1 \u0026 -1 \\\\ 0 \u0026 1 \\end{bmatrix} \\in \\mathbb{R}^{3 \\times 2}\\)。 它们的乘积 \\(\\boldsymbol{A}\\boldsymbol{B}\\) 是一个 \\(2 \\times 2\\) 矩阵。\n$$ \\boldsymbol{AB} = \\begin{bmatrix} (1\\cdot0 + 2\\cdot1 + 3\\cdot0) \u0026 (1\\cdot2 + 2\\cdot(-1) + 3\\cdot1) \\\\ (3\\cdot0 + 2\\cdot1 + 1\\cdot0) \u0026 (3\\cdot2 + 2\\cdot(-1) + 1\\cdot1) \\end{bmatrix} = \\begin{bmatrix} 2 \u0026 3 \\\\ 2 \u0026 5 \\end{bmatrix} $$而它们的乘积 \\(\\boldsymbol{B}\\boldsymbol{A}\\) 是一个 \\(3 \\times 3\\) 矩阵。\n$$ \\boldsymbol{BA} = \\begin{bmatrix} 0 \u0026 2 \\\\ 1 \u0026 -1 \\\\ 0 \u0026 1 \\end{bmatrix} \\begin{bmatrix} 1 \u0026 2 \u0026 3 \\\\ 3 \u0026 2 \u0026 1 \\end{bmatrix} = \\begin{bmatrix} 6 \u0026 4 \u0026 2 \\\\ -2 \u0026 0 \u0026 2 \\\\ 3 \u0026 2 \u0026 1 \\end{bmatrix} $$矩阵乘法的性质与常见错误\n不满足交换律: 从上例可见，\\(\\boldsymbol{A}\\boldsymbol{B} \\neq \\boldsymbol{B}\\boldsymbol{A}\\)。有时甚至其中一个乘积有定义，另一个则没有。\n满足结合律: \\((\\boldsymbol{A}\\boldsymbol{B})\\boldsymbol{C} = \\boldsymbol{A}(\\boldsymbol{B}\\boldsymbol{C})\\)\n满足分配律: \\(\\boldsymbol{A}(\\boldsymbol{B} + \\boldsymbol{C}) = \\boldsymbol{A}\\boldsymbol{B} + \\boldsymbol{A}\\boldsymbol{C}\\)\n常见错误: 矩阵乘法不是按元素相乘！\n编程语言中，数组的运算符通常执行按元素相乘（称为哈达玛积 (Hadamard product)），这与数学上的矩阵乘法完全不同。务必使用专门的矩阵乘法函数（如 NumPy 中的 np.dot() 或 @ 运算符）。\n3. 特殊矩阵与相关运算 # 单位矩阵 (Identity Matrix) # 单位矩阵 \\(\\boldsymbol{I}_n \\in \\mathbb{R}^{n \\times n}\\) 是一个方块矩阵，其主对角线上的元素为1，其余元素为0。\n$$ \\boldsymbol{I}_3 = \\begin{bmatrix} 1 \u0026 0 \u0026 0 \\\\ 0 \u0026 1 \u0026 0 \\\\ 0 \u0026 0 \u0026 1 \\end{bmatrix} $$它在矩阵乘法中扮演着数字“1”的角色：对于任何 \\(\\boldsymbol{A} \\in \\mathbb{R}^{m \\times n}\\)，都有 \\(\\boldsymbol{I}_m \\boldsymbol{A} = \\boldsymbol{A} \\boldsymbol{I}_n = \\boldsymbol{A}\\)。\n逆矩阵 (Inverse Matrix) # 对于一个方块矩阵 \\(\\boldsymbol{A} \\in \\mathbb{R}^{n \\times n}\\)，如果存在另一个矩阵 \\(\\boldsymbol{B} \\in \\mathbb{R}^{n \\times n}\\) 使得：\n$$ \\boldsymbol{A}\\boldsymbol{B} = \\boldsymbol{B}\\boldsymbol{A} = \\boldsymbol{I}_n $$则称矩阵 \\(\\boldsymbol{A}\\) 是可逆的 (invertible) 或 非奇异的 (non-singular)，并称 \\(\\boldsymbol{B}\\) 是 \\(\\boldsymbol{A}\\) 的逆矩阵，记作 \\(\\boldsymbol{A}^{-1}\\)。\n注意: 不是所有方块矩阵都有逆矩阵。如果一个矩阵不可逆，我们称之为奇异的 (singular)。 2x2 矩阵求逆公式 对于一个 \\(2 \\times 2\\) 矩阵 \\(\\boldsymbol{A} = \\begin{bmatrix} a \u0026 b \\\\ c \u0026 d \\end{bmatrix}\\)，它的逆存在当且仅当其行列式 (determinant) \\(ad-bc \\neq 0\\)。此时，逆矩阵为：\n$$ \\boldsymbol{A}^{-1} = \\frac{1}{ad-bc} \\begin{bmatrix} d \u0026 -b \\\\ -c \u0026 a \\end{bmatrix} $$ 转置矩阵 (Transpose) # 矩阵 \\(\\boldsymbol{A} \\in \\mathbb{R}^{m \\times n}\\) 的转置是一个 \\(n \\times m\\) 矩阵，记作 \\(\\boldsymbol{A}^T\\)，其元素满足 \\((\\boldsymbol{A}^T)_{ij} = a_{ji}\\)。简单来说，就是将原矩阵的行变成列，列变成行。\n对称矩阵 (Symmetric Matrix) # 如果一个方块矩阵 \\(\\boldsymbol{A}\\) 满足 \\(\\boldsymbol{A} = \\boldsymbol{A}^T\\)，则称其为对称矩阵。对称矩阵在机器学习中（如协方差矩阵、核矩阵）有极其重要的应用。\n4. 重要性质总结 # 以下是关于逆和转置的一些重要性质，需要牢记：\n逆的性质: \\(\\boldsymbol{A}\\boldsymbol{A}^{-1} = \\boldsymbol{I} = \\boldsymbol{A}^{-1}\\boldsymbol{A}\\) \\((\\boldsymbol{A}\\boldsymbol{B})^{-1} = \\boldsymbol{B}^{-1}\\boldsymbol{A}^{-1}\\) (顺序反转，类似穿脱鞋袜) \\((\\boldsymbol{A}^{-1})^{-1} = \\boldsymbol{A}\\) 转置的性质: \\((\\boldsymbol{A}^T)^T = \\boldsymbol{A}\\) \\((\\boldsymbol{A}+\\boldsymbol{B})^T = \\boldsymbol{A}^T + \\boldsymbol{B}^T\\) \\((\\boldsymbol{A}\\boldsymbol{B})^T = \\boldsymbol{B}^T\\boldsymbol{A}^T\\) (顺序同样反转) 思考题 # 如果 \\(\\boldsymbol{A}\\) 和 \\(\\boldsymbol{B}\\) 都是对称矩阵，它们的和 \\(\\boldsymbol{A}+\\boldsymbol{B}\\) 一定是对称的吗？如果 \\(\\boldsymbol{A}\\) 和 \\(\\boldsymbol{B}\\) 都是对称矩阵，它们的积 \\(\\boldsymbol{A}\\boldsymbol{B}\\) 一定是对称的吗？为什么？（提示：考虑 \\((\\boldsymbol{A}\\boldsymbol{B})^T\\)）\n对称矩阵的和：总是对称的；对称矩阵的积：不一定对称，只有当两个对称矩阵可交换时，它们的积才是对称的 问题1：对称矩阵的和 # 结论：是的，对称矩阵的和一定是对称的。\n证明： 设 \\(\\boldsymbol{A}\\) 和 \\(\\boldsymbol{B}\\) 都是 \\(n \\times n\\) 对称矩阵，即 \\(\\boldsymbol{A}^T = \\boldsymbol{A}\\) 且 \\(\\boldsymbol{B}^T = \\boldsymbol{B}\\)。\n我们需要证明 \\((\\boldsymbol{A} + \\boldsymbol{B})^T = \\boldsymbol{A} + \\boldsymbol{B}\\)。\n利用转置的性质：\n$$ (\\boldsymbol{A} + \\boldsymbol{B})^T = \\boldsymbol{A}^T + \\boldsymbol{B}^T = \\boldsymbol{A} + \\boldsymbol{B} $$因此 \\(\\boldsymbol{A} + \\boldsymbol{B}\\) 是对称矩阵。\n问题2：对称矩阵的积 # 结论：不一定，对称矩阵的积不一定是对称的。\n分析： 按照提示，考虑 \\((\\boldsymbol{A}\\boldsymbol{B})^T\\)：\n$$ (\\boldsymbol{A}\\boldsymbol{B})^T = \\boldsymbol{B}^T\\boldsymbol{A}^T = \\boldsymbol{B}\\boldsymbol{A} $$由于 \\(\\boldsymbol{A}\\) 和 \\(\\boldsymbol{B}\\) 都是对称矩阵。\n要使 \\(\\boldsymbol{A}\\boldsymbol{B}\\) 对称，必须满足：\n$$ \\boldsymbol{A}\\boldsymbol{B} = (\\boldsymbol{A}\\boldsymbol{B})^T = \\boldsymbol{B}\\boldsymbol{A} $$即 \\(\\boldsymbol{A}\\) 和 \\(\\boldsymbol{B}\\) 必须可交换。但一般情况下，矩阵乘法不满足交换律。\n反例： 考虑两个 \\(2 \\times 2\\) 对称矩阵：\n$$ \\boldsymbol{A} = \\begin{pmatrix} 1 \u0026 1 \\\\ 1 \u0026 0 \\end{pmatrix}, \\quad \\boldsymbol{B} = \\begin{pmatrix} 0 \u0026 1 \\\\ 1 \u0026 1 \\end{pmatrix} $$计算它们的积：\n$$ \\boldsymbol{A}\\boldsymbol{B} = \\begin{pmatrix} 1 \u0026 1 \\\\ 1 \u0026 0 \\end{pmatrix}\\begin{pmatrix} 0 \u0026 1 \\\\ 1 \u0026 1 \\end{pmatrix} = \\begin{pmatrix} 1 \u0026 2 \\\\ 0 \u0026 1 \\end{pmatrix} $$$$ \\boldsymbol{B}\\boldsymbol{A} = \\begin{pmatrix} 0 \u0026 1 \\\\ 1 \u0026 1 \\end{pmatrix}\\begin{pmatrix} 1 \u0026 1 \\\\ 1 \u0026 0 \\end{pmatrix} = \\begin{pmatrix} 1 \u0026 0 \\\\ 2 \u0026 1 \\end{pmatrix} $$显然 \\(\\boldsymbol{A}\\boldsymbol{B} \\neq \\boldsymbol{B}\\boldsymbol{A}\\)，且 \\(\\boldsymbol{A}\\boldsymbol{B}\\) 不是对称矩阵。\n总结 # 对称矩阵的和：总是对称的 对称矩阵的积：不一定对称，只有当两个对称矩阵可交换时，它们的积才是对称的 这说明对称性在加法运算下保持，但在乘法运算下不一定保持。\n本节知识点总结 # 矩阵定义: 行数 \\(\\times\\) 列数的数字矩形阵列。 基本运算: 矩阵加法和数乘是按元素的，矩阵乘法是行与列的点积。 矩阵乘法要点: 不满足交换律，且内外维度必须匹配。 核心特殊矩阵: 单位矩阵 \\(\\boldsymbol{I}\\): 乘法中的“1”。 逆矩阵 \\(\\boldsymbol{A}^{-1}\\): 使得 \\(\\boldsymbol{A}\\boldsymbol{A}^{-1}=\\boldsymbol{I}\\)，仅方块矩阵可能存在。 转置矩阵 \\(\\boldsymbol{A}^T\\): 行列互换。 对称矩阵 \\(\\boldsymbol{A}\\): \\(\\boldsymbol{A}=\\boldsymbol{A}^T\\)，在机器学习中非常重要。 重要法则: 乘积的逆和乘积的转置都会导致顺序反转。 ","date":"2026-02-12","externalUrl":null,"permalink":"/blogs/math/linear-algebra/03/","section":"Blog","summary":"","title":"线性代数：矩阵","type":"blogs"},{"content":" 我们已经将线性方程组表示为紧凑的矩阵形式 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{b}\\)，并学习了矩阵的基本运算。现在，我们将系统地探讨如何求解这类方程组。本节介绍的高斯消元法不仅是一种求解算法，其过程本身也揭示了矩阵和解空间的深刻结构。\n1. 解的结构：特解与齐次解 # 在寻找求解方法之前，我们先来理解一个线性方程组解的通用结构。这个结构非常优美且重要。\n一个线性方程组 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{b}\\) 的通解 (general solution) 由两部分组成：\n$$ \\boldsymbol{x} = \\boldsymbol{x}_p + \\boldsymbol{x}_h $$ 变量说明: \\(\\boldsymbol{x}_p\\) 是方程 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{b}\\) 的一个任意特解 (particular solution)。 \\(\\boldsymbol{x}_h\\) 是相关齐次方程 (homogeneous equation) \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{0}\\) 的所有解。 直观解释\n这个结构有着清晰的几何意义：\n齐次解 \\(\\boldsymbol{x}_h\\): 所有满足 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{0}\\) 的解构成一个向量子空间（称为零空间(Null space)），它一定穿过原点。在三维空间中，它可能是一个点（原点）、一条过原点的线、一个过原点的面。这个解集描述了整个解集的“形状”和“方向”。 特解 \\(\\boldsymbol{x}_p\\): 这个解的作用像一个“定位器”。它将穿过原点的齐次解空间“平移”到正确的位置，使其能够满足 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{b}\\)。 因此，求解 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{b}\\) 的过程可以分解为三个步骤：\n找到一个特解 \\(\\boldsymbol{x}_p\\) 来满足 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{b}\\)。 找到齐次方程 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{0}\\) 的所有解 \\(\\boldsymbol{x}_h\\)。 将它们相加得到通解。 2. 高斯消元法 (Gaussian Elimination) # 高斯消元法是一种通过一系列初等行变换 (elementary row operations) 将复杂的方程组简化，从而轻松求解的算法。\n核心工具\n增广矩阵 (Augmented Matrix): 将系数矩阵 \\(\\boldsymbol{A}\\) 和常数向量 \\(\\boldsymbol{b}\\) 并排放在一起，形成 \\([\\boldsymbol{A} | \\boldsymbol{b}]\\)。对这个增广矩阵的行进行操作，就等价于对方程组进行操作，但形式上更简洁。\n初等行变换:\n交换 (Exchange)：交换两行的位置。 缩放 (Scaling)：将某一行乘以一个非零常数 \\(\\lambda\\)。 加减 (Addition)：将一行的倍数加到另一行上。 这些操作不会改变方程组的解集。\n算法目标\n高斯消元的目标是将增广矩阵化为一种“阶梯状”的简单形式。\n目标1：行阶梯形矩阵 (Row Echelon Form, REF) 一个矩阵处于REF，如果它满足：\n所有全零行都在非零行的下方。 某行的第一个非零元素（称为主元 (pivot) 或 主元系数 (leading coefficient)）严格位于其上一行主元的右侧。 这就形成了一个“阶梯”结构。\n目标2：简化行阶梯形矩阵 (Reduced Row Echelon Form, RREF) RREF在REF的基础上增加了两个更严格的条件：\n每个主元的值都必须是 1。\n每个主元所在的列，除了主元本身，其他所有元素都必须是 0。\n一个矩阵的RREF是唯一的，这使得它成为求解的理想形式。\n变量的分类\n当矩阵化为REF或RREF后，变量可以分为两类：\n基本变量 (Basic variables): 对应主元所在的列的变量。 自由变量 (Free variables): 不包含主元的列所对应的变量。自由变量可以取任意实数值，是导致无穷多解的根源。 数值示例 (Example 2.6) 我们通过高斯消元法求解一个方程组。 初始增广矩阵：\n$$ \\left[ \\begin{array}{ccccc|c} -2 \u0026 4 \u0026 -2 \u0026 -1 \u0026 4 \u0026 -3 \\\\ 4 \u0026 -8 \u0026 3 \u0026 -3 \u0026 1 \u0026 2 \\\\ 1 \u0026 -2 \u0026 1 \u0026 -1 \u0026 1 \u0026 0 \\\\ 1 \u0026 -2 \u0026 0 \u0026 -3 \u0026 4 \u0026 a \\end{array} \\right] $$经过一系列初等行变换后，可以得到其行阶梯形矩阵 (REF)：\n$$ \\left[ \\begin{array}{ccccc|c} 1 \u0026 -2 \u0026 1 \u0026 -1 \u0026 1 \u0026 0 \\\\ 0 \u0026 0 \u0026 -1 \u0026 1 \u0026 -3 \u0026 2 \\\\ 0 \u0026 0 \u0026 0 \u0026 1 \u0026 -2 \u0026 1 \\\\ 0 \u0026 0 \u0026 0 \u0026 0 \u0026 0 \u0026 a+1 \\end{array} \\right] $$ 解的条件: 从最后一行 \\([0, 0, 0, 0, 0 | a+1]\\) 可知，要使方程成立 (\\(0=a+1\\))，必须有 \\(a = -1\\)。如果 \\(a \\neq -1\\)，则系统无解。 回代求解: 假设 \\(a=-1\\)，我们可以从下往上回代 (back substitution) 求出解。 变量分类: 第1、3、4列有主元，所以 \\(x_1, x_3, x_4\\) 是基本变量。第2、5列没有主元，所以 \\(x_2, x_5\\) 是自由变量。 继续化简到简化行阶梯形矩阵 (RREF) 会让求解更直接。\n3. 高斯消元法的应用 # 求解 \\(A\\boldsymbol{x}=\\boldsymbol{b}\\)\n构造增广矩阵 \\([\\boldsymbol{A} | \\boldsymbol{b}]\\)。 通过初等行变换将其化为RREF。 检查是否存在形如 \\([0, \\dots, 0 | c]\\) 且 \\(c \\neq 0\\) 的行，若有则无解。 将自由变量用参数（如 \\(\\lambda_1, \\lambda_2, \\dots\\)）表示。 将基本变量用这些参数表示，写出通解。 求解逆矩阵 \\(A^{-1}\\)\n求解 \\(\\boldsymbol{A}\\boldsymbol{X} = \\boldsymbol{I}\\) 等价于同时求解 \\(n\\) 个线性方程组。我们可以利用高斯-若尔当消元法 (Gauss-Jordan elimination) 一次性完成。\n构造增广矩阵 \\([\\boldsymbol{A} | \\boldsymbol{I}_n]\\)。 通过初等行变换将左侧的 \\(\\boldsymbol{A}\\) 化为 \\(\\boldsymbol{I}_n\\)。 如果成功，右侧的矩阵就是 \\(\\boldsymbol{A}^{-1}\\)。最终形式为 \\([\\boldsymbol{I}_n | \\boldsymbol{A}^{-1}]\\)。 如果左侧无法化为 \\(\\boldsymbol{I}_n\\)（例如出现全零行），则说明矩阵 \\(\\boldsymbol{A}\\) 不可逆。 Minus-1 Trick：快速求解齐次解\n这是一个寻找 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{0}\\) 解的巧妙技巧。\n将 \\(\\boldsymbol{A}\\) 化为RREF。 在对角线上缺少主元的位置，通过添加新行的方式构造一个增广方阵\\(\\tilde{\\boldsymbol{A}}\\)。新添加的行在该对角线位置的值为 -1，而在该行的其他所有位置都补0。 \\(\\tilde{\\boldsymbol{A}}\\) 中包含 -1 的那些列向量，就构成了齐次方程解空间的一组基。 数值示例 (Minus-1 Trick) 给定RREF矩阵 \\(\\boldsymbol{A} = \\begin{bmatrix} 1 \u0026 3 \u0026 0 \u0026 0 \u0026 3 \\\\ 0 \u0026 0 \u0026 1 \u0026 0 \u0026 9 \\\\ 0 \u0026 0 \u0026 0 \u0026 1 \u0026 -4 \\end{bmatrix}\\)。 它缺少第2和第5个主元。我们构造 \\(5 \\times 5\\) 的 \\(\\tilde{\\boldsymbol{A}}\\)：\n$$ \\tilde{\\boldsymbol{A}} = \\begin{bmatrix} 1 \u0026 3 \u0026 0 \u0026 0 \u0026 3 \\\\ 0 \u0026 \\color{red}{-1} \u0026 0 \u0026 0 \u0026 0 \\\\ 0 \u0026 0 \u0026 1 \u0026 0 \u0026 9 \\\\ 0 \u0026 0 \u0026 0 \u0026 1 \u0026 -4 \\\\ 0 \u0026 0 \u0026 0 \u0026 0 \u0026 \\color{red}{-1} \\end{bmatrix} $$包含 -1 的第2列和第5列就是解的基向量。齐次解为：\n$$ \\boldsymbol{x}_h = \\lambda_1 \\begin{bmatrix} 3 \\\\ -1 \\\\ 0 \\\\ 0 \\\\ 0 \\end{bmatrix} + \\lambda_2 \\begin{bmatrix} 3 \\\\ 0 \\\\ 9 \\\\ -4 \\\\ -1 \\end{bmatrix} $$ 本节知识点总结 # 解的结构: 通解 = 特解 + 齐次解。 高斯消元法: 通过初等行变换将增广矩阵化简为行阶梯形(REF)或简化行阶梯形(RREF)。 主元 (Pivot): REF中每行第一个非零元素，其数量和位置至关重要。 变量分类: 主元列对应基本变量，非主元列对应自由变量。 核心应用: 求解任意线性方程组 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{b}\\)。 通过 \\([\\boldsymbol{A}|\\boldsymbol{I}] \\to [\\boldsymbol{I}|\\boldsymbol{A}^{-1}]\\) 高效计算逆矩阵。 利用 Minus-1 Trick 快速写出齐次方程 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{0}\\) 的解。 ","date":"2026-02-12","externalUrl":null,"permalink":"/blogs/math/linear-algebra/04/","section":"Blog","summary":"","title":"线性代数：求解线性方程组","type":"blogs"},{"content":" 我们从线性代数最基本也是最经典的问题开始：求解线性方程组。许多复杂的科学和工程问题，经过数学建模后，最终都会归结为求解一个大规模的线性方程组。理解它，是通往线性代数抽象世界的第一扇门。\n1. 从实际问题到数学模型 # 应用场景\n考虑一个生产计划问题（例 2.1）： 一家公司生产 \\(n\\) 种产品 \\(N_1, \\dots, N_n\\)，需要消耗 \\(m\\) 种资源 \\(R_1, \\dots, R_m\\)。\n已知： 生产一个单位的产品 \\(N_j\\) 需要消耗 \\(a_{ij}\\) 个单位的资源 \\(R_i\\)。 资源 \\(R_i\\) 的总供应量为 \\(b_i\\)。 目标： 找到一个最优生产计划，即每种产品 \\(N_j\\) 应该生产多少个单位，记为 \\(x_j\\)，恰好能用完所有资源。 要消耗完所有资源 \\(R_i\\)，那么生产所有产品对该资源的总消耗量必须等于其总供应量 \\(b_i\\)。对于第 \\(i\\) 种资源，我们有：\n$$ a_{i1}x_1 + a_{i2}x_2 + \\dots + a_{in}x_n = b_i $$这里，\\(a_{i1}x_1\\) 是生产 \\(x_1\\) 单位产品 \\(N_1\\) 所需资源 \\(R_i\\) 的量，以此类推。由于我们有 \\(m\\) 种资源，我们就得到了 \\(m\\) 个这样的方程。\n2. 线性方程组的定义与解 # 准确的数学定义\n将上述问题泛化，我们就得到了一个由 \\(m\\) 个方程和 \\(n\\) 个未知数组成的线性方程组 (system of linear equations)：\n$$ \\begin{align*} a_{11}x_1 + a_{12}x_2 + \\cdots + a_{1n}x_n \u0026= b_1 \\\\ a_{21}x_1 + a_{22}x_2 + \\cdots + a_{2n}x_n \u0026= b_2 \\\\ \\vdots \\qquad \u0026 \\qquad \\vdots \\\\ a_{m1}x_1 + a_{m2}x_2 + \\cdots + a_{mn}x_n \u0026= b_m \\end{align*} $$ 变量说明： \\(x_1, x_2, \\dots, x_n\\) 是未知数 (unknowns)。 \\(a_{ij}\\) 是第 \\(i\\) 个方程中第 \\(j\\) 个未知数的系数 (coefficients)。 \\(b_i\\) 是第 \\(i\\) 个方程的常数项 (constant terms)。 一个满足该系统中所有方程的 \\(n\\) 元组 \\((\\boldsymbol{x_1, \\dots, x_n})\\) 被称为该线性方程组的一个解 (solution)。\n数值示例：解的三种可能性\n一个线性方程组的解集可能有三种情况：\n无解 (No solution)：系统内存在矛盾的方程。 例如，对于系统 (2.4)：\n$$ \\begin{align*} x_1 + x_2 + x_3 \u0026= 3 \\quad \u0026(1) \\\\ x_1 - x_2 + 2x_3 \u0026= 2 \\quad \u0026(2) \\\\ 2x_1 \\qquad + 3x_3 \u0026= 1 \\quad \u0026(3) \\end{align*} $$将方程 (1) 和 (2) 相加，我们得到 \\(2x_1 + 3x_3 = 5\\)。这与方程 (3) 的 \\(2x_1 + 3x_3 = 1\\) 直接矛盾。因此，该系统无解。我们称这样的系统为不相容的 (inconsistent)。\n唯一解 (Exactly one solution)：系统提供的信息不多不少，刚好能确定所有未知数。 例如，系统 (2.5) 经计算可得唯一解 \\((1, 1, 1)\\)。\n$$ \\begin{align*}x_1 + x_2 + x_3 \u0026= 3 \\quad (1) \\\\x_1 - x_2 + 2x_3 \u0026= 2 \\quad (2) \\\\x_2 + x_3 \u0026= 2 \\quad (3)\\end{align*} $$ 无穷多解 (Infinitely many solutions)：系统中的方程存在冗余，无法唯一确定所有未知数。 例如，对于系统 (2.6)：\n$$ \\begin{align*} x_1 + x_2 + x_3 \u0026= 3 \\quad \u0026(1) \\\\ x_1 - x_2 + 2x_3 \u0026= 2 \\quad \u0026(2) \\\\ 2x_1 \\qquad + 3x_3 \u0026= 5 \\quad \u0026(3) \\end{align*} $$我们发现方程 (3) 恰好是 (1) 和 (2) 的和，它没有提供任何新信息，是冗余的 (redundant)。我们可以引入一个自由变量 (free variable)，比如令 \\(x_3 = a \\in \\mathbb{R}\\)。然后用 \\(a\\) 来表示其他变量，得到一组解：\\((\\frac{5}{2} - \\frac{3}{2}a, \\frac{1}{2} + \\frac{1}{2}a, a)\\)。由于 \\(a\\) 可以取任何实数，所以该系统有无穷多解。\n3. 几何解释 # 线性方程组的几何意义为我们提供了强大的直观。\n直观解释：\n在二维空间 (\\(\\mathbb{R}^2\\))中，一个关于 \\(x_1, x_2\\) 的线性方程（如 \\(ax_1 + bx_2 = c\\)）代表一条直线。 在三维空间 (\\(\\mathbb{R}^3\\))中，一个关于 \\(x_1, x_2, x_3\\) 的线性方程代表一个平面。 求解一个线性方程组，在几何上等价于寻找所有这些线（或面）的公共交点。\n几何上的三种可能性：\n唯一解：所有线（或面）交于一个点。 无解：线（或面）之间存在平行情况，导致它们没有公共交点。 无穷多解：线（或面）重合，交集是一条线、一个平面或更高维度的空间。 Figure 2.3: 展示两条直线相交于一点，作为唯一解的几何诠释 4. 迈向紧凑表示：矩阵 # 当方程数量和未知数增多时，上面的写法显得非常累赘。我们需要一种更紧凑、更强大的表示方法。我们可以将系数 \\(a_{ij}\\) 收集起来，形成一个矩阵 (matrix)，将未知数 \\(x_j\\) 和常数项 \\(b_i\\) 分别收集起来，形成向量 (vectors)。\n原始的方程组：\n$$ a_{11}x_1 + a_{12}x_2 + \\cdots + a_{1n}x_n = b_1 \\\\ \\vdots \\\\ a_{m1}x_1 + a_{m2}x_2 + \\cdots + a_{mn}x_n = b_m $$可以被优雅地重写为：\n$$ x_1 \\begin{bmatrix} a_{11} \\\\ a_{21} \\\\ \\vdots \\\\ a_{m1} \\end{bmatrix} + x_2 \\begin{bmatrix} a_{12} \\\\ a_{22} \\\\ \\vdots \\\\ a_{m2} \\end{bmatrix} + \\cdots + x_n \\begin{bmatrix} a_{1n} \\\\ a_{2n} \\\\ \\vdots \\\\ a_{mn} \\end{bmatrix} = \\begin{bmatrix} b_1 \\\\ b_2 \\\\ \\vdots \\\\ b_m \\end{bmatrix} $$这个视角非常重要：整个方程组可以看作是寻找一组标量 \\(x_1, \\dots, x_n\\) 去线性组合一组列向量，以得到目标向量。\n最终，我们可以写成最紧凑的形式：\n$$ \\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{b} $$ 变量说明： \\(\\boldsymbol{A}\\) 是一个 \\(m \\times n\\) 的系数矩阵。 \\(\\boldsymbol{x}\\) 是一个 \\(n \\times 1\\) 的未知数向量。 \\(\\boldsymbol{b}\\) 是一个 \\(m \\times 1\\) 的常数向量。 理解要点 # 线性方程组的核心，无论是代数上还是几何上，都是关于相交和组合。\\(A\\boldsymbol{x}=\\boldsymbol{b}\\) 这种形式不仅简洁，更揭示了线性代数的本质：将问题转化为对矩阵和向量这两种结构化对象的操作。\n本节知识点总结 # 线性方程组：由多个线性方程构成的系统，是许多实际问题的数学模型。 解的三种形态：无解（不相容）、唯一解、无穷多解。 几何直观：解是 \\(n\\) 维空间中 \\(m\\) 个超平面（线、面等）的交集。 核心代数表示：可以紧凑地表示为矩阵向量乘积的形式 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{b}\\)。 重要视角：求解 \\(\\boldsymbol{A}\\boldsymbol{x} = \\boldsymbol{b}\\) 等价于寻找一个对矩阵 \\(\\boldsymbol{A}\\) 的列向量的线性组合，使其等于向量 \\(\\boldsymbol{b}\\)。 ","date":"2026-02-12","externalUrl":null,"permalink":"/blogs/math/linear-algebra/02/","section":"Blog","summary":"","title":"线性代数：线性方程组","type":"blogs"},{"content":" 我们已经知道，向量可以在一个向量空间中进行加法和数乘运算。一个自然的问题是：给定一组向量，我们是否能用其中的一部分来表示出另一部分？换句话说，这组向量中是否存在“冗余”的信息？线性无关性就是用来精确描述这种“冗余性”的语言。\n1. 线性组合 (Linear Combination) # 准确的数学定义 (Definition 2.11)\n给定向量空间 \\(V\\) 中的一组向量 \\(\\{\\boldsymbol{x}_1, \\boldsymbol{x}_2, \\dots, \\boldsymbol{x}_k\\}\\) 和一组标量 \\(\\{\\lambda_1, \\lambda_2, \\dots, \\lambda_k\\} \\subset \\mathbb{R}\\)，形如：\n$$ \\boldsymbol{v} = \\lambda_1\\boldsymbol{x}_1 + \\lambda_2\\boldsymbol{x}_2 + \\cdots + \\lambda_k\\boldsymbol{x}k = \\sum{i=1}^{k} \\lambda_i\\boldsymbol{x}_i $$的向量 \\(\\boldsymbol{v}\\) 被称为向量组 \\(\\{\\boldsymbol{x}_1, \\dots, \\boldsymbol{x}_k\\}\\) 的一个线性组合。\n2. 线性无关与线性相关的定义 # 我们特别关心一种特殊的线性组合——结果为零向量 \\(\\boldsymbol{0}\\) 的线性组合。\n准确的数学定义 (Definition 2.12)\n对于向量空间 \\(V\\) 中的一组向量 \\(\\{\\boldsymbol{x}_1, \\boldsymbol{x}_2, \\dots, \\boldsymbol{x}_k\\}\\)：\n如果方程 \\(\\lambda_1\\boldsymbol{x}_1 + \\lambda_2\\boldsymbol{x}_2 + \\cdots + \\lambda_k\\boldsymbol{x}_k = \\boldsymbol{0}\\) 只存在唯一解 \\(\\lambda_1 = \\lambda_2 = \\cdots = \\lambda_k = 0\\)，那么这组向量被称为线性无关 (linearly independent)。这个唯一的解被称为平凡解 (trivial solution)。 如果该方程除了平凡解之外还存在其他解（即至少有一个 \\(\\lambda_i \\neq 0\\)），那么这组向量被称为线性相关 (linearly dependent)。 直观解释\n线性无关: “没有冗余”: 向量组中的任何一个向量都不能被其他向量的线性组合表示出来。 “提供了新方向”: 每个向量都贡献了一个其他向量无法企及的“维度”或“方向”。 几何上: 在三维空间中，两个不共线的向量是线性无关的。三个不共面的向量是线性无关的。 线性相关: “存在冗余”: 向量组中至少有一个向量可以被其他向量的线性组合所表示。 “未提供新方向”: 至少有一个向量位于由其他向量张成的空间中（如平面或直线）。 几何上: 在三维空间中，两个共线的向量是线性相关的。三个共面的向量是线性相关的。 数值示例：地理位置 (Example 2.13)\n想象一下如何描述从内罗毕（Nairobi, Kenya）到基加利（Kigali, Rwanda）的路线。一个人可能会这样说：\n向量 \\(\\boldsymbol{v}_1\\): “从内罗毕向西北方向走 506 公里，到达坎帕拉（Kampala, Uganda）”。 向量 \\(\\boldsymbol{v}_2\\): “然后从坎帕拉向西南方向走 374 公里，到达基加利”。 这两个向量是线性无关的，因为你无法只通过“西北”方向的移动来达到一个有“西南”分量的位置。它们提供了两个独立的方向。\n现在，加入第三个向量： 3. 向量 \\(\\boldsymbol{v}_3\\): “（从内罗毕）向西走 751 公里”。 事实证明，\\(\\boldsymbol{v}_3\\) 恰好是 \\(\\boldsymbol{v}_1\\) 和 \\(\\boldsymbol{v}_2\\) 的向量和（即 \\(\\boldsymbol{v}_3 = 1 \\cdot \\boldsymbol{v}_1 + 1 \\cdot \\boldsymbol{v}_2\\)）。因此，向量组 \\(\\{\\boldsymbol{v}_1, \\boldsymbol{v}_2, \\boldsymbol{v}_3\\}\\) 是线性相关的。\\(\\boldsymbol{v}_3\\) 是一个冗余信息，因为它可以用前两个向量表示出来。\nFigure 2.7，展示三个地理向量的关系 3. 判断线性无关性的方法 # 性质判断\n任何包含零向量的向量组一定是线性相关的。（因为可以让零向量的系数不为零，其他为零） 任何包含两个相同向量的向量组一定是线性相关的。 如果一个向量是另一个向量的倍数，那么这两个向量线性相关。 高斯消元法 (系统性方法)\n这是判断任意向量组线性无关性的最通用、最强大的方法。\n构造矩阵: 将向量组 \\(\\{\\boldsymbol{x}_1, \\dots, \\boldsymbol{x}_k\\}\\) 作为列向量，构建一个矩阵 \\(\\boldsymbol{A} = [\\boldsymbol{x}_1, \\boldsymbol{x}_2, \\dots, \\boldsymbol{x}_k]\\)。 求解齐次方程: 判断线性无关性等价于判断齐次方程 \\(\\boldsymbol{A}\\boldsymbol{\\lambda} = \\boldsymbol{0}\\) 是否只有零解 \\(\\boldsymbol{\\lambda} = \\boldsymbol{0}\\)。 高斯消元: 对矩阵 \\(\\boldsymbol{A}\\) 进行高斯消元，将其化为行阶梯形矩阵 (REF)。 数主元个数: 如果REF中每一列都有主元 (pivot)，这意味着没有自由变量，方程 \\(\\boldsymbol{A}\\boldsymbol{\\lambda} = \\boldsymbol{0}\\) 只有唯一的零解。因此，向量组是线性无关的。 如果REF中存在没有主元的列，这意味着存在自由变量，方程 \\(\\boldsymbol{A}\\boldsymbol{\\lambda} = \\boldsymbol{0}\\) 有无穷多非零解。因此，向量组是线性相关的。 数值示例 (Example 2.14)\n判断向量 \\(\\boldsymbol{x}_1 = \\begin{bmatrix} 1 \\\\ 2 \\\\ -3 \\\\ 4 \\end{bmatrix}\\)， \\(\\boldsymbol{x}_2 = \\begin{bmatrix} 1 \\\\ 1 \\\\ 0 \\\\ 2 \\end{bmatrix}\\)， \\(\\boldsymbol{x}_3 = \\begin{bmatrix} -1 \\\\ -2 \\\\ 1 \\\\ 1 \\end{bmatrix}\\) 是否线性相关。\n构造矩阵 \\(\\boldsymbol{A}\\):\n$$ \\boldsymbol{A} = \\begin{bmatrix} 1 \u0026 1 \u0026 -1 \\\\ 2 \u0026 1 \u0026 -2 \\\\ -3 \u0026 0 \u0026 1 \\\\ 4 \u0026 2 \u0026 1 \\end{bmatrix} $$ 进行高斯消元，得到REF（或RREF）:\n$$ \\begin{bmatrix} 1 \u0026 1 \u0026 -1 \\\\ 2 \u0026 1 \u0026 -2 \\\\ -3 \u0026 0 \u0026 1 \\\\ 4 \u0026 2 \u0026 1 \\end{bmatrix} \\xrightarrow{\\text{消元}} \\begin{bmatrix} 1 \u0026 1 \u0026 -1 \\\\ 0 \u0026 1 \u0026 0 \\\\ 0 \u0026 0 \u0026 1 \\\\ 0 \u0026 0 \u0026 0 \\end{bmatrix} $$ 观察主元：矩阵有3列，消元后的REF有3个主元（每列都有一个）。\n结论：由于每一列都有主元，方程 \\(\\boldsymbol{A}\\boldsymbol{\\lambda} = \\boldsymbol{0}\\) 只有零解。因此，这三个向量是线性无关的。\n理解要点\n线性无关是构建向量空间“骨架”的砖块。一组线性无关的向量张开了一个“不塌陷”的空间，其中每个向量都提供了不可替代的贡献。通过高斯消元法寻找主元，是我们洞察这种贡献是否独立的最有效工具。\n本节知识点总结 # 线性组合: 向量的基本表达方式，即向量的加权和。 线性无关: 组合为零向量的唯一方式是所有权重都为零。直观上表示“无冗余”。 线性相关: 存在非零的权重组合能得到零向量。直观上表示“有冗余”。 判断核心: 线性无关性的判断问题，本质上是求解一个齐次线性方程组是否有非零解的问题。 实用工具: 高斯消元法是判断线性无关性的标准算法，通过检查REF的主元列来得出结论。 ","date":"2026-02-12","externalUrl":null,"permalink":"/blogs/math/linear-algebra/06/","section":"Blog","summary":"","title":"线性代数：线性无关","type":"blogs"},{"content":" 至此，我们已经研究了向量空间这一“静态”的舞台。现在，我们要研究舞台之间的“幕布切换”——即向量空间之间的变换。我们特别关心那些能保持向量空间原有结构（如网格线、原点位置）的“良好”变换，这些变换就是线性映射。\n1. 线性映射的定义 # 准确的数学定义 (Definition 2.15)\n考虑两个实向量空间 \\(V \\) 和 \\(W \\)。一个映射 (或函数) \\(\\Phi: V \\to W \\) 如果满足以下两个条件，则被称为线性映射 (Linear Mapping) (或线性变换/向量空间同态)：\n保持加法结构: \\(\\Phi(\\boldsymbol{x} + \\boldsymbol{y}) = \\Phi(\\boldsymbol{x}) + \\Phi(\\boldsymbol{y}), \\quad \\forall \\boldsymbol{x}, \\boldsymbol{y} \\in V\\) (两个向量先相加再变换，等同于先各自变换再相加) 保持数乘结构: \\(\\Phi(\\lambda\\boldsymbol{x}) = \\lambda\\Phi(\\boldsymbol{x}), \\quad \\forall \\lambda \\in \\mathbb{R}, \\forall \\boldsymbol{x} \\in V\\) (一个向量先拉伸再变换，等同于先变换再拉伸) 这两个条件可以合并为一个：\n$$ \\Phi(\\lambda\\boldsymbol{x} + \\psi\\boldsymbol{y}) = \\lambda\\Phi(\\boldsymbol{x}) + \\psi\\Phi(\\boldsymbol{y}), \\quad \\forall \\lambda, \\psi \\in \\mathbb{R}, \\forall \\boldsymbol{x}, \\boldsymbol{y} \\in V $$直观解释\n一个线性映射在几何上保持了网格线的平行和等距特性，并且原点位置不变 (\\(\\Phi(\\boldsymbol{0}_V) = \\boldsymbol{0}_W\\))。常见的线性映射包括：\n旋转 (Rotation) 缩放 (Scaling) 反射 (Reflection) 剪切 (Shear) Figure 2.10，展示旋转、拉伸等线性变换对一组向量的作用 2. 线性映射的矩阵表示 # 线性映射与矩阵之间存在着一种深刻而美妙的等价关系。\n核心思想：任何一个从 \\(n \\) 维向量空间 \\(V \\) 到 \\(m \\) 维向量空间 \\(W \\) 的线性映射 \\(\\Phi\\)，都可以用一个 \\(m \\times n \\) 的变换矩阵 (transformation matrix) \\(\\boldsymbol{A}_{\\Phi} \\) 来唯一表示。\n如何构建这个矩阵？\n在输入空间 \\(V \\) 中选定一组基 \\(\\mathcal{B} = (\\boldsymbol{b}_1, \\dots, \\boldsymbol{b}_n)\\)（注意，这里是有序基）。\n在输出空间 \\(W \\) 中选定一组基 \\(\\mathcal{C} = (\\boldsymbol{c}_1, \\dots, \\boldsymbol{c}_m) \\)。\n考察 \\(V \\) 中的每个基向量 \\(\\boldsymbol{b}_j \\) 在经过映射 \\(\\Phi \\) 后的像 \\(\\Phi(\\boldsymbol{b}_j) \\)。这个像位于 \\(W \\) 中，因此可以被唯一地表示为 \\(\\mathcal{C} \\) 中基向量的线性组合。\n将像 \\(\\Phi(\\boldsymbol{b}_j) \\) 在基 \\(\\mathcal{C}\\)下的坐标向量，作为变换矩阵 \\(\\boldsymbol{A}{\\Phi}\\) 的第 \\(j \\) 列。\n$$ \\boldsymbol{A}_{\\Phi} = \\begin{bmatrix} | \u0026 | \u0026 \u0026 | \\\\ \\Phi(\\boldsymbol{b}_1)_{\\mathcal{C}} \u0026 \\Phi(\\boldsymbol{b}_2)_{\\mathcal{C}} \u0026 \\cdots \u0026 \\Phi(\\boldsymbol{b}_n)_{\\mathcal{C}} \\\\ | \u0026 | \u0026 \u0026 | \\end{bmatrix} $$一旦得到这个矩阵，对于 \\(V \\) 中任意向量 \\(\\boldsymbol{x}\\)（其在基 \\(\\mathcal{B} \\) 下的坐标为 \\(\\hat{\\boldsymbol{x}}\\)），其像 \\(\\Phi(\\boldsymbol{x}) \\) 在基 \\(\\mathcal{C} \\) 下的坐标 \\(\\hat{\\boldsymbol{y}} \\) 就可以通过简单的矩阵乘法得到：\n$$ \\hat{\\boldsymbol{y}} = \\boldsymbol{A}_{\\Phi}\\hat{\\boldsymbol{x}} $$ 要点\n矩阵不仅仅是数字的表格，它编码了一个线性变换的全部信息。矩阵的每一列，都描绘了对应基向量的“命运”——它将被变换到何方。知道了所有基向量的去向，我们就知道了整个空间的去向。\n3. 基变换 (Change of Basis) # 同一个线性映射，如果选择不同的基（坐标系），其对应的变换矩阵也会不同。基变换公式揭示了这些不同矩阵之间的关系。\n定理与公式 (Theorem 2.20)\n设 \\(\\Phi: V \\to W \\) 是一个线性映射。\n\\(A_{\\Phi} \\) 是 \\(\\Phi \\) 在 \\(V \\) 的旧基 \\(\\mathcal{B} \\) 和 \\(W \\) 的旧基 \\(\\mathcal{C}\\)下的变换矩阵。 \\(\\tilde{A}_{\\Phi} \\) 是 \\(\\Phi \\) 在 \\(V \\) 的新基 \\(\\tilde{\\mathcal{B}} \\) 和 \\(W \\) 的新基 \\(\\tilde{\\mathcal{C}}\\)下的变换矩阵。 则它们的关系为：\n$$ \\tilde{\\boldsymbol{A}}_{\\Phi} = \\boldsymbol{T}^{-1}\\boldsymbol{A}_{\\Phi}\\boldsymbol{S} $$ 变量说明: \\(\\boldsymbol{S} \\) 是从 \\(V \\) 中新基到旧基的基变换矩阵。它的第 \\(j \\) 列是新基向量 \\(\\tilde{\\boldsymbol{b}}_j \\) 在旧基 \\(\\mathcal{B}\\)下的坐标。 \\(\\boldsymbol{T} \\) 是从 \\(W \\) 中新基到旧基的基变换矩阵。 \\(\\boldsymbol{T}^{-1} \\) 是从 \\(W \\) 中旧基到新基的基变换矩阵。 直观解释 (复合映射视角): 要用新基坐标计算变换，可以分三步走：\n$\\boldsymbol{S}\\hat{\\boldsymbol{x}}$: 先用矩阵 \\(\\boldsymbol{S} \\) 将向量在新基 \\(\\tilde{\\mathcal{B}} \\) 下的坐标 \\(\\hat{\\boldsymbol{x}} \\) 换算成在旧基 \\(\\mathcal{B} \\) 下的坐标。 $\\boldsymbol{A}_{\\Phi}(\\boldsymbol{S}\\hat{\\boldsymbol{x}})$: 然后用旧的变换矩阵 \\(\\boldsymbol{A}_{\\Phi} \\) 进行线性变换，得到在旧基 \\(\\mathcal{C}\\)下的像坐标。 $\\boldsymbol{T}^{-1}(\\boldsymbol{A}_{\\Phi}\\boldsymbol{S}\\hat{\\boldsymbol{x}})$: 最后用矩阵 \\(\\boldsymbol{T}^{-1} \\) 将像坐标从旧基 \\(\\mathcal{C} \\) 换算到新基 \\(\\tilde{\\mathcal{C}}\\)下。 相似矩阵 (Similar Matrices)\n一个重要的特例是自同态 (endomorphism)，即映射在同一个空间内部进行 \\(\\Phi: V \\to V\\)，且输入和输出都使用同一组基。此时基变换公式简化为：\n$$ \\tilde{A}_{\\Phi} = \\boldsymbol{S}^{-1}\\boldsymbol{A}_{\\Phi}\\boldsymbol{S} $$称矩阵 \\(\\boldsymbol{A}_{\\Phi} \\) 和 \\(\\tilde{A}_{\\Phi}\\) 是相似的。相似矩阵描述的是同一个线性变换在不同坐标系下的不同表现形式。寻找一个“最好”的基，使得变换矩阵变得尽可能简单（如对角矩阵），是特征值分解的核心思想。\n4. 核 (Kernel) 与 像 (Image) # 线性映射的两个基本子空间：\n定义 (Definition 2.23)\n对于线性映射 \\(\\Phi: V \\to W\\)：\n核 (Kernel) 或 零空间 (Null Space): 输入空间 \\(V \\) 中，所有被映射到输出空间 \\(W \\) 的零向量 \\(\\boldsymbol{0}_W \\) 的向量集合。\n$$ \\text{ker}(\\Phi) := \\{\\boldsymbol{v} \\in V \\mid \\Phi(\\boldsymbol{v}) = \\boldsymbol{0}_W\\} $$对于矩阵变换 \\(\\boldsymbol{A}\\boldsymbol{x} \\)， 核就是齐次方程 \\(\\boldsymbol{A}\\boldsymbol{x}=\\boldsymbol{0}\\) 的解空间。\n像 (Image) 或 值域 (Range): 输出空间 \\(W \\) 中，所有可以被 \\(V \\) 中某个向量映射“到达”的向量集合。\n$$ \\text{Im}(\\Phi) := \\{\\Phi(\\boldsymbol{v}) \\mid \\boldsymbol{v} \\in V\\} $$对于矩阵变换 \\(\\boldsymbol{A}\\boldsymbol{x} \\)， 像就是矩阵 \\(\\boldsymbol{A} \\) 的列空间 (column space)，即由其列向量张成的子空间。\nFigure 2.12，展示核与像的示意图 秩-零度定理 (Rank-Nullity Theorem)\n这是线性代数的基本定理之一，它深刻地揭示了输入空间、核与像之间的维度关系。\n$$ \\text{dim}(\\text{ker}(\\Phi)) + \\text{dim}(\\text{Im}(\\Phi)) = \\text{dim}(V) $$ 零度 (Nullity): \\(\\text{nullity}(\\Phi) = \\text{dim}(\\text{ker}(\\Phi))\\) 秩 (Rank): \\(\\text{rank}(\\Phi) = \\text{dim}(\\text{Im}(\\Phi)) = \\text{rk}(\\boldsymbol{A}_{\\Phi})\\) 定理释义：输入空间的维度，等于“被压缩掉的维度”（核的维度）加上“变换后剩下的维度”（像的维度）。这是一个深刻的维度守恒定律。\n本节知识点总结 # 线性映射: 保持向量加法和数乘运算的结构性变换。 矩阵表示: 任何线性映射都唯一对应一个变换矩阵，该矩阵的列是基向量的像。 基变换: 同一个线性变换在不同基下的矩阵表示通过 \\(\\tilde{A} = T^{-1}AS \\) 关联，相似矩阵是其重要特例。 核与像: 两个核心子空间。核是被映射为零的输入集合，像是所有可能的输出集合。 秩-零度定理: \\(\\text{dim}(\\text{核}) + \\text{dim}(\\text{像}) = \\text{dim}(\\text{输入空间})\\)，揭示了维度在变换中的守恒关系。 ","date":"2026-02-12","externalUrl":null,"permalink":"/blogs/math/linear-algebra/08/","section":"Blog","summary":"","title":"线性代数：线性映射","type":"blogs"},{"content":" 在前面的学习中，我们不只是在解方程，更是在观察解的“结构”。特别是齐次方程 \\(\\boldsymbol{A}\\boldsymbol{x}=\\boldsymbol{0}\\) 的解集，它具有一种美妙的封闭性：任意两个解相加仍然是解，任意解的数乘也仍然是解。这种结构正是“向量空间”的雏形。本节将这个概念正式化、公理化。\n1. 前置概念：群 (Group) # 在定义向量空间之前，我们需要了解一个更基础的代数结构——群。群研究的是单个运算下的代数结构。\n准确的数学定义\n一个群 (Group) 是一个集合 \\(G\\) 与其上的一个二元运算 \\(\\otimes\\) （例如加法 + 或乘法 ·）组成的代数结构，记作 \\((G, \\otimes)\\)，它必须满足以下四个公理：\n封闭性 (Closure)：对于 \\(G\\) 中任意两个元素 \\(x, y\\)，它们的运算结果 \\(x \\otimes y\\) 仍然在 \\(G\\) 中。\n$$ \\forall x, y \\in G : x \\otimes y \\in G $$ 结合律 (Associativity)：运算的顺序不影响结果。\n$$ \\forall x, y, z \\in G : (x \\otimes y) \\otimes z = x \\otimes (y \\otimes z) $$ 单位元 (Neutral Element)：\\(G\\) 中存在一个“单位”元素 \\(e\\)，任何元素与它运算都保持不变。\n$$ \\exists e \\in G, \\forall x \\in G : x \\otimes e = e \\otimes x = x $$ 逆元 (Inverse Element)：对于 \\(G\\) 中的每一个元素 \\(x\\)，都存在一个“逆”元素 \\(y\\)（通常记作 \\(x^{-1}\\)），使得它们运算的结果是单位元 \\(e\\)。\n$$ \\forall x \\in G, \\exists y \\in G : x \\otimes y = y \\otimes x = e $$ 如果一个群还额外满足交换律 (Commutativity)，即 \\(\\forall x, y \\in G : x \\otimes y = y \\otimes x\\)，那么它被称为阿贝尔群 (Abelian Group) 或交换群。\n数值示例：\n\\((\\mathbb{Z}, +)\\)：整数集合与加法运算构成一个阿贝尔群。单位元是0，任何整数 \\(n\\) 的逆元是 \\(-n\\)。 \\((\\mathbb{R} \\setminus\\{0\\}, \\cdot)\\)：非零实数集合与乘法运算构成一个阿贝尔群。单位元是1，任何数 \\(x\\) 的逆元是 \\(1/x\\)。 \\((\\mathbb{N}_0, +)\\) (非负整数集与加法)：不是群。虽然有单位元0，但除了0以外的元素（如3）没有逆元（-3不在集合中）。 2. 向量空间的定义 # 向量空间在一个阿贝尔群的基础上，引入了第二种运算——标量乘法，并要求这两种运算之间和谐共存。\n准确的数学定义 (Definition 2.9)\n一个实向量空间 (Real Vector Space) 是一个集合 \\(V\\) 配备了两种运算：\n向量加法 + : \\(V \\times V \\to V\\) 标量乘法 · : \\(\\mathbb{R} \\times V \\to V\\) 并且满足以下公理：\n(V, +) 是一个阿贝尔群：这自动包含了向量加法的封闭性、结合律、交换律，以及存在零向量 \\(\\boldsymbol{0}\\) (单位元) 和每个向量 \\(\\boldsymbol{v}\\) 的负向量 \\(-\\boldsymbol{v}\\) (逆元)。\n标量乘法与向量加法的分配律：\n$$ \\forall \\lambda \\in \\mathbb{R}, \\forall \\boldsymbol{x}, \\boldsymbol{y} \\in V : \\lambda \\cdot (\\boldsymbol{x} + \\boldsymbol{y}) = \\lambda \\cdot \\boldsymbol{x} + \\lambda \\cdot \\boldsymbol{y} $$ 标量乘法与标量加法的分配律：\n$$ \\forall \\lambda, \\psi \\in \\mathbb{R}, \\forall \\boldsymbol{x} \\in V : (\\lambda + \\psi) \\cdot \\boldsymbol{x} = \\lambda \\cdot \\boldsymbol{x} + \\psi \\cdot \\boldsymbol{x} $$ 标量乘法的结合律：\n$$ \\forall \\lambda, \\psi \\in \\mathbb{R}, \\forall \\boldsymbol{x} \\in V : \\lambda \\cdot (\\psi \\cdot \\boldsymbol{x}) = (\\lambda\\psi) \\cdot \\boldsymbol{x} $$ 标量乘法的单位元：\n$$ \\forall \\boldsymbol{x} \\in V : 1 \\cdot \\boldsymbol{x} = \\boldsymbol{x} $$其中，1是实数中的乘法单位元。\n理解要点\n向量空间是一个高度结构化的“舞台”，在这个舞台上，“演员”（向量）可以自由地进行加法和数乘两种表演，而不会“跑出舞台”（封闭性）。这些公理确保了我们的代数运算（如移项、合并同类项）可以像处理普通数字一样安全地应用于向量。\n3. 向量子空间 (Vector Subspace) # 在实际应用中，我们常常关心一个大向量空间内部的、自身也构成向量空间的小集合，这就是子空间。\n准确的数学定义 (Definition 2.10)：\n设 \\(V\\) 是一个向量空间，其非空子集 \\(U \\subseteq V\\) 如果在继承 \\(V\\) 的加法和标量乘法运算下，其自身也构成一个向量空间，则称 \\(U\\) 是 \\(V\\) 的一个向量子空间 (vector subspace) 或 线性子空间 (linear subspace)。\n由于 \\(U\\) 中的元素天然满足 \\(V\\) 的所有运算律（如结合律、分配律），要判断 \\(U\\) 是否为子空间，我们只需验证其封闭性和单位元的存在性即可。\n子空间判定三原则：\n一个非空子集 \\(U \\subseteq V\\) 是 \\(V\\) 的子空间，当且仅当它满足以下三个条件：\n包含零向量: \\(\\boldsymbol{0}_V \\in U\\)。 对加法封闭: \\(\\forall \\boldsymbol{x}, \\boldsymbol{y} \\in U \\implies \\boldsymbol{x} + \\boldsymbol{y} \\in U\\)。 对标量乘法封闭: \\(\\forall \\lambda \\in \\mathbb{R}, \\forall \\boldsymbol{x} \\in U \\implies \\lambda \\boldsymbol{x} \\in U\\)。 直观解释与示例\n\\(\\mathbb{R}^3\\) 中的子空间: 平凡子空间：仅包含零向量 \\(\\{\\boldsymbol{0}\\}\\) 的集合，以及 \\(\\mathbb{R}^3\\) 本身。 过原点的直线：形如 \\(\\{\\lambda \\boldsymbol{v} | \\lambda \\in \\mathbb{R}\\}\\)，其中 \\(\\boldsymbol{v} \\neq \\boldsymbol{0}\\)。 过原点的平面：形如 \\(\\{\\lambda_1 \\boldsymbol{v}_1 + \\lambda_2 \\boldsymbol{v}_2 | \\lambda_1, \\lambda_2 \\in \\mathbb{R}\\}\\)，其中 \\(\\boldsymbol{v}_1, \\boldsymbol{v}_2\\) 不共线。 齐次方程组的解空间: 对于任意矩阵 \\(\\boldsymbol{A} \\in \\mathbb{R}^{m \\times n}\\)，其齐次方程 \\(\\boldsymbol{A}\\boldsymbol{x}=\\boldsymbol{0}\\) 的解集是 \\(\\mathbb{R}^n\\) 的一个子空间。 非齐次方程组的解空间: \\(\\boldsymbol{A}\\boldsymbol{x}=\\boldsymbol{b}\\) (当 \\(\\boldsymbol{b} \\neq \\boldsymbol{0}\\)) 的解集不是一个子空间，因为它不包含零向量 \\(\\boldsymbol{0}\\)。 Figure 2.6，展示R^2中哪些是子空间，哪些不是 与机器学习的联系\n子空间是降维和数据压缩的核心思想。例如，主成分分析(PCA)算法的目标就是找到一个低维的子空间，使得原始高维数据点到这个子空间的投影距离之和最小。这意味着我们用这个子空间来近似地“捕捉”数据的核心变化，从而丢弃冗余信息，达到降维的目的。\n本节知识点总结 # 群 (Group)：定义了单一封闭运算的基本代数结构，向量加法满足阿贝尔群的要求。 向量空间: 一个由向量组成的集合，定义了向量加法和标量乘法两种运算，并满足一套严格的公理。它是线性代数所有理论的根基。 向量子空间: 一个大向量空间中的“自给自足”的小世界，它本身也是一个向量空间。 子空间判定: 验证三条核心性质：包含零向量、对加法封闭、对数乘封闭。 重要实例: \\(\\mathbb{R}^n\\) 及其所有过原点的线、面等，以及齐次线性方程组的解空间。 ","date":"2026-02-12","externalUrl":null,"permalink":"/blogs/math/linear-algebra/05/","section":"Blog","summary":"","title":"线性代数：向量空间","type":"blogs"},{"content":" 线性代数（Linear Algebra）是数学的一个核心分支，它在机器学习、物理学、计算机图形学等众多领域都扮演着至关重要的角色。本章将引导我们深入理解线性代数的核心思想，从具体问题出发，逐步建立起抽象的理论框架，并最终回归到实际应用。\n在开始之前，我们先理解“代数”(Algebra)的本质。一个代数系统通常由两部分构成：\n一组对象 (Objects)：比如我们熟悉的数字。 一套操作这些对象的法则 (Rules)：比如加法和乘法。 线性代数，顾名思义，就是专门研究向量 (Vectors) 以及操作向量的法则的数学分支。\n1. 向量的广义概念 # 我们中学时接触的向量通常是带有箭头的线段，称为几何向量 (Geometric vectors)，例如 \\(\\vec{v}\\)。用粗体小写字母表示向量，例如 \\(\\boldsymbol{x}\\) 和 \\(\\boldsymbol{y}\\)。\n然而，向量的概念远比几何图形要广泛得多。\n准确的数学定义\n虽然严格定义一个向量空间需要满足包含结合律、分配律等在内的8条公理（详见文末思考题），但其最核心的特征在于“封闭性”。简单来说，如果一个对象集合满足以下两个性质，它就具备了成为向量空间的基础：\n可相加性：两个向量可以相加，得到的结果仍然是同类型的向量。 可数乘性：一个向量可以与一个标量（即一个普通的数，如-1, 0, 3.14）相乘，得到的结果也仍然是同类型的向量。 这种“对运算保持封闭”的特性是向量的核心。\n直观解释与示例\n让我们来看几个“出人意料”的向量例子，这有助于我们打破思维定势：\n1. 几何向量(Geometric vectors)：这是我们最熟悉的例子。两个几何向量 \\(\\boldsymbol{\\vec{x}}\\) 和 \\(\\boldsymbol{\\vec{y}}\\) 相加，遵循平行四边形法则，结果 \\(\\boldsymbol{\\vec{z}} = \\boldsymbol{\\vec{x}} + \\boldsymbol{\\vec{y}}\\) 仍然是一个几何向量。将一个向量 \\(\\boldsymbol{\\vec{x}}\\) 乘以一个标量 \\(\\lambda \\in \\mathbb{R}\\)，会得到一个被拉伸或压缩了 \\(\\lambda\\) 倍的向量 \\(\\lambda \\boldsymbol{\\vec{x}}\\)，它方向相同或相反，但仍是几何向量。\n2. 多项式 (Polynomials)：考虑所有最高次数不超过2的多项式（形如 \\(P_1(t) = at^2+bt+c\\)，系数可为0） 和另一个二次多_项式_ \\(P_2(t) = dt^2+et+f\\) 相加，结果仍然是一个二次多项式。将 \\(P_1(t)\\) 乘以一个标量 \\(\\lambda\\)，结果 \\(\\lambda P_1(t)\\) 也还是一个多项式。因此，多项式也是向量！\n2. 多项式 (Polynomials)：考虑所有最高次数不超过 2 的多项式（形式为 \\(P(t) = at^2+bt+c\\)，其中系数 \\(a,b,c\\) 可为任意实数，包括 0）。 将两个这样的多项式 \\(P_1(t)\\) 和 \\(P_2(t)\\) 相加，合并同类项后，结果仍然是一个最高次数不超过 2 的多项式。将 \\(P_1(t)\\) 乘以标量 \\(\\lambda\\)，结果也还在这个集合中。\n注意：如果严格限制为“二次多项式”（即要求 \\(a \\neq 0\\)），则它们构不成向量空间。因为两个二次多项式相加可能抵消掉二次项（如 \\(t^2\\) 和 \\(-t^2\\) 相加得 0），导致结果跳出集合，破坏了“封闭性”。因此，向量空间必须包含低次多项式和零多项式。\n3. 音频信号 (Audio Signals)：音频信号可以表示为一串数字。两个音频信号相加（对应声音的混合），结果还是一个音频信号。将一个音频信号的振幅乘以一个标量（对应调节音量），结果也还是一个音频信号。所以，音频信号也是向量。\n4. \\(\\mathbb{R}^n\\) 中的数组/元组 (Tuples of real numbers)：这是本书和机器学习中最核心的向量形式。一个包含 \\(n\\) 个实数的有序元组，通常写成列向量的形式。\n数值示例\n例如，一个在三维空间中的向量 \\(\\boldsymbol{a}\\) 可以表示为：\n$$ \\boldsymbol{a} = \\begin{bmatrix} 1 \\\\ 2 \\\\ 3 \\end{bmatrix} \\in \\mathbb{R}^3 $$两个 \\(\\mathbb{R}^3\\) 中的向量 \\(\\boldsymbol{a} = \\begin{bmatrix} 1 \\\\ 2 \\\\ 3 \\end{bmatrix}\\) 和 \\(\\boldsymbol{b} = \\begin{bmatrix} 4 \\\\ 5 \\\\ 6 \\end{bmatrix}\\) 相加是按元素相加：\n$$ \\boldsymbol{a} + \\boldsymbol{b} = \\begin{bmatrix} 1+4 \\\\ 2+5 \\\\ 3+6 \\end{bmatrix} = \\begin{bmatrix} 5 \\\\ 7 \\\\ 9 \\end{bmatrix} $$结果仍然是一个 \\(\\mathbb{R}^3\\) 中的向量。\n向量 \\(\\boldsymbol{a}\\) 与标量 \\(\\lambda=2\\) 相乘也是按元素进行：\n$$ \\lambda \\boldsymbol{a} = 2 \\cdot \\begin{bmatrix} 1 \\\\ 2 \\\\ 3 \\end{bmatrix} = \\begin{bmatrix} 2 \\cdot 1 \\\\ 2 \\cdot 2 \\\\ 2 \\cdot 3 \\end{bmatrix} = \\begin{bmatrix} 2 \\\\ 4 \\\\ 6 \\end{bmatrix} $$结果也仍在 \\(\\mathbb{R}^3\\) 中。因此，\\(\\mathbb{R}^n\\) 中的元组是向量。\n理解要点\n向量的核心身份：向量不仅仅是带箭头的线段。它的真正身份由其所遵守的运算法则（加法封闭和数乘封闭）来定义。任何满足这些法则的数学对象，都可以被纳入“向量”的大家庭。\n2. 线性代数与机器学习 # 应用场景和重要意义：在机器学习中，我们处理的几乎所有数据最终都会被转换成向量。\n一张 100x100 像素的灰度图片可以被“拉直”成一个 \\(\\mathbb{R}^{10000}\\) 空间中的向量。 一个用户的信息（年龄、收入、购物次数）可以表示为一个 \\(\\mathbb{R}^3\\) 空间中的向量。 自然语言处理中的一个词，也可以通过词嵌入（Word Embedding）技术表示为一个高维向量。 线性代数为我们提供了分析、处理和变换这些高维数据向量的语言和工具箱。例如，降维技术（如PCA）的本质就是找到一个更低维的向量空间来近似表示原始数据，而这完全是线性代数的范畴。\n3. 核心思想：封闭性与向量空间 # 数学中一个重要的思想是“封闭性 (Closure)”。当我们拥有一组对象和一些运算时，我们自然会问：从一个小的初始集合出发，通过这些运算，我们最终能得到一个多大的集合？\n对于向量而言，这个问题就变成了：\n从一小撮向量开始，通过不断地将它们相加和进行数乘，我们能得到的所有向量的集合是什么样的？\n这个集合，我们称之为向量空间 (Vector Space)。这个概念是整个线性代数的基石，它为我们提供了一个结构化的舞台来研究向量。\n思考题 # 考虑一个定义在区间 \\([a, b]\\) 上的所有连续函数组成的集合 \\(C[a, b]\\)。我们熟悉的函数加法（如 \\(h(x) = f(x) + g(x)\\)）和函数与常数的乘法（如 \\(p(x) = \\lambda \\cdot f(x)\\)）是这个集合上的两种运算。请问，这个函数集合 \\(C[a, b]\\) 能否被看作是一个向量空间？为什么？\n是的，满足向量空间的所有公理（封闭性、加法公理、数乘公理）。 1. 集合的封闭性 # \\(C[a,b]\\) 在定义的运算下是封闭的，且所有运算结果仍是连续函数：\n加法封闭性：若 \\(f, g \\in C[a,b]\\)，则 \\(f + g\\) 也连续，故 \\((f + g) \\in C[a,b]\\) 数乘封闭性：若 \\(f \\in C[a,b]\\) 且 \\(\\lambda \\in \\mathbb{R}\\)，则 \\(\\lambda f\\) 也连续，故 \\(\\lambda f \\in C[a,b]\\) 2. 向量空间八条公理的验证 # 对于任意 \\(f, g, h \\in C[a,b]\\) 和实数 \\(\\alpha, \\beta\\)：\n加法四条公理：\n结合律：\\((f + g) + h = f + (g + h)\\) 交换律：\\(f + g = g + f\\) 零元存在：零函数 \\(\\mathbf{0}(x) = 0\\) 满足 \\(f + \\mathbf{0} = f\\) 逆元存在：对每个 \\(f\\)，存在 \\(-f\\) 使得 \\(f + (-f) = \\mathbf{0}\\) 数乘四条公理：\n数乘结合律：\\(\\alpha(\\beta f) = (\\alpha\\beta)f\\) 单位元：\\(1 \\cdot f = f\\) 分配律（对函数）：\\(\\alpha(f + g) = \\alpha f + \\alpha g\\) 分配律（对数）：\\((\\alpha + \\beta)f = \\alpha f + \\beta f\\) 这些性质通过逐点验证得到。例如交换律：\\((f + g)(x) = f(x) + g(x) = g(x) + f(x) = (g + f)(x)\\) 对所有 \\(x \\in [a,b]\\) 成立，故 \\(f + g = g + f\\)。\n3. \\(C[a,b]\\) 的附加结构特征 # 重要说明：以下结构并非向量空间定义本身所必需，而是可以在 \\(C[a,b]\\) 上额外构造的结构：\n无限维：\\(C[a,b]\\) 不存在有限个函数能够张成整个空间，这说明它与 \\(\\mathbb{R}^n\\) 不同，不是有限维的。维度概念在无限维空间需用 Schauder 基或 Hamel 基讨论。 可成为内积空间：若额外定义内积 \\(\\langle f, g \\rangle = \\int_a^b f(x)g(x)dx\\)，\\(C[a,b]\\) 便成为内积空间。 可成为赋范空间：若赋予上确界范数 \\(|f|\\infty = \\sup{x \\in [a,b]} |f(x)|\\)，\\(C[a,b]\\) 成为赋范空间。 结论 # \\(C[a,b]\\) 满足向量空间的所有必要条件，因此是一个向量空间。它是函数分析中的重要例子，展示了向量空间概念从有限维到无限维函数空间的推广。\n本节知识点总结 # 代数：研究“对象”和“法则”的数学分支。 线性代数：专门研究“向量”及其运算法则的学科。 向量的本质：一个抽象概念，指代任何满足加法封闭性和数乘封闭性的对象。 向量的实例：几何向量、多项式、音频信号、以及最重要的 \\(\\mathbb{R}^n\\) 中的数组。 与AI的联系：数据在计算机中通常以向量形式存在，线性代数是处理这些数据向量的理论基础。 核心思想前瞻：对向量运算的“封闭性”的探讨，将自然地引出下一阶段的核心概念——向量空间。 ","date":"2026-02-10","externalUrl":null,"permalink":"/blogs/math/linear-algebra/01/","section":"Blog","summary":"","title":"线性代数：引言","type":"blogs"},{"content":"","date":"2026-01-12","externalUrl":null,"permalink":"/tags/agent/","section":"Tags","summary":"","title":"Agent","type":"tags"},{"content":"","date":"2026-01-12","externalUrl":null,"permalink":"/tags/anthropic/","section":"Tags","summary":"","title":"Anthropic","type":"tags"},{"content":"","date":"2026-01-12","externalUrl":null,"permalink":"/tags/%E8%AF%91%E6%96%87/","section":"Tags","summary":"","title":"译文","type":"tags"},{"content":" 原文链接: engineering/demystifying-evals-for-ai-agents TL;DR # 核心观点：评估是 AI 智能体从原型走向生产的关键。由于智能体具有多轮交互、状态保持和工具调用等特性，传统的单轮 LLM 评估已不再适用。高效的评估体系应侧重于自动化、多维度评分以及对结果而非路径的考核。\n1. 核心评估架构 (The Evaluation Architecture)\n智能体评估不再是简单的“提示词-响应”对，而是一个包含完整交互周期的系统：\n输入 (Task/Input)：定义明确的测试任务和环境初始状态。 执行 (Execution)：智能体与环境（工具、API、浏览器）进行多轮交互。 记录 (Transcript)：捕获完整的工具调用链、推理过程和中间状态。 结果 (Outcome)：环境的最终状态（如：数据库是否更新、文件是否生成）。 2. 分层评分策略 (Grading Strategy)\n不要依赖单一的评分方式，应采用混合评分器 (Hybrid Graders) 策略来平衡成本、速度和准确性：\n评分器类型 适用场景 示例 策略建议 基于代码 (Deterministic) 客观、刚性的任务 单元测试、Lint 检查、字符串匹配、文件存在性检查 首选。用于验证核心功能是否跑通（如代码能否运行）。 基于模型 (Model-based) 主观、开放性任务 LLM-as-a-Judge（语气、同理心、内容相关性） 补充。需使用清晰的量规 (Rubric)，并与人类专家进行校准。 人工评分 (Human) 黄金标准、校准 专家审查、众包 基准。不用于大规模自动化，仅用于验证和校准 LLM 评分器的准确性。 3. 不同类型智能体的评估方案\n编程智能体 (Coding Agents)： 方案：核心依赖单元测试（通过/失败）。 进阶：结合静态分析（代码风格）和 LLM 评分（代码可读性）。 对话智能体 (Conversational Agents)： 方案：引入第二模型模拟用户进行多轮对抗/协作测试。 指标：多维评分（任务是否完成 + 交互质量 + 轮次限制）。 研究智能体 (Research Agents)： 方案：事实核查 (Groundedness) + 来源质量验证。 难点：无唯一标准答案，依赖 LLM 对覆盖率和连贯性进行评分。 计算机操作智能体 (Computer Use Agents)： 方案：基于沙盒环境，验证最终状态（如订单是否生成）而非中间点击路径。 4. 关键度量指标 (Key Metrics)\npass@k (探索能力)：在 $k$ 次尝试中至少成功 1 次的概率。适用于“只要有一个方案可行即可”的场景（如编程）。 pass^k (可靠性)：连续 $k$ 次尝试全部成功的概率。适用于要求高度一致性的场景（如客户服务）。 能力 vs. 回归：新功能开发看重能力评估（有难度的任务），维护阶段看重回归评估（确保存量任务 100% 通过）。 5. 落地执行路线图 (Implementation Roadmap)\n从错误开始 (Start with Failures)：不需要数百个任务，从 20-50 个基于真实故障或手动测试用例的任务开始。 考核结果而非路径 (Grade Outcomes, Not Paths)：智能体可能会用意想不到的方式解决问题。只要结果正确（如退款成功），不要纠结于它调用的工具顺序，避免评估过拟合。 阅读对话记录 (Read Transcripts)：这是调试评估体系的唯一真理。确保失败是公平的，而不是因为评分器本身的问题。 隔离环境 (Isolate Environments)：杜绝试验间的状态污染（如缓存、残留文件），确保每次运行都是独立的。 警惕饱和 (Watch for Saturation)：当通过率接近 100% 时，评估即失效，需引入更难的任务以推动能力提升。 总结建议：构建评估体系是一个从“手动测试”到“自动化代码检查”，再到“LLM 辅助评分”的演进过程。最有效的团队会将自动化评估（用于快速迭代）、生产环境监控（用于获取真实数据）和定期人工审查（用于校准标准）三者结合使用。\n引言 (Introduction) # 优秀的评估能够帮助团队更自信地发布 AI 智能体 (AI agents)。如果没有评估，团队很容易陷入被动响应的循环——只能在生产环境中发现问题，而修复一个故障又会导致其他故障。评估能够让问题和行为变更在影响用户之前变得可见，其价值会在智能体的整个生命周期中产生复利效应。\n正如我们在 构建高效的智能体 (Building effective agents) 一文中所述，智能体的运作涉及多个轮次：调用工具、修改状态以及根据中间结果进行调整。正是这些让 AI 智能体变得有用的能力——自主性、智能性和灵活性——同时也增加了评估它们的难度。\n通过我们的内部工作以及与处于智能体开发前沿的客户的合作，我们学会了如何为智能体设计更严谨、更有用的评估。以下是在真实世界的部署中，跨越多种智能体架构和用例的有效经验。\n评估的结构 (The structure of an evaluation) # 评估 (evaluation)（简称 \u0026ldquo;eval\u0026rdquo;）是对 AI 系统的一种测试：给 AI 一个输入，然后对其输出应用评分逻辑以衡量是否成功。在这篇文章中，我们要关注的是自动化评估 (automated evals)，即可以在开发过程中运行且无需真实用户参与的评估。\n单轮评估 (Single-turn evaluations) 非常直观：包括一个提示词 (prompt)、一个响应和评分逻辑。对于早期的 LLM，单轮、非智能体式的评估是主要的评估方法。随着 AI 能力的提升，多轮评估 (multi-turn evaluations) 正变得越来越普遍。\n在一个简单的评估中，智能体处理一个提示词，评分器检查输出是否符合预期。而在一个更复杂的多轮评估中，一个编程智能体接收工具、一个任务（在本例中是构建一个 MCP 服务器）和一个环境，执行“智能体循环”（工具调用和推理），并用实现代码更新环境。然后，评分环节使用单元测试来验证 MCP 服务器是否正常工作。 智能体评估 (Agent evaluations) 则更加复杂。智能体在多个轮次中使用工具，修改环境中的状态，并在此过程中进行调整——这意味着错误可能会传播和累积。前沿模型还能找到超越静态评估限制的创造性解决方案。例如，Opus 4.5 解决了一个关于预订航班的 𝜏2-bench 问题，它是通过发现策略中的漏洞来完成的。按照字面规则它“未通过”评估，但实际上为用户提供了一个更好的解决方案。\n在构建智能体评估时，我们使用以下定义：\n任务 (Task)（也称为问题 (problem) 或 测试用例 (test case)）是一个具有定义好的输入和成功标准的单个测试。 对一个任务的每次尝试称为一次 试验 (Trial)。因为模型输出在不同运行间存在差异，我们会运行多次试验以产生更一致的结果。 评分器 (Grader) 是对智能体表现的某些方面进行打分的逻辑。一个任务可以有多个评分器，每个评分器包含多个断言（有时称为检查 (checks)）。 对话记录 (Transcript)（也称为 轨迹 (Trace) 或 路径 (Trajectory)）是一次试验的完整记录，包括输出、工具调用、推理、中间结果以及任何其他交互。对于 Anthropic API，这是评估运行结束时的完整消息数组——包含评估期间对 API 的所有调用和所有返回的响应。 结果 (Outcome) 是试验结束时环境的最终状态。一个航班预订智能体可能会在对话记录的结尾说“您的航班已预订”，但实际结果是环境的 SQL 数据库中是否存在该预订记录。 评估框架 (Evaluation harness) 是端到端运行评估的基础设施。它提供指令和工具，并发运行任务，记录所有步骤，对输出进行评分，并汇总结果。 智能体框架 (Agent harness)（或 脚手架 (scaffold)）是使模型能够充当智能体的系统：它处理输入，编排工具调用，并返回结果。当我们评估“一个智能体”时，我们评估的是框架和模型协同工作的效果。例如，Claude Code 是一个灵活的智能体框架，我们利用其核心原语通过 Agent SDK 构建了我们的 长时间运行智能体框架 (long-running agent harness)。 评估套件 (Evaluation suite) 是旨在衡量特定能力或行为的任务集合。套件中的任务通常共享一个广泛的目标。例如，一个客户支持评估套件可能会测试退款、取消和升级处理。 智能体评估的各个组件图示 为什么要构建评估？ (Why build evaluations?) # 当团队刚开始构建智能体时，依靠手动测试、吃自家的狗粮 (dogfooding) 和直觉的组合，可以取得令人惊讶的进展。更严格的评估甚至可能看起来像是拖慢发布速度的负担。但在早期的原型设计阶段之后，一旦智能体投入生产并开始扩展，没有评估的构建方式就会开始崩溃。\n崩溃点通常出现在用户报告变更后智能体体验变差时，而团队处于“盲飞”状态，除了猜测和检查外无法进行验证。缺乏评估时，调试是被动的：等待投诉，手动复现，修复错误，并祈祷没有引起其他回退 (regression)。团队无法区分真正的回退和噪音，无法在发布前自动针对数百种场景测试变更，也无法衡量改进。\n我们已经多次看到这种演变过程。例如，Claude Code 最初是基于 Anthropic 员工和外部用户的反馈进行快速迭代。后来，我们添加了评估——首先是针对简洁性和文件编辑等狭窄领域，然后是针对过度设计 (over-engineering) 等更复杂的行为。这些评估有助于发现问题，指导改进，并聚焦研究与产品的协作。结合生产环境监控、A/B 测试、用户研究等手段，评估为 Claude Code 在扩展过程中的持续改进提供了信号。\n在智能体生命周期的任何阶段编写评估都是有用的。在早期，评估迫使产品团队明确智能体成功的定义；而在后期，它们有助于维持一致的质量标准。\nDescript 的智能体帮助用户编辑视频，因此他们围绕成功编辑工作流的三个维度构建了评估：不要破坏东西、做我要求的、并把它做好。他们从手动评分演变为由产品团队定义标准并定期进行人工校准的 LLM 评分器，现在定期运行两个独立的套件进行质量基准测试和回归测试。Bolt AI 团队在已经拥有广泛使用的智能体后才开始构建评估。在 3 个月内，他们构建了一个评估系统，运行他们的智能体并通过静态分析对输出评分，使用浏览器智能体测试应用程序，并采用 LLM 裁判来评估指令遵循等行为。\n有些团队在开发之初就创建评估；另一些则在规模化后，当缺乏评估成为改进智能体的瓶颈时才添加。评估在智能体开发初期特别有用，可以显式地编码预期行为。两名工程师阅读同一份初始规范，可能会对 AI 应如何处理边缘情况产生不同的解读。一个评估套件可以解决这种歧义。无论何时创建，评估都有助于加速开发。\n评估还决定了你采用新模型的速度。当更强大的模型问世时，没有评估的团队面临数周的测试，而拥有评估的竞争对手可以迅速确定模型的优势，调整提示词，并在几天内完成升级。\n一旦存在评估，你就免费获得了基线和回归测试：延迟、Token 使用量、单任务成本和错误率都可以在静态任务库上进行跟踪。评估也可以成为产品团队和研究团队之间最高带宽的沟通渠道，定义研究人员可以优化的指标。显然，评估的好处不仅仅在于跟踪回退和改进。鉴于成本是显而易见的，而收益是在后期累积的，其复利价值很容易被忽视。\n如何评估 AI 智能体 (How to evaluate AI agents) # 我们看到如今有几种常见的智能体类型已被大规模部署，包括编程智能体、研究智能体、计算机操作智能体和对话智能体。每种类型可能部署在各行各业，但它们可以使用类似的技术进行评估。你不需要从头发明评估方法。以下部分描述了几种智能体类型的成熟技术。以这些方法为基础，然后将其扩展到你的领域。\n智能体评分器的类型 (Types of graders for agents) # 智能体评估通常结合三种类型的评分器：基于代码的、基于模型的和人工的。每个评分器评估对话记录或结果的一部分。有效评估设计的一个重要组成部分是为工作选择合适的评分器。\n基于代码的评分器 (Code-based graders)\n方法 (Methods) 优势 (Strengths) 劣势 (Weaknesses) • 字符串匹配检查（精确、正则、模糊等）\n• 二元测试（从失败到通过、从通过到通过）\n• 静态分析（Lint、类型、安全）\n• 结果验证\n• 工具调用验证（使用的工具、参数）\n• 对话记录分析（轮次、Token 使用量） • 快速\n• 便宜\n• 客观\n• 可复现\n• 易于调试\n• 验证特定条件 • 对不完全匹配预期模式的有效变体脆弱\n• 缺乏细微差别\n• 对于评估某些更主观的任务局限性较大 基于模型的评分器 (Model-based graders)\n方法 (Methods) 优势 (Strengths) 劣势 (Weaknesses) • 基于量规 (Rubric) 的评分\n• 自然语言断言\n• 成对比较\n• 基于参考的评估\n• 多裁判共识 • 灵活\n• 可扩展\n• 捕捉细微差别\n• 处理开放式任务\n• 处理自由格式输出 • 非确定性\n• 比代码更昂贵\n• 需要与人工评分器校准以确保准确性 人工评分器 (Human graders)\n方法 (Methods) 优势 (Strengths) 劣势 (Weaknesses) • 领域专家 (SME) 审查\n• 众包判断\n• 抽样检查\n• A/B 测试\n• 标注者间一致性 • 黄金标准质量\n• 匹配专家用户判断\n• 用于校准基于模型的评分器 • 昂贵\n• 缓慢\n• 规模化时通常需要接触人类专家 对于每个任务，评分可以是加权的（组合评分必须达到阈值）、二元的（所有评分器必须通过），或者是混合的。\n能力评估 vs. 回归评估 (Capability vs. regression evals) # 能力 (Capability) 或“质量”评估 询问“这个智能体能做好什么？”它们的通过率起点应该较低，针对智能体难以应对的任务，给团队一个攀登的目标。\n回归 (Regression) 评估 询问“智能体是否仍然能处理它以前能处理的所有任务？”，其通过率应接近 100%。它们防止倒退，因为分数的下降信号表明某处已损坏并需要改进。当团队在能力评估上攀登高峰时，运行回归评估以确保变更不会在其他地方引起问题同样重要。\n在智能体发布并优化后，具有高通过率的能力评估可以“毕业”成为回归套件，持续运行以捕捉任何漂移。曾经衡量“我们可以做这个吗？”的任务，随后变为衡量“我们还能可靠地做这个吗？”。\n评估编程智能体 (Evaluating coding agents) # 编程智能体 (Coding agents) 编写、测试和调试代码，浏览代码库，并像人类开发人员一样运行命令。现代编程智能体的有效评估通常依赖于定义明确的任务、稳定的测试环境以及对生成代码的全面测试。\n确定性评分器是编程智能体的天然选择，因为软件评估通常很直接：代码能否运行且测试能否通过？两个广泛使用的编程智能体基准测试，SWE-bench Verified 和 Terminal-Bench，都遵循这种方法。SWE-bench Verified 给智能体提供来自流行 Python 仓库的 GitHub issue，并通过运行测试套件来对解决方案评分；只有当解决方案修复了失败的测试且不破坏现有测试时，才算通过。仅一年时间，LLM 在此评估上的通过率已从 40% 提升至 \u0026gt;80%。Terminal-Bench 采取了不同的路径：它测试端到端的技术任务，例如从源码构建 Linux 内核或训练 ML 模型。\n一旦你有了一组通过或失败的测试来验证编程任务的关键结果，对对话记录进行评分通常也很有用。例如，基于启发式的代码质量规则可以基于通过测试之外的标准评估生成的代码，而带有清晰量规的模型评分器可以评估诸如智能体如何调用工具或与用户交互等行为。\n示例：编程智能体的理论评估\n考虑一个编程任务，智能体必须修复一个身份验证绕过漏洞。如下面的说明性 YAML 文件所示，人们可以使用评分器和指标来评估此智能体。\ntask: id: \u0026#34;fix-auth-bypass_1\u0026#34; desc: \u0026#34;Fix authentication bypass when password field is empty and ...\u0026#34; graders: - type: deterministic_tests required: [test_empty_pw_rejected.py, test_null_pw_rejected.py] - type: llm_rubric rubric: prompts/code_quality.md - type: static_analysis commands: [ruff, mypy, bandit] - type: state_check expect: security_logs: {event_type: \u0026#34;auth_blocked\u0026#34;} - type: tool_calls required: - {tool: read_file, params: {path: \u0026#34;src/auth/*\u0026#34;}} - {tool: edit_file} - {tool: run_tests} tracked_metrics: - type: transcript metrics: - n_turns - n_toolcalls - n_total_tokens - type: latency metrics: - time_to_first_token - output_tokens_per_sec - time_to_last_token 注意，此示例展示了所有可用评分器的范围以作说明。在实践中，编程评估通常依赖单元测试进行正确性验证，并依赖 LLM 量规评估整体代码质量，仅在需要时添加其他评分器和指标。\n评估对话智能体 (Evaluating conversational agents) # 对话智能体 (Conversational agents) 在支持、销售或辅导等领域与用户互动。与传统聊天机器人不同，它们保持状态，使用工具，并在对话中采取行动。虽然编程和研究智能体也可能涉及与用户的多轮交互，但对话智能体提出了一个独特的挑战：交互本身的质量是你评估的一部分。对话智能体的有效评估通常依赖于可验证的最终状态结果以及捕捉任务完成度和交互质量的量规。与大多数其他评估不同，它们通常需要第二个 LLM 来模拟用户。我们在对齐审计智能体 (alignment auditing agents) 中使用这种方法，通过扩展的对抗性对话对模型进行压力测试。\n对话智能体的成功可以是多维度的：工单是否解决（状态检查），是否在 \u0026lt;10 轮内完成（对话记录约束），以及语气是否得当（LLM 量规）？包含多维度特性的两个基准测试是 𝜏-Bench 及其后续版本 τ2-Bench。这些基准测试模拟了零售支持和机票预订等领域的跨多轮交互，其中一个模型扮演用户角色，而智能体则处理现实场景。\n示例：对话智能体的理论评估\n考虑一个支持任务，智能体必须为一位沮丧的客户处理退款。\ngraders: - type: llm_rubric rubric: prompts/support_quality.md assertions: - \u0026#34;Agent showed empathy for customer\u0026#39;s frustration\u0026#34; - \u0026#34;Resolution was clearly explained\u0026#34; - \u0026#34;Agent\u0026#39;s response grounded in fetch_policy tool results\u0026#34; - type: state_check expect: tickets: {status: resolved} refunds: {status: processed} - type: tool_calls required: - {tool: verify_identity} - {tool: process_refund, params: {amount: \u0026#34;\u0026lt;=100\u0026#34;}} - {tool: send_confirmation} - type: transcript max_turns: 10 tracked_metrics: - type: transcript metrics: - n_turns - n_toolcalls - n_total_tokens - type: latency metrics: - time_to_first_token - output_tokens_per_sec - time_to_last_token 正如在我们的编程智能体示例中一样，此任务展示了多种评分器类型以作说明。在实践中，对话智能体评估通常使用基于模型的评分器来评估沟通质量和目标完成情况，因为许多任务——比如回答问题——可能有多个“正确”的解决方案。\n评估研究智能体 (Evaluating research agents) # 研究智能体 (Research agents) 收集、综合和分析信息，然后生成如答案或报告等输出。与单元测试提供二元通过/失败信号的编程智能体不同，研究质量只能相对于任务来判断。什么算作“全面”、“来源可靠”甚至“正确”，取决于上下文：市场扫描、收购尽职调查和科学报告各自需要不同的标准。\n研究评估面临独特的挑战：专家可能对综合报告是否全面持不同意见，随着参考内容不断变化，基本事实 (ground truth) 也会发生转变，而且更长、更开放的输出为错误创造了更多空间。例如，像 BrowseComp 这样的基准测试，测试 AI 智能体能否在开放网络中大海捞针——这些问题的设计初衷是易于验证但难以解决。\n构建研究智能体评估的一种策略是结合多种评分器类型。基于事实检查 (Groundedness checks) 验证主张是否由检索到的来源支持，覆盖率检查 (Coverage checks) 定义好答案必须包含的关键事实，来源质量检查 (Source quality checks) 确认参考的来源是权威的，而不仅仅是首先检索到的。对于有客观正确答案的任务（“X 公司第三季度的收入是多少？”），精确匹配是有效的。LLM 可以标记不受支持的主张和覆盖范围的缺失，也可以验证开放式综合报告的连贯性和完整性。\n鉴于研究质量的主观性，基于 LLM 的量规应经常根据专家的人工判断进行校准，以便有效地对这些智能体进行评分。\n计算机操作智能体 (Computer use agents) # 计算机操作智能体 (Computer use agents) 通过与人类相同的界面——截图、鼠标点击、键盘输入和滚动——与软件交互，而不是通过 API 或代码执行。它们可以使用任何带有图形用户界面 (GUI) 的应用程序，从设计工具到旧的企业软件。评估需要在真实或沙盒环境中运行智能体，让它可以操作软件应用程序，并检查它是否达到了预期的结果。例如，WebArena 测试基于浏览器的任务，使用 URL 和页面状态检查来验证智能体是否导航正确，并结合后端状态验证用于修改数据的任务（确认订单实际上已下达，而不仅仅是确认页面出现）。OSWorld 将此扩展到完整的操作系统控制，使用评估脚本在任务完成后检查各种工件：文件系统状态、应用程序配置、数据库内容和 UI 元素属性。\n浏览器操作智能体需要在 Token 效率和延迟之间取得平衡。基于 DOM 的交互执行速度快，但消耗大量 Token，而基于截图的交互较慢，但 Token 效率更高。例如，当要求 Claude 总结维基百科时，从 DOM 中提取文本效率更高。当在亚马逊上寻找新的笔记本电脑包时，截图效率更高（因为提取整个 DOM 会占用大量 Token）。在我们的 Claude for Chrome 产品中，我们开发了评估来检查智能体是否为每个上下文选择了正确的工具。这使我们能够更快、更准确地完成基于浏览器的任务。\n如何看待智能体评估中的非确定性 (How to think about non-determinism in evaluations for agents) # 无论智能体类型如何，智能体行为在不同运行之间都会有所不同，这使得评估结果比表面上看起来更难解释。每个任务都有自己的成功率——可能在一个任务上是 90%，在另一个任务上是 50%——并且在一个评估运行中通过的任务可能会在下一个运行中失败。有时，我们想要衡量的是智能体在一个任务上成功的频率（试验的比例）。\n两个指标有助于捕捉这种细微差别：\npass@k 衡量智能体在 k 次尝试中获得至少一个正确解决方案的可能性。随着 k 的增加，pass@k 分数上升——更多的“射门”意味着至少 1 次成功的几率更高。50% 的 pass@1 分数意味着模型在第一次尝试时成功完成了评估中一半的任务。在编程中，我们通常最感兴趣的是智能体在第一次尝试时找到解决方案——pass@1。在其他情况下，只要有一个方案有效，提出多个解决方案也是可以的。\npass^k 衡量所有 k 次试验都成功的概率。随着 k 的增加，pass^k 下降，因为要求在更多试验中保持一致性是一个更难跨越的门槛。如果你的智能体有 75% 的单次试验成功率，并且你运行 3 次试验，全部通过的概率是 (0.75)³ ≈ 42%。这个指标对于面向客户的智能体尤为重要，因为用户期望每次都有可靠的行为。\npass@k 和 pass^k 随着试验次数增加而分化。在 k=1 时，它们是相同的（均等于单次试验成功率）。到 k=10 时，它们讲述了相反的故事：pass@k 接近 100%，而 pass^k 降至 0%。 这两个指标都很有用，使用哪一个取决于产品需求：对于只要一次成功就很重要的工具使用 pass@k，对于一致性至关重要的智能体使用 pass^k。\n从零到一：通往优秀智能体评估的路线图 (Going from zero to one: a roadmap to great evals for agents) # 本节列出了我们将评估从无到有并建立可信评估的实用、经过实战检验的建议。把它看作是评估驱动的智能体开发路线图：尽早定义成功，清晰地衡量它，并持续迭代。\n收集初始评估数据集的任务 (Collect tasks for the initial eval dataset) # 步骤 0. 尽早开始\n我们看到团队推迟构建评估，因为他们认为需要数百个任务。实际上，从真实失败中提取 20-50 个简单任务就是一个很好的开始。毕竟，在智能体开发早期，系统的每一次变更通常都有明显、显著的影响，这种巨大的效应意味着小样本量就足够了。更成熟的智能体可能需要更大、更难的评估来检测较小的效应，但在开始时最好采取 80/20 原则。等待的时间越长，评估就越难构建。早期，产品需求自然转化为测试用例。如果等太久，你就得从现有系统中反向工程成功标准。\n步骤 1. 从你已经手动测试的内容开始\n从你在开发过程中运行的手动检查开始——那些你在每次发布前验证的行为以及最终用户尝试的常见任务。如果你已经在生产环境中，请查看你的错误跟踪器和支持队列。将用户报告的故障转换为测试用例，确保你的套件反映实际使用情况；按用户影响优先级排序有助于你将精力投入到最重要的地方。\n步骤 2：编写带有参考答案且无歧义的任务\n确保任务质量比看起来要难。一个好的任务是两位领域专家能够独立得出相同通过/失败结论的任务。他们自己能通过这个任务吗？如果不能，任务就需要改进。任务规范中的歧义会变成指标中的噪音。这也适用于基于模型的评分器的标准：模糊的量规会产生不一致的判断。\n每个任务都应该能被正确遵循指令的智能体通过。这可能很微妙。例如，审计 Terminal-Bench 时发现，如果一个任务要求智能体编写脚本但没有指定文件路径，而测试假设脚本在特定路径下，智能体可能会非因自身过错而失败。评分器检查的所有内容都应在任务描述中清晰明了；智能体不应因模棱两可的规范而失败。对于前沿模型，如果在多次试验中通过率为 0%（即 0% pass@100），这通常是任务损坏的信号，而不是智能体无能，这提示需要仔细检查你的任务规范和评分器。对于每个任务，创建一个参考答案 (reference solution) 是很有用的：一个已知能通过所有评分器的工作输出。这证明了任务是可解的，并验证了评分器配置正确。\n步骤 3：构建平衡的问题集\n既要测试行为应该发生的情况，也要测试不应该发生的情况。片面的评估会导致片面的优化。例如，如果你只测试智能体在应该搜索时是否搜索，你可能会得到一个几乎对所有事情都进行搜索的智能体。尽量避免类别不平衡 (class-imbalanced) 的评估。我们在为 Claude.ai 构建网络搜索评估时亲身体会到了这一点。挑战在于防止模型在不应该搜索时进行搜索，同时保留其在适当时候进行广泛研究的能力。团队构建了覆盖两个方向的评估：模型应该搜索的查询（如查找天气）和应该根据现有知识回答的查询（如“谁创立了苹果？”）。在触发不足（应该搜索时不搜索）或过度触发（不应该搜索时搜索）之间取得适当平衡是困难的，需要对提示词和评估进行多轮改进。随着更多示例问题的出现，我们将继续添加到评估中以提高覆盖率。\n设计评估框架和评分器 (Design the eval harness and graders) # 步骤 4：构建具有稳定环境的健壮评估框架\n评估中的智能体必须与生产中使用的智能体功能大致相同，且环境本身不引入额外的噪音，这一点至关重要。每次试验都应通过从干净的环境开始来“隔离”。运行之间不必要的共享状态（残留文件、缓存数据、资源耗尽）可能会因基础设施的不稳定而非智能体性能导致相关性故障。共享状态也可能人为地提高性能。例如，在一些内部评估中，我们观察到 Claude 通过检查先前试验的 git 历史记录，在某些任务上获得了不公平的优势。如果多个不同的试验因环境中相同的限制（如受限的 CPU 内存）而失败，这些试验就不是独立的，因为它们受相同因素影响，评估结果在衡量智能体性能时就变得不可靠。\n步骤 5：深思熟虑地设计评分器\n如上所述，优秀的评估设计包括为智能体和任务选择最佳评分器。我们建议尽可能选择确定性评分器，必要时或为了增加灵活性使用 LLM 评分器，并审慎使用人工评分器进行额外验证。\n有一种常见的本能是检查智能体是否遵循了非常具体的步骤，如按正确顺序调用一系列工具。我们发现这种方法过于僵化，导致测试过于脆弱，因为智能体经常找到评估设计者未曾预料到的有效方法。为了不必要地惩罚创造力，通常最好是对智能体生产的内容进行评分，而不是其采取的路径。\n对于包含多个组件的任务，应建立部分得分 (partial credit) 机制。一个能正确识别问题并验证客户身份但未能处理退款的支持智能体，比起那些立即失败的智能体有意义上的更好。在结果中体现这种成功的连续性很重要。\n模型评分通常需要仔细迭代来验证准确性。作为裁判的 LLM (LLM-as-judge) 评分器应与人类专家紧密校准，以获得人类评分与模型评分之间偏差很小的信心。为了避免幻觉，给 LLM 一个退路，比如提供指令让其在信息不足时返回“未知”。创建清晰、结构化的量规来对任务的每个维度进行评分，然后用隔离的作为裁判的 LLM 对每个维度进行评分，而不是用一个 LLM 对所有维度评分，也会有所帮助。一旦系统稳健，偶尔进行人工审查就足够了。\n有些评估存在微妙的失败模式，即使智能体表现良好得分也很低，因为智能体因评分漏洞、智能体框架限制或歧义而未能解决任务。即使是成熟的团队也会忽略这些问题。例如，Opus 4.5 最初在 CORE-Bench 上得分 42%，直到一位 Anthropic 研究员发现了多个问题：僵化的评分在期望 \u0026ldquo;96.124991…\u0026rdquo; 时惩罚了 \u0026ldquo;96.12\u0026rdquo;，任务规范模糊，以及无法精确复现的随机任务。修复错误并使用限制较少的框架后，Opus 4.5 的得分跃升至 95%。同样，METR 发现 他们的时间跨度基准测试中有几个配置错误的任务，要求智能体优化到规定的分数阈值，但评分却要求超过该阈值。这惩罚了像 Claude 这样遵循指令的模型，而那些忽略既定目标的模型却获得了更高的分数。仔细复查任务和评分器有助于避免这些问题。\n让你的评分器能够抵御绕过或黑客攻击。智能体不应能轻易“欺骗”评估。设计任务和评分器时，应确保通过测试真正需要解决问题，而不是利用意外的漏洞。\n长期维护和使用评估 (Maintain and use the eval long-term) # 步骤 6：检查对话记录\n除非你阅读许多试验的对话记录和评分，否则你无法知道你的评分器是否工作良好。在 Anthropic，我们投资了用于查看评估对话记录的工具，并定期花时间阅读它们。当任务失败时，对话记录会告诉你智能体是犯了真正的错误，还是你的评分器拒绝了一个有效的解决方案。它通常还会暴露出关于智能体和评估行为的关键细节。\n失败应该看起来是公平的：清楚智能体哪里错了以及为什么错。当分数没有攀升时，我们需要确信这是由于智能体性能而非评估本身。阅读对话记录是你验证评估是否衡量了真正重要内容的途径，也是智能体开发的一项关键技能。\n步骤 7：监控能力评估的饱和度\n一个 100% 的评估可以跟踪回退，但无法提供改进信号。评估饱和 (Eval saturation) 发生在智能体通过所有可解任务时，没有留下改进空间。例如，SWE-Bench Verified 分数今年从 30% 开始，前沿模型现在正接近 \u0026gt;80% 的饱和度。随着评估接近饱和，进展也会放缓，因为只剩下最困难的任务。这可能使结果具有欺骗性，因为巨大的能力提升仅表现为分数的微小增加。例如，代码审查初创公司 Qodo 最初对 Opus 4.5 不以为然，因为他们的单次尝试 (one-shot) 编程评估没有捕捉到其在更长、更复杂任务上的收益。作为回应，他们开发了一个新的智能体评估框架，提供了更清晰的进展图景。\n作为一项规则，在有人深入研究评估细节并阅读一些对话记录之前，我们不会轻信评估分数。如果评分不公、任务模糊、有效方案被罚或框架限制了模型，则应修改评估。\n步骤 8：通过开放贡献和维护保持评估套件的长期健康\n评估套件是一个活的工件，需要持续关注和明确的所有权才能保持有用。\n在 Anthropic，我们尝试了各种评估维护方法。证明最有效的是建立专门的评估团队来拥有核心基础设施，而领域专家和产品团队贡献大部分评估任务并自己运行评估。\n对于 AI 产品团队来说，拥有并迭代评估应该像维护单元测试一样常规。团队可能会在 AI 功能上浪费数周时间，这些功能在早期测试中“有效”，但未能满足那些设计良好的评估本可以尽早暴露的未明示预期。定义评估任务是压力测试产品需求是否具体到足以开始构建的最佳方法之一。\n我们建议实行评估驱动开发：在智能体能够实现之前，先构建评估来定义计划中的能力，然后迭代直到智能体表现良好。在内部，我们经常构建目前“足够好”的功能，但这是对几个月后模型能力的押注。起步通过率较低的能力评估使这一点变得可见。当新模型发布时，运行套件会迅速揭示哪些押注得到了回报。\n最接近产品需求和用户的人最有资格定义成功。以目前的模型能力，产品经理、客户成功经理或销售人员可以使用 Claude Code 将评估任务作为 PR 贡献出来——让他们做吧！或者更好的是，积极地赋能他们。\n创建有效评估的流程 评估如何与其他方法配合以全面了解智能体 (How evals fit with other methods for a holistic understanding of agents) # 自动化评估可以在不部署到生产环境或影响真实用户的情况下，针对数千个任务运行智能体。但这只是了解智能体性能的众多方法之一。完整的图景包括生产环境监控、用户反馈、A/B 测试、手动对话记录审查和系统性的人类评估。\n了解 AI 智能体性能的方法概览\n方法 (Method) 优点 (Pros) 缺点 (Cons) 自动化评估 (Automated evals)\n在没有真实用户的情况下通过程序运行测试 • 迭代速度更快\n• 完全可复现\n• 无用户影响\n• 可在每次提交时运行\n• 无需生产部署即可大规模测试场景 • 需要更多的前期投入来构建\n• 随着产品和模型的发展需要持续维护以避免漂移\n• 如果不匹配真实使用模式，可能会产生错误的信心 生产环境监控 (Production monitoring)\n跟踪实时系统中的指标和错误 • 揭示大规模的真实用户行为\n• 捕捉合成评估遗漏的问题\n• 提供智能体实际表现的基本事实 • 被动，问题在你知晓前已到达用户\n• 信号可能有噪音\n• 需要在仪表化 (instrumentation) 上投入\n• 缺乏用于评分的基本事实 A/B 测试 (A/B testing)\n在真实用户流量下比较变体 • 衡量实际用户结果（留存、任务完成）\n• 控制混杂因素\n• 可扩展且系统化 • 缓慢，需要数天或数周才能达到显著性并需要足够的流量\n• 仅测试你部署的变更\n• 如果不能彻底审查对话记录，较难获得指标变化背后的“原因”信号 用户反馈 (User feedback)\n显式信号，如点踩或错误报告 • 暴露你未预料到的问题\n• 来自实际人类用户的真实案例\n• 反馈通常与产品目标相关 • 稀疏且是自选的 (self-selected)\n• 偏向严重问题\n• 用户很少解释为什么失败\n• 非自动化\n• 主要依靠用户发现问题可能会产生负面用户影响 手动对话记录审查 (Manual transcript review)\n人类阅读智能体对话 • 建立对失败模式的直觉\n• 捕捉自动化检查遗漏的微妙质量问题\n• 有助于校准“好”的标准并掌握细节 • 耗时\n• 无法扩展\n• 覆盖范围不一致\n• 审查者疲劳或不同的审查者可能影响信号质量\n• 通常只给出定性信号而非清晰的定量评分 系统性人类研究 (Systematic human studies)\n由受过训练的评分员对智能体输出进行结构化评分 • 来自多位人工评分员的黄金标准质量判断\n• 处理主观或模棱两可的任务\n• 为改进基于模型的评分器提供信号 • 相对昂贵且周转慢\n• 难以频繁运行\n• 评分员间的分歧需要协调\n• 复杂领域（法律、金融、医疗）需要人类专家进行研究 这些方法对应智能体开发的不同阶段。自动化评估在发布前和 CI/CD 中特别有用，在每次智能体变更和模型升级时运行，作为防止质量问题的第一道防线。生产环境监控在发布后启动，以检测分布漂移和未预料到的现实世界故障。一旦你有足够的流量，A/B 测试用于验证重大变更。用户反馈和对话记录审查是填补空白的持续实践——不断分流反馈，每周抽样阅读对话记录，并在需要时深入挖掘。保留系统性人类研究用于校准 LLM 评分器或评估以人类共识为参考标准的主观输出。\n就像安全工程中的瑞士奶酪模型 (Swiss Cheese Model) 一样，没有任何单一的评估层能捕捉到每一个问题。结合多种方法，穿过一层的故障会被另一层捕捉。 最高效的团队结合了这些方法——自动化评估用于快速迭代，生产环境监控用于获取基本事实，定期人工审查用于校准。\n结论 (Conclusion) # 没有评估的团队会陷入被动响应的泥潭——修复一个故障，制造另一个，无法区分真正的回退和噪音。早期投资的团队发现了相反的情况：随着故障变成测试用例，开发加速，测试用例防止回退，指标取代猜测。评估给整个团队一个清晰的攀登目标，将“智能体感觉变差了”转化为可操作的事情。价值会产生复利，但前提是你将评估视为核心组件，而不是事后的想法。\n不同智能体类型的模式各异，但这里描述的基本原理是不变的。尽早开始，不要等待完美的套件。从你看到的故障中获取现实任务。定义明确、稳健的成功标准。深思熟虑地设计评分器并结合多种类型。确保问题对模型来说足够难。迭代评估以提高信噪比。阅读对话记录！\nAI 智能体评估仍然是一个新生、快速发展的领域。随着智能体承担更长的任务，在多智能体系统中协作，并处理日益主观的工作，我们需要调整我们的技术。随着我们了解更多，我们将继续分享最佳实践。\n附录：评估框架 (Appendix: Eval frameworks) # 几个开源和商业框架可以帮助团队实施智能体评估，而无需从头构建基础设施。正确的选择取决于你的智能体类型、现有技术栈，以及你需要离线评估、生产可观测性还是两者兼有。\nHarbor 专为在容器化环境中运行智能体而设计，拥有跨云提供商大规模运行试验的基础设施，以及定义任务和评分器的标准化格式。流行的基准测试如 Terminal-Bench 2.0 通过 Harbor 注册表发布，使得运行已建立的基准测试和自定义评估套件变得容易。\nPromptfoo 是一个轻量级、灵活且开源的框架，专注于提示词测试的声明式 YAML 配置，断言类型范围从字符串匹配到作为裁判的 LLM 量规。我们在许多产品评估中使用 Promptfoo 的某个版本。\nBraintrust 是一个结合了离线评估与生产可观测性和实验跟踪的平台——对于既需要在开发期间迭代又需要监控生产质量的团队很有用。其 autoevals 库包括针对真实性、相关性和其他常见维度的预构建评分器。\nLangSmith 提供追踪、离线和在线评估以及数据集管理，并与 LangChain 生态系统紧密集成。Langfuse 作为自托管的开源替代方案，为有数据驻留要求的团队提供类似的功能。\n许多团队结合多种工具，推出自己的评估框架，或者仅仅使用简单的评估脚本作为起点。我们发现，虽然框架是加速进展和标准化的宝贵方式，但它们的价值取决于你通过它们运行的评估任务。通常最好的做法是快速选择一个适合你工作流的框架，然后将精力投入到评估本身，通过迭代高质量的测试用例和评分器来实现。\n","date":"2026-01-12","externalUrl":null,"permalink":"/blogs/agent/demystifying-evals-for-ai-agents-anthropic/","section":"Blog","summary":"","title":"译文-解析 AI Agent 评估-Anthropic","type":"blogs"},{"content":"","date":"2026-01-08","externalUrl":null,"permalink":"/tags/skills/","section":"Tags","summary":"","title":"Skills","type":"tags"},{"content":"","date":"2026-01-08","externalUrl":null,"permalink":"/series/skills%E7%B3%BB%E5%88%97/","section":"Series","summary":"","title":"Skills系列","type":"series"},{"content":" obra/superpowers | Plugin Marketplace Superpowers 是一个专为 AI 编程智能体（如 Claude Code）设计的可组合框架。它不仅仅是一个提示词库，更是一套通过标准化“技能（Skills）”来强制执行严格软件工程规范的系统。\n在当前的 AI 辅助开发中，智能体往往容易陷入上下文丢失、逻辑幻觉或产生“看起来能运行但难以维护”的代码。Superpowers 通过引入技能即代码 (Skills as Code) 的理念，强制智能体遵循测试驱动开发 (TDD)、系统化调试和多阶段代码审查等行业最佳实践，将 AI 从随意的聊天机器人转变为遵守纪律的工程伙伴。\n核心架构与原理 # Superpowers 的核心组件是 技能 (Skill)。\n定义：每个技能都是一个包含 YAML 元数据（Frontmatter）的 Markdown 文件 (SKILL.md)。 发现与解析：框架通过插件系统（如 Claude Code 插件或 OpenCode 插件）动态扫描、解析并加载这些技能。 强制调用：系统通过 SessionStart 钩子注入引导指令，强制智能体在执行任何操作前，必须先检索并调用相关技能。 层级覆盖 (Shadowing)：支持 Project \u0026gt; Personal \u0026gt; Superpowers 的优先级机制。开发者可以在项目级定义特定技能，覆盖个人或默认的通用技能，实现高度定制化。 跨平台适配与工具映射 (The Tool Mapping Layer)：Superpowers 内置了一个“中间件”层，能够屏蔽底层系统的差异。例如，它会自动将通用的 Tasks 工具映射为 OpenCode 的 @mention 子智能体机制，或将 TodoWrite 映射为 update_plan。这意味着同一套技能代码可以在 Claude Code、OpenCode 甚至 Codex 等不同平台上无缝运行，实现了真正的“一次编写，到处运行”。 架构设计 核心技能库详解 # Superpowers 提供了一套全面的技能库，涵盖了软件开发的各个生命周期。这些技能不仅仅是流程指令，还集成了静态知识库 (Static Knowledge Base)。例如，TDD 技能会自动索引反模式文档 (@testing-anti-patterns.md)，让 AI 在写测试时能够查阅具体的错误案例，如同工程师手边的参考手册，实现了 \u0026ldquo;Instruction + Reference\u0026rdquo; 的双重增强。\n工作流程与原则 1. 测试 (Testing)：工程质量的基石 # 测试不仅仅是开发的后置环节，而是 Superpowers 框架中的驱动力。\ntest-driven-development (测试驱动开发) 核心原则：强制执行 红-绿-重构 (RED-GREEN-REFACTOR) 循环。 铁律 (The Iron Law)：“没有失败的测试，就不能编写生产代码。” 流程：智能体必须先编写一个能够复现需求或 Bug 的失败测试（Red），然后编写最小量的代码使其通过（Green），最后优化代码结构（Refactor）。 反模式防御：技能文档明确列出了常见的测试反模式（如测试 Mock 的行为而非真实逻辑、在生产代码中通过 if(test) 留后门等），并指导智能体避免这些陷阱。 2. 调试 (Debugging)：根因分析优先 # 针对 AI 容易进行“猜测性修复”的问题，调试技能强制要求系统化的分析过程。\nsystematic-debugging (系统化调试) 四阶段法： 根本原因调查 (Root Cause Investigation)：通过日志、追踪和复现，定位错误的源头，而非仅仅观察症状。 模式分析 (Pattern Analysis)：对比正常与异常的执行路径。 假设与测试 (Hypothesis \u0026amp; Testing)：提出单一变量假设并验证。 实施 (Implementation)：只有在确认根因后才进行修复。 辅助技术：集成 root-cause-tracing（调用栈回溯）、defense-in-depth（深度防御验证）和 condition-based-waiting（基于条件的等待，消除测试中的 Flaky 等待）等子技能。 verification-before-completion (完成前验证) 在标记任务完成前，强制运行验证逻辑，确保修复确实有效且未引入回归错误。 3. 协作与工作流 (Collaboration \u0026amp; Workflow) # Superpowers 将复杂的开发任务拆解为结构化的工作流，确保 AI 与人类开发者的顺畅协作。\nbrainstorming (头脑风暴) 强制性起点：在编写任何代码之前，智能体必须调用此技能。 交互模式：通过问答（Q\u0026amp;A）形式澄清需求，探索 2-3 种技术方案并权衡利弊（Trade-offs），最终输出一份详细的设计文档。 writing-plans (编写计划) 基于设计文档，将工作拆解为一系列微小的、可执行的步骤（Tasks）。每个步骤都必须包含明确的 TDD 预期。 executing-plans (执行计划) 批处理执行：智能体不会一次性写完所有代码，而是按批次执行任务。 架构师检查点 (Architect Checkpoints)：在每批任务完成后，智能体必须暂停并报告进度，等待人类的反馈或批准，从而确保持续的监督。 using-git-worktrees (使用 Git Worktrees) 自动为新功能或修复创建隔离的 Git 工作树，允许开发者在同一仓库中并行处理多个任务而不污染主工作区。 finishing-a-development-branch (完成开发分支) 在任务结束时，提供结构化的选项：本地合并、创建 Pull Request 或清理工作区，并再次运行全量测试。 4. 代码审查与质量控制 (Code Review) # 框架内置了专门的“审查者智能体”角色，模拟资深工程师的 Code Review 流程。\nrequesting-code-review (请求代码审查) 开发者或执行智能体调用此技能，唤起 superpowers:code-reviewer 智能体。该智能体不负责写代码，只负责根据预定义的清单（代码质量、架构一致性、测试覆盖率）对变更进行严格审查。 receiving-code-review (接收代码审查) 指导开发者（或智能体）如何处理反馈。强调“响应模式（The Response Pattern）”：阅读 -\u0026gt; 理解 -\u0026gt; 验证 -\u0026gt; 评估 -\u0026gt; 响应 -\u0026gt; 实施。它鼓励基于技术事实的讨论，而不是盲目接受所有建议。 5. 高级编排 (Advanced Orchestration) # 针对复杂问题，Superpowers 支持多智能体协作。\ndispatching-parallel-agents (调度并行代理) 场景：当测试套件中有多个互不相关的独立故障时。 机制：主智能体识别独立的故障域，并并行启动多个子智能体，每个子智能体在一个隔离的环境中修复一个特定的故障，最后合并结果。 subagent-driven-development (子代理驱动开发) 这是框架中最复杂的编排模式。它将实现计划转化为流水线： Orchestrator：分发任务。 Implementer：执行 TDD 编写代码。 Spec Reviewer：第一道防线，检查代码是否符合原始需求规格。 Code Quality Reviewer：第二道防线，检查代码风格、可维护性和最佳实践。 只有通过了两级审查的代码才会被标记为完成。 6. 元技能 (Meta-Skills) # Superpowers 具有自我进化的能力。\nwriting-skills (编写技能) 指导用户或智能体如何编写新的 SKILL.md。它采用 Skill TDD 方法：先编写一个能触发 AI 错误行为的“压力场景（Pressure Scenario）”（Red），然后编写技能文档来修正该行为（Green），最后优化文档结构（Refactor）。 using-superpowers (使用 Superpowers) 这是系统的“引导加载程序”。它教会智能体如何查找、读取和理解其他技能，并确立了“在行动前必须调用技能”的根本原则。 总结 # Superpowers 并非试图让 AI 变得“更有创造力”，而是让 AI 变得更守纪律。通过将 TDD、系统化调试和代码审查等工程铁律固化为可执行的“技能”，Superpowers 为 AI 辅助软件开发提供了一个可靠、可扩展且高质量的框架。\n对于希望在团队中规模化使用 AI 编程的组织而言，Superpowers 提供了一种将隐性工程知识转化为显性 AI 行为准则的有效路径。\n","date":"2026-01-08","externalUrl":null,"permalink":"/blogs/agent/superpowers/","section":"Blog","summary":"","title":"Superpowers：构建可组合、工程化的 AI 智能体框架","type":"blogs"},{"content":" 论文 An Information Theoretic Perspective on Agentic System Design 将 Agent 系统设计转化为可量化的“工程学”。它证明了在“压缩器-预测器”架构中，“前端重载”（Front-loading） 策略——即使用更大、更智能的模型进行上下文压缩，而非盲目堆砌后端推理模型的规模——是提升性能与降低成本的最优解。 1. 引言：Agent 设计困境 # 在构建如 Deep Research 或 Claude Code 等现代 Agent 系统时，开发者通常采用 “压缩器-预测器”（Compressor-Predictor） 的复合架构：\n压缩器 (Compressor)：负责处理海量原始数据（如 100 篇搜索结果），将其提炼为精简摘要。 预测器 (Predictor)：基于摘要进行推理，生成最终用户答案。 然而，当前的系统设计往往陷入“试错循环”：当系统回答错误时，是压缩器漏掉了关键信息，还是预测器推理能力不足？为了回答这个问题，工程师通常需要运行昂贵的端到端评估。\n这篇论文提出了一种范式转移：将 Agent 系统视为信息通信过程。通过引入信息论（Information Theory）作为数学框架，作者量化了模型间的信息流动，并揭示了关于计算资源分配的 Scaling Laws。\n图 1：为什么压缩器很重要。许多 Agent LM 系统依赖压缩器，而个人设备正变得足够强大以承载它们。（左）压缩器将长输入 \\(X\\) 压缩成较短的摘要 \\(Z\\)，预测器读取 \\(Z\\) 以提取最终答案 \\(Y\\)。（右）如今的消费级硬件可以运行越来越大的开源 LM，图中展示了 FP16 精度下在 Google Pixel 手机和 Apple MacBook 笔记本上运行的模型。LM Arena 的排名表示相对性能。 2. 理论框架：作为“噪声信道”的压缩器 # 作者将压缩过程建模为一个马尔可夫链：\n$$ X \\xrightarrow{p(z|x)} Z \\xrightarrow{p(y|z)} Y $$ \\(X\\)：原始长上下文。 \\(Z\\)：压缩后的摘要（由压缩器 \\(p(z|x)\\) 生成）。 \\(Y\\)：最终预测结果（由预测器 \\(p(y|z)\\) 生成）。 在此框架下，压缩器不仅仅是一个总结工具，更是一个有损噪声信道（Noisy Channel）。设计的核心目标是在压缩率（Rate）和失真度（Distortion）之间寻找平衡。\n为了在不运行下游预测任务的情况下评估压缩质量，作者提出了基于 互信息（Mutual Information, MI） 的评估指标 \\(I(X; Z)\\)。通过蒙特卡洛估计器，该指标可以直接利用现代推理引擎（如 SGLang）输出的 Log Probabilities 进行计算：\n$$ \\begin{align*} I(X; Z) \u0026= \\mathbb{E}_{x,z \\sim p(x,z)} \\left[ \\log \\frac{p(z|x)}{\\mathbb{E}_{x'}[p(z|x')]} \\right], \\\\ \u0026\\approx \\frac{1}{NM} \\sum_{i=1}^{N} \\sum_{j=1}^{M} \\left[ \\log p(z_{ij}|x_i) - \\log \\left( \\frac{1}{N} \\sum_{l=1}^{N} p(z_{ij}|x_l) \\right) \\right] \\equiv \\hat{I}(X; Z), \\end{align*} $$理论定义\n\\(I(X; Z)\\)：表示变量 \\(X\\) 和 \\(Z\\) 之间的互信息。直观上，它衡量知道 \\(Z\\) 能减少多少关于 \\(X\\) 的不确定性。 \\(\\mathbb{E}_{x,z \\sim p(x,z)}\\)：这是对联合分布 \\(p(x,z)\\) 求期望。意味着在整个数据集上考虑所有成对的 \\((x, z)\\)。 \\(p(z|x)\\)：这是条件概率（或编码器分布），即给定输入 \\(x\\) 生成 \\(z\\) 的概率。 \\(\\mathbb{E}_{x'} [p(z|x')]\\)：分母部分实际上是边缘分布 \\(p(z)\\) 的展开形式。因为直接计算 \\(p(z)\\) 很困难（需要在所有可能的 \\(x\\) 上积分），这里写成了期望形式 \\(p(z) = \\int p(z|x')p(x')dx'\\)。 核心逻辑：互信息的本质是 \\(KL(p(x,z) || p(x)p(z))\\)。公式里的项 \\(\\log \\frac{p(z|x)}{p(z)}\\) 也就是 点互信息 (PMI)。如果 \\(z\\) 和 \\(x\\) 强相关，分子 \\(p(z|x)\\) 会远大于分母 \\(p(z)\\)（随机出现的概率），对数值就会很大。 蒙特卡洛估计器（近似计算）：\n由于在实际操作中无法遍历真实的概率分布，需要通过采样来近似计算：\n\\(\\frac{1}{NM} \\sum_{i=1}^{N} \\sum_{j=1}^{M}\\)：这是对外部期望 \\(\\mathbb{E}_{x,z}\\) 的蒙特卡洛近似。 \\(N\\)：表示采样的输入样本（Contexts）数量，即 Batch size。 \\(M\\)：表示对于每一个输入 \\(x_i\\)，采样生成的 \\(z\\) 的数量。 \\(\\log p(z_{ij}|x_i)\\)：对应第一行分子的对数。这计算了生成的 \\(z_{ij}\\) 与其原本对应的输入 \\(x_i\\) 之间的匹配度（Log-likelihood）。 \\(\\log \\left( \\frac{1}{N} \\sum_{l=1}^{N} p(z_{ij}|x_l) \\right)\\)：对应第一行分母 \\(p(z)\\) 的对数。 这是一个边缘概率的估计。 它通过将当前的 \\(z_{ij}\\) 与 Batch 中所有其他输入 \\(x_l\\) 进行配对来计算。 直观理解：看这个 \\(z_{ij}\\) 在整个数据集中出现的“普遍程度”。 \\(\\hat{I}(X; Z)\\)：最终得到的互信息估计值。 这个公式展示了如何在一个 Batch 的数据中，利用 对比（Contrastive） 的思想来计算互信息：\n计算 \\(z\\) 和它真正来源 \\(x\\) 的匹配度（正样本对）。 减去 \\(z\\) 和所有 \\(x\\) 的平均匹配度（近似边缘分布）。 两者的差值越大，说明 \\(z\\) 包含的关于 \\(x\\) 的独特信息越多，互信息越高。如果一个摘要是“正确的废话”（笼统概述，丢失具体数据等信息），那么它在任何文档下生成的概率都很高，两项相减接近，互信息就很低。 关键：MI 仅依赖于压缩器输出的统计特性，是一个 与任务无关（Task-Agnostic） 的质量代理指标。\n3. 深度拆解：压缩与预测的 Scaling Paradox # 论文通过在 LongHealth、FinanceBench、FineWeb 等数据集上的大规模实验，揭示了违反直觉的 Scaling 现象。这一章节不仅挑战了传统的算力分配直觉，还提供了精确的数据支撑。\n3.1 压缩器的 Scaling Law：算力增长的“次线性”红利 # 通常直觉认为，模型参数量越大，推理成本越高。但在 Agent 的压缩环节，作者观察到了极其特殊的现象：更大的模型反而更“省”算力。\n图 2：压缩器规模对准确率、长度和计算成本的影响 论文 图2 和 图3 的实验数据：\n精简度红利 (Conciseness)：在 LongHealth 数据集上，Qwen-2.5 7B 模型生成的摘要长度显著短于 1.5B 模型。更大的模型能更精准地抓住重点，抛弃冗余信息，其生成的 Token 数量最高可减少 4.6倍。 FLOPs 的次线性增长：推理的总计算量（FLOPs）近似等于 \\(\\text{Model Size} \\times \\text{Generated Tokens}\\)。虽然 7B 模型的参数量是 1.5B 的近 5 倍，但由于它生成的 Token 数大幅减少，相互抵消后，FLOPs-per-generation 仅增加了 1.3%。 性能飞跃：在消耗几乎相同算力的情况下，7B 模型的下游准确率比 1.5B 模型高出 3.1倍，甚至超越了 GPT-4o 单独作为预测器的 Baseline 约 4个百分点。 这揭示了一个工程真理：在压缩任务中，智能（参数量）即效率。\n图 3：压缩器 vs 预测器 的 Scaling 效率对比 3.2 资源分配决断：Bottleneck 到底在哪里？ # 论文在 Section 3.1 提出了一个关键的工程问题：如果预算有限，应该把算力投在“压缩器”还是“预测器”？\n图3数据给出了明确答案：\n压缩器主导性：将压缩器（Qwen-2.5）从 1B 扩展到 7B，下游 QA 准确率提升了惊人的 60%。 预测器边际效应递减：固定压缩器，将后端预测器（Llama-3）从 70B 扩展到庞大的 405B，准确率仅提升了 12%。 压缩器构成了系统的信息瓶颈（Information Bottleneck）。如果压缩器产生的摘要 \\(Z\\) 丢失了 \\(X\\) 中的关键事实（如日期、数值），后端的预测器无论推理能力多强（哪怕是 GPT-4o 或 Llama-3-405B），都无法凭空恢复这些信息。这也就是作者所说的“归因问题”——大部分性能损失实际上源于前端的压缩失真。\n4. 率-失真（Rate-Distortion）分析：量化信息密度 # 为了不依赖昂贵的下游任务（如 QA 准确率）来评估压缩器，作者引入了率-失真理论。这部分分析为如何选择模型提供了数学依据。\n4.1 比特效率（Bit Efficiency） # 作者定义了一个新指标：Bit Efficiency，即每输出一个 Token 所包含的互信息量（MI per token）。\n图 4：互信息与比特效率-在 LongHealth 上，更大的压缩器生成的输出包含更多关于其输入的信息（以查询为条件） 如图4所示，更大的模型（如 7B）不仅总互信息更高，其 Bit Efficiency 也显著更高。这意味着大模型输出的每一个字都更“干货”，含金量更高。 相比之下，小模型（1B级别）倾向于“啰嗦且无效”，生成大量 token 却无法有效编码原始上下文中的关键信息。 4.2 率-失真曲线的拟合 # 在 图6(左) 中，作者展示了基于实验数据的 \\(R-D\\) 曲线。横轴为 Rate（信息率），纵轴为 Distortion（\\(1 - \\text{Accuracy}\\)）。\n曲线形态：数据完美拟合了指数衰减函数 \\(D(R) \\approx C e^{-bR} + D_0\\)。 预测器的天花板：图中不同颜色的曲线代表不同规模的预测器（1B, 8B, 70B, 405B）。值得注意的是，随着压缩器 Rate 的提升，所有预测器的失真度都迅速下降。但当 Rate 达到一定阈值后，曲线趋于平缓（\\(D_0\\)），此时再大的预测器也无法进一步降低失真。 图 6：率-失真 (Rate-Distortion) 分析 4.3 互信息作为通用代理指标 # 图6(右) 在 FineWeb 数据集上，作者发现：\n强相关性：压缩器的互信息率（Rate）与模型困惑度（Perplexity）之间的皮尔逊相关系数达到 \\(r = -0.84\\) (\\(R^2 = 0.71\\))。 意义：这意味着开发者可以在不运行任何特定问答任务的情况下，仅仅通过计算 MI 来评估和筛选压缩器模型。如果一个压缩器的 MI 高，它在下游几乎所有任务（无论是抽取式问答还是创造性生成）上的表现都会更好。 5. 实战演练：Deep Research 系统的极致优化 # 论文在 Section 3.5 和 Appendix D.6 中，将上述理论应用到了目前最火热的 Deep Research 场景（类似于 OpenAI Deep Research 或 Perplexity Pro）。\n5.1 工作流重构 # 图 8：深度研究工作流程 作者设计了一个标准化的 Deep Research Pipeline（见 图8）：\n分解：预测器将大问题分解为多个 (Query, Subtask) 对。 并行压缩：多个压缩器并行运行，分别进行搜索、阅读网页，并根据 Subtask 生成摘要。 综合：预测器汇总所有摘要生成最终报告。 使用 DeepResearch Bench 和 RACE 评分体系（衡量全面性、深度、指令遵循、可读性）进行评估。\n5.2 惊人的降本增效数据 # 对比基准（Uncompressed GPT-4o，即直接把搜索到的 Raw HTML 喂给 GPT-4o）与优化方案（本地 Qwen-2.5 压缩器 + GPT-4o 预测器）：\n图 7：Deep Research（深度研究）扩展结果 成本暴跌 (图7左)：\n优化方案的 API 成本仅为基准的 26% - 28%。 原因在于：大量的 Raw HTML 处理（Reading/Compressing）被转移到了本地运行的开源模型（如 Qwen-2.5）上，这些本地算力几乎是“免费”的。只有高度浓缩的摘要才会被发送给昂贵的 GPT-4o API。 性能无损甚至反超 (图7右)：\n使用 14B 的 Qwen-2.5 作为本地压缩器时，最终报告的 RACE 评分比基准 高出 2.3%。 极小模型的潜力：即使使用极小的 3B 模型作为压缩器，也能恢复基准 99% 的性能，同时节省 74% 的成本。 这一结果有力地证明了：Front-loading（前端重载）策略 ——即在本地用较强的模型处理数据压缩——是构建低成本、高性能 Agent 的关键路径。\n6. 面向工程师的设计原则 # 基于以上分析，论文提出了四条 Agent 系统设计原则：\n压缩器规模可实现次线性计算成本增长：不要害怕在前端使用大模型，它们生成的 Token 更少，总计算量可能持平甚至更低。 前端重载（Front-load）算力：将计算资源向压缩端倾斜。与其依赖云端的超大模型处理所有 Token，不如在本地运行强大的压缩模型。 优化信息密度：关注互信息（MI）指标。模型并不是记得越多越好，而是要以最高的密度传递有效信息。 模型家族差异：不同模型家族的压缩特性不同。本研究中，Qwen-2.5 在压缩任务上的算力效率和 Scaling 表现优于 Llama-3 和 Gemma-3。 总结 # 这篇文章将 Agent 系统设计从经验主义推向了理论指导。在多模型协作系统中，“通信带宽”的质量（即压缩器的互信息保留能力）决定了系统的上限。通过在数据入口处部署更大、更智能的压缩器，不仅能解决“上下文腐烂（Context Rot）”问题，还能在保持高性能的同时显著降低推理成本。\n","date":"2025-12-31","externalUrl":null,"permalink":"/blogs/agent/information-theoretic-perspective-on-agentic-system-design/","section":"Blog","summary":"","title":"Agent 系统中“压缩器-预测器”的信息瓶颈与 Scaling Law","type":"blogs"},{"content":"","date":"2025-12-31","externalUrl":null,"permalink":"/tags/compressor-predictor/","section":"Tags","summary":"","title":"Compressor-Predictor","type":"tags"},{"content":"","date":"2025-12-31","externalUrl":null,"permalink":"/tags/informationtheory/","section":"Tags","summary":"","title":"InformationTheory","type":"tags"},{"content":"","date":"2025-12-31","externalUrl":null,"permalink":"/tags/rate-distortion/","section":"Tags","summary":"","title":"Rate-Distortion","type":"tags"},{"content":"","date":"2025-12-31","externalUrl":null,"permalink":"/tags/%E8%AE%BA%E6%96%87/","section":"Tags","summary":"","title":"论文","type":"tags"},{"content":"","date":"2025-12-26","externalUrl":null,"permalink":"/tags/graphrag/","section":"Tags","summary":"","title":"GraphRAG","type":"tags"},{"content":"","date":"2025-12-26","externalUrl":null,"permalink":"/series/youtu-graphrag/","section":"Series","summary":"","title":"Youtu-GraphRAG","type":"series"},{"content":" TencentCloudADP/Youtu-GraphRAG 1. 引言 # 在 RAG 系统中，面对“谁是电影《X》导演的父亲？”这类多跳问题（Multi-hop Question），简单的向量检索往往失效，因为向量空间难以捕捉多重关系的逻辑链路。\nyoutu-graphrag 引入了 Agentic RAG 的范式来解决这一难题。它不再试图一次性检索出所有答案，而是模仿人类专家的解题思路：先将大问题拆解为小问题，然后通过“检索-思考-再检索”的循环逐步逼近答案。这一过程被称为 IRCoT (Iterative Retrieval Chain of Thought)。\n2. 战略层：GraphQ 问题分解器 # 推理的第一步并非直接检索，而是降维。GraphQ 模块负责利用图 Schema 将复杂的自然语言问题转化为“图可解”的原子子问题。\n2.1 Schema 感知的分解 (Schema-Aware Decomposition) # 在 models/retriever/agentic_decomposer.py 中，decompose 方法调用 LLM 进行拆解。与普通分解不同，Prompt 中显式注入了图的 Ontology（本体论/Schema）。\n# Prompt 核心指令 (config/base_config.yaml) \u0026#34;\u0026#34;\u0026#34; Given the following ontology and the question, decompose the complex question... CRITICAL REQUIREMENTS: 1. Each sub-question must be... Explicitly reference entities and relations... 2. Identify all schema types that might be involved \u0026#34;\u0026#34;\u0026#34; 这种设计迫使 LLM 在分解时“戴着镣铐跳舞”，确保生成的子问题中的术语（如关系谓词）尽可能与图谱中的定义对齐，从而提高后续检索的命中率。\n2.2 类型预判与搜索空间剪枝 # GraphQ 的另一个重要产出是 involved_types。LLM 会预测回答该问题可能涉及的节点类型（如 person）、关系类型（如 directed_by）和属性。\n在后续的 KTRetriever 检索中，这些类型信息充当了预过滤器。如果 LLM 预测只涉及“人物”和“电影”，检索器就可以直接忽略“地点”或“组织”类型的节点。这种机制在数百万节点的大规模图谱中，对于降低检索噪声、提升信噪比具有重要的工程价值。\n3. 执行层：Map-Reduce 并行检索 # 分解得到的子问题（sub_questions）通常是相互独立的简单事实查询。main.py 中的 initial_question_decomposition 实现了类似 Map-Reduce 的处理逻辑：\nMap (并行执行): 系统检测到多个子问题后，会调用 kt_retriever.process_subquestions_parallel。这利用了 Python 的 ThreadPoolExecutor 并发地对每个子问题执行检索。 Reduce (结果聚合): 所有子问题的检索结果（三元组和文本块）被汇总到一个集合中。 # main.py if len(sub_questions) \u0026gt; 1: logger.info(\u0026#34;🚀 Using parallel sub-question processing...\u0026#34;) aggregated_results, _ = kt_retriever.process_subquestions_parallel(...) all_triples.update(aggregated_results[\u0026#39;triples\u0026#39;]) 这一设计显著降低了首轮检索的延迟。然而，并行度受限于 LLM API 的速率限制（Rate Limit）和 Embedding 模型的并发吞吐能力，在实际部署时通常需要配置 token 桶等限流机制。\n4. 核心大脑：IRCoT 迭代推理循环 # 对于逻辑依赖性强的问题（例如：先查出A是谁，再查A做了什么），并行检索无法解决。这时系统进入 IRCoT 模式。\n4.1 状态机设计 # agent_retrieval 函数（位于 main.py）维护了一个基于 LLM 的状态机循环。循环的驱动力来自于 Prompt 的特殊指令：\n\u0026ldquo;If you have enough information\u0026hellip; write \u0026lsquo;So the answer is:\u0026rsquo;\u0026hellip;\u0026rdquo; \u0026ldquo;If you need more information\u0026hellip; write \u0026lsquo;The new query is:\u0026rsquo;\u0026hellip;\u0026rdquo;\n4.2 动态上下文累积 # 在每一轮迭代（Step）中，系统执行以下操作：\n构建上下文: 将当前累积的所有三元组（Triples）和文本块（Chunks）拼接成 Context。 LLM 决策: LLM 阅读 Context 和历史思维链（Thoughts），决定是输出答案还是发起新查询。 动态检索: 如果发起新查询，KTRetriever 会针对这个新生成的问题执行检索。 知识更新: 新检索到的知识被合并入 all_triples 和 all_chunk_ids 集合中。 # main.py (Agent Loop) while step \u0026lt;= max_steps: # ... Prompt Construction ... response = kt_retriever.generate_answer(ircot_prompt) if \u0026#34;So the answer is:\u0026#34; in response: break # 终止条件 if \u0026#34;The new query is:\u0026#34; in response: new_query = ... # 执行新一轮检索，更新知识库 retrieval_results, _ = kt_retriever.process_retrieval_results(new_query, ...) all_triples.update(retrieval_results[\u0026#39;triples\u0026#39;]) 4.3 潜在风险分析 # 这种串行迭代机制虽然推理能力强大，但也带来了两个显著的工程挑战：\n延迟叠加 (Latency Stacking): 每一轮 Step 都包含一次 LLM 调用和一次检索操作。如果迭代 5 次，总耗时可能是单次 RAG 的 5-10 倍。 上下文溢出 (Context Overflow): 随着知识不断累积 (context += ...)，Prompt 的长度单调增加。如果不引入动态的 Token 截断或相关性过滤机制，很容易在后期迭代中通过 Token Limit 导致崩溃。 5. 落地回归：Chunk 回溯 (Graph-to-Text) # 虽然推理过程依赖于图谱（三元组），但最终生成答案时，系统并未抛弃原始文本。\n在检索的每一阶段，系统都会通过节点属性中的 chunk id 回溯到原始文档片段，并使用 _rerank_chunks_by_relevance 对这些片段进行重排序。最终提供给 LLM 的上下文是 “结构化骨架（图）+ 非结构化血肉（文本）” 的混合体。这确保了生成的答案既有逻辑的严密性，又有原文的细节丰富度。\n6. 总结 # youtu-graphrag 的推理层展示了一个成熟的 Agentic 系统雏形。它通过 GraphQ 实现了复杂问题的降维，通过 IRCoT 实现了动态的路径规划。\n从 Schema 的定义，到图谱的构建与演进，再到多粒度的存储与索引，最后到 Agent 的动态推理，youtu-graphrag 提供了一套完整的、垂直统一的解决方案。\n","date":"2025-12-26","externalUrl":null,"permalink":"/blogs/rag/youtu-graphrag-code-5/","section":"Blog","summary":"","title":"Youtu-GraphRAG 源码：Agentic 分解与 IRCoT 迭代推理","type":"blogs"},{"content":" TencentCloudADP/Youtu-GraphRAG 1. 引言 # 在图检索增强生成（GraphRAG）中，社区发现（Community Detection）不仅仅是为了聚类，更是为了解决“全局性问题”（Global Query）。例如，“这篇文章主要讲了什么？”这类问题无法通过检索单个实体回答，必须依赖对图谱的高层抽象。\n主流 GraphRAG 方案通常采用 Leiden 或 Louvain 算法，这些算法主要基于图的拓扑结构（如模块度最大化）。然而，纯拓扑聚类忽略了节点间的文本语义相似性。youtu-graphrag 提出的 FastTreeComm 算法试图通过融合 语义（Embedding） 与 拓扑（Topology） 来生成质量更高的社区。\n2. 核心机制：双重感知 (Dual Perception) # FastTreeComm 的核心在于其相似度计算公式。不同于传统算法仅看边（Edge），它计算的是任意两个节点间的综合距离。\n2.1 相似度融合公式 # 在 _compute_sim_matrix 方法中，算法构建了一个 \\(N \\times N\\) 的相似度矩阵：\n$$ Sim(u, v) = \\alpha \\cdot Sim_{struct}(u, v) + (1 - \\alpha) \\cdot Sim_{semantic}(u, v) $$源码中，权重系数 \\(\\alpha\\) 由 struct_weight 参数控制（默认 0.3），这意味着算法在设计上更偏重于语义相似度。\n# utils/tree_comm.py def _compute_sim_matrix(self, level_nodes): # ... sim_matrix = (self.struct_weight * structural_sim_matrix + (1 - self.struct_weight) * semantic_sim_matrix) return sim_matrix 这种全量矩阵计算虽然保证了精度，但在工程上隐含了 \\(O(N^2)\\) 的空间复杂度风险。当同层级节点数达到数万规模时，该矩阵可能导致内存溢出，这限制了算法在单机环境下的扩展性。\n2.2 语义侧：三元组增强 Embedding # 为了计算 \\(Sim_{semantic}\\)，算法没有简单地使用节点名称的 Embedding，而是引入了“三元组增强”机制。\n_get_triple_strings 方法将节点及其一跳邻居（1-hop neighbors）序列化为文本字符串：\n\u0026ldquo;NodeA relation1 NodeB, NodeA relation2 NodeC\u0026hellip;\u0026rdquo;\n这一设计使得在拓扑上不直接相连、但在局部结构和语义上相似的节点（例如两个在不同文档中出现的“苹果公司”节点），能够获得极高的语义相似度，从而被聚类到同一社区。\n2.3 拓扑侧：向量化 Jaccard 计算 # \\(Sim_{struct}\\) 基于 Jaccard 相似系数（共享邻居的比例）。代码通过稀疏矩阵运算实现了高效的向量化计算：\n# utils/tree_comm.py def _compute_jaccard_matrix_vectorized(self, level_nodes): # Intersection = A * A.T intersection = sub_adj.dot(sub_adj.T).toarray() # Union = RowSum + ColSum - Intersection union = row_sums[:, None] + row_sums - intersection return intersection / (union + 1e-9) 3. 聚类策略：递归式 K-Means # 不同于 Louvain 的贪婪优化，FastTreeComm 采用了 K-Means 作为底层聚类器，并配合 “分裂-合并”（Split-Merge） 策略。\n3.1 初始划分与递归细分 # 算法首先对 Level 2 的所有节点进行一次粗粒度的 K-Means 聚类 (_fast_clustering)。随后，对于规模较大的簇，算法会尝试递归调用 _refine_cluster 进行细分。\n这种策略避免了在大规模图上运行复杂图算法的时间开销，但 K-Means 的随机初始化特性也意味着社区划分结果在不同运行次间可能存在微小波动。\n3.2 动态合并 # 在细分之后，算法会检查子社区中心（Centroid）之间的相似度。如果两个子社区的中心相似度超过 merge_threshold（默认 0.5），它们会被重新合并。\n这一逻辑旨在解决 K-Means 强制划分可能导致的社区割裂问题，确保语义高度紧密的群体不会因为 K 值的设定而被强行分开。\n4. Level 4 构建：超级节点与摘要 # 社区发现的最终产物是知识树的 Level 4。代码不仅仅是输出聚类结果，而是物理地在 NetworkX 图中创建了新的“超级节点”。\n4.1 超级节点创建 # create_super_nodes 方法遍历生成的社区，为每个社区创建一个新节点（label=\u0026quot;community\u0026quot;）。同时，建立从成员实体到社区节点的 member_of 边，从而在物理存储上构成了层级结构。\n4.2 智能摘要 (LLM Summarization) # 为了让 Level 4 节点具备可检索性，系统调用 LLM 为每个社区生成 name 和 description。\n# utils/tree_comm.py prompt = f\u0026#34;\u0026#34;\u0026#34;Generate names and summaries... 1. **Naming Rules**: Reflect geographic, cultural, or member traits 2. **Summary Requirements**: Less than 100 words... Highlight key attributes ...\u0026#34;\u0026#34;\u0026#34; 代码通过 Batch 处理（batch_size=5）来降低 LLM API 的调用频率。然而，对于包含成百上千个社区的大型图谱，这也是一个显著的时间瓶颈。在生产环境中，这通常需要通过异步队列（如 Celery）来后台处理，而非在构建流程中同步阻塞执行。\n5. 总结 # FastTreeComm 算法通过 语义优先、拓扑辅助 的双重感知机制，有效弥补了传统图聚类算法对文本内容视而不见的缺陷。它生成的社区不仅是结构的聚类，更是语义的聚合。\n尽管其全量相似度矩阵计算和同步摘要生成在超大规模数据集上存在性能天花板，但在中等规模的垂直领域知识库中，这种精细化的处理方式能够显著提升宏观问题的召回准确率，为 RAG 系统提供了宝贵的全局视野。\n","date":"2025-12-26","externalUrl":null,"permalink":"/blogs/rag/youtu-graphrag-code-3/","section":"Blog","summary":"","title":"Youtu-GraphRAG 源码：FastTreeComm 社区发现算法","type":"blogs"},{"content":" TencentCloudADP/Youtu-GraphRAG 1. 引言 # 在传统的 RAG 系统中，检索通常意味着对文本块（Chunks）进行向量相似度搜索。然而，在 GraphRAG 语境下，单纯的文本检索会丢失图的拓扑信息。如何既利用向量的高效性，又保留图的结构性，是检索器设计的核心挑战。\nyoutu-graphrag 给出的答案是 “多粒度索引” (Multi-Granularity Indexing) 配合 “双路检索” (Dual-Path Retrieval)。系统并未止步于单一的节点索引，而是构建了四套独立的 FAISS 索引，并通过两条并行路径分别召回细节与全貌。\n2. 四重索引机制 # DualFAISSRetriever 在初始化阶段会构建四个独立的向量索引文件。这种设计显著增加了存储成本，但也极大地丰富了检索的语义维度。\n2.1 索引类型与构建逻辑 # Node Index (节点索引): 内容: name + description。 作用: 传统的实体链接与召回。 Relation Index (关系索引): 内容: 边的类型名称（如 directed_by, located_in）。 作用: 捕捉问题中的谓词语义（例如问“导演是谁”，能通过 directed_by 快速定位相关边）。 Triple Index (三元组索引): 内容: HeadNodeText, Relation, TailNodeText 组成的完整句子。 作用: 这是最细粒度的索引。它将图的最小结构单元（边）文本化，使得检索器能直接匹配完整的语义事实，而不仅仅是单个实体。 Community Index (社区索引): 内容: 社区名称 + 社区摘要。 作用: 响应宏观主题查询。 2.2 物理存储 # 代码显示，这些索引并非仅存在于内存，而是被持久化到 retriever/faiss_cache_new/{dataset}/ 目录下。除了 .index 文件（FAISS 索引），系统还保存了 .json 映射文件（ID 到文本的映射）和 .pt 文件（PyTorch Tensor 格式的原始 Embedding），以支持后续的重排序计算。\n3. 双路检索逻辑 (Dual-Path Logic) # 检索的核心入口是 dual_path_retrieval 方法。它并非简单的线性执行，而是并行触发了两条路径，分别对应不同的检索意图。\n3.1 Path 1: 基于三元组的微观检索 (retrieve_via_triples) # 此路径旨在精准定位事实。\n向量搜索: 首先在 Triple Index 中查找 Top-K 相似的三元组。 拓扑扩展 (3-Hop Expansion): 这是该模块最激进的设计。在 _process_triple_index 方法中，系统不仅返回命中的三元组，还会调用 _collect_neighbor_triples，进而触发 _get_3hop_neighbors。 逻辑: 以命中节点为中心，向外进行广度优先搜索（BFS），获取 3 跳范围内的所有邻居节点及其关联的三元组。 意图: 试图通过图结构补充上下文，解决向量检索“只见树木不见森林”的问题。 这种无限制的 3 跳扩展在稠密图中可能存在显著的性能风险。代码中的 _get_3hop_neighbors 使用队列进行 BFS，且未设置最大邻居数量限制（Limit/Top-K）。如果命中了一个度数极高的“超级节点”（Hub Node），待处理的三元组数量将呈指数级爆炸，可能导致检索延迟从毫秒级飙升至秒级甚至超时。在生产化改造时，此处必须引入基于度中心性的剪枝策略。 重排序 (Reranking): 扩展后的三元组集合可能包含大量噪声。系统随后调用 _calculate_triple_relevance_scores，计算 Query 与每个三元组文本的余弦相似度，并过滤掉低于阈值（默认 0.1）的结果。 3.2 Path 2: 基于社区的宏观检索 (retrieve_via_communities) # 此路径旨在获取背景知识。\n向量搜索: 在 Community Index 中查找 Top-K 相似的社区。 成员展开: 通过 comm_map 找到对应的社区 ID，进而检索该社区包含的所有成员实体 (_get_community_nodes)。 结果: 返回一组与主题高度相关的节点集合，这些节点可能在微观路径中未被覆盖。 4. 缓存与性能优化 # 为了缓解 Embedding 和图遍历带来的计算压力，DualFAISSRetriever 实现了多层缓存：\nFAISS 搜索缓存: faiss_search_cache 记录了 Query Embedding 哈希值对应的搜索结果，应对重复查询。 节点 Embedding 缓存: node_embedding_cache 存储了所有节点的向量表示。代码支持从 .pt (Torch) 或 .npz (Numpy) 加载，这显示了对不同运行环境的兼容性考量。 GPU 加速: 在 _preload_faiss_indices 中，代码检测 CUDA 环境。如果可用，会自动将 CPU 索引转移至 GPU (faiss.index_cpu_to_gpu)，这对大规模向量库的检索速度提升是决定性的。 5. 总结 # DualFAISSRetriever 采用了 \u0026ldquo;Recall broadly, Filter strictly\u0026rdquo; (广召回，严过滤) 的设计哲学。\n通过同时维护四套索引，它确保了无论是微观实体还是宏观社区都能被向量化；通过 3 跳邻居扩展，它利用图拓扑捕捉了向量空间难以表达的关联性。尽管其激进的扩展策略在稠密图场景下需要工程上的约束与剪枝，但这种索引与拓扑深度耦合的设计，正是 GraphRAG 区别于传统 RAG 的核心竞争力所在。\n","date":"2025-12-26","externalUrl":null,"permalink":"/blogs/rag/youtu-graphrag-code-4/","section":"Blog","summary":"","title":"Youtu-GraphRAG 源码：多粒度双路检索——Dual FAISS 索引与其物理存储设计","type":"blogs"},{"content":" TencentCloudADP/Youtu-GraphRAG 1. 引言 # youtu-graphrag 是一个面向复杂推理场景的图增强检索增强生成（GraphRAG）框架。与传统的 RAG 系统（主要依赖向量相似度检索）或通用的 GraphRAG（主要依赖全局摘要）不同，该项目采用了一种“垂直统一”的架构设计。其核心特征在于通过 图模式（Graph Schema） 对知识构建进行约束，并通过 智能体（Agent） 机制实现从构建到检索的动态流程。\n本文将依据源码，从宏观视角解析其系统架构、数据组织形式以及核心工作流，为后续深入各模块细节奠定基础。\n2. 核心设计理念 # 通过分析 config/base_config.yaml 和 main.py，可以看出该系统遵循两个核心设计原则：\nSchema 引导 (Schema-Guided): 系统不依赖完全开放的信息抽取，而是基于预定义的种子 Schema（包含节点类型、关系类型、属性）进行知识提取。这在 models/constructor/kt_gen.py 中有明确体现，旨在降低噪声并提高知识结构的规范性。 代理式范式 (Agentic Paradigm): 构建侧: 支持 Schema 的动态演进，允许模型在提取过程中发现新类型并反向更新 Schema。 检索侧: 采用 IRCoT (Iterative Retrieval Chain of Thought) 机制，支持多步迭代检索和自我反思。 3. 四层知识树架构 (Four-Level Knowledge Tree) # youtu-graphrag 在逻辑和物理存储上将知识图谱划分为四个明确的层级。这种分层设计旨在解决不同粒度问题的检索需求。\n依据 utils/graph_processor.py 中的节点加载逻辑和 models/constructor/kt_gen.py 的构建逻辑，四层结构如下：\nLevel 4: 社区 (Community) 定义: 由算法生成的节点聚类，代表宏观的主题或知识簇。 载体: 超级节点 (Super Node)，包含由 LLM 生成的社区摘要 (description)。 生成方式: utils/tree_comm.py 中的 FastTreeComm 算法。 Level 3: 关键词 (Keyword) 定义: 连接社区与具体实体的桥梁节点。 载体: 关键词节点，用于加速索引和横向关联。 Level 2: 实体与关系 (Entity \u0026amp; Relation) 定义: 知识图谱的主干，包含具体的业务实体及其相互关系。 载体: 标准的图节点与边。 Level 1: 属性 (Attribute) 定义: 实体的细粒度特征（如时间、数值、状态）。 载体: 属性被升格为独立节点 (label=\u0026quot;attribute\u0026quot;)，而非仅作为节点属性存在。这允许系统通过属性值进行跨实体的关联检索。 4. 系统宏观架构 # 基于 main.py 的执行流程，系统架构可分为 写入路径 (Write Path) 和 读取路径 (Read Path)。\ngraph TD Config[Configuration \u0026 Schema] --\u003e Controller[\"Main Controller (main.py)\"] subgraph \"Write Path: Knowledge Construction\" Controller --\u003e KTBuilder[KTBuilder] KTBuilder --\u003e Extraction[LLM Extraction] Extraction --\u003e Evolution{Agent Mode?} Evolution -- Yes --\u003e SchemaUpdate[Update Schema JSON] Evolution -- No --\u003e GraphMem[NetworkX Graph] SchemaUpdate --\u003e GraphMem GraphMem --\u003e TreeComm[FastTreeComm] TreeComm --\u003e CommDetect[Community Detection] CommDetect --\u003e Level4[Generate Level 4 Nodes] Level4 --\u003e Storage[(Output JSON \u0026 Indices)] end subgraph \"Read Path: Retrieval \u0026 Reasoning\" Controller --\u003e GraphQ[GraphQ Decomposer] GraphQ --\u003e SubQ[Sub-Questions] SubQ --\u003e KTRetriever[Enhanced KT Retriever] KTRetriever --\u003e DualFAISS[Dual FAISS Retriever] Storage -.-\u003e DualFAISS DualFAISS --\u003e Path1[Path 1: Node/Relation/Triple] DualFAISS --\u003e Path2[Path 2: Community] Path1 \u0026 Path2 --\u003e Rerank[Reranking] Rerank --\u003e IRCoT[IRCoT Agent Loop] IRCoT --\u003e FinalAnswer[Final Answer] end 5. 工作流深度解析 # 5.1 写入路径：图谱构建 (Graph Construction) # 构建过程由 main.py 中的 graph_construction 函数触发，核心类为 models.constructor.kt_gen.KTBuilder。\n初始化与切分: 加载数据集配置与 Schema，对输入文档进行切分 (chunk_text)。 信息抽取 (Extraction): 根据 config.construction.mode 选择 Prompt 模板。 调用 LLM 提取实体、关系和属性。 Schema 演进: 在 agent 模式下，如果 LLM 返回了 new_schema_types，系统会调用 _update_schema_with_new_types 实时更新本地 Schema 文件。 图结构组装: 将提取的 Level 1 (属性) 和 Level 2 (实体/关系) 写入内存中的 networkx.MultiDiGraph。 社区发现 (Community Detection): 调用 process_level4 方法。 使用 FastTreeComm 算法（位于 utils/tree_comm.py），结合语义 Embedding 和拓扑结构进行聚类。 生成 Level 4 的社区节点并建立其与成员节点的连接 (member_of)。 持久化: 最终图谱以 JSON 格式保存至 output/graphs/{dataset}_new.json，同时保存 Chunk 映射文件。 5.2 读取路径：检索与推理 (Retrieval) # 检索过程由 main.py 中的 retrieval 函数触发，支持 noagent（单次检索）和 agent（迭代检索）两种模式。\n索引构建 (Indexing): KTRetriever 初始化时会调用 DualFAISSRetriever 构建四类 FAISS 索引：Node、Relation、Triple、Community。 问题分解 (Decomposition): models.retriever.agentic_decomposer.GraphQ 负责将复杂问题分解为子问题 (sub_questions)。 同时预测涉及的节点类型 (involved_types)，作为后续检索的过滤器。 并行检索: 对于每个子问题，系统并行执行检索。 IRCoT 迭代 (仅 Agent 模式): 进入 agent_retrieval 循环。 LLM 根据当前上下文判断信息是否充足。 若不足，生成新查询 (The new query is: ...) 再次调用检索器。 若充足，生成最终答案 (So the answer is: ...)。 6. 代码入口示例 # 系统的入口是 main.py，其逻辑结构清晰地反映了上述流程：\n# main.py (代码片段逻辑示意) if __name__ == \u0026#34;__main__\u0026#34;: # 1. 配置加载 config = get_config(args.config) # 2. 构建阶段 (Write Path) if config.triggers.constructor_trigger: # 初始化 Builder，传入 Schema builder = constructor.KTBuilder(dataset, schema_path, ...) # 执行构建 builder.build_knowledge_graph(corpus_path) # 3. 检索阶段 (Read Path) if config.triggers.retrieve_trigger: # 初始化分解器 graphq = decomposer.GraphQ(dataset, ...) # 初始化检索器 kt_retriever = retriever.KTRetriever(dataset, ...) # 构建 FAISS 索引 kt_retriever.build_indices() # 根据模式执行推理 if config.triggers.mode == \u0026#34;agent\u0026#34;: agent_retrieval(graphq, kt_retriever, ...) else: no_agent_retrieval(graphq, kt_retriever, ...) 7. 总结 # youtu-graphrag 通过四层架构解决了知识粒度问题，通过Schema 引导解决了结构化数据的质量问题，并通过Agent 闭环解决了静态图谱无法适应动态推理的问题。\n","date":"2025-12-26","externalUrl":null,"permalink":"/blogs/rag/youtu-graphrag-code-1/","section":"Blog","summary":"","title":"Youtu-GraphRAG 源码：架构蓝图与核心机制","type":"blogs"},{"content":" TencentCloudADP/Youtu-GraphRAG 1. 引言 # 在构建知识图谱（Knowledge Graph, KG）时，开发者通常面临一个两难选择：是采用 Schema-Free（开放式信息抽取，灵活性高但噪声大、实体对齐难）还是 Schema-Based（基于预定义本体，结构严谨但难以覆盖长尾知识）。\nyoutu-graphrag 在架构上选择了一种折中方案：“有界自动化” (Bounded Automation)。它以种子 Schema 作为冷启动约束，同时引入 Agent 机制允许系统在运行过程中自动发现新类型并反向更新 Schema。这种设计意在平衡结构化的规范性与非结构化数据的多样性。\n2. 种子 Schema 与数据约束 # 构建过程的起点是定义在 schemas/ 目录下的 JSON 文件。KTBuilder 类在初始化时会加载该文件作为抽取的“宪法”。\n2.1 物理结构 # 以 schemas/demo.json 为例，Schema 定义了三类核心要素：\n{ \u0026#34;Nodes\u0026#34;: [\u0026#34;person\u0026#34;, \u0026#34;organization\u0026#34;, \u0026#34;event\u0026#34;, ...], \u0026#34;Relations\u0026#34;: [\u0026#34;located_in\u0026#34;, \u0026#34;employed_by\u0026#34;, ...], \u0026#34;Attributes\u0026#34;: [\u0026#34;date\u0026#34;, \u0026#34;description\u0026#34;, \u0026#34;status\u0026#34;, ...] } 2.2 Prompt 注入 # 在 models/constructor/kt_gen.py 中，_get_construction_prompt 方法负责将内存中的 Schema 对象序列化为 JSON 字符串，并注入到 LLM 的 Prompt 中。\n# models/constructor/kt_gen.py def _get_construction_prompt(self, chunk: str) -\u0026gt; str: recommend_schema = json.dumps(self.schema, ensure_ascii=False) # ... # 将 schema 填入模板 return self.config.get_prompt_formatted(..., schema=recommend_schema, chunk=chunk) 查看 config/base_config.yaml 中的 Prompt 模板 (prompts.construction.general_agent)，可以看到系统通过自然语言指令对 LLM 施加了强约束：\n\u0026ldquo;Prioritize the following predefined schema for extraction; ```{schema}```\u0026rdquo;\n这种显式的 Schema 注入虽然能显著提高抽取结果的规范性，但在工程实践中也引入了上下文窗口压力。随着 Schema 的演进，如果节点类型增加到数百个，Prompt 的 Token 消耗将急剧上升，甚至挤占待处理文本 (chunk) 的空间。\n3. 图谱构建的核心逻辑 (Level 1 \u0026amp; Level 2) # KTBuilder 将非结构化文本转化为图结构的过程，主要体现在 process_level1_level2（非 Agent 模式）和 process_level1_level2_agent（Agent 模式）方法中。\n3.1 属性节点化 (Level 1 Attribute) # 不同于主流属性图（Property Graph）将属性作为节点内的 KV 对存储，youtu-graphrag 选择将属性升格为独立的节点。\n在 _process_attributes_agent 方法中：\n# models/constructor/kt_gen.py def _process_attributes_agent(self, extracted_attr: dict, chunk_id: int, ...): for entity, attributes in extracted_attr.items(): for attr in attributes: attr_node_id = f\u0026#34;attr_{self.node_counter}\u0026#34; self.graph.add_node( attr_node_id, label=\u0026#34;attribute\u0026#34;, # 显式标记为属性节点 properties={\u0026#34;name\u0026#34;: attr, \u0026#34;chunk id\u0026#34;: chunk_id}, level=1, # 定义为 Level 1 ) # ... 建立实体到属性的边 ... 这种设计虽然增加了图的规模（节点数量激增），但为基于属性值的跨实体关联提供了拓扑基础。例如，两个不同的实体如果都具有 \u0026ldquo;2023\u0026rdquo; 这个时间属性，它们在图中就会通过 \u0026ldquo;2023\u0026rdquo; 这个属性节点间接相连，这对于多跳推理至关重要。\n3.2 实体与关系 (Level 2 Entity/Relation) # 实体节点的构建逻辑位于 _find_or_create_entity_direct。值得注意的是，代码通过 self.lock (Threading Lock) 保护了节点计数器 self.node_counter 和图的写入操作，这表明该 Builder 设计为支持多线程并发处理 (max_workers 默认为 32)。\n4. 动态演进机制 (The Agentic Loop) # 这是该框架最复杂的特性。在 agent 模式下，系统不仅执行抽取，还执行 Schema 的自适应更新。\n4.1 演进触发 # LLM 的响应不仅包含抽取的数据，还可能包含 new_schema_types 字段。这是通过 Prompt 中的以下指令触发的：\n\u0026ldquo;If you find new and important entity types\u0026hellip; include them in a \u0026rsquo;new_schema_types\u0026rsquo; field.\u0026rdquo;\n4.2 物理回写与竞争风险 # 一旦检测到新类型，process_level1_level2_agent 会调用 _update_schema_with_new_types。\n# models/constructor/kt_gen.py def _update_schema_with_new_types(self, new_schema_types: Dict[str, List[str]]): # 1. 读取磁盘上的 Schema 文件 with open(schema_path, \u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: current_schema = json.load(f) # 2. 合并去重逻辑 (简单的字符串比对) if \u0026#34;nodes\u0026#34; in new_schema_types: for new_node in new_schema_types[\u0026#34;nodes\u0026#34;]: if new_node not in current_schema.get(\u0026#34;Nodes\u0026#34;, []): current_schema[\u0026#34;Nodes\u0026#34;].append(new_node) updated = True # 3. 实时回写磁盘 if updated: with open(schema_path, \u0026#39;w\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: json.dump(current_schema, f, ...) # 4. 更新内存对象 self.schema = current_schema 从源码分析，这里的实现存在明显的工程隐患：\n并发写冲突: 虽然内存操作有线程锁保护，但对文件系统的读写操作缺乏文件锁（File Lock）。在多进程部署（如 Gunicorn）场景下，多个 Worker 同时发现新类型并尝试回写 json 文件时，极易导致 Schema 文件损坏或更新丢失。 Schema 污染与漂移: 代码仅通过字符串是否存在来判断是否添加新类型。这意味着语义相同但词面不同的词（如 Company 和 Corporation）会被视为不同类型添加到 Schema 中。随着时间推移，Schema 可能迅速膨胀并充满冗余，导致后续的抽取 Prompt 变得混乱。 5. 总结 # youtu-graphrag 的构建模块通过 Schema 引导 确保了知识抽取的基线质量，并通过 Agent 演进 赋予了系统适应新领域的能力。\n然而，源码层面的实现揭示了其作为原型系统的特征：它依赖于简单的文件回写机制来实现状态共享。在将其推向生产环境时，架构师应当考虑引入中心化的元数据管理服务（Metadata Service）来替代文件操作，并增加“人在回路”（Human-in-the-loop）的审核机制，以防止 Agent 对 Schema 造成不可逆的污染。\n","date":"2025-12-26","externalUrl":null,"permalink":"/blogs/rag/youtu-graphrag-code-2/","section":"Blog","summary":"","title":"Youtu-GraphRAG 源码：有界自动化——Schema 引导的图谱构建与动态演进","type":"blogs"},{"content":" Youtu-GraphRAG: Vertically Unified Agents for Graph Retrieval-Augmented Complex Reasoning 1. 背景与痛点 # 图检索增强生成（GraphRAG）通过将碎片化知识组织成结构化图谱，显著增强了 LLM 在多跳推理（Multi-hop Reasoning）任务中的表现。然而，现有的 GraphRAG 方案通常面临两个主要瓶颈：\n构建与检索的割裂 (Isolation): 现有的优化往往孤立地关注图构建（如 OpenIE 的噪声问题）或图检索（如 GNN 匹配），缺乏两者的协同。构建出的图往往不适应检索需求，或者检索器无法充分利用图的结构信息。 领域泛化困难 (Domain Shift): 当应用场景从通用领域切换到垂直领域（如医疗、法律）时，预定义的提取规则或检索策略往往失效，导致性能下降。 评估的非真实性 (Knowledge Leakage): LLM 可能利用预训练记忆中的知识回答问题，而非依赖检索到的上下文，这掩盖了 RAG 系统的真实检索能力。 图1：现有 GraphRAG 流程 (a, b) 与 Youtu-GraphRAG 统一范式 (c) 的对比 为了解决这些问题，论文提出了 Youtu-GraphRAG，一种垂直统一的智能体范式 (Vertically Unified Agentic Paradigm)。其核心在于通过 图模式 (Graph Schema) 将构建、组织和检索三个阶段紧密耦合，实现了 Token 成本与推理精度的 Pareto 改进。\n图2：Youtu-GraphRAG 的全景架构图 2. 核心方法论 # Youtu-GraphRAG 的架构包含三个关键组件：Schema 引导的构建、双重感知社区检测、以及基于 Schema 的智能检索。\n2.1 Schema 引导的智能体抽取 (Schema-Bounded Agentic Extraction) # 为了解决开放式抽取（OpenIE）带来的噪声问题，该框架引入了“有界自动化”的理念。\n定义 Schema: 系统首先定义一个紧凑的种子 Schema \\(S\\)： $$ S \\triangleq \\langle S_e, S_r, S_{attr} \\rangle $$ 其中 \\(S_e\\) 为实体类型，\\(S_r\\) 为关系类型，\\(S_{attr}\\) 为属性类型。LLM 被限制在这个笛卡尔积空间 \\(S_e \\times S_r \\times S_{attr}\\) 内进行生成，从而保证了图谱的纯净度。\n动态演进 (Dynamic Evolution): 为了适应新领域，系统引入了 Schema 演进机制。Agent 会分析文档 \\(d\\) 中的关系模式，通过以下更新函数自动扩展 Schema： $$ \\Delta S = \\langle \\Delta S_e, \\Delta S_r, \\Delta S_{attr} \\rangle = \\mathbb{I}[ f_{LLM}(d, S) \\odot S] \\geq \\mu $$ 其中 \\(\\mu\\) 是置信度阈值。这意味着只有当新模式在文档中频繁且一致地出现时，才会被接纳进入 Schema。\n2.2 双重感知社区检测 (Dually-Perceived Community Detection) # 在构建基础图谱后，系统通过社区检测生成层次化知识树。传统的 Leiden 或 Louvain 算法仅基于拓扑结构，忽略了节点的语义信息。Youtu-GraphRAG 提出了一种融合 拓扑结构 (Topology) 与 子图语义 (Subgraph Semantics) 的聚类算法。\n实体表示: 首先聚合节点 \\(e_i\\) 及其一跳邻居 \\(\\mathcal{N}_i\\) 的三元组 Embedding： $$ \\mathbf{e}_i = \\frac{1}{|\\mathcal{N}_i|} \\sum_{(e_i, r, e_j) \\in \\mathcal{N}_i} [\\mathbf{e}_i \\| \\mathbf{r}_{ij} \\| \\mathbf{e}_j] $$双重感知评分: 在聚类过程中，定义节点 \\(e_i\\) 与社区 \\(C_m\\) 的亲和度评分 \\(\\phi(e_i, C_m)\\)： $$ \\phi(e_i, C_m) = \\underbrace{S_r(e_i, C_m)}_{\\text{relational}} \\oplus \\lambda \\underbrace{S_s(e_i, C_m)}_{\\text{semantic}} $$ 拓扑重叠 (\\(S_r\\)): 计算节点与社区关系的 Jaccard 相似度： $$ S_r(e_i, C_m) = \\frac{\\|\\Psi(e_i) \\cap \\Psi(C_m)\\|_2}{\\|\\Psi(e_i) \\cup \\Psi(C_m)\\|_2} $$ 语义相似度 (\\(S_s\\)): 计算节点 Embedding 与社区质心的余弦相似度。 图3：双重感知社区检测流程。(a) 初始划分；(b) 中心识别；(c) 迭代融合 最终，系统构建了一个深度 \\(L=4\\) 的知识树 \\(\\mathcal{K}\\)：\n$$ \\mathcal{K} = \\bigcup_{\\ell=1}^4 L_\\ell $$ 其中 \\(L_4\\) 为社区，\\(L_3\\) 为关键词，\\(L_2\\) 为实体-关系三元组，\\(L_1\\) 为属性。\n2.3 智能检索器 (Agentic Retriever) # 检索阶段同样利用 Schema 进行复杂问题的降维。\nSchema 增强的问题分解: 给定复杂问题 \\(q\\)，Agent 根据 Schema \\(S\\) 将其分解为原子子查询集 \\(Q = \\{q_1, q_2, \\dots, q_i\\}\\)。Schema 的存在确保了分解出的子问题中的谓词和实体类型是图谱中存在的，避免了“幻觉式”检索。\n迭代推理与反思 (Iterative Reasoning and Reflection): 检索 Agent 被形式化为元组 \\(\\mathcal{A} = \\langle S, \\mathcal{H}, f_{LLM} \\rangle\\)，其中 \\(\\mathcal{H}\\) 是历史记忆。Agent 在 Reasoning（推理）和 Reflection（反思）之间交替：\n$$ \\mathcal{A}^{(t)} = \\underbrace{f_{\\text{LLM}} \\left( q^t \\right.}_{\\text{Reasoning}} , \\underbrace{\\left. \\mathcal{H}^{(t-1)} \\right)}_{\\text{Reflection}}, $$多路检索策略: 针对不同的子查询类型，系统并行执行四种检索策略：\nEntity Matching: \\(\\arg \\max \\cos(\\mathbf{e}, \\mathbf{q}_i)\\) Triple Matching: \\(\\arg \\max \\cos((\\mathbf{e}_h, \\mathbf{r}, \\mathbf{e}_t), \\mathbf{q}_i)\\) Community Filtering: \\(\\arg \\max \\cos(\\mathbf{e}_{C_m}, \\mathbf{q}_i)\\) DFS Path Traversal: 深度优先路径遍历。 图4：Youtu-GraphRAG (中) 与传统 Embedding 匹配 (左) 及传统 Agent (右) 的对比 3. 评估创新：AnonyRAG 与匿名还原任务 # 为了解决 LLM 的数据泄露问题，论文构建了 AnonyRAG 数据集（包含中文和英文版本），并提出了 Anonymity Reversion (匿名还原) 任务。\n方法: 将数据集中的关键实体（人名、地名）替换为匿名标记（如 [PERSON#277]），迫使 LLM 无法利用预训练知识回答问题，必须完全依赖检索到的上下文线索进行推理。 意义: 这种评估方式剥离了 LLM 本身的参数化记忆，能够真实地衡量 RAG 系统的信息检索与逻辑推理能力。 4. 实验结果 # 实验在 HotpotQA, 2Wiki, MuSiQue 等 6 个基准数据集上进行。结果显示，Youtu-GraphRAG 实现了显著的性能突破：\nPareto Frontier 移动: 如图 6 所示，在保持最高准确率的同时，大幅降低了 Token 消耗（最高节省 90.71%）。 准确率提升: 相比 SOTA 基线（包括 GraphRAG, HippoRAG 等），准确率提升高达 16.62%。 泛化能力: 在“拒绝回答模式”（Reject Mode）下表现优异，证明了其在检索证据不足时能有效抑制幻觉。 图6：Youtu-GraphRAG 在成本（Token 消耗）与性能（准确率）权衡上的显著优势 5. 总结 # Youtu-GraphRAG 的核心贡献在于将 Schema 这一要素贯穿了 RAG 的全生命周期。它不仅仅是一个静态的约束，更是连接构建 Agent 和检索 Agent 的通用语言（Common Language）。配合双重感知的社区算法，该框架成功地在非结构化文本中建立起了高质量的结构化索引。\n","date":"2025-12-26","externalUrl":null,"permalink":"/blogs/rag/youtu-graphrag/","section":"Blog","summary":"","title":"Youtu-GraphRAG：垂直统一的图增强推理RAG","type":"blogs"},{"content":"","date":"2025-12-06","externalUrl":null,"permalink":"/series/latentmas/","section":"Series","summary":"","title":"LatentMAS","type":"series"},{"content":" 链接：arXiv:2511.20639 代码：GitHub 核心愿景：构建一个无需训练（Training-free）、基于潜在空间（Latent Space）进行直接思维传输的多智能体系统。\n本文将深入 LatentMAS 的核心代码逻辑，解析其如何通过 methods/ 和 models.py 实现论文中的分布对齐、潜在生成及记忆传输，并着重分析工程实现中针对不同后端（HuggingFace vs vLLM）所做的策略调整与理论偏差。\n1. 核心基础设施：分布对齐 (\\(W_a\\)) 的数值解法 # 论文核心痛点之一是 Latent Space 与 Input Embedding Space 分布不一致。论文提出通过线性投影 \\(W_a\\) 进行对齐。\n代码位置: models.py -\u0026gt; ModelWrapper._build_latent_realign_matrix\ndef _build_latent_realign_matrix(self, model, device, args) -\u0026gt; Tuple[torch.Tensor, torch.Tensor]: # ... 获取输入输出层权重 ... # 构建岭回归 (Ridge Regression) 正规方程: (A^T A + lambda*I) X = A^T B # 其中 A = output_weight, B = input_weight # 1. 计算 Gram 矩阵 (A^T A) gram = torch.matmul(output_weight.T, output_weight) # 2. 添加正则化项 (Ridge term) 对应论文附录 A.1 公式 (8) 中的 lambda reg = 1e-5 * torch.eye(gram.shape[0], device=gram.device, dtype=gram.dtype) gram = gram + reg # 3. 计算 A^T B rhs = torch.matmul(output_weight.T, input_weight) # 4. 求解线性方程组得到 Wa realign_matrix = torch.linalg.solve(gram, rhs) # ... 计算 target_norm 用于后续缩放 ... return realign_matrix, target_norm 源码与论文对照：\n一致性：代码完全复现了论文公式 (3) 及附录 A.1 的推导。它没有简单求逆，而是求解正规方程，且显式引入了 1e-5 的正则化项（论文中的 \\(\\lambda\\)）以保证数值稳定性。 细节补充：代码额外引入了 target_norm，在对齐后对向量模长进行归一化。这是论文未详细展开但对工程稳定性至关重要的细节，防止潜在向量在多次循环后数值爆炸或消失。 2. 内部循环：自回归潜在思维生成 # 这是智能体“默念”的过程，跳过 Token 解码，直接在向量层面推理。\n代码位置: models.py -\u0026gt; ModelWrapper.generate_latent_batch\n# 核心潜在生成循环 for step in range(latent_steps): # 1. 对齐映射: h_t -\u0026gt; e_{t+1} latent_vec = self._apply_latent_realignment(last_hidden, source_model) latent_embed = latent_vec.unsqueeze(1) # 构造 [Batch, 1, Hidden] # 2. 前向传播 (Forward Pass) # 关键点：直接传入 inputs_embeds 绕过 Embedding 层 outputs = self.model( inputs_embeds=latent_embed, past_key_values=past, # 传入当前的 KV Cache use_cache=True, # ... ) # 3. 状态更新 past = outputs.past_key_values last_hidden = outputs.hidden_states[-1][:, -1, :] # 获取最后一个 Token 的隐状态 源码与论文对照：\n一致性：完全符合论文 Section 3.1 的描述。循环中没有 softmax 或 argmax 操作，纯粹是 Hidden State 到 Hidden State 的流转。 实现机制：利用 HF Transformers 的 inputs_embeds 参数实现了这一逻辑，这是一种标准的“侵入式”推理控制手段。 3. 脑机接口：思维传输的双重实现路径 # 这是整个框架中最核心的交互逻辑，但在代码中根据后端不同，出现了两条不同的路径。\n路径 A：标准 HuggingFace 模式 (理论的完美复现) # 代码位置: methods/latent_mas.py -\u0026gt; LatentMASMethod.run_batch\n# 遍历智能体链 (Planner -\u0026gt; Critic -\u0026gt; ...) for agent in self.agents: if agent.role != \u0026#34;judger\u0026#34;: # ... # 生成潜在思维，并接收更新后的 past_kv past_kv = self.model.generate_latent_batch( wrapped_ids, ... past_key_values=past_kv, # \u0026lt;--- 核心：直接继承上一个智能体的 KV Cache ) # ... else: # Judger 智能体使用累积的 past_kv 进行最终文本解码 generated_batch, _ = self.model.generate_text_batch( ..., past_key_values=past_for_decoding, ) 解读： 此路径严格对应论文 Section 3.2 的 Theorem 3.3 (Lossless Information Transfer)。智能体之间传递的是 past_key_values 对象（显存中的张量）。\n计算复杂度：\\(O(1)\\)。接收方无需处理历史信息，直接在现有 Cache 上追加计算。 信息保真度：100% 无损。 路径 B：vLLM 混合模式 (工程上的妥协与变通) # 代码位置: methods/latent_mas.py -\u0026gt; LatentMASMethod.run_batch_vllm\n由于 vLLM 的 PagedAttention 机制难以从外部直接注入非标准的 KV Cache，代码实现出现了显著的策略调整。\n# 1. 使用 HF 模型计算潜在思维的 Embedding (而非 KV) past_kv, previous_hidden_embedding = self.model.generate_latent_batch_hidden_state(...) # 记录生成的向量 embedding_record.append(previous_hidden_embedding) # ... 在 Judger 阶段 ... # 2. 拼接历史 Embedding past_embedding = torch.cat(embedding_record, dim=1).to(self.vllm_device) # 3. 构造 \u0026#34;软提示\u0026#34; (Soft Prompt)：将潜在向量拼接到文本 Embedding 中间 whole_prompt_emb = torch.cat([left_emb, past_embedding[i], right_emb], dim=0) # 4. 传给 vLLM 进行生成 (vLLM 会对这些 Embedding 进行 Prefill 计算) outputs = self.model.vllm_engine.generate( [{\u0026#34;prompt_embeds\u0026#34;: embeds} for embeds in whole_prompt_emb], ... ) ⚠️ 关键差异分析：\n理论偏差：论文声称“传输 KV 以避免冗余重计算 (avoid redundant recomputation)”。但在 vLLM 模式下，传输的是 Embedding。vLLM 接收到这些 Embedding 后，必须在内部进行一次完整的 Prefill（预填充） 计算来生成 KV Cache。 工程考量：虽然违背了“无重算”的理论纯度，但利用 vLLM 极度优化的 Attention Kernel，这种“重算”在实际 Wall-clock time 上依然比 HF 的“不重算”要快得多。这是一种用计算量换取利用高性能算子机会的工程策略。 4. 上下文管理与 Prompt 依赖 # 4.1 记忆截断 (Truncation) # 代码位置: methods/latent_mas.py -\u0026gt; _truncate_past\n论文主要描述了累积式的思维增长，但代码中增加了一个控制机制：\nif self.sequential_info_only or self.latent_only: # 仅保留新增的 latent steps 对应的 KV，丢弃之前的历史 past_kv = self._truncate_past(past_kv, tokens_to_keep) 这表明 LatentMAS 支持一种“遗忘模式”，只传递最新的思维切片。这是论文中未详细讨论的显存优化手段，防止长链条协作时 KV Cache 无限增长。\n4.2 显式的 Prompt 引导 # 代码位置: prompts.py\n虽然 LatentMAS 强调“潜在协作”，但代码显示它高度依赖自然语言 Prompt 来配置智能体状态：\n# Judger 的 Prompt user_prompt = \u0026#34;\u0026#34;\u0026#34; You are provided with latent information for reference... The latent information might contain irrelevant contents. Ignore it if it is not helpful... \u0026#34;\u0026#34;\u0026#34; 这意味着，\\(W_a\\) 的数学对齐只是必要条件，还需要通过 Prompt Engineering 明确告知模型“你将接收一段非自然语言的潜在输入”，模型才能正确处理这些向量。这补充了论文中关于系统构建的隐含前提。\n5. 总结 # LatentMAS 的源码实现通过 ModelWrapper 精巧地封装了底层张量操作。\n对于算法验证：Standard HF 模式是论文理论的忠实实现，实现了真正的 KV 零拷贝传输。 对于高性能部署：vLLM 模式采用了一种“Embedding 拼接 + 重新 Prefill”的变通方案。虽然在机制上与论文强调的“无重算”有出入，但它成功解决了 vLLM 架构兼容性问题。 ","date":"2025-12-06","externalUrl":null,"permalink":"/blogs/agent/latent-collaboration-in-multi-agent-systems-code/","section":"Blog","summary":"","title":"LatentMAS 源码分析","type":"blogs"},{"content":" 链接：arXiv:2511.20639 代码：GitHub TL;DR # 核心问题：现有的基于 LLM 的多智能体系统（MAS）依赖自然语言（文本）进行通信，导致信息丢失（连续转离散）、低效（解码/编码开销大）以及高计算复杂度。 核心贡献： LatentMAS 框架：提出首个完全在连续潜在空间（Latent Space）内进行推理和通信的免训练多智能体框架。 关键机制： 潜在思维生成：自回归生成最后一层隐藏状态而非 Token。 线性对齐算子：通过 \\(W_a\\) 矩阵解决潜在状态与输入嵌入的分布对齐问题。 潜在工作记忆传输：通过 KV Cache 的直接拼接实现无损信息传递。 理论支撑：证明了潜在思维比文本具有更高的表达能力（Theorem 3.1），且通信是无损的（Theorem 3.3），计算复杂度显著降低（Theorem 3.4）。 实证结果：在 9 个基准测试中，准确率提升高达 14.6%，Token 减少约 80%，推理速度提升 4 倍以上。 1. 痛点：Token 是多智能体协作的“带宽杀手” # 在现有的多智能体系统（MAS）中，自然语言（Text）被视为“通用语（Lingua Franca）”。然而，从信息论的角度看，这是一种极度低效的压缩方式。\n信息有损：Transformer 内部的高维隐藏状态（Hidden States, \\(h \\in \\mathbb{R}^{d_h}\\)）包含了丰富的概率分布和语义细微差别，但在输出时被迫坍缩为离散的 Token ID。 计算冗余：智能体 A 生成 Token 需要解码，智能体 B 接收 Token 需要重新编码（Prefill）。 LatentMAS 的核心直觉：如果智能体之间“脑波（Latent States）”相通，我们就不需要笨重地“说话”了。\n2. 核心架构：LatentMAS 的技术实现 # LatentMAS 是一个端到端、免训练的框架。如下图所示，它将协作过程完全下沉到了潜在空间。\n2.1 架构概览 # Figure 3: LatentMAS 架构概览。左侧展示了单个智能体的潜在思维生成，右侧展示了跨智能体的潜在工作记忆传输。 Latent Reasoning（潜在推理）：智能体不再输出 Token，而是让最后一层的 Hidden State 在模型内部自回归循环。 Latent Communication（潜在通信）：智能体 A 的 KV Cache 直接被“嫁接”到智能体 B 的注意力层中。 2.2 关键技术一：自回归潜在思维与分布对齐 (Distribution Alignment) # 这是论文中最具数学美感的部分。模型直接将 \\(t\\) 时刻的输出 \\(h_t\\) 作为 \\(t+1\\) 时刻的输入。\n挑战：输出嵌入空间（Output Space）和输入嵌入空间（Input Space）的分布并不一致（Distribution Shift）。直接回环会导致 OOD（Out-of-Distribution）问题。\n解决方案：引入线性对齐算子 \\(W_a\\)。 作者并不重新训练模型，而是通过数学方法寻找一个投影矩阵 \\(W_a \\in \\mathbb{R}^{d_h \\times d_h}\\)，使得输出向量 \\(h\\) 映射回输入空间 \\(e\\)：\n$$ e = h W_a, \\quad \\text{其中} \\quad W_a \\approx W_{\\text{out}}^{-1} W_{\\text{in}} $$由于 \\(W_{\\text{out}}\\) 通常不可逆（非方阵或奇异），作者在附录 A.1 中指出，通过 岭回归（Ridge Regression） 求解该问题，引入正则化项 \\(\\lambda\\) 以保证数值稳定性：\n$$ W_a = (W_{\\text{out}}^\\top W_{\\text{out}} + \\lambda I)^{-1} W_{\\text{out}}^\\top W_{\\text{in}} $$ Figure 6: 对齐效果可视化 (MedQA) 未对齐 (\\(h_t\\))：灰色区域。可以看到生成的隐藏状态偏离了原始 Token Embedding 的流形分布。 对齐后 (\\(e_{t+1}\\)) / 真实输入 (\\(e_t\\))：绿色与橙色区域。经过 \\(W_a\\) 投影后，潜在向量完美重合回了合法的输入分布空间。 结论：这一步是 LatentMAS 免训练且能稳定推理的数学基石。 2.3 关键技术二：无损工作记忆传输 (KV Cache Transfer) # 在 LatentMAS 中，智能体 \\(A_1\\) 的输出不是文本，而是其计算过程中产生的所有层的 KV Cache 集合 \\(\\mathcal{M}_{A_1}\\)：\n$$ \\mathcal{M}_{A_1} = \\left\\{ (K_{A_1, \\text{cache}}^{(l)}, V_{A_1, \\text{cache}}^{(l)}) \\mid l = 1, \\dots, L \\right\\} $$智能体 \\(A_2\\) 接收这些记忆时，直接将其拼接到自己的 Cache 中。这不仅实现了零开销的信息读取，更重要的是实现了无损信息传递。\n3. 理论深度：为什么“向量”优于“文本”？ # 论文给出了两个强有力的定理来支撑这一架构的优越性。\n定理 3.1：潜在思维的表达能力 (Expressiveness) # 假设存在线性表示假设，如果用文本无损地表达长度为 \\(m\\) 的潜在思维，所需的文本长度至少为：\n$$ \\text{Length}_{\\text{text}} = \\Omega\\left( \\frac{d_h \\cdot m}{\\log |\\mathcal{V}|} \\right) $$ 参数解读：\\(d_h\\) 是隐藏层维度（如 4096），\\(|\\mathcal{V}|\\) 是词表大小（如 150k）。 推论：由于 \\(d_h\\) 很大，1 个潜在步（Latent Step）承载的信息量相当于数百个 Token。这就是为什么 LatentMAS 能用极少的步数完成复杂推理。 定理 3.4：复杂度分析 (Complexity) # 基于文本的 MAS 复杂度受制于漫长的 Token 生成过程。LatentMAS 的时间复杂度显著降低：\nTextMAS 复杂度：\\(\\approx O(d_h^3 m^2 / \\log^2 |\\mathcal{V}|)\\) （受限于等效文本长度的平方） LatentMAS 复杂度：\\(O((d_h^2 m + d_h m^2)L)\\) 4. 实验数据 # 实验在 9 个基准测试上进行，涵盖数学 (GSM8K, AIME)、科学 (GPQA) 和代码 (HumanEval)。\n4.1 核心性能表 # 下表展示了在 MAS 设置下，LatentMAS 对比单模型和 TextMAS 的提升（以 Qwen3-14B 为例）：\n数据解读：\n极速推理：在 ARC-E 任务上，LatentMAS 比 TextMAS 快了 4.3倍。 极度省 Token：在 GSM8K 上，Token 消耗减少了 80.6%。这是因为智能体间的通信不消耗 Output Token。 能力增强：在代码任务 HumanEval+ 上，准确率显著提升 5.4%，证明了潜在空间协作能激发更强的推理能力。 4.2 案例分析：纠错能力的本质 # TextMAS on GSM8K LatentMAS on GSM8K 场景：GSM8K 数学题，需要多步计算。 TextMAS 失败原因：Planner 输出了包含细微逻辑错误的文本。Solver 只能基于这段错误的文本继续推理，导致错误传播 (Error Propagation)。 LatentMAS 成功原因：由于传递的是高维 KV Cache，Solver 接收到的是 Planner 的“原始思维流”。即使 Planner 的初步倾向有误，Solver 也能利用其强大的解码头和自身推理能力，在潜在空间中重构（Re-interpret） 正确的逻辑路径，从而输出正确答案。 5. 总结与批判性思考 # 5.1 创新点总结 # 打破模态壁垒：首次证明了多智能体协作不需要“翻译”成人类语言，直接交换“脑电波”更高效。 数学保证：通过 \\(W_a\\) 投影解决了潜在空间游走（Drift）的问题，提供了坚实的理论支撑。 工程友好：不需要微调模型，完全兼容现有的 KV Cache 机制（如 vLLM），即插即用。 5.2 局限性与挑战 (Critical Review) # 作为审稿视角的补充，本文也存在以下限制：\n同构约束 (Homogeneity Constraint)： 目前 LatentMAS 强依赖于 KV Cache 的直接拼接。这意味着协作的智能体必须拥有完全相同的 Transformer 架构（层数 \\(L\\)、维度 \\(d_h\\)、头数 \\(H\\) 必须一致）。你无法让 Qwen-72B 指导 Qwen-7B，也无法让 GPT-4 与 Llama-3 潜在协作。这是目前最大的落地阻碍。 黑盒调试困难 (The Black Box)： 在 TextMAS 中，我们可以看 Log 知道 Planner 说了什么蠢话。但在 LatentMAS 中，中间交互全是浮点数矩阵。如果系统出错，人类难以介入调试。 通信带宽压力： 虽然节省了 Token 生成计算，但在分布式系统中传输 KV Cache（尤其是长上下文时）带来的显存带宽（VRAM Bandwidth）和网络带宽压力，远大于传输几百个文本 Token。这更适合单机多卡环境，而非跨节点的云端 API。 LatentMAS 是 Agent 系统向“原生 AI 协作（Native AI Collaboration）”迈出的关键一步，它牺牲了可解释性，换取了极致的效率和表达力。\n","date":"2025-12-06","externalUrl":null,"permalink":"/blogs/agent/latent-collaboration-in-multi-agent-systems/","section":"Blog","summary":"","title":"LatentMAS：当大模型学会“脑对脑”直接通信","type":"blogs"},{"content":"","date":"2025-12-06","externalUrl":null,"permalink":"/tags/latentspace/","section":"Tags","summary":"","title":"LatentSpace","type":"tags"},{"content":"","date":"2025-12-06","externalUrl":null,"permalink":"/tags/multi-agent/","section":"Tags","summary":"","title":"Multi-Agent","type":"tags"},{"content":"","date":"2025-12-06","externalUrl":null,"permalink":"/tags/%E6%BA%90%E7%A0%81/","section":"Tags","summary":"","title":"源码","type":"tags"},{"content":"","date":"2025-11-07","externalUrl":null,"permalink":"/tags/contextengineering/","section":"Tags","summary":"","title":"ContextEngineering","type":"tags"},{"content":" Context Engineering 2.0: The Context of Context Engineering\nContext Rot: How Increasing Input Tokens Impacts LLM Performance\n当前对大型语言模型（LLM）长上下文能力的研究，普遍集中于如何将上下文窗口扩大到数百万 Token，或如何优化自注意力机制的 \\(O(N^2)\\) 复杂度。然而，这篇由 Hua 等人撰写的《Context Engineering 2.0: The Context of Context Engineering》论文，提出了一个更基础的理论框架：**上下文工程（Context Engineering）的本质是一个跨越历史的熵减（Entropy Reduction）**过程。它不仅将当前 LLM 的实践与人机交互（HCI）的深厚历史相连接，更通过系统化的架构原则，为解决长上下文带来的内在性能衰减提供了工程指导。\n一、理论基石：上下文与上下文工程的形式化定义 # 论文首先明确了上下文工程的理论根基，为这一领域提供了一套形式化语言，避免了对“上下文”的模糊理解。\n1. 上下文的集合论定义 # 论文借鉴并形式化了 Anind K. Dey（2001b）的经典定义。它将上下文 \\(C\\) 定义为所有与特定用户-应用交互相关的实体 \\(\\mathcal{E}_{rel}\\) 的表征信息的并集：\n$$ C = \\bigcup_ {e \\in \\mathcal {E} _ {r e l}} \\operatorname {C h a r} (e) \\tag {2} $$其中：\n\\(e\\) 是实体（用户、应用、环境、工具、记忆模块等）。 \\(\\operatorname{Char}(e)\\) 返回描述实体 \\(e\\) 的信息集合（如用户输入、系统指令、当前工作目录）。 \\(\\mathcal{E}_{rel}\\) 是与当前交互相关的实体集合。 这一定义强调了上下文的整体性（Holistic），它远超于对话历史或系统提示的范畴，涵盖了环境、设备、状态等多个维度，是对当前狭隘实践的直接修正。\n2. 上下文工程的转化函数 # 上下文工程被定义为一个系统性的过程，其目标是增强机器理解和任务性能。在形式上，它被视为一个转化函数 \\(CE\\)，将原始上下文 \\(C\\) 和目标任务 \\(\\mathcal{T}\\) 映射为一个优化的上下文处理函数 \\(f_{\\text{context}}\\)：\n$$ C E: (C, \\mathcal {T}) \\rightarrow f _ {\\text {context}} \\tag {3} $$$$ f _ {\\text {context}} (C) = \\mathcal {F} \\left(\\phi_ {1}, \\phi_ {2}, \\dots , \\phi_ {n}\\right) (C) \\tag {4} $$\\(f_{\\text{context}}\\) 本身是各种具体操作（如收集、存储、选择、抽象）的灵活组合，体现了上下文工程是一项有目的、主动介入的工程活动。\n表1：在代表性维度上比较上下文工程1.0和2.0 3. 熵减是核心驱动力 # 论文将上下文工程的核心使命定义为熵减：人类意图是高熵的，机器需要低熵的表示。因此，上下文工程的“努力”在于将高熵的人类意图转化为机器可理解的低熵表示。这种努力的成本是衡量人机交互效率的关键，并随着机器智能的提升而逐渐从人向机器转移，构成了论文四阶段模型（Era 1.0 到 Era 4.0）演进的根本逻辑。\n二、实证批判：Context Rot 与长上下文的内在脆弱性 # 论文强调了上下文管理的重要性，这一观点得到了现代 LLM 性能衰减的实证支持。最新的研究报告《Context Rot: How Increasing Input Tokens Impacts LLM Performance》提供了一个关键的实证验证，即简单地堆叠上下文本身就会系统性地损害模型性能。\n传统误区：行业长期以来以 LLM 在如 NIAH（Needle in a Haystack）等测试中的高分，来标榜其长上下文能力。 问题诊断：Context Rot 指出，这些测试往往是简单的词汇匹配，难以反映模型在进行真实语义推理、事实整合或遵循复杂指令时的鲁棒性。 关键发现：实证实验表明，即使在上下文窗口容量未满的情况下，随着输入长度的增加，模型的性能仍会系统性、非均匀性地衰减。这种衰减是由于海量 Token 引入了噪音和分散注意力，降低了输入上下文的信噪比。 Context Rot 的发现，从经验层面佐证了《Context Engineering 2.0》的理论需求：长上下文窗口并非银弹。单纯的容量扩张并不能解决信息熵的问题。当高熵的、未经处理的上下文被堆叠时，它会压倒模型的注意力机制，导致推理的脆弱。因此，上下文工程——即对上下文的主动降熵——成为确保系统鲁棒性的必要条件。\n三、战略响应：鲁棒的上下文管理架构 # 为应对长上下文带来的挑战，论文提出了从收集、管理到使用的系统性策略，其核心在于建立分层（Layered）、抽象（Abstracted）、**隔离（Isolated）**的记忆架构。\n1. 分层记忆与结构化存储 # 上下文的存储必须遵循 “最小充分原则”，并根据时间尺度和重要性进行分层管理：\n短期记忆 (\\(M_s\\))：具备高时间相关性，通常是当前的对话历史或上下文窗口。\n$$ M _ {s} = f _ {\\text {short}} \\left(c \\in C: w _ {\\text {temporal}} (c) \u003e \\theta_ {s}\\right) \\tag {5} $$其中 \\(w_{\\text{temporal}}(c)\\) 是时间相关性权重，\\(\\theta_s\\) 是短期记忆阈值。\n长期记忆 (\\(M_l\\))：是经过处理和抽象、具备高重要性的上下文，通常存储在外部数据库（如向量数据库、结构化文件系统）。\n$$ M _ {l} = f_{\\text {long}} \\left(c \\in C: w_{\\text {importance}} (c) \u003e \\theta_{l} \\wedge w_{\\text {temporal}} (c) \\leq \\theta_ {s}\\right) \\tag {6} $$\\(w_{\\text{importance}}(c)\\)是重要性权重。\n记忆转移 (\\(f_{\\text{transfer}}\\))：连接两层记忆的机制，即“自烘焙”（Self-Baking）过程，用于将短期的、频繁访问的信息固化为长期知识。\n2. 自烘焙（Self-Baking）与信息抽象 # “Context Rot”的实证研究表明，原始、未经处理的上下文会作为性能衰减的噪声源。为解决这一根本问题，论文引入了一个关键的上下文管理机制：自烘焙（Self-Baking）。\n图6：Era 2.0时代 Self-Baking 的代表性设计 Self-Baking是一个系统性的过程，智能体通过该过程，主动地、选择性地将其自身的原始、高熵上下文，转化为紧凑、结构化、低熵的知识表示。\n这个过程并非简单的信息压缩，而是知识的提炼与固化。它在概念上类似于人类的认知过程：我们将日常经历的、琐碎的情景记忆（Episodic Memory，如“昨天下午我和张三讨论了项目A的预算问题”），逐步巩固为更稳定、更普适的语义记忆（Semantic Memory，如“项目A的预算非常紧张”）。\n从工程角度看，“Self-Baking”是实现论文核心理论——“熵减”——的主要实践手段。它主动地管理信息信噪比，确保智能体在与环境的长期交互中，能够构建一个稳定、可用、不断演进的知识库，而不是被自身不断膨胀的原始记忆所淹没。\n这一过程并非单一方法，而是可以通过多种架构模式实现，每种模式在结构、灵活性和计算成本上都有不同的权衡：\n抽象模式 描述 优势与挑战 典型案例 自然语言摘要 定期对长上下文生成简洁的自然语言概述。 优势： 简单灵活。\n挑战： 摘要本身仍是非结构化文本，难以支持深度推理。 任务日志、对话总结 结构化 Schema 提取 将关键信息编码到预定义结构（如任务树、实体图、事件记录）中。 优势： 极利于逻辑推理和关系查询。\n挑战： 需设计复杂提取器，维护多层表示时可能出现不一致。 CodeRabbit 的结构化 Case File 向量压缩/融合 将原始上下文编码为密集向量（Embedding），并通过池化等操作将旧向量融合成更抽象的语义表示。 优势： 紧凑、灵活，利于语义搜索。\n挑战： 人类不可读，难以编辑或检查。 层次化向量记忆系统 最终，“Self-Baking”标志着一个根本性的范式转变：它将工程焦点从被动地在不断扩大的上下文窗口中累积数据，转向主动地将信息构建为一个持久、演进的知识库。这是使智能体能够从经验中学习、保持长期任务连贯性，并扩展其推理能力而免于被自身历史淹没的关键所在。\n3. 上下文隔离与协调 # 在单一、扁平的上下文中，所有信息——无论其来源、重要性或时间相关性——都被混合在一起。这种架构直接导致了论文所警示的**“上下文污染”（Context Pollution）**，这也是“Context Rot”现象背后的关键驱动因素之一。上下文污染指的是不相关的历史信息、工具输出或并行任务的细节，干扰了当前任务的执行，导致模型注意力分散、产生逻辑错误，并无谓地消耗计算资源。\n为解决此问题，上下文工程必须引入架构性约束。上下文隔离（Context Isolation）正是这样一种核心的架构原则，它通过建立明确的边界来主动管理信息流，从根本上防止污染。一旦系统由多个隔离的单元（如不同的智能体或子智能体）组成，它们之间的**协调（Coordination）**就成为新的核心挑战。\n论文中的 Figure 7（如下图所示）为跨智能体的上下文共享模式提供了一个清晰的分类框架，展示了从高熵、脆弱的通信到低熵、鲁棒的协调机制的演进路径。\nFig 7: 跨智能体上下文共享的通用模式 模式 A: 将上下文嵌入提示（Embedding Context Into Prompts）\n这是最基础、最直接的通信方式。一个智能体的输出（通常是自然语言形式的总结或思考过程）被直接拼接或嵌入到下一个智能体的输入提示中。\n机制：Agent 1 Output → Prompt → Agent 2 Input 熵与鲁棒性：这种方式的熵值最高。自然语言的内在歧义性、缺乏结构，以及对上下文长度的快速消耗，都使其成为一种**脆弱的（Brittle）**通信协议。接收方智能体需要付出额外的认知努力去解析和理解非结构化的输入，极易产生误解，导致任务链的失败。 适用场景：适用于简单的、线性的、短链条的任务流，其中智能体间的交互信息量小且无歧义。 模式 B: 交换结构化消息（Structured Messages Exchange）\n为克服模式 A 的脆弱性，此模式引入了预定义的通信协议。智能体之间不再传递自由文本，而是交换遵循特定 Schema 的结构化消息。\n机制：Agent 1 → Message(task_type, inputs, outputs, ...) → Agent 2 熵与鲁棒性：通过强制执行 Schema（如 JSON），该模式显著降低了通信的熵。信息被组织在明确的字段中，消除了歧义，使通信内容机器原生可读。这大幅提升了系统的鲁棒性和可预测性，因为接口是明确且稳定的。 适用场景：适用于需要清晰、可靠信息交接的协作任务，是大多数生产级多智能体系统的基础通信方式。 模式 C: 使用共享内存进行间接通信（Shared Memory for Indirect Communication）\n这是最先进、最灵活的协调模式。智能体之间不进行直接点对点通信，而是通过读写一个共享的中央数据存储区来异步协作。这种模式进一步降低了系统耦合度。Figure 7 将其细分为三种复杂程度递增的实现：\nC1. 内存块（Memory Blocks）：最简单的共享内存形式，可以看作是一个共享的日志或便笺（Scratchpad）。智能体将条目（Entry）写入，其他智能体按需读取。它实现了基础的异步通信，但信息本身缺乏组织。 C2. 结构化黑板（Structured Blackboard）：这是内存块的演进。共享内存被划分为不同的区域或主题（如“待办任务”、“已完成分析”、“关键发现”）。智能体只订阅和操作与其职责相关的分区，实现了更高层次的组织性和更低的协调复杂性。 C3. 基于图的内存（Graph-based Memory）：这是最复杂的形态。上下文被组织为一个知识图谱，其中节点代表任务、实体或知识点，边代表它们之间的逻辑关系（如“依赖于”、“细化了”、“因果关系是”）。这种方式提供了对复杂协作过程最高保真度的、最低熵的表示，因为它明确编码了信息之间的深层逻辑依赖关系，而无需 LLM 从扁平文本中进行推断。 三种模式的比较\n协调模式 通信风格 熵水平 鲁棒性 耦合度 核心优势 A. 提示嵌入 同步 / 直接 高 低 紧密 实现简单，适用于短链条任务 B. 结构化消息 同步 / 直接 中 中 中 通信明确，接口稳定，可靠性高 C. 共享内存 异步 / 间接 低 高 松散 解耦智能体，支持复杂、动态的协作 综上所述，从模式 A 到模式 C 的演进，不仅是技术复杂度的提升，更体现了上下文工程在架构层面的成熟。它标志着智能体系统的设计从临时的、点对点的“对话”，转向了持久的、系统化的“协作”。通过构建坚实的“信息防火墙”（隔离）和高效的“通信总线”（协调），为构建可扩展、可维护且真正鲁棒的复杂AI系统提供了必要的结构性保障。\n四、语义操作系统与未解的难题 # 论文将上下文工程的终极目标定为构建一个能够管理和演化个体一生数字痕迹的**“语义操作系统”，并提出了“数字存在”（Digital Presence）的宏伟愿景。这要求系统具备类人的记忆管理能力：主动添加、修改，甚至进行“智慧的遗忘”**。\n然而，这种愿景同时指向了当前研究中被严重低估的挑战：\n量化与可解释性挑战：在实践层面，如何精确量化抽象（Self-Baking）带来的“熵减”效益？如何构建一个能够追溯、校验、甚至“撤销”错误记忆的系统，以确保推理链的可靠性？ 伦理与安全挑战：一个拥有个体所有上下文的“语义操作系统”将构成人类历史上最强大的数据集合。其隐私、安全、以及被用于操纵个体意图的潜在风险是巨大的。论文对这一点的讨论略显不足，无法构成对未来系统设计者的充分警示。 可持续性挑战：存储和持续处理终身数据所需的计算资源和能源成本是天文数字。在当前的硬件和经济模型下，“终身上下文工程”在很大程度上仍然是一个缺乏可持续性的概念。 结语：上下文工程的时代 # 《Context Engineering 2.0》是一份关键性的路线图。它“纠正”了 LLM 时代将“上下文”物化为“Token 数量”的工程偏误，并将问题重新聚焦于信息的本质（高熵 vs. 低熵）。\n无论是 Context Rot 的实证研究，还是 \\(O(N^2)\\) 的算法瓶颈，都指向了同一个结论：智能体的鲁棒性和可扩展性，也许不再取决于底层模型的原始能力，而是在于外部上下文管理架构的设计质量。\n对于开发者而言，构建高级 AI 智能体的关键，不在于无止境地扩大 token 限制，而在于设计出精巧、高效、多层次的上下文管理架构——从多模态的整体性收集，到分层存储，再到以“Self-Baking”为核心的降熵抽象，最终实现多智能体间结构化的、高信噪比的信息交换。\n","date":"2025-11-07","externalUrl":null,"permalink":"/blogs/agent/context-engineering-2.0/","section":"Blog","summary":"","title":"构建鲁棒的 AI 系统：Context Engineering 2.0中的分层记忆、隔离与协调","type":"blogs"},{"content":" 参考链接：claude-skills-blog、agents-kills、claude-skills-deep-dive、Github Agent Skills（智能体技能）不仅仅是大语言模型（LLM）的工具扩展，它代表了一种基于 Prompt 的元工具（Meta-Tool）架构。该规范旨在通过 渐进式披露（Progressive Disclosure） 机制，解决通用大模型在垂直领域应用中面临的上下文窗口限制、指令复杂性与执行确定性三大挑战。本文将从架构设计、文件规范、执行逻辑以及与现有技术栈（MCP, Function Calling）的对比四个维度进行系统分析。\n一、 核心设计理念：渐进式披露 (Progressive Disclosure) # Agent Skills 的核心工程价值在于上下文管理（Context Management）。传统的 Prompt 工程倾向于一次性将所有规则灌入上下文，导致 Token 消耗巨大且模型注意力分散。Skills 架构采用漏斗模型（如图中顶部所示），将信息加载分为三个层级：\nMetadata 层（轻量加载）\n内容：仅包含技能的名称（Name）和简述（Description）。 机制：在系统启动或对话初期，系统仅将这部分极少量的文本加载到 LLM 的上下文中。 目的：使 LLM 能够以最小的 Token 消耗“感知”到成百上千种技能的存在，并基于语义判断是否需要调用。 Instructions 层（按需加载）\n内容：SKILL.md 中的完整 Markdown 指令、Prompt 模板、SOP（标准作业程序）。 机制：Lazy Loading（懒加载）。只有当 LLM 决定调用特定技能时，系统才读取该层内容并注入到当前的对话上下文中。 目的：将通用 LLM 临时转化为特定领域的“专家”，而在任务结束后释放上下文。 Scripts \u0026amp; Assets 层（执行加载）\n内容：Python/Bash 脚本、大型参考文档、模板文件。 机制：仅在具体执行步骤中，通过工具调用或路径引用来访问。 目的：处理高密度信息或确定性逻辑，避免无关数据污染上下文。 二、 规范解剖：目录结构与文件定义 # Agent Skills 采用标准化的目录结构来封装能力，使其具备可移植性和复用性。\n2.1 目录结构标准 # 一个标准的 Skill 包（my-skill/）包含以下组件：\nSKILL.md (Core/Mandatory)：技能的核心定义文件，承载元数据与指令。 /scripts (Optional)：存放可执行代码（Python, Bash, JS 等）。用于承载逻辑运算、数据清洗等确定性任务。 /references (Optional)：存放领域知识文档（Markdown/JSON）。供 LLM 在运行时阅读（RAG source）。 /assets (Optional)：存放静态资源（HTML 模板、CSV 样本、图片）。通常用于生成最终交付物。 2.2 SKILL.md：双重定义的载体 # SKILL.md 采用了 YAML Frontmatter 与 Markdown Body 结合的格式，分别服务于系统的路由与模型的推理。\nYAML Frontmatter (定义层 - Define)\n面向对象：路由系统 / 宿主程序。 关键字段： name: 技能调用的唯一标识符（ID）。 description: 用于语义路由的描述文本，决定了 LLM 何时触发该技能。 version: 版本控制。 作用：作为“索引”存在于上下文的 Metadata 层。 Markdown Body (指南层 - Guide)\n面向对象：LLM (Claude/GPT)。 关键内容：自然语言编写的步骤说明、约束条件、Few-Shot 示例。 作用：当技能被激活时，这部分内容被“展开”并注入 Prompt，指导 AI 如何一步步完成任务。 三、 运行机制：概率与确定性的融合 # LLM 本质是概率模型，擅长意图理解和规划，但不擅长精确计算和逻辑执行。Skills 架构通过 “LLM Brain + Deterministic Script” 的模式解决了这一矛盾。\n3.1 确定性逻辑 (Deterministic Logic) 的卸载 # 流程如下：\n触发 (Trigger)：LLM 规划出任务路径，决定执行某个具体步骤（如“计算销售额同比增长”）。 委托 (Delegate)：LLM 不直接进行计算（防止幻觉），而是调用 /scripts 目录下的 Python 脚本（如 data_proc.py）。 执行 (Execute)：脚本在沙箱中运行，执行精确的数学运算或数据处理。 反馈 (Feedback)：脚本将结构化的 Result Data 返回给 LLM。 这种设计将复杂的认知负载从 概率性的生成（Generation） 转移到了 确定性的代码执行（Execution） 上，显著增强了系统的鲁棒性。\n四、 技术栈对比：Skills, MCP 与 Function Calling # 为了精准定位 Skills，需将其与现有的 AI 基础设施概念进行对比。\n4.1 抽象层级对比 # 概念 抽象层级 核心职责 类比 Function Calling 原子层 (Mechanism) 接口执行。LLM 输出 JSON 参数以调用函数。 “手”：能够抓取物体，但不知道抓什么、为什么要抓。 MCP 连接层 (Protocol) 标准化连接。统一 AI 与外部数据/工具的通信协议。 “神经/血管”：连接大脑与手脚，传输数据，但不包含业务逻辑。 Agent Skills 逻辑层 (Module) 流程编排。封装 Prompt (SOP) + 代码，定义“如何做”。 “技能手册/小脑”：指导手去完成“泡咖啡”这一复杂动作序列的知识。 4.2 深度分析 # Skills 与 Function Calling 的关系：\nFunction Calling 是底层能力，Skills 是上层应用。 一个 Skill 的执行过程中，通常会包含多次 Function Calling（例如调用 bash 运行脚本，调用 read_file 读取文档）。Skills 为 Function Calling 提供了上下文背景和执行顺序的约束。 Skills 与 MCP (Model Context Protocol) 的关系：\n互补而非替代。 MCP 解决“连接外部”：它标准化了如何连接 Google Drive、PostgreSQL 或 GitHub。它提供的是“原材料”和“通道”。 Skills 解决“内部处理”：它定义了当通过 MCP 获取到数据后，应该按照什么步骤进行分析、总结和报告。 最佳实践：通过 MCP 获取数据，利用 Skill 定义的脚本和 Prompt 处理数据。 4.3 Token 消耗差异 # Function Calling / MCP (Raw)：如果在 System Prompt 中挂载 100 个工具的完整 Schema，每次对话都会消耗大量 Token，且容易造成模型困惑。 Skills：利用前文提到的“渐进式披露”，初始仅加载几百 Token 的索引。其 Token 效率在工具数量增加时呈指数级优势。 五、 总结 # Agent Skills Specification 提供了一个标准化的框架，用于构建健壮（Robust）且可互操作（Interoperable）的 AI Agents。\n它不是一项单一的黑科技，而是一套工程实践的组合：\n利用 Metadata 实现路由的低成本化。 利用 Context Injection 实现能力的即插即用。 利用 Scripts 实现逻辑的确定性。 对于开发者而言，采用 Skills 规范意味着从“编写单一的 Prompt”转向“构建模块化的智能体操作系统组件”。\n","date":"2025-11-01","externalUrl":null,"permalink":"/blogs/agent/claude-skills/","section":"Blog","summary":"","title":"Claude Agent Skills：元工具架构分析","type":"blogs"},{"content":" 参考原文链接：claude-skills-deep-dive Claude 的智能体 技能 (Skills) 系统代表了一种复杂的、基于提示词（prompt-based）的 元工具 (meta-tool) 架构，它通过专门的指令注入扩展了 大语言模型 (LLM) 的能力。与传统的函数调用或代码执行不同，skills 通过 提示词扩展 (prompt expansion) 和 上下文修改 (context modification) 来运作，从而在不编写可执行代码的情况下，改变 Claude 处理后续请求的方式。\n本篇深度解析将从第一性原理（first principles）解构 Claude 的智能体技能系统，记录一个名为 “Skill” 的工具如何作为元工具将特定领域的提示词注入到对话上下文中。我们将以 skill-creator（技能创建者）和 internal-comms（内部通信）技能作为案例研究，以此贯穿整个生命周期，通过检查从文件解析、API 请求结构到 Claude 决策过程的每一个环节。\nClaude 智能体技能概览 (Claude Agent Skills Overview) # Claude 使用 技能 (Skills) 来改进其执行特定任务的方式。技能被定义为包含指令、脚本和资源的文件夹，Claude 可以在需要时加载它们。Claude 使用一种 声明式的、基于提示词的系统 来进行技能的发现和调用。AI 模型（Claude）根据系统提示词中呈现的文本描述来决定是否调用 skills。在代码层面不存在算法式的技能选择或 AI 驱动的意图检测。决策完全发生在 Claude 的推理过程中，依据的是所提供的技能描述。\nSkills 不是可执行代码。它们不运行 Python 或 JavaScript，幕后也没有 HTTP 服务器或函数调用在发生。它们也不是硬编码在 Claude 的系统提示词中。Skills 存在于 API 请求结构的一个独立部分中。\n那么它们是什么？Skills 是专门的提示词模板，用于将特定领域的指令注入到对话上下文中。当一个技能被调用时，它会同时修改 对话上下文 (conversation context)（通过注入指令提示词）和 执行上下文 (execution context)（通过更改工具权限并可能切换模型）。技能不是直接执行动作，而是扩展为详细的提示词，让 Claude 做好解决特定类型问题的准备。每个技能看起来都是 Claude 所见工具模式（tool schema）的一个动态补充。\n当用户发送请求时，Claude 接收三样东西：用户消息、可用工具（Read, Write, Bash 等）以及 Skill 工具。Skill 工具的描述包含了一份格式化的列表，列出了所有可用技能及其 name（名称）、description（描述）和其他字段的组合。Claude 读取这个列表，利用其自然语言理解能力将你的意图与技能描述进行匹配。如果你说“帮我为日志创建一个技能”，Claude 会看到 internal-comms 技能的描述（“当用户想要使用公司喜欢的格式编写内部通信时”），识别出匹配项，并使用命令 \u0026quot;internal-comms\u0026quot; 调用 Skill 工具。\n术语说明 (Terminology Note):\nSkill 工具 (Skill tool - 大写 S) = 管理所有技能的 元工具 (meta-tool)。它出现在 Claude 的 tools 数组中，与 Read, Write, Bash 等并列。 技能 (skills - 小写 s) = 像 pdf, skill-creator, internal-comms 这样的个体技能。这些是 Skill 工具所加载的专门指令模板。 下图更直观地展示了 Claude 如何使用 skills：\n图 1: 流程图显示用户输入 -\u003e Claude 接收消息 -\u003e 检查 Skill 工具是否存在 -\u003e 读取 Skill 工具描述 -\u003e 匹配用户请求 -\u003e 调用 Skill 工具 -\u003e 验证/检查权限/加载提示词 -\u003e 在新上下文中执行 -\u003e 返回结果 技能选择机制在代码层面没有算法路由或意图分类。Claude Code 不使用嵌入（embeddings）、分类器或模式匹配来决定调用哪个技能。相反，系统将所有可用技能格式化为嵌入在 Skill 工具提示词中的文本描述，并让 Claude 的语言模型做出决定。这是纯粹的 LLM 推理。没有正则表达式，没有关键词匹配，没有基于机器学习的意图检测。决策发生在 Claude 通过 Transformer 的前向传播（forward pass）内部，而不是在应用程序代码中。\n当 Claude 调用一个技能时，系统遵循一个简单的工作流：它加载一个 markdown 文件 (SKILL.md)，将其扩展为详细的指令，将这些指令作为新的用户消息注入到对话上下文中，修改执行上下文（允许的工具、模型选择），并在这种丰富后的环境中继续对话。这与传统工具根本不同，传统工具执行并返回结果。技能是 准备 (prepare) Claude 去解决一个问题，而不是直接解决它。\n下表有助于更好地消除工具（Tools）和技能（Skills）及其能力之间的歧义：\n方面 (Aspect) 传统工具 (Traditional Tools) 技能 (Skills) 执行模型 同步，直接 提示词扩展 (Prompt expansion) 目的 执行特定操作 引导复杂工作流 返回值 即时结果 对话上下文 + 执行上下文变更 示例 Read, Write, Bash internal-comms, skill-creator 并发性 通常安全 非并发安全 类型 多种多样 始终是 \u0026ldquo;prompt\u0026rdquo; (提示词) 构建智能体技能 (Building Agent Skills) # 现在让我们通过以 Anthropic 技能库中的 skill-creator 技能为例，深入探讨如何构建技能。提醒一下，智能体技能 (agent skills) 是由指令、脚本和资源组成的有组织的文件夹，智能体可以动态发现和加载它们，以便在特定任务上表现更好。Skills 通过将你的专业知识打包成 Claude 可组合的资源来扩展 Claude 的能力，将通用智能体转变为符合你需求的专用智能体。\n核心洞察 (Key Insight): 技能 = 提示词模板 + 对话上下文注入 + 执行上下文修改 + 可选数据文件和 Python 脚本\n每个 Skill 都定义在一个名为 SKILL.md（大小写不敏感）的 markdown 文件中，并带有存储在 /scripts、/references 和 /assets 下的可选打包文件。这些打包文件可以是 Python 脚本、Shell 脚本、字体定义、模板等。以 skill-creator 为例，它包含 SKILL.md、用于许可的 LICENSE.txt，以及 /scripts 文件夹下的一些 Python 脚本。skill-creator 没有任何 /references 或 /assets。\n图 2: 个体技能对象结构图，展示 skill-creator 包含 SKILL.md, LICENSE.txt, scripts/ (init_skill.py, package_skill.py, quick_validate.py) 技能从多个来源被发现和加载。Claude Code 扫描用户设置 (~/.config/claude/skills/)、项目设置 (.claude/skills/)、插件提供的技能以及内置技能，以构建可用技能列表。对于 Claude Desktop，我们可以如下上传自定义技能。\n图 3: Claude Desktop \"上传技能\" 界面截图 注意: 构建技能最重要的概念是 渐进式披露 (Progressive Disclosure) —— 只展示足够的信息帮助智能体决定下一步做什么，然后在需要时展示更多细节。在 智能体技能 (agent skills) 的案例中：\n披露 Frontmatter (前置元数据)：极简信息（名称、描述、许可）。 如果选中某个 skill，加载 SKILL.md：全面但聚焦的内容。 然后在 skill 执行时加载辅助资产、参考资料和脚本。 编写 SKILL.md (Writing SKILL.md) # SKILL.md 是技能提示词的核心。它是一个 markdown 文件，遵循两部分结构——前置元数据（frontmatter）和内容。前置元数据配置技能 如何 (HOW) 运行（权限、模型、元数据），而 markdown 内容告诉 Claude 做什么 (WHAT)。前置元数据是用 YAML 编写的文件头。\n┌─────────────────────────────────────┐ │ 1. YAML Frontmatter (Metadata) │ ← 配置 (Configuration) │ --- │ │ name: skill-name │ │ description: Brief overview │ │ allowed-tools: \u0026#34;Bash, Read\u0026#34; │ │ version: 1.0.0 │ │ --- │ ├─────────────────────────────────────┤ │ 2. Markdown Content (Instructions) │ ← 给 Claude 的提示词 (Prompt) │ │ │ Purpose explanation │ │ Detailed instructions │ │ Examples and guidelines │ │ Step-by-step procedures │ └─────────────────────────────────────┘ Frontmatter (前置元数据) # 前置元数据包含控制 Claude 如何发现和使用技能的元数据。作为一个例子，这里是 skill-creator 的前置元数据：\n--- name: skill-creator description: Guide for creating effective skills. This skill should... license: Complete terms in LICENSE.txt --- 让我们逐一浏览前置元数据的字段。\nname (必填) # 不言自明。skill 的名称。skill 的 name 被用作 Skill Tool 中的 command（命令）。\ndescription (必填) # description 字段提供了技能功能的简要摘要。这是 Claude 用来确定何时调用技能的主要信号。在上面的例子中，描述明确指出“当用户想要创建一个新技能时应使用此技能”——这种清晰的、以行动为导向的语言有助于 Claude 将用户意图与技能能力相匹配。\n系统会自动将来源信息附加到描述中（例如，\u0026quot;(plugin:skills)\u0026quot;），这有助于在加载多个技能时区分不同来源的技能。\nwhen_to_use (未记录—可能已弃用或未来特性) # ⚠️ 重要提示: when_to_use 字段在代码库中广泛出现，但没有记录在任何官方 Anthropic 文档中。此字段可能是：\n一个正在被逐步淘汰的弃用功能 一个尚未正式支持的内部/实验性功能 一个尚未发布的计划功能 建议: 依赖详细的 description 字段代替。在官方文档出现之前，避免在生产环境的技能中使用 when_to_use。\n尽管未记录，但以下是 when_to_use 目前在代码库中的工作方式：\nfunction formatSkill(skill) { let description = skill.whenToUse ? `${skill.description} - ${skill.whenToUse}` : skill.description; return `\u0026#34;${skill.name}\u0026#34;: ${description}`; } 当存在时，when_to_use 会通过连字符分隔符附加到描述后面。例如：\n\u0026quot;skill-creator\u0026quot;: Create well-structured, reusable skills... - When user wants to...\n这个组合字符串是 Claude 在 Skill 工具提示词中看到的内容。然而，由于此行为未记录，它可能会在未来的版本中更改或删除。更安全的方法是将使用指南直接包含在 description 字段中，如上面的 skill-creator 示例所示。\nlicense (可选) # 不言自明。\nallowed-tools (可选) # allowed-tools 字段定义了技能可以在未经用户批准的情况下使用哪些工具，类似于 Claude 的 allowed-tools。\n这是一个逗号分隔的字符串，会被解析为允许的工具名称数组。你可以使用通配符来限定权限范围，例如，Bash(git:*) 仅允许 git 子命令，而 Bash(npm:*) 允许所有 npm 操作。skill-creator 技能使用了 \u0026quot;Read,Write,Bash,Glob,Grep,Edit\u0026quot; 来赋予其广泛的文件和搜索能力。一个常见的错误是列出所有可用工具，这会产生安全风险并破坏安全模型。\n只包含你的技能实际需要的工具——如果你只是读写文件，\u0026quot;Read,Write\u0026quot; 就足够了。\n# ✅ skill-creator 允许使用多个工具 allowed-tools: \u0026#34;Read,Write,Bash,Glob,Grep,Edit\u0026#34; # ✅ 仅限特定的 git 命令 allowed-tools: \u0026#34;Bash(git status:*),Bash(git diff:*),Bash(git log:*)\u0026#34; # ✅ 仅限文件操作 allowed-tools: \u0026#34;Read,Write,Edit,Glob,Grep\u0026#34; # ❌ 不必要的攻击面 allowed-tools: \u0026#34;Bash,Read,Write,Edit,Glob,Grep,WebSearch,Task,Ag...\u0026#34; # ❌ 带有所有 npm 命令的不必要攻击面 allowed-tools: \u0026#34;Bash(npm:*),Read,Write\u0026#34; model (可选) # model 字段定义了技能可以使用哪个模型。默认情况下，它继承用户会话中的当前模型。对于像代码审查这样的复杂任务，技能可以请求更强大的模型，如 Claude Opus 或其他开源中文模型（懂得都懂）。\nmodel: \u0026#34;claude-opus-4-20250514\u0026#34; # 使用特定模型 model: \u0026#34;inherit\u0026#34; # 使用会话的当前模型 (默认) version, disable-model-invocation, 和 mode (可选) # 技能支持三个可选的前置元数据字段用于版本控制和调用控制。\nversion 字段（例如 version: \u0026quot;1.0.0\u0026quot;）是一个元数据字段，用于跟踪技能版本，从前置元数据解析而来，但主要用于文档和技能管理目的。 disable-model-invocation 字段（布尔值）防止 Claude 通过 Skill 工具自动调用该技能。当设置为 true 时，该技能将从展示给 Claude 的列表中排除，只能由用户通过 /skill-name 手动调用，这使其非常适合需要显式用户控制的危险操作、配置命令或交互式工作流。 mode 字段（布尔值）将技能归类为“模式命令 (mode command)”，用于修改 Claude 的行为或上下文。当设置为 true 时，该技能会出现在技能列表顶部的特殊“模式命令”部分（与常规实用技能分开），使其对于像 debug-mode（调试模式）、expert-mode（专家模式）或 review-mode（审查模式）这样建立特定操作上下文或工作流的技能更加突出。 SKILL.md 提示词内容 (SKILL.md Prompt Content) # 在前置元数据之后是 markdown 内容——这是当 skill 被调用时 Claude 接收到的实际提示词。这是你定义 skill 的行为、指令和工作流的地方。编写有效技能提示词的关键是保持聚焦并使用 渐进式披露：在 SKILL.md 中提供核心指令，并引用外部文件以获取详细内容。\n这是一个推荐的内容结构：\n--- # Frontmatter here --- # [Brief Purpose Statement - 1-2 sentences] (简短目的陈述) ## Overview (概述) [What this skill does, when to use it, what it provides] (此技能做什么，何时使用，提供什么) ## Prerequisites (先决条件) [Required tools, files, or context] (所需工具、文件或上下文) ## Instructions (指令) ### Step 1: [First Action] (步骤 1：首个动作) [Imperative instructions] (命令式指令) [Examples if needed] (如果需要，提供示例) ### Step 2: [Next Action] (步骤 2：下一个动作) [Imperative instructions] ### Step 3: [Final Action] (步骤 3：最终动作) [Imperative instructions] ## Output Format (输出格式) [How to structure results] (如何构建结果) ## Error Handling (错误处理) [What to do when things fail] (失败时做什么) ## Examples (示例) [Concrete usage examples] (具体使用案例) ## Resources (资源) [Reference scripts/, references/, assets/ if bundled] (引用的脚本、参考资料、资产) 作为一个例子，skill-creator 技能包含以下指令，指定了创建技能所需的每个工作流步骤。\n## Skill Creation Process ### Step 1: Understanding the Skill with Concrete Examples ### Step 2: Planning the Reusable Skill Contents ### Step 3: Initializing the Skill ### Step 4: Edit the Skill ### Step 5: Packaging a Skill 当 Claude 调用此技能时，它会接收整个提示词作为新指令，并预先添加了基础目录路径。{baseDir} 变量解析为技能的安装目录，允许 Claude 使用 Read 工具加载参考文件：Read({baseDir}/scripts/init_skill.py)。这种模式保持了主提示词简洁，同时按需提供详细文档。\n提示词内容的最佳实践：\n保持在 5,000 字（约 800 行）以内，以避免压垮上下文。 使用命令式语言（“Analyze code for\u0026hellip;”/“分析代码以\u0026hellip;”），而不是第二人称（“You should analyze\u0026hellip;”/“你应该分析\u0026hellip;”）。 引用外部文件以获取详细内容，而不是嵌入所有内容。 使用 {baseDir} 作为路径，绝不要硬编码绝对路径如 /home/user/project/。 ❌ Read /home/user/project/config.json ✅ Read {baseDir}/config.json 当技能被调用时，Claude 仅获得 allowed-tools 中指定的工具的访问权限，如果前置元数据中指定了模型，则模型可能会被覆盖。技能的基础目录路径会自动提供，使得打包的资源可被访问。\n随技能打包资源 (Bundling Resources with Your Skill) # 当你将支持资源与 SKILL.md 一起打包时，Skills 会变得更加强大。标准结构使用三个目录，每个服务于特定目的：\nmy-skill/ ├── SKILL.md # 核心提示词和指令 ├── scripts/ # 可执行的 Python/Bash 脚本 ├── references/ # 加载到上下文中的文档 └── assets/ # 模板和二进制文件 为什么要打包资源？ 保持 SKILL.md 简洁（低于 5,000 字）可以防止压垮 Claude 的上下文窗口。打包资源让你能够提供详细文档、自动化脚本和模板，而不会使主提示词臃肿。Claude 仅在需要时使用 渐进式披露 加载它们。\nscripts/ 目录 # scripts/ 目录包含 Claude 通过 Bash 工具运行的可执行代码——自动化脚本、数据处理器、验证器或执行确定性操作的代码生成器。\n作为一个例子，skill-creator 的 SKILL.md 像这样引用脚本：\nWhen creating a new skill from scratch, always run the `init_skill.py`. Usage: ```scripts/init_skill.py \u0026lt;skill-name\u0026gt; --path \u0026lt;output-directory\u0026gt;``` The script: - Creates the skill directory at the specified path - Generates a SKILL.md template with proper frontmatter and TOC - Creates example resource directories: scripts/, references/, assets/ - Adds example files in each directory that can be customized 当 Claude 看到这条指令时，它执行 python {baseDir}/scripts/init_skill.py。{baseDir} 变量自动解析为技能的安装路径，使技能在不同环境中可移植。\n使用 scripts/ 处理复杂的多步操作、数据转换、API 交互，或任何用代码比用自然语言表达逻辑更精确的任务。\nreferences/ 目录 # references/ 目录存储 Claude 在被引用时读入其上下文的文档。这是文本内容——markdown 文件、JSON 模式、配置模板，或任何 Claude 完成任务所需的文档。\n作为一个例子，mcp-creator 的 SKILL.md 像这样引用 references：\n#### 1.4 Study Framework Documentation **Load and read the following reference files:** - **MCP Best Practices**: [📄 View Best Practices](./reference/mcp_best_practices.md) **For Python implementations, also load:** - **Python SDK Documentation**: Use WebFetch to load `https://...` - [🐍 Python Implementation Guide](./reference/python_mcp_server.md) **For Node/TypeScript implementations, also load:** - **TypeScript SDK Documentation**: Use WebFetch to load `https://...` - [⚡ TypeScript Implementation Guide](./reference/node_mcp_server.md) 当 Claude 遇到这些指令时，它使用 Read 工具：Read({baseDir}/references/mcp_best_practices.md)。内容被加载到 Claude 的上下文中，提供详细信息而不会弄乱 SKILL.md。\n使用 references/ 存放详细文档、大型模式库、清单、API 模式，或任何对于 SKILL.md 来说太冗长但对任务必要的文本内容。\nassets/ 目录 # assets/ 目录包含 Claude 通过路径引用但不加载到上下文中的模板和二进制文件。可以将这视为技能的静态资源——HTML 模板、CSS 文件、图像、配置样板或字体。\n在 SKILL.md 中：\nUse the template at {baseDir}/assets/report-template.html as the base. Reference the architecture diagram at {baseDir}/assets/diagram.png. Claude 看到文件路径但不会读取内容。相反，它可能会将模板复制到新位置，填充占位符，或在生成的输出中引用该路径。\n使用 assets/ 存放 HTML/CSS 模板、图像、二进制文件、配置模板，或任何 Claude 通过路径操作而不是读入上下文的文件。\nreferences/ 和 assets/ 之间的关键区别在于：\nreferences/: 通过 Read 工具加载到 Claude 上下文中的文本内容。 assets/: 仅通过路径引用，不加载到上下文中的文件。 这种区别对上下文管理很重要。references/ 中的 10KB markdown 文件在加载时会消耗上下文 token。assets/ 中的 10KB HTML 模板则不会。Claude 只知道路径存在。\n最佳实践: 始终对路径使用 {baseDir}，绝不要硬编码绝对路径。这使得技能在用户环境、项目目录和不同安装之间具有可移植性。\n常见技能模式 (Common Skill Patterns) # 正如所有工程领域一样，理解常见模式有助于设计有效的技能。以下是工具集成和工作流设计中最有用的模式。\n模式 1: 脚本自动化 (Script Automation) # 用例: 需要多个命令或确定性逻辑的复杂操作。 此模式将计算任务卸载到 scripts/ 目录中的 Python 或 Bash 脚本。技能提示词告诉 Claude 执行脚本并处理其输出。\n图 4: 流程图 SKILL.md 指令 -\u003e 委托给 Python/Bash 脚本 -\u003e 执行确定性逻辑 -\u003e 输出 JSON/数据结果 -\u003e Claude 处理 -\u003e 格式化报告 SKILL.md 示例:\nRun scripts/analyzer.py on the target directory: `python {baseDir}/scripts/analyzer.py --path \u0026#34;$USER_PATH\u0026#34; --output output.json` Parse the generated `report.json` and present findings. 所需工具:\nallowed-tools: \u0026#34;Bash(python {baseDir}/scripts/*:*), Read, Write\u0026#34; 模式 2: 读取 - 处理 - 写入 (Read - Process - Write) # 用例: 文件转换和数据处理。 最简单的模式——读取输入，按照指令转换它，写入输出。用于格式转换、数据清理或报告生成。\n图 5: 流程图 输入文件 -\u003e 读取 -\u003e Claude 处理 -\u003e 转换 -\u003e Claude 分析 -\u003e 格式化 -\u003e 输出文件 SKILL.md 示例:\n## Processing Workflow 1. Read input file using Read tool 2. Parse content according to format 3. Transform data following specifications 4. Write output using Write tool 5. Report completion with summary 所需工具:\nallowed-tools: \u0026#34;Read, Write\u0026#34; 模式 3: 搜索 - 分析 - 报告 (Search - Analyze - Report) # 用例: 代码库分析和模式检测。 使用 Grep 搜索代码库中的模式，读取匹配的文件以获取上下文，分析发现，并生成结构化报告。或者，搜索企业数据存储以获取数据，分析检索到的数据以获取信息，并生成结构化报告。\n图 6: 流程图 搜索模式 -\u003e 文件 1/2/N -\u003e 聚合与分析 -\u003e 结构化报告 SKILL.md 示例:\n## Analysis Process 1. Use Grep to find relevant code patterns 2. Read each matched file 3. Analyze for vulnerabilities 4. Generate structured report 所需工具:\nallowed-tools: \u0026#34;Grep, Read\u0026#34; 模式 4: 命令链执行 (Command Chain Execution) # 用例: 有依赖关系的多步操作。 执行一系列命令，其中每一步都取决于前一步的成功。常见于类似 CI/CD 的工作流。\n图 7: 流程图 Command 1 -\u003e Success -\u003e Command 2 -\u003e Success -\u003e Command 3 -\u003e Results -\u003e Stage Report (Failure 路径指向 Error Report) SKILL.md 示例:\nExecute analysis pipeline: npm install \u0026amp;\u0026amp; npm run lint \u0026amp;\u0026amp; npm test Report results from each stage. 所需工具:\nallowed-tools: \u0026#34;Bash(npm install:*), Bash(npm run:*), Read\u0026#34; 高级模式 (Advanced Patterns) # 向导式多步工作流 (Wizard-Style Multi-Step Workflows) # 用例: 每一步都需要用户输入的复杂流程。 将复杂任务分解为离散步骤，并在每个阶段之间进行明确的用户确认。用于设置向导、配置工具或引导式流程。\nSKILL.md 示例:\n## Workflow ### Step 1: Initial Setup 1. Ask user for project type 2. Validate prerequisites exist 3. Create base configuration Wait for user confirmation before proceeding. ### Step 2: Configuration 1. Present configuration options 2. Ask user to choose settings 3. Generate config file Wait for user confirmation before proceeding. ### Step 3: Initialization 1. Run initialization scripts 2. Verify setup successful 3. Report results 基于模板的生成 (Template-Based Generation) # 用例: 从存储在 assets/ 中的模板创建结构化输出。 加载模板，用用户提供或生成的数据填充占位符，并写入结果。常见于报告生成、样板代码创建或文档编写。\nSKILL.md 示例:\n## Generation Process 1. Read template from {baseDir}/assets/template.html 2. Parse user requirements 3. Fill template placeholders: - {{name}} → user-provided name - {{summary}} → generated summary - {{date}} → current date 4. Write filled template to output file 5. Report completion 迭代式精炼 (Iterative Refinement) # 用例: 需要多次传递且深度递增的过程。 首先执行广泛分析，然后对发现的问题进行逐步深入的挖掘。用于代码审查、安全审计或质量分析。\nSKILL.md 示例:\n## Iterative Analysis ### Pass 1: Broad Scan 1. Search entire codebase for patterns 2. Identify high-level issues 3. Categorize findings ### Pass 2: Deep Analysis For each high-level issue: 1. Read full file context 2. Analyze root cause 3. Determine severity ### Pass 3: Recommendation For each finding: 1. Research best practices 2. Generate specific fix 3. Estimate effort Present final report with all findings and recommendations. 上下文聚合 (Context Aggregation) # 用例: 结合多个来源的信息以建立全面理解。 从不同文件和工具收集数据，综合成连贯的图景。用于项目摘要、依赖分析或影响评估。\nSKILL.md 示例:\n## Context Gathering 1. Read project README.md for overview 2. Analyze package.json for dependencies 3. Grep codebase for specific patterns 4. Check git history for recent changes 5. Synthesize findings into coherent summary 智能体技能内部架构 (Agent Skills Internal Architecture) # 随着概述和构建过程的涵盖，我们现在可以检查 skills 在幕后实际上是如何工作的。skills 系统通过一个 元工具 (meta-tool) 架构运作，其中一个名为 Skill 的工具充当所有个体技能的容器和调度器。这种设计在实现和目的上将技能与传统工具从根本上区分开来。\nSkill 工具是一个管理所有技能的 元工具。\n技能对象设计 (Skills Object Design) # 像 Read, Bash, 或 Write 这样的传统工具执行离散动作并返回即时结果。Skills 的运作方式不同。它们不是直接执行动作，而是将专门的指令注入对话历史，并动态修改 Claude 的执行环境。这通过两条用户消息发生——一条包含用户可见的元数据，另一条包含完整的、对 UI 隐藏但发送给 Claude 的技能提示词——并通过改变智能体的上下文来更改权限、切换模型，并在技能使用期间调整思维 token 参数。\n图 8: 架构图显示 Claude 与 Skill 上下文。Skill 工具执行 -\u003e 加载 pdf.md -\u003e 解析 frontmatter -\u003e 返回两样东西: newMessages (注入技能提示词, role: 'user') 和 contextModifier (修改权限, 模型, token) 特性 (Feature) 普通工具 (Normal Tool) 技能工具 (Skill Tool) 本质 直接动作执行者 提示词注入 + 上下文修改器 消息角色 assistant → tool_use\nuser → tool_result assistant → tool_use Skill\nuser → tool_result\nuser → skill prompt ← 注入! 复杂性 简单 (3-4 条消息) 复杂 (5-10+ 条消息) 上下文 静态 动态 (每轮修改) 持久性 仅工具交互 工具交互 + 技能提示词 Token 开销 极小 (~100 tokens) 显著 (~1,500+ tokens 每轮) 用例 简单，直接任务 复杂，引导式工作流 复杂性是巨大的。普通工具生成简单的消息交换——助手工具调用，随后是用户结果。技能注入多条消息，在动态修改的上下文中操作，并携带显著的 Token 开销以提供指导 Claude 行为的专门指令。\n理解 Skill 元工具如何工作揭示了这个系统的机制。让我们检查它的结构：\nPd = { name: \u0026#34;Skill\u0026#34;, // 工具名称常量: $N = \u0026#34;Skill\u0026#34; inputSchema: { command: \u0026#34;string\u0026#34; // 例如, \u0026#34;pdf\u0026#34;, \u0026#34;skill-creator\u0026#34; }, outputSchema: { success: \u0026#34;boolean\u0026#34;, commandName: \u0026#34;string\u0026#34; }, // 🔑 关键字段: 这生成技能列表 prompt: async () =\u0026gt; fN2(), // 验证和执行 validateInput: async (input, context) =\u0026gt; { /* 5 error codes */ }, checkPermissions: async (input, context) =\u0026gt; { /* allow/deny/ask */ }, call: async *(input, context) =\u0026gt; { /* yields messages + context */ } } prompt 字段将 Skill 工具与 Read 或 Bash 等其他具有静态描述的工具区分开来。Skill 工具不使用固定字符串，而是使用动态提示生成器，通过聚合所有可用技能的名称和描述在运行时构建其描述。这实现了 渐进式披露 (progressive disclosure) —— 系统最初只加载极简元数据（技能名称和来自前置元数据的描述）到 Claude 的初始上下文中，提供刚好足够的信息让模型决定哪个技能匹配用户的意图。完整的技能提示词仅在 Claude 做出选择后才加载，防止上下文膨胀，同时保持可发现性。\nasync function fN2() { let A = await atA(), { modeCommands: B, limitedRegularCommands: Q } = vN2(A), G = [...B, ...Q].map((W) =\u0026gt; W.userFacingName()).join(\u0026#34;, \u0026#34;); // ...省略部分代码... return `Execute a skill within the main conversation \u0026lt;skills_instructions\u0026gt; When users ask you to perform tasks, check if any of the available skills matches. How to use skills: - Invoke skills using this tool with the skill name only (no arguments). - When you invoke a skill, you will see \u0026lt;command-message\u0026gt;The \u0026#34;{name}\u0026#34; skill is loading...\u0026lt;/command-message\u0026gt; - The skill\u0026#39;s prompt will expand and provide detailed instructions. - Examples: - \\`command: \u0026#34;pdf\u0026#34;\\` - invoke the pdf skill - \\`command: \u0026#34;xlsx\u0026#34;\\` - invoke the xlsx skill - \\`command: \u0026#34;ms-office-suite:pdf\u0026#34;\\` - invoke using fully qualified name Important: - Only use skills listed in \u0026lt;available_skills\u0026gt; below - Do not invoke a skill that is already running - Do not use this tool for built-in CLI commands (like /help, /compact) \u0026lt;/skills_instructions\u0026gt; \u0026lt;available_skills\u0026gt; ${Y}${J} \u0026lt;/available_skills\u0026gt; `; } 与某些助手（如 ChatGPT）中某些工具存在于系统提示词中不同，Claude 智能体技能不存在于系统提示词中。它们作为 Skill 工具描述的一部分存在于 tools 数组中。个别技能的名称表示为 Skill 元工具输入模式的 command 字段的一部分。为了更好地可视化它的样子，这里是实际的 API 请求结构：\n{ \u0026#34;model\u0026#34;: \u0026#34;claude-sonnet-4-5-20250929\u0026#34;, \u0026#34;system\u0026#34;: \u0026#34;You are Claude Code, Anthropic\u0026#39;s official CLI...\u0026#34;, \u0026#34;messages\u0026#34;: [ {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Help me create a new skill\u0026#34;}, // ... 对话历史 ], \u0026#34;tools\u0026#34;: [ // ← 发送给 Claude 的 Tools 数组 { \u0026#34;name\u0026#34;: \u0026#34;Skill\u0026#34;, // ← 元工具 (Meta-tool) \u0026#34;description\u0026#34;: \u0026#34;Execute a skill...\\n\\n\u0026lt;skills_instructions\u0026gt;...\u0026#34;, \u0026#34;input_schema\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;command\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;The skill name (no arguments)\u0026#34; // ← 技能名称 } } } }, { \u0026#34;name\u0026#34;: \u0026#34;Bash\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Execute bash commands...\u0026#34;, // ... }, { \u0026#34;name\u0026#34;: \u0026#34;Read\u0026#34;, // ... } // ... 其他工具 ] } \u0026lt;available_skills\u0026gt; 部分存在于 Skill 工具的描述中，并为每个 API 请求重新生成。系统通过聚合当前加载的技能（来自用户和项目配置、插件提供的技能以及任何内置技能）来动态构建此列表，默认受 15,000 个字符的 token 预算限制。这种预算限制迫使技能作者编写简洁的描述，并确保工具描述不会压垮模型的上下文窗口。\n技能对话和执行上下文注入设计 # 大多数 LLM API 支持 role: \u0026quot;system\u0026quot; 消息，理论上可以携带系统提示词。事实上，OpenAI 的 ChatGPT 将其默认工具携带在其系统提示词中，包括用于内存的 bio、用于任务调度的 automations、用于控制 canvas 的 canmore、用于图像生成的 img_gen、file_search、python 和用于互联网搜索的 web。最后，工具提示词占据了其系统提示词中约 90% 的 token 计数。如果我们有大量工具和/或技能要加载到上下文中，这可能有用但效率极低。\n然而，系统消息具有不同的语义，使其不适合用于技能。系统消息设置跨越整个对话持久存在的全局上下文，以比用户指令更高的权威性影响所有后续轮次。\n技能需要临时的、有范围的行为。skill-creator 技能应该只影响技能创建相关的任务，而不是将会话剩余部分的 Claude 转变为永久的 PDF 专家。使用 role: \u0026quot;user\u0026quot; 配合 isMeta: true 使技能提示词对 Claude 表现为用户输入，使其保持临时性并局限于当前交互。技能完成后，对话返回正常的对话上下文和执行上下文，没有残留的行为修改。\n像 Read, Write, 或 Bash 这样的普通工具具有简单的通信模式。当 Claude 调用 Read 时，它发送文件路径，接收文件内容，并继续工作。用户在他们的记录中看到“Claude used the Read tool（Claude 使用了 Read 工具）”，这就是足够的透明度。工具做了一件事，返回了一个结果，交互就结束了。Skills 的运作有着根本的不同。技能不是执行离散动作并返回结果，而是注入全面的指令集，修改 Claude 推理和处理任务的方式。\n这创造了一个普通工具从未面临的设计挑战：用户需要透明度了解哪些技能正在运行以及它们在做什么，而 Claude 需要详细的、可能冗长的指令来正确执行技能。如果用户在他们的聊天记录中看到完整的技能提示词，UI 就会被成千上万字的内部 AI 指令弄乱。如果技能激活完全隐藏，用户就会失去对系统代表他们所做事情的可见性。解决方案需要将这两个通信通道分离成具有不同可见性规则的独特消息。\n技能系统在每条消息上使用 isMeta 标志来控制它是否出现在用户界面中。当 isMeta: false（或者当标志被省略并默认为 false）时，消息渲染在用户看到的对话记录中。当 isMeta: true 时，消息作为 Claude 对话上下文的一部分发送到 Anthropic API，但从未出现在 UI 中。这个简单的布尔标志实现了复杂的双通道通信：一条流用于人类用户，另一条用于 AI 模型。元提示 (Meta-prompting) 用于元工具 (meta-tools)!\n当技能执行时，系统向对话历史注入两条独立的用户消息。第一条携带 isMeta: false 的技能元数据，使其作为状态指示器对用户可见。第二条携带 isMeta: true 的完整技能提示词，将其从 UI 中隐藏，同时使其对 Claude 可用。这种拆分解决了透明度与清晰度的权衡问题，向用户展示正在发生的事情，而不用实现细节压垮他们。\n元数据消息使用简洁的 XML 结构，前端可以解析并适当地显示：\nlet metadata = [ `\u0026lt;command-message\u0026gt;${statusMessage}\u0026lt;/command-message\u0026gt;`, `\u0026lt;command-name\u0026gt;${skillName}\u0026lt;/command-name\u0026gt;`, args ? `\u0026lt;command-args\u0026gt;${args}\u0026lt;/command-args\u0026gt;` : null ].filter(Boolean).join(\u0026#39;\\n\u0026#39;); // Message 1: 没有 isMeta 标志 -\u0026gt; 默认为 false -\u0026gt; 可见 (VISIBLE) messages.push({ content: metadata, autocheckpoint: checkpointFlag }); 例如，当 PDF 技能激活时，用户在他们的记录中看到一个干净的加载指示器：\n\u0026lt;command-message\u0026gt;The \u0026#34;pdf\u0026#34; skill is loading\u0026lt;/command-message\u0026gt; \u0026lt;command-name\u0026gt;pdf\u0026lt;/command-name\u0026gt; \u0026lt;command-args\u0026gt;report.pdf\u0026lt;/command-args\u0026gt; 这条消息保持故意的极简——通常 50 到 200 个字符。XML 标签使前端能够以特殊格式渲染它，验证是否存在适当的 \u0026lt;command-message\u0026gt; 标签，并维护会话期间执行了哪些技能的审计跟踪。因为省略时 isMeta 标志默认为 false，所以此元数据自动出现在 UI 中。\n技能提示词消息采取相反的方法。它从 SKILL.md 加载完整内容，可能用附加上下文扩充它，并显式设置 isMeta: true 以对用户隐藏它：\nlet skillPrompt = await skill.getPromptForCommand(args, context) // 如果需要，使用 prepend/append 内容进行扩充 let fullPrompt = prependContent.length \u0026gt; 0 || appendContent.length \u0026gt; 0 ? [...prependContent, ...appendContent, ...skillPrompt] : skillPrompt; // Message 2: 显式 isMeta: true -\u0026gt; 隐藏 (HIDDEN) messages.push({ content: fullPrompt, isMeta: true // 对 UI 隐藏, 发送给 API }); 典型的技能提示词包含 500 到 5,000 字，提供全面的指导以改变 Claude 的行为。PDF 技能提示词可能包含：\nYou are a PDF processing specialist. Your task is to extract text from PDF documents using the pdftotext tool. ## Process 1. Validate the PDF file exists 2. Run pdftotext command to extract text 3. Read the output file 4. Present the extracted text to the user ## Tools Available You have access to: - Bash(pdftotext:*) - For running pdftotext command - Read - For reading extracted text - Write - For saving results if needed ## Output Format Present the extracted text clearly formatted. Base directory: /path/to/skill User arguments: report.pdf 此提示词建立了任务上下文，概述了工作流，指定了可用工具，定义了输出格式，并提供了环境特定路径。带有标题、列表和代码块的 markdown 结构帮助 Claude 解析并遵循指令。有了 isMeta: true，这整个提示词被发送到 API 但从不弄乱用户的记录。\n除了核心元数据和技能提示词外，技能还可以为附件和权限注入额外的条件消息：\nlet allMessages = [ createMessage({ content: metadata, autocheckpoint: flag }), // Message 1 createMessage({ content: skillPrompt, isMeta: true }), // Message 2 // ...attachmentMessages, // ... (allowedTools.length || skill.model ? [ // createPermissionsMessage({ // type: \u0026#34;command_permissions\u0026#34;, // allowedTools: allowedTools, // model: skill.useSmallFastModel ? getFastModel() : skill.model // }) // ] : []) ]; 附件消息可以携带诊断信息、文件引用或补充技能提示词的附加上下文。权限消息仅在技能在其前置元数据中指定 allowed-tools 或请求模型覆盖时出现，提供修改运行时执行环境的元数据。这种模块化组合允许每条消息都有特定用途，并根据技能的配置被包含或排除，扩展了基本的双消息模式以处理更复杂的场景，同时通过 isMeta 标志保持相同的可见性控制。\n为什么是两条消息而不是一条？ (Why Two Messages Instead of One?) # 单消息设计将迫使做出不可能的选择。设置 isMeta: false 会使整个消息可见，将成千上万字的 AI 指令倾倒在用户的聊天记录中。用户会看到类似这样的东西：\n┌─────────────────────────────────────────────┐ │ The \u0026#34;pdf\u0026#34; skill is loading │ │ │ │ You are a PDF processing specialist. │ │ │ │ Your task is to extract text from PDF │ │ documents using the pdftotext tool. │ │ │ │ ## Process │ │ │ │ 1. Validate the PDF file exists │ │ 2. Run pdftotext command to extract text │ │ 3. Read the output file │ │ ... [500 more lines] ... │ └─────────────────────────────────────────────┘ UI 变得不可用，充满了给 Claude 而不是给人类看的内部实现细节。或者，设置 isMeta: true 会隐藏一切，不提供关于哪个技能被激活或它接收了什么参数的透明度。用户将无法看到系统代表他们正在做什么。\n双消息拆分通过给每条消息不同的 isMeta 值解决了这个问题。\n消息 1 带有 isMeta: false 提供面向用户的透明度。 消息 2 带有 isMeta: true 为 Claude 提供详细指令。 这种细粒度的控制实现了透明度而不会信息过载。\n这些消息也服务于根本不同的受众和目的：\n方面 (Aspect) 元数据消息 (Metadata Message) 技能提示词消息 (Skill Prompt Message) 受众 人类用户 Claude (AI) 目的 状态/透明度 指令/指导 长度 ~50-200 字符 ~500-5,000 字 格式 结构化 XML 自然语言 markdown 可见性 应该是可见的 应该是隐藏的 内容 “正在发生什么？” “如何做？” 代码库甚至通过不同的路径处理这些消息。元数据消息被解析以寻找 \u0026lt;command-message\u0026gt; 标签，经过验证，并格式化用于 UI 显示。技能提示词消息直接发送到 API 而不经过解析或验证——它是仅供 Claude 推理过程使用的原始指令内容。合并它们将违反单一职责原则 (Single Responsibility Principle)，强迫一条消息通过两个不同的处理管道服务于两个不同的受众。\n案例研究：执行生命周期 (Case Study: Execution Lifecycle) # 现在涵盖了智能体技能的内部架构，让我们通过以一个假设的 pdf 技能作为案例研究，检查当用户说“Extract text from report.pdf（从 report.pdf 提取文本）”时发生的完整执行流程。\n图 9: 序列图 (Page 31 \u0026 32 \u0026 42)。用户请求 -\u003e Claude 接收 -\u003e 决定使用 Skill 工具 -\u003e Skill 工具执行 (加载 SKILL.md, 解析 frontmatter, 生成 newMessages, 生成 contextModifier) -\u003e 产出 newMessages 和 contextModifier -\u003e 发送给 API (Turn 1 完成) -\u003e API 返回 -\u003e Bash 工具执行 (带有 Skill 上下文) 第一阶段：发现与加载（启动）(Phase 1: Discovery \u0026amp; Loading) # 当 Claude Code 启动时，它扫描技能：\nasync function getAllCommands() { // Load from all sources in parallel let [userCommands, skillsAndPlugins, pluginCommands, builtins] = await Promise.all([ loadUserCommands(), // ~/.claude/commands/ loadSkills(), // .claude/skills/ + plugins loadPluginCommands(), // Plugin-defined commands getBuiltinCommands() // Hardcoded commands ]); return [...userCommands, ...skillsAndPlugins, ...pluginCommands, ...builtins] .filter(cmd =\u0026gt; cmd.isEnabled()); } 具体的技能加载逻辑：\n// Specific skill loading async function loadPluginSkills(plugin) { // ... const skillFiles = findSkillMdFiles(plugin.skillsPath); const skills = []; for (const file of skillFiles) { const content = readFile(file); const { frontmatter, markdown } = parseFrontmatter(content); skills.push({ type: \u0026#34;prompt\u0026#34;, name: `${plugin.name}:${getSkillName(file)}`, description: `${frontmatter.description} (plugin:${plugin.name})`, whenToUse: frontmatter.when_to_use, // ← Note: underscore to camelCase allowedTools: parseTools(frontmatter[\u0026#39;allowed-tools\u0026#39;]), model: frontmatter.model === \u0026#34;inherit\u0026#34; ? undefined : frontmatter.model, isSkill: true, promptContent: markdown, // ... other fields }); } return skills; } 对于 pdf 技能，这产生：\n{ type: \u0026#34;prompt\u0026#34;, name: \u0026#34;pdf\u0026#34;, description: \u0026#34;Extract text from PDF documents (plugin:document-utils)\u0026#34;, whenToUse: \u0026#34;When user wants to extract or process text from PDF files\u0026#34;, allowedTools: [\u0026#34;Bash(pdftotext:*)\u0026#34;, \u0026#34;Read\u0026#34;, \u0026#34;Write\u0026#34;], model: undefined, // Uses session model isSkill: true, disableModelInvocation: false, promptContent: \u0026#34;You are a PDF processing specialist...\u0026#34;, // ... other fields } 第二阶段：第一轮 - 用户请求与技能选择 (Phase 2: Turn 1 - User Request \u0026amp; Skill Selection) # 用户发送请求：“Extract text from report.pdf”。Claude 接收此消息以及工具数组中的 Skill 工具。在 Claude 决定调用 pdf 技能之前，系统必须在 Skill 工具的描述中呈现可用技能。\n技能过滤与呈现 (Skill Filtering \u0026amp; Presentation) # 并非所有加载的技能都出现在 Skill 工具中。技能必须在前置元数据中具有 description 或 when_to_use，否则会被过滤掉。过滤标准：\nasync function getSkillsForSkillTool() { const allCommands = await getAllCommands(); return allCommands.filter(cmd =\u0026gt; cmd.type === \u0026#34;prompt\u0026#34; \u0026amp;\u0026amp; cmd.isSkill === true \u0026amp;\u0026amp; !cmd.disableModelInvocation \u0026amp;\u0026amp; (cmd.source === \u0026#34;builtin\u0026#34; || cmd.isModeCommand === true) \u0026amp;\u0026amp; (cmd.hasUserSpecifiedDescription || cmd.whenToUse) // ← 必须有描述 ); } 技能格式化 (Skill Formatting) # 每个技能都被格式化用于 \u0026lt;available_skills\u0026gt; 部分。作为一个例子，我们假设的 pdf 技能可能被格式化为：\n\u0026quot;pdf\u0026quot;: Extract text from PDF documents - When user wants to extract or process text from PDF files\nfunction formatSkill(skill) { let name = skill.name; let description = skill.whenToUse ? `${skill.description} - ${skill.whenToUse}` : skill.description; return `\u0026#34;${name}\u0026#34;: ${description}`; } Claude 的决策过程 (Claude\u0026rsquo;s Decision Process) # 现在，当用户提示：“Extract text from report.pdf”。Claude 接收带有 Skill 工具的 API 请求，读取 \u0026lt;available_skills\u0026gt;，并进行推理（假设地，因为我们看不到推理痕迹）：\nInternal reasoning: - User wants to \u0026#34;extract text from report.pdf\u0026#34; - This is a PDF processing task - Looking at available skills... - \u0026#34;pdf\u0026#34;: Extract text from PDF documents - When user wants to extract... - This matches! The user wants to extract text from a PDF - Decision: Invoke Skill tool with command=\u0026#34;pdf\u0026#34; 注意这里没有算法匹配。没有词法匹配。没有语义匹配。没有搜索。这是纯粹的 LLM 推理，基于技能的描述做出决定。一旦完成，Claude 返回一个工具使用：\n{ \u0026#34;type\u0026#34;: \u0026#34;tool_use\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;toolu_123abc\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Skill\u0026#34;, \u0026#34;input\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;pdf\u0026#34; } } 第三阶段：技能工具执行 (Phase 3: Skill Tool Execution) # Skill 工具现在执行。这对应于序列图中黄色的“SKILL TOOL EXECUTION”框，它在产生结果之前执行验证、权限检查、文件加载和上下文修改。\n步骤 1：验证 (Validation) # async validateInput({ command }, context) { let skillName = command.trim().replace(/^\\//, \u0026#34;\u0026#34;); // Error 1: Empty if (!skillName) return { result: false, errorCode: 1 }; // Error 2: Unknown skill const allSkills = await getAllCommands(); if (!skillExists(skillName, allSkills)) { return { result: false, errorCode: 2 }; } // ... 其他检查 (无法加载, 模型禁用, 非 Prompt 类型) return { result: true }; } pdf 技能通过所有验证检查 ✓\n步骤 2：权限检查 (Permission Check) # async checkPermissions({ command }, context) { // ... 获取当前权限上下文 ... // 检查拒绝规则 // ... // 检查允许规则 // ... // 默认: 询问用户 return { behavior: \u0026#34;ask\u0026#34;, message: `Execute skill: ${skillName}` }; } 假设没有规则，用户被提示：“Execute skill: pdf?” 用户批准 ✓\n步骤 3：加载技能文件并生成执行上下文修改 (Load Skill File \u0026amp; Generate Execution Context Modification) # 随着验证和权限获得批准，Skill 工具加载技能文件并准备执行上下文修改：\nasync *call({ command }, context) { // ... 获取技能对象 ... // Load the skill prompt const promptContent = await skill.getPromptForCommand(\u0026#34;\u0026#34;, context); // Generate metadata tags const metadata = [/* ... */].join(\u0026#39;\\n\u0026#39;); // Create messages const messages = [ { type: \u0026#34;user\u0026#34;, content: metadata }, // Visible to user { type: \u0026#34;user\u0026#34;, content: promptContent, isMeta: true }, // Hidden // ... attachments, permissions ]; // Extract configuration const allowedTools = skill.allowedTools || []; const modelOverride = skill.model; // Yield result with execution context modifier yield { type: \u0026#34;result\u0026#34;, data: { success: true, commandName: skillName }, newMessages: messages, // 🔑 执行上下文修改函数 contextModifier(context) { let modified = context; // 注入允许的工具 (Pre-approve tools) if (allowedTools.length \u0026gt; 0) { // ... 更新 toolPermissionContext ... } // 覆盖模型 if (modelOverride) { // ... 更新 mainLoopModel ... } return modified; } }; } Skill 工具产生其结果，包含 newMessages（元数据 + 技能提示词 + 用于对话上下文注入的权限）和 contextModifier（用于执行上下文修改的工具权限 + 模型覆盖）。这完成了序列图中黄色的“SKILL TOOL EXECUTION”框。\n第四阶段：发送到 API（第一轮完成）(Phase 4: Send to API (Turn 1 Completion)) # 系统构建完整的消息数组发送到 Anthropic API。这包括来自对话的所有消息加上新注入的技能消息：\n{ \u0026#34;model\u0026#34;: \u0026#34;claude-sonnet-4-5-20250929\u0026#34;, \u0026#34;messages\u0026#34;: [ { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Extract text from report.pdf\u0026#34; }, { \u0026#34;role\u0026#34;: \u0026#34;assistant\u0026#34;, \u0026#34;content\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;tool_use\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Skill\u0026#34;, \u0026#34;input\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;pdf\u0026#34; } } ] }, { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;\u0026lt;command-message\u0026gt;The \\\u0026#34;pdf\\\u0026#34; skill is loading...\u0026lt;/command-message\u0026gt;\u0026#34; // isMeta: false (default) - UI 可见 }, { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;You are a PDF processing specialist...\\n\\n## Process...\u0026#34;, \u0026#34;isMeta\u0026#34;: true // UI 隐藏, 发送给 API }, { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;command_permissions\u0026#34;, \u0026#34;allowedTools\u0026#34;: [\u0026#34;Bash(pdftotext:*)\u0026#34;, \u0026#34;Read\u0026#34;, \u0026#34;Write\u0026#34;], \u0026#34;model\u0026#34;: undefined } } ] } 以上显示了我们到目前为止所做的工作。应用了执行上下文修饰符，为后续的工具调用预批准了 Bash(pdftotext:*), Read, 和 Write。请求被发送到 Anthropic API。这结束了技能执行。如果这是一个普通工具，我们就完成了。然而，技能是不同的。智能体技能仅仅注入了对话上下文和执行上下文。这意味着我们仍然需要用所有这些注入的上下文调用 Claude 智能体来完成用户的请求！\n第五阶段：Bash 工具执行（带有技能上下文的工具使用！）(Phase 5: Bash Tool Execution (Tool use with Skill Context!)) # Claude 接收带有注入的对话上下文的 API 响应。技能提示词已经改变了 Claude 的行为，为其提供：\n专门的 PDF 处理指令（对话上下文） 对 Bash(pdftotext:*), Read, 和 Write 工具的预批准访问（执行上下文） 要遵循的清晰工作流（对话上下文） Claude 处理上下文并遵循 pdf 技能的工作流：\nI\u0026#39;ll extract text from report.pdf. Let me process the file. [Following pdf skill\u0026#39;s instructions] 1. Validate that report.pdf exists 2. Run pdftotext command to extract text 3. Read the output file 4. Present the extracted text to you Claude 使用 Bash 工具（预先批准，无需用户提示）：\n{ \u0026#34;type\u0026#34;: \u0026#34;tool_use\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;toolu_456def\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Bash\u0026#34;, \u0026#34;input\u0026#34;: { \u0026#34;command\u0026#34;: \u0026#34;pdftotext report.pdf output.txt\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Extract text from PDF using pdftotext\u0026#34; } } Bash 工具成功执行，返回结果。然后 Claude 使用 Read 工具读取输出文件并将提取的文本呈现给用户。技能成功引导 Claude 完成了专门的 PDF 提取工作流，通过将指令注入对话上下文并修改工具权限的执行上下文。\n结论：心智模型回顾 (Conclusion: The Mental Model Recap) # Claude Code 中的技能是 基于提示词的对话和执行上下文修改器，通过元工具架构工作：\n关键要点：\n技能是 SKILL.md 文件中的 提示词模板，不是可执行代码。 Skill 工具（大写 S）是 tools 数组中管理个体技能的 元工具，不在系统提示词中。 技能通过注入指令提示词（通过 isMeta: true 消息）来修改 对话上下文。 技能通过更改工具权限和模型选择来修改 执行上下文。 选择通过 LLM 推理 发生，而不是算法匹配。 工具权限通过执行上下文修改 限定于技能执行范围。 技能每次调用注入两条用户消息——一条用于用户可见的元数据，一条用于发送到 API 的隐藏指令。 优雅的设计：通过将专业知识视为修改对话上下文的 提示词 和修改执行上下文的 权限，而不是执行的 代码，Claude Code 实现了传统函数调用难以企及的灵活性、安全性和可组合性。\n参考资料 (References) # Introducing Agent Skills Equipping Agents for the Real World with Agent Skills Claude Code Documentation Anthropic API Reference Official Documented Frontmatter Fields Internal Comms Skill Skill Creator Skill ChatGPT 5 System Prompt (leaked, not official) ","date":"2025-11-01","externalUrl":null,"permalink":"/blogs/agent/claude-skills-deep-dive-translate/","section":"Blog","summary":"","title":"译文：Claude Agent Skills：第一性原理深度解析","type":"blogs"},{"content":"","date":"2025-10-26","externalUrl":null,"permalink":"/series/graphiti/","section":"Series","summary":"","title":"Graphiti","type":"series"},{"content":" getzep/graphiti 引言 # 在构建了包含双时态信息的知识图谱后，下一个核心问题是：如何从中检索出对 AI Agent 有用的信息？ 简单的关键词匹配（Full-text Search）在面对语义模糊的查询时往往力不从心，而单纯的向量检索（Vector Search）又容易丢失图谱的结构信息。\nGraphiti 的检索模块（Search Module）是其架构中的“神经中枢”。它实现了一个高度模块化、策略驱动的混合检索管道，将 BM25、向量相似度、图遍历（BFS）以及多种重排序（Reranking）算法有机结合。本文将深入 graphiti_core/search/ 目录，分析其实现逻辑。\n1. 策略模式：灵活的 SearchConfig # Graphiti 并没有硬编码单一的搜索逻辑，而是采用了 策略模式（Strategy Pattern）。每一次搜索请求都由一个 SearchConfig 对象驱动，它定义了“用什么方法搜”以及“如何排序结果”。\n在 graphiti_core/search/search_config.py 中：\nclass SearchConfig(BaseModel): edge_config: EdgeSearchConfig | None node_config: NodeSearchConfig | None # ... class EdgeSearchConfig(BaseModel): search_methods: list[EdgeSearchMethod] # [bm25, cosine_similarity, bfs] reranker: EdgeReranker # [rrf, mmr, cross_encoder, node_distance] 开发者可以根据场景选择预定义的“配方”。例如，search_config_recipes.py 中定义的 EDGE_HYBRID_SEARCH_NODE_DISTANCE 专门用于“查找与特定节点最相关的边”，而 COMBINED_HYBRID_SEARCH_CROSS_ENCODER 则侧重于通过重排序模型获取最高精度的结果。\n2. 并行执行与多路召回 # 主入口 search() 函数位于 graphiti_core/search/search.py。它的实现极具并发性，利用 semaphore_gather 同时在 Edge, Node, Episode, Community 四个维度发起搜索。\n以 edge_search 为例，它实现了多路召回（Multi-way Retrieval）：\n全文检索 (BM25): edge_fulltext_search 原理: 利用 Neo4j/FalkorDB 的全文索引，匹配关键词。 作用: 捕获精确匹配（如人名、特定术语）。 向量相似度 (Cosine): edge_similarity_search 原理: 计算 Query Vector 与 fact_embedding 的余弦相似度。 作用: 捕获语义相关性（如搜“职业”匹配到“工程师”）。 图遍历 (BFS): edge_bfs_search 原理: 如果指定了 bfs_origin_node_uuids，则从这些节点出发，进行 N 跳（默认3跳）的广度优先搜索。 作用: 捕获拓扑相关性（如“朋友的朋友”），这是传统 RAG 无法做到的。 这些搜索方法并行执行，最终汇总为一个候选集（Candidates Pool），等待重排序。\n3. 重排序算法详解 (Rerankers) # 召回只是第一步，如何从成百上千个候选结果中挑出最相关的 Top-K？Graphiti 实现了四种重排序策略。\n3.1 RRF (Reciprocal Rank Fusion) # 代码: graphiti_core/search/search_utils.py -\u0026gt; rrf 逻辑: 不依赖具体的分数，只依赖排名位置。公式为 1 / (rank + k)。 优势: 鲁棒性强，无需额外模型调用，是默认且最高效的选择。 3.2 MMR (Maximal Marginal Relevance) # 代码: graphiti_core/search/search_utils.py -\u0026gt; maximal_marginal_relevance 逻辑: 这是一个纯 NumPy 实现。它在“与查询的相关性”和“与已选结果的多样性”之间进行权衡。 优势: 避免返回大量重复信息。 性能隐忧: 该实现需要将所有候选结果的 Embedding 加载到内存中构建相似度矩阵。在大规模候选集下，内存压力和计算开销不容忽视。 3.3 Node Distance (图距离重排序) # 代码: graphiti_core/search/search_utils.py -\u0026gt; node_distance_reranker 逻辑: 这是一种 图特有 的算法。它执行 Cypher 最短路径查询，优先返回那些在拓扑结构上距离 center_node 更近的事实。 场景: 当 Agent 聚焦于某个特定实体（如“分析 Alice 的社交圈”）时，此策略效果极佳。 3.4 Cross-Encoder (交叉编码器) # 代码: graphiti_core/cross_encoder/ 逻辑: 将 Query 和 Fact 拼接，送入模型直接打分。GeminiRerankerClient 是直接使用 Gemini 模型来打分，BGERerankerClient 使用专门的 Reranker 模型来打分。 OpenAI 实现的 Hack: Graphiti 的 OpenAIRerankerClient 有一个实现细节。它并没有使用专门的 Rerank API，而是构造了一个 Prompt 让 GPT-4o-mini 判断 True/False，并利用 Logprobs（对数概率）来计算置信度分数。 # 伪代码 logit_bias={\u0026#39;True_Token_ID\u0026#39;: 1, \u0026#39;False_Token_ID\u0026#39;: 1} logprobs = await client.chat.completions.create(..., logprobs=True) score = np.exp(logprobs[0].logprob) # 将对数概率转为 0-1 分数 这种做法虽然巧妙利用了生成式模型的能力，但相比专用 Rerank 模型（如 BGE, Cohere），在延迟和成本上通常更高。 4. 搜索流程全景图 # graph TD Query[User Query] --\u003e Embed[生成 Query Vector] subgraph Multi-way_Retrieval [多路召回] Embed --\u003e BM25[BM25 Search] Embed --\u003e Vector[Vector Search] Embed --\u003e BFS[BFS Graph Traversal] end BM25 --\u003e Candidates[候选集池] Vector --\u003e Candidates BFS --\u003e Candidates subgraph Reranking_Strategy [重排序策略] Candidates --\u003e RRF[RRF Fusion] Candidates --\u003e MMR[MMR Diversity] Candidates --\u003e NodeDist[Node Distance] Candidates --\u003e CrossEnc[Cross-Encoder] end RRF -.-\u003e Final[Top-K Results] MMR -.-\u003e Final NodeDist -.-\u003e Final CrossEnc -.-\u003e Final 5. 实现分析 # 5.1 忠实于“图”的本质 # Graphiti 的搜索不仅仅是 RAG 的“向量检索+”。它通过 BFS 和 Node Distance Reranker 真正利用了图的 连接性。这使得它不仅能回答“关于 X 的事实”，还能回答“与 X 相关联的事实”，这是其核心竞争力。\n5.2 性能与成本的权衡 # Cross-Encoder: 虽然精度最高，但其昂贵的 API（GeminiRerankerClient/OpenAIRerankerClient）调用成本（每一条候选边都要算一次）使其不适合作为初筛手段，只能用于最后的小范围精排。 并发控制: 为了应对多路搜索带来的并发压力，代码中广泛使用了 semaphore_gather。这是一种在应用层进行流控的必要手段，防止瞬间打满数据库连接池或触发 API Rate Limit。 总结 # Graphiti 的检索模块展示了一个成熟的搜索系统应有的灵活性。它通过配置化将简单的搜索原语组合成复杂的检索策略，既保留了向量检索的语义优势，又引入了图特有的拓扑推理能力。\n","date":"2025-10-26","externalUrl":null,"permalink":"/blogs/rag/graphiti-code-3/","section":"Blog","summary":"","title":"Graphiti：混合检索与重排序策略","type":"blogs"},{"content":" getzep/graphiti 引言 # 在前几篇文章中，介绍了 Graphiti 的核心引擎：如何构建图谱、维护时序逻辑以及执行混合检索。然而，一个优秀的开源项目不仅要有强大的内核，还需要有良好的 扩展性 和 互操作性。\nGraphiti 在这方面做了两件关键的事情：\n向内： 通过驱动抽象层（Driver Abstraction）兼容多种图数据库。 向外： 通过 MCP（Model Context Protocol）服务将能力标准化地暴露给 AI Agent。 1. 数据库驱动抽象：Open/Closed 原则的实践 # Graphiti 目前支持 Neo4j 和 FalkorDB 两种后端。为了避免在上层业务逻辑中充斥着 if db_type == 'neo4j': ... 的判断，它设计了一个 GraphDriver 抽象层。\n1.1 核心接口设计 # 在 graphiti_core/driver/driver.py 中，GraphDriver 定义了最小功能集：\nclass GraphDriver(ABC): @abstractmethod def execute_query(self, cypher_query_: str, **kwargs: Any) -\u0026gt; Coroutine: # 统一的异步查询接口 pass 上层逻辑（如搜索、写入）只依赖这个接口，完全不感知底层数据库的连接池、事务机制或协议细节。\n1.2 Cypher 适配 # 虽然 Neo4j 和 FalkorDB 都使用 Cypher 语言，但在向量索引和函数调用上存在显著差异。Graphiti 在 graphiti_core/graph_queries.py 中通过工厂函数模式解决了这个问题。\n案例：向量相似度计算\nNeo4j: 使用 vector.similarity.cosine，返回值是 [0, 1] 的相似度。 FalkorDB: 使用 vec.cosineDistance，返回值是距离。 代码通过 get_vector_cosine_func_query 函数动态生成适配的 Cypher 片段：\ndef get_vector_cosine_func_query(vec1, vec2, db_type=\u0026#39;neo4j\u0026#39;): if db_type == \u0026#39;falkordb\u0026#39;: # 手动将距离转换为相似度 return f\u0026#39;(2 - vec.cosineDistance({vec1}, vecf32({vec2})))/2\u0026#39; return f\u0026#39;vector.similarity.cosine({vec1}, {vec2})\u0026#39; 1.3 性能与兼容性的权衡 # FalkorDB 的驱动实现（falkordb_driver.py）为了兼容性做了一些妥协。例如，它需要在 Python 层面手动将 datetime 对象转换为 ISO 字符串，因为 FalkorDB 的原生驱动对时间类型的支持与 Graphiti 的期望不一致。这种转换虽然带来了一定的 CPU 开销，但保证了双时态逻辑在不同数据库上的一致性。\n2. MCP 服务：Agent 的“大脑外挂” # Graphiti 并不满足于做一个 Python 库，它通过 MCP (Model Context Protocol) 将自己变成了一个即插即用的服务。这使得 Claude Desktop、Cursor 等支持 MCP 的客户端可以直接“挂载” Graphiti 作为长时记忆。\n2.1 架构：FastMCP 与异步队列 # MCP 服务代码位于 mcp_server/graphiti_mcp_server.py。它使用了 FastMCP 框架来定义工具（Tools）。\n挑战： Graphiti 的写入操作（提取、去重、冲突检测）非常耗时，直接同步调用会阻塞 Agent 的交互界面，导致用户体验极差。\n解法：Fire-and-Forget 模式 服务器实现了一个基于 asyncio.Queue 的后台任务队列。\n# graphiti_mcp_server.py @mcp.tool() async def add_memory(...): # 将任务放入队列，不等待执行结果 await episode_queues[group_id].put(process_episode) # 立即返回成功响应 return SuccessResponse(message=\u0026#34;Episode queued for processing...\u0026#34;) 这种设计确保了 Agent 能够获得秒级的响应反馈，而复杂的图谱构建逻辑在后台从容执行。\n2.2 Schema 注入：个性化记忆 # Graphiti MCP Server 不仅仅是透传 API，它还通过定义特定的 Pydantic 模型（Requirement, Preference, Procedure）向系统注入了领域知识。\nENTITY_TYPES = { \u0026#39;Requirement\u0026#39;: Requirement, # 提取用户需求 \u0026#39;Preference\u0026#39;: Preference, # 提取用户偏好 \u0026#39;Procedure\u0026#39;: Procedure, # 提取操作流程 } 这告诉底层的 LLM 提取器：“请特别留意这三类信息”。这使得 Graphiti 从通用的知识图谱变成了 专属于 Agent 的个性化记忆体。\n2.3 cursor_rules.md：Prompt Engineering 的延伸 # 源码中包含的 cursor_rules.md 是系统的一部分。它不是给 Python 解释器看的，而是给 AI Agent（如 Cursor）看的。它明确规定了 Agent 的行为准则：\n\u0026ldquo;Search First\u0026rdquo;: 行动前必须先搜索记忆。 \u0026ldquo;Capture Immediately\u0026rdquo;: 发现新信息必须立即写入。 这展示了 AI 原生应用开发时，代码逻辑与提示词工程（Prompt Engineering）的深度融合。\n3. 总结 # Graphiti 这个时序知识图谱引擎 从单纯的“检索”走向“认知”与“记忆”：\n理念: 用“情节”和“双时态”解决 RAG 的状态缺失问题。 构建: 用 Embedding + LLM 实现智能的实体解析。 时序: 用语义冲突检测维护世界状态的演变。 检索: 用混合搜索策略挖掘深层关联。 生态: 用驱动抽象和 MCP 协议连接数据库与 Agent。 ","date":"2025-10-26","externalUrl":null,"permalink":"/blogs/rag/graphiti-code-4/","section":"Blog","summary":"","title":"Graphiti：驱动抽象与 MCP 服务","type":"blogs"},{"content":" getzep/graphiti 在构建智能 Agent 的过程中，\u0026ldquo;记忆（Memory）\u0026ldquo;一直是一个核心且棘手的工程挑战。传统的 RAG（Retrieval-Augmented Generation）技术栈——即将文档切片、向量化并存入向量数据库——虽然解决了静态知识的检索问题，但在面对动态、演进的世界状态时往往显得力不从心。例如，当用户昨天说“我住在纽约”，今天说“我搬到了旧金山”时，传统 RAG 可能会同时检索到这两条冲突的信息，却无法告知 Agent 哪条才是当前的真实状态。\nGraphiti 是一个旨在解决此类问题的开源框架。它定位于一个 动态的、时序感知的知识图谱引擎。作为系列博文的第一篇，下面将从宏观视角剖析 Graphiti 的核心概念、分层架构以及它如何试图通过工程手段解决动态知识管理难题。\n核心概念：从文档到情节 (From Documents to Episodes) # Graphiti 与传统知识库最大的区别在于其数据模型的设计。它不以“文档（Document）”为中心，而是引入了 “情节（Episode）” 作为最基础的数据摄入单元。\n1. Episodic Memory（情景记忆） # 在源码 graphiti_core/nodes.py 中，EpisodicNode 被定义为图谱中的一种特殊节点。它不仅存储原始数据（content），还记录了数据的来源类型（Message/JSON/Text）和发生时间（valid_at）。每一个被提取出的实体和事实，都会通过 MENTIONS 边回溯到其来源的 EpisodicNode。\n这种设计使得 Graphiti 能够保留 数据的上下文来源。当图谱中的某个事实（Edge）被检索时，系统不仅知道“发生了什么”，还能追溯到“是基于哪次对话或哪个文件得知的”。\n2. Bi-temporal Data Model（双时态数据模型） # Graphiti 试图解决的一个核心问题是 事实的时效性。在 graphiti_core/edges.py 的 EntityEdge 定义中，我们可以看到双时态设计的具体实现：\nSystem Time (系统时间): created_at 和 expired_at。记录数据何时被写入数据库，以及何时被标记为过时（逻辑删除）。这是不可变的审计日志。 Valid Time (有效时间): valid_at 和 invalid_at。记录事实在现实世界中生效和失效的时间范围。 通过维护 invalid_at，Graphiti 能够处理事实的 演变。当新信息与旧信息冲突时（例如“居住地变更”），系统不会覆盖旧记录，而是更新旧记录的 invalid_at，从而维护了一条连续的历史时间线。\n宏观架构解析 # Graphiti 的架构可以清晰地划分为接口层、核心引擎层和基础设施层。\ngraph TD User[Client / Agent] --\u003e Interface_Layer subgraph Interface_Layer [接口层] MCP[MCP Server] API[FastAPI Server] end MCP --\u003e Core[Graphiti Core Orchestrator] API --\u003e Core subgraph Graphiti_Core [核心引擎 graphiti_core] Core --\u003e Ingestion[增量摄入 Pipeline] Core --\u003e Retrieval[混合检索引擎] Ingestion --\u003e Extractor[实体/关系提取] Ingestion --\u003e Temporal[时序冲突检测] Ingestion --\u003e Dedupe[实体消歧与去重] Retrieval --\u003e HybridSearch[\"混合搜索 (BM25 + Vector + BFS)\"] Retrieval --\u003e Reranker[\"重排序 (RRF / CrossEncoder)\"] end subgraph Infrastructure [基础设施] LLM[\"LLM Client (OpenAI/Anthropic)\"] DB[\"(Graph DB: Neo4j / FalkorDB)\"] end Ingestion --\u003e LLM Ingestion --\u003e DB Retrieval --\u003e DB 1. 接口层 (Interface Layer) # 项目提供了两种对外的交互方式：\nREST API: 基于 FastAPI 实现的标准 HTTP 服务，位于 server/ 目录。 MCP Server: 位于 mcp_server/ 目录。这是一个基于 Model Context Protocol 的实现，允许 Claude Desktop 或 Cursor 等支持 MCP 的客户端直接调用 Graphiti 的能力（如 add_memory, search_nodes）。这表明 Graphiti 的设计初衷就是作为 Agent 的“外挂大脑”。 2. 核心引擎 (Graphiti Core) # 这是逻辑最密集的部分，位于 graphiti_core/。它主要包含两条路径：\n写入路径 (Write Path): 由 graphiti.py 中的 add_episode 方法编排。这是一个重度依赖 LLM 的流程。它不只是简单的写入，而是包含了一个 “读取-比对-写入” 的闭环：\n从文本中提取实体和关系。 利用 Embedding 检索图谱中已存在的相似实体（Entity Resolution）。 利用 LLM 判断新旧实体是否为同一对象（去重）。 检测新事实是否与旧事实在语义上冲突，并据此更新时间戳。 这种设计虽然保证了图谱的质量和一致性，但也带来了显著的性能开销。每次写入都需要多次 LLM 调用和数据库查询，使其成为一个 IO 密集型操作。\n读取路径 (Read Path): 由 search/ 模块负责。Graphiti 实现了一个复杂的混合检索策略。它不仅仅是向量搜索，还结合了全文检索（BM25）和图遍历（BFS）。\nConfigurable Strategy: 通过 SearchConfig 对象，开发者可以灵活配置检索策略。 Reranking: 支持 RRF（倒数排名融合）和 Cross-Encoder 等重排序算法，以在多路召回的结果中筛选出最相关的内容。 3. 基础设施层 (Infrastructure Layer) # Graphiti 采用了适配器模式来隔离底层依赖：\nGraphDriver: 在 driver/ 目录中，通过 GraphDriver 抽象基类屏蔽了 Neo4j 和 FalkorDB 的语法差异（如向量索引的创建和查询语法）。 LLMClient: 支持 OpenAI, Anthropic, Gemini 等多种模型提供商。 关键流程概览 # 增量构建 (Incremental Building) # Graphiti 的核心价值在于“增量”。不同于 GraphRAG 等需要全量文档构建索引的方案，Graphiti 旨在处理流式数据。当 add_episode 被调用时，它只处理当前片段，并尝试将其“编织”进现有的图谱网络中。这种编织过程依赖于复杂的实体解析算法，我们将在后续博文中详细分析。\n混合检索 (Hybrid Retrieval) # 在检索方面，Graphiti 并不迷信向量。源码显示，它在搜索边（Edge）时，会同时发起全文本检索（匹配关键词）、向量检索（匹配语义）和广度优先搜索（匹配拓扑结构）。这种多路召回策略旨在解决向量检索在处理精确匹配（如人名）时的精度问题，以及利用图谱特有的结构信息。\n总结 # Graphiti 试图用自动化的手段解决知识图谱构建中最繁琐的实体对齐和时序维护问题。然而，这种自动化是有代价的：它对 LLM 的推理能力和 Token 消耗有很高的依赖，且随着图谱规模的增长，冲突检测和去重的计算成本也会增加。\n","date":"2025-10-26","externalUrl":null,"permalink":"/blogs/rag/graphiti/","section":"Blog","summary":"","title":"Graphiti：全貌与架构 —— 为什么 RAG 还不够？","type":"blogs"},{"content":" getzep/graphiti 构建知识图谱的核心挑战在于 实体解析（Entity Resolution），即判断新提取的实体（如 \u0026ldquo;Apple\u0026rdquo;）是指向一个新的节点，还是与图中已存在的节点（如 \u0026ldquo;Apple Inc.\u0026quot;）指代同一对象。在传统 ETL 流程中，这通常依赖于基于规则的算法（如 Levenshtein 距离）。然而，在增量式、非结构化的数据流中，传统的规则往往难以应对语义的多义性和上下文的依赖性。\nGraphiti 在 add_episode 流程中实现了一套基于 Embedding 召回 与 LLM 判决 的两阶段去重机制。本文将依据源码，详细追踪数据从原始文本转化为图谱节点与边的完整路径，重点剖析其如何处理实体消歧与关系构建。\n1. 增量摄入总线：add_episode # 位于 graphiti_core/graphiti.py 的 add_episode 方法是写入路径的入口。它并非简单的线性流程，而是一个包含多次读写交互的闭环系统。\n该方法的主要逻辑步骤如下：\n上下文检索： 调用 retrieve_episodes 获取历史 Episode，作为 LLM 理解当前文本的背景。 实体提取： 调用 extract_nodes 从文本中识别实体。 实体解析与映射： 调用 resolve_extracted_nodes 对新实体进行去重，并建立 UUID 映射表。 关系提取： 调用 extract_edges 提取实体间的关系，并利用映射表修正 UUID 指针。 关系解析与冲突检测： 调用 resolve_extracted_edges 处理边的去重和时序冲突。 持久化： 调用 add_nodes_and_edges_bulk 将最终结果写入图数据库。 flowchart TD Start([Input: Episode Content]) --\u003e Context[Retrieve Previous Episodes] Context --\u003e ExtractNodes[\"1. Extract Nodes (LLM)\"] ExtractNodes --\u003e ResolveNodes[\"2. Resolve Nodes (Hybrid Search + LLM)\"] ResolveNodes -- UUID Mapping --\u003e ExtractEdges[\"3. Extract Edges (LLM)\"] ExtractEdges --\u003e ResolveEdges[4. Resolve Edges \u0026 Conflict Check] ResolveEdges --\u003e Persist[5. DB Persistence] subgraph Node_Resolution [节点解析逻辑] ResolveNodes --\u003e Search[Search Candidates] Search --\u003e DedupePrompt[LLM Deduplication] DedupePrompt --\u003e Map[Build UUID Map] end 2. 实体解析的核心实现：resolve_extracted_nodes # 实体解析逻辑封装在 graphiti_core/utils/maintenance/node_operations.py 中。这是一个典型的 RAG（检索增强生成）应用场景：先检索候选，再生成判断。\n2.1 第一阶段：基于混合搜索的候选召回 (Recall) # 对于每一个新提取的实体，Graphiti 并不会全表扫描，而是利用其搜索模块寻找相似的“候选旧实体”。\n# graphiti_core/utils/maintenance/node_operations.py search_results: list[SearchResults] = await semaphore_gather( *[ search( clients=clients, query=node.name, group_ids=[node.group_id], search_filter=SearchFilters(), config=NODE_HYBRID_SEARCH_RRF, # 使用 RRF 混合搜索配置 ) for node in extracted_nodes ] ) 这里使用了 NODE_HYBRID_SEARCH_RRF 配置（定义在 search_config_recipes.py），意味着它同时利用了：\nBM25 (全文检索): 捕捉字面拼写相似性（如 \u0026ldquo;Graphiti\u0026rdquo; vs \u0026ldquo;Graphiti Core\u0026rdquo;）。 Cosine Similarity (向量检索): 捕捉语义相似性（如 \u0026ldquo;iPhone Maker\u0026rdquo; vs \u0026ldquo;Apple\u0026rdquo;）。 这种混合召回策略有效地平衡了精确匹配和模糊语义匹配，大大缩小了后续 LLM 需要判断的范围。\n2.2 第二阶段：基于 LLM 的语义判决 (Adjudication) # 召回候选集后，Graphiti 将“新实体列表”和“候选旧实体列表”一并送入 LLM 进行裁决。\nPrompt: 位于 graphiti_core/prompts/dedupe_nodes.py 的 nodes 函数。\n# Prompt 结构摘要 content = f\u0026#34;\u0026#34;\u0026#34; \u0026lt;PREVIOUS MESSAGES\u0026gt;...\u0026lt;CURRENT MESSAGE\u0026gt;... # 上下文 \u0026lt;ENTITIES\u0026gt; # 新提取的实体 ... \u0026lt;EXISTING ENTITIES\u0026gt; # 搜索召回的候选旧实体 ... Task: For each entity, return... the duplicate_idx as an integer. - If an entity is a duplicate..., return the idx of the candidate it is a duplicate of. - If an entity is not a duplicate..., return -1. \u0026#34;\u0026#34;\u0026#34; 关键指令分析： Prompt 中明确指示 LLM 依据“语义等价性（Semantic Equivalence）”进行判断，即它们是否指代现实世界中的同一个对象。上下文（Messages）在这里起到了至关重要的消歧作用。例如，如果文中提到 \u0026ldquo;Java\u0026rdquo; 且上下文是地理，LLM 应当避免将其与编程语言 \u0026ldquo;Java\u0026rdquo; 节点合并。\n2.3 第三阶段：UUID 映射与修正 # LLM 返回的结果被解析为 NodeResolutions 对象。代码随即构建一个 uuid_map 字典。\nresolved_nodes = [] uuid_map = {} # {新实体临时UUID: 旧实体真实UUID} for resolution in node_resolutions: # ... if resolution.duplicate_idx != -1: # 判定为重复：使用旧节点 resolved_node = existing_nodes[resolution.duplicate_idx] uuid_map[extracted_node.uuid] = resolved_node.uuid else: # 判定为新实体：使用新节点 resolved_node = extracted_node # ... 这个 uuid_map 是后续步骤的关键。在提取边（Edge）时，LLM 输出的是基于新实体的临时 UUID。extract_edges 完成后，系统会立即使用 uuid_map 将边两端的节点 ID 替换为图谱中最终确认的真实 UUID。这确保了新提取的关系能够正确地“挂载”到现有的图谱结构上。\n3. 关系提取与处理：extract_edges 与 resolve_extracted_edges # 边的处理逻辑与节点类似，但在去重之外增加了更复杂的逻辑。\n3.1 关系提取 (Edge Extraction) # graphiti_core/utils/maintenance/edge_operations.py 中的 extract_edges 函数负责从文本中提取三元组。\n值得注意的是，Graphiti 支持自定义 Schema。add_episode 允许传入 edge_types 和 edge_type_map 参数。这些定义会被注入到 Prompt 中，引导 LLM 提取特定类型的关系（如 WORKS_FOR, IS_LOCATED_IN）。\n3.2 关系去重与 Embedding 过滤 # 与节点去重不同，边的去重逻辑更加依赖向量相似度。在 resolve_extracted_edges 中：\nScope 限制： 仅检索连接相同 Source 和 Target 节点的边。 Embedding 计算： 对新边的 fact 文本（如 \u0026ldquo;Alice works at Google\u0026rdquo;）计算 Embedding。 相似度过滤： 计算新边与候选边的 Cosine Similarity。只有相似度超过阈值（默认 0.6）的边才会被视为潜在重复项送入 LLM。 这种预过滤机制（dedupe_edges_bulk 中也有体现）显著减少了 LLM 的 Context 长度，因为边的描述文本通常比实体名称长得多，全部送入 LLM 成本过高。\n4. 批量模式下的优化 (Bulk Optimization) # 在 graphiti_core/utils/bulk_utils.py 中，针对 add_episode_bulk 场景，Graphiti 引入了额外的优化策略以应对大量数据的同时摄入。\n4.1 内存中去重 (In-Memory Deduplication) # 在批量处理时，新的一批数据内部可能存在大量重复（例如多个 Episode 都提到了 \u0026ldquo;Elon Musk\u0026rdquo;）。Graphiti 首先在内存中对这一批新提取的节点进行相互比对。\n为了提高效率，这里引入了一个基于规则的快速筛选层：\n# 近似 BM25：基于词重叠 (Word Overlap) node_words = set(node.name.lower().split()) existing_node_words = set(existing_node.name.lower().split()) has_overlap = not node_words.isdisjoint(existing_node_words) 只有当词重叠检测失败时，才回退到向量相似度计算。这种分层过滤策略在大规模数据处理中能有效降低计算开销。\n4.2 并查集 (Union-Find) 的应用 # 为了处理复杂的传递性重复关系（例如 A=B, B=C，则 A=C），代码中实现了 UnionFind 类和 compress_uuid_map 函数。这确保了在一个批次内，无论实体出现的顺序如何，所有指向同一对象的引用最终都会被统一映射到同一个规范 UUID 上。\n5. 实现分析 # 5.1 串行依赖导致的延迟 # 从代码流程来看，resolve_extracted_nodes（节点解析）必须在 extract_edges（关系提取）之前完成，因为关系提取依赖于节点解析生成的 uuid_map 来修正指针。而 resolve_extracted_edges（关系解析）又必须在关系提取之后进行。\n这种强串行依赖（Node Extract -\u0026gt; Node Resolve -\u0026gt; Edge Extract -\u0026gt; Edge Resolve）导致了处理单个 Episode 的端到端延迟较高。虽然代码在每一层内部使用了 semaphore_gather 进行并发处理（例如并发搜索多个实体的候选），但层与层之间无法流水线化。\n5.2 对 LLM 语义判断的依赖 # 系统的去重准确率高度依赖 LLM 在 dedupe_nodes Prompt 下的表现。源码中并未包含对 LLM 判决置信度的检查机制。如果 LLM 产生幻觉或误判（例如将 \u0026ldquo;Java Island\u0026rdquo; 误判为 \u0026ldquo;Java Language\u0026rdquo;），这种错误将被永久写入图谱数据库，且后续很难通过自动化手段发现和修复。\n总结 # Graphiti 的构建过程是一个 AI Native 工作流。它并没有试图用传统的确定性算法解决模糊的实体解析问题，而是通过 Search + LLM 的组合，将这一难题转化为一个语义理解任务。虽然这带来了较高的延迟和 Token 成本，但却赋予了系统处理非结构化、语义模糊数据的强大能力，这是构建高质量 Agent 记忆体的关键基础。\n","date":"2025-10-26","externalUrl":null,"permalink":"/blogs/rag/graphiti-code-1/","section":"Blog","summary":"","title":"Graphiti：实体解析与增量摄入","type":"blogs"},{"content":" getzep/graphiti 引言 # 在现实世界中，信息往往是流动的。“现任美国总统是拜登”这句话在 2024 年是事实，但在 2025 年可能就不再是了。传统的知识图谱或向量数据库通常只存储“当前状态”，对于过往的历史信息往往直接覆盖或丢弃。这种“健忘”的设计使得 AI Agent 难以理解世界的演变过程。\nGraphiti 最具创新性的设计之一，就是内置了对 时间 的“一等公民”支持。它实现了一个 双时态（Bi-temporal） 数据模型，并结合 LLM 的语义理解能力，构建了一套自动化的冲突检测与状态演变机制。本文将深入源码，剖析 Graphiti 是如何用代码捕捉“时间”的。\n1. 双时态数据模型：Valid Time vs System Time # 在 graphiti_core/edges.py 的 EntityEdge 类定义中，我们看到了四个与时间相关的字段。这并非冗余设计，而是为了实现双时态逻辑的标准范式。\n1.1 有效时间 (Valid Time) # valid_at (生效时间): 事实在现实世界中开始成立的时间。 invalid_at (失效时间): 事实在现实世界中停止成立的时间。 这两个字段定义了事实的 生命周期。例如，用户说“我 2020 年搬到了上海，2022 年离开了”，那么这条“居住在上海”的边，其 valid_at 为 2020，invalid_at 为 2022。如果 invalid_at 为 None，则表示该事实当前仍然有效（Present）。\n1.2 系统时间 (System Time) # created_at (创建时间): 这条记录被写入数据库的时间。 expired_at (过期时间): 这条记录被系统标记为“逻辑删除”或“被修正”的时间。 这两个字段服务于 审计与版本控制。它们记录了系统对世界认知变化的过程。\n2. 自动化时间提取 # Graphiti 不仅被动接收时间，还会主动从文本中提取时间信息。这一逻辑封装在 graphiti_core/utils/maintenance/temporal_operations.py 的 extract_edge_dates 函数中。\n2.1 相对时间解析 # LLM Prompt (prompts/extract_edge_dates.py) 接收了一个关键参数：reference_timestamp（通常是 Episode 的发生时间）。Prompt 被明确指示：\n\u0026ldquo;Use the reference timestamp as the current time when determining the valid_at and invalid_at dates.\u0026rdquo;\n这意味着如果用户说“我去年入职的”，LLM 会结合 reference_timestamp 计算出具体的年份并填充 valid_at。\n2.2 代码实现细节 # 源码中使用了 ensure_utc 辅助函数（位于 utils/datetime_utils.py），强制将所有提取出的时间转换为 UTC 时区。这是一个容易被忽视但至关重要的工程细节，它避免了跨时区部署时的时间线混乱。\n# graphiti_core/utils/datetime_utils.py def ensure_utc(dt: datetime | None) -\u0026gt; datetime | None: if dt is None: return None if dt.tzinfo is None: return dt.replace(tzinfo=timezone.utc) # ... 3. 冲突检测与状态演变 # 当新事实进入系统时，最复杂的问题出现了：新事实是否与旧事实冲突？ 如果冲突，系统该如何演变？\n这一核心逻辑由 resolve_extracted_edge 函数（edge_operations.py）编排。\n3.1 第一步：候选者筛选 (Candidate Selection) # 系统首先需要找到“可能”冲突的旧边。显然，检查全图是不现实的。Graphiti 使用 get_edge_invalidation_candidates 函数（search_utils.py）进行高效筛选：\n拓扑约束: 只查找连接相同 Source 和 Target 实体的边。 语义约束: 计算新边与旧边的 Embedding 相似度，只保留语义相关的边。 3.2 第二步：LLM 语义判决 (Semantic Adjudication) # 筛选出的候选边被送入 LLM，进行纯逻辑层面的矛盾判断。\nPrompt (prompts/invalidate_edges.py):\nBased on the provided EXISTING FACTS and a NEW FACT, determine which existing facts the new fact contradicts. LLM 不需要关心时间，只关心逻辑。例如：\n事实 A: \u0026ldquo;Alice likes Bob\u0026rdquo;\n事实 B: \u0026ldquo;Alice hates Bob\u0026rdquo;\n判定: 冲突。\n事实 A: \u0026ldquo;Alice lives in NY\u0026rdquo;\n事实 B: \u0026ldquo;Alice works in NY\u0026rdquo;\n判定: 不冲突（兼容）。\n3.3 第三步：时序逻辑解析 (Temporal Resolution) # 一旦 LLM 确认了语义冲突，resolve_edge_contradictions 函数（edge_operations.py）就会接管，根据时间戳执行确定性的更新逻辑。\n# graphiti_core/utils/maintenance/edge_operations.py def resolve_edge_contradictions(resolved_edge, invalidation_candidates): invalidated_edges = [] for edge in invalidation_candidates: # Case: 新事实 (resolved_edge) 在时间上覆盖了旧事实 (edge) if (edge.valid_at \u0026lt; resolved_edge.valid_at): # 关键操作：将旧边的失效时间 (invalid_at) 设为新边的生效时间 edge.invalid_at = resolved_edge.valid_at # 标记旧记录被修改的时间 edge.expired_at = utc_now() invalidated_edges.append(edge) return invalidated_edges 场景推演：\nT1: 系统记录 (Alice)-[LIVES_IN]-\u0026gt;(NY)，valid_at=2020。 T2: 用户输入 \u0026ldquo;Alice moved to SF in 2023\u0026rdquo;。 冲突检测: LLM 判定 \u0026ldquo;LIVES_IN NY\u0026rdquo; 与 \u0026ldquo;LIVES_IN SF\u0026rdquo; 语义冲突。 状态更新: 旧边 (NY) 被更新：invalid_at 设为 2023。 新边 (SF) 被写入：valid_at 设为 2023，invalid_at 为空。 结果是，Graphiti 自动维护了一条连续的时间线：2020-2023 在 NY，2023 至今在 SF。\n4. 逻辑流转图 # 下图展示了从新事实提取到旧事实失效的完整逻辑分支：\ngraph TD NewEdge[新提取的边] --\u003e FindCandidates[搜索: 连接相同节点的旧边] FindCandidates --\u003e VectorFilter[过滤: 语义相似度筛选] VectorFilter --\u003e LLM[LLM 判断: 是否存在逻辑矛盾?] LLM -- 无矛盾 --\u003e SaveNew[仅保存新边] LLM -- 有矛盾 --\u003e TimeCheck{时间检查} TimeCheck -- 新边时间 \u003e 旧边时间 --\u003e Invalidate[更新旧边: invalid_at = 新边.valid_at] Invalidate --\u003e SaveBoth[保存新边 + 更新旧边] TimeCheck -- 新边时间 \u003c 旧边时间 --\u003e Ignore[\"忽略新边 (或标记为历史补充)\"] SaveBoth --\u003e DB[(Graph Database)] 5. 实现分析 # 5.1 软删除与全量历史 # Graphiti 的设计哲学是“永不遗忘”。通过 invalid_at 标记，旧事实依然存在于数据库中。这虽然支持了强大的历史查询，但也带来了一个显著的工程挑战：数据膨胀。随着时间推移，数据库中可能充斥着大量已失效的历史数据。目前的源码中尚未包含自动化的归档（Archiving）或生命周期管理（TTL）机制，这在长期使用中可能需要额外的维护脚本。\n5.2 对 LLM 判决的依赖 # 整个时序逻辑的基石是 LLM 对“语义冲突”的判断。这是一个非确定性的环节。如果 LLM 误判（例如认为“喜欢苹果公司”和“喜欢吃苹果”冲突），系统可能会错误地将有效事实标记为失效。Prompt Engineering 在这里起到了决定性作用。\n总结 # Graphiti 通过双时态模型和 LLM 辅助的冲突检测，成功地将时间维度引入了知识图谱。它不再是一个静态的事实库，而是一个能够理解“变化”的动态系统。这种能力对于构建能够长期陪伴用户、理解用户状态演变的 AI Agent 至关重要。\n","date":"2025-10-26","externalUrl":null,"permalink":"/blogs/rag/graphiti-code-2/","section":"Blog","summary":"","title":"Graphiti：双时态逻辑与冲突处理","type":"blogs"},{"content":"","date":"2025-10-26","externalUrl":null,"permalink":"/tags/kg/","section":"Tags","summary":"","title":"KG","type":"tags"},{"content":"","date":"2025-10-26","externalUrl":null,"permalink":"/tags/rag/","section":"Tags","summary":"","title":"RAG","type":"tags"},{"content":" TL;DR # 核心理念：从提示工程到上下文工程 随着 AI 智能体（Agents）变得更加复杂，重点已从单纯的“提示工程（编写指令）”转向“上下文工程”。上下文工程旨在管理和优化进入模型有限上下文窗口（Context Window）的所有信息（系统提示、工具、数据、历史记录等）。\n1. 核心挑战：注意力是有限资源\n上下文腐烂 (Context Rot)：随着上下文中词元（Tokens）数量增加，模型准确回忆信息的能力会下降。 注意力预算：LLM 的注意力机制有其物理限制（n² 关系）。必须将上下文视为具有“边际收益递减”的稀缺资源。 指导原则：寻找能最大化预期结果可能性的、最小的高信噪比词元集合。 2. 优化上下文的三大支柱\n系统提示 (System Prompts)：寻找“最佳适中区（Goldilocks zone）”。避免过于僵化的硬编码逻辑（导致脆弱），也避免过于模糊的高层指导。提示应足够具体以指导行为，又足够灵活以利用模型的启发式能力。 工具 (Tools)：追求最小可行工具集。工具功能应自包含、无歧义且通过返回高效的词元来节省上下文。避免工具集臃肿导致智能体决策瘫痪。 示例 (Examples)：提供多样化、标准的（canonical）示例。对 LLM 而言，示例胜过千言万语。 3. 动态检索：从“预加载”到“即时 (Just-in-Time)”\n范式转变：不再预先将所有数据塞入上下文，而是让智能体维护轻量级引用（如文件路径），并在运行时通过工具自主检索数据。 渐进式披露：智能体通过探索逐步发现信息（如通过文件名、时间戳判断相关性），只在工作记忆中保留必要内容。 混合策略：对于特定任务（如 Claude Code），结合“预置关键信息”与“自主即时检索”以平衡速度与准确性。 4. 解决长时程任务 (Long-Horizon Tasks) 的三大技术 当任务时长超过上下文窗口限制时，需采用以下策略对抗上下文污染：\n压缩 (Compaction)：定期总结对话历史，保留关键决策和未解决问题，丢弃冗余信息（如清除过往的工具调用结果），以此重启上下文。 结构化笔记 (Structured Note-taking)：即“智能体记忆”。让智能体维护外部文件（如 NOTES.md），记录进度和依赖关系。这是实现持久化记忆的低开销方式。 子智能体架构 (Sub-agent Architectures)：主智能体负责协调，将具体任务分发给拥有干净上下文窗口的子智能体。子智能体进行广泛探索后，只返回提炼后的摘要。 AI Agent的有效上下文工程 # 原文：Effective context engineering for ai agents\n对于 AI Agent 而言，上下文（Context）是一种至关重要但有限的资源。在这篇文章中，我们将探讨如何有效地筛选和管理驱动这些 Agent 的上下文策略。\n在“提示工程（Prompt Engineering）”占据应用 AI 领域焦点数年之后，一个新的术语开始崭露头角：上下文工程（Context Engineering）。使用语言模型进行构建的重心，正逐渐从“为提示词寻找完美的字句”，转移到一个更宏观的问题上来：“什么样的上下文配置最有可能激发模型的预期行为？”\n上下文指的是在从大语言模型（LLM）进行采样时所包含的 Token 集合。我们面临的工程问题是：如何在 LLM 固有的约束下，优化这些 Token 的效用，以持续稳定地实现预期结果。高效地驾驭 LLM 通常需要具备上下文思维——换句话说：要考量 LLM 在任意给定时刻可用的整体状态，以及该状态可能引发的潜在行为。\n在本文中，我们将探讨上下文工程这门新兴的艺术，并为构建可控、高效的 Agent 提供一个精炼的心智模型。\n上下文工程 vs. 提示工程 # 在 Anthropic，我们将上下文工程视为提示工程的自然演进。提示工程指的是为获得最佳结果而编写和组织 LLM 指令的方法（参见我们的文档以获取概述和实用的提示策略）。上下文工程则指的是在 LLM 推理过程中，筛选和维护最佳 Token 集合（信息）的一系列策略，这涵盖了除提示词之外所有可能进入模型的信息。\n在 LLM 工程化的早期阶段，提示（Prompting）是 AI 工程工作中最大的组成部分，因为除日常聊天外的绝大多数用例都需要针对单样本分类（one-shot classification）或文本生成任务来优化提示。顾名思义，提示工程的主要焦点是如何编写有效的提示，尤其是系统提示（System Prompts）。然而，随着我们要构建更强大的、能够在多轮推理和更长时间范围内运行的 Agent，我们需要管理整个上下文状态（包括系统指令、工具、模型上下文协议 MCP、外部数据、消息历史等）的策略。\n一个在循环中运行的 Agent 会生成越来越多可能与下一轮推理相关的数据，这些信息必须被周期性地提炼。上下文工程既是一门艺术也是一门科学，它研究如何从那个不断膨胀的潜在信息宇宙中，精心筛选出哪些内容应当进入那个有限的上下文窗口。\n提示工程 vs. 上下文工程\n与编写提示这一离散的一次性任务不同，上下文工程是迭代的，每当我们决定向模型传递什么内容时，都会进行一次筛选（Curation）过程。 为什么上下文工程对构建强大的 Agent 至关重要 # 尽管 LLM 速度极快且能够处理海量数据，但我们观察到，它们像人类一样，在达到某个临界点后会失去焦点或感到困惑。关于“大海捞针（Needle-in-a-haystack）”式基准测试的研究揭示了上下文衰退（Context Rot）的概念：随着上下文窗口中 Token 数量的增加，模型从中准确回忆信息的能力会下降。\n虽然某些模型的性能下降曲线较为平缓，但这一特性在所有模型中都存在。因此，必须将上下文视为一种边际收益递减的有限资源。就像人类的工作记忆容量有限一样，LLM 在解析大量上下文时也有一个“注意力预算（Attention Budget）”。每引入一个新的 Token 都会消耗一部分预算，这就更需要我们仔细筛选提供给 LLM 的 Token。\n这种注意力的稀缺源于 LLM 的架构限制。LLM 基于 Transformer 架构，该架构允许每个 Token 关注整个上下文中的所有其他 Token。对于 n 个 Token，这会产生 n² 个成对关系。\n随着上下文长度的增加，模型捕捉这些成对关系的能力会被稀释，从而在“上下文大小”和“注意力焦点”之间产生一种自然的张力。此外，模型的注意力模式是在训练数据分布中形成的，而在训练数据中，较短的序列通常比较长的序列更常见。这意味着模型处理超长上下文依赖关系的经验较少，专门用于此的参数也较少。\n虽然像位置编码插值（Position Encoding Interpolation）这样的技术允许模型通过适配训练时的较小上下文来处理更长的序列，但这会伴随 Token 位置理解能力的某种程度下降。这些因素造成的是一个性能梯度而非断崖式下跌：模型在长上下文中仍然非常强大，但与短上下文相比，其在信息检索和长程推理方面的精度可能会降低。\n这些现实情况意味着，深思熟虑的上下文工程对于构建高能力的 Agent 至关重要。\n高效上下文的解构 # 既然 LLM 受限于有限的注意力预算，好的上下文工程就意味着找到能够最大化预期结果可能性的、最小的高信号（High-signal） Token 集合。知易行难，但在接下来的部分，我们将概述这一指导原则在上下文不同组件中的实践意义。\n系统提示（System Prompts）应该极其清晰，使用简单、直接的语言，并以合适的高度向 Agent 呈现构想。所谓的“合适高度”，是指介于两种常见失败模式之间的“金发姑娘区（Goldilocks zone，意指恰到好处）”。在极端的一端，我们看到工程师在提示中硬编码复杂的、脆弱的逻辑，试图引出精确的 Agent 行为。这种方法会导致系统脆弱，并随着时间推移增加维护的复杂性。在另一端，工程师有时会提供模糊、高层次的指导，这既无法给 LLM 提供具体的输出信号，又错误地假设了模型拥有共享的上下文背景。最佳的高度能达成一种平衡：既足够具体以有效指导行为，又足够灵活，能为模型提供强大的启发式方法（Heuristics）来引导行为。\n校准系统提示：在光谱的一端是脆弱的、基于 if-else 的硬编码提示；另一端则是过于笼统或错误假设共享上下文的提示。 我们建议将提示组织成不同的板块（例如 \u0026lt;background_information\u0026gt;、\u0026lt;instructions\u0026gt;、## Tool guidance、## Output description 等），并使用 XML 标签或 Markdown 标题等技术来划分这些部分。不过，随着模型能力的提升，提示的确切格式正变得不再那么重要。\n无论你决定如何构建系统提示，都应致力于使用最小的信息集来完整勾勒出你预期的行为。（注：最小并不一定意味着简短；你仍然需要在前期给予 Agent 足够的信息以确保它遵守预期。）最好的做法是，首先用可用的最强模型测试一个最小化的提示，观察其在任务上的表现，然后根据初始测试中发现的失败模式，添加清晰的指令和示例以提升性能。\n**工具（Tools）**允许 Agent 与环境交互，并在工作时引入新的、额外的上下文。因为工具定义了 Agent 与其信息/行动空间之间的契约，所以工具必须能够提升效率——既要返回 Token 高效（token efficient）的信息，也要能鼓励高效的 Agent 行为。\n在《为 AI Agent 编写工具》一文中，我们讨论了如何构建易于 LLM 理解且功能重叠最小的工具。与设计良好的代码库中的函数类似，工具应该是自包含的、对错误具有鲁棒性的，并且其预期用途应极其明确。输入参数同样应该是描述性的、无歧义的，并能发挥模型的固有优势。\n我们看到最常见的失败模式之一是工具集臃肿，涵盖了过多的功能，或者导致在使用哪个工具时出现模棱两可的决策点。如果人类工程师都无法确定在特定情况下该用哪个工具，就别指望 AI Agent 能做得更好。正如我们将要讨论的，为 Agent 筛选一个**最小可行工具集（Minimal Viable Set of Tools）**也有助于在长时间交互中更可靠地维护和修剪上下文。\n提供示例（即常说的小样本提示 Few-shot Prompting）是一个我们持续强烈推荐的最佳实践。然而，团队往往试图在提示中塞入一长串边缘案例清单，试图穷尽 LLM 在特定任务中应遵循的所有规则。我们不推荐这样做。相反，我们建议精心筛选一组多样化的、典型的示例，有效地展示 Agent 的预期行为。对于 LLM 来说，示例就是“胜过千言万语的图片”。\n对于上下文的不同组成部分（系统提示、工具、示例、消息历史等），我们的总体建议是：深思熟虑，让你的上下文既信息丰富又保持精炼。现在，让我们深入探讨在运行时（Runtime）动态检索上下文。\n上下文检索与 Agent 式搜索 # 在《构建高效的 AI Agent》中，我们强调了基于 LLM 的工作流与 Agent 之间的区别。自那篇文章发布以来，我们倾向于对 Agent 下一个简单的定义：LLM 在循环中自主使用工具。\n通过与客户的合作，我们看到该领域正趋向于这个简单的范式。随着底层模型变得更加强大，Agent 的自主性水平也可以随之扩展：更智能的模型允许 Agent 独立地探索细微的问题空间并从错误中恢复。\n我们现在看到工程师在为 Agent 设计上下文时的思维发生了转变。如今，许多 AI 原生应用采用某种形式的基于嵌入（Embedding-based）的推理前检索，为 Agent 提供推理所需的重要上下文。随着领域向更具 Agent 特性的方法演进，我们越来越多地看到团队使用“即时（Just-in-time）”上下文策略来增强这些检索系统。\n采用“即时”方法的 Agent 不会预先处理所有相关数据，而是维护轻量级的标识符（文件路径、存储的查询、网页链接等），并使用这些引用在运行时通过工具动态地将数据加载到上下文中。Anthropic 的 Agent 编码解决方案 Claude Code 就使用这种方法对大型数据库执行复杂的数据分析。模型可以编写有针对性的查询、存储结果，并利用像 head 和 tail 这样的 Bash 命令来分析大量数据，而无需将完整的数据对象加载到上下文中。这种方法模仿了人类的认知：我们通常不会背诵整个信息库，而是引入外部组织和索引系统（如文件系统、收件箱和书签），以便按需检索相关信息。\n除了存储效率外，这些引用的元数据（Metadata）还提供了一种有效改进行为的机制，无论是显式的还是隐式的。对于在一个文件系统中操作的 Agent 来说，一个位于 tests 文件夹下的 test_utils.py 文件，其意图显然不同于位于 src/core_logic/ 下的同名文件。文件夹层级、命名约定和时间戳都提供了重要的信号，帮助人类和 Agent 理解如何以及何时利用信息。\n让 Agent 自主导航和检索数据还实现了渐进式披露（Progressive Disclosure）——换句话说，允许 Agent 通过探索逐步发现相关上下文。每一次交互都会产生为下一次决策提供信息的上下文：文件大小暗示了复杂性；命名约定暗示了用途；时间戳可以作为相关性的参考。Agent 可以逐层构建理解，只在工作记忆中保留必要的内容，并利用笔记策略实现额外的持久化。这种自我管理的上下文窗口使 Agent 能够专注于相关的子集，而不是淹没在详尽但可能无关的信息中。\n当然，这里存在权衡：运行时探索比检索预先计算好的数据要慢。不仅如此，还需要有主见且深思熟虑的工程设计，以确保 LLM 拥有有效导航其信息环境的正确工具和启发式方法。如果没有适当的引导，Agent 可能会因滥用工具、钻牛角尖或未能识别关键信息而浪费上下文资源。\n在某些场景下，最有效的 Agent 可能会采用一种混合策略：为了速度预先检索一些数据，并自行决定进行进一步的自主探索。决定“正确”自主性水平的边界取决于任务本身。Claude Code 就是一个采用这种混合模型的 Agent：CLAUDE.md 文件会被简单粗暴地预先放入上下文，而像 glob 和 grep 这样的原语则允许它导航环境并即时检索文件，从而有效地绕过了索引陈旧和复杂语法树的问题。\n对于内容动态性较低的上下文（例如法律或金融工作），混合策略可能更适合。随着模型能力的提升，Agent 设计的趋势将是让智能模型表现得更智能，逐步减少人类的干预。鉴于该领域日新月异的发展速度，对于在 Claude 之上构建 Agent 的团队来说，“做最简单有效的事”可能仍是我们最好的建议。\n针对长时程任务的上下文工程 # 长时程任务（Long-horizon tasks）要求 Agent 在一系列动作中保持连贯性、上下文和目标导向行为，而这些动作序列产生的 Token 数量往往超过 LLM 的上下文窗口。对于跨越数十分钟到数小时连续工作的任务（例如大型代码库迁移或综合性研究项目），Agent 需要专门的技术来绕过上下文窗口大小的限制。\n等待更大的上下文窗口似乎是一个显而易见的策略。但很可能在可预见的未来，所有大小的上下文窗口都会受到上下文污染和信息相关性问题的影响——至少在追求极致 Agent 性能的情况下是如此。为了使 Agent 能够在长时间范围内有效工作，我们开发了一些直接应对这些上下文污染限制的技术：压缩（Compaction）、结构化笔记（Structured note-taking）和多 Agent 架构（Multi-agent architectures）。\n压缩 (Compaction) # 压缩是指当对话接近上下文窗口限制时，总结其内容，并用该摘要重新启动一个新的上下文窗口的做法。压缩通常是上下文工程中提升长期连贯性的第一个手段。其核心在于，压缩能以高保真度的方式提炼上下文窗口的内容，使 Agent 能够以最小的性能损耗继续工作。\n例如，在 Claude Code 中，我们通过将消息历史传递给模型，让其总结和压缩最关键的细节来实现这一点。模型会保留架构决策、未解决的 Bug 和实现细节，同时丢弃冗余的工具输出或消息。然后，Agent 可以基于这个压缩后的上下文加上最近访问的五个文件继续工作。用户获得了连续性，而无需担心上下文窗口的限制。\n压缩的艺术在于取舍（保留什么 vs 丢弃什么），因为过于激进的压缩可能导致细微但关键的上下文丢失，而其重要性可能在之后才会显现。对于实现压缩系统的工程师，我们建议在复杂的 Agent 追踪记录（Traces）上仔细调整你的提示。首先最大化召回率（Recall），以确保你的压缩提示捕获了追踪中的每一个相关信息，然后通过消除多余内容来迭代提高精确率（Precision）。\n一个容易剔除的多余内容的例子是清除工具调用和结果——一旦一个工具在消息历史的深处被调用过，Agent 为什么还需要再次看到原始结果呢？最安全、最轻量的压缩形式之一是工具结果清除，这也是 Claude 开发者平台最近推出的一项功能。\n结构化笔记 (Structured note-taking) # 结构化笔记，或称 Agent 记忆（Agentic Memory），是一种 Agent 定期将笔记写入并持久化到上下文窗口之外的存储中的技术。这些笔记会在稍后的时间被拉回上下文窗口。\n这种策略以最小的开销提供了持久性记忆。就像 Claude Code 创建一个待办事项列表，或者你的自定义 Agent 维护一个 NOTES.md 文件一样，这个简单的模式允许 Agent 在复杂任务中跟踪进度，维护那些否则会在数十次工具调用中丢失的关键上下文和依赖关系。\nClaude 玩《精灵宝可梦（Pokémon）》的例子展示了记忆如何在非编码领域改变 Agent 的能力。该 Agent 在数千个游戏步骤中保持精确的记录——追踪诸如“在过去的 1,234 步中，我一直在 1 号道路训练我的宝可梦，皮卡丘已经升了 8 级，目标是 10 级”这样的目标。在没有任何关于记忆结构的提示下，它绘制了已探索区域的地图，记住了它解锁了哪些关键成就，并维护了战斗策略笔记，帮助它学习哪些攻击对不同对手最有效。\n在上下文重置后，Agent 会阅读自己的笔记，并继续进行数小时的训练序列或地牢探索。这种跨越摘要步骤的连贯性使得长时程策略成为可能，而如果仅靠将所有信息保留在 LLM 的上下文窗口中，这是不可能实现的。\n作为我们 Sonnet 4.5 发布的一部分，我们在 Claude 开发者平台上以公开测试版的形式发布了一个记忆工具，它通过一个基于文件的系统，使得在上下文窗口之外存储和查阅信息变得更加容易。这使得 Agent 能够随着时间推移建立知识库，跨会话维护项目状态，并引用以前的工作，而无需将所有内容都塞在上下文中。\n子 Agent 架构 (Sub-agent architectures) # 子 Agent 架构提供了另一种绕过上下文限制的方法。与其让一个 Agent 试图在整个项目中维护状态，不如让专门的子 Agent 使用干净的上下文窗口来处理聚焦的任务。主 Agent 负责制定高层计划并进行协调，而子 Agent 则执行深入的技术工作或使用工具查找相关信息。每个子 Agent 可能会进行广泛的探索，消耗数万甚至更多的 Token，但最终只返回其工作的浓缩、提炼后的摘要（通常为 1,000-2,000 个 Token）。\n这种方法实现了明确的关注点分离（Separation of Concerns）——详细的搜索上下文被隔离在子 Agent 内部，而主导 Agent 则专注于综合和分析结果。这种模式在《我们如何构建多 Agent 研究系统》中进行过讨论，在复杂的研究任务上，它显示出比单 Agent 系统有显著的改进。\n在这些方法之间进行选择取决于任务的特性。例如：\n压缩适合需要大量来回交流的任务，能保持对话流畅性； 笔记在具有明确里程碑的迭代开发中表现出色； 多 Agent 架构擅长处理复杂的研究和分析，这类任务中并行探索能带来巨大回报。 即使模型不断改进，在长时间交互中保持连贯性的挑战，仍将是构建更高效 Agent 的核心问题。\n结论 # 上下文工程代表了我们基于 LLM 构建应用方式的根本性转变。随着模型变得越来越强大，挑战不再仅仅是打磨完美的提示词——而是在每一步都深思熟虑地筛选哪些信息能进入模型有限的注意力预算。无论你是在为长时程任务实施压缩，设计 Token 高效的工具，还是让 Agent 能够即时探索其环境，指导原则始终如一：找到能最大化你预期结果可能性的、最小的高信号 Token 集合。\n我们概述的这些技术将随着模型的改进而继续演进。我们已经看到，更智能的模型需要更少的指令式工程，允许 Agent 以更高的自主性运行。但即使能力不断扩展，将上下文视为一种宝贵的、有限的资源，仍将是构建可靠、高效 Agent 的核心准则。\n立即开始在 Claude 开发者平台中使用上下文工程，并通过我们的记忆和上下文管理手册获取实用的技巧和最佳实践。\n","date":"2025-10-03","externalUrl":null,"permalink":"/blogs/agent/effective-context-engineering-for-ai-agents-claude/","section":"Blog","summary":"","title":"AI Agent的有效上下文工程-Anthropic","type":"blogs"},{"content":"","date":"2025-10-03","externalUrl":null,"permalink":"/tags/%E7%BF%BB%E8%AF%91/","section":"Tags","summary":"","title":"翻译","type":"tags"},{"content":"","date":"2025-10-01","externalUrl":null,"permalink":"/tags/llm/","section":"Tags","summary":"","title":"LLM","type":"tags"},{"content":"","date":"2025-10-01","externalUrl":null,"permalink":"/tags/lora/","section":"Tags","summary":"","title":"LoRA","type":"tags"},{"content":" blog\nTL;DR # Thinking Machines 通过大量实验证明，在后训练（Post-Training）阶段，只要正确掌握关键细节，LoRA 能够以更高的计算效率和更低的内存占用，达到与全量微调（FullFT）完全相同的最终性能和样本效率。这被称为“低悔恨区域（low-regret regime）”。\n关键实验发现 # 1. 监督微调（SFT）的表现\n中小数据集：在指令微调和推理任务的中小型数据集上，LoRA 的表现与 FullFT 持平。 容量限制：当数据集过大且超过 LoRA 的参数容量时，性能会不如 FullFT。 Batch Size 敏感性：LoRA 对大批量（Large Batch Size）的容忍度低于 FullFT。当批量过大时，无论秩（Rank）多高，LoRA 的损失都会受到惩罚。 2. 强化学习（RL）的表现\n极低秩依然有效：在 RL（如 PPO/GRPO）中，即使 Rank=1，LoRA 也能完全匹配 FullFT 的性能。 理论解释：基于信息论，RL 每个 episode 提供的有效信息量极低（约 1 bit，仅依靠标量奖励信号），因此不需要像监督学习那样大的参数容量。 3. LoRA 的应用范围（关键！）\n必须覆盖所有层：要达到 FullFT 的效果，必须将 LoRA 应用于所有权重矩阵，特别是 MLP 和 MoE 层。 仅 Attention 是不够的：仅在 Attention 层使用 LoRA 效果明显较差，即使增加秩也无法弥补这一差距。 超参数与工程细节 # 学习率（LR）设置：\nLoRA 的最佳学习率通常是 FullFT 的 10 倍。 由于引入了 $1/r$ 的缩放因子，LoRA 的最佳学习率在不同秩（Rank）下大致保持不变（独立于秩）。 对于短时间的训练任务，可能需要更高的学习率乘数（约 15 倍）。 计算效率：\nLoRA 的训练计算量（FLOPs）约为 FullFT 的 2/3（因为反向传播时无需计算庞大权重矩阵的梯度）。 LoRA 在多租户服务（Multi-tenant serving）和显存占用上具有天然优势。 实操建议总结 # 范围：对所有层（特别是 MLP/MoE）启用 LoRA，不要只微调 Attention。 LR：将学习率设置为全量微调最佳 LR 的 10 倍左右。 Batch Size：避免使用过大的 Batch Size。 RL 场景：在强化学习中可以放心使用非常低的秩（如 Rank 8-16），效果不会打折。 引言 # 当今领先的语言模型包含超过万亿参数，在数十万亿tokens上进行预训练。基础模型的性能随着规模不断提升，因为这些万亿参数对于学习和表示书面人类知识中的所有模式是必要的。\n相比之下，后训练涉及更小的数据集，通常聚焦于更狭窄的知识领域和行为范围。使用太字节（terabit）的权重来表示来自千兆字节（gigabit）或兆字节（megabit）训练数据的更新似乎是浪费的。这种直觉激发了参数高效微调（Parameter Efficient Fine-Tuning，PEFT），它通过更新一组更小的参数来调整大型网络。\n领先的PEFT方法是低秩适应（Low-Rank Adaptation），即LoRA。LoRA将原始模型中的每个权重矩阵 \\(W\\) 替换为修改后的版本 \\(W' = W + \\gamma BA\\) ，其中B和A是矩阵，它们一起包含的参数远少于W，\\(\\gamma\\) 是一个常数缩放因子。实际上，LoRA创建了微调所带来更新的低维表示。\nLoRA可能在后训练的成本和速度方面提供优势，并且还有一些操作性原因使其优于完整微调（以下简称FullFT）：\n多租户服务（Multi-tenant serving）。由于LoRA训练一个适配器（即A和B矩阵）同时保持原始权重不变，单个推理服务器可以在内存中保留许多适配器（不同的模型版本）并以批处理方式同时从它们采样。Punica: Multi-Tenant LoRA Serving（Chen, Ye等，2023）现代推理引擎如vLLM和SGLang实现了此功能。 训练的布局大小（Layout size for training）。当微调整个模型时，优化器状态需要与原始权重一起存储，通常以更高的精度存储。因此，FullFT通常需要比从同一模型采样多一个数量级的加速器，从而需要不同的布局。对于训练，除了存储权重外，我们通常还需要为所有权重存储梯度和优化器动量；此外，这些变量通常以比推理时用于存储权重的精度（bfloat16或更低）更高的精度（float32）存储。由于LoRA训练的权重少得多且使用的内存少得多，它可以在仅比用于采样的布局稍大的布局上进行训练。这使得训练更容易获得，并且通常更高效。 加载和传输的便利性（Ease of loading and transfer）。由于存储的权重更少，LoRA适配器在设置或在机器之间传输时速度快且容易。 这些原因足以解释自2021年原始LoRA论文发表以来LoRA日益增长的流行度。LoRA: Low-Rank Adaptation of Large Language Models（Hu等，2021）然而，文献对于LoRA相对于FullFT的表现如何尚不清楚。\n人们一致认为LoRA在类似预训练的设置中表现不佳，LoRA Learns Less and Forgets Less（Biderman等，2024）即那些具有非常大数据集且超过LoRA参数存储限制的情况。但对于后训练中典型的数据集大小，LoRA有足够的容量来存储必要的信息。然而，这一事实并不保证样本效率和计算效率。问题是：LoRA能否匹配完整微调的性能，如果能，在什么条件下？\n在我们的实验中，我们发现确实，当我们正确掌握几个关键细节时，LoRA以与FullFT相同的样本效率学习并达到相同的最终性能。\n对于LoRA重要的因素 # 本文涵盖了我们进行的一系列监督微调和强化学习实验，以确定LoRA在哪些条件下匹配FullFT效率。为此，我们与之前关于LoRA的实验做了一些不同的事情：\n我们研究了训练集大小和LoRA参数数量之间的一般关系，而不是专注于特定的数据集和任务。 在监督学习中，我们测量对数损失（log loss）而不是采用基于采样的评估，同样以普遍性为目标。对数损失测量在训练步数和训练参数的范围内给出了干净的结果和缩放定律（scaling laws）。 我们发现：\n对于小型到中型指令调整和推理数据集上的监督微调，LoRA的表现与完整微调相同。 对于超过LoRA容量的数据集，LoRA表现不如FullFT。损失并非达到一个无法突破的明确下限，而是LoRA导致训练效率下降，这取决于模型容量与数据集大小之间的关系。 在某些情况下，LoRA对大批量大小的容忍度低于完整微调——当批量大小增加超过某一点时，它在损失方面付出更大的代价。增加LoRA秩（rank）无法缓解这一惩罚；这是矩阵乘积参数化的一个特性，它具有与优化原始权重矩阵不同的训练动态。 即使在小数据设置中，当应用于所有权重矩阵，特别是MLP和MoE层时，LoRA表现更好。仅注意力（attention-only）LoRA表现不佳，即使我们通过对仅注意力LoRA使用更高的秩来匹配可训练参数的数量。 即使秩很小，LoRA在强化学习中的表现也等同于FullFT。我们发现RL需要非常低的容量，基于信息论论证，我们预期到这一结果。 我们还研究了用于LoRA的超参数对其相对于完整微调的学习率的影响。我们检查了超参数（如初始化尺度和乘数）中的一些不变性，并解释为什么1/r预因子使最优学习率（LR）近似独立于秩。我们还通过实验展示了LoRA的最优LR与FullFT的最优LR之间的关系。\n我们实验的结果是对\u0026quot;低悔恨区域\u0026quot;（low-regret regime）的描述，在该区域中，LoRA在数据集大小和LoRA参数方面的表现与FullFT相似。我们发现这个区域涵盖了大多数后训练场景，为在许多应用中使用高效微调打开了大门。\n方法和结果 # 我们设计实验来详细测量LoRA在一系列条件下相对于FullFT的性能。以下是我们实验设置的一些细节：\n我们在三个数量级上改变LoRA秩，秩在1到512之间，并将这些与完整微调进行比较。 为了消除使用次优学习率带来的潜在混淆，我们为每个实验条件扫描了LR。我们使用恒定学习率计划（无预热或冷却）。 我们的实验使用了Llama 3系列模型 The Llama 3 Herd of Models（Dubey等，2024）和Qwen3模型 Qwen3 Technical Report（Qwen团队，2025），包括一个混合专家（MoE）模型。 主要的监督学习实验使用了Tulu3 Tulu 3: Pushing Frontiers in Open Language Model Post-Training（Ivison等，2024）和OpenThoughts3 OpenThoughts: Data Recipes for Reasoning Models（Guha等，2025）数据集，分别聚焦于指令遵循和推理。这两组在范围、结构和应用上差异显著，支持了我们结果的普遍性。 我们的RL实验使用数学推理任务，以答案正确性作为奖励。 LoRA秩 # 我们在Tulu3数据集和OpenThoughts3数据集的子集上训练了一个epoch。对于每个数据集和模型大小，我们扫描了LoRA秩和学习率。在下面的图中，我们为每个秩绘制一条彩色线，其中该线是通过在每个训练步骤取所有学习率上的逐点最小值获得的：\n图1：Tulu3和OpenThoughts3数据集上各种等级的LoRA训练曲线。FullFT和高等级LoRA具有相似的学习曲线，损失随着步数的对数线性下降。当适配器容量不足时，低等级LoRA会脱离最小损失曲线。在底部图表中（1B模型），高等级LoRA在一个数据集上表现优于FullFT，而在另一个数据集上表现较差。由于训练动态或泛化行为的差异，LoRA在不同数据集上的表现可能存在一些随机变化。 我们看到FullFT和高秩LoRA具有相似的学习曲线，损失随步数的对数线性减少。中等和低秩LoRA在某个与秩相关的步数阈值处脱离最小损失学习曲线。直观地说，当适配器耗尽容量时学习会减慢，而容量又由秩决定。\n接下来，我们绘制损失如何随LR变化，以检查我们的扫描是否涵盖了每个秩的最佳学习率。\n图2：在 Tulu3 上，各种 LoRA 秩的学习率与最终损失。高秩 LoRA 和 FullFT 的最小损失大致相同。LoRA 的最佳 LR 高 10 倍。 我们发现FullFT的最优学习率比高秩LoRA低10倍。参见Biderman等（2024），图S1，对于使用采样评估的实验，它发现了类似的10倍比率。我们将在稍后讨论LoRA超参数时回到这一点。\n所有不同秩的LoRA运行的最优LR似乎相似；我们在下面为这一发现提供理论解释。然而，确实存在一些秩依赖性，Rank=1的最优LR低于更高秩LoRA。在Rank=4和Rank=512之间，最优LR变化不到2倍。\n批量大小效应 # 我们发现在某些设置中，LoRA对大批量大小的容忍度低于FullFT。性能差距随着更大的批量大小而增长，与秩无关。对于下一个实验，我们使用了 OpenThoughts3 的一个小型10,000示例子集。\n图3：批量大小对LoRA与FullFT性能的影响。左图：不同批量大小的学习曲线显示，在较大的批量大小下，LoRA（虚线）和FullFT（实线）之间存在持续的差距。右图：最终损失作为批量大小的函数，显示LoRA为增加的批量大小付出了更大的代价。 图3中的左侧图显示，在大批量大小下，LoRA（虚线）和FullFT（实线）学习曲线之间存在持续的差距。对于较小的批量大小32，差距较小并随时间缩小。\n右侧图表将最终损失绘制为批量大小的函数。我们看到LoRA的损失差距随着更大的批量大小而越来越偏离FullFT。\n大批量下的学习差距似乎不依赖于秩，而是LoRA的一个特性。可能的原因是矩阵乘积参数化（BA）在该数据集上的优化动态不如完整矩阵（W）有利。然而，LoRA和FullFT都在较小的批量大小下达到最佳损失，因此这个差距在实践中可能不那么重要。\n应用LoRA的层 # 我们研究了将LoRA应用于网络中不同层的效果。Hu等的原始论文建议仅将LoRA应用于注意力矩阵，许多后续论文也遵循了这一建议，尽管最近的趋势是将其应用于所有层。与我们的结果类似，QLoRA论文也发现LoRA表现不如MLP或MLP+注意力，尽管他们发现MLP+注意力 \u0026gt; MLP \u0026gt; 注意力，而我们发现前两者大致相同。实际上，当将LoRA应用于所有层，特别是MLP（包括MoE）层时，我们获得了更好的结果。事实上，将LoRA应用于注意力矩阵除了应用于MLP外没有显示出额外的好处。Biderman等（2024）获得了类似的结果，仅注意力LoRA在仅MLP之上没有提供额外好处。\n图4：仅注意力机制的LoRA明显不如仅MLP的LoRA，并且在LoRA-on-MLP的基础上无法进一步提高性能。这种效应适用于密集模型（Llama-3.1-8B）和稀疏MoE（Qwen3-30B-A3B-Base）。 仅注意力LoRA的表现不佳不能用参数较少来解释。在这个特定案例中，秩256的仅注意力版本表现不如秩128的仅MLP版本，尽管它们的参数数量大致相同。（比较下表中的粗体数字。）\nLlama-3.1-8B 上 LoRA 的参数计数 对于MoE实验，我们为每个专家训练了一个单独的LoRA，每个专家的秩等于总秩除以活跃专家的数量（Qwen3 MoE等于8）。这种缩放使MoE层的LoRA参数与FullFT参数的比率与其他层相同。\n我们在另外两个设置中进行了类似的实验，比较不同的LoRA层：（1）在OpenThoughts3数据集的小子集上进行监督学习，秩=256，以及（2）在MATH数据集上进行强化学习。我们在下一节描述我们的实验设置。在这些设置中，仅注意力LoRA表现也不如仅MLP LoRA（其表现与MLP+注意力相似）。\n图5：当改变我们应用LoRA的层时，学习率与最终损失或奖励的关系。 强化学习 # 我们实验的一个关键发现是，当运行强化学习的策略梯度算法时，即使秩低至1，LoRA也完全匹配FullFT的学习性能。\n对于这些实验，我们使用了一个基本的策略梯度算法，带有重要性采样修正；\\(\\text{objective} =\\sum_t \\frac{p_{\\text{learner}}}{p_{\\text{sampler}}} Adv_t\\)（参见 Your Efficient RL Framework Secretly Brings You Off-Policy RL Training\n）。我们使用了类似GRPO的中心化方案🔗（Shao等，2024），其中我们为每个问题采样多个完成并减去每组的平均奖励。\n图6（下方）显示了在MATH 🔗（Hendrycks等，2021）和GSM 🔗（Cobbe等，2021）数据集上的LR扫描，为每个使用典型的超参数。我们使用Llama-3.1-8B基础模型，因为已知Qwen2.5和Qwen3在预训练数据上改善了其数学性能，如Qwen技术报告所述🔗（Qwen团队，2024），这使得更难测量仅在RL期间学习的内容。\nLoRA显示出更宽的有效学习率范围，并达到与FullFT（黑线）相同的峰值性能，至少在RL的噪声所允许的精度限制内。\n图6：在小学数学（GSM，左图）或 MATH（右图）数据集上进行强化学习时，学习率与最终奖励（准确率）的关系。 这一结果由信息论论证所预期。监督学习可以说每个episode提供O（token数量）比特。相比之下，在策略梯度方法中，学习由优势函数（advantage function）驱动，该函数每个episode仅提供O（1）比特。当每个episode包含数千个token时，RL每个训练token吸收的信息比监督学习少约1000倍。\n我们可以根据我们的实验使用更精确的数字。在MATH示例中，我们训练了约10,000个问题，每个问题32个样本。假设每个完成产生一个信息比特，整个训练过程只需要吸收320,000比特。Llama-3.1-8B的rank-1 LoRA已经有300万个参数（我们通过在模型中所有权重矩阵上相加 \\(rank·d_{in}\\)（对于矩阵A）和 \\(rank·d_{out}\\)（对于矩阵B）来计算这一点。），几乎是那个数字的10倍。即使在rank-1时，LoRA也有足够的容量来吸收训练期间提供的所有信息。\n作为另一个比较点，DeepSeek-R1-Zero 在530万个episode上训练（训练进行了10,400步，每步包含32个独特问题，每个问题采样16次），对应于530万比特的信息。这少于低秩LoRA中的参数数量，我们预测结果可以用LoRA复现。\n为了进一步验证我们关于LoRA在推理RL中有效性的发现，我们使用Qwen3-8b-base在DeepMath数据集🔗（He等，2025）上进行了更大规模的实验，因为它比MATH数据集大得多，通常包含更难的问题。为了加快实验速度，我们将样本限制为8192个token的长度用于训练和评估。这个样本长度允许回溯和推理，但相对于更长的思维链限制了性能。\n图7：在 DeepMath 数据集上使用 Qwen3-8b-base 进行的实验。在左图中，我们展示了不同秩和完全微调的学习曲线。对于每种设置，我们都展示了最佳学习率，从而产生最高的最终性能。在右图中，我们绘制了学习率与最终性能的关系图。与我们之前的数学实验一样，LoRA 似乎具有更宽的接近最佳学习率的峰值。 来自 DeepMath 数据集和 Qwen3-8b-Base 的实验的其他图表。左图显示了 AIME 测试集上的基准分数，该测试集比训练集更具挑战性。右图显示了训练步骤中的思维链 (CoT) 长度，这可以被看作是学习推理的标志。 我们观察到，当为每个设置选择最优学习率时，不同大小的LoRA和完整微调的训练进展几乎相同。此外，当我们在AIME 2024和AIME 2025的保留问题上评估模型时，我们看到类似的发现。此外，我们从LoRA和完整微调运行中观察到类似的定性行为：两者都发展出高级推理行为，如回溯、自我验证和上下文探索，这在模型CoT的延长中是可见的。\n设置LoRA超参数 # LoRA采用的一个障碍是需要选择最优超参数，这些超参数与为FullFT优化的超参数不同。在本节中，我们表明这个问题并不像乍看起来那么令人生畏，并讨论我们与超参数选择相关的发现。\n最优学习率和秩 # 遵循Hu等，我们考虑以下LoRA参数化：\n$$ W' = W + \\frac{\\alpha}{r}BA $$其中 \\(r\\) 是LoRA秩，\\(\\alpha\\) 是LoRA缩放因子，\\(A\\)、\\(B\\) 是LoRA权重矩阵（秩为 \\(r\\)）。我们对本文中的实验使用 \\(\\alpha = 32\\)，遵循其他实现的标准实践。\n\\(1/r\\) 缩放因子使最优学习率近似独立于秩。事实上，一个更强的条件成立——在训练开始时学习曲线完全相同，无论秩如何。这种效果是惊人的，在我们的实验中，不同秩的学习曲线的接近程度让我们担心bug导致秩参数被忽略。因此，在短训练区域中，最优LR也独立于秩。然而，如我们在学习率与损失的图（图2）中所示，在更长训练区域中，最优LR对秩有一定依赖性。\n图9：这些图着眼于在训练初期，对于具有相同学习率的不同秩的学习曲线的差异。左图显示了学习曲线。右图显示了秩 16 和 256 之间的差异，这种差异随着时间的推移而增大。奇怪的是，在最初的几个步骤中，它是负的（尽管很小），因此曲线的这一部分在图中缺失了。 我们可以通过查看第一次训练更新后LoRA矩阵的预期更新来部分解释这一结果。我们可以将LoRA乘积 \\(BA\\) 视为 \\(r\\) 个rank-1外积的总和：\\(BA = \\sum_{i=1}^r b_i a_i^T = \\sum_{i=1}^r \\Delta_i\\)，其中我们定义 \\(\\Delta_i = b_i a_i^T\\)。这里，\\(\\partial \\text{Loss}/\\partial \\Delta_i\\) 对所有 \\(i\\) 都相同；然而梯度 \\(\\partial \\text{Loss}/\\partial b_i\\) 和 \\(\\partial \\text{Loss}/\\partial a_i\\) 将依赖于初始化（例如，\\(\\partial \\text{Loss}/\\partial b_i\\) 依赖于 \\(a_i\\）。由于 \\(a_i\\) 和 \\(b_i\\) 的初始化不依赖于秩，因此 \\(\\mathbb{E}[\\Delta_i]\\) 对所有 \\(i\\) 都相同且不依赖于秩。在训练的第一步，每个这些项的预期更新相等且独立于秩。因此 \\((1/r)\\sum_{i=1}^r \\Delta_i\\) 只是 \\(r\\) 个具有相同期望的项的样本平均值，所以平均值的期望，即适配器 \\((1/r)BA\\) 的变化，不依赖于秩。\n参数化不变性 # LoRA可能适用四个超参数：\n出现在 \\(α / r\\) 中的缩放因子 \\(α\\)。 下投影矩阵 \\(A\\) 的学习率，\\(LR_A\\)。 上投影矩阵 \\(B\\) 的学习率，\\(LR_B\\)。 矩阵 \\(A\\) 的初始化尺度，\\(\\text{init}_A\\)。对于随机初始化，这是 \\(A\\) 初始元素的标准差。矩阵 \\(B\\) 初始化为零，因此不需要定义 \\(\\text{init}_B\\)。 必须调整四个不同的参数似乎令人不知所措。然而，训练动态中的不变性意味着其中两个是冗余的，学习行为由两个决定。我们通过注意到当使用Adam训练且 \\(ε = 0\\) 时*（我们可以将此结果扩展到 \\(ε \u003e 0\\)；我们需要将其缩放 \\(1/q\\)，因为梯度按该因子缩放）*，优化过程对以下两参数变换是不变的。\n对于 \\(p, q \u003e 0\\)：\n\\(α → \\frac{1}{pq} \\cdot α\\) \\(\\text{init}_A → p \\cdot \\text{init}_A\\) \\(LR_A → p \\cdot LR_A\\) \\(LR_B → q \\cdot LR_B\\) 由于四个自由度中的两个不影响学习过程，我们剩下一个2D参数空间。我们可以为这个2D空间选择不同的基，例如以下这个便于直接解释：\n\\(α \\cdot \\text{init}_A \\cdot LR_B\\)。这决定了初始更新的规模，或者等效地，学习曲线的初始斜率。由于 \\(B\\) 初始化为零，\\(LR_A\\) 和对 \\(A\\) 的初始更新无关紧要。 \\(\\text{init}_A / LR_A\\)。由于Adam在每一步将 \\(A\\) 的元素更新约 \\(LR_A\\) ，这个时间尺度参数决定了将 \\(A\\) 显著转换离开其初始状态所需的步数。 我们可以用这个基础重新解释之前关于LoRA的一些提案。\nLoRA+ 🔗 （Hayou等，2024）提议对 \\(A\\) 和 \\(B\\) 使用不同的LR，\\(B\\) 的速率更高。用我们上面的基础表示，增加 \\(LR_B\\) 等价于增加 \\(\\text{init}_A/LR_A\\)，使 \\(A\\) 在更长的时间尺度上变化。\nUnsloth的LoRA超参数指南建议对高秩LoRA使用更高的 \\(α\\) 值，例如通过避免 \\(1/r\\) 缩放。这也等价于增加 \\(\\text{init}_A/LR_A\\)。当我们增加 \\(α\\) 时，需要相应降低 \\(LR_A\\) 和 \\(LR_B\\) 以获得相同的更新大小。这反过来简单地使 \\(LR_A\\) 相对于 \\(\\text{init}_A\\) 更小。\n在我们的实验中，我们使用了Huggingface peft库中使用的标准参数化（Mangrulkar等，2022），由Hu等提出：\\(A\\) 使用尺度为 \\(1/\\sqrt{d_{in}}\\) 的均匀分布，\\(B\\) 零初始化，两者使用相同的LR，\\(α = 32\\)。在我们的调参过程中，未能找到比该组合更优的超参数。\nLoRA与FullFT的最优学习率 # 我们的实验表明，对于相同应用，LoRA的最优LR始终是FullFT使用的10倍，无论是监督学习还是强化学习。这显示在每个性能（损失或奖励）相对于学习率的U形图中。这一观察应该使从FullFT到LoRA的学习超参数转移更加直接。\n我们尚未对这一观察有充分的理论解释。我们可以尝试从最优LoRA LR对秩不变以及完整秩LoRA与FullFT直接可比这些事实推导出这一结果。这种分析建议LR比率为模型的隐藏大小除以 \\(2 \\cdot \\alpha\\)，这与最优比率固定为10且独立于基础模型的经验结果不匹配。\n对于我们的经验分析，我们对14个不同的Llama和Qwen模型在Tulu3数据集上进行了LoRA和FullFT的LR扫描。根据这些扫描，我们拟合了一个函数，该函数基于模型的隐藏大小和它是Llama还是Qwen的指示器来预测最优学习率。使用的函数形式为：\n$$ \\text{LR} = M_{\\text{LoRA}} \\cdot \\left(\\frac{2000}{\\text{hidden size}}\\right)^{\\text{model pow} + \\text{LoRA pow}} $$其中：\n\\(M_{\\text{LoRA}}\\) 是使用LoRA时应用的乘数（如果是FullFT则为1） \\(\\text{model pow}\\) 是指数调整，为每个模型源（Llama和Qwen）分别计算 \\(\\text{LoRA pow}\\) 是LoRA的额外指数调整 \\(\\text{hidden size}\\) 是模型残差流（residual stream）的维度。 我们通过使用线性插值根据我们扫描的数据预测损失来对预测的学习率进行评分，并通过对14个问题的预测损失求和来评估参数。我们的优化发现LoRA相对于FullFT的乘数为9.8，Qwen3和Llama模型对hidden_size的依赖性不同，但LoRA LR对hidden_size的依赖性与FullFT LR相同，即优化发现 \\(\\text{LoRA pow} = 0\\)。\n短期和长期运行中的学习率 # LoRA的典型初始化在有效学习率的变化中创建了一个隐式计划。这导致短期和长期训练运行之间的差异，以及与FullFT相比学习曲线形状的一些差异。\n在训练开始时，\\(B\\) 初始化为零。当 \\(B\\) 非常小时，对 \\(A\\) 的更改对添加到原始网络权重的适配器 \\(BA\\) 的影响可以忽略不计。随着 \\(B\\) 变大，对 \\(A\\) 的更新开始对网络输出产生更大的影响，有效学习率在训练过程中随着 \\(B\\) 在规模上接近 \\(A\\) 而增加。我们发现，到Tulu3和OpenThoughts数据集上完整训练运行结束时，\\(B\\) 矩阵的谱范数（spectral norms）最终大于 \\(A\\) 矩阵。\n这意味着对于较短的训练运行，最优LR应该设置得更高。初步证据表明，对于短运行基于传闻证据，更高的乘数在约100步或更少时有效。，相对于FullFT的最优乘数约为15倍，对于更长的运行收敛到前述的10倍乘数。\n讨论 # 我们希望超越我们的经验结果，讨论一些与LoRA性能和适用性相关的更广泛的考虑因素，这些因素对研究人员和构建者都会感兴趣。\n首先，让我们更深入地检查我们的主要结果，即LoRA表现与完整微调类似的两个条件：\nLoRA应用于网络的所有层，特别是容纳大多数参数的MLP/MoE层。 LoRA在不受容量约束时表现良好，即可训练参数的数量超过要学习的信息量，这可以根据数据集大小估计。 当（1）满足时，我们在训练开始时获得与FullFT类似的学习动态。然后，根据（2），LoRA继续看起来像FullFT，直到我们开始达到容量限制。\n为什么可能需要在所有层上使用LoRA # 如我们之前所示，如果我们仅将LoRA放在注意力层上，即使在微小数据区域，我们也会获得更慢的学习。\n一种可能的解释可能来自将经验神经切线核（empirical Neural Tangent Kernel，eNTK）视为进行少量微调时发生情况的近似，遵循Malladi等。A Kernel-Based View of Language Model Fine-Tuning（Malladi等，2022）eNTK基于梯度的点积，具体来说是梯度 \\(g_i = \\partial/\\partial\\theta \\log p(\\text{token}_i | \\text{prefix}_i)\\)，以及 \\(K(i, j) = g_i \\cdot g_j\\)。因此，具有最多参数的层通常对核具有最大的影响。该论文还指出，当您训练所有层时，LoRA的eNTK与完整微调的eNTK大致相同。因此LoRA训练 \\(\\approx\\) eNTK（LoRA）\\(\\approx\\) eNTK（FullFT）\\(\\approx\\) FullFT。只有当我们将LoRA应用于包含构成点积的大多数参数的层时，近似eNTK（LoRA）\\(\\approx\\) eNTK（FullFT）才成立。\n监督学习和强化学习需要多少容量？ # 过去的工作Physics of Language Models: Part 3.3, Knowledge Capacity Scaling Laws（Allen-Zhu和Li，2024）表明神经网络可以每个参数存储2比特。这些结果涉及在长训练极限中吸收的最大信息量，而不是计算效率或学习速度。\n每个参数2比特的结果依赖于巧妙构建的合成数据集，以包含精确量的信息。估计给定现实学习问题所需的信息内容并不那么直接。一个经典观察是，当最小化对数损失时，在第一个epoch期间测量的总对数损失提供了数据集描述长度的测量。也就是说，记忆数据集所需的比特数的上界。LLM数据集通常每个token的损失约为1比特（0.69 nats），具体取决于数据集和模型大小。\n这个估计测量了完美记忆数据集所需的容量，这高估了减少测试数据对数损失的\u0026quot;可泛化\u0026quot;学习所需的实际容量。测量监督学习的容量要求以及这些要求如何与可训练参数数量相互作用是未来工作的一个开放问题。\n对于RL，我们声称策略梯度算法每个episode学习大约1比特的信息，因为在episode结束时有一个单一的奖励值。这不是RL的基本属性，因为其他算法可以想象从每个episode中学习更多。例如，基于模型的RL算法训练学习代理预测观察并构建世界模型，可能从每个episode中提取更多信息。每个episode 1比特的声明可能仅狭义地适用于策略梯度算法。\n我们可以用信息论术语来完善比特计数论证。将一个episode（由轨迹 \\(\\tau\\) 和最终奖励组成）视为提供关于未知奖励函数 \\(R\\) 的一些信息的消息（即噪声信道）。我们将以当前策略和训练历史为条件，查看策略梯度估计器与 \\(R\\) 之间的互信息。REINFORCE更新是 \\(G = S \\cdot \\text{Adv}\\)，其中 \\(S = \\nabla \\log p_\\theta(\\tau)\\)。给定历史，\\(S\\) 独立于 \\(R\\)，因此唯一依赖于 \\(R\\) 的组件是标量优势。\n根据数据处理不等式：\n$$ I(G ; R | \\text{history}) \\leq I((S, \\text{Adv}) ; R | \\text{history}) = I(\\text{Adv} ; R | S, \\text{history}) \\leq H(\\text{Adv}). $$如果我们将优势量化为 \\(B\\) 个箱（bins），那么 \\(H(\\text{Adv}) \\lesssim \\log(B)\\)。也就是说，每个episode获得的有用信息的比特数是 \\(O(1)\\)，独立于模型大小。这些比特告诉我们我们处于奖励函数的离散集合（或等效地，最优策略类）的哪个成员中。这种互信息分析反映了一些优化算法理论分析中使用的方法。Information Complexity of Black-Box Convex Optimization: A New Look via Feedback Information Theory（Raginsky和Rakhlin，2009）请注意，这个估计是训练吸收信息的上界；实际学习量将取决于策略初始化和其他细节。例如，如果我们用一个没有获得任何奖励的策略初始化，那么优势的熵为零（不是log(B)），它不会学到任何东西。\nLoRA的计算效率优势 # 我们上面的实验根据训练步数测量学习进度，但我们也可能对不同方法的计算效率感兴趣。我们计算出LoRA每次传递的FLOP略多于完整微调的⅔。因此，它通常在整体计算效率上优于FullFT。\n我们通过分析给定权重矩阵上前向-反向传递中使用的FLOP来推导这个⅔比率。这些操作占神经网络模型中绝大多数FLOP。我们使用以下符号：\n\\(W \\in \\mathbb{R}^{N \\times N}\\) 是权重矩阵 \\(x \\in \\mathbb{R}^N\\) 是输入向量 \\(y = Wx \\in \\mathbb{R}^N\\) 是输出向量 \\(\\bar{x}, \\bar{y} \\in \\mathbb{R}^N\\) 是反向传递中计算的相对于 \\(x\\) 和 \\(y\\) 的损失梯度 \\(\\bar{W} \\in \\mathbb{R}^{N \\times N}\\) 是相对于 \\(W\\) 的损失梯度 完整微调执行以下操作：\n前向\n\\(y = Wx\\)（\\(N^2\\) 次乘加） 反向\n\\(\\bar{x} = W^T \\bar{y}\\)（\\(N^2\\) 次乘加） \\(\\bar{W} \\mathrel{+}= x \\bar{y}^T\\)（\\(N^2\\) 次乘加） 前向传递需要 \\(N^2\\) 次乘加，反向传递需要另外 \\(2 \\cdot N^2\\)，总共 \\(3N^2\\)。因此，需要两者的训练使用的FLOP是仅前向推理的3倍。\n对于LoRA，我们用 \\(W + BA\\) 替换 \\(W\\)，其中 \\(B \\in \\mathbb{R}^{N \\times R}\\) 和 \\(A \\in \\mathbb{R}^{R \\times N}\\)，\\(R \\ll N\\)。由于我们只更新 \\(\\bar{A}\\) 和 \\(\\bar{B}\\)，我们用一个便宜得多的操作替换更新 \\(\\bar{W}\\) 的第三步。\\(A\\) 和 \\(B\\) 是 \\(N \\cdot R\\) 矩阵，因此每个上的完整前向-反向计算需要 \\(3NR\\) 次乘加，而不是 \\(W\\) 的 \\(3N^2\\)。两者的总和是 \\(6NR\\)。我们还对 \\(Wx\\) 和 \\(\\bar{x}\\) 执行前向-反向传递，相当于FullFT的前两步。乘加的总数是 \\(2N^2 + 6NR\\)。当 \\(R \\ll N\\) 时，这略多于 \\(3N^2\\) 的 \\(\\frac{2}{3}\\)。\n如果我们根据FLOP这种分析省略了用于注意力的FLOP，在长上下文设置中可能很重要。而不是训练步数绘制LoRA性能，它将显示出相对于FullFT的明显优势。\n开放问题 # 与我们的结果相关的几个问题，我们希望在未来看到研究：\n完善我们对LoRA性能的预测以及它与完整微调匹配的精确条件。我们已经粗略描述了等效性能的区域，并可以根据token或episode估计所需的容量，但我们还不能做出准确的预测。 我们对LoRA学习率和训练动态的理论理解有限。解释LoRA和FullFT学习率之间比率的更完整理论将是有价值的。 LoRA变体（如PiSSA（Meng, Wang \u0026amp; Zhang，2024））根据本文的方法论测量时表现如何？ 将LoRA应用于MoE层有多种选择。LoRA用户将受益于对它们表现如何的研究，以及每种方法与张量并行（tensor parallelism）和专家并行（expert parallelism）等对大型MoE模型重要的方法的兼容性。 结束语 # 在Thinking Machines，我们相信微调在许多专业领域推进AI实用性的力量。我们对LoRA的兴趣源于使这种力量广泛可及并易于根据特定需求定制的目标。\n除了实际用途外，LoRA研究还引导我们对模型容量、数据集复杂性和样本效率进行更深入的研究。观察学习速度和性能如何依赖于容量为研究机器学习中的基本问题提供了一个视角。我们期待在未来推进这项研究。\n","date":"2025-10-01","externalUrl":null,"permalink":"/blogs/llm/lora-without-regret-thinkingmachines/","section":"Blog","summary":"","title":"LoRA：无憾之选-Thinking Machines","type":"blogs"},{"content":"","date":"2025-10-01","externalUrl":null,"permalink":"/tags/thinkingmachines/","section":"Tags","summary":"","title":"ThinkingMachines","type":"tags"},{"content":"","date":"2025-10-01","externalUrl":null,"permalink":"/tags/%E5%BE%AE%E8%B0%83/","section":"Tags","summary":"","title":"微调","type":"tags"},{"content":"","date":"2025-09-30","externalUrl":null,"permalink":"/tags/gametheory/","section":"Tags","summary":"","title":"GameTheory","type":"tags"},{"content":" 论文: Psychologically Enhanced AI Agents (MBTI-in-Thoughts)\n代码: GitHub - spcl/MBTI-in-Thoughts\n引言 # 随着大型语言模型（LLM）能力的提升，如何精确控制其行为模式（Behavioral Alignment）成为关键挑战。传统的Prompt Engineering往往依赖于具体的指令堆叠，而缺乏系统性的理论支撑。\n苏黎世联邦理工学院（ETH Zurich）与BASF等机构的研究团队提出了 MBTI-in-Thoughts (MiT) 框架。该研究并非旨在验证MBTI本身的心理学科学性，而是将其作为一种 “结构化行为原型系统”（Structured Behavioral Archetypes） ，通过Context Engineering将心理学特征注入LLM，从而实现对模型在 认知（Cognition） 和 情感（Affect） 两个维度上的精确调控。\n值得注意的是，该研究在 GPT-4o、Qwen3-235B 等前沿SOTA模型上进行了广泛验证，证明了心理学Priming在超大规模参数模型中依然具有显著的调控效力。\n1. 理论形式化：从标签到向量空间 # MiT框架的核心贡献之一，是将定性的心理学描述转化为定量的向量空间表示。这为不同心理学框架（如MBTI, Big Five, HEXACO）的统一处理提供了数学基础。\nFigure1 MBTI-in-Thoughts 框架概览，展示理论层与应用层的映射关系 论文提出，任何心理学框架 \\(\\mathcal{F}\\) 均可定义为一个将智能体映射到 \\(n\\) 维特质空间的函数：\n$$ \\mathcal{F}: \\text{Agent} \\to \\mathbb{R}^n $$具体到MBTI框架，研究者将其重构为一个8维的概率向量空间。对于任意智能体 \\(A\\)，其MBTI状态表示为四对互补的标量：\n$$ \\text{MBTI}(A) = [(E_A, I_A), (S_A, N_A), (T_A, F_A), (J_A, P_A)] \\in [0,1]^8 $$其中需满足归一化约束（如 \\(T_A + F_A = 1\\)）。这种连续值的定义方式突破了MBTI传统的二元分类限制，允许模型表现出“中间状态”或“倾向性强弱”，从而更符合LLM输出概率分布的本质。\n2. 注入机制：三级提示工程与闭环验证 # 为了在模型中激活特定的人格原型，论文设计了三种不同粒度的提示（Priming）策略，并在 GPT-4o mini 上进行了严格的鲁棒性验证。\n2.1 三级Priming策略 # 不同于简单的“你是一个INTJ”的指令，研究探索了信息的不同呈现方式对模型的影响：\n极简注入 (Minimal): 仅包含人格标签（如 \u0026ldquo;Respond from an ISFP perspective\u0026rdquo;）。 理论上下文注入 (General MBTI Context): 在提示中包含对MBTI理论基础的概述，明确提及MBTI术语，利用模型预训练中关于该理论的知识储备。 隐式描述注入 (Detailed Profile-Specific): 这是最为精细的策略。不直接提及“MBTI”或具体类型标签（如“ENTP”），而是详细描述该类型的行为特征、沟通风格、决策偏好和压力应对机制。这种方法通过激活模型对人类行为特征的深层语义理解来塑造人格。 2.2 验证闭环 (Verification Loop) # MiT框架引入了客观验证机制，拒绝“盲目信任”Prompt的效果。\n方法: 让注入了人格的Agent完成标准的 16Personalities 测试（60道Likert量表题）。 流程: Agent生成回答 -\u0026gt; 映射为数值 -\u0026gt; 提交至测评API -\u0026gt; 获取官方评分。 结果: E/I (外向/内向)、T/F (思维/情感)、J/P (判断/感知) 维度展示了极高的可分离性（Separability）。 S/N (实感/直觉) 维度的分离性相对较弱。论文深度分析指出，S/N主要涉及信息获取风格（Information-gathering style），在单轮文本交互中，其信号往往比社交偏好（E/I）或决策逻辑（T/F）更为弥散（diffuse），导致区分度略低。 Figure2 各人格类型在四个维度上的评分分布箱线图，展示注入的鲁棒性 3. 实验分析：人格对任务表现的系统性影响 # 研究使用了 GPT-4o (SOTA)、Qwen3-235B (大规模开源) 等模型，在情感生成和认知博弈两大类任务中进行了评估。\n3.1 情感中心任务：创意写作 # 在 WritingPrompts 数据集上，不同人格的Agent表现出了显著的风格差异（见论文 Figure 3）：\nF型（情感型）优势: 在“情感充沛度”（Emotionally Chargedness）和“讨喜程度”（Likeability）上得分显著高于T型。 E型（外向型）优势: 在“幽默感”（Humor）和“可读性”（Readability）上表现更佳，且倾向于生成Happy Ending。 结论: 通过人格参数，可以像调节“旋钮”一样控制生成文本的情感色彩和叙事基调。 Figure3 MBTI人格维度对LLM写作风格与质量特征的差异化影响评估 3.2 认知中心任务：博弈论交互 # 这是论文中最具洞察力的部分。研究让Agent参与囚徒困境、猎鹿博弈等经典游戏，揭示了人格特征如何重塑Agent的策略选择，甚至覆盖了“利益最大化”的通用指令。\n理性 vs. 合作: T型（思考型）: 表现出极高的背叛率（Defection Rate, ~90%）。它们倾向于纳什均衡，为了个人利益最大化而选择背叛，即使这违背了集体最优。 F型（情感型）: 背叛率仅约50%，更倾向于合作策略，表现出某种“利他”或“关系导向”的特征。 诚实与欺骗: I型（内向）与J型（判断）: 在博弈沟通中表现出显著更高的诚实率（Honesty Rate）。它们倾向于言行一致。 E型（外向）: 更倾向于使用虚张声势（Bluffing）或欺骗策略来博取胜利。 适应性: F型Agent的策略转换率（Strategy Switch Rate）是T型的两倍，表明其对对手行为的反应更敏感，而T型则更固执地坚持预设的最优策略。 Figure4 博弈论任务中的背叛率、策略转换率和诚实率统计 关键洞察: 实验中所有Agent都被告知“目标是赢得最高分”。然而，F型和I/J型Agent依然选择了次优的合作或诚实策略。这证明了心理学Priming具有极强的约束力，能够从底层逻辑上改变模型对“目标”的理解和执行路径。\n4. 多智能体协作：独立性与准确性的权衡 # 论文在多智能体（Multi-Agent）部分探讨了三种通信协议对群体决策的影响（见论文 Figure 6及附录D）：\n多数投票 (Majority Voting): 独立思考，无交流。 交互式通信 (Interactive Communication): 通过共享黑板（Blackboard）进行轮流发言。 带自省的交互 (Interactive with Self-Reflection, ICSR): 在发言前，Agent先在私有暂存区（Scratchpad）进行基于自身人格的独立推理。 Figure6 Analysis of inter-agent communication protocols. 反直觉的结论: 虽然ICSR（带自省）的表现优于普通的交互模式，但简单的多数投票（Majority Voting）在最终准确率上与最复杂的ICSR相当，甚至在某些任务中略优。\n深度解读: 这并不意味着复杂的架构没有意义。论文指出，ICSR的核心优势在于认知独立性（Cognitive Independence）。\n在普通交互中，Agent容易产生“盲从效应”（Echoing），即后发言者被先发言者带偏。 引入“自省”机制后，Agent被迫先巩固自己的观点，从而在讨论中贡献更多样化的视角。 虽然投票在简单任务上高效，但在需要复杂推理和避免群体迷思（Groupthink）的场景中，保持Agent的认知独立性至关重要。 5. 总结与展望 # MBTI-in-Thoughts 并非简单的“AI算命”，它揭示了LLM内部知识表征的一种高效调用方式。\n压缩指令假说: 人格标签（如\u0026quot;ENTP\u0026quot;）本质上是一组高度压缩的复杂行为指令集。通过Priming，我们无需列举成百上千条规则（“要幽默”、“要逻辑严密”、“要敢于冒险”\u0026hellip;），只需一个“密钥”即可激活模型潜在的特定行为模式簇。 泛化能力: 实验证明该方法在从GPT-4o到Qwen-235B等不同规模的模型上均有效，且可扩展至Big Five等其他心理学框架。 局限性:\n长程衰减: 当前验证主要集中在单次或短程交互，人格特征在超长上下文中的稳定性（Personality Decay）仍需进一步研究。 文化偏差: MBTI及LLM训练数据均带有强烈的西方文化印记，特定人格（如“内向”）在不同文化语境下的表现可能需要针对性的Prompt调整。 该研究为构建个性化Agent、游戏NPC以及更符合人类伦理的AI协作系统提供了坚实的实证基础。\n","date":"2025-09-30","externalUrl":null,"permalink":"/blogs/llm/mbti-in-thoughts-psychologically-enhanced-ai-agents/","section":"Blog","summary":"","title":"MBTI-in-Thoughts：基于MBTI的LLM行为调控与结构化思维框架","type":"blogs"},{"content":"","date":"2025-09-30","externalUrl":null,"permalink":"/tags/promptengineering/","section":"Tags","summary":"","title":"PromptEngineering","type":"tags"},{"content":"","date":"2025-09-30","externalUrl":null,"permalink":"/tags/psychology/","section":"Tags","summary":"","title":"Psychology","type":"tags"},{"content":" I. 项目概述 # Memvid 是一个创新的AI记忆库项目。其核心思想是将大规模文本知识库压缩成单一、可搜索的视频文件。它并非存储文本数据本身，而是将文本分块（Chunk），每一块内容编码成一个二维码（QR Code），并将这些二维码作为视频的连续帧（Frame），最终利用现代视频编码技术（如 H.265/HEVC）生成一个高度压缩的 .mp4 或 .mkv 文件。\n这种设计的精妙之处在于，它巧妙地将文本数据的压缩问题转化为了视频数据的压缩问题，从而利用了数十年来在视频编码领域积累的先进技术。视频编码器尤其擅长压缩具有高度空间和时间冗余的图像序列，而由二维码组成的视频帧正是这种理想的压缩对象。\n最终，Memvid 实现了一个类似于“AI记忆的SQLite”的解决方案：一个自包含、无需复杂基础设施、支持毫秒级语义搜索的便携式知识库。\nII. 系统架构 # Memvid 的架构设计清晰，职责分离明确。主要可以分为数据编码和数据检索两大流程。\nA. 核心组件 # 项目由以下几个核心模块构成：\nMemvidEncoder (memvid/encoder.py): 数据编码的“总指挥”。负责接收文本输入，协调分块、QR码生成、视频帧合成以及最终的视频文件和索引的生成。 IndexManager (memvid/index.py): 索引管理的“大脑”。负责将文本块转化为向量嵌入（Embeddings），并使用 FAISS 库构建和管理用于高效相似度搜索的向量索引。 MemvidRetriever (memvid/retriever.py): 数据检索的“执行者”。负责接收用户查询，通过 IndexManager 进行语义搜索，定位到视频中的关键帧，并高效地解码QR码以提取原始文本。 MemvidChat (memvid/chat.py): 对话式交互的“接口”。它整合了 MemvidRetriever 和 LLMClient，实现了检索增强生成（RAG）的功能，让用户可以通过自然语言与知识库进行对话。 LLMClient (memvid/llm_client.py): 大语言模型（LLM）的“适配器”。通过抽象基类 LLMProvider 提供了一个统一的接口，用以支持多种后端LLM服务（如 Google Gemini, OpenAI GPT, Anthropic Claude），具有良好的可扩展性。 DockerManager (memvid/docker_manager.py): FFmpeg 的“环境隔离与兼容性解决方案”。这是一个非常出色的工程实践，它将复杂的 FFmpeg 依赖（特别是对于 H.265/AV1 等高级编解码器）封装在 Docker 容器中，从而确保了跨平台的一致性和易用性。 utils.py \u0026amp; config.py: 项目的“工具箱”与“配置中心”。utils 提供了QR码编解码、文本分块等原子操作。config 则集中管理了所有可调参数，使得项目行为高度可定制。 B. 数据流 # 1. 编码/索引流程 (Encoding/Indexing Flow) # 此流程将原始文本数据转化为一个视频文件和两个索引文件（.json 和 .faiss）。\ngraph TD A[原始文本/PDF/EPUB] --\u003e|MemvidEncoder.add_text（）| B(文本分块 chunk_text); B --\u003e C{文本块 Chunks}; C --\u003e|IndexManager| D(SentenceTransformer 生成向量嵌入); D --\u003e E(FAISS 索引); E --\u003e F[\"memory_index.faiss\"]; C --\u003e|为每个Chunk构建JSON Payload| G(JSON：id, text, frame); G --\u003e|utils.encode_to_qr| H(生成QR码图像); H --\u003e I(保存为临时PNG帧文件); I --\u003e J{视频编码}; J --\u003e|mp4v codec| K(OpenCV VideoWriter); J --\u003e|h265/h264/av1 codec| L(FFmpeg: Native or Docker); K --\u003e M[\"memory.mp4\"]; L --\u003e M[\"memory.mp4\"]; C --\u003e N(生成元数据); N --\u003e O[\"memory_index.json\"]; subgraph \"IndexManager\" D E end subgraph \"MemvidEncoder\" B G H I J N end 流程详解:\n输入与分块: MemvidEncoder 接收文本、PDF或EPUB文件，使用 utils.chunk_text 函数将其分割成设定大小（默认为1024字符）且有重叠（默认为32字符）的文本块。 内容编码: 对每一个文本块，构建一个包含ID、文本内容和帧号的JSON对象。 QR码生成: 这个JSON字符串被 utils.encode_to_qr 函数编码成一个QR码。值得注意的是，为了处理较长的文本，该函数在编码前会先使用 gzip 对数据进行压缩，并添加 \u0026ldquo;GZ:\u0026rdquo; 前缀作为标识，这是一个双重压缩的细节。 帧文件暂存: 生成的每个QR码图像被保存为独立的PNG文件，存放在一个临时目录中。 视频合成: 对于 mp4v 编码: MemvidEncoder 直接使用 cv2.VideoWriter 逐帧读取PNG文件，合成为视频。这是一种简单、兼容性好的原生方法。 对于 H.265 等高级编码: MemvidEncoder 会构建一个 FFmpeg 命令行。并通过 DockerManager 决定是在Docker容器内还是在本地执行这个命令。这个设计决策是架构的关键，它保证了高级编解码功能在不同开发环境中的稳定性和一致性。 索引构建: 向量索引: 与此同时，所有文本块被送入 IndexManager，通过 sentence-transformers 模型（默认为 all-MiniLM-L6-v2）生成384维的向量嵌入。这些向量被添加到一个 FAISS 索引中，并保存为 .faiss 文件。 元数据索引: 每个文本块的ID、预览文本、字符数、对应的视频帧号等元数据被保存在一个 .json 文件中。 2. 检索/对话流程 (Retrieval/Chat Flow) # 此流程根据用户查询，从视频知识库中检索信息并生成回答。\ngraph TD A[用户查询 Query] --\u003e|MemvidChat.chat（）| B(MemvidRetriever.search); B --\u003e C(IndexManager.search); subgraph \"IndexManager\" C -- Query --\u003e D(生成查询向量); D -- 向量 --\u003e E(FAISS 向量搜索); E -- Top-K Chunk IDs --\u003e C; end C -- Top-K Chunk IDs --\u003e B; B -- Chunk IDs --\u003e F(从index.json获取帧号); F -- 帧号 Frame Numbers --\u003e G(并行解码视频帧); G -- 帧图像 --\u003e H(并行解码QR码); H -- 解码出的JSON --\u003e I(提取文本块 Context); subgraph \"LLM Interaction (RAG)\" J[System Prompt] --\u003e K; L[聊天历史] --\u003e K; I --\u003e K; A --\u003e K; K(构建完整Prompt) --\u003e M(LLMClient); M --\u003e N[LLM API （Google/OpenAI/...）]; N --\u003e O(LLM Response); end O --\u003e P[返回给用户]; 流程详解:\n查询向量化: 当 MemvidChat 收到用户查询后，MemvidRetriever 将查询文本同样用 sentence-transformers 模型转化为查询向量。 语义搜索: IndexManager 使用查询向量在 FAISS 索引中执行高效的相似度搜索（默认是L2距离），返回最相似的 Top-K 个文本块的ID。 帧定位: MemvidRetriever 根据这些 chunk ID，从 .json 索引中查找它们对应的视频帧号。 并行帧解码: 这是性能优化的关键。MemvidRetriever 使用 ThreadPoolExecutor 并行地从视频文件中提取所需的多个帧图像，并通过 lru_cache 缓存已解码的帧，避免重复I/O和计算。 上下文组装: 解码QR码得到原始的JSON数据，从中提取出文本块，这些文本块共同组成了与查询最相关的“上下文（Context）”。 RAG生成: MemvidChat 将系统提示、聊天历史、刚检索到的上下文和用户的当前问题组合成一个完整的Prompt，通过 LLMClient 发送给指定的大语言模型。 返回结果: LLM生成的回应最终被返回给用户。 III. 核心模块深度解析 # A. memvid.encoder.MemvidEncoder (数据编码模块) # 职责: 核心任务是编排从文本到视频和索引的整个过程。 关键实现: 文件支持: 通过 add_pdf 和 add_epub 方法，集成了对PDF和EPUB文件的文本提取能力，依赖 PyPDF2 和 ebooklib 等库，极大地增强了易用性。 编解码策略: build_video 方法是其核心。它根据用户指定的 codec 参数，动态选择编码后端。 如果 codec 是 mp4v，则调用 _encode_with_opencv，使用原生Python库完成，无需外部依赖。 如果 codec 是 h265 等高级编码器，则调用 _encode_with_ffmpeg。此方法会进一步咨询 DockerManager 是否应该使用Docker。这种分层决策使得代码逻辑非常清晰。 鲁棒性: build_video 方法中包含 allow_fallback 参数。如果高级编码失败，它能自动降级并尝试使用 mp4v 重新编码，确保了任务的成功率。 B. memvid.index.IndexManager (索引管理模块) # 职责: 负责向量生成、存储、搜索以及元数据管理。 关键实现: FAISS索引类型: 支持 Flat (精确搜索，适用于小数据集) 和 IVF (倒排文件索引，适用于大数据集) 两种索引类型。 智能FAISS训练: 在 _add_to_index 方法中，包含了非常智能和健壮的 FAISS 训练逻辑。它会检查 IVF 索引是否需要训练。如果需要，它会进一步检查是否有足够的训练数据（len(embeddings) \u0026lt; nlist）。如果数据不足，它不会报错退出，而是自动、平滑地切换到 Flat 索引，并打印警告信息。这个细节极大地提升了用户体验，避免了用户因不理解 FAISS 训练机制而导致的失败。 数据校验: 在添加chunks之前，_is_valid_chunk 会进行简单的校验，过滤掉空或过长的文本，保证了送入模型的数据质量。 C. memvid.retriever.MemvidRetriever (数据检索模块) # 职责: 实现从视频记忆库中进行快速、高效的语义检索。 关键实现: 性能优化: 检索性能是该模块的核心。它综合运用了多种优化手段： 并行化: _decode_frames_parallel 利用 ThreadPoolExecutor 来并发处理多个帧的解码请求。 缓存: _frame_cache 是一个实例级别的字典缓存，而 utils.extract_and_decode_cached 函数上的 @lru_cache 装饰器则提供了更细粒度的函数级别缓存。两者结合，大大减少了对同一视频帧的重复读取和QR码解码操作。 接口设计: 提供了 search (只返回文本) 和 search_with_metadata (返回文本、得分、帧号等详细信息) 两种方法，满足不同场景的需求。 D. memvid.docker_manager.DockerManager (Docker后端管理器) # 职责: 这是一个教科书级别的工程实践范例，它将一个复杂、易变的外部依赖（FFmpeg）完全隔离。 关键实现: 环境自检: 构造函数中会自动检测 docker 命令是否存在、Docker守护进程是否在运行以及所需的镜像 memvid-h265 是否已构建。 自动构建: ensure_container_ready 方法在 auto_build=True 时，如果发现镜像不存在，会自动执行 docker build 命令，对用户非常友好。 跨平台路径处理: _convert_path_for_docker 和 _is_wsl 方法专门处理了Windows、Linux和WSL（Windows Subsystem for Linux）之间的路径差异，例如将WSL下的 /mnt/c/Users 转换为Docker可以挂载的 C:\\\\Users。这是保证跨平台可用性的关键细节。 命令执行: 它不是直接在容器里执行 ffmpeg 命令，而是执行一个Python脚本 ffmpeg_executor.py，并将 ffmpeg 命令作为JSON参数传入。这种方式能更好地处理复杂的命令行参数和转义问题，比直接拼接字符串更健壮。 IV. 算法与性能优化 # Memvid 在多个层面都体现了对性能的极致追求。\n存储压缩: 核心算法: 利用视频编码器压缩QR码序列。H.265 (HEVC) 尤其适合这种场景，因为它对静态背景和重复模式的压缩效率极高。 双层压缩: 在 utils.encode_to_qr 中，对较长的文本先进行 gzip 压缩，再进行QR码编码。这在文本本身有冗余时，能进一步减小QR码的复杂度，从而提高视频压缩率。 检索性能: 索引算法: 采用 FAISS 进行高效的向量相似度搜索，这是当前大规模向量检索的主流方案。 I/O优化: batch_extract_frames 通过对帧号排序后顺序读取，利用了文件系统的预读特性，可能比随机读取更快。 计算并行化: 使用 ThreadPoolExecutor 并行化CPU密集型的QR码解码任务。 多级缓存: MemvidRetriever 中的实例缓存和 utils 中的 lru_cache 构成了有效的多级缓存策略，最大化地复用计算结果。 V. 代码设计与工程实践 # 高度模块化: 项目遵循了“高内聚、低耦合”的设计原则。每个模块职责单一，例如 LLMClient 只管与LLM交互，DockerManager 只管Docker，使得代码易于理解、维护和扩展。 配置驱动: config.py 文件是项目的“控制面板”。几乎所有的魔法数字和关键参数（如QR码版本、编解码器参数、嵌入模型名称等）都被集中管理。用户可以通过修改配置来调整项目行为，而无需触碰核心逻辑代码。 鲁棒性设计: 优雅降级: FAISS训练失败时自动切换到Flat索引；高级视频编码失败时自动降级到mp4v。 依赖检查: LLMClient 和 encoder 会在使用可选依赖（如openai, PyPDF2）前进行检查，并在缺失时给出明确的错误提示。 VI. 总结 # Memvid 提出了一个新颖的、利用视频压缩技术解决AI知识库存储问题的方案。\n其核心优势在于:\n创新性: 以前所未有的方式将文本压缩与视频编码技术相结合。 高性能: 在存储和检索两个方面都进行了深入的性能优化。 高可用性: 通过Docker化、优雅降级和智能配置，大大降低了使用门槛，并保证了跨平台的稳定性。 模块化与可扩展性: 清晰的架构使其易于维护，并且可以方便地扩展新的LLM支持或视频编码技术。 通过对源码的解读，可以看出 Memvid 不仅仅是一个实验性的想法，而是一个经过深思熟虑、具备工业级潜力的开源库。它为处理和分发大规模、静态的AI知识库提供了一个极具吸引力的新范式。\n","date":"2025-09-26","externalUrl":null,"permalink":"/blogs/project/memvid-compressing-massive-text-into-a-single-video-file/","section":"Blog","summary":"","title":"Memvid：将海量文本压缩进单一视频文件","type":"blogs"},{"content":"","date":"2025-09-26","externalUrl":null,"permalink":"/tags/%E5%82%A8%E5%AD%98/","section":"Tags","summary":"","title":"储存","type":"tags"},{"content":"","date":"2025-09-26","externalUrl":null,"permalink":"/tags/%E8%AE%B0%E5%BF%86/","section":"Tags","summary":"","title":"记忆","type":"tags"},{"content":"","date":"2025-09-23","externalUrl":null,"permalink":"/tags/icepop/","section":"Tags","summary":"","title":"IcePop","type":"tags"},{"content":"","date":"2025-09-23","externalUrl":null,"permalink":"/tags/moe/","section":"Tags","summary":"","title":"MoE","type":"tags"},{"content":"","date":"2025-09-23","externalUrl":null,"permalink":"/tags/rl/","section":"Tags","summary":"","title":"RL","type":"tags"},{"content":" huggingface/inclusionAI || modelscope/inclusionAI\nnotion/icepop\nTL;DR\n最近的工作[1]强调了当前强化学习（RL）训练框架中模型训练和推理生成之间的不匹配问题。我们观察到，这个问题在 专家混合（MoE）架构中可能会加剧 ☹️。特别是当模型倾向于生成长响应时，这种差异会进一步放大 😱。尽管之前的工作[1]提出通过引入重要性采样校正来缓解这个问题，但我们认为，随着训练的进行，这种技术可能在基准测试中达到性能瓶颈。相反，我们提出了一种简单而有效的方法——‣𝑰𝒄𝒆𝑷𝒐𝒑 🤩 ，即使在强大的 SFT-ed MoE 模型上，也能通过 RL 实现稳定的训练和卓越的下游性能。\nMoE上的不匹配问题 # 不匹配问题指的是当前 RL 训练框架中训练后端和推理引擎之间的概率差异，这不可避免地将在线策略训练转变为离线策略[1]。我们观察到，这种实现差距在 MoE 模型上的 RL 训练过程中变得更加显著。与密集模型不同，为了实现更高的参数效率，MoE 架构采用了一种路由机制，该机制在训练和推理过程中仅为每个 token 选择少数排名靠前的\u0026quot;专家\u0026quot;。然而，我们发现这种结构设计加剧了在线策略 RL（on-policy RL）训练期间的不匹配问题，从而阻止了 MoE 模型充分释放强化学习的潜力。\n图1：MoE和密集模型之间 \\(|\\log p_{\\rm infer} - \\log p_{\\rm train}|\\) 的比较。我们选择了三个代表性模型：[Ring-mini-2.0](https://huggingface.co/inclusionAI/Ring-mini-2.0)(MoE)，Qwen3-4B（密集），Qwen3-30B-A3B（MoE），显示MoE模型通常在训练和推理引擎之间表现出更大的差异。我们将包含更多不同模型大小的比较。 根据下面的策略梯度方程，我们可以看到另一个不匹配问题出现了，即 \\(\\textcolor{red}{\\theta_{\\rm infer}}\\) 和 \\(\\textcolor{blue}{\\theta_{\\rm train}}\\)。在MoE模型中，路由函数 \\(\\texttt{TopK}(\\cdot)\\) 为每个输入token动态激活一部分\u0026quot;专家\u0026quot;（如：网络）。理想情况下，对于固定的输入，无论策略模型部署在哪个引擎上，\\(\\texttt{TopK}(\\cdot)\\) 的输出都应该是相同的。然而，当 \\(\\textcolor{red}{\\theta_{\\rm infer}}\\) 和 \\(\\textcolor{blue}{\\theta_{\\rm train}}\\) 之间存在显著差距时，这将不可避免地导致 \\(\\textcolor{red}{\\pi_{\\rm infer}}\\) 和 \\(\\textcolor{blue}{\\pi_{\\rm train}}\\) 之间的更大分歧。\n$$ \\small{\\begin{equation}\\theta \\leftarrow \\theta + \\mu \\cdot \\mathbb{E}_{a\\sim \\textcolor{red}{\\pi_{{\\rm{infer}}}}(\\textcolor{red}{\\theta_{\\rm infer}} ), \\ \\textcolor{red}{\\theta_{\\rm infer}} \\sim \\mathtt{TopK}_{\\rm infer}(a)}\\left[ R(a) \\cdot \\nabla_{\\theta}\\log \\textcolor{blue}{\\pi_{\\rm{train}}}(a;\\textcolor{blue}{\\theta_{\\rm train}});\\textcolor{blue}{\\theta_{\\rm train}} \\!\\sim \\!\\texttt{TopK}_{\\rm train}(a) \\right]\\end{equation}} $$对于 MoE 模型，我们识别出训练-推理差异问题的两个主要原因：\n训练和推理阶段选择的专家可能不同。\n我们之前的分析表明，即使在第一个 MoE 层中，已经有一小部分 token 在训练后端和推理引擎中激活了不同的专家。\n例如，当选择 Top-k 和 Top-(k+1) 专家的概率非常接近时，即使是轻微的精度差异也可能导致在训练期间和推理期间选择不同的专家，从而导致计算概率的巨大差异。\n随着更多路由网络的堆叠，不匹配变得明显。\n我们进一步注意到，随着 MoE 层的加深，在训练后端和推理引擎中调用相同专家的 token 比例迅速下降约10%。\n在每一层，路由网络决定激活哪些专家。在深层 MoE 模型中，它为每层选择多个专家，因此即使每次调用 \\(\\texttt{TopK}(\\cdot)\\) 的小差异也会累积，并随着深度的增长而越来越放大。\n这对 MoE RL 会产生什么影响？ # 概率差异被放大，特别是对于长序列。 在训练的最开始，我们发现在某些 token 位置已经明显存在显著的概率差异。由于预测的自回归特性，出现在后面位置的 token 更容易受到差异累积的影响，导致变异范围更大。\n图2：在步骤0时，不同 token 位置的概率差异。 随着训练的进行，问题加剧：训练和推理引擎之间同一 token 的概率差距在各个 token 位置持续增加，甚至影响长序列中的前面 token，并破坏优化过程的稳定性。\n图3：训练后，训练和推理引擎在不同 token 位置计算的对数概率。 不匹配问题很快导致在线策略 MoE RL 训练崩溃。 在在线策略 RL 训练中，与密集模型相比，我们观察到负责生成长序列的 MoE 模型更容易受到这种不匹配问题的影响，经常导致训练崩溃。\n图4：崩溃模型的训练信号。 例如，上图显示差异在步骤150后逐渐增加，一旦差异超过0.05，训练基本上就失败了。由于实现不同，概率差异可能因复合效应而变得更大。\n引理（复合概率差异）\n设 \\(\\pi_{\\text{infer}}(\\cdot;\\theta)\\) 和 \\(\\pi_{\\text{train}}(\\cdot;\\theta)\\) 为推理和训练策略。将步骤 \\(t\\) 的概率差异定义为：\n$$ \\delta_t \\;=\\; D_{\\mathrm{KL}}\\!\\big(\\pi_{\\text{infer}}(\\cdot;\\theta_t)\\,\\|\\,\\pi_{\\text{train}}(\\cdot;\\theta_t)\\big). $$它测量推理引擎的分布偏离训练引擎分布的程度。\n在使用不匹配引擎的 RL 训练期间，参数更新为：\n$$ \\theta_{t+1} \\;=\\; \\theta_t \\;+\\; \\mu\\,g_t, \\qquad g_t \\;=\\; \\mathbb{E}_{a\\sim \\pi_{\\text{infer}}(\\theta_t)}\\!\\big[R(a)\\,\\nabla_\\theta \\log \\pi_{\\text{train}}(a;\\theta_t)\\big]. $$在线策略更新是 \\(g_t^\\star = \\mathbb{E}_{a\\sim \\pi{_\\text{train}}(\\theta_t)}[R(a)\\,\\nabla_\\theta \\log \\pi_{\\text{train}}(a;\\theta_t)]\\) ，偏差是 \\(b_t = g_t - g_t^\\star\\)。假设以下局部条件对 \\(\\theta\\) 成立。\n平滑差异\n\\(\\delta(\\theta)\\) 是 \\(L\\)-平滑的，满足 \\(\\big|\\,\\delta(\\theta+\\Delta)-\\delta(\\theta)\\,\\big| \\;\\le\\; L\\,\\|\\Delta\\| \\;+\\; c_0 \\|\\Delta\\|^2\\)，其中 \\(c_0\\) 是曲率常数。\n这意味着小的参数更新只会导致差异的小变化。\n偏差与差异成正比\n\\(\\|b_t\\| \\;\\ge\\; c_1\\,\\delta_t, ~~\\big\\langle \\nabla_\\theta \\delta(\\theta_t),\\, b_t \\big\\rangle \\;\\ge\\; c_2\\,\\delta_t\\)， 其中 \\(c_1\\) 是偏差幅度系数，\\(c_2\\) 是偏差对齐系数。\n不匹配越大，偏差越多地推向恶化方向。\n有界在线策略漂移（ drift）\n存在 \\(M\\ge 0\\) 使得 \\(\\big|\\langle \\nabla_\\theta \\delta(\\theta_t),\\, g_t^\\star \\rangle\\big| \\le M\\)\n仅在线策略训练不会导致失控分歧；不稳定性主要来自偏差。\n用 IcePop 释放 MoE RL：丢弃所有噪声梯度更新！ # 为了解决上述问题，我们提出了一种简单而有效的技术，𝑰𝒄𝒆𝑷𝒐𝒑 🤩。我们应用双侧掩码（double-sided masking）来缓解概率差异的有害复合效应（compounding effects），只保留“健康”的梯度更新。\n双侧裁剪（Double-sided clipping）：不仅裁剪训练概率≫推理概率的token，还裁剪训练概率≪推理概率的token。 掩码（Masking）：差异过大的token从梯度更新中移除。 $$ \\begin{align*} \\mathcal{J}_{{\\text{IcePop}}}(\\theta) \u0026= \\mathbb{E}_{x \\sim \\mathcal{D}, \\{y_i\\}_{i=1}^G \\sim \\pi_{\\textcolor{red}{\\text{infer}}}(\\cdot \\mid x; \\theta_{\\rm old})} \\left[ \\frac{1}{G} \\sum_{i=1}^G \\frac{1}{|y_i|} \\sum_{t=1}^{|y_i|} \\Big[\\mathcal{M}\\Bigl(\\frac{\\pi_{\\textcolor{blue}{\\text{train}}}(y_{i,t} \\mid x, y_{i, \\lt t};\\theta_{\\text{old}})}{\\pi_{\\textcolor{red}{\\text{infer}}}(y_{i,t} \\mid x, y_{i, \\lt t}; \\theta_{\\mathrm{old}})}; \\alpha, \\beta\\Bigr) \\right. \\\\ \u0026\\left. \\qquad \\qquad \\qquad \\qquad \\quad \\qquad \\cdot \\min \\left( r_{i,t}\\widehat{A}_{i,t}, \\text{clip} \\left( r_{i,t}, 1 - \\varepsilon, 1 + \\varepsilon \\right) \\widehat{A}_{i,t} \\right) \\right]\\Bigg] \\end{align*} $$其中 \\(r_{i,t} = \\frac{\\pi_{\\textcolor{blue}{\\text{train}}}(y_{i,t} \\mid x, y_{i, \\lt t}; \\ \\theta)}{\\pi_{\\textcolor{blue}{\\text{train}}}(y_{i,t} \\mid x, y_{i, \\lt t}; \\ \\theta_{\\text{old}})}\\)，掩码函数如下：\n$$ \\begin{equation} \\mathcal{M}(k) =\\begin{cases} k \u0026 \\text{if \\ } k \\in [\\alpha, \\beta] \\\\ 0 \u0026 \\text{otherwise}\\end{cases} \\end{equation} $$以及两个超参数 \\(\\alpha\\)、\\(\\beta\\) 来控制下限和上限。\nIcePop 的梯度:\n$$ \\small{\\nabla_\\theta \\mathcal{J}_{\\text{IcePop}}(\\theta) \\sim \\small{\\begin{equation}\\mathbb{E}_{a \\sim \\textcolor{red}{\\pi_{\\text{infer}}}(\\theta_{\\text{old}})} \\Bigg[\\mathcal{M}\\Bigg(\\frac{\\textcolor{blue}{\\pi_{\\text{train}}}(a;\\theta_{\\text{old}})}{\\textcolor{red}{\\pi_{\\text{infer}}}(a;\\theta_{\\text{old}})}\\Bigg ) \\cdot \\nabla_\\theta \\log \\textcolor{blue}{\\pi_{\\text{train}}}(a;\\theta) \\cdot \\hat{A} \\cdot r(a)\\Bigg)\\Bigg].\\end{equation}}} $$我们的工作与 TIS[1] 的区别：\n当 \\(\\dfrac{\\textcolor{blue}{\\pi_{\\text{train}}}(a;\\theta_{\\text{old}})}{\\textcolor{red}{\\pi_{\\text{infer}}}(a;\\theta_{\\text{old}})} \\lt \\alpha\\) 时，\\(\\textcolor{blue}{\\pi_{\\text{train}}}\\) 倾向于为动作分配较小的值，相反，\\(\\textcolor{red}{\\pi_{\\text{infer}}}\\) 输出较高的概率，当比率足够小时，表明训练和推理引擎之间存在巨大分歧。TIS 乘以一个小系数来缓解噪声梯度更新，但是，随着训练的进行，我们发现这种小干扰可以逐渐放大，最终导致基准性能的“平台期”。\n当 \\(\\dfrac{\\textcolor{blue}{\\pi_{\\text{train}}}(a;\\theta_{\\text{old}})}{\\textcolor{red}{\\pi_{\\text{infer}}}(a;\\theta_{\\text{old}})} \u003e \\beta\\) 时，TIS 继续通过乘以一个适中的系数来更新策略梯度。对于IcePop，梯度为零，这意味着我们放弃所有噪声更新，只保留那些健康的策略梯度。\n👂🏼想听听IcePop名字背后的故事吗？\n😄 我们在享受冰棍时想出了这个名字！\n就像冰棍能够让过热的东西冷却下来一样，该算法通过在两侧裁剪极端概率比并掩码差异过大的 token 来 “冷却” 不稳定的在线策略训练。 这种选择性校正 “弹出” 不稳定的贡献，同时保留高效更新，从而在不减慢推理的情况下稳定训练。 实验 # 在本文中，我们在Ring-mini-2.0上比较了三种设置，这是由InclusionAI开发的MoE模型。它拥有16.8B总参数和0.75B激活参数：(1) IcePop，(2) TIS，以及 (3) baseline——不带KL项的原版GRPO，在重复运行中始终失败和崩溃。我们收集了具有挑战性的推理问题作为训练数据集。使用IcePop，我们发现在线策略训练的不稳定性可以得到有效解决，甚至比TIS取得更好的性能。\n在下游任务上，IcePop 优于 TIS 和 baseline。 模型评估：在具有挑战性的基准AIME25上，IcePop 在整个训练过程中持续优于 TIS，表现出很大的提升，最终将基础分数（63%）提高了14%以上，并将与 TIS 的性能差距扩大了相对6%。 图5：AIME25上Avg@64的性能比较。我们使用相同的设置评估所有模型。 更多分析 # 概率差异\n不解决不匹配问题，概率差异增长迅速，如基线设置所示。相比之下，TIS 和 IcePop 都将训练-推理概率的 KL 散度保持在合理范围内。尽管随着训练的进行，所有三种方法的最大概率差异都在上升，但 IcePop 的差异保持相对较低，甚至在400步内有所减少。我们还注意到，TIS 始终显示比我们的方法更大的极端差异和更快的增长，这可能是由于在训练期间包含了噪声策略更新。\n图6：概率差异的最大值。 训练稳定性 我们相信稳定的训练过程是坚实的基础，为展示强化学习的力量提供了充足的空间。值得注意的是，IcePop 和 TIS 都在600个梯度步骤内缓解了 RL 训练的不稳定性，避免了基线设置中发生的快速训练崩溃。\n图7：训练奖励。基线的奖励在180-200步后崩溃。IcePop 和 TIS 都保持稳定增长。 图8：梯度范数（Gradient norm）。基线爆炸，IcePop 和 TIS 保持稳定。 探索空间 我们观察到 IcePop 的对数概率始终保持比 TIS 相对较低的值，这隐含地表明我们的方法避免了过度自信的预测，从而确保了更大的探索空间，其中低概率 token 更有可能被选择，最终增加响应的多样性。\n图9：token 的对数概率。基线快速增长然后跌至底部，而 IcePop 相对稳定。 病态 Token（Ill-conditioned Tokens） 在我们的实验中，我们发现我们的掩码机制的裁剪比例保持在训练 token 的1-2‰左右。随着训练的进行，裁剪比例急剧上升，表明出现了越来越微妙但有害的梯度更新，需要更高的裁剪比例。我们还对裁剪的 token 进行了详细分析。下面的右图显示，与所有 token 相比，裁剪的 token 表现出更高的熵，表明裁剪的 token 在训练中起着重要作用。\n图10：裁剪比例（Clipping ratio）。在我们的默认设置中，IcePop保持约1-2‰的token被裁剪。 图11：所有 token 和裁剪 token 之间 token 熵的比较。与所有 token 相比，裁剪 token 显示出更高比例的高熵 token。 结论和未来工作 # 我们对MoE上的训练-推理概率不匹配问题提供了初步分析。在线策略 RL 训练的不稳定性可能源于训练和推理引擎之间不断增长的概率差异。IcePop 通过在损失级别纠正不匹配来解决这个问题。 一个重要的方向是正式表征崩溃边界，定义为在线策略训练变得不稳定 Reference # [1] Feng Yao, Liyuan Liu, Dinghuai Zhang, Chengyu Dong, Jingbo Shang and Jianfeng Gao. Your Efficient RL Framework Secretly Brings You Off-Policy RL Training. Notion Blog. 2025.\n","date":"2025-09-23","externalUrl":null,"permalink":"/blogs/llm/small-leak-can-sink-a-great-ship-boost-rl-training-on-moe-with-icepop/","section":"Blog","summary":"","title":"译文-Small Leak Can Sink a Great Ship—Boost RL Training on MoE with 𝑰𝒄𝒆𝑷𝒐𝒑!","type":"blogs"},{"content":" date: 2025年9月14日 slug: defeating-nondeterminism-in-llm-inference-translate status: Published tags: LLM, RL, 翻译 type: Post\nhttps://thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference/\n可重现性是科学进步的基石。然而，从大型语言模型中获得可重现的结果却异常困难。\n例如，您可能观察到多次向 ChatGPT 询问相同问题会提供不同的结果。这本身并不令人惊讶，因为从语言模型获取结果涉及\u0026quot;采样\u0026quot;，这是一个将语言模型的输出转换为概率分布并概率性地选择一个标记（token）的过程。\n可能更令人惊讶的是，即使我们将温度（temperature）调整到 0¹（这意味着大型语言模型（LLM）始终选择最高概率的标记，这被称为贪婪采样（greedy sampling）），从而使采样在理论上成为确定性的，LLM API 在实践中仍然不是确定性的（参见以往的讨论这里、这里或这里）。即使在您自己的硬件上使用诸如 vLLM 或 SGLang 等开源推理库运行推理，采样仍然不是确定性的（参见这里或这里）。\n但为什么 LLM 推理引擎不是确定性的呢？一个常见的假设是浮点非结合性和并发执行的某种组合导致基于哪个并发核心首先完成的非确定性。我们将其称为 LLM 推理非确定性的\u0026quot;并发 + 浮点\u0026quot;假设。例如，最近的一篇 arXiv 预印本写道：\nGPU 中的浮点运算表现出非结合性，意味着 $(a + b) + c \\neq a + (b + c)$，这是由于有限精度和舍入误差造成的。这个性质直接影响 Transformer 架构中注意力分数和 logits 的计算，其中跨多个线程的并行操作可以根据执行顺序产生不同的结果。\n您也可以发现其他人重复\u0026quot;并发 + 浮点\u0026quot;假设，比如这里（\u0026ldquo;存在速度权衡，为了使端点快速，使用了 GPU，它们进行并行[非确定性]计算。任何现代 GPU 神经网络计算都会受到这些影响。\u0026quot;），或者这里（\u0026ldquo;因为 GPU 是高度并行化的，加法或乘法的顺序在每次执行时可能不同，这可能会级联到输出中的小差异。\u0026quot;）。\n虽然这个假设并非完全错误，但它没有揭示完整的图景。例如，即使在 GPU 上，在相同数据上重复运行相同的矩阵乘法总是会提供按位相等的结果。我们确实在使用浮点数。我们的 GPU 确实有很多并发性。为什么我们在这个测试中看不到非确定性？\nA = torch.randn(2048, 2048, device=\u0026#39;cuda\u0026#39;, dtype=torch.bfloat16) B = torch.randn(2048, 2048, device=\u0026#39;cuda\u0026#39;, dtype=torch.bfloat16) ref = torch.mm(A, B) for _ in range(1000): assert (torch.mm(A, B) - ref).abs().max().item() == 0 为了理解 LLM 推理非确定性的真正原因，我们必须深入研究。\n不幸的是，即使定义 LLM 推理确定性的含义也很困难。可能令人困惑的是，以下陈述都同时为真：\nGPU 上的一些核（kernel）是非确定性的。 然而，语言模型前向传播中使用的所有核都是确定性的。 此外，LLM 推理服务器（如 vLLM）的前向传播也可以被声称是确定性的。 尽管如此，从任何使用推理服务器的人的角度来看，结果都是非确定性的。 在这篇文章中，我们将解释为什么\u0026quot;并发 + 浮点\u0026quot;假设错过了要点，揭开 LLM 推理非确定性背后的真正罪魁祸首，并解释如何击败非确定性并在 LLM 推理中获得真正可重现的结果。\n原罪：浮点非结合性 # 在谈论非确定性之前，解释为什么会有数值差异是有用的。毕竟，我们通常将机器学习模型视为遵循交换律或结合律等结构规则的数学函数。难道不应该有一个我们的机器学习库应该为我们提供的\u0026quot;数学上正确\u0026quot;的结果吗？\n罪魁祸首是浮点非结合性。也就是说，对于浮点数：\n$$ (a + b) + c \\neq a + (b + c) $$(0.1 + 1e20) - 1e20 \u0026gt;\u0026gt;\u0026gt; 0 0.1 + (1e20 - 1e20) \u0026gt;\u0026gt;\u0026gt; 0.1 具有讽刺意味的是，打破结合性正是使浮点数有用的原因。\n浮点数之所以有用，是因为它们允许\u0026quot;动态\u0026quot;精度级别。为了解释的目的，我们将使用十进制（而不是二进制），其中浮点数的格式为 $\\text{尾数} * 10^\\text{指数}$。我们还将为尾数使用 3 位数字，为指数使用 1 位数字。\n例如，对于值 3450，我们可以将其精确表示为 $3.45 * 10^3$。我们也可以表示更小的值，如 0.486 为 $4.86 * 10^{-1}$。通过这种方式，浮点允许我们表示非常小以及非常大的值。在科学中，我们可以说浮点允许我们维护恒定数量的\u0026quot;有效数字\u0026rdquo;。\n如果您将两个具有相同指数的浮点数相加，它看起来类似于整数加法。例如，123（$1.23 * 10^2$）+ 456（$4.56 * 10^2$）得到 579（$5.79 * 10^2$）。\n但是当我们将两个具有不同指数的浮点数相加时会发生什么，比如 1230 和 23.4？在这种情况下，确切的结果是 1253.4。然而，我们一次只能保持 3 位数的精度。因此，浮点加法将丢弃最后 2 位数字并获得值 $1.25 * 10^3$（或 1250）。\n但此时，我们已经破坏了信息。请注意，每当我们将两个具有不同\u0026quot;尺度\u0026rdquo;（即不同指数）的浮点数相加时，这种情况就会发生。而将不同指数的浮点数相加是经常发生的。事实上，如果我们能保证永远不需要不同的指数，我们就可以只使用整数！\n换句话说，每当我们以不同的顺序将浮点数相加时，我们就可能得到完全不同的结果。举一个极端的例子，根据顺序的不同，对这个数组求和有 102 种不同的结果。\nimport random vals = [1e-10, 1e-5, 1e-2, 1] vals = vals + [-v for v in vals] results = [] random.seed(42) for _ in range(10000): random.shuffle(vals) results.append(sum(vals)) results = sorted(set(results)) print(f\u0026#34;There are {len(results)} unique results: {results}\u0026#34;) # Output: # There are 102 unique results: [-8.326672684688674e-17, -7.45931094670027e-17, ..., 8.326672684688674e-17] 尽管这是非相同输出的根本原因，但它没有直接回答非确定性来自哪里。它没有帮助我们理解为什么浮点值会以不同的顺序相加、何时发生这种情况以及如何避免它。\n答案在于核的实现方式。\n为什么核不总是以相同的顺序添加数字？ # 如上所述，核为什么以不同顺序添加数字的一个常见解释是\u0026quot;并发 + 浮点\u0026quot;假设。该假设指出，如果并发线程完成的顺序是非确定性的，并且累积顺序取决于并发线程完成的顺序（比如原子加法（atomic add）），那么我们的累积顺序也将是非确定性的。\n令人困惑的是，虽然这可能导致非确定性核，但并发性（和原子加法）最终在 LLM 推理非确定性中完全不涉及！为了解释真正的罪魁祸首是什么，让我们首先理解为什么现代 GPU 核很少需要原子加法。\n何时需要原子加法？ # 通常 GPU 跨许多\u0026quot;核心\u0026quot;（即 SM）并发启动程序。由于核心之间没有固有的同步，如果核心需要相互通信，这就构成了挑战。例如，如果所有核心都必须累积到同一个元素，您可以使用\u0026quot;原子加法\u0026quot;（有时称为\u0026quot;取并加\u0026quot;）。原子加法是\u0026quot;非确定性的\u0026quot;——结果累积的顺序纯粹取决于哪个核心首先完成。\n具体来说，想象您正在用 100 个核心归约一个 100 元素向量（例如 torch.sum()）。虽然您可以并行加载所有 100 个元素，但我们最终必须归约到单个元素。完成此操作的一种方法是使用某种\u0026quot;原子加法\u0026quot;原语，其中硬件保证所有加法都将被处理，但不保证顺序。\n图2：原子加法确保每个核心的贡献都将反映在最终的总和中。但是，它不能保证贡献将以什么顺序添加。顺序完全取决于哪个核心首先完成，这是一个不确定的属性。因此，多次执行相同的并行程序可能会导致不确定的输出。\n这通常是人们所说的\u0026quot;非确定性\u0026quot;的含义——您用完全相同的输入执行相同的核两次，但得到不同的结果。这被称为运行间非确定性（run-to-run nondeterminism），即您用完全相同的依赖项运行相同的 Python 脚本两次，但得到不同的结果。\n虽然并发原子加法确实使核非确定性，但对于绝大多数核来说，原子加法是不必要的。事实上，在 LLM 的典型前向传播中，通常没有单个原子加法存在。\n考虑到并行化归约可以从原子加法中受益，这可能令人惊讶。原子加法最终不需要的原因有两个主要原因。\n沿着\u0026quot;批次\u0026quot;维度通常有足够的并行性，因此我们不需要沿着归约维度并行化。例如，假设我们不是归约单个 100 维向量，而是并行归约 500 个向量。在这种情况下，我们可以在每个核心中归约一个完整的向量，并允许每个核心处理不同的向量。 随着时间的推移，大多数神经网络库都采用了各种策略来在不牺牲性能的情况下实现确定性。例如，我们可以执行\u0026quot;分割\u0026quot;（或树）归约，其中我们将 100 元素归约分成五个 20 元素归约（因此实现五路并行性）。然后，为了组合其余的五个元素，我们可以执行单独的\u0026quot;清理\u0026quot;归约（它不是并行化的，但对足够少的元素进行操作以保持便宜）或利用信号量（它确保每个并发线程块将以确定性顺序累积）²。 由于这两个因素，避免原子加法对于绝大多数神经网络操作来说是微不足道的性能损失。\n仍然有几个常见操作在避免原子时会有显著的性能损失。例如，PyTorch 中的 scatter_add（a[b] += c）。然而，在 LLM 中唯一常用的是 FlashAttention 反向传播³。\n然而，LLM 的前向传播不涉及需要原子加法的操作。因此，LLM 中的前向传播实际上是\u0026quot;运行间确定性的\u0026quot;。\n维基百科写道：\u0026ldquo;确定性算法是一种给定特定输入，总是产生相同输出的算法。\u0026ldquo;在这种情况下，给定完全相同的输入（即推理服务器正在处理的确切请求），前向传播总是产生完全相同的输出。\n然而，前向传播本身是\u0026quot;确定性的\u0026quot;并不足以确保包含它的系统是确定性的。例如，如果我们请求的输出依赖于并行用户请求（例如批量标准化（batch-norm））怎么办？由于每个单独的请求无法知道并行请求将是什么，从它们的角度来看，我们的整体 LLM 推理也是非确定性的！\n事实证明，我们请求的输出确实依赖于并行用户请求。不是因为我们以某种方式在批次间泄露信息——而是因为我们的前向传播缺乏\u0026quot;批次不变性\u0026rdquo;，导致我们请求的输出依赖于我们前向传播的批次大小。\n批次不变性和\u0026quot;确定性\u0026rdquo; # 为了解释批次不变性，让我们简化系统并仅查看矩阵乘法（matmul）。您可以假设所有矩阵乘法实现都是\u0026quot;运行间确定性的\u0026quot;⁴。然而，它们不是\u0026quot;批次不变的\u0026quot;。换句话说，当批次大小改变时，批次中的每个元素都可能得到不同的结果。\n从数学角度来看，这是一个相当不寻常的特性。矩阵乘法应该沿着批次中的每个元素\u0026quot;独立\u0026quot;——批次中的其他元素或批次大小都不应该影响批次中特定元素的计算结果。\n然而，正如我们可以经验性地观察到的，这并不成立。\nimport torch torch.set_default_device(\u0026#39;cuda\u0026#39;) B = 2048 D = 4096 a = torch.linspace(-1000, 1000, B*D).reshape(B, D) b = torch.linspace(-1000, 1000, D*D).reshape(D, D) # Doing a matrix vector multiplication by taking # the first element of the batch out1 = torch.mm(a[:1], b) # Doing a matrix matrix multiplication and then taking # the first element of the batch out2 = torch.mm(a, b)[:1] print((out1 - out2).abs().max()) # tensor(1669.2500, device=\u0026#39;cuda:0\u0026#39;) 请注意，这是\u0026quot;运行间确定性的\u0026quot;。如果您多次运行脚本，它将确定性地返回相同的结果⁵。\n然而，当非批次不变核被用作更大推理系统的一部分时，系统可能变得非确定性。当您向推理端点进行查询时，服务器承受的负载量从用户的角度来看实际上是\u0026quot;非确定性的\u0026quot;。负载决定了核运行的批次大小，从而改变每个单独请求的最终结果！\n图4：虽然可以声称推理服务器本身是“确定性的”，但对于单个用户而言，情况就不同了。从单个用户的角度来看，其他并发用户不是系统的“输入”，而是系统的非确定性属性。这使得从每个用户的角度来看，LLM 推理具有“非确定性”。\n如果您将核不变的某种属性（即批次大小）与该属性的非确定性（即服务器承受的负载）结合，您就得到了一个非确定性系统。\n换句话说，几乎所有 LLM 推理端点非确定性的主要原因是负载（因此批次大小）非确定性地变化！这种非确定性并非 GPU 独有——从 CPU 或 TPU 提供的 LLM 推理端点也将有这种非确定性来源。\n所以，如果我们想在推理服务器中避免非确定性，我们必须在核中实现批次不变性。为了理解如何实现这一点，让我们首先看看为什么核首先没有批次不变性。\n如何使核批次不变？ # 为了使 Transformer 实现批次不变，我们必须使每个核批次不变。幸运的是，我们可以假设每个逐点操作都是批次不变的⁶。因此，我们只需要担心涉及归约的 3 个操作——RMSNorm、矩阵乘法和注意力⁷。\n方便的是，这些操作也按照实现批次不变性难度的递增级别排序。每一个都需要一些额外的考虑来以合理的性能实现批次不变性。让我们首先谈谈 RMSNorm。\n批次不变 RMSNorm # 图5：数据并行 RMSNorm 理想情况下，我们希望避免并行化策略中内核之间的通信。实现这一目标的一种方法是将一个批处理元素分配给每个内核，从而保证每个归约完全在单个内核中完成。这被称为“数据并行”策略，因为我们只是沿着不需要通信的维度进行并行化。在这个例子中，我们有四行和四个内核，使我们的内核饱和。\n# x: [batch_size, hidden_dim] # weight: [hidden_dim] def rms_norm(x, weight): return x * torch.rsqrt(torch.mean(x ** 2, dim=-1, keepdim=True)) * weight 批次不变性的要求是，**无论核的批次大小如何，每个元素的归约顺序都必须固定。**请注意，这并不意味着我们必须始终使用相同的归约策略。例如，如果我们改变要归约的元素数量，即使我们的归约策略发生变化，我们仍然可以是批次不变的⁸。\n因此，只有当我们的批次大小影响归约策略时，我们才会打破批次不变性。\n让我们看看 RMSNorm 的标准并行策略。一般来说，并行算法受益于最小化跨核心的通信。为了这次讨论的目的，您可以假设当我们提到\u0026quot;核心\u0026quot;时，我们指的是 SM。更具体地说，这里重要的特性是我们的核启动的线程块数量大于 SM 的数量。因此，我们可以开始的一个策略是将每个批次元素分配给一个核心，如图5所示。\n增加我们的批次大小不会影响我们的归约策略；如果批次大小为 200 为我们的核提供足够的并行性，那么批次大小为 2000 绝对会提供足够的并行性。\n图6：用于更大批量的数据并行 RMSNorm 扩展到更大批量的数据并行策略非常简单 \u0026mdash; 不是让每个核心处理一行，而是允许每个核心依次处理不同的行。由于每个批处理元素的归约策略保持不变，因此这保留了批处理不变性。\n另一方面，减少批次大小可能会带来挑战。因为我们将每个批次元素分配给一个核心，减少我们的批次大小最终将导致核心数量多于批次元素，使一些核心处于空闲状态。\n在遇到这种情况时，一个好的核工程师会采用前面部分提到的解决方案之一（原子加法或分割归约），保持良好的并行性，从而获得良好的性能。不幸的是，这改变了归约策略，阻止了这个核成为批次不变的。\n图7：拆分-减少RMSNorm 如果我们有一个小的批处理大小，我们的数据并行策略可能不再有足够的并行性来饱和我们的核心。在这种情况下，在多个核心之间“拆分”减少可能更有效，从而使我们能够充分利用我们的GPU。但是，这会失去批处理不变性，因为我们不再以相同的顺序减少每个元素。\n最简单的解决方案是简单地完全忽略这些情况。这并非完全不合理——小批次大小意味着核可能很快就会执行完毕，因此速度下降可能不会是灾难性的。\n如果我们不得不优化这个用例，一种方法是始终使用即使对于非常小的批次大小也有足够并行性的归约策略。这种归约策略对于更大的批次大小会导致过多的并行性，但允许我们在整个大小范围内实现不错的（但不是峰值）性能。\n批次不变矩阵乘法 # 数据并行 Matmul 与 RMSNorm 类似，matmul 的标准并行策略是一种“数据并行”策略，将整个归约保持在一个核心中。最直接的想法是将输出张量分成 2D tile ，并将每个 tile 分配给不同的核心。然后，每个核心计算属于该 tile 的点积，再次在一个核心内执行整个归约。 与 RMSNorm 不同，围绕算术强度和利用 tensorcore 的额外约束迫使我们拆分 2D tile ，而不是单个输出元素，以实现高效的 matmul 内核。\n从核心上讲，您可以将矩阵乘法简单地视为逐点操作后跟归约。然后，如果我们通过将输出分块为 tile 来并行化我们的矩阵乘法，我们就有了一个类似的\u0026quot;数据并行\u0026quot;核策略，将每个归约保持在一个核心内。\n也类似于 RMSNorm，我们的\u0026quot;批次\u0026quot;维度（M 和 N）可能变得太小，迫使我们沿着归约维度（K）分割。尽管有两个\u0026quot;批次\u0026quot;维度，矩阵乘法也要求我们每个核心有更多的\u0026quot;工作\u0026quot;以便有效地利用张量核心。例如，如果您有一个 [1024, K] x [K, 1024] 的矩阵乘法和标准的 2D tile 大小 [128, 128]，数据并行策略只能将这个矩阵乘法分割成 64 个核心，不足以饱和 GPU。\n矩阵乘法中沿着归约维度分割被称为 Split-K 矩阵乘法。就像 RMSNorm 一样，使用这种策略会破坏批次不变性。\n矩阵乘法的另一个有趣的并行策略是 stream-k。Stream-k 很有趣，因为它比典型的矩阵乘法具有更少的不变性。如所讨论的，大多数矩阵乘法库不是批次不变的，但它们至少是您可以称之为批次位置不变的（即改变元素在批次内的位置不影响数值）。然而，stream-k 也不是批次位置不变的！其核心洞察是您可以通过对不同的输出 tile 以不同方式沿 k 分割来获得更清洁的负载平衡，但利用这一点也使我们的核不是批次位置不变的。\n图9：Split-K Matmul 如果我们的批处理维度相当小，我们可能没有足够的并行性，需要一个 split-k matmul。在这个例子中，我们将每个归约分成两个核心，这两个核心将分别累积，然后在最后合并它们的结果。然而，将每个归约分成两个核心，使我们仍然可以利用八个核心。\n矩阵乘法有一个额外的复杂性——张量核心指令。尽管对于归约我们可以简单地一次操作一行，有效的矩阵乘法核必须一次操作整个\u0026quot;tile\u0026quot;。\n每个张量核心指令（比如 wgmma.mma_async.sync.aligned.m64n128k16）在内部可能有不同的归约顺序。使用不同张量核心指令的一个原因可能是批次大小非常小。例如，如果我们使用操作长度为 256 的 tile 的张量核心 PTX 指令，但批次大小只有 32，我们就浪费了几乎所有的计算！在批次大小为 1 时，最快的核通常根本不使用张量核心。\n图10：填充后的 Tensor-Core 指令。如果批量大小太小，我们可能无法在输出中放入任何一个 2D tile。在这种情况下，切换到较小的 tensor-core 指令或完全避开 tensor-core 是最有效的！然而，这两种选择都会阻止我们的内核保持批量不变性。\n因此，确保矩阵乘法批次不变性的最简单方法是编译一个核配置并将其用于所有形状。尽管我们会失去一些性能，但这在 LLM 推理中通常不是灾难性的。特别是，当 M 和 N 都很小时最需要 split-k，幸运的是在我们的情况下，N（即模型维度）通常相当大！⁹\n图11：尽管获得了批量不变性，但与cuBLAS相比，我们只损失了大约20%的性能。请注意，这也不是一个优化的Triton内核（例如，没有TMA）。然而，一些性能模式说明了我们的批量不变性要求在哪些方面损失了性能。首先，请注意，由于指令过于庞大且并行性不足，我们在非常小的批量大小下会损失大量的性能。其次，当我们增加批量大小时，会出现一个“拼图”模式，这是由量化效应（包括tile和wave）引起的，这些效应通常可以通过改变tile大小来改善。您可以在这里找到更多关于这些量化效应的信息。\n批次不变注意力 # 图12：FlashAttention2策略：我们沿着Q进行并行化，并同时沿着K/V进行归约。这意味着我们的整个归约可以保持在单个核心内，使其成为另一种数据并行策略。\n在获得矩阵乘法的批次不变性之后，注意力引入了两个额外的复杂性——恰当地说，因为它包含两个矩阵乘法。\n与只沿特征维度归约的 RMSNorm 和矩阵乘法相反，我们现在沿特征维度和序列维度归约。 由于上述原因，注意力必须处理影响序列处理方式的各种推理优化（分块预填充（chunked prefill）、前缀缓存（prefix caching）等）。 因此，为了在 LLM 推理中实现确定性，我们的数值必须既不变于一次处理多少请求，也不变于每个请求在推理引擎中如何被切片。\n让我们首先走过注意力的标准并行策略，这首先在 FlashAttention2 中引入。类似于 RMSNorm 和矩阵乘法，默认策略是\u0026quot;数据并行\u0026quot;策略。由于我们沿着键/值张量归约，数据并行策略只能沿着查询张量并行化。\n例如，根据推理引擎的选择，一个序列可能在几个部分中处理（如在分块预填充中）或者一次全部处理（如果预填充没有被拆分）。为了实现\u0026quot;批次不变性\u0026quot;，给定标记的归约顺序不依赖于来自其序列的多少其他标记正在同时处理是必要的。如果您将 KV 缓存中的 K/V 值与正在处理的当前标记中的 K/V 值分开归约（如在 vLLM 的 Triton 注意力核中），这无法实现。例如，在处理序列中的第 1000 个查询标记时，无论 KV 缓存中有 0 个标记（预填充）还是 999 个标记（解码），归约顺序都必须相同。\n图13：具有 KV 缓存的 FlashAttention 显式地将 KV 缓存与当前 KV 值分开处理会破坏批次不变性的原因有点微妙，并且与“边界条件”有关。 特别是，假设您的块大小为 32，但我们目前在 KV 缓存中有 80 个元素。 然后，我们计算另外 48 个未缓存的元素。 在这种情况下，我们需要三个块（两个完整块和一个掩码块）来计算“P 缓存”，另外两个块（一个完整块和一个掩码块）来计算“P”。 因此，当我们只有四个块（即 128 个）元素要计算时，我们需要总共五个块来计算我们的缩减，这肯定会改变我们的缩减顺序。 例如，如果我们 KV 缓存中没有元素并且总共处理 128 个元素，那么在这两种情况下，我们都需要具有相同的数值，以确保注意力的“批次不变性”。\n为了解决这个问题，我们可以在注意力内核本身之前更新KV缓存和页表，确保无论处理多少个token，我们的键和值始终以一致的方式布局。\n有了这个额外的细节（以及前面部分提到的所有内容，例如一致的 tile 大小），我们就能够实现一个批处理不变的注意力实现！\n然而，这里有一个重要的问题。与矩阵乘法不同，我们在 LLM 推理中看到的注意力形状通常确实需要一个拆分规约的核函数，通常称为 Split-KV 或 FlashDecoding。这是因为如果我们不沿着规约维度并行化，我们只能沿着批次维度、头维度和“查询长度”维度并行化。在注意力机制的解码阶段，查询长度非常小，因此除非我们有非常大的批次大小，否则我们通常无法使 GPU 饱和。\n不幸的是，像处理 RMSNorm 和 Matmuls 那样忽略这种情况并不容易。例如，如果你有一个非常长的 KV 缓存，尽管只处理一个请求，注意力核函数也可能花费很长时间。\n图14：固定数量的 Split-KV 策略（即 FlashDecode）。如果我们的查询长度变得非常小（就像在解码期间那样），我们可能会陷入核函数中几乎没有并行性的情况。在这些情况下，我们需要再次沿着规约维度进行拆分——这次是 KV 维度。如何沿 KV 维度进行拆分的典型策略是计算出我们需要多少并行性，然后均匀地划分 KV 维度。例如，如果我们的 KV 长度是 1000，并且我们需要 4 次拆分，每个核心将处理 250 个元素。 不幸的是，这也破坏了批次不变性，因为我们精确的规约策略取决于我们在任何给定请求中处理了多少来自序列的查询 token 。\n此外，通常用于注意力机制的拆分规约策略也对批次不变性构成了挑战。例如，FlashInfer 的“平衡调度算法”选择了能够使 GPU 所有核心饱和的最大拆分大小，从而使规约策略不是“批次不变的”。然而，与 RMSNorm/Matmuls 不同，无论批次大小如何，选择一个固定的拆分数量是不够的。\n相反，为了实现批次不变性，我们必须采用“固定拆分大小”的策略。换句话说，我们不是固定拆分的数量，而是固定每次拆分的大小，然后最终得到一个可变数量的拆分。通过这种方式，我们可以保证无论我们正在处理多少个 token ，我们总是执行相同的规约顺序。\n这需要一些我们代码发布中未包含的内部 FlexAttention 更改。我们将在不久的将来将它们上游化！\n图15：固定大小的 Split-KV 策略。这个策略和前一个策略唯一的区别是我们的拆分现在是“固定大小”的。例如，如果我们的 KV 长度是 1000，我们不会把它分成四个均匀的长度为 250 的拆分，而是会把它分成三个固定大小的长度为 256 的拆分和一个长度为 232 的拆分。 这使我们能够保持批次不变性，因为我们的规约策略不再依赖于我们一次处理多少个查询 token ！\n实现 # 我们通过利用 vLLM 的 FlexAttention 后端以及 torch.Library，提供了一个在 vLLM 之上进行确定性推理的演示。通过 torch.Library，我们能够以非侵入性的方式替换掉大多数相关的 PyTorch 操作符。你可以在 thinking-machines-lab/batch-invariant-ops 找到“批次不变”核函数库，以及在“确定性”模式下运行的 vLLM 示例。\n实验 # 补全结果的不确定性有多大？ # 我们使用 Qwen/Qwen3-235B-A22B-Instruct-2507，在温度为 0 的情况下，使用提示“Tell me about Richard Feynman”（非思考模式）采样 1000 个补全，每个生成 1000 个 token 。令人惊讶的是，我们生成了 80 个独特的补全，其中最常见的出现了 78 次。\n观察补全的不同之处，我们发现补全内容在前 102 个 token 上实际上是完全相同的！补全内容出现分歧的第一个实例发生在第 103 个 token 。所有补全都生成了序列“Feynman was born on May 11, 1918, in”。然而，其中 992 个补全接着生成了“Queens, New York”，而 8 个补全生成了“New York City”。\n另一方面，当我们启用批次不变的核函数时，我们所有的 1000 个补全都是完全相同的。这是我们从采样器中数学上所期望的，但如果没有我们的批次不变的核函数，我们是无法实现确定性结果的。\n性能 # 我们尚未在优化批次不变核函数的性能方面投入大量精力。但是，让我们运行一些实验来验证我们的性能仍然可用。\n我们将设置一个带有一个 GPU 的 API 服务器，运行 Qwen-3-8B，并请求 1000 个序列，输出长度在 90 到 110 之间。\n配置 时间（秒） vLLM 默认 26 未优化的确定性 vLLM 55 + 改进的注意力核函数 42 大部分的性能下降来自于 vLLM 中的 FlexAttention 集成尚未经过深度优化。尽管如此，我们看到性能并非灾难性的。\n真正的在线策略强化学习 # 正如研究人员指出的，训练和推理之间不同的数值，隐含地将我们的在线策略（on-policy）强化学习（RL）变成了离线策略（off-policy）强化学习。\n当然，如果我们甚至无法从两个相同的推理请求中获得逐位相同的结果，那么在训练和推理之间获得逐位相同的结果是不可能的。然后，确定性推理使我们能够修改我们的训练栈，以在采样和训练之间获得逐位相同的结果，从而实现真正的同策略强化学习。\n我们在 Bigmath 上的 RLVR 设置中进行了实验，其中 RL 策略从 Qwen 2.5-VL instruct 8B 初始化，最大 rollout 长度为 4096。\n如果我们不进行离策略校正（即重要性加权）进行训练，我们的奖励会在训练中途崩溃，而添加一个离策略校正项则可以使训练顺利进行。但是，如果我们在采样器和训练器之间实现了逐位相同的结果，我们就是完全的同策略（即 0 KL 散度），并且也可以顺利训练。\n我们还可以绘制采样器和训练器之间对数概率（logprobs）的 KL 散度，其中所有 3 次运行都表现出显著不同的行为。当使用重要性加权运行时，它保持在 0.001 左右，并有偶尔的峰值。然而，不使用重要性加权运行最终会导致 KL 散度在奖励崩溃的大约同一时间出现峰值。当然，当运行“真正的同策略强化学习”时，我们的 KL 散度保持在 0 的平坦线上，表明训练策略和采样策略之间没有分歧。\n图16：注意，没有重要性加权的运行在第 318 步附近有一个显著的损失峰值，并且这伴随着对数概率 KL 散度的相应峰值。同时，使用离线策略校正或使用“真正的在线策略”运行都允许 RL 顺利继续。显示“真正的在线策略”的蓝线不是一个错误——它只是一条在 0 处的平坦线。\n结论 # 现代软件系统包含多层抽象。在机器学习中，当我们遇到不确定性和细微的数值差异时，我们常常倾向于将它们掩盖过去。毕竟，我们的系统已经是“概率性”的了，多一点不确定性又有什么关系呢？在失败的单元测试中提高 atol/rtol 的值有什么问题呢？训练器和采样器之间的对数概率差异可能不是一个真正的错误，对吧？\n我们拒绝这种失败主义。只要稍加努力，我们就能理解不确定性的根源，甚至解决它们！我们希望这篇博文能为社区提供一个关于如何解决推理系统中不确定性的坚实理解，并激励其他人去全面了解他们的系统。\n脚注：\n¹ 这意味着大型语言模型（LLM）始终选择最高概率的标记，这被称为贪婪采样（greedy sampling）。\n² 信号量策略可以在这里找到描述。\n³ 有趣的事实：您知道广泛使用的 FlashAttention 反向传播的 Triton 实现实际上在算法上与 Tri Dao 的 FlashAttention-2 论文不同吗？标准的 Triton 实现在反向传播中进行额外的重计算，避免原子但成本增加 40% FLOPs！\n⁴ 这并不完全正确，但大多数常见的矩阵乘法实现确实具有这个特性。\n⁵ 它不是\u0026quot;硬件/软件版本不变的\u0026quot;——您的 GPU/PyTorch 版本可能返回不同的值，但它应该确定性地返回相同的值。\n⁶ 尽管这对于 PyTorch 中的所有核都是真的，但它并非本质上真实。例如，CPU 上的一些核实现会在数组的某些部分使用向量化内在函数，在其他部分使用非向量化内在函数，这些内在函数不一定总是具有按位相同的数值。\n⁷ 与并行性相关的归约超出了本讨论的范围，但相同的原理适用。一个可能有用的事实是，在 Blackwell 以及带有 CUDA 12.8+ 的 Hopper 上，NVLink-Sharp 交换机内归约是确定性的。与许多事情一样，这个信息可以在 NCCL 的 github issues 上找到。\n⁸ The Quack 博客文章有一些很好的例子，展示了您可以执行的各种归约策略的层次结构（例如线程归约、warp 归约、块归约、集群归约）。\n⁹ 可以在这里找到有关 LLM 推理中矩阵乘法形状的有趣分析。意力核本身之前更新 KV 缓存和页表，确保我们的键和值始终一致地布局，无论正在处理多少标记。\n有了这个额外的细节（以及前面部分提到的所有内容，如一致的 tile 大小），我们能够实现批次不变的注意力实现！\n然而，这里有一个重大问题。与矩阵乘法不同，我们在 LLM 推理中看到的注意力形状通常确实需要分割归约核，通常称为 Split-KV 或 FlashDecoding。这是因为如果我们不沿着归约并行化，我们只能沿着批次维度、头维度和\u0026quot;查询长度\u0026quot;维度并行化。在注\n","date":"2025-09-14","externalUrl":null,"permalink":"/blogs/llm/effective-context-engineering-for-ai-agents-claude/","section":"Blog","summary":"","title":"击败 LLM 推理中的非确定性-Thinking Machines","type":"blogs"},{"content":"","date":"2025-09-12","externalUrl":null,"permalink":"/tags/meta/","section":"Tags","summary":"","title":"Meta","type":"tags"},{"content":" 论文：REFRAG: Rethinking RAG based Decoding\n代码（非官方）：simulanics/REFRAG\nRAG 的效率困境 # LLM在利用外部知识库进行检索增强生成（Retrieval-Augmented Generation, RAG）方面展现了卓越的能力。然而，这一能力并非没有代价。RAG 系统通常会将检索到的多个文本段落拼接起来，形成一个很长的上下文（Long-Context）输入给 LLM。处理这种长上下文会引发两个核心问题：\n高延迟：尤其是首个 Token 的生成时间（Time-to-First-Token, TTFT）会随着上下文长度二次方增长，严重影响实时交互体验。 高内存占用：LLM 推理过程中需要缓存键值对（Key-Value Cache），其内存消耗与上下文长度成正比，限制了吞吐量。 论文《REFRAG: Rethinking RAG based Decoding》指出，RAG 的上下文具有独特的“稀疏性”。检索到的段落通常是为了内容多样性或去重而选择的，彼此之间语义关联较弱，导致模型内部的注意力模式呈现出“块对角（block-diagonal）”特性。这意味着在解码过程中，大部分跨段落的注意力计算是冗余且不必要的。\n基于这一洞察，研究者提出了 REFRAG，一个专为 RAG 应用设计的、能 “压缩、感知和扩展”上下文的高效解码框架。实验结果表明，REFRAG 可以在不损失模型效果（Perplexity）的前提下，实现高达 30.85 倍的 TTFT 加速，并将 LLM 的有效上下文窗口扩展 16 倍。\nREFRAG 的核心思想与架构 # REFRAG 的核心思想是：用预先计算好的、压缩后的“块嵌入（chunk embeddings）”来替代大部分原始的上下文 Token。这从根本上缩短了输入到解码器的序列长度，从而解决了延迟和内存占用的瓶颈。\n其模型架构如「图1」所示，主要由两部分构成：\n轻量级编码器（Light-weight Encoder）：例如 RoBERTa。它的任务是将输入的上下文文本（Context Text）分割成多个固定大小的“块（chunks）”，并为每个块生成一个紧凑的向量表示，即“块嵌入”。这个过程可以预先计算并缓存，以实现高效复用。 解码器（Decoder-only Foundation Model）：例如 LLaMA。它不再接收冗长的原始上下文 Token，而是接收用户的查询（Question）Token 和由编码器生成的“块嵌入”序列。 Figure1 REFRAG主要设计 这种架构带来了三大优势：\n缩短输入长度：解码器的输入序列长度从 (查询Token数 + 上下文Token数) 锐减为 (查询Token数 + 块数)。由于一个块可以包含数十个 Token，输入长度被大幅压缩，显著降低了计算量。 消除冗余计算：块嵌入可以被预先计算和缓存。在多次推理或多轮对话中，相同的上下文段落无需重复编码。 降低注意力复杂度：注意力计算的复杂度从与上下文 Token 数量的二次方相关，转变为与块数量的二次方相关，极大地提升了效率。 关键技术：REFRAG 的训练方法 # 为了让解码器能够理解并有效利用这些“块嵌入”，REFRAG 采用了一套精心设计的训练流程，包含持续预训练（Continual Pre-training, CPT）和监督微调（Supervised Fine-tuning, SFT）两个阶段。其中，CPT 阶段是实现高效压缩的关键。\n1. 重构任务（Reconstruction Task） # 这是对齐编码器和解码器的核心步骤。在这一任务中，研究者冻结解码器的参数，只训练轻量级编码器和一个投影层（projection layer）。训练目标是让编码器生成的“块嵌入”经过投影后，能够被解码器准确地重构出原始的文本块。\n这个设计的精妙之处在于，它强迫“块嵌入”必须包含足够的信息来还原原文，从而确保了压缩过程的信息保真度，使得嵌入向量成为原始文本块的一个忠实表征。\n2. 课程学习（Curriculum Learning） # 直接从多个块嵌入中重构长文本是一项非常困难的任务。为了解决这一优化难题，REFRAG 引入了课程学习（Curriculum Learning）策略。训练从最简单的任务开始，例如只用一个块嵌入重构一个文本块，然后逐步增加难度，过渡到用多个块嵌入重构多个文本块。如「图6」所示，训练过程中，简单任务（如 1 x k）的样本比例逐渐减少，而复杂任务（如 256 x k）的样本比例逐渐增加。\nFigure6 训练期间课程学习中的数据混合 3. 基于强化学习的选择性压缩（RL-based Selective Compression） # REFRAG 的一个重要创新是并非所有上下文都同等重要。某些关键的文本块可能需要以原始 Token 的形式呈现给解码器，以获得最精确的答案。为此，REFRAG 引入了一个基于强化学习（Reinforcement Learning, RL）的轻量级策略网络。\n该策略网络学习判断哪些文本块应该被“扩展”为原始 Token，哪些可以保持为压缩的“块嵌入”。其奖励信号是解码器在预测下一个段落时的困惑度（Perplexity）的负数。通过这种方式，模型学会在效率和精度之间做出动态权衡：\n对于信息量大、与查询高度相关的关键块，策略网络会选择扩展它们，确保解码器能获取最完整的信息。 对于大部分辅助性或冗余的上下文，则保持其压缩状态，以最大化效率。 这种“随处压缩（compress anywhere）”的能力使得 REFRAG 非常灵活，能够支持多轮对话和复杂的智能体（agentic）应用。\n实验效果与分析 # 研究者在多个数据集上对 REFRAG 进行了严谨的评估，并与 LLaMA、CEPE 等多个基线模型进行了比较。\n延迟与吞吐量 # 如「图2」所示，REFRAG 在系统性能上取得了显著提升。\nFigure2 REFRAG在𝑘=16时的推理加速经验验证。 TTFT 加速：在 16384 个 Token 的中长上下文场景下，当压缩率 k=16 时，REFRAG (Cached) 实现了 16.53 倍的 TTFT 加速，是当时 SOTA 模型 CEPE (2.01x) 的 8 倍以上。当压缩率 k=32 时，TTFT 加速更是达到了 30.85 倍。 吞吐量提升：相较于 LLaMA，REFRAG 实现了高达 6.78 倍的吞吐量提升，同样远超 CEPE。 模型效果（Perplexity） # 性能的提升并未以牺牲模型效果为代价。如「表1」和「表 2」所示，在 ArXiv、Book 等多个评估数据集上：\nREFRAG 在不同压缩率下（REFRAG8, REFRAG16）的困惑度均优于 CEPE、REPLUG 等基线模型。 即使面对比训练时更长的上下文，REFRAG 依然能保持优异的性能，证明了其良好的泛化能力。 与 LLaMA 相比，REFRAG 的 TTFT 加速 16.53 倍，同时在四个数据集上的平均困惑度还提升了 9.3%。 下游应用性能 # RAG 性能：在 RAG 任务中，无论是在强检索器还是弱检索器场景下，REFRAG 都表现出色。如 [图 4] 所示，在同等延迟下（例如 REFRAG 处理 8 个段落 vs LLaMA 处理 1 个段落），REFRAG 由于能看到更广的上下文，其平均性能提升了 1.22% (强检索器) 到 1.93% (弱检索器)。\nFigure4 强检索器场景（左）与弱检索器场景（右）下的 RAG 性能对比 多轮对话：在多轮对话任务中，传统模型因上下文窗口限制（如 LLaMA 的 4k）不得不截断历史对话，导致信息丢失。而 REFRAG 凭借其压缩能力，可以在有限的计算预算内保留更完整的对话历史，从而在多个数据集上显著优于基线模型（「表 4」）。\nTable4 在多轮RAG任务上的表现，对于#段落=5和#段落=10的情况。 结论 # REFRAG 提出了一种专为 RAG 应用优化的新型解码框架。它通过一个轻量级编码器将冗长的上下文压缩为高效的“块嵌入”，并利用强化学习策略智能地决定何时解压关键信息，从而在不牺牲模型精度的前提下，极大地降低了 RAG 系统的推理延迟、提升了吞吐量。\n这项工作不仅为解决 LLM 在长上下文应用中的效率瓶颈提供了一个实用且可扩展的方案，也揭示了针对特定应用场景（如 RAG）设计专门优化策略的重要性，为未来高效大模型推理系统的发展开辟了新的方向。\n","date":"2025-09-12","externalUrl":null,"permalink":"/blogs/rag/refrag-rethinking-rag-based-decoding/","section":"Blog","summary":"","title":"REFRAG：Rethinking RAG based Decoding","type":"blogs"},{"content":"","date":"2025-09-07","externalUrl":null,"permalink":"/tags/openai/","section":"Tags","summary":"","title":"OpenAI","type":"tags"},{"content":" Web\nPDF\n语言模型（LLM）的“幻觉”——即模型在不确定时，倾向于捏造看似合理但错误的陈述，而非承认知识的局限——是当前阻碍其在关键领域应用的核心障碍之一。Adam Tauman Kalai等人于2025年9月4日发表的论文《Why Language Models Hallucinate》，为我们提供了一个清晰且深刻的分析框架。该文的核心价值不在于提出一种新颖的算法来“修复”幻觉，而在于从统计学和评估体系两个层面，系统性地揭示了幻觉产生的根源，并指明了一条更根本的解决路径。论文有两大核心贡献：\n理论创新：如何巧妙地将一个复杂的“生成”问题，规约为一个更易于分析的“二元分类”问题，从而为幻觉的存在性提供了坚实的数学证明。 实践洞察：揭示了当前主流的LLM评估体系，是如何在无意中“鼓励”和“强化”模型的幻觉行为，并提出了一个社会技术层面的解决方案。 论文将幻觉这一复杂现象，还原为了一个可分析、可度量的统计学问题。它清晰地论证了幻觉的产生和持续，源于两个环环相扣的核心因素：\n预训练的“原罪”：幻觉是模型在学习语言分布过程中，固有的统计压力所导致的必然产物。 后训练与评估的“合谋”：当前主流的评估体系（benchmarks）普遍采用“二元评分”机制，系统性地奖励“猜测”行为，而惩罚“承认不确定性”，从而加剧了幻觉问题。 一、 重新定义问题：从生成到分类的巧妙规约 # 论文的第一个精妙之处，在于它绕开了直接证明“一个生成模型必然会产生错误”这一棘手的任务。作者们设计了一个名为“Is-It-Valid (IIV)”的虚拟二元分类问题，其核心思想是：让一个分类器来判断一个给定的文本字符串是“有效的 (valid)”还是“错误的 (error)”。\n生成任务：模型需要从一个庞大的输出空间中，生成一个属于“有效集合” \\(\\mathcal{V}\\) 的实例。 IIV分类任务：给定一个实例 \\(x\\) ，模型仅需判断 \\(x\\) 属于“有效集合” \\(\\mathcal{V}\\) 还是“错误集合” \\(\\mathcal{E}\\) 。 通过这个设定，作者建立了一个连接生成模型错误率和IIV分类错误率的关键不等式：\n$$ (\\text{generative error rate}) \\ge 2 \\cdot (\\text{IIV misclassification rate}) $$这个不等式是论文理论部分的基石。它直观地揭示了一个深刻的联系：\n左侧的“生成错误率” 指的是语言模型产生幻觉（即输出错误内容）的概率。 右侧的“IIV分类错误率” 指的是一个理想分类器在判断某个内容是否有效时犯错的概率。 这个关系意味着，如果某个知识领域本身就难以被有效分类（即IIV分类错误率高），那么任何试图在该领域只生成有效内容的语言模型，其产生幻觉的概率必然会很高。例如，对于那些没有明显规律可循的任意事实（如个人生日），模型很难学习到一个完美的分类边界来区分所有正确和错误的日期组合，因此其IIV分类错误率天然就高。根据上述不等式，这直接导致了模型在被问及此类问题时，生成错误答案（幻觉）的概率下限也被抬高了。\n这一规约的巧妙之处在于，它将一个难以捉摸的、开放式的生成问题，转化为了一个具有坚实理论基础的、封闭式的分类问题，使得对幻觉的分析从哲学思辨进入了数学证明的范畴。\n二、 幻觉的统计溯源：预训练阶段的“原罪” # 基于IIV框架，论文进一步探讨了在预训练阶段，幻觉产生的两个主要统计驱动因素。\n2.1 任意事实与“独生样本率” (Singleton Rate) # 这是论文中最具洞察力的发现之一。对于那些缺乏内在逻辑模式、只能靠记忆的“任意事实”（Arbitrary Facts），模型的表现高度依赖于训练数据。作者引入了“独生样本率”（Singleton Rate, SR）这一概念，即在训练数据中仅出现过一次的事实的比例。\n基于Alan Turing的“缺失质量”估计理论，作者们在论文的定理二（Theorem 2）中，给出了幻觉错误率（err）的一个惊人下界：\n$$ \\text{err} \\ge \\text{sr} - \\frac{2}{\\min_c |E_c|} - \\frac{35+6\\ln N}{\\sqrt{N}} - \\delta $$其中，\\(sr\\) 是独生样本率，\\(|E_c|\\) 是错误选项的数量，\\(N\\) 是训练样本总数，\\(\\delta\\) 是校准误差。在 \\(N\\) 足够大时，后两项可以忽略。\n模型产生幻觉的错误率，至少与训练数据中那些“仅见过一次”的事实的比例相当。独生样本是模型知识边界的脆弱地带。由于只见过一次，模型无法对其形成稳健的统计表征，也无法泛化。这些样本构成了模型知识的“已知未知”区域。当模型被问及这些或与之相关但未见过的事实时，其犯错的概率非常高。例如，如果训练数据中20%的生日信息都只出现过一次，那么可以预期，模型在回答关于生日的问题时，至少会产生20%的幻觉。\n这个发现将幻觉与训练数据的统计特性直接挂钩，为我们理解和预测模型的幻觉行为提供了量化依据。\n2.2 模型结构缺陷 (Poor Models) # 论文还指出了另一种情况：当模型本身的结构或能力不足以捕捉数据中的复杂模式时，也会导致幻觉。一个经典的例子是n-gram模型。如论文中提到的，一个trigram模型（只看前两个词）无法区分“She lost it and was completely out of her mind”和“He lost it and was completely out of his mind”中的性别对应关系，因为它缺乏捕捉长距离依赖的能力。\n这说明，即使数据中存在清晰的模式，一个“糟糕”的模型（a poor model）由于其表征能力的限制，其IIV分类错误率也会很高，从而不可避免地产生幻觉。这解释了为什么在需要复杂推理（如精确的字母计数）的任务上，一些看似强大的模型（如 DeepSeek-V3）会表现不佳，而具备链式思考能力的模型则能正确回答。\n三、 幻觉的持续存在：后训练与评估的“合谋” # 如果说预训练为幻觉埋下了统计的“种子”，那么后训练（post-training）和现行的评估体系则为它的“茁壮成长”提供了土壤。这是论文的第二个核心贡献，一个强有力的“社会技术”层面的批判。\n作者指出，当前绝大多数主流的LLM基准测试和排行榜，都采用二元评分（Binary Grading），即“非对即错”。如论文「表2」所示，包括GPQA, MMLU-Pro, SWE-bench在内的大多数知名评估基准，对于模型输出的“我不知道”（IDK）或任何形式的不确定性表达，都直接判为0分。\n这种评估机制创造了一种“激励扭曲”：\n猜测的期望收益 \u0026gt; 承认不确定性的收益。在一个只奖励正确答案，而不区分“猜错”和“不说”的体系下，模型的最优策略就是“大胆猜测”。一个总是猜测的模型（Model B）会在这些基准测试中得分高于一个诚实地承认不确定性的模型（Model A）。 优化目标与真实世界需求脱节。模型开发者为了在排行榜上获得更高名次，会不自觉地优化模型，使其更倾向于“应试”，而不是成为一个在现实世界中值得信赖的助手。 作者将这种现象称为“惩罚不确定性的流行病”（an \u0026ldquo;epidemic\u0026rdquo; of penalizing uncertainty）。这完美解释了为什么尽管学术界和工业界投入了大量精力进行指令微调（Instruction Tuning）和从人类反馈中强化学习（RLHF），幻觉问题依然顽固存在。因为只要评估的“指挥棒”不变，模型就会持续被训练成一个“熟练的应试者”，而不是一个“诚实的知识伙伴”。\n四、 解决方案：从技术修复到生态重塑 # 基于以上分析，论文的作者们没有提出一个新的模型架构或RLHF变体，而是提出了一个更根本的、社会技术层面的解决方案：改造现有的评估体系。\n具体建议是，在主流的评估基准（如MMLU, SWE-bench）中引入明确的置信度阈值和错误惩罚机制。例如，在问题中明确指示：\n“只有当你的置信度高于 t 时才回答。错误答案将被扣除 p 分，而‘我不知道’的回答得0分。”\n这种改变会带来两个深远的影响：\n重新校准激励机制：它迫使模型在回答前进行“风险评估”。只有当模型预测的正确概率高于预设的阈值t时，回答的期望收益才是正的。这会激励模型开发者去训练能够准确评估自身不确定性的模型。 实现客观公正的评估：通过明确指定评分规则，评估变得更加客观。一个在所有置信度阈值下都表现优异的模型，才是真正强大的模型。 这个解决方案的深刻之处在于，它认识到幻觉不仅是一个技术问题，更是一个由社区、排行榜和评估文化共同塑造的“生态问题”。试图通过纯技术手段去“堵”幻觉的“漏洞”，而不改变其产生的激励环境，终将是治标不治本。\n总结 # 《Why Language Models Hallucinate》通过一个优雅的理论框架和对实践的敏锐洞察，成功地“祛魅”了语言模型的幻觉现象。\n从理论上看，它将幻觉的产生与可证明的统计下界联系起来，特别是与“独生样本率”的关联，为我们理解模型的知识边界提供了强有力的量化工具。 从实践上看，它尖锐地指出了当前LLM评估生态的系统性缺陷，并提出了一个虽然实施困难但方向正确的改革方案。 这篇论文最大的“巧思”在于，它告诉我们，解决幻觉的关键可能不在于设计更复杂的模型，而在于构建一个更诚实的评估环境。与其无休止地进行技术上的“军备竞赛”，不如回归本源，重新思考我们到底想要一个什么样的AI——是一个在任何情况下都能给出答案的“万事通”，还是一个在知道与不知道的边界上保持谦逊和诚实的伙伴？\n","date":"2025-09-07","externalUrl":null,"permalink":"/blogs/llm/llm-hallucinate/","section":"Blog","summary":"","title":"Why Language Models Hallucinate——解构LLM幻觉","type":"blogs"},{"content":"","date":"2025-09-07","externalUrl":null,"permalink":"/tags/%E5%B9%BB%E8%A7%89/","section":"Tags","summary":"","title":"幻觉","type":"tags"},{"content":" RAG without Embeddings\nColab Notebook\nHow OpenAI Could Change the Way We Retrieve Information — Without Embeddings or Vector Databases TL;DR # 这篇文章介绍了一种由 OpenAI Cookbook 提出的新型 RAG（检索增强生成）架构，旨在利用 GPT-4.1 和 Gemini 等模型百万级 Token 上下文窗口的能力，绕过传统的向量嵌入（Embeddings）和向量数据库，直接通过 LLM Agent 进行智能检索。\n🛠️ 核心机制：模仿人类阅读的三步流程 # 该方法不再依赖语义相似度匹配，而是将检索视为一个多步骤的推理过程：\n路由与导航 (Routing \u0026amp; Navigation)： 系统将文档切分（例如按章节），但不转化为向量。 核心 Agent 使用“草稿纸 (Scratchpad)”机制进行推理，像人类查阅目录或略读一样，递归地判断哪些文本块包含相关信息，通过层级向下钻取。 生成与引用 (Generate Answer)： 在定位到精确的段落后，LLM 生成答案并强制要求附带精确的引用来源（Citations）。 答案验证 (The Judge)： 引入“LLM 即裁判 (LLM-as-Judge)”步骤（如使用 o4-mini），对比生成的答案与源文档，验证事实准确性和引用正确性，以消除幻觉。 ✅ 主要优势 (Pros) # 零基础设施门槛：无需选择嵌入模型、维护向量数据库或处理复杂的索引更新。 零输入延迟 (Zero-ingest Latency)：新文档上传后可立即查询，非常适合频繁更新的数据。 更高的推理质量：通过“草稿纸”推理和全文档上下文理解，比单纯的语义匹配更能处理复杂的跨段落查询。 可信度高：内置的验证机制确保了答案有据可查。 ⚠️ 权衡与挑战 (Cons) # 高昂的成本：每个查询需要多次 LLM 调用（路由、生成、验证），比廉价的向量搜索贵得多。 高延迟：多步推理和递归搜索导致响应速度较慢。 扩展性瓶颈：面对成千上万份文档的海量数据集时，如果不配合预过滤步骤，单纯依靠 Agent 遍历是行不通的。 🎯 适用场景 # 适合：高价值、高精度的任务。例如复杂的法律简报分析、技术手册查询，或者数据动态变化且需要精确引用的场景。 不适合：需要毫秒级响应的通用聊天机器人，或处理海量静态数据的场景（此时传统 RAG 仍是首选）。 一句话总结：这是一种“用计算换架构”的范式转变——牺牲速度和成本，换取了更简化的架构和更高的检索准确性，实际上是将“嵌入”内化到了大模型的注意力机制之中。\nRAG Without Embeddings # 如果你正在基于大型语言模型 (LLMs) 构建应用，你大概率已经与检索增强生成 (RAG) 打过交道了。对于利用外部数据来让 LLM 获取事实依据，这是一种极好的方法，但其中的“R”部分——检索 (Retrieval)——可能会相当复杂。\n我深知这种痛苦——花几个小时纠结分块 (chunk) 的大小、重叠策略，以及该选哪种嵌入模型 (embedding model)。而且还要管理向量数据库 (vector databases)？那完全是基础设施层面的挑战。\n因此，当我偶然发现 OpenAI 在其 Cookbook 中关于一种不同的 RAG 方法的最新探索时——一种承诺绕过传统嵌入的方法——我的开发者大脑顿时兴奋了起来。我们真的可以在没有向量数据库那些繁琐操作的情况下获得出色的检索结果吗？这个想法感觉真正打破了常规。\n其中的秘诀似乎在于利用像 GPT-4 或 Gemini Flash 这样较新模型的海量上下文窗口 (context windows)。能够一次性处理一百万个 token 的模型开启了全新的工作流。但是，上下文窗口的大小是唯一起作用的因素吗？\n超越标准 RAG 流程 # 传统的 RAG 流程非常直截了当：\n输入 (Ingest)：分割文档，对文本块进行嵌入，并存储在向量数据库中。 查询 (Query)：对查询内容进行嵌入，在数据库中搜索相似的文本块。 增强与生成 (Augment \u0026amp; Generate)：将文本块和查询一起投喂给 LLM 以获取答案。 这需要繁重的预处理工作，还得维护那个向量索引。这是一种稳健的方法，但它引入了延迟、存储开销以及对嵌入质量的依赖。此外，分块 (chunking) 过程本身可能导致上下文丢失，相关信息被分割到不同的块中，从而导致检索遗漏。\nAgentic 方法：模仿你的大脑 # OpenAI 的 Agentic RAG 则截然不同。它是一个多步骤的过程，模仿了你处理长文档以寻找答案的方式。你不会通读每一个字；你会略读，找到有希望的章节，深入研读，然后提取相关内容。\n这种方法使用不同的 LLM 调用，在每个阶段充当专门的“智能体 (Agents)”。这就提出了一个有趣的问题：这种方法真的是“无嵌入”的吗？还是 LLM 本身在其注意力机制 (attention mechanism) 中隐式地创建并使用了一种形式的“语义表征”？\n窥探代码：一个多步骤的过程 # 加载文档 (Load the Document)\nimport requests # ... other imports ... def load_document(url: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;Load a document from a URL and return its text content.\u0026#34;\u0026#34;\u0026#34; print(f\u0026#34;Downloading document from {url}...\u0026#34;) response = requests.get(url) response.raise_for_status() # ... (pdf reading and text extraction) ... return full_text # Load the document (e.g., a legal manual with 900k+ tokens) document_text = load_document(\u0026#34;https://www.uspto.gov/sites/default/files/docume 初始分割 (Initial Split)\n# ... (tokenizer import) ... def split_into_20_chunks(text: str, min_tokens: int = 500) -\u0026gt; List[Dict[str, An \u0026#34;\u0026#34;\u0026#34;Split text into up to 20 chunks, respecting sentence boundaries.\u0026#34;\u0026#34;\u0026#34; sentences = sent_tokenize(text) tokenizer = tiktoken.get_encoding(TOKENIZER_NAME) # ... (logic to build chunks from sentences and handle chunk size) ... return chunks document_chunks = split_into_20_chunks(document_text, min_tokens=500) # This results in ~20 chunks, each potentially holding tens of thousands of tok 路由与导航（核心智能体） (Routing \u0026amp; Navigation (The Core Agent))\n# ... (OpenAI client setup) ... def route_chunks(question: str, chunks: List[Dict[str, Any]], depth: int, scratchpad: str = \u0026#34;\u0026#34;) -\u0026gt; Dict[str, Any]: \u0026#34;\u0026#34;\u0026#34;Ask the model which chunks contain info relevant to the question.\u0026#34;\u0026#34;\u0026#34; # ... (system prompt defining role as document navigator) ... user_message = f\u0026#34;QUESTION: {question}\\n\\n\u0026#34; if scratchpad: user_message += f\u0026#34;CURRENT SCRATCHPAD:\\n{scratchpad}\\n\\n\u0026#34; user_message += \u0026#34;TEXT CHUNKS:\\n\\n\u0026#34; # ... (add chunks text with IDs to user_message) ... # Step 1: Ask model to record reasoning in scratchpad (required tool call) # ... (model call with tools and tool_choice=\u0026#34;required\u0026#34;) ... # ... (process tool call, update scratchpad) ... # Step 2: Ask model to select relevant chunk IDs (structured output) text_format = { \u0026#34;format\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;json_schema\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;selected_chunks # ... (model call with text_format) ... # ... (extract selected_ids from JSON response) ... return { \u0026#34;selected_ids\u0026#34;: selected_ids, \u0026#34;scratchpad\u0026#34;: new_scratchpad } navigate_to_paragraphs 函数随后会递归调用 route_chunks，在文档层级中向下钻取，直到内容颗粒度足够细。\n生成最终答案 (Generate Final Answer)\nfrom pydantic import BaseModel, field_validator from typing import List, Literal class LegalAnswer(BaseModel): answer: str citations: List[str] @field_validator(\u0026#39;citations\u0026#39;) def validate_citations(cls, citations, info): # ... (logic to check if citations match passed valid IDs) ... return citations def generate_answer(question: str, paragraphs: List[Dict[str, Any]], scratchpad: str) -\u0026gt; LegalAnswer: \u0026#34;\u0026#34;\u0026#34;Generate an answer from the retrieved paragraphs.\u0026#34;\u0026#34;\u0026#34; # ... (prepare context string from paragraphs with IDs) ... # ... (system prompt focusing on answering ONLY from provided text) ... response = client.responses.parse( model=\u0026#34;gpt-4.1\u0026#34;, input=[ {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: system_prompt.format(...)}, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: f\u0026#34;QUESTION: {question}\\n\\nSCRATCHPAD: { ], text_format=LegalAnswer, temperature=0.3 ) return response.output_parsed 使用 Pydantic 和 field_validator 的结构化输出可确保引用的有效性，并提高可追溯性。\n答案验证（裁判） # class VerificationResult(BaseModel): is_accurate: bool explanation: str confidence: Literal[\u0026#34;high\u0026#34;, \u0026#34;medium\u0026#34;, \u0026#34;low\u0026#34;] def verify_answer(question: str, answer: LegalAnswer, cited_paragraphs: List[Dict[str, Any]]) -\u0026gt; VerificationResult \u0026#34;\u0026#34;\u0026#34;Verify if the answer is grounded in the cited paragraphs.\u0026#34;\u0026#34;\u0026#34; # ... (prepare context string from cited paragraphs with IDs) ... system_prompt = \u0026#34;\u0026#34;\u0026#34;You are a fact-checker for legal information. Your job is to verify if the provided answer: 1. Is factually accurate according to the source paragraphs 2. Uses citations correctly # ... (instructions for confidence scoring) ... \u0026#34;\u0026#34;\u0026#34; response = client.responses.parse( model=\u0026#34;o4-mini\u0026#34;, input=[ {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: system_prompt}, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: f\u0026#34;QUESTION: {question}\\n\\nANSWER TO VER ], text_format=VerificationResult ) return response.output_parsed 这个 “LLM即裁判 (LLM-as-Judge)” 步骤充当了关键的最后一道防线，提高了系统的整体可靠性。\n抽丝剥茧：深入审视 OpenAI 的 Agentic 策略 # 虽然 OpenAI 对 Agentic 检索的尝试绝对是在挑战极限，但我们绝对有必要先踩一脚刹车，真正深入挖掘它可能在哪些地方受阻，以及它如何与老派的传统 RAG 进行较量。\n昂贵的代价与漫长的等待 (The Price Tag and the Wait Time):\n你注意到的第一件事是什么？高昂的成本和显著的延迟。与简单、快速地跳转到向量数据库不同，这种方法需要 LLM 针对每个查询多次执行操作。这会累积成巨大的成本——无论是算力还是等待时间。因此，这里隐约可见的大问题是：随着数据量变大、问题变得更棘手，这种金钱和时间上的负担实际上如何扩展？我们能不能削减一些开销？或许缓存一些中间步骤，或者仅在初始路由调用中使用更小、更灵活的模型会是明智之举。\n扩展的阵痛 (Scaling Pains):\n好吧，当你只是筛选一份文档时，感觉相当不错。但如果要跨越成千上万份文档进行搜索呢？那完全是另一回事了。 在这种规模下，你可能必须在文档级别预先过滤或索引内容；这不再是可选项，而是变成了必选项。感觉真正有前途的前进方向是一种混合架构——你使用某种粗略但快速的方法来迅速缩小可能性范围，然后针对那少数精选的内容释放 Agentic 系统进行深度推理。\n不可避免的上下文瓶颈 (The Unavoidable Context Crunch):\n即使最新的模型吹嘘上下文窗口延伸到数百万个 token——哇，对吧？——它们仍然会碰壁。对于真正海量或极其复杂的文档，这个 Agentic 系统仍可能遇到限制。接下来的难题就变成了：它如何优雅地处理那些刚刚突破这个限制的文档？也许未来的迭代会变得更聪明，或许使用迭代摘要或智能上下文压缩技术来绕过这个障碍。\n并非所有查询都生而平等 (Not All Queries Are Created Equal):\n你知道，问题也分三六九等。简单的、基于事实的问题？是的，那可能会相对轻松过关。但是那些多部分查询，或者有点抽象的问题呢？它们注定会更棘手。那个内部的“思维草稿 (scratchpad)”机制真的能为复杂的思考提供足够的支持吗，还是我们需要内置额外的工具或推理辅助？\n机器中的幽灵：幻觉 (The Ghost in the Machine: Hallucination):\n看，任何依赖 LLM 的系统都有胡编乱造的风险——即幻觉。这个 Agentic 流程中的多步验证环节正是为了对抗这一点而设计的。但它有多万无一失呢？是否存在“裁判”模型可能错过细微不准确之处的棘手边缘情况？老实说，做一个对比研究，比较传统 RAG 和这种新的 Agentic 方法之间的幻觉率，将会是非常有见地的。\n嵌入：消失了，还是仅仅被融合了？ (Embeddings: Gone, or Just Integrated?):\n最后，有一点更具哲学意味但值得细细咀嚼：这真的是“无嵌入”的吗？因为在其运作深处，特别是在那个注意力机制内，LLM 绝对正在构建一种对输入的内部表征——可以说，那就是一种动态嵌入。这微妙地改变了我们看待整个过程的方式。Agentic 方法似乎并没有完全废弃嵌入，而是将它们直接编织进核心检索和推理循环中，改变了它们被使用的位置和方式，而不仅仅是抛弃它们。\n优点和缺点：权衡与选择 # 这种 Agentic 风格的 RAG 虽然不可否认地聪明，但肯定也伴随着它自己的一套权衡。在好的一面，你获得了极棒的零输入延迟 (zero-ingest latency)——这意味着你可以立即查询全新的文档，无需枯燥地等待预处理。它在文档中移动的方式有一种真正的优雅，有点像模仿人类阅读的方式，在需要的地方深入钻研。这种动态的、分层的导航通常可以挖掘出更精准的答案来回答错综复杂的问题，特别是通过连接不同部分的观点的交叉推理。此外，对于核心检索部分，你跳过了整个索引基础设施的令人头痛的问题，使得设置变得简单了不少。而且至关重要的是，特别是当准确性至高无上时，那个明确的、结构化的验证过程增加了一层至关重要的信任，确保它给你的答案真正由源材料支持。\n但是，一如既往，这些好处不是免费的。该系统自然会产生更高的单次查询成本，因为每个问题会触发多次 LLM 调用，而不是仅仅一次快速、廉价的向量数据库查找。这意味着，是的，事情可能会花更长一点的时间——你可能会注意到由于那递归的文档探索而增加的延迟。最重要的是，这种特定的、无索引的实现与经典 RAG 相比，当你面对真正庞大的文档堆时，在可扩展性方面会感到有点受限，除非你加入一些初始的预过滤步骤。\n知道何时使用它 # 那么，考虑到这种取舍，这种方法在什么场景下真正大显身手？它似乎特别适合处理冗长、专业文档中的复杂问题——想想详细的法律简报或密集的能技术手册——在这些地方，把事实弄得完全正确，并能够用精确的引用指向它们，是绝对不可妥协的。对于总是处于变化中、不断更新的数据，它感觉也是对的，仅仅因为没有静态索引需要你不断重建。反过来说，如果你需要的是一个快如闪电的通用聊天机器人，需要瞬间从巨大的静态数据集中提取信息，传统的基于嵌入的 RAG 可能仍然是你的首选。\n探讨未来的概念 # OpenAI 的头脑们自然正在思考这可以如何演变。一个想法是混合系统：也许使用传统的嵌入搜索来快速缩小范围，然后让 Agentic 系统在那个更小、更易于管理的池子中漫游和推理。另一个非常酷的可能性？利用那些海量的上下文窗口，仅一次性地从文档构建一个详细的知识图谱 (knowledge graph)，作为一个前期任务，然后让一个不同的、也许更轻量的模型在那个图谱中快速穿梭寻找答案。这有点像是结合了两个世界的长处，你不觉得吗？最后，调节“颗粒度”——让用户轻松指定他们希望系统深入到什么程度，是需要句子级的证据（对法律工作至关重要）还是也许只是一个段落（对于筛选新闻文章可能完全没问题）。这全在于找到精准度与运营成本之间的那个最佳平衡点。\n总结 # 深入研究这种 Agentic RAG 方法确实在我的脑海中巩固了几件关键的事情。那些百万 token 的上下文窗口？彻底改变了游戏规则，实现了这种流畅的、即时的文档探索。它阅读的方式，在层级中移动，感觉很自然，绝对有助于搞定更棘手问题的相关性。拥有那个思维草稿 (scratchpad)？它通过给模型空间来一步步解决问题，真正提升了推理能力。而且事实上，你可以在不需要整个数据库设置的情况下（至少对于核心检索部分）相当快地开始，这绝对是一个胜利，取决于你试图实现什么。但至关重要的是，那个内置的检查让你更信任答案——知道它们实际上是直接从源头提取并经过验证的。\n总的来说？这个 Agentic RAG 的东西真的令人兴奋。它真正展示了 LLM，特别是那些拥有大上下文窗口和 Agentic 能力的 LLM，如何以新鲜、强大的方式破解复杂的信息问题——尤其是当“把事情做对”和“深入理解”比“快如闪电”或处理“暴力堆量”更重要时。\n","date":"2025-09-04","externalUrl":null,"permalink":"/blogs/rag/no-embedding-rag-translate/","section":"Blog","summary":"","title":"超越嵌入：OpenAI 的 Agentic RAG 方法深度解析","type":"blogs"},{"content":"","date":"2025-09-04","externalUrl":null,"permalink":"/tags/%E6%97%A0%E5%B5%8C%E5%85%A5/","section":"Tags","summary":"","title":"无嵌入","type":"tags"},{"content":" Arxiv: A comprehensive taxonomy of hallucinations in Large Language Models 大型语言模型（LLM）彻底改变了自然语言处理领域，但其产生“幻觉”——即生成看似合理但事实错误或完全捏造的内容——的倾向，仍然是一个严峻的挑战。这种现象不仅是技术上的小瑕疵，更是关乎模型可靠性、安全性和信任度的核心问题。本文基于最新的学术研究，旨在全面、系统地剖析LLM幻觉的定义、分类、成因、评估方法及缓解策略。\n一、定义LLM幻觉及其必然性 # 1. 概念性定义\n在LLM领域，幻觉被广泛理解为模型生成了“看似合理但并非事实的内容”。这意味着模型产生的输出可能是“错误的、不相关的，或者根本不符合事实逻辑的”。这与医学上指没有外部刺激的感官体验不同，LLM的幻觉是指在回应用户查询时，创造了非事实信息，并且通常不明确说明其内容的虚构性。\n2. 形式化定义与理论必然性\n论文《幻觉是不可避免的：大型语言模型的一种内在局限性》提供了一个严谨的形式化框架，从可计算性理论的角度论证了幻觉的必然性。\n形式化定义：\n幻觉被形式化地定义为一个可计算的LLM（表示为 h）与一个可计算的基准真相函数（表示为 f）之间的不一致性。\nf 的形式化世界 (基准真相函数): 这个世界被概念化为一个集合 \\(G_f = \\{(s, f(s))|s \\in S\\}\\)，其中对于任何来自所有有限长度字符串集合 S 的输入字符串 s，f(s) 代表唯一的正确输出。 幻觉条件: 一个LLM h 被认为相对于基准真相函数 f 产生了“幻觉”，如果在其所有训练阶段中，总存在至少一个输入字符串 s，使得LLM的输出 h(s) 与正确的输出 f(s) 不匹配。形式化表达为：\\(\\forall i \\in \\mathbb{N}, \\exists s \\in S\\) 使得 \\(h_i \\neq f(s)\\)。 理论必然性的推论：\n该框架通过一系列定理指出，幻觉是可计算LLM的一种固有特性，无论其架构、学习算法或训练数据如何。其核心论证根植于对角论证法（diagonalization），这是可计算性理论中的一个经典证明技术。\n定理1：可计算可枚举的LLM集合必将产生幻觉：对于任何可计算可枚举的LLM集合（这包括了所有当前的多项式时间有界LLM），都存在一个可计算的基准真相函数 f，使得该集合中所有LLM的所有状态都会产生幻觉。 定理2：LLM将在无穷多个问题上产生幻觉：该论断进一步扩展，指出对于任何可计算可枚举的LLM集合，都存在一个基准真相函数 f，使得该集合中所有LLM的所有状态都会在无穷多个输入上产生幻觉。 定理3：任何可计算的LLM都将产生幻觉：该定理将结论推广至任何单个的可计算LLM。这意味着，即使是未来的LLM，只要它们是可计算的，就必然会存在使其产生幻觉的基准真相。 推论1：无法自我消除幻觉：一个直接的推论是，所有可计算的LLM在本质上都缺乏阻止自己产生幻觉的能力。这意味着仅依赖LLM内部机制（如基于提示的思维链推理）的缓解策略，无法从根本上消除幻觉。 这些理论（详情见 表1）揭示了一个根本性的事实：幻觉并非一个可以通过改进训练或架构就能完全根除的“bug”，而是根植于可计算性本质的内在限制。\n二、LLM幻觉的核心分类法 # 学术界对LLM幻觉提出了几种关键的分类方法，其中最基础和被广泛接受的两个维度是“内在与外在”和“事实性与忠实性”。\n1. 内在幻觉 (Intrinsic Hallucination) vs. 外在幻觉 (Extrinsic Hallucination)\n这个分类基于生成文本与所提供输入上下文的关系。\n内在幻觉: 指生成的文本直接与输入或上下文相矛盾。这种错误源于模型在推理过程中无法保持逻辑一致性。例如，一篇待摘要的文章指出某疫苗于2019年获批，而模型生成的摘要却称该疫苗被拒绝。 外在幻觉: 指生成的文本与训练数据不一致，并且“既不能被输入上下文支持，也无法被其证伪”。这通常涉及引入现实中不存在的实体、事实或事件。例如，声称“巴黎虎于1885年被猎杀至灭绝”，这是一个完全捏造的实体和事件。 2. 事实性幻觉 (Factuality Hallucination) vs. 忠实性幻觉 (Faithfulness Hallucination)\n这个分类关注生成内容的真实性及其对输入的遵循程度。\n事实性幻觉: 指模型生成了“事实错误的内容”，直接与“真实世界知识”或“已建立的验证来源”相矛盾。它关乎内容的绝对正确性。例如，模型声称“查尔斯·林德伯格是第一个登上月球的人”。 忠实性幻觉: 指模型的输出“偏离了输入提示或提供的上下文”。生成的回答可能内部逻辑自洽且看似合理，但未能遵循用户的明确指令或输入信息。这与内在幻觉高度相关。例如，在摘要任务中，原文明确指出FDA批准了某疫苗，而摘要却称FDA拒绝了它，这既是内在幻 giác，也是忠实性幻觉。 这些重叠但视角不同的分类法（见 表2 总结）表明，该领域仍在积极地完善对幻觉的定义。理解这些细微差别至关重要，因为不同类型的幻觉通常源于不同的底层机制，需要特定的检测和缓解策略。\n三、幻觉的具体类型与表现 # 除了核心分类，LLM幻觉还表现为多种具体形式，每种形式都有其独特的特征和影响。\n事实错误与捏造 (Factual errors and fabrications): 包括不正确的事实（如Google Bard曾错误宣称詹姆斯·韦伯太空望远镜拍摄了第一张系外行星图像）和完全捏造的实体/信息（如在法律文书中引用不存在的判例）。 上下文不一致 (Contextual inconsistencies): 模型输出的信息未出现在提供的上下文中或与之相矛盾。 指令不一致/偏离 (Instruction inconsistencies/deviation): 模型未能遵循用户的明确指令，例如要求用西班牙语翻译却用英语回答。 逻辑不一致 (Logical inconsistencies): 输出包含内部逻辑错误或自相矛盾的陈述。 时间错乱 (Temporal disorientation): 产生过时的、时代错误的或时间上不正确的事实。 伦理违规 (Ethical violations): 产生有害、诽谤或法律上不正确的内容，例如错误地指控个人犯罪。 其他类型: 还包括混合幻觉（Amalgamated hallucinations，错误地组合多个事实）、无意义回复（Nonsensical responses）以及特定于任务的幻觉（如对话、摘要、代码生成、多模态中的幻觉）。 这些多样化的表现形式凸显了幻觉问题的复杂性，并警示我们“一刀切”的解决方案是行不通的。\n四、幻觉的深层根源 # LLM幻觉源于训练数据、模型架构和用户提示之间复杂的相互作用（见 表3 总结）。\n数据相关因素: 训练数据质量与数量: 包含错误、噪声或不一致性的数据是产生事实错误回答的直接原因。 数据偏差与代表性不足: 训练数据中的偏见会导致模型复现“模仿性谬误”。 过时的数据与知识边界: 静态的训练数据使模型无法处理动态更新的信息，且模型常常无法识别自身知识的边界。 模型相关因素: 自回归（Auto-regressive）性质: 模型的核心设计是预测下一个最可能的词元，而非确保事实准确性。 架构与训练过程: 如单向表示的局限性、训练与推理间的暴露偏差（exposure bias）、过度优化特定指标等都可能增加幻觉。 解码策略: 随机性采样（如高“temperature”设置）会增加创造性，但同时也显著提升了幻觉风险。 过度自信与校准不足: 模型常以极高的置信度输出错误信息，误导用户。 缺乏推理能力: 模型主要依赖统计相关性而非真正的因果或逻辑推理。 提示相关因素: 对抗性攻击: 在提示中故意嵌入虚假信息可能诱导模型产生或放大错误内容，形成“垃圾进，垃圾出”的问题。 提示方法: 模糊或限制性不足的提示会增加幻觉的概率。 综合来看，幻觉并非简单的bug，而是当前LLM设计范式的一种涌现属性（emergent property）。\n五、幻觉的缓解策略 # 鉴于幻觉的理论必然性，研究重点已从“完全消除”转向“有效缓解”。策略可分为架构层面和系统层面。\n架构缓解策略: 工具增强（Tool-augmented）: 如 Toolformer，让LLM学会在推理时调用外部API、计算器或代码解释器，将事实密集型任务外包给更可靠的专业工具。 通过检索机制进行事实溯源: 检索增强生成（RAG） 是最广泛采用的框架之一。它通过在生成前从可信的知识库中检索相关文档，为模型提供事实依据，从而约束其生成内容。 使用合成或对抗性过滤数据进行微调: 在经过验证的、事实正确的数据集上进行微调，或使用幻觉检测模型过滤掉不佳的输出，以优化模型。 系统缓解策略: 护栏（Guardrails）与符号集成: 在部署层面应用基于规则的控制机制。逻辑验证器可检查输出的内部逻辑，而事实过滤器则可将生成的主张与外部知识图谱进行比对。 基于规则的回退机制: 当模型不确定或输出被标记为潜在幻觉时，系统可以执行预定义的回退策略，如拒绝回答、请求人类介入或要求用户澄清。 未来的方向在于开发结合多种互补策略的 混合与上下文感知（hybrid and context-aware） 的缓解系统。\n结论 # LLM幻觉是一个复杂、普遍且理论上不可避免的挑战。它源于数据、模型和交互等多个层面的复杂因素。这一深刻的认知要求我们将战略重心从“根除幻觉”转向开发稳健的检测机制、实施有效的缓解策略，并确保持续的人工监督。\n","date":"2025-08-16","externalUrl":null,"permalink":"/blogs/llm/llm-hallucinations-taxonomy/","section":"Blog","summary":"","title":"LLM幻觉不可避免？","type":"blogs"},{"content":"","date":"2025-08-01","externalUrl":null,"permalink":"/tags/prompt/","section":"Tags","summary":"","title":"Prompt","type":"tags"},{"content":" Where to show Demos in Your Prompt: A Positional Bias of In-Context Learning 本文源自Cobbina和Zhou (2025)发表的研究《Where to show Demos in Your Prompt: A Positional Bias of In-Context Learning》。该研究首次系统性地揭示并量化了大型语言模型（LLM）中一种新颖且重要的现象——“样例位置偏见”（Demos\u0026rsquo; Position in Prompt Bias, DPP Bias），即上下文学习（ICL）中，示例（demos）在提示语中的空间位置对模型性能和预测稳定性产生的巨大影响。本文将遵循“理解先于评判”的原则，首先梳理该论文的研究框架、核心方法与关键发现，随后结合建设性质疑，探讨其理论贡献、方法论稳健性及未来研究方向。\n1. 研究背景与核心问题 # 上下文学习（In-Context Learning, ICL）已成为现代大型语言模型的核心能力之一，它允许模型在不更新参数的情况下，仅通过在提示语中提供少量示例来适应新任务。然而，ICL的性能表现出显著的“脆弱性”，对提示语的细微变化高度敏感。虽然已有研究（如 Lu et al., 2022）深入探讨了示例内容的选择（demo selection）和示例内部的排列顺序（internal permutation of demos）对模型性能的影响，但Cobbina与Zhou的这项工作则另辟蹊径，将目光投向了一个更基本却被长期忽视的维度：将内容和顺序完全固定的示例模块作为一个整体，其在提示语中的外部放置位置（external placement）会产生何种影响？\nCobbina与Zhou敏锐地捕捉到这一研究空白。他们提出的核心问题是：\n在一个结构化的提示语（如包含系统指令和用户查询的对话格式）中，将内容完全相同的示例模块放置在不同位置，是否会影响模型的输出？如果会，这种影响的模式、规模和内在机制是什么？\n该论文将这种纯粹由空间位置（spatial placement）而非内容引发的性能波动，定义为DPP偏见。这一问题的提出具有重大的理论与实践价值，因为它直接挑战了“LLM能够以位置无关的方式稳健整合上下文信息”这一基础假设。若此假设不成立，则当前大量的提示工程实践可能缺乏坚实的理论基础，模型的可靠性和可复现性也将面临严重考验。\n2. 理论基础与方法解读 # 为系统性地研究DPP偏见，作者们设计了一套严谨的评估框架，包括明确的问题形式化、规范化的位置定义以及创新的评估指标。\n2.1 问题形式化与位置定义 # 研究的核心在于精密的变量控制。作者们在实验中保持提示语的所有内容要素——系统指令、用户查询、以及示例本身——完全不变，仅改变示例模块（demo block）的插入位置。这种设计有效地将性能变化归因于唯一变量：位置。\n作者利用当前主流的指令微调模型普遍采用的“对话式”模板，定义了四种具有代表性的规范化位置（Canonical Demo Positions）：\n位置标识 全称 描述 结构示意图 ssp Start of System Prompt 示例置于系统指令之前，是整个提示语的最前端。 [Demos] [System Instructions] \u0026lt;user\u0026gt; [Query] esp End of System Prompt 示例置于系统指令之后，但在用户消息之前。 [System Instructions] [Demos] \u0026lt;user\u0026gt; [Query] sum Start of User Message 示例置于用户消息的开头，但在实际查询之前。（被视为默认位置） [System Instructions] \u0026lt;user\u0026gt; [Demos] [Query] eum End of User Message 示例置于用户查询之后，是整个提示语的末尾。 [System Instructions] \u0026lt;user\u0026gt; [Query] [Demos] 这四种位置的划分逻辑清晰，覆盖了示例与系统指令和用户查询的主要相对关系（之前/之后），为系统性探究提供了坚实基础。\n🔍 方法疑问: 论文将 sum (Start of User Message) 设定为计算预测变化率（Prediction Change）时的“默认”基准。这一选择的依据是什么？虽然许多提示模板确实将示例放在用户查询前，但将其奉为“默认标准”可能是一种先验假设。如果选择一个理论上更优的位置（如本文发现的 ssp）作为基准，可能会揭示出从“最优”到“次优”位置的性能衰减模式，从而提供不同维度的洞察。\n2.2 创新诊断指标 # 除了任务相关的标准指标（如准确率、F1值、ROUGE等），作者引入了两个任务无关的诊断性指标，用以量化DPP偏见的影响：\n准确率变化 (Accuracy Change, \\(∆_{metric}\\)):\n$$ ∆_{metric} = Metric_{position} - Metric_{zero-shot} $$该指标衡量在特定位置放置示例相对于零样本（zero-shot）基线的净性能增益。它清晰地显示了ICL在不同位置下的“有效性”。\n预测变化率 (Prediction Change, \\(∆_{pred}\\)):\n$$ ∆_{pred} = \\frac{\\#answer \\\\ flips}{\\#Q} $$该指标衡量当示例位置从默认的 sum 移动到其他位置时，模型预测结果发生改变的样本比例。这是一个衡量输出不稳定性或波动性（volatility）的关键指标，即使在整体准确率变化不大的情况下，高 \\(∆_{pred}\\) 也意味着模型决策边界的剧烈扰动。\n这两个指标的引入是本研究方法论上的一大亮点，它们共同构成了对DPP偏见“效果”与“副作用”的完整度量。\n3. 实验设计与结果分析 # 本研究的实验部分以其广度和深度令人印象深刻，覆盖了4个模型家族（QWEN, LLAMA3, MISTRAL, COHERE）的10个不同规模（1.5B到72B）的模型，以及8个跨越不同能力的NLP任务（分类、问答、摘要、推理）。\n图 1：在prompt中的四种配置（DPP） 3.1 核心发现：显著的“首位效应”与“末位惩罚” # 实验结果揭示了一个一致且强烈的模式：\n首位效应 (Primacy Effect)：将示例放置在提示语的早期位置（ssp 和 esp）通常能获得最高且最稳定的性能。如图2所示，在多个分类任务中，ssp 位置带来的准确率提升（Accuracy Change）普遍高于其他位置。 末位惩罚 (Recency Penalty)：将示例放置在用户查询之后（eum）表现最差。它不仅常常导致性能低于其他ICL位置，甚至有时会低于zero-shot基线。更严重的是，eum 位置会引发剧烈的预测波动。如图1和图3所示，eum 位置可导致高达45.5%的预测发生翻转，而这些翻转往往并未带来正确率的提升。 图2：四个DPP在四个数据集上的准确率变化 图3：三个DPP（不包括总和）在四个数据集上的预测变化（与总和相比）比率 3.2 统计显著性与波动性深度分析 # 为了确保观察到的性能差异并非偶然，并更深入地理解预测波动，论文进行了细致的统计检验和可视化分析。\n表1 表2 统计稳健性验证：在第4.5节和表1、表2中，作者采用了配对的威尔科克森符号秩检验（Paired Wilcoxon signed-rank test）。结果（以MMLU任务为例）显示，ssp、esp、sum 这三个位置的性能相较于零样本基线有极显著的提升（p \u0026lt; 0.01），而 eum 位置的提升则不具备统计显著性。更进一步的配对比较显示，eum 的性能显著劣于其他三个位置（p \u0026lt; 0.05）。这为“首位效应”和“末位惩罚”提供了强有力的统计学证据，证明了这些发现的可靠性。 Sankey图揭示的内在波动：论文中的Sankey图（如Fig. 5-7）是理解预测波动性的关键。它们直观地展示了当示例位置改变时，预测结果从“正确”到“错误”（C→I）、“错误”到“正确”（I→C）的具体流向。以图6为例，我们可以看到，即使 esp 位的净准确率仅比 sum 位高一点，其内部却发生了剧烈的变化：大量的样本从正确变为错误，同时又有大量的样本从错误变为正确。这揭示了一个惊人的事实：即使宏观准确率看似稳定，模型底层的决策逻辑可能已经天翻地覆。 这也凸显了 \\(∆_{pred}\\) 指标的重要性，它捕捉了这种被传统准确率指标所掩盖的“隐性不稳定性”。 3.3 规模的影响：非线性的伸缩法则 # 论文通过对不同尺寸模型的分析，揭示了DPP偏见与模型规模的复杂关系：\n普遍趋势：更大的模型通常表现出更强的位置鲁棒性，即不同位置间的性能差异和预测波动性会减小（如图4所示）。 非单调变化：有趣的是，最佳位置并非随模型增大而固定。在GSM8K算术推理任务中，小型模型在 ssp 处表现最佳，而LLAMA3-70B这样的大型模型却在 eum 位置获得了惊人的性能提升（从21.5%提升至88%）。 图4:（a）四个 DPP 在 MMLU 上的预测变化（与总和相比）：随着模型规模的增加而下降 （b）四个DPP在MMLU上的准确性变化（相对于零-shot的改进）：随着模型规模的增加而下降 📊 结果解释: 这种非单调的规模效应是一个值得深思的发现。作者推测这与模型能力有关，即大模型可能拥有更强的能力来“回顾”并整合位于查询之后的信息。然而，另一种可能性是，对于某些特定任务（如结构化推理），将示例放在最后，能够让模型在完成对查询的初步解析后，再将示例作为一种“解题模板”或“格式参考”，从而表现更佳。这究竟是一种通用的涌现能力，还是特定模型与特定任务结构耦合的产物？\n3.4 任务与模型的特异性：不存在“万能钥匙” # 通过详尽的“胜-平-负”（win-tie-loss）分析（如图8-10），论文有力地证明了不存在一个普遍最优的示例位置。最佳位置是模型相关且任务相关的。\n⚠️ 数据关注: 整个研究的实验都基于k=5个示例。ICL的性能同样对示例数量k敏感（即所谓的“k-shot sensitivity”）。研究结论在多大程度上依赖于k=5这一设定？例如，在k=1的单示例场景下，位置效应是否会被放大或削弱？在k值更大（如k=10）时，模型是否会因为上下文窗口的压力而表现出不同的位置偏好？这是一个重要的、可能影响结论普适性的控制变量。\n4. 主要贡献与创新总结 # Cobbina与Zhou的这项工作为ICL领域带来了以下关键贡献：\n识别并定义新偏见：首次系统性地揭示了“Demos\u0026rsquo; Position in Prompt (DPP) bias”，填补了ICL敏感性研究的重要空白。 构建严谨评估框架：提出了一套包含规范化位置定义、创新诊断指标（\\(∆_{metric}\\)， \\(∆_{pred}\\)）以及统计验证方法的评估流水线，为后续研究提供了标准化的工具。 提供大规模经验证据：通过跨模型、跨任务的详尽实验，证实了DPP偏见的普遍存在，并揭示了其与模型规模、任务类型的复杂交互关系。 深化对ICL机制的理解：研究结果为解释LLM的内在机制（如注意力机制的“首位效应”和“中间遗忘”）提供了新的、角度独特的经验证据。 提供实践指导与警示：明确指出不存在“一招鲜”的最佳示例位置，强调了提示工程必须是模型和任务感知的，并警示从业者不能忽视提示语的宏观结构。 5. 局限性与未来方向讨论 # 💭 理论思考: 论文将DPP偏见的根源归结为架构原因（自回归模型的内在特性）和数据原因（指令微调语料中固定的模板格式）。未来的研究可以通过精心设计的实验来解耦这两个因素。例如，在一个完全不含固定格式、示例位置完全随机的语料上从头预训练一个模型，观察其是否仍然表现出类似的位置偏见。\n🌐 应用局限与商业价值:\n商业闭源模型：本研究聚焦于开源模型。像GPT-4、Claude 3等顶尖商业模型，经过更复杂的对齐和微调，是否能更好地克服这种位置偏见？或者它们会展现出新的、不同的偏好模式？验证这一点对于企业级应用至关重要。 实际应用启示：这一发现对构建RAG（检索增强生成）系统、AI Agent以及提示市场（Prompt Marketplace）有直接影响。例如，RAG系统在将检索到的知识片段注入提示时，应将其放在ssp或esp位置以获得最佳效果。提示工程师在设计和售卖提示时，不仅要优化内容，还必须明确标注其最佳结构和位置。 低成本缓解策略：作者提出的缓解策略成本较高。未来可以探索更轻量级的方法，如通过元指令（meta-instruction）引导模型注意力（例如，在提示语中加入“请仔细研究以下示例，然后回答最后的问题”）。 🔍 方法论拓展:\n多模态场景：位置偏见在视觉-语言模型中将如何表现？当提示包含交错的文本和图像时，图像的位置、文本示例的位置，它们之间会产生怎样的交互效应？这是一个全新的前沿领域。 思维链（CoT）的扩展：该研究聚焦于标准的输入-输出示例。对于包含复杂推理过程的思维链（Chain-of-Thought, CoT）示例，位置效应可能更为复杂。将一个详细的CoT示例放在开头（ssp），是否能更有效地引导模型学习推理“范式”？反之，将其放在最后（eum），是否可能干扰模型对当前问题的独立思考？ ","date":"2025-08-01","externalUrl":null,"permalink":"/blogs/agent/bicl-prompt-positional-bias/","section":"Blog","summary":"","title":"上下文学习中的“位置效应”","type":"blogs"},{"content":"","date":"2025-07-28","externalUrl":null,"permalink":"/tags/gspo/","section":"Tags","summary":"","title":"GSPO","type":"tags"},{"content":" 本文介绍了组序列策略优化（GSPO），这是我们为训练大语言模型而开发的稳定、高效且性能优异的强化学习算法。与以往采用token级重要性比率的算法不同，GSPO基于序列似然定义重要性比率，并执行序列级裁剪、奖励和优化。我们证明了GSPO相比GRPO算法实现了卓越的训练效率和性能，显著稳定了专家混合（MoE）强化学习训练，并具有简化强化学习基础设施设计的潜力。GSPO的这些优点为最新的Qwen3模型的显著改进做出了贡献。\n1 引言 # 强化学习（RL）已成为扩展语言模型的关键范式（OpenAI, 2024; DeepSeek-AI, 2025; Qwen, 2025b;a）。通过大规模强化学习，语言模型发展出处理复杂问题的能力，如竞赛级数学和编程，通过进行更深入和更长的推理过程。\n为了成功地通过更大的计算投入来扩展强化学习，最重要的先决条件是保持稳定和鲁棒的训练动态。然而，当前最先进的强化学习算法（以GRPO为代表，Shao等，2024）在训练巨大语言模型时表现出严重的稳定性问题，经常导致灾难性且不可逆的模型崩溃（Qwen, 2025a; MiniMax, 2025）。这种不稳定性阻碍了通过持续强化学习训练来突破语言模型能力边界的努力。\n在本文中，我们识别出GRPO的不稳定性源于其算法设计中重要性采样权重的根本误用和失效。这引入了高方差的训练噪声，随着响应长度的增加而逐渐累积，并被裁剪机制进一步放大，最终导致模型崩溃。\n为了解决这些核心局限性，我们提出了组序列策略优化（GSPO），这是一种用于训练大语言模型的新型强化学习算法。GSPO的关键创新在于其基于序列似然的理论基础的重要性比率定义（Zheng等，2023），符合重要性采样的基本原理。此外，GSPO将标准化奖励计算为查询的多个响应的优势，确保序列级奖励和优化之间的一致性。\n我们的实证评估表明GSPO在训练稳定性、效率和性能方面显著优于GRPO。关键的是，GSPO从根本上解决了大型专家混合（MoE）模型强化学习训练中的稳定性挑战，消除了对复杂稳定化策略的需求，并显示出简化强化学习基础设施的潜力。GSPO的这些优点最终为最新Qwen3模型的卓越性能改进做出了贡献。我们设想GSPO作为一个鲁棒且可扩展的算法基础，将使语言模型的大规模强化学习训练得以持续发展。\n2 预备知识 # 符号表示 在本文中，由参数 \\(θ\\) 参数化的自回归语言模型被定义为策略 \\(π_θ\\)。我们使用 \\(x\\) 表示查询， \\(D\\) 表示查询集合。给定查询 \\(x\\) 的响应 \\(y\\)，其在策略 \\(π_θ\\) 下的似然表示为 \\(\\pi_\\theta(y|x) = \\prod_{t=1}^{|y|} \\pi_\\theta(y_t|x, y_{\\lt t})\\)，其中 \\(|y|\\) 表示 \\(y\\) 中的token数量。查询-响应对 \\((x, y)\\) 可以由验证器 \\(r\\) 评分，得到奖励 \\(r(x, y) \\in [0, 1]\\)。\n近端策略优化（PPO） 使用从旧策略 \\(π_{θ_{old}}\\) 生成的样本，PPO（Schulman等，2017）通过裁剪机制将策略更新限制在旧策略的近端区域内。具体来说，PPO采用以下目标进行策略优化（为了简洁起见，我们此后省略KL正则化项，因为它不是本文的重点）：\n其中token \\(y_t\\) 的重要性比率定义为 \\(w_t(θ) = \\frac{π_θ(y_t|x,y_{\\lt t})}{π_{θ_{old}}(y_t|x,y_{\\lt t})}\\)，\\(y_t\\) 的优势 \\(\\hat{A}_t\\) 由另一个价值模型估计，\\(ε\\) 是重要性比率的裁剪范围。\nPPO在实践中的核心挑战在于其严重依赖价值模型。具体来说，价值模型通常与策略模型具有相似的大小，引入了相当大的内存和计算负担。此外，算法有效性取决于其价值估计的可靠性。虽然获得可靠的价值模型本身就具有挑战性，但确保其可扩展到更长的响应和更复杂的任务则面临更大的挑战。\n组相对策略优化（GRPO） GRPO（Shao等，2024）通过计算同一查询的一组响应中每个响应的相对优势来绕过对价值模型的需求。具体来说，GRPO优化以下目标：\n其中 \\(G\\) 是每个查询 \\(x\\) 生成的响应数量（即组大小），token \\(y_{i,t}\\) 的重要性比率 \\(w_{i,t}(θ)\\) 和优势 \\(\\hat{A}_{i,t}\\) 分别为：\n其中 \\(y_i\\) 中的所有token共享相同的优势 \\(\\hat{A}_i\\)。\n3 动机 # 模型规模、稀疏性（例如，在专家混合模型中）和响应长度的增长需要大的rollout批次大小来最大化强化学习期间的硬件利用率。为了提高样本效率，标准做法是将大批量rollout数据分割成多个小批次进行梯度更新。这个过程不可避免地引入了离策略学习设置，其中响应 \\(y\\) 是从旧策略 \\(π_{θ_{old}}\\) 而不是正在优化的当前策略 \\(π_θ\\) 中采样的。这也解释了PPO和GRPO中裁剪机制的必要性，它防止过度\u0026quot;离策略\u0026quot;的样本参与梯度估计。\n虽然裁剪等机制旨在管理这种离策略差异，但我们在GRPO中识别出一个更根本的问题：其目标是病态的。这个问题在训练长响应任务的大模型时变得特别严重，导致灾难性的模型崩溃。GRPO目标的病态性源于重要性采样权重的误用。重要性采样的原理是通过重新加权从行为分布 \\(π_{beh}\\) 中抽取的样本来估计目标分布 \\(π_{tar}\\) 下函数 \\(f\\) 的期望：\n关键的是，这依赖于从行为分布 \\(π_{beh}\\) 中进行多个样本（\\(N ≫ 1\\)）的平均，使得重要性权重 \\(\\frac{π_{tar}(z)}{π_{beh}(z)}\\) 能够有效地纠正分布不匹配。\n相比之下，GRPO在每个token位置 \\(t\\) 应用重要性权重 \\(\\frac{π_θ(y_{i,t}|x,y_{i,\\lt t})}{π_{θ_{old}}(y_{i,t}|x,y_{i,\\lt t})}\\)。由于这个权重基于来自每个下一个token分布 \\(π_{θ_{old}}(·|x, y_{i,\\lt t})\\) 的单个样本 \\(y_{i,t}\\)，它无法执行预期的分布纠正作用。相反，它向训练梯度中引入高方差噪声，这种噪声在长序列上累积并被裁剪机制加剧。我们经验性地观察到，这可能导致通常不可逆的模型崩溃。一旦崩溃发生，即使恢复到先前的检查点并仔细调整超参数（例如，裁剪范围）、扩展生成长度或切换强化学习查询，恢复训练也是徒劳的。\n上述观察表明GRPO设计中的一个根本问题。token级重要性权重的失败指向一个核心原则：优化目标的单位应该与奖励的单位匹配。由于奖励是授予整个序列的，在token级别应用离策略纠正似乎是有问题的。这促使我们放弃token级目标，探索利用重要性权重并直接在序列级别执行优化。\n4 算法 # 4.1 GSPO：组序列策略优化 # 虽然GRPO中的token级重要性权重 \\(\\frac{π_θ(y_{i,t}|x,y_{i,\\lt t})}{π_{θ_{old}}(y_{i,t}|x,y_{i,\\lt t})}\\) 是有问题的，但我们观察到当我们在语言生成的背景下应用重要性采样时：\n序列级重要性权重 \\(\\frac{π_θ(y|x)}{π_{θ_{old}}(y|x)}\\) 具有明确的理论意义：它反映了从 \\(π_{θ_{old}}(·|x)\\) 采样的响应 \\(y\\) 偏离 \\(π_θ(·|x)\\) 的程度，这自然地与序列级奖励对齐，也可以作为裁剪机制的有意义指标。\n基于这个直观的观察，我们提出了组序列策略优化（GSPO）算法。GSPO采用以下序列级优化目标：\n其中我们采用基于组的优势估计：\n并基于序列似然定义重要性比率 \\(s_i(θ)\\)（Zheng等，2023）：\n因此，GSPO对整个响应而不是单个token应用裁剪，以从梯度估计中排除过度\u0026quot;离策略\u0026quot;的样本，这与序列级奖励和优化都匹配。注意我们在 \\(s_i(θ)\\) 中采用长度标准化来减少方差并将 \\(s_i(θ)\\) 控制在统一的数值范围内。否则，少数token的似然变化可能导致序列级重要性比率的剧烈波动，不同长度响应的重要性比率将需要不同的裁剪范围。我们还注意到，由于重要性比率的不同定义，GSPO和以前算法（例如GRPO）中的裁剪范围通常在数量级上有所不同。\n4.2 梯度分析 # 我们可以推导GSPO目标的梯度如下（为了简洁起见省略clipping/裁剪）：\n为了比较，GRPO目标的梯度如下（注意 \\(\\hat{A}_{i,t} = \\hat{A}_i\\)）：\n因此，GSPO和GRPO之间的根本区别在于它们如何加权token对数似然的梯度。在GRPO中，token根据其各自的\u0026quot;重要性权重\u0026quot; \\(\\frac{π_θ(y_{i,t}|x,y_{i,\\lt t})}{π_{θ_{old}}(y_{i,t}|x,y_{i,\\lt t})}\\) 进行加权。然而，这些不等权重可能在 \\((0, 1 + ε]\\)（对于\\(\\hat{A}_i \u003e 0\\)）或 \\([1 - ε, +∞)\\)（对于 \\(\\hat{A}_i \u003c 0\\)）之间变化，它们并非微不足道，随着训练的进行，它们的影响可能累积并导致不可预测的后果。相比之下，GSPO对响应中的所有token进行等权重加权，消除了GRPO的这种不稳定因素。\n4.3 GSPO-token：token级目标变体 # 在多轮强化学习等场景中，我们可能希望在token级别进行更细粒度的优势调整。为此，我们引入了GSPO的token级目标变体，即GSPO-token，以允许token级优势定制：\n其中\n\\(\\text{sg}[·]\\) 表示仅取数值但停止梯度，对应PyTorch中的detach操作。GSPO-token的梯度可以推导为：\n注意项 \\(\\frac{π_θ(y_{i,t}|x,y_{i,\\lt t})}{\\text{sg}[π_θ(y_{i,t}|x,y_{i,\\lt t})]}\\) 的数值为1。因此，比较方程(11)和方程(18)，当我们将响应 \\(y_i\\) 中所有token的优势设置为相同值（即 \\(\\hat{A}_{i,t} = \\hat{A}_i\\) ）时，GSPO-token在优化目标、裁剪条件和理论梯度方面在数值上与GSPO相同，同时享有按token调整优势的更高灵活性。\n5 实验和讨论 # 5.1 实证结果 # 我们使用从Qwen3-30B-A3B-Base微调的冷启动模型进行实验，并报告训练奖励曲线以及在AIME'24（32次采样的Pass@1）、LiveCodeBench（202410-202502，8次采样的Pass@1）和CodeForces（Elo评级）基准上的模型性能曲线。我们以GRPO算法作为基线，我们已经仔细调优（例如，方程2中的两个裁剪范围）以确保公平比较。注意GRPO需要路由重放训练策略才能使MoE强化学习正常收敛，这将在§5.3中额外讨论，而GSPO已经消除了对这种策略的需求。\nFigure1 从Qwen3-30B-A3B-Base微调的冷启动模型的训练曲线。GSPO比GRPO具有显著更高的训练效率 图1显示GSPO的训练始终稳定进行。我们观察到GSPO可以通过增加训练计算、定期更新查询集和扩展生成长度来提供持续的性能改进。此外，GSPO还表现出优于GRPO的训练效率，在相同的训练计算和消耗查询下实现更好的训练准确性和基准性能。最后，我们已成功将GSPO应用于最新Qwen3模型的强化学习训练，有力证明了GSPO在释放大语言模型强化学习扩展能力方面的有效性。\n5.2 关于裁剪分数的有趣观察 # GSPO与GRPO相比的一个关键区别是它裁剪整个响应而不是单个token的做法。特别是，如图2所示，我们观察到GSPO和GRPO之间裁剪token分数有两个数量级的差异（虽然调整裁剪范围不会改变幅度差异）。然而，尽管裁剪了显著更多的token并因此使用更少的token进行训练（或梯度估计），GSPO仍然实现了比GRPO更高的训练效率。这个反直觉的发现——裁剪更大比例的token导致更优的训练效率——进一步表明GRPO的token级梯度估计本质上是嘈杂和低效的样本利用。相比之下，GSPO的序列级方法提供了更可靠和有效的学习信号。\nFigure2 GSPO和GRPO强化学习训练期间裁剪token的平均分数 5.3 GSPO对MoE训练的益处 # 背景 与密集模型的强化学习训练相比，MoE模型的稀疏激活性质引入了独特的稳定性挑战。特别是，我们发现当采用GRPO算法时，MoE模型的专家激活波动性可能阻止强化学习训练正常收敛。具体来说，在一次或多次梯度更新后，为同一响应激活的专家可能发生显著变化。例如，对于48层的Qwen3-30B-A3B-Base模型，在每次强化学习梯度更新后，对于同一rollout样本，在新策略 \\(π_θ\\) 下激活的专家中大约有10%与在旧策略 \\(π_{θ_{old}}\\) 下激活的专家不同。这种现象在更深的MoE模型中变得更加突出，使得token级重要性比率 \\(w_{i,t}(θ) = \\frac{π_θ(y_{i,t}|x,y_{i,\\lt t})}{π_{θ_{old}}(y_{i,t}|x,y_{i,\\lt t})}\\) 剧烈波动并进一步使其失效，如§3和§4.2中所讨论的，从而阻碍强化学习训练的正常收敛。\n我们以前的方法 为了解决这个挑战，我们以前采用了路由重放训练策略。具体来说，我们缓存 \\(π_{θ_{old}}\\) 中激活的专家，并在计算重要性比率 \\(w_{i,t}(θ) = \\frac{π_θ(y_{i,t}|x,y_{i,\\lt t})}{π_{θ_{old}}(y_{i,t}|x,y_{i,\\lt t})}\\) 时在 \\(π_θ\\) 中\u0026quot;重放\u0026quot;这些路由模式。这样，对于每个token \\(y_{i,t}\\)，\\(π_θ(y_{i,t}|x, y_{i,\\lt t})\\) 和 \\(π_{θ_{old}}(y_{i,t}|x, y_{i,\\lt t})\\) 共享相同的激活网络，以便我们可以恢复token级重要性比率的稳定性，并确保在梯度更新间一致激活网络的优化。图3表明路由重放是MoE模型GRPO训练正常收敛的关键技术。\nFigure3 路由重放策略在MoE模型GRPO训练正常收敛中起关键作用 GSPO的益处 虽然路由重放使MoE模型的GRPO训练能够正常收敛，但其重用路由模式的做法产生了额外的内存和通信开销，也可能限制MoE模型的实际容量。相比之下，如图1所示，GSPO消除了对路由重放的依赖，完全能够常规计算重要性比率 \\(s_i(θ)\\)，正常收敛，稳定优化。关键洞察是GSPO只关注序列似然「即 \\(\\pi_\\theta(y_i \\mid x)\\)」，对单个token似然「即 \\(\\pi_\\theta(y_{i,t} \\mid x, y_{i, \\lt t})\\)」不敏感。由于MoE模型始终保持其语言建模能力，序列似然不会剧烈波动。总之，GSPO从根本上解决了MoE模型中的专家激活波动性问题，消除了对路由重放等复杂变通方法的需求。这不仅简化和稳定了训练过程，还允许模型在没有人为约束的情况下利用其全部容量。\n5.4 GSPO对强化学习基础设施的益处 # 鉴于训练引擎（例如Megatron）和推理引擎（例如SGLang和vLLM）之间的精度差异，在实践中，我们通常使用训练引擎重新计算采样响应在旧策略 \\(π_{θ_{old}}\\) 下的似然。然而，GSPO仅使用序列级而不是token级似然进行优化，直观上，前者对精度差异的容忍度要高得多。因此，GSPO使得直接使用推理引擎返回的似然进行优化成为可能，从而避免了使用训练引擎重新计算的需要。这在部分rollout和多轮强化学习等场景以及训练-推理分离框架中特别有益。\n6 结论 # 我们提出了组序列策略优化（GSPO），这是一种用于训练大语言模型的新型强化学习算法。遵循重要性采样的基本原理，GSPO基于序列似然定义重要性比率，并执行序列级裁剪、奖励和优化。GSPO相比GRPO表现出显著优越的训练稳定性、效率和性能，并对MoE模型的大规模强化学习训练表现出特别的有效性，为最新Qwen3更新中的卓越改进奠定了基础。以GSPO作为可扩展的算法基石，我们将继续扩展强化学习，并期待由此产生的智能基础性进步。\n","date":"2025-07-28","externalUrl":null,"permalink":"/blogs/llm/gspo-rl-llm/","section":"Blog","summary":"","title":"GSPO：组序列策略优化","type":"blogs"},{"content":"","date":"2025-07-28","externalUrl":null,"permalink":"/tags/qwen/","section":"Tags","summary":"","title":"Qwen","type":"tags"},{"content":"","date":"2025-07-22","externalUrl":null,"permalink":"/tags/kimi-k2/","section":"Tags","summary":"","title":"Kimi-K2","type":"tags"},{"content":" 代码仓库：MoonshotAI/Kimi-K2\n在大型语言模型（LLM）的浪潮之巅，我们正见证着一场从静态模仿学习到动态“具身智能”（Agentic Intelligence）的深刻范式转移。模型不再仅仅是文本的续写者，更被期望能像一个智能体（Agent）一样，在复杂环境中自主感知、规划、推理并采取行动。Kimi K2，一个拥有1万亿总参数和320亿激活参数的混合专家（MoE）模型，正是为探索这一前沿领域而生。\n这篇技术报告详细阐述了Kimi K2从模型设计、预训练、后训练到评估的全过程。它不仅在多个基准测试中取得了开源模型的领先水平，更在软件工程和智能体任务上展现出与顶尖闭源模型相媲美的强大能力。\n一、 预训练：稳定、高效与架构创新 # Kimi K2的强大能力根植于其扎实的预训练基础。团队在15.5万亿（Trillion）Tokens的高质量数据上进行了训练，并在此过程中引入了多项关键创新，以解决大规模MoE模型训练的核心挑战。\n1.1 MuonClip：驯服万亿模型训练的不稳定性 # 在追求更高“令牌效率”（Token Efficiency，即每个token带来的学习信号）的道路上，团队选择了Muon优化器。然而，实践表明，随着模型规模的扩大，Muon相比AdamW更容易出现注意力logit爆炸导致的训练不稳定问题。为此，Kimi团队提出了一个新颖的权重裁剪机制——QK-Clip，并将其与Muon结合，形成了全新的MuonClip优化器。\nQK-Clip的核心思想：当注意力logit超过预设阈值时，通过缩放查询（Query）和键（Key）的投影权重来直接约束其增长，而非粗暴地裁剪logit本身。\n让我们回顾一下注意力机制的计算过程。对于每个注意力头 $h$，其Q, K, V投影计算如下：\n$$ Q^h = XW_q^h, \\quad K^h = XW_k^h, \\quad V^h = XW_v^h $$注意力输出为：\n$$ O^h = \\text{softmax}\\left(\\frac{1}{\\sqrt{d}}Q^h{K^h}^T\\right)V^h $$QK-Clip关注的是softmax的输入，即注意力logit。团队定义了一个逐头的最大logit值 \\(S_{\\text{max}}^h\\)：\n$$ S_{\\text{max}}^h = \\frac{1}{\\sqrt{d}} \\max_{X \\in B} \\max_{i,j} Q_i^h{K_j^h}^T $$当 \\(S_{\\text{max}}^h\\) 超过阈值 \\(\\tau\\) 时，QK-Clip会计算一个缩放因子 \\(\\gamma_h = \\min(1, \\tau/S_{\\text{max}}^h)\\)，并用它来缩放Q和K的投影权重。这一操作在当前步骤的前向和后向传播之后进行，仅作为指导信号，不影响当前梯度。具体到Kimi K2使用的多头潜在注意力（MLA），裁剪被精细地应用于非共享的组件上。\n如 图2 所示，使用MuonClip后，Kimi K2在整个训练过程中最大注意力logit被有效控制在100以内，最终训练损失曲线平滑稳定，无任何损失尖峰（图3）。这证明了MuonClip在万亿参数模型上实现了可扩展且稳健的训练。\n1.2 数据增强：用“复述”提升令牌效用 # 高质量的人类数据日益稀缺，如何最大化利用现有数据成为关键。Kimi K2引入了一套精巧的“复述”（Rephrasing）流程，对高质量的知识和数学语料进行数据增强，以在不引入过拟合风险的前提下提高令牌的利用率。\n该流程包含三个核心部分：\n风格与视角多样化提示：使用精心设计的Prompt，引导LLM以不同的风格和视角重写原文，同时保持事实的准确性。 分块自回归生成：为处理长文档，系统将文本分块，逐块重写，再拼接回完整的文章，有效规避了LLM的输出长度限制。 忠实度验证：通过比较重写内容与原文的语义一致性，确保增强后数据的质量。 实验结果（表1）表明，相比于简单地重复数据10个epoch，使用10次不同复述的版本进行单次训练，在SimpleQA上的准确率有显著提升，证明了该策略的有效性。\n1.3 模型架构：追求更优的稀疏性与推理效率 # Kimi K2是一个拥有1.04万亿总参数和320亿激活参数的MoE模型。其架构设计基于深入的缩放定律（Scaling Law）研究，在DeepSeek-V3的基础上做出了关键调整（表2）：\n更高的稀疏度：团队的“稀疏性缩放定律”研究（图5）表明，在激活参数（即FLOPs）固定的情况下，增加总专家数量能持续降低模型损失。因此，Kimi K2将总专家数从256个增加到384个，稀疏度达到48（384/8），在性能和成本间取得了更好的平衡。 更少的注意力头：为了优化长上下文场景下的推理效率，Kimi K2将注意力头的数量从128个减少到64个。研究（图6）发现，虽然增加注意力头能带来微小的性能提升（0.5%-1.2%），但这会显著增加长序列推理的FLOPs（在128k长度下增加83%）。因此，减少注意力头是一个更具性价比的选择。 二、 深入Kimi K2的训练流程与工程实践 # 训练一个万亿参数模型不仅是算法的胜利，更是系统工程的杰作。Kimi K2的成功离不开其高度优化的训练基础设施和流程设计。\n2.1 硬件基础与并行策略 # Kimi K2的训练依托于一个强大的计算集群，该集群配备了NVIDIA H800 GPU。每个节点拥有2TB内存和8张通过NVLink及NVSwitch高速互联的H800 GPU，而节点间则通过8 x 400 Gbps的RoCE网络进行通信。\n为了驾驭如此庞大的模型，团队设计了一套灵活且高效的并行策略，结合了：\n流水线并行（PP）：将模型的不同层切分到不同的GPU上。 专家并行（EP）：将MoE层的不同专家（Experts）分布到不同的GPU上，Kimi K2采用了16路专家并行。 数据并行（DP）：使用ZeRO-1数据并行，只对梯度进行分区，以减少显存冗余。 这种组合策略非常灵活，使得Kimi K2可以在任意32的倍数个节点上进行训练，无论是小规模实验还是大规模训练都能复用同一套并行配置，极大地提升了研究迭代效率。\n2.2 极致的优化：通信重叠与激活管理 # 在万亿模型训练中，每一寸显存和每一次通信都至关重要。Kimi K2采用了多种技术来榨干硬件性能：\n通信与计算重叠：通过标准的interleaved 1F1B流水线调度，团队成功地将专家并行所需的All-to-All通信与计算过程重叠起来。如图7所示，通过精心设计的调度，流水线并行和专家并行的通信开销被最大程度地隐藏在计算时间之内，从而提高了GPU的有效利用率。 激活值管理：即便经过优化，完整的激活值也无法完全放入GPU显存。为此，团队采用了三管齐下的策略： 选择性重计算：对计算开销小但显存占用大的操作（如LayerNorm、SwiGLU）进行重计算，用计算换空间。 FP8存储：将MoE层和SwiGLU层的输入激活值以FP8-E4M3的格式进行压缩存储，同时保证计算过程仍使用高精度，以避免性能损失。 CPU激活卸载（Offload）：将剩余的激活值流式传输到CPU内存中，并在需要时再加载回GPU。 2.3 预训练配方：15.5T Tokens的征途 # Kimi K2的预训练配方（Recipe）经过了精心设计：\n总计处理：15.5万亿（Trillion）Tokens。 优化器：MuonClip (见算法1)。 学习率调度：采用WSD学习率调度。在前10T个tokens，学习率经过500步的预热后保持在2e-4；在后续的5.5T个tokens，学习率通过余弦衰减降至2e-5。 其他参数：权重衰减（Weight Decay）为0.1，全局批大小（Global Batch Size）为6700万个tokens。 长文本适应：在预训练末期，模型先用4k序列长度的数据进行了4000亿tokens的退火（annealing）训练，随后又用32k序列长度的数据训练了600亿tokens，并通过YaRN方法将上下文能力扩展至128k。 2.4 为RL而生的分布式设施 # 强化学习（RL）的训练，尤其是涉及与环境交互的智能体训练，对基础设施提出了更高的要求。\n高效引擎切换：RL需要在推理引擎（生成数据）和训练引擎（模型更新）之间频繁切换。对于K2这样的巨型模型，参数的重分片和广播是巨大的挑战。团队为此设计了一个分布式检查点引擎（checkpoint engine）（图10），它与训练节点共存，负责高效地将更新后的参数广播给所有推理节点。这一设计使得K2的全量参数更新能在30秒内完成，对于RL的迭代来说几乎是可忽略的开销。 智能体式部署（Agentic Rollout）：为了处理长耗时、与外部环境交互的智能体任务，系统采用了**部分部署（partial rollout）**技术，允许未完成的长任务被暂停并在下一轮RL迭代中继续，同时通过大量并发部署来摊平单个任务的等待延迟。 三、 后训练：迈向真正的具身智能 # 预训练赋予了模型强大的基础能力，而后训练则负责将其塑造为能够执行复杂任务的智能体。Kimi K2的后训练流程包含大规模的智能体数据合成和统一的强化学习框架。\n3.1 大规模智能体数据合成：模拟真实世界的工具使用 # 为了教会模型如何使用工具，Kimi K2构建了一个大规模、自动化的数据合成流水线（图8），模拟智能体在真实世界中使用工具的场景。该流程分为三步：\n工具、智能体与任务生成：\n工具库构建：结合了3000多个真实的MCP（Model Context Protocol）工具和通过领域演化生成的20000多个合成工具，确保了工具集的多样性和覆盖面（图9）。 智能体多样化：通过生成不同的系统提示和工具组合，创建出数千个能力、专长各异的智能体。 基于准则的任务生成：为每个智能体生成从简单到复杂的任务，并附带明确的成功标准（Rubric），用于后续的客观评估。 多轮轨迹生成：\n模拟用户与环境：通过LLM模拟具有不同风格的用户，并构建一个复杂的工具模拟器（ functionally equivalent to a world model）。这个模拟器能执行工具调用、维护状态，并引入随机性，以产生成功的、部分失败的和完全失败的各种真实轨迹。 质量评估与筛选：一个LLM裁判根据预设的Rubric评估每条轨迹，只有满足成功标准的轨迹才会被保留用于训练。 这种结合了可扩展模拟与真实执行环境（用于编码等任务）的混合方法，为Kimi K2提供了大量高质量、多样化且经得起验证的工具使用范例。\n3.2 统一强化学习（RL）框架：从可验证到自省思 # Kimi K2的RL框架旨在提升模型的综合能力，它不仅能处理有明确对错的任务，还能在主观性强的领域进行自我优化。\n3.2.1 可验证奖励RL (RLVR) # 对于数学、逻辑、代码等具有可验证答案的领域，Kimi K2在一个类似Gym的框架中进行RL训练。数据经过精心筛选，确保覆盖广泛且难度适中，从而最大化学习效率。这部分训练让模型在客观任务上的能力得到“硬”提升。\n3.2.2 超越验证：自省思准则奖励 (Self-Critique Rubric Reward) # 对于帮助性、创造性等主观任务，模型需要理解并对齐更细致的人类偏好。Kimi K2在此引入了“自省思准则奖励”机制：\nK2作为裁判：首先，K2作为一个演员（actor）生成多个回答。然后，K2切换到裁判（critic）角色，根据一套核心价值观和任务特定准则（附录F），对自己的输出进行成对比较和打分，从而产生偏好信号。 闭环裁判优化：在RL训练过程中，裁判模型会不断从RLVR任务的“客观”反馈中学习，将其性能信号蒸馏到自己的评估模型中。这使得裁判的主观判断始终植根于可验证的数据，并与演员的进化保持同步。 3.2.3 RL算法的精进 # Kimi K2采用了Kimi K1.5中引入的策略优化算法，其目标函数如下：\n$$ L_{RL}(\\theta) = \\mathbb{E}{x \\sim D} \\left[ \\frac{1}{K} \\sum_{i=1}^K \\left( r(x, y_i) - \\bar{r}(x) - \\tau \\log \\frac{\\pi_\\theta(y_i|x)}{\\pi_{\\text{old}}(y_i|x)} \\right)^2 \\right] $$为应对更大规模的RL挑战，团队还引入了三项改进：\n预算控制：对不同任务设置最大生成长度，并对超长回答进行惩罚，以提升模型的令牌效率。 PTX损失：在RL中引入高质量的SFT数据作为辅助损失，防止模型遗忘已学到的重要能力。 温度衰减：在训练初期使用高采样温度鼓励探索，后期则降低温度以促进收敛到高质量的稳定输出。 四、 性能评估：开源模型的全新标杆 # Kimi K2在广泛的基准测试中展现了卓越的性能，特别是在其设计的核心领域——智能体与编程能力上。\n4.1 Kimi-K2-Instruct 评估 # 智能体与竞技编程（图1）：在SWE-bench Verified上，Kimi K2取得了65.8%的成绩，大幅超越其他开源模型，并显著缩小了与Claude 4 Opus（72.5%）的差距。在LiveCodeBench v6和OJBench等竞技编程榜单上，它同样位居榜首。 工具使用（图1）：在多轮工具使用基准Tau2-bench和ACEBench上，Kimi K2分别取得了66.1和76.5的高分，再次证明其在受控的、智能体驱动的工具编排方面的强大实力。 数学与STEM（图1）：在AIME 2025上获得49.5分，在GPQA-Diamond上获得75.1分，展现了其在复杂推理任务上的顶级水平。 通用与长文本能力：在MMLU上达到89.5%，在需要长上下文的DROP任务上达到93.5%，全面超越了其他开源模型。 开放式评估：在LMSYS Arena盲测中，Kimi K2成为排名第一的开源模型，并在总榜上名列第五（截至2025年7月17日），这直接反映了其在真实用户场景中的高质量体验。 4.2 Kimi-K2-Base 评估 # 基础模型同样表现出色（表4），在MMLU、MMLU-Pro、SimpleQA等12个英语通用语言基准中的10个上取得了SOTA性能。在代码（CRUXEval 74.00%）和数学（MATH 70.22%）等领域也树立了新的开源基准。\n结论 # Kimi K2不仅是一个参数量巨大的模型，更是对“开放的具身智能”的一次系统性探索。通过创新的MuonClip优化器，它实现了万亿参数模型的稳定高效预训练；通过大规模的智能体数据合成与统一的强化学习框架，它将强大的基础能力转化为可落地、可交互的智能体技能。Kimi K2的开源，无疑将为社区在软件工程、具身智能等前沿方向的研究与应用提供一个极其强大的新起点。\n","date":"2025-07-22","externalUrl":null,"permalink":"/blogs/llm/kimi-k2-technical-report/","section":"Blog","summary":"","title":"kimi-k2技术报告","type":"blogs"},{"content":"","date":"2025-07-22","externalUrl":null,"permalink":"/tags/moonshot/","section":"Tags","summary":"","title":"Moonshot","type":"tags"},{"content":"","date":"2025-07-20","externalUrl":null,"permalink":"/tags/manus/","section":"Tags","summary":"","title":"Manus","type":"tags"},{"content":" TL;DR # 不训练端到端模型，完全依赖前沿大模型的上下文学习能力（in-context learning），实现小时级迭代、与底层模型解耦。 六大原则速览 # # 原则 原文标题 目的 关键做法（原文摘录） 1 把 KV-Cache 当生命线 Design Around the KV-Cache 延迟↓ 成本↓ • 提示前缀字节级稳定 • 上下文仅追加、序列化确定 • 显式 cache breakpoint 2 掩码优于增删 Mask, Don’t Remove 稳定工具空间 \u0026amp; 缓存不失效 • 用 logits mask 屏蔽/强制工具 • 工具定义常驻上下文 3 文件系统即无限上下文 Use the File System as Context 解决超长输入 \u0026amp; 可恢复压缩 • 内容可裁剪，URL/路径留档 • 代理自主读写文件 4 用复述操控注意力 Manipulate Attention Through Recitation 防目标漂移 • 维护 todo.md 并持续追加到最新上下文 5 保留失败轨迹 Keep the Wrong Stuff In 让模型自我纠错 • 失败动作与异常栈全部保留 6 拒绝同质化 Few-shot Don’t Get Few-Shotted 防行为僵化 • 模板、措辞、顺序引入可控噪声 原文提炼 # “KV-cache hit rate is the single most important metric.” “Mask, don’t remove.” “File system = unlimited, persistent, operable memory.” “Recitation biases attention without architectural changes.” “Erasing failure removes evidence.” “Don’t few-shot yourself into a rut.” 如何塑造上下文，决定了 Agent 能多快、多稳、走多远——Context is the Agent. 详细分析 # Context Engineering for AI Agents: Lessons from Building Manus\n这篇文章分享了 Manus 团队在构建 AI Agent 过程中的核心经验，他们将其总结为“上下文工程”（Context Engineering）。其核心论点是，在当前技术阶段，与其投入巨大成本对模型进行端到端的训练，不如基于前沿大模型的上下文学习能力（in-context learning）进行构建，这能带来更快的迭代速度和产品灵活性。 Manus 将其探索过程戏称为“随机研究生下降”（Stochastic Graduate Descent），即一个通过架构搜索、提示词调整和经验猜测等手动过程不断寻找局部最优解的实践科学。\n以下是 Manus 从实践中总结出的六大上下文工程核心原则的深度解读。\n1. 围绕 KV-Cache 进行设计（Design Around the KV-Cache） # 核心原则：KV-Cache 命中率是衡量生产级 AI Agent 最重要的单一指标，直接影响延迟和成本。\n原理解读： Agent 的工作模式是“思考-行动”循环。在每一轮迭代中，模型根据当前上下文选择一个动作，执行后产生观察结果，然后将“动作”和“观察结果”都附加到上下文中，为下一步决策做准备。 这导致 Agent 的上下文会随着任务步骤的增加而变得非常长，而其输出（通常是结构化的函数调用）则相对较短。 Manus 提到，其输入与输出的平均 Token 比例高达 100:1。\nKV-Cache 是一种缓存技术，对于拥有相同前缀（prefix）的上下文，它可以极大地降低首次生成 Token 的时间（TTFT）和推理成本。 文本提到，使用 Claude Sonnet 模型时，命中缓存的 Token 成本仅为未命中时的十分之一。 因此，最大化 KV-Cache 命中率对于优化性能和控制成本至关重要。\n实践方法：\n保持提示词前缀稳定：任何微小的改动（如精确到秒的时间戳）都会导致缓存失效。 上下文采用仅追加模式 (Append-only)：避免修改历史动作或观察，并确保序列化（如 JSON）的顺序是确定的，以防静默地破坏缓存。 必要时显式标记缓存断点：在一些推理框架下，需要手动指定缓存结束的位置，至少要包含系统提示词的末尾。 深度解读： 这个原则揭示了 Agent 工程的经济学本质。它不再仅仅是追求功能的实现，而是必须在性能和成本之间做出精妙的权衡。将 KV-Cache 命中率提升到战略高度，意味着团队必须从底层设计上就贯彻“确定性”和“可复用性”的思想。这迫使开发者将 Agent 的“记忆”或“上下文”不仅仅看作一个动态增长的对话历史，而是一个需要被精心管理、以实现最高计算效率的“资产”。这种思维方式是从学术研究走向生产环境的关键一步。\n2. 使用掩码，而非移除（Mask, Don\u0026rsquo;t Remove） # 核心原则：当 Agent 的工具集（Action Space）变得庞大时，不要在迭代中动态增删工具定义，而应通过掩码（Masking）在解码阶段限制模型的选择。\n原理解读： 动态地从上下文中增删工具定义会带来两个严重问题：\n工具定义通常位于上下文的靠前位置，任何修改都会使得后续所有内容的 KV-Cache 失效，成本高昂。 如果历史记录中引用了已被移除的工具，模型会感到困惑，可能导致输出格式错误或产生幻觉。 实践方法： Manus 使用一个上下文感知的状态机来管理工具的可用性。它并不移除工具，而是在模型生成下一个 Token 时，通过“掩码”技术在解码层面直接屏蔽或强制选择某些工具。 他们利用了模型推理服务提供的不同函数调用模式（如自动、必须调用、指定调用），并通过预设函数名称前缀（如 browser_ 或 shell_）来简化按组别屏蔽或启用的操作。\n深度解读： 这一原则体现了在与“黑箱”模型（LLM）协作时的一种巧妙思路。我们无法直接修改模型的内部逻辑，但可以通过塑造其“视野”来引导其行为。使用掩码而非移除，是一种“软控制”而非“硬删除”的策略。它在不破坏上下文连贯性和缓存效率的前提下，实现了对 Agent 在特定状态下行为的精确约束。这好比是给 Agent 配备了齐全的工具箱，但根据当前工序，只高亮显示它应该使用的工具。这是一种既能保持系统稳定性，又能实现动态逻辑控制的优雅方案。\n3. 将文件系统作为上下文（Use the File System as Context） # 核心原则：面对超长上下文窗口的限制、性能衰减和高成本问题，应将文件系统视为最终的、无限大小的持久化上下文。\n原理解读： 现代模型虽然支持数十万 Token 的上下文，但在实际 Agent 场景中仍然捉襟见肘，因为网页、PDF 等非结构化数据的观察结果可能非常巨大。 同时，过长的上下文不仅成本高昂，还可能导致模型性能下降（即“迷失在中间”问题）。 激进的压缩策略则必然导致信息丢失，因为无法预知哪一段历史信息会在未来变得至关重要。\n实践方法： Manus 的核心思想是：压缩必须是可恢复的。例如，网页内容可以从上下文中丢弃，只要保留其 URL；文档内容可以省略，只要其文件路径在沙箱中可用。 模型被训练成能够按需读写文件，将文件系统不仅用作存储，更用作一种结构化的外部记忆体。\n深度解读： 这是对“上下文”概念的一次延伸，从模型的内部记忆（in-context memory）扩展到了外部环境记忆（externalized memory）。它借鉴了早期“神经图灵机”的思想，即计算单元可以与外部存储交互。通过让 Agent 学会使用文件系统，Manus 将上下文的瓶颈从 LLM 的窗口大小转移到了几乎无限的硬盘空间。这不仅解决了信息容量问题，更重要的是，它促使 Agent 学习一种更高级的智能行为：信息管理。Agent 必须自己决定何时将信息“卸载”到文件，何时再将其“加载”回注意力中，这是一种更接近人类工作方式的智能。\n4. 通过“复述”来操控注意力（Manipulate Attention Through Recitation） # 核心原则：通过让 Agent 在执行任务时反复读写一个“任务清单”（如 todo.md 文件），将全局目标和计划“复述”到上下文的末尾，从而强化模型的注意力。\n原理解读： 对于需要几十步才能完成的复杂任务，Agent 很容易偏离最初的目标，尤其是在长程依赖和复杂干扰下。 这种现象被称为“目标漂移”。\n实践方法： Manus 设计了一种行为模式，让 Agent 在处理复杂任务时，创建一个 todo.md 文件，并随着任务的进展不断更新它（例如勾掉已完成项）。 这个行为看似简单，但其本质是将任务规划反复写入上下文的最新部分。由于 Transformer 模型的注意力机制对最近的 Token 更敏感，这种“复述”行为相当于在不断提醒模型：“别忘了，我们现在要做的总体目标是这个”。\n深度解读： 这是一种非常精妙的“心理学技巧”，完全基于对 Transformer 注意力机制的深刻理解。它没有修改任何底层模型架构，而是通过设计一种特定的行为模式，利用模型的自身特性来克服其弱点。这说明，优秀的 Agent 设计不仅是技术问题，也是一种“行为设计”。通过让 Agent “自言自语”或“写任务清单”，Manus 创造了一种内部反馈循环，有效地对抗了长序列任务中的注意力衰减和目标遗忘问题。\n5. 保留失败记录（Keep the Wrong Stuff In） # 核心原则：不要隐藏或清理 Agent 的失败尝试。将错误动作和其产生的异常信息（如堆栈跟踪）保留在上下文中，是提升 Agent 鲁棒性的最有效方法之一。\n原理解读： 一个常见的诱惑是当 Agent 犯错时，试图通过重试或重置状态来“粉饰太平”，让执行记录看起来干净。 但这样做抹去了宝贵的学习证据。 模型如果看不到失败的案例，就无法学会如何避免和修正错误。\n实践方法： 当模型看到一个失败的动作和由此产生的错误观察时，它会隐式地更新其内部信念，从而降低在后续步骤中重复同样错误的可能性。 Manus 认为，错误恢复能力是衡量真正智能行为的关键指标，但在学术界和公开基准测试中，这一点往往被忽视。\n深度解读： 这个原则挑战了传统软件工程中“优雅处理异常”的观念。在 Agent 工程中，“异常”不是需要被隐藏的“程序 bug”，而是需要被学习的“宝贵经验”。保留失败记录，本质上是在上下文中为模型提供负样本（negative examples）。这使得 Agent 的学习过程更接近现实世界——试错是常态，从失败中学习是进步的关键。一个真正强大的 Agent 不在于从不犯错，而在于犯错后能从中吸取教训并自我修正。\n6. 避免陷入“少样本”陷阱（Don\u0026rsquo;t Get Few-Shotted） # 核心原则：在上下文中，过于相似和重复的成功案例（Few-shot examples）会固化模型的行为，使其在面对需要变化的场景时变得脆弱。\n原理解读： 语言模型是出色的模仿者。如果上下文中充满了大量重复的“动作-观察”对，模型会倾向于机械地模仿这种模式，即使情况已经变化，不再适用。 文章举例说，在处理 20 份相似的简历时，Agent 很容易陷入一种“节奏”，仅仅因为历史记录里都是这么做的，从而导致行为僵化、过度泛化甚至幻觉。\n实践方法： Manus 的解决方案是在动作和观察的序列化中引入少量、结构化的变体。 这可能包括使用不同的序列化模板、改变措辞、或在顺序和格式上引入微小的“噪音”。 这种受控的随机性有助于打破模式，调整模型的注意力，防止其陷入思维定式。\n深度解读： 这揭示了 Few-shot Prompting 在 Agent 场景下的一个悖论。虽然提供范例能引导模型，但提供过多高度同质化的范例则会扼杀其灵活性。Agent 需要的不是刻板的模仿，而是在理解模式的基础上进行适应和推理。通过主动在上下文中引入“多样性”，Manus 相当于在训练过程中加入了“扰动”（perturbation），这迫使模型不能仅仅依赖表层模式匹配，而是要去理解更深层的任务逻辑。这确保了 Agent 在面对长序列重复性任务时，依然能保持“清醒”和适应性。\n总结 # Manus 的分享为 AI Agent 的落地实践提供了极其宝贵的路线图。其核心思想是，当前阶段的 Agent 开发者更应该成为一名“上下文工程师”而非“模型训练师”。 通过精心设计 Agent 与其上下文（记忆、环境、反馈）的交互方式，可以在不改变底层模型的情况下，极大地提升 Agent 的性能、稳定性和扩展性。 这些原则——无论是围绕 KV-Cache 的成本优化，还是通过“复述”和“保留失败”来操控模型行为——都体现了一种戴着镣铐跳舞的智慧，是通往更智能、更可靠的 Agent 系统必经的实践之路。\n","date":"2025-07-20","externalUrl":null,"permalink":"/blogs/agent/context-engineering-for-ai-agents-lessons-from-building-manus/","section":"Blog","summary":"","title":"Manus：Agent的上下文工程经验总结","type":"blogs"},{"content":" 代码：Meirtz/Awesome-Context-Engineering\n论文：A Survey of Context Engineering for Large Language Models\n随着LLM的能力日益强大，我们早已不满足于简单的问答。我们希望构建更复杂、更智能、更可靠的AI系统。这就要求我们必须超越传统的“提示工程（Prompt Engineering）”，进入一个更系统化、更科学的全新范式。最近，一篇名为《A Survey of Context Engineering for Large Language Models》的综述论文为我们系统地梳理了这一领域，今天，我将基于这篇论文，为大家详细解读上下文工程的核心思想与技术蓝图。\n从“炼丹”到“科学”：为什么我们需要上下文工程？ # 我们都熟悉“提示工程”，它在过去几年里极大地推动了LLM的应用，但它更像一门“艺术”或“炼丹术”，依赖于经验和反复试错。然而，现代AI系统（如AI Agent）的上下文（Context）早已不是一个简单的静态文本字符串，而是一个动态的、结构化的、多方面的信息流。为了系统地设计、管理和优化这些复杂的信息载荷，我们需要一门更严谨的学科——上下文工程。\n上下文工程的正式定义:\n论文为我们提供了一个严谨的数学框架来理解上下文工程。首先，我们知道自回归LLM的原理是最大化给定上下文 \\(C\\) 后，生成输出序列 \\(Y = (y_1, ..., y_T)\\) 的条件概率：\n$$ P_{\\theta}(Y|C) = \\prod_{t=1}^{T} P_{\\theta}(y_t|y_{\\lt t}, C) $$在传统的提示工程中，\\(C\\) 仅仅是一个静态的提示词（C = prompt）。\n而上下文工程则将 \\(C\\) 重新定义为一个由多个信息组件 \\((c_1, c_2, ..., c_n)\\) 动态编排、组装而成的结构化集合。这个组装过程由一个高级函数 \\(A\\) 完成：\n$$ C = A(c_1, c_2, ..., c_n) $$这些信息组件 \\(c_i\\) 并非随意组合，它们对应了现代AI系统所需信息的各个维度：\n\\(c_{instr}\\): 系统指令和规则。 \\(c_{know}\\): 通过RAG等方式检索到的外部知识。 \\(c_{tools}\\): 可用外部工具（API）的定义和签名。 \\(c_{mem}\\): 来自先前交互的持久化信息（记忆）。 \\(c_{state}\\): 用户、世界或多智能体系统的动态状态。 \\(c_{query}\\): 用户的当前直接请求。 基于此，上下文工程的核心目标就变成了一个形式化的优化问题：寻找一套最优的上下文生成函数 \\(F = \\{A, \\text{Retrieve}, \\text{Select}, ...\\}\\)，使得在任务分布 \\(T\\) 下，LLM输出质量的期望值最大化。\n$$ F^* = \\arg\\max_F \\mathbb{E}_{\\tau \\sim T} [\\text{Reward}(P_\\theta(Y|C_F(\\tau)), Y^*_\\tau)] $$其中，\\(C_F(\\tau)\\) 是由函数集 \\(F\\) 为特定任务 \\(\\tau\\) 生成的上下文，\\(Y^*_\\tau\\) 是理想的输出。这个优化过程还必须遵守模型的最大上下文长度限制 \\(|C| \\le L_{max}\\)。\n为了更清晰地展示两者的区别，论文总结了一张对比表（见表1）：\n表1：提示工程与上下文工程的范式对比\n维度 提示工程 上下文工程 模型 \\(C = \\text{prompt}\\) (静态字符串) \\(C = A(c_1, c_2, ..., c_n)\\) (动态、结构化组装) 目标 \\(\\arg\\max_{\\text{prompt}} P_\\theta(Y\\text{prompt})\\) (\\arg\\max_F \\mathbb{E}{\\tau \\sim T} [\\text{Reward}(P\\theta(Y 复杂度 手动或自动搜索字符串空间 对函数集 \\(F\\) 进行系统级优化 信息 信息内容在提示中是固定的 在约束下最大化任务相关信息 状态 主要是无状态的 内在地、显式地管理状态 （\\(c_{mem}\\), \\(c_{state}\\)） 可扩展性 随着长度和复杂性增加，变得脆弱 通过模块化组合管理复杂性 错误分析 手动检查和迭代修正 对单个上下文函数进行系统化评估和调试 总而言之，上下文工程将我们从“提示词的设计艺术”解放出来，引导我们进入“信息物流与系统优化的科学”领域。\n上下文工程的技术蓝图：一个系统的分类法 # 这篇综述论文最大的贡献之一，是提供了一个清晰、全面的技术分类框架（见图1），将上下文工程分解为三大基础组件 (Foundational Components) 和 四大系统实现 (System Implementations)。\nFigure1 上下文工程分类法总览 三大基础组件：构建上下文的基石 # 基础组件是上下文工程的“原子操作”，它们构成了处理信息的完整流水线。\n1. 上下文检索与生成 (Context Retrieval and Generation) 这是上下文的源头。它负责从各种来源获取原始信息。\n提示工程与上下文生成：这部分是传统提示工程的超集，包括了如思维链（Chain-of-Thought, CoT）、思想树（Tree-of-Thoughts, ToT）等技术，用于生成结构化的推理路径。 外部知识检索：主要指以检索增强生成（RAG）为代表的技术，从外部知识库（如文档、数据库、知识图谱）中获取信息，以缓解模型的知识过时和幻觉问题。 动态上下文组装：这是一个编排步骤，负责将来自不同来源的信息（用户输入、工具、记忆等）整合成一个连贯、优化的上下文，送给LLM处理。 2. 上下文处理 (Context Processing) 获取原始信息后，需要对其进行转换和优化，以最大化其效用。\n长上下文处理：为了突破LLM的上下文窗口限制，研究者们开发了多种技术，如高效注意力机制（FlashAttention）、架构创新（Mamba）和位置编码插值等。 上下文自我优化与适应：让LLM具备自我反思和修正的能力。例如，Self-Refine机制允许模型通过迭代反馈来逐步完善输出。 多模态及结构化上下文：处理超越纯文本的信息，如图像、音频、视频，以及知识图谱、表格等结构化数据。 3. 上下文管理 (Context Management) 这是关于如何高效地组织、存储和利用上下文信息。\n基本约束：处理有限上下文窗口带来的“中间遗忘”（Lost-in-the-Middle）等现象。 记忆层次与存储架构：借鉴操作系统的思想，设计类似虚拟内存的机制（如MemGPT），在快速的上下文窗口（主存）和慢速的外部存储之间进行信息交换，从而实现长期记忆。 上下文压缩：通过技术手段在不损失关键信息的前提下，减少上下文占用的Token数量，从而提升效率。 四大系统实现：从理论到实践 # 当我们将上述基础组件有机地结合起来，便构成了面向应用的复杂AI系统。\n1. 检索增强生成系统 (Retrieval-Augmented Generation, RAG) 这是目前最主流的应用之一。现代RAG系统已经超越了简单的“检索-生成”模式，发展出了模块化RAG（可灵活插拔替换不同组件）、Agentic RAG（由AI Agent自主决定何时、如何检索）和图增强RAG（利用知识图谱进行更精准、多跳的推理）。\n2. 记忆系统 (Memory Systems) 为了让AI Agent能够进行长期、有状态的交互，记忆系统至关重要。它通过实现短期工作记忆和长期持久化记忆的协同，让模型能够从过去的经验中学习和适应，表现得更像一个持续存在的智能体，而非一次性的问答机器。\n3. 工具集成推理 (Tool-Integrated Reasoning) 这使得LLM能够超越其内置知识的限制，通过调用外部API（如搜索引擎、计算器、代码解释器）来与真实世界互动、获取最新信息并执行复杂任务。这从根本上将LLM从一个“文本生成器”转变为一个“世界交互器”。\n4. 多智能体系统 (Multi-Agent Systems) 这是上下文工程复杂性的顶峰。它涉及多个独立的AI Agent通过复杂的通信协议、编排机制和协调策略进行协作，共同完成单个Agent无法解决的宏大任务。这要求在整个系统中对每个Agent的上下文进行精妙的管理和同步。\n核心挑战与未来展望 # 尽管上下文工程取得了巨大进展，但这篇论文也敏锐地指出了该领域面临的一个根本性不对称（fundamental asymmetry）：\n当前的LLM在先进的上下文工程技术加持下，表现出卓越的理解复杂上下文的能力，但在生成同等复杂的长篇输出方面却存在明显的局限性。\n简单来说，模型可以“读懂”一本复杂的书，但很难“写出”一本同样复杂的书。解决这种“理解-生成”能力之间的鸿沟，是未来研究的重中之重。\n展望未来，上下文工程的发展将聚焦于以下几个方向：\n构建统一的理论基础：为上下文的效率、组合和优化建立数学模型。 探索下一代模型架构：如状态空间模型（Mamba）等，以更高效地处理超长序列。 发展智能上下文组装：让AI能够自动学习如何为特定任务构建最优上下文。 完善评估体系：开发能够衡量系统动态、多组件行为的“活”基准测试（living benchmarks）。 结语 # 上下文工程标志着AI领域从经验主义的“手工艺时代”迈向了系统科学的“工业化时代”。它为我们提供了一幅清晰的路线图，指导我们如何构建更强大、更可靠的下一代AI系统。\n正如这篇综述所总结的，AI系统的性能从根本上取决于其接收到的上下文信息。理解、掌握并系统化地应用上下文工程，将是每一位AI研究者和工程师在未来取得成功的关键。\n","date":"2025-07-19","externalUrl":null,"permalink":"/blogs/agent/context-engineering-survey/","section":"Blog","summary":"","title":"Context Engineering综述解读","type":"blogs"},{"content":" EasonWong0327/Hybrid-RAG-System 1. 引言 # 在通用领域，RAG（检索增强生成）系统的核心指标通常是“流畅度”和“相关性”。然而，在医疗AI领域，核心指标必须是 “准确性” 和 “安全性”。一个流畅但错误的医疗建议（幻觉）可能导致严重的后果。\nHybrid-RAG-System 是一个企业级医疗问答解决方案。根据对源码的阅读，其架构本质并非单纯的“Chatbot”，而是一套由 “规则引擎 + 统计模型 + 大语言模型” 组成的混合决策流水线。其核心价值在于实现了一套完整的 可信AI (Trustworthy AI) 闭环，解决了医疗场景中最为棘手的“幻觉”与“知识冲突”问题。\n2. 系统架构概览 # 该系统基于 FastAPI 构建服务层，底层融合了 ElasticSearch（精确检索）和 Faiss（语义检索），核心智力由 Qwen3-4B 提供。但其架构的灵魂在于贯穿全流程的审计模块。\n宏观架构图 # graph TD User[用户输入] --\u003e API[FastAPI 网关] API --\u003e DST[Smart DST\n智能对话状态跟踪] subgraph \"检索层 (Recall)\" DST -- 增强查询 --\u003e Router[自适应权重路由] Router --\u003e ES[ElasticSearch\n关键词检索] Router --\u003e Faiss[Faiss\n向量检索] ES \u0026 Faiss --\u003e Candidates[候选文档集] end subgraph \"清洗层 (Sanitization)\" Candidates --\u003e ConflictDet[知识冲突检测器] ConflictDet -- 标记矛盾/剂量错误 --\u003e Context[安全上下文] end subgraph \"生成层 (Generation)\" Context --\u003e Prompt[动态 Prompt 构建\n注入冲突警告] Prompt --\u003e LLM[Qwen3-4B] LLM -- Logits/Text --\u003e RawAns[原始回答] end subgraph \"审计层 (Auditing)\" RawAns --\u003e HallucinationDet[增强型幻觉检测器] RawAns --\u003e ToxicityDet[毒性检测器] HallucinationDet -- 评分/拦截 --\u003e FinalAns[最终回答] end FinalAns --\u003e User 3. 核心设计探秘 # 将按照数据流动的方向，分析支撑该系统的五大核心模块。\n3.1 听懂人话：智能对话状态跟踪 (Smart DST) # 源码位置: QA/core/dialogue/smart_dst.py 设计挑战: 用户口语（“脑袋疼”）与专业文档（“头痛”）存在术语鸿沟；多轮对话中存在指代模糊。 核心实现:\n该模块放弃了不可控的端到端生成，采用了 “词典映射 + 规则填充” 的确定性策略。\n高级实体识别 (_advanced_entity_recognition): 强制将同义词映射回标准主实体。 查询增强 (_generate_enhanced_query): 利用上下文栈回溯历史实体，解决指代消解。 # 代码片段：同义词归一化与置信度计算 def _advanced_entity_recognition(self, query: str): # 遍历医疗实体词典 for variant in all_variants: if variant in query: # 关键：无论用户说的是土话还是术语，系统内部统一使用 main_entity confidence = 1.0 if variant == main_entity else 0.8 entity_mention = EntityMention(text=variant, ...) 3.2 动态寻源：自适应混合检索 (Adaptive Hybrid Retrieval) # 源码位置: QA/core/retrievers/adaptive_hybrid_retriever.py 设计挑战: 向量检索不懂“阿司匹林”的具体拼写，关键词检索不懂“感觉像针扎一样”的语义。 核心实现: 系统引入了 元认知（Meta-Cognition） 步骤。在检索前，先判断查询意图，再动态调整 ElasticSearch (ES) 和 Faiss 的权重。\n# 代码片段：基于意图的动态权重路由 def _calculate_weights(self, query_analysis): # 默认权重 weights = {\u0026#39;faiss\u0026#39;: 0.6, \u0026#39;es\u0026#39;: 0.4} # 策略路由 if query_type == \u0026#39;factual\u0026#39;: # 事实类问题依赖语义 weights[\u0026#39;faiss\u0026#39;] = 0.7 elif query_type == \u0026#39;treatment\u0026#39;: # 治疗方案依赖精确匹配 weights[\u0026#39;faiss\u0026#39;] = 0.8 # 注：此处代码倾向语义，但在低置信度时会反转 # 兜底机制：如果语义理解置信度低，强制依赖关键词 if confidence \u0026lt; 0.3: weights[\u0026#39;faiss\u0026#39;] *= 0.9 weights[\u0026#39;es\u0026#39;] *= 1.1 return weights 3.3 输入清洗：知识冲突检测机制 (Knowledge Conflict Detector) # 源码位置: QA/core/detectors/knowledge_conflict_detector.py 设计挑战: 检索回来的 Top-K 文档可能包含相互矛盾的信息（如不同来源的剂量建议）。 核心实现: 这是该系统最独特的预处理防线。它不盲目信任检索结果，而是假设文档间存在对抗关系。它能识别五类冲突：观点、剂量、时效、权威性、严重程度。\n剂量冲突检测: 专门的正则提取逻辑，防止数值错误。 冲突注入: 检测结果不会被丢弃，而是转化为结构化警告注入 Prompt。 # 代码片段：剂量冲突检测逻辑 if len(set(values)) \u0026gt; 1: # 如果提取出的剂量数值不一致 conflict = ConflictDetail( conflict_type=ConflictType.DOSAGE_CONFLICT, description=f\u0026#34;发现{unit}剂量冲突: {\u0026#39;, \u0026#39;.join(set(values))}\u0026#34;, severity=\u0026#34;high\u0026#34;, # 标记为高危 recommendation=\u0026#34;剂量冲突，必须咨询医生确认正确剂量\u0026#34; ) 3.4 概率生成：白盒监控与动态 Prompt # 源码位置: QA/core/generators/qwen3_generator.py 设计挑战: LLM 何时在瞎编？如何让它知道上下文有冲突？ 核心实现:\n动态 Prompt: 将冲突检测器的结果（警告和建议）显式写入 Prompt，利用 LLM 的 In-Context Learning 能力进行自我修正。 白盒监控: 深入模型推理层，捕获 Logits 并计算困惑度 (Perplexity)。如果生成的某个关键实体概率极低，即便语句通顺，系统也会标记风险。 # 代码片段：计算生成概率分布 for i, (score, token_id) in enumerate(zip(scores, generated_tokens)): probs = torch.softmax(score[0], dim=-1) token_prob = probs[token_id].item() # 获取当前Token的置信度 # ... 计算困惑度 ... 3.5 输出审计：增强型幻觉检测 (Enhanced Hallucination Detector) # 源码位置: QA/core/detectors/hallucination_detector.py 设计挑战: 如何防止模型生成违背常识或违反法规的内容？ 核心实现: 这是系统的最后一道防线。它是一个包含 8 个维度的综合审计器。\n医疗知识图谱 (KG): 内置微型 KG，通过逻辑推理（而非概率）拦截常识性错误（如“高血压禁忌”校验）。 语义一致性: 使用 BERT 计算生成文本与检索文档的向量相似度。 合规性正则: 强制过滤“100%治愈”、“神药”等违规词汇。 # 代码片段：基于KG的事实核查 def _verify_medical_facts(self, text, entities): for entity in entities: if entity in self.medical_knowledge_graph: # 检查文本是否包含了该疾病的\u0026#34;禁忌\u0026#34; for contraindication in kg_info.get(\u0026#39;contraindications\u0026#39;, []): if contraindication in text: fact_violations.append(...) # 记录违规 4. 关键数据流分析 (Case Study) # 追踪一个典型的高风险用例：“高血压患者能喝两杯白酒吗？”\nPhase 1: 理解 (Smart DST) 输入：“能喝两杯白酒吗？”（假设上下文已知高血压）。 动作：DST 回溯上下文，重写查询为 疑似疾病:高血压 | 饮食禁忌:白酒 | 剂量:两杯。 Phase 2: 检索 (Hybrid Retriever) 动作：ES 检索“高血压 AND 白酒”，Faiss 检索语义相关文档。 结果：检索到两篇文档。Doc A 说“少量饮酒可能有益”，Doc B 说“严禁饮酒，影响药效”。 Phase 3: 清洗 (Conflict Detector) 动作：识别到 Doc A 和 Doc B 存在 Viewpoint Conflict (观点冲突)。 输出：生成警告 [警告] 检测到关于'饮酒'的观点冲突，存在风险。 Phase 4: 生成 (Generator) 动作：Prompt 被修改，包含上述警告。 LLM 输出：“虽然有部分观点认为少量饮酒可行，但鉴于存在冲突且酒精可能影响降压药效果，建议高血压患者避免饮酒。” Phase 5: 审计 (Hallucination Detector) 动作：查阅内置 KG，发现 高血压 -\u0026gt; contraindications -\u0026gt; 过度饮酒。 判断：回答符合 KG 逻辑，语义与文档一致，无违规词。 结果：放行。 5. 总结 # 核心优势: 它最大的亮点在于 “不信任”。它不信任用户的表达（引入 DST），不信任单一的检索（引入混合检索），不信任检索到的源文档（引入冲突检测），也不信任 LLM 的生成（引入幻觉检测）。 架构哲学: 通过引入确定性的规则引擎（正则、词典、KG）来约束概率性的神经网络（LLM、Embedding），从而实现了医疗场景所需的可解释性和可控性。 改进空间: 目前的知识图谱（KG）和实体词典是硬编码在 Python 文件中的。在生产环境中，这部分应当解耦并对接专业的图数据库（如 Neo4j）或医疗术语服务，以支持实时更新和更大规模的知识库。 ","date":"2025-07-15","externalUrl":null,"permalink":"/blogs/rag/hybrid-rag-system/","section":"Blog","summary":"","title":"Hybrid-RAG-System：构建高置信度医疗问答系统","type":"blogs"},{"content":"","date":"2025-07-15","externalUrl":null,"permalink":"/tags/%E5%8C%BB%E7%96%97/","section":"Tags","summary":"","title":"医疗","type":"tags"},{"content":" 参考：humanlayer/12-factor-agents\nAI Agent 开发正陷入一个怪圈：原型惊艳，产品拉胯。无数团队在“概念验证”阶段展示出 Agent 强大的自主规划和工具调用能力，但当这些系统面对生产环境的复杂性与不确定性时，便迅速退化为不可预测、难以维护的“黑箱”。这就是业界普遍面临的“80%陷阱”——功能看似完成，但离可靠交付遥遥无期。\n问题的根源不在于模型能力，而在于架构纪律的缺失。\n本文将深度解析 humanlayer/12-factor-agents 项目提出的 12 条架构原则。这些原则提供了一套系统性的方法论，旨在帮助开发者从“框架使用者”转变为“系统架构师”，构建出真正健壮、可控且可演进的 AI 应用。\n核心思想转变：从“循环”到“确定性状态机” # 传统的 Agent 开发范式常被简化为一个简单的循环：while True: 思考 -\u0026gt; 执行 -\u0026gt; 观察。这种模式是脆弱的，因为它隐藏了至关重要的状态和控制流细节。\n12要素方法论引导的核心转变是：将 Agent 视为一个由外部事件驱动的、可被精确管理的确定性状态机 (Deterministic State Machine)。\n在这个模型中，Agent 的核心是一个纯粹的逻辑单元，它接收当前的状态（历史事件）和新的事件，然后确定性地输出下一步的动作。这种架构思想，是理解以下12条铁律的基石。\n第一部分：核心引擎 - 状态、控制与逻辑 # 这是 Agent 的内部骨架，决定了其运行的稳定性和可预测性。\n铁律 #8: 掌控你的控制流 (The Conductor)\n要点：\n放弃固定的while循环，根据 Agent 的决策意图，由外部代码（编排器）来决定下一步的流程：是继续、是中断，还是终止。\n解析： 生产级工作流充满了需要“等待”的时刻：等待人类审批、等待长时间任务、等待外部回调。一个无法被中断和精确恢复的 Agent 是毫无用处的。掌控控制流意味着，当 LLM 决策为 fetch_data 时，编排器可以立即执行并继续循环；当决策为 request_human_approval 时，编排器则应保存当前状态，中断执行，等待外部事件（如一个 Webhook 调用）来恢复流程。这是实现一切高级交互的基础。\n铁律 #5: 统一状态 (The Immutable Ledger)\n要点：\nAgent 的完整状态，就是其从创生至今的所有历史事件的有序列表。不存在与此分离的“执行状态”。\n解析： 这个原则极大地简化了系统的复杂性。整个 Agent 的生命周期被物化为一个不可变的事件日志（Event Log），包括用户请求、工具调用、API 返回、错误信息、人类反馈等。这个日志就是唯一可信源。需要持久化？序列化这个列表即可。需要调试？打印这个列表即可。需要暂停/恢复？从这个列表重建状态即可。\n铁律 #12: 无状态 Reducer (The Pure Function Core)\n要点：\nAgent 的核心决策逻辑应被设计成一个纯函数：\nnewState = agent(currentState, newEvent) 解析： 这是统一状态原则的逻辑延伸，也是实现高度可测试性的关键。给定相同的历史状态和新的事件，一个设计良好的 Agent 核心应总是输出相同的下一步决策。这使得开发者可以编写大量的单元测试用例，来验证 Agent 在各种复杂上下文下的行为是否符合预期，从而将“炼丹”过程转变为严谨的软件工程。\n第二部分：LLM 接口 - 精通对话的艺术 # 这是 Agent 与大语言模型交互的接口层，决定了信息传递的效率和准确性。\n铁律 #3: 掌控你的上下文 (The Art of Input)\n要点：\n精通上下文工程（Context Engineering）。放弃标准聊天格式的束缚，设计为特定任务优化的、信息密度更高的上下文结构。\n解析： 上下文的质量直接决定了 LLM 的输出质量。与其发送冗长的 JSON 消息数组，不如设计一种更紧凑的格式，例如用 XML 标签包裹不同类型的事件。\u0026lt;human_request\u0026gt;、\u0026lt;tool_call\u0026gt;、\u0026lt;tool_error\u0026gt; 等标签为 LLM 提供了强大的元信息，使其能更好地理解对话历史的结构和语义，从而在长对话中保持专注。\n铁律 #2: 掌控你的提示 (The Source Code of Instruction)\n要点：\nPrompt 即代码，必须纳入版本控制，并与业务逻辑一起接受测试和迭代。\n解析： 框架的黑箱式 Prompt 抽象是优化的噩梦。开发者必须能够完全控制发送给模型的每一个 token，包括系统指令、少量示例（few-shot examples）、动态数据和输出格式要求。只有这样，才能进行精细的性能调优和行为修正。\n铁律 #4: 工具即结构化输出 (The Intent Contract)\n要点：\n工具调用并非神秘的魔法，它仅仅是 LLM 遵循预定义 Schema 生成的结构化数据（JSON）。\n解析： 此原则将 LLM 的决策（生成什么 JSON）与应用的执行（如何处理这个 JSON）彻底解耦。这意味着收到一个 delete_user 的“工具调用”时，系统可以先检查权限、记录审计日志、发送高危操作警告，甚至要求二次确认，而不是直接执行删除操作。LLM 提出意图，但最终执行权在你的确定性代码手中。\n铁律 #9: 将错误压缩进上下文 (The Self-Healing Mechanism)\n要点：\n工具执行失败时，应将详细的、格式化的错误信息作为一次新的事件，反馈到 Agent 的上下文中。\n解析： 这是赋予 Agent 从失败中学习和恢复能力的关键。当 Agent 看到具体的错误信息（如 APIError: Quota exceeded 或 DBError: Unique constraint failed），它就有机会调整策略，例如切换备用 Key、改变请求参数，或请求人类协助。\n第三部分：系统边界与集成 # 这部分定义了 Agent 如何作为组件融入更庞大的软件生态系统。\n铁律 #10: 小而专注的 Agent (The Microservice Philosophy)\n要点：\n像构建微服务一样构建 Agent，而非一个试图包揽一切的单体。\n解析： LLM 的性能会随上下文长度增加而下降。构建一个拥有几十个工具、处理超长流程的“全能 Agent”是通往失败的捷径。正确的做法是将大任务拆解，由多个职责单一的小 Agent 协作完成，并通过确定性代码或一个“编排 Agent”进行协调。这保证了每个 Agent 的上下文都保持简短和聚焦，从而确保了整体系统的可靠性。\n铁律 #6: 简单的生命周期 API (The Service Contract)\n要点：\nAgent 必须通过一组简单的、标准的 API（如 RESTful API）来管理其生命周期。\n解析： Agent 需要被外部系统触发和管理。POST /agents 用于创建新任务，GET /agents/{id} 用于查询状态，POST /agents/{id}/events 用于注入外部事件（如 Webhook 回调）以恢复其执行。这使其能无缝集成到现有的 CI/CD、工作流引擎和监控告警体系中。\n铁律 #7: 人类亦是工具 (Human-in-the-Loop as a Tool)\n要点：\n将“请求人类输入”或“等待人类审批”抽象成与其他 API 调用完全相同的标准工具。\n解析： 这种抽象统一了 Agent 与外部世界的交互模型。对 Agent 而言，调用 send_email_to_human 和调用 query_database 在逻辑上没有区别——都是选择一个工具并等待结果。这使得实现复杂的人机协作流程变得极其优雅和模块化。\n铁律 #11: 随处触发与响应 (The Omnichannel Presence)\n要点： 将 Agent 的核心逻辑与具体的通信渠道（如 Slack、Email、CLI）解耦。\n解析： 一个强大的 Agent 应该能够适应用户的协作环境。它应该能解析来自邮件的触发指令，也能将审批请求发送到 Slack 频道。这意味着 Agent 的核心服务是渠道无关的，外部的适配器层负责翻译不同渠道的输入/输出格式。\n铁律 #1: 自然语言到工具调用 (The System Entrypoint)\n要点：\n将 LLM 的首要职责明确为“翻译”：将非结构化的自然语言请求，翻译成结构化的、机器可执行的工具调用意图。\n解析： 这为整个 Agent 系统定义了一个清晰的入口和能力边界。它强调了 LLM 的长处在于理解和意图识别，而非精确计算或执行复杂业务规则，为系统的其他部分设定了明确的预期。\n结论：从工匠到建筑师的飞跃 # 这 12 条架构铁律共同描绘了一幅蓝图，指导开发者如何构建出可预测、可维护、可扩展的 AI Agent。它要求我们进行一次思维上的范式转换：从一个专注于调教模型、拼凑功能的工匠，成长为一个设计稳健系统、管理复杂性的软件建筑师。\n在通往通用人工智能的漫长道路上，真正能落地并创造价值的，永远是那些遵循了严谨工程原则的系统。这 12 要素，正是通往这条道路的坚实阶梯。\n","date":"2025-07-12","externalUrl":null,"permalink":"/blogs/agent/agent-12-factor/","section":"Blog","summary":"","title":"构建可靠 AI Agent 的 12 条架构铁律","type":"blogs"},{"content":" https://www.anthropic.com/engineering/built-multi-agent-research-system\n我们的研究功能使用多个 Claude 智能体来更有效地探索复杂主题。我们在此分享构建该系统时遇到的工程挑战和学到的经验教训。\nClaude 现在具备了研究能力，可以在网络、Google Workspace 以及任何集成中进行搜索，以完成复杂的任务。\n这个多智能体系统从原型到产品的历程，教会了我们在系统架构、工具设计和提示工程方面的关键经验。一个多智能体系统由多个协同工作的智能体（在循环中自主使用工具的 LLM）组成。我们的研究功能涉及一个智能体，它根据用户查询规划研究过程，然后使用工具创建并行的智能体，同时搜索信息。多智能体系统在智能体协调、评估和可靠性方面带来了新的挑战。\n本文将分解对我们有效的设计原则——我们希望您在构建自己的多智能体系统时会发现它们很有用。\n多智能体系统的好处 # 研究工作涉及开放式问题，很难提前预测所需的步骤。您无法为探索复杂主题硬编码一个固定的路径，因为这个过程本质上是动态且依赖于路径的。当人们进行研究时，他们倾向于根据发现不断更新自己的方法，沿着调查过程中出现的线索前进。\n这种不可预测性使得人工智能智能体特别适合研究任务。研究要求在调查展开时能够灵活地调整方向或探索相关的分支。模型必须能够自主运行多个回合，根据中间发现决定追求哪个方向。线性的、一次性的流程无法处理这些任务。\n搜索的本质是压缩：从庞大的语料库中提炼出见解。子智能体通过在各自的上下文中并行操作来促进压缩，同时探索问题的不同方面，然后将最重要的标记（token）浓缩给首席研究智能体。每个子智能体还提供了关注点分离——不同的工具、提示和探索轨迹——这减少了路径依赖性，并实现了彻底、独立的调查。\n一旦智能达到一个阈值，多智能体系统就成为扩展性能的重要方式。例如，尽管在过去十万年里，个体人类变得更加聪明，但在信息时代，由于我们的集体智慧和协调能力，人类社会的能力呈指数级增长。即使是通用智能体在作为个体运作时也会面临限制；智能体群体可以完成更多的工作。\n我们的内部评估显示，多智能体研究系统在涉及同时追求多个独立方向的广度优先查询中表现尤为出色。我们发现，在一个内部研究评估中，以 Claude Opus 4 为 Lead Agent 、Claude Sonnet 4 为子智能体的多智能体系统，其性能比单智能体的 Claude Opus 4 高出90.2%。例如，当被要求识别信息技术标准普尔500指数中所有公司的董事会成员时，多智能体系统通过将任务分解给子智能体找到了正确答案，而单智能体系统则因缓慢的顺序搜索而未能找到答案。\n多智能体系统之所以有效，主要是因为它们有助于花费足够的 token 来解决问题。在我们的分析中，三个因素解释了 BrowseComp 评估中95%的性能差异（该评估测试了浏览智能体定位难以找到信息的能力）。我们发现，仅 token 使用量就解释了80%的差异，工具调用次数和模型选择是另外两个解释性因素。这一发现验证了我们的架构，该架构将工作分散到具有独立上下文窗口的智能体中，以增加并行推理的能力。最新的 Claude 模型在 token 使用上起到了巨大的效率倍增器作用，因为升级到 Claude Sonnet 4 带来的性能提升比在 Claude Sonnet 3.7 上将 token 预算翻倍还要大。多智能体架构有效地扩展了 token 使用量，以完成超出单个智能体限制的任务。\n但也有一个缺点：在实践中，这些架构消耗 token 的速度很快。根据我们的数据，智能体通常比聊天互动多使用约4倍的 token，而多智能体系统比聊天多使用约15倍的 token。为了在经济上可行，多智能体系统需要用于那些任务价值足够高，能够支付得起性能提升带来的成本的场景。此外，一些需要所有智能体共享相同上下文或智能体之间存在许多依赖关系的领域，目前并不适合多智能体系统。例如，大多数编码任务比研究任务涉及的真正可并行化的任务要少，而且 LLM 智能体在实时协调和委派给其他智能体方面还不够出色。我们发现，多智能体系统在涉及大量并行化、信息量超出单个上下文窗口以及与众多复杂工具接口的有价值任务中表现出色。\n研究功能的架构概述 # 我们的研究系统采用多智能体架构，具有协调者-工作者（orchestrator-worker）模式，其中一个 Lead Agent 协调整个过程，同时将任务委派给并行的专业子智能体。\n多智能体架构的运作方式：用户查询通过一个主智能体进行处理，该主智能体创建专门子智能体以并行搜索不同方面。 当用户提交查询时， Lead Agent 分析查询，制定策略，并生成子智能体以同时探索不同方面。如上图所示，子智能体充当智能过滤器，通过迭代使用搜索工具收集信息（在本例中是关于2025年的人工智能智能体公司），然后将公司列表返回给 Lead Agent ，以便其汇编最终答案。\n使用检索增强生成（RAG）的传统方法采用静态检索。也就是说，它们获取一组与输入查询最相似的文本块，并使用这些文本块生成响应。相比之下，我们的架构使用多步搜索，动态地查找相关信息，适应新的发现，并分析结果以形成高质量的答案。\n多智能体研究系统的完整工作流程 流程图描述：当用户提交查询时，系统创建一个 LeadResearcher 智能体，进入一个迭代的研究过程。LeadResearcher 首先思考方法并将其计划保存到内存中以保持上下文，因为如果上下文窗口超过200,000个 token，它将被截断，保留计划非常重要。然后，它创建具有特定研究任务的专门子智能体（这里显示了两个，但可以是任意数量）。每个子智能体独立执行网络搜索，使用交错思考（interleaved thinking）评估工具结果，并将发现返回给 LeadResearcher。LeadResearcher 综合这些结果并决定是否需要更多研究——如果需要，它可以创建额外的子智能体或完善其策略。一旦收集到足够的信息，系统退出研究循环，并将所有发现传递给一个 CitationAgent，该智能体处理文档和研究报告以确定引用的具体位置。这确保了所有声明都正确归属于其来源。最终，带有引用的研究结果将返回给用户。\n研究智能体的提示工程和评估 # 多智能体系统与单智能体系统有关键区别，其中包括协调复杂性的迅速增长。早期的智能体曾犯过这样的错误：为简单查询生成50个子智能体，无休止地在网络上搜索不存在的来源，以及通过过多的更新相互干扰。由于每个智能体都由提示引导，提示工程是我们改进这些行为的主要手段。以下是我们学到的一些提示智能体的原则：\n像你的智能体一样思考。 要迭代提示，你必须理解其效果。为了帮助我们做到这一点，我们使用控制台（Console），通过我们系统中的确切提示和工具构建了模拟，然后逐步观察智能体的工作。这立即揭示了失败模式：智能体在已经有足够结果时仍继续工作，使用过于冗长的搜索查询，或选择不正确的工具。有效的提示依赖于建立一个准确的智能体心智模型，这可以使最有影响力的改变变得显而易见。 教协调者如何委派任务。 在我们的系统中， Lead Agent 将查询分解为子任务，并向子智能体描述它们。每个子智能体需要一个目标、一个输出格式、关于使用哪些工具和来源的指导，以及明确的任务边界。没有详细的任务描述，智能体会重复工作、留下空白或无法找到必要的信息。我们开始时允许 Lead Agent 给出简单、简短的指令，如“研究半导体短缺”，但发现这些指令常常含糊不清，以至于子智能体误解任务或执行与其他智能体完全相同的搜索。例如，一个子智能体探索了2021年的汽车芯片危机，而另外两个则重复调查了当前的2025年供应链，没有进行有效的分工。 根据查询复杂性调整投入。 智能体很难判断不同任务的适当投入，所以我们在提示中嵌入了扩展规则。简单的事实查找只需要1个智能体进行3-10次工具调用，直接比较可能需要2-4个子智能体，每个进行10-15次调用，而复杂的研究可能需要超过10个子智能体，并有明确分工。这些明确的指导方针帮助 Lead Agent 有效地分配资源，并防止在简单查询上过度投入，这是我们早期版本中常见的失败模式。 工具设计和选择至关重要。 智能体与工具的接口和人机接口一样关键。使用正确的工具是高效的——通常，这是绝对必要的。例如，一个在Slack中搜索只存在于网络上的上下文的智能体从一开始就注定要失败。通过让模型访问外部工具的MCP服务器，这个问题变得更加复杂，因为智能体会遇到描述质量参差不齐的未知工具。我们为智能体提供了明确的启发式方法：例如，首先检查所有可用工具，将工具使用与用户意图匹配，为广泛的外部探索搜索网络，或优先选择专业工具而非通用工具。糟糕的工具描述可能会让智能体走上完全错误的道路，因此每个工具都需要一个明确的目的和清晰的描述。 让智能体自我改进。 我们发现 Claude 4 模型可以成为出色的提示工程师。当给定一个提示和一种失败模式时，它们能够诊断出智能体失败的原因并提出改进建议。我们甚至创建了一个工具测试智能体——当给定一个有缺陷的MCP工具时，它会尝试使用该工具，然后重写工具描述以避免失败。通过数十次测试该工具，这个智能体发现了关键的细微差别和错误。这种改进工具人体工程学的过程，使得未来使用新描述的智能体完成任务的时间减少了40%，因为它们能够避免大多数错误。 先宽后窄。 搜索策略应模仿专家的人类研究：在深入研究具体细节之前先探索整体情况。智能体通常会默认使用过长、具体的查询，结果返回很少。我们通过提示智能体从简短、宽泛的查询开始，评估可用的信息，然后逐步缩小焦点来纠正这种倾向。 引导思考过程。 扩展思考模式（extended thinking mode）能引导 Claude 在一个可见的思考过程中输出额外的 token，可以作为一个可控的草稿纸。 Lead Agent 使用思考来规划其方法，评估哪些工具适合任务，确定查询复杂度和子智能体数量，并定义每个子智能体的角色。我们的测试表明，扩展思考改善了指令遵循、推理和效率。子智能体也会进行规划，然后在工具结果出来后使用交错思考来评估质量、识别差距并完善下一次查询。这使得子智能体在适应任何任务时都更有效。 并行工具调用改变了速度和性能。 复杂的研究任务自然涉及探索许多来源。我们早期的智能体执行顺序搜索，速度非常慢。为了提高速度，我们引入了两种并行化：（1） Lead Agent 并行启动3-5个子智能体，而不是串行启动；（2）子智能体并行使用3个以上的工具。这些变化使复杂查询的研究时间减少了高达90%，使得研究功能能够在几分钟内完成更多工作，而不是几小时，同时覆盖比其他系统更多的信息。 我们的提示策略侧重于灌输良好的启发式方法，而不是僵化的规则。我们研究了熟练的人类如何处理研究任务，并将这些策略编码到我们的提示中——比如将难题分解为更小的任务，仔细评估来源质量，根据新信息调整搜索方法，以及识别何时应专注于深度（详细调查一个主题）与广度（并行探索多个主题）。我们还通过设置明确的护栏来主动减轻意外的副作用，以防止智能体失控。最后，我们专注于一个具有可观察性和测试用例的快速迭代循环。\n有效的智能体评估 # 良好的评估对于构建可靠的人工智能应用至关重要，智能体也不例外。然而，评估多智能体系统带来了独特的挑战。传统评估通常假设人工智能每次都遵循相同的步骤：给定输入X，系统应遵循路径Y以产生输出Z。但多智能体系统并非如此运作。即使起点相同，智能体也可能采取完全不同的有效路径来达到目标。一个智能体可能搜索三个来源，而另一个搜索十个，或者它们可能使用不同的工具找到相同的答案。因为我们并不总是知道正确的步骤是什么，所以我们通常不能仅仅检查智能体是否遵循了我们预先规定的“正确”步骤。相反，我们需要灵活的评估方法，既能判断智能体是否达到了正确的结果，又能判断其过程是否合理。\n立即用小样本开始评估。 在智能体开发的早期阶段，变化往往会产生巨大影响，因为有大量唾手可得的改进空间。一个提示的微调可能会将成功率从30%提高到80%。在效果如此之大的情况下，只需几个测试用例就可以发现变化。我们从大约20个代表真实使用模式的查询集开始。测试这些查询通常能让我们清楚地看到变化的影响。我们经常听说人工智能开发团队推迟创建评估，因为他们认为只有包含数百个测试用例的大型评估才有用。然而，最好是立即用几个例子开始小规模测试，而不是等到能够构建更全面的评估时再进行。 LLM-as-judge（以LLM为裁判）的评估方法如果做得好，可以扩展。 研究输出很难以编程方式评估，因为它们是自由格式的文本，很少有唯一的正确答案。LLM是评分输出的天然选择。我们使用了一个LLM裁判，它根据一个评分标准来评估每个输出：事实准确性（声明是否与来源匹配？）、引用准确性（引用的来源是否与声明匹配？）、完整性（是否涵盖了所有被要求的内容？）、来源质量（它是否使用了主要来源而非质量较低的次要来源？），以及工具效率（它是否以合理的次数使用了正确的工具？）。我们尝试了多个裁判来评估每个组成部分，但发现使用单个LLM调用、单个提示输出0.0-1.0的分数和合格/不合格等级的方式最一致，并且与人类的判断最为吻合。当评估测试用例确实有明确答案时，这种方法尤其有效，我们可以使用LLM裁判简单地检查答案是否正确（例如，它是否准确列出了研发预算排名前三的制药公司？）。使用LLM作为裁判使我们能够可扩展地评估数百个输出。 人工评估能捕捉到自动化遗漏的问题。 人工测试智能体能发现评估遗漏的边缘案例。这些包括对不寻常查询的幻觉性答案、系统故障或微妙的来源选择偏见。在我们的案例中，人工测试人员注意到，我们早期的智能体总是选择经过SEO优化的内容农场，而不是权威性更高但排名较低的来源，如学术PDF或个人博客。在我们的提示中加入来源质量的启发式方法帮助解决了这个问题。即使在自动化评估的世界里，手动测试仍然至关重要。 多智能体系统具有涌现行为，这些行为是在没有特定编程的情况下产生的。例如，对 Lead Agent 的微小改动可能会不可预测地改变子智能体的行为。成功需要理解交互模式，而不仅仅是单个智能体的行为。因此，这些智能体的最佳提示不仅仅是严格的指令，而是定义了分工、解决问题的方法和投入预算的协作框架。要做到这一点，依赖于仔细的提示和工具设计、可靠的启发式方法、可观察性以及紧密的反馈循环。请参阅我们 Cookbook 中的开源提示，以获取我们系统中的示例提示。\n生产可靠性和工程挑战 # 在传统软件中，一个错误可能会破坏一个功能、降低性能或导致服务中断。在智能体系统中，微小的变化会级联成巨大的行为变化，这使得为必须在长期运行过程中维持状态的复杂智能体编写代码变得异常困难。\n智能体是有状态的，错误会累积。 智能体可以长时间运行，在多次工具调用中维持状态。这意味着我们需要持久地执行代码并在此过程中处理错误。没有有效的缓解措施，微小的系统故障对智能体来说可能是灾难性的。当错误发生时，我们不能简单地从头开始：重新启动对用户来说既昂贵又令人沮丧。相反，我们构建了能够从智能体发生错误的地方恢复的系统。我们还利用模型的智能来优雅地处理问题：例如，让智能体知道某个工具正在失败并让其适应，效果出奇地好。我们将基于 Claude 构建的 AI 智能体的适应性与重试逻辑和定期检查点等确定性保障措施相结合。 调试得益于新方法。 智能体做出动态决策，并且即使使用相同的提示，每次运行之间也是非确定性的。这使得调试更加困难。例如，用户会报告智能体“找不到明显的信息”，但我们看不出原因。是智能体使用了糟糕的搜索查询吗？选择了差的来源？还是遇到了工具故障？增加完整的生产追踪让我们能够诊断智能体失败的原因并系统地修复问题。除了标准的可观察性，我们还监控智能体的决策模式和交互结构——所有这些都在不监控单个对话内容的情况下进行，以维护用户隐私。这种高层次的可观察性帮助我们诊断根本原因，发现意外行为，并修复常见故障。 部署需要仔细协调。 智能体系统是由提示、工具和执行逻辑组成的高度有状态的网络，几乎连续运行。这意味着每当我们部署更新时，智能体可能处于其流程的任何位置。因此，我们需要防止我们出于好意的代码更改破坏现有的智能体。我们不能同时将所有智能体更新到新版本。相反，我们使用“彩虹部署”（rainbow deployments）来避免干扰正在运行的智能体，通过在保持新旧版本同时运行的情况下，逐渐将流量从旧版本转移到新版本。 同步执行会产生瓶颈。 目前，我们的 Lead Agent 同步执行子智能体，等待每组子智能体完成后再继续。这简化了协调，但在智能体之间的信息流中造成了瓶颈。例如， Lead Agent 无法引导子智能体，子智能体之间无法协调，整个系统可能会因为等待单个子智能体完成搜索而被阻塞。异步执行将实现额外的并行性：智能体可以并发工作，并在需要时创建新的子智能体。但这种异步性在结果协调、状态一致性和跨子智能体的错误传播方面增加了挑战。随着模型能够处理更长、更复杂的研究任务，我们预计性能的提升将证明这种复杂性是值得的。 结论 # 在构建人工智能智能体时，“最后一英里”往往成为整个旅程的大部分。在开发人员机器上能工作的代码库，需要大量的工程工作才能成为可靠的生产系统。智能体系统中错误的复合性意味着，对传统软件来说的小问题可能会让智能体完全脱轨。一个步骤的失败可能导致智能体探索完全不同的轨迹，从而产生不可预测的结果。由于本文中描述的所有原因，原型和生产之间的差距通常比预期的要大。\n尽管存在这些挑战，多智能体系统已被证明对于开放式研究任务非常有价值。用户表示，Claude 帮助他们找到了他们未曾考虑过的商业机会，驾驭了复杂的医疗保健选项，解决了棘手的技术错误，并通过发现他们自己不会找到的研究联系，节省了多达数天的工作时间。通过精心的工程设计、全面的测试、注重细节的提示和工具设计、稳健的运营实践，以及对当前智能体能力有深刻理解的研究、产品和工程团队之间的紧密协作，多智能体研究系统可以在规模上可靠地运行。我们已经看到这些系统正在改变人们解决复杂问题的方式。\n一个 [Clio](https://www.anthropic.com/research/clio) 嵌入图，展示了人们今天使用研究功能的最常见方式。最常用的用例类别包括：在专业领域开发软件系统（10%）、开发和优化专业和技术内容（8%）、开发业务增长和收入生成策略（8%）、协助学术研究和教育材料开发（7%），以及研究和核实有关人员、地点或组织的信息（5%）。 附录 # 以下是关于多智能体系统的一些额外杂项技巧。\n对多回合改变状态的智能体进行终态评估。 评估在多回合对话中修改持久状态的智能体存在独特的挑战。与只读的研究任务不同，每个动作都可以改变后续步骤的环境， tạo ra các phụ thuộc mà các phương pháp đánh giá truyền thống khó xử lý。我们发现，专注于终态评估而不是逐回合分析是成功的关键。与其判断智能体是否遵循了特定的过程，不如评估它是否达到了正确的最终状态。这种方法承认智能体可能会找到通往同一目标的不同路径，同时仍确保它们交付预期的结果。对于复杂的工作流程，将评估分解为应发生特定状态变化的离散检查点，而不是试图验证每个中间步骤。 长程对话管理。 生产环境中的智能体通常会进行跨越数百回合的对话，需要仔细的上下文管理策略。随着对话的延长，标准的上下文窗口变得不足，需要智能的压缩和记忆机制。我们实现了一些模式，其中智能体在进入新任务之前，会总结已完成的工作阶段并将基本信息存储在外部存储器中。当接近上下文限制时，智能体可以生成具有干净上下文的新子智能体，同时通过仔细的交接保持连续性。此外，它们可以从内存中检索存储的上下文（如研究计划），而不是在达到上下文限制时丢失之前的工作。这种分布式方法可以防止上下文溢出，同时在扩展交互中保持对话的连贯性。 子智能体输出到文件系统以最小化“传话游戏”效应。 直接的子智能体输出可以绕过主协调器来处理某些类型的结果，从而提高保真度和性能。与其要求子智能体通过 Lead Agent 沟通所有内容，不如实现一个工件（artifact）系统，让专门的智能体可以创建独立持久的输出。子智能体调用工具将其工作存储在外部系统中，然后将轻量级的引用传回协调器。这可以防止在多阶段处理过程中的信息丢失，并减少因通过对话历史复制大量输出而产生的 token 开销。这种模式特别适用于结构化输出，如代码、报告或数据可视化，因为子智能体的专业化提示会比通过通用协调器过滤产生更好的结果。 ","date":"2025-06-15","externalUrl":null,"permalink":"/blogs/agent/claude-built-multi-agent-research-system/","section":"Blog","summary":"","title":"译文-构建多智能体研究系统-Anthropic","type":"blogs"},{"content":"","date":"2025-04-27","externalUrl":null,"permalink":"/series/fastapi/","section":"Series","summary":"","title":"FastAPI","type":"series"},{"content":"","date":"2025-04-27","externalUrl":null,"permalink":"/tags/fastapi/","section":"Tags","summary":"","title":"FastAPI","type":"tags"},{"content":" 本文将通过一个完整的实战项目——构建符合模型上下文协议（MCP）的天气查询服务，串联前五篇的核心知识点，涵盖项目结构规划、配置管理、MCP 协议适配、认证集成以及 Docker 容器化部署，展示从开发到交付的完整工程链路。 1. 项目规划与结构 # 遵循第一篇提到的工程化原则，将代码拆分为模块化结构，而非单文件应用。\nweather-mcp/ ├── app/ │ ├── __init__.py │ ├── main.py # 应用入口与 MCP 挂载 │ ├── config.py # 环境变量配置 │ ├── dependencies.py # 依赖注入（认证、HTTP客户端） │ ├── models.py # Pydantic 模型定义 │ └── tools.py # 核心业务逻辑（MCP工具函数） ├── .env # 敏感配置（不提交到 git） ├── Dockerfile # 容器构建文件 ├── requirements.txt └── .gitignore 2. 配置管理 (pydantic-settings) # 硬编码密钥是安全大忌。使用 pydantic-settings 从环境变量或 .env 文件读取配置。\n# app/config.py from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): WEATHER_API_KEY: str MCP_AUTH_TOKEN: str = \u0026#34;secret-token-123\u0026#34; # 默认值仅用于开发 ENV: str = \u0026#34;production\u0026#34; model_config = SettingsConfigDict(env_file=\u0026#34;.env\u0026#34;, extra=\u0026#34;ignore\u0026#34;) settings = Settings() 3. 核心业务与 HTTP 客户端 # 需要一个异步 HTTP 客户端来调用上游天气 API。\n# app/dependencies.py import httpx from typing import AsyncGenerator # 使用 httpx.AsyncClient 替代 requests 以支持异步 # 并将其作为 Yield 依赖项，确保资源释放 async def get_http_client() -\u0026gt; AsyncGenerator[httpx.AsyncClient, None]: async with httpx.AsyncClient(timeout=10.0) as client: yield client # app/models.py from pydantic import BaseModel, Field class WeatherQuery(BaseModel): city: str = Field(..., description=\u0026#34;City name to query weather for\u0026#34;) class WeatherData(BaseModel): temperature: float description: str humidity: int 4. MCP 服务实现与认证集成 # 将使用 fastapi-mcp 将 FastAPI 路由转换为 MCP 工具，并集成 Bearer Token 认证。\n# app/main.py from contextlib import asynccontextmanager from typing import Annotated from fastapi import FastAPI, Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi_mcp import FastApiMCP, AuthConfig import httpx from app.config import settings from app.models import WeatherQuery, WeatherData from app.dependencies import get_http_client # 1. 定义认证逻辑 security = HTTPBearer() async def verify_token(credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)]): if credentials.credentials != settings.MCP_AUTH_TOKEN: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=\u0026#34;Invalid authentication token\u0026#34;, headers={\u0026#34;WWW-Authenticate\u0026#34;: \u0026#34;Bearer\u0026#34;}, ) # 2. 初始化 FastAPI 应用 app = FastAPI(title=\u0026#34;Weather MCP Service\u0026#34;) # 3. 定义核心工具函数（路由） # 注意：operation_id 将成为 MCP 工具的名称 @app.post( \u0026#34;/weather\u0026#34;, response_model=WeatherData, operation_id=\u0026#34;get_current_weather\u0026#34;, description=\u0026#34;Get current weather for a specific city.\u0026#34; ) async def get_weather( query: WeatherQuery, client: Annotated[httpx.AsyncClient, Depends(get_http_client)] ): # 模拟外部 API 调用 # 真实场景应调用 https://api.weatherprovider.com... # response = await client.get(f\u0026#34;...\u0026#34;, params={\u0026#34;q\u0026#34;: query.city, \u0026#34;appid\u0026#34;: settings.WEATHER_API_KEY}) # data = response.json() return { \u0026#34;temperature\u0026#34;: 25.5, \u0026#34;description\u0026#34;: \u0026#34;Sunny\u0026#34;, \u0026#34;humidity\u0026#34;: 60 } # 4. 挂载 MCP 服务 # auth_config 将保护 /mcp 端点 mcp = FastApiMCP( app, name=\u0026#34;WeatherService\u0026#34;, version=\u0026#34;1.0.0\u0026#34;, include_operations=[\u0026#34;get_current_weather\u0026#34;], # 显式指定暴露的工具 auth_config=AuthConfig(dependencies=[Depends(verify_token)]) ) mcp.mount() # 默认挂载在 /mcp 5. Docker 容器化部署 # 为了交付标准化的服务，需要编写 Dockerfile。推荐使用 Multi-stage 构建以减小镜像体积，并使用非 root 用户运行以提高安全性。\n# Dockerfile # Stage 1: Builder FROM python:3.11-slim as builder WORKDIR /app COPY requirements.txt . # 安装依赖到用户目录，避免污染系统环境 RUN pip install --user --no-cache-dir -r requirements.txt # Stage 2: Runner FROM python:3.11-slim WORKDIR /app # 从 builder 阶段复制安装好的包 COPY --from=builder /root/.local /root/.local COPY ./app ./app # 更新 PATH 环境变量 ENV PATH=/root/.local/bin:$PATH # 创建非 root 用户 RUN useradd -m appuser USER appuser # 生产级启动命令：使用 gunicorn 管理 uvicorn workers CMD [\u0026#34;gunicorn\u0026#34;, \u0026#34;app.main:app\u0026#34;, \u0026#34;-w\u0026#34;, \u0026#34;4\u0026#34;, \u0026#34;-k\u0026#34;, \u0026#34;uvicorn.workers.UvicornWorker\u0026#34;, \u0026#34;-b\u0026#34;, \u0026#34;0.0.0.0:8000\u0026#34;] 运行命令：\n# 构建镜像 docker build -t weather-mcp:v1 . # 运行容器 (注入环境变量) docker run -d \\ -p 8000:8000 \\ -e WEATHER_API_KEY=\u0026#34;your_api_key\u0026#34; \\ -e MCP_AUTH_TOKEN=\u0026#34;your_secure_token\u0026#34; \\ weather-mcp:v1 6. 集成验证 # 当服务启动后，AI Agent（如 Cherry Studio 或 Cursor）可以通过以下配置接入该服务：\nServer URL: http://\u0026lt;your-ip\u0026gt;:8000/mcp Headers: Authorization: Bearer your_secure_token AI 将自动识别 get_current_weather 工具，解析其输入 Schema (WeatherQuery)，并在用户提问“查询北京天气”时自动构造请求调用你的 API。\n总结 # 工程闭环：通过分层结构、配置管理、依赖注入和 Pydantic 建模，构建了一个可维护、可测试的微服务。 MCP 适配：借助 fastapi-mcp，以极低的成本将传统 Web API 转化为 AI 生态的一部分，无需重写业务逻辑。 交付标准：Docker 化和环境变量配置是现代云原生应用交付的底线要求。 ","date":"2025-04-27","externalUrl":null,"permalink":"/blogs/python/fastapi/fastapi-06/","section":"Blog","summary":"","title":"FastAPI：构建 MCP 服务","type":"blogs"},{"content":" 本文将剖析 FastAPI 的底层架构（ASGI）、并发模型的设计原则、路由分发机制，以及如何通过 APIRouter 实现大型项目的模块化结构。 1. 架构基石：ASGI 与 Uvicorn # FastAPI 本身并不直接处理 HTTP 连接，它是建立在 Starlette（负责 Web 路由与 ASGI 协议）和 Pydantic（负责数据验证）之上的框架。理解 FastAPI 的第一步，是理解 ASGI (Asynchronous Server Gateway Interface)。\n1.1 同步与异步的界限 # 传统的 Python Web 框架（如 Flask, Django）基于 WSGI 标准，本质上是同步阻塞的。当一个请求处理数据库查询时，该线程被挂起，无法处理其他请求。\nFastAPI 基于 ASGI 标准，支持异步 I/O。这要求我们需要一个 ASGI 服务器来运行应用，最常用的是 Uvicorn。\nUvicorn：负责监听 Socket 端口，解析 HTTP 协议，将原始字节转换为 ASGI 消息字典。 FastAPI：消费这些消息，执行业务逻辑，返回响应。 1.2 并发模型：async def vs def # 这是工程实践中极易混淆且影响性能的关键点。FastAPI 对路由处理函数（Path Operation Functions）提供了两种定义方式，其运行机制截然不同。\n机制解析 # async def (异步函数)：\n运行环境：直接在主事件循环（Event Loop）中运行。 适用场景：执行支持 await 的非阻塞 I/O 操作（如使用 httpx, motor, tortoise-orm 等异步库）。 警告：严禁在此类函数中执行阻塞操作（如 time.sleep(), 大量 CPU 计算，或使用同步的 requests/pymysql 库）。一旦阻塞，整个服务器将无法响应任何新请求。 def (普通函数)：\n运行环境：FastAPI 会将其放入底层的 线程池（ThreadPool） 中执行。 适用场景：必须使用同步库（如 requests），或执行某些无法异步化的计算。 代价：线程切换存在上下文开销，并发上限受限于线程池大小。 代码对比 # import time import asyncio from fastapi import FastAPI app = FastAPI() # 场景A：正确使用 async # 在 Event Loop 中挂起，不阻塞其他请求 @app.get(\u0026#34;/async-sleep\u0026#34;) async def async_sleep(): await asyncio.sleep(1) return {\u0026#34;status\u0026#34;: \u0026#34;async done\u0026#34;} # 场景B：正确使用 def (处理同步阻塞) # FastAPI 自动将其丢入线程池，主 Loop 不受影响 @app.get(\u0026#34;/sync-sleep\u0026#34;) def sync_sleep(): time.sleep(1) return {\u0026#34;status\u0026#34;: \u0026#34;sync threadpool done\u0026#34;} # 场景C：【严重错误】在 async 中执行同步阻塞 # 整个服务将被卡死 1 秒，无法处理任何并发请求 @app.get(\u0026#34;/blocking-error\u0026#34;) async def blocking_error(): time.sleep(1) # 禁止的操作 return {\u0026#34;status\u0026#34;: \u0026#34;server blocked\u0026#34;} 2. 路由机制与参数解析 # FastAPI 利用 Python 的类型提示（Type Hints）进行路由参数的解析与注入。\n2.1 实例化与上下文 # app = FastAPI( title=\u0026#34;Core API\u0026#34;, version=\u0026#34;1.0.0\u0026#34;, docs_url=\u0026#34;/api/docs\u0026#34; # 自定义文档路径 ) app 对象维护了路由表、中间件栈和异常处理器。在生产环境中，通常不需要直接运行 uvicorn.run(app, ...)，而是通过命令行启动 worker 进程管理：\nuvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 2.2 路径参数 (Path Parameters) # 路径参数是 URL 的一部分。FastAPI 通过函数参数的类型注解自动进行类型转换。\n@app.get(\u0026#34;/items/{item_id}\u0026#34;) async def read_item(item_id: int): # 如果访问 /items/foo，FastAPI 会自动返回 422 错误 # 因为 \u0026#34;foo\u0026#34; 无法转换为 int return {\u0026#34;item_id\u0026#34;: item_id} 2.3 查询参数 (Query Parameters) # 声明在函数签名中、但未包含在路由路径中的参数，自动被识别为查询参数（?key=value）。\n@app.get(\u0026#34;/files/{file_path:path}\u0026#34;) async def read_file( file_path: str, # 路径参数 skip: int = 0, # 查询参数，默认 0 limit: int = 10 # 查询参数，默认 10 ): return {\u0026#34;file\u0026#34;: file_path, \u0026#34;skip\u0026#34;: skip, \u0026#34;limit\u0026#34;: limit} 注意：file_path:path 是 Starlette 的特殊语法，允许路径参数包含 /，例如 /files/home/user/data.txt。 3. 工程化结构：APIRouter 模块化 # 在实际工程中，将所有路由堆积在 main.py 是不可维护的。FastAPI 提供了 APIRouter 用于路由分发，类似于 Flask 的 Blueprint 或 Django 的 app urls。\n3.1 目录结构建议 # project/ ├── app/ │ ├── __init__.py │ ├── main.py # 入口文件 │ └── api/ # 接口目录 │ ├── __init__.py │ ├── users.py # 用户模块 │ └── orders.py # 订单模块 3.2 定义子路由 (app/api/users.py) # from fastapi import APIRouter # 实例化路由器 router = APIRouter( prefix=\u0026#34;/users\u0026#34;, tags=[\u0026#34;Users\u0026#34;], responses={404: {\u0026#34;description\u0026#34;: \u0026#34;Not found\u0026#34;}}, ) @router.get(\u0026#34;/\u0026#34;) async def read_users(): return [{\u0026#34;username\u0026#34;: \u0026#34;Admin\u0026#34;}, {\u0026#34;username\u0026#34;: \u0026#34;User1\u0026#34;}] @router.get(\u0026#34;/me\u0026#34;) async def read_user_me(): return {\u0026#34;username\u0026#34;: \u0026#34;current_user\u0026#34;} 3.3 注册路由 (app/main.py) # from fastapi import FastAPI from app.api import users, orders app = FastAPI() # 将子路由挂载到主应用 app.include_router(users.router) app.include_router(orders.router) # 假设 orders 模块已定义 通过这种方式，我们可以分离关注点，不同团队成员可以维护不同的业务模块，互不干扰。\n4. 底层机制：OpenAPI 反射生成 # FastAPI 的自动文档（Swagger UI / ReDoc）并非魔术，而是基于 反射（Reflection） 机制。\n解析签名：应用启动时，FastAPI 遍历所有注册的路由函数。 提取类型：分析函数的参数名、类型注解（int, str, Pydantic Model）以及默认值。 生成 Schema：根据 OpenAPI 3.1.0 标准，生成对应的 JSON Schema 描述（包含输入约束、输出格式、HTTP 方法等）。 渲染 UI：/docs 端点读取上述生成的 JSON，渲染为交互式网页。 这也是为什么在 FastAPI 中必须严格编写类型提示的原因——它不仅仅是为了代码补全，更是 API 契约定义的核心来源。\n总结 # 并发选择：I/O 密集型任务且库支持异步时使用 async def；涉及同步阻塞调用时务必使用 def。 类型驱动：FastAPI 严重依赖 Python Type Hints 进行数据解析和文档生成，需养成良好的类型标注习惯。 结构治理：项目起步阶段即应使用 APIRouter 规划目录结构，避免后期重构成本。 ","date":"2025-04-27","externalUrl":null,"permalink":"/blogs/python/fastapi/fastapi-01/","section":"Blog","summary":"","title":"FastAPI：基础架构与路由机制","type":"blogs"},{"content":" 数据校验是 Web 服务健壮性的第一道防线。本文将深入探讨 FastAPI 如何结合 Pydantic 进行从原子参数到复杂结构体的数据验证，重点分析 Python 3.9+ 的 Annotated 语法实践、Pydantic V2 的核心特性，以及在工程中如何设计可维护的模型继承链。 1. 原子参数校验：路径与查询 # 在处理简单的非 JSON 数据（如 URL 中的 ID 或分页参数）时，FastAPI 提供了 Path 和 Query 类。为了保持代码的整洁和类型系统的兼容性，推荐使用 Python 标准库 typing.Annotated 进行声明。\n1.1 为什么使用 Annotated？ # 在旧版本 FastAPI 中，参数默认值的写法（如 q: str = Query(None)）混淆了“类型定义”与“验证逻辑”。Annotated 实现了关注点分离：第一个参数是类型，第二个参数是验证元数据。这使得静态类型检查工具（如 mypy, pyright）能正确识别变量类型，而不需要为了运行时行为牺牲静态检查的准确性。\n1.2 校验实战 # from typing import Annotated from fastapi import FastAPI, Path, Query app = FastAPI() @app.get(\u0026#34;/items/{item_id}\u0026#34;) async def read_items( # 路径参数：必须大于等于1，小于等于1000 item_id: Annotated[int, Path(title=\u0026#34;Item ID\u0026#34;, ge=1, le=1000)], # 查询参数：可选，最大长度50，正则匹配（仅允许字母数字） # 如果不传，默认为 None q: Annotated[str | None, Query(max_length=50, pattern=\u0026#34;^[a-zA-Z0-9]+$\u0026#34;)] = None, # 查询参数：包含别名（前端传 page-size，后端接收 size） size: Annotated[int, Query(alias=\u0026#34;page-size\u0026#34;, gt=0)] = 10 ): results = {\u0026#34;item_id\u0026#34;: item_id, \u0026#34;size\u0026#34;: size} if q: results.update({\u0026#34;q\u0026#34;: q}) return results 常用的校验算子：\ngt: greater than (\u0026gt;) ge: greater than or equal (\u0026gt;=) lt: less than (\u0026lt;) le: less than or equal (\u0026lt;=) min_length / max_length: 字符串长度限制 pattern: 正则表达式约束 2. 结构化建模：Pydantic 核心机制 # 对于 POST / PUT 请求中的 JSON Body，FastAPI 依赖 Pydantic 进行解析。Pydantic 的核心理念是：解析即验证（Parse, don\u0026rsquo;t validate）。如果数据符合模型结构，它不仅被验证，还会被转换（Coercion）为指定的 Python 类型。\n2.1 Pydantic V2 的核心变更 # 当前 FastAPI 版本底层已支持 Pydantic V2。V2 的核心校验逻辑重写为 Rust (pydantic-core)，带来了显著的性能提升。\nAPI 变更注意： 序列化：model.dict() 已被废弃，应使用 model.model_dump()。 JSON 序列化：model.json() 已被废弃，应使用 model.model_dump_json()。 2.2 嵌套模型与复杂类型 # 处理复杂业务逻辑时，数据往往具有层级结构。\nfrom typing import List from pydantic import BaseModel, HttpUrl, Field class Image(BaseModel): url: HttpUrl # 自动验证 URL 格式 name: str class Item(BaseModel): name: str description: str | None = None price: float = Field(gt=0, description=\u0026#34;价格必须为正数\u0026#34;) tax: float | None = None # 嵌套模型列表 # Set 集合类型可用于自动去重 tags: set[str] = set() # 引用其他模型 images: List[Image] | None = None @app.post(\u0026#34;/items/\u0026#34;) async def create_item(item: Item): # 此处 item 已经是验证通过并转换好的 Item 对象 # price 自动转为 float，url 自动转为 HttpUrl 对象 # 将模型转换为字典，准备存入数据库 item_dict = item.model_dump() return item_dict 3. 高级校验模式 # 3.1 Strict 模式（严格模式） # 默认情况下，Pydantic 会尝试转换类型。例如，如果模型定义 id: int，前端传 JSON {\u0026quot;id\u0026quot;: \u0026quot;123\u0026quot;}，Pydantic 会自动将其转换为整数 123。\n在某些对类型要求极高（如金融交易）的场景下，这种隐式转换可能带来风险。可以使用 Pydantic 的 Strict 类型来禁止转换。\nfrom pydantic import BaseModel, StrictInt, StrictBool class Transaction(BaseModel): # 必须传 JSON number 类型，传 string \u0026#34;100\u0026#34; 会报错 amount: StrictInt # 必须传 JSON boolean 类型，传 \u0026#34;true\u0026#34; 或 1 会报错 is_verified: StrictBool 3.2 Field 元数据 # Field 不仅用于数据校验，还承担着文档生成的重任。\ntitle / description: 渲染到 OpenAPI 文档。 examples: 提供给 Swagger UI 的示例值，方便调试。 default_factory: 处理可变类型的默认值（如 list, dict），避免 Python 中可变默认参数的陷阱。 class LogEntry(BaseModel): timestamp: datetime = Field(default_factory=datetime.now) details: dict = Field(default_factory=dict) 4. 工程设计模式：模型继承策略 (DRY) # 在实际 CRUD 开发中，创建（Create）、更新（Update）、读取（Read）的数据结构往往高度相似但略有不同。例如，用户注册时有密码，但在返回用户信息时必须剔除密码。\n为了避免代码重复（DRY 原则），推荐使用继承链模式。\n4.1 基础模型 (Base) # 定义所有操作共有的字段。\nclass UserBase(BaseModel): username: str email: str full_name: str | None = None 4.2 输入模型 (Create/Input) # 继承 Base，并添加创建时特有的字段（如密码）。\nclass UserCreate(UserBase): password: str # 必填，但在 API 响应中不存在 4.3 响应模型 (Response/Output) # 继承 Base，添加数据库生成的字段（如 ID、创建时间）。\nclass UserResponse(UserBase): id: int is_active: bool # Pydantic V2 配置：允许从 ORM 对象读取数据 model_config = {\u0026#34;from_attributes\u0026#34;: True} 4.4 在路由中的应用 # @app.post(\u0026#34;/users/\u0026#34;, response_model=UserResponse) async def create_user(user_in: UserCreate): # 1. user_in 包含 password # 2. 执行数据库操作，保存 hash 后的密码 user_db = fake_save_user(user_in) # 3. 返回 user_db (包含 password 字段) # 4. FastAPI 根据 response_model=UserResponse 自动过滤掉 password return user_db 通过这种分层设计，我们确保了输入数据的完整性，同时严格控制了输出数据的安全性，且最大程度地复用了代码定义。\n总结 # 原子校验：使用 Annotated[Type, Constraints] 语法，保持类型系统的纯净。 Pydantic V2：利用 Rust 内核的高性能，注意 API 从 .dict() 到 .model_dump() 的迁移。 严格模式：在关键业务字段使用 Strict 类型防止隐式转换带来的副作用。 继承复用：通过 Base -\u0026gt; Create -\u0026gt; Response 的模型继承链，解决字段冗余与敏感数据泄露问题。 ","date":"2025-04-27","externalUrl":null,"permalink":"/blogs/python/fastapi/fastapi-02/","section":"Blog","summary":"","title":"FastAPI：请求数据校验与 Pydantic 建模","type":"blogs"},{"content":" API 的输出质量直接关系到客户端的集成体验与系统性能。本文将深入 FastAPI 的响应处理机制，包括利用响应模型进行数据清洗、引入高性能 JSON 序列化库 orjson 提升吞吐量、符合 RESTful 语义的状态码管理，以及如何构建全局统一的异常处理体系。 1. 响应模型的数据治理 # 在上一篇中，提到了 UserResponse 模型的继承设计。这里将深入 response_model 在实际运行时的核心行为：数据清洗（Data Validation \u0026amp; Filtering）。\n1.1 安全性与过滤 # response_model 的最大价值在于它充当了 API 的“出口安检”。无论后端逻辑或数据库返回了多少冗余或敏感字段（如 password_hash, internal_id），只要不在 response_model 定义中，这些字段在序列化阶段就会被无情丢弃。\n# 数据库模型可能包含所有字段 class DBUser: def __init__(self): self.username = \u0026#34;admin\u0026#34; self.password = \u0026#34;secret\u0026#34; # 敏感数据 self.role = \u0026#34;superuser\u0026#34; # 响应模型仅定义允许公开的字段 class PublicUser(BaseModel): username: str @app.get(\u0026#34;/user\u0026#34;, response_model=PublicUser) async def get_user(): # 即使这里返回了完整的 DB 对象 return DBUser() # 客户端收到的 JSON 仅为: {\u0026#34;username\u0026#34;: \u0026#34;admin\u0026#34;} 1.2 稀疏字段优化 # 对于包含大量可选字段（Optional）的模型，默认返回 null 可能会导致响应体积膨胀。使用 response_model_exclude_unset=True 可以仅返回显式赋值的字段。\n@app.get(\u0026#34;/items/{id}\u0026#34;, response_model=Item, response_model_exclude_unset=True) async def read_item(id: str): # 如果 Item 模型中有 description: str | None = None # 且数据库中该值为默认值 None # 则返回的 JSON 中完全不会出现 \u0026#34;description\u0026#34; 键 return item_data 2. 性能优化：引入 ORJSON # FastAPI 默认使用 Python 标准库 json 进行序列化。在处理大规模数据（如返回数万行的列表）时，标准库的性能往往成为瓶颈。ORJSON 是一个基于 Rust 开发的高性能 JSON 库，其序列化速度通常是标准库的数倍至数十倍。\n2.1 替换 DefaultResponse # 要在 FastAPI 中全局启用 ORJSON，只需在应用初始化时指定 default_response_class。\n安装依赖：\npip install orjson 配置应用：\nfrom fastapi import FastAPI from fastapi.responses import ORJSONResponse app = FastAPI(default_response_class=ORJSONResponse) 2.2 处理特殊数据类型 # ORJSONResponse 的另一个优势是它原生支持 numpy.ndarray, datetime, UUID 等类型的序列化，无需像标准库那样编写自定义 Encoder。\nimport numpy as np from fastapi.responses import ORJSONResponse @app.get(\u0026#34;/vector\u0026#34;, response_class=ORJSONResponse) async def get_vector(): # orjson 可以直接序列化 numpy 数组，无需 tolist() data = np.random.rand(1000) return {\u0026#34;vector\u0026#34;: data} 3. 状态码管理与语义化 # 硬编码数字状态码（如 200, 404）降低了代码的可读性。FastAPI 推荐使用 fastapi.status 模块提供的常量。\n3.1 常用状态码映射 # 在 RESTful API 设计中，区分不同的成功状态至关重要：\n200 OK：通用查询成功、更新成功（PUT）。 201 Created：资源创建成功（POST）。 202 Accepted：请求已接受进入后台队列，尚未处理完成。 204 No Content：删除成功（DELETE），响应体必须为空。 3.2 代码实践 # from fastapi import FastAPI, status, Response @app.post(\u0026#34;/items/\u0026#34;, status_code=status.HTTP_201_CREATED) async def create_item(item: Item): return {\u0026#34;id\u0026#34;: \u0026#34;new_id\u0026#34;} @app.delete(\u0026#34;/items/{id}\u0026#34;, status_code=status.HTTP_204_NO_CONTENT) async def delete_item(id: str): # 执行删除逻辑... # 204 响应不能有 Body，直接返回 Response 对象 return Response(status_code=status.HTTP_204_NO_CONTENT) 4. 全局异常处理体系 # 在工程实践中，不仅需要处理正常的响应，更需要统一异常的返回格式。直接抛出 Python 原生异常（如 KeyError）会导致服务器返回 500 Internal Server Error 且不包含任何有效信息，这是不合格的 API 设计。\n4.1 自定义 HTTPException # 虽然 FastAPI 提供了 HTTPException，但在大型项目中，通常建议封装业务异常基类。\n4.2 全局异常处理器 (Global Exception Handlers) # 通过 @app.exception_handler 捕获特定异常，统一返回 JSON 格式的错误信息。\nfrom fastapi import Request from fastapi.responses import JSONResponse # 1. 定义自定义业务异常 class UnicornException(Exception): def __init__(self, name: str): self.name = name # 2. 注册异常处理器 @app.exception_handler(UnicornException) async def unicorn_exception_handler(request: Request, exc: UnicornException): return JSONResponse( status_code=418, content={\u0026#34;message\u0026#34;: f\u0026#34;Oops! {exc.name} did something wrong.\u0026#34;}, ) # 3. 覆盖 FastAPI 默认的验证异常 (RequestValidationError) # 使得参数校验错误的返回格式符合内部规范 from fastapi.exceptions import RequestValidationError @app.exception_handler(RequestValidationError) async def validation_exception_handler(request, exc): return JSONResponse( status_code=422, content={ \u0026#34;code\u0026#34;: 422, \u0026#34;message\u0026#34;: \u0026#34;参数校验失败\u0026#34;, \u0026#34;detail\u0026#34;: exc.errors() # 保留 Pydantic 的详细错误信息 }, ) 这种机制确保了无论系统内部发生何种错误（业务逻辑错误、数据库连接错误、参数校验错误），前端收到的永远是结构一致的 JSON（如都包含 code, message），极大地降低了前端对接的复杂度。\n总结 # 响应清洗：response_model 是数据安全的最后一道防线，务必定义专门的 Output Schema。 高性能序列化：在数据密集型应用中，替换为 ORJSONResponse 是最低成本的性能优化手段。 RESTful 语义：严格使用 status 常量，区分 200, 201, 204 的使用场景。 异常统一：杜绝直接抛出 500 错误，通过全局 exception_handler 建立标准的错误响应协议。 ","date":"2025-04-27","externalUrl":null,"permalink":"/blogs/python/fastapi/fastapi-03/","section":"Blog","summary":"","title":"FastAPI：响应处理与序列化控制","type":"blogs"},{"content":" 除了标准的 JSON 数据交互，Web 工程还涉及对 HTTP 协议元数据（Headers/Cookies）的控制以及二进制文件流的处理。本文将深入 FastAPI 如何提取协议头信息、处理表单数据，并重点分析 UploadFile 的内存管理机制及大文件上传的安全实践。 1. 协议元数据：Header 与 Cookie # 在 FastAPI 中，Header 和 Cookie 是与 Path 和 Query 同级的依赖项类。提取这些数据的机制遵循 HTTP 协议规范，但在命名转换上有一个关键细节。\n1.1 Header 的自动转换 # HTTP 协议中的 Header 通常使用连字符（如 User-Agent），而 Python 变量名不能包含连字符。FastAPI 会自动将参数名中的下划线 _ 转换为连字符 -。\nfrom typing import Annotated from fastapi import FastAPI, Header, Cookie @app.get(\u0026#34;/items/\u0026#34;) async def read_items( # 自动映射请求头 \u0026#34;User-Agent\u0026#34; -\u0026gt; user_agent user_agent: Annotated[str | None, Header()] = None, # 如果需要禁止自动转换，需设置 convert_underscores=False # 或者直接使用 alias 指定原始 Header 名 x_token: Annotated[str | None, Header(alias=\u0026#34;X-Token\u0026#34;)] = None ): return {\u0026#34;User-Agent\u0026#34;: user_agent, \u0026#34;X-Token\u0026#34;: x_token} 1.2 Cookie 的状态保持 # Cookie 的处理方式与 Header 类似。需要注意的是，Cookie 是客户端可伪造的，不可用于存储敏感信息（如权限标识），仅建议用于存储 Session ID 或非敏感的用户偏好设置。\n@app.get(\u0026#34;/me\u0026#34;) async def read_current_user( session_id: Annotated[str | None, Cookie()] = None ): if not session_id: return {\u0026#34;msg\u0026#34;: \u0026#34;No session\u0026#34;} return {\u0026#34;session_id\u0026#34;: session_id} 2. 表单数据解析 # 尽管现代 API 多使用 JSON，但在处理文件上传或对接传统 Web 系统时，application/x-www-form-urlencoded 格式依然存在。\n2.1 Form 依赖项 # 使用 Form 必须预先安装 python-multipart 库，这是因为 Starlette 需要它来解析表单流。\npip install python-multipart from fastapi import Form @app.post(\u0026#34;/login/\u0026#34;) async def login( username: Annotated[str, Form()], password: Annotated[str, Form()] ): return {\u0026#34;username\u0026#34;: username} 工程约束：由于 HTTP 协议限制，请求体编码只能是 application/json 或 application/x-www-form-urlencoded 等其中一种。因此，不能在同一个路由中同时定义 Pydantic Model（JSON Body）和 Form 参数。\n3. 文件上传机制深度解析 # FastAPI 提供了 bytes（全量读取）和 UploadFile（流式处理）两种方式处理文件。在工程实践中，强烈建议使用 UploadFile。\n3.1 bytes vs UploadFile # bytes (File)：将整个文件内容一次性读取到内存中。 风险：如果用户上传 1GB 的视频，服务器内存将瞬间飙升，极易导致 OOM (Out Of Memory) 崩溃。 UploadFile：基于 Python 的 SpooledTemporaryFile 实现。 机制：设置一个内存阈值（通常为 1MB）。文件小于该值时存内存；超过该值时，自动写入磁盘临时目录。 优势：内存占用恒定，适合处理任意大小的文件。 3.2 流式写入（最佳实践） # 不要使用 await file.read() 一次性读入内存，应该使用分块读取（Chunked Read）并写入目标存储（如本地磁盘或 S3）。\nimport shutil from pathlib import Path from fastapi import FastAPI, UploadFile # 定义存储目录 UPLOAD_DIR = Path(\u0026#34;uploads\u0026#34;) UPLOAD_DIR.mkdir(exist_ok=True) @app.post(\u0026#34;/upload/\u0026#34;) async def upload_file(file: UploadFile): destination = UPLOAD_DIR / file.filename try: # 方式A：使用 shutil (同步操作，会阻塞) # 注意：file.file 是一个标准的 Python file-like object # with destination.open(\u0026#34;wb\u0026#34;) as buffer: # shutil.copyfileobj(file.file, buffer) # 方式B：异步分块写入 (推荐，非阻塞) async with aiofiles.open(destination, \u0026#39;wb\u0026#39;) as out_file: while content := await file.read(1024 * 1024): # 每次读取 1MB await out_file.write(content) finally: # 务必关闭文件句柄，释放临时资源 await file.close() return {\u0026#34;filename\u0026#34;: file.filename} 注：方式B需要安装 aiofiles 库。\n3.3 安全性考量 # 在处理文件上传时，必须防范以下两类攻击：\n路径遍历 (Directory Traversal)： 攻击者可能构造文件名 ../../etc/passwd。 防御：不直接使用 file.filename，而是自行生成 UUID 作为文件名，或使用 os.path.basename 进行清洗。 文件类型伪造： 攻击者可能将 .exe 重命名为 .jpg。 防御：不要仅通过 content_type 判断。应读取文件头（Magic Number）校验真实类型（可使用 python-magic 库）。 import uuid # 安全的文件名生成示例 file_ext = file.filename.split(\u0026#34;.\u0026#34;)[-1] safe_filename = f\u0026#34;{uuid.uuid4()}.{file_ext}\u0026#34; 总结 # 协议规范：Header 会自动处理 HTTP 头的大小写规范，Cookie 仅用于非敏感状态保持。 内存安全：处理文件上传时，严禁使用 bytes 类型接收大文件，必须使用 UploadFile。 异步 IO：在保存文件时，优先采用异步分块读写策略，避免阻塞 Event Loop。 安全防御：不仅要关注上传功能实现，更要处理文件名清洗和文件内容校验，防止恶意文件攻击。 ","date":"2025-04-27","externalUrl":null,"permalink":"/blogs/python/fastapi/fastapi-04/","section":"Blog","summary":"","title":"FastAPI：协议级数据处理（Header/Cookie/File）","type":"blogs"},{"content":" 依赖注入（Dependency Injection, DI）是 FastAPI 解耦架构的核心。本文将深入 DI 的执行原理，重点分析带有 yield 的依赖项如何优雅地管理资源（如数据库连接）的生命周期，以及如何在类与函数之间选择合适的依赖形式。 1. 依赖注入的设计哲学 # 在传统开发中，常在函数内部手动初始化资源（如 db = Session()），这导致代码耦合度高、难以测试。FastAPI 的 DI 系统允许将“需要什么”声明在函数参数中，由框架负责“如何构建”和“何时注入”。\n1.1 Depends 的工作流 # 当请求到达路由时，FastAPI 执行以下步骤：\n解析路由函数签名的 Depends 参数。 查找该依赖项是否已在缓存中（默认 use_cache=True）。 如果未缓存，执行依赖函数，获取返回值。 将返回值注入到路由函数中。 关键点：如果依赖项依赖于其他依赖项，FastAPI 会构建“依赖图（Dependency Graph）”，并按拓扑顺序自底向上执行。 2. 资源生命周期管理：Yield 依赖 # 这是工程实践中最重要的模式。对于数据库会话、网络连接等需要“创建 -\u0026gt; 使用 -\u0026gt; 销毁”的资源，普通的 return 依赖无法处理“销毁”逻辑。FastAPI 借鉴了 pytest 的 fixture 机制，支持包含 yield 的依赖项。\n2.1 数据库 Session 管理的标准写法 # # database.py from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker engine = create_engine(\u0026#34;postgresql://user:pass@localhost/db\u0026#34;) SessionLocal = sessionmaker(bind=engine) # 依赖项定义 def get_db(): db = SessionLocal() # 1. Setup: 创建连接 try: yield db # 2. Inject: 注入给路由函数，并暂停执行 finally: db.close() # 3. Teardown: 路由执行完毕后（无论成功或异常），恢复执行关闭连接 2.2 在路由中使用 # from sqlalchemy.orm import Session from fastapi import Depends @app.get(\u0026#34;/users/\u0026#34;) async def read_users(db: Session = Depends(get_db)): # 此时 db 是一个打开的数据库会话 users = db.query(User).all() return users # 函数返回后，get_db 中的 finally 块被触发，连接被归还/关闭 这种模式完美替代了传统的中间件或装饰器，且具有类型感知能力，是 FastAPI 处理事务范围（Transaction Scope）的标准方式。\n3. 依赖形式：函数 vs 类 # FastAPI 允许使用函数或类作为依赖项。两者在底层机制上一致（都是 Callable），但在工程组织上各有优势。\n3.1 函数依赖 (Function Dependency) # 适用于简单的逻辑复用，如获取当前用户、提取分页参数。\nasync def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100): return {\u0026#34;q\u0026#34;: q, \u0026#34;skip\u0026#34;: skip, \u0026#34;limit\u0026#34;: limit} @app.get(\u0026#34;/items/\u0026#34;) async def read_items(commons: Annotated[dict, Depends(common_parameters)]): return commons 3.2 类依赖 (Class Dependency) # 当依赖项参数较多，或需要利用继承特性时，使用类更为清晰。\nclass CommonQueryParams: def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100): self.q = q self.skip = skip self.limit = limit @app.get(\u0026#34;/items/\u0026#34;) async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]): # 此处 commons 是 CommonQueryParams 的实例 # 享有完整的 IDE 属性提示 return commons.dict() 简写技巧：如果依赖项是类，可以简写为 commons: Annotated[CommonQueryParams, Depends()]，FastAPI 会自动推断使用 CommonQueryParams 类本身。 4. 全局依赖与子依赖 # 4.1 全局依赖 (Global Dependencies) # 某些逻辑（如 API Key 校验、IP 白名单）需要应用于整个应用。可以在 FastAPI 或 APIRouter 初始化时注入。\nasync def verify_token(x_token: str = Header()): if x_token != \u0026#34;fake-super-secret-token\u0026#34;: raise HTTPException(status_code=400, detail=\u0026#34;X-Token header invalid\u0026#34;) # 应用于整个 App app = FastAPI(dependencies=[Depends(verify_token)]) # 或应用于特定 Router user_router = APIRouter(dependencies=[Depends(verify_token)]) 注意：全局依赖的返回值通常会被忽略，它们主要用于执行副作用（Side Effects）或抛出异常。\n4.2 缓存机制 (Cache) # 默认情况下，如果一个请求的依赖图中多次出现同一个依赖项，FastAPI 只会执行一次，并在同一次请求中复用返回值。\n场景：get_current_user 依赖 get_token，get_user_permissions 也依赖 get_token。FastAPI 保证 get_token 只执行一次。 禁用缓存：如果依赖项需要每次都重新计算（不常见），可设置 Depends(dep, use_cache=False)。 总结 # 资源管理：必须掌握 yield 依赖模式，它是数据库连接、Redis 客户端等资源安全关闭的基石。 依赖图：理解依赖的传递性和拓扑执行顺序，有助于设计复杂的权限验证系统。 类型选择：简单逻辑用函数，复杂参数组合用类，保持代码结构的清晰。 复用策略：利用 use_cache=True（默认）避免重复计算，提高单次请求的处理效率。 ","date":"2025-04-27","externalUrl":null,"permalink":"/blogs/python/fastapi/fastapi-05/","section":"Blog","summary":"","title":"FastAPI：依赖注入系统","type":"blogs"},{"content":"","date":"2025-04-27","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"2025-03-22","externalUrl":null,"permalink":"/tags/think-tool/","section":"Tags","summary":"","title":"Think-Tool","type":"tags"},{"content":" engineering/claude-think-tool 一种能提升 Claude 解决复杂问题能力的新工具 在我们持续增强 Claude 解决复杂问题能力的过程中，我们发现了一种特别有效的方法：一个“思考”工具（\u0026ldquo;think\u0026rdquo; tool）。该工具在处理复杂任务时，为结构化思考创造了一个专门的空间。\n这项技术虽然简单但功能强大——我们稍后会解释，它不同于 Claude 新的“扩展思考”能力——它显著提升了 Claude 在作为代理（agent）使用工具时的能力。这包括遵守策略、做出一致的决策以及处理多步骤问题，所有这些都只需极低的实现开销。\n在本文中，我们将探讨如何在不同的应用程序中实现“思考”工具，并基于经过验证的基准测试结果，为开发者分享实践指导。\n什么是“思考”工具？ # 通过“思考”工具，我们赋予了 Claude 在得出最终答案的过程中，增加一个额外思考步骤的能力——这个步骤有其自己指定的空间。\n虽然这听起来与“扩展思考”（extended thinking）相似，但它们是不同的概念。“扩展思考”关注的是 Claude 在开始生成回应之前所做的事情。通过“扩展思考”，Claude 会在采取行动前深入思考并迭代其计划。而“思考”工具则是让 Claude 在开始生成回应后，增加一个步骤来停下来思考它是否掌握了继续前进所需的所有信息。这在执行长链条的工具调用或与用户进行长时间的多步骤对话时尤其有用。\n这使得“思考”工具更适用于 Claude 仅凭用户查询无法获得 formulating a response所需全部信息，并且需要处理外部信息（例如，工具调用结果中的信息）的场景。Claude 使用“思考”工具进行的推理，其全面性不如“扩展思考”，它更侧重于模型发现的新信息。\n我们建议在更简单的工具使用场景中使用“扩展思考”，例如非序列化的工具调用或直接的指令遵循。“扩展思考”也适用于那些你不需要 Claude 调用工具的用例，如编码、数学和物理。而“思考”工具则更适合以下情况：Claude 需要调用复杂的工具，在长链条的工具调用中仔细分析工具输出，在充满详细指导、策略繁重的环境中导航，或者做出序列化的决策（其中每一步都建立在前一步的基础上，且错误代价高昂）。\n以下是一个使用 τ-Bench 标准工具规范格式的示例实现：\n{ \u0026#34;name\u0026#34;: \u0026#34;think\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;使用此工具进行思考。它不会获取新信息或更改数据库，只是将思考内容附加到日志中。在需要复杂推理或某种缓存记忆时使用。\u0026#34;, \u0026#34;input_schema\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;thought\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;需要思考的内容。\u0026#34; } }, \u0026#34;required\u0026#34;: [\u0026#34;thought\u0026#34;] } } 在 τ-Bench 上的性能表现 # 我们使用 τ-bench (tau-bench) 对“思考”工具进行了评估。这是一个全面的基准测试，旨在测试模型在真实客服场景中使用工具的能力，其中“思考”工具是评估标准环境的一部分。\nτ-bench 评估 Claude 在以下方面的能力：\n与模拟用户进行真实的对话导航 始终如一地遵循复杂的客服代理策略指南 使用多种工具访问和操作环境数据库 τ-bench 中使用的主要评估指标是 pass^k，它衡量的是对于一个给定的任务，所有 k 次独立任务试验都成功的概率，并在所有任务中取平均值。与其它 LLM 评估中常见的 pass@k 指标（衡量 k 次试验中至少有一次成功）不同，pass^k 评估的是一致性和可靠性——这对于客服应用至关重要，因为在这些应用中，持续遵守策略是必不可少的。\n性能分析 # 我们的评估比较了几种不同的配置：\n基准 (无“思考”工具，无“扩展思考”模式) 仅扩展思考模式 仅“思考”工具 带优化提示的“思考”工具 (针对航空领域) 结果显示，当 Claude 3.7 在基准测试的“航空”和“零售”客服领域有效使用“思考”工具时，性能得到了显著提升：\n航空领域: 带有优化提示的“思考”工具在 pass^1 指标上达到了 0.570，而基准仅为 0.370——相对提升了 54%。 零售领域: 仅使用“思考”工具就达到了 0.812，而基准为 0.783。 Claude 3.7 Sonnet 在 τ-Bench “航空”领域的性能评估 Claude 3.7 Sonnet 在 τ-Bench “航空”领域评估中的表现\n配置 k=1 k=2 k=3 k=4 k=5 \u0026ldquo;思考\u0026rdquo; + 提示 0.584 0.444 0.384 0.356 0.340 \u0026ldquo;思考\u0026rdquo; 0.404 0.254 0.186 0.140 0.100 扩展思考 0.412 0.290 0.232 0.192 0.160 基准 0.332 0.206 0.148 0.116 0.100 在航空领域，最佳性能是通过将“思考”工具与一个优化的提示（prompt）相结合实现的，该提示给出了分析客户请求时可以使用的推理方法示例。以下是优化提示的示例：\n## 使用思考工具 在收到工具结果后，采取任何行动或回应用户之前，使用思考工具作为草稿来： - 列出适用于当前请求的具体规则 - 检查是否已收集所有必需信息 - 验证计划中的行动是否符合所有政策 - 迭代检查工具结果的正确性 以下是一些在思考工具内部可以迭代思考的例子： \u0026lt;think_tool_example_1\u0026gt; 用户想取消航班 ABC123 - 需要验证：用户ID，预订ID，原因 - 检查取消规则： * 是否在预订后24小时内？ * 如果不是，检查票务等级和保险 - 验证没有航段已飞行或已过期 - 计划：收集缺失信息，验证规则，获取确认 \u0026lt;/think_tool_example_1\u0026gt; \u0026lt;think_tool_example_2\u0026gt; 用户想预订3张去纽约的机票，每人2件托运行李 - 需要用户ID来检查： * 会员等级对应的行李额度 * 个人资料中有哪些支付方式 - 行李计算： * 经济舱 × 3位乘客 * 如果是普通会员：每人1件免费行李 → 额外3件行李 = $150 * 如果是银卡会员：每人2件免费行李 → 额外0件行李 = $0 * 如果是金卡会员：每人3件免费行李 → 额外0件行李 = $0 - 支付规则验证： * 最多1张旅行券，1张信用卡，3张礼品卡 * 所有支付方式必须在个人资料中 * 旅行券余额将作废 - 计划： 1. 获取用户ID 2. 验证会员等级以确定行李费 3. 检查个人资料中的支付方式及其组合是否允许 4. 计算总费用：票价 + 任何行李费 5. 获取明确的预订确认 \u0026lt;/think_tool_example_2\u0026gt; 特别有趣的是不同方法的比较。使用带优化提示的“思考”工具比“扩展思考”模式（其表现与未加提示的“思考”工具相似）取得了明显更好的结果。仅使用“思考”工具（无提示）比基准有所提升，但仍不及优化后的方法。\n“思考”工具与优化提示的结合，以显著优势取得了最强的性能，这可能是因为基准测试的航空公司策略部分高度复杂，模型从给定的“如何思考”的示例中获益最多。\n在零售领域，我们同样测试了多种配置以理解每种方法具体的影响。\nClaude 3.7 Sonnet τ-Bench “零售”领域评估 Claude 3.7 Sonnet 在 τ-Bench “零售”领域评估中的性能\n配置 k=1 k=2 k=3 k=4 k=5 \u0026ldquo;思考\u0026rdquo; + 无提示 0.812 0.735 0.685 0.650 0.626 扩展思考 0.770 0.681 0.623 0.581 0.548 基准 0.783 0.695 0.643 0.607 0.583 即便没有额外的提示，“思考”工具也取得了最高的 pass^1 分数 0.812。零售领域的策略显然比航空领域更容易导航，Claude 仅通过拥有一个思考空间就能得到提升，无需进一步指导。\n从 τ-Bench 分析中得出的关键洞见 # 我们的详细分析揭示了几个模式，可以帮助你有效地实现“思考”工具：\n在困难领域，提示至关重要。仅仅提供“思考”工具可能会在一定程度上提升性能，但将其与优化的提示相结合，在困难领域会产生显著更好的结果。然而，较简单的领域可能仅从拥有“思考”能力中就能获益。 跨试验的一致性得到改善。使用“思考”工具带来的提升在 pass^k（k 最高到5）中都得以保持，这表明该工具有助于 Claude 更有效地处理边缘案例和异常场景。 在 SWE-Bench 上的性能表现 # 在评估 Claude 3.7 Sonnet 时，一个类似的“思考”工具被添加到了我们的 SWE-bench 设置中，为其取得 0.623 的当前最佳成绩做出了贡献。调整后的“思考”工具定义如下：\n{ \u0026#34;name\u0026#34;: \u0026#34;think\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;使用此工具进行思考。它不会获取新信息或对代码仓库做任何更改，只是记录思考过程。在需要复杂推理或头脑风暴时使用。例如，如果你在探索代码库并发现了 bug 的来源，调用此工具来头脑风暴几种独特的修复方法，并评估哪种更改可能最简单有效。或者，如果你收到一些测试结果，调用此工具来头脑风暴如何修复失败的测试。\u0026#34;, \u0026#34;input_schema\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;thought\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;你的思考内容。\u0026#34; } }, \u0026#34;required\u0026#34;: [\u0026#34;thought\u0026#34;] } } 我们的实验（n=30 个样本使用“思考”工具，n=144 个样本不使用）显示，单独包含此工具的效果平均提升了 1.6% 的性能。\n何时使用“思考”工具 # 基于这些评估结果，我们确定了 Claude 能从“思考”工具中获益最多的具体场景：\n工具输出分析。当 Claude 在行动前需要仔细处理前一个工具调用的输出，并可能需要回溯其方法时。 策略繁重的环境。当 Claude 需要遵循详细的指导方针并验证合规性时。 序列化决策。当每个行动都建立在前一个行动之上，且错误代价高昂时（常见于多步骤领域）。 实施最佳实践 # 为了充分利用 Claude 的“思考”工具，我们根据 τ-bench 实验推荐以下实施实践。\n使用特定领域的示例进行策略性提示 提供关于何时以及如何使用“思考”工具的明确指令，是最有效的方法，就像用于 τ-bench 航空领域的那个一样。提供针对你特定用例的示例，能显著提高模型使用“思考”工具的效率： 推理过程中期望的详细程度； 如何将复杂指令分解为可操作的步骤； 处理常见场景的决策树； 如何检查是否已收集所有必要信息。 将复杂的指导放在系统提示中 我们发现，当关于“思考”工具的指令很长和/或复杂时，将其包含在系统提示中比放在工具描述本身更有效。这种方法提供了更广阔的上下文，并帮助模型更好地将思考过程融入其整体行为中。 何时不使用“思考”工具 # 尽管“思考”工具可以带来显著的改进，但它并不适用于所有的工具使用场景，并且会增加提示长度和输出 token 的成本。具体来说，我们发现在以下用例中，“思考”工具没有任何改进：\n非序列化工具调用。如果 Claude 只需要进行单个工具调用或多个并行调用来完成任务，增加“思考”不太可能有任何改进。 简单的指令遵循。当没有太多约束需要 Claude 遵守，且其默认行为已足够好时，额外的“思考”不太可能带来收益。 开始使用 # “思考”工具是对你的 Claude 实现的一个直接补充，只需几个步骤就能带来有意义的改进：\n在代理式工具使用场景中进行测试。从具有挑战性的用例开始——那些 Claude 目前在遵守策略或在长工具调用链中进行复杂推理时遇到困难的场景。 添加工具定义。实现一个为你的领域定制的“思考”工具。它只需要最少的代码，但能实现更结构化的推理。同时，考虑在系统提示中加入关于何时以及如何使用该工具的说明，并附上与你领域相关的示例。 监控和优化。观察 Claude 在实践中如何使用该工具，并调整你的提示以鼓励更有效的思考模式。 最好的部分是，添加这个工具在性能结果方面几乎没有负面影响。除非 Claude 决定使用它，否则它不会改变外部行为，也不会干扰你现有的工具或工作流程。\n结论 # 我们的研究表明，“思考”工具可以显著提升 Claude 3.7 Sonnet¹ 在需要遵守策略和在长链条工具调用中进行推理的复杂任务上的性能。“思考”并非万能解决方案，但对于正确的用例，它能带来巨大的好处，且实现复杂度极低。\n我们期待看到您将如何使用“思考”工具，来构建更强大、更可靠、更透明的 AI 系统。\n¹ 虽然我们的 τ-Bench 结果侧重于 Claude 3.7 Sonnet 使用“思考”工具的改进，但我们的实验表明，Claude 3.5 Sonnet（新版）也能通过与 3.7 Sonnet 相同的配置实现性能增益，这表明这种改进也适用于其他 Claude 模型。\n","date":"2025-03-22","externalUrl":null,"permalink":"/blogs/agent/claude-think-tool-translate/","section":"Blog","summary":"","title":"译文-“思考”工具：使Claude在复杂的工具使用情况下能够停止并思考-Anthropic","type":"blogs"},{"content":" engineering/building-effective-agents 在过去的一年里，我们与数十个跨行业构建大型语言模型（LLM）Agent 的团队进行了合作。我们发现，最成功的实施案例无一例外地使用了简单、可组合的模式，而非复杂的框架。\n在这篇文章中，我们将分享与客户合作以及我们自己构建 Agent 时学到的经验，并为开发者提供构建高效 Agent 的实用建议。\n什么是 Agent？ # “Agent”可以有多种定义。一些客户将 Agent 定义为能够长时间独立运行、使用各种工具完成复杂任务的完全自主系统。另一些客户则用这个词来描述遵循预定义工作流程、更具规范性的实施方案。在 Anthropic，我们将这些变体都归类为 Agent 系统，但在架构上对“工作流”和“Agent”做了重要区分：\n工作流（Workflows）：LLM 和工具通过预定义的代码路径进行编排的系统。 Agent：LLM 动态指导自身流程和工具使用，并保持对任务完成方式控制权的系统。 接下来，我们将详细探讨这两种 Agent 系统。在附录 1（“实践中的 Agent”）中，我们描述了客户在使用这些系统中发现特别有价值的两个领域。\n何时（以及何时不）使用 Agent # 在使用 LLM 构建应用程序时，我们建议尽可能寻找最简单的解决方案，仅在需要时才增加复杂性。这可能意味着根本不构建 Agent 系统。Agent 系统通常以延迟和成本换取更好的任务性能，您应考虑这种权衡在何时有意义。\n当确实需要更高复杂性时，对于定义明确的任务，工作流提供了可预测性和一致性；而当需要大规模的灵活性和模型驱动的决策时，Agent 是更好的选择。然而，对于许多应用来说，通过检索和上下文示例来优化单次 LLM 调用通常就足够了。\n何时以及如何使用框架 # 有许多框架可以使 Agent 系统的实现变得更容易，包括：\nLangChain 的 LangGraph Amazon Bedrock 的 AI Agent 框架 Rivet，一个拖放式 GUI LLM 工作流构建器 Vellum，另一个用于构建和测试复杂工作流的 GUI 工具 这些框架通过简化标准的底层任务（如调用 LLM、定义和解析工具、将调用链接在一起）使入门变得容易。然而，它们常常创建额外的抽象层，这可能会掩盖底层的提示和响应，使调试变得更加困难。它们也可能诱使你在本可以用更简单设置解决问题时增加不必要的复杂性。\n我们建议开发者从直接使用 LLM API 开始：许多模式可以用几行代码实现。如果您确实使用了框架，请确保您理解其底层代码。对底层机制的错误假设是客户出错的常见原因。\n请参阅cookbook获取一些示例实现。\n构建块、工作流和 Agent # 在本节中，我们将探讨在生产中看到的 Agent 系统的常见模式。我们将从我们的基础构建块——增强型 LLM——开始，并逐步增加复杂性，从简单的组合式工作流到自主 Agent。\n构建块：增强型 LLM # Agent 系统的基本构建块是经增强的 LLM，这些增强功能包括检索、工具和记忆。我们目前的模型可以主动使用这些功能——生成自己的搜索查询、选择合适的工具，并决定保留哪些信息。\nThe augmented LLM 我们建议在实现中关注两个关键方面：根据您的具体用例定制这些功能，并确保它们为您的 LLM 提供一个简单、文档齐全的接口。\n在本文的其余部分，我们假设每次 LLM 调用都可以访问这些增强功能。\n工作流：提示链 (Prompt Chaining) # 提示链将一个任务分解为一系列步骤，其中每个 LLM 调用处理前一个调用的输出。您可以在任何中间步骤上添加程序化检查（见下图中的“gate”），以确保流程仍在正轨上。\nThe prompt chaining workflow 何时使用此工作流：此工作流非常适合可以轻松、清晰地分解为固定子任务的情况。其主要目标是通过使每次 LLM 调用成为一个更容易的任务来换取更高的准确性，尽管这会增加延迟。\n示例：\n生成营销文案，然后将其翻译成另一种语言。 编写一份文档大纲，检查大纲是否满足某些标准，然后根据大纲撰写文档。 工作流：路由 (Routing) # 路由对输入进行分类，并将其导向一个专门的后续任务。此工作流允许关注点分离，并构建更专业的提示。没有此工作流，针对一种输入的优化可能会损害在其他输入上的性能。\nThe routing workflow 何时使用此工作流：当存在可以分开处理的明确类别，并且分类可以被 LLM 或更传统的分类模型/算法准确处理时，路由效果很好。\n示例：\n将不同类型的客户服务查询（一般问题、退款请求、技术支持）引导到不同的下游流程、提示和工具中。 将简单/常见的问题路由到像 Claude 3.5 Haiku 这样较小的模型，而将困难/不寻常的问题路由到像 Claude 3.5 Sonnet 这样能力更强的模型，以优化成本和速度。 工作流：并行化 (Parallelization) # LLM 有时可以同时处理一个任务，并以编程方式汇总它们的输出。这种工作流，即并行化，主要有两种变体：\n分段 (Sectioning)：将任务分解为并行运行的独立子任务。 投票 (Voting)：多次运行同一任务以获得多样化的输出。 The parallelization workflow 何时使用此工作流：当划分的子任务可以为了速度而并行化，或者当需要多个视角或尝试以获得更高置信度的结果时，并行化是有效的。对于具有多种考虑因素的复杂任务，如果每个考虑因素都由单独的 LLM 调用处理，LLM 通常表现更好。\n示例：\n分段：实施安全护栏，其中一个模型实例处理用户查询，而另一个实例筛选不当内容或请求。 分段：自动化评估 LLM 性能，其中每个 LLM 调用评估模型在给定提示上性能的不同方面。 投票：审查一段代码是否存在漏洞，其中几个不同的提示会审查并标记代码中发现的问题。 工作流：编排者-工作者 (Orchestrator-Workers) # 在编排者-工作者工作流中，一个中央 LLM 动态地分解任务，将它们委托给工作者 LLM，并综合它们的结果。\nThe orchestrator-workers workflow 何时使用此工作流：此工作流非常适合您无法预测所需子任务的复杂任务。与并行化的关键区别在于其灵活性——子任务不是预先定义的，而是由编排者根据具体输入确定的。\n示例：\n每次都需要对多个文件进行复杂更改的编码产品。 涉及从多个来源收集和分析信息以寻找可能相关信息的搜索任务。 工作流：评估者-优化者 (Evaluator-Optimizer) # 在评估者-优化者工作流中，一个 LLM 调用生成响应，而另一个在循环中提供评估和反馈。\n评估者-优化者工作流 何时使用此工作流：当我们有明确的评估标准，并且迭代改进能提供可衡量的价值时，此工作流特别有效。这类似于人类作者在撰写精炼文档时经历的迭代写作过程。\n示例：\n文学翻译，其中翻译者 LLM 最初可能无法捕捉到细微差别，但评估者 LLM 可以提供有用的评论。 复杂的搜索任务，需要多轮搜索和分析以收集全面信息，由评估者决定是否需要进一步搜索。 Agents # 随着 LLM 在关键能力（理解复杂输入、进行推理和规划、可靠地使用工具以及从错误中恢复）上的成熟，Agent 开始在生产环境中出现。Agent 通过用户的命令或互动讨论开始工作。一旦任务明确，Agent 便独立规划和操作，可能会返回给人类以获取更多信息或判断。\n自主 Agent 何时使用 Agent：Agent 可用于开放式问题，这些问题难以或不可能预测所需步骤数量，也无法硬编码固定路径。Agent 的自主性使其成为在受信任环境中扩展任务的理想选择。\nAgent 的自主性意味着更高的成本和潜在的复合错误。我们建议在沙盒环境中进行广泛测试，并配备适当的安全护栏。\n示例：\n一个用于解决 SWE-bench 任务的编码 Agent，这些任务涉及根据任务描述对多个文件进行编辑。 我们的“计算机使用”参考实现，其中 Claude 使用计算机来完成任务。 High-level flow of a coding agent 结合和定制这些模式 # 这些构建模块并非规定性的。它们是开发者可以塑造和组合以适应不同用例的常见模式。对于任何LLM功能来说，成功的关键在于衡量性能并在实现上进行迭代。重复一遍：只有当它明显改善结果时，你才应该考虑增加复杂性。\n总结 # 在 LLM 领域取得成功，关键不在于构建最复杂的系统，而在于根据您的需求构建正确的系统。从简单的提示开始，通过全面的评估对其进行优化，只有当更简单的解决方案无法满足需求时，才添加多步 Agent 系统。\n在实施 Agent 时，我们遵循三个核心原则：\n保持设计的简单性。 通过明确展示 Agent 的规划步骤来优先考虑透明度。 通过详尽的工具文档和测试，精心打造您的 Agent-计算机接口（ACI）。 通过遵循这些原则，您可以创建不仅功能强大，而且可靠、可维护并受用户信任的 Agent。\n附录 1：实践中的 Agent # 我们与客户的合作揭示了 AI Agent 两个特别有前景的应用，它们展示了上述模式的实用价值。\nA. 客户支持 客户支持将熟悉的聊天机器人界面与通过工具集成增强的功能相结合。这是一个非常适合更开放式 Agent 的领域，因为：\n支持互动自然地遵循对话流程，同时需要访问外部信息和执行操作。 可以集成工具来拉取客户数据、订单历史和知识库文章。 可以以编程方式处理退款或更新工单等操作。 成功与否可以通过用户定义的解决方案来明确衡量。 B. 编码 Agent 软件开发领域已显示出 LLM 功能的巨大潜力，其能力从代码补全发展到自主解决问题。Agent 在此特别有效，因为：\n代码解决方案可通过自动化测试进行验证。 Agent 可以利用测试结果作为反馈来迭代解决方案。 问题空间定义明确且结构化。 产出质量可以客观衡量。 附录 2：为您的工具进行提示工程 # 无论您在构建哪种 Agent 系统，工具都可能是您 Agent 的重要组成部分。工具使 Claude 能够通过在我们的 API 中指定其确切结构和定义来与外部服务和 API 交互。\n我们为决定工具格式提出的建议如下：\n给模型足够的 token 在“思考”后再行动。 保持格式接近于模型在互联网上自然见过的文本格式。 确保没有格式上的“开销”，例如必须保持数千行代码的准确计数，或对其编写的任何代码进行字符串转义。 一个经验法则是，思考一下投入到人机界面（HCI）中的精力，并计划投入同样多的精力来创建优秀的 Agent-计算机接口（ACI）。以下是一些想法：\n设身处地为模型着想：根据描述和参数，使用这个工具是否显而易见？如果需要仔细思考，那么对模型来说可能也是如此。 测试模型如何使用您的工具：在我们的工作台中运行许多示例输入，看看模型会犯什么错误，然后进行迭代。 Poka-yoke（防错）您的工具：更改参数，使其更难出错。例如，我们发现当 Agent 移出根目录后，模型在使用相对文件路径的工具时会出错。为了解决这个问题，我们更改了工具，使其始终需要绝对文件路径——我们发现模型完美地使用了这个方法。 ","date":"2024-12-21","externalUrl":null,"permalink":"/blogs/agent/building-effective-agents-translate/","section":"Blog","summary":"","title":"构建高效AI Agent-Anthropic","type":"blogs"},{"content":" 原文：engineering/contextual-retrieval\nTL;DR # Anthropic提出了一种RAG改进方法——Contextual Retrieval，通过使用Claude自动为文档块添加背景上下文，将检索失败率降低了49%（结合重排序可降低67%）。该方法成本极低（每百万token仅$1.02），能显著提升大规模知识库的检索准确性，适用于需要精确检索特定领域信息的AI应用。\n一、核心问题：传统RAG的上下文丢失 # 传统RAG系统为了高效检索会将文档分割成小块（chunks），但这会导致关键上下文信息丢失：\n示例场景：在SEC财报文件中，一个文本块只写着\u0026quot;公司收入较上季度增长3%\u0026quot;，但没有说明是哪家公司、哪个季度。当用户查询\u0026quot;ACME公司2023年Q2收入增长多少\u0026quot;时，系统很难准确检索到这个信息。\n二、解决方案：Contextual Retrieval # Anthropic提出的方法是在嵌入之前，为每个文档块自动添加特定上下文信息。包含两个子技术：\n1. Contextual Embeddings（上下文嵌入） # 在生成向量嵌入前，为每个块添加解释性上下文 上下文通常50-100个token，说明该块的来源和背景 2. Contextual BM25（上下文BM25） # 同样在建立BM25索引前添加相同上下文 结合精确关键词匹配和语义搜索的优势 改造示例：\n原始块：\u0026#34;公司收入较上季度增长3%\u0026#34; 改造后：\u0026#34;该块来自ACME公司2023年Q2的SEC财报文件；上季度收入为3.14亿美元。公司收入较上季度增长3%\u0026#34; 三、性能提升效果 # 实验基于多个知识领域（代码库、小说、ArXiv论文等）测试，使用1 - recall@20（前20个结果中未检索到相关文档的失败率）作为指标：\n技术方案 检索失败率 改善幅度 传统RAG（嵌入+BM25） 5.7% 基准线 + Contextual Embeddings 3.7% ↓35% + Contextual Embeddings \u0026amp; BM25 2.9% ↓49% + 再增加Reranking重排序 1.9% ↓67% 结论：所有技术叠加使用效果最好，失败率从5.7%降至1.9%。\n核心洞察：RAG的失败往往不在于检索算法，而在于检索对象本身缺乏足够上下文。通过低成本自动化添加上下文，可以显著提升AI系统回答特定领域问题的准确性。\n原文译文 # 要让 AI 模型在特定语境下发挥作用，它往往需要获取背景知识。\n要让 AI 模型在特定语境下发挥作用，它往往需要获取背景知识。例如，客户支持聊天机器人需要了解其服务对象的特定业务知识，而法律分析机器人需要了解大量的过往案例。\n开发人员通常使用检索增强生成 (Retrieval-Augmented Generation, RAG) 来增强 AI 模型的知识。RAG 是一种从知识库中检索相关信息并将其附加到用户提示词 (prompt) 中的方法，它能显著提升模型的回答质量。问题在于，传统的 RAG 解决方案在对信息进行编码时会丢失上下文，这导致系统往往无法从知识库中检索到相关信息。\n在这篇文章中，我们将概述一种能显著改进 RAG 检索步骤的方法。该方法被称为“上下文检索 (Contextual Retrieval)”，它使用了两种子技术：上下文嵌入 (Contextual Embeddings) 和 上下文 BM25 (Contextual BM25)。该方法可以将检索失败的数量减少 49%，如果结合重排序 (reranking) 技术，则可减少 67%。这代表了检索准确性的显著提升，并直接转化为下游任务中更好的性能表现。\n您可以使用 Claude 通过我们的 Cookbook 轻松部署您自己的上下文检索 (Contextual Retrieval) 解决方案。\n关于仅使用更长提示词的说明 # 有时最简单的解决方案就是最好的。如果您的知识库小于 200,000 个 Token（约 500 页材料），您可以直接将整个知识库包含在给模型的提示词中，无需使用 RAG 或类似方法。\n几周前，我们发布了针对 Claude 的 提示词缓存 (Prompt Caching)，这使得该方法明显更快且更具成本效益。开发人员现在可以在 API 调用之间缓存常用的提示词，将延迟降低 2 倍以上，并将成本降低高达 90%（您可以通过阅读我们的 提示词缓存 Cookbook 了解其工作原理）。\n然而，随着您的知识库不断增长，您将需要更具扩展性的解决方案。这正是上下文检索 (Contextual Retrieval) 发挥作用的地方。\nRAG 入门：扩展至更大的知识库 # 对于无法放入上下文窗口 (context window) 的较大型知识库，RAG 是典型的解决方案。RAG 通过以下步骤对知识库进行预处理：\n将知识库（文档“语料库”）分解为较小的文本块 (chunks)，通常不超过几百个 Token； 使用嵌入模型 (embedding model) 将这些文本块转换为编码了含义的向量嵌入； 将这些嵌入存储在允许通过语义相似度 (semantic similarity) 进行搜索的向量数据库 (vector database) 中。 在运行时，当用户向模型输入查询时，向量数据库用于根据与查询的语义相似度查找最相关的文本块。然后，最相关的文本块会被添加到发送给生成式模型的提示词中。\n虽然嵌入模型擅长捕捉语义关系，但它们可能会错过关键的精确匹配。幸运的是，有一项较老的技术可以在这些情况下提供帮助。BM25 (Best Matching 25) 是一种排名函数，它使用词汇匹配 (lexical matching) 来查找精确的单词或短语匹配。它对于包含唯一标识符或技术术语的查询特别有效。\nBM25 建立在 TF-IDF (词频-逆文档频率) 概念之上。TF-IDF 衡量一个词对集合中某个文档的重要性。BM25 对此进行了改进，它考虑了文档长度并对词频应用了饱和函数，这有助于防止常见词主导结果。\n以下是 BM25 如何在语义嵌入失败的地方取得成功的示例：假设用户在技术支持数据库中查询 \u0026ldquo;Error code TS-999\u0026rdquo;。嵌入模型可能会找到关于一般错误代码的内容，但可能会错过精确的 \u0026ldquo;TS-999\u0026rdquo; 匹配。BM25 会寻找这个特定的文本字符串来识别相关文档。\nRAG 解决方案可以通过结合嵌入和 BM25 技术，使用以下步骤更准确地检索最适用的文本块：\n将知识库（文档“语料库”）分解为较小的文本块，通常不超过几百个 Token； 为这些文本块创建 TF-IDF 编码和语义嵌入； 使用 BM25 根据精确匹配查找排名靠前的文本块； 使用嵌入根据语义相似度查找排名靠前的文本块； 使用排名融合 (rank fusion) 技术组合并去重步骤 (3) 和 (4) 的结果； 将前 K 个 (top-K) 文本块添加到提示词中以生成回答。 通过同时利用 BM25 和嵌入模型，传统的 RAG 系统可以提供更全面和准确的结果，平衡了精确的术语匹配与更广泛的语义理解。\n一个标准的检索增强生成 (RAG) 系统，同时使用嵌入和 BM25 来检索信息。TF-IDF（词频-逆文档频率）衡量词的重要性并构成 BM25 的基础。 这种方法允许您以经济高效的方式扩展到巨大的知识库，远超单个提示词所能容纳的范围。但这些传统的 RAG 系统有一个显著的局限性：它们往往会破坏上下文。\n传统 RAG 中的上下文难题 (The context conundrum) # 在传统 RAG 中，文档通常被分割成较小的块以便高效检索。虽然这种方法对许多应用都很有效，但当单个文本块缺乏足够的上下文时，就会导致问题。\n例如，假设您的知识库中嵌入了一组财务信息（比如美国证券交易委员会 (SEC) 的备案文件），而您收到了以下问题：\u0026ldquo;What was the revenue growth for ACME Corp in Q2 2023?\u0026quot;（ACME 公司 2023 年第二季度的收入增长是多少？）\n一个相关的文本块可能包含以下文本：\u0026ldquo;The company\u0026rsquo;s revenue grew by 3% over the previous quarter.\u0026quot;（公司收入较上一季度增长了 3%。）然而，这个文本块本身并没有说明它指的是哪家公司或相关的时间段，这使得检索正确信息或有效使用该信息变得困难。\n介绍上下文检索 (Introducing Contextual Retrieval) # 上下文检索通过在嵌入（“上下文嵌入 (Contextual Embeddings)”）和创建 BM25 索引（“上下文 BM25 (Contextual BM25)”）之前，将块特定的解释性上下文前置到每个文本块中，从而解决了这个问题。\n让我们回到 SEC 备案文件集合的例子。以下是一个文本块可能被转换的示例：\noriginal_chunk = \u0026#34;The company\u0026#39;s revenue grew by 3% over the previous quarter.\u0026#34; contextualized_chunk = \u0026#34;This chunk is from an SEC filing on ACME corp\u0026#39;s performance in Q2 2023; the previous quarter\u0026#39;s revenue was $314 million. The company\u0026#39;s revenue grew by 3% over the previous quarter.\u0026#34; 值得注意的是，过去也有人提出过其他利用上下文改进检索的方法。其他的提议包括：向文本块添加通用文档摘要（我们进行了实验，发现增益非常有限）、假设性文档嵌入 (hypothetical document embedding) 和基于摘要的索引（我们评估后发现性能较低）。这些方法与本文提出的方法不同。\n实现上下文检索 # 当然，手动为知识库中成千上万甚至数百万个文本块添加注释的工作量太大了。为了实现上下文检索，我们将目光投向 Claude。我们编写了一个提示词，指示模型提供简洁的、针对特定文本块的上下文，利用整个文档的上下文来解释该文本块。我们使用以下 Claude 3 Haiku 提示词为每个文本块生成上下文：\n\u0026lt;document\u0026gt; {{WHOLE_DOCUMENT}} \u0026lt;/document\u0026gt; Here is the chunk we want to situate within the whole document \u0026lt;chunk\u0026gt; {{CHUNK_CONTENT}} \u0026lt;/chunk\u0026gt; Please give a short succinct context to situate this chunk within the overall document for the purposes of improving search retrieval of the chunk. Answer only with the succinct context and nothing else. 生成的上下文文本（通常为 50-100 个 Token）会在嵌入之前以及创建 BM25 索引之前被前置到文本块中。\n实际的预处理流程如下所示：\n上下文检索预处理流程图。上下文检索是一种提高检索准确性的预处理技术。 如果您有兴趣使用上下文检索，可以从我们的 Cookbook 开始。\n使用提示词缓存 (Prompt Caching) 降低上下文检索的成本 # 由于我们上面提到的特殊提示词缓存功能，使用 Claude 进行上下文检索具有独特的低成本优势。有了提示词缓存，您不需要为每个文本块都传入参考文档。您只需将文档加载到缓存中一次，然后引用之前缓存的内容。假设文本块为 800 Token，文档为 8k Token，上下文指令为 50 Token，每个文本块的上下文为 100 Token，那么生成上下文嵌入块的一次性成本为每百万文档 Token 1.02 美元。\n方法论 (Methodology) # 我们在各种知识领域（代码库、小说、ArXiv 论文、科学论文）、嵌入模型、检索策略和评估指标上进行了实验。我们在 附录 II 中包含了每个领域使用的一些问题和答案示例。\n下图展示了在使用表现最佳的嵌入配置 (Gemini Text 004) 并检索前 20 个文本块 (top-20-chunks) 时，所有知识领域的平均性能。我们使用 1 minus recall@20 作为评估指标，该指标衡量的是相关文档未能被检索到前 20 个文本块中的百分比。您可以在附录中看到完整结果——在我们评估的每一个嵌入源组合中，上下文处理都提高了性能。\n性能提升 # 我们的实验表明：\n上下文嵌入 (Contextual Embeddings) 将前 20 个文本块的检索失败率降低了 35% (5.7% → 3.7%)。 结合 上下文嵌入 和 上下文 BM25 (Contextual BM25) 将前 20 个文本块的检索失败率降低了 49% (5.7% → 2.9%)。 结合上下文嵌入和上下文 BM25 将前 20 个文本块的检索失败率降低了 49%。 实现注意事项 # 在实现上下文检索时，有几个注意事项需要牢记：\n分块边界 (Chunk boundaries)：考虑如何将文档分割成块。块的大小、边界和重叠的选择会影响检索性能。 嵌入模型 (Embedding model)：虽然上下文检索提高了我们需要测试的所有嵌入模型的性能，但某些模型可能比其他模型获益更多。我们发现 Gemini 和 Voyage 嵌入模型特别有效。 自定义上下文生成提示词 (Custom contextualizer prompts)：虽然我们提供的通用提示词效果很好，但通过针对您的特定领域或用例定制提示词（例如，包含可能仅在知识库的其他文档中定义的关键术语表），您可能会获得更好的结果。 块的数量 (Number of chunks)：向上下文窗口添加更多文本块会增加包含相关信息的机会。然而，过多的信息可能会分散模型的注意力，因此存在一个限度。我们尝试了提供 5、10 和 20 个文本块，发现使用 20 个是这些选项中性能最好的（参见附录进行比较），但这值得在您的用例中进行实验。 始终运行评估 (Always run evals)：通过传递上下文化的文本块并区分什么是上下文、什么是文本块，可能会改善响应生成。\n通过重排序 (Reranking) 进一步提升性能 # 在最后一步中，我们可以将上下文检索与另一种技术相结合，以进一步提升性能。在传统的 RAG 中，AI 系统搜索其知识库以找到潜在的相关信息块。对于大型知识库，这种初始检索通常会返回大量相关性和重要性各异的文本块——有时数以百计。\n重排序 (Reranking) 是一种常用的过滤技术，用于确保只有最相关的文本块被传递给模型。重排序提供了更好的响应，并降低了成本和延迟，因为模型处理的信息更少了。关键步骤如下：\n执行初始检索以获取排名靠前的潜在相关文本块（我们使用了前 150 个）； 将前 N 个 (top-N) 文本块连同用户的查询一起传递给重排序模型； 使用重排序模型，根据文本块与提示词的相关性和重要性给每个文本块打分，然后选择前 K 个 (top-K) 文本块（我们使用了前 20 个）； 将前 K 个文本块作为上下文传入模型以生成最终结果。 结合上下文检索和重排序以最大化检索准确性。 性能提升 # 市场上有几种重排序模型。我们使用 Cohere reranker 进行了测试。Voyage 也提供了一个重排序器，尽管我们没有时间测试它。我们的实验表明，在各个领域中，添加重排序步骤都能进一步优化检索。\n具体来说，我们发现 重排序后的上下文嵌入 (Reranked Contextual Embedding) 和 上下文 BM25 将前 20 个文本块的检索失败率降低了 67% (5.7% → 1.9%)。\n重排序后的上下文嵌入和上下文 BM25 将前 20 个文本块的检索失败率降低了 67%。 成本和延迟考量 # 重排序的一个重要考量是其对延迟和成本的影响，尤其是在重排序大量文本块时。因为重排序在运行时增加了一个额外的步骤，它不可避免地会增加少量的延迟，即使重排序器是并行对所有文本块进行评分的。在重排序更多文本块以获得更好性能与重排序更少文本块以降低延迟和成本之间，存在着固有的权衡 (trade-off)。我们建议针对您的具体用例尝试不同的设置，以找到适当的平衡点。\n结论 # 我们进行了大量的测试，比较了上述所有技术的不同组合（嵌入模型、BM25 的使用、上下文检索的使用、重排序器的使用以及检索的前 K 个结果的总数），涵盖了各种不同的数据集类型。以下是我们发现的摘要：\n嵌入 + BM25 优于单独使用嵌入； Voyage 和 Gemini 在我们测试的模型中拥有最好的嵌入； 向模型传递前 20 个文本块比仅传递前 10 个或前 5 个更有效； 向文本块添加上下文极大地提高了检索准确性； 重排序比不重排序要好； 所有这些优势是可以叠加的 (All these benefits stack)：为了最大化性能提升，我们可以结合上下文嵌入（来自 Voyage 或 Gemini）与上下文 BM25，加上一个重排序步骤，并将 20 个文本块添加到提示词中。 我们鼓励所有使用知识库的开发人员使用我们的 Cookbook 来试验这些方法，以解锁更高水平的性能。\n附录 I # 以下是各个数据集、嵌入提供商、除嵌入外是否使用 BM25、是否使用上下文检索以及是否使用重排序在 Retrievals @ 20 下的结果细分。\n请参阅 附录 II 查看 Retrievals @ 10 和 @ 5 的细分，以及每个数据集的示例问题和答案。\n各数据集和嵌入提供商的 1 minus recall @ 20 结果。 ","date":"2024-09-21","externalUrl":null,"permalink":"/blogs/rag/claude-contextual-retrieval-translate/","section":"Blog","summary":"","title":"Contextual Retrieval-Anthropic","type":"blogs"},{"content":"","date":"2022-11-14","externalUrl":null,"permalink":"/series/python/","section":"Series","summary":"","title":"Python","type":"series"},{"content":" Python 为开发者提供功能极其丰富且经过严格测试的标准库。在处理网络通信、文件操作、数据处理等无数常见任务时，无需寻找和安装第三方库，可以直接利用 Python 内置的强大工具。\n官方文档：https://docs.python.org/zh-cn/3.11/library/index.html\n操作系统与文件系统接口 # 这类模块是编写与操作系统交互的脚本和应用的基础。\nos 与 pathlib：与文件系统对话 # os 模块: 提供了与操作系统进行交互的底层接口。\nos.getcwd(): 获取当前工作目录。 os.chdir(path): 更改当前工作目录。 os.environ: 一个表示环境变量的字典。 os.path 子模块: 包含了处理路径名的核心函数，是跨平台编程的关键。 os.path.join(path, *paths): 强烈推荐。智能地拼接一个或多个路径部分，自动使用适合当前操作系统的路径分隔符 (/ 或 \\\\)。 os.path.exists(path): 判断路径是否存在。 os.path.splitext(path): 分离文件名和扩展名。 pathlib 模块 (Python 3.4+ 的现代选择): pathlib 提供了面向对象的接口来处理文件系统路径，代码更具表现力且更符合 Pythonic 风格。在现代 Python 开发中，应优先使用 pathlib 而不是 os.path。\nfrom pathlib import Path # 传统方式 (os.path) # import os # cwd = os.getcwd() # file_path = os.path.join(cwd, \u0026#39;data\u0026#39;, \u0026#39;config.json\u0026#39;) # 现代方式 (pathlib) p = Path.cwd() / \u0026#39;data\u0026#39; / \u0026#39;config.json\u0026#39; # 使用 / 运算符自然地拼接路径 print(f\u0026#34;Path exists: {p.exists()}\u0026#34;) print(f\u0026#34;Parent directory: {p.parent}\u0026#34;) print(f\u0026#34;File name: {p.name}\u0026#34;) print(f\u0026#34;File stem: {p.stem}\u0026#34;) # \u0026#39;config\u0026#39; print(f\u0026#34;File suffix: {p.suffix}\u0026#34;) # \u0026#39;.json\u0026#39; # 轻松读取和写入文件 # p.write_text(\u0026#34;Hello, world!\u0026#34;, encoding=\u0026#39;utf-8\u0026#39;) # content = p.read_text(encoding=\u0026#39;utf-8\u0026#39;) shutil：高级文件操作 # shutil 模块提供了对文件和文件集合的高级操作，是对 os 模块的补充。\nshutil.copyfile(src, dst): 复制文件内容（不包括元数据）。 shutil.copytree(src, dst): 递归地复制整个目录树。 shutil.move(src, dst): 移动文件或目录。 shutil.rmtree(path): 递归地删除整个目录树。 sys：与 Python 解释器交互 # sys 模块能够访问由解释器使用或维护的变量，并与解释器进行交互。\nsys.argv: 命令行参数列表，其中 sys.argv[0] 是脚本本身的名称。 sys.path: 一个字符串列表，指定了模块的搜索路径。 sys.platform: 标识当前操作系统平台（如 'win32', 'linux', 'darwin'）。 sys.exit(): 退出 Python 程序。 数据处理与持久化 # 文本处理：re (正则表达式) # re 模块是 Python 进行复杂文本模式匹配和处理的利器。\nre.search(pattern, string): 在字符串中搜索模式的第一次出现。 re.match(pattern, string): 从字符串的开头匹配模式。 re.findall(pattern, string): 找到所有不重叠的匹配项，并以列表形式返回。 re.sub(pattern, repl, string): 查找并替换。 re.compile(pattern): 将一个正则表达式模式编译成一个模式对象，当一个模式需要被重复使用时，这可以极大地提高性能。 import re # 从文本中提取所有邮箱地址 text = \u0026#34;Contact us at support@example.com or sales@example.org.\u0026#34; emails = re.findall(r\u0026#39;[\\\\w\\\\.-]+@[\\\\w\\\\.-]+\u0026#39;, text) print(emails) # -\u0026gt; [\u0026#39;support@example.com\u0026#39;, \u0026#39;sales@example.org\u0026#39;] 结构化数据：json, csv, sqlite3 # json: 用于处理 JSON (JavaScript Object Notation) 数据。是现代 Web API 和配置文件中最通用的数据交换格式。 json.dumps(obj): 将 Python 对象序列化为 JSON 格式的字符串。 json.loads(s): 将 JSON 格式的字符串反序列化为 Python 对象。 json.dump(obj, fp) / json.load(fp): 直接与文件对象进行读写。 csv: 用于读写 CSV (Comma-Separated Values) 文件。 csv.reader: 逐行读取 CSV 文件，每行返回一个字符串列表。 csv.DictReader: 更推荐。逐行读取，每行返回一个字典，将标题行的值作为键。 sqlite3: 一个实现了轻量级、无需服务器的 SQL 数据库引擎的接口。它允许在一个单一的文件中创建和管理一个完整的关系型数据库。 数据压缩：zlib, gzip, bz2, zipfile # 标准库提供了处理多种数据压缩格式的模块，允许直接读写压缩文件。\ngzip: 用于处理 .gz 文件。 zipfile: 用于处理 .zip 归档文件。 数学与数值计算 # math 与 random # math: 提供了对 C 语言标准定义的数学函数的访问，如 math.sin(), math.log(), math.sqrt() 以及 math.pi 和 math.e 等常数。 random: 实现了各种分布的伪随机数生成器。 random.random(): 返回 [0.0, 1.0) 范围内的随机浮点数。 random.randint(a, b): 返回一个 [a, b] 范围内的随机整数。 random.choice(seq): 从一个非空序列中随机选择一个元素。 random.shuffle(x): 对序列 x 进行原地随机排序。 注意：random 模块不应用于密码学安全相关的场景。应使用 secrets 模块。 高精度计算：decimal 与 fractions # 标准浮点数（float）存在精度问题（例如 0.1 + 0.2 并不精确等于 0.3）。这两个模块解决了这个问题。\ndecimal: 实现了定点和浮点数运算，精度可由用户指定。在金融和货币计算等要求精确十进制表示的场景中至关重要。 fractions: 实现了有理数算术，可以精确地表示分数。 from decimal import Decimal # 浮点数的不精确性 print(0.1 + 0.2) # -\u0026gt; 0.30000000000000004 # Decimal 的精确性 print(Decimal(\u0026#39;0.1\u0026#39;) + Decimal(\u0026#39;0.2\u0026#39;)) # -\u0026gt; 0.3 日期与时间：datetime # datetime 模块提供了用于处理日期和时间的类。\n核心对象: date, time, datetime, timedelta (表示两个日期或时间之间的差)。 常用操作: datetime.datetime.now(): 获取当前的本地日期和时间。 strftime(): 将 datetime 对象格式化为字符串。 strptime(): 将字符串解析为 datetime 对象。 使用 timedelta 进行日期算术。 from datetime import datetime, timedelta now = datetime.now() print(f\u0026#34;Current time: {now.strftime(\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)}\u0026#34;) three_days_ago = now - timedelta(days=3) print(f\u0026#34;Three days ago was: {three_days_ago.date()}\u0026#34;) 网络与互联网 # urllib.request：访问网络资源 # 提供了获取 URL 资源的基础接口。\n# from urllib.request import urlopen # with urlopen(\u0026#39;\u0026lt;http://worldtimeapi.org/api/ip\u0026gt;\u0026#39;) as response: # body = response.read() # print(json.loads(body)) 📌 实践建议: 虽然 urllib 功能齐全，但其 API 相对复杂。在实际项目中，绝大多数开发者会选择使用第三方库 requests，它提供了极其简洁和人性化的 API 来处理 HTTP 请求。但了解 urllib 仍然是理解底层网络操作的基础。\nsmtplib：发送电子邮件 # 实现了 SMTP (Simple Mail Transfer Protocol) 协议的客户端，用于发送邮件。\n开发工具与调试 # unittest 与 doctest：自动化测试 # unittest: Python 的官方测试框架，其风格类似于 Java 的 JUnit。它提供了丰富的断言方法和测试组织结构（测试用例、测试套件）。 doctest: 一个独特的模块，它会在文档字符串 (docstring) 中搜索看起来像交互式 Python 会话的文本，然后执行这些会话来验证代码。 logging：日志记录 # logging 模块是 Python 的标准日志记录工具，远比 print() 语句强大。\n五种日志级别: DEBUG, INFO, WARNING, ERROR, CRITICAL。 灵活性: 可以将日志输出到控制台、文件、网络套接字等。 配置化: 可以通过配置来控制日志的格式、级别和输出目标。 pdb：Python 调试器 # pdb 模块为 Python 程序提供了一个交互式的源代码调试器。可以在代码的任何位置设置断点 (import pdb; pdb.set_trace())，然后进入一个交互式环境，检查变量、单步执行代码。\n","date":"2022-11-14","externalUrl":null,"permalink":"/blogs/python/base/python-standard-library/","section":"Blog","summary":"","title":"Python 标准库","type":"blogs"},{"content":"","date":"2022-11-14","externalUrl":null,"permalink":"/tags/%E5%9F%BA%E7%A1%80/","section":"Tags","summary":"","title":"基础","type":"tags"},{"content":" 在 Python 的里，一切皆为对象。整数、字符串、列表，甚至函数，都是类的实例。类是创建对象的蓝图，它定义了一组属性（Attributes）和方法（Methods），封装了数据和操作数据的行为。\n类与实例剖析 # 类的定义与实例化 # 使用 class 关键字定义一个类。类名通常采用驼峰命名法 (CamelCase)。\nclass Dog: # 这是一个类属性，它被所有 Dog 类的实例共享 species = \u0026#34;Canis familiaris\u0026#34; # __init__ 是一个特殊的初始化方法（构造器） # 当一个实例被创建时，__init__ 会被自动调用 def __init__(self, name, age): # self 代表实例对象本身，必须作为方法的第一个参数 # 下面是实例属性，它们属于每个独立的实例 self.name = name self.age = age # 这是一个实例方法 def bark(self): return f\u0026#34;{self.name} says Woof!\u0026#34; 实例化是根据蓝图创建具体对象的过程。\nd1 = Dog(\u0026#34;Buddy\u0026#34;, 5) print(f\u0026#34;{d1.name} is {d1.age} years old.\u0026#34;) # -\u0026gt; Buddy is 5 years old. print(d1.bark()) # -\u0026gt; Buddy says Woof! self 的本质 # self 并非关键字，而是一个约定俗成的名称，指向实例对象本身。当你调用 d1.bark() 时，Python 解释器在内部会自动将其转换为 Dog.bark(d1)。self 使得方法能够访问和操作实例自身的属性和方法。\n类属性与实例属性 # 类属性 (Class Attribute): 在 class 代码块内、所有方法之外定义。被该类的所有实例共享。通常用于定义该类所有对象共有的、不变的特性。 实例属性 (Instance Attribute): 通常在 __init__ 方法中通过 self.attribute = value 定义。每个实例独有一份，用于描述每个对象特有的状态。 d2 = Dog(\u0026#34;Lucy\u0026#34;, 3) print(d1.species) # -\u0026gt; Canis familiaris print(d2.species) # -\u0026gt; Canis familiaris # 修改类属性会影响所有实例 Dog.species = \u0026#34;A friendly dog\u0026#34; print(d1.species) # -\u0026gt; A friendly dog 类方法：超越实例的范畴 # 并非所有与类相关的功能都必须绑定到具体的实例上。\n实例方法、类方法与静态方法 # 类型 装饰器 第一个参数 用途 实例方法 (无) self (实例) 操作或访问实例的状态（实例属性）。这是最常见的方法类型。 类方法 @classmethod cls (类) 操作或访问类的状态（类属性）。最常用于工厂方法，即创建类的实例的替代构造器。 静态方法 @staticmethod (无特定参数) 作为一个与类相关的工具函数，它不依赖于类或实例的状态。逻辑上属于这个类，但功能上是独立的。 实践范例：工厂模式 # class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day @classmethod def from_string(cls, date_string): \u0026#34;\u0026#34;\u0026#34;工厂方法：通过 \u0026#39;YYYY-MM-DD\u0026#39; 格式的字符串创建 Date 实例。\u0026#34;\u0026#34;\u0026#34; year, month, day = map(int, date_string.split(\u0026#39;-\u0026#39;)) return cls(year, month, day) # cls 就是 Date 类本身 @staticmethod def is_valid_date(date_string): \u0026#34;\u0026#34;\u0026#34;静态方法：一个独立的工具函数，用于检查日期字符串的格式。\u0026#34;\u0026#34;\u0026#34; try: year, month, day = map(int, date_string.split(\u0026#39;-\u0026#39;)) return True except (ValueError, TypeError): return False # 使用 d1 = Date(2025, 11, 4) d2 = Date.from_string(\u0026#34;2025-12-25\u0026#34;) # 使用类方法作为替代构造器，代码更具语义化 print(Date.is_valid_date(\u0026#34;2025-02-30\u0026#34;)) # True print(d2.year) # -\u0026gt; 2025 继承：类的层次结构 # 继承允许一个类（子类）获取另一个类（父类）的属性和方法，是实现代码重用的核心机制。\n基本继承与 super() # class Animal: def __init__(self, name): self.name = name def speak(self): raise NotImplementedError(\u0026#34;Subclass must implement this abstract method\u0026#34;) class Bulldog(Dog): # Bulldog 继承自 Dog def __init__(self, name, age, wrinkle_level): # super() 返回一个代理对象，让你能调用父类的方法。 # 这是实现方法扩展的标准做法，能正确处理复杂的多重继承。 super().__init__(name, age) self.wrinkle_level = wrinkle_level # 覆盖 (Override) 父类的 bark 方法 def bark(self): return f\u0026#34;{self.name} says Grrr!\u0026#34; 多重继承与方法解析顺序 (MRO) # Python 支持一个类从多个父类继承。当不同父类有同名方法时，Python 使用 C3 线性化算法来确定一个明确的方法解析顺序 (MRO)。\nclass A: pass class B(A): pass class C(A): pass class D(B, C): pass # 可以通过 __mro__ 属性或 mro() 方法查看 print(D.__mro__) # -\u0026gt; (\u0026lt;class \u0026#39;__main__.D\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.B\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.C\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.A\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;object\u0026#39;\u0026gt;) MRO 保证了属性查找顺序的确定性和一致性，即使在复杂的菱形继承结构中也是如此。\n说明：C3 线性化算法 C3 线性化算法（C3 Linearization Algorithm）是 Python（从 2.3 版本开始）用来确定多重继承中方法解析顺序（Method Resolution Order, MRO）的算法。它的核心任务是，当一个类继承自多个父类时，创建一个明确、一致且无歧义的方法和属性查找顺序列表。\n1. 为什么需要一个复杂的算法？—— “菱形问题”\n如果没有一个好的算法，多重继承会很快变得混乱。最经典的问题就是 “菱形问题” (The Diamond Problem)：\ngraph TD A[class A] --\u003e B[class B] A --\u003e C[class C] B --\u003e D[class D] C --\u003e D[class D] class A: def who_am_i(self): print(\u0026#34;I am an A\u0026#34;) class B(A): def who_am_i(self): print(\u0026#34;I am a B\u0026#34;) class C(A): def who_am_i(self): print(\u0026#34;I am a C\u0026#34;) class D(B, C): pass d = D() d.who_am_i() # ??? 当调用 d.who_am_i() 时，Python 应该调用哪个版本的方法？B 的还是 C 的？如果 B 和 C 都没有这个方法，应该只调用一次 A 的方法，还是两次？\n一个糟糕的 MRO 算法可能会导致：\n歧义：不知道该调用哪个方法。 冗余调用：多次调用同一个基类的方法。 不一致性：查找顺序违背了程序员的直觉。 C3 算法就是为了优雅地解决这些问题而生的。\n2. C3 算法的三个指导原则\nC3 算法之所以优秀，是因为它始终遵循三个关键原则：\n子类优先于父类 (Children First)：在 MRO 列表中，子类永远出现在其父类之前。 尊重父类顺序 (Parent Order Matters)：在定义类时，父类的顺序 (class D(B, C):) 会被保留。在 MRO 列表中，B 会出现在 C 之前。 单调性 (Monotonicity)：一个类的 MRO 是其所有父类 MRO 的延伸和扩展，而不会颠覆父类自身的继承顺序。这保证了继承链的一致性和可预测性。 3. C3 算法的工作原理\nC3 算法的核心思想是合并（merge）父类的 MRO 列表。其基本公式可以表示为：\nMRO(C) = [C] + merge(MRO(Parent1), MRO(Parent2), ..., [Parent1, Parent2, ...])\n这里的 merge 操作是关键，它的规则如下：\n从第一个父类的 MRO 列表的头部（第一个元素）开始。 检查这个头部元素是否出现在任何其他父类 MRO 列表的尾部（除头部外的所有元素）中。 如果“否”（它是一个“好头”），则将其从所有列表中取出，并添加到最终的 MRO 列表中。然后回到步骤 1。 如果“是”（它是一个“坏头”，意味着有其他类需要优先于它被解析），则跳过这个列表，去检查下一个父类 MRO 列表的头部。 重复以上过程，直到所有列表为空。如果无法找到一个“好头”但列表仍不为空，则意味着继承层次结构存在冲突，Python 会在定义类时就抛出 TypeError。 4. 实例演练：破解菱形问题\n让我们用 C3 算法手动计算上面菱形问题中 D 的 MRO。\n已知 MROs: (为简化，我们假设它们都继承自 object) MRO(object) = [object] MRO(A) = [A, object] MRO(B) = [B, A, object] MRO(C) = [C, A, object] 计算 MRO(D): MRO(D) = [D] + merge(MRO(B), MRO(C), [B, C]) 代入已知 MROs: merge([B, A, object], [C, A, object], [B, C]) 开始合并: 检查 B (第一个列表的头): B 没有出现在 [C, A, object] 的尾部，也没有出现在 [B, C] 的尾部。它是个好头。 取出 B。 当前 MRO: [D, B] 待合并列表: [A, object], [C, A, object], [C] 检查 A (第一个列表的头): A 出现在了 [C, A, object] 的尾部。它是个坏头。跳过。 检查 C (第二个列表的头): C 没有出现在 [A, object] 的尾部。它是个好头。 取出 C。 当前 MRO: [D, B, C] 待合并列表: [A, object], [A, object] 检查 A (第一个列表的头): A 没有出现在 [A, object] 的尾部。它是个好头。 取出 A。 当前 MRO: [D, B, C, A] 待合并列表: [object], [object] 检查 object (第一个列表的头): object 是个好头。 取出 object。 当前 MRO: [D, B, C, A, object] 待合并列表: [], [] (全部为空) 最终结果:MRO(D) = [D, B, C, A, object] 现在，让我们用 Python 验证一下：\n\u0026gt;\u0026gt;\u0026gt; D.__mro__ (\u0026lt;class \u0026#39;__main__.D\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.B\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.C\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.A\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;object\u0026#39;\u0026gt;) 结果完全一致！这意味着当调用 d.who_am_i() 时，Python 会：\n在 D 中查找。 在 B 中查找（找到并执行 B 的版本）。 如果 B 中没有，则在 C 中查找。 如果 C 中也没有，则在 A 中查找。 5. C3 的健壮性：检测不一致的继承\nC3 算法还能在类定义时就阻止不合逻辑的继承。\nclass X: pass class Y: pass class A(X, Y): pass class B(Y, X): pass class C(A, B): pass # TypeError: Cannot create a consistent method resolution # order (MRO) for bases Y, X 这里，A 的 MRO 要求 X 在 Y 之前，而 B 的 MRO 要求 Y 在 X 之前。C3 算法在合并时会发现无法同时满足这两个相互矛盾的条件，因此直接拒绝创建类 C，从而在源头上避免了混乱。\n总结：\nC3 线性化算法是 Python 多重继承系统的智能核心。它不仅仅是一个技术细节，更是保证了 Python 的 OOP 模型既强大灵活，又保持了确定性、可预测性和安全性的关键所在。它优雅地解决了困扰许多其他语言的菱形继承问题，让开发者可以更有信心地使用多重继承。\n对象模型控制与自动化 # @property：受控属性伪装 # @property 装饰器可以将一个方法转换为一个“只读属性”，并能为其添加验证逻辑的“设置器”和“删除器”，从而在不破坏简洁访问方式的前提下实现封装。\nclass Circle: def __init__(self, radius): self._radius = radius # 使用 _ 前缀约定为内部属性 @property def radius(self): \u0026#34;\u0026#34;\u0026#34;Getter: 返回半径。\u0026#34;\u0026#34;\u0026#34; return self._radius @radius.setter def radius(self, value): \u0026#34;\u0026#34;\u0026#34;Setter: 在设置值之前进行验证。\u0026#34;\u0026#34;\u0026#34; if value \u0026lt;= 0: raise ValueError(\u0026#34;Radius must be positive\u0026#34;) self._radius = value @property def area(self): \u0026#34;\u0026#34;\u0026#34;一个通过计算得出的只读属性。\u0026#34;\u0026#34;\u0026#34; return 3.14159 * (self._radius ** 2) c = Circle(10) print(c.radius) # 像访问属性一样调用 getter print(c.area) # 访问计算属性 c.radius = 12 # 调用 setter，自动进行验证 数据类 (dataclasses) # 对于主要用于存储数据的类，dataclasses 模块可以自动生成 __init__, __repr__, __eq__ 等大量模板代码，从而让开发者“告别”模板代码 (Python 3.7+)。\nfrom dataclasses import dataclass @dataclass(frozen=True) # frozen=True 使实例创建后不可变 class InventoryItem: name: str unit_price: float quantity_on_hand: int = 0 def total_cost(self) -\u0026gt; float: return self.unit_price * self.quantity_on_hand item1 = InventoryItem(\u0026#34;Pen\u0026#34;, 1.5, 100) item2 = InventoryItem(\u0026#34;Pen\u0026#34;, 1.5, 100) print(item1) # 自动生成了友好的 __repr__ print(item1 == item2) # True, 自动生成了 __eq__ dataclasses 极大提高了开发效率，是现代 Python 中构建数据模型类的首选方式。\n性能与内存优化：__slots__ # 默认情况下，Python 实例使用一个字典 (__dict__) 来存储属性，这很灵活但内存开销大。当需要大规模创建实例时，__slots__ 可以显著优化性能和内存。\n__slots__ 通过声明一组固定的属性，让 Python 使用更紧凑的内部结构替代 __dict__。\nclass Point: __slots__ = (\u0026#39;x\u0026#39;, \u0026#39;y\u0026#39;) # 声明实例只有 x 和 y 两个属性 def __init__(self, x, y): self.x = x self.y = y p = Point(1, 2) # p.z = 3 # -\u0026gt; AttributeError，因为实例不能再动态添加新属性 属性存储__dict__：灵活、“昂贵” # 工作原理\n当你定义一个普通 Python 类（没有 __slots__）时，每个实例都会在内部维护一个名为 __dict__ 的字典。这个字典负责存储该实例的所有属性（包括方法的引用，尽管方法通常是类属性）。\nclass MyClass: def __init__(self, a, b): self.a = a self.b = b self.c = 0 # 运行时动态添加的属性 obj = MyClass(1, 2) # obj.__dict__ 内部存储了这些信息: # {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2, \u0026#39;c\u0026#39;: 0} 为什么“内存开销大”？\n字典本身的开销：Python 的字典是一种非常高效的数据结构，但它为了实现 O(1) 的平均查找时间，需要额外的空间来存储哈希表、键、值，以及处理哈希冲突等。即使实例只有一个属性，__dict__ 本身就有一个固定的最小开销。 每个实例一个字典：这意味着如果你创建了 10,000 个 MyClass 的实例，你就创建了 10,000 个 __dict__ 对象。即使每个实例只存储几个简单的属性，这些字典加起来的内存占用也会相当可观。 动态灵活性: __dict__ 允许你在运行时随意添加、修改、删除属性（如 obj.d = 4）。这种灵活性是以内存为代价的。 为什么“性能”有影响？\n查找的间接性：访问 obj.a 时，Python 实际上是先查找 obj 对象，找到它的 __dict__，然后在 __dict__ 中查找键 'a'。这个额外的查找步骤（虽然平均是 O(1)）仍然比直接访问一个预先知道位置的内存空间要慢。 字典操作的开销：添加、删除属性时，字典需要进行哈希计算、可能扩容等操作，这些都会消耗 CPU 时间。 在一个非常大的仓库（__dict__）里找东西。即使知道大概的位置，还是需要在这个巨大的仓库里搜索。如果有很多这样的仓库（每个实例一个），管理和搜索它们会变得很慢。\n__slots__方案 # 工作原理\n当定义 __slots__ 时，实际上是在告诉 Python：“这个类的实例只会拥有这些指定的属性，并且不需要为它们准备一个 __dict__。”\nclass Point: __slots__ = (\u0026#39;x\u0026#39;, \u0026#39;y\u0026#39;) # 声明实例只有 x 和 y 两个属性 def __init__(self, x, y): self.x = x self.y = y p = Point(1, 2) \u0026#34;\u0026#34;\u0026#34; \u0026gt;\u0026gt;\u0026gt; p.__dict__ Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; AttributeError: \u0026#39;Point\u0026#39; object has no attribute \u0026#39;__dict__\u0026#39;. Did you mean: \u0026#39;__dir__\u0026#39;? \u0026#34;\u0026#34;\u0026#34; 在这种情况下，Point 的实例 p 不会拥有 __dict__。取而代之的是，Python 会为 x 和 y 在实例内部预留固定的内存空间（通常是以 C 语言结构体的方式实现），并提供直接访问这些内存位置的方法。\n如何“优化性能和内存”？\n内存优化： 无 __dict__ 开销：最直接的节省就是省去了每个实例持有一个字典的开销。 固定内存占用：每个实例的内存占用就等于其声明的属性所占用的空间，加上少量对象管理的开销。这比一个动态字典的总开销要小得多，尤其是在属性数量不多但实例数量庞大的情况下。 性能优化： 直接访问：访问 p.x 时，Python 可以直接根据预定义的结构（相当于 C 语言中的结构体成员访问）定位并读取内存，速度更快。 避免动态操作开销：因为属性是固定的，Python 不需要执行字典的哈希、查找、扩容等动态操作，从而提高了属性访问和修改的速度。 使用 __slots__ 就像是为实例设计了一个固定大小的、带有明确标签的工具箱。每个工具（属性）都有其固定位置。查找和取用工具时，直接知道在哪里找，非常快。而且，这个工具箱比前面那个大仓库（__dict__）小很多，也更容易携带。\n实际节省效果 # 通过一个简单的实验来量化内存节省：\n# %% # 导入所需库 import timeit import gc import psutil import os import sys # --- 定义测试用的类 --- class PointWithDict: \u0026#34;\u0026#34;\u0026#34;使用默认 __dict__ 的类\u0026#34;\u0026#34;\u0026#34; def __init__(self, x, y): self.x = x self.y = y class PointWithSlots: \u0026#34;\u0026#34;\u0026#34;使用 __slots__ 优化内存的类\u0026#34;\u0026#34;\u0026#34; __slots__ = (\u0026#34;x\u0026#34;, \u0026#34;y\u0026#34;) def __init__(self, x, y): self.x = x self.y = y # --- 测试函数定义 --- def measure_memory(cls_to_test, num_instances): \u0026#34;\u0026#34;\u0026#34; 测量创建指定数量的实例所导致的进程内存增量。 返回值为字节数。 \u0026#34;\u0026#34;\u0026#34; # 在测量前运行垃圾回收，确保一个干净的初始状态 gc.collect() # 获取当前进程对象 process = psutil.Process(os.getpid()) # 记录创建对象前的内存占用 mem_before = process.memory_info().rss # 创建对象列表 objects = [cls_to_test(i, i * 2) for i in range(num_instances)] # 记录创建对象后的内存占用 mem_after = process.memory_info().rss # 清理创建的对象，避免影响后续测试 del objects gc.collect() # 返回内存增量 return mem_after - mem_before def measure_performance(cls_to_test, num_instances): \u0026#34;\u0026#34;\u0026#34; 测量对象的创建、访问和修改性能。 返回一个包含三个时间值的元组。 \u0026#34;\u0026#34;\u0026#34; # 1. 测量创建时间 # timeit 的 setup 参数用于准备环境，stmt 是要重复执行的语句 creation_stmt = f\u0026#34;[{cls_to_test.__name__}(i, i*2) for i in range({num_instances})]\u0026#34; creation_setup = f\u0026#34;from __main__ import {cls_to_test.__name__}\u0026#34; # 我们只执行一次创建操作，因为创建大量对象本身就很耗时 creation_time = timeit.timeit(stmt=creation_stmt, setup=creation_setup, number=1) # 准备用于访问和修改测试的对象列表 objects = [cls_to_test(i, i * 2) for i in range(num_instances)] # 2. 测量访问时间 def access_objects(): total = 0 for p in objects: total += p.x total += p.y return total # number 设置为 100，以获得更稳定的平均时间 access_time = timeit.timeit(access_objects, number=100) # 3. 测量修改时间 def modify_objects(): for p in objects: p.x += 1 p.y -= 1 modify_time = timeit.timeit(modify_objects, number=100) # 清理对象 del objects gc.collect() return creation_time, access_time, modify_time def run_comparison(cls_to_test, num_instances): \u0026#34;\u0026#34;\u0026#34; 运行并打印指定类的完整内存和性能测试结果。 \u0026#34;\u0026#34;\u0026#34; print(f\u0026#34;--- 正在测试: {cls_to_test.__name__} ---\u0026#34;) # 内存测试 mem_used = measure_memory(cls_to_test, num_instances) print(f\u0026#34;内存占用增量: {mem_used / 1024**2:.2f} MB ({mem_used:,.0f} 字节)\u0026#34;) # 性能测试 creation, access, modification = measure_performance(cls_to_test, num_instances) print(f\u0026#34; - 对象创建时间: {creation:.6f} 秒\u0026#34;) print(f\u0026#34; - 属性访问时间 (100次): {access:.6f} 秒\u0026#34;) print(f\u0026#34; - 属性修改时间 (100次): {modification:.6f} 秒\u0026#34;) print(\u0026#34;-\u0026#34; * 30 + \u0026#34;\\n\u0026#34;) #%% # 定义测试规模 NUM_INSTANCES = 500_000 # 使用下划线提高可读性 print(f\u0026#34;Python 版本: {sys.version.split()[0]}\u0026#34;) print(f\u0026#34;测试实例数量: {NUM_INSTANCES:,}\u0026#34;) print(\u0026#34;=\u0026#34; * 40 + \u0026#34;\\n\u0026#34;) # 对两个类分别进行测试 run_comparison(PointWithDict, NUM_INSTANCES) run_comparison(PointWithSlots, NUM_INSTANCES) # %% 实验结果:\nPointWithSlots 实例的内存占用会比 PointWithDict 实例小很多。 但是在实际测试中发现与官方文档中的描述存在差异——访问 PointWithSlots 实例属性的性能并不会明显优于 PointWithDict。 # - Mac本地（3.11.8） \u0026#34;\u0026#34;\u0026#34; Python 版本: 3.11.8 测试实例数量: 500,000 ======================================== --- 正在测试: PointWithDict --- 内存占用增量: 80.04 MB (83,927,040 字节) - 对象创建时间: 0.188822 秒 - 属性访问时间 (100次): 5.118392 秒 - 属性修改时间 (100次): 7.855338 秒 ------------------------------ --- 正在测试: PointWithSlots --- 内存占用增量: 48.02 MB (50,352,128 字节) - 对象创建时间: 0.203976 秒 - 属性访问时间 (100次): 5.476134 秒 - 属性修改时间 (100次): 6.850384 秒 ------------------------------ \u0026#34;\u0026#34;\u0026#34; # - colab(3.11.13) \u0026#34;\u0026#34;\u0026#34; Python 版本: 3.11.13 测试实例数量: 500,000 ======================================== --- 正在测试: PointWithDict --- 内存占用增量: 82.04 MB (86,024,192 字节) - 对象创建时间: 0.293373 秒 - 属性访问时间 (100次): 4.721711 秒 - 属性修改时间 (100次): 4.083924 秒 ------------------------------ --- 正在测试: PointWithSlots --- 内存占用增量: 48.43 MB (50,782,208 字节) - 对象创建时间: 0.162264 秒 - 属性访问时间 (100次): 5.220282 秒 - 属性修改时间 (100次): 4.003415 秒 ------------------------------ \u0026#34;\u0026#34;\u0026#34; 总结 __slots__ 的权衡 # 优势: 显著的内存节省：特别适合于需要创建数万甚至数百万个对象的场景。 更快的属性访问和修改速度。与官方文档存在差异的主要原因可能在于 Python 3.6+ 版本对字典进行了重大优化，使得小字典的访问速度大幅提升 Stack Overflow 紧凑字典（Compact Dict）：Python 3.6+ 采用新的字典实现，内存布局更紧凑 小字典优化：对于少量属性（如你的测试中只有 x, y 两个属性），字典查找已经非常快 键共享字典：同一类的多个实例可以共享键，减少内存和提升速度 阻止动态添加意外属性：有助于提高代码的健壮性，避免无意中引入新的状态。 劣势: 丧失灵活性：不能动态添加新属性。 不支持 __dict__：你无法通过 instance.__dict__ 来查看或修改属性，也无法使用 vars(instance)。 继承时的注意事项：如果父类定义了 __slots__，子类也必须定义自己的 __slots__（或者在其 __slots__ 中包含父类的 slot 名称），否则子类将无法使用 __slots__，会重新获得 __dict__，导致一些潜在的混乱。 对某些内省工具不友好：一些依赖 __dict__ 的第三方库或工具可能无法与 __slots__ 类完美工作。 何时使用 __slots__？\n当且仅当：\n正在处理大量同类型的对象（数千到数百万）。 这些对象的属性集合是固定的，不会在运行时动态增减。 不关心实例的 __dict__，或者知道如何处理没有 __dict__ 的类。 内存占用或属性访问速度是性能瓶颈，且 __slots__ 能提供可观的改善。 在其他情况下，__dict__ 的灵活性通常更受欢迎。\n高级设计模式与底层机制 # “私有”变量与名称改写 # Python 没有真正的 private。但 __ (双下划线) 前缀会触发名称改写 (Name Mangling)，解释器会将 __variable 改为 _ClassName__variable。这主要为了避免子类意外覆盖父类的内部同名属性，而不是为了实现数据隐藏。\nclass MyClass: def __init__(self): self.__super_private = \u0026#34;secret\u0026#34; obj = MyClass() # print(obj.__super_private) # -\u0026gt; AttributeError print(obj._MyClass__super_private) # -\u0026gt; secret，仍可访问 抽象基类 (ABCs) - 定义接口规范 # abc 模块允许你定义抽象基类，以强制其子类必须实现特定的方法。这在构建框架或定义插件接口时至关重要。\nfrom abc import ABC, abstractmethod class MediaLoader(ABC): @abstractmethod def load(self, source): \u0026#34;\u0026#34;\u0026#34;定义一个接口，子类必须实现它。\u0026#34;\u0026#34;\u0026#34; pass class ImageLoader(MediaLoader): def load(self, source): # 若不实现 load 方法，则在实例化时会报错 print(f\u0026#34;Loading image from {source}\u0026#34;) # loader = MediaLoader() # -\u0026gt; TypeError: Can\u0026#39;t instantiate abstract class... img_loader = ImageLoader() 元类 (Metaclasses) - 创建类的类 # 官方文档：元类\n这是 Python 对象模型最深邃的部分。元类是创建类的类。默认的元类是 type。当你写下 class MyClass: ... 时，Python 解释器在背后调用元类来创建 MyClass 这个类对象。\n元类允许你在类被创建时自动地修改或增强类。这是 Django ORM、SQLAlchemy 等框架实现其“魔法”的核心技术，它们通过元类读取类定义，并自动生成与数据库交互所需的方法和属性。\n极少开发场景需要编写元类，但理解其概念至关重要。它解释了 Python 框架中许多强大功能的来源，并展示了 Python 对象模型的极致灵活性——不仅能控制对象的行为，还能控制类本身的行为。\n","date":"2022-11-12","externalUrl":null,"permalink":"/blogs/python/base/python-class/","section":"Blog","summary":"","title":"Python 类","type":"blogs"},{"content":" 在任何编程语言中，错误都是不可避免的。Python 提供了一套强大而灵活的异常处理机制，它不仅能帮助我们捕获和处理运行时错误，更是控制程序流程、构建可靠应用的重要工具。\n错误类型：语法错误与异常 # 语法错误 (Syntax Errors) # 语法错误，也称为解析错误，是程序在被解释器解析阶段就发现的错误。这意味着代码甚至还没开始运行。通常是因为违反了 Python 的语法规则，比如拼写错误的关键字、缺少冒号或括号不匹配。\nwhile True print(\u0026#39;Hello world\u0026#39;) # File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1 # while True print(\u0026#39;Hello world\u0026#39;) # ^^^^^ # SyntaxError: invalid syntax 解释器会指出出错的行，并用一个小箭头 ^ 标记出最早检测到错误的位置。这类错误必须在运行前修复。\n异常 (Exceptions) # 即使程序的语法完全正确，在运行期间也可能发生错误，这些错误被称为异常。当 Python 解释器遇到一个它无法处理的情况时，就会“抛出 (raise)”一个异常。\n常见的内置异常包括：\nZeroDivisionError: 尝试除以零。 NameError: 使用一个未被定义的局部或全局变量。 TypeError: 将一个操作或函数应用于了不合适类型的对象。 ValueError: 操作或函数的参数类型正确，但值不合适。 FileNotFoundError: 尝试打开一个不存在的文件。 KeyError: 在字典中查找一个不存在的键。 当异常发生且未被处理时，程序会立即中止，并打印出一条回溯 (Traceback) 信息，它显示了异常发生时的完整调用栈。\n捕获与处理异常 # 为了防止异常导致程序崩溃，我们可以使用 try...except 语句来捕获并处理它们。\ntry 子句: 包含可能会引发异常的代码块。 except 子句: 如果 try 子句中发生了指定类型的异常，解释器会立即跳转到对应的 except 子句执行。 捕获特定异常 # 这是最推荐的做法。只捕获你明确知道如何处理的特定异常。\ntry: x = int(input(\u0026#34;Please enter a number: \u0026#34;)) result = 10 / x print(f\u0026#34;10 / {x} = {result}\u0026#34;) except ValueError: print(\u0026#34;Oops! That was not a valid number. Try again...\u0026#34;) except ZeroDivisionError: print(\u0026#34;Oops! You cannot divide by zero.\u0026#34;) 你可以为一个 try 块提供多个 except 子句来处理不同类型的异常。\n捕获多个异常及对象 # 如果多个异常的处理逻辑相同，可以将它们放在一个元组里。你还可以通过 as 关键字来访问异常对象本身，它通常包含了错误的详细信息。\ntry: # ... some code ... except (RuntimeError, TypeError, NameError) as err: print(f\u0026#34;An error occurred: {err}\u0026#34;) # err 是异常类的实例，打印它通常会显示错误消息 else 和 finally 执行逻辑 # try 语句可以与 else 和 finally 子句配合使用，以实现更复杂的控制流和清理逻辑。理解它们的执行时机至关重要。\n核心定义 # else 子句：它的存在是为了**“庆祝成功”。它只在 try 块没有引发任何异常**的情况下执行。 finally 子句：它的存在是为了**“无论如何都要清理”。它总是**会执行，不管 try 块中是否发生了异常，也不管异常是否被 except 捕获。 场景化执行流程分析 # 让我们用一个统一的函数来分析所有可能的情况：\ndef test_division(dividend, divisor): print(f\u0026#34;\\\\n--- Testing {dividend} / {divisor} ---\u0026#34;) try: print(\u0026#34;Step 1: Entering the \u0026#39;try\u0026#39; block.\u0026#34;) result = dividend / divisor print(\u0026#34;Step 2: Division successful.\u0026#34;) except ZeroDivisionError: print(\u0026#34;Step 3 (Except): Caught a ZeroDivisionError!\u0026#34;) else: print(f\u0026#34;Step 4 (Else): No exceptions occurred! The result is {result}.\u0026#34;) finally: print(\u0026#34;Step 5 (Finally): This cleanup block always runs.\u0026#34;) 场景 1: 成功执行（无异常）\n执行路径: try -\u0026gt; else -\u0026gt; finally\ntest_division(10, 2) 输出:\n--- Testing 10 / 2 --- Step 1: Entering the \u0026#39;try\u0026#39; block. Step 2: Division successful. Step 4 (Else): No exceptions occurred! The result is 5.0. Step 5 (Finally): This cleanup block always runs. 分析: try 块成功执行，因此跳过 except 并执行 else，最后执行 finally。\n场景 2: 发生异常并被捕获\n执行路径: try (失败) -\u0026gt; except -\u0026gt; finally\ntest_division(10, 0) 输出:\n--- Testing 10 / 0 --- Step 1: Entering the \u0026#39;try\u0026#39; block. Step 3 (Except): Caught a ZeroDivisionError! Step 5 (Finally): This cleanup block always runs. 分析: try 块中发生异常，立即跳转到匹配的 except 块。else 块被跳过，最后执行 finally 。\n场景 3: 发生异常但未被捕获\n执行路径: try (失败) -\u0026gt; finally -\u0026gt; 异常向上传播\ntest_division(10, \u0026#39;a\u0026#39;) # 这会引发 TypeError 输出及行为:\n--- Testing 10 / \u0026#39;a\u0026#39; --- Step 1: Entering the \u0026#39;try\u0026#39; block. Step 5 (Finally): This cleanup block always runs. Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 4, in test_division TypeError: unsupported operand type(s) for /: \u0026#39;int\u0026#39; and \u0026#39;str\u0026#39; 分析: try 块中发生 TypeError，没有匹配的 except。在异常向上传播（导致程序终止）之前，finally 块被执行，这是它保证清理操作的核心特性。\n设计哲学 # 场景 try 块 except 执行? else 执行? finally 执行? 成功 顺利完成 否 是 是 异常被捕获 中途失败 是 否 是 异常未被捕获 中途失败 否 否 是 (在程序崩溃前) else 的用途: 让 try 块尽可能小，只包裹真正可能引发目标异常的代码，而将“成功后才应执行的代码”放入 else，使逻辑更清晰。 finally 的用途: 保证资源的释放。无论程序如何退出，它都能确保关闭文件、释放锁等关键清理操作得以执行，防止资源泄漏。 📌 最佳实践: 对于文件等资源，优先使用 with 语句。它在内部自动管理了 try...finally 逻辑，代码更简洁、更安全。 raise 主动抛出异常 # 可以使用 raise 语句来主动抛出一个异常。\n抛出内置或自定义异常 # def check_age(age): if age \u0026lt; 0: raise ValueError(\u0026#34;Age cannot be negative.\u0026#34;) 重新抛出异常 # 在 except 块中，单独使用 raise 语句可以重新抛出刚刚捕获到的异常，让调用栈的上层继续处理它。\ntry: # ... except FileNotFoundError as e: log_error(e) # 记录错误 raise # 重新抛出 FileNotFoundError 异常链 (raise ... from ...) # 为了不丢失原始异常的上下文，可以使用 raise ... from ... 语法将它们链接起来。\ndef process_data(data): try: value = int(data) except ValueError as e: raise RuntimeError(\u0026#34;Failed to process data\u0026#34;) from e 回溯信息将同时显示 RuntimeError 和导致它的 ValueError，使问题根源一目了然。\n自定义异常 # 通过创建自己的异常类，可以让代码的错误语义更加清晰。自定义异常应直接或间接地继承自内置的 Exception 类。\nclass InputValidationError(Exception): \u0026#34;\u0026#34;\u0026#34;Raised when an input value is invalid.\u0026#34;\u0026#34;\u0026#34; def __init__(self, expression, message): self.expression = expression self.message = message try: user_input = \u0026#34;invalid-email\u0026#34; if \u0026#34;@\u0026#34; not in user_input: raise InputValidationError(user_input, \u0026#34;Email address is missing \u0026#39;@\u0026#39;\u0026#34;) except InputValidationError as e: print(f\u0026#34;Validation Error: Expression \u0026#39;{e.expression}\u0026#39; failed. Reason: {e.message}\u0026#34;) 结论 # 不要滥用 except:：避免使用宽泛的 except Exception: 或裸 except:。始终捕获你期望处理的、最具体的异常。 异常不是控制流：异常处理的是意外情况。对于可预期的、正常的程序流程，不要使用异常来控制。 提供清晰的错误信息：无论是通过自定义异常还是内置异常的消息，都应提供足够的信息帮助开发者定位问题。 ","date":"2022-11-08","externalUrl":null,"permalink":"/blogs/python/base/python-errors/","section":"Blog","summary":"","title":"Python 错误与异常","type":"blogs"},{"content":" 程序的本质是处理数据，而输入与输出（I/O）是程序与外部世界沟通的桥梁。无论是向用户显示信息、从文件中读取配置，还是保存复杂的程序状态，都离不开 I/O 操作。\n格式化字符串 # 向用户展示数据时，清晰、美观的格式至关重要。Python 提供了多种强大的字符串格式化方法。\nF-Strings 格式 # 自 Python 3.6 引入，F-strings 已成为最推荐的格式化方式。它简洁、可读性强且性能出色。\n语法：在字符串字面值前加上 f 或 F，并在 {} 中直接嵌入表达式或变量。 特性：{} 中的表达式会在运行时被求值。 import math year = 2025 event = \u0026#39;Conference\u0026#39; # 基本用法 print(f\u0026#39;Results of the {year} {event}\u0026#39;) # -\u0026gt; \u0026#39;Results of the 2025 Conference\u0026#39; # 表达式求值 print(f\u0026#39;Pi is approximately {math.pi:.3f}.\u0026#39;) # -\u0026gt; \u0026#39;Pi is approximately 3.142.\u0026#39; # :.3f 是一个格式说明符，表示保留三位小数的浮点数 # 对齐与填充 table = {\u0026#39;Sjoerd\u0026#39;: 4127, \u0026#39;Jack\u0026#39;: 4098, \u0026#39;Dcab\u0026#39;: 7678} for name, phone in table.items(): print(f\u0026#39;{name:10} ==\u0026gt; {phone:10d}\u0026#39;) # -\u0026gt; # Sjoerd ==\u0026gt; 4127 # Jack ==\u0026gt; 4098 # Dcab ==\u0026gt; 7678 str.format() 方法 # 在 F-strings 出现之前，str.format() 是标准方法。它依然非常强大，尤其是在格式化字符串本身是变量时。\n# 位置参数 print(\u0026#39;We are the {} who say \u0026#34;{}!\u0026#34;\u0026#39;.format(\u0026#39;knights\u0026#39;, \u0026#39;Ni\u0026#39;)) # -\u0026gt; \u0026#39;We are the knights who say \u0026#34;Ni!\u0026#34;\u0026#39; # 关键字参数 print(\u0026#39;This {food} is {adjective}.\u0026#39;.format( food=\u0026#39;spam\u0026#39;, adjective=\u0026#39;absolutely horrible\u0026#39;)) # -\u0026gt; \u0026#39;This spam is absolutely horrible.\u0026#39; # 位置与关键字参数混合 print(\u0026#39;The story of {0}, {1}, and {other}.\u0026#39;.format(\u0026#39;Bill\u0026#39;, \u0026#39;Manfred\u0026#39;, other=\u0026#39;Georg\u0026#39;)) # -\u0026gt; \u0026#39;The story of Bill, Manfred, and Georg.\u0026#39; repr() vs str() # Python 中有两个内置函数可以将任何值转换为字符串：\nstr(): 返回值的“用户友好”表示形式。其目标是可读性。 repr(): 返回值的“官方”或“开发者友好”表示形式。其目标是无歧义，理想情况下，eval(repr(obj)) == obj。 s = \u0026#39;hello, world.\u0026#39; print(str(s)) # -\u0026gt; hello, world. print(repr(s)) # -\u0026gt; \u0026#39;hello, world.\u0026#39; (带有引号，是合法的字符串字面值) import datetime today = datetime.datetime.now() print(str(today)) # -\u0026gt; 2025-11-04 11:01:00.123456 print(repr(today)) # -\u0026gt; datetime.datetime(2025, 11, 4, 11, 1, 0, 123456) 文件读写 # 文件处理是 I/O 的核心。Python 提供了简洁而强大的接口来操作文件。\nopen() 函数 # open() 函数返回一个文件对象，是所有文件操作的起点。\nopen(file, mode='r', encoding=None)\nfile: 文件路径字符串。 mode: 一个字符串，指定文件的打开模式。 'r': 读 (Read) - 默认值。如果文件不存在，则引发 FileNotFoundError。 'w': 写 (Write) - 文件若存在则清空其内容，若不存在则创建。 'a': 追加 (Append) - 在文件末尾写入，若不存在则创建。 'r+': 读写模式。 'b': 二进制 (Binary) 模式，附加在其他模式后（如 'rb', 'wb'）。用于处理非文本文件（如图片、音频）。 encoding: 文本文件的编码格式。强烈推荐始终显式指定 encoding='utf-8'，以避免在不同操作系统上出现编码错误。 with 语句 # with 语句是处理文件对象的最佳实践。它能确保在代码块执行完毕或发生异常时，文件都会被自动、正确地关闭。\nwith open(\u0026#39;workfile.txt\u0026#39;, \u0026#39;w\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: f.write(\u0026#39;This is the first line.\\\\n\u0026#39;) f.write(\u0026#39;This is the second line.\\\\n\u0026#39;) # 此处文件 f 已被自动关闭 文件对象方法 # 读取文件:\nf.read(size): 读取并返回最多 size 个字符（文本模式）或字节（二进制模式）的数据。若 size 省略或为负，则读取整个文件。 f.readline(): 读取并返回文件中的一行（直到并包括 \\\\n 字符）。 for line in f: 遍历文件对象的最高效、最简洁的方式。它逐行读取，内存占用小。 with open(\u0026#39;workfile.txt\u0026#39;, \u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: for line in f: print(line, end=\u0026#39;\u0026#39;) # end=\u0026#39;\u0026#39; 避免打印额外的换行符 写入文件:\nf.write(string): 将 string 的内容写入文件，并返回写入的字符数。注意：write() 不会自动添加换行符 \\\\n。 要写入非字符串数据，必须先将其转换为字符串。 value = (\u0026#39;the answer\u0026#39;, 42) with open(\u0026#39;results.txt\u0026#39;, \u0026#39;w\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: f.write(str(value)) # 将元组转换为字符串后写入 文件指针:\nf.tell(): 返回一个整数，表示文件对象在文件中的当前位置（从文件开头算起的字节数）。 f.seek(offset, whence): 移动文件指针。offset 是偏移量，whence 是参考位置（0: 文件开头, 1: 当前位置, 2: 文件末尾）。 持久化：JSON 与 Pickle # 当需要保存列表、字典等复杂数据结构时，简单的文本文件就力不从心了。这时需要序列化（Serialization）技术。\nJSON: 通用数据交换 # JSON 是一种轻量级、人类可读的数据交换格式，被广泛用于 Web API 和配置文件。Python 的 json 模块提供了完整的支持。\njson.dump(obj, fp): 将 Python 对象 obj 序列化为 JSON 格式并写入文件对象 fp。 json.load(fp): 从文件对象 fp 中读取 JSON 数据，并将其反序列化为 Python 对象。 json.dumps(obj) / json.loads(s): 分别处理字符串而非文件对象。 import json data = { \u0026#34;name\u0026#34;: \u0026#34;John Doe\u0026#34;, \u0026#34;age\u0026#34;: 30, \u0026#34;isStudent\u0026#34;: False, \u0026#34;courses\u0026#34;: [ {\u0026#34;title\u0026#34;: \u0026#34;History\u0026#34;, \u0026#34;credits\u0026#34;: 3}, {\u0026#34;title\u0026#34;: \u0026#34;Math\u0026#34;, \u0026#34;credits\u0026#34;: 4} ] } # 写入 JSON 文件 with open(\u0026#39;data.json\u0026#39;, \u0026#39;w\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: json.dump(data, f, indent=4) # indent=4 使文件格式更美观 # 读取 JSON 文件 with open(\u0026#39;data.json\u0026#39;, \u0026#39;r\u0026#39;, encoding=\u0026#39;utf-8\u0026#39;) as f: loaded_data = json.load(f) print(loaded_data[\u0026#39;courses\u0026#39;][0][\u0026#39;title\u0026#39;]) # -\u0026gt; History 注意：JSON 的数据类型有限，Python 中的元组在序列化后会变成列表，且无法表示自定义的类对象。\nPickle: 专属序列化 # pickle 模块是 Python 特有的序列化解决方案。它使用二进制协议，能够处理几乎所有的 Python 数据类型，包括自定义类的实例。\n优势: 功能强大，能够序列化复杂的 Python 对象，如函数、类实例，并完整地保留其数据和类型。 劣势: 安全风险: 绝对不要 unpickle 来自不可信或未经身份验证来源的数据！ 反序列化 pickle 数据可以执行任意代码。 跨语言不兼容: pickle 格式是 Python 专用的，其他语言无法解析。 版本问题: 不同 Python 版本间的 pickle 协议可能不兼容。 pickle 模块的接口与 json 模块非常相似：\npickle.dump(obj, fp): 将对象 obj 封存到文件对象 fp。 pickle.load(fp): 从文件对象 fp 中读取并重建一个 Python 对象。 import pickle # 定义一个自定义类 class Task: def __init__(self, name, priority): self.name = name self.priority = priority def __repr__(self): return f\u0026#34;Task(name=\u0026#39;{self.name}\u0026#39;, priority={self.priority})\u0026#34; # 创建实例并封存 task1 = Task(\u0026#34;Finish report\u0026#34;, 1) task2 = Task(\u0026#34;Call Mom\u0026#34;, 2) tasks_list = [task1, task2] # 使用二进制写模式 \u0026#39;wb\u0026#39; with open(\u0026#39;tasks.pkl\u0026#39;, \u0026#39;wb\u0026#39;) as f: pickle.dump(tasks_list, f) # 从文件加载并解封 # 使用二进制读模式 \u0026#39;rb\u0026#39; with open(\u0026#39;tasks.pkl\u0026#39;, \u0026#39;rb\u0026#39;) as f: loaded_tasks = pickle.load(f) print(loaded_tasks) # -\u0026gt; [Task(name=\u0026#39;Finish report\u0026#39;, priority=1), Task(name=\u0026#39;Call Mom\u0026#39;, priority=2)] print(type(loaded_tasks[0])) # -\u0026gt; \u0026lt;class \u0026#39;__main__.Task\u0026#39;\u0026gt; 结论：选择合适的 I/O 工具 # 格式化输出: 优先使用 F-strings，以获得最佳的可读性和性能。 文件处理: 始终使用 with open(...) 结构，并明确指定 encoding='utf-8'。 数据持久化: 当需要与其它系统（如 Web 前端）交换数据，或需要人类可读的配置文件时，选择 JSON。 当需要在 Python 程序间保存和恢复复杂的内部状态，且数据来源绝对安全时，可以使用 Pickle。 ","date":"2022-11-07","externalUrl":null,"permalink":"/blogs/python/base/python-io/","section":"Blog","summary":"","title":"Python I/O指南","type":"blogs"},{"content":" 在 Python 中，模块（Module）是代码组织的核心单元。任何一个非凡的 Python 应用，都是由一个个精心设计的模块构筑而成。\n模块基础：代码封装与重用 # What \u0026amp; Why？ # 从本质上讲，一个 Python 模块就是一个包含了 Python 定义和语句的文件，其文件名就是模块名加上 .py 后缀。\n使用模块主要解决了三大问题：\n代码重用：将常用的函数、类或变量封装在模块中，可以在任何需要的地方导入并使用，避免重复编写代码。 命名空间隔离：每个模块都有自己独立的私有符号表（Private Symbol Table），这意味着在模块 A 中定义的全局变量不会与模块 B 中的同名变量发生冲突。这对于构建大型项目至关重要。 逻辑组织：将相关的代码组织在不同的模块中，使得项目结构更加清晰，易于理解和维护。 import 语句：引入代码 # import 语句是使用模块的第一步。当解释器执行 import fibo 时，它会查找 fibo.py 文件，并执行其内容，然后将模块对象 fibo 引入到当前模块的符号表中。\n# 文件: fibo.py def fib(n): # ... (斐波那契数列实现) def fib2(n): # ... # 文件: main.py import fibo # 导入 fibo 模块 # 必须通过 \u0026#34;模块名.函数名\u0026#34; 的方式访问 fibo.fib(1000) 核心机制：import fibo 并没有将 fib 和 fib2 函数直接导入到当前命名空间，而是导入了名为 fibo 的模块对象。你需要通过该对象的属性来访问其内部的定义。\nimport 方式及其影响 # Python 提供了多种导入方式，每种都有其特定的适用场景和注意事项。\nfrom ... import ...：直接导入名称 这种方式将模块中的指定名称直接导入到当前命名空间，无需通过模块名前缀来访问。\nfrom fibo import fib, fib2 fib(500) # 直接调用，无需 fibo. 前缀 优点：代码更简洁。 缺点：如果导入的名称与当前模块中的现有名称冲突，后者将被覆盖。\nimport ... as ...：为名称创建别名 别名在处理名称过长或避免命名冲突时非常有用。\nimport fibo as fi from fibo import fib as fibonacci fi.fib(100) fibonacci(100) from ... import *：导入所有名称（需谨慎使用） 这会导入模块中所有非下划线开头的公开名称。\nfrom fibo import * fib(500) ⚠️ 警告：这种方式极易污染当前命名空间，导致名称冲突和代码可读性下降。在生产代码中应极力避免使用。它仅在少数场合（如 Python 交互式会话或某些框架的特定模式下）有其便利性。\n模块的执行与 __name__ # 首次导入与缓存机制 # 一个模块的顶层代码在首次被导入时会执行一次。这包括所有的赋值语句、函数定义、类定义等。后续对同一模块的 import 语句不会再次执行模块代码，而是直接从缓存 sys.modules 中获取已加载的模块对象。这是一种重要的性能优化。\nif __name__ == \u0026quot;__main__\u0026quot;: # 每个模块都有一个名为 __name__ 的内置变量。它的值取决于模块是如何被执行的：\n作为顶层脚本直接运行：如果一个 .py 文件被直接执行（例如 python my_module.py），那么在该模块内部，__name__ 的值是字符串 '__main__'。 被其他模块导入：如果一个模块被 import 语句加载，那么 __name__ 的值是该模块的文件名（不含 .py 后缀）。 这个特性催生了 Python 中一个非常重要的编码模式，它允许一个文件既可以作为可重用的模块被导入，也可以作为独立的脚本来执行。\n实践范例 (fibo.py):\n# 文件: fibo.py # --- 模块定义部分 --- def fib(n): # ... def fib2(n): # ... # --- 脚本执行部分 --- if __name__ == \u0026#34;__main__\u0026#34;: import sys # 这段代码只有在 \u0026#34;python fibo.py 100\u0026#34; 这样直接运行时才会执行 # 如果被其他模块 import，这段代码将被忽略 print(f\u0026#34;Fibonacci result for {sys.argv[1]}: {fib(int(sys.argv[1]))}\u0026#34;) 模块搜索路径 # 当执行 import spam 时，Python 解释器会按照一个明确的顺序搜索 spam 模块：\nsys.modules 缓存：首先检查模块是否已经被加载。 内置模块：检查 spam 是否是一个内置模块（如 sys, os）。 sys.path 列表：按顺序遍历 sys.path 列表中的每个目录。 sys.path 是一个字符串列表，它指定了模块的搜索路径。它的内容通常包括：\n当前脚本所在的目录。 环境变量 PYTHONPATH 中指定的目录列表。 Python 安装的默认路径（如 site-packages 目录，用于存放第三方库）。 你可以通过以下代码查看当前的搜索路径：\nimport sys print(sys.path) 包：层次化命名空间 # 当项目变得复杂，仅靠单个模块来组织代码已然不够。**包（Packages）**提供了一种通过“带点号的模块名”来组织模块命名空间的方法。\n定义：一个包就是一个包含其他模块（或子包）的目录。 结构：目录中必须包含一个 __init__.py 文件（即使为空），Python 才会将其视为一个包。 注：自 Python 3.3 起，引入了“命名空间包”，允许没有 __init__.py 的目录成为包的一部分。在实践中，创建 __init__.py 依然是标准做法。 示例包结构:\nsound/ ├── __init__.py ├── formats/ │ ├── __init__.py │ ├── wavread.py │ └── wavwrite.py └── effects/ ├── __init__.py ├── echo.py └── reverse.py 使用包 # 你可以使用点号来导入包中的模块：\nimport sound.effects.echo sound.effects.echo.echofilter(...) # 或者使用 from 语句 from sound.effects import echo echo.echofilter(...) __init__.py 作用 # __init__.py 文件有三个主要作用：\n标记目录为包：这是它最基本的功能。\n执行包的初始化代码：当你 import sound.effects 时，sound/__init__.py 和 sound/effects/__init__.py 会依次被执行。\n简化导入（可选）：可以在 __init__.py 中将子模块的接口“提升”到包的命名空间中，方便外部调用。 这样，用户就可以直接这样导入：\n# 在 sound/effects/__init__.py 中 from .echo import echofilter from .reverse import reversefilter from sound.effects import echofilter # 而不是 from sound.effects.echo import echofilter 包内导入（相对导入） # 在同一个包的不同模块之间，可以使用相对导入来相互引用，这使得包的结构更加稳固，不易受外部路径变化的影响。\nfrom . import spam：从同级目录导入 spam 模块。 from .. import spam：从上级目录导入 spam 模块。 from ..subpackage import spam：从上级目录的另一个子包导入。 示例 (sound/effects/echo.py):\nfrom ..formats import wavread # 从上级的 formats 包导入 wavread 模块与标准库 # 使用 dir() 探索模块 内置函数 dir() 可以找出模块定义的所有名称（变量、函数、类等）。\nimport fibo import sys dir(fibo) # [\u0026#39;__name__\u0026#39;, \u0026#39;fib\u0026#39;, \u0026#39;fib2\u0026#39;] dir(sys) # 返回一个很长的列表，包含 sys 模块的所有名称 Python 标准库：\u0026ldquo;Batteries Included\u0026rdquo; Python 自带一个庞大而功能丰富的标准库，提供了大量现成的模块，用于处理各种常见任务，如文件 I/O (os), 正则表达式 (re), 网络编程 (socket), JSON 处理 (json), 日期时间 (datetime) 等。在着手编写新代码前，先查阅标准库文档，往往能发现已经有现成的解决方案。\n结论 # 模块和包是 Python 语言的支柱，它们共同构成了强大而灵活的代码组织机制。掌握好模块系统，意味着你能够：\n编写出结构清晰、易于维护的代码。 高效地重用自己和他人的工作成果。 从容地构建从小型脚本到大型企业级应用的各类项目。 ","date":"2022-11-05","externalUrl":null,"permalink":"/blogs/python/base/python-module/","section":"Blog","summary":"","title":"Python 模块：从代码组织到大型应用架构","type":"blogs"},{"content":" Python 之所以强大和流行，其内置的、设计精良的数据结构功不可没。它们是构建任何程序的基础模块。\n序列类型 —— 有序数据的世界 # 序列是 Python 中最基本的数据结构类型，它们的共同特点是其成员有序排列，可以通过索引访问。列表和元组是序列类型的两大核心。\n列表 (List): 动态、可变 # 列表是 Python 中最灵活的序列类型，它是一个可变的、有序的元素集合。你可以随时添加、删除或修改其中的元素。\n核心特性与常用方法:\n增删改查: append(x): 在末尾添加。 extend(iterable): 合并另一个序列。 insert(i, x): 在指定索引 i 处插入。 remove(x): 按值删除第一个匹配项。 pop([i]): 按索引 i (默认为末尾) 移除并返回该元素。 排序与反转: sort(): 原地排序。 reverse(): 原地反转。 其他: copy(): 返回列表的浅拷贝。 count(x): 统计元素 x 出现的次数。 代码实践:\n# 初始化与基本操作 fruits = [\u0026#39;orange\u0026#39;, \u0026#39;apple\u0026#39;, \u0026#39;pear\u0026#39;, \u0026#39;banana\u0026#39;] fruits.append(\u0026#39;grape\u0026#39;) # 添加 fruits[1] = \u0026#39;green apple\u0026#39; # 修改 print(fruits) # [\u0026#39;orange\u0026#39;, \u0026#39;green apple\u0026#39;, \u0026#39;pear\u0026#39;, \u0026#39;banana\u0026#39;, \u0026#39;grape\u0026#39;] # 列表作为堆栈 (LIFO) stack = [3, 4, 5] stack.append(6) # 入栈 stack.pop() # 出栈 -\u0026gt; 返回 6 # 列表作为队列 (FIFO) - 效率低，推荐使用 collections.deque from collections import deque queue = deque([\u0026#34;Eric\u0026#34;, \u0026#34;John\u0026#34;]) queue.append(\u0026#34;Terry\u0026#34;) # 入队 queue.popleft() # 高效出队 -\u0026gt; 返回 \u0026#39;Eric\u0026#39; 关键技巧：列表推导式\n列表推导式是 Python 的一大特色，它提供了一种极其优雅和简洁的方式来创建列表。\n# 传统方式 squares = [] for x in range(10): squares.append(x**2) # 列表推导式 squares = [x**2 for x in range(10)] # -\u0026gt; [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] # 加入条件判断 even_squares = [x**2 for x in range(10) if x % 2 == 0] # -\u0026gt; [0, 4, 16, 36, 64] 元组 (Tuple): 不变 # 元组与列表类似，也是一个有序的序列，但其核心特性是不可变。一旦创建，元组的内容就无法被修改。\n核心特性与应用场景:\n不可变性: 保证了数据的完整性，使其可以作为字典的键或集合的元素。 序列解包: 这是元组最优雅的特性之一，可以用于变量赋值、函数返回等。 性能: 通常情况下，元组比列表占用更少的内存，处理速度也稍快。 代码实践:\n# 创建元组 (注意单元素元组需要一个逗号) t = 123, \u0026#39;hello\u0026#39;, True singleton = (\u0026#39;world\u0026#39;,) # 序列解包 x, y, z = t print(f\u0026#34;x={x}, y={y}, z={z}\u0026#34;) # x=123, y=hello, z=True # 作为字典的键 location_data = { (39.9042, 116.4074): \u0026#34;Beijing\u0026#34;, (31.2304, 121.4737): \u0026#34;Shanghai\u0026#34; } # 尝试修改元组会引发 TypeError # t[0] = 456 # -\u0026gt; TypeError: \u0026#39;tuple\u0026#39; object does not support item assignment 集合与映射 —— 无序但高效 # 与序列类型不同，这一类数据结构通常不依赖于元素的顺序，而是通过唯一的键或成员关系来组织数据。\n集合 (Set): 高效、唯一 # 集合是一个无序、不含重复元素的容器。它的基本用法包括成员检测、消除重复元素。集合对象支持合集、交集、差集、对称差分等数学运算。\n核心特性与操作:\n唯一性: 自动去除重复元素。 成员测试: in 操作的效率远高于列表。 集合运算: | 或 union(): 并集 \u0026amp; 或 intersection(): 交集 - 或 difference(): 差集 ^ 或 symmetric_difference(): 对称差集 代码实践:\n# 创建与去重 basket = {\u0026#39;apple\u0026#39;, \u0026#39;orange\u0026#39;, \u0026#39;apple\u0026#39;, \u0026#39;pear\u0026#39;, \u0026#39;orange\u0026#39;, \u0026#39;banana\u0026#39;} print(basket) # {\u0026#39;banana\u0026#39;, \u0026#39;pear\u0026#39;, \u0026#39;apple\u0026#39;, \u0026#39;orange\u0026#39;} (顺序不固定) # 成员测试 print(\u0026#39;orange\u0026#39; in basket) # True # 集合运算 a = set(\u0026#39;abracadabra\u0026#39;) b = set(\u0026#39;alacazam\u0026#39;) print(f\u0026#34;Difference (a - b): {a - b}\u0026#34;) # {\u0026#39;r\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;d\u0026#39;} print(f\u0026#34;Union (a | b): {a | b}\u0026#34;) # {\u0026#39;a\u0026#39;, \u0026#39;c\u0026#39;, \u0026#39;r\u0026#39;, \u0026#39;d\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;m\u0026#39;, \u0026#39;z\u0026#39;, \u0026#39;l\u0026#39;} # 与列表推导式类似，集合也支持推导式 unique_consonants = {c for c in \u0026#39;programming\u0026#39; if c not in \u0026#39;aeiou\u0026#39;} print(unique_consonants) # {\u0026#39;p\u0026#39;, \u0026#39;r\u0026#39;, \u0026#39;g\u0026#39;, \u0026#39;m\u0026#39;, \u0026#39;n\u0026#39;} 📌 最佳实践: 当你需要对一个列表进行快速去重时，list(set(original_list)) 是最高效的方法。\n字典 (Dictionary): 灵活映射 # 字典是 Python 的主力数据结构，它存储的是键-值 (key-value) 对。每个键必须是唯一的且不可变的。自 Python 3.7 起，字典会保持元素的插入顺序。\n核心特性与操作:\n快速查找: 通过键来索引值，时间复杂度接近 O(1)。 动态性: 可以随时添加、修改或删除键值对。 常用方法: get(key, default): 安全地获取值，若键不存在则返回 default 值（默认为 None）。 keys(): 返回所有键的视图。 values(): 返回所有值的视图。 items(): 返回所有 (键, 值) 对的视图，常用于循环。 代码实践:\n# 创建字典 tel = {\u0026#39;jack\u0026#39;: 4098, \u0026#39;sape\u0026#39;: 4139} tel[\u0026#39;guido\u0026#39;] = 4127 # 添加新条目 print(tel) # {\u0026#39;jack\u0026#39;: 4098, \u0026#39;sape\u0026#39;: 4139, \u0026#39;guido\u0026#39;: 4127} # 安全访问 print(tel.get(\u0026#39;sape\u0026#39;)) # 4139 print(tel.get(\u0026#39;non_exist\u0026#39;)) # None # 字典推导式 power_of_two = {x: 2**x for x in range(5)} print(power_of_two) # {0: 1, 1: 2, 2: 4, 3: 8, 4: 16} 操作数据结构的通用技巧 # 掌握了数据结构本身后，我们还需要一些通用的“招式”来高效地操作它们。\ndel 语句：通用的删除利器 # del 是一个通用的 Python 语句 (Statement)，而非特定数据结构的方法 (Method)。它的适用范围非常广泛，是 Python 语言核心语法的一部分，其核心作用是“解除绑定”——即移除一个名称与一个对象之间的关联。\n删除列表元素或切片: del my_list[0], del my_list[2:5] 删除字典键值对: del my_dict['key'] 删除整个变量: del my_variable (从命名空间中移除) 辨析 del, .pop(), 和 .remove()\n操作方式 del list[i] list.pop(i) list.remove(value) 作用目标 索引或切片 索引 (默认为-1) 第一个匹配的值 返回值 None (无) 被删除的元素 None (无) 核心场景 静默地删除，不关心其值。 需要使用被删除的元素。 只知道值，不知道位置。 更 Pythonic 的遍历 # Python 提供了多种优雅的循环技巧，让代码更具可读性。\n遍历字典: 使用 .items() 同时获取键和值。\nknights = {\u0026#39;gallahad\u0026#39;: \u0026#39;the pure\u0026#39;, \u0026#39;robin\u0026#39;: \u0026#39;the brave\u0026#39;} for k, v in knights.items(): print(k, v) 遍历序列时获取索引: 使用 enumerate()。\nfor i, v in enumerate([\u0026#39;tic\u0026#39;, \u0026#39;tac\u0026#39;, \u0026#39;toe\u0026#39;]): print(i, v) # 0 tic, 1 tac, 2 toe 同时遍历多个序列: 使用 zip() 将它们“拉”在一起。\nquestions = [\u0026#39;name\u0026#39;, \u0026#39;quest\u0026#39;, \u0026#39;color\u0026#39;] answers = [\u0026#39;lancelot\u0026#39;, \u0026#39;the holy grail\u0026#39;, \u0026#39;blue\u0026#39;] for q, a in zip(questions, answers): print(f\u0026#39;What is your {q}? It is {a}.\u0026#39;) 总结：选择正确的工具 # 选择哪种数据结构，取决于具体需求。下面是一个快速参考：\n数据结构 可变性 (Mutable) 有序性 (Ordered) 唯一性 (Unique) 核心用途 列表 (List) ✅ 是 ✅ 是 ❌否 通用、灵活的元素序列 元组 (Tuple) ❌否 ✅ 是 ❌否 不可变的数据记录，用作键 集合 (Set) ✅ 是 ❌否 ✅ 是 去重、成员测试、数学运算 字典 (Dict) ✅ 是 ✅ 是 (自 3.7) 键唯一 存储键值对，快速查找 ","date":"2022-11-04","externalUrl":null,"permalink":"/blogs/python/base/python-data-structure/","section":"Blog","summary":"","title":"Python 数据结构","type":"blogs"},{"content":"","date":"2022-11-04","externalUrl":null,"permalink":"/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/","section":"Tags","summary":"","title":"数据结构","type":"tags"},{"content":" 从信息论视角重塑图表示：GIB与AD-GCL如何打造更鲁棒的GNN # 图神经网络（GNN）已成为处理图结构化数据的强大工具，在社交网络、药物发现、知识图谱等众多领域取得了巨大成功。然而，标准的GNN模型在看似强大的背后，隐藏着一个致命的弱点：它们对微小的扰动（Perturbation）异常敏感。无论是对图结构进行微小的修改，还是对节点特征添加一些噪声，都可能导致GNN的预测结果发生灾难性的错误 [1]。\n这种脆弱性引发了一个根本性的问题：一个好的图表示（Graph Representation）究竟应该是什么样的？ 仅仅在测试集上取得高精度就足够了吗？\n普渡大学Pan Li教授团队的工作从信息论的视角出发，对这一问题进行了深刻的反思，并提出了**图信息瓶颈（Graph Information Bottleneck, GIB）和对抗性图对比学习（Adversarial Graph Contrastive Learning, AD-GCL）**两大核心思想，为构建更鲁棒、更泛化的GNN模型指明了方向。\n1. 问题的根源：GNN学到了什么？ # GNN的脆弱性根源在于，在强大的过参数化（Over-parameterization）能力下，模型为了拟合训练数据，往往会学到大量与预测目标不相关的信息（Irrelevant Information）。\n可以将输入数据D（包括图结构A和节点特征X）所包含的信息，与预测目标Y所需的信息，用一个韦恩图来表示：\n最小充分信息（Minimal Sufficient Info.）: 图中红色和蓝色区域的交集。这是对预测Y有用且必要的信息。 不相关信息（Irrelevant Info.）: 蓝色区域中不与红色区域相交的部分。这部分信息与Y无关，但却是输入数据D的一部分。 一个理想的表示Z，应该只包含最小充分信息。然而，标准GNN在训练时，会不可避免地将大量不相关信息也编码到表示Z中。这些不相关信息就像“噪声”，虽然在干净的训练集上有助于区分样本，但在面对攻击或分布外数据时，就会成为模型的“阿喀琉斯之踵”，极大地影响模型的鲁棒性。\n因此，一个好的图表示，不仅要性能优越，更要具备鲁棒性和稳定性。其核心在于——捕获最小化的充分信息（Capturing the minimal sufficient information）。\n2. 监督学习下的理想解：图信息瓶颈 (GIB) # 信息瓶颈（Information Bottleneck）理论 [2] 为实现这一目标提供了数学框架。其核心思想是在“压缩”和“预测”之间找到一个最佳平衡点。\n将这一原理应用于图数据，提出了图信息瓶颈（GIB）[3]。其目标是学习一个表示Z，它满足两个条件：\n充分性 (Sufficiency): Z需要包含尽可能多关于预测目标Y的信息。 最小性 (Minimality): Z需要尽可能“忘记”原始输入数据D的信息，即对D进行最大程度的压缩。 这两个目标通过优化以下目标函数来实现，其中I(·;·)代表互信息（Mutual Information）：\n$$ \\min_{p(Z|D)} [-I(Y; Z) + \\beta I(D; Z)] $$ max I(Y; Z)（最大化与Y的互信息）：等价于最小化I(Y; Z)，确保表示的预测能力。 min I(D; Z)（最小化与D的互信息）：通过压缩，强制表示丢弃不相关信息，提升鲁棒性。 β是一个权衡系数，用于平衡预测性能和压缩程度。 GIB原则为构建鲁棒GNN提供了一个理论上界。实验证明，在监督学习场景下，基于GIB训练的GNN在面对对抗性攻击和随机噪声时，其鲁棒性远超传统GNN模型。\n3. 从监督到自监督：对抗图对比学习 (AD-GCL) # GIB虽好，但它有一个致命的依赖：需要任务标签Y。在许多现实场景中，获取高质量的标签是极其昂贵甚至不可能的，这限制了GIB的广泛应用。\n此时，学界自然会转向自监督学习，尤其是目前最主流的图对比学习（Graph Contrastive Learning, GCL）[4]。GCL的核心思想是，一个图经过数据增强（如图/边/属性的扰动）后，其“身份”不应改变。因此，模型的目标是最大化同一个图的两个不同增强视图（view）的表示之间的一致性（或互信息）。\n$$ \\max_{f} I(f(t_1(G)); f(t_2(G))) $$这本质上是一个InfoMax原则——最大化信息。然而，InfoMax原则本身存在问题。为了最大化两个视图的互信息，模型可能仍然会走“捷径”，去学习那些虽然与任务无关、但在不同视图中保持一致的“噪声”或“伪影”作为识别信号。实验表明，如果用随机标签来监督一个InfoMax模型，它依然能很好地区分不同图，但其学到的表示对于真实任务毫无泛化能力。\n这揭示了InfoMax与鲁棒性之间的内在矛盾。\n如何将GIB的“最小化信息”思想融入无需标签的GCL框架中？答案是：通过对抗学习让数据增强本身变得可学习。\n由此，提出了对抗图对比学习（AD-GCL）[5]。它将GCL从一个单纯的最大化问题，转变为一个生成器（Augmenter）和编码器（Encoder）之间的Min-Max对抗游戏。\n$$ \\min_{T \\in \\mathcal{T}} \\max_{f} I(f(G); f(t(G))) \\quad \\text{where } t(G) \\sim T(G) $$这个过程可以理解为：\n编码器 (f)：努力工作，试图最大化原始图G和增强视图t(G)表示之间的一致性，以识别出图的身份。 增强器 (T)：扮演“对手”的角色，智能地生成一个“最难”的增强视图t(G)，试图最小化这种一致性，让编码器难以识别。 在这场对抗博弈中，增强器会丢弃掉所有冗余、不相关的信息，只保留最核心、最本质的结构和特征。为了在这种“最苛刻”的条件下依然能识别出图的身份，编码器被迫只能学习那些区分图身份的最小充分信息。\n通过这种方式，AD-GCL在没有标签Y的情况下，巧妙地实现了GIB的“最小化充分表示”的目标。实验结果表明，AD-GCL在无监督和迁移学习任务上取得了SOTA性能，尤其在多个数据集上显著优于传统的、基于固定增强策略的GCL方法。\n4. 另一维度的思考：距离编码 # 除了信息论层面的鲁棒性问题，标准GNN还存在固有的结构表达能力缺陷，例如无法区分某些同构图、无法有效捕获节点间的相对位置信息等。\n为了解决这个问题，该团队还探索了距离编码（Distance Encoding）[6]。其核心思想是：\n预先计算一些能够反映图结构拓扑的特征，例如节点对之间的最短路径距离（Shortest Path Distance, SPD）。 将这些预计算出的结构特征（距离编码）作为额外的输入，与原始节点特征拼接在一起，再送入GNN进行学习。 这种简单的“插件式”增强，能直接为GNN提供它们本身难以捕获的、宝贵的拓扑和位置信息，从而在链路预测、图分类等多个任务上大幅提升模型性能。\n总结 # Pan Li教授团队的工作为重塑图表示学习提供了全新视角。\n理论基石 - GIB: 指出了理想图表示的核心是捕获最小充分信息，并给出了监督学习下的数学框架，为鲁棒性研究提供了理论指导 [3]。 实践飞跃 - AD-GCL: 通过巧妙的对抗性学习框架，将GIB的思想推广到更具挑战性、也更具实用价值的自监督对比学习中，让模型在没有标签的情况下，学会“在对抗中丢弃冗余，保留精华” [5]。 能力拓展 - 距离编码: 提出了一个正交于信息瓶颈思想的有效技巧，通过直接注入结构信息来弥补GNN的内在表达能力缺陷 [6]。 这些工作不仅在学术上具有开创性，也为工业界构建更可靠、更强大的图智能应用提供了坚实的理论基础和可行的技术路径。\n参考文献 # [1] Zügner, D., Akbarnejad, A., \u0026amp; Günnemann, S. (2018). Adversarial attacks on neural networks for graph data. In Proceedings of the 24th ACM SIGKDD International Conference on Knowledge Discovery \u0026amp; Data Mining.\n[2] Tishby, N., Pereira, F. C., \u0026amp; Bialek, W. (2000). The information bottleneck method. arXiv preprint physics/0004057.\n[3] Wu, T., Ren, H., Li, P., \u0026amp; Leskovec, J. (2020). Graph Information Bottleneck. In Advances in Neural Information Processing Systems 33.\n[4] You, Y., Chen, T., Sui, Y., \u0026amp; Chen, M. (2020). Graph contrastive learning with augmentations. In Advances in Neural Information Processing Systems 33.\n[5] Suresh, S., Hao, C., Neville, J., \u0026amp; Li, P. (2021). Adversarial Graph Augmentation to Improve Graph Contrastive Learning. In Advances in Neural Information Processing Systems 34.\n[6] Li, P., Wang, Y., Wang, H., \u0026amp; Leskovec, J. (2020). Distance encoding: Design provably more powerful neural networks for graph representation learning. In Advances in Neural Information Processing Systems 33.\n","date":"2022-10-09","externalUrl":null,"permalink":"/blogs/kg/modular-manifolds-thinking-machines/","section":"Blog","summary":"","title":"GIB与AD-GCL如何打造更鲁棒的GNN","type":"blogs"},{"content":"","date":"2022-10-09","externalUrl":null,"permalink":"/tags/gnn/","section":"Tags","summary":"","title":"GNN","type":"tags"},{"content":" PDF: Multimodal Data Enhanced Representation Learning for Knowledge Graphs\n1. 引言 # 1.1 研究背景 # 知识图谱（Knowledge Graph, KG）以结构化的形式存储人类知识，通常以三元组 \\((h, r, t)\\) 表示，其中 \\(h\\) 为头实体，\\(t\\) 为尾实体，\\(r\\) 为关系。知识表示学习（Representation Learning）旨在将实体和关系映射到低维连续向量空间中，以便于计算和推理。\n1.2 问题陈述 # 现有的主流方法（如 TransE, TransH, TransR 等）主要基于 结构化知识（Structure Knowledge），即仅利用实体间的关系约束来学习嵌入。这种方法存在两个主要局限：\n信息利用率低：忽略了实体自身包含的丰富信息（如视觉图像、文本描述）。如图像中不仅包含显式知识（如“椅子有腿”），还包含隐式关联（如“蝴蝶与花高度相关”）。 缺乏零样本能力：无法处理未出现在训练集三元组中的实体（Out-of-Knowledge-Base, OOKB），即无法为新实体生成表示。 Figure1 图像数据中的显式结构化知识（左）与隐式语义关联（右） 1.3 论文目标 # 为了解决上述问题，论文提出了一种名为 TransAE 的新模型。该模型结合了 多模态自动编码器（Multimodal Autoencoder） 与 TransE 模型，旨在联合学习结构化知识与多模态（视觉和文本）知识，从而提高表示学习的质量，并赋予模型处理未见实体的能力。\n2. 核心方法：TransAE 模型 # TransAE 的核心思想是利用自动编码器的中间隐藏层作为实体的嵌入表示（Embedding），并同时约束该表示满足 TransE 的几何结构关系。\nFigure2 TransAE 整体架构示意图 注：模型输入图像和文本特征，通过自动编码器学习实体表示，同时结合 TransE 的损失函数进行联合训练。\n2.1 知识提取与预处理 (Knowledge Extraction) # 在输入模型之前，论文首先对多模态数据进行特征提取：\n视觉知识：使用在 ImageNet 上预训练的 VGG16 Net。提取全连接层的输出作为图像特征向量，维度固定为 4096。 文本知识：使用 PV-DM (Doc2Vec) 模型。该模型能综合考虑词序和语义上下文，将变长的文本描述映射为固定长度的向量，实验中设定维度为 100。 2.2 多模态自动编码器 (Multimodal Autoencoder) # 模型的主体是一个前馈神经网络构成的自动编码器，包含编码器（Encoder）和解码器（Decoder）。\n编码阶段 (Encoder) # 编码器负责将不同模态的特征融合并压缩为联合表示。\n输入层：接收图像特征 \\(v_i^{(1)}\\) 和文本特征 \\(v_t^{(1)}\\)。 第一隐藏层：分别对图像和文本进行非线性映射，降低维度。 $$ v_i^{(2)} = f(W_i^{(1)} \\times v_i^{(1)} + b_i^{(1)}) \\tag{1} $$ $$ v_t^{(2)} = f(W_t^{(1)} \\times v_t^{(1)} + b_t^{(1)}) \\tag{2} $$ 第二隐藏层（瓶颈层）：将处理后的图像和文本特征进行拼接（Concatenate），然后映射到联合空间。这一层的输出 \\(v^{(3)}\\) 即为最终的实体嵌入表示。 $$ v^{(3)} = f(W^{(2)} \\times (v_i^{(2)} \\oplus v_t^{(2)}) + b^{(2)}) \\tag{3} $$ 其中 \\(\\oplus\\) 表示拼接操作，\\(v^{(3)}\\) 将直接参与 TransE 的计算。 解码阶段 (Decoder) # 解码器结构与编码器对称，旨在从联合表示 \\(v^{(3)}\\) 重构原始输入，以确保嵌入向量保留了足够的多模态信息。\n解耦层：将 \\(v^{(3)}\\) 映射回分离的模态空间。 $$ v_i^{(4)} = f(W_i^{(3)} \\times v^{(3)} + b_i^{(3)}) \\tag{4} $$ $$ v_t^{(4)} = f(W_t^{(3)} \\times v^{(3)} + b_j^{(3)}) \\tag{5} $$ 输出层（重构层）：恢复原始维度的特征。 $$ v_i^{(5)} = f(W_i^{(4)} \\times v_i^{(4)} + b_i^{(4)}) \\tag{6} $$ $$ v_t^{(5)} = f(W_t^{(4)} \\times v_t^{(4)} + b_j^{(4)}) \\tag{7} $$ 其中 \\(f\\) 为激活函数（本文使用 Sigmoid）。\n重构损失 (\\(L_a\\)) # 自动编码器的目标是最小化输入与输出之间的差异： $$ L_a = \\|v_i^{(1)} - v_i^{(5)}\\|_2^2 + \\|v_t^{(1)} - v_t^{(5)}\\|_2^2 \\tag{8} $$ 2.3 结构化知识学习 (TransE 模块) # 论文利用 TransE 的假设指导 \\(v^{(3)}\\) 的学习。对于三元组 \\((h, r, t)\\)，其嵌入向量应满足 \\(h + r \\approx t\\)。\nTransAE 定义结构损失函数 \\(L_e'\\) 为： $$ L_e' = \\sum_{(h,r,t) \\in S} \\sum_{(h',r,t') \\in S'} \\max(0, [\\gamma + d(v_h^{(3)}, r, v_t^{(3)}) - d(v_{h'}^{(3)}, r, v_{t'}^{(3)})]) \\tag{12} $$ \\(v_h^{(3)}, v_t^{(3)}\\)：由自动编码器生成的头、尾实体表示。 \\(r\\)：关系嵌入（随机初始化并随模型训练）。 \\(d(\\cdot)\\)：距离度量函数（L1 或 L2 范数）。 \\(\\gamma\\)：间隔超参数（Margin）。 \\(S'\\)：负采样集合（通过随机替换头或尾实体生成）。 2.4 联合训练目标 # TransAE 的最终目标函数 \\(L\\) 结合了多模态重构误差、结构化误差以及正则化项： $$ L = L_a + \\beta L_e' + \\alpha \\Omega(\\theta) \\tag{13} $$ \\(\\beta\\)：平衡结构化知识与多模态知识权重的超参数。 \\(\\alpha \\Omega(\\theta)\\)：参数正则化项，用于防止过拟合。 这种设计使得 \\(v^{(3)}\\) 既能通过 \\(L_a\\) 捕获实体的视觉和文本特征，又能通过 \\(L_e'\\) 适应知识图谱的结构约束。\n3. 实验验证 # 3.1 实验设置 # 数据集：作者构建了 WN9-IMG-TXT 数据集。 基于 WN18 的子集 WN9-IMG。 规模：6,555 个实体，9 种关系，11,741 个训练三元组。 多模态数据：每个实体关联最多 10 张 ImageNet 图片和一段 WordNet 文本定义。 基线模型：TransE, TransH, TransR, TransD, RESCAL, HolE, DistMult, ComplEx 以及多模态模型 IKRL, DKRL。 参数配置：使用 RMSProp 优化器，学习率 \\(\\lambda=0.001\\)，\\(\\beta=0.4\\)。 3.2 链接预测 (Link Prediction) # 该任务预测三元组缺失的头实体或尾实体。评估指标为 Mean Rank (越低越好) 和 Hits@10 (越高越好)。\n实验结果（Filter 设置下）：\nTransAE：Mean Rank 17，Hits@10 94.2%。 TransE：Mean Rank 165，Hits@10 87.1%。 IKRL (Union)：Mean Rank 21，Hits@10 93.8%。 结论：TransAE 在各项指标上均优于仅使用结构信息的基线模型，且略优于同类多模态模型 IKRL。这表明融合文本和视觉信息能显著提升实体的表示质量。\n3.3 三元组分类 (Triplet Classification) # 该任务判断一个给定的三元组是否正确。\nTransAE 准确率：97.9%。 相比 IKRL (96.9%) 和 TransR (95.3%) 均有提升。证明模型能有效区分正负样本。 3.4 零样本学习 (Zero-shot Learning / OOKB) # 这是 TransAE 的核心优势。实验将数据集分为训练集和测试集，测试集中包含训练集中未出现的实体（OOKB实体）。\n原理：对于 OOKB 实体，虽然没有结构化训练数据（三元组），但可以通过其图像和文本输入到训练好的 Autoencoder 中，直接计算出 \\(v^{(3)}\\) 作为嵌入表示。 结果： 实体预测：Mean Rank 约为 290（在 6555 个实体中），这是一个具有实际意义的结果，而传统 TransE 等方法在此任务中无法进行预测（相当于随机猜测）。 分类准确率：对于包含 OOKB 实体的三元组，分类准确率达到 90.4%。 3.5 多模态权重分析 # 作者调整了超参数 \\(w\\)（对应公式中的权重比例），分析多模态重构损失 \\(L_a\\) 对训练的影响。\nFigure3 不同多模态自动编码器损失权重下的模型训练收敛曲线对比 结果显示，随着自动编码器损失权重的增加（即更重视多模态信息），模型的收敛速度变快，且最终性能更好。这证实了引入外部知识有助于提升学习效率和质量。\n4. 论文贡献与展望 # 4.1 核心贡献总结 # 根据论文陈述及实验结果，本研究的实际产出包括：\n统一的表示框架：TransAE 成功在一个端到端的模型中联合了视觉、文本和结构化知识，通过共享隐藏层实现特征融合。 性能提升：在标准 KG 任务（链接预测、分类）上，超越了传统的结构化方法及部分多模态方法。 零样本泛化能力：验证了模型利用多模态特征为未知实体（OOKB）生成有效表示的能力，解决了传统 KG 嵌入方法的冷启动问题。 4.2 局限性与未来工作 # 作者在结论部分指出了当前工作的潜在改进空间：\n图文不匹配问题：目前的视觉和文本知识来自不同源，可能存在语义不一致，导致表示学习受损。未来可研究如何匹配和对齐图文信息。 噪声处理：提取视觉特征时目前仅关注每个实体的单张或多张图片，忽略了图片中可能包含的复杂关系或其他实体。未来可探索从开放域图像中提取更精细的关系信息。 5. 总结 # TransAE 展示了一条通往更丰富知识表示的路径：事实（Facts）不仅存在于图谱的连边中，也蕴含在实体的多模态描述里。通过将感知（视觉/文本）与认知（关系结构）结合，模型不仅“看”得更准，还能推断未曾“见”过的实体，体现了多模态融合在知识图谱领域的巨大潜力。\n","date":"2022-10-05","externalUrl":null,"permalink":"/blogs/kg/transae-multimodal-data-enhanced-representation-learning/","section":"Blog","summary":"","title":"TransAE：基于多模态数据增强的知识图谱表示学习","type":"blogs"},{"content":"","date":"2022-10-05","externalUrl":null,"permalink":"/tags/%E8%A1%A8%E7%A4%BA%E5%AD%A6%E4%B9%A0/","section":"Tags","summary":"","title":"表示学习","type":"tags"},{"content":"","date":"2022-10-05","externalUrl":null,"permalink":"/tags/%E5%A4%9A%E6%A8%A1%E6%80%81/","section":"Tags","summary":"","title":"多模态","type":"tags"},{"content":"","date":"2022-09-27","externalUrl":null,"permalink":"/series/kg%E5%8F%91%E5%B1%95%E6%8A%A5%E5%91%8A2022/","section":"Series","summary":"","title":"KG发展报告（2022）","type":"series"},{"content":"","date":"2022-09-27","externalUrl":null,"permalink":"/tags/%E6%8A%A5%E5%91%8A/","section":"Tags","summary":"","title":"报告","type":"tags"},{"content":"","date":"2022-09-27","externalUrl":null,"permalink":"/tags/%E5%AF%B9%E8%AF%9D/","section":"Tags","summary":"","title":"对话","type":"tags"},{"content":"","date":"2022-09-27","externalUrl":null,"permalink":"/tags/%E6%90%9C%E7%B4%A2/","section":"Tags","summary":"","title":"搜索","type":"tags"},{"content":"","date":"2022-09-27","externalUrl":null,"permalink":"/tags/%E6%8E%A8%E8%8D%90/","section":"Tags","summary":"","title":"推荐","type":"tags"},{"content":"","date":"2022-09-27","externalUrl":null,"permalink":"/tags/%E9%97%AE%E7%AD%94/","section":"Tags","summary":"","title":"问答","type":"tags"},{"content":" 参考：知识图谱发展报告（2022）下载链接 (访问密码: 2096)\n从谷歌、百度等通用搜索，到电商、学术网站的垂直搜索，我们每天都在与各种搜索和推荐系统打交道。它们的核心使命始终如一：精准捕获用户意图，返回匹配的结果。近年来，知识图谱（Knowledge Graph）作为一种能描述世界知识的“超级大脑”，正深刻地改变着搜索与推荐的技术范式。本文将系统性地剖析基于知识的搜索与推荐，带你了解其三大核心任务，并一窥背后的关键技术与未来趋势。\n一、核心任务：我们究竟在“搜索”什么？ # 当我们将知识图谱引入搜索与推荐，其描述的实体和关系本身既可以是搜索的目标，也可以是增强搜索能力的辅助资源。根据用户需求的不同，我们可以将“基于知识的搜索与推荐”的核心任务划分为三大类：\n实体搜索与推荐 (Entity Search \u0026amp; Recommendation): 这是最常见和应用最广泛的任务，目标是从知识图谱中找出用户需要的实体。例如，在电商中搜索一部手机、在学术网站上推荐一篇论文，都属于此范畴。 实体关系搜索 (Entity Relationship Search): 当我们关心的不再是单个实体，而是实体之间的联系时，就进入了关系搜索的领域。其目标是从知识图谱中找出用户关注的一组实体之间的关系，例如挖掘企业间的投资链条，或推荐社交网络中的人际关系路径。 基于关键词的知识探索 (Keyword-based Knowledge Exploration): 这是前两类任务的泛化形式。用户可能没有明确的实体或关系目标，只是想通过一组关键词来探索和理解知识图谱的某个领域。其目标是返回一个与关键词相关的、有意义的知识子图。 二、实体搜索与推荐：找到你想要的那个“点” # 实体搜索与推荐是整个领域技术最成熟、应用最广泛的方向。其输入通常是一组关键词或用户感兴趣的实体，输出则是一个排序后的实体列表。\n1. 核心模型：如何从输入到输出？\n从关键词到实体: 最直接的方法是关键词匹配。但用户往往描述的是实体的属性，而非其精确名称。因此，早期技术会将一个实体的所有属性信息整合成一个“虚拟文档”，再使用经典的文档搜索技术（如VSM、TF-IDF）进行匹配。然而，这种方法无法区分不同属性的重要性。后续的BM25F、FSDM等模型则通过为不同属性赋予不同权重来优化，而排序学习（Learning to Rank）技术则能综合更多特征，得到更优的排序结果。 从实体到实体: 这类任务更偏向于“推荐”。当用户输入一个或一组实体后，系统需要推荐其他相关实体。核心思路有两类：一类是从输入实体出发，在知识图谱上进行“随机游走”，根据到达其他实体的概率来衡量相关性；另一类是分析输入实体集共有的结构特征（如共同的类型、属性、关联路径模式等），再基于这些特征去发现更多相似实体。其中，元路径 (Meta-path) 是一个关键概念，它定义了实体间特定的语义关系模式，极大地提升了推荐的准确性和可解释性。 2. 关键挑战：排序、探索与摘要\n实体排序: 除了与查询的相关性，实体自身的“重要性”也至关重要。这通常通过图论中的中心性算法来度量，如PageRank、HITS等，它们能有效评估一个实体（节点）在整个知识图谱中的影响力。 结果探索: 当搜索结果数量巨大时，如何帮助用户高效筛选？分面搜索 (Faceted Search) 是主流方案。系统自动按实体的关键属性（如品牌、类别、价格区间）进行分组，用户通过点选这些“分面”即可快速过滤掉无关结果。 实体摘要: 在结果列表中，每个实体应该展示哪些信息？实体摘要算法的目标就是自动从海量属性中挑选一个最优子集，既能突出核心信息，又能保证多样性和可读性，帮助用户快速判断其相关性。 三、实体关系搜索：连接知识的“边” # 实体关系搜索关注的是知识图谱中的连接结构，其技术挑战与实体搜索有所不同。\n1. 核心算法：如何找到连接？\n两个实体间: 关系通常被定义为连接两个实体的路径。当需要寻找最重要的关系时，问题可以转化为在带权图上寻找最短路径，可通过经典的Dijkstra等算法解决。 多个实体间: 当输入是多个实体时，目标是找到一个能连通所有这些实体的“最小”子图。这个问题在图论中是一个经典的NP难问题——斯坦纳树 (Steiner Tree) 问题。由于其计算复杂度极高，工业界和学术界通常采用各种近似算法和启发式搜索策略来求解。 2. 关键挑战：关系排序与探索\n如何评价一条关系（一个子图）的重要性？一种直观但有效的方法是，规模越小、结构越紧凑的子图，其所描绘的关系通常越重要。此外，子图中顶点和边的类型多样性、平均相似度等宏观特征，也比简单地累加单个边或点的权重更为有效。\n四、基于关键词的知识探索：在知识海洋中自由漫游 # 这是最灵活但也最具挑战性的任务。用户仅提供一组关键词，系统需要返回一个包含这些关键词的、有意义的知识子图。\n这个问题通常被建模为组斯坦纳树 (Group Steiner Tree, GST) 问题。即每个关键词可能对应图谱中的多个实体节点，目标是找到一棵权和最小的树，它能连接上每个关键词所对应的节点组中的至少一个节点。这同样是一个NP难问题。为了在超大规模知识图谱上实现高效搜索，研究者们设计了大量精巧的近似算法和索引技术（如双向搜索、距离索引、中心标记索引等），用以在保证效果的前提下，大幅提升搜索性能。\n总结与展望 # 基于知识的搜索与推荐已经形成了一个层次分明、技术多样的研究体系。从“找点”（实体搜索）、到“连边”（关系搜索）、再到“探图”（知识探索），技术正不断深化，以满足日益复杂的用户需求。\n关键启示 (Takeaways):\n从“能用”到“好用”：可解释性是下一站。 对于已相对成熟的实体搜索与推荐，未来的研究重点将转向如何提升结果的可解释性，让用户明白“为什么推荐这个给我”。 关系搜索：沉睡的巨人正在苏醒。 实体关系搜索虽然研究历史悠久，但受限于性能瓶颈，应用发展较缓。随着技术成熟，它有望成为知识图谱应用的下一个爆发点。 更大的图景：从图内搜索到图的搜索。 当前研究大多局限于单个给定的知识图谱。然而，在开放的网络环境下，首先需要解决的问题是“应该去哪个知识图谱里搜索？”。数据集搜索（Dataset Search）以及将搜索能力深度内化为知识图谱管理系统的基础组件，将是未来广阔的研究空间。 ","date":"2022-09-27","externalUrl":null,"permalink":"/blogs/kg/knowledge-grap-development-report/search-and-recommendation/","section":"Blog","summary":"","title":"知识图谱发展报告（2022）-基于知识的搜索和推荐","type":"blogs"},{"content":" 参考：知识图谱发展报告（2022）下载链接 (访问密码: 2096)\n从苹果的Siri到微软的小冰，再到各类智能音箱，智能问答与对话系统已深度融入我们的生活。然而，相较于简单的信息检索，要实现真正“听得懂、答得准、有思想”的智能交互，我们仍面临巨大挑战。本文将深入剖析基于知识图谱的问答与对话系统背后的核心技术，梳理其关键科学问题、主流技术路线以及未来的发展趋势。\n一、 智能交互的基石：三大核心科学问题 # 问答与对话系统本质上是连接人类自然语言与机器结构化知识的桥梁。尽管IBM Watson等系统在特定场景取得了成功，但要实现更深层次的语义理解和举一反三的智能，必须攻克以下三个核心科学问题。\n问句语义解析：如何让机器理解人的问题？ 这是首要挑战。当用户提出“路遥写的哪本书获得了矛盾文学奖？”时，系统需要将这句自然语言精准地转换为知识图谱可以执行的形式化查询语句，如 “λx. 作者(x, 路遥), 获奖(x, 矛盾文学奖)”。这个过程不仅涉及词法和句法分析，更需要实体链接（将“路遥”链接到知识图谱中的对应实体）和关系预测等复杂技术。传统方法依赖人工模板，难以应对开放域和多变的提问方式。近年来，基于Seq2Seq、Transformer等深度学习模型的端到端语义解析成为主流，但它们依然面临着如何理解形式语言的层次化结构、如何处理不同知识图谱下的语义歧义以及如何实现领域泛化等难题。\n大规模知识推理：如何补全知识的缺失环节？ 任何知识图谱都存在不完备性。例如，图谱中可能只记录了某人的“工作所在地”，却没有记录“出生地”。但常识告诉我们，一个人的“父母所在地”很可能就是其“出生地”。这种隐含知识对回答“某某出生在哪里？”至关重要。传统的符号逻辑推理虽然精确，但在大规模知识图谱上计算效率低下且难以处理语义鸿沟。因此，以知识图谱表示学习为核心的数值计算推理模式应运而生。它将实体和关系映射到低维向量空间，通过数值计算来预测缺失的链接。然而，如何在保证推理效率的同时提升精度，并有效融合语言文本与知识图谱的优势，是当前研究的重点。\n融合知识图谱的文本生成：如何生成自然流畅的答案？ 用户期待的不是冷冰冰的数据，而是自然、流畅且信息丰富的回答。对于问题“洛国富是哪儿人？”，一个优质的回答是“洛国富出生于巴西，他现在是中国人”，而不是简单地返回“巴西”和“中国”。这要求系统不仅能从知识图谱中检索到“出生地”和“国籍”等关键信息，还能将这些碎片化的知识组织成符合语法和逻辑的自然语言文本。如何有效融合知识图谱的结构化事实，兼顾答复内容的丰富性和语言表达的多样性，是实现更友好交互体验的关键。\n二、 技术路径的演进：从解析、检索到端到端 # 围绕上述科学问题，业界和学术界探索了多种技术路线，并在不断演进。\n基于语义解析的问答方法：这是最经典的方法，核心是将自然语言问句（NLQ）严格翻译成形式化查询语言（如SPARQL）。早期依赖组合范畴语法（CCG）等，近年来则转向基于深度学习的端到端生成模型。为了降低直接生成复杂查询语句的难度，研究者们引入了语义查询图、模板等中间表示，或通过持续学习来适应新领域。\n基于检索排序的问答方法：此方法将问答视为一个语义匹配问题。它通过表示学习，将问题和知识图谱中的候选答案（实体、关系）都转换为向量，然后计算相似度进行排序。这种方法回避了复杂的语义解析，训练简单，泛化性强。其核心在于如何构建高质量的问题和答案表示，以及如何设计高效的排序打分网络。近年来，研究方向已从简单打分网络演进到图神经网络等更复杂的结构，以捕捉更深层次的语义关联。\n任务型对话方法：专注于完成订票、导航等特定任务。其核心是对话状态跟踪（Dialogue State Tracking, DST），即准确识别用户意图和抽取关键信息（槽值）。技术路径已从传统的流水线式（意图识别-\u0026gt;槽位填充-\u0026gt;策略选择-\u0026gt;回复生成）向端到端的神经网络模型演进。随着预训练语言模型的兴起，如何利用GPT-2等模型统一处理多个对话子任务，以及如何通过提示学习（Prompt Learning）实现少样本场景下的对话状态跟踪，成为新的研究热点。\n生成式对话方法：旨在进行开放域的、流畅的对话。基于编码器-解码器框架（如Transformer）的生成模型是当前主流。为了解决其容易生成通用、无信息量回复（如“我不知道”）的问题，研究者们致力于将外部知识（如常识知识库、维基百科）融入生成过程。通过引入注意力机制、拷贝机制或在预训练阶段注入知识，模型可以在生成回复时参考事实信息，从而显著提升对话的知识性和质量。\n三、 未来展望：迈向更复杂、鲁棒和多模态的智能交互 # 当前，问答与对话技术正朝着处理更复杂、更真实场景的方向发展。\n应对更复杂的问答类型：从简单的单事实问答，向需要多步推理（多跳问答）、时间/数值计算的复杂问题演进是必然趋势。 融合多元化的知识来源：真正的智能需要整合来自文本、知识图谱、表格甚至图像的多元信息，实现跨来源的联合推理和问答。 结合符号与数值计算：结合符号推理的逻辑严谨性与深度学习的泛化能力，构建混合式模型，是实现更强泛化能力和可解释性的重要方向。 提升模型的鲁棒性与可解释性：深度学习模型的“黑箱”特性和在对抗攻击下的脆弱性是其走向关键应用的主要障碍。开发更具可解释性、更鲁棒的模型迫在眉睫。 迈向跨模态的知识服务：用户的需求是多模态的，未来的知识服务必须能够理解和回答关于图像、声音等非文本内容的问题，视觉问答（VQA）等领域将成为新的增长点。 总结与启示 # 问答与对话技术的发展历程，是从知识驱动（专家系统）到数据驱动（检索式问答），再到如今知识与数据联合驱动的演进过程。其核心始终是弥合人类语言的灵活性与机器知识的结构化之间的鸿沟。\n核心启示:\n知识图谱是基石：要实现从“信息检索”到“知识服务”的跃迁，知识图谱提供的结构化语义和推理能力不可或缺，它是系统实现深度理解的根基。 混合驱动是未来：单纯依赖符号逻辑或纯粹的数据驱动都存在局限。未来最具潜力的技术路径，在于如何巧妙地将符号推理的严谨性与神经网络的强大拟合能力相结合，并有效处理来自文本、表格、图像等多源异构的知识。 ","date":"2022-09-27","externalUrl":null,"permalink":"/blogs/kg/knowledge-grap-development-report/question-and-dialogue/","section":"Blog","summary":"","title":"知识图谱发展报告（2022）-基于知识的问答与对话","type":"blogs"},{"content":" 参考：知识图谱发展报告（2022）下载链接 (访问密码: 2096)\n在数据驱动的时代，知识图谱（KG）已成为支撑智能搜索、推荐系统、问答机器人等众多AI应用的核心基础设施。然而，随着知识图谱构建过程日益自动化，其质量问题也愈发凸显。一个低质量、充满错误或过时信息的知识图谱，不仅会影响上层应用的性能，甚至可能导致错误的决策。因此，对知识图谱进行系统性的质量评估与管理，已从“锦上添花”变为“必不可少”的关键环节。\n一、 知识图谱质量管理的全景蓝图 # 知识图谱的质量管理并非一蹴而就的收尾工作，而是一个贯穿其整个生命周期的系统工程。我们可以将其划分为构建前、构建中和构建后三个关键阶段，每个阶段都有其独特的质量控制重点。\n图1 知识图谱质量管理全周期概览 构建前：数据源的质量管理。 这是质量控制的源头。核心在于评估数据来源的专业性 (Expertise) 和可信赖性 (Trustworthiness)。一个来自权威机构的数据源显然比来源不明的网络文本质量更高。 构建中：知识获取的质量风控。 在从数据源抽取知识的过程中，无论是基于模式、机器学习还是大规模预训练模型的方法，都可能引入噪音和错误。例如，在基于模式的抽取中，需要警惕“语义漂移”问题。此阶段的重点是控制抽取过程中的风险，确保知识的准确性。 构建后：知识图谱的持续维护。 自动构建的知识图谱不可避免地存在知识缺失、事实错误或信息过时等问题。因此，在图谱初步建成后，必须进行持续的质量维护，包括补全缺失知识、发现并纠正错误知识，以及检测并更新过期知识。 二、 衡量知识图谱质量的核心维度 # 要管理质量，首先要能度量质量。业界已经定义了丰富的评估维度来刻画知识图谱的质量。虽然维度繁多，但以下几个是我们在实践中最为关注的核心维度：\n准确性 (Accuracy): 这是最核心的维度，指数据正确、可靠的程度。它又可细分为： 语法准确性： 指三元组（实体、关系、实体）的表达是否符合预定义的模式（Schema）或语法规则。例如，属性 \u0026lt;foo:gender\u0026gt; 的值只能是 \u0026ldquo;male\u0026rdquo; 或 \u0026ldquo;female\u0026rdquo;。 语义准确性： 指三元组所表达的事实是否与真实世界相符。例如，三元组 \u0026lt;大海, 颜色, 黄色\u0026gt; 在语法上可能没问题，但在语义上是错误的。 完整性 (Completeness): 关注知识图谱是否包含了目标领域所有必要的知识。它通常体现在三个层面：本体完整性（是否涵盖所有必要的概念和关系）、属性完整性（某个类别的实体是否缺失了关键属性）和数量完整性（图谱中的实体数量与真实世界相比的覆盖率）。 一致性 (Consistency): 指图谱中的知识不存在相互矛盾。例如，一个实体不能同时属于两个互斥的概念（如“城市”和“演员”）。一致性是保证知识图谱逻辑自洽的基础。 时效性 (Timeliness): 知识是随时间变化的，尤其是在新闻、金融等动态领域。时效性衡量图谱中的知识是否能及时反映现实世界的最新状态。 代表性 (Representativeness): 也可理解为“偏向性”。它关注知识图谱中的数据分布是否能公正地代表整个目标领域，是否存在系统性偏见。例如，一个全球知识图谱在地理位置或语言上的分布是否均衡。 图2 完整性 vs 代表性 如图所示，完整性和代表性是两个不同的概念。一个图谱可能覆盖了领域内大部分实体（高完整性），但这些实体可能高度集中在某个子区域，导致代表性较差。\n三、 核心质量维度的评估方法 # 针对不同的质量维度，我们有不同的评估方法，从简单的规则校验到复杂的模型推理。\n语法准确性评估： 这类评估相对直接，主要依赖预定义的规则和模式进行验证。例如，可以使用像 W3C RDF Validator 这样的工具，或基于 SWIQA 框架定义一套句法规则、合法值规则等进行自动化检测。 图3 SWIQA 框架语法准确性规则 语义准确性评估： 语义准确性的评估更具挑战性，主要有两大类方法： 基于知识图谱表示学习 (KRL) 的方法： 这类方法通过 TransE、KGTtm 等模型将实体和关系嵌入到低维向量空间。通过计算三元组的得分函数，可以量化其成立的可能性或置信度。得分低的三元组被认为是潜在错误的。 基于外部证据搜索的方法： 这种方法的核心思想是“用事实说话”。对于一个待验证的三元组，例如 (Albert Einstein, award, Nobel Prize for Physics)，系统会自动在网页、文本语料库等外部信源中搜索支持或反对该事实的证据，然后通过机器学习模型（如 FactCheck）综合判断其真伪。 图4 证据搜索模型 FactCheck 四、 知识图谱的质量维护实战 # 评估只是手段，最终目的是通过维护来提升质量。在图谱构建完成后，质量维护主要围绕以下三个方面展开：\n1. 缺失知识的发现与补全\n实体类型补全： 为那些没有被赋予类型的实体找到其正确的上位概念。早期方法依赖统计规则，而现代方法（如 LRN、基于盒式嵌入的模型）则利用深度学习捕捉类型间的复杂依赖关系。 实体关系补全： 这是知识图谱补全的核心任务，即预测实体间缺失的关系。主流方法基于知识图谱表示学习，通过在向量空间中进行计算（如 h + r ≈ t）来预测缺失的头实体、尾实体或关系。 属性/属性值补全： 补全实体缺失的属性或属性值。例如，CyGNet 模型通过融入时间信息，并结合复制（从历史事实中复制）与生成（从全局词表中生成）机制，来预测时序知识图谱中实体的属性值。 图5 CyGNet 模型推理机制 2. 错误知识的发现与纠正\n错误知识的检测与关系补全类似，也大量依赖于知识图谱表示学习。其核心思想是，在模型训练完成后，那些得分极低、与图谱整体结构格格不入的三元组，很可能是错误的。此外，引入规则引导的模型（如 RUGE）可以通过软规则（带置信度的规则）来辅助识别冲突或不合理的知识。\n3. 过期知识的检测与更新\n为了保持图谱的“新鲜度”，需要建立更新机制。常见策略包括：\n基于更新频率预测： 统计历史更新模式，预测哪些知识可能需要更新。 基于时间标签： 利用事实自带的有效期（如总统任期），预测知识的失效时间。 基于热点事件发现： 监控社交媒体或新闻热点，当与图谱中实体相关的热词出现时，触发对其相关信息的更新。 总结与展望 # 知识图谱的质量管理是一个复杂但至关重要的系统工程。它要求我们从数据源头开始把关，在构建过程中严密风控，并在建成后进行持续的评估与迭代维护。\n核心启示 (Takeaways):\n质量是全生命周期的议题： 高质量的知识图谱无法一蹴而就，必须将质量控制的理念融入到从数据源选择到持续维护的每一个环节。 评估与维护相辅相成： 清晰的质量维度和有效的评估方法是质量维护的前提；而补全、纠错、更新等维护手段则是提升质量的最终落点。 ","date":"2022-09-26","externalUrl":null,"permalink":"/blogs/kg/knowledge-grap-development-report/quality-assessment-and-management/","section":"Blog","summary":"","title":"知识图谱发展报告（2022）-质量评估与管理","type":"blogs"},{"content":"","date":"2022-09-26","externalUrl":null,"permalink":"/tags/%E8%B4%A8%E9%87%8F%E8%AF%84%E4%BC%B0/","section":"Tags","summary":"","title":"质量评估","type":"tags"},{"content":"","date":"2022-09-25","externalUrl":null,"permalink":"/tags/%E6%9F%A5%E8%AF%A2/","section":"Tags","summary":"","title":"查询","type":"tags"},{"content":"","date":"2022-09-25","externalUrl":null,"permalink":"/tags/%E5%AD%98%E5%82%A8/","section":"Tags","summary":"","title":"存储","type":"tags"},{"content":" 参考：知识图谱发展报告（2022）下载链接 (访问密码: 2096)\n知识图谱（Knowledge Graph）通过图（Graph）结构来描述现实世界中的“实体”、“属性”及实体间的“关系”，已成为AI领域不可或缺的基础设施。其背后强大的数据模型——资源描述框架（RDF），为海量、异构的网络资源提供了标准化的描述方式。然而，当知识图谱的规模从百万级增长到百亿甚至千亿级三元组时，如何高效地存储和查询这些数据，便成为一个核心的技术挑战。本文将深入探讨基于RDF模型的知识图谱存储与查询技术，剖析从传统关系型数据库方案到原生图数据库，再到面向未来的分布式系统的演进路径。\nRDF与SPARQL：知识图谱的数据与语言 # RDF是W3C制定的标准，用于描述网络资源。在RDF模型中，任何实体（如哲学家“亚里士多德”）、属性（如姓名）或事件都可以被看作一个“资源”，并通过唯一的国际化资源标识符（IRI）来标识。知识由一个个“陈述”构成，每个陈述都遵循一个简单的三元组（Triple）结构：（主体 Subject, 谓词 Predicate, 客体 Object）。\n例如，在描述亚里士多德生平的事实中：\n(Aristotle, placeOfDeath, Chalcis) 表示 “亚里士多德” 死于 “卡尔基斯”。 (Aristotle, name, \u0026quot;Aristotle\u0026quot;@en) 表示 “亚里士多德” 的名字是 \u0026ldquo;Aristotle\u0026rdquo;（英文）。 图1 示例 RDF 资源 当海量的三元组汇集在一起时，就形成了一个庞大的RDF数据集。为了从中检索信息，W3C同样定义了标准的查询语言——SPARQL。它类似于关系数据库的SQL，允许用户通过声明式的语法来描述需要查询的数据模式。\n图2 示例 RDF 三元组 从数据管理的角度看，RDF数据天然形成一个有向标记图：实体和字面值是图中的节点，而谓词则是连接节点的有向边的标签。因此，一个SPARQL查询的本质，可以被看作是在庞大的RDF数据图上，寻找与查询模式相匹配的子图（Subgraph Matching）。\n图3 示例 RDF 数据图 图4 示例 SPARQL 查询图 关系型数据库的探索：用传统武器应对新挑战 # 在知识图谱技术发展的早期，研究者们自然地想到了利用成熟的关系型数据库（RDBMS）来存储RDF数据。这种思路的核心在于如何将RDF的三元组模型映射到关系模型的二维表结构上。\n1. 简单三元组表 (Simple Triple Table) # 这是最直观的方法：创建一个包含三列（Subject, Predicate, Object）的巨大表格来存储所有三元组。这种方法通用性好，但性能是其致命弱点。复杂的SPARQL查询通常需要对这张巨型表进行多次自连接（Self-Join），这在RDBMS中是极其昂贵的操作，导致查询效率低下。\n2. 结构化存储的尝试：属性表 # 为了减少连接操作，研究者提出了**水平存储（Horizontal Schema）或属性表（Property Table）**方案。其思想是将每个主体（Subject）作为表中的一行，每个谓词（Predicate）作为一个列。这样，查询一个实体的多个属性就无需连接操作。然而，这种方法的弊端同样明显：\n稀疏性：知识图谱中的谓词种类繁多，会导致表拥有大量列，而大部分单元格都是空值（NULL）。 多值问题：一个属性可能对应多个值（如mainInterest），在关系表中难以优雅地处理。 灵活性差：新增属性意味着需要修改表结构（ALTER TABLE），成本高昂。 Jena和Oracle等系统通过聚类或类型信息将相似的三元组组织到不同的属性表中，一定程度上缓解了问题，但并未从根本上解决。\n3. 性能优化的策略：垂直划分与全索引 # 垂直划分 (Vertical Partitioning)：该策略为每个谓词（Predicate）创建一个独立的双列表（Subject, Object）。这种方法（如SW-Store采用）将自连接转化为了不同表之间的连接，效率有所提升。但其缺点是，当SPARQL查询中的谓词是变量时，该方法就难以应对了。 全索引策略 (Exhaustive Indexing)：为了极致加速连接操作，RDF-3x和Hexastore等系统提出为三元组的所有六种排列（SPO, SOP, PSO, POS, OSP, OPS）都建立索引。这使得任何连接模式都能通过高效的归并排序连接（Merge-join）来完成。然而，这种策略带来了巨大的存储开销，并且在分布式扩展方面存在天然的局限性。 回归图的本质：原生图模型的存储与查询 # 鉴于RDBMS方案的种种局限，研究界逐渐回归到RDF数据的图本质上，开发了原生图数据库（Native RDF Graph Database）。这类系统将数据原生作为图来存储，并设计了专门针对图的索引和查询算法。\n图5 gStore 进行二进制编码的示例 gStore是这一领域的杰出代表。它将每个资源的属性和关系信息编码成一个二进制位串，称为签名（Signature）。然后，它将所有资源的签名组织成一棵高效的索引结构——VS-tree*。当处理SPARQL查询时，gStore首先利用VS*-tree快速过滤掉大量不相关的候选节点，极大地缩小了搜索空间，然后再在剩余的候选子图上执行精确的匹配。这种基于图结构整体信息的索引方式，在处理复杂查询时，相比传统系统展现出数量级的性能优势。\n图6 在国际通用 RDF 测评数据集上的比较结果 新兴技术与未来展望：分布式与新硬件 # 随着DBpedia、YAGO等知识图谱规模达到数十亿甚至上百亿，单机系统已难以为继，存储和查询技术正朝着分布式架构和利用新硬件的方向发展。\n1. 拥抱新硬件与新理论 # 新硬件：研究人员开始利用GPU强大的并行计算能力（如TripleID-Q）和RDMA（远程直接内存访问）的高带宽、低延迟特性（如Wukong系统）来加速RDF查询处理。 最坏情况下最优连接 (Worst-case Optimal Joins)：源自数据库理论的AGM界为多表连接的结果集大小提供了一个紧密的上界。将这一理论应用于SPARQL查询优化，可以设计出连接顺序更优、中间结果集更小的查询计划，显著提升性能。 2. 从单机到分布式：应对海量数据 # 互联网本身就是一个巨大的、去中心化的知识图谱，这催生了对分布式RDF数据管理技术的需求。目前主要有三条技术路线：\n基于现有云平台：利用Hadoop、Spark等成熟的分布式计算框架。这类方法（如S2RDF, Sparklify）将RDF数据转换并存储在HDFS或Parquet等分布式文件系统上，利用MapReduce或Spark SQL进行查询。它们提供了良好的可扩展性和容错性，但通常缺乏对RDF数据和SPARQL查询的深度优化。 基于数据划分的定制系统：这类方法的核心思想是将RDF图划分成多个子图，部署到不同计算节点上，目标是最小化查询时跨节点的通信。划分策略可以是数据驱动的（如利用METIS等图划分算法），也可以是查询日志驱动的（通过分析历史查询模式，将经常一起被访问的数据放在同一节点）。 联邦查询系统 (Federated Systems)：随着关联开放数据（LOD）的发展，网络上出现了大量独立的RDF数据源。联邦查询系统（如FedX）旨在对这些自治的数据源执行统一的查询。其核心挑战在于：如何在不移动原始数据的情况下，智能地将一个SPARQL查询分解成多个子查询，并分发到正确的数据源上执行，最后再将结果合并。 图7 关联数据 总结与启示 # 知识图谱的存储与查询技术经历了一条清晰的演进路径：从最初借助成熟但存在“水土不服”问题的关系型数据库，到回归数据本质、设计原生图数据库，再到如今为应对互联网级别的海量数据而发展的分布式系统。每一种技术路线都在特定的历史时期解决了关键问题，并为后续发展奠定了基础。\n核心启示:\n没有银弹：无论是关系模型还是图模型，单机还是分布式，每种方案都有其适用场景和优缺点。技术的选择应基于数据规模、数据特点、查询负载和性能要求进行综合权衡。 未来是分布式的：随着数据规模的持续膨胀，分布式架构是必然趋势。未来的研究重点将持续聚焦于更智能的数据划分策略、更高效的分布式查询优化以及与新硬件的深度融合，以真正释放海量知识图谱的巨大潜力。 ","date":"2022-09-25","externalUrl":null,"permalink":"/blogs/kg/knowledge-grap-development-report/storage-and-querying/","section":"Blog","summary":"","title":"知识图谱发展报告（2022）-知识图谱的存储与查询","type":"blogs"},{"content":" 参考：知识图谱发展报告（2022）下载链接 (访问密码: 2096)\n知识图谱（KG）已成为驱动现代AI应用的核心引擎，但其构建过程中固有的不完备性（Incompleteness） 和噪声（Noise） 两大问题，严重制约了其实际效能。知识推理技术正是为了解决这些挑战而生，它旨在从现有数据中推导出新知识或检测逻辑冲突，从而释放知识图谱的全部潜力。本文将深入剖析知识推理的三大核心技术脉络：基于本体的符号推理、基于表示学习的统计推理，以及将二者优势融合的混合推理，并展望其未来发展趋势。\n一、 传统推理之路：基于本体的符号推理 # 符号推理是知识推理的经典范式，它依赖于形式化的逻辑（如描述逻辑）和预定义的规则来进行严谨的演绎。这种方法的核心在于利用本体（Ontology）中定义的公理和约束，来丰富和净化知识图谱。\n1. 技术核心：物化与查询改写\n符号推理主要通过两种方式实现：\n本体物化 (Ontology Materialization)：这是一种“预计算”策略，通过前向链推理，将本体中所有隐含的知识（三元组）全部推导出来，并显式地添加到数据集中。这样做的好处是查询时速度快，但缺点也十分明显：当本体复杂或存在循环依赖时，可能会产生无穷尽的推理结果，导致“物化爆炸”。SUMA系统便是一种通过部分物化来规避此问题的代表性工作。 图1 SUMA 系统架构图 查询改写 (Query Rewriting)：与物化相反，这是一种“即时计算”策略。它在查询阶段，根据本体知识将用户的原始查询（如SPARQL）改写成一个更完备的新查询，这个新查询能够从原始数据中直接获取所有（包括隐含的）答案。这种方法常用于虚拟知识图谱（VKG）中，它避免了存储开销，但改写过程本身可能非常耗时，且改写的查询规模可能呈指数级增长。 图2 虚拟知识图谱系统实例 2. 代表性引擎：PAGOdA\nPAGOdA 是一个高效的可扩展推理引擎，它巧妙地结合了物化和即时推理。其核心思想是将大部分计算负载委托给高性能的Datalog引擎（如RDFox），仅在处理关键且复杂的逻辑时，才调用重量级的OWL推理机（如HermiT），从而在完备性和效率之间取得了出色的平衡。\n图3 PAGOdA 系统架构图 总的来说，符号推理逻辑严谨、过程可解释，在保证数据一致性方面表现卓越。但其对高质量本体和规则的依赖、以及固有的计算复杂性，限制了其在超大规模和噪声数据环境下的应用。\n二、 数据驱动的新浪潮：基于表示学习的统计推理 # 随着深度学习的兴起，数据驱动的统计推理为知识推理开辟了新路径。其核心思想是将知识图谱中的实体和关系映射到低维连续的向量空间中，通过向量运算来模拟推理过程。\n1. 知识图谱嵌入 (KG Embedding)\n这是最主流的统计推理方法，旨在为知识图谱补全（Link Prediction）。它通过设计一个评分函数 f(h, r, t) 来评估三元组的置信度。\n经典模型：TransE 模型将关系视为头实体到尾实体的翻译（h + r ≈ t），模型简单高效。但它难以处理对称、一对多等复杂关系。 演进模型：后续的 RotatE 将关系建模为复数空间中的旋转操作，BoxE 则使用“盒子”嵌入来表达更丰富的逻辑关系，显著提升了对复杂关系的建模能力。 图4 不同嵌入模型对不同推理模式的表达能力 2. 图神经网络 (GNNs)\nGNNs通过聚合邻居节点信息来学习实体表示，能更充分地利用图谱的局部结构信息。R-GCN 和 CompGCN 等模型为知识图谱的异构性（即不同的关系类型）设计了专属的聚合机制。GNNs的一大优势是其具备**归纳推理（Inductive Reasoning）**的能力，能够对训练时未出现的新实体进行推理，这在动态变化的知识图谱中尤为重要。GraIL 和 CoMPILE 等工作通过抽取局部子图进行推理，是这一方向的代表。\n图5 R-GCN 和 CompGCN 模型聚合过程示例 3. 本体表示学习 (Ontology Representation Learning)\n此类方法更进一步，不仅学习实例层（ABox）的事实，还致力于将本体层（TBox）的公理和概念层次结构编码到向量空间中。例如，EL Embedding 模型利用高维空间中的球体来表示概念，通过球体之间的位置关系（如包含、相交）来建模概念间的逻辑关系，实现了逻辑与表示的深度融合。\n图6 EL Embedding 可视化的概念表示 统计推理方法泛化能力强，能从数据中自动学习模式，但其“黑盒”特性导致可解释性差，且推理结果不具备符号逻辑的严格保证。\n三、 两全其美：符号与嵌入的混合推理 # 为了结合两者的优点，混合推理应运而生，并迅速成为当前研究的热点。其核心目标是让符号知识指导表示学习，同时用统计模型弥补符号逻辑的脆弱性。\n1. 规则注入嵌入 (Injecting Rules into Embeddings)\n这种思路是将符号规则作为一种“软约束”融入嵌入模型的训练过程中。例如，可以在损失函数中增加一项，惩罚那些不符合逻辑规则的嵌入表示（如KALE模型）；或者利用规则推理出高置信度的新三元组，并将其加入训练数据中，从而迭代地增强模型和知识图谱（如IterE模型）。\n图7 IterE 中详细的推理过程 2. 嵌入赋能推理 (Empowering Reasoning with Embeddings)\n反之，也可以用嵌入的“模糊”能力来“软化”刚性的符号推理过程。在复杂查询回答任务中，GQE 和 Query2box 等模型将逻辑运算符（如与∧、或∨）映射为向量空间中的几何运算（如交集、并集），使得推理过程可以处理不确定性。在定理证明方面，NTP（神经定理证明器）等工作将Prolog的符号推理与实体/关系的相似性计算相结合，使其能够进行更灵活的“模糊匹配”，缓解了因知识图谱不完备导致的推理链中断问题。\n图8 基于 NTP 计算图的示例性构造案例 3. 多跳推理与规则归纳\n混合推理也在更复杂的任务中展现了巨大潜力。在多跳问答中，EmbedKGQA 模型通过将问题和推理路径共同嵌入向量空间，实现了高效的答案查找。在规则归纳（即从数据中学习规则）方面，RuLES 和 NeuralLP 等模型利用嵌入表示来指导规则的搜索和学习过程，让机器能够自动发现高质量的逻辑规则。\n图9 EmbedKGQA 的框架概览 总结与展望 # 知识推理技术正沿着一条从纯符号、纯统计到二者深度融合的路径演进。虽然已经取得了显著进展，但要实现真正大规模、高可靠的工业应用，仍面临诸多挑战。\n核心启示 (Key Takeaways):\n融合是未来：单一范式已无法满足复杂应用的需求。将符号逻辑的严谨性与神经网络的泛化能力相结合的混合推理，是未来最确定的发展方向。 可解释性是关键：如何让数据驱动的推理过程变得透明、可信、可解释，是决定该技术能否在金融、医疗等高风险领域落地的核心。利用符号逻辑增强嵌入向量的可解释性是一个值得探索的研究重点。 从实验室到工业界：未来的工作需要更关注推理算法的平台化和工具化。构建像OpenKG这样开放、友好的项目工具，降低技术使用门槛，对于推动知识推理算法的普及和应用至关重要。 ","date":"2022-09-24","externalUrl":null,"permalink":"/blogs/kg/knowledge-grap-development-report/knowledge-reasoning/","section":"Blog","summary":"","title":"知识图谱发展报告（2022）-知识推理","type":"blogs"},{"content":"","date":"2022-09-24","externalUrl":null,"permalink":"/tags/%E7%9F%A5%E8%AF%86%E6%8E%A8%E7%90%86/","section":"Tags","summary":"","title":"知识推理","type":"tags"},{"content":"","date":"2022-09-23","externalUrl":null,"permalink":"/tags/%E7%9F%A5%E8%AF%86%E8%9E%8D%E5%90%88/","section":"Tags","summary":"","title":"知识融合","type":"tags"},{"content":" 参考：知识图谱发展报告（2022）下载链接 (访问密码: 2096)\n在人工智能领域，知识图谱 (Knowledge Graph) 已成为驱动语义搜索、智能问答和推荐系统等应用的核心动力。然而，这些图谱往往由不同机构、使用不同数据源构建，导致了严重的异构性和冗余。如何将这些散落的知识孤岛融合成一个统一、一致的整体？这便是“知识融合” (Knowledge Fusion) 要解决的核心问题。本文将带你深入剖析知识融合的定义、关键任务、主流技术方法以及未来的发展方向。\n什么是知识融合？一个全局概览 # 知识融合旨在将来自不同知识图谱的实体、概念和关系进行整合，消除异构性与歧义，最终形成一个统一、一致且简洁的知识库，从而实现跨图谱应用的互操作性。\n一个典型的知识融合流程如下图所示，主要包含预处理、匹配和真值发现三大环节，并辅以配置、外部资源和人机交互来提升效果。\n图1 知识融合的常见流程 预处理 (Pre-processing): 对输入的知识图谱进行清洗，并采用分块 (Blocking) 技术将可能匹配的实体对划分到较小的候选中，以避免后续计算的平方级复杂度。 匹配 (Matching): 这是知识融合的核心，根据匹配对象的不同，可细分为本体匹配、实体对齐和实体链接。其关键挑战在于如何从语义上消解不同来源对象间的异构性。 真值发现 (Truth Discovery): 在匹配的基础上，当多个数据源对同一事实有不同描述时（例如，珠穆朗玛峰的高度），此环节旨在从中推断出最可信的“真值”。 核心任务一：本体匹配 (Ontology Matching) # 本体匹配的目标是建立不同知识图谱中模式层（Schema Level）概念之间的语义映射关系，例如将一个图谱中的“作者”概念与另一个图谱中的“创作者”对齐。\n这是一个相对成熟的领域，早期的代表性工作包括 RiMOM 和 Falcon-AO。值得一提的是 LogMap 系统，它不仅因其高可扩展性、逻辑推理与修复能力在2021年荣获了“十年最具影响力论文奖”，近年来还集成了表示学习技术，持续保持着其先进性。\n图2 本体匹配方法 LogMap [Chen et al., 2021] 核心任务二：实体对齐 (Entity Alignment) # 实体对齐（也称实例匹配）侧重于发现不同知识图谱中指代现实世界同一物体的实例（Entities）。例如，将 KG1 中的“北京大学”与 KG2 中的“Peking University”对齐。这是当前知识融合领域的研究热点。\n1. 基于表示学习的主流范式 # 近年来，以知识图谱表示学习为基础的实体对齐方法已成为主流。其通用框架如下图所示，主要包含两大模块：\n图3 基于表示学习的实体对齐框架 表示学习模块 (Embedding Module): 将每个知识图谱内的实体和关系嵌入到低维向量空间中。图神经网络 (GNN) 是当前最常用的技术之一。 对齐模块 (Alignment Module): 利用少量已知的“种子对齐”作为监督信号进行训练，然后通过度量向量相似度来发现新的对齐实体。 代表性工作 Dual-AMN 通过设计关系型注意力的卷积层来捕捉图谱内结构，并巧妙地设置了一组“代理向量”来隐式表示和捕捉图谱间的对齐关系，有效提升了对齐性能。\n2. 应对动态与时序挑战 # 传统的实体对齐方法大多假设知识图谱是静态的，但这与事实不符。为了应对这一挑战，新的研究方向应运而生：\n动态实体对齐: 如 DiNGAI 模型，它首次提出动态实体对齐任务，能够针对知识图谱中不断变化的事实，对实体表示进行高效的局部更新，避免了从头训练的巨大开销。 时序实体对齐: 如 TEA-GNN 模型，它将时间戳信息融入图神经网络，并设计了时间感知的注意力机制来处理带有时效性的事实，实现了在时序知识图谱上的精准对齐。 图4 面向时序知识图谱的实体对齐方法 TEA-GNN 3. 融合多模态与人机协作 # 为了进一步提升对齐的准确性，研究者们也开始探索融合更多维度的信息：\n人机协作: 通过主动学习等技术，用最小的人工标注成本获取最高价值的训练数据。例如，RAC 模型结合深度强化学习和多臂老虎机策略，自适应地选择最高效的查询策略交由人工标注。 图5 基于人机协作的实体对齐方法 RAC 多模态实体对齐: 实体常常关联着图像、数值等信息。MMEA 和 EVA 等工作通过引入图像特征（利用VGG、ResNet等模型提取）来辅助对齐，将不同模态的信息投影到一个公共空间进行互补，有效解决了仅靠文本难以区分的歧义问题。 图6 基于多模态的实体对齐方法 MMEA 核心任务三：真值发现 (Truth Discovery) # 真值发现旨在从多个来源的冲突数据中推断出事实的真值。传统方法主要分为三类：迭代式方法（如 TruthFinder）、最优化方法和概率图模型。\n近年来，深度学习也被用于解决此问题。例如，CASE 将真值发现建模为异构信息网络中的表示学习问题；BAT 则利用图自编码器和注意力机制来聚合信息，预测真值。此外，EvolveT 等工作利用卡尔曼滤波等技术，实现了对流式数据中真值的快速、高效估计。\n核心任务四：实体链接 (Entity Linking) # 实体链接的任务是将自然语言文本中提及的实体（mention）链接到知识图谱中对应的正确实体上。这项任务的核心挑战在于处理“一词多义”（如“苹果”公司 vs. “苹果”水果）和“多词同义”（如“NLP” vs. “自然语言处理”）的歧义性。\n一个完整的实体链接流程通常包括：提及识别、候选生成、候选排序和不可链接预测四个步骤。\n图7 端到端实体链接方法 CHOLAN 现代实体链接模型如 Facebook 的 BLINK 采用“检索-排序”两阶段架构，兼顾了效率和精度。而 CHOLAN 则利用 Transformer 构建了一个端到端模型，将实体提及、上下文、候选实体及其描述信息拼接后，共同输入一个 BERT 模型进行最终决策。\n工具、数据集与技术展望 # 工具与平台: 本体匹配: OAEI 评测平台提供了丰富的工具和系统。 实体对齐: OpenEA 是一个集成了12种主流方法的开源库，框架灵活，易于扩展。 真值发现: CrowdTruthInference 库集成了17种真值推断算法。 图8 基于表示学习的实体对齐开源软件库 OpenEA 技术展望: 大规模知识图谱预训练: 借鉴NLP领域的成功经验，在融合后的大规模知识图谱上进行预训练，为下游任务提供通用的知识表示，尤其有助于解决低资源场景下的问题。 面向动态流式数据: 未来的研究将更多地考虑知识的动态演化，开发面向流式数据的动态实体对齐和真值发现技术。 构建高质量评测基准: 当前的数据集（如 DBP15K）已显陈旧且规模较小。未来亟需构建规模更大、质量更高、场景更复杂（如多模态、跨语言）的新一代评测数据集，以推动该领域的持续发展。 总结与启示 # 知识融合是充分发挥知识图谱价值的关键技术，其研究正从处理静态、单模态数据向动态、多模态、人机协同的复杂场景演进。\n核心启示 (Takeaways): # 技术核心多元化: 知识融合是一个多阶段的系统工程，其中实体对齐是当前最活跃的研究方向，而本体匹配、真值发现、实体链接等任务同样不可或缺。 场景复杂度提升: 未来的技术挑战将更多地集中在动态时序、多模态融合、低资源和人机协作等复杂场景中。 数据与模型双轮驱动: 高质量、大规模的预训练模型和评测基准将是推动知识融合技术未来突破的双重引擎。 ","date":"2022-09-23","externalUrl":null,"permalink":"/blogs/kg/knowledge-grap-development-report/knowledge-fusion/","section":"Blog","summary":"","title":"知识图谱发展报告（2022）-知识融合","type":"blogs"},{"content":"","date":"2022-09-19","externalUrl":null,"permalink":"/tags/%E4%BA%8B%E4%BB%B6%E7%9F%A5%E8%AF%86%E6%8A%BD%E5%8F%96/","section":"Tags","summary":"","title":"事件知识抽取","type":"tags"},{"content":" 参考：知识图谱发展报告（2022）下载链接 (访问密码: 2096)\n在当今信息爆炸的时代，海量非结构化的文本数据（如新闻、报告、社交媒体内容）蕴含着巨大的价值。然而，要让机器理解并利用这些信息，首先需要将其转化为结构化的知识。事件知识获取（Event Knowledge Acquisition）正是实现这一目标的关键技术，它旨在从文本中自动识别所发生的事件及其参与元素，为智能问答、舆情监控、金融风控和自动文摘等下游应用提供坚实的基础。\n本文将系统梳理事件知识获取领域的核心概念、关键任务和技术演进，带你深入了解如何让机器读懂“谁在何时何地做了什么事”。\n一、 什么是事件抽取？ # 根据美国国家标准技术研究所（NIST）在ACE（Automatic Content Extraction）评测中的定义，一个“事件”由两部分构成：\n事件触发词 (Trigger): 标识事件发生的核心词语，通常是动词或名词。 事件元素 (Argument): 事件的参与者，扮演着不同的角色（Role），如时间、地点、施事者、受事者等。 因此，事件抽取（Event Extraction）任务可以分解为两个核心步骤：\n事件类型识别： 识别出文中的触发词，并判断其所属的预定义事件类型（例如，在ACE 2005标准中，定义了“冲突(Conflict)”、“交易(Transaction)”等8大类33小类事件）。 事件元素识别： 在确定事件类型后，根据该类型预定义的模板（Template），从上下文中找出所有对应的事件元素及其扮演的角色。 让我们通过一个实例来直观理解：\n原文： “雅虎公司 9号 宣布 购并 奇摩网站。”\n经过事件抽取，我们可以得到以下结构化信息：\n触发词： 购并 事件类型： Business / Merge-Org (商业 / 企业并购) 事件元素： Org (机构): 雅虎公司 Time (时间): 9号 Org (机构): 奇摩网站 图1 “购并”事件的基本组成要素 这个过程看似简单，但其背后是自然语言处理领域几十年来的持续探索。早期的MUC（Message Understanding Conference）和后来的ACE评测，不仅定义了任务规范，也为学术界提供了宝贵的评测数据集和统一的评价标准，如准确率（Precision, P）、召回率（Recall, R）和F1值。\n$$ \\text{F-Measure} = \\frac{2 \\times P \\times R}{P+R} $$ 二、 事件模式的自动归纳：摆脱人工定义的束缚 # 传统的事件抽取依赖于预先手动定义的事件类型和模板（即事件模式，Event Schema）。这种方式成本高昂，且难以迁移到新的领域。为了解决这一瓶颈，事件模式自动归纳 (Event Schema Induction) 应运而生。其目标是从无标注的纯文本中，自动地发现潜在的事件类型，并归纳出其对应的角色结构。\n例如，通过分析大量文本，系统可以自动发现一个“运输”事件模式，它通常包含“施事者”、“运输的物品”、“目的地”等角色，而无需人工预先告知。\n图2 事件模式实例 实现事件模式归纳的技术主要分为两大流派：\n基于概率图模型的方法： 这类方法借鉴了主题模型（如Latent Dirichlet Allocation, LDA）的思想。它将“事件类型”类比为“主题”，将“事件元素”类比为“词汇”。模型假设每种事件类型都对应一个关于元素角色的概率分布，通过对大量文本进行无监督学习，从而聚类出不同的事件模式。 基于表示学习的方法： 随着深度学习的发展，利用神经网络将事件、触发词和元素表示为低维稠密的向量（Embedding）成为主流。其核心思想是：语义上相似的事件或元素在向量空间中的距离也相近。通过对这些向量进行聚类，同样可以实现事件模式的归纳。这种方法能更好地捕捉上下文的细微语义差异，例如，通过上下文区分“士兵”在“袭击”事件中究竟是“施事者”还是“受事者”。 三、 事件识别与抽取技术的演进 # 在事件模式确定后（无论是手动定义还是自动归纳），下一步就是具体的事件抽取。这项技术也经历了从句子级到文档级的演进。\n1. 句子级事件抽取 # 这是最经典的事件抽取场景，技术路线主要包括：\n基于模式匹配： 早期的系统（如Riloff的AutoSlog）通过人工或半自动构建的词汇-句法模式来抽取事件。这种方法精度较高，但召回率和可移植性有限。 基于机器学习： 这是目前的主流方法，将事件抽取看作一个分类或序列标注问题。 分类方法： 对每个候选触发词和元素进行分类，判断其类型和角色。常用的模型包括最大熵（MaxEnt）、支持向量机（SVM）等。 序列标注方法： 使用隐马尔可夫模型（HMM）、最大熵马尔可夫模型（MEMM）或条件随机场（CRF）等模型，将句子作为一个序列进行联合标注，能更好地捕捉元素之间的依赖关系。 2. 篇章级事件抽取 # 在真实场景中，一个完整事件的各个元素常常分散在文档的多个句子中。这给句子级抽取带来了巨大挑战。篇章级事件抽取（Document-level Event Extraction）旨在解决这一问题。\n跨文档推理： 该方向的代表性工作（Heng Ji, 2008）提出，一个实体在同一个文档簇中倾向于扮演相同的角色（\u0026ldquo;One Argument Role for Cluster\u0026rdquo;）。通过在文档间进行信息聚合和联合推理，可以有效修正和补全句子级的抽取结果。 端到端图方法： 近年来，为了解决元素分散和长距离依赖问题，研究者提出了创新的图模型。例如，Doc2EDAG模型不再依赖触发词，而是将整个任务建模为在一个文档内构建一个基于实体的有向无环图（EDAG）。模型从识别实体开始，逐步扩展路径，将不同句子中的相关实体连接起来，形成完整的事件结构。这种方法在处理金融公告等复杂文档时表现出色。 四、 超越事件本身：挖掘事件间的深层关系 # 现实世界中的事件并非孤立存在，它们之间往往存在着复杂的逻辑关联。事件关系抽取旨在识别这些关系，构建起事件的逻辑网络。\n因果关系 (Causal Relation): 识别事件间的“原因-结果”链条，例如，“飓风肆虐”导致“建筑物倒塌”。 时序关系 (Temporal Relation): 根据TimeML等标准，确定事件发生的先后、包含等13种时间关系，构建事件时序图。 子事件关系 (Sub-event Relation): 识别复杂的宏观事件与其包含的微观子事件之间的构成关系。例如，“袭击”事件可以由“占领”、“射杀”、“摧毁”等一系列子事件构成。 图3 子事件关系示例 五、 为事件建模：事件表示学习 # 为了让机器更好地处理和推理事件，我们需要一种高效的事件表示方法。传统的独热（One-hot）表示存在维度灾难和语义鸿沟问题。事件表示学习（Event Representation Learning）旨在为事件学习一个低维、稠密的向量表示。\n其中，一个代表性的工作是Ding等人提出的张量神经网络（Tensor Neural Network, NTN）。该模型专门为 (施事者, 事件词, 受事者) 这样的三元组结构设计。与普通的关系表示不同，它能精准地捕捉事件的非对称性——“A攻击B”和“B攻击A”是完全不同的事件。NTN通过张量运算，显式地建模了施事者、受事者与事件词之间的复杂、高阶的交互关系，生成信息量更丰富的事件向量。\n图4 张量神经网络结构 总结与启示 # 事件知识获取技术正从处理孤立、简单的文本片段，向着理解完整、复杂的篇章文档演进。回顾其发展历程，我们可以看到清晰的技术趋势：\n从预定义到自动发现： 事件模式自动归纳技术正在逐步降低对人工标注和领域知识的依赖，使系统能适应更广泛的领域。 从句子到篇章： 篇章级的抽取模型，特别是基于图的方法，正成为解决真实世界复杂文档理解的核心。 从孤立到关联： 事件关系抽取使得机器能够构建事件之间的逻辑网络，实现更深层次的文本理解和推理。 从稀疏到稠密： 以神经网络为基础的事件表示学习，为事件的相似度计算、预测和推理提供了强大的数学工具。 未来，随着预训练语言模型的进一步发展，事件知识获取技术必将在精度和泛化能力上达到新的高度，成为连接海量文本与智能应用之间不可或缺的桥梁。\n","date":"2022-09-19","externalUrl":null,"permalink":"/blogs/kg/knowledge-grap-development-report/event-knowledge-extraction/","section":"Blog","summary":"","title":"知识图谱发展报告（2022）-事件知识抽取","type":"blogs"},{"content":"","date":"2022-09-16","externalUrl":null,"permalink":"/tags/%E5%AE%9E%E4%BD%93%E5%85%B3%E7%B3%BB%E6%8A%BD%E5%8F%96/","section":"Tags","summary":"","title":"实体关系抽取","type":"tags"},{"content":" 参考：知识图谱发展报告（2022）下载链接 (访问密码: 2096)\n一、任务定义与研究意义 # 实体关系抽取（Entity Relation Extraction）旨在从非结构化文本中检测并识别出实体之间存在的特定语义关系，并以结构化的三元组（如 \u0026lt;实体1, 关系, 实体2\u0026gt;）形式存储。例如，对于文本“华扬联众数字技术股份有限公司于2017年8月2日在上海证券交易所上市。”，关系抽取系统应输出 \u0026lt;华扬联众数字技术股份有限公司, 上市时间, 2017年8月2日\u0026gt; 和 \u0026lt;华扬联众数字技术股份有限公司, 上市地点, 上海证券交易所上市\u0026gt; 等三元组。\n作为信息抽取的关键环节，实体关系抽取不仅是构建大规模知识图谱的核心技术，也是实现从文本语法分析到语义分析的关键步骤。它为智能信息检索、智能问答、人机交互等应用提供关键支撑，对自然语言处理、机器学习、逻辑推理等多个学科的理论完善与发展具有重要的推动作用。\n二、研究内容与核心挑战 # 实体关系抽取的研究内容主要围绕语义关系表征、抽取数据处理和复杂关系建模三个方面展开。然而，该任务面临着源于自然语言内在特性的三大核心挑战：\n自然语言表达的多样性：同一语义关系可以通过多种不同的文本模式进行表达。例如，“总部位置”关系可以表述为“X的总部位于Y”、“X总部坐落于Y”或“作为X的总部所在地，Y”等，这种表达的多样性对模型的泛化能力提出了极高要求。 关系表达的隐含性：文本中有时并不包含明确的关系标识词，需要通过上下文进行推理。例如，从“蒂姆·库克\u0026hellip;透露了他将带领苹果公司进一步开拓中国市场的讯号”中，虽然没有直接陈述，但可以推断出蒂姆·库克与苹果公司之间存在“CEO”关系。 实体关系的复杂性：真实世界中，一对实体间可能同时存在多种关系（如北京和中国的“首都”、“政治中心”等多重关系），并且某些关系具有时效性（如“夫妻关系”可能因离婚而变为“前夫/前妻关系”）。这种关系的共存性和时空依赖性为精确抽取带来了巨大挑战。 三、技术方法与研究现状 # 实体关系抽取的技术已从早期的基于“特征工程”的机器学习方法，发展到利用远程监督自动标注语料，再到当前由深度学习，特别是预训练语言模型（如BERT、GPT）主导的新范式。以下将从语义关系表征、数据处理和复杂关系建模三个维度介绍其技术现状。\n1. 语义关系表征 # 当前，基于神经网络自动学习关系特征已成为主流。早期工作多采用“流水线（Pipeline）”方法，即先进行实体识别，再进行关系分类。这种方式存在错误传播问题，且忽略了两任务间的内在关联。\n为解决此问题，**联合抽取（Joint Extraction）**模型应运而生，并已成为研究热点。其核心思想是利用实体识别任务来辅助学习更优的关系特征。目前主要有三类联合抽取范式：\n序列标注（Sequence Labeling）：该方法通常在预训练语言模型之上，构建一个统一的标注体系来同时完成实体和关系的抽取。例如，通过设计特定的标签（Tagging Scheme）将关系信息融入到实体标签中。后续研究通过引入分层强化学习、多任务学习或构建层级标注框架来增强实体与关系的交互，并更好地处理关系重叠问题。 表填充（Table Filling）：此范式将句子中的词对视为一个二维表格或矩阵，实体识别任务对应于填充对角线，而关系分类则对应于填充上三角或下三角区域的元素。这种结构天然适合处理实体嵌套和关系重叠问题。后续工作通过引入多头选择机制（Multi-head Selection）、单阶段解码等方式，进一步解决了三元组重叠和暴露偏置（Exposure Bias）问题。 序列生成（Sequence Generation）：该方法将关系抽取任务重新定义为一个序列到序列的生成问题，直接生成包含实体和关系的结构化文本。早期的工作采用带拷贝机制的CopyNet来生成三元组。为了解决长实体生成困难和自回归解码带来的暴露偏置问题，后续研究提出了树状解码、非自回归解码等更优的生成策略，显著提升了模型的性能。 2. 抽取数据处理 # 神经网络是典型的数据驱动模型，高质量的标注数据至关重要。针对人工标注成本高、一致性差的问题，研究界探索了多种数据处理技术。\n远程监督（Distant Supervision）与噪声处理：该技术通过将知识库与非结构化文本对齐，自动生成大量训练数据。其核心挑战在于由对齐假设带来的错误标签噪声。为缓解此问题，研究者提出了基于多示例学习（Multi-instance Learning）的方法，通过引入句子级别的注意力机制或对包内所有句子进行最大池化操作来选择或聚合信息。此外，负样本学习和示例对比学习也被用于直接过滤或抑制噪声样本。 小样本关系抽取（Few-shot Relation Extraction）：为解决真实场景中大量关系类型缺乏标注数据（长尾问题）的挑战，小样本学习被引入关系抽取。通过构建如FewRel等基准数据集，研究者基于原型网络、多级匹配策略和预训练语言模型等技术，探索模型在极少标注样本下的快速泛化能力。 从预训练模型中抽取知识：近年来，研究发现预训练语言模型（PLM）自身记忆了大量的世界知识。通过设计“完形填空”式的查询（如LAMA任务），可以直接从PLM中“探查”出事实性知识。这为将PLM作为一个现成的、开放的知识库进行关系抽取开辟了新的研究方向。 联邦学习（Federated Learning）：针对金融、医疗等领域的隐私保护需求，联邦远程监督关系抽取被提出。它通过跨平台的协作学习和基于集成蒸馏的训练框架，在保护数据隐私的同时，缓解数据噪声问题并降低通信开销。 3. 复杂关系建模 # 传统关系抽取多处理单个句子内的二元简单关系。为适应更复杂的真实场景，研究已向更复杂的单位和关系类型扩展。\n文档级关系抽取（Document-level RE）：当实体对的关系需要整合文档中跨越多个句子的信息才能判断时，就需要进行文档级关系抽取。为此，研究者构建了如DocRED等大规模数据集，并提出利用图神经网络（GNN）来建模实体间的复杂交互和推理路径。 多元、跨文档与开放关系抽取：研究进一步扩展到多元关系（N-ary Relation，涉及两个以上实体）、跨文档关系（Cross-document RE，需从多篇文档中聚合信息）和开放关系抽取（Open RE，旨在发现预定义类别之外的新型关系）。这些研究通过引入基于图LSTM的网络、持续学习（Continual Learning）和原型表示等方法，来解决更具挑战性的抽取任务。 多模态关系抽取：随着多媒体内容的普及，研究开始探索融合文本、图像等多种模态信息进行关系抽取。例如，利用面部图像信息来辅助判断文本中人物的社会关系。 四、发展趋势 # 实体关系抽取技术正朝着更智能、更稳健、更融合的方向发展，主要趋势包括：\n新类别/开放类别上的小样本学习能力：真实场景要求模型能快速学习新知识。利用预训练-提示（Prompt）学习范式，摆脱对大规模微调数据的依赖，实现高效的开放类别小样本关系抽取将是关键方向。 数据隐私保护下的关系可信抽取：在金融、医疗等敏感领域，如何在保护数据隐私的前提下，自动生成大规模可信数据，并训练出鲁棒、高效的关系抽取模型，是技术落地面临的核心挑战。 多模态关系抽取：未来的信息抽取将面向包含丰富布局和多媒体信息的富文本文档。设计能够融合文本、视觉、听觉等多模态信息的预训练模型和抽取框架，是提升抽取能力的重要途径。 数据驱动与知识驱动的融合：单纯的数据驱动方法在达到一定瓶颈后难以提升。模拟人类决策过程，将专家知识（如逻辑规则）与数据驱动的神经网络模型进行深度融合，构建神经符号（Neuro-symbolic）学习框架，是突破当前性能瓶颈的关键挑战。 ","date":"2022-09-16","externalUrl":null,"permalink":"/blogs/kg/knowledge-grap-development-report/entity-relation-extraction/","section":"Blog","summary":"","title":"知识图谱发展报告（2022）-实体关系抽取","type":"blogs"},{"content":"","date":"2022-09-15","externalUrl":null,"permalink":"/tags/%E5%AE%9E%E4%BD%93%E6%8F%90%E5%8F%96/","section":"Tags","summary":"","title":"实体提取","type":"tags"},{"content":" 参考：知识图谱发展报告（2022）下载链接 (访问密码: 2096)\n一、任务定义、目标与研究意义 # 实体是构成世界的基本单元，而在文本中，实体则是承载信息的核心。一段文本所蕴含的信息，通常可由其包含的实体及实体间的相互联系来表述。知识图谱本质上是一个以实体为节点的巨大知识网络，因此，文本中的实体及其关联信息是构建知识图谱的最重要知识来源。\n实体抽取（Entity Extraction）的主要目标是从非结构化文本当中识别出实体提及（Entity Mention），并将其划分到预先指定的类别体系中，例如人名、地名、机构名、日期等。以句子“美国白宫首席副新闻秘书卡琳·让·皮埃尔新冠检测结果呈阳性”为例，实体抽取任务需要识别出“卡琳·让·皮埃尔”为人名，“美国”为地名。\n作为海量文本分析和知识图谱构建的核心技术，实体抽取是文本语义理解的基石，为解决信息过载问题提供了有效手段。它通过将文本结构化为以实体为中心的语义表示，为舆情监控、网络搜索、智能问答等下游应用提供了关键的基础支撑，是实现大数据资源化、知识化和普适化的核心技术之一。\n二、研究内容与挑战 # 实体抽取的核心研究对象是如何从文本中识别指定类别的实体，通常包含实体边界识别（判断一个字符串是否构成完整实体）和实体分类（将识别出的实体划分到指定类别）两个子任务。在人名、地名、机构名等通用特定领域，中英文实体抽取的F1值已能达到90%以上。\n然而，当前该领域的核心挑战在于如何将限定领域的优良表现迁移至开放领域，这面临以下几个核心挑战：\n类别开放：限定领域通常只关注少数实体类别，而开放领域则需要处理数量庞大、粒度不一的各种实体类别。例如，实体类别从早期的几十个（如BBN数据集的64类、OntoNotes的87类）已扩展到数万个（如利用WordNet构建的10331类）。同时，不同领域（如计算机科学的SciREX、生物领域的BC5CDR）的类别体系各不相同，类别间还存在上下位、共现等复杂关系，使得孤立考虑每个类别的方法低效且不切实际。 实体结构复杂：传统实体抽取主要关注“扁平化”实体（Flat NER），不考虑实体间的嵌套、重叠及不连续情况。但在开放领域中，复杂结构十分常见。例如，“中华人民共和国教育部”中同时包含了“中华人民共和国”和“教育部”两个嵌套实体；“心、肺功能异常”中包含了“心功能异常”和“肺功能异常”两个重叠实体。以CRF为代表的传统模型难以建模此类复杂结构。 标注资源缺乏：由于类别开放和结构复杂，为所有待抽取类别构建充足、高质量的标注资源极其困难。现有的大量实体类别仅有极少量标注数据。尽管存在一些外部或半标注资源，但它们通常质量较差、噪声大，且与目标任务存在知识不匹配的问题。如何利用极少量标注数据训练出有效的抽取模型，是开放领域实体抽取的另一大挑战。 三、研究现状与发展趋势 # 深度神经网络方法已成为实体抽取的主流。近年来，预训练语言模型的兴起更是为实体抽取领域带来了深刻变革，为解决上述三大挑战提供了重要的技术基础。下面将从模型架构、学习算法和模态融合三个层面介绍其研究现状。\n1. 模型架构：从序列标注到生成模型 # 传统方法通常将实体抽取建模为序列标注问题，其中条件随机场（CRF）模型最为常用。但CRF的语义表达能力有限，难以处理嵌套、重叠等复杂结构。\n为解决此问题，研究界设计了多种面向复杂结构的特定抽取结构：\n针对嵌套与重叠实体，提出了基于依存树节点、超图结构、以及基于转移系统（Transition-based）的模型。此外，锚点-指针网络（Anchor-pointer Network）将任务转化为识别不同锚点对应的实体边界。 针对非连续实体，主要工作聚焦于扩展传统的BIO标注体系，并引入超图、团（clique）等特殊结构。 这些方法虽然在特定任务上有效，但其标记结构适用范围窄，且设计复杂以避免歧义。近年来，研究趋势转向更灵活、通用的任务范式，如**区块抽取（Span Extraction）和生成式（Generative）**模型。前者将任务转化为类似阅读理解的区块抽取，后者则直接生成目标实体的位置或区块。这类模型对数据依赖度低，可复用性与迁移性好，能有效利用其他任务的资源，是未来迈向实际应用的重要方向。\n2. 学习算法：从粗粒度有监督到细粒度小样本学习 # 传统实体抽取研究多依赖大规模标注语料进行有监督学习。但在开放领域，这一前提难以满足。因此，近年来的工作重点转向两大挑战：开放类别的细粒度实体抽取与资源缺乏的小样本实体抽取。\n对于开放类别抽取，主要有两条技术路线： 数据层面：利用远距离监督或数据增强等弱监督方式，为模型提供额外数据。这类方法不需改动模型，但面临数据质量差、噪声大和覆盖度不足的挑战。 模型层面：利用标签间的关联信息辅助抽取。通过预设的标签结构或自动学习标签间的隐式关系，提升在稀疏资源类别上的抽取性能。 对于小样本实体抽取，通常包括预学习、微调和预测三个阶段。现有方法可归为三类： 基于原型学习（Prototype-based Learning）：利用少量样本为新类别构建原型，并据此进行抽取。 基于弱监督学习：利用少量样本从大规模无标签语料中扩充训练数据。 基于自学习（Self-learning）：通过模型与数据间的相互迭代，让模型在少量标注数据和大规模无标注数据上自我学习和提升。 3. 模态融合：从单语单模到多语多模 # 深度学习与预训练模型打通了不同语言与模态间的信息壁垒，使得多语言、多模态实体抽取成为研究热点。\n多语言实体抽取：核心思路是“单语标注，多语使用”。通过利用富标注语言的资源，提升低资源语言的抽取性能。技术手段包括数据对齐（如利用Wikipedia的多语链接）、表示对齐（利用多语言模型将不同语言映射到统一语义空间）和知识蒸馏。 多模态实体抽取：通过引入图像、语音等额外模态信息，辅助完成文本实体抽取，尤其是在短文本或不规范文本等存在歧义的场景中。主要技术包括表示层融合与跨模态多任务学习。 四、产业发展现状 # 实体抽取作为自然语言处理的基础技术，已在产业界广泛应用。国内外人工智能厂商（如百度、阿里、华为、腾讯）均在其开放平台中提供了实体抽取服务，并针对法律、金融、医疗等垂直领域提供定制化服务。\n同时，学术界和工业界也涌现了众多优秀的开源工具，如LTP、FudanNLP、CoreNLP、Stanza、THULAC、HanNLP和spaCy等。这些工具通常内置了基础的命名实体识别模型，但对于开放领域的复杂抽取支撑仍显不足。\n实体抽取的应用场景十分广泛：\n新闻媒体：辅助新闻采编，提高工作效率。 法律服务：识别法律文书中的关键信息，构建知识图谱，实现类案推荐与法条关联。 电商领域：从快递单据中提取姓名、电话、地址等结构化信息，提升处理效率。 医疗领域：识别电子病历中的医学实体，辅助病历质控、优化诊疗流程。 行业知识图谱构建：在汽车、政务、油气勘探、客服等领域，通过实体抽取整合信息，构建知识图谱，支撑智能导购、高效问答和多轮对话系统。 五、总结与展望 # 实体抽取正从限定领域迈向开放领域，面临着类别开放、结构复杂和标注缺乏三大挑战。深度学习和预训练语言模型的兴起为解决这些挑战带来了范式级的变革。\n然而，开放领域的实体抽取仍面临诸多难题。未来，如何设计更通用、高效、高速的模型架构，如何更充分地利用现有资源以实现模型的快速跨类别泛化，以及如何更好地实现多模态多语言的深度融合，将是实体抽取领域在大模型时代所面临的重要挑战和研究方向。\n","date":"2022-09-15","externalUrl":null,"permalink":"/blogs/kg/knowledge-grap-development-report/entity-extraction/","section":"Blog","summary":"","title":"知识图谱发展报告（2022）-实体提取","type":"blogs"},{"content":" 参考：知识图谱发展报告（2022）下载链接 (访问密码: 2096)\n一、任务定义、目标和研究意义 # 人们通常以网络的形式组织知识图谱中的知识，网络中每个节点代表实体(人名、地名、机构名、概念等)，而每条连边则代表实体间的关系。然而，直接应用符号表示的知识图谱存在计算效率低、数据稀疏等诸多挑战性难题。\n表示学习旨在将研究对象的语义信息表示为稠密低维实值向量。在该低维向量空间中，两个对象距离越近，则说明其语义相似度越高。知识表示学习，则是面向知识图谱中的实体和关系进行表示学习。\n知识表示学习实现了对实体和关系的分布式表示，它具有以下主要优点：\n显著提升计算效率：传统的图算法计算复杂、扩展性差，而分布式表示能高效地进行语义相似度计算等操作。 有效缓解数据稀疏：通过将大量对象投影到统一的低维空间，高频对象的语义信息可以帮助完善低频对象的语义表示。 实现异质信息融合：不同来源的知识图谱规范和信源不同，通过表示学习模型可以将它们投影到统一的语义空间，实现信息融合。 二、研究内容和关键科学问题 # 知识表示学习是面向知识图谱中实体和关系的表示学习。通过将实体或关系投影到低维向量空间，我们能够实现对实体和关系的语义信息的表示，可以高效地计算实体、关系及其之间的复杂语义关联。但是，知识表示学习仍面临很多挑战。\n1. 复杂关系建模 # 根据知识图谱中关系两端连接实体的数目，可将关系划分为 1-1、1-N、N-1 和 N-N 四种类型。例如 N-1 类型关系指的是，该类型关系中的一个尾实体会平均对应多个头实体。研究发现，各种知识获取算法在处理四种类型关系时的性能差异较大，在处理复杂关系时性能显著降低。\n2. 多源信息融合 # 现有知识表示学习很多指利用了知识图谱的三元组结构信息进行表示学习，其他大量信息没有被有效利用。\n知识图谱其他信息，如实体和关系的描述信息、类别信息等。 图谱外的海量信息，如与图谱中实体和关系有关的文本信息、图片信息等。 如何充分融合这些多源异质信息，实现知识表示学习，具有重要意义，可以改善数据稀疏问题，提高知识表示的区分能力。\n3. 关系路径建模 # 多步的关系路径也能反映实体之间的语义关系。利用两实体间的关系路径信息，预测它们的关系，取得显著效果，说明关系路径蕴含着丰富信息。知识表示学习孤立学习每个三元组具有局限性，考虑关系路径信息进行突破。\n4. 时序信息建模 # 大量知识具有时效性，随着时间发展在动态变化。例如，美国总统在2010年是奥巴马，在2020年是拜登。利用时序分析和图神经网络等技术，对于分析图谱结构随时间的变化规律和趋势，以及知识推理都具有重要意义。\n5. 模型知识增强 # 语言模型是自然语言理解的核心能力，以预训练语言模型 BERT、GPT 为代表的最先进的深度学习方法，仍然面临鲁棒性差、可扩展性差和可解释性差等问题。\n知识表示学习是构建结构化符号知识到深度语言模型的桥梁，如何低成本植入结构化知识到预训练语言模型增强模型的语义理解能力，是目前知识表示学习的热点方向。\n三、技术方法和研究现状 # 研究者提出了多种模型，学习知识图谱中的实体和关系的分布式表示。\n1. 复杂关系建模 # TransE 将知识图谱中的关系看作实体间的某种平移向量，效果很好。但是，不能处理 1-N、N-1、N-N 的复杂关系。 TransH 提出让一个实体在不同的关系下有不同的表示。 TransR 认为不同关系拥有不同的语义空间，对每个三元组将实体利用矩阵投影到对应的空间中，再建立从头实体到尾实体的平移关系。 TransAt 引入注意力机制，TransMS 使用非线性函数传播多向语义。\nTransX 系列之后，在如何处理复杂关系建模的挑战问题上，提出了多种模型， 从不同角度尝试解决复杂关系建模问题。\n部分工作从空间着手，ManifoldE 将传统的基于“点”的表示上升为“流形”表示，并设计了 Sphere 和 Hyperplane 两种流形的设置。\nComplEx 从复数空间上建模实体和关系嵌入，以更好地捕获对称和非对称的关系。 RotatE 在复数空间上将关系看做是头实体到尾实体的旋转。 HAKE 则是将实体映射到极坐标系，通过在链接预测任务上的实验表明 HAKE 能有效地在知识图中建立语义层次模型。\nChronoR 是一种用于学习实体、关系和时间表示的模型，可以通过使用高维旋转作为变换算子，捕捉到时间和多关系特征之间的丰富信息，并在时序知识图谱链接预测任务取得优异效果。\n2. 多源信息融合 # 利用知识图谱的三元组结构信息进行表示学习，尚有大量与知识有关的其他信息没有得到有效利用。因此需要对现有知识表示学习模型进行多源信息融合，融合包括文本描述、类别、属性以及图片等多源异质信息。\n文本描述 多数知识图谱中含有大量对实体进行描述的文本信息，其中包含着丰富的语义信息。\nDKRL 给出两种融合文本描述信息的模型：一种是 CBOW，将文本中的词向量简单相加作为文本表示；一种是 GCN，能够考虑文本中的词序信息。\nKEPLER 是利用预训练语言表示和知识表示联合学习的统一模型，如下图所示，通过联合学习将事实知识信息嵌入到预训练模型中，同时通过基于文本训练的预训练语言模型可以得到文本语义增强的知识表示。\n图1 含文本描述的知识图谱例子（左），KEPLER 框架（右） 实体类别 实体由层次类或类型和语义类别的关系来表示。融合实体相关类别信息有助于增强实体的语义表示。\nSSE（Semantically Smooth Embedding） 模型引入语义类别信息，使得同一类型的实体在嵌入空间更接近。 TKRL模型借助层次结构信息将实体类别信息编码到知识表示。\n视觉信息 知识图谱中的实体通常包含丰富的视觉信息，如人物图片、动物图片等。\nIKRL模型将图像信息融入到知识图谱进行知识表示学习。在 IKRL 的基础上，[Mousselly-Sergieh et al.，2018] 提出了一种同时融入基于语言学和图像信息的多模态知识表示方法，并构建了一个大规模的多模态知识表示数据集。\n逻辑规则 一种可以被利用的信息是逻辑规则。利用三元组和给定的逻辑规则，获取实体和关系的向量表示。\nKALE 在一个统一框架中将三元组看作原子公式，并利用转移模型进行建模。规则被形式化为复杂公式，并利用 t 阶模糊逻辑建模，并将复合公式的真值定义为其成分真值的组合。 RUGE进一步提出基于软规则的改进方法。\n多语言信息 多语言知识图谱一般包含多种不同语言实体中的结构性知识。\nMTransE 分别在独立空间中对实体和关系进行编码，并可以对任意实体或关系向量进行跨语言转换，且多语言知识图谱的嵌入模型保留了单语言嵌入时的优良特性。 IPTransE 将不同 KG 的实体和关系联合编码到一个统一的低维语义空间，利用迭代和参数共享来提高跨语言对齐性能。\n不确定信息 为具有不确定信息的 KG 中的三元组添加一个置信度描述不确定性。\nUKGE 通过引入规则作为先验知识，利用概率软逻辑方式进行置信度推断。 UOKGE 考虑 KG 中存在不确定本体信息，根据置信度分数学习不确定本体感知知识图上的实体、类和属性的嵌入。 针对不确定知识图谱中长尾关系的少样本问题，[Zhang et al.，2021] 提出基于高斯分布的度量学习方法，利用 Gaussian Embedding 方式建模实体及关系的语义不确定性。\n3. 关系路径建模 # 关系路径是指两个实体之间的多步关系，而不仅限于两个实体之间直接相连的关系。在知识图谱中，多步关系包含了两个实体之间丰富的语义关系，有助于多步推理。\n图2 单步与多步关系信息示例 Path-based TransE(PTransE) 模型将关系路径建模成一组关系的组合，并给出相加、相乘和循环神经网络等多种关系组合形式。\n循环跳跃网络模型 RSN 将关系路径对实体和关系进行联合学习，利用递归神经网络与残差连接结合，以捕获 KG 中长期依赖关系。\n以上方法存在误差传播和可解释性差的问题。 为此，RPJE 模型联合路径和规则进行知识表示学习。 受到神经架构搜索（NAS）的启发，Interstellar 将其作为一种循环架构搜索问题来处理路径信息。 此外，基于图神经网络（GNN）的方法也被广泛应用，如 R-GCN 和基于注意力的模型。 最近，研究者利用Transformer的强大能力，提出了 CoKE 等模型用于关系路径编码。\n4. 时序信息建模 # 当前很多研究集中于静态知识图谱，但许多事实是随时间动态变化的，因此时序知识图谱同样重要。相关工作可分为两类：\n外推任务 (Extrapolation task)：旨在对未来的事实进行预测。例如，ATiSE 模型考虑了图谱演化中的不确定性，采用多维高斯分布进行表示学习；DBKGE 则构建了动态贝叶斯模型在度量空间中跟踪实体语义。 插值任务 (Interpolation task)：旨在预测一个事实在给定的时间点是否有效，也称时序知识图补全。例如，[Leblay \u0026amp; Chekol，2018]在现有关系嵌入模型上进行了扩展；[Garcia-Duran et al.，2018]则将谓词和时间戳序列拼接后输入LSTM进行编码。 5. 模型知识增强 # 预训练语言模型（PLM）虽强大，但因缺乏结构化知识的自觉运用，导致在知识运用和推理方面能力不足。融合结构化知识的PLM成为研究热点，主要有以下几种方式：\n知识增广 (Knowledge Augmentation)：从输入端增强模型，直接将知识加入输入，或设计特定模块融合原始输入和知识化输入。 知识支撑 (Knowledge Support)：在模型内部进行优化，如在底层引入知识指导层处理特征，或在顶层构建后处理模块以得到更准确的输出。 知识约束 (Knowledge Constraint)：利用知识构建额外的预测目标和约束函数来增强原始目标函数。例如，利用知识图谱启发式标注语料作为新目标，或构建额外的预训练目标。 图3 融合结构化知识到预训练语言模型的三种途径 四、开源工具与基准数据集 # 1. 知识表示学习开源工具 # 为了促进模型的研究和开发，许多优秀的开源工具被提出，它们实现了多种主流模型，并支持在大型知识图谱上进行高效训练。\nOpenKE: 由清华大学发布，是高效训练知识图谱表示的早期工具包，实现了TransE、TransR等8种常见模型。 BigGraph: 由Facebook研发，专注于大模型图谱的尺度化以及在机器集群上的分布式训练。 DGL-KE: Amazon推出的可有效计算知识图谱表示的开源包，利用多处理、多GPU和分布式并行，实现了对超大规模知识图谱的高效训练。 其他工具: 还包括GraphVite，AmpliGraph，Pykg2vec，LibKGE，Scikit-KGE，PyKEEN等，它们分别由不同机构开发，采用Pytorch，TensorFlow等不同框架，各有侧重。 2. 测试基准数据集 # 为了评测算法性能，研究者们从公开知识图谱中抽取子集构造了大量基准数据集。\n基于WordNet: 如WN18，WN18RR等，是语言知识图谱。 基于Freebase: 如FB15K，FB15K-237，FB86M等，是世界知识图谱。 基于Wikidata: 如Wikidata5M，WikiKG90Mv2等，是链接知识库。 时序知识图谱: 针对时序任务，也提出了专门的数据集，如ICEWS14，GDELT等。 这些数据集覆盖了从几万到上亿实体的不同规模，可以满足不同场景下的测试需求。\n五、技术展望与发展趋势 # 尽管知识表示学习领域发展迅速，但仍有许多挑战有待研究。\n面向不同知识类型的表示学习：当前按关系类型（1-1/1-N等）划分过于粗糙，未来需结合认知科学，对树状、网格状、时序等不同知识结构进行针对性建模。 面向多源信息融合的表示学习：当前信息融合来源和手段有限，未来需要探索融合更多类型的信息（如文本、图像、音视频），并研究如何融合多个异构知识图谱。 考虑复杂推理模式的表示学习：目前主要依赖三元组，未来需要将关系路径、逻辑规则等复杂推理模式（如“父亲的父亲是祖父”）更精确地融入表示学习中。 超大规模知识图谱的表示学习：现有工具主要针对百万级实体，而Wikidata等图谱已达上亿实体、数十亿关系。如何适配千万级以上规模的图谱，在负采样、并行训练、通信管理等方面都是巨大挑战。 大规模知识图谱的在线表示学习：真实世界的知识图谱在不断动态增长，需要设计高效的在线学习方案，以应对图谱的动态更新和数据稀疏性问题。 ","date":"2022-09-14","externalUrl":null,"permalink":"/blogs/kg/knowledge-grap-development-report/knowledge-representation-learning/","section":"Blog","summary":"","title":"知识图谱发展报告（2022）-知识表示学习","type":"blogs"},{"content":"","date":"2022-09-13","externalUrl":null,"permalink":"/tags/%E5%BB%BA%E6%A8%A1/","section":"Tags","summary":"","title":"建模","type":"tags"},{"content":"","date":"2022-09-13","externalUrl":null,"permalink":"/tags/%E7%9F%A5%E8%AF%86%E8%A1%A8%E7%A4%BA/","section":"Tags","summary":"","title":"知识表示","type":"tags"},{"content":" 参考：知识图谱发展报告（2022）下载链接 (访问密码: 2096)\n一、任务定义、目标和研究意义 # 知识表示（knowledge representation）将现实世界中的各类知识表达成计算机可存储和可计算的结构，使得计算机可以无障碍地理解所存储的知识。五大特点：\n客观事物的机器标识（A KR is a surrogate），即知识表示首先需要定义客观实体的机器指代或指称； 一组本体约定和概念模型（A KR is a Set of ontological commitments），即知识表示还需要定义用于描述客观事物的概念和类别体系。 支持推理的表示基础（A KR is a Theory of Intelligent Reasoning），即知识表示还需要提供机器推理的模型与方法。 用于高效计算的数据结构（A KR is a medium of efficient computation），即知识表示也是一种用于高效计算的数据结构。 人可理解的机器语言（A KR is a medium of human expression），即知识表示需要接近人的认知，是人可理解的机器语言。 知识表示已经探索过语义网络、专家系统、语义网、知识图谱等形态，形成了基于框架的语言、产生式规则、RDF 以及 OWL 等知识表示语言。\n当前人工智能在语言理解、视觉场景分析、决策分析等方面依然面临巨大的挑战，其中一个关键挑战便是如何让机器掌握大量的知识，尤其是常识知识，这体现了知识表示的重要性。\n二、研究内容与关键科学问题 # 根据知识呈现的形态和方式，分为本体知识、规则知识、事件知识等。\n本体知识表达实体和关系的语义层次，用于建模领域的概念模型；规则知识表达实体和关系之间存在的推理规律，是更抽象的知识；事件知识包含多种事件要素，是更多维更复杂的知识。\n1. 本体知识 # 在计算机领域，本体可以在语义层次上描述知识，因此可以用于建立某个领域知识的通用概念模型，即定义组成“主 题领域”的词汇表的“基本术语”及其“关系”，以及结合这些术语和关系来定义词汇表外延的“规则”[[Neches et al., 1991][1]]。\n“领域”是指一个本体，描述一个特定领域；“术语”指给定领域的重要概念；“基本术语之间的关系”包括类的层次结构等关系；“词汇表外延的规则”包括概念的属性约束、值约束、不相交描述以及对象间的逻辑关系规定等。本体体现了客观事物内在、外在的关系，具有概念化、明确性、形式化和共享性的特点。\n图1 学校领域概念及概念间层次关系 从逻辑结构上看，知识图谱一般可分为两层，数据层存储知识图谱中的所有三元组信息，模式层(也称 schema 层或本体层)位于数据层之上，对数据层知识结构进行提炼，即通过在模式层上建立约束和规则，可规范图谱中的实体、关系、实体属性、属性值之间的联系，以及完成在知识图谱上的推理。基于知识图谱，本体既可以以模式层的形式出现，表达数据层的抽象知识，也可以以数据层的形式出现，表达资源之间的约束关系，尤其是层次约束关系。\n2. 规则知识 # 规则是传统推理中一种重要的方式，在知识图谱中规则被表示为：\n其中，body 表示规则主体，head 表示规则的头部，一条规则由主体推导出头部。规则头由一个二元的原子构成，而规则的主体则由一个或者多个一元原子或者二元原子所构成。原子就是包含了变量的三元组，其本身也有肯定和否定之分。如果主体中仅仅包含肯定的原子，那么这样的规则也可以被称之为霍恩规则。\n$$ head \\leftarrow body $$引用规则的过程称为推理，如果一个程序处理推理过程，则该程序称为推理引擎。有一种推理引擎以规则知识为基础推理，其具有易于理解、易于获取、易于管理的特点，这样的推理引擎称为“规则引擎”。\n3. 事件知识 # 具体参考后续篇章\n三、技术方法和研究现状 # 1. 本体知识建模 # 1）本体知识建模语言\n本体描述语言是本体构建环节中的重要工具，客观的信息资源只有经过本体语言的描述转化后才能够在计算机、网络上实现输入、导出、分类、语义关联、逻辑推理等一系列的功能。\n目前代表性的本体描述语言有：XML、RDF、RDFS和OWL。\n2）本体知识建模工具\n本体建模工具有 Protégé、Apollo、OntoStudio、TopBraid Composer、Semantic Turkey、 Knoodl、Chimaera、OliEd、WebODE、Kmgen 和 DOME。\n3）应用实践\n本体可用于网站的组织和导航。\n本体可用于提高网络搜索的精确度及支持特殊查询。\n基于本体推理的中医药诊疗实践[[陈华钧 et al., 2011][2]]。\n基于本体建模对化工生产过程进行控制[[荣冈 et al., 2015][3]]。\n2. 规则知识建模 # 1）规则知识建模语言\n在规则引擎中，通常会使用某种表述性的语言来描述规则。目前并没有一个通用的规则引擎标准。规则建模语言可以分为结构化的（Structured）和基于标记的（Markup，通常为XML）两类。下面介绍几种常用的逻辑规则编程语言。\nProlog 的命名来自“逻辑编程”(programming of Logic)，广泛应用在人工智能的研究中。 Datalog 是一种数据查询语言，语法与 Prolog 相似。Datalog 不是某一种具体的语言。 *Rule Markup Language (RuleML)**是一系列 Web 文档和数据语言的统一系统，通过模式语言进行句法指定，最初为 XML 开发并转换为其他格式，如 JSON。 Semantic Web Rule Language(SWRL)的规则部分概念是由 RuleML 所演变而来，并结合了 OWL 本体论的部分概念。 图2 规则在语义网技术栈中的定位 2）规则知识建模工具\n常见的规则知识建模工具有：Cyc 推理引擎、KAON2、Flora-2、Prova。\n3）应用实践\n规则引擎在应用中作为一个嵌入在应用程序中的组件，其核心思想是将复杂多变的规则从业务流程中解放出来，以规则脚本的形式存储在文件或数据库中。使得业务规则的变化不需要修改代码重启机器就可以在线完成。\n四、技术展望与发展趋势 # 自动构建的过程中，如果数据是结构化的(例如图表数据)，已知属性名称、属性间的层次结构等，构建本体相对较为容易。如果缺乏以上信息，则只能通过文本信息等非结构化数据提炼知识构建本体，技术上将面临很多挑战。整体来看，呈现以下趋势：\n多模态数据及数据的结构化 低资源场景下的本体构建 大规模本体构建 规则引擎的发展也遇到了很多问题，需要在未来进一步研究和解决。主要问题如下：\n规则可视化配置 规则执行的效率 规则的维护 参考文献 # $\\scriptsize{Neches R, et al. Enabling technology for knowledge sharing[J]. AI magazine, 1991, 12(3): 36-36.}$ $\\scriptsize{陈华钧. 基于本体推理的中医药五行诊疗系统:中国，CN102156801A [P]. 2011-08-17.}$ $\\scriptsize{荣冈. 一种基于本体构建模型的化工生产过程控制方法:中国， CN104678780A [P]. 2015-06-03.}$ ","date":"2022-09-13","externalUrl":null,"permalink":"/blogs/kg/knowledge-grap-development-report/knowledge-representation-and-modeling/","section":"Blog","summary":"","title":"知识图谱发展报告（2022）-知识表示与建模","type":"blogs"},{"content":"","date":"2022-08-09","externalUrl":null,"permalink":"/tags/amhen/","section":"Tags","summary":"","title":"AMHEN","type":"tags"},{"content":" 代码：ZhijunLiu95/FAME\n在当今数据驱动的世界中，网络（或图）无处不在，从社交网络、电商平台到文献引用网络。这些真实世界的网络往往具有高度的复杂性，具体表现为：\n异构性（Heterogeneity） 多重性（Multiplexity） 属性化（Attributed） 如何为这种复杂的**属性化多重异构网络（AMHENs）**学习到高质量的节点表示（Embedding），是推荐系统、广告、节点分类等下游任务取得成功的关键。然而，现有方法大多难以同时兼顾网络的异构性、多重性和属性信息，并且在面对动辄数百万节点、数十亿边的大规模网络时，其高昂的计算和内存开销往往令人望而却步。\n为了解决这些挑战，来自烟台大学、京东金融等机构的研究者在CIKM 2020会议上提出了一种名为 FAME (Fast Attributed Multiplex Heterogeneous network Embedding) 的框架。FAME以其卓越的效率和效果，为大规模AMHEN的表示学习问题提供了一个全新的解决方案。本文将深入解析FAME模型的设计思想和技术细节。\n一、 核心挑战与问题定义 # 在深入模型之前，我们首先要明确FAME试图解决的核心挑战：\n异构性与多重性的融合：如何在一个统一的框架内，自动地、无需手动定义元路径（meta-path），就能捕捉和融合网络中不同类型的节点、关系以及它们之间复杂的交互模式？ 全局结构与节点属性的保留：如何确保学习到的节点向量既能反映网络的高阶拓扑结构，又能融入节点自身的属性信息？ 大规模网络的可扩展性：如何设计一个足够轻量级的算法，使其能够在拥有数百万节点和边的大规模网络上快速执行，而无需依赖昂贵的分布式计算平台？ 基于此，论文将AMHEN的表示学习问题形式化定义如下：\n问题1 (AMHEN Embedding): 给定一个属性化多重异构网络 \\(G = \\{V, \\mathcal{E}, X\\}\\)，其中 \\(V\\) 是节点集，\\(\\mathcal{E}\\) 是多重关系边集，\\(X\\) 是节点属性矩阵。目标是学习一个映射函数 \\(f: V \\rightarrow \\mathbb{R}^d\\)，将每个节点 \\(v_i \\in V\\) 映射到一个低维（\\(d \\ll |V|\\)）的向量表示，即嵌入（Embedding）。\n二、 FAME模型架构详解 # FAME的整体架构如论文中的 图1 所示，其核心思想是“先融合，再投影”。整个过程分为两个关键步骤：谱图变换 (Spectral Graph Transformation) 和 快速随机投影嵌入 (Fast Random Projection Embedding)。\n第一步：谱图变换 (Spectral Graph Transformation) - 捕捉多重关系与高阶结构 # 这一步的目标是，将原始网络中不同类型的关系（边）融合成一个能体现复杂交互信息的单一关系表示，并捕捉节点间的高阶邻近性。\n网络分解：首先，FAME将复杂的AMHEN分解为多个简单的子网络。每个子网络 \\(G_r\\) 只包含一种类型的关系 \\(r\\)。例如，在电商网络中，可以将网络分解为“用户-商品点击”子网络、“用户-商品购买”子网络等。每个子网络 \\(G_r\\) 对应一个邻接矩阵 \\(A_r\\)。 加权融合：为了融合不同关系的重要性，FAME对所有子网络的邻接矩阵进行加权求和，得到一个拓展的邻接矩阵 \\(\\mathbb{A}\\)。 $$ \\mathbb{A} = \\sum_{r=1}^{|\\mathcal{R}|} \\beta_r A_r $$其中，\\(|\\mathcal{R}|\\) 是关系类型的总数，\\(\\beta_r\\) 是一个可学习或预设的权重，代表了关系类型 \\(r\\) 的重要性。例如，“购买”关系的重要性（权重）通常会高于“点击”。\n高阶邻近性建模：邻接矩阵的一次方 \\(\\mathbb{A}^1\\) 只能表示节点间的直接连接（1跳邻居）。为了捕捉更长的、跨越不同关系的元路径结构（如 “用户1 -\u0026gt; 商品 -\u0026gt; 用户2”），FAME利用了邻接矩阵幂的特性：\\(\\mathbb{A}^i\\) 的元素 \\((\\mathbb{A}^i)_{uv}\\) 表示从节点 \\(u\\) 到节点 \\(v\\) 长度为 \\(i\\) 的路径数量。FAME通过对不同阶数的邻接矩阵幂进行加权求和，来构建一个综合性的变换函数 \\(F(\\mathbb{A})\\)。 $$ \\begin{aligned} F(\\mathbb{A})\u0026=\\sum\\limits_{i=1}^K \\alpha_i\\mathbb{A}^i\\\\ \u0026=\\sum\\limits_{i=1}^K \\alpha_i(\\sum\\limits_{r=1}^{|\\mathcal{R}|} \\beta_r\\mathbf{A}_r)^i \\end{aligned} $$这里，\\(K\\) 是考虑的最高阶数（最长路径长度），\\(\\alpha_i\\) 是一个递减的权重，反映了“路径越短，节点关系越紧密”的普遍假设。通过这个变换，FAME能够在一个矩阵 \\(F(\\mathbb{A})\\) 中同时编码短距离和长距离、跨多种关系的复杂结构信息，而无需手动枚举元路径。这一过程在论文的 图2 中有生动的展示。\n归一化：为了消除网络中度数高的节点带来的数据倾斜问题，FAME在执行谱图变换前，会对融合后的邻接矩阵 \\(\\mathbb{A}\\) 进行归一化处理。 $$ \\mathbb{A} = D^{-1}\\mathbb{A} $$其中 \\(D\\) 是 \\(\\mathbb{A}\\) 的度矩阵。\n第二步：快速随机投影嵌入 (Fast Random Projection Embedding) - 实现高效降维 # 谱图变换虽然强大，但如何将其与节点特征高效地结合并降维，是决定模型可扩展性的关键。传统的图卷积网络（GCN）虽然在这方面取得了巨大成功，但其固有的复杂性使其难以胜任大规模网络的挑战。\n1. 传统谱图GCN的瓶颈\n为了理解FAME的创新，我们首先需要回顾一下标准谱图GCN的工作原理。其核心思想是在图的傅里叶域（谱域）中进行卷积操作。一个信号（节点特征）\\(X\\) 在网络上的卷积可以表示为：\n$$ g_\\theta \\star X = \\Phi g_\\theta(\\Lambda)\\Phi^T X $$其中，\\(g_\\theta(\\Lambda)\\) 是一个作用于图谱（特征值对角矩阵\\(\\Lambda\\)）上的滤波器，由参数 \\(\\theta\\) 控制。为了捕捉局部信息，这个滤波器通常被设计成一个 \\(K\\) 阶多项式：\n$$ g_\\theta(\\Lambda) = \\sum_{k=1}^K \\theta_k \\Lambda^k $$当我们将这个理论应用到节点表示学习中，为了从 \\(m\\) 维的输入特征生成 \\(d\\) 维的嵌入，滤波器参数就需要从一个向量 \\(\\theta\\) 扩展为一个庞大的参数矩阵 \\(\\Theta \\in \\mathbb{R}^{K \\times m \\times d}\\)。最终的嵌入计算公式变为：\n$$ Z = \\sigma(\\Phi(\\sum_{k=1}^K \\Lambda^k)\\Phi^T X \\Theta_k) \\quad (8) $$瓶颈就在这里！ 这个公式清楚地揭示了两个问题：\n参数量巨大：需要学习和优化的参数数量为 \\(K \\times m \\times d\\)。当节点特征维度 \\(m\\) 很高时，这个参数量会爆炸式增长。 计算复杂度高：整个滤波操作的复杂度与网络中的边数成线性关系，对于大规模网络而言，训练过程将非常耗时。 2. FAME的解决方案：用固定投影替换可学习滤波器\nFAME的核心创新在于，它彻底抛弃了上述复杂且需要学习的滤波器 \\(g_\\Theta\\)，代之以一个极为高效的、固定的、非学习的操作。它用两个部分来共同实现“滤波”和“降维”：\n谱图变换 \\(F(\\mathbb{A})\\)：在前一步已经计算好，它扮演了捕捉多阶邻域结构的角色，等效于传统GCN中的 \\(\\sum \\Lambda^k\\) 部分。 稀疏随机投影矩阵 \\(R\\)：它取代了需要学习的庞大参数矩阵 \\(\\Theta_k\\)。这个矩阵 \\(R \\in \\mathbb{R}^{m \\times d}\\) 的元素是固定的，根据以下稀疏规则生成，无需任何训练： $$ r_{ji} = \\sqrt{s} \\begin{cases} 1 \u0026 \\text{with prob. } \\frac{1}{2s} \\\\ 0 \u0026 \\text{with prob. } 1 - \\frac{1}{s} \\\\ -1 \u0026 \\text{with prob. } \\frac{1}{2s} \\end{cases} $$其中 \\(s\\) 是一个控制稀疏度的参数（如 \\(s=\\sqrt{m}\\)）。\n3. 最终嵌入计算与极致优化\n通过这种替换，FAME的最终嵌入计算公式变得异常简洁：\n$$ Z = F(\\mathbb{A}) \\cdot X \\cdot R = \\sum_{i=1}^{K} \\alpha_i \\mathbb{A}^i \\cdot X \\cdot R $$更进一步，为了解决直接计算 \\(\\mathbb{A}^i\\) 的高昂成本，FAME利用矩阵乘法的结合律进行终极优化。它将计算顺序从 \\((A * A) * X * R\\) 调整为 \\(A * (A * X * R)\\)。具体地，令 \\(Z^1 = \\mathbb{A}·X·R\\)，则 \\(Z^i = \\mathbb{A}Z^{i-1}\\)。最终的嵌入为：\n$$ Z = \\sum_{i=1}^{K} \\alpha_i Z^i $$这个简单的变换将每一步的计算复杂度从依赖于节点数 \\(n\\) 的平方或立方（\\(O(n^3·K+n^2·m+n·m·d)\\)）降低到只与边的数量 \\(e\\) 线性相关（\\(O(K \\cdot e \\cdot n + n·m·d)\\)），对于稀疏图而言，这是巨大的效率提升。整个学习流程在论文的 算法1 中有清晰的伪代码描述。\n三、 实验效果 # FAME的有效性和高效性在论文的实验部分得到了充分验证（详见论文 表4 至 表7）：\n效率惊人：在多个真实数据集上，FAME的运行速度比GATNE、GTN等先进的AMHEN模型快了数万倍甚至数十万倍。在其他方法需要数小时甚至因内存溢出（OOM）而失败的大规模数据集上，FAME仅需数秒或数分钟即可完成。 效果卓越：在链接预测和节点分类任务上，FAME的性能全面超越了多种基线模型。即使与GATNE这类专门为AMHEN设计的复杂模型相比，FAME在更复杂的网络上也表现出更优的性能，证明了其自动捕捉多重关系结构的能力。 可扩展性强：FAME是少数几个能够处理完整、大规模Alibaba数据集（千万级节点，亿级边）的模型之一，并取得了当前最优的性能。 总结 # FAME模型通过一个巧妙的两步框架，成功地解决了大规模属性化多重异构网络（AMHEN）表示学习的核心挑战。\n它通过谱图变换，自动、优雅地融合了网络的多重关系和高阶结构信息，避免了繁琐的元路径工程。 它利用快速稀疏随机投影和矩阵乘法结合律，将计算复杂度显著降低，实现了在超大规模网络上的高效运行。 总而言之，FAME是一个无监督、高效且有效的模型，它不仅在理论上设计精巧，更在实践中展现了处理真实世界复杂大规模网络的巨大潜力，为相关领域的研究和应用提供了强有力的工具。\n","date":"2022-08-09","externalUrl":null,"permalink":"/blogs/kg/fame-spectral-graph-transformation-fast-random-projection-embedding/","section":"Blog","summary":"","title":"FAME：高效处理大规模属性化多重异构网络","type":"blogs"},{"content":"","date":"2022-08-09","externalUrl":null,"permalink":"/tags/%E5%BF%AB%E9%80%9F%E9%9A%8F%E6%9C%BA%E6%8A%95%E5%BD%B1/","section":"Tags","summary":"","title":"快速随机投影","type":"tags"},{"content":"","date":"2022-08-09","externalUrl":null,"permalink":"/tags/%E8%B0%B1%E5%9B%BE%E5%8F%98%E6%8D%A2/","section":"Tags","summary":"","title":"谱图变换","type":"tags"},{"content":" Pay Attention to Relations: Multi-embeddings for Attributed Multiplex Networks\n在真实世界中，网络结构远比简单的“节点-边”模型复杂。从社交网络中用户间的多种互动（点赞、转发、评论），到生物网络中蛋白质在不同组织内的多样性功能，我们面对的往往是属性化异构多路复兴网络（Attributed Heterogeneous Multiplex Networks, AHMeN）。这类网络包含多种类型的节点和边，且节点自身还带有丰富的属性信息。\n传统的图卷积网络（GCN）等模型在处理这种复杂性时常常力不从心，因为它们通常为每个节点只学习一个单一的、全局性的嵌入向量，这难以捕捉节点在不同关系、不同上下文中的多面性。\n为了解决这一挑战，来自北卡罗来纳大学夏洛特分校的研究者们提出了 RAHMeN (Relation-aware Embeddings for Attributed Heterogeneous Multiplex Networks)，一个新颖的、能够为节点生成“多重嵌入”（Multi-embeddings）的框架。其核心思想是：一个节点的表示不应该是单一的，而应是一组与其参与的各种关系相对应的、互相联系的嵌入向量。这篇博客将严格遵循原论文，为您深入解析RAHMeN的模型架构与设计。\n一、核心困境：单一嵌入的局限性 # 想象一下，在蛋白质相互作用（PPI）网络中，一个蛋白质可能同时存在于大脑、心脏和肝脏组织中，且在不同组织内扮演的角色（即与其他蛋白质的相互作用关系）截然不同。\n传统模型：会为这个蛋白质生成一个唯一的嵌入向量，试图将它在所有组织中的信息“平均化”或压缩在一起。这无疑会丢失大量上下文特定的信息。 RAHMeN的设想：应该为这个蛋白质生成一组嵌入，比如一个“大脑上下文”的嵌入、一个“心脏上下文”的嵌入等。这些嵌入既保留了特定上下文的独特性，又通过某种机制相互关联，共同描绘出该蛋白质的全貌。 RAHMeN正是基于这一设想，通过两大核心组件——关系感知的图卷积和关系语义自注意力机制——来实现这一目标。\n二、RAHMeN模型架构深度解析 # RAHMeN的整个流程可以看作一个分层、迭代的过程。在模型的每一层（k-level），它都会执行以下两个关键步骤，如下图所示:\nFigure3 RAHMeN步骤概述 步骤一：关系感知的图卷积 (Relation-Specific Graph Convolution) # 与为整个图应用一个统一卷积核的传统GCN不同，RAHMeN为网络中的**每一种关系（Relation）**都学习了一套专属的图卷积算子。论文将“关系”定义为一个三元组 \\(r = (源节点类型, 边类型, 目标节点类型)\\)。\n在模型的第 \\(k\\) 层，对于一个目标节点 \\(v\\) 和一个特定的关系 \\(r\\)，RAHMeN执行以下操作来生成其关系专属的嵌入向量 \\(h_{v,r}^k\\)。\n1. 邻居信息聚合 (Neighbor Aggregation)\n首先，模型只关注在关系 \\(r\\) 下与节点 \\(v\\) 相连的邻居 \\(u ∈ N(v,r)\\)。它收集这些邻居在上一层（\\(k-1\\)）的、同样是关系 \\(r\\) 专属的嵌入 \\(h_{u,r}^{k-1}\\)，并通过一个关系专属的可学习权重矩阵 \\(W_{n,r}^k\\) 进行变换和聚合（这里采用均值聚合）。\n公式(1)如下：\n$$ \\phi_r^k(\\mathcal{N}(v,r)) = \\sum_{u \\in \\mathcal{N}(v,r)} \\frac{1}{|\\mathcal{N}(v,r)|}\\mathbf{W}^k _{n,r} \\mathbf{h}_{u,r}^{k-1} $$ \\(h_{u,r}^{k-1}\\): 邻居节点 \\(u\\) 在第 \\(k-1\\) 层、关系 \\(r\\) 下的嵌入。 \\(W_{n,r}^k\\): 第 \\(k\\) 层专用于聚合关系 \\(r\\) 下邻居信息的可学习权重矩阵。 2. 自身信息融合与更新 (Self-Representation Combination \u0026amp; Update)\n得到聚合后的邻居信息 \\(\\phi_r^k\\)，RAHMeN会将其与节点 \\(v\\) 自身的、经过变换后的信息相结合。这一步同样是关系专属的。\n公式(2)如下：\n$$ \\tilde{\\boldsymbol{h}}_{v, r}^k = \\sigma \\left( \\mathbf{W}_{s, r}^k \\boldsymbol{h}_{v, r}^{k-1} + \\phi_r^k \\left( \\mathcal{N}(v, r) \\right) + \\boldsymbol{b}_r^k \\right) $$ \\(h_{v,r}^{k-1}\\): 节点 \\(v\\) 自身在第 \\(k-1\\) 层、关系 \\(r\\) 下的嵌入。 \\(W_{s,r}^k\\): 第 \\(k\\) 层专用于变换节点 \\(v\\) 自身在关系 \\(r\\) 下信息的权重矩阵。 \\(σ\\): 非线性激活函数，如ELU。 \\(b_r^k\\): 偏置项。 通过这一步，RAHMeN为节点 \\(v\\) 在每一个关系 \\(r\\) 下都生成了一个新的嵌入 \\(h_{v,r}^k\\)。至此，我们得到了一组关系专属的、但彼此独立的节点表示。\n步骤二：关系语义自注意力机制 (Relational Semantic Self-Attention) # 仅仅得到一组独立的嵌入是不够的。一个节点在“大脑”中的功能可能会受到其在“中枢神经系统”中功能的影响。为了让这些关系专属的嵌入能够“相互沟通”、共享信息，RAHMeN引入了其模型名称的核心——注意力机制。\n1. 关系嵌入序列化\n首先，将节点 \\(v\\) 在第 \\(k\\) 层的所有关系专属嵌入 \\(h_{v,r}^k\\) 沿着关系维度堆叠起来，形成一个嵌入矩阵 \\(H_v^k\\)，其维度为 \\(|R| × d\\)，其中 \\(|R|\\) 是关系总数，\\(d\\) 是嵌入维度。\n公式(3)（概念表示）：\n$$ \\tilde{\\boldsymbol{h}}_v^k = \\text{CONCAT}(\\tilde{\\boldsymbol{h}}_{v,r}^k | \\forall r \\in \\mathcal{R}) $$2. 计算注意力权重\n接下来，模型计算一个 \\(|R| × |R|\\) 的注意力矩阵 \\(a_v^k\\)。这个矩阵的第 \\((i, j)\\) 个元素表示：在为节点 \\(v\\) 生成其在关系 \\(i\\) 下的最终嵌入时，应该对关系 \\(j\\) 下的上下文信息“关注”多少。\n公式(4)如下：\n$$ \\mathbf{a}_v^k = \\text{softmax} \\left( \\mathbf{W}_{\\text{rel}}^k ·\\tanh(\\mathbf{W}_{\\text{attn}}^k \\tilde{\\boldsymbol{h}}_v^k) \\right) $$ \\(W_{attn}^k\\): 一个可学习的变换矩阵，大小为 \\(|R| × d × d_a\\)，用于将每个关系嵌入投影到注意力空间。 \\(W_{rel}^k\\): 关键的关系级注意力矩阵（论文中描述为a trainable relation attention matrix），大小为 \\(|R| × d_a × |R|\\)。它负责计算不同关系之间的相似度或重要性。 \\(tanh\\) 和 \\(softmax\\): 标准的激活和归一化函数。 3. 应用注意力，生成多重嵌入\n最后，用计算出的注意力矩阵 \\(a_v^k\\) 对原始的关系嵌入矩阵 \\(H_v^k\\) 进行加权，得到一组全新的、融合了所有关系上下文信息的“多重嵌入” \\(H_v'^k\\)。\n公式(5)如下：\n$$ \\mathbf{h}_v^k = \\mathbf{a}_v^k ·[\\tilde{\\boldsymbol{h}}_{v,r}^k | \\forall r \\in \\mathcal{R}] $$这里的乘法是矩阵乘法 \\((|R| × |R|) × (|R| × d)\\)，结果 \\(h_v^k\\) 仍然是一个 \\(|R| × d\\) 的矩阵。\\(h_v^k\\) 的每一行就是节点 \\(v\\) 在一个特定关系下的、经过全局信息融合后的新嵌入。这组嵌入就是第 \\(k\\) 层的最终输出。\n经过 \\(K\\) 层这样的操作，模型最终为每个节点 \\(v\\) 生成一组 )|R|) 个 \\(d\\) 维的嵌入向量 \\(z_v\\)，即最终的“多重嵌入”。\n三、模型优化 # RAHMeN采用了一种半监督的训练方式。类似于node2vec，它通过在图中进行随机游走来生成节点序列。特别地，它是针对每一种关系 \\(r\\) 单独进行随机游走，从而捕获特定关系的局部结构。\n对于生成的节点序列，模型采用负采样（Negative Sampling）优化的 skip-gram 目标。其目标是最大化在同一游走路径、同一上下文窗口内节点对的共现概率，即最小化负对数似然损失。\n公式(10)（损失函数）：\n$$ E = -\\log \\sigma(\\mathbf{c}_u^T ·\\mathbf{z}_{v,r}) - \\sum_{i=1}^L \\mathbb{E}_{v_i \\sim P_{r(u)}}[\\log \\sigma(-\\mathbf{c}_{u}^T \\mathbf{z}_{v,r})] $$ \\((v,r,u)\\): 一个正样本三元组，表示在关系 \\(r\\) 的上下文中，节点 \\(u\\) 是 \\(v\\) 的邻居。 \\(z_{v,r}\\): 节点 \\(v\\) 在关系 \\(r\\) 下的最终嵌入。 \\(c_u\\): 节点 \\(u\\) 的上下文嵌入。 \\(L\\): 负采样的样本数量。 \\(P_{r(u)}\\): 噪声分布。 四、RAHMeN的关键优势总结 # 表达能力强：通过为每个节点生成一组多重嵌入，RAHMeN能够精细地刻画节点在不同关系上下文中的角色和特性，远胜于单一嵌入模型。 设计精巧：关系感知的图卷积确保了信息聚合的上下文相关性，而关系语义自注意力机制则实现了跨关系的信息流动和重要性评估。 可解释性：模型学到的注意力矩阵 \\(a_v\\) 提供了极佳的可解释性。例如，在论文的生物网络实验中，模型自动发现“大脑”组织的蛋白质嵌入与“中枢神经系统”和“神经系统”高度相关，这完全符合生物学知识。 归纳学习能力：RAHMeN学习的是一套图卷积和注意力变换函数，而不是针对特定节点的嵌入。因此，它可以自然地应用于归纳学习任务，为在训练中未见过的节点生成嵌入。 总而言之，RAHMeN通过其创新的“多重嵌入”和“关系注意力”设计，为理解和建模复杂网络提供了一个强大而富有洞察力的工具，真正做到了“关注关系”（Pay Attention to Relations）。\n","date":"2022-08-01","externalUrl":null,"permalink":"/blogs/kg/rahmen-multi-embeddings/","section":"Blog","summary":"","title":"RAHMeN：当图网络学会“关注”复杂关系","type":"blogs"},{"content":" Recommendation algorithm based on attributed multiplex heterogeneous network\n在处理真实世界中复杂的 属性化多重异构网络（AMHEN） 时，清华大学和阿里巴巴提出了GATNE模型。它通过巧妙的基向量与边向量分解，并引入自注意力机制来动态融合多重关系，为该领域树立了一个强大的基准。\n论文《Recommendation algorithm based on attributed multiplex heterogeneous network》提出的 SSN_GATNE-T 模型，正是在GATNE框架基础之上的一次精细化探索与改进。该模型并没有颠覆GATNE的核心架构，而是聚焦于其最关键的组件—— 自注意力机制，通过替换其中的激活函数与归一化函数，旨在更精准地捕获节点间的交互信息，从而提升推荐系统的性能。\n1. 前情回顾：GATNE的核心机制 # 在深入了解SSN_GATNE-T之前，有必要先回顾GATNE的核心设计。GATNE将一个节点 \\(v_i\\) 在特定边类型 \\(r\\) 下的表示 \\(v_{i,r}\\) 分解为两部分：\n基向量 \\(b_i\\): 所有关系类型共享，捕获节点通用特征。 边向量 \\(u_{i,p}\\): 每种关系类型 \\(p\\) 独有一个，捕获节点在特定关系下的语义。 其最关键的创新在于，当计算节点 \\(v_i\\) 在目标关系 \\(r\\) 下的表示时，它使用自注意力机制来动态计算该节点在所有关系类型下的边向量 \\(\\{u_{i,1}, ..., u_{i,m}\\}\\) 的重要性权重。GATNE原始的注意力系数计算公式如下：\n$$ \\mathbf{a}_{i,r} = \\text{softmax}(\\mathbf{w}_r^T \\tanh(\\mathbf{W}_r \\mathbf{U}_i))^T $$其中，(\\mathbf{U}i = [u{i,1}, \u0026hellip;, u_{i,m}]$ 是节点所有边向量的集合。这个公式可以分解为两步：\n特征变换与激活：使用tanh函数对经过线性变换的边向量特征进行非线性激活。 权重归一化：使用softmax函数将得到的注意力分数转换为一个概率分布，确保所有权重之和为1。 GATNE的这一设计取得了巨大成功，但SSN_GATNE-T的作者认为，这里的激活函数和归一化函数仍有改进空间。\n2. SSN_GATNE-T的核心改进：注意力机制的再思考 # SSN_GATNE-T继承了GATNE的整体框架，但对其自注意力机制的计算公式进行了两处关键的修改，旨在“更好地减少潜在用户信息的损失”。其新的注意力系数计算公式为：\n$$ \\mathbf{a}_{i,r} = \\text{softsign}(\\mathbf{w}_r^T \\text{sigmoid}(\\mathbf{W}_r \\mathbf{U}_i))^T $$对比GATNE的原始公式，变化显而易见：\ntanh 被 sigmoid 替代 softmax 被 softsign 替代 下面详细解读这两处修改的意图和作用。\n2.1 激活函数：从 tanh 到 sigmoid # GATNE (tanh): tanh函数的输出范围是 (-1, 1)，它是一个零点对称的函数。它对特征进行了非线性变换，使其分布在0的周围。 SSN_GATNE-T (sigmoid): sigmoid函数的输出范围是 (0, 1)。这个范围使得其输出可以被直观地理解为一种“门控”信号或“概率”值。将变换后的边向量特征压缩到 (0, 1) 区间，可以看作是在计算每种关系类型的“激活强度”或“相关性得分”。相较于tanh，sigmoid的输出非负，在某些场景下可能更具解释性。 2.2 归一化函数：从 softmax 到 softsign # 这是SSN_GATNE-T最核心的改进。\nGATNE (softmax): softmax通过指数函数将一组任意实数转换为一个概率分布。它的优点是输出的权重和严格为1，但缺点是具有“赢家通吃”（winner-takes-all）的倾向。由于指数函数的放大效应，一个稍大的输入值会得到一个远大于其他值的概率，这可能导致模型过分关注某一种关系，而忽略其他次要但同样有用的关系信息。 SSN_GATNE-T (softsign): softsign是一个非指数型的归一化函数，其数学形式为 \\(f(x) = \\frac{x}{1+|x|}\\)，输出范围是 (-1, 1)。与softmax相比，softsign有几个关键特性： 平滑性：它比softmax更加平滑，不会因为输入的微小差异而产生剧烈的输出变化，对异常值不那么敏感。 无“赢家通吃”：它不会强制将最大的权重推向1，而是根据输入值的大小进行平滑缩放。这使得模型可以同时考虑多种不同强度的关系，保留了更丰富的交互信息。论文作者认为，这有助于“减少潜在用户信息的损失”。 计算效率：不涉及指数运算，计算上可能更高效。 通过这一替换，SSN_GATNE-T的注意力机制在融合多重关系时，策略变得更加“柔和”，能够更好地平衡主要关系和次要关系的影响，从而捕获更全面的用户兴趣。\n3. 模型设计对比：GATNE vs. SSN_GATNE-T # 为了更清晰地展示二者的区别，可以总结如下表：\n设计模块 GATNE (2019) SSN_GATNE-T (2021) 基本架构 基向量 + 边向量分解 继承GATNE，保持不变 边向量聚合 基于邻居聚合（如均值） 继承GATNE，保持不变 注意力输入激活 tanh sigmoid 注意力权重归一化 softmax softsign 优化器 Adam Adam (论文中强调其对快速收敛和调优的帮助) 从对比中可以看出，SSN_GATNE-T并非对GATNE的颠覆，而是一次精准的“外科手术式”升级。它保留了GATNE强大的表示分解和邻域聚合框架，仅在注意力计算这一核心环节进行了函数级的优化。\n4. 实验与结论 # 该论文在Amazon和YouTube这两个公开数据集上进行了实验，并将SSN_GATNE-T与GATNE及其他主流模型进行了对比。实验结果显示，SSN_GATNE-T在ROC-AUC、PR-AUC和F1-score等所有评估指标上均取得了优于GATNE的性能。\n特别是在F1-score指标上，提升尤为明显。这表明通过softsign函数平衡不同关系类型的权重，确实有助于模型做出更准确的预测。消融实验也证实，sigmoid和softsign的引入都对模型性能有正向贡献。\n结论可以总结为：\n精细化改进的有效性：SSN_GATNE-T证明了，即使在GATNE这样强大的基线上，通过对核心组件（如注意力机制中的激活与归一化函数）进行有针对性的、细微的调整，依然可以获得可观的性能提升。 softsign的潜力：该研究揭示了softsign作为softmax替代方案在图注意力网络中的潜力。它提供了一种更平滑、鲁棒性更好的方式来融合多源信息，尤其适用于存在多种强度不一关系的多重网络。 对复杂推荐场景的价值：通过更有效地挖掘和融合用户与物品之间的多重交互信息，SSN_GATNE-T为解决大规模、复杂网络环境下的推荐问题，特别是冷启动问题，提供了新的思路。 ","date":"2022-07-25","externalUrl":null,"permalink":"/blogs/kg/ssn_gatne-t-vs-gatne-t/","section":"Blog","summary":"","title":"SSN_GATNE-T：基于GATNE的精细化改进与探索","type":"blogs"},{"content":" 代码：THUDM/GATNE\n论文：Representation Learning for Attributed Multiplex Heterogeneous Network\n在现实世界中，无论是电商推荐、社交网络还是金融风控，我们面对的数据网络日益复杂。传统的网络表示学习（Network Embedding）方法，如DeepWalk或node2vec，在处理仅包含单一类型节点和边的同构网络（Homogeneous Network）时表现出色，但当网络变得更加复杂时，这些方法便捉襟见肘。\n真实世界的网络通常是属性化的（Attributed）、多重的（Multiplex）且异构的（Heterogeneous）。例如，在一个电商网络中：\n异构性：存在“用户”和“商品”两种不同类型的节点。 多重性：用户与商品之间存在多种类型的关系（边），如“点击”、“收藏”、“加入购物车”、“购买”等。 属性化：每种节点都拥有丰富的属性信息，如用户的年龄、性别，商品的价格、品牌等。 这种网络被称为属性化多重异构网络（Attributed Multiplex Heterogeneous Network, AMHEN）。处理此类网络是学术界和工业界面临的重大挑战。清华大学与阿里巴巴达摩院的研究者在KDD 2019上发表的论文《Representation Learning for Attributed Multiplex Heterogeneous Network》中，提出了一个名为GATNE (General Attributed Multiplex HeTerogeneous Network Embedding) 的统一框架，旨在有效解决这一难题。\n1. 问题的提出：真实世界网络的复杂性与挑战 # 在深入模型之前，首先要理解AMHEN带来的核心挑战：\n多重关系融合（Multiplex Edges）：一个节点对（如一个用户和一个商品）之间可能存在多种关系。这些关系强度不同，语义各异（例如，“购买”关系比“点击”关系更能体现用户的兴趣）。如何有效地区分并融合这些关系，学习到一个统一的、信息丰富的节点表示是首要难题。 异构性与属性信息利用：不同类型的节点和边具有完全不同的特征空间和语义含义。模型必须能够处理这种异构性，并有效利用节点自带的丰富属性信息，而不是仅仅依赖网络结构。 部分观测与归纳能力（Partial Observations \u0026amp; Inductive Learning）：真实世界的网络是动态变化的，新节点（如新用户、新商品）不断涌现。模型必须具备归纳学习（Inductive Learning）的能力，能够为未在训练阶段出现过的节点生成表示，而不是像传统方法那样只能进行直推式学习（Transductive Learning）。 可扩展性（Scalability）：工业级网络通常包含数十亿的节点和数百亿的边，对算法的效率和扩展性提出了极高的要求。 GATNE框架正是为了系统性地应对以上所有挑战而设计的。\n2. GATNE 模型核心设计 # GATNE的核心思想是将一个节点在特定关系下的最终表示（Overall Embedding）分解为三个部分：基向量（Base Embedding）、边向量（Edge Embedding）和属性向量（Attribute Embedding）。其中，边向量的设计是其精髓所在。\n2.1 核心思想：基向量 + 边向量分解 # 对于网络中的任意一个节点 \\(v_i\\)，其在特定边类型 \\(r\\) 下的最终表示 \\(v_{i,r}\\)，被分解为两个主要部分：\n基向量 \\(b_i\\)：这个向量是与边类型无关的，由节点本身决定，并在所有边类型上共享。它旨在捕获节点固有的、通用的基础特征。 边向量 \\(u_{i,r}\\)：这个向量是与边类型相关的。同一个节点 \\(v_i\\) 在面对不同类型的边（如“点击”边和“购买”边）时，会拥有不同的边向量。它旨在捕获节点在特定关系下的上下文语义。 这种分解的设计非常巧妙，它允许模型在学习通用节点特征的同时，为每种关系保留其独特的语义信息。\n2.2 边向量的聚合：从邻居获取上下文 # 节点的边向量 \\(u_{i,r}\\) 并非直接学习，而是通过聚合其在边类型 \\(r\\) 下的邻居节点信息来动态生成。这一思想借鉴了GraphSAGE [1] 的聚合机制。一个节点的 \\(k\\) 阶边向量由其邻居的 \\(k-1\\) 阶边向量聚合而成：\n$$ \\mathbf{u}{i, r}^{(k)}=\\text{aggregator}\\left(\\left\\{\\mathbf{u}{j, r}^{(k-1)}, \\forall v_{j} \\in N_{i, r}\\right\\}\\right) $$其中，\\(N_{i,r}\\) 是节点 \\(v_i\\) 在边类型 \\(r\\) 下的邻居集合。聚合函数（aggregator）可以是均值、最大池化等。通过多层聚合，节点的边向量能够捕获到其在高阶邻域内的结构信息。\n2.3 自注意力机制的引入：动态融合多重关系 # GATNE最核心的创新在于如何将一个节点的多个边向量（每个对应一种边类型）融合成一个最终的边向量部分。如果一个网络有 \\(m\\) 种边类型，那么每个节点 \\(v_i\\) 就会有 \\(m\\) 个基础边向量 \\(\\{u_{i,1}, u_{i,2}, ..., u_{i,m}\\}\\)。\nGATNE并未使用简单的拼接或平均，而是引入了自注意力机制（Self-Attention）[2]来动态地计算不同边向量的权重。对于目标边类型 \\(r\\)，模型会为所有类型的边向量 \\(u_{i,p} (p=1,...,m)\\) 计算一个注意力系数 \\(a_{i,r,p}\\)。\n$$ a_{i,r} = \\text{softmax}(\\mathbf{w}_r^T \\tanh(\\mathbf{W}_r \\mathbf{U}_i))^T $$其中，\\(\\mathbf{U}i = [u{i,1}, ..., u_{i,m}]\\) 是节点 \\(v_i\\) 所有边向量的拼接矩阵，\\(\\mathbf{W}_r\\) 和 \\(\\mathbf{w}_r\\) 是针对目标边类型 \\(r\\) 的可学习的注意力参数。\n最终，节点 \\(v_i\\) 在边类型 \\(r\\) 下的整体表示 \\(v_{i,r}\\) 由基向量和加权融合后的边向量组合而成：\n$$ \\mathbf{v}{i, r}=\\mathbf{b}{i}+\\alpha_{r} \\mathbf{M}{r} \\mathbf{U}{i} \\mathbf{a}_{i, r} $$其中 \\(\\mathbf{M}_r\\) 是可学习的变换矩阵，\\(\\alpha_r\\) 是一个超参数，用于控制边向量的重要性。\n通过自注意力机制，GATNE能够根据目标任务（目标边类型 \\(r\\)）动态地判断其他类型的关系对当前任务的贡献度，从而实现信息的智能、高效融合。\n3. 从直推式到归纳式学习：GATNE-I # 上述模型（论文中称为GATNE-T）是直推式的，它为训练集中出现的每个节点直接学习基向量 \\(b_i\\)。这无法处理新节点。\n为了解决这个问题，论文提出了GATNE的归纳式版本——GATNE-I。其核心改动在于，不再为每个节点学习独立的基向量和初始边向量，而是学习一个从节点属性生成这些向量的函数。\n具体来说，节点的基向量 \\(b_i\\) 和初始边向量 \\(u_{i,r}^{(0)}\\) 由其原始属性 \\(x_i\\) 通过一个变换函数（如多层感知机MLP）生成：\n$$ \\mathbf{b}i = h_z(\\mathbf{x}i) $$$$ \\mathbf{u}{i,r}^{(0)} = g{z,r}(\\mathbf{x}_i) $$其中 \\(z\\) 是节点 \\(v_i\\) 的类型，\\(h_z\\) 和 \\(g_{z,r}\\) 是针对不同节点类型和边类型的可学习的函数。节点的最终表示也加入了一个直接由属性变换而来的项：\n$$ \\mathbf{v}{i, r}=h{z}\\left(\\mathbf{x}{i}\\right)+\\alpha{r} \\mathbf{M}{r} \\mathbf{U}{i} \\mathbf{a}{i, r}+\\beta{r} \\mathbf{D}{z} \\mathbf{x}{i} $$通过这种方式，只要一个新节点带有属性信息，GATNE-I就能为其生成高质量的表示，完美解决了冷启动和归纳学习的挑战。\n4. 模型优化与训练 # GATNE的训练过程采用了基于元路径的随机游走（meta-path based random walks）[3]来生成节点序列，这种策略能够有效保留异构网络中的复杂语义关系。在生成的序列上，模型采用异构Skip-gram目标，并通过负采样进行优化。目标函数为：\n$$ E=-\\log \\sigma\\left(\\mathbf{c}{j}^{T} \\cdot \\mathbf{v}{i, r}\\right)-\\sum_{l=1}^{L} \\mathbb{E}{v{k} \\sim P_{n}(v)}\\left[\\log \\sigma\\left(-\\mathbf{c}{k}^{T} \\cdot \\mathbf{v}{i, r}\\right)\\right] $$ 5. 实验效果与结论 # GATNE在Amazon、YouTube、Twitter以及阿里巴巴的真实工业数据集上进行了广泛的实验。结果表明，无论是在链路预测还是在真实的推荐系统A/B测试中，GATNE均显著优于包括DeepWalk、metapath2vec、MNE在内的所有基线模型。 例如，在阿里巴巴数据集上，GATNE-I相比当时最优的方法，在F1分数上取得了高达5.99%的提升。在阿里巴巴推荐系统的离线A/B测试中，GATNE-I的Top-N点击命中率相比MNE和DeepWalk分别提升了3.26%和24.26%，展现了其强大的工业应用价值。\n总结来说，GATNE的主要贡献在于：\n统一框架：首次提出了一个能够统一处理属性、多重关系和异构性的网络表示学习框架AMHEN。 创新的表示分解：将节点表示分解为共享的基向量和关系特定的边向量，兼顾了通用性与特异性。 自注意力融合：巧妙地运用自注意力机制，实现了对多重关系的动态、加权融合，极大地提升了模型的表达能力。 兼具直推式与归纳式能力：同时提出了GATNE-T和GATNE-I两个版本，使其既能处理静态网络，也能应对动态网络中的新节点问题，具备很强的实用性。 GATNE的设计为复杂图表示学习提供了一个强大且优雅的解决方案，对学术研究和工业应用都具有重要的指导意义。\n","date":"2022-07-22","externalUrl":null,"permalink":"/blogs/kg/gatne-representation-learning-for-attributed-multiplex-heterogeneous-network/","section":"Blog","summary":"","title":"GATNE：面向属性、多重、异构网络的统一表示学习框架","type":"blogs"},{"content":" HowtoReadPaper\n告别低效阅读：掌握三遍阅读法，高效读懂任何学术论文 # 对于科研人员和研究生而言，阅读学术论文是一项核心日常。然而，如何高效地阅读论文，却是一项鲜少被系统传授的技能。许多人习惯于从头到尾逐字阅读，这种方式不仅耗时巨大，还常常在细节中迷失方向，导致事倍功半，倍感挫败。\nS. Keshav 教授提出的“三遍阅读法”（The Three-Pass Approach）是一个极为实用且高效的策略。它提倡通过三次递进的阅读，系统性地、有层次地深入理解一篇论文，从而在最短的时间内获取最有价值的信息。\n核心理念 # 三遍阅读法的核心在于，将一次完整的阅读分解为三个独立的阶段。每一遍都有其特定的目标，并为下一遍的阅读奠定基础。\n第一遍： 获得对论文的整体印象，建立“鸟瞰”视角。 第二遍： 把握论文的主要内容，但不纠结于技术细节。 第三遍： 深入理解论文，达到能够复现和批判的程度。 第一遍阅读：鸟瞰全景（5-10分钟） # 第一遍阅读的目标是快速扫描，对论文有一个宏观的把握，并判断是否需要投入更多时间进行后续阅读。\n操作步骤：\n仔细阅读标题、摘要和引言（Introduction）。 这三部分是作者对工作的核心浓缩。 阅读章节和小节的标题，但忽略正文内容。 这有助于快速了解论文的整体结构和论证逻辑。 阅读结论（Conclusion）。 结论部分会再次总结论文的主要发现。 浏览参考文献。 快速扫一眼，看看其中是否有自己熟悉的文献，这有助于将该论文定位到相应的知识体系中。 完成后，应当能回答以下五个核心问题（5C）：\n论文类别（Category）： 这是一篇什么类型的论文？是提出了一个新系统原型，还是一项测量研究，或是一个理论分析？ 相关背景（Context）： 它与哪些其他研究相关？它建立在哪些理论基础之上？ 正确性（Correctness）： 论文的基本假设看起来是否合理？ 主要贡献（Contribution）： 这篇论文最主要的贡献是什么？ 行文清晰度（Clarity）： 论文的写作水平如何，是否易于理解？ 完成第一遍后，就可以做出决策：如果论文内容不符合兴趣，或者其假设存在明显问题，亦或是暂时不具备理解该论文所需的背景知识，那么就可以到此为止。对于那些并非核心研究领域但未来可能相关的论文，第一遍阅读已经足够。\n第二遍阅读：把握内容（约1小时） # 如果决定继续阅读，第二遍的目标是理解论文的主要内容，但可以暂时忽略复杂的证明和实现细节。\n操作步骤：\n仔细阅读论文正文，但跳过证明等技术细节。 重点是理解作者的论点和支撑论点的证据。在阅读时，在页边空白处记下要点或评论是一个好习惯。 重点关注图、表和示意图。 仔细检查图表：坐标轴是否清晰标注？实验结果是否包含误差棒以体现统计显著性？这些细节是区分严谨工作和粗糙工作的重要标志。 标记未读过的相关参考文献。 这是一个扩展背景知识的好方法。 完成第二遍后，应当能够向他人清晰地总结出这篇论文的核心论点、主要方法和实验证据。这个深度对于大部分感兴趣但非自己专业方向的论文来说，已经非常充分。\n有时，在第二遍阅读后仍会感到困惑。这可能是因为主题陌生、术语不熟，或者作者使用了自己不了解的证明或实验技术。此时有三种选择：\n（a）暂时搁置这篇论文。 （b）先去阅读相关的背景材料，稍后再回来读。 （c）坚持下去，直接进入第三遍阅读。 第三遍阅读：深度理解（4-5小时） # 第三遍阅读的目标是完全、深入地吃透论文，尤其是在需要审阅（Review）这篇论文时。\n核心方法： 尝试在脑海中**“虚拟地复现”**（Virtually Re-implement）这篇论文。即，站在作者的角度，使用和他们相同的假设，尝试从头重构整个工作。\n通过将自己的“虚拟复现”与论文的实际内容进行对比，可以非常敏锐地发现：\n论文真正的创新点在哪里。 其背后隐藏的假设和未明说的前提条件。 论证过程中可能存在的缺陷或漏洞。 完成第三遍后，应当能够从记忆中完整地重构出论文的整体结构，并清晰地指出其优点和缺点，包括那些作者没有明确指出的隐含假设、缺失的关键引文以及实验或分析方法中潜在的问题。\n应用：如何进行文献综述（Literature Survey） # 阅读论文的技能在进行文献综述时将面临最终考验。三遍阅读法同样可以指导这个过程：\n起点： 使用学术搜索引擎（如 Google Scholar）和合适的关键词，找到3-5篇该领域最新的高质量论文。对每篇论文进行第一遍阅读，并特别关注其“相关工作”（Related Work）部分。 发现关键人物和文献： 寻找这些论文参考文献中被共同引用、反复出现的作者和论文。这些通常是该领域的关键研究者和奠基性工作。下载这些关键论文。 锁定顶级会议： 访问这些关键研究者近期的发表记录，找到他们常发表的顶级会议。浏览这些会议最近的论文集，可以快速定位更多高质量的相关工作。 深入阅读： 将所有收集到的论文作为一个集合，对它们进行第二遍阅读。如果在这个过程中又发现了一篇之前遗漏的关键论文，就把它也加进来，并重复这个过程。 通过这种系统性的方法，可以高效地在一个新领域建立起完整的知识图谱。\n","date":"2022-07-20","externalUrl":null,"permalink":"/blogs/sundry/how-to-read-a-paper/","section":"Blog","summary":"","title":"如何阅读一篇论文","type":"blogs"},{"content":"","date":"2022-07-20","externalUrl":null,"permalink":"/tags/%E6%9D%82%E8%AE%B0/","section":"Tags","summary":"","title":"杂记","type":"tags"},{"content":"","date":"2022-07-16","externalUrl":null,"permalink":"/tags/linux-%E6%8C%87%E4%BB%A4/","section":"Tags","summary":"","title":"Linux 指令","type":"tags"},{"content":"无论是后端开发者、数据科学家，还是运维工程师，Linux 都是绕不开的强大工具。它稳定、高效、开源，是现代服务器和开发环境的基石。掌握常用的 Linux 命令，能极大地提升工作效率。\n本文内容涵盖文件操作、进程监控、远程会话管理等核心领域。\n1. Linux 命令的基本哲学 # 在开始之前，先了解 Linux 命令的基本格式和两个核心哲学。\n命令格式:\ncommand [options] [arguments] command: 你要执行的命令，如 ls, cd。 options: 命令的选项或标志，通常以 (短选项，如 l) 或 - (长选项，如 -all) 开头，用于调整命令的行为。 arguments: 命令作用的对象，通常是文件名或路径。 核心哲学:\n一切皆文件 (Everything is a file)：在 Linux 中，硬件设备、目录、网络连接等都被抽象为文件，这使得我们可以用统一的方式来处理它们。 组合小程序，完成复杂任务：Linux 提倡使用功能单一的小工具，通过管道（Pipe）将它们组合起来，像乐高积木一样搭建出强大的功能。 2. 文件与目录核心操作 # 这是最基础也是最频繁的操作。\n命令 描述 常用示例 ls 列出目录中的文件和子目录。 ls -alh (列出所有文件，包括隐藏文件，并以人类可读的格式显示大小) cd 切换当前工作目录。 cd /var/log (切换到指定目录) cd .. (返回上一级目录) cd ~ 或 cd (返回家目录) pwd 显示当前所在的目录路径。 pwd mkdir 创建新目录。 mkdir my_project mkdir -p dir1/dir2 (-p 选项可以递归创建多层目录) touch 创建一个空文件或更新文件时间戳。 touch new_file.txt cp 复制文件或目录。 cp source.txt dest.txt cp -r source_dir/ dest_dir/ (-r 选项用于递归复制整个目录) mv 移动或重命名文件/目录。 mv old_name.txt new_name.txt (重命名) mv file.txt /path/to/dest/ (移动) rm 删除文件或目录。 rm file.txt rm -r old_dir/ (-r 选项用于递归删除目录) 警告：rm -rf / 是世界上最危险的命令，请谨慎使用 -rf！ chmod 修改文件或目录的权限。 chmod u+x script.sh (给文件的所有者 u 增加可执行权限 x) chmod 755 script.sh (使用数字模式设置权限) 3. IO 重定向与管道 # 这是发挥 Linux \u0026ldquo;组合\u0026rdquo; 哲学威力的关键。\n管道 | 将上一个命令的标准输出 (stdout) 连接到下一个命令的标准输入 (stdin)。 示例：查找所有 Python 相关的进程。\nps -aux | grep python 重定向 \u0026gt; 和 \u0026gt;\u0026gt; 将命令的输出重定向到文件，而不是显示在屏幕上。\n\u0026gt; (覆盖)：如果文件存在，则覆盖其内容；如果不存在，则创建新文件。 \u0026gt;\u0026gt; (追加)：如果文件存在，则将输出追加到文件末尾；如果不存在，则创建新文件。 示例：\n# 将命令输出保存到 log.txt (覆盖) ls -l \u0026gt; log.txt # 将新的日志追加到 log.txt echo \u0026#34;This is a new log entry\u0026#34; \u0026gt;\u0026gt; log.txt 标准输出与标准错误 Linux 中有两个主要的输出流：\n1: 标准输出 (stdout) 2: 标准错误 (stderr) 默认情况下，\u0026gt; 只重定向标准输出。要同时重定向两者，可以使用 2\u0026gt;\u0026amp;1。 示例：将命令的所有输出（包括错误信息）都保存到文件。 ./my_program \u0026gt; output.log 2\u0026gt;\u0026amp;1 4. 进程监控与管理 # 实时了解系统正在运行什么，并对它们进行管理。\nps -aux 静态查看当前系统的所有进程。\nUSER: 进程所有者 PID: 进程 ID，是进程的唯一标识。 %CPU: CPU 使用率。 %MEM: 内存使用率。 STAT: 进程状态 (R:运行, S:睡眠, Z:僵尸)。 COMMAND: 启动进程的命令。 top 动态、实时地显示系统进程状态，相当于任务管理器。\nPID, USER, %CPU, %MEM, COMMAND: 与 ps 类似。 PR: 优先级。 NI: Nice 值，负值表示高优先级。 在 top 界面，可以按 P (按CPU排序)，M (按内存排序)，q (退出)。 kill 向进程发送信号，通常用于终止进程。\n# 优雅地终止进程 (发送 SIGTERM 信号) kill [PID] # 强制终止进程 (发送 SIGKILL 信号)，用于进程无响应时 kill -9 [PID] 5. GPU 资源监控 (AI/ML 必备) # 对于进行深度学习等计算密集型任务的用户，nvidia-smi 是必不可少的工具。\nnvidia-smi 实时监控 NVIDIA GPU 的状态。\nFan: 风扇转速。 Temp: GPU 温度。 Perf: 性能状态 (P0 最高，P12 最低)。 Pwr:Usage/Cap: 当前功耗 / 总功耗。 Memory-Usage: 显存使用量，这是最常关注的指标。 GPU-Util: GPU 利用率。 下方会列出当前正在使用 GPU 的进程及其 PID 和显存占用。 指定使用的 GPU 当服务器有多张 GPU 时，可以通过环境变量 CUDA_VISIBLE_DEVICES 来指定程序在哪张卡上运行。 命令行中指定：\n# 让 python 程序只在 GPU 1 上运行 CUDA_VISIBLE_DEVICES=1 python my_script.py 在 Python 脚本中指定：\nimport os os.environ[\u0026#34;CUDA_VISIBLE_DEVICES\u0026#34;] = \u0026#34;0\u0026#34; # 指定使用 GPU 0 # 后续的 PyTorch 或 TensorFlow 代码将只会看到 GPU 0 6. 文本编辑神器：Vim # Vim 是 Linux 环境下强大的文本编辑器，虽然学习曲线陡峭，但回报巨大。\n三种模式 正常模式 (Normal Mode): 默认模式，用于移动光标、删除、复制、粘贴文本。 插入模式 (Insert Mode): 用于输入文本。按 i, a, o 等键从正常模式进入。按 Esc 返回正常模式。 命令模式 (Command Mode): 在正常模式下按 : 进入，用于保存、退出、搜索等。 常用操作 dd: 删除整行。 yy: 复制整行。 p: 粘贴。 u: 撤销。 Ctrl + r: 重做（反撤销）。 :w: 保存。 :q: 退出。 :wq: 保存并退出。 :q!: 强制退出（不保存）。 7. 环境配置与解压缩 # 配置文件\n.bashrc 或 .profile: 用户家目录下的隐藏文件，用于定义用户的环境变量和别名。 export PATH=$PATH:/path/to/my/bin: 临时将一个新路径添加到 PATH 环境变量中，使其下的可执行文件能被直接调用。将此行写入 .bashrc 可使其永久生效（需 source ~/.bashrc 或重开终端）。 别名 alias 为长命令创建快捷方式，写入 .bashrc 中。\nalias ll=\u0026#39;ls -alh\u0026#39; alias myip=\u0026#39;curl ifconfig.me\u0026#39; 解压缩\ntar (常用于 .tar.gz 或 .tgz 文件) 解压: tar -xzvf archive.tar.gz 压缩: tar -czvf archive.tar.gz directory_to_compress/ zip/unzip 解压: unzip archive.zip 压缩: zip -r archive.zip directory_to_compress/ 8. 让程序在后台运行 # 当需要运行耗时很长的任务，并且希望关闭终端后程序依然运行时，以下工具至关重要。\nnohup \u0026ldquo;No Hang Up\u0026rdquo; 的缩写，让命令在退出终端后继续运行，默认输出会重定向到 nohup.out 文件。\nnohup python my_long_task.py \u0026amp; \u0026amp; 符号表示将命令放到后台执行。\nscreen 一个更强大的工具，可以创建多个虚拟终端会话，并随时分离（detach）和重新连接（reattach）。\n新建会话: screen -S my_session_name 在会话中运行程序: python my_app.py 分离会话: 按 Ctrl + a 然后按 d。此时你可以安全地关闭终端。 列出所有会话: screen -ls 重新连接会话: screen -r my_session_name ","date":"2022-07-16","externalUrl":null,"permalink":"/blogs/sundry/linux-operation/","section":"Blog","summary":"","title":"Linux常用指令","type":"blogs"},{"content":"","date":"2022-07-15","externalUrl":null,"permalink":"/tags/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/","section":"Tags","summary":"","title":"正则表达式","type":"tags"},{"content":"在编程和文本处理的世界里，正则表达式（Regular Expression，常缩写为 Regex 或 Regexp）是一个无比强大的工具。它能帮你从海量文本中，精准地查找、匹配和替换符合特定模式的字符串。无论你是前端开发者需要验证用户输入，还是后端工程师需要解析日志文件，掌握 Regex 都将让你事半功倍。\n这篇指南将带你从最基础的概念开始，逐步深入到高级技巧，彻底掌握这个“文本瑞士军刀”。\n什么是正则表达式？ # 简单来说，正则表达式是一组由字母、数字和特殊符号组成的“规则字符串”，它定义了你想要查找的文本模式。\n它是一种从左到右匹配目标字符串的模式。想象一下，你想为一个应用设定用户名规则：必须包含小写字母、数字、下划线和连字符，并且长度在3到15个字符之间。\n使用正则表达式，我们可以这样定义这个规则：\n^[a-z0-9_-]{3,15}$\n我们来拆解一下这个例子：\n^：开始标记，表示匹配必须从字符串的开头开始。 [a-z0-9_-]：字符集，表示允许的字符包括：所有小写字母（a-z）、所有数字（0-9）、下划线（_）和连字符（-）。 {3,15}：量词，表示前面的字符集必须连续出现3到15次。 $：结束标记，表示匹配必须在字符串的结尾结束。 根据这个规则，john_doe 和 john12-as 是合法的，但 Jo 就不合法，因为它包含了大写字母且长度太短。\n1. 基本匹配 # 正则表达式最简单的形式就是直接匹配。它由一些字母和数字组合而成，会精确匹配一模一样的字符串。\n示例：正则表达式 the 匹配：The fat cat sat on **the** mat. 注意：基本匹配是大小写敏感的。因此，The 不会匹配 the。 2. 核心利器：元字符 # 元字符是 Regex 的精髓，它们不代表字面意思，而是有特殊的含义。\n2.1 点运算符 . # 点 . 是最简单的元字符，它能匹配除换行符外的任意单个字符。\n示例：c.t 匹配：The fat **cat** sat on the mat. 2.2 字符集 [] # 方括号 [] 用来定义一个字符集，表示匹配方括号内任意一个字符。\n示例：[Tt]he 匹配：*The** car is parked in **the** garage. 否定字符集 [^]：如果在方括号内的开头使用 ^，则表示匹配除了方括号内字符以外的任意字符。\n示例：[^c]ar 匹配：The car parked in the ga**rar**ge. (匹配了 rar，但跳过了 car) 2.3 重复次数（量词） # 量词用来指定一个字符或字符组需要重复出现的次数。\n符号 描述 示例 匹配 * 匹配0次或多次 \\\\s*cat\\\\s* The fat** cat **sat on the concatenation. + 匹配1次或多次 c.+t The fat **cat sat on the mat**. ? 匹配0次或1次（表示可选） [T]?he **The** car is parked in **the** garage. {n,m} 匹配最少n次，最多m次 [0-9]{2,3} The number was 9.9997 but we rounded it off to **10**. {n} 精确匹配n次 [0-9]{3} The number was 9.**999**7 but we rounded it off to 10.0. 2.4 特征标群 () # 括号 () 用于将多个字符组合成一个单元（一个子模式）。量词可以作用于这个单元。\n示例：(ab)* 说明：匹配连续出现的 \u0026ldquo;ab\u0026rdquo; 零次或多次。 2.5 或运算符 | # 竖线 | 的作用是“或”，用于匹配 | 左边或右边的表达式。\n示例：(T|t)he|car 匹配：*The** **car** is parked in **the** garage. 2.6 转义特殊字符 \\\\ # 如果你想匹配元字符本身（如 .、*、?），需要在其前面加上反斜杠 \\\\ 进行转义。\n示例：(f|c|m)at\\\\.? 匹配：The fat **cat** sat on the **mat.** 2.7 锚点 ^ 和 $ # 锚点用于将匹配锁定在字符串的特定位置。\n^：匹配字符串的开头。 $：匹配字符串的结尾。 示例：^(T|t)he 匹配以 The 或 the 开头的字符串。 示例：at\\\\.$ 匹配以 at. 结尾的字符串。 3. 简写字符集 # 为了方便，Regex 提供了一些常用字符集的简写形式。\n简写 等同于 描述 \\\\w [a-zA-Z0-9_] 匹配字母、数字或下划线（单词字符） \\\\W [^\\\\w] 匹配非单词字符 \\\\d [0-9] 匹配任意数字 \\\\D [^\\\\d] 匹配任意非数字字符 \\\\s [\\\\t\\\\n\\\\f\\\\r ] 匹配任意空白字符（空格、制表符、换行符等） \\\\S [^\\\\s] 匹配任意非空白字符 . 匹配除换行符外的所有字符 4. 高级技巧：零宽度断言 # 零宽度断言（Lookarounds）是一种强大的高级技巧。它们用于检查某个位置之前或之后是否符合特定模式，但这个模式本身不会被包含在最终的匹配结果中。它们只起到断言（判断）的作用，不消耗任何字符。\n符号 类型 描述 (?=...) 正先行断言 (Positive Lookahead) 要求当前位置的后面能匹配 ... 模式 (?!...) 负先行断言 (Negative Lookahead) 要求当前位置的后面不能匹配 ... 模式 (?\u0026lt;=...) 正后发断言 (Positive Lookbehind) 要求当前位置的前面能匹配 ... 模式 (?\u0026lt;!...) 负后发断言 (Negative Lookbehind) 要求当前位置的前面不能匹配 ... 模式 正先行断言示例：(T|t)he(?=\\\\sfat) 匹配：*The** fat cat sat on the mat. (只匹配 \u0026ldquo;The\u0026rdquo;，因为它后面跟着 \u0026quot; fat\u0026quot;) 负先行断言示例：(T|t)he(?!\\\\sfat) 匹配：The fat cat sat on **the** mat. (只匹配第二个 \u0026ldquo;the\u0026rdquo;，因为它后面没有跟着 \u0026quot; fat\u0026quot;) 正后发断言示例：(?\u0026lt;=(T|t)he\\\\s)(fat|mat) 匹配：The **fat** cat sat on the mat. (只匹配 \u0026ldquo;fat\u0026rdquo;，因为它前面是 \u0026ldquo;The \u0026ldquo;) 负后发断言示例：(?\u0026lt;!(T|t)he\\\\s)cat 匹配：The cat sat on **cat**. (只匹配第二个 \u0026ldquo;cat\u0026rdquo;，因为它前面没有 \u0026ldquo;The \u0026quot; 或 \u0026ldquo;the \u0026ldquo;) 5. 模式的“开关”：标志 # 标志（Flags）也叫修饰符，可以改变整个正则表达式的搜索行为。\n标志 名称 描述 i Case Insensitive 忽略大小写进行匹配 g Global Search 全局搜索，查找所有匹配项，而不是找到第一个就停止 m Multiline 多行模式。使 ^ 和 $ 能匹配每一行的开头和结尾，而不仅仅是整个字符串的开头和结尾 示例：/the/gi 说明：g 表示全局搜索，i 表示忽略大小写。 匹配：*The** fat cat sat on **the** mat. (匹配所有 \u0026ldquo;the\u0026rdquo;，不分大小写) 6. 贪婪与惰性匹配 # 默认情况下，量词（如 * 和 +）是贪婪的（Greedy），它们会尽可能多地匹配字符。\n贪婪示例：/(.*at)/ 匹配：The fat cat sat on the mat. -\u0026gt; 匹配结果是 *The fat cat sat on the mat** 有时我们希望它尽可能少地匹配。这时，可以在量词后面加上一个 ?，使其变为惰性的（Lazy）。\n惰性示例：/(.*?at)/ 匹配：The fat cat sat on the mat. -\u0026gt; 匹配结果是 *The fat** (匹配到第一个 at 就停止了) 结语 # 正则表达式初看起来可能像一堆天书般的符号，但一旦理解了其核心组件——元字符、量词、锚点和标志——就会发现它是一个逻辑严谨且功能强大的工具。\n掌握 Regex 的最好方法就是不断练习。利用在线的 Regex 测试工具（如 regex101.com），尝试构建和测试自己的模式，很快就能运用自如，解决各种复杂的文本处理难题。\n","date":"2022-07-15","externalUrl":null,"permalink":"/blogs/sundry/regex/","section":"Blog","summary":"","title":"正则表达式（Regex）指南","type":"blogs"},{"content":"","date":"2022-06-27","externalUrl":null,"permalink":"/tags/markdown/","section":"Tags","summary":"","title":"Markdown","type":"tags"},{"content":" Markdown 是一种轻量级的标记语言，可用于在纯文本文档中添加格式化元素。Markdown 由 John Gruber 于 2004 年创建，如今已成为世界上最受欢迎的标记语言之一。\n专注于文字内容 纯文本，易读易写，可以方便地纳入版本控制 语法简单，没有什么学习成本，能轻松在码字的同时做出美观大方的排版 使用 Markdown 与使用 Word 类编辑器不同。在 Word 之类的应用程序中，单击按钮以设置单词和短语的格式，并且，更改立即可见。而 Markdown 与此不同，当你创建 Markdown 格式的文件时，可以在文本中添加 Markdown 语法，以指示哪些单词和短语看起来应该有所不同。\nMarkdown 语法的首要设计目标是尽可能易读。基于这个目标，Markdown 格式的文档能够以纯文本形式原样发布，而不会看起来像被填满了标签或格式化指令。\n基本语法 # 千万不要被「标记」、「语言」吓到，Markdown的语法十分简单，常用的标记符号不超过十个，用于日常写作记录绰绰有余，不到半小时就能完全掌握。\n就是这十个不到的标记符号，却能让人优雅地沉浸式记录，专注内容而不是纠结排版，达到「心中无尘，码字入神」的境界。\n标题语法 # 一般使用“#”的数量来标记标题的级别，数量越高，级别越大，字体越小。注意在标题与“#”之间有一个空格。\nMarkDown语法 HTML 效果 # Heading level 1 /Heading level 1/ Heading level 1 ## Heading level 2 /Heading level 2/ Heading level 2 ### Heading level 3 /Heading level 3/ Heading level 3 #### Heading level 4 /Heading level 4/ Heading level 4 ##### Heading level 5 /Heading level 5/ Heading level 5 段落语法 # 创建段落，使用空白行将一行或多行文本进行分隔。\n不能使用空格或者Tab键进行首行缩进。\n与 HTML 中对应的符号是 \u0026lt;p\u0026gt; \u0026lt;/p\u0026gt;\n强调语法 # 粗体 # 在单词或短语的前后各添加两个星号（asterisks）或下划线（underscores）。如需加粗一个单词或短语的中间部分用以表示强调的话，在要加粗部分的两侧各添加两个星号。\n斜体 # 在单词或短语前后添加一个星号（asterisk）或下划线（underscore）。要斜体突出单词的中间部分，请在字母前后各添加一个星号，中间不要带空格。\n粗斜体 # 要同时用粗体和斜体突出显示文本，请在单词或短语的前后各添加三个星号或下划线。要加粗并用斜体显示单词或短语的中间部分，请在要突出显示的部分前后各添加三个星号，中间不要带空格。\n引用语法 # 要创建块引用，请在段落前添加一个 \u0026gt; 符号。\n块引用可以包含多个段落。为段落之间的空白行添加一个 \u0026gt; 符号。\n块引用可以嵌套。在要嵌套的段落前添加一个 \u0026gt;\u0026gt; 符号。\n块引用可以包含其他 Markdown 格式的元素，但是并非所有元素都可以使用。\n列表语法 # 有序列表 # 要创建有序列表，请在每个列表项前添加数字并紧跟一个英文句点。数字不必按数学顺序排列，但是列表应当以数字 1 起始。\n无序列表 # 要创建无序列表，请在每个列表项前面添加破折号 (-)、星号 (*) 或加号 (+) 。缩进一个或多个列表项可创建嵌套列表。\n要在保留列表连续性的同时在列表中添加另一种元素，请将该元素缩进四个空格或一个制表符。例如：\nOpen the file.\nHello world\nFind the following code block on line 21:\n\u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Test\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; Update the title to match the name of your website.\nMarkDown picture 代码语法 # 要将单词或短语表示为代码，请将其包裹在反引号 (```) 中。\n如果你要表示为代码的单词或短语中包含一个或多个反引号，则可以通过将单词或短语包裹在双反引号( )中\n要创建代码块，请将代码块的每一行缩进至少四个空格或一个制表符。\n\u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Test\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;/html\u0026gt; 分隔线语法 # 要创建分隔线，请在单独一行上使用三个或多个星号 (***)、破折号 (---) 或下划线 (___) ，并且不能包含其他内容。\n链接语法 # 链接文本放在中括号内，链接地址放在后面的括号中，链接title可选。\n超链接Markdown语法代码：\n[超链接显示名](超链接地址 \u0026quot;超链接title\u0026quot;)\n对应的HTML代码：\n\u0026lt;a href=\u0026quot;超链接地址\u0026quot; title=\u0026quot;超链接title\u0026quot;\u0026gt;超链接显示名\u0026lt;/a\u0026gt;\n给链接加title # 链接title是当鼠标悬停在链接上时会出现的文字，这个title是可选的，它放在圆括号中链接地址后面，跟链接地址之间以空格分隔。\n这是一个链接 [Markdown语法](\u0026lt;https://markdown.com.cn\u0026gt; \u0026quot;最好的markdown教程\u0026quot;)。\n渲染效果：\n这是一个链接 Markdown语法。\n网址和 Email 地址 # 使用尖括号可以很方便地把URL或者email地址变成可点击的链接。\n\u0026lt;https://markdown.com.cn\u0026gt;\n\u0026lt;fake@example.com\u0026gt;\n渲染效果：\nhttps://markdown.com.cn\nfake@example.com\n带格式的链接 # 强调\n链接, 在链接语法前后增加星号。 要将链接表示为代码，请在方括号中添加反引号。\n引用类型链接 # 引用样式链接是一种特殊的链接，它使URL在Markdown中更易于显示和阅读。参考样式链接分为两部分：与文本保持内联的部分以及存储在文件中其他位置的部分，以使文本易于阅读。\n链接的第一部分格式\n引用类型的链接的第一部分使用两组括号进行格式设置。第一组方括号包围应显示为链接的文本。第二组括号显示了一个标签，该标签用于指向存储在文档其他位置的链接。\n尽管不是必需的，可以在第一组和第二组括号之间包含一个空格。第二组括号中的标签不区分大小写，可以包含字母，数字，空格或标点符号。\n以下示例格式对于链接的第一部分效果相同：\n`[hobbit-hole][1]`` ``[hobbit-hole] [1]` 链接的第二部分格式\n引用类型链接的第二部分使用以下属性设置格式：\n放在括号中的标签，其后紧跟一个冒号和至少一个空格（例如[label]:）。\n链接的URL，可以选择将其括在尖括号中。\n链接的可选标题，可以将其括在双引号，单引号或括号中。\n可以将链接的第二部分放在Markdown文档中的任何位置。有些人将它们放在出现的段落之后，有些人则将它们放在文档的末尾（例如尾注或脚注)。\n不同的 Markdown 应用程序处理URL中间的空格方式不一样。为了兼容性，请尽量使用%20代替空格。\n图片语法 # 要添加图像，请使用感叹号 (!), 然后在方括号增加替代文本，图片链接放在圆括号里，括号里的链接后可以增加一个可选的图片标题文本。\n插入图片Markdown语法代码：\n`![图片alt](图片链接 \u0026quot;图片title\u0026quot;)`。 对应的HTML代码：\n`\u0026lt;img src=\u0026quot;图片链接\u0026quot; alt=\u0026quot;图片alt\u0026quot; title=\u0026quot;图片title\u0026quot;\u0026gt;` 链接图片 # 给图片增加链接，请将图像的Markdown 括在方括号中，然后将链接添加在圆括号中。 [![shiprock.c3b9a023](\u0026lt;https://cdn.jsdelivr.net/gh/gongzitaiyi/picture@master/uPic/2025/11/dyBQ7y.png\u0026gt;)](\u0026lt;https://markdown.com.cn/basic-syntax/images.html\u0026gt;)\n转义字符语法 # 要显示原本用于格式化 Markdown 文档的字符，请在字符前面添加反斜杠字符\\\\。\n以下列出的字符都可以通过使用反斜杠字符从而达到转义目的:\nCharacter Name \\ backslash ` backtick (see also escaping backticks in code) * asterisk _ underscore { } curly braces [ ] brackets ( ) parentheses # pound sign + plus sign - minus sign (hyphen) . dot ! exclamation mark 特殊字符自动转义 # 在 HTML 文件中，有两个字符需要特殊处理： \u0026lt; 和 \u0026amp; 。 \u0026lt; 符号用于起始标签，\u0026amp; 符号则用于标记 HTML 实体，如果你只是想要使用这些符号，你必须要使用实体的形式，像是 \u0026lt; 和 \u0026amp;。\n\u0026amp; 符号其实很容易让写作网页文件的人感到困扰，如果你要打「AT\u0026amp;T」 ，你必须要写成「AT\u0026amp;T」 ，还得转换网址内的 \u0026amp; 符号，如果你要链接到：\n\u0026lt;http://images.google.com/images?num=30\u0026amp;q=larry+bird\u0026gt; 你必须要把网址转成：\n\u0026lt;http://images.google.com/images?num=30\u0026amp;amp;q=larry+bird\u0026gt; 才能放到链接标签的 href 属性里。不用说也知道这很容易忘记，这也可能是 HTML 标准检查所检查到的错误中，数量最多的。\nMarkdown 允许你直接使用这些符号，它帮你自动转义字符。如果你使用 \u0026amp; 符号的作为 HTML 实体的一部分，那么它不会被转换，而在其它情况下，它则会被转换成 \u0026amp;。所以你如果要在文件中插入一个著作权的符号，你可以这样写：\n\u0026amp;copy; Markdown 将不会对这段文字做修改，但是如果你这样写：\nAT\u0026amp;T Markdown 就会将它转为：\nAT\u0026amp;amp;T 类似的状况也会发生在 \u0026lt; 符号上，因为 Markdown 支持 行内 HTML ，如果你使用 \u0026lt; 符号作为 HTML 标签的分隔符，那 Markdown 也不会对它做任何转换，但是如果你是写：\n4 \u0026lt; 5 Markdown 将会把它转换为：\n4 \u0026amp;lt; 5 需要特别注意的是，在 Markdown 的块级元素和内联元素中， \u0026lt; 和 \u0026amp; 两个符号都会被自动转换成 HTML 实体，这项特性让你可以很容易地用 Markdown 写 HTML。（在 HTML 语法中，你要手动把所有的 \u0026lt; 和 \u0026amp; 都转换为 HTML 实体。\n标签 # 内嵌 HTML 标签 # 对于 Markdown 涵盖范围之外的标签，都可以直接在文件里面用 HTML 本身。如需使用 HTML，不需要额外标注这是 HTML 或是 Markdown，只需 HTML 标签添加到 Markdown 文本中即可。\n行级內联标签 # HTML 的行级內联标签如 \u0026lt;span\u0026gt;、\u0026lt;cite\u0026gt;、\u0026lt;del\u0026gt; 不受限制，可以在 Markdown 的段落、列表或是标题里任意使用。依照个人习惯，甚至可以不用 Markdown 格式，而采用 HTML 标签来格式化。例如：如果比较喜欢 HTML 的 \u0026lt;a\u0026gt; 或 \u0026lt;img\u0026gt; 标签，可以直接使用这些标签，而不用 Markdown 提供的链接或是图片语法。当你需要更改元素的属性时（例如为文本指定颜色或更改图像的宽度），使用 HTML 标签更方便些。\nHTML 行级內联标签和区块标签不同，在內联标签的范围内， Markdown 的语法是可以解析的。\nThis **word** is bold. This \u0026lt;em\u0026gt;word\u0026lt;/em\u0026gt; is italic. 渲染效果：\nThis word is bold. This word is italic.\n区块标签 # 区块元素──比如 \u0026lt;div\u0026gt;、\u0026lt;table\u0026gt;、\u0026lt;pre\u0026gt;、\u0026lt;p\u0026gt; 等标签，必须在前后加上空行，以便于内容区分。而且这些元素的开始与结尾标签，不可以用 tab 或是空白来缩进。Markdown 会自动识别这区块元素，避免在区块标签前后加上没有必要的 \u0026lt;p\u0026gt; 标签。\n例如，在 Markdown 文件里加上一段 HTML 表格：\nThis is a regular paragraph. \u0026lt;table\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;one\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;two\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/table\u0026gt; This is another regular paragraph. 渲染结果：\nhis is a regular paragraph.\none two This is another regular paragraph.\n请注意，Markdown 语法在 HTML 区块标签中将不会被进行处理。例如，你无法在 HTML 区块内使用 Markdown 形式的*强调*。\n拓展语法 # hn Gruber的原始设计文档中概述的基本语法主要是为了应付大多数情况下的日常所需元素，但对于某些人来说还不够，这就是扩展语法的用武之地。\n一些个人和组织开始通过添加其他元素（例如表，代码块，语法突出显示，URL自动链接和脚注）来扩展基本语法。可以通过使用基于基本Markdown语法的轻量级标记语言，或通过向兼容的Markdown处理器添加扩展来启用这些元素。\n并非所有Markdown应用程序都支持扩展语法元素。需要检查的应用程序所使用的轻量级标记语言是否支持要使用的扩展语法元素。如果没有，那么仍然有可能在Markdown处理器中启用扩展。\n表格 # 要添加表，请使用三个或多个连字符（---）创建每列的标题，并使用管道（|）分隔每列。可以选择在表的任一端添加管道。\n| Syntax | Description | | ----------- | ----------- | | Header | Title | | Paragraph | Text | 呈现的输出如下所示：\nSyntax Description Header Title Paragraph Text 单元格宽度可以变化，如下所示。呈现的输出将看起来相同。\n| Syntax | Description | | --- | ----------- | | Header | Title | | Paragraph | Text | Syntax Description Header Title Paragraph Text 对齐 # 可以通过在标题行中的连字符的左侧，右侧或两侧添加冒号（:），将列中的文本对齐到左侧，右侧或中心。\n| Syntax | Description | Test Text | | :--- | :----: | ---: | | Header | Title | Here\u0026#39;s this | | Paragraph | Text | And more | 呈现的输出如下所示：\nSyntax Description Test Text Header Title Here’s this Paragraph Text And more 格式化表格中的文字 # 可以在表格中设置文本格式。例如，可以添加链接，代码（仅反引号（```）中的单词或短语，而不是代码块）和强调。\n不能添加标题，块引用，列表，水平规则，图像或HTML标签。\n在表中转义管道字符 # 可以使用表格的HTML字符代码（|）在表中显示竖线（|）字符。\n围栏代码块 # Markdown基本语法允许通过将行缩进四个空格或一个制表符来创建代码块。如果发现不方便，请尝试使用受保护的代码块。根据Markdown处理器或编辑器的不同，将在代码块之前和之后的行上使用三个反引号（(`````）或三个波浪号（~~~）。\n{ \u0026ldquo;firstName\u0026rdquo;: \u0026ldquo;John\u0026rdquo;, \u0026ldquo;lastName\u0026rdquo;: \u0026ldquo;Smith\u0026rdquo;, \u0026ldquo;age\u0026rdquo;: 25 }\n呈现的输出如下所示：\n{ \u0026#34;firstName\u0026#34;: \u0026#34;John\u0026#34;, \u0026#34;lastName\u0026#34;: \u0026#34;Smith\u0026#34;, \u0026#34;age\u0026#34;: 25 } 语法高亮 # 许多Markdown处理器都支持受围栏代码块的语法突出显示。使用此功能，可以为编写代码的任何语言添加颜色突出显示。要添加语法突出显示，请在受防护的代码块之前的反引号旁边\n```json { \u0026#34;firstName\u0026#34;: \u0026#34;John\u0026#34;, \u0026#34;lastName\u0026#34;: \u0026#34;Smith\u0026#34;, \u0026#34;age\u0026#34;: 25 } 输出效果： ```json { \u0026#34;firstName\u0026#34;: \u0026#34;John\u0026#34;, \u0026#34;lastName\u0026#34;: \u0026#34;Smith\u0026#34;, \u0026#34;age\u0026#34;: 25 } 脚注 # 脚注使可以添加注释和参考，而不会使文档正文混乱。当创建脚注时，带有脚注的上标数字会出现在添加脚注参考的位置。读者可以单击链接以跳至页面底部的脚注内容。\n要创建脚注参考，请在方括号（[^1]）内添加插入符号和标识符。标识符可以是数字或单词，但不能包含空格或制表符。标识符仅将脚注参考与脚注本身相关联-在输出中，脚注按顺序编号。\n在括号内使用另一个插入符号和数字添加脚注，并用冒号和文本（[^1]: My footnote.）。不必在文档末尾添加脚注。可以将它们放在除列表，块引号和表之类的其他元素之外的任何位置。\nHere\u0026#39;s a simple footnote,[^1] and here\u0026#39;s a longer one.[^bignote] [^1]: This is the first footnote. [^bignote]: Here\u0026#39;s one with multiple paragraphs and code. Indent paragraphs to include them in the footnote. `{ my code }` Add as many paragraphs as you like. 呈现的输出如下所示：\nHere\u0026rsquo;s a simple footnote,1 and here\u0026rsquo;s a longer one.2\nIndent paragraphs to include them in the footnote.\n{ my code }\nAdd as many paragraphs as you like.\n标题编号 # 许多Markdown处理器支持标题的自定义ID - 一些Markdown处理器会自动添加它们。添加自定义ID允许直接链接到标题并使用CSS对其进行修改。要添加自定义标题ID，请在与标题相同的行上用大括号括起该自定义ID。\n### My Great Heading {#custom-id} HTML看起来像这样：\n\u0026lt;h3 id=\u0026#34;custom-id\u0026#34;\u0026gt;My Great Heading\u0026lt;/h3\u0026gt; 链接到标题ID # 通过创建带有数字符号（#）和自定义标题ID的[标准链接]((/basic-syntax/links.html)，可以链接到文件中具有自定义ID的标题。\nMarkdown HTML 预览效果 [Heading IDs](#heading-ids) \u0026lt;a href=\u0026quot;#heading-ids\u0026quot;\u0026gt;Heading IDs\u0026lt;/a\u0026gt; Heading IDs 定义列表 # 一些Markdown处理器允许创建术语及其对应定义的定义列表。要创建定义列表，请在第一行上键入术语。在下一行，键入一个冒号，后跟一个空格和定义。\nFirst Term : This is the definition of the first term. Second Term : This is one definition of the second term. : This is another definition of the second term. HTML看起来像这样：\n\u0026lt;dl\u0026gt; \u0026lt;dt\u0026gt;First Term\u0026lt;/dt\u0026gt; \u0026lt;dd\u0026gt;This is the definition of the first term.\u0026lt;/dd\u0026gt; \u0026lt;dt\u0026gt;Second Term\u0026lt;/dt\u0026gt; \u0026lt;dd\u0026gt;This is one definition of the second term. \u0026lt;/dd\u0026gt; \u0026lt;dd\u0026gt;This is another definition of the second term.\u0026lt;/dd\u0026gt; \u0026lt;/dl\u0026gt; 呈现的输出如下所示：\nFirst Term This is the definition of the first term. Second Term This is one definition of the second term. This is another definition of the second term. 删除线 # 可以通过在单词中心放置一条水平线来删除单词。结果看起来像这样。此功能使可以指示某些单词是一个错误，要从文档中删除。若要删除单词，请在单词前后使用两个波浪号~~。\n~~世界是平坦的。~~ 我们现在知道世界是圆的。 呈现的输出如下所示：\n~~世界是平坦的。~~我们现在知道世界是圆的。\n任务列表语法 # 任务列表使可以创建带有复选框的项目列表。在支持任务列表的Markdown应用程序中，复选框将显示在内容旁边。要创建任务列表，请在任务列表项之前添加破折号-和方括号[ ]，并在[ ]前面加上空格。要选择一个复选框，请在方括号[x]之间添加 x 。\n- [x] Write the press release - [ ] Update the website - [ ] Contact the media 呈现的输出如下所示：\nWrite the press release Update the website Contact the media 使用 Emoji 表情 # 有两种方法可以将表情符号添加到Markdown文件中：将表情符号复制并粘贴到Markdown格式的文本中，或者键入emoji shortcodes。\n复制和粘贴表情符号 # 在大多数情况下，可以简单地从Emojipedia 等来源复制表情符号并将其粘贴到文档中。许多Markdown应用程序会自动以Markdown格式的文本显示表情符号。从Markdown应用程序导出的HTML和PDF文件应显示表情符号。\nTip: 如果使用的是静态网站生成器，请确保将HTML页面编码为UTF-8。.\n使用表情符号简码 # 一些Markdown应用程序允许通过键入表情符号短代码来插入表情符号。这些以冒号开头和结尾，并包含表情符号的名称。\n去露营了！ :tent: 很快回来。 真好笑！ :joy: 呈现的输出如下所示：\n去露营了！ \u0026#x26fa; 很快回来。\n真好笑！ \u0026#x1f602;\nNote: 注意：可以使用此表情符号简码列表，但请记住，表情符号简码因应用程序而异。\n自动网址链接 # 许多Markdown处理器会自动将URL转换为链接。这意味着如果输入http://www.example.com，即使未使用方括号，的Markdown处理器也会自动将其转换为链接。\n禁用自动URL链接 # 如果不希望自动链接URL，则可以通过将URL表示为带反引号的代码来删除该链接。\n`http://www.example.com` 呈现的输出如下所示：\nhttp://www.example.com\n展开折叠 # 在MarkDown中使用展开折叠功能依托于 HTML 5 中的 details 标签\n定义和用法 # \u0026lt;details\u0026gt; 标签用于描述文档或文档某个部分的细节。\n标签可以为 details 定义标题。标题是可见的，用户点击标题时，会显示出 details。 summary 子标签，在此写入折叠的内容。\n演示 # \u0026lt;details\u0026gt;\u0026lt;summary\u0026gt;展开/折叠\u0026lt;/summary\u0026gt; 被折叠的内容 \u0026lt;/details\u0026gt; 展开/折叠 被折叠的内容 This is the first footnote.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nHere\u0026rsquo;s one with multiple paragraphs and code.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2022-06-27","externalUrl":null,"permalink":"/blogs/sundry/markdown-grammar/","section":"Blog","summary":"","title":"Markdown语法","type":"blogs"},{"content":"","date":"2022-06-24","externalUrl":null,"permalink":"/series/leidenalg/","section":"Series","summary":"","title":"Leidenalg","type":"series"},{"content":"","date":"2022-06-24","externalUrl":null,"permalink":"/tags/leidenalg/","section":"Tags","summary":"","title":"Leidenalg","type":"tags"},{"content":" 一、引言 # 在复杂网络分析中，社区检测是关键任务之一。leidenalg 库提供了多路复用（Multiplex）社区检测功能，适用于处理多个图层或切片的网络。本文将介绍如何使用 leidenalg 进行多路复用社区检测。\n二、多路复用社区检测基础概念 # 层（Layer）与切片（Slice） # 层（Layer） ：多个图定义在相同顶点集上，但边集不同。每个节点属于同一社区。 切片（Slice） ：图可以有不同的顶点集，节点在不同切片中可属于不同社区。需将切片转换为层才能使用相同算法。 三、层多路复用（Layer Multiplex） # 示例：电话与邮件通信图 # 假设 G_telephone 和 G_email 分别表示朋友间电话和邮件通信图，顶点集相同。可使用 find_partition_multiplex() 函数进行社区检测：\n\u0026gt;\u0026gt;\u0026gt; membership, improv = la.find_partition_multiplex( ... [G_telephone, G_email], ... la.ModularityVertexPartition) 层权重与不同分区类型 # 层权重（layer_weight） ：可为不同层指定权重，调整层在整体质量中的重要性。如邮件层权重设为 0.5： \u0026gt;\u0026gt;\u0026gt; diff = optimiser.optimise_partition_multiplex( ... [part_telephone, part_email], ... layer_weights=[1,0.5]) 不同分区类型 ：可为不同层使用不同分区类型，如为电话图使用 CPMVertexPartition，邮件图使用更高分辨率参数： \u0026gt;\u0026gt;\u0026gt; part_telephone = la.CPMVertexPartition( ... G_telephone, resolution_parameter=0.01) \u0026gt;\u0026gt;\u0026gt; part_email = la.CPMVertexPartition( ... G_email, resolution_parameter=0.3) \u0026gt;\u0026gt;\u0026gt; diff = optimiser.optimise_partition_multiplex( ... [part_telephone, part_email]) 负链接处理 # 当图包含负链接（如冲突或敌意关系）时，可将图分为正负两层，正层权重为正，负层权重为负。例如：\n\u0026gt;\u0026gt;\u0026gt; G_pos = G.subgraph_edges(G.es.select(weight_gt = 0), delete_vertices=False) \u0026gt;\u0026gt;\u0026gt; G_neg = G.subgraph_edges(G.es.select(weight_lt = 0), delete_vertices=False) \u0026gt;\u0026gt;\u0026gt; G_neg.es[\u0026#39;weight\u0026#39;] = [-w for w in G_neg.es[\u0026#39;weight\u0026#39;]] \u0026gt;\u0026gt;\u0026gt; part_pos = la.ModularityVertexPartition(G_pos, weights=\u0026#39;weight\u0026#39;) \u0026gt;\u0026gt;\u0026gt; part_neg = la.ModularityVertexPartition(G_neg, weights=\u0026#39;weight\u0026#39;) \u0026gt;\u0026gt;\u0026gt; diff = optimiser.optimise_partition_multiplex( ... [part_pos, part_neg], ... layer_weights=[1,-1]) 四、二分网络社区检测 # 二分网络中节点分为两类，如产品和顾客，仅允许两类间链接。可通过创建三个层来检测社区：\n层 1 ：所有节点大小为 1，包含相关链接。 层 2 ：仅一类节点大小为 1，无链接。 层 3 ：另一类节点大小为 1，无链接。 将层 2 和层 3 的层权重设为 -1，层 1 权重设为 1，可实现二分网络社区检测。例如：\n\u0026gt;\u0026gt;\u0026gt; p_01, p_0, p_1 = la.CPMVertexPartition.Bipartite(G, ... resolution_parameter_01=0.1) \u0026gt;\u0026gt;\u0026gt; diff = optimiser.optimise_partition_multiplex([p_01, p_0, p_1], ... layer_weights=[1, -1, -1]) CPM 方法的公式为：\n$$ Q=\\sum_{i j}[A_{i j}-(\\gamma_{0}\\delta(s_{i},0)+\\gamma_{1}\\delta(s_{i},1))\\delta(s_{i},s_{j})-\\gamma_{01}(1-\\delta(s_{i},s_{j}))]\\delta(\\sigma_{i},\\sigma_{j}) $$其中，$\\gamma_{0}$、$\\gamma_{1}$ 和 $\\gamma_{01}$ 分别表示类内连接、类内连接和类间连接的分辨率参数。通过将三个层的权重和分辨率参数设置为上述方式，可以实现对二分网络的社区检测。\n五、切片到层的转换 # 背景与方法 # 多路复用层有两局限：各图需有相同顶点集；节点只能属于一个社区。为突破此限制，引入切片概念。切片是不同图，可有不同顶点集，节点在不同切片中可属不同社区。通过构建耦合图，将切片转换为层。\n示例：三个时间切片 # 假设三个时间切片 G_1、G_2、G_3，耦合图为 1 -- 2 -- 3。转换步骤如下：\n创建耦合图并设置权重： \u0026gt;\u0026gt;\u0026gt; G_coupling = ig.Graph.Formula(\u0026#39;1 -- 2 -- 3\u0026#39;) \u0026gt;\u0026gt;\u0026gt; G_coupling.es[\u0026#39;weight\u0026#39;] = 0.1 # 切片间耦合强度 \u0026gt;\u0026gt;\u0026gt; G_coupling.vs[\u0026#39;slice\u0026#39;] = [G_1, G_2, G_3] 转换为层： \u0026gt;\u0026gt;\u0026gt; layers, interslice_layer, G_full = la.slices_to_layers(G_coupling) 创建各层分区，并优化： \u0026gt;\u0026gt;\u0026gt; partitions = [la.CPMVertexPartition(H, node_sizes=\u0026#39;node_size\u0026#39;, ... weights=\u0026#39;weight\u0026#39;, resolution_parameter=gamma) ... for H in layers] \u0026gt;\u0026gt;\u0026gt; interslice_partition = la.CPMVertexPartition(interslice_layer, resolution_parameter=0, ... node_sizes=\u0026#39;node_size\u0026#39;, weights=\u0026#39;weight\u0026#39;) \u0026gt;\u0026gt;\u0026gt; diff = optimiser.optimise_partition_multiplex(partitions + [interslice_partition]) 注意 ：通常将切片间层设为 CPMVertexPartition，分辨率参数为 0，节点大小设为 0。\n六、时间社区检测 # 对于时间切片的社区检测，可以使用 find_partition_temporal() 函数。例如：\n\u0026gt;\u0026gt;\u0026gt; membership, improvement = la.find_partition_temporal( ... [G_1, G_2, G_3], ... la.CPMVertexPartition, ... interslice_weight=0.1, ... resolution_parameter=gamma) 或者使用 time_slices_to_layers() 函数获取层和分区：\n\u0026gt;\u0026gt;\u0026gt; layers, interslice_layer, G_full = la.time_slices_to_layers([G_1, G_2, G_3], interslice_weight=0.1) \u0026gt;\u0026gt;\u0026gt; partitions = [la.CPMVertexPartition(H, node_sizes=\u0026#39;node_size\u0026#39;, weights=\u0026#39;weight\u0026#39;, resolution_parameter=gamma) for H in layers] \u0026gt;\u0026gt;\u0026gt; interslice_partition = la.CPMVertexPartition(interslice_layer, resolution_parameter=0, node_sizes=\u0026#39;node_size\u0026#39;, weights=\u0026#39;weight\u0026#39;) \u0026gt;\u0026gt;\u0026gt; diff = optimiser.optimise_partition_multiplex(partitions + [interslice_partition]) 七、总结 # 本文介绍了 leidenalg 库的多路复用社区检测功能，包括层与切片的概念、层多路复用的实现、负链接与二分网络的处理，以及切片到层的转换方法和时间社区检测。通过这些方法，可更灵活地分析复杂网络的社区结构。\n八、参考文献 # Mucha, P. J., Richardson, T., Macon, K., Porter, M. A., \u0026amp; Onnela, J.-P. (2010). Community structure in time-dependent, multiscale, and multiplex networks. Science, 328(5980), 876–8. 10.1126/science.1184819 Traag, V. A., \u0026amp; Bruggeman, J. (2009). Community detection in networks with positive and negative links. Physical Review E, 80(3), 036115. 10.1103/PhysRevE.80.036115 Barber, M. J. (2007). Modularity and community detection in bipartite networks. Physical Review E, 76(6), 066102. 10.1103/PhysRevE.76.066102 ","date":"2022-06-24","externalUrl":null,"permalink":"/blogs/kg/leidenalg/02/","section":"Blog","summary":"","title":"leidenalg 包教程（2）","type":"blogs"},{"content":"","date":"2022-06-24","externalUrl":null,"permalink":"/tags/%E6%95%99%E7%A8%8B/","section":"Tags","summary":"","title":"教程","type":"tags"},{"content":" 安装 # 简单来说，可以使用pip install leidenalg直接安装。\n也可以使用源码进行安装， 安装这个包需要C核心库igraph和python包python-igraph，然后可以通过python setup.py test安装\n不建议Windows，使用源代码进行安装。\n介绍 # leidenalg软件包建立在igraph基础之上，有助于网络的社区发现。leiden是大型网络中社区发现方法的通用算法。如果只想对给定的图$G$进行模块化社区发现可以：\nimport leidenalg as la import igraph as ig partition = la.find_partition(G, la.ModularityVertexPartition) # 模块化顶点分区 在igraph包中内置了Louvain算法community_multilevel()，可以简单的将无向无权重图进行分区。在leidenalg包中，有一些其他的功能，并且使用的leidena算法比louvain算法性能更好。\n例子：Zachary 空手道俱乐部网络\nimport igraph as ig import leidenalg as la # 加载 Zachary 空手道俱乐部图 G = ig.Graph.Famous(\u0026#39;Zachary\u0026#39;) # 检测具有模块化的社区 partition = la.find_partition(G, la.ModularityVertexPartition) # 绘制结果 ig.plot(partition) 在这种情况下，算法实际上会找到最佳分区，可以使用igraph包中的community_optimal_modularity()来检查,会得到不同的结果。\n但是，这样做会得到很多的子社区。下面介绍使用CPMVertexPartition如何将它一分为二：\npartition = la.find_partition(G, la.CPMVertexPartition, resolution_parameter = 0.05); ig.plot(partition) 传递给find_partition的额外参数**kwargs都会传递给给定partition_type。可以传递resolution_parameter、weights 、node_sizes等参数。\n除此之外，leidenalg还支持有向图和加权图，可以在一定程度上自定义优化程序，而且还支持处理多路复用图（多元异构图）。\n高级技巧 # 1. 优化器 # leidenalg包提供对函数的简单访问find_partition()，但是底层使用Optimiser类完成。显示构建一个Optimiser对象：\noptimiser = la.Optimiser() 函数find_partition()不在执行任何其他操作，仅在提供的分区上调用optimise_partition。\ndiff = optimiser.optimise_partition(partition) optimise_partition()简单地尝试优化分区，可以反复调用以不断优化\nG = ig.Graph.Erdos_Renyi(100, p=5./100) partition = la.ModularityVertexPartition(G) diff = 1 while diff \u0026gt; 0: diff = optimiser.optimise_partition(partition) 调用可能没有改进当前分区，下一次可能会改进。\n进行重复迭代可以调用内置的函数，传入参数：\ndiff = optimiser.optimise_partition(partition, n_iterations=10) 如果n_iterations\u0026lt;0，优化器会持续下去优化，直到遇到没有改进的分区的迭代。\noptimise_partition()的执行过程又是建立在move_nodes()和merge_nodes()算法之上，可以自行调用：\ndiff = optimiser.move_nodes(partition) diff = optimiser.merge_nodes(partition) 使用以上基本算法实现Louvain算法聚合分区，并在聚合分区上重复使用move_nodes():\npartition = la.ModularityVertexPartition(G) while optimiser.move_nodes(partition) \u0026gt; 0: partition = partition.aggregate_partition() 尽管这样可以找到最终的聚合分区，但是在各个节点级别上的实际分区仍然不清楚。为了做到这一点，我们需要基于聚合分区更新成员关系，为此我们使用函数 from_coarse_partition()\npartition = la.ModularityVertexPartition(G) partition_agg = partition.aggregate_partition() while optimiser.move_nodes(partition_agg) \u0026gt; 0: partition.from_coarse_partition(partition_agg) partition_agg = partition_agg.aggregate_partition() 现在partition_agg包含聚合分区，并且partition包含原始图G的实际分区。partition_agg.quality() == partition.quality() 两个基本等价。\n也可以使用merge_nodes()代替move_nodes()，函数的选择取决于特定的替换社区。\n在Leiden算法中，可以先细化分区，再聚合。算法可以使用函数数move_nodes_constrained()和 merge_nodes_constrained()来完成：\n# Set initial partition 设置初始化分区 partition = la.ModularityVertexPartition(G) refined_partition = la.ModularityVertexPartition(G) partition_agg = refined_partition.aggregate_partition() while optimiser.move_nodes(partition_agg): # Get individual membership for partition 得到个体分区的资格 partition.from_coarse_partition(partition_agg, refined_partition.membership) # Refine partition 细化分区 refined_partition = la.ModularityVertexPartition(G) optimiser.merge_nodes_constrained(refined_partition, partition) # Define aggregate partition on refined partition 在细化分区上定义聚合分区 partition_agg = refined_partition.aggregate_partition() # But use membership of actual partition使用实际分区的资格 aggregate_membership = [None] * len(refined_partition) for i in range(G.vcount()): aggregate_membership[refined_partition.membership[i]] = partition.membership[i] partition_agg.set_membership(aggregate_membership) 这些函数又依赖两个关键的基本函数： diff_move()和move_node()。前者计算移动节点时的差异，后者实际移动节点，并更新所有必要的内部管理。函数move_nodes()然后执行：\nfor v in G.vs: best_comm = max(range(len(partition)), key=lambda c: partition.diff_move(v.index, c)) partition.move_node(v.index, best_comm) 实际的实现更为复杂，总体是这个思路。\n2. Resolution profile 分辨率配置文件 # 该参数的作用是一种阈值：社区的密度至少要达到$\\gamma$，而社区之间的密度要低于$\\gamma$，较高的分辨率会导致更多的社区，而较低的分辨率会导致更少的社区\n一些如CPMVertexPartition或RBConfigurationVertexPartition方法接受的分辨率（解析）参数。虽然某些方法具有某种“天生”的分辨率参数，但实际上却是十分武断的。然而，这里使用的方法（以线性方式依赖于分辨率参数）允许对分辨率参数的范围内进行有效扫描。这些方法可以表述为$Q = E - \\gamma N$。\n在CPMVertexPartition中 $E= \\sum_c m_c$ 表示社区 $c$ 中的边的数量，$N=\\sum_c {n_c \\choose 2}$ 表示期望的社区 $c$ 内部的边的总和。如果存在最佳分区 $\\gamma_1$ 和 $\\gamma_2$，那么 $\\gamma_1 ≤ \\gamma ≤ \\gamma_2$ 都是最佳分区。\n可以使用Optimiser对象构建这样的分辨率参数：\nG = ig.Graph.Famous(\u0026#39;Zachary\u0026#39;) optimiser = la.Optimiser() profile = optimiser.resolution_profile(G, la.CPMVertexPartition, resolution_range=(0,1)) 绘制分辨率参数与子社区总边数的关系图，结果如下：\n现在，配置文件包含一个指定类型的分区列表（例子中为CPMVertexPartition），用于解析参数发生变化。特别是，profile[i]应该更好直到profile[i+1]，或者有另外的说明，对于profile[i].resolution_parameter和 profile[i+1].resolution_parameter之间的分区的分辨率参数i更好。当然，会有一些变化，因为optimise_partition()会找到不同质量的分区，不同的运行，变化点也可能会有所不同。\n此函数会重复调用optimise_partition() ，因此可能需要大量时间。特别是对于改变点附近的分辨率参数，可能有许多可能的分区，因此需要大量运行。\n3. 固定（确定）节点 # 由于某些原因，只更新分区（子社区）的一部分可能是有益的。例如，在一些数据上已经运行了leiden算法，并对结果进行了一些分析，随后又收集了一些新的数据，特别是新的节点，那么保持以前的社区分配不变，而只更新新节点的社区分配，这样做可能是有益的。\n可以使用is_membership_fixed参数传入find_partition()来完成分区。\n例如，假设我们之前检测到图G的分区，它被扩展到图G2。假设之前退出的节点是相同的，我们可以通过以下方式创建一个新的分区:\nnew_membership = list(range(G2.vcount())) new_membership[:G.vcount()] = partition.membership 然后，我们只能按照如下方式更新新节点的社区分配\nnew_partition = la.CPMVertexPartition(G2, new_membership, resolution_parameter=partition.resolution_parameter) is_membership_fixed = [i \u0026lt; G.vcount() for i in range(G2.vcount())] diff = optimiser.optimise_partition(partition, is_membership_fixed=is_membership_fixed) 在这个例子中，使用了CPMVertexPartition，也可以使用其他VertexPartition。\n4. 最大社区规模 # 在某种情况下，可能需要限制最大子社区的规模，可以通过max_comm_size参数来指定最大子社区规模。此参数可以在优化期间进行设定产生约束，也可以直接传递到find_partition()。\npartition = la.find_partition(G, la.ModularityVertexPartition, max_comm_size=10) # 限定最大子社区规模为10 参考 # leidenalg Traag, V. A., Krings, G., \u0026amp; Van Dooren, P. (2013). Significant scales in community structure. Scientific Reports, 3, 2930. 10.1038/srep029302 Zanini, F., Berghuis, B. A., Jones, R. C., Robilant, B. N. di, Nong, R. Y., Norton, J., Clarke, Michael F., Quake, S. R. (2019). northstar: leveraging cell atlases to identify healthy and neoplastic cells in transcriptomes from human tumors. BioRxiv, 820928. 10.1101/820928 ","date":"2022-06-21","externalUrl":null,"permalink":"/blogs/kg/leidenalg/01/","section":"Blog","summary":"","title":"leidenalg 包教程（1）","type":"blogs"},{"content":"","date":"2022-06-13","externalUrl":null,"permalink":"/tags/%E6%9C%AC%E4%BD%93/","section":"Tags","summary":"","title":"本体","type":"tags"},{"content":"在这篇博客中，将引导您了解本体和知识知识图谱，讲述它们之间的区别以及它们如何组织大量数据和信息。\n英文原文链接\n什么是本体？ # 本体是语义数据模型，用于定义domain中事物的类型以及可用于描述它们的属性。本体是广义的数据模型，这意味着它们仅对具有某些属性的事物的一般类型进行建模，而并不包含有关我们domain中具体个体的信息。例如，本体论不能描述您的狗，斑点和它的所有个体特征，主要描述狗的一般概念，尝试描述大多数狗可能具有的特征。这样做可以使我们在将来用本体来描述其他狗。\n本体有三个主要组成部分，通常描述如下:\n类： 存在于数据中的不同类型的。 关系：连接两个类的属性。 属性：描述单个类的属性。 例如，假设我们有以下关于书籍、作者和出版商的信息：\n首先，我们要标识类(数据中事物的唯一类型)。这个示例数据似乎捕获了关于书籍的信息，因此它是类的一个很好的候选项。具体来说，示例数据捕获了关于书籍的某些类型的内容，比如作家和出版商。再深入一点，我们可以看到我们的数据还捕获了关于出版商和作者的信息，比如他们的位置。这给我们留下了这个例子中的四个类：\n书籍 作者 出版商 位置 下一步，我们需要标识关系和属性(为了简单，我们可以将关系和实体属性都视为属性)。使用我们在之前定义的类，我们可以查看数据并开始列出我们看到的每个类的所有属性。例如，在书籍类中，一些属性可能是:\n书籍有作者 书籍有出版商 书集是在一个日期出版的 书籍之后有续集(其他书) 其中一些属性是连接两个类的关系。例如，关系属性“书籍有作者”是一个连接书籍类和作者类的关系。其他属性，像“书籍发布的日期”是实体属性，只描述一个类，而不是将两个类连接在一起。\n需要注意的是，这些属性可能适用于任何给定的书籍，但它们不一定适用于每一本书。例如，很多书都没有续集。这在我们的本体中很好，因为我们只是想确保我们捕获了可能适用于许多(但不一定是所有)书籍的属性。\n虽然上面的属性列表很容易阅读，但是重写这些属性以更清楚地定义我们的类和属性会有所帮助。例如，“书籍有作者”可以写成:\ngraph LR A(书)--\u003eB(有作者)--\u003eC(作者) 尽管你可以包括更多的属性，这取决于你的用例，对于这个博客，我已经定义了以下属性:\ngraph LR A(书)--\u003eB(有作者)--\u003eC(作者) A1(书)--\u003eD(有出版商)--\u003eE(出版商) A2(书)--\u003eF(出版于)--\u003eG(出版日期) A3(书)--\u003eH(后面是)--\u003eA4(书) E1(出版商)--\u003eI(位于)--\u003eJ(位置) J1(位置)--\u003eI1(位于)--\u003eJ2(位置) 记住，我们的本体是一个通用的数据模型，这意味着我们不想在本体中包含关于特定书籍的信息。相反，我们希望创建一个可重用的框架，将来我们可以用它来描述其他书籍。\n当我们结合类和关系时，我们能够以图的形式查看本体:\n什么是知识图谱？ # 使用本体作为一个框架，我们可以添加关于个别书籍、作者、出版商和位置的真实数据来创建一个知识图谱。利用上面表中的信息和本体，我们可以创建每个本体关系的特定实例。比如，如果我们的本体中有这样的关系“书籍→有作者→作者”，这个关系的单个实例如下：\n如果我们把我们拥有的关于《杀死一只知更鸟》这本书的所有信息加进去，我们可以看到知识图谱的开端：\n如果我们对所有的数据都这么做，我们最终会得到一个使用本体对数据进行编码的图。通过使用知识图谱，我们可以将数据看作一个关系网络，而不是作为单独的表格在我们无法理解的数据点间绘制新的连接。具体来说，使用SPARQL，我们可以查询数据和使用推理功能（让知识图谱建立之前没有定义的连接）。\n那么本体和知识图谱有什么不同呢？\n正如你在上面例子中所看的，当你将本体（我们的数据模型）应用到一组单独的数据点（书籍、作者和出版商数据）时，那么就是创建了一个知识图谱。换句话说：\n本体+数据=知识图谱\n","date":"2022-06-13","externalUrl":null,"permalink":"/blogs/kg/ontology-and-knowledge-graph/","section":"Blog","summary":"","title":"本体和知识图谱","type":"blogs"},{"content":" 函数 # 函数简介 # 函数以def关键词开头，后接函数名和圆括号()。\n函数执行的代码以冒号起始，并且缩进。\nreturn [表达式] 结束函数，选择性地返回一个值给调用方。不带表达式的return相当于返回None。\ndef functionname (parameters): \u0026#34;函数_文档字符串\u0026#34; function_suite return expression python的函数具有非常灵活多样的参数，既可以实现简单的调用又可以传入复杂的参数。python允许函数调用时参数顺序与声明时不一致，解释器能够用参数名匹配参数值。\n位置参数 (positional argument)\n默认参数 (default argument)\n可变参数 (variable argument)：args，可以是零个或任意个，自动组装成元组，并且会存放所有未命名的变量参数。\n关键字参数 (keyword argument)：*kw，可以是零个或任意个，自动组装成字典。\ndef printinfo(arg1, *args, **kwargs): print(arg1) print(args) print(kwargs) printinfo(70, 60, 50) # 70 # (60, 50) # {} printinfo(70, 60, 50, a=1, b=2) # 70 # (60, 50) # {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2} 命名关键字参数 (name keyword argument)：, nkw\n参数组合：上述参数中「位置参数、默认参数、可变参数和关键字参数」和「位置参数、默认参数、命名关键字参数和关键字参数」可以四个一起使用，但是顺序不能改变，不然解释器会出现解析错误。\n尤其注意可变参数和命名关键参数不可同时使用\ndef printinfo(arg1, *, nkw, nkw1, **kwargs): print(arg1) print(nkw) print(nkw1) print(kwargs) printinfo(70, nkw=10, nkw1=11, a=1, b=2) # 70 # 10 # {\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2} printinfo(70, 10, 11, a=1, b=2) # TypeError: printinfo() takes 1 positional argument but 3 were given # 没有写参数名，10和11被当作「位置参数」，因此报错 变量作用域 # Python 中，程序的变量并不是在哪个位置都可以访问的，访问权限决定于这个变量是在哪里赋值的。\n定义在函数内部的变量拥有局部作用域，该变量称为局部变量。\n定义在函数外部的变量拥有全局作用域，该变量称为全局变量。\n局部变量只能在其被声明的函数内部访问，而全局变量可以在整个程序范围内访问。\n当内部作用域想修改外部作用域的变量时，就要用到global和nonlocal关键字。\n内嵌函数\ndef outer(): print(\u0026#39;outer函数在这被调用\u0026#39;) def inner(): print(\u0026#39;inner函数在这被调用\u0026#39;) inner() # 该函数只能在outer函数内部被调用 outer() # outer函数在这被调用 # inner函数在这被调用 函数对象 # 函数对象是指函数可以被当作「数据」来处理\n函数可以被引用 函数可以作为容器类型的元素 函数可以作为参数传入另外一个函数 函数的返回值可以是一个函数 参考文章： python API\n闭包 # 是函数式编程的一个重要的语法结构，是一种特殊的内嵌函数。 如果在一个内部函数里对外层非全局作用域的变量进行引用，那么内部函数就被认为是闭包。 通过闭包可以访问外层非全局作用域的变量，这个作用域称为 闭包作用域。 def make_counter(init): counter = [init] def inc(): counter[0] += 1 def dec(): counter[0] -= 1 def get(): return counter[0] def reset(): counter[0] = init def nonloc(): nonlocal counter # 使用关键字nonlocal可以修改闭包作用域中的变量 counter = [9, 1] print(counter) return inc, dec, get, reset, nonloc inc, dec, get, reset, nonloc = make_counter(0) inc() inc() inc() # 三次调用inc()函数， 不用重复赋值 print(get()) # 3 dec() print(get()) # 2 reset() print(get()) # 0 inc() inc() print(get()) # 2 dec() print(get()) # 1 nonloc() # [9,1] print(get()) # 9 参考文章：函数对象与闭包\nLambda表达式 # 在 Python 里有两类函数：\n第一类：用 def 关键词定义的正规函数 第二类：用 lambda 关键词定义的匿名函数 lambda 表达式（有时称为 lambda 构型）被用于创建匿名函数。 表达式 lambda parameters: expression 会产生一个函数对象 ，没有函数名。\nlambda - 定义匿名函数的关键词。 parameters - 函数参数，它们可以是位置参数、默认参数、关键字参数，和正规函数里的参数类型一样。 :冒号，在函数参数和表达式中间要加个冒号。 expression - 只是一个表达式，输入函数参数，输出一些值。 注意：\nexpression 中没有 return 语句，因为 lambda 不需要它来返回，表达式本身结果就是返回值。 匿名函数拥有自己的命名空间，且不能访问自己参数列表之外或全局命名空间里的参数。 匿名函数的应用\n匿名函数 常常应用于函数式编程的高阶函数 (high-order function)中，主要有两种形式：\n参数是函数 (filter, map) 返回值是函数 (closure) # 在 filter和map函数中的应用： # filter(function, iterable) 过滤序列，过滤掉不符合条件的元素，返回一个迭代器对象，如果要转换为列表，可以使用 list() 来转换。 odd = lambda x: x % 2 == 1 templist = filter(odd, [1, 2, 3, 4, 5, 6, 7, 8, 9]) print(list(templist)) # [1, 3, 5, 7, 9] # map(function, *iterables) 根据提供的函数对指定序列做映射。 m1 = map(lambda x: x ** 2, [1, 2, 3, 4, 5]) print(list(m1)) # [1, 4, 9, 16, 25] m2 = map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10]) print(list(m2)) # [3, 7, 11, 15, 19] ","date":"2022-05-01","externalUrl":null,"permalink":"/blogs/python/base/python-function-lambda/","section":"Blog","summary":"","title":"Python函数与Lambda表达式","type":"blogs"},{"content":" 列表 # 列表是有序集合，无定长，能存储任意数量和类型的数据，语法为：[元素1, 元素2, ..., 元素n]\n创建列表\n使用range()创建\n使用推导式创建\n==由于列表中的元素可以任何对象，因此列表中保存的是对象的指针，使用推导式创建列表时，只是创建看i个指向列表的引用。==\n创建混合列表\n列表的元素可以是任何对象\n更新列表\nlist.append(obj) 在列表末尾添加新的对象，只接受一个参数，参数可以是任何数据类型，被追加的元素在 list 中保持着原结构类型。\nx = [\u0026#39;Monday\u0026#39;, \u0026#39;Tuesday\u0026#39;, \u0026#39;Wednesday\u0026#39;, \u0026#39;Thursday\u0026#39;, \u0026#39;Friday\u0026#39;] x.append([\u0026#39;Thursday\u0026#39;]) print(x) # [\u0026#39;Monday\u0026#39;, \u0026#39;Tuesday\u0026#39;, \u0026#39;Wednesday\u0026#39;, \u0026#39;Thursday\u0026#39;, \u0026#39;Friday\u0026#39;, [\u0026#39;Thursday\u0026#39;]] print(len(x)) # 6 list.extend(seq) 在列表末尾一次性追加另一个序列中的多个值（用新列表扩展原来的列表\nx = [\u0026#39;Monday\u0026#39;, \u0026#39;Tuesday\u0026#39;, \u0026#39;Wednesday\u0026#39;, \u0026#39;Thursday\u0026#39;, \u0026#39;Friday\u0026#39;] x.append([\u0026#39;Thursday\u0026#39;, \u0026#39;Sunday\u0026#39;]) print(x) # [\u0026#39;Monday\u0026#39;, \u0026#39;Tuesday\u0026#39;, \u0026#39;Wednesday\u0026#39;, \u0026#39;Thursday\u0026#39;, \u0026#39;Friday\u0026#39;, [\u0026#39;Thursday\u0026#39;, \u0026#39;Sunday\u0026#39;]] print(len(x)) # 6 list.insert(index, obj) 在编号 index 位置插入 obj。\nlist.remove(obj) 移除列表中某个值的第一个匹配项\nlist.pop([index=-1]) 移除列表中的一个元素（默认最后一个元素），并且返回该元素的值\ndel [var1, var2 ……] 删除单个或多个对象。\n获取元素\n索引 切片，切片的通用写法是 start : stop : step 复制、浅拷贝与深拷贝\nlist1 = [123, 456, 789, 213] # 复制： 只是复制了新对象的引用，不会开辟新的内存空间。 list2 = list1 # 浅拷贝: 创建新对象，其内容是原对象的引用，但是对于多层嵌套的可变对象（内层对象）随着原序列的变化而变化，内层对象并不会改变。以下三种浅拷贝操作 # list3 = list1[:] # 切片 # list3 = list(list1)# 工厂函数 import copy list3 = copy.copy(list1)# copy()函数 # 深拷贝：和浅拷贝对应，深拷贝拷贝了对象的所有元素，包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象，不再与原来的对象有任何关联。 list4 = copy.deepcopy(list1) # 非可变对象 print(list1) # [123, 456, 789, 213] print(list2) # [123, 456, 789, 213] print(list3) # [123, 456, 789, 213] list1.sort() print(list2) # [123, 213, 456, 789] print(list3) # [123, 456, 789, 213] print(id(list1)) # 140442132250784 print(id(list2)) # 140442132250784 print(id(list3)) # 140442129839520 print(\u0026#39;####************####\u0026#39;) # 可变对象 listPlus1 = [[123, 456], [789, 213]] listPlus2 = listPlus1 listPlus3 = listPlus1[:] listPlus4 = copy.deepcopy(listPlus1) print(listPlus1) # [[123, 456], [789, 213]] print(listPlus2) # [[123, 456], [789, 213]] print(listPlus3) # [[123, 456], [789, 213]] print(listPlus4) # [[123, 456], [789, 213]] print(id(listPlus1)) # 140442130839584 print(id(listPlus2)) # 140442130839584 print(id(listPlus3)) # 140442132935696 print(id(listPlus4)) # 140442130944240 listPlus1[0][0] = 111 listPlus1.append([1, 2]) print(listPlus2) # [[111, 456], [789, 213], [1, 2]] print(listPlus3) # [[111, 456], [789, 213]] print(listPlus4) # [[123, 456], [789, 213]] 常用操作符\n等号操作符：==，列表中元素相同时为True 连接操作符 +，两个列表相加拼接，内存增加，创建一个新的列表 重复操作符 ，数乘列表复制拼接，内存增加，创建一个新的列表 成员关系操作符 in、not in 其他操作\nlist.count(obj) 统计某个元素在列表中出现的次数 list.index(x[, start[, end]]) 从列表中找出某个值第一个匹配项的索引位置 list.reverse() 反向列表中元素 list.sort(key=None, reverse=False) 对原列表进行排序。False默认升序。 元组 # 「元组」定义语法为：(元素1, 元素2, ..., 元素n)\n创建元组 Python 的元组与列表类似，不同之处在于tuple被创建后就不能对其进行修改，类似字符串。 元组使用小括号，列表使用方括号。 元组与列表类似，也用整数来对它进行索引 (indexing) 和切片 (slicing)。 元组中只包含一个元素时，需要在元素后面添加逗号，否则括号会被当作运算符使用。 更新元组 元组有不可更改的性质，因此不能直接给元组中的元素赋值，但是如果元组中元素是可更改的，那么就可以更改元组中元素的值。\nt1 = (1, 2, 3, [4, 5, 6]) print(t1) # (1, 2, 3, [4, 5, 6]) t1[3][0] = 9 print(t1) # (1, 2, 3, [9, 5, 6]) 常用操作符\n可参考列表\n内置方法\ncount：count(x) 是记录x在元组 t 中该元素出现几次 index：index(x) 是找到元素x在元组 t 的索引 解压元组\n解压（unpack）一维元组（有几个元组，定义几个变量）\nt = (1, 10.31, \u0026#39;python\u0026#39;) (a, b, c) = t print(a, b, c) # 1 10.31 python 解压二维元组（按照元组里的元组结构定义变量）\nt = (1, 10.31, (\u0026#39;OK\u0026#39;, \u0026#39;python\u0026#39;)) (a, b, (c, d)) = t print(a, b, c, d) # 1 10.31 OK python 如果你只想要元组其中几个元素，用通配符「 * 」，英文叫 wildcard，在计算机语言中代表一个或多个元素。例如把多个元素丢给了 rest 变量。如果你根本不在乎 rest 变量，那么就用通配符「 * 」加上下划线「 _ 」。\n字符串 # python中引号之间的字符集合，pyhton中单引号与双引号并无区别。\n常用转义字符\n\\\\\\\\ 反斜杠符号 \\\\' 单引号 \\\\\u0026quot; 双引号 \\\\n 换行 \\\\t 横向制表符(TAB) \\\\r 回车 切片和拼接\n类似于元组具有不可修改性 从 0 开始 (和 Java 一样) 切片通常写成 start:end 这种形式，包括「start 索引」对应的元素，不包括「end索引」对应的元素。 索引值可正可负，正索引从 0 开始，从左往右；负索引从 -1 开始，从右往左。使用负数索引时，会从最后一个元素开始计数。最后一个元素的位置编号是 -1。 常用内置方法\ncapitalize() 将字符串的第一个字符转换为大写。\nlower() 转换字符串中所有大写字符为小写。\nupper() 转换字符串中的小写字母为大写。\nswapcase() 将字符串中大写转换为小写，小写转换为大写。\ncount(str, beg= 0,end=len(string)) 返回str在 string 里面出现的次数，如果beg或者end指定则返回指定范围内str出现的次数。\nendswith(suffix, beg=0, end=len(string)) 检查字符串是否以指定子字符串 suffix 结束，如果是，返回 True，否则返回 False。如果 beg 和 end 指定值，则在指定范围内检查。\nstartswith(substr, beg=0,end=len(string)) 检查字符串是否以指定子字符串 substr 开头，如果是，返回 True，否则返回 False。如果 beg 和 end 指定值，则在指定范围内检查。\nfind(str, beg=0, end=len(string)) 检测 str 是否包含在字符串中，如果指定范围 beg 和 end，则检查是否包含在指定范围内，如果包含，返回开始的索引值，否则返回 -1。\nrfind(str, beg=0,end=len(string)) 类似于 find() 函数，不过是从右边开始查找。\nisnumeric() 如果字符串中只包含数字字符，则返回 True，否则返回 False。\nljust(width[, fillchar])返回一个原字符串左对齐，并使用fillchar（默认空格）填充至长度width的新字符串。\nrjust(width[, fillchar])返回一个原字符串右对齐，并使用fillchar（默认空格）填充至长度width的新字符串。\nlstrip([chars]) 截掉字符串左边的空格或指定字符。\nrstrip([chars]) 删除字符串末尾的空格或指定字符。\nstrip([chars]) 在字符串上执行lstrip()和rstrip()。\npartition(sub) 找到子字符串sub，把字符串分为一个三元组(pre_sub,sub,fol_sub)，如果字符串中不包含sub则返回('原字符串','','')。\nrpartition(sub)类似于partition()方法，不过是从右边开始查找。\nreplace(old, new [, max]) 把 将字符串中的old替换成new，如果max指定，则替换不超过max次。\nsplit(str=\u0026quot;\u0026quot;, num) 不带参数默认是以空格为分隔符切片字符串，如果num参数有设置，则仅分隔num个子字符串，返回切片后的子字符串拼接的列表。\nsplitlines([keepends]) 按照行(\u0026rsquo;\\r\u0026rsquo;, \u0026lsquo;\\r\\n\u0026rsquo;, \\n\u0026rsquo;)分隔，返回一个包含各行作为元素的列表，如果参数keepends为 False，不包含换行符，如果为 True，则保留换行符。\nmaketrans(intab, outtab) 创建字符映射的转换表，第一个参数是字符串，表示需要转换的字符，第二个参数也是字符串表示转换的目标。\ntranslate(table, deletechars=\u0026quot;\u0026quot;) 根据参数table给出的表，转换字符串的字符，要过滤掉的字符放到deletechars参数中。\nstr7 = \u0026#39;this is string example....wow!!!\u0026#39; intab = \u0026#39;aeiou\u0026#39; outtab = \u0026#39;12345\u0026#39; trantab = str7.maketrans(intab, outtab) print(trantab) # {97: 49, 111: 52, 117: 53, 101: 50, 105: 51} print(str7.translate(trantab)) # th3s 3s str3ng 2x1mpl2....w4w!!! 格式化\nformat 格式化函数\nstr8 = \u0026#34;{0} Love {1}\u0026#34;.format(\u0026#39;I\u0026#39;, \u0026#39;Lsgogroup\u0026#39;) # 位置参数 print(str8) # I Love Lsgogroup str8 = \u0026#34;{a} Love {b}\u0026#34;.format(a=\u0026#39;I\u0026#39;, b=\u0026#39;Lsgogroup\u0026#39;) # 关键字参数 print(str8) # I Love Lsgogroup str8 = \u0026#34;{0} Love {b}\u0026#34;.format(\u0026#39;I\u0026#39;, b=\u0026#39;Lsgogroup\u0026#39;) # 位置参数要在关键字参数之前 print(str8) # I Love Lsgogroup str8 = \u0026#39;{0:.2f}{1}\u0026#39;.format(27.658, \u0026#39;GB\u0026#39;) # 保留小数点后两位 print(str8) # 27.66GB 格式化符号\n%c 格式化字符及其ASCII码 %s 格式化字符串，用str()方法处理对象 %r 格式化字符串，用rper()方法处理对象 %d 格式化整数 %o 格式化无符号八进制数 %x 格式化无符号十六进制数 %X 格式化无符号十六进制数（大写） %f 格式化浮点数字，可指定小数点后的精度 %e 用科学计数法格式化浮点数 %E 作用同%e，用科学计数法格式化浮点数 %g 根据值的大小决定使用%f或%e %G 作用同%g，根据值的大小决定使用%f或%E 操作符辅助指令\nm.n m 是显示的最小总宽度,n 是小数点后的位数（如果可用的话） - 用作左对齐 + 在正数前面显示加号( + ) # 在八进制数前面显示零(\u0026lsquo;0\u0026rsquo;)，在十六进制前面显示'0x\u0026rsquo;或者'0X\u0026rsquo;(取决于用的是\u0026rsquo;x\u0026rsquo;还是\u0026rsquo;X\u0026rsquo;) 0 显示的数字前面填充'0\u0026rsquo;而不是默认的空格 print(\u0026#39;%5.1f\u0026#39; % 27.658) # \u0026#39; 27.7\u0026#39; print(\u0026#39;%.2e\u0026#39; % 27.658) # 2.77e+01 print(\u0026#39;%10d\u0026#39; % 10) # \u0026#39; 10\u0026#39; print(\u0026#39;%-10d\u0026#39; % 10) # \u0026#39;10 \u0026#39; print(\u0026#39;%+d\u0026#39; % 10) # +10 print(\u0026#39;%#o\u0026#39; % 10) # 0o12 print(\u0026#39;%#x\u0026#39; % 108) # 0x6c print(\u0026#39;%010d\u0026#39; % 5) # 0000000005 字典 # 字典 是无序的 键:值（{key:value}）对集合，键必须是互不相同的（在同一个字典之内）。\n创建与访问\n通过字符串或数值作为key来创建字典\ndic1 = {1: \u0026#39;one\u0026#39;, 2: \u0026#39;two\u0026#39;, 3: \u0026#39;three\u0026#39;} print(dic1) # {1: \u0026#39;one\u0026#39;, 2: \u0026#39;two\u0026#39;, 3: \u0026#39;three\u0026#39;} print(dic1[1]) # one print(dic1[4]) # 字典中并不存在`key`为4的键值对，报错：KeyError: 4。 dic2 = {\u0026#39;rice\u0026#39;: 35, \u0026#39;wheat\u0026#39;: 101, \u0026#39;corn\u0026#39;: 67} print(dic2) # {\u0026#39;wheat\u0026#39;: 101, \u0026#39;corn\u0026#39;: 67, \u0026#39;rice\u0026#39;: 35} print(dic2[\u0026#39;rice\u0026#39;]) # 35 通过构造函数dict()创建字典\n通过key将数据放入字典，每个key只对应一个value，多次对一个key放入value只保存最后一个value\n内置方法\ndict.fromkeys(seq, [value]) 用于创建一个新字典，以序列 seq 中元素做字典的键，value 为字典所有键对应的初始值。\ndict.keys()返回一个可迭代对象，可以使用 list() 来转换为列表，列表为字典中的所有键。\ndict.values()返回一个迭代器，可以使用 list() 来转换为列表，列表为字典中的所有值。\ndict.items()以列表返回可遍历的 (键, 值) 元组数组。\ndic = {\u0026#39;Name\u0026#39;: \u0026#39;Lsgogroup\u0026#39;, \u0026#39;Age\u0026#39;: 7} print(dic.items()) # dict_items([(\u0026#39;Name\u0026#39;, \u0026#39;Lsgogroup\u0026#39;), (\u0026#39;Age\u0026#39;, 7)]) print(tuple(dic.items())) # ((\u0026#39;Name\u0026#39;, \u0026#39;Lsgogroup\u0026#39;), (\u0026#39;Age\u0026#39;, 7)) print(list(dic.items())) # [(\u0026#39;Name\u0026#39;, \u0026#39;Lsgogroup\u0026#39;), (\u0026#39;Age\u0026#39;, 7)] dict.get(key, default=None) 返回指定键的值，如果值不在字典中返回默认值。\ndict.setdefault(key, default=None)和get()方法 类似, 如果键不存在于字典中，将会添加键并将值设为默认值。\nkey in dict in 操作符用于判断键是否存在于字典中，如果键在字典 dict 里返回true，否则返回false。而not in操作符刚好相反，如果键在字典 dict 里返回false，否则返回true。\ndict.pop(key[,default])删除字典给定键 key 所对应的值，返回值为被删除的值。key 值必须给出。若key不存在，则返回 default 值。\ndel dict[key] 删除字典给定键 key 所对应的值。\ndict.popitem()随机返回并删除字典中的一对键和值，如果字典已经为空，却调用了此方法，就报出KeyError异常。\ndict.clear()用于删除字典内所有元素。\ndict.copy()返回一个字典的浅复制。参考上篇博客中复制、浅拷贝、深拷贝的分析\ndict.update(dict2)把字典参数 dict2 的 key:value对 更新到字典 dict 里。\n集合 # Python 中set与dict类似，也是一组key的集合，但不存储value。由于key不能重复，所以，在set中，没有重复的key。\n注意，key为不可变类型，即可哈希的值。\n创建\n先创建对象再加入元素。 在创建空集合的时候只能使用s = set()，因为s = {}创建的是空字典。 使用set(value)工厂函数，把列表或元组转换成集合，重复元素会被set自动过滤。 访问\n可以使用for把集合中的数据一个个读取出来。使用len()內建函数得到集合的大小。 可以通过in或not in判断一个元素是否在集合中已经存在 内置方法\nset.add(elmnt)用于给集合添加元素，如果添加的元素在集合中已存在，则不执行任何操作。\nset.update(set)用于修改当前集合，可以添加新的元素或集合到当前集合中，如果添加的元素在集合中已存在，则该元素只会出现一次，重复的会忽略。\nset.remove(item) 用于移除集合中的指定元素。如果元素不存在，则会发生错误。\nset.discard(value) 用于移除指定的集合元素。remove() 方法在移除一个不存在的元素时会发生错误，而 discard() 方法不会。\nset.pop() 用于随机移除一个元素。\nset.intersection(set1, set2) 返回两个集合的交集。\nset1 \u0026amp; set2 返回两个集合的交集。\nset.intersection_update(set1, set2) 交集，在原始的集合上移除不重叠的元素。\nset.union(set1, set2) 返回两个集合的并集。\nset1 | set2 返回两个集合的并集。\nset.difference(set) 返回集合的差集。\nset1 - set2 返回集合的差集。\nset.difference_update(set) 集合的差集，直接在原来的集合中移除元素，没有返回值。\nset.symmetric_difference(set)返回集合的异或。\nset1 ^ set2 返回集合的异或。\nset.symmetric_difference_update(set)移除当前集合中在另外一个指定集合相同的元素，并将另外一个指定集合中不同的元素插入到当前集合中。\nset.issubset(set)判断集合是不是被其他集合包含，如果是则返回 True，否则返回 False。\nset1 \u0026lt;= set2 判断集合是不是被其他集合包含，如果是则返回 True，否则返回 False。\nset.issuperset(set)用于判断集合是不是包含其他集合，如果是则返回 True，否则返回 False。\nset1 \u0026gt;= set2 判断集合是不是包含其他集合，如果是则返回 True，否则返回 False。\nset.isdisjoint(set) 用于判断两个集合是不是不相交，如果是返回 True，否则返回 False。\n==集合的逻辑操作参考博客「python天池-part1-02」中的逻辑运算==\n转换\nse = set(range(4)) li = list(se) tu = tuple(se) print(se, type(se)) # {0, 1, 2, 3} \u0026lt;class \u0026#39;set\u0026#39;\u0026gt; print(li, type(li)) # [0, 1, 2, 3] \u0026lt;class \u0026#39;list\u0026#39;\u0026gt; print(tu, type(tu)) # (0, 1, 2, 3) \u0026lt;class \u0026#39;tuple\u0026#39;\u0026gt; 不可变集合\nPython 提供了不能改变元素的集合的实现版本，即不能增加或删除元素，类型名叫frozenset。需要注意的是frozenset仍然可以进行集合操作，只是不能用带有update的方法。\n序列 # 针对序列的内置函数 list(sub) 把一个可迭代对象转换为列表。\ntuple(sub) 把一个可迭代对象转换为元组。\nstr(obj) 把obj对象转换为字符串\nlen(s) 返回对象（字符、列表、元组等）长度或元素个数。\nmax(sub)返回序列或者参数集合中的最大值\nmin(sub)返回序列或参数集合中的最小值\nsum(iterable[, start=0]) 返回序列iterable与可选参数start的总和。\nsorted(iterable, key=None, reverse=False) 对所有可迭代的对象进行排序操作。\niterable \u0026ndash; 可迭代对象。 key \u0026ndash; 主要是用来进行比较的元素，只有一个参数，具体的函数的参数就是取自于可迭代对象中，指定可迭代对象中的一个元素来进行排序。 reverse \u0026ndash; 排序规则，reverse = True 降序 ， reverse = False 升序（默认）。 返回重新排序的列表。 x = [-8, 99, 3, 7, 83] print(sorted(x)) # [-8, 3, 7, 83, 99] print(sorted(x, reverse=True)) # [99, 83, 7, 3, -8] t = ({\u0026#34;age\u0026#34;: 20, \u0026#34;name\u0026#34;: \u0026#34;a\u0026#34;}, {\u0026#34;age\u0026#34;: 25, \u0026#34;name\u0026#34;: \u0026#34;b\u0026#34;}, {\u0026#34;age\u0026#34;: 10, \u0026#34;name\u0026#34;: \u0026#34;c\u0026#34;}) x = sorted(t, key=lambda a: a[\u0026#34;age\u0026#34;]) print(x) # [{\u0026#39;age\u0026#39;: 10, \u0026#39;name\u0026#39;: \u0026#39;c\u0026#39;}, {\u0026#39;age\u0026#39;: 20, \u0026#39;name\u0026#39;: \u0026#39;a\u0026#39;}, {\u0026#39;age\u0026#39;: 25, \u0026#39;name\u0026#39;: \u0026#39;b\u0026#39;}] reversed(seq) 函数返回一个反转的迭代器。\ns = \u0026#39;lsgogroup\u0026#39; x = reversed(s) print(type(x)) # \u0026lt;class \u0026#39;reversed\u0026#39;\u0026gt; print(x) # \u0026lt;reversed object at 0x000002507E8EC2C8\u0026gt; print(list(x)) # [\u0026#39;p\u0026#39;, \u0026#39;u\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;r\u0026#39;, \u0026#39;g\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;g\u0026#39;, \u0026#39;s\u0026#39;, \u0026#39;l\u0026#39;] t = (\u0026#39;l\u0026#39;, \u0026#39;s\u0026#39;, \u0026#39;g\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;g\u0026#39;, \u0026#39;r\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;u\u0026#39;, \u0026#39;p\u0026#39;) print(list(reversed(t))) # [\u0026#39;p\u0026#39;, \u0026#39;u\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;r\u0026#39;, \u0026#39;g\u0026#39;, \u0026#39;o\u0026#39;, \u0026#39;g\u0026#39;, \u0026#39;s\u0026#39;, \u0026#39;l\u0026#39;] r = range(5, 9) print(list(reversed(r))) # [8, 7, 6, 5] x = [-8, 99, 3, 7, 83] print(list(reversed(x))) # [83, 7, 3, 99, -8] enumerate(sequence, [start=0])，用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列，同时列出数据和数据下标，一般用在 for 循环当中。\nseasons = [\u0026#39;Spring\u0026#39;, \u0026#39;Summer\u0026#39;, \u0026#39;Fall\u0026#39;, \u0026#39;Winter\u0026#39;] a = list(enumerate(seasons)) print(a) # [(0, \u0026#39;Spring\u0026#39;), (1, \u0026#39;Summer\u0026#39;), (2, \u0026#39;Fall\u0026#39;), (3, \u0026#39;Winter\u0026#39;)] b = list(enumerate(seasons, 1)) print(b) # [(1, \u0026#39;Spring\u0026#39;), (2, \u0026#39;Summer\u0026#39;), (3, \u0026#39;Fall\u0026#39;), (4, \u0026#39;Winter\u0026#39;)] for i, element in a: print(\u0026#39;{0},{1}\u0026#39;.format(i, element)) # 0,Spring # 1,Summer # 2,Fall # 3,Winter zip(iter1 [,iter2 [...]])\n用于将可迭代的对象作为参数，将对象中对应的元素打包成一个个元组，然后返回由这些元组组成的对象，这样做的好处是节约了不少的内存。 我们可以使用 list() 转换来输出列表。 如果各个迭代器的元素个数不一致，则返回列表长度与最短的对象相同，利用 号操作符，可以将元组解压为列表。 a = [1, 2, 3] b = [4, 5, 6] c = [4, 5, 6, 7, 8] zipped = zip(a, b) print(zipped) # \u0026lt;zip object at 0x000000C5D89EDD88\u0026gt; print(list(zipped)) # [(1, 4), (2, 5), (3, 6)] zipped = zip(a, c) print(list(zipped)) # [(1, 4), (2, 5), (3, 6)] a1, a2 = zip(*zip(a, b)) print(list(a1)) # [1, 2, 3] print(list(a2)) # [4, 5, 6] ","date":"2022-04-30","externalUrl":null,"permalink":"/blogs/python/base/python-sequence-container-types/","section":"Blog","summary":"","title":"Python容器序列类型","type":"blogs"},{"content":" 变量 # 在使用变量前，需要现对其赋值 变量名可以包括字母、数字、下划线，但不能以字母开头 python 变量名大小写是敏感的 first = 2 second = 3 third = first + second print(third) # 5 运算符 # 算数运算符\n加（+）、减（-）、乘（*）、除（/）、整除（//）、取余（%）、幂（**）\nprint(1 + 1) # 2 print(2 - 1) # 1 print(3 * 4) # 12 print(3 / 4) # 0.75 print(3 // 4) # 0 print(3 % 4) # 3 print(2 ** 3) # 8 比较运算符\n大于（\u0026gt;）、大于等于（\u0026gt;=）、小于（\u0026lt;）、小于等于（\u0026lt;=）、等于（==）、不等于（!=）\nprint(2 \u0026gt; 1) # True print(2 \u0026gt;= 4) # False print(1 \u0026lt; 2) # True print(5 \u0026lt;= 2) # False print(3 == 4) # False print(3 != 5) # True 逻辑运算符\n与（and）、或（or）、非（not）\nprint((3 \u0026gt; 2) and (3 \u0026lt; 5)) # True print((1 \u0026gt; 3) or (9 \u0026lt; 2)) # False print(not (2 \u0026gt; 1)) # False 位运算符\n按位取反（~）、按位与（\u0026amp;）、按位或（|）、按位异或（^）、左移（\u0026laquo;）、右移（\u0026raquo;）\nprint(bin(4)) # 0b100 print(bin(5)) # 0b101 print(bin(~4), ~4) # -0b101 -5 print(bin(4 \u0026amp; 5), 4 \u0026amp; 5) # 0b100 4 print(bin(4 | 5), 4 | 5) # 0b101 5 print(bin(4 ^ 5), 4 ^ 5) # 0b1 1 print(bin(4 \u0026lt;\u0026lt; 2), 4 \u0026lt;\u0026lt; 2) # 0b10000 16 print(bin(4 \u0026gt;\u0026gt; 2), 4 \u0026gt;\u0026gt; 2) # 0b1 1 三元运算符\nx, y = 4, 5 small = x if x \u0026lt; y else y print(small) # 4 其他运算符\n存在（in）、不存在（not in）\nletters = [\u0026#39;A\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;C\u0026#39;] if \u0026#39;A\u0026#39; in letters: print(\u0026#39;A\u0026#39; + \u0026#39; exists\u0026#39;) if \u0026#39;h\u0026#39; not in letters: print(\u0026#39;h\u0026#39; + \u0026#39; not exists\u0026#39;) # A exists # h not exists 是（is）、不是（not is）\n# 比较的两个变量均指向可变类型 a = [\u0026#34;hello\u0026#34;] b = [\u0026#34;hello\u0026#34;] print(a is b, a == b) # False True print(a is not b, a != b) # True False \u0026#39;\u0026#39;\u0026#39; - is, is not 对比的是两个变量的内存地址 - ==, != 对比的是两个变量的值 - 比较的两个变量，指向的都是地址不可变的类型（str等），那么is，is not 和 ==，！= 是完全等价的。 - 对比的两个变量，指向的是地址可变的类型（list，dict，tuple等），则两者是有区别的。 \u0026#39;\u0026#39;\u0026#39; 数据类型 # int整型 \u0026lt;class 'int'\u0026gt;float浮点型\u0026lt;class 'float'\u0026gt;bool布尔型\u0026lt;class 'bool'\u0026gt;\n整型\na = 1031 print(a, type(a)) # 1031 \u0026lt;class \u0026#39;int\u0026#39;\u0026gt; 浮点型\nprint(1, type(1)) # 1 \u0026lt;class \u0026#39;int\u0026#39;\u0026gt; print(1., type(1.)) # 1.0 \u0026lt;class \u0026#39;float\u0026#39;\u0026gt; a = 0.00000023 b = 2.3e-7 print(a) # 2.3e-07 print(b) # 2.3e-07 # 有时候我们想保留浮点型的小数点后 n 位。可以用 decimal 包里的 Decimal 对象和 getcontext() 方法来实现。 decimal.getcontext().prec = 4 c = Decimal(1) / Decimal(3) print(c) # 0.3333 布尔型\n# 在布尔（boolean）型变量中只有True和False两个值，在数字运算中分别用1和0表示 print(True + True) # 2 print(True + False) # 1 print(True * False) # 0 # 除了对变量赋值True和False，还可以用bool(X)，X可以是基本类型（int、float、bool）和容器类型（字符串、列表、元组、字典和集合），如果X是空的（数值——0，容器——没有元素），返回也是false print(type(0), bool(0), bool(1)) # \u0026lt;class \u0026#39;int\u0026#39;\u0026gt; False True print(type(10.31), bool(0.00), bool(10.31)) # \u0026lt;class \u0026#39;float\u0026#39;\u0026gt; False True print(type(True), bool(False), bool(True)) # \u0026lt;class \u0026#39;bool\u0026#39;\u0026gt; False True 在python中万物皆对象（object），基本数据类型也不例外，只要是对象就有相应的属性（attributes）和方法（methods）。\n# 举例找一个整数的二进制表示，再返回其长度 a = 1031 print(bin(a)) # 0b10000000111 print(a.bit_length()) # 11 获取类型信息\ntype()\nprint(type(1)) # \u0026lt;class \u0026#39;int\u0026#39;\u0026gt; print(type(5.2)) # \u0026lt;class \u0026#39;float\u0026#39;\u0026gt; print(type(True)) # \u0026lt;class \u0026#39;bool\u0026#39;\u0026gt; print(type(\u0026#39;5.2\u0026#39;)) # \u0026lt;class \u0026#39;str\u0026#39;\u0026gt; isinstance()\nprint(isinstance(1, int)) # True print(isinstance(5.2, float)) # True print(isinstance(True, bool)) # True print(isinstance(\u0026#39;5.2\u0026#39;, str)) # True 区别\ntype() 不会认为子类是一种父类类型，不考虑继承关系。 isinstance() 会认为子类是一种父类类型，考虑继承关系。 位运算 # 1. 原码、反码和补码\n二进制的三种表示形式，计算机内部使用补码表示。\n原码：有一位符号位\n00 00 00 11 -\u0026gt; 3 10 00 00 11 -\u0026gt; -3 反码：正数的反码是原码，复数的反码是符号位不变，其余位取反\n00 00 00 11 -\u0026gt; 3 11 11 11 00 -\u0026gt; -3 补码：正数的补码就是原码，负数的补码就是反码+1\n00 00 00 11 -\u0026gt; 3 11 11 11 01 -\u0026gt; -3 符号位：最高位为符号位，0表示正数，1表示负数。在位运算中符号位也参与运算。\n2. 按位运算\n按位非～\n把二进制数的0和1全部取反，包括符号位\n按位与\u0026amp;\n只有两个都为1时才为1\n按位或｜\n只要其中一个为1就为1\n按位异或^\n对应位不同时为1\n按位左移\n将数值的二进制表示向左移动i位\n按位右移\n将数值的二进制表示向右移动i位\n3. 位运算实现快速计算\n通过“\u0026raquo;”和“\u0026laquo;”可以实现快速计算2的倍数\n通过“^”可以快速交换两个数\n通过a\u0026amp;(-a)可以快速获取a的最后为1位置的整数\nprint(8\u0026gt;\u0026gt;3) # 1 a = 2 b = 5 a ^= b b ^= a a ^= b print(a) # 5 print(b) # 2 print(a\u0026amp;(-a)) 4. 位运算实现整数集合\n一个数的二进制表示可以看做一个集合（0/1分别表示不在/在集合中）。\neg：集合{1, 3, 4, 8}，可以表示成 01 00 01 10 10 而对应的位运算可就可以看作对集合进行的操作。\n元素与集合的操作：\n# a | (1\u0026lt;\u0026lt;i) -\u0026gt; 把 i 插入到集合中 # a \u0026amp; ~(1\u0026lt;\u0026lt;i) -\u0026gt; 把 i 从集合中删除 # a \u0026amp; (1\u0026lt;\u0026lt;i) -\u0026gt; 判断 i 是否属于该集合（零不属于，非零属于） a = 4 print(bin(a)) #0b100 print(bin(a|(1\u0026lt;\u0026lt;7))) # 0b10000100 print(bin(a\u0026amp;~(1\u0026lt;\u0026lt;2))) # 0b0 print(bin(a\u0026amp;(1\u0026lt;\u0026lt;1))) # 0b0 集合之间的操作：\n# a 补 -\u0026gt; ~a # a 交 b -\u0026gt; a \u0026amp; b # a 并 b -\u0026gt; a | b # a 差 b -\u0026gt; a \u0026amp; (~b) a = 4 b = 19 print(bin(a)) # 0b100 print(bin(b)) # 0b10011 print(bin(~a)) # -0b101 print(bin(a\u0026amp;b)) # 0b0 print(bin(a|b)) # 0b10111 print(bin(a\u0026amp;(~b))) # 0b100 整数在内存中是以补码的形式存在的，输出自然也是按照补码输出。\n注意：\npython中bin一个负数，是原码的二进制加上一个负号 python中整型是不限制程度的，不会超范围溢出，普通32位，超过会自动当长整型处理，几乎没有限制 流程控制 # 条件语句 # if语句\n# if expression: # expr_true_suite if 2 \u0026gt; 1 and not 2 \u0026gt; 3: print(\u0026#39;Correct Judgement!\u0026#39;) # Correct Judgement! expression为真才执行expr_true_suite\nif - else语句\n# if expression: # expr_true_suite # else: # expr_false_suite if - elif - else语句\nif expression1: expr1_true_suite elif expression2: expr2_true_suite · · elif expressionN: exprN_true_suite else: expr_false_suite assert关键词\nassert这个关键词我们称之为“断言”，当这个关键词后边的条件为 False 时，程序自动崩溃并抛出AssertionError的异常。\nmy_list = [\u0026#39;lsgogroup\u0026#39;] my_list.pop(0) assert len(my_list) \u0026gt; 0 # AssertionError 循环语句 # while循环\nwhile 布尔表达式: 代码块 while - else循环\nwhile 布尔表达式: 代码块 else: 代码块 当while循环正常执行完的情况下，执行else输出，如果while循环中执行了跳出循环的语句，比如 break，将不执行else代码块的内容。\nfor循环\nfor 迭代变量 in 可迭代对象: 代码块 for - else循环\nfor 迭代变量 in 可迭代对象: 代码块 else: 代码块 与while - else类似\nrange函数\n# range([start,] stop[, step=1]) for i in range(2, 9): # 不包含9 print(i) # 2 # 3 # 4 # 5 # 6 # 7 # 8 enumerate()函数\nenumerate(sequence, [start=0]) sequence：一个序列、迭代器或其他支持迭代对象。 start：下标起始位置。 返回 enumerate(枚举) 对象 seasons = [\u0026#39;Spring\u0026#39;, \u0026#39;Summer\u0026#39;, \u0026#39;Fall\u0026#39;, \u0026#39;Winter\u0026#39;] lst = list(enumerate(seasons)) print(lst) # [(0, \u0026#39;Spring\u0026#39;), (1, \u0026#39;Summer\u0026#39;), (2, \u0026#39;Fall\u0026#39;), (3, \u0026#39;Winter\u0026#39;)] lst = list(enumerate(seasons, start=1)) # 下标从 1 开始 print(lst) # [(1, \u0026#39;Spring\u0026#39;), (2, \u0026#39;Summer\u0026#39;), (3, \u0026#39;Fall\u0026#39;), (4, \u0026#39;Winter\u0026#39;)]p break语句\n可以跳出当前所在层的循环\ncontinue语句\n提前终止本轮循环，进入下一轮\npass语句\npass 语句的意思是“不做任何事”，如果你在需要有语句的地方不写任何语句，那么解释器会提示出错，而 pass 语句就是用来解决这些问题的。\n推导式 # 列表推导式\n# [ expr for value in collection [if condition] ] x = [(i, i ** 2) for i in range(100) if (i % 2) != 0 and (i % 3) == 0] print(x) # [(3, 9), (9, 81), (15, 225), (21, 441), (27, 729), (33, 1089), (39, 1521), (45, 2025), (51, 2601), (57, 3249), (63, 3969), (69, 4761), (75, 5625), (81, 6561), (87, 7569), (93, 8649), (99, 9801)] 元组推导式\n# ( expr for value in collection [if condition] ) a = (x for x in range(10)) print(a) # \u0026lt;generator object \u0026lt;genexpr\u0026gt; at 0x0000025BE511CC48\u0026gt; print(tuple(a)) # (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) 字典推导式\n# { key_expr: value_expr for value in collection [if condition] } b = {i: i % 2 == 0 for i in range(10) if i % 3 == 0} print(b) # {0: True, 3: False, 6: True, 9: False} 集合推导式\n# { expr for value in collection [if condition] } c = {i for i in [1, 2, 3, 4, 5, 5, 6, 4, 3, 2, 1]} print(c) # {1, 2, 3, 4, 5, 6} 异常处理 # Python 标准异常总结\nBaseException：所有异常的 基类 Exception：常规异常的 基类 StandardError：所有的内建标准异常的基类 ArithmeticError：所有数值计算异常的基类 FloatingPointError：浮点计算异常 OverflowError：数值运算超出最大限制 ZeroDivisionError：除数为零 AssertionError：断言语句（assert）失败 AttributeError：尝试访问未知的对象属性 EOFError：没有内建输入，到达EOF标记 EnvironmentError：操作系统异常的基类 IOError：输入/输出操作失败 OSError：操作系统产生的异常（例如打开一个不存在的文件） WindowsError：系统调用失败 ImportError：导入模块失败的时候 KeyboardInterrupt：用户中断执行 LookupError：无效数据查询的基类 IndexError：索引超出序列的范围 KeyError：字典中查找一个不存在的关键字 MemoryError：内存溢出（可通过删除对象释放内存） NameError：尝试访问一个不存在的变量 UnboundLocalError：访问未初始化的本地变量 ReferenceError：弱引用试图访问已经垃圾回收了的对象 RuntimeError：一般的运行时异常 NotImplementedError：尚未实现的方法 SyntaxError：语法错误导致的异常 IndentationError：缩进错误导致的异常 TabError：Tab和空格混用 SystemError：一般的解释器系统异常 TypeError：不同类型间的无效操作 ValueError：传入无效的参数 UnicodeError：Unicode相关的异常 UnicodeDecodeError：Unicode解码时的异常 UnicodeEncodeError：Unicode编码错误导致的异常 UnicodeTranslateError：Unicode转换错误导致的异常 python标准警告总结\nWarning：警告的基类 DeprecationWarning：关于被弃用的特征的警告 FutureWarning：关于构造将来语义会有改变的警告 UserWarning：用户代码生成的警告 PendingDeprecationWarning：关于特性将会被废弃的警告 RuntimeWarning：可疑的运行时行为(runtime behavior)的警告 SyntaxWarning：可疑语法的警告 ImportWarning：用于在导入模块过程中触发的警告 UnicodeWarning：与Unicode相关的警告 BytesWarning：与字节或字节码相关的警告 ResourceWarning：与资源使用相关的警告 try - except语句\ntry: 检测范围 except Exception[as reason]: 出现异常后的处理代码 try 语句按照如下方式工作：\n首先，执行try子句（在关键字try和关键字except之间的语句） 如果没有异常发生，忽略except子句，try子句执行后结束。 如果在执行try子句的过程中发生了异常，那么try子句余下的部分将被忽略。如果异常的类型和except之后的名称相符，那么对应的except子句将被执行。最后执行try - except语句之后的代码。 如果一个异常没有与任何的except匹配，那么这个异常将会传递给上层的try中。 try - except - finally语句\ntry: 检测范围 except Exceptionas reason: 出现异常后的处理代码 finally: 无论如何都会被执行的代码\n不管try子句里面有没有发生异常，finally子句都会执行。\ntry - except - else语句\n如果在try子句执行时没有发生异常，Python将执行else语句后的语句。\ntry: 检测范围 except: 出现异常后的处理代码 else: 如果没有异常执行这块代码 raise语句\nPython 使用raise语句抛出一个指定的异常。\ntry: raise NameError(\u0026#39;HiThere\u0026#39;) except NameError: print(\u0026#39;An exception flew by!\u0026#39;) # An exception flew by! ","date":"2022-04-29","externalUrl":null,"permalink":"/blogs/python/base/python-variables-operators-data-types/","section":"Blog","summary":"","title":"Python变量、运算符与数据类型","type":"blogs"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"}]