将mn维的共现矩阵R分解为mk维的用户矩阵U和k*n维的物品矩阵V相乘的形式。其中m是用户数量,n是物品数量,k是隐向量维度。k的大小决定了隐向量表达能力的强弱。k取值越小,隐向量的表达能力就越弱;反之,k取值越大,隐向量表达能力越强
实例:
基于用户矩阵U和物品矩阵V,用户u对物品i的预估评分为:
r ^ u i = q i T p u \hat{r}_{ui}=q_{i}^{T}p_{u} r^ui=qiTpu
参数说明:
qi:物品i的隐向量
pu:用户u的隐向量
这是一篇讲解矩阵分解非常详细的博文,放在这里,便于日后自己多读几遍,全面深入理解矩阵分解算法
基于矩阵分解的推荐算法
程序亲测有效,程序中附有详细说明
import numpy as np
import pandas as pd
import os
import time
import math
'''
说明: 使用的数据集 MovieLens 100K
数据集下载地址: https://grouplens.org/datasets/movielens/100k/
'''
'''
参数说明:
R:用户-物品对应的共现矩阵 m*n
P:用户因子矩阵 m*k
Q:物品因子矩阵 k*n
K:隐向量的维度
steps:最大迭代次数
alpha:学习率
Lambda:L2正则化的权重系数
'''
# 将矩阵R分解成P,Q
def matrix_factorization(R, P, Q, K, steps, alpha=0.05, Lambda=0.002):
# 总时长
sum_st = 0
# 前一次的损失大小
e_old = 0
# 程序结束的标识
flag = 1
# 梯度下降结束条件1:满足最大迭代次数
for step in range(steps):
# 每次跌代开始的时间
st = time.time()
cnt = 0
e_new = 0
for u in range(1, len(R)):
for i in range(1, len(R[u])):
if R[u][i] > 0:
eui = R[u][i] - np.dot(P[u, :], Q[:, i])
for k in range(K):
temp = P[u][k]
P[u][k] = P[u][k] + alpha * eui * Q[k][i] - Lambda * P[u][k]
Q[k][i] = Q[k][i] + alpha * eui * temp - Lambda * Q[k][i]
for u in range(1, len(R)):
for i in range(1, len(R[u])):
if R[u][i] > 0:
cnt += 1
e_new = e_new + pow(R[u][i] - np.dot(P[u, :], Q[:, i]), 2)
e_new = e_new / cnt
et = time.time()
sum_st = sum_st + (et - st)
# 第一次迭代不执行前后损失之差
if step == 0:
e_old = e_new
continue
# 梯度下降结束条件2:loss过小,跳出
if e_new < 1e-3:
flag = 2
break
# 梯度下降结束条件3:前后loss之差过小,跳出
if (e_old - e_new) < 1e-10:
flag = 3
break
else:
e_old = e_new
print(f'--------Summary---------\nThe type of jump out:{flag}\nTotal steps:{step+1}\nTotal time:{sum_st}\n'
f'Average time:{sum_st / (step+1)}\nThe e is :{e_new}')
return P, Q
#查看数据内容
def view_data():
rtnames = ['user', 'item', 'score', 'time']
data = pd.read_csv("D:\\YSA\\MovieLens\\ml-100k\\ML100K.txt", sep='\t', header=None, names=rtnames)
# print(data.head())
# 用户数量
u_cnt = len(np.unique(data[rtnames[0]]))
# 项目数量
i_cnt = len(np.unique(data['item']))
# 记录数量
r_cnt = len(data)
# 用户最小访问项目数量
u_cnt_min = min(data['user'].value_counts().values)
# 用户平均访问项目数量
u_cnt_avg = r_cnt / u_cnt
# 项目最小被访问量
i_cnt_min = min(data['item'].value_counts().values)
# 项目平均被访问量
i_cnt_avg = r_cnt / i_cnt
# 分割数据集成训练集、测试集
def split_data(dformat):
# 读取原始数据
rating = pd.read_csv("D:\\YSA\\MovieLens\\ml-100k\\ML100K.txt", sep='\t', header=None, names=dformat)
# 按照时间顺序排序
rating.sort_values(by=['time'], axis=0, inplace=True)
# 按照时间顺序值8:2,确定边界线
boundary = rating['time'].quantile(0.8)
# 按时间分界点切分数据,生成训练集
train = rating[rating['time'] < boundary]
# 训练集按用户、时间顺序排序
train.sort_values(by=['user', 'time'], axis=0, inplace=True)
# 按时间分界点切分数据,生成测试集
test = rating[rating['time'] >= boundary]
# 测试集按用户、时间顺序排序
test.sort_values(by=['usr', 'time'], axis=0, inplace=True)
data = pd.concat([train, test])
# 确认目录是否存在
if os.path.exists("D:\\YSA\\MovieLens\\ml-100k"):
pass
else:
os.mkdir("D:\\YSA\\MovieLens\\ml-100k")
# 将训练集、测试集写入文件中
train.to_csv("D:\\YSA\\MovieLens\\ml-100k\\ML100K_Train.txt", sep=',', index=False, header=None)
test.to_csv("D:\\YSA\\MovieLens\\ml-100k\\ML100K_test.txt", sep=',', index=False, header=None)
print(f'split data complete!')
# 获取本地数据
def getData(path, dformat):
# 读取用户-共现矩阵数据
rating = np.loadtxt(path+"\\Basic_MF\\rating.txt", delimiter=',', dtype=float)
# 读取训练集数据
trainData = pd.read_csv(path+"\\ML100K_Train.txt", sep=',', header=None, names=dformat)
# 读取测试集数据
testData = pd.read_csv(path+"\\ML100K_test.txt", sep=',', header=None, names=dformat)
data = pd.concat([trainData, testData])
# 总用户数量
all_user = np.unique(data['user'])
# 总项目数量
all_item = np.unique(data['item'])
return rating, trainData, testData, all_user, all_item
# 生成用户-物品矩阵并保存到本地文件中
def getUserItem(path, train, all_user, all_item):
train.sort_values(by=['user, item'], axis=0, inplace=True)
# 用户-项目共现矩阵行数
num_user = len(all_user)+1
# 用户-项目共现矩阵列数
num_item = len(all_item)+1
# 用户-项目共现矩阵初始化
rating_mat = np.zeros([num_user, num_item], dtype=int)
# 用户-项目共现矩阵赋值
for i in range(len(train)):
user = train.iloc[i]['user']
item = train.iloc[i]['item']
score = train.iloc[i]['score']
rating_mat[user][item] = score
# 判断文件夹是否存在
if os.path.exists(path+"\\BasicMF"):
pass
else:
os.mkdir(path+"\\BasicMF")
# 保存用户-项目共现矩阵到文件
np.savetxt(path+"\\BasicMF\\rating.txt", rating_mat, fmt='%d', delimiter=',', newline='\n')
print(f'generate rating matrix complete!')
# 训练
def train(path, rating, K, steps):
R = rating
M = len(R)
N = len(R[0])
# 用户矩阵初始化
P = np.random.normal(loc=0, scale=0.01, size=(M, K))
# 项目矩阵初始化
Q = np.random.normal(loc=0, scale=0.01, size=(K, N))
P, Q = matrix_factorization_BiasSVD(R, P, Q, K, steps)
# 判断文件夹是否存在
if os.path.exists(path + "\\Basic_MF"):
pass
else:
os.mkdir(path + "\\Basic_MF")
# 将P,Q保存到文件
np.savetxt(path+"\\Basic_MF\\userMatrix.txt", P, fmt="%.6f", delimiter=',', newline='\n')
np.savetxt(path+"\\Basic_MF\\itemMatrix.txt", Q, fmt="%.6f", delimiter=',', newline='\n')
print("train complete!")
# 生成topk推荐列表
def topK(dic, k):
keys = []
values = []
for i in range(k):
key, value = max(dic.items(), key=lambda x: x[1])
keys.append(key)
values.append(value)
dic.pop(key)
return keys, values
# 测试
def test(path, trainData, testData, all_item, k):
# 读取用户矩阵
P = np.loadtxt(path+"\\Basic_MF\\userMatrix.txt", delimiter=',', dtype=float)
# 读取项目矩阵
Q = np.loadtxt(path+"\\Basic_MF\\itemMatrix.txt", delimiter=',', dtype=float)
# 测试集中的用户集合
testUser = np.unique(testData['user'])
# 测试集的长度
test_lenght = len(testData)
Hits = 0
MRR = 0
NDCG= 0
# 开始时间
st = time.time()
for user_i in testUser:
# 测试集第i个用户在训练集已访问的项目
visited_list = list(trainData[trainData['user'] == user_i]['item'])
# 没有训练数据,跳过
if len(visited_list) == 0:
continue
# 测试集第i个用户的访问项目并去重
test_list = list(testData[testData['user'] == user_i]['item'].drop_duplicates())
# 测试集第i个用户的访问项目中去除该用户在训练集已访问的项目
test_list = list(set(test_list) - set(test_list).intersection(set(visited_list)))
# 测试集第i个用户的访问项目为空,跳过
if len(test_list) == 0:
continue
# 生成测试集第i个用户未访问的项目:评分对
poss = {}
for item in all_item:
if item in visited_list:
continue
else:
poss[item] = np.dot(P[user_i, :], Q[:, item])
# 生成测试集第i个用户的推荐列表
ranked_list, test_score = topK(poss, k)
# 命中测试集第i个用户访问项目的列表
h = list(set(test_list).intersection(set(ranked_list)))
Hits += len(h)
for item in test_list:
for i in range(len(ranked_list)):
if item == ranked_list[i]:
MRR += 1 / (i+1)
NDCG += 1 / (math.log2(i+1+1))
else:
continue
HR = Hits / test_lenght
MRR /= test_lenght
NDCG /= test_lenght
# 结束时间
et = time.time()
print("HR@10:%.4f\nMRR@10:%.4f\nNDCG@10:%.4f\nTotal time:%.4f" % (HR, MRR, NDCG, et-st))
if __name__ == '__main__':
rtnames = ['user', 'item', 'score', 'time']
path = "D:\\YSA\\MovieLens\\ml-100k"
rating, trainData, testData, all_user, all_item = getData(path, rtnames)
# train(path, rating, 30, 10)
test(path, trainData, testData, all_item, 10)
1.https://blog.csdn.net/qq_34862636/article/details/105432139