在只有一个通道的情况下,“卷积核”(“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 后输出大小计算公式如下:\(\left \lfloor N = \frac{W-F+2P}{S}+1 \right \rfloor\),其中 \(\lfloor \rfloor\) 是向下取整符号,用于结果不是整数时进行向下取整(Pytorch 的 Conv2d 卷积函数的默认参数 ceil_mode = False,即默认向下取整, dilation = 1)。
W×W(默认输入尺寸为正方形)Filter 大小 F×FSPN×N2,特征图经反卷积(也叫转置卷积) keras-Conv2DTranspose(pytorch-ConvTranspose2d) 后得到的特征图大小计算方式:\(out = (in - 1) *s -2p + k\),还有另外一个写法:\(W = (N - 1)*S - 2P + F\),可由卷积输出大小计算公式反推得到。\(in\) 是输入大小, \(k\) 是卷积核大小, \(s\) 是滑动步长, padding 的像素数 \(p\),\(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\),这里公式表示的是除法结果向下取整再加 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 计算同理):
主要参数解释:
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\),这里表示的是向下取整再加 1。padding = same: 池化层输出的特征图大小为 \(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 同理):
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 的概率分布,公式如下:
交叉熵刻画了两个概率分布之间的距离,它是分类问题中使用比较广泛的一种损失函数,交叉熵一般会与 softmax 回归一起使用,公式如下:
0 或 1),如果该类别和样本的类别相同就是 1,否则是 0;从数学上来理解就是,为了让学到的模型分布更接近真实数据的分布,我们需要最小化模型数据分布与训练数据之间的 KL 散度,而因为训练数据的分布是固定的,因此最小化 KL 散度等价于最小化交叉熵,而且交叉熵计算更简单,所以机器/深度学习中常用交叉熵 cross-entroy 作为分类问题的损失函数。
Adam、AdaGrad、RMSProp优化算法具有自适应性。
感受野理解(Receptive Field)是指后一层神经元在前一层神经元的感受空间,也可以定义为卷积神经网络中每层的特征图(Feature Map)上的像素点在原始图像中映射的区域大小,即如下图所示:

注意:感受野在 CNN 中是呈指数级增加的。小卷积核(如 3*3)通过多层叠加可取得与大卷积核(如 7*7)同等规模的感受野,此外采用小卷积核有两个优势:
model capacity)和复杂度(model complexity)。计算感受野时,我们需要知道:
感受野的计算方式有两种:自底向上和自顶向下(top to down),这里只讲解后者。正常卷积(且不带 padding)感受野计算公式如下:
其中 \(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 批归一化xavierCNN 中的残差结构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 函数公式如下:
\(\sigma (x)=\frac{1}{1+exp(-x)}\)
ReLU激活函数公式如下:
ReLU 的输出要么是 0, 要么是输入本身。虽然方程简单,但实际上效果更好。在网上看了很多版本的解释,有从程序实例分析也有从数学上分析,我找了个相对比较直白的回答,如下:
ReLU 函数计算简单,可以减少很多计算量。反向传播求误差梯度时,涉及除法,计算量相对较大,采用 ReLU 激活函数,可以节省很多计算量;sigmoid 函数反向传播时,很容易就会出现梯度消失问题(在sigmoid接近饱和区时,变换太缓慢,导数趋于 0,这种情况会造成信息丢失),从而无法完成深层网络的训练。ReLU 会使一部分神经元的输出为 0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。sigmoid 型函数,ReLU 函数有助于随机梯度下降方法收敛。权值(权重)共享这个词是由 LeNet5 模型提出来的。以 CNN 为例,在对一张图偏进行卷积的过程中,使用的是同一个卷积核的参数。
比如一个 3×3×1 的卷积核,这个卷积核内 9 个的参数被整张图共享,而不会因为图像内位置的不同而改变卷积核内的权系数。说的再直白一些,就是用一个卷积核不改变其内权系数的情况下卷积处理整张图片(当然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特征
激活函数也称非线性映射函数,常见的激活函数有: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)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\times 800\),最后一层特征图 feature map 大小: \(25\times 25\)region proposal,大小为 \(665\times 665\),这样,映射到特征图中的大小:\(665/32=20.78\),即大小为 \(20.78\times 20.78\),源码中,在计算的时候会进行取整操作,于是,进行所谓的第一次量化,即映射的特征图大小为20*20;pooled_w=7、pooled_h=7,即 pooling 后固定成 \(7\times 7\) 大小的特征图,所以,将上面在 feature map上映射的 \(20\times 20\) 的 region proposal 划分成 49个同等大小的小区域,每个小区域的大小 \(20/7=2.86\),,即 \(2.86\times 2.86\),此时,进行第二次量化,故小区域大小变成 \(2\times 2\);feature map。所以,通过上面可以看出,经过两次量化,即将浮点数取整,原本在特征图上映射的 \(20\times 20\) 大小的 region proposal,偏差成大小为 \(7\times 7\) 的,这样的像素偏差势必会对后层的回归定位产生影响。所以,产生了后面的替代方案: RoiAlign。
ROI Align 的工作原理如下图。

ROI Align 是在 Mask RCNN 中使用以便使生成的候选框region proposal 映射产生固定大小的 feature map 时提出的,根据上图,有着类似的映射:
Conv layers 使用的是 VGG16,feat_stride=32 (即表示,经过网络层后图片缩小为原图的1/32),原图 \(800\times 800\),最后一层特征图feature map大小: \(25*\times 25\);RoiPooling 那样就行取整操作,保留浮点数;pooled_w=7,pooled_h=7,即 pooling 后固定成 \(7\times 7\) 大小的特征图,所以,将在 feature map 上映射的 \(20.78\times 20.78\) 的 region proposal 划分成 49 个同等大小的小区域,每个小区域的大小 \(20.78/7=2.97\),即 \(2.97\times 2.97\);4,即表示,对于每个 \(2.97\times 2.97\) 的小区域,平分四份,每一份取其中心点位置,而中心点位置的像素,采用双线性插值法进行计算,这样,就会得到四个点的像素值,如下图:
上图中,四个红色叉叉‘×’的像素值是通过双线性插值算法计算得到的。
最后,取四个像素值中最大值作为这个小区域(即:\(2.97\times 2.97\) 大小的区域)的像素值,如此类推,同样是 49 个小区域得到 49 个像素值,组成 \(7\times 7\) 大小的 feature map。
总结:知道了 RoiPooling 和 RoiAlign 实现原理,在以后的项目中可以根据实际情况进行方案的选择;对于检测图片中大目标物体时,两种方案的差别不大,而如果是图片中有较多小目标物体需要检测,则优先选择 RoiAlign,更精准些。
为了解决正负样本不平衡的问题,我们通常会在交叉熵损失的前面加上一个参数 \(\alpha\) ,即:
尽管 \(\alpha\) 平衡了正负样本,但对难易样本的不平衡没有任何帮助。因此,Focal Loss 被提出来了,即:
实验表明 \(\gamma\) 取 2, \(\alpha\) 取 0.25 的时候效果最佳。
Focal loss 成功地解决了在单阶段目标检测时,正负样本区域极不平衡而目标检测 loss 易被大批量负样本所左右的问题。RetinaNet 达到更高的精度的原因不是网络结构的创新,而是损失函数的创新!为了使得最后面的两个全连接层能够共享 conv layers(VGG/ResNet) 权重。在所有的 RoIs 都被 pooling 成(512×7×7)的feature map后,将它 reshape 成一个一维的向量,就可以利用 VGG16 的预训练的权重来初始化前两层全连接。

上采样大致被总结成了三个类别:
nearest)、双线性插值算法(bilinear)、双三次插值算法(bicubic)等,这是传统图像处理方法。Conv2dTranspose2d等)Unpooling 的方法(简单的补零或者扩充操作)计算效果:最近邻插值算法 < 双线性插值 < 双三次插值。计算速度:最近邻插值算法 > 双线性插值 > 双三次插值
知名的有TensorFlow Lite、小米MACE、腾讯的 ncnn 等,目前都没有用过。
和防止模型过拟合的方法类似,另外还有模型融合方法。
最后的 scale and shift 操作则是为了让因训练所需而“刻意”加入的BN能够有可能还原最初的输入。不加也可以。
参考知乎文章 N折交叉验证的作用(如何使用交叉验证),我的理解之后补充。
NMS全称是非极大值抑制,顾名思义就是抑制不是极大值的元素。在目标检测任务中,通常在解析模型输出的预测框时,预测目标框会非常的多,其中有很多重复的框定位到了同一个目标,NMS 的作用就是用来除去这些重复框,从而获得真正的目标框。而 NMS 的过程则用到了 IOU,IOU 是一种用于衡量真实和预测之间相关度的标准,相关度越高,该值就越高。IOU 的计算是两个区域重叠的部分除以两个区域的集合部分,简单的来说就是交集除以并集。
在 NMS 中,首先对预测框的置信度进行排序,依次取置信度最大的预测框与后面的框进行 IOU 比较,当 IOU 大于某个阈值时,可以认为两个预测框框到了同一个目标,而置信度较低的那个将会被剔除,依次进行比较,最终得到所有的预测框。
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
目录第1题连续问题分析:解法:第2题分组问题分析:解法:第3题间隔连续问题分析:解法:第4题打折日期交叉问题分析:解法:第5题同时在线问题分析:解法:第1题连续问题如下数据为蚂蚁森林中用户领取的减少碳排放量iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021-12-132310022021-12-144510012021-12-1423010022021-12-154510012021-12-1523.......找出连续3天及以上减少碳排放量在100以上的用户分析:遇到这类
我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or
如何学习ruby的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/
深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG
文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk=Var(yt)Cov(yt,yt−k)其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞
写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c