尽管矩阵分解模型在评级预测任务上实现了不错的性能,但它本质上是线性模型。因此,此类模型无法捕获可能预示用户偏好的复杂非线性和复杂关系。在本节中,我们介绍了非线性神经网络协同过滤模型AutoRec。它使用自动编码器架构识别协作过滤(CF),并旨在基于显式反馈将非线性变换集成到CF中。神经网络已被证明能够逼近任何连续函数,使其适合于解决矩阵分解的局限性并丰富矩阵分解的表达能力:

  • AutoRec具有与自动编码器相同的结构,自动编码器由输入层,隐藏层和重构(输出)层组成。自动编码器是一种神经网络,可以学习将其输入复制到其输出,以便将输入编码为隐藏(通常是低维)表示形式。在AutoRec中,不是将用户/项目显式嵌入到低维空间中,而是使用交互矩阵的列/行作为输入,然后在输出层中重建交互矩阵。
  • AutoRec与传统的自动编码器不同:AutoRec而不是学习隐藏的表示,而是专注于学习/重建输出层。它使用部分观察到的交互矩阵作为输入,旨在重建完整的评分矩阵。同时,出于推荐的目的,通过重构将输入的缺失条目填充在输出层中。

AutoRec有两种变体:基于用户和基于项目。为简便起见,这里我们仅介绍基于项目的AutoRec。可以相应地导出基于用户的AutoRec。

1. AutoRec模型

Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图1 表示评分矩阵第 Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图2 列,默认情况下未知评分设置为0,神经系统结构定义为:

Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图3%20%3D%20f(%5Cmathbf%7BW%7D%20%5Ccdot%20g(%5Cmathbf%7BV%7D%20%5Cmathbf%7BR%7D%7B*i%7D%20%2B%20%5Cmu)%20%2B%20b)%0A#card=math&code=h%28%5Cmathbf%7BR%7D%7B%2Ai%7D%29%20%3D%20f%28%5Cmathbf%7BW%7D%20%5Ccdot%20g%28%5Cmathbf%7BV%7D%20%5Cmathbf%7BR%7D_%7B%2Ai%7D%20%2B%20%5Cmu%29%20%2B%20b%29%0A)

Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图4#card=math&code=f%28%5Ccdot%29) 和 Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图5#card=math&code=g%28%5Ccdot%29) 代表激活函数, Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图6Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图7 是权重矩阵, Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图8Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图9 是偏差。让 Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图10#card=math&code=h%28%20%5Ccdot%20%29) AutoRec的整个网络。 Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图11#card=math&code=h%28%5Cmathbf%7BR%7D_%7B%2Ai%7D%29)输出是重建 Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图12列的矩阵。

以下目标函数旨在最大程度地减少重构误差:

Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图13%5Cparallel%7B%5Cmathcal%7BO%7D%7D%5E2%7D%20%2B%5Clambda(%5C%7C%20%5Cmathbf%7BW%7D%20%5C%7C_F%5E2%20%2B%20%5C%7C%20%5Cmathbf%7BV%7D%5C%7C_F%5E2)%0A#card=math&code=%5Cunderset%7B%5Cmathbf%7BW%7D%2C%5Cmathbf%7BV%7D%2C%5Cmu%2C%20b%7D%7B%5Cmathrm%7Bargmin%7D%7D%20%5Csum%7Bi%3D1%7D%5EM%7B%5Cparallel%20%5Cmathbf%7BR%7D%7B%2Ai%7D%20-%20h%28%5Cmathbf%7BR%7D%7B%2Ai%7D%29%5Cparallel_%7B%5Cmathcal%7BO%7D%7D%5E2%7D%20%2B%5Clambda%28%5C%7C%20%5Cmathbf%7BW%7D%20%5C%7C_F%5E2%20%2B%20%5C%7C%20%5Cmathbf%7BV%7D%5C%7C_F%5E2%29%0A)

Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图14只考虑到评分的贡献, 也就是说在反向传播期间只更新和观察输入关联的权重。

  1. from d2l import mxnet as d2l
  2. from mxnet import autograd, gluon, np, npx
  3. from mxnet.gluon import nn
  4. from plotly import express as px
  5. import pandas as pd
  6. import mxnet as mx
  7. import sys
  8. npx.set_np()

2. 模型实现

典型的自动编码器由编码器和解码器组成。编码器将输入投影到隐藏表示中,而解码器将隐藏层映射到重建层。我们遵循这种做法,并创建具有密集层的编码器和解码器。编码器的激活函数默认为sigmoid,并且不对解码器应用任何激活。编码转换后包括了Dropout,以减少过度拟合。掩盖未观察到的输入的梯度,以确保仅观察到的等级有助于模型学习过程。

  1. class AutoRec(nn.Block):
  2. def __init__(self, num_hidden, num_users, dropout=0.05):
  3. super(AutoRec, self).__init__()
  4. self.encoder = nn.Dense(num_hidden, activation='sigmoid', use_bias=True)
  5. self.decoder = nn.Dense(num_users, use_bias=True)
  6. self.dropout = nn.Dropout(dropout)
  7. def forward(self, input):
  8. hidden = self.dropout(self.encoder(input))
  9. pred = self.decoder(hidden)
  10. if autograd.is_training(): # 在训练过程中遮盖梯度
  11. return pred * np.sign(input)
  12. else:
  13. return pred

3. 评估器

由于输入和输出已更改,因此我们需要重新实现评估功能,同时我们仍将RMSE用作准确性度量。

  1. def evaluator(network, inter_matrix, test_data, devices):
  2. scores = []
  3. for values in inter_matrix:
  4. feat = gluon.utils.split_and_load(values, devices, even_split=False)
  5. scores.extend([network(i).asnumpy() for i in feat])
  6. recons = np.array([item for sublist in scores for item in sublist])
  7. rmse = np.sqrt(np.sum(np.square(test_data - np.sign(test_data) * recons))
  8. / np.sum(np.sign(test_data)))
  9. return float(rmse)

4. 训练模型

现在,让我们在MovieLens数据集上训练和评估AutoRec。使用之前的训练函数:

  1. def train_recsys_rating(net, train_iter, test_iter, loss, trainer, num_epochs, devices=d2l.try_all_gpus(), evaluator=None,
  2. **kwargs):
  3. timer = d2l.Timer()
  4. data = []
  5. for epoch in range(num_epochs):
  6. metric, l = d2l.Accumulator(3), 0.
  7. for i, values in enumerate(train_iter):
  8. timer.start()
  9. input_data = []
  10. values = values if isinstance(values, list) else [values]
  11. for v in values:
  12. input_data.append(gluon.utils.split_and_load(v, devices))
  13. train_feat = input_data[0:-1] if len(values) > 1 else input_data
  14. train_label = input_data[-1]
  15. with autograd.record():
  16. preds = [net(*t) for t in zip(*train_feat)]
  17. ls = [loss(p, s) for p, s in zip(preds, train_label)]
  18. [l.backward() for l in ls]
  19. l += sum([l.asnumpy() for l in ls]).mean() / len(devices)
  20. trainer.step(values[0].shape[0])
  21. metric.add(l, values[0].shape[0], values[0].size)
  22. timer.stop()
  23. if len(kwargs) > 0: # AutoRec中会用到这个
  24. test_rmse = evaluator(net, test_iter, kwargs['inter_mat'], devices)
  25. else:
  26. test_rmse = evaluator(net, test_iter, devices)
  27. train_l = l / (i + 1)
  28. data.append((epoch+1, train_l, test_rmse))
  29. print(f'train loss {metric[0] / metric[1]:.3f}, test RMSE {test_rmse:.3f}')
  30. print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec on {str(devices)}')
  31. fig = px.line(pd.DataFrame(data, columns=['epoch', 'train loss', 'test RMSE']), x='epoch', y=['train loss', 'test RMSE'], width=580, height=400)
  32. fig.show()

我们可以清楚地看到,测试RMSE低于矩阵分解模型,从而证明了神经网络在评级预测任务中的有效性。

  1. devices = d2l.try_all_gpus()
  2. # 加载数据
  3. df, num_users, num_items = d2l.read_data_ml100k()
  4. train_data, test_data = d2l.split_data_ml100k(df, num_users, num_items)
  5. _, _, _, train_inter_mat = d2l.load_data_ml100k(train_data, num_users,
  6. num_items)
  7. _, _, _, test_inter_mat = d2l.load_data_ml100k(test_data, num_users,
  8. num_items)
  9. train_iter = gluon.data.DataLoader(train_inter_mat, shuffle=True,
  10. last_batch="rollover", batch_size=256,
  11. num_workers=d2l.get_dataloader_workers())
  12. test_iter = gluon.data.DataLoader(np.array(train_inter_mat), shuffle=False,
  13. last_batch="keep", batch_size=1024,
  14. num_workers=d2l.get_dataloader_workers())
  15. # 设置net以及loss进行训练
  16. net = AutoRec(500, num_users)
  17. net.initialize(ctx=devices, force_reinit=True, init=mx.init.Normal(0.01))
  18. lr, num_epochs, wd, optimizer = 0.002, 25, 1e-5, 'adam'
  19. loss = gluon.loss.L2Loss()
  20. trainer = gluon.Trainer(net.collect_params(), optimizer, {"learning_rate": lr, 'wd': wd})
  21. train_recsys_rating(net, train_iter, test_iter, loss, trainer, num_epochs,
  22. devices, evaluator, inter_mat=test_inter_mat)

Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图15

Mxnet (43): 使用基于自编码器的AutoRec模型完成推荐系统 - 图16

5. 参考

https://d2l.ai/chapter_recommender-systems/autorec.html