本文章里關(guān)于 TFiwS 原理的部分摘自蓮叔的文章, 當(dāng)然, 也可以直接查看 TFiwS 的白皮書.
本文章包括 原理篇 和 實(shí)例篇.
Google在2018年3月底公布了 Swift for TensorFlow, 目前已經(jīng)開源. TensorFlow 在機(jī)器學(xué)習(xí)領(lǐng)域可以說是生態(tài)做的最好的, 已經(jīng)滿足了包括 Python, Java, Swift, Go, C等多種語言的支持.
一. 原理篇
為什么 TensorFlow 選擇 Swift ?
- Swift 開源, 性能高, 本身是靜態(tài)語言, 比較安全, 而且它開發(fā)效率高.
- 在Swift for TensorFlow 設(shè)計(jì)總覽這篇文章里, 詳細(xì)介紹了 TFiwS 的主要組成部分以及結(jié)合方式, TensorFlow 通過
Graph Program Extraction
算法, 可以讓開發(fā)者用Eager Execution
式的編程模型來實(shí)現(xiàn)代碼唠椭,同時(shí)保留 TensorFlow 計(jì)算圖的高性能優(yōu)勢, 由于實(shí)現(xiàn)可靠的Graph Program Extraction
算法對于編程語言的設(shè)計(jì)有很高的要求, 所以選擇了 Swift.
Swift for TensorFLow 概覽
Swift for TensorFlow 簡稱 TFiwS, 倒過來念就是 SwiFT.
TensorFlow 的兩種模式.
- 圖模式(Graph Mode)
import tensorflow as tf
x = tf.constant([[1,2,3], [4,5,6]])
xt = tf.transpose(x)
y = tf.matmul(x, xt)
with tf.Session() as sess:
print sess.run(y)
圖模式的一個(gè)重要特點(diǎn)是: Lazy Evaluation, 比如在執(zhí)行 xt = tf.transpose(x) 的時(shí)候服协,實(shí)際上并沒有觸發(fā)矩陣轉(zhuǎn)正的運(yùn)算榛臼,而是只是生成了一個(gè)名為”轉(zhuǎn)置”的運(yùn)算節(jié)點(diǎn)唬格,添加到了計(jì)算圖中偏陪。最后,當(dāng)執(zhí)行 sess.run(y)的時(shí)候瞧哟,所有計(jì)算才開始運(yùn)行并闲。
因此, 圖模式的性能很強(qiáng), 在計(jì)算時(shí)已經(jīng)知道了所有的計(jì)算節(jié)點(diǎn),可以做很多優(yōu)化, 但是, 可用性很差, 先建圖后計(jì)算的模式并不符合直覺八毯,而且只能使用節(jié)點(diǎn)支持的運(yùn)算來構(gòu)建計(jì)算圖. 在 Debug 的時(shí)候不能通過 print x
這樣的形式來 debug 一些中間變量(執(zhí)行到 print
的時(shí)候 x
還沒有 value)
- 快速執(zhí)行模式(Eager Execution)
import tensorflow as tf
tf.enable_eager_execution()
x = tf.constant([[1,2,3],[4,5,6]])
y = tf.matmul(x, tf.transpose(x))
print(y)
Eager 模式與圖相反搓侄,計(jì)算是立即發(fā)生的,整個(gè)過程并不會構(gòu)建圖话速。所以可以使用自然的流程控制讶踪,比如 if
語句來書寫模型,也可以在執(zhí)行的過程中插入 print x
來獲取中間值泊交,幫助我們 debug乳讥。
Eager 模式更符合直覺柱查,易于理解,易于 debug云石。但因?yàn)闆]有 lazy唉工,所以很難做優(yōu)化,性能并不好汹忠。
新增的 TFiwS 模式
let x : Tensor<Float> = [[1,2,3], [4,5,6]]
var y = Tensor<Float>(zeros: x.shape)
print(y)
if x.sum() > 100{
y = x
}else{
y = x ? x
}
print(y)
TFiwS 實(shí)現(xiàn)了一個(gè)改進(jìn)版的 Swift 編譯器淋硝,在編譯階段會自動(dòng)分析代碼中的 tensor 運(yùn)算,并翻譯成計(jì)算圖宽菜,最終由 TensorFlow Runtime 運(yùn)行計(jì)算圖.
既然是以圖的模式運(yùn)行, 為什么又能像 Eager 模式這種能獲取到中間值呢?
在上述代碼中,
- 可以理解為與 Tensor 類相關(guān)的操作都是 tensor 邏輯, 最終會生成圖, 這一部分有 TensorFlow Runtime 執(zhí)行, (在顯存中計(jì)算)
- 而代碼中非 tensor 邏輯 則是由 Swift Runtime 執(zhí)行.(本地計(jì)算)
這里的 Runtime 不是指的 OC里面runtime機(jī)制 這種, 他就是指的在運(yùn)行中.
那么這兩種運(yùn)行模式他們是怎么做數(shù)據(jù)交互的呢?
這里涉及到一個(gè)技術(shù), Program Slicing, 程序切片.
我們來看一下這份代碼, 在實(shí)際運(yùn)行中, 是怎么利用程序切片的呢.
func linear(x : FloatTensor, w : FloatTensor, b : FloatTensor) -> FloatTensor
{
let tmp = matmul(x, w)
let tmp2 = tmp + b
print(tmp2)
let tmp3 = tmp2 * magicNumberGenerateFromTensor(x: tmp2)
return tmp3
}
對于 Graph 部分
移除所有本地代碼谣膳,然后針對兩種情況做處理:
- 如果本地代碼依賴圖代碼的結(jié)果,則插入 tfop(“send”, 用于指明這一步需要將結(jié)果發(fā)送給本地代碼铅乡;
- 如果圖代碼依賴本地代碼继谚,則插入 tfop("receive"), 用于接受本地代碼發(fā)過來的結(jié)果
TensorFlow Runtime 運(yùn)行的部分變成這樣
func linear(x : FloatTensor, w : FloatTensor, b : FloatTensor) -> FloatTensor
{
let tmp = matmul(x, w)
let tmp2 = tmp + b
//REMOVED: print(tmp2)
tfop("send", tmp2)
let result = tfop("receive")
// REMOVED: magicNumberGenerateFromTensor(x: tmp2)
let tmp3 = tmp2 * result
return tmp3
}
對于非 tensor 邏輯部分
- 刪除圖相關(guān)代碼,針對依賴/被依賴的部分插入 send/receive 操作阵幸,
- 區(qū)別只是需要在開頭插入啟動(dòng)圖代碼花履,以及最終從圖的部分拿到結(jié)果返回。
Swift Runtime 運(yùn)行的部分變成這樣.
func linear(x : FloatTensor, w : FloatTensor, b : FloatTensor) -> FloatTensor
{
let tensorProgram = startTensorGraph(graphName: "GeneratedGraphName")
//REMOVED: let tmp = matmul(x, w)
//REMOVED: let tmp2 = tmp + b
let tmp2 = receivedFromTensorFlow(tensorProgram)
print(tmp2)
let result = magicNumberGenerateFromTensor(x: tmp2)
sendToTensorFlow(tensorProgram, result)
let tmp3 = finishTensorGraph(handle: tensorProgram)
//REMOVED: let tmp3 = tmp2 * magicNumberGenerateFromTensor(x: tmp2)
return tmp3
}
從一份代碼, 切片成兩份, 將 graph 部分編譯成圖, 最終結(jié)果如圖.
整個(gè)執(zhí)行過程:
- 執(zhí)行本地 startTensorGraph挚赊,觸發(fā)圖開始計(jì)算诡壁;
- TensorFlow Runtime 開始計(jì)算圖;
- 計(jì)算完畢 wx + b 后荠割,發(fā)現(xiàn)有SEND 節(jié)點(diǎn)欢峰,于是將結(jié)果發(fā)給本地;
- 本地代碼收到結(jié)果涨共,如代碼所寫纽帖, 執(zhí)行 print;
- 本地代碼調(diào)用函數(shù),計(jì)算 magicNumber举反,并將結(jié)果發(fā)送給 TF Runtime懊直;
- TF Runtime 收到 magic 后,開始結(jié)算最終的結(jié)果 tmp3 火鼻,并發(fā)回到本地室囊;
- 本地收到 tmp3 ,返回結(jié)果魁索。
由此實(shí)現(xiàn)了: 寫 eager 的代碼融撞,但跑起來具備圖的性能,并且像 eager 模式一樣支持本地的控制流程和用 print 進(jìn)行 debug.
至于 SEND節(jié)點(diǎn) 和 RECV節(jié)點(diǎn), 這是讓TensorFlow 做分布式計(jì)算的, 在不同的機(jī)器上同步結(jié)果.
二. 實(shí)例篇
在 TensorFlow 的 Swift 項(xiàng)目里, 我們能看到關(guān)于 TFiwS 的全部內(nèi)容, 包括原理, 安裝方式, 運(yùn)行方式, 作為一個(gè)開源沒多久的項(xiàng)目, 在使用的過程中, 還是可能會出現(xiàn)各種奇怪的問題.
安裝 TFiwS
在這個(gè)界面, 下載最新的預(yù)編譯包, 如果你的顯卡是N卡, 帶有GPU的話, 可以選擇安裝 Xcode 10 (CUDA GPU), 為了簡便, 也可以只安裝普通版. 下面的步驟是針對普通版的.
安裝成功后, 我們可在這個(gè)文件夾里看到我們安裝的 toolchain.
/Library/Developer/Toolchains/
這個(gè) toolchain 里包括 Swift 編譯器, lldb, 以及其他相關(guān)工具的副本, 可以用來運(yùn)行 TensorFlow 相關(guān)的項(xiàng)目.
-
在 Xcode -> Preferences -> Components -> Toolchains, 可以看到我們安裝的 toolchain. 鼠標(biāo)右鍵對應(yīng)的 toolchain, 可以查看原目錄.
image.png 為了能在 Terminal 里能使用 Swift toolchain, 我們這樣做
$ export PATH=/Library/Developer/Toolchains/swift-latest/usr/bin:"${PATH}"
上面是在添加環(huán)境變量, 使之能快速訪問到 Swift for TensorFlow toolchain.
到這里如果沒問題的話, 就可以使用 TFiwS.
- 在 Terminal 中, 直接輸入
swift
, 如果沒報(bào)錯(cuò), 就OK了.
如果報(bào)錯(cuò), 顯示 找不到 TensorFlow.
解決辦法:
swiftenv
環(huán)境管理工具看這里
1. 在 Terminal 中直接輸入
$ /Library/Developer/Toolchains/swift-tensorflow-DEVELOPMENT-2018-09-17-a.xctoolchain/usr/bin/swift
進(jìn)入 Swift 開發(fā)環(huán)境后, 輸入 import TensorFlow,
如果沒報(bào)錯(cuò), 就說明是 環(huán)境變量 的問題.
2. 解決環(huán)境變量問題, 安裝 [swiftenv](https://swiftenv.fuller.li/en/latest/)
大部分人都應(yīng)該使用過 Homebrew,
安裝, 以及 配置環(huán)境變量
$ brew install kylef/formulae/swiftenv
$ echo 'if which swiftenv > /dev/null; then eval "$(swiftenv init -)"; fi' >> ~/.bash_profile
3. 顯示本機(jī) Swift 版本
$ swiftenv versions
4. 添加我們新安裝的 toolchain 全局可用.
$ swiftenv global tensorflow-DEVELOPMENT-2018-09-17-a
如果以后想切換Swift版本也是同理
使用TFiwS
到這里我們基本上就可以使用 TFiwS 了, TFiwS官方提供了一個(gè)練手項(xiàng)目swift-models, 這里面有一個(gè) MNIST 項(xiàng)目可以體驗(yàn)一下.
一些注意點(diǎn)
- 我們安裝的這個(gè) toolchain 只支持 macOS開發(fā), 不支持iOS/tvOS/watchOS.
-
我們可以使用 Swift Playground 編寫代碼, 為了編譯不報(bào)錯(cuò), 我們需要切換 toolchain, 如果你使用的不是 Xcode 的默認(rèn) toolchain, 會有一個(gè)鎖鏈的圖標(biāo).
image.png.
- Playground 里面寫的代碼只會有代碼提示, 是不能直接運(yùn)行的. 我目前是這樣. 要運(yùn)行我們寫的代碼, 直接在對應(yīng)的文件目錄下
$ swift -O TFiwS_MNIST.swift
- 如果你想在 Xcode 里面寫自己的 TensorFlow 代碼, 需要配置 Xcode 項(xiàng)目. 可以參考官方的 instructions
有幾點(diǎn)需要注意:
- 你創(chuàng)建的是 macOS 的 Command Line Tool 項(xiàng)目, 不是 Cocoa App 項(xiàng)目.
- 配置對應(yīng)項(xiàng)目的編譯系統(tǒng), 進(jìn)入 Xcode , File -> Project Settings -> Build System -> 選擇 legacy Build System
- 在對應(yīng)項(xiàng)目的 target 里, 設(shè)置 Build Settings
3.1. 設(shè)置 Optimization Level
Build Settings → Swift Compiler-Code Generation → Optimization Level
3.2. 添加 tensorflow 靜態(tài)庫.
在上面我們能找到新安裝的 toolchain 的文件目錄, 打開包內(nèi)容, 在這個(gè)目錄下, 直接拖 libtensorflow.so
和libtensorflow_framework.so
到 Linked Frameworks and Libraries
General -> Linked Frameworks and Libraries
/Library/Developer/Toolchains/xxxxx.xctoolchain/usr/lib/swift/macosx
3.3. 改變 Runtime Search Paths
Build Settings → Linking → Runpath Search Path:
這里我們可以直接添加/Library/Developer/Toolchains/swift-latest/usr/lib/swift/macosx
2.4. 設(shè)置 -lpython
Built Setting -> linking -> Other Linker Flags 添加 -lpython
設(shè)置完, 就可以 Xcode 編寫 TF 代碼, 有代碼提示.
swift -O xx.swift
如果直接使用 Playground 是不需要添加 libtensorflow.so
和libtensorflow_framework.so
這些操作的, 只需要修改 toolchain , 出現(xiàn) 一個(gè)藍(lán)色鐵索 的圖標(biāo)就可以保證編寫代碼時(shí)自帶提示了.
如果你需要在 jupyter notebook
里使用 TFiwS. 文檔請參考這里, 這里有幾點(diǎn)注意.
- 目前只支持 python2 環(huán)境下運(yùn)行, 強(qiáng)烈建議安裝 Anaconda 大禮包, 很方便管理環(huán)境.
- 創(chuàng)建完 python2 的環(huán)境后, 激活環(huán)境,
source activate env_name
- 按照前面的官方文檔安裝第三方庫, 包括 ipykernel pandas matplotlib numpy.
- 將項(xiàng)目 clone 到本地后, 進(jìn)入到 register.py 所在目錄下. 執(zhí)行以下命令, register.py 里面是一些腳本指令
python register.py --sys-prefix --swift-toolchain <path to extracted swift toolchain directory>
- 如果你一時(shí)找不到 toolchain 的目錄, 可以 直接在 Xcode 里偏好設(shè)置里面, 找到 Components里面 toolchain 一欄, 鼠標(biāo)右鍵可以查找 show in Finder.