在推荐系统中,我们经常看到如下图的表格,表格中的数字代表用户对某个物品的评分,0
代表未评分。我们希望能够预测目标用户对物品的评分,进而根据评分高低,将分高的物品推荐给用户。
y | 物品1 | 物品2 | 物品3 | 物品4 | 物品5 |
---|---|---|---|---|---|
用户1 | 5 | 5 | 0 | 1 | 1 |
用户2 | 5 | 0 | 4 | 1 | 1 |
用户3 | 1 | 0 | 1 | 5 | 5 |
用户4 | 1 | 1 | 0 | 4 | 0 |
基于矩阵分解的协同过滤算法正好能解决这个问题。
基于矩阵分解的协同过滤算法通常都会构造如下图所示评分表y
,这里我们以电影为例:
y | 电影1 | 电影2 | 电影3 | 电影4 | 电影5 |
---|---|---|---|---|---|
用户1 | 5 | 5 | 0 | 1 | 1 |
用户2 | 5 | 0 | 4 | 1 | 1 |
用户3 | 1 | 0 | 1 | 5 | 5 |
用户4 | 1 | 1 | 0 | 4 | 0 |
我们认为,有很多因素会影响到用户给电影评分,如电影内容:感情戏,恐怖元素,动作成分,推理悬疑等等。假设我们现在想预测用户2
对电影2
的评分,用户2
他很喜欢看动作片与推理悬疑,不喜欢看感情戏与恐怖的元素,而电影2
只有少量的感情戏与恐怖元素,大部分都是动作与推理的剧情,则用户2
对电影2
评分可能很高,比如5
分。
基于上面的设想,我们只要知道所有用户对电影内容各种因素喜欢程度与所有电影内容的成分,就能预测出所有用户对所有电影的评分了。
若只考虑两种因素则用户喜好表与电影内容表如下:
用户喜好表x
:
x | 因素1 | 因素2 |
---|---|---|
用户1 | 5 | 0 |
用户2 | 5 | 0 |
用户3 | 0 | 5 |
用户4 | 0 | 5 |
值越大代表用户越喜欢某种因素。
电影内容表:w
:
w | 电影1 | 电影2 | 电影3 | 电影4 | 电影5 |
---|---|---|---|---|---|
因素1 | 0.9 | 1.0 | 0.99 | 0.1 | 0 |
因素2 | 0 | 0.01 | 0 | 1.0 | 0.9 |
值越大代表电影中某因素内容越多。
用户2
对电影2
评分为: 5 × 1.0 + 0 × 0.01 = 5.0 5\times1.0+0\times0.01=5.0 5×1.0+0×0.01=5.0
对于所有用户,我们可以将矩阵x
与矩阵w
相乘,得到所有用户对所有电影的预测评分如下表:
xw | 电影1 | 电影2 | 电影3 | 电影4 | 电影5 |
---|---|---|---|---|---|
用户1 | 4.5 | 5.0 | 4.95 | 0.5 | 0 |
用户2 | 4.5 | 5.0 | 4.95 | 0.5 | 0 |
用户3 | 0 | 0.05 | 0 | 5 | 4.5 |
用户4 | 0 | 0.05 | 0 | 5 | 4.5 |
假设电影评分表y
(为m
行n
列的矩阵),我们考虑d
种因素,则电影评分表可以分解为用户喜好表x
(为m
行d
列的矩阵),与电影内容表w
(为d
行n
列的矩阵)。其中d
为超参数,大小由我们自己定。
基于矩阵分解的协同过滤算法思想为:一个用户评分矩阵可以分解为一个用户喜好矩阵与内容矩阵,我们只要能找出正确的用户喜好矩阵参数与内容矩阵参数(即表内的值),就能对用户评分进行预测,再根据预测结果对用户进行推荐。
将用户喜好矩阵与内容矩阵进行矩阵乘法就能得到用户对物品的预测结果,而我们的目的是预测结果与真实情况越接近越好。所以,我们将预测值与评分表中已评分部分的值构造平方差损失函数:
l o s s = 1 2 ∑ ( i , j ) ∈ r ( i , j ) = 1 ( ∑ l = 1 d x i l w l j − y i j ) 2 loss = \frac{1}{2}\sum\limits_{(i,j)\in r(i,j)=1}(\sum\limits_{l=1}^dx_{il}w_{lj}-y_{ij})^2 loss=21(i,j)∈r(i,j)=1∑(l=1∑dxilwlj−yij)2
其中:
i:第i个用户
j:第j个物品
d:第d种因素
x:用户喜好矩阵
w:内容矩阵
y:评分矩阵
r:评分记录矩阵,无评分记为0,有评分记为1。r(i,j)=1代表用户i对物品j进行过评分,r(i,j)=0代表用户i对物品j未进行过评分
损失函数python
实现代码如下:
import numpy as np
loss = np.sum(np.multiply((y-np.dot(x,w))**2,record))
其中,record
为评分记录矩阵。
我们的目的就是最小化平方差损失函数,通常机器学习都是使用梯度下降的方法来最小化损失函数得到正确的参数。
对每个参数求得偏导如下:
∂ l o s s ∂ x i k = ∑ j ∈ r ( i , j ) = 1 ( ∑ l = 1 d x i l w l j − y i j ) w k j \frac{\partial loss}{\partial x_{ik}} = \sum\limits_{j\in r(i,j)=1}(\sum\limits_{l=1}^dx_{il}w_{lj}-y_{ij})w_{kj} ∂xik∂loss=j∈r(i,j)=1∑(l=1∑dxilwlj−yij)wkj
∂ l o s s ∂ w k j = ∑ i ∈ r ( i , j ) = 1 ( ∑ l = 1 d x i l w l j − y i j ) x i k \frac{\partial loss}{\partial w_{kj}} = \sum\limits_{i\in r(i,j)=1}(\sum\limits_{l=1}^dx_{il}w_{lj}-y_{ij})x_{ik} ∂wkj∂loss=i∈r(i,j)=1∑(l=1∑dxilwlj−yij)xik
则梯度为:
Δ x = r . ( x w − y ) w T \Delta x = r.(xw-y)w^T Δx=r.(xw−y)wT
Δ w = x T [ ( x w − y ) . r ] \Delta w = x^T[(xw-y).r] Δw=xT[(xw−y).r]
其中:
.表示点乘法,无则表示矩阵相乘
上标T表示矩阵转置
梯度python
代码如下:
x_grads = np.dot(np.multiply(record,np.dot(x,w)-y),w.T)
w_grads = np.dot(x.T,np.multiply(record,np.dot(x,w)-y))
然后再进行梯度下降:
#梯度下降,更新参数
for i in range(n_iter):
x_grads = np.dot(np.multiply(record,np.dot(x,w)-y),w.T)
w_grads = np.dot(x.T,np.multiply(record,np.dot(x,w)-y))
x = alpha*x - lr*x_grads
w = alpha*w - lr*w_grads
其中:
n_iter:训练轮数
lr:学习率
alpha:权重衰减系数,用来防止过拟合
1.随机初始矩阵值
2.构造损失函数,求得矩阵参数梯度
3.进行梯度下降,更新矩阵参数值
4.喜好矩阵与内容矩阵相乘得到预测评分
5.根据预测评分进行推荐
本次使用电影评分数据为672
个用户对9123
部电影的评分记录,部分数据如下:
userId | movieRow | rating |
---|---|---|
1 | 30 | 2.5 |
7 | 30 | 3 |
31 | 30 | 4 |
32 | 30 | 4 |
其中:
userId:用户编号
movieRow:电影编号
rating:评分值
如:
1
对电影30
评分为2.5
分。7
对电影30
评分为3
分。然后,我们还有电影编号与电影名字对应的数据如下:
movieRow | title |
---|---|
0 | Toy Story (1995) |
1 | Jumanji (1995) |
2 | Grumpier Old Men (1995) |
3 | Waiting to Exhale (1995) |
其中:
movieRow:电影编号
title:电影名称
大家已经知道,要使用基于矩阵分解的协同过滤算法,首先得有用户与电影评分的矩阵,而我们实际中的数据并不是以这样的形式保存,所以在使用算法前要先构造出用户-电影评分矩阵,python
实现代码如下:
import numpy as np
#获取用户数与电影数
userNo = max(ratings_df['userId'])+1
movieNo = max(ratings_df['movieRow'])+1
#创建电影评分表
rating = np.zeros((userNo,movieNo))
for index,row in ratings_df.iterrows():
rating[int(row['userId']),int(row['movieRow'])]=row['rating']
构造出表格后,我们就能对用户进行电影推荐了。
# -*- coding: utf-8 -*-
import warnings
warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
#获取数据
ratings_df = pd.read_csv('real_ratings.csv')
movies_df = pd.read_csv('movies.csv')
userNo = max(ratings_df['userId'])+1
movieNo = max(ratings_df['movieRow'])+1
#创建电影评分表
rating = np.zeros((userNo,movieNo))
for index,row in ratings_df.iterrows():
rating[int(row['userId']),int(row['movieRow'])]=row['rating']
def recommend(userID,lr,alpha,d,n_iter,data):
'''
userID(int):推荐用户ID
lr(float):学习率
alpha(float):权重衰减系数
d(int):矩阵分解因子(即元素个数)
n_iter(int):训练轮数
data(ndarray):用户-电影评分矩阵
'''
#获取用户数与电影数
m,n = data.shape
#初始化参数
x = np.random.uniform(0,1,(m,d))
w = np.random.uniform(0,1,(d,n))
#创建评分记录表,无评分记为0,有评分记为1
record = np.array(data>0,dtype=int)
#梯度下降,更新参数
for i in range(n_iter):
x_grads = np.dot(np.multiply(record,np.dot(x,w)-data),w.T)
w_grads = np.dot(x.T,np.multiply(record,np.dot(x,w)-data))
x = alpha*x - lr*x_grads
w = alpha*w - lr*w_grads
#预测
predict = np.dot(x,w)
#将用户未看过的电影分值从低到高进行排列
for i in range(n):
if record[userID-1][i] == 1 :
predict[userID-1][i] = 0
recommend = np.argsort(predict[userID-1])
a = recommend[-1]
b = recommend[-2]
c = recommend[-3]
d = recommend[-4]
e = recommend[-5]
print('为用户%d推荐的电影为:\n1:%s\n2:%s\n3:%s\n4:%s\n5:%s。'\
%(userID,movies_df['title'][a],movies_df['title'][b],movies_df['title'][c],movies_df['title'][d],movies_df['title'][e]))
recommend(666,1e-4,0.999,20,100,rating)
>>>
为用户666推荐的电影为:
1:Aquamarine (2006)
2:It's a Boy Girl Thing (2006)
3:Kill the Messenger (2014)
4:Onion Field, The (1979)
5:Wind Rises, The (Kaze tachinu) (2013)。
[数据下载:提取码:p8u0]( 链接 :https://pan.baidu.com/s/1R87yrW7bcXSEo6ztXxhqpA )