一、数据处理
DataSet的作用:
1.获得每一个数据及其label
2.告诉我们总共有多少个数据
DateLoader的作用:为后面的网络提供不同的网络形式
下面以CIFAR10数据集来进行演示(CIFAR10数据集是包含了60000个3*32*32的有色图片,有10个类别,其中每类6000张图片。50000张训练图片和10000张测试图片)

从下图可以看出,点进CIFAR10数据集类的getitem方法返回的是图片和所属的标签

import torchvision
train_set = torchvision.datasets.CIFAR10("dataset", train=True, download=True)
test_set = torchvision.datasets.CIFAR10("dataset", train=False, download=True)
print(test_set[0])
print(test_set[1])
print(test_set[2])
img, target = test_set[0]
print(test_set.classes[target])
img.show()
可以看到,打印出来图片是PIL类型,但是我们Pytorch中用的是图片的张量形式,所以我们可以把数据集转换成tensor形式
具体代码修改如下
import torchvision
dataset_transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()
])
train_set = torchvision.datasets.CIFAR10("dataset", train=True, transform=dataset_transform, download=True)
test_set = torchvision.datasets.CIFAR10("dataset", train=False, transform=dataset_transform, download=True)
而DataLoader的作用是对数据集进行一个打包操作,假设batch_size设置为4,图解如下

下面代码示例
import torchvision
from torch.utils.data import DataLoader
dataset_transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()
])
train_set = torchvision.datasets.CIFAR10("dataset", train=True, transform=dataset_transform, download=True)
test_set = torchvision.datasets.CIFAR10("dataset", train=False, transform=dataset_transform, download=True)
test_loader = DataLoader(test_set, batch_size=4, shuffle=True)
for data in test_loader:
imgs, targets = data
print(imgs.shape)
print(targets)

总结一下,就是dataloader只是对dataset进行一个打包操作,里面的数据量是一样的
二、神经网络模型创建
卷积层就像 "特征探测器"比如看一张猫的照片时,它不会一下子看全整张图,而是用很多小 "过滤器"(比如检测边缘、颜色块、纹理的小工具)在图片上滑动,每次只关注一小片区域。这样就能提取出图片里的关键特征,比如猫的胡须边缘、耳朵的形状、毛色的深浅变化等
池化层就像 "信息精简器"卷积层提取出的特征可能很细致但数据量太大,池化层会把相邻的信息合并一下(比如只保留一片区域里最亮的点),让特征变 "浓缩"。比如把一张大照片缩成小照片,但重要特征(像猫的眼睛位置)还能保留,这样既能减少参数量从而减少计算量,又能让特征更稳定
全连接层就像 "最终决策者"经过前面两层处理后,已经得到了很多关键特征(比如 "有胡须"" 三角形耳朵 ""毛茸茸")。全连接层会把这些特征全部连接起来做综合判断,比如把所有特征汇总后得出结论:"这张图里有 90% 的概率是一只猫"
简单说,卷积层负责 "找特征",池化层负责 "精简特征",全连接层负责 "用特征做判断"
非线性激活把非线性激活想象成给神经网络 "注入活力" 的过程。
假设没有非线性激活,不管神经网络有多少层,本质上都相当于做一次简单的线性计算(就像 y=ax+b 这样的直线关系)。这样的网络再深,也只能处理简单的线性问题,比如 "房价和面积成正比" 这种关系。
但现实世界的问题大多是复杂的非线性关系 —— 比如 "图片里是不是猫",没法用简单的直线公式来表达。
这时候非线性激活就派上用场了,它就像一个 "转换器",能把输入的数值做一番非线性处理后再输出。比如最常用的 ReLU 激活函数,它会把所有负数变成 0,正数保持不变,就像一个 "过滤器"。
有了这种非线性转换,神经网络才能学会处理复杂的曲线关系、组合特征。比如识别猫的时候,它能把 "三角形耳朵 + 毛茸茸 + 胡须" 这些特征用非线性的方式组合起来,而不是简单地把这些特征加加减减。
那么为什么需要非线性?
比如识别猫的时候:
- 卷积层可能提取出 "有胡须(x1=2)"、"耳朵是三角形(x2=3)"、"背景是草地(x3=-1)"
- 经过激活函数后,ReLU 会保留 x1=2、x2=3,过滤 x3=-1
- 这些非线性处理后的特征再传给全连接层,才能组合出 "有胡须且耳朵是三角形 = 猫" 的复杂判断
如果没有激活函数,就只能做简单的加法(2+3+(-1)=4),无法体现 "且" 这种非线性关系。
简单说:没有非线性激活,神经网络就是个 "死脑筋" 只会算直线;有了它,神经网络才能 "灵活思考",处理各种复杂问题。
接下来我们把刚才数据处理部分的batch_size改为64,用一个线性层实现一下将img分为10类。
首先展平imgs,即展平一个批次的64张图片(64*3*32*32),为(1,1,1,196608),然后通过torch提供的线性层来实现变换
import torch
import torchvision
from torch import nn
from torch.nn import Flatten, Linear
from torch.utils.data import DataLoader
dataset_transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()
])
train_set = torchvision.datasets.CIFAR10("dataset", train=True, transform=dataset_transform, download=True)
test_set = torchvision.datasets.CIFAR10("dataset", train=False, transform=dataset_transform, download=True)
test_loader = DataLoader(test_set, batch_size=64, shuffle=True)
class MyModel(nn.Module):
def __init__(self):
super(MyModel, self).__init__()
self.model = Linear(196608, 10)
def forward(self, x):
x = self.model(x)
return x
model = MyModel()
for data in test_loader:
imgs, targets = data
print(imgs.shape)
# output = torch.reshape(imgs, (1, 1, 1, -1))
output = torch.flatten(imgs)
print(output.shape)
output = model(output)
print(output.shape)
三、误差反向传播和优化器
创建网络(VGG16)
# 网络
class MyConvModel(nn.Module):
def __init__(self):
super(MyConvModel, self).__init__()
self.model = Sequential(
Conv2d(3, 32, 5, 1, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, 1, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, 1, padding=2),
MaxPool2d(2),
Flatten(),
Linear(64 * 4 * 4, 64),
Linear(64, 10)
)
def forward(self, x):
x = self.model(x)
return x
定义损失函数和优化器,并且让其反向传播,保留参数梯度
import torch
import torchvision
from torch import nn
from torch.nn import Flatten, Linear, Sequential, Conv2d, MaxPool2d
from torch.utils.data import DataLoader
dataset_transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()
])
train_set = torchvision.datasets.CIFAR10("dataset", train=True, transform=dataset_transform, download=True)
test_set = torchvision.datasets.CIFAR10("dataset", train=False, transform=dataset_transform, download=True)
test_loader = DataLoader(test_set, batch_size=64, shuffle=True)
# 网络
class MyConvModel(nn.Module):
def __init__(self):
super(MyConvModel, self).__init__()
self.model = Sequential(
Conv2d(3, 32, 5, 1, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, 1, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, 1, padding=2),
MaxPool2d(2),
Flatten(),
Linear(64 * 4 * 4, 64),
Linear(64, 10)
)
def forward(self, x):
x = self.model(x)
return x
model = MyConvModel()
# 分类任务定义交叉熵损失函数
loss = nn.CrossEntropyLoss()
# 定义学习率
learning_rate = 1e-2
# 定义优化器
optim = torch.optim.SGD(model.parameters(), lr=learning_rate)
for data in test_loader:
imgs, targets = data
output = model(imgs)
res_loss = loss(output, targets)
optim.zero_grad()
res_loss.backward()
optim.step()
print(".")
其中
for data in test_loader:
imgs, targets = data
output = model(imgs)
res_loss = loss(output, targets)
res_loss.backward()
print(".")
是对数据进行一轮的学习,更新参数,res_loss是对这一轮中的一批进行计算loss,然后根据本轮的loss去计算梯度(backward),再去更新参数(step)。那么本批次的更新到此结束。为了不影响下一个批次数据对模型的优化,本批次所记录的梯度需要清0。
总结:因为模型的参数在optim.step()时已经被更新了,梯度只是 “指导参数如何更新的临时计算结果”。清空梯度只是为了避免下一批数据的梯度和上一批混淆,确保每批数据都能独立地 “指导一次参数更新”。
我们可以记录下每一轮的loss总和loss_sum,然后多轮更新计算,观察loss_sum的变化情况
# 训练轮数
epoch = 20
# 定义优化器
optim = torch.optim.SGD(model.parameters(), lr=learning_rate)
for i in range(epoch):
loss_sum = 0
for data in test_loader:
imgs, targets = data
output = model(imgs)
res_loss = loss(output, targets)
optim.zero_grad()
res_loss.backward()
optim.step()
loss_sum += res_loss
print(loss_sum)
四、模型的加载和保存
上面我们训练好了一个误差相对较低的模型,我们如何保存它?下一次能不能直接用它而不必重新训练
这里只推荐一种模型保存方式
# 假设你训练好的模型叫 model,优化器叫 optimizer
# 保存模型参数和训练信息(可选)
torch.save({
'model_state_dict': model.state_dict(), # 模型参数
'optimizer_state_dict': optimizer.state_dict(), # 优化器参数(如需继续训练)
'epoch': epoch, # 当前训练的轮数
'loss': loss # 最后的损失值
}, 'model_params.pth') # 保存为 .pth 文件
模型的加载方式
# 1. 先创建和训练时一样的模型结构
model = YourModel() # 实例化模型
optimizer = torch.optim.Adam(model.parameters()) # 实例化优化器(如需继续训练)
# 2. 加载保存的参数
checkpoint = torch.load('model_params.pth') # 加载文件
model.load_state_dict(checkpoint['model_state_dict']) # 载入模型参数
五、实战分类任务-完整模型训练
1.train和eval
在深度学习中,model.eval() 是一个非常重要的方法,用于将模型切换到评估模式(evaluation mode)。
当你训练完模型,想要用它进行预测或评估时,需要调用这个方法。它主要会影响模型中一些具有特殊行为的层,例如:
- Dropout 层:在评估模式下会停止随机丢弃神经元
- Batch Normalization 层:会使用训练过程中计算的移动平均值和方差,而不是当前批次的统计数据
# 训练阶段
model.train() # 切换到训练模式
for batch in train_loader:
# 训练代码...
# 评估阶段
model.eval() # 切换到评估模式
with torch.no_grad(): # 通常会配合关闭梯度计算以提高效率
for batch in test_loader:
# 评估/预测代码...
调用 model.eval() 不会改变模型的权重,只是改变其运行时的行为,确保评估结果的一致性和准确性。完成评估后,如果需要继续训练,应调用 model.train() 切换回训练模式。
2.argmax
argmax(1)返回的是numpy在横向最大值的索引
- 这会进行逐元素比较,得到一个布尔数组
- 例如,如果模型预测为
[1, 3, 5, ...],真实标签为[1, 2, 5, ...] - 比较结果为
[True, False, True, ...] - 形状仍为
[64],True 表示预测正确,False 表示预测错误
# 假设这是一个简化的批次(批次大小=3)
outputs = torch.tensor([
[0.1, 0.8, 0.05, 0.05], # 预测为类别1
[0.7, 0.1, 0.1, 0.1], # 预测为类别0
[0.05, 0.05, 0.8, 0.1] # 预测为类别2
])
targets = torch.tensor([1, 0, 2]) # 真实标签
print(outputs.argmax(1)) # 输出: tensor([1, 0, 2])
print(outputs.argmax(1) == targets) # 输出: tensor([True, True, True])
print((outputs.argmax(1) == targets).sum()) # 输出: tensor(3)
所以利用argmax可以计算出一个批次预测正确的个数,从而计算出一轮的正确率
3. 代码
# 导入必要的库
import torch # 导入PyTorch库
import torchvision.datasets # 导入torchvision中的数据集模块
from torch.utils.tensorboard import SummaryWriter # 导入TensorBoard用于可视化
from model import * # 从model模块导入所有内容(这里可能是自定义的一些工具函数)
import time # 导入时间模块,可能用于计时
# 准备数据集
from torch.utils.data import DataLoader # 导入DataLoader用于数据加载
# 加载CIFAR10训练集,保存在"./datasets"目录,train=True表示是训练集
# transform=ToTensor()将图像转换为Tensor格式,download=True表示如果本地没有就下载
train_data = torchvision.datasets.CIFAR10("./datasets",
train=True,
transform=torchvision.transforms.ToTensor(),
download=True)
# 加载CIFAR10测试集,train=False表示是测试集,其他参数同训练集
test_data = torchvision.datasets.CIFAR10("./datasets",
train=False,
transform=torchvision.transforms.ToTensor(),
download=True)
# 获取训练集和测试集的大小
train_data_size = len(train_data)
test_data_size = len(test_data)
# 打印数据集大小
print("训练集长度{}".format(train_data_size))
print("测试集长度{}".format(test_data_size))
# 使用DataLoader加载数据集,batch_size=64表示每次加载64个样本
train_dataloader = DataLoader(train_data, 64)
test_dataloader = DataLoader(test_data, 64)
# 定义卷积神经网络模型
class MyConvModel(nn.Module): # 继承nn.Module
def __init__(self):
super(MyConvModel, self).__init__() # 调用父类构造函数
# 定义模型序列,包含卷积层、池化层和全连接层
self.model = Sequential(
Conv2d(3, 32, 5, 1, padding=2), # 卷积层:输入3通道,输出32通道,5x5卷积核,步长1,填充2
MaxPool2d(2), # 最大池化层:2x2池化核
Conv2d(32, 32, 5, 1, padding=2), # 卷积层:输入32通道,输出32通道
MaxPool2d(2), # 最大池化层
Conv2d(32, 64, 5, 1, padding=2), # 卷积层:输入32通道,输出64通道
MaxPool2d(2), # 最大池化层
Flatten(), # 展平操作,将多维张量转换为一维
Linear(64 * 4 * 4, 64), # 全连接层:输入维度64*4*4,输出64
Linear(64, 10) # 全连接层:输入64,输出10(CIFAR10有10个类别)
)
# 定义前向传播
def forward(self, x):
x = self.model(x)
return x
# 创建网络模型实例
myConvModel = MyConvModel()
# 定义损失函数,使用交叉熵损失(适用于分类任务)
lossFun = nn.CrossEntropyLoss()
# 设置学习率为0.01
learning_rate = 1e-2
# 定义优化器,使用SGD(随机梯度下降)
optim_sgd = torch.optim.SGD(myConvModel.parameters(), lr=learning_rate)
# 训练相关参数初始化
train_time = 0 # 训练次数计数器
test_time = 0 # 测试次数计数器
epoch = 20 # 训练轮数
# 创建SummaryWriter实例,用于记录日志(保存到"logs_train"目录)
writer = SummaryWriter("logs_train")
# 开始训练循环,共训练epoch轮
for i in range(epoch):
print("第{}轮训练".format(i + 1))
myConvModel.train() # 将模型切换到训练模式
# 遍历训练数据集
for data in train_dataloader:
imgs, targets = data # 获取图像和对应的标签
outputs = myConvModel(imgs) # 将图像输入模型,得到输出
loss = lossFun(outputs, targets) # 计算损失值
# 优化器更新步骤
optim_sgd.zero_grad() # 梯度清零
loss.backward() # 反向传播计算梯度
optim_sgd.step() # 更新参数
train_time += 1 # 训练次数加1
# 每训练100次打印一次损失
if train_time % 100 == 0:
print("训练次数{}, loss值是{}".format(train_time, loss.item()))
# 将训练损失写入TensorBoard
writer.add_scalar("train_loss", loss.item(), train_time)
# 每轮训练结束后,在测试集上评估
total_test_loss = 0 # 测试集总损失
total_accuracy = 0 # 测试集总准确率
myConvModel.eval() # 将模型切换到评估模式
with torch.no_grad(): # 关闭梯度计算,节省内存和计算资源
for data in test_dataloader:
imgs, targets = data # 获取测试集图像和标签
outputs = myConvModel(imgs) # 模型预测
test_loss = lossFun(outputs, targets) # 计算测试损失
total_test_loss += test_loss.item() # 累加测试损失
# 计算准确率:预测结果中最大值的索引与标签相同的数量
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy += accuracy # 累加正确预测的数量
# 打印测试集整体表现
print("整个测试集上的loss:{}".format(total_test_loss))
print("整个测试集上的正确率:{}".format(total_accuracy / test_data_size))
test_time += 1 # 测试次数加1
# 将测试损失和准确率写入TensorBoard
writer.add_scalar("test_loss", total_test_loss, test_time)
writer.add_scalar("test_accuracy", total_accuracy / test_data_size, test_time)
# 保存当前轮次的模型
torch.save(myConvModel, "myConvModel_{}.pth".format(i + 1))
print("模型已保存")
# 关闭SummaryWriter
writer.close()
六、引入GPU
要将以上训练过程迁移到 GPU 上运行,需要对代码进行几处关键修改,主要涉及将模型、数据和损失函数移动到 GPU 设备上
1.设备选择
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
2.模型移动到 GPU
myConvModel = myConvModel.to(device)
3.损失函数移动到GPU
lossFun = lossFun.to(device)
4.数据移动到 GPU
imgs = imgs.to(device)
targets = targets.to(device)
5.完整代码
import torch
import torchvision.datasets
from torch.utils.tensorboard import SummaryWriter
import torch.nn as nn # 补充导入nn模块
from model import *
import time
# 准备数据集
from torch.utils.data import DataLoader
# 检查是否有可用的GPU,优先使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")
train_data = torchvision.datasets.CIFAR10("./datasets",
train=True,
transform=torchvision.transforms.ToTensor(),
download=True)
test_data = torchvision.datasets.CIFAR10("./datasets",
train=False,
transform=torchvision.transforms.ToTensor(),
download=True)
train_data_size = len(train_data)
test_data_size = len(test_data)
print("训练集长度{}".format(train_data_size))
print("测试集长度{}".format(test_data_size))
# DataLoader加载数据集
train_dataloader = DataLoader(train_data, 64)
test_dataloader = DataLoader(test_data, 64)
# 网络
class MyConvModel(nn.Module):
def __init__(self):
super(MyConvModel, self).__init__()
self.model = nn.Sequential( # 补充nn.前缀
nn.Conv2d(3, 32, 5, 1, padding=2), # 补充nn.前缀
nn.MaxPool2d(2), # 补充nn.前缀
nn.Conv2d(32, 32, 5, 1, padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, 1, padding=2),
nn.MaxPool2d(2),
nn.Flatten(), # 补充nn.前缀
nn.Linear(64 * 4 * 4, 64), # 补充nn.前缀
nn.Linear(64, 10)
)
def forward(self, x):
x = self.model(x)
return x
# 网络模型创建并移动到GPU
myConvModel = MyConvModel()
myConvModel = myConvModel.to(device) # 将模型移动到GPU
# 损失函数创建并移动到GPU
lossFun = nn.CrossEntropyLoss()
lossFun = lossFun.to(device) # 将损失函数移动到GPU
# 学习率
learning_rate = 1e-2
# 优化器
optim_sgd = torch.optim.SGD(myConvModel.parameters(), lr=learning_rate)
# 参数
train_time = 0
test_time = 0
epoch = 20
# 可视化
writer = SummaryWriter("logs_train")
for i in range(epoch):
print("第{}轮训练".format(i + 1))
myConvModel.train()
start_time = time.time() # 记录训练开始时间
for data in train_dataloader:
imgs, targets = data
# 将数据移动到GPU
imgs = imgs.to(device)
targets = targets.to(device)
outputs = myConvModel(imgs)
loss = lossFun(outputs, targets)
optim_sgd.zero_grad()
loss.backward()
optim_sgd.step()
train_time = train_time + 1
if train_time % 100 == 0:
end_time = time.time()
print(f"训练次数{train_time}, loss值是{loss.item()}, 耗时{end_time - start_time:.2f}秒")
writer.add_scalar("train_loss", loss.item(), train_time)
# 在测试集上
total_test_loss = 0
total_accuracy = 0
myConvModel.eval()
with torch.no_grad():
for data in test_dataloader:
imgs, targets = data
# 将测试数据移动到GPU
imgs = imgs.to(device)
targets = targets.to(device)
outputs = myConvModel(imgs)
test_loss = lossFun(outputs, targets)
total_test_loss = total_test_loss + test_loss.item()
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy = total_accuracy + accuracy
# 计算准确率时将结果移回CPU并转换为浮点数
print("整个测试集上的loss:{}".format(total_test_loss))
print("整个测试集上的正确率:{}".format(total_accuracy.to("cpu").item() / test_data_size))
test_time = test_time + 1
writer.add_scalar("test_loss", total_test_loss, test_time)
writer.add_scalar("test_accuracy", total_accuracy.to("cpu").item() / test_data_size, test_time)
# 保存模型
torch.save(myConvModel, "myConvModel_{}.pth".format(i + 1))
print("模型已保存")
writer.close()
浙公网安备 33010602011771号