完整代码:
# 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)
# # #分析sigmiod,relu
# # # sgd,Adam
按代码模块进行解析:
第一部分:环境验证与数据加载
# import torch
# print(torch.__version__)#1.X 1、验证安装的开发环境是否正确,
注释掉了,用于检查 PyTorch 是否安装成功。
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
导入 PyTorch 核心库及相关模块:
nn
:构建神经网络。DataLoader
:批量加载数据。datasets
:内置数据集(如 MNIST)。ToTensor
:将图片转为 Tensor 格式。
training_data = datasets.MNIST(
root="data",
train=True,
download=True,
transform=ToTensor(),
)
下载 训练集(60,000 张图):
root="data"
:保存到本地data/
文件夹。transform=ToTensor()
:将图片转为 Tensor(灰度值归一化到 [0, 1])。
test_data = datasets.MNIST(
root="data",
train=False,
download=True,
transform=ToTensor(),
)
下载 测试集(10,000 张图)。
train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
使用
DataLoader
将数据打包成 批次(每批 64 张图),方便训练。
第二部分:设备选择与模型定义
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
自动选择设备:
优先使用 NVIDIA GPU(cuda);
其次 Apple 芯片(mps);
最后回退到 CPU。
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.hidden1 = nn.Linear(28*28, 128)
self.hidden2 = nn.Linear(128, 256)
self.out = nn.Linear(256, 10)
def forward(self, x):
x = self.flatten(x)
x = torch.relu(self.hidden1(x))
x = torch.relu(self.hidden2(x))
x = self.out(x)
return x
定义一个 三层全连接神经网络:
输入层:28×28 = 784 像素;
隐藏层1:128 个神经元;
隐藏层2:256 个神经元;
输出层:10 个类别(0~9 数字);
激活函数:ReLU。
model = NeuralNetwork().to(device)
将模型迁移到 GPU(或 CPU)。
第三部分:训练与测试函数
def train(dataloader, model, loss_fn, optimizer):
model.train()
for batch, (X, y) in enumerate(dataloader):
X, y = X.to(device), y.to(device)
pred = model(X)
loss = loss_fn(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 100 == 0:
print(f"loss: {loss.item():.7f} [batch {batch}]")
训练函数:
每个批次前向传播 → 计算损失 → 反向传播 → 更新权重;
每 100 个批次打印一次损失值。
def test(dataloader, model, loss_fn):
model.eval()
test_loss, correct = 0, 0
with torch.no_grad():
for X, y in dataloader:
X, y = X.to(device), y.to(device)
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= len(dataloader)
correct /= len(dataloader.dataset)
print(f"Test Accuracy: {100*correct:.2f}%, Avg loss: {test_loss:.4f}")
测试函数:
不更新权重(
model.eval()
);计算整体损失与准确率。
第四部分:损失函数与优化器
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)
使用 交叉熵损失(适合多分类);
使用 Adam 优化器(比 SGD 更稳定)。
第五部分:训练循环
epochs = 10
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)
print("Done!")
test(test_dataloader, model, loss_fn)
训练 10 轮;
每轮遍历一次完整训练集;
最后测试模型性能。
代码模块具体逐行逐句解释:
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.hidden1 = nn.Linear(28*28, 128)
self.hidden2 = nn.Linear(128, 256)
self.out = nn.Linear(256, 10)
def forward(self, x):
x = self.flatten(x)
x = torch.relu(self.hidden1(x))
x = torch.relu(self.hidden2(x))
x = self.out(x)
return x
第一部分:类定义与初始化 (__init__
方法)
class NeuralNetwork(nn.Module):
作用:声明一个继承自 PyTorch 基类 nn.Module
的新类
含义:所有自定义神经网络必须继承此类才能享受 PyTorch 的训练/推理功能(如 model.train()
, model.eval()
)
super().__init__()
作用:调用父类 nn.Module
的构造函数
重要性:初始化模块的必要内部结构(如参数注册器),不可省略
self.flatten = nn.Flatten()
作用:创建一个展平层对象
功能详解:将多维输入(如图像 [B, H, W])压缩为一维向量 [B, H×W]
典型场景:连接卷积层和非全连接层时的过渡操作
self.hidden1 = nn.Linear(28*28, 128)
作用:定义第一个全连接层(又称密集层)
参数解析:
in_features=28*28=784
:输入特征数(对应 28×28 图像的像素总数)out_features=128
:本层神经元数量
内部机制:自动创建权重矩阵W₁
(形状 784×128) 和偏置向量b₁
(长度 128)
self.hidden2 = nn.Linear(128, 256)
作用:定义第二个全连接层
参数解析:
in_features=128
:前一层的输出特征数out_features=256
:本层神经元数量
内部机制:自动创建权重矩阵W₂
(形状 128×256) 和偏置向量b₂
(长度 256)
self.out = nn.Linear(256, 10)
作用:定义输出层
特殊设计:
out_features=10
:对应分类任务的类别数(如 MNIST 手写数字识别)输出未经过激活函数(直接输出 logits),配合交叉熵损失函数使用
第二部分:前向传播 (forward
方法)
def forward(self, x):
作用:定义数据的前向传播路径
关键性质:每次调用 model(input)
时会自动执行此方法
x = self.flatten(x)
作用:展平输入张量
示例:
输入形状
[batch_size, 28, 28]
→ 输出形状[batch_size, 784]
必要性:全连接层只能接受一维特征向量
x = torch.relu(self.hidden1(x))
作用:通过第一隐藏层并进行 ReLU 激活
计算过程:
线性变换:
x = W₁·x + b₁
ReLU 激活:
x[x<0]=0
(保留正值,引入非线性)
设计理由:解决线性模型无法拟合复杂模式的问题
x = torch.relu(self.hidden2(x))
作用:通过第二隐藏层并进行 ReLU 激活
计算过程:
线性变换:
x = W₂·x + b₂
ReLU 激活:同上
效果:进一步提取高阶特征,增加模型表达能力
x = self.out(x)
作用:通过输出层生成最终结果
注意:此处不添加激活函数
原因:分类任务通常在损失函数中结合 LogSoftmax(如 CrossEntropyLoss
),logits 更灵活可控
return x
作用:返回模型输出
输出形式:原始 logits(未归一化的概率分数)
后续处理:通常会接入软最大值函数(Softmax)进行概率转换,或直接用于计算损失
def train(dataloader, model, loss_fn, optimizer):
model.train()
for batch, (X, y) in enumerate(dataloader):
X, y = X.to(device), y.to(device)
pred = model(X)
loss = loss_fn(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 100 == 0:
print(f"loss: {loss.item():.7f} [batch {batch}]")
第1行:model.train()
功能:将模型设置为 训练模式
底层逻辑:
激活模型中所有适用于训练的特殊组件。
示例:
nn.Dropout(p=0.5)
在训练时会随机屏蔽50%的神经元,而在推理模式(model.eval()
)下无效。
确保模型处于可学习状态(参数注册钩子启用)。
关键性:若省略此步,模型可能因未启用必要层(如 Dropout)导致性能下降或错误。
第2行:for batch, (X, y) in enumerate(dataloader):
功能:遍历数据集的一个完整周期(Epoch)
参数解析:
dataloader
:PyTorch 的DataLoader
对象,负责按批次加载数据。enumerate()
:同时获取当前批次的索引batch
和数据(X, y)
。
典型输出:X
: 输入特征张量,形状为[batch_size × input_dim]
(如图像数据为[B, C, H, W]
)。y
: 目标标签张量,形状为[batch_size × output_dim]
(分类任务通常为 one-hot 编码或类别索引)。
设计目的:通过迭代实现 mini-batch SGD(随机梯度下降),逐步优化模型参数。
第3-4行:X, y = X.to(device), y.to(device)
功能:将数据迁移到指定计算设备(CPU/GPU)
底层逻辑:
device
通常是预定义的变量(如torch.device('cuda')
),表示可用的硬件资源。.to(device)
方法执行以下操作:数据搬运:将张量从 CPU 内存复制到 GPU 显存(若
device='cuda'
)。类型匹配:自动转换数据类型以匹配模型参数的类型(如
float32
)。
重要性:确保模型与数据在同一设备上运算,否则会抛出RuntimeError: ... not on the same device
。
注意:此操作仅影响张量的存储位置,不改变其数值内容。
第5行:pred = model(X)
功能:执行前向传播(Forward Propagation)
计算流程:
输入
X
经模型各层依次变换(如线性层、激活函数、归一化层等)。输出
pred
是模型对输入X
的原始预测值(Logits),尚未应用任何激活函数。
维度示例:
输入形状:
[batch_size, input_dim]
→ 输出形状:[batch_size, num_classes]
(分类任务)。
注意:此处保持线性输出,供损失函数后续处理。
第6行:loss = loss_fn(pred, y)
功能:计算当前批次的损失值
核心机制:
loss_fn
是预定义的损失函数(如nn.CrossEntropyLoss()
或nn.MSELoss()
)。对比模型输出
pred
与真实标签y
,量化预测误差。
数学本质:分类任务:交叉熵损失
L = -Σ(y * log(softmax(pred)))
。回归任务:均方误差
L = ||pred - y||²_2
。
作用:为反向传播提供优化目标,指导参数更新方向。
第7行:optimizer.zero_grad()
功能:清空优化器的梯度缓存
底层逻辑:
PyTorch 采用 累积梯度 策略,每次调用
loss.backward()
会将新梯度累加到现有梯度上。此操作将所有可训练参数的梯度置零,防止跨批次梯度混合。
必要性:若不执行此步,梯度会指数级增长,导致参数更新异常剧烈(如爆炸性梯度)。
内部实现:遍历模型的所有可训练参数,执行param.grad = None
。
第8行:loss.backward()
功能:执行反向传播(Backward Propagation)
计算流程:
根据链式法则自动计算损失对每个参数的梯度。
梯度存储在
param.grad
属性中(仅存在于requires_grad=True
的参数)。
技术核心:利用自动微分系统高效计算复杂计算图的梯度。
注意:此操作 不会立即更新参数,仅计算梯度。
第9行:optimizer.step()
功能:根据梯度更新模型参数
执行过程:
优化器(如 SGD、Adam)按照预设规则(学习率、动量等)更新参数。
SGD 示例:
param = param - lr * param.grad
。
完成一次参数更新后,梯度会被自动清零(部分优化器除外)。
效果:使模型向损失降低的方向调整参数。
注意:此操作是参数更新的唯一入口,必须在zero_grad()
之后调用。
第10-11行:if batch % 100 == 0: print(f"loss: {loss.item():.7f} [batch {batch}]")
功能:定期打印训练进度
实现细节:
batch % 100 == 0
:每处理 100 个批次打印一次日志。loss.item()
:将张量转换为 Python 标量(浮点数),用于格式化输出。
输出示例:loss: 0.1234567 [batch 100]
。
用途:监控训练稳定性,辅助调试(如发现 NaN 或爆炸性损失)。