APE 自动化指令集构建与优化实践
背景介绍
Automatic Prompt Engineer (APE) 基于论文《Large Language Models Are Human-Level Prompt Engineers》(2023.3)。其核心思想是通过逆向工程,根据输入和输出样本让模型生成并寻找更优的 Prompt。这种方法旨在减少人工编写指令的成本,并利用大模型自身的能力来优化任务描述。
指令生成策略
Few-Shot 样本设计
作者基于原始输入 + 输出(部分样本仅有输出,如自由生成类任务),让大模型预测原始指令是什么。虽然论文中提到了多种模板,但实际测试表明,只要采用"Few-Shot 样本在前,待生成的指令在最后"的向前生成类型即可。
原始论文使用 text-davinci-002,每个样本使用 5 条 few-shot 样例作为上下文。在实际应用中,可以将生成指令的模型改为 ChatGPT,并根据对话模型的特点调整 Prompt 模板。ChatGPT 相比 Davinci 系列废话较多,需要明确约束其输出格式。
关键经验总结
- 抽象任务的样本多样性:对于相对抽象、偏生成类的任务,Few-Shot 样本要给够,模型才有可能猜到'无偏'的指令。例如相似新闻标题生成任务,如果样本太少,模型可能只能预测到子集指令。需要根据任务输入输出的多样性程度调整 Few-Shot 样本数。
- 构建指令样本说人话:样本中的输出应符合作务语意。例如将判断两个标题是否描述同一事件的分类任务,如果输出是'相同/不相同',模型能准确预测指令;如果输出过于模糊,模型会给出笼统的回答。
- 不要你以为,要模型以为:在医学术语标准化等任务上对比发现,合理使用的模型生成指令往往比人工编写的指令准确率更高。这可能是因为模型之间的一致性,使得模型生成的指令能提供更精准的任务描述。
指令打分机制
为了评估多组样本生成的多个候选指令的优劣,主要使用两种打分方式:
Accuracy(准确率)
使用模型预测的正确率。例如对于 QA 问题,根据不同指令在相同样本上模型回答的准确率来评价指令的效果。
Log Probability(对数概率)
使用模型预测的 logprobs 作为评价指标。这是一个 Trick:将输入 + 输出 + 指令都喂给模型,计算模型生成原始输出的概率。这解决了生成类任务解码随机性导致不同指令无法比较的问题。
获取 LogProbs 方法:调用 OpenAI 接口时设置 echo=True, logprobs=1,返回所有采样 token 的 logprobs。设置 max_tokens=0 不让模型生成新文本,即可让模型原样返回输入及对应的条件概率。
优化流程
作者加入了随机搜索:过滤低分指令,对高分指令集让模型基于特定模板生成相似指令,排序选出最优。在实际实现中,建议保留人工干预环节:针对得到的高分指令,补充自己认为缺少核心信息后,再次使用 Log Prob 打分评估是否有提升。
代码实现示例
以下是一个简化的 Python 实现框架,展示了如何调用 API 进行指令生成与打分。
import openai
import os
from typing import List, Dict
client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def generate_instruction(input_data: str, output_data: str, few_shots: List[Dict]) -> str:
"""
基于 Few-Shot 样本生成指令
"""
prompt_template = """
请根据以下输入输出样本,推断出最可能的系统指令。
样本 1:
输入:{input_1}
输出:{output_1}
样本 2:
输入:{input_2}
输出:{output_2}
... (更多 Few-Shot)
当前样本:
输入:{input}
输出:{output}
请仅输出指令内容,不要包含其他解释。
"""
formatted_few_shots = "\n".join([f"样本{i+1}:\n输入:{s['in']}\n输出:{s['out']}" for i, s in enumerate(few_shots)])
full_prompt = prompt_template.format(
input=input_data,
output=output_data,
**{f"input_{i+1}": s['in'], f"output_{i+1}": s['out'] for i, s in enumerate(few_shots)}
)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": full_prompt}],
temperature=0.7
)
return response.choices[0].message.content
def score_instruction(instruction: str, input_data: str, output_data: str) -> float:
"""
使用 Log Probability 评分
"""
prompt = f"""{instruction}\n输入:{input_data}\n输出:{output_data}"""
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
echo=True,
logprobs=True,
max_tokens=0
)
total_logprob = sum(token.logprob for token in response.choices[0].logprobs.content_tokens)
return total_logprob
if __name__ == "__main__":
samples = [
{"in": "输入 A", "out": "输出 A"},
{"in": "输入 B", "out": "输出 B"}
]
instruction = generate_instruction("输入 X", "输出 X", samples)
score = score_instruction(instruction, "输入 X", "输出 X")
print(f"Generated Instruction: {instruction}")
print(f"Score: {score}")
效果验证
作者在 24 个指令任务上进行了测试,每类任务挑选 5 对样本,使用上述方案得到最优指令,再在剩余样本上与人工指令及 Greedy 方案(无搜索和打分排序的 APE)进行对比。结果显示,APE 的效果在大部分任务上可以打平甚至超越人工指令。在 BigBench 等高难度样本上,APE 在 17/21 个任务上也超越了人工指令。
医疗数据集案例
在 4 个医学数据集上,APE + 人工优化得到的最优指令表现如下:
| 任务 | 优化后的指令 |
|---|
| 搜索意图 | 生成医学相关问题的答案。给定一个输入问题,需要根据问题生成相应的输出答案。答案包括临床表现、病因、治疗方法、作用、定义等等,如果有多个问题,返回多问 |
| 医疗术语标准化 | 将医学手术名称的术语表述标准化。输入是医学手术的名称,输出是对该手术的名称进行修正、标准化,以供医学专业人员更好地理解 |
| 医疗药物功能实体抽取 | 给定药品信息和用途说明,根据用途说明提取出药品的主治功能 |
| 医疗文献 QA 生成 | 训练一个问答系统,给定一些医学文本,能够回答用户提问关于该文本内容的问题。每个输入 - 输出对是一组文本和对应的问题及答案。输出的形式是 Json 格式 |
以医学术语标准化为例,通过简化应用界面,可以直接对生成的指令做修改,再 Eval 效果,实现了人机协同的指令优化闭环。
总结
APE 提供了一种自动化的 Prompt 工程路径。通过 Few-Shot 引导生成、LogProb 精确打分以及人工微调,可以有效提升大模型在特定任务上的表现。关键在于理解模型的解码特性,并提供足够多样且语义清晰的样本数据。