大数据——协同过滤推荐算法:矩阵分解

news/2024/5/20 0:31:30 标签: 大数据, 推荐算法, 矩阵

矩阵分解的方法也分为很多种:SVD、LFM、BiasSVD和SVD++。

  • Traditional SVD
    一般SVD矩阵分解指的是SVD奇异值分解,将矩阵分解成三个相乘矩阵,中间矩阵就是奇异值矩阵。SVD分解的前提是矩阵是稠密的,现实场景中数据都是稀疏的,无法使用SVD,需要均值或者其他方法来填充,会对数据造成一定影响。具体公式如下:
    M m × n = U m × k ∑ k × k K k × n T M_{m×n} = U_{m×k}\sum_{k×k}K^T_{k×n} Mm×n=Um×kk×kKk×nT
  • FunkSVD(LFM)
    FunkSVD是最原始的LFM模型,它将矩阵分解为2个矩阵:用户-隐含特征矩阵,物品-隐含特征矩阵,一般这个过程跟线性回归类似。以下公式为矩阵分解后的损失函数,p为用户-隐含特征矩阵,q为物品-隐含特征矩阵,我们通过求损失函数的最小值来计算p和q,后面部分为L2范式,防止过拟合。下面公式用随机梯度下降来求最优解。
    m i n q p ∑ ( u , i ) ∈ R ( r u i − q i T p u ) 2 + λ ( ∣ ∣ q i ∣ ∣ 2 + ∣ ∣ p u ∣ ∣ 2 ) \mathop{min}\limits_{qp}\sum_{(u,i)∈R}(r_{ui}-q^T_ip_u)^2+\lambda(||q_i||^2+||p_u||^2) qpmin(u,i)R(ruiqiTpu)2+λ(∣∣qi2+∣∣pu2)
  • BiasSVD
    BiasSVD就是在FunkSVD的基础上加上Baseline基准预测的偏置项,具体公式如下:
    m i n q p ∑ ( u , i ) ∈ R ( r u i − u − b u − b i − q i T p u ) 2 + λ ( ∣ ∣ q i ∣ ∣ 2 + ∣ ∣ p u ∣ ∣ 2 + ∣ ∣ b u ∣ ∣ 2 + ∣ ∣ b i ∣ ∣ 2 ) \mathop{min}\limits_{qp}\sum_{(u,i)∈R}(r_{ui}-u-b_u-b_i-q^T_ip_u)^2+\lambda(||q_i||^2+||p_u||^2+||b_u||^2+||b_i||^2) qpmin(u,i)R(ruiububiqiTpu)2+λ(∣∣qi2+∣∣pu2+∣∣bu2+∣∣bi2)
  • SVD++
    改进的BiasSVD,即在BiasSVD的基础上再加上用户的隐式反馈信息。这里不做多介绍。

1 LFM原理解析

LFM隐语义模型的核心思想是通过隐含特征联系用户和物品。
在这里插入图片描述

P是用户和隐含特征矩阵,这里表示三个隐含特征;
Q是隐含特征和物品矩阵
Q是User-Item,由P*Q得来。

我们利用矩阵分解技术,将User-Item评分矩阵分解为P和Q的矩阵,然后利用P*Q还原评分矩阵。其中 R 11 = P 1 , k ⃗ ⋅ Q k , 1 ⃗ R_{11} = \vec{P_{1,k}}·\vec{Q_{k,1}} R11=P1,k Qk,1
所以我们的评分为:
r u i ^ = p u , k ⃗ ⋅ q i , k ⃗ = ∑ k = 1 k p u k q i k \hat{r_{ui}} = \vec{p_{u,k}}·\vec{q_{i,k}} = \sum_{k=1}^{k}p_{uk}q_{ik} rui^=pu,k qi,k =k=1kpukqik

损失函数

C o s t = ∑ u , i ∈ R ( r u i − r ^ u i ) 2 = ∑ u , i ∈ R ( r u i − ∑ k = 1 k p u k q i k ) 2 Cost = \sum_{u,i∈R}(r_{ui}-\hat r_{ui})^2 = \sum_{u,i∈R}(r_{ui}-\sum_{k=1}^{k}p_{uk}q_{ik})^2 Cost=u,iR(ruir^ui)2=u,iR(ruik=1kpukqik)2
加入L2范式防止过拟合,如下:
C o s t = ∑ u , i ∈ R ( r u i − ∑ k = 1 k p u k q i k ) 2 + λ ( ∑ U p u k 2 + ∑ I q i k 2 ) Cost = \sum_{u,i∈R}(r_{ui}-\sum_{k=1}^{k}p_{uk}q_{ik})^2 +\lambda(\sum_U p^2_{uk}+\sum_I q^2_{ik}) Cost=u,iR(ruik=1kpukqik)2+λ(Upuk2+Iqik2)
对损失函数求偏导可得:
∂ ∂ p u k C o s t = 2 ∑ u , i ∈ R ( r u i − ∑ k = 1 k p u k q i k ) ( − q i k ) + 2 λ p u k \frac{∂}{∂p_{uk}}Cost = 2\sum_{u,i∈R}(r_{ui}-\sum_{k=1}^{k}p_{uk}q_{ik})(-q_{ik}) + 2\lambda p_{uk} pukCost=2u,iR(ruik=1kpukqik)(qik)+2λpuk
∂ ∂ q i k C o s t = 2 ∑ u , i ∈ R ( r u i − ∑ k = 1 k p u k q i k ) ( − p u k ) + 2 λ q i k \frac{∂}{∂q_{ik}}Cost = 2\sum_{u,i∈R}(r_{ui}-\sum_{k=1}^{k}p_{uk}q_{ik})(-p_{uk}) + 2\lambda q_{ik} qikCost=2u,iR(ruik=1kpukqik)(puk)+2λqik

随机梯度下降法优化

根据偏导可更新梯度下降参数:
p u k : = p u k + α [ ∑ u , i ∈ R ( r u i − ∑ k = 1 k p u k q i k ) ( q i k ) − λ 1 p u k ] p_{uk}:=p_{uk} + \alpha[\sum_{u,i∈R}(r_{ui}-\sum_{k=1}^{k}p_{uk}q_{ik})(q_{ik}) - \lambda_1 p_{uk}] puk:=puk+α[u,iR(ruik=1kpukqik)(qik)λ1puk]
q i k : = p i k + α [ ∑ u , i ∈ R ( r u i − ∑ k = 1 k p u k q i k ) ( p u k ) − λ 2 q i k ] q_{ik}:=p_{ik} + \alpha[\sum_{u,i∈R}(r_{ui}-\sum_{k=1}^{k}p_{uk}q_{ik})(p_{uk}) -\lambda_2 q_{ik}] qik:=pik+α[u,iR(ruik=1kpukqik)(puk)λ2qik]
随机梯度下降
应用到每个向量里
p u k : = p u k + α [ ( r u i − ∑ k = 1 k p u k q i k ) ( q i k ) − λ 1 p u k ] p_{uk}:=p_{uk} + \alpha[(r_{ui}-\sum_{k=1}^{k}p_{uk}q_{ik})(q_{ik}) - \lambda_1 p_{uk}] puk:=puk+α[(ruik=1kpukqik)(qik)λ1puk]
q i k : = p i k + α [ ( r u i − ∑ k = 1 k p u k q i k ) ( p u k ) − λ 2 q i k ] q_{ik}:=p_{ik} + \alpha[(r_{ui}-\sum_{k=1}^{k}p_{uk}q_{ik})(p_{uk}) -\lambda_2 q_{ik}] qik:=pik+α[(ruik=1kpukqik)(puk)λ2qik]

算法实现

这里只展示LFM算法的代码,数据集分割代码和评估代码见:大数据——协同过滤推荐算法:线性回归算法

# FunkSVD
class LFM(object):
   '''max_epochs 梯度下降迭代次数
       alpha 学习率
       reg 过拟合参数
       columns 数据字段名称'''
   def __init__(self,max_epochs,p_reg,q_reg,alpha,number_LatentFactors=30,columns=['userId','movieId','rating']):
       self.max_epochs = max_epochs
       self.p_reg = p_reg
       self.q_reg = q_reg
       self.number_LatentFactors=number_LatentFactors #隐式特征的数量
       self.alpha = alpha
       self.columns = columns
       
   def fit(self,data):
       '''
       :param data:uid,mid,rating
       :return:'''
       self.data = data
       # 用户评分数据
       self.users_rating = data.groupby(self.columns[0]).agg([list])[[self.columns[1], self.columns[2]]]
       # 电影评分数据
       self.items_rating = data.groupby(self.columns[1]).agg([list])[[self.columns[0], self.columns[2]]]
       # 全局平均分
       self.global_mean = self.data[self.columns[2]].mean()
       # 调用随机梯度下降训练模型参数
       self.p,self.q = self.sgd()
       
   def _init_matrix(self):
       # 构建p的矩阵,其中第二项元素的大小为用户数量*隐式特征
       p = dict(zip(self.users_rating.index, 
                    np.random.rand(len(self.users_rating), self.number_LatentFactors).astype(np.float32)))
       # 构建q的矩阵,其中第二项元素的大小为物品数量*隐式特征
       q = dict(zip(self.items_rating.index,
                    np.random.rand(len(self.items_rating), self.number_LatentFactors).astype(np.float32)))
       return p,q
   
   def sgd(self):
       '''
       最小二乘法,优化q和p值
       :return: q p'''
       p,q = self._init_matrix()
       
       for i in range(self.max_epochs):
           error_list = []
           for uid,mid,r_ui in self.data.itertuples(index=False):
               # user-lf p
               # item-lf q
               v_pu = p[uid]
               v_qi = q[mid]
               err = np.float32(r_ui - np.dot(v_pu, v_qi))
               
               v_pu += self.alpha*(err*v_qi - self.p_reg*v_pu)
               v_qi += self.alpha*(err*v_pu - self.q_reg*v_qi)
               
               p[uid] = v_pu
               q[mid] = v_qi
               
               error_list.append(err**2)
               
       return p,q
   
   def predict(self,uid,mid):
       '''
       使用评分公式进行预测
       param uid,mid;
       return predict_rating;'''
       if uid not in self.users_rating.index or mid not in self.items_rating.index:
           return self.global_mean
       p_u = self.p[uid]
       q_i = self.q[mid]
       return np.dot(p_u,q_i)
   
   def test(self,testset):
       '''
       使用预测函数预测测试集数据
       param testset;
       return yield;'''
       for uid,mid,real_rating in testset.itertuples(index=False):
           try:
               # 使用predict函数进行预测
               pred_rating = self.predict(uid,mid)
           except Exception as e:
               print(e)
           else:
               # 返回生成器对象
               yield uid,mid,real_rating,pred_rating

测试LFM算法

trainset, testset = data_split('ml-latest-small/ratings.csv',random=True)
lfm = LFM(100,0.01,0.01,0.02,10,['userId','movieId','rating'])
lfm.fit(trainset)
pred_test = lfm.test(testset)
# 生成器对象用list进行转化,然后转化为dataframe格式
df_pred_LFM = pd.DataFrame(list(pred_test), columns=[['userId','movieId','rating','pred_rating']])
rmse, mae = accuray(df_pred_als,'all')
print('rmse:',rmse,';mae:',mae)

rmse: 1.0718 ;mae: 0.8067

2 BiasSVD原理

BiasSVD是在FunkSVD矩阵分解的基础上加上了偏置项的。评分公式如下:
r ^ u i = u + b u + b i + p u k ⃗ ⋅ q k i ⃗ = u + b u + b i + ∑ k = 1 k p u k ⋅ q k i \hat r_{ui} = u+b_u+b_i+\vec{p_{uk}}·\vec{q_{ki}} = u+b_u+b_i+\sum^{k}_{k=1}{p_{uk}}·{q_{ki}} r^ui=u+bu+bi+puk qki =u+bu+bi+k=1kpukqki

损失函数
C o s t = ∑ u , i ∈ R ( r u i − r ^ u i ) 2 = ∑ u , i ∈ R ( r u i − u − b u − b i − ∑ k = 1 k p u k ⋅ q k i ) 2 Cost = \sum_{u,i∈R}(r_{ui}-\hat r_{ui})^2 = \sum_{u,i∈R}(r_{ui}-u-b_u-b_i-\sum^{k}_{k=1}{p_{uk}}·{q_{ki}})^2 Cost=u,iR(ruir^ui)2=u,iR(ruiububik=1kpukqki)2
加入L2范式
C o s t = ∑ u , i ∈ R ( r u i − r ^ u i ) 2 = ∑ u , i ∈ R ( r u i − u − b u − b i − ∑ k = 1 k p u k ⋅ q k i ) 2 + λ ( ∑ U b u 2 + ∑ I b i 2 + ∑ U p u k 2 + ∑ I q i k 2 ) Cost = \sum_{u,i∈R}(r_{ui}-\hat r_{ui})^2 = \sum_{u,i∈R}(r_{ui}-u-b_u-b_i-\sum^{k}_{k=1}{p_{uk}}·{q_{ki}})^2 +\lambda(\sum_Ub^2_u+\sum_Ib^2_i+\sum_Up^2_{uk}+\sum_Iq^2_{ik}) Cost=u,iR(ruir^ui)2=u,iR(ruiububik=1kpukqki)2+λ(Ubu2+Ibi2+Upuk2+Iqik2)

随机梯度下降法优化
梯度下降更新参数:
p u k : = p u k + α [ ( r u i − u − b u − b i − ∑ k = 1 k p u k ⋅ q k i ) q i k − λ 1 p u k ] p_{uk}:=p_{uk} + \alpha[(r_{ui}-u-b_u-b_i-\sum^{k}_{k=1}{p_{uk}}·{q_{ki}})q_{ik}-\lambda_1p_{uk}] puk:=puk+α[(ruiububik=1kpukqki)qikλ1puk]
q i k : = p i k + α [ ( r u i − u − b u − b i − ∑ k = 1 k p u k ⋅ q k i ) p u k − λ 2 q i k ] q_{ik}:=p_{ik} + \alpha[(r_{ui}-u-b_u-b_i-\sum^{k}_{k=1}{p_{uk}}·{q_{ki}})p_{uk}-\lambda_2q_{ik}] qik:=pik+α[(ruiububik=1kpukqki)pukλ2qik]
b u : = b u + α [ ( r u i − u − b u − b i − ∑ k = 1 k p u k ⋅ q k i ) − λ 3 b u ] b_{u}:=b_{u} + \alpha[(r_{ui}-u-b_u-b_i-\sum^{k}_{k=1}{p_{uk}}·{q_{ki}})-\lambda_3b_{u}] bu:=bu+α[(ruiububik=1kpukqki)λ3bu]
b i : = b i + α [ ( r u i − u − b u − b i − ∑ k = 1 k p u k ⋅ q k i ) − λ 4 b i ] b_{i}:=b_{i} + \alpha[(r_{ui}-u-b_u-b_i-\sum^{k}_{k=1}{p_{uk}}·{q_{ki}})-\lambda_4b_{i}] bi:=bi+α[(ruiububik=1kpukqki)λ4bi]

算法实现

# BiasSVD
class BiasSvd(object):
   '''max_epochs 梯度下降迭代次数
       alpha 学习率
       reg 过拟合参数
       columns 数据字段名称'''
   def __init__(self,alpha,p_reg,q_reg,bu_reg,bi_reg,number_LatentFactors=10,max_epochs=10,columns=['userId','movieId','rating']):
       self.max_epochs = max_epochs
       self.p_reg = p_reg
       self.q_reg = q_reg
       self.bu_reg = bu_reg
       self.bi_reg = bi_reg
       self.number_LatentFactors=number_LatentFactors #隐式特征的数量
       self.alpha = alpha
       self.columns = columns
       
   def fit(self,data):
       '''
       :param data:uid,mid,rating
       :return:'''
       self.data = data
       # 用户评分数据
       self.users_rating = data.groupby(self.columns[0]).agg([list])[[self.columns[1], self.columns[2]]]
       # 电影评分数据
       self.items_rating = data.groupby(self.columns[1]).agg([list])[[self.columns[0], self.columns[2]]]
       # 全局平均分
       self.global_mean = self.data[self.columns[2]].mean()
       # 调用随机梯度下降训练模型参数
       self.p,self.q, self.bu,self.bi= self.sgd()
       
   def _init_matrix(self):
       # 构建p的矩阵,其中第二项元素的大小为用户数量*隐式特征
       p = dict(zip(self.users_rating.index, 
                    np.random.rand(len(self.users_rating), self.number_LatentFactors).astype(np.float32)))
       # 构建q的矩阵,其中第二项元素的大小为物品数量*隐式特征
       q = dict(zip(self.items_rating.index,
                    np.random.rand(len(self.items_rating), self.number_LatentFactors).astype(np.float32)))
       return p,q
   
   def sgd(self):
       '''
       随机梯度下降,优化q和p值
       :return: q p'''
       p,q = self._init_matrix()
       bu = dict(zip(self.users_rating.index, np.zeros(len(self.users_rating))))
       bi = dict(zip(self.items_rating.index,np.zeros(len(self.items_rating))))
       
       for i in range(self.max_epochs):
           error_list = []
           for uid,mid,r_ui in self.data.itertuples(index=False):
               # user-lf p
               # item-lf q
               v_pu = p[uid]
               v_qi = q[mid]
               err = np.float32(r_ui - self.global_mean - bu[uid] - bi[mid]-np.dot(v_pu, v_qi))
               
               v_pu += self.alpha*(err*v_qi - self.p_reg*v_pu)
               v_qi += self.alpha*(err*v_pu - self.q_reg*v_qi)
               
               p[uid] = v_pu
               q[mid] = v_qi
               
               bu[uid] += self.alpha*(err - self.bu_reg*bu[uid])
               bi[mid] += self.alpha*(err - self.bi_reg*bi[mid])
               
               error_list.append(err**2)
               
       return p,q,bu,bi
   
   def predict(self,uid,mid):
       '''
       使用评分公式进行预测
       param uid,mid;
       return predict_rating;'''
       if uid not in self.users_rating.index or mid not in self.items_rating.index:
           return self.global_mean
       p_u = self.p[uid]
       q_i = self.q[mid]
       return self.global_mean+self.bu[uid]+self.bi[mid]+np.dot(p_u,q_i)
   
   def test(self,testset):
       '''
       使用预测函数预测测试集数据
       param testset;
       return yield;'''
       for uid,mid,real_rating in testset.itertuples(index=False):
           try:
               # 使用predict函数进行预测
               pred_rating = self.predict(uid,mid)
           except Exception as e:
               print(e)
           else:
               # 返回生成器对象
               yield uid,mid,real_rating,pred_rating

算法使用

trainset, testset = data_split('ml-latest-small/ratings.csv',random=True)
bsvd = BiasSvd(0.02,0.01,0.01,0.01,0.01,10,20,['userId','movieId','rating'])
bsvd.fit(trainset)
pred_test = bsvd.test(testset)
# 生成器对象用list进行转化,然后转化为dataframe格式
df_pred_LFM = pd.DataFrame(list(pred_test), columns=[['userId','movieId','rating','pred_rating']])
rmse, mae = accuray(df_pred_als,'all')
print('rmse:',rmse,';mae:',mae)

rmse: 1.0718 ;mae: 0.8067


http://www.niftyadmin.cn/n/4940089.html

相关文章

Day 27 C++ set/ multiset 容器,pair对组

文章目录 set容器(集合)基本概念定义特点set构造和赋值构造赋值总结示例 set大小和交换函数原型示例 set插入和删除函数原型示例 set查找和统计函数原型示例 set容器排序利用仿函数改变排序规则set存放自定义数据类型 multiset容器(多重集合&…

vue3 基础知识 02

你好,今天过的怎么样呀,嘿嘿,加油夏 💕 文章目录 一、模板语法二、指令三、key diff 算法 一、模板语法 React的开发模式: React 使用的 jsx,对应的代码编写的类似于js的一种语法;通过 Babel …

高忆管理:策略:短期利空落地 市场有望企稳回升

高忆管理指出,基于A股商场出资环境分析,尤其是当时商场存量博弈为主的布景下,主张重视以下“21”主线轮动:(1)国产科技代替立异:电子(半导体、消费电子)、通信&#xff0…

leetcode原题:堆盘子 (考查对栈的理解和使用)

题目: 堆盘子。设想有一堆盘子,堆太高可能会倒下来。因此,在现实生活中,盘子堆到一定高度时,我们就会另外堆一堆盘子。请实现数据结构SetOfStacks,模拟这种行为。SetOfStacks应该由多个栈组成,…

二、Dubbo 实现方式

二、Dubbo 实现方式 2.1 配置开发环境 JDK、IDE、Maven/GradleZooKeeper,作为注册中心Dubbo 源码2.2 基于XML配置实现服务端 定义Service作为服务暴露使用定义 provider 配置文件:服务方提供方应用名称、注册中心、监听端口、Bean、暴露接口通过指定服务…

Java 开发工具 IntelliJ IDEA

1. IntelliJ IDEA 简介 IntelliJ IDEA 是一款出色的 Java 集成开发环境(IDE),提供了丰富的功能和工具,支持多种语言和框架的开发,如 Java、Kotlin、Scala、 Android、Spring、Hibernate 等。IntelliJ IDEA 专注于提高…

【环境配置】Windows 10 安装 PyTorch 开发环境,以及验证 YOLOv8

Windows 10 安装 PyTorch 开发环境,以及验证 YOLOv8 最近搞了一台Windows机器,准备在上面安装深度学习的开发环境,并搭建部署YOLOv8做训练和测试使用; 环境: OS: Windows 10 显卡: RTX 3090 安…

10个经典战略分析模型,助力洞察市场明确优势

在企业的经营管理过程中,要时刻清晰内外部环境和自身的优劣势,做好企业略规划,进行企业内外部资源的分析,对经营环境,企业核心竞争力有足够的判断,才能明确企业的发展方向。本文为大家分享10个常用的战略分…