FM英文全称是“Factorization Machine”,简称FM模型,中文名“因子分解机”,是2010年由Rendle提出的,但是真正在各大厂大规模在CTR预估和推荐领域广泛使用,其实也就是最近几年的事。

FM

从LR到SVM再到FM

LR.jpg
LR模型是CTR预估领域早期最成功的模型,大多工业推荐排序系统采取LR这种“线性模型+人工特征组合引入非线性”的模式。因为LR模型具有简单方便易解释容易上规模等诸多好处,所以目前仍然有不少实际系统仍然采取这种模式。但是,LR模型最大的缺陷就是人工特征工程,耗时费力费人力资源,那么能否将特征组合的能力体现在模型层面呢?
改进LR.jpg
其实想达到这一点并不难,如上图在计算公式里加入二阶特征组合即可,任意两个特征进行组合,可以将这个组合出的特征看作一个新特征,融入线性模型中。而组合特征的权重可以用来表示,和一阶特征权重一样,这个组合特征权重在训练阶段学习获得。其实这种二阶特征组合的使用方式,和多项式核SVM是等价的。虽然这个模型看上去貌似解决了二阶特征组合问题了,但是它有个潜在的问题:它对组合特征建模,泛化能力比较弱,尤其是在大规模稀疏特征存在的场景下,这个毛病尤其突出,比如CTR预估和推荐排序,这些场景的最大特点就是特征的大规模稀疏。所以上述模型并未在工业界广泛采用。那么,有什么办法能够解决这个问题吗?
FM.jpg
于是,FM模型此刻可以闪亮登场了。如上图所示,FM模型也直接引入任意两个特征的二阶特征组合,和SVM模型最大的不同,在于特征组合权重的计算方法。FM对于每个特征,学习一个大小为FM - 图4的一维向量,于是,两个特征FM - 图5FM - 图6的特征组合的权重值,通过特征对应的向量FM - 图7FM - 图8的内积FM - 图9来表示。这本质上是在对特征进行embedding化表征,和目前非常常见的各种实体embedding本质思想是一脉相承的,但是很明显在FM这么做的年代(2010年),还没有现在能看到的各种眼花缭乱的embedding的形式与概念。所以FM作为特征embedding,可以看作当前深度学习里各种embedding方法的老前辈。当然,FM这种模式有它的前辈模型吗?有,等会会谈。其实,和目前的各种深度DNN排序模型比,它仅仅是少了2层或者3层MLP隐层,用来直接对多阶特征非线性组合建模而已,其它方面基本相同。
FM1.jpg
那么为什么说FM的这种特征embedding模式,在大规模稀疏特征应用环境下比较好用?为什么说它的泛化能力强呢?参考上图说明。即使在训练数据里两个特征并未同时在训练实例里见到过,意味着FM - 图11一起出现的次数为FM - 图12,如果换做SVM的模式,是无法学会这个特征组合的权重的。但是因为FM是学习单个特征的embedding,并不依赖某个特定的特征组合是否出现过,所以只要特征FM - 图13和其它任意特征组合出现过,那么就可以学习自己对应的embedding向量。于是,尽管FM - 图14这个特征组合没有看到过,但是在预测的时候,如果看到这个新的特征组合,因为FM - 图15FM - 图16都能学会自己对应的embedding,所以可以通过内积算出这个新特征组合的权重。这是为何说FM模型泛化能力强的根本原因。

其实本质上,这也是目前很多花样的embedding的最核心特点,就是从0/1这种二值硬核匹配,切换为向量软匹配,使得原先匹配不上的,现在能在一定程度上算密切程度了,具备很好的泛化性能。

从MF到FM

FM我们大致应该知道是怎么个意思了,这里又突然冒出个MF,长得跟FM貌似还有点像,那么MF又是什么呢?它跟FM又有什么关系?
MF.jpg
MF(Matrix Factorization,矩阵分解)模型是个在推荐系统领域里资格很深的老前辈协同过滤模型了。核心思想是通过两个低维小矩阵(一个代表用户embedding矩阵,一个代表物品embedding矩阵)的乘积计算,来模拟真实用户点击或评分产生的大的协同信息稀疏矩阵,本质上是编码了用户和物品协同信息的降维模型。
MF1.jpg
当训练完成,每个用户和物品得到对应的低维embedding表达后,如果要预测某个FM - 图19FM - 图20的评分的时候,只要它们做个内积计算FM - 图21,这个得分就是预测得分。看到这里,让你想起了什么吗?MF和FM不仅在名字简称上看着有点像,其实他们本质思想上也有很多相同点。那么,MF和FM究竟是怎样的关系呢?
MF2.jpg
本质上,MF模型是FM模型的特例,MF可以被认为是只有User ID 和Item ID这两个特征Fields的FM模型,MF将这两类特征通过矩阵分解,来达到将这两类特征embedding化表达的目的。而FM则可以看作是MF模型的进一步拓展,除了User ID和Item ID这两类特征外,很多其它类型的特征,都可以进一步融入FM模型里,它将所有这些特征转化为embedding低维向量表达,并计算任意两个特征embedding的内积,就是特征组合的权重,如果FM只使用User ID 和Item ID,你套到FM公式里,看看它的预测过程和MF的预测过程一样吗?

从谁更早使用特征embedding表达这个角度来看的话,很明显,和FM比起来,MF才是真正的前辈,无非是特征类型比较少而已。而FM继承了MF的特征embedding化表达这个优点,同时引入了更多Side information作为特征,将更多特征及Side information embedding化融入FM模型中。所以很明显FM模型更灵活,能适应更多场合的应用范围。

  • 在你有使用MF做协同过滤的想法的时候,暂时压抑一下这种冲动,可以优先考虑引入FM来做的,而非传统的MF,因为可以在实现等价功能的基础上,很方便地融入其它任意你想加入的特征,把手头的事情做得更丰富多彩。
  • 从实际大规模数据场景下的应用来讲,在排序阶段,绝大多数只使用ID信息的模型是不实用的,没有引入Side Information,也就是除了User ID/Item ID外的很多其它可用特征的模型,是不具备实战价值的。原因很简单,大多数真实应用场景中,User/Item有很多信息可用,而协同数据只是其中的一种,引入更多特征明显对于更精准地进行个性化推荐是非常有帮助的。而如果模型不支持更多特征的便捷引入,明显受限严重,很难真正实用,这也是为何矩阵分解类的方法很少看到在Ranking阶段使用,通常是作为一路召回形式存在的原因。

    优化FM的计算效率

    FM计算效率1.jpg
    再说回来,FM如今被广泛采用并成功替代LR模型的一个关键所在是:它可以通过数学公式改写,把表面貌似是 FM - 图24的复杂度降低到FM - 图25,其中FM - 图26是特征数量,FM - 图27是特征的embedding size,这样就将FM模型改成了和LR类似和特征数量n成线性规模的时间复杂度了,这点非常好。

那么,如何改写原始的FM数学公式,让其复杂度降下来呢?因为原始论文在推导的时候没有给出详细说明,我相信不少人看完估计有点懵,所以这里简单解释下推导过程,数学公式帕金森病患者可以直接跳过下面内容往后看,这并不影响你理解本文的主旨。
FM计算效率2.jpg
上图展示了整个推导过程,我相信如果数学基础不太扎实的同学看着会有点头疼,转换包括四个步骤,下面分步骤解释下。
FM计算效率3.jpg
第一个改写步骤及为何这么改写参考上图,比较直观,不解释了;
FM计算效率4.jpg
第二步转换更简单,更不用解释了。
FM计算效率5.jpg
第三步转换不是太直观,可能需要简单推导一下,很多人可能会卡在这一步,所以这里解释解释。
FM计算效率6.jpg
其实吧,如果把k维特征向量内积求和公式抽到最外边后,公式就转成了上图这个公式了(不考虑最外边k维求和过程的情况下)。它有两层循环,内循环其实就是指定某个特征的第f位(这个f是由最外层那个k指定的)后,和其它任意特征对应向量的第f位值相乘求和;而外循环则是遍历每个的第f位做循环求和。这样就完成了指定某个特征位f后的特征组合计算过程。最外层的k维循环则依此轮循第f位,于是就算完了步骤三的特征组合。
FM计算效率7.jpg
对上一页公式图片展示过程用公式方式,再一次改写(参考上图),其实就是两次提取公共因子而已,这下应该明白了吧?要是还不明白,那您的诊断结果是数学公式帕金森晚期,跟我一个毛病,咱俩病友同病相怜,我也没辙了。
FM计算效率8.jpg
第四步公式变换,意思参考上图,这步也很直白,不解释。

于是,通过上述四步的公式改写,可以看出在实现FM模型时,时间复杂度就降低到了FM - 图35了,而虽说看上去n还有点大,但是其实真实的推荐数据的特征值是极为稀疏的,就是说大量FM - 图36其实取值是0,意味着真正需要计算的特征数n是远远小于总特征数目n的,无疑这会进一步极大加快FM的运算效率。

这里需要强调下改写之后的FM公式的第一个平方项,怎么理解这个平方项的含义呢?这里其实蕴含了后面要讲的使用FM模型统一多路召回的基本思想,所以这里特殊提示一下。
FM计算效率9.jpg
参考上图,你体会下这个计算过程。它其实等价于什么?
FM计算效率10.jpg
这个平方项,它等价于将FM的所有特征项的embedding向量累加,之后求内积。我再问下之前问过的问题:“我们怎样利用FM模型做统一的召回?”这个平方项的含义对你有启发吗?你可以仔细想想它们之间的关联。

如何用FM做召回模型

统一召回和多路召回优缺点

目前工业界推荐系统在召回阶段,大多数采用了多路召回策略,比如典型的召回路有:基于用户兴趣标签的召回;基于协同过滤的召回;基于热点的召回;基于地域的召回;基于Topic的召回;基于命名实体的召回等等,除此外还有很多其它类型的召回路。

现在我们来探讨下第一个问题:在召回阶段,能否用一个统一的模型把多路召回招安?就是说改造成利用单个模型,单路召回的模式?具体到这篇文章,就是说能否利用FM模型来把多路召回统一起来?在回答上述问题之前,我估计你会提出疑问:目前大家用多路召回用的好好的,为啥要多此一举,用一个模型把多路召回统一起来呢?这个问题非常好,我们确实应该先看这么做的必要性。

我们先来说明下统一召回和多路召回各自的优缺点,我觉得使用统一召回模式,相对多路召回有如下优点:

首先,采用多路召回,每一路召回因为采取的策略或者模型不同,所以各自的召回模型得分不可比较,比如利用协同过滤召回找到的候选Item得分,与基于兴趣标签这一路召回找到的候选Item得分,完全是不可比较的。这也是为何要用第二阶段Ranking来将分数统一的原因。而如果采取统一的召回模型,比如FM模型,那么不论候选项Item来自于哪里,它们在召回阶段的得分是完全可比的。

其次,貌似在目前“召回+Ranking”两阶段推荐模型下,多路召回分数不可比这个问题不是特别大,因为我们可以依靠Ranking阶段来让它们可比即可。但是其实多路召回分数不可比会直接引发一个问题:对于每一路召回,我们应该返回多少个Item是合适的呢?如果在多路召回模式下,这个问题就很难解决。既然分数不可比,那么每一路召回多少候选项K就成为了超参,需要不断调整这个参数上线做AB测试,才能找到合适的数值。而如果召回路数特别多,于是每一路召回带有一个超参K,就是这一路召回多少条候选项,这样的超参组合空间是非常大的。所以到底哪一组超参是最优的,就很难定。其实现实情况中,很多时候这个超参都是拍脑袋上线测试,找到最优的超参组合概率是很低的。

而如果假设我们统一用FM模型来做召回,其实就不存在上面这个问题。这样,我们可以在召回阶段做到更好的个性化,比如有的用户喜欢看热门的内容,那么热门内容在召回阶段返回的比例就高,而其它内容返回比例就低。所以,可以认为各路召回的这组超参数就完全依靠FM模型调整成个性化的了,很明显这是使用单路单模型做召回的一个特别明显的好处。

再次,对于工业界大型的推荐系统来说,有极大的可能做召回的技术人员和做Ranking的技术人员是两拨人。这里隐含着一个潜在可能会发生的问题,比如召回阶段新增了一路召回,但是做Ranking的哥们不知道这个事情,在Ranking的时候没有把能体现新增召回路特性的特征加到Ranking阶段的特征中。这样体现出来的效果是:新增召回路看上去没什么用,因为即使你找回来了,而且用户真的可能点击,但是在排序阶段死活排不上去。也就是说,在召回和排序之间可能存在信息鸿沟的问题,因为目前召回和排序两者的表达模式差异很大,排序阶段以特征为表达方式,召回则以“路/策略/具体模型”为表达方式,两者之间差异很大,是比较容易产生上述现象的。

但是如果我们采用FM模型来做召回的话,新增一路召回就转化为新增特征的问题,而这一点和Ranking阶段在表现形式上是相同的,对于召回和排序两个阶段来说,两者都转化成了新增特征问题,所以两个阶段的改进语言体系统一,就不太容易出现上述现象。

上面三点,是我能想到的采用统一召回模型,相对多路召回的几个好处。但是是不是多路召回一定不如统一召回呢?其实也不是,很明显多路召回这种策略,上线一个新召回方式比较灵活,对线上的召回系统影响很小,因为不同路召回之间没有耦合关系。但是如果采用统一召回,当想新增一种召回方式的时候,表现为新增一种或者几种特征,可能需要完全重新训练一个新的FM模型,整个召回系统重新部署上线,灵活性比多路召回要差。
上面讲的是必要性,讲完了必要性,我们下面先探讨如何用FM模型做召回,然后再讨论如何把多路召回改造成单路召回,这其实是两个不同的问题。

FM做召回模型

如果要做一个实用化的统一召回模型,要考虑的因素有很多,比如Context上下文特征怎么处理,实时反馈特征怎么加入等。为了能够更清楚地说明,我们先从极简模型说起,然后逐步加入必须应该考虑的元素,最后形成一个实用化的统一召回模型。

不论是简化版本FM召回模型,还是复杂版本,首先都需要做如下两件事情:

第一,离线训练。这个过程跟在排序阶段采用FM模型的离线训练过程是一样的,比如可以使用线上收集到的用户点击数据来作为训练数据,线下训练一个完整的FM模型。在召回阶段,我们想要的其实是:每个特征和这个特征对应的训练好的embedding向量。这个可以存好待用。
FM做召回模型.jpg
第二,如果将推荐系统做个很高层级的抽象的话,可以表达成学习如下形式的映射函数:

FM - 图40

意思是,我们利用用户(User)相关的特征,物品(Item)相关的特征,以及上下文特征(Context,比如何时何地用的什么牌子手机登陆等等)学习一个映射函数F。学好这个函数后,当以后新碰到一个Item,我们把用户特征,物品特征以及用户碰到这个物品时的上下文特征输入F函数,F函数会告诉我们用户是否对这个物品感兴趣。如果他感兴趣,就可以把这个Item作为推荐结果推送给用户。

说了这么多,第二个我们需要做的事情是:把特征划分为三个子集合,用户相关特征集合,物品相关特征集合以及上下文相关的特征集合。而用户历史行为类特征,比如用户过去点击物品的特征,可以当作描述用户兴趣的特征,放入用户相关特征集合内。至于为何要这么划分,后面会讲。

做完上述两项基础工作,我们可以试着用FM模型来做召回了。

极简版FM召回模型

我们先来构建一个极简的FM召回模型,首先,我们先不考虑上下文特征,晚点再说。
极简的FM召回模型.jpg
第一步,对于某个用户,我们可以把属于这个用户子集合的特征,查询离线训练好的FM模型对应的特征embedding向量,然后将n个用户子集合的特征embedding向量累加,形成用户兴趣向量U,这个向量维度和每个特征的维度是相同的。

类似的,我们也可以把每个物品,其对应的物品子集合的特征,查询离线训练好的FM模型对应的特征embedding向量,然后将m个物品子集合的特征embedding向量累加,形成物品向量I,这个向量维度和每个特征的维度也是是相同的。

对于极简版FM召回模型来说,用户兴趣向量U可以离线算好,然后更新线上的对应内容;物品兴趣向量I可以类似离线计算或者近在线计算,问题都不大。
极简的FM召回模型1.jpg
第二步,对于每个用户以及每个物品,我们可以利用步骤一中的方法,将每个用户的兴趣向量离线算好,存入在线数据库中比如Redis(用户ID及其对应的embedding),把物品的向量逐一离线算好,存入Faiss(Facebook开源的embedding高效匹配库)数据库中。

当用户登陆或者刷新页面时,可以根据用户ID取出其对应的兴趣向量embedding,然后和Faiss中存储的物料embedding做内积计算,按照得分由高到低返回得分Top K的物料作为召回结果。提交给第二阶段的排序模型进行进一步的排序。这里Faiss的查询速度至关重要,至于这点,后面我们会单独说明。

这样就完成了一个极简版本FM召回模型。但是这个版本的FM召回模型存在两个问题。

问题一:首先我们需要问自己,这种累加用户embedding特征向量以及累加物品embedding特征向量,之后做向量内积。这种算法符合FM模型的原则吗?和常规的FM模型是否等价?

我们来分析一下。这种做法其实是在做用户特征集合U和物品特征集合I之间两两特征组合,是符合FM的特征组合原则的,考虑下列公式是否等价就可以明白了:

FM - 图43 (公式1)
FM - 图44 (公式2)

其实两者是等价的,建议您可以推导一下(这其实不就是上面在介绍FM公式改写的第三步转换吗?当然,跟完全版本的FM比,我们没有考虑U和I特征集合内部任意两个特征的组合,等会会说这个问题)。

也可以这么思考问题:在上文我们说过,FM为了提升计算效率,对公式进行了改写,改写后的高效计算公式的第一个平方项其实等价于:把所有特征embedding向量逐位累加成一个求和向量V,然后自己和自己做个内积操作FM - 图45。这样等价于根据FM的原则计算了任意两个特征的二阶特征组合了。而上面描述的方法,和标准的FM的做法其实是一样的,区别无非是将特征集合划分为两个子集合U和I,分别代表用户相关特征及物品相关特征。而上述做法其实等价于在用户特征和物品特征之间做两两特征组合,只是少了U内部之间特征,及I内部特征之间的特征组合而已。一般而言,其实我们不需要做U内部特征之间以及I内部特征之间的特征组合,对最终效果影响很小。于是,沿着这个思考路径,我们也可以推导出上述做法基本和FM标准计算过程是等价的。

第二个问题是:这个版本FM是个简化版本模型,因为它没考虑场景上下文特征,那么如果再将上下文特征引入,此时应该怎么做呢?

加入场景上下文特征

上面叙述了如何根据FM模型做一个极简版本的召回模型,之所以说极简,因为我们上面说过,抽象的推荐系统除了用户特征及物品特征外,还有一类重要特征,就是用户发生行为的场景上下文特征(比如什么时间在什么地方用的什么设备在刷新),而上面版本的召回模型并没有考虑这一块。

之所以把上下文特征单独拎出来,是因为它有自己的特点,有些上下文特征是近乎实时变化的,比如刷新微博的时间,再比如对于美团嘀嘀这种对地理位置特别敏感的应用,用户所处的地点可能随时也在变化,而这种变化在召回阶段就需要体现出来。所以,上下文特征是不太可能像用户特征离线算好存起来直接使用的,而是用户在每一次刷新可能都需要重新捕获当前的特征值。动态性强是它的特点。

而考虑进来上下文特征,如果我们希望构造和标准的FM等价的召回模型,就需要多考虑两个问题:

  • 既然部分上下文特征可能是实时变化的,无法离线算好,那么怎么融入上文所述的召回计算框架里?
  • 我们需要考虑上下文特征C和用户特征U之间的特征组合,也需要考虑C和物品特征I之间的特征组合。上下文特征有时是非常强的特征。那么,如何做能够将这两对特征组合考虑进来呢?

我们可以这么做:
场景特征Step1.jpg
首先,由于上下文特征的动态性,所以给定用户UID后,可以在线查询某个上下文特征对应的embedding向量,然后所有上下文向量求和得到综合的上下文向量C。这个过程其实和U及I的累加过程是一样的,区别无非是上下文特征需要在线实时计算。而一般而言,场景上下文特征数都不多,所以在线计算,速度方面应可接受。
场景特征Step2.jpg
然后,将在线算好的上下文向量C和这个用户的事先算好存起来的用户兴趣向量U进行内积计算FM - 图48。这个数值代表用户特征和上下文特征的二阶特征组合得分,算好备用。至于为何这个得分能够代表FM中的两者(U和C)的特征组合,其实道理和上面讲的U和I做特征组合道理是一样的。
场景特征Step3.jpg
再然后,将U和C向量累加求和,利用(U+C)去Faiss通过内积方式取出Top K物品,这个过程和极简版是一样的,无非查询向量由U换成了(U+C)。通过这种方式取出的物品同时考虑到了用户和物品的特征组合,以及上下文和物品的特征组合。道理和之前讲的内容是类似的。

假设返回的Top K物品都带有内积的得分Score1,再考虑上一步的得分Score,将两者相加对物品重排序(因为跟物品无关,所以其实不影响物品排序,但是会影响最终得分,FM最外边的Sigmoid输出可能会因为加入这个得分而发生变化),就得到了最终结果,而这个最终结果考虑了U/I/C两两之间的特征组合。

于是我们通过这种手段,构造出了一个完整的FM召回模型。这个召回模型通过构造user embedding,Context embedding和Item embedding,以及充分利用类似Faiss这种高效embedding计算框架,就构造了高效执行的和FM计算等价的召回系统。

将多路召回融入FM召回模型

上文所述是如何利用FM模型来做召回,下面我们讨论下如何将多路召回统一到FM召回模型里来。

我们以目前不同类型推荐系统中共性的一些召回策略来说明这个问题,以信息流推荐为例子,传统的多路召回阶段通常包含以下策略:协同过滤,兴趣分类,兴趣标签,兴趣Topic,兴趣实体,热门物品,相同地域等。这些不同角度的召回策略都是较为常见的。
多路融入FM召回.jpg
我们再将上述不同的召回路分为两大类,可以把协同过滤作为一类,其它的作为一类,协同过滤相对复杂,我们先说下其它类别。

对于比如兴趣分类,兴趣标签,热门,地域等召回策略,要把这些召回渠道统一到FM模型相对直观,只需要在训练FM模型的时候,针对每一路的特性,在用户特征端和物品特征端新增对应特征即可。比如对于地域策略,我们可以把物品所属地域(比如微博所提到的地域)和用户的感兴趣地域都作为特征加入FM模型即可。兴趣标签,Topic,兴趣实体等都是类似的。所以大多数情况下,在多路召回模式下你加入新的一路召回,在FM统一召回策略下,对应地转化成了新增特征的方式。

然后我们再说协同过滤这路召回。其实本质上也是将一路召回转化为新加特征的模式。我们上文在介绍FM模型和MF模型关系的时候提到过:本质上MF模型这种典型的协同过滤策略,是FM模型的一个特例,可以看作在FM模型里只有User ID和Item ID这两类(Fields)特征的情形。意思是说,如果我们将user ID和Item ID作为特征放入FM模型中进行训练,那么FM模型本身就是包含了协同过滤的思想的。当然,对于超大规模的网站,用户以亿计,物品可能也在千万级别,如果直接把ID引入特征可能会面临一些工程效率问题以及数据稀疏的问题。对于这个问题,我们可以采取类似在排序阶段引入ID时的ID 哈希等降维技巧来进行解决。

所以综合来看,在多路召回下的每一路召回策略,绝大多数情况下,可以在FM召回模型模式中转化为新增特征的方式。

在具体实施的时候,可以沿着这个路径逐步替换线上的多路召回:先用FM模型替换一路召回,线上替换掉;再新加入某路特征,这样上线,就替换掉了两路召回;如此往复逐渐把每一路召回统一到一个模型里。这是比较稳的一种替换方案。当然如果你是个猛人,直接用完整的FM召回模型一步替换掉线上的各路召回,也,未尝不可。只要小流量AB测试做好也没啥。

FM模型能否将召回和排序阶段一体化

前文有述,之所以目前常见的工业推荐系统会分为召回排序两个阶段,是因为这两个阶段各司其职,职责分明。召回主要考虑泛化性并把候选物品集合数量降下来;排序则主要负责根据用户特征/物品特征/上下文特征对物品进行精准排名。

那么,我们现在可以来审视下本文开头提出的第二个问题了:FM模型能否将常见的两阶段模型一体化?即是否能将实用化的推荐系统通过FM召回模型简化为单阶段模型?意思是推荐系统是否能够只保留FM召回这个模块,扔掉后续的排序阶段,FM召回按照得分排序直接作为推荐结果返回。我们可以这么做吗?

这取决于FM召回模型是否能够一并把原先两阶段模型的两个职责都能承担下来。这句话的意思是说,FM召回模型如果直接输出推荐结果,那么它的速度是否足够快?另外,它的精准程度是否可以跟两阶段模型相媲美?不会因为少了第二阶段的专门排序环节,而导致推荐效果变差?如果上面两个问题的答案都是肯定的,那么很明显FM模型就能够将现有的两阶段推荐过程一体化。

我们分头来分析这个问题的答案:准确性和速度。先从推荐精准度来说明,因为如果精准度没有办法维持,那么速度再快也没什么意义。

所以现在的第一个子问题是:FM召回模型推荐结果的质量,是否能够和召回+排序两阶段模式接近?
我们假设一个是FM统一召回模型直接输出排序结果;而对比模型是目前常见的多路召回+FM模型排序的配置。从上文分析可以看出,尽管FM召回模型为了速度够快,做了一些模型的变形,但是如果对比的两阶段模型中的排序阶段也采取FM模型的话,我们很容易推理得到如下结论:如果FM召回模型采用的特征和两阶段模型的FM排序模型采用相同的特征,那么两者的推荐效果是等价的。这意味着:只要目前的多路召回都能通过转化为特征的方式加入FM召回模型,而且FM排序阶段采用的特征在FM召回模型都采用。那么两者推荐效果是类似的。这意味着,从理论上说,是可以把两阶段模型简化为一阶段模型的。

既然推理的结论是推荐效果可以保证,那么我们再来看第二个问题:只用FM召回模型做推荐,速度是否足够快?
我们假设召回阶段FM模型对User embedding和Item embedding的匹配过程采用Facebook的Faiss系统,其速度快慢与两个因素有关系:

  1. 物品库中存储的Item数量多少,Item数量越多越慢;
  2. embedding大小,embedding size越大,速度越慢;

微博机器学习团队18年将Faiss改造成了分布式版本,并在业务易用性方面增加了些新功能,之前我们测试的查询效率是:假设物品库中存储100万条微博embedding数据,而embedding size=300的时候,TPS在600左右,平均每次查询小于13毫秒。而当库中微博数量增长到200万条,embedding size=300的时候,TPS在400左右,平均查询时间小于20毫秒。这意味着如果是百万量级的物品库,embedding size在百级别,一般而言,通过Faiss做embedding召回速度是足够实用化的。如果物品库大至千万量级,理论上可以通过增加Faiss的并行性,以及减少embedding size来获得可以接受的召回速度。

当然,上面测试的是纯粹的Faiss查询速度,而事实上,我们需要在合并用户特征embedding的时候,查询用户特征对应的embedding数据,而这块问题也不太大,因为绝大多数用户特征是静态的,可以线下合并进入用户embedding,Context特征和实时特征需要线上在线查询对应的embedding,而这些特征数量占比不算太大,所以速度应该不会被拖得太慢。

综上,FM召回模型从理论分析角度,其无论在实用速度方面,还是推荐效果方面,应该能够承载目前“多路召回+FM排序”两阶段推荐模式的速度及效果两方面功能,所以推论它是可以将推荐系统改造成单模型单阶段模式的

当然,上面都是分析结果,并非实测,所以不能确定实际应用起来也能达到上述理论分析的效果。

FFM

从FM到FFM

FFM的全称是Field-aware FM,直观翻译过来,就是能够意识到特征域(Field)的存在的FM模型。那么FFM模型是有第六感吗?它怎么能够感知到特征域的存在呢?这里先不解释,后面会说明。我们先看个例子。
FFM1.jpg
上图是一个人造的广告CTR的数据例子,代表的意思是在某个网站(Publisher)上刊登一则广告(Advertiser),某个用户(用户性别特征Gender)是否会点击某条广告的数据。这个例子中假设包含三个特征域(Field):网站Publisher(可能的特征值是ESPN、Vogue、NBC)、广告(可能的特征值是Nike、Adidas、Gucci)和性别特征Gender(可能的特征值是Male、Female)。由这个例子可以看出组合特征的重要性:如果在体育网站ESPN上发布Nike的广告,那么100次展现,80次会被点击,而20次不会被点击。意味着组合特征(Publisher=”ESPN” and Advertiser=”Nike”)是个很强的预测用户是否点击的二阶组合特征。上图同时展示了一条用户点击记录。

我们用这个例子来说明FFM的基本思想,FM模型可以看做是FFM模型的一个特例,所以在说明FFM模型思想之前,我们先用上述例子说明FM的思想,然后通过和FM模型的对比,很容易理解FFM模型的基本思路。
FFM2.jpg
FM模型在做二阶特征组合的时候,对于每个二阶组合特征的权重,是根据对应两个特征的Embedding向量内积,来作为这个组合特征重要性的指示。当训练好FM模型后,每个特征都可以学会一个特征embedding向量,参考上图。当做预测的时候,比如我们接收到上面例子的数据,需要预测用户是否会点击这条广告,则对三个特征做两两组合,每个组合特征的权重,可以根据两个对应的特征embedding内积求得,对所有组合特征求和后,套接Sigmoid函数即可做出二分类预测。
FFM3.jpg
对于FM模型来说,每个特征学会唯一的一个特征embedding向量,注意,在这里,和FFM的最大不同点冒出来了。为了更容易向FFM模型理解过渡,我们可以这么理解FM模型中的某个特征的embedding:拿Vespn这个特征作为例子,当这个特征和其它特征域的某个特征进行二阶特征组合的时候,不论哪个特征域的特征和Vespn特征进行组合,Vespn这个特征都反复使用同一个特征embedding去做内积,所以可以理解为Vespn这个特征在和不同特征域特征进行组合的时候,共享了同一个特征向量。

沿着这个思路思考,我会问出一个问题:我们可以改进下FM模型吗?怎么改进?下图给个提示。
FFM4.jpg
如果你对算法敏感的话,你可以这么回答我:既然FM模型的某个特征,在和任意其它特征域的特征进行组合求权重的时候,共享了同一个特征向量。那么,如果我们把这个事情做地更细致些,比如Vespn这个特征,当它和Nike(所属特征域Advertiser)组合的时候用一个特征embedding向量,而当它和Male(所属特征域Gendor)组合的时候,使用另外一个特征embedding向量,这样是否在描述特征组合的时候更细腻一些?也就是说,当Vespn这个特征和属于Advertiser这个域的特征进行组合的时候,用一个特征embedding;和属于Gendor这个特征域的特征进行组合的时候,用另外一个特征embedding。这意味着,如果有FM - 图55个特征域,那么每个特征由FM模型的一个FM - 图56维特征embedding,拓展成了FM - 图57FM - 图58维特征embedding。之所以是FM - 图59,而不是FM - 图60,是因为特征不和自己组合,所以不用考虑自己。

这其实就是FFM模型的基本思想。所以从上面两个图的示意可以看出,为何说FM模型是FFM模型的特例。
FFM5.jpg
我们再回头看下刚才那个点击数据的例子,看看在FFM场景下是怎样应用的,上图展示了这个过程。因为这个例子有三个特征域,所以Vespn有两个特征embedding,当和Nike特征组合的时候,用的是针对Advertisor这个特征域的embedding去做内积;而当和Male这个特征组合的时候,则用的是针对Gendor这个特征域的embedding去做内积。同理,Nike和Male这两个特征也是根据和它组合特征所属特征域的不同,采用不同的特征向量去做内积。而两两特征组合这个事情的做法,FFM和FM则是完全相同的,区别就是每个特征对应的特征embedding个数不同。FM每个特征只有一个共享的embedding向量,而对于FFM的一个特征,则有FM - 图62个特征embedding向量,用于和不同的特征域特征组合时使用。

从上面的模型演化过程,你可以体会到,为何这篇文章的标题将FFM模型称为笨重,它笨重在哪里?说它笨重,是和FM模型相比较而言的。我们可以推出,假设模型具有FM - 图63个特征,那么FM模型的参数量是FM - 图64(暂时忽略掉一阶特征的参数),其中k是特征向量大小。而FFM模型的参数量呢?因为每个特征具有FM - 图65FM - 图66维特征向量,所以它的模型参数量是FM - 图67,也就是说参数量比FM模型扩充了FM - 图68倍。这意味着,如果我们的任务有100个特征域,FFM模型的参数量就是FM模型的大约100倍。这其实是很恐怖的,因为现实任务中,特征数量FM - 图69是个很大的数值,特征域几十上百也很常见。另外,我们在上一篇介绍FM模型的文章里也讲过,FM模型可以通过公式改写,把本来看着是n的平方的计算复杂度,降低到FM - 图70。而FFM无法做类似的改写,所以它的计算复杂度是FM - 图71,这明显在计算速度上也比FM模型慢得多。所以,无论是急剧膨胀的参数量,还是变慢的计算速度,无论从哪个角度看,相对FM模型,FFM模型是略显笨重的。

正因为FFM模型参数量太大,所以在训练FFM模型的时候,很容易过拟合,需要采取早停等防止过拟合的手段。而根据经验,FFM模型的k值可以取得小一些,一般在几千万训练数据规模下,取8到10能取得较好的效果,当然,FM - 图72具体取哪个数值,这其实跟具体训练数据规模大小有关系,理论上,训练数据集合越大,越不容易过拟合,这个FM - 图73值可以设置得越大些。

FFM做召回模型

如果要做一个实用化的统一召回模型,要考虑的因素有很多,比如Context上下文特征怎么处理,一阶项特征怎么加入等。为了能够更清楚地说明,我们先从简易模型说起,然后逐步加入必须应该考虑的元素,最后形成一个实用化的FFM版本的召回模型。

不论是简化版本FFM召回模型,还是完全化版本,首先都需要先做如下两件事情:

第一,离线训练。这个过程跟在排序阶段采用FFM模型的离线训练过程是一样的,比如可以使用线上收集到的用户点击数据来作为训练数据,线下训练一个完整的FFM模型。在召回阶段,我们想要的其实是:每个特征和这个特征对应的训练好的FM - 图74个embedding向量。这个可以存好待用。
FFM6.jpg
第二,如果将推荐系统做个很高层级的抽象的话,可以表达成学习如下形式的映射函数:

FM - 图76

意思是,我们利用用户(User)相关的特征,物品(Item)相关的特征,以及上下文特征(Context,比如何时何地用的什么牌子手机登陆等等)学习一个映射函数F。学好这个函数后,当以后新碰到一个Item,我们把用户特征,物品特征以及用户碰到这个物品时的上下文特征输入F函数,F函数会告诉我们用户是否对这个物品感兴趣。如果他感兴趣,就可以把这个Item作为推荐结果推送给用户。

第二个我们需要做的事情是:把特征域划分为三个子集合,用户相关特征集合,物品相关特征集合以及上下文相关的特征集合。而用户历史行为类特征,比如用户过去点击物品的特征,可以当作描述用户兴趣的特征,放入用户相关特征集合内。至于为何要这么划分,后面会讲。

简易版FFM召回模型

根据FFM计算原则构建用户及物品Embedding

上文简单叙述过,用排序模型做召回的特点。其实,你可以这么理解:把FM/FFM等模型用来做召回,看做原先的“召回+排序”两阶段过程中的第二个过程前置,放到召回阶段来做排序。它本质上其实就是希望同时将两阶段过程用同一个阶段吸收掉。

只是因为召回阶段面临的待处理物料数量太大,所以依赖一种高效的计算模式,而这个目前看不是问题,成熟的方案就是Faiss的Embedding匹配的模式,速度应该是足够实用化的。

所以问题就转换成了:如何根据某个模型的计算标准,打出对应的用户侧Embedding,以及物品侧的Embedding。于是,我们可以将召回阶段的FM/FFM或者其它模型看成是一种受约束的排序过程,这里的“约束”,指的是需要明确将FM/FFM召回模型划分为两个阶段:首先需要离线将用户侧特征和物品侧特征进行分离编码,然后在线快速embedding匹配的时候完成模型计算过程。这不像传统的排序阶段使用FM/FFM模型,此时,两侧特征可以同时作为模型的输入,明显更灵活,受约束更小。所以,我们可以把召回阶段采用这种排序模型看成一种受约束的排序。

我们的主题是利用FFM模型做召回。于是问题转换成了:如何根据FFM模型的计算原则,打出对应的用户侧Embedding,以及物品侧Embedding。怎么做呢?

我用一个极度简化的例子来说明这个过程:假设在这个例子中,我们只使用五个特征域,用户侧采用两个特征域U1和U2,而物品侧采用三个特征域I1,I2和I3。当面对具体数据实例的时候,对应特征域下会有一个对应的特征值存在。对于某个具体的特征值f1来说,根据FFM的计算原则,它在离线训练阶段会学会4个对应的embedding向量,分别在这个特征和其它特征域的特征进行特征组合的时候使用。
FFM7.jpg
首先,要意识到,我们当前的任务是希望通过FFM模型来做用户任意特征和物品任意特征的组合

对于用户侧的两个特征,我们取出它们分别用于和对应的三个物品侧特征域组合时要用的embedding向量。比如对于U1来说,我们分别将这三个特征embedding称为U11/U12/U13,U11的两个下标数字的含义是:这是第1个用户侧的特征域U1和第1个物品侧特征域I1进行组合时使用的特征embedding。U12则是第1个用户侧的特征域U1和第2个物品侧特征域I2进行组合时使用的特征embedding。如此处理,于是每个用户侧的特征取出三个特征向量,每个物品侧的特征取出两个特征向量。形成上图的结构。

根据FFM的计算规则,如果我们希望计算用户侧和物品侧的两两特征组合,需要将特征向量求内积时的对应关系建立起来,图中箭头标出了对应关系。你可能看着有点乱,但是对应关系里面隐藏着一个规律,你可以找找这个规律看。提示下:你可以看看U和I特征向量下标编号,有什么规律性的对应关系吗?U12<—>I21、U23<—>I32……,嗯,我估计你看出来了,规律就是
FFM8.jpg
上面那张图的特征向量之间的对应关系,看着确实有点让人眼花缭乱,那么能否让它们的对应关系看上去更简洁直接一些呢?很简单,只需要把物品侧的特征向量重新排下顺序即可。这个重排序的过程,可以看做是:对原先顺序排列的物品侧特征向量矩阵,做了一个转置操作。这样,每个物品侧的特征向量,就和需要求内积的对应用户侧特征向量,形成了整齐的一一对齐的效果了。具体过程参考上图。
FFM9.jpg
我们讲过,模型做召回,要解决的问题是:如何利用FFM原则打出对应的用户侧embedding和物品侧embedding。前面两段所讲的,是根据FFM原则,对应的特征向量应该如何对齐的过程,而如果向量对齐后,怎么打出两个embedding向量?很简单,把刚才对齐的二维向量拉平,顺序concat连接,就形成了展开的一维的用户embedding和物品embedding。

然后,我们可以把每个物品的embedding离线存入Faiss,用户embedding离线算好,放在内存数据库中。当用户登录或者刷新时,在线根据用户的embedding向量,通过Faiss的快速查询功能,根据内积往回拉取top K物品,返回的物品就是根据FFM模型计算得分最高的推荐结果。

这样,其实就已经完成了一个简易版本的FFM召回模型。我们考虑下之前说的关键点:两个拉长版本的User Embeding和Item Embedding,通过Faiss内积计算,最后的得分,是否和标准的FFM计算结果等价?
FFM10.jpg
两者很显然是等价的,内积的操作是两个长向量对应位的数值相乘,然后求和,所以拉长向量匹配版本和分拆成子项分别求内积再求和,数值是一样的,从上图示例可以很容易看出这一点。

从上述说明可以看出,此时我们获得了一个基础版本的FFM召回模型,这个版本的召回模型,只考虑了U和I特征的相互组合,其它的因素还没考虑。

此时应该回头再想想我们的标题:沉重的FFM。为什么我说FFM沉重呢?你可以算算这个拉平的embedding向量的长度。假设在我们的实际任务中,用户侧有50个特征域(M=50),物品侧有50个特征域(N=50),每个特征向量的大小k=10,可以很容易推断出用户和物品的embedding长度,它的 size=MNK=505010=25000,两万五千,“苦不苦,想想红军两万五,累不累,想想革命老前辈”,如果把一个数值位换成一里地,那快赶上长征的距离了。而这对于Faiss来说,如果物品库比较大,速度明显是跟不上的。

一种直观减小embedding长度的方法是把k值往小放,比如k=2或者4。如果只是使用FFM模型做召回,这个策略是可行的,反正召回阶段不用特别准,推荐结果的准确性靠第二个排序阶段来来保证,召回阶段原则上能把好的物料找回来即可。即使这样,embedding size=50502=5000,长度也还是很长,虽说比不上长征的里程,但是明显比苏小妹的脸还是要长的。

另外一种思路是把特征域数量降下来,比如M=N=10,就是说用户和物品两侧各有10个特征域,这样的话embedding size=10102=200。嗯,这个基本可以实用化了。如果只是将FFM用来做召回,虽说受限严重,但这么做,也不是不可以。

但是,我希望FFM不仅能够不受特征域数量限制地做召回,而且最好它还能一阶段地把排序也做掉,所以靠上面两个手段,是不能从根本上解决问题的。有什么加速策略吗?我想了两个方法,后面会分别介绍。

我们先把速度问题往后放一放,等会再谈。先一步一步优化这个FFM召回模型。

上面介绍的FFM召回模型,只是个简易版本,和标准FFM模型相比,很多内容它还没有考虑进来,比如用户侧或物品侧内部特征组合问题,一阶项如何引入的问题以及如何融入场景上下文特征问题,如果再将这三者引入,此时应该怎么做呢?

加入用户及物品侧内部特征组合

上小节所述内容,本质上是在计算用户侧任意特征和物品侧任意特征之间的两两特征组合。到这里你发现,如果希望在召回阶段完整复现FFM模型,还需要考虑用户侧内部两两特征组合,以及物品侧内部两两特征组合。

至于用户侧或者物品侧内部的两两特征组合的计算方法,可以仿照上述计算用户侧和物品侧特征组合的方法,也可以按照标准的FFM计算流程计算,总之方式比较灵活。关键的问题是:假设用户侧的内部特征两两组合得分Score(User_iUser_j)及Score(Iem_iItem_j)算出来后,如何把它们集成进入那两个长长的用户embedding和物品embedding中?
FFM11.jpg
可以如上图所示去做,在用户的二阶项embedding后添加两位:一位就是用户侧内部特征组合得分,在对应的物品侧位置,增加一位,值设置为1。这样的话,在Faiss做内积的过程中,就将用户侧内部特征组合得分计入;类似地,在物品侧也可以如此炮制。这样就将U和I的内部特征组合融入FFM召回模型中了,FM模型也是一样的道理

理论上来说,如果是只用FM/FFM模型做召回,用户侧内部的特征组合对于返回结果排序没有影响,所以可以不用加入。物品侧内部特征之间的特征组合可能会对返回的物品排序结果有影响,可以考虑引入这种做法,把它统一加进去。而如果是希望用FM/FFM模型一阶段地替代掉“多路召回+Ranking”的两阶段模式,则可以考虑完全复现FM/FFM模型,如此,应将两侧的内部特征组合都考虑进去。

加入一阶项

FFM12.jpg
我们知道,标准的FM/FFM公式中是包含一阶项的,也就是LR模型。如果我们根据上节方法所述,做出了用户侧和物品侧的二阶项embedding,此时,想要把一阶项加入FM/FFM召回模型,应该怎么做呢?
FFM13.jpg
其实很简单,上图展示了一种做法,在用户侧的embedding中增加两位,第一位是属于用户特征域的特征对应的一阶项累加和,相应地,在物品侧对应位置增加一位,设置值为1,这样在Faiss求内积的过程中,就把用户侧的一阶项引入了。类似地,也可以如此加入物品侧的一阶项。
FFM14.jpg
还有一种做法,如上图所示,不做用户侧和物品侧的一阶项求和,而是直接将用户侧及物品侧对应特征的一阶权重拼接到二阶项的embedding后。同样的,对应的物品侧或用户侧相应位置设置为1。这样,也可以在Faiss求内积过程中,把一阶项算入得分中。

微博在业务中的实践表明,如果采取FM召回模型,对于有些应用来说,一阶项对于最终效果有明显影响,所以在用FM/FFM做召回的时候,是需要将一阶项考虑进去的,这可能是个别一阶特征比较重要导致的。我们在Criteo数据集合的实验结果也证明:如果是FM模型,一阶项是有用的,去掉一阶项,只保留二阶项,AUC大约会掉1个绝对百分点,对于CTR来说,这个差距还是很明显的;而如果是采用DeepFM模型,则FM部分是否保留一阶项对最终结果没有什么影响,这说明DNN的隐层有效地将一阶项的作用吸收掉了。

加入场景上下文特征

我们上面说过,抽象的推荐系统除了用户特征及物品特征外,还有一类重要特征,就是用户发生行为的场景上下文特征(比如什么时间在什么地方用的什么设备在刷新),而上面逐步改进版本的FFM召回模型还没有考虑这一块。

之所以把上下文特征单独拎出来,是因为它有自己的特点,有些上下文特征是近乎实时变化的,比如刷新微博的时间,再比如对于美团嘀嘀这种对地理位置特别敏感的应用,用户所处的地点可能随时也在变化,而这种变化在召回阶段就需要体现出来。所以,上下文特征是不太可能像用户特征离线算好存起来直接使用的,而是用户在每一次刷新可能都需要重新捕获当前的特征值。动态性强是它的特点。

而考虑进来上下文特征,如果我们希望构造和标准的FFM等价的召回模型,就需要多考虑两个问题:

  • 既然部分上下文特征可能是实时变化的,无法离线算好,那么怎么实时地将它融入上文所述的FFM召回计算框架里?
  • 我们需要考虑上下文特征C和用户特征U之间的特征组合,也需要考虑C和物品特征I之间的特征组合。上下文特征有时是非常强的特征。那么,如何做能够将这两对特征组合考虑进来呢?

FFM15.jpg
首先,由于上下文特征的动态性,所以给定用户UID后,可以在线查询某个上下文特征对应的(F-1)个embedding向量,F是任务特征域的个数。这(F-1)个特征向量可以分成三组:一组是用于拿Context特征和用户特征域的特征进行特征组合用的,在我们上面给的例子里,有两个;第二组是拿Context特征和物品特征域的特征进行特征组合用的,我们的例子里这个数目是三;第三组是Context特征用于自身内部特征组合用的,这个我们先忽略,因为它的做法和上文所述的用户侧及物品侧求内部特征组合的做法是一样的。

为了简化说明,我们假设只有一个Context特征,于是它对应了(6-1)=5个embedding向量,其中2个是用于和用户侧特征进行组合的,3个是用于和物品侧特征进行组合的。我们把它们拆分成两组,如上图所示。
FFM16.jpg
然后,我们来计算上下文特征和用户侧特征如何进行特征组合。如上图所示,其实这个过程和上文讲的用户侧与物品侧的FFM特征组合过程是一样的。物品侧和上下文侧特征找到对应的embedding向量做内积计算即可。这里不展开讲,如果不理解的话再回头看下上面的叙述。因为这两类特征都在用户发生访问行为的时候能获得,不依赖和物品发生关系,所以这个过程可以在用户侧在线计算完成。

这个内积数值代表用户特征和上下文特征的二阶特征组合得分,算好备用。
FFM17.jpg
再然后,我们来计算上下文特征和物品侧特征的特征组合,如上图所示。其实很好理解,就相当于在做用户侧特征与物品侧特征组合的时候,在用户侧新加入了几个特征,无非这几个特征是Context特征,而其实不是用户侧的特征,但是做法是完全一样的。这样,就可以将Context特征打入用户侧embedding以及物品侧embedding,于是Context和物品的特征组合问题就解决了。

利用这个用户侧embedding,用Faiss通过内积方式取出Top K物品。通过这种方式取出的物品同时考虑到了用户和物品的特征组合,以及上下文和物品的特征组合

假设返回的Top K物品都带有内积的得分Score1,再考虑上一步的得分Score,将两者相加对物品重排序(因为跟物品无关,所以其实不影响物品排序,如果是召回阶段使用FM/FFM,是可以不考虑引入的),就得到了最终结果。而这个最终结果,在遵循FFM计算原则的基础上,考虑了U/I/C两两之间的特征组合。当然,我们可以把上面说的一阶项以及/内部特征组合也融入这个系统。

于是我们通过这种手段,构造出了一个完整的FFM召回模型。这个召回模型通过构造user embedding,Context embedding和Item embedding,以及充分利用类似Faiss这种高效embedding计算框架,就构造了高效执行的和FFM计算完全等价的召回系统。

前文提过,FFM按照上述方法做,打出来的两个embedding长度太长,可能影响Faiss的效率。下面提供两个可能的提速方案。

并行拉取提速策略

FFM18.jpg
如果用上述方法做FFM召回模型,有可能被拉平的用户embedding以及物品embedding长度太长,这会导致Faiss提取速度变慢,以致这个方法因为速度太慢而变得不可行。那么一种比较直接的提速想法就是:把太长的用户embedding打断成连续片段,物品embedding也相应地打断,同一个物品的embedding片段分别存在不同的Faiss数据库中,这样由于减少了embedding的长度,所以会极大加快Faiss的提取速度。

在结果返回时,对每个User Embedding片段拉回的Item子集合进行合并,同一个物品,把各自的片段内积得分累加,就得到了这个物品相对用户的FFM最终得分,很容易推断,这种片段得分累加策略,和作为整体计算长向量内积,两者得分是相同的。按照这个得分对返回的物品重排序,于是就得到了最终计算结果。这是一种典型的并行策略。

虽然,理论上,这个方案能够处理相当长的embedding匹配问题。但是,这个方案有个问题:并不能保证返回结果的最终排序和真实排序是一致的。因为有可能某个综合总得分较高的物品没有被从任何一个Faiss子数据库拉回来,比如这个物品每个片段的得分都不太高也不太低的情况,是可能发生这种漏召回的情况的。

(FM+FFM)混合提速策略

FFM19.jpg
本系列文章中,上篇在介绍FM召回模型的时候,可以看出,它的一个特别简洁的方式是把用户侧的特征embedding累加,以及物品侧的特征embedding累加,所以FM打出来的两个embedding长度,只跟k相关,跟特征数目没关系,无论多少特征,embedding size恒等于k。所以看着特别简洁,效率也高。

那么FFM是否能够参照FM的思路,把一部分特征的embedding累加起来,通过这种方式来减小用户侧或物品侧的embedding大小呢?我觉得,结论是这样的:如果你坚持做一个原汁原味版的FFM,是不可能存在类似的特征合并的,因为用户侧和物品侧的做内积的embedding向量都是一一对应的,且无公共因子项可提出,所以没有可能进行特征embedding合并。

但是,如果我们不是原教旨FFM主义分子,一定坚持计算过程完全符合FFM计算原则,那么这个事情还是可以做的。参考上图,我觉得可以这么做:不同用户侧的特征,对应Fileds的向量直接累加;而在物品侧,则是属于同一个特征域的向量直接累加。这样可以保证用户embedding和物品embedding大小一致。这样的话,用户侧和物品侧的embedding size=M*K,比如M=50,K=10,那么长度是500,这样的长度还是可以把速度做起来的。

如果参照上面的做法,这其实等价于做了这么个事情:用户侧的特征仍然坚持了FFM的计算原则,就是每个特征针对其它不同特征域的组合,采用了不同的特征向量;但是,物品侧的特征向量,因为同一个特征域的(F-1)个特征域合并成一个,类似于这里采取的是FM的特征embedding思路。所以,这个方法看上去貌似是一个处于FFM和FM模型之间的一种混合模型。至于效果的话,我估计应该比FM好,比FFM不如,很可能也介于两者之间。当然,这只是我的分析结论。实际效果如何要通过实验来证明。

上面是按照合并物品侧的同一个特征域的特征向量角度来做的。完全也可以反过来,就是去合并用户侧的同一个特征域的特征向量。而如果是那样,则embedding size=N*K。

好了,经过了一系列补充特性,以及一些性能优化方案,我们就得到了一个完整版本的FFM召回模型。上面所讲都是说如何用FFM模型来做召回,那么下面我们开始探讨本文开头提出的第一个问题:如何用FFM召回模型统一多路召回策略?

FFM做统一的召回模型

上文书提到过,目前工业界推荐系统在召回阶段,大多数采用了多路召回策略,比如典型的召回路有:基于用户兴趣标签的召回;基于协同过滤的召回;基于热点的召回;基于地域的召回;基于Topic的召回;基于命名实体的召回等等,除此外还有很多其它类型的召回路。

现在我们来探讨下第一个问题:在召回阶段,能否用一个统一的模型把多路召回招安?就是说改造成利用单个模型,单路召回的模式?具体到这篇文章,就是说能否利用FFM模型来把多路召回统一起来?

在回答上述问题之前,我估计你会提出疑问:目前大家用多路召回用的好好的,为啥要多此一举,用一个模型把多路召回统一起来呢?这个问题非常好,我们确实应该先看这么做的必要性。

统一召回和多路召回优缺点

我们先来说明下统一召回和多路召回各自的优缺点,我觉得使用统一召回模式,相对多路召回有如下优点:

首先,采用多路召回,每一路召回因为采取的策略或者模型不同,所以各自的召回模型得分不可比较,比如利用协同过滤召回找到的候选Item得分,与基于兴趣标签这一路召回找到的候选Item得分,完全是不可比较的。这也是为何要用第二阶段Ranking来将分数统一的原因。而如果采取统一的召回模型,比如FM/FFM模型,那么不论候选项Item来自于哪里,它们在召回阶段的得分是完全可比的。

其次,貌似在目前“召回+Ranking”两阶段推荐模型下,多路召回分数不可比这个问题不是特别大,因为我们可以依靠Ranking阶段来让它们可比即可。但是其实多路召回分数不可比会直接引发一个问题:对于每一路召回,我们应该返回多少个Item是合适的呢?如果在多路召回模式下,这个问题就很难解决。既然分数不可比,那么每一路召回多少候选项K就成为了超参,需要不断调整这个参数上线做AB测试,才能找到合适的数值。而如果召回路数特别多,于是每一路召回带有一个超参K,就是这一路召回多少条候选项,这样的超参组合空间是非常大的。所以到底哪一组超参是最优的,就很难定。其实现实情况中,很多时候这个超参都是拍脑袋上线测试,找到最优的超参组合概率是很低的。

而如果假设我们统一用FM/FFM模型来做召回,其实就不存在上面这个问题。这样,我们可以在召回阶段做到更好的个性化,比如有的用户喜欢看热门的内容,那么热门内容在召回阶段返回的比例就高,而其它内容返回比例就低。所以,可以认为各路召回的这组超参数就完全依靠FM模型调整成个性化的了,很明显这是使用单路单模型做召回的一个特别明显的好处。

再次,对于工业界大型的推荐系统来说,有极大的可能做召回的技术人员和做Ranking的技术人员是两拨人。这里隐含着一个潜在可能会发生的问题,比如召回阶段新增了一路召回,但是做Ranking的哥们不知道这个事情,在Ranking的时候没有把能体现新增召回路特性的特征加到Ranking阶段的特征中。这样体现出来的效果是:新增召回路看上去没什么用,因为即使你找回来了,而且用户真的可能点击,但是在排序阶段死活排不上去。也就是说,在召回和排序之间可能存在信息鸿沟的问题,因为目前召回和排序两者的表达模式差异很大,排序阶段以特征为表达方式,召回则以“路/策略/具体模型”为表达方式,两者之间差异很大,是比较容易产生上述现象的。
但是如果我们采用FM/FFM模型来做召回的话,新增一路召回就转化为新增特征的问题,而这一点和Ranking阶段在表现形式上是相同的,对于召回和排序两个阶段来说,两者都转化成了新增特征问题,所以两个阶段的改进语言体系统一,就不太容易出现上述现象。

上面三点,是我能想到的采用统一召回模型,相对多路召回的几个好处。但是是不是多路召回一定不如统一召回呢?其实也不是,很明显多路召回这种策略,上线一个新召回方式比较灵活,对线上的召回系统影响很小,因为不同路召回之间没有耦合关系。但是如果采用统一召回,当想新增一种召回方式的时候,表现为新增一种或者几种特征,可能需要完全重新训练一个新的FM/FFM模型,整个召回系统重新部署上线,灵活性比多路召回要差。
上面讲的是必要性,讲完了必要性,我们下面探讨如何把多路召回改造成单路召回。

多路召回融入FFM召回模型

其实,用FFM模型统一多路召回,和FM模型统一多路召回,基本是一样的,只有些许不同。

我们以目前不同类型推荐系统中共性的一些召回策略来说明这个问题,以信息流推荐为例子,传统的多路召回阶段通常包含以下策略:协同过滤,兴趣分类,兴趣标签,兴趣Topic,兴趣实体,热门物品,相同地域等。这些不同角度的召回策略都是较为常见的。
FFM20.jpg
我们再将上述不同的召回路分为两大类,可以把协同过滤作为一类,其它的作为一类,协同过滤相对复杂,我们先说下其它类别。

对于比如兴趣分类,兴趣标签,热门,地域等召回策略,要把这些召回渠道统一到FM/FFM模型相对直观,只需要在训练FM/FFM模型的时候,针对每一路的特性,在用户特征端和物品特征端新增对应特征即可。比如对于地域策略,我们可以把物品所属地域(比如微博所提到的地域)和用户的感兴趣地域都作为特征加入FM/FFM模型即可。兴趣标签,Topic,兴趣实体等都是类似的。所以大多数情况下,在多路召回模式下你加入新的一路召回,在FM/FFM统一召回策略下,对应地转化成了新增特征的方式。

然后我们再说协同过滤这路召回。其实本质上也是将一路召回转化为新加特征的模式。我们以前提到过:本质上MF模型这种典型的协同过滤策略,是FM模型的一个特例,而FM模型又是FFM模型的特例,所以其实MF模型也是FFM模型的特例。MF可以看作在FM/FFM模型里只有User ID和Item ID这两类(Fields)特征的情形。意思是说,如果我们将user ID和Item ID作为特征放入FFM模型中进行训练,那么FFM模型本身就是包含了协同过滤的思想的。

当然,对于超大规模的网站,用户以亿计,物品可能也在千万级别,如果直接把ID引入特征可能会面临一些工程效率问题以及数据稀疏的问题。

FM要想把ID特征融入,应该是可行的,因为毕竟每个特征只需要学习一个k维大小特征向量,虽然ID数量大,但是总还是能接受。但是,如果是在FFM召回模型中融入ID特征,你会发现这里有个严重的问题:因为每个特征要包含(F-1)个k维特征向量,这对于FFM来说,ID特征会有超量的参数需要学习。比如假设F=101,k=10,UID有1亿个不同ID。这意味着光UID特征,就需要1000亿参数,这个……估计你会被吓退。所以,感觉FFM是很难把协同特征引入的,除非,事先通过其它方法对ID进行协同embedding编码,在FFM中直接使用,而不作为它的参数。否则,这在参数量以及存储量上来说,是很难做到的。

在具体实施统一多路召回的时候,可以沿着这个路径逐步替换线上的多路召回:先用FM/FFM模型替换一路召回,线上替换掉;再新加入某路特征,这样上线,就替换掉了两路召回;如此往复逐渐把每一路召回统一到一个模型里。这是比较稳的一种替换方案。当然如果你是个猛人,直接用完整的FFM召回模型一步替换掉线上的各路召回,也未尝不可。只要小流量AB测试做好也没啥。

FFM模型召回和排序一体化

我们在前文讲过,召回和排序各司其职。召回主要考虑泛化性并把候选物品集合数量降下来;排序则主要负责根据用户特征/物品特征/上下文特征对物品进行精准排名。

那么,我们现在可以来审视下本文开头提出的第二个问题了:FFM模型能否将常见的两阶段模型一体化?即是否能将实用化的推荐系统通过FFM召回模型简化为单阶段模型?意思是推荐系统是否能够只保留FFM召回这个模块,绕过后续的排序阶段,FFM召回模块按照得分排序直接作为推荐结果返回。我们可以这么做吗?

这取决于FFM召回模型是否能够一并把原先两阶段模型的两个职责都能承担下来。这句话的意思是说,FFM召回模型如果直接输出推荐结果,那么它的速度是否足够快?另外,它的精准程度是否可以跟两阶段模型相媲美?不会因为少了第二阶段的专门排序环节,而导致推荐效果变差?如果上面两个问题的答案都是肯定的,那么很明显FFM模型就能够将现有的两阶段推荐过程一体化。

在本系列的第一篇介绍FM召回模型的文章里,分析结论是:FM模型无论在推荐精准性,还是推荐速度方面,应该是能够同时承载两阶段模型的功能的。

那么FFM召回模型也可以担任类似的重任吗?我的答案是:It Depends。要看情况,跟应用的复杂情况有关。
如果从推荐的精准性角度考虑,假设我们能够把排序阶段的特征都引入FFM召回模型,那么应该能够得到等价的排序结果,这个很好理解,因为这等于你在召回部分复制了一个完全相同的FFM排序模型,类似于把排序功能前置到了召回阶段,所以推荐精准度基本等价。

看着好像这个事情是能做的是吧?其实不然。

在前文我们分析过如何用FFM模型来做召回模型,你会再次发现FFM模型的特性,就是太沉重。这种“沉重性”在召回阶段,表现为:用FFM模型打出来的用户Embedding长度太长,如果用户侧有M个特征域,物品侧有N个特征域,单个特征embedding向量大小为K,先不考虑上下文特征域,打出来的用户Embedding size=MNK。而这个长度是很容易失控的。

如果这个长度太长,意味着单机版本的Faiss速度肯定是跟不上的,那这个事情就得搁浅。而如果长度可以接受,Faiss速度OK,那么这事情就能成。所以关键是这个MNK到底有多长。于是问题转换成了:M、N和K,各自大约有多大?

我们拿一个工业级的CTR数据Criteo来说明(4500万数据,39个特征域,为了好计算,我们假设是40个特征域)。先说K,这是单个特征向量的大小,在Criteo这种工业级的数据规模下,实验证明,K=8效果最好。如果FFM模型只是用在召回阶段,后面还会再接上排序模型,也就是两阶段模式,k主观随意设置小点,比如2到4,问题不太大,因为推荐的精准性还可以依赖排序模型来保证。而现在我们对FFM模型的期待更多,希望它一步把排序也做掉,于是这个k就不能调小,就得是8,否则推荐效果受影响。

再来说M和N,我们假设仍然是这个数据集,它有40个域,我们再假设这些特征域在用户侧和物品侧平分,就是:M=N=20。

于是我们可以算出,如果用FFM模型来做Criteo数据的召回模型,打出来的用户侧embedding大小为:MNk=20208=3200。如果采用单机版本的Faiss做,这速度估计是跟不上的。如果采用上文讲的对用户侧embedding分布式切割的思路,比如把这个embedding切成10份,那么速度应该是能接受的,但是前面也说过,这可能对推荐精度有损失。

当然,我们也可以采取上文提到的(FM+FFM)嫁接版本来做,如果是这样,打出来的用户侧embedding size=MK或NK。对应Criteo数据来说,这个长度就是160,这对于Faiss来说,速度绝对不是问题,所以是可以充当一体化模型的。但是效果估计比不了原汁原味版本的FFM。

另外,如果排序包含ID特征,估计FFM召回模型也比较难以承担这个重任。

从上面这个实际例子来看,是否能使用FFM模型来做一体化推荐模型?这个问题的答案其实取决于任务复杂度,也就是特征域的个数,很明显结论是:如果特征域数量比较少,那么FFM模型是可行的,如果特征域数量比较多,则这事情做不了。除非,你愿意采取embedding分段切割模式损失精度,或者采取(FM+FFM)嫁接版本,而这也可能会损失精度。

当然,上面都是分析结果,并非实测,所以不能确定实际应用起来也能达到上述理论分析的效果。

Source