引言
在这篇文章中,我们将通过PyTorch实现一个简单的多层感知机(MLP)。MLP 是一种经典的前馈神经网络,广泛用于分类和回归任务。我们将使用一个常见的数据集(MNIST 手写数字识别数据集),并逐步构建模型、训练和评估它的性能。
本文使用的编程环境是FunHPC | 算力简单易用 AI乐趣丛生中的免费P4显卡,学生认证可以免费使用,单次最长使用时间是24小时,显存8G。现在注册还有15元的代金券,代金券可以用于选择一些高端显卡。为什么选择使用云计算平台训练,省电,省心。在云计算平台上都预装了多个深度学习框架,可以快速开始实战项目。除了这个平台,更多平台可以参考这篇博客:免费GPU算力平台分享:深度学习爱好者的福音-CSDN博客
注意:
以下内容,不同水平的同学可以根据自己的水平选择性看,对于参数的详解部分,有助于理解参数设置的原因,以及训练过程中的参数调整。但是如果是零基础的话,最好是直接运行每一部分的代码,先熟悉整个流程。
1. 环境配置
学生认证成功后,找到免费显卡,点击立即租用,基础镜像那里选择深度学习框架以及python环境:
2. 加载数据集
首先,导入所需的库:
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
-
from torch import nn, optim:
nn
模块包含构建神经网络所需的类和函数,例如层、激活函数和损失函数。nn.Module
是所有神经网络模块的基类,你可以继承它来创建自己的网络。optim
模块提供了多种优化算法,用于更新网络的权重,以最小化损失函数。常用的优化器包括SGD、Adam和RMSprop。
-
import torchvision:
torchvision
是一个用于计算机视觉任务的库,它提供处理图像和视频的常用工具。- 它包括数据集加载器,可以方便地下载和加载标准数据集,如MNIST、CIFAR10和ImageNet。
torchvision
还包括预训练模型,可以直接使用或作为特征提取器。
-
import torchvision.transforms as transforms:
transforms
模块提供了一系列的图像预处理功能,这些功能在训练深度学习模型之前对图像数据进行处理。- 常见的变换包括缩放、裁剪、归一化、旋转和翻转等。
- 使用
transforms
可以在加载数据时定义一个变换流水线,以确保数据以一致的方式进行处理。
我们将使用 torchvision
中的 MNIST 数据集,首先定义一些数据预处理步骤,并加载训练和测试数据集。
# 超参数设置
batch_size_train = 64
batch_size_valid = 64
batch_size_test = 1000
epochs = 5
# 数据预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
# 加载数据
trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size_train, shuffle=True)
testset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size_test, shuffle=False)
1.数据预处理:
transforms.Compose
允许将多个图像变换操作合并为一个,在数据加载时应用。transforms.ToTensor()
将PIL图像或NumPy数组转换为torch.FloatTensor
,并将图像的像素值从[0, 255]归一化到[0.0, 1.0]。transforms.Normalize((0.1307,), (0.3081,))
根据MNIST数据集的通道均值和标准差进行归一化。transforms.Compose
允许将多个图像变换操作合并为一个,在数据加载时应用。
2.加载数据:
torchvision.datasets.MNIST
是用于加载MNIST数据集的函数。root
参数指定数据集的存储路径。train=True
表示加载训练集;train=False表示加载测试集。
download=True
表示如果数据集不存在则下载它。transform
是上面定义的预处理步骤。torch.utils.data.DataLoader
是一个迭代器,它批量加载数据,并在训练中提供高效的数据迭代。
shuffle=True
- 当设置为
True
时,数据在每个epoch开始时会被随机打乱。 - 这有助于模型学习到更广泛的数据特征,因为模型无法预测下一个批次的数据样本。
- 洗牌可以提高模型的泛化能力,减少过拟合的风险。
- 在训练集上通常推荐使用
shuffle=True
,因为训练的目的是让模型学习到从各种不同的数据样本中提取特征的能力。
shuffle=False
- 当设置为
False
时,数据将按照原始顺序进行加载。 - 这意味着每个epoch中数据的顺序都是一样的。
- 在测试集或验证集上通常使用
shuffle=False
,因为我们关心的是模型在固定数据集上的性能,而不是其泛化能力。 - 对于测试和验证过程,通常更关注模型的性能指标(如准确率、召回率等)是否稳定和准确。
选择batch_size
的考虑因素
-
硬件限制:可用内存(特别是GPU内存)是决定
batch_size
大小的主要因素。 -
数据集大小:较大的数据集可能允许使用较大的
batch_size
,而较小的数据集可能需要较小的batch_size
以确保多样性。 -
模型复杂性:更复杂的模型可能需要更大的
batch_size
来有效训练。
MINST数据集简介
类别 | 文件名 | 描述 |
---|---|---|
训练集图像 | train-images-idx3-ubyte.gz | 包含 60,000 张 28x28 像素的灰度手写数字图片。 |
训练集标签 | train-labels-idx1-ubyte.gz | 包含 60,000 个标签,每个标签对应一张图像的数字(0-9)。 |
测试集图像 | t10k-images-idx3-ubyte.gz | 包含 10,000 张 28x28 像素的手写数字图片。 |
测试集标签 | t10k-labels-idx1-ubyte.gz | 包含 10,000 个标签,对应测试集中每张图片的数字标签。 |
3. 构建多层感知机模型
接下来我们定义一个简单的 MLP 模型。这个模型包含三个线性层,中间层使用 ReLU 激活函数。
# 定义MLP模型
class MLP(nn.Module):
def __init__(self):
super(MLP, self).__init__()
self.fc1 = nn.Linear(28 * 28, 128)
self.fc2 = nn.Linear(128, 64)
self.fc3 = nn.Linear(64, 10)
def forward(self, x):
x = x.view(-1, 28 * 28)
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = self.fc3(x)
return x
深度学习模型的基本结构通常在两个主要方法中定义:__init__
和forward
。
-
初始化方法 (
__init__
):- 在这个方法中,我们构建模型的骨架,即定义其基本的网络结构。这包括初始化网络中的各种层,如卷积层、全连接层、池化层、激活函数层等。这些层是模型处理数据所需的组件。
-
前向传播方法 (
forward
):forward
方法定义了数据在模型中的传输路径,即前向传播过程。在这一过程中,输入数据x
会按照我们定义的网络结构,依次流经各个层,进行计算和变换。- 该方法精心编排了数据如何通过每一层,包括应用激活函数、进行卷积运算、执行池化操作等。
- 最终,
forward
方法输出模型对输入数据的预测或输出。
4. 定义损失函数和优化器
在这段代码中,我们首先检测GPU是否可用,以确保模型可以利用GPU加速计算。如果GPU可用,模型将被转移到GPU上,否则,它将使用CPU。接下来,我们进行模型训练的初始化工作,包括初始化模型以及定义损失函数和优化器。
# 使用cuda
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f'Using device: {device}')
# 初始化模型、损失函数、优化器
model = MLP().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
5. 训练并评估模型
接下来时训练并测试模型的阶段,在训练函数中每当迭代训练一次就会测试一次模型的精度。
# 评估模型
def evaluate_model(model, testloader, criterion):
model.eval()
test_loss = 0
correct = 0
total = 0
all_labels = []
all_predictions = []
with torch.no_grad():
for images, labels in testloader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
test_loss += loss.item()
_, predicted = torch.max(outputs, 1)
correct += (predicted == labels).sum().item()
total += labels.size(0)
all_labels.extend(labels.cpu().numpy())
all_predictions.extend(predicted.cpu().numpy())
accuracy = correct / total
average_loss = test_loss / len(testloader)
return average_loss, accuracy, all_labels, all_predictions
# 训练模型
def train_model(model, trainloader, testloader, criterion, optimizer, epochs=5):
train_losses = []
test_losses = []
test_accuracies = []
for epoch in range(epochs):
model.train()
running_loss = 0.0
for images, labels in trainloader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
train_loss = running_loss / len(trainloader)
train_losses.append(train_loss)
# 每个epoch后评估模型
test_loss, accuracy, _, _ = evaluate_model(model, testloader, criterion)
test_losses.append(test_loss)
test_accuracies.append(accuracy)
print(f"Epoch {epoch+1}/{epochs}, Train Loss: {train_loss:.4f}, Test Loss: {test_loss:.4f}, Accuracy: {accuracy:.4f}")
# 训练并评估模型
train_losses, test_losses, test_accuracies = train_model(model, trainloader, testloader, criterion, optimizer, epochs=epochs)
6. 评估模型
这次实验进行了五次迭代,epoch为0时为首次迭代。可以看到,在epoch等于1时,训练损失和测试损失都有显著下降,同时准确率提高了1%。此后,模型的整体性能变化不大。可以发现,这个模型虽然很简单,但是在这个任务中的表现可以稳定在97%以上的准确率。
通过混淆矩阵可以清楚地看出,该模型在测试数据集上的表现非常出色,只有少量样本被错误分类。
7. 总结
在本次实战案例中,我们可以看到,即使是一个相对简单的模型,也能够成功完成原本需要人类智慧才能实现的任务。 那么如果我们可以设计出更复杂的模型,投喂更广泛的数据是不是模型的能力会超过人类呢。