近日在做一个影视网站时,考虑将推荐系统集成到网站中,所以从网上查阅了一些资料,最终得以实现,下面将自己的实现原理及过程写下来,以便作为记录。
1、影视相似度计算
这个推荐系统的主要是根据用户的观看记录,然后为其推荐相似的影视,所以最后采用了基于内容的协同过滤算法来实现,算法中采用欧几里德距离作为影视相似度的衡量标准。代码如下:
# 计算两个物品的相似度(欧几里德距离)
def calculate_euclidean(movie1,movie2, types, weight_types_dic):
#如果两数据集数目不同,计算两者之间都对应有的数
#计算欧几里德距离,并将其标准化
sum = 0
for i in range(len(types)):
tmp_type = types[i]
weight = weight_types_dic[tmp_type]
tmp_type_content1 = movie1[tmp_type]
tmp_type_content2 = movie2[tmp_type]
if (type(tmp_type_content1).__name__ == 'list' and type(tmp_type_content2).__name__ == 'list'):
tmp_type_content1 = '|'.join(tmp_type_content1)
tmp_type_content2 = '|'.join(tmp_type_content2)
similarity = weight * get_equal_rate_1(tmp_type_content1, tmp_type_content2)
sum += similarity
# print(tmp_type + ' ' + tmp_type_content1 + ' ' + tmp_type_content2 + ' ' + (str)(similarity))
euclidean = sum / 10
print(movie1['name'] + ' 和 ' + movie2['name'] + ' 相似度为 ' + (str)(euclidean) + '\n')
return euclidean
2、影视相似度矩阵计算
由于服务器资源的限制,所以没有采用消息队列的方式来实现计算影视相似度,这里影视相似度的计算主要分为两种类型:
1、总量
这种情况主要用于推荐系统的初始化阶段,计算当前服务器中所有影视之间的相似度,总量计算只进行一次,以后只进行增量计算。
2、增量
每天计算前一天通过爬重新爬取到的影视与其他影视之间的相似度,同时计算其他影视与前一天新爬取到的影视之间的相似度,然后对影视相似度矩阵进行更新。
代码如下:
# 对字典列表根据指定的key去重
def distinct(items,key):
key = itemgetter(key)
items = sorted(items, key=key)
return [next(v) for _, v in groupby(items, key=key)]
# 获取昨天的日期
def get_yesterday():
today=datetime.date.today()
oneday=datetime.timedelta(days=1)
yesterday=today-oneday
return (str)(yesterday)
# 计算两个字符串的相似度
def get_equal_rate_1(str1, str2):
if (str2 == None):
str2 = ''
if (type(str1).__name__ == 'list' or type(str2).__name__ == 'list'):
str1 = '|'.join(str1)
str2 = '|'.join(str2)
return difflib.SequenceMatcher(None, str1.lower(), str2.lower()).quick_ratio()
# 计算影视的相似度
# 比较的项目:name、type、type2、region、language、release_date
def get_recommendations(movie_type, type):
if (movie_type == 'movie'):
types = ['name', 'type', 'type2', 'region', 'language', 'release_date', 'directors', 'actors']
weight_types_dic = {'name': 2.5, 'type': 0.5, 'type2': 0.5, 'region': 0.5, 'language': 0.5, 'release_date': 0.5,
'directors': 2.5, 'actors': 2.5}
collection = 'movie'
elif (movie_type == 'drama'):
types = ['name', 'type']
weight_types_dic = {'name': 6, 'type': 4}
collection = 'drama'
elif (movie_type == 'piece'):
types = ['name', 'type', 'type2', 'description']
weight_types_dic = {'name': 4, 'type': 2, 'type2': 2, 'description': 2}
collection = 'piece'
# 计算所有影视之间的相似度
db_utils = MongoDbUtils(collection)
db_utils2 = MongoDbUtils(collection)
if (type == 'all'):
# 计算所有影视之间的相似度
dic = {}
dic2 = {}
elif (type == 'latest'):
# 计算最近更新的影视与其它影视之间的相似度
dic = {'acquisition_time': {'$regex': '.*' +get_yesterday() + '.*'}}
dic2 = {}
# 计算当前影视与其它影视之间的相似度
movies = db_utils.find(dic)
total = movies.count() + 1
for i, movie1 in enumerate(movies):
collection = 'recommendations'
db_utils3 = MongoDbUtils(collection)
db_utils5 = MongoDbUtils(collection)
tmp_dic = [{'$project': {"_id": 0, "euclidean": 0}}, {'$match': {"temp_id": movie1['_id']}}]
movies2 = db_utils2.find(dic2)
dic3 = {'temp_id': movie1['_id']}
sort_movies = (list)(db_utils5.find(dic3).sort([('euclidean', -1)]))
if (len(sort_movies) < 20):
min_euclidean = 0
else:
min_euclidean = sort_movies[len(sort_movies) - 1]['euclidean']
total2 = movies2.count() + 1
recommendations = []
for j, movie2 in enumerate(movies2):
# 如果两个影视的_id相同(同一个影视),则跳过
if (movie2['_id'] == movie1['_id']):
continue
# 如果两个影视的相似度已获取,则跳过
print('正在计算 ' + (str)(i + 1) + '/' + (str)(total) + ' ' + (str)(j + 1) + '/' + (str)(total2) + ' ' +
movie1['name'] + ' ' + movie2['name'])
euclidean = calculate_euclidean(movie1, movie2, types, weight_types_dic)
if (euclidean < min_euclidean):
print('跳过 ' + (str)(i + 1) + '/' + (str)(total) + ' ' + (str)(j + 1) + '/' + (str)(total2) + ' ' +
movie1['name'] + ' ' + movie2['name'])
continue
recommendation = {'temp_id': movie1['_id'], 'temp_id2': movie2['_id'], 'euclidean': euclidean}
recommendations.append(recommendation)
recommendations = distinct(recommendations, 'temp_id2')
recommendations = sorted(recommendations, key=lambda x: x['euclidean'], reverse=True)[:20]
if (len(sort_movies) > 0 and recommendations[len(recommendations) - 1]['euclidean'] == sort_movies[len(sort_movies) - 1]['euclidean']):
print(movie1['name'] + ' 推荐数据不用更新')
continue
# 删除当前影视的原有推荐数据,然后插入新的推荐数据
try:
db_utils5.delete(dic3)
db_utils5.insert(recommendations)
except:
continue
# 计算其他影视与当前影视的相似度,以便更新其他影视的推荐数据
if (type == 'latest'):
# 昨日有更新数据
if (total > 1):
movies = db_utils.find(dic2)
total = movies.count() + 1
for i, movie1 in enumerate(movies):
collection = 'recommendations'
db_utils5 = MongoDbUtils(collection)
db_utils6 = MongoDbUtils(collection)
tmp_dic = [{'$project': {"_id": 0, "euclidean": 0}}, {'$match': {"temp_id": movie1['_id']}}]
movies2 = db_utils2.find(dic)
dic3 = {'temp_id': movie1['_id']}
sort_movies = (list)(db_utils5.find(dic3).sort([('euclidean', -1)]))
if (len(sort_movies) < 20):
min_euclidean = 0
else:
min_euclidean = sort_movies[len(sort_movies) - 1]['euclidean']
total2 = movies2.count() + 1
recommendations = (list)(db_utils6.find(dic3).sort([('euclidean', -1)]))
for j, movie2 in enumerate(movies2):
# 如果两个影视的_id相同(同一个影视),则跳过
if (movie2['_id'] == movie1['_id']):
continue
# 如果两个影视的相似度已获取,则跳过
print('正在计算 ' + (str)(i + 1) + '/' + (str)(total) + ' ' + (str)(j + 1) + '/' + (str)(total2) + ' ' +
movie1['name'] + ' ' + movie2['name'])
euclidean = calculate_euclidean(movie1, movie2, types, weight_types_dic)
if (euclidean < min_euclidean):
print('跳过 ' + (str)(i + 1) + '/' + (str)(total) + ' ' + (str)(j + 1) + '/' + (str)(total2) + ' ' +
movie1['name'] + ' ' + movie2['name'])
continue
recommendation = {'temp_id': movie1['_id'], 'temp_id2': movie2['_id'], 'euclidean': euclidean}
recommendations.append(recommendation)
recommendations = distinct(recommendations, 'temp_id2')
recommendations = sorted(recommendations, key=lambda x: x['euclidean'], reverse=True)[:20]
if (len(sort_movies) > 0 and recommendations[len(recommendations) - 1]['euclidean'] ==
sort_movies[len(sort_movies) - 1]['euclidean']):
print(movie1['name'] + ' 推荐数据不用更新')
continue
# 删除当前影视的原有推荐数据,然后插入新的推荐数据
print((str)(movie1['_id']) + ' ' + (str)(min_euclidean) + ' ' + (str)(recommendations[len(recommendations) - 1]['euclidean']))
db_utils5.delete(dic3)
try:
db_utils5.insert(recommendations)
except:
continue
3、测试示例及结果
公共部分:
types = ['name', 'type', 'type2', 'region', 'language', 'release_date', 'directors', 'actors']
weight_types_dic = {'name': 2.5, 'type': 0.5, 'type2': 0.5, 'region': 0.5, 'language': 0.5, 'release_date': 0.5,
'directors': 2.5, 'actors': 2.5}
collection = 'movie'
db_utils = MongoDbUtils(collection)
drama1 = db_utils.find({'name': '匆匆那年'}).__getitem__(0)
drama2 = db_utils.find({'name': '匆匆那年(国剧)'}).__getitem__(0)
calculate_euclidean(drama1, drama2, types, weight_types_dic)
结果:
1、匆匆那年
和匆匆那年(国剧)
name 匆匆那年 匆匆那年(国剧) 1.6666666666666665
type 电影 电视剧 0.2
type2 爱情片 国产剧 0.0
region 大陆 大陆 0.5
language 国语 国语 0.5
release_date 2014 2014 0.5
directors 张一白 姚婷婷 0.0
actors 彭于晏|倪妮|郑恺|魏晨|张子萱 杨�W|何泓姗|白敬亭|杜维瀚|蔡文静 0.5714285714285714
匆匆那年 和 匆匆那年(国剧) 相似度为 0.3938095238095237
2、匆匆那年
和泰版匆匆那年
name 匆匆那年 泰版匆匆那年 2.0
type 电影 电视剧 0.2
type2 爱情片 海外剧 0.0
region 大陆 泰国 0.0
language 国语 其它 0.0
release_date 2014 2019 0.375
directors 张一白 Sakon Tiacharoen 0.0
actors 彭于晏|倪妮|郑恺|魏晨|张子萱 提顶·玛哈由踏纳|安苏玛琳·瑟拉帕萨默莎|查澈威·德查拉朋 0.22222222222222224
匆匆那年 和 泰版匆匆那年 相似度为 0.27972222222222226
3、匆匆那年
和匆匆那年好久不见
name 匆匆那年 匆匆那年好久不见 1.6666666666666665
type 电影 电视剧 0.2
type2 爱情片 国产剧 0.0
region 大陆 大陆 0.5
language 国语 国语 0.5
release_date 2014 2017 0.375
directors 张一白 李远/韩天 0.0
actors 彭于晏|倪妮|郑恺|魏晨|张子萱 张彬彬/王萌黎/金泽灏 0.18518518518518517
匆匆那年 和 匆匆那年好久不见 相似度为 0.34268518518518515
4、匆匆那年(国剧)
和泰版匆匆那年
name 匆匆那年(国剧) 泰版匆匆那年 1.4285714285714284
type 电视剧 电视剧 0.5
type2 国产剧 海外剧 0.16666666666666666
region 大陆 泰国 0.0
language 国语 其它 0.0
release_date 2014 2019 0.375
directors 姚婷婷 Sakon Tiacharoen 0.0
actors 杨�W|何泓姗|白敬亭|杜维瀚|蔡文静 提顶·玛哈由踏纳|安苏玛琳·瑟拉帕萨默莎|查澈威·德查拉朋 0.20833333333333331
匆匆那年(国剧) 和 泰版匆匆那年 相似度为 0.26785714285714285
5、匆匆那年(国剧)
和匆匆那年好久不见
name 匆匆那年(国剧) 匆匆那年好久不见 1.25
type 电视剧 电视剧 0.5
type2 国产剧 国产剧 0.5
region 大陆 大陆 0.5
language 国语 国语 0.5
release_date 2014 2017 0.375
directors 姚婷婷 李远/韩天 0.0
actors 杨�W|何泓姗|白敬亭|杜维瀚|蔡文静 张彬彬/王萌黎/金泽灏 0.0
匆匆那年(国剧) 和 匆匆那年好久不见 相似度为 0.3625
6、泰版匆匆那年
和匆匆那年好久不见
name 泰版匆匆那年 匆匆那年好久不见 1.4285714285714284
type 电视剧 电视剧 0.5
type2 海外剧 国产剧 0.16666666666666666
region 泰国 大陆 0.0
language 其它 国语 0.0
release_date 2019 2017 0.375
directors Sakon Tiacharoen 李远/韩天 0.0
actors 提顶·玛哈由踏纳|安苏玛琳·瑟拉帕萨默莎|查澈威·德查拉朋 张彬彬/王萌黎/金泽灏 0.0
泰版匆匆那年 和 匆匆那年好久不见 相似度为 0.24702380952380948
完整代码地址:https://github.com/wpwbb510582246/PocketFilm/tree/master/Recommender 父项目地址:https://github.com/wpwbb510582246/PocketFilm