本篇文章先简单介绍论文思路,而后使用Tensoflow2.0、Keras API复现算法部分。包括:python
自定义模型算法
自定义损失函数网络
自定义评价指标RMSE机器学习
就题目而言《AutoRec: Autoencoders Meet Collaborative Filtering》,自编码机碰见协同过滤,可见是使用自编码机结合协同过滤思想进行的算法。论文通过数据集Movielens和Netfix验证有不错的效果,更重要的是它是对特征交叉引入深度学习的开端,论文两页,简单易懂。函数
令m个用户,n个物品,构成用户-物品矩阵,每一个物品对被用户进行评分。根据协同过滤思想,有基于用户的方式,也有基于物品的方式,取决于输入是物品分表示的用户向量,仍是用户评分表示物品向量$r{i},r{u}$。自编码机部分将评分向量进行低维压缩,用低维空间表示评分向量,并对向量不一样部分进行交叉,而后重构向量。工具
算法模型图为:学习
采用的模型公式为(基本就是逻辑回归的方式):测试
其中,中间的神经元数量,即映射低维空间设为k,则k是一个超参数根据效果进行调控。编码
须要注意的部分是lua
每一个评分向量(不管物品仍是用户向量),都存在稀疏性即没有用户对其进行评分,则在反向传播的过程当中不能考虑这部份内容;
为了防止过拟合,须要在损失函数中添加W和V参数矩阵正则化:
前一部分为真实值与预测值的平方偏差(仅仅计算有评分的部分),第二部分为正则项,所求为F范数。
预测公式:
如果基于物品的AutoRec(I-AutoRec)则,输入待求的物品向量,到下面预测公式中,获得一个完整的向量,求第几个用户就取第几个维度从而获得此用户对此物品的评分,即
基于用户的AutoRec(U-AutoRec)表示相似。
实验验证
论文的评价方式,使用的RMSE,与其余算法比较获得比较好的参数是k=500,f映射使用线性函数,g映射使用Sigmoid函数。
复现包括网络模型,损失函数,以及评价指标三部分,因为部分的改动不能直接使用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部分能够看到预测的精度仍是凑合的):
本篇文章主要是针对AutoRec论文的主要部分进行了介绍,而后使用TensorFlow2.0的Keras接口实现了自定义的模型,损失,以及指标,并训练了I-AutoRec模型。
关于AutoRec要说的是,
编码器部分若是使用深层网络好比三层会增长预测的准确性;
自编码器部分的输出向量通过了编码过程的泛化至关于对缺失部分有了预测能力,这是自编码机用于推荐的缘由;
I-AutoRec推荐过程,须要输入物品的矩阵而后获得每一个用户对物品的预测评分,而后取用户本身评分的Top能够进行推荐,U-AutoRec只须要输入一次目标用户的向量就能够重建用户对全部物品的评分,而后获得推荐列表,可是用户向量可能稀疏性比较大影响最终的推荐效果。
AutoRec使用了单层网络,存在表达能力不足的问题,但对于基于机器学习的矩阵分解,协同过滤来讲,因为这层网络的加入特征的表达能力获得提升。