python
# 导入PyTorch核心库,用于构建和训练神经网络
import torch
# 导入优化器模块,用于更新模型参数
import torch.optim as optim
# 导入神经网络层模块,用于搭建网络结构
import torch.nn as nn
# 导入激活函数、损失函数等功能性API
import torch.nn.functional as F
# 导入数据集和数据预处理工具
from torchvision import datasets, transforms
# 导入数值计算库
import numpy as np
# 导入绘图库,用于绘制损失曲线
import matplotlib.pyplot as plt
# ===================== 【超参数设置】 =====================
# 输入图像的尺寸,MNIST数据集是28x28像素
input_size = 28
# 分类类别数量,0-9共10个数字
num_classes = 10
# 训练轮数:将整个训练集完整训练几遍
num_epochs = 10
# 批次大小:每次喂给模型多少张图片一起训练
batch_size = 10
# ===================== 【加载MNIST数据集】 =====================
# 加载训练数据集
train_dataset = datasets.MNIST(
root='./data', # 数据集存放路径
train=True, # True表示加载训练集
transform=transforms.ToTensor(), # 将图片转换为PyTorch张量
download=True # 如果本地没有数据集,自动从网上下载
)
# 加载测试数据集
test_dataset = datasets.MNIST(
root='./data', # 数据集存放路径
train=False, # False表示加载测试集
transform=transforms.ToTensor() # 将图片转换为PyTorch张量
)
# ===================== 【构建数据加载器】 =====================
# 训练集加载器:按批次加载数据,并打乱顺序
train_loader = torch.utils.data.DataLoader(
dataset=train_dataset, # 指定要加载的数据集
batch_size=batch_size, # 每个批次包含的样本数量
shuffle=True # 打乱数据顺序,提高模型泛化能力
)
# 测试集加载器
test_loader = torch.utils.data.DataLoader(
dataset=test_dataset,
batch_size=batch_size,
shuffle=True
)
# ===================== 【定义CNN神经网络模型】 =====================
class CNN(nn.Module): # 定义CNN类,继承PyTorch的nn.Module
def __init__(self):
super(CNN, self).__init__() # 初始化父类,固定写法
# 第一个卷积模块:卷积+激活+池化
self.conv1 = nn.Sequential(
nn.Conv2d(
in_channels=1, # 输入通道数:灰度图为1
out_channels=16, # 输出通道数:生成16张特征图
kernel_size=5, # 卷积核大小5x5
stride=1, # 卷积步长为1
padding=2 # 填充2圈0,保持输出尺寸不变
),
nn.ReLU(), # 激活函数,增加非线性
nn.MaxPool2d(kernel_size=2) # 最大池化,尺寸缩小一半
)
# 第二个卷积模块
self.conv2 = nn.Sequential(
nn.Conv2d(16, 32, 5, 1, 2), # 输入16通道→输出32通道
nn.ReLU(),
nn.MaxPool2d(2) # 尺寸再缩小一半
)
# 全连接层:将卷积特征映射到10个分类结果
self.out = nn.Linear(32 * 7 * 7, 10)
# 前向传播:定义数据流向
def forward(self, x):
x = self.conv1(x) # 输入经过第一个卷积模块
x = self.conv2(x) # 经过第二个卷积模块
x = x.view(x.size(0), -1) # 展平特征图,适配全连接层
output = self.out(x) # 全连接层输出分类结果
return output
# ===================== 【初始化模型、优化器、损失函数】 =====================
model = CNN() # 创建CNN模型实例
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam优化器
loss_func = nn.CrossEntropyLoss() # 交叉熵损失函数,用于分类任务
# ===================== 【开始训练模型】 =====================
print("开始训练...")
train_loss_list = [] # 保存每轮的平均损失,用于绘图
# 循环训练指定轮数
for epoch in range(num_epochs):
total_loss = 0 # 累计当前轮的总损失
# 遍历训练集中的每一个批次
for i, (images, labels) in enumerate(train_loader):
output = model(images) # 前向传播:计算模型输出
loss = loss_func(output, labels) # 计算损失值
optimizer.zero_grad() # 清空上一步的梯度
loss.backward() # 反向传播:计算梯度
optimizer.step() # 更新模型参数
total_loss += loss.item() # 累加损失
avg_loss = total_loss / len(train_loader) # 计算本轮平均损失
train_loss_list.append(avg_loss) # 保存损失
print(f"Epoch [{epoch+1}/{num_epochs}] 平均损失: {avg_loss:.4f}")
# ===================== 【测试模型准确率】 =====================
print("\n开始测试...")
correct = 0 # 预测正确的样本数
total = 0 # 总样本数
with torch.no_grad(): # 测试时不需要计算梯度,节省资源
for images, labels in test_loader:
outputs = model(images) # 模型预测
_, predicted = torch.max(outputs.data, 1) # 获取预测类别
total += labels.size(0) # 累计总样本数
correct += (predicted == labels).sum().item() # 累计正确数
# 计算并打印准确率
print(f'测试集准确率: {100 * correct / total:.2f}%')
# ===================== 【绘制训练损失曲线】 =====================
plt.plot(train_loss_list, label='Training Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('训练损失变化')
plt.legend()
plt.show()
超参数 = 人工手动设置的参数
直接调用库内置 MNIST 数据集,无需自己准备图片文件。
python
# 导入PyTorch的核心库,提供张量计算、自动求导等基础功能
import torch
# 导入PyTorch的优化器模块,用于更新神经网络的权重参数
import torch.optim as optim
# 导入PyTorch的神经网络层模块,包含卷积层、全连接层等网络组件
import torch.nn as nn
# 导入PyTorch的函数式接口,包含激活函数、损失函数等工具
import torch.nn.functional as F
# 从torchvision库中导入数据集工具和数据预处理工具
from torchvision import datasets, transforms
# 导入numpy数值计算库,用于数组、矩阵相关计算
import numpy as np
# 导入matplotlib的pyplot模块,用于绘制图表可视化数据
import matplotlib.pyplot as plt
# 定义输入图像的尺寸,MNIST数据集的图片大小为28*28像素
input_size = 28
# 定义分类的类别数量,手写数字识别分为0-9共10个类别
num_classes = 10
# 定义训练的总轮数,代表将整个训练集完整训练10次
num_epochs = 10
# 定义批次大小,每次训练时输入模型的样本数量为10
batch_size = 10
# 加载MNIST训练数据集
train_dataset = datasets.MNIST(
# 指定数据集的存储根目录为当前文件夹下的data文件夹
root='./data',
# 设置为True,表示加载训练集数据
train=True,
# 数据预处理:将PIL图片格式转换为PyTorch专用的张量格式
transform=transforms.ToTensor(),
# 设置为True,如果本地没有数据集,自动从官方服务器下载
download=True
)
# 加载MNIST测试数据集
test_dataset = datasets.MNIST(
# 测试集存储路径与训练集一致
root='./data',
# 设置为False,表示加载测试集数据
train=False,
# 测试集同样需要转换为张量格式
transform=transforms.ToTensor(),
)
# 创建训练集的数据加载器,用于批量读取数据
train_loader = torch.utils.data.DataLoader(
# 指定需要加载的训练数据集
dataset=train_dataset,
# 设置每个批次包含的样本数,使用上方定义的batch_size
batch_size=batch_size,
# 设置为True,训练时打乱数据顺序,防止模型记忆数据顺序
shuffle=True
)
# 创建测试集的数据加载器
test_loader = torch.utils.data.DataLoader(
# 指定需要加载的测试数据集
dataset=test_dataset,
# 测试批次大小与训练保持一致
batch_size=batch_size,
# 测试集打乱顺序,保证测试的随机性
shuffle=True
)
# 定义CNN神经网络类,继承自nn.Module(PyTorch所有模型的基类)
class CNN(nn.Module):
# 类的初始化函数,定义网络的层结构
def __init__(self):
# 调用父类nn.Module的初始化方法,固定语法
super(CNN, self).__init__()
# 定义第一个卷积模块,使用Sequential组合多层网络
self.conv1 = nn.Sequential(
# 二维卷积层,用于提取图像特征
nn.Conv2d(
# 输入通道数:MNIST是灰度图,通道数为1
in_channels=1,
# 输出通道数:卷积后生成16张特征图
out_channels=16,
# 卷积核的尺寸为5x5
kernel_size=5,
# 卷积核移动的步长为1
stride=1,
# 图像边缘填充2圈0,保证卷积后尺寸不变
padding=2,
),
# ReLU激活函数,增加网络的非线性表达能力
nn.ReLU(),
# 二维最大池化层,池化核大小2x2,将特征图尺寸缩小一半
nn.MaxPool2d(kernel_size=2, ),
)
# 定义第二个卷积模块
self.conv2 = nn.Sequential(
# 二维卷积层:输入16通道,输出32通道,卷积核5x5
nn.Conv2d(16, 32, 5, 1, 2),
# ReLU激活函数
nn.ReLU(),
# 最大池化层,尺寸再次缩小一半
nn.MaxPool2d(kernel_size=2),
)
# 定义全连接层:将卷积特征展平后映射为10分类结果
self.out = nn.Linear(32*7*7, 10)
# 前向传播函数,定义数据在网络中的流动路径
def forward(self,x):
# 输入数据经过第一个卷积模块
x = self.conv1(x)
# 数据经过第二个卷积模块
x = self.conv2(x)
# 将特征图展平:batch维度保留,其余维度合并为一维
x = x.view(x.size(0), -1)
# 数据传入全连接层,得到最终的分类输出
output = self.out(x)
# 返回模型的输出结果
return output
# 实例化CNN模型,创建一个可以训练的网络对象
model = CNN()
# 定义Adam优化器,传入模型参数和学习率lr=0.001
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 定义交叉熵损失函数,适用于多分类任务
loss_func = nn.CrossEntropyLoss()
# 打印训练开始的提示信息
print("开始训练...")
# 创建空列表,用于存储每一轮训练的平均损失值
train_loss_list = []
# 外层循环:遍历每一个训练轮数
for epoch in range(num_epochs):
# 初始化当前轮次的总损失为0
total_loss = 0
# 内层循环:遍历训练集中的每一个批次数据
for i, (images, labels) in enumerate(train_loader):
# 前向传播:将图片输入模型,得到预测输出
output = model(images)
# 计算损失值:对比预测输出和真实标签
loss = loss_func(output, labels)
# 清空上一步的梯度,防止梯度累加
optimizer.zero_grad()
# 反向传播:计算损失对模型参数的梯度
loss.backward()
# 优化器更新模型的权重参数
optimizer.step()
# 将当前批次的损失值累加到总损失中
total_loss += loss.item()
# 计算当前轮次的平均损失:总损失 / 批次数量
avg_loss = total_loss / len(train_loader)
# 将平均损失添加到列表中
train_loss_list.append(avg_loss)
# 打印当前轮次的训练信息
print(f"Epoch [{epoch+1}/{num_epochs}] 平均损失: {avg_loss:.4f}")
# 打印测试开始的提示信息
print("\n开始测试...")
# 初始化预测正确的样本数量为0
correct = 0
# 初始化测试集的总样本数量为0
total = 0
# torch.no_grad():测试阶段关闭梯度计算,节省内存和计算资源
with torch.no_grad():
# 遍历测试集中的每一个批次
for images, labels in test_loader:
# 将测试图片输入模型,得到输出
outputs = model(images)
# 获取预测结果:取输出中概率最大的类别
_, predicted = torch.max(outputs.data, 1)
# 累加当前批次的样本总数
total += labels.size(0)
# 统计预测正确的样本数量并累加
correct += (predicted == labels).sum().item()
# 计算并打印模型在测试集上的准确率
print(f'测试集准确率: {100 * correct / total:.2f}%')
# 绘制训练损失曲线:x轴为轮数,y轴为损失值
plt.plot(train_loss_list, label='Training Loss')
# 设置x轴标签
plt.xlabel('Epoch')
# 设置y轴标签
plt.ylabel('Loss')
# 设置图表标题
plt.title('训练损失变化')
# 显示图例
plt.legend()
# 展示绘制的图表
plt.show()