当前位置: 首页 > news >正文

吴恩达机器学习作业十二:协同过滤(电影推荐系统)

数据集在作业一

协同过滤

基于模型的协同过滤(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]])

总结

读取数据——序列化和解序列化——损失函数和对应梯度函数——添加新客户——训练——预测

http://www.dtcms.com/a/361210.html

相关文章:

  • 广电手机卡到底好不好?
  • Git基础使用和PR贡献
  • .Net程序员就业现状以及学习路线图(二)
  • Android面试指南(六)
  • 大模型落地全流程实践:从技术选型到企业级部署
  • 音视频开发入门:FFmpeg vs GStreamer,新手该如何选择?
  • 松灵斯坦福Mobile ALOHA同款 | 通过低成本全身远程操作实现双手机器人移动操控学习
  • 01数据结构-红黑树
  • 永磁同步电机无速度算法--高频脉振方波注入法(测量轴系转子位置误差信号解耦处理)
  • Spark引擎中RDD的性质
  • 【牛客JZ31】—栈的压入弹出序列判断算法详解
  • 【73页PPT】MES应用介绍(附下载方式)
  • SpringBoot @RefreshScope 注解的极致玩法
  • SpringCloud-服务注册-服务发现
  • AI瘦身狂魔!微软推出原生1-bit大模型,性能不减,内存仅需同行零头!
  • 博0进化版
  • 9月校招难题怎么解?AI面试精准匹配人才
  • 系统架构设计师备考第12天——计算机语言-建模形式化语言
  • Windows 命令行:cd 命令1,cd 命令的简单使用
  • 数据结构:单链表的应用(力扣算法题)第二章
  • APP性能测试,你需要关注哪些指标?
  • React 学习笔记3 生命周期 受控/非受控组件
  • 阿里云代理商:轻量应用服务是什么?怎么用轻量应用服务器搭建个人博客?
  • 大模型落地:从微调到部署的全景式实战指南
  • MFC应用防止多开
  • Prometheus Alertmanager 告警组件学习
  • Linux 正则表达式与grep命令
  • 车载卫星通信:让自动驾驶“永不掉线”?
  • Kafka面试精讲 Day 4:Consumer消费者模型与消费组
  • 指针数组与数组指针的区别