首页 » 智能 » 谷歌为何要养苹果的亲儿子Swift?原来意在可微分编程_措辞_代码

谷歌为何要养苹果的亲儿子Swift?原来意在可微分编程_措辞_代码

少女玫瑰心 2024-11-11 00:52:50 0

扫一扫用手机浏览

文章目录 [+]

作者:JoaquínThu

机器之心编译

谷歌为何要养苹果的亲儿子Swift?原来意在可微分编程_措辞_代码 智能

参与:Panda、Racoon

Python 并不完美,而 Swift 则正在谷歌和苹果的共同养育下茁壮发展,有望发展为深度学习领域一门新的紧张措辞。
近日,Tryolabs 的研究工程师 Joaquín Alori 发布了一篇长文,从 Python 的缺陷一起谈到了谷歌在 Swift 机器学习方面的大操持,并且文中还给出了相称多一些详细的代码实例。
可微分编程真如 Yann LeCun 所言的那样会成为新一代的程序开拓范式吗?Swift 又将在个中扮演若何的角色?大概你能在这篇文章中找到答案。

近日,国外一小哥在 tryolabs 上写了一篇博文,为我们详尽地先容了 Python 的毛病与比较之下 Swift 的上风,阐明了为什么 Swift 版的 TensorFlow 未来在机器学习领域有非常好的发展前景。
个中包含大量代码示例,展示了如何用 Swift 优雅地编写机器学习程序。

两年之前,谷歌的一个小团队开始研究让 Swift 措辞成为首个在措辞层面上一流地整合了可微分编程能力的主流措辞。
该项目的研究范围其实分歧凡响,而且也取得了一些出色的初期研究成果,彷佛离"大众年夜众运用也并不很远了。

只管如此,该项目却并未在机器学习社区引起多大反响,而且很多实践者还对此浑然不觉。
造成这种结果的紧张缘故原由之一是措辞的选择。
机器学习社区的很多人很大程度上并不关心 Swift,谷歌研究它也让人们感到迷惑;由于 Swift 紧张用来开拓 iOS 运用而已,在数据科学生态系统中险些毫无存在感。

不过,事实却并非如此,只需粗略地看看谷歌这个项目,就能创造这是一个弘大且年夜志勃勃的操持,乃至足以将 Swift 确立为机器学习领域的关键成员。
此外,纵然我们 Tryolabs 也紧张利用 Python,但我们还是认为 Swift 是一个绝佳的选择;也因此,我们决定写这篇文章以帮助众人理解谷歌的操持。

但在深入 Swift 以及「可微分编程」的真正含义之前,我们该当先回顾一下当前的状况。

Python,你怎么了?!

到目前为止,Python 都依然是机器学习领域最常被利用的措辞,谷歌也有大量用 Python 编写的机器学习软件库和工具。
那么,为什么还要用 Swift?Python 有什么问题吗?

直接说吧,Python 太慢了。
其余,Python 的并行性表现并不好。

为了应对这些缺陷,大多数机器学习项目在运行打算密集型算法时,都会利用用 C/C++/Fortran/CUDA 写的软件库,然后再利用 Python 将不同的底层运算组合到一起。
对付大部分项目而言,这种做法实在效果很好;但总体概况而言,这会产生一些问题。
我们先看看个中一些问题。

外部二进制文件

为每个打算密集型运算都调用外部二进制文件会限定开拓者的事情,让他们只能在算法的表层的一小部分上进行开拓。
比如,编写自定义的卷积实行办法是无法实现的,除非开拓者乐意利用 C 等措辞来进行开拓。
大部分程序员都不会选择这么做,要么是由于他们没有编写低层高性能代码的履历,要么则是由于在 Python 开拓环境与某个低层措辞环境之间来回切换会变得过于麻烦。

这会造成一种不幸的情形:程序员会尽力只管即便少地写繁芜代码,并且默认情形更方向于调用外部软件库的运算。
对付机器学习这样动态发展的领域来说,这并不是一个好征象,由于很多东西都还并未确定下来,还非常须要新想法。

对软件库的抽象理解

让 Python 代码调用更低层代码并不如将 Python 函数映射成 C 函数那么大略。
不幸的现实是:机器学习软件库的创建者必须为了性能而做出一些开拓上的选择,而这又会让事情变得更加繁芜。
举个例子,在 TensorFlow 图(graph)模式中(这是该软件库中唯一的性能模式),你的 Python 代码在你认为会运行时常常并不运行。
在这里,Python 实际上的浸染是底层 TensorFlow 图的某种元编程(metaprogramming)措辞。

其开拓流程为:开拓者首先利用 Python 定义一个网络,然后 TensorFlow 后端利用该定义来构建网络并将其编译为一个 blob,而开拓者却再也无法访问其内部。
编译之后,该网络才终于可以运行,开拓者可以开始向其馈送数据以便演习和推理。
这种事情办法让调试事情变得非常困难,由于在网络运行时,你没法利用 Python 理解个中究竟发生了什么。
你也没法利用 pdb 等方法。
纵然你想利用古老但好用的 print 调试方法,你也只能利用 tf.print 并在你的网络中构建一个 print 节点,这又必须连接到网络中的另一个节点,而且在 print 得到任何信息之前还必须进行编译。

不过也存在更加直接的办理方案。
用 PyTorch 时,你的代码必须像用 Python 一样命令式地运行,唯一不透明的情形是运行在 GPU 上的运算是异步式地实行的。
这常日不会有问题,由于 PyTorch 对此很智能,它会等到用户交互操作所依赖的所有异步调用都结束之后才会转让掌握权。
只管如此,也还是有一些问题存在,尤其是在基准评测(benchmarking)等任务上。

行业滞后

所有这些可用性问题不仅让写代码更困难,而且还会导致家当界毫无必要地滞后于学术界。
一贯以来都有论文在研究如何调度神经网络中所用的低层运算,并在这一过程中将准确度提升几个百分点,但是家当界仍旧须要很永劫光才能实际运用这些进展。

一个缘故原由是纵然这些算法上的改变可能本身比较大略,但上面提到的工具问题还是让它们非常难以实现。
因此,由于这些改进可能只能将准确度提升 1%,以是企业可能会认为为此进行投入并不值得。
对付小型机器学习开拓团队而言,这个问题尤为明显,由于他们每每缺少可包袱实现/整合本钱的规模经济。

因此,企业每每会直接忽略这些进步,直到这些改进被加入到 PyTorch 或 TensorFlow 等软件库中。
这能节省企业的实现和整合本钱,但也会导致家当界滞后学术界一两年韶光,由于这些软件库的掩护者基本不会立即实现每篇新论文提出的新方法。

举个详细的例子,可变形卷积彷佛可以提升大多数卷积神经网络(CNN)的性能表现,但论文发布大概 2 年之后才涌现第一个开源的实现。
不仅如此,将可变形卷积的实现整合进 PyTorch 或 TensorFlow 的过程非常麻烦,而且末了这个算法也并没得到广泛的利用。
PyTorch 直到最近才加入对它的支持,至于官方的 TensorFlow 版本,至今仍没有见到。

现在,假设说有 n 篇能将准确度提升 2% 的论文都碰着了这种情形,那么家当界将错失落准确度显著提升 (1.02^n)% 的机会,而缘故原由不过是没有得当的工具罢了。
如果 n 很大,那就太让人遗憾了。

速率

在某些情形中,同时利用 Python 与快速软件库依然还是会很慢。
确实,如果是用 CNN 来实行图像分类,那么利用 Python 与 PyTorch/TensorFlow 会很快。
此外,就算在 CUDA 环境中编写全体网络,性能也可能并不会得到太多提升,由于大卷积霸占了大部分的推理韶光,而大卷积又已经有了经由良好优化的代码实现。
但情形并非总是如此。

如果不是完备用低层措辞实现的,那么由很多小运算组成的网络每每最随意马虎涌现性能问题。
举个例子,Fast.AI 的 Jeremy Howard 曾在一篇博客文章中表达了自己对用 Swift 来做深度学习开拓的热爱,他表示只管利用了 PyTorch 那出色的 JIT 编译器,他仍旧无法让 RNN 的事情速率比肩完备用 CUDA 实现的版本。

此外,对付延迟程度很主要的情形,Python 也不是一种非常好的措辞;而且 Python 也不能很好地运用于与传感器通信等非常底层的任务。
为理解决这个问题,一些公司的做法是仅用 Python 和 PyTorch/TensorFlow 开拓模型。
这样,在实验和演习新模型时,他们就能利用 Python 的易用性上风。
而在之后的生产支配时,他们会用 C++ 重写他们的模型。
不愿定他们是会完备重写,还是会利用 PyTorch 的 tracing 功能或 TensorFlow 的图模式来大略地将其串行化,然后再环绕它利用 C++ 来重写 Python。
不管是哪种办法,都须要重写大量 Python 代码。
对付小公司而言,这样做每每本钱过高。

所有这些问题都是众所周知的。
公认的深度学习教父之一 Yann LeCun 就曾说机器学习须要一种新措辞。
他与 PyTorch 的创建者之一 Soumith Chintala 曾在一组推文中谈论了几种可能的候选措辞,个中提到了 Julia、Swift 以及改进 Python。
另一方面,Fast.AI 的 Jeremy Howard 彷佛已经下定决心站队 Swift。

谷歌接管了寻衅

幸运的是,谷歌的 Swift for TensorFlow(S4TF)团队接过了这一难题。
不仅如此,他们的全体项目进展还非常透明。
他们还发布了一份非常详确的文档(https://github.com/tensorflow/swift/blob/master/docs/WhySwiftForTensorFlow.md),个中详细地先容了他们做出这一决定的进程,并阐明了他们为这一任务考虑过的其它措辞并终极选中 Swift 的缘故原由。

在他们考虑过的措辞中,最值得关注的包括:

Go:在这份文档中,他们表示 Go 过于依赖其接供词给的动态调度,而且如果要实现他们想要的特性,必须对这门措辞进行大刀阔斧的修正。
这与 Go 措辞的保持大略和小表面积的哲学不符。
相反,Swift 的协议和扩展都有很高的自由度:你想要调度有多静态,就能有多静态。
其余,Swift 也相称繁芜,而且还在越来越繁芜,以是再让它繁芜点以知足谷歌想要的特性并不是什么大问题。

C++ 和 Rust:谷歌的目标用户群是那些大部分事情都利用 Python 的人,他们更感兴趣的是花韶光思考模型和数据,而不是思考如何风雅地管理内存或所有权(ownership)。
Rust 和 C++ 的繁芜度都足够,但都很看重底层细节,而这在数据科学和机器学习开拓中常日是不合理的。

Julia:如果你在 HackerNews 或 Reddit 上读到过任何有关 S4TF 的帖子,那么最常看到的评论是:「为啥不选 Julia?」在前面提到的那份文档中,谷歌提到 Julia 看起来也很有潜力,但他们并未给出不选 Julia 的靠谱情由。
他们提到 Swift 的社区比 Julia 大得多,事实确实如此,然而 Julia 的科研社区和数据科学社区却比 Swift 大得多,而这些社区的人才更可能更多地利用 S4TF。
要记住,谷歌团队的 Swift 专业人才更多,毕竟发起 S4TF 项目的正是 Swift 的创建者 Chris Lattner,相信这在谷歌的决定中起到了重大的浸染。

一种新措辞:作者认为他们在宣言中说得很好:「创建一种措辞的事情量多得吓人。
」这须要太长的韶光,而机器学习又发展得太快。

那么,Swift 的上风在哪里?

大略来说,Swift 让你可险些完备用 Python 的办法在非常高的层面上进行编程,同时又可以担保非常快的速率。
数据科学家可像利用 Python 一样来利用 Swift,同时可用 Swift 内置的已优化机器学习库来进行更加风雅的开拓,比如管理内存,乃至当常用的 Swift 代码约束太大时还能降至指针层面进行操作。

本文的目的不是先容 Swift 措辞,以是不会连篇累牍地详细先容其特性。
如果你想详细理解这门措辞,看官方文档就够了。
这里只会先容 Swift 的几个亮点,并希望这能吸引人们去考试测验它。
下面几节将按随机顺序先容 Swift 的一些亮点,以是排序与它们的主要程度无关。
之后,本文将深入先容可微分编程,并聊聊谷歌在 Swift 上的大操持。

亮点一

Swift 速率很快。
这是作者在开始利用 Swift 时所做的第一项测试。
作者写了一些短脚本来评估 Swift 与 Python 和 C 的相对表现。
说实话,这些测试并不特殊繁芜。
也便是用整型数添补一个数组,然后再将它们全部加起来。
这个测试本身并不能透彻地理解 Swift 在各种情形下的速率表现,但作者想理解的是 Swift 能否达到 C 一样的速率,而不是 Swift 是否总能和 C 一样快。

第一组比较作者选的是 Swift vs Python。
为了让对应的每一行所实行的任务同等,作者对某些地方的花括号的位置进行了调度。

import time | import Foundation|result = [] | var result = [Int]()for it in range(15): | for it in 0.. start = time.time() | let start = CFAbsoluteTimeGetCurrent() for _ in range(3000): | for _ in 0.. result.append(it) | result.append(it)} sum_ = sum(result) | let sum = result.reduce(0, +) end = time.time() | let end = CFAbsoluteTimeGetCurrent() print(end - start, sum_) | print(end - start, sum) result = [] | result = []}

只管在这个特定的代码段中,Python 与 Swift 代码看起来句法附近,但运行结果表明这个 Swift 脚本的运行速率比 Python 脚本的运行速率快 25 倍。
在这个 Python 脚本中,最外层的循环每实行一次均匀耗时 360 μs,比较之下 Swift 的是 14 μs。
差别非常明显。

其余,也还有其它一些事情值得把稳。
比如,+ 既是一个运算符也是一个函数,它会被通报给 reduce(后面我会详细先容);CFAbsoluteTimeGetCurrent 揭示了 Swift 在传承下来的 iOS 命名空间方面的怪异特性;.< 范围运算符让你可以选择该范围是否包含区间端点以及哪个端点。

但是,这个测试并不能解释 Swift 有多快。
要知道 Swift 有多快,我们得将其与 C 来比比看。
我也这样做了,但让人失落望的是,初始结果并不好。
用 C 编写的版本均匀耗时 1.5 μs,比我们的 Swift 代码快 10 倍。
Uh oh.

不过诚笃讲,这样比较实在并不公正。
这段 Swift 代码并没利用动态数组,因此当数组规模变大时,它会在内存堆中不断重新分配位置。
这也意味着它会在每个附加(append)的数组上实行边界检讨。
为了佐证这一点,我们来看看干系定义。
Swift 的标准类型包括整型、浮点数和数组,它们并没有硬编码到编译器中,而是标准库中所定义的构造体(struct)。
因此,根据数组的附加(append)定义,我们可以理解到很多信息。
知道了这一点后,我的测试办法乃至可以包括预分配数组的内存以及利用指针来填充数组。
这样得到的脚本实在也并不是很长:

import Foundation// Preallocating memoryvar result = ContiguousArray(repeating: 0, count: 3001)for it in 0.. let start = CFAbsoluteTimeGetCurrent()

// Using a buffer pointer for assignment result.withUnsafeMutableBufferPointer({ buffer infor i in 0.. buffer[i] = it } }) let sum = result.reduce(0, +) let end = CFAbsoluteTimeGetCurrent() print(end - start, sum)

这段新代码耗时 3 μs,速率已经达到 C 的一半,可以说是很不错的结果了。
不过为了进行完全的比较,作者连续对代码进行了阐发,以便理解该代码的 Swift 版本和 C 版本的差异究竟可以做到多小。
事实证明,作者之前利用的 reduce 方法会毫无必要地间策应用 nextPartialResult 函数实行一些打算,这可以供应非必需的泛化能力。
在利用指针重写了这段代码之后,作者终极让这段代码达到了与 C 同等的速率。
但是,这显然不符合我们利用 Swift 的目的,由于这种操作实质上便是写更冗长更丑陋的 C 措辞。
只管如此,知道在确实须要时可以达到 C 的速率也是一件好事。

总结:利用 Swift,你没法在实行 Python 层面的事情时得到 C 措辞等级的速率,但你能在两者之间取得良好的平衡。

亮点二

Swift 采取的函数署名方法也很有趣。
它们的最基本形式实在相称大略:

func greet(person: String, town: String) -> String { return "Hello \(person)! Glad you could visit from \(town)."}

greet(person: "Bill", town: "Cupertino")

其函数署名由参数名加它们的类型构成,没其它多余花哨的东西。
唯一不同平凡的是 Swift 须要你在调用该函数时供应参数名,因此你在调用上面的 greet 时必须写下 person 和 town,如上面代码段中末了一行所示。

当我们向个中引入参数标签时,情形还会变得更加有趣。

func greet(_ person: String, from town: String) -> String { return "Hello \(person)! Glad you could visit from \(town)."}

greet("Bill", from: "Cupertino")

顾名思义,参数标签便是函数的参数的标签,而且它们是在函数署名中各自的参数之前声明的。
在上面的示例中,from 是 town 的参数标签,_ 是 person 的参数标签。
对付末了一个标签,作者利用的是,由于 _ 在 Swift 中是一个分外字母,其含义是:「在调用这个参数时不供应任何参数名。

有了参数标签,每个参数都有两个不同的名字:一个是参数标签,在调用该函数时利用;另一个是参数名,在函数的主体定义中利用。
这看起来彷佛有些任性,但会让你的代码更易读。

看看上面的函数署名,基本就像是在读英语。
「Greet person from town.」上面的函数调用看起来也同样清楚直白:「Greet Bill from Cupertino.」如果没有参数标签,就有些含混不清了:「Greet person town.」我们不知道这里的 town 是什么意思。
这是我们现在所处的城镇吗?还是我们为了面见这个人而将要前去的城镇?又或是这个人原来来处的城镇?如果没有参数标签,我们就必须阅读函数主体才能知晓实际情形,或者采取让函数名或参数名更长更直白的方法。
如果你有大量参数,那么情形将变得非常繁芜;在作者看来这会导致代码变得更丑而且会让函数名变得毫无必要地长。
参数标签更加好看,而且也更随意马虎扩展,而且幸运的是它们也在 Swift 中得到了广泛的运用。

亮点三

Swift 广泛地利用了闭包(closure)。
因此,有一些捷径可让该措辞的利用更靠近人的直觉。
这个来自 Swift 的文档的示例展现了这些捷径简洁明了又具有很强的表现力的特性。

我们的目标是将下面的数组向后排序:

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

如果用不那么隧道的 Swift 代码形式,可为数组利用 sorted 方法,并采取一个自定义函数来定义按逐对顺序比较数组元素的办法,就像这样:

func backward(_ s1: String, _ s2: String) -> Bool { return s1 > s2}var reversedNames = names.sorted(by: backward)

backward 函数一次可比较两项,如果这两项的顺序与所需顺序一样,则返回 true;否则便返回 false。
sorted 数组方法须要这样一个函数作为一个输入才能知道如何对数组进行排序。
顺便一提,我们还可以看到这里利用了参数标签 by——这是如此的简洁明了。

如果我们采取更隧道的 Swift,可以创造利用闭包能更好地完成这项任务。

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

{} 之间的代码是一个正被定义的闭包,同时也被通报用作 sorted 的一个参数。
你大概从未听说过闭包,但实在很大略,闭包便是一个获取高下文的不决名的函数你可以将其看作是增强版的 Python lambda。
该闭包中的关键词 in 的浸染是分开该闭包的参数及其主体。
: 等更直不雅观的关键词已被署名类型定义所占用(在这个案例中,该闭包的参数类型是从 sorted 的署名中自动推导出来的,因此可以避免利用 :),而且我们都知道命名是编程中最困难的事情之一,所以为此只能连续利用不那么直不雅观的关键词了。

不管从哪个角度看,这段代码都已经简洁了许多。

但我们还可能做得更好:

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

这里我们移除了 return 语句,这是由于在 Swift 中,单行闭包就暗含了 return。

即便如此,我们还能连续更进一步:

reversedNames = names.sorted(by: { $0 > $1 } )

Swift 也有暗含的命名位置参数,以是在上面的案例中,$0 是第一个参数,$1 是第二个参数,$2 是第三个参数等等。
这个代码已经很紧凑了,而且非常随意马虎理解,但是我们乃至还能做得更好:

reversedNames = names.sorted(by: >)

在 Swift 中,> 运算符便是一个名为 > 的函数。
因此,我们可以将其通报给 sorted 方法,使我们的代码达到极度简洁和可读的程度。

这种操作适用于 +=、-=、、== 和 = 等运算符,你可以在标准库中查看它们的定义。
这些函数/运算符与普通函数之间的差异是前者已在标准库中利用 infix、prefix 或 suffix 关键词显式地声明为运算符。
举个例子,+= 函数在 Swift 标准库的这一行(https://github.com/apple/swift/blob/1ed846d8525679d2811418a5ba29405200f6e85a/stdlib/public/core/Policy.swift#L468)中被定义成了一个运算符。
可以看到,这个运算符遵照多个不同的协议,比如 Array 和 String,由于很多不同的类型都有自己的 += 函数实现。

更进一步,我们还能定义自己的自定义运算符。
GPUImage2 软件库便是一个很好的例子。
这个软件库让用户可以加载图像,利用一系列变换来修正它,然后再以某种办法来输出它。
很自然,这些变换序列的定义会在该库中不断反复涌现,因此这个库的创建者决定定义一个新的运算符 →,可用于将这些变换链接到一起。

func -->(source:T, destination:T) -> T { source.addTarget(destination) return destination}infix operator --> : AdditionPrecedence

在以上轻微简化过的代码中,首先声明了 --> 函数,然后其被定义为了一个 infix 运算符。
infix 的意思是如果要利用这个运算符,就必须将其放置在两个参数之间。
这让你可以写出如下的代码:

let testImage = UIImage(named:"WID-small.jpg")!let toonFilter = SmoothToonFilter()let luminanceFilter = Luminance()let filteredImage = testImage.filterWithPipeline{input, output in input --> toonFilter --> luminanceFilter --> output // Interesting part}

比起一大堆相互链接的方法或一长串 source.addTarget(...) 函数,上面的代码要简短和随意马虎多了。

亮点四

前面作者已经提到过,Swift 的基本类型是标准库中定义的构造体,而且并没有硬编码到编译器中,由于它们常日是用其它措辞写的。
这很有用处,一大缘故原由是让我们可以利用名叫扩展(extension)的 Swift 特性,其让我们可以向任意类型添加新特性,包括基本类型。
操作办法是这样的:

extension Double { var radians: Double { return self (Double.pi / 180) }}360.radians // -> 6.28319

只管这个例子并不是很有用,但也展示 Swift 这门措辞的扩展能力,由于这能让你做很多事情,比如向 Swift 阐明器输入任何数字以及在其上调用任何你想用的自定义方法。

末了一个亮点

除了拥有编译器之外,Swift 还具有阐明器并且支持 Jupyter Notebook。
在学习这门措辞时,阐明器尤其好用,由于它支持直接在命令提示符处输入 swift,然后立马开始代码测试。
Python 也具备差不多一样的功能。
另一方面,由于整合了 Jupyter Notebook,因此可以轻松进行可视化、实行数据探索和编写报告。
末了,当你须要运行生产代码时,你可以编译它并利用 LLVM 供应的出色优化能力。

谷歌的大操持

作者在前面的章节中提到了 Swift 的一些特性,但个中有一个特性与其它不同:Jupyter Notebook 是新加入的,而且事实上正是由 S4TF 团队加入的。
这非常值得一说,由于这能让我们一窥谷歌投入这个项目时的想法:他们不仅想为 Swift 措辞本身创建一个软件库,而且他们还想深入地改进这门措辞本身以及干系工具,然后再利用这门措辞的改进版本创建一个新的 TensorFlow 软件库。

只要看看 S4TF 团队在哪些事情上投入的韶光最多就能看出这一点。
他们到目前为止做的大部分事情都是在苹果公司的 Swift 编译器代码库本身上完成的。
更详细而言,谷歌目前完成的大部分事情都在 Swift 编译器代码库中的一个 dev 分支中。
谷歌正为 Swift 措辞本身添加新特性——他们首先会在自己的分支中创建和测试这些新特性,然后会将它们合并到苹果的主分支中。
这意味着运行在世界各地的 iOS 设备上的标准 Swift 措辞终极将能集成这些改进。

现在来谈谈更实在的东西:谷歌正为 Swift 构建什么特性?

首先说个大特性。

可微分编程

比来,可微分编程炒得确实很热。
特斯拉的人工智能卖力人 Andrej Karpathy 称之为软件 2.0(Software 2.0),Yann LeCun 乃至流传宣传:「深度学习已去世,可微分编程万岁。
」另一些人则说有必要创建一套全新的工具了,包括新的 Git、新的 IDE 以及新的编程措辞。
Wink wink.

以是,什么是可微分编程?

简而言之,可微分编程是一种程序自身可被微分的编程范式。
这让你可以设定一个你想要优化的详细目标,让你的程序可以根据这个目标自动打算自己的梯度,然后再在这个梯度的方向上优化自己。
这和演习神经网络完备一样。

如果能让程序自己优化自己,我们大概就能创造出我们自己完备无法编写出来的程序。
想想这一点还挺有趣:你的程序可以利用梯度针对特界说务优化自身,因此它的编程能力比你还强。
过去几年的发展已经表明在越来越多的案例已经涌现了这种情形,而且目前我们还看不到这一发展趋势的终点。

一种可微分的措辞

写了这么长的先容之后,终于可以谈谈谷歌为 Swift 开拓的原生可微分编程版本了。

func cube(_ x: Float) -> Float { return x x x}let cube = gradient(of: cube)cube(2) // 8.0cube(2) // 12.0

这里我们首先定义了一个大略的函数 cube,其返回的结果是输入的立方。
接下来便是激动民气的部分了:我们只需在原始函数上调用 gradient,就能创建原始函数的导数函数。
这里没有利用任何软件库或外部代码,gradient 只是由 S4TF 团队为 Swift 措辞引入的一个新函数。
该函数利用了 S4TF 团队对 Swift 内核进行的修正,可以实现梯度函数的自动打算。

这是 Swift 的一个重大新特性。
对付任意 Swift 代码,只假如可微分的,都可以自动打算梯度。
上面的代码没有导入任何东西或奇怪的依赖包,就只是纯粹的 Swift。
PyTorch、TensorFlow 或其它任何大型机器学习库都支持这一功能,但条件是你要利用特定于库的特定运算。
而且在这些 Python 库中操作梯度并不如纯挚用 Swift 那样轻量、透明,而且那些库集成也不如 Swift 原生集成那么好。

这是 Swift 措辞的一个重大新特性;而且可以说 Swift 是首个为这一特性供应原生支持的主流措辞

为了进一步解释这在实际运用中的利用办法,以下运用于一个标准机器学习演习流程的脚本更完全透彻展示了这一新特性:

struct Perceptron: @memberwise Differentiable { var weight: SIMD2 = .random(in: -1.. var bias: Float = 0

@differentiable func callAsFunction(_ input: SIMD2) -> Float { (weight input).sum() + bias }}var model = Perceptron()let andGateData: [(x: SIMD2, y: Float)] = [ (x: [0, 0], y: 0), (x: [0, 1], y: 0), (x: [1, 0], y: 0), (x: [1, 1], y: 1),]for _ in 0.. let (loss, loss) = valueWithGradient(at: model) { model -> Float invar loss: Float = 0for (x, y) in andGateData { let ŷ = model(x) let error = y - ŷ loss = loss + error error / 2 } return loss } print(loss) model.weight -= loss.weight 0.02 model.bias -= loss.bias 0.02}

同样,上面的代码完备是用 Swift 写的,不带任何依赖包。
在这段代码中,我们可以看到谷歌为 Swift 引入的两个新特性:callAsFunction 和 valueWithGradient。
第一个很大略,其浸染是实例化类和构造体,让我们可以像调用函数一样调用它们。
这里,Perceptron 构造体被实例化为了 model,然后 model 又在 let ŷ = model(x) 中被作为一个函数而调用。
在这样操作时,实际上调用的是 callAsFunction 方法。
如果你曾经用过 Keras 或 PyTorch 模型,你一定知道这是一种处理模型/层的常用办法。
但 Keras 和 PyTorch 这两个库利用了 Python 的 call 方法来实现它们各自的 call 和 forward;Swift 之前没有这样的特性,于是谷歌把它加了进去。

上面的脚本中还有一个有趣的新特性:valueWithGradient。
该函数会返回在特定点评估的函数或闭包的结果值和梯度。
在以上案例中,我们定义并用作 valueWithGradient 的输入的闭包实际上是我们的丢失函数。
这个丢失函数的输入是我们的模型,以是当我们说 valueWithGradient 会在特定的点评估我们的函数时,我们的意思是其会利用有特定权重配置的模型评估我们的丢失函数。
打算了上述的值和梯度之后,我们可以把值打印出来(这是我们的丢失)并利用梯度更新模型的权重。
重复这一过程一百次,我们就演习了一个模型。
我们还可以访问丢失函数内部的 andGateData,这是 Swift 闭包可以获取其周围高下文的又一案例。

微分外部代码

Swift 还有一个神奇的特性:我们不仅可以微分 Swift 运算,还能微分外部的、非 Swift 的软件库——只需我们在 Swift 中手动定义这些运算操作的导数。
这意味着你可以利用 C 软件库中一些非常快速的实现或一些 Swift 还不具备的运算操作。
你只需将其导入到你的项目中、编写导数代码,然后就可以在你的大型神经网络中利用这些运算操作,让反向传播等功能无缝运行。

此外,这件事做起来实在非常大略:

import Glibc // we import pow and log from herefunc powerOf2(_ x: Float) -> Float { return pow(2, x)}

@derivative(of: powerOf2)func dPowerOf2d(_ x: Float) -> (value: Float, pullback: (Float) -> Float) { let d = powerOf2(x) log(2) return (value: d, pullback: { v in v d })}

powerOf2(3), // 8gradient(of: powerOf2)(3) // 5.545

Glibc 是一个 C 软件库,因此 Swift 编译器并不知道其运算操作的导数是什么。
通过利用 @derivative,我们可以为编译器供应有关这些外部运算操作的导数的信息,然后搭配 Swift 的原生运算,可以非常轻松地构建出大型的可微分网络。
在这个示例中,我们导入了 Glibc 的 pow 和 log,并用它们创建了 powerOf2 函数及其导数。

为 Swift 开拓的新 TensorFlow 软件库确当前版本就正在利用这一特性进行开拓。
这个库从 TF Eager 软件库的 C API 导入了其所有运算操作,但其不是将 TensorFlow 的自动微分系统直接接上去,而是要指定每个根本运算操作的导数,然后再让 Swift 处理。
但是,并非所有运算都须要这种操作,由于许多运算都是更基本运算组合而成的,因此 Swift 可以自动推断它们的导数。
但由于这个库确当前版本基于 TF Eager,因此存在一个大缺陷:TF Eager 非常慢,因此 Swift 的这个版本也很慢。
这个问题该当只是暂时性的,随着与 XLA(通过 x10)和 MLIR 的整合,这个问题可以得到办理。

话虽如此,实际上 Swift TensorFlow API 已经初具规模,谷歌的开拓者已经可以利用这个 API 进行开拓了。
利用它,你可以这样演习一个大略模型:

import TensorFlowlet hiddenSize: Int = 10struct IrisModel: Layer { var layer1 = Dense(inputSize: 4, outputSize: hiddenSize, activation: relu) var layer2 = Dense(inputSize: hiddenSize, outputSize: hiddenSize, activation: relu) var layer3 = Dense(inputSize: hiddenSize, outputSize: 3)

@differentiable func callAsFunction(_ input: Tensor) -> Tensor { return input.sequenced(through: layer1, layer2, layer3) }}var model = IrisModel()let optimizer = SGD(for: model, learningRate: 0.01)let (loss, grads) = valueWithGradient(at: model) { model -> Tensor inlet logits = model(firstTrainFeatures) return softmaxCrossEntropy(logits: logits, labels: firstTrainLabels)}print("Current loss: \(loss)")

可以看到,这与之前的无导入的模型演习脚本非常相似。
它的设计非常类似 PyTorch,真是太棒了。

与 Python 的互操作性

Swift 目前仍面临的一大问题是当前的机器学习和数据科学生态系统仍处于起步阶段。
幸运的是,谷歌正在办理这个问题,其办法是为 Swift 纳入 Python 互操作性。
其想法是让开发者可在 Swift 代码中编写 Python 代码;通过这种办法,数量弘大的 Python 软件库就能为 Swift 所用了。

这种操作的一种范例用例是用 Swift 演习模型,然后用 Python 的 matplotlib 来绘制图表:

import Pythonprint(Python.version)let np = Python.import("numpy")let plt = Python.import("matplotlib.pyplot")// let time = np.arange(0, 10, 0.01)let time = Array(stride(from: 0, through: 10, by: 0.01)).makeNumpyArray()let amplitude = np.exp(-0.1 time)let position = amplitude np.sin(3 time)

plt.figure(figsize: [15, 10])

plt.plot(time, position)plt.plot(time, amplitude)plt.plot(time, -amplitude)

plt.xlabel("Time (s)")plt.ylabel("Position (m)")plt.title("Oscillations")

plt.show()

这看起来就像是纯挚的 Python 代码加了一点 let 和 var 语句。
这是由谷歌供应的一段代码示例。
作者只做了一项修正,即注释掉了一行 Python 代码,并用 Swift 对其进行了重写。
可以看到,这两者在这里竟然可以交互得如此之好。
这项任务完成起来并不如完备利用 Python 那样清晰简洁,由于我们必须利用 makeNumpyArray() 和 Array();但这种操作是可行的。

谷歌成功实现 Python 互操作性的方法是引入了 PythonObject 类型,其可表示 Python 中的任何工具。
Python 互操作性被限定在单个 Swift 软件库中,因此 S4TF 团队仅需为 Swift 措辞本身添加少量功能,比如添加少量改进以适应 Python 的极度动态性。
至于现在的 Python 支持已经达到了何种程度,目前尚不清楚他们将如何处理 with 语句等更隧道的 Python 元素,而且可以肯定地说还有其它一些极度情形有待考虑;只管如此,现在已经实现的成果就已经很不错了。

而在 Swift 与其它措辞的整合方面,作者对 Swift 的最早的兴趣点之一便是想看看它在处理实时打算机视觉任务上的表现。
由于这个缘故原由,作者终极找到了 OpenCV 的一个 Swift 版本,而通过 FastAI 的论坛,终极找到了一个大有潜力的 OpenCV 封装类(wrapper):SwiftCV。
但是,这个库很奇怪。
OpenCV 是用 C++ 构建的(并且刚刚废弃了其 C API),而 Swift 目前并不支持 C++(不过将会支持)。
因此,SwiftCV 必须将 OpenCV 代码封装在 C++ 代码的一个兼容 C 的子集中,然后再以 C 软件包的形式导入。
之后,才能将其封装到 Swift 中。

S4TF 项目的当前状态

只管作者对 S4TF 项目一贯不吝赞颂之辞,但也必须承认其还不敷以支持一样平常的生产利用。
其新的 API 仍在不断变革,这个新的 TensorFlow 库的性能也仍旧不是很好;即便其数据科学生态系统正在发展壮大,但总体仍处于起步阶段。
最主要的是,其 Linux 支持情形很奇怪,目前官方仅支持 Ubuntu。
考虑到所有这些问题,要担保所有这些问题及时得到办理,还有很多事情要做。

谷歌正在努力提升其性能,包括最近添加的 x10 以及在让 MLIR 达到标准方面所做的事情。
其余,谷歌还有一些项目致力于在 Swift 中复制许多 Python 数据科学生态系统的功能,比如 SwiftPlot、类似 Pandas 的 Penguin、类似 Scikit-learn 的 swiftML。

但最让人惊异的是苹果公司也与谷歌在同一方向上推动 Swift 的发展。
在苹果的 Swift 发展路线图上,下一个重大版本的紧张目标是在非苹果平台上建立不断发展增长的 Swift 软件生态系统。
这一目标也反响在了苹果对多个项目的支持上,比如 Swift Server Work Group、类似 numpy 的 Numerics、一个运行在 Linux 上的官方措辞做事器以及将 Swift 移植到 Windows 系统的事情。

此外,Fast.ai 的 Sylvain Gugger 也正为 FastAI 构建一个 Swift 版本,而 Jeremy Howard 也已经将 Swift 课程纳入到了他们的广受欢迎的在线课程中。
其余,第一批基于 S4TF 干系软件库的学术论文也正陆陆续续揭橥出来。

总结

在作者本人看来,只管 Swift 很有可能发展成机器学习生态系统的一大关键角色,但风险仍旧存在。
个中最大的风险是:只管 Python 存有缺陷,但对付大部分机器学习任务来说已经足够好了。
对付许多已经熟习 Python 的人来说,惯性可能太大,也没有换成另一种措辞的情由。
其余,谷歌已经不是一次两次放弃大型项目了,而 S4TF 的一些关键职员的分开也让人担忧。

给出了这些免责声明之后,作者仍旧以为 Swift 是一门很棒的措辞,这些新增的功能也极具创新性,相信它们终极能在机器学习社区找到自己的位置。
因此,如果你也想为这个潜力无穷的项目添砖加瓦,现在便是很好的机遇。
Swift 在机器学习领域的地位还远未确立,还有很多工具有待开拓。
随着 Swift 机器学习生态系统的持续发展,现在的小项目大概未来可以发展为巨大的社区项目。

原文链接:https://tryolabs.com/blog/2020/04/02/swift-googles-bet-on-differentiable-programming/

标签:

相关文章

沧州,历史与现代交融的文化名城

沧州,位于河北省东部,是一座历史悠久、文化底蕴深厚的城市。她不仅是中国三大名楼之一的“沧州楼”的所在地,更是许多历史名人、文化遗迹...

智能 2025-01-06 阅读0 评论0