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 torchvisionimport torchvision
import torch.nn.functional as F
import torchvision.datasets as datasets
import torchvision.transforms as transforms
# !pip install torchmetricsimport torchmetrics技术支持
defimshow(img):
npimg = img.numpy()
plt.imshow(np.transpose(npimg,(1,2,0)))
plt.show()# get some random training images
dataiter =iter(dataloader_train)
images, labels =next(dataiter)
labels
# show images
imshow(torchvision.utils.make_grid(images))技术支持
classCNN(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__()# 1st convolutional layer
self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=8, kernel_size=3, padding=1)# Max pooling layer
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)# 2nd convolutional layer
self.conv2 = nn.Conv2d(in_channels=8, out_channels=16, kernel_size=3, padding=1)# Fully connected layer
self.fc1 = nn.Linear(16*7*7, num_classes)defforward(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))# Apply first convolution and ReLU activation
x = self.pool(x)# Apply max pooling
x = F.relu(self.conv2(x))# Apply second convolution and ReLU activation
x = self.pool(x)# Apply max pooling
x = x.reshape(x.shape[0],-1)# Flatten the tensor
x = self.fc1(x)# Apply fully connected layerreturn x
x = x.reshape(x.shape[0],-1)# Flatten the tensor
x = self.fc1(x)# Apply fully connected layerreturn x技术支持
# Set up of multiclass accuracy metric
acc = Accuracy(task="multiclass",num_classes=10)# Iterate over the dataset batches
model.eval()with torch.no_grad():for images, labels in dataloader_test:# Get predicted probabilities for test data batch
outputs = model(images)
_, preds = torch.max(outputs,1)
acc(preds, labels)
precision(preds, labels)
recall(preds, labels)#Compute total test accuracy
test_accuracy = acc.compute()print(f"Test accuracy: {test_accuracy}")>>> Test accuracy:0.9857000112533569技术支持
要加载已保存的模型,我们需要用相同的架构初始化一个新模型。然后通过 torch.load 配合加载 state dict 的方法,将参数加载到新模型中。
# Save the model
torch.save(model.state_dict(),'MulticlassCNN.pth')# Create a new model
loaded_model = CNN(in_channels=1, num_classes=10)# Load the saved model
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)是现代计算机视觉的基石,支撑着图像识别、人脸检测和自动驾驶等应用。这类网络能够自动从图像中提取模式和特征,在视觉任务上通常比传统机器学习技术更为强大。
在本教程中,我们将使用 PyTorch 实现一个 CNN。PyTorch 是一个既易于上手、又适合科研与生产的深度学习框架。
先修要求:深度学习与 PyTorch
在深入讨论 CNN 之前,您需要熟悉深度学习领域以及我们在环境搭建中将使用的 Python 库。
深度学习 是机器学习的一个分支,其基本模型结构由输入层、隐藏层和输出层构成。这样的网络可以有一个或多个隐藏层。深度学习背后的最初直觉是构建受人脑学习方式启发的模型:通过称为神经元的相互连接的细胞进行学习。这也是我们仍将深度学习模型称为“神经”网络的原因。与其它监督学习模型相比,这些分层结构需要更多数据来从非结构化数据中学习模式,通常至少需要数十万条数据。
尽管用于实现深度学习算法的框架与包有很多,我们将聚焦于 PyTorch——最受欢迎且维护良好的框架之一。除了被工业界的深度学习工程师广泛使用,PyTorch 也深受研究者青睐。许多深度学习论文都基于 PyTorch 发布。它的设计直观、易用,并与 Python 库 NumPy 有诸多共通之处。
如果您需要这些概念的入门介绍,欢迎今天就加入Deep Learning with PyTorch 课程。
什么是卷积神经网络(CNN)?
卷积神经网络(常称 CNN 或 ConvNet)是一类特别适合计算机视觉任务的深度神经网络。CNN 的发明可追溯到 20 世纪 80 年代,但直到 2010 年代,随着图形处理器(GPU)带来的计算突破,CNN 才真正进入主流。CNN 的迅速普及帮助神经网络领域重获关注,开启了我们至今仍处于其中的“神经网络第三浪潮”。
CNN 的灵感来源于生物视觉皮层。皮层由对视野特定区域敏感的小片区细胞组成。这个观点在 1962 年被 Hubel 和 Wiesel 的经典实验进一步拓展。
CNN 试图通过由不同、任务特定的层构成的复杂神经网络来复现这一特性。CNN 被称为“前馈”网络,因为信息在模型中单向流动。与某些使用反向传播等技术的模型不同,它没有将输出反馈回自身的连接。
具体来说,CNN 通常由以下层组成:
卷积层
这是 CNN 的第一个构件。顾名思义,主要的数学操作是“卷积”,即将滑动窗口函数应用于表示图像的像素矩阵。应用于矩阵的滑动函数称为核或滤波器。在卷积层中,会应用若干尺寸相同的滤波器,每个滤波器用于识别图像中的特定模式,例如数字的弯曲、边缘、整体形状等。
激活函数
通常在每次卷积操作后会应用 ReLU 激活函数。该函数有助于网络学习图像特征之间的非线性关系,使网络更稳健地识别不同模式,同时也有助于缓解梯度消失问题。
池化层
池化层的目标是从卷积后的矩阵中提取最重要的特征。这通过应用聚合操作实现,可降低特征图(卷积矩阵)的维度,从而在训练网络时减少内存占用。池化对于缓解过拟合也很重要。
全连接层
这些层位于卷积神经网络的末端,其输入对应于最后一个池化层生成的一维扁平化矩阵。我们对其应用 ReLU 激活函数以引入非线性。
您可以在我们的教程Convolutional Neural Networks in Python中阅读更详细的 CNN 数学原理说明。
为何在图像分类中使用 CNN?
卷积神经网络是计算机视觉领域最具影响力的创新之一。与 SVM、决策树等传统机器学习模型相比,它们的表现更为出色,并实现了当前最先进的结果。
此外,卷积层赋予 CNN 平移不变性,使其能够在位置、方向、尺度或平移发生变化时,依然从数据中识别并提取模式与特征。
CNN 已在许多真实案例与应用中取得成功,例如:
除了图像分类任务外,CNN 也很通用,可应用于自然语言处理、时间序列分析和语音识别等多个领域。
用 PyTorch 实现一个 CNN
既然您已经熟悉了 CNN 的理论,我们就开始上手实践。在本节中,我们将使用 PyTorch 构建并训练一个简单的 CNN。我们的目标是构建一个能对图像中的数字进行分类的模型。为了训练和测试模型,我们将使用著名的 MNIST 数据集,它包含 70,000 张 28x28 的灰度手写数字图像。
1. 导入所需库
下方是本教程将使用的库。核心思路是用 PyTorch 构建 CNN,并借助其计算机视觉模块
torchvision下载与加载 MNIST 数据集。最后,我们还将使用torchmetrics评估模型性能。2. 加载与预处理数据集
PyTorch 还拥有丰富的工具与扩展生态,包括用于计算机视觉的 torchvision 模块。Torchvision 提供了多个可用于训练与测试神经网络的图像数据集。在本教程中,我们将使用 MNIST 数据集。
首先,我们会下载 MNIST 数据集并将其转换为张量(tensor),这是 PyTorch 的核心数据结构,与 NumPy 数组类似,但具备 GPU 加速能力。
接着,我们还将使用 DataLoader 来对训练集与测试集进行批处理与打乱。PyTorch 的 DataLoader 可基于 Dataset 创建,用于加载数据、分批,并在需要时对数据执行变换,最终生成可用于训练的数据样本。下面的代码中,我们加载数据并以批大小为 60 张图像的方式保存到 DataLoader 中:
可选地,训练集还可以进一步划分为训练集与验证集。验证是在训练过程中用于评估模型性能的技术,有助于检测潜在的过拟合与欠拟合,特别有助于调优超参数。不过,为了简化起见,本教程不使用验证集。若您想进一步了解验证,请参阅我们的Introduction to Deep Learning with PyTorch 课程中的完整讲解。
现在我们已有数据,让我们看看随机一批数字长什么样:
3. 定义 CNN 架构
为解决分类问题,我们将使用
nn.Module类,这是 PyTorch 用于直观构建复杂神经网络架构的基础模块。在下面的代码中,我们创建了一个名为
CNN的类,继承自nn.Module。CNN类将作为蓝图,定义一个包含两个卷积层、随后接一个全连接层的 CNN。在 PyTorch 中,我们使用
nn.Conv2d定义卷积层,向其传入输入与输出特征图的数量,同时设置核(滤波器)大小与填充等参数。接下来,我们用
nn.MaxPool2d添加最大池化层。该层在前一卷积层的输出上滑动一个不重叠的窗口,并在每个位置选取窗口中的最大值向前传递。此操作可缩小特征图的空间尺寸,减少网络中的参数数量与计算复杂度。最后,我们添加一个全连接线性层。forward()函数定义了各层之间的连接方式,并在每个卷积层之后加入 ReLU 激活函数。定义好
CNN类后,我们就可以创建模型并将其移动到训练与运行所用的设备上。包括 CNN 在内的神经网络在 GPU 上通常表现更好,但这未必适用于您的电脑。因此,我们仅在可用时使用 GPU,否则使用常规 CPU。
4. 训练 CNN 模型
现在我们已有模型,是时候训练它了。首先需要确定如何评估模型的表现。由于我们处理的是多分类问题,将使用 交叉熵损失函数,在 PyTorch 中对应
nn.CrossEntropyLoss。我们还将使用 Adam 优化器,这是最常用的优化算法之一。我们将迭代 10 个 epoch 和训练批次来训练模型,并对每个批次执行如下所示的常规步骤。
5. 评估模型
模型训练完成后,我们可以在测试数据集上评估其性能。我们将使用分类问题中常见的指标——准确率。准确率衡量数据集中被正确分类的样本占总样本数的比例,计算方式为模型正确预测数除以总预测数。
首先,我们在 torchmetrics 中设置准确率指标。然后使用模型的 .eval 方法将模型置于评估模式,因为某些 PyTorch 层在训练与测试阶段的行为不同。我们还添加
torch.no_grad的 Python 上下文,表示不会进行梯度计算。接着,在不计算梯度的情况下遍历测试样本。对于每个测试批次,我们获取模型输出,取最可能的类别,并与标签一起传递给准确率函数。最后计算指标并打印结果。我们得到 0.98 的准确率,这意味着模型对 98% 的数字进行了正确分类。不错!
您还可以使用其它常见的分类指标,包括召回率和精确率。我们在Intermediate Deep Learning with PyTorch 课程中通过实践示例对这些指标进行了详细讲解。
提升模型性能
虽然我们的 CNN 模型表现已相当不错,但仍有多种策略可以进一步提升其准确率、鲁棒性以及在新数据上的泛化能力。
本节将探索数据增强、超参数调优与迁移学习等关键技术,以优化模型表现。
数据增强技术
数据增强是一种通过随机创建新的训练数据来提升模型准确率的技术。例如,在数据加载阶段,可以对训练图像应用变换,如缩放、水平或垂直翻转、随机旋转等。这样便可生成增强后的图像,并赋予与原图相同的标签,从而扩大训练集规模。
对原始图像添加随机变换可以在扩大训练集规模与多样性的同时,提升模型对真实世界图像中常见变化与畸变的鲁棒性,并在模型学会忽略随机变换的过程中降低过拟合。
但需要注意的是,数据增强有时可能会对训练造成不利影响。例如,在我们的任务中,如果对数字“6”应用垂直翻转,它会看起来像数字“9”。若仍以“6”的标签传给模型,就会使模型困惑并阻碍训练。这些例子表明,某些特定增强可能会影响标签。
超参数调优
提升模型性能的另一策略是调整模型各层相关超参数的取值。这种超参数调优需要对神经网络背后的数学原理及不同超参数的意义有较深理解。
例如,您可以通过改变滤波器大小或增加填充来微调 CNN 的各层;也可以为神经元设置不同的初始权重。
由于我们事先并不知道超参数的最优值,通常需要一定的试错过程。常见的方法是网格搜索,它可以在参数网格上系统地评估模型。
不过,使用该方法时要留意其计算开销通常较大,尤其是在处理复杂神经网络和大型训练数据集时。
同样地,您也可以通过增加更多卷积层与线性层来提升模型复杂度。但在添加新层时需谨慎,因为神经元数量可能会急剧增加,从而导致训练时间变长并可能产生过拟合。
您可以在我们的Introduction to Deep Learning with PyTorch 课程中进一步学习超参数调优。
使用预训练模型
从零开始训练深度学习模型过程漫长且枯燥,通常还需要大量训练数据。相较之下,我们常常可以使用预训练模型,即在某些任务上已训练好的模型。
有时,如果预训练模型已能解决我们关心的任务,就可以直接复用;在其他情况下,我们可能需要对其进行调整以适应新任务,这被称为迁移学习。
在 PyTorch 中使用预训练模型非常方便。Torchvision 提供了一系列针对各种图像相关任务的预训练模型。这些模型基于大规模图像数据集进行训练,并可直接获取。请查看我们的Deep Learning for Images with PyTorch 课程,了解有关它们的全部内容。
部署 CNN 模型
在 PyTorch 中训练好高精度的分类模型后,您可以保存模型及其预训练权重,以便今后复用并与团队共享,确保他们能够顺利加载。
要保存模型,我们可以使用
torch.save。常见的模型文件扩展名包括pt或pth。若只保存模型权重,可将model.state_dict传给torch.save并提供输出文件名,例如MulticlassCNN.pth。要加载已保存的模型,我们需要用相同的架构初始化一个新模型。然后通过
torch.load配合加载 statedict的方法,将参数加载到新模型中。总结
我们全面回顾了 CNN,并介绍了 CNN 架构中各层的细节。随后,我们给出如何在 PyTorch 中实现 CNN 的指南,涵盖从数据加载、模型设计到训练与评估的主要步骤。最后,我们也分析了多种提升模型性能的策略。我们将这些技能应用于一个与多分类任务相关的真实情境中。
关于深度学习还有很多值得学习的内容,它可以说是 AI 中最令人兴奋且要求严苛的领域之一。所幸 DataCamp 能为您提供帮助。请查看我们的专题资料与课程,成为神经网络专家: