├── README.md └── main.py /README.md: -------------------------------------------------------------------------------- 1 | # 电影推荐系统 2 | 3 | ## 原始数据 4 | 5 | 本实验采用数据集[MovieLens 100K Dataset](http://grouplens.org/datasets/movielens/)。这个数据集由国外GroupLens Research团队整理提供。 6 | 7 | 这个数据集收集了943个用户对1682部电影的总共10,000条记录。所用文件如下 8 | 9 | - u.data: 10,000条记录,每一条记录格式为 user id | item id | rating | timestamp,其中每一个用户至少有20条评价。 10 | - u.user: 每一条记录格式为 user id | age | gender | occupation | zip code。 11 | - u.occupation: 所有工作的列表。 12 | 13 | 14 | 15 | ## 数据处理 16 | 17 | 虽然每个评价在1到5之内,由于每个用户的习惯不一样,导致某个用户评价普遍偏高,或者普遍偏低。为了消除这种影响,需要对每个用户的评价进行正规化,以下是正规化的代码。 18 | 19 | ```python 20 | # 读入数据 21 | base=np.loadtxt("ml-100k/u.data", int) 22 | # 初始化: 默认情况下每个用户对每部电影都是-1分, 表示没有评价 23 | # rate是一个二维数组, 行数为user行, 列数为movies列 24 | rate=np.ones([users, movies])*(-1) 25 | # 将已经知道的数据录入到rate表里 26 | for j in range(base.shape[0]): 27 | rate[base[j, 0]-1, base[j, 1]-1]=base[j, 2] 28 | avg=np.zeros(users) 29 | std=np.zeros(users) 30 | for j in range(users): 31 | tem=rate[j, :] 32 | tem=tem[tem!=-1] 33 | avg[j]=(tem.sum()+0.0)/tem.shape[0] 34 | std[j]=np.std(tem) 35 | rate_V=np.zeros([users, movies]) 36 | for j in range(users): 37 | rate_V[j, :]=avg[j]*np.ones(movies) 38 | rate_V[rate!=-1]=rate[rate!=-1] 39 | # Z-normalization 40 | for i in range(users): 41 | for j in range(movies): 42 | rate_V[i, j]=(rate_V[i, j]-avg[i])/std[i] 43 | ``` 44 | 45 | 46 | 47 | ## 算法基本思想 48 | 49 | 为了向用户进行推荐, 50 | 51 | * 对于每一部用户U尚未评价的电影M,找到与其“相似”并且对电影M有评价的那些用户 52 | * 然后取平均值,作为用户U对电影M的评价 53 | * 将所有估算的电影的评价从高到低排序,选取前几部进行推荐 54 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import numpy as np 3 | from scipy.spatial import distance 4 | 5 | # 用户总数, 用户用0到942来标识 6 | users=943 7 | # 电影数目, 电影用0到1681来标识 8 | movies=1682 9 | # 用户工作的种类 10 | jobs_num=21 11 | # 所有的工作 12 | jobs=['administrator', 'artist','doctor','educator','engineer','entertainment','executive', 13 | 'healthcare','homemaker','lawyer','librarian','marketing','none','other','programmer','retired','salesman' 14 | ,'scientist','student','technician','writer'] 15 | # 用户数据 16 | user_raw=np.loadtxt("ml-100k/u.user", str, delimiter='|') 17 | # 默认推荐5部电影 18 | guess=5 19 | # 选取相似的用户数目 20 | selection=15 21 | # 读入数据 22 | base=np.loadtxt("ml-100k/u.data", int) 23 | # 初始化: 默认情况下每个用户对每部电影都是-1分, 表示没有评价 24 | # rate是一个二维数组, 行数为user行, 列数为movies列 25 | rate=np.ones([users, movies])*(-1) 26 | # 将已经知道的数据录入到rate表里 27 | for j in range(base.shape[0]): 28 | rate[base[j, 0]-1, base[j, 1]-1]=base[j, 2] 29 | avg=np.zeros(users) 30 | std=np.zeros(users) 31 | for j in range(users): 32 | tem=rate[j, :] 33 | tem=tem[tem!=-1] 34 | avg[j]=(tem.sum()+0.0)/tem.shape[0] 35 | std[j]=np.std(tem) 36 | rate_V=np.zeros([users, movies]) 37 | for j in range(users): 38 | rate_V[j, :]=avg[j]*np.ones(movies) 39 | rate_V[rate!=-1]=rate[rate!=-1] 40 | # Z-normalization 41 | for i in range(users): 42 | for j in range(movies): 43 | rate_V[i, j]=(rate_V[i, j]-avg[i])/std[i] 44 | 45 | 46 | def compute_dist1(user_guess): 47 | # tem_1d为一维数据 48 | tem_1d=rate_V[user_guess, :] 49 | # 将tem_1d转化为二维数据 50 | tem_2d=np.zeros([1, movies]) 51 | tem_2d[0, :]=tem_1d 52 | # 算法二 根据用户以往观看过的电影进行推荐 53 | # 计算任意两个用户的距离, 距离越小代表相似度越小 54 | dist=distance.cdist(tem_2d, rate_V, 'euclidean') 55 | # 将得到的结果转化为一维数据 56 | dist=dist[0, :] 57 | return dist 58 | 59 | 60 | def compute_dist2(user_guess): 61 | user_data=np.zeros([users, jobs_num+4]) 62 | for j in range(user_raw.shape[0]): 63 | if user_raw[j, 2]=='M': 64 | user_data[j, jobs_num]=1 65 | for k in range(jobs_num): 66 | if user_raw[j, 3]==jobs[k]: 67 | user_data[j, k]=1 68 | break 69 | age=int(user_raw[j, 1]) 70 | # 将年龄分为3个年临段分为(0~30), (30~60), (60~) 71 | if age<30: 72 | user_data[j, jobs_num+1]=1 73 | elif age<60: 74 | user_data[j, jobs_num+2]=1 75 | else: 76 | user_data[j, jobs_num+3]=1 77 | tem_1d=user_data[user_guess, :] 78 | tem_2d=np.zeros([1, jobs_num+4]) 79 | tem_2d[0, :]=tem_1d 80 | dist=distance.cdist(tem_2d, user_data, 'cityblock') 81 | dist=dist[0, :] 82 | return dist 83 | 84 | 85 | def recom(dist, i, user_guess): 86 | # 初始化猜测的分数 87 | pred=np.ones(movies)*(-100) 88 | for j in range(movies): 89 | # 当没有对一部电影评价的时候才需要预测 90 | if rate[user_guess, j]==-1: 91 | tem=np.array([rate_V[:, j], dist]).T 92 | # 选取所有对这部电影有评价的其它用户的评价 93 | tem=tem[rate[:, j]!=-1] 94 | # 根据距离进行排序 95 | tem=tem[tem[:, 1].argsort()] 96 | # 取所有的评价 97 | tem=tem[:, 0] 98 | if tem.shape[0]>selection: 99 | tem=tem[0:selection] 100 | if tem.shape[0]>0: 101 | pred[j]=(tem.sum()+0.0)/tem.shape[0] 102 | tem=np.array([range(movies), pred]).T 103 | # 去掉那些已经评价的 104 | tem=tem[rate[:, user_guess]!=-1] 105 | tem=tem[tem[:, 1].argsort()][::-1] 106 | print("算法%d推荐的电影的编号(排名不分先后):"%(i)) 107 | print tem[0:guess, 0] 108 | 109 | 110 | if __name__ == '__main__': 111 | user_guess=int(raw_input("请输入需要预测的用户编号(0到1681):\n")) 112 | if user_guess not in range(1682): 113 | print("没有这个用户") 114 | exit(0) 115 | dist1=compute_dist1(user_guess) 116 | dist2=compute_dist2(user_guess) 117 | a=0.3 118 | dist3=dist1+a*dist2 119 | recom(dist1, 1, user_guess) 120 | recom(dist2, 2, user_guess) 121 | recom(dist3, 3, user_guess) 122 | --------------------------------------------------------------------------------