马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
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.2 使用 PyTorch 搭建简朴网络
在 PyTorch 中,神经网络的构建通常通过继承 torch.nn.Module 来完成。我们可以定义网络的结构并实现前向传播。
1.2.1 示例代码:构建两层全连接网络
以下是一个使用 PyTorch 构建两层全连接网络的简朴示例:
- import torch
- import torch.nn as nn
- # 定义简单的全连接神经网络
- class SimpleNN(nn.Module):
- def __init__(self, input_size, hidden_size, output_size):
- super(SimpleNN, self).__init__()
- # 定义两层全连接层
- self.fc1 = nn.Linear(input_size, hidden_size) # 第一层全连接
- self.fc2 = nn.Linear(hidden_size, output_size) # 第二层全连接
- def forward(self, x):
- # 定义前向传播
- x = self.fc1(x) # 输入通过第一层
- x = self.fc2(x) # 输入通过第二层
- return x
- # 定义模型参数
- input_size = 10 # 输入特征数
- hidden_size = 5 # 隐藏层神经元个数
- output_size = 2 # 输出类别数
- # 初始化模型
- model = SimpleNN(input_size, hidden_size, output_size)
- # 打印模型结构
- print(model)
复制代码 1.2.2 输出结果
运行以上代码后,你将看到模子的结构:
- SimpleNN(
- (fc1): Linear(in_features=10, out_features=5, bias=True)
- (fc2): Linear(in_features=5, out_features=2, bias=True)
- )
复制代码 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 激活函数的代码:
- class SimpleNNWithActivation(nn.Module):
- def __init__(self, input_size, hidden_size, output_size):
- super(SimpleNNWithActivation, self).__init__()
- # 定义两层全连接层
- self.fc1 = nn.Linear(input_size, hidden_size)
- self.relu = nn.ReLU() # 添加 ReLU 激活函数
- self.fc2 = nn.Linear(hidden_size, output_size)
- def forward(self, x):
- x = self.fc1(x) # 输入通过第一层
- x = self.relu(x) # 应用 ReLU 激活函数
- x = self.fc2(x) # 输入通过第二层
- return x
- # 初始化网络
- model_with_activation = SimpleNNWithActivation(input_size, hidden_size, output_size)
- # 打印模型结构
- print(model_with_activation)
复制代码 2.2.2 输出结果
运行后打印的模子结构如下:
- SimpleNNWithActivation(
- (fc1): Linear(in_features=10, out_features=5, bias=True)
- (relu): ReLU()
- (fc2): Linear(in_features=5, out_features=2, bias=True)
- )
复制代码 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 -> 激活函数 -> 隐藏层2 -> 激活函数 -> 输出层
复制代码 相较于简朴的单层全连接网络,MLP 通过多个隐藏层和非线性激活函数,能够表现更复杂的函数关系,是神经网络的底子模子。
3.2 使用 PyTorch 实现多层感知机
以下是实现一个具有两层隐藏层的 MLP 的代码示例。
3.2.1 示例代码:构建多层感知机
- import torch
- import torch.nn as nn
- # 定义多层感知机
- class MLP(nn.Module):
- def __init__(self, input_size, hidden_sizes, output_size):
- super(MLP, self).__init__()
- # 定义第一层全连接层
- self.fc1 = nn.Linear(input_size, hidden_sizes[0])
- # 定义第二层全连接层
- self.fc2 = nn.Linear(hidden_sizes[0], hidden_sizes[1])
- # 定义输出层
- self.fc3 = nn.Linear(hidden_sizes[1], output_size)
- # 定义 ReLU 激活函数
- self.relu = nn.ReLU()
- def forward(self, x):
- # 数据通过第一层
- x = self.fc1(x)
- x = self.relu(x) # 应用 ReLU 激活
- # 数据通过第二层
- x = self.fc2(x)
- x = self.relu(x) # 再次应用 ReLU 激活
- # 数据通过输出层
- x = self.fc3(x)
- return x
- # 定义模型参数
- input_size = 784 # 输入层节点数(如28x28图像展开为784维向量)
- hidden_sizes = [128, 64] # 两个隐藏层,节点数分别为128和64
- output_size = 10 # 输出层节点数(如10个分类)
- # 初始化模型
- mlp_model = MLP(input_size, hidden_sizes, output_size)
- # 打印模型结构
- print(mlp_model)
复制代码 3.2.2 输出结果
运行以上代码后,会打印出模子结构:
- MLP(
- (fc1): Linear(in_features=784, out_features=128, bias=True)
- (fc2): Linear(in_features=128, out_features=64, bias=True)
- (fc3): Linear(in_features=64, out_features=10, bias=True)
- (relu): ReLU()
- )
复制代码 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 ) 的向量。
- # 示例:将 28x28 图像展平成一维向量
- images = torch.randn(64, 1, 28, 28) # 假设 64 个批量的 MNIST 数据
- flattened_images = images.view(images.size(0), -1) # 展平成 (64, 784)
- print(flattened_images.shape) # 输出形状:(64, 784)
复制代码 3.4 模子练习的须要组件
为了练习多层感知机,须要以下组件:
- 损失函数:盘算猜测结果与真实值之间的误差。
- 优化器:调解模子参数以最小化损失函数。
- 数据加载器:批量加载数据,支持随机抽样和迭代。
3.4.1 示例代码:定义损失函数和优化器
- import torch.optim as optim
- # 定义交叉熵损失函数(适合分类任务)
- criterion = nn.CrossEntropyLoss()
- # 定义随机梯度下降优化器
- optimizer = optim.SGD(mlp_model.parameters(), lr=0.01)
- print("Loss function and optimizer defined!")
复制代码 3.5 注意事项
- 隐藏层的数量和节点数:
- 隐藏层越多,网络的表达能力越强,但盘算复杂度也会增长;
- 节点数的选择须要平衡模子性能和盘算开销,可根据经验或通过交叉验证调解。
- 过拟合标题:
- 多层感知机可能轻易过拟合,特殊是在隐藏层过多或节点数过大时;
- 可以引入正则化技能(如 Dropout)来缓解过拟合。
- 初始化权重:
- 默认的权重初始化方式通常表现良好,但在某些复杂任务中,可以思量使用自定义初始化方法。
3.6 多层感知机的优化方向
为了进一步优化多层感知机的性能,可以尝试以下方法:
- 更复杂的激活函数:
- 除了 ReLU,还可以尝试 Leaky ReLU、ELU 或 GELU 等更高级的激活函数。
- 正则化技能:
- 添加 Dropout 层随机丢弃部分神经元,防止过拟合;
- 在损失函数中加入 L2 正则化项(权重惩罚)。
- 使用 GPU:
- 通过将模子和数据加载到 GPU 上,明显加速练习过程:
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
- mlp_model.to(device)
复制代码
四、现实案例:手写数字分类
在本节中,将联合经典的 MNIST 数据集,使用多层感知机(MLP)完成手写数字分类任务。MNIST 数据集包含 28x28 的灰度图像,共 10 个类别(数字 0 到 9)。
4.1 数据加载与预处理
4.1.1 下载 MNIST 数据集
使用 torchvision 提供的工具,可以轻松下载并加载 MNIST 数据集。
- from torchvision import datasets, transforms
- from torch.utils.data import DataLoader
- # 数据预处理
- transform = transforms.Compose([
- transforms.ToTensor(), # 转换为 Tensor
- transforms.Normalize((0.5,), (0.5,)) # 归一化到 [-1, 1]
- ])
- # 加载 MNIST 数据集
- train_dataset = datasets.MNIST(
- root='./data', train=True, transform=transform, download=True
- )
- test_dataset = datasets.MNIST(
- root='./data', train=False, transform=transform, download=True
- )
- # 定义数据加载器
- train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
- test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
- 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 模子代码
- import torch.nn as nn
- class MLP(nn.Module):
- def __init__(self, input_size, hidden_sizes, output_size):
- super(MLP, self).__init__()
- self.fc1 = nn.Linear(input_size, hidden_sizes[0]) # 第一层全连接
- self.fc2 = nn.Linear(hidden_sizes[0], hidden_sizes[1]) # 第二层全连接
- self.fc3 = nn.Linear(hidden_sizes[1], output_size) # 输出层
- self.relu = nn.ReLU() # 激活函数
- def forward(self, x):
- x = self.fc1(x)
- x = self.relu(x)
- x = self.fc2(x)
- x = self.relu(x)
- x = self.fc3(x)
- return x
- # 定义模型参数
- input_size = 28 * 28 # MNIST 图像展平成一维向量,28x28 = 784
- hidden_sizes = [128, 64] # 两个隐藏层,节点数分别为 128 和 64
- output_size = 10 # 输出层(10 个分类)
- # 初始化模型
- model = MLP(input_size, hidden_sizes, output_size)
- print(model)
复制代码 4.2.2 模子结构解释
- 输入层为 784(28x28 睁开后),输出层为 10(对应 10 个数字类别)。
- 两个隐藏层分别包含 128 和 64 个神经元,通过 ReLU 激活函数引入非线性。
- 模子的参数(权重和偏置)会在练习中主动更新。
4.3 定义练习过程
模子练习包括前向传播、损失盘算、反向传播和优化步调。
4.3.1 练习函数
- import torch
- import torch.optim as optim
- # 定义损失函数和优化器
- criterion = nn.CrossEntropyLoss() # 适合分类任务的交叉熵损失
- optimizer = optim.SGD(model.parameters(), lr=0.01) # 随机梯度下降优化器
- # 训练函数
- def train(model, train_loader, criterion, optimizer, epochs=5):
- for epoch in range(epochs):
- model.train() # 切换到训练模式
- running_loss = 0.0
- for images, labels in train_loader:
- # 展平图像为向量
- images = images.view(images.size(0), -1)
- # 清零梯度
- optimizer.zero_grad()
- # 前向传播
- outputs = model(images)
- loss = criterion(outputs, labels)
- # 反向传播和优化
- loss.backward()
- optimizer.step()
- running_loss += loss.item()
- print(f"Epoch [{epoch+1}/{epochs}], Loss: {running_loss/len(train_loader):.4f}")
复制代码 4.3.2 代码说明
- 损失函数:CrossEntropyLoss 盘算猜测值与真实标签之间的误差。
- 优化器:SGD(随机梯度下降)更新模子参数。
- 练习流程:
- 数据通过模子的前向传播盘算输出;
- 通过损失函数盘算误差;
- 反向传播盘算梯度;
- 使用优化器更新权重。
4.4 模子测试与评估
练习完成后,我们须要在测试集上评估模子的性能,盘算分类准确率。
4.4.1 测试函数
- def test(model, test_loader):
- model.eval() # 切换到评估模式
- correct = 0
- total = 0
- with torch.no_grad(): # 测试时不需要计算梯度
- for images, labels in test_loader:
- # 展平图像为向量
- images = images.view(images.size(0), -1)
- # 前向传播
- outputs = model(images)
- _, predicted = torch.max(outputs, 1) # 获取最大值对应的类别
- total += labels.size(0)
- correct += (predicted == labels).sum().item()
- accuracy = 100 * correct / total
- print(f"Test Accuracy: {accuracy:.2f}%")
复制代码 4.4.2 测试流程
- 模式切换:model.eval() 将模子设置为评估模式,关闭 Dropout 和 BatchNorm 的动态举动。
- 无梯度盘算:通过 torch.no_grad() 节流内存和盘算开销。
- 准确率盘算:通过比力猜测类别与真实标签,盘算准确率。
4.5 完备运行流程
将练习和测试联合在一起,完成整个流程。
- # 训练模型
- train(model, train_loader, criterion, optimizer, epochs=5)
- # 测试模型
- test(model, test_loader)
复制代码 4.6 运行结果
颠末 5 轮练习,模子在 MNIST 测试集上的分类准确率通常可以到达 90% 以上。
- Epoch [1/5], Loss: 0.5204
- Epoch [2/5], Loss: 0.3198
- Epoch [3/5], Loss: 0.2712
- Epoch [4/5], Loss: 0.2397
- Epoch [5/5], Loss: 0.2163
- Test Accuracy: 92.58%
复制代码 4.7 进一步优化
如果想进一步提升模子性能,可以尝试以下方法:
- 增长隐藏层:引入更多隐藏层或增长隐藏层的节点数。
- 使用更复杂的优化器:如 Adam 优化器 (optim.Adam)。
- 学习率调解:引入学习率调度器,动态调解学习率。
- 正则化:添加 Dropout 层,减少过拟合风险。
- # 替换优化器为 Adam
- optimizer = optim.Adam(model.parameters(), lr=0.001)
复制代码 五、总结
在本文中,我们从零开始实现了一个完备的多层感知机(MLP)模子,用于 MNIST 手写数字分类标题。通过这一过程,你应该已经把握了以下关键知识点:
- 全连接神经网络的基本结构:如何通过 nn.Linear 搭建最底子的神经网络;
- 激活函数的引入:如何通过非线性激活函数(如 ReLU)提升模子的学习能力;
- 多层感知机的实现:如何设计一个包含多层隐藏层的深度学习模子;
- 完备的练习与测试流程:从数据加载、前向传播、反向传播到模子性能的评估。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |