第7节 神经网络
7.1 卷积层
pytorch官网网站卷积层:
Conv2d — PyTorch 2.8 documentation
7.1.1 nn.Conv2d类
nn.Conv2d:是一个类,继承自torch.nn.Module。它是一个可学习的模块,包含了卷积操作所需的可学习参数(权重weight和偏置bias ,默认有偏置),并且将卷积操作封装成一个具有状态的对象。在使用时,需要先实例化一个nn.Conv2d对象,然后将输入数据传入该对象进行计算。而torch.nn.functional.conv2d:是一个函数。它只是单纯地执行卷积计算,没有内部状态,不包含可学习的参数,需要用户手动传入权重和偏置(偏置可选)来完成卷积操作。
nn.Conv2d类核心参数:
- in_channels (int):输入图像的通道数,比如 RGB 彩色图像通常设为 3,灰度图设为 1。
- out_channels (int):卷积操作后输出特征图的通道数,即卷积核(滤波器)的数量。
- kernel_size (int or tuple):卷积核的尺寸,若填 3 表示 3×3 方形卷积核;填 (3,5) 则是高 3、宽 5 的矩形卷积核。
- stride (int or tuple, optional):卷积核在输入上滑动的步长,控制每次移动的像素数,可设为单值(如 2 表示高宽步长均为 2)或元组(如 (2,1) 分设高、宽步长)。默认值:1。
- padding (int, tuple or str, optional):在输入图像四周填充像素的方式 / 数量,填 1 表示上下左右各补 1 层;填 (1,2) 分设高、宽方向补 1 和 2;填'same' 可让输出尺寸与输入同(需配合 stride=1)。默认值:0。
- padding_mode (string, optional):填充的具体模式:'zeros' 是补 0,'reflect' 镜像反射填充,'replicate' 复制边缘像素填充,'circular' 循环填充(较少用)。默认值:'zeros'。
- dilation (int or tuple, optional):卷积核内部元素的间距(空洞卷积参数),设 2 时卷积核会 “扩张”,感受野更大,常用于图像分割等任务。默认值:1。
- groups (int, optional):控制输入输出通道的分组连接数,需满足 in_channels 和 out_channels 能被 groups 整除。设 groups=in_channels 时是 “深度卷积”,可减少计算量。默认值:1。
- bias (bool, optional):是否给卷积输出加可学习的偏置项,通常配合 weight 一起训练,若数据已做中心化处理,也可设 False 减少参数。默认值:True。
动态图演示参数变化的网址:
https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md
卷积前后维度计算公式:
7.1.2 实战演示
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriterdataset = torchvision.datasets.CIFAR10("dataset",train=False,
transform=torchvision.transforms.ToTensor(),download=True)
dataloader = DataLoader(dataset, batch_size=64)class model(nn.Module):def __init__(self):super(model, self).__init__()self.conv1 = Conv2d(in_channels=3,out_channels=6, kernel_size=3, stride=1, padding=0)def forward(self, x):x = self.conv1(x) # 进行卷积操作return xmodel = model() # 创建模型
# 打印网络结构
print(model) # (conv1): Conv2d(3, 6, kernel_size=(3, 3), stride=(1, 1))writer = SummaryWriter("logs")
step = 0
for data in dataloader:imgs, targets = dataoutput = model(imgs) # 进行卷积操作print(imgs.shape) # 卷积前图片大小,torch.Size([64, 3, 32, 32])print(output.shape) # 卷积后图片大小,torch.Size([64, 6, 30, 30]) writer.add_images("input", imgs, step)output = torch.reshape(output, (-1, 3, 30, 30)) writer.add_images("output", output, step)step += 1writer.close()
运行结果:
7.2 Pooling(池化层)
(1)、MaxUnpool(上采样)
MaxUnpool(上采样)在神经网络中起到恢复特征图尺寸的作用,常与最大池化配合使用,尤其是在语义分割、图像生成等需要对特征图进行上采样操作以恢复到原始图像尺寸的任务中。它主要是利用最大池化时记录的索引信息,将池化后的特征图恢复到原来的尺寸,从而保留更多的细节信息。
(2)、AvgPool(平均池化)
AvgPool(平均池化)是对由多个输入平面组成的输入信号执行二维平均池化操作。与最大池化不同,平均池化是计算池化窗口内所有元素的平均值,以此来代替池化窗口内的特征值,从而实现下采样的目的。
(3)、AdaptiveMaxPool2d(自适应最大池化)
AdaptiveMaxPool2d(自适应最大池化)是一种特殊的最大池化方式,它可以根据用户指定的输出大小,自动计算池化窗口大小和步长,将输入特征图变换为指定大小的输出特征图。这使得在不同输入尺寸的情况下,能够灵活地调整输出特征图的大小,方便后续的网络层处理。
(4)、MaxPool(最大池化,下采样)
最大池化(MaxPool) 是卷积神经网络(CNN)中 下采样(Downsampling) 的核心操作之一,通过 保留局部区域最大值,实现以下核心目标:压缩特征图尺寸,降低计算量与参数规模;强化显著特征(如边缘、纹理),提升模型鲁棒性;引入局部不变性(平移、缩放等),增强泛化能力。
7.2.1 nn.MaxPool2d类(最大池化)
nn.MaxPool2d用于对由多个输入平面组成的输入信号执行二维最大池化操作。在卷积神经网络(CNN)中,最大池化是一种常用的下采样技术,其核心作用是在保留重要特征的同时,降低数据的维度,减少计算量,并且在一定程度上提高模型的鲁棒性。
(1)、核心参数:
kernel_size (int或tuple):指定池化窗口的大小。若为整数,例如kernel_size=3,则表示池化窗口的高度和宽度均为3;若为元组,如kernel_size=(2, 3),那么池化窗口的高度为 2,宽度为 3。
stride (int 或 tuple,可选):控制池化窗口在输入数据上滑动的步长。默认值为kernel_size。若为整数,如stride=2,意味着在高度和宽度方向上,池化窗口每次移动 2 个像素;若为元组stride=(1, 2),则在高度方向上每次移动 1 个像素,在宽度方向上每次移动 2 个像素。
padding (int 或 tuple,可选):在输入数据的四周添加填充。默认值为 0,即不进行填充。若为整数padding=1,则在输入数据的上下左右各填充 1 行 / 列;若为元组padding=(2, 1),表示在高度方向上填充 2 行,宽度方向上填充 1 列。
dilation (int 或 tuple,可选):定义池化窗口中元素的间距,默认值为 1。当dilation > 1时,池化窗口中的元素不再紧密相连,而是有一定的间隔,类似于空洞卷积的概念,但在最大池化中较少使用。若为整数dilation=2,池化窗口内元素的间距在高度和宽度方向上均为 2;若为元组dilation=(1, 2),则高度方向间距为 1,宽度方向间距为 2。
return_indices (bool,可选):若设置为True,则在返回池化结果的同时,还会返回最大值在原输入张量中的索引。这在后续进行反池化(如nn.MaxUnpool2d)操作时非常有用,默认值为False。
ceil_mode (bool,可选):决定输出形状的计算方式。若为True,在计算输出形状时使用向上取整(ceil);若为False,则使用向下取整(floor),默认值为False。在处理输入数据尺寸不能被步长或池化窗口大小整除的情况时,该参数会影响最终输出的尺寸。
(2)、输出形状计算
假设输入张量的形状为(N, C, H_in, W_in),其中N是批量大小,C是通道数,H_in和W_in分别是输入特征图的高度和宽度。经过nn.MaxPool2d操作后,输出张量的形状为(N, C, H_out, W_out),其中H_out和W_out的计算公式如下:
例如,输入张量形状为(16, 32, 64, 64)(批量大小为 16,通道数为 32,特征图大小为 64×64),使用nn.MaxPool2d(kernel_size=3, stride=2, padding=1)进行池化操作。根据上述公式计算:
(3)、为什么要进行最大池化?最大池化的作用是什么?
最大池化的目的是保留输入的特征,同时把数据量减小(数据维度变小),对于整个网络来说,进行计算的参数变少,会训练地更快,(池化最大的作用是增大感受野,让后面的卷积看到更全局的内容。)
如上面案例中输入是5x5的,但输出是3x3的,甚至可以是1x1的
类比:1080p的视频为输入图像,经过池化可以得到720p,也能满足绝大多数需求,传达视频内容的同时,文件尺寸会大大缩小
池化一般跟在卷积后,卷积层是用来提取特征的,一般有相应特征的位置是比较大的数字,最大池化可以提取出这一部分有相应特征的信息
池化不影响通道数
池化后一般再进行非线性激活
7.2.2 nn.MaxPool池化实战
(1)、简单的池化操作
import torch
from torch import nn
from torch.nn import MaxPool2d# 最大池化无法对long数据类型进行实现,将input变成浮点数的tensor数据类型
input = torch.tensor([[1, 2, 0, 3, 1],[0, 1, 2, 3, 1],[1, 2, 1, 0, 0],[5, 2, 3, 1, 1],[2, 1, 0, 1, 1]], dtype=torch.float32)
input = torch.reshape(input, (-1, 1, 5, 5)) # -1表示torch计算batch_size
print(input.shape)# 搭建神经网络
class Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=True)def forward(self, input):output = self.maxpool1(input)return output
# 创建神经网络
tudui = Tudui()
output = tudui(input)
print(output)
运行结果:
(2)、对CIFAR10 实现最大池化
import torchvision
from torch import nn
from torch.nn import MaxPool2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriterdataset = torchvision.datasets.CIFAR10("dataset", train=False, download=True,transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=64)# 搭建神经网络
class Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.maxpool1 = MaxPool2d(kernel_size=3, ceil_mode=True)def forward(self, input):output = self.maxpool1(input)return output# 创建神经网络
tudui = Tudui()writer = SummaryWriter("logs")
step = 0
for data in dataloader:imgs, targets = datawriter.add_images("input", imgs, step)# output尺寸池化后不会有多个channel,原来是3维的图片,经过最大池化后还是3维的,# 不需要像卷积一样还要reshape操作(影响通道数的是卷积核个数)output = tudui(imgs)writer.add_images("output", output, step)step = step + 1writer.close()
运行结果:
7.3 Non-linear Activations(非线性激活)
在神经网络中,线性变换(如卷积、全连接层)的叠加仍然是线性运算,无法拟合复杂的非线性关系。非线性激活函数通过对线性输出进行非线性转换,赋予模型拟合复杂数据分布的能力,是深度神经网络能够学习复杂特征的核心原因之一。
官网:torch.nn — PyTorch 2.8 documentation
常见的非线性激活函数:
(1)、ReLU函数
可参考博客:神经网络 - 激活函数(ReLU 函数)-CSDN博客
1、基本定义:
ReLU(Rectified Linear Unit,修正线性单元)是最常用的激活函数之一,数学表达式为:
ReLU(x)=max(0,x)
即输入大于 0 时输出该值,输入小于等于 0 时输出 0。
2. 特点
计算高效:仅需判断输入是否为正,无需复杂运算,训练和推理速度快。
稀疏激活:负输入会被直接置为 0,使部分神经元 “休眠”,减少冗余计算,提高模型泛化能力。
缓解梯度消失:正区间的梯度恒为 1(不会像 Sigmoid 那样梯度衰减),有助于深层网络的训练。
3. 缺点
死亡 ReLU 问题:若神经元长时间接收负输入,其权重更新可能停滞,导致该神经元永久 “死亡”(始终输出 0)。
输出无上下界:可能导致某些神经元输出值过大,影响模型稳定性。
(2)、Sigmoid 激活函数
1. 基本定义
Sigmoid 函数将输入映射到(0, 1)区间,数学表达式为:
常用于二分类任务的输出层,表示 “属于某一类的概率”。
2. 特点
输出有界:结果在(0, 1)之间,可直接解释为概率。
平滑连续:导数处处可求,便于反向传播。
3. 缺点
梯度消失:当输入绝对值较大时(x >> 0或x << 0),导数接近 0,导致深层网络权重难以更新。
输出非零均值:输出均值约为 0.5,可能使后续层的输入偏向正值,影响梯度下降效率。
计算成本高:涉及指数运算,速度慢于 ReLU。
(3)、ReLU与Sigmoid对比
7.3.1 ReLU函数代码演示
核心参数:输入:(N,*) N 为 batch_size,*不限制可以是任意
import torch
from torch import nn
from torch.nn import ReLU
input = torch.tensor([[1,-0.5],[-1,3]])
input = torch.reshape(input,(-1,1,2,2)) #input必须要指定batch_size,-1表示batch_size自己算,1表示是1维的
print(input.shape) #torch.Size([1, 1, 2, 2])# 搭建神经网络
class Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.relu1 = ReLU() #inplace默认为Falsedef forward(self,input):output = self.relu1(input)return output
# 创建网络
tudui = Tudui()
output = tudui(input)
print(output)
运行结果:
7.3.2 Sigmoid函数
import torch
import torchvision.datasets
from torch import nn
from torch.nn import Sigmoid
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriterdataset = torchvision.datasets.CIFAR10("dataset", train=False, download=True,transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=64)# 搭建神经网络
class Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.sigmoid1 = Sigmoid() # inplace默认为Falsedef forward(self, input):output = self.sigmoid1(input)return output# 创建网络
tudui = Tudui()writer = SummaryWriter("logs")
step = 0
for data in dataloader:imgs, targets = datawriter.add_images("input", imgs, global_step=step)output = tudui(imgs)writer.add_images("output", output, step)step = step + 1writer.close()
运行结果:
7.4 Linear Layers(线性层)
nn.Linear是 PyTorch 中最基础的全连接层,也称为仿射变换层。它对输入张量进行一次线性变换,计算公式为:output=xWT+b,其中W是形状为(out_features,in_features)的权重矩阵,b是长度为out_features的偏置向量(若bias=True)。
(1)、构造函数参数
in_features:输入样本的尺寸,即输入张量最后一个维度的大小。
out_features:输出样本的尺寸,即输出张量最后一个维度的大小。
bias:是否添加偏置项,默认为True。若设置为False,则该层不会学习附加的偏置项。
device:指定权重和偏置所在设备,若为None,默认继承父模块设备。
dtype:指定权重和偏置的数据类型,若为None,默认继承父模块dtype。
d代表特征数,L代表神经元个数,K和b在训练过程中神经网络会自行调整,以达到比较合理的预测。
代码实例 vgg16 model
(2)、输入与输出
输入:类型为浮点型张量,形状为(…,in_features),其中 “…” 可以是任意维度,最后一维的长度必须与in_features大小一致。
输出:类型为浮点型张量,形状为(…,out_features),即除了最后一维变为out_features外,其他维度与输入相同。
(3)、权重与偏置
权重:可通过.weight方法调取,形状为(out_features,in_features),默认按均匀分布U(−in_features1,in_features1)初始化。
偏置:若bias=True,可通过.bias方法调取,形状为(out_features),默认初始化为全零。
(4)、线性层作用:
线性层(Linear Layer),也被称为全连接层(Fully Connected Layer),在深度学习中具有重要作用,以下是其主要作用介绍:
(1)、实现特征变换与映射
降维或升维:线性层可以根据设置的输入特征数(in_features)和输出特征数(out_features)对数据进行降维或升维操作。例如,在图像识别任务中,经过一系列卷积层处理后,会得到高维的特征图,使用线性层可以将这些高维特征映射到低维空间,减少数据的维度,降低计算复杂度,同时提取出更具代表性的特征 。相反,也可以通过线性层增加特征维度,丰富数据的表示。
特征空间变换:将输入数据从一个特征空间映射到另一个特征空间,使得在新的特征空间中,数据的模式更容易被发现和学习。比如在自然语言处理中,将词向量通过线性层进行变换,使其能够更好地捕捉语义信息。
(2)、作为分类器或回归器
分类任务:在线性层后接激活函数(如 Softmax),可以将线性层的输出转换为概率分布,用于判断输入数据属于不同类别的可能性。例如在手写数字识别中,线性层的输出经过 Softmax 函数后,会得到输入图像属于 0 - 9 每个数字类别的概率,从而实现对数字的分类。
回归任务:在回归任务中,线性层可以直接输出一个或多个连续值。比如预测房价时,输入房屋的各种特征(如面积、房间数量等),通过线性层的计算,直接输出预测的房价数值。
(3)、组合和整合信息
在深度神经网络中,不同层的神经元可能学习到不同方面的信息,线性层可以将这些来自不同神经元的信息进行加权组合,从而得到更全面、更有价值的表示。例如在循环神经网络(RNN)中,线性层可以将隐藏状态的信息进行整合,为后续的决策提供依据。
(4)、衔接不同类型的网络层
线性层常常作为卷积神经网络(CNN)、循环神经网络(RNN)等与最终输出层之间的桥梁。比如在 CNN 中,卷积层和池化层主要用于提取图像的局部特征,而线性层可以将这些局部特征整合起来,得到全局的特征表示,进而用于分类或其他任务;在 RNN 中,线性层可以将循环单元输出的隐藏状态转换为符合任务需求的输出形式。
7.4.1 torch.flatten()(摊平)
torch.flatten是 PyTorch 中用于将张量维度“摊平”的函数,它能将输入张量的指定维度范围合并为一个连续的一维向量(或保留部分维度,仅摊平特定范围)。其核心作用是打破张量的多维结构,便于后续处理(如送入线性层)。
计算公式可理解为:将输入张量中start_dim到end_dim之间的所有维度 “挤压” 成一个维度,新维度的大小为这些维度的乘积。
(1)、参数:
input:必需,待摊平的输入张量(任意维度)。
start_dim:可选,默认值为 0,指定开始摊平的维度(从 0 开始计数)。
end_dim:可选,默认值为 - 1(表示最后一个维度),指定结束摊平的维度。
(2)、输入与输出
- 输入:形状为(d0,d1,...,dn)的任意维度张量。
- 输出:形状由start_dim和end_dim决定:
- 若start_dim=0且end_dim=-1(默认),输出为一维张量,形状为(d0×d1×...×dn)。
- 若指定部分维度(如start_dim=1,end_dim=2),则仅摊平该范围,其他维度保留。例如输入形状为(b,c,h,w),start_dim=1,end_dim=2,输出形状为(b,c×h,w)。
(3)、例子
示例 1:默认参数(完全摊平为一维)
import torch
x = torch.randn(2, 3, 4) # 形状:(2, 3, 4)
x_flat = torch.flatten(x) # 等价于 torch.flatten(x, 0, -1)
print(x_flat.shape) # 输出:torch.Size([24]) (2×3×4=24)
示例 2:指定部分维度摊平(保留批次维度)
x = torch.randn(8, 3, 28, 28) # 假设是8张3通道28×28的图像
x_flat = torch.flatten(x, start_dim=1) # 从第1维开始摊平(包含1、2、3维)
print(x_flat.shape) # 输出:torch.Size([8, 2352]) (3×28×28=2352)
示例 3:摊平中间维度
x = torch.randn(2, 3, 4, 5)
x_flat = torch.flatten(x, start_dim=1, end_dim=2) # 仅摊平1、2维
print(x_flat.shape) # 输出:torch.Size([2, 12, 5]) (3×4=12)
7.4.2 Linear Layers实战演示
import torch
import torchvision.datasets
from torch import nn
from torch.nn import Linear
from torch.utils.data import DataLoaderdataset = torchvision.datasets.CIFAR10("dataset", train=False, transform=torchvision.transforms.ToTensor(),download=True)
dataloader = DataLoader(dataset, batch_size=64, drop_last=True)class Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.linear1 = Linear(196608, 10)def forward(self, input):output = self.linear1(input)return outputtudui = Tudui()for data in dataloader:imgs, targets = dataprint(imgs.shape) # torch.Size([64, 3, 32, 32])# output = torch.reshape(imgs,(1,1,1,-1)) # 想把图片展平# print(output.shape) # torch.Size([1, 1, 1, 196608])# output = tudui(output)# print(output.shape) # torch.Size([1, 1, 1, 10])output = torch.flatten(imgs) # 摊平print(output.shape) # torch.Size([196608])output = tudui(output)print(output.shape) # torch.Size([10])
运行结果: