跳轉到內容

用你的標註訓練第一個模型

在沒有 GPU 的筆記本上執行 Potato 的示例評論研究,用模擬器標註 120 條評論,訓練 sklearn-text,並檢視模型給出的預標籤。

v2.14.0 新增

本教程在一個示例研究自己的標籤上訓練情感分類器,並把分類器的答案作為預標籤展示給下一位標註者。教程寫給第一次嘗試 Potato 模型訓練的研究負責人,每一步都能在沒有 GPU 的筆記本上執行,除 Potato 本身外無需任何安裝。你要啟動 Potato 的示例評論項目,讓使用者模擬器標註項目 160 條評論中的 120 條,在管理員訓練頁面訓練 sklearn-text,檢視這次執行的留出得分,再以新標註者的身份登入,看到模型的答案。關於迴圈中每個部分的一般作用,請參閱模型訓練概覽。

安裝 Potato 和示例項目

本教程使用 sklearn-text。這個訓練器把每條評論轉換成 TF-IDF 特徵,並擬合一個邏輯迴歸分類器。sklearn-text 無需額外安裝,因為 scikit-learn 是 Potato 的核心依賴之一。示例項目在 Potato 的 GitHub 倉庫裡,而不在 pip 包中,所以要安裝 Potato 並克隆倉庫:

bash
pip install --upgrade potato-annotation
git clone https://github.com/davidjurgens/potato.git
cd potato

下面的每條命令都在倉庫根目錄執行。項目資料夾 examples/ai-assisted/model-training/ 中有三個檔案:

  • config.yaml 是研究配置,註釋裡寫著本教程用到的命令。
  • data/reviews.json 包含 160 條合成的商品評論,每行一個 JSON 物件,帶有 id 和 text。
  • data/gold.json 給出每條評論的正確標籤。模擬器按這個檔案作答。

示例配置

配置定義了一個名為 sentiment 的 radio 方案,標籤為 positive 和 negative,並開啟了模型訓練。下面的程式碼塊是去掉註釋的原始檔案,該檔案無需修改即可通過 python -m potato.validate_cli --strict:

yaml
annotation_task_name: "Review Sentiment (model training demo)"
 
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
 
annotation_schemes:
  - annotation_type: "radio"
    name: "sentiment"
    description: "Is this review positive or negative?"
    labels:
      - "positive"
      - "negative"
    sequential_key_binding: true
 
model_training:
  enabled: true
  writeback:
    schemas: ["sentiment"]
    include_unlabeled: true
    min_confidence: 0.6

四項設定決定了教程的走向:

  • num_annotators_per_item: 1 讓每條評論得到一個標籤。三位模擬標註者各標 40 條,就能覆蓋 120 條不同的評論,剩下 40 條未標註的留給模型。
  • require_password: false 讓新標註者只憑使用者名稱即可登入。最後一步就用這種方式登入。
  • admin_api_key: "demo-admin-key" 用來開啟管理員頁面。在其他人能訪問的伺服器上,請更換這個金鑰。
  • model_training 註冊 /admin/training 路由。writeback 塊會把 sentiment 方案的預測展示給標註者,下文回寫一節會說明其中每個鍵。

在 8000 埠啟動伺服器

用示例配置啟動 Potato:

bash
potato start examples/ai-assisted/model-training/config.yaml -p 8000

伺服器會載入這 160 條評論,並在 http://localhost:8000 提供標註頁面。如果你的機器上 8000 埠已被佔用,請給 -p 指定另一個埠,並在下面的每個 URL 裡使用該埠。

用模擬器標註 120 條評論

模型需要人工標籤才能學習。使用者模擬器代替三位標註者。每位模擬標註者按金標準檔案作答,大多數答案是正確的。在第二個終端裡,從倉庫根目錄執行模擬器:

bash
python -m potato.simulator --server http://localhost:8000 --users 3 \
  --strategy gold_standard --competence good=1.0 --max-annotations 40 \
  --gold-file examples/ai-assisted/model-training/data/gold.json \
  --sequential --fast-mode --no-export

--strategy gold_standard 讓每位模擬標註者按 gold.json 作答,--competence good=1.0 讓三位標註者都使用模擬器的 good 檔案。good 標註者會抽取一個 0.80 到 0.90 之間的準確率,並以該機率答對每條評論。因此這些標籤含有錯誤,和真實團隊的標籤一樣。--sequential 讓三位標註者依次執行,--fast-mode 去掉作答之間的停頓,--no-export 則跳過模擬器自身結果檔案的寫入。模擬器大約兩秒完成,並以一份摘要收尾。繼續之前,請確認摘要中報告了 Total annotations: 120。你也可以在 http://localhost:8000 手工標註評論。當 train 劃分中已標註的條目少於 10 個時,sklearn-text 拒絕擬合。

用管理員金鑰開啟訓練頁面

訓練頁面需要管理員會話。開啟 http://localhost:8000/admin,在頁面詢問 API 金鑰時輸入 demo-admin-key,然後開啟 http://localhost:8000/admin/training。

Potato 訓練頁面,已選擇 sklearn-text、sentiment 方案和 local 後端,上方是 Start a run 面板,下方的 Runs 表列出兩次已完成的執行,留出得分分別為 0.847 和 0.840在示例項目上完成兩次 sklearn-text 執行後的訓練頁面

頂部的 Start a run 面板包含全部設定。Schema 列出項目唯一的方案 sentiment (radio)。Trainer 列出全部 38 個訓練器,項目無法執行的訓練器會在名稱旁標註原因,例如 "not installed"。頁面選中 sklearn-text,只要 sklearn-text 適用於該方案。Runs on 顯示項目唯一的後端 local (this machine)。

訓練 sklearn-text

保持 Parameters 為空以使用訓練器的預設值,然後點選 Train now。這次執行先把標註合併成每條評論一個標籤,再把 120 條已標註的評論劃分為 train、val 和 test 三個集合,並在單獨的程序中擬合模型。我們的執行用了 1.5 秒,執行結束時面板顯示 "Run finished."。

sklearn-text 預設用 scikit-learn 的 TfidfVectorizer 提取特徵,用 LogisticRegression 作分類器。當 train 劃分中有 50 個或更多條目時,sklearn-text 還會對分類器的置信度做校準。訓練條目少於 1,000 時用 Platt 縮放,達到 1,000 條起用保序迴歸。只有當校準使訓練準確率下降不超過 0.05 時,才保留校準後的模型。校準在這裡很重要,因為回寫步驟會把每個置信度與 min_confidence 比較。

要用指令碼啟動同樣的執行,請在 X-API-Key 請求頭中傳送管理員金鑰:

bash
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"]}'

響應中帶有新的 run_id 和 "started": true。

執行頁面和留出得分

完成的執行會出現在 Runs 表中,附有 train 和 val 的大小以及它的 Held-out score。點選執行 ID,即可開啟 /admin/training/runs/<run_id> 處的執行頁面。執行頁面列出該次執行的狀態、訓練器、後端、劃分大小、劃分種子、留出得分和參數,然後是一張包含每項指標的表以及執行日誌。

預設情況下,一次執行會把已標註條目的 15% 留出作 val,15% 留出作 test,由 split_seed 決定哪些條目落入哪個集合。只要 split_seed 的值不變,每次執行的劃分就保持一致,所以對同一批標籤的兩次執行是在相同的條目上評分的。sklearn-text 執行的留出得分是 val 上的 macro-F1。Macro-F1 對每個標籤(這裡是 positive 和 negative)的 F1 得分取平均。執行還會記錄 test 上的準確率和 macro-F1。train 劃分上的準確率記為 train_accuracy,從不用作得分。在我們的執行中,日誌如下:

text
[info] Read 76 labelled rows from the train split
[info] Applied sigmoid calibration over 5 folds
[info] Held-out val macro_f1: 0.725

Potato 自己的文件報告了同一項目上的一次執行,下表把那次執行與我們的執行並排列出:

Potato 的文件我們的執行
已標註評論120120
train、val 和 test 條目數未說明76、19 和 25
留出得分(val macro-F1)0.840.725
test macro-F1未說明0.826
已釋出的預標籤8073
低於 min_confidence 的預測411

你的數字會與兩列都不同,因為每位模擬標註者隨機抽取一個準確率,並犯下不同的錯誤。在我們的執行中,模擬標籤在 120 條評論中有 97 條與 gold.json 一致,在 19 條 val 評論中有 15 條一致。留出得分衡量的是與你的標註者所給標籤的一致程度,而不是與任何標準答案的一致程度,所以 val 中的標註錯誤會拉低得分。我們的執行釋出的 73 個預標籤全部與 gold.json 相符。在沒有金標準檔案的真實研究裡,看到較低的留出得分時,應先檢查標註者之間的一致性,再去怪模型。

為方案開啟回寫

一次執行的預測只通過回寫送到標註者面前。示例配置已經為 sentiment 方案開啟了回寫。對於你自己的項目,請在配置的 model_training 下加入這個塊,並重啟伺服器:

yaml
model_training:
  enabled: true
  writeback:
    schemas: ["sentiment"]
    include_unlabeled: true
    min_confidence: 0.6

writeback.schemas 指定標註者能看到其預測的方案。該列表預設為空,所以開啟訓練後,在你指定方案之前,標註頁面不會有任何變化。include_unlabeled: true 讓每次執行除了對 val 和 test 評論做預測,也對無人標註的評論做預測。沒有 include_unlabeled 時,一次執行只對 val 和 test 評論做預測,而這些評論已經有人工標籤。min_confidence: 0.6 會在模型置信度低於 0.6 時,不把該預測放到標註頁面上。Potato 仍會儲存這個預測。

回寫在每次成功執行之後執行,並跳過該次執行 train 劃分中的所有條目。在我們的執行中,模型對 84 條評論做了預測(19 條 val、25 條 test 和 40 條未標註),其中 11 個預測低於 0.6,執行釋出了其餘 73 個。GET /admin/training/api/status 的 writeback 欄位給出相同的數量,摘要為 "Published 73 prediction(s) as prelabels."。已儲存的預測會在伺服器啟動時再次回寫,所以預標籤在重啟後依然存在。若要從條目上撤下某次執行的預標籤,請傳送 DELETE /admin/training/api/runs/<run_id>/predictions。

新標註者看到的預標籤

在隱私瀏覽視窗中開啟 http://localhost:8000,用一個沒人用過的使用者名稱登入,例如 newannotator。每條已標註的評論都已有它的那一個標籤,所以新標註者被分配到 40 條未標註的評論,我們的執行中進度計數器顯示 0/40。我們的第一條評論是 "The toaster: Disappointed, it stopped working within a week. Much smaller than the photos suggest.",其中 Negative 已被選中,置信度為 0.68。

Potato 標註頁面,顯示一條烤麵包機評論,模型已選中 Positive 選項,該條目仍標記為 Not labeled一條開啟時已選中模型答案的評論,此時標註者還沒有儲存任何內容

標註者只是檢視評論時,預標籤不會被儲存,狀態標籤會一直顯示 "Not labeled",直到標註者儲存答案。標註者更改選擇或點選 Next 時,模型的答案就成為標註者的答案。在我們的執行中,預測低於 min_confidence 的 5 條未標註評論開啟時沒有任何選中項。通過 ai_support.holdout 被排除在機器協助之外的標註者,完全看不到預標籤。預標籤會把標註者拉向模型的答案,所以請等留出得分高到值得信任時再開啟回寫,並用檢測被不加思考接受的預標籤中的檢查,留意那些照單全收所有預標籤的標註者。

後續步驟

在換成你自己的資料之前,同一個項目還支援幾個實驗:

  • 更改一個參數。 在 Parameters 中輸入 {"vectorizer_kwargs": {"ngram_range": [1, 2]}} 並再次訓練,把詞對加入特徵。我們加入詞對的第二次執行在同一劃分上得到 0.774 的 val macro-F1,併發布了 74 個預標籤。sklearn-text 不接受的參數名會被拒絕,並附上該訓練器接受的名稱列表。
  • 試試 transformer。 hf-text-classification(pip install "potato-annotation[train-text]")和 setfit(pip install "potato-annotation[embeddings]")可以學習同一個方案,訓練器頁面給出了每個訓練器的參數。
  • 閉合迴圈。 迴圈中的模型加入了稽核佇列、用 auto_retrain 隨標註到來而重新訓練,以及把一次執行的劃分儲存為資料集版本。
  • 把訓練移到 GPU 上。 在 GPU 伺服器、SLURM 叢集或 NAT 後的機器上訓練會把同樣的執行傳送到另一臺機器。
  • 訓練其他類型的模型。 關於微調 LLM、視覺模型、音訊模型和科學模型的教程,用其他訓練器和方案遵循相同的步驟。

這裡用到的每個鍵都列在模型訓練配置參考中。要開始你自己項目的第一次執行,請把上面的 model_training 塊複製到配置裡,並在 writeback.schemas 下寫上你的方案。

延伸閱讀