Kernel 优化架构分析

Kernel 优化架构分析

推理引擎的 Kernel 层通常是推理引擎中用于执行底层数学运算的组件。在深度学习模型推理过程中,需要对大量数据进行高效的数学运算,如矩阵乘法、卷积、池化等。Kernel 层就是实现这些运算的核心部分,它直接影响着推理引擎的速度和效率。本章将从四个方面对推理引擎的 Kernel 优化方面进行介绍。

在算法优化方面,主要针对卷积算子的计算进行优化。卷积操作是深度学习模型中计算密集且耗时的部分,因此对其进行优化能够显著提升推理性能。其中,对于卷积 kernel 算子的优化主要关注 Im2Col、Winograd 等算法的应用。这些算法通过特定的数学变换和近似,减少了卷积操作的计算复杂度,从而提升了推理速度。其主要方法有:

  1. 空间组合优化算法:将大卷积分解为小卷积,减少内存访问次数,提高缓存利用率。

  2. Im2Col/Col2Im:将输入图像和卷积核转换为列向量的形式,然后使用矩阵乘法来实现卷积,这样可以利用高效的矩阵乘法库。

  3. Winograd 算法:通过预计算和转换,减少卷积中的乘法次数,特别适用于小尺寸的卷积核。

  4. 快速傅里叶变换(FFT):对于大尺寸的卷积核,使用 FFT 将空间域的卷积转换为频域的点乘,提高计算效率。

在内存布局方面,主要的方法有:

  1. 权重和输入数据的重排:重新组织权重和输入数据的内存布局,使得数据访问更加连续,减少缓存缺失。

  2. 通道优先(Channel First/Last):根据硬件特性选择通道数据的存储顺序,例如 NCHW 或 NHWC。

  3. NC1HWC0 与 NCHW4:NC1HWC0 布局通常用于支持 Winograd 算法或其他需要特定通道分组操作的卷积算法。这种布局是针对特定硬件(如某些 AI 加速器)优化的,其中 C 被分割为 C1 和 C0,C1 代表通道的分组数,C0 代表每个分组中的通道数。这种布局可以减少内存访问次数,提高缓存利用率,并可能减少所需的内存带宽。NCHW4 是一种特殊的内存布局,其中 C 被分割为 4 个通道,这种布局通常用于支持 4 通道的 SIMD 操作。NCHW4 布局可以在支持 4 通道向量化指令的硬件上提供更好的性能,例如某些 ARM 处理器。这种布局可以减少数据填充(padding)的需要,并提高数据处理的并行度。

从汇编优化的方面,主要的方法有:

  1. 指令优化:针对特定的 CPU 指令集(如 AVX、AVX2、SSE 等)进行优化,使用向量化指令来提高计算效率。

  2. 循环优化:减少循环的开销,增加指令级的并行性。

  3. 存储优化:优化内存访问模式,使得数据访问更加连续,以提高缓存命中率和内存带宽利用率。

从调度优化的方面,则主要有并行计算和自动调优等方法。根据操作间的依赖关系和执行时间,合理调度任务,减少等待时间和提高资源利用率。

推理引擎的 Kernel 层是整个推理系统的基础,对于实现高性能的深度学习推理至关重要。随着深度学习应用的普及和硬件技术的进步,推理引擎的 Kernel 层也在不断地发展和优化,以适应更加多样化的应用场景和性能要求。

Kernel 层介绍

推理引擎架构

在推理引擎架构中,Runtime 层和 kernel 层是紧密相连的两个组件。Runtime 提供了一个执行环境,它管理整个推理过程,包括模型的加载、预处理、执行和后处理。它还负责资源管理,如内存分配和线程管理。其通常具有平台无关性,可以在不同的操作系统和硬件上运行,为上层应用提供 API 接口,使得用户能够轻松地集成和使用深度学习模型。Kernel 层包含了一系列的低级函数,它们直接在硬件上执行数学运算,如卷积、矩阵乘法和激活函数。其通常是硬件特定的,针对不同的处理器(如 CPU、GPU、TPU 等)有不同的实现。Kernel 层实现时,会进行各种优化,包括算法优化、内存访问优化、向量化、并行化和硬件特定的汇编优化。

从层与层之间集成的角度来说,Runtime 层会根据模型的操作和目标硬件选择合适的 Kernel 层来执行计算。Kernel 层作为 Runtime 层的一部分,被集成到整个推理流程中。而从交互和适配的角度来说,Runtime 层负责调用 Kernel 层提供的函数,传递必要的输入数据,并处理 Kernel 层的输出结果。它可能会提供一些适配层(adapter layer),以便在不同的硬件上运行相同的 Kernel 代码,或者将不同硬件的 Kernel 接口统一化。Runtime 层的优化和 Kernel 层的优化共同决定了整个推理引擎的性能。Runtime 的决策(如算子融合、内存管理等)会影响到 Kernel 层的性能表现。

总结来说,Runtime 提供了一个高层次的抽象,管理模型的执行和资源,而 Kernel 层则是底层的实现,直接在硬件上执行数学运算。Kernel 层的优化主要体现在各种高性能算子和算子库的设计上,这些算子和算子库通常针对不同的处理器架构(如 CPU、GPU、TPU 等)进行了优化,以提高计算速度和效率。

Kernel 层推理架构

下面分别从 CPU 和 GPU 的角度介绍一下几种人工高性能算子和封装的高性能算子库:

CPU 优化:

  1. NEON: NEON 是 ARM 架构上的 SIMD(单指令多数据)扩展,用于提高多媒体处理和浮点运算的性能。推理引擎可以利用 NEON 指令集来优化 Kernel 层,特别是在移动设备和嵌入式设备上。

  2. AVX: AVX(Advanced Vector Extensions)是 Intel 处理器上的 SIMD 指令集,用于提高浮点运算和整数运算的性能。推理引擎可以利用 AVX 指令集来优化 Kernel 层,特别是在 Intel CPU 上。

  3. Metal: Metal 是苹果开发的低级图形和计算 API,用于优化在 Apple GPU 上的性能。推理引擎可以利用 Metal API 来优化 Kernel 层,特别是在 iOS 和 macOS 设备上。

  4. TVM: TVM(Tensor Virtual Machine)是一个开源的深度学习编译器框架,用于优化深度学习模型在各种硬件上的性能。它支持 CPU、GPU、TPU 和其他类型的硬件。

GPU 优化:

  1. CUDA: CUDA 是 NVIDIA 的并行计算平台和编程模型,用于在 NVIDIA GPU 上执行并行计算。推理引擎可以利用 CUDA 来优化 Kernel 层,特别是在大规模矩阵运算和卷积操作方面。

  2. OpenCL: OpenCL 是一个开放的标准,用于编写在异构系统上运行的程序。它允许开发者利用 CPU、GPU 和其他类型的处理器来加速计算密集型任务。推理引擎可以利用 OpenCL 来优化 Kernel 层,特别是在 GPU 上。

  3. Vulkan: Vulkan 是新一代的图形和计算 API,用于在各种 GPU 上执行并行计算。推理引擎可以利用 Vulkan API 来优化 Kernel 层,特别是在高性能计算和图形处理方面。

  4. Tensor Cores: Tensor Cores 是 NVIDIA GPU 上的一种特殊类型的核心,专门用于加速矩阵乘法和卷积操作。推理引擎可以利用 Tensor Cores 来优化 Kernel 层,特别是在执行大规模的矩阵运算时。

此外,封装的高性能算子库有:

  1. cuDNN(CUDA Deep Neural Network Library):由 NVIDIA 开发,为 GPU 优化的深度神经网络算子库,包括卷积、池化、归一化、激活函数等。

  2. MKL-DNN(Intel Math Kernel Library for Deep Neural Networks):由 Intel 开发,为 CPU 优化的深度神经网络算子库,现在发展成为 oneDNN,支持多种 Intel 处理器。

  3. MIOpen:由 AMD 开发,为 GPU 优化的深度学习算子库,特别针对 AMD 的 GPU 架构进行了优化。

  4. TensorRT:NVIDIA 的深度学习推理优化器,它提供了 C++和 Python 接口,可以对模型进行优化并生成高性能的推理引擎。

  5. ONNX Runtime:支持 ONNX 模型的跨平台推理引擎,包括了对多种硬件平台的高性能算子实现。

  6. ACL(ARM Compute Library):由 ARM 开发,为 ARM 架构的 CPU 和 GPU 提供优化的算子库,包括卷积、池化、全连接层等。

这些算子方面的优化方法和技术可以根据具体的硬件平台和模型需求来选择和组合,以提高推理引擎在 Kernel 层的性能。在实际应用中,开发者需要根据目标设备和性能要求来选择最合适的优化策略。

推理流程

推理流程示意图

推理引擎的推理流程是指从加载模型到输出推理结果的一系列步骤。首先加载预先训练好的模型文件,这些文件可能是以特定格式(如 ONNX、TensorFlow SavedModel、PyTorch TorchScript 等)保存的。此外,对模型结构,包括层的类型、参数和拓扑结构进行解析。之后将模型转换或编译为推理引擎能够执行的格式,这其中可能包括优化模型结构、融合某些层以减少计算和内存访问、选择合适的 Kernel 实现等操作。对于支持硬件加速的推理引擎,这一步可能还包括为特定硬件(如 GPU、TPU 等)生成优化的执行代码。在上述离线模块生成的推理模型经过模型压缩(或者不压缩)后送入编译模块再次进行汇编层面的优化,完成优化后就可以真正在线执行推理。

在在线执行阶段,将输入数据(如图像、文本等)转换为模型所需的格式,将预处理后的输入数据复制到设备内存中,为推理做准备。在推理引擎中执行模型,最后将处理后的推理结果返回给用户或下游应用程序。

整个推理流程的目标是高效、准确地执行模型推理,同时尽可能减少计算和延迟。

开发推理程序

开发推理程序示意图

Kernel 层所进行的优化一般蕴含在上图中的⑤进行执行,Runtime 会根据模型的操作和目标硬件选择合适的 Kernel 来执行计算。Kernel 层作为 Runtime 的一部分,被集成到整个推理流程中。其负责调用 Kernel 层提供的函数,传递必要的输入数据,并处理 Kernel 的输出结果。总的来说,Runtime 层的优化和 Kernel 层的优化共同决定了整个推理引擎的性能。Runtime 的决策(如算子融合、内存管理等)会影响到 Kernel 层的性能表现。

卷积的数学原理

在通常形式中,卷积是对两个实变函数的一种数学运算。在泛函分析中,卷积、旋积或褶积 (Convolution) 是通过两个函数 𝑓 和 𝑔 生成第三个函数的一种数学运算,其本质是一种特殊的积分变换,表征函数 𝑓 与 𝑔 经过翻转和平移的重叠部分函数值乘积对重叠长度的积分。

卷积神经网络(Convolution Neural Networks, CNN)的概念拓展自信号处理领域的卷积。信号处理的卷积定义为:

 可以证明,关于几乎所有的实数 x,随着 x 的不同取值,积分定义了一个新函数 ℎ(𝑥),称为函数 𝑓 与 𝑔 的卷积,记为:

 对于信号处理的卷积定义为连续的表示,真正计算的过程中会把连续用离散形式进行计算:

 通俗理解

卷积计算在直觉上不易理解,其可视化后如下图所示。图中红色滑块在移动过程中与蓝色方块的积绘制成的三角图案即为卷积结果在各点上的取值:

更具体来说,解释卷积需要清楚“怎么卷”和”怎么积“两个步骤。

  1. “卷”的过程

根据上述介绍中卷积的定义:

 令

𝑥=𝜏,𝑦=𝑡−𝜏则有 𝑥+𝑦=𝑡 成立。

换言之,在卷积的过程中,函数 𝑓(𝑥) 与 𝑔(𝑦) 中 𝑥,𝑦 取值受线性函数 𝑥+𝑦=𝑡 的约束。若令 𝑡=10,则 𝑥,𝑦 的取值序列有如下图所示的对应关系。直观上 𝑥,𝑦 的取值有一种翻转对应的感觉。

 1.”积“的过程

当 𝑡 的取值变化时,𝑥,𝑦 的取值约束在下图所示的斜率为 -1 的直线簇中。

定义:

 下图(左)中 𝑡 的取值从上到下分别为-1,-0.5,0,0.5,1,令 𝑦=𝑡−𝜏,𝑥=𝜏,左图中的红线为 𝑔(𝑡−𝜏) 的函数图像,蓝线为 𝑓(𝜏) 的函数图像。黄色区域为不同 t 的取值条件下参与卷积的有效区间,黑色直线最右端的点的取值为卷积结果。

 

 总结来说,积分的本质为可理解为求和的极限,卷积中“积”的过程即为相应的函数相乘和求积分的过程。

具体例子

以信号处理的系统响应函数为例(改编自对卷积的定义和意义的通俗解释):

定义输入信号是 𝑓(𝑡) ,随时间变化其数值保持不变。系统响应函数是 𝑔(𝑡),图中的响应函数是随时间指数下降的,它的物理意义是说:如果在 𝑡=𝑡1 的时刻有一个输入,那么随着时间的流逝,这个输入将不断衰减。

由于信号 𝑓(𝑡) 是连续输入的,也就是说,每个时刻都有新的信号进来。同时,该时刻前面的输入对后面仍有影响,该影响以 𝑔(𝑡) 的形式衰减。如下图所示,在 𝑡2 时刻,系统的输入既包括 𝑡2 时刻的新信号,也包括 𝑡1 时刻衰减后的响应(黄色框中的红色虚线点)。所以可以推知,系统最终输出的是所有之前输入信号的累积效果。

 如下图(左)所示,由于信号的响应随时间衰减,我们假定每个时刻的新信号产生的响应的影响范围为 5 个等距时间段 𝑇,即在 𝑡0 产生的新信号的响应影响至 𝑡5 时刻。那么在 𝑇=𝑡5 时刻,输出结果跟图中绿色框的区域整体有关。

 其中,𝑓(𝑡5) 因为是刚输入的,所以其输出结果应该是 𝑓(𝑡5)𝑔(𝑡0),而时刻 𝑡4 的输入 𝑓(𝑡4),只经过了 1 个时间单位的衰减,所以产生的输出应该是 𝑓(𝑡4)𝑔(𝑡1),如此类推,即图中虚线所描述的关系。这些对应点相乘然后累加,就是 𝑇=𝑡5 时刻的输出信号值,这个结果也是 𝑓 和 𝑔 两个函数在 𝑇=𝑡5 时刻的卷积值。

从上图来看 𝑓 和 𝑔 这种对应关系并不直观,因此对 𝑔 关于 𝑦 轴对称变换一下,变为 𝑔(−𝑡𝑖),变换后 𝑓 和 𝑔 的对应关系如下图所示。

 进一步,将 𝑔(−𝑡𝑖) 图像右移 𝑇 个时间单位,得到 𝑔(𝑇−𝑡𝑖):

 这样我们就得到了 𝑓∗𝑔(𝑇)=∫𝑓(𝑡)𝑔(𝑇−𝑡)𝑑𝑡 的直观的图示表达。

一般来讲,当我们用计算机处理数据时,时间会被离散化,传感器会定期地反馈数据。所以在上述例子中,假设传感器每秒反馈一次测量结果是比较现实的。这样,时刻 𝑡𝑖 只能取整数值,如果假设 𝑔 和 𝑓 都定义在整数时刻 𝑡𝑖 上,就可以定义上述条件下离散形式的卷积:

ℎ(𝑇)=(𝑓∗𝑔)(𝑇)=∑𝑖=0𝑖=𝑇𝑓(𝑡𝑖)𝑔(𝑇−𝑡𝑖)

卷积的性质

  1. 交换律:𝑓∗𝑔=𝑔∗𝑓
  1. 结合律:(𝑓∗𝑔)∗ℎ=𝑓∗(𝑔∗ℎ)
  1. 分配律:𝑓∗(𝑔+ℎ)=𝑓∗𝑔+𝑓∗ℎ
  1. 单位响应:存在一个函数 𝛿,使得 𝑓∗𝛿=𝑓,其中 𝛿 是狄拉克 𝛿 函数(连续)或单位脉冲函数(离散)。

卷积物理意义

卷积的物理意义取决于它所应用的具体领域。在不同的领域中,卷积可以代表不同的物理过程。

  1. 信号处理

在信号处理领域,卷积的物理意义通常与系统的响应有关。假设我们有一个输入信号 𝑓(𝑡)(例如,一个音频信号)和一个系统对该信号的响应 𝑔(𝑡)(系统的输出)。系统的响应 𝑔(𝑡) 可以是理想的,也可以是实际测量得到的。

当输入信号 𝑓(𝑡) 通过系统时,系统的输出 ℎ(𝑡) 可以通过卷积 𝑓∗𝑔 来计算。这里的卷积表示系统对输入信号的累积响应。具体来说,ℎ(𝑡) 在任意时刻 𝑡 的值是输入信号 𝑓(𝑡) 与系统响应 𝑔(𝑡) 在过去所有时刻的加权叠加。权重由系统响应 𝑔 决定,反映了系统对不同时刻输入信号的“记忆”。

例如,如果一个系统对输入信号的响应是滞后的,那么输出信号 ℎ(𝑡) 将是输入信号 𝑓(𝑡) 的平滑版本,其中 𝑔(𝑡) 决定了平滑的程度。

 图像处理

在图像处理中,卷积通常用于滤波和特征提取。在这种情况下,图像可以被视为二维信号 𝑓(𝑥,𝑦),其中 𝑥 和 𝑦 分别是图像的水平和垂直坐标。卷积核 𝑔(𝑥,𝑦) 是一个小的权重矩阵,它在图像上滑动,计算每个像素点的加权平均值。

这个过程的物理意义是将图像的每个像素点与其周围的像素点进行比较,通过加权平均来确定这个像素点的新值。卷积核 𝑔 的设计决定了这种比较的方式,例如,边缘检测卷积核会突出显示图像中强度变化明显的区域,从而检测出边缘。

  1. 概率论

在概率论中,两个独立随机变量的联合概率密度函数的卷积给出了它们的和的概率密度函数。这背后的物理意义是,如果你有两个随机过程,它们分别产生随时间变化的概率密度函数,那么这两个过程叠加后的新过程的概率密度函数可以通过卷积原来的两个概率密度函数来得到。

  1. 其他物理系统

在其他物理系统中,卷积可以表示扩散过程、波的传播、材料的混合等。在这些情况下,卷积描述了一个物理量(如温度、压力、浓度等)随时间和空间的分布变化,它是通过系统中各个部分之间的相互作用和传播效应累积而成的。

总的来说,卷积的物理意义是将两个函数(或信号)通过一种特定的方式结合起来,产生一个新的函数,这个新函数在某种意义上代表了这两个函数的相互作用或混合。具体意义取决于应用领域和函数(信号)的物理含义。

离散卷积

从上文信号分析的部分中,我们得到离散形式的卷积公式:

(𝑓∗𝑔)(𝑛)≜∑𝑍𝑛𝑓(𝑚)𝑔(𝑛−𝑚)

离散卷积是指将两个离散序列中的数,按照规则,两两相乘再相加的操作。

离散卷积可以看作矩阵的乘法,然而,这个矩阵的一些元素被限制为必须和另外一些元素相等。比如对于单变量的离散卷积,矩阵每一行中的元素都与上一行对应位置平移一个单位的元素相同。这种矩阵叫做 Toeplitz 矩阵。

将上述离散卷积公式拓展到二维空间即可得到神经网络中的卷积,可简写为:

𝑆(𝑖,𝑗)=(𝐼∗𝐾)(𝑖,𝑗)=∑𝑚∑𝑛𝐼(𝑚,𝑛)𝐾(𝑖−𝑚,𝑗−𝑛)

其中:S 为卷积的输出,I 为卷积输入,K 为卷积核的尺寸。因为卷积是可交换的,我们可等价地写作:

𝑆(𝑖,𝑗)=(𝐾∗𝐼)(𝑖,𝑗)=∑𝑚∑𝑛𝐼(𝑖−𝑚,𝑗−𝑛)𝐾(𝑚,𝑛)

对于二维的情况,卷积对应着一个双重分块循环矩阵。除了元素相等方面的限制之外,卷积通常对应着一个非常稀疏的矩阵。这是因为核的大小通常远小于输入图像的大小。任何一个使用矩阵乘法但是并不依赖于矩阵结构的特殊性质的神经网络算法,都适用于卷积计算,并且不需要对神经网络做出大的修改。

在卷积网络的术语中,卷积的第一个参数 𝑓 通常叫做输入(input),第二个参数 𝑔 叫做核函数(kernel function)。输出有时被称为特征映射(feature map)。在机器学习的应用中,输入通常是多维数组的数据,而核通常是由学习算法优化得到的多维数组的参数。我们把这些多维数组叫做张量。因为在输入与核中的每一个元素都必须明确地分开存储,我们通常假设在存储了数值的有限点集以外,这些函数的值都为 0。这意味着在实际操作中,我们可以通过对有限个数组元素的求和来实现无限求和。

CNN 卷积计算

当我们在神经网络的上下文中讨论卷积时,通常不是特指数学文献中所使用的那种标准的离散卷积运算。当提到神经网络中的卷积时,通常是指由多个并行卷积组成的计算。这是因为,虽然具有单个核的卷积可以作用在多个空间位置上,但它只能提取一种类型的特征。通常,我们希望网络的每一层能够在多个位置提取多种类型的特征。当处理图像时,我们通常把卷积的输入输出看作 3 维的张量 [𝐶,𝑊,𝐻]=[[·]𝑊×𝐻,[·]𝑊×𝐻,[·]𝑊×𝐻],其中一个索引 𝐶 用于表明不同的通道(比如 RGB 三通道 [𝑅𝑚×𝑛,𝐺𝑚×𝑛,𝐵𝑚×𝑛]),另外两个索引 𝑊,𝐻 标明在每个通道的空间坐标。软件实现通常使用批处理模式,所以实际上会使用 4 维的张量 [𝑁,𝐶,𝑊,𝐻],第 4 维索引 𝑁 用于标明批处理中的不同实例。

卷积神经网络主要由卷积层、池化层和全连接层三个部分构成。其中,卷积层是卷积神经网络的核心部分,它通过对输入图像进行卷积操作来提取图像的特征。卷积层的输入通常是一个多通道的(例如多通道图像),每个通道代表一个特征,卷积层的输出也是多通道的,其中每个通道表示一个不同的特征。池化层用于降低特征图的空间分辨率,并增强模型对输入图像的平移不变性和鲁棒性。全连接层通常用于将卷积层和池化层提取的特征进行分类或回归。它的输入是一维向量,其输出的维度与任务的分类数或回归值的维度相同。

如下图所示, 神经网络中的卷积计算过程可描述为:的卷积核在3∗3  的图像上进行滑动,每次滑动时,都把卷积核和对应位置的元素进行相乘再求和。青色区域为其感受野。 

 名词解释

卷积的操作过程中涉及到多个专业名词和不同的计算方式,如、、、等,下面将深入介绍不同名词之间的关系和表达含义。

PaddingStrideChannelKernel

:防止图像边缘信息丢失,在输入图像的周围添加额外的行列,通常用来进行填充。其作用为使卷积后图像分辨率不变,方便计算特征图尺寸的变化,弥补边界。

填充()Padding/“0”

填充背后的数学原理是这样的,如果我们有一个

 的图像,用𝑛×𝑛  的卷积核做卷积,那么输出的大小就是𝑘×𝑘  。这样的话会有两个缺点,第一个缺点是每次做卷积操作,图像就会缩小,在经过多层卷积后,得到的图像就会非常小;第二个缺点是在角落或者边缘区域的像素点在输出中采用较少,卷积核遍历时只经过一次,而其他区域的像素点可能会计算多次。针对以上两个问题,我们采用的方法就是填充。假设填充的行列数为(𝑛−𝑘+1)×(𝑛−𝑘+1)/ ,因为我们在周围都填充了一个像素点,输出也就变成了𝑝 。 (𝑛+2𝑝−𝑘+1)×(𝑛+2𝑝−𝑘+1)

通常有种方法:方式、方式和自定义。

Padding 3 Valid Same Padding:

Valid 方式

意为不填充,这样的话,输入为

Valid  的图像,用𝑛×𝑛  的卷积核卷积,最终得到𝑘×𝑘  的输出。 (𝑛−𝑘+1)×(𝑛−𝑘+1) :

Same 方式

意为填充后输出大小和输入大小一致。根据原始输入计算得到的输出尺寸

Same  ,填充𝑛−𝑘+1 行列后,公式变为𝑝 / 。令𝑛+2𝑝−𝑘+1 ,使得输出和输入大小相等,解得𝑛+2𝑝−𝑘+1=𝑛 。这就产生了两种情况。若𝑝=(𝑘−1)/2 为奇数,可以直接根据公式计算得到𝑘  ;若𝑝 为偶数,则只能进行不对称填充(比如左边填充多一点,右边填充少一点)以保证输出尺寸不变。 𝑘 自定义方式:

自定义意为填充自定义的行列数。定义

/ 为图的宽,𝑤 为图的高,其生成的特征图大小的计算公式为:

 步长是指卷积核在每一次卷积操作中滑动的距离。步长的大小可以影响输出数据的大小,也可以影响特征提取能力和计算复杂度。当步长增大时,输出数据的尺寸会减小,特征提取能力会变弱,但计算速度会加快。

步长()Stride

我们可以把的过程看作对全卷积函数输出的下采样,如果想在输出的每个方向上每隔

Stride  个像素进行采样,或者说在图像中卷积核的滑动距离为𝑠  ,那么这个过程可用公式描述为:

 通道数也称为深度或特征图数量,是指卷积神经网络中每一层输出的特征图数量。通道数的大小直接影响了卷积神经网络的特征提取能力和计算复杂度。通过增加通道数,可以增强卷积神经网络的特征提取能力,但也会增加计算复杂度。

通道数()Channel:是具有可学习参数的算子,用于对输出图像进行特征提取,输出通常为特征图。每一个卷积核代表一种模式特征,有几个卷积核就有几张特征图,一个卷积核都对应一个特征图。在机器学习中,卷积核的参数是由反向传播梯度下降算法计算更新,非人工设置。其特点为:)卷积核每次仅连接区域,是卷积核的尺寸;)卷积核参数重复使用(参数共享),在图像上滑动。

卷积核()Kernel//1 K×K K×K 2:输出特征图的尺寸的计算公式为如下所示:

特征图()Feature Map

其中:为卷积的输出,为卷积输入,为卷积核的尺寸。

朴素卷积过程

为了更好地理解后续的示例,现将卷积神经网络 CNN 的每层的相关参数定义为:

H:图片高度; W:图片宽度; C:原始图片通道数; N:卷积核个数; K:卷积核高宽大小; P:图像边扩充大小; S:滑动步长。

定义 𝑃=0,𝑆=1,以 𝑁=6 个卷积核对一张 𝑊=5,𝐻=5,𝐶=1 的图片进行卷积的过程为例,其经过的步骤为:

  1. 一个卷积核覆盖的 𝐾×𝐾=3×3 的区域,对应位置的数据相乘后相加。
  1. 每个卷积核均对 1 所述区域做乘加操作,并在不同通道(Channel)对应位置相加(本例中每个卷积核的通道数 𝐶=1),得到的结果为特征图上相应位置的数值。

 1,2 步骤的过程如下图所示。

 

  1. 每个卷积核在图像上从左到右,从上到下滑动,依次计算特征图中的每一个像素点。根据特征图大小的计算公式,可知 𝑊𝑓𝑜𝑢𝑡=𝐻𝑓𝑜𝑢𝑡=3;本例中有 𝑁=6 个卷积核,输出 6 张特征图。

     

每滑动一次,计算得到第 𝑙 个特征图上的一个像素点,如上图所示。其滑动的总次数即为特征图的像素点数量。

若有多个通道(假设 𝐶=3),则每个通道的 (𝑖.𝑗) 位置的值对应相加得到最终结果:

 在卷积神经网络的层与层之间进行计算的时候,在 1,2 步骤之后,往往需要加偏置量 𝑏 ,以打破卷积神经网络的平移不变性,增强神经网络的拟合能力:

 

在卷积操作中卷积核是可学习的参数,其参数的更新由梯度下降算法确定。经过上面的介绍,可知每层卷积的参数量为 𝐶×𝐾×𝐾×𝑁。卷积层的参数较少,这也是由卷积层的局部连接共享权重特性所决定。

 局部连接:每个神经元仅与输入神经元的一块区域连接,这块局部区域称作感受野(receptive field)。在图像卷积操作中,即神经元在空间维度(spatial dimension,即在图像平面滑动区域)是局部连接,但在深度(通道方面的计算)上是全部连接。这种局部连接保证了学习后的卷积核能够对于局部的输入特征有最强的响应。

权重共享:计算同一个输出特征图时采用的卷积核是共享的,即一个卷积核在输入图像上滑动,产生一张特征图。该特征图的每个像素点都是由上述卷积核在输入图像上滑动卷积计算产生。这样可以很大程度上减少参数。在卷积层,通常采用多个卷积核提取不同特征,单个卷积核的不同通道之间权重不共享(比如 RGB 有三通道,每个通道的权重参数相互独立)。另外,偏置参数对同一个卷积核的各个参数共享。

 

计算复杂度分析

  1. 时间复杂度

CNN 的时间复杂度主要取决于卷积层的计算量。对于单个卷积层,其时间复杂度可以表示为

其中 𝑓𝑖𝑛 是输入特征图的尺寸,𝐶𝑖𝑛 和 𝐶𝑜𝑢𝑡 分别是输入和输出通道数,𝐾 是卷积核的边长。这个公式表明,时间复杂度由与输入特征图的大小、输入通道数、输出通道数以及卷积核的大小完全确定。

对于整个 CNN,时间复杂度是所有卷积层时间复杂度的累加。因此,减少卷积层的数量或降低每个卷积层的计算量都可以有效降低 CNN 的时间复杂度。例如,可以通过使用更小的卷积核、减少卷积层的通道数或采用更高效的卷积算法来优化 CNN 的性能。

在本章的后续小节会为大家介绍卷积的优化算法。

  1. 空间复杂度

空间复杂度(访存量),严格来讲包括两部分:总参数量 + 各层输出特征图。

  • 参数量:模型所有带参数的层的权重参数总量(即模型体积,下式第一个求和表达式,其中 𝐶𝑙 为本层的输出通道数,对于第 𝑙 个卷积层来说,本层的 𝐶𝑖𝑛 就是上一层的输出通道数 𝐶𝑙−1,𝐷 为总的卷积层数)
  • 特征图:模型在实时运行过程中每层所计算出的输出特征图大小(下式第二个求和表达式)

因此,空间复杂度与总层数、每层输入和输出通道数以及卷积核的大小有关。

优化 CNN 的空间复杂度通常通过减少模型的参数数量来实现。例如,可以采用卷积核分解的方法,将一个较大的卷积核分解为多个较小的卷积核,从而减少模型的参数数量。此外,还可以通过剪枝、量化等方法来进一步降低模型的复杂度。

pytorch 实现

首先定义 padding 模式。

def get_padding(inputs, ks, mode="SAME"):
    """
    Return padding list in different modes.
    params: inputs (input array)
    params: ks (kernel size) [p, q]
    return: padding list [n,m,j,k]
    """
 
    pad = None
    if mode == "FULL":
        pad = [ks[0] - 1, ks[1] - 1, ks[0] - 1, ks[1] - 1]
    elif mode == "VALID":
        pad = [0, 0, 0, 0]
    elif mode == "SAME":
        pad = [(ks[0] - 1) // 2, (ks[1] - 1) // 2,
               (ks[0] - 1) // 2, (ks[1] - 1) // 2]
        if ks[0] % 2 == 0:
            pad[2] += 1
        if ks[1] % 2 == 0:
            pad[3] += 1
    else:
        print("Invalid mode")
    return pad

确定了输入尺寸、卷积核尺寸、Padding 以及 Stride,输出的尺寸就被确定下来。之后利用这些参数计算特征图的大小,并定义卷积。

def conv(inputs, kernel, stride, mode="SAME"):
    # 确定卷积核的尺寸
    ks = kernel.shape[:2]
    
    # get_padding 确定 padding 的模式和数值
    pad = get_padding(inputs, ks, mode="SAME")
    padded_inputs = np.pad(inputs, pad_width=((pad[0], pad[2]), (pad[1], pad[3]), (0, 0)), mode="constant")
          
    # 得到输入图像的尺寸和通道数
    height, width, channels = inputs.shape
    
    # 确定输出特征图的尺寸
    out_width = int((width + pad[0] + pad[2] - ks[0]) / stride + 1)
    out_height = int((height + pad[1] + pad[3] - ks[1]) / stride + 1)
    outputs = np.empty(shape=(out_height, out_width))
    
    # 进行卷积计算    for r, y in enumerate(range(0, padded_inputs.shape[0]-ks[1]+1, stride)):
        for c, x in enumerate(range(0, padded_inputs.shape[1]-ks[0]+1, stride)):
            outputs[r][c] = np.sum(padded_inputs[y:y+ks[1], x:x+ks[0], :] * kernel)
    return outputs

卷积的优化手段

Tensor 运算

张量(Tensor)是标量、矢量、矩阵等概念的总称与拓展,是机器学习领域的基础数据结构。程序中的张量是一个多维数组的数据结构。

#define MAX_DIM 6
struct Tensor {
    // 维度信息
    size_t dim[MAX_DIM];
    uint8_t num_dim;
 
    // 数据信息
    float* data;
    size_t num_data;
};

0 维张量,就是一个数。1 维张量等同于一个向量。2 维张量对应一个矩阵。3 维张量则是一个立方体。

 

张量集到张量集的映射称为张量计算。用编程语言来说,输入是若干张量,输出也是若干个张量,并且无副作用(参考函数式编程)的函数称之为张量计算。

张量有 “维度” 和 “数据” 两个组成要素,张量计算,也就包含维度与数据这两个组成要素的处理。比如矩阵乘法 C = MatMul(A, B),首先是根据输入的两个张量 A, B 确定 C 的维度,然后根据 A 和 B 的数据再去计算 C 的数据。具体一些可参考下面的代码:

Tensor* MatMul(Tensor* A, Tensor* B) {
    Tensor* C = new Tensor;
    // 计算维度
    C->num_dim = 2;
    C->dim[0] = A->dim[0];
    C->dim[1] = B->dim[1];
 
    // 分配内存
    C->data = malloc(C->dim[0]*C->dim[1]*sizeof(float));
 
    // 计算数据
    Matrix::multi(C, A, B);
    return C;
}

Tensor 内存布局

NHWC 和 NCHW 是卷积神经网络(cnn)中广泛使用的数据格式。它们决定了多维数据,如图像、点云或特征图如何存储在内存中。

NHWC(样本数,高度,宽度,通道):这种格式存储数据通道在最后,是 TensorFlow 的默认格式>NCHW(样本数,通道,高度,宽度):通道位于高度和宽度尺寸之前,经常与 PyTorch 一起使用。

Tensor 卷积运算

 当中张量的内存布局为 NHWC 时,卷积计算相应的伪代码如下。其中外三层循环遍历输出 C 的每个数据点,对于每个输出数据都需要经由内三层循环累加求和得到(点积)。

for (int oh = 0; oh < OH; oh++) {
  for (int ow = 0; ow < OW; ow++) {
    for (int oc = 0; oc < OC; oc++) {
    //OH,OW,OC 为输出图像的尺寸和通道数,ow,oh,oc 为输入图像当前像素点的坐标和通道序号
      C[oh][ow][oc] = 0;
      for (int kh = 0; kh < KH, kh++){
        for (int kw = 0; kw < KW, kw++){
          for (int ic = 0; ic < IC, ic++){
          //KW,KH,IC 分别为第 i 个卷积核的尺寸和通道数,卷积核的通道数 IC 应与输入图像的通道数相匹配
            C[oh][ow][oc] += A[oh+kh][ow+kw][ic] * B[kh][kw][ic];
            //表示在输入图像的 ic 通道下 ow,oh 位置开始进行卷积计算,ow+kh 表示在 ow 处右移 kh 个位置
            //+=操作表示不同通道下的乘加结果求和,应“简单卷积过程”小节下步骤 3 的第二个公式
          }
        }
      }
    }
  }
}

和矩阵乘的优化方法类似,我们也可针对该计算进行向量化、并行化、循环展开的基本的优化操作。

小结与思考

本篇介绍了卷积计算的数学原理及其在 CNN 的应用过程。可以观察到,当卷积核在图像上滑动的时候,可训练参数量较少,但产生的连接计算量庞大,且部分参数在不同连接中进行了重复计算,造成了算力资源的浪费。为解决此问题,诸多研究者了各种各样的卷积优化算法,该部分将于后续文章进行详细介绍。

 

参考文献链接
posted @   吴建明wujianming  阅读(134)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2023-06-08 DragonEgg示例分析
2022-06-08 MLIR技术杂谈
2021-06-08 GPU特征处理技术
2020-06-08 TOF摄像机可以替代Flash激光雷达吗?
2020-06-08 毫米波雷达分类和技术方案
2020-06-08 高精地图与自动驾驶(下)
2020-06-08 高精地图与自动驾驶(上)
点击右上角即可分享
微信分享提示