内容推荐的介绍
- 基于内容的推荐方法是非常直接的,它以物品的内容描述信息为依据来做出的推荐,本质上是基于对物品和用户自身的特征或属性的直接分析和计算。例如,假设已知电影A是一部动作片,而恰巧我们得知某个用户特别喜欢看动作片,那么我们基于这样的已知信息,就可以将电影A推荐给该用户。
基于内容的推荐实现步骤
- 画像构建:顾名思义,画像就是刻画物品或用户的特征。本质上就是给用户或物品贴标签。
- 物品画像:分类信息、标题、电影/音乐:演员、歌手。
- 用户画像:喜欢的物品行为偏好、基本的人口学属性、活跃程度、风控维度。
- 标签的来源:
- PGC 物品画像–冷启动:物品自带的属性(物品一产生就具备的):如电影的标题、导演、演员、类型等等;服务提供方设定的属性(服务提供方为物品附加的属性):如短视频话题、微博话题(平台拟定);爬虫获取(其他渠道)。
- UGC 冷启动问题:用户在享受服务过程中提供的物品的属性:如用户评论内容,微博话题(用户拟定)
- 物品冷启动处理:
根据PGC内容构建的物品画像的可以解决物品的冷启动问题。- 根据PGC内容构建物品画像。
- 利用物品画像计算物品间两两相似情况。
- 为每个物品产生TOP-N最相似的物品进行相关推荐:如与该商品相似的商品有哪些?与该电影相似的电影有哪些。
基于内容推荐流程
- 建立物品画像
- 用户打tag;电影的分类值
- 根据电影的id 把tag和分类值合并起来 求tf-idf
- 根据tf-idf的结果 为每一部电影筛选出 top-n(tf-idf比较大的)个关键词
- 电影id-关键词-关键词权重
- 建立倒排索引
- 通过关键词找到电影
- 遍历 电影id-关键词-关键词权重 数据, 读取每一个关键词,用关键词作为key [(关键词对应的电影id,tf-idf)] 作为value 保存到dict当中。
- 用户画像
- 看用户看过那些电影,电影id-关键词-关键词权重数据中找到电影所对应的关键词
- 把用户看过的所有的关键词放到一起 统计词频 每个词出现了几次
- 出现次数多的关键词 作为用户的兴趣词,这个兴趣词实际上就是用户画像的关键词
- 根据用户的兴趣词 找到兴趣词对应的电影 多个兴趣词可能对应一个电影 {电影id:[关键词1权重,关键词2权重]} :把每一个部电影对应的关键词权重求和之后排序,按权重比较高的排在前面推荐给用户。
基于电影推荐的相关算法及代码实现
物品画像
- 物品画像构建步骤:
- 利用tags.csv中每部电影的标签作为电影的候选关键词。
- 利用TF·IDF计算每部电影的标签的tfidf值,选取TOP-N个关键词作为电影画像标签。
- 将电影的分类词直接作为每部电影的画像标签。
基于TF-IDF的特征提取技术
-
前面提到,物品画像的特征标签主要都是指的如电影的导演、演员、图书的作者、出版社等结构话的数据,也就是他们的特征提取,尤其是体征向量的计算是比较简单的,如直接给作品的分类定义0或者1的状态。但另外一些特征,比如电影的内容简介、电影的影评、图书的摘要等文本数据,这些被称为非结构化数据,首先他们本应该也属于物品的一个特征标签,但是这样的特征标签进行量化时,也就是计算它的特征向量时是很难去定义的。
-
因此这时就需要借助一些自然语言处理、信息检索等技术,将如用户的文本评论或其他文本内容信息的非结构化数据进行量化处理,从而实现更加完善的物品画像/用户画像。TF-IDF算法便是其中一种在自然语言处理领域中应用比较广泛的一种算法。可用来提取目标文档中,并得到关键词用于计算对于目标文档的权重,并将这些权重组合到一起得到特征向量。
-
TF-IDF算法原理
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指的是一个词语在文档中的出现频率。假设文档集包含的文档数为 N N N,文档集中包含关键词 k i k_i ki的文档数为 n i n_i ni, f i j f_{ij} fij表示关键词 k i k_i ki在文档 d j d_j dj中出现的次数, f d j f_{dj} fdj表示文档 d j d_j dj中出现的词语总数, k i k_i ki在文档dj中的词频 T F i j TF_{ij} TFij定义为: T F i j = f i j f d j TF_{ij}=\frac {f_{ij}}{f_{dj}} TFij=fdjfij。并且注意,这个数字通常会被正规化,以防止它偏向长的文件(指同一个词语在长文件里可能会比短文件有更高的词频,而不管该词语重要与否)。
IDF是一个词语普遍重要性的度量。表示某一词语在整个文档集中出现的频率,由它计算的结果取对数得到关键词 k i k_i ki的逆文档频率 I D F i IDF_i IDFi: I D F i = l o g N n i IDF_i=log\frac {N}{n_i} IDFi=logniN
由TF和IDF计算词语的权重为: w i j = T F i j w_{ij}=TF_{ij} wij=TFij· I D F i = f i j f d j IDF_{i}=\frac {f_{ij}}{f_{dj}} IDFi=fdjfij· l o g N n i log\frac {N}{n_i} logniN -
结论:TF-IDF与词语在文档中的出现次数成正比,与该词在整个文档集中的出现次数成反比。
-
用途:在目标文档中,提取关键词(特征标签)的方法就是将该文档所有词语的TF-IDF计算出来并进行对比,取其中TF-IDF值最大的k个数组成目标文档的特征向量用以表示文档。
-
注意:文档中存在的停用词(Stop Words),如“是”、“的”之类的,对于文档的中心思想表达没有意义的词,在分词时需要先过滤掉再计算其他词语的TF-IDF值。
物品画像代码实现
- 加载数据集
import pandas as pd
import numpy as np
'''
- 利用tags.csv中每部电影的标签作为电影的候选关键词
- 利用TF·IDF计算每部电影的标签的tfidf值,选取TOP-N个关键词作为电影画像标签
- 并将电影的分类词直接作为每部电影的画像标签
'''
def get_movie_dataset():
# 加载基于所有电影的标签
# all-tags.csv来自ml-latest数据集中
# 由于ml-latest-small中标签数据太多,因此借助其来扩充
_tags = pd.read_csv("datasets/ml-latest-small/all-tags.csv", usecols=range(1, 3)).dropna()
tags = _tags.groupby("movieId").agg(list)
# 加载电影列表数据集
movies = pd.read_csv("ml-latest-small/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)
# 构建电影数据集,包含电影Id、电影名称、类别、标签四个字段
# 如果电影没有标签数据,那么就替换为空列表
# map(fun,可迭代对象)
movie_dataset = pd.DataFrame(
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
movie_dataset = get_movie_dataset()
print(movie_dataset)
- 基于TF·IDF提取TOP-N关键词,构建电影画像
from gensim.models import TfidfModel
import pandas as pd
import numpy as np
from pprint import pprint
# ......
def create_movie_profile(movie_dataset):
'''
使用tfidf,分析提取topn关键词
:param movie_dataset:
: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, mid in enumerate(movie_dataset.index):
# 根据每条数据返回,向量
vector = model[corpus[i]]
# 按照TF-IDF值得到top-n的关键词
movie_tags = sorted(vector, key=lambda x: x[1], reverse=True)[:30]
# 根据关键词提取对应的名称
movie_profile[mid] = dict(map(lambda x:(dct[x[0]], x[1]), movie_tags))
return movie_profile
movie_dataset = get_movie_dataset()
pprint(create_movie_profile(movie_dataset))
- 完善画像关键词
from gensim.models import TfidfModel
import pandas as pd
import numpy as np
from pprint import pprint
# ......
def create_movie_profile(movie_dataset):
'''
使用tfidf,分析提取topn关键词
:param movie_dataset:
: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()
pprint(create_movie_profile(movie_dataset))
倒排索引原理及代码实现
- 通常数据存储数据,都是以物品的ID作为索引,去提取物品的其他信息数据,而倒排索引就是用物品的其他数据作为索引,去提取它们对应的物品的ID列表。
- 相关代码
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)
用户画像原理及代码实现
-
用户画像构建步骤:
- 根据用户的评分历史,结合物品画像,将有观影记录的电影的画像标签作为初始标签反打到用户身上。
- 通过对用户观影标签的次数进行统计,计算用户的每个初始标签的权重值,排序后选取TOP-N作为用户最终的画像标签。
-
相关代码
import pandas as pd
import numpy as np
from gensim.models import TfidfModel
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("ml-latest-small/ratings.csv", usecols=range(2), dtype={"userId":np.int32, "movieId": np.int32})
watch_record = watch_record.groupby("userId").agg(list)
# print(watch_record)
movie_dataset = get_movie_dataset()
movie_profile = create_movie_profile(movie_dataset)
user_profile = {}
for uid, mids in watch_record.itertuples():
record_movie_prifole = movie_profile.loc[list(mids)]
counter = collections.Counter(reduce(lambda x, y: list(x)+list(y), record_movie_prifole["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)