AI上推荐 之 基于内容的推荐(ContentBasedRecommend)

1. 前言

随着信息技术和互联网的发展, 我们已经步入了一个信息过载的时代,这个时代,无论是信息消费者还是信息生产者都遇到了很大的挑战:

  • 信息消费者:如何从大量的信息中找到自己感兴趣的信息?
  • 信息生产者:如何让自己生产的信息脱颖而出, 受到广大用户的关注?

为了解决这个矛盾, 推荐系统应时而生, 并飞速前进,在用户和信息之间架起了一道桥梁,一方面帮助用户发现对自己有价值的信息, 一方面让信息能够展现在对它感兴趣的用户前面。 推荐系统近几年有了深度学习的助推发展之势迅猛, 从前深度学习的传统推荐模型(协同过滤,矩阵分解,LR, FM, FFM, GBDT)到深度学习的浪潮之巅(DNN, Deep Crossing, DIN, DIEN, Wide&Deep, Deep&Cross, DeepFM, AFM, NFM, PNN, FNN, DRN), 现在正无时无刻不影响着大众的生活。

推荐系统通过分析用户的历史行为给用户的兴趣建模, 从而主动给用户推荐给能够满足他们兴趣和需求的信息, 能够真正的“懂你”。 想上网购物的时候, 推荐系统在帮我们挑选商品, 想看资讯的时候, 推荐系统为我们准备了感兴趣的新闻, 想学习充电的时候, 推荐系统为我们提供最合适的课程, 想消遣放松的时候, 推荐系统为我们奉上欲罢不能的短视频…, 所以当我们淹没在信息的海洋时, 推荐系统正在拨开一层层波浪, 为我们追寻多姿多彩的生活!

这篇文章又回到了传统的推荐方式协同过滤的时代了, 因为最近刚好学习到基于内容的推荐和基于回归模型的推荐, 之前在刚开始学习协同过滤的时候, 其实也了解过这两种推荐方式, 但当时并不是太了解具体是怎么做的, 这次又重温了一下, 知道了里面的一些实施细节, 所以借机会整理一下。

今天首先是看一下基于内容的推荐方式, 这种推荐方式是非常直接的, 它是以物品的内容描述信息为依据做出的推荐, 本质上是基于对物品和用户自身的特征或者属性直接分析和计算。 再白话一点, 就是事先先给出物品的画像和用户的画像, 这样, 我们就能根据用户喜欢什么直接给用户推荐物品, 这也是一种非常简单和快捷的方式,例如,假设已知电影A是一部喜剧,而恰巧我们得知某个用户喜欢看喜剧电影,那么我们基于这样的已知信息,就可以将电影A推荐给该用户。 (PS:之前从福哥的口中了解到,虽然现在深度学习模型很火, 但大多数还停留在理论的研究阶段,真正能够应用到实践中的并不是太多,因为模型本身的部署比较复杂,且对于数据, 设备等要求也非常高, 对于中小规模的公司来讲,早期的一些简单的推荐方法可以作为初版推荐系统的不二之选, 所以有时候还是有必要学习一下那些简单推荐方式的思想的, 而基于内容的推荐的思想就挺简单的哈哈)。

之前学习的时候, 也是停留在理论阶段, 也知道先构建物品画像(给物品打标签)和用户画像(给用户打标签),然后实施推荐。 但是并没有理解到一些细节,比如如何给物品打标签, 如何给用户打标签? 如何解决物品冷启动问题? 等, 所以这次想通过之前用到过的比较简单的movielens数据集, 来走一波基于内容的推荐流程, 这里面会涉及到tf-idf技术, word2vec技术, doc2vec技术等, 所以通过这次的学习, 收获还是蛮大的, 把之前的知识也串联了一下。

关于movidlens数据集和任务, 这是一个基于用户对看过的一些电影的 评分情况给用户实施电影推荐的一个任务, 详细的可以参考之前的文章或者资料,这里就不过多描述了。因为之前也用协同过滤, 矩阵分解等完成过这个任务, 这里主要是基于内容的走一下这个推荐流程:

  1. 建立物品画像
    1. 基于用户给电影打的tag和电影的分类值,得到每一部电影的总标签
    2. 求每一部电影标签的tf-idf值
    3. 根据tf-idf的结果, 为每一部电影选择top-n(tf-idf值较大)的关键词作为整部电影的关键词
    4. 最后得到了 电影id— 关键词 — 关键词权重
  2. 建立倒排索引
    1. 这个是为了能够根据关键词找到对应的电影, 好方便得到用户画像之后(用户喜欢啥样的电影)对用户进行一些推荐
  3. 建立用户画像
    1. 看用户看过哪些电影, 基于前面的物品画像找到电影对应的关键词
    2. 把用户看过的所有关键词放到一起, 统计词频, 每个词出现了几次
    3. 出现次数最多的关键词作为用户的兴趣词, 这个就是用户的画像
  4. 根据用户的兴趣词, 基于倒排表找到电影, 就可以对用户实施推荐了。

上面就是基于内容推荐的整个流程, 这篇文章会走一遍这个流程, 但是再走之前,需要介绍到一些技术, 首先是tf-idf技术, 这是一种在自然语言处理领域中应用比较广泛的一种算法。可用来提取目标文档中,并得到关键词用于计算对于目标文档的权重,并将这些权重组合到一起得到特征向量。这里会再次介绍一下Tf-idf的原理。 然后走一遍基于内容推荐的整体流程, 最后,针对物品的冷启动问题, 再介绍两种比较经典的技术, 一个叫做Word2Vec, 另一个叫做Doc2Vec, 这里也会整理他们的细节部分, 并实现这个模型完成影片的推荐, 重点是看看这俩模型是如何使用的。

这篇文章会很长, 这里给出目录大纲, 各取所需即可 😉

  • 基于内容的推荐算法简介
  • 基于TF-IDF的特征提取技术
  • 基于内容的推荐流程(物品画像 -> 倒排索引 -> 用户画像 -> 产生推荐)
  • 物品的冷启动处理(Word2Vec, Doc2Vec)

Ok, let’s go!

2. 基于内容的推荐算法简介

2.1 简介

基于内容的推荐方法是非常直接的,它以物品的内容描述信息为依据来做出的推荐,本质上是基于对物品和用户自身的特征或属性的直接分析和计算。

例如,假设已知电影A是一部喜剧,而恰巧我们得知某个用户喜欢看喜剧电影,那么我们基于这样的已知信息,就可以将电影A推荐给该用户。

2.2 实现步骤

画像构建:画像就是刻画物品或用户的特征。本质上就是给用户或物品贴标签。

  • 物品画像:主要包括物品的分类信息, 标题, 电影/音乐主演, 歌手等等。 例如给电影《战狼2》贴标签, “动作”、“吴京”、“吴刚”、“张翰”、“大陆电影”、“国产”、“爱国”、"军事"等等一系列标签。 物品的标签来源主要包括两个方面:
    1. PGC(应用生成内容): 物品自带的属性,比如电影的标题, 导演,演员,类型, 或者服务提供方设定的属性, 比如短视频话题,微博话题
    2. UGC(用户生成内容): 用户在享受服务过程中提供的物品属性, 如用户评论内容, 微博话题等。
  • 用户画像:主要包括用户的喜好,行为偏好, 基本的人口学属性, 活跃程度, 风控维度等。例如已知用户的观影历史是:"《战狼1》"、"《战狼2》"、"《建党伟业》"、"《建军大业》"、"《建国大业》"、"《红海行动》"、"《速度与激情1-8》"等,我们是不是就可以分析出该用户的一些兴趣特征如:“爱国”、“战争”、“赛车”、“动作”、“军事”、“吴京”、"韩三平"等标签。

2.3 基于内容推荐的算法流程

  • 根据PGC/UGC内容构建物品画像
  • 根据用户行为记录生成用户画像
  • 根据用户画像从物品中寻找最匹配的TOP-N物品进行推荐

物品冷启动处理:

  • 根据PGC内容构建物品画像
  • 利用物品画像计算物品间两两相似情况
  • 为每个物品产生TOP-N最相似的物品进行相关推荐:如与该商品相似的商品有哪些?与该文章相似文章有哪些?

后面会拿movieLens数据集走一遍这个流程, 但是在走之前, 先介绍一个技术叫做tf-idf。

3. 基于TF-IDF的特征提取技术

上面提到,物品画像的特征标签主要都是指的如电影的导演、演员、图书的作者、出版社等结构化的数据,也就是他们的特征提取,尤其是特征向量的计算是比较简单的,如直接给作品的分类定义0或者1的状态。

但另外一些特征,比如电影的内容简介、电影的影评、图书的摘要等文本数据,这些被称为非结构化数据,首先他们本应该也属于物品的一个特征标签,但是这样的特征标签进行量化时,也就是计算它的特征向量时是很难去定义的。

因此这时就需要借助一些自然语言处理、信息检索等技术,将如用户的文本评论或其他文本内容信息的非结构化数据进行量化处理,从而实现更加完善的物品画像/用户画像。

TF-IDF算法便是其中一种在自然语言处理领域中应用比较广泛的一种算法。可用来提取目标文档中,并得到关键词用于计算对于目标文档的权重,并将这些权重组合到一起得到特征向量。

3.1 算法原理

TF-IDF自然语言处理领域中计算文档中词或短语的权值的方法,是词频(Term Frequency,TF)和逆转文档频率(Inverse Document Frequency,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在文档 d j d_j 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是一个词语普遍重要性的度量,表示某一个词语在整个文档集中出现的频率。某一特定词语的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, 注意这个地方是N在分子上, 相当于频率取了个倒数, 这样我们依然是希望 I D F i IDF_i IDFi越大越好

TF-IDF算法基于一个这样的假设:若一个词语在目标文档中出现的频率高而在其他文档中出现的频率低,那么这个词语就可以用来区分出目标文档。这个假设需要掌握的有两点:

  • 在本文档出现的频率高
  • 在其他文档中出现的频率低

因此,TF-IDF算法的计算可以分为词频(Term Frequency,TF)和逆转文档频率(Inverse Document Frequency,IDF)两部分,由TF和IDF的乘积来设置文档词语的权重 w i j = T F i j ⋅ I D F i = f i j f d j ⋅ l o g N n i w_{ij}=TF_{ij}·IDF_{i}=\frac {f_{ij}}{f_{dj}}·log\frac {N}{n_i} wij=TFijIDFi=fdjfijlogniN

TF-IDF与词语在文档中的出现次数成正比,与该词在整个文档集中的出现次数成反比。

用途:在目标文档中,提取关键词(特征标签)的方法就是将该文档所有词语的TF-IDF计算出来并进行对比,取其中TF-IDF值最大的k个数组成目标文档的特征向量用以表示文档。

注意:文档中存在的停用词(Stop Words),如“是”、“的”之类的,对于文档的中心思想表达没有意义的词,在分词时需要先过滤掉再计算其他词语的TF-IDF值。

这样说起来可能比较抽象,下面看一个具体的例子:

3.2 TF-IDF算法举例

对于计算影评的TF-IDF, 以电影“加勒比海盗:黑珍珠号的诅咒”为例,假设它总共有1000篇影评,其中一篇影评的总词语数为200,其中出现最频繁的词语为“海盗”、“船长”、“自由”,分别是20、15、10次,并且这3个词在所有影评中被提及的次数分别为1000、500、100,就这3个词语作为关键词的顺序计算如下。

  1. 将影评中出现的停用词过滤掉, 计算其他词语的词频。 以出现的最多的三个词为例进行计算:
    • ”海盗“出现的词频20/200 = 0.1
    • "船长“出现的词频15/200 = 0.075
    • "自由"出现的词频10/200 = 0.05
  2. 计算词语的拟文档频率:
    • "海盗“的IDF为: log(1000/1000) = 0
    • "船长"的IDF为: log(1000/500) = 0.3
    • "自由"的IDF为: log(1000/100) = 1
  3. 由1和2计算的结果求出词语的TF-IDF结果, "海盗"为0, "船长"为0.0225, "自由"为0.05

通过对比可得, 该篇影评的关键词排序应为:“自由”, “船长”,“海盗”。 把这些词语的TF-IDF值作为它们的权重按照对应的顺序依次排列(0.05, 0.0225, 0), 就得到这篇影评的特征向量, 我们就用这个向量来代表这篇影评, 向量中每一个维度的分量大小对应这个属性的重要性。

将总的影评集中所有的影评向量与特定的系数相乘求和, 得到这部电影的综合影评向量, 与电影的基本属性结合构建视频的物品画像, 同理构建用户画像, 可采用多种方法计算物品画像和用户画像之间的相似度, 为用户做出推荐。

具体实现的时候, 可以调用具体的tfidf模型实现, 下面就具体看看基于内容的推荐了。

4. 从MovieLens数据集中走一遍基于内容的推荐

这里采用的数据集是MovieLens数据集, 具体的可以从下面给出的GitHub中得到。这里先说一下下面要干的事情, 这里要完成一个基于内容的推荐算法, 根据最前面给出的步骤流程, 需要先给每个电影打上标签,构建出画像。 然后再根据用户观看的电影, 给用户构建画像,最后实施推荐。 主要就是这三大块。 下面是主要的细节。

4.1 物品画像

构造物品画像, 这里要分为三部分, 对于每一部分, 可以通过代码来看一下:

  1. 首先, 需要构造一个电影数据集, 【movieId, title, genres, tags】的DataFrame, 这里需要用到数据集中的all_tags.csv文件和movies.csv文件,前者里面是用户基于看过的电影,给电影打的标签, 后者是电影及其主题信息, 我们给电影打标签的时候,是要结合这两部分。具体代码如下:

    def get_movie_dataset():
        # 加载基于所有电影的标签
        # all-tag.csv
        _tags = pd.read_csv("ml-latest-small/all-tags.csv", usecols=range(1, 3)).dropna()
        tags = _tags.groupby("movidId").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, 可迭代对象)
        df = 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())
        movies_dataset = pd.DataFrame(df, columns=['movieId', 'title', 'genres', 'tags'])
        
        movies_dataset.set_index("movieId", inplace=True)
        return movies_dataset
    
    movies_dataset = get_movie_dataset()
    

    这里的map函数用的比较巧妙,我们可以看一下movies_dataset:
    在这里插入图片描述

  2. 然后, 基于TF-IDF提取每个电影里面的Top-N关键词, 构建电影画像,这里重点看一下tf-idf模型的使用。 思路是这样, 在上面movies_dataset的基础上, 要取出tags那一列,然后建立一个词典, 并且统计出每个单词的词频。 这个可以通过Dictionary函数实现。 然后就是构建TF-IDF模型, 对每个tag计算tf-idf值。计算完了之后, 选出tf-idf值比较大的前N个关键词作为该电影的电影画像。代码如下:

    from gensim.models import TfidfModel
    from pprint import pprint
    from gensim.corpora import Dictionary
    
    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_profile = create_movie_profile(movie_dataset)
    

    这里的movie_profile长下面这样:
    在这里插入图片描述
    这里就建好了物品画像了, 但是我们还需要建立一个倒排索引

  3. 为了根据指定的关键词迅速匹配到对应的电影, 需要对物品画像的标签词, 建立倒排索引。这里建立倒排索引的目的是为用户产生推荐用的, 因为后面建立用户画像的时候, 会根据用户观看过的历史记录得到用户感兴趣的电影的标签,我们要基于这个标签对用户产生新的推荐。所以这里需要先建立一个倒排表, 即能够根据标签,去提取他们对应的电影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)
    

    这里的操作还是挺秒的感觉, 得到的inverted_table是个字典的形式, 张下面这样:

    在这里插入图片描述

这里,物品画像就基本完事了, 对于每部电影, 我们已经得到了对应的tags, 且基于tags, 我们也能得到每个电影。 下面构建用户画像。

4.2 用户画像

用户画像构建步骤:

  • 根据用户的评分历史,结合物品画像,将有观影记录的电影的画像标签作为初始标签反打到用户身上
  • 通过对用户观影标签的次数进行统计,计算用户的每个初始标签的权重值,排序后选取TOP-N作为用户最终的画像标签

这个就是根据用户观看过的电影, 找到他们对应的标签,统计一下标签的频率, 得到最终的标签作为用户的画像, 即该用户习惯看什么类型的电影。这里就用到了rating.csv, 这里面是用户对于电影的评分记录。

import collections
from functools import reduce

def create_user_profile():
    watch_record = pd.read_csv("datasets/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()

这里简单的看下用户画像的结果:

在这里插入图片描述
构建用户画像,我们就拿到了每个用户感兴趣的一些话题或者词语, 这样,我们就可以根据倒排表实施推荐了。

4.3 基于内容产生TopN推荐

通过上面的内容, 我们已经得到了物品画像,即每个电影打上了标签, 是什么样的风格, 也得到了用户画像, 即每个用户喜欢什么样风格的电影, 接下来, 我们就可以基于电影的内容对用户实施推荐了。

基本思路就是, 遍历用户画像, 拿到他的兴趣词,根据这个兴趣词, 去倒排表中拿到符合这些兴趣词的电影及对应的权重,由于不同兴趣词想对应的电影权重不同, 最终我们把拿到的每部电影的权重进行汇总加和得到最终的电影权重, 并基于这个最终权重, 对用户实施推荐。代码如下:

# 为用户产生推荐结果
for uid, interest_words in user_profile.items():
    result_table = {}   # 电影id: [0.2, 0.5]
    for interest_word, interest_weight in interest_words:
        related_movies = inverted_table[interest_word]
        for mid, relate_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

看下最终结果:
在这里插入图片描述

这样, 我们就完成了整个推荐的过程了,关于详细的代码, 可以去我下面的GitHub链接。
是不是思路比较简单? 但是实施起来,也是有很多的小细节, 且与协同过滤又有很大的不同了, 这个方法感觉也可以用到粗排的阶段。算是提供一种思路吧。

下面还想介绍两种技术, 这个可以用在物品冷启动问题里面, 也就是对于一个新物品到来的时候, 这时候用户都没有看过,用户是无法打标签的, 这时候的物品可能只有系统给出的一些标签,比如主题,演员啊啥的。 这时候要把这个新的物品推荐给用户的时候, 我们需要通过两种技术:

  • Word2Vec: 这个可以根据得到电影标签的词向量, 根据这个词向量, 就能够得到tag之间的相似性, 这样就能够根据用户看过的某个电影, 得到这个电影的标签, 然后根据这些标签得到与其近似的标签, 然后得到这些近似标签下的电影对该用户产生推荐
  • Doc2Vec:这个可以根据电影的所有标签, 训练一个模型来得到最终电影的影片向量, 根据这个, 就能够直接计算用户看过的某个电影与其他电影的相似性, 然后根据这个相似性给用户推荐最相似的几篇文章。这个也是基于内容计算物品embedding的一种方式, 之前设计新闻推荐比赛的教程里面,有个自带的embedding,或许就是通过这种方式计算的。

下面我们具体来看一下这两个技术吧。

5. 物品的冷启动处理

5.1 Word2Vec

关于这个技术, 在学习NLP的时候写过的文章里面都整理过了, 这里再来简单的走一遍这个技术, 然后掉包来实现一下。

利用Word2Vec可以计算电影所有标签词之间的关系程度, 可用于计算电影之间的相似度。

5.1.1原理简介

word2vec是google在2013年开源的一个NLP(Natural Language Processing自然语言处理) 工具,它的特点是将所有的词向量化,这样词与词之间就可以定量的去度量他们之间的关系,挖掘词之间的联系。

one-hot vector VS. word vector

  • 用向量来表示词并不是word2vec的首创
  • 最早的词向量是很冗长的,它使用是词向量维度大小为整个词汇表的大小,对于每个具体的词汇表中的词,将对应的位置置为1。
  • 比如下面5个词组成词汇表,词"Queen"的序号为2, 那么它的词向量就是(0,1,0,0,0)同样的道理,词"Woman"的词向量就是(0,0,0,1,0)。
    在这里插入图片描述

one hot vector的问题

  • 如果词汇表非常大,如达到万级别,这样每个词都用万维的向量来表示浪费内存。这样的向量除了一个位置是1,其余位置全部为0,表达效率低(稀疏),需要降低词向量的维度
  • 难以发现词之间的关系,以及难以捕捉句法(结构)和语义(意思)之间的关系
  • Dristributed representation可以解决One hot representation的问题,它的思路是通过训练,将每个词都映射到一个较短的词向量上来。所有的这些词向量就构成了向量空间,进而可以用普通的统计学的方法来研究词与词之间的关系。这个较短的词向量维度一般需要我们在训练时指定。
  • 比如下图我们将词汇表里的词用"Royalty(王位)",“Masculinity(男性气质)”, "Femininity(女性气质)"和"Age"4个维度来表示,King这个词对应的词向量可能是(0.99,0.99,0.05,0.7)。当然在实际情况中,我们并不一定能对词向量的每个维度做一个很好的解释。
    在这里插入图片描述
    有了用Dristributed representation表示的较短的词向量,就可以较容易的分析词之间的关系,比如将词的维度降维到2维,用下图的词向量表示我们的词时,发现: K i n g ⃗ − M a n ⃗ + W o m a n ⃗ = Q u e e n ⃗ \vec{King} - \vec{Man} + \vec{Woman} = \vec{Queen} King Man +Woman =Queen
    在这里插入图片描述
5.1.2 两个实现模型 CBOW和Skip-Gram

拥有差不多上下文的两个单词的意思往往是相近的, 所以这俩模型都是基于一个中心词和上下文词构成关系。

CBOW把一个词从词窗剔除。在CBOW下给定n词围绕着词w,word2vec预测一个句子中其中一个缺漏的词c,即以概率 p ( c ∣ w ) p(c|w) p(cw)来表示。相反地,Skip-gram给定词窗中的文本,预测当前的词 p ( w ∣ c ) p(w|c) p(wc)
在这里插入图片描述

  • 功能:通过上下文预测当前词出现的概率

  • 原理分析

    假设文本如下:“the florid prose of the nineteenth century.”

    想象有个滑动窗口,中间的词是关键词,两边为相等长度的文本来帮助分析。文本的长度为7,就得到了7个one-hot向量,作为神经网络的输入向量,训练目标是:最大化在给定前后文本情况下输出正确关键词的概率,比如给定(“prose”,“of”,“nineteenth”,“century”)的情况下,要最大化输出"the"的概率,用公式表示就是

    P(“the”|(“prose”,“of”,“nineteenth”,“century”))

  • 特性

    • hidden layer只是将权重求和,传递到下一层,是线性的

Skip-gram和CBOW相反,则我们要求的概率就变为 P ( C o n t e x t ( w ) ∣ w ) P(Context(w)|w) P(Context(w)w), 是根据中心词预测上下文词, 具体的细节和公式推导可以参考NLP那里的几篇文章。
在这里插入图片描述
word2vec算法可以计算出每个词语的一个词向量,我们可以用它来表示该词的语义层面的含义。 下面就是通过电影的每个标签, 来计算他们之间的相关关系。看一下Word2Vec的具体使用过程。

思路是这样:得到电影标签的词向量, 根据这个词向量, 就能够得到tag之间的相似性, 这样就能够根据用户看过的某个电影, 得到这个电影的标签, 然后根据这些标签得到与其近似的标签, 然后得到这些近似标签下的电影对该用户产生推荐, 我们依然是直接用gensim.models里面的Word2Vec模型。使用很简单:

from gensim.models import Word2Vec
# 由于前面我们已经得到了每部影片的tags,物品画像里面。 所以这里我们就可以直接建立word2vec模型, 来进行标签的词向量计算
sentences = list(movie_profile["profile"].values)   # 二维列表  每个元素是字符串
# 这里可以直接建立模型
model = Word2Vec(sentences, window=3, min_count=1, iter=20)
# 建立完了之后, 对于某个电影的某个tag, 我们就可以得到与其相似的N个词
words = input("words: ")
ret = model.wv.most_similar(positive=[words], topn=10)   # 找到最相似的n 个词
print(ret)

看下结果, 给定某个关键词, 就可以返回与其相似的N个关键词过来:

在这里插入图片描述
在使用gensim训练word2vec的时候,有几个比较重要的参数:

  • size: 表示词向量的维度。
  • window:决定了目标词会与多远距离的上下文产生关系。
  • sg: 如果是0,则是CBOW模型,是1则是Skip-Gram模型。
  • workers: 表示训练时候的线程数量
  • min_count: 设置最小的词频
  • iter: 训练时遍历整个数据集的次数

注意训练的时候输入的语料库一定要是字符组成的二维数组,如:[[‘北’, ‘京’, ‘你’, ‘好’], [‘上’, ‘海’, ‘你’, ‘好’]]。使用模型的时候有一些默认值,可以通过在Jupyter里面通过Word2Vec??查看。在之前设计推荐系统竞赛内容的时候, 也用到过这个模型,那个地方是基于用户点击的文章历史序列, 来求的每篇文章之间的序列上的一个关联, 得到的一个embedding向量, 更像是item2vec。 和这里的使用情况还不太一样, 感兴趣的可以看一下新闻推荐之特征工程

5.2 Doc2Vec

Doc2Vec是建立在Word2Vec上的,用于直接计算以文档为单位的文档向量,doc2vec的目标是创建文档的向量化表示,而不管其长度如何。 但与单词不同的是,文档并没有单词之间的逻辑结构,因此必须找到另一种方法。

采用了Word2Vec的两个模型, 但是添加了另一个向量ID。

  1. 段落向量的分布式记忆的版本(PV-DM)
    在这里插入图片描述
    这个是CBOW模型的一个小扩展。 它不是仅是使用一些单词来预测下一个单词,还添加了另一个特征向量,即文档Id。因此,当训练单词向量W时,也训练文档向量D,并且在训练结束时,它包含了文档的向量化表示。

  2. 段落向量的分布式词袋版本(PV-DBOW)

    在这里插入图片描述
    这种就类似于skip-gram模型了。

doc2vec模型的使用方式:对于训练,它需要一组文档。 为每个单词生成词向量W,并为每个文档生成文档向量D. 该模型还训练softmax隐藏层的权重。 在推理阶段,可以呈现新文档,并且固定所有权重以计算文档向量。

这里我们将一部电影的所有标签词,作为整个文档,这样可以计算出每部电影的向量,通过计算向量之间的距离,来判断用于计算电影之间的相似程度。这里直接调包来实现这个模型。

根据这个, 就能够直接计算用户看过的某个电影与其他电影的相似性, 然后根据这个相似性给用户推荐最相似的几篇文章。这个也就是基于内容计算影片embedding的一种方式。

from gensim.models.doc2vec import Doc2Vec, TaggedDocument
from gensim.test.utils import get_tmpfile

# 建立文档, words就是影片的tags, tags就是影片的id
documents = [TaggedDocument(words, [movie_id]) for movie_id, words in movie_profile["profile"].iteritems()]

# 训练Doc2Vec模型
model = Doc2Vec(documents, vector_size=100, window=3, min_count=1, wordkers=4, epochs=20)

# 获取某个电影的tages
words = movie_profile["profile"].loc[6]
print(words)
# 拿到该影片的Doc2vec向量
inferred_vector = model.infer_vector(words)
sims = model.docvecs.most_similar([inferred_vector], topn=10)
print(sims)

看下结果:
在这里插入图片描述
给定一篇文档, 就会返回与其相似的N篇文档。

D2V主要参数解释:

model_dm = Doc2Vec(x_train,min_count=1, window = 3, size = size, sample=1e-3, negative=5, workers=4)

  • min_count:忽略所有单词中单词频率小于这个值的单词。
  • window:窗口的尺寸。(句子中当前和预测单词之间的最大距离)
  • size:特征向量的维度
  • sample:高频词汇的随机降采样的配置阈值,默认为1e-3,范围是(0,1e-5)。
  • negative: 如果>0,则会采用negativesampling,用于设置多少个noise words(一般是5-20)。默认值是5。
  • workers:用于控制训练的并行数。

这就是所有的基于内容推荐的内容了。

6. 小总

这里简单的小总一下, 这篇文章主要是围绕着基于内容推荐的思路进行展开的, 这个思路其实很简单,就是刻画出物品的特点和用户的兴趣点来, 根据用户的兴趣点去推荐相应的物品就行。 具体实施的时候有很多细节, 这里拿了一个MovieLens数据集走了一遍这个流程, 并借着这个机会又温习了一下TF-IDF技术, Word2Vec技术和Doc2Vec技术。

参考

整理这篇文章的同时, 也刚建立了一个GitHub项目, 准备后面把各种主流的推荐模型复现一遍,并用通俗易懂的语言进行注释和逻辑整理, 今天的基于内容推荐的数据集和代码已经上传, 感兴趣的可以看一下 😉

筋斗云:https://github.com/zhongqiangwu960812/AI-RecommenderSystem