一、基于内容的推荐算法
1、简介
基于内容的推荐方法是非常直接的,它以物品的内容描述信息为依据来做出的推荐,本质上是基于对物品和用户自身的特征或属性的直接分析和计算。
例如,假设已知电影A是一部喜剧,而恰巧我们得知某个用户喜欢看喜剧电影,那么我们基于这样的已知信息,就可以将电影A推荐给该用户。
基于内容的推荐实现步骤
画像构建。顾名思义,画像就是刻画物品或用户的特征。本质上就是给用户或物品贴标签。
- 物品画像。例如给电影《战狼2》贴标签,可以有哪些?“动作”、“吴京”、“吴刚”、“张翰”、“大陆电影”、“国产”、“爱国”、“军事”等等一系列标签是不是都可以贴上。
- 用户画像。例如已知用户的观影历史是:“战狼1”、“战狼2”、“建党伟业”、“建军大业”、“建国大业”、“红海行动”、“速度与激情1-8”等,我们是不是就可以分析出该用户的一些兴趣特征如:“爱国”、“战争”、“赛车”、“动作”、“军事”、“吴京”、“韩三平”等标签。
问题:物品的标签来自哪儿?
a)PGC 物品画像--冷启动
- 物品自带的属性(物品一产生就具备的):如电影的标题、导演、演员、类型等等;
- 服务提供方设定的属性(服务提供方为物品附加的属性):如短视频话题、微博话题(平台拟定)
- 其他渠道:如爬虫
根据PGC内容构建的物品画像可以解决物品的冷启动问题
b)UGC 冷启动问题
- 用户在享受服务过程中提供的物品的属性:如用户评论内容,微博话题(用户拟定)
基于内容推荐的算法流程
- 根据PGC/UGC内容构建物品画像
- 根据用户行为记录生成用户画像
- 根据用户画像从物品中寻找最匹配的TOP-N物品进行推荐
物品冷启动处理
- 根据PGC内容构建物品画像
- 利用物品画像计算物品间两两相似情况
- 为每个物品产生TOP-N最相似的物品进行相关推荐:如与该商品相似的商品有哪些?与该文章相似的文章有哪些?
二、电影画像构建
物品画像构建步骤:
- 利用tags.csv中每部电影的标签作为电影的候选关键词
- 利用TFIDF计算每部电影的标签的tfidf值,选取TOP-N个关键词作为电影画像标签
- 将电影的分类词直接作为每部电影的画像标签
1、基于TF-IDF的特征提取技术
1)TF-IDF介绍
上面提到,物品画像的特征标签主要都是指的如电影、导演、演员、图书的作者、出版社等结构化的数据,也就是他们的特征提取,尤其是体征向量的计算是比较简单的,如直接给作品的分类定义为0或1的状态。
但另外一些特征,比如电影的内容简介、电影的影评、图书的摘要等文本数据,这些被称为非结构化数据,首先他们本应该也属于物品的一个特征标签,但是这样的特征标签进行量化时,也就是计算他的特征向量时是很难去定义的。
因此这时就需要借助一些自然语言处理、信息检索等技术,如将用户的文本评论或其他文本内容信息的非结构化数据进行量化处理,从而实现更加完善的物品画像或用户画像。
TF-IDF算法便是其中一种在自然语言处理领域中应用比较广泛的一种算法。可用来提取目标文档,并得到关键词用于计算对于目标文档权重,并将这些权重组合到一起得到特征向量。
2)算法原理
TF-IDF自然语言处理领域中计算文档中词或短语的权值的方法,是词频(Term Frequency,TF)和逆文档频率(Inverse Document Frequency,IDF)的乘积。TF指的是某一个给定的词语在该文件中出现的次数。这个数字通常会被正则化,以防止它偏向长的文件(同一个词语在长文件里可能会比短文件有更高的词频,而不管该词语重要与否)。IDF是一个词语普遍重要性的度量,某一特定词语的IDF,可由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。
TF-IDF算法基于一个这样的假设:若一个词语在目标文档中出现的频率高而在其他文档中出现的频率低,那么这个词语就可以用来区分出目标文档。这个假设需要掌握的有两点:
- 在本文档出现的频率高
- 在其他文档出现的频率低
因此,TF-IDF算法的计算可以分为词频(Term Frequency,TF)和逆文档频率(Inverse Document Frequency,IDF)两部分,由TF和IDF的乘积来设置文档词语的权重。
结论:TF-IDF与词语在文档中的出现次数成正比,与该词在整个文档集中的出现次数成反比。
用途:在目标文档中,提取关键词(特征标签)的方法就是将该文档所有词语的TF-IDF计算出来并进行对比,取其中TF-IDF值最大的k个数组成目标文档的特征向量用以表示文档。
注意:文档中存在的停用词(Stop Words),如“是”、“的”之类,对于文档的中心思想表达没有意义的词,在分词时需要先过滤掉再计算其他词语的TF-IDF值。
2、算法实现
2.1、加载数据集并做处理
import pandas as pd
import numpy as np
'''
- 利用tags.csv中每部电影的标签作为电影的候选关键词
- 利用TF·IDF计算每部电影的标签的tfidf值,选取TOP-N个关键词作为电影画像标签
- 并将电影的分类词直接作为每部电影的画像标签
'''
def get_movie_dataset():
# 加载基于所有电影的标签
_tags = pd.read_csv("./all-tags.csv",usecols=range(1,3)).dropna()
tags = _tags.groupby("movieId").agg([list])
# 加载电影列表数据集
movies = pd.read_csv("./movies.csv",index_col="movieId")
# 将类别词分开
movies["genres"] = movies["genres"].apply(lambda x: x.split("|"))
# 为每部电影匹配对应的标签数据,如果没有将会是NAN
movies_index = set(movies.index) & set(tags.index)
new_tags = tags.loc[list(movies_index)]
ret = movies.join(new_tags) # join按index进行组合,列方向
# 加载电影数据集,包含电影Id、电影名称、类别、标签四个字段
# 如果电影没有标签数据,那么就替换为空列表 map(fun,可迭代对象)
movie_dataset = pd.DataFrame(
list(map(lambda x: (x[0],x[1],x[2],x[2]+x[3]) if x[3] is not np.nan else (x[0],x[1],x[2],[]),ret.itertuples())),
columns=["movieId","title","genres","tags"]
)
movie_dataset.set_index("movieId",inplace=True)
return movie_dataset
2.2、基于TF-IDF提取TOP-N关键词并加入电影类别,构建电影画像
def create_movie_profile(movie_dataset):
'''
使用tfidf,分析提取topn关键词
:param movie_dataset:包含电影Id、电影名称、类别、标签四个字段
:return:
'''
dataset = movie_dataset['tags'].values
from gensim.corpora import Dictionary
# 根据数据集建立词袋,并统计词频,将所有词放入一个字典中,使用索引进行获取
dct = Dictionary(dataset)
# 根据每条数据,返回对应的词索引和词频
corpus = [dct.doc2bow(line) for line in dataset]
# 训练TF-IDF模型,即计算TF-IDF值
model = TfidfModel(corpus)
_movie_profile = []
for i,data in enumerate(movie_dataset.itertuples()):
mid = data[0]
title = data[1]
genres = data[2]
vector = model[corpus[i]]
movie_tags = sorted(vector,key=lambda x: x[1],reverse=True)[:30]
topN_tags_weights = dict(map(lambda x:(dct[x[0]],x[1]),movie_tags))
# 将类别词的添加进去,并设置权重值为1.0
for g in genres:
topN_tags_weights[g] = 1.0
topN_tags = [i[0] for i in topN_tags_weights.items()]
_movie_profile.append((mid,title,topN_tags,topN_tags_weights))
movie_profile = pd.DataFrame(_movie_profile,columns=["movieId","title","profile","weights"])
movie_profile.set_index("movieId",inplace=True)
return movie_profile
movie_dataset = get_movie_dataset()
movie_profile = create_movie_profile(movie_dataset)
movie_profile.head()
结果如图:
2.3、建立倒排索引
为了根据指定关键词迅速匹配到对应的电影,因此需要对物品画像的标签词,建立倒排索引。通常存储数据,都是以物品的ID作为索引,去提取物品的其他信息数据,而倒排索引就是用物品的其他数据作为索引,去提取它们对应的物品的ID列表。
# 建立tag-物品的倒排索引
def create_inverted_table(movie_profile):
inverted_table = {}
for mid,weights in movie_profile["weights"].iteritems():
for tag,weight in weights.items():
# inverted_table dict : 用tag作为key去取值,如果取不到就返回[]
_ = inverted_table.get(tag,[])
_.append((mid,weight))
inverted_table.setdefault(tag,_)
return inverted_table
inverted_table = create_inverted_table(movie_profile)
pprint(inverted_table)
运行结果:
三、基于用户画像的电影推荐
1、用户画像构建步骤
- 根据用户的评分历史,结合物品画像,将有观影记录的电影的画像标签作为初始标签反打到用户身上;
- 通过对用户观影标签的次数进行统计,计算用户的每个初始标签的权重值,排序后选取TOP-N作为用户最终的画像标签。
from functools import reduce
import collections
from pprint import pprint
'''
user profile画像建立
1.提取用户观看列表;
2.根据观看列表和物品画像为用户匹配关键词,并统计词频;
3.根据词频排序,最多保留TOP-k个词,这里k设为100,作为用户的标签。
'''
def create_user_profile():
watch_record = pd.read_csv("./ratings.csv",usecols=range(2),dtype={"userId":np.int32,"movieId":np.int32})
watch_record = watch_record.groupby("userId").agg([list])
movie_dataset = get_movie_dataset()
movie_profile = create_movie_profile(movie_dataset)
user_profile = {}
for uid,mids in watch_record.itertuples():
record_movie_profile = movie_profile.loc[list(mids)]
counter = collections.Counter(reduce(lambda x,y:list(x)+list(y),record_movie_profile["profile"].values))
# 兴趣词
interest_words = counter.most_common(50)
maxcount = interest_words[0][1]
interest_words = [(w,round(c/maxcount,4)) for w,c in interest_words]
user_profile[uid] = interest_words
return user_profile
user_profile = create_user_profile()
pprint(user_profile)
运行结果:
2、产生推荐结果
为用户产生TOP-N推荐结果
user_profile = create_user_profile()
for uid,interest_words in user_profile.items():
result_table = {} # 电影id:[0.2,0.5,0.7]
for interest_word,interest_weight in interest_words:
related_movies = inverted_table[interest_word]
for mid,related_weight in related_movies:
_ = result_table.get(mid,[])
_.append(interest_weight) # 只考虑用户的兴趣程度
# _.append(related_weight) # 只考虑兴趣词与电影的关联程度
# _.append(interest_weight*related_weight) # 二者都考虑
result_table.setdefault(mid,_)
rs_result = map(lambda x:(x[0],sum(x[1])),result_table.items())
rs_result = sorted(rs_result,key=lambda x:x[1],reverse=True)[:100]
print(uid)
pprint(rs_result)
break
运行结果:
----------------------------------------------------------------
参考课程:推荐系统及算法,如侵删。