移動裝置上實現“詩人”也能用TensorFlow

OReillyData發表於2016-12-12

編者注:本文原始刊登在Pete Warden的部落格裡, 授權轉載於此。

在《詩人也能用TensorFlow》那篇博文中,我介紹瞭如何使用你自己的圖片來訓練一個可識別圖片物件的神經網路模型。接下來就是將這個模型運用到你的移動裝置中。在這篇文章中,我會介紹如何在你的iOS應用程式中執行這個模型。

你可以在本文中找到書面指南,也可以通過下面的視訊及註解瞭解詳細的操作步驟。

我假定你已經完成了《詩人也能用TensorFlow》的步驟。 所以你應該已經安裝了Docker, 並在home路徑下建立了一個tf_files的資料夾。這個資料夾裡有一個包含你的模型的retrained_graph.pd檔案。 如果你還沒有完成以上步驟,你需要按照《詩人也能用TensorFlow》教程示例來完成你自己的模型訓練。

第一步,開啟Docker QuickStart Terminal並利用最新的Docker映象啟動一個新的Docker容器。 本教程依賴了一些TensorFlow的新特性,所以用於《詩人也能用TensorFlow》的v0.8版映象不能用了。

docker run -it -p 8888:8888 -v $HOME/tf_files:/tf_files tensorflow/tensorflow:nightly-devel

你應該可以看到自己在一個新的shell視窗中,提示符以“root@”開頭,以“#”結尾 ,這表示你已經執行在Docker映象中了。為了確保設定正確,請執行ls -lah /tf_files/,並確認已生成了retrained_graph.pb檔案。

接下來,首先我們要確保此模型能夠產生合理的結果。在這裡我使用預設的花朵影象來做測試。但如果你已經訓練了自定義類別,可以用你自己的影象檔案來代替。整個編譯過程可能需要幾分鐘。如果執行速度太慢,請確認你已經更新了VirtualBox的配置來充分利用計算機記憶體和處理器能力。

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

對於花朵頂部是雛菊的圖片,這個模型應該可以給出一個合理的第一選擇標籤。在我們對模型檔案進行進一步處理以便其能在移動應用程式中使用後,我們還會使用此命令來確保它仍然能得到合理的結果。

移動裝置的記憶體容量有限,而且應用程式需要下載到裝置本地執行。因此在預設情況下,TensorFlow的iOS版本僅包含支援介面中常見的運算的程式碼,並不包含很多的外部依賴性包。你可以在tensorflow/contrib/makefile/tf_op_files.txt檔案中檢視此版本所支援的運算列表。其中一個不支援的運算是DecodeJpeg,因為對它的實現依賴於libjpeg(這很難在iOS中支援),並且會增加編譯後程式碼的大小。

雖然我們可以編寫一個使用iOS原生的影象庫的新的實現方法,但是對於大多數移動應用程式,我們不需要解碼JPEG影象。因為我們可以直接使用相機的影象緩衝區。

不幸的是,我們重新訓練所基於的Inception模型包括一個DecodeJpeg運算。我們一般通過在解碼之後直接反饋Mul節點來繞過這一運算。但是在不支援該運算的平臺上,即使從未呼叫它,也會在載入影象時發生錯誤。為了避免這種情況的出現,optimize_for_inference指令碼里刪除了輸入和輸出節點集合中不需要的所有節點。

該指令碼還做了一些其他優化以提高執行速度。例如它把顯式批處理標準化運算跟卷積權重進行了合併,從而降低了計算量。執行方式如下:

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中建立一個新檔案。想確認它沒有更改網路輸出,可以在更新的模型上再次執行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/optimized_graph.pb

你應該可以看到跟第一次對圖片進行識別非常類似的結果,因為不管對模型的處理流程作出什麼更改,其底層的數學運算結果是應該一致的。

重新訓練後的模型仍然是87MB。這導致了任何包含它的應用程式的下載包都會很大。有很多方法可以減少下載包的大小,但有一個方法非常簡單有用,並且不會增加太多的複雜性。Apple使用.ipa包釋出應用程式,其中所有的內容都使用zip壓縮。

通常因為權重都是略微不同的浮點值,所以模型不能被很好地壓縮。但是你可以通過將特定常數範圍內的所有權重湊整到256個級別,同時仍然保持浮點格式,從而實現更好的壓縮比。

應用以下這些改進:給予壓縮演算法更多可利用的重複性;不需要任何的新運算元;並且僅略微降低精度(通常降低小於1%的)

以下是如何呼叫quantize_graph指令碼以應用這些改進的方法:

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

你會發現rounded_graph.pb檔案的原始大小仍然是87MB。但是如果你在Finder(資源管理器)中右鍵單擊它,並選擇“壓縮”,應該可以看到生成了一個大小約24MB的壓縮檔案。 這個差異就是iOS壓縮的.ipa檔案或者Android壓縮的.apk檔案和原始檔案(未壓縮過)大小的差異。

為了驗證模型仍然正常工作,請再次執行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/rounded_graph.pb

這一次,評分的結果有稍微明顯的變化(由於量化的影響),但是標籤的總體個數和順序應該與之前兩次執行的結果是相同的。

我們需要執行的最後一個處理步驟是記憶體對映。因為儲存模型權重值的緩衝區大小為87MB,所以在需要將這些權重內容載入應用程式使用的記憶體時,甚至在執行模型之前,就會對iOS的記憶體空間造成很大的壓力。這會導致穩定性出問題,因為作業系統可能會無預兆地殺掉使用太多記憶體的應用程式。幸運的是這些緩衝區是隻讀的,因此可以將這些內容對映到記憶體中,以便作業系統可以在有記憶體壓力時輕鬆地丟棄它們,從而避免崩潰的可能。

為了支援這一點,我們需要重新整理模型,使權重值能夠被儲存在可以輕鬆地跟主GraphDef分開載入的部分,即使它們仍然在同一個檔案中。以下是操作命令:

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

要注意的一點是,磁碟上的檔案不再是一個普通的GraphDef protobuf。所以如果你嘗試載入到一個像label_image這樣的程式中,將會發生錯誤。你需要稍微改變一下載入模型檔案的方法,下面我將會介紹在iOS上的一個應用例子。

到目前為止,我們已經在Docker上執行了所有的指令碼。出於演示的目的,在Docker中執行指令碼要容易很多,因為在Ubuntu上安裝Python依賴包比在OS X上更加簡單。

現在,我們將切換到本機終端,以便我們可以編譯一個使用你訓練的模型的iOS應用程式。

你需要安裝Xcode7.3或者更高版本的命令列工具來開發編譯應用程式。你可以從Apple官網上下載Xcode。完成安裝後,開啟一個新的終端視窗,下載TensorFlow原始碼(使用git clone https://github.com/tensorflow/tensorflow命令)到你本機的一個資料夾中。請把下面命令裡的“〜/ projects / tensorflow”替換為該資料夾路徑,然後執行以下命令編譯Tensorflow框架並把模型檔案複製過來:

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

檢查終端輸出的結果以確保編譯成功。這時你應該可以找到可以在Xcode中開啟的相機示例專案。這個應用程式顯示了你的相機的實時拍攝的內容,以及它可以識別的內容裡的物件的標籤,所以它是一個很好的用來測試新模型的演示專案。

上面的命令應該已經將你需要的模型檔案複製到應用程式的資料資料夾中了,但是你仍然需要讓Xcode知道應該將這些檔案包含在應用程式中。要刪除相機專案的預設模型檔案,請在Xcode的左側專案導航器中,在資料資料夾中選中imagenet_comp_graph_label_strings.txt和tensorflow_inception_graph.pb檔案。刪除它們,並在刪除提示中選擇“移至垃圾箱”。

接下來,開啟一個包含新模型檔案的Finder視窗,例如在命令列終端中做如下操作:

open tensorflow/contrib/ios_examples/camera/data

將mmapped_graph.pb 和retrained_labels.txt 從該Finder視窗拖動到專案導航器中的資料資料夾中。確保在對話方塊的核取方塊中為CameraExample啟用了“新增到目標”。這可以在你編譯應用程式時,讓Xcode知道它應該包括這些檔案。所以如果在後面步驟中你看到了檔案丟失的錯誤資訊,請仔細檢查這一步驟。

640?wx_fmt=jpeg

圖1 新增檔案對話方塊,顯示要選擇的選項。圖片由Pete_Warden友情提供

我們已經有了應用程式中的檔案。現在需要更新一些其他資訊。我們需要更新載入檔案的名稱,還有一些後設資料,包括輸入影象的大小、節點名稱以及如何在載入影象之前對畫素值進行數值壓縮。要進行這些更改,在Xcode裡開啟CameraExampleViewController.mm,查詢靠近檔案頂部的模型設定部分,並用以下內容替換它們:

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裝置(這個不能在模擬器上執行,因為它需要一個攝像頭)到本機,並且按Command+R鍵來編譯和執行修改後的示例。如果一切正常,你應該會看到應用程式啟動,實時顯示攝像頭拍攝的內容,並開始顯示你的訓練類別的標籤。

你可以找一個想識別的物件型別的東西用來測試。把相機對準它,看看程式是否能夠給出正確的標籤。如果你沒有任何實物物件,可以嘗試在網路上搜尋影象,並將相機對準你的計算機顯示器。

恭喜你已經能夠訓練自己的模型,並在手機上執行它!

640?wx_fmt=jpeg

圖2 鬱金香的圖片搜尋結果與iPhone應用程式的螢幕。 圖片由Pete Warden友情提供

上面步驟裡的許多內容都可以被用於安卓系統或樹莓派上,也包括TensorFlow提供的其他模型。它們可以被用於從自然語言處理到語音合成的多個方面。我很高興看到在多種裝置上出現了使用深不可測的深度學習的新應用程式,所以我迫不及待地想知道你搞出了什麼新東西!

相關資料:

Pete Warden

Pete Warden是TensorFlow移動團隊的技術主管。在此之前,他是Jetpac的CTO。Jetpac於2014年被谷歌收購。它的深度學習技術經過優化,可在移動和嵌入式裝置上執行。Pete曾在蘋果公司從事影象處理的GPU優化工作,併為O'Reilly撰寫了多本資料處理的書籍。

640?wx_fmt=png

相關文章