当前位置: 首页 > 科技观察

从算法实现到MiniFlow实现,打造机器学习的基础平台

时间:2023-03-12 20:44:24 科技观察

Infrastructure(基础设施)相对于大数据、云计算、深度学习,并不是一个很流行的概念。使用MySQL、Django、Spring、Hadoop开发业务逻辑,但没有真正参与过基础设施项目的开发。在机器学习领域也是如此。借助开源的Caffe、TensorFlow或AWS、GoogleCloudML,可以实现很多业务应用,但框架或平台可能会因为行业的发展、对高可用、高性能的追求而流行或衰落,灵活性和易用性使用的基础设施几乎是永恒的。来自谷歌的王永刚老师在《为什么 AI 工程师要懂一点架构》中提到,科研院所不应该只懂算法,算法实现不等于解决问题,解决问题也不等于现场解决问题。架构知识是工程师进行高效团队合作的通用语言。谷歌凭借其强大的基础设施能力,使得AI研究走在了行业的前面,行业的发展也让深度学习和AutoMachineLearning成为可能。未来会有更多人关注底层架构和设计。因此,今天的主题是介绍机器学习的基础架构,包括以下几个方面:基础架构的分层设计;机器学习的数值计算;重新实现TensorFlow;分布式机器学习平台设计。第一部分,基础设施的分层设计,我们想象一下,如果我们在AWS上使用和编写一个TensorFlow应用程序,我们经历了多少层应用程序抽象?首先,物理服务器和网络宽带就不用说了,通过TCP/IP协议的抽象,我们直接在AWS虚拟机上操作和本地操作没有区别。其次,操作系统和编程语言的抽象,让我们不去感知底层内存的物理地址和读写磁盘的系统调用,只需要按照Python规范编写代码即可。然后,我们使用了TensorFlow计算库。其实我们只需要调用顶层的PythonAPI即可。底层是通过Protobuf序列化和swig进行跨语言研究,然后通过gRPC或者RDMA进行通信。底层是调用Eigen或者CUDA库进行矩阵运算。因此,为了实现软件之间的解耦和抽象,系统架构往往采用分层架构,通过分层屏蔽底层的实现细节,每一层底层都相当于上层应用的基础设施。那么我们如何在分层世界中生存呢?可能有人会想,既然有人实现了操作系统和编程语言,我们还需要关注底层的实现细节吗?这个问题没有标准答案,不同的人在不同的工作时期会有不同的感受。下面我举两个例子。在《为了 1% 情形,牺牲 99% 情形下的性能:蜗牛般的 Python 深拷贝》一文中,作者介绍了Python标准库中copy.deep_copy()的实现。1%的情况是指深拷贝时对象内部可能有引用自己的对象,所以在拷贝的时候需要记录所有拷贝的对象信息,而99%的情况是不会直接应用对象对自己。为了兼容100%的情况,库损失了6倍以上的性能。在深入了解Python源码后,我们可以通过实现深拷贝算法来解决上述性能问题,从而优化我们的业务逻辑。又如阿里巴巴杨军先生在Strata数据大会上分享的《Pluto: 一款分布式异构深度学习框架》,引入了基于TensorFlow的control_dependencies实现GPU显存冷热数据的插入输出,让用户几乎察觉不到它。大大减少内存使用。了解源码的人可能已经发现,TensorFlow的Dynamiccomputationgraph,又名tensorflow/fold项目,也是基于control_dependencies实现的,在声明式机器学习框架中实现动态计算图并不容易。这两个实现在TensorFlow的官方文档中是没有的。只有对源码的理解足够深入,才能在功能和性能上有巨大的突破。因此,如果你是企业中TensorFlow框架的基础设施维护者,突破PythonAPI抽象层是非常有必要的。当你在应用机器学习的时候,不知不觉中用到了很多基础设施的抽象,其中最重要的就是机器学习算法本身的实现。接下来,我们将突破抽象,更深入地理解底层的实现原理。第二部分是机器学习的数值计算。机器学习本质上是一系列的数值计算。因此,TensorFlow不是一个深度学习库,而是一个数值计算库。当我们听到像香农熵、贝叶斯、反向传播这样的概念时,不用担心,这些都是数学,可以通过计算机编程来实现。接触过机器学习的都知道,LR一般指的是逻辑回归(Logisticregression),也可以指线性回归(Linearregression)。前者属于分类算法,后者属于回归算法。两种LR都有一些可以调优的超参数,比如训练轮数(Epochnumber)、学习率(Learningrate)、优化器(Optimizer)等,通过实现这个算法可以帮助我们理解它的原理和调优技巧.下面是Python中线性回归的最简单实现,模型就是y=w*x+b。从这个例子可以看出,机器学习算法的实现并不依赖于Scikit-learn或TensorFlow等库。它本质上是数值运算,不同的语言实现会有性能差异。细心的朋友可能会发现为什么这里w的梯度(Gradient)是-2*x*(y-x*x-b),而b的梯度是-2*(y-w*x-b),如何to保证计算后Loss减少,准确率增加?这是一个数学保证。我们定义Lossfunction(Meansquareerror)为y–w*x–b的平方,也就是说,预测值越接近y,Loss就越高。小,目标就变成了在w和b任意取值下寻找Lossfunction的最小值,所以对w和b取偏导后,可以得到上面两个公式。有兴趣的不妨看看线性回归下MSE偏导数的数学公式证明。逻辑回归类似于线性回归。当是分类问题时,需要对w*x+b的预测结果进行归一化处理。一般采用Sigmoid方法。在Python中,1.0/(1+numpy.exp(-x))这种方式。由于预测值不同,Lossfunction的定义也不同,计算偏导数得到的数值计算公式也不同。有兴趣的也可以看看我的公式推导。你可以看到最终的偏导数非常简单,可以很容易地用任何编程语言实现。但我们自己的实施可能不是最有效的。为什么不直接使用Scikit-learn、MXNet等开源库已经实现的算法呢?我们对这个算法的理解其实是在工程中使用它的一个非常重要的部分。基础。比如在真实的业务场景中,一个样本的特征可能有几百亿甚至上千亿的维度,而通过前面的算法我们了解到LR模型的大小和样本的维度是一样的features,也就是说,一个接受了数百亿维度的样本具有维度特征的模型有数百亿个参数。如果使用标准的双精度浮点数来保存模型参数,那么100亿维度的模型参数至少要超过40G,而且1000亿维度的特征是单机加载不出来的。的。因此,虽然Scikit-learn通过原生接口实现了高性能的LR算法,但它只能在单机上进行训练。由于MXNet本身不支持SpareTensor,超高维稀疏数据的训练效率很低。TensorFlow本身支持SpareTensor,也支持模型并行,可以支持百亿维特征的模型训练,但是没有LR优化效率不是很高。在该场景下,第四范式基于ParameterServer实现了一个支持模型并行和数据并行的超高维、高性能机器学习库。在此基础上,大规模LR、GBDT等算法的训练效率可以满足工程需求。需要。机器学习还有很多有趣的算法,如决策树、支持向量机、神经网络、朴素贝叶斯等,工程上只需要一部分数学理论就可以轻松实现。限于篇幅,这里不再赘述。我们前面介绍的其实就是机器学习中的命令式编程接口。我们提前推导出偏导数的公式,然后像其他编程脚本一样按照代码的顺序执行。我们知道,TensorFlow提供了一个声明式编程接口,通过描述计算图来延迟和优化执行过程。接下来,我们将介绍这方面的内容。第三部分,TensorFlow的重新实现首先大家可能会有疑问,我们需要重新实现TensorFlow吗?TensorFlow灵活的编程接口,基于Eigen和CUDA的高性能计算,支持分布式和HadoopHDFS集成,这些是个人甚至企业都很难完全赶上的实现,而我们可以使用MXNet即使命令式编程接口是需要,并且没有强烈需要新的TensorFlow框架。其实在学习TensorFlow的过程中,通过实现一个类TensorFlow的项目,不仅惊叹于其源代码和界面的精美设计,也加深了对声明式编程、DAG实现、自动偏导等的理解,反向传播等概念的理解。甚至在Benchmark测试中,发现纯Python实现的项目在线性回归模型训练上比TensorFlow快22倍。当然,这是在特定场景下进行压测的结果。主要原因是TensorFlow开销中存在Python和C++的跨语言切换。本项目是MiniFlow,一个实现链式法则,自动求导,支持命令式编程和声明式编程,兼容TensorFlowPythonAPI的数值计算库。如果你有兴趣,可以在这个地址参与开发。下面是两个API的对比图。了解TensorFlow和MXNet(或NNVM)源码的朋友可能知道Op、Graph、Placeholer、Variable这两个抽象概念,都是通过DAG来描述模型的计算流图,所以我们也需要实现类似功能接口。不同于以往的LR代码,Graph-based模型允许用户自定义Loss函数,即用户可以使用传统的Meansquareerror,也可以自定义一个任意的数学公式作为Lossfunction,这就需要框架本身能够自动计算而不是预先实现根据Lossfunction的导数的计算方法。然后是用户可以定义的最小操作,即Op,需要平台实现基本的算子,比如ConstantOp、AddOp、MultipleOp等,当用户实现自定义算子时,可以添加到流程中在不影响框架本身训练过程的情况下自动推导。参考TensorFlow的Python源码,我们在下面定义了Op的基类。所有Ops都应该实现forward()和grad(),方便模型训练时自动推导,通过重载Python算子,开发者可以提供更易用的接口。那么对于常量(ConstantOp)和变量(VariableOp),它们的前向运算就是得到自己的值,而求导时常量的导数为0,偏导时变量的导数为1,其他变量也为0.具体代码如下。其实更重要的是,我们需要实现加法(AddOp)、减法(MinusOp)、乘法(MultipleOp)、除法(DivideOp)、平方(PowerOp)等运算符的正逆运算逻辑,然后根据chain任何复杂数学公式的推导都可以简化为这些基本算子的推导。比如加减法,我们知道两个数相加的导数等于导数的加法,所以根据这个数学原理,我们可以很容易的实现AddOp,MinusOp的实现也类似,所以我就不细说了。乘法和除法比较复杂。显然,两个数相乘的导数不等于导数的乘积。比如x和x的平方,一阶导数相乘得到2x,导数先相乘得到x的平方的3倍。所以需要用到乘数法则,基本公式是,代码实现如下。除法和平方的推导方法也类似,因为在数学上已经证明了,所以只需要写代码就可以实现基本的正反运算。限于篇幅,这里不再详细介绍MiniFlow的源码实现。有兴趣的可以通过上面的Github链接找到完整的源码实现。下面提供了使用相同API接口实现的模型的性能测试结果。对于小批量数据处理,需要MiniFlow在Python/C++运行环境频繁切换的场景下有更好的表现。前面介绍了机器学习算法和深度学习库的实现。不是每个人都有能力重写或优化这部分基础设施。很多时候我们只是这些算法的使用者,但从另一个角度来说,我们有必要维护一个高可用的计算平台来进行机器学习的训练和预测。下面将从这方面介绍如何搭建分布式机器学习平台。第四部分,分布式机器学习平台的设计随着大数据和云计算的发展,实现一个高可用的分布式机器学习平台已经成为基本需求。无论是Caffe、TensorFlow,还是我们自研的高性能机器学习库,都只是解决数值计算、算法实现、模型训练的问题。任务的隔离、调度和故障转移需要由上层平台来实现。那么在设计机器学习全流程的基础平台时,需要涵盖哪些功能呢?首先,必须实现资源隔离。在共享底层计算资源的集群中,用户提交的训练任务不应受到其他任务的影响,CPU、内存、GPU等资源应尽可能隔离。如果你使用Hadoop或者Spark集群,cgroups会默认挂载在task进程上,保证CPU和内存的隔离。随着Docker等容器技术的成熟,我们也可以使用Kubernetes、Mesos等项目来启动和管理用户实现。模型训练任务。二是实现资源调度共享。随着GPU用于通用计算的普及,支持GPU调度的编排工具也越来越多。但是,一些企业仍然拥有专用的GPU卡,无法实现资源的动态调度和共享。这将不可避免地导致计算资源短缺。严重浪费。在设计机器学习平台时,需要尽可能考虑常见的集群共享场景,比如同时支持模型训练、模型存储、模型服务等功能。基准测试的一个典型例子是GoogleBorg系统。然后,平台需要具有灵活的兼容性。目前,机器学习业务发展迅速,针对不同场景的机器学习框架也越来越多。灵活的平台架构,可兼容几乎所有主流应用框架,避免因业务发展而频繁变更基础架构。目前,Docker是一种非常适合的容器格式规范。通过写一个Dockerfile,可以描述框架的运行环境和系统依赖。在此基础上,我们可以在平台上实现TensorFlow、MXNet、Theano、CNTK、Torch、Caffe、Keras、Scikit-learn、XGBoost、PaddlePaddle、Gym、Neon、Chainer、PyTorch、Deeplearning4j、Lasagne、Dsstne、H2O、GraphLab和MiniFlow与其他框架集成。***,需要在机器学习场景下实现API服务。针对机器学习模型开发、模型训练、模型服务三个主要流程,我们可以定义提交训练任务、创建开发环境、启动模型服务、提交离线预测任务等API,使用熟悉的编程语言实现Web服务接口。实现一个类似谷歌的云端深度学习平台,可以参考以下三个步骤。当然,要实现一个涵盖数据引入、数据处理、特征工程、模型评估功能的机器学习平台,还需要集成HDFS、Spark、Hive等大数据处理工具,实现类似Azkaban、Oozie的工作流管理工具.在易用性和低门槛方面做更多的工作。总结***综上所述,机器学习的基础架构包括机器学习算法、机器学习库、机器学习平台等多个层次的内容。根据业务需要,我们可以选择特定的领域进行深入研究和二次开发。使用轮子并根据需要进行改造同样重要。在机器学习和人工智能大行其道的今天,希望大家也能关注底层的基础设施。算法研究人员可以了解更多的工程设计和实现,研发工程师可以了解更多的算法原理和优化。让机器学习在平台上,在真实应用的实际场景中发挥更大的作用。