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





5.00/5 (3投票s)
在本文中,我们将开始为这个新的自定义模型准备数据,该模型稍后将使用Create ML框架进行训练。
引言
深度神经网络在图像分类等任务中非常出色。十年前需要花费数百万美元和一个完整的研究团队才能完成的结果,现在对于拥有一个半像样的GPU的任何人来说都很容易获得。然而,深度神经网络有一个缺点。它们可能非常笨重和缓慢,因此在移动设备上运行效果不佳。幸运的是,Core ML提供了一个解决方案:它使您能够创建在iOS设备上运行良好的精简模型。
在本系列文章中,我们将展示两种使用Core ML的方法。首先,您将学习如何将预先训练的图像分类器模型转换为Core ML模型,并在iOS应用中使用它。然后,您将训练自己的机器学习(ML)模型,并使用它来制作一个“不是热狗”应用程序——就像您可能在HBO的《硅谷》中看到的那样。
到目前为止,我们已经处理了一个已训练的ML模型——将其从ResNet转换为Core ML,然后在示例iOS应用程序中使用它。为了在图像中检测热狗,我们将构建自己的模型。
构建热狗检测模型需要什么?
要构建一个能够区分热狗和“不是热狗”的图像分类模型,您需要三样东西:
- 包含热狗的照片
- 不包含热狗的照片
- 用于在这些照片上训练模型的机制
在典型的ML项目中,最耗时的任务是收集和准备数据。从头开始训练神经网络需要海量数据——至少数百个,但很可能是数千个记录,数百万甚至数十亿个记录并不少见。我们在上一篇文章中使用的ResNet模型是在ImageNet 2012分类数据集的128万张图像上训练的。所有这些图像都必须获取、收集并手动标注其中1000个标签之一。
这种对数据的“饥渴”如果不是因为以下两点,会造成很多麻烦:
- 从预训练模型进行迁移学习
- 公开可用的数据集
迁移学习来拯救
神经网络的训练不是一次性的事件。模型通过分析一批又一批的数据来学习,每次都会更新其参数以减少预测误差。以下是图像分类中的一个常见场景:
- 模型首先在数百万张各种图像上进行训练,将每张图像分配给数百个类别之一。
- 在训练过程中,各种模型参数,例如卷积神经网络(CNN)中的滤波器,会学习特定的任务。有些任务非常基础,例如边缘或颜色的检测。其他滤波器则在此“基础”知识上构建,以学习更复杂模式的检测技巧,例如人物、眼睛或手。这些层称为“特征提取层”。
- 特征提取层之后是分类层,负责为图片分配标签。网络中分类部分通常比特征提取部分简单得多。
- 模型会再次进行训练,这次是在不同的图像集上(甚至是为了不同的任务)。由于模型已经能够检测基本特征,这些特征无论图片中出现什么都不会发生太大变化,因此只需要更新分类层。
我们使用现成模型(通常在海量数据集上训练)并使用自己的数据进一步训练它的过程,称为“迁移学习”。
迁移学习如此吸引人的原因是,它需要更少的数据——因此,准备数据和训练所需的时间也更少。
对于许多图像分类任务,您可以使用每个类别约30张图像就能获得不错的结果,训练只需几分钟而不是几个小时。我们不是指“生产质量”——但足以用于演示或概念验证。
我们将在下一篇文章中使用迁移学习来训练我们的模型。
公开数据集
收集每个类别30张图像似乎并不费力……但仍然,拍摄30张不同的热狗照片可能太耗时。
幸运的是,有一些包含免费图像的数据集。其中一个数据集Google Open Images Dataset包含大约900万张已标注图像,其中包含大约20000个图像级类别。对我们来说有个好消息:其中一个类别是“Hot dog”。
注释和图像分别在CC_BY 4.0和CC_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_urls
与image_labels
的键,而LabelName
是用作连接image_labels
与label_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创建和训练自定义模型——请参阅下一篇文章。