传统推荐算法(三) 不一般的SVD++

写在前面

论文地址:https://www.cs.rochester.edu/twiki/pub/Main/HarpSeminar/Factorization_Meets_the_Neighborhood-_a_Multifaceted_Collaborative_Filtering_Model.pdf

SVD++

在LFM中,只考虑了用户特征向量和物品特征向量(显示反馈)和偏置向量,在SVD++中,进一步考虑了用户对其有过浏览/评分行为的商品的隐式反馈.
20190704203618.png20190704214140.png
其中Nu是用户有过浏览等行为的物品组合,没有隐性反馈数据(比如浏览等)时,Nu和用户评过分的物品集合Ru就是重复的,此时一个数据用两次,给人两种不同模型的组合的感觉.

隐性反馈的理解

说说我自己的理解吧,感觉不像之前的LFM理解那么透彻,欢迎交流.

隐性反馈这部分,就是使用物品表示用户,可以看成是根据物品对用户进行简单画像。

20190704215117.png

某个电影本身就可以代表某种兴趣,看过这个电影代表这个人对某种电影感兴趣,或者说对这个电影的某些特质感兴趣.因此,不仅仅是显性评分,用户的隐性行为,本身就可以反映用户的喜好,但这种反映是隐性的,是一种弱反映.SVD++把隐性行为和显性行为结合起来,充分利用了现有的信息来获得用户的特征表达.

再看看[1]中的这张图:
20190704203618.png
一个物品的隐性反馈,是通过所有用户的信息来训练得到的.从这里我们可以看出,隐性反馈的用户特征表达,不像显性评分那样去尽可能拟合某个用户的评分,而是考虑了整个用户群体得到的,这就从整体把握了这个物品反馈。

从这个角度看,如果说显性评分的训练可能造成过拟合的问题,那么将和用户有关的隐性反馈综合起来后,就对用户得到了更好的特征表达,一定程度上避免了显性反馈的过拟合的问题;同时,当用户较多而物品较稀疏时,训练出的pu可能不够充分,但此时有了训练较成分的隐性反馈,可以一定程度上解决显性反馈的欠拟合问题。

这和[2]在多模态中用到的弱配对有一些相同的效果,利用图片和对应的文本进行分类的过程中,如果仅考虑一张图片和一条文本为一个样本,当然也行,但是过于关注单个样本很容易过拟合.但是如果将几张同类图片和对应的文本看成一个训练样本,这样训练出的模型更加具有泛化性,因为这种模型考虑了一个类别所对应的更多的图像和文本组合,可以get到更加通用的特征,而不仅仅考虑原先的单个图像和文本特征之间的关系.

代码实现

我们重点分析一下如何实现论文中SVD++的隐性反馈

def build_graph(self, mu, implicit_feedback):
    mu = super(SVDPP, self).create_constants(mu)
    self.users, self.items, self.ratings = super(SVDPP, self).create_placeholders()
    
    N = self.create_implicit_feedback(implicit_feedback)

    p_u, b_u, y_u = self.create_user_terms(self.users, N)
    q_i, b_i = self.create_item_terms(self.items)

    self.pred = self.create_prediction(mu, b_u, b_i, p_u, q_i, y_u)

这里的super(SVDPP, self).xxx调用父类方法是python2中的写法,python3可以直接使用super().xxx.感兴趣可以看看[4][5].

mu是y的平均值,就是μ;y_u就是和用户u有关的隐性反馈.看一下y_u怎么来的:

def create_user_terms(self, users, N):
        num_users = self.num_users
        num_items = self.num_items
        num_factors = self.num_factors
        
        p_u, b_u = super(SVDPP, self).create_user_terms(users)
        
        with tf.variable_scope('user'):
            implicit_feedback_embeddings = tf.get_variable(
                    name='implicit_feedback_embeddings',
                    shape=[num_items, num_factors],
                    initializer=tf.zeros_initializer(),
                    regularizer=tf.contrib.layers.l2_regularizer(self.reg_y_u))

        y_u = tf.gather(
                tf.nn.embedding_lookup_sparse(
                        implicit_feedback_embeddings,
                        N,
                        sp_weights=None,
                        combiner='sqrtn'),
                users,
                name='y_u')

y_u是从implicit_feedback_embeddings中通过N进行lookup搜寻得到的,implicit_feedback_embeddings的shape=[num_items, num_factors],就是所有物品的隐性反馈;N记录了所有用户所对应的物品id,tf.nn.embedding_lookup_sparse执行过后,就得到了所有用户的隐性反馈,tf.gather通过users找到其隐性反馈.

里面的细节combiner='sqrtn’需要注意,look_up可以通过id找到每个用户的物品的隐性反馈向量,最后通过sqrtn的方式来组合成这个用户的隐性反馈,"sqrtn" is the weighted sum divided by the square root of the sum of the squares of the weights. 这里我们不设权重,那就直接相加除以N(u)的平方根,也就是:
20190705094553.png
然后训练过程就不难了:

def run_train(self, x, y, epoches, batch_size, val_data):
        train_gen = BatchGenerator(x, y, batch_size=batch_size)
        steps_per_epoch = np.ceil(train_gen.length / batch_size).astype(int)
        
        self.sess.run(tf.global_variables_initializer())
        
        for i in range (1, epoches+1):
            print('Epoch {} / {}'.format(i, epoches))
            pbar = utils.Progbar(steps_per_epoch)
            print('stpes_per_epoch', steps_per_epoch)
            for step, batch in enumerate(train_gen.next(), start=1):
                users = batch[0][:, 0]
                items = batch[0][:, 1]
                ratings = batch[1]
                
                self.sess.run(self.optimizer,
                              feed_dict={
                                      self.users: users,
                                      self.items: items,
                                      self.ratings: ratings})
                pred = self.predict(batch[0])
                
                update_values = [
                        ('rmse', rmse(ratings, pred)),
                        ('mae', mae(ratings, pred))]
                
                if(val_data is not None and step == steps_per_epoch):
                    valid_x, valid_y = val_data
                    valid_pred = self.predict(valid_x)
                    update_values += [
                            ('val_rmse', rmse(valid_y, valid_pred)),
                            ('val_mae', mae(valid_y, valid_pred))]
                    
                pbar.update(step, values=update_values)

这里使用了keras中的可视化工具Progbar,还挺好用的,可以实时显示训练过程:
20190705095941.png

原python2代码:https://github.com/WindQAQ/tf-recsys
修改后代码:https://github.com/wyl6/Recommender-Systems-Samples/tree/master/RecSys Traditional/MF/SVD%2B%2B

参考

[1] https://www.cs.rochester.edu/twiki/pub/Main/HarpSeminar/Factorization_Meets_the_Neighborhood-_a_Multifaceted_Collaborative_Filtering_Model.pdf
[2] https://link.springer.com/content/pdf/10.1007%2Fs00500-018-3108-y.pdf
[3] https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=7917316
[4] https://www.runoob.com/python/python-func-super.html
[5] http://funhacks.net/explore-python/Class/super.html


更多精彩内容请移步公众号:推荐算法工程师

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值