悟已往之不谏,知来者之可追。
实迷途其未远,觉今是而昨非。
–陶渊明

0. 前(fei)言(hua)

我曾经以为我是天下第一倒霉蛋,既不是富二代,也谈不到恋爱,脑子也是笨蛋,又是高度近视,直到考上研才发现我其实一直都很走运,尤其是在这种重要的人生节点,我总是能走运,获得我的努力配不上的成绩。虽然我不信命运,但是既受此天恩,又岂能不抚壮而弃秽兮?
大二的时候用C++写过全连接的网络,很简单,现在有个网络需要部署在开发板上,内存1M,除了用cpp写我想不出别的办法了;往年竟然有人在这板子上面跑python,我只能说是我孤陋寡闻了。奈何笨鸟先飞,虽然用cpp重构费事了些,但是也不妨为一个很好的练手程序。我听说同校有的社团大一招新就是手写卷积层,确实是我菜了。我一直以为卷积层这玩意谁不会啊,对应位置相乘,一个块移来移去的,小学生理解起来也没有什么问题。但是当输入图像是多通道、feature map产生多个的时候呢?你还是能完全清楚的说出计算过程吗?恐怕未必很多人能够完整说出。网上类似的文章虽然有很多,但我总感觉读起来隔靴搔痒,太过学术,不得其中门道,故斗胆撰文,谨抛砖引玉,若能启读者之思,则诚为我之幸甚。若有疏漏,恳请诸位读者斧正。

1. 卷积层的性质

我们都知道,卷积层在keras中是这样定义的:

layers.Conv2D(
        input_shape=(pic_width, pic_height, 1), # 输入层尺寸
        filters=3,			# 卷积核特征图个数(似乎有别的叫法?)
        kernel_size=(5, 5),	# 卷积核长宽
        strides=2,  		# 使用步长为2
        padding='same',  	# 使用 'same' 填充以保持维度
        activation='tanh',	# 激活函数
        kernel_initializer='uniform'	# 初始化器
    )


这里我想讲的就是这个所谓filters,这个参数的实际意义就是,这一层卷积层能够从输入图像中提取多少个高维特征,也就是,和输入图像的层数无关,譬如,你可以从一张图片中提取64种高维特征。
它实际是怎么执行的呢?就是生成了filters个卷积核,注意我这里没有说卷积核内部什么结构,就是生成了这么多个卷积核。然后用随机初始化器初始化这些卷积核,这样训练之后参数都不一样,一个卷积核能提取一种特征,实际意义也就是提取了这么多个高维特征。

2. 卷积核结构

那么上面说到,卷积层会生成filters个卷积核,那么卷积核内部是什么结构呢?
答:平面尺寸是自定义的kernel_size,深度是输入图像的深度
例如,当输入图像是三通道,那么卷积核就是kernel_size_x * kernel_size_y * input_img.depth
你可能觉得,这多理所当然啊?这不是显然卷积核深度等于输入图像的深度吗?好吧我承认这很显然,我之前一直以为是一张图各个深度共用一个卷积核。

3. 卷积层计算过程

我为什么一开始错误的觉得各个深度共用一张卷积核?因为如果各个层各自有卷积核的话,那岂不是一个卷积核就能卷出(深度)张图片?这好像不太对吧?

正确的做法是:对于每一个卷积核,将其每一层和输入图像的对应层相卷积,然后将这些层得到的结果相加,再加上偏置项。

举个例子:对于一个输入图像,shape=(5*5*3),卷积核尺寸(3*3),请问卷积核的深度应该是多少?由前文得知卷积核深度等于图像深度,所以卷积核尺寸(3*3*3)。那么我先取两者的第一层做卷积:输入图像的第一层和卷积层的第一层做卷积,得到一个(3*3*1)的输出矩阵对吧,这是单通道的卷积,相信应该都能理解。然后我再取输入图像的第二层和卷积核的第二层做卷积,得到一个输出图像,再第三层对应卷积。那么我是不是一共得到了三张输出图片?没错!我们把它们对应位置相加就可以!这样不就合成一张图片了吗!再把这张图的每个元素加上偏置项,这样就一个卷积核得到一张卷积图了!以上就是多维卷积核卷积多维图像的计算方式。相信这样的叙述即使没有图片应该也是都能看懂的(其实是平板没电了画不了图)。

4. 卷积层级联的shape

假设输入图像shape是(64, 64, 5),
通过一个filters=6, kernel_size = (3, 3), step = 1的卷积层,求输出shape?
答:首先,输出图像的二维尺寸是(input_img.cols - kernel_size.cols) / step + 1 深度就是等于filters,也就是6,所以输出图像shape=(62, 62, 6)
那么卷积层参数的shape是多少呢?答:(3, 3, 5, 6)前面的3*3好理解,5就是我们说到卷积核的每一层都要和输入图像的每一层卷积,所以深度相等,最后的6呢?就是卷积核的个数,一共有6个(3, 3, 5)的卷积核。
小结一下,当输入图像shape=(64, 64, 5),卷积层filters=6, kernel_size = (3, 3), step = 1
输出图像shape=(62, 62, 6),
卷积层参数shape=(3, 3, 5, 6)

如果此时通过第二个卷积层,filters=4, kernel_size = (10, 10), step = 1
输出图像shape=(53, 53, 4)
卷积层参数shape=(10, 10, 6, 4)

以此类推。

5. 总结

  1. 一个卷积层会生成filters个独立的卷积核,这个参数是你自己设定的提取高维特征的数目
  2. 一个卷积核的深度等于输入图像的深度,因为要对应层卷积然后相加。
  3. 输出图像的深度等于卷积核的个数,因为每个卷积核只输出一张单通道的图。

回朕车以复路兮,及行迷之未远。
步余马于兰皋兮,驰椒丘且焉止息。
–屈原