乐趣区

关于推荐系统:推荐系统实践UserBase-CF-预测评分

一. 筹备

1. 评分预测公式


预测分数 = (用户类似度 * 对电影的评分) 之和 / 类似度之和

2. 数据集

movielens 数据集 中 ml-latest-small.zip
地址:
https://grouplens.org/dataset…

二. 算法实现

1. 加载数据集

def load_data(data_path):
    ''' 加载数据,data_path 为数据集门路
      file_path = r'E:\RecommendData\ml-latest-small\ratings.csv'
    '''cache_path = os.path.join(cache_dir,'ratings_matrix.cache')
    print('开始加载数据集...')
    if os.path.exists(cache_path):
        print('加载缓存中')
        ratings_matrix = pd.read_pickle(cache_path)
        print("从缓存加载数据结束")
    else:
        # 从数据集中加载数据
        print("加载新数据中...")
        dtype = {'userId': np.int32, 'movieId': np.int32, 'rating': np.float32}
        # 读取 csv 文件内容
        ratings = pd.read_csv(data_path, dtype=dtype, usecols=range(3))
        # 对读取到的数据进行透视, 组成用户为 index movieId 为列的数据结构
        ratings_matrix = ratings.pivot_table(index=['userId'], columns=['movieId'], values="rating")
        # 将数据存入缓存文件
        ratings_matrix.to_pickle(cache_path)
        print("加载数据结束")
    return ratings_matrix

2. 应用皮尔逊算法计算用户类似度

def compute_pearson_similarity(ratings_matrix, based='user'):
    '''计算皮尔逊相关系数'''
    user_similarity_cache_path = os.path.join(cache_dir, 'user_similarity.cache')
    item_similarity_cache_path = os.path.join(cache_dir, 'item_similarity.cache')
    if based == 'user':
        # 计算用户类似度
        if os.path.exists(user_similarity_cache_path):
            similarity = pd.read_pickle(user_similarity_cache_path)
        else:
            # 计算用户类似度
            similarity = ratings_matrix.T.corr()
            # 将用户类似度写入缓存中
            similarity.to_pickle(user_similarity_cache_path)
    elif based == 'item':
        # 计算物品类似度
        if os.path.exists(item_similarity_cache_path):
            # item similar 已存在, 读取缓存
            similarity = pd.read_pickle(item_similarity_cache_path)
        else:
            # item similarity 不存在, 从新计算类似度, 保留进缓存
            similarity = ratings_matrix.corr()
            # 将 item 类似度写入缓存中
            similarity.to_pickle(item_similarity_cache_path)
    else:
        print("传入 based 值谬误")
    return similarity

3. 预测算法

def predict(uid, iid, ratings_matrix, user_similar):
    # 获取与 uid 类似的用户
    similar_users = user_similar[uid].drop([uid]).dropna()
    # 筛选正相干的用户
    similar_users = similar_users.where(similar_users > 0).dropna()
    # 提醒没有类似用户
    if similar_users.empty is True:
        raise Exception("用户 <%d> 没有类似的用户" % uid)
    # uid 近邻类似用户中筛选 对 iid 物品有评分记录的用户
    ids = set(ratings_matrix[iid].dropna().index) & set(similar_users.index)
    # 依据用户 ids 获取对应的类似的用户及类似度
    finally_similar_users = similar_users.loc[list(ids)]
    sum_up = 0
    sum_down = 0
    # 对每个类似的用户进行循环
    for sim_uid, similarity in finally_similar_users.iteritems():
        # 类似用户评过分的说有电影
        sim_user_rated_movies = ratings_matrix.loc[sim_uid].dropna()
        # 类似用户对指定电影的评分
        sim_user_rating_for_item = sim_user_rated_movies[iid]
        # 类似用户 类似度 * 对电影的评分
        sum_up += similarity * sim_user_rating_for_item
        # 各个类似用户类似度之后
        sum_down += similarity
    # 预测分数为 (类似用户类似度 * 对电影的评分) 之和 / 类似度之和
    predict_rating = sum_up / sum_down
    print("预测出用户 <%d> 对电影 <%d> 的评分:%0.2f" % (uid, iid, predict_rating))
    return round(predict_rating, 2)

4. 对用户所有电影进行评分预测

def _predict_all(uid, item_ids, ratings_matrix, user_similar):
    # 预测全副评分
    # 对指定用户做所有电影举荐
    for iid in item_ids:
        try:
            # 对指定用户指定电影做评分预测
            rating = predict(uid, iid, ratings_matrix, user_similar)
        except Exception as e:
            print(e)
        else:
            yield uid, iid, rating

def predict_all(uid, rating_matrix, user_similar, filter_rule=None):
    # 预测全副评分,并依据条件进行前置过滤
    if not filter_rule:
        # 不进行过滤
        item_ids = rating_matrix.columns
    elif isinstance(filter_rule, str) and filter_rule == 'unhot':
        '''过滤非热门电影'''
        # 统计每部电影的评分次数
        count = rating_matrix.count()
        # 过滤评分次数高于 10 词的电影, 作为热门电影
        item_ids = count.where(count > 10).dropna().index
    elif isinstance(filter_rule, str) and filter_rule == 'rated':
        '''过滤用户评分过的电影'''
        # 获取用户对所有电影的评分记录
        user_ratings = rating_matrix.loc[uid]
        # 评分范畴是 1 -5,小于 6 的都是评分过的,除此以外的都是没有评分的
        _ = user_ratings < 6
        item_ids = _.where(_ == False).dropna().index
    elif isinstance(filter_rule, list) and set(filter_rule) == set(["unhot", "rated"]):
        count = rating_matrix.count()
        ids1 = count.where(count > 10).dropna().index
        user_ratings = rating_matrix.loc[uid]
        _ = user_ratings < 6
        ids2 = _.where(_ == False).dropna().index
        item_ids = set(ids1) & set(ids2)
    else:
        raise Exception("有效的过滤参数")
    yield from _predict_all(uid, item_ids, rating_matrix, user_similar)

5. 返回 K 个举荐后果

def top_k_rs_result(K):
    file_path = r'E:\RecommendData\ml-latest-small\ratings.csv'
    ratings_matrix = load_data(file_path)
    user_similarity = compute_pearson_similarity(ratings_matrix, based='user')
    results = predict_all(1, ratings_matrix, user_similarity, filter_rule=["unhot", "rated"])
    return sorted(results, key=lambda x: x[2], reverse=True)[:K]

三. 预测后果

[(1, 1041,  4.76),
 (1, 714,   4.72),
 (1, 80906, 4.7),
 (1, 1235,  4.63),
 (1, 3030,  4.63),
 (1, 65261, 4.63),
 (1, 1178,  4.57),
 (1, 1217,  4.56),
 (1, 318,   4.55),
 (1, 1104,  4.55),
 (1, 3451,  4.55),
 (1, 280,   4.54),
 (1, 168252, 4.52),
 (1, 3246,   4.5),
 (1, 58,     4.49),
 (1, 290,    4.49),
 (1, 115569, 4.49),
 (1, 1243,   4.48),
 (1, 142488, 4.47),
 (1, 800,    4.45)]
退出移动版