1. 简介

本篇文章先简单介绍论文思路,然后使用Tensoflow2.0、Keras API复现算法部分。包括:

  • 自定义模型

  • 自定义损失函数

  • 自定义评价指标RMSE

就题目而言《AutoRec: Autoencoders Meet Collaborative Filtering》,自编码机遇见协同过滤,可见是使用自编码机结合协同过滤思想进行的算法。论文经过数据集Movielens和Netfix验证有不错的效果,更重要的是它是对特征交叉引入深度学习的开端,论文两页,简单易懂。

2. 算法模型

令m个用户,n个物品,构成用户-物品矩阵,每个物品对被用户进行评分。根据协同过滤思想,有基于用户的方式,也有基于物品的方式,取决于输入是物品分表示的用户向量,还是用户评分表示物品向量$r{i},r{u}$。自编码机部分将评分向量进行低维压缩,用低维空间表示评分向量,并对向量不同部分进行交叉,然后重构向量。

算法模型图为:

采用的模型公式为(基本就是逻辑回归的方式):

其中,中间的神经元数量,即映射低维空间设为k,则k是一个超参数根据效果进行调控。

需要注意的部分

  1. 每个评分向量(无论物品还是用户向量),都存在稀疏性即没有用户对其进行评分,则在反向传播的过程中不能考虑这部分内容;

  2. 为了防止过拟合,需要在损失函数中添加W和V参数矩阵正则化:

前一部分为真实值与预测值的平方误差(仅仅计算有评分的部分),第二部分为正则项,所求为F范数。

预测公式:

若是基于物品的AutoRec(I-AutoRec)则,输入待求的物品向量,到下面预测公式中,得到一个完整的向量,求第几个用户就取第几个维度从而得到此用户对此物品的评分,即

基于用户的AutoRec(U-AutoRec)表示类似。

实验验证

论文的评价方式,使用的RMSE,与其他算法比较得到比较好的参数是k=500,f映射使用线性函数,g映射使用Sigmoid函数。



3. 代码复现

复现包括网络模型,损失函数,以及评价指标三部分,由于部分的改动不能直接使用TF原生的库函数。

首先导入需要使用的工具包:

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from sklearn.model_selection import train_test_split

定义模型

基于Keras的API的模型定义需要继承Model类,重写方法call(前向传播过程),如果需要加入dropout的模型需要将训练和预测分开可以使用参数training=None的方式来指定,这里不需要这个参数,因此省略。

class AutoRec(keras.Model):
def __init__(self, feature_nums, hidden_units, **kwargs):
super(AutoRec, self).__init__()
self.feature_nums = feature_nums # 基于物品则为物品特征数-即用户数,基于用户则为物品数量
self.hidden_units = hidden_units # 论文中的k参数
self.encoder = keras.layers.Dense(self.hidden_units, input_shape=[self.feature_nums], activation='sigmoid') # g映射
self.decoder = keras.layers.Dense(self.feature_nums, input_shape=[self.hidden_units]) # f映射 def call(self, X):
# 前向传播
h = self.encoder(X)
y_hat = self.decoder(h)
return y_hat

定义损失函数

此损失函数虽然为MSE形式,但是在计算的过程中发现,仅仅计算有评分的部分,无评分部分不进入损失,同时还有正则化,这里一起写出来。

基于Keras API的方式,需要继承Loss类,和方法call初始化传入model参数为了取出W和V参数矩阵。

mask_zero表示没有评分的部分不进入损失函数,同时要保证数据类型统一tf.int32,tf.float32否则会报错。

class  Mse_Reg(keras.losses.Loss):
def __init__(self, model, reg_factor=None):
super(Mse_Reg, self).__init__()
self.model = model
self.reg_factor = reg_factor def call(self, y_true, y_pred) :
y_sub = y_true - y_pred
mask_zero = y_true != 0
mask_zero = tf.cast(mask_zero, dtype=y_sub.dtype)
y_sub *= mask_zero
mse = tf.math.reduce_sum(tf.math.square(y_sub)) # mse损失部分
reg = 0.0
if self.reg_factor is not None:
weight = self.model.weights
for w in weight:
if 'bias' not in w.name:
reg += tf.reduce_sum(tf.square(w)) # 求矩阵的Frobenius范数的平方
return mse + self.reg_factor * 0.5 * reg
return mse

定义RMSE评价指标

定义评价指标需要继承类Metric,方法update_state和result以及reset,reset方法感觉使用较少,主要是更新状态和得到结果。

class RMSE(keras.metrics.Metric):
def __init__(self):
super(RMSE, self).__init__()
self.res = self.add_weight(name='res', dtype=tf.float32, initializer=tf.zeros_initializer()) def update_state(self, y_true, y_pred, sample_weight=None):
y_sub = y_true - y_pred
mask_zero = y_true != 0
mask_zero = tf.cast(mask_zero, dtype=y_sub.dtype)
y_sub *= mask_zero
values = tf.math.sqrt(tf.reduce_mean(tf.square(y_sub)))
self.res.assign_add(values) def result(self):
return self.res

定义数据集

定义好各个部分之后,就可以构造训练集然后训练模型了。

get_data表示从path中加载数据,然后加数据通过pandas的透视表功能构造一个行为物品,列为用户的矩阵;

data_iter表示通过tf.data构造数据集。

# 定义数据
def get_data(path, base_items=True):
data = pd.read_csv(path)
rate_matrix = pd.pivot_table(data, values='rating', index='movieId', columns='userId',fill_value=0.0)
if base_items:
return rate_matrix
else :
return rate_matrix.T def data_iter(df, shuffle=True, batch_szie=32, training=False) :
df = df.copy()
X = df.values.astype(np.float32)
ds = tf.data.Dataset.from_tensor_slices((X, X)).batch(batch_szie)
if training:
ds = ds.repeat()
return ds

训练模型

万事俱备,就准备数据放给模型就好了。

要说明的是,如果fit的时候不设置steps_per_epoch会在数据量和batch大小不能整除的时候报迭代器的超出范围的错误。设置了此参数当然也要加上validation_steps=2,不然还是会报错,不信可以试试看。

path = 'ratings.csv' # 我这里用的是10w数据,不是原始的movielens-1m
# I-AutoRec,num_users为特征维度
rate_matrix = get_data(path)
num_items, num_users = rate_matrix.shape # 划分训练测试集
BARCH = 128
train, test = train_test_split(rate_matrix, test_size=0.1)
train, val = train_test_split(train, test_size=0.1)
train_ds = data_iter(train, batch_szie=BARCH, training=True)
val_ds = data_iter(val, shuffle=False)
test_ds = data_iter(test, shuffle=False) # 定义模型
net = AutoRec(feature_nums=num_users, hidden_units=500) # I-AutoRec, k=500
net.compile(loss=Mse_Reg(net), #keras.losses.MeanSquaredError(),
optimizer=keras.optimizers.Adam(),
metrics=[RMSE()])
net.fit(train_ds, validation_data=val_ds, epochs=10, validation_steps=2, steps_per_epoch=train.shape[0]//BARCH) loss, rmse = net.evaluate(test_ds)
print('loss: ', loss, ' rmse: ', rmse)

预测

df = test.copy()
X = df.values.astype(np.float32)
ds = tf.data.Dataset.from_tensor_slices(X) # 这里没有第二个X了
ds = ds.batch(32)
pred = net.predict(ds)
# 随便提出来一个测试集中有的评分看看预测的分数是否正常,pred包含原始为0.0的分数现在已经预测出来分数的。
print('valid: pred user1 for item1: ', pred[1][X[1].argmax()], 'real: ', X[1][X[1].argmax()])

得到结果(没有达到论文的精度,可能是数据量不足,而valid部分可以看到预测的精度还是凑合的):

4. 小结

本篇文章主要是针对AutoRec论文的主要部分进行了介绍,然后使用TensorFlow2.0的Keras接口实现了自定义的模型,损失,以及指标,并训练了I-AutoRec模型。

关于AutoRec要说的是,

编码器部分如果使用深层网络比如三层会增加预测的准确性;

自编码器部分的输出向量经过了编码过程的泛化相当于对缺失部分有了预测能力,这是自编码机用于推荐的原因;

I-AutoRec推荐过程,需要输入物品的矩阵然后得到每个用户对物品的预测评分,然后取用户自己评分的Top可以进行推荐,U-AutoRec只需要输入一次目标用户的向量就可以重建用户对所有物品的评分,然后得到推荐列表,但是用户向量可能稀疏性比较大影响最终的推荐效果。

AutoRec使用了单层网络,存在表达能力不足的问题,但对于基于机器学习的矩阵分解,协同过滤来说,由于这层网络的加入特征的表达能力得到提高。

推荐模型AutoRec:原理介绍与TensorFlow2.0实现的更多相关文章

  1. 推荐模型DeepCrossing: 原理介绍与TensorFlow2.0实现

    DeepCrossing是在AutoRec之后,微软完整的将深度学习应用在推荐系统的模型.其应用场景是搜索推荐广告中,解决了特征工程,稀疏向量稠密化,多层神经网路的优化拟合等问题.所使用的特征在论文中 ...

  2. 推荐模型NeuralCF:原理介绍与TensorFlow2.0实现

    1. 简介 NCF是协同过滤在神经网络上的实现--神经网络协同过滤.由新加坡国立大学与2017年提出. 我们知道,在协同过滤的基础上发展来的矩阵分解取得了巨大的成就,但是矩阵分解得到低维隐向量求内积是 ...

  3. 一文上手TensorFlow2.0(一)

    目录: Tensorflow2.0 介绍 Tensorflow 常见基本概念 从1.x 到2.0 的变化 Tensorflow2.0 的架构 Tensorflow2.0 的安装(CPU和GPU) Te ...

  4. TensorFlow模型部署到服务器---TensorFlow2.0

    前言 ​ 当一个TensorFlow模型训练出来的时候,为了投入到实际应用,所以就需要部署到服务器上.由于我本次所做的项目是一个javaweb的图像识别项目.所有我就想去寻找一下java调用Tenso ...

  5. 『TensorFlow2.0正式版教程』极简安装TF2.0正式版(CPU&GPU)教程

    0 前言 TensorFlow 2.0,今天凌晨,正式放出了2.0版本. 不少网友表示,TensorFlow 2.0比PyTorch更好用,已经准备全面转向这个新升级的深度学习框架了. ​ 本篇文章就 ...

  6. Google工程师亲授 Tensorflow2.0-入门到进阶

    第1章 Tensorfow简介与环境搭建 本门课程的入门章节,简要介绍了tensorflow是什么,详细介绍了Tensorflow历史版本变迁以及tensorflow的架构和强大特性.并在Tensor ...

  7. 04 MapReduce原理介绍

    大数据实战(上) # MapReduce原理介绍 大纲: * Mapreduce介绍 * MapReduce2运行原理 * shuffle及排序    定义 * Mapreduce 最早是由googl ...

  8. [转]MySQL主从复制原理介绍

    MySQL主从复制原理介绍 一.复制的原理 MySQL 复制基于主服务器在二进制日志中跟踪所有对数据库的更改(更新.删除等等).每个从服务器从主服务器接收主服务器已经记录到其二进制日志的保存的更新,以 ...

  9. ThinkPHP 的模型使用详细介绍--模型的核心(七)

    原文:ThinkPHP 的模型使用详细介绍--模型的核心(七) 注意:本节是ThinkPhp框架对数据操作的核心处理部分 大家还是在这里看清楚可以将其剪切放到代码编辑器中查看 本章节给大家着重介绍模型 ...

随机推荐

  1. python的threading的使用(join方法,多线程,锁threading.Lock和threading.Condition

    一.开启多线程方法一 import threading,time def write1(): for i in range(1,5): print('1') time.sleep(1) def wri ...

  2. Educational Codeforces Round 96 (Rated for Div. 2) E. String Reversal (思维,逆序对)

    题意:给你一个字符串,每次可以调换现字符串的相邻两个字符,问最少操作多少次使得这个字符串等于其反转过来的字符串. 题解:先考虑字符串中没有相同字符的情况,那么我们每次将目前字符串的最后一个字符一直调换 ...

  3. HDU-6290 奢侈的旅行 (Dijkstra+堆优化)

    高玩小Q不仅喜欢玩寻宝游戏,还喜欢一款升级养成类游戏.在这个游戏的世界地图中一共有nn个城镇,编号依次为11到nn.这些城镇之间有mm条单向道路,第ii 条单项道路包含四个参数ui,vi,ai,biu ...

  4. Codeforces Round #669 (Div. 2) B. Big Vova (枚举)

    题意:有一个长度为\(n\)的序列,你需要对其重新排序,构造一个新数组\(c\),\(c_{i}=gcd(a_{1},...,a{i})\)并且使得\(c\)的字典序最小. 题解:直接跑\(n\)次, ...

  5. Chip Factory HDU - 5536 字典树(删除节点|增加节点)

    题意: t组样例,对于每一组样例第一行输入一个n,下面在输入n个数 你需要从这n个数里面找出来三个数(设为x,y,z),找出来(x+y)^z(同样也可以(y+z)^1)的最大值 ("^&qu ...

  6. 实战交付一套dubbo微服务到k8s集群(4)之dubbo微服务底包镜像制作

    1.下载jre镜像 在运维主机(mfyxw50.mfyxw.com)操作 [root@mfyxw50 ~]# docker pull registry.cn-hangzhou.aliyuncs.com ...

  7. C++ 结构体 segment fault

    形如 struct node { int key; int height; int size; //tree node 个数 node *left, *right; node(int x) : key ...

  8. 初学算法之dijkstra

    dijkstra的代码思想网上各路高手所述备矣.这里只是存下用邻接矩阵和邻接表实现的dijkstra.(白书代码) 邻接矩阵 1 void dijkstra(int s){ 2 int dis[s]= ...

  9. Leetcode(53)-最大子序和

    给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和. 示例: 输入: [-2,1,-3,4,-1,2,1,-5,4], 输出: 6 解释: 连续子数组 ...

  10. Leetcode(35)-搜索插入位置

    给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引.如果目标值不存在于数组中,返回它将会被按顺序插入的位置. 你可以假设数组中无重复元素. 这个题目很简单,因为它是给定的排序数组而且没有重 ...