Mahout in action分析维基百科数据例子(一)

本文来源于我个人博客www.chenbiaolong.com

概要

本文主要根据mahout in action第六章分析维基百科链接数据的例子编写。大部分内容是直接翻译的mahout in action,不过不是逐字翻译,加入了一些个人理解。关于本文的前提背景可以参考其他博主翻译的文章:
Mahout in action 中文版-6.分布式推荐计算-6.1
Mahout in action 中文版-6.分布式推荐计算-6.2

6.3.2 使用MapReduce:生成用户向量

在这个例子中,我们主要目的是利用item-base的推荐算法实现维基百科网页的推荐。基础理论就是:如果网页A和网页B同时被多个网页同时引用,那么我们就可以推测网页A和网页B的内容相近。当一个用户看了网页A时,我们可以考虑将网页B推荐给它。计算的输入文件是维基百科的链接数据文件,这个文件的每一行数据并不是按照userId,itemId,preference格式排列的,而是按照userID:itemID1 itemID2 itemID3 ...格式排列。这个格式代表的意义是ID为userID的网页有指向ID为itemID1,itemID2...网页的超链接。可以在这里获得所需要的维基百科链接数据文件。将这个文件传送到HDFS文件系统,以便Hadoop集群使用该文件。
为了实现网页推荐功能,我们需要2次的MapReduce操作。

第一次的MapReduce操作将生成用户向量:

  • 在MapReduce框架中,输入的文件将会被看成(Long,String)的键值对,Key是Long类型,它表示值在文件中的位置。String是该行具体的数据内容。比如239/98955:590 22 9059239表示数据行位于的文件位置,95955:590 22是该行的内容
  • 每一行将会被一个map函数解析为一个user ID和若干个item ID。这个map函数将会生成新的key/value对:一个user ID关联一个item ID,比如98955/590
  • 框架收集所有的被user ID关联的item ID
  • reduce函数输出user ID和与该用户对各个item的喜好向量。用户喜好向量只能为0或者1(因为链接只有有和没有2种情况)。比如95955/[590:1.0,22:1.0,9059:1.0]表示网页95955有链接到590,22,9059的超链接。需要注意的是这里userIDitemID只是为了表示方便,实际上userIDitemID均表示的是各个网页的ID值。
    这些思路的具体实现可以参考代码6.1和6.2.代码实现了Hadoop的mapper和reducer接口。
    列表6.1 解析维基百科链接数据的mapper函数
public class WikipediaToItemPrefsMapper
    extends Mapper<LongWritable,Text,VarLongWritable,VarLongWritable> {
  private static final Pattern NUMBERS = Pattern.compile("(\\d+)");
  public void map(LongWritable key,
                  Text value,
                  Context context) 
        throws IOException, InterruptedException {
    String line = value.toString();
    Matcher m = NUMBERS.matcher(line);
    m.find();
    VarLongWritable userID = 
    new VarLongWritable(Long.parseLong(m.group()));
    VarLongWritable itemID = new VarLongWritable();
    while (m.find()) {
      itemID.set(Long.parseLong(m.group()));
      context.write(userID, itemID);
    }
   }
}

列表6.2 Reducer函数

public static class WikipediaToUserVectorReducer extends 
    Reducer<VarLongWritable,VarLongWritable,VarLongWritable,VectorWritable> {
  public void reduce(VarLongWritable userID,
            Iterable<VarLongWritable> itemPrefs,
            Context context) 
        throws IOException, InterruptedException {
    Vector userVector = new RandomAccessSparseVector(
    Integer.MAX_VALUE, 100); 
    for (VarLongWritable itemPref : itemPrefs) {
    userVector.set((int)itemPref.get(), 1.0f);
    }
    context.write(userID, new VectorWritable(userVector));
    }
}

可以看出,实际上第一次MapReduce操作只是将varLongWritableitemID转换成vectorWritable类型的userVector

6.3.3 使用MapReduce:计算共现值

下一个MapReduce操作将根据第一个Mapreduce的结果计算出各个item的共现值(co-occurrence)。这里对共现值简单做一个解释:比如网页A和网页B同时被网页C、D、E链接,那么网页A和网页B的共现值为3。第二个MapReduce函数

  1. 输入是用户ID与用户喜好向量键值对——即第一次MapReduce结果。比如98955/[590:1.0,22:1.0,9059:1.0]
  2. 根据第一次mapreduce的结果计算出各个item的共现值。比如第一次mapreduce的一个结果98955/[590:1.0,22:1.0,9059:1.0],先忽略userID:98955,这个用户ID对应的各个itemID都同时被userID:98955链接,因此各个itemID两两之间的共现值应该加1。因此我们的第二个job的map操作可以用itemID1:itemID2这种格式表示itemID1itemID2同时被某个userID网页链接,共现值加1.比如用590/22表示网页590和22的共现值需要加1.
  3. reduce操作就是统计itemID之间的总共现值。在map操作后拥有共现关系(itemID1itemID2同时被某网页链接表示itemID1itemID2拥有共现关系)的各个itemID都组成了itemID1:itemID2形式的key-value对。reduce操作需要统计各个网页之间的共现关系。

假设map操作后我们得到以下结果:

100 101
100 105
100 110
100 101
100 105
100 101

reduce操作计算100与其他各个ID的共现值,得到另外一组long:vector形式的输出结果。上述例子将输出
100/[101:3.0, 105:2.0, 110:1.0]的形式。这个共现值有什么用呢?简单来说,两个网页的共现值越大,意味着这两个网页越相似,我们也就可以根据这个特性进行网页推荐了。

Listing 6.3 Mapper component of co-occurrence computation

public class UserVectorToCooccurrenceMapper extends
        Mapper<VarLongWritable, VectorWritable, IntWritable, IntWritable> {

    public void map(VarLongWritable userID, VectorWritable userVector,
            Context context) throws IOException, InterruptedException {
        Iterator<Vector.Element> it = userVector.get().iterateNonZero();
        while (it.hasNext()) {
            int index1 = it.next().index();
            Iterator<Vector.Element> it2 = userVector.get().iterateNonZero();
            while (it2.hasNext()) {
                int index2 = it2.next().index();
                context.write(new IntWritable(index1), new IntWritable(index2));
            }
        }
    }
}

Listing 6.4 Reducer component of co-occurrence computation

public class UserVectorToCooccurrenceReducer extends
        Reducer<IntWritable, IntWritable, IntWritable, VectorWritable> {

    public void reduce(IntWritable itemIndex1,
            Iterable<IntWritable> itemIndex2s, Context context)
            throws IOException, InterruptedException {
        Vector cooccurrenceRow = new RandomAccessSparseVector(
                Integer.MAX_VALUE, 100);
        for (IntWritable intWritable : itemIndex2s) {
            int itemIndex2 = intWritable.get();
            cooccurrenceRow.set(itemIndex2,
                    cooccurrenceRow.get(itemIndex2) + 1.0);
        }
        context.write(itemIndex1, new VectorWritable(cooccurrenceRow));
    }
}

小结

本文主要解释了实现维基百科网页推荐的基础原理:通过网页之间的共现值判断网页之间的是否相似。本质上就是item-base类型的推荐算法。更详细的内容可以参考mahout in action的相关章节。由于mahout in action只是给出了mapreduce的实现算法,具体利用hadoop运行这两个mapreduce操作时会出现一些问题:比如有一些class在新版本的hadoop已经作了调整;这里的两个mapreduce操作存在相互依赖如何利用hadoop接口解决等。在下一篇文章中我将给出实现这两个mapreduce操作的相应工程代码。

展开阅读全文

没有更多推荐了,返回首页