ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential((0): BasicBlock(...) (1): BasicBlock(...))
(layer2): Sequential((0): BasicBlock(...) (1): BasicBlock(...))
(layer3): Sequential((0): BasicBlock(...) (1): BasicBlock(...))
(layer4): Sequential((0): BasicBlock(...) (1): BasicBlock(...))
(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
(fc): Linear(in_features=512, out_features=1000, bias=True)
)
ResNet随着网络层不断的加深,模型的准确率先是不断的提高,达到最大值(准确率饱和),然后随着网络深度的继续增加,模型准确率毫无征兆的出现大幅度的降低。这个现象与“越深的网络准确率越高”的信念显然是矛盾的、冲突的。ResNet团队把这一现象称为“退化(Degradation)”。ResNet团队把退化现象归因为深层神经网络难以实现“恒等变换(y=x)”。
深度学习的关键特征在于网络层数更深、非线性转换(激活)、自动的特征提取和特征转换,其中,非线性转换是关键目标,它将数据映射到高纬空间以便于更好的完成“数据分类”。随着网络深度的不断增大,所引入的激活函数也越来越多,数据被映射到更加离散的空间,此时已经难以让数据回到原点(恒等变换)。
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
in_channels:输入通道数,out_channels:输出通道数。
kernel_size:卷积核的大小
H=W=5(5行5列):kernel_size is a singe number
H=3,W=5(3行5列):kernel_size is a turple(3,5)
stride:卷积核在图像窗口上每次平移的间隔,即所谓的步长。
padding:填充包括图像的上下左右。Pytorch不同于Tensorflow的地方在于,Tensorflow提供的是
padding
的模式,比如same、valid
,且不同模式对应了不同的输出图像尺寸计算公式。而Pytorch则需要手动输入。(1)valid padding:不进行任何处理,只使用原始图像,不允许卷积核超出原始图像边界
(2)same padding:进行填充,允许卷积核超出原始图像边界,并使得卷积后结果的大小与原来的一致
dilation,扩张操作:控制kernel点(卷积核点)的间距。空洞卷积的rate,代表传统卷积核的相邻之间插入rate-1个空洞数。当rate=1时,相当于传统的卷积核。
https://i-blog.csdnimg.cn/blog_migrate/07e5688b578c28b33ee11f781456c503.png
group:控制输入和输出之间的连接。in-channels和out-channels都必须能被group整除。
默认值为1, 也就是说默认输出输入的所有通道各为一组。 比如输入数据大小为90x100x100x32,通道数32,要经过一个3x3x48的卷积,group默认是1,就是全连接的卷积层。
如果group是2,那么对应要将输入的32个通道分成2个16的通道,将输出的48个通道分成2个24的通道。对输出的2个24的通道,第一个24通道与输入的第一个16通道进行全卷积,第二个24通道与输入的第二个16通道进行全卷积。
极端情况下,输入输出通道数相同,比如为24,group大小也为24,那么每个输出卷积核,只与输入的对应的通道进行卷积。
conv = nn.Conv2d(in_channels=6, out_channels=6, kernel_size=1, groups=1) conv.weight.data.size()
第一个值为out_channels的大小,第二个值为in_channels的大小,后面两个值为kernel_size
torch.Size([6, 6, 1, 1]),该卷积层需要6*6*1*1=36个参数,即需要6个6*1*1的卷积核。
6*H_in*W_in的输入整个乘以一个6*1*1的卷积核,得到输出的一个channel的值,即1*H_out*W_out。
conv = nn.Conv2d(in_channels=6, out_channels=6, kernel_size=1, groups=2) conv.weight.data.size()
torch.Size([6, 3, 1, 1]),第一个输出的3通道与输入的3通道进行全卷积,一个卷积核大小为3*1*1,有3个卷积核。
conv = nn.Conv2d(in_channels=6, out_channels=6, kernel_size=1, groups=3) conv.weight.data.size()
torch.Size([6, 2, 1, 1])
conv = nn.Conv2d(in_channels=6, out_channels=6, kernel_size=1, groups=6) conv.weight.data.size()
torch.Size([6, 1, 1, 1])
bias:即是否要添加偏置参数作为可学习参数的一个,默认为True。(一个channel中一个bias)
padding_mode:填充模式
'zeros'就是最常见的零填充,即在矩阵的高、宽两个维度上用0进行填充,填充时将在一个维度的两边都进行填充。
'reflect'是以矩阵边缘为对称轴,将矩阵中的元素对称的填充到最外围。
# 定义一个1*1卷积,设置填充模式为'reflect',在高和宽维度上两边各填充1个单位 In [58]: conv_reflect = torch.nn.Conv2d(1,1,1,1,padding=1,padding_mode='reflect',bias=False) # 将卷积核的权重设置为1,这样可使卷积后的输出即为填充后的输入矩阵 In [59]: conv_reflect.weight = torch.nn.Parameter(torch.ones(1,1,1,1)) # 进行卷积计算,并输出结果 In [60]: conv_reflect(x) Out[60]: tensor([[[[ 6., 5., 6., 7., 8., 7.], [ 2., 1., 2., 3., 4., 3.], [ 6., 5., 6., 7., 8., 7.], [10., 9., 10., 11., 12., 11.], [14., 13., 14., 15., 16., 15.], [10., 9., 10., 11., 12., 11.]]]], grad_fn=<ThnnConv2DBackward>)
'replicate'将矩阵的边缘复制并填充到矩阵的外围.
# 定义一个1*1卷积,设置填充模式为'replicate',在高和宽维度上两边各填充1个单位 In [61]: conv_reflect = torch.nn.Conv2d(1,1,1,1,padding=1,padding_mode='replicate',bias=False) # 将卷积核的权重设置为1,这样可使卷积后的输出即为填充后的输入矩阵 In [62]: conv_reflect.weight = torch.nn.Parameter(torch.ones(1,1,1,1)) # 进行卷积计算,并输出结果 In [63]: conv_replicate(x) Out[63]: tensor([[[[ 1., 1., 2., 3., 4., 4.], [ 1., 1., 2., 3., 4., 4.], [ 5., 5., 6., 7., 8., 8.], [ 9., 9., 10., 11., 12., 12.], [13., 13., 14., 15., 16., 16.], [13., 13., 14., 15., 16., 16.]]]], grad_fn=<ThnnConv2DBackward>)
'circular'就是循环的进行填充
# 定义一个1*1卷积,设置填充模式为'circular',在高和宽维度上两边各填充1个单位 In [64]: conv_reflect = torch.nn.Conv2d(1,1,1,1,padding=1,padding_mode='circular',bias=False) # 将卷积核的权重设置为1,这样可使卷积后的输出即为填充后的输入矩阵 In [65]: conv_reflect.weight = torch.nn.Parameter(torch.ones(1,1,1,1)) # 进行卷积计算,并输出结果 In [66]: conv_circular(x) Out[66]: tensor([[[[16., 13., 14., 15., 16., 13.], [ 4., 1., 2., 3., 4., 1.], [ 8., 5., 6., 7., 8., 5.], [12., 9., 10., 11., 12., 9.], [16., 13., 14., 15., 16., 13.], [ 4., 1., 2., 3., 4., 1.]]]], grad_fn=<ThnnConv2DBackward>)
The parameters kernel_size
, stride
, padding
, dilation
can either be:
元组的第一个参数指向高维度(行)。
a single
int
– in which case the same value is used for the height and width dimensiona
tuple
of two ints – in which case, the first int is used for the height dimension, and the second int for the width dimension
计算输出大小
变量
torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)
以一个batch为统计单位,在C(channel)维度上进行归一化。具体来说,对于每个batch的数据,BatchNorm2d会计算其均值和方差,并使用这两个参数对数据进行归一化。然后,再通过可学习的参数γ和β进行缩放和平移。这个过程可以保证每一层的输入数据都具有相同的分布特征,从而使得神经网络训练更加稳定。
- 抑制梯度消失:在深度神经网络中,梯度消失是一个常见的问题。BatchNorm2d通过对激活函数前添加归一化层,抑制了梯度消失的问题,从而加速了优化过程。
- 加速优化过程:使用BatchNorm2d后,可以应用更大的学习率,使得训练过程更加稳定,大大提高了训练速度。
- 减小参数初始化带来的影响:BatchNorm2d通过对数据的归一化处理,使得权重初始化的影响减小,无论权重的初始值如何,都可以通过归一化和仿射变换得到较好的效果。
- 正则化作用:BatchNorm2d可以视为一种有效的正则化技术,它通过引入额外的参数γ和β,增加了模型的复杂度,从而在一定程度上避免了过拟合。此外,BatchNorm2d还具有数据增强的效果,通过对mini-batch的数据进行归一化处理,可以看作是对数据进行了一定的变换,类似于数据增强的效果。
从公式看: 归一化输出范围在0-1之间,实现对原始数据的等比例缩放。在对变量无量纲化过程中仅仅与该变量的最大值和最小值这两个极端值有关。
从公式看:标准化输出范围是负无穷到正无穷,将数据变换为均值为0,标准差为1的分布,切记:并非一定是正态的。在无量纲化过程中利用了所有的数据信息,但是该方法在无量纲化后不仅使得转换后的各变量均值相同,且标准差也相同。
num_features:图像的通道数,也即(N, C, H, W)中的C的值。
eps:为数值稳定性而添加到分母上的值。默认值:1e-5。
momentum:一个用于运行过程中均值和方差的一个估计参数。表示动量参数,用于指定运算中移动均值和方差的计算方式。默认值为0.1。在训练过程中,会使用移动平均的方式来计算均值和方差,这个参数决定了移动平均的衰减率。
affine:一个布尔值,当设置为True时,此模块具有可学习的仿射参数。仿射变换是指对归一化后的数据进行缩放和平移操作,即乘以可学习的参数γ和加上可学习的偏置β。变量名是weight (初值1)和 bias(初值0).
track_running_stats:设为True时,BatchNorm层会统计全局均值running_mean (初值0)和方差running_var(初值1).
注意在BatchNorm中,用于更新running_var时,使用无偏样本方差即,但是在对batch进行归一化时,使用有偏样本方差,因此如果batch_size=1,会报错。
torch.nn.ReLU(inplace=False)
inplace = False 时,不会修改输入对象的值,而是返回一个新创建的对象
inplace = True 时,会修改输入对象的值,返回的对象存储地址相同,类似于C语言的址传递。
torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
kernel_size:表示做最大池化的窗口大小.
stride:窗口的移动步长,默认值是kernel_size。
padding:在两侧添加隐式负无穷大填充。
dilation:控制窗口中元素步幅的参数.
return_indices :布尔类型,返回最大值位置索引.多返回了一个位置信息。元素位置从0开始计数,6表示第7个元素,9表示第10个元素.
ceil_mode: 布尔类型,为True,用向上取整的方法,计算输出形状;默认是向下取整。
向下取整的符号 : 这个就是由 ceil_mode 控制的。
dilation的作用:
torch.nn.Sequential(*args: Module)
torch.nn.Sequential(arg: OrderedDict[str, Module])
torch.nn.AdaptiveAvgPool2d(output_size)
自适应池化层。函数通过输入原始尺寸和目标尺寸,自适应地计算核的大小和每次移动的步长。
torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(1): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer2): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer3): Sequential(
(0): BasicBlock(
(conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer4): Sequential(
(0): BasicBlock(
(conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
(fc): Linear(in_features=512, out_features=1000, bias=True)
)
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=1000, zero_init_residual=False,
groups=1, width_per_group=64, replace_stride_with_dilation=None,
norm_layer=None):
"""
block: 残差块类型,定义了BasicBlock与Bottleneck两种
layers: 每个stage中残差块的数目,长度为4
num_classes: 类别数目
zero_init_residual:若为True,则将残差块的最后一个BN层初始化为0,
这样残差分支从0开始每一个残差分支,每一个残差块表现的像一个恒等映射
根据论文:网络可提升0.2%~0.3%
"""
super(ResNet, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
self._norm_layer = norm_layer
self.inplanes = 64
self.dilation = 1
if replace_stride_with_dilation is None:
# each element in the tuple indicates if we should replace
# the 2x2 stride with a dilated convolution instead
replace_stride_with_dilation = [False, False, False]
if len(replace_stride_with_dilation) != 3:
raise ValueError("replace_stride_with_dilation should be None "
"or a 3-element tuple, got {}".format(replace_stride_with_dilation))
self.groups = groups
self.base_width = width_per_group
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
bias=False)
self.bn1 = norm_layer(self.inplanes)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2,
dilate=replace_stride_with_dilation[0])
self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
dilate=replace_stride_with_dilation[1])
self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
dilate=replace_stride_with_dilation[2])
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(512 * block.expansion, num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
# Zero-initialize the last BN in each residual branch,
# so that the residual branch starts with zeros, and each residual block behaves like an identity.
# This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
if zero_init_residual:
for m in self.modules():
if isinstance(m, Bottleneck):
nn.init.constant_(m.bn3.weight, 0)
elif isinstance(m, BasicBlock):
nn.init.constant_(m.bn2.weight, 0)
def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
norm_layer = self._norm_layer
downsample = None
previous_dilation = self.dilation
if dilate:
self.dilation *= stride
stride = 1
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
conv1x1(self.inplanes, planes * block.expansion, stride),
norm_layer(planes * block.expansion),
)
layers = []
layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
self.base_width, previous_dilation, norm_layer))
self.inplanes = planes * block.expansion
for _ in range(1, blocks):
layers.append(block(self.inplanes, planes, groups=self.groups,
base_width=self.base_width, dilation=self.dilation,
norm_layer=norm_layer))
return nn.Sequential(*layers)
def _forward_impl(self, x):
# See note [TorchScript super()]
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
def forward(self, x):
return self._forward_impl(x)
resnet18 50网络结构以及pytorch实现代码 - 简书
BasicBlock:
def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
"""3x3 convolution with padding"""
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=dilation, groups=groups, bias=False, dilation=dilation)
def conv1x1(in_planes, out_planes, stride=1):
"""1x1 convolution"""
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
base_width=64, dilation=1, norm_layer=None):
"""
inplanes: 输入的通道树,int
planes:
stride:卷积层的步长
downsample: 分支下采样(nn.Sequential)
"""
super(BasicBlock, self).__init__()
if norm_layer is None:
norm_layer = nn.BatchNorm2d
if groups != 1 or base_width != 64:
raise ValueError('BasicBlock only supports groups=1 and base_width=64')
if dilation > 1:
raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
# Both self.conv1 and self.downsample layers downsample the input when stride != 1
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = norm_layer(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = norm_layer(planes)
self.downsample = downsample
self.stride = stride
def forward(self, x):
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
每个block里依次包含:conv3x3, bn, relu, conv3x3, bn。在forward中用out+=x实现短接。如果参数stride=2,则会在第一个conv3x3中出现下采样。那么需要赋值downsample,down sample也是一个3x3卷积。