事前ラベルと再学習によるモデル・イン・ザ・ループのラベリング
学習済みモデルの答えを事前ラベルとして見せ、信頼度の低い事前ラベルから先にレビューし、アノテーターが答えを修正するたびにモデルを再学習します。
v2.14.0の新機能
このチュートリアルでは、1つの感情分析の研究で、モデル・イン・ザ・ループのサイクルを一巡します。モデルは最初のラベルで学習し、モデルの予測は事前ラベルとしてアノテーターに表示され、レビュー担当者は信頼度の低い事前ラベルを修正し、その修正が次の実行に反映されます。対象は、最初のモデルを学習するのように最初のモデルを学習したことがあり、アノテーターがモデルと並んで作業できるようにしたい研究の担当者です。1つを除くすべての手順は、sklearn-textを使ってノートPCで動きます。獲得の手順は、小さなトランスフォーマーを学習し、CPUで数分かかります。
感情分析のプロジェクト
このプロジェクトは、Potatoのモデル学習のサンプルで、160件の合成の商品レビューと、sentimentという名前のradioスキーム1つからなります。事前ラベルとは、人が項目にラベルを付ける前に、モデルがその項目に提案した1つのラベルです。書き戻しは、学習の実行の予測を、アノテーションページの事前ラベルに変える手順です。チュートリアルの終わりには、このプロジェクトは最初の120件のラベルで分類器を学習し、残りのレビューに事前ラベルを公開し、信頼度の最も低いレビューから先に提供し、新しいラベルが届くたびに再学習し、ホールドアウトした分割をデータセットのバージョンとして保持します。
事前ラベルはアノテーションを速くしますが、アノテーターをモデルの答えに引き寄せもします。そのトレードオフに見合う場面は自分のラベルで学習したモデルによるモデル支援ラベリングで、事前ラベルを読まずに受け入れるアノテーターは事前ラベル付きデータで、レビューとゴム印を見分ける方法で扱います。以下の設定は、最初から一部のアノテーターを、すべての事前ラベルからホールドアウトします。ホールドアウトした答えによって、研究はその引き寄せを測ることができます。
プロジェクトを準備する
Potatoをインストールします。sklearn-textは、基本パッケージ以外に何も必要としません。
pip install --upgrade potato-annotationPotatoリポジトリのexamples/ai-assisted/model-training/を新しいフォルダーにコピーします。このフォルダーには、160件のレビューを持つdata/reviews.jsonと、シミュレーターが答えの元にするラベルを持つdata/gold.jsonがあります。サンプルのconfig.yamlを、次の設定に置き換えます。
annotation_task_name: "Review Sentiment (model in the loop)"
data_files:
- "data/reviews.json"
item_properties:
id_key: "id"
text_key: "text"
task_dir: "."
output_annotation_dir: "annotation_output"
require_password: false
admin_api_key: "demo-admin-key"
num_annotators_per_item: 1
assignment_strategy: llm_confidence
annotation_schemes:
- annotation_type: "radio"
name: "sentiment"
description: "Is this review positive or negative?"
labels:
- "positive"
- "negative"
sequential_key_binding: true
ai_support:
holdout:
fraction: 0.2
salt: "review-sentiment"
datasets:
enabled: true
model_training:
enabled: true
writeback:
schemas: ["sentiment"]
include_unlabeled: true
min_confidence: 0.6サンプルの設定に追加した各ブロックは、1つの役割を担います。
- **
assignment_strategy: llm_confidence**は、すべての項目を、事前ラベルの信頼度が最も低いものから先に提供します。予測のない項目は、キューの順で最後に来ます。 - **
ai_support.holdout**は、アノテーターと項目の組の20%に、事前ラベルを含むあらゆる形のAI支援を出しません。fractionやsaltを研究の途中で変えると群が組み直されるため、ホールドアウトはアノテーションを始める前に設定してください。 - **
datasets.enabled**は、最後の手順で、実行の分割をデータセットのバージョンとして保存できるようにします。 - **
writeback.schemas**は、予測をアノテーターに見せるスキームを指定します。このリストは既定で空で、リストにスキームを挙げるまで、学習を有効にしてもアノテーションページは何も変わりません。 - **
include_unlabeled: true**は、各実行が、まだ誰もラベルを付けていないレビューにも予測を出すようにします。この設定がないと、実行は、実行のホールドアウトしたvalとtestの項目にだけ予測しますが、それらの項目にはすでに人手のラベルがあります。 - **
min_confidence: 0.6**は、信頼度が0.6を下回る予測を、アノテーターの目に触れないようにします。Potatoは、その予測を実行とともに引き続き保存します。
writeback.fieldは既定のpredictionsのままで、これは事前ラベルの書き込み先となる項目のフィールドです。既定のままなら、pre_annotationブロックは要りません。サーバーを起動する前に、設定を確認します。
potato validate config.yaml
potato start config.yaml -p 8000最初のラベルを付ける
自分のラベルで学習するモデルには、まずラベルが必要です。sklearn-textは、学習分割に10件未満の項目しかないと、学習を拒否します。http://localhost:8000でレビューにいくつかラベルを付けるか、3人のシミュレートされたアノテーターに、正解ファイルから40件ずつラベルを付けさせます。
python -m potato.simulator --server http://localhost:8000 --users 3 \
--strategy gold_standard --competence good=1.0 --max-annotations 40 \
--gold-file data/gold.json --sequential --fast-mode --no-export--competence good=1.0は、シミュレートされたすべてのアノテーターにgoodプロファイルを割り当て、レビューの80〜90%に正しく答えます。num_annotators_per_item: 1では、3人のアノテーターが異なる120件のレビューをカバーし、40件をモデル用にラベルなしで残します。
学習して書き戻しのレポートを読む
管理者キーでhttp://localhost:8000/admin/trainingを開き、sklearn-textとsentimentスキームを選んで、Train nowをクリックします。同じ実行は、コマンドラインからも開始できます。
curl -X POST -H "X-API-Key: demo-admin-key" -H "Content-Type: application/json" \
http://localhost:8000/admin/training/api/runs \
-d '{"trainer": "sklearn-text", "schemas": ["sentiment"]}'実行には数秒かかります。Potatoは、ラベル付きのレビューをtrain、val、testに分け(既定では70%、15%、15%)、trainで分類器を当てはめ、valとtestで分類器のスコアを測ります。続いて、statusルートがスコアと書き戻しのレポートを返します。
curl -H "X-API-Key: demo-admin-key" http://localhost:8000/admin/training/api/statusこのチュートリアルの1回の実行は、ここで重要なフィールドだけに絞ると、次を返しました。
{
"metrics": {
"heldout_metric": "val macro_f1",
"heldout_score": 0.7333,
"n_train": 76.0,
"val_n": 20.0,
"test_n": 24.0,
"test_macro_f1": 0.8222
},
"writeback": {
"n_read": 84,
"n_written": 78,
"n_skipped_low_confidence": 6,
"n_skipped_train_split": 0,
"n_skipped_rejected": 0,
"n_skipped_schema_not_enabled": 0,
"summary": "Published 78 prediction(s) as prelabels."
}
}この実行は、84件のレビュー、つまりvalとtestの44件と、誰もラベルを付けていない40件に予測を出しました。6件の予測は0.6の下限を下回り、残りの78件が事前ラベルになりました。シミュレートされたアノテーターはランダムに間違え、ラベルを付けるレビューも異なるため、スコアと件数は実行ごとに異なります。各スキップのカウンターは、書き戻しの4つのチェックのどれかに対応し、その4つは設定に順に挙げてあります。
書き戻しは、人手のラベルを決して変更しません。予測は項目のデータに入り、アノテーションは各アノテーター自身の状態に残ります。新しいユーザー名でログインして結果を確認します。次のレビューは、モデルの答えが選ばれた状態で届きます。
新しいアノテーターには、モデルの答えが選ばれた状態で表示され、アノテーターはその答えを確認するか変更する
事前ラベルは、アノテーターが見ているだけの間は保存されません。アノテーターが事前ラベルを編集するか、次のレビューに進むと、その事前ラベルがアノテーターの答えになります。そのため、答えていないレビューは完了と数えられず、モデルの答えが人手のラベルとして学習に戻ることもありません。
アノテーターに事前ラベルを見せる前に、ホールドアウトスコアを読んでください。20件のレビューでvalのマクロF1が0.73という実行では、事前ラベルが間違う余地が大きく残ります。上流の書き戻しのリファレンスには、残りのスキップのカウンターと、各アノテーションタイプが事前ラベルをどう表示するかが載っています。
信頼度の低い事前ラベルをレビューする
llm_confidenceでは、次のアノテーターに、ラベルのない40件のレビューが、公開された事前ラベルの信頼度が低い順に提供されます。もう1つの戦略は、事前ラベルをラベリングのためではなく品質管理のために提供します。assignment_strategy: model_reviewは、事前ラベルを持つ項目だけを、信頼度の低い順に提供し、予測のない項目は除きます。モデルの出力のレビューをするときはmodel_reviewに切り替え、すべての項目にまだラベルが必要な間はllm_confidenceのままにします。2つのキューの説明は、上流のモデルレビューのリファレンスにあります。
モデルレビューのレポートは、キューと、モデルが除いたものを示します。
curl -H "X-API-Key: demo-admin-key" http://localhost:8000/admin/model-review上の実行のレポートです(リストは途中で切ってあります)。
{
"n_items": 160,
"n_prelabelled": 78,
"queue": ["review_062", "review_024", "review_044", "review_122", "..."],
"worst_first": ["review_062", "review_024", "review_044", "..."],
"n_empty": 82,
"empty_prediction_ids": ["review_006", "review_007", "review_008", "..."],
"provenance": {
"review_062": {
"sentiment": {
"run_id": "20261104T043431-de5686",
"trainer": "sklearn-text",
"heldout_metric": "val macro_f1",
"heldout_score": 0.7333
}
}
}
}項目のqueueでの順位は、その項目のスキーム全体での最も低い予測の信頼度で決まります。worst_firstは、キューのうち信頼度が最も低い20%で、1件を下回ることはありません。empty_prediction_idsは、事前ラベルのない項目を挙げます。このプロジェクトでは、空の82件は、76件の学習用レビューと、下限を下回った6件の予測です。学習用のレビューには、事前ラベルが付きません。
provenanceは、各事前ラベルの出どころを記録します。事前ラベルを学習の実行が書いたキュー内の項目ごとに、その実行、トレーナー、実行のホールドアウトスコアが書かれます。データファイルとともに届いた事前ラベルには、項目がありません。事前ラベルがおかしく見えるときは、実行IDから/admin/training/runs/<run_id>の実行ページに移れ、そこにはパラメーターと各項目の分割が表示されます。
判定を記録する
判定は、1つの事前ラベルに対するレビュー担当者の評価を、そのレビュー担当者のアノテーションとは別に記録するものです。「モデルが正しかった」ことと「人が独立にこのラベルを選んだ」ことは別の事実であり、2つを分けておくことで、モデルの適合率を測定できます。Potatoは、判定を1つのルートで受け付け、項目を割り当てられたアノテーターか、APIキーを持つ管理者から受け取ります。
curl -X POST -H "Content-Type: application/json" -H "X-API-Key: demo-admin-key" \
-d '{"instance_id": "review_062", "verdict": "correct", "schema_name": "sentiment"}' \
http://localhost:8000/api/model-review/verdictacceptはモデルが正しかったこと、correctは人が答えを変更したこと、rejectはモデルが間違っていたことを意味します。rejectは、ただちに項目から事前ラベルを外し、以降の実行は、その項目とスキームの予測を二度と公開しません。rejectは、モデルが見逃したラベルではなく、却下された事前ラベルとして、引き続きモデルの適合率に数えられ、同じ判定をもう一度送っても、どのカウントも変わりません。correctの判定は、後述の修正された項目と固定された項目の学習の節で説明するとおり、その項目を以降のすべての実行のtrain分割に移します。同じ項目に2つ目の判定を記録すると、最初の判定が置き換わり、未知の判定はHTTP 400で拒否されます。
獲得スコアでキューを並べ替える
信頼度順は、モデルの最上位の答えが最も弱い項目を先に提供します。能動学習の獲得関数は、各項目にラベルがあれば次のモデルにどれだけ役立つかで、ラベルのない項目を順位づけします。hf-text-classificationとhf-image-classificationは、すべての予測にそのようなスコアを書くacquisitionパラメーターを受け取り、llm_confidenceはそのスコアが最も高い項目から先に提供します。このトレーナーには、テキスト学習のextraが必要です。
pip install "potato-annotation[train-text]"TrainingページのParameters欄またはAPIから、このパラメーターつきで実行を開始します。
curl -X POST -H "X-API-Key: demo-admin-key" -H "Content-Type: application/json" \
http://localhost:8000/admin/training/api/runs \
-d '{"trainer": "hf-text-classification", "schemas": ["sentiment"],
"params": {"acquisition": "margin"}}'このパラメーターは、5つの値を取ります。
acquisition | 各予測に書かれるスコア |
|---|---|
least_confident | 既定。スコアは書かれず、項目は信頼度の順のままです。 |
margin | 1から、上位2つの確率の差を引いた値。 |
entropy | 予測分布のエントロピーを0〜1に正規化した値。 |
bald | ラベルとモデルの重みの相互情報量。ドロップアウトをオンにしたmc_samples回(既定は10)の順伝播から求めます。この手法はHoulsby et al. (2011)によります。スコアは、モデルにデータが足りない箇所では高く、データによらず項目が曖昧な箇所では低くなります。 |
coreset | モデルの埋め込み空間での、各項目から最も近い学習項目、またはすでに選ばれた項目までの距離。この手法はSener and Savarese (2018)によります。スコアはキューをデータ全体に分散させるため、1つの難しい項目のほぼ同じコピー10件が、最初の10位を占めることはありません。 |
マージンの実行の予測には、信頼度の隣にスコアが付きます。
{"label": "positive", "confidence": 0.5207,
"probabilities": {"negative": 0.4793, "positive": 0.5207},
"acquisition": 0.9586}llm_confidenceでは、獲得スコアのある項目が最初に、次に信頼度だけの項目、次に予測に信頼度がない項目、最後に予測のない項目の順に来ます。acquisitionは、単一ラベルのスキーム(radio、select、likert)で動きます。並べ替えは公開された事前ラベルを読むため、スキームがwriteback.schemasに入っている必要があり、ラベルのない項目がスコアを得るには、include_unlabeled: trueの設定が必要です。レビューは、モデルの間違った答えを探すものなので、model_reviewはスコアを無視して信頼度順のままにします。詳細は、上流の「次に何を尋ねるかを選ぶ」の節にあります。
新しい実行の事前ラベルを残す前に、新しい実行のホールドアウトスコアを、sklearn-textの実行のものと比べてください。Potatoは、項目とスキームごとに予測を1つだけ保持し、新しい実行の予測が古い実行の予測を置き換えます。新しい実行の予測が保存されても表示されない場合、例えばmin_confidenceの下では、設定で説明するとおり、古い実行の事前ラベルも項目から外れます。このチュートリアルのCPUでの1回の実行では、既定のモデルを3エポック(paramsのepochs: 3)学習したところ、84件の学習用レビューでtestのマクロF1が0.52となり、同じラベルでのsklearn-textの実行を下回り、この実行は37件の事前ラベルを公開しました。実行の事前ラベルを取り下げるには、実行の予測を削除します。
curl -X DELETE -H "X-API-Key: demo-admin-key" \
http://localhost:8000/admin/training/api/runs/<run_id>/predictions取り下げは、実行の保存された予測を削除し、実行の事前ラベルを項目から外します。以前の実行の事前ラベルは戻らないため、再公開するにはもう一度学習してください。
自動で再学習する
auto_retrainは、アノテーションが届くのに合わせて、自動的に実行を開始します。実行のホールドアウトスコアが事前ラベルの表示を支えるようになったら、model_trainingにこのブロックを追加します。
model_training:
enabled: true
writeback:
schemas: ["sentiment"]
include_unlabeled: true
min_confidence: 0.6
auto_retrain:
enabled: true
trainer: sklearn-text
schemas: ["sentiment"]
update_frequency: 25
min_interval_s: 600Potatoは、アノテーションを保存するたびに確認します。前回の自動実行以降に25件のアノテーションが届き、前回の実行が終わってから600秒が過ぎると、実行が始まります。どちらの値も既定値で、trainerとschemasは必須です。paramsは、TrainingページのParameters欄と同じように、トレーナーのパラメーターを渡します。
auto_retrainは、最初のアノテーションからではなく、最初の手動の実行のあとにオンにしてください。まだ実行のないプロジェクトは、プロジェクトにupdate_frequency件のアノテーションが集まるとすぐに学習します。最初からupdate_frequency: 20にしたところ、このプロジェクトの最初の自動実行は12件のレビューで学習し、3件のレビューでvalのマクロF1が1.0となり、72件の事前ラベルを公開しました。3件のレビューでのホールドアウトスコアは、次の72件についてほとんど何も語りません。
確認が実行を開始しないと判断したときは、/admin/training/api/statusのskip_reasonに、「waiting for 3 more annotation(s)」や「waiting 299 more second(s) since the last run」のような理由が出ます。自動の実行は実行キューには入りません。キューが空になったあとに保存された次のアノテーションが、新しい確認を引き起こします。カウンターはメモリ上にあるため、再起動のあと、すでに実行したことのあるプロジェクトは、再起動以降の新しいアノテーションから数え始めます。ライセンスの確認が必要なトレーナー(ultralytics-detect、ultralytics-segment、layoutlmv3)は、ブロックにaccept_licence: trueも必要で、ないとすべての自動実行が飛ばされます。両方のブロックを持つ完全な設定は、potato validate config.yaml --strictを通ります。
修正された項目と固定された項目の学習
2種類の項目が、安定した分割での位置にかかわらず、以降のすべての実行のtrain分割に入ります。1つ目は、correctの判定がある項目です。修正は、モデルが最も苦手だった項目から出るため、valやtestに入れると、モデル自身の間違いの標本でモデルのスコアを測ることになります。acceptとrejectの判定は、項目を移しません。2つ目は固定された項目で、管理者が手作業で学習に送ります。
curl -X POST -H "X-API-Key: demo-admin-key" -H "Content-Type: application/json" \
http://localhost:8000/admin/training/api/pinned \
-d '{"instance_ids": ["review_007", "review_008"]}'固定された項目は、誰かがその項目にラベルを付けた時点で学習に使われ、それまでは各実行がその項目を待機中として数えます。curation: {enabled: true}のとき、Catalogページのスライスの横にあるTrain on theseボタンは、組み込みのmodel-found-nothingスライスのように、スライスに一致するすべてを固定します。DELETE /admin/training/api/pinnedは、固定を解除します。
ホールドアウトした項目をtrainに移した実行は、その件数の警告をログに出します。1件のcorrectの判定のあと、このチュートリアルの次の実行は次のようにログに出しました。
1 held-out item(s) moved to train (corrected: 1). Held-out scores are computed without them, so they are not directly comparable with a run that had them.件数は、実行のバンドルのmanifest.jsonのstats.forced_trainにあります。2つの実行が異なる項目でスコアを測っているため、実行間でホールドアウトスコアを比べるときは、この警告を念頭に置いてください。
実行のtest分割をデータセットとして保存する
ホールドアウトスコアは、分割が変わると変わり、2つのモデルを比較できるようにするのは、固定された項目の集合です。実行ページのKeep this split as a datasetセクションは、実行のval、test、またはtrainの項目を、その項目の合意された人手のラベルとともに、データセットの新しいバージョンとして保存します。分割を選び、データセット名を入力して、Save as dataset versionをクリックします。ボタンの背後のルートは、コマンドラインからも同じリクエストを受け取ります。
curl -X POST -H "X-API-Key: demo-admin-key" -H "Content-Type: application/json" \
http://localhost:8000/admin/training/api/runs/<run_id>/pin-split \
-d '{"dataset": "sentiment-test", "split": "test"}'レスポンスには、新しいバージョンと項目数が書かれ、ここではバージョンv0001として保存された23件のtestレビューです。バージョンは保存後に変わらないため、のちのモデルや、データセットの実験ランナーでのLLMのプロンプトを、まったく同じ項目でスコアリングできます。すでにバージョンのあるデータセットに保存すると、その分割だけを持つ新しいバージョンが始まるため、のちの実行のval項目が、のちの実行が学習に使った項目と混ざることはありません。各例の入力には事前ラベルのフィールドが含まれないため、評価がモデルの答えを読むことはできません。datasets.enabledが設定されていないと、ルートはHTTP 400を返します。
アノテーターを事前ラベルからホールドアウトする
設定のai_support.holdoutブロックは、最初のアノテーションから、アノテーターと項目の組の20%を支援なしに保ちます。ホールドアウトされた組では、アノテーションページに事前ラベルが表示されず、ブラウザーに送られる項目のデータからは、writeback.field(ここではpredictions)もpre_annotation.fieldも除かれます。抽選は、アノテーターと項目ごとのソルトつきハッシュなので、組は訪問のたびに、また再起動のあとも、同じ群に留まります。1つのレビューに複数のアノテーターがいれば、ほとんどのレビューに、支援を受けたアノテーターと、ホールドアウトされたアノテーターの両方が付きます。管理者ダッシュボードのAI influenceタブは、2つの群を、答えがモデルと一致する頻度、答えが正解と一致する頻度、各群内の一致度で比べます。num_annotators_per_item: 1では、各レビューがどちらか一方の群にしか入らないため、効果を測る研究では、この値を1より大きくしてください。比較の読み方は、自分のラベルで学習したモデルによるモデル支援ラベリングで説明します。
関連資料
- モデル学習では、学習ループとTrainingページを説明します。
- モデル学習の設定には、すべての
writebackとauto_retrainのキーが既定値とともに載っています。 - 最初のモデルを学習するでは、このプロジェクトと最初の
sklearn-textの実行を準備します。 - 画像アノテーションで検出モデルとセグメンテーションモデルを学習するでは、画像のボックス検出器で同じループを回します。
- 自分のラベルで学習したモデルによるモデル支援ラベリングでは、事前ラベルが時間を節約する場面と、答えを固定してしまう場面を扱います。
- 事前ラベル付きデータで、レビューとゴム印を見分ける方法は、事前ラベルを読まずに受け入れるアノテーターを見つけます。
- 能動学習では、モデルの答えを見せずに次の項目を選ぶ方法を扱います。
- 上流のモデル学習のリファレンスには、すべての書き戻しのチェックとルートが載っています。
参考文献
- Houlsby, N., Huszár, F., Ghahramani, Z., and Lengyel, M. (2011). Bayesian Active Learning for Classification and Preference Learning. arXiv:1112.5745. https://arxiv.org/abs/1112.5745
- Sener, O., and Savarese, S. (2018). Active Learning for Convolutional Neural Networks: A Core-Set Approach. ICLR 2018. https://arxiv.org/abs/1708.00489