近日,数字智能公司ABBYY于GitHub平台发布了NeoML,这是一个用于构建、训练和部署机器学习模型的开源库,同时支持深度学习和传统的机器学习算法。这款跨平台框架针对在云环境、桌面和移动设备上运行的应用进行了优化。据文中所示的测试,NeoML与其他主流开源库相比,可以在所有设备上为预训练图像处理模型提供15%-20%的性能优化。
NeoML是一款跨平台的C++库,允许用户组建一个完整的机器学习(以下简称ML)模型开发周期。它的主要目标是在各种平台上简单且有效地运行现有模型,这些模型甚至可以是通过其他框架创建的。ABBYY的AI传播者Ivan Yamshchikov表示:
NeoML的发布是我们努力对全行业的AI创新做出贡献的结果。开发者可以享受到我们所分享框架的推理速度、跨平台能力,尤其是其在移动设备上的潜力,同时他们的反馈和贡献将使库得到发展和改进。我们很高兴能促使人工智能的进步,支持机器学习在更多高价值和有影响力的用例中得到应用。
那么,或许不少人会有这样的疑问,再多一个机器学习库对我们有什么好处?下面我将介绍ABBYY是如何创建这个库,中途遇到了什么困难以及最后是如何解决的。
NeoMLGitHub地址:https://github.com/neoml-lib
机器学习与人工智能的发展一直都是ABBYY数字智能技术的一部分,而随着时间的的推移,我们清楚地认识到ML需要统一;这也使得我们开始思考,如何以最简洁高效的方式来改进我们的机器学习工厂。公司中几乎所有的代码都是用C++写的,这意味着我们需要一个C/C++的解决方案。然而,几乎没有C++框架能满足我们所有的需求。当然,现在也有单独的库能实现各种功能,例如Liblinear、XGBoost、Scikit-learn、Libsvm、Caffe、TensorFlow等等。下一步我们开始分析它们的功能。
大多数的库只适合做研究而不适合生产。如果要应用到生产环境的话,它们的代码需要做大量修改:加入日志记录、错误处理,以及内存管理等。此外还需要很多额外功能,以及不同的构建系统和额外的依赖项。不是所有人都有C++接口,库的发展很快,它们的变化并不总是可预测的,它们的性能和稳定性会引来质疑,没有人能承诺提供支持。我们别无选择,只能自己动手丰衣足食,创建自己的库,把需要的东西都收集在里面,然后由我们自己决定它未来的道路。
Liblinear、Libsvm、Scikit-learn、以及XGBoost,这些已有的开源库都非常有帮助。我们从分析这些现有库开始,取其精华加以优化,实现了类似的想法。举例来说,我们只采用能放入内存的样本,只在CPU上运行,并且不使用低级别优化。如此一来,经典算法的运行速度问题并不在我们的考量范围内,因此我们并未在这方面花费太多心思去优化,即便如此,我们仍然成功超过了上述类似算法的速度。
融合之后的效果很好:训练更快,质量更高,开发速度也更快了。程序员再也不用重新发明车轮了,只需使用默认设置,就能立刻得到之前需要花费至少几天时间才能拿到的实验结果。于是库中出现了解决分类、回归和聚类问题的方法。
在我看来,经典算法的实现并不困难,倒是神经网络的情形比较有趣。
经典算法从本质上来说是一种使用共同原语(primitives)的独立方法,但神经网络的实现就要难上许多。其中除了数学和算法问题外,还出现了非明显架构和低级别优化问题。
在观察过Caffe和TensorFlow在当时的库后,我们认为Caffe的想法更接近我们的愿景。这也是为什么我们的数据是用blob表示,而非tensor。我们希望在操作中使用更高层次的概念,在训练中对网络进行修改,在使用过程中进行完善,最终在GPU上为用户公开透明地组织运算。
在NeoML中,网络是一个有向图,节点代表层,而边则代表从一些层输出到其他层输入的数据传输。层是执行某些操作的元素,而操作可以是任何东西,从改变输入数据的形状或计算简单的数学函数,到卷积或者LSTM等等。层可以随时从网络中添加或移除。网络中的所有数据——输入、输出以及层之间的数据传输,都以blob的形式呈现。一个blob就是一段连续的内存,该库利用一个特殊的独立于平台的接口,并不直接与blob内存交互。因此算法部分得以独立于真正执行计算的设备。举例来说,使用CUDA实现接口,就可以在GPU上进行计算。
说到网络的架构,我们从卷积网络开始,加入了各种卷积层、池、全连接层、激活,以及损失函数,并使用了一个简单的梯度下降作为优化器。
随后,又添加了对循环网络LSTM和GRU、高级优化器,甚至是更多的激活和损失函数,CTC、CRF等的支持。
目前,该库约有100种不同类型的层,这使得它几乎可以实现所有的现代网络架构。
新的架构在任务中证明了有效性后,我们开始尝试扩展功能。 下一步要考虑的就是如何提高效率了。
神经网络通常意味着庞大的计算量,没有低级别的优化,你将一无所获。我们首先进行优化的是我们的主要使用平台——Windows与x86处理器,在这个平台上,我们希望尽可能提高效率。
神经网络中的大部分运算都可以归结到BLAS(基础线性代数子程序),而x86上最好的BLAS非因特尔MKL莫属。 剩余运算则必须用SIMD独立实现,我们仅使用SEE指令,之前做过AVX/AVX2的实验,可惜在运算效果方面没有太大收益,于是为了降低支持成本,我们没有选择使用它们。当因特尔发布MKL-DNN时,我们非常激动:终于可以不用自己写这些东西了!然而在对比之后,我们的包反而在运行上快了20%左右,但我们并没有放弃这个想法。
目前,NeoML在x86上运行得还算不错,但仍有很大优化空间,我们计划在未来版本中对其进行优化。
对我们来说,GPU计算主要是用于学习的,训练大多时候都是在公司内部或是云端。这样我们就可以选择进行训练的设备,生活就变得简单了起来。例如,如果客户没有AVX,我们就可以不用支持SSE。因此,他们决定使用CUDA实现GPU的计算引擎,并在其所支持的英伟达显卡上进行计算。这个决策主要是因为有专门的库:cuDNN、cuBLAS、cuSparse等等。不过在未来我们可能会因为开发过程中bug不断或者运行效率低下而放弃cuDNN,而使用我们自己的实现。 其余的这些库都有很好的表现,我们就不用自己写内核了。
引用GPU后,效果立竿见影,很多网络的训练都加快了一个数量级。得益于此,我们的开发速度也更快,模型的质量也有所提高。
在主平台上成果显著,也已经建立了有效的训练后,我们开始考虑将库分发到其他的平台。
ABBYY的主要开发是在Windows上进行的,用于训练和测试的服务器也是Windows的,因此,第一个版本的库也是仅支持Windows这个操作系统。但ABBYY的产品也可以在其他平台上运行,很快,我们也开始将库移植到Linux和macOS上。移植过程很简单,因为我们唯一的依赖,英特尔MKL也提供其他OS的版本,而CUDA支持的训练也不需要。唯一的难点就是微软Visual Studio编译器和GCC与Clang的差异,这点解决起来很快。
现在,因为Windows版本的支持还有很多不足之处,我们开始主动使用Linux版本的库与竞争对手进行对比测试。此外,还要考虑在云端的学习网络任务。因此,在下一个版本中,我们会发布支持Linux上CUDA的NeoML版本。
ABBYY正在开发并销售图像处理和文本识别的SDK,其中包括可在手机上使用的SDK。因此,随着神经网络开始进入移动平台的SDK中,其运行效率也成了问题。此时,我们再次考虑使用第三方解决方案的可能性。在评估了安卓的TensorFlow Lite和iOS的Core ML的集成后,我们认为同时使用多个框架工作会产生不合理成本,还不如自己完善,哪怕这样效率会很低。
我们开始着手为ARM创建一个“计算引擎”。用NEON替代SSE,用Eigen替代MKL,在几星期内,我们制作了第一个在ARM CPU上运行的库的版本。结果这个解决方案在效率上完全符合我们的要求,甚至在速度上也超过了同类的产品。当然TF Lite和Core ML都在那之后取得了很大的进步,但我们也完成了一些重要的优化,其中大部分都和x86版本重叠,并且成本不高。不过,也有一些针对ARM的优化,其中最重要的是我们自己的矩阵乘法。得益于此,我们的速度超过Eigen库20%,于是舍弃了后者。
目前,NeoML在CPU上的运行速度和同行相比相差无几,很适合我们的需求。
另外,为了避免在iOS和安卓端推出大量成品模型,我们为Object C和Java语言添加了推理包装器。
几乎所有现代安卓和iOS手机都配备有独立的GPU。我们觉得这点很有趣,并开始考虑要怎么用了。第一次的实验是用RenderScript做的,什么结果都没有,并且运行起来慢得可怕。然而,用OpenCL、Vulkan和Metal做的实验则效果不错。在大型网络上,GPU可以提供5-7倍的效益。在小型网络上由于开销的存在,CPU拔得头筹。即使是在大型网络上,也并不是所有的GPU都可以带来收益,只有造价昂贵的顶级型号GPU才能有良好的效果。 此外,不同的型号GPU还需要单独写代码。举个例子,为Adreno优化的着色器不一定能适用于Mali。总体来说,虽然移动端GPU话题争议不断,但前景还是很值得期待的。
目前,我们已经实现了可以运行在Vulkan和Metal上的计算引擎,我们将其应用在一部分的任务后,继续进行它的开发。不得不说,在移动端GPU上进行计算有很多可以讨论的,由于其在很多方面都和台式机上的计算不同,这个话题值得再单独开一篇文章。
我们的模型已经可以完全自给自足了,模型学习自己的网络,轻松将其整合到桌面应用中,然后无需任何成本就可以移植到手机端平台。唯一的遗留问题是:在阅读新的文章,探索新的架构及其应用实例时,我们的数据科学家会不断遇到其他的框架。而为了开发能够高效且迅速地处理任何问题的模型,他们需要做到将第三方框架的模型转换到我们的框架中。
新的ONNX格式是一大进步,尽管它还很年轻,对许多框架的支持也不算尽善尽美,但它一直在积极开发之中,我们认为这是目前问题的最好解决方案。我们提供将神经网络模型从ONNX下载到我们库中的选项。当然,完整格式还是不支持的,因为它的规格太大了,并且还有很多版本。更主要的问题在于,不同框架使用的语义也不同。举个例子,同一个模型用不同的框架上传到ONNX上,其结果可能完全不同。因此我们决定在这个问题上将重点放到PyTorch上。当然,其他ONNX模型也可以用,可能就是效率不高。
因此,模型的开发过程可能会是这样:模型的第一次测试是用PyTorch进行,然后存储模型于ONNX中,接着从ONNX加载到NeoML中,再对NeoML模型重新训练,测量其速度和质量,最后模型再进入修订或生产中。
到此为止,我们拥有了支持机器学习模型整个开发周期所需的一切。
我们下一步决定将库开源,这样其他人也可以因此受益。这是库开发过程中新的一步,我们与社区分享自己的最佳实践,而作为回应,社区会给我们反馈以意见和建议,让库变得更快也更方便。
目前库已经具备一些独特的功能,可以作为各种平台上不同应用中启动模型的有效手段。希望能有类似脚本的开发者欣赏NeoML,并有可能在不远的将来加入到库的开发中来。
我们尝试通过定期与同行(通常都是TensorFlow)对比库在任务上的效率,以理解我们当前所处的水平。举例来说,下面对比了我们的库和在公共网络上直接访问MobileNetV2架构的TorchVision包的速度,该架构通过训练可以对ImageNet数据集进行分类。网络输入尺寸为224x224x3,测试是在台式机CPU和我在隔离期间能接触到的几款手机上进行的。
在一台搭载酷睿i5-4400,运行Ubuntu 20.04的PC上,我们启动网络一万次,结果如下所示。
内存消耗情况如下:
在万次启动的安卓手机上,情况如下:
在iOS手机上情况:
特别一提,手机上做运行时间测试,真的是一件吃力不讨好的事。如果你想,你甚至可以测得任何结果,这里没有列出来的几个测量结果则更是没有任何的指标性。但多次启动后的整体情况还是能看的出来的。
更详细的分析和优化则通常需要用不同的处理器计数器,如cpu_cycles、cpu_instructions、cache_access、cache_miss、branch_count、branch_miss、bus_cycles等等。从这些测量结果也能看出,这两个库的运行情况大致相同。
使用强大的NeoML框架来构建、训练和部署机器学习模型
ABBYY工程师使用NeoML处理计算机视觉和自然语言任务,包括图像预处理、分类、文档排版分析、OCR以及从结构化与非结构化文档中提取数据。你可以在云端、on-perm、浏览器或者设备上部署模型。
NeoML支持开放神经网络交换(Open Neural Network Exchange,ONNX),一个全球开放可互操作的ML模型生态系统,它提高工具的可兼容性,让开发者可以更容易使用正确的工具组成达成他们的目标。ONNX标准是由微软、Facebook以及其他合作伙伴共同支持开发的开源项目。
ABBYY邀请开发者、数据科学家、以及商业分析师在GitHub上使用并参与贡献NeoML。NeoML的代码采用Apache License 2.0授权,这家公司提供个性化的开发者支持、实时审查报告、定期更新,以及性能提升。在未来,ABBYY计划添加新的算法和架构,并进一步提高使用框架后算法可以达到的速度。
综上所述,我们有了一套ML模型的全周期开发和实现,这可以说是一个还算不错的解决方案。目前,NeoML在公司的所有产品中几乎都有应用体现,它的效率每天都能得到证明。
机器学习是ABBYY的首要任务之一。我们计划通过定期发布新版本来更新库,而在下一个版本中,我们计划添加一个Python包装器,支持新的网络架构,扩展对ONNX格式的支持,当然,也生产力也会有一定的提高。
原文链接:
领取专属 10元无门槛券
私享最新 技术干货