协同过滤推荐

                                         协同过滤推荐

1. 什么是协同过滤

协同过滤(collaborative filtering)是通过将用户和其他用户的数据进行对比来实现推荐的算法。

2. 协同过滤流程图

协同过滤推荐

3. 协同过滤分类

(1)基于用户的协同过滤推荐(User-based Collaborative Filtering Recommendation)
基于用户的协同过滤推荐算法先使用统计技术寻找与目标用户有相同喜好的邻居,然后根据目标用户的邻居的喜好产生向目标用户的推荐。基本原理就是利用用户访问行为的相似性来互相推荐用户可能感兴趣的资源,
(2)基于项目的协同过滤推荐(Item-based Collaborative Filtering Recommendation)
根据所有用户对物品或者信息的评价,发现物品和物品之间的相似度,然后根据用户的历史偏好信息将类似的物品推荐给该用户,
(3)基于模型的协同过滤推荐(Model-based Collaborative Filtering Recommendation)
基模型的协同过滤推荐就是基于样本的用户喜好信息,训练一个推荐模型,然后根据实时的用户喜好的信息进行预测推荐。

4. 相似度

相似度计算就是看两个物品(或用户)有多相似,拿电影来说,可能会比较类型、导演、地区等等,但是在协同过滤里,不关心这些属性,严格地按照许多用户的观点来计算相似度。比如下面的电影和用户评分矩阵:

协同过滤推荐

相似度计算

欧氏距离(euclidean metric)

欧氏距离指在m维空间中两个点之间的真实距离,或者向量的自然长度(即该点到原点的距离)。在二维和三维空间中的欧氏距离就是两点之间的实际距离,就是那个“根号下横坐标差的平方加纵坐标差的平方”。
假设我们看《海洋奇缘》和《一条狗的使命》的相似度,《海洋奇缘》列向量是(4, 3,1),《一条狗的使命》列向量是(4,3,2),做差再平方最后开根号。

 

协同过滤推荐

Python实现代码:

import numpy as np
def eulid_sim(colA, colB):
    return 1.0/(1.0 + np.linalg.norm(colA - colB))

代码解析:
numpy的线性代数(Linear algebra)库linalg里有一个norm函数,用于求范数(normal form),如果不指定范数的阶数,就默认指2范数,就是向量各个元素平方和开根号,比如向量A=(4,2,2),向量A的2范数||A||=根号下(4^2+2^2+2^2)(简书不支持LaTeX真是不方便……),所以np.linalg.norm(colA - colB))就是向量A和B的欧式距离了。
1.0/(1.0 + 欧式距离)的作用是使相似度的值在0到1之间变化,越相似,相似度的值越大,距离为0时,相似度为1。

皮尔逊相关系数

皮尔逊相关系数可以用来度量两个向量之间的相似度,比欧氏距离好的一点是它对用户评级不敏感,比如某个狂躁者对所有电影评分都是5,一个忧郁者对所有电影评分都是1,皮尔逊相关系数会认为这两个向量相等。看最后一个公式,对比两个向量的余弦公式,长得挺像,据说皮尔逊系数是两组向量的余弦

 协同过滤推荐

  • Python代码实现
def pearson_sim(colA, colB):
     if len(colA) < 3:
        return 1.0
     return 0.5 + 0.5 * np.corrcoef(colA, colB, rowvar=0)[0][1]

代码解析
len(colA) < 3是检查是否有3个或更多的点,如果不存在,则返回1,两向量完全相关。
corrcoef(colA, colB, rowvar=0)返回的是变量的相关系数矩阵,第[0][1]个元素是相关系数,rowvar=0代表列是variables。API在这里
0.5 + 0.5 *皮尔逊相关系数目的也是将取值范围归一化到0~1之间,皮尔逊相关系数的取值范围是-1~1,所以用0.5+0.5*系数的方式归一化。

余弦相似度

余弦相似度就是计算两个向量夹角的余弦值,如果夹角为90度,则相似度为0;如果方向相同,相似度为1。因为余弦值的范围也是-1~1,所以需要用同样的方法进行归一化。前面说过,||A||代表向量的2范数。

协同过滤推荐

 

Python代码

def cos_sim(colA, colB):
    num = float(colA.T * colB)
    denom = np.linalg.norm(colA) * np.linalg.norm(colB)
    return 0.5 + 0.5 * (num / denom)

相似度选择

计算两个电影之间的距离,是基于物品(item-based)的相似度,计算用户的距离,是基于用户(user-based)的相似度。到底使用哪种相似度,取决于用户和物品的数量。基于物品的相似度会随着物品增加而增加,基于用户的相似度会随着用户的增加而增加。如果用户很多,则倾向于物品相似度计算方法。对于大部分推荐引擎而言,用户数目往往大于物品数目,所以一般用物品相似度。

5. 构建评分估计函数

举例来说就是对于用户没看过的电影,我们要预测他会为这些电影打多少分,然后把排名前N个的电影推荐给他。

一般评分估计

流程图:

协同过滤推荐

# 计算某个物品和所有其他物品的相似度,进行累加,连评分也累加,最后用累加的总评分/总相似度得到预测该用户对新物品的评分
# data_mat:物品-用户矩阵
# user:用户编号
# item:要预测评分的物品编号
# sim_meas:相似度计算方法 
def stand_est(data_mat, user, item, sim_meas):
    n = np.shape(data_mat)[1] # 取第1轴的元素个数,在这里也就是列数
    sim_total = 0.0
    rat_sim_total = 0.0
    # 遍历整行的每个元素
    for j in range(n): 
        user_rating = data_mat[user, j] # 取一个评分
        if user_rating == 0: # 如果用户没有评分,就跳过这个物品
            continue 
        # 找出要预测评分的物品列和当前取的物品j列里评分都不为0的下标(也就是所有评过这两个物品的用户对这两个物品的评分)
        overlap = np.nonzero(np.logical_and(data_mat[:, item].A > 0, data_mat[:, j].A >0))[0]
        # 如果预测评分的物品列(假设叫列向量A)和当前取的物品j列(假设叫列向量B)没有都非零的项(也就是说两组向量里要么A评分B没评分,要么B评分A没评分),则认为相似度为0,否则,计算相似度
        if len(overlap) == 0:
            similarity = 0
        else:
            similarity = sim_meas(data_mat[overlap, item], data_mat[overlap, j]) # 注意overlap是一个array,所以这里还是传的两个列向量,两个元素中都没有0的列向量
        print('the %d and %d similarity is %f' % (item, j, similarity))
        sim_total += similarity # 累加相似度
        rat_sim_total += similarity * user_rating # 累加相似度*评分,
    if sim_total == 0:
        return 0
    else:
        return rat_sim_total / sim_total # 总评分/总相似度,除以总相似度是为了归一化,将评分归到相似度的范围(比如0~5)

 

SVD评分估计

流程图:

协同过滤推荐

# 用svd将矩阵变换到低维空间,再给出预估评分
# data_mat:物品-用户矩阵
# user:用户编号
# item:物品编号
# sim_meas:相似度计算方法
def svd_est(data_mat, user, item, sim_meas):
    n = np.shape(data_mat)[1]
    sim_total = 0.0
    rat_sim_total = 0.0
    u, sigma, vt = np.linalg.svd(data_mat) # 对原数据矩阵做svd操作
    sig4 = np.mat(np.eye(4) * sigma[:4]) # sigma[:4]是取sigma矩阵的前四个,python为了节省空接,sigma矩阵存成了行向量,所以通过eye(4)将其变回对角矩阵
    x_formed_items = data_mat.T * u[:,:4] * sig4.I # 利用u矩阵将其转换到低维空间,I操作是矩阵的逆.
    for j in range(n):
        user_rating = data_mat[user, j]
        if user_rating == 0 or j == item:
            continue
        similarity = sim_meas(x_formed_items[item, :].T, x_formed_items[j,:].T) #行向量转成列向量
        print('the %d and %d similarity is %f' % (item, j, similarity))
        sim_total += similarity
        rat_sim_total += similarity * user_rating
    if sim_total == 0:
        return 0
    else:
        return rat_sim_total / sim_total

6. 推荐

流程图:

协同过滤推荐

# data_mat:数据矩阵
# user:用户编号
# N: 要返回的前N个要推荐的items
# sim_meas: 相似度方法
# est_method:评分预估法
def recommend(data_mat, user, N, sim_meas, est_method):
    un_rated_items = np.nonzero(data_mat[user, :].A == 0)[1] # 上面说过,nonzero的第1个元素是第1轴的下标,这里也就是列下标
    if len(un_rated_items) == 0:
        return 'you rated everything'
    item_scores = []
    for item in un_rated_items:
        estimate_score = est_method(data_mat, user, item, sim_meas)
        item_scores.append((item, estimate_score))
    return sorted(item_scores, key=lambda jj:jj[1], reverse=True)[:N] # 排序,key=lambda jj:jj[1]表示按每个元素的下标为1的参数从大到小排序,取前N个

7. 评价

由于推荐引擎建好后既没有预测的目标值,也没有用户来调查他们对推荐的满意程度,所以常常将某些已知的评分值去掉,然后对它们进行预测,计算预测值和真实值之间的差异。
通常用于推荐引擎评价的指标是最小均方根误差(Root Mean Squared Error,RMSE),先计算均方误差的平均值,在取平方根。

 

 

参考:https://www.jianshu.com/p/861b66164b53/