推荐系统入门实践(二)
《动手深度学习》第二版,推荐系统章节的个人理解以及pytorch代码实现。
李沐大神的《动手深度学习》第二版已经在更新了,目前只有英文版。并且推荐系统章节只有mxnet实现,这是本人看完之后的理解以及自己写的pytorch代码实现。对于入门推荐系统很有帮助,有兴趣的可以看一看。
前言
在上一节我们模型的求解目标是用户矩阵 P P P,商品矩阵 Q Q Q,得到这两个矩阵后我们可以轻易求得 M a t r i x = P Q ⊤ Matrix = \mathbf{PQ}^\top Matrix=PQ⊤。对于一个推荐系统而言,很明显共现矩阵 M a t r i x Matrix Matrix才是我们需要的。因此,Auto:Rec 试图跳过求解 P , Q P,Q P,Q的过程,直接求得共现矩阵。
一、AutoRec
AutoRec的思想和矩阵分解极为类似,为了直接求得共现矩阵,它相当于将求取用户矩阵和商品矩阵然后再求共现矩阵的过程集成在了模型中。
AutoRec在模型中实现了一种被称为自编码器的结构,自编码器结构和只含一层隐藏层的普通前向神经网络一样。其作用是将向量
R
∗
i
\mathbf{R}_{*i}
R∗i作为其输入,通过自编码器后,得到的输出向量尽可能接近其本身,这种对自己编码重构的过程就被称为自编码器。
另外,这种结构还有几点需要注意:
-
很明显,输出向量 h ( R ∗ i ) = f ( W ⋅ g ( V R ∗ i + μ ) + b ) h(\mathbf{R}_{*i}) = f(\mathbf{W} \cdot g(\mathbf{V} \mathbf{R}_{*i} + \mu) + b) h(R∗i)=f(W⋅g(VR∗i+μ)+b)和 R ∗ i \mathbf{R}_{*i} R∗i具有相同的维度,为了让它们两尽可能相近,我们需要损失函数:
a r g m i n W , V , μ , b ∑ i = 1 M ∥ R ∗ i − h ( R ∗ i ) ∥ O 2 + λ ( ∥ W ∥ F 2 + ∥ V ∥ F 2 ) \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) W,V,μ,bargmin∑i=1M∥R∗i−h(R∗i)∥O2+λ(∥W∥F2+∥V∥F2)
-
中间隐藏层的神经元个数要远小于 R ∗ i \mathbf{R}_{*i} R∗i的维数。
-
这种通过中间隐藏层重构向量的过程和矩阵分解的思想极为类似。输出向量 h ( R ∗ i ) h(\mathbf{R}_{*i}) h(R∗i)逼近输入向量 R ∗ i \mathbf{R}_{*i} R∗i的过程和上一节用 u u u和 i i i的内积逼近 s c o r e score score的过程基本一样。
二、代码实现
注意需要把上一节的数据处理函数复制到工具包中才可以
导入包
from d2l import torch as d2l
import torch
from torch import nn
import os
import pandas as pd
import numpy as np
首先实现模型,这里注意计算损失函数时我们只能计算 M a t r i c Matric Matric原先有 s c o r e score score的部分。这里编码器的含义就是隐藏层,解码器就是输出层。
class AutoRec(nn.Module):
def __init__(self, num_hidden, num_users, dropout=0.05):
super().__init__()
self.encoder = nn.Sequential(nn.Linear(in_features=num_users, out_features=num_hidden),
nn.Sigmoid())
self.decoder = nn.Linear(in_features=num_hidden, out_features=num_users)
self.dropout = nn.Dropout(dropout)
def forward(self, input, is_training=True):
hidden = self.dropout(self.encoder(input.float()))
pred = self.decoder(hidden)
if is_training:
return pred * torch.sign(input)
else:
return pred
重新实现评估函数,函数依然采用RMSE,不过需要改一下形式,只计算原先有 s c o r e score score的部分
def evaluator_02(net, inter_matrix, test_data, devices):
test_data = torch.tensor(test_data, device=devices)
scores = torch.empty_like(test_data, device=devices)
idx = 0
for value in inter_matrix:
value = value.to(devices)
recons = net(value, False)
scores[idx:idx + value.shape[0]] = recons
idx += value.shape[0]
print(test_data.shape, scores.shape)
rmse = torch.sqrt(torch.sum((test_data - torch.sign(test_data) * scores) ** 2)
/ torch.sum(torch.sign(test_data)))
return float(rmse)
重新写一下训练函数,和上次的几乎一样
def train_recsys_rating_02(net, train_iter, test_iter, loss, optimizer, num_epochs, devices=torch.device('cuda'), evaluator=None, **kwargs):
timer = d2l.Timer()
net = net.to(devices)
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0, 2],
legend=['train loss', 'test RMSE'])
for epoch in range(num_epochs):
metric, train_l, i = d2l.Accumulator(3), 0, 0
for value in train_iter:
i += 1
value = value.to(devices)
pred = net(value)
optimizer.zero_grad()
l = loss(value, pred)
train_l += l.cpu().item()
l.backward()
optimizer.step()
metric.add(l, 1, value.shape[0])
timer.stop()
if len(kwargs) > 0:
test_rmse = evaluator(net, test_iter, kwargs['inter_mat'],
devices)
else:
test_rmse = evaluator(net, test_iter, devices)
animator.add(epoch + 1, (train_l / i, test_rmse))
print(f'train loss {metric[0] / metric[1]:.3f}, '
f'test RMSE {test_rmse:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
f'on {str(devices)}')
下面初始化一下数值就可以开始训练了。
devices = torch.device('cuda')
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 = torch.utils.data.DataLoader(train_inter_mat, shuffle=True,
batch_size=256)
test_iter = torch.utils.data.DataLoader(np.array(train_inter_mat), shuffle=False,
batch_size=1024)
net02 = AutoRec(500, num_users)
for parm in net02.parameters():
nn.init.normal_(parm, std=0.01)
lr, num_epochs, wd = 0.002, 25, 1e-5
loss = nn.MSELoss()
optimizer = torch.optim.Adam(net02.parameters(), lr=lr, weight_decay=wd)
train_recsys_rating_02(net02, train_iter, test_iter, loss, optimizer, num_epochs,
devices, evaluator_02, inter_mat=test_inter_mat)
总结
结束