📌 内容摘要
- Claude 做文本分类的准确率取决于四个因素:类别定义清晰度、示例质量、类别数量、文本歧义程度。
- 本文通过真实测试对比三种方案:Zero-shot、Few-shot、Chain-of-thought,给出适用场景和准确率区间。
- 重点覆盖生产中最棘手的三个问题:类别边界模糊、多标签分类、置信度获取与校准。
- 附完整的评估框架代码,帮你为自己的数据集测量实际准确率,而不是依赖别人的 benchmark。
一、先说结论:准确率能到多少?
直接回答:没有一个通用的数字。Claude 的文本分类准确率高度依赖任务特性,以下是基于真实场景的参考区间:
| 场景 | 准确率区间 | 主要影响因素 |
|---|---|---|
| 情感分析(正/负/中性) | 90–96% | 类别少、边界清晰 |
| 客服意图分类(5–10类) | 85–93% | 类别定义质量和边界模糊程度 |
| 新闻主题分类(20+类) | 80–90% | 类别数量、交叉类别 |
| 多标签分类(同时多个标签) | 75–88% | 标签组合复杂度 |
| 领域专属分类(业务自定义) | 70–90% | Few-shot 示例质量是关键杠杆 |
Claude 的分类准确率很少是”模型能力”的瓶颈,更多是”任务定义”的问题。类别定义模糊、边界案例没有处理、没有给示例——这些 Prompt 工程问题往往比换模型更值得先解决。
二、基础实现:三种方案对比
方案一:Zero-shot 分类
import anthropic
import json
client = anthropic.Anthropic()
def classify_zero_shot(
text: str,
categories: list[str],
descriptions: dict[str, str] = None, # 可选:每个类别的描述
) -> dict:
"""
Zero-shot 文本分类
Args:
text: 要分类的文本
categories: 类别列表
descriptions: 类别描述字典,如 {"正面": "表达满意、赞扬、喜爱的内容"}
Returns:
{"category": str, "raw": str}
"""
# 构建类别说明
if descriptions:
cat_lines = "\n".join(
f"- {cat}:{descriptions.get(cat, '')}"
for cat in categories
)
else:
cat_lines = "\n".join(f"- {cat}" for cat in categories)
prompt = f"""将以下文本分类到给定类别中。
类别:
{cat_lines}
文本:
{text}
只输出类别名称,不要任何解释。类别必须是上面列出的之一。"""
response = client.messages.create(
model = "claude-haiku-4-5-20251001", # 分类用 Haiku,速度快成本低
max_tokens = 32,
temperature= 0, # 分类任务用零温度,确保一致性
messages = [{"role": "user", "content": prompt}],
)
result = response.content[0].text.strip()
# 验证输出是否在类别列表里
# 处理可能的格式变化(如加了引号、额外空格)
for cat in categories:
if cat in result or result in cat:
return {"category": cat, "raw": result}
# 如果没有完全匹配,找最相似的
return {"category": result, "raw": result, "unmatched": True}
# 测试
test_cases = [
"这款手机的拍照功能超级好,电池续航也完全满足我的需求,强烈推荐!",
"快递太慢了,等了七天才到,包装也有损坏,很失望。",
"东西收到了,和描述基本一致,价格还算合理。",
]
for text in test_cases:
result = classify_zero_shot(
text=text,
categories=["正面", "负面", "中性"],
descriptions={
"正面": "表达满意、赞扬、喜爱、推荐的内容",
"负面": "表达不满、批评、失望、投诉的内容",
"中性": "客观描述,既无明显好评也无差评"
}
)
print(f"文本:{text[:30]}...")
print(f"分类:{result['category']}\n")
方案二:Few-shot 分类(推荐)
def classify_few_shot(
text: str,
categories: list[str],
examples: list[dict], # [{"text": "...", "label": "..."}]
) -> dict:
"""
Few-shot 文本分类
通过示例告诉 Claude 你对每个类别的具体期望
最佳实践:
- 每个类别至少2个示例
- 示例要覆盖边界情况(容易混淆的样本)
- 示例标签必须100%准确
"""
cat_list = "、".join(categories)
# 构建示例文本
examples_text = "\n".join(
f"文本:{ex['text']}\n分类:{ex['label']}"
for ex in examples
)
prompt = f"""你是一个文本分类专家。
将文本分类到以下类别之一:{cat_list}
参考示例:
{examples_text}
现在请分类:
文本:{text}
分类:"""
response = client.messages.create(
model = "claude-haiku-4-5-20251001",
max_tokens = 32,
temperature= 0,
messages = [{"role": "user", "content": prompt}],
)
result = response.content[0].text.strip()
# 精确匹配
for cat in categories:
if result == cat or result.startswith(cat):
return {"category": cat, "raw": result}
return {"category": result, "raw": result}
# 完整示例:客服工单意图分类
categories = ["退款申请", "物流查询", "产品咨询", "投诉建议", "账号问题"]
examples = [
# 每类至少2个示例,包含边界情况
{"text": "我要申请退款,东西不好用", "label": "退款申请"},
{"text": "买了三天想退货,可以吗", "label": "退款申请"},
{"text": "我的包裹到哪里了,发货多少天了", "label": "物流查询"},
{"text": "订单显示已发货但没有物流信息", "label": "物流查询"},
{"text": "这款产品适合敏感肌肤吗", "label": "产品咨询"},
{"text": "请问这个和另一款有什么区别", "label": "产品咨询"},
{"text": "客服态度太差了,要投诉", "label": "投诉建议"},
{"text": "建议你们改善一下包装", "label": "投诉建议"},
{"text": "我的账号登不上去了", "label": "账号问题"},
{"text": "忘记密码了怎么找回", "label": "账号问题"},
# 边界案例:同时涉及物流和投诉
{"text": "快递公司把我的东西弄坏了,要投诉他们", "label": "投诉建议"},
]
test_texts = [
"我的快递三天了还没到",
"这个产品能退吗,用了一次感觉不适合我",
"账号被盗了,我没有操作",
]
for text in test_texts:
result = classify_few_shot(text, categories, examples)
print(f"文本:{text}")
print(f"意图:{result['category']}\n")
方案三:Chain-of-thought 分类(高准确率)
def classify_with_reasoning(
text: str,
categories: list[str],
descriptions: dict[str, str],
) -> dict:
"""
带推理过程的分类
适合:类别边界模糊、需要高准确率、可以接受稍高延迟的场景
优点:准确率提升 3–8%,可以看到分类依据便于调试
缺点:生成 token 更多(成本约 3–5 倍)
"""
cat_desc = "\n".join(
f"- {cat}:{descriptions[cat]}"
for cat in categories
)
cat_list = "、".join(categories)
prompt = f"""请分析以下文本并分类。
可选类别:
{cat_desc}
文本:
{text}
请先简要分析文本的关键信息(1–2句),
然后在最后一行只输出类别名称(必须是:{cat_list} 之一)。
格式:
分析:[你的分析]
类别:[类别名称]"""
response = client.messages.create(
model = "claude-sonnet-4-6", # CoT 分类用 Sonnet,推理更强
max_tokens = 256,
temperature= 0,
messages = [{"role": "user", "content": prompt}],
)
raw = response.content[0].text.strip()
# 提取最后的类别行
category = ""
reasoning = ""
for line in raw.split("\n"):
if line.startswith("类别:"):
category = line.replace("类别:", "").strip()
elif line.startswith("分析:"):
reasoning = line.replace("分析:", "").strip()
# 验证类别
matched = next((c for c in categories if c == category), None)
return {
"category": matched or category,
"reasoning": reasoning,
"raw": raw,
"matched": bool(matched),
}
# 测试边界案例
ambiguous_text = "收到货了,东西还行,就是比我想象中小一点,不过价格也不贵"
result = classify_with_reasoning(
text=ambiguous_text,
categories=["好评", "差评", "中评"],
descriptions={
"好评": "总体满意,推荐购买",
"差评": "不满意,不推荐或要求退换",
"中评": "有优点有缺点,基本满足需求但有改进空间",
}
)
print(f"分类:{result['category']}")
print(f"推理:{result['reasoning']}")
三、多标签分类
def classify_multi_label(
text: str,
labels: list[str],
descriptions: dict[str, str] = None,
min_labels: int = 1,
max_labels: int = 3,
) -> dict:
"""
多标签分类:一段文本可以同时属于多个类别
使用场景:
- 新闻文章可以同时是"科技"和"商业"
- 评论可以同时提到"物流"和"包装"问题
- 邮件可以同时需要"技术支持"和"账单查询"
"""
labels_str = "、".join(labels)
desc_section = ""
if descriptions:
desc_lines = "\n".join(f"- {l}:{descriptions[l]}" for l in labels)
desc_section = f"\n标签说明:\n{desc_lines}\n"
prompt = f"""分析以下文本,从给定标签中选择所有适用的标签。
可选标签:{labels_str}
{desc_section}
规则:
- 选择 {min_labels} 到 {max_labels} 个最相关的标签
- 只选择文本中有明确证据支撑的标签
- 以 JSON 数组格式返回,如:["标签1", "标签2"]
- 只返回 JSON,不要其他内容
文本:
{text}"""
response = client.messages.create(
model = "claude-sonnet-4-6",
max_tokens = 128,
temperature= 0,
messages = [{"role": "user", "content": prompt}],
)
raw = response.content[0].text.strip()
# 解析 JSON
try:
# 处理可能的 markdown 代码块
if raw.startswith("```"):
raw = "\n".join(raw.split("\n")[1:-1])
selected = json.loads(raw)
# 过滤确保都在合法标签里
validated = [l for l in selected if l in labels]
return {"labels": validated, "raw": raw}
except json.JSONDecodeError:
# 备用:从文本里提取标签名
found = [l for l in labels if l in raw]
return {"labels": found, "raw": raw, "parse_error": True}
# 测试
article = """
苹果公司今天发布了最新款 MacBook Pro,搭载自研芯片性能大幅提升。
同时,苹果股价在消息公布后上涨了3.2%,市值创历史新高。
分析师认为这款产品将进一步巩固苹果在高端PC市场的地位。
"""
result = classify_multi_label(
text = article,
labels = ["科技", "商业", "金融", "产品发布", "市场分析", "政治"],
max_labels = 4,
)
print(f"标签:{result['labels']}")
# 预期输出:["科技", "商业", "金融", "产品发布"]
四、置信度获取与校准
def classify_with_confidence(
text: str,
categories: list[str],
examples: list[dict] = None,
) -> dict:
"""
获取置信度的分类方案
注意:Claude 不直接输出概率,这里用的是
"让模型自我评估确定性"的提示方式获得置信度估计
"""
cat_list = "、".join(categories)
examples_str = ""
if examples:
examples_str = "参考示例:\n" + "\n".join(
f"文本:{e['text']} → {e['label']}" for e in examples[:4]
) + "\n\n"
prompt = f"""{examples_str}将以下文本分类,并评估你的确定程度。
可选类别:{cat_list}
文本:{text}
以 JSON 格式返回:
{{
"category": "类别名",
"confidence": 0到1的数值(1表示完全确定,0.5表示不确定),
"reason": "一句话说明分类依据"
}}
只返回 JSON。"""
response = client.messages.create(
model = "claude-sonnet-4-6",
max_tokens = 200,
temperature= 0,
messages = [{"role": "user", "content": prompt}],
)
raw = response.content[0].text.strip()
if raw.startswith("```"):
raw = "\n".join(raw.split("\n")[1:-1])
try:
data = json.loads(raw)
# 验证类别
cat = data.get("category", "")
matched = next((c for c in categories if c == cat), None)
return {
"category": matched or cat,
"confidence": float(data.get("confidence", 0.5)),
"reason": data.get("reason", ""),
"valid": bool(matched),
}
except Exception:
return {"category": raw, "confidence": 0.5, "valid": False}
def batch_classify_with_routing(
texts: list[str],
categories: list[str],
examples: list[dict],
low_conf_threshold: float = 0.6,
) -> list[dict]:
"""
带置信度路由的批量分类
低置信度的结果升级到 Sonnet 重新分类,或标记为"需人工审核"
"""
results = []
haiku_count = sonnet_count = 0
for text in texts:
# 先用 Haiku 快速分类
result = classify_with_confidence(text, categories, examples)
haiku_count += 1
if result["confidence"] < low_conf_threshold:
# 置信度低:用 Sonnet + CoT 重新分类
cot_result = classify_with_reasoning(
text, categories,
{c: "" for c in categories}
)
result["category"] = cot_result["category"]
result["confidence"] = 0.75 # CoT 通常更准,估计更高置信度
result["upgraded"] = True
sonnet_count += 1
else:
result["upgraded"] = False
result["text"] = text[:50] + "..."
results.append(result)
print(f"分类完成:Haiku {haiku_count} 次,Sonnet 升级 {sonnet_count} 次")
return results
五、评估框架:测量你自己数据集的真实准确率
from collections import defaultdict, Counter
import time
def evaluate_classifier(
test_data: list[dict], # [{"text": "...", "label": "真实标签"}]
classifier: callable, # 分类函数,接受 text 返回 {"category": str}
categories: list[str],
verbose: bool = True,
) -> dict:
"""
全面评估分类器性能
计算指标:
- 总体准确率(Accuracy)
- 每类精确率(Precision)、召回率(Recall)、F1
- 混淆矩阵
- 耗时统计
"""
predictions = []
labels = []
latencies = []
for item in test_data:
start = time.time()
result = classifier(item["text"])
elapsed = (time.time() - start) * 1000
predictions.append(result["category"])
labels.append(item["label"])
latencies.append(elapsed)
# ── 计算指标 ────────────────────────────────────
# 总体准确率
correct = sum(1 for p, l in zip(predictions, labels) if p == l)
accuracy = correct / len(labels)
# 每类指标
per_class = {}
for cat in categories:
tp = sum(1 for p, l in zip(predictions, labels) if p == cat and l == cat)
fp = sum(1 for p, l in zip(predictions, labels) if p == cat and l != cat)
fn = sum(1 for p, l in zip(predictions, labels) if p != cat and l == cat)
precision = tp / (tp + fp) if (tp + fp) > 0 else 0
recall = tp / (tp + fn) if (tp + fn) > 0 else 0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
per_class[cat] = {
"precision": round(precision, 4),
"recall": round(recall, 4),
"f1": round(f1, 4),
"support": sum(1 for l in labels if l == cat),
}
# 宏平均 F1
macro_f1 = sum(m["f1"] for m in per_class.values()) / len(categories)
# 混淆矩阵
confusion = defaultdict(Counter)
for pred, true in zip(predictions, labels):
confusion[true][pred] += 1
# ── 打印报告 ────────────────────────────────────
if verbose:
print(f"\n{'='*60}")
print(f"分类评估报告")
print(f"{'='*60}")
print(f"样本数:{len(test_data)}")
print(f"总体准确率:{accuracy:.2%}")
print(f"宏平均 F1:{macro_f1:.4f}")
print(f"平均延迟:{sum(latencies)/len(latencies):.0f}ms")
print(f"\n每类指标:")
print(f"{'类别':<12} {'Precision':>10} {'Recall':>8} {'F1':>8} {'样本数':>6}")
print("-" * 50)
for cat, m in per_class.items():
print(f"{cat:<12} {m['precision']:>10.3f} {m['recall']:>8.3f} {m['f1']:>8.3f} {m['support']:>6}")
# 找出容易混淆的类别对
print(f"\n高频混淆(预测→真实,次数>1):")
for true_label, preds in confusion.items():
for pred_label, count in preds.items():
if pred_label != true_label and count > 1:
print(f" 预测"{pred_label}" 但实际是"{true_label}":{count} 次")
return {
"accuracy": accuracy,
"macro_f1": macro_f1,
"per_class": per_class,
"confusion": dict(confusion),
"avg_latency": sum(latencies) / len(latencies),
}
# 使用示例
import functools
# 把 classify_few_shot 包装成只接受 text 的函数
classifier_fn = functools.partial(
classify_few_shot,
categories = ["正面", "负面", "中性"],
examples = [
{"text": "非常好用,推荐", "label": "正面"},
{"text": "很满意,下次还会买", "label": "正面"},
{"text": "差评,根本不好用", "label": "负面"},
{"text": "太让人失望了", "label": "负面"},
{"text": "还行,一般般", "label": "中性"},
{"text": "中规中矩,没有惊喜", "label": "中性"},
]
)
# 准备测试集(你自己的标注数据)
test_data = [
{"text": "质量超出预期,太棒了", "label": "正面"},
{"text": "物流很慢,东西倒还好", "label": "中性"},
{"text": "假货,不推荐", "label": "负面"},
{"text": "包装精美,用起来很顺手", "label": "正面"},
{"text": "性价比还行,凑合用吧", "label": "中性"},
{"text": "完全不值这个价,后悔购买", "label": "负面"},
# 边界案例
{"text": "快递很快,但东西有点小瑕疵", "label": "中性"},
{"text": "送人很有面子,就是贵了点", "label": "正面"},
]
metrics = evaluate_classifier(
test_data = test_data,
classifier = classifier_fn,
categories = ["正面", "负面", "中性"],
)
六、批量分类与成本优化
def batch_classify_efficient(
texts: list[str],
categories: list[str],
examples: list[dict],
batch_size: int = 10, # 每次请求处理多少条(降低 API 调用次数)
) -> list[str]:
"""
批量分类优化:一次请求处理多条文本
比逐条调用效率高 5–10 倍,成本降低约 60%(减少重复的 prompt token)
注意:batch_size 不要设太大(>20),输出格式可能不稳定
"""
all_results = []
cat_list = "、".join(categories)
examples_str = "\n".join(
f"文本:{e['text']} → {e['label']}" for e in examples[:4]
)
for i in range(0, len(texts), batch_size):
batch = texts[i:i + batch_size]
# 构建批量输入
numbered = "\n".join(
f"{j+1}. {text}" for j, text in enumerate(batch)
)
prompt = f"""将以下文本分别分类为:{cat_list}
参考示例:
{examples_str}
待分类文本:
{numbered}
以 JSON 数组返回每条文本的分类结果,顺序与输入一致:
["类别1", "类别2", ...]
只返回 JSON 数组。"""
response = client.messages.create(
model = "claude-haiku-4-5-20251001",
max_tokens = batch_size * 20, # 每条结果约 5–15 token
temperature= 0,
messages = [{"role": "user", "content": prompt}],
)
raw = response.content[0].text.strip()
if raw.startswith("```"):
raw = "\n".join(raw.split("\n")[1:-1])
try:
batch_results = json.loads(raw)
# 验证数量是否匹配
if len(batch_results) != len(batch):
# 数量不匹配,逐条补充
while len(batch_results) < len(batch):
batch_results.append(categories[0])
all_results.extend(batch_results[:len(batch)])
except json.JSONDecodeError:
# 解析失败,用 "未知" 占位并记录
print(f"批次 {i//batch_size + 1} 解析失败,原始输出:{raw[:100]}")
all_results.extend(["未知"] * len(batch))
return all_results
# 成本对比
texts_100 = ["测试文本"] * 100
# 方案A:逐条调用(100次 API 调用)
# 方案B:批量调用(10次 API 调用,每次10条)
# 方案C:Anthropic Batch API(异步,打5折)
# 实际项目推荐:
# - 实时场景:批量调用(batch_size=5–10)
# - 离线批量:Anthropic Batch API(最省钱)
results = batch_classify_efficient(
texts = texts_100[:20], # 测试20条
categories = ["正面", "负面", "中性"],
examples = [
{"text": "很好用", "label": "正面"},
{"text": "不好用", "label": "负面"},
{"text": "一般般", "label": "中性"},
],
batch_size = 10,
)
print(f"分类完成,共 {len(results)} 条结果")
七、提升准确率的实用技巧
技巧一:类别定义要有排他性
# 模糊定义(容易混淆)
categories = ["客户投诉", "产品问题", "服务问题"]
# 问题:产品问题和客户投诉经常重叠,服务问题边界不清
# 清晰定义(有排他边界)
categories = ["退款退货", "物流配送", "产品质量", "账号权限", "其他"]
# 加上定义:
descriptions = {
"退款退货": "用户明确要求退款、退货或换货",
"物流配送": "关于发货时间、快递状态、地址问题(不包含退货)",
"产品质量": "产品本身的缺陷、与描述不符(不包含要求退款的)",
"账号权限": "登录、密码、权限、个人信息",
"其他": "以上类别都不符合时使用",
}
技巧二:为 Claude 提供"困难案例"示例
# 不只是典型案例,更要给边界案例
examples = [
# 典型正面
{"text": "好用!强烈推荐!", "label": "正面"},
# 典型负面
{"text": "很差,不推荐。", "label": "负面"},
# ⬇️ 这些才是真正有价值的边界示例
# "有优点有缺点" → 中性
{"text": "价格贵了点但质量确实好", "label": "中性"},
# "表面差评实际中性" → 中性
{"text": "没想象中好,不过也还凑合", "label": "中性"},
# "表面说好但暗含不满" → 中性
{"text": "东西倒是不错,就是要等这么久", "label": "中性"},
]
技巧三:对模型说"不确定时怎么做"
prompt_suffix = """ 特别说明: - 如果文本同时符合多个类别,选择最主要的那个 - 如果文本内容与分类无关(如空白、乱码),输出:无法分类 - 如果有多个明显的情感倾向,以整体印象为准"""
常见问题
Q:什么时候应该用 Claude 分类,什么时候用传统 ML 模型?
Claude 的优势是:类别定义可以随时改(不需要重新训练)、可以用自然语言描述复杂分类逻辑、少量示例(甚至零示例)就能工作。传统 ML 的优势是:大量标注数据时准确率可以更高、推理成本低 100 倍以上、延迟可以做到毫秒级。建议:标注数据少于 1000 条、需要快速迭代类别定义时用 Claude;标注数据充足、调用量大(每天百万次以上)时用微调后的小模型。
Q:中文文本和英文文本分类准确率有差距吗?
差距不大。Claude 4 系列的中文理解能力已经非常成熟,对于通用的情感分析、意图识别等任务,中英文表现相近。在高度专业的垂直领域(如法律、医疗)、方言/网络用语密集的场景,中文表现可能略低于英文,这时候增加该领域的 Few-shot 示例是最有效的提升方式。
Q:类别数量增加到 50 个以上,准确率会大幅下降吗?
会有明显影响,但不是灾难性的。类别增多时,建议两个优化:一是对类别进行层级分组(先分大类再分小类,两步走),二是为每个类别提供清晰的描述和边界案例示例。实测中,20 个类别的准确率比 5 个类别通常低 5–10%,50 个类别比 5 个类别低 15–25%,主要损失在容易混淆的相似类别上。
总结
Claude 文本分类的准确率瓶颈大多数时候不在模型能力,而在任务定义。三个最有效的提升杠杆:给类别清晰的排他性定义(消除边界模糊)、提供覆盖边界案例的高质量 Few-shot 示例、对低置信度结果做升级路由(Haiku 初筛 + Sonnet 精判)。用本文的评估框架在你自己的数据集上测量准确率,才能知道哪个优化方向的收益最大——而不是依赖通用 benchmark 的数字。