电影推荐系统 基于内容相似度的召回
转载代码:https://zhuanlan.zhihu.com/p/98295397
对上述文章代码进行了实际跑通。
废话不说,直接上代码:
数据说明:
item_profile.json user_profile.json 是加工出来的特征。
就是使用所有电影风格作为用户和电影的几个特征列,对每个用户的这几个电影风格列填充,用户评分的所有电影中有这个风格就是1,没有就是0。同样,电影特征列也是使用这几个电影风格,保持和用户的顺序一致。同样做填充1或0,这样就可以做相似度计算了。有些人认为基于这样的特征做相似度计算就是基于内容的推荐了。有些认为基于内容的推荐,大多是文本场景推荐,基于文本内容的向量化做相似度计算。
我认为,这是基于用户的行为产生了用户和物品的属性交换和转移,原来电影的风格属性转移到了用户身上,这样用户和物品有了相同的属性特征维度,就可以进行相似度的度量了。
因此,基于这样的特征做推荐,我感觉叫做基于画像标签的推荐更合适,实际是对用户进行了电影属性的画像,把电影的属性转移到了用户身上。当然画像不仅仅是打标签分类,也可以像这个特征处理一样,将电影的维度维度、及维度层级作为标签对待。
modelSamples.csv 数据是王哲的电影推荐系统,离线处理的数据,这里直接拿来使用,即加工了用户点击的电影的5个风格,作为用户的特征列。
# coding: utf-8 -*-
"""
Author: Alan
Desc:
编写一个基于内容推荐算法的电影推荐系统(训练模型)
"""
import json
import pandas as pd
import numpy as np
import math
import random
class CBRecommend:
# 加载dataProcessing.py中预处理的数据
def __init__(self, K):
# 给用户推荐的item个数
self.K = K
self.item_profile = json.load(open("./item_profile.json", "r"))
self.user_profile = json.load(open("./user_profile.json", "r"))
# 获取用户未进行评分的item列表
def get_none_score_item(self, user):
infile = "E:/IdeaProjects/SparrowRecSys-master/TFRecModel/src/com/sparrowrecsys/sampledata/"
items = pd.read_csv(infile + "movies.csv")["movieId"].values
data = pd.read_csv(infile + "ratings.csv")
have_score_items = data[data["userId"] == user]["movieId"].values
none_score_items = set(items) - set(have_score_items)
return none_score_items
# 获取用户对item的喜好程度(余弦相似度)
def cosUI(self, user, item):
user_vec = np.array(self.user_profile.get(str(user), [0]))
item_vec = np.array(self.item_profile.get(str(item), [0]))
# print("user_vec = ", user_vec)
# print("item_vec = ", item_vec)
Uia = sum(user_vec * item_vec)
# print("Uia = ", Uia)
# print(type(Uia))
if Uia.item() == 0:
return 0.0
Ua = math.sqrt(sum([math.pow(one, 2) for one in self.user_profile[str(user)]]))
Ia = math.sqrt(sum([math.pow(one, 2) for one in self.item_profile[str(item)]]))
return Uia / (Ua * Ia)
# 为用户进行电影推荐
def recommend(self, user):
user_result = {}
item_list = self.get_none_score_item(user)
for item in item_list:
user_result[item] = self.cosUI(user, item)
if self.K is None:
result = sorted(
user_result.items(), key=lambda k: k[1], reverse=True
)
else:
result = sorted(
user_result.items(), key=lambda k: k[1], reverse=True
)[:self.K]
print(result)
# 推荐系统效果评估
def evaluate(self):
evas = []
infile = "E:/IdeaProjects/SparrowRecSys-master/TFRecModel/src/com/sparrowrecsys/sampledata/"
data = pd.read_csv(infile + "ratings.csv")
# 随机选取20个用户进行效果评估
for user in random.sample([one for one in range(1, 6040)], 20):
have_score_items = data[data["userId"] == user]["movieId"].values
items = pd.read_csv(infile + "movies.csv")["movieId"].values
user_result = {}
for item in items:
user_result[item] = self.cosUI(user, item)
results = sorted(
user_result.items(), key=lambda k: k[1], reverse=True
)[:len(have_score_items)]
rec_items = []
for one in results:
rec_items.append(one[0])
eva = len(set(rec_items) & set(have_score_items)) / len(have_score_items)
evas.append(eva)
return sum(evas) / len(evas)
# 获取用户、电影属性表示
def get_users_movies_profile(self):
infile = "E:/IdeaProjects/SparrowRecSys-master/TFRecModel/src/com/sparrowrecsys/sampledata/modelSamples.csv"
data = pd.read_csv(infile)
cols = np.concatenate([data['userGenre1'].unique(),
data['userGenre2'].unique(),
data['userGenre3'].unique(),
data['userGenre4'].unique(),
data['userGenre5'].unique()]).astype(np.str)
genres = np.unique(cols)
nan = np.array(['nan']).astype(np.str)
genres = set(np.setdiff1d(genres, nan))
'''
{'Action',
'Adventure',
'Animation',
'Children',
'Comedy',
'Crime',
'Documentary',
'Drama',
'Fantasy',
'Film-Noir',
'Horror',
'IMAX',
'Musical',
'Mystery',
'Romance',
'Sci-Fi',
'Thriller',
'War',
'Western'}
'''
# 用户属性
user_profile_tmp = data.groupby('userId').apply(
lambda df: set(np.setdiff1d(np.unique(np.concatenate([df['userGenre1'].unique(),
df['userGenre2'].unique(),
df['userGenre3'].unique(),
df['userGenre4'].unique(),
df['userGenre5'].unique()]).astype(np.str)), nan)))
user_profile = pd.DataFrame(user_profile_tmp)
user_profile['userId'] = user_profile.index
# user_profile.rename(columns={'favor_genres': '0'}, inplace=True)
user_profile.columns = ['favor_genres', 'userId']
for genre in genres:
user_profile[genre] = user_profile['favor_genres'].apply(lambda x: 1 if genre in x else 0)
# 电影属性
item_profile_tmp = data.groupby('movieId').apply(
lambda df: set(np.setdiff1d(np.unique(np.concatenate([df['movieGenre1'].unique(),
df['movieGenre2'].unique(),
df['movieGenre3'].unique()]).astype(np.str)), nan)))
item_profile = pd.DataFrame(item_profile_tmp)
item_profile['movieId'] = item_profile.index
# user_profile.rename(columns={'favor_genres': '0'}, inplace=True)
item_profile.columns = ['favor_genres', 'movieId']
for genre in genres:
item_profile[genre] = item_profile['favor_genres'].apply(lambda x: 1 if genre in x else 0)
# 转json形式存储
# js = item_profile.drop(columns=['favor_genres']).to_json(orient="values", force_ascii=False)
# filename = "./item_profile.json"
# with open(filename, 'w', encoding='utf-8') as f:
# json.dump(js, f, ensure_ascii=False)
# items = json.load(open(filename, "r"))
# items = item_profile.set_index('movieId').drop(columns=['favor_genres']).to_dict('list')
users = user_profile.drop(columns=['favor_genres']).to_numpy()
users_dict = {}
for item in users:
users_dict[str(item[0])] = item[1:].tolist()
json.dumps(users_dict)
filename = "./user_profile.json"
with open(filename, 'w', encoding='utf-8') as f:
json.dump(users_dict, f, ensure_ascii=False)
items = item_profile.drop(columns=['favor_genres']).to_numpy()
items_dict = {}
for item in items:
items_dict[str(item[0])] = item[1:].tolist()
json.dumps(items_dict)
filename = "./item_profile.json"
with open(filename, 'w', encoding='utf-8') as f:
json.dump(items_dict, f, ensure_ascii=False)
if __name__ == "__main__":
cb = CBRecommend(K=10)
simi = cb.cosUI(1, 1)
cb.recommend(1)
print(cb.evaluate())