————————————————————————————————————————————
Pytorch构建模型大致可分为三部分:
1.构建网络层,在 继承于
torch.nn.Module
的类对象的构造器 init 方法中定义卷积、池化、线性、激活函数等;
2.定义前向传播,重写
forward
实例方法;
3.定义初始化方式,使用
torch.nn.init
中的函数。
PyTorch 中组装模型需要用到容器Containers,Containers包括Module、 Sequential、ModuleList、ModuleDict。
Module 是所有模型的基类,此外构建block一般用 Sequential,整体模型构建用Module
1. Module 类
torch.nn.Module
为所有模型的基类,它自定义层的步骤:
1.自定义一个Module的子类,实现两个基本的函数: (1)构造 init 函数 (2)层的逻辑运算函数,即正向传播的forward函数
2.在构造 init 函数中实现层的参数定义。 比如Linear层的权重和偏置,Conv2d层的in_channels, out_channels, kernel_size, stride=1,padding=0, dilation=1, groups=1,bias=True, padding_mode='zeros’这一系列参数;
3.在forward函数里实现前向运算。 一般都是通过torch.nn.functional.xx()函数来实现,如果该层含有权重,那么权重必须是nn.Parameter类型。
4.补充:一般情况下,我们定义的参数是可以求导的,但是自定义操作如不可导,需要实现backward函数。
下面是lenet模型的两种写法
区别在于是否将relu,maxpool操作创建为网络的成员对象组件:
如果将其创建为对象,使用
torch.nn
,则需要在__init__
函数中进行创建加入self.
如果不将其创建为对象,使用torch.nn.functional
,则需要在forward
中直接调用.
from torch.nn import Module
from torch.nn import Conv2d, ReLU, MaxPool2d, Linear
class LeNet5(Module): # 继承
def __init__(self, num_classes=10):
super(LeNet5, self).__init__()
self.conv1 = Conv2d(in_channels=1, out_channels=6, kernel_size=5, stride=1, padding=2)
self.conv2 = Conv2d(6, 16, 5)
self.linear1 = Linear(in_features=16 * 5 * 5, out_features=120)
self.linear2 = Linear(120, 84)
self.linear3 = Linear(84, num_classes)
self.relu = ReLU()
self.maxpool = MaxPool2d(kernel_size=2)
def forward(self, x): # 一个 module 相当于一个运算,必须实现 forward 函数
x = self.conv1(x) # 1 x 28 x 28 -> 6 x 28 x 28
x = self.relu(x)
x = self.maxpool(x) # 6 x 28 x 28 -> 6 x 14 x 14
x = self.conv2(x) # 6 x 14 x 14 -> 16 x 10 x 10
x = self.relu(x)
x = self.maxpool(x) # 16 x 10 x 10 -> 16 x 5 x 5
x = torch.flatten(x, 1)
x = self.linear1(x) # 400 -> 120
x = self.relu(x)
x = self.linear2(x) # 120 -> 84
x = self.relu(x)
x = self.linear3(x) # 84 -> num_classes
return x
import torch.nn as nn
import torch.nn.functional as F
class LeNet(nn.Module):
def __init__(self, classes): # 构建网络层组件(用torch.nn创建组件)
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, classes)
def forward(self, x): # 拼接网络层组件(用torch.nn.functional创建组件)
out = F.relu(self.conv1(x))
out = F.max_pool2d(out, 2)
out = F.relu(self.conv2(out))
out = F.max_pool2d(out, 2)
out = out.view(out.size(0), -1)
out = F.relu(self.fc1(out))
out = F.relu(self.fc2(out))
out = self.fc3(out)
return out
2. Sequential 类
torch.nn.Sequential
类能够按顺序组装网络层,nn.Sequential
通用继承于 nn.Module
类,与Module不同的是,Sequential 已经默认定义了forward函数,按照顺序依次输入输出。模块将按照构造函数中传递的顺序添加到模块中。
与nn.Module
相同,nn.Sequential
也是用来构建网络block的,但nn.Sequential
不需要像nn.Module
那么多过程,可以快速构建神经网络。
一般向 Sequential 中传入网络层有两种格式:
1.向 Sequential 中直接传入各网络层,实例化后以数字为索引 (用的较多)
这个LeNetModule由两个Sequential组成(features 和classifier )
import torch
import torchvision
import torch.nn as nn
from collections import OrderedDict
# ============================ Sequential
class LeNetSequential(nn.Module):
def __init__(self, classes): # 此LeNetModule由两个Sequential组成
super(LeNetSequential, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 6, 5),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, 5),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),)
self.classifier = nn.Sequential(
nn.Linear(16*5*5, 120),
nn.ReLU(),
nn.Linear(120, 84),
nn.ReLU(),
nn.Linear(84, classes),)
def forward(self, x): # 只需执行features(卷积池化)和classifier(全连接)
x = self.features(x)
x = x.view(x.size()[0], -1)
x = self.classifier(x)
return x
2.向 Sequential 中传入 OrderedDict 对象,实例化后可以自定义的名字为索引
import torch
import torchvision
import torch.nn as nn
from collections import OrderedDict
# ============================ Sequential
class LeNetSequentialOrderDict(nn.Module):
def __init__(self, classes):
super(LeNetSequentialOrderDict, self).__init__()
self.features = nn.Sequential(OrderedDict({ #通过有序字典对网络中的操作进行命名,这样可以很容易的索引到
'conv1': nn.Conv2d(3, 6, 5),
'relu1': nn.ReLU(inplace=True),
'pool1': nn.MaxPool2d(kernel_size=2, stride=2),
'conv2': nn.Conv2d(6, 16, 5),
'relu2': nn.ReLU(inplace=True),
'pool2': nn.MaxPool2d(kernel_size=2, stride=2),
}))
self.classifier = nn.Sequential(OrderedDict({
'fc1': nn.Linear(16*5*5, 120),
'relu3': nn.ReLU(),
'fc2': nn.Linear(120, 84),
'relu4': nn.ReLU(inplace=True),
'fc3': nn.Linear(84, classes),
}))
def forward(self, x):
x = self.features(x)
x = x.view(x.size()[0], -1)
x = self.classifier(x)
return x
3. Parameter类
torch.nn.Parameter
是继承自torch.Tensor
的子类,其主要作用是作为nn.Module
中的可训练参数使用。它与torch.Tensor
的区别就是nn.Parameter
会自动被认为是module的可训练参数,即加入到parameter()这个迭代器中去;而module中非nn.Parameter()的普通tensor是不在parameter中的。
nn.Parameter的对象的requires_grad属性的默认值是True,即是可被训练的,这与torh.Tensor对象的默认值相反。在nn.Module类中,pytorch也是使用nn.Parameter来对每一个module的参数进行初始化的。
4. 常见网络层
卷积层
nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0,
dilation=1, groups=1, bias=True, padding_mode='zeros')
dilation:空洞卷积,当大于1的时候可以增大感受野,同时保持特征图的尺寸
groups:可实现组卷积,即在卷积操作时不是逐点卷积,而是将输入通道范围分为多个组,稀疏连接达到降低计算量的目的
输入特征图必须写为( B , C , H , W ) 的形式
池化层
最大下采样池化层
nn.MaxPool2d(kernel_size, stride=None, padding=0,
dilation=1, return_indices=False, ceil_mode=False)
stride
– 注意:stride 默认值= kernel_size,而非1
return_indices
– 是否返回最大下采样的像素索引位置
ceil_mode
– when True, will use ceil instead of floor to compute the output shape
平均池化层
nn.AvgPool2d(kernel_size, stride=None, padding=0,
ceil_mode=False, count_include_pad=True, divisor_override=None)
count_include_pad
– 边界padding填充是否用于计算平均值
divisor_override
– 除法因子
最大上采样池化层
nn.MaxUnpool2d(kernel_size, stride=None, padding=0)
forward(self,input,indeces)
使用时,不仅要传入input,还要传入indeces最大下采样时得到的像素索引位置
全连接层(线性层)
nn.Linear(in_features, out_features, bias=True)
对一维向量进行线性组合
in_features
:输入向量长度(输入层结点数)
out_features
:输出向量长度(输出层结点数)
>>> linear = nn.Linear(784, 10)
>>> input = torch.randn(4, 784)
>>> output = linear(input)
>>> output.shape
torch.Size([4, 10])
RNN层
nn.RNN(input_size, hidden_size, num_layers=1, nonlinearity=tanh,
bias=True, batch_first=False, dropout=0, bidirectional=False)
input_size
输入特征的维度, 一般rnn中输入的是词向量,那么 input_size 就等于一个词向量的维度
hidden_size
隐藏层神经元个数,或者也叫输出的维度(因为rnn输出为各个时间步上的隐藏状态)
num_layers
网络的层数
nonlinearity
激活函数
bias
是否使用偏置
batch_first
输入数据的形式,默认是 False,就是这样形式,(seq(num_step), batch, input_dim),也就是将序列长度放在第一位,batch 放在第二位
dropout
是否应用dropout, 默认不使用,如若使用将其设置成一个0-1的数字即可
birdirectional
是否使用双向的 rnn,默认是 False
注意某些参数的默认值在标题中已注明
具体计算过程:
[batch_size, input_dim] * [input_dim, num_hiddens] + [batch_size, num_hiddens] *[num_hiddens, num_hiddens] +bias
GRU层
nn.GRU(input_size, hidden_size,num_layers=1)
LSTM 层
nn.LSTM(input_size=embedding_dim,hidden_size=hidden_size,num_layers=num_layers,
bias=True,batch_first=False,dropout=0.5,bidirectional=False)
激活函数层
激活函数层也可以用torch.nn.functional中的函数替代
Sigmoid层
导数<1,易梯度消失
nn.Sigmoid()
>>> sigmoid = nn.Sigmoid()
>>> sigmoid(torch.Tensor([1, 1, 2, 2]))
tensor([0.7311, 0.7311, 0.8808, 0.8808])
ReLU层
导数=1,但易梯度爆炸
nn.ReLU(inplace=False)
>>> relu = nn.ReLU(inplace=True)
>>> input = torch.randn(2, 2)
>>> input
tensor([[-0.4853, 2.3864],
[ 0.7122, -0.6493]])
>>> relu(input)
tensor([[0.0000, 2.3864],
[0.7122, 0.0000]])
>>> input
tensor([[0.0000, 2.3864],
[0.7122, 0.0000]])
Tanh层
导数<1,易梯度消失
nn.Tanh()
>>>m = nn.Tanh()
>>>input = torch.randn(2)
>>>output = m(input)
>>>print(output)
>tensor([0.5793, 0.2608])
Softplus层
nn.Softplus()
Softmax层
将一/二维数据变成符合概率分布的形式(非负,行和为1),常用于分类输出,放在FC层之后
nn.Softmax(dim=None)
>>> softmax = nn.Softmax(dim=1)
>>> score = torch.randn(1, 4)
>>> score
tensor([[ 0.3101, 3.5648, 1.0988, -1.5856]])
>>> softmax(score)
tensor([[0.0342, 0.8855, 0.0752, 0.0051]])
损失函数
衡量模型输出 与真实标签的差异
CrossEntropyLoss交叉熵损失函数
nn.CrossEntropyLoss(weight=None, ignore_index=-100, reduction='mean')
Softmax + CrossEntropyLoss,它们两个结合在一起时梯度反向传播的时候结果就会很好
weight
:各个类别loss的权重
ignore_index
:忽略某个类别
reduction
:计算模式(none/some/mean)
forward(self, input: Tensor, target: Tensor)
# input输入(模型输出),target目标(真实标签)
loss = nn.CrossEntropyLoss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.empty(3, dtype=torch.long).random_(5)
output = loss(input, target)
output.backward()
NLLLoss
负对数似然函数的负号功能
nn.NLLLoss(weight=None, size_average=None,
ignore_index=-100, reduce=None, reduction='mean')
weight
:各个类别loss的权重
ignore_index
:忽略某个类别
reduction
:计算模式(none/some/mean)
BCELoss
torch.nn.BCELoss(weight=None,size_average=None,reduce=None,reduction='elementwise_mean')
二分类交叉熵,是nn.CrossEntropyLoss函数的特例。
其分类限定为二分类,输入必须在[0,1]
TripletMarginLoss
class torch.nn.TripletMarginLoss(margin=1.0, p=2, eps=1e-06, swap=False, size_average=None,
reduce=None, reduction='elementwise_mean')
功能: 计算三元组损失,人脸验证中常用。 如下图Anchor、Negative、Positive,目标是让Positive元和Anchor元之间的距离尽可能的小,Positive元和Negative元之间的距离尽可能的大。
从公式上看,Anchor元和Positive元之间的距离加上一个threshold之后,要小于Anch
margin
(float)- 默认值为1
p
(int)- The norm degree ,默认值为2
swap
(float)– The distance swap is described in detail in the paper Learning shallow convolutional feature descriptors with triplet losses by V. Balntas, E. Riba et al. Default: False
size_average
(bool)- 当reduce=True时有效。为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。 reduce(bool)- 返回值是否为标量,默认为True。
Optimizer优化器
管理并更新模型中可学习参数的值,使得模型输出更接近真实标签。通俗一点,就是采样梯度更新模型的可学习参数,使得损失减小。
class Optimizer(object):
def __init__(self, params, defaults):
self.defaults = defaults
self.state = defaultdict(dict)
self.param_groups = []
...
param_groups = [{'params': param_groups}]
optimizer的基本属性
defaults
:优化器超参数
state
:参数的缓存,如momentum的缓存
params_groups
:管理的参数组
_step_count
:记录更新次数,学习率调整中使用
optimizer的基本方法
zero_grad()
:清空所管理参数的梯度,pytorch特性:张量梯度不自动清零,会将张量梯度累加;因此,需要在使用完梯度之后,或者在反向传播前,将梯度自动清零
step()
:执行一步更新
add_param_group()
:以字典的形式,添加参数组,不同参数组可以设置不同的学习率 lr,例如:可以为特征提取层与全连接层设置不同的学习率或者别的超参数
state_dict()
:获取优化器当前状态信息字典,长时间的训练,会隔一段时间保存当前的状态信息,用来在断点的时候恢复训练,避免由于意外的原因导致模型的终止
load_state_dict()
:加载状态信息字典,用于周期性保存参数
优化器中的常用参数
params
:管理的参数
learning rate
学习率
梯度下降:学习率(learning rate)控制更新的步伐
momentum
动量:结合当前梯度与上一次更新信息,用于当前更新,越往前梯度信息的作用就越小。
weight_decay
:L2正则化系数
nesterov
:是否采用NAG
十种优化器实例
optim.SGD:随机梯度下降法
optim.SGD(params, lr=<object object>,
momentum=0, dampening=0,
weight_decay=0, nesterov=False)
主要参数:
params
:管理的参数组
lr
:初始学习率
momentum
:动量系数0.9
weight_decay
:L2正则化系数
nesterov
:是否采用NAG
其他优化器:
optim.Adagrad:自适应学习率梯度下降法
optim.RMSprop: Adagrad的改进
optim.Adadelta: Adagrad的改进
optim.Adam:RMSprop结合Momentum
optim.Adamax:Adam增加学习率上限
optim.SparseAdam:稀疏版的Adam
optim.ASGD:随机平均梯度下降
optim.Rprop:弹性反向传播
optim.LBFGS:BFGS的改进
SGD与Adam是两种最常用的方式。
正则化网络层
正则化:减少方差的方法,方差表示数据扰动造成的影响,即减少数据扰动造成的过拟合。
Dropout层
按p概率,随机失活神经元(通过设置weight=0实现)
nn.Dropout(p=0.5, inplace=False)
>>> dropout = nn.Dropout(0.5, inplace=False)
>>> input = torch.randn(1, 20)
>>> output = dropout(input)
>>> output
tensor([[-2.9413, 0.0000, 1.8461, 1.9605, 0.2774, -0.0000, -2.5381, -2.0313,
-0.1914, 0.0000, 0.5346, -0.0000, 0.0000, 4.4960, -3.8345, -1.0938,
4.3297, 2.1258, -4.1431, 0.0000]])
>>> input
tensor([[-1.4707, 0.5105, 0.9231, 0.9802, 0.1387, -0.4195, -1.2690, -1.0156,
-0.0957, 0.8108, 0.2673, -2.0898, 0.6666, 2.2480, -1.9173, -0.5469,
2.1648, 1.0629, -2.0716, 0.9974]])
Batch Normalization(BN)
Batch Normalization:批标准化,不同样本在同一特征上的标准化
批:一批数据,通常为mini-batch
标准化:0均值,1标准差
torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1,
affine=True, track_running_stats=True)
num_features
:一个样本特征数量(最重要),也就是输出特征图的通道channal数
eps
:分母修正项,一般设置比较小的数,防止除以0导致错误
momentum
:指数加权平均估计当前mean/var
affine
:是否需要affine transform
track_running_states
:是训练状态,还是测试状态,训练:均值和方差采用指数加权平均计算,测试:当前统计值
主要属性:
running_mean:均值
running_var:方差
weight:affine transform中的gamma
bias:affine transform中的beta
>>> bn = nn.BatchNorm2d(64)
>>> input = torch.randn(4, 64, 28, 28)
>>> output = bn(input)
>>> output.shape
torch.Size([4, 64, 28, 28])
Layer Normalization(LN)
Layer Normalization:由于BN不适用于长网络,LN逐层求均值和方差
torch.nn.LayerNorm(normalized_shape, eps: float = 1e-5, elementwise_affine: bool = True)
主要参数:
normalized_shape
:该层特征形状
eps
:分母修正项
elementwise_affine
:是否需要affine transform(逐元素)
Instance Normalization(IN)
Instance Normalization:BN在图像生成(Image Generation)中不适用,逐Instance(channel)计算均值和方差
torch.nn.InstanceNorm2d(num_features, eps=1e-05, momentum=0.1,
affine=True, track_running_stats=True)
主要参数:
num_features
:一个样本特征数量(最重要)
eps
:分母修正项
momentum
:指数加权平均估计当前mean/var
affine
:是否需要affine transform
track_running_states
:是训练状态,还是测试状态
Group Normalization(GN)
起因:小batch样本中,BN估计的值不准
思路:数据不够,通道来凑
注意事项:
不再有running_mean和running_var
gamma和beta为逐通道(channel)的
应用场景:大模型(小batch size)任务
torch.nn.GroupNorm(num_groups, num_channels, eps=1e-05, momentum=0.1, affine=True)
主要参数:
num_groups
:分组数
num_channels
:通道数(特征数)
eps
:分母修正项
affine
:是否需要affine transform