个人简单理解 AutoRec 模型就是利用神经网络模型来实现矩阵分解的过程,数学表达上和LFM 隐语义模型很类似。假设图中蓝色神经元代表的为 k 层单隐藏层,图中 的 V M 分别代表输入到隐藏层、以及隐藏层到输出的参数矩阵,用数学表达形式就是:
其中,f() 和 g() 为输出层、隐藏层的激活函数。 其实从形式上来看,假设输入层维度为 n×m n表示batchsize,不考虑偏置项,第一层参数的维度为 m × k, 第二层参数的维度为 k × m,输入特征经过编码解码的结构后,特征维度不变,用于对输入中存在空值的地方进行预测,而优化的目标就是计算 在输入层存在的值与对应输出层输出值之间的均方差(只对原先存在的值进行计算,这里与LFM的思路是一致的),同时为了防止过拟合,加入L2正则化,最终如下:
对比 LFM 中的优化目标:
两者其实是一样的,只不过LFM中的输出层的值为 通过用户矩阵和物品矩阵相乘得到,而AutoRec输出层值为通过两层神经网络计算得到。这里我们也选用 SGD 作为优化方法,直接使用pytorch搭建神经网络,自动进行梯度计算和回传,不需要像 LFM 中得显示的计算梯度和更新。
代码复现:
数据下载链接:
import torch
import torch.nn as nn
import numpy as np
import pandas as pd
from torch.autograd import Variable
import torch.optim as optim
import torch.nn.functional as F
class AutoRec(nn.Module):
def __init__(self, user_nums, hidden_features):
super().__init__()
self.__encoder = nn.Sequential(
nn.Linear(user_nums, hidden_features, bias=True),
nn.Sigmoid()
)
# 解码器输出激活函数不能用Sigmoid,会限制输出
self.__decoder = nn.Sequential(
nn.Linear(hidden_features, user_nums, bias=True)
)
def forward(self, x):
return self.__decoder(self.__encoder(x))
def l2_regularization(model):
loss = 0
for name, param in model.named_parameters():
# 去除掉对偏置项的正则化
if name[-4:] != 'bias':
loss += torch.linalg.norm(param.flatten(), 2)
return loss
def lossFunction(y, pred, mask, lamda, model):
return F.mse_loss(pred * mask, y) + 0.5 * lamda * l2_regularization(model)
model = AutoRec(5, 2)
optimizer = optim.SGD(model.parameters(), lr=0.03)
user_item = pd.read_csv("./data.csv", index_col=0).fillna(0) # 空值全用0填充,计算Loss的时候仅对非空评分
user_item_matrix = torch.from_numpy(np.array(user_item)).float()
# 仅对存在值的区域进loss计算,制造掩膜
mask_matrix = user_item_matrix > 0
# 训练阶段
for epoch in range(2000):
# 遍历每一行
loss_epoch = 0
for i in range(user_item_matrix.shape[1]):
item_col = Variable(user_item_matrix[:, i])
mask_col = Variable(mask_matrix[:, i])
# forward 过程
output = model(item_col)
# loss: Mean Squared Error + L2 regularization
loss = lossFunction(item_col, output, mask_col, 0.04, model)
loss_epoch += loss.item()
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("epoch {}: loss:{}".format(epoch, loss_epoch/user_item_matrix.shape[1]))
# 推理阶段
for i in range(user_item_matrix.shape[1]):
item_col = Variable(user_item_matrix[:, i])
mask_col = Variable(mask_matrix[:, i])
output = model(item_col)
print("label: {}: ======> pred: {}".format(item_col.numpy(), output.detach().numpy()))
进行推理一下,效果还可以
第五行第一个0是待预测的值,算出来接近5, 墙裂推荐 属于是。
参考:
1. 《深度学习推荐系统》