我大部分时间都在考虑如何让神经网络的深度学习更快、更省电。实际上,这意味着关注一个名为GEMM的函数。它是BLAS(基本线性代数子程序)库的一部分,该库最早创建于1979年,在我开始尝试优化神经网络之前,我从未听说过它。为了解释为什么它如此重要,下面是我朋友贾杨青的论文中的一个图表:
这就是使用Alex Krizhevsky的Imagenet架构进行图像识别的典型深度卷积神经网络的时间。所有以fc(即:全连接层)或conv(即:卷积层)开头的层都是使用GEMM实现的,几乎所有的时间(95%的GPU版本,89%的CPU版本)都花在这些层上。
那么什么是GEMM呢?GEMM代表 GEneral Matrix to Matrix Multiplication (通用矩阵到矩阵乘法),本质上它完全按照tin上所说的做,将两个输入矩阵相乘,得到一个输出矩阵。其与3D图形世界中使用的矩阵运算的不同之处在于,它处理的矩阵通常非常大。例如,典型网络中的单个网络层可能需要将256行1152列的矩阵乘以1152行192列的矩阵,以产生256行192列的结果。天真地说,这需要5700万(256x1152x192)次浮点运算,而且在现代网络结构中可能有几十个这样的网络层,所以我经常看到一个往往需要几十亿次浮点运算来计算单个图像帧。下面是我绘制的一张图表,帮助我直观地了解它的工作原理:
全连接层
全连接层是已经存在了几十年的经典神经网络层,从FC层开始说明如何使用GEMM可能是最容易的。FC层的每个输出值都可以看到输入的每个值,将输入乘以该输入对应的权重,然后对结果求和以获得其输出。根据上图,它看起来是这样的:
上图中有“k”个输入值,“n”个神经元,每个神经元都有自己的学习权重集。对应的图中有“n”个输出值,每个神经元对应其中一个,该输出值利用对其权重和输入值进行点积运算计算得到。
卷积层
在卷积层中使用GEMM不是一个显而易见的选择。conv层将其输入视为二维图像,每个像素具有多个通道,非常类似于具有宽度、高度和深度的经典图像。不过,与我以前处理的图像不同,通道的数量可以达到数百个,而不仅仅是RGB或RGBA!
卷积运算通过获取若干“卷积核”的权重来产生其输出。并将其应用于整个图像。以下是输入图像和单个卷积核:
每个卷积核是另一个三维数字数组,深度与输入图像的深度相同,但宽度和高度要小得多,通常是7×7。为了得到结果,卷积运算将卷积核应用于输入图像上的点网格。在其应用的每个点,所有相应的输入值和权重都相乘,然后求和,在该点产生一个输出值。以下是视觉效果:
你可以将这个运算看做边缘检测器。卷积核包含一个权重图案,当它所查看的输入图像部分具有类似的模式时,它会输出一个高值。当输入与模式不匹配时,结果是该位置的数字较低。以下是一些通过神经网络第一层学习到的典型权重图案[1]:
因为神经网络第一层的输入是RGB图像,所以所有这些卷积核也可以可视化为RGB,并且它们显示了网络正在寻找的原始图案。这96个卷积核中的每一个都以网格模式应用于整个卷基层的输入,结果是96个二维数组,它们被视为深度为96个通道的输出图像。如果你习惯了像Sobel算子这样的图像处理操作,你可能可以想象其中的每一个都有点像一个边缘检测器,为图像中不同的重要模式进行了优化,因此每个通道都是这些权重图案在输入中出现的位置的映射。
你可能已经注意到,我对卷积核应用于什么样的网格一直很模糊。关键的控制因素是一个名为“stride”的参数,它定义了应用卷积核之间的间距。例如,当stride为1时,256×256的输入图像将在每个像素上应用卷积核,并且输出的宽度和高度将与输入的宽度和高度相同。如果stride为4,则同一输入图像每四个像素应用一个卷积核,因此输出将仅为64×64。一般来说stride会小于卷积核的大小,这意味着在卷积核可以看到的并应用的图表中,很多stride实际上会在边缘重叠。
GEMM是如何应用于卷积层的?
卷积似乎是一个相当专业的运算。它包含多次乘法计算和最后的求和计算,比如完全连接层,但我们不清楚应该如何或为什么要将其转化为GEMM矩阵乘法。我将在最后讨论将GEMM应用与卷积层的动机,但这里会讨论如何用矩阵乘法来表示卷积运算的。
第一步是将输入图像(实际上是3D数组)转换为2D数组,我们可以将其视为矩阵。应用每个卷积核的地方是图像中的一个小三维立方体,因此我们将每个输入值立方体作为一列复制到矩阵中。这被称为im2col,即:image-to-column(图像到列),我相信它来自一个原始的Matlab函数,以下是将im2col可视化:
现在,如果你像我一样是对图像处理感兴趣的极客,你可能会对进行这种转换时,如果stride小于卷积核大小,所需内存的增加感到震惊。这意味着包含在重叠卷积核中的像素将在矩阵中进行复制,这似乎效率低下。不过,你必须相信我,这种内存使用的浪费会带来计算上的优势。
现在你有了矩阵形式的输入图像,你对每个卷积核的权重做了同样的操作,将3D立方体序列化成行,作为矩阵乘法的第二个矩阵。以下是最终GEMM的样子:
这里的“k”是每个patch和卷积核中的值的个数,所以它是卷积核宽度高度深度。得到的矩阵列高为“patch数”,行宽为“卷积数”。通过后续操作,该矩阵实际上被视为一个3D数组,方法是以核数维度作为深度,然后根据patch在输入图像中的原始位置将patch拆分回行和列。
为什么GEMM可以应用于卷积层?
希望你现在能看到如何使用矩阵计算实现卷积层,但是你为什么要这么做还不清楚。简单的回答是,事实证明,Fortran世界的科学程序员花了几十年时间优化代码,以执行大型的矩阵乘法(large matrix to matrix multiplications),而且非常规则的内存访问模式带来的好处超过了浪费的存储成本。这篇来自Nvidia的论文[2]很好地介绍了您可以使用的一些不同方法,但它们也描述了为什么最终以修改版的GEMM作为他们最喜欢的方法。能够同时对同一个卷积核批处理大量输入图像也有很多优点,Caffe con TROL[3] 使用了这些方法,取得了很好的效果。GEMM方法的主要竞争对手是使用傅里叶变换在频率空间中进行运算,但在卷积中使用stride使其难以达到同样的效率。
好消息是,有一个单一的、易于理解的功能占用了我们大部分时间,这为优化速度和电力使用提供了一条非常清晰的途径,既可以通过更好的软件实现,也可以通过调整硬件来很好地运行操作。由于deep networks已被证明对语音、NLP和计算机视觉的大量应用非常有效,我期待着在未来几年看到大规模的改进,就像对3D游戏的广泛需求推动了GPU的革命,使得vertex和pixel处理运算发生革命一样。
-
Deep Learning for Computer Vision with Caffe and cuDNN https://developer.nvidia.com/blog/deep-learning-computer-vision-caffe-cudnn/ ↩
-
cuDNN: Efficient Primitives for Deep Learning https://arxiv.org/pdf/1410.0759.pdf ↩
-
Caffe con Troll: Shallow Ideas to Speed Up Deep Learning https://arxiv.org/pdf/1504.04343v1.pdf ↩