神经网络核心组件
利用PyTorch神经网路工具箱设计神经网络就像搭积木一样,可以极大简化构建模型的任务。
神经网络核心组件如下:
层:神经网络的基本结构,将输入张量转换为输出张量。
模型:由层构成的网络。
损失函数:参数学习的目标函数,通过最小化损失函数来学习各种参数。
优化器:如在使损失值最小时,就会涉及优化器。
这些核心组件并不是相互独立的,它们之间的相互关系如下图所示:
多个层连接在一起构成一个模型或网络,输入数据通过这个模型转换为预测值。预测值与真实值共同构成损失函数的输入,损失函数输出损失值(损失值可以是距离、概率值等),该损失值用于衡量预测值与真实值的匹配或相似程度。优化器利用损失值更新权重参数,目标是使损失值越来越小。这是一个循环过程,当损失值达到一个阈值或循环次数达到指定次数时,循环结束。
构建神经网络的主要工具
构建网络层:基于nn.Module类或nn.functional函数
nn中的大多数层(layer)在functional中都有与之对应的函数。
nn.functional中的函数是纯函数,而nn.Module类中的层(layer)继承自Module类,可自动提取可学习的参数。
使用nn.Module类:卷积层、全连接层、dropout层等 含 有可学习参数。
使用nn.functional中的函数:激活函数、池化层等 不含 可学习的参数。
nn.Module
nn(neural network, 神经网络)是专门为深度学习设计的一个模块,可以将上一节中的机器学习实例进一步简化。nn.Module是nn的一个核心数据结构,nn.Module可以是神经网路的某个层,也可以是包含多层的神经网络。在实际使用中,可以继承nn.Module来生成自己的网络/层。
nn.functional
nn中的层,一类是继承nn.Module,名称一般为nn.Xxx(第一个字母大写),如:nn.Conv2d等。另一类是nn.functional中的函数,名称一般为nn.functional.xxx(第一个字母小写),如nn.functional.conv2d等。两者的功能与性能都没有太大的差异。但在具体使用过程中,还是有一点区别的。
主要区别如下:
1) nn.Xxx继承于nn.Module,需要先实例化并传入参数,然后以函数调用的方式调用实例化对象并传入输入数据,可以与nn.Sequential结合使用而nn.functional.xxx无法与nn.Sequential结合使用。
2) nn.Xxx不需要自己定义和管理weight、bias参数;nn.functional.xxx需要自己定义weight、bias等参数,每次调用的时候都需要手动传入,不利于代码的复用。
3) dropout操作在训练和测试阶段是有区别的,使用nn.Xxx方式定义dropout,在调用model.eval()之后,自动实现状态的转换,而nn.functional.xxx却无此功能。
构建模型
使用PyTorch构建模型的方法有以下三种:
继承nn.Module基类 (常见)
使用nn.Sequential(简单,适合初学者)
继承nn.Module基类,再使用相关模型容器(nn.Sequential, nn.ModuleList, nn.ModuleDict等)进行封装。(比较灵活,但复杂些 )
继承nn.Module基类构建模型
参数 与 超参数
参数:模型f(x, θ)中的θ称为模型的参数,可以通过优化算法进行学习。
超参数:用来定义模型结构或优化策略。(自己来定义)
利用这种方法构建模型,先定义一个类,使之继承nn.Module基类。并把模型中用到的层放到构造函数__init__()中,在forward方法中实现模型的正向传播。
# 1) 导入模块
from torch import nn
import torch.nn.functional as F
# 2) 构建模型:通过继承基类nn.Module来构建模型
class Model_Seq(nn.Module):
# 把模型中需要用到的层放到构造函数__init__()中
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
# 初始化父类
super(Model_Seq, self).__init__()
# 展平(flatten)为向量
self.flatten = nn.Flatten()
# 第一层
# 全连接层
self.linear1 = nn.Linear(in_dim, n_hidden_1)
# bn:batch normalization,批量归一化层
self.bn1 = nn.BatchNorm1d(n_hidden_1)
# 第二层
# 全连接层
self.linear2 = nn.Linear(n_hidden_1, n_hidden_2)
# bn:batch normalization,批量归一化层
self.bn2 = nn.BatchNorm1d(n_hidden_2)
# 输出层
self.out = nn.Linear(n_hidden_2, out_dim)
# 模型的正向传播
def forward(self, x):
# 展平
x = self.flatten(x)
# 第一层
x = self.linear1(x)
x = self.bn1(x)
# 激活函数:ReLu
x = F.relu(x)
# 第二层
x = self.linear2(x)
x = self.bn2(x)
# 激活函数:ReLu
x = F.relu(x)
# 输出层
x = self.out(x)
# 激活函数:softmax
x = F.softmax(x, dim = 1)
return x
# 3) 查看模型
# 对一些超参数赋值
in_dim, n_hidden_1, n_hidden_2, out_dim = 28 * 28, 300, 100, 10
# 函数调用,传入输入数据
model_Seq = Model_Seq(in_dim, n_hidden_1, n_hidden_2, out_dim)
# 打印模型信息
print(model_Seq)
运行结果:
使用nn.Sequential按层顺序构建模型
使用nn.Sequential构建模型,因其内部实现了forward函数,因此可以不用重写。nn.Sequential里面的模块是按照先后顺序进行排列的,所以必须确保前一个模块的输出大小和下一个模块的输入大小是一致的。适合构建简单的模型。
1、利用可变参数
PyTorch中的nn.Sequential(*args)中的参数个数是可变的,又称不定长参数。
# 1、利用可变参数
# 1) 导入模块
from torch import nn
# 对一些超参数赋值
in_dim, n_hidden_1, n_hidden_2, out_dim = 28 * 28, 300, 100, 10
# 2) 构建模型
Seq_arg = nn.Sequential(
# 展平(flatten)为向量
nn.Flatten(),
# 第一层
# 全连接层
nn.Linear(in_dim, n_hidden_1),
# 批量归一化层
nn.BatchNorm1d(n_hidden_1),
# 激活函数ReLU
nn.ReLU(),
# 第二层
# 全连接层
nn.Linear(n_hidden_1, n_hidden_2),
# 批量归一化层
nn.BatchNorm1d(n_hidden_2),
# 激活函数ReLU
nn.ReLU(),
# 输出层
# 全连接层
nn.Linear(n_hidden_2, out_dim),
# 激活函数Softmax
nn.Softmax(dim=1)
)
# 3) 查看模型
print(Seq_arg)
运行结果:
如果需要为每个层指定名称,可使用add_module方法或OrderedDict方法。
2、使用add_module方法
# 使用 add_module 方法
# # 1) 导入模块
from torch import nn
# 对一些超参数赋值
in_dim, n_hidden_1, n_hidden_2, out_dim = 28 * 28, 300, 100, 10
# 2) 构建模型
Seq_module = nn.Sequential()
# 指定名称为:flatten,展平为向量
Seq_module.add_module("flatten", nn.Flatten())
# 第一层
# 指定名称为:linear1,全连接层
Seq_module.add_module("linear1", nn.Linear(in_dim, n_hidden_1))
# 指定名称为:bn1,批量归一化层
Seq_module.add_module("bn1", nn.BatchNorm1d(n_hidden_1))
# 指定名称为:relu1,激活函数为ReLU
Seq_module.add_module("relu1", nn.ReLU())
# 第二层
# 指定名称为:linear2,全连接层
Seq_module.add_module("linear2", nn.Linear(n_hidden_1, n_hidden_2))
# 指定名称为:bn2,批量归一化层
Seq_module.add_module("bn2", nn.BatchNorm1d(n_hidden_2))
# 指定名称为:relu2,激活函数为ReLU
Seq_module.add_module("relu2", nn.ReLU())
# 输出层
# 指定名称为:out,全连接层
Seq_module.add_module("out", nn.Linear(n_hidden_2, out_dim))
# 指定名称为:softmax,激活函数为Softmax
Seq_module.add_module("softmax", nn.Softmax(dim=1))
# 3) 查看模型
print(Seq_module)
运行结果:
3、使用OrderedDict方法
# 使用 OrderedDict 方法
# 1) 导入模块
from torch import nn
from collections import OrderedDict
# 对一些超参数赋值
in_dim, n_hidden_1, n_hidden_2, out_dim = 28 * 28, 300, 100, 10
# 2) 构建模型
Seq_dict = nn.Sequential(OrderedDict([
# 指定名称为:flatten,展平为向量
("flatten", nn.Flatten()),
# 第一层
# 指定名称为:linear1,全连接层
("linear1", nn.Linear(in_dim, n_hidden_1)),
# 指定名称为:bn1,批量归一化层
("bn1", nn.BatchNorm1d(n_hidden_1)),
# 指定名称为:relu1,激活函数为ReLU
("relu1", nn.ReLU()),
# 第二层
# 指定名称为:linear2,全连接层
("linear2", nn.Linear(n_hidden_1, n_hidden_2)),
# 指定名称为:bn1,批量归一化层
("bn2", nn.BatchNorm1d(n_hidden_2)),
# 指定名称为:relu1,激活函数为ReLU
("relu2", nn.ReLU()),
# 输出层
# 指定名称为:out,全连接层
("out", nn.Linear(n_hidden_2, n_hidden_1)),
# 指定名称为:softmax,激活函数为Softmax
("softmax", nn.Softmax(dim=1))]))
# 查看模型
print(Seq_dict)
运行结果:
继承nn.Module基类并应用模型容器来构建模型
当模型的结构比较复杂时,可以应用模型容器(如nn.Sequential, nn.ModuleList, nn.ModuleDict)对模型的部分结构进行封装,增强模型的可读性,减少代码量。
1、使用nn.Sequential模型容器
# 1、使用nn.Sequential模型容器
# 1) 导入模块
from torch import nn
import torch.nn.functional as F
# 2) 构建模型:通过继承基类nn.Module来构建模型
# 使用nn.Sequential构建网络,Sequential()函数的功能是将网络的层组合到一起
class Model_lay(nn.Module):
# 把模型中需要用到的层放到构造函数__init__()中
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
# 初始化父类
super(Model_lay, self).__init__()
# 展平(flatten)为向量
self.flatten = nn.Flatten()
# 第一层
# 全连接层 + bn:batch normalization,批量归一化层
self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1), nn.BatchNorm1d(n_hidden_1))
# 第二层
# 全连接层 + bn:batch normalization,批量归一化层
self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2), nn.BatchNorm1d(n_hidden_2))
# 输出层
# 全连接层
self.out = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
# 前向传播
def forward(self, x):
# 展平(flatten)为向量
x = self.flatten(x)
# 第一层激活函数ReLU(没有学习参数,使用nn.functional实现)
x = F.relu(self.layer1(x))
# 第二层激活函数ReLU(没有学习参数,使用nn.functional实现)
x = F.relu(self.layer2(x))
# 输出层激活函数softmax(没有学习参数,使用nn.functional实现)
x = F.softmax(self.out(x), dim=1)
return x
# 3) 查看模型
# 对一些超参数赋值
in_dim, n_hidden_1, n_hidden_2, out_dim = 28 * 28, 300, 100, 10
# 函数调用,传入输入数据
model_lay = Model_lay(in_dim, n_hidden_1, n_hidden_2, out_dim)
# 打印模型信息
print(model_lay)
运行结果:
2、使用nn.ModuleList模型容器
# 使用nn.ModuleList模型容器
# 1) 导入模块
from torch import nn
# 2) 构建模型:通过继承基类nn.Module来构建模型
class Model_lst(nn.Module):
# 把模型中需要用到的层放到构造函数__init__()中
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
# 初始化父类
super(Model_lst, self).__init__()
self.layers = nn.ModuleList([
# 展平(flatten)为向量
nn.Flatten(),
# 第一层
# 全连接层
nn.Linear(in_dim, n_hidden_1),
# bn:batch normalization,批量归一化层
nn.BatchNorm1d(n_hidden_1),
# 激活函数为ReLU
nn.ReLU(),
# 第二层
# 全连接层
nn.Linear(n_hidden_1, n_hidden_2),
# bn:batch normalization,批量归一化层
nn.BatchNorm1d(n_hidden_2),
# 激活函数为ReLU
nn.ReLU(),
# 输出层
# 全连接层
nn.Linear(n_hidden_2, out_dim),
# 激活函数为Softmax
nn.Softmax(dim=1)
])
# 前向传播
def forward(self, x):
# 使用for循环
for layer in self.layers:
x = layer(x)
return x
# 3) 查看模型
# 对一些超参数赋值
in_dim, n_hidden_1, n_hidden_2, out_dim = 28 * 28, 300, 100, 10
# 函数调用,传入输入数据
model_lst = Model_lst(in_dim, n_hidden_1, n_hidden_2, out_dim)
# 打印模型信息
print(model_lst)
运行结果:
3、使用nn.ModuleDict模型容器
# 3、使用nn.ModuleDict模型容器
# 1)导入模块
from torch import nn
# 2) 构建模型
class Model_dict(nn.Module):
# 把模型中需要用到的层放到构造函数__init__()中
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
# 初始化父类
super(Model_dict, self).__init__()
# 激活函数ReLU在模型中应该出现2次,但在定义字典时,只需定义一次,在定义forward函数的列表中则需要出现2次。
self.layers_dict = nn.ModuleDict({
# 展平(flatten)为向量
"flatten": nn.Flatten(),
# 第一层
# 全连接层
"linear1": nn.Linear(in_dim, n_hidden_1),
# bn:batch normalization,批量归一化层
"bn1": nn.BatchNorm1d(n_hidden_1),
# 激活函数为ReLU
"relu": nn.ReLU(),
# 第一层
# 全连接层
"linear2": nn.Linear(n_hidden_1, n_hidden_2),
# bn:batch normalization,批量归一化层
"bn2": nn.BatchNorm1d(n_hidden_2),
# 输出层
# 全连接层
"out": nn.Linear(n_hidden_2, out_dim),
# 激活函数为Softmax
"softmax": nn.Softmax(dim=1)
})
# 前向传播
def forward(self, x):
# 包含层名的列表
layers = ["flatten", "linear1", "bn1", "relu", "linear2", "bn2", "relu", "out", "softmax"]
# 使用for循环
for layer in layers:
x = self.layers_dict[layer](x)
return x
# 3) 查看模型
# 对一些超参数赋值
in_dim, n_hidden_1, n_hidden_2, out_dim = 28 * 28, 300, 100, 10
# 函数调用,传入输入数据
model_dict = Model_dict(in_dim, n_hidden_1, n_hidden_2, out_dim)
# 打印模型信息
print(model_dict)
运行结果:
自定义网路模块
残差网络中的残差块有两种,一种是正常的模块方式,将输入与输出相加,然后应用激活函数ReLU。
# 定义残差模块(正常的模块)
import torch.nn as nn
import torch.nn.functional as F
class ResNetBasicBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride):
super(ResNetBasicBlock, self).__init__()
# 3 * 3 卷积层,stride:步长,padding:补0的圈数
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1)
# 批量归一化层
self.bn1 = nn.BatchNorm2d(out_channels)
# 3 * 3 卷积层,stride:步长,padding:补0的圈数
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1)
# 批量归一化层
self.bn2 = nn.BatchNorm2d(out_channels)
# 前向传播
def forward(self, x):
# 3 * 3 卷积层
output = self.conv1(x)
# 激活函数ReLU(批量归一化层)
output = F.relu(self.bn1(output))
# 3 * 3 卷积层
output = self.conv2(output)
# 批量归一化层
output = self.bn2(output)
# 激活函数ReLU(批量归一化层的输出 + 输入x)
return F.relu(output + x)
另一种是在正常的模块的方式上添加1×1的卷积层来调整通道和分辨率,目的是使输入与输出形状一致。
# 定义残差模块(添加1×1的卷积层,使输入与输出形状一致)
class ResNetDownBolck(nn.Module):
def __init__(self, in_channels, out_channels, stride):
super(ResNetDownBolck, self).__init__()
# 3 * 3 卷积层,stride:步长,padding:补0的圈数
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride[0], padding=1)
# 批量归一化层
self.bn1 = nn.BatchNorm2d(out_channels)
# 3 * 3 卷积层,stride:步长,padding:补0的圈数
self.conv2 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride[1], padding=1)
# 批量归一化层
self.bn2 = nn.BatchNorm2d(out_channels)
# 长跳跃连接层
self.extra = nn.Sequential(
# 1 * 1 卷积层
nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride[0], padding=0),
# 批量归一化层
nn.BatchNorm2d(out_channels)
)
# 前向传播
def forward(self, x):
# 长跳跃连接层
extra_x = self.extra(x)
# 3 * 3 卷积层
output = self.conv1(x)
# 激活函数ReLU(批量归一化层)
output = F.relu(self.bn1(output))
# 3 * 3 卷积层
output = self.conv2(output)
# 批量归一化层
output = self.bn2(output)
# 激活函数ReLU(批量归一化层的输出 + 长跳跃连接层的输出)
return F.relu(output + extra_x)
组合现代经典的ResNet18网络结构
# 组合以上两个模块得到现代经典的ResNet18网络结构
class ResNet18(nn.Module):
def __init__(self):
super(ResNet18, self).__init__()
# 7 * 7 卷积层
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3)
# 批量归一化层
self.bn1 = nn.BatchNorm2d(64)
# 池化过程:将输入图像平均划分成若干个矩形区域。
# 最大池化层:将池化区域的像素点取最大值,对纹理特征信息更加敏感。
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# 第一层:ResNetBasicBlock + ResNetBasicBlock
self.layer1 = nn.Sequential(ResNetBasicBlock(64, 64, 1),
ResNetBasicBlock(64, 64, 1))
# 第二层:ResNetDownBolck + ResNetBasicBlock
self.layer2 = nn.Sequential(ResNetDownBolck(64, 128, [2, 1]),
ResNetBasicBlock(128, 128, 1))
# 第三层:ResNetDownBolck + ResNetBasicBlock
self.layer3 = nn.Sequential(ResNetDownBolck(128, 256, [2, 1]),
ResNetBasicBlock(256, 256, 1))
# 第四层:ResNetDownBolck + ResNetBasicBlock
self.layer4 = nn.Sequential(ResNetDownBolck(256, 512, [2, 1]),
ResNetBasicBlock(512, 512, 1))
# 平均池化层:对池化区域内的图像取平均值,对背景信息更加敏感。
self.avgpool = nn.AdaptiveAvgPool2d(output_size=(1, 1))
# 第六层:全连接层
self.fc = nn.Linear(512, 10)
# 前向传播
def forward(self, x):
# 与图片一一对应,共18层
output = self.conv1(x)
output = self.layer1(output)
output = self.layer2(output)
output = self.layer3(output)
output = self.layer4(output)
output = self.avgpool(output)
output = output.reshape(x.shape[0], -1)
output = self.fc(output)
return output
运行结果:
感受一下网络模块即可。