YOLO从零到论文:模型改进与科研实战全攻略
YOLO基础篇 · A26
作者:迪菲赫尔曼
大家好,我是迪菲赫尔曼。
前面已经准备好数据、标签和配置,也理解了 Loss。现在把它们放到一次真正的服务器训练里。
第一次训练容易同时卡在几件事上:不知道程序读了哪份数据,不敢关掉本地电脑,终端没动静就反复启动,结束后又找不到 best.pt。这一篇沿着一次实际操作讲完:准备材料、进入会话、启动训练、读日志、重新连接、核对输出,最后取回成果。
先用 4 张训练图、4 张验证图的 COCO8 检查链路,再按自己的情况选择自采月饼或 A24 的完整宠物案例。这次先建立一条你能解释、能检查、能复查的训练流程。
1. 先确认环境、权重与数据
A13 的解释器、源码与 GPU 自检已经通过,服务器课程目录已有指定 weights/yolo26n.pt。本篇把 tmux、训练启动、日志观察和成果备份串成一次完整训练。
在远端终端执行:
bash
cd /root/lanyun-tmp/yolo-study
source env/bin/activate
python -c "import sys, torch, ultralytics; print(sys.executable); print(ultralytics.__file__); print(torch.cuda.is_available())"
ls weights/yolo26n.pt
最后的 CUDA 可用项应为 True。找不到权重就先按 A15 上传,不能把网页链接保存成一个同名文本文件冒充权重。
第一次运行会同时检验环境、训练入口、数据读取、GPU 和输出保存。若自己的标注还在整理,先用固定小数据能减少同时排查的因素。
COCO8 只有 4 张训练图、4 张验证图。它是流程检查材料,不是足以证明研究效果的数据。 原类别表有 80 类,不能为了让文件看起来像月饼,就把它改成 0: mooncake。
从官方 COCO8 下载地址下载 coco8.zip,上传到服务器的 datasets 下,执行:
bash
python -m zipfile -e datasets/coco8.zip datasets
确认 datasets/coco8/images/train、images/val 和对应标签都存在。将 coco8_data.yaml 保存为服务器的 datasets/coco8/data.yaml,保留完整的 names,再明确根路径为:
yaml
path: /root/lanyun-tmp/yolo-study/datasets/coco8
这里只展示要核对的第一项,不能把这一行单独保存成完整数据配置。完整 train、val 与 names 都来自配套 YAML。
2. 把脚本放对地方,读懂第一次的参数
将 train_first.py 放到服务器课程根目录。不是放进源码包内部,也不是只保存在自己电脑里。
text
yolo-study/
├── env/
├── code/ultralytics-8.4.183/
├── datasets/coco8/data.yaml
├── weights/yolo26n.pt
├── outputs/
└── train_first.py
脚本中真正训练的部分是:
python
model = YOLO(str(model_path))
model.train(
data=str(args.data.resolve()),
epochs=3,
imgsz=320,
batch=2,
device=0,
workers=0,
amp=False,
plots=True,
project=str(ROOT / "outputs"),
name=args.name,
exist_ok=False,
seed=0,
)
其中 ROOT = Path(__file__).resolve().parent 以脚本所在目录为基准寻找权重。A13 已解释这个写法,所以脚本放错位置会改变相应路径。
| 参数 | 本次值 | 它控制什么 |
|---|---|---|
| data | 具体数据 YAML | 读哪份数据、使用什么类别 |
| epochs | 3 | 计划训练多少轮 |
| imgsz | 320 | 训练输入尺寸约定,后续还受图像整理流程影响 |
| batch | 2 | 每批图片数量,不是每图目标数量 |
| device | 0 | 使用当前可见的第 0 张 GPU |
| workers | 0 | 先不启用额外数据加载工作进程 |
| amp | False | 第一次用明确的全精度路径检查 |
| project/name | outputs/实验名 | 保存结果的位置 |
| seed | 0 | 固定随机种子,不能由此承诺跨平台逐位一致 |
这些是入门检查设置,不是 RTX 4090 的最佳性能参数。后面 A31 再逐项改变,而不是第一次就同时追求最大 batch、最高分和最快速度。
exist_ok=False 用于避免直接复用已有输出。框架发现同名目录时可能递增名称,所以最后应以实际打印的保存目录为准,不能只凭自己写了什么 name 推断输出位置。
3. 先建立服务器会话,再启动训练
tmux 在服务器上管理终端会话。你可以让训练在会话内运行,再分离本地连接,之后重新接回。
它不能让一台已经关机的实例继续计算,也不能阻止平台回收资源。进程崩溃、磁盘满、训练报错,仍然需要检查和恢复。
把连接、会话和训练放进服务器这层盒子里,再看每个操作只影响哪里。

图解|断开连接、训练结束、实例关机分别影响哪一层。作用范围示意 · 会话存在不证明训练仍然健康
这是关系示意;会话存在不证明训练健康,日志/进程和平台状态仍要分别检查。
先运行 tmux -V 确认服务器已有它。没有时,按平台或系统管理员允许的方式安装;不要为了这一步改坏公共环境。
在远端终端执行:
bash
tmux new -s yolo-shot
yolo-shot 是这里取的会话名,与磁盘上的项目文件夹名不是同一件事。若提示同名会话已经存在,先用 tmux ls 查看,再用 tmux attach -t yolo-shot 进入;不用为了消除提示先杀掉已有会话。
进入会话以后,再切到学习目录,执行 source env/bin/activate 激活课程环境。按 A13 打印 Python 与源码位置,确认没有因为新 Shell 回到另一份环境。
确认现在已经进入 yolo-shot,当前目录为课程根目录,并已激活 env。然后执行:
bash
cd /root/lanyun-tmp/yolo-study
source env/bin/activate
mkdir -p outputs
python -u train_first.py --data datasets/coco8/data.yaml --name first_coco8_gpu > outputs/first_coco8_gpu.log 2>&1
这里 --data、--name 是本篇脚本接收的命令行参数,不是让你把它们写进 Python 文件中。
它们让同一份入口能明确选择数据和实验名,不需要第一次就复制四个几乎相同的脚本。--data 后的相对路径按执行命令的当前目录解释,所以前面要求先 cd 到课程根目录。
重定向日志后,当前窗口可能不再持续打印训练行,这是正常的。另开远端终端查看:
bash
tail -n 40 /root/lanyun-tmp/yolo-study/outputs/first_coco8_gpu.log
同名日志会被覆盖,第二次实验换新名称;不要为了找结果反复启动同一条命令。
-u 减少 Python 输出缓冲;> 把标准输出写入文件;2>&1 把错误信息也写入同一日志。这是一条前台命令,没有新提示符不代表终端卡死。不要在日志尚未确认之前再次运行同一条训练命令。
4. 训练开始以后,按版本、数据和进度读日志
开头会打印 Ultralytics、Python、PyTorch、设备和训练配置。你应该能确认:固定源码 8.4.183、当前服务器 GPU、任务是 detect。
再看数据扫描:这份 COCO8 应读到 4 张训练图和 4 张验证图。若扫描的是自己的几百张月饼,说明你这次传入了另一份配置;那不一定错误,但不能再与本段的预期数量逐字比较。
模型结构和参数量会显示很多行。这一步不用背每一层,先确认没有在加载源码或建立模型时就终止。
常见列包括:
Epoch:当前轮数与总轮数。GPU_mem:相关 GPU 显存显示,不是电脑硬盘剩余空间。- 各项
loss:当前训练过程中计算的误差项;A25、A29 会解释含义。 Instances:这一批里统计到的目标相关数量,不是整个数据集的图片总数。Size:当前输入尺寸相关显示。
进度条达到一轮末尾后,通常会运行验证,再进入下一轮。验证打印 P、R、mAP 是在检查预测与标签,不是在继续给验证图反向更新参数。
Loss 不保证每轮都下降。只有几张图时,指标尤其容易波动;不要拿某一轮一个小数点就判断模块有没有用。
5. 分离、重新连接,并判断训练到底是什么状态
先按 Ctrl+B,松开,再按 D,将会话分离。不要在训练终端里打出文本 Ctrl+B D。
回到普通 SSH 终端后,可以查看:
bash
tmux ls
以后重新登录,再连接:
bash
tmux attach -t yolo-shot
看到会话存在,只证明会话还在。训练也可能已经成功结束或报错,继续看日志与进程。
对照一次真实的"分离---回来"

看到 [detached (from session yolo-shot)],说明退回外层终端;不是退出服务器,也不表示会话中曾经运行过的训练一定还健康。
再执行前面的 tmux attach -t yolo-shot,实际回到同一个会话:

图中依次显示课程目录、课程 Python 和当前会话名。这里只演示会话操作,没有启动训练;不能把它当成断线后模型仍在训练的证据。
bash
cd /root/lanyun-tmp/yolo-study
tail -n 40 outputs/first_coco8_gpu.log
持续观察可以用 tail -f,退出查看按 Ctrl+C。注意:你退出的是这个查看命令,不是另一会话中的训练进程。
日志长时间不更新时,检查进程、设备、磁盘和数据读取。不能只看 GPU 占用就判断学习正常,日志里还需要有轮数、损失与验证进展。
重新连上以后,先判断状态,再决定下一步:
| 看到的情况 | 下一步怎么做 |
|---|---|
| 会话在,日志轮数仍推进 | 继续观察,不再启动第二个训练 |
| 会话在,日志已有完成信息与保存目录 | 检查成果文件,训练进程可能已经正常结束 |
| 会话在,日志末尾是 Traceback | 从第一处相关报错排查,会话存在不代表训练成功 |
| SSH 连不上 | 先看实例状态与连接信息,不能直接断言检查点丢失 |
tail -f 只是日志观察命令。在观察窗口按 Ctrl+C,结束的是观察;在训练所在窗口按相同按键,影响的则是训练本身。先辨认当前窗口,再操作。
6. 训练结束,确认这一轮真正留下了什么
实际保存目录下应有类似内容:
text
outputs/first_coco8_gpu/
├── args.yaml
├── results.csv
├── results.png
└── weights/
├── best.pt
└── last.pt
A27 会逐个解释。现在先检查文件存在且不是 0 字节;下一篇开头提供 predict_trained_detect.py 的完整附件与明确命令,用本次 best.pt 再做预测。不需要自己猜测怎样把训练入口改成预测入口。
best 是当前验证规则下选出的检查点,不是"已经达到论文效果"的证明。 last 也不能因为名字叫最后一个,就保证还保留完整的中断恢复状态,A33 会专门区分。
7. 再训练自己准备好的数据
这一节只给已经完成 A21---A24 自采数据检查的同学执行。 使用现成资料路线的同学,完成 COCO8 流程后直接阅读 A27,在 A27 训练 A24 的宠物数据;不必创建一个空的月饼目录,也不把 A22 单图练习当作训练集。
A24 已检查通过的配置应为:
text
datasets/mooncake/data.yaml
其中 path 指向 datasets/mooncake/prepared,不是前面原始 raw 图片,也不是 COCO8。
继续使用同一份脚本:
bash
python -u train_first.py --data datasets/mooncake/data.yaml --name first_mooncake_gpu > outputs/first_mooncake_gpu.log 2>&1
这次应读到 A24 报告里的月饼数量,类别也应是你实际标注的 mooncake。
COCO8 的运行数字对应它自己的图片与类别;换成自采月饼以后,需要重新训练和验证,才能判断这份数据上的效果。想先完成一套资料齐全的真实数据练习,可以继续 A24、A27 与 R06 宠物头部检测连续案例:它有固定划分、GPU 训练、错误样本和对照记录,然后再迁移自己的月饼。
你可以用下面这张对应图,再检查一次自己跑的是哪条路线。

图解|COCO8检查路线与自采月饼路线分别保存什么。文件对应关系示意 · 输出以实际save_dir为准
没有完成自采数据检查,就先沿 COCO8 流程继续学习,再完成 A24 的数据准备和 A27 的训练排错,不需要创建空的月饼目录。
8. 训练失败与真正中断,分别处理
找不到 YAML,先查服务器文件与完整路径;找到 YAML 但图片不存在,再查配置里的根目录。CUDA 不可用,回到 A12/A13,不把 device=0 悄悄改为 CPU 后继续宣称 GPU 已经通过。
显存不足先记录参数、检查 GPU 进程,从 batch 开始逐项调整。训练结束但找不到结果,先读日志最后打印的实际保存目录,而不是在本地电脑搜索一个从未下载回来的文件。
先确认旧进程已经停止,避免重复训练写同一目录。检查 last.pt 是否完整,再按 A33 的恢复方式继续。
best.pt 用于选定模型,不自动等价于具备完整训练恢复状态;重新加载一个权重启动新训练,也与恢复优化过程不同。
后面的队列工具同样只管理命令状态;是否恢复训练内部状态,仍要明确指定检查点与模式。
完整报错读取方法见 A13。
先排除旧进程还在运行的情况,再处理真正的中断。恢复时需要哪些状态、为什么正常结束后的 last.pt 不一定适合续训,A33 会用真实中断例子展开。
9. 把成果取回本地,再检查资源状态
先在远端整理一个独立实验目录,保留配置、CSV、日志、权重和数据划分说明。
在本地终端执行示意命令:
bash
scp -P 2222 -r root@203.0.113.10:/root/lanyun-tmp/yolo-study/outputs/my_run ./my_run_backup
用户名、地址、端口、远端路径都换成真实值。scp 端口参数是大写 -P;目标 ./my_run_backup 位于本地当前目录。
复制完成后,核对关键文件大小与 SHA-256。不要只看目录名字出现了,就认为所有大权重都传完了。
结束 Python 训练只停止计算进程,不自动停止租用实例。关闭 SSH 或 VS Code 也不等于停止实例。
备份确认完成后,按平台控制台或正式工具停止不需要的资源,并检查状态。磁盘、快照或其他关联资源是否继续计费,按当时规则分别核对。
释放或删除资源可能影响数据保留,先确认再执行。不同平台的停止和存储规则不同,需要按你当前使用的平台核对。
这一课完成以后,你应当能指出数据配置、初始化权重、真实保存目录和本地备份的位置。下一篇再打开 args.yaml、CSV、曲线和预测图,把这次训练的结果读明白。
会话操作参考 tmux 官方入门说明。
本篇完整代码与记录
按正文指定的路径与文件名保存以下内容,文件之间的目录关系保持一致。
配套练习/coco8_data.yaml
yaml
train: images/train
val: images/val
names:
0: person
1: bicycle
2: car
3: motorcycle
4: airplane
5: bus
6: train
7: truck
8: boat
9: traffic light
10: fire hydrant
11: stop sign
12: parking meter
13: bench
14: bird
15: cat
16: dog
17: horse
18: sheep
19: cow
20: elephant
21: bear
22: zebra
23: giraffe
24: backpack
25: umbrella
26: handbag
27: tie
28: suitcase
29: frisbee
30: skis
31: snowboard
32: sports ball
33: kite
34: baseball bat
35: baseball glove
36: skateboard
37: surfboard
38: tennis racket
39: bottle
40: wine glass
41: cup
42: fork
43: knife
44: spoon
45: bowl
46: banana
47: apple
48: sandwich
49: orange
50: broccoli
51: carrot
52: hot dog
53: pizza
54: donut
55: cake
56: chair
57: couch
58: potted plant
59: bed
60: dining table
61: toilet
62: tv
63: laptop
64: mouse
65: remote
66: keyboard
67: cell phone
68: microwave
69: oven
70: toaster
71: sink
72: refrigerator
73: book
74: clock
75: vase
76: scissors
77: teddy bear
78: hair drier
79: toothbrush
配套练习/train_first.py
python
import argparse
from pathlib import Path
from ultralytics import YOLO
ROOT = Path(__file__).resolve().parent
DATA = ROOT / "datasets" / "mooncake" / "data.yaml"
NAME = "first_mooncake_gpu"
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--data", type=Path, default=DATA)
parser.add_argument("--name", default=NAME)
args = parser.parse_args()
model_path = ROOT / "weights" / "yolo26n.pt"
print("Initial weights:", model_path)
print("Data config:", args.data.resolve())
model = YOLO(str(model_path))
model.train(
data=str(args.data.resolve()),
epochs=3,
imgsz=320,
batch=2,
device=0,
workers=0,
amp=False,
plots=True,
project=str(ROOT / "outputs"),
name=args.name,
exist_ok=False,
seed=0,
)
print("Training output:", model.trainer.save_dir)
if __name__ == "__main__":
main()