深度学习在序列化推荐中的应用 (1)-GRU4REC 以及扩展

文章来源 http://kubicode.me/2018/09/19/Deep+Learning/GRU4REC-Session-Based-Recommendation/

前言

用户在互联网应用上的绝大部分的行为都是可以用一个序列来表示,比如购物、听音乐、看 feed 流等,用式子来表示就是:

{x_1,x_2,x_3,..,x_N} -> x_{N+1} $$ 因此对于这个序列如何建模来获取整个用户的意图行为至关重要,而之前传统的ML只能基于统计或者经验的方式来尽量抽取这些序列信息,并无法hold整个序列,16年提出的`GRU4REC`利用`RNN-Based`对用户序列进行建模并且取得了不错的效果,同时也会有一些研究对于`GRU4REC`做了不少改进和扩展,本文主要对`GRU4REC`以及扩展做一些简答的自我了解和记录。 ## GRU4REC `GRU4REC`是Session信息和GRU结合起来完成了推荐,他给定的场景是: > 用户在我们的应用上有一段行为`Session`(比如说点击item的需求),然后在于该Session信息来预测接下来可能会发生点击的item,而这笔的Session信息主要使用`GRU`模型来进行刻画: [![](https://img.6aiq.com/e/3ce49f5848c64719a4663ebe639e739c.png)](http://kubicode.me/img/Session-Based-Recommendation/gru4rec_arch.png) 1. 这边第一步的输入是用户的行为序列:$ [x_1,x_2,x_3,..,x_N]$ 2. 这些行为序列可以接下来使用两种Embedidng表示,一种是`One-Hot`方式,另一种是在`One-Hot`接下来过一个Embedding层 3. 将所有的输入进行向量化表示之后,会过若干层的GRU(就是比较核心的序列化建模了) 4. 完成序列化建模之后再进行一个`Feedforward`的网络转换 5. 最终对下一个目标进行预测,这边的目标其实就是$X_{N+1}$ > (作者说这种方式性能好,但是我到觉得这种场景下`One-Hot`不是很合适,`One-Hot`在这边他的DIM会巨大,并且会特别的稀疏,可能还是查表的来的好) 其实`GRU4REC`的整个思路还是很清晰,模型也很简单,但是该算法中比较重要的应该是他的加速优化和LOSS的选择可能会有比较大的参考价值意义: 为了能提高训练的效率,采用两种策略来进行训练的优化: 1. 使用`Mini-Batch`来进行训练: [![](https://img.6aiq.com/e/66892e2556e6467e991d3b80b0332b64.png)](http://kubicode.me/img/Session-Based-Recommendation/gru4rec_minibatch.png) 因为用户行为的Session有长有短,并且他的差异性很大,传统的滑窗方式来构建训练数据并不适用,他这里用的策略是将不同的`Session`给拼接了起来,在同一个序列中如果遇到下一次Session时,会将GRU中的向量参数给重新初始化掉,因为这边GRU是对Step进行预测,所以在序列中间直接初始化掉问题也不大,这样还可以提升数据的利用率,会比简单`PADDING`的方式更加的合适。 2. 取巧的训练数据采样: 原始的模型中是需要过softmax对于每个item都计算才能对目标的item进行训练,因为item的维度非常高,所以这里的计算量是超级大的。作者在这里比较机智的在目标的样本中根据`热门`程度进行了采样,采样完成之后将同一个`mini-batch`中但是是其他`Session`的next-item作为负样本。用这些正负样本来训练整个神经网络。下面这个图对于采样非常形象了: [![](https://img.6aiq.com/e/7e381d0fad174cf5b242e803e635bc91.png)](http://kubicode.me/img/Session-Based-Recommendation/gru4rec_sampling.png) 因此这个模型现在已经转为对正负样本的一个`0-1`分类的问题,而且推荐里面,并不存在绝对的正负样本,用户也可能会对多个item存在偏好,所以这边比较合适`Loss Function`就是用`Pair-Wise`的模式了(只需要 正样本的score大于负样本即可): 1. `BPR(Bayesian Personalized Ranking)`:这是一种基于矩阵分解的损失函数,他的式子是: ![9c6911dea21b4e58b15d9fef5e419c04.png](https://img.6aiq.com/file/2018/11/9c6911dea21b4e58b15d9fef5e419c04.png) $N_s$是样本量的大小,$\hat{r}_{s,i}$表示正样本的分数,$\hat{r}_{s,j}$表示负样本的分数。 2. `TOP1`:这是种基于正则化方式的损失函数 ![519c1186e4114386becb8aeea2972b66.png](https://img.6aiq.com/file/2018/11/519c1186e4114386becb8aeea2972b66.png) 这种方式可以将$\hat{r}_{s,i}$的分数计算的更高,但是他同是也会是负样本,所以这边加了二范数来压制$\hat{r}$作为负样本时的分数 `GRU4REC`的实验结果也是蛮简单的,Baseline的实验不在这个表中,数据后面跟着的涨幅就是和Baseline的对比: [![](https://img.6aiq.com/e/686e2dbc53d447409b1aae0c69af3384.png)](http://kubicode.me/img/Session-Based-Recommendation/gru4rec_exp.png) 这边显示的也是`BPR`和`TOP1`这两种LOSS的效果会明显好于传统的交叉熵. `GRU4REC`是较早的将序列行为和GRU进行结合,其中`LOSS`这块的构建还是非常值得借鉴的。 > 该作者还开放了源码[https://github.com/hidasib/GRU4Rec](https://github.com/hidasib/GRU4Rec) ## GRU4REC-Sampling > 其中`GRU4REC-Sampling`和`GRU4REC`是同一个作者 ^_^ `GRU4REC-Sampling`也是在基于`GRU4REC`上的缺陷提出了额外的`Sampling`和新的`Loss Function` 作者认为`GRU4REC`存在下面三种局限: 1. `BatchSize`一般都是比较小的,在总样本较多时,如果采样少的话,分数比较高的负样本被采样进来的概率就偏少了(这里高分数要用于下面的Loss) 2. `BatchSize`会影响运行速度,但是由于设计的是`Mini-Batch`并行的方式,所以增加`BatchSize`也不会对速度有多大的影响 3. 虽然`GRU4REC`用的是根据热度采样,但是实际中全根据热度也不一定适应所有数据集 所以在`GRU4REC-Sampling`中又进行了额外的采样:同样是在`Mini-Batch`中进行采样,采样时根据这个公式$supp_i^\alpha$,而这边的$a$是一个`0~1`的值,如果$a=0$表示均匀采样,那么$a=1$为完全的热门采样。 另外`GRU4REC`中的`BPR`和`TOP1`会存在梯度的消失问题,因此作者设计了一种新的损失函数希望来最大化正样本的分数: ![8734ff1ae76f48f7b4984ccf625bf280.png](https://img.6aiq.com/file/2018/11/8734ff1ae76f48f7b4984ccf625bf280.png) 从这儿可以看出,新的损失函数是对`Max-Score`的负样本做pair,但是这种是不可求导了,所以作者用了一种近似的方式来实现,刚刚对`Max-Score`做负样本的方式可以转为`Score`越大,则`Loss`中的权重也越大,而这个权重可以用归一化的`softmax`来表示: ![186a1a67ab914409a428abea42684b0a.png](https://img.6aiq.com/file/2018/11/186a1a67ab914409a428abea42684b0a.png) 有了每个样本的权重表示之后,原先的`Loss Function`可以更改为: 1. `TOP1-MAX`: ![1fba33e970cb49fea9d7c318ef99f66f.png](https://img.6aiq.com/file/2018/11/1fba33e970cb49fea9d7c318ef99f66f.png) 2. `PR-max`: ![fddc4524b6fe45a385017efed331d55f.png](https://img.6aiq.com/file/2018/11/fddc4524b6fe45a385017efed331d55f.png) 对比一下`GRU4REC`中的`Loss Function`,其实就是额外增加了一个$s_j$的权重值。 [![](https://img.6aiq.com/e/0476ab7f9c3748b8b6c4ce58968f6718.png)](http://kubicode.me/img/Session-Based-Recommendation/gru4rec_sampling_exp.png) 看下实验对比,额外的`Sampling`和新的`Loss Function`都还是有极大的提升的,惊呆。 > 我个人感觉`Sampling`起这么大的作用应该是采样之后样本不足了,这是一个训练时间和模型性能上的权衡,那么我如果不采样是不是效果就更好了-_-!! ## GRU4REC-DWell `GRU4REC-DWell`也是基于`GRU4REC`的一个简单的改进,其中`GRU4REC`已经证明在时序的推荐中序列化的建模非常有用。 另外作者认为在用户行为序列中,每个item的停留时间是非常重要的一个特征,而之前的`GRU4REC`算法只是用于简单的交互行为来构建样本,所以`GRU4REC-DWell`主要是很巧妙将用户在序列item上的停留时间和GRU4REC`结合了起来:` [![](https://img.6aiq.com/e/6af7c729b32142bca403579799fe563e.png)](http://kubicode.me/img/Session-Based-Recommendation/gru4rec_dwell.png) 这里主要的Idea就是在原始的用户行为中,作者根据item上面的停留时间根据阈值进行切片,如果停留时间长的可能会有很多个切片,每个切片都作为一个新的行为项: 给定一个行为序列的集合$X=\{x_1,x_2,…,x_n\}$,每一个$x_i$对应的停留时间为$dt_i$,其中$t$为切片的阀值,则$x_i$可以分割的切片为$d_t/t + 1$。 也就是如上图所示,$i_2,1$就由于停留时间较久,所以分割成了三个切片。然后其他的就如原始的`GRU4REC`一样了,但是作者在做对比实验室加入了`GRU4REC-SAMPLING`进行了一起对比: [![](https://img.6aiq.com/e/fdf91e1d213c4093a7df6fa16538d9da.png)](http://kubicode.me/img/Session-Based-Recommendation/gru4rec_dwell_exp.png) 实验中显示,停留时间信息的加入对于模型的作用是非常巨大的。 ## HRNN 用户往往会存在多段不连续的Session(比如逛淘宝时,早上公交逛一次,中午午睡时逛一次,晚上睡前逛一次,这样就有三段Session序列,每一段内部是连续的),而之前的模型都是将这些Session行为都是独立训练的,文本中作者认为同一用户的不同Session间是有关联的,建模每一段Session可以发现用户的衍化。 所以作者提出了一种层次化的RNN序列建模,在每一段的`Session-Level`内部使用RNN建模的同时,会有一个`User-Level`的RNN来建模当前用户跨Session的行为,而`User-Level`的RNN的输入就是每一段`Session-Level`的final state。 用户的所用行为表示为 $$C^u = \{ S_1^u,S_2^u,…S_{M_u}^u \}$$ $S_m^u$代表一次完整的Session,其中$S_m^u$代表对应`Session`的`Representations`(也就是最终一个final state),则`User`级别的`Representations`为 $$ c_m = GRU_{usr}(s_m,c_{m-1}),m = 1,…,M_u$$ 所以这边`HRNN`的整个层次结构如图所示: [![](https://img.6aiq.com/e/89289944a8804ca79cd74db9a5bce711.png)](http://kubicode.me/img/Session-Based-Recommendation/hrnn_arch.png) * 上面一层代表`Session-Level`的RNN,输入的是item,会对`next basket`进行预测,同时输出`final state` * 下面一层代表`User-Level`的RNN,输入的是`Session-Level`的`final state`,用户维护当前用户在整个应用的行为建模,并且会将当前Session的state输出作为下一次Session的`init state` [![](https://img.6aiq.com/e/c4cdfc6e167244afab4869419290463c.png)](http://kubicode.me/img/Session-Based-Recommendation/hrnn_exp.png) 主要对比的是原生的`GRU4REC`,性能大约有10%左右的提升,但是用的数据和`GRU4REC-Sampling`以及`GRU4REC-DWell`的不一样,感觉没有他们的提升多,并且在现实过程中,对于`Session`的划分也是需要很多的`trick`啊。 ## 总结 其实`GRU4REC`在DL中是一个非常`straight-forward`的框架,但是他的厉害之处就是设计了`Mini-Batch`和`Sampling`将整个模型跑了起来并且起到了一定的效果,另外后面的几个改进中停留时间的改进以及层次的`Session`还是比较不错,并且可实用性高一些。 ## 参考 1. Hidasi, Balázs, et al. “Session-based recommendations with recurrent neural networks.” arXiv preprint arXiv:1511.06939 (2015). 2. Hidasi, Balázs, and Alexandros Karatzoglou. “Recurrent neural networks with top-k gains for session-based recommendations.” arXiv preprint arXiv:1706.03847 (2017). 3. Bogina, Veronika, and Tsvi Kuflik. “Incorporating dwell time in session-based recommendations with recurrent Neural networks.” CEUR Workshop Proceedings. Vol. 1922\. 2017. 4. Quadrana, Massimo, et al. “Personalizing session-based recommendations with hierarchical recurrent neural networks.” Proceedings of the Eleventh ACM Conference on Recommender Systems. ACM, 2017.

本文地址:https://www.6aiq.com/article/1541399147565
本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出