特邀博文 / Hugo Zanini,机器学习工程师
目标检测是一类检测目标在图像中的位置,以及在给定图像中对每个感兴趣的目标进行分类的任务。在计算机视觉领域,我们可将此技术应用于图片检索、监控摄像头和无人驾驶汽车中。
就目标检测而言,深度卷积神经网络 (DNN) 家族中最负盛名的算法就是 YOLO (You Only Look Once)。
在本文中,我们将使用 TensorFlow 开发一种端到端解决方案, 用以在 Python 中训练自定义目标检测模型,然后将其投入生产,并通过 TensorFlow.js 在浏览器内运行实时推理。
本文的内容将分为以下四个步骤讲述:
目标检测流水线
准备数据
要想训练出优秀的模型,第一步便是获取优质数据。开发此项目时,我并未找到合适(并且足够小)的目标检测数据集,因此我决定自行创建。
我环顾四周,看到了卧室中的袋鼠标志,这是我为纪念在澳大利亚的生活而带回来的纪念品。于是,我决定构建一个袋鼠检测器。
为构建数据集,我使用图片搜索工具搜索并下载了 350 张袋鼠图像,并使用 LabelImg 应用手动标记了所有图像。由于每张图像中不止有一只袋鼠,所以我最后标记了 520 只袋鼠。
标记示例
在本例中,我只选择了一个类别,但我也可以使用软件为多种类别添加注释。接下来是为每张图像生成包含所有注释和边界框的 XML 文件(Pascal VOC 格式)。
<annotation>
<folder>images</folder>
<filename>kangaroo-0.jpg</filename>
<path>/home/hugo/Documents/projects/tfjs/dataset/images/kangaroo-0.jpg</path>
<source>
<database>Unknown</database>
</source>
<size>
<width>3872</width>
<height>2592</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>kangaroo</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>60</xmin>
<ymin>367</ymin>
<xmax>2872</xmax>
<ymax>2399</ymax>
</bndbox>
</object>
</annotation>
XML 注释示例
为了方便转换为 TF.record 格式(如下方所示),我之后将上述计划的 XML 文件转换为了两个 CSV 文件,其中包含在训练和测试 (80%-20%) 中已拆分的数据。这些文件有 9 列内容:
- 文件名:图像名
- 宽度:图像宽度
- 高度:图像高度
- 类别:图像类别(袋鼠)
- xmin:边界框 x 坐标的最小值
- ymin:边界框 y 坐标的最小值
- xmax:边界框 x 坐标的最大值
- ymax:边界框 y 坐标的最大值
- 来源:图像来源
借助 LabelImg 可以十分便捷地创建自己的数据集,但也欢迎直接使用我的 袋鼠数据集,我已将其上传至 Kaggle。
训练模型
有了优质的数据集,下面就该考虑模型的问题了。TensorFlow 2 所提供的 目标检测 API 能让我们轻松构建、训练,以及部署目标检测模型。在本项目中,我们将采用此 API,并使用 Google Colaboratory Notebook 训练模型。本部分的剩余内容将解释如何设置环境、选择模型以及开展训练。如果您想直接跳转至对应案例的 Colab Notebook,请访问 此处。
设置环境
创建 一个新的 Google Colab Notebook,然后选择 GPU 作为硬件加速器:
Runtime > Change runtime type > Hardware accelerator: GPU
克隆、安装,以及测试 TensorFlow 目标检测 API:
获取并处理数据
如前所述,我将在 Kaggle 上使用袋鼠数据集训练模型。如果您也想使用此数据集,则必须创建用户,然后前往 Kaggle 的帐号部分获取 API Token:
获取 API Token
接着,您便可以开始下载数据:
现在,我们需要创建一个 labelmap 文件,以定义我们将要使用的数据类别。袋鼠是唯一的类别,因此 我们 要在 Google Colab 的 “文件” 部分右击, 创建名为“labelmap.pbtxt”的新文件,如下所示:
item {
name: "kangaroo"
id: 1
}
最后一步是将数据转换为一系列 二进制记录,然后我们便能将这些数据输入到 TensorFlow 的目标检测 API。要完成这一步骤,可以使用能在袋鼠数据集中得到的 generate_tf_records.py 脚本,将数据转换为 TFRecord 格式。
选择模型
我们现在可以开始选择要将其作为袋鼠检测器的模型。TensorFlow 2 可提供 40 个针对 COCO 2017 数据集 的预训练检测模型。这个模型集合为 TensorFlow 2 Detection Model Zoo。
每个模型均拥有一定的速度、平均精度均值 (mAP) 和输出。一般而言,模型的 mAP 越高,速度就越慢。但由于本项目是基于某一类的目标检测问题,因此,选择速度较快的模型 (SSD MobileNet v2 320x320) 便已足够。
除了 Model Zoo,TensorFlow 还可提供 模型配置代码库。因此,我们可以在开始训练前获取需要对其进行修改的配置文件。现在开始下载文件吧:
配置训练
如前所述,下载的权重已在 COCO 2017 数据集中进行预训练,但此处关注的重点是训练模型识别某一种类别,因此我们只会在初始化网络时使用这些权重。这项技术被称为 迁移学习,多用于加速学习进程。
现在,我们要做的是设置 mobilenet_v2.config 文件,然后开始训练。我们强烈推荐您阅读有关 MobileNetV2 的论文 (Sandler, Mark, et al. - 2018),以了解此架构的要点。
选择最佳超参数需要进行一些实验。由于 Google Colab 中的资源有限,我将使用与该论文相同的批次大小,设置多个步骤以适当地减少损失,并将其他所有值设为默认值。如果您想尝试使用某些更复杂的方法查找超参数,我推荐您使用 Keras Tuner。这是一种易用型框架,内置有贝叶斯优化、Hyperband 和随机搜索算法。
setting-paramerts.ipynb
设置参数后,便可开始训练:
我们可以使用损失值了解训练情况。损失是一个数值,表示模型在训练样例上预测的准确程度。如果模型的预测完全准确,则损失为零;否则损失会较大。训练模型的目标是从所有样本中找到一组平均损失“较小”的权重和偏差(深入了解机器学习:训练与损失 | 机器学习速成课程)。
根据日志,我们可以看到损失值呈下降趋势,此时我们便可以说“该模型已收敛”。在下一部分中,我们将针对所有训练步骤绘制这些值,那时趋势便会更加明显。
训练模型(借助 Colab GPU)的时间大约为 4 小时,但您可以通过设置不同的参数来控制这一进程的速度。这一切均取决于您使用的类别数以及您的精确率/召回率目标。可识别多个类别的高精度网络需执行更多步骤,且需要更细致地调节参数。
验证模型
现在,我们将使用测试数据对训练后的模型进行评估:
此次评估在 89 张图像中完成,并基于 COCO 检测评估指标提出了三个指标:精确率、召回率和损失值。
召回率用于衡量模型识别正类别的表现如何,即在所有正类别中,被算法正确识别为正类别的比例是多少?
召回率
精确率定义了对正类别预测的信赖程度:在被模型识别为正类别的样本中,确实为正类别的比例是多少?
精确率
设置实例:想象一下我们的一张图像中有 10 只袋鼠,模型返回的结果为检测到 5 只,其中 3 只是真袋鼠 (TP = 3, FN =7),2 只为假的 (FP = 2)。在这一案例中,召回率为 30%(图像中有 10 只袋鼠,模型检测出 3 只),精确率为 60%(检测出的 5 个目标中有 3 个是正确的)。
精确率除以召回率即可得到交并比 (IoU) 阈值。IoU 为预测边界框 (B) 与真实边界框 (B) 的交汇面积与这两者的总面积的比值 (Zeng, N. - 2018):
交并比
为简单起见,我们可以认为 IoU 阈值可用于确定检测到的目标为真正例 (TP)、假正例 (FP) 还是假负例 (FN)。请查看以下示例:
IoU 阈值示例
了解这些概念后,我们便可分析从评估中获取的某些指标。在 TensorFlow 2 Detection Model Zoo 中, SSD MobileNet v2 320x320 的 mAP 为 0.202。对于不同的 IoU,模型会显示以下平均精度 (AP):
AP@[IoU=0.50:0.95 | area=all | maxDets=100] = 0.222
AP@[IoU=0.50 | area=all | maxDets=100] = 0.405
AP@[IoU=0.75 | area=all | maxDets=100] = 0.221
这可真不错!我们可以将获得的 AP 与在 COCO 数据集文档中通过 SSD MobileNet v2 320x320 获得的 mAP 进行对比:
我们并未区分 AP 与 mAP(对 AR 和 mAR 也是如此),因为从环境中便能对这两者加以区别。
每张图像可检测到的最大数值(1、10、100)会严重影响平均召回率 (AR) 。当每张图像中仅有一只袋鼠时,召回率大约为 30%,但如果这一数量达到 100 只,则召回率大约为 51%。这些值显示的结果并不乐观,但对于我们正试图解决的这类问题而言却很合理。
(AR)@[ IoU=0.50:0.95 | area=all | maxDets= 1] = 0.293
(AR)@[ IoU=0.50:0.95 | area=all | maxDets= 10] = 0.414
(AR)@[ IoU=0.50:0.95 | area=all | maxDets=100] = 0.514
对损失的分析非常简单,因此我们得到了 4 个值:
INFO:tensorflow: + Loss/localization_loss: 0.345804
INFO:tensorflow: + Loss/classification_loss: 1.496982
INFO:tensorflow: + Loss/regularization_loss: 0.130125
INFO:tensorflow: + Loss/total_loss: 1.972911
定位损失计算的是预测边界框与标记边界框之间的差别。分类损失指明边界框对应类别与预测类别是否匹配。正则化损失由网络的正则函数生成,可将优化算法向正确方向推动。最后要介绍的一个术语是总损失,为上述三类损失的总和。
TensorFlow 可提供一种工具,以简单方式将所有这些指标可视化。该工具名为 TensorBoard,可通过以下命令进行初始化:
%load_ext tensorboard
%tensorboard --logdir '/content/training/'
呈现的结果如下所示,您可以在其中探索所有训练和评估指标。
Tensorboard — 损失函数
在“IMAGES”标签页中,我们可以在并排显示的预测图像和真实图像间找到一些差异。在验证过程中,这也是可以探索的有趣资源。
Tensorboard — 测试图像
导出模型
既然我们已对训练进行了验证,那么是时候导出模型了。我们要将训练检查点转换为 protobuf (pb) 文件。此文件将显示图表定义和模型权重。
由于我们将使用 TensorFlow.js 部署模型,而且 Google Colab 虚拟机的最长生命周期为 12 小时,因此我们要先下载训练后的权重,并将其保存到本地。在运行命令 files.download('/content/saved_model.zip") 时,Colab 会针对相关文件自动显示提示。
如果您想查看模型是否已正确保存,请加载该模型并进行测试。我创建了一些函数,可进一步简化这一过程。欢迎您 从我的 GitHub 中复制 inferenceutils.py 文件来测试某些图像。
一切进展顺利,因此我们准备在生产环境中应用此模型。
部署模型
部署此模型后,每个人都可以打开 PC 或手机摄像头,然后通过网络浏览器执行实时推理。为做到这一点,我们会将已保存的模型转换为 Tensorflow.js 图层格式,然后在 JavaScript 应用中加载此模型,并在 Glitch 上提供所有相关资源。
转换模型
现在,您应该已经对本地保存的此架构有所了解:
├── inference-graph
│ ├── saved_model
│ │ ├── assets
│ │ ├── saved_model.pb
│ │ ├── variables
│ │ ├── variables.data-00000-of-00001
│ │ └── variables.index
开始之前,我们可以先创建一个独立的 Python 环境,在空白的工作区工作,以防止出现任何库冲突。安装 virtualenv,然后在 inference-graph 文件夹中打开终端,从而创建全新的虚拟环境并将其激活:
virtualenv -p python3 venv
source venv/bin/activate
pip install tensorflowjs[wizard]
启动转换向导:
tensorflowjs_wizard
现在,该工具将指导您完成转换过程,并解释您需要做出的每项选择。下方图像显示了在转换模型时需做出的所有选择。其中大部分选项拥有标准值,但分片大小和压缩率之类的选项可根据您的需求进行调整。
为使浏览器能够自动缓存权重,推荐您将这些权重拆分为多个约 4MB 的分片文件。为保证转换顺利进行,同样地,请切勿跳过算子验证。并非所有的 TensorFlow 算子均受支持,因此某些模型无法与 TensorFlow.js 兼容。请查看 此表,了解当前受支持的算子。
使用 TensorFlow.js 转换器实施的模型转换
如果一切进展顺利,您便能在 web_modeldirectory 中将模型转换为 Tensorflow.js 图层格式。该文件夹包含一个 model.json 文件和一组二进制格式的分片权重文件。model.json 文件包含模型拓扑结构(又名“架构”或“图形”:对图层及其连接方式的描述)和权重文件清单 (Lin, Tsung-Yi, et al)。
└ web_model
├── group1-shard1of5.bin
├── group1-shard2of5.bin
├── group1-shard3of5.bin
├── group1-shard4of5.bin
├── group1-shard5of5.bin
└── model.json
配置应用
现在,您可以在 JavaScript 中加载该模型。我创建了一个应用,这样便能在浏览器中直接执行推理。现在 复制代码库,以确定如何实时使用转换后的模型。项目结构如下:
├── models
│ └── kangaroo-detector
│ ├── group1-shard1of5.bin
│ ├── group1-shard2of5.bin
│ ├── group1-shard3of5.bin
│ ├── group1-shard4of5.bin
│ ├── group1-shard5of5.bin
│ └── model.json
├── package.json
├── package-lock.json
├── public
│ └── index.html
├── README.MD
└── src
├── index.js
└── styles.css
为方便起见,我在模型文件夹中存入了转换后的“袋鼠检测器”模型。不过,我们要将上一部分生成的 web_model 放入模型文件夹,然后对其进行测试。
首先需指定模型在函数 load_model(文件 src>index.js 的 10 - 15 行)中的加载方式。现在有两个选项。
第一个选项是创建本地 HTTP 服务器。借助这种方式,您可在允许接受请求的 URL 中获取模型,并将其用作 REST API。加载模型时,TensorFlow.js 将执行以下请求:
GET /model.json
GET /group1-shard1of5.bin
GET /group1-shard2of5.bin
GET /group1-shard3of5.bin
GET /group1-shardo4f5.bin
GET /group1-shardo5f5.bin
如果您选择这一选项,请使用以下命令定义 load_model 函数:
async function load_model() {
// It's possible to load the model locally or from a repo
// You can choose whatever IP and PORT you want in the "http://127.0.0.1:8080/model.json" just set it before in your https server
const model = await loadGraphModel("http://127.0.0.1:8080/model.json");
//const model = await loadGraphModel("https://raw.githubusercontent.com/hugozanini/TFJS-object-detection/master/models/web_model/model.json");
return model;
}
然后安装 http-server:
npm install http-server -g
前往“模型 > web_model”,然后运行以下命令,使您能通过 http://127.0.0.1:8080 获取模型。如果您希望将模型权重保存在安全地点,并控制能向模型请求推理的人员,这便是一个不错的选择。系统已添加“-c1”参数以禁用缓存,而“–cors”标志会开启跨域资源共享,从而允许给定作用域的 JavaScript 客户端使用托管文件。
http-server -c1 --cors .
另外,您也可以在其他地方上传模型文件。在我的案例中,我选择了自己的 Github 代码库,并在 load_model 函数中引用了 model.json URL:
async function load_model() {
// It's possible to load the model locally or from a repo
//const model = await loadGraphModel("http://127.0.0.1:8080/model.json");
const model = await loadGraphModel("https://raw.githubusercontent.com/hugozanini/TFJS-object-detection/master/models/web_model/model.json");
return model;
}
这是一个很好的选项,因为该选项可给予应用更多的灵活性,使其能在 Glitch 等平台上更便捷地运行。
在本地运行
为了在本地运行应用,需安装必需的软件包:
npm install
And start:
npm start
该应用将在 http://localhost:3000 上运行,您应该会看到类似这样的画面:
在本地运行应用
加载模型需要 1 至 2 秒的时间。之后,您便可向摄像头展示袋鼠图像,接着该应用就会描绘出图像的边界框。
在 Glitch 上发布
Glitch 是一个创建网页应用的简单工具。我们可以在其中上传代码,供所有人通过网络获取该应用。向 GitHub 代码库上传模型文件,并通过 load_model 函数引用这些文件。我们可以轻松登录 Glitch,点击“New project”(新项目)>“Import from Github”(从 Github 导入),然后选择应用代码库。
安装软件包需要几分钟时间,然后人们便可在公开 URL 中获取您的应用。点击“Show”(显示)>“In a new window”(在新窗口中),系统便会打开一个新标签页。复制此 URL,然后将其粘贴至任意网页浏览器(PC 或手机),您便可以开始运行目标检测。您可在下方视频中查看一些示例:
在不同设备上运行模型
首先,我做了一个测试,通过展示袋鼠标志来验证应用的稳健性。结果显示,此模型会准确聚焦袋鼠的特征,并没有关注很多图像中呈现的无关特征(如灰色图像或灌木丛)。
然后,我在手机中打开应用,并展示了测试集中的部分图像。模型运行十分顺畅,并且识别出了大部分袋鼠。如果您想测试我的在线应用,可在 此处 获取(唤醒 glitch 需要几分钟时间)。
除了准确率,这些实验的另一个有趣之处便是推理时间:这一切均通过 JavaScript 在浏览器中实时运行。优秀的目标检测模型可在浏览器中运行,且使用的计算资源较少,这在很多应用中是必须满足的要求,在工业领域更是如此。执行推理无需将用户信息发送至服务器,用户的隐私性便得到了保证。因此,在客户端部署机器学习模型可减少成本,应用的安全性也能得以提高。
后续工作
在浏览器中运行目标检测能够解决诸多现实世界的问题,我希望本篇文章能为涉及计算机视觉、Python、TensorFlow 和 JavaScript 的新项目奠定基础。
下一步,我希望能开展更多详细的训练实验。由于缺乏资源,我无法尝试多个参数,我确信此模型还有很大的改进空间。
我更加注重对模型的训练,但我也希望此应用能够拥有更佳的用户界面。如果您对改进本项目感兴趣,欢迎在 项目代码库 中创建 PR 请求。若能开发出一款更加人性化的应用,便再好不过了。
如果您有任何疑问或建议,可通过 Linkedin 与我联系。感谢您的阅读!
原文:Custom object detection in the browser using TensorFlow.js
中文:TensorFlow 公众号