用 Hadoop 计算共生矩阵

本文译自Calculating A Co-Occurrence Matrix with Hadoop

本文是《Data-Intensive Text Processing with MapReduce》提到的MapReduce算法的系列文章的延续。这次我们会使用语料库建立一个单词共生矩阵。

所谓共生矩阵可以描述为对于某种事件,给予一个特定的时间或者空间限制,然后记录在这种情况下会发生的事件。本文中的“事件”指的是在文本中出现的单词,我们要记录在限制条件内其他词的出现情况,这个限制条件是指其他词相对于目标单词的位置。例如,考虑这句话“The quick brown fox jumped over the lazy dog”。限制条件是2,jumped这个单词满足条件的共生是 [brown,fox,over,the]。共生矩阵可以应用于需要调查在某个事件发生时同时还发生了什么时间的情况。我们将使用《Data-Intensive Text Processing with MapReduce》书中第三章提到过的Stripes算法和Pairs算法来建立文本共生矩阵。用以建立共生矩阵的文本资料来自《莎士比亚全集》。

Pairs算法

实现pairs算法十分简单。map函数每调用一次就传入一行,按照空格把传入的行切分成字符串数组。下一步是建立两层循环。外层循环数组中的每个词,内层循环遍历当前词的邻接词。内层循环的迭代次数取决于需要捕捉的当前词的邻接距离。在内层循环的每次迭代的最后,我们会输出一个WordPair对象(由两个词组成,当前词居左,邻接词居右)作为key,该组词的出现频度作为值。下面是pairs算法的实现代码:

 
 
  1.  
  2. public class PairsOccurrenceMapper extends Mapper<LongWritable, Text, WordPair, IntWritable> {
  3. private WordPair wordPair = new WordPair();
  4. private IntWritable ONE = new IntWritable(1);
  5. @Override
  6. protected void map(LongWritable key, Text value, Context context)
  7. throwsIOException, InterruptedException {
  8. int neighbors = context.getConfiguration().getInt("neighbors", 2);
  9. String[] tokens = value.toString().split("\\s+");
  10. if (tokens.length > 1) {
  11. for (int i = 0; i < tokens.length; i++) {
  12. wordPair.setWord(tokens[i]);
  13. int start = (i - neighbors < 0) ? 0 : i - neighbors;
  14. int end = (i + neighbors >= tokens.length) ? tokens.length - 1 : i + neighbors;
  15. for (int j = start; j <= end; j++) {
  16. if (j == i) continue;
  17. wordPair.setNeighbor(tokens[j]);
  18. context.write(wordPair, ONE);
  19. }
  20. }
  21. }
  22. }
  23. }

Pairs算法中的Reducer只要简单的将作为key的同一WordPair的计数加和。

 
 
  1.  
  2. public class PairsReducer extends Reducer<WordPair,IntWritable,WordPair,IntWritable> {
  3. private IntWritable totalCount = new IntWritable();
  4. @Override
  5. protected void reduce(WordPair key, Iterable<IntWritable> values, Context context)
  6. throwsIOException, InterruptedException {
  7. int count = 0;
  8. for (IntWritable value : values) {
  9. count += value.get();
  10. }
  11. totalCount.set(count);
  12. context.write(key,totalCount);
  13. }
  14. }
Stripes算法

共生矩阵中的stripes算法实现一样很简单。 不过与pairs算法不同的是,每个词的所有相邻词被存储在一个hashmap中,以该邻接词为key,词的出现频数为值。当循环遍历完一个词的所有邻接词之后,这个词和与之关联的hashmap被输出。下面是stripes算法的实现代码:

 
 
  1. public class StripesOccurrenceMapper extends Mapper<LongWritable,Text,Text,MapWritable> {
  2. private MapWritable occurrenceMap = new MapWritable();
  3. private Text word = new Text();
  4. @Override
  5. protected void map(LongWritable key, Text value, Context context)
  6. throwsIOException, InterruptedException {
  7. int neighbors = context.getConfiguration().getInt("neighbors", 2);
  8. String[] tokens = value.toString().split("\\s+");
  9. if (tokens.length > 1) {
  10. for (int i = 0; i < tokens.length; i++) {
  11. word.set(tokens[i]);
  12. occurrenceMap.clear();
  13. int start = (i - neighbors < 0) ? 0 : i - neighbors;
  14. int end = (i + neighbors >= tokens.length) ? tokens.length - 1 : i + neighbors;
  15. for (int j = start; j <= end; j++) {
  16. if (j == i) continue;
  17. Text neighbor = new Text(tokens[j]);
  18. if(occurrenceMap.containsKey(neighbor)){
  19. IntWritable count = (IntWritable)occurrenceMap.get(neighbor);
  20. count.set(count.get()+1);
  21. }else{
  22. occurrenceMap.put(neighbor,new IntWritable(1));
  23. }
  24. }
  25. context.write(word,occurrenceMap);
  26. }
  27. }
  28. }
  29. }

Stripes算法的Reducer稍微有点复杂,因为我们要遍历每个key的所有map,包括遍历每个map中的所有值。

 
 
  1. public class StripesReducer extends Reducer<Text, MapWritable, Text, MapWritable> {
  2. private MapWritable incrementingMap = new MapWritable();
  3. @Override
  4. protected void reduce(Text key, Iterable<MapWritable> values, Context context)
  5. throwsIOException, InterruptedException {
  6. incrementingMap.clear();
  7. for (MapWritable value : values) {
  8. addAll(value);
  9. }
  10. context.write(key, incrementingMap);
  11. }
  12. private void addAll(MapWritable mapWritable) {
  13. Set<Writable> keys = mapWritable.keySet();
  14. for (Writable key : keys) {
  15. IntWritable fromCount = (IntWritable) mapWritable.get(key);
  16. if (incrementingMap.containsKey(key)) {
  17. IntWritable count = (IntWritable) incrementingMap.get(key);
  18. count.set(count.get() + fromCount.get());
  19. } else {
  20. incrementingMap.put(key, fromCount);
  21. }
  22. }
  23. }
  24. }
结论

现在来比较两种算法,看得出相较于Stripes算法,Pairs算法会产生更多的键值对。而且,Pairs 算法捕获到的是单个的共生事件而Stripes 算法能够捕获到所有的共生事件。Pairs算法和Stripes算法的实现都非常适宜于使用Combiner。因为这两种算法实现产生的结果都是可交换与可结合【译者注:可使用combiner的数据必须能够满足交换律与结合律,忘了这是那篇文档中提出的了】的,所以我们可以简单地重用reducer作为Combiner。如前所述,共生矩阵不仅仅能应用于文本处理,它会是我们手中的一项重要武器。谢谢你读到这里。

参考资料

Data-Intensive Processing with MapReduce by Jimmy Lin and Chris Dyer
Hadoop: The Definitive Guide by Tom White
Source Code and Tests from blog
Hadoop API
MRUnit 用来测试Apache Hadoop mapreduce

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值