在iOS平臺上使用TensorFlow教程(下)
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
版權宣告
作者:Matthijs Hollemans
譯者:運和憑
原文連結:http://machinethink.net/blog/tensorflow-on-ios/
本文由作者授權翻譯併發布,未經允許禁止轉載。
在上篇文章中,我們瞭解了TensorFlow的特性和具體如何使用,並且實際建立了一個分類器,那麼它的實際效果如何?
在本文裡我們還將看到如何在iOS上使用TensorFlow,最後討論一下iOS上使用TensorFlow的優劣。
實際效果如何?
在完成對分類器的訓練之後,接下來就是對其進行測試以瞭解它在實踐當中的執行效果。大家需要使用未在訓練中涉及過的資料完成這項測試。正因為如此,我們才在此前將資料集拆分為訓練集與測試集。
我們將建立一套新的test.py指令碼,負責載入計算圖定義以及測試集,並計算其正確預測的測試示例數量。這裡我將只提供重要的部分,大家可以點選此處檢視完整指令碼內容。
備註:測試集的結果精確度將始終低於訓練集的結果精確度(後者為97%)。不過如果前者遠低於後者,則大家應對分類器進行檢查並對訓練流程進行調整。我們預計測試集的實際結果應該在95%左右。任何低於90%的精度結果都應引起重視。
與之前一樣,這套指令碼會首先匯入必要軟體包,包括來自scikit-learn的指標包以輸出各類其它報告。當然,這一次我們選擇載入測試集而不再是訓練集。
import numpy as np import tensorflow as tf from sklearn import metrics X_test = np.load("X_test.npy") y_test = np.load("y_test.npy")
為了計算測試集的結果精確度,我們仍然需要計算圖。不過這一次不再需要完整的計算圖,因為train_op與loss兩個用於訓練的節點這裡不會被用到。大家當然可以再次手動建立計算圖,但由於此前我們已經將其儲存在graph.pb檔案當中,因此這裡直接載入即可。以下為相關程式碼:
with tf.Session() as sess: graph_file = os.path.join(checkpoint_dir, "graph.pb") with tf.gfile.FastGFile(graph_file, "rb") as f: graph_def = tf.GraphDef() graph_def.ParseFromString(f.read()) tf.import_graph_def(graph_def, name="")
TensorFlow可能會將其資料儲存為協議緩衝檔案(副檔名為.pb),因此我們可以使用部分helper程式碼以載入此檔案並將其作為計算圖匯入至會話當中。
接下來,我們需要從checkpoint檔案處載入W與b的值:
W = sess.graph.get_tensor_by_name("model/W:0") b = sess.graph.get_tensor_by_name("model/b:0")checkpoint_file = os.path.join(checkpoint_dir, "model") saver = tf.train.Saver([W, b]) saver.restore(sess, checkpoint_file)
正因為如此,我們需要將節點引入範圍併為其命名,從而利用get_tensor_by_name()輕鬆再次將其找到。如果大家沒有為節點提供明確的名稱,則可能需要認真查閱計算圖定義才能找到TensorFlow為其預設分配的名稱。
我們還需要引用其它幾個節點,特別是作為輸入內容的x與y以及其它負責進行預測的節點:
x = sess.graph.get_tensor_by_name("inputs/x-input:0") y = sess.graph.get_tensor_by_name("inputs/y-input:0") accuracy = sess.graph.get_tensor_by_name("score/accuracy:0") inference = sess.graph.get_tensor_by_name("inference/inference:0")
好的,到這裡我們已經將計算圖重新載入至記憶體當中。我們還需要再次將分類器學習到的內容載入至W與b當中。現在我們終於可以測試分類器在處理其之前從未見過的資料時表現出的精確度了:
feed = {x: X_test, y: y_test} print("Test set accuracy:", sess.run(accuracy, feed_dict=feed))
上述程式碼會執行accuracy節點並利用來自X_test陣列的聲學特徵作為輸入內容,同時使用來自y_test的標籤進行結果驗證。
備註:這一次,饋送詞典不再需要為learning_rate與regularization佔位符指定任何值。我們只需要在accuracy節點上執行計算圖的一部分,且此部分中並不包括這些佔位符。
我們還可以藉助scikit-learn的幫助顯示其它一些報告:
predictions = sess.run(inference, feed_dict={x: X_test}) print("Classification report:") print(metrics.classification_report(y_test.ravel(), predictions)) print("Confusion matrix:") print(metrics.confusion_matrix(y_test.ravel(), predictions))
這一次,我們使用inference節點以獲取預測結果。由於inference只會計算預測結果而不會檢查其精確度,因此饋送詞典中僅需要包含輸入內容x而不再需要y。
執行此指令碼之後,大家應該會看到類似於以下內容的輸出結果:
$ python3 test.py Test set accuracy: 0.958991 Classification report: precision recall f1-score support 0 0.98 0.94 0.96 474 1 0.94 0.98 0.96 477 avg / total 0.96 0.96 0.96 951 Confusion matrix: [[446 28] [ 11 466]]
測試集的預測精確度接近96%——與預期一樣,略低於訓練集的精確度,但也已經相當接近。這意味著我們的訓練已經獲得成功,且我們也證明了這套分類器能夠有效處理其從未見過的資料。其當然還不夠完美——每25次嘗試中即有1次屬於分類錯誤,但對於本教程來說這一結果已經完全令人滿意。
分類報告與混淆矩陣顯示了與錯誤預測相關的示例統計資訊。通過混淆矩陣,我們可以看到共有446項得到正確預測的女聲示例,而另外28項女聲示例則被錯誤地判斷為男聲。在466項男聲示例中分類器給出了正確結論,但有11項則被錯誤判斷為女聲。
這樣看來,我們的分類器似乎不太擅長分辨女性的語音,因為其女聲識別錯誤率更高。分類報告/回撥數字亦給出了相同的結論。
在iOS上使用TensorFlow
現在我們已經擁有了一套經過訓練的模型,其擁有比較理想的測試集預測精確度。下面我們將構建一款簡單的iOS應用,並利用這套模型在其中實現預測能力。首先,我們利用TensorFlow C++庫構建一款應用。在下一章節中,我們將把模型引入Metal以進行比較。
這裡我們既有好訊息也有壞訊息。壞訊息是大家需要利用原始碼自行構建TensorFlow。事實上,情況相當糟糕:大家需要安裝Java方可實現這專案標。而好訊息是整個流程其實並不複雜。感興趣的朋友可以點選此處檢視完整指南,但以下步驟也基本能夠幫助大家解決問題(在TensorFlow 1.0上實測有效)。
這裡需要注意的是,大家應當安裝Xcode 8,並確保活動開發者目錄指向您Xcode的安裝位置(如果大家先安裝了Homebrew,隨後才安裝Xcode,則其可能指向錯誤位置,意味著TensorFlow將無法完成構建):
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
TensorFlow利用一款名為bazel的工具進行構建,bazel則要求配合Java JDK 8。大家可以利用Homebrew輕鬆安裝必要的軟體包:
brew cask install java brew install bazel brew install automake brew install libtool
在完成之後,大家需要克隆TensorFlow GitHub庫。需要注意的是:請確保您指定的路徑不具備充足的空間,否則bazel會拒絕進行構建(沒錯,這是真的!)。我直接將其克隆到了自己的主目錄當中:
cd /Users/matthijs git clone https://github.com/tensorflow/tensorflow -b r1.0
其中的-b r1.0標記告知git克隆r1.0分支。大家可以隨意使用其它更新的分支,或者選擇使用master分支。
備註:在MacOS Sierra上,接下來即將執行的configure指令碼會提示多項錯誤。為了解決問題,我不得不選擇克隆master分支。在OS X El Capitan上,使用r1.0分支則不會引發任何錯誤。
在程式碼庫克隆完成後,大家需要執行configure指令碼。
cd tensorflow ./configure
其會提出幾個問題,以下為我給出的回答:
Please specify the location of python. [Default is /usr/bin/python]:
我的回答是/usr/local/bin/python3,因為我希望使用Python 3.6配合TensorFlow。如果大家選擇預設選項,則TensorFlow將利用Python 2.7進行構建。
Please specify optimization flags to use during compilation [Default is -march=native]:
在這裡直接按下回車鍵,接下來的幾個問題則全部按n選擇否。
在其問及要使用哪套Python庫時,按下回車以選擇預設選項(即Python 3.6庫)。
接下來的問題全部按n選擇否。現在該指令碼將下載幾項依賴性專案併為構建TensorFlow做好準備。
構建靜態庫
我們可以通過以下兩種方式構建TensorFlow:
在Mac系統上,使用bazel構建工具。
在iOS上使用Makefile。
由於我們需要面向iOS進行構建,因此選擇選項二。然而,我們還需要構建其它一些工具,因此也得涉及選項一的內容。
在tensorflow目錄下執行以下指令碼:
tensorflow/contrib/makefile/build_all_ios.sh
其會首先下載一些依賴性選項,而後開始進行構建流程。如果一切順利,其將創建出三套接入應用所必需的靜態庫,分別為: libtensorflow-core.a、libprotobuf.a、libprotobuf-lite.a。
警告:構建這些庫需要一段時間——我的iMac需要25分鐘,機型較舊的MacBook Pro則需要3個小時,而且整個過程中風扇一直在全力運轉!大家可能會在過程中看到一些編譯器警告甚至錯誤提示資訊一閒而過。當作沒看見就好,時間一到工作自然就緒!
到這裡工作還沒結束。我們還需要構建其它兩款helper工具。在終端當中執行以下兩條命令:
bazel build tensorflow/python/tools:freeze_graph bazel build tensorflow/python/tools:optimize_for_inference
注意:這一過程大約需要20分鐘左右,因為其需要再次從零開始構建TensorFLow(這一次使用bazel)。
備註:如果大家在過程中遇到了問題,請參閱官方指南。
為Mac裝置構建TensorFlow
這部分內容為可選專案,但由於大家已經安裝了全部必要軟體包,因此為Mac系統構建TensorFlow並不困難。其會建立一個新的pip軟體包,大家可進行安裝以取代官方TensorFlow軟體包。
為什麼不使用官方軟體包?因為這樣我們才能建立一套包含自定義選項的TensorFlow版本。舉例來說,如果大家在執行train.py指令碼時遇到了“此TensorFlow庫無法利用SSE4.1指令進行編譯”的警告提示,則可編譯一套特殊的TensorFLow版本以啟用這些指令。
要為Mac系統構建TensorFlow,請在終端中執行以下命令:
bazel build --copt=-march=native -c opt //tensorflow/tools/pip_package:build_pip_package bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg
其中的-march=native選項用於在您的CPU能夠支援的前提下,新增對SSE、AVX、AVX2以及FMA等的支援。
隨後安裝該軟體包:
pip3 uninstall tensorflow sudo -H pip3 install /tmp/tensorflow_pkg/tensorflow-1.0.0-XXXXXX.whl
欲瞭解更多細節資訊,請參閱TensorFlow官方網站。
“凍結”計算圖
我們將要建立的iOS應用將利用Python指令碼載入之前訓練完成的模型,並利用其作出一系列預測。
大家應該還記得,train.py將計算圖定義儲存在了/tmp/voice/graph.pb檔案當中。遺憾的是,大家無法直接將該計算圖載入至iOS應用當中。完整的計算圖中包含的操作目前還不受TensorFlow C++ API的支援。正因為如此,我們才需要使用剛剛構建完成的其它兩款工具。其中freeze_graph負責獲取graph.pb以及包含有W與b訓練結果值的checkpoint檔案。其還會移除一切在iOS之上不受支援的操作。
在終端當中立足tensorflow目錄執行該工具:
bazel-bin/tensorflow/python/tools/freeze_graph --input_graph=/tmp/voice/graph.pb --input_checkpoint=/tmp/voice/model --output_node_names=model/y_pred,inference/inference --input_binary --output_graph=/tmp/voice/frozen.pb
以上命令將在/tmp/voice/frozen.pb當中建立一套經過簡化的計算圖,其僅具備y_pred與inference兩個節點。其並不包含任何用於訓練的計算圖節點。
使用freeze_graph的好處在於,其還將固定該檔案中的權重值,這樣大家就無需分別進行權重值載入了:frozen.pb中已經包含我們需要的一切。而optimize_for_inference工具則負責對計算圖進行進一步簡化。其將作為grozen.pb檔案的輸入內容,並寫入/tmp/voice/inference.pb作為輸出結果。我們隨後會將此檔案嵌入至iOS應用當中。使用以下命令執行該工具:
bazel-bin/tensorflow/python/tools/optimize_for_inference --input=/tmp/voice/frozen.pb --output=/tmp/voice/inference.pb --input_names=inputs/x --output_names=model/y_pred,inference/inference --frozen_graph=True
iOS應用
大家可以在github.com/hollance/TensorFlow-iOS-Example中的VoiceTensorFlow資料夾內找到我們此次使用的iOS應用。
在Xcode當中開啟該專案,其中包含以下幾條注意事項:
此應用利用Objective-C++編寫(其原始檔副檔名為.mm)。在編寫時尚不存在面向TensorFlow的Swift API,因此只能使用C++。
其中的inference.pb檔案已經包含在專案當中。如果需要,大家也可以直接將自有版本的inference.pb複製到此專案的資料夾之內。
此應用與Accelerate.framework相連結。
此應用與我們此前已經編譯完成的幾套靜態庫相連結。
前往Project Settings(專案設定)螢幕並切換至Build Settings(構建設定)標籤。在Other Linker Flags(其它連結標記)下,大家會看到以下內容:
/Users/matthijs/tensorflow/tensorflow/contrib/makefile/gen/protobuf_ios/lib/ libprotobuf-lite.a /Users/matthijs/tensorflow/tensorflow/contrib/makefile/gen/protobuf_ios/lib/ libprotobuf.a -force_load /Users/matthijs/tensorflow/tensorflow/contrib/makefile/gen/lib/ libtensorflow-core.a
除非您的名稱同樣為“matthijs”,否則大家需要將其替換為您TensorFlow庫的實際克隆路徑。(請注意,這裡tensorflow出現了兩次,所以資料夾名稱應為tensorflow/tensorflow/...)
備註:大家也可以將這三個.a檔案複製到專案資料夾之內,如此即不必擔心路徑可能出現問題。我個人並不打算在這一示例專案中採取這種方式,因為libtensorflow-core.a檔案是一套體積達440 MB的庫。
另外請注意檢查Header Search Paths(標題搜尋路徑)。以下為目前的設定:
~/tensorflow ~/tensorflow/tensorflow/contrib/makefile/downloads ~/tensorflow/tensorflow/contrib/makefile/downloads/eigen ~/tensorflow/tensorflow/contrib/makefile/downloads/protobuf/src ~/tensorflow/tensorflow/contrib/makefile/gen/proto
另外,大家還需要將其更新至您的克隆目錄當中。
以下為我在構建設定當中進行了修改的其它條目:
Enable Bitcode: No
Warnings / Documentation Comments: No
Warnings / Deprecated Functions: No
Bitcode目前尚不受TensorFLow的支援,所以我決定將其禁用。我還關閉了警告選項,否則在編譯應用時會出現一大票問題提示。(禁用之後,大家仍然會遇到幾項關於值轉換問題的警告。大家當然也可以將其一併禁用,但我個人還是希望多少了解一點其中的錯誤。)
在完成了對Other Linker Flags與Header Search Paths的變更之後,大家即可構建並執行我們的iOS應用。
很好,現在大家已經擁有了一款能夠使用TensorFlow的iOS應用了!下面讓我們看看它的實際執行效果。
使用 TensorFlow C++ API
TensorFlow for iOS由C++編寫而成,但其中需要編寫的C++程式碼量其實——幸運的是——並不多。一般來講,大家只需要完成以下工作:
從.pb檔案中載入計算圖與權重值。
利用此計算圖建立一項會話。
將您的資料放置在一個輸入張量內。
在一個或者多個節點上執行計算圖。
從輸出結果張量中獲取結果。
在本示例應用當中,這一切皆發生在ViewController.mm之內。首先,我們載入計算圖:
- (BOOL)loadGraphFromPath:(NSString *)path { auto status = ReadBinaryProto(tensorflow::Env::Default(), path.fileSystemRepresentation, &graph); if (!status.ok()) { NSLog(@"Error reading graph: %s", status.error_message().c_str()); return NO; } return YES; }
此Xcode專案當中已經包含我們通過在graph.pb上執行freeze_graph與optimize_for_inference工具所構建的inference.pb計算圖。如果大家希望直接載入graph.pb,則會得到以下錯誤資訊:
Error adding graph to session: No OpKernel was registered to support Op 'L2Loss' with these attrs. Registered devices: [CPU], Registered kernels: <no registered kernels> [[Node: loss-function/L2Loss = L2Loss[T=DT_FLOAT](model/W/read)]]
這是因為C++ API所能支援的操作要遠少於Python API。這裡提到我們在loss函式節點中所使用的L2Loss操作在iOS上並不適用。正因為如此,我們才需要利用freeze_graph以簡化自己的計算圖。
在計算圖載入完成之後,我們使用以下命令建立一項會話:
- (BOOL)createSession{ tensorflow::SessionOptions options; auto status = tensorflow::NewSession(options, &session); if (!status.ok()) { NSLog(@"Error creating session: %s", status.error_message().c_str()); return NO; } status = session->Create(graph); if (!status.ok()) { NSLog(@"Error adding graph to session: %s", status.error_message().c_str()); return NO; } return YES;}
會話建立完成後,我們可以利用其執行預測操作。其中的predict:method會提供一個包含20項浮點數值的陣列——即聲學特徵——並將這些數字饋送至計算得意洋洋發中。
下面我們一起來看此方法的工作方式:
- (void)predict:(float *)example { tensorflow::Tensor x(tensorflow::DT_FLOAT, tensorflow::TensorShape({ 1, 20 })); auto input = x.tensor<float, 2>(); for (int i = 0; i < 20; ++i) { input(0, i) = example[i]; }
其首先將張量x定義為我們需要使用的輸入資料。此張量為{1,20},因為其一次提取一項示例且該示例中包含20項特徵。在此之後,我們將資料由float *陣列複製至該張量當中。
接下來,我們執行該項會話:
std::vector<std::pair<std::string, tensorflow::Tensor>> inputs = { {"inputs/x-input", x} }; std::vector<std::string> nodes = { {"model/y_pred"}, {"inference/inference"} }; std::vector<tensorflow::Tensor> outputs; auto status = session->Run(inputs, nodes, {}, &outputs); if (!status.ok()) { NSLog(@"Error running model: %s", status.error_message().c_str()); return; }
這裡得出了類似於Python程式碼的內容:
pred, inf = sess.run([y_pred, inference], feed_dict={x: example})
只是不那麼簡潔。我們需要建立饋送詞典、用於列出需要執行的全部節點的向量,外加一個負責容納對應結果的向量。最後,我們告知該會話完成上述任務。
在會話運行了全部必要節點後,我們即可輸出以下結果:
auto y_pred = outputs[0].tensor<float, 2>(); NSLog(@"Probability spoken by a male: %f%%", y_pred(0, 0)); auto isMale = outputs[1].tensor<float, 2>(); if (isMale(0, 0)) { NSLog(@"Prediction: male"); } else { NSLog(@"Prediction: female"); }}
出於演示需求,只需要執行inference節點即可完成對音訊資料的男聲/女聲判斷。不過我還希望檢視計算得出的概率,因此這裡我也運行了y_pred節點。
執行iOS應用
大家可以在iPhone模擬器或者實機之上執行這款應用。在模擬器上,大家仍然會看到“此TensorFlow庫無法利用SSE4.1指令進行編譯”的提示,但在實機上則不會出現這樣的問題。
出於測試的目的,這款應用只會進行兩項預測:一次為男聲示例預測,一次為女聲示例預測。(我直接從測試集中提取了對應示例。大家也可以配合其它示例並修改maleExample或者emaleExample陣列當中的數字。)
執行這款應用,大家應該會看到以下輸出結果。該應用首先給出了計算圖當中的各節點:
Node count: 9Node 0: Placeholder 'inputs/x-input'Node 1: Const 'model/W'Node 2: Const 'model/b'Node 3: MatMul 'model/MatMul'Node 4: Add 'model/add'Node 5: Sigmoid 'model/y_pred'Node 6: Const 'inference/Greater/y'Node 7: Greater 'inference/Greater'Node 8: Cast 'inference/inference'
需要注意的是,此計算圖中僅包含實施預測所必需的操作,而不包括任何與訓練相關的內容。
此後,其會輸出預測結果:
Probability spoken by a male: 0.970405% Prediction: male Probability spoken by a male: 0.005632% Prediction: female
如果大家利用Python指令碼嘗試使用同樣的示例,那麼結果也將完全一致。任務完成!
備註:這裡要提醒大家,此項演示專案中我們對資料進行了“偽造”(即使用了提取自測試集中的示例)。如果大家希望利用這套模型處理真正的音訊,則首先需要將對應音訊轉化為20項聲學特徵。
iOS平臺上TensorFlow的優勢與缺點
TensorFlow是一款出色的機器學習模型訓練工具,特別是對於那些不畏數學計算並樂於建立新型演算法的朋友。要對規模更大的模型進行訓練,大家甚至可以在雲環境下使用TensorFLow。
除了訓練之外,本篇博文還介紹瞭如何將TensorFLow新增至您的iOS應用當中。對於這一部分,我希望概括這種作法的優勢與缺點。
在iOS之上使用TensorFlow的優勢:
使用一款工具即可實現全部預期。大家可以同時利用TensorFlow訓練模型並將其引用於裝置之上。我們不再需要將自己的計算圖移植至BNNS或者Metal等其它API處。在另一方面,大家則必須至少將部分Python程式碼“移植”為C++形式。
TensorFlow擁有眾多超越BNNS或Metal的出色功能特性。
大家可以在模擬器上對其進行測試。(Metal要求使用者始終利用實機進行測試。)
在iOS上使用TensorFLow的缺點:
目前其尚不支援GPU。TensorFlow確實能夠利用Acclerate框架以發揮CPU的向量指令優勢,但在原始處理速度上仍無法與Metal相提並論。
TensorFLow API為C++,因此大家需要使用Objective-C++自行編寫程式碼。
大家無法直接利用Swift使用TensorFLow。C++ API相較於Python API存在更多侷限性。這意味著大家無法在裝置之上進行資料訓練,因為反向傳播所需要的自動梯度計算尚不受裝置支援。但這並不是什麼大問題,畢竟移動裝置的硬體本身就不適合進行大規模資料集訓練。
TensorFlow靜態庫的加入會令應用體積增加約40 MB。大家可以通過減少受支援操作的數量對其進行瘦身,但具體過程相當麻煩。另外這還不包含您模型本體的體積,這可能會讓應用尺寸進一步膨脹。
就個人來講,我認為在iOS上使用TensorFlow並沒有什麼價效比可言——至少就目前而言是如此。其優勢根本無法抵消致命的缺點。不過作為一款年輕的產品,我相信TensorFLow未來會得到進一步改善……
備註:如果大家決定在自己的iOS應用當中使用TensorFlow,則應意識到人們完全可以直接從應用包中複製計算圖的.pb檔案以竊取您的模型。雖然這個問題不僅存在於TensorFlow當中,但由於“凍結”計算圖檔案中同時包含模型引數與計算圖定義,因此對方能夠輕鬆完成逆向工程。如果您的模型將作為應用本身的核心競爭優勢存在,那麼請務必想辦法對其加以保護以避免受到惡意窺探。
在GPU上執行:使用Metal
在iOS之上使用TensorFLow的一大缺點在於,其執行在CPU之上。雖然對於資料與模型規模較小的TensorFlow專案而言,CPU的處理能力已經完全足夠,但對於較大的模型、特別是深度學習專案而言,大家無疑需要利用GPU進行相關運算。而在iOS系統上,這意味著我們必須選擇Metal。
大家仍然需要在自己的Mac裝置上利用TensorFlow進行訓練——或者使用其它擁有強大GPU的Linux裝置甚至雲資源——不過執行在iOS上的引用程式碼則可使用Metal而非TensorFlow庫。
在對必需的學習引數進行訓練之後——即W與b值——我們需要將其匯出為Metal可以讀取的格式。幸運的是,我們只需要將其作為二進位制格式儲存為一份浮點數值列表即可。
現在我們需要編寫另一套Python指令碼:export_weights.py(點選此處檢視完整版本)。其內容與我們之前用於載入計算圖定義及checkpoint檔案的test.py非常相似。不過這一次,我們使用以下內容:
W.eval().tofile("W.bin") b.eval().tofile("b.bin")
W.eval()負責計算W的當前值並將其返回為一個NumPy陣列(過程與執行sess.run(W)完全一致)。此後,我們使用tofile()將該NumPy資料儲存為一個二進位制檔案。好了,就是這麼簡單:-)
備註:對於我們的示例分類器,W是一個20 x 1的矩陣,即一份簡單的20項浮點數值列表。對於更為複雜的模型,大家的學習引數可能屬於四維張量。在這種情況下,大家可能需要對其中的部分維度進行順序調整,因為TensorFlow儲存資料的順序與Metal的預期存在差異。大家可以直接使用tf.transpose()命令實現這一目標,但再次重申,我們的這一示例專案並不需要這些過程。
下面來看我們這套邏輯分類器的Metal版本。大家可以點選此處在其原始碼的VoiceMetal資料夾中找到對應的Xcode專案。此專案以Swift語言編寫而成。
大家應該還記得,這裡的邏輯迴歸演算法採用了以下方程式進行計算:
y_pred = sigmoid((W * x) + b)
其計算過程與神經網路當中完全連線層的執行過程相同。因此為了利用Metal實現我們的分類器,只需要使用一個MPSCNNFullyConnected層。首先,我們將W.bin與b.bin載入至Data物件當中:
let W_url = Bundle.main.url(forResource: "W", withExtension: "bin"let b_url = Bundle.main.url(forResource: "b", withExtension: "bin"let W_data = try! Data(contentsOf: W_url!)let b_data = try! Data(contentsOf: b_url!)
此後,我們建立該完全連線層:
let sigmoid = MPSCNNNeuronSigmoid(device: device)let layerDesc = MPSCNNConvolutionDeor( kernelWidth: 1, kernelHeight: 1, inputFeatureChannels: 20, outputFeatureChannels: 1, neuronFilter: sigmoid)W_data.withUnsafeBytes { W in b_data.withUnsafeBytes { b in layer = MPSCNNFullyConnected(device: device, convolutionDeor: layerDesc, kernelWeights: W, biasTerms: b, flags: .none) }}
由於輸入內容為20個數字,我決定將完全連線層的設定為一套1 x 1且包含20條輸入通道的維度“影象”。而結果y_pred僅為單一數字,這樣該完全連線層將僅擁有一條輸出通道。作為輸入與輸出資料駐留所在的物件,MPSImage同樣擁有這些維度:
let inputImgDesc = MPSImageDeor(channelFormat: .float16, width: 1, height: 1, featureChannels: 20)let outputImgDesc = MPSImageDeor(channelFormat: .float16, width: 1, height: 1, featureChannels: 1)inputImage = MPSImage(device: device, imageDeor: inputImgDesc)outputImage = MPSImage(device: device, imageDeor: outputImgDesc)
由於使用的是應用中的TensorFlow版本,因此其中的predict方法將獲取用以構建單一示例的20條浮點數值。以下為完整的方法內容:
func predict(example: [Float]) { convert(example: example, to: inputImage) let commandBuffer = commandQueue.makeCommandBuffer() layer.encode(commandBuffer: commandBuffer, sourceImage: inputImage, destinationImage: outputImage) commandBuffer.commit() commandBuffer.waitUntilCompleted() let y_pred = outputImage.toFloatArray() print("Probability spoken by a male: (y_pred[0])%") if y_pred[0] > 0.5 { print("Prediction: male") } else { print("Prediction: female") }}
這即為Metal當中的執行會話版本。其中convert(example:to:)與toFloatArray()方法屬於helper,負責將資料載入進/出MPSImage物件。就是這麼簡單,我們已經成功完成了Metal版本的應用成果!大家需要在實機之上執行此應用,因為Metal並不支援模擬器執行機制。
Probability spoken by a male: 0.970215% Prediction: male Probability spoken by a male: 0.00568771% Prediction: female
需要注意的是,這些概率與TensorFlow提供的預測結果並不完全一致。這是因為Metal會在內部使用16位浮點數值,但二者的結果仍然相當接近!
鳴謝
本示例當中使用的資料集由Kory Becker構建並下載自Kaggle.com。感興趣的朋友亦可參閱Kory的博文與原始碼:
http://www.primaryobjects.com/2016/06/22/identifying-the-gender-of-a-voice-using-machine-learning/
其他作者亦釋出了與在iOS系統之上使用TensorFlow相關的文章,我本人也從中得到了大量啟發並引用了部分程式碼示例,具體包括:
《在iOS上使用Deep MNIST與TensorFlow:上手指南》,由 Matt Rajca撰寫。
《利用Metal Performance Shaders實現TensorFlow加速》,同樣來自 Matt Rajca。《Tensorflow Cocoa示例》,由Aaron Hillegass撰寫。
TensorFlow資源庫中的《TensorFlow iOS示例》。