豆瓣PARACEL:让分布式机器学习变得简单
在豆瓣,我们常通过机器学习的方式从各种数据中训练出模型,利用这些模型帮助我们理解用户并为大家挖掘出有价值的内容:豆瓣 FM 的个性化歌曲推荐、书影音的喜欢也喜欢、首页的豆瓣猜等等。
早期的时候,单机训练的程序基本就能满足需求。一方面数据量不大,另一方面有的模型算一次可以用很长时间,对性能要求就没有那么高。不过很快,随着豆瓣的壮大,我们有了分布式计算的需求。当时 Spark 还没有 Python 接口,豆瓣基于 Spark 的思路开发了 Dpark 系统。Dpark 非常成功,一下子把我们能解决问题的规模扩大了不少。
Dpark 的出现解决了豆瓣大部分的数据需求和一部分机器学习模型的训练需求。然而,对于那些对性能要求较高的模型,Python 并不能达到预期。同时,mapreduce 的计算模式对于机器学习算法来说不够灵活。因此,对于较为复杂的模型的并行训练,我们仍然采用 C++ 加 MPI 这样较为费时费力的方式来开发。
2013 年初,我们从 Jeff Dean 发表在 NIPS12 上的论文《Large Scale Distributed Deep Networks》中了解到 Google 公司用来做深度学习的训练框架 DistBelief。DistBelief 将巨大的深度学习模型分布存储在全局的参数服务器中,计算节点通过参数服务器进行信息传递,很好地解决了随机梯度下降和L-BFGS 算法的分布式训练问题。豆瓣当时并没有做深度学习的需求,但我们意识到机器学习问题可以转化成优化问题,而解优化问题的众多方法和随机梯度下降的过程是类似的:选定初始值,按某种方向,迭代直到收敛。
为了提高分布式算法的开发效率和增强代码被复用的能力,我们决定抽象出一个比 mapreduce 更适合机器学习模型训练的范式做成框架。Paracel 项目就是在这样的背景下产生的,自从 2014 年 3 月在豆瓣内部发布最初版本至今已一年时间,我们决定将它开源。
Paracel 是一个基于参数服务器通信模型的分布式训练框架。参数服务器可以被理解成一个全局分布式的 key-value 存储,用来存储待训练的模型。同时,参数服务器还能通过用户定义的 update 函数进行分布式计算,这时它与 mapreduce 系统中的 reducer 很类似。随着数据量的增长和模型的越来越复杂,单机的内存已经装载不下许多模型的参数了。Paracel 不仅支持数据并行剖分,同时支持模型的并行剖分。开发者可以将训练数据划分到各个计算节点,并将对应的模型在参数服务器端进行划分,使得算法在计算性能和内存使用上都做到可扩展。
Paracel 提供了简单又通用的通信接口,计算节点可以向参数服务器读取、写入、更新模型。在更新操作时,用户可以自定义的 update 函数。与琐碎的 MPI 通信方式相比,计算节点并不需要知道参数存放的具体信息,而是通过统一的接口与参数服务器进行交互,简化了开发的复杂度。值得强调的一点是,基于 Paracel 来开发分布式机器学习算法非常地直观,对于开发者而言,在 Paracel 中实现一个分布式机器学习算法和实现一个串行算法并没有太大区别。事实上,在开发 Paracel 中算法库的过程中,我们通常也是先实现一个单机的版本,然后在其基础上加少量的代码完成并行化的。
Paracel 解决的另一问题是 straggler 问题:由于一些软硬件的原因,节点的计算能力往往不尽相同。对于迭代问题来说,每一轮结束时算得快的节点都需等待算得慢的节点算完,再进行下一轮迭代。这种等待在节点数增多时将变得尤为明显,从而拖慢整体的性能。Paracel 放宽了“每个迭代步都等待”这个约束:当在一轮迭代结束时,算得快的节点可以继续下一轮迭代,但不能比最慢的节点领先参数s个迭代步。当领先超过s个迭代步,Paracel 才会强制进行等待。这样异步的控制方式既从整体上省去了等待时间,也能间接地帮助慢的节点赶上。从优化问题的角度来看,虽然单迭代步收敛得慢了,然而每个迭代步的时间开销变少了,总体上收敛也就变快了。
此外,我们还在 Paracel 项目中开源了一个基于它实现的算法工具集,用户在安装完 Paracel 后就可以用其中的算法工具进行数据处理。我们将会不断地在这个工具集中加入更多的算法。
最后,希望能有更多的人使用 Paracel。如果你想贡献代码,不妨 fork 它在 GitHub 上的代码仓库并给我们提交 pull requests.
项目主页:paracel.io
20 分钟教程:paracel.io/docs/quick_tutorial.html
API 文档:paracel.io/docs/api_reference.html