单层神经网络,也称为单层感知器,是最简单的神经网络类型。它仅包含一层神经元,这些神经元连接到输入层和输出层。对于图像分类器,输入层是图像,输出层是类别标签。
要使用 PyTorch 中的单层神经网络构建图像分类器,您首先需要准备数据。这通常涉及将图像和标签加载到 PyTorch 数据加载器中,然后将数据拆分为训练集和验证集。数据准备好后,您就可以定义神经网络了。
接下来,您可以使用 PyTorch 的内置函数在训练数据上训练网络,并在验证数据上评估其性能。您还需要选择一个优化器,例如随机梯度下降 (SGD),以及一个损失函数,例如交叉熵损失。
请注意,单层神经网络可能并非适用于所有任务,但它可以作为一个简单的分类器,也有助于您理解神经网络的内部工作原理并进行调试。
因此,让我们来构建我们的图像分类器。在这个过程中,您将学习
- 如何在 PyTorch 中使用和预处理内置数据集。
- 如何在 PyTorch 中构建和训练自定义神经网络。
- 如何在 PyTorch 中构建分步图像分类器。
- 如何在 PyTorch 中使用训练好的模型进行预测。
让我们开始吧。

使用 PyTorch 单层神经网络构建图像分类器。
图片由 Alex Fung 拍摄。部分权利保留。
概述
本教程分为三个部分;它们是
- 准备数据集
- 构建模型架构
- 训练模型
准备数据集
在本教程中,您将使用 CIFAR-10 数据集。这是一个图像分类数据集,包含 10 个类别中 60,000 张 32×32 像素的彩色图像,每个类别有 6,000 张图像。其中有 50,000 张训练图像和 10,000 张测试图像。类别包括飞机、汽车、鸟类、猫、鹿、狗、青蛙、马、船和卡车。CIFAR-10 是机器学习和计算机视觉研究中一个流行的数据集,因为它相对较小且简单,但又足够具有挑战性,需要使用深度学习方法。此数据集可以轻松导入到 PyTorch 库中。
以下是具体操作方法。
1 2 3 4 5 6 7 |
import torch import torchvision import torchvision.transforms as transforms # 导入 CIFAR-10 数据集 train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transforms.ToTensor()) test_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transforms.ToTensor()) |
如果您以前从未下载过数据集,您可能会看到此代码显示图像是从何处下载的
1 2 3 4 |
正在下载 https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 到 ./data/cifar-10-python.tar.gz 0%| | 0/170498071 [00:00<!--?, ?it/s] 正在将 ./data/cifar-10-python.tar.gz 解压到 ./data 文件已下载并验证 |
您指定了数据集应下载到的 root
目录,通过设置 train=True
来导入训练集,设置 train=False
来导入测试集。download=True
参数将在指定 root
目录中不存在数据集时下载数据集。
构建神经网络模型
让我们定义一个继承自 torch.nn.Module
的简单神经网络 SimpleNet
。网络在 __init__
方法中有两个全连接 (fc) 层,fc1
和 fc2
。第一个全连接层 fc1
以图像作为输入,并具有 100 个隐藏神经元。同样,第二个全连接层 fc2
具有 100 个输入神经元和 num_classes
个输出神经元。由于有 10 个类别,num_classes
参数默认为 10。
此外,forward
方法定义了网络的前向传播,其中输入 x
通过 __init__
方法中定义的层。该方法首先使用 view
方法重塑输入张量 x
以获得所需的形状。然后,输入将通过全连接层及其激活函数,最后返回输出张量。
通过我的《用PyTorch进行深度学习》一书来启动你的项目。它提供了包含可用代码的自学教程。
以下是以上所有内容的实现代码。
1 2 |
# 创建 Data 对象 dataset = Data() |
并且,编写一个函数来可视化这些数据,这在您稍后训练模型时也会很有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import torch.nn as nn class SimpleNet(nn.Module): def __init__(self, num_classes=10): super(SimpleNet, self).__init__() self.fc1 = nn.Linear(32*32*3, 100) # 具有 100 个隐藏神经元的全连接层 self.fc2 = nn.Linear(100, num_classes) # 具有 num_classes 输出的全连接层 def forward(self, x): x = x.view(-1, 32*32*3) # 重塑输入张量 x = self.fc1(x) x = torch.relu(x) x = self.fc2(x) return x |
现在,让我们实例化模型对象。
1 2 |
# 实例化模型 model = SimpleNet() |
想开始使用PyTorch进行深度学习吗?
立即参加我的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
训练模型
您将创建 PyTorch 的 DataLoader
类的两个实例,分别用于训练和测试。在 train_loader
中,您将批次大小设置为 64,并通过设置 shuffle=True
来随机打乱训练数据。
然后,您将定义用于训练模型的交叉熵损失函数和 Adam 优化器。您为优化器设置的学习率为 0.001。
test_loader
也是类似的,只是我们不需要打乱数据。
1 2 3 4 5 6 7 |
# 将数据加载到 PyTorch DataLoader 中 train_loader = torch.utils.data.DataLoader(train_set, batch_size=64, shuffle=True) test_loader = torch.utils.data.DataLoader(test_set, batch_size=64, shuffle=False) # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) |
最后,让我们设置一个训练循环来训练我们的模型几个 epoch。您将定义一些空列表来存储损失和准确率指标的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# 训练模型 num_epochs = 20 train_loss_history = [] train_acc_history = [] val_loss_history = [] val_acc_history = [] # 循环遍历 epoch 数量 for epoch in range(num_epochs): train_loss = 0.0 train_acc = 0.0 val_loss = 0.0 val_acc = 0.0 # 将模型设置为训练模式 model.train() # 遍历训练数据 for inputs, labels in train_loader: optimizer.zero_grad() outputs = model(inputs) # 计算损失 loss = criterion(outputs, labels) loss.backward() optimizer.step() # 累加累计损失和准确率 train_loss += loss.item() train_acc += (outputs.argmax(1) == labels).sum().item() # 计算平均训练损失和准确率 train_loss /= len(train_loader) train_loss_history.append(train_loss) train_acc /= len(train_loader.dataset) train_acc_history.append(train_acc) # 将模型设置为评估模式 model.eval() with torch.no_grad(): for inputs, labels in test_loader: outputs = model(inputs) loss = criterion(outputs, labels) val_loss += loss.item() val_acc += (outputs.argmax(1) == labels).sum().item() # 计算平均验证损失和准确率 val_loss /= len(test_loader) val_loss_history.append(val_loss) val_acc /= len(test_loader.dataset) val_acc_history.append(val_acc) print(f'Epoch {epoch+1}/{num_epochs}, train loss: {train_loss:.4f}, train acc: {train_acc:.4f}, val loss: {val_loss:.4f}, val acc: {val_acc:.4f}') |
运行此循环将向您显示以下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Epoch 1/20, train loss: 1.8757, train acc: 0.3292, val loss: 1.7515, val acc: 0.3807 Epoch 2/20, train loss: 1.7254, train acc: 0.3862, val loss: 1.6850, val acc: 0.4008 Epoch 3/20, train loss: 1.6548, train acc: 0.4124, val loss: 1.6692, val acc: 0.3987 Epoch 4/20, train loss: 1.6150, train acc: 0.4268, val loss: 1.6052, val acc: 0.4265 Epoch 5/20, train loss: 1.5874, train acc: 0.4343, val loss: 1.5803, val acc: 0.4384 Epoch 6/20, train loss: 1.5598, train acc: 0.4424, val loss: 1.5928, val acc: 0.4315 Epoch 7/20, train loss: 1.5424, train acc: 0.4506, val loss: 1.5489, val acc: 0.4514 Epoch 8/20, train loss: 1.5310, train acc: 0.4568, val loss: 1.5566, val acc: 0.4454 Epoch 9/20, train loss: 1.5116, train acc: 0.4626, val loss: 1.5501, val acc: 0.4442 Epoch 10/20, train loss: 1.5005, train acc: 0.4677, val loss: 1.5282, val acc: 0.4598 Epoch 11/20, train loss: 1.4911, train acc: 0.4702, val loss: 1.5310, val acc: 0.4629 Epoch 12/20, train loss: 1.4804, train acc: 0.4756, val loss: 1.5555, val acc: 0.4457 Epoch 13/20, train loss: 1.4743, train acc: 0.4762, val loss: 1.5207, val acc: 0.4629 Epoch 14/20, train loss: 1.4658, train acc: 0.4792, val loss: 1.5177, val acc: 0.4570 Epoch 15/20, train loss: 1.4608, train acc: 0.4819, val loss: 1.5529, val acc: 0.4527 Epoch 16/20, train loss: 1.4539, train acc: 0.4832, val loss: 1.5066, val acc: 0.4645 Epoch 17/20, train loss: 1.4486, train acc: 0.4863, val loss: 1.4874, val acc: 0.4727 Epoch 18/20, train loss: 1.4503, train acc: 0.4866, val loss: 1.5318, val acc: 0.4575 Epoch 19/20, train loss: 1.4383, train acc: 0.4910, val loss: 1.5065, val acc: 0.4673 Epoch 20/20, train loss: 1.4348, train acc: 0.4897, val loss: 1.5127, val acc: 0.4679 |
正如您所见,单层分类器仅训练了 20 个 epoch,其验证准确率约为 47%。如果训练更多 epoch,您可能会获得不错的准确率。同样,我们的模型只有一个包含 100 个隐藏神经元的层。如果添加更多层,准确率可能会显著提高。
现在,让我们绘制损失和准确率图,看看它们的样子。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import matplotlib.pyplot as plt # 绘制训练和验证损失 plt.plot(train_loss_history, label='train loss') plt.plot(val_loss_history, label='val loss') plt.legend() plt.show() # 绘制训练和验证准确率 plt.plot(train_acc_history, label='train acc') plt.plot(val_acc_history, label='val acc') plt.legend() plt.show() |
损失图是这样的:准确率图如下:
这是您可以看到模型如何根据真实标签进行预测的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import numpy as np # 获取一些验证数据 for inputs, labels in test_loader: break # 这行代码会在第一次迭代后停止循环 # 进行预测 outputs = model(inputs) _, predicted = torch.max(outputs, 1) # 显示图像及其标签 img_grid = torchvision.utils.make_grid(inputs) img_grid = img_grid / 2 + 0.5 # 取消归一化 npimg = img_grid.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) print('True Labels: ', labels) print('Predicted Labels: ', predicted) |
打印的标签如下:
1 2 3 4 5 6 |
True Labels: tensor([3, 8, 8, 0, 6, 6, 1, 6, 3, 1, 0, 9, 5, 7, 9, 8, 5, 7, 8, 6, 7, 0, 4, 9, 5, 2, 4, 0, 9, 6, 6, 5, 4, 5, 9, 2, 4, 1, 9, 5, 4, 6, 5, 6, 0, 9, 3, 9, 7, 6, 9, 8, 0, 3, 8, 8, 7, 7, 4, 6, 7, 3, 6, 3]) Predicted Labels: tensor([3, 9, 8, 8, 4, 6, 3, 6, 2, 1, 8, 9, 6, 7, 1, 8, 5, 3, 8, 6, 9, 2, 0, 9, 4, 6, 6, 2, 9, 6, 6, 4, 3, 3, 9, 1, 6, 9, 9, 5, 0, 6, 7, 6, 0, 9, 3, 8, 4, 6, 9, 4, 6, 3, 8, 8, 5, 8, 8, 2, 7, 3, 6, 9]) |
这些标签对应于以下图像
总结
在本教程中,您学习了如何仅使用单层神经网络构建图像分类器。特别是,您学习了
- 如何在 PyTorch 中使用和预处理内置数据集。
- 如何在 PyTorch 中构建和训练自定义神经网络。
- 如何在 PyTorch 中构建分步图像分类器。
- 如何在 PyTorch 中使用训练好的模型进行预测。
杰森在哪里?
模型工作正常,但之后的代码,即:
准确率/损失图和预测图像,
内核因此消息而崩溃:
‘内核似乎已崩溃。它将自动重启’。我试了好几次,结果都一样。有什么建议解决这个问题吗?谢谢。
您好 Chuck……虽然我们没有遇到此问题,但以下资源可能会有所帮助
https://stackoverflow.com/questions/47022997/jupyter-the-kernel-appears-to-have-died-it-will-restart-automatically
# 创建 Data 对象
“dataset = Data()”
Pytorch 报告了后续错误。数据类未创建?
你好 Leo… 请详细说明您的问题,以便我们能更好地帮助您。也就是说…您收到的是什么错误?
在本教程中,它可能被遗漏了,但在其他教程中存在。
在 dataset = Data() 之前使用此代码。
# 创建数据集类
class Data(Dataset)
def __init__(self)
self.x = torch.arange(-2, 2, 0.1).view(-1, 1)
self.y = torch.zeros(self.x.shape[0], 1)
self.y[self.x[:, 0] > 0.2] = 1
self.len = self.x.shape[0]
def __getitem__(self, idx)
return self.x[idx], self.y[idx]
def __len__(self)
return self.len
感谢 Fabio 的反馈和建议!
你好,
我认为
dataset = Data()
部分是错误地留在代码中的?它在后面的代码中似乎没有被使用,而且 Data() 方法在使用前从未定义过。
你好 Narae… 感谢您的反馈!
你好 Kames,
谢谢回复。
代码和错误在这里:
————————————————-
import torch
import torchvision
import torchvision.transforms as transforms
# 导入 CIFAR-10 数据集
train_set = torchvision.datasets.CIFAR10(root=’./data’, train=True, download=True, transform=transforms.ToTensor())
test_set = torchvision.datasets.CIFAR10(root=’./data’, train=False, download=True, transform=transforms.ToTensor())
# 创建 Data 对象
dataset = Data()
————————————————————–
NameError Traceback (最近一次调用)
Input In [1], in ()
7 test_set = torchvision.datasets.CIFAR10(root=’./data’, train=False, download=True, transform=transforms.ToTensor())
9 # Create the Data object
—> 10 dataset = Data()
NameError: name ‘Data’ is not defined
即使是单层神经网络,其宽度扩展性也不好。
如果宽度是 n,则所需的乘加运算次数是 n 的平方。
从 n=8 的合理值(给出 64 次乘加运算)开始,到 n=256(给出 65536 次乘加运算)时变得不合理。
但是,通过使用组合算法,您可以控制成本。
https://ai462qqq.blogspot.com/2023/03/switch-net-4-reducing-cost-of-neural.html
感谢 Sean 的反馈和贡献!