内容简介:项目地址:作为深度学习领域的初学者,很多人会好奇TensorFlow和PyTorch等深度学习框架是怎样实现高效反向传播算法的。毫无例外,这些系统都使用了CUDA进行并行计算加速,在此我用CUDA实现了一个简单的CNN网络,并在无扭曲的MNIST数据集上实现了99.23%的准确率。这里推荐
项目地址: github.com/hijkzzz/neu…
作为深度学习领域的初学者,很多人会好奇TensorFlow和PyTorch等深度学习框架是怎样实现高效反向传播算法的。毫无例外,这些系统都使用了CUDA进行并行计算加速,在此我用CUDA实现了一个简单的CNN网络,并在无扭曲的MNIST数据集上实现了99.23%的准确率。
预备知识
CUDA
这里推荐 《CUDA编程极简入门教程》
矩阵求导
推荐知乎大神分享的《矩阵求导术》
设计
存储
在实现神经网络之前,我们需要设计一个存储类,用于保存GPU上的参数和数据。这里称为Storage类,为了方便实现,我们直接使用CUDA提供的thrust::device_vector(类似于std::vector)管理显存上的动态数组。并增加一个std::vector保存Storage的形状,可以理解为TensorFlow中Tensor的形状。
矩阵乘法
神经网络的实现大量用到矩阵乘法,所以CUDA并行加速的一个关键在于实现高效的并行矩阵乘法。这里我直接使用了《CUDA编程极简入门教程》中Shared Memory加速的矩阵乘法。实际上还可以继续优化,使效率大大提升。
全连接层
设X为输入数据矩阵,其中每一行为一个样本。W为参数矩阵,b为偏置向量,L为样本平均损失。* 表示矩阵乘法,而非逐元素相乘,^T表示转置:
全连接 前向传播 Y = X * W 反向传播 dL/dX = dL/dY * W^T dL/dW = X^T * dL/dY 偏置 前向传播 Y = X + b 反向传播 dL/db = sum(dL/dY, 0) 逐样本梯度求和 复制代码
卷积层
为了方便用矩阵乘法实现卷积,我参考了Caffe的卷积原理,即im2col:
基本的思想是把卷积运算展成矩阵乘法,所以可以用并行加速的矩阵乘法高效实现卷积。设F为卷积核参数,且形状为: channel_out*channel_in*kernel_width*kernel_height ,X为一个输入样本形状为 channel_in*width*height ,b为偏置向量。
卷积 前向传播 col = im2col(im) 根据im2col展开输入图 Y = F * col 反向传播 dL/dF = dL/dY * col^T dL/d_col = F^T * dL/dY dL/d_im = col2im(dL/d_col) 偏置 前向传播 Y = X + b 逐通道相加 反向传播 dL/db = sum(sum(X, 2), 1) 对整个通道进行规约 复制代码
Maxpool
Maxpool的反向传播需要记录池化前元素的位置,然后把反向梯度直接传回
激活函数
激活函数的前向反向传播都是一样的
ReLU 前向传播 Y = relu(X) 反向传播 dL/dX = relu'(X) element_mul dL/dY 逐元素相乘 其中relu'(x) = 1 if x > 0 else 0 Sigmoid 前向传播 Y = sigmoid(X) 反向传播 dL/dX = sigmoid'(X) element_mul dL/dY 逐元素相乘 其中 sigmoid'(x) = sigmoid(x) * (1 - sigmoid(x)) 复制代码
Softmax
在工程实现上:为了防止Softmax的分母溢出,一般使用LogSoftmax代替。设定1_n为全为1的列向量
Logsoftmax 正向传播 Y = log_softmax(X) = x - log(exp(X) * 1_n) * 1_n^T 由前言中矩阵求导的方法可得 反向传播 dL/dX = dL/dY - (dL/dY * 1_n * exp(x)) / (exp(x) * 1_n) 复制代码
NLLLoss
NLLLoss是平均负的对数似然损失,为了配合LogSoftmax使用而实现。设Y为样本标签矩阵,每一行为一个样本。N为样本数量
前向传播 L = mean(sum(-log_P element_mul Y, 1), 0) 反向传播 用矩阵乘法,L可表示为 L = 1_n^T * ((-log_P element_mul Y) * 1_k) / N 由矩阵求导术可得 dL/d(log_P) = -Y / N NLLLoss+LogSoftmax为我们常见的Softmax损失 将dL/d(log_P)带入LogSoftmax梯度中可得softmax损失的梯度: softmax(X) - Y 复制代码
RMSProp
为了实现单独的优化器,我们需要在反向传播的时候把梯度保存下来,然后用RMSProp算法进行统一的滑动平均计算新梯度。同理可以很方便的实现Adam等优化器。
实现
源码结构
src
cuda CUDA源码
minist MNIST DEMO
test
cuda CUDA源码单元测试
CMakeLists.txt CMake编译脚本
复制代码
由于篇幅有限,所以这里只能去看GitHub上的实际代码。每个层都封装为了一个类,并且可调用connect函数连接层与层。
Debug/调优
可以通过CUDA提供的Visual Profiler可以很方便的看出程序的性能瓶颈。
在我的实验中发现80%的执行时间都在等待显卡I/O,所以通过Pinned Memory以及合并传输/内存分配等方式使运行效率提升了数十倍。 其次是矩阵乘法还有较大的优化空间,不过总的来说在GTX1070上数十秒便可以跑完MNIST的6W个样本,基本实现了我的目标。
编程一天,调试两天,Debug是开发的一个困难而且重要的环节,掌握适当的 工具 的方法将事半功倍。CUDA提供的Nsight、cuda-memcheck都是很好的工具。当然printf+注释大法也是屡试不爽。
测试
网络结构 conv 1 32 5 relu maxpool 2 conv 32 64 5 relu maxpool 2 conv 64 128 3 relu fc 4 * 128 128 relu fc 128 10 relu softmax nllloss 调参 shuffle = true batch_size = 128 learning_rate = 0.003 L2 = 0.0001 beta = 0.99 准确率 1 epoch 93% 10 epochs 99.12% 30 epochs 99.23% 10s / epoch(GTX1070) 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 自己动手实现一个Promise
- LeetCode146 动手实现LRU算法
- 自己动手实现神经网络分词模型
- 自己动手实现一个简单的React
- 动手造轮子:实现简单的 EventQueue
- 动手造轮子:基于 Redis 实现 EventBus
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Everything Store
Brad Stone / Little, Brown and Company / 2013-10-22 / USD 28.00
The definitive story of Amazon.com, one of the most successful companies in the world, and of its driven, brilliant founder, Jeff Bezos. Amazon.com started off delivering books through the mail. Bu......一起来看看 《The Everything Store》 这本书的介绍吧!