在上一篇文章中,我们了解了多层感知机(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
num_inputs, num_outputs, num_hiddens = 784, 10, 256
# 初始化权重和偏置
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)
ReLU 函数的公式为:
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)
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
metric = d2l.Accumulator(3)
animator = d2l.Animator(xlabel='轮数', xlim=[1, num_epochs], ylim=[0, 1],
legend=['train loss', 'train acc', 'test acc'], title="多层感知机模型训练效果图")
# 训练模型
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)
preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
titles = ["真实:" + true + '\n' + "预测:" + pred for true, pred in zip(trues, preds)]
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等深度学习框架,我们可以轻松地实现并优化多层感知机,而不需要手动处理繁琐的细节。