前言
Tensorflow是goole開源的一套機器學習庫,本篇文章並不介紹通過Tensorflow來生成預測模型,而是簡單的介紹一下在iOS上接入通過Tensorflow生成好的預測模型,一般即.pd檔案。
指令碼生成.a靜態庫
首先下載Tensorflow的專案,專案地址。然後在Readme裡找到iOS部分,裡面有說明需要安裝哪些工具和如何生成靜態庫(這裡會下載一些編譯的依賴庫,可能需要翻牆)。
我這裡直接使用tensorflow/contrib/makefile/build_all_ios.sh -a arm64
來生成一個只支援真機arm64的庫,這樣快一點節省時間。不然構建全部架構的話起碼需要兩個小時。
如果你之前已經下載過一遍依賴,再次構建的話可以使用tensorflow/contrib/makefile/build_all_ios.sh -a arm64 -T
,那樣的話就不會下載那些依賴了,直接進入編譯過程。
構建完成之後我們就能在
tensorflow/contrib/makefile/gen/lib
、
tensorflow/contrib/makefile/gen/protobuf_ios/lib
、
tensorflow/contrib/makefile/downloads/nsync/builds/lipo.ios.c++11
下找到生成好的靜態庫。
如果你要支援兩個架構那可以單獨生成兩個架構的.a靜態庫後,使用lipo來合併:
lipo libtensorflow-core-armv7s.a libtensorflow-core-arm64.a -create -output libtensorflow-core.a
複製程式碼
引入TensorFlow靜態庫
引入TensorFlow庫有兩種方式,一種是通過cocoaPods來管理,優點就是接入簡單方便,缺點是包有點大,因為它支援了你所有的機型架構(i386sim, x86_64sim, armv7, armv7s and arm64)。
所以另一種就是通過TensorFlow的一個指令碼自己來生成靜態庫,然後自己引入工程裡面,缺點就是稍微繁瑣一點,需要自己生成靜態庫,還有配置Header search paths
和Other Linker Flags
等。優點就是可以自己選擇適配哪些架構,有效的減小包的大小。
cocoaPods引入.framework靜態庫
建立一個新專案,然後在Podfile裡引入pod 'TensorFlow-experimental'
,然後在你要使用TensorFlow的類裡引入對應的標頭檔案,如下:
#import "ViewController.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/public/session.h"
複製程式碼
然後我們會發現編譯錯誤,因為我們引入的TensorFlow是C++的程式碼,所以你呼叫的類的檔案字尾.m
改為.mm
。
主工程引入.a靜態庫
我們在Tensorflow文件裡的Creating your Own App from your source libraries下可以看到整個連結生成的靜態庫的過程。
- 連結靜態庫路徑
在
Other Linker Flags
裡連結入四個庫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
下面。
這裡我把這三個庫都複製到我的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
。
- 設定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.
但是如果把這整個tensorflow資料夾加到版本管理裡,那麼就太大了,整個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++ 設定
C++ Language Dialect
為GNU++11 (or GNU++14),設定C++ Standard Library
為libc++
-
設定bitcode為NO
-
移除
Other Linker Flags
裡的-all_load
(如果有的話)
私有Pod引入.a靜態庫
我們上面說的是在主工程引入TensorFlow
的庫,但是如果你一個私有的Pod倉庫依賴了這個TensorFlow
庫,那麼在引入標頭檔案的時候就會報錯。因為你私有Pod的targets對應的build setting
並沒有設定相關的Header Search Paths
,私有pod在尋找標頭檔案的時候是根據自己target裡的Header Search Paths
來索引的。
我們可以看到TensorFlow-experimental.podspec裡的xcconfig
是怎麼給主工程設定的:
"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
有三個相關的引數用來配置build setting
,分別為xcconfig
(設定主工程和當前pod的build setting)、pod_target_xcconfig
(修改當前pod的build setting)、user_target_xcconfig
(修改主工程的build setting)。
所以你如果要設定你呼叫Tensorflow那個私有pod的build setting
,則需要使用pod_target_xcconfig
,比如我demo裡面在DSTensorflow.podspec
裡設定如下:
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
,因為靜態庫的連結都是主工程來進行連結,然後HEADER_SEARCH_PATHS
使用的是pod_target_xcconfig
,pod的target在尋找標頭檔案的時候是在自己的build setting
裡的HEADER_SEARCH_PATHS
裡配置的路徑進行尋找的。 -
我們在本地除錯的時候podfile裡寫的是本地podspec的路徑
pod 'DSTensorflow', :path => '../'
,所以在我們的demo裡的Pods
資料夾下面並不會有DSTensorflow
這個資料夾,而我們的HEADER_SEARCH_PATHS
和OTHER_LDFLAGS
的路徑為Pods/DSTensorflow
下的路徑,所以為了可以除錯,我就手動把那些檔案複製進了這個資料夾下面。當然如果你到時候這個私有pod正常釋出的了,使用pod 'DSTensorflow'
這樣正常依賴的話,Pods/DSTensorflow
下就會有對應的檔案了。
簡單的接入Tensorflow的Demo
Tensorflow編譯靜態庫指令碼解析
tensorflow-r1.8/tensorflow/contrib/makefile/build_all_ios.sh
這個就是建立靜態庫的指令碼,它主要有以下3個引數-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
我們之前已經說過了主要用來確定生成對應架構的靜態庫,預設為生成所有架構的靜態庫
-T
表示是否只build tensorflow的靜態庫,因為它預設需要下載一些依賴庫,工具庫來幫組build,但是如果已經下載過了,第二遍build的時候其實就不用下載了,這時候就可以用這個引數,這樣可以加快速度。
-g
表示只選擇註冊某些你這個模型需要的op操作,這個如果不選的話,你在呼叫你的模型的時候,有些模型就會報No OpKernel was registered to support Op xxx
的錯誤,表示你這個靜態庫並不支援這個op操作。
我們也可以自己呼叫指令碼來看看你的模型需要哪些op:
執行以下操作下載對應的工具:
$ bazel build --copt="-DUSE_GEMM_FOR_CONV" tensorflow/python/tools/print_selective_registration_header
執行以下操作得到你模型需要的op,並生成ops_to_register.h(tensorflow在建立靜態庫的時候就會用到這個檔案):
$ bazel-bin/tensorflow/python/tools/print_selective_registration_header --graphs=Users/you-path/graph.pb > tensorflow/core/framework/ops_to_register.h
複製程式碼
一個完整的例子如下:
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 平臺 TensorFlow 實踐:實際應用教程(附原始碼)(二) TensorFlow Mobile模型壓縮 解決No OpKernel was registered to support Op 'Less' with these attrs問題原始碼