Mengdong的技术博客

学习,记录,分享

导航

《Data-intensive Text Processing with MapReduce》读书笔记第3章:MapReduce算法设计(3)

本读书笔记的目录地址:http://www.cnblogs.com/mdyang/archive/2011/06/29/data-intensive-text-prcessing-with-mapreduce-contents.html

3.3 计算相对频度(relative frequency,频率)

改进共现矩阵:使用相对频度

回顾上一节的内容,对于n×n的共现矩阵(co-orrurance matrix)mmij的值是单词wiwj在指定上下文范围内共现(co-orrurance)的次数。考虑现实中的一种情况:有的单词在本文中出现的次数远远多于其他单词(例如the)就会发现,使用这种绝对计数的方法有一个缺点:wiwj的共现次数很多,可能只是因为wi(或wj)频繁出现(例如the)。举个例子,以一句话为上下文范围,"dog"与"the"的共现次数很可能等于"dog"的出现次数,因为几乎每个句子里都包含至少一个"the". 因此,本节对3.2节中的算法进行一下改进,我们将计算绝对频度(计数)改为计算相对频度f(wj|wi),用语言描述这个表达式即为:在包含wi的所有共现中,共现(wi,wj)占了多大比例(译者:这个概念与条件概率很类似,因此作者采取了条件概率的写法)?用数学公式表示如下:

公式中,N(a,b)表示共现(a,b)的计数。对于公式右边分母上的求和操作,w'的取值范围是所有与wi有共现关系的单词。简单说,相对频度f(wj|wi)就是用共现(wi,wj)的频度除以所有包含wi的共现之和得到的商。

stripe算法

对于3.2节中的stripe算法,将其改进为计算相对频度很简单:只需要在原先的reduce操作完毕后,再加上一步类似于归一化的操作,即,对于每个(w, H=[(w1,c1),(w2,c2)…(wn,cn)]),先遍历一遍H,计算计数加和S=c1+c2+…+cn,然后再次遍历H,将H更新为[(w1,c1/S),(w2,c2/S)…(wn,cn/S)]即可。这个算法可用,当然,与3.2节中的stripe算法类似,它也有内存上的局限性。

pair算法

那么3.2节中的pair算法能够通过简单改进计算相对频度吗?

pair算法中,reducer接受的数据类型是((wi,wj),count). 这里key使用的是自定义类型的数据。我们可以在reducer中构建类似于stripe算法中的关联数组H,类似于(w, H=[(w1,c1),(w2,c2)…(wn,cn)]). 对于(wi,ci)∈Hci即为共现(w,wi)的计数(频度)。当所有与w有关的共现都已统计完毕,即可计算相对频度。

这样解决所有问题了吗?没有,因为上面这个解决方法有一个隐含前提:对于所有具有相同wimo=((wi,wj),1)(这是pair算法中mapper的输出),mo都被送入同一个reducer. 回顾MapReduce中的划分器(partitioner),默认partitioner的做法是对key-value对中的key计算hash值h,然后取h mod r(其中r为reducer数,reducer被编号为0,1...r-1)作为接受该key-value对的reducer编号。而在pair算法中,key的类型是符合类型(wi,wj),因此即使具有相同的wi,如果wj不同的话,计算出来的hash值也很可能不一样,从而导致具有相同wi的中间结果被分发至不同的reducer. 因此我们还需要实现自定义的partitioner,这个partitioner仅仅根据key中的左值(即wi)计算hash.

至此pair算法初步改进完毕,改进后的pair算法也可以计算相对频度了。但由于使用了基于内存的关联数组,它也有了与stripe算法一样的内存局限性。最初pair算法相对于stripe算法的优势在于它没有内存瓶颈。那么有没有办法进一步改进pair算法,使其能够重获内存上的优势呢?

答案是“可以”。考虑如下的情况,如果我们能够在reducer开始处理中间结果之前就能计算出每个wi对应的共现频度加和S,那么就不需要生成关联数组维护计数了。这个解决办法的核心在于调整了计算顺序。而在MapReduce算法中,排序规则是可以自定义的,因此我们从排序上入手,看看能够利用排序规则达到调整计算顺序的目的。

在最初的pair算法中,mapper输出的数据类型是((wi,wj),1). 在此基础上对mapper做一点小改动:每次生成一个((wi,wj),1),我们额外生成一个((wi,*),1),用以表示包含wi的共现计数加1. 这两种中间对经过combiner的合并后将会分别变成形如((wi,wj),[cij1,cij2,…,cijn])与((wi,*),[ci1,ci2,…,cin])的中间结果。如果reducer能够先处理后者,再处理前者,那么就可以先计算出所有包含wi的共现计数和S,计算出S后即可直接处理所有形如((wi,wj),[cij1,cij2,…,cijn])的中间结果,无需记录庞大的关联数组了。要做到这一点,我们只要保证送入reducer中的数据((wi,*),1)类的key-value对排在((wi,wj),1)之前即可,这可以通过修改排序规则达成。

图3.12给出了上面改进算法的一个计算实例。

图3.12 改进后的pair算法

由于自定义的排序规则,(("dog", *),[6327,8514,...])排在(("dog","aardvark"),[2,1])和(("dog","aardwolf"),[1])...之前,因此reducer得以先计算出所有包含"dog"的共现计数S"dog",待到扫描(("dog","aardvark"),[2,1])和(("dog","aardwolf"),[1])...时即可直接利用S"dog"的值计算当前共现的相对频度。

反序(order inversion)模式

我们把这种模式称为“反序(order inversion)”模式。通过反序模式,我们可以控制中间结果进入reducer的顺序,从而在reducer中先计算出一些结果(根据先进入reducer的中间结果计算出),而这些结果对于高效处理后续的数据很有意义。要使用反序模式,需要先将算法中的操作序问题转化为一般排序问题

在本节的示例算法中,反序模式极大地减少了内存开销,提高了算法效率。该算法中应用反序模式的要求归纳起来有以下几点:

  1. 额外生成计算计数和(所有包现数)的((wi,*),1):用以wi的共
  2. 自定义的排序操作:对于一个确在所面定的wi,((wi,*),[ci1,ci2,…,cin])排有((wi,wj),[cij1,cij2,…,cijn])的前
  3. 自定义的partitioner:按照中间结果key的左值(即wi)划分中间结果
  4. 计数和在reducer中维护状态:使用一个变量记录当前wi的共现

在第4章我们还将看到,这种设计模式也可以应用于倒排索引(inverted index)的构建。

posted on 2011-07-18 11:37  mdyang  阅读(1089)  评论(0编辑  收藏  举报