mahout 0.6基于Item的CF代码分析

phase1: convert items to an internal index
     这步主要是将itemId转成一个int。
     input:用户评分文件(这也是我们最原始的输入了),格式一般为:userId \t itemId \t score。
     map:(index, itemId)
     reduce: (index, itemId)

phase2: convert user preferences into a vector per user 计算一个用户的对各个item的打分构成的向量
     input:用户评分文件
     param: --userBooleanData如果这个参数为true,则会忽略评分列,对于买或不买这类数据有时需要指这定这个值。
     如果booleanData==true  ,则map输出为(userid,itemid)),itemid类型为VarLongWritable
     如果booleanData==false  ,则map输出为(userid,EntityPrefWritable))
     这里有个小技巧,即EntityPrefWritable继承了VarLongWritable,所以不管map输出的是(VarLongWritable,VarLongWritable)还是(VarLongWritable,EntityPrefWritable),reduce的输入总是可以表达成(VarLongWritable,List

     reduce: 以用户为key,输出 (userId, VectorWritable),VectorWritable中保存了当前用户对所有项目的打分,对itemid的打分存放在一个RandomAccessSparseVector的idtoIndex(itemid)位置
    在reduce的过程中对用户计数器进行计数,每发现一个用户,计数器加1.

phase3:  build the rating matrix,计算出对于每个item,各个user对它的打分
         map阶段:输入是phase2的输出,对于每一个(userId, VectorWritable),取出向量中的每一个item,构造一个RandomAccessSparseVector itemVector,这个itemVector中只有一个元素,这一个元素的index是idToIndex(userid),   value 是用户对这个item的打分,也就是说这个itemVector中封装了一个用户对这个item的打分数据即:itemVector.get().setQuick(column, elem.get());
        最后输出(item_index,itemVector),即:ctx.write(new IntWritable(elem.index()), itemVector);

reduce阶段:对于一个item(item_index),合并所有用户对它的打分,形成一个VectorWritable,并输出。
      VectorWritable vectorWritable = VectorWritable.merge(vectors.iterator());
      vectorWritable.setWritesLaxPrecision(true);
      ctx.write(row, vectorWritable);
      至此,对于每一个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










你可能感兴趣的:(Hadoop,Mahout)