赞
踩
在推荐系统算法中,协同过滤算法更适合给用户进行个性化的推荐,其中基于模型的协同过滤算法相较于基于领域的协同过滤算法(基于内存的算法),在大数据处理上,有更大的优势。
用户与物品之间存在着隐含的联系,基于聚类算法,将商品和顾客分别聚类,通过各个类别之间的关系,得到顾客与商品之间存在的隐含联系。隐含的特征就是类别的分类个数称为隐分类个数(隐含特征 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∑(rui−xuTyi)2+λ(N∑∣∣xu∣∣22+M∑∣∣yi∣∣22)
L1 正则化与 L2 正则化
本身模型就是稀疏的,不需要使用 L1 正则化。接下来最小化 Loss 就可以了。
优化这个损失函数有两种方式:梯度下降法和 ALS 交替最小二乘法。对二维(用户、商品)矩阵的分解可以使用 “ALS交替最小二乘” 求解
思想:固定一个求另一个最优化
对 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∑(rui−xuTyi)2+(N∑∣∣xu∣∣22)
∂ 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 xu∂L(xu)=−2Yu(Ru−Yuxu)+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 进行优化。
显示矩阵转换:
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=pui∑cui(pui−xuTyi)2+λ(N∑∣∣xu∣∣22+M∑∣∣yi∣∣22)
使其达到最小即为目的。
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∑(rui−xuTyi)2+λ(N∑∣∣xu∣∣22+M∑∣∣yi∣∣22)
当 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 xu∂L(xu)=−2Yu(Ru−Yuxu)+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 ,在操作完成时,再合并到一起。
当 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 xu∂L(xu)=−2Yu(Ru−Yuxu)+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 ,在操作完成时,再合并到一起。
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())
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
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−μ−bu−bi)2+λ(N∑∣∣bu∣∣22+M∑∣∣bi∣∣22)
#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)
一般来讲:
ALS 的方法只能优化较少的特征,对多特征时运行较慢;
但 SVD 的方法可以同时优化多种特征。
三种梯度下降法:BGD、SGD、Mini-batch 梯度下降。
一、对称方阵矩阵的分解:
对于对称方阵 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 ATA⋅vi=λ⋅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) AAT⋅ui=λ(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 ATA⋅vi=λ⋅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=∣Avi∣Avi=λ 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
四、特性:
- SVD中的奇异值 Σ \Sigma Σ 为 D D D 的特征值开方;
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)
在推荐系统中,矩阵通常是稀疏的,而 SVD 要求矩阵是稠密的。
事实上,SVD 更适合进行降维
GitHub:图像降维
可以看到,减少特征数量很少时,并不影响图像的表达,基于此,应用于推荐系统。
可以看到使用两阶特征矩阵亦可以还原评分矩阵
这实际上也是一个隐语义模型,其隐特征维度为 2
使用 SVD 构造的这几个隐语义模型其实本质上就是损失函数的不同:
- 由于 SVD 方法不能解决稀疏矩阵的问题,因此使用机器学习方法,在 SVD 降维的基础上(设置超参数 构造特征数量: k k k)。
- 只关注有值部分,构造损失函数,使用梯度下降的优化方式,得到最小Loss的两个矩阵,以达到矩阵分解的效果。
- 三者的区别在于损失函数的构造方式不同。本质与前述稀疏矩阵的 Loss 损失函数类似
#安装必要的库和包
from google.colab import drive
drive.mount('/content/drive')
#进入安装环境
!ls "/content/drive/My Drive/"
#安装 surprise 包
!pip install surprise
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()
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)
输出结果:
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
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)
输出结果:
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
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)
输出结果:
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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。