前言
Tensorflow是goole開(kāi)源的一套機(jī)器學(xué)習(xí)庫(kù),本篇文章并不介紹通過(guò)Tensorflow來(lái)生成預(yù)測(cè)模型,而是簡(jiǎn)單的介紹一下在iOS上接入通過(guò)Tensorflow生成好的預(yù)測(cè)模型,一般即.pd文件。
腳本生成.a靜態(tài)庫(kù)
首先下載Tensorflow的項(xiàng)目,項(xiàng)目地址。然后在Readme里找到iOS部分,里面有說(shuō)明需要安裝哪些工具和如何生成靜態(tài)庫(kù)(這里會(huì)下載一些編譯的依賴庫(kù),可能需要翻墻)。
我這里直接使用tensorflow/contrib/makefile/build_all_ios.sh -a arm64來(lái)生成一個(gè)只支持真機(jī)arm64的庫(kù),這樣快一點(diǎn)節(jié)省時(shí)間。不然構(gòu)建全部架構(gòu)的話起碼需要兩個(gè)小時(shí)。
如果你之前已經(jīng)下載過(guò)一遍依賴,再次構(gòu)建的話可以使用tensorflow/contrib/makefile/build_all_ios.sh -a arm64 -T,那樣的話就不會(huì)下載那些依賴了,直接進(jìn)入編譯過(guò)程。
構(gòu)建完成之后我們就能在
tensorflow/contrib/makefile/gen/lib、
tensorflow/contrib/makefile/gen/protobuf_ios/lib、
tensorflow/contrib/makefile/downloads/nsync/builds/lipo.ios.c++11
下找到生成好的靜態(tài)庫(kù)。
如果你要支持兩個(gè)架構(gòu)那可以單獨(dú)生成兩個(gè)架構(gòu)的.a靜態(tài)庫(kù)后,使用lipo來(lái)合并:
lipo libtensorflow-core-armv7s.a libtensorflow-core-arm64.a -create -output libtensorflow-core.a
引入TensorFlow靜態(tài)庫(kù)
引入TensorFlow庫(kù)有兩種方式,一種是通過(guò)cocoaPods來(lái)管理,優(yōu)點(diǎn)就是接入簡(jiǎn)單方便,缺點(diǎn)是包有點(diǎn)大,因?yàn)樗С至四闼械臋C(jī)型架構(gòu)(i386sim, x86_64sim, armv7, armv7s and arm64)。
所以另一種就是通過(guò)TensorFlow的一個(gè)腳本自己來(lái)生成靜態(tài)庫(kù),然后自己引入工程里面,缺點(diǎn)就是稍微繁瑣一點(diǎn),需要自己生成靜態(tài)庫(kù),還有配置Header search paths和Other Linker Flags等。優(yōu)點(diǎn)就是可以自己選擇適配哪些架構(gòu),有效的減小包的大小。
cocoaPods引入.framework靜態(tài)庫(kù)
創(chuàng)建一個(gè)新項(xiàng)目,然后在Podfile里引入pod 'TensorFlow-experimental',然后在你要使用TensorFlow的類里引入對(duì)應(yīng)的頭文件,如下:
#import "ViewController.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/public/session.h"
然后我們會(huì)發(fā)現(xiàn)編譯錯(cuò)誤,因?yàn)槲覀円氲腡ensorFlow是C++的代碼,所以你調(diào)用的類的文件后綴.m改為.mm。
主工程引入.a靜態(tài)庫(kù)
我們?cè)?a target="_blank" rel="nofollow">Tensorflow文檔里的Creating your Own App from your source libraries下可以看到整個(gè)鏈接生成的靜態(tài)庫(kù)的過(guò)程。
- 鏈接靜態(tài)庫(kù)路徑
在
Other Linker Flags里鏈接入四個(gè)庫(kù)libtensorflow-core.a、libprotobuf-lite.a、libprotobuf.a、nsync.a,分別在目錄
tensorflow/contrib/makefile/gen/lib/、
tensorflow/contrib/makefile/gen/protobuf_ios/lib、
tensorflow/contrib/makefile/downloads/nsync/builds/lipo.ios.c++11下面。
這里我把這三個(gè)庫(kù)都復(fù)制到我的Demo里,所以在Other Linker Flags里加入的路徑是
${SRCROOT}/DSTensorflow/SDK/libtensorflow-core.a、
${SRCROOT}/DSTensorflow/SDK/libprotobuf-lite.a、
${SRCROOT}/DSTensorflow/SDK/libprotobuf.a、
${SRCROOT}/DSTensorflow/SDK/nsync.a。
- 設(shè)置Header Search paths
它文檔里寫(xiě)的是加入以下路徑里的頭文件
the root folder of tensorflow,
tensorflow/contrib/makefile/downloads/nsync/public
tensorflow/contrib/makefile/downloads/protobuf/src
tensorflow/contrib/makefile/downloads,
tensorflow/contrib/makefile/downloads/eigen, and
tensorflow/contrib/makefile/gen/proto.
但是如果把這整個(gè)tensorflow文件夾加到版本管理里,那么就太大了,整個(gè)tensorflow有六七百兆。所以我刪除了一些文件,只留下一些需要的文件,所以我工程里的路徑是:
"${SRCROOT}/DSTensorflow/SDK"
"${SRCROOT}/DSTensorflow/SDK/tensorflow/contrib/makefile/downloads"
"${SRCROOT}/DSTensorflow/SDK/tensorflow/contrib/makefile/downloads/eigen"
"${SRCROOT}/DSTensorflow/SDK/tensorflow/contrib/makefile/downloads/protobuf/src"
"${SRCROOT}/DSTensorflow/SDK/tensorflow/contrib/makefile/gen/proto"
"${SRCROOT}/DSTensorflow/SDK/tensorflow/contrib/makefile/downloads/nsync/public"
加入
-force_load
在Other Linker Flags的${SRCROOT}/DSTensorflow/SDK/libtensorflow-core.a路徑前面加入-force_load加入Accelerate framework
在Link Binary with Libraries"里加入Accelerate framework支持C++
設(shè)置C++ Language Dialect為GNU++11 (or GNU++14),設(shè)置C++ Standard Library為libc++設(shè)置bitcode為NO
移除
Other Linker Flags里的-all_load(如果有的話)
私有Pod引入.a靜態(tài)庫(kù)
我們上面說(shuō)的是在主工程引入TensorFlow的庫(kù),但是如果你一個(gè)私有的Pod倉(cāng)庫(kù)依賴了這個(gè)TensorFlow庫(kù),那么在引入頭文件的時(shí)候就會(huì)報(bào)錯(cuò)。因?yàn)槟闼接蠵od的targets對(duì)應(yīng)的build setting并沒(méi)有設(shè)置相關(guān)的Header Search Paths,私有pod在尋找頭文件的時(shí)候是根據(jù)自己target里的Header Search Paths來(lái)索引的。
我們可以看到TensorFlow-experimental.podspec里的xcconfig是怎么給主工程設(shè)置的:
"xcconfig": {
"HEADER_SEARCH_PATHS": "'${SRCROOT}/Pods/TensorFlow-experimental/Frameworks/tensorflow_experimental.framework/Headers' '${SRCROOT}/Pods/TensorFlow-experimental/Frameworks/tensorflow_experimental.framework/Headers/third_party/eigen3'",
"OTHER_LDFLAGS": "-force_load '${SRCROOT}/Pods/TensorFlow-experimental/Frameworks/tensorflow_experimental.framework/tensorflow_experimental' '-L ${SRCROOT}/Pods/TensorFlow-experimental/Frameworks/tensorflow_experimental.framework' -lprotobuf_experimental"
}
podspec有三個(gè)相關(guān)的參數(shù)用來(lái)配置build setting,分別為xcconfig(設(shè)置主工程和當(dāng)前pod的build setting)、pod_target_xcconfig(修改當(dāng)前pod的build setting)、user_target_xcconfig(修改主工程的build setting)。
所以你如果要設(shè)置你調(diào)用Tensorflow那個(gè)私有pod的build setting,則需要使用pod_target_xcconfig,比如我demo里面在DSTensorflow.podspec里設(shè)置如下:
s.preserve_paths = 'DFCVinScanner/SDK/**/*'
s.frameworks = 'Accelerate'
s.pod_target_xcconfig = {"HEADER_SEARCH_PATHS" => "$(inherited) '$(PODS_TARGET_SRCROOT)/DSTensorflow/SDK' '$(PODS_TARGET_SRCROOT)/DSTensorflow/SDK/tensorflow/contrib/makefile/downloads' '$(PODS_TARGET_SRCROOT)/DSTensorflow/SDK/tensorflow/contrib/makefile/downloads/eigen' '$(PODS_TARGET_SRCROOT)/DSTensorflow/SDK/tensorflow/contrib/makefile/downloads/protobuf/src' '$(PODS_TARGET_SRCROOT)/DSTensorflow/SDK/tensorflow/contrib/makefile/gen/proto' '$(PODS_TARGET_SRCROOT)/DSTensorflow/SDK/tensorflow/contrib/makefile/downloads/nsync/public'"}
s.user_target_xcconfig = {"OTHER_LDFLAGS" => ['$(inherited)', '$(PODS_ROOT)/DSTensorflow/DSTensorflow/SDK/nsync.a', '-force_load', '$(PODS_ROOT)/DSTensorflow/DSTensorflow/SDK/libtensorflow-core.a', '$(PODS_ROOT)/DSTensorflow/DSTensorflow/SDK/libprotobuf-lite.a', '$(PODS_ROOT)/DSTensorflow/DSTensorflow/SDK/libprotobuf.a']}
注意:
我的
OTHER_LDFLAGS是使用user_target_xcconfig,因?yàn)殪o態(tài)庫(kù)的鏈接都是主工程來(lái)進(jìn)行鏈接,然后HEADER_SEARCH_PATHS使用的是pod_target_xcconfig,pod的target在尋找頭文件的時(shí)候是在自己的build setting里的HEADER_SEARCH_PATHS里配置的路徑進(jìn)行尋找的。我們?cè)诒镜卣{(diào)試的時(shí)候podfile里寫(xiě)的是本地podspec的路徑
pod 'DSTensorflow', :path => '../',所以在我們的demo里的Pods文件夾下面并不會(huì)有DSTensorflow這個(gè)文件夾,而我們的HEADER_SEARCH_PATHS和OTHER_LDFLAGS的路徑為Pods/DSTensorflow下的路徑,所以為了可以調(diào)試,我就手動(dòng)把那些文件復(fù)制進(jìn)了這個(gè)文件夾下面。當(dāng)然如果你到時(shí)候這個(gè)私有pod正常發(fā)布的了,使用pod 'DSTensorflow'這樣正常依賴的話,Pods/DSTensorflow下就會(huì)有對(duì)應(yīng)的文件了。
簡(jiǎn)單的接入Tensorflow的Demo
Tensorflow編譯靜態(tài)庫(kù)腳本解析
tensorflow-r1.8/tensorflow/contrib/makefile/build_all_ios.sh這個(gè)就是創(chuàng)建靜態(tài)庫(kù)的腳本,它主要有以下3個(gè)參數(shù)-a -g -T:
Usage: build_all_ios.sh [-a:T]
-a [build_arch] build only for specified arch x86_64 [default=all]
-g [graph] optimize and selectively register ops only for this graph
-T only build tensorflow (dont download other deps etc)
-a我們之前已經(jīng)說(shuō)過(guò)了主要用來(lái)確定生成對(duì)應(yīng)架構(gòu)的靜態(tài)庫(kù),默認(rèn)為生成所有架構(gòu)的靜態(tài)庫(kù)
-T表示是否只build tensorflow的靜態(tài)庫(kù),因?yàn)樗J(rèn)需要下載一些依賴庫(kù),工具庫(kù)來(lái)幫組build,但是如果已經(jīng)下載過(guò)了,第二遍build的時(shí)候其實(shí)就不用下載了,這時(shí)候就可以用這個(gè)參數(shù),這樣可以加快速度。
-g表示只選擇注冊(cè)某些你這個(gè)模型需要的op操作,這個(gè)如果不選的話,你在調(diào)用你的模型的時(shí)候,有些模型就會(huì)報(bào)No OpKernel was registered to support Op xxx的錯(cuò)誤,表示你這個(gè)靜態(tài)庫(kù)并不支持這個(gè)op操作。
我們也可以自己調(diào)用腳本來(lái)看看你的模型需要哪些op:
執(zhí)行以下操作下載對(duì)應(yīng)的工具:
$ bazel build --copt="-DUSE_GEMM_FOR_CONV" tensorflow/python/tools/print_selective_registration_header
執(zhí)行以下操作得到你模型需要的op,并生成ops_to_register.h(tensorflow在創(chuàng)建靜態(tài)庫(kù)的時(shí)候就會(huì)用到這個(gè)文件):
$ bazel-bin/tensorflow/python/tools/print_selective_registration_header --graphs=Users/you-path/graph.pb > tensorflow/core/framework/ops_to_register.h
一個(gè)完整的例子如下:
tensorflow/contrib/makefile/build_all_ios.sh -a arm64 -g Users/you-path/graph.pb -T
問(wèn)題
No OpKernel was registered to support Op 'Conv2D'
參考
iOS 平臺(tái) TensorFlow 實(shí)踐:實(shí)際應(yīng)用教程(附源碼)(二)
TensorFlow Mobile模型壓縮
解決No OpKernel was registered to support Op 'Less' with these attrs問(wèn)題源碼