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>
This commit is contained in:
Shaun Zhou
2026-09-21 11:00:01 +08:00
co-authored by Claude Opus 5
parent 1b8c8d7cac
commit e61fd5887c
5 changed files with 211 additions and 33 deletions
+34
View File
@@ -69,3 +69,37 @@ def test_score_is_kept_per_box():
[("blackdot", [0.1, 0.1, 0.2, 0.2], 0.42)], 64, 64, FROM_NAME, TO_NAME, "v1"
)
assert prediction["result"][0]["score"] == pytest.approx(0.42)
# ---------------- 重训触发的语义 ----------------
#
# fit() 本身要 import label_studio_ml,主环境里装不了,所以这里只把它的决策逻辑
# 单独拎出来验证:什么情况下该训、什么情况下不该。规则见 ml_backend/model.py。
def should_retrain(event: str, count: int, after: int) -> bool:
"""与 FilmDefectDetector.fit 中的判定保持一致。"""
if event == "START_TRAINING":
return True
return bool(after and count >= after)
@pytest.mark.parametrize(
"event,count,after,expected",
[
("START_TRAINING", 0, 0, True), # 人点的按钮,无条件训
("START_TRAINING", 3, 200, True), # 没攒够也训
("ANNOTATION_CREATED", 199, 200, False),
("ANNOTATION_CREATED", 200, 200, True),
("ANNOTATION_CREATED", 999, 0, False), # after=0 表示只手动触发
],
)
def test_retrain_trigger_rules(event, count, after, expected):
assert should_retrain(event, count, after) is expected
def test_only_new_annotations_count_toward_the_threshold():
"""UPDATED / DELETED 是对已有标注的修改,不该把阈值提前顶到。"""
counted = [e for e in ("ANNOTATION_CREATED", "ANNOTATION_UPDATED", "ANNOTATION_DELETED",
"START_TRAINING") if e == "ANNOTATION_CREATED"]
assert counted == ["ANNOTATION_CREATED"]