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不是玄学,是有方法论可循的系统工程。核心就三件事——定义清晰的输出格式、引导内部推理路径、用数据验证而不是凭感觉。希望这篇实测记录能帮你少走点弯路。