【自制C++深度学习推理框架】卷积层的设计思路

卷积层的设计思路

使用Im2Col来实现高性能卷积

在深度学习中实现高性能卷积有以下几个方法:

  • 并行计算:在网络或硬件层面上,利用并行计算的优势对卷积过程进行加速,例如使用GPU。

  • 转换卷积算法:卷积操作可由矩阵相乘代替,使用Im2Col来实现高性能卷积。

  • 减少内存带宽:通过修改计算顺序、优化缓存管理或手动调整内存布局等方法来减少内存访问量,从而提高卷积性能。

  • 降低精度:使用低精度数据类型(如半精度浮点数)进行计算可以提高卷积性能,因为这些数据类型在内存和计算方面都需要较少的资源。

Im2Col(图像到列)是一种将卷积层输入的多维图像数据转换为二维列状数据的算法。该算法被广泛应用于深度学习框架中,以加速卷积计算,提高图像处理的效率和准确性。下面是 Im2Col 算法的基本原理:

  1. 输入数据:一个形状为 [ N , C , H , W ] [N, C, H, W] [N,C,H,W] 的四维张量,其中 N 表示样本数,C 表示通道数,H 和 W 分别表示图像高度和宽度。

  2. 设定卷积核大小和步幅:卷积层通常包含若干个卷积核,每个卷积核大小为 K h ∗ K w K_h * K_w KhKw,步幅为 S h ∗ S w S_h * S_w ShSw

  3. 对输入图像进行填充:在输入图像的周围填充 P h P_h Ph 行和 P w P_w Pw 列的零值,使得输出特征图大小为 [ ( H − K h + 2 ∗ P h ) / S h + 1 , ( W − K w + 2 ∗ P w ) / S w + 1 ] [(H - K_h + 2*P_h)/S_h+1, (W - K_w + 2*P_w)/S_w+1] [(HKh+2Ph)/Sh+1,(WKw+2Pw)/Sw+1]

  4. 将卷积核拉成一维向量:将每个卷积核从形状为 [ K h , K w , C ] [K_h, K_w, C] [Kh,Kw,C] 的三维张量转换为形状为 [ K h ∗ K w ∗ C ] [K_h * K_w * C] [KhKwC] 的一维向量。

  5. Im2Col 转换:将输入图像的每个大小为 K h ∗ K w K_h * K_w KhKw 的图块按行将像素展开成一列,生成一个形状为 [ K h ∗ K w ∗ C , H o u t ∗ W o u t ] [K_h * K_w * C, H_{out} * W_{out}] [KhKwC,HoutWout] 的二维张量。

  6. 矩阵乘法计算:将一维卷积核向量与 Im2Col 转换得到的列向量进行矩阵乘法运算,得到一个形状为 [ N ∗ n u m f i l t e r s , H o u t ∗ W o u t ] [N * num_filters, H_{out} * W_{out}] [Nnumfilters,HoutWout] 的矩阵,其中 num_filters 表示卷积核数量。

  7. 将结果转换为输出特征图:将结果矩阵重组成形状 [ N , n u m f i l t e r s , H o u t , W o u t ] [N, num_filters, H_{out}, W_{out}] [N,numfilters,Hout,Wout] 的四维张量,即为卷积层输出的特征图。

Im2Col

总之,Im2Col 算法是通过将输入图像转换为列状数据,并利用矩阵乘法计算来提高卷积运算效率和准确性。在实际应用中,使用 Im2Col 算法可以加速卷积操作,提升深度学习模型的训练和推理速度。

详细可以查看:

ConvolutionLayer的定义

由于ConvolutionLayer是带参数的,因此可通过继承ParamLayer类,定义ConvolutionLayer类来实现卷积层。具体来说:

  • 构造函数:定义了卷积层的一些基本参数,包括输出通道数 output_channel、输入通道数 in_channel、卷积核大小 kernel_h 和 kernel_w、padding_h 和 padding_w、stride_h 和 stride_w、group数 groups 和是否使用偏置 use_bias。通过该函数初始化了类成员变量。

  • 静态函数 GetInstance:给定运行时操作符 op,该函数返回一个 conv_layer 实例的指针。在卷积层中,该函数主要解析和验证输入张量的形状和参数,以及计算输出张量的形状。

  • 前向传播函数 Forward:根据输入张量 inputs 和已经初始化的卷积层参数,执行卷积操作,并将结果写入输出张量 outputs 中。具体实现涉及到 Im2Col 转换、矩阵乘法等计算过程。

此外,该类定义了一些私有成员变量,包括是否使用偏置 use_bias_、groups 数量 groups_、padding 和 stride 的高度和宽度等参数。这些变量在构造函数中进行初始化,并在前向传播函数中使用。

ConvolutionLayer的实例化

通过LayerRegistererWrapper kConvGetInstance("nn.Conv2d", ConvolutionLayer::GetInstance);来定义

一个 LayerRegistererWrapper 类型的变量 kConvGetInstance,用来注册一个名为 “nn.Conv2d” 的卷积层的 GetInstance 函数 ConvolutionLayer::GetInstance。

这样在使用该卷积层时只需要指定名称 “nn.Conv2d”,就可以自动调用对应的 GetInstance 函数了。

卷积层的 GetInstance 函数,用于对传入的运行时操作符 op 进行解析和验证,并返回一个卷积层实例的指针。具体来说:

  • 首先检查输入参数 op 是否为空,如果为空则报错并返回对应的错误码。

  • 从 op 中获取卷积层的 dilation(膨胀率)、in_channels(输入通道数)和 out_channels(输出通道数)等参数,进行解析和验证。如果这些参数不存在或格式不正确,则报错并返回对应的错误码。

  • 检查卷积核的 dilation 值是否为 1,因为目前只支持 dilation 值为 1。

  • 对于 padding 参数,首先检查该参数是否存在,不存在则报错并返回 kParameterMissingPadding 的错误码;接着尝试从运行时参数 op 中获取 padding 值,如果获取不到或者格式不正确,则同样报错返回对应的错误码。

  • 对于 bias、stride、kernel_size 参数,也采用类似的方法进行判断和提取,如果某个参数不存在或格式有误,则报错返回相应的错误码。

  • 如果 padding_mode 参数存在,则检查该参数是否满足规定的格式(字符串类型),如果格式有误,则报错返回相应的错误码。

  • 对 groups 参数进行检查,如果该参数不存在或者其值无效,则报错并返回相应的错误码。

  • 接着根据 kernel、padding、stride 参数的值,构造一个 ConvolutionLayer 类的实例 conv_layer,并对其参数进行相应的初始化。

  • 如果使用偏置,则从运行时参数 op 中获取 bias 属性的值,并进行一些检查和处理,以确保该属性的形状和值符合要求。如果 bias 属性不存在或数据有误,则报错并返回相应的错误码。

  • 最后如果存在权重属性 weight,则从运行时参数 op 中获取该属性的值,检查其形状是否正确。如果 weight 数据有误,则报错并返回相应的错误码。否则,将 weight 值设置给卷积层实例(即 conv_layer),并返回 kParameterAttrParseSuccess 表示解析和构造过程成功完成。

ConvolutionLayer的前向传播

在前向传播中,我们不仅使用Im2Col来实现高性能卷积,还使用了 Armadillo 库来进行矩阵乘法,也使用了OpenMP library线程并行处理。具体如下:

  • 首先进行参数和数据的检查,确保输入输出数据正确且合法。

  • 接着从权重参数 weights_ 中提取卷积核的数量、大小、通道数等相关信息,并对一些参数进行检查和校验。

  • 对 groups_ 参数进行一些校验和检查,确保卷积核数量和输入通道数是组数的整数倍。

  • 接着根据输出特征图的大小(即 output_h 和 output_w 值)计算输入矩阵的列长 col_len,并对其进行校验。

  • 然后,根据Im2Col原理,对每个分组内的卷积核参数提取并预处理成矩阵形式kernel_matrix_arr。

  • 接着,根据Im2Col原理,对每个分组内的输入数据input提前并预处理成矩阵形式input_matrix。

  • 获取或创建输出特征图的 output_tensor ,如果该 output_tensor 不存在,则创建一个新的空 tensor,并更新输出数组 output_tensor。

  • 接着对 output_tensor 的形状进行检查和校验,确保其形状与预期的输出特征图大小相符合。

  • 然后针对每个分组执行以下:将 kernel_matrix_arr 与输入变量 input_matrix 进行矩阵乘法操作得到中间结果 outputs_matrix。

  • 对于每个分组,改变张量的形状,根据是否使用偏置进行指定 bias 值的计算,最终将输出结果保存在 output_tensor 中的相应通道。

  • 最后返回成功标志 InferStatus::kInferSuccess,结束前向传播计算过程。

此外,在对每个batch_size进行卷积时利用 OpenMP 循环并行处理。其中:

  • #pragma omp 表示这是 OpenMP 库中的预处理指令。

  • parallel for 表示对循环进行并行处理,即多个线程之间分配迭代次数并并行执行循环内部的逻辑。

  • num_threads(batch_size) 指定了同时运行的线程数目,这里将其设置为输入数据的 batch_size。

  • for 循环将输入数据拆分成多个子任务(每个任务处理一个 batch 中的一组数据)。在执行时,不同的线程会负责处理不同的子任务,从而实现并行计算。

这样可以加速并行处理,使得程序可以充分利用系统资源,提高计算效率。

阅读的代码

  • source
    • layer
      • details
        • convolution.hpp
        • convolution.cpp
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值