ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

毕设深度学习项目实战:数据集制作、YOLO训练调参与可复现交付

毕设深度学习项目实战:数据集制作、YOLO训练调参与可复现交付 简介面向计算机相关专业毕业设计的人脸伪造/深度伪造检测项目提供配套的视频数据集说明、Python源代码与文档。数据基础来自Faceforensics约1000个真实视频与经DF伪造生成的约1000个视频后续更新补充Celeb-DF v1/v2两个版本内容代码使用mediapipe提取468维人脸关键点支持批量处理文件夹内视频直接用于模型训练前的特征工程。压缩包共8个文件含6个Python脚本人脸关键点提取、校准工具等、1个Markdown说明文档及辅助文件整体仅12KB轻量易用。已有210人学习下载。源码经实测运行通过上传者反馈毕业设计答辩评审平均分达96分适合计算机、人工智能、通信工程等专业学生作为毕设项目、课程设计或作业参考也可在此基础上二次开发。1. 毕设模型训练别急着跑代码先把数据集、源代码和文档说明当成一套交付物去设计答辩翻车的大多数情况不是最终模型精度不够高而是整个项目不可复现导师换台电脑运行你的源代码数据路径直接报错文档里写的命令和实际代码脱节辛辛苦苦训出来的权重被当成黑匣子。标题里的“数据集源代码文档说明”看起来是三样独立的东西实际上是一套闭环交付物——数据集决定模型能力的上限源代码决定别人能不能复现你的实验文档说明决定答辩时别人能不能看懂你的思路。这套方案适合做图像分类、目标检测、语义分割、OCR 这类深度学习毕业设计的学生也适合需要向导师交付完整工程的横向项目。下面按照拿到题目后的实际推进顺序来讲先定数据再选代码然后训练调参最后把文档补成能自证的交付物。2. 数据集选型与制作先定任务格式再动手收集和标注很多人拿到毕设题目第一步就是 git clone 一个模型仓库然后开始下载预训练权重最后才想起来数据还没有。正确顺序应该是先回答三个问题任务是什么、数据从哪来、标注格式是什么。任务类型决定了数据集的组织方式分类任务就是一套文件夹结构目标检测就是一套坐标标注体系分割任务又要换一套掩码格式。格式不统一后面所有训练代码都会卡在数据读取上。2.1 选数据集的三条路公开数据集、导师数据、自建数据怎么权衡公开数据集是最省力的一条路前提是题目本身允许使用。做鸟类识别系统CUB-200-2011 是经典选择细粒度分类方向每年都有人拿它做毕设官方标注齐全换 ResNet34 或者预训练模型做迁移学习都顺手。做目标检测VOC 和 COCO 适合当预训练来源但毕设题目往往带具体场景比如燃气管道图像数据集导师手里的工业数据和公开集差距很大直接用公开集训练出来的模型无法在真实场景里落地。遥感旋转目标检测用 DOTA 数据集配合 mmrotate 这类专门框架自动驾驶方向用 SemanticKITTI 做语义分割高光谱方向可以考虑 ICVL 公开数据集做压缩或分类实验。做 NLP 方向用 Hugging Face 的 datasets 库一条命令就能拉取数据再搭配 roberta 中文预训练模型做微调图神经网络方向则可以从公开来源获取 blogcatalog 这类论文常用数据集。导师提供的数据要单独处理。首先确认数据是否脱敏、是否允许写进论文、是否允许作为正式训练集发布不要因为数据版权问题在答辩前被卡住。最稳妥的做法是和数据提供方签一个简单说明写明数据用途仅限于毕业论文实验。自建数据则要控制质量用手机拍摄或现场采集时尽量覆盖不同光照、不同角度、不同距离类别数量要均衡检测任务里每个类别至少要有几百个标注框否则训练时模型容易偏向样本多的类别。2.2 把分类/检测数据整理成训练能吃的格式VOC标注转YOLO的转换脚本数据集拿到手之后第一件事不是训练而是把标注格式统一成目标框架认识的格式。做图像分类最简单的结构是按 train/val/test 三个子目录分好每个类别的图片放在对应类别名目录下PyTorch 的 ImageFolder 可以直接读。做目标检测则有三种常见格式VOC 的 xml、COCO 的 json、YOLO 的 txt。三者之间的转换脚本在每个毕设项目里都要写一遍YOLO 格式因为被 Ultralytics 等现代框架广泛支持通常作为最终落地的格式。# voc2yolo.py # 把VOC标注的xml转成YOLO格式的txt坐标归一化到[0,1] import os import xml.etree.ElementTree as ET from tqdm import tqdm classes [pipe, weld, leak] # 改成你自己的类别顺序一旦确定就不要改 def convert(img_dir, xml_dir, out_dir): os.makedirs(out_dir, exist_okTrue) xml_files [f for f in os.listdir(xml_dir) if f.endswith(.xml)] for xml_file in tqdm(xml_files): 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) lines [] for obj in root.iter(object): cls obj.find(name).text if cls not in classes: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 归一化并转为中心点宽高格式 cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{classes.index(cls)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(os.path.join(out_dir, xml_file[:-4] .txt), w) as f: f.write(\n.join(lines)) if __name__ __main__: convert(images, annotations/xml, annotations/labels)这段脚本的逻辑是遍历 xml 文件先读图片真实宽高再遍历每个目标对象取出 xmin、ymin、xmax、ymax 四个角点坐标转换成中心点加宽高的归一化表示。关键参数是 classes 列表它的顺序决定了模型输出层的类别编号训练和预测阶段必须保持一致。img_w 和 img_h 一定要取标注文件里记录的原始尺寸不能拿缩略图尺寸替代否则所有框都会偏移。脚本只处理简单嵌套结构的 VOC 文件如果标注里有旋转框或更复杂的物体需要换用专门工具。数据转换完之后还要做训练集和验证集的划分。常见做法是写一个 8:1:1 的划分脚本确保同一个目标不会同时出现在训练和验证里然后固定随机种子。2.3 数据增强先做翻转和色彩抖动别一上来就上生成模型见过不少学生一开口就是“我用扩散模型生成数据”但实际效果往往不如老老实实把真实数据用好。生成模型做数据补全应该放在常规数据增强之后作为最后的弥补手段。常规增强里左右翻转、随机亮度对比度、随机裁剪这几样就能覆盖大部分真实场景差异而且实现成本低、不会引入分布偏差。# augment.py # 对训练图片做轻量级增强标注框同步变化 import albumentations as A import cv2 import glob transform A.Compose([ A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.5), A.Cutout(num_holes8, max_hole_size24, fill_value0, p0.3) ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels], min_visibility0.3)) for img_path in glob.glob(train/images/*.jpg): img cv2.imread(img_path) label_path img_path.replace(images, labels).replace(.jpg, .txt) bboxes [] class_labels [] with open(label_path) as f: for line in f: parts line.strip().split() bboxes.append([float(x) for x in parts[1:5]]) class_labels.append(int(parts[0])) augmented transform(imageimg, bboxesbboxes, class_labelsclass_labels) cv2.imwrite(aug/ img_path.split(/)[-1], augmented[image]) with open(aug/ label_path.split(/)[-1], w) as f: for bbox, cls in zip(augmented[bboxes], augmented[class_labels]): f.write(f{cls} {bbox[0]:.6f} {bbox[1]:.6f} {bbox[2]:.6f} {bbox[3]:.6f}\n)核心参数是 bbox_params 里的 min_visibility0.3它表示增强后目标框与原始框的交并比小于 0.3 时直接丢弃该目标避免翻转或裁剪把目标切剩一个角。用 albumentations 的好处是它会在翻转时自动把 x 中心坐标重新计算不需要手工处理。还要注意YOLO 格式里 class 信息不写在 bbox 里必须通过 label_fields 传进去。这个离线增强脚本适合分类任务或者没有内置增强的框架用 YOLOv8 训练时官方在线增强里的 mosaic、hsv 已经很强离线增强加太多反而会让模型过拟合到增强噪声上。3. 源代码选型与最小改造从 git clone 到 first run四步拿到可复现基线代码选型这件事很多人只看 star 数。但毕设代码要的不是功能最多而是“能在毕业前稳定跑完”。选错了框架改 bug 的时间比训练时间还长。常见的可靠路径是目标检测用 Ultralytics YOLO 系列yolov5、yolov8、yolov11 都有官方训练脚本和文档适合训练自己的数据集旋转目标检测用 mmrotate 配 DOTAOCR 用 EasyOCR支持训练和加载自定义模型文本分类用 transformers 框架加载 roberta 中文预训练模型图像分类想用经典结构torchvision 官方 ResNet34 预训练权重直接拿来微调。如果只是快速验证想法Teachable Machine 这类无代码工具确实方便但毕业设计要交付可复现的源代码不建议把它作为主交付物。3.1 代码选型的三个判断点官方维护、依赖干净、任务匹配判断一段源代码能不能用于毕设看三点。第一官方维护状态看最近一次 commit 时间超过一年没更新的仓库慎用因为依赖很容易和最新环境冲突第二依赖是否干净requirements.txt 里如果全是基础包环境重建成本就低如果带一堆自定义算子换机器后编译环节会劝退你第三任务匹配度分类任务不需要硬上检测框架文本任务也不需要套视觉增强。一个简单的判断办法在 GitHub 搜索框里用你的任务加模型名字检索比如yolov8 train custom data优先选有官方文档且有训练参数解释的仓库这决定了后面调参时能不能搜到答案。3.2 最小跑通创建环境、下载预训练权重、做一次推理代码选好之后不要直接训练先把环境跑通。用 conda 创建独立环境Python 版本选 3.9 或 3.10然后安装依赖。# 创建独立环境避免污染系统Python conda create -n bishe python3.9 -y conda activate bishe # 安装CPU版或GPU版PyTorch先装torch再装ultralytics pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics8.1.0 # 下载yolo预训练模型并做一次推理验证环境 yolo predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg第一次推理成功后会在当前目录生成 runs/detect/predict 结果图说明模型下载和推理链路正常。yolo 命令的几个关键参数里model 指定权重文件首次运行时如果本地没有会自动从官方地址下载预训练模型并缓存到用户目录source 支持图片路径、视频路径、摄像头设备号或者 URL可以加 conf0.25 和 iou0.7 控制置信度阈值和 NMS 阈值这两个值直接决定检测框的多少置信度设太低会输出一堆误检框。环境跑通之后训练才有意义否则后面报错你分不清是代码问题还是安装问题。3.3 训练自己的数据集yaml配置、类别数、训练命令和恢复训练环境正常后先准备数据配置。Ultralytics YOLO 系列用 yaml 描述数据路径和类别。# gas_pipeline.yaml train: ./data/train/images val: ./data/val/images nc: 3 names: [pipe, weld, leak]yaml 里 train 和 val 写的是图片目录路径YOLO 会自动在同级目录下寻找 labels 文件夹。nc 是类别数names 是类别名列表顺序必须和标注 txt 里的 class id 一致。训练命令写成yolo train modelyolov8n.pt datagas_pipeline.yaml \ epochs100 imgsz640 batch8 \ projectruns namegas_v8n \ patience20这里 model 用 yolov8n.pt 作为预训练权重框架会在训练开始前把最后一层分类数自动改写成 nc 的值。epochs 设 100配合 patience20 实现早停——连续 20 个 epoch 验证集指标不提升就自动停止避免无意义的时间消耗。imgsz 设 640 是速度与精度的平衡点显存不够可以降到 320batch 设 8 是普通显卡的安全值如果显存充足可以加大但不要一次拉到显存上限训练中途爆显存会丢进度。project 和 name 控制输出目录runs/gas_v8n 下面会有 weights 文件夹存放 best.pt 和 last.pt训练中断后可以直接用 last.pt 恢复。用自己的数据训练时最常见的翻车点不是模型而是路径和类别号对不上。用 yolov8 新版本几乎不用手工改模型结构老版本代码可能需要手动修改 model.yaml 里的 nc 值具体看仓库文档。训练命令里的 batch、imgsz 是要跟着显卡变化的后面第 4 章会专门展开。4. 训练日志与调参判断看懂 loss 曲线和 mAP模型训练才能不玄学训练过程对很多学生来说是个黑匣子跑起来就开始刷手机等结果看运气。实际上训练日志里的每个数字都有明确含义看懂之后大部分调参问题都能自己判断。模型训练前传和反传的基本逻辑是前向传播计算预测结果和 loss反向传播更新梯度日志里的 loss 值和 mAP 值就是这一过程的外在表现。4.1 训练日志和 results 图里哪些数字能信YOLO 系列训练完成后会在输出目录生成 results.png里面画了多条曲线。最重要的一条是 metrics/mAP50-95(B)它表示在不同 IoU 阈值下 mAP 的平均值指标越接近 1 越好。毕设场景里如果目标本身不大mAP50 更能反映实际可用性因为 50-95 对框的定位精度要求很高很多小目标在 IoU 0.75 下天然吃亏。loss/box 是边框回归损失loss/cls 是分类损失loss/dfl 是分布焦点损失。看 loss 曲线时如果训练 loss 一直下降但验证 loss 拐头向上说明过拟合开始了早停会在这个点附近停下来如果训练 loss 和验证 loss 都不下降先检查数据是不是空的再检查学习率是不是太大。4.2 必调参数清单一张表格说清影响和推荐值训练一个模型需要调的参数其实不多把几个关键参数吃透比乱试一组 grid search 更有效。参数常见默认值影响毕设推荐imgsz640输入分辨率越大细节越清晰但显存占用越高显存吃紧就用 320目标小用 640batch8-16每步训练的样本数影响梯度稳定性和显存显卡占用控制在 70% 左右epochs100训练轮数100 起步配 patience20 早停lr00.01初始学习率小数据集用 0.005 更稳weight_decay0.0005正则化强度防过拟合默认值即可mosaic1.0四图拼一图增强数据少于 2000 张时建议关掉或设 0.5close_mosaic10最后 10 个 epoch 关闭 mosaic保持默认让模型适应真实比例workers8数据加载线程数Windows 下设为 2 或 4设太高容易卡死mosaic 是双刃剑。它能让模型看到更丰富的上下文但小数据集强化增强反而会把分布带偏。数据少时把 mosaic 关掉直接用原始图和简单翻转训练效果往往更好。学习率参数 lr0 在从预训练权重微调时不要设太大0.01 对初学者来说已经偏高用 0.005 更安全。4.3 训练中断、显存不足、loss 为 NaN 的现场处理训练到一半中断是最常见的事断电、显卡驱动崩溃、手动 CtrlC 都可能发生。YOLO 系列在 weights 目录下保存 last.pt可以直接恢复yolo train resume modelruns/gas_v8n/weights/last.ptresume 模式会读取上次训练的参数和 epoch 数继续跑而不是从头开始。显存不足报错时先降 batchbatch 降到 4 还不行就降 imgsz 到 320这两个参数对显存占用影响最大。注意不要同时开太多训练任务Windows 下数据加载 workers 设太高也会吃到额外显存。loss 变 NaN 的常见原因是学习率太高或者数据里有空的标注文件先去 labels 目录检查有没有 0 字节的 txt 文件把它删掉再重新划分数据集。如果学习率问题把 lr0 降到 0.001 再试。还有一种隐蔽的翻车点训练时打开了预训练模型自带的类别数和自己的数据类别不一致导致 NaN。YOLO 系列会自动覆盖但如果你用的是老版本源码或自定义网络需要手动确认最后一层的输出通道数等于自己的 nc 值。5. 文档说明与交付的4个常见坑README、数据说明和环境记录很多学生觉得文档说明就是写个 README两三句话完事。但实际上毕业论文的“文档说明”通常指三层内容README 告诉别人怎么跑数据说明告诉别人数据从哪来、格式是什么、怎么预处理复现说明记录环境和参数让实验可以原样跑回来。这三层都齐了才算合格交付。这里不讲怎么写长篇大论只记录我在实际交付中踩过的四个典型坑。5.1 坑一README 里的入口脚本和代码对不上现象导师拿到代码后按 README 里的命令运行提示找不到入口脚本或者运行到一半报模块不存在。原因项目开发过程中改过目录结构或入口文件名README 没有同步更新甚至有些同学直接把别的项目的 README 改了个标题就交上来了。解决文档里的每一条命令都要在一个全新环境里照着跑一遍再写进去。具体做法是新建一个干净的 conda 环境按 README 从安装依赖开始逐步执行任何一步报错都要修正文档直到跑通结果。最终把入口命令原样粘进 README比如yolo train datagas_pipeline.yaml modelyolov8n.pt并注明使用的 Python 版本、PyTorch 版本和依赖清单。5.2 坑二数据目录改名后训练报 FileNotFoundError现象训练命令能执行但跑到数据加载阶段报找不到图片或标签文件检查后发现数据集目录在本地叫 gas在压缩包里叫 gas_pipelineyaml 里写死的是原名。原因用压缩包传输代码和数据后目录被解压改名yaml 和脚本里的绝对路径或硬编码路径失效。解决所有路径尽量用相对路径并且启动脚本里做一个路径自动校正。比如在 train 命令前加一行cd $(dirname $0)把工作目录切到脚本所在目录yaml 里写./data/train/images而不是D:/projects/gas/data/train/images。交付时把目录结构写在文档里注明 data 目录和代码目录的层级关系。5.3 坑三同一次实验两次训练结果差很多导师说不可复现现象同一个数据集、同一个训练命令第一次跑 mAP 到 0.72第二次只有 0.64换台机器差距更大。原因数据划分用了未固定的随机种子训练过程中也没有固定随机数。PyTorch 默认情况下随机初始化权重数据加载顺序也和随机种子挂钩所以结果天然有波动。解决在训练入口脚本里显式固定种子写torch.manual_seed(42)、numpy.random.seed(42)数据划分脚本里设置random.seed(42)并固定划分比例。同时在文档里记录训练时的随机种子值。很多框架没有提供种子参数最稳妥的做法是把数据集划分脚本放到 tools 目录让它先生成 train.txt、val.txt再把这两个文件的列表写进文档这样数据划分就和随机种子无关了。5.4 坑四换一台机器就 OOM 或装不上依赖现象代码在自己笔记本上跑得好好的导师换到另一台机器上跑直接显存溢出或者安装依赖时版本冲突报错一堆。原因环境的差异没写在文档里。训练参数里 batch 和 imgsz 是针对自己的显卡设置的换一张显存小的卡自然跑不动requirements.txt 没有锁版本新机器安装到了最新版 PyTorch和代码不兼容。解决导出环境的具体版本信息写进文档用pip freeze requirements_lock.txt记录完整依赖再用nvidia-smi和python -c import torch; print(torch.version.cuda)记录显卡和 CUDA 版本。在文档里明确说明显存不足时先按第 4 章的参数表下调 batch 和 imgsz。如果代码依赖特定 PyTorch 版本给出安装命令而不是只写 requirement 文件。6. 验证与答辩演示把训练结果变成看得见、跑得动的东西模型训练完不等于毕业设计做完还要用一套验证脚本证明结果是可信的。先跑一次标准验证得到测试集上的精确率、召回率和每个类别的 AP这些数字是答辩时最有力的支撑。# evaluate.py # 用训练好的best.pt在测试集上做最终验证 from ultralytics import YOLO model YOLO(runs/gas_v8n/weights/best.pt) metrics model.val(datagas_pipeline.yaml, splittest) print(fmAP50: {metrics.box.map50:.4f}) print(fmAP50-95: {metrics.box.map:.4f})这段脚本会输出最终的 mAP 值并且自动生成混淆矩阵和 PR 曲线图到验证输出目录。这些图表可以放进毕业论文的实验章节。如果想让答辩演示更直观可以用 Gradio 写一个十行左右的可视化页面上传一张图片就能显示检测结果# demo.py import gradio as gr from ultralytics import YOLO model YOLO(runs/gas_v8n/weights/best.pt) def detect(img): results model(img, conf0.25) return results[0].plot() gr.Interface(fndetect, inputsimage, outputsimage).launch()这个 demo 不用写前端本地运行后浏览器打开就是上传窗口。如果题目是边缘部署可以试试把训练好的模型导出成 ONNX再用 NCNN 或 TensorRT 跑在树莓派 5 这类开发板上这条链路本身就是答辩加分项。还有一个更实用的技巧把所有操作封装成 run_all.sh从环境安装、数据划分、训练到验证一条命令跑完导师能直接复现代码评阅的得分往往比模型精度更高。我自己的教训是第一年做毕设时只交了一个 Jupyter Notebook训练过程全在内存里导师打开全是报错最后花了两个通宵补环境。从那以后我每次训练前都在项目根目录建一个 README里面只有三样东西能跑通的完整命令、数据格式说明、训练参数表。每次改动代码就顺手更新文档不等到最后一天补。毕设模型训练做到最后拼的不是谁的网络结构更花哨而是谁的工程能在别人的机器上跑起来。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进