[关闭]
@sambodhi 2018-11-15T19:37:18.000000Z 字数 10382 阅读 6744

使用 keras 进行图像分类

作者 | Rising Odegua
译者 | Sambodhi
编辑 | Tina

AI 前线导读:这是一篇实战类的文章,旨在向读者介绍计算机视觉的外围应用:图像分类问题。现在我们翻译了 Rising Odegua 撰写基于 keras 进行图像分类的文章。在本文,作者介绍了如何在数据集较小的情况下,如何使用 keras 进行图像分类。


上个周末,我经历了一场思想狂潮。

如果我有一个图像数据集,它非常非常小,我想自己捕获、并希望教会计算机能够识别或区分一些指定的类别,我应该怎么办呢?

假设我有几千张图像,我想训练一个模型,能够从一个类别自动检测出另一个类别。但是,我只有这么少的数据,那我能不能够训练出一个深度神经网络来成功地对这些图进行像分类呢?

经过研究,我发现,人们在计算机视觉领域中遇到的常见情况是:用很少的数据来训练深度神经网络。

让我们面对这一现实:并非每个人都可以访问 Google 或 Facebook 这样的大数据,而且有些数据很难获得。

但我也发现,这种问题的解决方案其实非常简单。今天,我将带领你们学习如何使用那些较小的图像数据集来训练卷积神经网络( Convolutional Neural Network ),前提是你需要有一个很好的分类器,它的分类正确率大约为 81%。

在下一篇文章中,我将展示一种非常强大的技术,称为迁移学习( transfer learning ),它能够将分类正确率提高到 95% 左右。

你现在就可以开始收集数据了。

我将使用 Kaggle 平台上现有的狗和猫的数据集。是的,我是挺懒的。我无法得到自己的数据。

Dogs vs. Cats Redux: Kernels Edition
https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/data

Kaggle 是全球数据科学和机器学习从业者的大本营。他们主办了一些最大的数据科学竞赛,这是一个获取开源数据以及向获奖专家学习的宝地。

我觉得我应该这样说:如果你来自非洲,我们有一个新平台,叫 Zindihttp://zindi.africa/ ),和 Kaggle 很像,但非常适合非洲社会。它包含了从非洲企业和组织收集来的数据集。这是防止人工智能出现偏见的一大步,而 Zindi 就是一个很好的平台。

让我们回到获取数据这个话题。在 Kaggle 的这一页上:
https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/data
这里我们有两个选择,我们可以下载数据到我们自己的电脑上来训练模型,或者我们可以使用 Kaggle 内核,它为我们提供更多的算力,让我们可以使用 GPU,以及几乎所有预先安装的用于机器学习和深度学习的库。

我不知道你怎么想的,但我宁愿选择 Kaggle 内核,除非我有 GPU。

如果你能负担得起 GPU 的费用,或者你是一名玩家,已经拥有 GPU,并且你想在自己的电脑上训练模型,只是因为你太喜欢鼓捣电脑了,那么就你可以按照这些教程来设置属于你自己的深度学习工作站:

如果你想偷懒,或者买不起 GPU,那么就去 Kaggle 启动这个引擎吧。

点击这个连接:https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/kernels ,我们打开这个页面。

Kaggle 上的 cats vs dogs 内核

注意:当你浏览 Kaggle 竞赛页面时,你最好阅读一下数据描述。

接下来,单击 Kernels(内核),然后单击右上角的蓝色按钮 New Kernel,系统会问你选择内核的类型( Select Kernel Type )。选择 Notebook,这样我们就可以做一些互动编程。

如果你不知道什么是 notebook,或者需要提高 notebooking 技能,你可以参阅有关 Jupyter Notebook 的网站,这两个都很棒:

单击 Notebook,系统会创建一个新的私有内核,并自动将猫狗数据集添加到你的文件路径中,当然是在云端。

给你这个内核起个名字,然后给它施加超能力( GPU ),这样计算时间就会更短。

你的 Notebook 打开后就像这样子的。

现在,我们的内核已经准备就绪,让我们导入一些将要使用的库。键入以下代码,然后按下 shift Enter 来运行单元格。

导入以下库
  1. cv2,也称为 OpenCV,是一种可用于 Python 和许多其他高级编程语言的图像和视频处理库。它用于各种图像和视频分析,如面部识别和检测、车牌识别、照片编辑、高级机器人视觉、光学字符识别等等。在本教程中,我们将使用它来读取和调整图像大小。

  2. NumPy 是 Python 中最流行的数学库。有了这个库,大型、多维数组和矩阵的工作和计算变得简单快捷。它有大量的高级数学函数来操作这些数组。

  3. Pandas 是一个为 Python 编程语言编写的软件库,用于数据处理和分析。值得一提的是,Panda 提供了用于操作数值表和时间序列的数据结构和操作。

  4. Matplotlib 是 Python 的绘图库。可用于绘制线条、条形图、图形、直方图甚至显示图像。

  5. %matplotlib inline 是一种命令,作用是使 Notebook 出现我们的图表。

  6. os 是一个内置的 Python 包,用于访问你的计算机和文件系统。它可用于显示目录中的内容,创建新文件夹,甚至还可以删除文件夹。

  7. random 可以帮助我们创建随机数,这些随机数将在我们拆分或者洗牌数据集时使用。

  8. gc 是垃圾收集器的简称,是手动清理和删除不必要变量的重要工具。我们将在 Kaggle 内核上积极使用它,因为我们正在处理图像数据集,因此分配给我们的可用内存可能已经满了。

在导入必要的库之后,我们将图像读入到内存中。数据作为 Zip 文件的形式存储在内核中。

我们的数据

我们可以看到以下三个文件:

  1. sample submission.csv
    这是一个 csv(comma seperated value,逗号分隔值)文件,用于在训练模型并在给定的测试文件上对其进行测试之后进行提交。由于这次比赛已结束,我们无法提交,因此,我们将忽略这个文件。

  2. test.zip
    这个文件包含了我们将在训练后要测试模型的图像,以了解模型是否已经学会区分猫和狗。

  3. train.zip
    这个文件是我们模型的 “原材料”。它包含了我们要用来教会模型掌握狗或猫长什么样的数据。

现在,要访问我们的训练图像,我们将使用之前导入的 os 包。

注意:Kaggle 上的所有数据文件路径都以根目录../input 开头。例如,这个内核中训练数据的文件路径为../input/train。

导入我们的图像。
  1. 在这里,我们创建一个训练和测试数据的文件路径。
  2. 这里,我们创建了两个变量:train_dogstrain_cats。一个是所有的狗图像,另一个是所有的猫图像。我们编写了一个列表理解(list comprehension),它使用 os.listdir() 命令来获取训练数据 zip 文件中的所有图像,并在其名称中检索带有 dog 的所有图像。

    我们对猫的图像也是如此操作。

  3. 我们也得到了测试图像。

  4. 训练数据集共包含 25000 张图像,但由于我们正在试验使用一个小型数据集,而且显然我们只能使用很少的算力,从这两个类中,我们只提取 2000 个图像。

    • 2000 张狗图像和 2000 张猫图像,组成了 4000 张图像的训练数据集。
    • 因此,我们从 train_dogstrain_cats 中获取前 2000 个图像,然后将它们连接到一个名为 train_img 的训练集中。
  5. 非常重要! 我们随机洗牌 train_imgs

  6. 在这里我们做一些清洗。你可能已经注意到,现在我们有了 train_imgs,就意味着train_dogstrain_cats 变量是无用的,占用了不必要的空间。如果不删除它们的话,当我们开始训练模型时,内存可能很快就耗尽了。

让我们在 train_imgs 查看一些图像。

让我们看看一些可爱的狗。
  1. 从 matplotlib 导入图像绘图模块。
  2. 运行 for 循环,绘制 train_imgs 中的前三个图像。

记住它的随机列表,但幸运的是,当我运行代码时,前三个图像是由两只狗和一只猫组成的,并注意到它们有不同的维度。

绘制的最后两个图像

在下一个代码块中,我们将使用 cv2 模块调整图像的大小。

首先,我们生命要使用的新维度。在本文中,我使用的是 150x150 来表示高度和宽度,还有 3 个通道。

声明一些重要的变量。

彩色图像由 3 个通道组成,即红色、绿色和蓝色像素值三组数组。我们可以使用 1 个通道来读取灰度格式(黑白)的图像。

现在,我们编写一个小函数,来帮助我们读取图像并调整为上述高度和宽度。

辅助函数
  1. 创建一个新的变量 X,它将包含新的训练集和 yy 用来保存训练标签。(如果图像是狗,则为 1;如果是猫则为 0)
  2. 我们一张一张地读取图像,然后使用 cv2 命令调整这些图像的大小。
  3. 如果图像是狗,我们就将 1 追加到 y ;如果是猫,就将 0 追加到 y

现在,让我们调用函数并处理图像。

  1. X, y = read_and_process_image(train_imgs)

X 现在是图像像素值数组的列表,y 是标签列表。让我们预览 X 中的第一张图片。

是的,这就是你的电脑所说的狗。

我的朋友是一条狗的形象。或者我们可以说我们的电脑叫什么狗。等一下,我怎么知道它是一条狗呢?让我们看一下标签列表 y 中的响应值。

记住1代表狗,0代表猫。

要记得我们说过,1 代表狗,0 代表猫。

还是不相信我,对吧?好,让我们绘制 X 的前 5 个数组。我们不能用上面提到的 matplotlib.imagempimg 模块在 X 中绘制图像,因为这些现在是像素数组,不是原始的 jpg 文件。因此,我们应该使用 imshow() 命令。

绘制前5个图像

现在,我们确信训练集已包含适当的狗和猫的图像,来看一下标签。要记住我们前面说过,总共有 4000 个图像(2000 条狗和 2000 只猫),因此我们的标签列表 y 应该包含 2000 个 1 和 2000 个 0。让我们绘制出来并确认一下。

  1. 我们导入了 seaborn 包,这是另一个构建在 matplotlib 之上的绘图包,它提供了非常漂亮的绘图。
  2. 我们对代码有洁癖,因此,我们删除了 train_imgs,因为它已经被转换为数组并保存在 X 中。
  3. X 和 y 当前是类列表(python 数组的列表),我们将它们转换为 numpy 数组,这样我们就可以将它提供给模型。
  4. 绘制彩色图表来确认 y 标签变量中的类数。

标签的数量柱状图

棒极了!现在我们有了 2000 个猫和狗的类数。让我们继续。

接下来,检查数据的形状。记得要经常检查并确认数据的形状,这点非常重要。

数据的形状。

我们可以看到,我们的图像是 4 阶张量,或者可以说是一个大小为 400x150x150x3 的 4 维数组,分别对应于批大小、高度、宽度和通道。

图像数组的形状对于我们要构建的 keras 模型很重要。该模型将一个数组(高度、宽度、通道)作为输入。

现在,我们的数据(X,y)已准备就绪,可以开始训练。但首先,我们必须做一些非常重要的事,那就是将我们的数据拆分成训练集和验证集。在开始训练模型之前,这是最重要的事情之一。

对于数据的拆分,我们将使用 python 中的一个名为 sklearn 的流行机器学习包中的一个函数,这个函数非常方便。

  1. sklearn 中导入 train_test_split
  2. 我们告诉函数,我们希望将 20% 的数据分配给验证集,另外 80% 的数据分配给训练集。
  3. 这里我们打印新的训练集和验证集的形状。

接下来,我们将声明一些重要的变量,这些变量将在训练模型时使用。

  1. 是的,我们还在清洗数据。
  2. 获取训练集和验证集的长度。

现在是时候创建我们的模型了。

我们将使用卷积神经网络(convnet)来训练模型。卷积神经网络是目前计算机视觉问题的标准。在任何图像问题中,它们总是优于其他类型的神经网络。

你还不熟悉卷积神经网络吗?在文末中有一些不错的网址,你可以从中了解关于卷积神经网络的信息。

我们将使用 KERAShttps://keras.io/)来创建模型。

据 Wikipedia:
Keras 是一个用 Pythono 编写的开源神经网络库。它能够运行在 TensorFlow、Microsoft Cognitive Toolkit 或 Theano 之上,旨在实现深度神经网络的快速实验,注重用户友好、模块化和可扩展性。

首先,我们导入将要使用的必要 keras 模块:

  1. 这里,我们导入 keras layers 模块,它包含了用于深度学习的不同类型的层,例如:

    • Convolutional layer (卷积层,主要用于计算机视觉)
    • Pooling layer (池化层,也用于计算机视觉)
    • Recurrent layer (递归层,主要用于序列和时间序列建模)
    • Embedding layers (嵌入层,主要用于自然语言处理)
    • Normalization layers (归一化层)
    • 还有许多其他
  2. 这里我们导入 keras 模型,它包含两种类型:

    • 我们将在本教程使用的序列模型;
    • 带有函数 API 的模型。
  3. 这里我们导入 keras optimizer(优化器),这是一个包含不同类型的反向传播算法的模块,用于训练模型。其中一些优化器是:

    • sgd (stochastic gradient descent,随机梯度下降算法)
    • rmsprop (root mean square propagation,均方根传播)
    • Adams
    • Adagrad
    • Adadelta
  4. 这里,我们导入在处理小型数据集时使用的最重要的函数之一(ImageDataGenerator)。下面详细介绍。

现在,让我们创建网络架构,我们将遵循一种流行、有效和简单的架构,名为 VGGnet

VGGnet:参见 Very Deep Convolutional Networks For Large-Scale Image Recognition
https://arxiv.org/pdf/1409.1556

网络架构就是我们排列卷积层的方式。

我们将使用一个小型的 vggnet,但是你可以看到,我们的过滤器的大小会随着向下层而增加。

32 → 64 →128 →512 — 最后一层是 1。

  1. 这里,我们创建一个序列模型,告诉 keras 将所有层按序列堆叠。
  2. 在这里,通过调用我们创建的模型上的 .add() 函数来创建第一层,并传递我们想要的层类型:Conv2D 层。第一层称为 input player(输入层),有一些重要的参数需要设置。

    • filter size[32](过滤器大小): 这是输出尺寸的大小(即卷积中输出过滤器的数量)
    • kernel_size[3,3](内核大小):指定 2D 卷积窗口的高度和宽度。
    • activation[‘relu’] (激活函数):我们选择一种称为非线性的激活函数,供神经网络使用。ReLU(Rectified Linear Unit,线性修正单元)是目前最常用的激活函数,其变体有 leaky ReLU 和 eLU。
    • input shape [150,150,3](输入形状):还记得我们调整图像大小的尺寸吗?150x150,对吧?我们在这里传递,包括 3 的通道。

    我们没有传递 4000 的第一个维度,因为这是批维度。

  3. 这里,我们添加 MaxPool2D 层。 它的作用是减小输入特征和空间尺寸,有助于减少网络中参数的数量和计算量,从而减少过拟合。

当模型记忆训练数据时,就会发生过拟合( Overfitting )。该模型在训练时表现出色,但在测试时会失败。

  1. 这里我们添加一个 Flatten 层。 conv2D 层提取并学习空间特征,然后在归并后将其传递给 dense 层, 这是 Flatten 层的工作。
  2. 这里我们添加一个值为 0.5 的 Dropout 层。在神经网络中,随机丢弃一些层,然后用减少的网络来学习。通过这种方式,网络在单个层上学会了独立和不可靠。更重要的是,它有助于过拟合。

    0.5 的意思是随机丢弃一半的层。

  3. 随后一层的输出大小为 1,另一个不同的激活函数称为 sigmoid。这是因为我们试图检测图像是狗还是猫。即我们希望模型输出图像是狗而非猫的概率,这意味着我们需要概率分数,其中较高的值意味着分类器认为图像是狗,较低的值则认为它是猫。

    sigmoid 非常适合这种任务,因为它接受一组数字,并返回 0 到 1 范围内的概率分布。

我们可以通过调用模型对象上的 keras 函数 .summary() 来预览卷积神经网络的布局和参数大小。

我们可以看到我们想要训练的参数的数量(300 万以上)和不同层的总布局。

下一步是编译模型。

我们将三个参数传递给 model.compile() 命令。

  1. Loss['binary_crossentropy']:我们指定一个损失函数,我们的优化器会将其最小化。在这种情况下,由于我们处理的是两类问题,我们使用 binary crossentropy loss(二进制交叉熵损失函数)。
  2. 还记得我们之前定义的 优化器 吗?我们将使用其中一个叫做 rmsprop 的优化器。这并非一个固定选择,它是 超参数调优 过程的一部分。这可能就是世界级模型和幼稚模型之间的区别。
  3. 在这里,我们指定在训练之后,测量模型性能要使用的度量标准。我们想知道模型的表现是否良好。

因为我们做的是分类问题,因此正确率指标 (acc) 是一个不错的选择。

注意:用于测量模型性能的度量指标取决于你正在处理的问题类型。。


最后,在开始训练模型之前,我们需要执行一些归一化。也就是将我们的图像像素值进行缩放,使其具有单位标准偏差且均值为 0。

我们将在 keras 使用一个名为 ImageDataGenerator 的重要模块,当我们在训练期间将图像输入模型时,这个模块会执行一些重要的功能。

但请告诉我什么是 ImageDataGenerator?

据 keras 作者 Francois Chollet 的说法,Keras ImageDataGenerator() 可以让我们快速设置 python 生成器,自动将图像文件转换为预处理的张量,可在训练期间直接输入到模型中。它可以轻松地执行以下功能:

  1. 将 JPEG 内容解码为 RGB 像素网格。
  2. 将它们转换为浮点张量。
  3. 将像素值(0 到 255 之间)重新缩放到 [0,1] 区间(神经网络在归一化数据方面表现更好)。
  4. 帮助我们轻松地增强图像。(因为我们训练一个小型数据集,因此我们将使用一个重要的特性。)

让我们创建 ImageDataGenerator 对象。我们将创建两个生成器,一个用于训练集,另一个用于验证集。

  1. 我们将重放缩(rescale)选项传递给 ImageDataGenerator 对象。rescale=1./255 选项是一个非常重要的参数。它将图像像素值进行归一化,使其均值为 0,标准差为 1。它可以帮助模型有效地学习和更新其参数。
  2. 第二组选项是图像增强选项。他们告诉 ImageDataGenerator 随机地对 Image(图像)应用一些变换。这将有助于增强我们的数据集并改进泛化。
  3. 这里,我们还为验证集创建了一个 ImageDataGenerator 对象。注意,我们不做数据扩展,只执行重放缩

现在我们有了 ImageDataGenerator 对象,让我们通过传递训练集和验证集来创建 python 生成器。

  1. 我们在上面创建的数据生成器上调用 .flow( ) 方法,并传入数据和标签集。
    X_trainy_train 用于训练,X_valy_val 用于验证。
    批大小告诉数据生成器一次只接受指定的批(在我们的例子中是 32)图像。
  2. 现在,我们通过在模型上调用 .fit( ) 方法,并传递一些参数来训练网络。
    第一个参数是训练集 ImageDataGenerator 对象 [train_generator]。
  3. 这里我们指定每个轮数(epoch)的步数。这告诉我们的模型在对损失函数进行梯度更新之前需要处理多少个图像。
    总共 3200 个图像除以批大小 32,将给我们 100 步。这意味着我们将在整个训练集中,一次性地对模型进行总计 100 次梯度更新。
    总共 3200 张图片除以批次大小 32 将给我们 100 步。这意味着我们将通过整个训练集对我们的模型进行总共 100 个梯度更新。
  4. 一个轮数是一个完整的周期或遍历整个训练集。在我们的例子中,当我们按照 steps_per_epoch 参数指定进行 100 次梯度更新时,会完成一个轮数。
    Epochs=64,表示我们要遍历训练数据 64 次,每次进行 100 次梯度更新。
  5. 我们传入验证数据生成器。
  6. 这里,我们也设置了步长。我将使用与上述相同的步长。

仅仅经过 64 个轮数之后,正确率就到达了约 80%。

模型训练的截图。

对于我们的模型来说是很不错了,因为我们是从零开始训练的,而且数据非常少。

增加轮数并使用批大小和优化器等一些超参数进行训练模型,也许有助于提高表现。

我把这个问题留给你们去探究。

接下来,保存我们的模型,使用如下所示的简单 Keras 函数,这样我们就可以在任何时候重用它,而不必在重新运行 notebook 时再次进行训练。

  1. #Save the model
  2. model.save_weights('model_wieghts.h5')
  3. model.save('model_keras.h5')

我们将绘制一些图表,显示训练集和验证集的正确率和损失,看看是否能得到一些见解。

  1. 在训练 keras 模型之后,它总是计算并保存我们在名为 history 的变量中编译模型时指定的度量。我们可以提取这些值并将其绘制出来。
    注意:history 对象包含训练期间发生的所有更新。
  2. 在这里,我们只需从 “acc” 列表中的值数量中获取 epoch 的大小。
  3. 在这里,我们根据轮数的大小来绘制正确率。
  4. 在这里,我们根据轮数的大小来绘制损失。

根据轮数绘制正确率。

那么,我们能够从这幅图得到什么样的信息呢?

  1. 首先要注意的是,我们并没有发生过拟合,因为训练和验证的正确率非常接近,并相互跟随。
  2. 我们还可以注意到,随着轮数的增加,正确率会不断提高,这让我们有一种直觉:增加轮数的大小可能会给我们带来更高的正确率。

根据轮数绘制损失

我们仍然没有发生过拟合,因为训练和验证损失都在接近下降的趋势,就像上面的正确率曲线的那副图一样。如果我们增加轮数的大小,损失可能会更低。

如此一来,你就有了一些直觉。现在试着增加轮数的大小并使用一些超参数看看会发生什么。

在结束本教程之前,我们将在测试集中的一些图像上测试模型。

用于预处理测试图像的代码

我们执行与训练集和验证集相同的预处理。

  1. 我们读取并将测试集中的前 10 个图像转换为数组列表。
    注意: y_test 将为空,因为测试集没有标签。
  2. 我们将数组列表转换为一个大的 numpy 数组。
  3. 我们创建一个测试 ImageDataGenerator,并仅执行归一化。
    注意:我们不会扩充测试集。

现在,我们将创建一个简单的 for 循环,它迭代生成器中的图像以进行预测。然后我们将结果绘制出来。

  1. 创建一个列表来保存我们要生成的标签。
  2. 我们设置要绘制图像的图形大小。
  3. 这里,我们通过在训练的模型上调用 .predict() 方法,对 ImageDataGenerator 提供的特定图像进行预测。
  4. pred 变量是模型确定当前图像是狗的概率。
    因为我们给狗的图像设置标签为 1,一个高概率——至少大于平均值 0.5,就意味着模型非常有信心地任务图像是狗,否则就是猫。
    因此,我们只需创建一个 if-else 语句,如果概率大于 0.5,则将字符串 “dog” 追加到 text_label,否则就把 “cat” 追加到 text_label 中。
    我们这样做,是为了在绘制图像时为图像添加标题。
  5. 这里,我们添加一个子图,这样我们就可以绘制多个图像。
  6. 这里,我们将预测的类作为标题添加到图像图中。
  7. 最后将图像绘制出来。

让我们看看模型在之前未见过的图像上是如何执行的。

嗯,模型从五张图片中,出了一个错误。但我并没有说过这模型是最好的…… 至少现在还没有说过。

本文确实篇幅很长,但我认为写出来还是值得的。在下一个教程中,我们将通过使用与训练的网络来改进模型,使其正确率达到 96% 左右。

敬请期待,祝你好运!

这是是我的 notebook 上关于 Kaggle 教程的链接:http://kks.me/aU7ea

下面是 CNN 和 keras 的精彩文章和书籍:


原文连接:
https://towardsdatascience.com/image-detection-from-scratch-in-keras-f314872006c9

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注