跳至主要内容

HumanEval:评估大语言模型代码生成能力的基准

了解如何使用 Hugging Face Evaluate 库评估您的 LLM 在代码生成方面的能力。
· 9分钟 阅读

使用 AI 探索

HumanEval 是由 OpenAI 开发的一个基准数据集,用于评估大语言模型(LLM)在代码生成任务中的表现。它已成为衡量 AI 模型理解与生成代码能力的重要工具。

在本教程中,我们将了解 HumanEval 与 pass@k 指标。此外,我们将使用 codeparrot-small 模型在 164 个问题上评估其代码生成能力。

在 HumanEval 的评估流程中,我们将使用 Hugging Face 生态来加载数据集、模型以及必要的指标。如果您对 LLM 及其能力还比较陌生,不妨阅读我们的《开发大语言模型》课程。

HumanEval feature image

作者供图

什么是 HumanEval?

HumanEval 由 OpenAI 开发,是专为大语言模型设计的评估数据集。它作为参考基准,用于评估 LLM 在代码生成任务上的表现,重点考察模型的语言理解、推理能力,以及解决与算法和简单数学相关问题的能力。

该数据集包含 164 道手写编程题,每题包括函数签名、文档字符串、函数体以及若干单元测试,平均每题有 7.7 个测试。

在下面的示例中,我们将克隆人类评估仓库,安装必要的 Python 包,然后在示例数据集上运行功能正确性评估。

$ git clone https://github.com/kingabzpro/human-eval
$ pip install -e human-eval
$ evaluate_functional_correctness data/example_samples.jsonl --problem_file=data/example_problem.jsonl

我们得到了以 pass@k 表示的结果,在此例中为 pass@1。该指标表示模型在第一次尝试中的 50% 成功率。下一节我们将详细介绍这一指标。

Reading samples...
6it [00:00, 2465.79it/s]
Running test suites...
100%|█████████████████████████████████████████████| 6/6 [00:03<00:00,  1.97it/s]
Writing results to data/example_samples.jsonl_results.jsonl...
100%|██████████████████████████████████████████| 6/6 [00:00<00:00, 12035.31it/s]
{'pass@1': 0.4999999999999999}

阅读《LLM 评估:指标、方法论与最佳实践》,了解如何利用关键指标、方法论与最佳实践评估 LLM,从而做出更明智的决策。

理解 Pass@k 指标

HumanEval 不同于传统的匹配类指标。它采用一种更实用的方法——“功能正确性”,使用 pass@k 指标来评估:针对某个问题,模型生成的前 k 个代码样本中,至少有一个通过单元测试的概率。

这种从文本相似度转向功能正确性的改变,更有意义地衡量了模型解决编程挑战的能力。这一过程类似于开发者通过单元测试来验证代码正确性。

下面是计算 pass@k 指标的数学公式:

pass@k mathematical formula.

  • n:生成样本总数。
  • c:在 n 个样本中,正确样本的数量。
  • k:考虑的样本数量(前 k)。

pass@k 的计算如下:

  • C(n-c, k):从错误样本(n-c)中选择 k 个样本的组合数,表示所选 k 个样本全部错误的情形。
  • C(n,k)/C(n−c,k):该比值给出所选 k 个样本全部错误的概率。
  • 1 - C(n−c,k)/C(n,k):用 1 减去上述概率,即得到 k 个样本中至少有一个正确的概率,即 pass@k 指标。

pass@k 尤其适合评估代码生成模型的实际表现,因为它反映了在有限尝试次数内生成正确解的可能性。

该指标广泛用于竞赛环境,例如由 Papers with Code 主办的排行榜,模型会依据其 pass@k 分数进行排名,包括 pass@10 和 pass@100 等。

HumanEval 确保生成的代码在语法上正确、在功能上有效。

使用 Hugging Face Evaluate 进行 HumanEval 评估

在本项目中,我们将加载 OpenAI HumanEval 数据集,并在 codeparrot/codeparrot-small 上运行功能正确性评估。

我们选择较小的代码生成模型以加快评估速度,因为我们将运行多个循环:外层循环 164 次(问题数量),内层循环 5 次(单个问题生成的样本数)。

1. 环境准备

我们将首先安装 Hugging Face 的 Evaluate Python 库。Evaluate 是一个功能强大的库,能让您用一行代码轻松评估各领域的机器学习模型与数据集,确保在本地或分布式训练环境中的一致性与可复现性。

%%capture
%pip install evaluate

同时,设置环境变量以便我们运行模型评估。

import os
os.environ["HF_ALLOW_CODE_EVAL"] = "1"
os.environ["TOKENIZERS_PARALLELISM"] = "false"

2. 加载数据集与评估指标

我们将从 Hugging Face 加载 openai_humaneval 数据集。该数据集包含 164 个 Python 编程问题,并含有注释与文档字符串中的英文自然文本。

我们还将加载 code_eval 评估指标,以便运行 HumanEval 基准。

from datasets import load_dataset
from evaluate import load
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from tqdm import tqdm

# Load HumanEval dataset
human_eval = load_dataset("openai_humaneval")['test']

# Load code evaluation metric
code_eval_metric = load("code_eval")

3. 加载模型与分词器

从 Hugging Face Hub 加载 CodeParrot 模型与分词器。CodeParrot 是基于 GPT-2 的模型,拥有 1.1 亿参数,专门针对 Python 代码生成进行训练。

# Specify the model name or path
model_name = "codeparrot/codeparrot-small"

# Load the model and tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
model.eval()

4. 修正分词器设置

设置 pad_token_id 和 pad_token_id,如果添加了新标记则调整模型的嵌入大小。

# Set pad_token_id and pad_token_id if not already set
if tokenizer.pad_token_id is None:
    tokenizer.pad_token_id = 0  # Commonly used pad token ID
if tokenizer.eos_token_id is None:
    tokenizer.eos_token_id = 2  # Commonly used eos token ID for Llama

# Ensure the tokenizer has the pad and eos tokens
if tokenizer.pad_token is None:
    tokenizer.add_special_tokens({'pad_token': '<pad>'})
if tokenizer.eos_token is None:
    tokenizer.add_special_tokens({'eos_token': '</s>'})

# Resize model embeddings if new tokens were added
if len(tokenizer) > model.config.vocab_size:
    model.resize_token_embeddings(len(tokenizer))

5. 生成代码解答

这里是核心步骤。我们将对每个问题生成 5 个样本,总计评估 164 个问题。外层循环从数据集中提取提示与测试。基于提示,为同一问题生成 5 个不同样本。

完成后,我们将得到两个列表:test_cases 和 candidates。

  • Test Cases:直接从数据集提取的测试用例。
  • Candidates:每个问题生成的 5 个代码样本。

这种设置便于全面评估并将生成解答与测试用例进行对比。

# Set the number of candidates per problem
num_samples_per_problem = 5  # Adjust as needed for pass@k computation

# Lists to store test cases and predictions
test_cases = []
candidates = []

# Create a progress bar for the outer loop (problems)
print("Generating code solutions...")
for problem in tqdm(human_eval, desc="Problems", unit="problem"):
    prompt = problem['prompt']
    test_code = problem['test']
    # Store the test cases
    test_cases.append(test_code)

    # Generate multiple candidate solutions for each problem
    problem_candidates = []

    # Create a progress bar for the inner loop (samples per problem)
    for _ in range(num_samples_per_problem):
        # Encode the prompt and get attention mask
        inputs = tokenizer(prompt, return_tensors="pt").to(device)

        # Generate code with attention mask and proper token IDs
        with torch.no_grad():
            outputs = model.generate(
                input_ids=inputs['input_ids'],
                attention_mask=inputs['attention_mask'],
                max_length=512,
                do_sample=True,
                temperature=0.7,
                top_p=0.95,
                num_return_sequences=1,
                pad_token_id=tokenizer.pad_token_id,
                eos_token_id=tokenizer.eos_token_id,
            )
        generated_code = tokenizer.decode(outputs[0], skip_special_tokens=True)
        # Remove the prompt from the generated code
        generated_code = generated_code[len(prompt):]
        problem_candidates.append(generated_code)
    # Add the candidates for the current problem
    candidates.append(problem_candidates)

print("Code generation complete.")

输出:

Generating code solutions...

Problems: 100%|██████████| 164/164 [34:35<00:00, 12.66s/problem]

Code generation complete.

6. 计算 pass@k

现在,我们将针对不同的 k 值(1 与 5),将生成的代码样本与参考测试用例进行对比,使用 pass@k 评估指标,并输出各 k 值的成功率(百分比)。

# Compute pass@k
k_values = [1, 5]
print("Evaluating generated code...")
pass_at_k, results = code_eval_metric.compute(
    references=test_cases,
    predictions=candidates,
    k=k_values,
    num_workers=4,  # Adjust based on your system
    timeout=10.0,   # Adjust the timeout as needed
)

# Print the results
for k in k_values:
    print(f"Pass@{k}: {pass_at_k[f'pass@{k}'] * 100:.2f}%")

结果显示:在只考虑最优候选(Pass@1)时,27.07% 的生成样本通过评估标准;在考虑前五个候选(Pass@5)时,通过率为 65.85%。鉴于我们使用的是较小的语言模型来生成 Python 代码,这一表现已相当不错。

然而,与 GPT-4o 的 Pass@1 达到 90.2% 相比,差距仍较明显。

Evaluating generated code...
Pass@1: 27.07%
Pass@5: 65.85%

如果您在运行上述代码时遇到问题,请查看 使用 DeepEval 的 HumanEval Kaggle 笔记本。

您还可以通过以下教程了解如何使用 MLflow 简化 LLM 的评估流程:《使用 MLflow 评估 LLM》。

结论

HumanEval 数据集相对简单,可能只能部分代表真实世界的编程挑战。此外,还存在过拟合方面的担忧:模型可能在相似数据集上训练,从而在 HumanEval 上获得特定提升。

因此,在评估大语言模型(LLM)的代码生成能力时,务必综合考虑多种性能指标,如 MBPP(Mostly Basic Python Problems)、ClassEval、DevQualityEval 等。

完成AI Fundamentals 技能路径,获取有关 ChatGPT、大语言模型、生成式 AI 等热门话题的实用知识。

Abid Ali Awan

作者

Abid Ali Awan

作为一名持证的数据科学家,我热衷于利用前沿技术打造创新的机器学习应用。凭借在语音识别、数据分析与报告、MLOps、对话式人工智能以及自然语言处理方面的扎实背景,我不断打磨构建智能系统的能力,力求带来切实影响。除技术专长外,我也擅长沟通,能够将复杂概念提炼为清晰、简明的表述。因此,我成为数据科学领域备受关注的博主,与不断壮大的数据专业人士社区分享洞见与实践经验。目前,我专注于内容创作与编辑,借助大语言模型打造有力且吸引人的内容,帮助企业与个人更好地发挥数据价值。

主题