Datawhale十月组队学习_推荐系统3

推荐系统|矩阵分解和FM:


1.核心思想


在这里插入图片描述矩阵分解模型其实就是在想办法基于这个评分矩阵去找到上面例子中的那两个矩阵, 也就是用户兴趣和物品的隐向量表达, 然后就把这个评分矩阵分解成Q和P两个矩阵乘积的形式, 这时候就可以基于这两个矩阵去预测某个用户对某个物品的评分了。 然后基于这个评分去进行推荐。这就是矩阵分解算法的原理。

2.基本原理


在这里插入图片描述通过分解协同过滤的共现矩阵来得到用户和物品的隐向量

谈到矩阵分解, 最常用的方法是特征值分解(EVD)或者奇异值分解(SVD)。
(1)首先是EVD, 它要求分解的矩阵是方阵, 显然用户-物品矩阵不满足这个要求, 而传统的SVD分解, 会要求原始矩阵是稠密的, 而我们这里的这种矩阵一般情况下是非常稀疏的, 如果想用奇异值分解, 就必须对缺失的元素进行填充, 而一旦补全, 空间复杂度就会非常高, 且补的不一定对。 然后就是SVD分解计算复杂度非常高, 而我们的用户-物品矩阵非常大, 所以基本上无法使用。
(2)2006年的Netflix Prize之后, Simon Funk公布了一个矩阵分解算法叫做Funk-SVD, 后来被Netflix Prize的冠军Koren称为Latent Factor Model(LFM)。 Funk-SVD的思想很简单: 把求解上面两个矩阵的参数问题转换成一个最优化问题,可以通过训练集里面的观察值利用最小化来学习用户矩阵和物品矩阵。

引入3个系数
1.训练集中所有记录的评分的全局平均数。在不同网站中,因为网站定位和销售物品不同, 网站的整体评分分布也会显示差异。比如有的网站中用户就喜欢打高分,有的网站中用户就喜欢打低分。而全局平均数可以表示网站本身对用户评分的影响。

2.用户偏差系数,可以使用用户 给出的所有评分的均值,也可以当做训练参数。这一项表示了用户的评分习惯中和物品没有关系的那种因素。比如有些用户比较苛刻,对什么东西要求很高,那么他评分就会偏低,而有些用户比较宽容, 对什么东西都觉得不错,那么评分就偏高

3.物品偏差系数,可以使用物品 收到的所有评分的均值,也可以当做训练参数。这一项表示了物品接受的评分中和用户没有关系的因素。比如有些物品本身质量就很高,因此获得的评分相对比较高,有的物品本身质量很差,因此获得的评分相对较低。

3.编程实现


在这里插入图片描述

1.首先,它会先初始化用户矩阵P和物品矩阵Q,P的维度是 [users_num, F] , Q的维度是 [item_nums, F],这个F是隐向量的维度。也就是把通过隐向量的方式把用户的兴趣和F的特点关联了起来。初始化这两个矩阵的方式很多,但根据经验,随机数需要和1/sqrt(F)成正比。下面代码中会发现。

2.有了两个矩阵之后,我就可以根据用户已经打分的数据去更新参数,这就是训练模型的过程,方法很简单,就是遍历用户,对于每个用户,遍历它打分的物品,这样就拿到了该用户和物品的隐向量,然后两者相乘加上偏置就是预测的评分,这时候与真实评分有个差距,根据上面的梯度下降就可以进行参数的更新。

这样训练完之后,我们就可以得到用户Alice和物品5的隐向量,根据这个就可以预测Alice对物品5的打分。下面的代码的逻辑就是上面这两步,这里使用带有偏置项和正则项的那个SVD算法

import math
import random
class SVD:
    def __init__(self,rating_data,F = 5,alpha = 0.1,lmbda = 0.1,max_iter = 100):
        # self.变量 好像可以将变量变为全局变量
        self.F = F        # 表示隐向量的维度
        self.P = dict()   # 用户矩阵P 大小是[users_num,F]
        self.Q = dict()   # 物品矩阵Q  大小是[item_num, F]
        self.bu = dict()  # 用户偏差系数
        self.bi = dict()  # 物品偏差系数
        self.mu = 0.0     # 全局平均数
        self.alpha = alpha # 学习率
        self.lmbda = lmbda # 正则项系数
        self.max_iter = max_iter         # 最大迭代次数
        self.rating_data = rating_data   # 评分矩阵
        '''
        初始化矩阵P和Q的方法很多,一般用随机数填充,但随机数的大小有讲究,
        根据经验,随机数需要和 1/sqrt(F)成正比
        '''
        cnt = 0  # 统计总的打分数,初始化mu用
        for user,items in self.rating_data.items():
            self.P[user] = [random.random() / math.sqrt(self.F) for x in range(0,F)]
            self.bu[user] = 0
            cnt += len(items)
            for item,rating in items.items():
                if item not in self.Q:
                    self.Q[item] = [random.random() / math.sqrt(self.F) for x in range(0,F)]
                    self.bi[item] = 0
        self.mu /= cnt

    '''
    有了矩阵之后,就可以进行训练,使用随机梯度下降的方法训练参数P和Q
    '''
    def train(self):
        for step in range(self.max_iter):
            for user,items in self.rating_data.items():
                for item,rui in items.items():
                    rhat_ui = self.predict(user,item) # 得到预测评分
                    # 计算误差
                    e_ui = rui - rhat_ui

                    self.bu[user] += self.alpha * (e_ui - self.lmbda * self.bu[user])
                    self.bi[item] += self.alpha * (e_ui - self.lmbda * self.bi[item] )

                    # 计算梯度下降更新梯度
                    for k in range(0,self.F):
                        self.P[user][k] += self.alpha * (e_ui*self.Q[item][k] - self.lmbda*self.P[user][k])
                        self.Q[item][k] += self.alpha * (e_ui*self.P[user][k] - self.lmbda*self.P[item][k])
                        self.alpha *= 0.1 # 每次迭代步长要逐步缩小

    '''
    预测user对item的评分,这里没有使用向量的形式
    '''
    def predict(self,user,item):
        # 计算所得评分 + 用户偏差系数 + 物品偏差系数 + 全局平均数
        return sum(self.P[user][f]*self.Q[item][f] for f in range(0,self.F)) + self.bu[user] + self.bi[item] + self.mu



# 采用字典的形式定义数据集,实际数据非常稀疏,因此使用字典形式而不是dataframe形式
def loadData():
    rating_data = {1:{'A':5,'B':3,'C':4,'D':4},
                   2:{'A':3,'B':1,'C':2,'D':3,'E':3},
                   3: {'A': 4, 'B': 3, 'C': 4, 'D': 3, 'E': 5},
                   4: {'A': 3, 'B': 3, 'C': 1, 'D': 5, 'E': 4},
                   5: {'A': 1, 'B': 5, 'C': 5, 'D': 2, 'E': 1}
                   }
    return rating_data

# 接下来是训练和预测
rating_data = loadData()
basi_svd = SVD(rating_data)
basi_svd.train()
for item in ['E']:
    print(item,basi_svd.predict(1,item))


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arya's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值