8.1推荐系统
实现协同过滤算法并将它运用在电影评分的数据集上,最后根据新用户的评分来给新用户推荐10部电影。
这个电影评分数据集由1到5的等级组成。
数据集有nu = 943个用户和nm = 1682部电影。
在计算完协同过滤的代价函数以及梯度后,将使用牛顿共轭梯度法求得参数。
数据集中,Y是一个(1682, 943)的矩阵,存储了从1到5的评分,矩阵R为二值指标矩阵,其中如果用户j对电影i进行评级,R(i, j)=1,否则R(i,j)=0。
协同过滤的目的是预测用户尚未评分的电影的评分,即R(i,j)=0的条目。
这样就可以向用户推荐预测评分最高的电影。
X是电影的特征矩阵,Theta是用户的特征矩阵,X的第i行对应x (i) ,表示第i部电影的特征向量(即描述第i部电影内容的特征量),Theta的第j行对应θ (j),表示第j个用户的特征向量(即第j个用户对不同类型电影的偏好),这里x (i),θ (j)都是100维的向量,因此X的维数是(1682,100),Theta的维数是(943,100)。
线性回归的代价函数和梯度
协同过滤
根据电影的特征来预测用户参数,再根据得到的用户参数来预测电影特征,循环这个过程
协同过滤过程
1 随机初始化 ,为较小的数值
2 用梯度下降优化(,)到损失最小得到优化后的电影特征和用户参数
3 用得到的特征和参数计算评分
低秩矩阵分解
寻找和电影i相关的电影j
衡量两个电影的相关度,即两个电影的特征向量的距离∥x (i)−x (j)∥,想要找到5部与电影i最相似的电影,只需找到∥x (i)−x (j)∥最小的五个
均值标准化
Y−μ,可以避免出现因为某用户对所有电影没有评分,而导致该用户所有预测的评分都为零的情况。
预测完之后加回均值μ。
相当于预测出的没有评分的用户的评分,为所有用户评分的均值。
1 argsort(a, axis=-1, kind=‘quicksort’, order=None):
argsort输出的是排序后当前位置所应该放的值的索引,比如 b=[‘b’, ‘a’, ‘c’],排序后第0个位置应该放’a’(索引为1),第1个位置应该放’b’(索引为0),第2个位置应该放’c’(索引为2),故最终输出为[1 0 2]
代码实现1 导入数据并提取数据
#给用户推荐电影
import numpy as np
import matplotlib.pyplot as plt
import scipy.io as sio
mat = sio.loadmat('ex8_movies.mat')
mat.keys()#dict_keys(['__header__', '__version__', '__globals__', 'Y', 'R'])
Y,R = mat['Y'],mat['R']
Y.shape,R.shape#((1682, 943), (1682, 943))
data = sio.loadmat('ex8_movieParams.mat')
data.keys()#dict_keys(['__header__', '__version__', '__globals__', 'X', 'Theta', 'num_users', 'num_movies', 'num_features'])
X,Theta,nu,nm,nf = data['X'],data['Theta'],data['num_users'],data['num_movies'],data['num_features']
X.shape,Theta.shape,nu,nm,nf
# ((1682, 10),
# (943, 10),
# array([[943]], dtype=uint16),
# array([[1682]], dtype=uint16),
# array([[10]], dtype=uint8))
nu = int(nu)#将用户数量,电影数量,特征数量由数组转换为整数
nm = int(nm)
nf = int(nf)
nu,nm,nf
2 序列化与解序列化
#1.序列化参数
def serialize(X,Theta):
return np.append(X.flatten(),Theta.flatten())#降维
#2.解序列化参数
def deserialize(params,nm,nu,nf):#params:X,theta序列化以后
X = params[:nm*nf].reshape(nm,nf)
Theta = params[nm*nf:].reshape(nu,nf)
return X,Theta
3 损失函数
# 损失函数
def costFunc(params,Y,R,nm,nu,nf,lamda):
X,Theta = deserialize(params,nm,nu,nf)
cost = 0.5 * np.square((X @ Theta.T - Y) * R).sum() #这里要点乘R,也就是对应位置相乘,R为0没打分,需要预测,没有误差
reg1= 0.5 * lamda * np.square(X).sum()#正则项
reg2= 0.5 * lamda * np.square(Theta) .sum()
return cost + reg1 + reg2
#取子数组减少损失函数验证时间
users = 4
movies = 5
features = 3
X_sub = X[:movies,:features]# 5 3
Theta_sub = Theta[:users,:features]#4 3
Y_sub = Y[:movies,:users]#
R_sub = R[:movies,:users]
cost1 = costFunc(serialize(X_sub,Theta_sub ),Y_sub,R_sub,movies,users,features,lamda = 0)
cost1#22.224603725685675
cost1 = costFunc(serialize(X_sub,Theta_sub ),Y_sub,R_sub,movies,users,features,lamda = 1.5)
cost1#31.344056244274217
4 梯度
#4.梯度
def costGradient(params,Y,R,nm,nu,nf,lamda):
X,Theta = deserialize(params,nu,nm,nf)#解序列化
X_grad = ((Theta @ X.T - Y) * R).T @ Theta + lamda * X #(5 ,3)
Theta_grad = ((Theta @ X.T - Y) * R) @ X + lamda * Theta #(4, 3)
return serialize(X_grad,Theta_grad) #传出序列化数据
grad1 = costGradient(serialize(X_sub,Theta_sub ),Y_sub,R_sub,movies,users,features,lamda = 0)
#grad1
5 添加自己的评分
#5.添加一个新用户
my_ratings = np.zeros((nm, 1))
#添加电影评分
my_ratings[9] = 5
my_ratings[66] = 5
my_ratings[96] = 5
my_ratings[121] = 4
my_ratings[148] = 4
my_ratings[285] = 3
my_ratings[490] = 4
my_ratings[599] = 4
my_ratings[643] = 4
my_ratings[958] = 5
my_ratings[1117] = 3
#6.均值归一化
def normalizeRatings(Y,R):
Y_mean = (Y.sum(axis = 1)/R.sum(axis = 1)).reshape(-1,1)#返回二维数组 # 这里求均值后是一维数组,为了方便,reshape成二维,可以直接矩阵相减,将(1682,)——>(1682,1)
Y_norm = (Y - Y_mean)*R
return Y_norm,Y_mean
Y_norm, Y_mean= normalizeRatings(Y,R) #错误点只进行了y_norm!!!!!!!!
#7.参数初始化
X = np.random.random((nm,nf))#电影特征
Theta = np.random.random((nu,nf))#用户参数
params = serialize(X,Theta)#序列化
lamda = 5
Y.shape#(1682, 943)
6 训练模型,拟合,预测
#8.模型训练
from scipy.optimize import minimize
res = minimize(x0 = params,
fun = costFunc,
args = (Y_norm,R,nm,nu,nf,lamda),
method = 'TNC',
jac = costGradient,
options = {'maxiter': 100})
params_fit = res.x
fit_X,fit_Theta = deserialize(params_fit,nm,nu,nf)#解序列化得到X,Theta
#9.预测
Y_pre = fit_X@fit_Theta.T#预测用户评分
print(Y_pre)
y_pre = Y_pre[:,-1] + Y_mean.flatten()#取评分矩阵中最后一列(后来添加进去的我作为用户的预测值)加上评分均值
index = np.argsort(-y_pre)#加-号从大到小排序
index[:10]#查看排名前十的电影array([1448, 660, 1121, 1535, 175, 97, 299, 496, 1624, 271],dtype=int64)
7 推荐电影
movies = []
with open('movie_ids.txt','r',encoding = 'latin 1')as f:
for line in f:
tokens = line.strip().split(' ')#split按空格分割
#strip( ) 用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
movies.append(' '.join(tokens[1:]))#不要电影前面的数字序号
len(movies)#1682
#推荐十部电影
for i in range(10):
print(index[i],movies[index[i]],y_pre[index[i]])
'''1499 Santa with Muscles (1996) 9.192908079544114
1466 Saint of Fort Washington, The (1993) 8.67795917524353
1188 Prefontaine (1997) 8.645424940425478
1292 Star Kid (1997) 8.321090861320673
1650 Spanish Prisoner, The (1997) 8.299654010882321
813 Great Day in Harlem, A (1994) 8.262249047464023
155 Reservoir Dogs (1992) 8.226853101881623
600 For Whom the Bell Tolls (1943) 8.220591016552554
1652 Entertaining Angels: The Dorothy Day Story (1996) 8.098208777452498
302 Ulee's Gold (1997) 8.083023368123555'''
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)