想象一下,你刚刚发布了一个新的软件库或专门的数据库 API。你希望开发人员能够毫不费力地使用它,比如只需输入像“查找昨天注册的所有用户”这样的自然语言命令,而不需要编写复杂的 SQL 查询或函数调用。

要构建一个能够将自然语言翻译成特定代码的工具,通常面临一个巨大的障碍: 数据 。 训练一个模型来理解特定的 API 通常需要成千上万对自然语言命令及其对应的代码片段。创建这个数据集既昂贵、耗时又枯燥。

但是,如果仅用一个标注示例就能构建出高精度的翻译系统呢?

这就是 ICIP (In-Context Inverse Programming,上下文逆向编程) 的承诺,这是德克萨斯大学奥斯汀分校和微软的研究人员在论文 “Language-to-Code Translation with a Single Labeled Example” 中提出的一种新方法。该技术利用大型语言模型 (LLM) 的能力,从未标注的代码中引导生成自己的训练数据,有效地教会自己如何使用新工具。

在本文的深入探讨中,我们将了解 ICIP 的工作原理,其“循环一致性”背后的数学原理,以及为什么它能以极少的人力投入达到接近全监督系统的性能。


问题: 高昂的监督成本

像 GPT-4 或 CodeLlama 这样的大型语言模型已经非常擅长编写代码。如果你要求它写一个 Python 脚本来排序列表,它可以立刻完成,因为它在预训练期间见过数百万个 Python 示例。

然而,当我们转向领域特定语言 (DSLs)新 API 时,问题就出现了。如果你为公司创建了一个自定义内部工具,LLM 很可能从未见过它。它不知道语法、函数名或逻辑。

为了教导模型,你通常需要使用少样本提示 (few-shot prompting) : 在提示上下文中向模型提供一系列示例 (指令和代码对) 。

  • 指令: “Remove all red items.” (移除所有红色项目)
  • 代码: inventory.filter(color='red').delete()

提供的示例越多,模型的表现就越好。但是提供这些示例需要人类专家坐下来编写它们。研究人员提出了一个关键问题: 我们能否利用现有的代码来自动化这一过程?

大多数软件项目都有大量的未标注程序——单元测试、日志文件或 API 文档示例——这些都是没有英语描述的代码。ICIP 旨在释放这些未标注数据的价值。


核心洞察: 逆向编程

ICIP 背后的核心洞察简单而深刻: 读代码比写代码更容易。

即使 LLM 不知道如何从头开始为新 API 生成代码,它通常也能看懂一段代码并猜测其功能。代码通常设计得具有可读性,拥有有意义的变量名和逻辑结构。

ICIP 颠覆了标准的生成过程。它不是将 语言 \(\rightarrow\) 代码,而是从 代码 \(\rightarrow\) 语言 开始。它使用 LLM 为未标注的代码生成合成描述,过滤掉糟糕的猜测,然后使用好的配对来教模型如何执行原始任务。

图 1: 我们的方法概览。从零个或多个带有自然语言命令标签的种子程序以及一组未标注程序开始,我们使用预训练的语言模型为程序标注命令,选择那些能引导 LM 以高概率重新生成目标程序的命令。这些自动标注的程序被用作下一轮标注的输入,最终用作语义解析器的上下文示例。

如图 1 所示,该过程形成一个循环。我们从一小组标注数据 (可能只是一个示例) 和一大组未标注代码开始。我们使用 LLM 标注代码,优化这些标签,然后使用新标注的数据来提示解析器。

ICIP 方法论

让我们拆解一下上下文逆向编程的机制。该方法是一个迭代过程,类似于统计学中使用的期望最大化 (EM) 算法。它在两个主要阶段之间交替: 采样过滤

1. 设置

我们假设拥有:

  1. 一个预训练的语言模型 (\(p_{\rm LM}\))。
  2. 一小组标注示例 , \(D_{L\Pi}\)。 (这可以少至 1 个示例) 。
  3. 一大组未标注程序 , \(D_{\Pi}\)。

我们的目标是创建一个由 (命令,程序) 对组成的合成数据集 \(\hat{D}_{L\Pi}\),用于提示 LLM。

2. 采样步骤 (猜测提示)

对于集合中的每个未标注程序 \(\pi_i\),我们要找到对应的自然语言命令 \(\ell\)。我们要求 LLM 查看代码并生成候选命令列表。

从数学上讲,我们正在根据程序 (\(\pi\)) 和现有示例从自然语言命令 (\(\ell\)) 的概率分布中进行采样:

从 LM 采样候选标签的方程

在这个方程中:

  • \(L'_i\) 是程序 \(\pi_i\) 的候选自然语言标签集合。
  • 模型查看现有的标注数据 (\(D_{L\Pi}\)) 和我们已经创建的任何合成数据 (\(\hat{D}_{L\Pi}\)),以理解所需的命令风格。

直观地说,模型看到一行代码如 query.sort_by('date') 并生成候选,例如:

  1. “Sort the results by date.” (按日期排序结果。)
  2. “Order by time.” (按时间排序。)
  3. “Find dates.” (查找日期。)

3. 过滤步骤 (循环一致性)

采样步骤是有噪声的。LLM 可能会产生幻觉或误解代码。为了解决这个问题,ICIP 引入了一个基于循环一致性 (通常称为“往返验证”) 的严格过滤机制。

其逻辑是: 如果生成的英语命令是正确的,将其翻译回代码应该能得到原始程序。

我们创建一个临时的候选数据集 (\(D'_{L\Pi}\)),然后对其进行过滤:

过滤候选集的方程

这一步有效地充当了质量控制门。我们只保留那些自然语言描述足够准确,能够生成正确代码的配对。

图 2: 所提出的学习算法细节。在采样步骤中,当前的种子标注示例和自动标注示例集被用于生成与每个未标注程序相关的候选自然语言命令集。在此步骤中,LM 接收来自上一轮迭代的种子标签和自动生成的标签作为提示。在过滤步骤中,该集合被筛选,仅保留那些能以高概率解析回原始程序的命令,提示中同样使用先前标注的示例。

图 2 可视化了这个流程。“LM Labeling”框生成描述 (例如,“Find cities in Jefferson county…”) 。“LM Parsing”框接收该描述并尝试重新生成 SQL。如果输出与原始输入 SELECT * FROM city... 匹配,则保留该配对。

两种类型的过滤器

研究人员提出了两种具体的过滤实现方式:

A. 硬性往返 (Hard Round Trip, HardRT) 这是一种更严格的方法。只有当 LLM 在收到候选命令 \(\hat{\ell}\) 提示时,将其原始程序 \(\pi\) 生成为最可能的输出时,该命令 \(\hat{\ell}\) 才会被接受。

硬性往返过滤方程

这确保了高精度,但如果模型稍有不确定,可能会拒绝有效的命令。

B. 最大往返 (Max Round Trip, MaxRT) 这是一种更灵活的概率方法。我们不要求模型完美地生成代码,而是查看概率分数。我们选择那个与所有其他候选命令相比,赋予原始程序 \(\pi\) 最高概率的命令 \(\hat{\ell}\)。

最大往返过滤方程

MaxRT 确保对于每个未标注程序,我们都选择最佳可用的描述,即使模型本身无法完美生成该代码。这有助于覆盖更多样化的编码模式。

4. 迭代

一旦过滤完成,我们将成功的配对添加到“已标注”集合中。然后我们重复这个过程!新标注的示例帮助 LLM 更好地理解任务,从而在下一轮中改进对剩余未标注程序的标注。

算法 1 上下文逆向编程

算法 1 总结了这个循环: 采样候选 \(\rightarrow\) 过滤它们 \(\rightarrow\) 更新数据集 \(\rightarrow\) 重复。


实验设置

为了证明这一点,作者在两个具有挑战性的语义解析基准上测试了 ICIP:

  1. Overnight: 包含日历、篮球统计和住房等各个领域查询的数据集。
  2. Spider: 一个著名的文本到 SQL 数据集,涉及复杂的数据库查询。

他们比较了不同的设置:

  • Few-shot (1+0): 基线。模型只获得 1 个标注示例和 0 个未标注示例。
  • Few-shot (1+100): 模型获得 1 个标注示例,加上 100 个未标注程序 (仅作为“代码示例”列出,没有翻译) 。
  • ICIP (1+100): 提出的方法。1 个标注种子,100 个未标注程序通过采样/过滤循环处理。
  • Fully Labeled (100+0): 一个全监督的“oracle”,其中人类实际标注了所有 100 个示例。

他们使用了稳健的指标,特别是执行准确率 (Execution Accuracy) (运行代码是否产生正确结果?) 和精确匹配 (Exact Match) (代码是否与标准答案完全相同?) 。


结果: 事半功倍

结果令人震惊。ICIP 显著优于标准的提示技术。

表 1 (如下) 显示了跨数据集的性能。

表 1: Overnight 和 Spider 数据集上的结果。指标描述见第 4 节开头。M + N 列表示用于构建提示的已标注和未标注示例的数量。Prog 表示保留测试示例上的程序精确匹配百分比。Ans 表示答案 (或指称) 准确率: 执行时产生与对应黄金程序相同结果的程序百分比。下标表示不同种子的标准误差。Overnight (All) 条件下未计算此误差,该条件显示了跨种子和数据集的平均性能;各领域的具体结果和变异度量见附录 A。

看表 1 左侧的 text-davinci-003 结果:

  • Few-shot (1+0): 16.6% 准确率。 (模型几乎不知道该做什么) 。
  • Few-shot (1+100): 31.2% 准确率。 (仅展示未标注代码有一点帮助) 。
  • ICIP + MaxRT (1+100): 42.7% 准确率。

关键是,比较 ICIP (1+100) 的结果 (42.7%) 与 Fully Labeled (100+0) 的结果 (49.7%)。 ICIP 达到了全监督系统约 85% 的性能 , 尽管只用了一个人工标注示例,而不是 100 个。

这证实了模型生成的合成标签质量足够高,可以作为训练数据。

迭代的力量

迭代循环真的很重要吗?是的。 图 3 显示,随着模型执行更多轮次的采样和过滤,准确率在攀升。

图 3: 使用 text-davinci-003 和单个种子标签在 Overnight Blocks 上的解析准确率随 ICIP + MaxRT 的连续轮次增加而提高。阴影区域表示 5 个种子的样本标准差。

在第一轮中,模型做出了不错的猜测。通过将这些猜测反馈回上下文中,模型获得了对语法和风格的更好理解,使其能够在第二轮中标注更难的示例。

定性分析: 模型写了什么?

人们可能会担心模型会生成无意义的描述。然而,查看 表 2 中的实际输出,我们看到 ICIP 生成了准确且自然的命令。

表 2: 推断出的程序标注示例,均在 Overnight Blocks 子任务上使用 text-davinci-003。错误以红色显示。前两组使用一个已标注示例进行提示,标注风格一致;第三组显示了一个完全无监督的模型,它产生的标注非常不同 (可能质量较低) ,但却产生了一个惊人有效的文本到代码模型 (见表 1) 。

注意第一行 (ICIP 1+100)。Gold (人类) 标签是 “find me all 3 inch tall special blocks” (帮我找到所有 3 英寸高的特殊积木) 。ICIP 生成的标签是 “find me all special blocks that have a height of 3 inches” (帮我找到所有高度为 3 英寸的特殊积木) 。虽然措辞不同,但语义是相同的。这种语言多样性实际上是有益的,因为它让模型接触到了用户可能表达请求的不同方式。


鲁棒性: 如果代码看起来很奇怪怎么办?

你可能会争辩说,LLM 预训练了太多的 SQL 和 Python,以至于它们已经知道了语法,所以 ICIP 并不是真的“学到”了新东西——它只是在记忆。

为了测试这一点,研究人员创建了一个“酷刑测试”。他们使用了数据集中使用的标准编程语言,并应用转换使其变得陌生。他们移除了大括号、反转了参数顺序,或匿名化了函数名称 (例如,将 filter() 变为 p0()) 。

表 3: 程序转换示例。

表 3 所示,“Anon. fns.”版本在人类眼中看起来像乱码: (p0 p1 (p0 p2...))

值得注意的是,ICIP 很好地适应了这些变化。因为它查看了未标注代码的结构和上下文,所以它仍然可以将自然语言映射到这些奇怪的、混淆的程序上。

表 4: 程序转换实验结果。即使程序语法发生重大变化,ICIP 仍能表现不俗。

表 4 显示,虽然在更难的转换上性能有所下降 (正如预期的那样) ,但 ICIP (1+100) 继续有效地发挥作用,相对于全监督 oracle 保持了类似的性能比率。这证明 ICIP 真正从未标注的示例中学到了新语法。


结论和启示

ICIP 方法代表了代码生成领域半监督学习的一个重大进步。它解决了开发人员在将 LLM 与新的、自定义的或快速变化的软件库集成时面临的“冷启动”问题。

以下是关键要点:

  1. 未标注数据很有价值: 不要扔掉你的日志或测试用例。即使没有英语描述,原始代码也包含了 LLM 掌握新领域所需的结构信息。
  2. 生成模型也是判别模型: 我们通常认为 LLM 是写作者,但 ICIP 表明它们也是优秀的读者。它们“逆向编程”——解释代码功能——的能力是一种强大的监督信号。
  3. 一致性是关键: ICIP 的成功依赖于“往返”检查。盲目信任模型会导致错误;只信任自洽的输出会产生高质量的合成数据。

对于学生和研究人员来说,ICIP 凸显了 AI 领域一个日益增长的趋势: 自我对齐 (Self-Alignment) 。 随着模型变得越来越强大,我们可以减少对昂贵人工标注的依赖,转而更多地依赖巧妙的算法,让模型通过单一的知识种子自我引导,实现完全的能力。