https://zhuanlan.zhihu.com/p/54289848
opencv
自从深度神经网络在ImageNet大放异彩之后,后来问世的深度神经网络就朝着网络层数越来越深的方向发展。直觉上我们不难得出结论:增加网络深度后,网络可以进行更加复杂的特征提取,因此更深的模型可以取得更好的结果。
但事实并非如此,人们发现随着网络深度的增加,模型精度并不总是提升,并且这个问题显然不是由过拟合(overfitting)造成的,因为网络加深后不仅测试误差变高了,它的训练误差竟然也变高了。作者提出,这可能是因为更深的网络会伴随梯度消失/爆炸问题,从而阻碍网络的收敛。作者将这种加深网络深度但网络性能却下降的现象称为退化问题(degradation problem)。
Is learning better networks as easy as stacking more layers? An obstacle to answering this question was the notorious problem of vanishing/exploding gradients [1, 9], which hamper convergence from the beginning.
Unexpectedly, such degradation is not caused by overfitting, and adding more layers to a suitably deep model leads to higher training error.
文中给出的实验结果进一步描述了这种退化问题:当传统神经网络的层数从20增加为56时,网络的训练误差和测试误差均出现了明显的增长,也就是说,网络的性能随着深度的增加出现了明显的退化。ResNet就是为了解决这种退化问题而诞生的。
图1 20层与56层传统神经网络在CIFAR上的训练误差和测试误差
随着网络层数的增加,梯度爆炸和梯度消失问题严重制约了神经网络的性能,研究人员通过提出包括Batch normalization在内的方法,已经一定程度上缓解了这个问题,但依然不足以满足需求。
This problem,however, has been largely addressed by normalized initialization [23, 9, 37, 13] and intermediate normalization layers[16], which enable networks with tens of layers to start converging for stochastic gradient descent (SGD) with backpropagation [22].
作者想到了构建恒等映射(Identity mapping)来解决这个问题,问题解决的标志是:增加网络层数,但训练误差不增加。为什么是恒等映射呢,我是这样子想的:20层的网络是56层网络的一个子集,56层网络的解空间包含着20层网络的解空间。如果我们将56层网络的最后36层全部短接,这些层进来是什么出来也是什么(也就是做一个恒等映射),那这个56层网络不就等效于20层网络了吗,至少效果不会相比原先的20层网络差吧。同样是56层网络,不引入恒等映射为什么就不行呢?因为梯度消失现象使得网络难以训练,虽然网络的深度加深了,但是实际上无法有效训练网络,训练不充分的网络不但无法提升性能,甚至降低了性能。
There exists a solution by construction to the deeper model: the added layers are identity mapping, and the other layers are copied from the learned shallower model. The existence of this constructed solution indicates that a deeper model should produce no higher training error than its shallower counterpart.
图2 残差学习基本单元
那怎么构建恒等映射呢?简单地说,原先的网络输入x,希望输出H(x)。现在我们改一改,我们令H(x)=F(x)+x,那么我们的网络就只需要学习输出一个残差F(x)=H(x)-x。作者提出,学习残差F(x)=H(x)-x会比直接学习原始特征H(x)简单的多。
https://zhuanlan.zhihu.com/p/31852747
从直观上看残差学习需要学习的内容少,因为残差一般会比较小,学习难度小点。不过我们可以从数学的角度来分析这个问题,首先残差单元可以表示为:
其中 和 分别表示的是第 个残差单元的输入和输出,注意每个残差单元一般包含多层结构。 是残差函数,表示学习到的残差,而 表示恒等映射, 是ReLU激活函数。基于上式,我们求得从浅层 到深层 的学习特征为:
利用链式规则,可以求得反向过程的梯度:
式子的第一个因子 表示的损失函数到达 的梯度,小括号中的1表明短路机制可以无损地传播梯度,而另外一项残差梯度则需要经过带有weights的层,梯度不是直接传递过来的。残差梯度不会那么巧全为-1,而且就算其比较小,有1的存在也不会导致梯度消失。所以残差学习会更容易。要注意上面的推导并不是严格的证明。
下面的代码使用的是pytorch写的,可以做一下参考
class Res2d(nn.Module):
def __init__(self, n_in, n_out, stride = 1):
super(PostRes2d, self).__init__()
self.conv1 = nn.Conv2d(n_in, n_out, kernel_size = 3, stride = stride, padding = 1)
self.bn1 = nn.BatchNorm2d(n_out)
self.relu = nn.ReLU(inplace = True)
self.conv2 = nn.Conv2d(n_out, n_out, kernel_size = 3, padding = 1)
self.bn2 = nn.BatchNorm2d(n_out)
if stride != 1 or n_out != n_in:
self.shortcut = nn.Sequential(
nn.Conv2d(n_in, n_out, kernel_size = 1, stride = stride),
nn.BatchNorm2d(n_out))
else:
self.shortcut = None
def forward(self, x):
residual = x
if self.shortcut is not None:
residual = self.shortcut(x)
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out += residual
out = self.relu(out)
return out
从Inception v1到Inception-ResNet,一文概览Inception家族的「奋斗史」
DenseNet的另一大特色是通过特征在channel上的连接来实现特征重用(feature reuse)。每个层都会与前面所有层在channel维度上连接(concat)在一起(这里各个层的特征图大小是相同的,后面会有说明),并作为下一层的输入。
1. 思想
2. 解决什么问题,有什么优势
3. 网络结构
https://blog.csdn.net/u014380165/article/details/75142664
https://zhuanlan.zhihu.com/p/37189203
文章介绍
这篇文章是CVPR2017的oral,非常厉害。文章提出的DenseNet(Dense Convolutional Network)主要还是和ResNet及Inception网络做对比,思想上有借鉴,但却是全新的结构,网络结构并不复杂,却非常有效!众所周知,最近一两年卷积神经网络提高效果的方向,要么深(比如ResNet,解决了网络深时候的梯度消失问题)要么宽(比如GoogleNet的Inception),而作者则是从feature入手,通过对feature的极致利用达到更好的效果和更少的参数。博主虽然看过的文章不算很多,但是看完这篇感觉心潮澎湃,就像当年看完ResNet那篇文章一样!
先列下DenseNet的几个优点,感受下它的强大:
1、减轻了vanishing-gradient(梯度消失)
2、加强了feature的传递
3、更有效地利用了feature
4、一定程度上较少了参数数量
相比ResNet,DenseNet提出了一个更激进的密集连接机制:即互相连接所有的层,具体来说就是每个层都会接受其前面所有层作为其额外的输入。图1为ResNet网络的连接机制,作为对比,图2为DenseNet的密集连接机制。可以看到,ResNet是每个层与前面的某层(一般是2~3层)短路连接在一起,连接方式是通过元素级相加。而在DenseNet中,每个层都会与前面所有层在channel维度上连接(concat)在一起(这里各个层的特征图大小是相同的,后面会有说明),并作为下一层的输入。对于一个 层的网络,DenseNet共包含 个连接,相比ResNet,这是一种密集连接。而且DenseNet是直接concat来自不同层的特征图,这可以实现特征重用,提升效率,这一特点是DenseNet与ResNet最主要的区别。
图1 ResNet网络的短路连接机制(其中+代表的是元素级相加操作) 图2 DenseNet网络的密集连接机制(其中c代表的是channel级连接操作)
如果用公式表示的话,传统的网络在 层的输出为:
而对于ResNet,增加了来自上一层输入的identity函数:
在DenseNet中,会连接前面所有层作为输入:
其中,上面的 代表是非线性转化函数(non-liear transformation),它是一个组合操作,其可能包括一系列的BN(Batch Normalization),ReLU,Pooling及Conv操作。注意这里 层与 层之间可能实际上包含多个卷积层。
DenseNet的前向过程如图3所示,可以更直观地理解其密集连接方式,比如 的输入不仅包括来自 的 ,还包括前面两层的 和 ,它们是在channel维度上连接在一起的。
CNN网络一般要经过Pooling或者stride>1的Conv来降低特征图的大小,而DenseNet的密集连接方式需要特征图大小保持一致。为了解决这个问题,DenseNet网络中使用DenseBlock+Transition的结构,其中DenseBlock是包含很多层的模块,每个层的特征图大小相同,层与层之间采用密集连接方式。而Transition模块是连接两个相邻的DenseBlock,并且通过Pooling使特征图大小降低。图4给出了DenseNet的网路结构,它共包含4个DenseBlock,各个DenseBlock之间通过Transition连接在一起。
图4 使用DenseBlock+Transition的DenseNet网络
如前所示,DenseNet的网络结构主要由DenseBlock和Transition组成,如图5所示。下面具体介绍网络的具体实现细节。
图6 DenseNet的网络结构
在DenseBlock中,各个层的特征图大小一致,可以在channel维度上连接。DenseBlock中的非线性组合函数 采用的是BN+ReLU+3x3 Conv的结构,如图6所示。另外值得注意的一点是,与ResNet不同,所有DenseBlock中各个层卷积之后均输出 个特征图,即得到的特征图的channel数为 ,或者说采用 个卷积核。 在DenseNet称为growth rate,这是一个超参数。一般情况下使用较小的 (比如12),就可以得到较佳的性能。假定输入层的特征图的channel数为 ,那么 层输入的channel数为 ,因此随着层数增加,尽管 设定得较小,DenseBlock的输入会非常多,不过这是由于特征重用所造成的,每个层仅有 个特征是自己独有的。
图6 DenseBlock中的非线性转换结构
由于后面层的输入会非常大,DenseBlock内部可以采用bottleneck层来减少计算量,主要是原有的结构中增加1x1 Conv,如图7所示,即BN+ReLU+1x1 Conv+BN+ReLU+3x3 Conv,称为DenseNet-B结构。其中1x1 Conv得到 个特征图它起到的作用是降低特征数量,从而提升计算效率。
图7 使用bottleneck层的DenseBlock结构
对于Transition层,它主要是连接两个相邻的DenseBlock,并且降低特征图大小。Transition层包括一个1x1的卷积和2x2的AvgPooling,结构为BN+ReLU+1x1 Conv+2x2 AvgPooling。另外,Transition层可以起到压缩模型的作用。假定Transition的上接DenseBlock得到的特征图channels数为 ,Transition层可以产生 个特征(通过卷积层),其中 是压缩系数(compression rate)。当 时,特征个数经过Transition层没有变化,即无压缩,而当压缩系数小于1时,这种结构称为DenseNet-C,文中使用 。对于使用bottleneck层的DenseBlock结构和压缩系数小于1的Transition组合结构称为DenseNet-BC。
DenseNet共在三个图像分类数据集(CIFAR,SVHN和ImageNet)上进行测试。对于前两个数据集,其输入图片大小为 ,所使用的DenseNet在进入第一个DenseBlock之前,首先进行进行一次3x3卷积(stride=1),卷积核数为16(对于DenseNet-BC为 )。DenseNet共包含三个DenseBlock,各个模块的特征图大小分别为 , 和 ,每个DenseBlock里面的层数相同。最后的DenseBlock之后是一个global AvgPooling层,然后送入一个softmax分类器。注意,在DenseNet中,所有的3x3卷积均采用padding=1的方式以保证特征图大小维持不变。对于基本的DenseNet,使用如下三种网络配置: , , 。而对于DenseNet-BC结构,使用如下三种网络配置: , , 。这里的 指的是网络总层数(网络深度),一般情况下,我们只把带有训练参数的层算入其中,而像Pooling这样的无参数层不纳入统计中,此外BN层尽管包含参数但是也不单独统计,而是可以计入它所附属的卷积层。对于普通的 网络,除去第一个卷积层、2个Transition中卷积层以及最后的Linear层,共剩余36层,均分到三个DenseBlock可知每个DenseBlock包含12层。其它的网络配置同样可以算出各个DenseBlock所含层数。
对于ImageNet数据集,图片输入大小为 ,网络结构采用包含4个DenseBlock的DenseNet-BC,其首先是一个stride=2的7x7卷积层(卷积核数为 ),然后是一个stride=2的3x3 MaxPooling层,后面才进入DenseBlock。ImageNet数据集所采用的网络配置如表1所示:
表1 ImageNet数据集上所采用的DenseNet结构
SeNet写的比较好的地址:
https://blog.csdn.net/xjz18298268521/article/details/79078551
而是采用了一种全新的「特征重标定」策略。具体来说,就是通过学习的方式来自动获取到每个特征通道的重要程度,然后依照这个重要程度去提升有用的特征并抑制对当前任务用处不大的特征。
论文通过显式地建模通道之间的相互依赖关系,自适应地重新校准通道的特征响应
SENet代码
from torch import nn
class SELayer(nn.Module):
def __init__(self, channel, reduction=16):
super(SELayer, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channel, channel // reduction, bias=False),
nn.ReLU(inplace=True),
nn.Linear(channel // reduction, channel, bias=False),
nn.Sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x).view(b, c)
y = self.fc(y).view(b, c, 1, 1)
return x * y.expand_as(x)
def conv3x3(in_planes, out_planes, stride=1):
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False)
class SEBasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None, reduction=16):
super(SEBasicBlock, self).__init__()
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = nn.BatchNorm2d(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes, 1)
self.bn2 = nn.BatchNorm2d(planes)
self.se = SELayer(planes, reduction)
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.se(out)
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out