传统的SVD方法需要将rating分解成用户向量p和物品向量q,通常大型系统中p和q的维数都比较高,例如电商系统中,用户的维数甚至达到上亿维,进行这样的大矩阵分解还是比较困难的。另外,通常rating中包含大量的缺失值,一般采用平均值等方法来填充,但是这些填充值本身就不一定准确,因此会一定程度上影响后续的矩阵分解结果的准确性。所以一般采用近似的方法来构造用户向量p和物品向量q,常用的方法包括随机梯度下降sgd、交叉最小二乘ALS等,本文主要介绍随机梯度下降法。
随机梯度下降法,首先需要构造损失函数,其中rui表示实际的rating,qTp表示预测值,损失函数前半部分表示rating的平方损失,后半部分为p、q向量的惩罚项,加上这部分是为了防止过拟合。接着需要求出p和q的导数,用于梯度下降。
(1)
(2)
上面公式(1)表示rating的预测误差,公式(2)表示q和p梯度下降后的值,只需要对存在的rating项进行逐个迭代,不需要填充缺失值。另外可以通过多轮迭代至误差收敛为止,这样可以找到合适的p和q。
import numpy as np
import pandas as pd
class SVD:
"""
ratings: 训练数据, n*3维数组 (user, item, rating)
K: 隐向量维数
Lambda: 惩罚系数
gamma: 学习率
steps: 迭代次数
"""
def __init__(self, ratings, logger=None, normalize=False, K=40, Lambda=0.005, gamma=0.02, steps=40):
# 字符串数组转换成数字数组
# user, item字符串映射为数字
self.ratings = []
self.user2id = {}
self.item2id = {}
# user已推荐过的items
self.userRecItems = {}
user_id = 0
item_id = 0
if normalize:
ratings[:,2] = self.minMaxScaler(ratings[:,2])
for user, item, r in ratings:
if user not in self.userRecItems:
self.userRecItems[user] = set()
new_tup = []
if user not in self.user2id:
self.user2id[user] = user_id
new_tup.append(user_id)
user_id += 1
e