MobileNet教程:用TensorFlow搭建在手机上运行的图像分类器
王瀚宸 编译自 Hackernoon 量子位 报道 | 公众号 QbitAI
在移动端本地运行神经网络成了明显的趋势,Google还为此推出了MobileNet框架。
MobileNet框架怎么用?Coastline Automation创始人Matt Harvey最近在Medium上发布了一份教程,教你用MobileNet来识别道路。
Coastline是一家用深度学习来监测行车情况、防止车祸的公司。以下是Matt Harvey的教程:
作为卷积神经网络中的新成员,MobileNet有着很多令人惊艳的表现,今天我们就用数据集训练一个试试。
MobileNet具有以下酷炫的特点:
1. 它们非常非常小
2. 它们非常非常快
3. 它们非常非常准
4. 它们很容易调试
这些特点是非常重要的。
目前,很多移动端上的深度学习任务都是在云端完成的。当你想要让手机识别一张图片,程序会先把这张图片通过网络发送到远程服务器上进行分类,随后再将结果发送回手机上。
随着手机计算能力的迅猛增加,加上SqueezeNet和MobileNet等架构让计算机视觉所需要的网络复杂度快速下降,深度学习计算很快就能完全在设备本地完成。
移动设备本地的深度学习,除了能在没有网络连接的情况下正常运行之外,另一个长处是节省时间,比如说一个车辆安全应用,对反应速度要求非常高,把图片传送到云端处理显然是不现实的。
因此,本文按照以下的顺序来介绍MobileNet:
1. MobileNet是什么?
2. 怎样搭建自己的数据集,在TensorFlow下训练MobileNet?
3. 怎样用TensorFlow训练一个在ImageNet上训练过的模型?
4. 跟Inception V3相比,MobileNet的表现怎么样?
5. 怎样使用再训练(retrained)的MobileNet来识别图片?
MobileNets是什么?
MobileNet是由Google的研究者们设计的一类卷积神经网络。它们在手机上运行,计算消耗小、运行速度快,因此很适合在移动端上做应用。
MobileNet和传统的CNN在结构上的差别主要是,传统CNN中在批规范化和ReLU(线性整流函数)前边,是一个3×3卷积层,而MobileNet将卷积过程分为一个3×3深度方向的卷积和一个1×1点对点的卷积。如果你想了解个中细节和缘由的话,我强烈建议你读一下他们的论文。
论文: MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications https://arxiv.org/pdf/1704.04861.pdf
那么MobileNet的短板是什么呢?准确性。跟我们熟悉的那些大型、消耗巨大资源的神经网络相比,MobileNet的准确性不如前者高。但是MobileNet的长处是能够在功耗和性能之间寻求良好的平衡点。
MobileNet拥有两个表观变量:width multiplier和resolution multiplier,我们可以通过调整这两个变量值来使得模型适应具体问题。Width multiplier让我们把网络变得稀疏,而resolution multiplier可以改变输入图片的分辨率,从而降低每层网络间的内部表达。
Google开源了MobileNet,并随之开放了16个ImageNet checkpoint,每一个对应一种不同的参数结构。这为我们训练自己的小又快的图像分类器提供了一个良好的开端。
搭建数据集,训练MobileNet
我们今天的挑战是搭建一个能够识别道路和非道路图片的分类器。这就像《硅谷》里面的“hot dog, not hot dog”应用,把热狗改成了道路。
为什么选择道路呢?因为在Coastline,我们正在基于计算机视觉开发用于汽车安全的移动应用。跟所有涉及视觉的app一样,用户隐私是非常需要考虑的一点。所以当用户打开我们的app时,系统会首先检查它看到的是否是道路。如果不是的话,那么它就会关闭摄像头。我们希望这个过程能够在尽可能快速、只占用少量资源的情况下完成。
为了解决这个问题,我们需要先为它创建数据集。我们的目标是收集10,000张图片,道路和非道路的图片大概各占50%。
我们从以下几个来源得到相应的图片:
- 从我们自己的数据集里,随机选取4,000张显然是道路的图片;
- 从ImageNet里随机选取2000张显然不是道路的图片;
- 从网上选取3000张不那么明显的非道路图片,以防分类器学会的是区分“天空、非天空”;
- 从网上选取1000张不那么明显的道路图片,以防分类器把挡风玻璃上的倒影等特征错认为道路特征。
我们将会把图片放进“道路”或“非道路”文件夹,这就是我们在重新训练网络之前所需要的图片上的准备工作。
此外,从网上搜集的图片可以有效地增加你的数据集的多样性,但这样做也有一个缺点,网站上图片的标签往往有些混乱。比如说,通过搜索“道路风景(road landscape)”所得到的图片可能是在美美的自然风光背景下郑重有条通向前方的道路:
或者是群山中有一条隐约可见的小路:
为解决这个问题,我们可以挨个浏览每张图片然后手动进行标注,但如果是这样那我们还要Deep learning干嘛呢?
实际上,我们将在所有数据的基础上重新训练一个大型的网络(比如Inception V3),注意要通过early stopping和heavy data augmentation等方法来防止过拟合。
然后我们让网络对数据集中所有的图片(包括之前用来训练的图片)进行分类,然后记录那些分错类或不确定的图片。随后我们逐个排查这些图片,把它们移动到正确的分类下。
这与之前手动分类图片相比,需要进行的调整的图片数量大大减少。重复这个多次步骤,能够让我们的准确率比Inception提高7个百分点。
随后我们将会使用TensorFlow以及迁移学习来在我们这个特定的数据集上对MobileNet进行调整。
使用ImageNet预训练过的模型
TensorFlow拥有一些很好的工具,你可以使用它们在不码任何代码的情况下就能够重新训练MobileNet。
TensorFlow下再训练MobileNet详情: https://github.com/tensorflow/tensorflow/commit/055500bbcea60513c0160d213a10a7055f079312
你可以用TensorFlow范例文件夹里的脚本文件,来在你自己的数据上重新训练MobileNet。
等等!你应该使用哪一版的MobileNet呢?这是个好问题。让我们先简单训练一下比较各个版本的表现。为了开始训练,我们将会运行以下一段来自TensorFlow repository根目录下的脚本:
python tensorflow/examples/image_retraining/retrain.py
--image_dir ~/ml/blogs/road-not-road/data/
--learning_rate=0.0001
--testing_percentage=20
--validation_percentage=20
--train_batch_size=32
--validation_batch_size=-1
--flip_left_right True
--random_scale=30
--random_brightness=30
--eval_step_interval=100
--how_many_training_steps=600
--architecture mobilenet_1.0_224
代码中“architecture”的标签对应着将要重新训练的MobileNet的版本。这里“1.0”对应着width multiplier,取值范围有1.0,0.75,0.50和0.25。“224”则对应着image resolution/resolution multiplier,这里可以选择的值有224,192,160和128。具体来说,为了训练最小型的MobileNet,你应该用“—architecture mobilenet_0.25_128”。
代码中所涉及的其他重要的参数有:
learning_rate:这是你需要进行调整的参数,我发现设置为0.0001有较好的训练效果。
testing and validation percentage:这个脚本将会把你的数据分为 训练集/验证集/测试集 三类。网络将会使用训练集进行训练,在每一个“eval_step_interval”之后使用验证集对网络表现的评估进行更新,最后在“how_many_training_steps”之后使用测试集进行测试,给出最终的分数。
validation_batch_size:把这项设为-1意味着让脚本使用你所有的数据来进行验证。当你没有很多数据时(比如说这里我们只有10000张图片),把这项设为-1可以有效地较少评估步骤中的差异。
用这种方法训练几个版本的MobileNet之后,让我们看看他们之间的比较吧。
MobileNet表现如何?
为了得到比较的基准,我们花18分钟对Inception进行了600步训练,最终达到了95.9%的正确率,模型大小有84MB。
在测试对1,000张图片进行快速识别时发现,Inception可以在NVIDIA GeForce 960m GPU的架构上以19fps(frame per second,每秒钟识别的图片数)的速度对图片进行识别。
当然,这个结果还有很多改进空间,但我们不打算在这上面继续花时间了。
训练Inception V3,只需要将脚本中的-architecture-标签那里改为inception_v3即可。
旁注:为什么准确率只有95.9%?这看起来应该是个很好解决的问题。确实,除了我们可以对训练参数进行充分的调试之外(我们实际上在另一次采用不同结构的训练中取得了98.9%的准确率),其实这两个类别之间的界定也有一些模糊。比如说:
有一张图片的内容是是树林中一条不清晰的小径。这到底是一条铁路还是马路?我自己都不清楚。
有一张图片是,在风景名胜中的远处有条路。这到底是算作是一个风景还是一条道路?或者是算作是一张图片中的道路?什么情况下我们认为这个分类会改变?
有一些偏艺术风的图片,是否应该算作是道路呢?
那么,MobileNet的表现如何呢?不出意外,没那么好。但是,牺牲一些准确率却在其他方面有了很大的提升。
使用最大的MobileNet(1.0, 224),我们能够在4分钟的训练下达到95.5%的正确率。最终产生的模型大小也仅有17Mb,在同样的GPU下能够以135fps的速度运行。
与Inception相比,MobileNet训练出的模型速度是前者的7倍,而尺寸只有前者的四分之一,准确率上只损失了0.4%。
那么最小尺寸的MobileNet(0.24, 128)表现怎么样呢?准确率下降了更多,只有89.2%,但是运行速度有450 fps,模型的大小仅有930kb,还不到1Mb!
用再训练的MobileNet来分类
现在你拥有了在你特定数据集上的重新训练后的MobileNet,是时候来试试了。不出意外的是,TensorFlow上也有相应的脚本文件来完成这项功能。
python3 tensorflow/examples/label_image/label_image.py
--graph=/tmp/mobilenet_0.50_192.pb
--labels=/tmp/output_labels.txt
--image=/home/harvitronix/ml/blogs/road-not-road/test-image.jpg
--input_layer=input
--output_layer=final_result
--input_mean=128
--input_std=128
--input_width=192
--input_height=192
△ 我们的算法把这张图片识别为道路,虽然信心值只有0.686811,但也很不错了
旁注:值得说明的是,在我们这个相当简单的两分类问题中,准确度(与模型大小、运行速度之间)的权衡没有这么显著。对于像拥有1001个分类的ImageNet来说,这个权衡就显得非常重要了。
见Google公布的表格: https://research.googleblog.com/2017/06/mobilenets-open-source-models-for.html
下一步
MobileNet的设计初衷是为了在移动端上运行神经网络。
接下来,我们还会创造一些新的训练数据,再进行精细的调试,然后将我们的再训练的MobileNet应用到一个Android app当中去。我们将会看到在现实中它可以达到怎样的运行速度和准确率。
- Java开发Spring笔记第二天
- PHP调用Go服务的正确方式 - Unix Domain Sockets
- 一条看似平常的报警邮件所做的分析(r8笔记第9天)
- 55. 上传文件(Web版) | 厚土Go学习笔记
- R语言与机器学习学习笔记(分类算法
- 54. 心跳的实现 | 厚土Go学习笔记
- 通过错误的sql来测试推理sql的解析过程(二) (r8笔记第7天)
- 53. Socket服务三次握手的示例 | 厚土Go学习笔记
- R分词继续,"不|知道|你在|说|什么"分词添加新词
- Java开发Spring第一天
- 关于R安装中文分词包安装不上的问题install.packages("tm")
- dataguard备库的数据文件的迁移实战(r8笔记第24天)
- Hive的left join、left outer join和left semi join三者的区别
- 52. Socket Server 自定义协议的简单实现 | 厚土Go学习笔记
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Python爬虫之scrapy构造并发送请求
- Python爬虫之scrapy模拟登陆
- Python爬虫之scrapy中间件的使用
- Python爬虫之scrapy_redis原理分析并实现断点续爬以及分布式爬虫
- Python爬虫之scrapy_splash组件的使用
- Python爬虫之scrapy的日志信息与配置
- Python爬虫之scrapyd部署scrapy项目
- 最近发现一个很有趣的随机小姐姐视频源码 分享给大家
- Codeforces Round #633 (Div. 2)C Powered Addition (贪心,二进制)
- Spring 整合 JUnit
- Java Stax解析XML示例
- Codeforces Round #633 (Div. 2) B Sorted Adjacent Differences(直观感知+排序插放)
- Spring 声明式事务
- Leetcode 1320 二指输入的的最小距离(多情况讨论,DP)
- Spring 基本注解