卷积神经网络

2025年03月17日 | 阅读 22 分钟

卷积神经网络是一种特殊的前馈人工神经网络,其神经元之间的连接模式受到视觉皮层的启发。

Convolutional Neural Network

视觉皮层包含一小片对视觉场敏感的细胞。如果存在某些特定方向的边缘,则大脑中只有一些单个神经元会被激活,例如,当某些神经元暴露于垂直边缘时就会被激活,而另一些则在显示水平或对角线边缘时被激活,这就是卷积神经网络的动机。

卷积神经网络,也称为 covnets,是参数共享的神经网络。假设有一个图像,它被看作是一个长方体,包含长度、宽度和高度。这里的图像尺寸由红色、绿色和蓝色通道表示,如下图所示。

Convolutional Neural Network

现在假设我们从同一图像中取一小块,然后在上面运行一个小神经网络,该网络有 k 个输出,并以垂直方式表示。现在,当我们滑动我们的小神经网络遍布整个图像时,它将生成另一个具有不同宽度、高度和深度的图像。我们会注意到,我们得到的不再是 R、G、B 通道,而是出现了一些更多的通道,并且通道数量减少,宽度和高度也减少,这实际上就是卷积的概念。如果我们能够获得与图像相同的块大小,那么它将是一个常规的神经网络。我们有一些权重来自于这个小块。

Convolutional Neural Network

数学上可以这样理解;

  • 卷积层包含一组可学习的滤波器,每个滤波器的高度、宽度和深度都与输入的体积(如果图像是输入层,则可能是 3)相同。
  • 假设我们要对一个 34x34x3 维的图像进行卷积,滤波器的尺寸为 axax3。这里的 a 可以是 3、5、7 等。它必须比图像的尺寸小。
  • 在正向传播过程中,每个滤波器都会滑动到整个输入体积上。它会一步一步地滑动,将每一步称为步幅(stride),对于更高维的图像,步幅的值可以是 2、3 或 4,然后计算滤波器权重和输入体积中的块的内积。
  • 当我们滑动滤波器并将其堆叠在一起时,每个滤波器都会产生一个二维输出,以获得与滤波器数量相同的深度值的输出体积。然后,网络将学习所有滤波器。

CNN 的工作原理

通常,卷积神经网络有三个层,如下所示:

  • 输入:如果图像由 32 宽度、32 高度组成,包含三个 R、G、B 通道,那么它将保存图像的原始像素([32x32x3])值。
  • 卷积:它计算与输入局部区域相关的神经元的输出,使得每个神经元计算权重和它们实际连接到的输入体积中的小区域之间的内积。例如,如果我们选择包含 12 个滤波器,则将生成一个 [32x32x12] 的体积。
  • ReLU 层:它专门用于逐元素应用激活函数,例如 max (0, x) 在零处阈值化。它生成 ([32x32x12]),这与体积尺寸不变有关。
  • 池化:此层用于沿空间维度(宽度、高度)执行下采样操作,从而生成 [16x16x12] 的体积。
    Convolutional Neural Network
  • 局部连接:它可以定义为接收前一层输入的常规神经网络层,然后计算类别分数,并生成一个与类别数量大小相同的 1 维数组。
    Convolutional Neural Network

我们将从输入图像开始,应用多个特征检测器(也称为滤波器)来创建包含卷积层的特征图。然后,在该层之上,我们将应用 ReLU 或 Rectified Linear Unit 来消除图像中的任何线性或增加非线性。

接下来,我们将对卷积层应用池化层,以便从每个特征图中创建一个池化特征图,因为池化层的主要目的是确保图像的空间不变性。它还有助于减小图像大小并避免数据过拟合。之后,我们将所有池化图像展平成一个长向量或列,然后将这些值输入到我们的人工神经网络中。最后,我们将将其输入到局部连接层以获得最终输出。

Convolutional Neural Network

构建 CNN

基本上,卷积神经网络包括添加一个额外的层,称为卷积层,它赋予人工智能或深度学习模型“眼睛”,因为借助它我们可以轻松地将 3D 帧或图像作为输入,而我们之前的人工神经网络只能接受包含某些特征信息的输入向量。

但在这里,我们将在前面添加一个卷积层,它能够像人类一样可视化图像。

在我们的数据集中,训练集和测试集文件夹中都有猫和狗的图像。我们将以训练集中的 4000 张猫图像和 4000 张狗图像分别训练我们的 CNN 模型,然后在测试集中以 1000 张猫图像和 1000 张狗图像分别评估我们的模型,这些模型之前并未训练过。所以,我们实际上将构建和训练一个卷积神经网络来识别图像中是狗还是猫。

为了实现 CNN,我们将使用 Jupyter Notebook。所以,我们将从导入库、数据预处理开始,然后构建 CNN、训练 CNN,最后进行单个预测。所有步骤都将以与 ANN 相同的方式进行,唯一的区别是我们现在不预处理经典数据集,而是预处理一些图像,因此数据预处理不同,将包括两个步骤,即首先,我们将预处理训练集,然后预处理测试集。

在第二部分,我们将构建整个 CNN 架构。我们将初始化 CNN 作为一系列层,然后添加卷积层,接着添加最大池化层。然后我们将添加第二个卷积层,使其成为一个深度神经网络,而不是浅层神经网络。接下来,我们将进入展平层,将所有卷积和池化的结果展平成一维向量,该向量将成为全连接神经网络的输入。最后,我们将所有这些连接到输出层。

在第三部分,我们将首先编译 CNN,然后在训练集上训练 CNN。然后,最后,我们将进行一次单独的预测来测试我们的模型,即当我们把 CNN 部署到不同的图像上时,一张有狗,一张有猫。

所以,这只是关于如何构建我们的 CNN 模型的一个简要描述,让我们开始实际实现。

我们将开始导入 TensorFlow 库以及 Keras 库的预处理模块。然后,我们将导入 Keras 库预处理模块的图像子模块,这将使我们能够在第一部分进行图像预处理。

输出

Convolutional Neural Network

可以看到,我们已经成功运行了上面图片所示的第一个单元格。使用 TensorFlow 后端,这是第一个单元格的输出,为了使它能够正常工作,我们必须确保运行 TensorFlow 和 Keras 的 pip install 命令。

接下来,我们将检查 TensorFlow 的版本。

输出

Convolutional Neural Network

可以看到 TensorFlow 的版本是 2.0.0。

在此之后,我们将进入第一部分:数据预处理,这将分两步进行,即首先,我们将预处理训练集,其次,我们将预处理测试集。

第一部分:数据预处理

预处理训练集

我们将对训练集的所有图像应用一些变换,但不对测试集图像应用,以避免过拟合。事实上,如果在训练集上训练 CNN 时不应用这些变换,那么训练集的准确率和测试集的准确率之间将存在巨大差异。

对于计算机视觉,避免过拟合的方法是应用变换,这些变换本质上是简单的几何变换、缩放或旋转图像。所以,基本上,我们将应用一些几何变换来移动一些像素,然后稍微旋转图像,我们将进行一些水平翻转、放大以及缩小。我们实际上将应用一系列变换来修改图像并使其增强,这称为图像增强。它实际上包括变换训练集图像,以防止我们的 CNN 模型过度学习。

我们将创建一个 train_datagen 对象,该对象属于 ImageDataGenerator 类,代表将应用于训练集图像的所有变换的工具,其中 rescale 参数通过将每个像素的值除以 255 来应用特征缩放,因为每个像素的值在 0 到 255 之间,这对于神经网络来说非常必要,其余的是将在训练集图像上执行图像增强以防止过拟合的变换。

在此之后,我们需要将 train_datagen 对象连接到训练集,为此,我们需要导入训练集,这可以通过如下方式完成。这里 training_set 是我们在笔记本中导入的训练集的名称,然后我们确实使用我们的 train_datagen 对象来调用 ImageDataGenerator 类的方法。我们将调用的方法是 flow_from_directory,它将有助于将图像增强工具连接到训练集的图像。我们将传递以下参数;

  • 第一个参数是通往训练集的路径。
  • 下一个参数是 target_size,这是图像在被馈送到卷积神经网络之前的最终尺寸。
  • 第三个是 batch_size,它与批次的大小有关,即我们希望每个批次中的图像总数。我们选择了 32,这是经典的默认值。
  • 最后,我们将 class_mode 设置为 binary 或 categorical。由于我们正在寻找二进制结果,因此我们将选择 binary class mode。

输出

Convolutional Neural Network

在运行上面的单元格(预处理训练集)之后,我们将在上面图片中的输出中看到,我们确实导入并用数据增强进行了预处理; 8000 张图像属于 2 个类别,即狗和猫。

预处理测试集

在完成训练集预处理后,我们将继续预处理测试集。我们将再次使用 ImageDataGenerator 对象来对测试图像应用变换,但在这里我们不会应用与上一步相同的变换。但是,我们需要像之前一样缩放像素,因为 CNN 的未来 predict 方法必须应用与训练集相同的缩放。

这里 test_set 是我们在笔记本中导入的测试集的名称,然后我们确实使用我们的 test_datagen,它只会应用于测试集图像的像素。然后我们调用相同的 flow_from_directory 函数来访问目录中的测试集。然后我们需要与上一步相同的 target_size、batch_size 和 class_mode。

输出

Convolutional Neural Network

我们可以从上面运行“预处理测试集”单元格后得到的图片中看到,2000 张图像属于 2 个类别。我们只应用了特征缩放,而不是图像增强。

第二部分:构建 CNN

在第二部分,我们将一起构建卷积神经网络,更具体地说,是整个神经网络的架构。所以,它实际上会与我们的人工神经网络的开始方式相同,因为卷积神经网络仍然是一系列层。

因此,我们将使用相同的类(Sequential 类)来初始化我们的 CNN。

初始化 CNN

这是第一步,我们不仅会调用 Sequential 类,还会实际创建 cnn 变量,该变量将代表这个卷积神经网络。并且这个 cnn 变量将再次作为 Sequential 类的实例创建,允许我们创建作为层序列的人工神经网络。

首先,我们需要调用 TensorFlow,它有一个快捷方式 tf,从中我们将调用 Keras 库,从中我们将访问模型模块,或者我们可以说从中我们将调用 Sequential 类。

在此之后,我们将一步一步地使用 add 方法添加不同的层,无论是卷积层还是全连接层,最后是输出层。所以,我们现在将开始成功使用 add 方法,从步骤 1:卷积开始。

步骤 1:卷积

我们首先获取 cnn 对象或卷积神经网络,从中我们将调用 add 方法来添加我们的第一个卷积层,该层将是某个类的对象,即 Conv2D 类。这个类,就像允许我们构建全连接层的 Dense 类一样,属于同一个模块,即 Keras 库的 layers 模块,但这次是 TensorFlow。

在类中,我们将传递三个重要参数,它们是:

  • 第一个参数是 filters,即我们希望应用于图像进行特征检测的特征检测器的数量。
  • kernel_size 确切地说是特征检测器的大小,即行的数量,也是列的数量。
  • 第三个是 activation,但在这里我们不会保留激活函数对应的激活参数的默认值,因为只要我们没有到达输出层,我们就希望得到一个整流器激活函数。这就是为什么我们将再次选择 ReLU 参数名称,因为它对应于整流器激活函数。
  • 最后是 input_shape 参数,因为它必须指定输入的输入形状。由于我们处理的是彩色图像,因此 input_shape 将是 [64, 64, 3]。

步骤 2:池化

接下来,我们将进行池化,更具体地说,如果我们谈论的是,我们将应用最大池化,为此,我们将再次获取 cnn 对象,从中我们将调用我们的新方法。由于我们将池化层添加到我们的卷积层,所以我们将再次调用 add 方法,并且在其中,我们将创建一个最大池化层对象或某个类的实例,该类称为 MaxPool2D 类。在类中,我们将传递 pool_sizestrides 参数。

添加第二层

现在我们将添加第二层,为此我们再次需要经历卷积和池化层,就像我们在上一步所做的那样,但在这里我们需要更改 input_shape 参数,因为它只在我们添加第一层以自动将第一层连接到输入层时输入,这会自动添加输入层。

由于我们在这里添加第二层卷积层,因此我们可以删除该参数。所以,我们准备进入步骤 3。

步骤 3:展平

在第三步中,我们将把这些卷积和池化的结果展平成一维向量,该向量将成为全连接层神经网络的输入,方式与我们在上一节中类似。我们将再次从获取我们的 cnn 对象开始,从中我们将调用 add 方法,因为我们将创建展平层的方式是再次创建 Flatten 类的实例,这样 Keras 将自动理解这是所有这些卷积和池化的结果,将被展平成一维向量。

所以,我们只需要指定我们要应用展平,并且为此我们将不得不再次通过 TensorFlow 的 Keras 库调用 layers 模块,从中我们将调用 flatten 类,并且我们不需要在其中传递任何参数。

步骤 4:完全连接

在步骤 4 中,我们处于与之前构建全连接神经网络完全相同的情况。所以,我们将向那个展平层添加一个新的全连接层,它本质上是一个一维向量,将成为全连接神经网络的输入。为此,我们将再次从获取 cnn 神经网络开始,从中我们将调用 add 方法,因为现在我们要添加一个新层,它是一个全连接层,属于 tf.keras.layers。但这次,我们将选择 Dense 类,然后传递 units(即我们希望在此全连接层中拥有的隐藏神经元数量)和 activation function 参数。

步骤 5:输出层

在这里,我们需要添加最终的输出层,该层将与前一个隐藏层完全连接。因此,我们将再次以与上一步相同的方式调用 Dense 类,但会更改输入参数的值,因为输出层中的单元数肯定不是 128。由于我们正在进行二元分类,因此它实际上是一个神经元来编码这个二元类别为“猫”或“狗”。对于激活层,建议使用 sigmoid 激活函数。否则,如果我们进行多类分类,我们将使用 SoftMax 激活函数。

第三部分:训练 CNN

在前面的步骤中,我们构建了大脑,它包含了人工智能的眼睛,现在我们将通过在所有训练集图像上训练 CNN 来使这个大脑变得智能,同时,我们将在 epoch 内在测试集上评估我们的模型。现在我们将训练我们的 CNN 超过 25 个 epoch,并且在每个 epoch,我们将实际查看我们的模型在测试集图像上的表现。这是一种与之前不同的训练方式,因为我们总是分开训练和评估,但这里我们将同时进行,因为我们正在进行一些特定的应用,即计算机视觉。

编译 CNN

现在我们将编译 CNN,这意味着我们将将其连接到一个优化器、一个损失函数和一些指标。由于我们再次进行二元分类,因此我们将以与编译 ANN 模型完全相同的方式编译我们的 CNN,因为事实上,我们将再次选择 adam 优化器来执行随机梯度下降,以更新权重以减小预测和目标之间的损失误差。然后我们将选择相同的损失,即 binary_crossentrophy 损失,因为我们正在执行完全相同的任务——二元分类。然后对于指标,我们将选择 accuracy 指标,因为它是衡量分类模型性能的最相关方式,这正是我们的 CNN 的情况。

所以,我们将获取我们的 cnn,然后调用 compile 方法,该方法将以优化器、损失函数和指标作为输入。

在训练集上训练 CNN 并评估测试集

编译后,我们将在训练集上训练 CNN,同时在测试集上进行评估,这与之前的情况不完全相同,但会有些相似。基本上,前两个步骤始终相同,即在第一步中,我们将获取 cnn,然后在第二步中获取 fit 方法,该方法将在训练集上训练 cnn。在其中,我们将传递以下参数:

  • 第一个参数是 set,当然是数据集(training set),我们将在其上训练我们的模型,该参数的名称只是 X,在第一部分创建。
  • 第二个参数与我们之前所做的有所不同。所以,它当然与我们不仅在训练集上训练 CNN,而且同时在测试集上评估它这一事实有关。而这正是我们的第二个参数所对应的,所以我们将在这里指定 validation data(test set),即我们想评估我们 CNN 的集合。
  • 最后是 epochs 参数,即 epoch 的数量。我们选择 25 个 epoch 来收敛训练集和测试集上的准确率。

输出

Convolutional Neural Network

从上面给出的图片可以看出,我们最终在训练集上达到了 **90%** 的最终准确率,在测试集上达到了 **80.50%** 的最终准确率。再次提醒,如果我们没有在第一部分进行图像增强预处理,我们将在训练集上达到大约 **98%** 甚至 **99%** 的准确率,这显然表明了 **过拟合**,以及在测试集上较低的准确率,大约 **70%**。这就是为什么我们强调图像增强是绝对基础的原因。

第四部分:进行单独预测

在第四部分,我们将进行一次单独的预测,实际上就是将我们的模型部署到 single prediction 文件夹中的两张单独图像上,我们的模型将分别识别狗和猫。所以,基本上,我们将把我们的 CNN 模型部署到这些单独的图像上,并希望我们的 CNN 能够成功预测狗和猫。为此,我们将开始导入 NumPy。接下来,我们将导入一个新模块,我们实际上之前已经导入过,即我们从 Keras 库的 preprocessing 模块的 image 子模块中导入了 ImageDataGenerator。事实上,我们现在要导入的是 image 模块。但是因为我们专门从该模块导入了一些东西,嗯,我们需要再次导入它。

所以,我们将从 Keras 开始,它将帮助我们访问 preprocessing 模块,从中我们将进一步导入 image 模块。接下来,当然是加载我们想要部署模型的单张图像,以预测其中是猫还是狗。我们将创建一个新的变量,即 test_set,它将通过从同一个 single prediction 文件夹加载我们想测试模型的图像来初始化。这可以通过首先调用 image 子模块来完成,从中我们将调用 load_img 函数,在该函数中,我们将简单地传递两个参数,即第一个参数是 path,指定我们想选择的特定图像,它将真正指向 test_set 图像变量,第二个参数起着至关重要的作用,因为它与将成为 predict 方法输入的图像有关,必须与训练期间使用的图像大小相同。

由于我们实际上将图像大小调整为 (64, 64) 的目标大小,无论是对于训练集还是测试集,并且在构建具有相同输入形状的 CNN 时也再次指定了它,所以我们将要处理的图像大小,无论是用于训练 CNN 还是调用 predict 方法,都必须是 (64, 64)。所以,为了在这里指定它,我们将输入第二个参数,即 target_size

但是,为了使我们的第一个 test_set 图像被 predict 方法接受,我们需要将图像格式转换为数组,因为 predict 方法期望其输入是 2D 数组。我们将借助 image 预处理模块的另一个函数来实现这一点,即 img_to_array 函数,它实际上将 PIL 图像 实例转换为 NumPy 数组,这正是 predict 方法期望的数组格式。我们将再次使用我们的 image 子模块,从中我们将调用 img_to_array(),并且在其内部,它将接受 PIL 格式的 test_size 图像,我们希望将其转换为 NumPy 数组格式。

由于 predict 方法必须以与训练期间使用的格式完全相同的格式调用,所以如果我们回到训练集和测试集的预处理阶段,我们创建了图像的批次。因此,我们的 CNN 不是在任何单个图像上训练的,而是在图像批次上训练的。所以,由于我们有一个额外的批量维度,并且我们要对单个图像进行部署,那么这个单个图像仍然必须在批次中,即使我们批次中只有一个图像,它也必须在这个批次中,这样我们的 CNN 模型的 predict 方法才能识别批次作为那个额外的维度。

接下来,我们将添加一个额外的维度,它将对应于包含该图像的批次。这可以通过更新我们的 test image 来简单地完成,添加一个对应于批次的额外维度。实现这一点的方法是使用 NumPy,因为 NumPy 数组很容易操作,所以我们将首先调用 NumPy,然后调用允许添加虚假维度或可以说对应于批次的维度的函数,该函数称为 expand_dims 函数,在该函数中,我们将输入我们要添加这个批次维度图像,然后添加一个额外的参数,即我们要在哪里添加这个维度,使得批次的维度总是我们总是提供第一批图像的第一个维度,然后每个批次中的不同图像。所以,将批次作为第一个维度是很自然的,并且要指定这一点正是我们需要输入的第二个参数,即 axis,我们必须将其设置为零。这就是为什么我们要添加到图像的批次维度将是第一个维度。

在此之后,我们可以调用 predict 方法,因为事实上,那个 test set 图像不仅是正确的 NumPy 数组,而且它具有对应于批次的额外维度,它具有 predict 方法所期望的完全正确的格式。

因此,我们可以创建一个新变量,称为 result,因为它将实际预测我们的 CNN 模型对测试图像的预测。这里我们不称之为 prediction,因为它只会返回零或一,这就是为什么我们需要编码以便表示 0 代表猫,1 代表狗。所以,我们将调用我们的第一个 result 变量,它实际上将是我们从 predict 方法调用中获得的输出。在 predict 方法内部,我们将传递 test_image,它现在具有 predict 方法期望的正确格式。

为了弄清楚 0 和 1 分别代表什么,我们将调用 training_settest_set,然后从中我们将进一步调用 class_indices,通过打印它,我们将获得正确的 class_indices。通过这一点,我们确实得到了狗对应于 1,猫对应于 0。

最后,当对这两张单张图像进行两次单张预测时,我们将用 if 条件结束。因为我们已经知道 result 包含批次中的结果,因为它是在一个批次中的测试图像上调用的,所以 result 也有一个批次维度,我们将访问这个批次。

在此之后,在批次中,我们将访问批次的第一个元素,它对应于 cat_or_dog_1 图像的预测。由于我们处理的是单张图像,所以需要一次预测,要获得它,我们需要访问批次的索引零,一次获取唯一预测,它具有 [0] 索引。所以,这就是我们如何通过首先访问批次然后访问批次的单个元素来获得预测,如果该预测等于一,那么我们已经知道它对应于狗,然后我们将创建一个名为 prediction 的新变量,并将该 prediction 变量设置为 dog。同样,在 else 条件下,如果 result prediction 等于 1,那么 prediction 将是 cat。现在我们将通过简单地打印 prediction 来结束。

输出

Convolutional Neural Network

我们可以看到我们的卷积神经网络预测图像中是一只狗。所以,可以得出结论,我们的第一次测试已成功通过。

现在我们将检查另一张猫的图像,为此我们需要将模型部署到这张单张图像上,并检查我们的 CNN 确实返回了猫。要做到这一点,我们需要在这里更改名称,即 cat_or_dog_2.jpg,然后再次播放该单元格,点击 Run 按钮。

输出

Convolutional Neural Network

所以,现在很清楚,我们的 CNN 模型在控制台输出中成功预测了猫。因此,我们的 CNN 得到了所有正确的答案。


下一个主题循环神经网络