3. 深度学习基础回顾(下):经典CNN架构、迁移学习与模型评估
好,咱们接着聊。上一节我们把深度学习的基础概念过了一遍,这一节我带你看看几个真正“能打”的经典CNN架构。说实话,这些网络我在自动驾驶项目里反复用过,有的甚至直接跑在量产车的嵌入式芯片上。你想想看,从LeNet到ResNet,这中间的变化其实就是一部“如何让网络更深、更快、更准”的进化史。
3.1 经典CNN架构:从LeNet到ResNet
3.1.1 LeNet-5:一切的开端
LeNet是Yann LeCun在1998年提出的,用来做手写数字识别。结构其实很简单:卷积→池化→卷积→池化→全连接。我刚开始学深度学习时,第一个跑通的网络就是LeNet。当时觉得“哇,这玩意儿能认数字,太神奇了”。
它的核心贡献在于:首次证明了卷积神经网络在图像任务上的有效性。虽然现在看它只有5层,参数才6万左右,但那个年代已经是革命性的了。
LeNet-5结构速览:
- 输入:32x32灰度图
- C1:6个5x5卷积核 → S2:2x2平均池化
- C3:16个5x5卷积核 → S4:2x2平均池化
- C5:120个5x5卷积核 → F6:84全连接 → 输出:10类
嗯,这里要注意:LeNet用的激活函数是sigmoid/tanh,现在基本被ReLU取代了。我在做车道线检测的早期原型时,试过用LeNet做特征提取,效果嘛……只能说够用,但不够好。
3.1.2 AlexNet:深度学习爆发的导火索
2012年,AlexNet在ImageNet上以碾压式的优势夺冠,直接引爆了深度学习热潮。我个人觉得,AlexNet最大的贡献不是网络结构本身,而是证明了GPU训练+大数据+深度网络这条路走得通。
它比LeNet深了不少,有8层(5个卷积+3个全连接),参数达到了6000万。结构上用了ReLU激活、Dropout、数据增强这些现在看起来很常规的操作,但在当时都是创新。
我的经验:AlexNet在嵌入式设备上跑起来有点吃力。6000万参数,光存储就要200多MB。我在一个老款Jetson TX1上试过,推理一帧要300多毫秒,根本没法用。所以后来做车道保持,我基本不用AlexNet,太臃肿了。
3.1.3 VGG:简单但有效
VGG是2014年牛津大学提出的,核心思想就一句话:全部用3x3的小卷积核,堆叠更深。VGG-16有16层,VGG-19有19层。参数比AlexNet还多,达到了1.38亿。
为什么全用3x3?两个3x3卷积堆叠,感受野相当于一个5x5,但参数更少,非线性更强。这个设计思路后来被广泛采用。我在做车道线语义分割时,VGG-16的编码器部分用得挺多,效果稳定,就是慢。
避坑指南:VGG的全连接层占了绝大部分参数(约90%)。我曾经在移植VGG到嵌入式平台时,发现光全连接层就有100多MB。后来果断把全连接层换成全局平均池化,模型直接缩小了80%。这个技巧在很多场景下都适用。
3.1.4 ResNet:残差学习的革命
ResNet是2015年何恺明团队提出的,解决了“网络越深,效果越差”的退化问题。核心就是残差连接(skip connection):让输入直接跨层传递,网络只需要学习残差部分。
为什么会退化?说白了,深层网络在反向传播时梯度容易消失。残差连接相当于给梯度开了一条“高速公路”,让信息能顺畅地流到浅层。ResNet-50有50层,但效果比VGG-16好得多,参数还更少(约2500万)。
# 一个简单的残差块实现(PyTorch风格)
class BasicBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1):
super().__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, 3, stride, padding=1)
self.bn1 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1)
self.bn2 = nn.BatchNorm2d(out_channels)
# 如果输入输出维度不匹配,用1x1卷积调整
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels, 1, stride),
nn.BatchNorm2d(out_channels)
)
def forward(self, x):
out = self.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
out += self.shortcut(x) # 残差连接
out = self.relu(out)
return out
我在车道保持项目中,最终选的就是ResNet-18作为骨干网络。18层,参数才1100万,在嵌入式芯片上能跑到30FPS以上,精度也够用。说白了,ResNet是“性价比之王”。
3.2 迁移学习:站在巨人的肩膀上
迁移学习,说白了就是把别人训练好的模型拿来,在自己的小数据集上微调一下。为什么有效?因为底层特征(边缘、纹理、颜色)是通用的,不管你是识别猫狗还是车道线,这些基础特征都一样。
我个人的习惯是:
- 冻结前几层:底层特征通用,不需要重新训练
- 微调后几层:高层特征跟任务相关,需要适应新数据
- 替换分类头:把原来的1000类分类器换成自己的(比如车道保持就2类:车道线/背景)
迁移学习在车道保持中的应用:
我做过一个实验:用ImageNet预训练的ResNet-18,冻结前5层,只微调后3层和分类头。在只有2000张车道线图片的情况下,训练了2个小时,精度就达到了90%以上。如果从零开始训练,同样的数据量,精度只有60%多。
嗯,这里要注意:迁移学习不是万能的。如果你的数据和预训练数据差异太大(比如红外图像 vs 自然图像),效果可能不升反降。我曾经踩过这个坑,后来老老实实从零开始训练了。
3.3 模型评估指标:选对尺子才能量对尺寸
做模型移植,不能光看精度。你得同时关注三个维度:准确率、速度、模型大小。这三者往往互相制约,你得找到最适合你场景的平衡点。
| 指标 | 含义 | 车道保持中的典型值 | 我的建议 |
|---|---|---|---|
| 准确率(Accuracy) | 预测正确的比例 | 95%以上 | 别只看整体准确率,要看车道线区域的召回率 |
| FPS(帧率) | 每秒处理帧数 | 嵌入式端≥30FPS | 车道保持至少需要15FPS,30FPS更安全 |
| 模型大小 | 模型文件占用的存储空间 | 嵌入式端≤10MB | 量化后一般能压缩到1/4 |
我的经验:在嵌入式平台上,我一般先定FPS目标(比如30FPS),然后在这个约束下最大化精度。模型大小是副产品,只要不超过Flash容量就行。千万别反过来——先追求99%精度,然后发现跑不动,那就白干了。
另外,模型大小不光影响存储,还影响加载速度和内存占用。我在一个只有256MB内存的设备上,加载一个50MB的模型,光模型参数就占了20%的内存,剩下的留给图像缓存和中间特征图,捉襟见肘。后来换成量化后的ResNet-18(约4.5MB),内存压力小多了。
3.4 小结:这一节你该记住什么
好,咱们快速回顾一下:
- LeNet:开山之作,现在基本不用了
- AlexNet:证明了深度学习的潜力,但太臃肿
- VGG:简单稳定,但全连接层是累赘
- ResNet:残差连接解决了深度退化,是嵌入式首选
- 迁移学习:小数据集的救星,但要注意数据分布
- 评估指标:精度、速度、大小,三者要权衡
下一节,咱们开始真正动手——搭建车道保持的数据集和训练pipeline。到时候我会把我在项目中踩过的坑、总结的经验,一股脑全倒出来。准备好了吗?