卷积层(2)
这一回我们来看看卷积层的解法。我们将采用两种方法求解:
-
一种是实力派解法
-
一种是软件库中常用的套路——“整容”后的偶像派解法。
这里需要一个小例子,我们假定一个1*5*5的输入,卷积层的维度是1*1*3*3,同时stride=1,padding=0。最终的输出是1*3*3。
这里先画个详细的图,图像中对其中的变量做了定义:
其中:
X表示输入的矩阵,我们用一维的0-24表示下标。
K表示卷积核的矩阵,我们用一维的0-8表示下标。
Z表示卷积的结果,我们用一维的0-8表示下标。
由于前面我们提到操作中stride=1,padding=0,所以我们可以给出简单版和复杂版的输出维度计算公式:
首先是简单版:
然后是复杂版:
很显然,当stride=1,padding=0时,两个公式是等价的。
1
实力派解法
所谓的实力派解法就是用卷积定义(这里就用相关操作)去做前向计算,然后利用前向的算法去求反向。
接下来这张图上详细介绍了输出的每一个数值是利用哪一部分的信息计算出来的:
上面这张图上详细地讲述了每一个像素的前向计算公式,同时在计算的过程中,我们将每一个输出结果对应的输入和kernel的信息作了标记。实际上这和上一回我们说的算法是一样的。
刚才我们看了前向传播,下面我们看看反向传播的计算。反向传播中的偏置项bias这里就不说了,和全连接的反向算法一样,相对简单些。另外我们需要计算两个数值:
-
给下一层传导的loss
-
本层卷积核参数的导数。
首先是下一层参数的loss,也可以理解为Loss对输入数据的梯度。从卷积的过程中直接观察是比较困难的,我们需要讲观看的视角做一下转换。想要求出每个输入的导数,就需要列出每个输入元素参与的计算,这样我们用前面得到的Loss和与这个元素相乘的参数进行相乘再相加就可以得到想要的结果了。这个思路和全连接求导的思路是一致的,只是变换的过程需要费点脑筋:
为了更加清晰地展示算法,我画了下面这张图:
从这张图可以清楚地看出输入数据和输出loss的关系。右边的图主体上是由5*5的小图组成,每一个小图就相当于输入所在位置的数据参与卷积计算的过程。可以看出,每个输入数据在卷积过程的参与度是不同的。对于我们这个问题,最终的输出是9个数字,每个数字由大小为9的卷积计算得到,也就是说我们一共进行了81次乘加操作,我们用矩阵的形式把这个参与次数再写出来:
1 2 3 2 1
2 4 6 4 2
3 6 9 6 3
2 4 6 4 2
1 2 3 2 1
相加后发现确实是81。数字的数量是相符的。
然后我们还可以从延展的虚线中看出,如果将上面小图中的两部分标出的位置进行乘加操作后就可以得出输入所在点的梯度了,而且另外一个发现就是——把所有输入点的计算组合起来,是输出数据的梯度和卷积参数核的卷积操作,但是其中有2处不同:
-
卷积核需要旋转180度
-
输出数据的梯度所在的矩阵需要在周围做padding,padding的数量等于卷积核的维度减1
所以这一步的求解最终可以转化成这个伪代码:
residual_x=conv2(padding(residual_z,kernel.shape()-1), rot180(kernel))
通过比较复杂的分析,我们最终得到了一个十分简洁的计算方法。
看完了给下一层传导的loss,下面是本层的参数导数。和之前类似,我们需要重新整理运算关系。同样地,我们再来一张图进行解释:
从这张图又可以很清楚地看出这个关系。这一次我们直接把输入数据和输出数据的残差做卷积,就可以得到参数w的导数。比上面的计算还要简单点。这里的求解也只需要一步就可以:
residual_w=conv2(x,residual_z)
实际上到此为止,卷积层的实力解法就到此结束了,实际上它与全连接层最大的不同就是线性部分,关于非线性部分的事情我们后面再说,但是由于线性部分和非线性部分相互独立,因此分来分析也是完全没有问题的。
所以从上面的分析中可以看出,想要采用实力派的解法需要能够清晰地画出下面三张图:
-
前向图:标有卷积核id->的输入小图->组成的输出大图
-
下层Loss:标有卷积核id->的输出小图->组成的输入大图
-
本层w导数:标有输入id->的输出小图->组成的卷积核大图
刚才看到的是stride=1,padding=0的解法,相对来说省略了一些细节的考虑。如果把这些加上,这种方法也是可以解的,只不过比刚才要复杂一些。这里就不做更进一步的推导了,有志成为“实力派”的童鞋可以去尝试一下进一步地推导。但不管做怎么样的变换,都离不开上面三张图的推导。熟练推导三张图能够让我们更加深刻地理解卷积(相关)操作内部的过程,是绝对有好处的。
2
“偶像派”解法
说实话,像神经网络里面这样的推导所涉及到的数学能力并不高,但是想熟练掌握需要好好地刷一刷。但是如果不想把自己搞得这么痛苦,就可以试试偶像派的解法。
偶像派的解法是怎么做到的呢?我们前面说了卷积层的卷积计算是线性的,既然是线性的我们能不能把输入矩阵做一个变换,使运算变成一个矩阵和卷积核向量相乘的运算呢?当然可以了,这就涉及到我们的“整容”过程了。
整容的过程只需要用到上面的第一张图——前向图。前向图中每一个小图都可以表示输入数据的一部分和卷积核做点积的过程。最终我们求出了9个结果值,因此也就有9个输入的部分数据和卷积做了点积。这个过程同样可以画成一张图:
这样看来,前向计算和求解卷积核参数导数的计算就会变得容易得多。不过如果考虑了padding和stride,写这个转换的算法还是需要点细心的。至于向下一层传递的导数,做法也类似,转成矩阵乘法的形式即可,这里就不再赘述了。
这样,偶像派的解法也就完成了。借助了矩阵和向量的乘法,我们的大脑得到了极大的解放。那么问题来了,对于我们常用的软件库,大家是怎么实现这个算法的呢?
基本上大家都选择偶像的道路……倒不是因为偶像派的思路清晰,而是因为矩阵乘法这样的运算经过大家多年的研究,运算效率非常有保障。而卷积运算在实现过程中像cache友好性这样的问题会差些,所以最终被淘汰。
当然,现在被广大群众所爱戴的——CUDNN库的实现比我们偶像派的做法在细节上更为精细,可以算是偶像+实力兼备。感兴趣的童鞋可以去了解一下。
聊完了线性部分求解的事情,下次就来一起看看非线性部分的一些问题。