VectorWritable vectorWritable = VectorWritable.merge(vectors.iterator());
至此,对于每一个Item,我们得到了所有用户对它的打分数据,保存在一个VectorWritable中.
phase4: RowSimilarityJob
这一步比较关键,计算item相似度,它拆分成了三个JOB。
param: --numberOfColumns, --similarityClassname,--maxSimilaritiesPerRow(默认:100)
Job1:再一次生成用户对各个item的打分向量,与之前不同的是还计算出了nonZeroEntries maxValues norms三个向量
input:phase3的输出
map: (itemId, VectorWritable ) ==>(userId, VectorWritable)
mapper每处理一个 (itemId, VectorWritable ) ,都会为nonZeroEntries maxValues norms(三个RandomAccessSparseVector变量)设置一个维度的值,最后在mapper的cleanup函数中,将这三个变量写到输出,为了区分与正常的输出,它们的key比较特殊,分别为Integer.MIN_VALUE,Integer.MIN_VALUE+1,Integer.MIN_VALUE+2
reduce:(userId, VectorWritable),并且将nonZeroEntries maxValues norms(三个RandomAccessSparseVector变量)写入到对应的路径下面
Job2:pairwise similarity item相似度计算
map: 对同一用户的所有item-rating,输出两两item之间的关系.
map的输入是job1中的输出:(userId, VectorWritable),在map中,对于每个user对所有item的打分向量即(userId, VectorWritable),首先根据向量中的每个元素的index值升序排序,此时的index值是一个itemid,这一点很重要,保证了下面的向量dots中的各个元素的index()的值都大于或等于map写出的key,即(occurrenceA.index()),使得最后在reducer中只是生成了一个上三角矩阵,这个要仔细体会一下.mapper最后输出的key为这个user的第一个打分项目的itemid,输出的value是在当前用户打分向量中,第一个item与其他各个item(包括第一个)的aggregate值构成的一个Vector.即:
ctx.write(new IntWritable(occurrenceA.index()),
new VectorWritable(dots));
reduce: 首先汇总一个itemid对应的所有aggaregate的值,最后计算对应的相似度,写到一个VectorWritable中
ctx.write(row, new VectorWritable(similarities));//生成了一个上三角矩阵,因为similarities中的元素的index()值都大于或等于row
Job3:生成完整的相似度矩阵,并且每一行只取前面最大的maxSimilaritiesPerRow个值
经过了job2之后,我们只是得到了一个上三角相似度矩阵,job3的作用就是通过这半个上三角矩阵生成一个完整的相似度矩阵.获取前maxSimilaritiesPerRow个值是用到了优先队列.
protected void map(IntWritable row, VectorWritable similaritiesWritable, Context ctx)
throws IOException, InterruptedException {
Vector similarities = similaritiesWritable.get();
TopK topKQueue = new TopK(maxSimilaritiesPerRow, Vectors.BY_VALUE);
Iterator nonZeroElements = similarities.iterateNonZero();
while (nonZeroElements.hasNext()) {
Vector.Element nonZeroElement = nonZeroElements.next();
topKQueue.offer(new Vectors.TemporaryElement(nonZeroElement));
Vector transposedPartial = similarities.like();
transposedPartial.setQuick(row.get(), nonZeroElement.get());
ctx.write(new IntWritable(nonZeroElement.index()), new VectorWritable(transposedPartial)); //生成下三角,nonZeroElement.index()>row.get()
}
Vector topKSimilarities = similarities.like();
for (Vector.Element topKSimilarity : topKQueue.retrieve()) {
topKSimilarities.setQuick(topKSimilarity.index(), topKSimilarity.get());
}
ctx.write(row, new VectorWritable(topKSimilarities)); //这是个细节问题,加上这一句,可以使得reduce的结果中保留原来的上三角
}
}
至此,item相似度计算完毕。最后reduce的结果是生成整个矩阵,而不是仅仅是上三角或下三角,见代码注释.并且此时对于每个item,它对应的相似度向量中仅仅保存了相似度最大的maxSimilaritiesPerRow个item,
可以认为这maxSimilaritiesPerRow个item即为当前item的相似item
phase 4 :进行三个mapreduce任务
job1 :prePartialMultiply1 对每个item对应的相似度向量进行处理,去除与自身的相似度,并将处理后的向量输出
job2: prePartialMultiply2 读取用户评分向量v,对于v ,只保留其中pref最大的maxPrefsPerUserConsidered个元素,其它的元素的值全部设置为NaN,对于这个处理后的向量中的每一个元素 ,的ite
输出一条记录,key为对应
mid, 而value为一个封装了用户对这个itemid打分的VectorOrPrefWritable,
job3: partialMultiply 对于一个itemid输出一个VectorAndPrefsWritable对象,该对象封装了一个该item的相似度向量,对这个 item打过分的所有用户,以及对应的prefs
VectorAndPrefsWritable vectorAndPrefs = new VectorAndPrefsWritable(similarityMatrixColumn, userIDs, prefValues);
context.write(key, vectorAndPrefs);
phase 5 为用户进行推荐:
map的输入为phase4中job3的输出. 即:itemid_index,VectorAndPrefsWritable(vector,List
,List) ,其中vector的值为Vector,为itemid_index的相似度向量
map输出为(userid,PrefAndSimilarityColumnWritable(pref,vector). 含义为userid对一个项目打分了,分数是pref,并且,这个项目相似度向量保存在vector中.
reduce阶段的输入为(userid,List
),用户对一个项目的打分时,与该项目相似的item(即对应的相似度向量中的item)都有可能被推荐,计算推荐的可能性思想是:对于每一个可能被推荐的item,,将该item与user打分的所有项目的相似度与对应的pref相乘的积累加起来,除以相似度的累加和.这里使用了两个vector分别来保存相似度与pref的乘积的累加和以及相似度的累加和,即numerators和denominators,vector中每维度对应着一个可能被推荐的item