简介:这是一份面向计算机相关专业毕业设计场景的模型训练配套资源,基于 Faceforensics 与 Celeb-DF 数据集整理构造,适合需要制作深度伪造检测或人脸特征提取项目的学生使用,也可作为课程设计或项目初期立项演示。资源核心是一条可运行的 landmark 提取流程:通过 mediapipe 对人脸视频逐帧提取 468 维三维特征,并提供批量提取脚本、工具函数与说明文档,用户只需修改视频文件夹路径即可整批处理。压缩包共 8 个文件,包括 6 个 Python 脚本、1 个 md 说明文件和 1 个 DS_Store 文件,整体仅 12KB,代码紧凑、模块划分清楚,可在原有基础上扩展。目前已有 210 人浏览学习,能够帮助快速搭建数据预处理与特征提取环节,显著缩短毕业设计前期探索时间,适合初学者参考与进阶。
1. 拿到“数据集+源代码+文档说明”时,先想清楚你要补哪块短板
做毕业设计最难受的不是写代码,而是代码、数据、文档三者缺一不可。所谓“用于毕业设计模型训练的数据集+源代码+文档说明”,本质上是一套完整的项目资产包:数据集解决“拿什么训”,源代码解决“怎么训”,文档说明解决“怎么把训练过程讲清楚并写进论文”。对多数学生来说,源代码最容易拿到,数据集最容易翻车——标注格式不统一、类别分布失衡、训练验证划分混乱,这些坑不会立刻报错,但会在训练指标上狠狠给你上一课。
这套东西适合三类人:一是刚定题、需要快速跑通一个基线模型的学生;二是换题重做、想复用已有代码框架的学生;三是导师要求“必须有实验数据和完整代码”才能过审的学生。本文按“先验证数据、再跑通代码、后调参数、最后写文档”的顺序,把这条路上最值得复制的一整套操作和踩坑经验拆给你。
2. 先跑通环境,再碰模型:最小可行的代码运行路径
拿到源代码之后,最容易出现的翻车现场是:代码能看懂、数据集也摆在眼前,但一运行就报模块缺失、版本冲突、显存不足。我一般不会急着看训练逻辑,先把环境整理干净,再用最小配置跑通一次前向传播,确认“代码能跑”这个基本盘。
2.1 用虚拟环境锁依赖,别把系统环境变成黑匣子
常见做法是先创建一个独立的 Python 虚拟环境,再按源代码里的 requirements 顺序安装依赖。不要直接用全局环境,原因是毕设项目之间依赖版本经常互相打架——这个项目要 torch 2.x,上个项目还停留在 1.x,全局装完基本没法收拾。
# 创建 python 3.10 虚拟环境,名字用项目名缩写,方便后面切换 python3.10 -m venv venv_bs source venv_bs/bin/activate # 先升级 pip,再安装核心依赖 pip install --upgrade pip pip install -r requirements.txt这段命令里的关键点是 python 版本。很多源代码是基于 torch 1.x 时代的写法,直接装最新版 torch 可能 API 已经变了;反过来,有些新代码要求 python 3.10+,老版本会直接语法报错。所以第一步先看 requirements 里有没有python_requires或torch版本约束,再决定用哪个 python 版本建环境。把版本锁住,后面调参才有后悔药。
依赖装完后,先跑一个最小的数据加载测试,而不是直接启动完整训练。因为完整训练一旦中途崩溃,时间成本和排查成本都会翻倍。
2.2 预训练模型先下载并放到指定位置:不下载就训练,等于从零开始
很多源代码仓库里写了pretrained=True,但实际训练时并不会自动为你下载权重。更常见的情况是,代码会尝试从 Hugging Face 或官方源拉取预训练模型,而网络状况不佳时下载会中断,最后抛出超时异常。你以为是代码问题,其实只是权重文件没到位。
# 以 ultralytics YOLOv8 为例,官方权重名是 yolov8n.pt / yolov8s.pt 等 # 预先下载并放到项目根目录或 weights 目录里 # 然后把训练脚本中的 model 路径指到本地文件 ls -lh weights/ # 输出示例: -rw-r--r-- 12M yolov8n.pt我一般会准备至少一个轻量级预训练权重(比如 n 或 s 级别),哪怕最终要训 s 或 m 模型,n 权重也能用来快速验证代码链路是否通畅。这一步别省。很多毕设方案里写“加载预训练模型”,论文里要写清楚用的是哪个权重、从哪下载的,所以把文件路径和文件名记录下来,后面写文档说明时直接能用。
2.3 数据集目录结构先扫一遍:缺 label 文件比缺图片更致命
拿到数据集后,第一件事不是看图片内容,而是看目录层级是否符合源代码的读取逻辑。以目标检测为例,最常见的结构是 images 和 labels 分开放,train 和 val 分开;但有的数据集把标注文件混在同一目录里,有的用 VOC 格式放 XML,而代码里写的是 YOLO 格式的 txt。这一步不核对,后面训练时要么报No labels found,要么 loss 诡异波动。
# 找一个典型目录,查看 data.yaml 内容 cat data.yaml # 输出示例: # train: /data/train/images # val: /data/val/images # nc: 2 # names: [cat, dog]如果一个数据集目录里直接放着data.yaml,那大概率是按 YOLO 系列格式整理的,适配性最好。如果只有图片文件夹和同名 XML 文件,就需要先做格式转换,这部分在第三章重点处理。先跑通环境阶段,只要确认图片、标签、配置文件三者目录对得上,就可以进行下一步了。
3. 数据集校验与格式对齐:训练前最该花时间的三个校验脚本
数据集不是“能打开图片”就行,真正做到可训练,需要满足三个条件:标注格式与代码读取格式一致、类别的数量和分布符合预期、训练集与验证集划分严格不重叠。这里给出三个可以直接抄的 Python 脚本,按顺序跑完,大部分数据集问题都能暴露在训练之前。
3.1 标注格式统一:快速把所有 VOC XML 转成 YOLO txt
毕设数据集里最常见的是 VOC 风格标注,即每张图对应一个 XML,记录目标框为xmin, ymin, xmax, ymax的绝对坐标。而 YOLO 要求的是归一化后的中心点坐标加宽高,存在一个 txt 里,每行是class_id cx cy w h。不转换,训练器会直接跳过这张图。
# convert_voc_to_yolo.py import xml.etree.ElementTree as ET import os # 只需要改这三个路径 xml_dir = "Annotations" # XML 所在目录 img_dir = "JPEGImages" # 图片所在目录 out_dir = "labels" # 输出 txt 目录 os.makedirs(out_dir, exist_ok=True) class_names = ["cat", "dog"] # 类别必须和 data.yaml 里的 names 完全一致 for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) txt_path = os.path.join(out_dir, xml_file.replace(".xml", ".txt")) with open(txt_path, "w") as f: for obj in root.findall("object"): cls = obj.find("name").text if cls not in class_names: continue # 跳过不在类别列表里的目标 cls_id = class_names.index(cls) box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) # 转换成归一化中心点坐标 + 宽高 cx = ((xmin + xmax) / 2) / img_w cy = ((ymin + ymax) / 2) / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h f.write(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n")这段代码的逻辑很直接:先读图片宽高,再遍历 XML 里的每个目标框,把绝对坐标转成归一化坐标。注意两个易错点:一是class_names的顺序必须和训练配置里的names完全一致,否则类别编号会错乱;二是转出来的 txt 是纯文本,不要加 BOM 头,也不要写任何额外行,否则解析器可能读错。转换完成后,随手抽查几个 txt 文件,确认坐标值都在 0 到 1 之间。
3.2 类别分布统计:一眼看出“有没有某个类别几乎没样本”
目标检测里最典型的翻车是:总共 5 个类别,其中一个类别只有 20 张样本,另外四个各有上千张。训练出来的模型对这个少数类几乎不识别,论文里的 PR 曲线却看起来还凑合。为了避免这个坑,我习惯在训练前跑一个统计脚本,把每个类别的目标框数量、对应图片数量拉出来看看。
# check_class_distribution.py import os label_dir = "labels" counts = {} for txt_file in os.listdir(label_dir): if not txt_file.endswith(".txt"): continue with open(os.path.join(label_dir, txt_file)) as f: for line in f: cls_id = line.strip().split()[0] counts[cls_id] = counts.get(cls_id, 0) + 1 # 按数量升序打印,排最前面的就是最可能出问题的类别 for cls_id in sorted(counts, key=counts.get): print(f"class {cls_id}: {counts[cls_id]} boxes")如果发现某个类别的框数量不足整体样本量的 5%,就要考虑:收集更多该类别数据,或者做数据增强,或者在论文里如实说明该类别识别效果受限。不要试图通过修改标注来弥补——那属于数据造假,答辩时被人问起来非常尴尬。
3.3 固定随机种子做训练/验证集划分:让每一次实验都可以复现
很多源代码仓库自带划分脚本,但也有不少只给了一个合并好的数据集,需要自己拆。划分的时候最大的坑是:直接按文件名前缀随机切,导致同一张图片的增强版本同时出现在训练集和验证集,结果验证分数虚高,论文里有“数据泄漏”的嫌疑。
# split_train_val.py import os import random from shutil import copy2 random.seed(42) # 固定种子,保证每次运行划分结果一致 data_root = "dataset" images = sorted(os.listdir(os.path.join(data_root, "images"))) random.shuffle(images) split_idx = int(len(images) * 0.85) # 85% 训练, 15% 验证 train_images = images[:split_idx] val_images = images[split_idx:] for split, img_list in [("train", train_images), ("val", val_images)]: os.makedirs(f"{data_root}/{split}/images", exist_ok=True) os.makedirs(f"{data_root}/{split}/labels", exist_ok=True) for img_name in img_list: base = os.path.splitext(img_name)[0] copy2(os.path.join(data_root, "images", img_name), os.path.join(data_root, split, "images", img_name)) # 注意:标签文件可能不存在,需要判断一下 label_src = os.path.join(data_root, "labels", base + ".txt") if os.path.exists(label_src): copy2(label_src, os.path.join(data_root, split, "labels", base + ".txt"))这里的random.seed(42)是复现的关键。很多同学嫌麻烦不写种子,每次跑出来训练集都不一样,前期调参时看到的效果差异,根本分不清是改参数还是换数据引起的。我一般还会把划分出的文件名列表存成一个 txt,方便后面写文档说明时附上“训练集包含哪 1150 张、验证集包含哪 200 张”这类具体信息。
3.4 图片完整性检查:坏图会让训练中途突然退出
最后一步检查不是看标注,而是验证图片本身能不能被 OpenCV 正常解码。有些数据集混入了 0 字节文件、损坏的 JPEG、或者其实是 PNG 但后缀写成 .jpg 的图片。这类问题训练时不一定立刻报错,但会在某个 epoch 的随机采样中让整个进程崩溃。
# check_images.py import cv2 import os img_dir = "dataset/train/images" bad_files = [] for img_name in sorted(os.listdir(img_dir)): path = os.path.join(img_dir, img_name) img = cv2.imread(path) # 读不出内容时返回 None if img is None: bad_files.append(path) continue # 顺手记录尺寸,也方便后面分析分辨率分布 h, w = img.shape[:2] if h < 32 or w < 32: bad_files.append(path) print("bad or too small:", len(bad_files)) for p in bad_files[:20]: print(p)这个脚本跑完之后,如果bad_files里确实有内容,建议直接从数据集里剔除,而不是试图修复——修复坏图片的成本远高于重新收集。另外顺带说一下,如果图片尺寸差异极大(从 200×200 到 4000×3000),训练时会频繁触发 resize,速度明显下降。这种情况下,可以在训练前统一把长边缩到某个固定值,比如 1280,再用缩放后的图片做训练,能省不少时间。
4. 训练脚本与参数设置:以 YOLOv8 为例讲清每个参数的作用
数据集校验通过后,才进入真正的训练环节。很多毕设源代码是基于 ultralytics YOLO 框架写的,因为可用预训练模型多、接口统一、文档齐全。这一章以该框架为例,讲清楚训练命令、核心参数、以及如何判断训练是否正常。
4.1 最小可复现的训练命令:先看链路的完整程度
我把训练过程拆成三步:加载预训练权重、在自有数据集上微调、导出验证指标。不建议一上来就调大 epoch 和 batch,先用最小配置跑 5 个 epoch,确认日志正常、loss 在下降、验证集能跑完,再正式启动长训练。
# train_yolov8.py from ultralytics import YOLO # 加载官方预训练权重,只作为初始化,后续会在自有数据上微调 model = YOLO("weights/yolov8n.pt") # 训练入口,所有参数都可以在命令行覆盖 results = model.train( data="dataset/data.yaml", epochs=5, # 先用 5 轮验证链路,没问题再调大 batch=8, # 按显存大小调整,一般 8 起步 imgsz=640, # 输入分辨率,越大越慢,对已有数据集尺寸先做评估 lr0=0.01, # 初始学习率,YOLO 系列常用默认值 device=0, # 0 表示使用第一块 GPU,CPU 训练则改为 cpu workers=4, # 数据加载线程数,Windows 上建议设低 ) # 训练结束后自动生成指标图,保存到 runs/detect/train 目录这段代码需要注意三点:第一,epochs=5不是正式训练量,而是“链路验证量”,确认每个环节不出错;第二,batch=8在 6G 显存上通常能跑动,再大的 batch 可能报显存不足,后面会讲怎么缓解;第三,device=0必须确认本机确实有 GPU,如果只有 CPU,设成cpu会慢很多但能跑。
跑完这 5 个 epoch 后,去runs/detect/train目录看结果。如果里面出现了results.png和confusion_matrix.png,说明整个训练和验证链路是通的;如果中途报错,优先看是数据加载问题还是显存问题,不要盲目改训练参数。
4.2 正式训练的三个必调参数:epochs、batch、imgsz 之间的取舍
链路跑通后,正式训练要在参数上做一个系统性的决定,而不是随手填。我一般按下面的表格来定参,每换一个数据集都会重新评估一遍,不会直接沿用别人的配置。
| 参数 | 选值逻辑 | 我常用的范围 | 尾注 |
|---|---|---|---|
| epochs | 看验证集 loss 曲线是否还有明显下降空间 | 50~150 | 小数据集 50 就够,大数据集 150 起步;早停机制建议保留 |
| batch | 显存允许范围内尽量大,但小于训练集图片数 | 8~32 | 显存不足优先减半,不要硬扛 |
| imgsz | 与数据集中目标尺寸直接相关,小目标用 1280 更好,但显存占用翻倍 | 640~1280 | 论文里要写清选值依据 |
| lr0 | 默认 0.01 对大多数情况可用,但数据量很小时降到 0.001 更稳 | 0.001~0.01 | 损失曲线震荡时优先降学习率,而不是加 batch |
| workers | Windows 下建议 2~4,Linux 可到 8 | 4~8 | 太大会导致 CPU 成为瓶颈,反而变慢 |
我建议每个参数只改一个,训完对比效果再改下一个。最忌讳的是同时改三个参数还都在不同 epoch 上,最后根本没法判断是哪个改动让结果变好。论文里的消融实验部分,如果你没保留这种“单因子变量”的习惯,就少了一组很有说服力的对比数据。
4.3 训练过程中看什么:loss 曲线之外更要看验证集指标
训练日志里每轮会打印当前 epoch 的 loss 值。很多新手只看 loss 是不是越来越小,忽略了一个关键点:训练 loss 在降,验证集指标可能在涨也可能在降。正确做法是以验证集指标为准,也就是每轮跑完之后的val部分的数值,尤其是mAP50和mAP50-95。
# 训练日志输出节选 # Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size # 50/150 3.2G 0.812 0.412 1.021 10 640 # Class Images Instances Box(P R mAP50 mAP50-95) # all 200 500 0.832 0.789 0.845 0.621如果发现训练 loss 一路降到很低,但mAP50在某个 epoch 后开始下滑,基本可以判断是过拟合了。解决办法优先级是:先加数据增强,再减小模型复杂度,最后才是降训练轮数。不要一上来就调模型结构,那是大动作,容易引入新问题。
4.4 断点续训与早停:让长训练不至于白跑
毕设训练通常要跑几个小时甚至过夜,中途断电或显存溢出很常见。ultralytics 框架本身就支持断点续训和早停,但如果用的是其他源代码,一般需要自己处理这两件事。
# 训练中断后从上次保存的权重继续训练 model = YOLO("runs/detect/train/weights/last.pt") results = model.train( data="dataset/data.yaml", epochs=150, # 填总轮数,框架会自动从 last.pt 的轮次继续 batch=16, imgsz=640, resume=True, # 关键:读取 last.pt 中的轮次和优化器状态 )resume=True是续训的关键,它不只恢复模型权重,还恢复学习率调度器状态,这是很多自己写的源码不注意的地方。早停则靠patience参数,一般设 20 或 30,意思是连续 20 轮验证集指标没提升就自动停止,节省时间同时避免过拟合。
5. 训练过程必踩的 5 个坑:现象、原因、解决一条龙
训练阶段的问题往往最折磨人,因为报错信息不一定直指根因。下面五条是我在毕设项目里见到最多、也最值得提前预防的情况,每条都按现象、原因、解决三段顺序写。
5.1 训练 loss 正常下降,但验证集 mAP50 长期低于 0.3
现象:训练 loss 曲线很好,模型似乎学到了东西,但mAP50就是上不去,多数类别基本不识别。
原因:绝大多数情况是数据集中存在严重的类别不平衡,或者标注框质量不过关。比如一张图里有 10 个小目标,但一个框画偏了导致目标中心落在错误位置,模型学到的是错误特征。
解决:按 3.2 节的统计脚本查各类别框数量;框数量明显偏少的类别,优先考虑加数据增强或补充对应样本。同时抽样画 20~30 张图的真实框和预测框做对比,人工看标注是不是准。这一步不做,后面调什么参数都是玄学。
5.2 训练中途显存溢出(OOM)
现象:前几个 epoch 很正常,跑到某个 epoch 的后半段直接报CUDA out of memory,训练中断。
原因:最直接的是 batch 太大,但还有一种隐蔽情况:模型在推理阶段根据输入图片尺寸自动调整特征图大小,某些特别大的图会把显存瞬间拉满。
解决:先把 batch 减半,如果还崩就再减。减 batch 后训练慢不少,我的习惯是同时开梯度累积,让实际见到样本的节奏保持不变。另外检查 imgsz 的值,如果设了 1280,降到 640 对显存是数量级缓解。
# 用梯度累积替代大 batch 的写法 model.train( data="dataset/data.yaml", epochs=100, batch=8, # 小 batch 硬跑 imgsz=640, amp=True, # 混合精度训练,显存不够时先开这个 warmup_epochs=3, # 不能省,否则小 batch 训练前期不稳定 )amp=True是混合精度的开关,很多新框架默认打开,但老代码不一定有。显存不够时先开 AMP,再不行减 batch,梯度累积是最后的手段。
5.3 训练集和验证集图片有重叠,验证指标虚高
现象:验证集指标高得离谱(mAP50 接近 0.98),显得很不真实;或者训练时验证指标波动极大。
原因:划分训练集和验证集时没有按图片维度去重,而是按文件名随机分配导致的。比如一张图片的原始版本在训练集,它的亮度增强版本在验证集,模型其实已经见过这张图了。
解决:重新划分数据集,划分前在代码里对图片做去重。如果数据集是从网上下载的,先计算每个图片的 md5 值,把内容相同的图直接去重,再按 3.3 节的脚本划分。
5.4 换了一台电脑,所有路径都失效
现象:源代码在自己电脑上能正常运行,换到实验室电脑或导师电脑上,报错信息全是FileNotFoundError或No such file or directory。
原因:源代码里写死了绝对路径,比如C:/Users/xxx/dataset/train/images,换机器自然失效。
解决:把代码里所有数据路径改成相对路径,以项目根目录为基准。用os.path.join拼接路径,不要直接写字符串。同时把data.yaml里的train和val统一改成相对路径写法。这一步做得好,后面写文档说明时也能体现专业度。
5.5 换了预训练模型,结构对不上
现象:从网上下载一个 YOLOv8m 预训练权重,代码里写的是 YOLOv8s,加载时报size mismatch,或者直接无法初始化。
原因:模型的 depth/multiple 参数不同,导致对应层的名称一致但形状不一致。框架的权重文件不是通用的,模型规格必须严格匹配。
解决:先用代码查一下当前模型对应规格。
from ultralytics import YOLO # 用 yaml 初始化并打印模型参数,确认规格 model = YOLO("yolov8s.yaml").load("yolov8m.pt") # 故意不匹配,观察报错报错后把load的权重换回 yolov8s.pt,不要试着改模型结构去适配权重,那样会让论文里的模型结构描述变得不可控。
6. 拿结果写论文:把训练产出变成文档说明里的硬材料
毕设文档说明不只要写“我训了一个模型”,而是要拿出可验证的记录:训练环境的准确版本、数据集的构成表、训练参数表、指标曲线。这些材料在训练结束那一刻就已经生成了,关键是知道去哪找、怎么引用、哪些该写进正文。
训练完成后,ultralytics 框架会在runs/detect/train下生成一组文件。我建议至少保留这些作为论文素材:results.png是 loss 和指标随轮次变化的曲线,直接可作为论文实验部分的图;confusion_matrix.png是分类混淆矩阵,比单纯说“准确率多少”更有说服力;weights/best.pt和weights/last.pt是两个关键权重,前者是验证集指标最好的一版,后者是最后一轮的一版。论文里应说明采用的是 best.pt。
用训练好的权重跑测试集推理时,记得把置信度阈值写清楚。我发现很多同学保存测试图时不写置信度和阈值,问起来就说“模型效果还行”。正确的做法是把预测框、类别、置信度都画在图上并导出,这些图可以直接进论文的“结果展示”章节。
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict( source="test_images/", # 测试图片目录 conf=0.3, # 置信度阈值,太低会出大量误检框 save=True, # 保存画框后的图片 save_txt=True, # 同时保存预测结果 txt project="inference_output" )文档说明里还应该包含三个表:环境信息表(python 版本、torch 版本、CUDA 版本、GPU 型号)、数据集构成表(类别名称、样本数量、标注格式、划分比例)、训练参数表(epochs、batch、imgsz、lr0、优化器)。这三个表不需要写得花哨,但必须完整,答辩时评委最容易从这些细节判断你是不是真的亲手把模型从零训起来的。
最后说个我自己的习惯:每次训练前,我会把当前环境的 pip list 输出保存一份、把 data.yaml 复制一份、把训练命令写进一个 run.sh 或 run.py,三者留在同一个实验目录里。这既是给论文准备原始记录,也是万一过几周要看某次实验是怎么跑的时,还有后悔药可吃。做好这一步,整套数据集加源代码加文档说明才算真正闭环。希望帮到你。
本文还有配套的精品资源,点击获取