您的位置:首页 > 其它

个性化推荐基本算法及源码分析(一)— 基于用户的协同过滤

2013-11-04 00:00 573 查看
摘要: 本文将首先简述下基于用户的协同过滤的基本原理,然后结合Mahout中实现的源码进行分析。

1.算法的基本原理

基于用户的协同过滤推荐的基本原理是,根据所有用户对物品或者信息的偏好,发现与当前用户口味和偏好相似的“邻居”用户群,在一般的应用中是采用计算“K- 邻居”的算法;然后,基于这 K 个邻居的历史偏好信息,为当前用户进行推荐。原理如下图所示:



上图示意出基于用户的协同过滤推荐机制的基本原理,假设用户 A 喜欢物品 A,物品 C,用户 B 喜欢物品 B,用户 C 喜欢物品 A ,物品 C 和物品 D;从这些用户的历史喜好信息中,我们可以发现用户 A 和用户 C 的口味和偏好是比较类似的,同时用户 C 还喜欢物品 D,那么我们可以推断用户 A 可能也喜欢物品 D,因此可以将物品 D 推荐给用户 A。

2.协同过滤算法的核心

2.1 搜集用户偏好

要从用户的行为和偏好中发现规律,并基于此给予推荐,如何收集用户的偏好信息成为系统推荐效果最基础的决定因素。用户有很多方式向系统提供自己的偏好信息,而且不同的应用也可能大不相同。

用户行为和用户偏好表:

用户行为 类型 特征 作用
评分 显式 整数量的偏好,可能的值是[0,n];一般为5或10 可精确得到用户的偏好。
投票 显式 布尔量化的偏好,取值0或1 可以较精确的得到用户的偏好。
转发 显式 布尔量化的偏好,取值0或1 如果是站内,可以同时推理得到被转发人的偏好(不精确)
保存书签 显式 布尔量化的偏好,取值0或1 用户将该项存为书签说明他对这个项目感兴趣
标记标签(Tag) 显式 一些单词,需要对单词进行分析,得到偏好 通过分析用户的标签,可以得到用户对项目的理解,同时可以分析出用户的情感:喜欢还是讨厌
评论 显式 一段文字,需要进行文本分析,得到偏好 通过分析用户的评论,可以得到用户的情感:喜欢还是讨厌
点击流(查看) 隐式 一组用户的点击,用户对物品感兴趣,需要进行分析,得到偏好 用户的点击一定程度上反映了用户的注意力,所以他也可以从一定程度上反应用户的喜好。
页面停留时间 隐式 一组时间信息,噪声大,需要进行去噪,分析,得到偏好 用户的页面停留时间一定程度上反映了用户的注意力和喜好,但噪声大,不好利用。
购买 隐式 布尔量化的偏好,取值是0或1 用户购买行为很明确地说明他对这个项目感兴趣。

2.2 数据预处理

预处理最核心的工作就是减噪和归一化。
减噪:用户行为数据是用户在使用应用过程中产生的,它可能存在大量的噪音和用户的误操作,我们可以通过经典的数据挖掘算法过滤掉行为数据中的噪音,这样可以是我们的分析更加精确。
归一化:如前面讲到的,在计算用户对物品的喜好程度时,可能需要对不同的行为数据进行加权。但可以想象,不同行为的数据取值可能相差很大,比如,用户的查看数据必然比购买数据大的多,如何将各个行为的数据统一在一个相同的取值范围中,从而使得加权求和得到的总体喜好更加精确,就需要我们进行归一化处理。最简单的归一化处理,就是将各类数据除以此类中的最大值,以保证归一化后的数据取值在 [0,1] 范围中。
进行的预处理后,根据不同应用的行为分析方法,可以选择分组或者加权处理,之后我们可以得到一个用户偏好的二维矩阵,一维是用户列表,另一维是物品列表,值是用户对物品的偏好,一般是 [0,1] 或者 [-1, 1] 的浮点数值。

2.3 找相似的用户或物品

2.3.1 相似度的计算

关于相似度的计算,现有的几种基本方法都是基于向量(Vector)的,其实也就是计算两个向量的距离,距离越近相似度越大。在推荐的场景中,在用户 - 物品偏好的二维矩阵中,我们可以将一个用户对所有物品的偏好作为一个向量来计算用户之间的相似度,或者将所有用户对某个物品的偏好作为一个向量来计算物品之间的相似度。
常用的相似度计算方法主要有欧几里得距离、皮尔逊相关系数、Cosine相似度、Jaccad系数,下面逐一进行介绍:

欧几里得距离:
Euclidean Distance,最初用于计算欧几里德空间中两个点的距离,假设 x,y 是 n 维空间的两个点,它们之间的欧几里德距离是:



可以看出,当 n=2 时,欧几里德距离就是平面上两个点的距离。
当用欧几里德距离表示相似度,一般采用以下公式进行转换:距离越小,相似度越大。



皮尔逊相关系数
Pearson Correlation Coefficient,皮尔逊相关系数一般用于计算两个定距变量间联系的紧密程度,它的取值在 [-1,+1] 之间。



有关皮尔逊相关系数的推导如下式:



Cosine相似度
Cosine Similarity,Cosine 相似度被广泛应用于计算文档数据的相似度:



Jaccard系数
Tanimoto 系数也称为 Jaccard 系数,是 Cosine 相似度的扩展,也多用于计算文档数据的相似度:



2.3.2 相似邻居的计算

相似邻居的计算包括固定数量的邻居和固定相似度门槛的邻居。
固定数量的邻居(K-neighborhoods):
不论邻居的“远近”,只取最近的 K 个,作为其邻居。如下图 中的 A,假设要计算点 1 的 5- 邻居,那么根据点之间的距离,我们取最近的 5 个点,分别是点 2,点 3,点 4,点 7 和点 5。但很明显我们可以看出,这种方法对于孤立点的计算效果不好,因为要取固定个数的邻居,当它附近没有足够多比较相似的点,就被迫取一些不太相似的点作为邻居,这样就影响了邻居相似的程度,可以看出图中,点 1 和点 5 其实并不是很相似。



固定相似度门槛的邻居(Threshold-based neighborhoods):
与计算固定数量的邻居的原则不同,基于相似度门槛的邻居计算是对邻居的远近进行最大值的限制,落在以当前点为中心,距离为 K 的区域中的所有点都作为当前点的邻居,这种方法计算得到的邻居个数不确定,但相似度不会出现较大的误差。下图中的 B,从点 1 出发,计算相似度在 K 内的邻居,得到点 2,点 3,点 4 和点 7,这种方法计算出的邻居的相似度程度比前一种优,尤其是对孤立点的处理。



2.4 计算推荐

基于用户的 CF 的基本思想相当简单,基于用户对物品的偏好找到相邻邻居用户,然后将邻居用户喜欢的推荐给当前用户。计算上,就是将一个用户对所有物品的偏好作为一个向量来计算用户之间的相似度,找到 K 邻居后,根据邻居的相似度权重以及他们对物品的偏好,预测当前用户没有偏好的未涉及物品,计算得到一个排序的物品列表作为推荐。下图 给出了一个例子,对于用户 A,根据用户的历史偏好,这里只计算得到一个邻居 - 用户 C,然后将用户 C 喜欢的物品 D 推荐给用户 A。

3. Mahout User-Based CF 源码分析

3.1 User-Based CF实现类GenricUserBasedRecommender类、接口关系图



3.2 GenericUserBasedRecommender源码分析

首先来看下GenericUserBasedRecommender的代码。基于用户协同过滤的核心算法在doEstimatePreference这个方法中。
protected float doEstimatePreference(long theUserID,long[] theNeighborhood, long itemID) throws TasteException {
if (theNeighborhood.length == 0) {
return Float.NaN;
}
DataModel dataModel = getDataModel();
double preference = 0.0;
double totalSimilarity = 0.0;
int count = 0;
for (long userID : theNeighborhood) {
if (userID != theUserID) {
// See GenericItemBasedRecommender.doEstimatePreference() too
Float pref = dataModel.getPreferenceValue(userID, itemID);
if (pref != null) {
double theSimilarity = similarity.userSimilarity(theUserID,
userID);
if (!Double.isNaN(theSimilarity)) {
preference += theSimilarity * pref;//Sum(Su(u_k,u_a)*x_a,m) where x_a,m != null and Su(u_k, u_a) is not NaN
totalSimilarity += theSimilarity;//Sum(Su(u_k,u_a))
count++;
}
}
}
}
if (count <= 1) {
return Float.NaN;
}
float estimate = (float) (preference / totalSimilarity);//Sum(Su(u_k,u_a)*x_a,m)/Sum(Su(u_k, u_a))
if (capper != null) {
estimate = capper.capEstimate(estimate);//结果标准化min(x_a,m) <= x_k,m <= max(x_a,m)
}
return estimate;
}
(注:为了更好的封装,主要的计算并没有在override方法——estimate()中实现,而是在estimate()方法中调用doEstimate()方法,并用protected修饰doEstimate()方法。)
通过上面的代码可以看出mahout中User-Based CF算法用的是基本的User-Based CF算法。其数学模型如下:



其中,

表示Recommender对user_u,item_k的预测评分,Su(uk,ua)表示uk,ua的相似度。

3.3 改进的UserBasedRecommender及源码

数学模型:



可以看出改进的User-Based CF考虑到了不同用户间打分的差异(有的人偏向于打高分,有的人偏向于打低分)因而预测要更为准确。

源码如下:

protected float doEstimatePreference(long theUserID, long[] theNeighborhood, long itemID) throws TasteException {
if (theNeighborhood.length == 0) {
return Float.NaN;
}
DataModel dataModel = getDataModel();
double preference = 0.0;
double totalSimilarity = 0.0;
int count = 0;
float userAveragePreference = averageUserPreferences(theUserID);
for (long userID : theNeighborhood) {
if (userID != theUserID) {
// See GenericItemBasedRecommender.doEstimatePreference() too
Float pref = dataModel.getPreferenceValue(userID, itemID);
float neighborhoodAveragePreference = averageUserPreferences(userID);
if (pref != null) {
double theSimilarity = similarity.userSimilarity(theUserID, userID);
if (!Double.isNaN(theSimilarity)) {
preference += theSimilarity * (pref - neighborhoodAveragePreference);
totalSimilarity += theSimilarity;
count++;
}
}
}
}
if (count <= 1) {
return Float.NaN;
}
float estimate = (float) (preference / totalSimilarity);
estimate += userAveragePreference;
if (capper != null) {
estimate = capper.capEstimate(estimate);
}
return estimate;
}

/**
* 改进的 User-Based CF算法
* @param userID
* @return 用户平均打分
* @throws TasteException
*/
protected float averageUserPreferences(long userID) throws TasteException {
float averageUserPreferences = 0;
float totalUserPreferences = 0;
int numUserPreferences = 0;
DataModel dataModel = getDataModel();
PreferenceArray userPreferences = dataModel.getPreferencesFromUser(userID);
for(int i = 0; i < userPreferences.length(); i++) {
float userPreference = userPreferences.getValue(i);
if(!Float.isNaN(userPreference)){
totalUserPreferences += userPreference;
numUserPreferences++;
}
}
averageUserPreferences = totalUserPreferences / numUserPreferences;
return averageUserPreferences;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息