推荐系统实践—UserCF实现

参考:https://github.com/Lockvictor/MovieLens-RecSys/blob/master/usercf.py#L169

数据集

本文使用了MovieLens中的ml-100k小数据集,数据集的地址为:传送门
该数据集中包含了943个独立用户对1682部电影做的10000次评分。

首先看一下数据:

data = pd.read_csv('u.data', sep='	', names=['user_id', 'item_id', 'rating', 'timestamp'])
print(data)

完整代码

  1 import numpy as np
  2 import pandas as pd
  3 import math
  4 from collections import defaultdict
  5 from operator import itemgetter
  6 
  7 np.random.seed(1)
  8 
  9 
 10 class UserCF(object):
 11 
 12     def __init__(self):
 13         self.train_set = {}
 14         self.test_set = {}
 15         self.movie_popularity = {}
 16 
 17         self.tot_movie = 0
 18         self.W = {}   # 相似度矩阵
 19 
 20         self.K = 20   # 最接近的K个用户
 21         self.M = 10   # 推荐电影数
 22 
 23     def split_data(self, data, ratio):
 24         ''' 按ratio的比例分成训练集和测试集 '''
 25         for line in data.itertuples():
 26             user, movie, rating = line[1], line[2], line[3]
 27             if np.random.random() < ratio:
 28                 self.train_set.setdefault(user, {})
 29                 self.train_set[user][movie] = int(rating)
 30             else:
 31                 self.test_set.setdefault(user, {})
 32                 self.test_set[user][movie] = int(rating)
 33         print('数据预处理完成')
 34 
 35     def user_similarity(self):
 36         ''' 计算用户相似度 '''
 37         movie_users = {}
 38         for user, items in self.train_set.items():
 39             for movie in items.keys():
 40                 if movie not in movie_users:
 41                     movie_users[movie] = set()
 42                 movie_users[movie].add(user)
 43                 if movie not in self.movie_popularity:   # 用于后面计算新颖度
 44                     self.movie_popularity[movie] = 0
 45                 self.movie_popularity[movie] += 1
 46         print('倒排表完成')
 47         self.tot_movie = len(movie_users)  # 用于计算覆盖率
 48 
 49         C, N = {}, {}    # C记录u,v之间给相同电影打分的数量, N记录用户打分的电影数量
 50         for movie, users in movie_users.items():
 51             for u in users:
 52                 C.setdefault(u, defaultdict(int))
 53                 N.setdefault(u, 0)
 54                 N[u] += 1
 55                 for v in users:
 56                     if u == v:
 57                         continue
 58                 C[u][v] += 1
 59 
 60         train_user_num = len(self.train_set)  # 训练集用户数
 61         count = 1
 62         for u, related_users in C.items():
 63             print('
相似度计算进度:{:.2f}%'.format(count * 100 / train_user_num), end='')
 64             count += 1
 65             self.W.setdefault(u, {})
 66             for v, cuv in related_users.items():
 67                 self.W[u][v] = float(cuv) / math.sqrt(N[u] * N[v])
 68         print('
相似度计算完成')
 69 
 70     def recommend(self, u):
 71         ''' 通过与u最相似的K个用户推荐M部电影 '''
 72         rank = {}
 73         user_movies = self.train_set[u]
 74         for v, similarity in sorted(self.W[u].items(), key=itemgetter(1), reverse=True)[0:self.K]:
 75             for movie, rating in self.train_set[v].items():
 76                 if movie in user_movies:
 77                     continue
 78                 rank.setdefault(movie, 0)
 79                 rank[movie] += similarity * rating
 80         return sorted(rank.items(), key=itemgetter(1), reverse=True)[0:self.M]
 81 
 82     def evaluate(self):
 83         ''' 评测算法 '''
 84         hit = 0
 85         ret = 0
 86         rec_tot = 0
 87         pre_tot = 0
 88         tot_rec_movies = set()  # 推荐电影
 89         for user in self.train_set:
 90             test_movies = self.test_set.get(user, {})
 91             rec_movies = self.recommend(user)
 92             for movie, pui in rec_movies:
 93                 if movie in test_movies.keys():
 94                     hit += 1
 95                 tot_rec_movies.add(movie)
 96                 ret += math.log(1+self.movie_popularity[movie])
 97             pre_tot += self.M
 98             rec_tot += len(test_movies)
 99         precision = hit / (1.0 * pre_tot)
100         recall = hit / (1.0 * rec_tot)
101         coverage = len(tot_rec_movies) / (1.0 * self.tot_movie)
102         ret /= 1.0 * pre_tot
103         print('precision=%.4f' % precision)
104         print('recall=%.4f' % recall)
105         print('coverage=%.4f' % coverage)
106         print('popularity=%.4f' % ret)
107 
108 
109 if __name__ == '__main__':
110     data = pd.read_csv('u.data', sep='	', names=['user_id', 'item_id', 'rating', 'timestamp'])
111     usercf = UserCF()
112     usercf.split_data(data, 0.7)
113     usercf.user_similarity()
114     usercf.evaluate()

结果

在不同的K值下运行的结果

相似度计算的改进

在现实中,很多人因为电影热门而去看它,此时也许这并不是他的兴趣所在,如果两个人同时看了相同的冷门电影,那么也许他们更有可能有更高的相似度。

对此,可以适当降低热门电影的加成比例,提高冷门电影的加成比例。

因此,只需对上述代码做此修改

C[u][v] += 1 / math.log(1 + len(users))

再重新进行评测,发现修改后在各项性能上都有所提高。

原文地址:https://www.cnblogs.com/zyb993963526/p/12815426.html