卷积神经网络基础:卷积运算原理、池化与激活函数、典型CNN网络结构

各位同学,今天我们来聊聊卷积神经网络的基础。说实话,很多做FPGA加速的同学,一上来就盯着硬件代码看,反而把算法层面的东西忽略了。我个人觉得,不理解卷积运算的本质,你写出来的加速器大概率是跑不快的。

咱们先看一张整体框架图,把今天要讲的内容串起来。

CNN知识体系总览 卷积运算 • 卷积核/滤波器 • 步长与填充 • 特征图计算 • 多通道卷积 • 1x1卷积 池化与激活 • 最大池化 • 平均池化 • ReLU激活 • Sigmoid/Tanh • 下采样作用 经典网络 • LeNet-5 • AlexNet • 网络结构对比 • 参数规模 • 硬件部署要点

一、卷积运算原理——说白了就是滑动窗口做乘法

卷积运算,你把它理解成一个「滑动窗口」就行了。一个小的矩阵(我们叫它卷积核或者滤波器),在输入图像上从左到右、从上到下地滑动,每次滑动都做一次点乘求和。

举个例子,输入是一张 5x5 的灰度图,卷积核是 3x3。卷积核滑到某个位置,把对应位置的 9 个像素值和卷积核的 9 个权重分别相乘,再全部加起来,得到一个输出值。这个输出值就是特征图上的一个点。

关键参数就三个:

  • 卷积核大小(Kernel Size):常见的有 3x3、5x5、7x7。我个人的经验是,3x3 在硬件上最友好,因为数据复用率高,DSP 资源也容易安排。
  • 步长(Stride):每次滑动多少格。步长=1 输出尺寸基本不变,步长=2 输出尺寸直接减半。
  • 填充(Padding):输入边缘补一圈 0,目的是控制输出尺寸。Same padding 就是让输出和输入一样大。

输出特征图的尺寸怎么算?公式很简单:

输出尺寸 = (输入尺寸 - 卷积核尺寸 + 2 * 填充) / 步长 + 1

举个例子:输入 32x32,卷积核 3x3,步长 1,填充 1。算一下:(32 - 3 + 2*1)/1 + 1 = 32。输出还是 32x32,完美。

我的小经验:在 FPGA 上做卷积加速时,我建议优先考虑步长=1、填充=1 的配置。为什么?因为数据流的控制最简单,地址计算也规整。我曾经在一个项目里用了步长=2,结果地址生成逻辑多花了三天才调通,得不偿失。

再说说多通道卷积。一张彩色图有 RGB 三个通道,那卷积核也得有三个通道。每个通道各自做卷积,然后把三个结果加起来,得到一个输出通道。如果有 N 个卷积核,就得到 N 个输出通道。说白了,输入通道数决定卷积核的深度,输出通道数决定卷积核的个数。

还有一个东西叫 1x1 卷积。你可能会问,1x1 能提取什么特征?其实它不提取空间特征,它做的是通道间的线性组合,用来升降维。我在做 MobileNet 的 FPGA 加速时,大量用了 1x1 卷积,计算量直接降了一个数量级。

二、池化与激活函数——给特征图「瘦身」和「加非线性」

卷积完了之后,一般会接池化和激活。这两个操作在硬件上实现起来非常便宜,但作用巨大。

池化(Pooling)

池化就是下采样。最常见的两种:

  • 最大池化(Max Pooling):取窗口里的最大值。我习惯用 2x2、步长=2 的最大池化,输出尺寸直接减半。
  • 平均池化(Average Pooling):取窗口里的平均值。现在用得少了,但早期网络里常见。

池化有什么好处?第一,减少参数量,防止过拟合。第二,带来平移不变性——特征稍微偏移一点,池化结果不变。第三,扩大感受野,后面的卷积能看到更大的范围。

注意:在 FPGA 上实现池化时,最大池化只需要比较器,平均池化需要加法器和除法器。如果你资源紧张,优先选最大池化。我曾经为了省几个 LUT,硬是把平均池化改成了最大池化,精度只掉了 0.3%,但资源省了 15%。

激活函数

激活函数的作用是引入非线性。如果没有激活函数,再多层卷积也只是线性变换,那跟单层没区别。

最常用的激活函数:

  • ReLU:f(x) = max(0, x)。简单粗暴,负数直接干掉。硬件实现只需要一个比较器和一个多路选择器,几乎不占资源。
  • Sigmoid:f(x) = 1/(1+e^{-x})。输出范围 0~1,适合做概率输出。但硬件实现很麻烦,需要查表或者分段逼近。
  • Tanh:f(x) = (e^x - e^{-x})/(e^x + e^{-x})。输出范围 -1~1,硬件实现同样复杂。

我个人强烈建议:在硬件加速中,能用 ReLU 就用 ReLU。原因很简单——它不需要乘法,不需要除法,甚至连指数运算都不用。你想想看,一个 DSP 块做乘法多宝贵,省下来做卷积不好吗?

避坑指南:我曾经在一个项目里用了 Swish 激活函数(f(x)=x*sigmoid(x)),精度确实比 ReLU 好一点,但硬件资源直接翻了三倍。后来跟算法团队沟通,换回 ReLU,精度只掉了 0.5%,但资源省了一半。所以,硬件加速不是一味追求精度,要在精度和资源之间找平衡。

三、典型CNN网络结构——从LeNet到AlexNet

理论讲完了,咱们看看实际网络长什么样。我挑两个最经典的:LeNet 和 AlexNet。

LeNet-5——CNN的鼻祖

LeNet 是 Yann LeCun 在 1998 年提出的,用来做手写数字识别。结构非常简单:

层 类型 核大小/步长 输出尺寸
输入 图像 - 32x32x1
C1 卷积 5x5 / 1 28x28x6
S2 平均池化 2x2 / 2 14x14x6
C3 卷积 5x5 / 1 10x10x16
S4 平均池化 2x2 / 2 5x5x16
C5 卷积 5x5 / 1 1x1x120
F6 全连接 - 84
输出 全连接 - 10

你看,LeNet 只有 7 层(不算输入),参数量大约 6 万。在今天的标准来看小得可怜,但在 90 年代已经是革命性的了。

LeNet 有个特点:它用的是平均池化,不是最大池化。这在当时是主流,但后来被最大池化取代了。另外,它的激活函数是 Sigmoid 和 Tanh,不是 ReLU。ReLU 是 2010 年以后才流行起来的。

硬件角度怎么看 LeNet?这个网络非常适合做 FPGA 加速的入门练习。层数少,参数少,数据流简单。我建议初学者先用 LeNet 跑通整个加速流程,再考虑更复杂的网络。

AlexNet——深度学习的引爆点

AlexNet 在 2012 年的 ImageNet 比赛上一举夺冠,直接引爆了深度学习的热潮。它的结构比 LeNet 复杂得多:

层 类型 核大小/步长 输出尺寸
输入 图像 - 227x227x3
Conv1 卷积 + ReLU 11x11 / 4 55x55x96
Pool1 最大池化 3x3 / 2 27x27x96
Conv2 卷积 + ReLU 5x5 / 1 27x27x256
Pool2 最大池化 3x3 / 2 13x13x256
Conv3 卷积 + ReLU 3x3 / 1 13x13x384
Conv4 卷积 + ReLU 3x3 / 1 13x13x384
Conv5 卷积 + ReLU 3x3 / 1 13x13x256
Pool5 最大池化 3x3 / 2 6x6x256
FC6 全连接 + ReLU - 4096
FC7 全连接 + ReLU - 4096
FC8 全连接 - 1000

AlexNet 有 8 层(5 个卷积 + 3 个全连接),参数量高达 6000 万。它有几个关键创新:

  • ReLU 激活函数:解决了 Sigmoid 的梯度消失问题,训练速度快了好几倍。
  • 最大池化:取代了平均池化,效果更好。
  • Dropout:随机丢弃一部分神经元,防止过拟合。
  • 数据增强:随机裁剪、翻转等,变相增加训练数据。
  • GPU 并行训练:用两块 GTX 580 并行训练,这是 GPU 加速的里程碑。

硬件加速的挑战:AlexNet 的全连接层占了绝大部分参数(约 5800 万)。在 FPGA 上做全连接层,说白了就是矩阵乘法,需要大量的 DSP 和 BRAM。我建议在部署时考虑量化(比如 INT8),否则 BRAM 根本存不下权重。

从 LeNet 到 AlexNet,我们学到了什么?

对比这两个网络,你会发现几个趋势:

  • 网络越来越深:从 7 层到 8 层,但更重要的是每层的通道数大幅增加。
  • 卷积核越来越小:从 11x11 到 5x5 再到 3x3,小卷积核可以堆叠得更深,参数量反而更少。
  • 激活函数从 Sigmoid 变成 ReLU:硬件实现也更简单了。
  • 池化从平均变成最大:硬件上只需要比较器。

这些趋势对硬件加速来说,其实是好事。小卷积核意味着更好的数据复用,ReLU 意味着更简单的计算单元,最大池化意味着更少的资源消耗。

总结一下:卷积运算就是滑动窗口做乘加,池化就是下采样,激活就是加非线性。LeNet 和 AlexNet 是 CNN 发展的两个里程碑,理解了它们,你就掌握了 CNN 的核心思想。后面更复杂的网络(VGG、ResNet、MobileNet 等)都是在这个基础上演变出来的。

好了,这一章的内容就到这里。记住,理论是基础,但真正的理解来自于动手实践。下一章我们会把这些知识用到 FPGA 上,看看怎么用硬件实现一个卷积加速器。

可可精品资料库