1. 问题背景:一个看似简单的情感分类任务
上个月接了个需求:对电商平台的用户评论做情感极性分类(正面/负面/中性),数据量日均5万条。一开始觉得这任务简单,直接用text-davinci-003时代的套路——写个"Classify the sentiment: {text}"就完事。
结果上线第一天就翻车了。准确率只有67%,而且大量"中性"评论被误判。翻看日志发现几个典型问题:
- "这个价格还行吧,但质量一般" → 模型输出Positive(只看到了"还行")
- "物流很快,但包装破损" → 模型输出Negative(只看到了"破损")
- 大量评论输出格式不统一,有的返回
positive,有的返回Positive,还有的返回"positive",甚至偶尔带解释文字
这三个问题分别对应Prompt工程中的:上下文忽略、判断标准缺失、输出格式失控。于是我开始系统性地尝试不同Prompt策略,用同一批500条人工标注数据做评估。
2. 环境与版本:技术栈固定变量
为了控制变量,整个实验固定以下环境:
# 环境固定
import openai
openai.api_version = "2024-02-15-preview"
model = "gpt-4o-2024-05-13" # 固定模型版本
temperature = 0.0 # 关闭随机性,保证可复现
max_tokens = 100 # 限制输出长度
评测数据:500条人工标注评论,分布为 正:负:中 = 3:3:2。所有测试在同一批数据、同一天、同一API Key下执行,避免模型更新带来的误差。
3. 方案设计:五种Prompt策略对比矩阵
| 策略编号 | 策略名称 | 核心设计 | 预期效果 |
|---|---|---|---|
| P0 | 零样本基线 | "Classify: {text}" |
作为下限参考 |
| P1 | 少样本 | 添加3个examplars | 提升格式稳定性 |
| P2 | CoT思维链 | 要求"先分析再判断" | 提升复杂句准确率 |
| P3 | 角色+XML约束 | 设定"资深NLP标注员"角色,强制XML输出 | 解决格式失控 |
| P4 | 组合方案 | P3 + P2 + 评分标准定义 | 综合最优 |
每个Prompt重复运行3次取平均值(虽然temperature=0,但API有微小波动)。
4. 核心实现:代码与Prompt详细拆解
4.1 零样本基线(P0)—— 惨不忍睹
def run_prompt(prompt_template, text):
response = openai.ChatCompletion.create(
model="gpt-4o-2024-05-13",
messages=[{"role": "system", "content": prompt_template["system"]},
{"role": "user", "content": prompt_template["user"].format(text=text)}],
temperature=0.0,
max_tokens=100
)
return response.choices[0].message.content
# P0: 零样本
p0_system = "You are a sentiment classifier."
p0_user = "Classify the sentiment of this review: {text}"
结果:准确率67%,格式错误率15%(返回了Positive. The reviewer likes...这种带解释的),中性评论的召回率仅有41%。
4.2 角色+XML+CoT组合方案(P4)—— 最终优化版
# P4: 组合优化方案
p4_system = """你是一位具有10年经验的NLP标注专家。你的任务是对电商评论进行情感极性分类。
分类规则:
1. 只输出XML格式,无任何额外说明
2. 情感标签仅限:positive / negative / neutral
3. 对于混合情感(如"物流快但质量差"),以**主情感**为准,并注明冲突点
4. 必须按以下步骤思考(不要在输出中展示):
a. 提取所有情感词及其情感权重
b. 判断是否有转折词(但、然而、不过)
c. 如果存在转折词,以转折后的情感为准
d. 确定最终极性
XML输出模板:
positive/negative/neutral
无冲突/具体冲突描述
0-1的浮点数
"""
p4_user = "{text}"
# 调用方式相同,效果天壤之别
这个Prompt的关键设计点:
- XML结构化输出:从根上解决了格式混乱问题,后续解析用
xml.etree.ElementTree直接处理,不需要正则匹配 - CoT步骤内化:让模型在内部推理,但输出保持简洁——这一步让准确率提升最大
- 冲突点显式化:模型被要求"承认"矛盾,反而减少了对次情感的误判
5. 踩坑与优化:三个真实教训
5.1 教训一:少样本不等于多示例
P1策略中我放了5个examplars,想着多多益善。结果token消耗暴涨87%(从约120 tokens/条涨到225 tokens/条),准确率只从67%→71%。后来发现问题是示例分布不均:3个正面、1个负面、1个中性,导致模型对中性评论的偏好更低了。
优化:示例压缩到3个且严格平衡(1:1:1),token消耗降至180,准确率反而到了74%。
5.2 教训二:温度参数不是摆设
一开始为了"创造性"设了temperature=0.3,结果同一个例子跑两次结果不一样。对于分类任务必须temperature=0。另外max_tokens设200时,模型偶尔会在XML后面补一句"希望这个答案对你有帮助"——设成100刚好够输出XML,也截断了废话。
5.3 教训三:系统提示词里的"不要"是无效指令
我在P3早期版本写了"不要输出任何解释",结果模型照样解释。改成"只输出XML格式"并给出模板后,才真正生效。正面指令永远优于负面指令——这是这个项目学到的最大教训。
6. 效果数据:最终对比与成本测算
| 策略 | 准确率 | 格式错误率 | 平均Token/条 | 成本/万条(USD) |
|---|---|---|---|---|
| P0零样本 | 67% | 15% | 118 | $2.36 |
| P1少样本 | 71% | 6% | 225 | $4.50 |
| P2 CoT | 78% | 10% | 156 | $3.12 |
| P3 角色+XML | 85% | 0% | 132 | $2.64 |
| P4 组合 | 92% | 0% | 148 | $2.96 |
关键结论:
- P4比P0准确率提升25个百分点,成本仅增加25%
- 格式错误率从15%降到0,这意味着下游解析代码可以直接删掉所有容错逻辑
- 中性评论召回率从41%→87%,主要是CoT步骤中"转折词检测"起了决定性作用
最终上线用了P4方案,用gpt-4o-mini(成本降低60%)复测准确率为88%,也远高于基线。如果你也在做类似的任务,建议直接从P4的Prompt框架起步,然后针对自己的数据微调冲突点描述。
最后说点题外话:Prompt Engineering不是玄学,是有方法论可循的系统工程。核心就三件事——定义清晰的输出格式、引导内部推理路径、用数据验证而不是凭感觉。希望这篇实测记录能帮你少走点弯路。