JavaScript深度学习
上QQ阅读APP看书,第一时间看更新

第 1 章 深度学习和JavaScript

本章要点

● 深度学习的定义及其与人工智能和机器学习的关联。

● 深度学习从各种机器学习技术中脱颖而出以及引发“深度学习革命”的原因。

● 使用JavaScript和TensorFlow.js进行深度学习的原因。

● 本书的整体结构。

人工智能(AI)的大热不是偶然的,深度学习革命真的发生了。深度学习革命(deep-learning revolution)是指,自2012年以来深度神经网络在运行速度和相关技术方面的疾速发展。自那时起,深度神经网络被应用到了越来越多的问题上,这样一来,相比以往,计算机能够解决更多种类的问题,并极大地提高了现有解决方案的准确率(参见表1-1中的示例)。对AI方面的专家而言,神经网络领域的很多突破是令人震惊的;对使用神经网络的工程师而言,这一发展带来的机遇是鼓舞人心的。

从传统意义上讲,JavaScript是一种用于创建Web浏览器UI和后端业务逻辑(通过Node.js)的编程语言,而深度学习革命似乎是Python、R和C++这些语言的专属领域。因此,作为用JavaScript来表达想法和发挥创造力的人,你可能觉得自己有点脱离深度学习革命了。本书旨在通过叫作TensorFlow.js的JavaScript深度学习库,将深度学习与JavaScript结合起来。如此一来,无须学习新的编程语言,JavaScript开发者就可以学习如何编写深度神经网络;更重要的是,我们相信深度学习和JavaScript本就该在一起。

这就如同异花授粉,把深度学习和JavaScript结合起来,将创造出任何其他编程语言所不具备的独特功能。两者结合相得益彰:有了JavaScript,深度学习应用程序可以在更多平台上运行,接触更多受众,变得更加可视化且具有互动性;有了深度学习,JavaScript开发者可以让他们的Web应用程序更加智能。本章随后将展示如何实现这一点。

表1-1列出了目前这场深度学习革命中取得的一些令人兴奋的成就,当然,未来深度学习会持续进步。本书中选择了一些这样的应用程序,并用TensorFlow.js创建一些示例来介绍它们的实现方式。这些示例有的是完整版,有的有所简化,后面的章节会深度讲解它们。因此,无须只是感叹目前的这些成就,在本书中,你可以学习它们、理解它们,并使用JavaScript来实现它们。

但在开始学习这些令人兴奋的深度学习实战示例之前,我们需要先介绍关于AI、深度学习和神经网络的一些上下文。

表1-1 自2012年深度学习革命开始以来,由于深度学习技术而使得准确率获得极大提高的任务示例(节选) 1

1可以从图灵社区本书主页浏览并下载相关资源。——编者注

2convolutional neutral network,即convnet,卷积神经网络。

3访问Magenta.js网站的Demos界面,浏览更多示例。

a 参见何凯明等人在CVPR 2016(IEv模式识别会议)上发表的“Deep Residual Learning for Image Recognition”。

b 参见Christian Szegedy等人在CVPR 2015上发表的“Going Deeper with Convolutions”。

c 参见陈云鹏等人发表的“Dual Path Networks”。

d 参见吴永辉等人发表的“Google's Neural Machine Translation System: Bridging the Gap between Human and Machine Translation”。

e 参见Chung-Cheng Chiu等人发表的“State-of-the-Art Speech Recognition with Sequence-to-Sequence Models”。

f 参见Volodymyr Mnih等人发表的“Playing Atari with Deep Reinforcement Learning”。

g 参见David Silver等人发表的“Mastering Chess and Shogi by Self-Play with a General Reinforcement Learning Algorithm”。

h 参见Varun Gulshan等人发表的“Development and Validation of a Deep Learning Algorithm for Detection of Diabetic Retinopathy in Retinal Fundus Photographs”。

1.1 人工智能、机器学习、神经网络和深度学习

人工智能机器学习神经网络深度学习这些词虽然意思上有一定关联,但是分别代表不同的概念。为了系统而全面地掌握这些概念,需要理解它们各自的含义。下面先来定义这些术语和它们之间的关系。

1.1.1 人工智能

人工智能是一个非常宽泛的领域,它的简洁定义是:试图将通常需要人类主观意识参与的任务自动化。正因如此,人工智能涵盖了机器学习、神经网络和深度学习,但又包含了其他很多和机器学习不同的策略。例如,早期的国际象棋软件仅包含由程序员精心编写的硬编码规则,这些并不算是机器学习,因为机器只是通过明确编写的程序来解决问题,而不是通过学习数据去探索解决问题的策略。在相当长的时间内,许多专家认为,人类级别的人工智能可以通过明确制定数量足够庞大的规则集来实现,这些规则用于处理知识并做出决策。这种策略叫作符号人工智能(symbolic AI)4,它是20世纪50年代至80年代主要的人工智能范式。

4符号人工智能的一种重要类型是专家系统(expert system)。

如图1-1所示,机器学习是人工智能的子领域。人工智能的一些领域采用了与机器学习不同的策略,如符号人工智能。神经网络是机器学习的子领域。机器学习中也存在非神经网络的技术,如决策树。相较于浅层神经网络(具有较少层的神经网络),深度学习是创造与应用深层神经网络(具有大量层的神经网络)的科学与艺术。

图1-1 人工智能、机器学习、神经网络和深度学习之间的关系

1.1.2 机器学习:它和传统编程有何不同

作为人工智能的子领域之一,机器学习与符号人工智能截然不同,它源于一个问题:计算机能否超越程序员的认知,通过自主学习来完成一项具体任务?如你所见,在所采取的策略方面,机器学习和符号人工智能是有本质区别的。符号人工智能依赖于硬编码的知识和规则,而机器学习竭力避免这种硬编码。如果没有程序员精心编写的硬编码规则,机器怎样去学习完成某项任务?答案就是从数据的例子中学习。

这种思路开启了一扇通往新编程范式的大门,如图1-2所示。现在举例说明这种机器学习范式,假设你在开发一个Web应用程序,它能够处理用户上传的照片。你想做的一个功能是自动区分包含人脸的照片和不包含人脸的照片,并且据此采取不同的动作。也就是说,新创建的这个程序能够将所有输入图像(由像素数组构成)输出为二元答案,即包含人脸和不包含人脸。

图1-2 对比经典编程范式和机器学习范式

人类能够在一瞬间完成这个任务:大脑里固有的遗传因子和人生经验赋予了我们这样的能力。然而,要用编程语言(唯一可行的人机交流方式)写一套明确的规则来准确判断某个图像中是否包含人脸,对任何程序员来说,无论他聪明与否或经验多寡,这都是一件很难的事情。对于如何通过计算像素的RGB值5,来检测那些看起来像脸部、眼睛、嘴巴的椭圆轮廓,你可以花很多天进行研究,也可以设计这些轮廓之间关系的经验法则。但你会很快发现,这些尝试充斥着随意的逻辑和站不住脚的参数。更重要的是,这么做的效果还不太好6!现实中有众多因素,比如脸部大小和形状、面部表情和细节特征、发型、肤色、面部方向、有无遮挡、是否佩戴眼镜、光照环境、画面背景等,这些因素会影响判断,因此面对图像无穷的变化,你能想到的任何经验法则都会显得捉襟见肘。

5R、G、B分别代表红、绿、蓝这3种颜色。

6事实上,很多人确实尝试过这类方法,效果并不理想。参见Erik Hjelmås和Boon Kee Low所著的论文“Face Detection: A Survey”,其中提供了一些典型示例,即在深度学习出现之前,利用人工设计规则进行人脸识别。

在机器学习范式中,人工编写一套判断规则对于这样的任务是徒劳的。相反,你要先找一些图像,一部分包含人脸,一部分不包含人脸。然后为每一个图像都写下预期的正确答案,也就是包含人脸或不包含人脸,这些答案叫作标签(label)。事实上,这是一个更可控也更简单的任务。如果有很多图像,标记全部图像可能要花点时间,但是标记任务可以分发给多个人同时进行。一旦标记完毕,就可以利用机器学习技术,让机器自己探索出一套规则。经过机器学习技术正确训练的规则,执行判断的准确率能超过99%,比通过人工编写规则能取得的任何成果都要好得多。

从上面的例子可以看出,机器学习就是自动发现解决复杂问题的规则的过程。这种自动化对人脸检测这类问题很有帮助,在这类问题中,人们能直观地知道规则并很容易为数据建立标签。对于其他问题,规则就不这么直观了。比如,假设我们要预测用户是否会点击网页上显示的广告,页面内容、广告内容以及其他信息(比如浏览时间和广告位置)都是已知的。即使这样,也没有人可以对这类问题做出准确判断。即使可以,这种规律也会随着时间推移以及新的页面和广告内容的引入而发生改变。但也不用灰心,在提供广告服务的服务器日志里,带标签的可用于训练的数据是存在的,而且很容易找到。正是由于这些数据和标签,机器学习非常擅长处理此类问题。

图1-3中详细列出了机器学习所包含的步骤,其中包含两个重要阶段。第一个是训练阶段(training phase)。这一阶段的输入是数据和答案,叫作训练数据(training data)。每一对作为输入的数据和预期的答案叫作样例(example)。根据这些样例,训练流程就可以自动发现规则(rule)。尽管这些规则是自动发现的,但是它们并非凭空创造。换言之,机器学习算法并不是通过自由发挥得出这些规则,人类工程师在训练之初就提供了这些规则的蓝图。这个蓝图封装在模型(model)中,而模型又形成了机器潜在可学习的规则的假设空间(hypothesis space)。如果没有这个假设空间,机器就会在一个完全无约束并且无限大的可能规则空间中寻找规则,对于在有限时间内找到较为完善的规则,这显然是无益的。本书后面将具体描述可利用的模型的种类,以及如何根据具体问题选择最佳的模型。现在只需知道,在深度学习中,根据神经网络的组成层数、每一层的具体类型以及各层之间的连接关系,模型最终会有所不同。

图1-3 比图1-2更为详细的机器学习范式示意图。机器学习的工作流程由两个阶段构成:训练阶段和推断阶段。在训练阶段中,机器自动发现数据与对应答案之间的规则,这个过程中发现的规则会封装在训练好的模型中。它们是训练阶段的成果,并且为推断阶段奠定基础。推断阶段指运用习得的模型为新的数据获取答案

基于训练数据和模型架构,在训练阶段中,机器可以习得数据与答案之间的转换规则,并将其封装在训练好的模型(trained model)中。这个过程将规则的蓝图作为输入,并且对其进行改变(或微调),让模型输出的结果越来越逼近预期结果。根据训练数据的多少、模型架构的复杂度和硬件的快慢,这一训练阶段会持续几毫秒到几天。这种机器学习方式,也就是用带标签的样例来逐步减小模型输出误差的方法,叫作监督式学习(supervised learning)7。本书中提到的绝大部分深度学习算法属于监督式学习。一旦有了训练好的模型,就可以将习得的规则应用到新数据上了(包括训练阶段没有出现的数据)。以上就是第二阶段,即推断阶段(inference phase),这一阶段相较训练阶段计算量较少,主要有两个原因:第一,推断阶段通常每次只针对一个输入(比如图像),而训练阶段则需要处理所有的训练数据;第二,在推断阶段,模型自身不会有任何变化。

7另一种机器学习方法是无监督式学习(unsupervised learning),它使用无标签数据。这方面的示例包括聚类和异常检测,前者发现数据集中不同的样例子集,后者判断给定的样例与训练集中的样例是否存在显著不同。

学习数据的表示

机器学习就是要从数据中学习。但到底学习哪些内容呢?其实就是一种有效地转换(transform)数据的方法,换言之,将数据从旧表示转换为新表示,从而更有效地解决现阶段的问题。

在进一步展开讨论之前,先来看一下“表示”的概念。简明来说,表示(representation)是一种处理数据的方式。通过不同的处理方式,同一组数据可以有不同的表示。比如,彩色图像可以用RGB或者HSV8来编码。这里,编码(encoding)和表示实质上指的是同一个东西,它们可以交换使用。用这两种方式编码得到的代表图像像素的数值是完全不同的,尽管编码对象是同一个图像。不同的表示适用于解决不同的问题。例如,要找出一个图像中所有的红色部分,使用RGB表示会比较简单。但要找到同一个图像中颜色饱和的部分,则HSV表示更有用。这实际上就是机器学习的本质:找到一种合适的方式把输入数据的旧表示转换成新表示,并且这一新表示适用于解决当下特定的任务,比如检测图像中车辆的位置或者判断图像中出现的是猫还是狗。

8H、S、V分别代表色相、饱和度、明度。

再看一个示例,图1-4中展示了一些白点和黑点。假设我们想研发一种算法,这个算法能够将某个点的二维坐标(x, y)作为输入,预测该点是黑色还是白色。在这个场景中,主要涉及以下两个方面。

● 输入数据是某个点的二维坐标(横坐标值和纵坐标值)。

● 输出是该点的颜色的预测值(黑色或白色)。

实际数据展现出的规律如图1-4a所示。给定xy的值后,机器如何判断该位置的颜色呢?不能只是简单地将x与某个数字进行比较,因为黑点和白点的横坐标值域是重叠的!同理,也不能单纯地取决于y。因此,可以得出这样的结论:对黑白分类任务而言,坐标点原来的表示并不是很友好。

而我们需要的是能更简单明了地区分两种颜色的新表示。因此,此处将原来的笛卡儿坐标系表示转换为极坐标系表示。换言之,用该点的角度和半径来表示它。其中,角度由x坐标轴和点到原点的连线构成(见图1-4a),半径是指点到原点的距离。经过这一变换,我们得到了同一组数据的新表示,如图1-4b所示。这一表示更适用于当前的任务,现在黑点和白点的角度值完全不会重叠。然而,这个新表示仍不够理想,因为它还不能通过与一个阈值(比如0)的简单比较得出颜色的分类。

图1-4 示例:机器学习就是寻求有效的表示转换。(a)平面上由黑点和白点组成的数据集的原表示。(b)(c) 两次连续的转换将原表示转换成一个更适合颜色分类任务的表示

幸运的是,我们可以进行第二次变换来达到此目的。这一变换基于下面这个简单的公式:

(角度的绝对值) - 135度

这次变换得到的表示,如图1-4c所示,是一维的。与图1-4b中的表示相比,这样做可以将各点到原点的距离这一不相干的信息剔除。从颜色分类任务的角度而言,这个表示恰到好处,因为它让决策过程变得极其简单:

如果公式计算结果小于0,则点为白色;
    否则,点为黑色

在上面的示例中,我们为原表示人为地选择了两个转换步骤。但是,如果能以颜色分类的准确率作为依据,实现自动搜索各种可能的坐标转换,那就是机器学习了。解决实际机器学习问题所需的转换步骤通常远多于两个。特别是在深度学习中,甚至可能达到上百个。同时,实际机器学习中涉及的表示转换可能比这个简单示例中的要复杂得多。深度学习领域的研究仍在不断发现更复杂且强大的转换,图1-4正诠释了“搜索更好的表示”这一本质。这一点适用于所有机器学习算法,包括神经网络、决策树、核方法以及其他算法等。

1.1.3 神经网络和深度学习

神经网络是机器学习的子领域,其中实现数据表示转换的系统,其架构部分参考了人和动物大脑中神经元的连接方式。那么,大脑中的神经元是如何连接的呢?虽然连接方式会因物种和大脑区域而有所不同,但有一点是共通的,那就是层结构。哺乳动物大脑的很多部分展现了层的特征,比如视网膜、大脑皮层和小脑皮层。

至少表面上看来,这一特征和人工神经网络9的结构大体相似,它们的数据都是通过多个可分离的步骤进行处理的。因此,将这些步骤称为(layer)恰如其分。这些层通常彼此叠加,只有相邻的层之间会建立连接。图1-5展示了一个简单的含有4层的神经网络,这个神经网络能够对手写数字的图像进行分类,在层与层之间可以看到原数据的表示在转换过程中形成的中间表示。输入数据(此处是一个图像)进入第1层(图1-5的左侧),然后按顺序一层层流入,每一层都会对数据的表示进行一次转换。随着数据经过越来越多的层,表示会越发偏离原表示,而越发接近神经网络的目标,那就是为输入图像打上正确的标签。当数据经过最后一层(图1-5的右侧)后,就会产生神经网络的最终输出,即图像分类任务的结果。

9artificial neural network,也可以简称为neural network,即神经网络,在计算机领域这种说法并没有歧义。

图1-5 一个由层组成的神经网络的示意图,弗朗索瓦·肖莱版权所有10

10摘自弗朗索瓦·肖莱所著的《Python深度学习》一书。

神经网络中的层和数学中的函数概念相似,它们都是从输入值到输出值的映射。然而它们又有所不同,这是因为神经网络中的层是有状态的(stateful)。换言之,它们在内部保留有记忆,这些记忆封装在相应的权重中。权重(weight)就是一组属于层的数值,这些数值决定了每一个输入的表示如何转换成输出的表示。比如,常用的密集层(dense layer)在转换输入数据时,会将它乘以一个矩阵,然后让该结果加上一个向量,这里的矩阵和向量就是密集层的权重。当神经网络用数据进行训练时,各层的权重会进行系统性修改,最终让损失函数(loss function)的值趋于最小。第2章和第3章中会用具体的示例进行说明。

尽管神经网络确实从人类大脑结构中汲取了部分灵感,但是我们不应将这两者过度类比。神经网络不是为了学习或模仿人类大脑的工作机制,它是完全不同的学术领域——神经科学的研究范畴,旨在让机器能够通过学习数据来执行有意义的实际任务。在结构和功能方面,虽然有些神经网络和人类大脑的某些部分确实有不可思议的相似性11,但是关于这一点的可靠性超出了本书的讨论范畴。无论怎样,这种相似性不应被过度解读。尤其是目前并没有证据表明大脑通过任何形式的梯度下降优化进行学习,而这恰恰是神经网络的主要训练方式(参见第2章)。对于很多引领深度学习革命的重要的神经网络技术,它们的发明和应用并不是因为有神经科学的理论支撑,而是因为能帮助神经网络更好、更快地解决实际学习任务。

11第4章有一个关于功能相似性的示例较具说服力,其中展示了一些输入如何最大限度地激活卷积神经网络的各个层,这与人类视觉系统各部分的神经元感受野(neuronal receptive field)的功能非常相似。

了解神经网络之后,接下来看一下深度学习(deep learning)的概念。深度学习就是关于深度神经网络(deep neural network)的学习和应用。而深度神经网络,简单来说,就是有很多层(通常多达数十甚至上百层)的神经网络。在这里,(deep)是指为数众多的连续的表示层,数据模型拥有的层数叫作模型的深度(depth)。这一领域还有其他名称,比如分层表示学习(layered representation learning)和层级表示学习(hierarchical representation learning)。现代深度学习通常包含数十至上百个连续的表示层,它们都是从训练数据中自动学习的。与此相反,其他机器学习方法倾向于专注学习一到两个表示层,因此,它们又叫作浅层学习(shallow learning)。

将深度学习中的“深”解读为对数据的深刻理解是错误的。M. C. Escher画作中自我指涉(self-reference)所造成的悖论可谓深刻,但这种深刻对AI研究者而言仍是不可企及的目标12。也许在未来,深度学习会让我们更接近这样的深刻,但它肯定没有像给神经网络添加层这样容易量化和实现。

12参见Douglas Hofstadter发表的文章“The Shallowness of Google Translate”。

信息栏1-1 神经网络不是唯一选择:其他机器学习技术

回顾图1-1,我们现在从“机器学习”的大圈进入了“神经网络”的小圈。然而,在深入探索之前,大致了解一些非神经网络机器学习技术是十分有益的。这不仅能帮助我们了解机器学习发展的历史背景,而且可以在现有代码中很好地理解它们的用法。

朴素贝叶斯分类器(naive Bayes classifier)是最早的机器学习方法之一。简单地说,对于计算事件发生概率的贝叶斯定理,其实现过程基于两个前提:第一,对已有事件发生可能性的先验认知;第二,观测到的数据,即特征(feature)。这一方法可以用观测到的数据计算每个事件类别对应的概率,然后按照最高的概率(事件发生可能性),将观测到的数据划入一个已知的类别。该方法假设观测到的数据之间是相互独立的,这是一个非常强的假设,同时对现实中的现象有所简化,名字中的“朴素”正是由此而来。

逻辑回归(logistic regression)也是一种分类方法。由于其简单和灵活多变的特性,它至今都非常流行,通常是数据科学家处理分类任务的首选方法。

核方法(kernel method)主要用于解决二分类问题,即共有两种类别的问题。它将原数据映射到新的更高维空间,并寻找一种转换方式,实现两种类别示例之间距离(又称margin,即间隔)的最大化,从而进行分类。支持向量机(SVM)是核方法最有代表性的例子。

决策树(decision tree)的结构与流程图类似。可以对输入的数据进行分类,或者根据输入的数值预测输出。在流程图的每一步,只需要简单地回答一个答案为“是”或“否”的问题,比如“X特征的值是否大于特定阈值”。每一步的流程取决于所选答案,然后进入答案对应的路径,在那里会出现另一个“是”或“否”的问题,以此类推。一旦到达了流程图的终点,也就获得了最终的答案。如你所见,对所有人而言,决策树非常直观且容易理解。

随机森林(random forest)和梯度提升机(gradient-boosted machine)通过整合大量有特定功能的决策树来提高整体的准确率。集成化(ensembling)又名集成学习(ensemble learning),这种方法会训练一些机器学习模型的集合(集成),并在推断阶段将它们的整体输出作为推断结果。现在,梯度提升是用来处理非感知数据最好的算法之一,例如信用卡诈骗检测中的信用数据。和深度学习一样,它也是Kaggle这类数据科学竞赛中较为常用的技巧。

神经网络的崛起、陨落和回归,以及这些现象背后的原因

神经网络的核心思想早在20世纪50年代就成形了。训练神经网络的关键技术,包括反向传播算法,在20世纪80年代就出现了。然而,20世纪80年代至21世纪前十年这一段漫长的时期里,神经网络技术在研究社区里几乎完全进入了蛰伏期。原因包括支持向量机等同类技术的流行,以及当时缺乏训练深度神经网络(非常多层)的能力。但是,在2010年前后,很多坚持奋战在神经网络领域的人们开始取得意义重大的突破,这些人包括:多伦多大学的Geoffrey Hinton研究小组、蒙特利尔大学的Yoshua Bengio、纽约大学的Yann LeCun和瑞士意大利语区高等专业学院Dalle Molle人工智能研究所(IDSIA)的研究者。这些团体取得了很多里程碑式的成果,包括第一次在GPU(图形处理单元)上真正实现深度神经网络,以及将ImageNet计算机视觉比赛中的错误率从25%降到不足5%等。

自2012年以来,深度卷积神经网络已经成了所有计算机视觉任务的首选算法;说得更宽泛点儿,它们适用于所有感知性任务。语音识别就是非计算机视觉的感知性任务示例之一。在2015—2016年的大型计算机视觉会议中,几乎所有的演讲报告都和convnet有某种程度的关联。同时,深度学习还应用到了其他一些问题上,比如自然语言处理。它在各种应用程序中取代了SVM和决策树的地位,例如对于ATLAS探测器在大型强子对撞机上获得的粒子数据,多年来欧洲核子研究组织(CERN)一直采用基于决策树的方法进行分析。但是,考虑到神经网络性能更佳,在大数据集上易于训练,CERN最终还是转向了深度神经网络的阵营。

那么,为什么深度学习会从所有的机器学习算法中脱颖而出呢?(参见信息栏1-1中列出的一些流行的非深度神经网络的机器学习方法。)深度学习快速崛起的主要原因是它能在很多问题上获得更好的性能,但这并不是唯一的原因。深度学习还让解决问题变得更简单,因为它能实现特征工程(feature engineering)的自动化,这一度是机器学习流程中最重要、最困难的一步。

对于前面介绍的浅层学习,这种机器学习技术通常只需借助简单的转换方法,如非线性高维映射(核方法)或决策树等,将输入数据转换成一个或两个连续的表示空间。但是复杂问题所需的表示更为精细,这些技术无法实现。因此,人类工程师不得不在原始的输入数据上耗费更多的精力,让它们也能够由这些技术处理。也就是说,工程师必须手动为数据设计合适的表示层,这就是特征工程。相对而言,深度学习可以自动实现这个过程。通过深度学习,可以借助程序一次性学习所有特征,无须手动设计。这极大地简化了机器学习的流程,从而可以将复杂、精细的多步骤流水线替换成单个、简单的端到端深度学习模型。通过实现特征工程自动化,深度学习减少了机器学习所需要的人力投入,模型本身变得更为稳健,可谓一箭双雕。

在学习数据时,深度学习有两个至关重要的特点:一是循序渐进、一层接一层地发展更为复杂的表示;二是在整个过程中,中间这些递进的表示层同时是被学习的,每一层的更新都会同时兼顾上层和下层的表示需求。正是这两个特点的结合,使深度学习相比之前的机器学习策略获得了更大的成功。

1.1.4 进行深度学习的必要性

如果神经网络的基本思想和核心技术早在20世纪80年代就已存在,为什么直到2012年后才发生深度学习革命呢?在中间的30多年里都发生了什么?总体来说,有3股技术力量推动了机器学习的发展:

● 硬件

● 数据集和基准

● 算法上的革新

接下来逐个看一下这些关键因素。

硬件

深度学习是由实验发现而非理论引导的工程科学领域。只有当硬件条件允许尝试新想法,或者是更为常见的放大旧想法的实验规模时,才有可能实现算法上的革新。对计算机视觉和语音识别中应用的典型的深度学习模型而言,其所需要的算力超出了笔记本计算机算力几个数量级。

在21世纪前十年里,为了满足画面日益逼真的电子游戏在图像处理上的需求,英伟达(NVIDIA)和超威半导体(AMD)等公司投资了数十亿美元来开发高速的、适用于并行计算的芯片(GPU),这些芯片其实是廉价且功能单一的、专用于在屏幕上实时渲染复杂三维图像的超级计算机。2007年,NVIDIA发布了一种专为NVIDIA GPU设计的通用编程接口:CUDA(Compute Unified Device Architecture,计算统一设备体系结构)。这标志着GPU领域的投资真正开始推动科学研究社区的发展。从物理建模领域开始,小规模的GPU集群逐渐开始在适合高度并行计算的应用程序中取代传统的大规模CPU集群。深度神经网络所涉及的运算主要由矩阵乘法和矩阵加法构成,因此它们也具有高度的并行性。

在2011年前后,一些研究者开始编写神经网络的CUDA实现,Dan Ciresan和Alex Krizhevsky就是其中最早的一批。当下,在训练深度神经网络时,高端GPU可以提供的并行算力是一般CPU的上百倍。正是依靠现代GPU的惊人算力,很多顶尖的深度神经网络的训练才成为可能。
 

数据和基准

如果说硬件和算法之于深度学习革命就如同蒸汽机之于工业革命,那么数据就相当于“蒸汽机所烧的煤”。数据是这些智能机器的能量来源,没有了它,一切皆无可能。谈及数据,在过去20年,除了存储硬件呈指数级发展(遵循摩尔定律),根本变化是互联网的崛起。互联网让机器学习收集和分发大量的数据集成为可能,如果没有互联网,现在大型企业所用的图像数据集、视频数据集以及自然语言数据集都不可能存在。例如,用户在Flickr平台生成的带水印的图像就是计算机视觉研究的数据宝库,YouTube平台上的视频也是如此。维基百科则是自然语言处理的一个关键数据集来源。

如果必须说哪个数据集是深度学习崛起的关键助力,那么一定是ImageNet了。这个大型视觉数据库包含约1419万个图像,每张都有针对1000种图像类型的手动注释。ImageNet的特殊之处不仅在于其数据庞大,还在于和它相关的年度竞赛13。正如ImageNet和Kaggle自2010年来所展示的那样,公开的竞赛是激励研究者和工程师开拓领域疆界的绝佳方式。它们让研究者竞相超越一个共同的基准,从而极大地促进近来深度学习的崛起。
 

算法上的革新

除了硬件和数据上的进步,我们还缺少训练深度神经网络(具有非常多的层)的可靠方法,直到21世纪前十年后期,这种现象才有所好转。结果就是:当时的神经网络仍非常浅,仅使用一到两层表示。因此,它们无法和其他更精细的浅层方法竞争,例如SVM和随机森林。问题的关键在于穿越多层的梯度传播,这是因为随着所经过层数的增加,用于训练神经网络的反馈信号会逐渐消失。

这一点在2009—2010年发生了改变,其中的几个简单但意义重大的算法革新,让梯度传播变得更为有效。

● 更好的神经网络层激活函数(activation function) ,例如线性整流函数(简称ReLU)。

● 更好的权重初始化方案(weight-initialization scheme),例如Glorot初始化方法。

● 更好的优化方案(optimization scheme),例如RMSProp和ADAM优化器。

只有当这些改进可以训练10层或更多层的模型时,深度学习才会大放异彩。终于,2014—2016年出现了一些更为高级的帮助梯度传播的方法,包括批标准化(batch normalization)、残差连接(residual connection)和深度可分离卷积(depthwise separable convolution)。现在,我们已经可以从头训练有上千层的深度神经网络模型了。

13ImageNet大规模视觉识别挑战赛(ILSVRC)。——译者注

1.2 为何要结合JavaScript和机器学习

与AI和数据科学的其他分支一样,机器学习通常使用传统的后端编程语言来实现,比如Python和R,这些编程语言在Web浏览器外部的服务器或工作站上运行14。通常普通浏览器页面并不直接具备训练深度神经网络所需的多核计算和GPU加速计算能力,另外对于训练这样的模型,有时候所需的海量数据从后端获取更为方便15,因此这种现象很正常。直到最近,很多人仍只是把“实现JavaScript深度学习”看作新奇的想法。这一节将诠释为什么对很多应用程序而言,在浏览器环境中用JavaScript进行深度学习是一个明智之举,同时还会介绍为什么结合深度学习和Web浏览器可以创造很多独特的机会(特别是通过TensorFlow.js)。

14参见Srishti Deoras发表的文章“Top 10 Programming Languages for Data Scientists to Learn in 2018”。

15例如,可以直接从近乎无限大的原生文件系统中读取。

一旦某个机器学习模型训练完毕,必须将其部署到某种生产环境中,这样才能开始基于真实数据进行预测,比如对图像和文本进行分类、检测音频或视频中发生的事件,等等。如果没有部署环节,训练模型只是在浪费算力。将Web前端作为部署的“某种生产环境”通常是人们所预期的,甚至可以说是必不可少的。你可能已经对Web浏览器的重要性深有体会,在台式计算机和笔记本计算机上,Web浏览器是当今用户获取互联网内容和服务绝对主流的方式,也是用户在使用期间花费时间最多的地方,其使用频率远超第二名。同时,它还是用户进行工作、社交和娱乐的主要阵地,其中运行的多种多样的应用程序,为在客户端上进行机器学习提供了丰富的机会。对移动端的前端而言,无论是在用户参与度上,还是在用户的使用时间上,Web浏览器都落后于原生移动应用程序。尽管如此,但移动端Web浏览器仍是不可小觑的力量,它们有更广泛的受众,可以随时使用,并且有更快的开发周期16。事实上,正是因为Web浏览器的灵活性和易用性,很多移动应用程序会为某些内容类型嵌入启用了JavaScript的Web页面,包括推特和脸书。

16参见Rishabh Borde发表的文章“Internet Time Spend in Mobile Apps 2017–19: It's 8x than Mobile Web”。

正因为这种广泛的影响力,模型所需的数据都可以从浏览器中获得,所以Web浏览器成为部署深度学习模型的合理选择。但浏览器可以获得什么类型的数据呢?答案是“太多了”!深度学习较为流行的应用程序都可以当作示例,包括分类和检测图像以及视频中的物体、转录音频、翻译自然语言和分析文本内容。可以说,Web浏览器拥有展示文本数据、图像数据、音频数据和视频数据的最全面的技术和API。因此,通过TensorFlow.js和一些简单的转换流程等,可以直接在浏览器中使用强大的机器学习模型。至于在浏览器中部署深度学习模型,本书后面的章节中提供了很多具体示例。例如,在用网络摄像头捕获了一些图像后,可以用TensorFlow.js运行MobileNet模型来标记图像中的物体,运行YOLO2模型将检测到的物体用方框进行标注,运行Lipnet来读取唇语,也可以运行CNN-LSTM模型为图像添加字幕。

如果用浏览器的Web Audio API控制麦克风捕获音频,TensorFlow.js可以通过运行模型,对语音进行实时识别。还有很多关于文本数据的优秀的应用程序,比如对影评进行情感分析,判断是否为正面评价(第9章)。除了这些数据形式,Web浏览器还可以从移动设备上获取一系列传感器数据,比如HTML5提供了API来访问地理位置(包括纬度和经度)、运动信息(设备的朝向和加速度)和背景光(参见Mobile HTML5网站)。如果将这些传感器数据和深度学习还有其他一些数据形式结合起来,它们可以使很多优秀的新应用程序成为可能。

基于浏览器的深度学习应用程序还有另外5个优势:更少的服务器开销、更低的推断延迟、保护数据隐私、即时GPU加速和随时使用。

服务器开销通常是设计和伸缩网络服务时的一个重要考量。通常,快速运行深度学习模型所需的算力是相当高的,这使GPU加速成了必选项。如果模型没有在客户端上部署,它们就需要部署到有GPU支持的机器上,比如由谷歌云平台或亚马逊Web服务(Amazon Web Services)提供的配有CUDA GPU的虚拟机。这样的云端GPU机器通常开销非常大,即使是最基本款的GPU机器,每小时大约也要花费0.5~1美元。随着流量的增加,在云端运行GPU机器的开销会越来越大,更别说服务器端在可扩展性和额外复杂度上随之出现的挑战了。如果将模型部署在客户端上,所有的顾虑都会迎刃而解。客户端下载模型大小不等,通常为几兆字节(MB),这里的额外延迟可以通过浏览器的缓存和本地存储能力缓解(参见12.3.1节)。

更低的推断延迟。对于某些应用程序类型,可接受的最大延迟要求非常严格,因此在客户端上运行深度学习模型成为必然。任何涉及实时音频数据、图像数据和视频数据的应用程序都可以归为这个范畴。如果图像的每一帧都需要传输到服务器端进行推断,会发生什么情况?假设网络摄像头以每帧400像素×400像素、每秒10帧的速率捕捉图像,图像的颜色使用三色通道(RGB),每个颜色通道都使用8位深度。即使采用JPEG压缩,每一张图片的大小也至少有150KB左右。在一个典型的有300kbit/s上传带宽的移动网络中,上传一张图片可能要花费500毫秒以上的时间,这所导致的延迟是肉眼可见的,而这对于某些应用程序(比如游戏)是不可接受的。这一计算并没有考虑网络连接的波动情况(网络连接有可能中断)。用于下载推断结果的额外时间,还有大量的移动数据用量,都可能终止这种情境下的服务器端推断。

客户端推断会将数据和计算都放在设备上,这样解决了潜在的延迟和网络连接稳定性方面的问题。在客户端上运行模型是实时机器学习应用程序的唯一选择,比如标记物体和探测网络摄像头图像中的动作。即使对于那些没有延迟要求的应用程序,降低模型推断延迟也可以增强应用程序的响应速度,从而提升用户体验。

保护数据隐私。将训练数据和推断数据放在客户端的另一个好处是保护了用户的隐私。数据隐私在当下的意义已经越来越重要。对某些类型的应用程序而言,数据隐私是一个强制要求,比如涉及健康和医疗数据的应用程序。假设有一个“皮肤病诊断助手”应用程序,它必须从网络摄像头收集关于病人皮肤的图像,然后利用深度学习生成可能的皮肤情况诊断。很多国家及地区的健康信息隐私法规禁止在一个中心化的服务器上传输这样的图像进行推断。但是通过在浏览器中使用模型进行推断,数据根本无须离开用户的手机,甚至无须存储下来,这样就确保了用户健康数据的隐私。

现在考虑另外一个基于浏览器的应用程序场景,假设这个应用程序要使用深度学习来为用户在其中所创造的文本内容提供改进意见。有些用户可能会写一些敏感内容,比如法律文件,那么用户肯定不希望相关数据通过公共网络传输到一个远程服务器上。这时使用浏览器和JavaScript在客户端运行模型就能有效地打消这种顾虑。

即时GPU加速。除了有数据这一前提条件,在Web浏览器中运行机器学习模型的另一个前提条件是通过GPU加速获得足够的算力。正如之前提到的,很多前沿的深度学习模型计算强度非常高,因此使用GPU上的并行计算进行加速成了一个必选项,当然,除非你愿意让用户为一个推断结果等待数分钟,但这在现实应用程序中几乎不可能发生。幸运的是,现代Web浏览器都备有WebGL API,它的设计初衷是加速二维图像和三维图像的渲染,但也可以应用到加速神经网络所需的并行计算中。TensorFlow.js的作者已经将基于WebGL的深度学习加速功能融入到了一个专用库中。只需要一行JavaScript的import代码,就可以获取GPU加速。

基于WebGL的神经网络加速可能不能完全和基于原生、特定的GPU加速17相提并论,但是它能将神经网络的速度提升几个数量级,并且让像PoseNet从图像中提取人体姿态这样的实时推断成为可能。

如果用预训练模型进行推断的开销非常大,那么在该模型上进行训练或迁移学习就更是如此了。训练或迁移学习预训练模型开启了一系列令人兴奋的应用程序,例如深度学习模型的个性化定制、深度学习的前端可视化以及联邦学习(federated learning)18。借助启用WebGL加速的TensorFlow.js,可以在Web浏览器中实现以足够快的速度训练或微调神经网络。

随时使用。一般来说,在浏览器中运行的应用程序有“无须安装”的天然优势:只需输入URL或点击某个链接就能获取应用程序。这样就避免了可能的烦琐又易出错的安装步骤,以及在安装新软件时潜在的权限控制风险。对TensorFlow.js所提供的基于WebGL的神经网络加速来说,在用浏览器进行机器学习的语境下,并不需要特殊的显卡或为这些显卡安装驱动。后者通常也不是一个简单的过程。绝大多数相对较新的台式计算机、笔记本计算机和移动设备安装了适用于浏览器和WebGL的显卡。这些设备只要装有和TensorFlow.js兼容的Web浏览器(这很容易实现),无须任何额外设置,就可以运行WebGL加速的神经网络。对于易访问性极其重要的场景,例如深度学习教育,这一特性尤其诱人。

17比如NVIDIA CUDA以及TensorFlow和PyTorch等Python深度学习库所使用的CuDNN。

18联邦学习就是在不同设备上训练同一模型,然后汇总训练结果,从而获得一个不错的模型。

信息栏1-2 使用GPU和WebGL加速计算

训练机器学习模型和使用模型进行推断需要大量的数学运算。例如,神经网络中广泛使用的密集层就需要将一个大矩阵和一个向量相乘,然后让结果加上另一个向量。像这样的常用运算通常涉及上千次至上百万次浮点运算,这类运算有一个很重要的特性,那就是很多时候它们可以并行进行。

举个例子,两个向量的加法可以拆分成很多更小的运算,比如两个数字相加。这些更小的运算互不依赖。例如计算两个向量中索引1上元素的和,无须提前知道索引0上元素的和。因此,无论这些向量有多大,这些更小的运算都可以同时进行,而不是逐个地计算。

串行计算又叫作SISD,并行计算则叫作SIMD。前者即单指令流单数据流,比如用CPU实现的简单向量加法;后者即单指令流多数据流,比如用GPU实现的并行向量加法。同样进行单次运算,CPU所花的时间通常少于GPU所花的时间;但是从计算大量数据时所花的总时间来看,GPU的SIMD性能则要优于CPU的SISD。一个深度神经网络可能包含上百万个参数,对于给定的输入,可能需要进行至少数十亿次元素与逐元素的数学运算。从这个角度来看,GPU擅长的并行计算可以说是大放异彩。

WebGL加速利用GPU的并行计算实现比CPU更快的向量计算

准确地说,现代CPU也可以进行一定程度的SIMD运算,但相比之下,GPU拥有更多的计算单元,几乎是前者的数百到数千倍,并且GPU可以在很多输入数据切片上同时运行指令。向量加法是相对简单的SIMD任务,其中每一步计算只涉及一个索引,并且每个索引对应的计算结果是相互独立的。机器学习中其他常见的SIMD任务就复杂得多了。比如在矩阵乘法中,每一步计算会用到多个索引上的数据,并且这些索引之间互相依赖。尽管如此,但通过并行计算进行加速的基本原理是一样的。

还有一点值得注意,那就是GPU设计的初衷并不是加速神经网络。这一点也反映在它的名字上,即图形处理单元。GPU的主要目的是处理二维图像和三维图像。在很多涉及图像渲染的应用程序中,比如三维游戏,以最快的速度处理图像非常重要。只有这样,屏幕上图像的更新频率才能达到流畅的游戏体验所需的高帧率,这也是GPU发明者利用SIMD并行计算的初衷。但是,GPU擅长的并行计算恰好满足了机器学习的需求,这实属意外的惊喜。

对于TensorFlow.js用来进行GPU加速的WebGL库,其设计初衷是在Web浏览器中渲染三维物体的纹理(表面图案)——本质上就是数字矩阵!因此,可以把这些数字当作神经网络的权重或激活值,然后复用WebGL的SIMD纹理运算,让它们来运行神经网络。这正是TensorFlow.js在浏览器中加速神经网络的方式。

除了上述优势,基于机器学习的Web应用程序和不涉及机器学习的普通Web应用程序的优势是共通的。

● 和原生应用程序开发不同,用TensorFlow.js编写的JavaScript应用程序可以在各种生态的设备上运行,包括Mac、Windows和Linux的桌面端系统以及安卓和iOS的移动端系统。

● 得益于它高度优化的(二维和三维)图形处理能力,Web浏览器是数据可视化和互动方面最丰富且最成熟的环境。对展示神经网络的行为和内部构造这一应用场景而言,没有什么能出浏览器其右。TensorFlow Playground就是很好的示例(参见TensorFlow Playground网站),它是一个非常流行的Web应用程序,可以通过它的图形界面和神经网络互动来解决分类问题,还可以用它微调神经网络的结构和超参数,然后观察其隐藏层和输出对应的变化情况(见图1-6)。如果你还没有试过TensorFlow Playground,强烈建议你试用一下。许多人表示这是他们看过的关于神经网络的最有指导意义和令人愉悦的教学素材之一。事实上,TensorFlow Playground是TensorFlow.js的重要先驱,因此,相比前者,后者的能力范围要大得多,并且有更深度的性能优化。此外,TensorFlow.js还内置了专用于可视化深度学习模型的模块(参见第7章)。无论是想构建像TensorFlow Playground这样基本的教育性应用程序,还是想以引人入胜的直观方式展示前沿深度学习研究,TensorFlow.js都可以帮助你朝目标迈进一大步(参见t分布随机邻域嵌入算法的实时可视化示例,即tSNE19)。

19参见Nicola Pezzotti在Google AI Blog发表的文章“Realtime tSNE Visualizations with TensorFlow.js”。

{%}

图1-6 TensorFlow Playground网站截图。它是一个非常流行的Web应用程序,由Daniel Smilkov及其同事共同制作,主要用于教授神经网络的工作原理,同时也是TensorFlow.js项目的重要先驱

1.2.1 用Node.js进行深度学习

考虑到安全问题和性能问题,Web浏览器在设计时对可利用的资源进行了限制,具体体现在有限的内存和文件系统配额方面。但对训练涉及大量数据的大型机器学习模型来说,这意味着浏览器并不是一个理想的环境。当然,对于很多其他类型的推断,比如无须消耗大量资源的小规模模型训练和迁移学习任务,Web浏览器仍然非常适用。但是,当Node.js出现后,JavaScript的地位就不可同日而语了。Node.js让JavaScript脱离了Web浏览器的桎梏,使它能够最大限度地利用系统的原生资源,比如内存和文件系统。TensorFlow.js包含了一个Node.js版本,叫作tfjs-node,可以与C++和CUDA代码编译而成的TensorFlow库直接对接,这样TensorFlow.js用户也能够受益于Python版TensorFlow所使用的CPU并行计算和GPU核函数计算。现实数据表明,tfjs-node中模型的训练速度和Python中Keras的运行速度是相当的。因此,tfjs-node也可以用于训练涉及大量数据的大型机器学习模型。本书会介绍使用tfjs-node来训练超出浏览器能力极限的大型模型的一些示例,例如第5章的口令识别器、第9章的文本情感分析器。

但是在训练机器学习模型时,为什么不选用更为成熟的Python环境而是选用Node.js呢?这主要有两个原因:第一,Node.js的性能更好;第二,Node.js和现有的技术栈与开发者技能更匹配。

首先,就性能而言,目前行业顶尖的JavaScript解释器,如Node.js使用的V8引擎,能够利用即时编译技术达到超过Python的性能。也正因如此,只要模型规模足够小,其中编程语言的解释性能可以决定训练速度,这时用tfjs-node训练模型通常比用Keras(Python)更快。

其次,Node.js本身也是一个非常流行的构建服务器端应用程序的生态。如果你当前的后端就是用Node.js编写的,并且想将机器学习也囊括进你的技术栈中,那么相比Python,使用tfjs-node通常是更好的选择。通过前后端使用同一种语言,可以直接复用代码库中的大部分代码,包括那些用来加载和格式化数据的部分,从而更快地构建模型训练的整个流程。此外,整个过程并没有引入新的语言,这样可以控制开发的复杂度和成本,也可以避免专门雇用Python程序员所耗费的精力和开销。

最后,除了仅支持浏览器API或仅支持Node.js API的与数据相关的代码,用TensorFlow.js编写的机器学习代码可以同时在浏览器环境和Node.js端运行。本书中绝大部分示例程序可以同时在两种环境中运行。另外,本书将那些没有环境依赖并与机器学习直接相关的核心代码,同那些有环境依赖的数据获取和UI代码进行了分离。这样有一个额外的优势,那就是只需学习一个库,就能同时学会如何在服务器端和客户端进行深度学习。

1.2.2 JavaScript生态系统

在评估JavaScript是否适合深度学习这样的应用程序时,一定不能忽视JavaScript背后极其强大的生态。多年来编程语言不断地发展更新,但最受欢迎的一直是JavaScript。在GitHub上,无论是相关代码仓库的总量,还是提交代码的活跃度,都可以证明这一点(参见GitHut网站)。npm是一种JavaScript包管理器,截至2018年7月,其中软件包的数量已经高达60万,比Python的包管理器PyPi上的数量高出不止4倍(参见Module Counts网站)。尽管Python和R在机器学习和数据科学方面有更为完善的社区,但是JavaScript社区也在积极地为构建基于JavaScript的机器学习流程而努力。

想从云存储和数据库接入数据吗?谷歌云平台和亚马逊Web服务都提供了Node.js的API。当下最流行的数据库系统也提供了对Node.js驱动的官方支持,比如MongoDB和RethinkDB。想要用JavaScript来整理数据吗?如果想,那么可以阅读Ashley Davis所著的Data Wrangling with JavaScript。想要实现数据可视化吗?JavaScript社区拥有一些成熟且强大的库,例如d3.js、vega.js和plotly.js等,它们在很多方面远超Python可视化库。数据准备就绪之后,就要展开介绍本书的主题了,即TensorFlow.js。它将帮助你完成从创建、训练到执行深度学习模型的全流程,当然,还包括如何保存、读取和可视化这些模型。

最后,JavaScript生态圈还在持续不断地朝令人兴奋的新领域和新方向演进,其影响力已经从Web浏览器和Node.js后端环境这些传统强项,延伸到了桌面端应用程序(比如Electron)和原生移动端应用程序(比如React Native和Ionic)。在开发用户界面和应用程序时,使用这些跨平台框架通常比使用各个平台专门的开发工具便捷得多。因此,在向各个主流计算平台推广深度学习方面,JavaScript是很有潜力的编程语言。表1-2中总结了将JavaScript和深度学习相结合的主要优势。

表1-2 用JavaScript进行深度学习的优势概览

1.3 为何选用TensorFlow.js

工欲善其事,必先利其器。在开始用JavaScript进行深度学习前,应该先选择一个正确的工具。本书选择的是TensorFlow.js,本节会详细介绍TensorFlow.js以及选择它的原因。

1.3.1 TensorFlow、Keras和TensorFlow.js的前世今生

TensorFlow.js是JavaScript语言的深度学习库。可以看到,TensorFlow.js在设计上是与TensorFlow(Python深度学习框架)保持一致且兼容的。要想真正理解TensorFlow.js,需要先简单了解一下TensorFlow的发展历程。

TensorFlow是由谷歌从事深度学习的团队于2015年11月创造的开源资源,本书作者正是这个团队的成员。从开源至今,TensorFlow获得了巨大的成功,在谷歌以及更大的技术社区的各种工业应用程序和研究项目中得到了广泛应用。另外,数据的表示又叫作张量(tensor),它会“流经”(flow)模型的每一层和其他数据处理节点,从而实现机器学习模型的推断和训练,因此“TensorFlow”这一名字暗指在该框架下编写的典型程序中发生的事情。

什么是张量?这只不过是计算机科学家对“多维矩阵”更严谨的说法罢了。在神经网络和深度学习中,每个数据和计算结果都会用张量表示。例如,一个灰度图像可以表示为一个二维数组,这就是一个二维张量;同理,彩色图像多出了一个维度,即颜色通道,因此可以表示为三维张量。音频、视频、文本以及其他任何类型的数据都可以用张量来表示。每个张量都由两种基本属性构成:数据的类型(比如float32或int32)和形状。数据的形状描述了张量各个维度的尺寸,例如二维张量的形状可能是[128, 256],而三维张量的形状则可能是[10, 20, 128]。一旦数据转换成了某个特定的数据类型和形状,无论它最初的意义是什么,都可以输入到任何与其类型和形状匹配的层。因此,张量其实就是深度学习模型赖以沟通的语言。

但为什么要用张量呢?上一节曾提到,运行深度神经网络所需的大部分计算通常以并行化的方式在GPU上进行,这也意味着要对很多数据进行相同的运算。张量能够将数据结构化,从而实现高效并行计算。如果将形状为[128, 128]的张量A与形状为[128, 128]的张量B相加,显而易见,这将产生128×128个独立的加法运算。

那TensorFlow中的“flow”又指什么呢?把张量想象成能够承载数据的流体就明白了。只不过在TensorFlow中,张量流过的是(graph),即由各种数学运算(节点)互相连接而成的数据结构。如图1-7所示,这些节点可以看作神经网络中连续的层,每一个节点都将张量作为输入,然后产生新的张量作为输出。随着张量“流经”TensorFlow图的各个节点,它也转换成不同的形状和值。就像本章前面介绍的,这实际上就是表示的转换,也正是神经网络的关键。利用TensorFlow,机器学习工程师可以编写各种不同的神经网络,包括从浅层神经网络到有相当深度的神经网络,以及从用于计算机视觉的convnet到用于处理序列数据的循环神经网络。TensorFlow的图数据结构可以被序列化,并且被部署到包括大型机和手机在内的各种设备上。

图1-7 TensorFlow和TensorFlow.js中的常见场景——张量“流经”各个层的示意图

TensorFlow的核心部分在设计上是非常通用且灵活的:运算不限于神经网络的层,可以是任意定义明确的数学函数,比如像张量加法和张量乘法这样发生在神经网络层内部的低阶数学运算。这样一来,当自定义一些有关深度学习的新运算时,深度学习工程师和研究者就有很大的发挥空间。尽管如此,但是对很大一部分深度学习从业者而言,花费精力来摆弄这些底层的操作并不值得,还可能造成更臃肿、更易出错的代码以及更长的开发周期。绝大多数深度学习工程师只需用到几种固定的层类型20,几乎不需要再创建新的层类型。这就和乐高积木的原理一样,乐高积木只有很少的几种基础方块类型,玩家在搭建模型时无须思考积木的制作方法。虽然搭建乐高模型可以带来无数可能的组合和无穷的威力,但是TensorFlow的低阶API其实更接近培乐多这样的彩泥玩具。当然,乐高积木和培乐多彩泥都可以构建玩具小屋,但除非对小屋的尺寸、形状、材质或原料有特殊要求,否则使用乐高来搭小屋一般更快捷、更方便。另外,对绝大多数人而言,用积木搭的小屋会比用彩泥搭的小屋更稳固、更养眼。

20比如卷积层、池化层、密集层等,后面的章节会具体介绍。

在TensorFlow中,与乐高积木对应的就是高阶API,即Keras21。Keras提供了一系列较为常用的神经网络层类型,每种类型都带有配置参数。用户还可以将这些层连接起来,形成完整的神经网络。除此之外,Keras的API还可以完成以下操作。

21事实上,自TensorFlow推出以来出现了很多高阶API,有些是谷歌工程师创建的,有些是开源社区创建的,其中较受欢迎的包括Keras、tf.Estimator、tf.contrib.slim和TensorLayers等。对本书读者来说,目前与TensorFlow.js最相关的高阶API是Keras。这是因为TensorFlow.js的高阶API是以Keras为蓝图创建的,而且TensorFlow.js在保存模型和加载模型方面与Keras具有双向兼容性。

● 指定神经网络的训练方式(损失函数、度量指标和优化器)。

● 向神经网络注入数据,从而进行训练、评估或使用模型进行推断。

● 检测正在进行的训练过程(回调函数)。

● 保存和读取模型。

● 打印或绘制模型架构。

借助Keras,用户仅通过几行代码就能完成深度学习整个流程。另外,有了低阶API的灵活性和高阶API的易用性加持,TensorFlow和Keras形成了一个共同生态,这种生态在工业和学术研究中得到了广泛应用。作为当下正在发生的深度学习革命的重要组成部分,它们在推广深度学习方面有着不可小觑的贡献。以TensorFlow框架和Keras框架为界,在它们出现之前,要想真正进行深度学习,必须拥有CUDA编程技能以及用C++编写神经网络的大量经验;在它们出现之后,再创建具有GPU加速的深度神经网络,整个流程所需的技能门槛和精力都大大下降。但是仍有一个问题悬而未决,那就是在JavaScript中或者直接在浏览器中运行TensorFlow模型或Keras模型,还缺少一种方法。要想在浏览器中使用训练好的深度学习模型,必须通过HTTP请求在后端获取推断结果,这就是TensorFlow.js要解决的痛点。Nikhil Thorat和Daniel Smilkov是谷歌深度学习数据可视化和人机交互方面的专家,他们发起了TensorFlow.js22。正如前面提到的,非常流行的TensorFlow Playground率先在浏览器端演示了深度神经网络的工作原理,它是TensorFlow.js的重要前驱。2017年9月,deeplearn.js库发布了,它有着和TensorFlow的低阶API高度类似的低阶API。deeplearn.js率先实现了WebGL加速的神经网络运算,从而能够以低延迟在浏览器端运行真正的神经网络。

22还有一个有趣的事实,这两位专家对于TensorBoard的开发(非常流行的TensorFlow模型可视化工具)也起到了关键作用。

随着deeplearn.js取得了初步成功,谷歌大脑团队的更多成员加入了这一项目,项目也随之更名为TensorFlow.js。自此,JavaScript API进行了大量的翻新工作,极大地增强了与TensorFlow在API方面的兼容性。除此之外,TensorFlow.js低阶核心基础上还构建了一个类Keras的高阶API,方便用户利用TensorFlow.js来定义、训练和运行深度学习模型。前面介绍了Keras的强大功能和可用性,这些对于TensorFlow.js同样适用。为了进一步增强不同生态之间的兼容性,我们还构建了模型转换器,能够让TensorFlow.js导入在TensorFlow和Keras中保存的模型,或者向它们导出模型。自从在2018年春季TensorFlow开发者峰会和谷歌I/O大会上亮相以来,TensorFlow.js快速发展成一个高度流行的JavaScript深度学习库,至今仍在GitHub上保有同类库中最高星级和复制数量。

图1-8是TensorFlow.js的架构概览。架构的最底层负责快速数学运算所需的并行计算,尽管绝大多数用户不太关注这一层,但是在这一层上保持高计算性能至关重要,这样更高层的API才能尽可能快地进行训练和推断。在浏览器中,它利用WebGL实现GPU加速(参见信息栏1-2)。在Node.js中,它还可以直接利用多核CPU进行并行计算,或使用CUDA进行GPU加速,这跟Python中TensorFlow和Keras的数学运算所使用的技术是一样的。在最底层之上是Core API层,这一层和TensorFlow的底层API有着非常好的兼容性,并支持加载TensorFlow中的SavedModel模型。图1-8中的最上层是类Keras的Layers API。对绝大多数使用TensorFlow.js的程序员而言,Layers API通常是最正确的选择,自然也是本书的焦点所在。另外,它支持和Keras进行双向模型导入或导出。

图1-8 TensorFlow.js架构概览,以及它与Python中TensorFlow和Keras的关系

1.3.2 为何选用TensorFlow.js

TensorFlow.js既不是深度学习领域唯一的JavaScript库,也不是这方面的第一个库,比如brain.js和ConvNetJS就出现得比较早。那么,为什么TensorFlow.js会从这些类似的库中脱颖而出呢?第一个原因是它的全面性。对于深度学习在生产环境中所涉及的所有关键流程,TensorFlow.js是目前唯一全部支持的库,它包括以下特性。

● 支持训练和推断。

● 支持Web浏览器和Node.js两种环境。

● 能够利用GPU加速(在浏览器中使用WebGL,在Node.js中使用CUDA核函数)。

● 支持用JavaScript定义神经网络模型架构。

● 支持模型的序列化和反序列化。

● 支持与Python深度学习框架间的双向模型格式转换。

● 兼容Python深度学习框架使用的API。

● 内置数据获取和可视化所需的API。

第二个原因是生态圈。绝大部分JavaScript深度学习库会定义风格迥异的专属API,相较而言,TensorFlow.js与TensorFlow和Keras是深度集成的。你是否已有一个用Python中的TensorFlow或Keras训练的模型,而且想在浏览器中使用它?没问题!你是否在浏览器中创建了一个TensorFlow.js模型,然后想在Keras中使用它,从而实现更快的加速设备,比如谷歌的张量处理器(TPU)?没问题!与非JavaScript框架的深度集成,这不仅意味着更好的相互兼容性,还意味着开发者能够更好地在不同的编程语言环境和基础设施栈间进行知识迁移。举个例子,一旦你通过本书掌握了TensorFlow.js,再学习使用Python中的Keras会相当顺利。同理,如果你对Keras有一定的了解,而且有足够的JavaScript编程功底,那么学起TensorFlow.js来也会非常快。还有一点需要注意,那就是TensorFlow.js的流行度和它背后社区的力量。TensorFlow.js的开发者致力于长期维护和支持该库,从GitHub上的星级和复制数量,到外部贡献者的数量,从各种讨论的活跃度,到Stack Overflow上相关提问和回答的数量,这些方面都能够说明TensorFlow.js的无可替代性。

1.3.3 TensorFlow.js在全球的应用情况

若要证明某个库的功能和流行度,没有什么比它在真实应用程序中的使用情况更具说服力的了。以下是几个名声赫赫的关于TensorFlow.js的应用程序。

● 谷歌的Magenta项目使用TensorFlow.js来运行RNN和其他类型的深度神经网络,从而在浏览器中自动生成乐谱和新颖的音律(参见Magenta网站的Demos界面)。

● Dan Shiffman和他在纽约大学的同事创造了ML5.js,它是可以直接在浏览器中调用各种深度学习模型的易学的高阶API,实现了目标检测和图像风格迁移等功能。

● 开源软件开发者Abhishek Singh基于浏览器创造了能将美式手语转换成语音的交互界面,让聋哑人也可以使用像亚马逊Echo这类的智能音箱。

● Canvas Friends是一个基于TensorFlow.js的Web应用程序,可以通过游戏化的方式帮助用户提高绘画技能(参见Canvas Friends的网站)。

● MetaCar是在浏览器中实现的自动驾驶模拟器。其中,用TensorFlow.js实现的强化学习算法是成功模拟的关键。

● Clinic doctor是基于Node.js的服务器端性能监控应用程序,它用TensorFlow.js实现了隐马尔可夫模型,并以此来探测CPU使用率的突然增加。

● 还可以在GitHub网站的TensorFlow页面中找到更多由TensorFlow.js开源社区共同创造的优秀的应用程序。

1.3.4 本书中的TensorFlow.js知识

通过对本书的学习,你可以用TensorFlow.js构建以下应用程序。

● 对用户上传的图像进行自动分类的网站。

● 能够从浏览器调用传感器获取图像和音频数据,然后对其进行识别和迁移学习等实时机器学习任务的深度神经网络。

● 位于客户端的自然语言处理模型,例如有助于管理评论区言论的情绪分类器。

● 位于后端的、基于Node.js的机器学习模型训练器,可以处理吉字节级的数据和GPU加速。

● 能完成小型控制和游戏任务的强化学习模型。

● 可以展示已训练模型的内部构造和机器学习实验结果的可视化界面。

更重要的是,你不仅能学会如何构建和运行这样的应用程序,还会知晓其背后的工作原理。也就是说,通过阅读本书,可以获得深度学习模型构建过程中不同问题所涉及的策略和相关限制的实用知识,同时还可以了解训练和部署这些模型的具体步骤以及重要注意事项。

机器学习涉及的领域非常广,而TensorFlow.js是一个灵活且全面的库。因此,尽管有些应用程序超出了本书的讨论范畴,但是它们完全可以通过TensorFlow.js现已提供的技术来实现,比如下面这些示例。

● 在Node.js环境中,对涉及大量数据[太字节(TB)级数据]的深度神经网络进行高性能、分布式训练。

● 非神经网络技术,比如SVM、决策树和随机森林算法。

● 高级深度学习应用程序,比如能将大量文本概括成几个代表性句子的文本摘要引擎,能根据输入图像生成文本描述的图像转文本引擎,还有能够增强输入图像分辨率的生成图像模型。

但无论如何,本书提供了有关深度学习的基础知识,可为你以后学习这些高级应用程序的代码和技术文章做好知识储备。

与其他技术一样,TensorFlow.js也有局限性。有些任务确实超出了它的能力范围,尽管未来的技术发展很可能会突破这些限制,但是现在了解一下这些限制也没有坏处。

● 运行深度学习模型所需的内存超出浏览器上随机存储器(RAM)和WebGL的上限。也就是说,如果在浏览器中进行推断,模型的尺寸需要控制在100MB左右;训练阶段通常需要更多的内存和算力,即使模型的尺寸没达到上限,也有可能导致训练过程过于缓慢。通常,训练阶段所涉及的数据量要比推断阶段大,因此在评估浏览器内训练可行性时,还需要考虑这个限制因素。

● 创造高端强化学习模型,比如能够在对弈系统中击败人类选手。

● 使用Node.js以分布式(多机器)的方式训练深度学习模型。

1.4 练习

无论你是JavaScript前端开发者,还是Node.js开发者,基于本章介绍的内容,头脑风暴一些你可以应用机器学习的场景,让正在编写的程序更为智能,表1-1、表1-2以及1.3.3节可以为你提供一些思路。也可以思考下面几种场景。

(1) 一些经营墨镜等配饰的时尚网站可以通过网络摄像头拍摄用户的面部图像,并使用基于TensorFlow.js的深度神经网络检测面部轮廓特征,然后利用这些关键信息将墨镜图像贴附在用户面部图像上,用户可以通过合成的图像进行试戴体验。这种体验是相对真实的,因为借助客户端推断,这种试戴可以在低延迟、高帧率的情况下进行。同时由于捕获的图像没有离开过浏览器,因此用户的隐私数据在这一过程中得到了充分保护。

(2) 使用React Native(基于JavaScript的跨平台原生移动端应用程序开发框架)编写的移动端体育应用程序可以记录用户的锻炼信息。利用HTML5 API,应用程序可以从手机的陀螺仪和加速度计中获取实时数据。然后基于TensorFlow.js的模型可以通过处理这些数据,判断用户当前的状态,比如休息、散步、慢跑或者冲刺。

(3) 浏览器插件可以通过网络摄像头每5秒为用户进行拍照,然后将这些数据传至基于TensorFlow.js的模型,自动检测当前用户的年龄特征,从而判断用户对一些网站是否有访问权限。

(4) 基于浏览器的编程环境可以使用基于TensorFlow.js的循环神经网络来检测代码注释中的低级错误。

(5) 基于Node.js的服务器端应用程序可以提供物流查询服务,就是根据货物的运送状态、类型、数量、所属地交通状况等实时信息来确定预计到达时间(ETA)。整个训练和推断流程都可以用Node.js和TensorFlow.js编写,这样就简化了服务器端的技术栈。

1.5 小结

● AI是实现认知性任务自动化的研究。机器学习是AI的子领域,旨在通过学习训练数据,自动发现图像分类这类任务背后的规则。

● 机器学习要解决的核心问题是如何转换数据的表示,从而更好地解决当下的问题。

● 在机器学习中,神经网络可以通过连续的数学运算步骤(层)来转换数据的表示。深度学习领域涉及拥有一定“深度”的神经网络,也就是拥有很多层的神经网络。

● 得益于硬件性能的提升、带标签数据的增长以及算法上的革新,深度学习领域自2010年以来取得了一系列惊人的成就,解决了很多之前难以解决的问题,还创造了很多令人兴奋的新机遇。

● 与其他语言一样,JavaScript和Web浏览器同样适用于训练和部署深度神经网络。

● TensorFlow.js是一个全面、灵活且强大的JavaScript开源深度学习库,也是本书的重点。