本篇文章先簡單介紹論文思路,而後使用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使用了單層網絡,存在表達能力不足的問題,但對於基於機器學習的矩陣分解,協同過濾來講,因爲這層網絡的加入特徵的表達能力獲得提升。