前言
Tensorflow是goole開源的一套機(jī)器學(xué)習(xí)庫(kù),本篇文章并不介紹通過Tensorflow來生成預(yù)測(cè)模型,而是簡(jiǎn)單的介紹一下在iOS上接入通過Tensorflow生成好的預(yù)測(cè)模型,一般即.pd文件。
腳本生成.a靜態(tài)庫(kù)
首先下載Tensorflow的項(xiàng)目议薪,項(xiàng)目地址。然后在Readme里找到iOS部分媳友,里面有說明需要安裝哪些工具和如何生成靜態(tài)庫(kù)(這里會(huì)下載一些編譯的依賴庫(kù)斯议,可能需要翻墻)。
我這里直接使用tensorflow/contrib/makefile/build_all_ios.sh -a arm64
來生成一個(gè)只支持真機(jī)arm64的庫(kù)醇锚,這樣快一點(diǎn)節(jié)省時(shí)間哼御。不然構(gòu)建全部架構(gòu)的話起碼需要兩個(gè)小時(shí)。
如果你之前已經(jīng)下載過一遍依賴,再次構(gòu)建的話可以使用tensorflow/contrib/makefile/build_all_ios.sh -a arm64 -T
恋昼,那樣的話就不會(huì)下載那些依賴了看靠,直接進(jìn)入編譯過程。
構(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來合并:
lipo libtensorflow-core-armv7s.a libtensorflow-core-arm64.a -create -output libtensorflow-core.a
引入TensorFlow靜態(tài)庫(kù)
引入TensorFlow庫(kù)有兩種方式辟宗,一種是通過cocoaPods來管理爵赵,優(yōu)點(diǎn)就是接入簡(jiǎn)單方便吝秕,缺點(diǎn)是包有點(diǎn)大,因?yàn)樗С至四闼械臋C(jī)型架構(gòu)(i386sim, x86_64sim, armv7, armv7s and arm64)空幻。
所以另一種就是通過TensorFlow的一個(gè)腳本自己來生成靜態(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ù)的過程坊罢。
- 鏈接靜態(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
它文檔里寫的是加入以下路徑里的頭文件
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ù)
我們上面說的是在主工程引入TensorFlow
的庫(kù)诸老,但是如果你一個(gè)私有的Pod倉(cāng)庫(kù)依賴了這個(gè)TensorFlow
庫(kù),那么在引入頭文件的時(shí)候就會(huì)報(bào)錯(cuò)钳恕。因?yàn)槟闼接蠵od的targets對(duì)應(yīng)的build setting
并沒有設(shè)置相關(guān)的Header Search Paths
别伏,私有pod在尋找頭文件的時(shí)候是根據(jù)自己target里的Header Search Paths
來索引的。
我們可以看到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ù)用來配置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ù)的鏈接都是主工程來進(jìn)行鏈接巩检,然后HEADER_SEARCH_PATHS
使用的是pod_target_xcconfig
,pod的target在尋找頭文件的時(shí)候是在自己的build setting
里的HEADER_SEARCH_PATHS
里配置的路徑進(jìn)行尋找的示启。我們?cè)诒镜卣{(diào)試的時(shí)候podfile里寫的是本地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)說過了主要用來確定生成對(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ù)來幫組build夹姥,但是如果已經(jīng)下載過了,第二遍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)用腳本來看看你的模型需要哪些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
問題
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問題源碼