吴恩达机器学习作业十二:协同过滤(电影推荐系统)
数据集在作业一
协同过滤
基于模型的协同过滤(Model-based Collaborative Filtering)
思路:通过机器学习或深度学习模型对用户 - 物品交互数据进行建模,学习用户和物品的潜在特征,再基于特征计算推荐分数。
常见模型:
矩阵分解(Matrix Factorization,如 SVD):将稀疏的用户 - 物品评分矩阵分解为用户潜在特征矩阵和物品潜在特征矩阵,通过矩阵相乘预测未评分的物品
这个电影推荐系统涉及到的参数
X:电影特征矩阵,(nm,nf),nm:电影总数,nf:潜在特征数量,(i,j)代表第i部电影对于第j个特征的所具有的强度
Theta:用户偏好矩阵,(nu,nf),nu:用户总数,nf:与上面一样,(i,j)代表第i个用户对于第j个特征的喜爱程度
Y:评分矩阵,(nm,nu),(i,j)表示第i个电影的第j个用户的评分
注意这里的特征没有具体定义和标签,评分矩阵也不是具体的标签,没有具体的定义,这也就是它不同于传统监督学习的地方,这里的X的(i,j)表示第i部电影对于第j个特征的所具有的强度,这个特征可能是喜剧或者动作戏,这是参数从训练中学到的。所以X和Theta都是参数,需要训练。
R
:指示矩阵(Indicator Matrix),其核心作用是标记哪些用户对哪些物品有过评分,从而在计算损失时只关注 “已评分的数据”,忽略 “未评分的数据”。
解释
我们这里要实现一个电影推荐系统,它是根据之前的评分矩阵Y来对参数X(电影特征矩阵)和Theta(用户偏好矩阵)进行优化,这样我们就可以根据已经优化好的参数得出用户还未评分的电影的预计评分,这样就可以给用户推荐预计评分最佳的电影,同时我们对于新用户(只看过几部的),也可以加入数据集进行优化,然后得出新用户的预计评分最高的电影。
具体代码实现
读取数据
import numpy as np
import matplotlib.pyplot as plt
import scipy.io as sio# 读取数据
mat=sio.loadmat("ex8_movies.mat")
R=mat['R']
Y=mat['Y']
print(R.shape,Y.shape)
data=sio.loadmat("ex8_movieParams.mat")
# print(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']
类型转换
nu = int(nu)#将用户数量,电影数量,特征数量由数组转换为整数
nm = int(nm)
nf = int(nf)
序列化和解序列化参数
这个的目的是为了方便输入参数,使用 scipy.optimize.minimize
时,目标函数(fun
)和梯度函数(jac
)的参数列表必须完全一致,而X0作为待优化参数必须是一维参数。
# 序列化和解序列化参数
def serialize(X,Theta):return np.append(X.flatten(),Theta.flatten())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
损失函数
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
这里因为X和Theta都是参数,所以有两个正则化项。
梯度函数
def gradient(params, Y, R, nm, nu, nf, lamda):X, Theta = deserialize(params, nm, nu, nf) # X: (1682, 10), Theta: (944, 10)# 计算预测误差(此时 X@Theta.T 形状为 (1682, 944),与 Y 一致)error = (X @ Theta.T - Y) * R # 误差矩阵形状 (1682, 944)# 计算梯度(维度匹配)X_grad = error @ Theta + lamda * X # X_grad 形状 (1682, 10),与 X 一致Theta_grad = error.T @ X + lamda * Theta # Theta_grad 形状 (944, 10),与 Theta 一致return serialize(X_grad, Theta_grad)
添加一个新客户
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
把新客户加入Y,并扩大参数
Y = np.c_[my_ratings, Y]
R = np.c_[(my_ratings != 0), R]
均值归一化
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) * Rreturn Y_norm, Y_meanY_norm, Y_mean = normalizeRatings(Y, R)
使用均值归一化的原因:
解决冷启动问题:新用户 / 新物品的预测合理性
在推荐系统中,常会遇到以下情况:
新用户:从未对任何物品评分(即其评分记录 R 全为 0);
新物品:从未被任何用户评分(即其评分记录 R 全为 0)。
如果不做均值归一化,模型对这类用户 / 物品的预测评分可能为 0(或初始随机值),这显然不合理(例如,电影评分通常在 1-5 分之间)。
效果:对于无评分的新用户 j,模型会用物品的平均评分 mu[i] 作为初始预测(而非 0),更符合实际场景(例如,新用户未评分时,推荐大众平均喜欢的物品)。
参数初始化
nu+=1
X = np.random.random((nm, nf)) # 电影特征
Theta = np.random.random((nu, nf)) # 用户参数
params = serialize(X, Theta) # 序列化
lamda = 5
模型训练
from scipy.optimize import minimizeres = minimize(x0=params,fun=costFunc,args=(Y_norm, R, nm, nu, nf, lamda),method='TNC',jac=gradient,options={'maxfun': 100})params_fit = res.x
fit_X, fit_Theta = deserialize(params_fit, nm, nu, nf) # 解序列化得到X,Theta
预测
Y_pre = fit_X @ fit_Theta.T # 预测用户评分
# print(Y_pre)
print(Y_mean)
y_pre = Y_pre[:, 0] + Y_mean.flatten() # 取第一列加上评分均值
index = np.argsort(-y_pre) # 加-号从大到小排序
print(index[:10])
展示
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:])) # 不要电影前面的数字序号# 推荐十部电影
for i in range(10):print(index[i], movies[index[i]], y_pre[index[i]])
总结
读取数据——序列化和解序列化——损失函数和对应梯度函数——添加新客户——训练——预测