现代图像识别模型具有数百万个参数。从头开始训练需要大量标记的训练数据和大量计算能力(数百小时 GPU 或更多)。迁移学习是一项通过采用在相关任务上经过训练并在新模型中重复使用的一部分模型来快速实现这一目标的技术。在本教程中,我们将重用 ImageNet 上训练的强大图像分类器的特征提取功能,并简单地在顶部训练新的分类层。有关该方法的更多信息,您可以到 Decaf 上阅读这篇论文。
尽管这种方法无法与训练整个模型相提并论,但对于许多应用来说已经有着出奇制胜的效果,特别对适量的训练数据有效(数千,而非数百万标记的图像),且无需 GPU, 就可以在笔记本电脑上运行 30 分钟。本教程将向您展示如何在您自己的图像上运行示例脚本,并对您有助于控制训练过程的一些选项作进一步解释。
注意:本教程的一个版本也可作为 codelab 使用。
本教程使用 TensorFlow Hub 来调用预先训练过的模型或模块。对于初学者,我们将使用具有在 ImageNet 上训练的 Inception V3 架构的图像特征提取模块,并稍后返回其他选项,包括 NASNet / PNASNet,以及MobileNet V1 和 V2。
在开始之前,您需要安装 PIP 包 tensorflow-hub 以及最新版本的 TensorFlow。有关详细信息,请参阅 TensorFlow Hub 的 安装说明。
花卉训练
图片来自 Kelly Sikkema
任何训练在开始之前,需要一组图像来向网络传授您想要识别的新类别。本文后半部分会介绍该如何准备自己的图像,但为了方便起见,我们创建了一个关于经许可的花卉照片的知识共享档案,以便初始使用。要获取花卉照片集,请运行以下命令:
- cd ~
- curl -LO http://download.tensorflow.org/example_images/flower_photos.tgz
- tar xzf flower_photos.tgz
复制代码
获得图像后,可以从 GitHub 下载示例代码(它不是库安装的一部分):
- mkdir ~/example_code
- cd ~/example_code
- curl -LO https://github.com/tensorflow/hub/raw/master/examples/image_retraining/retrain.py
复制代码
在最简单的情况下,可以这样运行(大约需要半小时):
- python retrain.py --image_dir ~/flower_photos
复制代码
该脚本还有许多其他选项。 您可以通过以下方式获得完整列表:
此脚本加载预先训练的模块,并在您下载的花卉照片的顶部训练一个新的分类器。在完整的网络训练过程中,原始的 ImageNet 类别中并无任何花卉种类。迁移学习的神奇之处在于,经过训练用来区分某些对象的较低层无需任何改动,就可以重复用于多种识别任务。
瓶颈 根据机器速度,脚本可能需要 30 分钟或更久才能完成。第一阶段分析磁盘上的所有映像,并计算和缓存每个映像的瓶颈值。“瓶颈” 是一个非正式术语,我们经常在实际进行分类的最终输出层之前使用该层。(TensorFlow Hub 将其称为 “图像特征向量”)倒数第二层已经过训练,可以输出一组足够好的值,分类器可以用它来区分要求识别的所有类。这就意味着它必须是一个有意义的,并且紧凑的图像摘要,毕竟它必须包含足够的信息,以便分类器在一组非常小的值中做出正确的选择。我们在最后一层的再训练可以在新类上运行的原因是因为结果表明,区分 ImageNet 中所有 1,000 个类所需的信息通常也可用于区分新类型的对象。
因为每个图像在训练期间多次重复使用,并且计算每个瓶颈需要花费大量时间,所以它会加速将这些瓶颈值缓存到磁盘上,因此不必反复重新计算它们。默认情况下,它们存储在 / tmp / bottleneck 目录中,如果重新运行脚本,它们将被重用,因此您无需在此部分上流连。
训练
一旦瓶颈完成,就开始对网络顶层进行实际训练。您将看到一系列步骤输出,每一个步骤输出均显示训练的准确性,并验证准确度和交叉熵。训练准确性显示当前训练批次中使用的图像百分比标记为正确的类别。验证准确度是来自不同组的随机选择的图像组的精度。关键的区别在于训练精度是基于网络能够学习的图像,因此网络可以过度拟合训练数据中的噪音。衡量网络性能的真正标准是测量其在训练数据中未包含的数据集上的性能 - 这是通过验证准确度来衡量的。如果训练精度高但验证精度仍然很低,则意味着网络过度拟合并记住训练图像中的特定特征,这些特征对于普遍情况来说可能并无用处。交叉熵是一种损失函数,可以让我们一瞥学习过程的进展情况。训练的目标是尽可能减少损失,因此您可以通过关注损失是否保持向下趋势来判断学习是否有效,忽略短暂的噪音。
默认情况下,此脚本将运行 4,000 个训练步骤。每个步骤从训练集中随机选择十个图像,从缓存中找到它们的瓶颈,并将它们输入到最后一层从而获得预测。然后将这些预测与实际标签进行比较,通过反向传播过程更新最终层的权重。随着过程的继续,您应该看到报告的准确度得到改善,并且在完成所有步骤之后,对与训练和验证图片分开的一组图像运行最终测试准确度评估。该测试评估是训练模型将如何在分类任务上执行的最佳估计。您看到的准确度值应介于 90% 和 95% 之间,但是由于训练过程中的随机性,准确值会因批次不同而不同。此数字是基于完全训练模型后给定正确标签的测试集中图像的百分比。
使用 TensorBoard 可视化再训练
该脚本包含 TensorBoard 摘要,使之更容易理解、调试和优化再训练。例如,您可以将图形和统计数据进行可视化,诸如在训练期间权重或准确度变化。
要启动 TensorBoard,请在重新训练期间或之后运行此命令:
- tensorboard --logdir /tmp/retrain_logs
复制代码
TensorBoard 运行后,将 Web 浏览器导航到 localhost:6006 以查看 TensorBoard。
rewin.py 脚本默认情况下会将 TensorBoard 摘要记录到 / tmp / retrain_logs。您可以使用 --summaries_dir 标志更改目录。
TensorBoard 的 GitHub 存储库 提供了有关 TensorBoard 使用的更多信息,包括提示和技巧以及调试信息。
使用再训练模型
该脚本能够将在您的类别上训练的新模型写入 /tmp/output_graph.pb,并将包含标签的文本文件写入 /tmp/output_labels.txt。新模型包含内嵌的 TensorFlow Hub 模块和新的分类层。这两个文件都采用 C ++ 和 Python 图像分类示例可以读入的格式,因此您可以立即开始使用新模型。由于您已经替换了顶层,因此您需要在脚本中指定新名称,例如,如果您使用 label_image,则使用标志 --output_layer = final_result。
这里有一个示例,向我们展示了如何使用重新训练的图形运行 label_image 示例。按照惯例,所有 TensorFlow Hub 模块都接受具有固定范围 [0,1] 中颜色值的图像输入,因此您无需设置 --input_mean 或 --input_std 标志。
- curl -LO https://github.com/tensorflow/tensorflow/raw/master/tensorflow/examples/label_image/label_image.py
- python label_image.py \
- --graph=/tmp/output_graph.pb --labels=/tmp/output_labels.txt \
- --input_layer=Placeholder \
- --output_layer=final_result \
- --image=$HOME/flower_photos/daisy/21652746_cc379e0eea_m.jpg
复制代码
您会看到一个花卉标签列表,大多数情况下顶部会有菊花(虽然每个重新训练的模型可能会略有不同)。您可以使用自己的图像替换 --image 参数来尝试。
如果您想在自己的 Python 程序中使用重新训练的模型,那么上面的 label_image 脚本是一个合理的起点。label_image 目录还包含 C ++ 代码,您可以将其用作模板,将 TensorFlow 与您自己的应用程序集成。
如果您发现默认的 Inception V3 模块对于您的应用程序来说太大或太慢,请查看下面的 “其他模型架构” 部分,了解加快和缩小网络的选项。
在您自己的类别上进行训练
如果您已经设法让脚本处理花卉示例图像,您就可以开始考虑教它来识别更喜欢的类别。理论上,您需要做的就是将其指向一组子文件夹,每个子文件夹以您的一个类别命名,并且仅包含该类别的图像。如果您照做并将子目录的根文件夹作为参数传递给 --image_dir,则脚本就会像对花卉一样进行训练。
以下是花卉存档的文件夹结构,为您提供脚本正在寻找的布局类型示例:
实践过程中,您想要获得期待的准确性,可能需要一些工作。我会尝试引导您解决下面可能遇到的一些常见问题。
创建一套训练图像
首先要看的是你收集的图像,因为我们通过训练看到的最常见的问题来自于被输入的数据。
为了使训练更好地运作,您至少应该收集一百张您想要识别的各种物体的照片。收集的照片越多,训练的模型的准确性就越高。您还需要确保照片很好地代表了应用程序实际接触到的内容。比方说,假如您的所有照片都是在室内空白墙背景拍摄,而用户试图识别户外的物体,则您在部署时可能无法看到很好的结果。
另一个需要避免的缺陷是,任何与标记图像相同的内容会对学习过程产生影响,可能你稍有不慎就会获得一些没用的东西。比方说,如果您在蓝色房间中拍摄一种物体,而另一种物体在绿色物体中拍摄,则模型最终会根据背景颜色进行预测,而不是您实际关注的物体的特征。为避免这种情况发生,请尝试尽可能多地在不同时间和不同设备上拍摄照片。
您也许还想再考虑一下使用的类别。那么将大量不同物理形式的大类别划分为更具视觉冲突力的小类别就非常值得。例如,您可以使用 “汽车”,“摩托车” 和 “卡车” 代替 “车辆”。同样值得思考的是您是否有 “封闭世界” 或 “开放世界” 问题。在一个封闭的世界中,你唯一要求分类的东西就是你所知道的对象类别。这可能适用于您知道用户可能正在拍摄花卉照片的植物识别应用程序,因此您所要做的就是决定使用哪个物种。相比之下,漫游机器人在世界各地漫游时可以通过相机看到各式各样的东西。在这种情况下,您应该会希望分类器报告它无法确定看到的到底是什么,这恐怕很难,如果您经常收集大量典型的 “背景” 照而其中并无相关对象,您可以将它们添加到图像文件夹中的额外 “未知” 类。
还需要检查来确保所有图像都标记正确。用户生成的标签通常不能尽如我意。例如:标记为 #daisy 的图片也可能包含名为 Daisy 的人物和角色。如果您浏览图像并清除所有错误,那么整体将会出现奇迹般的准确性。
训练步骤
如果您对图像感到满意,可以通过更改学习过程的详细信息来了解改善结果的方法。最简单的尝试是 --how_many_training_steps。默认值为 4,000,但如果将其增加到 8,000,那么它将执行双倍的训练时间。提高准确度会减缓你训练的时间,而且在某些节点会完全停止(甚至因过度拟合而出现故障),不过你可以尝试一下,看看到底什么才是最适合你的模型。
扭曲
改善图像训练结果的常用方法是以随机方式使训练输入变形,裁剪或增亮。对于相同图像有着各种可能的变体的情况,这具有扩展训练数据的有效尺寸的优点,并且倾向于帮助网络学习应对将在分类器的实际使用过程中产生的所有失真情况。在我们的脚本中启用这些失真的最大缺点是瓶颈缓存失效,因为输入图像永远不会被完全地重用。这就意味着需要更长的时间(很多小时)的训练过程,因此建议您在拥有一个相当满意的模型作品之后,尝试将此作为一种润饰模型的方法。
通过将 --random_crop, - random_scale和--random_brightness 传递给脚本来启用这些失真。 这些都是控制每个图像应用失真的百分比值。该值从 5 或 10 开始是合理的,然后试验看看哪些值对您的应用有帮助。 --flip_left_right 将水平地随机镜像一半的图像,只要这些反转有可能在您的应用程序中发生,那么这就是有意义的。不过,如果你试图识别英文字母,那么这就不是一个好主意,因为翻转之后这些含义将被破坏殆尽。
超参数
您可以尝试调整其他几个参数,看看是否对结果有所帮助。 --learning_rate 控制训练期间最后一层更新的大小。 直观地说,如果这比学习小,需要更长的时间,但是它最终可以帮助提高整体精确度。不过也非尽然,因此需要您仔细研究,看看哪种方法更适用于您的情况。 --train_batch_size 控制在每个训练步骤中检查的图像数量,以估算最终图层的更新。
训练,验证和测试集
当您将脚本指向图像文件夹时,脚本所做的一件事就是将它们分成不同的三组。最大的通常是训练集,它们是训练期间馈入网络的所有图像,其结果用来更新模型的权重。 您也许想知道为什么我们不使用所有的图像进行训练? 当我们进行机器学习时,有一个很大的潜在问题是:我们的模型可能只是记住训练图像的不相关细节,从而得出正确的答案。例如,您可以想象一个网络在其显示的每张照片的背景中记住一个图案,并用它来匹配标签与对象。它可能在训练过程中对以前看到过的所有图像能够产生很好的结果,但是在新的图像上则以失败告终,究其原因是因为它没有学习到对象的普通特征,只是记住了训练图像的次要细节。
这个问题被称为过度拟合,为了避免这个问题,我们将一些数据保留在训练过程之外,这样模型就无法记住它们。 然后我们使用这些图像作为检查以确保不会发生过度拟合,因为如果我们看到它们具有良好的准确性,则表明网络没有过度拟合,这是一个好兆头。通常的分割是将 80% 的图像放入主训练集中,保留 10% 以备在训练期间能够经常运行用来验证,剩下的 10% 则作为测试集用于预测分类器在现实世界的表现。可以使用 --testing_percentage 和 --validation_percentage 标志来控制这些比率。一般情况下,将这些值保留为默认值,因为通常调整它们不会对训练有任何优势。
请注意,该脚本使用图像的文件名(而不是完全随机的函数)在训练、验证和测试集之间划分图像。这样做是为了确保图像不会在分别运行的训练集和测试集之间移动,因为如果用于训练模型的图像随后在验证集中使用,那么这有可能会出现问题。
您可能会注意到验证准确度在迭代之间波动。大部分这种波动源于这样的事实:为每个验证精度测量选择验证集的随机子集。通过选择 --validation_batch_size = -1,使用整个验证集进行每次精度计算,可以大大减少波动,但是代价是训练时间有所增加。
训练完成后,您会发现它在测试检查集中错误分类的图像时独具慧眼。可以通过添加标志 --print_misclassified_test_images 来完成。这可以帮助您了解哪种类型的图像最容易混淆模型,哪些类别最难以区分。例如,您可能会发现特定类别的某些子类型或某些不寻常的照片角度特别难以识别,这可能会激发您为该子类型添加更多的训练图像。通常,检查错误分类的图像也可能指向输入数据集中的错误,例如错误标记,低质量或模糊图像。然而,通常应该避免在测试集中修正个别错误,因为它们不太会反映(更大型的)训练集中的那些更普遍的问题。
其他模型架构
默认情况下,脚本使用带有 Inception V3 体系结构预训练实例的图像特征提取模块。这是一个很好的起点,因为它为再训练脚本提供了高精度的结果和适中的运行时间。但是现在让我们来看看 TensorFlow Hub 模块的其他选项
一方面,该列表显示了更新的,功能更强大的体系结构,例如 NASNet(特别是 nasnet_large 和 pnasnet_large),可以为您提供额外的精确度。
另一方面,如果您打算在移动设备或其他资源受限的环境中部署模型,您可能希望以更小的文件大小或更快的速度(也在训练中)来交换一点精确度。为此,试试不同的模块实现 MobileNet V1 或 V2 架构,或者 nasnet_mobile。
使用不同模块进行训练很简单:只需将 --tfhub_module 标志与模块 URL 一起传递,例如:
- <blockquote>python retrain.py \
复制代码
这将在 /tmp/output_graph.pb 中创建一个 9 MB 的模型文件,其中的模型使用 MobileNet V2 的基线版本。在浏览器中打开模块 URL 将转到模块文档。
如果您只是想让它快一点,您可以将输入图像(第二个数字)的大小从 '224' 减小到 '192','160' 或 '128' 像素的平方,甚至 '96' (仅适用于 V2)。为了获得更大幅度地节省,您可以选择百分比(第一个数字)'100','075','050' 或 '035'(V1 的 '025')来控制 “特征深度” 或神经元的每个位置数量。权重的数量(以及文件大小和速度)随着该分数的平方而缩小。GitHub 上的 MobileNet V1 博文和 MobileNet V2 页面上报道了 Imagenet 分类的各自权衡。
Mobilenet V2 不会将功能深度百分比应用于瓶颈层。在 Mobilenet V1 会将功能百分比应用于瓶颈层,对小深度来说,分类层的工作就显得困难重重。Mobilenet V2 是否有助于瞒天过海并使用原始 1001 ImageNet 类得分,而非应用于严格的瓶颈? 您可以尝试将 removenet_v1 ... / feature_vector 替换为模块名称中的 mobilenet_v1 ... / classification。
和之前一样,您可以将所有重新训练的模型与 label_image.py 一起使用。您需要指定模型所需的图像大小,例如:
- python label_image.py \
- --graph=/tmp/output_graph.pb --labels=/tmp/output_labels.txt \
- --input_layer=Placeholder \
- --output_layer=final_result \
- --input_height=224 --input_width=224 \
- --image=$HOME/flower_photos/daisy/21652746_cc379e0eea_m.jpg
复制代码
有关将重新训练的模型部署到移动设备的更多信息,请参阅本教程的 codelab 版本,特别是第 2 部分, 介绍了 TensorFlow Lite 及其提供的其他优化(包括模型权重的量化)。
转载自TensorFlow公众号
|