【深度学习②】| DNN篇
0 序言
本文将系统介绍基于PyTorch的深度神经网络(DNN)相关知识,包括张量的基础操作
、DNN的工作原理
、实现流程
,以及批量梯度下降
、小批量梯度下降方法
和手写数字识别案例
。通过学习,你将掌握DNN的核心概念、PyTorch实操技能,理解从数据处理到模型训练、测试的完整流程,具备搭建和应用简单DNN模型的能力。
1 张量
在深度学习中,数据的表示与处理是所有操作的基础。张量作为PyTorch中最核心的数据结构,承载着数据存储与计算的功能,是构建神经网络、实现模型训练的前提。因此,我们首先从张量的基本概念和操作入手。
1.1 数组与张量
NumPy的数组和PyTorch的张量是数据处理的核心结构,二者在语法上高度相似,但张量支持GPU加速,更适用于深度学习。因此在学习本小节前,如果你没有NumPy数组的相关基础,点此链接先学习后再学习本文:深度学习①-NumPy数组篇
- 对应关系:
np
对应torch
,数组array
对应张量tensor
,n维数组对应n阶张量。 - 转换方法:
1.数组转张量:ts = torch.tensor(arr)
2.张量转数组:arr = np.array(ts)
1.2 从数组到张量
PyTorch在NumPy基础上修改了部分函数或方法,主要差异见下表:
NumPy的函数 | PyTorch的函数 | 用法区别 |
---|---|---|
.astype() | .type() | 无 |
np.random.random() | torch.rand() | 无 |
np.random.randint() | torch.randint() | 不接纳一维张量 |
np.random.normal() | torch.normal() | 不接纳一维张量 |
np.random.randn() | torch.randn() | 无 |
.copy() | .clone() | 无 |
np.concatenate() | torch.cat() | 无 |
np.split() | torch.split() | 参数含义优化 |
np.dot() | torch.matmul() | 无 |
np.dot(v,v) | torch.dot() | 无 |
np.dot(m,v) | torch.mv() | 无 |
np.dot(m,m) | torch.mm() | 无 |
np.exp() | torch.exp() | 必须传入张量 |
np.log() | torch.log() | 必须传入张量 |
np.mean() | torch.mean() | 必须传入浮点型张量 |
np.std() | torch.std() | 必须传入浮点型张量 |
1.3 用GPU存储张量
默认情况下,张量存储在CPU中,而GPU能显著提升计算速度,因此需掌握张量和模型的GPU迁移方法。
- 张量迁移到GPU:
import torch ts1 = torch.randn(3,4) # 存储在CPU ts2 = ts1.to('cuda:0') # 迁移到第一块GPU(cuda:0)
- 模型迁移到GPU:
class DNN(torch.nn.Module): # 定义神经网络类pass # 具体结构略 model = DNN().to('cuda:0') # 模型迁移到GPU
- 查看GPU运行情况:在cmd中输入
nvidia-smi
,可查看显卡型号、内存使用等信息。当然了,硬件设备没有GPU的直接用CPU也行,速度慢一些,如果你电脑里有多个显卡,那你可以迁移到多块上,比如会有cuda:1、cuda:2等等。
2 DNN的原理
上一章介绍了数据的基础载体,也就是张量,而深度神经网络的核心是通过学习数据特征拟合输入与输出的关系。本章将解析DNN的工作原理,包括数据集处理
、训练
、测试
和使用
的完整流程。
2.1 划分数据集
数据集是模型学习的基础,需包含输入特征和输出特征,并划分为训练集(用于模型学习)和测试集(用于验证模型泛化能力)。
- 划分原则:通常按7:3或8:2的比例划分,如1000个样本中800个为训练集,200个为测试集。
- 对应关系:输入特征的数量决定输入层神经元数,输出特征的数量决定输出层神经元数(例如:3个输入特征→输入层3个神经元,3个输出特征→输出层3个神经元)。
2.2 训练网络
训练是DNN优化内部参数(权重和偏置)的过程,通过前向传播计算预测值,再通过反向传播调整参数,最终拟合函数。
2.2.1 前向传播
输入特征从输入层逐层传递到输出层,每个神经元的计算为:
- 线性计算:y=ω1x1+ω2x2+...+ωnxn+by = \omega_1 x_1 + \omega_2 x_2 + ... + \omega_n x_n + by=ω1x1+ω2x2+...+ωnxn+b
其中,ω\omegaω为权重,bbb为偏置 - 非线性转换:y=σ(线性计算结果)y = \sigma(\text{线性计算结果})y=σ(线性计算结果)
其中,σ\sigmaσ为激活函数,如ReLU、Sigmoid,用于引入非线性,避免多层网络等效于单层)
2.2.2 反向传播
通过损失函数计算预测值与真实值的差距,再反向求解梯度,调整权重和偏置以减小损失。
- 损失函数:衡量预测误差(如MSE、BCELoss)。
- 学习率:控制参数调整幅度,
过大可能导致损失震荡
,过小则收敛缓慢
。
2.2.3 batch_size
每次前向传播和反向传播的样本数量,影响训练效率和稳定性:
- 批量梯度下降(BGD):一次输入所有样本,计算量大、速度慢。
- 随机梯度下降(SGD):一次输入1个样本,速度快但收敛不稳定。
- 小批量梯度下降(MBGD):一次输入若干样本(如32、64),平衡效率和稳定性,常用且batch_size通常设为2的幂次方。
2.2.4 epochs
全部样本完成一次前向和反向传播的次数。例:10240个样本,batch_size=1024,则1个epoch包含10240/1024=10次传播,5个epoch共50次传播。
2.3 测试网络
测试用于验证模型的泛化能力,避免过拟合,
过拟合就是模型仅适配训练集,对新数据无效。
比如说在训练集准确率能到100%,但是到新数据集只有50-60%这种情况。
解决方法:用测试集输入进行前向传播,对比预测输出与真实输出,计算准确率。
2.4 使用网络
训练好的模型可用于新数据预测,只需输入新样本的特征,通过一次前向传播得到输出。
3 DNN的实现
掌握了DNN的原理后,本章将通过PyTorch实操实现一个完整的DNN模型,包括数据集制作、网络搭建、训练、测试及模型保存。
3.1 制作数据集
需生成包含输入和输出特征的样本,并划分训练集和测试集。
import torch# 生成10000个样本,3个输入特征(均匀分布),3个输出特征(One-Hot编码)
X1 = torch.rand(10000, 1)
X2 = torch.rand(10000, 1)
X3 = torch.rand(10000, 1)
Y1 = ((X1 + X2 + X3) < 1).float()
Y2 = ((1 < (X1 + X2 + X3)) & ((X1 + X2 + X3) < 2)).float()
Y3 = ((X1 + X2 + X3) > 2).float()# 整合数据集并迁移到GPU
Data = torch.cat([X1, X2, X3, Y1, Y2, Y3], axis=1).to('cuda:0')# 划分训练集(70%)和测试集(30%)
Data = Data[torch.randperm(Data.size(0)), :] # 打乱顺序
train_size = int(len(Data) * 0.7)
train_Data = Data[:train_size, :]
test_Data = Data[train_size:, :]
3.2 搭建神经网络
基于torch.nn.Module
构建网络,需定义__init__
(网络结构)和forward
(前向传播)方法。
import torch.nn as nnclass DNN(nn.Module):def __init__(self):super(DNN, self).__init__()self.net = nn.Sequential(nn.Linear(3, 5), nn.ReLU(), # 输入层→隐藏层1(3→5神经元,ReLU激活)nn.Linear(5, 5), nn.ReLU(), # 隐藏层1→隐藏层2nn.Linear(5, 5), nn.ReLU(), # 隐藏层2→隐藏层3nn.Linear(5, 3) # 隐藏层3→输出层(5→3神经元))def forward(self, x):return self.net(x) # 前向传播# 创建模型并迁移到GPU
model = DNN().to('cuda:0')
其中,
self.net = nn.Sequential(nn.Linear(3, 5), nn.ReLU(), # 输入层→隐藏层1(3→5神经元,ReLU激活)nn.Linear(5, 5), nn.ReLU(), # 隐藏层1→隐藏层2nn.Linear(5, 5), nn.ReLU(), # 隐藏层2→隐藏层3nn.Linear(5, 3) # 隐藏层3→输出层(5→3神经元)
)
以上程序的计算逻辑如下图:
3.3 网络的内部参数
内部参数即权重(weight)和偏置(bias),初始为随机值(如Xavier、He初始值),训练中不断优化。
- 查看参数:
for name, param in model.named_parameters():print(f"参数:{name}\n 形状:{param.shape}\n 数值:{param}\n")
- 参数特点:存储在GPU上(
device='cuda:0'
),且开启梯度计算(requires_grad=True
)。
3.4 网络的外部参数
外部参数(超参数)需在训练前设定,包括:
- 网络结构:层数、隐藏层节点数、激活函数(如ReLU、Sigmoid)。
- 训练参数:损失函数(如
nn.MSELoss()
)、优化器(如torch.optim.SGD
)、学习率、batch_size、epochs。
3.5 训练网络
通过多轮前向传播、损失计算、反向传播和参数优化,最小化损失。
epochs = 1000
losses = [] # 记录损失变化
loss_fn = nn.MSELoss() # 损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 优化器# 提取训练集输入和输出
X = train_Data[:, :3]
Y = train_Data[:, -3:]for epoch in range(epochs):Pred = model(X) # 前向传播loss = loss_fn(Pred, Y) # 计算损失losses.append(loss.item()) # 记录损失optimizer.zero_grad() # 清空梯度loss.backward() # 反向传播optimizer.step() # 更新参数
3.6 测试网络
关闭梯度计算,用测试集验证模型准确率。
# 提取测试集输入和输出
X = test_Data[:, :3]
Y = test_Data[:, -3:]with torch.no_grad(): # 关闭梯度Pred = model(X) # 前向传播# 规整预测值(与真实值格式一致)Pred[:, torch.argmax(Pred, axis=1)] = 1Pred[Pred != 1] = 0# 计算准确率correct = torch.sum((Pred == Y).all(1))total = Y.size(0)print(f'测试集精准度: {100 * correct / total} %')
3.7 保存与导入网络
训练好的模型需保存,以便后续使用。
- 保存模型:
torch.save(model, 'model.pth')
- 导入模型:
new_model = torch.load('model.pth')
- 验证导入:用新模型测试,准确率应与原模型一致。
3.8 完整程序
这里演示的并未用到GPU。
import torch
import torch.nn as nn
import torch.optim as optim# ======================== 1. 制作数据集 ======================== #
# 生成3个输入特征(均匀分布在[0,1)),共10000个样本
X1 = torch.rand(10000, 1) # 输入特征1
X2 = torch.rand(10000, 1) # 输入特征2
X3 = torch.rand(10000, 1) # 输入特征3# 计算3个输出特征(One-Hot编码,对应和的三个区间)
sum_xy = X1 + X2 + X3
Y1 = (sum_xy < 1).float() # 和 < 1 → 类别1
Y2 = ((sum_xy >= 1) & (sum_xy < 2)).float() # 1 ≤ 和 < 2 → 类别2(修正边界,避免遗漏)
Y3 = (sum_xy >= 2).float() # 和 ≥ 2 → 类别3# 拼接输入和输出特征,形成完整数据集(共6列:3输入 + 3输出)
data = torch.cat([X1, X2, X3, Y1, Y2, Y3], dim=1)# 打乱数据顺序(避免训练时样本顺序影响)
shuffled_index = torch.randperm(data.shape[0]) # 生成随机索引
data = data[shuffled_index]# 划分训练集(70%)和测试集(30%)
train_size = int(len(data) * 0.7)
train_data = data[:train_size]
test_data = data[train_size:]# ======================== 2. 搭建神经网络 ======================== #
class DNN(nn.Module):def __init__(self):super(DNN, self).__init__()# 构建网络:3层隐藏层,每层5个神经元,ReLU激活self.net = nn.Sequential(nn.Linear(3, 5), # 输入层(3特征)→ 隐藏层1(5神经元)nn.ReLU(), # 激活函数:引入非线性,避免网络退化为线性模型nn.Linear(5, 5), # 隐藏层1 → 隐藏层2nn.ReLU(),nn.Linear(5, 5), # 隐藏层2 → 隐藏层3nn.ReLU(),nn.Linear(5, 3) # 隐藏层3 → 输出层(3类别,对应One-Hot编码))def forward(self, x):"""前向传播:输入数据→网络计算→输出预测值"""return self.net(x)# 初始化模型(自动运行在CPU,因未指定设备)
model = DNN()# ======================== 3. 定义训练参数 ======================== #
epochs = 1000 # 训练轮次:整个数据集遍历1000次
loss_fn = nn.MSELoss() # 损失函数:均方误差(适合One-Hot编码的分类任务)
optimizer = optim.SGD( # 优化器:随机梯度下降model.parameters(), # 优化对象:模型的权重和偏置lr=0.01 # 学习率:控制参数更新幅度(需根据任务调整)
)# 提取训练集的输入(前3列)和输出(后3列)
X_train = train_data[:, :3]
Y_train = train_data[:, 3:]# ======================== 4. 训练网络 ======================== #
loss_recorder = [] # 记录每轮损失,用于分析训练趋势
for epoch in range(epochs):# 1. 前向传播:用当前模型预测训练数据pred = model(X_train)# 2. 计算损失:预测值与真实值的差距loss = loss_fn(pred, Y_train)loss_recorder.append(loss.item()) # 保存损失值(转换为Python数值)# 3. 反向传播:计算梯度并更新参数optimizer.zero_grad() # 清空上一轮的梯度(否则会累积,导致错误)loss.backward() # 反向传播:自动计算参数的梯度optimizer.step() # 根据梯度更新参数(权重和偏置)# 可选:每100轮打印训练进度if (epoch + 1) % 100 == 0:print(f"训练轮次: {epoch+1:4d} / {epochs}, 损失值: {loss.item():.6f}")# ======================== 5. 测试网络 ======================== #
# 提取测试集的输入和输出
X_test = test_data[:, :3]
Y_test = test_data[:, 3:]with torch.no_grad(): # 测试阶段无需计算梯度,加速运算并节省内存# 前向传播预测pred_test = model(X_test)# 将预测值规整为One-Hot格式(与真实标签一致)# 步骤:找到每个样本预测值最大的索引,将该位置设为1,其余设为0max_index = torch.argmax(pred_test, dim=1) # 每个样本的最大概率类别索引pred_onehot = torch.zeros_like(pred_test) # 初始化全0的One-Hot张量# 根据索引设置1(dim=1表示按列操作,unsqueeze将索引转为列向量)pred_onehot.scatter_(1, max_index.unsqueeze(1), 1)# 计算准确率:预测与真实完全匹配的样本数 ÷ 总样本数correct_count = torch.sum(torch.all(pred_onehot == Y_test, dim=1)) # 逐样本判断是否全对total_count = Y_test.shape[0]accuracy = 100 * correct_count / total_countprint(f"\n测试集准确率: {accuracy:.2f} %")# ======================== 6. 保存模型 ======================== #
torch.save(model, "dnn_model.pth")
print("模型已保存为 'dnn_model.pth'(可后续加载复用)")
运行结果如下:
从最终结果上来看,损失从 0.226 逐步降至 0.166,符合梯度下降的收敛规律:
前期(1~300 轮):损失快速下降 → 模型快速学习输入(X1/X2/X3)和输出(区间分类)的关联;
后期(300~1000 轮):损失下降变缓 → 接近局部最优解
,参数调整空间缩小
可以理解为模型学不动了
。
最终损失仍较高,说明模型未完全拟合数据,
不过考虑到本次模型结构简单,我们只用了3 层隐藏层,每层仅 5 个神经元,再加上优化器(SGD)较基础,没有换更智能的优化器,所以这个结果也不意外。
4 批量梯度下降
上一章实现了基础DNN,但未明确梯度下降的批量处理方式。批量梯度下降(BGD)每次用全部样本更新参数,适用于小数据集。本章将完整演示其流程。
4.1 制作数据集
从Excel导入数据,转换为张量并划分训练集和测试集。
import pandas as pd# 导入数据并转换为张量
df = pd.read_csv('Data.csv', index_col=0)
arr = df.values.astype(np.float32) # 转为float32避免类型冲突
ts = torch.tensor(arr).to('cuda') # 迁移到GPU# 划分训练集(70%)和测试集(30%)
ts = ts[torch.randperm(ts.size(0)), :]
train_size = int(len(ts) * 0.7)
train_Data = ts[:train_size, :]
test_Data = ts[train_size:, :]
4.2 搭建神经网络
输入特征为8个,输出特征为1个,网络结构如下:
class DNN(nn.Module):def __init__(self):super(DNN, self).__init__()self.net = nn.Sequential(nn.Linear(8, 32), nn.Sigmoid(), # 8→32神经元,Sigmoid激活nn.Linear(32, 8), nn.Sigmoid(),nn.Linear(8, 4), nn.Sigmoid(),nn.Linear(4, 1), nn.Sigmoid() # 输出层1个神经元)def forward(self, x):return self.net(x)model = DNN().to('cuda:0')
这里的原理在上一节有做过图进行演示,道理是一样的。
self.net = nn.Sequential(nn.Linear(8, 32), nn.Sigmoid(), # 8→32神经元,Sigmoid激活nn.Linear(32, 8), nn.Sigmoid(),nn.Linear(8, 4), nn.Sigmoid(),nn.Linear(4, 1), nn.Sigmoid() # 输出层1个神经元
)
然后这里使用sigmoid激活则是为引入非线性,避免多层线性层退化为单层,让网络学习复杂特征,虽然存在梯度消失缺陷,但因本网络隐藏层少、任务简单,故仍采用;
4.3 训练网络
使用BGD(每次输入全部训练样本):
loss_fn = nn.BCELoss(reduction='mean') # 二分类损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.005) # Adam优化器
epochs = 5000
losses = []# 提取训练集输入和输出
X = train_Data[:, :-1]
Y = train_Data[:, -1].reshape((-1, 1)) # 调整输出形状for epoch in range(epochs):Pred = model(X)loss = loss_fn(Pred, Y)losses.append(loss.item())optimizer.zero_grad()loss.backward()optimizer.step()
4.4 测试网络
X = test_Data[:, :-1]
Y = test_Data[:, -1].reshape((-1, 1))with torch.no_grad():Pred = model(X)Pred[Pred >= 0.5] = 1 # 预测值≥0.5视为1,否则为0Pred[Pred < 0.5] = 0correct = torch.sum((Pred == Y).all(1))total = Y.size(0)print(f'测试集精准度: {100 * correct / total} %')
4.5 完整程序
注意:这里仍然是用CPU来运行的,想改成GPU的,ts = torch.tensor(arr)
这个程序指定好你得cuda即可,程序后面的也是如此。
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np # 用于数组类型转换# ======================== 1. 制作数据集 ======================== #
# 从xlsx读取数据
df = pd.read_excel(r'D:\ProjectPython\DNN_CNN\Data.xlsx', index_col=0,engine='openpyxl')
# 转换为float32(PyTorch默认浮点类型,避免类型冲突)
arr = df.values.astype(np.float32)
# 转换为PyTorch张量(自动运行在CPU,因未指定device)
ts = torch.tensor(arr) # 打乱数据顺序(避免训练时样本顺序影响模型学习)
shuffled_idx = torch.randperm(ts.size(0)) # 生成随机索引
ts = ts[shuffled_idx]# 划分训练集(70%)和测试集(30%)
train_size = int(len(ts) * 0.7)
train_Data = ts[:train_size] # 前70%为训练集
test_Data = ts[train_size:] # 后30%为测试集# ======================== 2. 搭建神经网络 ======================== #
class DNN(nn.Module):def __init__(self):super(DNN, self).__init__()# 构建4层全连接网络,Sigmoid作为激活函数self.net = nn.Sequential(# 输入层:8个特征 → 隐藏层1:32个神经元nn.Linear(8, 32), nn.Sigmoid(), # 隐藏层1 → 隐藏层2:8个神经元nn.Linear(32, 8), nn.Sigmoid(), # 隐藏层2 → 隐藏层3:4个神经元nn.Linear(8, 4), nn.Sigmoid(), # 隐藏层3 → 输出层:1个神经元(输出概率,范围0~1)nn.Linear(4, 1), nn.Sigmoid() )def forward(self, x):"""前向传播:输入数据通过网络计算,返回预测概率"""return self.net(x)# 初始化模型(自动运行在CPU)
model = DNN() # ======================== 3. 训练配置(超参数) ======================== #
loss_fn = nn.BCELoss(reduction='mean') # 二分类交叉熵损失(适配Sigmoid的概率输出)
optimizer = optim.Adam( # Adam优化器(收敛更快,适合演示)model.parameters(), # 优化对象:模型的权重和偏置lr=0.005 # 学习率:控制参数更新幅度
)
epochs = 5000 # 训练轮次:整个训练集遍历5000次
losses = [] # 记录每轮损失,用于分析训练趋势# ======================== 4. 训练网络 ======================== #
# 提取训练集的输入(前8列)和输出(最后1列,需调整形状为[batch, 1])
X_train = train_Data[:, :-1]
Y_train = train_Data[:, -1].reshape(-1, 1) # 确保输出是二维张量(匹配BCELoss输入要求)for epoch in range(epochs):# 1. 前向传播:用当前模型预测训练数据pred = model(X_train)# 2. 计算损失:预测概率与真实标签的差距loss = loss_fn(pred, Y_train)losses.append(loss.item()) # 保存损失值(转换为Python原生浮点数)# 3. 反向传播 + 参数更新optimizer.zero_grad() # 清空上一轮的梯度(否则会累积,导致错误)loss.backward() # 自动计算参数的梯度(反向传播)optimizer.step() # 根据梯度更新参数(权重和偏置)# 可选:每500轮打印训练进度(方便观察收敛情况)if (epoch + 1) % 500 == 0:print(f"训练轮次: {epoch+1:5d} / {epochs}, 当前损失: {loss.item():.6f}")# ======================== 5. 测试网络 ======================== #
# 提取测试集的输入和输出(同样调整输出形状)
X_test = test_Data[:, :-1]
Y_test = test_Data[:, -1].reshape(-1, 1)with torch.no_grad(): # 测试阶段无需计算梯度,提升效率并节省内存# 前向传播预测pred_test = model(X_test)# 将概率预测转换为0/1(≥0.5视为正类,<0.5视为负类)pred_test[pred_test >= 0.5] = 1.0pred_test[pred_test < 0.5] = 0.0# 计算准确率:预测与真实完全一致的样本数 ÷ 总样本数# torch.all(..., dim=1):逐行判断所有列是否都匹配(此处仅1列,即判断单个值是否相等)correct_count = torch.sum(torch.all(pred_test == Y_test, dim=1))total_count = Y_test.size(0)accuracy = 100 * correct_count / total_countprint(f"\n测试集准确率: {accuracy:.2f} %")
运行结果如下:
从输出的结果上来说,训练时损失从0.4203逐步降至0.0567,前期快速下降、后期收敛趋缓,体现模型有效学习数据关联但逼近优化瓶颈。后续可通过替换ReLU激活、采用AdamW + 学习率调度优化训练、分析数据平衡与特征质量等方式突破性能瓶颈。
5 小批量梯度下降
批量梯度下降在大数据集上效率低,小批量梯度下降(MBGD)按批次输入样本,平衡效率和稳定性。本章将使用PyTorch的DataSet
和DataLoader
实现MBGD。
5.1 制作数据集
继承Dataset
类封装数据,实现数据加载、索引获取和长度计算。
from torch.utils.data import Dataset, DataLoader, random_splitclass MyData(Dataset):def __init__(self, filepath):df = pd.read_csv(filepath, index_col=0)arr = df.values.astype(np.float32)ts = torch.tensor(arr).to('cuda')self.X = ts[:, :-1] # 输入特征self.Y = ts[:, -1].reshape((-1, 1)) # 输出特征self.len = ts.shape[0] # 样本总数def __getitem__(self, index):return self.X[index], self.Y[index] # 获取索引对应的样本def __len__(self):return self.len # 返回样本总数# 划分训练集和测试集
Data = MyData('Data.csv')
train_size = int(len(Data) * 0.7)
test_size = len(Data) - train_size
train_Data, test_Data = random_split(Data, [train_size, test_size])# 批次加载器(shuffle=True表示每个epoch前打乱数据)
train_loader = DataLoader(dataset=train_Data, shuffle=True, batch_size=128)
test_loader = DataLoader(dataset=test_Data, shuffle=False, batch_size=64)
5.2 搭建神经网络
与4.2节结构相同(输入8,输出1)。
5.3 训练网络
按批次输入样本进行训练:
loss_fn = nn.BCELoss(reduction='mean')
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)
epochs = 500
losses = []for epoch in range(epochs):for (x, y) in train_loader: # 按批次获取样本Pred = model(x)loss = loss_fn(Pred, y)losses.append(loss.item())optimizer.zero_grad()loss.backward()optimizer.step()
5.4 测试网络
按批次测试并计算总准确率:
correct = 0
total = 0
with torch.no_grad():for (x, y) in test_loader:Pred = model(x)Pred[Pred >= 0.5] = 1Pred[Pred < 0.5] = 0correct += torch.sum((Pred == y).all(1))total += y.size(0)
print(f'测试集精准度: {100 * correct / total} %')
5.5 完整程序
# 导入必要库
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader, random_split
import numpy as np# ======================== 1. 自定义数据集类(支持.xlsx) ======================== #
class MyData(Dataset):def __init__(self, filepath):"""初始化:读取Excel数据,转换为PyTorch张量(CPU):param filepath: 数据集文件路径(.xlsx格式)"""# 读取Excel(必须安装openpyxl!engine指定解析器)df = pd.read_excel( filepath, index_col=0, # 第1列作为索引engine='openpyxl' # 强制使用openpyxl解析.xlsx) arr = df.values.astype(np.float32) # 转换为float32(匹配PyTorch)ts = torch.tensor(arr) # 转为CPU张量(未指定device的情况下不使用GPU)self.X = ts[:, :-1] # 输入特征(前8列)self.Y = ts[:, -1].reshape(-1, 1) # 输出标签(最后1列,调整为列向量)self.len = len(ts) # 样本总数def __getitem__(self, index):"""获取指定索引的样本:(输入特征, 输出标签)"""return self.X[index], self.Y[index]def __len__(self):"""返回数据集总样本数"""return self.len# ======================== 2. 加载并划分数据集 ======================== #
data_path = "Data.xlsx" # 改为你的Excel文件路径(确保存在)
full_dataset = MyData(data_path) # 初始化自定义数据集# 划分训练集(70%)和测试集(30%)
train_size = int(0.7 * len(full_dataset))
test_size = len(full_dataset) - train_size
train_dataset, test_dataset = random_split(full_dataset, [train_size, test_size])# 创建数据加载器(小批量读取)
batch_size = 128 # 小批量大小(可调整,建议2的幂次)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True # 训练集每个epoch前打乱
)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False # 测试集保持顺序,方便复现
)# ======================== 3. 定义神经网络模型 ======================== #
class DNN(nn.Module):def __init__(self):super(DNN, self).__init__()# 4层全连接网络,Sigmoid激活(输出层适配二分类)self.net = nn.Sequential(nn.Linear(8, 32), nn.Sigmoid(), # 8→32nn.Linear(32, 8), nn.Sigmoid(), # 32→8nn.Linear(8, 4), nn.Sigmoid(), # 8→4nn.Linear(4, 1), nn.Sigmoid() # 4→1(输出0~1概率))def forward(self, x):"""前向传播:输入→网络→预测概率"""return self.net(x)model = DNN() # CPU运行# ======================== 4. 训练配置(损失函数 + 优化器) ======================== #
loss_fn = nn.BCELoss(reduction='mean') # 二分类交叉熵损失
optimizer = optim.Adam(model.parameters(), lr=0.005) # Adam优化器
epochs = 500 # 训练轮次
losses = [] # 记录batch级损失# ======================== 5. 小批量训练(MBGD) ======================== #
for epoch in range(epochs):for batch_idx, (x, y) in enumerate(train_loader):# 1. 前向传播pred = model(x)# 2. 计算损失loss = loss_fn(pred, y)losses.append(loss.item())# 3. 反向传播 + 更新参数optimizer.zero_grad() # 清空梯度loss.backward() # 计算梯度optimizer.step() # 更新参数# 每100轮打印进度if (epoch + 1) % 100 == 0:print(f"训练轮次: {epoch+1:4d} / {epochs}, 最近批次损失: {loss.item():.6f}")# ======================== 6. 测试网络 ======================== #
correct = 0
total = 0with torch.no_grad(): # 关闭梯度,加速测试for x, y in test_loader:pred = model(x)# 概率→0/1(阈值0.5)pred[pred >= 0.5] = 1.0pred[pred < 0.5] = 0.0# 统计正确数(逐样本判断)batch_correct = torch.sum(torch.all(pred == y, dim=1))correct += batch_correct.item()total += y.size(0)accuracy = 100 * correct / total
print(f"\n测试集准确率: {accuracy:.2f} %")
运行结果如下:
6 手写数字识别
前几章介绍了通用DNN的实现,本章以**MNIST数据集(手写数字图像)**为例,展示DNN在图像分类任务中的应用。
6.1 制作数据集
使用torchvision
下载MNIST数据集,转换为张量并标准化。
from torchvision import transforms, datasets# 数据转换:转为张量并标准化(均值0.1307,标准差0.3081)
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize(0.1307, 0.3081)
])# 下载训练集和测试集
train_Data = datasets.MNIST(root='D:/Jupyter/dataset/mnist/',train=True, download=True, transform=transform
)
test_Data = datasets.MNIST(root='D:/Jupyter/dataset/mnist/',train=False, download=True, transform=transform
)# 批次加载器
train_loader = DataLoader(train_Data, shuffle=True, batch_size=64)
test_loader = DataLoader(test_Data, shuffle=False, batch_size=64)
6.2 搭建神经网络
输入为28×28的图像(展平为784维),输出为10个数字(0-9):
class DNN(nn.Module):def __init__(self):super(DNN, self).__init__()self.net = nn.Sequential(nn.Flatten(), # 展平图像为784维向量nn.Linear(784, 512), nn.ReLU(),nn.Linear(512, 256), nn.ReLU(),nn.Linear(256, 128), nn.ReLU(),nn.Linear(128, 64), nn.ReLU(),nn.Linear(64, 10) # 输出10个类别)def forward(self, x):return self.net(x)model = DNN().to('cuda:0')
6.3 训练网络
使用交叉熵损失(自带softmax激活)和带动量的SGD优化器:
loss_fn = nn.CrossEntropyLoss() # 多分类损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.5) # 动量参数
epochs = 5
losses = []for epoch in range(epochs):for (x, y) in train_loader:x, y = x.to('cuda:0'), y.to('cuda:0') # 迁移到GPUPred = model(x)loss = loss_fn(Pred, y)losses.append(loss.item())optimizer.zero_grad()loss.backward()optimizer.step()
6.4 测试网络
correct = 0
total = 0
with torch.no_grad():for (x, y) in test_loader:x, y = x.to('cuda:0'), y.to('cuda:0')Pred = model(x)_, predicted = torch.max(Pred.data, dim=1) # 获取预测类别(最大值索引)correct += torch.sum(predicted == y)total += y.size(0)
print(f'测试集精准度: {100 * correct / total} %')
6.5 完整程序
# 导入核心库
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import transforms, datasets # 用于加载MNIST数据集
from torch.utils.data import DataLoader # 用于批量加载数据# ======================== 1. 数据集准备(MNIST) ======================== #
# 数据预处理:转为张量 + 标准化(均值和标准差是MNIST的统计特征,加速训练收敛)
transform = transforms.Compose([transforms.ToTensor(), # 将PIL图像转为张量(自动缩放到[0,1])transforms.Normalize( # 标准化:(x-mean)/std,减少数据分布差异对训练的影响mean=0.1307, # MNIST像素均值(官方推荐的参数)std=0.3081 # MNIST像素标准差(官方推荐的参数))
])# 下载并加载训练集(自动下载到root目录,train=True表示训练集)
train_dataset = datasets.MNIST(root='./data/mnist', # 数据保存路径(不存在则自动创建)train=True, # 加载训练集download=True, # 自动下载transform=transform # 应用预处理
)# 下载并加载测试集(train=False表示测试集)
test_dataset = datasets.MNIST(root='./data/mnist', train=False, download=True, transform=transform
)# 创建批量加载器(小批量梯度下降,提升效率)
batch_size = 64 # 每批64张图像
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True # 训练集打乱,增加随机性
)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False # 测试集保持顺序,方便结果复现
)# ======================== 2. 定义神经网络模型 ======================== #
class DNN(nn.Module):def __init__(self):super(DNN, self).__init__()self.net = nn.Sequential(nn.Flatten(), # 将28×28的图像展平为784维向量(28*28=784)# 全连接层 + ReLU激活(逐步压缩特征维度)nn.Linear(784, 512), nn.ReLU(), # 784→512nn.Linear(512, 256), nn.ReLU(), # 512→256nn.Linear(256, 128), nn.ReLU(), # 256→128nn.Linear(128, 64), nn.ReLU(), # 128→64nn.Linear(64, 10) # 64→10(输出10个数字的预测概率))def forward(self, x):"""前向传播:输入图像→网络→10类预测概率"""return self.net(x)# 初始化模型(默认运行在CPU,无需显式指定设备)
model = DNN() # ======================== 3. 训练配置(损失函数 + 优化器) ======================== #
loss_fn = nn.CrossEntropyLoss() # 多分类交叉熵损失(自带Softmax,输出层无需激活)
optimizer = optim.SGD( # 带动量的随机梯度下降(加速收敛)model.parameters(), # 优化对象:模型参数lr=0.01, # 学习率momentum=0.5 # 动量(利用历史梯度加速更新)
)
epochs = 5 # 训练轮次(遍历整个训练集5次)# ======================== 4. 训练网络 ======================== #
for epoch in range(epochs):# 遍历训练集的每个批次for batch_idx, (images, labels) in enumerate(train_loader):# 1. 前向传播:计算当前批次的预测概率outputs = model(images)# 2. 计算损失:预测与真实标签的差距loss = loss_fn(outputs, labels)# 3. 反向传播 + 参数更新optimizer.zero_grad() # 清空上一轮梯度loss.backward() # 反向传播计算梯度optimizer.step() # 更新模型参数# 每轮结束打印进度(观察损失变化,可选)print(f"训练轮次: {epoch+1}/{epochs}, 最近批次损失: {loss.item():.4f}")# ======================== 5. 测试网络 ======================== #
correct = 0 # 正确预测的样本数
total = 0 # 测试集总样本数with torch.no_grad(): # 测试阶段关闭梯度计算,提升效率for images, labels in test_loader:# 前向传播预测outputs = model(images)# 获取预测类别(取概率最大的索引,dim=1表示按行取最大值)_, predicted = torch.max(outputs.data, dim=1)# 统计正确数和总数correct += (predicted == labels).sum().item()total += labels.size(0)# 计算准确率
accuracy = 100 * correct / total
print(f"\n测试集准确率: {accuracy:.2f} %")
运行结果如下:
训练中损失从0.2480震荡降至0.0620,体现模型有效学习但受小批量随机性影响;测试集96.44%准确率达到DNN基线,证明能够很好地去捕捉数字特征却存在提升空间 。可通过延长训练轮次、换Adam优化器/加学习率调度加速收敛,或升级为CNN利用图像空间关联,突破性能瓶颈。不过这个准确率已经算是可以了。
7 小结
本文系统介绍了PyTorch实现深度神经网络(DNN)的全流程,核心内容包括:
- 张量操作:作为数据载体,需掌握与NumPy的转换、GPU迁移及函数差异。
- DNN原理:通过数据集划分、前向/反向传播、参数优化实现模型训练,理解batch_size和epochs的作用。
- 实现流程:从数据集制作、网络搭建(基于
nn.Module
)、训练(损失函数+优化器)到测试,完整覆盖模型生命周期。 - 梯度下降方法:对比批量(BGD)和小批量(MBGD)的适用场景,掌握
DataSet
和DataLoader
的使用。 - 实战案例:MNIST手写数字识别展示了DNN在图像分类中的应用,涉及数据预处理和多分类损失函数。
通过学习,可掌握DNN的核心概念和PyTorch实操技能,为更复杂的深度学习模型(如CNN、RNN)做好准备,另外就是文章内的程序都是用CPU跑的,也不需要跑很长时间就可以完成模型的训练并看到结果。