Pytorch框架03-网络的搭建(nn.Module/卷积层/池化层/非线性激活/线性层/CIFAR-10分类模型搭建)
三、Pytorch网络的搭建
3.1 nn.Module-神经网络基本骨架
import torch
import torch.nn as nn
"""
1.nn.Module:Base class for all neural network modules.Your models should also subclass this class.
2.一般要实现__init__(self)和forward(self)方法
"""
class sinFunction(nn.Module):
def __init__(self):
super().__init__()
def forward(self, input):
return torch.sin(input)
sf = sinFunction()
input = torch.tensor([0.25 * torch.pi])
print(sf(input)) #这一行代码调用了 sf(input),即调用了 sinFunction 类的 forward 方法,并将输入 input 传递给它。
3.2 神将网络-卷积层
- torch.nn.functional中卷积
torch.nn.functional.conv2d(*input*, *weight*, *bias=None*, *stride=1*, *padding=0*, *dilation=1*, *groups=1*) → Tensor
参数:
**minibatch:**批次大小,即一次处理的样本数量。
**in_channels:**输入通道的数量(例如RGB图像为3)。
**iH:**输入图像或特征图的高度。
**iW:**输入图像或特征图的宽度。
**out_channels:**输出通道的数量,也就是卷积核的数量,这决定了输出张量的深度。
**groups:**用于分组卷积的参数。如果 groups = 1,则为标准卷积。如果 groups > 1,则输入通道和卷积核将被分成多个组,每组独立进行卷积操作。
**in_channels:**输入通道的数量(与输入张量中的 in_channels 对应)。
**kH:**卷积核的高度(即卷积核的大小)。
**kW:**卷积核的宽度(即卷积核的大小)。
bias 是一个可选的张量,在卷积操作之后加到输出上。它的形状为:bias = (out_channels)
这个张量的大小与输出通道的数量相同。默认情况下,bias 是 None,如果提供了,它将加到卷积结果上。
stride 定义了卷积核滑动时每次移动的步幅。步幅可以是一个单独的数值,也可以是一个元组:
1.如果是单一的数值(例如 stride = 1),则步幅在高度和宽度上都是相同的。
2.如果是元组 (sH, sW),则 sH 表示垂直方向上的步幅,sW 表示水平方向上的步幅。
import torch
import torch.nn.functional as F
# torch.nn.functional.conv2d(input, weight, bias=None, stride=1, padding=0, dilation=1, groups=1) →
# 输入参数:(input) = (minibatch, in_channels, iH, iW)
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]])
input = torch.reshape(input, (1, 1, 5, 5)) # 一次处理一个样本,一个通道
# 卷积核:(weight) = (out_channels, in_channels/groups, kH, kW)
weight = torch.tensor([[1, 2, 1],
[0, 1, 0],
[2, 1, 0]])
weight = torch.reshape(weight, (1, 1, 3, 3)) # 一个卷积盒,标准卷积
d = F.conv2d(
input=input,
weight=weight,
stride=1, # 步长为1
padding=0, # 不做填充
bias=None # 不添加偏振
)
print(d)
- torch.nn中卷积
卷积的作用:
- 特征提取:卷积帮助网络从原始图像中提取低级到高级的特征(如边缘、纹理、形状等)。
- 局部感知与共享权重:卷积通过局部连接和权重共享,使得特征学习更高效且具有平移不变性。
- 效率:卷积操作能够在减少计算复杂度的同时提取图像中的重要信息,适合处理大规模图像数据。
1.方法:
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*)
2.公式:
3.常用参数:
- **in_channels (int):**这是输入图像的通道数。
例如,对于RGB图像,in_channels 为 3,因为每个图像有3个通道(红、绿、蓝)。如果是灰度图像,in_channels 为 1。
- **out_channels (int):**这是卷积操作生成的输出通道数。
它表示卷积层会生成多少个不同的特征图(每个输出通道对应一个特征图)。
例如,如果 out_channels = 64,那么卷积操作会生成64个不同的输出特征图。
- kernel_size (int 或 tuple):(随机初始化)
卷积核的大小,控制每次卷积操作时核的覆盖范围。如果设置为一个整数 k,则卷积核的大小为 k x k。如果设置为元组 (kH, kW),则分别表示卷积核的高度和宽度。
例如,kernel_size = 3 表示3x3卷积核,kernel_size = (3, 5) 表示高度为3、宽度为5的卷积核。
- stride (int 或 tuple, optional):
步幅控制卷积核在输入数据上的滑动速度。步幅越大,输出特征图的尺寸越小。默认值为1,表示每次卷积核滑动一个像素。
如果设置为一个整数 s,则步幅在高度和宽度上都是 s。
如果设置为元组 (sH, sW),则分别控制卷积核在垂直方向和水平方向上的步幅。
- padding (int, tuple 或 str, optional):
填充用于在输入数据的边缘添加额外的像素,通常是为了保持输出的空间尺寸或者避免特征丢失。
如果设置为整数 p,则表示在四个边上都添加 p 个像素的零填充。
如果设置为元组 (pH, pW),则表示垂直方向和水平方向上的填充分别为 pH 和 pW。
有些框架也允许使用字符串 same 或 valid 来自动确定填充:
'same':填充使得输出特征图的空间尺寸与输入相同。
'valid':不使用填充,输出特征图的尺寸会减小。
4.案例:
from PIL import Image
import torch.nn as nn
from torch.nn import Conv2d
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
"""
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)
"""
# 将图片变为6通道展示
# 1.将图片转换为torch
img = Image.open("data/tyut.png")
to_tensor = transforms.ToTensor()
img_tensor = to_tensor(img)
# 2.创建网络
class net(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = Conv2d(
in_channels=3, # 输入通道数
out_channels=6, # 6层卷积核
kernel_size=3, # 卷积核大小3*3
stride=1 # 步长为1
)
def forward(self, x):
return self.conv1(x)
# 3.输出结果
n = net()
output = n(img_tensor)
print(output.shape)
3.3 神将网络-最大池化层
最大池化(Max Pooling)是一种常用于卷积神经网络(CNN)中的降维操作
- 作用:
- 减少计算量:通过池化操作,减少了特征图的尺寸,降低了后续层的计算量和内存消耗。
- 防止过拟合:池化层通过去除一些细节信息,增强了模型的泛化能力,防止过拟合。
- 保留重要特征:最大池化通过选取局部区域的最大值,保留了图像中的显著特征(如边缘、角点等),减少了不重要的噪声。
- 空间不变性:通过池化,模型对图像的微小平移、旋转等变换更加鲁棒,即具有一定的空间不变性。
- 方法:
torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
- 参数:
1.kernel_size (Union[int, Tuple[int, int]])
作用:定义池化窗口的大小。它指定了池化操作时考虑的区域的大小。
2.stride (Union[int, Tuple[int, int]])
作用:定义池化窗口滑动的步幅,即每次池化窗口移动的距离。默认情况下,步幅等于 kernel_size,即池化窗口每次移动的距离与窗口大小相同。
3.padding (Union[int, Tuple[int, int]])
作用:在输入张量的边缘添加零填充。这个参数用来处理边缘像素,保证池化操作可以覆盖到输入的边缘部分。对于最大池化来说,这种填充通常是隐式的。
4.dilation (Union[int, Tuple[int, int]])
作用:控制池化窗口元素之间的间距。通过增加窗口中元素的间距,池化操作会跳过一些中间值,从而能够“扩展”池化窗口的覆盖范围。
5.ceil_mode (bool)
作用:控制池化输出尺寸的计算方法。当 ceil_mode=True 时,输出尺寸使用向上取整的方式计算;否则,使用向下取整(默认行为)。
-
过程:
1.将池化核与覆盖数据部分作比较,取其最大部分作为输出
2.从第0行0列进行,依次按先行后列的顺序,按照步长为stride(默认与kernel_size一致)
3.对于输入数据包含四个维度
(N,C,Hin,Win)
【批次大小,通道数,高度,宽度】 -
**案例1:**将下述矩阵分别做池化操作(ceil_mode=True或ceil_mode=False)
import torch
from torch import nn as nn
data = 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)
re_data = torch.reshape(data, (1, 1, 5, 5))
class MaxPool(nn.Module):
def __init__(self):
super().__init__()
self.maxPool = nn.MaxPool2d(kernel_size=3, ceil_mode=True)
def forward(self, data):
return self.maxPool(data)
maxPool = MaxPool()
res = maxPool(re_data)
print(res)
/*
tensor([[[[2., 3.],
[5., 1.]]]])
*/
- **案例2:**对CIFAR10的测试集的图片做最大池化操作
import torchvision
from torch import nn as nn
from torch.utils.data.dataloader import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 1.加载数据
data = torchvision.datasets.CIFAR10('data-train', train=False, transform=torchvision.transforms.ToTensor(), download=True)
data_loader = DataLoader(data, batch_size=64)
# 2.池化层网络
class MaxPool(nn.Module):
def __init__(self):
super().__init__()
self.maxPool = nn.MaxPool2d(kernel_size=3, ceil_mode=True)
def forward(self, data):
return self.maxPool(data)
# 3.将数据加载到网络
writer = SummaryWriter('logs')
maxPool = MaxPool()
step = 0
for x in data_loader:
imgs, labels = x
pool = maxPool(imgs)
writer.add_images('Pooling', pool, step)
step += 1
writer.close()
3.4 神经网络-非线性激活
神经网络中的非线性激活函数用于引入非线性因素,从而使得神经网络能够学习和表示复杂的模式和关系。没有非线性激活函数,神经网络即使拥有多个层,也只会表现为一个简单的线性变换,无法解决复杂的任务。
非线性激活函数有多个,如ReLU函数,ELU函数等,下面介绍ReLU函数的非线性激活:
ReLU函数:torch.nn.ReLU(inplace=False)]
f
(
x
)
=
m
a
x
(
0
,
x
)
f(x) = max(0,x)
f(x)=max(0,x)
- 使用方法:
import torch
from torch import nn as nn
input = torch.tensor([[-1, 0.2, -0.3], [1, 3, -2], [2, -1, -2]])
class ReLUTest(nn.Module):
def __init__(self):
super().__init__()
self.relu = nn.ReLU()
def forward(self, input):
return self.relu(input)
relu = ReLUTest()
res = relu(input)
print(res)
#结果:
tensor([[0.0000, 0.2000, 0.0000],
[1.0000, 3.0000, 0.0000],
[2.0000, 0.0000, 0.0000]])
- **案例:**使用Sigmoid对CIFAR10的测试集的图片做非线性激活
import torchvision
from torch.utils.data.dataloader import DataLoader
from torch.utils.tensorboard import SummaryWriter
from torch import nn as nn
# 1.加载数据集
data = torchvision.datasets.CIFAR10('data-train', train=False, transform=torchvision.transforms.ToTensor(),
download=True)
data_loader = DataLoader(data, batch_size=64)
# 2.构建网络
class SigmoidTest(nn.Module):
def __init__(self):
super().__init__()
self.sigmoid = nn.Sigmoid()
def forward(self, input):
return self.sigmoid(input)
# 3.得出结果
net = SigmoidTest()
writer = SummaryWriter('logs')
step = 0
for x in data_loader:
imgs, labels = x
res = net(imgs)
writer.add_images('非线性激活', res, step)
step += 1
writer.close()
3.5 神经网络-线性层
线性层(Linear Layer)是神经网络中的基本构建模块之一,也常被称为全连接层(Fully Connected Layer)。它的主要作用是对输入数据进行线性变换,通过一组权重和偏置来生成输出。
- 线性层的数学表达式:
假设输入为向量 x,权重矩阵为 W,偏置为 b,输出为 y,那么线性层的运算可以表示为:
y
=
W
x
+
b
y=Wx+b
y=Wx+b
- 线性变换:
线性层对输入进行线性变换,即通过矩阵乘法和加法运算将输入映射到一个新的空间。通过训练过程中的权重更新,网络能够学习到如何将输入数据映射到合适的输出空间。
- 方法:
torch.nn.Linear(in_features, out_features, bias=True)
in_features:这个参数表示输入特征的数量,也就是每个输入样本的特征维度。例如,如果输入是一个包含10个特征的向量,那么in_features就是10。
out_features:这个参数表示输出特征的数量,也就是该层输出的维度或神经元的数量。比如,如果希望该层的输出是一个包含5个特征的向量,那么out_features就是5。
bias:是否添加偏振
在进行线性变换时,它们的权重W和偏置b由下方公式生成:

通过上述图片的计算,可以得到新维度下的特征值
- 输入数据与输出数据
- **案例:**将图片中的特征值个数通过线性变换得到10
import torch
import torchvision
from PIL import Image
from torch import nn as nn
img = Image.open('data/tyut.png')
to_tensor = torchvision.transforms.ToTensor()
tensor_img = to_tensor(img)
print(tensor_img.shape) # torch.Size([3, 1280, 1276])
print(torch.reshape(tensor_img, (1, -1)).shape) # torch.Size([1, 4899840])
class LinearTest(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(4899840, 10)
def forward(self, input):
return self.linear(input)
test = LinearTest()
print(test(torch.reshape(tensor_img, (1, -1))).shape) #torch.Size([1, 10])
3.6 CIFAR-10分类模型搭建
CIFAR-10 是一个广泛使用的计算机视觉数据集,包含 10 个类别的 60,000 张彩色图像。每个类别有 6,000 张图像,图像的大小是 32x32 像素。这个数据集常用来训练和评估机器学习模型,尤其是卷积神经网络(CNN)。
上述为网络的构建过程:
- 3@32x32 -> 32@32x32 的卷积操作
nn.Conv2d(in_channels=3, out_channels=32, kernel_size=5, stride=1, padding=2)
"""
1.输入通道为3, 输出通道为32,卷积核的大小为5,dilation=1(非空洞卷积)
2.通过下述公式可以计算padding和stride
32 = (32 + 2*p -1*(5-1)-1)/s +1 =》 31*s = 27 + 2*p
结果:s=1 p=2 / s=2 p=17.5(舍去)
"""
- 32@32x32 -> 32@16x16的池化操作
nn.MaxPool2d(kernel_size=2, stride=1, padding=2)
"""
1. 池化核大小为2
"""
- 32@16x16 -> 32@16x16的卷积操作
nn.Conv2d(in_channels=16, out_channels=16, kernel_size=5, stride=1, padding=2),
"""
1.输入通道为32, 输出通道为32,卷积核的大小为5,dilation=1(非空洞卷积)
2.通过下述公式可以计算padding和stride
16 = (16 + 2*p -1*(5-1)-1)/s +1 =》 15*s = 11 + 2*p
结果:s=1 p=2 / s=2 p=9.5(舍去)
"""
- 32@16x16 -> 32@8x8的池化操作
nn.MaxPool2d(kernel_size=2)
- 32@8x8 -> 64@8x8的卷积操作
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=5, stride=1, padding=2)
- 64@8x8 -> 64@4x4的池化操作
nn.MaxPool2d(kernel_size=2)
- 64@4x4变为1维张量
nn.Flatten()
"""
shape=64*4*4=1024
"""
- 线性池将1024特征 -> 64特征 -> 10特征
nn.Linear(in_features=1024, out_features=64),
nn.Linear(in_features=64, out_features=20)
- 网络搭建源码
import torchvision
from torch import nn as nn
from torch.utils.data import DataLoader
# 1.搭建网络
class CIFAR10_test(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels=3, out_channels=32, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=5, stride=1, padding=2),
nn.MaxPool2d(kernel_size=2),
nn.Flatten(),
nn.Linear(in_features=1024, out_features=64),
nn.Linear(in_features=64, out_features=10)
)
def forward(self, input):
return self.model(input)
# 2.加载数据集
data = torchvision.datasets.CIFAR10('data-train', train=False, transform=torchvision.transforms.ToTensor(),
download=True)
data_loader = DataLoader(data, batch_size=64)
# 3.输出结果
testNet = CIFAR10_test()
for x in data_loader:
imgs, labels = x
res = testNet(imgs)
print(res.shape)