当前位置: 首页 > news >正文

【深度学习②】| 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的DataSetDataLoader实现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)的全流程,核心内容包括:

  1. 张量操作:作为数据载体,需掌握与NumPy的转换、GPU迁移及函数差异。
  2. DNN原理:通过数据集划分、前向/反向传播、参数优化实现模型训练,理解batch_size和epochs的作用。
  3. 实现流程:从数据集制作、网络搭建(基于nn.Module)、训练(损失函数+优化器)到测试,完整覆盖模型生命周期。
  4. 梯度下降方法:对比批量(BGD)和小批量(MBGD)的适用场景,掌握DataSetDataLoader的使用。
  5. 实战案例:MNIST手写数字识别展示了DNN在图像分类中的应用,涉及数据预处理和多分类损失函数。

通过学习,可掌握DNN的核心概念和PyTorch实操技能,为更复杂的深度学习模型(如CNN、RNN)做好准备,另外就是文章内的程序都是用CPU跑的,也不需要跑很长时间就可以完成模型的训练并看到结果。

http://www.lryc.cn/news/608275.html

相关文章:

  • 编译器与解释器:核心原理与工程实践
  • 基于Postman进行http的请求和响应
  • 操作系统:远程过程调用( Remote Procedure Call,RPC)
  • Jupyter notebook如何显示行号?
  • SQL Server从入门到项目实践(超值版)读书笔记 22
  • Spring事务失效场景
  • kotlin小记(1)
  • 集合框架(重点)
  • linux ext4缩容home,扩容根目录
  • 网络安全基础知识【6】
  • Ext系列文件系统
  • 【软考中级网络工程师】知识点之级联
  • 错误处理_IncompatibleKeys
  • 企业资产|企业资产管理系统|基于springboot企业资产管理系统设计与实现(源码+数据库+文档)
  • 【学习笔记】MySQL技术内幕InnoDB存储引擎——第6章 锁
  • 在linux(ubuntu)服务器上安装NTQQ并使用
  • junit中@InjectMocks作用详解
  • Redisson高并发实战:Netty IO线程免遭阻塞的守护指南
  • 零基础 “入坑” Java--- 十六、字符串String 异常
  • wxPython 实践(六)对话框
  • Java 大视界 -- Java 大数据在智能安防视频监控系统中的视频摘要生成与智能检索优化进阶(377)
  • ARMv8/v9架构FAR_EL3寄存器介绍
  • 图漾AGV行业常用相机使用文档
  • UE5 Insight ProfileCPU
  • MySQL 中 count(*)、count(1) 和 count(字段名) 有什么区别?
  • 【高等数学】第七章 微分方程——第七节 常系数齐次线性微分方程
  • Flutter开发 dart语言基本语法
  • [BJDCTF2020]EasySearch
  • 错误: 找不到或无法加载主类 原因: java.lang.ClassNotFoundException
  • 谷歌开源Agent框架ADK快速入门