文章首发于我的 github 仓库。
在只有一个通道的情况下,“卷积核”(“kernel”
)就相当于滤波器(“filter”
),这两个概念是可以互换的。一个 “Kernel”
更倾向于是 2D
的权重矩阵。而 “filter”
则是指多个 kernel
堆叠的 3D
结构。如果是一个 2D
的 filter
,那么两者就是一样的。但是一个3D
filter
,在大多数深度学习的卷积中,它是包含 kernel
的。每个卷积核都是独一无二的,主要在于强调输入通道的不同方面。
不管是
TensorFlow
、Keras
、Caffe
还是Pytorch
,其卷积层和池化层的参数默认值可能有所不同,但是最终的卷积输出大小计算公式是一样的。
卷积层主要参数有下面这么几个:
Kernal
大小(在 Tensorflow/keras
框架中也称为filter
);Padding
;Stride
;Channels
。1,在 Pytorch
框架中,图片(feature map
)经卷积 Conv2D
后输出大小计算公式如下: ⌊ N = W − F + 2 P S + 1 ⌋ \left \lfloor N = \frac{W-F+2P}{S}+1 \right \rfloor ⌊N=SW−F+2P+1⌋,其中 ⌊ ⌋ \lfloor \rfloor ⌊⌋ 是向下取整符号,用于结果不是整数时进行向下取整(Pytorch
的 Conv2d
卷积函数的默认参数 ceil_mode = False
,即默认向下取整, dilation = 1
)。
W×W
(默认输入尺寸为正方形)Filter
大小 F×F
S
P
N×N
2,特征图经反卷积(也叫转置卷积) keras-Conv2DTranspose
(pytorch-ConvTranspose2d
) 后得到的特征图大小计算方式: o u t = ( i n − 1 ) ∗ s − 2 p + k out = (in - 1) *s -2p + k out=(in−1)∗s−2p+k,还有另外一个写法: W = ( N − 1 ) ∗ S − 2 P + F W = (N - 1)*S - 2P + F W=(N−1)∗S−2P+F,可由卷积输出大小计算公式反推得到。 i n in in 是输入大小, k k k 是卷积核大小, s s s 是滑动步长, padding
的像素数 p p p, o u t out out 是输出大小。
反卷积也称为转置卷积,一般主要用来还原 feature map
的尺寸大小,在 cnn
可视化,fcn
中达到 pixel classification
,以及 gan
中从特征生成图像都需要用到反卷积的操作。反卷积输出结果计算实例。例如,输入:2x2
, 卷积核大小:4x4
, 滑动步长:3
,填充像素为 0
, 输出:7x7
,其计算过程就是, (2 - 1) * 3 + 4 = 7
。
3,池化层如果设置为不填充像素(对于 Pytorch
,设置参数padding = 0
,对于 Keras/TensorFlow
,设置参数padding="valid"
),池化得到的特征图大小计算方式: N = ( W − F ) / S + 1 N=(W-F)/S+1 N=(W−F)/S+1,这里公式表示的是除法结果向下取整再加 1
。
总结:对于Pytorch
和 tensorflow
的卷积和池化函数,卷积函数 padding
参数值默认为 0/"valid"
(即不填充),但在实际设计的卷积神经网络中,卷积层一般会填充像素(same
),池化层一般不填充像素(valid
),输出 shape
计算是向下取整。注意:当 stride
为 1
的时候,kernel
为 3
、padding
为 1
或者 kernel
为 5
、padding
为 2
,这两种情况可直接得出卷积前后特征图尺寸不变。
注意不同的深度学习框架,卷积/池化函数的输出
shape
计算会有和上述公式有所不同,我给出的公式是简化版,适合面试题计算,实际框架的计算比这复杂,因为参数更多。
如果希望输出特征图的空间维度Keras/TensorFlow
设置卷积层的过程中可以设置 padding
参数值为 “valid” 或 “same”
。“valid”
代表只进行有效的卷积,对边界数据不处理。“same” 代表 TensorFlow
会自动对原图像进行补零(表示卷积核可以停留在图像边缘),也就是自动设置 padding
值让输出与输入形状相同。
图像张量的形状有两种约定,通道在前(channel-first
)和通道在后(channel-last
)的约定,常用深度学习框架使用的数据张量形状总结如下:
Pytorch/Caffe
: (N, C, H, W
);TensorFlow/Keras
: (N, H, W, C
)。举例理解就是Pytorch
的卷积层和池化层的输入 shape
格式为 (N, C, H, W)
,Keras
的卷积层和池化层的输入 shape
格式为 (N, H, W, C)
。
值得注意的是 OpenCV
读取图像后返回的矩阵 shape
的格式是 (H, W, C)
格式。当 OpenCV 读取的图像为彩色图像时,返回的多通道的 BGR 格式的矩阵(HWC
),在内存中的存储如下图:
注意:对于
Pytorch、Keras
的卷积层和池化层函数,其padding
参数值都默认为不填充像素,默认值为0
和valid
。
class torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
二维池化层,默认输入的尺度是(N, C_in,H,W)
,输出尺度(N,C_out,H_out,W_out)
。池化层输出尺度的 Width 默认计算公式如下(ceil_mode= True
时是向上取整,Height
计算同理):
⌊ W i n + 2 ∗ padding [ 0 ] − dilation [ 0 ] × ( kernel_size [ 0 ] − 1 ) − 1 stride[0] + 1 ⌋ \left\lfloor \frac{W_{in} + 2 * \text{padding}[0] - \text{dilation}[0] \times (\text{kernel\_size}[0] - 1) - 1}{\text{stride[0]}} + 1 \right\rfloor ⌊stride[0]Win+2∗padding[0]−dilation[0]×(kernel_size[0]−1)−1+1⌋
主要参数解释:
kernel_size
(int or tuple
):max pooling
的窗口大小。stride
(int or tuple
, optional):
max pooling的窗口移动的步长。默认值是
kernel_size`。padding
(int or tuple
, optional
):默认值为 0
,即不填充像素。输入的每一条边补充 0
的层数。dilation
:滑动窗中各元素之间的距离。ceil_mode
:默认值为 False
,即上述公式默认向下取整,如果设为 True
,计算输出信号大小的时候,公式会使用向上取整。
Pytorch
中池化层默认ceil mode = false
,而Caffe
只实现了ceil mode= true
的计算方式。
示例代码:
import torch
import torch.nn as nn
import torch.autograd as autograd
# 大小为3,步幅为2的正方形窗口池
m = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# pool of non-square window
input = autograd.Variable(torch.randn(20, 16, 50, 32))
output = m(input)
print(output.shape) # torch.Size([20, 16, 25, 16])
keras.layers.MaxPooling2D(pool_size=(2, 2), strides=None, padding='valid', data_format=None)
对于 2D
空间数据的最大池化。默认输入尺寸是 (batch_size, rows, cols, channels)/(N, H, W, C_in)
的 4D
张量,默认输出尺寸是 (batch_size, pooled_rows, pooled_cols, channels)
的 4D
张量。
padding = valid
:池化层输出的特征图大小为: N = ( W − F ) / S + 1 N=(W-F)/S+1 N=(W−F)/S+1,这里表示的是向下取整再加 1。padding = same
: 池化层输出的特征图大小为 N = W / S N = W/S N=W/S,这里表示向上取整。主要参数解释:
pool_size
: 整数,或者 2
个整数表示的元组, 沿(垂直,水平)方向缩小比例的因数。(2,2
)会把输入张量的两个维度都缩小一半。 如果只使用一个整数,那么两个维度都会使用同样的窗口长度。strides
: 整数,2
个整数表示的元组,或者是 None
。 表示步长值。 如果是 None
,那么默认值是 pool_size
。padding
: "valid"
或者 "same"
(区分大小写)。data_format
: 字符串,channels_last
(默认)或 channels_first
之一。 表示输入各维度的顺序。 channels_last
代表尺寸是 (batch, height, width, channels)
的输入张量, 而 channels_first
代表尺寸是 (batch, channels, height, width)
的输入张量。 默认值根据 Keras
配置文件 ~/.keras/keras.json
中的 image_data_format
值来设置。如果还没有设置过,那么默认值就是 "channels_last"
。class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
二维卷积层, 输入的尺度是(N, C_in, H, W)
,输出尺度(N,C_out,H_out,W_out)
。卷积层输出尺度的 Weight
计算公式如下(Height
同理):
⌊ W i n + 2 × padding [ 0 ] − dilation [ 0 ] × ( kernel_size [ 0 ] − 1 ) − 1 stride [ 0 ] + 1 ⌋ \left\lfloor \frac{W_{in} + 2 \times \text{padding}[0] - \text{dilation}[0] \times (\text{kernel\_size}[0] - 1) - 1}{\text{stride}[0]} + 1\right\rfloor ⌊stride[0]Win+2×padding[0]−dilation[0]×(kernel_size[0]−1)−1+1⌋
Pytorch/Caffe
框架输入输出数据的尺寸都是 ((N, C, H, W)
),常规卷积的卷积核权重shape
都为(C_out, C_in, kernel_height, kernel_width
),常规卷积是这样,但是分组卷积的卷积核权重shape
为(C_out, C_in/g, kernel_height, kernel_width
)和DW
卷积的卷积核 权重shape
为(C_in, 1, kernel_height, kernel_width
)。
kernel_size
, stride
, padding
, dilation
参数可以是以下两种形式( Maxpool2D
也一样):
a single int
:同样的参数值被应用与 height
和 width
两个维度。a tuple of two ints
:第一个 int
值应用于 height
维度,第二个 int
值应用于 width
维度,也就是说卷积输出后的 height
和 width
值是不同的,要分别计算。主要参数解释:
in_channels
(int
) – 输入信号的通道。out_channels
(int
) – 卷积产生的通道。kerner_size
(int or tuple
) - 卷积核的尺寸。stride
(int or tuple
, optional
) - 卷积步长,默认值为 1
。padding
(int or tuple
, optional
) - 输入的每一条边补充 0
的层数,默认不填充。dilation
(int or tuple
, optional
) – 卷积核元素之间的间距,默认取值 1
。groups
(int
, optional
) – 从输入通道到输出通道的阻塞连接数。bias
(bool
, optional
) - 如果 bias=True
,添加偏置。示例代码:
######## Pytorch卷积层输出大小验证
import torch
import torch.nn as nn
import torch.autograd as autograd
# With square kernels and equal stride
# output_shape: height = (50-3)/2+1 = 24.5,卷积向下取整,所以 height=24.
m = nn.Conv2d(16, 33, 3, stride=2)
# # non-square kernels and unequal stride and with padding
# m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2)) # 输出shape: torch.Size([20, 33, 28, 100])
# # non-square kernels and unequal stride and with padding and dilation
# m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1)) # 输出shape: torch.Size([20, 33, 26, 100])
input = autograd.Variable(torch.randn(20, 16, 50, 100))
output = m(input)
print(output.shape) # 输出shape: torch.Size([20, 16, 24, 49])
keras.layers.Conv2D(filters, kernel_size, strides=(1, 1), padding='valid', data_format=None, dilation_rate=(1, 1), activation=None, use_bias=True, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, bias_constraint=None)
2D
卷积层 (例如对图像的空间卷积)。输入输出尺寸格式要求和池化层函数一样。输入尺寸:(N, H, W, C)
,卷积核尺寸:(K, K, C_in, C_out
)。
当使用该层作为模型第一层时,需要提供 input_shape
参数(整数元组,不包含 batch
轴),例如,input_shape=(128, 128, 3)
表示 128x128
的 RGB
图像,在 data_format="channels_last"
时。
主要参数解释:
filters
: 整数,输出空间的维度 (即卷积中滤波器的输出数量)。kernel_size
: 一个整数,或者 2
个整数表示的元组或列表,指明 2D
卷积窗口的宽度和高度。 可以是一个整数,为所有空间维度指定相同的值。strides
: 一个整数,或者 2
个整数表示的元组或列表,指明卷积核模板沿宽度和高度方向的移动步长。 可以是一个整数,为所有空间维度指定相同的值。 指定任何 stride 值 != 1 与指定 dilation_rate 值 != 1 两者不兼容,默认取值 1,即代表会不遗漏的滑过输入图片(Feature Map
)的每一个点。padding
: "valid"
或 "same"
(大小写敏感),默认valid
,这里的 "same"
代表给边界加上 Padding
让卷积的输出和输入保持同样("same"
)的尺寸(即填充像素)。data_format
: 字符串, channels_last (默认)
或 channels_first
之一,表示输入中维度的顺序。 channels_last
对应输入尺寸为 (batch_size, height, width, channels)
, channels_first 对应输入尺寸为 (batch_size, channels, height, width)
。 它默认为从 Keras
配置文件 ~/.keras/keras.json
中 找到的 image_data_format
值。 如果你从未设置它,将使用 channels_last
。dilation_rate
: 一个整数或 2
个整数的元组或列表, 指定膨胀卷积(空洞卷积 dilated convolution
)的膨胀率。 可以是一个整数,为所有空间维度指定相同的值。 当前,指定任何 dilation_rate 值 != 1 与 指定 stride 值 != 1 两者不兼容。Pytorch
的 Conv2d
函数不要求提供 输入数据的大小 (H,W)
,但是要提供输入深度,Keras
的 Conv2d
函数第一层要求提供 input_shape
参数 (H,W, C)
,其他层不需要。
分类问题中,直接使用输出层的输出有两个问题:
softmax
回归解决了以上两个问题,它将输出值变换为值为正且和为 1 的概率分布,公式如下:
s o f t m a x ( y ) i = y i ′ = e y i ∑ j = 1 n e y j softmax(y)_{i} = y_{i}^{'} = \frac{e^{yi}}{\sum_{j=1}^{n}e^{yj}} softmax(y)i=yi′=∑j=1neyjeyi
交叉熵刻画了两个概率分布之间的距离,它是分类问题中使用比较广泛的一种损失函数,交叉熵一般会与 softmax
回归一起使用,公式如下:
L = − ∑ c = 1 M y c l o g ( p c ) 或者 H ( p , q ) = − ∑ p ( x ) l o g q ( x ) L = -\sum_{c=1}^{M}y_{c}log(p_{c})或者H(p,q)=-\sum p(x)logq(x) L=−c=1∑Myclog(pc)或者H(p,q)=−∑p(x)logq(x)
0
或 1
),如果该类别和样本的类别相同就是 1
,否则是 0
;从数学上来理解就是,为了让学到的模型分布更接近真实数据的分布,我们需要最小化模型数据分布与训练数据之间的 KL 散度
,而因为训练数据的分布是固定的,因此最小化 KL 散度
等价于最小化交叉熵,而且交叉熵计算更简单,所以机器/深度学习中常用交叉熵 cross-entroy
作为分类问题的损失函数。
Adam
、AdaGrad
、RMSProp
优化算法具有自适应性。
感受野理解(Receptive Field
)是指后一层神经元在前一层神经元的感受空间,也可以定义为卷积神经网络中每层的特征图(Feature Map
)上的像素点在原始图像中映射的区域大小,即如下图所示:
注意:感受野在 CNN
中是呈指数级增加的。小卷积核(如 3*3
)通过多层叠加可取得与大卷积核(如 7*7
)同等规模的感受野,此外采用小卷积核有两个优势:
model capacity
)和复杂度(model complexity
)。计算感受野时,我们需要知道:
参考 感受野(receptive file)计算
感受野的计算方式有两种:自底向上和自顶向下(top to down
),这里只讲解后者。正常卷积(且不带 padding
)感受野计算公式如下:
F ( i , j − 1 ) = ( F ( i , j ) − 1 ) ∗ s t r i d e + k e r n e l _ s i z e F(i, j-1) = (F(i, j)-1)*stride + kernel\_size F(i,j−1)=(F(i,j)−1)∗stride+kernel_size
其中 F ( i , j ) F(i, j) F(i,j) 表示第 i
层对第 j
层的局部感受野,所以这个公式是从上层向下层计算感受野的。仔细看这个公式会发现和反卷积输出大小计算公式一模一样,实际上感受野计算公式就是 feature_map
计算公式的反向推导。
以下 Python
代码可以实现计算 Alexnet zf-5
和 VGG16
网络每层输出 feature map
的感受野大小,卷积核大小和输入图像尺寸默认定义好了,代码如下:
# !/usr/bin/env python
# [filter size, stride, padding]
net_struct = {'alexnet': {'net':[[11,4,0],[3,2,0],[5,1,2],[3,2,0],[3,1,1],[3,1,1],[3,1,1],[3,2,0]],
'name':['conv1','pool1','conv2','pool2','conv3','conv4','conv5','pool5']},
'vgg16': {'net':[[3,1,1],[3,1,1],[2,2,0],[3,1,1],[3,1,1],[2,2,0],[3,1,1],[3,1,1],[3,1,1],
[2,2,0],[3,1,1],[3,1,1],[3,1,1],[2,2,0],[3,1,1],[3,1,1],[3,1,1],[2,2,0]],
'name':['conv1_1','conv1_2','pool1','conv2_1','conv2_2','pool2','conv3_1','conv3_2',
'conv3_3', 'pool3','conv4_1','conv4_2','conv4_3','pool4','conv5_1','conv5_2','conv5_3','pool5']},
'zf-5':{'net': [[7,2,3],[3,2,1],[5,2,2],[3,2,1],[3,1,1],[3,1,1],[3,1,1]],
'name': ['conv1','pool1','conv2','pool2','conv3','conv4','conv5']}}
def outFromIn(isz, net, layernum):
"""
计算feature map大小
"""
totstride = 1
insize = isz
# for layer in range(layernum):
fsize, stride, pad = net[layernum]
outsize = (insize - fsize + 2*pad) / stride + 1
insize = outsize
totstride = totstride * stride
return outsize, totstride
def inFromOut(net, layernum):
"""
计算感受野receptive file大小
"""
RF = 1
for layer in reversed(range(layernum)): # reversed 函数返回一个反向的迭代器
fsize, stride, pad = net[layer]
RF = ((RF -1)* stride) + fsize
return RF
if __name__ == '__main__':
imsize = 224
feature_size = imsize
print ("layer output sizes given image = %dx%d" % (imsize, imsize))
for net in net_struct.keys():
feature_size = imsize
print ('************net structrue name is %s**************'% net)
for i in range(len(net_struct[net]['net'])):
feature_size, stride = outFromIn(feature_size, net_struct[net]['net'], i)
rf = inFromOut(net_struct[net]['net'], i+1)
print ("Layer Name = %s, Output size = %3d, Stride = % 3d, RF size = %3d" % (net_struct[net]['name'][i], feature_size, stride, rf))
程序输出结果如下:
layer output sizes given image = 224x224
************net structrue name is alexnet**************
Layer Name = conv1, Output size = 54, Stride = 4, RF size = 11
Layer Name = pool1, Output size = 26, Stride = 2, RF size = 19
Layer Name = conv2, Output size = 26, Stride = 1, RF size = 51
Layer Name = pool2, Output size = 12, Stride = 2, RF size = 67
Layer Name = conv3, Output size = 12, Stride = 1, RF size = 99
Layer Name = conv4, Output size = 12, Stride = 1, RF size = 131
Layer Name = conv5, Output size = 12, Stride = 1, RF size = 163
Layer Name = pool5, Output size = 5, Stride = 2, RF size = 195
************net structrue name is vgg16**************
Layer Name = conv1_1, Output size = 224, Stride = 1, RF size = 3
Layer Name = conv1_2, Output size = 224, Stride = 1, RF size = 5
Layer Name = pool1, Output size = 112, Stride = 2, RF size = 6
Layer Name = conv2_1, Output size = 112, Stride = 1, RF size = 10
Layer Name = conv2_2, Output size = 112, Stride = 1, RF size = 14
Layer Name = pool2, Output size = 56, Stride = 2, RF size = 16
Layer Name = conv3_1, Output size = 56, Stride = 1, RF size = 24
Layer Name = conv3_2, Output size = 56, Stride = 1, RF size = 32
Layer Name = conv3_3, Output size = 56, Stride = 1, RF size = 40
Layer Name = pool3, Output size = 28, Stride = 2, RF size = 44
Layer Name = conv4_1, Output size = 28, Stride = 1, RF size = 60
Layer Name = conv4_2, Output size = 28, Stride = 1, RF size = 76
Layer Name = conv4_3, Output size = 28, Stride = 1, RF size = 92
Layer Name = pool4, Output size = 14, Stride = 2, RF size = 100
Layer Name = conv5_1, Output size = 14, Stride = 1, RF size = 132
Layer Name = conv5_2, Output size = 14, Stride = 1, RF size = 164
Layer Name = conv5_3, Output size = 14, Stride = 1, RF size = 196
Layer Name = pool5, Output size = 7, Stride = 2, RF size = 212
************net structrue name is zf-5**************
Layer Name = conv1, Output size = 112, Stride = 2, RF size = 7
Layer Name = pool1, Output size = 56, Stride = 2, RF size = 11
Layer Name = conv2, Output size = 28, Stride = 2, RF size = 27
Layer Name = pool2, Output size = 14, Stride = 2, RF size = 43
Layer Name = conv3, Output size = 14, Stride = 1, RF size = 75
Layer Name = conv4, Output size = 14, Stride = 1, RF size = 107
Layer Name = conv5, Output size = 14, Stride = 1, RF size = 139
卷积核池化的定义核过程理解是不难的,但是其作用却没有一个标准的答案,我在网上看了众多博客和魏秀参博士的书籍,总结了以下答案。
卷积层和池化层的理解可参考魏秀参的《解析卷积神经网络》书籍,卷积(convolution
)操作的作用如下:
局部感知,参数共享
的特点大大降低了网络参数,保证了网络的稀疏性。池化/汇合(pooling
)操作作用如下:
魏秀参-《解析卷积神经网络》
首先权值共享就是滤波器共享,滤波器的参数是固定的,即是用相同的滤波器去扫一遍图像,提取一次特征特征,得到feature map。在卷积网络中,学好了一个滤波器,就相当于掌握了一种特征,这个滤波器在图像中滑动,进行特征提取,然后所有进行这样操作的区域都会被采集到这种特征,就好比上面的水平线。
典型的用于分类的CNN主要由卷积层+激活函数+池化层组成,最后用全连接层输出。卷积层负责提取图像中的局部特征;池化层用来大幅降低参数量级(降维);全连接层类似传统神经网络的部分,用来输出想要的结果。
CNN
具有局部连接、权值共享、池化操作(简单说就是下采样)和多层次结构的特点。
translation invariant
),且可以学到模式的空间层次结构。(二)计算机视觉四大基本任务(分类、定位、检测、分割
卷积操作可获取图像区域不同类型特征,而汇合等操作可对这些特征进行融合和抽象,随着若干卷积、汇合等操作的堆叠,各层得到的深度特征逐渐从泛化特征(如边缘、纹理等)过渡到高层语义表示(躯干、头部等模式)。
首先所谓过拟合,指的是一个模型过于复杂之后,它可以很好地“记忆”每一个训练数据中随机噪音的部分而忘记了去“训练”数据中的通用趋势。训练好后的模型过拟合具体表现在:模型在训练数据上损失函数较小,预测准确率较高;但是在测试数据上损失函数比较大,预测准确率较低。解决办法如下:
数据增强
, 增加数据多样性;L1, L2正则化
;dropout
;BN
,batch normalization;80%
用于训练集,20%
用于验证集(训练集和验证集一定不能相交);训练都时候每隔一定 Epoch
比较验证集但指标和训练集是否一致,如果不一致,并且变坏了,那么意味着过拟合。learning curve
来判别过拟合,参考此博客。underfitting
欠拟合的表现就是模型不收敛,原因有很多种,这里以神经网络拟合能力不足问题给出以下参考解决方法:
he_normal
,深度学习框架都内置了很多权重初始化方法;ReLu
,循环神经网络中的循环层使用的激活函数一般为 tanh
,或者 ReLu
;SGD
优化器速度慢但是会达到最优.神级网络欠拟合的特征就是模型训练了足够长但时间后, loss
值依然很大甚至与初始值没有太大区别,且精度很低,测试集亦如此。根据我的总结,原因一般有以下几种:
L1 范数(L1 norm
)是指向量中各个元素绝对值之和,也有个美称叫“稀疏规则算子”(Lasso regularization)。 比如 向量 A=[1,-1,3], 那么 A 的 L1 范数为 |1|+|-1|+|3|。简单总结一下就是:
在支持向量机学习过程中,L1 范数实际是一种对于成本函数求解最优的过程,因此,L1 范数正则化通过向成本函数中添加 L1 范数,使得学习得到的结果满足稀疏化,从而方便人类提取特征。
L1
范数可以使权值参数稀疏,方便特征提取。 L2 范数可以防止过拟合,提升模型的泛化能力。
Tensorflow
是一个通过计算图的形式来表述计算的编程系统,计算图也叫数据流图,可以把计算图看做是一种有向图,Tensorflow 中的每一个计算都是计算图上的一个节点,而节点之间的边描述了计算之间的依赖关系。
在神经网络中间层也进行归一化处理,使训练效果更好的方法,就是批归一化Batch Normalization(BN)。
(1). 可以使用更高的学习率。如果每层的 scale
不一致,实际上每层需要的学习率是不一样的,同一层不同维度的 scale 往往也需要不同大小的学习率,通常需要使用最小的那个学习率才能保证损失函数有效下降,Batch Normalization 将每层、每维的 scale 保持一致,那么我们就可以直接使用较高的学习率进行优化。
(2). 移除或使用较低的 dropout
。 dropout 是常用的防止 overfitting 的方法,而导致 overfitting 的位置往往在数据边界处,如果初始化权重就已经落在数据内部,overfitting现象就可以得到一定的缓解。论文中最后的模型分别使用10%、5%和0%的dropout训练模型,与之前的 40%-50% 相比,可以大大提高训练速度。
(3). 降低 L2 权重衰减系数。 还是一样的问题,边界处的局部最优往往有几维的权重(斜率)较大,使用 L2 衰减可以缓解这一问题,现在用了 Batch Normalization,就可以把这个值降低了,论文中降低为原来的 5 倍。
(4). 代替Local Response Normalization层。 由于使用了一种 Normalization,再使用 LRN 就显得没那么必要了。而且 LRN 实际上也没那么 work。
(5). Batch Normalization调整了数据的分布,不考虑激活函数,它让每一层的输出归一化到了均值为0方差为1的分布,这保证了梯度的有效性,可以解决反向传播过程中的梯度问题。目前大部分资料都这样解释,比如 BN 的原始论文认为的缓解了 Internal Covariate Shift(ICS) 问题。
关于训练阶段和推理阶段 BN
的不同可以参考 Batch Normalization详解 。
BP
算法中的链式求导逐层传递逐层减小,最后趋近于0,导致对某些层的训练失效;BP
算法中的链式求导逐层传递逐层增大,最后趋于无穷,导致某些层无法收敛;在反向传播过程中需要对激活函数进行求导,如果导数大于 1,那么随着网络层数的增加,梯度更新将会朝着指数爆炸的方式增加这就是梯度爆炸。同样如果导数小于 1,那么随着网络层数的增加梯度更新信息会朝着指数衰减的方式减少这就是梯度消失。
出现梯度消失和梯度爆炸的问题主要是因为参数初始化不当以及激活函数选择不当造成的。其根本原因在于反向传播训练法则,属于先天不足。当训练较多层数的模型时,一般会出现梯度消失问题(gradient vanishing problem)和梯度爆炸问题(gradient exploding problem)。注意在反向传播中,当网络模型层数较多时,梯度消失和梯度爆炸是不可避免的。
深度神经网络中的梯度不稳定性,根本原因在于前面层上的梯度是来自于后面层上梯度的乘积。当存在过多的层次时,就出现了内在本质上的不稳定场景。前面的层比后面的层梯度变化更小,故变化更慢,故引起了梯度消失问题。前面层比后面层梯度变化更快,故引起梯度爆炸问题。
常用的有以下几个方案:
relu、leakrelu
等激活函数BN
批归一化xavier
CNN
中的残差结构LSTM
结构循环神经网络(recurrent neural network, RNN), 主要应用在语音识别、语言模型、机器翻译以及时序分析等问题上。
在经典应用中,卷积神经网络在不同的空间位置共享参数,循环神经网络是在不同的时间位置共享参数,从而能够使用有限的参数处理任意长度的序列。
RNN
可以看做作是同一神经网络结构在时间序列上被复制多次的结果,这个被复制多次的结构称为循环体,如何设计循环体的网络结构是 RNN 解决实际问题的关键。RNN 的输入有两个部分,一部分为上一时刻的状态,另一部分为当前时刻的输入样本。
不一定。导致模型不收敛的原因有很多种可能,常见的有以下几种:
(1). 减少网络层参数。用两个 3*3 卷积比用 1 个 5*5 卷积拥有更少的参数量,只有后者的 2∗3∗3/5∗5=0.72。但是起到的效果是一样的,两个 3×3 的卷积层串联相当于一个 5×5 的卷积层,感受野的大小都是 5×5,即 1 个像素会跟周围 5×5 的像素产生关联。把下图当成动态图看,很容易看到两个 3×3 卷积层堆叠(没有空间池化)有 5×5 的有效感受野。
(2). 更多的非线性变换。2 个 3×3 卷积层拥有比 1 个 5×5 卷积层更多的非线性变换(前者可以使用两次 ReLU 激活函数,而后者只有一次),使得卷积神经网络对特征的学习能力更强。
paper中给出的相关解释:三个这样的层具有 7×7 的有效感受野。那么我们获得了什么?例如通过使用三个 3×3 卷积层的堆叠来替换单个 7×7 层。首先,我们结合了三个非线性修正层,而不是单一的,这使得决策函数更具判别性。其次,我们减少参数的数量:假设三层 3×3 卷积堆叠的输入和输出有 C 个通道,堆叠卷积层的参数为 3×(3×3C) = 27C
个权重;同时,单个 7×7 卷积层将需要 7×7×C = 49C
个参数,即参数多 81%。这可以看作是对 7×7 卷积滤波器进行正则化,迫使它们通过 3×3 滤波器(在它们之间注入非线性)进行分解。
此回答可以参考 TensorFlow 实战 p110,网上很多回答都说的不全。
Sigmoid
函数公式如下:
σ ( x ) = 1 1 + e x p ( − x ) \sigma (x)=\frac{1}{1+exp(-x)} σ(x)=1+exp(−x)1
ReLU激活函数公式如下:
f ( x ) = m a x ( x , 0 ) = { x , if x ≥ 0 0 , if x < 0 f(x) = max(x, 0) = \begin{cases} x, & \text{if $x$ $\geq$ 0} \\ 0, & \text{if $x$ < 0} \end{cases} f(x)=max(x,0)={x,0,if x ≥ 0if x < 0
ReLU 的输出要么是 0, 要么是输入本身。虽然方程简单,但实际上效果更好。在网上看了很多版本的解释,有从程序实例分析也有从数学上分析,我找了个相对比较直白的回答,如下:
ReLU
函数计算简单,可以减少很多计算量。反向传播求误差梯度时,涉及除法,计算量相对较大,采用 ReLU
激活函数,可以节省很多计算量;sigmoid
函数反向传播时,很容易就会出现梯度消失问题(在sigmoid接近饱和区时,变换太缓慢,导数趋于 0,这种情况会造成信息丢失),从而无法完成深层网络的训练。ReLU
会使一部分神经元的输出为 0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。sigmoid
型函数,ReLU
函数有助于随机梯度下降方法收敛。ReLU为什么比Sigmoid效果好
权值(权重)共享这个词是由LeNet5模型提出来的。以CNN为例,在对一张图偏进行卷积的过程中,使用的是同一个卷积核的参数。
比如一个3×3×1的卷积核,这个卷积核内9个的参数被整张图共享,而不会因为图像内位置的不同而改变卷积核内的权系数。说的再直白一些,就是用一个卷积核不改变其内权系数的情况下卷积处理整张图片(当然CNN中每一层不会只有一个卷积核的,这样说只是为了方便解释而已)。
如何理解CNN中的权值共享
使用预训练模型的好处,在于利用训练好的SOTA模型权重去做特征提取,可以节省我们训练模型和调参的时间。
至于为什么只微调最后几层神经网络权重,是因为:
(1). CNN中更靠近底部的层(定义模型时先添加到模型中的层)编码的是更加通用的可复用特征,而更靠近顶部的层(最后添加到模型中的层)编码的是更专业业化的特征。微调这些更专业化的特征更加有用,它更代表了新数据集上的有用特征。
(2). 训练的参数越多,过拟合的风险越大。很多SOTA模型拥有超过千万的参数,在一个不大的数据集上训练这么多参数是有过拟合风险的,除非你的数据集像Imagenet那样大。
Python深度学习p127.
dropout
效果跟bagging
效果类似(bagging是减少方差variance,而boosting是减少偏差bias)以 标准神经网络为例,正常的流程是:我们首先把输入数据x通过网络前向传播,然后把误差反向传播一决定如何更新参数让网络进行学习。使用dropout之后,过程变成如下:
1,首先随机(临时)删掉网络中一半的隐藏神经元,输入输出神经元保持不变(图3中虚线为部分临时被删除的神经元);
2,然后把输入x通过修改后的网络进行前向传播计算,然后把得到的损失结果通过修改的网络反向传播。一小批训练样本执行完这个过程后,在没有被删除的神经元上按照随机梯度下降法更新对应的参数(w,b);
3,然后重复这一过程:
(1). 在训练模型阶段
不可避免的,在训练网络中的每个单元都要添加一道概率流程,标准网络和带有dropout网络的比较图如下所示:
(2). 在测试模型阶段
预测模型的时候,输入是当前输入,每个神经单元的权重参数要乘以概率p。
input 的 dropout 概率推荐是 0.8, hidden layer 推荐是0.5, 但是也可以在一定的区间上取值。(All dropout nets use p = 0.5 for hidden units and p = 0.8 for input units.)
方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。它通过计算和统计图像局部区域的梯度方向直方图来构成特征。在深度学习取得成功之前,Hog特征结合SVM分类器被广泛应用于图像识别中,在行人检测中获得了较大的成功。
HOG
的核心思想是所检测的局部物体外形能够被光强梯度或边缘方向的分布所描述。通过将整幅图像分割成小的连接区域(称为cells),每个 cell
生成一个方向梯度直方图或者 cell 中pixel 的边缘方向,这些直方图的组合可表示(所检测目标的目标)描述子。
为改善准确率,局部直方图可以通过计算图像中一个较大区域(称为block)的光强作为 measure
被对比标准化,然后用这个值(measure)归一化这个 block 中的所有 cells 。这个归一化过程完成了更好的照射/阴影不变性。与其他描述子相比,HOG 得到的描述子保持了几何和光学转化不变性(除非物体方向改变)。因此HOG描述子尤其适合人的检测。
HOG 特征提取方法就是将一个image:
cells
(2*2)
总结:颜色空间归一化——>梯度计算——>梯度方向直方图——>重叠块直方图归一化———>HOG特征
HOG特征检测-简述
激活函数也称非线性映射函数,常见的激活函数有:ReLU
函数、sigmoid
函数、tanh
函数等,其计算公式如下:
Sigmoid
激活函数,可用作分类任务的输出层。可以看出经过sigmoid激活函数后,模型输出的值域被压缩到 0到1
区间(这和概率的取值范围一致),这正是分类任务中sigmoid很受欢迎的原因。tanh(x)
型函数是在 sigmoid
型函数基础上为解决均值问题提出的激活函数。tanh 的形状和 sigmoid 类似,只不过tanh将“挤压”输入至区间(-1, 1)。至于梯度,它有一个大得多的峰值1.0(同样位于z = 0处),但它下降得更快,当|x|的值到达 3 时就已经接近零了。这是所谓梯度消失(vanishing gradients)问题背后的原因,会导致网络的训练进展变慢。ReLU
处理了sigmoid、tanh中常见的梯度消失问题,同时也是计算梯度最快的激励函数。但是,ReLU函数也有自身缺陷,即在 x < 0 时,梯度便为 y。换句话说,对于小于 y 的这部分卷积结果响应,它们一旦变为负值将再无法影响网络训练——这种现象被称作“死区"。参考 神经网络模型复杂度分析 文章。
判断一个输出向量和期望的向量有多接近,交叉熵(cross entroy
)是常用的评判方法之一。交叉熵刻画了两个概率分布之间的距离,是分类问题中使用比较广泛的一种损失函数。给定两个概率分布 p
和 q
,通过 q
来表示 p
的交叉熵公式为: H ( p , q ) = − ∑ p ( x ) l o g q ( x ) H(p,q)=−∑p(x)logq(x) H(p,q)=−∑p(x)logq(x)
常用数据增强方法:
Fliplr,Flipud
。不同于旋转180度,这是类似镜面的翻折,跟人在镜子中的映射类似,常用水平、上下镜面翻转。rotate
。顺时针/逆时针旋转,最好旋转 90-180
度,否则会出现边缘缺失或者超出问题,如旋转 45
度。zoom
。图像可以被放大或缩小,imgaug库可用Scal函数实现。crop
。一般叫随机裁剪,操作步骤是:随机从图像中选择一部分,然后降这部分图像裁剪出来,然后调整为原图像的大小。根本上理解,图像crop就是指从图像中移除不需要的信息,只保留需要的部分translation
。平移是将图像沿着x或者y方向(或者两个方向)移动。我们在平移的时候需对背景进行假设,比如说假设为黑色等等,因为平移的时候有一部分图像是空的,由于图片中的物体可能出现在任意的位置,所以说平移增强方法十分有用。Affine
。包含:平移(Translation
)、旋转(Rotation
)、放缩(zoom
)、错切(shear
)。imgaug
图像增强库使用 GaussianBlur
函数。Sharpen
。imgaug库使用Sharpen函数。数据增强分两类,一类是离线增强,一类是在线增强:
深度学习中的数据增强
faster rcnn
将 roi pooling
替换为 roi align
效果有所提升。
RPN
生成的 ROI
区域大小是对应与输入图像大小(每个roi区域大小各不相同),为了能够共享权重,所以需要将这些 ROI
映射回特征图上,并固定大小。ROI Pooling操作过程如下图:
ROI Pooling
具体操作如下:
Conv layers
使用的是 VGG16
,feat_stride=32
(即表示,经过网络层后图片缩小为原图的 1/32),原图 800 × 800 800\times 800 800×800,最后一层特征图 feature map
大小: 25 × 25 25\times 25 25×25region proposal
,大小为 665 × 665 665\times 665 665×665,这样,映射到特征图中的大小: 665 / 32 = 20.78 665/32=20.78 665/32=20.78,即大小为 20.78 × 20.78 20.78\times 20.78 20.78×20.78,源码中,在计算的时候会进行取整操作,于是,进行所谓的第一次量化,即映射的特征图大小为20*20;pooled_w=7、pooled_h=7
,即 pooling
后固定成 7 × 7 7\times 7 7×7 大小的特征图,所以,将上面在 feature map
上映射的 20 × 20 20\times 20 20×20 的 region proposal
划分成 49
个同等大小的小区域,每个小区域的大小 20 / 7 = 2.86 20/7=2.86 20/7=2.86,,即 2.86 × 2.86 2.86\times 2.86 2.86×2.86,此时,进行第二次量化,故小区域大小变成 2 × 2 2\times 2 2×2;feature map
。所以,通过上面可以看出,经过两次量化,即将浮点数取整,原本在特征图上映射的 20 × 20 20\times 20 20×20 大小的 region proposal
,偏差成大小为 7 × 7 7\times 7 7×7 的,这样的像素偏差势必会对后层的回归定位产生影响。所以,产生了后面的替代方案: RoiAlign
。
ROI Align
的工作原理如下图。
ROI Align
是在 Mask RCNN
中使用以便使生成的候选框region proposal
映射产生固定大小的 feature map
时提出的,根据上图,有着类似的映射:
Conv layers
使用的是 VGG16
,feat_stride=32
(即表示,经过网络层后图片缩小为原图的1/32),原图 800 × 800 800\times 800 800×800,最后一层特征图feature map大小: 25 ∗ × 25 25*\times 25 25∗×25;RoiPooling
那样就行取整操作,保留浮点数;pooled_w=7,pooled_h=7
,即 pooling
后固定成 7 × 7 7\times 7 7×7 大小的特征图,所以,将在 feature map
上映射的 20.78 × 20.78 20.78\times 20.78 20.78×20.78 的 region proposal
划分成 49 个同等大小的小区域,每个小区域的大小 20.78 / 7 = 2.97 20.78/7=2.97 20.78/7=2.97,即 2.97 × 2.97 2.97\times 2.97 2.97×2.97;4
,即表示,对于每个 2.97 × 2.97 2.97\times 2.97 2.97×2.97 的小区域,平分四份,每一份取其中心点位置,而中心点位置的像素,采用双线性插值法进行计算,这样,就会得到四个点的像素值,如下图:‘×’
的像素值是通过双线性插值算法计算得到的。最后,取四个像素值中最大值作为这个小区域(即: 2.97 × 2.97 2.97\times 2.97 2.97×2.97 大小的区域)的像素值,如此类推,同样是 49
个小区域得到 49
个像素值,组成 7 × 7 7\times 7 7×7 大小的 feature map
。
总结:知道了 RoiPooling
和 RoiAlign
实现原理,在以后的项目中可以根据实际情况进行方案的选择;对于检测图片中大目标物体时,两种方案的差别不大,而如果是图片中有较多小目标物体需要检测,则优先选择 RoiAlign
,更精准些。
RoIPooling、RoIAlign笔记
为了解决正负样本不平衡的问题,我们通常会在交叉熵损失的前面加上一个参数 α \alpha α ,即:
C E = { − α l o g ( p ) , i f y = 1 − ( 1 − α ) l o g ( 1 − p ) , i f y = 0 CE = \left\{\begin{matrix} -\alpha log(p), & if \quad y=1\\ -(1-\alpha)log(1-p), & if\quad y=0 \end{matrix}\right. CE={−αlog(p),−(1−α)log(1−p),ify=1ify=0
尽管 α \alpha α 平衡了正负样本,但对难易样本的不平衡没有任何帮助。因此,Focal Loss
被提出来了,即:
C E = { − α ( 1 − p ) γ l o g ( p ) , i f y = 1 − ( 1 − α ) p γ l o g ( 1 − p ) , i f y = 0 CE = \left\{\begin{matrix} -\alpha (1-p)^\gamma log(p), & if \quad y=1\\ -(1-\alpha) p^\gamma log(1-p), & if\quad y=0 \end{matrix}\right. CE={−α(1−p)γlog(p),−(1−α)pγlog(1−p),ify=1ify=0
实验表明 γ \gamma γ 取 2
, α \alpha α 取 0.25
的时候效果最佳。
Focal loss
成功地解决了在单阶段目标检测时,正负样本区域极不平衡而目标检测 loss
易被大批量负样本所左右的问题。RetinaNet
达到更高的精度的原因不是网络结构的创新,而是损失函数的创新!为了使得最后面的两个全连接层能够共享 conv layers(VGG/ResNet)
权重。在所有的 RoIs
都被 pooling
成(512×7×7)的feature map
后,将它 reshape
成一个一维的向量,就可以利用 VGG16
的预训练的权重来初始化前两层全连接。
s o f t m a x ( y ) i = e y i ∑ j = 1 n e y j softmax(y)_{i} = \frac{e^{y_{i}}}{\sum_{j=1}^{n}e^{y_j}} softmax(y)i=∑j=1neyjeyi
上采样大致被总结成了三个类别:
nearest
)、双线性插值算法(bilinear
)、双三次插值算法(bicubic
)等,这是传统图像处理方法。Conv2dTranspose2d
等)Unpooling
的方法(简单的补零或者扩充操作)计算效果:最近邻插值算法 < 双线性插值 < 双三次插值。计算速度:最近邻插值算法 > 双线性插值 > 双三次插值
知名的有TensorFlow Lite、小米MACE、腾讯的 ncnn 等,目前都没有用过。
和防止模型过拟合的方法类似,另外还有模型融合方法。
最后的 scale and shift
操作则是为了让因训练所需而“刻意”加入的BN能够有可能还原最初的输入。不加也可以。
参考知乎文章 N折交叉验证的作用(如何使用交叉验证),我的理解之后补充。
NMS全称是非极大值抑制,顾名思义就是抑制不是极大值的元素。在目标检测任务中,通常在解析模型输出的预测框时,预测目标框会非常的多,其中有很多重复的框定位到了同一个目标,NMS 的作用就是用来除去这些重复框,从而获得真正的目标框。而 NMS 的过程则用到了 IOU,IOU 是一种用于衡量真实和预测之间相关度的标准,相关度越高,该值就越高。IOU 的计算是两个区域重叠的部分除以两个区域的集合部分,简单的来说就是交集除以并集。
在 NMS 中,首先对预测框的置信度进行排序,依次取置信度最大的预测框与后面的框进行 IOU 比较,当 IOU 大于某个阈值时,可以认为两个预测框框到了同一个目标,而置信度较低的那个将会被剔除,依次进行比较,最终得到所有的预测框。