avatar

基于矩阵分解的推荐系统

矩阵分解简介


矩阵分解活跃在推荐领域,基于SVD的推荐系统也是一种矩阵分解。给定一个用户评分表,通常是一个大矩阵,M行N列,M代表用户数,n代表项目数。而且这个矩阵在实践中非常稀疏。我们希望我们可以通过现有的用户评分来预测用户对未定价商品的评价。通过矩阵分解,可以挖掘用户和项目的潜在因素以估计缺失值。

交替最小二乘法

ALS通常用于基于矩阵分解的推荐系统中。例如:将用户(user)对商品(item)的评分矩阵分解为两个矩阵:一个用户因子矩阵X(n_user,K),一个物品因子矩阵Y(n_item,K)。将矩阵分解后再合并,可以达到缺失值填充的效果,因此我们就可以基于这个新填充的矩阵来进行推荐。

以下公式取

分解的公式为:

其中,K称为隐含特征数,一般取

为了使低维矩阵逼近原始评分矩阵R,需要最小化平方损失函数:

其中:
表示用户i的偏好的隐含特征向量,维度为(1,K);
表示商品j包含的隐含特征向量,维度为(1,K);
表示用户i对商品j的评分;
表示用户i对商品j的评分的近似。

损失函数一般需要加入正则化项来避免过拟合,因此我们使用L2正则化:

其中,是正则化项的系数。

交替最小二乘法的思想为:

  • 先固定X,求损失函数对Y的每一行的偏导数,令偏导数为0,得到

  • 然后固定Y,求损失函数对X的每一行的偏导数,令偏导数为,得到

  • 最后计算,然后求出

重复以上步骤直到收敛,画出随迭代次数变化的曲线。

数据集


  1. 采用MovieLens-100k数据集
  2. u.data – 由943个用户对1682个电影的10000条评分组成。每个用户至少评分20部电影。用户和电影从1号开始连续编号。数据是随机排序的。

  1. 数据集u1.base / u1.test到u5.base / u5.test都是将u.data数据集按照80% / 20%的比例分割的训练集和测试集。

  2. 也可按照自己的评估方法划分训练集和验证集。

代码示例


导入依赖

1
2
3
4
5
import numpy as np
import pandas as pd
import pickle
from sklearn.datasets import load_svmlight_file
import matplotlib.pyplot as plt

读取数据

1
2
3
4
5
6
7
8
9
# 读取数据,划分训练集和测试集
path = 'ml-100k/'
train_set = pd.read_csv(path+'u1.base', names=['user id','item id','rating','timestamp'], delimiter='\t')
test_set = pd.read_csv(path+'u1.test', names=['user id','item id','rating','timestamp'], delimiter='\t')

train_df = train_set.copy()
train_df['user_item'] = train_df['user id']*10000 + train_df['item id']

train_df.head()

设置下标

1
2
3
4
# 设置下标
user_id = train_set['user id'].unique()
item_id = train_set.set_index('user id')
rates = train_set.set_index(['user id','item id'])

初始化评价矩阵

1
2
3
4
5
6
# 初始化评价矩阵
evaluation = np.zeros((user_id.size,1682))
for uid in range(943):
item = np.array(item_id.loc[(uid+1),'item id']) - 1
rate = np.array(rates.loc[(uid+1),'rating'])
evaluation[uid,item] = rate

定义损失函数

1
2
3
4
5
6
7
8
9
# 定义损失函数
def compute_loss(R, X, Y, penalty):
R = np.matrix(R) # m*n
X = np.matrix(X) # m*k
Y = np.matrix(Y) # n*k
loss = np.power((R - X.dot(Y.T)),2)
reg = penalty*(np.power(X,2).dot(np.power(Y.T,2)))
loss = loss + reg
return loss.mean()

定义交替计算函数

1
2
3
4
5
6
7
8
9
def compute_XY(R, X, Y, penalty, num_iters):
loss = np.zeros((num_iters,1))
for i in range(num_iters):
X = np.linalg.inv((Y.T.dot(Y) + penalty*np.identity(K))).dot(Y.T).dot(R.T).T
Y = np.linalg.inv((X.T.dot(X) + penalty*np.identity(K))).dot(X.T).dot(R).T
loss[i] = compute_loss(R,X,Y,penalty)
# loss[num_iters] = compute_loss(R,X,Y,penalty)
R = X.dot(Y.T)
return loss, R

定义惩罚参数以及隐含特征量K

1
2
3
4
# 定义惩罚参数
penalty = 0.1
# 定义隐含特征量K
K = 10

初始化用户因子矩阵P和物品因子矩阵Q

1
2
3
4
5
# 初始化用户因子矩阵P和物品因子矩阵Q
user = np.random.rand(evaluation.shape[0],K)
item = np.random.rand(evaluation.shape[1],K)
user_df = pd.DataFrame(user)
item_df = pd.DataFrame(item)
1
user_df.head()

1
item_df.head()

计算损失值

1
compute_loss(evaluation, user, item, penalty)

6.870335800342345

画出损失值随迭代次数变化的曲线图

1
2
3
4
5
6
7
8
num_iters = 20
J_history, eval_matrix = compute_XY(evaluation, user, item, penalty, num_iters)

fig, ax = plt.subplots(figsize=(12,8))
plt.plot(np.arange(num_iters), J_history)
ax.set_xlabel('iterations')
ax.set_ylabel('loss')
plt.show()

查看合并后的评价矩阵

1
2
3
4
5
df = pd.DataFrame(eval_matrix)
df = df.round()
df[df<=0] = 0
df[df>5] = 5
df.head()

Author: WJZheng
Link: https://wellenzheng.github.io/2020/04/13/%E5%9F%BA%E4%BA%8E%E7%9F%A9%E9%98%B5%E5%88%86%E8%A7%A3%E7%9A%84%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment