当前位置:   article > 正文

推荐系统算法:矩阵分解_矩阵分解 python 基于rmse

矩阵分解 python 基于rmse


基于模型的协同过滤推荐算法:隐语义模型之矩阵分解

推荐系统算法中,协同过滤算法更适合给用户进行个性化的推荐,其中基于模型的协同过滤算法相较于基于领域的协同过滤算法(基于内存的算法),在大数据处理上,有更大的优势。

隐语义模型:

用户与物品之间存在着隐含的联系,基于聚类算法,将商品和顾客分别聚类,通过各个类别之间的关系,得到顾客与商品之间存在的隐含联系。隐含的特征就是类别的分类个数称为隐分类个数(隐含特征 Latent Factor),隐分类个数可多可少,相比较之下模型的可解释都不强,因为隐含特征是自动进行分类的。

一、稀疏矩阵分解

推荐系统中存在着用户行为的稀疏矩阵,由于稀疏矩阵无法给用户进行商品推荐,因此考虑补全这些缺失值,并根据缺失值TOP-N 给用户进行个性化推荐。依据打分商品推荐未打分商品。

可以对稀疏矩阵进行分解,假设有如下用户对电影的观影喜好:
在这里插入图片描述
假设隐分类个数:3
在这里插入图片描述
我们可以将评分矩阵分解为:User 矩阵和 Item 矩阵,User 矩阵代表用户对电影隐分类类别的喜好程度;item 矩阵代表各个电影都属于哪一类隐分类类别。如果可以分解,那么相乘必将得到完整的评分矩阵。

User 矩阵为 X = [ x 1 , x 2 , . . . , x N ] X= [x_1,x_2,...,x_N] X=[x1,x2,...,xN]隐分类个数 k=3 维列向量,
Item 矩阵为 Y = [ y 1 , y 2 , . . . y M ] Y = [y_1,y_2,...y_M] Y=[y1,y2,...yM] 隐分类个数 k=3 维列向量,
r u i r_{ui} rui 在原始评分矩阵中若存在值,则为 1 ,else 0.

矩阵分解的目标函数:
L o s s = ∑ r u i ( r u i − x u T y i ) 2 + λ ( ∑ N ∣ ∣ x u ∣ ∣ 2 2 + ∑ M ∣ ∣ y i ∣ ∣ 2 2 ) Loss = \sum_{r_{ui}}{(r_{ui} -x_u^Ty_i)^2 +\lambda (\sum_N{||x_u||^2_2}+\sum_M{||y_i||^2_2})} Loss=rui(ruixuTyi)2+λ(Nxu22+Myi22)
L1 正则化与 L2 正则化
本身模型就是稀疏的,不需要使用 L1 正则化。接下来最小化 Loss 就可以了。

优化这个损失函数有两种方式:梯度下降法和 ALS 交替最小二乘法。对二维(用户、商品)矩阵的分解可以使用 “ALS交替最小二乘” 求解

二、 矩阵分解的优化方式

(一)、 ALS(Alternating Least Squares,交替最小二乘)

思想:固定一个求另一个最优化

  1. 优化显式信息矩阵:

对 x 进行优化:
L o s s = ∑ r u i ( r u i − x u T y i ) 2 + ( ∑ N ∣ ∣ x u ∣ ∣ 2 2 ) Loss = \sum_{r_{ui}}{(r_{ui} -x_u^Ty_i)^2 + (\sum_N{||x_u||^2_2})} Loss=rui(ruixuTyi)2+(Nxu22)

∂ L ( x u ) x u = − 2 Y u ( R u − Y u x u ) + 2 λ x u = 0 \frac{\partial L(x_u)}{x_u} = -2Y_u(R_u-Y_ux_u)+2\lambda x_u = 0 xuL(xu)=2Yu(RuYuxu)+2λxu=0

x u = ( Y u Y u T + λ I ) − 1 Y u R u x_u = (Y_uY_u^T+\lambda I)^{-1}Y_uR_u xu=(YuYuT+λI)1YuRu

然后对 y 进行优化。

  1. 对隐式信息矩阵进行优化:

显示矩阵转换:
p u i = 1 , r u i > = 1 p_{ui} = 1, r_{ui}>=1 pui=1,rui>=1
p u i = 0 , r u i = 0 p_{ui} = 0, r_{ui}=0 pui=0,rui=0

设置置信度:
c u i = 1 + α r u i c_{ui} = 1+ \alpha r_{ui} cui=1+αrui
r u i > 0 r_{ui}>0 rui>0 c u i c_{ui} cui会线性的增长, r u i = 0 r_{ui}=0 rui=0 c u i = 1 c_{ui}=1 cui=1

目标函数:
L o s s = ∑ p u i c u i ( p u i − x u T y i ) 2 + λ ( ∑ N ∣ ∣ x u ∣ ∣ 2 2 + ∑ M ∣ ∣ y i ∣ ∣ 2 2 ) Loss = \sum_{p_{ui}}{c_{ui}}{(p_{ui} -x_u^Ty_i)^2 +\lambda (\sum_N{||x_u||^2_2}+\sum_M{||y_i||^2_2})} Loss=puicui(puixuTyi)2+λ(Nxu22+Myi22)
使其达到最小即为目的。

1. 工具

  • spark mllib库(spark3.0版本后废弃)
    支持常见的算法,包括 分类、回归、聚类和协同过滤
    from pyspark.mllib.recommendation import ALS, Rating, MatrixFactorizationModel
  • spark ml库(官方推荐)
    功能更全面更灵活,ml在DataFrame上的抽象级别更高,数据和操作耦合度更低,使用起来像sklearn
    Spark安装很多坑,需要慢慢来
  • Python代码
    https://github.com/tushushu/imylu/blob/master/imylu/recommend/als.py
  • surprise

2. 示例:使用 MovieLens 数据集进行评分预测

方法 1 : 使用 ALS 算法,采用分布式 spark 计算用户(userid == 100)的电影评分

  • 模型目标函数:

L o s s = ∑ r u i ( r u i − x u T y i ) 2 + λ ( ∑ N ∣ ∣ x u ∣ ∣ 2 2 + ∑ M ∣ ∣ y i ∣ ∣ 2 2 ) Loss = \sum_{r_{ui}}{(r_{ui} -x_u^Ty_i)^2 +\lambda (\sum_N{||x_u||^2_2}+\sum_M{||y_i||^2_2})} Loss=rui(ruixuTyi)2+λ(Nxu22+Myi22)

  • Loss 最小化的求解方法: ALS(通过先固定一个求解另一个的优化方式 )
  1. 初始化Items矩阵;
  2. 固定Items矩阵,求Users矩阵使得Loss函数最小化;

y i y_i yi 固定时,将其写为矩阵形式,可以对单个 x u x_u xu 进行优化求解:
∂ L ( x u ) x u = − 2 Y u ( R u − Y u x u ) + 2 λ x u = 0 \frac{\partial L(x_u)}{x_u} = -2Y_u(R_u-Y_ux_u)+2\lambda x_u = 0 xuL(xu)=2Yu(RuYuxu)+2λxu=0
x u = ( Y u Y u T + λ I ) − 1 Y u R u x_u = (Y_uY_u^T+\lambda I)^{-1}Y_uR_u xu=(YuYuT+λI)1YuRu
所以,可以将 x u x_u xu 进行并行化操作,在每个分区执行不同的 x u x_u xu ,在操作完成时,再合并到一起。

  1. 固定Users矩阵,求Items矩阵使得Loss函数最小化;
  2. 重复上述2,3步过程
  • ALS 可以并行处理的原因:

y i y_i yi 固定时,将其写为矩阵形式,可以对单个 x u x_u xu 进行优化求解:
∂ L ( x u ) x u = − 2 Y u ( R u − Y u x u ) + 2 λ x u = 0 \frac{\partial L(x_u)}{x_u} = -2Y_u(R_u-Y_ux_u)+2\lambda x_u = 0 xuL(xu)=2Yu(RuYuxu)+2λxu=0
x u = ( Y u Y u T + λ I ) − 1 Y u R u x_u = (Y_uY_u^T+\lambda I)^{-1}Y_uR_u xu=(YuYuT+λI)1YuRu
所以,可以将 x u x_u xu 进行并行化操作,在每个分区执行不同的 x u x_u xu ,在操作完成时,再合并到一起。

  • spark中 ALS 参数设置:
  • 显式行为:

numBlocks 是用于并行化计算的分块个数 (设置为-1为自动配置)。
maxTter: 最小迭代次数;
regParam:惩罚项系数 λ \lambda λ(建议为0.01)
rank:隐分类个数
userCol= , 用户变量名称
itemCol= , 商品变量名称
ratingCol= ,评分变量名称
nonnegative:是否使用非负约束,默认不使用 false。
predictionCol:预测列的名字

  • 隐式行为:

ImplicitPrefs = true此处表明rating矩阵是隐式评分

from pyspark.ml.recommendation import ALS
from pyspark import SparkContext
from pyspark.sql import SQLContext
from pyspark.sql.types import *
import pandas as pd
import os

#Spark 环境配置
sc = SparkContext()
sql_sc = SQLContext(sc)

#数据加载
pd_df_ratings = pd.read_csv('./ratings.csv')
#转换为 spark 下的 DataFrame 格式
pyspark_df_ratings = sql_sc.createDataFrame(pd_df_ratings)
pyspark_df_ratings = pyspark_df_ratings.drop('Timestamp')

print(pyspark_df_ratings.show(5, truncate=False))

# 创建ALS模型
als = ALS(rank = 8, 
		  maxIter = 15, 
		  regParam = 0.1, 
		  userCol= 'userId', 
		  itemCol='movieId', 
		  ratingCol='rating'
		  )
model = als.fit(pyspark_df_ratings)

# 对userId=100进行Top-N推荐
recommendations = model.recommendForAllUsers(5)

# 对user-id == 100 进行电影推荐
print(recommendations.where(recommendations.userId == 100).collect())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

在这里插入图片描述

方法2:基于 surprise 的 Baseline 模型的 ALS 优化方法

  • BaseLine的基本思想:

Baseline算法的思想就是设立基线 μ \mu μ,并引入用户的偏差 b u b_u bu 以及item的偏差 b i b_i bi
r u i ^ = b u i = μ + b u + b i \hat{r_{ui}} = b_{ui} = \mu +b_u +b_i rui^=bui=μ+bu+bi

  • BaseLine的目标函数:

L o s s = ∑ r ^ u i ( r ^ u i − μ − b u − b i ) 2 + λ ( ∑ N ∣ ∣ b u ∣ ∣ 2 2 + ∑ M ∣ ∣ b i ∣ ∣ 2 2 ) Loss = \sum_{\hat{r}_{ui}}{(\hat{r}_{ui} - \mu - b_u - b_i)^2 +\lambda (\sum_N{||b_u||^2_2}+\sum_M{||b_i||^2_2})} Loss=r^ui(r^uiμbubi)2+λ(Nbu22+Mbi22)

  • 优化方法:ALS 交替最小二乘
  • 验证方法:K 折交叉验证
#from surprise import SVD
from surprise import Dataset
from surprise import Reader
from surprise import BaselineOnly, KNNBasic, NormalPredictor
from surprise import accuracy
from surprise.model_selection import KFold
#import pandas as pd

# 数据读取
reader = Reader(line_format='user item rating timestamp', sep=',', skip_lines=1)
data = Dataset.load_from_file('./ratings.csv', reader=reader)
train_set = data.build_full_trainset()

# ALS优化
bsl_options = {
			   'method': 'als',
			   'n_epochs': 5,
			   'reg_u': 12, # user 的正则化参数,默认为 15
			   'reg_i': 5 # item 的正则化参数, 默认为 10
			   }
# SGD优化
#bsl_options = {'method': 'sgd','n_epochs': 5}
algo = BaselineOnly(bsl_options = bsl_options)
#algo = BaselineOnly()
#algo = NormalPredictor()

# 定义K折交叉验证迭代器,K=3
kf = KFold(n_splits=3)
for trainset, testset in kf.split(data):
    # 训练并预测
    algo.fit(trainset)
    predictions = algo.test(testset)
    # 计算RMSE
    accuracy.rmse(predictions, verbose = True)

uid = str(196)
iid = str(302)

# 输出uid对iid的预测结果
pred = algo.predict(uid, iid, r_ui=4, verbose=True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

在这里插入图片描述

3. ALS 的缺点:计算时所需要的时间与空间复杂度太高

一般来讲:

ALS 的方法只能优化较少的特征,对多特征时运行较慢;
但 SVD 的方法可以同时优化多种特征。

(二)、梯度下降法

三种梯度下降法:BGD、SGD、Mini-batch 梯度下降。

三、SVD 矩阵分解:

1. 原理:

一、对称方阵矩阵的分解:

对于对称方阵 A A A 可以分解为: A = V D V T A = VDV^T A=VDVT 的形式,
若矩阵 A n , m A_{n,m} An,m 不是对称方阵,可以将 A n , m A_{n,m} An,m 转换为 A A n , n T AA^T_{n,n} AAn,nT 进行矩阵分解。

二、 A A T AA^T AAT A T A A^TA ATA 的特征值是相同的( A m , n A_{m,n} Am,n ):

A T A A^TA ATA 存在特征值、特征向量 λ , v i \lambda, v_i λ,vi A T A ⋅ v i = λ ⋅ v i A^TA \cdot v_i = \lambda \cdot v_i ATAvi=λvi A A T ( A v i ) = A λ v i AA^T(Av_i) = A\lambda v_i AAT(Avi)=Aλvi A A T ⋅ u i = λ ( u i ) AA^T\cdot u_i = \lambda (u_i) AATui=λ(ui)所以两者有着相同的特征值 A T A n , n = V n , n D V T , A A m , m T = U m , m B U T A^TA_{n,n} = V_{n,n}DV^T, AA^T_{m,m} = U_{m,m}BU^T ATAn,n=Vn,nDVT,AAm,mT=Um,mBUT D , B D,B D,B 中的特征值是相同的 λ \lambda λ

三、非方阵的矩阵分解:

A T A A^TA ATA 存在特征值、特征向量 λ , v i \lambda, v_i λ,vi A T A ⋅ v i = λ ⋅ v i A^TA \cdot v_i = \lambda \cdot v_i ATAvi=λvi
由 二 可得:相应的 u i = A v i u_i = Av_i ui=Avi也是特征值 λ \lambda λ 的特征向量。
u i u_i ui 进行标准化可以得到: w i = A v i ∣ A v i ∣ = A v i λ w_i = \frac{Av_i}{|Av_i|} = \frac{Av_i}{\sqrt{\lambda}} wi=AviAvi=λ Avi A v i = λ ⋅ w i Av_i = \sqrt{\lambda}\cdot w_i Avi=λ wi
A V = W Σ AV = W\Sigma AV=WΣ A = W Σ V T A = W\Sigma V^T A=WΣVT

四、特性:

  1. SVD中的奇异值 Σ \Sigma Σ D D D 的特征值开方;
  1. 右奇异值向量为 A T A A^TA ATA 的特征向量;
  2. 左奇异值向量为 A v i Av_i Avi的标准化向量。
  3. W m , m W_{m,m} Wm,m V n , n V_{n,n} Vn,n 分别代表 A A A 中的行特征矩阵与列特征矩阵,即: U s e r User User 矩阵与 I t e m Item Item 矩阵

参考

2. 示例:

from scipy.linalg import svd
import numpy as np

from scipy.linalg import svd
A = np.array([[1,2],
	    [1,1],
	    [0,0]])
p,s,q = svd(A,full_matrices=False)

print('P=', p)
print('S=', s)
print('Q=', q)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述
在推荐系统中,矩阵通常是稀疏的,而 SVD 要求矩阵是稠密的。
事实上,SVD 更适合进行降维

SVD降维

GitHub:图像降维
在这里插入图片描述
在这里插入图片描述
可以看到,减少特征数量很少时,并不影响图像的表达,基于此,应用于推荐系统。

3. SVD 在推荐系统中的应用

在这里插入图片描述
可以看到使用两阶特征矩阵亦可以还原评分矩阵
这实际上也是一个隐语义模型,其隐特征维度为 2

Funk SVD、Bias SVD、SVD++

使用 SVD 构造的这几个隐语义模型其实本质上就是损失函数的不同:

  • 由于 SVD 方法不能解决稀疏矩阵的问题,因此使用机器学习方法,在 SVD 降维的基础上(设置超参数 构造特征数量: k k k)。
  • 只关注有值部分,构造损失函数,使用梯度下降的优化方式,得到最小Loss的两个矩阵,以达到矩阵分解的效果。
  • 三者的区别在于损失函数的构造方式不同。本质与前述稀疏矩阵的 Loss 损失函数类似
  1. FunkSvd: a r g m i n p i , q j ∑ i , j ∈ K ( m i j − q j T p i ) 2 + λ ( ∣ ∣ p i ∣ ∣ 2 2 + ∣ ∣ q j ∣ ∣ 2 2 ) \underset{p_i, q_j}{argmin}\sum_{i,j\in K}(m_{ij} - q_j^Tp_i)^2 + \lambda (||p_i||_2^2+||q_j||_2^2) pi,qjargmini,jK(mijqjTpi)2+λ(pi22+qj22)
  2. BiasSVD: 通过 baseline 的方式设置损失函数:
    a r g m i n p i , q j ∑ i , j ∈ K ( m i j − μ − b u − b i − q j T p i ) 2 + λ ( ∣ ∣ p i ∣ ∣ 2 2 + ∣ ∣ q j ∣ ∣ 2 2 + ∣ ∣ b u ∣ ∣ 2 2 + ∣ ∣ q b i ∣ ∣ 2 2 ) \underset{p_i, q_j}{argmin}\sum_{i,j\in K}(m_{ij} - \mu - b_u - b_i - q_j^Tp_i)^2 + \lambda (||p_i||_2^2+||q_j||_2^2+||b_u||_2^2+||qb_i||_2^2) pi,qjargmini,jK(mijμbubiqjTpi)2+λ(pi22+qj22+bu22+qbi22)
  3. SVD ++ 加入了用户的隐式行为:
    a r g m i n p i , q j ∑ i , j ∈ K ( m i j − μ − b u − b i − q j T ( p i + ∣ I i ∣ − 1 2 + ∑ j ∈ I i y j ) ) 2 + λ ( ∣ ∣ p i ∣ ∣ 2 2 + ∣ ∣ q j ∣ ∣ 2 2 + ∣ ∣ b u ∣ ∣ 2 2 + ∣ ∣ b i ∣ ∣ 2 2 + ∣ ∣ ∑ j ∈ I i y j ∣ ∣ 2 2 ) \underset{p_i, q_j}{argmin}\sum_{i,j\in K}(m_{ij} - \mu - b_u - b_i - q_j^T(p_i + |I_i|^{-\frac{1}{2}} + \sum_{j\in{I_i}}y_j))^2 + \lambda (||p_i||_2^2+||q_j||_2^2+||b_u||_2^2+||b_i||_2^2+||\sum_{j\in{I_i}}y_j||_2^2) pi,qjargmini,jK(mijμbubiqjT(pi+Ii21+jIiyj))2+λ(pi22+qj22+bu22+bi22+jIiyj22)
  • 优化方式:梯度下降法
工具
准备工作:
上传Google云盘 movielens 数据集
使用Google colab

#安装必要的库和包
from google.colab import drive
drive.mount('/content/drive')

#进入安装环境
!ls "/content/drive/My Drive/"

#安装 surprise 包
!pip install surprise
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
数据读取:
from surprise import SVD
from surprise import Dataset
from surprise.model_selection import cross_validate
from surprise import Reader
from surprise import accuracy
from surprise.model_selection import KFold
import pandas as pd
import time


# from google.colab import drive
# drive.mount('/content/drive')
import os
os.chdir("/content/drive/My Drive/Colab Notebooks/")

time1=time.time()

# 数据读取
reader = Reader(line_format='user item rating timestamp', sep=',', skip_lines=1)
data = Dataset.load_from_file('ratings.csv', reader=reader)
train_set = data.build_full_trainset()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
(1) 使用Funk SVD方法:
time1=time.time()

# 使用funkSVD
algo = SVD(biased=False)# True 即为 Bias SVD

# 定义K折交叉验证迭代器,K=3
kf = KFold(n_splits=3)
for trainset, testset in kf.split(data):
    # 训练并预测
    algo.fit(trainset)
    predictions = algo.test(testset)
    # 计算RMSE
    accuracy.rmse(predictions, verbose=True)

uid = str(196)
iid = str(302)
# 输出uid对iid的预测结果
pred = algo.predict(uid, iid, r_ui=4, verbose=True)
time2=time.time()
print(time2-time1)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
输出结果:

RMSE: 0.8724
RMSE: 0.8737
RMSE: 0.8722
user: 196 item: 302 r_ui = 4.00 est = 4.57 {‘was_impossible’: False}
145.8252305984497

(2) 使用biased SVD:
time1=time.time()
# 使用 Biased SVD
algo = SVD(biased = True)

# 定义K折交叉验证迭代器,K=3
kf = KFold(n_splits=3)
for trainset, testset in kf.split(data):
    # 训练并预测
    algo.fit(trainset)
    predictions = algo.test(testset)
    # 计算RMSE
    accuracy.rmse(predictions, verbose=True)

uid = str(196)
iid = str(302)
# 输出uid对iid的预测结果
pred = algo.predict(uid, iid, r_ui=4, verbose=True)
time2=time.time()
print(time2-time1)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
输出结果:

RMSE: 0.8465
RMSE: 0.8432
RMSE: 0.8462
user: 196 item: 302 r_ui = 4.00 est = 3.97 {‘was_impossible’: False}
141.18379878997803

(3) 使用 SVD++ :
from surprise import SVDpp

time1=time.time()
# 使用 Biased SVD
algo = SVDpp(n_factors = 5)

# 定义K折交叉验证迭代器,K=3
kf = KFold(n_splits=3)
for trainset, testset in kf.split(data):
    # 训练并预测
    algo.fit(trainset)
    predictions = algo.test(testset)
    # 计算RMSE
    accuracy.rmse(predictions, verbose=True)

uid = str(196)
iid = str(302)
# 输出uid对iid的预测结果
pred = algo.predict(uid, iid, r_ui=4, verbose=True)
time2=time.time()
print(time2-time1)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
输出结果:

RMSE: 0.8308
RMSE: 0.8293
RMSE: 0.8309
user: 196 item: 302 r_ui = 4.00 est = 3.90 {‘was_impossible’: False}
4811.0583329200745

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/649287
推荐阅读
相关标签
  

闽ICP备14008679号