基於CNN的電影推薦系統


從深度學習卷積神經網絡入手,基於 Github 的開源項目來完成 MovieLens 數據集的電影推薦系統。

什么是推薦系統呢?

什么是推薦系統呢?首先我們來看看幾個常見的推薦場景。

如果你經常通過豆瓣電影評分來找電影,你會發現下圖所示的推薦:

enter image description here

如果你喜歡購物,根據你的選擇和購物行為,平台會給你推薦相似商品:

enter image description here

在互聯網的很多場景下都可以看到推薦的影子。因為推薦可以幫助用戶和商家滿足不同的需求:

  • 對用戶而言:找到感興趣的東西,幫助發現新鮮、有趣的事物。

  • 對商家而言:提供個性化服務,提高信任度和粘性,增加營收。

常見的推薦系統主要包含兩個方面的內容,基於用戶的推薦系統(UserCF)和基於物品的推薦系統(ItemCF)。兩者的區別在於,UserCF 給用戶推薦那些和他有共同興趣愛好的用戶喜歡的商品,而 ItemCF 給用戶推薦那些和他之前喜歡的商品類似的商品。這兩種方式都會遭遇冷啟動問題。

下面是 UserCF 和 ItemCF 的對比:

enter image description here

CNN 是如何應用在文本處理上的?

提到卷積神經網絡(CNN),相信大部分人首先想到的是圖像分類,比如 MNIST 手寫體識別,CAFRI10 圖像分類。CNN 已經在圖像識別方面取得了較大的成果,隨着近幾年的不斷發展,在文本處理領域,基於文本挖掘的文本卷積神經網絡被證明是有效的。

首先,來看看 CNN 是如何應用到 NLP 中的,下面是一個簡單的過程圖:

enter image description here

和圖像像素處理不一樣,自然語言通常是一段文字,那么在特征矩陣中,矩陣的每一個行向量(比如 word2vec 或者 doc2vec)代表一個 Token,包括詞或者字符。如果一段文字包含有 n 個詞,每個詞有 m 維的詞向量,那么我們可以構造出一個 n*m 的詞向量矩陣,在 NLP 處理過程中,讓過濾器寬度和矩陣寬度保持一致整行滑動。

實戰基於 CNN 的電影推薦系統

將 CNN 的技術應用到自然語言處理中並與電影推薦相結合,來訓練一個基於文本的卷積神經網絡,實現電影個性化推薦系統。

首先感謝作者 chengstone 的分享,源碼請訪問下面網址:

在驗證了 CNN 應用在自然語言處理上是有效的之后,從推薦系統的個性化推薦入手,在文本上,把 CNN 成果應用到電影的個性化推薦上。並在特征工程中,對訓練集和測試集做了相應的特征處理,其中有部分字段是類型性變量,特征工程上可以采用 one-hot 編碼,但是對於 UserID、MovieID 這樣非常稀疏的變量,如果使用 one-hot,那么數據的維度會急劇膨脹,對於這份數據集來說是不合適的。

具體算法設計如下:

1. 定義用戶嵌入矩陣。

用戶的特征矩陣主要是通過用戶信息嵌入網絡來生成的,在預處理數據的時候,我們將 UserID、MovieID、性別、年齡、職業特征全部轉成了數字類型,然后把這個數字當作嵌入矩陣的索引,在網絡的第一層就使用嵌入層,這樣數據輸入的維度保持在(N,32)和(N,16)。然后進行全連接層,轉成(N,128)的大小,再進行全連接層,轉成(N,200)的大小,這樣最后輸出的用戶特征維度相對比較高,也保證了能把每個用戶所帶有的特征充分攜帶並通過特征表達。

具體流程如下:

enter image description here

2. 生成用戶特征。

生成用戶特征是在用戶嵌入矩陣網絡輸出結果的基礎上,通過2層全連接層實現的。第一個全連接層把特征矩陣轉成(N,128)的大小,再進行第二次全連接層,轉成(N,200)的大小,這樣最后輸出的用戶特征維度相對比較高,也保證了能把每個用戶所帶有的特征充分攜帶並通過特征表達。

具體流程如下:

enter image description here

3. 定義電影 ID 嵌入矩陣。

通過電影 ID 和電影類型分別生成電影 ID 和電影類型特征,電影類型的多個嵌入向量做加和輸出。電影 ID 的實現過程和上面一樣,但是對於電影類型的處理相較於上面,稍微復雜一點。因為電影類型有重疊性,一個電影可以屬於多個類別,當把電影類型從嵌入矩陣索引出來之后是一個(N,32)形狀的矩陣,因為有多個類別,這里采用的處理方式是矩陣求和,把類別加上去,變成(1,32)形狀,這樣使得電影的類別信息不會丟失。

具體流程如下:

enter image description here

4. 文本卷積神經網絡設計。

文本卷積神經網絡和單純的 CNN 網絡結構有點不同,因為自然語言通常是一段文字與圖片像素組成的矩陣是不一樣的。在電影文本特征矩陣中,矩陣的每一個行構成的行向量代表一個 Token,包括詞或者字符。如果一段文字有 n 個詞,每個詞有 m 維的詞向量,那么我們可以構造出一個 n*m 的矩陣。而且 NLP 處理過程中,會有多個不同大小的過濾器串行執行,且過濾器寬度和矩陣寬度保持一致,是整行滑動。在執行完卷積操作之后采用了 ReLU 激活函數,然后采用最大池化操作,最后通過全連接並 Dropout 操作和 Softmax 輸出。這里電影名稱的處理比較特殊,並沒有采用循環神經網絡,而采用的是文本在 CNN 網絡上的應用。

對於電影數據集,我們對電影名稱做 CNN 處理,其大致流程,從嵌入矩陣中得到電影名對應的各個單詞的嵌入向量,由於電影名稱比較特殊一點,名稱長度有一定限制,這里過濾器大小使用時,就選擇2、3、4、5長度。然后對文本嵌入層使用滑動2、3、4、5個單詞尺寸的卷積核做卷積和最大池化,然后 Dropout 操作,全連接層輸出。

具體流程如下:

enter image description here

具體過程描述:

(1)首先輸入一個 32*32 的矩陣;

(2)第一次卷積核大小為 2*2,得到 31*31 的矩陣,然后通過 [1,14,1,1] max-pooling 操作,得到的矩陣為 18*31

(3)第二次卷積核大小為 3*3,得到 16*29的矩陣,然后通過[1,13,1,1] 的max-pooling 操作,得到的矩陣為 4*29

(4)第三次卷積核大小 4*4,得到 1*26 的矩陣,然后通過 [1,12,1,1] 的max-pooling 操作,得到的矩陣為 1*26

(5)第四次卷積核大小 5*5,得到 1*22 的矩陣,然后通過 [1,11,1,1] 的max-pooling 操作,得到的矩陣為 1*22

(6)最后通過 Dropout 和全連接層,len(window_sizes) * filter_num =32,得到 1*32的矩陣。

5. 電影各層做一個全連接層。

將上面幾步生成的特征向量,通過2個全連接層連接在一起,第一個全連接層是電影 ID 特征和電影類型特征先全連接,之后再和 CNN 生成的電影名稱特征全連接,生成最后的特征集。

具體流程如下:

enter image description here

6. 完整的基於 CNN 的電影推薦流程。

把以上實現的模塊組合成整個算法,將網絡模型作為回歸問題進行訓練,得到訓練好的用戶特征矩陣和電影特征矩陣進行推薦。

enter image description here

基於 CNN 的電影推薦系統代碼調參過程

在訓練過程中,我們需要對算法預先設置一些超參數,這里給出的最終的設置結果:

# 設置迭代次數
num_epochs = 5
# 設置BatchSize大小
batch_size = 256
# 設置dropout保留比例
dropout_keep = 0.5
# 設置學習率
learning_rate = 0.0001
# 設置每輪顯示的batches大小
show_every_n_batches = 20

首先對數據集進行划分,按照 4:1 的比例划分為訓練集和測試集,下面給出的是算法模型最終訓練集合測試集使用的划分結果:

# 將數據集分成訓練集和測試集,隨機種子不固定
train_X, test_X, train_y, test_y = train_test_split(features,
                                                    targets_values,
                                                    test_size=0.3,
                                                    random_state=0) 

接下來是具體模型訓練過程。訓練過程,要不斷調參,根據經驗調參粒度可以選擇從粗到細分階段進行。

調參過程對比:

(1)第一步,先固定,learning_rate=0.01 和 num_epochs=10,測試batch_size=128 對迭代時間和 Loss 的影響;

(2)第二步,先固定,learning_rate=0.01 和 num_epochs=10,測試batch_size=256 對迭代時間和 Loss 的影響;

(3)第三步,先固定,learning_rate=0.01 和 num_epochs=10,測試batch_size=512 對迭代時間和 Loss 的影響;

(4)第四步,先固定,learning_rate=0.01 和 num_epochs=5,測試batch_size=128 對迭代時間和 Loss 的影響;

(5)第五步,先固定,learning_rate=0.01 和 num_epochs=5,測試batch_size=256 對迭代時間和 Loss 的影響;

(6)第六步,先固定,learning_rate=0.01 和 num_epochs=5,測試batch_size=512 對迭代時間和 Loss 的影響;

(7)第七步,先固定,batch_size=256 和 num_epochs=5,測試learning_rate=0.001 對 Loss 的影響;

(8)第八步,先固定,batch_size=256 和 num_epochs=5,測試learning_rate=0.0005 對 Loss 的影響;

(9)第九步,先固定,batch_size=256 和 num_epochs=5,測試learning_rate=0.0001 對 Loss 的影響;

(10)第十步,先固定,batch_size=256 和 num_epochs=5,測試learning_rate=0.00005 對 Loss 的影響。

得到的調參結果對比表如下:

enter image description here

通過上面(1)-(6)步調參比較,在 learning_ratebatch_size 相同的情況下,num_epochs 對於訓練時間影響較大;而在learning_ratenum_epochs 相同情況下,batch_size 對 Loss 的影響較大,batch_size 選擇512,Loss 有抖動情況,權衡之下,最終確定后續調參固定采用 batch_size=256num_epochs=5 的超參數值,后續(7)-(10)步,隨着 learning_rate 逐漸減小,發現 Loss 是先逐漸減小,而在learning_rate=0.00005 時反而增大,最終選擇出學習率為learning_rate=0.0001 的超參數值。

基於 CNN 的電影推薦系統電影推薦

在上面,完成模型訓練驗證之后,實際來進行推薦電影,這里使用生產的用戶特征矩陣和電影特征矩陣做電影推薦,主要有三種方式的推薦。

1. 推薦同類型的電影。

思路是:計算當前看的電影特征向量與整個電影特征矩陣的余弦相似度,取相似度最大的 top_k 個,這里加了些隨機選擇在里面,保證每次的推薦稍稍有些不同。

def recommend_same_type_movie(movie_id_val, top_k=20):
    loaded_graph = tf.Graph()  #
    with tf.Session(graph=loaded_graph) as sess:  #
        # Load saved model
        loader = tf.train.import_meta_graph(load_dir + '.meta')
        loader.restore(sess, load_dir)

        norm_movie_matrics = tf.sqrt(tf.reduce_sum(tf.square(movie_matrics), 1, keep_dims=True))
        normalized_movie_matrics = movie_matrics / norm_movie_matrics

        # 推薦同類型的電影
        probs_embeddings = (movie_matrics[movieid2idx[movie_id_val]]).reshape([1, 200])
        probs_similarity = tf.matmul(probs_embeddings, tf.transpose(normalized_movie_matrics))
        sim = (probs_similarity.eval())
        print("您看的電影是:{}".format(movies_orig[movieid2idx[movie_id_val]]))
        print("以下是給您的推薦:")
        p = np.squeeze(sim)
        p[np.argsort(p)[:-top_k]] = 0
        p = p / np.sum(p)
        results = set()
        while len(results) != 5:
            c = np.random.choice(3883, 1, p=p)[0]
            results.add(c)
        for val in (results):
            print(val)
            print(movies_orig[val])
        return result

2. 推薦您喜歡的電影。

思路是:使用用戶特征向量與電影特征矩陣計算所有電影的評分,取評分最高的top_k 個,同樣加了些隨機選擇部分。

def recommend_your_favorite_movie(user_id_val, top_k=10):
    loaded_graph = tf.Graph()  #
    with tf.Session(graph=loaded_graph) as sess:  #
        # Load saved model
        loader = tf.train.import_meta_graph(load_dir + '.meta')
        loader.restore(sess, load_dir)

        # 推薦您喜歡的電影
        probs_embeddings = (users_matrics[user_id_val - 1]).reshape([1, 200])
        probs_similarity = tf.matmul(probs_embeddings, tf.transpose(movie_matrics))
        sim = (probs_similarity.eval())

        print("以下是給您的推薦:")
        p = np.squeeze(sim)
        p[np.argsort(p)[:-top_k]] = 0
        p = p / np.sum(p)
        results = set()
        while len(results) != 5:
            c = np.random.choice(3883, 1, p=p)[0]
            results.add(c)
        for val in (results):
            print(val)
            print(movies_orig[val])

        return results

3. 看過這個電影的人還看了(喜歡)哪些電影。

(1)首先選出喜歡某個電影的 top_k 個人,得到這幾個人的用戶特征向量;

(2)然后計算這幾個人對所有電影的評分 ;

(3)選擇每個人評分最高的電影作為推薦;

(4)同樣加入了隨機選擇。

def recommend_other_favorite_movie(movie_id_val, top_k=20):
    loaded_graph = tf.Graph()  #
    with tf.Session(graph=loaded_graph) as sess:  #
        # Load saved model
        loader = tf.train.import_meta_graph(load_dir + '.meta')
        loader.restore(sess, load_dir)
        probs_movie_embeddings = (movie_matrics[movieid2idx[movie_id_val]]).reshape([1, 200])
        probs_user_favorite_similarity = tf.matmul(probs_movie_embeddings, tf.transpose(users_matrics))
        favorite_user_id = np.argsort(probs_user_favorite_similarity.eval())[0][-top_k:]

        print("您看的電影是:{}".format(movies_orig[movieid2idx[movie_id_val]]))

        print("喜歡看這個電影的人是:{}".format(users_orig[favorite_user_id - 1]))
        probs_users_embeddings = (users_matrics[favorite_user_id - 1]).reshape([-1, 200])
        probs_similarity = tf.matmul(probs_users_embeddings, tf.transpose(movie_matrics))
        sim = (probs_similarity.eval())
        p = np.argmax(sim, 1)
        print("喜歡看這個電影的人還喜歡看:")
        results = set()
        while len(results) != 5:
            c = p[random.randrange(top_k)]
            results.add(c)
        for val in (results):
            print(val)
            print(movies_orig[val])
        return results

基於 CNN 的電影推薦系統不足

基於上述方法所帶來的不足:

  1. 由於一個新的用戶在剛開始的時候並沒有任何行為記錄,所以系統會出現冷啟動的問題;

  2. 由於神經網絡是一個黑盒子過程,我們並不清楚在反向傳播的過程中的具體細節,也不知道每一個卷積層抽取的特征細節,所以此算法缺乏一定的可解釋性;

  3. 一般來說,在工業界,用戶的數據量是海量的,而卷積神經網絡又要耗費大量的計算資源,所以進行集群計算是非常重要的。但是由於本課程所做實驗環境有限,還是在單機上運行,所以后期可以考慮在服務器集群上全量跑數據,這樣獲得的結果也更准確。

總結

上面通過 Github 上一個開源的項目,梳理了 CNN 在文本推薦上的應用,並通過模型訓練調參,給出一般的模型調參思路,最后建議大家自己把源碼下載下來跑跑模型,效果更好。

參考文獻

  1. 推薦系統

  2. 推薦系統實踐,p50-60,p120-130,項亮。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM