Posts Tagged ‘算法’

PARACEL:让分布式机器学习变得简单

星期一, 三月 30th, 2015

在豆瓣,我们常通过机器学习的方式从各种数据中训练出模型,利用这些模型帮助我们理解用户并为大家挖掘出有价值的内容:豆瓣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

算法工程师如何改进豆瓣电影 TOP250

星期四, 七月 4th, 2013

影迷们经常关注的电影排行榜里,一部由100人评出9.0分的电影,和一部由10000人评出8.0分的电影,谁应该排在前面呢?

这是我们算法工程师时常会面对的问题。

一些深度影迷可能会想到 imdb.com (互联网电影数据库) 所采用的贝叶斯公式[见附注],这个公式的思路就是通过每部影片的[评分人数]作为调节排序的杠杆:如果这部影片的评分人数低于一个预设值,则影片的最终得分会向全部影片的平均分拉低。

由此可见,平衡评分人数和得分,避免小众高分影片排前,是这个计算方法的出发点。可问题在于:调节整个榜单的排序主要依赖于这个[评分人数预设值]。如果它设置的很低,那么最终的排序结果,就是每部影片自身评分从高到低在排序;如果它被设置得过高,那么只适用高曝光率的影片。据说 imdb.com 的这个预设值从500一路调整到了25000,遗憾的是这个算法仍然无法很好的解决他们的问题。

我们看看国内电影市场的现状。2013年上映的《疯狂原始人》两个月内在豆瓣电影得到了13万人次的评分,而1974年上映的《教父2》,到目前为止的评分总人数还不到10万人。近几年观影方式的多样化以及影院观影的持续火爆,使得新近上映的影片很轻松地就能获得大量的评分,相较之下,老片子的曝光机会少了很多。显然,继续调节 [评分人数的预设值] 已无法满足当前国内电影排行榜的实际需求。

如何解决这个问题呢?对算法工程师而言,我们通常会先用最基本的算法模型来应对,然后针对该算法带来的问题再修改并衍生出新的算法。比如针对这里的[评分人数预设值],我们可以分出老片和新片两个排行分别对待,也可以把时间因素考虑在内,如此等等。

不过这次我们决定换个做法。

在重新审视过 [豆瓣电影TOP250] 这一产品之后,我们提炼出两个关键指标:

1 它应该具备人群的广泛适应性。

例如一些动漫作品,因为拥有大量的粉丝,容易得到高分。如果采用 imdb.com 的榜单公式计算,这些影片的排名就会很靠前,但它们显然不适合被推荐给非动漫迷。

我们的解决办法,是将电影划分为若干分类,每一分类对应着喜欢此类有显著代表性的人群。如此一来,排序问题就变成了推荐问题,即把某部影片分别向所有类人群做推荐,能被推荐给越多人群的电影也就越具备广泛性。

实际上,实验的结果也证明了《肖申克的救赎》这类电影的人群广泛度远远超越了《EVA》这样的动漫作品。

2 它还需要具备持续关注度,不能昙花一现。

1957年的《十二怒汉》时至今日仍然被人津津乐道,而一些票房大片上映时非常红,过后就乏人问津。如何解决他们的排序关系?

我们取得每部影片在不同时间周期内的收藏人数和评分,将其汇成一条收藏曲线,再分析不同的曲线及其间关系,计算相应的分数。

这样,更新后的算法便初步形成了。算法更新后,榜单产生了一些变化,具体哪些变化?这就去看看吧!

再说两句题外话,其实依靠简单的维度去做排序的榜单,我们平时也见的很多。这也许能解决一时的问题。对比简单排序甚至人工编辑的方式,一个算法模型在结果展示上可能没有优势,但面对环境因素的应变和扩展性上,算法有能力自我学习和进化,相信这也是产品生命力的一种体现吧。

附录: imdb.com 的top榜单公开公式
The formula for calculating the Top Rated 250 Titles gives a true Bayesian estimate:

weighted rating (WR) = (v ÷ (v+m)) × R + (m ÷ (v+m)) × C

where:

R = average for the movie (mean) = (Rating) -单部电影的得分
v = number of votes for the movie = (votes) -单部电影的有效评分人数
m = minimum votes required to be listed in the Top 250 (currently 25000)  -入选top250榜单所需最低的有效评分人数
C = the mean vote across the whole report (currently 7.0)  -所有影片的平均分