一、环境
1、首先你得安装好Xcode 8,确定开发者目录指向你安装Xcode的位置并且已经被激活。(如果你在安装Xcode之前已经安装了Homebrew,这可能会指向错误的地址,导致TensorFlow安装失败):
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
2、安装Homebrew:https://brew.sh/index_zh-cn.html
3、Homebrew安装其他软件
brew install python3
brew cask install java
brew install bazel
brew install automake
brew install libtool
brew install wget
4、命令:brew list 查看已安装的软件
5、成功安装了python3,那pip3也随着被安装了,使用python3 的包管理器pip3来安装所需要的包。
pip3 install numpy
pip3 install scipy
pip3 install scikit-learn
pip3 install pandas
pip3 install tensorflow
6、命令:pip3 list 查看已安装的包
这些包会安装在/usr/local/lib/python3.6/site-packages目录下。
7、下载TensorFlow源码,需要使用源码文件编译模型,并且使用官方iOS的Demo。
克隆TensorFlow GitHub仓库。注意,一定要保存在没有空格的路径下,否则bazel会拒绝构建。我是克隆到我的主目录下:
cd /Users/javalong/Downloads/Tensorflow
git clone https://github.com/tensorflow/tensorflow
一旦GitHub仓库克隆完毕,你就需要运行配置脚本(configure script):
cd /Users/javalong/Downloads/Tensorflow/tensorflow-master
./configure
这里有些地方可能需要你自行配置,比如:
Please specify the location of python. [Default is /usr/bin/python]:
我写的是/usr/local/bin/python3,因为我使用的是Python 3.6。如果你选择默认选项,就会使用Python 2.7来创建TensorFlow。
Please specify optimization flags to use during compilation [Default is
-march=native]:
这里只需要按Enter键。后面两个问题,只需要选择n(表示 no)。当询问使用哪个Python库时,按Enter键选择默认选项(应该是Python 3.6 库)。剩下的问题都选择n。随后,这个脚本将会下载大量的依赖项并准备构建TensorFlow所需的一切。
8、构建静态库
有两种方法构建TensorFlow:1.在Mac上使用bazel工具;2.在IOS上,使用Makefile。我们是在IOS上构建,所以选择第2种方式。不过因为会用到一些工具,也会用到第一种方式。
在TensorFlow的目录中执行以下脚本:
tensorflow/contrib/makefile/build_all_ios.sh
这个脚本首先会下载一些依赖项,然后开始构建。一切顺利的话,它会创建三个链入你的app的静态库:libtensorflow-core.a, libprotobuf.a, libprotobuf-lite.a。
还有另外两个工具需要构建,在终端运行如下两行命令:
bazel build tensorflow/python/tools:freeze_graph
bazel build tensorflow/python/tools:optimize_for_inference
Note: 这个过程至少需要20分钟,因为它会从头开始构建TensorFlow(本次使用的是bazel)。如果遇到问题,请参考官方指导。
9、iOS工程配置引用参考:
9.1、Other Linker Flags :
-force_load $(SRCROOT)/../../makefile/gen/lib/libtensorflow-core.a
如图:
9.2、Header Search Paths :
$(SRCROOT)/../../makefile/gen/proto $(SRCROOT)/../../makefile/downloads/eigen $(SRCROOT)/../../makefile/downloads $(SRCROOT)/../../makefile/downloads/protobuf/src/ $(SRCROOT)/../../../..
9.3、Libaray Search Paths :
$(SRCROOT)/../../makefile/gen/lib $(SRCROOT)/../../makefile/gen/protobuf_ios/lib
如图:
9.4、Demo工程与makefile目录相对关系如图:
10、库的引用如图:
二、模型训练
1.下载数据
cd /Users/javalong/Desktop
mkdir Test
cd Test
curl -O http://download.tensorflow.org/example_images/flower_photos.tgz
tar xzf flower_photos.tgz
进入flower_photos,可以看到5个文件夹,daisy dandelion roses sunflowers tulips
2. 利用预训练模型训练数据
模型是谷歌的Inceptionv3(http://arxiv.org/abs/1512.00567)。在2012年的imageNet上进行训练,并在2012ImageNet上取得了3.4%的top-5准确率(人类的只有5%)。
这么一个复杂的网络若是直接自己训练,起码需要几天甚至十几天的时间。所以这里我采用复用深度学习的方法。即模型前面的层的参数都不变,而只训练最后一层的方法。最后一层是一个softmax分类器,这个分类器在原来的网络上是1000个输出节点(ImageNet有1000个类),所以需要删除网络的最后的一层,变为所需要的输出节点数量,然后再进行训练。
tensorflow/examples/image_retraining/retrain.py 中采用的方法是这样的:将自己的训练集中的每张图像输入网络,最后在瓶颈层(bottleneck),就是倒数第二层,会生成一个2048维度的特征向量,将这个特征保存在一个txt文件中,再用这个特征来训练softmax分类器。
进入tensorflow源代码目录
2.1、
cd /Users/javalong/Downloads/Tensorflow/tensorflow-master
2.2、
python3 tensorflow/examples/image_retraining/retrain.py \
--bottleneck_dir=/Users/javalong/Desktop/Test/ \
--how_many_training_steps 100000 \
--model_dir=/Users/javalong/Desktop/Test/inception2 \
--output_graph=/Users/javalong/Desktop/Test/retrained_graph2.pb \
--output_labels=/Users/javalong/Desktop/Test/retrained_labels.txt \
--image_dir=/Users/javalong/Desktop/Test/flower_photos
脚本会下载Inceptionv3模型,80多兆,可以提前下载好放到Test的inception2目录下
模型路径:http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz
训练完成后,在Test目录下看到模型文件retrained_graph.pb和标签retrained_labels.txt。
3. 模型验证
新建文件label_image.py,注意格式,代码缩进使用tab
#-*- coding:utf-8 -*-
import os
import numpy as np
import tensorflow as tf
from sklearn import metrics
# 随便一张玫瑰花图片
image_path = "test1.jpg"
# read in the image_data
image_data = tf.gfile.FastGFile(image_path, "rb").read()
# Loads label file, strips off carriage return
label_lines = [line.strip() for line in tf.gfile.GFile("retrained_labels.txt")]
# Unpersists graph from file
with tf.gfile.FastGFile("retrained_graph2.pb", "rb") as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
_ = tf.import_graph_def(graph_def, name="")
with tf.Session() as sess:
# Feed the image_data as input to the graph and get first prediction
softmax_tensor = sess.graph.get_tensor_by_name("final_result:0")
preditions = sess.run(softmax_tensor, {"DecodeJpeg/contents:0":image_data})
# sort to show labels of first prediction in order of confidence
top_k = preditions[0].argsort()[-len(preditions[0]):][::-1]
for node_id in top_k:
human_string = label_lines[node_id]
score = preditions[0][node_id]
print('%s (score = %.5f' %(human_string, score))
执行文件
python label_image.py
得到类似下图结果
三 、编译模型
将模型文件转化成ios可以上可以运行的文件
1、首先编译tensorflow源码示例中的label_image来测试retrained_graph2.pb模型
1.1、
cd /Users/javalong/Downloads/Tensorflow/tensorflow-master
1.2、
bazel build tensorflow/examples/label_image:label_image
1.3、
bazel-bin/tensorflow/examples/label_image/label_image \
--output_layer=final_result \
--labels=/Users/javalong/Desktop/Test/retrained_labels.txt \
--image=/Users/javalong/Desktop/Test/flower_photos/daisy/5547758_eea9edfd54_n.jpg \
--graph=/Users/javalong/Desktop/Test/retrained_graph2.pb
如图所示:
2、优化模型并去掉ios不支持的算子
翻译文章中说明如下:
“由于移动设备内存有限,并且app需要下载,因此TensorFlow的iOS版本只支持那些推理中常见的算子(ops,提供英文原文以防翻译错误),没有大量支持扩展的依赖项。你可以在tensorflow/contrib/makefile/tf_op_files.txt这个文件中找到可以使用的算子列表。”
文章中提到,DecodeJpeg是不能使用的算子之一,因为其实现方法依赖于libjpeg,而它在iOS上运行非常麻烦(painful to support),并且增加binary footprint(不懂。。。)。尽管可以用iOS原生的图像库实现,大部分应用其实直接图像buffer而不需要对jpeg进行编码。
问题麻烦的地方在于,我们使用的Inception模型包括了DecodeJpeg算子。我们通过直接将图片数据传给Mul结点绕过DecodeJpeg操作。尽管如此,图被加载的时候,如果平台不支持,即使算子没有被调用,还是会报错,因此我们利用optimize_for_inference去掉所有没有被输入和输出结点用到的结点。这个脚本同时会完成一些优化来加速,例如将batch normalization转换成卷积权重(the convolutional weights)来减少计算次数。
2.1、
bazel build tensorflow/python/tools:optimize_for_inference
2.2、
bazel-bin/tensorflow/python/tools/optimize_for_inference \
--input=/Users/javalong/Desktop/Test/retrained_graph2.pb \
--output=/Users/javalong/Desktop/Test/optimized_graph.pb \
--input_names=Mul \
--output_names=final_result
如图所示:
同样可以通过label_image验证optimized_graph.pb模型的有效性。
3、将模型的权重变成256之内的常数
优化前模型87.5M,优化后的模型还是有87.2M,可以损失一定的精确度,将模型的权重从浮点型转化成整数。
3.1、
bazel build tensorflow/tools/quantization:quantize_graph
3.2、
bazel-bin/tensorflow/tools/quantization/quantize_graph --input=/Users/javalong/Desktop/Test/optimized_graph.pb \
--output=/Users/javalong/Desktop/Test/rounded_graph.pb \
--output_node_names=final_result \
--mode=weights_rounded
如图所示:
4、验证rounded_graph.pb模型
4.1、
bazel-bin/tensorflow/examples/label_image/label_image \
--output_layer=final_result \
--labels=/Users/javalong/Desktop/Test/retrained_labels.txt \
--image=/Users/javalong/Desktop/Test/flower_photos/daisy/5547758_eea9edfd54_n.jpg \
--graph=/Users/javalong/Desktop/Test/rounded_graph.pb
如图所示:
5、内存映射(memory mapping)
由于app将87M的模型权值加载到内存中,会对RAM带来压力导致稳定性问题,因为OS可能会杀死占用内存太多的应用。幸运的是,这些缓冲区的内容是只读的,能以os在内存遇到压力的时候很容易释放的方式将模型映射到内存中。为此,我们将模型转换成可以从GraphDef分别加载到形式。
5.1、
bazel build tensorflow/contrib/util:convert_graphdef_memmapped_format
5.2、
bazel-bin/tensorflow/contrib/util/convert_graphdef_memmapped_format \
--in_graph=/Users/javalong/Desktop/Test/rounded_graph.pb \
--out_graph=/Users/javalong/Desktop/Test/mmapped_graph.pb
需要注意的是,此时模型文件已经不是一般的GraphDef protobuf,所以如果还按照以前的方法加载会遇到错误。
5.3、
bazel-bin/tensorflow/examples/label_image/label_image \
--output_layer=final_result \
--labels=/Users/javalong/Desktop/Test/retrained_labels.txt \
--image=/Users/javalong/Desktop/Test/flower_photos/daisy/5547758_eea9edfd54_n.jpg \
--graph=/Users/javalong/Desktop/Test/mmapped_graph.pb
如图所示:
四、使用iOS Demo工程
1、demo是在tensorflow官方提供的ios示例代码camera的基础上进行的修改,工程路径:tensorflow/contrib/ios_examples/camera。
2、将rounded_graph.pb和retrained_labels.txt导入到data目录下。
3、修改CameraExampleViewController.mm中要加载到文件,图片等尺寸,结点的名字和如何缩放像素值。
4、最后程序运行的结果如下: