NLP第10課:動手實戰(zhàn)基于 CNN 的電影推薦系統(tǒng)

本文從深度學(xué)習(xí)卷積神經(jīng)網(wǎng)絡(luò)入手,基于 Github 的開源項目來完成 MovieLens 數(shù)據(jù)集的電影推薦系統(tǒng)。

什么是推薦系統(tǒng)呢?

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

如果你經(jīng)常通過豆瓣電影評分來找電影,你會發(fā)現(xiàn)下圖所示的推薦:

image

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

image

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

  • 對用戶而言:找到感興趣的東西,幫助發(fā)現(xiàn)新鮮、有趣的事物。

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

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

下面是 UserCF 和 ItemCF 的對比:

image

CNN 是如何應(yīng)用在文本處理上的?

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

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

image

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

動手實戰(zhàn)基于 CNN 的電影推薦系統(tǒng)

將 CNN 的技術(shù)應(yīng)用到自然語言處理中并與電影推薦相結(jié)合,來訓(xùn)練一個基于文本的卷積神經(jīng)網(wǎng)絡(luò),實現(xiàn)電影個性化推薦系統(tǒng)。

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

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

具體算法設(shè)計如下:

1. 定義用戶嵌入矩陣。

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

具體流程如下:

image

2. 生成用戶特征。

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

具體流程如下:

image

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

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

具體流程如下:

image

4. 文本卷積神經(jīng)網(wǎng)絡(luò)設(shè)計。

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

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

具體流程如下:

image

具體過程描述:

(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 生成的電影名稱特征全連接,生成最后的特征集。

具體流程如下:

image

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

把以上實現(xiàn)的模塊組合成整個算法,將網(wǎng)絡(luò)模型作為回歸問題進行訓(xùn)練,得到訓(xùn)練好的用戶特征矩陣和電影特征矩陣進行推薦。

image

基于 CNN 的電影推薦系統(tǒng)代碼調(diào)參過程

在訓(xùn)練過程中,我們需要對算法預(yù)先設(shè)置一些超參數(shù),這里給出的最終的設(shè)置結(jié)果:

    # 設(shè)置迭代次數(shù)
    num_epochs = 5
    # 設(shè)置BatchSize大小
    batch_size = 256
    #設(shè)置dropout保留比例
    dropout_keep = 0.5
    # 設(shè)置學(xué)習(xí)率
    learning_rate = 0.0001
    # 設(shè)置每輪顯示的batches大小
    show_every_n_batches = 20

首先對數(shù)據(jù)集進行劃分,按照 4:1 的比例劃分為訓(xùn)練集和測試集,下面給出的是算法模型最終訓(xùn)練集合測試集使用的劃分結(jié)果:

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

接下來是具體模型訓(xùn)練過程。訓(xùn)練過程,要不斷調(diào)參,根據(jù)經(jīng)驗調(diào)參粒度可以選擇從粗到細分階段進行。

調(diào)參過程對比:

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

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

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

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

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

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

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

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

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

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

得到的調(diào)參結(jié)果對比表如下:

image

通過上面(1)-(6)步調(diào)參比較,在 learning_ratebatch_size 相同的情況下,num_epochs對于訓(xùn)練時間影響較大;而在 learning_rate、num_epochs 相同情況下,batch_size 對 Loss 的影響較大,batch_size 選擇512,Loss 有抖動情況,權(quán)衡之下,最終確定后續(xù)調(diào)參固定采用 batch_size=256num_epochs=5 的超參數(shù)值,后續(xù)(7)-(10)步,隨著 learning_rate 逐漸減小,發(fā)現(xiàn) Loss 是先逐漸減小,而在 learning_rate=0.00005 時反而增大,最終選擇出學(xué)習(xí)率為 learning_rate=0.0001 的超參數(shù)值。

基于 CNN 的電影推薦系統(tǒng)電影推薦

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

1. 推薦同類型的電影。

思路是:計算當(dāng)前看的電影特征向量與整個電影特征矩陣的余弦相似度,取相似度最大的 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

  1. 推薦您喜歡的電影。

思路是:使用用戶特征向量與電影特征矩陣計算所有電影的評分,取評分最高的 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

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

(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 的電影推薦系統(tǒng)不足

這里討論一下基于上述方法所帶來的不足:

  1. 由于一個新的用戶在剛開始的時候并沒有任何行為記錄,所以系統(tǒng)會出現(xiàn)冷啟動的問題;

  2. 由于神經(jīng)網(wǎng)絡(luò)是一個黑盒子過程,我們并不清楚在反向傳播的過程中的具體細節(jié),也不知道每一個卷積層抽取的特征細節(jié),所以此算法缺乏一定的可解釋性;

  3. 一般來說,在工業(yè)界,用戶的數(shù)據(jù)量是海量的,而卷積神經(jīng)網(wǎng)絡(luò)又要耗費大量的計算資源,所以進行集群計算是非常重要的。但是由于本課程所做實驗環(huán)境有限,還是在單機上運行,所以后期可以考慮在服務(wù)器集群上全量跑數(shù)據(jù),這樣獲得的結(jié)果也更準確。

總結(jié)

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

參考文獻及推薦閱讀

  1. 推薦系統(tǒng)

  2. Deep Convolutional Neural Networks for Sentiment Analysis of ShortTexts,CND Santos ,M Gattit ,2014.

  3. 推薦系統(tǒng)實踐,p50-60,p120-130,項亮。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容