Data-Intensive Text Processing with MapReduce第三章(4)-MapReduce算法设计-3.3计算相对频率

 

3.3计算相对频率

 

让我们在之前讲到的pairs和stripes算法的基础上继续在大型数据集上构建重现矩阵M。回忆在这个大的n×n矩阵中华,当n=|V|(词典大小),元素mij包含单词wi与wj在具体语境下共同出现的次数。无约束的计数的缺点是它没有考虑到实际上有些词会比其它词更加频繁地出现。单词wi可能比wj出现的次数多因为其中一个可能是常用词。一个简单的解决方法是把无约束的计数转变为相对频率,f(wj |wi)。那怎样统计在有wi的上下文中出现的wj频率?可以用下面的公式计算:

 

f(wj jwi) = N(wi;wj)/ ∑w’ N(wi,w0)         (3.1)

 

在这里,N(.,.)标明一个特殊的同现次对在数据集中被观测到的次数,我们需要统计joint事件(单词的同现),以我们知道的边界(控制变量与其它变量同现的计数的总数)把它分割。

 

用stripes方法可以直接计算相关频率。在reducer中,和控制变量(在上面的例子中是wi)一起出现的所有词语的个数在关联数组中会用到。因此,能够计算这些数的总值来达到边界(即∑w0 N(wi;w0)),然后用边界值分割所有joint事件来取得所有单词的相关频率。这个实现必须对图3.9的算法做下小修改,并且用来说明把复杂的数据结构用在MapReduce的分布式计算环境中。虽然合适的key和value其中一个就可以使用MapReduce执行框架来把需要计算的数据集中起来。要知道和之前的一样,这个算法也是假设把每一个关联数组存到内存中。

 

那怎样用pairs方法来计算相对频率呢?在pairs方法中,reducer把接受到的(wi,wj)作为key计数作为value。单单用这个无法计算f(wj |wi)因为我们没有边界值。幸运地,与mapper一样,reducer可以通过多个key来保存状态。在reducer里面,我们可以把和wi同时出现的所有词缓存到内存中,实际上是用stripes方法来建立关联数组。要让它可行,我们还需定义pair的排序顺序来让key先用左边词然后用右边词排序。有个这个排序,我们可以很容易地发现与我们用(wi)做条件的词相关的所有pairs是否都出现。在这个时候我们可以通过保存在内存中的数据来计算相对频率,然后在最终键值对中把结果发送出去。

 

还需要修改一个地方来使算法工作。我们必须保证左边单词相同的所有pairs都发送到同一reducer中。不幸的是,它不会自动发生:回忆起默认的partitioner是基于中间键的哈希值,以reducers的数量为模。对于一个复杂的键,用来计算哈希值。作为结果,不敢保证,例如,(dog,aardvark)和(dog,zebra)指定到同一reducer。为了产生期待的行为,我们必须定义一个自定partitioner来只关注于左边的单词。就是说,partitioner应该只是基于左边单词的哈希算法来分割。

 

这个算法可以工作,但它有着和stripes方法一样的缺点:当文档集的数量增长时,词典大小也跟着增加,在某些时候有可能没有足够的内存来存储所有的同现词和我们监控的词的个数。为了计算同现矩阵,使用pairs方法的优点是不会遇到内存瓶颈的问题。有没有一种方法来修改pairs方法使之保留这个优势?

 

后来人们发现,这样的算法确实是可行的,虽然它需要MapReduce中的一些协调机制。数据到达reducer的顺序是否正确的洞察力。如果有可能以某种方式来使在reducer中计算(或有权使用)边界值在处理joint计数之前,reducer就可以简单的通过边界值拆分joint计数来计算相对频率。“之前”和“之后”的通知可以通过键值对的顺序来捕获,它可以明确地控制在程序员手中。程序员可以定义键的排序顺序来让先要处理的数据在reducer中比后需要处理的数据先处理。然而,我们仍然需要计算边界计数。回忆在基本的pairs算法中,每一个mapper发送一个以同现词对为键的键值对。为了计算相关频率,我们修改了mapper,使它发送出以表单(wi,*)为“特殊”键,值为1的键值对,这代表着单词对对边界值的贡献。通过使用combiners,这些部分边界计数会在发送给reducer之前先做聚集。作为选择,使用in-mapper combining模式可以更加有效地聚集边界计数。

 

在reducer中,我们必须保证特殊键值对表示部分边界值在普通键值对表示joint次数之前执行。这通过定义keys的排序顺序来完成,然后包含表单(wi; *)的特殊标志的pairs

排在任何其它wi为左单词的键值对的前面。另外,和之前一样,我们必须也定义一个partitioner来观察每一个pair中的左单词。通过恰当的数据排序,reducer能直接计算相对频率。

 

图3.12是一个真实的例子,它列出了reducer会遇到的键值对列表。首先,reducer面对的是以(dog,*)为特殊键和多个值,每个值都代表从map阶段(在这里假设无论combiners或in-mapper combining,这些值都是代表部分聚集的计数)得到的部分边界贡献的键值对。Reducer累计这些计数来达到极限值∑w’ N(dog,w’)。reducer保存这些值来计算子序列的键。从(dog,*)开始,reducer会遇到一系列代表joint计数的键;第一个的键是(dog, aardvark)。与这个键相关的是在map阶段生成的部分joint计数(在这里是两个不同的值)。统计这些计数可以得出最终joint计数,即dog和aardvark在整个集合中同时出现的次数。在这点上,因为reducer已经知道了极限值,通过简单的计算就可以计算相关频率。所有子joint计数都用相同的方法处理。当reducer遇到下一个特殊的键值对(doge; *)时,reducer重置它的内部状态并开始重新计算下一轮的极限值,因为只有极限值(一个整数)需要存储。不需要缓存单独的同现词的统计数,因此,我们消除了之前算法的伸缩性瓶颈。

 

Key                                  values

(dog,*)                              [6327, 8514, . . .]       计算总数: ∑w’ N(dog,w’) = 42908

(dog, aardvark)               [2,1]                             f(aardvark|dog) = 3/42908

(dog, aardwolf)               [1]                                 f(aardwolf|dog) = 1/42908

. . .

(dog, zebra)                     [2,1,1,1]                       f(zebra|dog) = 5/42908

(doge, *)                           [682, . . .]                     计算总数: ∑w0 N(doge,w’) = 1267

. . .

图 3.12:计算相对频率的pairs算法中发送到reducer的一连串键值对例子。它说明了顺序反转模式

 

这个设计模式,我们称为“顺序反转”,经常出现并且跨很多领域的应用。之所以会这样命名是因为通过适当的协调,我们可以在处理需要计算的数据之前在reducer中存取计算结果(例如,一个聚集统计)。主要的问题是把计算队列转换为一个排序问题。在多数情况下,特定的算法往往需要固定的数据排序方式:通过控制键的排序和键空间的分配,我们可以把需要在特定环境下计算的数据排序后发送到reducer中。这样做大大减少了reducer需要在内存中保存的部分结果的数量。

 

总结,运用顺序反转模式来计算相关频率的特殊程序需要以下内容:

 

1.在mapper中为每一个同现词对发送一个特殊的键值对来捕获它对边界值的贡献值。

2.控制中间键的排序顺序,使特殊键值对表示边界贡献值在任何键值对表示joint单词同现次数之前执行

3.定义一个自定partitioner来保证所有左单词相同的pair都传到同一reducer中。

4.通过reducer中的多个键来保存状态,首先计算基于特殊键值对的边界值然后用边界值来分割joint的计数来得到相关频率。

正如我们将在第四章看到的,这个设计模式也可用于构建反向索引,即通过为链接列表恰当地设置压缩参数。

你可能感兴趣的:(mapreduce,设计模式,算法,分布式计算,processing,pair)