1. 项目缘起与整体设计思路1.1 为什么选猫狗分类作为切入点猫狗分类这个题目在深度学习圈子里算是Hello World级别的存在。但别小看它真正把它从训练到部署完整跑通一遍尤其是最后落到Jetson Nano这种边缘设备上中间踩的坑足够写满一个笔记本。我选择这个项目作为练手核心原因有三点第一数据集获取门槛低Kaggle上的猫狗数据集规模适中25000张训练图加12500张测试图既不会小到欠拟合也不会大到普通显卡跑不动第二二分类问题的评价指标直观准确率、精确率、召回率一目了然方便快速验证模型结构改动的效果第三部署环节能完整覆盖从PC训练到边缘推理的全链路包括模型转换、量化、加速推理等实际工程问题。这个项目最终要达成的目标是在PC端用TensorFlow 2完成猫狗图像分类模型的训练将训练好的模型经过格式转换和优化后部署到Jetson Nano上实现本地实时推理。适合有一定Python基础、了解神经网络基本概念、想打通训练-部署全流程的开发者参考。如果你之前只在Colab上跑过模型从没碰过边缘设备那这个项目会帮你补上工程落地这一课。1.2 整体技术路线选型整个项目的技术路线可以拆成两大阶段训练阶段和部署阶段。训练阶段在PC端完成用TensorFlow 2的Keras API搭建卷积神经网络配合数据增强、学习率调度、早停等策略训练模型。部署阶段则涉及模型格式转换、推理优化和在Jetson Nano上的环境配置。为什么训练和部署要分开因为Jetson Nano的算力有限它的GPU只有128个CUDA核心内存4GB直接在上面训练模型会非常慢。合理的做法是在算力充足的PC或服务器上完成训练然后把推理能力搬到边缘设备上。这就像你在厨房把菜做好再端到餐桌上吃而不是把灶台搬到餐桌上现炒。模型格式的选择上我最终用了TensorFlow SavedModel作为中间格式再转换为TensorRT引擎在Jetson Nano上运行。为什么不直接用Keras的.h5文件因为.h5格式在跨平台部署时兼容性不够好而SavedModel是TensorFlow的标准格式包含完整的计算图和变量方便后续转换。TensorRT则是NVIDIA推出的推理优化库能对模型做层融合、精度校准、内核自动调优在Jetson Nano上能带来数倍的推理加速。1.3 硬件与软件环境规划PC端我用的配置是Intel i7处理器、32GB内存、NVIDIA RTX 3060显卡12GB显存。这个配置训练猫狗分类模型绰绰有余batch size设到64也不会爆显存。软件环境是Ubuntu 20.04 CUDA 11.2 cuDNN 8.1 TensorFlow 2.8。这里要提醒一句TensorFlow版本和CUDA版本的对应关系非常严格装错了就是各种找不到库的报错建议直接查官方兼容性表格再动手。Jetson Nano这边我用的是4GB版本配了一张32GB的microSD卡作为系统盘。系统刷的是NVIDIA官方提供的JetPack 4.6里面已经包含了CUDA 10.2、cuDNN 8.0和TensorRT 8.0。注意JetPack 4.6对应的TensorRT版本和PC端可能不一致模型转换时要注意版本兼容性。另外Jetson Nano的电源很重要一定要用5V 4A的DC电源或者高质量的Micro USB电源供电不足会导致推理过程中突然关机我一开始用了个劣质电源调试了半天以为是代码问题后来换了电源才解决。2. 数据集处理与模型训练核心细节2.1 猫狗数据集的获取与清洗Kaggle上的猫狗数据集Dogs vs. Cats下载下来是25000张带标签的训练图和12500张无标签的测试图。训练集里猫和狗各12500张文件名格式是cat.0.jpg、dog.1.jpg这样。但实际用的时候不能直接拿来训练有几个问题需要处理。第一个问题是图片尺寸不一致。数据集里的图片大小从几十像素到上千像素都有直接resize到统一尺寸会损失信息。我的做法是先统一resize到256x256训练时再随机裁剪到224x224。这样既保证了输入尺寸一致又通过随机裁剪增加了数据多样性。第二个问题是数据不平衡。虽然猫狗各12500张看起来是平衡的但实际检查发现有些图片是损坏的有些是重复的。我写了个简单的脚本检查图片完整性用PIL打开每张图捕获异常把打不开的图片剔除。重复图片用感知哈希pHash检测汉明距离小于5的视为重复只保留一张。这一步大概剔除了200多张问题图片虽然比例不大但能避免训练时的意外报错。第三个问题是验证集划分。我从训练集里划出20%作为验证集也就是20000张训练、5000张验证。注意划分时要保证猫狗比例一致用分层抽样stratified sampling而不是随机抽样。Keras的image_dataset_from_directory默认就是按文件名排序后取前80%做训练这样如果文件名有规律可能导致验证集里全是某一种类。我手动写了个划分脚本先按类别分组再在每组内随机取20%做验证。2.2 数据增强策略的设计与取舍数据增强是防止过拟合的关键手段但增强策略不能乱加。我见过有人把水平翻转、垂直翻转、旋转90度、随机裁剪、颜色抖动全堆上去结果模型训练不动因为增强后的图片已经和原始分布差太远了。我的增强策略是这样的随机水平翻转概率0.5、随机旋转±15度、随机缩放0.9到1.1倍、随机平移±10%、随机亮度调整±0.1。垂直翻转我没用因为猫狗图片垂直翻转后不符合现实场景猫不会倒着走路。旋转角度限制在15度以内是因为角度太大时图片边缘会出现黑边影响模型判断。颜色抖动我只调了亮度没调对比度和饱和度。原因是猫狗分类主要靠形状和纹理特征颜色信息相对次要过度调整颜色反而可能让模型学到无关特征。实测下来这套增强策略让验证集准确率从85%左右提升到了92%以上效果很明显。还有一个细节是归一化。我用的方法是把像素值从0-255缩放到0-1之间再减去均值0.5、除以标准差0.5。这个操作在tf.keras.layers.Rescaling里可以一行搞定。注意归一化要在数据增强之后做否则增强操作会破坏归一化后的数值范围。2.3 模型结构的选择与调优过程模型结构我试了三套方案自己搭的小型CNN、VGG16迁移学习、MobileNetV2迁移学习。最后选了MobileNetV2原因后面细说。自己搭的CNN是四层卷积加两层全连接参数量约200万。训练了30个epoch验证集准确率卡在88%左右上不去了。分析原因是数据量不够大从零训练容易过拟合而且小模型的特征提取能力有限。VGG16迁移学习效果很好验证集准确率能到96%但模型大小有500多MB参数量1.38亿。在PC上跑没问题但放到Jetson Nano上推理一次要好几秒完全达不到实时要求。而且VGG16的输入尺寸固定224x224不够灵活。MobileNetV2是最终选择。它的核心是深度可分离卷积depthwise separable convolution把标准卷积拆成深度卷积和逐点卷积两步参数量和计算量都大幅降低。MobileNetV2在ImageNet上的准确率和VGG16接近但参数量只有350万模型大小14MB左右。在Jetson Nano上推理一张224x224的图片只要几十毫秒完全满足实时需求。迁移学习的做法是加载ImageNet预训练的MobileNetV2去掉顶部的分类层冻结前面的卷积基只训练新加的全连接层。训练10个epoch后再解冻最后30层做微调学习率降到1e-5。这样做的原因是前面的层提取的是通用特征边缘、纹理不需要重新训练后面的层提取的是高层语义特征需要针对猫狗任务微调。实测下来冻结训练阶段验证准确率就到93%了微调后能到97%左右。2.4 训练过程中的关键参数与技巧优化器我用的Adam初始学习率1e-3。但直接固定学习率训练到后期会震荡所以加了ReduceLROnPlateau回调验证集损失连续3个epoch不下降就把学习率乘以0.5。这个策略让模型在后期能更精细地收敛。损失函数是二分类交叉熵binary crossentropy因为猫狗分类是二分类问题。输出层用sigmoid激活输出一个0到1之间的概率值大于0.5判为狗小于0.5判为猫。这里有个小技巧如果猫狗样本不平衡可以给损失函数加类别权重但我们的数据基本平衡所以没加。Batch size设的64。试过128训练速度确实快但验证集准确率波动大试过32训练稳定但速度慢。64是个折中。Epochs设了50但实际用EarlyStopping回调验证集损失连续8个epoch不下降就停止训练最终在第37个epoch停止。还有一个容易被忽略的点是随机种子。为了让实验可复现我在训练开始时固定了Python、NumPy、TensorFlow的随机种子。虽然完全复现很难GPU的浮点运算有随机性但至少能保证数据划分和增强的随机性一致。3. 模型转换与Jetson Nano部署实操3.1 从Keras模型到TensorRT引擎的转换路径训练好的模型是Keras的.h5格式要部署到Jetson Nano上需要经过几步转换。完整的路径是Keras .h5 → TensorFlow SavedModel → ONNX → TensorRT引擎。为什么绕这么大一圈因为TensorRT原生不支持直接解析Keras模型需要中间格式过渡。第一步把.h5转成SavedModel。用model.save(saved_model, save_formattf)一行搞定。SavedModel目录下会有saved_model.pb和variables文件夹包含了完整的计算图和权重。第二步SavedModel转ONNX。这里用tf2onnx工具命令是python -m tf2onnx.convert --saved-model saved_model --output model.onnx --opset 13。opset版本选13是因为TensorRT 8.0对opset 13的支持最好。转换过程中可能会遇到不支持的算子比如某些自定义层这时候需要把自定义层替换成标准层再转换。第三步ONNX转TensorRT。这一步在Jetson Nano上做因为TensorRT引擎是和硬件绑定的在PC上生成的引擎不能直接在Jetson Nano上用。用trtexec工具trtexec --onnxmodel.onnx --saveEnginemodel.trt --fp16。--fp16表示用半精度浮点能进一步加速推理精度损失很小。转换过程大概需要几分钟取决于模型大小。注意TensorRT版本必须和JetPack里的版本一致否则引擎加载会失败。JetPack 4.6对应TensorRT 8.0如果你在PC上装了TensorRT 8.4生成的引擎在Jetson Nano上是用不了的。3.2 Jetson Nano环境配置与依赖安装Jetson Nano拿到手后第一件事是刷系统。用NVIDIA SDK Manager或者直接下载JetPack镜像烧录到SD卡。烧录完成后系统会自动重启按照提示完成初始设置。系统装好后先更新软件源sudo apt update sudo apt upgrade。然后安装Python环境。JetPack 4.6自带Python 3.6但很多新库需要Python 3.8以上。我的做法是用Miniconda创建一个Python 3.8的虚拟环境避免污染系统环境。安装TensorFlow。注意Jetson Nano上不能直接pip install tensorflow因为官方PyPI上的TensorFlow不支持ARM架构和Jetson的GPU。需要用NVIDIA提供的预编译版本下载对应的whl文件安装。安装完成后用tf.test.is_gpu_available()验证GPU是否可用。安装TensorRT的Python绑定。JetPack里已经包含了TensorRT的C库但Python绑定需要额外安装。用sudo apt install python3-libnvinfer-dev安装。安装完成后import tensorrt不报错就说明成功了。安装其他依赖OpenCV用于图像读取和预处理PyCUDA用于GPU内存管理NumPy用于数值计算。这些都可以用pip安装但OpenCV建议用apt安装python3-opencv因为pip安装的版本可能缺少某些编解码器。3.3 推理脚本的编写与性能优化推理脚本的核心逻辑是读取图片 → 预处理 → 送入TensorRT引擎 → 解析输出 → 显示结果。预处理要和训练时保持一致包括resize到224x224、归一化到[-1,1]、添加batch维度。TensorRT引擎的加载和推理用Python API实现。关键步骤是创建runtime、反序列化引擎、创建执行上下文、分配GPU内存、执行推理。这里有个坑TensorRT的输入输出是绑定在GPU内存上的需要用PyCUDA分配显存然后把数据从CPU拷贝到GPU推理完成后再从GPU拷贝回CPU。性能优化方面我做了几件事。第一用FP16精度推理速度比FP32快约1.5倍准确率只掉了0.2%。第二把输入尺寸从256x256改成224x224计算量减少了约25%。第三用CUDA流stream实现异步推理让数据拷贝和计算重叠。第四对连续视频帧做批处理一次推理多帧提高GPU利用率。实测下来优化前单张图片推理耗时约120毫秒优化后降到35毫秒左右帧率从8FPS提升到28FPS基本能满足实时视频处理的需求。如果进一步降低输入分辨率到160x160帧率能到40FPS以上但准确率会下降约1.5%需要根据实际场景权衡。3.4 部署过程中的常见报错与解决部署过程中遇到的报错五花八门我挑几个典型的说说。第一个是ImportError: libcudart.so.10.2: cannot open shared object file。这是因为CUDA库路径没加到环境变量里。解决方法是把/usr/local/cuda/lib64加到LD_LIBRARY_PATH里或者直接在~/.bashrc里export。第二个是TensorRT engine deserialization failed。原因通常是引擎文件和当前TensorRT版本不匹配。解决办法是删掉旧的引擎文件用当前版本的trtexec重新生成。第三个是CUDA out of memory。Jetson Nano只有4GB内存其中GPU共享一部分。如果同时开了太多程序或者推理时batch size设得太大就会爆内存。解决方法是关掉不必要的后台进程把batch size降到1或2或者用sudo nvpmodel -m 0切换到10W高性能模式注意散热。第四个是推理结果全错。排查后发现是预处理不一致训练时用的是RGB通道推理时OpenCV默认读的是BGR。加一行cv2.cvtColor(img, cv2.COLOR_BGR2RGB)就解决了。这个坑很隐蔽因为模型不会报错只是结果不对。4. 常见问题排查与实操经验汇总4.1 训练阶段的高频问题速查训练阶段最常见的问题是过拟合和欠拟合。过拟合的表现是训练集准确率很高比如99%验证集准确率低比如85%。解决方法包括增加数据增强、加Dropout层、加L2正则化、减小模型复杂度、早停。欠拟合的表现是训练集和验证集准确率都低。解决方法包括增加模型复杂度、减小正则化强度、增加训练轮数、提高学习率。学习率设置也是个高频问题。学习率太大会导致损失震荡不下降太小会导致收敛太慢。我的经验是先用1e-3试如果损失震荡就降到1e-4如果损失下降太慢就升到3e-3。配合ReduceLROnPlateau能省不少调参时间。还有一个问题是数据加载速度慢。如果直接用Python的PIL读图片GPU利用率会很低因为CPU读图速度跟不上GPU计算速度。解决方法是把数据转成TFRecord格式或者用tf.data.Dataset的prefetch和num_parallel_calls做并行加载。我用了后者把num_parallel_calls设成CPU核心数GPU利用率从40%提升到了85%以上。4.2 Jetson Nano部署的避坑指南Jetson Nano的坑主要集中在环境和性能上。环境方面最大的坑是版本兼容性。JetPack版本、CUDA版本、TensorRT版本、TensorFlow版本这四个必须严格对应错一个就是各种报错。建议直接查NVIDIA官方文档的兼容性表格不要凭感觉装。性能方面Jetson Nano的CPU是ARM Cortex-A57四核性能大概相当于手机处理器。如果推理脚本里有大量Python循环或者CPU预处理会成为瓶颈。我的做法是把预处理也放到GPU上做用TensorRT的预处理层或者CUDA核函数实现。另外Jetson Nano的散热很重要长时间高负载运行会降频加个散热风扇能稳定不少。电源问题前面提过这里再强调一次一定要用5V 4A的电源。我一开始用了个5V 2A的推理时经常自动关机换了电源后再没出现过。还有SD卡建议用Class 10以上的高速卡低速卡会导致系统卡顿和读写错误。4.3 模型精度与速度的平衡策略在边缘设备上部署模型永远面临精度和速度的权衡。我的策略是分场景处理如果对精度要求高比如医疗影像就用FP32精度、大输入尺寸、复杂模型如果对速度要求高比如实时视频监控就用FP16或INT8精度、小输入尺寸、轻量模型。INT8量化能把推理速度再提升一倍但需要校准数据集来确定量化参数。TensorRT提供了校准工具用几百张代表性图片跑一遍就能生成校准表。实测INT8量化后准确率掉了约1%但速度提升了近两倍。如果准确率要求不是特别苛刻INT8是值得尝试的。还有一个技巧是模型剪枝。把不重要的权重置零能减小模型大小和计算量。但剪枝后需要重新微调否则准确率会下降。我试过剪掉30%的权重微调后准确率只掉了0.5%模型大小减少了约25%。不过剪枝流程比较复杂如果时间紧优先考虑量化和输入尺寸优化。4.4 完整项目复现的步骤清单最后整理一份完整的复现步骤方便你照着做。PC端训练部分第一步下载猫狗数据集清洗图片划分训练集和验证集。第二步用tf.data.Dataset构建数据管道加数据增强和归一化。第三步加载MobileNetV2预训练权重冻结卷积基加全局平均池化层和全连接层。第四步编译模型用Adam优化器和二分类交叉熵损失。第五步训练模型加EarlyStopping和ReduceLROnPlateau回调。第六步解冻部分层做微调。第七步保存模型为SavedModel格式。模型转换部分第八步用tf2onnx把SavedModel转成ONNX。第九步把ONNX文件拷贝到Jetson Nano。第十步在Jetson Nano上用trtexec把ONNX转成TensorRT引擎加--fp16参数。Jetson Nano部署部分第十一步配置Jetson Nano环境安装TensorFlow、TensorRT、OpenCV等依赖。第十二步编写推理脚本加载TensorRT引擎实现预处理、推理、后处理。第十三步用摄像头或视频文件测试调整batch size和输入尺寸优化性能。第十四步加散热风扇用5V 4A电源确保稳定运行。这套流程我完整跑过三遍每次都能复现。中间最耗时的环节是环境配置和模型转换训练本身反而很快MobileNetV2在RTX 3060上训练50个epoch大概只要20分钟。部署后的推理效果也令人满意在Jetson Nano上跑猫狗分类准确率97%左右帧率28FPS完全能满足实时应用的需求。我个人在实际操作中的体会是边缘部署这件事难点不在模型本身而在工程细节。环境版本、数据格式、内存管理、电源散热每一个环节出问题都会导致失败。建议第一次做的时候先把PC端的训练和推理跑通再一步步往Jetson Nano上迁移不要一上来就两头同时搞那样出了问题很难定位。另外多看看NVIDIA官方论坛和TensorRT的文档很多坑别人已经踩过了直接抄作业能省不少时间。