Skip to content


向量化与并行计算

应用场景决定知识的储备与工具的选择,反过来,无论你选择了什么样的工具,你一定会努力地把它改造成符合自己应用场景所需的那个样子。从这个道理来说,我选择了R作为数据挖掘人员手中攻城陷池的那把云梯,并努力地把它改造成自己希望的那个样子。

我最初接触到专门用于科学计算的工具,是大名鼎鼎的matlab,正如它帮助了无数中国学生顺利毕业的赫赫功劳一样,它是我对于向量化计算的启蒙老师。用过matlab的人都会对其循环结构的效率无法忍受,不知道是否有意而为之的这样的设计缺陷,迫使人们要想真正地用好它,就得接受它提供的向量化计算的思想,在掌握了这个专门为高效计算而设计的计算思想之后,你会发现自己获得的不单是计算效率上的极大提升,还有算法设计思想上高屋建瓴式的跨越。

毕竟matlab是更适合于研究领域的商业软件,而且是闭源的,毕业后不久,我就选择了R作为matlab的替代品,看中的正是R在向量化计算支持之余的灵活性及丰富的第三方库,似乎天生就是数据挖掘人员最趁手的那把刀。因为我需要的就是这样的一个计算环境,它不单是一门编程语言,也不必是一个已经很完备的工具。它就是这样的一个环境,拥有很多的各领域相关的工具包,我可以不必操心太多过于底层的或与工作主题没有直接关系的问题,同时当不满意时我又具有对它最自由的掌控。实际上,我寻求的就是matlab与C之间的一个平衡点。R是一个面向科学工程计算特别是统计计算的工具,与matlab一样,其循环结构的效率也无法让人满意,所以,我们必须利用向量化的编程范式,必要时采用并行/分布计算(因为,向量化本质上就是一种并行计算,也是我们通常理解那种并行计算的天然先驱)。而这一切,在R面前,都不是什么问题。

所谓向量化

wikipedia中的定义是:Vectorization is the more limited process of converting a computer program from a scalar implementation, which processes a single pair of operands at a time, to a vector implementation which processes one operation on multiple pairs of operands at once。向量化计算是一种特殊的并行计算的方式,相比于一般程序在同一时间只执行一个操作的方式,它可以在同一时间执行多次操作,通常是对不同的数据执行同样的一个或一批指令,或者说把指令应用于一个数组/向量。

像R和matlab这样的现代科学软件包,都会以向量化作为其计算的基本特点(即使python的numpy包也是如此),所以在R的基本运算中,随处可见向量化计算的影子,以下仅举几例,以让读者了解向量化是多么地简单和直观:

向量取值:V[1:10]  (把get操作应用于向量V的不同元素)

向量赋值:V[1:10] <- seq(1,10)  (把set操作应用于一个序列与向量V的对应元素)

apply系列:lapply(V, mean)  (跟python的map函数类似,是向量化最直接的表达形式)

矩阵运算:A + B;A %*% B  (矩阵的基本运算也是向量化的典型形式)

这些操作很常见,以致于我们都没有意识到这就是一种有助于提高计算性能的向量化计算,更忘了由此而把这样的思想扩展到我们算法设计的更多方面。熟练使用它,你获得不单是计算上的好处,还有对问题理解上的整体性。

向量化的处理方式是现代计算机的一个特点,无论硬件还是软件上,都提供了支持。

硬件上的支持:Intel’s MMX, SSE, and ARM’s NEON instruction sets support such vectorized loops.(摘自wikipedia:http://en.wikipedia.org/wiki/Vectorization_(computer_science))

软件上的支持:著名的线性代数运算包blas对矩阵运算的自动并行化。例如,在R里执行如下的简单命令,在不需要作任何额外工作的情况下,就可以自动地实现并行化(前提是你的机器安装了blas)。

1
2
M = matrix(5000*5000, 5000, 5000)  ## 生成一个5000*5000的矩阵
S = M %*% M    ## 作矩阵乘法,在多核并安装了blas的机器里,会自动并行

应用向量化的思维方式去解决问题:

当我们习惯了用C语言的单步循环的思想来思考问题的时候,要把思维切换到向量化的方式是件比较困难的事件,以下显示一个例子,看用向量化的思想是怎么去解决问题,这样的描述又是多么美。

任务:对于稀疏矩阵M,让其第i(i=1…m)行的各非零元素减去某个值w[i]。

正常的循环式思维的解决方案比较直观,作两层循环,让第i行非零元素减去w[i]即可,但这样的操作在脚本语言特别是像R这样的科学计算语言里执行效率不高,而当你面对的是一个稀疏矩阵时,问题又会变得复杂。

向量化的解决方案如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## 利用向量化的计算范式设计算法
## 实例:稀疏矩阵不同行的各元素减去某个值
 
library(Matrix)
generate <- function(nr, nc, sr){
  L = nr*nc
  M = Matrix(data=0, nrow=nr, ncol=nc)
  idx = sample(1:L, as.integer(sr*L))
  val = rnorm(sr*L, mean=1)
  M[idx] = val
  return(M)
}
 
M = generate(10000, 5000, 0.1)  ## 生成稀疏矩阵
V = rnorm(10000)  ## 矩阵各行减去的值
N = M
N@x = 1  ## 复制并设置矩阵
A = Diagonal(nrow(N), V) %*% N  ## 把V的值放到新矩阵的各行非零单元中
M@x = M@x - A@x  # M = M-A

除去数据准备阶段,真正用于解决问题的只有第16到19行的4行命令,十分短小精悍,但需要一定的线性代数知识去理解这个过程。

从向量化到并行计算:

除了计算机硬件与科学计算包的支持外,向量化计算还是并行计算的天然先驱,如果你向量化后的算法效率仍然不佳,可以考虑把它并行化。

R那浩如烟海的第三方包里提供了大量支持并行计算的包,这里选择的是snowfall这个包,它可以简单地构建一个计算集群,把计算并行分布到集群的不同节点进行计算。以下通过一个例子比较循环,向量化以及并行三种方式在效率上的差异。

实战案例:for循环,apply,snowfall(并行)的比较。

任务:对一个矩阵每列排序并取回前10个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
library(snowfall)
 
mysort <- function(x){
#    replicate(2, sort(x))
    return(sort(x)[1:10])
}
 
do_for <- function(M){
    ans = matrix(0, 10, ncol(M))
    for(i in 1:ncol(M)){
        ans[,i] = sort(M)[1:10]
    }
}
 
do_apply <- function(M){
    return(apply(M, 2, mysort))
}
 
do_snowfall <- function(M, ncl){
    sfInit(parallel=TRUE, cpus=ncl)  ##初始化集群环境
    ans <- sfApply(M, 2, mysort)  ##把任务分发到各个slave
    sfStop()  ##关闭集群
    return(ans)
}
 
#M = matrix(rnorm(10000000), 100, 100000)
#system.time(ans <- do_for(M))
#system.time(ans <- do_apply(M))
#system.time(ans <- do_snowfall(M, 4))  ##用4个核并行

在我的服务器里运行的结果是这样的,do_for循环基本不可用,do_apply可以在一分钟内运算完毕,而do_snowfall的时间为do_apply的一半。当取消第4行的注释,即增加各个子任务的计算负荷后,do_apply与do_snowfall的计算时间都增加,而do_snowfall的时间变为do_apply的三分之一。

结论1:向量化的计算方式比起传统的循环计算有极大的性能优势。

结论2:由于并行的过程为:master把任务分解,分发到多个slave进行运算,slave返回结果到master。所以多核计算并不一定比最优的单核计算快,要看性能的瓶颈在slave还是在master上。

结论3:适合并行/分布计算的情景主要有两种:一是各slave的计算耗费为瓶颈,并行到多核能减少计算时间,越是slave耗时型的计算并行收益越大;二是一台机器的内存不足以进行整体的计算,分布到多台机器计算能把内存占用分开,这种情况下即使分布计算比单机慢也是可以接受的。

map-reduce扯扯关系:

Map-reduce是google三驾马车之一,试图把所有计算都转换为map和reduce两种基本的运算方式,而达到良好的可并行性,从而实现计算上的可扩展性。虽然并不是每个公司都能实现像map-reduce那样的巨型计算框架,但是如果你能熟练地使用向量化的思想设计你的算法,那其实就是一个map-reduce的超集。向量化计算这个编程范式比map-reduce要复杂,但本质上都是使用了两种基本的运算,如果你把apply想像为map,把矩阵或矢量的乘运算想像为reduce,它们就是一体的。你总可以把向量化的计算通过apply和矩阵运算来实现,如果有一天你拥有了一个类map-reduce的计算框架,根据它们的对应关系,你的算法迁移就会非常地方便,甚至,你可以在你已经实现的向量化算法集当中抽取出一些共性来搭建自己的map-reduce系统。

别忘了一切优化的终极准则:过早的优化是魔鬼。

并行计算或分布式计算是为数据量与计算量日益膨胀的情况下所必须考虑的,但它的逻辑结构与维护成本也要高得多,如果你的应用场景还没达到需要进行并行或者分布计算的程度,没必要淌这趟混水。但是,向量化计算的编程范式却是很重要的,一个用向量化计算方式实现的算法,不但在当时获得了效益,而且其可扩展性也必定是强悍的,因为正如前文所述,向量化计算就是并行计算的天然先驱。除此之外,经常用向量化的方式来思考你的问题,你一定能感受到一种整体之美。

后记:

以上内容是从我参加第三届中国R语言会议的讲稿中整理出来的,以讲向量化的思想为主,R语言为辅,当时的PPT可以从这里下载(PPT上内容比较少,可以下载下来看备注部分)。我一直认为,通过这样的讲演,除了布道,更重要的是能够对自己一段时间以来的思考与工作做一个总结,其间无论是从自身整理还是从他人的提问,自己从中都能有所收获。根据当场及事后一些听众的疑问,我后续整理了下面一些内容。

如果问题很难向量化怎么办:并不是说R中不能使用for式的循环,事实上只有一层的for循环,并且对性能没有太大的影响的话,是不需要作过多的优化的。如果是两层for循环,可以考虑用apply去掉一层,如果有三层的循环,那很有可能就是你的算法有问题了。如果你的问题确实很难转换成向量化的形式,又或者说你已经懒于这样去做,那么不妨把最难于转换的一部分用C来实现,然后给R做一个接口,剩下的再考虑向量化会简单很多。我提倡的是结合R自有函数与向量化思想进行编程,因为R的自有函数大多都是用底层语言实现好的,效率有保证。

有人疑问是不是所有算法都可以并行:非基本的算法或多或少都是可以并行的,像kmeans,虽然从整体来说是一个迭代依赖的非并行式算法,但在每一步的迭代中却是可以实施并行的。应用map-reduce框架基于这么一个假设,你的问题,总可以找到一种算法来实现,这种算法可以或大部分可以用map加reduce基本操作来实现。如果向量化能跟map-reduce对应起来,那么也可以认为,你的问题,总能找到一种可以用向量化思想表示的算法来完全或部分解决,如果有些部分很难向量化,参考上一个问题。

我选择的算法本身就很复杂:有这么一个说法,如果你的数据不足够多,信息不够完整,你会想出很多奇思怪招来从中获得结论,实际上很多科学上精巧而复杂的算法是基于这种情况而设计出来的。而在实际中如果你的数据急剧膨胀,信息与噪声都足够地多,你的问题焦点就变为如何用一个或一些简单有效算法或算法的组合来提取有价值的信息。如果你在小数据集的时候习惯了采用复杂的算法,当你的应用场景转变了,数据规模变了,却仍旧沿用原有的思维方式,是注定要尴尬的(仅针对工程应用环境,而非科学应用环境)。

本文链接:http://www.wentrue.net/blog/?p=945

Posted in 未分类.


写在世界杯前

足球曾带给我生命中很多的第一次,以及很多的朋友。人生有很多值得回忆的片段,就像生活的珠玑一般。如果这些珠玑有一根闪亮的丝线串联起来,就是一个完美的艺术品了。而这次的丝线,是世界杯。

最初接触到足球时,94年世界杯已经远去,所以我只能一次次地从队友的口中重温巴乔那落寞的背影。那时,即使失败那也是一种悲情的浪漫。

98法兰西之夏,战火初燃,正好赶上我在积极地备战中考,绿茵场上的球星们在努力地追逐着大力神杯,而我也正希冀着找回自己三年前失落的那所重点中学。天气很炎热,每天晚自习后,我都会走到学校门口的小店里,要一碗豆腐脑,顺便瞟几眼屏幕里已经稍有认识的球员,然后匆匆赶回宿舍休息或是夜战。那时,我的眼中其实只有中国国家队,97年的金州兵败,是我第一次体验了作为中国球迷的苦涩。记得那时我完整地看完的一场小组赛是阿根廷对日本,因为那时我认识巴蒂、奥特加,还有传球失误导致失利的日本天才中场中田英寿。当然,那时我最喜欢的是无所不能的外星人罗纳尔多(那时我们叫他朗拿度)。及至中考结束,在家里,我第一次熬夜看球,看着巴西杀入决赛,然后金杯旁落,我第一次体会到在足球场上,球星不并等同于胜利。我也开始明白,作为一个球迷或是一名球员,失意是一件随时都要准备面对的事情。那时,我正逐步走出自己的童话王国。

02日韩世界杯时,我已经在读大二。由于宿舍没有电视,当时网络看直播也不成熟,我和同学相约跑到隔壁邮科院的电影院去看球,因为电影院不放电影了,专门摆了一个电子屏幕放球赛。于是我第一次有了全场人一起欢呼一起叹息的团体看球体验。其实只去过一次,因为第一次聚众大屏幕看球的体验就是眼睁睁地看着阿根廷负于英格兰,后来小组出局。随后的我,就像生活中大多数时候的状态一样,再也找不到做一件事情的理由与目的。所以,那届世界杯,除了中国队的比赛,我看过的赛事只有寥寥几场,也许是看似空闲的大学光阴,被我挥霍到网络与游戏世界中去了吧,抛却了曾经的钟爱与梦想,去追逐一些虚拟的快乐。那段时间,是我人生的第一段黑暗时期。但是,德国人的顽强,让我看到了一丝力量。这个衰落中的帝国,这个近年来已经越来越难找到闪耀巨星的日耳曼战车,每一次的关键赛事,却总能勇往直前。

06年的德国世界杯是迄今为止我看过最完整的一届世界杯,基本上绝大部分的比赛我都通过直播或录像看过。当时正是研一的尾声,那通常也是一个中国学生在学校系统学习的最后一段时间。基本上,我可以静下心来,做一些自己想做的事了。那一次我才了解到,原来世界杯最精彩的不是决赛,而是小组赛。当全世界球迷的目光都聚焦在那个沸腾的国家,当所有人都还有希望还有期待的时候,才是最美好的时刻。当几家欢笑几家愁一幕幕上映,当一个个希望化为一张张回国的机票,这个全球球迷的嘉年华就只退化为那座金杯。虽然最后捧杯的是铜墙铁壁的意大利,但我同样记住了华丽的阿根廷,激情四射的克林斯曼,逆境还魂的法国与悲情谢幕的齐祖,甚至灵魂附体的黄健翔,还有很多很多,有了这些,对于我们大部分人来说,远比那座金杯更来得珍贵。那时,我开始明白世界杯的真正意义。也是那年,虽然仍旧磕磕碰碰,我却意气风发。一个事物之所以精彩,是因为它能给所有人以希望与快乐,而非它的结局。

2010,又会上演什么样的故事?

———————————————————————————————————-

一直觉得,踢球的孩子更容易长大,因为他们很快地就能找到自己的队友,并且一开始就得在场上学习寻找自己合适的位置,继而扮演好自己的角色。无论成功或者失败的体验,他们都能首先在球场上获得。

球场是人生舞台的一个剪影,曾经年轻的我们都幻想过有朝一日自己能成为顶级俱乐部的球星,国家队的栋梁之才,尽情的在广阔的绿茵场上奔跑,胜利了撒足狂奔,失败了悲情落泪。到今天,我们默默地奔跑在离自己距离最近的一支小球队中,追逐着最简单的快乐,即使有时泛起那么点功利心,跟以前相比也渺小得那么地不值一提。

———————————————————————————————————-

到目前为止的这个夏天已经带我们太多的悲喜剧,赛季初挥金如土的银河战舰在四大皆空的同时,凭借着自身输出的斯内德和罗本,把国米和拜仁送进了欧冠决赛,行为艺术家伊布拉同学,赛季前为了欧冠离开国米,然后国米收获了40多年来从未染指的欧冠奖杯。我们没有理由不期待接下来更多的精彩。

最后,不能免俗,谈谈我看好的几支球队吧。

巴西:一旦巴西务实起来,你没有理由不看好他。

西班牙:西班牙依然看上去很美,如果他夺冠,估计是大多数“真球迷”所乐意看到的。

英格兰:英格兰的阵容其实很大牌的,还有一个那么大牌的教练。足球鼻祖,那么多年了,再拿一回大家估计也可以接受。

德国:哎,悲情的巴拉克,强悍的德国战车。

阿根廷:支持阿根廷,马拉多纳你滚蛋吧!

Posted in Life & Thinking.


劝世一言

如果你想微笑,请务必抓紧时间,因为总有一天,你再也不会有那样的心情。

Posted in Life & Thinking.

Tagged with .


登山者

远行的登山者看不到任何的机会,因为宿命又如期而至,就在每次、每一次他将要看到希望的时候。爬坡,滑回原点,再爬,在他体力耗尽滑向深渊之前,希望将一次比一次渺茫。在他神智不清的视野里,他看见过佛祖,他也看见过基督。他伸出了手,他们却消失无踪,于是他选择了继续独自坚持。

很累,他紧紧地依着自己的登山索,他知道一刻都不能放手,但真的很想睡上一觉。

嗡… … 耳边一直传来那种不知道来自哪个世界的杂音。

还好,他还有力气,接着走这段必然会有尽头的路。

轮回,是历史的一部分,也是生命的基本。你也许早已熟知,但它仍然会很残酷。

Posted in Life & Thinking.


开发大型机器学习系统的经验教训(来自googlesearch blog)

近期在googlesearch blog上发布了一篇题为《Lessons learned developing a practical large scale machine learning system》的博客,作者应该是google机器学习团队的成员,列举了他们在开发一个可伸缩的大型机器学习系统Seti时所积累的一些经验教训。虽然所列出的三点教训看起来都很简单,似乎显而易见,但在现实操作中我们往往会受到种种的“诱惑”而走向另一个极端。特别典型的情景是,这些道理对于工作在一线的有过一定的项目经验积累的工程师来说,是有一定的直观感受的,但由于没有很好地概括描述或通过一个有权威性的嘴巴说出来,而可能被一些只存在于设计纸上的美妙构想而引向错误的方向,导致劳民伤财,积重难返。最简单的道理,却最容易被人们一遍又一遍地触犯着,所以想把它们写下来,一方面提醒自己以后少受这样的诱惑而犯错,另一方面也希望给类似应用场景与想法的朋友有一个权威性的归纳与佐证。

以下大都属于充分尊重原博客基础上的意译,穿插部分自己的看法。

这里说的机器学习系统其实是指一个分类系统(或者说模式识别系统),大部分情况下,这两者可以等同而视之,但在回复里也有人指出机器学习的范畴应该比较模式识别更大,这里不再执着于谁大谁小的问题,只需要知道,下面提到的机器学习系统或Seti主要是一个分类器,它具有如下的一些特点:

  • 是一个二元分类器,输出结果是样本在各个类别的分类概率(理论上,多类分类问题都可以转化为二元分类问题,所以二元分类器是基本);
  • 支持并行化运算;
  • 支持超过千亿级别的样本量的计算;
  • 支持数十亿甚至更大量的特征的计算;
  • 能自动识别合理的特征组合;
  • 精度上跟现时最先进的分类器具有可比性;
  • 对于增量式数据可以在数分钟内作出响应。(这里说的应该是增量式训练的效率)

对于一个预测或分类问题,如果你没有足够的数据量,你得关注于如何在少量的数据样本下充分利用统计知识构建一个精巧的分类器,反之,如果你的数据量爆棚,你就得关注你的系统如何才能适应如此庞大的样本量,并从中挖掘中有用的信息来。Seti所解决的问题规模大体上是像下表所描述的那样的:

Training set size Unique features
Mean 100 Billion 1 Billion
Median 1 Billion 10 Million

通常来说,一个好的机器学习系统需要更多地强调精度,但面对一个大型的系统时作这样的片面强调容易犯下很多错误,以下是我们在开发Seti过程中积累的几个经验教训,当然有一些是事后我们才总结出来的,当时我们并没有意识到。(注:作者应该是想说,有些因素跟精度一样不能忽视,甚至更重要)

1、保持系统的简单,即使这意味着会损失一定的精度。(Keep it simple, even at the expense of a little accuracy)

诱惑:让你的分类器在不同领域的应用中都具有高精度是非常重要的,所以我们应该专注于算法的精度上。

但是,实际中的算法有其它几个方面具有同等重要的地位:

  • 容易使用:如果这个系统还有其它人或其它团队在使用,他们必定希望这个系统在配置与使用上都是简单的,他们可能不是机器学习的专家,所以他们也不希望把时间浪费在系统的启动与运作上。
  • 系统的可靠性:大家更注重在实际环境中部署一个可靠的机器学习系统,它必需是稳定的并且不需要总是去关注它是否崩溃的。早期的Seti虽然在精度上更好,但它的复杂性、对网络与GFS文件系统的压力、需要持续关注等缺点导致了很多人都不愿意去部署它。

(很多情况下,我们都可以认为以上两点是等价的,即:系统的简单易用约等于稳定可靠)

Seti通常应用于对原有系统有极大提升效果的场景(参看教训3),因此大家都不太会关注Seti所使用的不同算法所造成的在精度上的细微差别。另一方面,这些精度上的小差别经常可以通过其它的手段来抹平,比如更好的数据过滤、添加其它更合适的特征、调整参数等等,如果这个系统是稳定的、可扩展的、简单易用的,以上的这些额外的步骤也更容易实现,而这些系统特点往往决定了它会被团队接受或抛弃。

对于学术界来说,设计一个精度稍差但更稳定与简单易用的算法并不是一个有吸引力的事情,但依据我们的经验,这个在实际当中具有非凡的价值。

2、从当前的一些具体的应用开始。(Start with a few specific applications in mind)

诱惑:构建一个不限定于任何特定应用的系统,不单能囊括当前的各类应用,还能适用于未来的各种分类任务。

但是,我们决定聚焦于一小撮的初始的应用上,这个决定基于如下几个原因:

  • 对于这些少量的领域应用,我们可以抽象出一些它们共通的东西,为这些少量应用所开发的系统,往往也适用于其它的领域与应用。
  • 更重要的是,这让我们能够更迅速地决定哪些特性是不需要的。实际上我们是那么容易地就就使得系统过度地概括。如果缺少对这些少量领域的关注,我们连决定输入文件的格式都是极其困难的(比如,是不是需要同时接受二值/类别/实数等多种类型的输入?容许多个类别标签?容许分数标签?样本是否可以加权?)。
  • 一开始就跟几个不同的团队合作,可以帮我们了解到他们面临的共同问题,将来把系统部署于其它团队时这个过程过渡会更平滑。

3、知道什么时候说“不”。(Know when to say “no”)

诱惑:我们有了锤子,所以我们眼中什么都是钉子,任何的问题都可以用机器学习系统来解决。

很早以前我们就发现,虽然机器学习系统带来了明显的收益,但它同时也给整个系统带来了复杂性、不透明性与不可预测性。有些情况下,简单的技术已经足以解决手头的问题,从长远来看,与其把努力花在整合、维护与诊断在线机器学习系统上,还不如花在其它的一些提升系统性能的方法上。

Seti适用的前提是对现在的系统上有明显的预测效果上的提升,我们也常常建议大家避免把它应用于效果提升不明显的场景。

补充1:看到Seti所应用的数据规模时,我的第一反应是怎么可能得到这么大量的标记数据,因为训练分类器是需要标记好的数据集的,同样文中屡次提到分类器的精度,而计算分类器的精度同样不能缺少标记数据。我的理解是:一种情况下,这些标记数据是来自于google用户无意识的点击贡献,另一种情况是,他们使用的是半监督学习的方法,从一个小型的人工标记的数据集开始训练,然后覆盖到数据的全集。

补充2:文中的一个主要的观点是:商用的系统与学术界追求的系统其目标是不一样的。学术界会倾向于探寻即使是数据量不足的情况下,怎么得到有统计意义的结果,而精度永远都是最重要的。商业界在不缺乏数据的情况下需要关注于如何从带噪声的数据中过滤出有价值的信息,同时系统必须具有可扩展性,此时,精度并不是一个唯一重要的因素。

Posted in Algorithm.

Tagged with .


snowfall的并行分布式矩阵运算

实践小记,不求详尽。解决方案限制为用R、R的C扩展或第三方扩展包。

计算问题:对于一个规模为10W*100的双精度矩阵A,计算A*t(A)并对每一行取前10个最大的值,结果为一个10W*10的矩阵。

正常的解法:直接计算A*t(A),得到一个10W*10W的矩阵,对每一行作sort,然后取A[, 1:10],问题是10W*10W这个临时矩阵太庞大,一般内存都容不下,也没有必要放一下那么大的临时数据。

C扩展的解决方案:写一个C的底层扩展,计算出结果矩阵的每一行时,马上作sort,然后只保留前10个最大的值,其它的丢弃,这样避免了内存占用过大的情况。因为每一行的计算是互相独立的,所以可以利用openmp或MPI进行并行计算以提速。8核计算,这种方式可以达到28分钟的计算时间。

snowfall的解决方案:

snowfall的基本使用可以参考我之前的一篇文章。集群初始化与结束的语句基本一致,不同之处在于之前是做apply类型操作,现在是做矩阵相乘,关键是如下的语句:

res = docall(rbind, sfClusterApplyLB(splitRows(A, CHUNK), multiply_k, t(A), 10))

sfClusterApplyLB和splitRows都是snow包的函数,由于snowfall依赖snow,所以只要载入snowfall包即可。参数CHUNK表示把矩阵按行分成几块,由splitRows函数完成分块,然后分发给不同的slave进行运算,这个分发的任务由sfClusterApplyLB函数完成,multiply_k函数是个自定义函数,形式如下,其完成的功能是对每一个分块的矩阵完成要实现的相乘、排序、取前10列的功能。最后通过docall函数把各个分块得到的结果再组合到一起。

1
2
3
4
5
multiply_k <- function(a, B, k){
 x <- crossprod(t(a), B)
 x <- t(apply(x, 1, FUN=sort, decreasing=TRUE))
 return(x[,1:k])
}

计算效率如下(balin, aragorn, dwalin都是机器名):

5 CPU balin单机23分钟   100分块,5核跑满
7 CPU (balin 3, aragorn 2, dwalin 2)15分钟   60分块,7核跑满
7 CPU (balin 3, aragorn 2, dwalin 2)15分钟   100分块,7核跑满

分块越少,需要被调配的任务越少,通常会运行得越快。但分块越少每个slave占内存就会越多,要解决这个矛盾,可以用多台机器,充分利用多机器的内存,分布计算。

Posted in Programming.

Tagged with , .