☰
AutoRec 实战指南:用自编码器架构做基于显式反馈的评分预测
2026/10/3 13:03:25 网站建设 项目流程
  • 文档
  • 教程
  • 人工智能
  • 深度学习
  • NLP
  • 计算机视觉
  • 强化学习

【免费下载链接】d2l-en

Interactive deep learning book with multi-framework code, math, and discussions. Adopted at 500 universities from 70 countries including Stanford, MIT, Harvard, and Cambridge.

项目地址:https://gitcode.com/gh_mirrors/d2/d2l-en
点击查看免费下载

本指南讲解《Dive into Deep Learning》推荐系统章节中的 AutoRec 模型——一种用自编码器(Autoencoder)架构实现协同过滤的非线性神经网络模型。你将掌握 AutoRec 的模型数学定义、基于 MXNet 的完整实现(含梯度掩码、Dropout、专用评估器),并能在 MovieLens 100K 数据集上完成端到端训练与 RMSE 评估,理解它相比矩阵分解模型提升评分预测能力的关键原理。

从矩阵分解到非线性协同过滤:AutoRec 的动机

推荐系统章节之前介绍的矩阵分解模型(matrix factorization)将用户-物品交互矩阵分解为两个低秩矩阵的乘积,是一种经典的模型驱动协同过滤方法,在评分预测任务上表现良好。但矩阵分解本质上是一个线性模型:

$$ \hat{\mathbf{R}} = \mathbf{P}\mathbf{Q}^\top $$

其中 $\mathbf{P} \in \mathbb{R}^{m \times k}$、$\mathbf{Q} \in \mathbb{R}^{n \times k}$ 分别是用户与物品的潜因子矩阵。线性模型的表达能力有限,难以捕获用户偏好中那些复杂的非线性、细粒度关联模式——例如用户对"导演、类型、年代"组合的偏好往往是交互式的而非线性加和的。

AutoRec(Sedhain et al., 2015)正是为解决这一局限而提出的:它把协同过滤(Collaborative Filtering, CF)问题重新表述为一个自编码器结构,将非线性变换引入显式反馈建模。根据万能近似定理,神经网络可以逼近任意连续函数,因此 AutoRec 能够弥补矩阵分解的线性限制、丰富其表达能力。这与本章 推荐系统概述 中对"许多基于模型的 CF 方法可以通过神经网络扩展"的论述一脉相承。

AutoRec 与经典自编码器的同与异:

  • 相同之处:结构与自编码器一致,包含输入层、隐藏层和重建(输出)层。自编码器是一种学习"把输入复制到输出"的神经网络,从而将输入编码为隐藏(通常是低维)表示。
  • 不同之处:传统自编码器关注隐藏表示本身,而 AutoRec 聚焦于学习/重建输出层。它以部分观测的交互矩阵为输入,目标是重建出一个完整的评分矩阵;输入中的缺失条目通过输出层的重建被填充,从而用于推荐。

AutoRec 有两种变体:基于用户的(user-based)和基于物品的(item-based)。原书为简洁起见只介绍 item-based 变体,user-based 变体可以对称地推导出来,本文同样以 item-based 为主线。

模型定义:从单列输入到整列重建

设 $\mathbf{R}_{*i}$ 表示评分矩阵的第 $i$ 列(即物品 $i$ 被所有用户评分的向量),未知评分默认置零。AutoRec 的神经网络架构定义为:

$$ h(\mathbf{R}{*i}) = f(\mathbf{W} \cdot g(\mathbf{V} \mathbf{R}{*i} + \mu) + b) $$

其中 $f(\cdot)$ 与 $g(\cdot)$ 为激活函数,$\mathbf{W}$、$\mathbf{V}$ 为权重矩阵,$\mu$、$b$ 为偏置项,$h(\cdot)$ 代表 AutoRec 的整个网络。输出 $h(\mathbf{R}_{*i})$ 即评分矩阵第 $i$ 列的重建结果。

训练目标是最小化如下重建误差:

$$ \underset{\mathbf{W},\mathbf{V},\mu, b}{\mathrm{argmin}} \sum_{i=1}^M{\parallel \mathbf{R}{*i} - h(\mathbf{R}{*i})\parallel_{\mathcal{O}}^2} +\lambda(| \mathbf{W} |_F^2 + | \mathbf{V}|_F^2) $$

其中 $| \cdot |_{\mathcal{O}}$ 表示只统计已观测评分的贡献:在反向传播中,只有与已观测输入相关联的权重才会被更新,这正是后续代码中"梯度掩码(mask)"操作的理论依据。正则项 $\lambda(| \mathbf{W} |_F^2 + | \mathbf{V}|_F^2)$ 对两个权重矩阵施加 Frobenius 范数惩罚,用于防止过拟合,其作用与矩阵分解目标函数中的 $\lambda$ 正则项一致。

实现 AutoRec 模型(MXNet)

在编码实现前,先导入所需依赖:

#@tab mxnet from d2l import mxnet as d2l from mxnet import autograd, gluon, np, npx from mxnet.gluon import nn import mxnet as mx npx.set_np()

一个典型的自编码器由编码器(encoder)和解码器(decoder)组成:编码器把输入投影为隐藏表示,解码器把隐藏层映射到重建层。AutoRec 的 MXNet 实现沿用了这一实践,用全连接层构建两者,关键设计如下:

  • 编码器激活函数:默认使用sigmoid;
  • 解码器:不施加激活函数(输出层直接回归评分值域);
  • Dropout:在编码变换之后引入,用于降低过拟合;
  • 梯度掩码:未观测输入的梯度被掩蔽,确保只有已观测评分参与模型学习。
#@tab mxnet class AutoRec(nn.Block): def __init__(self, num_hidden, num_users, dropout=0.05): super(AutoRec, self).__init__() self.encoder = nn.Dense(num_hidden, activation='sigmoid', use_bias=True) self.decoder = nn.Dense(num_users, use_bias=True) self.dropout = nn.Dropout(dropout) def forward(self, input): hidden = self.dropout(self.encoder(input)) pred = self.decoder(hidden) if autograd.is_training(): # Mask the gradient during training return pred * np.sign(input) else: return pred

逐点拆解这段代码:

  • encoder:nn.Dense(num_hidden, activation='sigmoid', use_bias=True),输入维度自动由首次前向传播推断,输出维度为num_hidden(隐藏层神经元数),对应数学式中的 $g(\mathbf{V} \mathbf{R}_{*i} + \mu)$;
  • decoder:nn.Dense(num_users, use_bias=True),把隐藏表示映射回用户维度的重建向量,对应 $f(\mathbf{W} \cdot (\cdot) + b)$,此处不设激活函数,让输出直接逼近 1~5 的评分;
  • forward中pred * np.sign(input)即梯度掩码:np.sign(input)在已观测评分处为 ±1、在未知(置零)处为 0,相乘后未知条目对应的梯度恒为 0,从实现层面落实了目标函数中"仅已观测评分参与反向传播"的约束;推理(非训练)阶段则直接返回pred,输出完整的重建评分。

数据准备:MovieLens 100K 的读取、划分与装载

AutoRec 的输入是评分矩阵的列(物品维度),而非矩阵分解那种"(用户, 物品)二元组"。因此数据加载流程与 矩阵分解 章节不同,分为三步,对应的工具函数定义于 d2l/mxnet.py:

  1. 读取:d2l.read_data_ml100k()从ml-100k数据集下载u.data文件(tab 分隔的四列:user_id, item_id, rating, timestamp),并统计出num_users与num_items。该数据集包含 943 个用户对 1682 部电影的 10 万条评分(1~5 星),详见 MovieLens 数据集;
  2. 划分:d2l.split_data_ml100k(df, num_users, num_items)默认使用random模式,随机保留 90% 交互作为训练集、其余 10% 作为测试集(也支持按时间戳的seq-aware模式);
  3. 装载:d2l.load_data_ml100k(train_data, num_users, num_items)在feedback='explicit'模式下构建形状为(num_items, num_users)的交互矩阵inter(注意:行是物品、列是用户,恰好对应 item-based AutoRec 按列取输入的设计),inter[item_index, user_index] = score填入真实评分,其余为 0。

随后构造训练/测试 DataLoader,并完成模型初始化与训练:

#@tab mxnet devices = d2l.try_all_gpus() # Load the MovieLens 100K dataset df, num_users, num_items = d2l.read_data_ml100k() train_data, test_data = d2l.split_data_ml100k(df, num_users, num_items) _, _, _, train_inter_mat = d2l.load_data_ml100k(train_data, num_users, num_items) _, _, _, test_inter_mat = d2l.load_data_ml100k(test_data, num_users, num_items) train_iter = gluon.data.DataLoader(train_inter_mat, shuffle=True, last_batch="rollover", batch_size=256, num_workers=d2l.get_dataloader_workers()) test_iter = gluon.data.DataLoader(np.array(train_inter_mat), shuffle=False, last_batch="keep", batch_size=1024, num_workers=d2l.get_dataloader_workers()) # Model initialization, training, and evaluation net = AutoRec(500, num_users) net.initialize(ctx=devices, force_reinit=True, init=mx.init.Normal(0.01)) lr, num_epochs, wd, optimizer = 0.002, 25, 1e-5, 'adam' loss = gluon.loss.L2Loss() trainer = gluon.Trainer(net.collect_params(), optimizer, {"learning_rate": lr, 'wd': wd}) d2l.train_recsys_rating(net, train_iter, test_iter, loss, trainer, num_epochs, devices, evaluator, inter_mat=test_inter_mat)

几个值得注意的配置细节:

  • 训练 DataLoader:shuffle=True打乱批次顺序,last_batch="rollover"表示最后不足一个 batch 的剩余样本滚动并入下一个 epoch,batch_size=256;
  • 测试 DataLoader:以np.array(train_inter_mat)(即训练矩阵)作为特征输入,last_batch="keep"保留尾部不足批次的样本,batch_size=1024;
  • 隐藏层维度:AutoRec(500, num_users)隐藏单元数为 500,解码器输出维度为num_users(943);
  • 初始化:mx.init.Normal(0.01)用标准差 0.01 的正态分布初始化参数,force_reinit=True强制重新初始化;
  • 优化配置:学习率lr=0.002、训练 25 个 epoch、权重衰减wd=1e-5、优化器adam、损失gluon.loss.L2Loss()(即 $\ell_2$ 损失,等价于 MSE,配合权重衰减对应目标函数中的 Frobenius 正则)。

重写评估器:基于交互矩阵的掩码 RMSE

由于 AutoRec 的输入/输出形态从"二元组"变成了"整列向量",无法直接复用矩阵分解章节基于mx.metric.RMSE()的评估器,需要重新实现。评估指标仍为 RMSE(均方根误差):

#@tab mxnet def evaluator(network, inter_matrix, test_data, devices): scores = [] for values in inter_matrix: feat = gluon.utils.split_and_load(values, devices, even_split=False) scores.extend([network(i).asnumpy() for i in feat]) recons = np.array([item for sublist in scores for item in sublist]) # Calculate the test RMSE rmse = np.sqrt(np.sum(np.square(test_data - np.sign(test_data) * recons)) / np.sum(np.sign(test_data))) return float(rmse)

该评估器的要点:

  • 遍历inter_matrix(传入的是测试 DataLoader)逐批取出交互矩阵,用gluon.utils.split_and_load分发到多个设备,对每个样本(一列评分向量)执行network(i)得到重建结果并收集;
  • 计算 RMSE 时用np.sign(test_data)作为掩码:测试矩阵中真实评分处为 ±1(求和即观测数)、未知处为 0,从而只统计已观测评分的误差,分母np.sum(np.sign(test_data))给出测试观测总数,与目标函数中 $| \cdot |_{\mathcal{O}}$ 的"仅统计已观测条目"思想完全一致。

训练流程源码解读

train_recsys_rating是本书推荐系统章节共享的训练工具函数,实现在 d2l/mxnet.py。它与矩阵分解章节共用同一函数,AutoRec 场景的关键差异在评估分支:

if len(kwargs) > 0: # It will be used in section AutoRec test_rmse = evaluator(net, test_iter, kwargs['inter_mat'], devices) else: test_rmse = evaluator(net, test_iter, devices)
  • 训练主循环中,loss(p, s)计算 $\ell_2$ 损失,trainer.step(values[0].shape[0])以该批次样本数为梯度缩放因子更新参数,并实时累计train loss与test RMSE到动画曲线(d2l.Animator);
  • 当调用时传入了inter_mat=test_inter_mat(即本节的写法),训练函数会把测试交互矩阵透传给 AutoRec 专属的evaluator,完成掩码 RMSE 的逐 epoch 评估;
  • 训练结束后打印最终训练损失、测试 RMSE 以及吞吐量(examples/sec)。

从源码结构可以推断:该函数在设计之初就为 AutoRec 预留了**kwargs扩展位(代码注释明确标注 "It will be used in section AutoRec"),说明 AutoRec 评估逻辑与通用训练循环是解耦、可插拔的。

实验结果与结论

在上述超参数(隐藏维度 500、dropout 0.05、lr 0.002、25 epochs、wd 1e-5、Adam)下,AutoRec 在 MovieLens 100K 测试集上的 RMSE 明显低于矩阵分解模型(矩阵分解章节中潜因子维度设为 30,训练 20 个 epoch)。这一对比印证了神经网络的非线性表达能力在评分预测任务中的有效性:同样的显式反馈数据,线性模型与非线性自编码器之间的性能差距,正是 AutoRec 引入非线性变换与 Dropout 正则化带来的收益。

需要注意的是,这里的对比结论基于本书章节默认配置下的实验设置;具体 RMSE 数值会随随机划分、初始化与硬件环境波动,应以实际复现结果为准。

小结

  • AutoRec 用自编码器框架重新表述矩阵分解算法,在协同过滤中融入非线性层与 Dropout 正则化;
  • 在 MovieLens 100K 数据集上的实验表明,AutoRec 取得了优于矩阵分解模型的测试 RMSE,验证了神经网络在评分预测任务上的有效性;
  • item-based AutoRec 以评分矩阵的列(物品向量)为输入、重建完整评分为输出;user-based 变体可对称推导。

练习与进一步探索

动手复现并深入理解 AutoRec,可以从以下三个方向切入:

  1. 改变隐藏维度:将AutoRec(500, num_users)中的num_hidden从 500 调整为 100、200、1000 等取值,观察模型性能变化,理解容量与欠/过拟合的权衡;
  2. 增加更多隐藏层:在 encoder 与 decoder 之间堆叠额外的全连接层,验证加深网络是否有助于提升评分预测精度;
  3. 探索激活函数组合:尝试把 encoder 的sigmoid换成tanh、relu等,或在 decoder 上施加激活,寻找更优的 encoder/decoder 激活组合。

以上练习的评判基准,正是本文实现的掩码 RMSE 评估器,可与矩阵分解模型的结果横向对比。

延伸阅读

  • 本章完整目录与推荐系统知识体系:推荐系统章节索引
  • 数据来源与预处理细节:MovieLens 数据集
  • 线性基线模型:矩阵分解
  • 协同过滤与显式/隐式反馈的概念基础:推荐系统概述
  • 数据集读取、划分、装载与训练工具的源码实现:d2l/mxnet.py
  • 文档
  • 教程
  • 人工智能
  • 深度学习
  • NLP
  • 计算机视觉
  • 强化学习

【免费下载链接】d2l-en

Interactive deep learning book with multi-framework code, math, and discussions. Adopted at 500 universities from 70 countries including Stanford, MIT, Harvard, and Cambridge.

项目地址:https://gitcode.com/gh_mirrors/d2/d2l-en
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询