总结:学习了 PyTorch 中的基本概念和常用功能,张量(Tensor)的操作、自动微分(Autograd)、正向传播、反向传播。通过了解认识LeNet 模型,定义神经网络类,熟悉卷积神经网络的基本结构和训练过程,卷积层和全连接层的创建和运用。在训练网络中,通过反向传播计算梯度,利用优化器根据计算出的梯度更新网络中的参数。--2025/02/12
目录
1. 实验环境
1.1. 导入库
Import numpy as np: 用于处理数组和矩阵运算
Import torch as t :pytorch的核心库,构建和训练深度学习模型
import torch.optim as optim:pytorch的优化器模块,根据计算出的梯度更新神经网络的参数
import torch.nn as nn:PyTorch 提供的神经网络模块,专门用于构建和训练神经网络模型
import torch.nn.functional as F:包含函数操作,如激活函数、损失函数、卷积操作等)
1.2. 版本
torch:2.6.0
Python 3.8.8
jupyter-notebook : 6.3.0
2. 基本概念
2.1. Tensor 张量
Tensor是PyTorch中重要的数据结构,可认为是一个高维数组。它可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)以及更高维的数组。
- 标量(0 维):一个数值,例如 3.14。
- 向量(1 维):一组有序的数值,例如 [1, 2, 3]。
- 矩阵(2 维):一个二维的数值表格,例如 [[1, 2], [3, 4]]。
- 高维数组(n 维):更高维的数组,用于存储更复杂的数据结构,例如图像数据(通常为 4 维:batch_size, channels, height, width)。
# 初始化一个 5x3 的张量
x = t.Tensor(5, 3)
# 赋值为一个 2x2 的初始化矩阵
x = t.Tensor([[1, 2], [3, 4]])
# 获取张量的行数(即第一个维度)
x.size(0)
# 获取张量的列数(即第二个维度)
x.size(1)
# 获取列数的另一种方式
x.size()[1]
# 随机生成一个 5x3 的张量
x = t.rand(5, 3)
y = t.rand(5, 3)
# 三种方法来进行两个矩阵相加:
a = x + y # 方法 1:直接相加
b = t.add(x, y) # 方法 2:使用 PyTorch 自带的加法函数
c = t.Tensor(5, 3) # 初始化一个空张量
t.add(x, y, out=c) # 方法 3:将结果存储到 c 中
Tensor 与 NumPy 的转换
# 创建一个全为 1 的张量
a = t.ones(5)
# 将张量转换为 NumPy 数组
b = a.numpy()
# 创建一个全为 1 的 NumPy 数组
a = np.ones(5)
# 将 NumPy 数组转换为张量
b = t.from_numpy(a)
2.2. Scalar 标量
Scalar(标量)是一个没有维度的单一数值,它是零维的张量。标量在 PyTorch 中通常表示为一个只有一个元素的 Tensor。
2.2.1. 获取标量值
(1) 使用 .item()
从包含单一元素的张量中提取该元素的值:
(2) 使用 .tolist()
将标量张量转换为列表:
# 使用 item() 方法获取标量值
scalar = b[0]
scalar_value = scalar.item()
# 使用 tolist() 方法将标量张量转换为列表
x = t.Tensor([3.14]) # 创建一个包含单一值的张量
scalar_list = x.tolist() # 将其转换为列表
print(scalar_list) # 输出: [3.14]
2.3. Autograd 自动微分
深度学习中的训练过程本质上是通过反向传播求导数,而 PyTorch 提供的 autograd
模块自动为张量计算梯度,从而避免了手动计算导数的复杂过程。
2.3.1. 启动自动微分
在创建张量时,可以通过 requires_grad=True
来启动自动微分功能,表示该张量需要计算梯度。
x = t.ones(2, 2, requires_grad=True) # 创建一个可以计算梯度的张量
2.3.2. 反向传播
反向传播通过调用 backward()
来计算梯度。以下示例展示了如何计算梯度:
2.3.3. 梯度累加和清零
在每次执行 backward()
之后,PyTorch 会将梯度值累加,因此每次反向传播之前需要手动清零梯度。
#清零梯度,以下划线结束的函数是inplace操作,修改自身的值
x.grad.zero_()
3. LeNet 神经网络
LeNet 是一种经典的卷积神经网络(CNN)结构,广泛应用于图像分类任务。LeNet 模型通常由两个卷积层、两个池化层以及三个全连接层组成。
3.1. 基本概念
3.1.1. torch.nn
torch.nn
是 PyTorch 中专门为构建神经网络设计的模块,封装了构建和训练神经网络所需的大量工具,包括各种层(如卷积层、全连接层、激活函数等)和常用的操作(如损失函数、优化器等)。
3.1.2. nn.Module
类
nn.Module
是 torch.nn
中最重要的类,所有的神经网络模型都应该继承自 nn.Module
。我们可以通过继承 nn.Module
来定义自己的网络模型。在继承 nn.Module
时,需要定义以下两个方法:
__init__
:用于定义模型的结构,初始化网络的各个层。forward
:定义前向传播,描述数据如何通过网络流动。
3.1.3. 卷积层 (nn.Conv2d
)
卷积层用于从输入数据中提取空间特征。nn.Conv2d
用于定义二维卷积层。它的常用参数包括:
- in_channels:输入数据的通道数(例如,RGB图像的通道数为3,灰度图像为1)。
- out_channels:卷积层输出的通道数(卷积核的数量)。
- kernel_size:卷积核的大小,通常是一个整数或元组(
height, width
)。
# 卷积层 '1'表示输入通道数为1→灰度图像, '6'表示输出通道数→卷积核的数量,'5'表示卷积核为5*5
self.conv1 = nn.Conv2d(1, 6, 5)
# 卷积层
self.conv2 = nn.Conv2d(6, 16, 5)
3.1.4. 全连接层 (nn.Linear
)
全连接层是神经网络中非常重要的一部分,主要用于将提取到的特征映射到最终的输出。nn.Linear
定义了一个全连接层,其常用参数为:
- in_features:输入特征的数量。
- out_features:输出特征的数量。
# 仿射层/全连接层,y = Wx + b
#16*5*5是卷积层输出的特征图像展平后的维度,表示每个样本通过卷积层处理后的大小。
self.fc1 = nn.Linear(16*5*5, 120)
#120,84,10全连接层的输出维度。10表示模型的预测类别数。10类
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
3.2. 定义神经网络类 Net
import torch.nn as nn
import torch.nn.functional as F
#包含常见函数,比如激活函数relu,池化操作maxpool
class Net(nn.Module):#定义神经网络类Net,继承于nn.Module
def __init__(self):#定义构造函数,用来定义网络各个层
super(Net, self).__init__()#调用父类nn.module的构造函数
# 卷积层 '1'表示输入图片为单通道的灰度图像, '6'表示输出通道数,'5'表示卷积核为5*5
self.conv1 = nn.Conv2d(1, 6, 5)
# 卷积层
self.conv2 = nn.Conv2d(6, 16, 5)
# 仿射层/全连接层,y = Wx + b
#16*5*5是卷积层输出的特征图像展平后的维度,表示每个样本通过卷积层处理后的大小。
self.fc1 = nn.Linear(16*5*5, 120)
#120,84,10全连接层的输出维度。10表示模型的预测类别数。10类
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x): #定义向前传播
# 卷积 -> 激活函数(ReLU) -> 池化(MaxPool)
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
#self.conv1(x):通过卷积层conv1,将输入x进行卷积操作
#F.relu():relu激活函数,将输入的负值变为0,正值不变。
#F.max_pool2d(...,(2,2)):最大池化操作,池化窗口大小2*2
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
# reshape,展平数据,输入全连接层,‘-1’表示自适应
x = x.view(x.size()[0], -1)
#展平的数据进入全连接层,relu激活函数增加非线性
#最后一层输出类别的预测值
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
#创建Net对象,神经网络
net = Net()
#打印出网络的结构显示每一层参数
print(net)
Net( (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1)) (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1)) (fc1): Linear(in_features=400, out_features=120, bias=True) (fc2): Linear(in_features=120, out_features=84, bias=True) (fc3): Linear(in_features=84, out_features=10, bias=True) )
3.2.1 __init__
构造函数
在 __init__
方法中,我们定义了网络的各个层(卷积层、池化层、全连接层)。LeNet 网络一般包含两个卷积层和三个全连接层。
3.2.2. forward
向前传播函数
在 forward
方法中,我们定义了数据如何在网络中流动,包括卷积、池化、激活函数、展平和全连接层的应用。
3.3. 训练网络
3.3.1. 可学习参数
- 权重(weight):网络中的参数,决定了输入和输出之间的关系,影响每一层的输出结果。
- 偏置(bias):每个神经元的额外参数,与权重一起决定神经元的激活值。
3.3.2. 批量处理(Mini-Batch)
批量处理mini-batch:多个样本组成的小批量.
PyTorch 的神经网络层(如卷积层)要求输入的形状是一个 4D 张量(batch_size, channels, height, width)。
只想输入一个样本,input.unsqueeze(0)将batch_size设为1
3.3.3. 损失函数
损失函数:衡量网络输出(预测值)与目标值之间的差距
均方误差 (MSE, Mean Squared Error):用于回归问题,计算预测值与真实值之间的平均平方误差。
交叉熵损失 (Cross-Entropy Loss):用于分类问题,衡量真实标签与预测概率分布之间的差异。
3.3.4. 反向传播
反向传播包括三个步骤:
- 正向传播:先通过前向传播计算出模型的输出和损失。
- 计算梯度:然后通过反向传播计算出每个参数的梯度。梯度的绝对值较大,该参数对损失有很大影响,需要大幅调整
- 参数更新:使用这些梯度来调整网络的参数,使得损失最小化。
#梯度变化
print('反向传播前conv1.bias偏置的梯度')
print(net.conv1.bias.grad)
# 执行一次前向传播
output = net(input)
target = t.arange(0,10).view(1,10).float()
criterion = nn.MSELoss()
# 计算损失
loss = criterion(output, target)
# 执行反向传播(retain_graph=True)用于保留计算图
loss.backward(retain_graph=True)
# 打印反向传播后的梯度
print('反向传播后conv1.bias偏置的梯度')
print(net.conv1.bias.grad)
# 如果需要继续进行其他操作,记得清零梯度
net.zero_grad()
梯度的符号(正负)表示了参数调整的方向:
负梯度:表示 如果减小 当前参数的值,损失函数会减少。当前参数的值较大,应该减少它来减小损失。
正梯度:表示 如果增大 当前参数的值,损失函数会减少。意味着当前参数的值较小,应该增加它来减小损失。
3.3.5. 优化器:SGD
主要任务是 根据反向传播计算出的梯度更新网络中的参数(如权重和偏置),从而使得损失函数逐步降低,最终达到优化目标。
SGD(随机梯度下降)优化器
SGD 是最常见的一种优化器,它的基本思路是每次使用一个 mini-batch(小批量)计算梯度,并使用这个梯度更新网络参数。
import torch.optim as optim
#SGD随机梯度下降,创建优化器,将需要优化的参数传入优化器
#指定学习率learning rate
optimizer = optim.SGD(net.parameters(),lr = 0.01)
#先梯度清零
optimizer.zero_grad()
#前向传播
output = net(input)
#计算损失函数
loss = criterion(output,target)
#反向传播
loss.backward()
#更新参数
optimizer.step()
总结
- LeNet 网络结构:包含卷积层、池化层、全连接层。
nn.Module
和forward
:通过继承nn.Module
和定义forward
方法来实现网络结构。- 损失函数和优化器:通过损失函数(如交叉熵损失)和优化器(如 SGD)来训练网络。