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

Gorgonia,一个专为Go语言设计的机器学习库:基准测试TensorFlow和Theano

时间:2023-03-22 14:44:02 科技观察

Gorgonia是一个促进Go中机器学习的库,旨在简化涉及多维数组的数学方程式的编写和评估。如果这听起来很像Theano或TensorFlow,那是因为它们的想法非常相似。具体来说,Gorgonia和Theano一样是相当低级的;但像Tensorflow这样的高级。项目地址:https://github.com/chewxy/gorgonia项目介绍:https://blog.chewxy.com/2016/09/19/gorgonia/可执行自动微分可执行符号微分可执行梯度下降优化可执行数值稳定性提供了很多帮助快速创建神经网络的便利函数(与Theano和Tensorflow相当)支持CUDA/GPGPU计算(尚不支持OpenCL,需要发送拉取请求)将支持分布式计算1.为什么选择Gorgonia?Gorgonia的使用主要是为了方便开发人员。如果您广泛使用Go堆栈,则可以在熟悉且舒适的环境中创建生产就绪的机器学习系统。机器学习/人工智能通常大致分为两个阶段:(a)试点阶段,构建、测试和重新测试多个模型,以及(b)部署阶段,在测试和试点后部署模型。这两个阶段是不可或缺的,有着不同的作用,就像数据科学家和数据工程师的区别一样。原则上,两个阶段使用的工具也不同:实验阶段通常使用Python/Lua(使用Theano、Torch等),之后会用性能更高的语言重写模型,比如C++(使用dlib、mlpack等)。当然,现在差距正在慢慢缩小,人们经常共享可用于填补差距的工具,例如Tensorflow。然而,Gorgonia的目标是在Go环境中完成同样的事情。目前Gorgonia的性能相当好,它的速度与Theano和Tensorflow相当(由于目前Gorgonia中的CUDA错误,尚未完成官方基准测试;此外,由于实现可能略有不同,因此很难比较一个确切的tit-for-tat模型)。2、安装安装包为go-get:goget-ugithub.com/chewxy/gorgonia。Gorgonia依赖很少,非常稳定,所以目前不需要托管工具。下表是Gorgonia调用的外部包列表,按依赖顺序排列(子包已省略):3.保持更新Gorgonia项目有邮件列表和Twitter账号,官方更新和公告发布到这两个网站:https://groups.google.com/forum/#!forum/gorgoniahttps://twitter.com/gorgoniaML4.使用Gorgonia通过创建计算图然后执行它来工作。请将其视为一种仅限于数学函数的编程语言;其实这应该是用户思考的主要实例。它创建的计算图是一个AST。MicrosoftCNTK的BrainScript可能是说明构建计算图与运行它不同的最佳示例,因此用户应该对两者使用不同的思维方式。虽然Gorgonia的实现不像CNTK的BrainScript那样强制分离思想,但语法确实有一点帮助。这是一个示例-要定义数学表达式z=x+y,您应该这样做:packagemainimport("fmt""log"."github.com/chewxy/gorgonia")funcmain(){g:=NewGraph()varx,y,z*Nodevarerrerror//定义表达式x=NewScalar(g,Float64,WithName("x"))y=NewScalar(g,Float64,WithName("y"))z,err=Add(x,y)iferr!=nil{log.Fatal(err)}//compileintoaprogramprog,locMap,err:=Compile(g)iferr!=nil{log.Fatal(err)}//createaVMtoruntheprogramonmachine:=NewTapeMachine(prog,locMap)//setinitialvaluesthenrunLet(x,2.0)Let(y,2.5)ifmachine.RunAll()!=nil{log.Fatal(err)}fmt.Printf("%v",z.Value())//输出:4.5}你可以发现它比其他类似的包更冗长。例如,Gorgonia并没有编译成可调用的函数,而是专门编译成需要TapeMachine才能运行的程序;此外,它需要手动调用一个Let(...)。作者认为这是一件好事——它可以将人类的思维转化为机器思维。当我们试图找出问题所在时,它很有帮助。5.虚拟内存系统当前版本的Gorgonia有两个虚拟内存系统:TapeMachineLispMachine它们具有不同的功能并接受不同的输入。TapeMachine通常更擅长执行静态表达式(即计算图不会改变);由于其静态特性,它适用于一次编写、多次运行的表达式(如线性回归、SVM等)。LispMachine将图形作为输入并直接在图形的节点上执行。如果图形发生变化,只需创建一个新的轻量级LispMachine来执行它。LispMachine适用于创建可变大小的递归神经网络等任务。在Gorgonia发布之前,有第三种基于堆栈的虚拟内存,类似于TapeMachine,但可以更好地处理人工梯度。当作者解决所有问题时,它可能会重见天日。6.微分Gorgonia进行符号微分和自动微分,这两个过程之间存在细微差别。作者认为这样理解是最恰当的:自动微分是在运行时进行的微分,与图的执行同时发生;符号分化是在写作阶段完成的分化。这里的“runtime”当然指的是表达式图的执行,而不是程序的实际运行。通过介绍这两个虚拟内存系统,很容易理解Gorgonia是如何进行符号微分和自动微分的。使用与上面相同的示例,读者可以看到这里没有进行任何区分。这次让我们用LispMachine试试:,Float64,WithName("x"))y=NewScalar(g,Float64,WithName("y"))z,err=Add(x,y)iferr!=nil{log.Fatal(err)}//setinitialvaluesthenrunLet(x,2.0)Let(y,2.5)//默认情况下,LispMachine执行前向模式和后向模式执行m:=NewLispMachine(g)ifm.RunAll()!=nil{log.Fatal(err)}fmt.Printf("z:%v\n",z.Value())xgrad,err:=x.Grad()iferr!=nil{log.Fatal(err)}fmt.Printf("dz/dx:%v\n",xgrad)ygrad,err:=y.Grad()iferr!=nil{log.Fatal(err)}fmt.Printf("dz/dy:%v\n",ygrad)//输出://z:4.5//dz/dx:1//dz/dy:1}当然,Gorgonia也支持更传统的符号微分,比如在Theano中:packagemainimport("fmt""log"."github.com/chewxy/gorgonia")funcmain(){g:=NewGraph()varx,y,z*Nodevarerrerror//定义表达式x=NewScalar(g,Float64,WithName("x"))y=NewScalar(g,Float64,WithName("y"))z,err=Add(x,y)iferr!=nil{log.Fatal(err)}//symbolicallydifferentiatezwithregardstoxandy//thisaddsthegradientnodestothegraphgvargradsNodesgrads,err=Grad(z,x,y)iferr!=nil{log.Fatal(err)}//compileintoaprogramprog,locMap,err:=Compile(g)iferr!=nil{log.Fatal(err)}//createaVMtoruntheprogramonmachine:=NewTapeMachine(prog,locMap)//setinitialvaluesthenrunLet(x,2.0)Let(y,2.5)ifmachine.RunAll()!=nil{log.Fatal(err)}fmt.Printf("z:%v\n",z.Value())xgrad,err:=x.Grad()iferr!=nil{log.Fatal(err)}fmt.Printf("dz/dx:%v|%v\n",xgrad,grads[0])ygrad,err:=y.Grad()iferr!=nil{log.Fatal(err)}fmt.Printf("dz/dy:%v|%v\n",ygrad,grads[1])//Output://z:4.5//dz/dx:1|1//dz/dy:1|1}虽然Gorgonia的人能嗅到老版本在dualValue条件下支持前向模式微分的痕迹,但是它目前仅执行反向模式自动微分(也称为反向传播),并且未来可能会返回正向模式微分。7.Graphs关于计算图或表达式图的说法确实很多,但究竟是什么呢?请把它想象成你想要的数学表达式的AST。这是上述示例的图表(但带有矢量和标量加法):顺便说一下,Gorgonia的图表打印功能相当不错。这是方程y=x2及其导数的图形示例:该图易于阅读。表达式是自下而上构建的,而导数是自上而下构建的。所以每个节点的导数大致处于同一水平。带有红色轮廓的节点表示它们是根节点,而绿色轮廓表示叶子节点,带有黄色背景的节点表示输入节点,虚线箭头表示哪个节点是指向该节点的梯度节点。具体来说,比如c42011e840(dy/dx)表示输入c42011e000(即x)的梯度节点。8.NodeRendering节点渲染如下:补充说明:如果是输入节点,则不会显示Op线。如果没有值绑定到节点,将显示NIL。但是如果有值和梯度,它会尽量显示节点绑定的值。9.使用CUDA此外,还有额外要求:需要CUDAtoolkit8.0。安装此程序将安装nvcc编译器,这是使用CUDA运行代码所必需的。去安装github.com/chewxy/gorgonia/cmd/cudagen。这是cudagen程序的安装URL。运行cudagen会为Gorgonia生成CUDA相关的代码。请务必使用UseCudaFor选项,并确保在您的代码中手动启用CUDA操作。runtime.LockOSThread()必须在运行虚拟内存的主函数中调用。CUDA需要线程亲和性,因此必须锁定OS线程。1.例子那么,我们如何使用CUDA呢?假设有一个文件main.go:import("fmt""log""runtime"T"github.com/chewxy/gorgonia""github.com/chewxy/gorgonia/tensor")funcmain(){g:=T.NewGraph()x:=T.NewMatrix(g,T.Float32,T.WithName("x"),T.WithShape(100,100))y:=T.NewMatrix(g,T.Float32,T.WithName("y"),T.WithShape(100,100))xpy:=T.Must(T.Add(x,y))xpy2:=T.Must(T.Tanh(xpy))prog,locMap,_:=T.Compile(g)m:=T.NewTapeMachine(prog,locMap,T.UseCudaFor("tanh"))T.Let(x,tensor.New(tensor.WithShape(100,100),tensor.WithBacking(tensor.Random(tensor.Float32,100*100))))T.Let(y,tensor.New(tensor.WithShape(100,100)),tensor.WithBacking(tensor.Random(tensor.Float32,100*100))))runtime.LockOSThread()fori:=0;i<1000;i++{iferr:=m.RunAll();err!=nil{log.Fatalf("iteration:%d.Err:%v",i,err)}}运行时.UnlockOSThread()fmt.Printf("%1.1f",xpy2.Value())}如果正常运行:gorunmain.goCUDA不会用到。如果程序要使用CUDA运行,它必须调用:gorunmain.go即使这样,也只有tanh函数使用CUDA。2.说明使用CUDA的要求复杂度主要与其性能有关。正如DaveCheney的名言:cgo不是Go。不幸的是,使用CUDA必然需要cgo;要使用cgo,需要进行很多权衡。所以解决办法就是把CUDA相关的代码嵌套在build标签cuda里面,默认不使用cgo(好吧,就一点点,还是可以用cblas或者blase)。安装CUDAtoolkit8.0的原因是:有很多CUDA计算能力,为它们生成代码会导致一个巨大的二进制文件,没有任何好处。相反,用户将倾向于针对他们特定的计算能力进行编译。最后,使用CUDA对操作的明确规范的要求是由于cgo调用的成本。目前正在进行其他工作以启用批量cgo调用,但在完成之前,此解决方案将是“升级”某些操作的关键。3.CUDA支持的运算到目前为止,只有非常基本的简单运算可以支持CD??UA:element-wiseunaryoperations:abssincosexplnlog2negsquaresqrtinv(reciprocalofanumber)cubetanhsigmoidlog1pexpm1softplusElementarybinaryoperations-只有算术运算支持CUDA:addsubmuldivpow根据笔者个人项目的大量分析,发现真正重要的是tanh、sigmoid、expm1、exp和cube这几个激活函数。其他使用MKL+AVX的操作工作正常,并不是神经网络缓慢的主要原因。4.CUDA改进在一个小的基准测试中,仔细使用CUDA(通常称为sigmoid)显示了比非CUDA代码有很大的改进(假设CUDA内核是原始的和未优化的):BenchmarkOneMilCUDA-83003348711ns/opBenchmarkOneMil-85033169036ns/op10.API稳定性Gorgonia的API目前不稳定,从1.0版本开始会逐渐稳定。1.0版本定义为测试覆盖率达到90%,相关Tensor方法已经完成。11.路线图这是Gorgonia的目标,按重要性排列:测试覆盖率超过80%。目前Gorgonia覆盖率为50%,张量为80%。更高级的操作(例如einsum)。当前的张量运算符非常原始。包中的TravisCI。套装中的工作服。清除测试。测试是多年积累的结果,适当重构有助于测试。如果可能,使用表驱动测试。为了提高性能,特别是应该进行重新分配以尽量减少系统类型的影响。将Op接口从半输出公开/更改为全输出以提高Op可扩展性(或创建ComposeOp类型以实现可扩展性)。这样每个人都可以定制Ops。重构CuBLAS和Blase包以遵循CUDA实现。分布式计算。试图将作业分散到多台机器上并相互通信至少3次,但均未成功。更好地记录做出某些决定的原因,并在高层次上设计Gorgonia。HigherOrderDerivativeOptimizationAlgorithm(LBFGS)无导数优化算法XII.目标Gorgonia的主要目标是成为一个基于机器学习/图形计算的高性能库,可以跨多台机器扩展。它应该将Go(简单的编译和部署过程)的吸引力带到机器学习领域。路还很长,但我们已经迈出了第一步。次要目标是提供一个探索非标准深度学习和神经网络相关事物的平台,包括neo-hebbianlearning、corner-cut算法、进化算法等。显然,由于您最有可能在Github上阅读,因此Github将构建包工作流程的主要部分来完成包。参见:CONTRIBUTING.md十三、贡献者和重要贡献者我们欢迎任何贡献。但是还有一类新的贡献者,称为关键贡献者。重要的贡献者是对图书馆的运作方式和/或其周围环境有深入了解的人。以下是重要贡献者的一些示例:为特定功能/方法的原因/机制以及不同部分如何相互交互编写了大量文档编写代码并测试Gorgonia更复杂的连接和测试部分,并接受至少5个拉取请求对包的某些部分提供专家分析(假设您可能是优化函数的浮点运算专家)回答至少10个支持性问题更新一次(如果有人使用Gorgonia)。十四、如何获得支持今天最好的支持方式就是在Github上留言。15.常见问题为什么runtime.GC()在测试的时候好像是随机调用的?答案很简单:包旨在以特定方式使用CUDA:具体来说,CUDA设备及其上下文绑定到指定虚拟内存,而不是包。这意味着对于创建的每个虚拟内存,每个设备的每个虚拟内存都会创建一个不同的CUDA实例。因此,在其他可能使用CUDA的应用程序中,一切正常(但这需要进行压力测试)。CUDA实例只有在虚拟内存被垃圾回收时才会被销毁(在终结函数的帮助下)。测试中创建了大约100个虚拟内存,大部分垃圾回收是随机的;当环境使用过多时,会导致GPU显存耗尽。因此,在任何可能使用GPU的测试结束时,都会调用runtime.GC()以强制进行垃圾回收以释放GPU内存。人们不太可能在生产中启动过多的虚拟内存,所以这不是问题。如果有什么问题,请在Github上留言,我们会想办法给虚拟内存增加一个Finish()方法。16.许可Gorgonia在Apache2.0变体下获得许可。它在所有意图和目的上都与Apache2.0具有相同的许可,除了重要的贡献者(例如软件包的商业支持者)之外,没有人可以直接从商业上受益。但有可能直接从Gorgonia的衍生产品中获利(例如在产品中使用Gorgonia作为库)。任何人都可以将Gorgonia用于商业目的(例如商业软件)。其他各种版权声明这是在写Gorgonia过程中灵感改编的软件包和库(上面已经声明使用的Go包):【本文为专栏机器心翻译原创,微信公众号《机器之心(id:almosthuman2014)》】点此查看该作者更多好文