📌 内容摘要

  • 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 的数字。