第三章 算子基础:卷积、池化、激活函数、全连接层在ISP中的角色

各位好,我是老周。今天咱们聊聊AI ISP里最基础的几个算子——卷积、池化、激活函数和全连接层。说实话,很多做ISP的工程师一听到“神经网络”就觉得头大,觉得那是算法工程师的事。但你要做算子移植和硬件加速,不懂这些算子的底层逻辑,那调试起来真的会抓瞎。

我个人习惯,先把这些算子在ISP里到底扮演什么角色搞清楚,然后再去抠计算量和访存量。这样你写代码的时候,心里才有底。

3.1 卷积层:ISP的“特征提取器”

卷积在ISP里干的事,说白了就是“看图说话”。它用一个小的窗口(卷积核)在图像上滑动,提取局部特征。比如边缘、纹理、颜色变化这些。

在传统ISP里,这些工作靠的是手工设计的滤波器——比如Sobel算子做边缘检测,高斯滤波做去噪。但在AI ISP里,卷积核的权重是训练出来的,不是人手工调的。

关键点:卷积核的大小决定了感受野。3×3的核看局部细节,5×5或7×7的核看更大范围的结构。在硬件实现时,3×3是最常见的,因为它在计算效率和特征提取能力之间取得了平衡。

我在项目中遇到过一个问题:用5×5的卷积核做去噪,效果确实好,但硬件资源消耗直接翻倍。后来我们改成两个3×3的级联,效果差不多,资源省了一大截。这就是所谓的“小核堆叠”技巧。

3.1.1 卷积的计算量分析

咱们来算笔账。假设输入特征图是H×W×Cin,输出是H×W×Cout,卷积核大小是K×K。

一次乘加操作算一次计算。那么总的计算量就是:

MACs = K × K × C_in × C_out × H × W

举个例子:输入是1920×1080的RGB图(3通道),用64个3×3的卷积核,输出也是1920×1080×64。算一下:

MACs = 3 × 3 × 3 × 64 × 1920 × 1080 ≈ 3.58 × 10^9

嗯,35.8亿次乘加操作。这还只是一个卷积层。你想想看,一个完整的AI ISP网络可能有几十层,这个计算量有多恐怖。

我的建议:在做硬件加速时,优先优化计算量最大的层。通常前几层的通道数少但分辨率高,后几层通道数多但分辨率低。你要根据实际硬件资源,决定是并行计算还是分时复用。

3.1.2 卷积的访存量分析

计算量是一回事,访存量是另一回事。很多时候,瓶颈不在计算,而在数据搬运。

卷积的访存包括三部分:

  • 输入特征图:H × W × Cin × 数据类型大小
  • 卷积核权重:K × K × Cin × Cout × 数据类型大小
  • 输出特征图:H × W × Cout × 数据类型大小

还是刚才那个例子,假设用FP16(2字节):

输入访存:1920 × 1080 × 3 × 2 = 12.4 MB
权重访存:3 × 3 × 3 × 64 × 2 = 3.5 KB
输出访存:1920 × 1080 × 64 × 2 = 265 MB

看到了吗?输出特征图的访存量是265MB,而权重只有3.5KB。这就是为什么硬件加速器要设计大容量的片上缓存——不是为了存权重,而是为了存中间特征图。

注意:我曾经在一个项目里忽略了输出特征图的访存优化,结果DDR带宽被占满,帧率死活上不去。后来用了“输出驻留”策略——让部分输出数据留在片上缓存,等所有计算完成后再一次性写回DDR。帧率直接翻倍。

3.2 池化层:降维与特征压缩

池化层在ISP里干的事,就是“压缩”。把一张大图缩小,保留最重要的信息,丢掉不重要的细节。

最常见的两种池化:

  • 最大池化:取窗口内的最大值。保留最显著的特征,适合边缘检测后的特征图。
  • 平均池化:取窗口内的平均值。平滑特征,适合去噪后的特征图。

在AI ISP里,池化层通常放在卷积层后面,用来降低分辨率。比如从1920×1080降到960×540,计算量直接减少75%。

3.2.1 池化的计算量与访存量

池化的计算量比卷积小得多。以2×2最大池化为例,每个输出像素只需要做3次比较操作。对于H×W×C的输入:

比较次数 = (H/2) × (W/2) × C × 3 ≈ H × W × C × 0.75

访存量方面:

  • 输入:H × W × C × 数据类型大小
  • 输出:(H/2) × (W/2) × C × 数据类型大小

输出数据量只有输入的四分之一。这就是池化层的“压缩”效果——不仅计算量小,还能减少后续层的数据搬运量。

我的经验:在硬件实现时,池化层可以和卷积层“融合”在一起。比如在做卷积的同时,把池化的步长考虑进去,直接输出降采样后的结果。这样能省掉一次中间数据的读写。

3.3 激活函数:非线性变换

激活函数的作用,是给神经网络引入非线性。如果没有激活函数,再多层卷积也只是线性变换的堆叠,表达能力有限。

在AI ISP里,最常用的激活函数是ReLU和它的变种:

激活函数 公式 特点 ISP中的应用场景
ReLU max(0, x) 计算简单,梯度不饱和 大多数隐藏层
Leaky ReLU max(αx, x) 避免神经元死亡 低光照增强网络
PReLU max(ax, x),a可学习 自适应负斜率 HDR重建网络
Sigmoid 1/(1+e-x) 输出范围(0,1) 注意力机制、门控

ReLU的计算量几乎可以忽略——就是一个比较和选择。但它的访存量不小,因为要读写整个特征图的数据。

关键点:ReLU的硬件实现非常简单,只需要一个比较器。但要注意,ReLU会把负数全部置零,这会导致特征图变得稀疏。在硬件加速时,可以利用这种稀疏性跳过零值的计算,节省功耗和带宽。

我曾经在一个去噪网络里用了Sigmoid做最后的输出映射,结果硬件实现时发现指数运算太耗资源。后来换成ReLU加一个线性缩放,效果差不多,资源省了80%。

3.4 全连接层:特征聚合与决策

全连接层在ISP里用得不多,但也不是没有。它通常出现在网络的最后几层,用来做全局特征聚合和最终决策。

比如在自动白平衡网络中,输入是经过卷积提取的特征图,最后通过全连接层输出三个通道的增益系数。或者在一些质量评估网络中,全连接层输出一个分数。

3.4.1 全连接的计算量

全连接层的计算量公式很简单:

MACs = N_in × N_out

其中Nin是输入神经元数量,Nout是输出神经元数量。

举个例子:输入是经过全局平均池化后的512维特征向量,输出是3个白平衡增益系数:

MACs = 512 × 3 = 1536

这个计算量相比卷积层来说,简直可以忽略不计。

3.4.2 全连接的访存量

全连接的访存主要是权重矩阵:

权重访存 = N_in × N_out × 数据类型大小

还是刚才的例子,用FP16:

权重访存 = 512 × 3 × 2 = 3 KB

嗯,3KB,小得可怜。但要注意,如果全连接层的输入是展平后的特征图(比如把32×32×64展平成65536维),那权重矩阵就会变得非常大:

权重访存 = 65536 × 256 × 2 = 33.5 MB

这就很恐怖了。所以在AI ISP里,我们通常用全局平均池化代替全连接层,或者把全连接层放在分辨率最低的特征图后面。

避坑指南:我曾经接手过一个项目,算法工程师把全连接层直接接在32×32的特征图上,结果模型大小超过100MB,根本没法在嵌入式芯片上部署。后来我们改成全局平均池化+小全连接层,模型大小降到2MB,精度只掉了0.3%。

3.5 各算子计算量与访存量对比

咱们把四个算子放在一起对比一下,这样你心里有个谱:

算子 计算量(相对) 访存量(相对) 硬件实现难度 ISP中的典型位置
卷积层 极高 高 中 特征提取前端
池化层 低 中 低 卷积层之间
激活函数 极低 中 极低 卷积层之后
全连接层 低(小输入) 低(小输入) 低 网络末端

说白了,卷积层是计算和访存的主力,也是硬件加速的重点。池化和激活函数是“搭便车”的,通常可以和卷积融合实现。全连接层在ISP里用得少,但用的时候要注意输入维度。

3.6 知识结构图:AI ISP算子全景

下面这张图展示了四个算子在AI ISP中的角色和关系:

AI ISP算子全景图 AI ISP算子全景图 RAW/Bayer输入 卷积层(特征提取) 计算量:极高 | 访存量:高 激活函数(非线性) 计算量:极低 | 访存量:中 池化层(降维压缩) 计算量:低 | 访存量:中 重复N次 全连接层(决策输出) 计算量:低 | 访存量:低 ISP处理结果 硬件加速重点 1. 卷积层:并行计算 2. 激活函数:融合实现 3. 池化层:步长控制 4. 全连接:矩阵乘法

这张图把四个算子的流程和关系画清楚了。从RAW输入开始,经过卷积→激活→池化的循环,最后通过全连接层输出结果。每个算子的计算量和访存量特点也标出来了。

3.7 小结:算子移植的核心思路

做算子移植和硬件加速,说白了就是三件事:

  1. 算清楚账:每个算子的计算量和访存量是多少,瓶颈在哪。
  2. 找对策略:计算瓶颈用并行化解决,访存瓶颈用缓存优化解决。
  3. 融合优化:能合并的算子尽量合并,减少中间数据的读写。

我个人习惯,拿到一个AI ISP网络,先跑一遍profile,看看每层的计算量和访存量分布。然后盯着最耗资源的那几层做优化。别一上来就想把所有层都加速,那样反而容易顾此失彼。