Skip to content
チュートリアル15分で読めます

Solo Mode:1人のアノテーターで10,000件のラベリングを行う方法

PotatoのSolo Modeの手順解説。まず自分でシードセットにラベルを付け、残りはLLMに任せ、精度チェックを見ながらどこで止めるかを決めます。

Potato Team

10,000件の商品レビューにセンチメント(Positive、Neutral、Negative)のラベルを付ける必要があるとします。3人のアノテーターを雇って全件にラベルを付けてもらうと、数週間かかり、費用は数千ドルになります。Solo Modeを使えば、1人のドメインエキスパートが500〜1,000件のインスタンスにラベルを付けるだけで同等の品質を得られます。残りはLLMが処理し、LLMが確信を持てないものを人間がレビューします。

このチュートリアルでは、その全工程を順に説明します。


必要なもの

  • Potato 2.3.0以降と、LLMプロバイダー用のAIエクストラ:pip install potato-annotation[ai]
  • OpenAIまたはAnthropicのAPIキー(LLMコンポーネント用)
  • JSONL形式のデータセット
  • 知識のあるアノテーター1人(あなた自身でも構いません)

ステップ1:データの準備

data/reviews.jsonlを作成し、1行に1件のレビューを書きます。

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のダッシュボードが表示され、プロジェクトはセットアップ段階にあり、Potatoが合成したプロンプトをあなたがレビューできる状態になっています。


ステップ4:並行アノテーション(最初の50件)

Potatoは埋め込みベースのクラスタリングを使って、多様なレビューを50件選んでいます。これらはランダムに選ばれたものではなく、データの分布をできるだけ広くカバーするように選ばれています。

1件ずつラベルを付けてください。シードラベルの品質がLLMの学習できる上限を決めるので、ここが最も重要なフェーズです。時間をかけ、一貫性を保ってください。

所要時間の目安: 1インスタンスあたり20〜30秒で、15〜25分。

これらのラベルが十分に集まると、指示しなくてもLLMがあなたと並行してラベル付けを始めます。


ステップ5:LLMが追いつく

このフェーズは自動で進みます。Potatoは50件のシードラベルをfew-shot例として、200件のインスタンスのバッチをLLMに送ります。その後、10件のホールドアウトしたシードラベルと予測を比較して、ベースラインの精度を推定します。

ダッシュボードに進捗インジケーターが表示されます。LLMプロバイダーにもよりますが、通常1〜2分かかります。

典型的な結果: 最初のキャリブレーションでのLLMの精度は75〜85%程度です。これは想定どおりです。LLMはまだあなたのアノテーションのスタイルを学習していません。


ステップ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が間違えた具体的なインスタンスが表示されます。LLMがどう失敗するかを理解するには、これが最も早い方法です。


ステップ7:プロンプトのレビューに戻る

混同分析をもとに、PotatoはLLM向けに改善したガイドラインの案を作り、左右に並べて表示します。片側が現在のプロンプト、もう片側がエラーパターンから提案する具体的な編集です。

たとえば、Potatoは次の一文の追加を提案するかもしれません。

「製品を『fine』『okay』『decent』と表現し、強い感情を伴わないレビューは、再購入に触れていてもNeutralとラベル付けする。」

提案された編集を1つずつ確認し、承認、修正、または却下してください。自分で補足説明を書き加えることもできます。

所要時間の目安: 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)

ラベリング関数はデータセットの18%を92%以上の精度でカバーします。これらのインスタンスは自動でラベル付けされるので、LLMと人間はより難しいケースに集中できます。

あなたの作業: 生成された関数に目を通し、信頼できなさそうなものは無効にしてください。Potatoは設定した精度の閾値を超える関数しか残さないので、この手順は任意です。


ステップ9:アクティブアノテーション(125〜375件)

ラベル付けの大部分はここで行います。Potatoは、上のinstance_selectionで設定した重み付きプールを使ってインスタンスを選びます。

  • low_confidence_weight(0.30):LLMの信頼度が最も低いレビュー
  • disagreement_weight(0.25):LLMとラベリング関数の判断が食い違うレビュー
  • diversity_weight(0.20):1か所に固まらず、埋め込み空間全体に散らばるように選ばれたレビュー
  • llm_predicted_weight(0.15):人間との比較のために引き戻された、LLMがラベル付けしたレビュー
  • random_weight(0.10):キャリブレーション用のランダムなレビュー

ほかに2つの重みがあり、ここではデフォルトのゼロのままなので何の影響もありません。発見されたエッジケースのルールに一致するインスタンス用のedge_case_rule_weightと、ラウンド間で信頼度が大きく変動するインスタンス用のcartography_weightです。Potatoは重みの合計が1.0になるかを確認し、ならない場合は警告をログに出しますが、値は書かれたとおりに使います。合計が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ラウンド実行します。

  1. LLMが、更新されたガイドラインと増やしたfew-shot例でデータセット全体をラベル付けし直す
  2. Potatoが、すべての人間ラベルに対して精度を再計算する
  3. 新しい混同パターンを見つける
  4. ガイドラインを再び改善する

このフェーズはほとんど手がかかりません。必要なのはガイドラインの変更を承認することだけです。

典型的な結果: 改善ラウンドごとに精度が2〜4%向上します。


ステップ11:不一致の解消

Potatoは、判断が最も割れているインスタンス、つまりLLM、ラベリング関数、最近傍分析がそれぞれ異なる答えを出すケースを提示します。各インスタンスについて、次の情報が表示されます。

  • レビューのテキスト
  • LLMの予測と信頼度
  • ラベリング関数の投票
  • ラベル付き済みの最も近い例3件とそのラベル
  • LLMの思考過程(chain-of-thought)の推論

これらは難しいケースであり、ここで付けるラベルは、工程のほかの部分よりも大きな重みを持ちます。

所要時間の目安: 100〜150件で20〜30分。


ステップ12:エッジケースの合成

Potatoは、残っている混同パターンを狙った合成レビューを生成します。たとえばLLMが「再購入に触れている中立的なレビュー」にまだ苦戦している場合、次のような例を生成します。

「値段の割にはまあまあの製品です。セールがあればもう1つ買うかもしれません。」

これらの合成例にあなたがラベルを付けると、PotatoはそれらをLLMのfew-shotコンテキストに追加します。

所要時間の目安: 30件で10〜15分。


ステップ13:定期レビュー

この時点で、LLMはデータセットの大部分にラベルを付け終えています。PotatoはLLMのラベルをすべて信頼度順に並べ、最も信頼度の低いものを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

精度が3バッチ続けて動かなくなると、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は、LLMがラベル付けしたインスタンスをランダムに100件取り出し、あなたにレビューを求めます。あなたがラベルを付けると、PotatoはそれをLLMのラベルと比較します。

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

LLMが閾値を超えれば、プロジェクトは完了です。超えなければ、アクティブアノテーションに戻ってもう1ラウンド行います。最終検証から許されている遷移は、この戻りだけです。

所要時間の目安: 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はインスタンスとアノテーターの組み合わせごとに1行で、各アノテーションスキームがそれぞれの列に展開されています。そのため、汎用的なvalue列ではなく、スキームの名前で列を選びます。


品質保証:ハイブリッド検証

論文に使える品質のデータセットにするには、2人目のアノテーターを加えてサンプルをレビューしてもらいます。検証の方法については、Solo Modeのソースドキュメントに詳しい説明があります。

Solo Modeには独自の検証ブロックがないので、これは通常の2回目のパスとして行います。ラベルをエクスポートし、1人のレビュアーが10%のサンプルを見る通常のプロジェクトとしてPotatoに戻し、任意の2人のアノテーターの場合と同じ方法で、レビュアーとSolo Modeのラベルとの一致度を計算します。(icl_labelingにはverificationブロックがありますが、これはSolo Modeではなくin-context labelingに属するものです。)


トラブルシューティング

LLMの精度が閾値の手前で頭打ちになる

  • シード数を増やす: 50件ではなく75〜100件のシードインスタンスを試す
  • LLMを切り替える: GPT-4oの代わりにclaude-sonnet-4-20250514を試す(またはその逆)
  • 閾値を下げる: 93%に届かない場合、用途によっては90%で十分かどうかを検討する
  • データを確認する: データセットによっては本質的に曖昧なものがあります。人間同士の一致率が90%にしかならないのであれば、LLMにそれ以上を期待しないでください

アクティブアノテーションのバッチ数が多すぎる

  • バッチサイズを増やす: batch_sizeを25から50に変更する
  • プールの重みを調整する: エスカレーションされるインスタンスの大半が低信頼度プールから来ている場合は、low_confidence_weightを下げ、disagreement_weightまたはdiversity_weightを上げる

ラベリング関数のカバレッジが低い

  • 強い語彙的シグナルがないタスク(皮肉の検出、暗黙的なセンチメントなど)では、これは普通のことです
  • ラベリング関数が最も効果を発揮するのは、明示的でキーワードに依存するパターンです
  • Solo Modeはラベリング関数がなくても動作し、その分のインスタンスはLLMが担当します

参考資料