TensorFlow 智能移动项目:1~5

2023-04-24 10:57:41 浏览数 (3)

一、移动 TensorFlow 入门

本章介绍如何设置开发环境,以使用 TensorFlow 构建所有 iOS 或 Android 应用,本书其余部分对此进行了讨论。 我们不会详细讨论可用于开发的所有受支持的 TensorFlow 版本,OS 版本,Xcode 和 Android Studio 版本,因为可以在 TensorFlow 网站或通过 Google。 相反,我们将在本章中简要讨论示例工作环境,以便我们能够快速了解​​可使用该环境构建的所有出色应用。

如果您已经安装了 TensorFlow,Xcode 和 Android Studio,并且可以运行和测试示例 TensorFlow iOS 和 Android 应用,并且如果您已经安装了 NVIDIA GPU 以进行更快的深度学习模型训练,则可以跳过本章。 或者,您可以直接跳到您不熟悉的部分。

我们将在本章涵盖以下主题(如何设置 Raspberry Pi 开发环境将在第 12 章,“在 Raspberry Pi 上开发 TensorFlow 应用”中进行讨论):

  • 设置 TensorFlow
  • 设置 Xcode
  • 设置 Android Studio
  • TensorFlow Mobile 与 TensorFlow Lite
  • 运行示例 TensorFlow iOS 应用
  • 运行示例 TensorFlow Android 应用

设置 TensorFlow

TensorFlow 是领先的机器智能开源框架。 当 Google 在 2015 年 11 月将 TensorFlow 作为一个开源项目发布时,已经有其他一些类似的深度学习开源框架:Caffe,Torch 和 Theano。 在 5 月 10 日的 Google I/O 2018 上,GitHub 上的 TensorFlow 已达到 99000 星,在 4 个月内增加了 14k 星,而 Caffe 仅增加了 2k 至 24k 星。 两年后,它已经成为最流行的开源框架,用于训练和部署深度学习模型(它对传统机器学习也有很好的支持)。 截至 2018 年 1 月,TensorFlow 在 GitHub 上拥有近 8.5 万颗星,而其他三个领先的开源深度学习框架 Caffe,CNTK 和 Mxnet 分别拥有 22k,13k 和 12k 颗星。

如果您对机器学习,深度学习,机器智能和人工智能(AI)的流行语有些困惑,这里有个简短的摘要:机器智能和 AI 确实是同一回事。 机器学习是 AI 的一个领域,也是最受欢迎的领域; 深度学习是机器学习的一种特殊类型,也是解决诸如计算机视觉,语音识别和合成以及自然语言处理之类的复杂问题的现代且最有效的方法。 因此,在本书中,当我们说 AI 时,我们主要是指深度学习,这是将 AI 从漫长的冬天带到夏天的救星。 有关 AI 冬季和深度学习的更多信息,您可以查看这里和这里。

我们假设您已经对 TensorFlow 有了基本的了解,但是如果您还没有,请查看入门和教程部分或 Awesome TensorFlow 教程。 关于该主题的两本好书是《Python 机器学习:Python , scikit-learn 和 TensorFlow 机器学习和深度学习》和《使用 Scikit-Learn 和 TensorFlow 动手进行机器学习》。

TensorFlow 可以安装在 MacOS,Ubuntu 或 Windows 上。 我们将介绍在 MacOS X El Capitan(10.11.6),macOS Sierra(10.12.6)和 Ubuntu 16.04 上从源代码安装 TensorFlow 1.4 的步骤。 如果您使用其他操作系统或版本,则可以参考 TensorFlow 安装文档以获取更多信息。 当您阅读本书时,可能会出现更新的 TensorFlow 版本。 尽管您仍然应该能够使用较新版本运行本书中的代码,但这并不能保证,因此我们在 Mac 和 Ubuntu 上使用 TensorFlow 1.4 发行源代码来设置 TensorFlow; 这样,您可以轻松地测试运行并与书中的应用一起玩。

自从我们于 2017 年 12 月撰写以上段落以来,TensorFlow 已有四个新的正式版本(1.5、1.6、1.7 和 1.8),以及截至 2018 年 5 月的新版本 Xcode(9.3),您可以在以下位置下载, 或在 TensorFlow 源代码仓库。 TensorFlow 的较新版本(例如 1.8)默认情况下支持 NVIDIA CUDA 和 cuDNN 的较新版本(有关详细信息,请参阅“在 Ubuntu 上设置基于 GPU 的 TensorFlow”部分),并且最好遵循官方的 TensorFlow 文档来安装具有 GPU 支持的最新 TensorFlow 版本。 在本章及以下各章中,我们将以特定的 TensorFlow 版本为例,但将对所有 iOS,Android 和 Python 代码进行测试,并在需要时针对其中的最新 TensorFlow,Xcode 和 Android Studio 版本进行更新。 本书的源代码仓库位于这里。

总体而言,我们将在 Mac 上使用 TensorFlow 开发 iOS 和 Android TensorFlow 应用,并在 Ubuntu 上使用 TensorFlow 训练应用中使用的深度学习模型。

在 MacOS 上设置 TensorFlow

通常,您应该使用 VirtualEnv,Docker 或 Anaconda 安装在单独的环境中安装 TensorFlow。 但是由于我们必须使用 TensorFlow 源代码构建 iOS 和 Android TensorFlow 应用,因此我们不妨从源代码构建 TensorFlow 本身,在这种情况下,使用本机 PIP 安装选择可能比其他选择更容易。 如果您想尝试不同的 TensorFlow 版本,我们建议您使用 VirtualEnv,Docker 和 Anaconda 选项之一安装其他 TensorFlow 版本。 在这里,我们将使用本地 PIP 和 Python 2.7.10 直接在 MacOS 系统上安装 TensorFlow 1.4。

请按照以下步骤在 MacOS 上下载并安装 TensorFlow 1.4:

  1. 从 GitHub 上的 TensorFlow 发布页面下载 TensorFlow 1.4.0 源代码(ziptar.gz
  2. 解压缩下载的文件并将tensorflow-1.4.0文件夹拖到您的主目录
  3. 确保已安装 Xcode 8.2.1 或更高版本(否则,请先阅读“设置 Xcode”部分)
  4. 打开一个新的终端窗口,然后单击cd tensorflow-1.4.0
  5. 运行xcode-select --install以安装命令行工具
  6. 运行以下命令以安装构建 TensorFlow 所需的其他工具和包:
代码语言:javascript复制
sudo pip install six numpy wheel  
brew install automake 
brew install libtool 
./configure 
brew upgrade bazel 
  1. 从 TensorFlow 源代码构建,仅提供 CPU 支持(我们将在下一部分介绍 GPU 支持),并生成带有.whl文件扩展名的 PIP 包文件:
代码语言:javascript复制
bazel build --config=opt //tensorflow/tools/pip_package:build_pip_package 

bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg 
  1. 安装 TensorFlow 1.4.0 CPU 包:
代码语言:javascript复制
sudo pip install --upgrade /tmp/tensorflow_pkg/tensorflow-1.4.0-cp27-cp27m-macosx_10_12_intel.whl

老实说,如果您在此过程中遇到任何错误,则搜索错误消息应该是修复该错误的最佳方法,因为我们打算在本书中重点介绍从我们长期积累的技巧和知识,从数小时的构建和调试实用的移动 TensorFlow 应用中获取,它们在其他地方不易获得。 运行sudo pip install命令时,您可能会看到的一个特定错误是Operation not permitted错误。 要解决此问题,您可以通过重新启动 Mac 并点击Cmd R键来禁用 Mac 的系统完整性保护SIP) 要进入恢复模式,请在工具终端下,在重新启动 Mac 之前运行csrutil disable。 如果您对禁用 SIP 不满意,可以按照 TensorFlow 文档尝试使用更复杂的安装方法之一,例如 VirtualEnv。

如果一切顺利,则应该能够在终端窗口,Python 或最好在 IPython 上运行,然后运行import tensorflow as tftf.__version__将 1.4.0 作为输出。

在 GPU 驱动的 Ubuntu 上设置 TensorFlow

使用良好的深度学习框架(例如 TensorFlow)的好处之一是在模型训练中无缝支持使用图形处理单元GPU) 。 在 GPU 上训练非平凡的基于 TensorFlow 的模型要比在 CPU 上训练要快得多,并且当前 NVIDIA 提供 TensorFlow 支持的最佳和最具成本效益的 GPU。 Ubuntu 是使用 TensorFlow 运行 NVIDIA GPU 的最佳操作系统。 您可以花几百美元轻松购买一个 GPU,然后将其安装在带有 Ubuntu 系统的廉价台式机上。 您也可以在 Windows 上安装 NVIDIA GPU,但 TensorFlow 对 Windows 的支持不如对 Ubuntu 的支持。

为了训练本书中应用中部署的模型,我们使用 NVIDIA GTX 1070,您可以在 Amazon 或 eBay 上以大约 400 美元的价格购买。 蒂姆·戴特默斯(Tim Dettmers)有一个不错的博客,其中介绍了用于深度学习的 GPU。 在获得这样的 GPU 并将其安装在 Ubuntu 系统上之后,以及在安装启用 GPU 的 TensorFlow 之前,您需要安装 NVIDIA CUDA 8.0(或 9.0)和 cuDNN(CUDA-DeepNeuralNetwork)6.0 (或 7.0),两者均受 TensorFlow 1.4 支持。

使用 TensorFlow 设置自己的 GPU 驱动的 Ubuntu 的另一种方法是在支持 GPU 的云服务(例如 Google Cloud Platform 的 Cloud ML Engine)中使用 TensorFlow。 每个选项都有优点和缺点。 云服务通常是基于时间的计费。 如果您的目标是训练或重新训练要在移动设备上部署的模型,这意味着模型并不复杂,并且如果您计划长时间进行机器学习训练,那么拥有自己的 GPU 成本效益更高并且令人满意。

请按照以下步骤在 Ubuntu 16.04 上安装 CUDA 8.0 和 cuDNN 6.0(您应该能够以类似的方式下载并安装 CUDA 9.0 和 cuDNN 7.0):

  1. 在这个页面中找到 NVIDIA CUDA 8.0 GA2 版本,并进行以下屏幕截图中所示的选择:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-24owtE5N-1681653027406)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/0e74beec-5ac6-4755-8268-dcf83c27a700.png)]

图 1.1:准备在 Ubuntu 16.04 上下载 CUDA 8.0

  1. 下载基本安装程序,如以下屏幕快照所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQ8ALCmL-1681653027407)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/49296f66-83aa-48a5-973b-a4d9c8dd4348.png)]

图 1.2:为 Ubuntu 16.04 选择 CUDA 8.0 安装程序文件

  1. 打开一个新的终端并运行以下命令(您还需要将最后两个命令添加到.bashrc文件中,以使两个环境变量在您下次启动新终端时生效):
代码语言:javascript复制
sudo dpkg -i /home/jeff/Downloads/cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64.deb  
sudo apt-get update 
sudo apt-get install cuda-8-0 
export CUDA_HOME=/usr/local/cuda 
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
  1. 通过这里下载用于 CUDA 8.0 的 NVIDIA cuDNN 6.0,您将被要求先免费注册一个 NVIDIA 开发者帐户(免费)。 您可以下载它,如下一个屏幕截图所示(选择突出显示的 cuDNN v6.0 Linux 版):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wl1Sp3ho-1681653027408)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/f8e7636c-a193-4915-8c4f-8a234ffaf788.png)]

图 1.3:在 Linux 上为 CUDA 8.0 选择 cuDNN 6.0

  1. 假设下载的文件位于默认的~/Downloads目录下,请解压缩该文件,然后您会看到一个名为cuda的文件夹,其中包含两个名为includelib64的子文件夹
  2. 将 cuDNN includelib64文件复制到CUDA_HOMElib64include文件夹中:
代码语言:javascript复制
sudo cp ~/Downloads/cuda/lib64/* /usr/local/cuda/lib64 
sudo cp ~/Downloads/cuda/include/cudnn.h /usr/local/cuda/include 

现在我们准备在 Ubuntu 上安装启用 GPU 的 TensorFlow 1.4(此处给出的前两个步骤与在 MacOS 上设置 TensorFlow 一节中描述的步骤相同):

  1. 从 GitHub 上的 TensorFlow 发布页面下载 TensorFlow 1.4.0 源代码(ziptar.gz
  2. 解压缩下载的文件并将文件夹拖到主目录
  3. 从这里下载 bazel 安装程序
  4. 打开一个新的终端窗口,然后运行以下命令以安装构建 TensorFlow 所需的工具和包:
代码语言:javascript复制
sudo pip install six numpy wheel  
cd ~/Downloads 
chmod  x bazel-0.5.4-installer-linux-x86_64.sh 
./bazel-0.5.4-installer-linux-x86_64.sh --user
  1. 从具有 GPU 支持的 TensorFlow 源进行构建,并生成带有.whl文件扩展名的 pip 包文件:
代码语言:javascript复制
cd ~/tensorflow-1.4.0 
./configure 
bazel build --config=opt --config=cuda //tensorflow/tools/pip_package:build_pip_package 
bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg 
  1. 安装 TensorFlow 1.4.0 GPU 包:
代码语言:javascript复制
sudo pip install --upgrade /tmp/tensorflow_pkg/tensorflow-1.4.0-cp27-cp27mu-linux_x86_64.whl 

现在,如果一切顺利,您可以启动 IPython 并输入以下脚本以查看 TensorFlow 使用的 GPU 信息:

代码语言:javascript复制
In [1]: import tensorflow as tf 

In [2]: tf.__version__ 
Out[2]: '1.4.0' 

In [3]: sess=tf.Session() 
2017-12-28 23:45:37.599904: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:892] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero 
2017-12-28 23:45:37.600173: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1030] Found device 0 with properties:  
name: GeForce GTX 1070 major: 6 minor: 1 memoryClockRate(GHz): 1.7845 
pciBusID: 0000:01:00.0 
totalMemory: 7.92GiB freeMemory: 7.60GiB 
2017-12-28 23:45:37.600186: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1120] Creating TensorFlow device (/device:GPU:0) -> (device: 0, name: GeForce GTX 1070, pci bus id: 0000:01:00.0, compute capability: 6.1) 

恭喜你! 现在,您就可以训练本书中应用中使用的深度学习模型了。 在我们开始玩我们的新玩具并用它来训练我们的炫酷模型然后在移动设备上部署和运行它们之前,我们首先来看看准备好开发移动应用需要做什么。

设置 Xcode

Xcode 用于开发 iOS 应用,您需要 Mac 电脑和免费的 Apple ID 才能下载和安装它。 如果您的 Mac 相对较旧并且使用 OS X El Capitan(版本 10.11.6),则可以从这里下载 Xcode 8.2.1。 或者,如果您安装了 macOS Sierra(10.12.6 版)或更高版本,则可以从前面的链接下载 Xcode 9.2 或 9.3(截至 2018 年 5 月的最新版本)。 本书中的所有 iOS 应用均已在 Xcode 8.2.1、9.2 和 9.3 中进行了测试。

要安装 Xcode,只需双击下载的文件,然后按照屏幕上的步骤进行操作。 这很简单。 现在,您可以在 Xcode 随附的 iOS 模拟器或您自己的 iOS 设备上运行应用。 从 Xcode 7 开始,您可以在 iOS 设备上免费运行和调试 iOS 应用,但如果要分发或发布您的应用,则需要以每年 99 美元的价格参加 Apple 开发人员计划。

尽管您可以使用 Xcode 模拟器测试运行书中的许多应用,但是书中的某些应用需要使用实际的 iOS 设备上的相机拍摄照片,然后才能使用经过 TensorFlow 训练的深度学习模型对其进行处理。 此外,通常最好在实际设备上测试模型的准确表现和内存使用情况:在模拟器中运行良好的模型可能会崩溃或在实际设备中运行太慢。 因此,强烈建议或要求您(如果并非总是)至少在您的实际 iOS 设备上测试并运行本书中的 iOS 应用一次。

本书假定您熟悉 iOS 编程,但是如果您不熟悉 iOS 开发,则可以从许多出色的在线教程中学习,例如 Ray Wenderlich 的 iOS 教程。 我们不会介绍复杂的 iOS 编程; 我们将主要向您展示如何在我们的 iOS 应用中使用 TensorFlow C API 来运行 TensorFlow 训练有素的模型来执行各种智能任务。 Apple 的两种官方 iOS 编程语言 Objective-C 和 Swift 代码都将用于与我们的移动 AI 应用中的 C 代码进行交互。

设置 Android Studio

Android Studio 是开发 Android 应用的最佳工具,并且 TensorFlow 对其使用提供了强大的支持。 与 Xcode 不同,您可以在 Mac,Windows 或 Linux 上安装并运行 Android Studio。 有关详细的系统要求,请参阅 Android Studio 网站。 在这里,我们将介绍如何在 Mac 上设置 Android Studio 3.0 或 3.0.1-本书中的所有应用均已在两个版本上进行了测试。

首先,从前面的链接下载 Android Studio 3.0.1,如果最新版本是 3.0.1 以上,并且您不介意解决可能的小问题,请下载最新版本。 您也可以从这里的存档中下载 3.0.1 或 3.0。

然后,双击下载的文件并将Android Studio.app图标拖放到Applications。 如果您以前安装了 Android Studio,则系统会提示您是否要用较新的 Android Studio 替换它。 您只需选择替换即可。

接下来,打开 Android Studio,您需要提供 Android SDK 的路径,如果您安装了以前版本的 Android Studio,则默认情况下它位于~/Library/Android/sdk中,或者您可以选择打开现有的 Android Studio 项目,然后转到“在 MacOS 上设置 TensorFlow”一节中创建的 TensorFlow 1.4 源目录,然后打开tensorflow/examples/android文件夹。 之后,您可以通过单击安装工具消息的链接或转到 Android Studio 的Tools | Android | SDK Manager来下载 Android SDK,如以下屏幕截图所示。 从 SDK 工具标签中,您可以选中特定版本的 Android SDK 工具旁边的框,然后单击确定按钮以安装该版本:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0zQGe2Xr-1681653027408)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/3b90efdb-6655-4e35-b74a-a44e69e17487.png)]

图 1.4:Android SDK Manager,用于安装 SDK 工具和 NDK

最后,由于 TensorFlow Android 应用使用 C 中的本机 TensorFlow 库来加载和运行 TensorFlow 模型,因此您需要安装 Android 本机开发套件NDK),您可以执行以下任一操作,从上一个屏幕快照中显示的 Android SDK Manager 中获取,或者直接从这里下载 NDK。 NDK 版本 r16b 和 r15c 均已通过测试可运行本书中的 Android 应用。 如果直接下载 NDK,则在打开项目并选择 Android Studio 的File | Project Structure后,您可能还需要设置 Android NDK 位置,如以下屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qqIIkRPs-1681653027408)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/1f6a2d74-0be0-4f36-9796-2cf66eb1694f.png)]

图 1.5:设置项目级别的 Android NDK 位置

安装并设置了 Android SDK 和 NDK 之后,您就可以测试运行示例 TensorFlow Android 应用了。

TensorFlow Mobile 与 TensorFlow Lite

在我们开始运行示例 TensorFlow iOS 和 Android 应用之前,让我们澄清一下。 TensorFlow 当前有两种在移动设备上开发和部署深度学习应用的方法:TensorFlow Mobile 和 TensorFlow Lite。 TensorFlow Mobile 从一开始就是 TensorFlow 的一部分,而 TensorFlow Lite 是开发和部署 TensorFlow 应用的较新方法,因为它具有更好的性能和更小的应用大小。 但是有一个关键因素可以让我们在本书中专注于 TensorFlow Mobile,同时仍在一个章节中介绍 TensorFlow Lite:从 TensorFlow 1.8 和 2018 年 5 月的 Google I/O 开始,TensorFlow Lite 仍在开发人员预览版中。 现在,准备投入生产的移动 TensorFlow 应用,您必须按照 Google 的建议使用 TensorFlow Mobile。

我们决定现在专注于 TensorFlow Mobile 的另一个原因是,虽然 TensorFlow Lite 仅对模型运算符提供了有限的支持,但 TensorFlow Mobile 支持自定义以添加默认情况下 TensorFlow Mobile 不支持的新运算符,您会发现它经常发生在我们的各种模型中 AI 应用的模型。

但是将来,当 TensorFlow Lite 不在开发人员预览版中时,它很可能会取代 TensorFlow Mobile,或者至少克服其当前的局限性。 为了为此做好准备,我们将在下一章中详细介绍 TensorFlow Lite。

运行示例 TensorFlow iOS 应用

在本章的最后两部分中,我们将测试运行 TensorFlow 1.4 随附的三个示例 iOS 应用和四个示例 Android 应用,以确保您正确设置了移动 TensorFlow 开发环境并快速预览了其中的一些内容 TensorFlow 移动应用可以做到。

三个示例 TensorFlow iOS 应用的源代码位于tensorflow/examples/ios: simplecamerabenchmark。 为了成功运行这些样本,您需要首先下载一个由 Google 训练的深度学习模型,称为 Inception,用于图像识别。 Inception 有多个版本:v1 到 v4,在每个较新的版本中准确率更高。 在这里,我们将使用 Inception v1 作为为其开发的示例。 下载模型文件后,将与模型相关的文件复制到每个样本的data文件夹中:

代码语言:javascript复制
curl -o ~/graphs/inception5h.zip https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip 
unzip ~/graphs/inception5h.zip -d ~/graphs/inception5h 
cd tensorflow/examples/ios 
cp ~/graphs/inception5h/* simple/data/ 
cp ~/graphs/inception5h/* camera/data/ 
cp ~/graphs/inception5h/* benchmark/data/ 

现在,在打开和运行应用之前,转到每个app文件夹并运行以下命令以下载每个应用所需的 Pod:

代码语言:javascript复制
cd simple 
pod install 
open tf_simple_example.xcworkspace 
cd ../camera 
pod install 
open tf_camera_example.xcworkspace 
cd ../benchmark 
pod install 
open tf_benchmark_example.xcworkspace  

然后,您可以在 iOS 设备上运行这三个应用,或者在 iOS 模拟器上运行简单和基准的应用。 如果在运行简单应用后点击运行模型按钮,您将看到一条文本消息,提示已加载 TensorFlow Inception 模型,随后是几个顶级识别结果以及置信度值。

如果在运行基准测试应用后点击 Benchmark Model 按钮,您将看到运行模型超过 20 次的平均时间。 例如,在我的 iPhone 6 上平均需要大约 0.2089 秒,在 iPhone 6 模拟器上平均需要 0.0359 秒。

最后,在 iOS 设备上运行照相机应用并将其对准相机可以向您实时显示该应用看到和识别的对象。

运行示例 TensorFlow Android 应用

tensorflow/examples/android中有四个样本 TensorFlow Android 应用,分别为 TF 分类,TF 检测,TF 语音和 TF 风格化。 运行这些示例的最简单方法是使用 Android Studio 在前面的文件夹中打开项目,如“设置 Android Studio”部分中所示,然后通过编辑项目的build.gradle文件进行单个更改,并将def nativeBuildSystem = 'bazel'更改为def nativeBuildSystem = 'none'

现在,将 Android 设备连接到您的计算机,然后通过选择 Android Studio 的Run | Run 'android'构建,安装和运行该应用。这会在您的设备上安装四个名称为“TF 分类”,“TF 检测”,“TF 语音”和“TF 风格化”的 Android 应用。 “TF 分类”就像 iOS 相机应用一样,使用 TensorFlow Inception v1 模型对设备相机进行实时对象分类。 “TF 检测”使用另一种模型,称为单发多框检测器SSD)和 MobileNet,这是 Google 发布的一组新的深度学习模型,专门针对移动和嵌入式设备, 要执行对象检测,请在检测到的对象上绘制矩形。 “TF 语音”使用另一种不同的深度学习(语音识别)模型来收听和识别一小部分单词,例如YesNoLeftRightStopStart。 “TF 风格化”使用另一种模型来更改相机看到的图像样式。 有关这些应用的更多详细信息,您可以在这个页面中查看 TensorFlow Android 示例文档。

总结

在本章中,我们介绍了如何在 Mac 和 Ubuntu 上安装 TensorFlow 1.4,如何在 Ubuntu 上设置具有成本效益的 NVIDIA GPU 以便进行更快的模型训练以及如何为移动 AI 应用开发设置 Xcode 和 Android Studio。 我们还向您展示了如何运行一些很酷的 TensorFlow 示例 iOS 和 Android 应用。 在本书的其余部分,我们将详细讨论如何在基于 GPU 的 Ubuntu 系统上构建和训练或重新训练应用中使用的每个模型以及其他模型,并向您展示如何在以下环境中部署模型 iOS 和 Android 应用,并编写代码以在移动 AI 应用中使用模型。 现在我们已经准备就绪,我们已经迫不及待要上路了。 这将是一段激动人心的旅程,我们当然很乐意与朋友分享这一旅程。 那么,为什么不从我们最好的朋友开始,让我们看看构建狗品种识别应用需要什么呢?

二、通过迁移学习对图像进行分类

上一章中描述的示例 TensorFlow iOS 应用,Simple 和 Camera 以及 Android 应用“TF 分类”都使用了 Inception v1 模型,该模型是 Google 公开提供的预训练的图像分类深度神经网络模型。 该模型针对 ImageNet 进行了训练,ImageNet 是最大和最知名的图像数据库之一,其中有超过一千万个图像被标注为对象类别。 Inception 模型可用于将图像分类为列出的 1,000 个类别之一。 这 1000 个对象类别包括很多对象中的很多犬种。 但是,识别狗品种的准确率不是很高,约为 70%,因为模型经过训练可以识别大量对象,而不是像狗品种之类的特定对象。

如果我们想提高准确率并在使用改进模型的智能手机上构建移动应用怎么办,那么当我们四处走走并看到一只有趣的狗时,我们可以使用该应用告诉我们它是哪种狗。

在本章中,我们将首先讨论为什么对于这样的图像分类任务,迁移学习或重新训练经过预训练的深度学习模型是完成任务的最经济有效的方法。 然后,我们将向您展示如何使用良好的狗数据集对一些最佳图像分类模型进行再训练,以及在第 1 章,“移动 TensorFlow 入门”。 此外,我们还将分步说明如何将 TensorFlow 添加到基于 Objective-C 或 Swift 的 iOS 和 Android 应用中。

总而言之,我们将在本章中介绍以下主题:

  • 迁移学习 - 什么和为什么
  • 将 Inception v3 模型用于再训练
  • 将 MobileNet 模型用于再训练
  • 在示例 iOS 应用中使用经过重新训练的模型
  • 在示例 Android 应用中使用经过重新训练的模型
  • 将 TensorFlow 添加到您自己的 iOS 应用中
  • 将 TensorFlow 添加到您自己的 Android 应用中

迁移学习 – 什么和为什么

我们人类不会从头开始学习新事物。 取而代之的是,无论是否有意识地,我们都充分利用所学到的知识。 人工智能中的迁移学习试图做同样的事情-这种技术通常只需要训练的大型模型中的一小块,然后将其重新用于相关任务的新模型中,而无需访问大型训练数据和计算资源来训练原始模型。 总体而言,迁移学习仍然是 AI 中的一个开放问题,因为在许多情况下,仅需人类反复尝试几个例子,然后再学习掌握新事物,就会花很多时间来训练和学习 AI。 但是,在图像识别领域,迁移学习已被证明是非常有效的。

用于图像识别的现代深度学习模型通常是深度神经网络,或更具体地说,是具有许多层的深度卷积神经网络CNN)。 这种 CNN 的较低层负责学习和识别较低层的特征,例如图像的边缘,轮廓和零件,而最后一层则确定图像的类别。 对于不同类型的对象,例如犬种或花朵类型,我们不需要重新学习网络较低层的参数或权重。 实际上,从头开始需要花费数周的训练来学习用于图像识别的现代 CNN 的所有权重,通常是数百万甚至更多。 在图像分类的情况下,迁移学习使我们能够使用特定的图像集重新训练此类 CNN 的最后一层,通常不到一小时,而所有其他层都保持不变,并且达到了几乎相同的精度,就像我们从头开始训练整个网络数周一样。

迁移学习的第二个主要好处是,我们只需要少量的训练数据就可以重新训练 CNN 的最后一层。 如果必须从头开始训练深层 CNN 的数百万个参数,则需要大量的训练数据。 例如,对于我们的狗品种再训练,我们只需要为每个狗品种提供 100 幅以上的图像,即可建立一个比原始图像分类模型更好的狗品种分类模型。

如果您不熟悉 CNN,请查看其中的最佳资源之一的视频和评论,这是 Stanford CS231n 课程“用于视觉识别的 CNN”。 CNN 的另一个很好的资源是 Michael Nielsen 的在线书籍《神经网络和深度学习》的第 6 章。

在接下来的两个部分中,我们将使用针对 TensorFlow 的两个最佳的经过预训练的 CNN 模型和一个犬种数据集来重新训练模型并生成更好的犬种识别模型。 第一个模型是 Inception v3,它是比 Inception v1 更准确的模型,已针对准确率进行了优化,但大小较大。 另一个模型是 MobileNet,它针对移动设备的大小和效率进行了优化。 TensorFlow 支持的预训练模型的详细列表位于这里。

将 Inception v3 模型用于再训练

在上一章中设置的 TensorFlow 源代码中,有一个 Python 脚本tensorflow/examples/image_retraining/retrain.py,可用于重新训练 Inception v3 或 MobileNet 模型。 在运行脚本以重新训练 Inception v3 模型以进行狗品种识别之前,我们需要首先下载斯坦福狗数据集, 120 个犬种的图片(您只需要在链接中下载图片,而不是标注即可)。

~/Downloads中解压缩下载的狗images.tar文件,您应该在~/Downloads/Images中看到文件夹列表,如以下屏幕截图所示。 每个文件夹对应一个犬种,并且包含约 150 张图像(您无需为图像提供显式标签,因为文件夹名称用于标记文件夹中包含的图像):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BNHjmcvF-1681653027408)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/c8c4fa68-5db2-406d-9444-fbf1fdeb5418.png)]

图 2.1:由文件夹或狗的品种分开的狗集图像

您可以下载数据集,然后在 Mac 上运行retrain.py脚本,因为该脚本在相对位置上运行不会花费太长时间(少于一小时) 小型数据集(总共约 20,000 张图像),但是,如上一章所述,如果您在 GPU 驱动的 Ubuntu 上执行此操作,则该脚本仅需几分钟即可完成。 此外,当使用大型图像数据集进行再训练时,在 Mac 上运行可能需要花费数小时或数天,因此在 GPU 驱动的计算机上运行它是有意义的。

假设您已经创建了/tf_file目录和/tf_file/dogs_bottleneck目录,那么重新训练模型的命令如下:

代码语言:javascript复制
python tensorflow/examples/image_retraining/retrain.py  
--model_dir=/tf_files/inception-v3  
--output_graph=/tf_files/dog_retrained.pb  
--output_labels=/tf_files/dog_retrained_labels.txt  
--image_dir ~/Downloads/Images  
--bottleneck_dir=/tf_files/dogs_bottleneck 

这五个参数在这里需要一些解释:

  • --model_dir指定应该由retrain.py自动下载 Inception v3 模型的目录路径,除非它已经在目录中。
  • --output_graph表示再训练模型的名称和路径。
  • --output_labels是由图像数据集的文件夹(标签)名称组成的文件,稍后将其与经过重新训练的模型一起使用以对新图像进行分类。
  • --image_dir是用于重新训练 Inception v3 模型的图像数据集的路径。
  • --bottleneck_dir用于缓存在瓶颈(最后一层之前的那一层)上生成的结果; 最后一层使用这些结果进行分类。 在重新训练期间,每个映像都使用了几次,但该映像的瓶颈值保持不变,即使将来重新运行重新训练脚本也是如此。 因此,第一次运行需要更长的时间,因为它需要创建瓶颈结果。

在再训练期间,您将每 10 步看到 3 个值,默认总计 4,000 步。 前 20 个步骤和后 20 个步骤以及最终精度如下所示:

代码语言:javascript复制
INFO:tensorflow:2018-01-03 10:42:53.127219: Step 0: Train accuracy = 21.0% 
INFO:tensorflow:2018-01-03 10:42:53.127414: Step 0: Cross entropy = 4.767182 
INFO:tensorflow:2018-01-03 10:42:55.384347: Step 0: Validation accuracy = 3.0% (N=100) 
INFO:tensorflow:2018-01-03 10:43:11.591877: Step 10: Train accuracy = 34.0% 
INFO:tensorflow:2018-01-03 10:43:11.592048: Step 10: Cross entropy = 4.704726 
INFO:tensorflow:2018-01-03 10:43:12.915417: Step 10: Validation accuracy = 22.0% (N=100) 
... 
... 
INFO:tensorflow:2018-01-03 10:56:16.579971: Step 3990: Train accuracy = 93.0% 
INFO:tensorflow:2018-01-03 10:56:16.580140: Step 3990: Cross entropy = 0.326892 
INFO:tensorflow:2018-01-03 10:56:16.692935: Step 3990: Validation accuracy = 89.0% (N=100) 
INFO:tensorflow:2018-01-03 10:56:17.735986: Step 3999: Train accuracy = 93.0% 
INFO:tensorflow:2018-01-03 10:56:17.736167: Step 3999: Cross entropy = 0.379192 
INFO:tensorflow:2018-01-03 10:56:17.846976: Step 3999: Validation accuracy = 90.0% (N=100) 
INFO:tensorflow:Final test accuracy = 91.0% (N=2109) 

训练精度是神经网络用于训练的图像上的分类精度,而验证精度是神经网络未用于训练的图像上的验证精度。 因此,验证准确率是衡量模型准确率的一种更可靠的度量,并且通常应该比训练准确率小一点,但是如果训练收敛并进行得很好,也就是说,训练的模型是否既不欠拟合也不过拟合。

如果训练精度很高,但验证精度仍然很低,则意味着模型过拟合。 如果训练精度仍然很低,则表明模型不适合。 同样,交叉熵是损失函数值,如果再训练顺利进行,则总体上应该越来越小。 最后,测试准确率取决于尚未用于训练或验证的图像。 通常,这是我们可以说出的关于重新训练模型的最准确的值。

如前面的输出所示,在再训练结束时,我们看到验证精度与训练精度相似(90% 和 93%,相比之初为 3% 和 21%),最终测试精度为 91% 。 交叉熵也从开始时的 4.767 下降到最后的 0.379。 因此,我们现在有了一个很好的再训练狗品种识别模型。

为了进一步提高准确率,您可以使用retrain.py的其他参数(例如训练步骤) (--how_many_training_steps),学习率(--learning_rate),和数据扩充(--flip_left_right, --random_crop, --random_scale, --random_brightness). 通常,这是一个乏味的过程,涉及到许多“肮脏的工作”,这是最著名的深度学习专家之一 Andrew Ng 在他的“应用深度学习的基本原理”演讲中提到的(视频可在以下位置找到)。

您可以使用另一个 Python 脚本label_image来对经过重新训练的模型进行快速测试,以测试自己的图像(例如/tmp/lab1.jpg中的 Labrador Retriever 图像),您可以在首先对其进行如下构建后运行该脚本:

代码语言:javascript复制
bazel build tensorflow/examples/image_retraining:label_image 

bazel-bin/tensorflow/examples/label_image/label_image  
--graph=/tf_files/dog_retrained.pb  
--image=/tmp/lab1.jpg  
--input_layer=Mul  
--output_layer=final_result  
--labels=/tf_files/dog_retrained_labels.txt 

您会看到与以下内容相似的前五个分类结果(但是,因为网络随机变化,可能不完全相同)如下:

代码语言:javascript复制
n02099712 labrador retriever (41): 0.75551 
n02099601 golden retriever (64): 0.137506 
n02104029 kuvasz (76): 0.0228538 
n02090379 redbone (32): 0.00943663 
n02088364 beagle (20): 0.00672507 

--input_layer (Mul)--output_layer (final_result)的值非常重要–必须与模型中定义的值相同,这样分类才能完全起作用。 如果您想知道如何获取它们(从图,aka 模型,文件dog_retrained.pb中获取),则有两个 TensorFlow 工具可能会有所帮助。 第一个是适当命名的summarize_graph。 这是构建和运行它的方法:

代码语言:javascript复制
bazel build tensorflow/tools/graph_transforms:summarize_graph

bazel-bin/tensorflow/tools/graph_transforms/summarize_graph --in_graph=/tf_files/dog_retrained.pb

您将看到类似于以下内容的摘要结果:

代码语言:javascript复制
No inputs spotted.
No variables spotted.
Found 1 possible outputs: (name=final_result, op=Softmax)
Found 22067948 (22.07M) const parameters, 0 (0) variable parameters, and 99 control_edges
Op types used: 489 Const, 101 Identity, 99 CheckNumerics, 94 Relu, 94 BatchNormWithGlobalNormalization, 94 Conv2D, 11 Concat, 9 AvgPool, 5 MaxPool, 1 DecodeJpeg, 1 ExpandDims, 1 Cast, 1 MatMul, 1 Mul, 1 PlaceholderWithDefault, 1 Add, 1 Reshape, 1 ResizeBilinear, 1 Softmax, 1 Sub

有一个可能的输出,名称为final_result。 不幸的是,有时summarize_graph工具没有告诉我们输入名称,因为它似乎对用于训练的节点感到困惑。 删除仅用于训练的节点(我们将在稍后讨论)之后,summarize_graph工具将返回正确的输入名称。 另一个名为 TensorBoard 的工具为我们提供了更完整的模型图。 如果直接从二进制文件安装了 TensorFlow,则应该可以只运行 TensorBoard,因为默认情况下,它安装在/usr/local/bin中。 但是,如果像我们之前那样从源代码安装 TensorFlow,则可以运行以下命令来构建 TensorBoard:

代码语言:javascript复制
git clone https://github.com/tensorflow/tensorboard 
cd tensorboard/ 
bazel build //tensorboard 

现在,确保您具有/tmp/retrained_logs,在运行retrain.py时自动创建并运行:

代码语言:javascript复制
bazel-bin/tensorboard/tensorboard --logdir /tmp/retrain_logs

然后在浏览器中启动 URL http://localhost:6006。 首先,您将看到准确率图,如以下屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GFin3z7h-1681653027409)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/3ae39776-c298-4b42-9125-f25ca84c9ed2.png)]

图 2.2:Inception v3 重新训练模型的训练和验证准确率

在下面的屏幕截图中的交叉熵图,和我们之前对于运行retrain.py的输出所描述的一样:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpiU85KV-1681653027409)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/cd4cea6a-8847-40bd-9ec4-3d9696972a5f.png)]

图 2.3:Inception v3 重新训练模型的训练和验证交叉熵

现在单击GRAPHS选项卡,您将看到一个名为Mul的操作,另一个名为final_result的操作 ,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K2HEMYPh-1681653027409)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/18aa75d8-28e3-497c-ba8f-145baa6cee37.png)]

图 2.4:重新训练模型中的Mulfinal_result节点

实际上,如果您希望与 TensorFlow 进行小的交互,则可以尝试几行 Python 代码来找出输出层和输入层的名称,如 iPython 交互所示:

代码语言:javascript复制
In [1]: import tensorflow as tf 
In [2]: g=tf.GraphDef() 
In [3]: g.ParseFromString(open("/tf_files/dog_retrained.pb", "rb").read()) 
In [4]: x=[n.name for n in g.node] 
In [5]: x[-1:] 
Out[5]: [u'final_result'] 

请注意,由于无法保证节点的顺序,因此此代码段并不总是有效,但它通常会为您提供所需的信息或验证。

现在,我们准备讨论如何进一步修改重新训练的模型,以便可以在移动设备上部署和运行它。 重新训练的模型文件dog_retrained.pb的大小太大,大约 80MB,在部署到移动设备之前,应该经过两个步骤进行优化:

  1. 去除未使用的节点:删除模型中仅在训练期间使用但在推理期间不需要的节点。
  2. 量化模型:将模型参数的所有 32 位浮点数转换为 8 位值。 这样可以将模型大小减小到其原始大小的 25%,同时保持推理精度大致相同。

TensorFlow 文档提供有关量化及其工作原理的更多详细信息。

有两种方法可以执行前面的两个任务:使用strip_unused工具的旧方法和使用transform_graph工具的新方法。

让我们看看旧方法的工作原理:首先运行以下命令以创建一个模型,其中所有未使用的节点都将被删除:

代码语言:javascript复制
bazel build tensorflow/python/tools:strip_unused 

bazel-bin/tensorflow/python/tools/strip_unused  
  --input_graph=/tf_files/dog_retrained.pb  
  --output_graph=/tf_files/stripped_dog_retrained.pb  
  --input_node_names=Mul  
  --output_node_names=final_result  
     --input_binary=true 

如果在输出图中运行前面的 Python 代码,则可以找到正确的输入层名称:

代码语言:javascript复制
In [1]: import tensorflow as tf 
In [2]: g=tf.GraphDef() 
In [3]: g.ParseFromString(open("/tf_files/ stripped_dog_retrained.pb", "rb").read()) 
In [4]: x=[n.name for n in g.node] 
In [5]: x[0] 
Out[5]: [u'Mul'] 

现在运行以下命令来量化模型:

代码语言:javascript复制
python tensorflow/tools/quantization/quantize_graph.py  
   --input=/tf_files/stripped_dog_retrained.pb   
--output_node_names=final_result   
--output=/tf_files/quantized_stripped_dogs_retrained.pb  
   --mode=weights 

之后,可以在 iOS 和 Android 应用中部署和使用模型quantized_stripped_dogs_retrained.pb,我们将在本章的以下部分中看到。

剥离未使用的节点并量化模型的另一种方法是使用称为transform_graph的工具。 这是 TensorFlow 1.4 中推荐的新方法,并且可以在 Python label_image脚本中正常工作,但是在部署到 iOS 和 Android 应用时仍然会导致不正确的识别结果。

代码语言:javascript复制
bazel build tensorflow/tools/graph_transforms:transform_graph
bazel-bin/tensorflow/tools/graph_transforms/transform_graph  
  --in_graph=/tf_files/dog_retrained.pb  
  --out_graph=/tf_files/transform_dog_retrained.pb  
  --inputs='Mul'  
  --outputs='final_result'  
  --transforms=' 
    strip_unused_nodes(type=float, shape="1,299,299,3") 
    fold_constants(ignore_errors=true) 
    fold_batch_norms 
    fold_old_batch_norms 
    quantize_weights' 

在测试中使用label_image脚本可以正确运行quantized_stripped_dogs_retrained.pbtransform_dog_retrained.pb。 但是只有第一个可以在 iOS 和 Android 应用中正常工作。

有关图转换工具的详细文档,请参见其 GitHub README。

将 MobileNet 模型用于再训练

上一节中生成的剥离和量化模型的大小仍超过 20MB。 这是因为用于再训练的预先构建的 Inception v3 模型是大规模的深度学习模型,具有超过 2500 万个参数,并且 Inception v3 并非以移动优先为目标。

2017 年 6 月,谷歌发布了 MobileNets v1,共 16 种针对 TensorFlow 的移动优先深度学习模型。 这些模型的大小只有几 MB,具有 47 万至 424 万个参数,仍然达到了不错的精度(仅比 Inception v3 低一点)。 有关更多信息,请参见其自述文件。

上一节中讨论的retrain.py脚本还支持基于 MobileNet 模型的重新训练。 只需运行如下命令:

代码语言:javascript复制
python tensorflow/examples/image_retraining/retrain.py  
  --output_graph=/tf_files/dog_retrained_mobilenet10_224.pb  
  --output_labels=/tf_files/dog_retrained_labels_mobilenet.txt  
  --image_dir ~/Downloads/Images  
  --bottleneck_dir=/tf_files/dogs_bottleneck_mobilenet  
  --architecture mobilenet_1.0_224 

生成的标签文件dog_retrained_labels_mobilenet.txt实际上与使用 Inception v3 模型进行再训练期间生成的标签文件相同。 --architecture参数指定 16 个 MobileNet 模型之一,而值mobilenet_1.0_224表示使用模型大小为 1.0 的模型(对于其他参数,其他三个可能的值分别为 0.75、0.50 和 0.25 – 1.0,相反,准确但最大的大小为 0.25)和 224 作为图像输入大小(其他三个值分别为 192、160 和 128)。 如果将_quantized添加到架构值的末尾,即--architecture mobilenet_1.0_224_quantized,则模型也将被量化,从而导致重新训练的模型大小约为 5.1MB。 非量化模型的大小约为 17MB。

您可以按以下步骤测试先前使用label_image生成的模型:

代码语言:javascript复制
bazel-bin/tensorflow/examples/label_image/label_image  
--graph=/tf_files/dog_retrained_mobilenet10_224.pb  
--image=/tmp/lab1.jpg  
--input_layer=input  
--output_layer=final_result  
--labels=/tf_files/dog_retrained_labels_mobilenet.txt  
--input_height=224  
--input_width=224  
--input_mean=128  
--input_std=128 

n02099712 labrador retriever (41): 0.824675 
n02099601 golden retriever (64): 0.144245 
n02104029 kuvasz (76): 0.0103533 
n02087394 rhodesian ridgeback (105): 0.00528782 
n02090379 redbone (32): 0.0035457 

请注意,在运行label_image时,input_layer名为input。 我们可以使用交互式 iPython 代码或之前看到的图摘要工具找到该名称:

代码语言:javascript复制
bazel-bin/tensorflow/tools/graph_transforms/summarize_graph  
--in_graph=/tf_files/dog_retrained_mobilenet10_224.pb 
Found 1 possible inputs: (name=input, type=float(1), shape=[1,224,224,3])  
No variables spotted. 
Found 1 possible outputs: (name=final_result, op=Softmax)  
Found 4348281 (4.35M) const parameters, 0 (0) variable parameters, and 0 control_edges 
Op types used: 92 Const, 28 Add, 27 Relu6, 15 Conv2D, 13 Mul, 13 DepthwiseConv2dNative, 10 Dequantize, 3 Identity, 1 MatMul, 1 BiasAdd, 1 Placeholder, 1 PlaceholderWithDefault, 1 AvgPool, 1 Reshape, 1 Softmax, 1 Squeeze 

那么,我们什么时候应该在移动设备上使用 Inception v3 或 MobileNet 重新训练的模型? 如果您想获得最高的准确率,则应该并且可以使用基于 Inception v3 的重新训练模型。 如果速度是您的首要考虑因素,则应考虑使用具有最小参数大小和图像输入大小的 MobileNet 重训练模型,以换取一些精度损失。

benchmark_model是为您提供模型精确基准的一种工具。 首先,将其构建如下:

代码语言:javascript复制
bazel build -c opt tensorflow/tools/benchmark:benchmark_model

然后,针对基于 Inception v3 或 MobileNet v1 的重新训练模型运行它:

代码语言:javascript复制
bazel-bin/tensorflow/tools/benchmark/benchmark_model  
--graph=/tf_files/quantized_stripped_dogs_retrained.pb  
--input_layer="Mul"  
--input_layer_shape="1,299,299,3"  
--input_layer_type="float"  
--output_layer="final_result"  
--show_run_order=false  
--show_time=false  
--show_memory=false  
--show_summary=true  

您将获得相当长的输出,最后会有一行像 FLOPS 的估计值:11.42B,这意味着它将使用基于 Inception v3 的重新训练模型约 11B FLOPS(浮点运算)进行推断。 iPhone 6 运行大约 2 B FLOPS,因此在 iPhone 6 上运行模型大约需要 5–6 秒。 其他现代智能手机可以运行 10B FLOPS。

通过将图文件替换为基于 MobileNet 模型的重新训练模型dog_retrained_mobilenet10_224.pb并重新运行基准测试工具,您将看到 FLOPS 估计值变为约 1.14B,大约快了 10 倍。

在示例 iOS 应用中使用经过重新训练的模型

我们在第 1 章,“移动 TensorFlow 入门”中看到的 iOS 简单示例使用了 Inception v1 模型。 为了使该应用使用我们经过重新训练的 Inception v3 模型和 MobileNet 模型来更好地识别狗的品种,我们需要对该应用进行一些更改。 首先,让我们看看在 iOS 简单应用中使用经过重新训练的quantized_stripped_dogs_retrained.pb会发生什么:

  1. 双击tensorflow/examples/ios/simple中的tf_simple_example.xcworkspace文件以 Xcode 打开应用
  2. 拖动我们用来测试label_image脚本的quantized_stripped_dogs_retrained.pb模型文件,dog_retrained_labels.txt标签文件和lab1.jpg图像文件,然后拖放到项目的数据文件夹中,并确保同时选中“按需复制项目”和“添加到目标”,如以下屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sbBZuuPp-1681653027409)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/d2e25f9d-c88d-4fe5-918d-ba7e64420682.png)]

图 2.5:将重新训练的模型文件和标签文件添加到应用

  1. 单击 Xco​​de 中的RunModelViewController.mm文件,该文件使用 TensorFlow C API 处理输入图像,通过 Inception v1 模型运行它,并获得图像分类结果,并更改行:
代码语言:javascript复制
NSString* network_path = FilePathForResourceName(@"tensorflow_inception_graph", @"pb");
NSString* labels_path = FilePathForResourceName(@"imagenet_comp_graph_label_strings", @"txt");
NSString* image_path = FilePathForResourceName(@"grace_hopper", @"jpg");

使用正确的模型文件名,标签文件名和测试图像名称进行以下操作:

代码语言:javascript复制
NSString* network_path = FilePathForResourceName(@"quantized_stripped_dogs_retrained", @"pb");
NSString* labels_path = FilePathForResourceName(@"dog_retrained_labels", @"txt");
NSString* image_path = FilePathForResourceName(@"lab1", @"jpg");
  1. 同样在RunModelViewController.mm中,要匹配我们的 Inception v3(从 v1)重新训练模型所需的输入图像大小,请将const int wanted_width = 224;const int wanted_height = 224;中的224值更改为299,并同时将const float input_mean = 117.0f;中的值 ]和const float input_std = 1.0f;128.0f
  2. 从以下项更改输入和输出节点名称的值:
代码语言:javascript复制
std::string input_layer = "input"; 
std::string output_layer = "output"; 

为以下正确值:

代码语言:javascript复制
std::string input_layer = "Mul"; 
std::string output_layer = "final_result"; 
  1. 最后,您可以编辑dog_retrained_labels.txt文件以删除每行中的前导nxxxx字符串(例如,删除n02099712 labrador retriever中的n02099712)– 在 Mac 上,您可以通过按住Option键然后进行选择和删除–从而使识别结果更具可读性

立即运行应用,然后单击运行模型按钮,在 Xcode 的控制台窗口或应用的编辑框中,您将看到以下识别结果,与运行label_image脚本的结果非常一致:

代码语言:javascript复制
Predictions: 41 0.645  labrador retriever 
64 0.195  golden retriever 
76 0.0261  kuvasz 
32 0.0133  redbone 
20 0.0127  beagle 

要使用 MobileNet(mobilenet_1.0_224_quantized)训练模型dog_retrained_mobilenet10_224.pb,请按照与之前类似的步骤进行操作,而在步骤 2 和 3 中,我们使用dog_retrained_mobilenet10_224.pb,但是在步骤 4 中,我们需要保留const int wanted_width = 224;const int wanted_height = 224;,仅将const float input_meanconst float input_std更改为128。 最后,在步骤 5 中,我们必须使用std::string input_layer = "input";std::string output_layer = "final_result";。 这些参数与dog_retrained_mobilenet10_224.pblabel_image脚本使用的参数相同。

再次运行该应用,您将看到类似的最佳识别结果。

在示例 Android 应用中使用经过重新训练的模型

在 Android 的“TF 分类”应用中使用经过重新训练的 Inception v3 模型和 MobileNet 模型也非常简单。 请按照此处的步骤测试两个重新训练的模型:

  1. 使用 Android Studio 打开位于tensorflow/examples/android中的示例 TensorFlow Android 应用。
  2. 将两个重新训练的模型quantized_stripped_dogs_retrained .pbdog_retrained_mobilenet10_224.pb以及标签文件dog_retrained_labels.txt拖放到 android 应用的assets文件夹中。
  3. 打开文件ClassifierActivity.java,以使用 Inception v3 训练后的模型,并替换以下代码:
代码语言:javascript复制
private static final int INPUT_SIZE = 224; 
private static final int IMAGE_MEAN = 117; 
private static final float IMAGE_STD = 1; 
private static final String INPUT_NAME = "input"; 
private static final String OUTPUT_NAME = "output"; 

这些行:

代码语言:javascript复制
private static final int INPUT_SIZE = 299; 
private static final int IMAGE_MEAN = 128; 
private static final float IMAGE_STD = 128; 
private static final String INPUT_NAME = "Mul"; 
private static final String OUTPUT_NAME = "final_result"; 
private static final String MODEL_FILE = "file:///android_asset/quantized_stripped_dogs_retrained.pb"; 
private static final String LABEL_FILE = 
"file:///android_asset/dog_retrained_labels.txt";  
  1. 或者,要使用 MobileNet 训练后的模型,请用以下代码行替换代码:
代码语言:javascript复制
private static final int INPUT_SIZE = 224; 
private static final int IMAGE_MEAN = 128; 
private static final float IMAGE_STD = 128; 
private static final String INPUT_NAME = "input"; 
private static final String OUTPUT_NAME = "final_result"; 
private static final String MODEL_FILE = "file:///android_asset/dog_retrained_mobilenet10_224.pb"; 
private static final String LABEL_FILE = "file:///android_asset/dog_retrained_labels.txt";
  1. 将 Android 设备连接到计算机并在其上运行该应用。 然后点击 TF 分类应用,将相机指向一些狗的照片,您将在屏幕上看到最佳结果。

这就是在示例 TensorFlow iOS 和 Android 应用中使用两个经过重新训练的模型所需要的全部。 既然您已经了解了如何在示例应用中使用经过重新训练的模型,那么您可能想知道的下一件事是如何将 TensorFlow 添加到自己的新的或现有的 iOS 或 Android 应用中,以便可以开始添加 AI 对您自己的移动应用的强大功能。 这就是本章其余部分将详细讨论的内容。

将 TensorFlow 添加到您自己的 iOS 应用

在 TensorFlow 的早期版本中,将 TensorFlow 添加到您自己的应用非常繁琐,需要使用 TensorFlow 的手动构建过程和其他手动设置。 在 TensorFlow 1.4 中,该过程非常简单,但在 TensorFlow 网站上并未详细记录详细步骤。 缺少的另一件事是缺少有关如何在基于 Swift 的 iOS 应用中使用 TensorFlow 的文档; 示例 TensorFlow iOS 应用都在 Objective-C 中,它们调用了 TensorFlow 的 C API。 让我们看看我们如何做得更好。

将 TensorFlow 添加到您的 Objective-C iOS 应用

首先,按照以下步骤将具有图像分类功能的 TensorFlow 添加到您的 Objective-C iOS 应用(我们将从一个新应用开始,但是如果需要将 TensorFlow 添加到现有应用,则可以跳过第一步):

  1. 在您的 Xcode 中,单击“文件 | 新增 | 项目 …”,选择“Single View App”,然后选择接下来的,输入HelloTensorFlow作为产品名称,选择 Obj-C 作为语言,然后单击接下来并选择项目的位置,然后单击创建。 关闭 Xcode 中的项目窗口(因为我们稍后将使用 Pod 来打开项目的工作区文件)。
  2. 打开一个终端窗口,即cd到项目所在的位置,然后创建一个名为Podfile的新文件,其内容如下:
代码语言:javascript复制
target 'HelloTensorFlow' 
pod 'TensorFlow-experimental'
  1. 运行命令pod install下载并安装 TensorFlow Pod。
  2. 在 Xcode 中打开HelloTensorFlow.xcworkspace文件,然后将两个文件(ios_image_load.mmios_image_load.h)拖放到 TensorFlow iOS 示例目录tensorflow/examples/ios/simpleHelloTensorFlow项目文件夹中。
  3. 将两个模型quantized_stripped_dogs_retrained.pbdog_retrained_mobilenet10_224.pblabel file dog_retrained_labels.txt以及几个测试图像文件拖放到项目文件夹中,之后,您应该看到类似以下的内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KEomP26v-1681653027410)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/b89b3444-a6a9-45b4-bb02-ddf8388a6992.png)]

图 2.6:添加工具文件,模型文件,标签文件和图像文件

  1. ViewController.m重命名为ViewController.mm,因为我们将在该文件中混合使用 C 代码和 Objective-C 代码来调用 TensorFlow C API 并处理图像输入和推断结果。 然后,在@interface ViewController之前,添加以下#include和函数原型:
代码语言:javascript复制
#include <fstream> 
#include <queue> 
#include "tensorflow/core/framework/op_kernel.h" 
#include "tensorflow/core/public/session.h" 
#include "ios_image_load.h" 

NSString* RunInferenceOnImage(int wanted_width, int wanted_height, std::string input_layer, NSString *model); 
  1. ViewController.mm的末尾,添加从tensorflow/example/ios/simple/RunModelViewController.mm复制的以下代码,对函数RunInferenceOnImage稍作更改,以接受具有不同输入大小和输入层名称的不同再训练模型:
代码语言:javascript复制
namespace { 
    class IfstreamInputStream : public ::google::protobuf::io::CopyingInputStream { 
... 
static void GetTopN( 
... 
bool PortableReadFileToProto(const std::string& file_name, 
... 
NSString* FilePathForResourceName(NSString* name, NSString* extension) { 
... 
NSString* RunInferenceOnImage(int wanted_width, int wanted_height, std::string input_layer, NSString *model) { 
  1. 仍然在viewDidLoad方法的ViewController.mm中,首先添加添加标签的代码,以使用户知道他们可以使用该应用执行的操作:
代码语言:javascript复制
UILabel *lbl = [[UILabel alloc] init]; 
[lbl setTranslatesAutoresizingMaskIntoConstraints:NO]; 
lbl.text = @"Tap Anywhere"; 
[self.view addSubview:lbl];  

然后将标签置于屏幕中央的约束:

代码语言:javascript复制
NSLayoutConstraint *horizontal = [NSLayoutConstraint constraintWithItem:lbl attribute:NSLayoutAttributeCenterX 
relatedBy:NSLayoutRelationEqual toItem:self.view 
attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]; 

NSLayoutConstraint *vertical = [NSLayoutConstraint constraintWithItem:lbl attribute:NSLayoutAttributeCenterY 
relatedBy:NSLayoutRelationEqual toItem:self.view 
attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]; 

[self.view addConstraint:horizontal]; 
[self.view addConstraint:vertical];  

最后,在此处添加点击手势识别器:

代码语言:javascript复制
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; 
[self.view addGestureRecognizer:recognizer]; 
  1. 在轻敲处理器中,我们首先创建两个alert操作,以允许用户选择重新训练的模型:
代码语言:javascript复制
UIAlertAction* inceptionV3 = [UIAlertAction actionWithTitle:@"Inception v3 Retrained Model" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { 
        NSString *result = RunInferenceOnImage(299, 299, "Mul", @"quantized_stripped_dogs_retrained"); 
        [self showResult:result]; 
}]; 
UIAlertAction* mobileNet = [UIAlertAction actionWithTitle:@"MobileNet 1.0 Retrained Model" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { 
        NSString *result = RunInferenceOnImage(224, 224, "input", @"dog_retrained_mobilenet10_224"); 
        [self showResult:result]; 
}]; 

然后创建一个none操作,并将所有三个alert操作添加到警报控制器并显示它:

代码语言:javascript复制
UIAlertAction* none = [UIAlertAction actionWithTitle:@"None" style:UIAlertActionStyleDefault 
        handler:^(UIAlertAction * action) {}]; 

UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Pick a Model" message:nil preferredStyle:UIAlertControllerStyleAlert]; 
[alert addAction:inceptionV3]; 
[alert addAction:mobileNet]; 
[alert addAction:none]; 
[self presentViewController:alert animated:YES completion:nil]; 
  1. 推断的结果在方法showResult中显示为另一个警报控制器:
代码语言:javascript复制
-(void) showResult:(NSString *)result { 
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Inference Result" message:result preferredStyle:UIAlertControllerStyleAlert]; 

    UIAlertAction* action = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; 
    [alert addAction:action]; 
    [self presentViewController:alert animated:YES completion:nil]; 
}  

与调用 TensorFlow 相关的核心代码在RunInferenceOnImage方法中,该方法基于 TensorFlow iOS 简单应用进行了略微修改,包括首先创建一个 TensorFlow 会话和一个图:

代码语言:javascript复制
tensorflow::Session* session_pointer = nullptr; 
tensorflow::Status session_status = tensorflow::NewSession(options, &session_pointer); 
... 
std::unique_ptr<tensorflow::Session> session(session_pointer); 
tensorflow::GraphDef tensorflow_graph; 
NSString* network_path = FilePathForResourceName(model, @"pb"); 
PortableReadFileToProto([network_path UTF8String], &tensorflow_graph); 
tensorflow::Status s = session->Create(tensorflow_graph); 

然后加载标签文件和图像文件,并将图像数据转换为适当的 Tensor 数据:

代码语言:javascript复制
NSString* labels_path = FilePathForResourceName(@"dog_retrained_labels", @"txt"); 
... 
NSString* image_path = FilePathForResourceName(@"lab1", @"jpg"); 
std::vector<tensorflow::uint8> image_data = LoadImageFromFile([image_path UTF8String], &image_width, &image_height, &image_channels); 
tensorflow::Tensor image_tensor(tensorflow::DT_FLOAT, tensorflow::TensorShape({1, wanted_height, wanted_width, wanted_channels})); 
auto image_tensor_mapped = image_tensor.tensor<float, 4>(); 
tensorflow::uint8* in = image_data.data(); 
float* out = image_tensor_mapped.data(); 
for (int y = 0; y < wanted_height;   y) { 
    const int in_y = (y * image_height) / wanted_height; 
... 
} 

最后,使用图像张量数据和输入层名称调用 TensorFlow 会话的run方法,获取返回的输出结果,并对其进行处理以获取置信度值大于阈值的前五个结果:

代码语言:javascript复制
std::vector<tensorflow::Tensor> outputs; 
tensorflow::Status run_status = session->Run({{input_layer, image_tensor}},{output_layer}, {}, &outputs); 
... 
tensorflow::Tensor* output = &outputs[0]; 
const int kNumResults = 5; 
const float kThreshold = 0.01f; 
std::vector<std::pair<float, int> > top_results; 
GetTopN(output->flat<float>(), kNumResults, kThreshold, &top_results); 

在本书的其余部分,我们将实现RunInferenceOnxxx方法的不同版本,以使用不同的输入来运行不同的模型。 因此,如果您不完全理解前面的一些代码,请不要担心; 通过构建更多的应用,您将为新的自定义模型编写自己的推理逻辑而感到自在。

此外,完整的 iOS 应用 HelloTensorFlow 也包含在本书的源代码存储库中。

现在,在模拟器中或实际的 iOS 设备上运行该应用,首先,您将看到以下消息框,要求您选择重新训练的模型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p0nAa1Pm-1681653027410)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/1823f6a6-f212-4ac9-9d78-a978a234c4aa.png)]

图 2.7:选择不同的再训练模型进行推理

然后,您将在选择模型后看到推断结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KS4OFUYg-1681653027410)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/b9de1c02-8b3a-4482-b7f5-4d0d8d3e4692.png)]

图 2.8:基于不同再训练模型的推理结果

请注意,MobileNet 训练后的模型在同一款 iPhone 上的运行速度要快得多,在 iPhone 6 上要比 Inception v3 训练后的模型快约一秒钟。

将 TensorFlow 添加到您的 Swift iOS 应用

自 2014 年 6 月诞生以来,Swift 已成为最优雅的现代编程语言之一。因此,对于某些开发人员而言,将现代 TensorFlow 集成到其基于 Swift 的现代 iOS 应用中既有趣又有用。 这样做的步骤与基于 Objective-C 的应用的步骤相似,但具有一些与 Swift 相关的技巧。 如果您已经按照 Objective-C 部分的步骤进行操作,则可能会发现这里的某些步骤是重复的,但是对于那些可能会跳过 Objective-C 部分并直接进入 Swift 的用户而言,仍然提供了完整的步骤:

  1. 在您的 Xcode 中,单击“文件 | 新增 | 项目…”,选择“Single View App”,然后接下来的,输入HelloTensorFlow_Swift作为产品名称,选择 Swift 将设置为语言,然后单击接下来并选择项目的位置,然后单击创建。 关闭 Xcode 中的项目窗口(因为稍后将使用 Pod 来打开项目的工作区文件)。
  2. 打开一个终端窗口,即cd到项目所在的位置,然后创建一个名为Podfile的新文件,其内容如下:
代码语言:javascript复制
target 'HelloTensorFlow_Swift' 
pod 'TensorFlow-experimental' 
  1. 运行命令pod install下载并安装 TensorFlow Pod;
  2. 在 Xcode 中打开HelloTensorFlow_Swift.xcworkspace文件,然后将两个文件(ios_image_load.mmios_image_load.h)拖放到 TensorFlow iOS 示例目录tensorflow/examples/ios/simpleHelloTensorFlow_Swift项目文件夹中。 当将两个文件添加到项目中时,您将看到一个消息框,如以下屏幕截图所示,询问您是否要配置 Objective-C 桥接头,Swift 代码调用 C 头需要此头。 Objective-C 代码。 因此,单击创建桥接标题按钮:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-34n2r9ES-1681653027411)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/afa22723-f59f-4544-8a61-e73ee7c43e85.png)]

图 2.9:添加 C 文件时创建桥接标题

  1. 同样将quantized_stripped_dogs_retrained .pbdog_retrained_mobilenet10_224.pb这两个模型,标签文件dog_retrained_labels.txt和几个测试图像文件拖放到项目文件夹中–之后,您应该会看到类似以下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FEDYBpgY-1681653027411)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/8236a55a-15fe-41a1-9ff7-c7280ae6c43f.png)]

图 2.10:添加工具文件,模型文件,标签文件和图像文件

  1. 使用以下代码创建一个名为RunInference.h的新文件(一个窍门是,我们必须在下一步中使用 Objective-C 类作为RunInferenceOnImage方法的包装,以便我们的 Swift 代码进行间接调用) 。否则,将发生构建错误):
代码语言:javascript复制
#import <Foundation/Foundation.h> 
@interface RunInference_Wrapper : NSObject 
    - (NSString *)run_inference_wrapper:(NSString *)name; 
@end
  1. 创建另一个名为RunInference.mm的文件,该文件以以下include对象和原型开头:
代码语言:javascript复制
#include <fstream> 
#include <queue> 
#include "tensorflow/core/framework/op_kernel.h" 
#include "tensorflow/core/public/session.h" 
#include "ios_image_load.h" 

NSString* RunInferenceOnImage(int wanted_width, int wanted_height, std::string input_layer, NSString *model); 
  1. 在以下代码中添加RunInference.mm,以实现在其.h文件中定义的RunInference_Wrapper
代码语言:javascript复制
@implementation RunInference_Wrapper 
- (NSString *)run_inference_wrapper:(NSString *)name { 
    if ([name isEqualToString:@"Inceptionv3"]) 
        return RunInferenceOnImage(299, 299, "Mul", @"quantized_stripped_dogs_retrained"); 
    else 
        return RunInferenceOnImage(224, 224, "input", @"dog_retrained_mobilenet10_224"); 
} 
@end     
  1. RunInference.mm的末尾,添加与 Objective-C 部分中ViewController.mm完全相同的方法,与tensorflow/example/ios/simple/RunModelViewController.mm中的方法略有不同:
代码语言:javascript复制
class IfstreamInputStream : public namespace { 
    class IfstreamInputStream : public ::google::protobuf::io::CopyingInputStream { 
... 
static void GetTopN( 
... 
bool PortableReadFileToProto(const std::string& file_name, 
... 
NSString* FilePathForResourceName(NSString* name, NSString* extension) { 
... 
NSString* RunInferenceOnImage(int wanted_width, int wanted_height, std::string input_layer, NSString *model) {
  1. 现在打开viewDidLoad method末尾的ViewController.swift,首先添加添加标签的代码,以使用户知道他们可以使用该应用做什么:
代码语言:javascript复制
let lbl = UILabel() 
lbl.translatesAutoresizingMaskIntoConstraints = false 
lbl.text = "Tap Anywhere" 
self.view.addSubview(lbl) 

然后将标签置于屏幕中央的约束:

代码语言:javascript复制
let horizontal = NSLayoutConstraint(item: lbl, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0) 

let vertical = NSLayoutConstraint(item: lbl, attribute: .centerY, relatedBy: .equal, toItem: self.view, attribute: .centerY, multiplier: 1, constant: 0) 

self.view.addConstraint(horizontal) 
self.view.addConstraint(vertical) 

最后,在此处添加点击手势识别器:

代码语言:javascript复制
let recognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.tapped(_:))) 
self.view.addGestureRecognizer(recognizer) 
  1. 在轻击处理器中,我们首先添加alert动作,以允许用户选择 Inception v3 训练后的模型:
代码语言:javascript复制
let alert = UIAlertController(title: "Pick a Model", message: nil, preferredStyle: .actionSheet) 
alert.addAction(UIAlertAction(title: "Inception v3 Retrained Model", style: .default) { action in 
    let result = RunInference_Wrapper().run_inference_wrapper("Inceptionv3") 

    let alert2 = UIAlertController(title: "Inference Result", message: result, preferredStyle: .actionSheet) 
    alert2.addAction(UIAlertAction(title: "OK", style: .default) { action2 in 
    }) 
    self.present(alert2, animated: true, completion: nil) 

}) 

然后在展示之前,为 MobileNet 重训练模型创建另一个动作以及一个none动作:

代码语言:javascript复制
alert.addAction(UIAlertAction(title: "MobileNet 1.0 Retrained Model", style: .default) { action in 
    let result = RunInference_Wrapper().run_inference_wrapper("MobileNet") 

    let alert2 = UIAlertController(title: "Inference Result", message: result, preferredStyle: .actionSheet) 
    alert2.addAction(UIAlertAction(title: "OK", style: .default) { action2 in 
    }) 
    self.present(alert2, animated: true, completion: nil) 
}) 
alert.addAction(UIAlertAction(title: "None", style: .default) { action in 
}) 

self.present(alert, animated: true, completion: nil) 
  1. 打开HelloTensorFlow_Swift-Bridging-Header.h文件,并向其中添加一行代码:#include "RunInference.h"

现在,在模拟器中运行该应用,您将看到一个警报控制器,要求您选择模型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jnVb7EBf-1681653027411)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/098c0b7a-c32a-4ffc-bf3d-49c1ea80d36f.png)]

图 2.11:选择一个经过训练的模型进行推理

以及不同再训练模型的推理结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HA5gG3Tz-1681653027411)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/f3d05b5e-fda7-46d6-8e47-f234a1b4175e.png)]

图 2.12:不同再训练模型的推理结果

继续,既然您知道了将强大的 TensorFlow 模型添加到 iOS 应用需要做什么,无论它是用 Objective-C 还是 Swift 编写的,都没有理由阻止您将 AI 添加到您的移动应用中,除非您是 Android。 但是您知道我们当然也会照顾 Android。

将 TensorFlow 添加到您自己的 Android 应用

事实证明,将 TensorFlow 添加到自己的 Android 应用比 iOS 容易。 让我们跳到步骤:

  1. 如果您有现有的 Android 应用,请跳过此步骤。 否则,在 Android Studio 中,选择“文件 | 新增 | 新项目…”并接受所有默认设置,然后单击完成。
  2. 打开build.gradleModule: app)文件,并在依赖项{...};内部和末尾添加编译'org.tensorflow:tensorflow-android: '
  3. 生成gradle文件,您将在app目录的位置app/build/intermediates/transforms/mergeJniLibs/debug/0/lib的子文件夹内看到libtensorflow_inference.so,这是 Java 代码与之对话的 TensorFlow 本机库。
  4. 如果这是一个新项目,则可以通过首先切换到包,然后右键单击该应用并选择“新建 | 文件夹 | assets文件夹”来创建assets文件夹。 ,如以下屏幕截图所示,然后从包切换回 Android:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xTCPEphz-1681653027412)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/6cf6b889-8ecb-4837-9087-19b34d6e68ed.png)]

图 2.13:将素材文件夹添加到新项目

  1. 将两个重新训练的模型文件和标签文件以及几个测试图像拖放到资产文件夹中,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SH8Sgc83-1681653027412)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/fff27710-5222-410c-8d40-f451dea548f8.png)]

图 2.14:将模型文件,标签文件和测试图像添加到素材

  1. 按住选项按钮,将tensorflow/examples/android/src/org/tensorflow/demoClassifier.javatensorflow/examples/android/src/org/tensorflow/demo拖放到项目的 Java 文件夹中,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kharFPaj-1681653027412)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/7f41c4fe-6454-44e2-82d8-892290761eb5.png)]

图 2.15:将 TensorFlow 分类器文件添加到项目中

  1. 打开MainActivity,首先创建与重新训练的 MobileNet 模型相关的常数-输入图像大小,节点名称,模型文件名和标签文件名:
代码语言:javascript复制
private static final int INPUT_SIZE = 224; 
private static final int IMAGE_MEAN = 128; 
private static final float IMAGE_STD = 128; 
private static final String INPUT_NAME = "input"; 
private static final String OUTPUT_NAME = "final_result"; 

private static final String MODEL_FILE = "file:///android_asset/dog_retrained_mobilenet10_224.pb"; 
private static final String LABEL_FILE = "file:///android_asset/dog_retrained_labels.txt"; 
private static final String IMG_FILE = "lab1.jpg";
  1. 现在,在onCreate方法内部,首先创建一个Classifier实例:
代码语言:javascript复制
Classifier classifier = TensorFlowImageClassifier.create( 
                getAssets(), 
                MODEL_FILE, 
                LABEL_FILE, 
                INPUT_SIZE, 
                IMAGE_MEAN, 
                IMAGE_STD, 
                INPUT_NAME, 
                OUTPUT_NAME);  

然后从assets文件夹中读取我们的测试图像,根据模型指定的大小进行调整,然后调用推理方法recognizeImage

代码语言:javascript复制
Bitmap bitmap = BitmapFactory.decodeStream(getAssets().open(IMG_FILE)); 
Bitmap croppedBitmap = Bitmap.createScaledBitmap(bitmap, INPUT_SIZE, INPUT_SIZE, true); 
final List<Classifier.Recognition> results = classifier.recognizeImage(croppedBitmap); 

为简单起见,我们没有向 Android 应用添加任何与 UI 相关的代码,但是您可以在获取结果后在此行设置一个断点,并调试运行该应用; 您将看到以下屏幕截图所示的结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XIyeYzNW-1681653027412)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/4c91b3a9-1a32-4288-aa75-205d0b71d584.png)]

图 2.16:使用 MobileNet 再训练模型的识别结果

如果您通过将MODEL_FILE更改为quantized_stripped_dogs_retrained.pb,将INPUT_SIZE更改为299,并且将INPUT_NAME更改为Mul来使用 Inception v3 训练后的模型,则调试该应用,您将获得如下所示的结果 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1s92KyhD-1681653027412)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/ca49452a-b256-4c9e-8e96-6de4c75effa7.png)]

图 2.17:使用 Inception v3 再训练模型的识别结果

既然您已经了解了如何将 TensorFlow 和经过重新训练的模型添加到自己的 iOS 和 Android 应用,那么如果想要添加非 TensorFlow 相关功能应该不会太难。例如使用手机的摄像头拍摄狗的照片和识别它的品种。

总结

在本章中,我们首先简要介绍了什么是迁移学习以及为什么我们能够并且应该使用它来重新训练经过预训练的深度学习图像分类模型。 然后,我们提供了有关如何重新训练基于 Inception v3 的模型和 MobileNet 模型的详细信息,以便我们可以更好地理解和认识我们最好的朋友。 之后,我们首先展示了如何在 TensorFlow 示例 iOS 和 Android 应用中使用经过重新训练的模型,然后给出了有关如何将 TensorFlow 添加到您自己的基于 Objective-C 和 Swift 的 iOS 应用中的分步教程,以及您自己的 Android 应用。

现在我们有了最好的朋友,其中涵盖了一些不错而干净的技巧,我们知道还有很多其他人,无论好坏。 在下一章中,我们将学习如何变得更聪明,如何识别图片中所有有趣的对象以及如何在智能手机上随时随地对其进行定位。

三、检测物体及其位置

对象检测比上一章中讨论的图像分类迈出了一步。 图像分类仅返回图像的类别标签,而对象检测返回图像中标识的对象列表以及每个标识对象的边界框。 现代的对象检测算法使用深度学习来构建可用于检测和定位单个图像中各种对象的模型。 在过去的几年中,更快,更准确的对象检测算法相继问世.2017 年 6 月,谷歌发布了 TensorFlow 对象检测 API,该 API 集成了几种领先的对象检测算法。

在本章中,我们将首先简要概述对象检测:创建有效的深度学习模型进行对象检测,然后使用该模型进行推理的过程。 然后,我们将详细讨论 TensorFlow 对象检测 API 的工作原理,如何使用其多个模型进行推理以及如何使用自己的数据集对其进行重新训练。 然后,我们将向您展示如何在 iOS 应用中使用预训练的对象检测模型以及重新训练的模型。 我们将介绍一些强大的技巧,使您可以手动构建自定义的 TensorFlow iOS 库,以解决使用 TensorFlow Pod 的问题; 这将帮助您准备好处理本书其余部分中介绍的任何受 TensorFlow 支持的模型。 在本章中,我们将不提供用于对象检测的 Android 示例应用,因为 TensorFlow 源代码已经附带了一个很好的示例,可以使用 TensorFlow 对象检测预训练模型以及 YOLO 模型进行操作。 我们将在本章最后介绍。 我们将向您展示如何在 iOS 应用中使用另一种领先的物体检测模型 YOLO v2。 总而言之,我们将在本章中介绍以下主题:

  • 物体检测:快速概述
  • 设置 TensorFlow 对象检测 API
  • 重新训练 SSD-MobileNet 和更快的 RCNN 模型
  • 在 iOS 中使用对象检测模型
  • 使用 YOLO2:另一种物体检测模型

对象检测 – 快速概述

自从 2012 年神经网络取得突破以来,当名为 AlexNet 的深层 CNN 模型通过大大降低错误率赢得了年度 ImageNet 视觉识别挑战时,许多计算机视觉和自然语言处理领域的研究人员就开始利用深度学习模型的强大功能这一优势。 基于深度学习的现代对象检测全部基于 CNN,并建立在诸如 AlexNet,Google Inception 或其他流行的 VGG 网络等预训练的模型之上。 这些 CNN 通常已经训练了数百万个参数,并且可以将输入图像转换为一组特征,这些特征可以进一步用于诸如上一章中涉及的图像分类以及对象检测以及其他与计算机视觉相关的任务。

2014 年,提出了一种最新的对象检测器,该对象检测器使用称为 RCNN(具有 CNN 特征的区域)的标记对象检测数据集对 AlexNet 进行了训练,与传统的检测方法相比,它在准确率上有了很大的提高。 RCNN 结合了一种称为区域提议的技术,该技术可生成大约 2,000 个可能的区域候选者,并在每个这些区域上运行 CNN 以进行分类和边界框预测。 然后,将这些结果合并以生成检测结果。 RCNN 的训练过程非常复杂,耗时数天,推理速度也很慢,在 GPU 上的图像上花费了将近一分钟。

自从提出 RCNN 以来,表现更好的对象检测算法纷至沓来:快速 RCNN,更快的 RCNN,YOLO(您只看一次),SSD(单发多框检测器)和 YOLO v2。

2014 年,Andrej Karpathy 对 RCNN 作了很好的介绍,“玩转 RCNN,先进的物体检测器”。 贾斯汀·约翰逊(Justin Johnson)在斯坦福大学 CS231n 课程中提供了一个很好的视频讲座“空间定位和检测”,内容涉及物体检测,其中包括 RCNN,Fast RCNN,Faster RCNN 和 YOLO 的详细信息。 YOLO2 网站是这里。

快速 RCNN 通过首先在整个输入图像上而不是数千个建议的区域上应用 CNN,然后处理区域建议,从而显着改善了训练过程和推理时间(10 小时的训练和 2.x 秒的推理)。 更快的 RCNN 通过使用区域建议网络进一步将推理速度提高到了实时(0.2 秒),因此在训练后,不再需要耗时的区域建议过程。

与 RCNN 检测系列不同,SSD 和 YOLO 都是单发方法,这意味着它们将单个 CNN 应用于完整的输入图像,而无需使用区域建议和区域分类。 这使这两种方法都非常快,它们的平均平均精度(mAP)约为 80%,优于 Faster RCNN。

如果这是您第一次听说这些方法,则可能会感到有些迷茫。 但是,作为对使用 AI 增强移动应用功能感兴趣的开发人员,您无需了解设置深度神经网络架构和训练对象检测模型的所有细节; 您应该只知道如何使用以及(如果需要)重新训练经过预训练的模型,以及如何在 iOS 和 Android 应用中使用经过预训练或重新训练的模型。

如果您真的对深度学习研究感兴趣,并且想知道每个检测器如何工作以决定使用哪种检测器的所有细节,那么您绝对应该阅读每种方法的论文,并尝试自己复制训练过程。 这将是一条漫长而有益的道路。 但是,如果您想听 Andrej Karpathy 的建议,“不要成为英雄”(在 YouTube 上搜索“Andrej 的计算机视觉深度学习”),那么您可以“采取最有效的方法,下载经过预训练的模型, 添加/删除其中的某些部分,然后在您的应用上对其进行微调”,这也是我们将在此处使用的方法。

在开始研究哪种方法最适合 TensorFlow 之前,让我们快速了解一下数据集。 有 3 个主要的数据集用于训练对象检测:PASCAL VOC,ImageNet 和 Microsoft COCO,它们具有的类数分别为 20、200 和 80。 TensorFlow 对象检测 API 当前支持的大多数预训练模型都在 80 级 MS COCO 数据集上进行了训练(有关预训练模型及其训练的数据集的完整列表,请参见这里。

尽管我们不会从头开始进行训练,但是您会经常提到 PASCAL VOC 或 MS COCO 数据格式,以及它们涵盖的 20 或 80 个通用类,它们都是在重新训练或使用经过训练的模型时使用的。 在本章的最后部分,我们将尝试使用 VOC 训练的 YOLO 模型和 COCO 训练的模型。

设置 TensorFlow 对象检测 API

TensorFlow 对象检测 API 在其官方网站上有详细记录,您一定要查看其“快速入门:用于现成的推断的 Jupyter 笔记本”指南,快速介绍了如何在 Python 中使用良好的预训练模型进行检测。 但是那里的文档分布在许多不同的页面上,有时难以理解。 在本节和下一节中,我们将通过重组在许多地方记录的重要细节并添加更多示例和代码说明来简化官方文档,并提供有关以下内容的两个分步教程:

  1. 如何设置 API 并使用其预训练的模型进行现成的推断
  2. 如何使用 API​​重新训练预训练模型以执行更具体的检测任务

快速安装和示例

执行以下步骤来安装和运行对象检测推断:

  1. 在第 1 章,“移动 TensorFlow 入门”中创建的 TensorFlow 源根中,获取 TensorFlow 模型存储库,其中包含 TensorFlow 对象检测 API 作为其研究模型之一:
代码语言:javascript复制
git clone https://github.com/tensorflow/models
  1. 安装matplotlibpillowlxmljupyter库。 在 Ubuntu 或 Mac 上,您可以运行:
代码语言:javascript复制
sudo pip install pillow
sudo pip install lxml
sudo pip install jupyter
sudo pip install matplotlib
  1. 转到models / research目录,然后运行以下命令:
代码语言:javascript复制
protoc object_detection/protos/*.proto --python_out=.

这将编译object_detection/protos目录中的所有 Protobuf,以使 TensorFlow 对象检测 API 满意。 Protobuf 或 Protocol Buffer 是一种自动序列化和检索结构化数据的方法,它比 XML 轻巧且效率更高。 您所需要做的就是编写一个描述数据结构的.proto文件,然后使用protoc(proto 编译器)生成自动解析和编码 protobuf 数据的代码。 注意--python_out参数指定了所生成代码的语言。 在本章的下一部分中,当我们讨论如何在 iOS 中使用模型时,我们将使用带有--cpp_out的协议编译器,因此生成的代码是 C 。 有关协议缓冲区的完整文档,请参见这里。

  1. 仍在模型/研究中,运行export PYTHONPATH=$PYTHONPATH:pwd:pwd/slim,然后运行python object_detection/builders/model_builder_test.py以验证一切正常。
  2. 启动jupyter notebook命令并在浏览器中打开http://localhost:8888。 首先单击object_detection,然后选择object_detection_tutorial.ipynb笔记本并逐个单元运行演示。

使用预训练的模型

现在让我们来看一下使用预训练的 TensorFlow 对象检测模型在 Python 笔记本中进行推理的主要组件。 首先,定义一些关键常量:

代码语言:javascript复制
MODEL_NAME = 'ssd_mobilenet_v1_coco_2017_11_17'
MODEL_FILE = MODEL_NAME   '.tar.gz'
DOWNLOAD_BASE = 'http://download.tensorflow.org/models/object_detection/'
PATH_TO_CKPT = MODEL_NAME   '/frozen_inference_graph.pb'
PATH_TO_LABELS = os.path.join('data', 'mscoco_label_map.pbtxt')
NUM_CLASSES = 90

笔记本代码下载并使用了预训练的对象检测模型ssd_mobilenet_v1_coco_2017_11_17(使用 SSD 方法构建,我们在上一章中介绍的 MobileNet CNN 模型之上,在上一节中进行了简要介绍)。 TensorFlow 检测模型动物园中提供了 TensorFlow 对象检测 API 支持的预训练模型的完整列表,并且大多数都是使用 MS COCO 数据集进行训练的。 用于推理的确切模型是frozen_inference_graph.pb文件(在下载的ssd_mobilenet_v1_coco_2017_11_17.tar.gz文件中),该模型用于现成的推理以及重新训练。

位于models/research/object_detection/data/mscoco_label_map.pbtxt中的mscoco_label_map.pbtxt标签文件具有 90(NUM_CLASSES)个项目,用于ssd_mobilenet_v1_coco_2017_11_17模型可以检测到的对象类型。 它的前两个项目是:

代码语言:javascript复制
item {
  name: "/m/01g317"
  id: 1
  display_name: "person"
}
item {
  name: "/m/0199g"
  id: 2
  display_name: "bicycle"
}
…
item {
  name: "/m/03wvsk"
  id: 89
  display_name: "hair drier"
}
item {
  name: "/m/012xff"
  id: 90
  display_name: "toothbrush"
}

我们在前面的步骤 3 中讨论了 Protobuf,描述mscoco_label_map.pbtxt中数据的 proto 文件是string_int_label_map.proto,位于models/research/object_detection/protos中,其内容如下:

代码语言:javascript复制
syntax = "proto2";
package object_detection.protos;
message StringIntLabelMapItem {
  optional string name = 1;
  optional int32 id = 2;
  optional string display_name = 3;
};

message StringIntLabelMap {
  repeated StringIntLabelMapItem item = 1;
};

因此,基本上,协议编译器基于string_int_label_map.proto创建代码,然后可以使用该代码有效地序列化mscoco_label_map.pbtxt中的数据。 稍后,当 CNN 检测到对象并返回整数 ID 时,可以将其转换为namedisplay_name供人类阅读。

将模型下载,解压缩并加载到内存中后,标签映射文件也将加载,并且位于models/research/object_detection/test_images的一些测试图像可以在其中添加您自己的任何测试图像以进行检测测试。 。 接下来,定义适当的输入和输出张量:

代码语言:javascript复制
with detection_graph.as_default():
  with tf.Session(graph=detection_graph) as sess:
    image_tensor = detection_graph.get_tensor_by_name('image_tensor:0')
    detection_boxes = detection_graph.get_tensor_by_name('detection_boxes:0')
    detection_scores = detection_graph.get_tensor_by_name('detection_scores:0')
    detection_classes = detection_graph.get_tensor_by_name('detection_classes:0')
    num_detections = detection_graph.get_tensor_by_name('num_detections:0')

再次,如果您想知道这些输入和输出张量名称来自models/research/object_detection/ssd_mobilenet_v1_coco_2017_11_17/frozen_inference_graph.pb中下载并保存的 SSD 模型中,那么您可以在 iPython 中使用以下代码来查找:

代码语言:javascript复制
import tensorflow as tf
g=tf.GraphDef()
g.ParseFromString(open("object_detection/ssd_mobilenet_v1_coco_2017_11_17/frozen_inference_graph.pb","rb").read())
x=[n.name for n in g.node]
x[-4:]
x[:5]
The last two statements will return: 
[u'detection_boxes',
 u'detection_scores',
 u'detection_classes',
 u'num_detections']
and
[u'Const', u'Const_1', u'Const_2', u'image_tensor', u'ToFloat']

另一种方法是使用上一章中描述的汇总图工具:

代码语言:javascript复制
bazel-bin/tensorflow/tools/graph_transforms/summarize_graph --in_graph= models/research/object_detection/ssd_mobilenet_v1_coco_2017_11_17/frozen_inference_graph.pb

这将生成以下输出:

代码语言:javascript复制
Found 1 possible inputs: (name=image_tensor, type=uint8(4), shape=[?,?,?,3])
No variables spotted. 
Found 4 possible outputs: (name=detection_boxes, op=Identity) (name=detection_scores, op=Identity (name=detection_classes, op=Identity) (name=num_detections, op=Identity)

加载每个测试映像后,将运行实际检测:

代码语言:javascript复制
image = Image.open(image_path)
image_np = load_image_into_numpy_array(image)
image_np_expanded = np.expand_dims(image_np, axis=0)
(boxes, scores, classes, num) = sess.run(
    [detection_boxes, detection_scores, detection_classes, num_detections],
    feed_dict={image_tensor: image_np_expanded})

最后,使用matplotlib库将检测到的结果可视化。 如果使用tensorflow/models存储库随附的默认两个测试图像,则会在图 3.1 中看到结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fE74MfCc-1681653027413)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/0df3a5c2-f983-4921-a901-b14c262d34a4.png)]

图 3.1:检测到的对象及其边界框和置信度分数

在“在 iOS 中使用对象检测模型”部分,我们将了解如何在 iOS 设备上使用相同的模型并绘制相同的检测结果。

您还可以在前面提到的 Tensorflow 检测模型 Zoo 中测试其他预训练模型。 例如,如果使用faster_rcnn_inception_v2_coco模型,将object_detection_tutorial.ipynb笔记本中的MODEL_NAME = 'ssd_mobilenet_v1_coco_2017_11_17'替换为MODEL_NAME = ' faster_rcnn_inception_v2_coco_2017_11_08'(可从 TensorFlow 检测模型 Zoo 页面的 URL 或MODEL_NAME = ' faster_rcnn_resnet101_coco_2017_11_08'中获得),则可以看到类似于其他两个基于 Faster RCNN 的模型的检测结果的内容,但是它们花费的时间更长。

另外,在两个faster_rcnn模型上使用summarize_graph工具会在输入和输出上生成相同的信息:

代码语言:javascript复制
Found 1 possible inputs: (name=image_tensor, type=uint8(4), shape=[?,?,?,3]) 
Found 4 possible outputs: (name=detection_boxes, op=Identity) (name=detection_scores, op=Identity) (name=detection_classes, op=Identity) (name=num_detections, op=Identity)

通常,与其他大型的基于 Inception 或 Resnet-CNN 的大型模型相比,基于 MobileNet 的模型速度最快,但准确率较低(mAP 值较小)。 顺便说一下,下载的ssd_mobilenet_v1_coco, faster_rcnn_inception_v2_coco_2017_11_08faster_rcnn_resnet101_coco_2017_11_08文件的大小分别为 76MB,149MB 和 593MB。 稍后我们将看到,在移动设备上,基于 MobileNet 的模型(例如ssd_mobilenet_v1_coco)运行速度要快得多,有时,大型模型(例如faster_rcnn_resnet101_coco_2017_11_08)只会在较旧的 iPhone 上崩溃。 希望您可以使用基于 MobileNet 的模型,经过重新训练的 MobileNet 模型或将来可以提供更高准确率的ssd_mobilenet的将来版本解决问题,尽管ssd_mobilenet的 v1 在许多用例中已经足够好。

重新训练 SSD-MobileNet 和 Faster RCNN 模型

经过预训练的 TensorFlow 对象检测模型当然可以很好地解决某些问题。 但是有时候,您可能需要使用自己的带标注的数据集(在您特别感兴趣的对象或对象部分周围带有边界框)并重新训练现有模型,以便它可以更准确地检测不同的对象类别集合。

我们将使用 TensorFlow 对象检测 API 网站中记录的相同的 Oxford-IIIT Pets 数据集来重新训练本地计算机上的两个现有模型,而不是使用文档中介绍的 Google Cloud。 必要时,我们还将为每个步骤添加说明。 以下是有关如何使用 Oxford Oxford Pets 数据集重新训练 TensorFlow 对象检测模型的分步指南:

  1. 在终端窗口中,最好在我们的 GPU 驱动的 Ubuntu 上cd models/research first,以加快重新训练的速度,然后运行以下命令下载数据集(images.tar.gz约为 800MB,annotations.tar.gz为 38MB):
代码语言:javascript复制
wget http://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz 
wget http://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz
tar -xvf images.tar.gz
tar -xvf annotations.tar.gz
  1. 运行以下命令以将数据集转换为 TFRecords 格式:
代码语言:javascript复制
python object_detection/dataset_tools/create_pet_tf_record.py 
 --label_map_path=object_detection/data/pet_label_map.pbtxt 
 --data_dir=`pwd` 
 --output_dir=`pwd`

该命令将在models/research目录中生成两个名为pet_train_with_masks.record(268MB)和pet_val_with_masks.record(110MB)的 TFRecord 文件。 TFRecords 是一种有趣的二进制格式,其中包含 TensorFlow 应用可用于训练或验证的所有数据,如果您想使用 TensorFlow 对象检测 API 重新训练自己的数据集,则 TFRecords 是必需的文件格式。

  1. 如果在上一节中测试对象检测笔记本时还没有下载ssd_mobilenet_v1_coco模型和faster_rcnn_resnet101_coco模型并将其解压缩到models/research目录,请执行以下操作:
代码语言:javascript复制
wget http://storage.googleapis.com/download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_coco_2017_11_17.tar.gz
tar -xvf ssd_mobilenet_v1_coco_2017_11_17.tar.gz
wget http://storage.googleapis.com/download.tensorflow.org/models/object_detection/faster_rcnn_resnet101_coco_11_06_2017.tar.gz
tar -xvf faster_rcnn_resnet101_coco_11_06_2017.tar.gz
  1. 替换object_detection/samples/configs/faster_rcnn_resnet101_pets.config文件中出现的PATH_TO_BE_CONFIGURED五次,因此它们变为:
代码语言:javascript复制
fine_tune_checkpoint: "faster_rcnn_resnet101_coco_11_06_2017/model.ckpt"
...
train_input_reader: {
tf_record_input_reader {
input_path: "pet_train_with_masks.record"
}
label_map_path: "object_detection/data/pet_label_map.pbtxt"
}
eval_input_reader: {
tf_record_input_reader {
input_path: "pet_val_with_masks.record"
}
label_map_path: "object_detection/data/pet_label_map.pbtxt"
...
}

faster_rcnn_resnet101_pets.config文件用于指定模型检查点文件的位置,该文​​件包含模型的训练后权重,在步骤 2 中生成的用于训练和验证的 TFRecords 文件以及要检测的 37 类宠物的标签项。 object_detection/data/pet_label_map.pbtxt的第一项和最后一项如下:

代码语言:javascript复制
item {
id: 1
name: 'Abyssinian'
}
...
item {
id: 37
name: 'yorkshire_terrier'
}
  1. 同样,在object_detection/samples/configs/ssd_mobilenet_v1_pets.config文件中更改PATH_TO_BE_CONFIGURED的五次出现,因此它们变为:
代码语言:javascript复制
fine_tune_checkpoint: "object_detection/ssd_mobilenet_v1_coco_2017_11_17/model.ckpt"
train_input_reader: {
tf_record_input_reader {
input_path: "pet_train_with_masks.record"
}
label_map_path: "object_detection/data/pet_label_map.pbtxt"
}
eval_input_reader: {
tf_record_input_reader {
input_path: "pet_val_with_masks.record"
}
label_map_path: "object_detection/data/pet_label_map.pbtxt"
...
}
  1. 创建一个新的train_dir_faster_rcnn目录,然后运行重新训练命令:
代码语言:javascript复制
python object_detection/train.py 
   --logtostderr 
   --pipeline_config_path=object_detection/samples/configs/faster_rcnn_resnet101_pets.config 
   --train_dir=train_dir_faster_rcnn

在基于 GPU 的系统上,从最初的损失 5.0 到损失 0.2 左右,只需不到 25,000 步的训练即可:

代码语言:javascript复制
tensorflow/core/common_runtime/gpu/gpu_device.cc:1030] Found device 0 with properties:
 name: GeForce GTX 1070 major: 6 minor: 1 memoryClockRate(GHz): 1.7845
 pciBusID: 0000:01:00.0
 totalMemory: 7.92GiB freeMemory: 7.44GiB
 INFO:tensorflow:global step 1: loss = 5.1661 (15.482 sec/step)
 INFO:tensorflow:global step 2: loss = 4.6045 (0.927 sec/step)
 INFO:tensorflow:global step 3: loss = 5.2665 (0.958 sec/step)
 ...
 INFO:tensorflow:global step 25448: loss = 0.2042 (0.372 sec/step)
 INFO:tensorflow:global step 25449: loss = 0.4230 (0.378 sec/step)
 INFO:tensorflow:global step 25450: loss = 0.1240 (0.386 sec/step)
  1. 在大约 20,000 个步骤(大约 2 个小时)后,按Ctrl C结束上述重新训练脚本的运行。 创建一个新的train_dir_ssd_mobilenet目录,然后运行:
代码语言:javascript复制
python object_detection/train.py 
   --logtostderr 
   --pipeline_config_path=object_detection/samples/configs/ssd_mobilenet_v1_pets.config 
   --train_dir=train_dir_ssd_mobilenet

训练结果应如下所示:

代码语言:javascript复制
INFO:tensorflow:global step 1: loss = 136.2856 (23.130 sec/step)
 INFO:tensorflow:global step 2: loss = 126.9009 (0.633 sec/step)
 INFO:tensorflow:global step 3: loss = 119.0644 (0.741 sec/step)
 ...
 INFO:tensorflow:global step 22310: loss = 1.5473 (0.460 sec/step)
 INFO:tensorflow:global step 22311: loss = 2.0510 (0.456 sec/step)
 INFO:tensorflow:global step 22312: loss = 1.6745 (0.461 sec/step)

您可以看到,与Faster_RCNN模型相比,SSD_Mobilenet模型的重新训练在开始和结束时的损失都更大。

  1. 经过大约 20,000 个训练步骤,终止前面的再训练脚本。 然后创建一个新的eval_dir目录并运行评估脚本:
代码语言:javascript复制
python object_detection/eval.py 
   --logtostderr 
   --pipeline_config_path=object_detection/samples/configs/faster_rcnn_resnet101_pets.config 
   --checkpoint_dir=train_dir_faster_rcnn 
   --eval_dir=eval_dir
  1. 打开另一个终端窗口,在 TensorFlow 根目录下打开cd,然后打开models/research,然后运行tensorboard --logdir=.。 在浏览器中,打开http://localhost:6006,您将看到损失图,如图 3.2 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XV4Jtzdk-1681653027413)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/539aa2c8-da68-42e8-ad74-ad3f2a25e07f.png)]

图 3.2:训练对象检测模型时的总损失趋势

您还将看到一些评估结果,如图 3.3 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5hGhPNon-1681653027413)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/ee6c4a9d-473f-429c-a7e2-df3162500502.png)]

图 3.3:重新训练物体检测模型时评估图像检测结果

  1. 同样,您可以为SSD_MobileNet模型运行评估脚本,然后使用 TensorBoard 查看其损失趋势和评估图像结果:
代码语言:javascript复制
python object_detection/eval.py 
    --logtostderr 
    --pipeline_config_path=object_detection/samples/configs/ssd_mobilenet_v1_pets.config 
    --checkpoint_dir=train_dir_ssd_mobilenet 
    --eval_dir=eval_dir_mobilenet
  1. 您可以使用以下命令生成重新训练的图:
代码语言:javascript复制
python object_detection/export_inference_graph.py 
    --input_type image_tensor 
    --pipeline_config_path object_detection/samples/configs/ssd_mobilenet_v1_pets.config 
    --trained_checkpoint_prefix train_dir_ssd_mobilenet/model.ckpt-21817 
    --output_directory output_inference_graph_ssd_mobilenet.pb

python object_detection/export_inference_graph.py 
    --input_type image_tensor 
    --pipeline_config_path object_detection/samples/configs/faster_rcnn_resnet101_pets.config 
    --trained_checkpoint_prefix train_dir_faster_rcnn/model.ckpt-24009 
    --output_directory output_inference_graph_faster_rcnn.pb

您需要用自己的特定检查点值替换--trained_checkpoint_prefix值(上述 21817 和 24009)。

到此为止-您现在拥有两个经过重新训练的对象检测模型output_inference_graph_ssd_mobilenet.pboutput_inference_graph_faster_rcnn.pb,可以在您的 Python 代码(上一节中的 Jupyter 笔记本)或移动应用中使用它们。 不用再拖延了,让我们跳到移动世界,看看如何使用我们拥有的预训练和重新训练的模型。

在 iOS 中使用对象检测模型

在上一章中,我们向您展示了如何使用 TensorFlow 实验性容器将 TensorFlow 快速添加到您的 iOS 应用中。 TensorFlow 实验性 Pod 在诸如 Inception 和 MobileNet 之类的模型或其经过重新训练的模型中工作良好。 但是,如果至少在撰写本文时(2018 年 1 月)使用 TensorFlow 实验荚,并使用SSD_MobileNet模型,则在加载ssd_mobilenet图文件时可能会收到以下错误消息:

代码语言:javascript复制
Could not create TensorFlow Graph: Not found: Op type not registered 'NonMaxSuppressionV2'

除非将 TensorFlow 实验 Pod 更新为包括未在此处注册的操作,否则解决这些问题的唯一方法是通过从 TensorFlow 源构建自定义 TensorFlow iOS 库,这就是我们为什么向您展示第 1 章,“移动 TensorFlow 入门”中的内容,“如何从源代码获取和设置 TensorFlow”。 让我们看一下构建自己的 TensorFlow iOS 库并使用它来创建具有 TensorFlow 支持的新 iOS 应用的步骤。

手动构建 TensorFlow iOS 库

只需执行以下步骤即可构建自己的 TensorFlow iOS 库:

  1. 如果您将 TensorFlow 1.4 源 zip 解压缩到您的主目录,请在 Mac 上打开一个新终端,将cd到 TensorFlow 源根目录,即~/tensorflow-1.4.0
  2. 运行tensorflow/contrib/makefile/build_all_ios.sh命令,此过程从 20 分钟到大约一个小时不等,具体取决于您的 Mac 速度。 构建过程成功完成后,您将创建三个库:
代码语言:javascript复制
tensorflow/contrib/makefile/gen/protobuf_ios/lib/libprotobuf-lite.a
tensorflow/contrib/makefile/gen/protobuf_ios/lib/libprotobuf.a
tensorflow/contrib/makefile/gen/lib/libtensorflow-core.a

前两个库处理我们之前讨论的 protobuf 数据。 最后一个库是 iOS 通用静态库。

如果您运行该应用,请完成以下步骤,并在 Xcode 控制台中遇到错误,Invalid argument: No OpKernel was registered to support Op 'Less' with these attrs. Registered devices: [CPU], Registered kernels: device='CPU'; T in [DT_FLOAT],您需要在此处执行步骤 2 之前更改tensorflow/contrib/makefile/Makefile文件(请参阅第 7 章,“使用 CNN 和 LSTM 识别绘图”中的“为 iOS 构建自定义 TensorFlow 库”一节)。 使用新版本的 TensorFlow 时可能看不到错误。

在应用中使用 TensorFlow iOS 库

要在您自己的应用中使用库,请执行以下操作:

  1. 在 Xcode 中,单击“文件 | 新增 | 项目…”,选择“Single View App”,然后输入 TFObjectDetectionAPI 作为产品名称,然后选择 Objective-C 作为语言(如果您想使用 Swift,请参阅上一章有关如何将 TensorFlow 添加到基于 Swift 的 iOS 应用并进行此处所示的必要更改),然后选择项目的位置并单击“创建”。
  2. TFObjectDetectionAPI项目中,单击项目名称,然后在构建设置下,单击 并添加用户定义的设置,然后输入TENSORFLOW_ROOT作为 TensorFlow 源根的路径(例如$HOME/tensorflow-1.4),如图 3.4 所示。 如果您要引用较新的 TensorFlow 来源,此用户定义的设置将在其他设置中使用,以便以后轻松更改项目设置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VrTnaK52-1681653027413)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/a8474e70-b35d-4585-a9f9-620aa6cff313.png)]

图 3.4:添加TENSORFLOW_ROOT用户定义的设置

  1. 单击目标,然后在“构建设置”下搜索“其他链接器标志”。 向其添加以下值:
代码语言:javascript复制
-force_load $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/lib/libtensorflow-core.a $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/protobuf_ios/lib/libprotobuf.a $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/protobuf_ios/lib/libprotobuf-lite.a $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads/nsync/builds/lipo.ios.c  11/nsync.a

需要第一个–force_load,因为它确保 TensorFlow 所需的 C 构造器将被链接,否则,您仍可以构建和运行该应用,但会遇到有关未注册会话的错误。

最后一个库用于nsync,这是一个 C 库,可导出互斥量和其他同步方法。 在新的 TensorFlow 版本中引入。

  1. 搜索“标题搜索路径”,并添加以下值:
代码语言:javascript复制
$(TENSORFLOW_ROOT) $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads/protobuf/src $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads/eigen $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/proto

之后,您会看到类似图 3.5 的内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BcQ9PbiI-1681653027414)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/bae0f128-065f-4267-b1cc-b2450650e8d7.png)]

图 3.5:为目标添加所有与 TensorFlow 相关的构建设置

  1. 在目标的“构建阶段”中,在带库的链接二进制文件中添加 Accelerate 框架,如图 3.6 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TPYeFSUH-1681653027414)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/3c92f162-ad1d-4718-a72f-07f10ebc71fa.png)]

图 3.6:添加加速框架

  1. 返回用于构建 TensorFlow iOS 库的终端,在tensorflow/core/platform/default/mutex.h中找到以下两行代码:
代码语言:javascript复制
#include "nsync_cv.h"
#include "nsync_mu.h"

然后将其更改为:

代码语言:javascript复制
#include "nsync/public/nsync_cv.h"
#include "nsync/public/nsync_mu.h"

只需将手动构建的 TensorFlow 库 TensorFlow 添加到 iOS 应用即可。

使用从 TensorFlow 的更高版本(例如 1.4)手动构建的 TensorFlow 库在您的应用中加载 TensorFlow 对象检测模型时,将不会出现使用 TensorFlow 实验性 POD 或从早期版本构建的手动库时可能会看到的错误版。这是因为位于tensorflow/contrib/makefile中的名为tf_op_files.txt的文件用于定义应为 TensorFlow 库构建和包括哪些操作,在 TensorFlow 1.4 中定义的操作比早期版本更多。例如,TensorFlow 1.4 中的tf_op_files.txt文件有一行tensorflow/core/kernels/non_max_suppression_op.cc定义了NonMaxSuppressionV2操作,这就是为什么我们手动构建的库中定义了该操作,防止出现错误Could not create TensorFlow Graph: Not found: Op type not registered 'NonMaxSuppressionV2',我们将查看是否发生过使用 TensorFlow 窗格的错误。将来,如果遇到类似的Op type not registered错误,则可以通过在tf_op_files.txt文件中添加定义操作的正确的源代码文件,然后再次运行build_all_ios.sh来修复该错误。创建一个新的libtensorflow-core.a文件。

向 iOS 应用添加对象检测功能

现在执行以下步骤以将模型文件,标签文件和代码添加到应用,并运行以查看实际的对象检测:

  1. 拖放上一节中的三个物体检测模型图ssd_mobilenet_v1_frozen_inference_graph.pbfaster_rcnn_inceptionv2_frozen_inference_graph.pbfaster_rcnn_resnet101_frozen_inference_graph.pb,以及mscoco_label_map.pbtxt标签映射文件和几个测试图像发送到TFObjectDetectionAPI项目。
  2. 将 TensorFlow iOS 示例简单应用或上一章中创建的 iOS 应用中的ios_image_load.mm及其.h文件添加到项目中。
  3. 在这里(在 Mac 上为protoc-3.4.0-osx-x86_64.zip文件)下载协议缓冲区版本 3.4.0。 要使用 TensorFlow 1.4 库需要确切的 3.4.0 版本,而更高版本的 TensorFlow 可能需要更高版本的协议缓冲区。
  4. 假设下载的文件解压缩到~/Downloads目录中,请打开“终端”窗口并运行以下命令:
代码语言:javascript复制
cd <TENSORFLOW_ROOT>/models/research/object_detection/protos

~/Downloads/protoc-3.4.0-osx-x86_64/bin/protoc string_int_label_map.proto --cpp_out=<path_to_your_TFObjectDetectionAPI_project>, the same location as your code files and the three graph files.
  1. protoc编译器命令完成后,您将在项目的源目录中看到两个文件:string_int_label_map.pb.ccstring_int_label_map.pb.h。 将两个文件添加到 Xcode 项目中。
  2. 在 Xcode 中,像在上一章中一样,将ViewController.m重命名为ViewController.mm,然后类似于第 2 章,“通过迁移学习对图像进行分类”的HelloTensorFlow应用的ViewController.mm,在点击的处理器中为三个对象检测模型添加三个UIAlertAction,我们已将模型添加到项目中并将要测试。 现在,完整的项目文件应如图 3.7 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eb4f0Hkz-1681653027414)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/7023289e-ae57-4518-920e-140985314d13.png)]

图 3.7:TFObjectDetection API 项目文件

  1. 继续在ViewController.mm中添加其余代码。 在viewDidLoad中,添加以编程方式创建新UIImageView的代码,以首先显示测试图像,并在选择了特定模型以在测试图像上运行之后显示检测到的结果,然后添加以下函数实现:
代码语言:javascript复制
NSString* FilePathForResourceName(NSString* name, NSString* extension)
int LoadLablesFile(const string pbtxtFileName, object_detection::protos::StringIntLabelMap *imageLabels)
string GetDisplayName(const object_detection::protos::StringIntLabelMap* labels, int index) 
Status LoadGraph(const string& graph_file_name, std::unique_ptr<tensorflow::Session>* session)
void DrawTopDetections(std::vector<Tensor>& outputs, int image_width, int image_height)
void RunInferenceOnImage(NSString *model)

下一步之后,我们将解释这些函数的实现,您可以在该书的源代码仓库的ch3/ios文件夹中获取所有源代码。

  1. 在 iOS 模拟器或设备中运行该应用。 首先,您会在屏幕上看到一张图片。 点按任意位置,您将看到一个对话框,要求您选择模型。 选择SSD MobileNet模型,在模拟器中花费大约一秒钟,在 iPhone 6 上花费五秒钟,以在图像上绘制检测结果。 Faster RCNN Inception V2 需要更长的时间(在模拟器中大约需要 5 秒,在 iPhone 6 上大约需要 20 秒); 该模型也比SSD MobileNet更精确,可以捕获SSD MobileNet模型遗漏的一个狗物体。 最后一个模型,更快的 RCNN Resnet 101,在 iOS 模拟器中花费了将近 20 秒,但由于其大小而在 iPhone 6 上崩溃。 图 3.8 总结了运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T3LUDHYE-1681653027414)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/411e68db-3026-4ba4-964a-3d9a174dce6f.png)]

图 3.8:使用不同的模型运行应用并显示检测结果

返回步骤 7 中的函数,FilePathForResourceName函数是用于返回资源文件路径的助手函数:mscoco_label_map.pbtxt文件,该文件定义了要检测的 90 个对象类的 ID,内部名称和显示名称。 ,模型图文件和测试图像。 它的实现与我们在上一章的HelloTensorFlow应用中看到的实现相同。

LoadLablesFileGetDisplayName函数使用 Google Protobuf API 加载和解析mscoco_label_map.pbtxt文件,并返回显示名称以显示检测到的对象的 ID。

LoadGraph 尝试加载三个用户选择的模型文件之一,并返回加载状态。

这两个关键函数是RunInferenceOnImageDrawTopDetections。 正如我们在“设置 TensorFlow 对象检测 API”部分中所看到的那样,summary_graph工具显示了我们在应用中使用的三种预训练对象检测模型的以下信息(请注意uint8类型):

代码语言:javascript复制
Found 1 possible inputs: (name=image_tensor, type=uint8(4), shape=[?,?,?,3])

这就是为什么我们需要使用uint8创建一个图像张量,而不是float类型来加载到我们的模型,否则在运行模型时会出现错误。 另请注意,当我们使用 TensorFlow C API 的SessionRun方法将image_data转换为Tensor类型的image_data时,我们不使用input_meaninput_std就像我们在使用图像分类模型时所做的(有关详细比较,请参见第 2 章,“通过迁移学习对图像进行分类”的 HelloTensorFlow 应用的RunInferenceOnImage实现)。 我们知道有四个名为detection_boxesdetection_scoresdetection_classesnum_detections的输出,因此RunInferenceOnImage具有以下代码来为模型输入图像输入并获得四个输出:

代码语言:javascript复制
tensorflow::Tensor image_tensor(tensorflow::DT_UINT8, tensorflow::TensorShape({1, image_height, image_width, wanted_channels}));

auto image_tensor_mapped = image_tensor.tensor<uint8, 4>();
tensorflow::uint8* in = image_data.data();
uint8* c_out = image_tensor_mapped.data();
for (int y = 0; y < image_height;   y) {
    tensorflow::uint8* in_row = in   (y * image_width * image_channels);
    uint8* out_row = c_out   (y * image_width * wanted_channels);
    for (int x = 0; x < image_width;   x) {
        tensorflow::uint8* in_pixel = in_row   (x * image_channels);
        uint8* out_pixel = out_row   (x * wanted_channels);
        for (int c = 0; c < wanted_channels;   c) {
            out_pixel[c] = in_pixel[c];
        }
    }
}
std::vector<Tensor> outputs;
Status run_status = session->Run({{"image_tensor", image_tensor}},
             {"detection_boxes", "detection_scores", "detection_classes", "num_detections"}, {}, &outputs);

要在检测到的对象上绘制边界框,我们将outputs张量向量传递给DrawTopDetections,后者使用以下代码解析outputs向量以获取四个输出的值,并循环遍历每次检测以获得边界框值(左,上,右,下)以及检测到的对象 ID 的显示名称,因此您可以编写代码以使用以下名称绘制边界框:

代码语言:javascript复制
auto detection_boxes = outputs[0].flat<float>();
auto detection_scores = outputs[1].flat<float>();
auto detection_classes = outputs[2].flat<float>();
auto num_detections = outputs[3].flat<float>()(0);

LOG(INFO) << "num_detections: " << num_detections << ", detection_scores size: " << detection_scores.size() << ", detection_classes size: " << detection_classes.size() << ", detection_boxes size: " << detection_boxes.size();

for (int i = 0; i < num_detections; i  ) {
    float left = detection_boxes(i * 4   1) * image_width;
    float top = detection_boxes(i * 4   0) * image_height;
    float right = detection_boxes(i * 4   3) * image_width;
    float bottom = detection_boxes((i * 4   2)) * image_height; 
    string displayName = GetDisplayName(&imageLabels, detection_classes(i)); 
    LOG(INFO) << "Detected " << i << ": " << displayName << ", " << score << ", (" << left << ", " << top << ", " << right << ", " << bottom << ")";
    ...
  }

当前面的LOG(INFO)行与图 3.1 中的第二个测试图像一起运行时,以及 TensorFlow Object Detection API 网站上显示的演示图像时,将输出以下信息:

代码语言:javascript复制
num_detections: 100, detection_scores size: 100, detection_classes size: 100, detection_boxes size: 400
 Detected 0: person, 0.916851, (533.138, 498.37, 553.206, 533.727)
 Detected 1: kite, 0.828284, (467.467, 344.695, 485.3, 362.049)
 Detected 2: person, 0.779872, (78.2835, 516.831, 101.287, 560.955)
 Detected 3: kite, 0.769913, (591.238, 72.0729, 676.863, 149.322)

这就是在 iOS 应用中使用现有的经过预训练的对象检测模型所需要的。 如何在 iOS 中使用我们的经过训练的对象检测模型? 事实证明,这与使用预训练模型几乎相同,在处理再训练图像分类模型时,无需像上一章一样修改input_size, input_mean, input_stdinput_name。 您只需要执行以下操作:

  1. 将您的训练后的模型(例如,在上一节中创建的output_inference_graph_ssd_mobilenet.pb文件,用于模型的训练的标签映射文件,例如pet_label_map.pbtxt)添加到TFObjectDetectionAPI 项目
  2. ViewController.mm中,使用重新训练的模型调用RunInferenceOnImage
  3. 仍在ViewController.mm中,在DrawTopDetections函数内调用LoadLablesFile([FilePathForResourceName(@"pet_label_map", @"pbtxt") UTF8String], &imageLabels);

而已。 运行该应用,您可以看到针对重新训练的模型对检测到的结果进行了更精细的调整。 例如,使用通过使用牛津宠物数据集进行重新训练而生成的前面的重新训练模型,我们希望看到边界框围绕头部区域而不是整个身体,而这正是我们在图 3.9 中所示的测试图像所看到的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3QdwEhv3-1681653027415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/ced173a8-1b82-4868-a274-3cdf666556e1.png)]

图 3.9:比较预训练和再训练模型的检测结果

使用 YOLO2 – 另一种物体检测模型

正如我们在第一部分中提到的,YOLO2 是另一个很酷的对象检测模型,它使用了与 RCNN 系列不同的方法。 它使用单个神经网络将输入图像划分为固定大小的区域(但不像 RCNN 系列方法那样提供区域建议),并预测每个区域的边界框,类别和概率。

TensorFlow Android 示例应用具有使用预训练的 YOLO 模型的示例代码,但没有 iOS 示例。 由于 YOLO2 是最快的对象检测模型之一,而且非常准确(请在其网站上查看其与 SSD 模型的 mAP 比较),因此有必要了解一下如何在 iOS 应用中使用它。

YOLO 使用称为 Darknet 的独特开源神经网络框架来训练其模型。 还有另一个名为 darkflow 的库,该库可以将使用 Darknet 训练的 YOLO 模型的神经网络权重转换为 TensorFlow 图格式,并重新训练预训练的模型。

要以 TensorFlow 格式构建 YOLO2 模型,请首先从这里获取 darkflow。因为它需要 Python3 和 TensorFlow 1.0(Python 2.7 和 TensorFlow 1.4 或更高版本也可能工作),所以我们将使用 Anaconda 来设置一个新的具有 Python3 支持的 TensorFlow 1.0 环境:

代码语言:javascript复制
conda create --name tf1.0_p35 python=3.5
source activate tf1.0_p35
conda install -c derickl tensorflow

同时运行conda install -c menpo opencv3以安装 OpenCV 3,这是 darkflow 的另一个依赖项。 现在,将cd移至 darkflow 目录,然后运行pip install .安装 darkflow。

接下来,我们需要下载经过预训练的 YOLO 模型的权重-我们将尝试两个 Tiny-YOLO 模型,它们超级快,但不如完整的 YOLO 模型准确。 同时运行 Tiny-YOLO 模型和 YOLO 模型的 iOS 代码几乎相同,因此我们仅向您展示如何运行 Tiny-YOLO 模型。

您可以在 YOLO2 官方网站上下载 tiny-yolo-voc(受 20 个对象类的 PASCAL VOC 数据集训练)和 tiny-yolo(受 80 个对象类的 MS COCO 数据集训练)的权重和配置文件。 或 darkflow 仓库。 现在,运行以下命令将权重转换为 TensorFlow 图文件:

代码语言:javascript复制
flow --model cfg/tiny-yolo-voc.cfg --load bin/tiny-yolo-voc.weights --savepb
flow --model cfg/tiny-yolo.cfg --load bin/tiny-yolo.weights --savepb

生成的两个文件tiny-yolo-voc.pbtiny-yolo.pb将位于built_graph目录中。 现在,转到 TensorFlow 源根目录,并像上一章一样运行以下命令来创建量化模型:

代码语言:javascript复制
python tensorflow/tools/quantization/quantize_graph.py --input=darkflow/built_graph/tiny-yolo.pb --output_node_names=output --output=quantized_tiny-yolo.pb --mode=weights

python tensorflow/tools/quantization/quantize_graph.py --input=darkflow/built_graph/tiny-yolo-voc.pb --output_node_names=output --output=quantized_tiny-yolo-voc.pb --mode=weights

现在,请按照以下步骤查看如何在我们的 iOS 应用中使用两个 YOLO 模型:

  1. quantized_tiny-yolo-voc.pbquantized_tiny-yolo.pb都拖到TFObjectDetectionAPI项目中
  2. ViewController.mm中添加两个新的警报操作,因此在运行该应用时,您将看到可用于运行的模型,如图 3.10 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xdaWkWrO-1681653027415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/532a45ca-35ee-452b-b33a-79d557fee667.png)]

图 3.10:将两个 YOLO 模型添加到 iOS 应用

  1. 添加以下代码以将输入图像处理到张量中以馈送到输入节点,并在加载了 YOLO 模型图的情况下运行 TensorFlow 会话以生成检测输出:
代码语言:javascript复制
tensorflow::Tensor image_tensor(tensorflow::DT_FLOAT,tensorflow::TensorShape({1, wanted_height, wanted_width, wanted_channels}));
auto image_tensor_mapped = image_tensor.tensor<float, 4>();
tensorflow::uint8* in = image_data.data();
float* out = image_tensor_mapped.data();
for (int y = 0; y < wanted_height;   y) {
   ...   
   out_pixel[c] = in_pixel[c] / 255.0f;
}
std::vector<tensorflow::Tensor> outputs;
tensorflow::Status run_status = session->Run({{"input", image_tensor}}, {"output"}, {}, &outputs);

请注意,此处的for-loopsession->Run与上一章中用于图像分类的代码以及使用本章前面所示的其他模型进行对象检测的代码中存在细微但重要的区别(我们未显示...中的代码段,因为与这两个示例中的相同)。 为了使图像数据转换正确,您需要了解模型的详细信息,或者从 Python,Android 或 iOS 的有效示例中学习,当然还要进行必要的调试。 为了正确设置输入和输出节点名称,可以使用summarize_graph工具或我们多次显示的 Python 代码段。

  1. 将输出结果传递给名为YoloPostProcess的函数,该函数类似于tensorflow/examples/android/src/org/tensorflow/demo/TensorFlowYoloDetector.java Android 示例文件中的后处理代码:
代码语言:javascript复制
tensorflow::Tensor* output = &outputs[0];
std::vector<std::pair<float, int> > top_results;
YoloPostProcess(model, output->flat<float>(), &top_results);

我们不会在此处显示其余代码。 您可以在源代码存储库的ch3/ios中检出完整的 iOS 应用。

  1. 运行该应用,然后选择 YOLO2 Tiny VOC 或 YOLO2 Tiny COCO,与使用 SSD MobileNet v1 模型相比,您会看到类似的速度,但检测结果的准确率较差。

尽管基于 MobileNet 的 TensorFlow 模型和 Tiny YOLO2 模型的准确率较低,但 TensorFlow 对象检测模型和 YOLO2 模型在移动设备上的运行速度都非常快。 较大的 Faster RNN 模型和完整的 YOLO2 模型更准确,但是它们花费的时间更长,甚至无法在移动设备上运行。 因此,向移动应用添加快速对象检测的最佳方法是使用 SSD MobileNet 或 Tiny-YOLO2 模型,或经过重新训练和微调的模型。 模型的未来版本很可能会具有更好的表现和准确率。 凭借本章介绍的知识,您应该能够在 iOS 应用中快速启用对象检测。

总结

在本章中,我们首先简要概述了各种不同的基于深度学习的对象检测方法。 然后,我们详细介绍了如何使用 TensorFlow 对象检测 API 通过预训练的模型进行现成的推理,以及如何在 Python 中重新训练预训练的 TensorFlow 对象检测模型。 我们还提供了有关如何手动构建 TensorFlow iOS 库,使用该库创建新的 iOS 应用以及如何在 iOS 中使用预先存在和经过重新训练的 SSD MobileNet 和 Faster RCNN 模型的详细教程。 最后,我们展示了在您的 iOS 应用中使用另一种强大的对象检测模型 YOLO2 所需要的内容。

在下一章中,这是我们与计算机视觉相关的第三项任务,我们将仔细研究如何在 Python 和 TensorFlow 中训练和构建有趣的深度学习模型,以及如何在 iOS 和 Android 应用中使用它来添加令人赞叹的图像艺术风格。

四、以惊人的艺术风格变换图片

自从 2012 年深层神经网络在 AlexNet 赢得 ImageNet 挑战后开始起飞以来,人工智能研究人员一直在将深度学习技术(包括经过预训练的深度 CNN 模型)应用于越来越多的问题领域。 有什么能比创造艺术更有创造力? 一种想法已经提出并实现了,称为神经样式传递,它使您可以利用预训练的深度神经网络模型并传递图像或任何梵高的样式或莫奈的杰作),例如另一张图片(例如个人资料图片或您喜欢的狗的图片),从而创建将图片内容与杰作风格融合在一起的图片。 实际上,有一个名为 Prisma 的 iOS 应用在 2016 年获得了年度最佳应用奖。 在短短几秒钟内,它将以您选择的任何样式迁移您的图片。

在本章中,我们将首先概述三种神经样式迁移方法,其中一种是原始方法,一种是经过改进的方法,另一种是进一步改进的方法。 然后,我们将详细研究如何使用第二种方法来训练快速神经样式迁移模型,该模型可在您的 iOS 和 Android 智能手机中使用,以实现 Prisma 的功能。 接下来,我们将实际在 iOS 应用和 Android 应用中使用该模型,引导您完成从头开始创建此类应用的整个过程。 最后,我们将向您简要介绍 TensorFlow Magenta 开源项目,您可以将其用于基于深度学习构建更多的音乐和艺术生成应用,并向您展示如何使用单个预训练的样式迁移模型, 是基于神经样式迁移的最新研究进展而创建的,其中包括 26 种很酷的艺术样式,可在您的 iOS 和 Android 应用中获得更快的性能和结果。 总之,本章将涵盖以下主题:

  • 神经样式迁移 – 快速概述
  • 训练快速的神经样式迁移模型
  • 在 iOS 中使用快速的神经样式迁移模型
  • 在 Android 中使用快速的神经样式迁移模型
  • 在 iOS 中使用 TensorFlow Magenta 多样式模型
  • 在 Android 中使用 TensorFlow Magenta 多样式模型

神经样式迁移 – 快速概述

使用深度神经网络将图像内容与另一种图像的样式合并的原始思想和算法于 2015 年夏季发表在题为《艺术风格的神经算法》的论文中。它是 2014 年 ImageNet 图像识别挑战赛的获胜者,该挑战赛具有 16 个卷积层或特征映射,分别代表不同级别的图像内容。 在这种原始方法中,首先将最终迁移的图像初始化为与内容图像合并的白噪声图像。 内容损失函数定义为内容图像和结果图像的卷积层conv4_2上都被馈入 VGG-19 网络后,特定的一组特征表示形式的平方误差损失。 样式损失函数计算样式图像和所得图像在五个不同卷积层上的总误差差。 然后,将总损失定义为内容损失和样式损失的总和。 在训练期间,损失会降到最低,并生成将一个图像的内容与另一个图像的样式混合在一起的结果图像。

尽管原始神经样式迁移算法的结果令人惊叹,但其性能却很差-训练是样式迁移图像生成过程的一部分,通常在 GPU 上花费几分钟,在 CPU 上花费约一个小时才能生成良好的图像。 结果。

如果您对原始算法的细节感兴趣,可以在以下位置阅读该论文以及文档齐全的 Python 实现。我们不会讨论这种原始算法,因为在手机上运行该算法是不可行的,但是尝试该算法很有趣且有用,可以更好地了解如何针对不同的计算机视觉任务使用预训练的深度 CNN 模型。

自然地,在 2016 年,论文中发布了一种“快三个数量级”的新算法,即《实时样式传递和超分辨率的感知损失》,作者是 Justin Johnson 等。 它使用单独的训练过程,并定义了更好的损失函数,这些函数本身就是深度神经网络。 训练后(在下一节中我们将看到,在 GPU 上可能要花费几个小时),使用训练后的模型来生成样式迁移的图像在计算机上几乎是实时的,而在智能手机上只需几秒钟。

使用这种快速神经迁移算法仍然有一个缺点:只能针对特定样式训练模型,因此,要在您的应用中使用不同的样式,必须逐一训练这些样式以为每种样式生成一个模型 。 2017 年发表了一篇名为《学习风格的艺术表现形式》的新论文,它发现一个单一的深度神经网络模型可以概括许多不同的样式。 TensorFlow Magenta 项目包括具有多种样式的预训练模型,我们将在本章的最后两个部分中看到,在 iOS 和 Android 应用中使用这种模型来产生强大而神奇的艺术效果是多么容易。

训练快速的神经样式迁移模型

在本部分中,我们将向您展示如何使用带有 TensorFlow 的快速神经样式迁移算法训练模型。 执行以下步骤来训练这样的模型:

  1. 在 Mac 的终端上,或者最好在 GPU 驱动的 Ubuntu 上,运行git clone https://github.com/jeffxtang/fast-style-transfer,这是 Johnson 的快速样式迁移的 TensorFlow 实现的一个很好的分支,已修改为允许在 iOS 或 Android 应用中使用经过训练的模型。
  2. cd到快速样式迁移目录,然后运行setup.sh脚本下载预训练的 VGG-19 模型文件以及 MS COCO 训练数据集,我们在上一章中提到过,注意下载大文件可能需要几个小时。
  3. 运行以下命令,使用名为starry_night.jpg的样式图像和名为ww1.jpg的内容图像进行训练,以创建检查点文件:
代码语言:javascript复制
mkdir checkpoints
mkdir test_dir
python style.py --style images/starry_night.jpg --test images/ww1.jpg --test-dir test_dir --content-weight 1.5e1 --checkpoint-dir checkpoints --checkpoint-iterations 1000 --batch-size 10

images目录中还有一些其他样式的图像,可用于创建不同的检查点文件。 此处使用的starry_night.jpg样式图片是梵高的一幅著名画作,如图 4.1 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LNZjkACR-1681653027415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/ea1f4494-b7f4-4be2-a897-adfcbc4f0ef4.jpg)]

图 4.1:用梵高的绘画作为风格图像

在第 1 章, “移动 TensorFlow 入门”中设置的 NVIDIA GTX 1070 GPU 驱动的 Ubuntu 上,整个训练大约需要 5 个小时,并且在 CPU 上肯定要花更长的时间 。

该脚本最初是为 TensorFlow 0.12 编写的,但后来为 TensorFlow 1.1 进行了修改,并且已被验证为可以在 TensorFlow 1.4 的 Python 2.7 环境中正常运行。

  1. 在文本编辑器中打开evaluate.py文件,然后取消注释以下两行代码(在 158 和 159 行):
代码语言:javascript复制
# saver = tf.train.Saver()
# saver.save(sess, "checkpoints_ios/fns.ckpt")
  1. 运行以下命令,以输入图像img_placeholder和迁移的图像preds创建新的检查点:
代码语言:javascript复制
python evaluate.py --checkpoint checkpoints 
 --in-path examples/content/dog.jpg 
 --out-path examples/content/dog-output.jpg
  1. 运行以下命令以构建一个 TensorFlow 图文件,该文件将图定义和检查点中的权重结合在一起。 这将创建一个大约 6.7MB 的.pb文件:
代码语言:javascript复制
python freeze.py --model_folder=checkpoints_ios --output_graph fst_frozen.pb
  1. 假设您具有/tf_files目录,将生成的fst_frozen.pb文件复制到/tf_filescd直接复制到 TensorFlow 源根目录(可能是~/tensorflow-1.4.0),然后运行以下命令以生成量化模型的.pb文件(我们在第 2 章,“通过迁移学习对图像进行分类”中介绍了量化):
代码语言:javascript复制
bazel-bin/tensorflow/tools/quantization/quantize_graph 
--input=/tf_files/fst_frozen.pb  
--output_node_names=preds 
--output=/tf_files/fst_frozen_quantized.pb 
--mode=weights

这会将冻结的图文件大小从 6.7MB 减小到 1.7MB,这意味着,如果在您的应用中放置 50 种不同风格的 50 个模型,则增加的大小将约为 85MB。 苹果于 2017 年 9 月宣布,蜂窝无线应用下载限制已增加至 150MB,因此用户仍应能够通过蜂窝网络下载具有 50 多种不同样式的应用。

这就是使用样式图像和输入图像来训练和量化快速神经迁移模型的全部步骤。 您可以在步骤 3 中生成的 test_dir 目录中签出生成的图像,以查看样式迁移的效果。 如果需要,您可以使用中记录的超参数 https://github.com/jeffxtang/fast-style-transfer/blob/master/docs.md#style 进行查看,以及希望样式迁移效果更好。

在了解如何在 iOS 和 Android 应用中使用这些模型之前,重要的一点是,您需要记下在第 5 步中使用的,指定为--in-path值的图像的确切图像宽度和高度参数,并在 iOS 或 Android 代码中使用图像的宽度和高度值(您会看到多久了),否则在应用中运行模型时,会出现 Conv2DCustomBackpropInput: Size of out_backprop doesn't match computed错误 。

在 iOS 中使用快速的神经样式迁移模型

事实证明,在由 TensorFlow 实验性容器构建的 iOS 应用中,使用在步骤 7 中生成的fst_frozen_quantized.pb模型文件没有问题,如第 2 章,“通过迁移学习对图像分类”,但 TensorFlow Magenta 项目中的预训练多样式模型文件(我们将在本章的后续部分中使用)将不会随 TensorFlow Pod 一起加载(截至 2018 年 1 月)—尝试加载多样式模型文件时将引发以下错误:

代码语言:javascript复制
Could not create TensorFlow Graph: Invalid argument: No OpKernel was registered to support Op 'Mul' with these attrs. Registered devices: [CPU], Registered kernels:
  device='CPU'; T in [DT_FLOAT]
   [[Node: transformer/expand/conv1/mul_1 = Mul[T=DT_INT32](transformer/expand/conv1/mul_1/x, transformer/expand/conv1/strided_slice_1)]]

在第 3 章,“检测对象及其位置”中,我们讨论了原因以及如何使用手动构建的 TensorFlow 库修复此错误。 由于我们将在同一 iOS 应用中使用这两种模型,因此我们将使用功能更强大的手动 TensorFlow 库创建一个新的 iOS 应用。

对快速神经迁移模型进行添加和测试

如果您尚未手动构建 TensorFlow 库,则需要先回到上一章。 然后执行以下步骤以将 TensorFlow 支持和快速的神经样式迁移模型文件添加到您的 iOS 应用并测试运行该应用:

  1. 如果您已经具有添加了 TensorFlow 手动库的 iOS 应用,则可以跳过此步骤。 否则,类似于我们在上一章中所做的,创建一个新的基于 Objective-C 的 iOS 应用,例如NeuralStyleTransfer,或者在现有应用中,在PROJECT下创建一个新的用户定义设置,命名为TENSORFLOW_ROOT,值为$HOME/tensorflow-1.4.0,假定在那儿已安装 TensorFlow 1.4.0,然后在TARGET的构建设置中,将其他链接器标志设置为:
代码语言:javascript复制
-force_load $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/lib/libtensorflow-core.a $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/protobuf_ios/lib/libprotobuf.a $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/protobuf_ios/lib/libprotobuf-lite.a $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads/nsync/builds/lipo.ios.c  11/nsync.a

然后将标题搜索路径设置为:

代码语言:javascript复制
$(TENSORFLOW_ROOT) $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads/protobuf/src $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/downloads/eigen $(TENSORFLOW_ROOT)/tensorflow/contrib/makefile/gen/proto
  1. fst_frozen_quantized.pb 文件和一些测试图像拖放到项目的文件夹中。 从以前的 iOS 应用中,或从本书源代码仓库中Ch4/ios下的NeuralStyleTransfer应用文件夹中复制我们在前几章中使用过的相同ios_image_load.mm.h文件到项目中。
  2. ViewController.m重命名为ViewController.mm并将其替换为Ch4/ios/NeuralStyleTransfer中的ViewController.h.mm文件。 在测试运行该应用后,我们将详细介绍核心代码段。
  3. 在 iOS 模拟器或 iOS 设备上运行该应用,您将看到一张狗图片,如图 4.2 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bXLQcpNY-1681653027415)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/9bdc2a19-7e59-4f72-b41f-ea3eeba0c5c5.png)]

图 4.2:应用样式之前的原始狗图片

  1. 点击以选择快速样式迁移,几秒钟后,您将在图 4.3 中看到一张新图片,其中已迁移了繁星点点的夜色:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RdEYvQG1-1681653027416)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/a78207bb-2407-4343-b84d-2f17aacfd4ff.png)]

图 4.3:就像让梵高画出您喜欢的狗一样

您只需选择喜欢的图片作为样式图像,然后按照上一节中的步骤操作,即可轻松构建具有不同样式的其他模型。 然后,您可以按照本节中的步骤在 iOS 应用中使用模型。 如果您想了解模型的训练方法,则应在上一节的 GitHub 存储库中查看代码。 让我们详细看一下使用该模型完成魔术的 iOS 代码。

回顾快速神经迁移模型的 iOS 代码

ViewController.mm中有几个关键代码段,它们在输入图像的预处理和迁移图像的后处理中是唯一的:

  1. 在步骤 5 中,将两个常量wanted_widthwanted_height定义为与存储库图像examples/content/dog.jpg的图像宽度和高度相同的值:
代码语言:javascript复制
const int wanted_width = 300;
const int wanted_height = 400;
  1. iOS 的分派队列用于在非 UI 线程中加载和运行我们的快速神经迁移模型,并在生成样式迁移的图像后,将图像发送到 UI 线程进行显示:
代码语言:javascript复制
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    UIImage *img = imageStyleTransfer(@"fst_frozen_quantized");
    dispatch_async(dispatch_get_main_queue(), ^{
        _lbl.text = @"Tap Anywhere";
        _iv.image = img;
    });
});
  1. 定义了一个浮点数的 3 维张量,该张量用于将输入图像数据转换为:
代码语言:javascript复制
tensorflow::Tensor image_tensor(tensorflow::DT_FLOAT, tensorflow::TensorShape({wanted_height, wanted_width, wanted_channels}));
auto image_tensor_mapped = image_tensor.tensor<float, 3>();
  1. 发送到 TensorFlow Session->Run方法的输入节点名称和输出节点名称定义为与训练模型时相同:
代码语言:javascript复制
std::string input_layer = "img_placeholder";
std::string output_layer = "preds";
std::vector<tensorflow::Tensor> outputs;
tensorflow::Status run_status = session->Run({{input_layer, image_tensor}} {output_layer}, {}, &outputs);
  1. 模型完成运行并发送回输出张量(其中包含 0 到 255 范围内的 RGB 值)后,我们需要调用一个名为tensorToUIImage的实用函数,以将张量数据首先转换为 RGB 缓冲区:
代码语言:javascript复制
UIImage *imgScaled = tensorToUIImage(model, output->flat<float>(), image_width, image_height);

static UIImage* tensorToUIImage(NSString *model, const Eigen::TensorMap<Eigen::Tensor<float, 1, Eigen::RowMajor>, Eigen::Aligned>& outputTensor, int image_width, int image_height) {
    const int count = outputTensor.size();
    unsigned char* buffer = (unsigned char*)malloc(count);

    for (int i = 0; i < count;   i) {
        const float value = outputTensor(i);
        int n;
        if (value < 0) n = 0;
        else if (value > 255) n = 255;
        else n = (int)value;
        buffer[i] = n;
    }
  1. 然后,我们将缓冲区转换为UIImage实例,然后再调整其大小并返回以供显示:
代码语言:javascript复制
UIImage *img = [ViewController convertRGBBufferToUIImage:buffer withWidth:wanted_width withHeight:wanted_height];
UIImage *imgScaled = [img scaleToSize:CGSizeMake(image_width, image_height)];
return imgScaled;

完整的代码和应用程​​序位于Ch4/ios/NeuralStyleTransfer文件夹中。

在 Android 中使用快速的神经样式迁移模型

在第 2 章,“通过迁移学习对图像进行分类”中,我们描述了如何将 TensorFlow 添加到您自己的 Android 应用中,但没有任何 UI。 让我们创建一个新的 Android 应用,以使用我们之前训练并在 iOS 中使用的快速样式迁移模型。

由于此 Android 应用提供了一个很好的机会来使用最少的 TensorFlow 相关代码,Android UI 和线程化代码来运行完整的 TensorFlow 模型驱动的应用,因此,我们将从头开始添加每行代码,以帮助您进一步了解从头开始开发 Android TensorFlow 应用需要什么:

  1. 在 Android Studio 中,选择“文件 | 新增 | 新项目…”,然后输入FastNeuralTransfer作为应用名称; 在单击“完成”之前,接受所有默认设置。
  2. 创建一个新的assets文件夹,如图 2.13 所示,然后将您训练过的快速神经迁移模型从 iOS 应用中拖动(如果您在上一节中尝试过),或者从文件夹/tf_files中拖动,如“训练快速神经样式迁移模型”部分步骤 7 所示,以及一些测试图像到assets文件夹。
  3. 在应用的build.gradle 文件中,在 dependencies 的末尾添加一行 compile 'org.tensorflow:tensorflow-android: '和。
  4. 打开res/layout/activity_main.xml文件,在其中删除默认的TextView,然后首先添加一个ImageView以显示样式迁移前后的图像:
代码语言:javascript复制
<ImageView
    android:id="@ id/imageview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>
  1. 添加一个按钮以启动样式迁移操作:
代码语言:javascript复制
<Button
    android:id="@ id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Style Transfer"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintHorizontal_bias="0.502"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.965" />
  1. 在应用的MainActivity.java文件中,首先输入我们最重要的导入:
代码语言:javascript复制
import org.tensorflow.contrib.android.TensorFlowInferenceInterface;    

TensorFlowInferenceInterface提供 JAVA 接口来访问本机 TensorFlow 推理 API。 然后确保MainActivity类实现了Runnable 接口,因为我们需要保持我们的应用响应速度,并在工作线程上加载并运行 TensorFlow 模型。

  1. 在类的开头,定义六个常量,如下所示:
代码语言:javascript复制
private static final String MODEL_FILE = "file:///android_asset/fst_frozen_quantized.pb";
private static final String INPUT_NODE = "img_placeholder";
private static final String OUTPUT_NODE = "preds";
private static final String IMAGE_NAME = "pug1.jpg";
private static final int WANTED_WIDTH = 300;
private static final int WANTED_HEIGHT = 400;

您可以将任何训练有素的模型文件用于MODEL_FILEINPUT_NODEOUTPUT_NODE的值与我们在 Python 训练脚本中设置并在 iOS 应用中使用的值相同。 同样,WANTED_WIDTHWANTED_HEIGHT与我们在“训练快速神经样式迁移模型”部分的第 5 步中使用的--in-path图像的宽度和高度相同。

  1. 声明四个实例变量:
代码语言:javascript复制
private ImageView mImageView;
private Button mButton;
private Bitmap mTransferredBitmap;

private TensorFlowInferenceInterface mInferenceInterface;

mImageViewmButton将使用onCreate方法中的简单findViewById方法进行设置。 mTransferredBitmap将保留已迁移图像的位图,以便mImageView可以显示它。 mInferenceInterface用于加载我们的 TensorFlow 模型,将输入图像输入模型,运行模型,并返回推理结果。

  1. 在我们的 TensorFlow 推断线程向Handler实例发送消息之后,创建一个Handler实例来处理在主线程中显示最终迁移的图像的任务,我们还创建一个方便的Toast消息:
代码语言:javascript复制
Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        mButton.setText("Style Transfer");
        String text = (String)msg.obj;
        Toast.makeText(MainActivity.this, text, 
                   Toast.LENGTH_SHORT).show();
        mImageView.setImageBitmap(mTransferredBitmap);
    } };
  1. onCreate方法内部,我们将使用mImageView实例变量绑定布局 xml 文件中的ImageView,将测试图像的位图加载到assets文件夹中,并在ImageView中显示 :
代码语言:javascript复制
mImageView = findViewById(R.id.imageview);
try {
    AssetManager am = getAssets();
    InputStream is = am.open(IMAGE_NAME);
    Bitmap bitmap = BitmapFactory.decodeStream(is);
    mImageView.setImageBitmap(bitmap);
} catch (IOException e) {
    e.printStackTrace();
}
  1. 类似地设置mButton并设置一个点击监听器,以便在点击按钮时,创建并启动一个新线程,并调用run方法:
代码语言:javascript复制
mButton = findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mButton.setText("Processing...");
        Thread thread = new Thread(MainActivity.this);
        thread.start();
    }
});
  1. 在线程的run方法中,我们首先声明三个数组,并为其分配适当的内存:intValues数组保存测试图像的像素值,每个像素值代表 32 位 ARGB(Alpha,红,绿,蓝色)值; floatValues数组如模型所预期的那样分别保存每个像素的红色,绿色和蓝色值,因此其大小是intValues的三倍,并且outputValues的大小与floatValues相同 ],但保留模型的输出值:
代码语言:javascript复制
public void run() {
    int[] intValues = new int[WANTED_WIDTH * WANTED_HEIGHT];
    float[] floatValues = new float[WANTED_WIDTH * WANTED_HEIGHT * 3];
    float[] outputValues = new float[WANTED_WIDTH * WANTED_HEIGHT * 3];

然后,我们获得测试图像的位图数据,对其进行缩放以匹配训练中使用的图像的大小,然后将缩放后的位图的像素加载到intValues数组并将其转换为floatValues

代码语言:javascript复制
Bitmap bitmap = BitmapFactory.decodeStream(getAssets().open(IMAGE_NAME));
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, WANTED_WIDTH, WANTED_HEIGHT, true);
scaledBitmap.getPixels(intValues, 0, scaledBitmap.getWidth(), 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight());

for (int i = 0; i < intValues.length; i  ) {
    final int val = intValues[i];
    floatValues[i*3] = ((val >> 16) & 0xFF);
    floatValues[i*3 1] = ((val >> 8) & 0xFF);
    floatValues[i*3 2] = (val & 0xFF);
}

注意,valintValues像素数组的每个元素是一个 32 位整数,在其每个 8 位区域中均保留 ARGB。 我们使用向右移位(用于红色和绿色)和按位与运算来提取每个像素的红色,绿色和蓝色值,而忽略intValues元素中最左边的 8 位的 Alpha 值。 因此floatValues[i*3]floatValues[i*3 1]floatValues[i*3 2]分别保持像素的红色,绿色和蓝色值。

现在,我们创建一个新的TensorFlowInferenceInterface实例,并在其中将AssetManager实例和模型文件名传递到assets文件夹中,然后使用 TensorFlowInferenceInterface实例将转换后的[ floatValues数组。 如果模型需要多个输入节点,则可以调用多个feed方法。 然后,我们通过传递输出节点名称的字符串数组来运行模型。 在这里,对于我们的快速样式迁移模型,我们只有一个输入节点和一个输出节点。 最后,我们通过传递输出节点名称来获取模型的输出值。 如果希望接收多个输出节点,则可以调用多个访存:

代码语言:javascript复制
AssetManager assetManager = getAssets();
mInferenceInterface = new TensorFlowInferenceInterface(assetManager, MODEL_FILE);
mInferenceInterface.feed(INPUT_NODE, floatValues, WANTED_HEIGHT, WANTED_WIDTH, 3);
mInferenceInterface.run(new String[] {OUTPUT_NODE}, false);
mInferenceInterface.fetch(OUTPUT_NODE, outputValues);

模型生成的outputValues在每个元素中都保留 0 到 255 之间的 8 位红色,绿色和蓝色值之一,我们首先对红色和绿色值使用左移操作,但是具有不同的移位大小(16 和 8),然后使用按位或运算将 8 位 Alpha 值(0xFF)与 8 位 RGB 值组合,将结果保存在intValues数组中:

代码语言:javascript复制
for (int i=0; i < intValues.length;   i) {
    intValues[i] = 0xFF000000
                    | (((int) outputValues[i*3]) << 16)
                    | (((int) outputValues[i*3 1]) << 8)
                    | ((int) outputValues[i*3 2]);

然后,我们创建一个新的Bitmap实例,并使用intValues数组设置其像素值,将位图缩放到测试图像的原始大小,并将缩放后的位图保存到mTransferredBitmap

代码语言:javascript复制
Bitmap outputBitmap = scaledBitmap.copy( scaledBitmap.getConfig() , true);
outputBitmap.setPixels(intValues, 0, outputBitmap.getWidth(), 0, 0, outputBitmap.getWidth(), outputBitmap.getHeight());
mTransferredBitmap = Bitmap.createScaledBitmap(outputBitmap, bitmap.getWidth(), bitmap.getHeight(), true);

最后,我们向主线程的处理器发送一条消息,以使其知道显示样式迁移图像的时间:

代码语言:javascript复制
Message msg = new Message();
msg.obj = "Tranfer Processing Done";
mHandler.sendMessage(msg);

因此,总共不到 100 行代码,您就有了一个完整的 Android 应用,可以对图像进行惊人的样式迁移。 在 Android 设备或虚拟设备上运行该应用,首先将看到一个带有按钮的测试图像,点击该按钮,几秒钟后,您将看到样式迁移的图像,如图 4.4 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I7Jq4uxa-1681653027416)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/07ef305b-1e23-4a4c-98f5-faf14f398d65.png)]

图 4.4:Android 上原始图像和样式迁移的图像

快速神经样式模型存在的一个问题是,即使量化后每个模型只有 1.7MB,我们仍然需要针对每种样式分别进行训练,并且每个训练的模型只能支持一种样式迁移。 幸运的是,这个问题有很好的解决方案。

在 iOS 中使用 TensorFlow Magenta 多样式模型

TensorFlow Magenta 项目允许您使用 10 多种经过预训练的模型来生成新的音乐和图像。 在本节和下一节中,我们将重点介绍使用 Magenta 的图像样式化模型。 您可以单击链接在计算机上安装 Magenta,尽管要在移动应用中使用其炫酷的图像样式迁移模型,也不必安装 Magenta。 基于论文《艺术风格的习得表示》实现的 Magenta 预训练样式迁移模型,消除了一个模型只能具有一种风格的限制,并允许多种风格包含在单个模型文件中,您可以选择使用这些样式的任意组合。 您可以在这个页面上快速浏览该演示,但可以在此处下载两个预训练的检查点模型。 由于检查点文件中保存了某些NaN(不是数字)错误,因此无法直接在您的移动应用中使用。 我们不会详细说明如何删除这些数字并生成可在您的应用中使用的.pb模型文件(如果感兴趣,您可以查看这里),我们仅使用 TensorFlow Android 示例tensorflow/examples/android/assets中包含的经过预训练的stylize_quantized.pb模型文件来查看其工作原理。

如果您确实想训练自己的模型,则可以按照前面的image_stylization链接中的训练模型下的步骤进行。 但是请注意,您至少需要 500GB 的可用磁盘空间才能下载 ImageNet 数据集,并需要强大的 GPU 来完成训练。 在本节或下一节中看到代码和结果之后,您更有可能对预训练的stylize_quantized.pb模型启用的炫酷样式迁移效果感到满意。

在本章前面创建的 iOS 应用中,执行以下步骤来使用和运行多样式模型:

  1. stylize_quantized.pb文件从tensorflow/examples/android/assets拖放到 Xcode 中的 iOS apps文件夹中。
  2. 使用用于加载和处理快速迁移样式模型的相同 dispatch_async ,向抽头处理器中添加新的UIAlertAction
代码语言:javascript复制
UIAlertAction* multi_style_transfer = [UIAlertAction actionWithTitle:@"Multistyle Transfer" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
    _lbl.text = @"Processing...";
    _iv.image = [UIImage imageNamed:image_name];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        UIImage *img = imageStyleTransfer(@"stylize_quantized");
        dispatch_async(dispatch_get_main_queue(), ^{
            _lbl.text = @"Tap Anywhere";
            _iv.image = img;
        });
    });
}];
  1. input_layeroutput_layer值替换为新模型的正确值,并添加一个名为style_num的新输入节点名称(这些值来自StylizeActivity.java中的示例 Android 代码,但您也可以使用summarize_graph工具,TensorBoard 或我们在前几章中显示的代码段中找到它们):
代码语言:javascript复制
std::string input_layer = "input";
std::string style_layer = "style_num";
std::string output_layer = "transformer/expand/conv3/conv/Sigmoid";
  1. 与快速样式迁移模型不同,此处的多样式模型期望使用 4 维浮点张量作为图像输入:
代码语言:javascript复制
tensorflow::Tensor image_tensor(tensorflow::DT_FLOAT, tensorflow::TensorShape({1, wanted_height, wanted_width, wanted_channels}));
auto image_tensor_mapped = image_tensor.tensor<float, 4>();
  1. 我们还需要将style_tensor定义为形状为[NUM_STYLES * 1]的另一个张量,其中NUM_STYLESViewController.mm的开头定义为const int NUM_STYLES = 26;。 数字 26 是 stylize_quantized.pb模型文件中内置的样式数,您可以在其中运行 Android TF 风格化应用并查看 26 种结果,如图 4.5 所示。 请注意,第 20 张图片(左下角的图片)是梵高熟悉的繁星点点的夜晚:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n4095Y9F-1681653027416)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/ea7a04fe-8b56-47b2-ac5a-7f25ced4acbd.png)]

图 4.5:多样式模型中的 26 种样式图像

代码语言:javascript复制
tensorflow::Tensor style_tensor(tensorflow::DT_FLOAT, tensorflow::TensorShape({ NUM_STYLES, 1}));
auto style_tensor_mapped = style_tensor.tensor<float, 2>();
float* out_style = style_tensor_mapped.data();
for (int i = 0; i < NUM_STYLES; i  ) {
    out_style[i] = 0.0 / NUM_STYLES; 
}
out_style[19] = 1.0;

out_style数组中所有值的总和必须为 1,最终的样式迁移图像将是由out_style数组中指定的值加权的样式的混合。 例如,前面的代码将仅使用繁星点点的夜晚样式(数组索引 19 对应于图 4.5 中的样式图像列表中的第 20 个图像)。

如果希望将繁星点点的夜景图像和右上角图像均匀混合,则需要用以下代码替换前面代码块中的最后一行:

代码语言:javascript复制
out_style[4] = 0.5;
out_style[19] = 0.5;

如果您希望所有 26 种样式均等地混合使用,请将前面的for循环更改为以下样式,并且不要将其他值设置为任何特定的out_style元素:

代码语言:javascript复制
for (int i = 0; i < NUM_STYLES; i  ) {
    out_style[i] = 1.0 / NUM_STYLES; 
}

稍后,您将在图 4.8 和 4.9 中看到这三种设置的样式迁移效果。

  1. session->Run调用更改为以下行,以将图像张量和样式张量都发送到模型:
代码语言:javascript复制
tensorflow::Status run_status = session->Run({{input_layer, image_tensor}, {style_layer, style_tensor}}, {output_layer}, {}, &outputs);

这些就是使用多样式模型运行 iOS 应用所需的全部更改。 现在运行您的应用,您将首先看到如图 4.6 所示的内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qKmu1OCt-1681653027416)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/f2841f1f-b0c3-412d-822b-5e9d0f34f235.png)]

图 4.6:显示原始内容图像

点击任意位置,您将看到两个样式选择,如图 4.7 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aLUbStFZ-1681653027417)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/c462927f-71be-46f1-995f-5cbd536ae175.png)]

图 4.7:显示两种样式模型的选择

两张已迁移图像的结果,out_style[19] = 1.0; 如图 4.8 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iWx8H5Nn-1681653027417)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/cc28a550-1a66-4845-b330-1c01d3806e62.png)]

图 4.8:两种不同模型的样式迁移结果(左侧是快速样式迁移,右侧是多样式)

图 4.9 显示了在图 4.5 中使用星夜图像和右上角图像的均等混合以及所有 26 种样式的均等混合的结果,如图 4.9 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NgOQ5lkI-1681653027417)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/c5dd63f4-32a7-4177-afb8-af0e8a8770e8.png)]

图 4.9:多种样式的不同混合的结果(左边:一半星空一半其它,右边:所有 26 种样式的混合)

多样式模型在 iPhone 6 上运行大约需要 5 秒钟,比快速样式迁移模型运行快大约 2-3 倍。

在 Android 中使用 TensorFlow Magenta 多样式模型

尽管 TensorFlow Android 示例应用已经具有使用多种样式模型的代码(实际上我们在上一节的 iOS 应用中使用了 Android 示例应用中的模型),但示例应用中与 TensorFlow 相关的代码却与超过 600 行的StylizeActivity.java文件中的很多 UI 代码混在一起。 您还可以通过 TensorFlow Android 样式迁移的 Codelab 进行操作,但是代码与 TensorFlow Android 示例应用大致相同。 由于我们已经使用 TensorFlow 快速样式迁移模型实现了 Android 应用的简约实现,因此很有趣的是,我们如何仅需更改几行代码就可以拥有一个强大的多样式样式迁移应用。 这也应该是一种更直观的方法,以了解如何将出色的 TensorFlow 模型添加到现有的 Android 应用中。

因此,这就是在我们之前构建的 Android 应用中使用多样式迁移模型所需要的:

  1. stylize_quantized.pb文件从tensorflow/examples/android/assets拖放到我们 Android 应用的assets文件夹中。
  2. 在 Android Studio 中,打开MainActivity.java,找到以下三行代码:
代码语言:javascript复制
private static final String MODEL_FILE = "file:///android_asset/fst_frozen_quantized.pb";
private static final String INPUT_NODE = "img_placeholder";
private static final String OUTPUT_NODE = "preds";

然后将它们替换为以下四行:

代码语言:javascript复制
private static final int NUM_STYLES = 26;
private static final String MODEL_FILE = "file:///android_asset/stylize_quantized.pb";
private static final String INPUT_NODE = "input";
private static final String OUTPUT_NODE = "transformer/expand/conv3/conv/Sigmoid";

这些值与我们在上一节中构建的 iOS 应用相同。 如果您仅进行 Android 应用开发并跳过了上一个 iOS 部分,请快速阅读上一个 iOS 部分中对步骤 3 的解释。

  1. 替换以下代码片段,该片段将输入图像馈送到快速样式迁移模型并处理输出图像:
代码语言:javascript复制
mInferenceInterface.feed(INPUT_NODE, floatValues, WANTED_HEIGHT, WANTED_WIDTH, 3);
mInferenceInterface.run(new String[] {OUTPUT_NODE}, false);
mInferenceInterface.fetch(OUTPUT_NODE, outputValues);
for (int i = 0; i < intValues.length;   i) {
    intValues[i] = 0xFF000000
            | (((int) outputValues[i * 3]) << 16)
            | (((int) outputValues[i * 3   1]) << 8)
            | ((int) outputValues[i * 3   2]);
}

使用首先设置styleVals数组的代码段(如果对styleVals和如何设置数组的值感到困惑,请查看上一节第 5 步中的注解):

代码语言:javascript复制
final float[] styleVals = new float[NUM_STYLES];
for (int i = 0; i < NUM_STYLES;   i) {
    styleVals[i] = 0.0f / NUM_STYLES;
}
styleVals[19] = 0.5f;
styleVals[4] = 0.5f;

然后将输入图像张量和样式值张量同时馈送到模型,并运行模型以获取迁移的图像:

代码语言:javascript复制
mInferenceInterface.feed(INPUT_NODE, floatValues, 1, WANTED_HEIGHT, WANTED_WIDTH, 3);
mInferenceInterface.feed("style_num", styleVals, NUM_STYLES);
mInferenceInterface.run(new String[] {OUTPUT_NODE}, false);
mInferenceInterface.fetch(OUTPUT_NODE, outputValues);

最后,它处理输出:

代码语言:javascript复制
for (int i=0; i < intValues.length;   i) {
    intValues[i] = 0xFF000000
                    | (((int) (outputValues[i*3] * 255)) << 16)
                    | (((int) (outputValues[i*3 1] * 255)) << 8)
                    | ((int) (outputValues[i*3 2] * 255));
}

注意,多样式模型返回浮点数数组到outputValues,它们的范围都在 0.0 到 1.0 之间,因此我们需要将它们相乘。在应用左位移操作以获取红色和绿色值之前,先进行 255 乘以 255,然后对 intValues 数组的每个元素应用按位“或”设置最终 ARGB 值。

这就是将酷炫的多样式模型添加到独立的 Android 应用所需的全部工作。 现在,让我们运行该应用,并使用不同的测试图像,但使用与 iOS 应用中相同的三种样式值组合。

将第 20 和第 5 种样式图像按步骤 3 中的代码片段进行均等混合后,原始图像和迁移的图像如图 4.10 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MDgLznpe-1681653027417)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/f582db90-6795-43d5-8d6d-28e2835e58de.png)]

图 4.10:原始内容图像和样式迁移的图像,结合了第五个图像和繁星点点的夜晚图像

如果替换以下两行代码:

代码语言:javascript复制
styleVals[19] = 0.5f;
styleVals[4] = 0.5f;

用单行代码styleVals[19] = 1.5f;或替换以下代码段:

代码语言:javascript复制
for (int i = 0; i < NUM_STYLES;   i) {
    styleVals[i] = 0.0f / NUM_STYLES;
}
styleVals[19] = 0.5f;
styleVals[4] = 0.5f;

带有以下代码段:

代码语言:javascript复制
for (int i = 0; i < NUM_STYLES;   i) {
    styleVals[i] = 1.0f / NUM_STYLES;
}

然后,您将在图 4.11 中看到效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pGKXVJeG-1681653027417)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/a6009bea-e770-4c76-b7ab-9f426ea66e0b.png)]

图 4.11:仅以星空风格来风格化的图像,以及将所有 26 种风格均等混合的图像

在一些强大的 TensorFlow 模型的帮助下以及我们如何在移动应用中使用它们的知识,看起来我们的移动开发人员也可以成为伟大的艺术家。

总结

在本章中,我们首先概述了自 2015 年以来开发的各种神经样式迁移方法。然后,我们展示了如何训练第二代样式迁移模型,该模型足够快,可以在几秒钟内在移动设备上运行。 之后,我们介绍了如何在 iOS 应用和 Android 应用中使用该模型,这些方法是从头开始构建的,采用极简方法,总共不到 100 行代码。 最后,我们讨论了如何在 iOS 和 Android 应用中使用 TensorFlow Magenta 多样式神经迁移模型,该模型在单个小模型中包含 26 种惊人的艺术样式。

在下一章中,我们将探讨另一个在人类或我们最好的朋友演示时被视为智能的任务:能够识别语音命令。 谁不想让我们的狗狗理解“坐下”,“来”,“不”等命令或我们的婴儿对“是”,“停止”或“走”做出回应? 让我们看看我们如何开发与它们一样的移动应用。

五、了解简单的语音命令

如今,语音服务(例如 Apple Siri,Amazon Alexa,Google Assistant 和 Google Translate)已变得越来越流行,因为语音是我们在某些情况下查找信息或完成任务的最自然和有效的方法。 这些语音服务中的许多服务都是基于云的,因为用户语音可能会很长而且很自由,并且自动语音识别ASR)非常复杂,并且需要大量的计算能力。 实际上,得益于深度学习的突破,仅在最近几年,在自然和嘈杂的环境中 ASR 才变得可行。

但是在某些情况下,能够离线识别设备上的简单语音命令是有意义的。 例如,要控制 Raspberry-Pi 驱动的机器人的运动,您不需要复杂的语音命令,不仅设备上的 ASR 比基于云的解决方案还快,而且即使在没有网络访问的环境。 设备上的简单语音命令识别还可以通过仅在发出某些明确的用户命令时才向服务器发送复杂的用户语音来节省网络带宽。

在本章中,我们将首先概述 ASR 技术,涵盖基于最新的深度学习系统和顶级开源项目。 然后,我们将讨论如何训练和重新训练 TensorFlow 模型,以识别简单的语音命令,例如"left", "right", "up", "down", "stop", "go"。 接下来,我们将使用训练有素的模型来构建一个简单的 Android 应用,然后再构建两个完整的 iOS 应用,一个由 Objective-C 实现,另一个由 Swift 实现。 在前两章中我们没有介绍使用 TensorFlow 模型的基于 Swift 的 iOS 应用,而本章是回顾和加强我们对构建基于 Swift 的 TensorFlow iOS 应用的理解的好地方。

总之,本章将涵盖以下主题:

  • 语音识别 – 快速概述
  • 训练简单的命令识别模型
  • 在 Android 中使用简单的语音识别模型
  • 在带有 Objective-C 的 iOS 中使用简单的语音识别模型
  • 在带有 Swift 的 iOS 中使用简单的语音识别模型

语音识别 – 快速概述

1990 年代出现了第一个实用的独立于说话者的大词汇量和连续语音识别系统。 在 2000 年代初期,领先的初创公司 Nuance 和 SpeechWorks 提供的语音识别引擎为许多第一代基于 Web 的语音服务提供了支持,例如 TellMe,Phone 的 AOL 和 BeVocal。 当时构建的语音识别系统主要基于传统的隐马尔可夫模型HMM),并且需要手动编写语法和安静环境以帮助识别引擎更准确地工作。

现代语音识别引擎几乎可以理解嘈杂环境下人们的任何说话,并且基于端到端深度学习,尤其是另一种更适合自然语言处理的深度神经网络,称为循环神经网络RNN)。 与传统的基于 HMM 的语音识别不同,传统的基于 HMM 的语音识别需要人的专业知识来构建和微调手工设计的特征以及声学和语言模型,而基于 RNN 的端到端语音识别系统则将音频输入直接转换为文本,而无需将音频输入转换为语音表示以进行进一步处理。

RNN 允许我们处理输入和/或输出的序列,因为根据设计,网络可以存储输入序列中的先前项目或可以生成输出序列。 这使 RNN 更适用于语音识别(输入是用户说出的单词序列),图像标题(输出是由一系列单词组成的自然语言句子),文本生成和时间序列预测 。 如果您不熟悉 RNN,则一定要查看 Andrey Karpathy 的博客,循环神经网络的不合理有效性。 在本书的后面,我们还将介绍一些详细的 RNN 模型。

关于 RNN 端到端语音识别的第一篇研究论文发表于 2014 年,使用的是连接主义的时间分类CTC)层。 2014 年下半年,百度发布了 Deep Speech,这是第一个使用基于 CTC 的端到端 RNN 构建但拥有庞大数据集的商业系统之一 ,并在嘈杂的环境中实现了比传统 ASR 系统更低的错误率。 如果您有兴趣,可以查看深度语音的 TensorFlow 实现,但是由于此类基于 CTC 的系统存在问题,生成的模型需要太多的资源才能在手机上运行。 在部署期间,它需要一个大型语言模型来纠正部分由 RNN 的性质引起的生成的文本错误(如果您想知道为什么,请阅读前面链接的 RNN 博客以获取一些见识)。

在 2015 年和 2016 年,较新的语音识别系统使用了类似的端到端 RNN 方法,但将 CTC 层替换为基于注意力的模型,因此运行模型时不需要大型语言模型,因此可以在内存有限的移动设备上进行部署。 在本书的此版本中,我们将不会探讨这种可能性,而将介绍如何在移动应用中使用最新的高级 ASR 模型。 相反,我们将从一个更简单的语音识别模型开始,我们知道该模型肯定会在移动设备上很好地工作。

要将离线语音识别功能添加到移动应用,您还可以使用以下两个领先的开源语音识别项目之一:

  • CMU Sphinx 大约 20 年前开始,但仍在积极开发中。 要构建具有语音识别功能的 Android 应用,您可以使用其为 Android 构建的 PocketSphinx。 要构建具有语音识别功能的 iOS 应用,您可以使用 OpenEars 框架,这是一个免费的 SDK,在 iOS 应用中使用 CMU PocketSphinx 构建离线语音识别和文本转换。
  • Kaldi,成立于 2009 年,最近非常活跃,截至 2018 年 1 月,已有 165 个参与者。要在 Android 上进行尝试,您可以查看此博客文章。 对于 iOS,请查看在 iOS 上使用 Kaldi 的原型。

由于这是一本关于在移动设备上使用 TensorFlow 的书,因此 TensorFlow 可用于为图像处理,语音处理和文本处理以及其他智能任务(本章其余部分的)构建强大的模型。 我们将重点介绍如何使用 TensorFlow 训练简单的语音识别模型并将其在移动应用中使用。

训练简单的命令识别模型

在本节中,我们将总结编写良好的 TensorFlow 简单音频识别教程中使用的步骤。 一些在训练模型时可能对您有帮助的提示。

我们将建立的简单语音命令识别模型将能够识别 10 个单词:"yes", "no", "up", "down", "left", "right", "on", "off", "stop", "go"; 它也可以检测沉默。 如果没有发现沉默,并且没有发现 10 个单词,它将生成“未知”。 稍后运行tensorflow/example/speech_commands/train.py脚本时,我们将下载语音命令数据集并用于训练模型,实际上除了这 10 个单词外,还包含 20 个单词:"zero", "two", "three", ..., "ten"(到目前为止,您已经看到的 20 个词称为核心词)和 10 个辅助词:"bed", "bird", "cat", "dog", "happy", "house", "marvin", "sheila", "tree", "wow"。 核心词比辅助词(约 1750)具有更多的.wav文件记录(约 2350)。

语音命令数据集是从开放语音记录站点收集的。您应该尝试一下,也许自己花些时间来录制自己的录音,以帮助改善录音效果,并在需要时了解如何收集自己的语音命令数据集。 关于使用数据集构建模型,还有一个 Kaggle 竞赛,您可以在此处了解有关语音模型和提示的更多信息。

在移动应用中要训练和使用的模型基于纸质卷积神经网络,用于小大小关键词发现,这与大多数其他基于 RNN 的大规模语音识别模型不同。 基于 CNN 的语音识别模型是可能的,但很有趣,因为对于简单的语音命令识别,我们可以在短时间内将音频信号转换为图像,或更准确地说,将频谱图转换为频率窗口期间音频信号的分布(有关使用wav_to_spectrogram脚本生成的示例频谱图图像,请参见本节开头的 TensorFlow 教程链接)。 换句话说,我们可以将音频信号从其原始时域表示转换为频域表示。 进行此转换的最佳算法是离散傅立叶变换DFT),快速傅立叶变换FFT)只是一种有效的选择 DFT 实现的算法。

作为移动开发人员,您可能不需要了解 DFT 和 FFT。 但是,您最好了解所有这些模型训练在移动应用中使用时是如何工作的,因为我们知道我们将要介绍的 TensorFlow 简单语音命令模型训练的幕后花絮,这是 FFT 的使用,前十大模型之一。当然,除其他事项外,20 世纪的算法使基于 CNN 的语音命令识别模型训练成为可能。 有关 DFT 的有趣且直观的教程,您可以阅读以下文章。

现在,让我们执行以下步骤来训练简单语音命令识别模型:

  1. 在终端上,cd到您的 TensorFlow 源根,可能是~/tensorflow-1.4.0
  2. 只需运行以下命令即可下载我们之前讨论的语音命令数据集:
代码语言:javascript复制
python tensorflow/examples/speech_commands/train.py

您可以使用许多参数:--wanted_words默认为以yes开头的 10 个核心词; 您可以使用此参数添加更多可以被模型识别的单词。 要训​​练自己的语音命令数据集,请使用--data_url --data_dir=<path_to_your_dataset>禁用语音命令数据集的下载并访问您自己的数据集,其中每个命令应命名为自己的文件夹,其中应包含 1000-2000 个音频剪辑,大约需要 1 秒钟的长度; 如果音频片段更长,则可以相应地更改--clip_duration_ms参数值。 有关更多详细信息,请参见train.py源代码和 TensorFlow 简单音频识别教程。

  1. 如果您接受train.py的所有默认参数,则在下载 1.48GB 语音命令数据集之后,在 GTX-1070 GPU 驱动的 Ubuntu 上,完成 18,000 个步骤的整个训练大约需要 90 分钟。 训练完成后,您应该在/tmp/speech_commands_train文件夹内看到检查点文件的列表,以及conv.pbtxt图定义文件和名为conv_labels.txt的标签文件,其中包含命令列表(与命令列表相同)。 --wanted_words参数是默认值或设置为,在文件的开头加上两个附加词_silence_unknown):
代码语言:javascript复制
-rw-rw-r-- 1 jeff jeff 75437 Dec 9 21:08 conv.ckpt-18000.meta
-rw-rw-r-- 1 jeff jeff 433 Dec 9 21:08 checkpoint
-rw-rw-r-- 1 jeff jeff 3707448 Dec 9 21:08 conv.ckpt-18000.data-00000-of-00001
-rw-rw-r-- 1 jeff jeff 315 Dec 9 21:08 conv.ckpt-18000.index
-rw-rw-r-- 1 jeff jeff 75437 Dec 9 21:08 conv.ckpt-17900.meta
-rw-rw-r-- 1 jeff jeff 3707448 Dec 9 21:08 conv.ckpt-17900.data-00000-of-00001
-rw-rw-r-- 1 jeff jeff 315 Dec 9 21:08 conv.ckpt-17900.index
-rw-rw-r-- 1 jeff jeff 75437 Dec 9 21:07 conv.ckpt-17800.meta
-rw-rw-r-- 1 jeff jeff 3707448 Dec 9 21:07 conv.ckpt-17800.data-00000-of-00001
-rw-rw-r-- 1 jeff jeff 315 Dec 9 21:07 conv.ckpt-17800.index
-rw-rw-r-- 1 jeff jeff 75437 Dec 9 21:07 conv.ckpt-17700.meta
-rw-rw-r-- 1 jeff jeff 3707448 Dec 9 21:07 conv.ckpt-17700.data-00000-of-00001
-rw-rw-r-- 1 jeff jeff 315 Dec 9 21:07 conv.ckpt-17700.index
-rw-rw-r-- 1 jeff jeff 75437 Dec 9 21:06 conv.ckpt-17600.meta
-rw-rw-r-- 1 jeff jeff 3707448 Dec 9 21:06 conv.ckpt-17600.data-00000-of-00001
-rw-rw-r-- 1 jeff jeff 315 Dec 9 21:06 conv.ckpt-17600.index
-rw-rw-r-- 1 jeff jeff 60 Dec 9 19:41 conv_labels.txt
-rw-rw-r-- 1 jeff jeff 121649 Dec 9 19:41 conv.pbtxt

conv_labels.txt包含以下命令:

代码语言:javascript复制
_silence_
_unknown_
yes
no
up
down
left
right
on
off
stop
go

现在运行以下命令,将图定义文件和检查点文件组合成一个我们可以在移动应用中使用的模型文件:

代码语言:javascript复制
python tensorflow/examples/speech_commands/freeze.py 
--start_checkpoint=/tmp/speech_commands_train/conv.ckpt-18000 
--output_file=/tmp/speech_commands_graph.pb
  1. (可选)在移动应用中部署speech_commands_graph.pb模型文件之前,可以使用以下命令对其进行快速测试:
代码语言:javascript复制
python tensorflow/examples/speech_commands/label_wav.py  
--graph=/tmp/speech_commands_graph.pb 
--labels=/tmp/speech_commands_train/conv_labels.txt 
--wav=/tmp/speech_dataset/go/9d171fee_nohash_1.wav

您将看到类似以下的输出:

代码语言:javascript复制
go (score = 0.48427)
no (score = 0.17657)
_unknown_ (score = 0.08560)
  1. 使用summarize_graph工具查找输入节点和输出节点的名称:
代码语言:javascript复制
bazel-bin/tensorflow/tools/graph_transforms/summarize_graph --in_graph=/tmp/speech_commands_graph.pb

输出应如下所示:

代码语言:javascript复制
Found 1 possible inputs: (name=wav_data, type=string(7), shape=[]) 
No variables spotted.
Found 1 possible outputs: (name=labels_softmax, op=Softmax) 

不幸的是,它仅对于输出名称是正确的,并且不显示其他可能的输入。 使用tensorboard --logdir /tmp/retrain_logs,然后在浏览器中打开http://localhost:6006与图进行交互也无济于事。 但是,前面各章中显示的小代码段可以帮助您了解输入和输出名称,以下内容与 iPython 进行了交互:

代码语言:javascript复制
In [1]: import tensorflow as tf
In [2]: g=tf.GraphDef()
In [3]: g.ParseFromString(open("/tmp/speech_commands_graph.pb","rb").read())
In [4]: x=[n.name for n in g.node]
In [5]: x
Out[5]: 
[u'wav_data',
 u'decoded_sample_data',
 u'AudioSpectrogram',
 ...
 u'MatMul',
 u'add_2',
 u'labels_softmax']

因此,我们看到wav_datadecoded_sample_data都是可能的输入。 如果在freeze.py文件中看不到注释,我们就必须深入研究模型训练代码,以准确找出应该使用的输入名称:“结果图包含一个名为 WAV 的编码数据输入 wav_data,用于原始 PCM 数据(在 -1.0 到 1.0 范围内浮动)的一种称为decoded_sample_data,输出称为labels_softmax。” 实际上,在该模型的情况下,有一个 TensorFlow Android 示例应用,这是我们在第 1 章,“移动 TensorFlow 入门”中看到的一部分,称为 TF 语音,专门定义了那些输入名称和输出名称。 在本书后面的几章中,您将看到如何在需要时借助或不借助我们的三种方法来查找模型训练的源代码,以找出关键的输入和输出节点名称。 或者希望,当您阅读本书时,TensorFlow summarize_graph工具将得到改进,以为我们提供准确的输入和输出节点名称。

现在是时候在移动应用中使用我们的热门新模型了。

在 Android 中使用简单的语音识别模型

位于tensorflow/example/android的用于简单语音命令识别的 TensorFlow Android 示例应用具有在SpeechActivity.java文件中进行音频记录和识别的代码,假定该应用需要始终准备好接受新的音频命令。 尽管在某些情况下这确实是合理的,但它导致的代码比仅在用户按下按钮后才进行记录和识别的代码要复杂得多,例如 Apple 的 Siri 的工作方式。 在本部分中,我们将向您展示如何创建新的 Android 应用并添加尽可能少的代码来记录用户的语音命令并显示识别结果。 这应该可以帮助您更轻松地将模型集成到自己的 Android 应用中。 但是,如果您需要处理语音命令应始终自动记录和识别的情况,则应查看 TensorFlow 示例 Android 应用。

使用模型构建新应用

执行以下步骤来构建一个完整的新 Android 应用,该应用使用我们在上一节中构建的speech_commands_graph.pb模型:

  1. 通过接受前面几章中的所有默认设置,创建一个名为AudioRecognition的新 Android 应用,然后将compile 'org.tensorflow:tensorflow-android: '行添加到应用build.gradle文件依赖项的末尾。
  2. <uses-permission android:name="android.permission.RECORD_AUDIO" />添加到应用的AndroidManifest.xml文件中,以便可以允许该应用记录音频。
  3. 创建一个新的资产文件夹,然后将在上一节的步骤 2 和 3 中生成的speech_commands_graph.pbconv_actions_labels.txt文件拖放到assets文件夹中。
  4. 更改activity_main.xml文件以容纳三个 UI 元素。 第一个是用于识别结果显示的TextView
代码语言:javascript复制
<TextView
    android:id="@ id/textview"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text=""
    android:textSize="24sp"
    android:textStyle="bold"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

第二个TextView将显示上一节第 2 步中使用train.py Python 程序训练的 10 个默认命令:

代码语言:javascript复制
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="yes no up down left right on off stop go"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintHorizontal_bias="0.50"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.25" />

最后一个 UI 元素是一个按钮,在点击该按钮时,它会开始录音一秒钟,然后将录音发送到我们的模型以进行识别:

代码语言:javascript复制
<Button
    android:id="@ id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Start"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintHorizontal_bias="0.50"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.8" />
  1. 打开MainActivity.java,首先创建MainActivity implements Runnable类。 然后添加以下常量,以定义模型名称,标签名称,输入名称和输出名称:
代码语言:javascript复制
private static final String MODEL_FILENAME = "file:///android_asset/speech_commands_graph.pb";
private static final String LABEL_FILENAME = "file:///android_asset/conv_actions_labels.txt";
private static final String INPUT_DATA_NAME = "decoded_sample_data:0";
private static final String INPUT_SAMPLE_RATE_NAME = "decoded_sample_data:1";
private static final String OUTPUT_NODE_NAME = "labels_softmax";
  1. 声明四个实例变量:
代码语言:javascript复制
private TensorFlowInferenceInterface mInferenceInterface;
private List<String> mLabels = new ArrayList<String>();
private Button mButton;
private TextView mTextView;
  1. onCreate方法中,我们首先实例化mButtonmTextView,然后设置按钮单击事件处理器,该事件处理器首先更改按钮标题,然后启动线程进行记录和识别:
代码语言:javascript复制
mButton = findViewById(R.id.button);
mTextView = findViewById(R.id.textview);
mButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mButton.setText("Listening...");
        Thread thread = new Thread(MainActivity.this);
        thread.start();
    }
});

onCreate方法的末尾,我们逐行读取标签文件的内容,并将每一行保存在mLabels数组列表中。

  1. public void run()方法的开头(单击“开始”按钮时开始),添加代码,该代码首先获得用于创建 Android AudioRecord对象的最小缓冲区大小,然后使用buffersize创建新的AudioRecord实例具有 16,000 SAMPLE_RATE和 16 位单声道格式,这是我们模型所期望的原始音频的类型,并最终从AudioRecord实例开始记录:
代码语言:javascript复制
int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);

if (record.getState() != AudioRecord.STATE_INITIALIZED) return;
record.startRecording();

Android 中有两个用于记录音频的类:MediaRecorderAudioRecordMediaRecorderAudioRecord更易于使用,但是它会保存压缩的音频文件,直到 Android API Level 24(Android 7.0)为止,该 API 支持录制未经处理的原始音频。 根据这里,截至 2018 年 1 月,市场上有 70% 以上的 Android 设备仍在运行 7.0 或更早的 Android 版本。 您可能不希望将应用定位到 Android 7.0 或更高版本。 另外,要解码由MediaRecorder录制的压缩音频,您必须使用MediaCodec,使用起来非常复杂。 AudioRecord尽管是一个低级的 API,但实际上非常适合记录未处理的原始数据,然后将其发送到语音命令识别模型进行处理。

  1. 创建两个由 16 位短整数组成的数组audioBufferrecordingBuffer,对于 1 秒记录,每次AudioRecord对象读取并填充audioBuffer数组后,实际读取的数据都会附加到 recordingBuffer
代码语言:javascript复制
long shortsRead = 0;
int recordingOffset = 0;
short[] audioBuffer = new short[bufferSize / 2];
short[] recordingBuffer = new short[RECORDING_LENGTH];
while (shortsRead < RECORDING_LENGTH) { // 1 second of recording
    int numberOfShort = record.read(audioBuffer, 0, audioBuffer.length);
    shortsRead  = numberOfShort;
    System.arraycopy(audioBuffer, 0, recordingBuffer, recordingOffset, numberOfShort);
    recordingOffset  = numberOfShort;
}
record.stop();
record.release();
  1. 录制完成后,我们首先将按钮标题更改为Recognizing
代码语言:javascript复制
runOnUiThread(new Runnable() {
    @Override
    public void run() {
        mButton.setText("Recognizing...");
    }
});

然后将recordingBuffer短数组转换​​为float数组,同时使float数组的每个元素都在 -1.0 和 1.0 的范围内,因为我们的模型期望在-之间浮动 1.0 和 1.0:

代码语言:javascript复制
float[] floatInputBuffer = new float[RECORDING_LENGTH];
for (int i = 0; i < RECORDING_LENGTH;   i) {
    floatInputBuffer[i] = recordingBuffer[i] / 32767.0f;
}
  1. 如前几章所述,创建一个新的TensorFlowInferenceInterface,然后使用两个输入节点的名称和值调用其feed方法,其中一个是采样率,另一个是存储在floatInputBuffer中的原始音频数据 ]数组:
代码语言:javascript复制
AssetManager assetManager = getAssets();
mInferenceInterface = new TensorFlowInferenceInterface(assetManager, MODEL_FILENAME);

int[] sampleRate = new int[] {SAMPLE_RATE};
mInferenceInterface.feed(INPUT_SAMPLE_RATE_NAME, sampleRate);

mInferenceInterface.feed(INPUT_DATA_NAME, floatInputBuffer, RECORDING_LENGTH, 1);

之后,我们调用run方法在模型上运行识别推理,然后fetch输出 10 个语音命令中每个命令的输出分数以及“未知”和“沉默”输出:

代码语言:javascript复制
String[] outputScoresNames = new String[] {OUTPUT_NODE_NAME};
mInferenceInterface.run(outputScoresNames);

float[] outputScores = new float[mLabels.size()];
mInferenceInterface.fetch(OUTPUT_NODE_NAME, outputScores);
  1. outputScores数组与mLabels列表匹配,因此我们可以轻松找到最高得分并获取其命令名称:
代码语言:javascript复制
float max = outputScores[0];
int idx = 0;
for (int i=1; i<outputScores.length; i  ) {
    if (outputScores[i] > max) {
        max = outputScores[i];
        idx = i;
    }
}
final String result = mLabels.get(idx);

最后,我们在TextView中显示结果,并将按钮标题更改回"Start",以便用户可以再次开始记录和识别语音命令:

代码语言:javascript复制
runOnUiThread(new Runnable() {
    @Override
    public void run() {
        mButton.setText("Start");
        mTextView.setText(result);
    }
});

显示模型驱动的识别结果

现在,在您的 Android 设备上运行该应用。 您将看到如图 5.1 所示的初始屏幕:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wWZgl2Td-1681653027418)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/a321196b-4e44-43ec-94c5-42ee47ea29d9.png)]

图 5.1:应用启动后显示初始屏幕

点击START按钮,然后开始说上面显示的 10 个命令之一。 您将看到按钮标题更改为“监听…”,然后是“识别…”,如图 5.2 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yhAszZku-1681653027418)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/f4eb7e04-1d5f-4c70-a91c-055d8cdcb1c6.png)]

图 5.2:监听录制的音频并识别录制的音频

识别结果几乎实时显示在屏幕中间,如图 5.3 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CC2hC76Q-1681653027418)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/bc4d09f3-f7d6-47fa-91f5-d0130635da7c.png)]

图 5.3:显示识别的语音命令

整个识别过程几乎立即完成,用于识别的speech_commands_graph.pb模型仅为 3.7MB。 当然,它仅支持 10 条语音命令,但是即使使用train.py脚本的 --wanted_words参数或您自己的数据集支持数十个命令,大小也不会发生太大变化,正如我们在训练部分中讨论的那样。

诚然,此处的应用屏幕截图并不像上一章中那样生动有趣(一张图片价值一千个单词),但是语音识别当然可以做艺术家不能做的事情,例如发出语音命令来控制机器人的运动。

该应用的完整源代码位于 Github 上该书的源代码存储库的Ch5/android文件夹中。 现在让我们看看如何使用该模型构建 iOS 应用,其中涉及一些复杂的 TensorFlow iOS 库构建和音频数据准备步骤,以使模型正确运行。

通过 Objective-C 在 iOS 中使用简单的语音识别模型

如果您已经阅读了前三章中的 iOS 应用,那么您可能更喜欢使用手动构建的 TensorFlow iOS 库而不是 TensorFlow 实验窗格,就像使用手动库方法一样,您可以更好地控制可以添加哪些 TensorFlow 操作来使您的模型满意,这也是我们决定专注于 TensorFlow Mobile 而不是第 1 章,“移动 TensorFlow”的 TensorFlow Lite 的原因之一。

因此,尽管您可以在阅读本书时尝试使用 TensorFlow Pod,以查看 Pod 是否已更新以支持模型中使用的所有操作,但从现在开始,我们将始终使用手动构建的 TensorFlow 库( 请参见 iOS 应用中第 3 章,“检测对象及其位置”的“在 iOS 中使用对象检测模型的”部分的步骤 1 和 2)。

使用模型构建新应用

现在执行以下步骤来创建一个新的 iOS 应用以使用语音命令识别模型:

  1. 在 Xcode 中创建一个名为 AudioRecognition 的新 Objective-C 应用,并将项目设置为使用 TensorFlow 手动构建的库,如“以惊人的艺术样式迁移图片”的步骤 1 中所述。 还将AudioToolbox.frameworkAVFoundation.frameworkAccelerate.framework添加到目标的带库的链接二进制文件。
  2. speech_commands_graph.pb模型文件拖放到项目中。
  3. ViewController.m的扩展名更改为mm,然后添加音频记录和处理所使用的以下标头:
代码语言:javascript复制
#import <AVFoundation/AVAudioRecorder.h>
#import <AVFoundation/AVAudioSettings.h>
#import <AVFoundation/AVAudioSession.h>
#import <AudioToolbox/AudioToolbox.h>

还添加 TensorFlow 的标头:

代码语言:javascript复制
#include <fstream>
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/public/session.h"

现在,定义一个音频SAMPLE_RATE常量,一个指向浮点数组的 C 指针,该数组保存将要发送到模型的音频数据,我们的关键audioRecognition函数签名以及两个属性,其中包含记录的文件路径和一个 iOS AVAudioRecorder实例。 我们还需要让ViewController实现AudioRecorderDelegate,以便它知道录制何时结束:

代码语言:javascript复制
const int SAMPLE_RATE = 16000;
float *floatInputBuffer;
std::string audioRecognition(float* floatInputBuffer, int length);
@interface ViewController () <AVAudioRecorderDelegate>
@property (nonatomic, strong) NSString *recorderFilePath;
@property (nonatomic, strong) AVAudioRecorder *recorder;
@end

在此,我们不会显示以编程方式创建两个 UI 元素的代码段:一个按钮,当您点击该按钮时,它将开始录制 1 秒钟的音频,然后将音频发送到我们的模型以进行识别,以及一个显示识别结果的标签。 但是,我们将在下一部分中的 Swift 中展示一些 UI 代码以供复习。

  1. 在按钮的UIControlEventTouchUpInside处理器内,我们首先创建一个AVAudioSession实例,并将其类别设置为记录并将其激活:
代码语言:javascript复制
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
NSError *err = nil;
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&err];
if(err){
    NSLog(@"audioSession: %@", [[err userInfo] description]);
    return;
}

[audioSession setActive:YES error:&err];
if(err){
    NSLog(@"audioSession: %@", [[err userInfo] description]);
    return;
}

然后创建一个记录设置字典:

代码语言:javascript复制
NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:SAMPLE_RATE] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt: 1] forKey:AVNumberOfChannelsKey];
[recordSetting setValue :[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey];
[recordSetting setValue :[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsBigEndianKey];
[recordSetting setValue :[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsFloatKey];
[recordSetting setValue:[NSNumber numberWithInt:AVAudioQualityMax] forKey:AVEncoderAudioQualityKey];

最后,在按钮点击处理器中,我们定义保存录制的音频的位置,创建AVAudioRecorder实例,设置其委托并开始录制 1 秒钟:

代码语言:javascript复制
self.recorderFilePath = [NSString stringWithFormat:@"%@/recorded_file.wav", [NSHomeDirectory() stringByAppendingPathComponent:@"tmp"]];
NSURL *url = [NSURL fileURLWithPath:_recorderFilePath];
err = nil;
_recorder = [[ AVAudioRecorder alloc] initWithURL:url settings:recordSetting error:&err];
if(!_recorder){
    NSLog(@"recorder: %@", [[err userInfo] description]);
    return;
}
[_recorder setDelegate:self];
[_recorder prepareToRecord];
[_recorder recordForDuration:1];
  1. AVAudioRecorderDelegateaudioRecorderDidFinishRecording的委托方法中,我们使用 Apple 的扩展音频文件服务,该服务用于读写压缩和线性 PCM 音频文件,以加载记录的音频,并将其转换为模型所需的格式, 并将音频数据读入存储器。 我们在这里不会显示这部分代码,它主要基于此博客。 在此处理之后,floatInputBuffer指向原始音频样本。 现在,我们可以将数据传递到工作线程中的audioRecognition方法中,并在 UI 线程中显示结果:
代码语言:javascript复制
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    std::string command = audioRecognition(floatInputBuffer, totalRead);
    delete [] floatInputBuffer;
    dispatch_async(dispatch_get_main_queue(), ^{
        NSString *cmd = [NSString stringWithCString:command.c_str() encoding:[NSString defaultCStringEncoding]];
        [_lbl setText:cmd];
        [_btn setTitle:@"Start" forState:UIControlStateNormal];
    });
});
  1. audioRecognition方法内部,我们首先定义一个 C string数组,其中包含要识别的 10 个命令以及两个特殊值"_silence_""_unknown_"
代码语言:javascript复制
std::string commands[] = {"_silence_", "_unknown_", "yes", "no", "up", "down", "left", "right", "on", "off", "stop", "go"};

在完成标准 TensorFlow SessionStatusGraphDef设置后(如我们在前几章的 iOS 应用中所做的那样),我们读出了模型文件,并尝试使用它创建 TensorFlow Session

代码语言:javascript复制
NSString* network_path = FilePathForResourceName(@"speech_commands_graph", @"pb");

PortableReadFileToProto([network_path UTF8String], &tensorflow_graph);

tensorflow::Status s = session->Create(tensorflow_graph);
if (!s.ok()) {
    LOG(ERROR) << "Could not create TensorFlow Graph: " << s;
    return "";
}

如果成功创建了会话,则为模型定义两个输入节点名称和一个输出节点名称:

代码语言:javascript复制
std::string input_name1 = "decoded_sample_data:0";
std::string input_name2 = "decoded_sample_data:1";
std::string output_name = "labels_softmax";
  1. 对于"decoded_sample_data:0",我们需要将采样率值作为标量发送(否则在调用 TensorFlow Sessionrun方法时会出错),并且在 TensorFlow C API 中定义了张量,如下所示:
代码语言:javascript复制
tensorflow::Tensor samplerate_tensor(tensorflow::DT_INT32, tensorflow::TensorShape());
samplerate_tensor.scalar<int>()() = SAMPLE_RATE;

对于 "decoded_sample_data:1",需要将浮点数中的音频数据从floatInputBuffer数组转换为 TensorFlow audio_tensor张量,其方式类似于前几章的image_tensor的定义和设置方式:

代码语言:javascript复制
tensorflow::Tensor audio_tensor(tensorflow::DT_FLOAT, tensorflow::TensorShape({length, 1}));
auto audio_tensor_mapped = audio_tensor.tensor<float, 2>();
float* out = audio_tensor_mapped.data();
for (int i = 0; i < length; i  ) {
    out[i] = floatInputBuffer[i];
}

现在我们可以像以前一样使用输入来运行模型并获取输出:

代码语言:javascript复制
std::vector<tensorflow::Tensor> outputScores;
tensorflow::Status run_status = session->Run({{input_name1, audio_tensor}, {input_name2, samplerate_tensor}},{output_name}, {}, &outputScores);
if (!run_status.ok()) {
    LOG(ERROR) << "Running model failed: " << run_status;
    return "";
}
  1. 我们对模型的outputScores输出进行简单的解析,然后返回最高分。 outputScores是 TensorFlow 张量的向量,其第一个元素包含 12 个可能的识别结果的 12 个得分值。 可以通过flat方法访问这 12 个得分值,并检查最大得分:
代码语言:javascript复制
tensorflow::Tensor* output = &outputScores[0];
const Eigen::TensorMap<Eigen::Tensor<float, 1, Eigen::RowMajor>, Eigen::Aligned>& prediction = output->flat<float>();
const long count = prediction.size();
int idx = 0;
float max = prediction(0);
for (int i = 1; i < count; i  ) {
    const float value = prediction(i);
    printf("%d: %f", i, value);
    if (value > max) {
        max = value;
        idx = i;
    }
}

return commands[idx];

在应用可以录制任何音频之前,您需要做的另一件事是在应用的Info.plist文件中创建一个新的隐私-麦克风使用说明属性,并将该属性的值设置为诸如“听到并识别” 您的语音命令”。

现在,在 iOS 模拟器上运行该应用(如果您的 Xcode 版本早于 9.2,而 iOS 模拟器版本早于 10.0,则您可能必须在实际的 iOS 设备上运行该应用,因为您可能无法在 iOS 或 iPhone 模拟器(10.0 之前的版本)中录制音频,您将首先看到带有 Start 按钮位于中间的初始屏幕,然后点击该按钮并说出 10 个命令之一,识别结果应出现在顶部 ,如图 5.4 所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JIQ5O5pu-1681653027418)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/71ef1770-f7c0-4152-bb72-ea7993f5b728.png)]

图 5.4:显示初始画面和识别结果

是的,应该会出现识别结果,但实际上不会出现,因为在 Xcode 输出窗格中会出现错误:

代码语言:javascript复制
Could not create TensorFlow Graph: Not found: Op type not registered 'DecodeWav' in binary running on XXX's-MacBook-Pro.local. Make sure the Op and Kernel are registered in the binary running in this process.

使用tf_op_files.txt修复模型加载错误

我们已经在前面的章节中看到了这种臭名昭著的错误,除非您知道它的真正含义,否则弄清楚该修复程序可能要花很多时间。 TensorFlow 操作由两部分组成:位于 tensorflow/core/ops文件夹中的称为ops的定义(这有点令人困惑,因为操作既可以表示其定义,其实现,也可以表示其定义)。 和位于 tensorflow/core/kernels文件夹中的实现(称为内核)。 tensorflow/contrib/makefile文件夹中有一个名为tf_op_files.txt的文件,其中列出了在手动构建库时需要内置到 TensorFlow iOS 库中的操作的定义和实现。 tf_op_files.txt文件应该包含所有操作定义文件,如为 TensorFlow 移动部署准备模型,因为它们占用的空间很小。 但从 TensorFlow 1.4 或 1.5 开始,tf_op_files.txt文件中并未包含所有操作的操作定义。 因此,当我们看到“未注册操作类型”错误时,我们需要找出哪个操作定义和实现文件负责该操作。 在我们的情况下,操作类型名为DecodeWav。 我们可以运行以下两个 Shell 命令来获取信息:

代码语言:javascript复制
$ grep 'REGISTER.*"DecodeWav"' tensorflow/core/ops/*.cc
tensorflow/core/ops/audio_ops.cc:REGISTER_OP("DecodeWav")

$ grep 'REGISTER.*"DecodeWav"' tensorflow/core/kernels/*.cc
tensorflow/core/kernels/decode_wav_op.cc:REGISTER_KERNEL_BUILDER(Name("DecodeWav").Device(DEVICE_CPU), DecodeWavOp);

在 TensorFlow 1.4 的 tf_op_files.txt文件中,已经有一行文本tensorflow/core/kernels/decode_wav_op.cc,但可以肯定的是tensorflow/core/ops/audio_ops.cc丢失了。 我们需要做的就是在tf_op_files.txt文件中的任意位置添加一行tensorflow/core/ops/audio_ops.cc,并像在第 3 章,“检测对象及其位置”中一样运行tensorflow/contrib/makefile/build_all_ios.sh,以重建 TensorFlow iOS 库。 然后再次运行 iOS 应用,并继续轻按启动按钮,然后说出语音命令以识别或误解,直到您无聊为止。

本章将重点介绍如何解决Not found: Op type not registered错误的过程,因为将来在其他 TensorFlow 模型上工作时,可以节省大量时间。

但是,在继续学习下一章中将介绍和使用另一种新的 TensorFlow AI 模型之前,让我们给其他喜欢使用更新的且至少对他们更凉快的 Swift 语言的 iOS 开发人员一些考虑。

通过 Swift 在 iOS 中使用简单的语音识别模型

我们在第 2 章中使用 TensorFlow 窗格创建了一个基于 Swift 的 iOS 应用。 现在让我们创建一个新的 Swift 应用,该应用使用我们在上一节中手动构建的 TensorFlow iOS 库,并在我们的 Swift 应用中使用语音命令模型:

  1. 通过 Xcode 创建一个新的“Single View iOS”项目,并按照与上一节中的步骤 1 和 2 相同的方式设置该项目,除了将语言设置为 Swift。
  2. 选择 Xcode “文件 | 新增 | 文件 …”,然后选择 Objective-C 文件。 输入名称RunInference。 您将看到一个消息框,询问您“您是否要配置一个 Objective-C 桥接头?” 单击创建桥接标题。 将文件RunInference.m重命名为RunInfence.mm,因为我们将混合使用 C,C 和 Objective-C 代码来进行后期录音音频处理和识别。 我们仍在 Swift 应用中使用 Objective-C,因为要从 Swift 调用 TensorFlow C 代码,我们需要一个 Objective-C 类作为 C 代码的包装。
  3. 创建一个名为RunInference.h的头文件,并添加以下代码:
代码语言:javascript复制
@interface RunInference_Wrapper : NSObject
- (NSString *)run_inference_wrapper:(NSString*)recorderFilePath;
@end

现在,您在 Xcode 中的应用应类似于图 5.5:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MJtM9Z41-1681653027419)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/intel-mobi-proj-tf/img/3febd651-7749-4a23-ba86-ae458ef8cfea.png)]

图 5.5:基于 Swift 的 iOS 应用项目

  1. 打开ViewController.swift。 在import UIKit之后的顶部添加以下代码:
代码语言:javascript复制
import AVFoundation

let _lbl = UILabel()
let _btn = UIButton(type: .system)
var _recorderFilePath: String!

然后使ViewController看起来像这样(未显示为_btn_lbl定义NSLayoutConstraint并调用addConstraint的代码段):

代码语言:javascript复制
class ViewController: UIViewController, AVAudioRecorderDelegate {
    var audioRecorder: AVAudioRecorder!
override func viewDidLoad() {
    super.viewDidLoad()

    _btn.translatesAutoresizingMaskIntoConstraints = false
    _btn.titleLabel?.font = UIFont.systemFont(ofSize:32)
    _btn.setTitle("Start", for: .normal)
    self.view.addSubview(_btn)

    _btn.addTarget(self, action:#selector(btnTapped), for: .touchUpInside)

    _lbl.translatesAutoresizingMaskIntoConstraints = false
    self.view.addSubview(_lbl)
  1. 添加一个按钮点击处理器,并在其内部,首先请求用户的录制许可:
代码语言:javascript复制
@objc func btnTapped() {
    _lbl.text = "..."
    _btn.setTitle("Listening...", for: .normal)

    AVAudioSession.sharedInstance().requestRecordPermission () {
        [unowned self] allowed in
        if allowed {
            print("mic allowed")
        } else {
            print("denied by user")
            return
        }
    }

然后创建一个AudioSession实例,并将其类别设置为记录,并将状态设置为活动,就像我们在 Objective-C 版本中所做的一样:

代码语言:javascript复制
let audioSession = AVAudioSession.sharedInstance()

do {
    try audioSession.setCategory(AVAudioSessionCategoryRecord)
    try audioSession.setActive(true)
} catch {
    print("recording exception")
    return
}

现在定义AVAudioRecorder要使用的设置:

代码语言:javascript复制
let settings = [
    AVFormatIDKey: Int(kAudioFormatLinearPCM),
    AVSampleRateKey: 16000,
    AVNumberOfChannelsKey: 1,
    AVLinearPCMBitDepthKey: 16,
    AVLinearPCMIsBigEndianKey: false,
    AVLinearPCMIsFloatKey: false,
    AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
    ] as [String : Any]

设置文件路径以保存录制的音频,创建AVAudioRecorder实例,设置其委托并开始录制 1 秒钟:

代码语言:javascript复制
do {
    _recorderFilePath = NSHomeDirectory().stringByAppendingPathComponent(path: "tmp").stringByAppendingPathComponent(path: "recorded_file.wav")
    audioRecorder = try AVAudioRecorder(url: NSURL.fileURL(withPath: _recorderFilePath), settings: settings)
    audioRecorder.delegate = self
    audioRecorder.record(forDuration: 1)
} catch let error {
    print("error:"   error.localizedDescription)
}
  1. ViewController.swift的末尾,添加具有以下实现的AVAudioRecorderDelegate方法audioRecorderDidFinishRecording,该实现主要调用run_inference_wrapper进行音频后处理和识别:
代码语言:javascript复制
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
    _btn.setTitle("Recognizing...", for: .normal)
    if flag {
        let result = RunInference_Wrapper().run_inference_wrapper(_recorderFilePath)
        _lbl.text = result
    }
    else {
        _lbl.text = "Recording error"
    }
    _btn.setTitle("Start", for: .normal)
}

AudioRecognition_Swift-Bridging-Header.h文件中,添加#include "RunInference.h",以便前面的 Swift 代码RunInference_Wrapper().run_inference_wrapper(_recorderFilePath)起作用。

  1. run_inference_wrapper方法内的RunInference.mm中,从 Objective-C AudioRecognition应用中的ViewController.mm复制代码,如上一节的步骤 5-8 所述,该代码将保存的录制音频转换为格式 TensorFlow 模型接受模型,然后将其与采样率一起发送给模型以获取识别结果:
代码语言:javascript复制
@implementation RunInference_Wrapper
- (NSString *)run_inference_wrapper:(NSString*)recorderFilePath {
...
}

如果您确实想将尽可能多的代码移植到 Swift,则可以用 Swift 替换 C 中的音频文件转换代码。 还有一些非官方的开源项目提供了官方 TensorFlow C API 的 Swift 包装器。 但是为了简单起见和达到适当的平衡,我们将保持 TensorFlow 模型的推论,在本示例中,还将保持音频文件的读取和转换,以及在 C 和 Objective-C 中与控制 UI 和录音,并启动调用来进行音频处理和识别。

这就是构建使用语音命令识别模型的 Swift iOS 应用所需的全部内容。 现在,您可以在 iOS 模拟器或实际设备上运行它,并看到与 Objective-C 版本完全相同的结果。

总结

在本章中,我们首先快速概述了语音识别以及如何使用端到端深度学习方法构建现代 ASR 系统。 然后,我们介绍了如何训练 TensorFlow 模型以识别简单的语音命令,并介绍了如何在 Android 应用以及基于 Objective-C 和 Swift 的 iOS 应用中使用该模型的分步教程。 我们还讨论了如何通过找出丢失的 TensorFlow 操作或内核文件,添加它并重建 TensorFlow iOS 库来修复 iOS 中常见的模型加载错误。

ASR 用于将语音转换为文本。 在下一章中,我们将探讨另一个将文本作为输出的模型,并且文本中将包含完整的自然语言句子,而不是本章中的简单命令。 我们将介绍如何构建模型以将图像,我们的老朋友转换为文本,以及如何在移动应用中使用该模型。 观察和描述您在自然语言中看到的内容需要真正的人类智慧。 福尔摩斯是完成这项任务的最佳人选之一。 我们当然还不如福尔摩斯,但是让我们看看如何开始。

1 人点赞