Skip to content
教程阅读约需 11 分钟

Solo Mode:一个标注者如何标注 10,000 个样本

Potato Solo Mode 分步教程:先自己标注一个种子集,其余交给 LLM 标注,再用准确率检查决定何时停手。

Potato Team

你有 10,000 条产品评论要做情感标注(正面、中性、负面)。雇三名标注者把全部数据标完要花几周时间和几千美元。Solo Mode 让一位领域专家只标注 500-1,000 个实例就拿到相当的质量;其余交给 LLM,而 LLM 拿不准的那些由人来审。

本教程把整个流程走一遍。


你需要准备什么

  • Potato 2.3.0 或更高版本,并安装 LLM 提供商所需的 AI 扩展:pip install potato-annotation[ai]
  • 一个 OpenAI 或 Anthropic API 密钥(用于 LLM 组件)
  • JSONL 格式的数据集
  • 一位懂行的标注者(可以是你自己)

第 1 步:准备数据

创建 data/reviews.jsonl,每行一条评论:

json
{"id": "rev_001", "text": "Absolutely love this product! Best purchase I've made all year.", "source": "amazon"}
{"id": "rev_002", "text": "It works fine. Nothing special but gets the job done.", "source": "amazon"}
{"id": "rev_003", "text": "Broke after two weeks. Complete waste of money.", "source": "amazon"}
{"id": "rev_004", "text": "The quality is decent for the price point. I might buy again.", "source": "amazon"}
{"id": "rev_005", "text": "Arrived damaged and customer service was unhelpful.", "source": "amazon"}

在本教程里,就当这个文件装着 10,000 条评论。


第 2 步:创建配置

创建 config.yaml:

yaml
annotation_task_name: "Product Review Sentiment (Solo Mode)"
task_dir: "."
 
data_files:
  - "data/reviews.jsonl"
 
item_properties:
  id_key: id
  text_key: text
 
# --- Solo Mode Configuration ---
solo_mode:
  enabled: true
 
  # The model that labels. A list, so you can give fallbacks.
  labeling_models:
    - endpoint_type: openai
      model: "gpt-4o"
      api_key: ${OPENAI_API_KEY}
      temperature: 0.1
      max_tokens: 1024
 
  embedding:
    model_name: "all-MiniLM-L6-v2"
 
  uncertainty:
    strategy: direct_confidence
 
  # When you are allowed to stop labeling
  thresholds:
    end_human_annotation_agreement: 0.93
    minimum_validation_sample: 50
    confidence_low: 0.5
    confidence_high: 0.85
    periodic_review_interval: 100
 
  # What decides which instance you see next
  instance_selection:
    low_confidence_weight: 0.30
    disagreement_weight: 0.25
    diversity_weight: 0.20
    llm_predicted_weight: 0.15
    random_weight: 0.10
 
  batches:
    llm_labeling_batch: 25
    max_parallel_labels: 200
 
  labeling_functions:
    enabled: true
    auto_extract: true
    max_functions: 15
    min_confidence: 0.92
    min_coverage: 3
 
  edge_case_rules:
    enabled: true
    auto_extract_on_labeling: true
 
  confusion_analysis:
    enabled: true
    min_instances_for_pattern: 3
    max_patterns: 20
 
  prompt_optimization:
    enabled: true
    target_accuracy: 0.93
 
  refinement_loop:
    enabled: true
    trigger_interval: 50
    min_improvement: 0.02
    max_cycles: 3
 
  state_dir: "solo_state_reviews"
 
# --- Annotation Schema ---
annotation_schemes:
  - annotation_type: radio
    name: sentiment
    description: "What is the overall sentiment of this review?"
    labels:
      - "Positive"
      - "Neutral"
      - "Negative"
    label_requirement:
      required: true
    sequential_key_binding: true
 
output_annotation_dir: "output/"
export_annotation_format:
  - jsonl
  - parquet

第 3 步:启动服务器

bash
potato start config.yaml -p 8000

打开 http://localhost:8000 并登录。Solo Mode 面板出现,显示项目处于设置状态,等你审阅它合成出来的提示。


第 4 步:并行标注 -- 你的前 50 个

Potato 用基于嵌入的聚类挑出了 50 条多样化的评论。它们不是随机选的,而是尽可能广地覆盖你的数据分布。

逐条标注。这是最要紧的阶段,因为种子标签的质量决定了 LLM 能学到的上限。慢慢来,保持一致。

预计时间: 每个实例 20-30 秒,共 15-25 分钟。

标够一定数量之后,LLM 不用你吩咐就会和你并行标注起来。


第 5 步:LLM 追上进度

这个阶段自己跑。Potato 把一批 200 个实例连同你的 50 个种子标签作为少样本示例一起发给 LLM,然后拿预测结果和 10 个留出的种子标签比对,估算基线准确率。

面板里会出现一个进度指示。视 LLM 提供商而定,通常要 1-2 分钟。

典型结果: 第一次校准时 LLM 的准确率落在 75-85%。这是预料之中的,它还没学会你的标注风格。


第 6 步:混淆分析

Potato 显示一个混淆矩阵,展示 LLM 与你的标签不一致的地方。典型输出:

text
Confusion Analysis (Round 1)
============================
Overall Accuracy: 0.82 (target: 0.93)

Top Confusion Pairs:
  Neutral -> Positive:  14 instances (7.0%)
  Negative -> Neutral:   9 instances (4.5%)
  Positive -> Neutral:   4 instances (2.0%)

这指出了 LLM 在这里的主要弱点:它总把中性评论升格成正面。这种情况很常见,因为 LLM 往往偏向正面。

你的操作: 把混淆对逐个看一遍。点开每一对,查看 LLM 标错的具体实例,这是弄清它怎么出错的最快办法。


第 7 步:回到提示审阅

基于混淆分析,Potato 为 LLM 起草一份优化过的指南,并并排展示:一边是当前的提示,另一边是它从错误模式中提出的具体修改。

例如,Potato 可能建议加上:

"Reviews that describe a product as 'fine', 'okay', or 'decent' without strong emotion should be labeled Neutral, even if they mention buying again."

把建议的修改逐条过一遍,批准、修改或拒绝。你也可以写进自己的说明。

预计时间: 5-10 分钟。


第 8 步:标注函数

Potato 从你的种子标签中找出模式,生成程序化的标注函数。这些是处理简单情况的快速、确定性规则:

text
Generated Labeling Functions:
  LF1: Strong positive words (love, amazing, best, excellent)
       Precision: 0.97, Coverage: 0.06
  LF2: Strong negative words (terrible, awful, worst, waste)
       Precision: 0.95, Coverage: 0.04
  LF3: Exclamation + positive adjective
       Precision: 0.94, Coverage: 0.03
  LF4: Return/refund mention + negative context
       Precision: 0.92, Coverage: 0.02
  ...
  Total coverage: 0.18 (1,800 of 10,000 instances)

标注函数以 92% 以上的精确率覆盖了数据集的 18%。这些实例会被自动标注,把 LLM 和人的精力腾出来对付更难的情况。

你的操作: 把生成的函数看一遍,禁用看起来不可靠的。这一步可选,因为 Potato 本来就只保留超过你所配精确率阈值的函数。


第 9 步:主动标注(125-375 个实例)

你的大部分标注工作在这里。Potato 按上面 instance_selection 里设定的加权池来挑选实例:

  • low_confidence_weight(0.30):LLM 置信度最低的评论
  • disagreement_weight(0.25):LLM 和标注函数给出不同标签的评论
  • diversity_weight(0.20):为铺开嵌入空间而非扎堆而选出的评论
  • llm_predicted_weight(0.15):由 LLM 标注、被拉回来供人工比对的评论
  • random_weight(0.10):用于校准的随机评论

还有两个权重,这里保持默认的 0,所以不起任何作用:edge_case_rule_weight,针对匹配已发现的边缘案例规则的实例;以及 cartography_weight,针对置信度在各轮之间来回摆动的实例。Potato 会检查这些权重之和是否为 1.0,不是的话记一条警告,但它仍按写下的数值使用,所以得你自己把它们凑齐。

你以 25 个为一批标注。每批之后,Potato 更新 LLM 的准确率估计,并决定要不要继续。

典型轨迹:

  • 第 1-3 批(75 个实例):准确率从 82% 升至 87%
  • 第 4-6 批(150 个实例):准确率达到 90%
  • 第 7-10 批(250 个实例):准确率在 91-92% 趋于平稳

如果一致性达到你设的 end_human_annotation_agreement,Solo Mode 就转向自主标注。否则它继续在审阅和优化之间循环。

预计时间: 总计 45-90 分钟,取决于需要多少批。


第 10 步:优化循环

如果主动标注之后准确率仍低于阈值,Potato 再跑一轮优化循环:

  1. LLM 用更新后的指南和更多少样本示例重新标注整个数据集
  2. Potato 对照所有人工标签重新计算准确率
  3. 它找出新的混淆模式
  4. 它再次优化指南

这个阶段基本不用你动手。你只需要批准指南的改动。

典型结果: 每轮优化准确率提高 2-4%。


第 11 步:分歧消解

Potato 呈现最有争议的实例:LLM、标注函数和最近邻分析三者给出不同答案的情况。每个实例你会看到:

  • 评论文本
  • LLM 预测和置信度
  • 标注函数投票
  • 3 个最近的已标注示例及其标签
  • LLM 的思维链推理

这些是难啃的情况,你在这里给出的标签,比流程中其他地方的分量更重。

预计时间: 100-150 个实例需 20-30 分钟。


第 12 步:边缘案例合成

Potato 针对剩下的混淆模式生成合成评论。比如,如果 LLM 在"提到会再买的中性评论"上仍然吃力,它会生成这样的例子:

"It's an okay product for the price. I might get another one if there's a sale."

你标注这些合成示例,Potato 把它们加进 LLM 的少样本上下文。

预计时间: 30 个示例需 10-15 分钟。


第 13 步:定期审阅

到这时 LLM 已经把数据集的大部分标完了。Potato 按置信度给它的全部标签排序,把置信度最低的那些以 25 个为一批发给你。

text
Confidence Escalation Progress:
  Batch 1: 25 instances, 23/25 correct (92%)
  Batch 2: 25 instances, 24/25 correct (96%)
  Batch 3: 25 instances, 25/25 correct (100%)
  -> Stopping: last 3 batches stable

一旦准确率连着三批不再变动,Solo Mode 就把剩下的高置信度标签当作可信的。注意这看的是稳定而不是完美:上面这一轮在 92%、96%、100% 之后停下,是因为数字已经稳住了,不是因为最后一批全对。

预计时间: 15-20 分钟。


第 14 步:提示优化

这个阶段自己跑。Potato 试 8 种提示变体,保留在你积累的人工标签上 F1 分数最高的那一个:

text
Prompt Optimization Results:
  Variant 1 (direct, 5 examples):     F1=0.91
  Variant 2 (CoT, 5 examples):        F1=0.93
  Variant 3 (direct, 10 examples):    F1=0.92
  Variant 4 (CoT, 10 examples):       F1=0.94  <-- selected
  Variant 5 (direct, 15 examples):    F1=0.92
  Variant 6 (CoT, 15 examples):       F1=0.93
  Variant 7 (self-consistency, 5x):   F1=0.94
  Variant 8 (self-consistency, 10x):  F1=0.94

然后它用这个最好的提示做最后一遍重新标注。


第 15 步:最终验证

Potato 抽出 100 个 LLM 标注的实例交给你审核。你来标,Potato 把你的标签和 LLM 的比对。

text
Final Validation:
  Reviewed: 100 instances
  LLM correct: 94/100 (94%)
  Threshold: 93%
  -> PASSED

如果 LLM 过了你设的阈值,项目就完成了。如果没过,它退回主动标注再来一轮 -- 这是最终验证唯一允许的转移。

预计时间: 10-15 分钟。


结果汇总

最后,面板会报告这些标签各自的来源。Solo Mode 没有命令行界面 -- 它跑在服务器里,由它自己的页面驱动 -- 所以这份汇总是从运行中的实例里看到的,不是从终端:

text
Solo Mode Complete
==================
Dataset: 10,000 instances
Total human labels: 612
  Seed: 50
  Active labeling: 275
  Disagreement exploration: 137
  Edge case synthesis: 30
  Confidence escalation: 75
  Final validation: 45

LLM labels: 8,200 (accuracy: 94.1%)
LF labels: 1,800 (precision: 95.3%)
Unlabeled: 0

Final label distribution:
  Positive: 4,823 (48.2%)
  Neutral:  3,011 (30.1%)
  Negative: 2,166 (21.7%)

Total human time: ~3.5 hours
Estimated multi-annotator cost (3x): ~$4,500
Solo Mode cost: ~$450 (API fees) + ~$175 (annotator time)
Savings: ~88%

人类标注了 10,000 个实例中的 612 个,大约 6%。其余的由 LLM 和标注函数以 94% 以上的准确率处理。


导出结果

Solo Mode 的写入走的是和其他项目一样的存储,所以标签也通过普通的导出器取出:

bash
python -m potato.export -c config.yaml -f jsonl -o ./export/

每行包含标签及其来源:

json
{"id": "rev_001", "sentiment": "Positive", "source": "human", "confidence": 1.0}
{"id": "rev_002", "sentiment": "Neutral", "source": "llm", "confidence": 0.91}
{"id": "rev_003", "sentiment": "Negative", "source": "labeling_function", "confidence": 0.97}

Parquet 导出:

python
import pandas as pd
df = pd.read_parquet("output/exports/parquet/annotations.parquet")
print(df["sentiment"].value_counts())

annotations.parquet 是每个实例、每个标注者一行,每个标注方案摊平成自己的一列,所以你按方案名取列,而不是取一个通用的 value 列。


质量保证:混合验证

要做出版级质量的数据集,加一位标注者来审核一个样本。Solo Mode 源文档更详细地讲了各种验证选项。

Solo Mode 自己没有验证配置块,所以这就是普通的第二遍:把标签导出,作为一个普通项目重新送回 Potato,由一位审核者过 10% 的样本,再像对待任意两名标注者那样计算审核者与 Solo Mode 标签之间的一致性。(icl_labeling 确实有一个 verification 块,但那属于上下文内标注,不属于 Solo Mode。)


故障排除

LLM 准确率在阈值以下停滞

  • 增加种子数量: 试 75-100 个种子实例,而不是 50 个
  • 更换 LLM: 试试用 claude-sonnet-4-20250514 代替 GPT-4o(或者反过来)
  • 降低阈值: 如果 93% 达不到,想想 90% 对你的用例是否可以接受
  • 检查数据: 有些数据集本身就是模糊的。如果人与人之间的一致性也只有 90%,就别指望 LLM 做得更好

主动标注需要太多批次

  • 增大批量: 把 batch_size 从 25 改成 50
  • 调整池权重: 如果升级上来的实例大多来自低置信度池,就降低 low_confidence_weight,提高 disagreement_weight 或 diversity_weight

标注函数覆盖率低

  • 对于没有强词汇信号的任务(如讽刺检测、隐含情感),这是正常的
  • 标注函数对显式的、由关键词驱动的模式效果最好
  • 没有标注函数,Solo Mode 照样能用,这些实例改由 LLM 覆盖

延伸阅读