1. What is CNN
ImageNet Classification with Deep Convolutional Networks算是深度学习的起源(当然,更远可以追溯到Yann LeCun1998年的Gradient-Based Learning Applied to Document Recognition)。
Alex Krizhevsky, Ilya Sutskever, 以及Geoffrey Hinton三人创造了一个“大规模、有深度的卷积神经网络”,并用它赢得了2012年度ILSVRC挑战(ImageNet Large-Scale Visual Recognition Challenge)从那时起CNN就变成了业内家喻户晓的名字。
CNN 核心思想:局部感受野(local field) + 权值共享 + 亚采样
1.1. CNN网络结构
如下图所示,CNN一般由卷积层,池化层和全连接层组成。 典型CNN结构1.1.1 卷积层
卷积层是CNN的精髓所在,对局部感受野进行卷积操作。了解数字图像处理的同学知道,对图像进行平滑操作,就是图像各个区域与核(Kernel)相乘相加。如图3*3的核
图1.1.1.1. 核 图1.1.1.2. 图像平滑操作 卷积层中的卷积操作从信号处理的角度来说,是滤波器(卷积核)对信号做频率筛选。CNN的训练就是找到最好的滤波器(Filter)使得滤波后的信号更容易分类。从模版匹配的角度看卷积,每个卷积核都可以看成一个特征模版,训练就是为了得到合适的滤波器,使得对特定模式有高的激活, 以达到分类/检测的目的。与图像中的卷积不同的是,CNN的卷积层可设置多个滤波器,得到不同的feature map。而且每个Filter的值不是固定的,而是可变的,可训练的。
卷积核(Kernel)有几个重要参数:
Kernel_size:
卷积核的大小,如3x3,以图像为例就是在图像的heightxwidth上的卷积核大小,当然也可以是6x3。
Stride:
步长,即卷积核在各个方向上移动的步长。对于一个NxMx3的图像,设置的卷积核步长通常是AxBx1,即第三通道步长是1,即各个通道之间是互不影响的。
Kernel_nums:
,卷积核的个数。Kernel_nums决定了卷积层输出的深度,即下图中'豆腐块'的个数
Padding:
以图像为例,做卷积操作的时候,卷积核按照大小和步长移动至图像边缘的时候,剩下的数据不足一个卷积核大小时的处理方式。一般有两种SAME
和VALID
--SAME方式:
不足补零
SAME方式会将不足一个kernel的一列再补零至kernel大小
--VALID方式:
不足舍弃
VALID方式会将不足一个kernel的最后一列舍弃
下面,我们以具体的数据详细介绍卷积操作:
Stride的影响:
对比图5和图6,以width方向为例(hight方向相同),Stride=1时,每做完一次卷积操作,卷积核向右移动一列。而Stride=2时则向右移动两列。
Pad的影响:
对比图6和图7, pad=same,补零。
多通道卷积:
当输入有多个通道(channel)时(例如图片可以有 RGB 三个通道),卷积核需要拥有相同的channel数,每个卷积核 channel 与输入层的对应 channel 进行卷积,将每个 channel 的卷积结果按位相加得到最终的 Feature Map。
多卷积核:
当有多个卷积核时,可以学习到多种不同的特征,对应产生包含多个 channel 的 Feature Map, 例如上图有两个 filter,所以 output 有两个 channel。图9. 两个卷积核,3通道卷积操作
为什么对局部感受野提取特征
对于图像而言,局部区域的像素关联性往往很强,而相距较远的区域关联性往往很弱。同样,对于文本而言,相近的词汇在语义表达上往往有紧密联系而相隔较远的词汇语义关联则相对较低。因此,只需要对局部信息进行特征提取,最后综合起来就能达到全局感知。
卷积层在Tensorflow中的实现
相对较旧的实现方式,手动指定filter的维度和初始化方法
tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
Args:
input: A Tensor. Must be one of the following types: half, bfloat16, float32, float64. A 4-D tensor. The dimension order is interpreted according to the value of data_format, see below for details.
filter: A Tensor. Must have the same type as input. A 4-D tensor of shape [filter_height, filter_width, in_channels, out_channels]
strides: A list of ints. 1-D tensor of length 4. The stride of the sliding window for each dimension of input. The dimension order is determined by the value of data_format, see below for details.
padding: A string from: "SAME", "VALID". The type of padding algorithm to use.
use_cudnn_on_gpu: An optional bool. Defaults to True.
data_format: An optional string from: "NHWC", "NCHW". Defaults to "NHWC". Specify the data format of the input and output data. With the default format "NHWC", the data is stored in the order of: [batch, height, width, channels]. Alternatively, the format could be "NCHW", the data storage order of: [batch, channels, height, width].
dilations: An optional list of ints. Defaults to [1, 1, 1, 1]. 1-D tensor of length 4. The dilation factor for each dimension of input. If set to k > 1, there will be k-1 skipped cells between each filter element on that dimension. The dimension order is determined by the value of data_format, see above for details. Dilations in the batch and depth dimensions must be 1.
name: A name for the operation (optional).
input
: 输入,shape=[batch, height, width, in_channels]。
filter
:卷积核。shape=[filter_height, filter_width, in_channels, out_channels]。其中out_channels是卷积核的个数。
strides
:步长
padding
: 选择SAME或VALID
use_cudnn_on_gpu
:是否使用cudnn加速,默认为true
最新(截止写这篇博客的时间)的搭建卷积层的方法:
tf.layers.conv2d(
`inputs`**: Tensor input.
`filters`**: Integer, the dimensionality of the output space (i.e. the number of filters in the convolution).
`kernel_size`**: An integer or tuple/list of 2 integers, specifying the height and width of the 2D convolution window. Can be a single integer to specify the same value for all spatial dimensions.
`strides`**: An integer or tuple/list of 2 integers, specifying the strides of the convolution along the height and width. Can be a single integer to specify the same value for all spatial dimensions. Specifying any stride value != 1 is incompatible with specifying any `dilation_rate` value != 1.
`padding`**: One of `"valid"` or `"same"` (case-insensitive).
`data_format`**: A string, one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape `(batch, height, width, channels)` while `channels_first` corresponds to inputs with shape `(batch, channels, height, width)`.
`dilation_rate`**: An integer or tuple/list of 2 integers, specifying the dilation rate to use for dilated convolution. Can be a single integer to specify the same value for all spatial dimensions. Currently, specifying any `dilation_rate` value != 1 is incompatible with specifying any stride value != 1.
`activation`**: Activation function. Set it to None to maintain a linear activation.
`use_bias`**: Boolean, whether the layer uses a bias.
`kernel_initializer`**: An initializer for the convolution kernel.
`bias_initializer`**: An initializer for the bias vector. If None, the default initializer will be used.
`kernel_regularizer`**: Optional regularizer for the convolution kernel.
`bias_regularizer`**: Optional regularizer for the bias vector.
`activity_regularizer`**: Optional regularizer function for the output.
`kernel_constraint`**: Optional projection function to be applied to the kernel after being updated by an `Optimizer` (e.g. used to implement norm constraints or value constraints for layer weights). The function must take as input the unprojected variable and must return the projected variable (which must have the same shape). Constraints are not safe to use when doing asynchronous distributed training.
`bias_constraint`**: Optional projection function to be applied to the bias after being updated by an `Optimizer`.
`trainable`**: Boolean, if `True` also add variables to the graph collection `GraphKeys.TRAINABLE_VARIABLES`.
`name`**: A string, the name of the layer.
`reuse`**: Boolean, whether to reuse the weights of a previous layer by the same name.
可以看出tf.layers.conv2d集成了对filter初始化,是否正则,是否加激活函数等多种功能。tf.nn和tf.layers两种方法的功能是相同的,tf.nn.conv2d是tf.layers.conv2d的后端。
此外,tf.nn.conv1d或者tf.layers.conv1d是只在一个方向上进行卷积操作的命令。
1.1.2. 池化层(Pooling)
池化层操作也可以称为降采样
或者欠采样
最大池化
和平均池化
图1.2.2. 池化操作
池化的主要目的有:
-
减少参数:
若输入通道数、特征图宽和高分别为c、w、h,则经过stride为2的pooling层(pad=same),输出为c、w/2、h/2,计算量减小为1/4。减少参数除了可以降低内存消耗外,更为重要的是使得构建更深层的网络成为可能, 防止过拟合。 -
图1.2.3. 池化操作提高感受野提高感受野:
如图1.2.3所示,可以看出经过5层3x3,stride=2的卷积操作之后,感受野提高了11倍。感受野的提高可以获得更多全局信息和上下文信息。
-
图1.2.4. 池化操作的旋转不变性不变性 invariance:
不变性包括translation(平移),rotation(旋转),scale(尺度)。当然这也是因为感受野的增大,使得特征局部位置的变化,不影响判断特征是否存在。进而增强了模型的鲁棒性和模型泛化能力。 -
更好权衡特征map大小和数量:
对于卷积层输出来说,每个特征map对应一个filters,特征map越多对应更多的filters,而不同的filters提取的是图像中不同方面的特征,也就是说filters越多对图像不同特征的提取越多。那么通常全连接层前的卷积层的map数量太多的时候,map的大小就不应该太大,以控制全连接层的参数。这样就可以在卷积层和全连接层之间增加pooling层,以权衡map数量和大小。 -
变长数据变定长:
这部分还有待补充
Pooling 层在Tensorflow中的实现
tf.nn.max_pool(
value,
ksize,
strides,
padding,
data_format='NHWC',
name=None
)
#### Args:
* **`input`**: A `Tensor`. Must be one of the following types: `float32`, `float64`, `int32`, `uint8`, `int16`, `int8`, `int64`, `bfloat16`, `uint16`, `half`, `uint32`, `uint64`. 4-D with shape `[batch, height, width, channels]`. Input to pool over.
* **`ksize`**: A list of `ints` that has length `>= 4`. The size of the window for each dimension of the input tensor.
* **`strides`**: A list of `ints` that has length `>= 4`. The stride of the sliding window for each dimension of the input tensor.
* **`padding`**: A `string` from: `"SAME", "VALID"`. The type of padding algorithm to use.
* **`Targmax`**: An optional [`tf.DType`](https://www.tensorflow.org/api_docs/python/tf/DType) from: `tf.int32, tf.int64`. Defaults to [`tf.int64`](https://www.tensorflow.org/api_docs/python/tf/int64).
* **`name`**: A name for the operation (optional).
ksize
:池化核的大小,通常不会在batch和channel方向做池化,因此可以设置为[1, ksize, ksize, 1]的形式。
也可以用集成版本:tf.layers.max_pooling2d,具体可参见官网说明。
1.1.3. 全连接层
全连接层(fully connected layers,FC)的输入和每个神经元都相连。图1.1.3.1是卷积层+池化层之后与全连接层相连的示意图。 图1.1.3.1-
分类器作用:
FC在整个卷积神经网络中起到“分类器”的作用。如果说卷积层、池化层和激活函数层等操作是将原始数据映射到隐层特征空间的话,全连接层则起到将学到的“分布式特征表示”映射到样本标记空间的作用。 图1.1.3.2.全连接层将分布特征映射到样本标记空间 在实际使用中,全连接层可由卷积操作实现:
a. 前层是全连接的全连接层可以转化为卷积核为1x1的卷积;例如,前层FC1是输出为4096个神经元的全连接层,当前全连接层FC2的神经元是512,则FC1:1x1x4096到FC2: 1x1x512可以由filter_size为1*1,filter_nums=512的卷积操作得到。
b. 前层是卷积层的全连接层可以转化为卷积核为hxw的全局卷积。例如,前层conv的输出为10x10x512,设置全连接层的神经元为4096,则可以对conv输出进行filter_size=10x10, filter_nums=4096的卷积操作得到1x1x4096的全连接层。 -
参数冗余:
如果将[10,10,3]的图片输入全连接层,首先要将图片展开成[300]的一维向量。假设全连接层有1000个神经元,偏置为0, 则每个神经元就有300个参数,仅这一层全连接网络就有300k个参数。同样的图片,我们对比一下输入到卷积网络需要的参数量:假如说卷积核大小为[3,3],卷积核个数为1000个,由于卷积层的权值共享(这部分将在下面的小节中详细介绍),只需要9k个参数。
卷积层也可以理解为全连接层,只是关注的不再是整个图片,而是将图片分割成不同的单元(有重叠),利用权值共享来减少参数量。最终将不同的local特征综合起来,通过全连接映射到样本标记空间。 图1.1.3.3. 全连接层参数冗余说明 对于全连接层的参数冗余
,目前的处理方法主要有两种:
a.用卷积层代替全连接层
b.卷积层或者池化层输出经过全局平均池化(global average pooling, GAP)
后,再输入给全连接层。
通常卷积层或者池化层的输出是:[batch, height, width, channels],当height变长是可以reshape成[batchxheight, widthxchannels]输入全连接层,如果是height是定长,[batch, hxwxchannels]输入全连接。这两种方式的参数量都是随w,channel爆炸增长的。因此全局平均池化是首先将每个feature map求平均,得到channel个特征输入全连接层。这在很大程度上可以减少全连接层的参数冗余。例如,卷积层或者池化层的输出是[batch, 6, 6, 10],有10个feature map。全局池化是对这6x6的map求平均,得到一个值,所以最后输入给全连接的就是[batch, 10]。
当然,也可以用全局平均池化完全取代全连接网络。以2分类来说,我们只需要在最后一层卷积层的时候的设置filter_nums=2,得到输出[batch, h, w, 2],则经过全局平均池化后的输出是[batch, 2],直接用softmax等作为网络目标函数来指导学习过程,可参见ECCV16。 图1.1.3.4. 传统CNN与全局平均池化接全连接层的对比
全连接层在Tensorflow中的实现
tf.layers.dense(
inputs,
units,
activation=None,
use_bias=True,
kernel_initializer=None,
bias_initializer=tf.zeros_initializer(),
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
trainable=True,
name=None,
reuse=None
)
#### Arguments:
* **`inputs`**: Tensor input.
* **`units`**: Integer or Long, dimensionality of the output space.
* **`activation`**: Activation function (callable). Set it to None to maintain a linear activation.
* **`use_bias`**: Boolean, whether the layer uses a bias.
* **`kernel_initializer`**: Initializer function for the weight matrix. If `None` (default), weights are initialized using the default initializer used by [`tf.get_variable`](https://www.tensorflow.org/api_docs/python/tf/get_variable).
* **`bias_initializer`**: Initializer function for the bias.
* **`kernel_regularizer`**: Regularizer function for the weight matrix.
* **`bias_regularizer`**: Regularizer function for the bias.
* **`activity_regularizer`**: Regularizer function for the output.
* **`kernel_constraint`**: An optional projection function to be applied to the kernel after being updated by an `Optimizer` (e.g. used to implement norm constraints or value constraints for layer weights). The function must take as input the unprojected variable and must return the projected variable (which must have the same shape). Constraints are not safe to use when doing asynchronous distributed training.
* **`bias_constraint`**: An optional projection function to be applied to the bias after being updated by an `Optimizer`.
* **`trainable`**: Boolean, if `True` also add variables to the graph collection `GraphKeys.TRAINABLE_VARIABLES` (see [`tf.Variable`](https://www.tensorflow.org/api_docs/python/tf/Variable)).
* **`name`**: String, the name of the layer.
* **`reuse`**: Boolean, whether to reuse the weights of a previous layer by the same name.
1.2 CNN 权值共享
我们上面已经分析了,对于全连接网络来说输入和每个神经元都是相连的。如图1.2.1所示是全连接各层的权重值, 可以看出,随着神经元数量的增加,全连接网络参数会呈爆炸式增长。那么CNN是如何通过权值共享减少参数冗余的呢?
图1.2.1.全连接网络 所谓CNN的权值共享,通俗来说,就是给一张图片,用同一个Filter去扫描图片的不同区域(local),对于这张图片来说,各个区域的权值是共享的。如图1.2.2所示,图片共享一个2*2的卷积核。当然,我们可以通过增加卷积核的个数,来学习不同的特征,得到不同的feature map。权值共享不仅仅是为了减少参数冗余,也有一定的现实意义。更更更通俗的来说,我们可以把卷积核当作一个模板,用这个模板去对比图像中的不同位置,寻找与模板中相对应的特征。比如有个曲线的特征过滤器,那么这个过滤器在扫描全图的时候,我们想要提取出所有的曲线区域,就应该保持过滤器不变。如果在上半部分过滤器是曲线,到下半部分变成了直线,那么在图像上下区域内提取出来的曲线特征是真正的曲线吗? 图1.2.2. 卷积网络卷积操作
1.3 CNN维度计算
CNN中涉及很多卷积,池化操作。弄清楚每一个参数的意义和每一种操作输入输出的关系,可以帮助我们在代码实现中设定参数,在网络训练中调整参数。我们以图1.3.1中的参数为例,详细介绍一下各层输入输出维度的计算方法。
假设输入Input_shape=[batch, height, width, channels]
,
核大小为filter=[in_channels, fh_size, fw_size, out_channels]
步长为: stride=[1, h_stride, w_stride, 1]
补零的个数为: padding
备注
:filter中in_channlel是上一层输出的channels, 而out_channels是卷积核的个数。
则输入输出的计算公式如下:
same
和valid
,前面已经介绍过same是不够补零,valid(padding=0)是不够舍弃。两种方式输入输出的关系如下所示:
图1.3.2: same和valid两种方式输入输出的计算公式
我们以具体的例子来说,如图:
后面的留给大家自己推导 图1.3.3.卷积神经网络维度计算示意图Convolutional Layer1
Input_shape=[1, 28, 28, 1]
,
Filter=[1,5,5,10]
,
Stride= [1, 1, 1, 1]
Pad=valid
得到(28-5+0)/1 +1 = 24, 所以
output_shape=[1, 24, 24,10]
2. Why CNN
CNN作为目前最流行的网络结构,可谓大红大紫。根据前面对CNN的详细介绍可以知道, 它引入的卷积层,池化层,权重共享等使得深度网络成为可能。
3. CNN发展历史
CNN:
2012年的AlexNet
2014年的VGG, 是牛津大学团队Visual Geometry Group的简称。
2014年的GoogLeNet, 也称作Inception V1
2014年Google团队Christian根据1x1卷积核(NiN) 发展Bootleneck Layer
2015年2月Christian团队提出了Batch_normalized层
2015年12月Christian团队发布了新版本的GoogleNet (Inception V3)
2015年12月何大神(膜拜!)发表了ResNet
2017年CVPR的Oral, DenseNet
还有针对移动端的轻量级网络:
2016年2月,伯克利&斯坦福的SqueezeNet
2016年04月,Google团队的MobileNet
2016年6月, Face++的ShuffleNet
2016年10月,Google团队的Xception
参考资料:
[1] CNN卷积详解:图很漂亮
[2] 知乎问答:CNN工作原理
[3] 卷积神经网络CNN_LeNet5实现
[4] Pooling层的作用
[5] CNN 详解
[6] 3D 示意图
[7] 一维/二维/三维卷积讲解
[8] 全连接层的作用
[9] CNN发展历史
[10] CNN发展历史详解
[11] CNN发展历史简介
[12] 网络训练调参经验
网友评论