如何使用PyTorch实现一个多层感知机模型?

一、多层感知机(MLP)核心原理

多层感知机是最基础的深度学习模型,本质是输入层+多个隐藏层+输出层 的全连接神经网络,核心特点:

  1. 隐藏层引入非线性激活函数(如ReLU),解决线性模型无法拟合复杂数据的问题;
  2. 全连接层(nn.Linear)是核心组件,每层神经元与上一层所有神经元相连;
  3. 适用于分类、回归等基础任务,是理解深度学习的入门关键。

二、完整实现代码(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():禁用梯度计算,减少内存占用,加速推理。

四、运行结果说明

  1. 训练过程中会打印每批次的损失,每轮结束后输出训练/测试准确率;
  2. 最终测试准确率通常能达到97%以上(调整超参数可更高);
  3. 可视化图表会展示“损失下降趋势”和“准确率上升趋势”,直观验证模型收敛;
  4. 模型会保存为mlp_mnist.pth,后续可通过model.load_state_dict(torch.load("mlp_mnist.pth"))加载使用。
posted @ 2026-01-06 22:25  高速de蜗牛  阅读(110)  评论(0)    收藏  举报