本文主要根据mahout in action第六章分析维基百科链接数据的例子编写。大部分内容是直接翻译的mahout in action,不过不是逐字翻译,加入了一些个人理解。关于本文的前提背景可以参考其他博主翻译的文章:
Mahout in action 中文版-6.分布式推荐计算-6.1
Mahout in action 中文版-6.分布式推荐计算-6.2
在这个例子中,我们主要目的是利用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操作将生成用户向量:
239/98955:590 22 9059
,239
表示数据行位于的文件位置,95955:590 22
是该行的内容user ID
和若干个item ID
。这个map函数将会生成新的key/value对:一个user ID
关联一个item ID
,比如98955/590
user ID
关联的item ID
user ID
和与该用户对各个item的喜好向量。用户喜好向量只能为0或者1(因为链接只有有和没有2种情况)。比如95955/[590:1.0,22:1.0,9059:1.0]
表示网页95955
有链接到590
,22
,9059
的超链接。需要注意的是这里userID
和itemID
只是为了表示方便,实际上userID
和itemID
均表示的是各个网页的ID值。 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操作只是将varLongWritable
的itemID
转换成vectorWritable
类型的userVector
。
下一个MapReduce操作将根据第一个Mapreduce的结果计算出各个item的共现值(co-occurrence)。这里对共现值简单做一个解释:比如网页A和网页B同时被网页C、D、E链接,那么网页A和网页B的共现值为3。第二个MapReduce函数
98955/[590:1.0,22:1.0,9059:1.0]
98955/[590:1.0,22:1.0,9059:1.0]
,先忽略userID:98955
,这个用户ID对应的各个itemID
都同时被userID:98955
链接,因此各个itemID
两两之间的共现值应该加1。因此我们的第二个job的map操作可以用itemID1:itemID2
这种格式表示itemID1
和itemID2
同时被某个userID
网页链接,共现值加1.比如用590/22
表示网页590和22的共现值需要加1.itemID
之间的总共现值。在map操作后拥有共现关系(itemID1
和itemID2
同时被某网页链接表示itemID1
和itemID2
拥有共现关系)的各个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操作的相应工程代码。