預測電影偏好?如何利用自編碼器實現協同過濾方法

機器之心發表於2018-05-20

深度自編碼器(Deep Autoencoder)由兩個對稱的深度信念網路組成,它相比常見的自編碼器加入了更多隱藏層。在本文中,作者將嘗試使用該工具進行協同過濾,幫助人們研究和預測大量使用者對於不同電影的喜好。

推薦系統使用協同過濾的方法,通過收集使用者的偏好資訊來預測特定使用者的興趣。協同過濾技術的基本假設是,如果使用者 A 對某個問題與人 B 有相同的口味或意見,那麼 A 就更有可能在其他問題上擁有與 B 的相同的意見。

本文將介紹如何根據使用者的偏好、觀看歷史、相同評級和其他電影的其他使用者的評價預測使用者對電影的評分。

預測電影偏好?如何利用自編碼器實現協同過濾方法

目錄:

  • 本文簡介

  • 深度自動編碼器

  • 模型實施

1 介紹

自動編碼器是一種深度學習神經網路架構,可實現協同過濾領域最佳的效能。文章的第一部是理論概述,將會介紹簡單的自動編碼器及深度自編碼器的基礎數學概念。在第二部分中,我們將深入實際展示如何在 TensorFlow 中逐步應用這一技術。本文僅覆蓋和評價模型中最重要的部分。

整個模型的輸入渠道和預處理可以在相應的 GitHub 中檢視:https://github.com/artem-oppermann/Deep-Autoencoders-For-Collaborative-Filtering

2. 深度自編碼器

自編碼器

討論深度自編碼器之前,我們先來介紹它稍微簡單些的版本。自編碼器(Autoencoder)是一種人工神經網路,用於學習一組輸入資料的表示(編碼),通常用於實現降維

在結構上,自編碼器的形式是一個前饋神經網路,由輸入層、一個隱藏層和一個輸出層(圖 1)構成。輸出層與輸入層的神經元數量相同,因此自編碼器屬於無監督學習,這意味著它不需要標記資料——只需要一組輸入資料即可,而不是輸入—輸出對。

預測電影偏好?如何利用自編碼器實現協同過濾方法

圖 1. 典型的 AutoEncoder 架構。

自編碼器的隱藏層比輸入層小,這使得模型可以通過學習資料中的相關性在隱藏層中建立資料的壓縮表示。

輸入層到隱藏層的轉換被稱為編碼步驟,從隱藏層到輸出層的轉換稱為解碼步驟。我們也可以在數學上將這些轉換定義為對映

預測電影偏好?如何利用自編碼器實現協同過濾方法

對映是通過將輸入資料向量乘以權重矩陣,新增一個偏差項並將所得到的向量應用於非線性運算,如 sigmoid,tanh 或整流線性單元來實現的。

自編碼器的訓練

在訓練期間,編碼器接收輸入資料樣本 x 並將其對映到所謂的隱藏層或隱層表示 z 上。然後解碼器將 z 對映到輸出向量 x' 上,後者是(在最好的情況下)輸入資料 x 的準確表示。需要注意的是,通常情況下準確地重建 x 是不可能的。

具有輸出 x' 的訓練包括應用隨機梯度下降以最小化預定損失,例如均方誤差:

預測電影偏好?如何利用自編碼器實現協同過濾方法

深度自編碼器

簡單自動編碼器的擴充套件版是 Deep Autoencoder(圖 2)。從圖 2 中可以看出,它與簡單的計數器部分唯一的區別在於隱藏層的數量。

預測電影偏好?如何利用自編碼器實現協同過濾方法

圖 2. 深度自編碼器的架構。

額外的隱藏層使自編碼器可以從數學上學習資料中更復雜的底層模式。深度自編碼器的第一層可以學習原始輸入中的一階特徵(例如影象中的邊緣)。第二層可以學習對應於一階特徵的外觀中的圖案的二階特徵(例如,根據哪些邊緣傾向於一起發生——例如以形成輪廓或角檢測器)。深度自編碼器更深層的特性往往可以學習到更高階的特性。

把所有東西放在一起:我們需要更多的層來處理更為複雜的資料——比如我們在協作過濾中使用的資料。

3. 實現

如前文所述,你將學會預測使用者對電影的評級。就此而言,我們將使用著名的 MovieLens 資料集(https://grouplens.org/datasets/movielens/)。MovieLensis 是一個基於網路的推薦系統和推薦使用者觀看電影的線上社群。

更具體地說,我們將使用 ml_1m.zip 資料集,該資料集包含 6,040 個 MovieLens 使用者製作的,約 3,900 部電影的 1,000,209 個匿名評級。我們需要的匯入檔案是 ratings.dat。該檔案包含 1,000,209 行,全部格式如下:user_id :: movie_id :: rating:time_stamp。

例如 ratings.dat 中的第一行:

1::595::5::978824268  

這意味著使用者 1 給了 595 號電影打了五星評分。評分時間可以被忽略,因為在這裡我們不會使用它。

我們的深度學習模型需要一個特定的資料結構來進行訓練和測試。這種資料結構是一個 UxM 矩陣,其中 U 是使用者數量,M 是電影數量。每行 i∈U 是唯一的使用者 ID,每列 j∈M 是唯一的電影 ID。這種矩陣的視覺化效果如圖 3 所示。

預測電影偏好?如何利用自編碼器實現協同過濾方法

此矩陣中的每個條目都是使用者給出特定電影的評分。輸入 0 意味著使用者沒有給這部電影任何評價。例如。上圖中,1 號使用者給電影 3 的評級為四星,而電影第 1 則根本沒有評級。

由於本教程將重點介紹深度學習模型的實現,因此不會在這裡介紹使用 User-Movie-Matrix 超出 ratings.dat 檔案的步驟。對於關於這個主題的進一步問題,你可以去我的 GitHub 頁面(https://github.com/artem-oppermann/Deep-Autoencoders-For-Collaborative-Filtering/blob/master/data/preprocess_data.py), 檢視相應的 python 指令碼。

訓練和測試資料集

在模型實現和訓練之前,我們需要對資料進行其他重新處理步驟,將資料劃分為訓練和測試資料集。這一步驟簡單明瞭。到目前為止,我們有一個 User-Movie Matrix,其中每一行都是評級列表。要從列表中獲得訓練和測試集,我們需要從每一行中取一部分評級,並將它們用於訓練,其餘子集則用於測試。

作為描述過程的一個例子,我們考慮一個僅包含 15 部電影的小得多的資料集。一個特定的使用者可能出給這樣的電影評級:

Movie Nr. : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Rating:     5 0 2 4 0 0 2 1 5  1  0  4  5  1  3

請記住,0 表示該電影未被評級。現在我們將前 10 部電影中的一部分作為訓練集並假設其餘的還沒有被評分:

Movie Nr. : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Rating:     5 0 2 4 0 0 2 1 5  0  0  0  0  0  0

因此,原始資料的最後 5 個電影等級被用作測試資料,而電影 1-10 被掩蓋為未被評級:

Movie Nr. : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Rating:     0 0 0 0 0 0 0 0 0  1  0  4  5  1  3

此處僅僅簡單演示瞭如何獲得不同的組合。在原始的 MovieLens 資料集中,我僅使用每個使用者的 10 個電影評級進行測試,其餘(絕大多數)用於模型的訓練。

TensorFlow 實現

1.模型架構

深度自編碼器在這裡作為一個類來實現,其中包含所有必需的操作,如類內的推理、優化、損失、準確性等。

在構造器中,核心初始化器設定了權重和偏差。下一步,網路中的所有權重和偏差都會被初始化。權重是遵從正態分佈的,平均值為 0.0,方差為 0.02,而偏差在開始時都設定為 0.0。

在這個特定的例子中,網路有三個隱藏層,每層包含 128 個神經元。輸入層(和輸出層)的大小對應於資料集中所有當前影片的數量。

class DAE:
    ''' Implementation of Deep Autoencoder class'''

    def __init__(self, FLAGS):    
        self.FLAGS=FLAGS
        self.weight_initializer=model_helper._get_weight_initializer()
        self.bias_initializer=model_helper._get_bias_initializer()
        self.init_parameters()


    def init_parameters(self):
        ''' Initializing the weights and biasis of the neural network.'''

        with tf.name_scope('weights'):
            self.W_1=tf.get_variable(name='weight_1', shape=(self.FLAGS.num_v,self.FLAGS.num_h), 
                                     initializer=self.weight_initializer)
            self.W_2=tf.get_variable(name='weight_2', shape=(self.FLAGS.num_h,self.FLAGS.num_h), 
                                     initializer=self.weight_initializer)
            self.W_3=tf.get_variable(name='weight_3', shape=(self.FLAGS.num_h,self.FLAGS.num_h), 
                                     initializer=self.weight_initializer)
            self.W_4=tf.get_variable(name='weight_5', shape=(self.FLAGS.num_h,self.FLAGS.num_v), 
                                     initializer=self.weight_initializer)

        with tf.name_scope('biases'):
            self.b1=tf.get_variable(name='bias_1', shape=(self.FLAGS.num_h), 
                                    initializer=self.bias_initializer)
            self.b2=tf.get_variable(name='bias_2', shape=(self.FLAGS.num_h), 
                                    initializer=self.bias_initializer)
            self.b3=tf.get_variable(name='bias_3', shape=(self.FLAGS.num_h), 
                                    initializer=self.bias_initializer)

2.訓練

給定一個輸入資料樣本 x(使用者—電影矩陣的一行),正向傳遞並計算網路輸出。隱藏層使用 sigmoid 作為啟用函式。請注意,最後一層沒有非線性或偏置項。

def _inference(self, x):
    '''Making one forward pass. Predicting the outputs, given the inputs.'''

    with tf.name_scope('inference'):
         a1=tf.nn.sigmoid(tf.nn.bias_add(tf.matmul(x, self.W_1),self.b1))
         a2=tf.nn.sigmoid(tf.nn.bias_add(tf.matmul(a1, self.W_2),self.b2))
         a3=tf.nn.sigmoid(tf.nn.bias_add(tf.matmul(a2, self.W_3),self.b3))   
         a4=tf.matmul(a3, self.W_4) 
    return a4

通過網路預測,我們可以計算這些預測與相應標籤(網路輸入 x)之間的損失。為了計算損失的平均值,我們還需要知道非零標籤的數量——也就是訓練集中使用者的總評分數。

def _compute_loss(self, predictions, labels,num_labels):
   ''' Computing the Mean Squared Error loss between the input and output of the network.

    @param predictions: predictions of the stacked autoencoder
    @param labels: input values of the stacked autoencoder which serve as labels at the same time
    @param num_labels: number of labels !=0 in the data set to compute the mean

    @return mean squared error loss tf-operation
    '''
         with tf.name_scope('loss'):
         loss_op=tf.div(tf.reduce_sum(tf.square(tf.subtract(predictions,labels))),num_labels)
         return loss_op

網路的優化/訓練步驟似乎有點棘手,讓我們一步一步討論。給定輸入 x,計算相應的輸出。你可能已經注意到,輸入 x 中的大部分值都是零值,因為使用者肯定沒有觀看和評估資料集中的所有 5953 部電影。因此,建議不要直接使用網路的原始預測。相反,我們必須確定資料輸入 x 中零值的索引,並將與這些索引相對應的預測向量中的值也設定為零。這種預測操縱極大地減少了網路的訓練時間,使網路有機會將訓練努力集中在使用者實際給出的評分上。

在此步驟之後,可以計算損失以及正則化損失(可選)。AdamOptimizer 會將損失函式最小化。請注意,該方法會返回一個均方根誤差(RMSE)而不是均方誤差(MSE),以測得更好的精度。

def _optimizer(self, x):
        '''Optimization of the network parameter through stochastic gradient descent.

            @param x: input values for the stacked autoencoder.

            @return: tensorflow training operation
            @return: ROOT!! mean squared error
        '''

        outputs=self._inference(x)
        mask=tf.where(tf.equal(x,0.0), tf.zeros_like(x), x) # indices of zero values in the training set (no ratings)
        num_train_labels=tf.cast(tf.count_nonzero(mask),dtype=tf.float32) # number of non zero values in the training set
        bool_mask=tf.cast(mask,dtype=tf.bool) # boolean mask
        outputs=tf.where(bool_mask, outputs, tf.zeros_like(outputs)) # set the output values to zero if corresponding input values are zero

        MSE_loss=self._compute_loss(outputs,x,num_train_labels)

        if self.FLAGS.l2_reg==True:
            l2_loss = tf.add_n([tf.nn.l2_loss(v) for v in tf.trainable_variables()])
            MSE_loss = MSE_loss +  self.FLAGS.lambda_ * l2_loss

        train_op=tf.train.AdamOptimizer(self.FLAGS.learning_rate).minimize(MSE_loss)
        RMSE_loss=tf.sqrt(MSE_loss)

        return train_op, RMSE_loss

3.測試

訓練幾個 epoch 之後,神經網路已經在訓練集中看到每個使用者的所有評分以及時間了。此時該模型應該已經瞭解資料中潛在的隱藏模式以及使用者對應的電影評級規律。給定使用者評分訓練樣本 x,該模型預測輸出 x'。該向量由輸入值 x 的重構(如預期)組成,但現在還包含輸入 x 中先前為零的值。這意味著該模型在給未評分的電影打分。這個評級對應於使用者的偏好——模型從資料中已識別和學習到的偏好。

為了能夠測量模型的準確性,我們需要訓練和測試資料集。根據訓練集進行預測。類似於訓練階段,我們只考慮對應於測試集中非零值的索引的輸出值。

現在我們可以計算預測值與實際評分之間的均方根誤差損失(RMSE)。RMSE 表示預測值與觀測值之間差異的樣本標準偏差。例如,RMSE 為 0.5 意味著平均預測評分與實際評分相差 0.5 星。

def _validation_loss(self, x_train, x_test):
        ''' Computing the loss during the validation time.
        @param x_train: training data samples
        @param x_test: test data samples
        @return networks predictions
        @return root mean squared error loss between the predicted and actual ratings
        '''
        outputs=self._inference(x_train) # use training sample to make prediction
        mask=tf.where(tf.equal(x_test,0.0), tf.zeros_like(x_test), x_test) # identify the zero values in the test ste
        num_test_labels=tf.cast(tf.count_nonzero(mask),dtype=tf.float32) # count the number of non zero values
        bool_mask=tf.cast(mask,dtype=tf.bool) 
        outputs=tf.where(bool_mask, outputs, tf.zeros_like(outputs))

        MSE_loss=self._compute_loss(outputs, x_test, num_test_labels)
        RMSE_loss=tf.sqrt(MSE_loss)

        return outputs, RMSE_loss

4.訓練結果

最後一步包括執行訓練過程並檢查模型效能。在這一點上,我不會詳細討論構建資料輸入管道、圖表、會話等細節。因為這些步驟通常是已知的。對此主題感興趣的讀者可以在我的 GitHub 中檢視這些步驟:https://github.com/artem-oppermann/Deep-Autoencoders-For-Collaborative-Filtering/blob/master/train.py

在這裡,你可以看到前 50 個迭代次數的訓練和測試表現。50 次後,測試集的預測和實際評分間的偏差是 0.929 星。

epoch_nr: 0, train_loss: 1.169, test_loss: 1.020

epoch_nr: 10, train_loss: 0.936, test_loss: 0.959

epoch_nr: 20, train_loss: 0.889, test_loss: 0.931

epoch_nr: 30, train_loss: 0.873, test_loss: 0.923

epoch_nr: 40, train_loss: 0.859, test_loss: 0.925

epoch_nr: 50, train_loss: 0.844, test_loss: 0.929

原文地址:https://towardsdatascience.com/deep-autoencoders-for-collaborative-filtering-6cf8d25bbf1d

相關文章