冲冲冲😊
here😊
文章目录
- PyTorch多层感知机模型构建与MNIST分类训练笔记
-
- [🎯 1. 任务概述](#🎯 1. 任务概述)
- [⚙️ 2. 环境设置](#⚙️ 2. 环境设置)
-
- [2.1 导入必要库](#2.1 导入必要库)
- [2.2 GPU配置](#2.2 GPU配置)
- [🧠 3. 模型构建](#🧠 3. 模型构建)
-
- [3.1 模型定义关键点](#3.1 模型定义关键点)
- [3.2 损失函数选择](#3.2 损失函数选择)
- [3.3 模型初始化与设备选择](#3.3 模型初始化与设备选择)
- [🔧 4. 优化器配置](#🔧 4. 优化器配置)
-
- [4.1 随机梯度下降优化器](#4.1 随机梯度下降优化器)
- [🔄 5. 训练循环实现](#🔄 5. 训练循环实现)
-
- [5.1 训练函数设计](#5.1 训练函数设计)
- [5.2 测试函数设计](#5.2 测试函数设计)
- [📦 6. 数据准备](#📦 6. 数据准备)
-
- [6.1 加载MNIST数据集](#6.1 加载MNIST数据集)
- [🚀 7. 训练执行](#🚀 7. 训练执行)
-
- [7.1 训练循环主体](#7.1 训练循环主体)
- [7.2 训练过程输出(部分)](#7.2 训练过程输出(部分))
- [📊 8. 结果可视化](#📊 8. 结果可视化)
-
- [8.1 损失曲线绘制](#8.1 损失曲线绘制)
- [8.2 准确率曲线绘制](#8.2 准确率曲线绘制)
PyTorch多层感知机模型构建与MNIST分类训练笔记
🎯 1. 任务概述
解决MNIST手写数字分类问题,创建一个简单的多层感知机(MLP)模型
- 使用
torch.nn.Linear
层构建模型- 使用ReLU作为激活函数
- 包含两个全连接隐藏层(120和84个神经元)和输出层(10个神经元对应10个数字类别)
- 模型输入为展平后的28×28=784像素图像
⚙️ 2. 环境设置
2.1 导入必要库
python
import torch
from torch import nn
import os
2.2 GPU配置
python
# os.environ["CUDA_VISIBLE_DEVICES"] = "3,4,6" # 只使用空闲的GPU
🧠 3. 模型构建
3.1 模型定义关键点
python
class Model(nn.Module):
def __init__(self):
super().__init__()
# 第一层输入展平后的特征长度28乘28,创建120个神经元
self.liner_1 = nn.Linear(28*28, 120)
# 第二层输入的是前一层的输出,创建84个神经元
self.liner_2 = nn.Linear(120, 84)
# 输出层接受第二层的输入84,输出分类个数10
self.liner_3 = nn.Linear(84, 10)
def forward(self, input):
x = input.view(-1, 28*28) # 将输入展平为二维(1,28,28)->(28*28)
x = torch.relu(self.liner_1(x))
x = torch.relu(self.liner_2(x))
x = self.liner_3(x)
return x
📝 模型结构说明:
- 输入层:将28×28图像展平为784维向量
- 隐藏层1:120个神经元,使用ReLU激活
- 隐藏层2:84个神经元,使用ReLU激活
- 输出层:10个神经元对应10个数字类别
3.2 损失函数选择
python
loss_fn = nn.CrossEntropyLoss() # 交叉熵损失函数
'''
注意两个参数
1. weight: 各类别的权重(处理不平衡数据集)
2. ignore_index: 忽略特定类别的索引
另外,它要求实际类别为数值编码,而不是独热编码
'''
🔍 为什么选择交叉熵损失?
- 适用于多分类问题
- 内部集成了Softmax计算,简化实现流程
- 对错误分类有较强的惩罚
3.3 模型初始化与设备选择
python
device = "cuda" if torch.cuda.is_available() else "cpu"
model = Model().to(device)
# print(device) # 可选:打印使用的设备
💡 GPU加速提示 :
使用
.to(device)
将模型移动到GPU可显著加快训练速度,特别是对于大模型和大数据集
🔧 4. 优化器配置
4.1 随机梯度下降优化器
python
optimizer = torch.optim.SGD(model.parameters(), lr=0.005)
🔧 关键参数解析:
params
: 需要优化的模型参数(通常为model.parameters()
)lr=0.005
: 学习率,控制参数更新步长的超参数- 其他可选参数:
momentum
(动量),weight_decay
(L2正则化)
🔄 5. 训练循环实现
5.1 训练函数设计
python
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset) # 获取当前数据集样本总数量
num_batches = len(dataloader) # 获取当前data loader总批次数
# train_loss用于累计所有批次的损失之和, correct用于累计预测正确的样本总数
train_loss, correct = 0, 0
for X, y in dataloader:
X, y = X.to(device), y.to(device)
# 进行预测,并计算当前批次的损失
pred = model(X)
loss = loss_fn(pred, y)
# 利用反向传播算法,根据损失优化模型参数
optimizer.zero_grad() # 先将梯度清零
loss.backward() # 损失反向传播,计算模型参数梯度
optimizer.step() # 根据梯度优化参数
with torch.no_grad():
# correct用于累计预测正确的样本总数
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
# train_loss用于累计所有批次的损失之和
train_loss += loss.item()
# train_loss 是所有批次的损失之和,所以计算全部样本的平均损失时需要除以总的批次数
train_loss /= num_batches
# correct 是预测正确的样本总数,若计算整个epoch总体正确率,需要除以样本总数量
correct /= size
return train_loss, correct
5.2 测试函数设计
python
def test(dataloader, model):
size = len(dataloader.dataset)
num_batches = len(dataloader)
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 /= num_batches
correct /= size
return test_loss, correct
📊 数据加载器相关方法区别:
方法 返回内容 适用场景 len(dataset)
数据集总样本数(如100) 数据统计、划分 len(dataloader)
总批次数(如4) 训练循环控制 len(dataloader.dataset)
等同于 len(dataset)
需要访问原始数据时
📦 6. 数据准备
6.1 加载MNIST数据集
python
import torchvision
from torchvision.transforms import ToTensor
train_ds = torchvision.datasets.MNIST("data/", train=True, transform=ToTensor(), download=True)
test_ds = torchvision.datasets.MNIST("data/", train=False, transform=ToTensor(), download=True)
train_dl = torch.utils.data.DataLoader(train_ds, batch_size=64, shuffle=True)
test_dl = torch.utils.data.DataLoader(test_ds, batch_size=64)
🚀 7. 训练执行
7.1 训练循环主体
python
# 对全部的数据集训练50个epoch(一个epoch表示对全部数据训练一遍)
epochs = 50
train_loss, train_acc = [], []
test_loss, test_acc = [], []
for epoch in range(epochs):
# 调用train()函数训练
epoch_loss, epoch_acc = train(train_dl, model, loss_fn, optimizer)
# 调用test()函数测试
epoch_test_loss, epoch_test_acc = test(test_dl, model)
train_loss.append(epoch_loss)
train_acc.append(epoch_acc)
test_loss.append(epoch_test_loss)
test_acc.append(epoch_test_acc)
# 定义一个打印模板
template = ("epoch:{:2d},train_loss:{:.6f},train_acc:{:.1f}%,""test_loss:{:.5f},test_acc:{:.1f}%")
print(template.format(epoch, epoch_loss, epoch_acc*100, epoch_test_loss, epoch_test_acc*100))
print("Done")
7.2 训练过程输出(部分)
epoch: 0,train_loss:2.157364,train_acc:46.7%,test_loss:1.83506,test_acc:63.7%
epoch: 1,train_loss:1.222660,train_acc:74.3%,test_loss:0.74291,test_acc:81.8%
epoch: 2,train_loss:0.612381,train_acc:84.0%,test_loss:0.49773,test_acc:86.3%
...
epoch:48,train_loss:0.110716,train_acc:96.9%,test_loss:0.12003,test_acc:96.4%
epoch:49,train_loss:0.108877,train_acc:97.0%,test_loss:0.11783,test_acc:96.5%
Done
📈 训练趋势分析:
- 初始准确率:46.7%(训练集),63.7%(测试集)
- 最终准确率:97.0%(训练集),96.5%(测试集)
- 过拟合现象轻微:训练集和测试集性能差距仅0.5%
📊 8. 结果可视化
8.1 损失曲线绘制
python
import matplotlib.pyplot as plt
plt.plot(range(1, epochs+1), train_loss, label="train_loss")
plt.plot(range(1, epochs+1), test_loss, label="test_loss", ls="--")
plt.xlabel("epoch")
plt.legend()
plt.show()
注释:损失曲线显示训练初期损失快速下降,后期趋于平稳
8.2 准确率曲线绘制
python
plt.plot(range(1, epochs+1), train_acc, label="train_acc")
plt.plot(range(1, epochs+1), test_acc, label="test_acc")
plt.xlabel("epoch")
plt.legend()
plt.show()
注释:准确率曲线稳步上升,最终达到96.5%的测试准确率