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。到时候我会把我在项目中踩过的坑、总结的经验,一股脑全倒出来。准备好了吗?