Skip to content

Latest commit

 

History

History
1325 lines (732 loc) · 67 KB

推荐系统笔记.md

File metadata and controls

1325 lines (732 loc) · 67 KB

[Toc]

基本概念:

它可以把那些最终会在用户(User)和物品(Item)之间产生的连接提前找出来。以人为中心,万物互联,从已有的连接去预测未来的连接。

是否需要推荐系统:

1.是否建立越多连接越好

2.现有的连接数是不是很多

公式:增加的连接数/(增加的活跃用户数*增加的有效物品数)

预测问题模式

评分预测;

行为预测:

直接预测行为本身发生的概率和预测物品的相对排序

CTR 预估

常见顽疾

冷启动问题;

探索与利用问题:Exploit 和 Explore 问题

安全问题。

关键元素

UI 和 UE;数据;领域知识;算法。

权重大致是 4-3-2-1

目标思维和不确定性思维

核心词可以表述为:逻辑、因果、分层

目标思维:

把一个推荐系统也看做一个函数,这个函数的输入有很多:UI、UE、数据、领域知识、算法等等,输出则是我们关注的指标:留存率、新闻的阅读时间、电商的 GMV、视频的 VV 等等。

目标思维背后是“量化一切”的价值取向。最先要量化的就是目标本身,接下来要量化的是所有的优化改进动作。

不确定思维:

绝大多数推荐算法都是概率算法,推荐系统追求的是目标的增长,出现意外的推荐也是有益的,可以探索用户的新兴趣。

内容推荐

用户画像概念

用户向量化后的结果

用户画像是指从用户产生的各种数据中挖掘和抽取用户在不同属性上的标签,如年龄、性别、职业、收入、兴趣等。

关键因素:

维度

量化:和第三个关键元素“效果”息息

用户画像分类:

1)基础用户画像

  • 人口统计学标签:用户的性别,年龄,地区等信息。
  • 行为特征标签:用户在互联网平台的注册,活跃,付费,浏览等方面的行为记录产生的用户标签。
  • 性格标签:豪爽大方,精打细算,冲动消费等类型标签

2)偏好用户画像

  • 长期偏好标签:用户对较长时间内,几个月甚至是几年内,对某类事物的稳定偏好。
  • 短期偏好标签:用户最近较短时间内,七天内甚至是几分钟内,对某类事物的偏好。
  • 泛化偏好标签:众多的用户偏好中,不同的偏好之间有关联性或者相似性,就像啤酒和尿布那样。用户对啤酒有过直接的行为,但对尿布还没有,那么尿布可能是他的泛化偏好。

物品标签

物品画像,则是每个物品的一系列标签。物品画像其中一个作用就是可以作为推荐模型中的物品特征。另外一方面,在推荐系统中,物品画像是用户画像的基础:物品画像+用户行为=用户画像

物品画像可分为两类:

  • 人工的方式给物品打标签;
  • 机器学习的方式给物品打标签。

用户画像的公式

用户偏好程度 = 行为类型权重值 × 次数 × 时间衰减 × TFIDF值

行为类型权重值是人为给用户行为的赋值。比如:看完=1,收藏=2,分享=3,购买=4等。次数则是行为发生的次数。时间衰减则是按一定的衰减系数,随着时间衰减。TFIDF值本来是文本处理领域的算法,用来提取一篇文章中的关键字。这里用来衡量标签的对一个用户的关键程度。下面我们来计算用户A的用户画像和偏好值。

第一步:列一下行为类型权重值,我们只考虑观看行为,权重都为1:

第二步:统计用户A的行为次数。用户A看了视频1两次,所以视频1带的标签“金融战争”和“做空”次数都记为2

第三步:计算时间衰减,假设用户A看视频1是两天前的行为,看视频4是今天的行为。衰减按照天来计算,衰减系数等于0.1556,热度计算公式为:热度=1×exp(-0.1556×天数)。按照这个衰减系数,45天后热度衰减到0.5。

按照这个计算方式,视频1的热度 = 1×exp(-0.1556×2) = 0.73,今天看的视频4,热度还为1。

第四步:计算TFIDF值。

用户画像构建方法

第一类就是原始数据,又叫静态数据。直接使用原始数据作为用户画像的内容,如注册资料,行为轨迹等信息,除了数据清洗等工作,数据本身并没有做任何抽象和归纳。这种方法通常对于用户冷启动等场景非常有用。

第二类就是统计分析或者动态信息特征选取。方法就是通过大量数据进行统计分析,这是最常见的用户画像数据,常见的兴趣标签,就是这一类。

第三类就是机器学习。通过机器学习,可以得出人类无法直观理解的稠密向量。

构建用户画像过程

文本向量化

根据行为数据把物品的结构化结果传递给用户,与用户自己的结构化信息合并

文本内容向量化

1.One-hot编码

计数word在文档中出现的次数,每个单词都有一个V维度的向量来表示,向量中只有1个位置非0(可以是1,也可以是出现次数),其他都是0。编码过于简单,有如下缺点:

  • 没有考虑词的顺序
  • 忽略了词与词之间的相互影响
  • 特征是离散的,稀疏的

2.tfidf

概念

是一种用于资讯检索与资讯探勘的常用加权技术。TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。

tf词频

指的是某一个给定的词语在该文件中出现的次数。这个数字通常会被归一化(一般是词频除以文章总词数), 以防止它偏向长的文件。

一个文档中,tf越大,说明这个词越重要

idf逆向文档频率
  • 首先来看一下df(即文档频率),df = (出现过这个词的文档的数量) / 文档总数量

  • 再来看idf,image-20200630180444935

tf-idf

​ tf-idf = tf * idf,可以把文章进行向量化

停用词:在分类中没有用的词,这些词一般词频 TF 高,但是 IDF 很低,起不到分类的作用

3.TextRank

1)文本中,设定一个窗口宽度,比如 K 个词,统计窗口内的词和词的共现关系,将其看成无向图

2)所有词初始化的重要性都是 1;

3)每个节点把自己的权重平均分配给“和自己有连接“的其他节点;

4)每个节点将所有其他节点分给自己的权重求和,作为自己的新权重;

5)如此反复迭代第 3、4 两步,直到所有的节点权重收敛为止。

4.内容分类

长文本的内容分类可以提取很多信息,而如今 UGC 当道的时代,短文本的内容分类则更困难一些。短文本分类方面经典的算法是 SVM ,在工具上现在最常用的是 Facebook 开源的 FastText

5.实体识别

命名实体识别(也常常被简称为 NER,Named-Entity Recognition)在 NLP 技术中常常被认为是序列标注问题,和分词、词性标注属于同一类问题。

所谓序列标注问题,就是给你一个字符序列,从左往右遍历每个字符,一边遍历一边对每一个字符分类,分类的体系因序列标注问题不同而不同:

  • 分词问题:对每一个字符分类为“词开始”“词中间”“词结束”三类之一;
  • 词性标注:对每一个分好的词,分类为定义的词性集合的之一;
  • 实体识别:对每一个分好的词,识别为定义的命名实体集合之一。

对于序列标注问题,通常的算法就是隐马尔科夫模型(HMM)或者条件随机场(CRF),我们在推荐系统中主要是挖掘出想要的结构化结果,对其中原理有兴趣再去深入了解。

实体识别还有比较实用化的非模型做法:词典法。提前准备好各种实体的词典,使用 trie-tree 数据结构存储,拿着分好的词去词典里找,找到了某个词就认为是提前定义好的实体了。

以实体识别为代表的序列标注问题上,工业级别的工具上 spaCy 比 NLTK 在效率上优秀一些。

6.聚类

LDA无监督模型, 设定主题个数,K 可以通过一些实验来对比挑选,方法:每次计算 K 个主题两两之间的平均相似度,选择一个较低的 K 值;如果你赶时间,在推荐系统领域,只要计算资源够用,主题数可以尽量多一些。

需要注意的是,得到文本在各个主题上的分布,可以保留概率最大的前几个主题作为文本的主题。LDA 工程上较难的是并行化,如果文本数量没到海量程度,提高单机配置也是可以的,开源的 LDA 训练工具有 Gensim,PLDA 等可供选择

7.词嵌入

机器很难处理原始文本数据,以向量的形式表示文本几乎一直是所有NLP任务中最重要的步骤. 词嵌入就是为每一个词学习得到一个稠密的向量。向量中各个维度上的值大小代表了词包含各个语义的多少。

拿着这些向量可以做以下的事情:

  • 计算词和词之间的相似度,扩充结构化标签;
  • 累加得到一个文本的稠密向量;
  • 用于聚类,会得到比使用词向量聚类更好的语义聚类效果。

Word2Vec:

Word2Vec 是用浅层神经网络学习得到每个词的向量表达,Word2Vec 最大的贡献在于一些工程技巧上的优化,使得百万词的规模在单机上可以几分钟轻松跑出来,得到这些词向量后可以聚类或者进一步合成句子向量再使用

Word2Vec生成的向量值可以看成是N维语义空间(N个语义词)中的坐标值(每个坐标轴对应一个语义)。当2个词在同一个N维语义空间中的距离接近时,说明2个词的含义接近。

包括两种模型,CBOW与Skip-gram。前者利用单词上下文预测单词,后者利用单词预测上下文。

标签选择

最常用的是两个方法:卡方检验(CHI)和信息增益(IG)。

基本思想是:

  • 把物品的结构化内容看成文档;
  • 把用户对物品的行为看成是类别;
  • 每个用户看见过的物品就是一个文本集合;
  • 在这个文本集合上使用特征选择算法选出每个用户关心的东西
卡方检验:

CHI 就是卡方检验,本身是一种特征选择方法。本质上在检验“词和某个类别 C 相互独立”这个假设是否成立

前面的 TF-IDF 和 TextRank 都是无监督关键词提取算法,而卡方检验(CHI)则是有监督的,需要提供分类标注信息。

卡方检验 属于类别C 不属于类别C 总计
包含词W A B A+B
不含词W C D C+D
总计 A+C B+D N=A+B+C+D

然后按照如下公式计算每一个词和每一个类别的卡方值:

关于这个卡方值计算,这里说明几点:

  1. 每个词和每个类别都要计算,只要对其中一个类别有帮助的词都应该留下;
  2. 由于是比较卡方值的大小,所以公式中的 N 可以不参与计算,因为它对每个词都一样,就是总的文本数;
  3. 卡方值越大,意味着偏离“词和类别相互独立”的假设越远,靠“词和类别互相不独立”这个备择假设越近。
信息增益

IG 即 Information Gain,信息增益,也是一种有监督的关键词选择方法,也需要有标注信息。

信息熵,一批文本被标注了类别,很难猜一条文本的类别,如果其中一个类别的文本 C 数远远多于其他类别,那么这条文本就大概率属于类别 C:

  • 各个类别的文本数量差不多时,信息熵就比较大。
  • 其中少数类别的文本数量明显较多时,信息熵就较小。

信息增益计算分三步:

  • 统计全局文本的信息熵;
  • 统计每个词的条件熵,就是知道了一个词后再统计文本的信息熵,只不过这里要分别计算包含词和不包含词两部分的信息熵,再按照各自文本比例加权平均;
  • 两者相减就是每个词的信息增益。

信息增益应用最广就是数据挖掘中的决策树分类算法,经典的决策树分类算法挑选分裂节点时就是计算各个属性的信息增益,始终挑选信息增益最大的节点作为分裂节点。

卡方检验和信息增益不同之处在于:前者是针对每一个行为单独筛选一套标签出来,后者是全局统一筛选。

这些方法都是在离线阶段批量完成的,把用户的画像生成配置成离线任务,每天更新一遍,次日就可以使用新的用户画像,当然你也可以频率更高。

内容推荐系统

要把基于内容的推荐做好,需要做好“抓、洗、挖、算”四门功课

内容推荐的框架图:

内容分析和用户分析:

基于内容的推荐,最重要的不是推荐算法,而是内容挖掘和分析。

内容分析的产出有两个:

  • 结构化内容库;

  • 内容分析模型。

    分类器模型;主题模型;实体识别模型;嵌入模型。

    这些模型主要用在:当新的物品刚刚进入时,需要实时地被推荐出去,这时候对内容的实时分析,提取结构化内容,再于用户画像匹配。

内容推荐算法

相似性推荐和推荐匹配计算:如BM25F 算法

相似度推荐:

把物品按属性分维度,定义相似度公式:

distance=f1(导演)+f2(主演)+…+fn(片长)

相对属性的为1,不同为0,计算这个相似度的值,根据结果排序,推荐前几个物品

这两种办法虽然可以做到快速实现、快速上线,但实际上都不属于机器学习方法

机器学习算法:

一种最典型的场景:提高某种行为的转化率,如点击、收藏、转发等。那么标准的做法是:收集这类行为的日志数据,转换成训练样本,训练预估模型。

每一条样本由两部分构成:一部分是特征,包含用户端的画像内容,物品端的结构化内容,可选的还有日志记录时一些上下文场景信息,如时间、地理位置、设备等等,另一部分就是用户行为,作为标注信息,包含“有反馈”和“无反馈”两类。

用这样的样本训练一个二分类器,常用模型是逻辑回归(Logistic Regression)和梯度提升树(GBDT)或者两者的结合。在推荐匹配时,预估用户行为发生的概率,按照概率排序。这样更合理更科学,而且这一条路可以一直迭代优化下去。

总结:

基于内容的推荐一般是推荐系统的起步阶段,而且会持续存在,它的重要性不可取代。因为:

  • 内容数据始终存在并且蕴含丰富的信息量,不好好利用就可惜了;
  • 产品冷启动阶段,没有用户行为,别无选择;
  • 新的物品要被推荐出去,首选内容推荐。

数据相关

1.数据收集

​ 内容:爬虫,人工录入,第三方倒流

​ 用户数据:日志收集(nginx,flume),评论,打分

​ 涉及技术:scrapy,flume,kafka,hdfs,hive

2.数据处理

​ 数据分词:jieba,TFIDF

​ 数据清洗:spark日志清洗

​ 数据清洗和数据分词的主要目的是拿到内容标签, 用户使用标签,从而方便合成用户画像

​ 涉及技术:spark,jieba, nlp,tfidf

3.内容分类

样本收集方法-半监督学习
  • 文本内容,可以进行lda主题分析和kmeans,生成参考样本与参考关键词

  • 进行人工筛查与重新标注,并且无监督方法的结果来丰富知识图谱与关键词表,最终生成一定数量的样本(每个分类1000以上)

  • 分类类目数量需要控制,类目数量越大,界限越不明显;这里可以采用多级分类,每个大分类再训练不同的分类模型的方法

分类算法
  • svm
  • 朴素贝叶斯
  • 随机森林

近邻推荐

协同过滤

分为两类:

基于记忆的协同过滤(Memory-Based);

基于模型的协同过滤(Model-Based)。

基于用户的协同过滤

这其实也是一个给用户聚类的过程,把用户按照兴趣口味聚类成不同的群体,给用户产生的推荐就来自这个群体的平均值。

主要步骤:

  • 收集用户偏好
  • 找到和目标用户兴趣相似的用户集合
  • 把相似用户看过的内容,推荐给目标用户

实现原理:

1.准备用户向量,从这个矩阵中,理论上可以给每一个用户得到一个向量。

向量有这么三个特点:

  • 向量的维度就是物品的个数;
  • 向量是稀疏的,也就是说并不是每个维度上都有数值;
  • 向量维度上的取值可以是简单的 0 或者 1,1 表示喜欢过,0 表示没有,当然因为是稀疏向量,所以取值为 0 的就忽略了。

2.用每一个用户的向量,两两计算用户之间的相似度,设定一个相似度阈值或者设定一个最大数量,为每个用户保留与其最相似的用户。

3.为每一个用户产生推荐结果。和他“臭味相投”的用户们喜欢过的物品汇总起来,去掉用户自己已经消费过的物品,剩下的排序输出就是推荐结果

公式:等号左边就是计算一个物品 和一个用户 u 的匹配分数,等号右边是这个分数的计算过程,分母是把和用户相似的 n 个用户的相似度加起来,分子是把这 n 个用户各自对物品的态度,按照相似度加权求和。

实践

1.构造矩阵

我们在做协同过滤计算时,所用的矩阵是稀疏的,简单说就是:很多矩阵元素不用存,因为是 0。这里介绍典型的稀疏矩阵存储格式。

CSR:这个存储稍微复杂点,是一个整体编码方式。它有三个组成:数值、列号和行偏移共同编码。

COO:这个存储方式很简单,每个元素用一个三元组表示(行号,列号,数值),只存储有值的元素,缺失值不存储。

把你的原始行为日志转换成上面的格式,就可以使用常用计算框架的标准输入了

2.相似度计算

1)单个相似度计算问题,如果碰上向量很长,无论什么相似度计算方法,都要遍历向量,如果用循环实现就更可观了,所以通常降低相似度计算复杂度的办法有两种:

  • 对向量采样计算. 可以减低维度,这个算法由 Twitter 提出,叫做 DIMSUM 算法,已经在 Spark 中实现了。
  • 向量化计算,比循环快很多。也就是我们在任何地方,都要想办法把循环转换成向量来直接计算,一般像常用的向量库都天然支持的,比如 Python 的 NumPy

2)如果用户量很大,两两之间计算代价就很大。

  • 将相似度计算拆成 Map Reduce 任务,将原始矩阵 Map 成 为用户对,为两个用户对同一个物品的评分之积,Reduce 阶段对这些乘积再求和,Map Reduce 任务结束后再对这些值归一化;
  • 不用基于用户的协同过滤

另外,这种计算对象两两之间的相似度的任务,如果数据量不大,一般来说不超过百万个,然后矩阵又是稀疏的,那么有很多单机版本的工具其实更快,比如 KGraph、 GraphCHI 等。

3.推荐计算

为每一个用户计算每一个物品的推荐分数,计算次数是矩阵的所有元素个数,这个代价,你当然不能接受,优化:

  • 只有相似用户喜欢过的物品需要计算,这个数量相比全部物品少了很多;

  • 把计算过程拆成 Map Reduce 任务。

    拆 Map Reduce 任务的做法是:

    1.遍历每个用户喜欢的物品列表;

    2.获取该用户的相似用户列表;

    3.把每一个喜欢的物品 Map 成两个记录发射出去,一个是为 < 相似用户 ID,物品 ID,1> 三元组,可以拼成一个字符串,为 < 相似度 >,另一个是键为 < 相似用户 ID,物品 ID,0> 三元组,值为 < 喜欢程度 * 相似度 >,其中的 1 和 0 为了区分两者,在最后一步中会用到;

    4.Reduce 阶段,求和后输出;

    5.< 相似用户 ID,物品 ID, 0> 的值除以 < 相似用户 ID,物品 ID, 1> 的值

4 存在问题和改进

存在问题

  • 新用户没有任何信息,无法计算相似度
  • 对于一个物品邻居没打分,那么该物品永远不会被推荐
  • 稀疏问题 ,数百万客户时计算量大 ,人有兴趣迁移

改进:

  • 相似度使用皮尔逊距离,正相关最大,说明相似度越大

  • 考虑用户打分物品的数目,指定一个共同打分的阈值,求一个共同打分的比例

  • 打分归一化

  • 惩罚对热门物品的喜欢程度,这是因为,热门的东西很难反应出用户的真实兴趣,更可能是被煽动,或者无聊随便点击的情形,这是群体行为常见特点;

  • 增加喜欢程度的时间衰减,一般使用一个指数函数,指数就是一个负数,值和喜欢行为发生时间间隔正相关即可

  • 新用户考虑基于内容和基于排行榜的推荐

应用场景

基于用户的协同过滤有两个产出:

  • 相似用户列表; 推荐用户
  • 基于用户的推荐结果; 推荐内容

基于物品(Item-Based)协同过滤

优势:

  • 一个系统中,用户数量远大于物品数量,找相似的时候,计算量小
  • 人的爱好善变,即使计算了相似度,也是可能发生变化的;物品不善变
  • 物品还可以进行打内容标签,可利用维度高

原理

基于用户的协同过滤的问题:

  • 用户数量往往比较大,计算起来非常吃力,成为瓶颈;

  • 用户变化很快,不是静态的,所以兴趣迁移问题很难反应出来;

  • 数据稀疏,用户和用户之间有共同的消费行为实际上是比较少的,而且一般都是一些热门物品,对发现用户兴趣帮助也不大。

基于物品的协同过滤首先计算相似物品,然后再根据用户消费过、或者正在消费的物品为其推荐相似的:

  • 物品的数量,或者严格的说,可以推荐的物品数量往往少于用户数量;所以一般计算物品之间的相似度就不会成为瓶颈。

  • 物品之间的相似度比较静态,它们变化的速度没有用户的口味变化快;所以完全解耦了用户兴趣迁移这个问题。

  • 物品对应的消费者数量较大,对于计算物品之间的相似度稀疏度是好过计算用户之间相似度的。

基本步骤:

  • 构建用户物品的关系矩阵,矩阵元素可以是用户的消费行为,也可以是消费后的评价,还可以是对消费行为的某种量化如时间、次数、费用等;
  • 假如矩阵的行表示物品,列表示用户的话,那么就两两计算行向量之间的相似度,得到物品相似度矩阵,行和列都是物品;
  • 产生推荐结果,根据推荐场景不同,有两种产生结果的形式。一种是为某一个物品推荐相关物品,另一种是在个人首页产生类似“猜你喜欢”的推荐结果。

计算物品相似度

从用户物品关系矩阵中得到的物品向量特点:

  • 它是一个稀疏向量;
  • 向量的维度是用户,一个用户代表向量的一维,这个向量的总共维度是总用户数量;
  • 向量各个维度的取值是用户对这个物品的消费结果,可以是行为本身的布尔值,也可以是消费行为量化如时间长短、次数多少、费用大小等,还可以是消费的评价分数;
  • 没有消费过的就不再表示出来,所以说是一个稀疏向量。

如何两两计算物品的相似度,一般选择余弦相似度,公式:

分母是计算两个物品向量的长度,求元素值的平方和再开方。分子是两个向量的点积,相同位置的元素值相乘再求和。

物品之间的相似度计算是这个算法最可以改进的地方。通常的改进方向有下面两种。

  1. 物品中心化。把矩阵中的分数,减去的是物品分数的均值;先计算每一个物品收到评分的均值,然后再把物品向量中的分数减去对应物品的均值。 这样做的目的是什么呢?抑制铁杆粉丝群体的非理性因素。
  2. 用户中心化。把矩阵中的分数,减去对应用户分数的均值;先计算每一个用户的评分均值,然后把他打过的所有分数都减去这个均值。这样仅仅保留了偏好,去掉了主观成分。毕竟每个人的标准不同

这种相似度计算方法,不只是适用于评分类矩阵,也适用于行为矩阵。所谓行为矩阵,即矩阵元素为 0 或者 1 的布尔值,也就是在前面的专栏中讲过的隐式反馈。隐式反馈取值特殊,有一些基于物品的改进推荐算法无法应用,比如著名的 Slope One 算法。

计算推荐结果

有两种应用场景

第一种属于 TopK 推荐,形式上也常常属于类似“猜你喜欢”这样的。

汇总和“用户已经消费过的物品相似”的物品,按照汇总后分数从高到低推出。核心思想就和基于用户的推荐算法一样,用相似度加权汇总。

和基于物品的推荐一样,我们在计算时不必对所有物品都计算一边,只需要按照用户评分过的物品,逐一取出和它们相似的物品出来就可以了。

这个过程都是离线完成后,去掉那些用户已经消费过的,保留分数最高的 k 个结果存储

第二种属于相关推荐

这类推荐不需要提前合并计算,当用户访问一个物品的详情页面时,或者完成一个物品消费的结果面,直接获取这个物品的相似物品推荐

用户冷启动方案有:

不走协同过滤;引导用户填写兴趣信息;利用第三方开放数据平台(成本高,可选);根据用户基础属性推荐(年龄、男女、居住地点等);排行榜

物品冷启动问题

  • 文本分析(nlp)
  • 主题分析
  • 打标签(类目标签、关键词标签)

协同过滤的例子:

基于用户的推荐 :实时新闻 基于物品的推荐 :图书 电子商务 电影

Slope One 算法

经典的基于物品推荐,相似度矩阵计算无法实时更新,整个过程都是离线计算的,而且还有另一个问题,相似度计算时没有考虑相似度的置信问题。例如,两个物品,他们都被同一个用户喜欢了,且只被这一个用户喜欢了,那么余弦相似度计算的结果是 1,这个 1 在最后汇总计算推荐分数时,对结果的影响却最大。

Slope One 算法专门针对评分矩阵,不适用于行为矩阵。Slope One 算法计算的不是物品之间的相似度,而是计算的物品之间的距离,相似度的反面。

用户 物A 物B 物C
用户1 5 3 2
用户2 3 4 没评分
用户3 没评分 2 5

两两计算物品之间的差距(差距/共同用户数)

物A 物B 物C
物A 0 -0.5(2) -3(1)
物B 0.5(2)= (5+3-3-4)/2 0 -1(2)
物C 3(1) 1(2) 0

括号里表示两个物品的共同用户数量,代表两个物品差距的置信程度。比如物品 A 和物品 B 之间的差距是 0.5,共同用户数是 2,反之,物品 B 和物品 A 的差距是 -0.5,共同用户数还是 2。

用户 3 给物品 B 的评分是 2,那么预测用户 3 给物品 A 的评分呢就是 2.5,因为从物品 B 到物品 A 的差距是 0.5。同理根据物品C对物A评分就是8分。

汇总分数:把单个预测的分数按照共同用户数加权求平均。

(8∗1+2.5∗2)/(1+2) = 4.33

相似度的本质

推荐算法中的相似度门派,实际上有这么一个潜在假设如果两个物体很相似,也就是距离很近,那么这两个物体就很容易产生一样的动作。

机器学习中,也有很多算法在某种角度看做是相似度度量。例如,逻辑回归或者线性回归中,一边是特征向量,另一边是模型参数向量,两者的点积运算,就可以看做是相似度计算,只不过其中的模型参数向量值并不是人肉指定的,而是从数据中由优化算法自动总结出来的。

在近邻推荐中,最常用的相似度是余弦相似度。然而可以选用的相似度并不只是余弦相似度,还有欧氏距离、皮尔逊相关度、自适应的余弦相似度、局部敏感哈希等

相似度的计算方法

数据分类:

向量的数值就有两种: 实数值; 布尔值,也就是 0 或者 1。

1 欧氏距离

欧氏距离,如名字所料,是一个欧式空间下度量距离的方法。通常相似度计算度量结果希望是[-1,1]或者[0,1]之间, 所以一般要做转化,距离加一后取倒数。这个公式能够把范围为 0 到正无穷的欧式距离转换为 0 到 1 的相似度。

欧式距离度量的是空间中两个点的绝对差异,适用于分析用户能力模型之间的差异,比如消费能力、贡献内容的能力等。

2 余弦相似度

余弦相似度在度量文本相似度、用户相似度、物品相似度的时候都较为常用;但是在这里需要提醒你一点,余弦相似度的特点:它与向量的长度无关。因为余弦相似度计算需要对向量长度做归一化。

这意味着:两个向量,只要方向一致,无论程度强弱,都可以视为“相似”。

针对这个问题,对余弦相似度有个改进,改进的算法叫做调整的余弦相似度(Adjusted Cosine Similarity)。调整的方法很简单,就是先计算向量每个维度上的均值,然后每个向量在各个维度上都减去均值后,再计算余弦相似度。

3 皮尔逊相关度

皮尔逊相关度,实际上也是一种余弦相似度,不过先对向量做了中心化,向量 p 和 q 各自减去向量的均值后,再计算余弦相似度。

皮尔逊相关度计算结果范围在 -1 到 1。-1 表示负相关,1 比表示正相关。皮尔逊相关度其实度量的是两个随机变量是不是在同增同减

由于皮尔逊相关度度量的时两个变量的变化趋势是否一致,所以不适合用作计算布尔值向量之间相关度,因为两个布尔向量也就是对应两个 0-1 分布的随机变量,这样的随机变量变化只有有限的两个取值

4 杰卡德(Jaccard)相似度

杰卡德相似度,是两个集合的交集元素个数在并集中所占的比例。由于集合非常适用于布尔向量表示,所以杰卡德相似度简直就是为布尔值向量私人定做的。

对应的计算方式是:

  • 分子是两个布尔向量做点积计算,得到的就是交集元素个数;
  • 分母是两个布尔向量做或运算,再求元素和。

余弦相似度适用于评分数据,杰卡德相似度适合用于隐式反馈数据。例如,使用用户的收藏行为,计算用户之间的相似度。

总结:

按照向量维度取值是否是布尔值来看,杰卡德相似度就只适合布尔值向量,余弦相似度弹性略大,适合两种向量。欧式距离度量的是绝对差异,余弦相似度度量的是方向差异,但是调整的余弦相似度则可以避免这个弱点。

矩阵分解

为什么要矩阵分解

近邻模型无法解决的问题:

  1. 物品之间存在相关性,信息量并不随着向量维度增加而线性增加;
  2. 矩阵元素稀疏,计算结果不稳定,增减一个向量维度,导致近邻结果差异很大的情况存在。

矩阵分解,直观上说来简单,就是把原来的大矩阵,近似分解成两个小矩阵的乘积,在实际推荐计算时不再使用大矩阵,而是使用分解得到的两个小矩阵。

1 基础的 SVD 算法

SVD 全称奇异值分解,属于线性代数的知识 ;推荐算法中实际上使用的是一个伪奇异值分解。

把用户和物品都映射到一个 k 维空间中,每一个维度也没有名字,所以常常叫做隐因子。每一个物品都得到一个向量 q,每一个用户也得到一个向量 p。q和p 中的元素,有正有负。

看上去很简单,难在哪呢?难在如何得到每一个用户,每一个物品的 k 维向量。这是一个机器学习问题。按照机器学习框架,一般就是考虑两个核心要素:

  • 损失函数;
  • 优化算法。

SVD 的损失函数,如果你不是算法工程师的话不必深究这个过程。

损失函数由两部分构成,加号前一部分控制着模型的偏差,加号后一部分控制着模型的方差。就是一个负责衡量模型准不准,另一个负责衡量模型稳不稳定。

整个 SVD 的学习过程就是:

  1. 准备好用户物品的评分矩阵,每一条评分数据看做一条训练样本;
  2. 给分解后的 U 矩阵和 V 矩阵随机初始化元素值;
  3. 用 U 和 V 计算预测后的分数;
  4. 计算预测的分数和实际的分数误差;
  5. 按照梯度下降的方向更新 U 和 V 中的元素值;
  6. 重复步骤 3 到 5,直到达到停止条件。

得到分解后的矩阵之后,实质上就是得到了每个用户和每个物品的隐因子向量,拿着物品和用户两个向量,计算点积就是推荐分数了。

2 增加偏置信息

为了防止铁粉和用户的标准不一造成的干扰,一个用户给一个物品的评分会由四部分相加:r=μ+bi+bu+pq

等号右边从左至右分别代表:全局平均分、物品的评分偏置、用户评分的偏置、用户和物品之间的兴趣偏好。

举例:你是一个对电影非常严格的人,你一般打分比平均分都要低 0.5,所以前三项从左到右分别就是 3,1,-0.5。如果简单的就靠这三项,也可以给计算出一个你会给《肖申克的救赎》打的分数,就是 3.5。

3 增加历史行为

SVD 中结合用户的隐式反馈行为和属性,这套模型叫做 SVD++。

先说隐式反馈怎么加入,方法是:除了假设评分矩阵中的物品有一个隐因子向量外,用户有过行为的物品集合也都有一个隐因子向量,维度是一样的。把用户操作过的物品隐因子向量加起来,用来表达用户的兴趣偏好。

类似的,用户属性,全都转换成 0-1 型的特征后,对每一个特征也假设都存在一个同样维度的隐因子向量,一个用户的所有属性对应的隐因子向量相加,也代表了他的一些偏好。

综合两者,SVD++ 的目标函数中,只需要把推荐分数预测部分稍作修改,原来的用户向量那部分增加了隐式反馈物品向量和用户属性向量

这样一来,在用户没有评分时,也可以用他的隐式反馈和属性做出一定的预测。

4 考虑时间因素

在 SVD 中考虑时间因素,有几种做法:

  1. 对评分按照时间加权,让久远的评分更趋近平均值;
  2. 对评分时间划分区间,不同的时间区间内分别学习出隐因子向量,使用时按照区间使用对应的隐因子向量来计算;
  3. 对特殊的期间,如节日、周末等训练对应的隐因子向量。

交替最小二乘原理 (ALS)

这是Facebook 在他们的推荐系统中选择的主要矩阵分解方法

交替最小二乘的核心是交替,什么意思呢?你的任务是找到两个矩阵 P 和 Q,让它们相乘后约等于原矩阵 R

通过迭代的方式解决了这个难题:

  1. 初始化随机矩阵 Q 里面的元素值;
  2. 把 Q 矩阵当做已知的,直接用线性代数的方法求得矩阵 P;
  3. 得到了矩阵 P 后,把 P 当做已知的,故技重施,回去求解矩阵 Q;
  4. 上面两个过程交替进行,一直到误差可以接受为止。

交替最小二乘有这么几个好处:

  1. 在交替的其中一步,也就是假设已知其中一个矩阵求解另一个时,要优化的参数是很容易并行化的;
  2. 在不那么稀疏的数据集合上,交替最小二乘通常比随机梯度下降要更快地得到结果,这一点就是隐式反馈的内容。

隐式反馈

矩阵分解算法,是为解决评分预测问题而生的。实际上推荐系统关注的是预测行为,行为也就是一再强调的隐式反馈。

那如何从解决评分预测问题转向解决预测行为上来呢?这就是另一类问题了,行话叫做 One-Class。收集到的数据只有明确的一类:用户干了某件事,而用户明确“不干”某件事的数据却没有明确表达。所以这就是 One-Class 的由来,One-Class 数据也是隐式反馈的通常特点。

对隐式反馈的矩阵分解,需要将交替最小二乘做一些改进,改进后的算法叫做加权交替最小二乘:Weighted-ALS。

加权交替最小二乘这样对待隐式反馈:

  • 如果用户对物品无隐式反馈则认为评分是 0;
  • 如果用户对物品有至少一次隐式反馈则认为评分是 1,次数作为该评分的置信度。

置信度 Cui 这样计算:cui=1+αC, 其中阿尔法是一个超参数,需要调教,默认值取 40 可以得到差不多的效果,C 就是次数了。

那些没有反馈的缺失值,就是在我们的设定下,取值为 0 的评分就非常多,有两个原因导致在实际使用时要注意这个问题:

  1. 本身隐式反馈就只有正类别是确定的,负类别是我们假设的,你要知道,One-Class 并不是随便起的名字;
  2. 这会导致正负类别样本非常不平衡,严重倾斜到 0 评分这边。

方法:

按照物品的热门程度采样。

什么样的样本最适合做负样本?按照物品热门程度采样的思想就是:一个越热门的物品,用户越可能知道它的存在。那这种情况下,用户还没对它有反馈就表明:这很可能就是真正的负样本。

推荐计算

在得到了分解后的矩阵后,相当于每个用户得到了隐因子向量,这是一个稠密向量,同时每个物品也得到了一个稠密向量。如果计算推荐结果,实际上复杂度还是很高,尤其对于用户数量和物品数量都巨大的应用。

Facebook 提出了两个办法得到真正的推荐结果:

第一种,利用一些专门设计的数据结构存储所有物品的隐因子向量,从而实现通过一个用户向量可以返回最相似的 K 个物品。

Facebook 给出了自己的开源实现 Faiss,类似的开源实现还有 Annoy,KGraph,NMSLIB。其中 Facebook 开源的 Faiss 和 NMSLIB(Non-Metric Space Library)都用到了 ball tree 来存储物品向量。

如果需要动态增加新的物品向量到索引中,推荐使用 Faiss,如果不是,推荐使用 NMSLIB 或者 KGraph。

第二种,就是拿着物品的隐因子向量先做聚类,海量的物品会减少为少量的聚类。然后再逐一计算用户和每个聚类中心的推荐分数,给用户推荐物品就变成了给用户推荐物品聚类。

得到给用户推荐的聚类后,再从每个聚类中挑选少许几个物品作为最终推荐结果。这样做的好处除了大大减小推荐计算量之外,还可以控制推荐结果的多样性,因为可以控制在每个类别中选择的物品数量。

矩阵分解的不足

得到矩阵分解结果后,常常在实际使用时,是用这个预测结果来排序

这种针对单个用户对单个物品的偏好程度进行预测,得到结果后再排序的问题,在排序学习中的行话叫做 point-wise,其中 point 意思就是:只单独考虑每个物品

与之相对的,还有直接预测物品两两之间相对顺序的问题,就叫做 pair-wise,pair,顾名思义就是成对成双。

矩阵分解都属于 point-wise 模型。这类模型的尴尬是:只能收集到正样本,没有负样本,于是认为缺失值就是负样本,再以预测误差为评判标准去使劲逼近这些样本

贝叶斯个性化排序

均方根误差用于评价模型预测精准程度的。要关注相对排序,用什么指标比较好呢?答案是 AUC,AUC 全称是 Area Under Curve,意思是曲线下的面积,这里的曲线就是 ROC 曲线。

AUC

AUC 这个值在数学上等价于:模型把关心的那一类样本排在其他样本前面的概率。最大是 1,完美结果,而 0.5 就是随机排列,0 就是完美地全部排错。

AUC 怎么计算呢?一般步骤如下。

  1. 用模型给样本计算推荐分数,比如样本都是用户和物品这样一对一对的,同时还包含了有无反馈的标识;
  2. 得到打过分的样本,每条样本保留两个信息,第一个是分数,第二个是 0 或者 1,1 表示用户消费过,是正样本,0 表示没有,是负样本;
  3. 按照分数对样本重新排序,降序排列;给每一个样本赋一个排序值,第一位 r1 = n,第二位 r2 = n-1,以此类推;
  4. 其中要注意,如果几个样本分数一样,需要将其排序值调整为他们的平均值;
  5. 最终按照下面这个公式计算就可以得到 AUC 值。

BPR 模型:

它提出了一个优化准则和学习框架。

那到底 BPR 做了什么事情呢?主要有三点:

  • 一个样本构造方法;
  • 一个模型目标函数;
  • 一个模型学习框架。

构造样本

矩阵分解训练时候处理的样本是:用户、物品、反馈,这样的三元组形式。

BPR 则不同,提出要关心的是物品之间对于用户的相对顺序,于是构造的样本是:用户、物品 1、物品 2、两个物品相对顺序,这样的四元组形式,其中,“两个物品的相对顺序”,取值是:

  • 如果物品 1 是消费过的,而物品 2 不是,那么相对顺序取值为 1,是正样本;
  • 如果物品 1 和物品 2 刚好相反,则是负样本;
  • 样本中不包含其他情况:物品 1 和物品 2 都是消费过的,或者都是没消费过的。

学习的数据是反应用户偏好的相对顺序,而在使用时,面对的是所有用户还没消费过的物品,这些物品仍然可以在这样的模型下得到相对顺序

目标函数

先假装矩阵分解结果已经有了,于是就计算出用户对于每个物品的推荐分数,只不过这个推荐分数可能并不满足均方根误差最小,而是满足物品相对排序最佳。

得到了用户和物品的推荐分数后,就可以计算四元组的样本中,物品 1 和物品 2 的分数差,这个分数可能是正数,也可能是负数,也可能是 0

用个符号来表示这个差:Xu12,表示的是对用户 u,物品 1 和物品 2 的矩阵分解预测分数差。然后再用 sigmoid 函数把这个分数差压缩到 0 到 1 之间。

也其实就是用这种方式预测了物品 1 排在物品 2 前面的似然概率,所以最大化交叉熵就是目标函数了。

目标函数通常还要防止过拟合,加上正则项,正则项其实认为模型参数还有个先验概率,这是贝叶斯学派的观点

BPR 认为模型的先验概率符合正态分布,对应到正则化方法就是 L2 正则

把这个目标函数化简和变形后,和把 AUC 当成目标函数是非常相似的,也正因为如此,BPR 模型的作者敢于宣称该模型是为 AUC 而生的。

训练方法

梯度下降可以承担这件事,梯度下降又有批量梯度和随机梯度下降两个选择,前者收敛慢,后者训练快却不稳定。因此 BPR 的作者使用了一个介于两者之间的训练方法,结合重复抽样的梯度下降。具体来说是这样做的:

  1. 从全量样本中有放回地随机抽取一部分样本;
  2. 用这部分样本,采用随机梯度下降优化目标函数,更新模型参数;
  3. 重复步骤 1,直到满足停止条件。

总结

传统的矩阵分解,无论是隐式反馈还是显式反馈,都是希望更加精准地预测用户对单个物品的偏好,而实际上,如果能够预测用户对物品之间的相对偏好,则更加符合实际需求的直觉。

BPR 就是这样一整套针对排序的推荐算法,它事实上提出了一个优化准则和一个学习框架,至于其中优化的对象是不是矩阵分解并不是它的重点。

召回

其实就是各种简单的、复杂的推荐算法,比如说基于内容的推荐,会产生一些推荐结果,一般同时还附带给每个结果产生一个推荐分数,那按分数排序行不行?不行!原因:

  • 有个算法可能只给出结果,不给分数,比如用决策树产生一些推荐结果;
  • 每种算法给出结果时如果有分数,分数的范围不一定一样,所以不能互相比较,大家各自家庭背景不一样;
  • 即使强行把所有分数都归一化,仍然不能互相比较,因为产生的机制不同,有的可能普遍偏高,有的可能普遍偏低。

召回方式:

基于用户历史行为行为的深度学习模型召回

基于兴趣标签做召回。比如推荐系统判断用户对股票、体育标签感兴趣,就会基于股票和体育标签做召回

基于自然语言处理做召回。比如看一篇文章,基于自然语言召回与当前文章相关的文章

基于热门、最新、编辑精选做召回

模型融合(Rank)

逻辑回归LR

CTR 预估就是在推荐一个物品之前,预估一下用户点击它的概率有多大,再根据这个预估的点击率对物品排序输出。

逻辑回归是广义线性模型,相比于传统线性模型,在线性模型基础上增加了 sigmoid 函数。

计算 CTR 预估时,需要两个东西:

  1. 特征:就是用量化、向量的方式把一个用户和一个物品的成对组合表示出来。量化方式包括两种:实数和布尔
  2. 权重:每个特征都有一个权重

特征工程 + 线性模型

权重的学习主要看两个方面:损失函数的最小化,就是模型的偏差是否足够小;另一个就是模型的正则化,就是看模型的方差是否足够小

要学习逻辑回归的权重,经典的方法如梯度下降一类,尤其是随机梯度下降,可以实现在实时数据流情形下,更新逻辑回归的权重,每一个样本更新一次。

简单的逻辑回归模型参数如何训练:

  1. 初始化参数;

  2. 用当前的参数预测样本的类别概率;

  3. 用预测的概率计算交叉熵;

  4. 用交叉熵计算参数的梯度;

  5. 用学习步长和梯度更新参数;

  6. 迭代上述过程直到满足设置的条件。

Google 在 2013 年 KDD 上发表了新的学习算法:FTRL,一种结合了 L1 正则和 L2 正则的在线优化算法,现在各家公司都采用了这个算法。

梯度提升决策树 GBDT

有几个问题:

第一个,把损失函数换成适合分类的损失函数,例如对数损失函数。

第二个,通常还需要考虑防止过拟合,也就是损失函数汇总需要增加正则项,正则化的方法一般是:限定总的树个数、树的深度、以及叶子节点的权重大小。

第三个,构建每一棵树时如果遇到实数值的特征,还需要将其分裂成若干区间,分裂指标有很多,可以参考 xgboost 中的计算分裂点收益,也可以参考决策树所用的信息增益。

二者结合

每一条样本,样本内容一般是把用户、物品、场景三类特征拼接在一起,先经过 N 棵 GBDT 树各自预测一下,给出自己的 0 或者 1 的预测结果,接着,这个 N 个预测结果再作为 N 个 one-hot 编码特征拼接成一个向量送入逻辑回归中,产生最终的融合预估结果。

也可以考虑在输入特征中加入各个召回模型产生的分数

FM 模型

原理

因为逻辑回归在做特征组合时样本稀疏,从而无法学到很多特征组合的权重,所以因子分解机的提出者就想,能不能对上面那个公式中的 wij 做解耦,让每一个特征学习一个隐因子向量出来。

模型训练

预测阶段

变形其他模型

FFM

某些特征实际上是来自数据的同一个字段,这个特征类型就是字段,即 Field。这种改进叫做 Field-aware Factorization Machines,简称 FFM。

深度和宽度结合

CTR 预估的常见做法就是广义线性模型,如 Logistic Regression,然后再采用特征海洋战术,就是把几乎所有的精力都放在搞特征上:挖掘新特征、挖掘特征组合、寻找新的特征离散方法等等。

这种简单模型加特征工程的做法好处多多:

  • 线性模型简单,其训练和预测计算复杂度都相对低;
  • 工程师的精力可以集中在发掘新的有效特征上,俗称特征工程;
  • 工程师们可以并行化工作,各自挖掘特征;
  • 线性模型的可解释性相对非线性模型要好。

深度学习在推荐领域的应用,其最大好处就是“洞悉本质般的精深”,优秀的泛化性能,可以给推荐很多惊喜。深度模型的泛化强于线性模型,但可解释性不好。

Wide & Deep 模型

数据生成

  • 每一条曝光日志就生成一条样本,标签就是 1/0,安装了 App 就是 1,否则就是 0。
  • 将字符串形式的特征映射为 ID,需要用一个阈值过滤掉那些出现样本较少的特征。
  • 对连续值做归一化

模型训练

其要点,在深度模型侧:

  1. 每个类别特征 embedding 成一个 32 维向量;
  2. 将所有类别特征的 embedding 变量连成一个 1200 维度左右的大向量;
  3. 1200 维度向量就送进三层以 ReLU 作为激活函数的隐藏层;
  4. 最终从 Logistic Regreesion 输出。

宽模型侧就是传统的做法:特征交叉组合。

模型应用

模型验证后,就发布到模型服务器。模型服务,每次网络请求输入的是来自召回模块的 App 候选列表以及用户特征,再对输入的每个 App 进行评分。评分就是用我们的“深宽模型”计算,再按照计算的 CTR 从高到低排序。

为了让每次请求响应时间在 10ms 量级,每次并不是串行地对每个候选 App 计算,而是多线程并行,将候选 App 分成若干并行批量计算。

总结

  1. 深宽模型是一个结合了传统线性模型和深度模型的工程创新。

  2. 这个模型适合高维稀疏特征的推荐场景,稀疏特征的可解释性加上深度模型的泛化性能,双剑合璧。

  3. 这个模型已经开源在 TensorFlow 中,大大减小了落地成本。

  4. 为了提高模型的训练效率,每一次并不从头开始训练,而是用上一次模型参数来初始化当前模型的参数。

  5. 将类别型特征先做嵌入学习,再将嵌入稠密向量送入深度模型中。

  6. 为了提高服务的响应效率,对每次请求要计算的多个候选 App 采用并行评分计算的方式,大大降低响应时间。

MAB多臂赌博机问题

Bandit 算法

Bandit 算法的思想是:看看选择会带来多少遗憾,遗憾越少越好

Bandit 算法的套路就是:小心翼翼地试,越确定某个选择好,就多选择它,越确定某个选择差,就越来越少选择它

Bandit 算法中有几个关键元素:臂,回报,环境。

臂:每次推荐要选择候选池,可能是具体物品,也可能是推荐策略,也可能是物品类别;

回报:用户是否对推荐结果喜欢,喜欢了就是正面的回报,没有买账就是负面回报或者零回报;

环境:推荐系统面临的这个用户就是不可捉摸的环境。

在冷启动和处理 EE 问题时,Bandit 算法简单好用,值得一试

1.汤普森采样算法

每个臂背后的概率分布,是一个贝塔分布

汤普森采样的过程。

  1. 取出每一个候选对应的参数 a 和 b;
  2. 为每个候选用 a 和 b 作为参数,用贝塔分布产生一个随机数;
  3. 按照随机数排序,输出最大值对应的候选;
  4. 观察用户反馈,如果用户点击则将对应候选的 a 加 1,否则 b 加 1;

用 Python 实现汤普森采样就一行:

choice = numpy.argmax(pymc.rbeta(1 + self.wins, 1 + self.trials - self.wins))

2.UCB 算法

UCB 算法全称是 Upper Confidence Bound,即置信区间上界。 它也为每个臂评分,每次选择评分最高的候选臂输出,每次输出后观察用户反馈,回来更新候选臂的参数。

这个评分公式也和汤普森采样是一样的思想:

  1. 以每个候选的平均收益为基准线进行选择;
  2. 对于被选择次数不足的给予照顾;
  3. 选择倾向的是那些确定收益较好的候选。

3.Epsilon 贪婪算法

  1. 先选一个 (0,1) 之间较小的数,叫做 Epsilon,也是这个算法名字来历。
  2. 每次以概率 Epsilon 做一件事:所有候选臂中随机选一个,以 1-Epsilon 的概率去选择平均收益最大的那个臂。

4. 效果对比

  1. 完全随机:就是不顾用户反馈的做法。
  2. 朴素选择:就是认准一个效果好的,一直推。
  3. Epsilon 贪婪算法:每次以小概率尝试新的,大概率选择效果好的。
  4. UCB:每次都会给予机会较少的候选一些倾向。
  5. 汤普森采样:用贝塔分布管理每一个候选的效果。

UCB 算法和汤普森采样都显著优秀很多。

LinUCB

它和传统的 UCB 算法相比,最大的改进就是加入了特征信息,每次估算每个候选的置信区间,不再仅仅是根据实验,而是根据特征信息来估算

LinUCB 算法做了一个假设:一个物品被选择后推送给一个用户,其收益和特征之间呈线性关系。在具体原理上,LinUCB 有一个简单版本以及一个高级版本。简单版本其实就是让每一个候选臂之间完全互相无关,参数不共享。高级版本就是候选臂之间共享一部分参数

岭回归(ridge regression)主要用于当样本数小于特征数时,对回归参数进行修正。对于加了特征的 Bandit 问题,正好符合这个特点:试验次数(样本)少于特征数。

LinUCB 的重点:

  1. LinUCB 不再是上下文无关地,像盲人摸象一样从候选臂中去选择了,而是要考虑上下文因素,比如是用户特征、物品特征和场景特征一起考虑。
  2. 每一个候选臂针对这些特征各自维护一个参数向量,各自更新,互不干扰。
  3. 每次选择时用各自的参数去计算期望收益和置信区间,然后按照置信区间上边界最大的输出结果。
  4. 观察用户的反馈,简单说就是“是否点击”,将观察的结果返回,结合对应的特征,按照刚才给出的公式,去重新计算这个候选臂的参数。

构建特征

LinUCB 算法有一个很重要的步骤,就是给用户和物品构建特征,也就是刻画上下文。

LinUCB几个优点

  1. 由于加入了特征,所以收敛比 UCB 更快,也就是比 UCB 更快见效;
  2. 各个候选臂之间参数是独立的,可以互相不影响地更新参数;
  3. 由于参与计算的是特征,所以可以处理动态的推荐候选池,编辑可以增删文章;

Bandit 算法都有个缺点:同时处理的候选臂数量不能太多,不超过几百个最佳。因为每一次要计算每一个候选臂的期望收益和置信区间,一旦候选太多,计算代价将不可接受。

COFIBA 算法

1 思想

很多的推荐场景中都有两个规律。

  1. 相似的用户对同一个物品的反馈可能是一样的。就是基于用户的协同过滤基本思想
  2. 在使用推荐系统过程中,用户的决策是动态进行的,尤其是新用户。只能“走一步看一步”,是一个动态的推荐过程

2. 细节

COFIBA 算法要点摘要如下

  1. 在时刻 t,有一个用户来访问推荐系统,推荐系统需要从已有的候选池子中挑一个最佳的物品推荐给他,然后观察他的反馈,用观察到的反馈来更新挑选策略。
  2. 这里的每个物品都有一个特征向量,所以这里的 Bandit 算法是 context 相关的,只不过这里虽然是给每个用户维护一套参数,但实际上是由用户所在的聚类类簇一起决定结果的。
  3. 这里依然是用岭回归去拟合用户的权重向量,用于预测用户对每个物品的可能反馈(payoff),这一点和我们上一次介绍的 LinUCB 算法是一样的。

对比上一次介绍的 LinUCB 算法,COFIBA 的不同有两个:

  1. 基于用户聚类挑选最佳的物品,即相似用户集体动态决策;
  2. 基于用户的反馈情况调整用户和物品的聚类结果。

在针对某个用户 i,在每一次推荐时做以下事情。

  1. 首先计算用户 i 的 Bandit 参数 W,做法和 LinUCB 算法相同,但是这个参数并不直接参与到选择决策中,注意这和 LinUCB 不同,只是用来更新用户聚类。
  2. 遍历候选物品,每一个物品已经表示成一个向量 x 了。
  3. 每一个物品都对应一个物品聚类类簇,每一个物品类簇对应一个全量用户聚类结果,所以遍历到每一个物品时,就可以判断出当前用户在当前物品面前,自己属于哪个用户聚类类簇,然后把对应类簇中每个用户的 M 矩阵 (对应 LinUCB 里面的 A 矩阵),b 向量(表示收益向量,对应 LinUCB 里面的 b 向量)加起来,从而针对这个类簇求解一个岭回归参数(类似 LinUCB 里面单独针对每个用户所做),同时计算其收益预测值和置信区间上边界。
  4. 每个待推荐的物品都得到一个预测值及置信区间上界,挑出那个上边界最大的物品作为推荐结果。
  5. 观察用户的真实反馈,然后更新用户自己的 M 矩阵和 b 向量,只更新每个用户,对应类簇里其他的不更新。

以上是 COFIBA 算法的一次决策过程。在收到用户真实反馈之后,还有两个计算过程:

  • 更新 user 聚类;
  • 更新 item 聚类。

简单来说就是这样:

用协同过滤来少选可以参与决策的用户代表,用 LinUCB 算法来实际执行选择;

根据用户的反馈,调整基于用户和基于物品的聚类结果,即对物品和用户的群体代表做换届选举;

基于物品的聚类如果变化,又进一步改变了用户的聚类结果;

不断根据用户实时动态的反馈来调整用户决策参数,从而重新划分聚类结果矩阵。

深度学习

深度学习也就是深度神经网络

对本质属性的挖掘,有两个好处:

  1. 可以更加高效且真实地反映出事物本身的样子(Embedding)。
  2. 可以更加高效真实地反映出用户和物品之间的连接(Predicting预测)。

各种 2vec

首先还是在文本领域,从 Word2vec 到 Sentence2vec,再到 Doc2vec

Word2Vec 最终是每个词都得到一个稠密向量,十分类似矩阵分解得到的隐因子向量,得到这个向量有两个训练方法。

  1. 用窗口内一个词去预测窗口正中央那个词,这样窗口内N个词产生N-1样本,这 N-1 条样本的输入特征是词的嵌入向量,预测标签是窗口那个词。
  2. 把上述的 N-1 条样本颠倒顺序,用窗口中央的词预测周围的词,只是把输入和输出换个位置,一样可以训练得到嵌入向量。

Sentence2vec,把一个句子表示成一个嵌入向量,通常是把其包含的词嵌入向量加起来就完事

Doc2vec 在窗口滑动过程中构建 N-1 条样本时,还增加一条样本,就是段落 ID 预测中央那个词,相当于窗口滑动一次得到 N 条样本

AutoEncoder,它是一种输入和输出一模一样的神经网络,这个神经网络就一个目的,更加清楚地认识自己,在这个优化目标指导下,学到的网络连接权重都是不同的嵌入向量。

从输入数据逐层降维,相当于是一个对原始数据的编码过程,到最低维度那一层后开始逐层增加神经元,相当于是一个解码过程,解码输出要和原始数据越接近越好,相当于在大幅度压缩原始特征空间的前提下,压缩损失越小越好。

YouTube 视频推荐

youtube 把推荐的预测任务看成是一个多分类,把候选物品当成多个类别,预测用户下一个会观看哪个视频。

循环神经网络RNN

认为现在这个时刻的信息不只和现在的输入有关,还和上一个时刻的状态有关。

播单生成:

通常有这么几种做法。

  1. 电台音乐 DJ 手工编排播单,然后一直播放下去,传统广播电台都是这样的。
  2. 用非时序数据离线计算出推荐集合,然后按照分数顺序逐一输出。
  3. 利用循环神经网络,把音乐播单的生成看成是歌曲时间序列的生成,每一首歌的得到不但受用户当前的特征影响,还受上一首歌影响。

Spotify 采用了第三种办法

1. 数据

个性化的播单生成,不再是推荐一个一个独立的音乐,而是推荐一个序列给用户。所用的数据就是已有播单,或者用户的会话信息。其中用户会话信息的意思就是,当一个用户在 App 上所做的一系列操作。

2. 建模

输入是当前神经网络的隐藏状态,然后每一首歌都得到一个线性加权值,再由 Softmax 函数为每一首歌计算得到一个概率。

神经网络的参数学习大致和简单逻辑回归的学习过程一样,但是神经网络有隐藏层,就要依靠链式规则。

神经网路的训练方法叫做误差方向传播

实际上就是链式求导法则,因为要更新参数,就需要计算参数在当前取值时的梯度,要计算梯度就要求导,要求导就要从交叉熵函数开始,先对输出层参数求导计算梯度,更新输出层参数,接着链式下去,对输入层参数求导计算梯度,更新输入层参数。

交叉熵是模型的目标函数,训练模型的目的就是要最小化它,也就是“误差反向传播”的“误差”。

其他原理

排行榜

用处:

  1. 排行榜可以作为解决新用户冷启动问题的推荐策略。
  2. 排行榜可以作为老用户的兴趣发现方式。
  3. 排行榜本身就是一个降级的推荐系统。

排行榜算法

  1. 考虑时间因素

  2. 考虑三种投票

  3. 考虑好评的平均程度

加权采样

每次召回时并不使用全部用户标签,而是按照权重采样一部分标签来使用。

加权采样有两种情况,一种是遍历整个样本,另一种是不知道总量样本是多大,流采样

1. 有限数据集

2. 无限数据集

蓄水池采样

要从大数据集中采样 k 个,其具体做法是这样的:

为每一个样本生成一个分数,分数还是用这个公式 Si=Rwi1;

如果结果不足 k 个,直接保存到结果中;

如果结果中已经有 k 个了,如果 Si 比已有的结果里最小那个分数大,就替换它。

去重

Simhash

核心思想也是为每个内容生成一个整数表示的指纹,然后用这个指纹去做重复或者相似的检测

  1. 首先,对原始内容分词,并且计算每个词的权重;
  2. 对每个词哈希成一个整数,并且把这个整数对应的二进制序列中的 0 变成 -1,1 还是 1,得到一个 1 和 -1 组成的向量;
  3. 把每个词哈希后的向量乘以词的权重,得到一个新的加权向量;
  4. 把每个词的加权向量相加,得到一个最终向量,这个向量中每个元素有正有负;
  5. 把最终这个向量中元素为正的替换成 1,为负的替换成 0,这个向量变成一个二进制位序列,也就是最终变成了一个整数

得到每个内容的 Simhash 指纹后,可以两两计算汉明距离,比较二进制位不同个数

Bloomfilter布隆过滤器

除了内容重复检测,还有一个需求是防止已经推荐的内容被重复推荐。

映射的方法是:设计 n 个互相独立的哈希函数,准备一个长度为 m 的二进制向量,最开始全是 0;每个哈希函数把集合内的元素映射为一个不超过 m 的正整数 k,m 就是二进制向量的长度;把这个二进制向量中的第 k 个位置设置为 1;也就是一个元素会在二进制向量中对应 n 个位置为 1。

nlp

  • nlp处理采用python实现,分为在线、离线,主要包括以下几个步骤:

    在线

    加载词典

    清洗爬虫数据(去除html/img等标签);

    分词,去停词(采用jieba分词)

    使用TFIDF模型,去重复(采用三种方式:1、标题完全一致;2、simHash;3、三句最长句子);

    结果写入Mysql;

    离线

    一天一次加载词典

    清洗爬虫数据(去除html/img等标签);

    分词,去停词(采用jieba分词)

    计算TFIDF(采用gensim计算TFIDF)

    结果写入Mysql;

  • 分词的好坏依赖词典,java分词库可以使用Ansj;

冷启动

用户冷启动
  • 不走协同过滤
  • 引导用户填写兴趣信息
  • 利用第三方开放数据平台(成本高,可选)
  • 根据用户基础属性推荐(年龄、男女、居住地点等)
  • 排行榜
物品冷启动问题
  • 文本分析(nlp)
  • 主题分析
  • 打标签(类目标签、关键词标签)

推荐系统冷启动问题可以用 Bandit 算法来解决一部分。

大致思路如下:

  1. 用分类或者 Topic 来表示每个用户兴趣,我们可以通过几次试验,来刻画出新用户心目中对每个 Topic 的感兴趣概率。
  2. 这里,如果用户对某个 Topic 感兴趣,就表示我们得到了收益,如果推给了它不感兴趣的 Topic,推荐系统就表示很遗憾 (regret) 了。
  3. 当一个新用户来了,针对这个用户,我们用汤普森采样为每一个 Topic 采样一个随机数,排序后,输出采样值 Top N 的推荐 Item。注意,这里一次选择了 Top N 个候选臂。
  4. 等着获取用户的反馈,没有反馈则更新对应 Topic 的 b 值,点击了则更新对应 Topic 的 a 值。

除了 Bandit 算法之外,还有一些其他的探索兴趣的办法。兴趣探索,势必要冒险,势必要面对用户的未知,而这显然就是可能会伤害当前用户价值的。

如果想在自己的推荐系统中引入探索机制,需要注意以下几点:

  1. 用于探索兴趣的物品,要保证其本身质量,纵使用户不感兴趣,也不至于引起其反感,损失平台品牌价值;
  2. 探索兴趣的地方需要产品精心设计,让用户有耐心陪你玩儿;
  3. 深度思考,这样才不会做出脑残的产品,产品不会早早夭折,才有可能让探索机制有用武之地。

组建团队及学习路径

团队组建

算法工程师,软件开发工程师,其他非技术角色

个人成长

  • 有较强的工程能力,能快速交付高效率少 Bug 的算法实现,虽然项目中不一定要写非常大量的代码;
  • 有较强的理论基础,能看懂最新的论文,虽然不一定要原创出漂亮的数学模型;
  • 有很好的可视化思维,能将不直观的数据规律直观地呈现出来,向非工程师解释清楚问题所在,原理所在。

除此之外,还有一些非典型工程师的加分项:

学习能力,沟通能力,表达能力