使用CuDNN进行卷积运算

CuDNN

NVIDIA ® cuDNN is a GPU-accelerated library of primitives for deep neural networks. It provides highly tuned implementations of routines arising frequently in DNN applications:

  • Convolution forward and backward, including cross-correlation
  • Pooling forward and backward
  • Softmax forward and backward
  • Neuron activations forward and backward:
    • Rectified linear (ReLU)
    • Sigmoid
    • Hyperbolic tangent (TANH)
  • Tensor transformation functions
  • LRN, LCN and batch normalization forward and backward

ENV

  • Ubuntu 16.04
  • CUDA 9.0
  • CuDNN 7.0
  • GCC 5.4.0
  • Alchemy / OpenCV
  • CLion 2018.1 RC2

cudnnConvolutionForward()

CuDNN中计算卷积操作的由cudnnConvolutionForward()来实现,其原型为

cudnnStatus_t CUDNNWINAPI cudnnConvolutionForward(
                                cudnnHandle_t                       handle,
                                const void                         *alpha,
                                const cudnnTensorDescriptor_t       xDesc,
                                const void                         *x,
                                const cudnnFilterDescriptor_t       wDesc,
                                const void                         *w,
                                const cudnnConvolutionDescriptor_t  convDesc,
                                cudnnConvolutionFwdAlgo_t           algo,
                                void                               *workSpace,
                                size_t                              workSpaceSizeInBytes,
                                const void                         *beta,
                                const cudnnTensorDescriptor_t       yDesc,
                                void                               *y );

其中:

  • x为输入数据的地址,w为卷积核的地址,y为输出数据的地址,对应的xDescwDescyDesc为描述这三个数据的描述子,比如记录了数据的batch size、channels、height和width等。
  • alpha对卷积结果x*w进行缩放,beta对输出y进行缩放,其表达式为:
dstValue = alpha[0]*computedValue + beta[0]*priorDstValue
  • workspace是指向进行卷积操作时需要的GPU空间的指针
  • workSpaceSizeInBytes为该空间的大小
  • algo用来指定使用什么算法来进行卷积运算
  • handle是创建的library context的句柄,使用CuDNN库必须用cudaCreate()来初始化。

实例

对图片进行拉普拉斯变换。

1. 准备图像

    auto image = imread("red.png");
    auto image_float = Matrix32f(image);

2. 准备使用CUDNN的环境

    //handle
    cudnnHandle_t handle;
    cudnnCreate(&handle);

3. 准备输入数据

    // input
    Tensor<float> input({ 1, image.channels(), image.rows, image.cols });
    Memory::copy(image_float.count() * sizeof(float), input.gptr(), image_float.ptr());

    cudnnTensorDescriptor_t input_descriptor;
    cudnnCreateTensorDescriptor(&input_descriptor);
    cudnnSetTensor4dDescriptor(input_descriptor,
                               CUDNN_TENSOR_NHWC,
                               CUDNN_DATA_FLOAT,
                               input.shape(0), input.shape(1), input.shape(2), input.shape(3));

input_descriptor保存了输入的信息,其中

  • CUDNN_TENSOR_NHWC是数据数据的结构,这表示,input为一个四维的张量,四个维度分别为
    • N: Number这里作为输入的图片个数,我们例子中只有一张图片,为1
    • H: Height,即图片的高
    • W: Width,即图片的宽
    • C: Channels,即通道数,这里是图片的通道数,如果输入为RGB三通道则为3,灰度图则为1,依次类推
  • CUDNN_DATA_FLOAT为计算的数据类型

4. 准备输出数据

    // output
    Tensor<float> output(input.shape());
    vector_set_gpu(output.count(), 0.0f, output.gptr());

    cudnnTensorDescriptor_t output_descriptor;
    cudnnCreateTensorDescriptor(&output_descriptor);
    cudnnSetTensor4dDescriptor(output_descriptor,
                               CUDNN_TENSOR_NHWC,
                               CUDNN_DATA_FLOAT,
                               output.shape(0), output.shape(1), output.shape(2), output.shape(3));

5. 准备卷积核

// kernel
    Tensor<float> kernel({ output.shape(1), input.shape(1), 3, 3 });
    auto kernel_size = kernel.count(2, 4);
    float kernel_[kernel_size] = { 0, 1, 0, 1, -4, 1, 0, 1, 0 };
    for(auto i = 0; i < kernel.count(0, 2); ++i) {
        memcpy(kernel.cptr() + i * kernel_size, kernel_, kernel_size * sizeof(float));
    }

    cudnnFilterDescriptor_t kernel_descriptor;
    cudnnCreateFilterDescriptor(&kernel_descriptor);
    cudnnSetFilter4dDescriptor(kernel_descriptor,
                               CUDNN_DATA_FLOAT,
                               CUDNN_TENSOR_NCHW,
                               kernel.shape(0), kernel.shape(1), kernel.shape(2), kernel.shape(3));

6. 卷积的描述子

    // convolution descriptor
    cudnnConvolutionDescriptor_t conv_descriptor;
    cudnnCreateConvolutionDescriptor(&conv_descriptor);
    cudnnSetConvolution2dDescriptor(conv_descriptor,
                                    1, 1, // zero-padding
                                    1, 1, // stride
                                    1, 1,
                                    CUDNN_CROSS_CORRELATION, CUDNN_DATA_FLOAT);

7. 选择算法

    // algorithm
    cudnnConvolutionFwdAlgo_t algo;
    cudnnGetConvolutionForwardAlgorithm(handle,
                                        input_descriptor,
                                        kernel_descriptor,
                                        conv_descriptor,
                                        output_descriptor,
                                        CUDNN_CONVOLUTION_FWD_PREFER_FASTEST,
                                        0,
                                        &algo);

告诉CuDNN优先使用计算速度快的算法,并且没有内存限制

8. 准备计算所用的空间

    // workspace size && allocate memory
    size_t workspace_size = 0;
    cudnnGetConvolutionForwardWorkspaceSize(handle,
                                            input_descriptor,
                                            kernel_descriptor,
                                            conv_descriptor,
                                            output_descriptor,
                                            algo,
                                            &workspace_size);

    void * workspace = nullptr;
    cudaMalloc(&workspace, workspace_size);

9. Convolution

    // convolution
    auto alpha = 1.0f, beta = 0.0f;
    cudnnConvolutionForward(handle,
                            &alpha, input_descriptor, input.gptr(),
                            kernel_descriptor, kernel.gptr(),
                            conv_descriptor, algo,
                            workspace, workspace_size,
                            &beta, output_descriptor, output.gptr());

10. 销毁

    // destroy
    cudaFree(workspace);

    cudnnDestroyTensorDescriptor(input_descriptor);
    cudnnDestroyTensorDescriptor(output_descriptor);
    cudnnDestroyConvolutionDescriptor(conv_descriptor);
    cudnnDestroyFilterDescriptor(kernel_descriptor);

    cudnnDestroy(handle);

效果

这里写图片描述

源代码

#include <cudnn_v7.h>
#include "alchemy.h"

using namespace std;
using namespace alchemy;

int main()
{
    // image
    auto image = imread("red.png");
    auto image_float = Matrix32f(image);

    //handle
    cudnnHandle_t handle;
    cudnnCreate(&handle);

    // input
    Tensor<float> input({ 1, image.channels(), image.rows, image.cols });
    Memory::copy(image_float.count() * sizeof(float), input.gptr(), image_float.ptr());

    cudnnTensorDescriptor_t input_descriptor;
    cudnnCreateTensorDescriptor(&input_descriptor);
    cudnnSetTensor4dDescriptor(input_descriptor,
                               CUDNN_TENSOR_NHWC,
                               CUDNN_DATA_FLOAT,
                               input.shape(0), input.shape(1), input.shape(2), input.shape(3));

    // output
    Tensor<float> output(input.shape());
    vector_set_gpu(output.count(), 0.0f, output.gptr());

    cudnnTensorDescriptor_t output_descriptor;
    cudnnCreateTensorDescriptor(&output_descriptor);
    cudnnSetTensor4dDescriptor(output_descriptor,
                               CUDNN_TENSOR_NHWC,
                               CUDNN_DATA_FLOAT,
                               output.shape(0), output.shape(1), output.shape(2), output.shape(3));

    // kernel
    Tensor<float> kernel({ output.shape(1), input.shape(1), 3, 3 });
    auto kernel_size = kernel.count(2, 4);
    float kernel_[kernel_size] = { 0, 1, 0, 1, -4, 1, 0, 1, 0 };
    for(auto i = 0; i < kernel.count(0, 2); ++i) {
        memcpy(kernel.cptr() + i * kernel_size, kernel_, kernel_size * sizeof(float));
    }

    cudnnFilterDescriptor_t kernel_descriptor;
    cudnnCreateFilterDescriptor(&kernel_descriptor);
    cudnnSetFilter4dDescriptor(kernel_descriptor,
                               CUDNN_DATA_FLOAT,
                               CUDNN_TENSOR_NCHW,
                               kernel.shape(0), kernel.shape(1), kernel.shape(2), kernel.shape(3));
    // convolution descriptor
    cudnnConvolutionDescriptor_t conv_descriptor;
    cudnnCreateConvolutionDescriptor(&conv_descriptor);
    cudnnSetConvolution2dDescriptor(conv_descriptor,
                                    1, 1, // zero-padding
                                    1, 1, // stride
                                    1, 1,
                                    CUDNN_CROSS_CORRELATION, CUDNN_DATA_FLOAT);

    // algorithm
    cudnnConvolutionFwdAlgo_t algo;
    cudnnGetConvolutionForwardAlgorithm(handle,
                                        input_descriptor,
                                        kernel_descriptor,
                                        conv_descriptor,
                                        output_descriptor,
                                        CUDNN_CONVOLUTION_FWD_PREFER_FASTEST,
                                        0,
                                        &algo);

    // workspace size && allocate memory
    size_t workspace_size = 0;
    cudnnGetConvolutionForwardWorkspaceSize(handle,
                                            input_descriptor,
                                            kernel_descriptor,
                                            conv_descriptor,
                                            output_descriptor,
                                            algo,
                                            &workspace_size);

    void * workspace = nullptr;
    cudaMalloc(&workspace, workspace_size);

    // convolution
    auto alpha = 1.0f, beta = 0.0f;
    cudnnConvolutionForward(handle,
                            &alpha, input_descriptor, input.gptr(),
                            kernel_descriptor, kernel.gptr(),
                            conv_descriptor, algo,
                            workspace, workspace_size,
                            &beta, output_descriptor, output.gptr());

    Matrix32f output_image(image.shape());
    cudaMemcpy(output_image.ptr(), output.gptr(), image.count() * sizeof(float), cudaMemcpyDeviceToHost);

    // destroy
    cudaFree(workspace);

    cudnnDestroyTensorDescriptor(input_descriptor);
    cudnnDestroyTensorDescriptor(output_descriptor);
    cudnnDestroyConvolutionDescriptor(conv_descriptor);
    cudnnDestroyFilterDescriptor(kernel_descriptor);

    cudnnDestroy(handle);


    // show
    imshow("original", image);
    imshow("output", Matrix(output_image/3.0));

    waitKey(0);
    return 0;
}
  • 12
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
### 回答1: CUDNN是一个用于深度学习的加速库,主要用于优化在CUDA架构上进行的深度学习任务。CUDNN提供了针对深度神经网络的各种基本操作的高性能实现,例如卷积、规范化、池化等。通过使用CUDNN,可以显著提高目标检测算法的训练和推理速度。 编写目标检测算法的步骤如下: 1.首先,需要定义目标检测的问题,确定需要检测的目标类别和标注数据集。 2.接下来,需要构建一个深度神经网络模型,用于图像的特征提取和目标检测。可以选择已经训练好的网络模型作为基础,例如VGG、ResNet等,也可以按照自己的需求设计模型。 3.将目标检测问题转化为一个监督学习问题。通常采用的方法是将目标检测问题转化为一个二分类问题,通过训练一个二分类模型来判断图像中是否存在目标。 4.使用CUDNN提供的高性能深度学习函数,通过构建网络结构和定义相应的操作,对目标检测算法进行优化。可以利用CUDNN中提供的卷积操作进行图像的特征提取,利用池化操作进行特征降维,利用规范化操作进行特征归一化等。 5.对目标检测算法进行训练和优化。通过使用CUDNN提供的高性能计算能力,可以加速深度学习模型的训练过程,减少模型的收敛时间。 6.最后,对目标检测算法进行测试和评估。利用测试集对目标检测算法进行验证,计算准确率、召回率等指标,评估算法的性能和效果。 总之,通过使用CUDNN库中提供的高性能深度学习函数,可以大大加速目标检测算法的训练和推理过程,提高算法的性能和效果。在编写目标检测算法时,合理地利用CUDNN的各种函数和操作,可以提高算法的运行效率,并在图像目标检测领域取得更好的结果。 ### 回答2: CUDNN是一个用于深度学习的加速库,可以优化卷积神经网络(CNN)的运算速度,从而提高目标检测算法的效率。在使用CUDNN进行编写目标检测算法时,可以按照以下步骤进行: 首先,需要使用CUDNN提供的函数来初始化CUDNN库,设置相关的参数和配置,例如设备号、数据类型等。 其次,需要定义卷积神经网络的结构。可以使用CUDNN提供的函数来创建输入、输出、卷积、池化等层的描述符,并设置相应的参数。这些描述符将用于配置CUDNN进行卷积和池化操作。 然后,需要定义卷积核的权重和偏置,并将其分配到GPU的显存中。可以使用CUDNN提供的函数来进行内存分配和初始化。 接下来,可以使用CUDNN提供的函数来进行前向传播和反向传播的计算。前向传播即将输入数据送入卷积神经网络,通过卷积和池化操作,计算出最后的输出。反向传播则是根据损失函数计算梯度,并进行梯度下降更新权重和偏置。 最后,进行模型训练和测试。可以使用CUDNN提供的函数来进行批量归一化、激活函数的计算等操作,以优化模型的训练效果。同时,可以使用CUDNN提供的函数来评估模型的准确率和精度。 总之,通过使用CUDNN库,可以在GPU上加速目标检测算法的运算速度,从而提高算法的效率和实时性。但在编写目标检测算法时,还需要考虑诸多因素,如网络结构的设计、数据集的选择和预处理等,以达到更好的检测效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值