机器学习(九):协同过滤

922 阅读6分钟

一、方法介绍

  • 协同过滤(Collaborative Filtering):简单来说 是利用某兴趣相投、拥有共同经验之群体的喜好来推荐用户感兴趣的信息。协同过滤分析用户兴趣,在用户群中找到指定用户的相似用户,综合这些相似用户对某一信息的评价,形成系统对该指定用户对此信息的喜好程度预测。
  • 最基本的2种策略:一种是找到有类似品味的人所喜欢的物品,一种是从一个人喜欢的物品中找到类似的物品,由此对应着基于用户的协同过滤和基于物品的协同过滤两种推荐技术。

二、推荐技术

(一)相关度评价

  • 欧几里得距离
  • 皮尔逊相关度:评分数据不规范时能给出更好的结果
  • 余弦相关度和杰卡德相关度

(二)基于用户的协同过滤

  • 局限性:

(1)每次计算用户相似度需要遍历每个用户和所有用户的评分,在网站用户增长到一定程度后计算会越发困难;

(2)基于用户的协同过滤的算法,不太容易从数学原理的角度去解释;

(3)用户的口味变化很快,兴趣迁移问题很难反应出来;

(4)数据稀疏,用户和用户之间有共同的消费行为实际上是比较少的,而且一般都是热门物品,帮助也不大。

(三)基于物品的协同过滤

(四)两种协同过滤算法的对比

-UserCFItemCF
性能适用于较少场合,不适用于客户很多的情况适用于物品数明显小于用户数的场合,物品很多计算量很大
领域时效性强,用户个性化兴趣不太明显长尾物品丰富,用户个性化需求强烈
实时性用户新行为不一定造成推荐结果的变化用户新行为一定会导致推荐结果的实时变化
冷启动新用户对很少物品有行为后不能立即进行推荐,因为用户相似度是每隔一段时间离线计算的;新物品上线后一段时间,有用户产生行为就可以将新物品推荐给类似用户的其他用户新用户只要对物品有行为就可以给他推荐类似的其他物品;但没有办法在不离线更新物品相似度的情况下推荐新物品
推荐理由很难提供令用户信服的推荐解释利用用户的历史行为给用户做推荐解释令人信服

三、适用场景

  • 1、UserCF给用户推荐那些和他有共同兴趣爱好的用户喜欢的物品,而ItemCF给用户推荐那些和他之前喜欢的物品类似的物品;

  • 2、从算法的原理可以看到,UserCF的推荐结果着重于反映和用户兴趣相似的小群体的热点,而ItemCF的推荐结果着重于维系用户的历史兴趣;

  • 3、换句话说,UserCF的推荐更社会化,反映了用户所在的小型兴趣群体中物品的热门程度,而ItemCF的推荐更加个性化,反映了用户自己的兴趣传承;

  • 4、UserCF更适合新闻推荐,很少有用户只看某个话题的新闻,主要是因为这个话题不可能保证每天都有新的消息,而这个用户却是每天都要看新闻的;

  • 5、个性化新闻推荐更加强调抓住新闻热点,热门程度和时效性是个性化新闻推荐的重点,而个性化相对于这两点略显次要;

  • 6、ItemCF更适合用户兴趣稳定的场景,如购书、电影网站推荐,首先在这些网站中,用户的兴趣是比较固定和持久的,这些网站中个性化推荐的任务是帮助用户发现和其研究领域相关的物品。

四、Python代码实现

  • 基于用户的协同过滤
#导入相关库
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

# 构建推荐指数函数,第item_id个物品对第user_id个用户的推荐分值,看最相似的k个用户
def Recommendation(user_id,item_id,similar,k=10):  #2,2,user_similar, 2
    """不减平均数的计算方法"""
    score = 0
    weight = 0
    user_id_action = freq_matrix[user_id,:]      #用户user_id 对所有商品的行为评分  
    item_id_action = freq_matrix[:,item_id]      #物品item_id 得到的所有用户评分  

    user_id_similar = similar[user_id,:]      #用户user_id 对所有用户的相似度    
    similar_index = np.argsort(user_id_similar)[-(k+1):-1]  #0,3 #最相似的k个用户的index(除了自己)
    
    for j in similar_index :  #0,3
        if item_id_action[j]!=0:  #表示相似的那个用户对该物品评分过
            #user_id_j_action = freq_matrix[j,:]

            score += user_id_similar[j]*(item_id_action[j])
            #print(score)
            weight += abs(user_id_similar[j])
            #print(weight)

    if weight==0:  
        return 0
    else:
        return score/float(weight)

#构建预测函数,得到预测分数矩阵
def predict(user_similar):
    """预测函数的功能: 传入相似度矩阵, 通过对每个用户和每个物品进行计算, 计算出一个推荐矩阵"""
    user_count = freq_matrix.shape[0]#用户数
    item_count = freq_matrix.shape[1]#商品数
    predic_matrix = np.zeros((user_count,item_count))
    for user_id in range(user_count):
        for item_id in range(item_count):
            if freq_matrix[user_id,item_id] == 0:
                #print (user_id,item_id)
                predic_matrix[user_id,item_id] = Recommendation(user_id,item_id,user_similar)
    return predic_matrix

def get_topk(group,n):
    return group.sort_values("推荐指数",ascending=False)[:n]

#读取数据
df = pd.read_csv("example.txt",header=None)
df.columns=['用户id','物品id','喜好程度']

#建立关系矩阵
dfpivot= df.pivot(index="用户id",columns="物品id",values="喜好程度")

#填充0
freq= dfpivot.fillna(0)

#取出值,得到评分数组
freq_matrix = freq.values

#计算相似性矩阵
user_similar=cosine_similarity(freq_matrix)

#得到预测分数矩阵
user_prediction_matrix = predict(user_similar)

#得到预测分数的dataframe
recommendation_df = pd.DataFrame(user_prediction_matrix,columns=freq.columns,index=freq.index)

recommendation_df_2 = recommendation_df.stack().reset_index()
recommendation_df_2.rename(columns={0:"推荐指数"},inplace=True)

recommendation_df_grouped = recommendation_df_2.groupby("用户id")

#获得top n推荐
topk = recommendation_df_grouped.apply(get_topk,n=3) #推荐几个

topk = topk.drop(["用户id"],axis=1)
topk.index = topk.index.droplevel(1)
topk.reset_index(inplace=True)
topk

  • 基于物品的协同过滤
import pandas as pd
import numpy as np
df = pd.read_csv("example.txt",header=None)
df.columns=['用户id','物品id','喜好程度']
dfpivot= df.pivot(index="用户id",columns="物品id",values="喜好程度")
dfpivot
freq= dfpivot.fillna(0)
freq
freqmatrix = freq.values
freqmatrix
#导入相关库
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity


#构建一个基于物品的推荐
def Recommendation_s(uid,iid,similar,k=10): #2,2,第2个物品对第2个用户的推荐分值,k可以优化,通过MSE与k的变化
    score = 0
    weight = 0
    uid_action = freqmatrix[uid,:]      #用户uid 对所有商品的行为评分  
    iid_action = freqmatrix[:,iid]      #物品iid 得到的所有用户评分  
    
    iid_sim = similar[iid,:]      #商品iid 和所有商品的相似度    
    sim_indexs = np.argsort(iid_sim)[-(k+1):-1]  #最相似的k个物品的index(除了自己)
    
    iid_i_mean = np.sum(iid_action)/iid_action[iid_action!=0].size
    
    for j in sim_indexs : #0,6
        if uid_action[j]!=0:  #表示用户用过该物品,用户对该物品有过评分
            iid_j_action = freqmatrix[:,j]
            iid_j_mean = np.sum(iid_j_action)/iid_j_action[iid_j_action!=0].size
            score += iid_sim[j]*(uid_action[j]-iid_j_mean)
            weight += abs(iid_sim[j])

    if weight==0:  
        return 0
    else:
        return iid_i_mean + score/float(weight)

    
#构建预测函数
def predict_mean(similar):
    user_cnt = freqmatrix.shape[0]
    item_cnt = freqmatrix.shape[1]
    pred = np.zeros((user_cnt,item_cnt))
    for uid in range(user_cnt):
        for iid in range(item_cnt):
            if freqmatrix[uid,iid] == 0: #表示用户没有用过该物品
                #print (uid,iid)
                pred[uid,iid] = Recommendation_s(uid,iid,similar)
    return pred

def get_topk(group,n):
    return group.sort_values("推荐指数",ascending=False)[:n]

#读取数据
df = pd.read_csv("example.txt",header=None)
df.columns=['用户id','物品id','喜好程度']

#建立关系矩阵
dfpivot= df.pivot(index="用户id",columns="物品id",values="喜好程度")

#填充0
freq= dfpivot.fillna(0)

#取出值,得到评分数组
freq_matrix = freq.values

#计算相似性矩阵
item_similar=cosine_similarity(freq_matrix.T)#计算物品的相似性

#得到预测分数矩阵
user_prediction_matrix = predict_mean(item_similar)

#得到预测分数的dataframe
recommendation_df = pd.DataFrame(user_prediction_matrix,columns=freq.columns,index=freq.index)

recommendation_df_2 = recommendation_df.stack().reset_index()
recommendation_df_2.rename(columns={0:"推荐指数"},inplace=True)

recommendation_df_grouped = recommendation_df_2.groupby("用户id")

#获得top n推荐
topk = recommendation_df_grouped.apply(get_topk,n=3) #推荐几个

topk = topk.drop(["用户id"],axis=1)
topk.index = topk.index.droplevel(1)
topk.reset_index(inplace=True)
topk