卷积神经网络基础:卷积层、池化层、全连接层、激活函数

各位同学,今天我们聊聊卷积神经网络(CNN)的四大核心组件。说实话,这玩意儿在嵌入式端做推理加速时,是绕不开的基础。我当年刚接触芯片加速器设计时,就是被这几个层的计算模式给折腾得够呛。今天咱们一个一个拆开讲。

卷积层:特征提取的“扫描仪”

卷积层说白了,就是一个滑动窗口的乘法累加操作。你想想看,一张图片进来,我们用一个小的“滤波器”(也叫卷积核)在图片上从左到右、从上到下地滑动,每到一个位置,就把滤波器里的数值和图片对应位置的像素值做点积,然后求和。

数学上,对于一个输入特征图 X 和卷积核 W,输出特征图 Y 在位置 (i, j) 的值是:

Y[i][j] = Σ_m Σ_n X[i+m][j+n] * W[m][n] + b

这里 b 是偏置项。嗯,这里要注意:卷积核的尺寸通常是奇数,比如 3×3、5×5。为什么?因为奇数核有中心点,方便对齐。我在项目中遇到过用 2×2 核的奇葩设计,结果边界处理起来特别麻烦,后来再也不用了。

关键参数:

  • 步长(Stride):每次滑动多少像素。步长越大,输出越小。
  • 填充(Padding):在输入边缘补零,控制输出尺寸。我习惯用“same”填充,让输出和输入尺寸一致。
  • 通道数:输入有几个通道,卷积核就有几个通道。输出通道数等于卷积核的个数。

在嵌入式加速器里,卷积层是计算量的“大头”。我做过一个统计,在 MobileNet 这样的轻量级网络上,卷积层占了总计算量的 90% 以上。所以,加速器设计的第一要务,就是优化卷积的数据流。

池化层:降维的“压缩器”

池化层的作用很简单:缩小特征图的尺寸,减少参数,防止过拟合。常见的池化有两种:最大池化和平均池化。

最大池化:取窗口内的最大值。数学上:

Y[i][j] = max(X[i*stride : i*stride+pool_size, j*stride : j*stride+pool_size])

平均池化:取窗口内的平均值。数学上:

Y[i][j] = avg(X[i*stride : i*stride+pool_size, j*stride : j*stride+pool_size])

我个人习惯用最大池化,因为它能保留最显著的特征。平均池化会把所有信息都“平均”掉,有时候反而会丢失关键信息。我曾经在一个图像分类项目里,把池化层从最大改成平均,准确率直接掉了 3 个点。从那以后,除非是特定场景(比如全连接层前的全局平均池化),我基本只用最大池化。

避坑指南: 池化层没有可训练参数,但它的计算模式对硬件设计有影响。我曾经在 FPGA 上实现池化时,忽略了边界处理,结果输出尺寸不对。后来我养成了习惯:先手算一遍输出尺寸,再写代码。

全连接层:分类的“决策者”

全连接层,说白了就是一个矩阵乘法加偏置。每个输入节点都连接到每个输出节点,所以叫“全连接”。数学上:

Y = W * X + b

其中 W 是权重矩阵,X 是输入向量,b 是偏置向量。

全连接层的参数量非常大。举个例子,如果输入是 1024 维,输出是 512 维,那权重矩阵就有 1024×512 = 524288 个参数。这在嵌入式设备上是个灾难。所以现在的轻量级网络(比如 MobileNet)都尽量用全局平均池化代替全连接层。

在加速器设计里,全连接层其实比卷积层好实现。为什么?因为它的数据流是规则的矩阵乘法,没有滑动窗口,没有填充,没有步长。我建议初学者先从全连接层的加速器开始练手,再挑战卷积层。

激活函数:非线性的“调味料”

如果没有激活函数,神经网络就只是个线性变换,再多层也没用。激活函数引入非线性,让网络能学习复杂模式。

ReLU:简单粗暴,效果拔群

ReLU 的数学定义:

f(x) = max(0, x)

说白了,负数全干掉,正数保留。ReLU 的好处是计算简单,没有指数运算,在硬件上只需要一个比较器就能实现。而且它能缓解梯度消失问题。

但 ReLU 有个问题:神经元“死亡”。如果某个神经元的输出一直是负数,那它的梯度就是 0,再也学不动了。我在一个深层网络里遇到过这个问题,后来换成了 Leaky ReLU(给负数一个很小的斜率),才解决了。

Sigmoid:经典但昂贵

Sigmoid 的数学定义:

f(x) = 1 / (1 + e^(-x))

输出范围在 0 到 1 之间,适合做二分类的输出层。但它的缺点很明显:

  • 计算复杂,有指数运算,硬件实现成本高
  • 梯度饱和,当 x 很大或很小时,梯度接近 0,训练困难
  • 输出不是零中心的,会影响梯度更新

在嵌入式加速器里,我几乎不用 Sigmoid。除非是输出层需要概率值,我会用查找表(LUT)来实现,避免直接算指数。

注意: 激活函数的选择会影响硬件设计。ReLU 只需要一个比较器,而 Sigmoid 需要查找表或近似计算。在资源受限的嵌入式设备上,优先选 ReLU 及其变体。

数据流:从输入到输出的“流水线”

一个典型的 CNN 数据流是这样的:

  1. 输入图片 → 卷积层(提取特征) → 激活函数(引入非线性) → 池化层(降维)
  2. 重复多次卷积+激活+池化
  3. 最后接全连接层(分类) → 输出结果

在加速器设计里,数据流的关键是“数据复用”。卷积层的数据复用有三种方式:

复用方式 说明 适用场景
输入特征图复用 同一个输入像素被多个卷积核使用 多输出通道
权重复用 同一个权重在滑动窗口内被多次使用 大尺寸输入
输出特征图复用 多个输入通道累加到同一个输出像素 多输入通道

我建议在设计加速器时,先分析你的网络结构,看看哪种复用方式占主导,然后针对性地优化数据流。比如在 MobileNet 里,深度可分离卷积的输入通道数很少,这时候权重复用的收益就不大,反而要优化输入特征图的复用。

总结一下:

  • 卷积层:计算量大,数据流复杂,是加速器的核心优化对象
  • 池化层:计算简单,没有参数,但要注意边界处理
  • 全连接层:参数量大,但数据流规则,适合用矩阵乘法加速
  • 激活函数:ReLU 是嵌入式首选,Sigmoid 尽量用查找表实现

好了,这一章的内容就到这里。下一章我们会深入卷积层的硬件实现,讲讲如何用脉动阵列(Systolic Array)来加速卷积计算。到时候我会分享一个我在项目里踩过的坑——数据流没设计好,导致加速器效率只有理论值的 30%。嗯,那是个难忘的教训。