欢迎光临

我们一直在努力
当前位置:首页 > 欧洲杯科技

欧洲杯足彩谷歌为何要养苹果的亲儿子Swift?原来意在可微分编程

日期: 来源:编辑:佚名
选自tryolabs作者:JoaquínThu机器之心编译参与:Panda、RacoonPython 并不完美,而 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..<15 { start = time.time() | let start = CFAbsoluteTimeGetCurrent() for _ in range(3000): | for _ in 0..<3000 { 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<Int>(repeating: 0, count: 3001)for it in 0..<15 { let start = CFAbsoluteTimeGetCurrent() // Using a buffer pointer for assignment result.withUnsafeMutableBufferPointer({ buffer infor i in 0..<3000 { 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

相关阅读

  • 欧洲锦标赛青春办公有神器,科大讯飞智能笔记本

  •   今年疫情,平板脱销,很多小朋友都要用平板来上课,可以说大大推动了平板产品的场景化发展。不过对于成年人来说,可能就很少有人用平板办公了,大部分原因是作为生产力工具功能不够强大,而作为轻办公工具又不够方便和便携,反而经...
  • 直播欧洲杯到底谁给New IP强加了“政治标签”?

  •   近日,英国《金融时报》发表了一篇名为《中国华为提议重塑互联网》的文章,文章指出中国正在利用国际电信联盟(ITU)向全世界强加一个全新的“新互联网”标准,意图输出中国制度。这种带有政治倾向的报道有失公允,被外媒解读为...
  • 欧洲锦标赛战"疫"中的科技助攻:AI机器人扮演何种角色?

  •   回首过去的半个月,新冠病毒成为中国乃至全球难以忘却的伤痛。短短几周时间,确诊人数已突破4万关口,超过摩纳哥全国人口,其中死亡814例。困境中总不乏英雄主义。一面是持续上升的确诊人数,另一面则是各个层面不断涌现的英勇...

聚合标签

欧洲杯直播 欧洲足球锦标赛直播 2020欧洲杯外围买球 欧洲杯足彩竞彩 2020欧洲杯在哪买球 2020欧洲杯竞猜网站 2020欧洲杯买球官网 2020欧洲杯买球官方平台 2020欧洲杯官方平台 欧洲杯外围买球网站 欧洲杯竞彩盘口 欧洲杯投注网站 欧洲杯足球竞猜盘口 2020欧洲杯官网 欧洲杯预选赛直播 欧洲杯足球竞猜网站 2020欧洲杯赌球官网 手机看欧洲杯直播 欧洲杯预选赛积分榜 2020欧洲杯买球 2020欧洲杯买球网站 2020欧洲杯怎么下注 2020欧洲杯外围竞猜 2020欧洲杯竞彩官网 2020欧洲杯决赛竞猜官网 直播欧洲杯 2020欧洲杯 2020欧洲杯预选赛 2020欧洲杯外围投注 欧洲杯外围 欧洲杯足球投注 欧洲杯视频 2020欧洲杯盘口 2020欧洲杯视频 欧洲杯官方网站 2020欧洲杯怎么买球 欧洲杯投注官网 2020欧洲杯竞彩网站 欧洲杯买球 欧锦赛网投 看欧洲杯 欧洲杯决赛竞猜 2020欧洲杯足彩 2020欧洲杯投注官网 欧洲杯2020 2020欧洲杯体彩 欧洲杯高清直播 2020欧洲杯竞猜 欧洲杯app 欧洲杯直播app 欧洲杯决赛 欧洲杯竞彩 欧洲杯投注app 2020欧洲杯如何买球 欧洲杯投注网 欧洲杯比分直播 欧洲杯买球网址 欧洲杯投注 看欧洲杯直播 欧洲杯直播平台 2020欧洲杯官方 欧洲国家杯 欧洲杯哪里买球 欧洲锦标赛 欧洲足球锦标赛 欧洲杯竞猜 欧锦赛 2020欧洲杯外围盘口 欧洲杯体彩 欧洲杯买球平台 欧洲杯官网 欧洲杯外围竞猜 2020欧洲杯足球投注平台 2020欧洲杯竞彩 欧洲杯直播频道 欧洲杯冠军 欧洲杯比分 欧洲杯决赛盘口 2020欧洲杯投注 欧洲杯足彩 欧洲杯赌球 欧洲杯 欧洲杯录播 欧洲杯手机直播 欧洲杯赔率 欧洲杯如何投注 欧锦赛投注 欧洲杯下注 欧洲杯2020直播 欧洲杯足球竞猜 欧洲杯投注平台 欧锦赛直播 2020欧洲杯外围 欧洲杯盘口 欧洲杯预选赛 2020欧洲杯直播 2020欧洲杯体彩官网 2020欧洲杯投注网站 欧洲杯手机竞猜 欧洲杯彩票 欧洲杯回放 欧锦赛盘口 2020欧锦赛 欧洲杯app下载 欧洲杯足球彩票 2020年欧洲杯 欧洲杯猜球 欧洲杯视频直播 欧洲杯冠军竞猜 欧洲杯录像 欧洲杯网上投注 2020欧洲杯赔率 2020足球欧洲杯 欧洲杯猜球网站 欧洲杯赛程 欧洲杯在线直播

热门文章

  • 欧洲杯视频值得推广的5项数据分析投资

  •   点击上方“蓝色字体”,选择 “设为星标”关键讯息,D1时间送达!分析技术常常被夸大为一种能够实现变革的魔法。但以下的这五项新兴分...
  • 2020欧锦赛刚刚,中国电信发布重要公告!

  •   中国电信通信保障倡议书尊敬的各乡镇(街办)、村(社区): 疫情当前,通信网络与水电一样是人民群众生活中的必需品,更是防疫一线的重要保障...

最新文章