我们如何从0到1搭建了训练、部署机器学习模型的开源库NeoML

2020 年 7 月 17 日

我们如何从0到1搭建了训练、部署机器学习模型的开源库NeoML

近日,数字智能公司ABBYY于GitHub平台发布了NeoML,这是一个用于构建、训练和部署机器学习模型的开源库,同时支持深度学习和传统的机器学习算法。这款跨平台框架针对在云环境、桌面和移动设备上运行的应用进行了优化。据文中所示的测试,NeoML与其他主流开源库相比,可以在所有设备上为预训练图像处理模型提供15%-20%的性能优化。


NeoML 是一款跨平台的 C++库,允许用户组建一个完整的机器学习(以下简称 ML)模型开发周期。它的主要目标是在各种平台上简单且有效地运行现有模型,这些模型甚至可以是通过其他框架创建的。ABBYY 的 AI 传播者 Ivan Yamshchikov 表示:


NeoML的发布是我们努力对全行业的AI创新做出贡献的结果。开发者可以享受到我们所分享框架的推理速度、跨平台能力,尤其是其在移动设备上的潜力,同时他们的反馈和贡献将使库得到发展和改进。我们很高兴能促使人工智能的进步,支持机器学习在更多高价值和有影响力的用例中得到应用。


那么,或许不少人会有这样的疑问,再多一个机器学习库对我们有什么好处?下面我将介绍 ABBYY 是如何创建这个库,中途遇到了什么困难以及最后是如何解决的。


NeoMLGitHub 地址:https://github.com/neoml-lib


创建 NeoML 的初衷


机器学习与人工智能的发展一直都是 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 种不同类型的层,这使得它几乎可以实现所有的现代网络架构。



新的架构在任务中证明了有效性后,我们开始尝试扩展功能。 下一步要考虑的就是如何提高效率了。


CPU 计算


神经网络通常意味着庞大的计算量,没有低级别的优化,你将一无所获。我们首先进行优化的是我们的主要使用平台——Windows 与 x86 处理器,在这个平台上,我们希望尽可能提高效率。


神经网络中的大部分运算都可以归结到 BLAS(基础线性代数子程序),而 x86 上最好的 BLAS 非因特尔 MKL 莫属。 剩余运算则必须用 SIMD 独立实现,我们仅使用 SEE 指令,之前做过 AVX/AVX2 的实验,可惜在运算效果方面没有太大收益,于是为了降低支持成本,我们没有选择使用它们。当因特尔发布 MKL-DNN 时,我们非常激动:终于可以不用自己写这些东西了!然而在对比之后,我们的包反而在运行上快了 20%左右,但我们并没有放弃这个想法。


目前,NeoML 在 x86 上运行得还算不错,但仍有很大优化空间,我们计划在未来版本中对其进行优化。


GPU 计算


对我们来说,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 下载到我们库中的选项。当然,完整格式还是不支持的,因为它的规格太大了,并且还有很多版本。更主要的问题在于,不同框架使用的语义也不同。举个例子,同一个模型用不同的框架上传到 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 框架来构建、训练和部署机器学习模型


  • 神经网络支持一百多种层类型

  • 支持CPU和GPU,快速推理

  • 支持语言:C++,Java,Object C

  • 传统机器学习:20余种算法(分类、回归、聚类等)

  • 支持ONNX

  • 跨平台:同样代码可以在Windows、Linux、macOS、iOS以及Android上运行


在任何地方均可部署


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 格式的支持,当然,也生产力也会有一定的提高。


原文链接:


https://towardsdatascience.com/abbyy-neoml-how-we-made-the-open-source-machine-learning-library-and-why-we-need-it-dc0a13e4c3f


2020 年 7 月 17 日 10:251387

评论

发布
暂无评论
发现更多内容

架构师训练营 1 期 -- 第二周总结

曾彪彪

极客大学架构师训练营

架构师训练营第二周学习感悟

听夜雨

极客大学架构师训练营

架构师训练营第 1 期 -- 第二周学习总结

发酵的死神

极客大学架构师训练营

第二周 框架设计作业

钟杰

极客大学架构师训练营

第二周作业

alpha

极客大学架构师训练营

第二周总结

睁眼看世界

极客大学架构师训练营

架构师训练营第 2 周学习总结

netspecial

极客大学架构师训练营

架构师训练营学习总结——第二周

文智

极客大学架构师训练营

架构师训练营第二周课程笔记及心得

Airs

架构师01期,第二周课后作业

子文

SpringBoot 异步任务

hepingfly

Java springboot 异步任务

架构师训练营第 1 期 -- 第二周作业

发酵的死神

极客大学架构师训练营

week1--作业一

hero_genlot

极客大学架构师训练营

深拷贝与浅拷贝到底是什么

C语言与CPP编程

c++ 面试题 C语言

架构师训练营第 1 期 - 第二周学习总结

Anyou Liu

极客大学架构师训练营

依赖倒置及接口隔离原则

天天向上

极客大学架构师训练营

架构师训练营 Week2 总结

lggl

总结 极客大学架构师训练营

回首挑灯看剑谱 - Week2 - 学习总结

小粽

开源推荐:国内3大主流前端UI表单设计器,千万不要让领导知道

互联网应用架构

Vue Element antd

Mac mini 2020上手体验

墨凡

Mac

架构师训练营第二周作业

听夜雨

极客大学架构师训练营

架构一期 - 甘霖 - Week2 - 作业一

小粽

什么是依赖倒置原则,为什么有时候依赖倒置原则又被称为好莱坞原则?

魏小龙

敏捷开发 依赖倒置原则

软件设计的基本原则

天天向上

极客大学架构师训练营

Spring 5 中文解析数据存储篇-JDBC数据存储(上)

青年IT男

Spring5

训练营第二周作业1

Yangjing

极客大学架构师训练营

架构一期第二周作业

Airs

十七张图玩转Node进程——榨干它

执鸢者

前端 进程 Node

依赖倒置原则

知行合一

软件设计原则

食堂就餐卡系统设计-作业

Kenny

作业

荷之美 | 中国荷苑

xcbeyond

生活 摄影 摄影征文 荷花

我们如何从0到1搭建了训练、部署机器学习模型的开源库NeoML-InfoQ