如何使用PyTorch实现一个多层感知机模型?
一、多层感知机(MLP)核心原理
多层感知机是最基础的深度学习模型,本质是输入层+多个隐藏层+输出层 的全连接神经网络,核心特点:
- 隐藏层引入非线性激活函数(如ReLU),解决线性模型无法拟合复杂数据的问题;
- 全连接层(nn.Linear)是核心组件,每层神经元与上一层所有神经元相连;
- 适用于分类、回归等基础任务,是理解深度学习的入门关键。
二、完整实现代码(MNIST分类任务)
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
# ===================== 1. 环境配置与参数设置 =====================
# 固定随机种子,保证结果可复现
torch.manual_seed(42)
# 设备选择:优先GPU,无则用CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")
# 超参数设置
BATCH_SIZE = 64 # 批次大小
EPOCHS = 10 # 训练轮数
LEARNING_RATE = 0.001 # 学习率
# ===================== 2. 数据加载与预处理 =====================
# 数据预处理:转为张量 + 归一化(MNIST像素值0-255,归一化到0-1)
transform = transforms.Compose([
transforms.ToTensor(), # 转为张量,shape从(28,28)→(1,28,28)
transforms.Normalize((0.1307,), (0.3081,)) # MNIST官方推荐的均值/标准差
])
# 加载MNIST数据集(自动下载)
train_dataset = datasets.MNIST(
root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.MNIST(
root='./data', train=False, download=True, transform=transform
)
# 数据加载器(批量加载+打乱+多线程)
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)
# ===================== 3. 定义多层感知机模型 =====================
class MLP(nn.Module):
def __init__(self, input_size=784, hidden_size1=128, hidden_size2=64, output_size=10):
super(MLP, self).__init__()
# 定义网络层:输入层→隐藏层1→隐藏层2→输出层
self.fc1 = nn.Linear(input_size, hidden_size1) # 输入层:28*28=784个特征→128个神经元
self.fc2 = nn.Linear(hidden_size1, hidden_size2) # 隐藏层1→隐藏层2
self.fc3 = nn.Linear(hidden_size2, output_size) # 隐藏层2→输出层(10个数字类别)
# 激活函数:ReLU(解决梯度消失,比Sigmoid更优)
self.relu = nn.ReLU()
# Dropout层:随机丢弃部分神经元,防止过拟合(可选但推荐)
self.dropout = nn.Dropout(0.2)
def forward(self, x):
# 前向传播:定义数据流动路径
# 展平输入:(batch_size, 1, 28, 28) → (batch_size, 784)
x = x.view(x.size(0), -1)
# 输入层→隐藏层1:线性变换 + 激活 + Dropout
x = self.fc1(x)
x = self.relu(x)
x = self.dropout(x)
# 隐藏层1→隐藏层2:线性变换 + 激活 + Dropout
x = self.fc2(x)
x = self.relu(x)
x = self.dropout(x)
# 隐藏层2→输出层:仅线性变换(损失函数会集成Softmax)
x = self.fc3(x)
return x
# 实例化模型并移到指定设备
model = MLP().to(device)
# 打印模型结构,直观查看网络层级
print("\nMLP模型结构:")
print(model)
# ===================== 4. 定义损失函数与优化器 =====================
# 损失函数:交叉熵损失(适用于分类任务,自动集成Softmax)
criterion = nn.CrossEntropyLoss()
# 优化器:Adam(自适应学习率,收敛速度快于SGD)
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
# ===================== 5. 训练模型 =====================
# 记录训练过程的指标
train_loss_history = []
train_acc_history = []
test_acc_history = []
def train_one_epoch(model, loader, criterion, optimizer, epoch):
"""训练一个轮次的函数"""
model.train() # 切换到训练模式(启用Dropout/BatchNorm)
total_loss = 0.0
correct = 0
total = 0
for batch_idx, (images, labels) in enumerate(loader):
# 将数据移到GPU/CPU
images, labels = images.to(device), labels.to(device)
# 1. 前向传播
outputs = model(images)
# 2. 计算损失
loss = criterion(outputs, labels)
# 3. 反向传播(梯度清零→求梯度→更新参数)
optimizer.zero_grad() # 清空梯度,避免累加
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新模型参数
# 统计损失和准确率
total_loss += loss.item()
_, predicted = torch.max(outputs.data, 1) # 获取预测类别(概率最大的索引)
total += labels.size(0)
correct += (predicted == labels).sum().item()
# 每100批次打印一次进度
if batch_idx % 100 == 0:
print(f"Epoch [{epoch+1}/{EPOCHS}], Batch [{batch_idx}], Loss: {loss.item():.4f}")
# 计算本轮平均损失和准确率
avg_loss = total_loss / len(loader)
avg_acc = 100 * correct / total
train_loss_history.append(avg_loss)
train_acc_history.append(avg_acc)
return avg_loss, avg_acc
# ===================== 6. 测试模型 =====================
def test(model, loader):
"""测试模型准确率的函数"""
model.eval() # 切换到评估模式(禁用Dropout/BatchNorm)
correct = 0
total = 0
with torch.no_grad(): # 禁用梯度计算,节省内存
for images, labels in loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
avg_acc = 100 * correct / total
test_acc_history.append(avg_acc)
return avg_acc
# ===================== 7. 执行训练与测试 =====================
print("\n开始训练...")
for epoch in range(EPOCHS):
# 训练
train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, epoch)
# 测试
test_acc = test(model, test_loader)
# 打印本轮结果
print(f"Epoch [{epoch+1}/{EPOCHS}] Summary:")
print(f"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%")
print(f"Test Acc: {test_acc:.2f}%\n")
# ===================== 8. 结果可视化 =====================
plt.figure(figsize=(12, 4))
# 子图1:训练损失变化
plt.subplot(1, 2, 1)
plt.plot(train_loss_history, label="Train Loss", color="red")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.title("Training Loss Trend")
plt.legend()
plt.grid(True)
# 子图2:训练/测试准确率变化
plt.subplot(1, 2, 2)
plt.plot(train_acc_history, label="Train Acc", color="blue")
plt.plot(test_acc_history, label="Test Acc", color="green")
plt.xlabel("Epoch")
plt.ylabel("Accuracy (%)")
plt.title("Training/Test Accuracy Trend")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
# ===================== 9. 保存模型 =====================
torch.save(model.state_dict(), "mlp_mnist.pth")
print("模型已保存为 mlp_mnist.pth")
三、核心代码解释
1. 模型定义(最关键部分)
class MLP(nn.Module):
def __init__(self, input_size=784, hidden_size1=128, hidden_size2=64, output_size=10):
super(MLP, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size1) # 输入层→隐藏层1
self.fc2 = nn.Linear(hidden_size1, hidden_size2) # 隐藏层1→隐藏层2
self.fc3 = nn.Linear(hidden_size2, output_size) # 隐藏层2→输出层
self.relu = nn.ReLU() # 非线性激活
self.dropout = nn.Dropout(0.2) # 防止过拟合
def forward(self, x):
x = x.view(x.size(0), -1) # 展平:(64,1,28,28)→(64,784)
x = self.relu(self.fc1(x)) # 线性+激活
x = self.dropout(x) # Dropout
x = self.relu(self.fc2(x)) # 线性+激活
x = self.dropout(x)
x = self.fc3(x) # 输出层(无激活,交给损失函数)
return x
nn.Linear(in_features, out_features):全连接层,参数是“输入特征数”和“输出特征数”;x.view(x.size(0), -1):将28×28的图像展平为784维向量,适配全连接层输入;ReLU:非线性激活函数,让模型能拟合复杂模式(如果不用激活,多层等价于单层线性模型);Dropout(0.2):随机丢弃20%的神经元,避免过拟合(训练时生效,测试时禁用)。
2. 训练流程
MLP的训练流程是PyTorch所有模型的通用范式:
前向传播(模型预测)→ 计算损失 → 梯度清零 → 反向传播(求梯度)→ 优化器更新参数
optimizer.zero_grad():必须先清零梯度,否则梯度会累加,导致参数更新错误;loss.backward():自动计算所有可训练参数的梯度;optimizer.step():用梯度更新权重和偏置。
3. 评估模式
model.eval():切换到评估模式,禁用Dropout/BatchNorm等训练特有的层;with torch.no_grad():禁用梯度计算,减少内存占用,加速推理。
四、运行结果说明
- 训练过程中会打印每批次的损失,每轮结束后输出训练/测试准确率;
- 最终测试准确率通常能达到97%以上(调整超参数可更高);
- 可视化图表会展示“损失下降趋势”和“准确率上升趋势”,直观验证模型收敛;
- 模型会保存为
mlp_mnist.pth,后续可通过model.load_state_dict(torch.load("mlp_mnist.pth"))加载使用。
浙公网安备 33010602011771号