PyTorch 框架实现多层感知机(MLP):手写数字分类全流程详解 ...

打印 上一主题 下一主题

主题 973|帖子 973|积分 2919

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
系列文章目录

Pytorch底子篇
01-PyTorch新手必看:张量是什么?5 分钟教你快速创建张量!
02-张量运算真简朴!PyTorch 数值盘算操纵完全指南
03-Numpy 还是 PyTorch?张量与 Numpy 的神奇转换技巧
04-揭秘数据处理神器:PyTorch 张量拼接与拆分实用技巧
05-深度学习从索引开始:PyTorch 张量索引与切片最全分析
06-张量外形任意改!PyTorch reshape、transpose 操纵超具体教程
07-深入解读 PyTorch 张量运算:6 大核心函数全面分析,代码示例一步到位!
08-主动微分到底有多强?PyTorch 主动求导机制深度分析
Pytorch实战篇
09-从零手写线性回归模子:PyTorch 实现深度学习入门教程
10-PyTorch 框架实现线性回归:从数据预处理到模子练习全流程
11-PyTorch 框架实现逻辑回归:从数据预处理到模子练习全流程
12-PyTorch 框架实现多层感知机(MLP):手写数字分类全流程详解


  

前言

在人工智能和深度学习领域,PyTorch 以其灵活性和简便性,迅速成为研究职员和开发者们的首选工具。然而,对于许多初学者而言,深度学习的入门看似门槛颇高:神经网络的结构、数学公式的明白、代码实现的细节……每一环都可能让人望而却步。
本文以经典的 MNIST 手写数字分类标题 为例,通过 从零开始的方式,带领你逐步实现一个多层感知机(MLP),从最底子的全连接网络开始,逐渐引入激活函数和多层结构,最终完成一个完备的练习与测试流程。本文不光提供具体的代码示例,更注意背后的原理分析与实践引导,力图让你在最短的时间内把握 PyTorch 的核心用法。
无论你是深度学习的初学者,还是希望深入了解 PyTorch 实战的开发者,这篇文章都将是你学习的绝佳出发点。
一、简朴全连接神经网络的搭建

1.1 什么是全连接神经网络?

全连接神经网络(Fully Connected Neural Network, FCNN)是深度学习中最底子的网络结构之一。它的特点是:网络中的每一层神经元与下一层的每个神经元都存在连接。全连接网络的核心是矩阵运算,可以表现为以下公式:
                                                                      y                            =                            W                            x                            +                            b                                  \ y = Wx + b                      y=Wx+b
此中:


  • ( W ) 是权重矩阵;
  • ( b ) 是偏置;
  • ( x ) 是输入;
  • ( y ) 是输出。
简朴的全连接网络通常由输入层、一个或多个隐藏层和输出层组成,每一层通过线性变换对输入数据进行处理。以下是全连接网络的基本结构表示图:
  1. 输入层 -> 隐藏层 -> 输出层
复制代码
1.2 使用 PyTorch 搭建简朴网络

在 PyTorch 中,神经网络的构建通常通过继承 torch.nn.Module 来完成。我们可以定义网络的结构并实现前向传播。
1.2.1 示例代码:构建两层全连接网络

以下是一个使用 PyTorch 构建两层全连接网络的简朴示例:
  1. import torch
  2. import torch.nn as nn
  3. # 定义简单的全连接神经网络
  4. class SimpleNN(nn.Module):
  5.     def __init__(self, input_size, hidden_size, output_size):
  6.         super(SimpleNN, self).__init__()
  7.         # 定义两层全连接层
  8.         self.fc1 = nn.Linear(input_size, hidden_size)  # 第一层全连接
  9.         self.fc2 = nn.Linear(hidden_size, output_size)  # 第二层全连接
  10.     def forward(self, x):
  11.         # 定义前向传播
  12.         x = self.fc1(x)  # 输入通过第一层
  13.         x = self.fc2(x)  # 输入通过第二层
  14.         return x
  15. # 定义模型参数
  16. input_size = 10  # 输入特征数
  17. hidden_size = 5  # 隐藏层神经元个数
  18. output_size = 2  # 输出类别数
  19. # 初始化模型
  20. model = SimpleNN(input_size, hidden_size, output_size)
  21. # 打印模型结构
  22. print(model)
复制代码
1.2.2 输出结果

运行以上代码后,你将看到模子的结构:
  1. SimpleNN(
  2.   (fc1): Linear(in_features=10, out_features=5, bias=True)
  3.   (fc2): Linear(in_features=5, out_features=2, bias=True)
  4. )
复制代码
1.2.3 代码分析


  • nn.Linear:PyTorch 提供的全连接层类,用于实现输入到输出的线性变换。
  • forward 方法:定义了数据的前向传播路径,规定输入数据如何流经网络层。

二、激活函数的引入

2.1 为什么须要激活函数?

全连接网络中的线性层只能实现输入和输出之间的线性变换。为了让网络具备非线性建模能力,我们须要引入激活函数。
激活函数的主要作用是:


  • 引入非线性,使网络能够学习复杂的模式;
  • 控制神经元的输出范围,避免梯度爆炸或梯度消失。
常见的激活函数包括:


  • ReLU (Rectified Linear Unit)

    • 定义:                                                                                             f                                     (                                     x                                     )                                     =                                     max                                     ⁡                                     (                                     0                                     ,                                     x                                     )                                              \ f(x) = \max(0, x)                               f(x)=max(0,x)
    • 特点:简朴高效,对正值保持线性,对负值置零。

  • Sigmoid

    • 定义:                                                                                             f                                     (                                     x                                     )                                     =                                                   1                                                       1                                           +                                                           e                                                               −                                                 x                                                                                                        \ f(x) = \frac{1}{1 + e^{-x}}                               f(x)=1+e−x1​
    • 特点:输出范围为 (0, 1),常用于二分类任务。

  • Tanh (双曲正切函数)

    • 定义:                                                                                             f                                     (                                     x                                     )                                     =                                                                                  e                                              x                                                          −                                                           e                                                               −                                                 x                                                                                                                    e                                              x                                                          +                                                           e                                                               −                                                 x                                                                                                        \ f(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}                               f(x)=ex+e−xex−e−x​
    • 特点:输出范围为 (-1, 1),常用于归一化数据的建模。

2.2 在网络中引入激活函数

在 PyTorch 中,激活函数可以通过 torch.nn 模块中的函数类实现,也可以直接使用 torch 提供的操纵符。
2.2.1 示例代码:为网络添加 ReLU 激活函数

以下是为之前的两层全连接网络添加 ReLU 激活函数的代码:
  1. class SimpleNNWithActivation(nn.Module):
  2.     def __init__(self, input_size, hidden_size, output_size):
  3.         super(SimpleNNWithActivation, self).__init__()
  4.         # 定义两层全连接层
  5.         self.fc1 = nn.Linear(input_size, hidden_size)
  6.         self.relu = nn.ReLU()  # 添加 ReLU 激活函数
  7.         self.fc2 = nn.Linear(hidden_size, output_size)
  8.     def forward(self, x):
  9.         x = self.fc1(x)  # 输入通过第一层
  10.         x = self.relu(x)  # 应用 ReLU 激活函数
  11.         x = self.fc2(x)  # 输入通过第二层
  12.         return x
  13. # 初始化网络
  14. model_with_activation = SimpleNNWithActivation(input_size, hidden_size, output_size)
  15. # 打印模型结构
  16. print(model_with_activation)
复制代码
2.2.2 输出结果

运行后打印的模子结构如下:
  1. SimpleNNWithActivation(
  2.   (fc1): Linear(in_features=10, out_features=5, bias=True)
  3.   (relu): ReLU()
  4.   (fc2): Linear(in_features=5, out_features=2, bias=True)
  5. )
复制代码
2.2.3 代码分析


  • 在 __init__ 方法中,定义了 ReLU 激活函数对象(self.relu = nn.ReLU())。
  • 在 forward 方法中,激活函数被插入到第一层和第二层之间,用于对第一层的输出进行非线性变换。

2.3 差别激活函数的对比

以下是三种常见激活函数的对比表:
激活函数数学公式输出范围优势缺点ReLU                                                                                             f                                     (                                     x                                     )                                     =                                     max                                     ⁡                                     (                                     0                                     ,                                     x                                     )                                              \ f(x) = \max(0, x)                               f(x)=max(0,x)[0, ∞)盘算简朴,收敛速率快对负值不敏感,可能导致神经元失活Sigmoid                                                                                             f                                     (                                     x                                     )                                     =                                                   1                                                       1                                           +                                                           e                                                               −                                                 x                                                                                                        \ f(x) = \frac{1}{1 + e^{-x}}                               f(x)=1+e−x1​(0, 1)输出平滑,适合概率猜测梯度消失,盘算开销较大Tanh                                                                                             f                                     (                                     x                                     )                                     =                                                                                  e                                              x                                                          −                                                           e                                                               −                                                 x                                                                                                                    e                                              x                                                          +                                                           e                                                               −                                                 x                                                                                                        \ f(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}                               f(x)=ex+e−xex−e−x​(-1, 1)输出对称,适合归一化数据梯度消失,盘算开销较大 通过这些对比,ReLU 是目前最常用的激活函数,尤其是在深层神经网络中。

2.4 激活函数的注意事项


  • 避免梯度消失标题:在使用 Sigmoid 和 Tanh 时,当输入过大或过小时,梯度可能趋近于 0。
  • ReLU 的“神经元失活”标题:如果输入总是负值,某些神经元可能永远无法被激活。
  • 选择激活函数的依据

    • ReLU 常用于隐藏层;
    • Sigmoid 常用于输出层的二分类任务;
    • Tanh 在数据归一化的场景下表现更好。

三、多层感知机(MLP)的结构与实现

3.1 什么是多层感知机?

多层感知机(Multilayer Perceptron, MLP)是深度学习中一种重要的神经网络结构。它由多层全连接层组成,并通过激活函数引入非线性,能够学习并拟合复杂的非线性关系。
3.1.1 MLP 的基本特点


  • 多层结构:包括输入层、一个或多个隐藏层和输出层。
  • 激活函数:隐藏层通常使用非线性激活函数(如 ReLU)。
  • 全连接层:每个神经元与下一层的所有神经元相连。
  • 前向传播和反向传播:前向传播盘算输出,反向传播通过梯度下降优化权重。
MLP 的基本结构如下:
  1. 输入层 -> 隐藏层1 -> 激活函数 -> 隐藏层2 -> 激活函数 -> 输出层
复制代码
相较于简朴的单层全连接网络,MLP 通过多个隐藏层和非线性激活函数,能够表现更复杂的函数关系,是神经网络的底子模子。

3.2 使用 PyTorch 实现多层感知机

以下是实现一个具有两层隐藏层的 MLP 的代码示例。
3.2.1 示例代码:构建多层感知机

  1. import torch
  2. import torch.nn as nn
  3. # 定义多层感知机
  4. class MLP(nn.Module):
  5.     def __init__(self, input_size, hidden_sizes, output_size):
  6.         super(MLP, self).__init__()
  7.         # 定义第一层全连接层
  8.         self.fc1 = nn.Linear(input_size, hidden_sizes[0])  
  9.         # 定义第二层全连接层
  10.         self.fc2 = nn.Linear(hidden_sizes[0], hidden_sizes[1])  
  11.         # 定义输出层
  12.         self.fc3 = nn.Linear(hidden_sizes[1], output_size)  
  13.         # 定义 ReLU 激活函数
  14.         self.relu = nn.ReLU()
  15.     def forward(self, x):
  16.         # 数据通过第一层
  17.         x = self.fc1(x)
  18.         x = self.relu(x)  # 应用 ReLU 激活
  19.         # 数据通过第二层
  20.         x = self.fc2(x)
  21.         x = self.relu(x)  # 再次应用 ReLU 激活
  22.         # 数据通过输出层
  23.         x = self.fc3(x)
  24.         return x
  25. # 定义模型参数
  26. input_size = 784  # 输入层节点数(如28x28图像展开为784维向量)
  27. hidden_sizes = [128, 64]  # 两个隐藏层,节点数分别为128和64
  28. output_size = 10  # 输出层节点数(如10个分类)
  29. # 初始化模型
  30. mlp_model = MLP(input_size, hidden_sizes, output_size)
  31. # 打印模型结构
  32. print(mlp_model)
复制代码
3.2.2 输出结果

运行以上代码后,会打印出模子结构:
  1. MLP(
  2.   (fc1): Linear(in_features=784, out_features=128, bias=True)
  3.   (fc2): Linear(in_features=128, out_features=64, bias=True)
  4.   (fc3): Linear(in_features=64, out_features=10, bias=True)
  5.   (relu): ReLU()
  6. )
复制代码
3.2.3 代码分析


  • 层的定义

    • 第一层全连接层:self.fc1,输入维度为 784,输出维度为 128;
    • 第二层全连接层:self.fc2,输入维度为 128,输出维度为 64;
    • 输出层:self.fc3,输入维度为 64,输出维度为 10。

  • 激活函数

    • 在每一层全连接层后使用 ReLU 激活函数,增长非线性建模能力。

  • 前向传播

    • forward 方法定义了输入数据流经网络的路径。


3.3 数据输入外形

多层感知机通常须要将输入数据展平为一维向量(即 flatten 操纵),以符合全连接层的输入要求。比方,对于 MNIST 数据集,输入是 28x28 的灰度图像,须要将其展平成大小为 ( 28 \times 28 = 784 ) 的向量。
  1. # 示例:将 28x28 图像展平成一维向量
  2. images = torch.randn(64, 1, 28, 28)  # 假设 64 个批量的 MNIST 数据
  3. flattened_images = images.view(images.size(0), -1)  # 展平成 (64, 784)
  4. print(flattened_images.shape)  # 输出形状:(64, 784)
复制代码

3.4 模子练习的须要组件

为了练习多层感知机,须要以下组件:

  • 损失函数:盘算猜测结果与真实值之间的误差。
  • 优化器:调解模子参数以最小化损失函数。
  • 数据加载器:批量加载数据,支持随机抽样和迭代。
3.4.1 示例代码:定义损失函数和优化器

  1. import torch.optim as optim
  2. # 定义交叉熵损失函数(适合分类任务)
  3. criterion = nn.CrossEntropyLoss()
  4. # 定义随机梯度下降优化器
  5. optimizer = optim.SGD(mlp_model.parameters(), lr=0.01)
  6. print("Loss function and optimizer defined!")
复制代码

3.5 注意事项


  • 隐藏层的数量和节点数

    • 隐藏层越多,网络的表达能力越强,但盘算复杂度也会增长;
    • 节点数的选择须要平衡模子性能和盘算开销,可根据经验或通过交叉验证调解。

  • 过拟合标题

    • 多层感知机可能轻易过拟合,特殊是在隐藏层过多或节点数过大时;
    • 可以引入正则化技能(如 Dropout)来缓解过拟合。

  • 初始化权重

    • 默认的权重初始化方式通常表现良好,但在某些复杂任务中,可以思量使用自定义初始化方法。


3.6 多层感知机的优化方向

为了进一步优化多层感知机的性能,可以尝试以下方法:

  • 更复杂的激活函数

    • 除了 ReLU,还可以尝试 Leaky ReLU、ELU 或 GELU 等更高级的激活函数。

  • 正则化技能

    • 添加 Dropout 层随机丢弃部分神经元,防止过拟合;
    • 在损失函数中加入 L2 正则化项(权重惩罚)。

  • 使用 GPU

    • 通过将模子和数据加载到 GPU 上,明显加速练习过程:
      1. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
      2. mlp_model.to(device)
      复制代码


四、现实案例:手写数字分类

在本节中,将联合经典的 MNIST 数据集,使用多层感知机(MLP)完成手写数字分类任务。MNIST 数据集包含 28x28 的灰度图像,共 10 个类别(数字 0 到 9)。

4.1 数据加载与预处理

4.1.1 下载 MNIST 数据集

使用 torchvision 提供的工具,可以轻松下载并加载 MNIST 数据集。
  1. from torchvision import datasets, transforms
  2. from torch.utils.data import DataLoader
  3. # 数据预处理
  4. transform = transforms.Compose([
  5.     transforms.ToTensor(),  # 转换为 Tensor
  6.     transforms.Normalize((0.5,), (0.5,))  # 归一化到 [-1, 1]
  7. ])
  8. # 加载 MNIST 数据集
  9. train_dataset = datasets.MNIST(
  10.     root='./data', train=True, transform=transform, download=True
  11. )
  12. test_dataset = datasets.MNIST(
  13.     root='./data', train=False, transform=transform, download=True
  14. )
  15. # 定义数据加载器
  16. train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
  17. test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
  18. print("MNIST 数据集加载完成!")
复制代码
4.1.2 数据处理说明


  • ToTensor():将 PIL 图像转换为 PyTorch 张量,并将像素值从 [0, 255] 缩放到 [0, 1]。
  • Normalize((0.5,), (0.5,)):将数据标准化到 [-1, 1] 区间,加速练习收敛。
  • 数据加载器:DataLoader 支持批量加载数据,并通过 shuffle 打乱练习集,增长模子的泛化能力。

4.2 定义模子结构

我们使用一个多层感知机模子,包含两层隐藏层和 ReLU 激活函数。每层的节点数根据 MNIST 数据特性选择。
4.2.1 模子代码

  1. import torch.nn as nn
  2. class MLP(nn.Module):
  3.     def __init__(self, input_size, hidden_sizes, output_size):
  4.         super(MLP, self).__init__()
  5.         self.fc1 = nn.Linear(input_size, hidden_sizes[0])  # 第一层全连接
  6.         self.fc2 = nn.Linear(hidden_sizes[0], hidden_sizes[1])  # 第二层全连接
  7.         self.fc3 = nn.Linear(hidden_sizes[1], output_size)  # 输出层
  8.         self.relu = nn.ReLU()  # 激活函数
  9.     def forward(self, x):
  10.         x = self.fc1(x)
  11.         x = self.relu(x)
  12.         x = self.fc2(x)
  13.         x = self.relu(x)
  14.         x = self.fc3(x)
  15.         return x
  16. # 定义模型参数
  17. input_size = 28 * 28  # MNIST 图像展平成一维向量,28x28 = 784
  18. hidden_sizes = [128, 64]  # 两个隐藏层,节点数分别为 128 和 64
  19. output_size = 10  # 输出层(10 个分类)
  20. # 初始化模型
  21. model = MLP(input_size, hidden_sizes, output_size)
  22. print(model)
复制代码
4.2.2 模子结构解释



  • 输入层为 784(28x28 睁开后),输出层为 10(对应 10 个数字类别)。
  • 两个隐藏层分别包含 128 和 64 个神经元,通过 ReLU 激活函数引入非线性。
  • 模子的参数(权重和偏置)会在练习中主动更新。

4.3 定义练习过程

模子练习包括前向传播、损失盘算、反向传播和优化步调。
4.3.1 练习函数

  1. import torch
  2. import torch.optim as optim
  3. # 定义损失函数和优化器
  4. criterion = nn.CrossEntropyLoss()  # 适合分类任务的交叉熵损失
  5. optimizer = optim.SGD(model.parameters(), lr=0.01)  # 随机梯度下降优化器
  6. # 训练函数
  7. def train(model, train_loader, criterion, optimizer, epochs=5):
  8.     for epoch in range(epochs):
  9.         model.train()  # 切换到训练模式
  10.         running_loss = 0.0
  11.         for images, labels in train_loader:
  12.             # 展平图像为向量
  13.             images = images.view(images.size(0), -1)
  14.             # 清零梯度
  15.             optimizer.zero_grad()
  16.             # 前向传播
  17.             outputs = model(images)
  18.             loss = criterion(outputs, labels)
  19.             # 反向传播和优化
  20.             loss.backward()
  21.             optimizer.step()
  22.             running_loss += loss.item()
  23.         print(f"Epoch [{epoch+1}/{epochs}], Loss: {running_loss/len(train_loader):.4f}")
复制代码
4.3.2 代码说明


  • 损失函数:CrossEntropyLoss 盘算猜测值与真实标签之间的误差。
  • 优化器:SGD(随机梯度下降)更新模子参数。
  • 练习流程

    • 数据通过模子的前向传播盘算输出;
    • 通过损失函数盘算误差;
    • 反向传播盘算梯度;
    • 使用优化器更新权重。


4.4 模子测试与评估

练习完成后,我们须要在测试集上评估模子的性能,盘算分类准确率。
4.4.1 测试函数

  1. def test(model, test_loader):
  2.     model.eval()  # 切换到评估模式
  3.     correct = 0
  4.     total = 0
  5.     with torch.no_grad():  # 测试时不需要计算梯度
  6.         for images, labels in test_loader:
  7.             # 展平图像为向量
  8.             images = images.view(images.size(0), -1)
  9.             # 前向传播
  10.             outputs = model(images)
  11.             _, predicted = torch.max(outputs, 1)  # 获取最大值对应的类别
  12.             total += labels.size(0)
  13.             correct += (predicted == labels).sum().item()
  14.     accuracy = 100 * correct / total
  15.     print(f"Test Accuracy: {accuracy:.2f}%")
复制代码
4.4.2 测试流程


  • 模式切换:model.eval() 将模子设置为评估模式,关闭 Dropout 和 BatchNorm 的动态举动。
  • 无梯度盘算:通过 torch.no_grad() 节流内存和盘算开销。
  • 准确率盘算:通过比力猜测类别与真实标签,盘算准确率。

4.5 完备运行流程

将练习和测试联合在一起,完成整个流程。
  1. # 训练模型
  2. train(model, train_loader, criterion, optimizer, epochs=5)
  3. # 测试模型
  4. test(model, test_loader)
复制代码

4.6 运行结果

颠末 5 轮练习,模子在 MNIST 测试集上的分类准确率通常可以到达 90% 以上
  1. Epoch [1/5], Loss: 0.5204
  2. Epoch [2/5], Loss: 0.3198
  3. Epoch [3/5], Loss: 0.2712
  4. Epoch [4/5], Loss: 0.2397
  5. Epoch [5/5], Loss: 0.2163
  6. Test Accuracy: 92.58%
复制代码

4.7 进一步优化

如果想进一步提升模子性能,可以尝试以下方法:

  • 增长隐藏层:引入更多隐藏层或增长隐藏层的节点数。
  • 使用更复杂的优化器:如 Adam 优化器 (optim.Adam)。
  • 学习率调解:引入学习率调度器,动态调解学习率。
  • 正则化:添加 Dropout 层,减少过拟合风险。
  1. # 替换优化器为 Adam
  2. optimizer = optim.Adam(model.parameters(), lr=0.001)
复制代码

五、总结

在本文中,我们从零开始实现了一个完备的多层感知机(MLP)模子,用于 MNIST 手写数字分类标题。通过这一过程,你应该已经把握了以下关键知识点:

  • 全连接神经网络的基本结构:如何通过 nn.Linear 搭建最底子的神经网络;
  • 激活函数的引入:如何通过非线性激活函数(如 ReLU)提升模子的学习能力;
  • 多层感知机的实现:如何设计一个包含多层隐藏层的深度学习模子;
  • 完备的练习与测试流程:从数据加载、前向传播、反向传播到模子性能的评估。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

写过一篇

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表