深度学习与Pytorch入门实战(九)卷积神经网络&Batch Norm

笔记摘抄

1. 卷积层

1.1 torch.nn.Conv2d() 类式接口

torch.nn.Conv2d(self, in_channels, out_channels, kernel_size, 
                stride=1, padding=0, dilation=1, groups=1, bias=True))

参数:

  • in_channel:输入数据的通道数,例RGB图片通道数为3;

  • out_channel:输出数据的通道数,也就是kernel数量;

  • kernel_size: 卷积核大小,可以是int,或tuple;kernel_size=2,意味着卷积大小(2,2),kernel_size=(2,3),意味着卷积大小(2,3)即非正方形卷积

  • stride:步长,默认为1,与kernel_size类似,stride=2,意味着步长上下左右扫描皆为2, stride=(2,3),左右扫描步长为2,上下为3;

  • padding:零填充

import  torch
import  torch.nn as nn

# batch, channel, height, width
x = torch.rand(1,1,28,28)                                  
# in_channel(和上面x中的channel数量一致), out_channel(kernel个数)
layer = nn.Conv2d(1,3,kernel_size=3,stride=2,padding=1)    

# 或者直接out=layer(x)
out = layer.forward(x)           
# torch.Size([1, 3, 14, 14]) 1指一张图片,3指三个kernel,14*14指图片大小
print(out.shape)                 
  • 第五行layer的相关属性:

    • layer.weight.shape = torch.Size([3,1,3,3]): 第一个3指out_channel,即输出数据的通道数(kernel个数);第二个1指in_channel,即输入数据的通道数,这个值必须 和 x的channel数目一致。(Tip:前两个参数顺序是和Conv2d函数的前两个参数顺序相反的)

    • layer.bias.shape = torch.Size([3]): 这个3指kernel个数,不同的通道共用一个偏置。

1.2 F.conv2d() 函数式接口

  • PyTorch里一般小写的都是 函数式的接口,相应的大写的是类式接口

  • 函数式的更加 low-level 一些,如果不需要做特别复杂的配置 只要用 类式接口即可。

import torch
from torch.nn import functional as F

#手动定义卷积核(weight)和偏置
w = torch.rand(16, 3, 5, 5)                       # 16种3通道的5*5卷积核
b = torch.rand(16)

#定义输入样本
x = torch.randn(1, 3, 28, 28)                     # 1张3通道的28*28的图像

#2D卷积得到输出
# 1张图片,16个输出(16个filter),f = (28+2-5+1)
out = F.conv2d(x, w, b, stride=1, padding=1)
print(out.shape)                                  # torch.Size([1, 16, 26, 26])

out = F.conv2d(x, w, b, stride=2, padding=2)
print(out.shape)                                  # torch.Size([1, 16, 14, 14])

2. 池化层Pooling(下采样)

torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, 
                   dilation=1, return_indices=False, ceil_mode=False)

参数:

  • kernel_size(int or tuple) - max pooling的窗口大小,

  • stride(int or tuple, optional) - max pooling的窗口移动的步长。默认值是kernel_size

  • padding(int or tuple, optional) - 输入的每一条边补充0的层数

  • dilation(int or tuple, optional) – 一个控制窗口中元素步幅的参数

  • return_indices - 如果等于True,会返回输出最大值的序号,对于上采样操作会有帮助

  • ceil_mode - 如果等于True,计算输出信号大小的时候,会使用向上取整,代替默认的向下取整的操作

接着上面卷积运算得到的1张16通道的14*14的图像,在此基础上做池化。

2.1 torch.MaxPool2d() 类式接口

# Maxpool
x = out                              # torch.Size([1, 16, 14, 14])

layer = nn.MaxPool2d(2,stride=2)     # 池化层(池化核为2*2,步长为2),最大池化

out = layer(x)           

print(out.shape)                     # torch.Size([1, 16, 7, 7])

2.2 F.avg_pool2d() 函数式接口

x = out                                  # torch.Size([1, 16, 14, 14])

out = F.avg_pool2d(x, 2, stride=2)

print(out.shape)                         # torch.Size([1, 16, 7, 7])

Tip:池化后通道数不变。

3. upsample(上采样)

F.interpolate(input, size=None, scale_factor=None, 
              mode='nearest', align_corners=None)

参数:

  • size(int):输出的spatial大小;

  • scale_factor(float): spatial尺寸的缩放因子;

  • mode(string):上采样算法:nearest,linear,bilinear,trilinear,area,默认nearest;

  • align_corners(bool, optional):如果align_corners=True,则对齐input和output的角点像素,只会对 mode=linear, bilinear 和 trilinear 有作用。默认是 False。

x = out                                                # torch.Size([1, 16, 7, 7])

out = F.interpolate(x, scale_factor=2, mode='nearest') # 向上采样,放大2倍,最近插值

print(out.shape)                                       # torch.Size([1, 16, 14, 14])

4. RELU激活函数

4.1 torch.nn.RELU() 类式接口

x = out                                 # torch.Size([1, 16, 14, 14])

layer = nn.ReLU(inplace=True)          # ReLU激活,inplace=True表示直接覆盖掉ReLU目标的内存空间
out = layer(x)
print(out.shape)                       # torch.Size([1, 16, 14, 14])

4.2 F.relu() 函数式接口

x = out                   # torch.Size([1, 16, 14, 14])

out = F.relu(x, inplace=True)
print(x.shape)            # torch.Size([1, 16, 14, 14])

5. Batch Norm

  • 归一化:使代价函数平均起来看更对称,使用梯度下降法更方便

  • 通常分为两步:调整均值、方差归一化

  • Batch Norm详情

5.1 Batch Norm

  • 一个Batch的图像数据shape为[样本数N, 通道数C, 高度H, 宽度W]

  • 将其最后两个维度flatten,得到的是[N, C, H*W]

  • 标准的Batch Normalization:

    • 通道channel这个维度上进行移动,对 所有样本 的所有值求均值和方差

    • 有几个通道,得到的就是几个均值和方差

  • eg. [6, 3, 784]会生成[3],代表当前batch中每一个channel的特征均值,3个channel有3个均值和3个方差,只保留了channel维度,所以是[3]。

5.2 Layer Norm

  • 样本N的维度上滑动,对每个样本的 所有通道 的 所有值 求均值和方差

  • 一个Batch有几个样本实例,得到的就是几个均值和方差。

  • eg. [6, 3, 784]会生成[6]

5.3 Instance Norm

  • 样本N和通道C两个维度上滑动,对Batch中的N个样本里的每个样本n,和C个通道里的每个样本c,其组合[n, c]求对应的所有值的均值和方差,所以得到的是N*C个均值和方差。

5.4 Batch Norm详解

  • 输入数据:6张3通道784个像素点的数据,将其分到三个通道上,在每个通道上也就是[6, 784]的数据

  • 然后分别得到和通道数一样多的统计数据 均值\(\mu\)方差\(\sigma\)

  • 将每个像素值减去 \(\mu\) 除以 \(\sigma\) 也就变换到了接近 \(N(0,1)\) 的分布

  • 后面又使用参数 \(\beta\)\(\gamma\) 将其变换到接近 \(N(β,γ)\) 的分布。

Tip:

  • \(\mu\)\(\sigma\) 只是样本中的统计数据,是没有梯度信息的,不过会保存在运行时参数里。

  • \(\gamma\)\(\beta\) 属于要训练的参数,他们是有梯度信息的。

正式计算:

nn.BatchNorm1d()

import torch
from torch import nn

x = torch.rand(100, 16, 784)     # 100张16通道784像素点的数据,均匀分布

layer = nn.BatchNorm1d(16)       # 传入通道数,因为H和W已经flatten过了,所以用1d
out = layer(x)

print(layer.running_mean.shape ,layer.running_mean)
#torch.Size([16]) tensor([0.0499, 0.0501, 0.0501, 0.0501, 0.0501, 0.0502, 0.0500, 0.0499, 0.0499,
#        0.0501, 0.0500, 0.0500, 0.0500, 0.0501, 0.0500, 0.0500])
print(layer.running_var.shape,layer.running_var)
#tensor([0.9083, 0.9083, 0.9083, 0.9084, 0.9083, 0.9083, 0.9084, 0.9083, 0.9083,
#        0.9083, 0.9083, 0.9083, 0.9084, 0.9084, 0.9083, 0.9083])

Tip:

  • layer.running_mean和layer.running_var得到的是 全局 的均值和方差

  • 不是当前Batch上的,只不过这里只跑了一个Batch而已所以它就是这个Batch上的。

nn.BatchNorm2d()

import torch
from torch import nn

x = torch.rand(1, 16, 7, 7)     # 1张16通道的7*7的图像

layer = nn.BatchNorm2d(16)      # 传入通道数(必须和上面的通道数目一致)
out = layer(x)

print(out.shape)                # torch.Size([1, 16, 7, 7])
print(layer.running_mean)       # running-μ
print(layer.running_var)        # running-σ^2

print(layer.weight.shape)       # torch.Size([16]),对应上面的γ
print(layer.bias.shape)         # torch.Size([16]),对应上面的β
print(vars(layer))              # 查看网络中一个层上的所有参数
# {'training': True,
#   '_parameters':
#       OrderedDict([('weight', Parameter containing:
#                               tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], requires_grad=True)),
#                   ('bias', Parameter containing:
#                               tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], requires_grad=True))]),
#   '_buffers':
#       OrderedDict([('running_mean', tensor([0.0527, 0.0616, 0.0513, 0.0488, 0.0484, 0.0510, 0.0590, 0.0459, 0.0448, 0.0586, 0.0535, 0.0464, 0.0581, 0.0481, 0.0420, 0.0549])),
#                   ('running_var', tensor([0.9089, 0.9075, 0.9082, 0.9079, 0.9096, 0.9098, 0.9079, 0.9086, 0.9081, 0.9075, 0.9052, 0.9081, 0.9093, 0.9075, 0.9086, 0.9073])),
#                   ('num_batches_tracked', tensor(1))]),
# '_backward_hooks': OrderedDict(),
# '_forward_hooks': OrderedDict(),
# '_forward_pre_hooks': OrderedDict(),
# '_state_dict_hooks': OrderedDict(),
# '_load_state_dict_pre_hooks': OrderedDict(),
# '_modules': OrderedDict(),
# 'num_features': 16,
# 'eps': 1e-05,
# 'momentum': 0.1,
# 'affine': True,
# 'track_running_stats': True}

Tip:

  • layer.weight 和 layer.bias是当前batch上的;

  • 如果在定义层时使用了参数affine=False,那么就是固定 \(\gamma=1\)\(\beta=0\) 不自动学习,这时参数layer.weightlayer.bias将是None。

5.5 Train和Test

  • 类似于Dropout,Batch Normalization在训练和测试时的行为不同。

  • 测试模式下,\(\mu\)\(\sigma_2\) 使用训练集得到的 全局\(\mu\)\(\sigma_2\)

    • 归一化前调用layer.eval()设置Test模式。

5.6 使用Batch Norm好处

  • 收敛更快(converge faster)

  • 表现的更好(Better performance)

  • 更稳定

    • Stable

    • larger learning rate(超参数没有那么敏感)

posted @ 2020-07-21 00:23  douzujun  阅读(1128)  评论(0编辑  收藏  举报