卷积神经网络(CNNs)是现代计算机视觉的基石,可以实现诸如图像识别、人脸检测和自动驾驶汽车等应用。这些网络旨在自动从图像中提取模式和特征,使其比传统的机器学习技术在视觉任务上更强大。
在本教程中,我们将使用PyTorch实现一个CNN,PyTorch是一个既用户友好又高效的深度学习框架,适用于研究和生产应用。
先决条件:深度学习和PyTorch
在深入了解CNNs的细节之前,您必须熟悉深度学习领域以及我们将在环境设置过程中使用的Python库。
深度学习是机器学习的一个子集,其基本模型结构是一个由输入、隐藏层和输出组成的网络。这样的网络可以有一个或多个隐藏层。深度学习最初的直觉是创建受到人脑学习启发的模型:通过称为神经元的互连单元学习。这就是为什么我们继续将深度学习模型称为“神经”网络的原因。这些分层模型结构需要比其他监督学习模型更多的数据来学习,以从非结构化数据中得出模式。通常我们谈论至少数十万个数据点。
虽然有几种用于实现深度学习算法的框架和包,但我们将重点放在PyTorch上,这是最受欢迎和维护良好的框架之一。除了被工业中的深度学习工程师使用外,PyTorch也是研究人员青睐的工具。许多深度学习论文都是使用PyTorch发布的。它被设计成直观且用户友好,与Python库NumPy有很多共同之处。
如果您需要对这些概念有所了解,请考虑今天报名参加使用PyTorch进行深度学习课程。
什么是卷积神经网络(CNN)?
卷积神经网络,通常称为CNN或ConvNet,是一种特定类型的深度神经网络,非常适合计算机视觉任务。CNN的发明可以追溯到20世纪80年代。然而,它们直到2010年代才变得流行,这是由于图形处理单元(GPU)的实施带来的计算突破。事实上,CNN的迅速普及帮助神经网络领域重新获得了显赫地位,导致了我们今天仍在经历的所谓“神经网络第三波”。
CNN的灵感来源于生物视觉皮层。皮层有对视觉领域特定区域敏感的小区域细胞。这个想法是由1962年Hubel和Wiesel进行的一项引人入胜的实验扩展的。
CNN通过创建由不同任务特定层组成的复杂神经网络来复制这一特征。CNN被称为“前馈”,因为信息直接通过模型流动。与使用反向传播等技术的其他模型不同,CNN中没有反馈连接,其中模型的输出被反馈回自身。
具体来说,CNN通常包括以下层:
卷积层
这是CNN的第一个构建模块。顾名思义,执行的主要数学任务称为卷积,即对表示图像的像素矩阵应用滑动窗口函数。应用于矩阵的滑动函数称为内核或滤波器。在卷积层中,应用了几个相同大小的滤波器,每个滤波器用于识别图像中的特定模式,例如数字的弯曲、边缘、数字的整体形状等。
激活函数
通常,在每次卷积操作后应用一个ReLU激活函数。该函数有助于网络学习图像中特征之间的非线性关系,使网络更具鲁棒性,以识别不同的模式。它还有助于缓解梯度消失问题。
池化层
池化层的目标是从卷积矩阵中提取最显著的特征。这是通过应用一些聚合操作来实现的,这些操作会减少特征图(卷积矩阵)的维度,从而在训练网络时减少内存使用。池化对于减轻过拟合也很重要。
全连接层
这些层位于卷积神经网络的最后一层,它们的输入对应于最后一个池化层生成的展平的一维矩阵。对它们应用ReLU激活函数以实现非线性。
卷积神经网络架构。来源:DataCamp
您可以在我们的教程《Python中的卷积神经网络》中阅读有关CNN背后数学的更详细解释。
为什么要使用CNN进行图像分类?
卷积神经网络已成为计算机视觉领域最具影响力的创新之一。它们的性能要比传统的机器学习模型(如SVMs和决策树)好得多,并取得了最新的技术成果。
此外,卷积层赋予了CNN它们的平移不变性特征,使其能够识别和提取数据中的模式和特征,而不受位置、方向、比例或平移变化的影响。
在许多不同的实际案例研究和应用中,CNN已被证明取得了成功,例如:
- 图像分类、目标检测、分割、人脸识别;
- 利用基于CNN的视觉系统的自动驾驶汽车;
- 使用卷积神经网络对晶体结构进行分类;
- 安全摄像头系统。
除了图像分类任务,CNN是多才多艺的,可以应用于一系列其他领域,如自然语言处理、时间序列分析和语音识别。
使用PyTorch实现卷积神经网络
现在您已经熟悉了CNN的理论,我们准备动手实践。在这一部分,我们将使用PyTorch构建和训练一个简单的CNN。我们的目标是构建一个模型,用于对图像中的数字进行分类。为了训练和测试我们的模型,我们将使用著名的MNIST数据集,这是一个包含70,000张灰度、28×28像素手写数字图像的集合。
1. 导入所需的库
以下是本教程中将使用的库。实质上,我们将利用PyTorch构建我们的CNN,以及PyTorch的计算机视觉模块torchvision
来下载和加载MNIST数据集。最后,我们还将使用torchmetrics
来评估我们模型的性能。
import numpy as np import pandas as pd import matplotlib.pyplot as plt import torch from torch import optim from torch import nn from torch.utils.data import DataLoader from tqdm import tqdm # !pip install torchvision import torchvision import torch.nn.functional as F import torchvision.datasets as datasets import torchvision.transforms as transforms # !pip install torchmetrics import torchmetrics
2. 加载和预处理数据集
PyTorch还附带了丰富的工具和扩展生态系统,其中包括torchvision,一个用于计算机视觉的模块。Torchvision包括几个图像数据集,可用于训练和测试神经网络。在我们的教程中,我们将使用MNIST数据集。
首先,我们将下载并将MNIST数据集转换为张量,这是PyTorch中的核心数据结构,类似于NumPy数组,但具有GPU加速能力。
然后,我们还将使用DataLoader来处理训练和测试数据集的分批和洗牌。可以从数据集创建一个PyTorch DataLoader来加载数据,将其分成批次,并在需要时对数据进行转换。然后,它产生一个准备用于训练的数据样本。在下面的代码中,我们加载数据并将其保存在批量大小为60张图像的DataLoaders中。
batch_size = 60 train_dataset = datasets.MNIST(root="dataset/", download=True, train=True, transform=transforms.ToTensor()) train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True) test_dataset = datasets.MNIST(root="dataset/", download=True, train=False, transform=transforms.ToTensor()) test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True)
可选地,训练数据集可以进一步分为训练和验证两个部分。验证是深度学习中用于评估模型在训练过程中性能的一种技术。它有助于检测模型的过度拟合和欠拟合,并且特别有助于优化超参数。然而,出于简单起见,我们在本教程中不会使用验证。如果您想了解更多关于验证的信息,可以查看我们的使用PyTorch进行深度学习入门课程中的完整解释。
现在我们有了数据,让我们看看一个随机批次的数字是什么样子的:
def imshow(img): npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) plt.show() # 获取一些随机训练图像 dataiter = iter(dataloader_train) images, labels = next(dataiter) labels # 显示图像 imshow(torchvision.utils.make_grid(images))
3. 定义CNN架构
为了解决分类问题,我们将利用nn.Module
类,这是PyTorch用于直观创建复杂神经网络架构的构建块。
在下面的代码中,我们创建了一个名为CNN
的类,它继承了nn.Module
类的属性。CNN
类将是一个具有两个卷积层和一个全连接层的CNN的蓝图。
在PyTorch中,我们使用nn.Conv2d
来定义一个卷积层。我们传递给它输入和输出特征映射的数量。我们还设置了一些卷积层工作的参数,包括内核或滤波器大小和填充。
接下来,我们添加一个最大池化层,使用nn.MaxPool2d
。在这个层中,我们在前一个卷积层的输出上滑动一个非重叠的窗口。在每个位置,我们选择窗口中的最大值向前传递。这个操作减少了特征映射的空间维度,减少了网络中的参数数量和计算复杂度。最后,我们添加一个全连接的线性层。
forward()
函数定义了不同层之间的连接方式,在每个卷积层之后添加了几个ReLU激活函数。
class CNN(nn.Module): def __init__(self, in_channels, num_classes): """ Building blocks of convolutional neural network. Parameters: * in_channels: Number of channels in the input image (for grayscale images, 1) * num_classes: Number of classes to predict. In our problem, 10 (i.e digits from 0 to 9). """ super(CNN, self).__init__() 第1个卷积层 self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=8, kernel_size=3, padding=1) 最大池化层 self.pool = nn.MaxPool2d(kernel_size=2, stride=2) 第2个卷积层 self.conv2 = nn.Conv2d(in_channels=8, out_channels=16, kernel_size=3, padding=1) 全连接层 self.fc1 = nn.Linear(16 * 7 * 7, num_classes) def forward(self, x): """ Define the forward pass of the neural network. Parameters: x: Input tensor. Returns: torch.Tensor The output tensor after passing through the network. """ x = F.relu(self.conv1(x)) 应用第一个卷积和ReLU激活 x = self.pool(x) 应用最大池化 x = F.relu(self.conv2(x)) 应用第二个卷积和ReLU激活 x = self.pool(x) 应用最大池化 x = x.reshape(x.shape[0], -1) 将张量展平 x = self.fc1(x) 应用全连接层 return x x = x.reshape(x.shape[0], -1) 将张量展平 x = self.fc1(x) 应用全连接层 return x
定义了CNN
类之后,我们可以创建我们的模型并将其移动到将要进行训练和运行的设备上。
神经网络,包括CNN,在GPU上运行时表现更好,但这可能并非在您的计算机上的情况。因此,我们将仅在GPU可用时在其上运行模型;否则,我们将使用普通CPU。
device = "cuda" if torch.cuda.is_available() else "cpu" model = CNN(in_channels=1, num_classes=10).to(device) print(model) >>> CNN( (conv1): Conv2d(1, 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv2): Conv2d(8, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (fc1): Linear(in_features=784, out_features=10, bias=True) )
4. 训练CNN模型
现在我们有了模型,是时候开始训练它了。为了做到这一点,我们首先需要确定如何衡量模型的性能。由于我们正在处理一个多类分类问题,我们将使用交叉熵损失函数,在PyTorch中称为nn.CrossEntropyLoss
。我们还将使用Adam优化器,这是最流行的优化算法之一。
# 定义损失函数 criterion = nn.CrossEntropyLoss() # 定义优化器 optimizer = optim.Adam(model.parameters(), lr=0.001)
我们将迭代十个周期和训练批次来训练模型,并为每个批次执行通常的步骤,如下所示。
num_epochs=10 for epoch in range(num_epochs): # 迭代训练批次 print(f"Epoch [{epoch + 1}/{num_epochs}]") for batch_index, (data, targets) in enumerate(tqdm(dataloader_train)): data = data.to(device) targets = targets.to(device) scores = model(data) loss = criterion(scores, targets) optimizer.zero_grad() loss.backward() optimizer.step()
Epoch [1/10] 100%|██████████| 1000/1000 [00:13<00:00, 72.94it/s] Epoch [2/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.27it/s] Epoch [3/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.16it/s] Epoch [4/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.00it/s] Epoch [5/10] 100%|██████████| 1000/1000 [00:13<00:00, 75.69it/s] Epoch [6/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.24it/s] Epoch [7/10] 100%|██████████| 1000/1000 [00:12<00:00, 78.23it/s] Epoch [8/10] 100%|██████████| 1000/1000 [00:12<00:00, 78.16it/s] Epoch [9/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.96it/s] Epoch [10/10] 100%|██████████| 1000/1000 [00:12<00:00, 77.93it/s]
5. 评估模型
一旦模型训练完成,我们可以评估其在测试数据集上的性能。我们将使用准确率,这是分类问题中常用的度量标准。准确率衡量了从数据集中所有对象的总数中正确分类的比例。它通过将正确预测的数量除以模型所做的总预测数量来计算。
首先,我们从torchmetrics设置了准确度指标。接下来,我们使用模型的.eval方法将模型置于评估模式,因为PyTorch模型中的一些层在训练和测试阶段的行为不同。我们还添加了一个带有torch.no_grad
的Python上下文,表示我们不会执行梯度计算。
然后,我们在不进行梯度计算的情况下迭代测试示例。对于每个测试批次,我们获取模型输出,取最可能的类,并将其与标签一起传递给准确度函数。最后,我们计算指标并打印结果。我们获得了0.98的准确度分数,这意味着我们的模型正确分类了98%的数字。不错!
# 设置多类准确度指标 acc = Accuracy(task="multiclass",num_classes=10) # 遍历数据集批次 model.eval() with torch.no_grad(): for images, labels in dataloader_test: # 获取测试数据批次的预测概率 outputs = model(images) _, preds = torch.max(outputs, 1) acc(preds, labels) precision(preds, labels) recall(preds, labels) # 计算总体测试准确度 test_accuracy = acc.compute() print(f"Test accuracy: {test_accuracy}") >>> Test accuracy: 0.9857000112533569
您还可以使用其他流行的分类指标,包括召回率和精确度。我们在使用PyTorch的中级深度学习课程中通过实际示例向您介绍这些指标。
提高模型性能
尽管我们的CNN模型表现出色,但我们可以使用几种策略进一步提高其准确性、稳健性和对新数据的泛化能力。
在本节中,我们将探讨关键技术,如数据增强、超参数调整和迁移学习,以优化我们模型的性能。
数据增强技术
数据增强是一种通过随机创建新训练数据来提高模型准确性的技术。例如,在加载过程中,可以对训练图像应用转换,如调整大小、水平或垂直翻转、随机旋转等。这样可以创建增强图像,并将其标记为与原始图像相同的标签,从而增加训练集的大小。
向原始图像添加随机变换允许我们生成更多数据,同时增加训练集的大小和多样性。这使得模型更能应对真实世界图像中常见的变化和失真,减少过拟合,因为模型学会忽略随机变换。
然而,要谨慎对待数据增强,因为有时它可能会损害训练过程。例如,在我们的问题中,如果我们对数字“6”应用垂直翻转,它看起来像数字“9”。将其传递给被标记为“6”的模型将混淆模型并妨碍训练。这些示例表明,有时特定的增强会影响标签。
超参数调整
另一种改善模型性能的策略是更改模型不同层中涉及的超参数的值。这种超参数调优需要对神经网络背后的数学以及不同超参数的重要性有深入的理解。
例如,您可以通过更改滤波器的大小或增加填充来调整您的CNN层。您还可以为神经元的初始权重设置不同的值。
由于我们事先不会知道超参数的最佳值,因此需要一定程度的试错。这通常通过一种称为网格搜索的技术来完成,该技术允许您系统地评估模型在一系列参数值的网格上的表现。
然而,在使用此技术时要注意,它通常计算成本高昂,特别是在处理复杂的神经网络和大型训练数据集时。
同样,您可以通过增加更多的卷积和线性层来增加模型的复杂性。但是,在添加新层时要谨慎,因为神经元的数量可能会急剧增加,导致训练时间更长并可能过拟合。
您可以在我们的PyTorch深度学习入门课程中了解更多关于超参数调优的知识。
使用预训练模型
从头开始训练深度学习模型是一个漫长而繁琐的过程,通常需要大量的训练数据。相反,我们经常可以使用预训练模型,即已经在某些任务上进行过训练的模型。
有时,如果预训练模型已经能够解决我们关心的任务,我们可以直接重用预训练模型。在其他情况下,我们可能需要调整预训练模型以适应新任务。这被称为迁移学习。
在PyTorch中使用预训练模型非常容易。Torchvision提供了一系列针对各种与图像相关任务的预训练模型。这些模型是在大规模图像数据集上预训练的,并且很容易获取。查看我们的使用PyTorch进行图像深度学习课程,了解关于它们的一切。
部署CNN模型
在PyTorch中训练了准确度很高的分类模型后,您现在可以保存模型及其预训练权重以备将来使用,并与团队共享,确保他们可以顺利加载它。
要保存模型,我们可以使用torch.save
。torch模型的常见文件扩展名为pt
或pth
。要保存模型的权重,我们将model.state_dict
传递给torch.save
,并提供输出文件名,例如MulticlassCNN.pth
。
要加载已保存的模型,我们需要用相同的架构初始化一个新模型。然后,我们使用torch.load
和load state dict
方法将参数加载到新模型中。
# 保存模型 torch.save(model.state_dict(), 'MulticlassCNN.pth') # 创建新模型 loaded_model = CNN(in_channels=1, num_classes=10) # 加载已保存的模型 loaded_model.load_state_dict(torch.load('MulticlassCNN.pth')) print(loaded_model) CNN( (conv1): Conv2d(1, 8, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv2): Conv2d(8, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (fc1): Linear(in_features=784, out_features=10, bias=True) )
结论
我们已经对CNN进行了全面的概述,详细介绍了CNN架构的每一层。此外,我们提供了如何在PyTorch中实现CNN的指南,涵盖了从数据加载和模型设计到模型训练和评估的主要步骤。最后,我们还分析了几种改进模型性能的策略。我们将所有这些技能应用到了与多类分类任务相关的真实场景中。
关于深度学习还有很多知识值得学习,这可以说是人工智能中最令人兴奋和要求严格的领域之一。幸运的是,DataCamp在这里提供帮助。查看我们专门的资料和课程,成为神经网络专家:
Source:
https://www.datacamp.com/tutorial/pytorch-cnn-tutorial