深度学习-----ptorch框架认识-手写数字识别.py项目解读
摘要:本文介绍了如何使用PyTorch实现MNIST手写数字识别任务。主要步骤包括:1)加载和预处理MNIST数据集,包括训练集(60,000张)和测试集(10,000张);2)创建数据加载器(DataLoader)进行批量处理;3)构建一个包含两个隐藏层的全连接神经网络模型;4)使用交叉熵损失函数和Adam优化器;5)实现训练和测试流程,包括前向传播、反向传播和参数更新;6)在10个epoch的
ptorch框架认识-手写数字识别.py项目完整代码如下:
# import torch
# print(torch.__version__)#1.X 1、验证安装的开发环境是否正确,
'''
MNIST包含70,000张手写数字图像: 60,000张用于训练,10,000张用于测试。
图像是灰度的,28x28像素的,并且居中的,以减少预处理和加快运行。
'''
import torch
from torch import nn #导入神经网络模块,
from torch.utils.data import DataLoader #数据包管理工具,打包数据,
from torchvision import datasets #封装了很多与图像相关的模型,及数据集
from torchvision.transforms import ToTensor #数据转换,张量,将其他类型的数据转换为tensor张量,numpy array,dataframe
'''下载训练数据集(包含训练图片+标签)'''
training_data = datasets.MNIST( #跳转到函数的内部源代码,pycharm 按下ctrl +鼠标点击
root="data",#表示下载的手写数字 到哪个路径。60000
train=True,#读取下载后的数据 中的 训练集
download=True,#如果你之前已经下载过了,就不用再下载
transform=ToTensor(), #张量,图片是不能直接传入神经网络模型
) #对于pytorch库能够识别的数据一般是tensor张量.
print(len(training_data))
# datasets.MNIST的参数:
# root(string): 表示数据集的根目录,
# train(bool, optional): 如果为True,则从training.pt创建数据集,否则从test.pt创建数据集
# download(bool, optional): 如果为True,则从internet下载数据集并将其放入根目录。如果数据集已下载,则不会再次下载
# transform(callable, optional): 接收PIL图片并返回转换后版本图片的转换函数
'''下载测试数据集(包含训练图片+标签) '''
test_data = datasets.MNIST(
root="data",
train=False,
download=True,
transform=ToTensor(),#Tensor是在深度学习中提出并广泛应用的数据类型,它与深度学习框架(如 PyTorch、TensorFlow)紧密集成,方便进行神经网络的训练和推理。
)#NumPy 数组只能在CPU上运行。Tensor可以在GPU上运行,这在深度学习应用中可以显著提高计算速度。
print(len(test_data))
# '''展示手写字图片,把训练数据集中的前59000张图片展示一下'''
# from matplotlib import pyplot as plt
# figure = plt.figure()
# for i in range(9):#
# img, label = training_data[i+59000]#提取第59000张图片
#
# figure.add_subplot(3, 3, i+1)#图像窗口中创建多个小窗口,小窗口用于显示图片
# plt.title(label)
# plt.axis("off") # plt.show(I)#显示矢量,
# plt.imshow(img.squeeze(), cmap="gray") #plt.imshow()将NumPy数组data中的数据显示为图像,并在图形窗口中显示该图像
# a = img.squeeze() # img.squeeze()从张量img中去掉维度为1的。如果该维度的大小不为1则张量不会改变。#cmap="gray"表示使用灰度色彩映射来显示图像。这意味着图像将以灰度模式显示
# plt.show()
'''创建数据DataLoader(数据加载器)
batch_size:将数据集分成多份,每一份为batch_size个数据。
优点:可以减少内存的使用,提高训练速度。
'''
train_dataloader = DataLoader(training_data, batch_size=64)#64张图片为一个包,1、损失函数2、GPU一次性接受的图片个数
test_dataloader = DataLoader(test_data, batch_size=64)
for X, y in test_dataloader:#X是表示打包好的每一个数据包
print(f"Shape of X [N, C, H, W]: {X.shape}")#
print(f"Shape of y: {y.shape} {y.dtype}")
break
'''判断当前设备是否支持GPU,其中mps是苹果m系列芯片的GPU。'''#返回cuda,mps。CPU m1 ,m2 集显CPU+GPU RTX3060,
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")#字符串的格式化。 CUDA驱动软件的功能:pytorch能够去执行cuda的命令,cuda通过GPU指令集去控制GPU
#神经网络的模型也需要传入到GPU,1个batchsize的数据集也需要传入到GPU,才可以进行训练。
''' 定义神经网络 类的继承这种方式'''
class NeuralNetwork(nn.Module):#通过调用类的形式来使用神经网络,神经网络的模型,nn.module
def __init__(self):#python基础关于类,self类自己本身
super().__init__()#继承的父类初始化
self.flatten = nn.Flatten()#展开,创建一个展开对象flatten
self.hidden1 = nn.Linear(28*28, 128)#第1个参数:有多少个神经元传入进来,第2个参数:有多少个数据传出去前一层神经元的个数,当前本层神经元个数
self.hidden2 = nn.Linear(128, 256)#为什么你要用128
self.out = nn.Linear(256, 10)#输出必需和标签的类别相同,输入必须是上一层的神经元个数
def forward(self, x): #前向传播,你得告诉它 数据的流向。是神经网络层连接起来,函数名称不能改。当你调用forward函数的时候,传入进来的图像数据
x = self.flatten.forward(x) #图像进行展开 self.flatten.forward
x = self.hidden1.forward(x)
x = torch.relu(x) #激活函数,torch使用的relu函数 relu,tanh
x = self.hidden2.forward(x)
x = torch.relu(x)
x = self.out.forward(x)
return x
model = NeuralNetwork().to(device)#把刚刚创建的模型传入到Gpu
print(model)
def train(dataloader, model, loss_fn, optimizer):
model.train()#告诉模型,我要开始训练,模型中w进行随机化操作,已经更新w。在训练过程中,w会被修改的
#pytorch提供2种方式来切换训练和测试的模式,分别是:model.train() 和 model.eval()。
# 一般用法是:在训练开始之前写上model.trian(),在测试时写上 model.eval() 。
batch_size_num = 1 #统计 训练的batch数量
for X, y in dataloader: #其中batch为每一个数据的编号
X, y = X.to(device), y.to(device) #把训练数据集和标签传入cpu或GPU
pred = model(X) #.forward可以被省略,父类中已经对此功能进行了设置。自动初始化 w权值
loss = loss_fn(pred, y) #通过交叉熵损失函数计算损失值loss
# Backpropagation 进来一个batch的数据,计算一次梯度,更新一次网络
optimizer.zero_grad() #梯度值清零
loss.backward() #反向传播计算得到每个参数的梯度值w
optimizer.step() #根据梯度更新网络w参数
loss_value = loss.item() #从tensor数据中提取数据出来,tensor获取损失值
if batch_size_num %100 ==0:
print(f"loss: {loss_value:>7f} [number:{batch_size_num}]")
batch_size_num += 1
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset)#10000
num_batches = len(dataloader)#打包的数量
model.eval() #测试,w就不能再更新。
test_loss, correct = 0, 0 #
with torch.no_grad(): #一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。
for X, y in dataloader:
X, y = X.to(device), y.to(device) #送到GPU
pred = model.forward(X)
test_loss += loss_fn(pred, y).item() #test_loss是会自动累加每一个批次的损失值
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
a = (pred.argmax(1) == y) #dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号
b = (pred.argmax(1) == y).type(torch.float)
test_loss /= num_batches #能来衡量模型测试的好坏。
correct /= size #平均的正确率
print(f"Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}")
loss_fn = nn.CrossEntropyLoss() #创建交叉熵损失函数对象,因为手写字识别中一共有10个数字,输出会有10个结果
# L1Loss:L1损失,也称为平均绝对误差(Mean Absolute Error, MAE)。它计算预测值与真实值之间的绝对差值的平均值。
# NLLLoss:负对数似然损失(Negative Log Likelihood Loss)。它用于多分类问题,通常与LogSoftmax输出层配合使用。
# NLLLoss2d:这是NLLLoss的一个特殊版本,用于处理2D图像数据。在最新版本的PyTorch中,这个损失函数可能已经被整合到NLLLoss中,通过指定reduction参数来实现同样的功能。
# PoissonNLLLoss:泊松负对数似然损失,用于泊松回归问题。
# GaussianNLLLoss:高斯负对数似然损失,用于高斯分布(正态分布)的回归问题。
# KLDivLoss:Kullback-Leibler散度损失,用于度量两个概率分布之间的差异。
# MSELoss:均方误差损失(Mean Squared Error Loss),计算预测值与真实值之间差值的平方的平均值。
# BCELoss:二元交叉熵损失(Binary Cross Entropy Loss),用于二分类问题。
# BCEWithLogitsLoss:结合了Sigmoid激活函数和二元交叉熵损失的损失函数,用于提高数值稳定性。
# HingeEmbeddingLoss:铰链嵌入损失,用于学习非线性嵌入或半监督学习。
# MultiLabelMarginLoss:多标签边际损失,用于多标签分类问题。
# SmoothL1Loss:平滑L1损失,是L1损失和L2损失(MSE)的结合,旨在避免梯度爆炸问题。
# HuberLoss:Huber损失,与SmoothL1Loss类似,但有一个可调的参数来控制L1和L2损失之间的平衡。
# SoftMarginLoss:软边际损失,用于二分类问题,可以看作是Hinge损失的一种软化版本。
# CrossEntropyLoss:交叉熵损失,用于多分类问题。它结合了LogSoftmax和NLLLoss的功能。
# MultiLabelSoftMarginLoss:多标签软边际损失,用于多标签二分类问题。
# CosineEmbeddingLoss:余弦嵌入损失,用于学习非线性嵌入,通过余弦相似度来度量样本之间的相似性。
# MarginRankingLoss:边际排序损失,用于排序问题,如学习到排序的嵌入空间。
# MultiMarginLoss:多边际损失,用于多分类问题,旨在优化分类边界的边际。
# TripletMarginLoss:三元组边际损失,用于学习嵌入空间中的距离度量,通常用于人脸识别或图像检索等任务。
# TripletMarginWithDistanceLoss:这是TripletMarginLoss的一个变体,允许使用自定义的距离函数。
# CTCLoss:连接时序分类损失(Connectionist Temporal Classification Loss),用于序列到序列的学习问题,特别是当输出序列的长度不固定时(如语音识别)。
#一会改成adam优化器 梯度下降
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)#创建一个优化器,SGD为随机梯度下降算法
# #params:要训练的参数,一般我们传入的都是model.parameters()。
# #lr:learning_rate学习率,也就是步长。
#loss表示模型训练后的输出结果与 样本标签的差距。如果差距越小,就表示模型训练越好,越逼近于真实的模型。
# train(train_dataloader, model, loss_fn, optimizer)#训练1次完整的数据,多轮训练,
# test(test_dataloader, model, loss_fn)
epochs = 10 #到底选择多少呢?
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)#10次训练
print("Done!")
test(test_dataloader, model, loss_fn)
MNIST包含70,000张手写数字图像: 60,000张用于训练,10,000张用于测试。
图像是灰度的,28x28像素的,并且居中的,以减少预处理和加快运行。为什么减少预处理和加快运行?
'''
MNIST包含70,000张手写数字图像: 60,000张用于训练,10,000张用于测试。
图像是灰度的,28x28像素的,并且居中的,以减少预处理和加快运行。
'''
import torch
from torch import nn #导入神经网络模块,
from torch.utils.data import DataLoader #数据包管理工具,打包数据,
from torchvision import datasets #封装了很多与图像相关的模型,及数据集
from torchvision.transforms import ToTensor #数据转换,张量,将其他类型的数据转换为tensor张量,numpy array,dataframe
MNIST数据集通过一系列精心设计的特征显著降低了预处理复杂度并加速了模型运行效率,以下是具体原因及技术细节:
减少预处理的原因
-
统一的物理布局与尺寸
-
居中对齐:所有数字图像均经过人工或算法居中处理,使核心内容集中在中心区域。这种规范化避免了因背景干扰导致的无效特征提取,也省去了后续的目标检测或ROI(Region of Interest)定位步骤。
-
固定分辨率:所有图像均为28×28像素,消除了因尺寸差异导致的缩放需求。若未统一尺寸,需通过插值或裁切调整大小,既损失信息又增加计算负担。
-
-
单通道灰度编码
-
简化数据结构:每张图像仅包含单个灰度通道(而非RGB三通道),减少了数据维度和内存占用。这使得模型无需处理多通道融合问题,同时降低了计算资源的消耗。
-
数值范围明确:像素值为0~255的整数,可直接通过
ToTensor()转换为浮点型张量(归一化为0~1),无需额外颜色空间转换。
-
-
预定义的标准化参数
-
全局统计量已知:数据集提供了基于全部样本计算的均值(0.1307)和标准差(0.3081),可直接用于标准化(Normalization)。这一操作能消除光照不均的影响,且无需在训练过程中动态计算批次统计量。
-
加快运行的原因
-
高效的数据加载与批处理
-
无复杂增强需求:由于图像已高度规范化,无需应用旋转、翻转等数据增强技术,大幅缩短了数据预处理流水线的时间。
-
向量化操作优化:统一的尺寸和通道数允许完全并行化处理,充分利用GPU的矩阵运算能力。例如,PyTorch的
DataLoader可高效打包固定形状的数据块,减少CPU到GPU的传输延迟。
-
-
轻量化的网络架构适配
-
紧凑的感受野设计:28×28的小尺寸适合浅层CNN(如经典的LeNet),池化层可快速缩小特征图尺寸,避免过大的空间维度导致显存溢出。
-
静态计算图优势:固定的输入尺寸允许框架提前分配内存空间,避免动态形状推理的开销,尤其在大规模训练时显著提升吞吐量。
-
-
社区标准化流程的成熟实践
-
端到端工作流集成:主流框架(如PyTorch)将MNIST作为基准数据集深度优化,提供一键下载、自动分割和标准化接口,开发者可直接调用预封装的
torchvision.datasets.MNIST模块,进一步简化代码实现。
-
综上所述,MNIST通过严格的前期规范化(居中、统一尺寸、灰度编码)和公开的统计参数,最大限度减少了数据预处理的需求;其小尺寸、单通道的特性,则天然适配高效深度学习范式,成为理想的入门级实验平台
import torch
from torch import nn #导入神经网络模块,
from torch.utils.data import DataLoader #数据包管理工具,打包数据,
from torchvision import datasets #封装了很多与图像相关的模型,及数据集
from torchvision.transforms import ToTensor #数据转换,张量,将其他类型的数据转换为tensor张量,numpy array,dataframe,这些库怎么相互配合工作?
'''
MNIST包含70,000张手写数字图像: 60,000张用于训练,10,000张用于测试。
图像是灰度的,28x28像素的,并且居中的,以减少预处理和加快运行。
'''
import torch
from torch import nn #导入神经网络模块,
from torch.utils.data import DataLoader #数据包管理工具,打包数据,
from torchvision import datasets #封装了很多与图像相关的模型,及数据集
from torchvision.transforms import ToTensor #数据转换,张量,将其他类型的数据转换为tensor张量,numpy array,dataframe
以下是这些 PyTorch 库典型的 协同工作流程及示例代码,展示它们如何配合完成一个深度学习任务(以手写数字识别为例):
整体协作逻辑
如同建造工厂生产线:datasets → 原材料仓库(提供原始数据)transforms → 加工车间(将原材料转为合格零件)DataLoader → 传送带(批量运输零件到组装线)nn → 组装车间(用零件搭建产品原型)torch → 能源供应系统(驱动整个工厂运行)
分步协作详解 + 代码示例
1. 准备阶段:获取并清洗数据 (datasets + transforms)
from torchvision import datasets, transforms
# ① 从内置仓库加载MNIST食材(训练集)
raw_data = datasets.MNIST(root='data', train=True, download=True)
# 此时的 raw_data 是一个包含 (图像, 标签) 对的数据集对象
# ② 定义加工工艺:将PNG图片→张量+标准化
preprocess = transforms.Compose([
transforms.ToTensor(), # 主要转换:转张量/归一化像素值
transforms.Normalize((0.1307,), (0.3081,)) # MNIST专属标准化参数
])
# ③ 应用工艺得到成品数据集
processed_data = raw_data # 实际使用时会在DataLoader中动态应用变换
2. 物流阶段:批量运输数据 (DataLoader)
from torch.utils.data import DataLoader
# ④ 创建自动传送带:每次运送64个样本
dataloader = DataLoader(
processed_data, # 装车的货物(必须是Dataset对象)
batch_size=64, # 每辆卡车容量
shuffle=True, # 每次发车前打乱货物顺序
num_workers=2 # 雇佣2个工人并行装卸货
)
# dataloader 现在是一个可迭代对象,每次__iter__()返回一个batch
3. 生产阶段:构建生产线 (nn + torch)
import torch
from torch import nn
# ⑤ 设计产品线蓝图(简单CNN)
class DigitClassifier(nn.Module):
def __init__(self):
super().__init__()
self.layers = nn.Sequential(
nn.Conv2d(1, 32, kernel_size=3), # 卷积层提取特征
nn.ReLU(), # 激活函数增加非线性
nn.MaxPool2d(2), # 池化层降维
nn.Flatten(), # 展平特征图
nn.Linear(32*12*12, 10) # 全连接层输出10类概率
)
def forward(self, x):
return self.layers(x) # 定义数据流经路径
# ⑥ 实例化生产设备并移至GPU
model = DigitClassifier()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device) # 把整条生产线搬到GPU厂房
4. 运行阶段:启动生产 (torch 核心操作)
for epoch in range(5): # 重复5个生产周期
for batch in dataloader: # 从传送带上取下一批原料
inputs, labels = batch # 解包原料(已自动转为张量)
inputs = inputs.to(device) # 把原料运到GPU仓库
labels = labels.to(device)
# ⑦ 正向传播:原料进入生产线
outputs = model(inputs) # 得到预测结果
# ⑧ 计算损失:对比预测与真实标签的差异
loss = nn.CrossEntropyLoss()(outputs, labels)
# ⑨ 反向传播:根据误差调整设备参数
loss.backward()
optimizer.step() # 更新权重参数
这段代码的原理是什么?'''下载训练数据集(包含训练图片+标签)''' training_data = datasets.MNIST( #跳转到函数的内部源代码,pycharm 按下ctrl +鼠标点击 root="data",#表示下载的手写数字 到哪个路径。60000 train=True,#读取下载后的数据 中的 训练集 download=True,#如果你之前已经下载过了,就不用再下载 transform=ToTensor(), #张量,图片是不能直接传入神经网络模型 ) #对于pytorch库能够识别的数据一般是tensor张量.
'''下载训练数据集(包含训练图片+标签)'''
training_data = datasets.MNIST( #跳转到函数的内部源代码,pycharm 按下ctrl +鼠标点击
root="data",#表示下载的手写数字 到哪个路径。60000
train=True,#读取下载后的数据 中的 训练集
download=True,#如果你之前已经下载过了,就不用再下载
transform=ToTensor(), #张量,图片是不能直接传入神经网络模型
) #对于pytorch库能够识别的数据一般是tensor张量.
# datasets.MNIST的参数:
# root(string): 表示数据集的根目录,
# train(bool, optional): 如果为True,则从training.pt创建数据集,否则从test.pt创建数据集
# download(bool, optional): 如果为True,则从internet下载数据集并将其放入根目录。如果数据集已下载,则不会再次下载
# transform(callable, optional): 接收PIL图片并返回转换后版本图片的转换函数
print(len(training_data))
这段代码的核心目的是 加载并预处理MNIST手写数字数据集的训练部分,使其成为适合输入神经网络的形式。以下是逐层解析其原理:
核心组件拆解
1. datasets.MNIST(...) — 数据集加载器
这是一个预置的便捷接口,专门用于加载经典的MNIST数据集。它的底层实现了以下关键操作:
自动检测/下载数据
-
如果本地
root="data"目录下不存在MNIST文件,且download=True,会自动从互联网下载完整数据集(约60MB)。 -
若已存在则直接加载,避免重复下载。
筛选训练集
-
train=True表示仅加载60,000张带标签的训练图片(而非10,000张测试集)。
返回结构化数据
-
最终生成一个
Dataset对象,其中每个元素是一对(image, label):-
image: PIL Image对象的黑白图像(28x28像素) -
label: 对应的数字类别(0~9)
-
2. transform=ToTensor() — 数据格式化
这是最关键的预处理步骤,解决两个问题:
类型转换
将PIL Image格式的图片 → PyTorch张量(Tensor):
原始类型: <class 'PIL.Image.Image'> → 转换后: torch.FloatTensor (形状 [C, H, W])
-
同时将像素值从
[0, 255]缩放到[0.0, 1.0]区间,符合神经网络输入规范。
维度重排
调整图像数据的存储顺序:
原PIL格式: (Height=28, Width=28, Channel=1)
→ 张量格式: (Channel=1, Height=28, Width=28) # PyTorch默认通道优先
这种格式是卷积神经网络(CNN)要求的输入形式。
完整执行流程示例
当你运行这段代码时,实际发生的步骤如下:
|
步骤 |
操作内容 |
输出结果 |
|---|---|---|
|
1️ |
检查 |
无 → 触发下载;有 → 跳过下载 |
|
2️ |
解压并加载训练集(60,000张图片) |
得到原始PIL Image列表 |
|
3️ |
对每张图片应用 |
转换为浮点型张量(形状 [1,28,28]) |
|
4️ |
打包成 |
可通过索引访问单个样本: |
为何必须这样做?
-
设备兼容性
神经网络只能接受张量(Tensor)作为输入,无法直接处理PIL Image或NumPy数组。 -
数值范围适配
原始像素值0-255过大,归一化到0-1能加速梯度下降收敛。 -
维度标准化
PyTorch要求输入张量的维度顺序为(BatchSize, Channels, Height, Width),此处通过ToTensor()提前调整好单张图片的维度([1,28,28]),后续在DataLoader中堆叠成批次时只需增加第一维。 -
高效集成下游流程
转化后的张量可直接喂入DataLoader构建批量数据流,无缝衔接模型训练。
扩展思考
如果想进一步优化数据预处理,可以在 transform 中添加更多操作:
transform = transforms.Compose([
transforms.ToTensor(), # 基础转换
transforms.Normalize((0.1307,), (0.3081,)) # MNIST专属标准化
])
这里的 Normalize 会根据MNIST数据集的整体均值和标准差进行标准化,能有效提升模型性能。
总结
这段代码的本质是 将原始图像数据转化为神经网络可消化的张量格式,并通过 datasets.MNIST 这个高级接口完成了数据加载、筛选和预处理的全流程整合。它是深度学习项目中典型的数据处理范式。
这段代码的原理是什么?'''下载测试数据集(包含训练图片+标签) '''
test_data = datasets.MNIST(
root="data",
train=False,
download=True,
transform=ToTensor(),#Tensor是在深度学习中提出并广泛应用的数据类型,它与深度学习框架(如 PyTorch、TensorFlow)紧密集成,方便进行神经网络的训练和推理。
)#NumPy 数组只能在CPU上运行。Tensor可以在GPU上运行,这在深度学习应用中可以显著提高计算速度。
print(len(test_data))
'''下载测试数据集(包含训练图片+标签) '''
test_data = datasets.MNIST(
root="data",
train=False,
download=True,
transform=ToTensor(),#Tensor是在深度学习中提出并广泛应用的数据类型,它与深度学习框架(如 PyTorch、TensorFlow)紧密集成,方便进行神经网络的训练和推理。
)#NumPy 数组只能在CPU上运行。Tensor可以在GPU上运行,这在深度学习应用中可以显著提高计算速度。
print(len(test_data))
这段代码的核心目标是 加载MNIST数据集的测试集并进行标准化预处理,以下是逐层解析其原理及关键细节:
核心组件拆解
1. datasets.MNIST(...) — 数据集加载器
通过 PyTorch 内置接口加载经典的 MNIST 手写数字数据集,重点在于参数配置:train=False
-
这是区分训练集与测试集的关键参数。
-
train=True(默认):加载 60,000 张训练图片 + 对应标签 -
train=False:加载 10,000 张测试图片 + 对应标签(独立于训练集)
-
-
测试集用于模型训练完成后的最终性能评估,确保结果客观。
root="data"
-
指定本地存储路径。若该目录不存在,会自动创建;若已存在且包含完整数据集,则直接加载。
download=True
-
若本地无缓存文件,会自动从网络下载 MNIST 数据集(约 60MB)。
-
若已下载过,此参数可省略或设为
False。
transform=ToTensor()
-
对每张图片执行以下预处理:
-
类型转换:将 PIL Image 格式的图片转换为 PyTorch 张量(
torch.FloatTensor)。 -
数值归一化:像素值从
[0, 255]缩放到[0.0, 1.0]区间,适配神经网络输入需求。 -
维度重排:调整图片维度顺序为
(Channel, Height, Width),即(1, 28, 28),符合 PyTorch 卷积层的输入规范(通道优先)。
-
2. print(len(test_data)) — 输出数据集大小
功能:打印测试集的总样本数。
预期输出:10000
-
因为 MNIST 测试集固定包含 10,000 张图片。
完整执行流程示例
|
步骤 |
操作内容 |
输出结果 |
|---|---|---|
|
1️ |
检查 |
无 → 触发下载;有 → 跳过下载 |
|
2️ |
解压并加载测试集(10,000 张图片) |
原始 PIL Image 列表 |
|
3️ |
对每张图片应用 |
转换为浮点型张量(形状 |
|
4️ |
打包成 |
可通过索引访问单个样本: |
|
5️ |
执行 |
输出 |
常见误区澄清
-
测试集 vs 训练集
-
测试集仅用于模型评估,绝对不能参与训练(如反向传播更新权重)。
-
若误用测试集训练,会导致模型“作弊”(记忆测试集特征),评估结果虚高。
-
-
ToTensor()的必要性-
原始图片是 PIL Image 格式,无法直接输入神经网络。
-
转换为张量后,可无缝接入 PyTorch 的计算图,并支持 GPU 加速。
-
-
数据增强的适用性
-
测试集通常不进行数据增强(如随机翻转、裁剪),以模拟真实场景下的推理过程。
-
数据增强一般仅应用于训练集,防止过拟合。
-
张量(Tensor)的核心优势
|
特性 |
NumPy 数组 |
PyTorch 张量 |
|---|---|---|
|
设备支持 |
CPU 仅限 |
CPU/GPU 双支持 |
|
梯度追踪 |
不支持 |
自动微分 |
|
显存优化 |
静态分配 |
动态内存管理 |
|
并行计算 |
单线程 |
多 GPU 分布式 |
实际意义:当模型部署到 GPU 时,张量可直接利用 CUDA 核心进行矩阵运算,相比 CPU 上的 NumPy 数组速度提升数十倍。
总结
这段代码实现了以下功能:
-
精准加载测试集:通过
train=False确保获取独立的 10,000 张测试图片及其标签。 -
标准化预处理:使用
ToTensor()将图片转换为张量,并调整数值范围和维度顺序。 -
设备兼容性:生成的张量可直接用于 GPU 加速推理或训练。
-
数据隔离原则:严格区分训练集和测试集,避免信息泄露导致的评估偏差。
重要提醒:测试集应在模型训练完成后仅用于最终评估,不可参与训练过程或超参数调优。
'''创建数据DataLoader(数据加载器)
batch_size:将数据集分成多份,每一份为batch_size个数据。
优点:可以减少内存的使用,提高训练速度。
'''
train_dataloader = DataLoader(training_data, batch_size=64)#64张图片为一个包,1、损失函数2、GPU一次性接受的图片个数
test_dataloader = DataLoader(test_data, batch_size=64)
for X, y in test_dataloader:#X是表示打包好的每一个数据包
print(f"Shape of X [N, C, H, W]: {X.shape}")#
print(f"Shape of y: {y.shape} {y.dtype}")
break
以下是对代码的逐行直接解读:
第1行 train_dataloader = DataLoader(training_data, batch_size=64)
- 功能:创建一个名为
train_dataloader的数据加载器。 - 参数:
training_data:输入的训练数据集(需为 PyTorchDataset对象)。batch_size=64:指定每个批次包含 64个样本。
- 作用:将训练数据按顺序划分为多个大小为64的批次(Batch),供模型训练时迭代使用。
第2行 test_dataloader = DataLoader(test_data, batch_size=64)
- 功能:创建一个名为
test_dataloader的数据加载器。 - 参数:
test_data:输入的测试数据集(需为 PyTorchDataset对象)。batch_size=64:指定每个批次包含 64个样本。
- 作用:将测试数据按顺序划分为多个大小为64的批次,供模型验证/测试时迭代使用。
第3行 for X, y in test_dataloader:
- 功能:遍历
test_dataloader中的每个批次。 - 变量:
X:当前批次的所有输入特征(如图像数据)。y:当前批次对应的真实标签(如类别索引)。
- 作用:逐个提取测试集中的批次数据用于后续操作(如打印形状)。
第4行 print(f"Shape of X [N, C, H, W]: {X.shape}")
- 功能:打印输入数据
X的形状。 - 输出示例:
torch.Size([64, 1, 28, 28])N=64:批次大小(64个样本)。C=1:通道数(单通道,如灰度图)。H=28:图像高度(像素)。W=28:图像宽度(像素)。
- 作用:验证输入数据的维度是否符合预期(如 NCHW 格式)。
第5行 print(f"Shape of y: {y.shape} {y.dtype}")
- 功能:打印标签
y的形状和数据类型。 - 输出示例:
torch.Size([64]) torch.int64y.shape=[64]:标签数量与批次大小一致(每个样本对应一个标签)。y.dtype=int64:标签的数据类型为长整型(分类任务的标准类型)。
- 作用:确认标签的维度和类型是否正确(如是否为整数类型)。
第6行 break
- 功能:终止循环。
- 作用:仅处理第一个批次后退出循环,避免遍历整个测试集。
总结
| 代码行 | 核心功能 | 关键参数/变量 |
|---|---|---|
| 第1行 | 创建训练集数据加载器 | batch_size=64 |
| 第2行 | 创建测试集数据加载器 | batch_size=64 |
| 第3行 | 遍历测试集的一个批次 | X(输入)、y(标签) |
| 第4行 | 打印输入数据形状 | X.shape |
| 第5行 | 打印标签形状及数据类型 | y.shape, y.dtype |
| 第6行 | 终止循环(仅处理第一个批次) |
|
'''判断当前设备是否支持GPU,其中mps是苹果m系列芯片的GPU。'''#返回cuda,mps。CPU m1 ,m2 集显CPU+GPU RTX3060,
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")#字符串的格式化。 CUDA驱动软件的功能:pytorch能够去执行cuda的命令,cuda通过GPU指令集去控制GPU
#神经网络的模型也需要传入到GPU,1个batchsize的数据集也需要传入到GPU,才可以进行训练。
以下是对代码的逐行直接解读:
第1行 device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
- 功能:按优先级顺序选择计算设备。
- 逻辑链:
- 首选
"cuda":若torch.cuda.is_available()返回True(表示存在英伟达 GPU),则device = "cuda"。 - 次选
"mps":若不存在 CUDA 设备,则检查torch.backends.mps.is_available()(判断是否支持苹果 M 系列芯片的 GPU)。若为True,则device = "mps"。 - 默认
"cpu":若以上两者均不可用,则回退到 CPU。
- 首选
- 特点:通过短路求值法实现多级条件判断,仅第一个满足条件的分支生效。
第2行 print(f"Using {device} device")
- 功能:打印最终选定的设备名称。
- 示例输出:
-
Using cuda device(检测到英伟达 GPU) -
Using mps device(检测到苹果 M 系列芯片) Using cpu device(无可用加速设备)
-
- 作用:直观展示当前代码运行所在的计算设备。
''' 定义神经网络 类的继承这种方式'''
class NeuralNetwork(nn.Module):#通过调用类的形式来使用神经网络,神经网络的模型,nn.module
def __init__(self):#python基础关于类,self类自己本身
super().__init__()#继承的父类初始化
self.flatten = nn.Flatten()#展开,创建一个展开对象flatten
self.hidden1 = nn.Linear(28*28, 128)#第1个参数:有多少个神经元传入进来,第2个参数:有多少个数据传出去前一层神经元的个数,当前本层神经元个数
self.hidden2 = nn.Linear(128, 256)#为什么你要用128
self.out = nn.Linear(256, 10)#输出必需和标签的类别相同,输入必须是上一层的神经元个数
def forward(self, x): #前向传播,你得告诉它 数据的流向。是神经网络层连接起来,函数名称不能改。当你调用forward函数的时候,传入进来的图像数据
x = self.flatten.forward(x) #图像进行展开 self.flatten.forward
x = self.hidden1.forward(x)
x = torch.relu(x) #激活函数,torch使用的relu函数 relu,tanh
x = self.hidden2.forward(x)
x = torch.relu(x)
x = self.out.forward(x)
return x
model = NeuralNetwork().to(device)#把刚刚创建的模型传入到Gpu
print(model)
def train(dataloader, model, loss_fn, optimizer):
model.train()#告诉模型,我要开始训练,模型中w进行随机化操作,已经更新w。在训练过程中,w会被修改的
#pytorch提供2种方式来切换训练和测试的模式,分别是:model.train() 和 model.eval()。
# 一般用法是:在训练开始之前写上model.trian(),在测试时写上 model.eval() 。
batch_size_num = 1 #统计 训练的batch数量
for X, y in dataloader: #其中batch为每一个数据的编号
X, y = X.to(device), y.to(device) #把训练数据集和标签传入cpu或GPU
pred = model(X) #.forward可以被省略,父类中已经对此功能进行了设置。自动初始化 w权值
loss = loss_fn(pred, y) #通过交叉熵损失函数计算损失值loss
# Backpropagation 进来一个batch的数据,计算一次梯度,更新一次网络
optimizer.zero_grad() #梯度值清零
loss.backward() #反向传播计算得到每个参数的梯度值w
optimizer.step() #根据梯度更新网络w参数
loss_value = loss.item() #从tensor数据中提取数据出来,tensor获取损失值
if batch_size_num %100 ==0:
print(f"loss: {loss_value:>7f} [number:{batch_size_num}]")
batch_size_num += 1
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset)#10000
num_batches = len(dataloader)#打包的数量
model.eval() #测试,w就不能再更新。
test_loss, correct = 0, 0 #
with torch.no_grad(): #一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。
for X, y in dataloader:
X, y = X.to(device), y.to(device) #送到GPU
pred = model.forward(X)
test_loss += loss_fn(pred, y).item() #test_loss是会自动累加每一个批次的损失值
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
a = (pred.argmax(1) == y) #dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号
b = (pred.argmax(1) == y).type(torch.float)
test_loss /= num_batches #能来衡量模型测试的好坏。
correct /= size #平均的正确率
print(f"Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}")
loss_fn = nn.CrossEntropyLoss() #创建交叉熵损失函数对象,因为手写字识别中一共有10个数字,输出会有10个结果
# L1Loss:L1损失,也称为平均绝对误差(Mean Absolute Error, MAE)。它计算预测值与真实值之间的绝对差值的平均值。
# NLLLoss:负对数似然损失(Negative Log Likelihood Loss)。它用于多分类问题,通常与LogSoftmax输出层配合使用。
# NLLLoss2d:这是NLLLoss的一个特殊版本,用于处理2D图像数据。在最新版本的PyTorch中,这个损失函数可能已经被整合到NLLLoss中,通过指定reduction参数来实现同样的功能。
# PoissonNLLLoss:泊松负对数似然损失,用于泊松回归问题。
# GaussianNLLLoss:高斯负对数似然损失,用于高斯分布(正态分布)的回归问题。
# KLDivLoss:Kullback-Leibler散度损失,用于度量两个概率分布之间的差异。
# MSELoss:均方误差损失(Mean Squared Error Loss),计算预测值与真实值之间差值的平方的平均值。
# BCELoss:二元交叉熵损失(Binary Cross Entropy Loss),用于二分类问题。
# BCEWithLogitsLoss:结合了Sigmoid激活函数和二元交叉熵损失的损失函数,用于提高数值稳定性。
# HingeEmbeddingLoss:铰链嵌入损失,用于学习非线性嵌入或半监督学习。
# MultiLabelMarginLoss:多标签边际损失,用于多标签分类问题。
# SmoothL1Loss:平滑L1损失,是L1损失和L2损失(MSE)的结合,旨在避免梯度爆炸问题。
# HuberLoss:Huber损失,与SmoothL1Loss类似,但有一个可调的参数来控制L1和L2损失之间的平衡。
# SoftMarginLoss:软边际损失,用于二分类问题,可以看作是Hinge损失的一种软化版本。
# CrossEntropyLoss:交叉熵损失,用于多分类问题。它结合了LogSoftmax和NLLLoss的功能。
# MultiLabelSoftMarginLoss:多标签软边际损失,用于多标签二分类问题。
# CosineEmbeddingLoss:余弦嵌入损失,用于学习非线性嵌入,通过余弦相似度来度量样本之间的相似性。
# MarginRankingLoss:边际排序损失,用于排序问题,如学习到排序的嵌入空间。
# MultiMarginLoss:多边际损失,用于多分类问题,旨在优化分类边界的边际。
# TripletMarginLoss:三元组边际损失,用于学习嵌入空间中的距离度量,通常用于人脸识别或图像检索等任务。
# TripletMarginWithDistanceLoss:这是TripletMarginLoss的一个变体,允许使用自定义的距离函数。
# CTCLoss:连接时序分类损失(Connectionist Temporal Classification Loss),用于序列到序列的学习问题,特别是当输出序列的长度不固定时(如语音识别)。
#一会改成adam优化器 梯度下降
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)#创建一个优化器,SGD为随机梯度下降算法
# #params:要训练的参数,一般我们传入的都是model.parameters()。
# #lr:learning_rate学习率,也就是步长。
#loss表示模型训练后的输出结果与 样本标签的差距。如果差距越小,就表示模型训练越好,越逼近于真实的模型。
# train(train_dataloader, model, loss_fn, optimizer)#训练1次完整的数据,多轮训练,
# test(test_dataloader, model, loss_fn)
epochs = 10 #到底选择多少呢?
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)#10次训练
print("Done!")
test(test_dataloader, model, loss_fn)
以下是对代码的逐行直接解读:
class NeuralNetwork(nn.Module):
- 功能:定义一个继承自
nn.Module的神经网络类。 - 说明:所有 PyTorch 神经网络必须继承
nn.Module类以支持自动微分等功能。
def __init__(self):
- 功能:类的构造函数,初始化网络结构。
- 关键操作:
super().__init__():调用父类nn.Module的初始化方法。self.flatten = nn.Flatten():将输入数据展平为一维向量(适用于图像像素展开)。self.hidden1 = nn.Linear(28*28, 128):全连接层,输入尺寸为 28×28=784,输出 128 维。self.hidden2 = nn.Linear(128, 256):第二层全连接层,输入 128 维,输出 256 维。self.out = nn.Linear(256, 10):输出层,输入 256 维,输出 10 维(对应 10 个类别)。
def forward(self, x):
- 功能:定义前向传播逻辑(数据流经网络的顺序)。
- 关键操作:
x = self.flatten.forward(x):展平输入数据(如 28×28 图像 → 784 维向量)。x = self.hidden1.forward(x):通过第一层全连接层。x = torch.relu(x):应用 ReLU 激活函数。x = self.hidden2.forward(x):通过第二层全连接层。x = torch.relu(x):再次应用 ReLU 激活函数。x = self.out.forward(x):通过输出层,生成最终预测结果。
- 返回值:模型输出的原始预测值(未归一化)。
model = NeuralNetwork().to(device)
- 功能:实例化模型并将参数加载到指定设备(GPU/MPS/CPU)。
- 说明:
device由前文逻辑决定(优先 CUDA → MPS → CPU)。
print(model)
- 功能:打印模型结构,展示各层名称及参数数量。
def train(dataloader, model, loss_fn, optimizer):
- 功能:训练模型的主函数。
- 关键操作:
model.train():设置模型为训练模式(启用 Dropout 等正则化层)。for X, y in dataloader::遍历训练数据的批次。X, y = X.to(device), y.to(device):将数据和标签移动到设备(GPU/CPU)。pred = model(X):前向传播生成预测值。loss = loss_fn(pred, y):计算交叉熵损失。optimizer.zero_grad():清空上一轮梯度。loss.backward():反向传播计算梯度。optimizer.step():根据梯度更新模型参数。if batch_size_num % 100 == 0::每 100 个批次打印一次损失值。
def test(dataloader, model, loss_fn):
- 功能:测试模型性能的主函数。
- 关键操作:
model.eval():设置模型为评估模式(关闭 Dropout 等正则化层)。with torch.no_grad()::禁用梯度计算以加速推理并减少内存占用。pred = model.forward(X):前向传播生成预测值。test_loss += loss_fn(pred, y).item():累计测试损失。correct += (pred.argmax(1) == y).type(torch.float).sum().item():统计正确预测数量。test_loss /= num_batches:计算平均测试损失。correct /= size:计算准确率。print(f"Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}"):输出测试结果。
loss_fn = nn.CrossEntropyLoss()
- 功能:定义损失函数为交叉熵损失(适用于多分类任务)。
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)
- 功能:定义优化器为 Adam 算法,学习率为 0.005。
- 说明:
model.parameters()获取模型所有可训练参数。
epochs = 10
- 功能:设置训练轮次为 10 次。
for t in range(epochs): ... train(...) ... test(...)
- 功能:执行训练循环,每个 epoch 完成后进行测试。
- 流程:
- 打印当前 epoch 编号。
- 调用
train()进行训练。 - 所有 epoch 完成后调用
test()评估最终性能。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)