在上一篇文章中,我们了解了多层感知机(MLP)的基本概念和结构。本文将带你通过一个从零开始的实现过程,帮助你更好地理解多层感知机的工作原理。在实现过程中,我们将继续使用之前的Fashion-MNIST图像分类数据集,并且把实现的过程拆解成几个简单的步骤。通过这篇文章,你将学会如何从头开始实现一个简单的多层感知机,并且能够理解其中的核心技术。
1. 初始化模型参数
首先,回顾一下Fashion-MNIST数据集的基本情况:每个图像包含28x28个灰度像素值,总共有10个类别。这些图像可以被视为每个图像具有784个输入特征(28x28=784)和10个类别的分类问题。
为了实现一个具有单隐藏层的多层感知机,我们需要定义模型的参数。这里,我们使用256个隐藏单元,并且我们将这个值作为超参数。对于层的宽度,通常选择2的幂次方(例如256),这是因为计算机硬件在处理时对这种数字处理效率更高。
在PyTorch中,我们通过定义权重矩阵和偏置向量来初始化模型参数。每一层都需要对应一个权重矩阵和偏置向量。
输入层 (784维) → 隐藏层 (256维) → 输出层 (10维)
初始化过程:
1. 随机初始化权重 W1 和 W2 为小的值(来自标准正态分布)
2. 偏置 b1 和 b2 初始化为零
输入层 (784) —> 权重 W1 —> 隐藏层 (256) —> 权重 W2 —> 输出层 (10)
↑ ↑
偏置 b1 偏置 b2
import torch
from torch import nn
#输入层的神经元数量,通常用于28x28像素的图像(如MNIST数据集)
#输出层的神经元数量,通常用于10个类别的分类任务。
#隐藏层的神经元数量,可以根据需要调整。
num_inputs, num_outputs, num_hiddens = 784, 10, 256
# 初始化权重和偏置
#W1 和 b1 是输入层到隐藏层的权重和偏置。
#W2 和 b2 是隐藏层到输出层的权重和偏置。
#使用 torch.randn 初始化权重为随机值,并乘以0.01以保持较小的初始值。
#使用 torch.zeros 初始化偏置为零。
W1 = nn.Parameter(torch.randn(num_inputs, num_hiddens, requires_grad=True) * 0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
W2 = nn.Parameter(torch.randn(num_hiddens, num_outputs, requires_grad=True) * 0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))
# 将参数打包到一起,方便后续更新
params = [W1, b1, W2, b2]
2. 激活函数
为了引入非线性,我们需要使用激活函数。在这里,我们实现了 ReLU(Rectified Linear Unit) 激活函数。ReLU的计算非常简单,它将输入值小于零的部分置为零,其他部分保持不变。
def relu(X):
a = torch.zeros_like(X)
return torch.max(X, a)
3. 模型
在多层感知机中,每一层都由一个线性变换(矩阵乘法加偏置)和一个激活函数组成。在这里,我们将每张28x28的图像展平成一个784维的向量,然后通过矩阵运算进行处理。
def net(X):
X = X.reshape((-1, num_inputs)) # 将图像展平
H = relu(torch.matmul(X, W1) + b1) # 隐藏层的计算
y = torch.matmul(H, W2) + b2 # 输出层的计算
return y
4. 损失函数
为了衡量预测结果与真实标签之间的差异,我们使用交叉熵损失函数,它通常用于多分类问题。PyTorch已经提供了一个内置的交叉熵损失函数,方便我们直接使用。
loss = nn.CrossEntropyLoss(reduction='none')
交叉熵损失函数能够计算模型输出与真实标签之间的差异。
5. 训练
多层感知机的训练过程与我们之前在softmax回归中学习的训练过程非常相似。我们使用随机梯度下降(SGD)来优化模型参数,并且训练过程中使用批量数据。
num_epochs, lr, batch_size = 10, 0.1, 256
updater = torch.optim.SGD(params, lr=lr)# 使用随机梯度下降(SGD)优化器来更新模型参数,学习率为 lr。
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)# 加载Fashion-MNIST数据集,返回训练和测试数据的迭代器。
metric = d2l.Accumulator(3)#用于累积训练过程中的指标(如损失和准确率)。
animator = d2l.Animator(xlabel='轮数', xlim=[1, num_epochs], ylim=[0, 1],
legend=['train loss', 'train acc', 'test acc'], title="多层感知机模型训练效果图") #创建一个动画器对象,用于可视化训练过程。
# 训练模型
#net: 之前定义的神经网络。
#train_iter: 训练数据迭代器。
#test_iter: 测试数据迭代器。
#loss: 损失函数,您需要确保在此之前定义或导入。
#num_epochs: 训练轮数。
#updater: 优化器。
#batch_size: 批次大小。
#animator: 用于可视化的动画器。
d2l.train_softmax(net, train_iter, test_iter, loss,
num_epochs, updater, batch_size, animator)
# 可视化优化过程
animator.show()
epoch 1, train loss 1.042, train acc 0.642, test acc 0.754
epoch 2, train loss 0.604, train acc 0.789, test acc 0.789
epoch 3, train loss 0.520, train acc 0.817, test acc 0.817
epoch 4, train loss 0.483, train acc 0.830, test acc 0.784
epoch 5, train loss 0.454, train acc 0.840, test acc 0.809
epoch 6, train loss 0.434, train acc 0.848, test acc 0.828
epoch 7, train loss 0.420, train acc 0.853, test acc 0.844
epoch 8, train loss 0.408, train acc 0.856, test acc 0.835
epoch 9, train loss 0.394, train acc 0.862, test acc 0.847
epoch 10, train loss 0.386, train acc 0.863, test acc 0.830
6. 预测
训练完成后,我们可以在测试集上评估模型的表现。
# d2l.py
def predict_ch3(net, test_iter, n=6):
"""预测标签(定义见第3章)"""
for X, y in test_iter:
break
trues = d2l.get_fashion_mnist_labels(y)#使用 d2l 库中的函数将真实标签转换为可读的文本标签。
preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))#使用模型 net 对输入 X 进行预测,获取预测的类别索引,并转换为可读的文本标签。
titles = ["真实:" + true + '\n' + "预测:" + pred for true, pred in zip(trues, preds)]# 为每个样本生成一个标题,包含真实标签和预测标签。
#X[0:n].reshape((n, 28, 28)): 选择前 n 个样本,并将其形状调整为28x28像素。
#1, n: 设置图像显示为1行 n 列。
#titles=titles[0:n]: 设置每个图像的标题。
#scale=2.5: 设置图像的缩放比例。
d2l.show_images(
X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n], scale=2.5)
d2l.predict_ch3(net, test_iter)
6. 小结
手动实现一个简单的多层感知机并不复杂,但如果网络层数很多,手动实现将变得更加麻烦。尤其是当模型非常深时,需要手动管理和更新大量的参数和梯度计算。然而,通过PyTorch等深度学习框架,我们可以轻松地实现并优化多层感知机,而不需要手动处理繁琐的细节。