ARTICLE DETAIL

资讯详情

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

PyTorch车型识别实战:MobileNetV2与ResNet双主干训练部署

PyTorch车型识别实战:MobileNetV2与ResNet双主干训练部署 简介面向计算机相关专业毕业生的深度学习车型识别系统项目属于中等难度实战源码适合正在筹备毕业设计或希望进行项目练习的学生使用。项目经导师指导并认可通过评审分数为九十八分源码均已完成本地编译与严格调试确保能够直接运行。资源包以压缩包形式提供内含十五个文件压缩包大小约1.12MB其中十个Python源码文件实现了包括网络结构构建、数据集预处理、训练入口、模型复杂度计算、学习率调度、日志记录等核心模块同时附带两份说明文档、一份运行说明、一份使用手册及环境配置文件便于理解整个项目脉络。目前已有一百七十二人学习下载。通过这份资源读者可以掌握从数据准备、模型设计训练到性能评估的完整工程方法既可作为毕业设计参考也可作为深度学习实战的练习项目。1. 一台摄像头、70 类车型和一份能落地的 PyTorch 源码做过车牌识别的人都知道车牌只是入口真正有业务价值的是「车是什么牌子、什么型号」。这个基于 PyTorch 的车型识别系统把 MobileNetV2 和 ResNet 两条主干都放进一个工程里配合 CLR 循环学习率、数据增强和 flops 计算脚本训练完可以直接导出权重跑推理。对正在做 Python 毕业设计、或者想快速上手图像分类项目的开发者来说它最大的价值不是“能跑通”而是“每一步都能看懂为什么这么做”——从数据集目录组织到训练入口脚本 run.py 的启动方式再到 start.py 里的推理流程完整覆盖了一个小型图像识别项目从训练到部署的全部环节。项目代码在本地编译运行通过调试痕迹和文档手册齐全适合当作基线工程二次开发。2. 模型选型逻辑MobileNetV2、ResNet 与 flops_benchmark 的取舍决策2.1 为什么同时保留 resnet.py 和 mobile_net.py 两个主干车型识别本质上是一个细粒度图像分类任务类别之间的差异常常集中在车灯、进气格栅、后视镜轮廓这些小区域。ResNet 通过残差结构缓解深层网络退化在 ImageNet 上精度表现稳健MobileNetV2 则用深度可分离卷积把计算量压到 ResNet 的十分之一左右。这两个模型在同一份数据上表现并不总是“越大越好”——数据集规模有限时小模型的隐式正则化效果反而可能占优。# utils 中加载模型的标准写法两套主干共用同一套训练循环 import torchvision.models as models def build_model(archmobile_net, num_classes70, pretrainedTrue): if arch mobile_net: model models.mobilenet_v2(pretrainedpretrained) model.classifier[1] torch.nn.Linear(model.last_channel, num_classes) elif arch resnet: model models.resnet50(pretrainedpretrained) model.fc torch.nn.Linear(model.fc.in_features, num_classes) return model这段代码的关键在两个替换点MobileNetV2 的分类头是classifier[1]ResNet 是fc层它们的输入维度分别来自last_channel和fc.in_features这样替换后可以兼容任意预训练权重。项目里保留两个入口是想让你在同样数据下做对照实验而不是闷头选一个。2.2 用 flops_benchmark.py 量化计算成本光看精度选模型不够还要看算力能不能扛得住。flops_benchmark.py 用 thop 库统计 FLOPs 和参数量这个脚本在毕设答辩时也很有用能直接回应“你的模型有多大计算量”这类问题python flops_benchmark.py --arch mobile_net --input-size 224# flops_benchmark.py 核心片段 from thop import profile, clever_format def run_benchmark(model, input_size224): model.eval() dummy_input torch.randn(1, 3, input_size, input_size) flops, params profile(model, inputs(dummy_input,)) flops, params clever_format([flops, params], %.3f) print(fFLOPs: {flops} | Params: {params})profile会逐层统计乘加运算量clever_format把大数字转成 M/G 单位。我自己跑过的经验是MobileNetV2 在 224x224 输入下约 3.0 GFLOPsResNet50 约 41 GFLOPs差距在 13 倍以上。如果后续要部署到 Jetson Nano 或树莓派这个数据直接决定选型方向。2.3 模型选型的边界条件场景推荐主干理由CPU 推理、嵌入式设备MobileNetV2参数量小前向延迟低GPU 训练、追求精度ResNet50特征表征能力强适合细粒度分类数据量 1 万张MobileNetV2小模型过拟合风险更低迁移学习微调ResNet50预训练特征更丰富选型要结合设备算力和数据规模一起看不能只看榜单精度。3. 训练链路拆解dataset.py、transfor.py 与 clr.py 的配合3.1 数据集目录结构与 dataset.py 的加载方式整个项目的训练数据组织遵循 ImageFolder 约定这在 dataset.py 里体现得最直接。根目录下分 train 和 val 两个文件夹每个类别的图片放在以类别名命名的子目录中data/ ├── train/ │ ├── Audi_A4/ │ ├── BMW_3_Series/ │ └── ... └── val/ ├── Audi_A4/ └── ...# dataset.py 核心片段 from torchvision import datasets, transforms def get_dataloader(data_rootdata, batch_size32, is_trainTrue): dataset datasets.ImageFolder( rootos.path.join(data_root, train if is_train else val), transformget_transform(is_train) ) loader torch.utils.data.DataLoader( dataset, batch_sizebatch_size, shuffleis_train, num_workers4, pin_memoryTrue ) return loader, dataset.classesImageFolder 会自动按子目录名生成类别索引dataset.classes返回的就是按字典序排序的类别名列表运行 start.py 推理时需要用这个列表做索引映射。num_workers4在 Windows 上如果报错改成 0 就行。3.2 transfor.py 里的数据增强组合车形识别的难点在于拍摄角度变化需要在保持关键特征的前提下增强泛化性。transfor.py 里采用的策略组合很有代表性# transfor.py 核心增强逻辑 train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.6, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])RandomResizedCrop的scale(0.6, 1.0)很关键——车型图片通常包含大面积车身裁剪比例太小会把车切碎0.6 下限能模拟不同远近拍摄距离ColorJitter用来对抗光照变化适合停车场、路边等室外场景。注意验证集的变换不能加随机操作必须用Resize(256) CenterCrop(224)保证结果可复现。3.3 clr.py 的循环学习率实现循环学习率是这个项目比较有含金量的部分。传统 StepLR 需要手动调下降节点而 CLR 让学习率在一个区间内周期性波动既能跳出局部最优又免去精细调参。clr.py 基于 PyTorch 的LambdaLR实现三角策略# clr.py 核心逻辑 def triangular_lr(step, base_lr, max_lr, step_size): cycle math.floor(1 step / (2 * step_size)) x abs(step / step_size - 2 * cycle 1) return base_lr (max_lr - base_lr) * max(0, 1 - x) # 使用方式 scheduler torch.optim.lr_scheduler.LambdaLR( optimizer, lr_lambdalambda step: triangular_lr(step, 0.001, 0.01, 500) )step_size表示半个周期的步数设为 500 意味着学习率在 1000 步内完成一次从低到高再降到底的波动。前几个周期学习率会冲得比较高模型loss出现小幅震荡是正常的不要因此调低max_lr。3.4 训练入口 run.py 的参数组织run.py 是整个训练的入口核心超参数集中在文件头的配置区修改一次即可全局生效# run.py 训练配置 BATCH_SIZE 32 EPOCHS 120 BASE_LR 0.001 MAX_LR 0.01 CLR_STEP_SIZE 500 WEIGHT_DECAY 1e-4 MOMENTUM 0.9参数建议值说明BATCH_SIZE32取决于显存8GB 显卡建议不超过 64EPOCHS120配合 CLR 足够收敛也不至于过拟合MAX_LR / BASE_LR10 倍差距差距过大容易震荡过小失去 CLR 意义WEIGHT_DECAY1e-4正则化弱一点避免车型细节被压掉训练时监控训练 loss 和验证集 top-1 准确率。如果发现训练 loss 下降但验证准确率停滞优先检查transfor.py里有没有加RandomErasing——这种强增强在小数据集上反而可能拖慢收敛我一般会先关掉它跑 20 个 epoch 做基线。4. 推理环节start.py 的工作流程与踩坑记录4.1 加载权重并重组分类头模型训练完成后权重文件保存在 results 目录下。start.py 做推理时最常遇到的问题是在加载权重之前忘记重组模型分类头导致参数形状不匹配# start.py 推理脚本核心逻辑 import torch import torchvision.transforms as transforms from PIL import Image def load_inference_model(archmobile_net, weights_pathresults/best.pth, num_classes70): model build_model(archarch, num_classesnum_classes, pretrainedFalse) state_dict torch.load(weights_path, map_locationcpu) # 兼容 DataParallel 保存的权重 if module. in list(state_dict.keys())[0]: state_dict {k.replace(module., ): v for k, v in state_dict.items()} model.load_state_dict(state_dict) model.eval() return model def predict(model, image_path, class_names, devicecpu): img Image.open(image_path).convert(RGB) tensor val_transform(img).unsqueeze(0).to(device) with torch.no_grad(): output model(tensor) prob torch.softmax(output, dim1) top1_idx torch.argmax(prob, dim1).item() return class_names[top1_idx], prob[0][top1_idx].item()map_locationcpu让权重可以脱离 GPU 加载方便在没有独立显卡的机器上演示。convert(RGB)必须写——有些摄像头拍摄的图片带 Alpha 通道通道数不一致会直接报错。4.2 CPU 推理的性能优化参数如果只能 CPU 推理有两个参数能明显提速。第一是开启 torch 的 CPU 线程优化torch.set_num_threads(4)第二是推理时关闭梯度计算并合并 batch# batch 推理减少 Python 循环开销 inputs torch.stack([val_transform(Image.open(p).convert(RGB)) for p in img_paths]) with torch.no_grad(): logits model(inputs) top1 logits.argmax(dim1)单张图片推理在 CPU 上大约 80-150ms批量推理能压到每张 40ms 以内。在答辩现场用 CPU 演示时建议先预热跑一次再计时这样数据更稳定。4.3 识别结果与类名映射不匹配一个值得警惕的问题ImageFolder 会按字母序排序类别train 目录里第一个文件夹如果是Audi_A4索引 0 就是 Audi_A4。如果后面你在 dataset.py 里新增了数据类别索引顺序会整体改变。所以 start.py 里class_names的加载必须和训练时保持一致# 从训练目录重新构建类别列表保证一致性 _, class_names get_dataloader(data_rootdata, is_trainFalse) model load_inference_model(weights_pathresults/best.pth, num_classeslen(class_names))这里如果发现识别结果张冠李戴比如把宝马识别成奥迪先不要怀疑模型没训练好大概率是这里索引错位了。5. 换数据集复用的三条实操经验5.1 先冻结主干只训练分类头拿到新的车型数据集时不要直接全量微调。稳妥的做法是先冻结 Backbone只训练分类层 15-20 个 epoch等损失降下来再解冻主干用较小的学习率微调for param in model.features.parameters(): param.requires_grad False # 只训练分类头 optimizer torch.optim.Adam(model.classifier.parameters(), lr0.001)冻结阶段学习率可以给高一点因为分类头是随机初始化的解冻后学习率要降到原来的十分之一避免破坏预训练特征。5.2 用早停配合 CLR 的终点CLR 的学习率在周期结束时回到最小值这天然适合做早停。在 run.py 里记录验证集准确率如果连续 15 个 epoch 没有刷新最佳值直接保存最后一次权重退出训练if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), results/best.pth) patience 0 else: patience 1 if patience 15: break注意这个“15 个 epoch”是在 CLR 完整跑完至少两个周期后才开始计数的否则可能在学习率上升阶段就误触发早停。5.3 用 flops_benchmark 预判部署环境把模型移植到嵌入式设备之前先跑一遍 flops_benchmark.py 看看结果。如果 FLOPs 超过设备算力预算优先检查输入分辨率能否从 224 降到 160这一项能减少约一半计算量其次是考虑把 MobileNetV2 换成 MobileNetV3-Small结构上更激进但精度往往只掉 1-2 个百分点。项目文档里已经有这两套主干的切换方法按第 2 章的build_model改一个参数就行。本文还有配套的精品资源点击获取
返回列表