ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Traffic-Net交通拥堵识别:从模型结构到训练避坑全解析

Traffic-Net交通拥堵识别:从模型结构到训练避坑全解析 简介基于Traffic-Net的交通拥堵程度识别源码与配套数据打包分享面向人工智能、计算机、数据科学等专业的在校学生、教师及开发者可满足课程设计、毕业设计、大作业或初期项目演示等场景。压缩包共10个文件包含Python核心脚本、JSON配置文件、Markdown说明文档及TXT文本记录其中Python脚本用于模型训练与推断JSON保存类别映射Markdown和TXT提供使用说明与踩坑记录整体仅6KB结构精简便于直接阅读和二次修改。代码完整稳定可快速运行配套数据集下载指引清晰。目前已有49人浏览学习适合作为深度学习图像分类或智慧交通方向的入门参考。借助完整代码与注释可理解Traffic-Net的训练流程并基于现有框架替换数据集或调整网络结构拓展其他场景的拥堵识别功能。1. Traffic-Net 交通拥堵识别这个 zip 值不值得解压拿到这个 Traffic-Net 交通拥堵程度识别源码包的人九成是卡在课程设计或毕设节点上的学生。它解决的不是“搞个模型然后赌一把高分”这种玄学问题而是给出一条从数据集下载、目录整理、训练到出评估指标的完整链路代码是能直接跑通的那种不是半成品。zip 里核心是 traffic_net.py 这个模型定义文件、model_class.json 类别配置、两个 README以及一份作者自己留下的错误.txt——里面记录的全是训练过程中真实踩过的报错。适合两类人一类是刚入门深度学习、想复现一个图像分类项目当作业交的另一类是手里已经有一批监控截图、想快速验证拥堵等级识别能不能落地的人。下面按我实际拆包调通这个项目的顺序把结构、训练、踩坑、评估和二次开发一次讲完。2. 先把模型结构看懂traffic_net.py 和 model_class.json 里藏着什么2.1 轻量卷积加 SE 注意力Traffic-Net 骨干为什么适合拥堵识别traffic_net.py 这个文件是整个项目的核心。拆开看它沿用的是一类轻量卷积网络的设计思路主体是几个卷积块堆叠每个卷积块里包含卷积、批归一化、ReLU 激活和最大池化中间穿插 SESqueeze-and-Excitation注意力模块。这种结构在 Traffic-Net 这类轻量网络里非常常见核心动机是拥堵识别任务的输入是监控摄像头画面模型要捕捉的是车流密度、车辆排列紧凑程度、是否停滞这些大范围空间纹理特征不需要像 ResNet 那样堆到五十层上百层。我拆包后把典型结构整理成表格你对照 traffic_net.py 里的层定义看基本能对上层块输出尺寸说明ConvBlock 1112 x 112 x 323x3 卷积步长 2BN ReLU下采样ConvBlock 256 x 56 x 643x3 卷积步长 2BN ReLUSE Block56 x 56 x 64全局平均池化 两个全连接给通道加权ConvBlock 328 x 28 x 1283x3 卷积步长 2BN ReLUGlobal Avg Pool1 x 1 x 128全局平均池化替代 Flatten 全连接FC 分类头num_classes输出类别数softmax 激活这里有个容易被忽略的设计点SE Block 放在第二个卷积块后面而不是最后。因为拥堵特征里“车辆排列紧密”和“路面纹理”属于不同尺度的信息SE 加在中间层能对中层通道做重标定比只放在最后效果好。训练时如果 loss 降不下去可以先把 SE 去掉试试对比一下是不是注意力模块在少量数据上过拟合了。2.2 model_class.json 与标签编码训练前先确认类别顺序model_class.json 这个文件很多人会直接忽略但它决定了训练和推理阶段类别索引能不能对得上。它是 JSON 格式常见内容长这样{ 0: 畅通, 1: 缓行, 2: 拥堵, 3: 严重拥堵 }注意这个文件里的顺序必须和数据文件夹的排序规则一致。项目代码在读取训练数据时通常是用 os.listdir 列出目录后做 sorted 排序再按字母序生成类别索引。也就是说如果数据文件夹里子目录名是 english、jams、normal、slow那么 sorted 之后索引 0 对应 english1 对应 jams依次类推。如果 model_class.json 里写的顺序和这个排序结果不一致训练出来的模型在推理时会全部错位——准确率看着挺高实际预测结果全是乱的。我一般拿到项目第一件事就是用下面这段代码核对映射关系避免训练到一半才发现标签全错import json import os # 假设数据目录位于 dataset/ 下子目录即类别 data_dir dataset/train real_classes sorted(os.listdir(data_dir)) with open(model_class.json, r, encodingutf-8) as f: cfg json.load(f) # 按索引顺序取配置文件里的类别名 cfg_classes [cfg[str(i)] for i in range(len(cfg))] print(配置文件顺序:, cfg_classes) print(目录实际顺序:, real_classes) if cfg_classes ! real_classes: print(警告: 类别顺序不一致, 请修改 model_class.json 或目录名)逻辑说明这里先把数据目录里的子目录名取出来做排序再按配置文件里从 0 开始的索引取出类别名两者对比。如果顺序不一致训练脚本里生成的标签和模型输出的 logits 就对不上后面所有评估都会失真。参数说明data_dir 要换成你自己存放训练数据的一级目录路径model_class.json 的键是字符串形式的索引不要漏掉 str() 转换否则在 Python 某些版本下会直接 KeyError。2.3 训练脚本默认超参batch、lr、轮数该不该动traffic_net.py 里如果同时包含了训练入口通常会有一大段 argparse 参数或者一个 config 区。这类课程设计项目最常见的默认配置如下参数默认值我的建议batch_size32显存不够就降到 16配合梯度累积learning_rate0.001用 Adam 时搭配 warmup别直接调大epochs50数据量小的话 30 轮足够收敛optimizerAdam换 SGD 动量需要把 lr 调到 0.01 级别weight_decay1e-4数据量少时可以加大到 1e-3 防过拟合schedulercosine比 step 下降更稳推荐保留这里最容易翻车的操作是直接把 lr 改成 0.01 想“加速收敛”换来的往往是 loss 剧烈震荡。轻量 CNN 配 Adamlr 超过 0.002 基本就会出问题尤其数据量只有几千张时。我的习惯是保持默认 0.001先跑 5 个 epoch 观察 loss 曲线如果前 5 轮 loss 没有明显下降再考虑是 lr 问题还是数据问题而不是盲目调参。3. 把数据集喂进去目录约定、训练命令与参数调优3.1 数据目录怎么摆一级子文件夹即类别别学 YOLO 那套标注这个项目用的是最省事的图像分类目录约定不需要像 YOLOv8 训练自己的数据集那样去画框、生成 txt 标签文件。你只需要把图片按类别放进不同子文件夹结构如下dataset/ ├── train/ │ ├── clear/ # 畅通 │ ├── slow/ # 缓行 │ ├── jam/ # 拥堵 │ └── serious_jam/ # 严重拥堵 └── val/ ├── clear/ ├── slow/ ├── jam/ └── serious_jam/如果训练脚本里默认读的是 train 和 val 两个目录你只需要把图片按这个结构放好就行。注意 val 目录下每个类别的图片数量和 train 保持类似的比例别让验证集某个类别只有一两张那样评估指标会非常不稳定。这里有一个很多人会犯的错误以为文件名里带类别关键字就行比如“jam_001.jpg”放在 clear 文件夹里模型训练时照样把它当 clear 用。目录名才是唯一标签来源文件名只影响排序不影响分类。3.2 从零拉起一次训练命令、日志与进度解读数据摆好、model_class.json 核对无误后直接跑训练。常见命令长这样python train.py --data_dir dataset --epochs 50 --batch_size 32 --lr 0.001 --output_dir checkpoints如果项目入口就是 traffic_net.py 本身可能是python traffic_net.py --mode train --data_dir dataset --epochs 50跑起来之后日志会输出类似这样的内容Epoch [10/50], Step [100/312], Loss: 0.4832, Acc: 0.8125 Epoch [10/50], Step [200/312], Loss: 0.4017, Acc: 0.8594解读日志有两个关键点。第一关注每轮结束时的验证集准确率而不是 step 级的训练准确率——训练准确率因为 dropout 和数据增强的关系本身就会比验证高几个点。第二如果 step 数远小于预期比如数据有几千张但 step 只显示几十说明 dataloader 的 batch_size 设置过大或者图片读取出了问题导致实际加载的样本数远小于文件夹里的文件数需要回到数据目录检查是否有无法解码的损坏文件。训练完成后checkpoints 目录下会生成模型权重文件通常是 .pt 或 .pth 格式。如果脚本只保存了最后一轮权重建议你自己改一下让它每个 epoch 都保存一次这样中途过拟合了还能回退到前面的轮次。3.3 样本不均衡类别权重和过采样两种处理思路交通拥堵数据天然是不均衡的。畅通和严重拥堵的样本好找缓行这种中间状态的数量往往只有前者的三分之一。如果不做处理模型会偏向样本多的类别验证时整体准确率还行但缓行的 recall 可能惨不忍睹。最常见的处理方式是在损失函数里传类别权重。以 PyTorch 为例在训练脚本里加一段权重计算import torch from torch.utils.data import DataLoader # 统计每个类别的样本数 class_counts [] for cls_name in sorted(os.listdir(dataset/train)): n len(os.listdir(os.path.join(dataset/train, cls_name))) class_counts.append(n) total sum(class_counts) # 权重 总样本数 / (类别数 * 该类别样本数), 常见做法 weights [total / (len(class_counts) * c) for c in class_counts] weights torch.tensor(weights, dtypetorch.float) criterion torch.nn.CrossEntropyLoss(weightweights)逻辑说明这段代码遍历训练目录下每个类别文件夹统计图片数量然后按公式计算权重——样本数越少的类别权重越大。CrossEntropyLoss 传入 weight 参数后每个 batch 的 loss 会按类别加权小样本类别的梯度贡献变大模型不会一边倒地学大类别。参数说明weights 列表的顺序必须和之前核对过的类别顺序一致否则权重就张冠李戴了。如果你的显卡显存比较小batch_size 只能用 8 或 16那单靠权重可能还不够可以配合过采样——常见做法是用 WeightedRandomSampler 让 dataloader 多抽小类别的样本sample_weights [] for cls_idx, cls_name in enumerate(sorted(os.listdir(dataset/train))): n len(os.listdir(os.path.join(dataset/train, cls_name))) sample_weights.extend([weights[cls_idx]] * n) sampler torch.utils.data.WeightedRandomSampler(sample_weights, num_sampleslen(sample_weights), replacementTrue) train_loader DataLoader(train_dataset, batch_size32, samplersampler)逻辑说明WeightedRandomSampler 会给每个样本分配一个被抽中的概率权重权重越高的样本被抽到的次数越多这样每个 epoch 里小类别样本出现的频率就上去了。参数说明replacementTrue 表示允许同一个样本在一个 epoch 里被重复抽取这是过采样的关键num_samples 一般设成总样本数这样每个 epoch 的长度和原来差不多。4. 训练避坑五条真实踩坑记录4.1 路径中文与环境问题最容易翻车的两个坎现象一项目解压后放在“D:\毕业设计\交通拥堵识别\”这种带中文的路径下训练脚本一跑就报 FileNotFoundError或者报路径里有非法字符。有时候不报错但数据集加载数量为 0。原因项目里的路径拼接大多用 os.path.join 加相对路径在 Windows 下中文路径配合某些编码方式的 Python 环境会产生乱码尤其是从 zip 解压出来的文件如果还带特殊符号容易被系统截断。README 里那句“项目路径不要用中文”说的就是这个问题。解决把整个目录重命名为纯英文比如 D:\traffic-net-project\traffic_net然后重新解压或移动再跑一次。顺便检查一下 zip 解压后是否多了一层嵌套目录常见做法是把最外层那层多余目录去掉保证 traffic_net.py 在项目根目录。现象二训练速度异常慢一个 epoch 要跑十几分钟而且 GPU 显存占用为 0。原因装了 CPU 版的 PyTorch。很多国内镜像默认装的 torch 是 CPU 版训练代码里 model.cuda() 虽然不报错但实际上根本没用上 GPU。解决去 PyTorch 官网选对应 CUDA 版本的安装命令重新安装比如 pip install torch torchvision --index-url 对应 CUDA 版本的地址。装完用 torch.cuda.is_available() 验证返回 True 再训练。4.2 数据和训练过程中的三个高频坑现象三训练跑到某个 epoch 中途直接报错中断错误信息类似 “cv2.error: OpenCV(4.x) … bad image format” 或 PIL 的 “OSError: image file is truncated”。原因数据集里混入了损坏或截断的图片文件。这类文件在文件夹里能看到但读取时解码失败。网络上下载的数据集经常有这种问题尤其是一些图片是抓包抓来的文件本身不完整。解决训练前跑一遍数据清洗把无法解码的图挑出来删掉或者跳过from PIL import Image import os bad_files [] for root, dirs, files in os.walk(dataset): for name in files: if not name.lower().endswith((.jpg, .jpeg, .png)): continue path os.path.join(root, name) try: img Image.open(path) img.load() except Exception: bad_files.append(path) print(损坏图片数量:, len(bad_files)) for p in bad_files: print(p) # 这里可以决定是删除还是移动到 backup 目录逻辑说明Image.open 是惰性加载真正解码是调用 load() 时才发生。所以必须执行 img.load() 才能触发完整读取捕获到异常就说明文件损坏或格式不对。参数说明这段代码遍历整个 dataset 目录包括 train 和 val。建议不要直接删除先打印列表确认一下万一是某些特殊格式比如 CMYK 模式的 JPEG被误判再单独处理。现象四loss 在训练到第 20 轮左右后不再下降验证准确率在一个值附近来回震荡上不去也下不来。原因lr 固定不变导致后期收敛困难或者模型在少量数据上已经过拟合验证集指标开始波动。固定 lr 训练到后期梯度更新步长对于已经接近最优的区域来说太大了loss 会在一个范围内来回跳。解决把训练脚本里的 scheduler 改成余弦退火或者手动在 60% 轮数处把 lr 降一个量级。如果数据量本身只有几千张还可以加上 label smoothing 缓解过拟合criterion torch.nn.CrossEntropyLoss(weightweights, label_smoothing0.1)现象五验证集准确率 95%但拿一张没见过的真实监控截图去预测结果完全不对甚至把黑夜场景预测成畅通。原因训练集和验证集划分时有重叠或者数据增强做得太强导致模型学到了和拥堵无关的伪特征。最常见的重叠情况是同一个视频抽帧出的连续帧一部分进了 train一部分进了 val模型实际记住的是背景场景不是拥堵程度。解决按视频或时间段划分数据保证同一来源的帧只出现在一个集合里。数据增强里把饱和度、色相的增强幅度调小例如颜色抖动参数从 0.5 降到 0.2因为拥堵识别的关键特征是车流密度和间距不是色彩。5. 训练完别急着交评估指标、混淆矩阵与推理脚本5.1 从 accuracy 到类内 precision、recall拥堵识别到底要看什么很多课程设计报告里只写一个 overall accuracy这在样本不均衡的数据集上说服力很弱。比如严重拥堵只占 10%模型全预测成畅通准确率也有 70%。真正能说明问题的是每个类别的精确率和召回率以及加权 F1。计算这几个指标不需要额外安装包用 sklearn 一行就能出from sklearn.metrics import classification_report, f1_score # y_true 为真实标签列表, y_pred 为预测标签列表 report classification_report(y_true, y_pred, target_names[clear, slow, jam, serious_jam]) print(report) weighted_f1 f1_score(y_true, y_pred, averageweighted) print(加权 F1:, round(weighted_f1, 4))逻辑说明classification_report 会输出每个类别的 precision、recall、f1-score 和样本数。你应该重点看“slow”这一行的 recall——如果明显低于其他类别说明模型把缓行误判成了畅通或拥堵。参数说明target_names 的顺序要和 model_class.json 里的一致否则报告里的类别名会对不上。5.2 用训练好的权重做单张图片推理模型评估完还得有一个能对单张图片预测的入口方便你拿自己的截图验证。项目如果没有提供推理脚本自己写一个很轻量import torch from PIL import Image from torchvision import transforms def predict(image_path, model, class_names, devicecpu): transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) img Image.open(image_path).convert(RGB) tensor transform(img).unsqueeze(0).to(device) model.eval() with torch.no_grad(): outputs model(tensor) probs torch.softmax(outputs, dim1)[0] pred_idx torch.argmax(probs).item() print(预测类别:, class_names[pred_idx]) print(置信度分布:) for i, name in enumerate(class_names): print(f {name}: {probs[i].item() * 100:.2f}%) class_names [clear, slow, jam, serious_jam] model torch.load(checkpoints/best_model.pt, map_locationdevice) predict(test_images/sample1.jpg, model, class_names)逻辑说明这里先对输入图片做和训练时一致的预处理——缩放到 224x224转张量并按 ImageNet 的均值和标准差归一化。注意这个归一化参数必须和训练脚本保持一致否则推理效果会明显变差。模型加载后一定要调用 eval() 模式因为训练模式下的 dropout 和 BN 统计方式不同直接推理结果会偏随机。参数说明device 参数支持 cpu 或 cuda:0如果你的权重是 GPU 上保存的加载时带上 map_location 参数可以避免 CPU 环境下报错。如果 model_class.json 里类别顺序和你这里 class_names 不一致推理结果会整体错位。5.3 混淆矩阵可视化发现“缓行被当成拥堵”这类系统性混淆混淆矩阵能直观告诉你模型在哪些类别之间容易混淆。交通拥堵场景最常见的就是缓行和拥堵这两个相邻等级分不清因为它们在视觉上本来就连续没有绝对清晰的边界。画矩阵用 sklearn 加 matplotlib十来行代码import matplotlib.pyplot as plt import numpy as np from sklearn.metrics import confusion_matrix cm confusion_matrix(y_true, y_pred) class_names [clear, slow, jam, serious_jam] fig, ax plt.subplots(figsize(6, 5)) im ax.imshow(cm, cmapBlues) ax.set_xticks(np.arange(len(class_names)), labelsclass_names) ax.set_yticks(np.arange(len(class_names)), labelsclass_names) plt.setp(ax.get_xticklabels(), rotation45, haright) for i in range(len(class_names)): for j in range(len(class_names)): ax.text(j, i, cm[i, j], hacenter, vacenter) ax.set_xlabel(预测类别) ax.set_ylabel(真实类别) plt.title(Confusion Matrix) plt.show()逻辑说明这段代码用 confusion_matrix 生成统计矩阵然后通过 imshow 绘制热力图。矩阵里第 i 行第 j 列表示真实类别为 i、被预测成 j 的样本数。对角线越亮说明准确率越高非对角线上的亮点就是混淆集中的位置。参数说明y_true 和 y_pred 必须是整数索引列表顺序和 class_names 对齐。如果发现缓行和拥堵混淆严重有两个处理方向一是把这两类的图片重新审查一遍看是否存在标注错误二是在后处理时把缓行的判定阈值调严比如要求缓行的 softmax 置信度必须超过 0.6 才输出否则归入相邻类别。6. 进阶把 Traffic-Net 移植到自己的拥堵识别场景6.1 冻结骨干只训分类头数据量少时最快见效的迁移方式如果不想用原始数据集想换成自己拍的街道截图最稳的路径是迁移学习。加载预训练权重后先冻结前面的卷积层只训练最后的分类头等分类头收敛后再解冻部分骨干层做微调。for param in model.conv1.parameters(): param.requires_grad False for param in model.conv2.parameters(): param.requires_grad False # conv3 保持可训练, 分类头保持可训练 optimizer torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr0.001)逻辑说明冻结浅层的原因是前几层学到的是通用纹理和边缘特征对不同数据集都适用而深层特征和分类头更贴近具体任务需要重新适应你的数据。参数说明如果你自己的数据集只有两三千张建议只训分类头 10 轮再解冻全部层用 1e-4 的学习率微调 10 轮这个节奏不容易过拟合。6.2 增强组合与导出部署拥堵识别场景里最有用的数据增强是随机裁剪、水平翻转、随机亮度和对比度调整。翻转时要注意如果将来要判断的是“单向车道拥堵”水平翻转会把左右方向颠倒但拥堵程度这个标签不受影响所以做水平翻转是安全的。导出 ONNX 便于部署到服务端或边缘设备一行命令即可import torch model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export(model, dummy_input, traffic_net.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}) print(ONNX 导出完成)逻辑说明dynamic_axes 让 batch 维度成为动态的这样部署时可以一次输入多张图。导出后可以用 onnxruntime 验证输出和 PyTorch 结果是否一致误差通常小于 1e-4。我做这类项目有个习惯不管时间多紧拿到 zip 后永远先做三件事——检查路径是否英文、核对 model_class.json 与目录顺序、清洗损坏图片然后再谈训练。这三步做完项目基本已经成功一半了。从那以后我每次带学生或自己复现项目都强制走一遍这个流程省掉的不只是排查报错的时间还有反复重训模型的精力。希望这些拆解和避坑记录能帮到你。本文还有配套的精品资源点击获取
返回列表