在iOS上接入Tensorflow

前言

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 pathsOther 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ù)的過程坊罢。

  1. 鏈接靜態(tài)庫(kù)路徑

Other Linker Flags里鏈接入四個(gè)庫(kù)libtensorflow-core.alibprotobuf-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阳掐。

  1. 設(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"

  1. 加入-force_load
    Other Linker Flags${SRCROOT}/DSTensorflow/SDK/libtensorflow-core.a路徑前面加入-force_load

  2. 加入Accelerate framework
    Link Binary with Libraries"里加入Accelerate framework

  3. 支持C++
    設(shè)置C++ Language Dialect為GNU++11 (or GNU++14)艺骂,設(shè)置C++ Standard Librarylibc++

  4. 設(shè)置bitcode為NO

  5. 移除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']}

注意:

  1. 我的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)行尋找的示启。

  2. 我們?cè)诒镜卣{(diào)試的時(shí)候podfile里寫的是本地podspec的路徑pod 'DSTensorflow', :path => '../'兢哭,所以在我們的demo里的Pods文件夾下面并不會(huì)有DSTensorflow這個(gè)文件夾,而我們的HEADER_SEARCH_PATHSOTHER_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問題源碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市婚度,隨后出現(xiàn)的幾起案子蘸秘,更是在濱河造成了極大的恐慌,老刑警劉巖蝗茁,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件醋虏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡哮翘,警方通過查閱死者的電腦和手機(jī)哎甲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門缔杉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來墩邀,“玉大人深胳,你說我怎么就攤上這事熔脂∨逖校” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵霞揉,是天一觀的道長(zhǎng)旬薯。 經(jīng)常有香客問我,道長(zhǎng)适秩,這世上最難降的妖魔是什么绊序? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮秽荞,結(jié)果婚禮上骤公,老公的妹妹穿的比我還像新娘。我一直安慰自己扬跋,他們只是感情好阶捆,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钦听,像睡著了一般洒试。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上朴上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天垒棋,我揣著相機(jī)與錄音,去河邊找鬼痪宰。 笑死叼架,一個(gè)胖子當(dāng)著我的面吹牛畔裕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乖订,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼柴钻,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了垢粮?” 一聲冷哼從身側(cè)響起贴届,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜡吧,沒想到半個(gè)月后毫蚓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昔善,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年元潘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片君仆。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡翩概,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出返咱,到底是詐尸還是另有隱情钥庇,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布咖摹,位于F島的核電站评姨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏萤晴。R本人自食惡果不足惜吐句,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望店读。 院中可真熱鬧嗦枢,春花似錦、人聲如沸屯断。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裹纳。三九已至择葡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剃氧,已是汗流浹背敏储。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留朋鞍,地道東北人已添。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓妥箕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親更舞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子畦幢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容