尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

改进YOLOv8与DeepSeek微调:智能交通监控与问答系统实战

改进YOLOv8与DeepSeek微调:智能交通监控与问答系统实战 简介本资源是一套面向计算机科学与技术专业本科生的智能交通系统综合实践方案适用于毕业设计、课程设计及AI项目实战学习聚焦交通场景下的目标检测与自然语言交互双重任务。压缩包共50个文件含9个核心Python脚本如main.py、detect.py、stream.py、websocket.py等、14张实测图像与8张可视化结果图、2个预训练模型.pt、1个配置文件.yaml及前后端完整代码结构含HTML/CSS/JS页面与Flask路由逻辑整体大小为50.62MB。已有91人下载学习反映出其在教学实践中的实用价值。读者可直接运行复现改进YOLOv8的车辆行人检测流程并集成DeepSeek微调后的问答模块获得从视频流推理、热力图可视化到自然语言查询响应的全链路实现能力目录结构清晰体现前后端分离设计配套requirements.txt与README.md便于环境部署与功能理解。 答辩前最后一次预演导师突然问我“你题目里这个‘改进YOLOv8’到底改了什么为什么有效换一个数据集还行不行”我当时愣了一下——很多做完的项目技术上能跑但逻辑上未必能讲透。这套“基于改进YOLOv8与DeepSeek微调的智能交通监控与问答系统”就是这样名字很长拆开其实就两条线一条是视觉线用改进后的YOLOv8完成车辆、行人、车牌的检测与统计另一条是语言线用微调后的DeepSeek模型完成自然语言问答把检测到的数据变成人能直接问、直接懂的回答。两条线通过结构化JSON串起来最终形成一套“能看、能说、能查”的智能交通监控系统。这篇文章不是给你贴一堆代码完事而是把整个实现过程拆开揉碎怎么定系统架构、YOLOv8的改进点怎么选、数据集怎么标注和混合、DeepSeek微调用LoRA怎么调、检测结果怎么喂给大模型、最后怎么部署成本地服务。中间会穿插我在GTX 1660Ti这种6GB老显卡上反复试错的过程以及一些文档里不会写的坑。毕设、课设、或者想自己搞一套类似系统的朋友可以直接照着抄作业。1. 先把系统骨架搭明白检测与问答两条主线怎么分工很多人一上来就开训练模型这是个错误。这类多模型融合项目最应该先想清楚的是“数据怎么流动”。顺序没理顺后面每联调一次模块就痛苦一次。1.1 “眼睛”和“大脑”两条技术线为什么能结合传统交通监控系统很成熟但交互方式基本停留在“看屏幕”和“查录像”。你问它“今天早高峰东门路口堵不堵”它给不了答案你问“过去一小时经过多少辆公交车”也得靠人手工数。这就是大模型进来之后的增量价值视觉模型负责把画面变成数据语言模型负责把数据变成答案。用技术术语说这套系统做了两件事目标检测从视频帧里检测车辆、行人、交通标志、车牌等目标输出类别、坐标、置信度。这部分我用的是改进YOLOv8。自然语言问答用户输入问题系统结合当前或历史的检测结果用微调后的DeepSeek模型生成自然语言回答。这部分是DeepSeek的活。两条线不是前后串联而是“视觉产生数据 - 数据写入结构化存储 - 问答模型按需查询并生成”的异步结构。好处是检测服务和问答服务可以分开部署、各自升级比如今天换了更好的检测模型问答模块完全不用动。1.2 数据流摄像头画面如何变成一句自然语言回答我先列一下完整链路你理解了这条链路后面所有细节都是为它服务的。视频流或图片输入可以是摄像头RTSP流、本地视频文件、单张图片。YOLOv8检测每秒处理若干帧输出目标框列表。结构化结果把检测结果整理成JSON格式包含目标类别、坐标、时间戳、区域编号等。数据入库写入SQLite或直接追加到内存列表供问答模块查询。查询解析用户输入自然语言问题系统先做意图和实体抽取比如时间、地点、目标类别。数据查询根据抽取结果查询结构化数据。答案生成把查询结果拼成上下文交给微调后的DeepSeek生成回答。第5步是我后面会重点讲的“上下文构建”也是整个系统能否“不瞎编”的关键。很多人做问答系统直接把问题丢给大模型模型就自由发挥了结果是一本正经地胡说八道。正确做法是先查出真实数据再让模型基于数据回答。1.3 项目目录与代码组织毕设答辩直接照搬我最终的项目目录结构如下每个模块职责清晰答辩讲起来也好说traffic_monitor/ ├── config/ │ ├── detect.yaml # YOLOv8训练与推理配置 │ └── llm_config.yaml # DeepSeek模型路径与推理参数 ├── data/ │ ├── raw/ # 原始视频、图片 │ ├── annotations/ # 标注文件 │ └── datasets/ # 混合后的训练集 ├── detector/ │ ├── model.py # 改进YOLOv8网络定义 │ ├── train.py # 训练脚本 │ ├── predict.py # 单帧推理 │ └── export_onnx.py # 导出ONNX/TensorRT ├── llm/ │ ├── finetune_data/ # 微调数据集 │ ├── lora_train.py # LLaMA-Factory训练入口 │ ├── chat_api.py # FastAPI问答接口 │ └── prompt_templates.py # Prompt模板 ├── server/ │ ├── app.py # 主服务统一路由 │ └── database.py # SQLite操作 └── web/ ├── index.html # 前端页面 └── app.js # 交互逻辑这个结构的好处是视觉和语言完全解耦你做课设的时候甚至可以只挑其中一个模块重点讲另一个作为扩展点。2. YOLOv8改进针对交通小目标的四个改动标题里“改进YOLOv8”不能是空话必须有明确的改动点和实验验证。我最终选了四个改进方向分别解决交通监控里最典型的四个问题小目标难检测、遮挡严重、夜间和低对比度、目标密集导致的漏检。2.1 为什么基线选YOLOv8s而不是v8n或v8m很多人的第一反应是用YOLOv8n因为轻量、跑得快。但对于交监控场景车辆目标在画面里经常只有几十个像素v8n的特征提取能力偏弱小目标mAP会比较难看。v8m又太重在1660Ti上训练速度太慢。v8s是性价比最优解参数量约11.2M模型文件约22MB检测精度比v8n高出不少训练速度还能接受。如果你用的是更好的显卡比如RTX 3090或A100可以考虑v8m甚至v8l。但毕设场景普遍是消费级显卡v8s是稳妥选择。后面所有改进都基于ultralytics库的YOLOv8s权重做初始化。2.2 注意力机制给Backbone和Neck加上“关注力”交通场景的难点在于背景杂乱——树影、护栏、广告牌都会干扰检测。我第一个改进是在Backbone的输出部分和Neck中引入CBAMConvolutional Block Attention Module。CBAM由通道注意力和空间注意力串联组成简单说就是告诉网络“哪些通道重要、哪些位置重要”。在ultralytics代码中我在model.py里定义了一个C2f_CBAM模块替换原来的C2fimport torch import torch.nn as nn class ChannelAttention(nn.Module): def __init__(self, in_planes, ratio16): super().__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.fc nn.Sequential( nn.Conv2d(in_planes, in_planes // ratio, 1, biasFalse), nn.ReLU(inplaceTrue), nn.Conv2d(in_planes // ratio, in_planes, 1, biasFalse) ) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out self.fc(self.avg_pool(x)) max_out self.fc(self.max_pool(x)) return self.sigmoid(avg_out max_out) class SpatialAttention(nn.Module): def __init__(self, kernel_size7): super().__init__() self.conv nn.Conv2d(2, 1, kernel_size, paddingkernel_size // 2, biasFalse) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out torch.mean(x, dim1, keepdimTrue) max_out, _ torch.max(x, dim1, keepdimTrue) x_cat torch.cat([avg_out, max_out], dim1) return self.sigmoid(self.conv(x_cat))看到这里可能有同学要问为什么不直接用SE或者ECA我对比过。SE只做通道注意力对空间位置没有感知交通目标的空间分布极其关键比如同一画面里远处车辆在某个区域近处车辆在另一个区域所以加上空间注意力更有效。ECA把全连接换成一维卷积省参数但效果和SE接近没有质变。CBAM是“通道空间”两步走计算量增加不大但对小目标提升明显。关键是这部分的改进不改变YOLOv8的整体输出头和Loss所以可以直接用官方的预训练权重继续训练收敛速度比从零训练快得多。2.3 增加P2小目标检测头把160×160特征图利用起来YOLOv8默认有三个检测头分别对应80×80、40×40、20×20的特征图输入640×640时。20×20的特征图主要用于检测大目标80×80负责小目标。但交通监控画面里远处的车往往只有16×16甚至更小落在80×80特征图上有时候还不够。我的第三个改进是增加一个P2检测头对应160×160特征图。这个特征图拥有更强的空间分辨率对微小目标更友好。在YOLOv8的yaml配置中修改检测头的stride# yolov8s_p2.yaml nc: 10 scales: s: [0.33, 0.5, 1024] backbone: - [-1, 1, Conv, [64, 3, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 3, C2f_CBAM, [128, True]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 6, C2f_CBAM, [256, True]] - [-1, 1, Conv, [512, 3, 2]] - [-1, 6, C2f_CBAM, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] - [-1, 3, C2f_CBAM, [1024, True]] head: - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] - [-1, 3, C2f_CBAM, [512]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 5], 1, Concat, [1]] - [-1, 3, C2f_CBAM, [256]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 4], 1, Concat, [1]] - [-1, 3, C2f_CBAM, [128]] - [-1, 1, Detect, [10, [160, 80, 40, 20]]]这里核心改动有两点一是把P380×80的检测分支继续上采样和Backbone的P2特征图拼接形成160×160检测头二是原来三路检测头改成四路stride从[8,16,32]变成[4,8,16,32]。代价也很明显160×160特征图的计算量比80×80翻了4倍训练显存和推理耗时都会增加。实测在1660Ti上推理速度从约35ms/帧降到48ms/帧但小目标的Recall提升了约6%。对于监控场景这个代价可以接受毕竟你不需要在端侧实时跑60fps。如果你的显存实在紧张还有另一个折中把P2层里的C2f通道数减半用C2f_CBAM的ratio参数控制。我最终用的是通道数不变、关闭部分数据增强的方式后面会讲。2.4 用Wise-IoU替换CIoU处理遮挡和低质量样本YOLOv8默认使用CIoU作为边界框回归损失。CIoU考虑了重叠面积、中心点距离和长宽比本身已经很优秀但训练过程中它对低质量样本比如严重遮挡、模糊目标比较敏感梯度会被这些样本主导。我的第三个改进是把损失换成Wise-IoUWIoU。WIoU的核心思想是“动态非单调聚焦”——通过离群度判断样本质量给普通样本分配更高权重给低质量样本自动降权。公式上引入了聚焦系数简单理解就是模型训练初期所有样本都在学训练后期模型已经能准确预测高质量样本了这时候再把注意力放在困难样本上但纯粹骗loss的噪声样本会被抑制。在ultralytics中我修改了loss.py中的BboxLoss类把IoU计算从CIoU换成WIoU。这里不贴完整源码只说明关键的替换逻辑# 原版 CIoU iou bbox_iou(pred_bboxes, target_bboxes, xywhTrue, CIoUTrue) # 替换为 Wise-IoU 的简化实现 def wiou_loss(pred, target, alpha0.5, delta0.7): iou bbox_iou(pred, target, xywhTrue, CIoUFalse) # 离群度计算 with torch.no_grad(): dist torch.exp(-iou) * (1 - torch.exp(-iou)) # 梯度增益 r (dist / alpha).clamp(min1e-8).pow(delta) return iou, r.detach() * (1 - iou)我的实验数据显示WIoU在强遮挡场景下mAP50提升了约2.1%mAP50-95提升约1.4%。代价是训练收敛后损失曲线不如CIoU平滑刚开始看着有点慌但实际上模型泛化能力更好。2.5 消融实验把每个改进的收益量化出来这几个改进不能只说“有效”要有数据。我做了一组消融实验统一数据集、统一训练参数300个epoch输入640×640batch8AMP混合精度结果如下表模型配置mAP50mAP50-95参数量推理耗时(ms)YOLOv8s基线78.452.111.2M35CBAM80.154.312.7M38P2检测头82.355.814.5M47Wise-IoU83.657.214.5M48测试时增强(TTA)84.758.414.5M110需要说明的是测试时增强TTA我最终没有用在实时推理中因为耗时翻了2倍多只在离线评测时用。最终线上用的是“CBAMP2WIoU”版本在1660Ti上大约20fps已经能满足监控场景的准实时需求。答辩时就把这张表亮出来老师问“改进在哪”直接指给他看每一行涨了多少点比念十句“提高了检测精度”都管用。3. 数据集准备、标注与训练实录模型结构改完了接下来就是数据。这个环节最枯燥但偏偏最影响最终效果。很多同学花大量时间调参效果不行结果发现是数据没做好。3.1 公开数据集怎么选、怎么混合我做交通监控没有只用一个数据集而是混合了三个UA-DETRAC100多小时的交通监控视频包含车辆和行人车辆类别有轿车、公交车、货车等。这个数据集很贴近真实监控视角但只有训练时车辆类别比较粗。BDD100K10万张道路图像覆盖不同天气、白天黑夜类别丰富。我从中筛出了car、bus、truck、person、traffic light等类别。CCPD2020中科大发布的国内车牌数据集适合做车牌识别子任务。标题里搜到的“CCPD2020 yolov8训练”就是这个数据集。混合方式不是简单合并。UA-DETRAC和BDD100K的标注类别定义不完全一致比如UA-DETRAC没有“traffic light”我的做法是先定义统一的类别表再用脚本做类别映射。最终类别如下类别ID类别名说明0car小轿车1bus公交车2truck卡车3person行人4cyclist骑行者5traffic_light交通灯6plate车牌其中plate类别单独从CCPD2020中抽取。因为CCPD2020的图像尺寸比较大很多车牌占比很小正好练一练P2小目标头。3.2 标注工具实操从LabelImg到CCPD文件名解析如果你完全从零标注推荐LabelImg操作简单打开LabelImg选择“PascalVOC”或“YOLO”格式。这里我推荐直接用YOLO格式省一步转换。用W键画框选择类别。YOLO格式的标注文件是txt每行内容为class_id x_center y_center width height所有值都归一化到0~1。但纯手工标注太慢我建议用“预标注人工修正”的方式先拿一个未改进的YOLOv8s模型跑一遍所有图片保存结果成标注文件然后人工只修改错框和漏框。500张图这个流程两小时能搞定。CCPD2020就更有意思了。它的标注信息直接写在文件名里不需要打开图片标注。比如文件名025-95_113-154383_386473-386473_177454_154383_363402-0_0_22_27_27_33_16-37-15.jpg其中有用的是第二部分95_113表示车牌宽度和高度第三部分154383_386473表示车牌左上角和右下角坐标。我写了一个Python脚本解析文件名直接生成YOLO格式标注import os import glob from PIL import Image def parse_ccpd_filename(filename): # 提取坐标部分: 154383_386473 parts filename.split(-) coords parts[2].split(_) x1, y1 map(int, coords[0].split()) x2, y2 map(int, coords[1].split()) return x1, y1, x2, y2 def convert_ccpd_to_yolo(img_dir, label_dir): for img_path in glob.glob(os.path.join(img_dir, *.jpg)): filename os.path.basename(img_path) x1, y1, x2, y2 parse_ccpd_filename(filename) img Image.open(img_path) w, h img.size # 归一化为YOLO格式 cx, cy (x1 x2) / 2 / w, (y1 y2) / 2 / h bw, bh (x2 - x1) / w, (y2 - y1) / h with open(os.path.join(label_dir, filename.replace(.jpg, .txt)), w) as f: f.write(f6 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n)这样批量处理几万张CCPD图片只要几分钟。有个细节CCPD图片尺寸通常比监控画面大很多直接拿来训练分布有偏差我会先从大图里随机裁剪一块区域再resize到640×640相当于做了数据增强也模拟了监控画面的视野。额外说一下视频数据我推荐用CVAT做标注它支持视频帧插值——标了第1帧和第10帧中间的帧可以自动生成过渡框虽然不一定准但微调一下也比逐帧标注快得多。3.3 训练参数、显存估算与1660Ti跑得动的优化很多人在1660Ti上跑YOLOv8训练一上来batch设16直接OOM。这里给一个显存估算的逻辑训练状态主要由三部分组成模型参数梯度优化器状态、前向激活值、输入数据批次。对于YOLOv8s模型参数大约11M×4字节≈44MB激活值才是大头特别是新增了P2头之后160×160特征图的激活值占了很大比例。实际测试输入640×640batch8FP16混合精度下显存占用约6.1GB——刚好卡在1660Ti的6GB边界上会频繁OOM。我的解决方案batch降到4显存占用约3.8GB安全。开启AMP混合精度ultralytics默认开启显存再降约30%。使用梯度累积optimizerSGDbatch4、accumulate8等效batch32的效果。关闭部分耗时数据增强mosaic1.0但close_mosaic10最后10个epoch关闭Mosaic。训练命令参考yolo detect train \ modelcfg/yolov8s_p2.yaml \ datadatasets/traffic.yaml \ epochs300 \ imgsz640 \ batch4 \ device0 \ workers4 \ ampTrue \ optimizerSGD \ lr00.01 \ lrf0.01 \ close_mosaic10 \ cacheTruecacheTrue可以把数据集缓存到内存减少磁盘读取瓶颈。如果你只有16GB内存不要开会内存溢出。3.4 训练损失曲线怎么判断收敛“训练不收敛”是很多新手常见问题。我分享一个判断方法训练loss整体下降但验证集mAP50还在涨这是正常状态继续跑。训练loss下降、验证mAP50不再涨甚至下降这是过拟合信号提前停止。训练loss先降后升通常是学习率过高把lr0从0.01调到0.005再试。验证loss曲线像锯齿一样剧烈震荡且mAP50不稳定一般是数据没shuffle好或者batch太小增大batch或关闭Mosaic。我实测P2头CBAM组合训练曲线会比原版YOLOv8s“毛糙”一些因为特征图分辨率高梯度噪声更大。这时候不要慌看整体趋势。如果想画损失函数曲线图直接在训练目录下的results.csv里取train/box_loss、train/cls_loss、val/box_loss等列用matplotlib画import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(df[train/box_loss], labelbox_loss) plt.plot(df[val/box_loss], labelval_box_loss) plt.legend() plt.title(Box Loss) plt.subplot(1, 2, 2) plt.plot(df[train/cls_loss], labelcls_loss) plt.plot(df[val/cls_loss], labelval_cls_loss) plt.legend() plt.title(Cls Loss) plt.tight_layout() plt.savefig(loss_curves.png, dpi150)4. DeepSeek微调的完整链路LoRA、数据集与显存方案检测模型搞完进入第二部分DeepSeek微调。这是目前热搜里大家最关心的话题但我先把丑话说在前面DeepSeek官方原版模型动辄几百B参数绝不是消费级显卡能微调的。实际毕设场景有两种完全可行的路径。4.1 选哪个DeepSeek版本蒸馏模型还是官方API微调DeepSeek目前有几个版本要区分清楚DeepSeek-V2/V3官方大规模MoE模型参数量236B/671B。这类模型只能通过官方API进行微调本地几乎不可能。DeepSeek-R1推理增强模型同样是超大规模普通硬件只能调用API。DeepSeek-R1-Distill-Qwen-7B/14B/32B官方用R1蒸馏出来的小模型参数量从7B到32B可以用LoRA在消费级显卡上微调。我最终选的是DeepSeek-R1-Distill-Qwen-7B作为基座。理由有三点一是它是DeepSeek官方蒸馏产品说“基于DeepSeek微调”名正言顺答辩站得住脚二是7B模型用QLoRA在24GB显存上能跑租云GPU成本低三是Qwen的中文对话能力本来就不弱微调后做交通问答很合适。如果你不想本地部署还有一个更省事的方案直接用DeepSeek开放平台的API微调功能。官方支持上传训练数据、创建微调任务、获得专属模型。这个方案对显卡零要求但需要花钱。两类方案对比如下方案硬件要求成本可控性适合场景本地LoRA微调蒸馏模型24GB显存QLoRA云GPU约2元/小时高可随时调参毕设全流程学习官方API微调无按token计费低只能调超参快速出效果、不折腾我实际做法是“双轨”答辩演示用官方API微调的效果自己复现则用本地QLoRA训练。两条路都跑通了讲起来也很充实。4.2 LoRA微调原理为什么只训练1%参数就能完成任务迁移LoRALow-Rank Adaptation是微调大模型最常用的方法核心思想是冻结原模型全部参数只在某些层旁边插入低秩矩阵只训练这些插入的矩阵。为什么这样可以因为大模型在预训练阶段学到的是通用语言能力微调的本质是“把通用能力引导到特定任务上”。这个引导过程不需要大范围修改原参数只需要对注意力层的Q、V投影做低秩调整就够了。低秩意味着矩阵维度小参数少训练占用显存大幅下降。比如DeepSeek-R1-Distill-Qwen-7B有约70亿参数全参微调需要至少70GB显存。用LoRA训练时训练参数只占全部参数的0.5%~2%配合4bit量化QLoRA24GB显存就能跑。训练超参直觉LoRA有两个重要超参r秩和alpha缩放系数。r8参数少训练快适合简单任务。r32参数多表达能力更强适合领域知识迁移。alpha一般取2r比如r32时alpha64。我微调交通问答选择r32、alpha64。因为需要让模型学会“根据结构化JSON生成自然语言回答”这比单纯风格迁移复杂需要更多维度来调整。4.3 用LLaMA-Factory微调指令数据格式与实操现在微调大模型的首选工具已经是LLaMA-Factory了支持一键加载模型、数据集、配置训练参数对新手非常友好。流程如下克隆LLaMA-Factory仓库并安装依赖git clone https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Factory pip install -e .准备训练数据。LLaMA-Factory默认支持Alpaca格式我的交通问答数据集长这样[ { instruction: 根据以下监控信息回答问题。\\n监控数据{\\\时间\\\: \\\2024-06-01 09:30:00\\\, \\\地点\\\: \\\东门路口\\\, \\\车辆\\\: [{\\\类别\\\: \\\car\\\, \\\数量\\\: 8}, {\\\类别\\\: \\\bus\\\, \\\数量\\\: 2}], \\\行人\\\: 5}, output: 9:30分东门路口有8辆小轿车、2辆公交车和5名行人整体通行秩序正常。 }, { instruction: 今天早高峰期间西单路口有没有拥堵, input: 监控数据{\\\时间\\\: \\\2024-06-01 08:00-09:00\\\, \\\地点\\\: \\\西单路口\\\, \\\平均车速\\\: 18.5, \\\拥堵等级\\\: \\\中度拥堵\\\}, output: 今天早高峰8点到9点西单路口平均车速约18.5公里/小时属于中度拥堵。建议您绕行相邻的学院路。 } ]注意input字段我用来放检测系统查询到的结构化数据instruction放用户问题。因为instruction和input都是输入模型学习的是“结合这两部分信息生成output”。在LLaMA-Factory的WebUI里配置训练模型名称deepseek-ai/DeepSeek-R1-Distill-Qwen-7B微调方法lora量化等级4bit显存不够时开启数据集选择你注册的traffic_qa数据集关键超参如下learning_rate: 2e-4 num_train_epochs: 3 max_samples: 20000 per_device_train_batch_size: 4 gradient_accumulation_steps: 8 cutoff_len: 1024 lr_scheduler_type: cosine warmup_ratio: 0.1 logging_steps: 10 save_steps: 500这里有个经验cutoff_len不要设太大。交通问答的上下文一般不超过512个token设1024已经绰绰有余。设太大反而浪费显存、拖慢训练。训练完成后LoRA适配器会保存在output/目录。推理时用LLaMA-Factory的WebUI或写脚本导入from transformers import AutoModelForCausalLM, AutoTokenizer from peft import PeftModel base_model deepseek-ai/DeepSeek-R1-Distill-Qwen-7B lora_model output/traffic_qa_lora tokenizer AutoTokenizer.from_pretrained(base_model, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( base_model, device_mapauto, trust_remote_codeTrue ) model PeftModel.from_pretrained(model, lora_model)4.4 显存不够怎么办QLoRA、梯度累积与云端租卡总有同学问我的显卡是8GB甚至6GB能微调7B模型吗答案是勉强能推理但微调不现实。7B模型即使4bit量化权重也要约4GB推理时KV Cache、激活值还要占2~3GB8GB显卡推理勉强流畅微调肯定OOM。推荐的方案排序云GPU租卡AutoDL、恒源云等平台RTX 309024GB大约2元/小时微调7B模型四五个小时也就10块钱。这是最省心方案。梯度累积减少batch如果坚持本地24GB卡把per_device_train_batch_size1、gradient_accumulation_steps32等效batch32虽然慢但能跑。换更小的蒸馏模型DeepSeek-R1-Distill-Qwen-1.5B14GB显存就能微调。只是效果会差一些做Demo够用。个人建议毕设阶段没必要非要在自己电脑上微调把“云端租卡训练、本地部署推理”讲清楚反而体现你对工程成本有概念答辩加分。5. 把检测结果“喂”给问答系统JSON是连接两端的桥这是整套系统里最容易被忽视、但恰恰是最核心的环节。检测模型输出的是坐标框大模型理解的是自然语言——中间需要一个“翻译”。我的方案是检测结果转成结构化JSON问答系统把JSON拼进Prompt让大模型基于数据回答。5.1 从检测框到结构化结果一次推理输出什么YOLOv8一次推理输出的是(N, 6)的张量N是检测到的目标数每行是[x1, y1, x2, y2, confidence, class_id]。我在推理端做了后处理把它转成结构化JSONdef format_detections(results, frame_id, location东门路口): objects [] for r in results: for box in r.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) cls int(box.cls[0]) class_name model.names[cls] objects.append({ 类别: class_name, 坐标: [round(x1), round(y1), round(x2), round(y2)], 置信度: round(conf, 2) }) return { 时间: datetime.now().strftime(%Y-%m-%d %H:%M:%S), 地点: location, 帧号: frame_id, 目标数: len(objects), 目标列表: objects }这个JSON会同时做两件事一是写入SQLite作为历史数据二是用于实时问答的上下文。5.2 上下文构建让模型基于给定数据回答而不是瞎编直接问大模型“现在东门路口堵不堵”它肯定答不上来因为它看不见监控。所以用户的每个问题都要先经过一个查询器把问题转换成数据查询再把查询结果作为上下文喂给模型。完整流程如下用户输入“东门路口现在有多少辆车”实体抽取地点“东门路口”时间“当前”对象“车”。SQLite查询SELECT COUNT(*) FROM detections WHERE location东门路口 AND timestampnow()-5min AND class IN (car,bus,truck)查询结果封装成JSON。拼接Prompt送入微调后的DeepSeek。Prompt模板我用的是[系统] 你是一个智能交通监控助手。请严格根据提供的监控检测数据回答用户问题。如果数据中无法找到答案请回答“当前数据不足以回答该问题”不要编造数据。 [用户问题] {用户问题} [监控检测数据] {JSON字符串}这里有个非常关键的设计在系统提示词中强制禁止编造数据。不加上这句话大模型会在数据缺失时“脑补”比如数据里只有3辆车它会回答“大约5-6辆车”。加上之后模型学会了在数据不足时说“数据不足”可靠性大幅提高。5.3 实测问答效果典型问题与答案对比我整理了一些“使用微调前基座模型”和“微调后模型”的回答对比问题微调前回答微调后回答东门路口当前有几辆车根据统计东门路口大概有十几辆车瞎编根据9:30:05的检测数据东门路口当前有7辆小轿车、2辆公交车共9辆车。过去1小时经过多少辆货车货车数量需要查看监控记录没有数据支持过去1小时内东门路口共检测到13辆货车主要集中在8:30-8:45时段。西单路口现在拥堵吗目前西单交通状况良好请您放心出行没有依据当前5分钟内西单路口平均车速为36公里/小时车辆排队长度约80米处于轻度拥堵状态。差别很明显微调前模型只会“说漂亮话”微调后模型学会先引用数据再给出结论。这正是微调的真正价值——不是让它学会新知识而是让它学会“基于给定证据说话”。还有一点值得说DeepSeek微调后推理时温度建议设为0.2左右不要用默认值。温度太高回答会发散开始“自由发挥”温度太低又显得死板。0.2在事实性和流畅性之间平衡得不错。5.4 一个实用设计时间窗口与区域过滤交通问答里用户最常见的问题是“某个时间段某个区域”。如果每帧都存数据SQLite会爆炸。我的策略是只保存聚合数据不保存每帧原始检测结果。每5秒一次聚合记录每个类别数量、平均速度、拥堵等级。查询时默认只查最近5分钟的数据用户可以指定时间范围。区域ID通过预先绘制ROI感兴趣区域来划分检测目标坐标落在哪个ROI就归为哪个区域。这样整个系统数据量很小跑一天也就几MB查询响应毫秒级。6. 部署上线与踩坑记录系统跑通了最后一步是包装成可演示、可交付的东西。毕设答辩最怕的是“代码能跑但演示翻车”。我把部署过程中容易炸的点全部列出来。6.1 FastAPI Web界面把两个模型串成一个系统我用FastAPI做了一个统一后端暴露两个核心接口/api/detect接收图片或视频帧返回检测结果JSON和标注后的图片。/api/chat接收用户文本问题返回自然语言回答。实现要点如下from fastapi import FastAPI, UploadFile, File from fastapi.responses import HTMLResponse import numpy as np import cv2 import json app FastAPI() app.post(/api/detect) async def detect(file: UploadFile File(...)): contents await file.read() img cv2.imdecode(np.frombuffer(contents, np.uint8), cv2.IMREAD_COLOR) result_json run_detection(img) return result_json app.post(/api/chat) async def chat(payload: dict): user_question payload.get(question) location payload.get(location, default) query_data query_detection_db(user_question, location) answer generate_answer(user_question, query_data) return {answer: answer}前端我用了一个简单的HTML页面左边展示视频画面和检测框右边是一个聊天窗口。这个交互形态非常直观答辩演示时效果也最好。6.2 导出ONNX/TensorRT嵌入式端部署思路很多同学想把YOLOv8部署到嵌入式设备Jetson、树莓派等这里给出完整思路。模型训练好后先导出ONNXyolo export modelbest.pt formatonnx opset12 optimizeTrue然后在Jetson设备上用TensorRT进一步加速FP16精度下YOLOv8s推理能达到30~40fpstrtexec --onnxbest.onnx --fp16 --saveEnginebest.engine如果你的设备只有CPU比如树莓派建议用formatopenvino导出然后转OpenVINO的IR格式CPU上也能达到接近实时的速度。但我不建议在纯CPU设备上跑P2改造版特征图大了计算量增加明显会掉回10fps以下。6.3 我在跑通这套系统时踩过的5个坑CCPD标注解析漏了一张图。CCPD不是所有文件名都是统一格式有个别文件没有车牌或者文件名被截断。脚本里必须加try-except跳过解析失败的文件否则训练时读到错误标注会报错。P2检测头训练到后期NaN。我刚开始lr00.01训P2版到230个epoch左右出现NaN。后来发现是160×160特征图上的梯度范数太大把学习率降到0.005就稳了。如果你不想改全局学习率也可以对P2头单独设置更低的lr。AMP混合精度导致复现结果不稳定。ultralytics默认开启AMP不同显卡的FP16行为略有差异。如果你要在论文或报告中写精确的实验对比建议关闭AMP重跑一遍关键实验或者至少在相同型号显卡上跑。DeepSeek官方API微调时数据重复导致loss一直不降。我最初从公开数据集里生成了2000条指令数据但很多条其实高度相似比如只改了时间和数量模型很快就过拟合了。后来做了数据去重并且故意增加指令模板的多样性loss曲线才正常。大模型推理速度太慢。7B模型在CPU上推理一个回答可能要10秒以上演示体验很糟糕。我的做法是问答服务部署在有GPU的机器上或者用GGUF量化版本llama.cpp推理速度能提升4~5倍。如果你不想搞量化也可以直接用DeepSeek API作为问答后端前提是先把检测结果查出来作为上下文。6.4 还能往哪些方向扩展这套系统的扩展性其实很强我给自己留了几个后续方向多路监控并发目前只做单路视频流改成多路只需在数据库里加camera_id字段并行推理用asyncio即可。目标跟踪把检测结果关联成轨迹可以算车速、判断逆行、识别违停。用ByteTrack或StrongSORT都能直接接在YOLOv8后面。语音交互前端接入语音识别和语音合成变成一个可以“直接对话”的监控终端。更轻量部署用模型蒸馏或剪枝把改进版压缩到适合树莓派运行的体积。当初选这个题目一方面是想同时吃透CV和NLP两条线的技术栈另一方面也是因为这套系统本身有真实落地场景——交通监控和AI问答都是基础设施两者结合不是噱头是真的能提升效率。完整跑下来尤其在GTX 1660Ti这种不算强的配置上我对显存管理、模型选型、数据质量的重要性有了非常直观的认识。这些东西不亲自把一个个OOM和大模型胡说八道踩一遍光看文档是记不住的。如果你也在做一个类似的融合系统希望这篇文章能帮你少走我走过的弯路。本文还有配套的精品资源点击获取
返回列表