TensorFlow在iOS和Mac上的使用
一、環境
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/matthijs
git clone https://github.com/tensorflow/tensorflow -b r1.0
-b r1.0表明克隆的是r1.0分支。當然你也可以隨時獲取最新的分支或者主分支。
Note:在MacOS Sierra 上,運行下麵的配置腳本報錯了,我隻能克隆主分支來代替。在OS X EI Caption 上使用r1.0分支就不會有任何問題。
一旦GitHub倉庫克隆完畢,你就需要運行配置腳本(configure script):
cd tensorflow
./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 https://download.tensorflow.org/example_images/flower_photos.tgz
tar xzf flower_photos.tgz
進入flower_photos,可以看到5個文件夾,daisy dandelion roses sunflowers tulips
2. 利用預訓練模型訓練數據
模型是穀歌的Inceptionv3(https://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 tensorflow
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目錄下
模型路徑:https://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 tensorflow
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.ph和retrained_labels.txt導入到data目錄下。
3、修改CameraExampleViewController.mm中要加載到文件,圖片等尺寸,結點的名字和如何縮放像素值。
4、最後程序運行的結果如下:
最後更新:2017-07-13 13:32:30