65.9K
CodeProject 正在变化。 阅读更多。
Home

为 iOS 上的 AI 图像分类准备数据

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2020年8月31日

CPOL

7分钟阅读

viewsIcon

6936

downloadIcon

80

在本文中,我们将开始为这个新的自定义模型准备数据,该模型稍后将使用Create ML框架进行训练。

引言

深度神经网络在图像分类等任务中非常出色。十年前需要花费数百万美元和一个完整的研究团队才能完成的结果,现在对于拥有一个半像样的GPU的任何人来说都很容易获得。然而,深度神经网络有一个缺点。它们可能非常笨重和缓慢,因此在移动设备上运行效果不佳。幸运的是,Core ML提供了一个解决方案:它使您能够创建在iOS设备上运行良好的精简模型。

在本系列文章中,我们将展示两种使用Core ML的方法。首先,您将学习如何将预先训练的图像分类器模型转换为Core ML模型,并在iOS应用中使用它。然后,您将训练自己的机器学习(ML)模型,并使用它来制作一个“不是热狗”应用程序——就像您可能在HBO的《硅谷》中看到的那样。

到目前为止,我们已经处理了一个已训练的ML模型——将其从ResNet转换为Core ML,然后在示例iOS应用程序中使用它。为了在图像中检测热狗,我们将构建自己的模型。

构建热狗检测模型需要什么?

要构建一个能够区分热狗和“不是热狗”的图像分类模型,您需要三样东西:

  • 包含热狗的照片
  • 不包含热狗的照片
  • 用于在这些照片上训练模型的机制

在典型的ML项目中,最耗时的任务是收集和准备数据。从头开始训练神经网络需要海量数据——至少数百个,但很可能是数千个记录,数百万甚至数十亿个记录并不少见。我们在上一篇文章中使用的ResNet模型是在ImageNet 2012分类数据集的128万张图像上训练的。所有这些图像都必须获取、收集并手动标注其中1000个标签之一。

这种对数据的“饥渴”如果不是因为以下两点,会造成很多麻烦:

  • 从预训练模型进行迁移学习
  • 公开可用的数据集

迁移学习来拯救

神经网络的训练不是一次性的事件。模型通过分析一批又一批的数据来学习,每次都会更新其参数以减少预测误差。以下是图像分类中的一个常见场景:

  1. 模型首先在数百万张各种图像上进行训练,将每张图像分配给数百个类别之一。
  2. 在训练过程中,各种模型参数,例如卷积神经网络(CNN)中的滤波器,会学习特定的任务。有些任务非常基础,例如边缘或颜色的检测。其他滤波器则在此“基础”知识上构建,以学习更复杂模式的检测技巧,例如人物、眼睛或手。这些层称为“特征提取层”。
  3. 特征提取层之后是分类层,负责为图片分配标签。网络中分类部分通常比特征提取部分简单得多。
  4. 模型会再次进行训练,这次是在不同的图像集上(甚至是为了不同的任务)。由于模型已经能够检测基本特征,这些特征无论图片中出现什么都不会发生太大变化,因此只需要更新分类层。

我们使用现成模型(通常在海量数据集上训练)并使用自己的数据进一步训练它的过程,称为“迁移学习”。

迁移学习如此吸引人的原因是,它需要更少的数据——因此,准备数据和训练所需的时间也更少。

对于许多图像分类任务,您可以使用每个类别约30张图像就能获得不错的结果,训练只需几分钟而不是几个小时。我们不是指“生产质量”——但足以用于演示或概念验证。

我们将在下一篇文章中使用迁移学习来训练我们的模型。

公开数据集

收集每个类别30张图像似乎并不费力……但仍然,拍摄30张不同的热狗照片可能太耗时。

幸运的是,有一些包含免费图像的数据集。其中一个数据集Google Open Images Dataset包含大约900万张已标注图像,其中包含大约20000个图像级类别。对我们来说有个好消息:其中一个类别是“Hot dog”。

注释和图像分别在CC_BY 4.0CC_BY 2.0许可下。

如果您有大量的硬盘空间,您可以下载整个数据集;但是,为了我们的目的,我们只需要少量图像。

探索Google Open Images Dataset

由于我们需要的训练图像集相对较小,让我们使用Python代码只获取我们需要的内容——请参阅代码下载中包含的笔记本。

为了继续,我们需要从下载页面获取三个文件:

  • 包含已定义标签的文件(LABEL_DESCRIPTIONS
  • 包含图像URL列表的文件(IMAGE_URLS
  • 将图像与标签匹配的文件(IMAGE_LABELS

可用数据集分为训练、验证和测试集。为了说明过程,并避免下载过多的数据,我们将仅使用测试数据集。如果您需要更多图像来训练模型,请考虑使用剩余的数据集。

我们将在接下来的步骤中使用以下文件:

IMAGE_URLS = 'https://storage.googleapis.com/openimages/2018_04/test/test-images-with-rotation.csv'
IMAGE_LABELS = 'https://storage.googleapis.com/openimages/v5/test-annotations-human-imagelabels.csv'
LABEL_DESCRIPTIONS = 'https://storage.googleapis.com/openimages/v6/oidv6-class-descriptions.csv'

现在我们准备开始下载数据(使用一些库,包括pandas)。

import numpy as np
import pandas as pd
import os
import random as rnd

image_urls = pd.read_csv(IMAGE_URLS)
image_labels = pd.read_csv(IMAGE_LABELS)
label_descriptions = pd.read_csv(LABEL_DESCRIPTIONS)
# Limiting image_labels to confirmed
image_labels = image_labels[image_labels.Confidence == 1]
print("image_urls count:", len(image_urls))
print("image_labels count total:{}, unique images: {}".format(len(image_labels), len(image_labels.ImageID.unique())))
print("label_names count:", len(label_names))

预期输出

image_urls count: 125436
image_labels count total:1110124, unique images: 124480
label_names count: 19994

让我们看一下下载文件中数据的结构。

您可以看到关系,其中ImageID是用作连接image_urlsimage_labels的键,而LabelName是用作连接image_labelslabel_descriptions的键。

一张图片可以有多个标签。

我们将在稍后使用这些关系。但首先,让我们尝试在这里找到我们的热狗。

hotdog_related_classes = label_descriptions[label_descriptions['DisplayName'].str.startswith('Hot dog')]
print(hotdog_related_classes)

我们有三个与热狗相关的类别。为了专注于最具代表性的例子,我们将:

  • 使用标记为“Hot dog”的图像来教会模型识别热狗
  • 使用随机选择的、标签与“Hot dog”完全无关的图像来教会模型识别非热狗

这导致了以下选择代码:

hotdog_class = label_descriptions[label_descriptions['DisplayName'] == 'Hot dog']
hotdog_label_name = hotdog_class[‘LabelName'].to_list()[0]
hotdog_labels = image_labels[(image_labels.LabelName == hotdog_label_name)]

hotdog_related_label_names = hotdog_related_classes[‘LabelName'].to_list()
other_labels = image_labels[~image_labels.LabelName.isin(hotdog_related_label_names)]

print("hot dog label count:", len(hotdog_labels.ImageID.unique()))
print("other label count:", len(other_labels.ImageID.unique()))

代码产生了以下结果:

hot dog label count: 47
other label count: 124480

您可以看到,我们的数据集中热狗图像不多——只有47张——但这应该足以满足我们的需求。另一方面,我们有大量没有热狗的图像——超过120,000张。

选择和下载图像

我们将从一个辅助函数开始,该函数将帮助我们选择“非热狗”图像的随机URL子集。

def get_image_url_by_labels(labels, max_row_count: int = 0) -> []:
    sel_images = image_urls[image_urls.ImageID.isin(labels.ImageID.to_list())]
    
    if max_row_count == 0:
        return sel_images.OriginalURL.to_list()
    else:
        return sel_images.sample(min(len(sel_images), max_row_count)).OriginalURL.to_list()

有了这个函数,我们就可以选择图像进行下载(设置一个固定的随机种子以确保结果可重复)。

rnd.seed(101)
hotdog_image_urls = get_image_url_by_labels(hotdog_labels)
other_image_urls = get_image_url_by_labels(other_labels, max_row_count = 120)
print("hot dog url count:", len(hotdog_image_urls))
print("other url count: ", len(other_image_urls))

生成的输出:

hot dog url count: 47
other url count:  120

正如您所见,我们决定添加47张热狗图像和120张包含非热狗对象的随机图像。很难预先判断这样的划分是否正确。一方面,它应该反映出在现实生活中看到热狗(与“非热狗”)的频率。另一方面,如果我们使用太多“非热狗”图片进行训练,模型会倾向于将一切都检测为“非热狗”。为什么?因为这比错误地检测热狗对准确性的损害要小。数据集的适当平衡需要单独的文章(或一系列文章),所以现在我们假设我们的猜测是正确的。

现在是时候下载图像了。

import urllib.request
import tqdm

HOTDOG_PATH = './dataset/hotdogs/'
OTHER_PATH = ‘./dataset/other/'

def download_file(url: str, dest_dir: str):
    base_name = os.path.basename(url)
    dest_path = os.path.join(dest_dir, base_name)
    
    try:
        urllib.request.urlretrieve(url, dest_path)
    except Exception as e:
        print("Could not download file: {}, error: {}".format(base_name, e))

os.makedirs(HOTDOG_PATH, exist_ok=True)
for url in tqdm.tqdm(hotdog_image_urls):
    download_file(url, HOTDOG_PATH)

os.makedirs(OTHER_PATH, exist_ok=True)
for url in tqdm.tqdm(other_image_urls):
    download_file(url, OTHER_PATH)

这里有一个小小的惊喜:并非Google Open Images Dataset中的所有图像仍然可以在其原始URL上找到。

幸运的是,数量不多。为了保持简单,我们假设缺失的图像不会足够严重地影响数据分布,以至于引起担忧。

确认图像已正确保存到选定的目的地(./dataset/hotdog./dataset/other)……这样我们就完成了训练数据的下载。

将数据拆分为训练集和测试集

在现实场景中,我们会将可用数据拆分为训练集、验证集和测试集。

在我们的例子中,Create ML将自动进行训练集和验证集的随机拆分。测试集仅用于比较不同模型的性能,而我们不打算这样做。

摘要

在本文中,您学习了如何从Google Open Images Dataset中选择和下载已标注的图像。现在我们准备使用Create ML创建和训练自定义模型——请参阅下一篇文章

© . All rights reserved.