1、 皮尔逊相关系数
在早期的推荐系统中皮尔逊相关系数是一个基础的相似性衡量标准,先从这个参数定义开始说起。 皮尔逊系数度量两个一一对应数列之间的线性相关程度,上述四种公式都是计算该系数方法,下面python 代码使用就是公式4。def sim_pearson(prefs,p1,p2): # Get the list of mutually rated items si={} for item in prefs[p1]: if item in prefs[p2]: si[item]=1 # if they are no ratings in common, return 0 if len(si)==0: return 0 # Sum calculations n=len(si) # Sums of all the preferences sum1=sum([prefs[p1][it] for it in si]) sum2=sum([prefs[p2][it] for it in si]) # Sums of the squares sum1Sq=sum([pow(prefs[p1][it],2) for it in si]) sum2Sq=sum([pow(prefs[p2][it],2) for it in si]) # Sum of the products pSum=sum([prefs[p1][it]*prefs[p2][it] for it in si]) # Calculate r (Pearson score) num=pSum-(sum1*sum2/n) den=sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n)) if den==0: return 0 r=num/den return r
说完皮尔逊系数的计算后我们来分析该系数的优势和不足。
优势:该系数可以修正‘夸大分值’的情况,举例就是user1 倾向总是评分为高分,user2 倾向总是评分在低分区域,加入使用时欧式距离这样两个user的相似性比较低,但是其实两个user爱好可能类似,只是他们对分数的标准不是那么一致。 不足:首先没有考虑到两个用户的给出评分数目,两个看过200部电影的用户,即便他们偶尔有些评分不一致,但我们也会觉得比两个仅仅看了2部相同电影用户相似。 其次,根据公式可以发现,如果两个user重叠的评分只有一个item 这样,该公式定义是无法进行计算的,在稀疏数据集上面这个问题尤其凸显出来,当然如果两个用户重叠次数太少我们也基本可以认为两者相似性不高。 最后,根据公式可以明显发现,如果一个user评分序列中所有的评分都是一样的,这样导致该公式是未定义的。2、 欧式距离定义相似度
这是最常用相似度,不做详细介绍。 3、 余弦相似性度量 简单贴下mahout中余弦公式实现public static double distance(double[] p1, double[] p2) { double dotProduct = 0.0; double lengthSquaredp1 = 0.0; double lengthSquaredp2 = 0.0; for (int i = 0; i < p1.length; i++) { lengthSquaredp1 += p1[i] * p1[i]; lengthSquaredp2 += p2[i] * p2[i]; dotProduct += p1[i] * p2[i]; } double denominator = Math.sqrt(lengthSquaredp1) * Math.sqrt(lengthSquaredp2); // correct for floating-point rounding errors if (denominator < dotProduct) { denominator = dotProduct; } // correct for zero-vector corner case if (denominator == 0 && dotProduct == 0) { return 0; } return 1.0 - dotProduct / denominator; }
4、 斯皮尔曼相关系数
理论可以参考这个博客: 下面是mahout中该参数实现主要部分@Override public double userSimilarity(long userID1, long userID2) throws TasteException { PreferenceArray xPrefs = dataModel.getPreferencesFromUser(userID1); PreferenceArray yPrefs = dataModel.getPreferencesFromUser(userID2); int xLength = xPrefs.length(); int yLength = yPrefs.length(); if (xLength <= 1 || yLength <= 1) { return Double.NaN; } // Copy prefs since we need to modify pref values to ranks xPrefs = xPrefs.clone(); yPrefs = yPrefs.clone(); // First sort by values from low to high xPrefs.sortByValue(); yPrefs.sortByValue(); // Assign ranks from low to high float nextRank = 1.0f; for (int i = 0; i < xLength; i++) { // ... but only for items that are common to both pref arrays if (yPrefs.hasPrefWithItemID(xPrefs.getItemID(i))) { xPrefs.setValue(i, nextRank); nextRank += 1.0f; } // Other values are bogus but don't matter } nextRank = 1.0f; for (int i = 0; i < yLength; i++) { if (xPrefs.hasPrefWithItemID(yPrefs.getItemID(i))) { yPrefs.setValue(i, nextRank); nextRank += 1.0f; } } xPrefs.sortByItem(); yPrefs.sortByItem(); long xIndex = xPrefs.getItemID(0); long yIndex = yPrefs.getItemID(0); int xPrefIndex = 0; int yPrefIndex = 0; double sumXYRankDiff2 = 0.0; int count = 0; while (true) { int compare = xIndex < yIndex ? -1 : xIndex > yIndex ? 1 : 0; if (compare == 0) { double diff = xPrefs.getValue(xPrefIndex) - yPrefs.getValue(yPrefIndex); sumXYRankDiff2 += diff * diff; count++; } if (compare <= 0) { if (++xPrefIndex >= xLength) { break; } xIndex = xPrefs.getItemID(xPrefIndex); } if (compare >= 0) { if (++yPrefIndex >= yLength) { break; } yIndex = yPrefs.getItemID(yPrefIndex); } } if (count <= 1) { return Double.NaN; } // When ranks are unique, this formula actually gives the Pearson correlation return 1.0 - 6.0 * sumXYRankDiff2 / (count * (count * count - 1)); }