本文章里关于 TFiwS 原理的部分摘自莲叔的文章, 当然, 也可以直接查看 TFiwS 的白皮书.
本文章包括 原理篇 和 实例篇.
Google在2018年3月底公布了 Swift for TensorFlow, 目前已经开源. TensorFlow 在机器学习领域可以说是生态做的最好的, 已经满足了包括 Python, Java, Swift, Go, C等多种语言的支持.
一. 原理篇
为什么 TensorFlow 选择 Swift ?
- Swift 开源, 性能高, 本身是静态语言, 比较安全, 而且它开发效率高.
- 在Swift for TensorFlow 设计总览这篇文章里, 详细介绍了 TFiwS 的主要组成部分以及结合方式, TensorFlow 通过
Graph Program Extraction
算法, 可以让开发者用Eager Execution
式的编程模型来实现代码,同时保留 TensorFlow 计算图的高性能优势, 由于实现可靠的Graph Program Extraction
算法对于编程语言的设计有很高的要求, 所以选择了 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)
图模式的一个重要特点是: Lazy Evaluation, 比如在执行 xt = tf.transpose(x) 的时候,实际上并没有触发矩阵转正的运算,而是只是生成了一个名为”转置”的运算节点,添加到了计算图中。最后,当执行 sess.run(y)的时候,所有计算才开始运行。
因此, 图模式的性能很强, 在计算时已经知道了所有的计算节点,可以做很多优化, 但是, 可用性很差, 先建图后计算的模式并不符合直觉,而且只能使用节点支持的运算来构建计算图. 在 Debug 的时候不能通过 print x
这样的形式来 debug 一些中间变量(执行到 print
的时候 x
还没有 value)
- 快速执行模式(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 模式与图相反,计算是立即发生的,整个过程并不会构建图。所以可以使用自然的流程控制,比如 if
语句来书写模型,也可以在执行的过程中插入 print x
来获取中间值,帮助我们 debug。
Eager 模式更符合直觉,易于理解,易于 debug。但因为没有 lazy,所以很难做优化,性能并不好。
新增的 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 实现了一个改进版的 Swift 编译器,在编译阶段会自动分析代码中的 tensor 运算,并翻译成计算图,最终由 TensorFlow Runtime 运行计算图.
既然是以图的模式运行, 为什么又能像 Eager 模式这种能获取到中间值呢?
在上述代码中,
- 可以理解为与 Tensor 类相关的操作都是 tensor 逻辑, 最终会生成图, 这一部分有 TensorFlow Runtime 执行, (在显存中计算)
- 而代码中非 tensor 逻辑 则是由 Swift Runtime 执行.(本地计算)
这里的 Runtime 不是指的 OC里面runtime机制 这种, 他就是指的在运行中.
那么这两种运行模式他们是怎么做数据交互的呢?
这里涉及到一个技术, Program Slicing, 程序切片.
我们来看一下这份代码, 在实际运行中, 是怎么利用程序切片的呢.
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 部分
移除所有本地代码,然后针对两种情况做处理:
- 如果本地代码依赖图代码的结果,则插入 tfop(“send”, 用于指明这一步需要将结果发送给本地代码;
- 如果图代码依赖本地代码,则插入 tfop("receive"), 用于接受本地代码发过来的结果
TensorFlow Runtime 运行的部分变成这样
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 逻辑部分
- 删除图相关代码,针对依赖/被依赖的部分插入 send/receive 操作,
- 区别只是需要在开头插入启动图代码,以及最终从图的部分拿到结果返回。
Swift Runtime 运行的部分变成这样.
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 部分编译成图, 最终结果如图.
整个执行过程:
- 执行本地 startTensorGraph,触发图开始计算;
- TensorFlow Runtime 开始计算图;
- 计算完毕 wx + b 后,发现有SEND 节点,于是将结果发给本地;
- 本地代码收到结果,如代码所写, 执行 print;
- 本地代码调用函数,计算 magicNumber,并将结果发送给 TF Runtime;
- TF Runtime 收到 magic 后,开始结算最终的结果 tmp3 ,并发回到本地;
- 本地收到 tmp3 ,返回结果。
由此实现了: 写 eager 的代码,但跑起来具备图的性能,并且像 eager 模式一样支持本地的控制流程和用 print 进行 debug.
至于 SEND节点 和 RECV节点, 这是让TensorFlow 做分布式计算的, 在不同的机器上同步结果.
二. 实例篇
在 TensorFlow 的 Swift 项目里, 我们能看到关于 TFiwS 的全部内容, 包括原理, 安装方式, 运行方式, 作为一个开源没多久的项目, 在使用的过程中, 还是可能会出现各种奇怪的问题.
安装 TFiwS
在这个界面, 下载最新的预编译包, 如果你的显卡是N卡, 带有GPU的话, 可以选择安装 Xcode 10 (CUDA GPU), 为了简便, 也可以只安装普通版. 下面的步骤是针对普通版的.
安装成功后, 我们可在这个文件夹里看到我们安装的 toolchain.
/Library/Developer/Toolchains/
这个 toolchain 里包括 Swift 编译器, lldb, 以及其他相关工具的副本, 可以用来运行 TensorFlow 相关的项目.
-
在 Xcode -> Preferences -> Components -> Toolchains, 可以看到我们安装的 toolchain. 鼠标右键对应的 toolchain, 可以查看原目录.
为了能在 Terminal 里能使用 Swift toolchain, 我们这样做
$ export PATH=/Library/Developer/Toolchains/swift-latest/usr/bin:"${PATH}"
上面是在添加环境变量, 使之能快速访问到 Swift for TensorFlow toolchain.
到这里如果没问题的话, 就可以使用 TFiwS.
- 在 Terminal 中, 直接输入
swift
, 如果没报错, 就OK了.
如果报错, 显示 找不到 TensorFlow.
解决办法:
swiftenv
环境管理工具看这里
1. 在 Terminal 中直接输入
$ /Library/Developer/Toolchains/swift-tensorflow-DEVELOPMENT-2018-09-17-a.xctoolchain/usr/bin/swift
进入 Swift 开发环境后, 输入 import TensorFlow,
如果没报错, 就说明是 环境变量 的问题.
2. 解决环境变量问题, 安装 [swiftenv](https://swiftenv.fuller.li/en/latest/)
大部分人都应该使用过 Homebrew,
安装, 以及 配置环境变量
$ brew install kylef/formulae/swiftenv
$ echo 'if which swiftenv > /dev/null; then eval "$(swiftenv init -)"; fi' >> ~/.bash_profile
3. 显示本机 Swift 版本
$ swiftenv versions
4. 添加我们新安装的 toolchain 全局可用.
$ swiftenv global tensorflow-DEVELOPMENT-2018-09-17-a
如果以后想切换Swift版本也是同理
使用TFiwS
到这里我们基本上就可以使用 TFiwS 了, TFiwS官方提供了一个练手项目swift-models, 这里面有一个 MNIST 项目可以体验一下.
一些注意点
- 我们安装的这个 toolchain 只支持 macOS开发, 不支持iOS/tvOS/watchOS.
-
我们可以使用 Swift Playground 编写代码, 为了编译不报错, 我们需要切换 toolchain, 如果你使用的不是 Xcode 的默认 toolchain, 会有一个锁链的图标.
.
- Playground 里面写的代码只会有代码提示, 是不能直接运行的. 我目前是这样. 要运行我们写的代码, 直接在对应的文件目录下
$ swift -O TFiwS_MNIST.swift
- 如果你想在 Xcode 里面写自己的 TensorFlow 代码, 需要配置 Xcode 项目. 可以参考官方的 instructions
有几点需要注意:
- 你创建的是 macOS 的 Command Line Tool 项目, 不是 Cocoa App 项目.
- 配置对应项目的编译系统, 进入 Xcode , File -> Project Settings -> Build System -> 选择 legacy Build System
- 在对应项目的 target 里, 设置 Build Settings
3.1. 设置 Optimization Level
Build Settings → Swift Compiler-Code Generation → Optimization Level
3.2. 添加 tensorflow 静态库.
在上面我们能找到新安装的 toolchain 的文件目录, 打开包内容, 在这个目录下, 直接拖 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. 设置 -lpython
Built Setting -> linking -> Other Linker Flags 添加 -lpython
设置完, 就可以 Xcode 编写 TF 代码, 有代码提示.
swift -O xx.swift
如果直接使用 Playground 是不需要添加 libtensorflow.so
和libtensorflow_framework.so
这些操作的, 只需要修改 toolchain , 出现 一个蓝色铁索 的图标就可以保证编写代码时自带提示了.
如果你需要在 jupyter notebook
里使用 TFiwS. 文档请参考这里, 这里有几点注意.
- 目前只支持 python2 环境下运行, 强烈建议安装 Anaconda 大礼包, 很方便管理环境.
- 创建完 python2 的环境后, 激活环境,
source activate env_name
- 按照前面的官方文档安装第三方库, 包括 ipykernel pandas matplotlib numpy.
- 将项目 clone 到本地后, 进入到 register.py 所在目录下. 执行以下命令, register.py 里面是一些脚本指令
python register.py --sys-prefix --swift-toolchain <path to extracted swift toolchain directory>
- 如果你一时找不到 toolchain 的目录, 可以 直接在 Xcode 里偏好设置里面, 找到 Components里面 toolchain 一栏, 鼠标右键可以查找 show in Finder.