核心目标: pass@k 旨在评估一个代码生成模型(如 GitHub Copilot, Codex, ChatGPT 等)在解决特定编程问题时的实际功能性可靠性。它超越了简单的语法正确性或模糊匹配,直接关注生成的代码是否能通过预定义的、严格的测试用例。

核心思想:

  1. 任务定义: 对于一个给定的编程问题描述(通常是一个自然语言提示),要求模型生成代码解决方案。

  2. 多次采样: 不是只生成一个解决方案,而是让模型独立生成 k 个不同的候选解决方案(通常是采样 k 次,引入随机性)。

  3. 执行测试: 对生成的 k 个候选代码中的每一个,都运行一套预先设计好的、全面的单元测试

  4. 判定通过: 检查每个候选代码是否完全通过了所有测试用例(即 pass 了所有测试)。

  5. 指标计算: pass@k 的值定义为:在 k 次尝试中,至少有一个候选代码完全通过所有测试的概率(或比例)

简单来说: pass@k 回答了这样一个问题:“如果我让这个模型尝试 k 次来解决这个问题,它最终能成功给出一个完全正确的解决方案的概率有多大?”

为什么 pass@k 如此重要?

  1. 关注功能正确性: 这是最核心的优势。它直接衡量生成的代码是否真正解决了问题,而不仅仅是看起来像代码或者语法正确。一个能通过所有测试的代码才是有实际价值的代码。

  2. 容忍多样性: 编程问题通常有多种正确的解法。pass@k 允许模型生成不同逻辑、不同风格的代码,只要其中任何一个能通过测试就算成功。这更符合实际开发中“条条大路通罗马”的情况。

  3. 评估模型可靠性: 较高的 pass@k 值(尤其是对于较小的 k,如 pass@1)表明模型非常可靠,通常一次就能生成正确的代码。较低的 pass@1 但较高的 pass@10 或 pass@100 则表明模型可能需要多次尝试才能“蒙对”,或者其解决方案存在较高的不确定性。

  4. 指导实际应用: 对于开发者工具(如 Copilot),pass@k 直接关系到用户体验。高 pass@1 意味着用户经常能直接获得可用的代码;高 pass@k(k>1)则意味着工具提供的多个建议中很可能包含一个可用的选项。

  5. 超越模糊匹配: 避免了像 BLEU 这样的指标可能带来的问题——一个与参考代码字符串相似度高但逻辑错误的代码可能得高分,而一个字符串不同但逻辑完全正确的代码可能得低分。pass@k 只看最终的执行结果。

如何计算 pass@k?

OpenAI的论文Evaluating Large Language Models Trained on Code中指出了一个计算方法:

链接:[2107.03374] Evaluating Large Language Models Trained on Code

假设我们有 n 个独立的编程问题需要评估。对于每个问题 i

  1. 让模型生成 c 个候选解决方案(c >= k)。通常 c 会远大于 k(例如 c = 200k = 1, 10, 100)。

  2. 对每个候选方案运行测试,记录其是否通过(pass = 1fail = 0)。

  3. 计算该问题 i 的 pass@k 估计值:

    • 统计生成的 c 个候选方案中,通过测试的个数 s_i

    • 问题 i 的 pass@k 概率估计为:
      pass@k_i=E_{problems}\left [ 1-\frac{\binom{c-s_i}{k}}{\binom{c}{k}} \right ]

      公式含义:1 - (从所有失败方案中选k个的方案数 / 从所有方案中选k个的方案数)。即 1 - 选出的k个方案全部失败的概率 = 至少有一个成功的概率
  4. 计算整体 pass@k 对所有 n 个问题的 pass@k_i 结果取平均值
    pass@k=\frac{1}{n}\sum_{i=1}^{n}pass@k_i

为什么需要这个估计公式?为什么不直接采样 k 次?

  • 效率: 直接为每个问题采样 k 次并计算比例(有多少次至少有一个通过)需要非常大的 n(问题数量)才能得到统计显著的可靠结果,尤其是当 k 较大时。计算成本很高。

  • 利用更多信息: 通过生成 c 个样本(c >> k),我们获得了关于模型在该问题上潜在成功率的更丰富信息。上面的估计公式是一种无偏估计,它利用了所有 c 个样本的信息来更精确地推断“如果只抽样 k 次会怎样”。

  • 避免乐观偏差: 如果直接选择 k 个样本中表现最好的一个(而不是独立随机抽样 k 次),会严重高估模型的实际 pass@k 性能。上述公式模拟的是独立随机抽样的场景。

举例说明:

假设有一个编程问题:

  • 我们生成 c = 5 个候选代码。

  • 测试结果:其中 3 个通过 (s_i = 3),2 个失败。

  • 计算该问题的 pass@1

    • Combination(5 - 3, 1) / Combination(5, 1) = Combination(2, 1) / Combination(5, 1) = 2 / 5 = 0.4

    • pass@1_i = 1 - 0.4 = 0.6

    • 解释:如果只随机生成 1 次代码,获得通过代码的概率是 60%。

  • 计算该问题的 pass@2

    • Combination(5 - 3, 2) / Combination(5, 2) = Combination(2, 2) / Combination(5, 2) = 1 / 10 = 0.1

    • pass@2_i = 1 - 0.1 = 0.9

    • 解释:如果随机独立生成 2 次代码,那么至少有一次获得通过代码的概率是 90%(因为两次都失败的概率只有 10%)。

import numpy as np

def pass_at_k(n, c, k):
    """
    :param n: total number of samples
    :param c: number of correct samples
    :param k: k in pass@$k$
    """
    if n - c < k:
        return 1.0
    return 1.0 - np.prod(1.0 - k / np.arange(n - c + 1, n + 1))

print(pass_at_k(5, 3, 1))  # 0.6
print(pass_at_k(5, 3, 2))  # 0.9

常见的 k 值及其意义:

  • pass@1 模型“首次尝试”就生成正确代码的能力。衡量精确度可靠性。这是最严格的指标,值通常较低但对用户体验至关重要(开发者希望第一个建议就是好的)。

  • pass@10 / pass@25 模型在提供少量(如 10 个或 25 个)备选建议时,包含至少一个正确解决方案的能力。衡量模型在提供有限选项时的实用性(如 Copilot 的补全列表)。

  • pass@100 / pass@200 模型在大量采样中找到正确解决方案的潜力。衡量模型的表达能力覆盖能力。即使 pass@1 不高,一个高的 pass@100 也表明模型有能力解决该问题,只是需要更多尝试或更好的筛选机制。

pass@k 的局限性与注意事项:

  1. 测试用例质量: pass@k 完全依赖于测试用例的质量和完备性。不充分或有错误的测试用例会导致指标失真(假阳性/假阴性)。设计全面的测试集是关键挑战。

  2. 计算成本: 生成大量候选代码(尤其是 c 很大时)并对每个代码执行测试(可能需要沙盒环境)计算开销非常大。

  3. 仅衡量功能正确性: 它不评估代码的可读性可维护性效率(时间复杂度/空间复杂度)、安全性是否符合编码规范。一个能通过测试但写得极其糟糕的代码也算 pass

  4. 问题难度差异: 整体 pass@k 是所有问题的平均值。不同问题的难度差异可能很大。报告时最好能分难度级别展示结果。

  5. 随机性: 由于涉及采样,多次评估同一模型在同一问题集上的 pass@k 可能会有小幅波动。通常需要报告平均值和置信区间。

  6. k 值选择: 选择合适的 k 值取决于应用场景。研究论文通常会报告多个 k 值下的结果。

总结:

pass@k 是评估代码生成模型功能性能力的黄金标准指标。它通过要求生成的代码通过严格的单元测试来判断其正确性,并通过计算在 k 次独立尝试中至少成功一次的概率来衡量模型的可靠性和解决问题的能力。尽管存在对测试用例的依赖和计算成本等限制,pass@k 因其直接面向实际价值(生成的代码能真正工作)而成为该领域不可或缺的核心评估指标。理解 pass@1pass@10pass@100 等不同 k 值下的结果,可以全面了解模型的精度、实用性和潜力。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐