在《詩人也能用TensorFlow》那篇博文中,我介紹了如何使用你自己的圖片來訓(xùn)練一個(gè)可識(shí)別圖片對(duì)象的神經(jīng)網(wǎng)絡(luò)模型。接下來就是將這個(gè)模型運(yùn)用到你的移動(dòng)設(shè)備中。在這篇文章中,我會(huì)介紹如何在你的iOS應(yīng)用程序中運(yùn)行這個(gè)模型。
你可以在本文中找到書面指南,也可以通過下面的視頻及注解了解詳細(xì)的操作步驟。
我假定你已經(jīng)完成了《詩人也能用TensorFlow》的步驟。 所以你應(yīng)該已經(jīng)安裝了Docker, 并在home路徑下創(chuàng)建了一個(gè)tf_files的文件夾。這個(gè)文件夾里有一個(gè)包含你的模型的retrained_graph.pd文件。 如果你還沒有完成以上步驟,你需要按照《詩人也能用TensorFlow》教程示例來完成你自己的模型訓(xùn)練。
第一步,打開Docker QuickStart Terminal并利用最新的Docker鏡像啟動(dòng)一個(gè)新的Docker容器。 本教程依賴了一些TensorFlow的新特性,所以用于《詩人也能用TensorFlow》的v0.8版鏡像不能用了。
docker run -it -p 8888:8888 -v $HOME/tf_files:/tf_files tensorflow/tensorflow:nightly-devel
你應(yīng)該可以看到自己在一個(gè)新的shell窗口中,提示符以“root@”開頭,以“#”結(jié)尾 ,這表示你已經(jīng)運(yùn)行在Docker鏡像中了。為了確保設(shè)置正確,請(qǐng)運(yùn)行l(wèi)s -lah /tf_files/,并確認(rèn)已生成了retrained_graph.pb文件。
接下來,首先我們要確保此模型能夠產(chǎn)生合理的結(jié)果。在這里我使用默認(rèn)的花朵圖像來做測試。但如果你已經(jīng)訓(xùn)練了自定義類別,可以用你自己的圖像文件來代替。整個(gè)編譯過程可能需要幾分鐘。如果運(yùn)行速度太慢,請(qǐng)確認(rèn)你已經(jīng)更新了VirtualBox的配置來充分利用計(jì)算機(jī)內(nèi)存和處理器能力。
cd /tensorflow/
bazel build tensorflow/examples/label_image:label_image
bazel-bin/tensorflow/examples/label_image/label_image \
–output_layer=final_result \
–labels=/tf_files/retrained_labels.txt \
–image=/tf_files/flower_photos/daisy/5547758_eea9edfd54_n.jpg \
–graph=/tf_files/retrained_graph.pb
對(duì)于花朵頂部是雛菊的圖片,這個(gè)模型應(yīng)該可以給出一個(gè)合理的第一選擇標(biāo)簽。在我們對(duì)模型文件進(jìn)行進(jìn)一步處理以便其能在移動(dòng)應(yīng)用程序中使用后,我們還會(huì)使用此命令來確保它仍然能得到合理的結(jié)果。
移動(dòng)設(shè)備的內(nèi)存容量有限,而且應(yīng)用程序需要下載到設(shè)備本地運(yùn)行。因此在默認(rèn)情況下,TensorFlow的iOS版本僅包含支持接口中常見的運(yùn)算的代碼,并不包含很多的外部依賴性包。你可以在tensorflow/contrib/makefile/tf_op_files.txt文件中查看此版本所支持的運(yùn)算列表。其中一個(gè)不支持的運(yùn)算是DecodeJpeg,因?yàn)閷?duì)它的實(shí)現(xiàn)依賴于libjpeg(這很難在iOS中支持),并且會(huì)增加編譯后代碼的大小。
雖然我們可以編寫一個(gè)使用iOS原生的圖像庫的新的實(shí)現(xiàn)方法,但是對(duì)于大多數(shù)移動(dòng)應(yīng)用程序,我們不需要解碼JPEG圖像。因?yàn)槲覀兛梢灾苯邮褂孟鄼C(jī)的圖像緩沖區(qū)。
不幸的是,我們重新訓(xùn)練所基于的Inception模型包括一個(gè)DecodeJpeg運(yùn)算。我們一般通過在解碼之后直接反饋Mul節(jié)點(diǎn)來繞過這一運(yùn)算。但是在不支持該運(yùn)算的平臺(tái)上,即使從未調(diào)用它,也會(huì)在加載圖像時(shí)發(fā)生錯(cuò)誤。為了避免這種情況的出現(xiàn),optimize_for_inference腳本里刪除了輸入和輸出節(jié)點(diǎn)集合中不需要的所有節(jié)點(diǎn)。
該腳本還做了一些其他優(yōu)化以提高運(yùn)行速度。例如它把顯式批處理標(biāo)準(zhǔn)化運(yùn)算跟卷積權(quán)重進(jìn)行了合并,從而降低了計(jì)算量。運(yùn)行方式如下:
bazel build tensorflow/python/tools:optimize_for_inference
bazel-bin/tensorflow/python/tools/optimize_for_inference \
–input=/tf_files/retrained_graph.pb \
–output=/tf_files/optimized_graph.pb \
–input_names=Mul \
–output_names=final_result
這將在/tf_files/optimized_graph.pb中創(chuàng)建一個(gè)新文件。想確認(rèn)它沒有更改網(wǎng)絡(luò)輸出,可以在更新的模型上再次運(yùn)行l(wèi)abel_image命令對(duì)樣例圖片進(jìn)行識(shí)別。
bazel-bin/tensorflow/examples/label_image/label_image \
–output_layer=final_result \
–labels=/tf_files/retrained_labels.txt \
–image=/tf_files/flower_photos/daisy/5547758_eea9edfd54_n.jpg \
–graph=/tf_files/optimized_graph.pb
你應(yīng)該可以看到跟第一次對(duì)圖片進(jìn)行識(shí)別非常類似的結(jié)果,因?yàn)椴还軐?duì)模型的處理流程作出什么更改,其底層的數(shù)學(xué)運(yùn)算結(jié)果是應(yīng)該一致的。
重新訓(xùn)練后的模型仍然是87MB。這導(dǎo)致了任何包含它的應(yīng)用程序的下載包都會(huì)很大。有很多方法可以減少下載包的大小,但有一個(gè)方法非常簡單有用,并且不會(huì)增加太多的復(fù)雜性。Apple使用.ipa包發(fā)布應(yīng)用程序,其中所有的內(nèi)容都使用zip壓縮。
通常因?yàn)闄?quán)重都是略微不同的浮點(diǎn)值,所以模型不能被很好地壓縮。但是你可以通過將特定常數(shù)范圍內(nèi)的所有權(quán)重湊整到256個(gè)級(jí)別,同時(shí)仍然保持浮點(diǎn)格式,從而實(shí)現(xiàn)更好的壓縮比。
應(yīng)用以下這些改進(jìn):給予壓縮算法更多可利用的重復(fù)性;不需要任何的新算子;并且僅略微降低精度(通常降低小于1%的)
以下是如何調(diào)用quantize_graph腳本以應(yīng)用這些改進(jìn)的方法:
bazel build tensorflow/contrib/quantization/tools:quantize_graph
bazel-bin/tensorflow/contrib/quantization/tools/quantize_graph \
–input=/tf_files/optimized_graph.pb \
–output=/tf_files/rounded_graph.pb \
–output_node_names=final_result \
–mode=weights_rounded
你會(huì)發(fā)現(xiàn)rounded_graph.pb文件的原始大小仍然是87MB。但是如果你在Finder(資源管理器)中右鍵單擊它,并選擇“壓縮”,應(yīng)該可以看到生成了一個(gè)大小約24MB的壓縮文件。 這個(gè)差異就是iOS壓縮的.ipa文件或者Android壓縮的.apk文件和原始文件(未壓縮過)大小的差異。
為了驗(yàn)證模型仍然正常工作,請(qǐng)?jiān)俅芜\(yùn)行l(wèi)abel_image命令:
bazel-bin/tensorflow/examples/label_image/label_image \
–output_layer=final_result \
–labels=/tf_files/retrained_labels.txt \
–image=/tf_files/flower_photos/daisy/5547758_eea9edfd54_n.jpg \
–graph=/tf_files/rounded_graph.pb
這一次,評(píng)分的結(jié)果有稍微明顯的變化(由于量化的影響),但是標(biāo)簽的總體個(gè)數(shù)和順序應(yīng)該與之前兩次運(yùn)行的結(jié)果是相同的。
我們需要運(yùn)行的最后一個(gè)處理步驟是內(nèi)存映射。因?yàn)楸4婺P蜋?quán)重值的緩沖區(qū)大小為87MB,所以在需要將這些權(quán)重內(nèi)容載入應(yīng)用程序使用的內(nèi)存時(shí),甚至在運(yùn)行模型之前,就會(huì)對(duì)iOS的內(nèi)存空間造成很大的壓力。這會(huì)導(dǎo)致穩(wěn)定性出問題,因?yàn)椴僮飨到y(tǒng)可能會(huì)無預(yù)兆地殺掉使用太多內(nèi)存的應(yīng)用程序。幸運(yùn)的是這些緩沖區(qū)是只讀的,因此可以將這些內(nèi)容映射到內(nèi)存中,以便操作系統(tǒng)可以在有內(nèi)存壓力時(shí)輕松地丟棄它們,從而避免崩潰的可能。
為了支持這一點(diǎn),我們需要重新整理模型,使權(quán)重值能夠被保存在可以輕松地跟主GraphDef分開加載的部分,即使它們?nèi)匀辉谕粋€(gè)文件中。以下是操作命令:
bazel build tensorflow/contrib/util:convert_graphdef_memmapped_format
bazel-bin/tensorflow/contrib/util/convert_graphdef_memmapped_format \
–in_graph=/tf_files/rounded_graph.pb \
–out_graph=/tf_files/mmapped_graph.pb
要注意的一點(diǎn)是,磁盤上的文件不再是一個(gè)普通的GraphDef protobuf。所以如果你嘗試加載到一個(gè)像label_image這樣的程序中,將會(huì)發(fā)生錯(cuò)誤。你需要稍微改變一下加載模型文件的方法,下面我將會(huì)介紹在iOS上的一個(gè)應(yīng)用例子。
到目前為止,我們已經(jīng)在Docker上運(yùn)行了所有的腳本。出于演示的目的,在Docker中運(yùn)行腳本要容易很多,因?yàn)樵赨buntu上安裝Python依賴包比在OS X上更加簡單。
現(xiàn)在,我們將切換到本機(jī)終端,以便我們可以編譯一個(gè)使用你訓(xùn)練的模型的iOS應(yīng)用程序。
你需要安裝Xcode7.3或者更高版本的命令行工具來開發(fā)編譯應(yīng)用程序。你可以從Apple官網(wǎng)上下載Xcode。完成安裝后,打開一個(gè)新的終端窗口,下載TensorFlow源碼(使用git clone https://github.com/tensorflow/tensorflow命令)到你本機(jī)的一個(gè)文件夾中。請(qǐng)把下面命令里的“?/ projects / tensorflow”替換為該文件夾路徑,然后運(yùn)行以下命令編譯Tensorflow框架并把模型文件復(fù)制過來:
cd ~/projects/tensorflow
tensorflow/contrib/makefile/build_all_ios.sh
cp ~/tf_files/mmapped_graph.pb tensorflow/contrib/ios_examples/camera/data/
cp ~/tf_files/retrained_labels.txt tensorflow/contrib/ios_examples/camera/data/
open tensorflow/contrib/ios_examples/camera/camera_example.xcodeproj
檢查終端輸出的結(jié)果以確保編譯成功。這時(shí)你應(yīng)該可以找到可以在Xcode中打開的相機(jī)示例項(xiàng)目。這個(gè)應(yīng)用程序顯示了你的相機(jī)的實(shí)時(shí)拍攝的內(nèi)容,以及它可以識(shí)別的內(nèi)容里的對(duì)象的標(biāo)簽,所以它是一個(gè)很好的用來測試新模型的演示項(xiàng)目。
上面的命令應(yīng)該已經(jīng)將你需要的模型文件復(fù)制到應(yīng)用程序的數(shù)據(jù)文件夾中了,但是你仍然需要讓Xcode知道應(yīng)該將這些文件包含在應(yīng)用程序中。要?jiǎng)h除相機(jī)項(xiàng)目的默認(rèn)模型文件,請(qǐng)?jiān)赬code的左側(cè)項(xiàng)目導(dǎo)航器中,在數(shù)據(jù)文件夾中選中imagenet_comp_graph_label_strings.txt和tensorflow_inception_graph.pb文件。刪除它們,并在刪除提示中選擇“移至垃圾箱”。
接下來,打開一個(gè)包含新模型文件的Finder窗口,例如在命令行終端中做如下操作:
open tensorflow/contrib/ios_examples/camera/data
將mmapped_graph.pb 和retrained_labels.txt 從該Finder窗口拖動(dòng)到項(xiàng)目導(dǎo)航器中的數(shù)據(jù)文件夾中。確保在對(duì)話框的復(fù)選框中為CameraExample啟用了“添加到目標(biāo)”。這可以在你編譯應(yīng)用程序時(shí),讓Xcode知道它應(yīng)該包括這些文件。所以如果在后面步驟中你看到了文件丟失的錯(cuò)誤信息,請(qǐng)仔細(xì)檢查這一步驟。

圖1 添加文件對(duì)話框,顯示要選擇的選項(xiàng)。圖片由Pete_Warden友情提供
我們已經(jīng)有了應(yīng)用程序中的文件。現(xiàn)在需要更新一些其他信息。我們需要更新加載文件的名稱,還有一些元數(shù)據(jù),包括輸入圖像的大小、節(jié)點(diǎn)名稱以及如何在加載圖像之前對(duì)像素值進(jìn)行數(shù)值壓縮。要進(jìn)行這些更改,在Xcode里打開CameraExampleViewController.mm,查找靠近文件頂部的模型設(shè)置部分,并用以下內(nèi)容替換它們:
C++
// If you have your own model, modify this to the file name, and make sure
// you’ve added the file to your app resources too.
static NSString* model_file_name = @”mmapped_graph”;
static NSString* model_file_type = @”pb”;
// This controls whether we’ll be loading a plain GraphDef proto, or a
// file created by the convert_graphdef_memmapped_format utility that wraps a
// GraphDef and parameter file that can be mapped into memory from file to
// reduce overall memory usage.
const bool model_uses_memory_mapping = true;
// If you have your own model, point this to the labels file.
static NSString* labels_file_name = @”retrained_labels”;
static NSString* labels_file_type = @”txt”;
// These dimensions need to match those the model was trained with.
const int wanted_input_width = 299;
const int wanted_input_height = 299;
const int wanted_input_channels = 3;
const float input_mean = 128.0f;
const float input_std = 128.0f;
const std::string input_layer_name = “Mul”;
const std::string output_layer_name = “final_result”;
最后,選擇并連接并你的iOS設(shè)備(這個(gè)不能在模擬器上運(yùn)行,因?yàn)樗枰粋€(gè)攝像頭)到本機(jī),并且按Command+R鍵來編譯和運(yùn)行修改后的示例。如果一切正常,你應(yīng)該會(huì)看到應(yīng)用程序啟動(dòng),實(shí)時(shí)顯示攝像頭拍攝的內(nèi)容,并開始顯示你的訓(xùn)練類別的標(biāo)簽。
你可以找一個(gè)想識(shí)別的對(duì)象類型的東西用來測試。把相機(jī)對(duì)準(zhǔn)它,看看程序是否能夠給出正確的標(biāo)簽。如果你沒有任何實(shí)物對(duì)象,可以嘗試在網(wǎng)絡(luò)上搜索圖像,并將相機(jī)對(duì)準(zhǔn)你的計(jì)算機(jī)顯示器。
恭喜你已經(jīng)能夠訓(xùn)練自己的模型,并在手機(jī)上運(yùn)行它!

圖2 郁金香的圖片搜索結(jié)果與iPhone應(yīng)用程序的屏幕。 圖片由Pete Warden友情提供
上面步驟里的許多內(nèi)容都可以被用于安卓系統(tǒng)或樹莓派上,也包括TensorFlow提供的其他模型。它們可以被用于從自然語言處理到語音合成的多個(gè)方面。我很高興看到在多種設(shè)備上出現(xiàn)了使用深不可測的深度學(xué)習(xí)的新應(yīng)用程序,所以我迫不及待地想知道你搞出了什么新東西!
相關(guān)資料:
Pete Warden
Pete Warden是TensorFlow移動(dòng)團(tuán)隊(duì)的技術(shù)主管。在此之前,他是Jetpac的CTO。Jetpac于2014年被谷歌收購。它的深度學(xué)習(xí)技術(shù)經(jīng)過優(yōu)化,可在移動(dòng)和嵌入式設(shè)備上運(yùn)行。Pete曾在蘋果公司從事圖像處理的GPU優(yōu)化工作,并為O'Reilly撰寫了多本數(shù)據(jù)處理的書籍。

