feat(scripts): 重训后同时维护 current.pt 与 best.pt

current.pt 指向最新一次训练的结果(后端与 push_predictions 都读它),
best.pt 指向历史上 mAP50 最高的那版,留作回退与对照。

数据集每次重训都在变,这个比较只是粗略参考而非严格的同条件对比,所以只用来
维护 best.pt 这个指针,不拦截 current.pt 的切换。

用服务器上三次真实训练验证过:首次建立、更好时更新、更差时保持不变。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Shaun Zhou
2026-09-23 16:03:00 +08:00
co-authored by Claude Opus 5
parent fad1fe9fbb
commit 6e746b7351
2 changed files with 31 additions and 4 deletions
+6 -3
View File
@@ -83,9 +83,12 @@ Label Studio 里:Settings → **Model** → Connect Model,URL 填 `http://lo
`ANNOTATION_CREATED`,`UPDATED` / `DELETED` 是对已有标注的修改,不该把阈值顶到; `ANNOTATION_CREATED`,`UPDATED` / `DELETED` 是对已有标注的修改,不该把阈值顶到;
- 已有重训在跑时重复点击会被忽略,不会叠起多个训练。 - 已有重训在跑时重复点击会被忽略,不会叠起多个训练。
重训脚本做四件事:导出标注 → `build_yolo_dataset.py` 装配 → `detect.py` 训练 → 重训脚本做五件事:导出标注 → `build_yolo_dataset.py` 装配 → `tile_dataset.py` 切成固定
把 `runs/detect/current.pt` 软链接指向新权重并重启本服务。启动脚本默认读这个软链接, 边长的片 → `detect.py` 训练 → 更新软链接并重启本服务。
所以换模型不用改任何脚本。**权重只在进程启动时加载一次**,这也是重训末尾要重启服务的原因。
两个软链接:**`current.pt`** 指向最新一次训练的结果(本服务与 `push_predictions.py` 都读它),
**`best.pt`** 指向历史上 mAP50 最高的那版,留作回退与对照。换模型不用改任何脚本。
**权重只在进程启动时加载一次**,这也是重训末尾要重启服务的原因。
训练进度看 `$RETRAIN_LOG`(默认 `/tmp/retrain.log`);Label Studio 网页端不显示训练进度。 训练进度看 `$RETRAIN_LOG`(默认 `/tmp/retrain.log`);Label Studio 网页端不显示训练进度。
+25 -1
View File
@@ -71,9 +71,31 @@ echo "--- 4/5 训练 -> $WORK_DIR"
--set data.yaml="$TRAIN_DATA" $IMGSZ_SET $EXTRA_SET --set data.yaml="$TRAIN_DATA" $IMGSZ_SET $EXTRA_SET
echo "--- 5/5 切换权重" echo "--- 5/5 切换权重"
# 指向最新权重的稳定软链接:启动脚本与 push_predictions 都读它,换模型不用改任何脚本 # 两个稳定软链接,换模型不用改任何脚本:
# current.pt —— 最新一次训练的结果,后端与 push_predictions 都读它
# best.pt —— 历史上 mAP50 最高的那版,留作回退与对照
ln -sfn "$WORK_DIR/weights/best.pt" "$REPO/runs/detect/current.pt" ln -sfn "$WORK_DIR/weights/best.pt" "$REPO/runs/detect/current.pt"
map50_of() { # $1 = 指向某次训练 weights/best.pt 的路径
local metrics
metrics="$(dirname "$(dirname "$1")")/metrics.json"
[ -f "$metrics" ] || return 1
"$PYTHON" -c "import json,sys; print(json.load(open(sys.argv[1]))[\"map50\"])" "$metrics"
}
NEW_MAP=$(map50_of "$WORK_DIR/weights/best.pt" || echo "")
OLD_BEST=$(readlink -f "$REPO/runs/detect/best.pt" 2>/dev/null || echo "")
OLD_MAP=$([ -n "$OLD_BEST" ] && map50_of "$OLD_BEST" || echo "")
# 数据集每次重训都在变,这个比较只是粗略参考,不是严格的同条件对比
if [ -z "$OLD_MAP" ] || [ -z "$NEW_MAP" ] || \
awk "BEGIN{exit !($NEW_MAP >= $OLD_MAP)}"; then
ln -sfn "$WORK_DIR/weights/best.pt" "$REPO/runs/detect/best.pt"
echo " best.pt 更新为本次(mAP50 ${NEW_MAP:-未知},此前 ${OLD_MAP:-无})"
else
echo " best.pt 保持不变(本次 mAP50 $NEW_MAP < 此前 $OLD_MAP)"
fi
if [ "$RESTART_BACKEND" = "1" ]; then if [ "$RESTART_BACKEND" = "1" ]; then
echo "--- 重启 ML backend" echo "--- 重启 ML backend"
# 必须显式传 YOLO_WEIGHTS 并清掉继承来的变量:本脚本可能是旧后端的子进程, # 必须显式传 YOLO_WEIGHTS 并清掉继承来的变量:本脚本可能是旧后端的子进程,
@@ -90,6 +112,8 @@ fi
echo "===== [$(date '+%F %T')] 重训完成:$WORK_DIR =====" echo "===== [$(date '+%F %T')] 重训完成:$WORK_DIR ====="
echo "指标见 $WORK_DIR/metrics.json,曲线见 $WORK_DIR/results.png" echo "指标见 $WORK_DIR/metrics.json,曲线见 $WORK_DIR/results.png"
echo "current.pt -> $(readlink "$REPO/runs/detect/current.pt")"
echo "best.pt -> $(readlink "$REPO/runs/detect/best.pt")"
if [ "$TILE_SIZE" -gt 0 ]; then if [ "$TILE_SIZE" -gt 0 ]; then
echo "推理请用同样的尺寸:--tile-size $TILE_SIZE --tile-overlap $TILE_OVERLAP" echo "推理请用同样的尺寸:--tile-size $TILE_SIZE --tile-overlap $TILE_OVERLAP"
fi fi