用你的标注训练第一个模型
在没有 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 并克隆仓库:
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:
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:
potato start examples/ai-assisted/model-training/config.yaml -p 8000服务器会加载这 160 条评论,并在 http://localhost:8000 提供标注页面。如果你的机器上 8000 端口已被占用,请给 -p 指定另一个端口,并在下面的每个 URL 里使用该端口。
用模拟器标注 120 条评论
模型需要人工标签才能学习。用户模拟器代替三位标注者。每位模拟标注者按金标准文件作答,大多数答案是正确的。在第二个终端里,从仓库根目录运行模拟器:
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。
在示例项目上完成两次 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 请求头中发送管理员密钥:
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,从不用作得分。在我们的运行中,日志如下:
[info] Read 76 labelled rows from the train split
[info] Applied sigmoid calibration over 5 folds
[info] Held-out val macro_f1: 0.725Potato 自己的文档报告了同一项目上的一次运行,下表把那次运行与我们的运行并排列出:
| Potato 的文档 | 我们的运行 | |
|---|---|---|
| 已标注评论 | 120 | 120 |
| train、val 和 test 条目数 | 未说明 | 76、19 和 25 |
| 留出得分(val macro-F1) | 0.84 | 0.725 |
| test macro-F1 | 未说明 | 0.826 |
| 已发布的预标签 | 80 | 73 |
低于 min_confidence 的预测 | 4 | 11 |
你的数字会与两列都不同,因为每位模拟标注者随机抽取一个准确率,并犯下不同的错误。在我们的运行中,模拟标签在 120 条评论中有 97 条与 gold.json 一致,在 19 条 val 评论中有 15 条一致。留出得分衡量的是与你的标注者所给标签的一致程度,而不是与任何标准答案的一致程度,所以 val 中的标注错误会拉低得分。我们的运行发布的 73 个预标签全部与 gold.json 相符。在没有金标准文件的真实研究里,看到较低的留出得分时,应先检查标注者之间的一致性,再去怪模型。
为方案开启回写
一次运行的预测只通过回写送到标注者面前。示例配置已经为 sentiment 方案开启了回写。对于你自己的项目,请在配置的 model_training 下加入这个块,并重启服务器:
model_training:
enabled: true
writeback:
schemas: ["sentiment"]
include_unlabeled: true
min_confidence: 0.6writeback.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。
一条打开时已选中模型答案的评论,此时标注者还没有保存任何内容
标注者只是查看评论时,预标签不会被保存,状态标签会一直显示 "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 下写上你的方案。
延伸阅读
- 模型训练概览
- 模型训练配置
- 训练器
- 用户模拟器
- 管理员仪表盘
- 主动学习
- Potato 仓库中的模型训练参考,其中有完整的
sklearn-text参数、留出得分和回写各节