Files
ModelTrainEval/ml_backend/README.md
T
Shaun ZhouandClaude Opus 5 e61fd5887c feat(ml-backend): Start Training 按钮真正触发重训,启动/重训脚本进 scripts/
原先点 Label Studio 的 Start Training 只是把计数加一:START_TRAINING 和普通标注事件
走同一条阈值判断,没配 RETRAIN_COMMAND 时更是静默什么都不做。实际使用中用户点了按钮、
以为在训练,其实权重一直没变。

- Start Training 无条件触发重训;攒够 RETRAIN_AFTER 条新标注也自动触发。
- 只数 ANNOTATION_CREATED:UPDATED / DELETED 是对已有标注的修改,不该把阈值顶到。
- 已有重训在跑时重复点击被忽略,不叠加启动。
- 没配 RETRAIN_COMMAND 时打警告说明不会真的训练,并在返回值里说清楚。
- 子进程输出重定向到 RETRAIN_LOG(默认 /tmp/retrain.log),训练进度有地方看。

scripts/start_ml_backend.sh、scripts/retrain_from_label_studio.sh 放进仓库,
可从工作区直接启动。重训末尾把 runs/detect/current.pt 软链接指向新权重,
启动脚本默认读它,换模型不必改脚本。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 11:00:01 +08:00

101 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Label Studio ML Backend:用训练好的 YOLO 做预标注
把 `runs/detect/<name>/weights/best.pt` 接进 Label Studio,打开任务时自动给出框,
人工只需修正而不是从零画。标注攒够数量还能自动触发重训,形成"边标边训"的闭环。
## 为什么要单独的虚拟环境
`label-studio-ml` 会把 jinja2 降到 3.0.3、带进 Flask 1.1.2,而 label-studio 1.23 需要
jinja2 3.1。装进主环境会直接弄坏 Label Studio。它本来就是个独立服务,单独建环境最省事。
另外 **PyPI 上的 `label-studio-ml` 是 2023 年的 1.0.9,不要用**,要装 GitHub 上的现行版本
(本文档基于 2.0.1.dev0 验证)。
```bash
uv venv .venv-ml --python 3.12
# --no-deps 装本体:它把 label-studio-sdk pin 到了 GitHub 某个提交,
# 那个仓库大、容易传输中断(服务器上实测失败过),依赖全部走 PyPI 更稳
VIRTUAL_ENV=.venv-ml uv pip install --no-deps \
"label-studio-ml @ git+https://github.com/HumanSignal/label-studio-ml-backend.git"
VIRTUAL_ENV=.venv-ml uv pip install \
"flask>2.3" "colorama~=0.4" "requests~=2.31" "semver~=3.0.2" \
"label-studio-sdk>=2.1" ultralytics
```
## 启动
```bash
bash scripts/start_ml_backend.sh
```
脚本会停掉旧进程、带齐环境变量重启,并打印权重、日志位置与网页端要填的 URL。
所有参数都能用环境变量覆盖,例如 `YOLO_WEIGHTS=... bash scripts/start_ml_backend.sh`。
它内部用 `python _wsgi.py` 而不是 `label-studio-ml start .`:后者用 PATH 上的 python
起子进程,在独立环境里会 `ModuleNotFoundError`。
`LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT` 必须和启动 Label Studio 时的值一致——任务里的图片是
`/data/local-files/?d=...`,后端要靠它把 URL 还原成磁盘路径。图片若来自上传或云存储,
则改为设置 `LABEL_STUDIO_URL` 与 `LABEL_STUDIO_API_KEY`,后端会自行下载。
健康检查:
```bash
curl http://localhost:9090/health
# {"model_class":"FilmDefectDetector","status":"UP"}
```
## 接入项目
Label Studio 里:Settings → **Model** → Connect Model,URL 填 `http://localhost:9090`。
- 勾选 **Interactive preannotations** 后,打开任务就会实时调用模型;
- 也可以在 Data Manager 里选中若干任务 → Actions → **Retrieve Predictions** 批量拉取。
**类别名必须和 labeling config 里的 `<Label value="...">` 完全一致。** 后端用的是权重里的
`model.names`(也就是训练时 `data.yaml` 的 names),对不上的话 Label Studio 会拒绝这条预测。
我们的模型是 9 类:black、blackdot、grease、mosquito、normal、obstacle、ripple、uneven、wrinkle。
## 环境变量
| 变量 | 默认 | 说明 |
| --- | --- | --- |
| `YOLO_WEIGHTS` | `yolo26n.pt` | 权重路径。版本号里带它的修改时间,换权重后 Label Studio 能区分新旧预测 |
| `YOLO_CONF` | `0.25` | 置信度阈值。预标注可以调低些,宁可多给几个框让人删 |
| `YOLO_IMGSZ` | `640` | 推理尺寸,与训练保持一致 |
| `YOLO_DEVICE` | `cuda` | `cuda` / `cpu` / `mps` |
| `RETRAIN_AFTER` | `0` | 累计多少条新标注触发重训,0 = 不触发 |
| `RETRAIN_COMMAND` | 空 | 触发时执行的命令 |
## 边标边训
**在 Label Studio 里点 Settings → Model → Start Training**,后端收到 `START_TRAINING`
事件,起一个子进程跑 `scripts/retrain_from_label_studio.sh`,立刻返回——训练要几十分钟,
绝不能在这个请求里同步做,否则会把 Label Studio 卡住。
触发规则:
- **Start Training 按钮无条件触发**,不看攒了多少条;
- 新增标注累计到 `RETRAIN_AFTER` 也会自动触发(默认 0 = 只手动)。只数
`ANNOTATION_CREATED`,`UPDATED` / `DELETED` 是对已有标注的修改,不该把阈值顶到;
- 已有重训在跑时重复点击会被忽略,不会叠起多个训练。
重训脚本做四件事:导出标注 → `build_yolo_dataset.py` 装配 → `detect.py` 训练 →
把 `runs/detect/current.pt` 软链接指向新权重并重启本服务。启动脚本默认读这个软链接,
所以换模型不用改任何脚本。**权重只在进程启动时加载一次**,这也是重训末尾要重启服务的原因。
训练进度看 `$RETRAIN_LOG`(默认 `/tmp/retrain.log`);Label Studio 网页端不显示训练进度。
## 已验证的行为
本地用 COCO 预训练权重实测过:
- `/health` 返回 UP;
- `/predict` 用 Label Studio 的真实请求体(tasks + label_config + params)调用,
`bus.jpg` 返回 10 个框,坐标换算正确(百分比、`original_width/height` 与原图一致);
- 图片无检出时返回一条空 `result` 的预测,表示"这张图没问题";
- `/webhook` 连发 3 条 `ANNOTATION_CREATED`,在第 3 条触发 `RETRAIN_COMMAND` 并把计数清零。
转换逻辑的单元测试在 `tests/test_ml_backend_convert.py`,跑在主环境里
(`convert.py` 刻意不 import `label_studio_ml`,就是为了能这样测)。