Item协同过滤(基于Python实现)

        在众多召回策略里面,基于Item与基于User(可参考:https://www.cnblogs.com/SysoCjs/p/11466424.html)在实现上非常相似。所以这里使用了跟基于User协同过滤的数据u.data。

u.data数据格式(user_id, item_id, rating, timestamp)

实现原理:
        区别于User,先根据User已经购买过,或者评价过的Items,基于算法,对其他Items做一个相似度计算,来获取基于该User的Items的相似Items,这样每个User_Item都可以得到一堆相似Items,根据相似度进行排序,每个User_Item取出topN个Items,然后将所有的User_item的各自topN个Items,共同组成一个大的Item_List,Item_list中的每个item的相似度乘上对应的User_item的权重,得到新的权重值,根据新的权重值排序,取topK个Items,作为最终的Items候选集。

具体实现:
        因为是使用Python来实现,所以还是选择最简单的算法:杰卡尔德算法。在User协同过滤中:通过两个用户共同拥有的物品集合数量,除以两个用户的物品的平均数量。这里,需要倒过来:通过两个item共同用户数量,除以两个item各自的用户的平均数量。所以要做的是,想方设法获取这三个参数值:共同用户数量、两个item各自的用户数量。

一、创建源数据的结构

        timestamp列数据在寻找相似用户里面,意义不大,可以不使用;对于pyspark程序,u.data数据是没有结构的,所以第一时间是读取u.data,并定义数据的结构,可以将数据的结构定义为:

dict{user_id:{item_id:rating}}
import pandas as pd
 
def generate_train_data(nrows=10):
    # 处理训练数据 -> dict{user_id:{item_id:rating}}
    df = pd.read_csv('../row_data/u.data',
                     sep='	',
                     nrows=nrows,
                     names=['user_id', 'item_id', 'rating', 'timestamp'])
 
    # d为每个用户的商品及对应打分的列表
    d = dict()
    for _, row in df.iterrows():
        # 类型转换
        user_id = str(row['user_id'])
        item_id = str(row['item_id'])
        rating = row['rating']
        if user_id not in d.keys():
            d[user_id] = {item_id: rating}
        else:
            d[user_id][item_id] = rating
    return d

二、统计杰卡尔德的参数

        基于Item的协同过滤,在遍历train_data的时候,需要同时统计每个item的User数量,以及item与item之间共同的User数量,因为,相对于基于User的协同过滤,item的User数量不可以直接通过len(train_data[i])来获取,需要另外定义一个类型为dict的N来存储,N的数据结构为{item: userNum}。

N = dict()  # N{item:userNum},统计item的user数量

在遍历train_data的同时,可以将N和C都收集得到:

N = dict()  # N{item:userNum},统计item的user数量
def item_sim(train_data):
    C = dict()  # C{item:{iten:simUserNum}}
    for u,items in train_data.items():
        for i in items.keys():
            # 对于每一个u的items,N[i]只有一个
            if N.get(i,-1)==-1:
                N[i] = 0
            N[i] += 1
            if C.get(i,-1)==-1:
                C[i] = dict()
            for j in items.keys():
                if i==j:
                    continue
                if C[i].get(j,-1)==-1:
                    C[i][j] = 0
                C[i][j] += 1
    return C

        这个时候,共同用户数量存储在C里面,而每个item对应的用户数量存储在N里面,那么接下来可以借用杰卡尔德公式计算Item-Item相似度矩阵。

# 计算相似度矩阵,杰卡尔德算法
def item_item(C):
    for i, related_items in C.items():
        for j, cij in related_items.items():
            C[i][j] = 2 * cij/(N[i]+N[j]*1.0)
    return C

        最终结果还是保存在C里面,此时C的数据结构变成:

C = dict()  # C{item:{iten:sim_score}}

二、过滤,统计推荐候选集

        最终是要得到Item候选集,所以先定义一个字典,用于存储item,及其对应的打分:

rank = dict() # rank{item:score}

        Item候选集仍然是指代推荐给指定User的Items,一般会根据业务需求,将该User购买过或评价过的Item进行过滤,这时,需要取出该User对应的r购买过或评价过的Item列表:

Ru = train_data[user_id]

        过滤掉User购买过或评价过的Item后,需要计算候选集中每个Item的打分,通过Item在该User的rating,乘上其相似Item的sim_score,得到最终打分score:

def recommendation(train_data, user_id, C, k):
    rank = dict() # rank{item:score}
    # 用户user_id有很多个已经购买的item,每个item都有好多个相似item,
    # 每个item取k个相似item
    Ru = train_data[user_id]
    for i, rating in Ru.items():    # 相对于user-based,item-based的一个用户对应于多个item,所以大循环,要遍历item
        for j, sim_score in sorted(C[i].items(), key=lambda x:x[1],reverse=True)[0:k]:
            # 相对于user-based,每个user还要遍历各自的item,item-based则不需要,需要它的矩阵就是item-iten
            # 过滤这个user已经打分过的item
            if j in Ru:
                continue
            if rank.get(j,-1)==-1:
                rank[j] = 0
            rank[j] += sim_score*rating
    return rank

三、根据指定User,得出其推荐Item候选集

if __name__ == '__main__':
    train_data = dict()
    with open(train_data_path,'r') as ft:
        train_data = eval(ft.read())
 
    C = dict()
    with open(sim_item_path, 'r') as fs:
        C = eval(fs.read())
 
    user_id = '196'
    k = 5
    rank = recommendation(user_id, C, train_data, k)
    print(sorted(rank.items(), key=lambda x: x[1], reverse=True)[0:k])
原文地址:https://www.cnblogs.com/SysoCjs/p/11466767.html