本文中,作者基于WWW’19论文提供的线索,详细解读了在浏览器中实现深度学习的可能性、可行性和性能现状。具体而言,作者重点分析了7个最近出现的基于JavaScript的DL框架,并对比了具体框架支持哪些DL任务。
深度学习(DeepLearning,DL)是一类利用多层非线性处理单元(称为神经元)进行特征提取和转换的机器学习算法。每个连续层使用前一层的输出作为输入。近十年来,深度学习技术的进步极大地促进了人工智能的发展。大量的人工智能应用,如图像处理、目标跟踪、语音识别和自然语言处理,都对采用DL提出了迫切的要求。因此,各种DL框架(Frameworks)和库(Libraries),如TensorFlow、Caffe、CNTK等,被提出并应用于实践。目前,这些应用程序可以在诸如Windows、Linux、MacOS/iOS和Android等异构开发环境上运行,此外,可以使用各种命令式编程语言开发,例如Windows上的C/C++、iOS和MacOS上的Objective-C,以及Android上的Java。不过,受限于DL框架和库的特点,例如训练数据量大、网络结构复杂、网络层级多、参数多等,通过本机程序调用运行DL的AI算法或模型的运算量非常大。
最近,关于DL的一种应用趋势是应用程序直接在客户端中执行DL任务,以实现更好的隐私保护和获得及时的响应。其中,在Web浏览器中实现DL,成为了人工智能社区关于客户端DL支持的重要研究目标。浏览器中的DL是用JavaScript实现的,依靠浏览器引擎来执行。基于DL的Web应用程序可以部署在所有平台的浏览器中,而不管底层硬件设备类型(PC、智能手机和可穿戴设备)和操作系统(Windows、Mac、iOS和Android)。这就使得在浏览器中实现DL具有非常良好的适配性能和普适性能,不会对客户端的选择有诸多限制。另外,HTML5、CSS3,特别是JavaScript语言的进步,使得支持创建DL驱动的Web应用程序具有良好的性能。得益于WebGL的发展,目前主流浏览器如GoogleChrome、MozillaFireFox、Safari等,都可以更好地利用显卡来加速DL任务。
1、浏览器中支持的深度学习功能
在这一章节中,我们以文献[1]为参考主线,重点探讨现有的框架提供了哪些特性来支持在浏览器中实现各种DL任务。我们首先介绍了进行分析的几个框架,然后从两个方面比较了这些框架的特性:提供的功能和开发人员的支持。对于所提供的功能,主要检查每个框架是否支持DL应用程序开发中常用的一些基本功能。而对于开发人员支持,主要讨论一些可能影响开发和部署DL应用程序效率的因素。
1.1选择的框架
为了选择最新的浏览器支持的DL框架,作者在GitHub上搜索关键字“deeplearningframework”,并用JavaScript语言过滤结果。然后选择了GitHub上星数超过1000的前7个框架[1]。对每个框架的具体介绍如下:
TensorFlow.js[2]:2018年3月由Google发布,是一个inbrowser机器学习库,支持使用JavaScript在浏览器中定义、训练和运行模型。TensorFlow.js由WebGL提供支持,并提供用于定义模型的高级API。TensorFlow.js支持所有Keras层(包括Dense、CNN、LSTM等)。因此,很容易将原生TensorFlow和Keras预先训练的模型导入到浏览器中并使用Tensorflow.js。
ConvNetJS[3]:是一个Javascript库,最初由斯坦福大学的AndrejKarpathy编写。ConvNetJS目前支持用于分类和回归的常用神经网络模型和代价函数。此外,它还支持卷积网络和强化学习。然而遗憾的是,尽管ConvNetJS可能是在TensorFlow.js之前最著名的框架,但其在2016年11月后已经不再维护了。
Keras.js[4]:抽象出许多框架作为后端支撑,包括TensorFlow、CNTK等。它支持导入Keras预先训练的模型进行推理。在GPU模式下,Keras.js的计算由WebGL执行。然而,这个项目也已经不再活跃。
WebDNN[5]:由东京大学发布的WebDNN号称是浏览器中最快的DNN执行框架。它只支持推理(训练)任务。该框架支持4个执行后端:WebGPU、WebGL、WebAssembly和FallbackpureJavaScript实现。WebDNN通过压缩模型数据来优化DNN模型,以加快执行速度。
brain.js[6]:是一个用于神经网络的JavaScript库,它取代了不推荐使用的“brain”库。它为训练任务提供DNN、RNN、LSTM和GRU。该库支持将训练好的DL模型的状态用JSON序列化和加载。
synaptic[7]:这是一个不依赖于JavaScript架构的神经网络库,基本上支持任何类型的一阶甚至二阶RNN。该库还包括一些内置的DL架构,包括多层感知器、LSTM、液态机(Liquidstatemachines)和Hopfield网络。
Mind[8]:这是一个灵活的神经网络库。核心框架只有247行代码,它使用矩阵实现来处理训练数据。它支持自定义网络拓扑和插件,以导入mind社区创建的预训练模型。然而,这个项目也已经不再活跃。
1.2所提供的功能
训练支持
大多数框架都支持在浏览器中完成训练和推理任务。然而,Keras.js和WebDNN不支持在浏览器中训练DL模型,它们只支持加载预训练的模型来执行推理任务。
支持的网络类型
有些框架并不是针对通用DL任务的,所以它们支持的网络类型有所不同。具体来说,TensorFlow.js、Keras.js和WebDNN支持三种网络类型:DNN、CNN和RNN。但ConvNetJS主要支持CNN任务,不支持RNN。brain.js和synaptic主要支持RNN任务,不支持CNN网络中使用的卷积和池化操作。Mind只支持基本的DNN。
支持的层类型
所有框架都支持以层(Layer)为单位构建神经网络。TensorFlow.js的层API支持49种不同的层,包括密集层、卷积层、池化层、RNN、归一化等。其他框架支持的层类型较少,这也与它们所支持的网络类型有关。需要注意的是,TensorFlow.js的核心API是以类似于原生TensorFlow的方式实现的,它结合了各种操作来构建计算图。synaptic是一个架构无关的框架,支持构建任何类型的一阶甚至二阶RNN网络。
支持的激活/优化类型
TensorFlow.js为开发者提供了关于激活/优化器的最多种类的选择。对于激活函数,其他框架只支持基本的sigmoid或ReLU。对于优化器,其他框架主要支持基本的随机梯度下降(SGD)。
支持GPU加速
TensorFlow.js是唯一支持GPU加速训练任务的框架。TensorFlow.js、Keras.js和WebDNN支持使用GPU来加速推理任务。WebDNN还支持更先进的技术—WebGPU,但WebGPU只被Safari的技术预览版所支持。
1.3开发者支持
文档。TensorFlow.js、ConvNetJS、WebDNN和synaptic提供的文档已经完成。Keras.js的文档并不完整,brain.js只有几篇教程。
演示。所有的框架都为开发者提供了入门的demo。TensorFlow.js提供了最丰富的demo,涵盖了广泛的用例。
从其他框架导入模型。TensorFlow.js、Keras.js和WebDNN支持在Python中从原生DL框架中导入模型,并且它们都提供了用于转换模型的Python脚本。TensorFlow.js支持由TensorFlow和Keras训练的模型。Keras.js支持Keras模型。WebDNN支持从TensorFlow、Keras、Caffe和Pytorch导入模型。在支持使用其他DL框架的预训练模型的情况下,可以大大减少开发工作量。
API保存/加载模型。所有支持浏览器中训练任务的框架都有保存模型的API。所有框架都有用于加载模型的API。
支持服务器端(Node.js)。所有框架都支持Node.js。这样的功能使得浏览器内部的计算可以卸载到远程服务器上。
库大小。表1中列出了需要加载到浏览器中的库文件的大小。ConvNetJS是最小的,只有33KB。TensorFlow.js和brain.js的文件大小非常大,分别为732KB和819KB。小型库更适合在浏览器中加载应用,因为所有的文件都要按需下载。
表1.基于JavaScript的框架在浏览器中支持深度学习的情况
2、浏览器中深度学习框架的性能
本节重点讨论现有框架的复杂度和后端处理器(CPU或GPU)对浏览器运行训练和推理任务时性能的影响。
2.1实验准备
如前所述,不同框架支持的网络类型是不一样的。在实验中采用最基本的全连接神经网络作为模型。对于运行DL任务的数据集,使用经典的MNIST手写数字识别数据库。待训练的模型有784个输入节点和10个输出节点。为了研究模型复杂度对性能的影响,对模型进行了不同的配置设置。参数包括:1)神经网络的隐藏层数(深度),范围在[1,2,4,8];2)每个隐藏层的神经元数(宽度),范围在[64,128,256]。深度和宽度的范围是基于“客户端DL模型应该是小尺寸,以便能够在客户端上运行”的假设而设定的。在训练过程中,批处理大小始终设置为64。
为了研究CPU和GPU的性能差异,使用一台HaseeT97E笔记本电脑,它的独立显卡是Nvidia1070Max-Q(配备8GBGPU内存)。CPU为Inteli7-8750H,其中包含IntelHDGraphics630,使得实验中也能够使用集成显卡来验证性能。在下文中,用nGPU和iGPU分别表示独立Nvidia显卡和集成Intel显卡的GPU。所有的实验都在Ubuntu18.04.01LTS(64位)上的Chrome浏览器(版本:71.0.3578.10dev64位)上运行,且使用各个框架最新发布的版本。
2.2训练性能
表2中给出了在训练过程中每个框架的CPU利用率的统计数据。110%是CPU利用率的上限。由于JavaScript引擎是单线程的,因此不能使用多核处理器的功能,它只能最大限度地提高单核的使用率。之所以CPU利用率超过100%,是因为其他内核和用户空间组件偶尔会在其他线程中同时运行。在CPU后端,TensorFlow.js有时无法最大化单个核心的利用率,其平均CPU利用率仅为82.1%。同时,作者发现在GPU(iGPU和nGPU)后端运行训练任务时,由于大部分计算都在GPU上,所以CPU的利用率并不高。iGPU上的训练比nGPU上训练的CPU利用率要高5-7%左右。
表2.训练过程的CPU利用率(%)
2.3推理性能
表3.模型文件的大小(MB)
2.4其它收获
根据以上结果我们可以看到,在浏览器能够胜任的小规模全连接神经网络中,ConvNetJS在训练和推理方面的表现都是最好的。不过,由于ConvNetJS已经不再维护,功能较少,开发者可能还是需要寻找替代品。Tensorflow.js是唯一可以利用GPU(nGPU和iGPU)加速训练过程的框架。它功能丰富,性能与ConvNetJS相当。所以TensorFlow.js对于训练和推理都是一个不错的选择。作者不建议在小规模的模型上使用GPU作为后端,因为GPU计算能力的优势并没有得到充分利用。
最后,为什么ConvNetJS在这些框架中的所有任务都有最好的性能。对于流程逻辑相同的同一模型,性能差异很可能由不同的实现细节来解释。为此,作者在完成相同训练任务时比较了ConvNetJS和TensorFlow.js的函数调用堆栈(FunctionCallStack)。令人惊讶的是,ConvNetJS的调用堆栈深度只有3,而TensorFlow.js是48。这样的结果表明,不同框架之间性能差异的一个可能原因是深度调用堆栈,这会消耗大量的计算资源。
3、浏览器DL框架与原生DL框架的比较
我们在上文已经分析了目前主要的浏览器DL框架结构、特点,也通过实验证明了不同框架的效果。那么,在浏览器中运行DL和原生平台上运行DL的性能差距有多大?作者比较了TensorFlow.js和Python中的原生TensorFlow的性能,两者都是由Google发布和维护的,并且有类似的API,因此所比较的结果是足够公平的。
3.1基于预训练模型的推理
作者使用Keras官方提供的预训练模型来衡量TensorFlow.js和原生TensorFlow在这些经典模型上完成推理任务时的性能。
3.1.1TensorFlow.js的局限性和浏览器约束
Keras官方提供了11个预训练模型。虽然在原生TensorFlow上运行这些模型时是一切正常的,但当作者在浏览器中使用TensorFlow.js运行它们时却遇到了一系列错误。作者认为,这些错误是由于TensorFlow.js本身的限制以及浏览器施加的限制所造成的。例如,对于NasNetLarge模型,浏览器会抛出以下错误信息“truncatedNormalisnotavalidDistribution"。对于ResNetV3模型,浏览器会抛出错误信息"Unknownlayer:Lambda"。出现这两个错误的原因是TensorFlow.js仍在开发中,到目前为止只提供了对有限的转换模型的支持。许多用户定义的操作算子TensorFlow.js是不支持的,例如,TensorFlow.js不支持使用RNN中控制流操作算子的模型。当尝试使用VGG16或VGG19时,浏览器会抛出"GLOUTOFMEMORY"的错误信息,这意味着GPU内存溢出。VGG16模式适用于超过1GB的GPU内存,不过实验中使用笔记本的GPU内存是8GB(Nvidia1070Max-Q),所以应该不是由内存所导致的问题。作者分析,这样的错误是由于浏览器的限制造成的。
表4.部分Keras预训练模型
3.1.2结论
在客户端DL的实时性要求下,如果用户想要达到10FPS(每秒帧数)的体验,就需要考虑使用更强大的独立显卡。而通过iGPU加速的移动网络模型也能满足要求。如果要求是达到1FPS,iGPU也完全可以满足。但如果只能使用CPU,那么在浏览器中运行这些很常见的模型看起来就太过于沉重了。
3.2决策树分析
最后,为了深入探索不同因素是如何影响DL在浏览器和原生框架上的性能差距的,作者建立了一个基于决策树的预测模型,具体研究各种因素的重要性。
3.2.1实验设置
作者考虑4个影响DL在浏览器和原生平台上性能差距的因素,如表5所示,包括后端(CPU或GPU)、任务类型(训练或推理)以及模型的深度和宽度。在DNN和RNN模型中,宽度是指每层神经元的数目。在CNN模型中,宽度是指卷积层中使用的核数。对于DNN、CNN和RNN,作者从Tensorflow.js官方样本示例中选取模型。DNN和CNN模型用于识别MNIST数据集上的手写数字,RNN模型用于从尼采的著作中生成文本。根据Tensorflow.js官方样本的数值集合确定深度和宽度的范围。
表5.造成性能差距的主要因素
3.2.2方法
作者首先为所有因素生成一棵完全生长的、未经修剪的决策树。这样一来,每个叶子只包含一个配置。然后,将树的深度设置为因子的数量,以防止在一条路径上多次使用一个因子。图6显示了DNN、CNN和RNN的决策树。
3.2.3结果
第二个重要因素是三个模型的任务类型。执行训练任务的比率较高,而推理任务的表现差距较小。例如,对于CPU后端的DNN模型,训练任务TensorFlow.js平均比原生TensorFlow慢33.9倍,推理任务TensorFlow.js执行速度比原生TensorFlow平均慢5.8倍。
最后,DNN和RNN的决策树都表明,深度和宽度的重要性取决于任务在哪个后端执行。在CPU后端,宽度的重要性大于深度的重要性,而深度在GPU后端扮演更重要的角色。然而,在CNN的情况下,对于训练任务来说,宽度比深度对性能差距的影响更大。表6总结了上述实验的研究结果。
表6.浏览器中DL的主要影响因素
4.示例
4.1TensorFlow.js[2]
TensorFlow.js的API主要是以TensorFlow为蓝本,并添加一些针对JS环境的例外。和TensorFlow一样,其核心数据结构是Tensor。TensorFlow.jsAPI提供了从JS数组创建张量的方法,以及对张量进行操作的数学函数。图7给出TensorFlow.js的结构示意图。TensorFlow.js由两组API组成:提供了低层次的线性代数操作(如矩阵乘法、张量加法等)的OpsAPI和被设计为在浏览器和服务器端运行的TensorFlow.js。在浏览器内部运行时,它通过WebGL利用设备的GPU来实现快速并行化浮点计算。在Node.js中,TensorFlow.js与TensorFlowC库绑定,可以完全访问TensorFlow。TensorFlow.js还提供了一个较慢的CPU实现作为后备(为简单起见图7中省略),用纯JS实现。这个后备设置可以在任何执行环境中运行,当环境无法访问WebGL或TensorFlow二进制时,会自动使用。
图7.TensorFlow.js的结构示意图。
TensorFlow.js提供了LayersAPI,它尽可能地镜像了KerasAPI,包括序列化格式。这实现了Keras和TensorFlow.js之间的双向门:用户可以在TensorFlow.js中加载一个预先训练好的Keras模型,对其进行修改、序列化,然后在KerasPython中加载回来。
和TensorFlow一样,TensorFlow.js的一个操作代表了一个抽象的计算(例如矩阵乘法),它独立于它所运行的物理设备。操作调用到内核,内核是数学函数的特定设备实现。为了支持特定设备的内核实现,Tensor-Flow.js有一个Backend的概念。Backend实现了内核以及方法,如read()和write(),这些方法用于存储支持张量的TypedArray。张量与支持它们的数据是解耦的,因此像reshape和clone这样的操作实际上是自由的。
此外,TensorFlow.js还支持自动微分,提供一个API来训练模型和计算梯度。在讨论TensorFlow.js中的自动微分问题之前,让我们先来回顾一下常见的自动微分方法。目前有两种常见的自动微分方法:Graph-based和eager。Graph-based引擎提供了一个API来构造一个计算图,并在之后执行。在计算梯度时,引擎会静态分析Graph来创建一个额外的梯度计算Graph。这种方式的性能比较好,而且容易实现序列化。而eager微分引擎则采取了不同的方式。在eager模式下,当一个操作被调用时,立即进行计算,这可以很容易的通过Print或使用debugger来检查结果。另一个好处是,当模型在执行时,主机语言的所有功能都是可用的。用户可以使用原生的if和while循环,而不需要使用专门的控制流API。TensorFlow.js的设计目标是优先考虑易用性而不是性能,所以TensorFlow.js支持eager自动微分方法。
TensorFlow.js的官网上提供了多个在浏览器中运行的在线演示和示例。比如,下面是一个通过网络摄像头玩Pacman游戏,使用一个预先训练好的MobileNet模型,并使用内部的MobileNet激活训练另一个模型,从用户定义的网络摄像头中预测4个不同的类。
下面的示例在TensorFlow中对钢琴演奏的MIDI进行了PerformanceRNN训练。然后,它被移植到浏览器中,在TensorFlow.js环境中仅使用Javascript运行。钢琴样本来自SalamanderGrandPiano。TensorFlow.js的中文网站上还有其它有趣的示例,大家可以直接上去尝试一下。
4.2ConvNetJS[3]
ConvNetJS的官网上提供了分类(Classifier)、回归(Regression)、自编码器(AutoEncoder)等示例。
这些实例并不需要消耗很多资源,在普通PC的浏览器中,运行一两分钟就可以初步进行分类等操作。我们从网站上选择了一些分类示例如下。更多的示例可以直接到网站上下载,大家也可以尝试直接用自己的浏览器搭建一个DL模型。
4.3Paddle.js[9]
最后,我们来介绍一下Paddle.js。2020年5月,百度飞桨联手百度APP技术团队开源了飞桨前端推理引擎Paddle.js。Paddle.js是百度Paddle的web方向子项目,是一个运行在浏览器中的开源深度学习框架,也是国内首个开源的机器学习Web在线预测方案。Paddle.js可以加载训练好Paddle.js模型,或者将Paddle.fluid模型通过Paddle.js的模型转换工具变成浏览器友好的模型进行在线预测使用。
Paddle.js使用现成的JavaScript模型或将其转换为可在浏览器中运行的paddel模型。目前,小型Yolo模型可在30ms内运行完毕,可以满足一般的实时场景需求。Paddel.js的部署示例如下,Github上有完整的部署过程说明,大家也可以去试用一下。
本文我们基于WWW’19论文提供的线索,详细地了解了在浏览器中实现深度学习的可能性、可行性和性能现状。具体地,重点分析了7个最近出现的基于JavaScript的DL框架,并对比了具体框架支持哪些DL任务。文中的实验将不同任务中浏览器DL和原生DL的性能进行比较。虽然,目前关于浏览器中实现DL的研究仍然处于早期阶段,但是,从本文的研究中仍然可以看出浏览器DL框架有很多很好的表现。例如,在完成某些任务时JavaScript的DL性能要优于原生DL,浏览器DL框架能够有效利用集成显卡等等。
当然,目前浏览器中的DL框架还有着很大的改进空间,距离大规模的推广和应用还有很大差距。但是,随着硬件设备的不断改进、性能的不断提升,浏览器本身的性能和智能化水平都在不断地提高。相应的,浏览器中的DL框架的性能也会越来越好。由上文的分析,我们认为,未来浏览器中DL框架的发展可以沿着多个方向:一是,可以提升浏览器中DL框架的执行性能,使其能够满足实时性的应用需求;二是,可以不断完善和增加浏览器中DL框架所适用的任务场景;三是,可以继续完善上下游的工具链,提升浏览器中DL框架的易用性,降低使用成本。
最后,无论对于AI还是对于web浏览器,浏览器中的DL框架的发展都会对其产生深远的影响。我们希望,关于浏览器中实现深度学习的探索工作可以为人工智能时代的Web应用的未来提供一点启示。
本文参考引用的文献:
分析师介绍:
仵冀颖,工学博士,毕业于北京交通大学,曾分别于香港中文大学和香港科技大学担任助理研究员和研究助理,现从事电子政务领域信息化新技术研究工作。主要研究方向为模式识别、计算机视觉,爱好科研,希望能保持学习、不断进步。