无采样的矩阵分解
最后更新于
最后更新于
很久以前,有个大佬说过,集体负责制就是不负责制,真正的负责是要落实到人的。 那么反过来企业级理解,无采样其实就是全采样的意思。
这里的采样,指的是负采样,所谓负,指的就是负样本,负样本是相对正样本而言的。
比如在一个购物网站,你购入了某个sku,或者在读书网站,你如痴如醉的读了某本书,那这就是平台期待你做的行为(购买、阅读、打赏、转发等),就是正。反之,点踩,拉黑,静音,视而不见等等这些就是负。
当然,用户行为有显式隐式之分,这里不作讨论。就正负样本中,正样本比较好收集,负样本(尤其是显式的)比较难收集。
难收集?难收集就不要收集咯!
所有用户没发生过行为的,全都是负样本。然后从里面随机采样,为每个正样本拉郎配一些负样本,就可以得到训练数据集。
在这个数据集上应用Ranknet(参见这一篇:Learning To Rank 之 RankNet),就可以训练出一个像模像样的推荐系统。
可是采样也是个技术活,用户明显表示不喜欢的,采样权重是不是得高点?具体参看这一篇文章: 负样本为王:评Facebook的向量化召回算法,这篇文章很好玩,我在实践中发现了和Facebook一样的结论,就是不能只用曝光未点击作为负样本,同时对负样本进行赋权也很重要。
采样这么难采,怎么办?
难采?难采就不要采咯!(梅开二度)
不采,就是全采,色即是空,这就是这篇文章的重点。我最早读到这个思路是在 Efficient Non-Sampling Factorization Machines for OptimalContext-Aware Recommendation 里面,后来顺藤摸瓜找到了 Efficient Neural Matrix Factorization without Sampling for Recommendation 。但是我意识到,这个Loss其实不止针对FM,任何可以把用户和物品变成向量,最后用内积评分的模型都可以采用这个Loss。
但是压缩,意味着新的矩阵要像老的矩阵,怎么评价像不像呢?我们用L2 Loss:
不过有困难要上,没有困难制造困难也要上,我们就干脆制造一个可以展开求和的条件。
看上去好复杂,其实计算复杂度一点都不高,只是正样本而已啦!
这里我给出我的Pytorch的实现,需要说明的是,这个代码我没有验证过,因为这是我从完整版简化而来,完整版里带了真负样本的计算,还有一些业务逻辑,我都给去掉了。
最后,作者的模型里是有h(最后的一层线性层)的,但是从模型看这个h完全就是冗余的,实测效果也是又没有h都没差咯。
但是在我实战的时候,结果显示这个Loss没有优于使用Ranknet Loss的LightGCN,我认为原因在于:我根据曝光强度,对负样本进行了分级,这个Loss做不到。
这里还需要再展开一点,我其实尝试魔改了原文的公式,把负样本加入到正样本中去,只是权重为负数,但是负样本太多了(几乎是正样本的100倍!),加上这个Loss不能分Batch训练,考虑负样本的情况下计算效率非常堪忧,丢掉部分负样本效果又下去了。 相比之下,Ranknet可以分Batch,可以在训练的时候实时采样生成样本对,计算效率和最后结果都要略微优于非采样Loss。
另外,这里不像Ranknet,正负样本是自动均衡的,怎么配平正负样本是个玄学问题,并不是正负样本权重和是1:1就好的,这个建议交给超参数调整去做(我的实验结果似乎是1:10比较好,实验做的太早了,忘了……)。
还有一点最重要的,这个Loss不支持引入Context Feature,你只能干巴巴的使用User Embedding和Item Embedding。
以上问题,我想也是实战中使用人数不多的原因。
不论如何,虽然形式简单,但是这个Loss数学上的优美仍然吸引到了我,类似的思路,今天用不到,以后遇到分解矩阵的问题或者只有正样本+无其它特征的推荐系统(比如召回模型,我这次不需要做召回所以没用上)的时候可以试一下。
进入正题,我们考虑用户对于系统有一些正样本 ,三元组里分别是用户,物品和正样本的权重。其实这可以表达为一个稀疏矩阵,不放记作 ,横坐标为用户,纵坐标为物品,格子中就是权重,我们以往的思路就是分解这个矩阵,在分解为用户向量组和物品向量组的过程中,找到高维稀疏矩阵的低维稠密表达。
这个“找”的过程,其实就是(有损)压缩。而压缩的本质,就是找共同点,找重复处。 比如,我把abcabcabcabc
压缩成{abc}4,就是找到了共同之处。那么用户行为矩阵 的压缩,就是找到了用户和物品的“群组”,物以类聚,人以群分。
这个 ,就是我们压缩以后的结果,但是我们先不急着展开这个表达式,我们先展开原本的式子:
这一步上过初中应该都能做出来,然后我们一个一个拆: ,第一项的是常数,常数求导是0,所以直接扔掉(P.S. 如果对Loss的真实数值有执念的,这个是可以算出来的,正样本权值平方求和嘛)。 第二项 是两个矩阵Element-wise相乘,只针对正样本是1,负样本都是0,所以可以简化为: 最后一项是个硬骨头,且拆分以后会利用第二项与之合并,下面细说。
看到 会想起什么?考虑到 (P,Q分别是训练中的User和Item的Embedding),我们肯定是展开来,利用矩阵乘法的结合律求 ,但是不行,因为 不是常数。
我们首先把想办法拆成正样本负样本两块,简写为, 简写为,其中,是给所有负样本赋的统一的值。
我们可以看到,正样本部分 我们就可以和第二项合并到一起了,但是剩下的部分仍然不能方便的求和,因为只有负样本的,这里,我们凭空再变一对出来,其中,前面的部分用于和剩余的负样本部分结合,第二部分加入到第二项中去。
我们梳理一下,第一项被我们轻松干掉了,第三项最后变成了,我们用矩阵乘法运算的结合律轻松求出,第二项变成了:
这个Loss很有禅意,形式很简洁,论文中实验结果表示很有效。如果抛开严谨的数学推导和一些数学技巧,我们可以粗略的看到:其实整个公式相当于先把正样本也当成负样本求和,然后再给圆回来的操作,这么理解就可以直观解释 的意义。