《Wide Residual Networks》
宽度的解耦意义
WRN-n-k denotes a residual network that has a total number of convolutional layers n and a widening factor k,k=2,N=2。
深度残差网络被证明能够扩展到数千层并且仍然具有改进的性能。然而,每提高== 1%== 的准确率都会使层数增加近一倍,因此训练非常深的残差网络存在特征重用减少的问题,这使得这些网络的训练速度非常慢。为了解决这些问题,在本文中,我们对 ResNet 块的架构进行了详细的实验研究,在此基础上我们提出了一种新颖的架构,我们减少了深度和增加残差网络的宽度。我们将由此产生的网络结构称为宽残差网络 (WRN),并表明它们远远优于常用的薄和非常深的对应物。例如,我们证明,即使是简单的 16 层深宽残差网络在准确性和效率方面也优于所有以前的深度残差网络,包括千层深网络,在 CIFAR、SVHN、COCO、和 ImageNet 的重大改进。我们的代码和模型可在原文附带的代码
在这项工作中,我们试图进行超出上述各点的实验研究。通过这样做,我们的目标是探索一组更丰富的 ResNet 块网络架构,并彻底检查除激活顺序之外的其他几个不同方面如何影响性能。正如我们在下面解释的那样,这种对架构的探索导致了新的有趣发现,对残差网络具有重要的实际意义。
残差网络中的宽度与深度。浅层网络与深层网络的问题在机器学习 [ 2 , 18 ] 中已经讨论了很长时间,其中指向电路复杂性理论文献的指针表明,浅层电路比深层电路需要指数级更多的组件。残差网络的作者试图让它们尽可能薄,以增加它们的深度和减少参数,甚至引入了一个“瓶颈”块,使 ResNet 块更薄。
可能只有少数块学习有用的表示,或者许多块共享很少对最终目标贡献很小的信息。这个问题在[ 28 ]中被表述为减少特征重用。[ 14 ]的作者试图通过在训练期间随机禁用残差块的想法来解决这个问题。这种方法可以看作是 dropout 的一个特例 [ 27],其中每个残差块都有一个恒等标量权重,在该权重上应用了 dropout。这种方法的有效性证明了上述假设。
在 ResNet 块中使用 dropout。Dropout首先在[ 27 ]中被引入,然后被许多成功的架构采用,如[ 16 , 26 ]等。它主要应用于具有大量参数的顶层,以防止特征自适应和过度拟合。然后主要被批量归一化所取代 [ 15] 这是一种通过将神经网络激活归一化为具有特定分布来减少内部协变量偏移的技术。它还可以用作正则化器,作者通过实验表明,具有批量归一化的网络比具有 dropout 的网络具有更好的准确性。在我们的例子中,随着残差块的扩大导致参数数量的增加,我们研究了 dropout 的影响,以规范训练并防止过度拟合。以前,残差网络中的 dropout 在 [ 13] 在块的标识部分插入了 dropout,作者展示了它的负面影响。相反,我们在这里认为应该在卷积层之间插入 dropout。宽残差网络的实验结果表明,这导致了一致的收益,甚至产生了新的最先进的结果(例如,具有 dropout 的 16 层深宽残差网络在 SVHN 上实现了 1.64% 的错误)。
图 1 : 论 文 中 使 用 的 各 种 残 差 块 。 批 量 归 一 化 和 R e L U 在 每 个 卷 积 之 前 ( 为 清 楚 起 见 省 略 ) 且 残 差 块 中 批 量 归 一 化 、 激 活 和 卷 积 的 顺 序 从 c o n v − B N − R e L U 更 改 为 B N − R e L U − c o n v 。 由 于 后 者 被 证 明 训 练 速 度 更 快 并 取 得 更 好 的 结 果 在 深 度 残 差 网 络 中 利 用 d r o p o u t 的 新 方 法 , 在 训 练 过 程 中 适 当 地 对 其 进 行 正 则 化 并 防 止 过 度 拟 合 。 图 1:论文中使用的各种残差块。批量归一化和 ReLU 在每个卷积之前(为清楚起见省略)\\ 且残差块中批量归一化、激活和卷积的顺序\\从conv - BN - ReLU更改为BN - ReLU - conv。由于后者被证明训练速度更快并取得更好的结果\\ 在深度残差网络中利用 dropout 的新方法,在训练过程中适当地对其进行正则化并防止过度拟合。 图1:论文中使用的各种残差块。批量归一化和ReLU在每个卷积之前(为清楚起见省略)且残差块中批量归一化、激活和卷积的顺序从conv−BN−ReLU更改为BN−ReLU−conv。由于后者被证明训练速度更快并取得更好的结果在深度残差网络中利用dropout的新方法,在训练过程中适当地对其进行正则化并防止过度拟合。
深 度 因 子 l 和 宽 度 因 子 k 深度因子l和宽度因子k 深度因子l和宽度因子k
表 1 : 宽 残 差 网 络 的 结 构 。 网 络 宽 度 由 因 子 k 决 定 。 原 始 架 构 [ 13 ] 等 价 于 k = 1 。 卷 积 组 显 示 在 括 号 中 , 其 中 N 是 组 中 的 块 数 , 下 采 样 由 第 一 层 执 行 , 在 c o n v 3 和 c o n v 4 组 中 。 为 清 除 而 省 略 最 终 分 类 层 。 在 所 示 的 特 定 示 例 中 , 网 络 使 用 类 型 B ( 3 , 3 ) 的 R e s N e t 块 。 表 1:宽残差网络的结构。网络宽度由因子k决定。原始架构 [ 13 ] 等价于k = 1。\\卷积组显示在括号中,其中N是组中的块数,\\ 下采样由第一层执行,\\ 在conv3和conv4组中。为清除而省略最终分类层。\\ 在所示的特定示例中,网络使用类型B ( 3 , 3 )的 ResNet 块。 表1:宽残差网络的结构。网络宽度由因子k决定。原始架构[13]等价于k=1。卷积组显示在括号中,其中N是组中的块数,下采样由第一层执行,在conv3和conv4组中。为清除而省略最终分类层。在所示的特定示例中,网络使用类型B(3,3)的ResNet块。
我们的残差网络的一般结构如表1 所示:它由一个初始卷积层conv1和 3 组(每组大小为N)的残差块conv2、conv3和conv4 组成,然后是平均池化层和最终分类层。 .在我们所有的实验中,conv1的大小都是固定的,而引入的加宽因子k缩放了三组conv2-4 中残差块的宽度。
class BasicBlock(nn.Module):
def forward(self, x):
if not self.equalInOut:# 输入输出chennel不相等
x = self.relu1(self.bn1(x))
else: # 输入输出chennel相等
out = self.relu1(self.bn1(x))
out = self.relu2(self.bn2(self.conv1(out if self.equalInOut else x)))
out = self.conv2(out)
return torch.add(x if self.equalInOut else self.convShortcut(x), out)
class NetworkBlock(nn.Module):
layers = []# 存放残差块
for i in range(int(nb_layers)):
layers.append()##生成conv Block
return nn.Sequential(*layers)# *参数代表此处接受任意多个参数,这些参数将以数组形式保存
def forward(self, x):
return self.layer(x)
class WideResNet(nn.Module):
block = BasicBlock
def forward(self, x):
out = self.conv1(x) # nn.Conv2d(3, nChannels[0], kernel_size=3, stride=1,padding=1, bias=False)
out = self.block1(out) # NetworkBlock(n, nChannels[0], nChannels[1], block, 1, dropRate)
out = self.block2(out) # NetworkBlock(n, nChannels[1], nChannels[2], block, 2, dropRate)
out = self.block3(out) # NetworkBlock(n, nChannels[2], nChannels[3], block, 2, dropRate)
out = F.avg_pool2d(out, 8)
out_feat = out.view(-1, self.nChannels)
out = F.normalize(out_feat, dim=1, p=2)
out = torch.abs(self.fc(out))
return out, out_feat
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
class BasicBlock(nn.Module):
def __init__(self, in_planes, out_planes, stride, dropRate=0.0):
super(BasicBlock, self).__init__()
self.bn1 = nn.BatchNorm2d(in_planes)
self.relu1 = nn.ReLU(inplace=True)
self.conv1 = nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_planes)
self.relu2 = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(out_planes, out_planes, kernel_size=3, stride=1,
padding=1, bias=False)
self.droprate = dropRate
self.equalInOut = (in_planes == out_planes)
self.convShortcut = (not self.equalInOut) and nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride,
padding=0, bias=False) or None
def forward(self, x):
if not self.equalInOut:
x = self.relu1(self.bn1(x))
else:
out = self.relu1(self.bn1(x))
out = self.relu2(self.bn2(self.conv1(out if self.equalInOut else x)))
if self.droprate > 0:
out = F.dropout(out, p=self.droprate, training=True)
out = self.conv2(out)
return torch.add(x if self.equalInOut else self.convShortcut(x), out)
class NetworkBlock(nn.Module):
def __init__(self, nb_layers, in_planes, out_planes, block, stride, dropRate=0.0):
super(NetworkBlock, self).__init__()
self.layer = self._make_layer(block, in_planes, out_planes, nb_layers, stride, dropRate)
def _make_layer(self, block, in_planes, out_planes, nb_layers, stride, dropRate):
layers = []
for i in range(int(nb_layers)):
layers.append(block(i == 0 and in_planes or out_planes, out_planes, i == 0 and stride or 1, dropRate))
return nn.Sequential(*layers)
def forward(self, x):
return self.layer(x)
class WideResNet(nn.Module):
def __init__(self, depth, num_classes, widen_factor=1, dropRate=0.0):
super(WideResNet, self).__init__()
nChannels = [16, 16*widen_factor, 32*widen_factor, 64*widen_factor, 64*widen_factor*10]
assert((depth - 4) % 6 == 0)
n = (depth - 4) / 6
block = BasicBlock
# 1st conv before any network block
self.conv1 = nn.Conv2d(3, nChannels[0], kernel_size=3, stride=1,
padding=1, bias=False)
self.dropout = torch.nn.Dropout(0.3)
# 1st block
self.block1 = NetworkBlock(n, nChannels[0], nChannels[1], block, 1, dropRate)
# 2nd block
self.block2 = NetworkBlock(n, nChannels[1], nChannels[2], block, 2, dropRate)
# 3rd block
self.block3 = NetworkBlock(n, nChannels[2], nChannels[3], block, 2, dropRate)
# global average pooling and classifier
self.bn1 = nn.BatchNorm2d(nChannels[3])
self.relu = nn.ReLU(inplace=True)
self.ID_mat = torch.eye(num_classes).cuda()
self.fc = nn.Linear(nChannels[3], num_classes, bias=False)
self.fc.weight.requires_grad = False # Freezing the weights during training
self.nChannels = nChannels[3]
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
nn.init.orthogonal(m.weight.data) # Initializing with orthogonal rows
def forward(self, x):
out = self.conv1(x)
out = self.block1(out)
out = self.block2(out)
out = self.block3(out)
# out = self.relu(self.bn1(out))
# out = self.relu(out)
out = F.avg_pool2d(out, 8)
out_feat = out.view(-1, self.nChannels)
out = F.normalize(out_feat, dim=1, p=2)
out = torch.abs(self.fc(out))
return out, out_feat
model = WideResNet(args.layers, args.dataset == 'cifar10' and 10 or 100,
args.widen_factor, dropRate=args.droprate)
model = WideResNet(28, args.cifar10,10, dropRate=0.3)
添加链接描述