宿遷做網(wǎng)站哪家公司好官方百度下載安裝
代碼下載:
基于tensorflow的個(gè)性化電影推薦系統(tǒng)實(shí)戰(zhàn)(有前端).zip資源-CSDN文庫
項(xiàng)目簡介:
- dl_re_web?: Web 項(xiàng)目的文件夾
- re_sys: Web app
- model:百度云下載之后,把model放到該文件夾下
- recommend: 網(wǎng)絡(luò)模型相關(guān)
- data: 訓(xùn)練數(shù)據(jù)集文件夾
- DataSet.py:數(shù)據(jù)集加載相關(guān)
- re_model.py: 網(wǎng)絡(luò)模型類
- utils.py:工具、爬蟲
- static?:Web 頁面靜態(tài)資源
- templates?: 為 Web 頁面的 Html 頁面
- venv:Django 項(xiàng)目資源文件夾
- db.sqlite3?: Django 自帶的數(shù)據(jù)庫
- manage.py: Django 執(zhí)行腳本
- 網(wǎng)絡(luò)模型.vsdx:網(wǎng)絡(luò)模型圖(visio)
- deep-learning-README.pdf:README的pdf版,如果github的README顯示存在問題請下載這個(gè)文件
- 項(xiàng)目背景
? 本系統(tǒng)將神經(jīng)網(wǎng)絡(luò)在自然語言處理與電影推薦相結(jié)合,利用MovieLens數(shù)據(jù)集訓(xùn)練一個(gè)基于文本的卷積神經(jīng)網(wǎng)絡(luò),實(shí)現(xiàn)電影個(gè)性化推薦系統(tǒng)。最后使用django框架并結(jié)合豆瓣爬蟲,搭建推薦系統(tǒng)web端服務(wù)。
-
主要實(shí)現(xiàn)功能
給用戶推薦喜歡的電影
推薦相似的電影
推薦看過的用戶還喜歡看的電影
-
網(wǎng)絡(luò)模型
一. 數(shù)據(jù)處理
1. MovieLens數(shù)據(jù)集
用戶數(shù)據(jù)users.dat
? 性別字段:將‘F’ 和 ‘M’轉(zhuǎn)換為0和1
? 年齡字段:轉(zhuǎn)為連續(xù)數(shù)字
電影數(shù)據(jù)movies.dat
? 流派字段:部分電影不僅只有一個(gè)分類,所以將該字段轉(zhuǎn)為數(shù)字列表
? 標(biāo)題字段:同上,創(chuàng)建英文標(biāo)題的數(shù)字字典,并生成數(shù)字列表,并去掉標(biāo)題中的年份
? 注:為方便網(wǎng)絡(luò)處理,以上兩字段長度需要統(tǒng)一
評分?jǐn)?shù)據(jù)ratings.dat
數(shù)據(jù)處理完之后將三個(gè)表做 inner merge,并保存為模型文件 data_preprocess.pkl
2. 處理后的數(shù)據(jù)
我們看到部分字段是類型性變量,如 UserID、MovieID 這樣非常稀疏的變量,如果使用?
one-hot
,那么數(shù)據(jù)的維度會急劇膨脹,算法的效率也會大打折扣。
二. 建模&訓(xùn)練
針對處理后數(shù)據(jù)的不同字段進(jìn)行模型的搭建
1. 嵌入層
根據(jù)上文,為了解決數(shù)據(jù)稀疏問題,
One-hot
的矩陣相乘可以簡化為查表操作,這大大降低了運(yùn)算量。我們不是每一個(gè)詞用一個(gè)向量來代替,而是替換為用于查找嵌入矩陣中向量的索引,在網(wǎng)絡(luò)的訓(xùn)練過程中,嵌入向量也會更新,我們也就可以探索在高維空間中詞語之間的相似性。
? 本系統(tǒng)使用tensorflow的
tf.nn.embedding_lookup
?,就是根據(jù)input_ids中的id,尋找embeddings中的第id行。比如input_ids=[1,3,5],則找出embeddings中第1,3,5行,組成一個(gè)tensor返回。tf.nn.embedding_lookup
不是簡單的查表,id對應(yīng)的向量是可以訓(xùn)練的,訓(xùn)練參數(shù)個(gè)數(shù)應(yīng)該是?category num*embedding size
,也可以說lookup是一種全連接層。
- 解析:
- 創(chuàng)建嵌入矩陣,我們要決定每一個(gè)索引需要分配多少個(gè) 潛在因子,這大體上意味著我們想要多長的向量,通常使用的情況是長度分配為32和50,此處選擇32和16,所以我們看到各字段嵌入矩陣的shape第1個(gè)維度,也就是第2個(gè)數(shù)字要么為32,要么為16;
- 而嵌入矩陣第0個(gè)緯度為6041、2、7、21,也就是嵌入矩陣的行數(shù),也就代表著這四個(gè)字段unique值有多少個(gè),例如Gender的值只有0和1(經(jīng)過數(shù)據(jù)處理)其嵌入矩陣就有2行
- 到現(xiàn)在,想必大家可以清楚嵌入矩陣的好處了,我們以
UserId
字段為例子,使用one-hot編碼,數(shù)據(jù)就需要增加數(shù)據(jù)量x6041
個(gè)數(shù)據(jù),如果數(shù)據(jù)量較大,或者字段的unique值較多,在訓(xùn)練時(shí)則會耗費(fèi)大量資源,但是如果使用嵌入矩陣,我們僅僅只用創(chuàng)建一個(gè)6041x32
的矩陣,然后使用tf.nn.embedding_lookup
與UserID字段的數(shù)據(jù)進(jìn)行全連接(相當(dāng)于查表操作),即可用一個(gè)一維的長度為32的數(shù)組表示出該UserID,大大簡化了運(yùn)算的耗時(shí)。- 在上一點(diǎn)已經(jīng)講過使用
tf.nn.embedding_lookup
與UserID字段的數(shù)據(jù)進(jìn)行全連接(相當(dāng)于查表操作),則每個(gè)嵌入層的shape應(yīng)該是這樣的(數(shù)據(jù)量,字段長度,索引長度)
,數(shù)據(jù)量
可以設(shè)計(jì)為每個(gè)epoch的大小;對于User數(shù)據(jù)來說,字段長度都為1,因?yàn)橛靡粋€(gè)值就能表示改獨(dú)一無二的值,如果對于文本,則可能需要使用數(shù)組來表示,即字段長度可能大于1,稍后會在Movie數(shù)據(jù)處理中進(jìn)一步解釋;索引長度則是嵌入矩陣的潛在因子。
?? 例子:對數(shù)據(jù)集字段UserID
、Gender
、Age
、JobID
分別構(gòu)建嵌入矩陣和嵌入層
def create_user_embedding(self, uid, user_gender, user_age, user_job):with tf.name_scope("user_embedding"):uid_embed_matrix = tf.Variable(tf.random_uniform([self.uid_max, self.embed_dim], -1, 1),name="uid_embed_matrix") # (6041,32)uid_embed_layer = tf.nn.embedding_lookup(uid_embed_matrix, uid, name="uid_embed_layer") # (?,1,32)gender_embed_matrix = tf.Variable(tf.random_uniform([self.gender_max, self.embed_dim // 2], -1, 1),name="gender_embed_matrix") # (2,16)gender_embed_layer = tf.nn.embedding_lookup(gender_embed_matrix, user_gender, name="gender_embed_layer") # (?,1,16)age_embed_matrix = tf.Variable(tf.random_uniform([self.age_max, self.embed_dim // 2], -1, 1),name="age_embed_matrix") # (7,16)age_embed_layer = tf.nn.embedding_lookup(age_embed_matrix, user_age, name="age_embed_layer")# (?,1,16)job_embed_matrix = tf.Variable(tf.random_uniform([self.job_max, self.embed_dim // 2], -1, 1),name="job_embed_matrix") # (21,16)job_embed_layer = tf.nn.embedding_lookup(job_embed_matrix, user_job, name="job_embed_layer")# (?,1,16)return uid_embed_layer, gender_embed_layer, age_embed_layer, job_embed_layer
類似地,我們在相應(yīng)代碼中分別創(chuàng)建了電影數(shù)據(jù)的MovieID、Genres、Title的嵌入矩陣,其中需要特別注意的是:
- Title嵌入層的shape是
(?,15,32)
,“?”代表了一個(gè)epoch的數(shù)量,32代表了自定義選擇的潛在因子數(shù)量,15則代表了該字段的每一個(gè)unique值都需要一個(gè)長度為15的向量來表示。- Genres嵌入層的shape是
(?,1,32)
,由于一個(gè)電影的Genres(電影的類型),可能屬于多個(gè)類別,所以該字段的需要做特殊的處理,即把第1緯度上的向量進(jìn)行加和,這樣做其實(shí)削減了特征的表現(xiàn),但是又防止比如僅僅只推薦相關(guān)類型的電影。
- 綜上,經(jīng)過嵌入層,我們得到一下模型:
針對User數(shù)據(jù)
模型名稱 | shape |
---|---|
uid_embed_matrix | (6041,32) |
gender_embed_matrix | (2,16) |
age_embed_matrix | (7,16) |
job_embed_matrix | (21,16) |
uid_embed_layer | (?,1,32) |
gender_embed_layer | (?,1,16) |
age_embed_layer | (?,1,16) |
job_embed_layer | (?,1,16) |
針對Movie數(shù)據(jù)
模型名稱 | shape |
---|---|
movie_id_embed_matrix | (3953,32) |
movie_categories_embed_matrix | (19,32) |
movie_title_embed_matrix | (5215,32) |
movie_id_embed_layer | (?,1,32) |
movie_categories_embed_layer | (?,1,32) |
movie_title_embed_layer | (?,15,32) |
2. 文本卷積層
本文僅介紹了推導(dǎo)過程,并為介紹卷積層設(shè)計(jì)的思路。設(shè)計(jì)思路請看參考文獻(xiàn)
文本卷積層僅涉及到電影數(shù)據(jù)的Title字段,其實(shí)Genres字段也是可以進(jìn)行文本卷積設(shè)計(jì)的,但是上文解釋過,考慮到推薦數(shù)據(jù)字段的影響,對Genres僅設(shè)計(jì)了常規(guī)的網(wǎng)絡(luò)。
卷積過程涉及到一下幾個(gè)參數(shù):
name&value | 解釋 |
---|---|
windows_size=[2,3,4,5] | 不同卷積的滑動窗口是可變的 |
fliter_num=8 | 卷積核(濾波器)的數(shù)量 |
filter_weight =(windows_size,32,1,fliter_num) | 卷積核的權(quán)重,四個(gè)參數(shù)分別為(高度,寬度,輸入通道數(shù),輸出通道數(shù)) |
filter_bias=8 | 卷積核的偏置=卷積核的輸出通道數(shù)=卷積核的數(shù)量 |
-
過程
我們將Title字段潛入層的輸出
movie_title_embed_layer
(shape=(?,15,32)),作為卷積層的輸入,所以我們先把movie_title_embed_layer
擴(kuò)展一個(gè)維度,shape變?yōu)?#xff08;?,15,32,1),四個(gè)參數(shù)分別為(batch,height,width,channels)movie_title_embed_layer_expand = tf.expand_dims(movie_title_embed_layer, -1) # 在最后加上一個(gè)維度
使用不同尺寸的卷積核做卷積和最大池化,相關(guān)參數(shù)的變化不再贅述
pool_layer_lst = [] for window_size in self.window_sizes:with tf.name_scope("movie_txt_conv_maxpool_{}".format(window_size)):# 卷積核權(quán)重 filter_weights = tf.Variable(tf.truncated_normal([window_size, self.embed_dim, 1, self.filter_num], stddev=0.1),name="filter_weights") # 卷積核偏執(zhí) filter_bias = tf.Variable(tf.constant(0.1, shape=[self.filter_num]), name="filter_bias")# 卷積層 第一個(gè)參數(shù)為:輸入 第二個(gè)參數(shù)為:卷積核權(quán)重 第三個(gè)參數(shù)為:步長conv_layer = tf.nn.conv2d(movie_title_embed_layer_expand, filter_weights, [1, 1, 1, 1], padding="VALID",name="conv_layer")# 激活層 參數(shù)的shape保持不變r(jià)elu_layer = tf.nn.relu(tf.nn.bias_add(conv_layer, filter_bias), name="relu_layer")# 池化層 第一個(gè)參數(shù)為:輸入 第二個(gè)參數(shù)為:池化窗口大小 第三個(gè)參數(shù)為:步長 maxpool_layer = tf.nn.max_pool(relu_layer, [1, self.sentences_size - window_size + 1, 1, 1],[1, 1, 1, 1],padding="VALID", name="maxpool_layer")pool_layer_lst.append(maxpool_layer)
可得到:
widow_size filter_weights filter_bias conv_layer relu_layer maxpool_layer 2 (2,32,1,8) 8 (?,14,1,8) (?,14,1,8) (?,1,1,8) 3 (3,32,1,8) 8 (?,13,1,8) (?,14,1,8) (?,1,1,8) 4 (4,32,1,8) 8 (?,12,1,8) (?,14,1,8) (?,1,1,8) 5 (5,32,1,8) 8 (?,11,1,8) (?,14,1,8) (?,1,1,8) 例子解析:
? 我們考慮window_size=2的情況,首先我們得到嵌入層輸出,并對其增加一個(gè)維度得到
movie_title_embed_layer_expand(shape=(?,15,32,1))
,其作為卷積層的輸入。? 卷積核的參數(shù)
filter_weights
為(2,32,1,8),表示卷積核的高度為2,寬度為32,輸入通道為1,輸出通道為32。其中輸出通道與上一層的輸入通道相同。? 卷積層在各個(gè)維度上的步長都為1,且padding的方式為VALID,則可得到卷基層的shape為(?,14,1,8)。
? 卷積之后使用relu函數(shù)進(jìn)行激活,并且加上偏置,shape保持不變。
? 最大池化的窗口為(1,14,1,1),且在每個(gè)維度上的步長都為1,即可得到池化后的shape為(?,1,1,8)。
? 依次類推,當(dāng)window_size為其他時(shí),也能得到池化層輸出shape為(?,1,1,8)。
得到四個(gè)卷積、池化的輸出之后,我們使用如下代碼將池化層的輸出根據(jù)第3維,也就是第四個(gè)參數(shù)相連,變形為(?,1,1,32),再變形為三維(?,1,32)。
pool_layer = tf.concat(pool_layer_lst, 3, name="pool_layer") #(?,1,1,32) max_num = len(self.window_sizes) * self.filter_num # 32 pool_layer_flat = tf.reshape(pool_layer, [-1, 1, max_num], name="pool_layer_flat") #(?,1,32) 其實(shí)僅僅是減少了一個(gè)緯度,?仍然為每一批批量
? 最后為了正則化防止過擬合,經(jīng)過dropout層處理,輸出shape為(?,1,32)。
?
3. 全連接層
? 對上文所得到的嵌入層的輸出和卷基層的輸出進(jìn)行全連接。
- 對User數(shù)據(jù)的嵌入層進(jìn)行全連接,最終得到輸出特征的shape為(?,200)
def create_user_feature_layer(self, uid_embed_layer, gender_embed_layer, age_embed_layer, job_embed_layer):with tf.name_scope("user_fc"):# 第一層全連接 改變最后一維uid_fc_layer = tf.layers.dense(uid_embed_layer, self.embed_dim, name="uid_fc_layer", activation=tf.nn.relu)gender_fc_layer = tf.layers.dense(gender_embed_layer, self.embed_dim, name="gender_fc_layer",activation=tf.nn.relu)age_fc_layer = tf.layers.dense(age_embed_layer, self.embed_dim, name="age_fc_layer", activation=tf.nn.relu)job_fc_layer = tf.layers.dense(job_embed_layer, self.embed_dim, name="job_fc_layer", activation=tf.nn.relu)# (?,1,32)# 第二層全連接user_combine_layer = tf.concat([uid_fc_layer, gender_fc_layer, age_fc_layer, job_fc_layer], 2)# (?, 1, 128)user_combine_layer = tf.contrib.layers.fully_connected(user_combine_layer, 200, tf.tanh) # (?, 1, 200)user_combine_layer_flat = tf.reshape(user_combine_layer, [-1, 200]) #(?,200)return user_combine_layer, user_combine_layer_flat
- 同理對Movie數(shù)據(jù)同樣進(jìn)行兩層全連接,最終得到輸出特征的shape為(?,200)
def create_movie_feature_layer(self, movie_id_embed_layer, movie_categories_embed_layer, dropout_layer):with tf.name_scope("movie_fc"):# 第一層全連接movie_id_fc_layer = tf.layers.dense(movie_id_embed_layer, self.embed_dim, name="movie_id_fc_layer",activation=tf.nn.relu) #(?,1,32)movie_categories_fc_layer = tf.layers.dense(movie_categories_embed_layer, self.embed_dim,name="movie_categories_fc_layer", activation=tf.nn.relu)#(?,1,32)# 第二層全連接movie_combine_layer = tf.concat([movie_id_fc_layer, movie_categories_fc_layer, dropout_layer],2) # (?, 1, 96)movie_combine_layer = tf.contrib.layers.fully_connected(movie_combine_layer, 200, tf.tanh) # (?, 1, 200)movie_combine_layer_flat = tf.reshape(movie_combine_layer, [-1, 200])return movie_combine_layer, movie_combine_layer_flat
4. 構(gòu)建計(jì)算圖&訓(xùn)練
? 構(gòu)建計(jì)算圖,訓(xùn)練。問題回歸為簡單的將用戶特征和電影特征做矩陣乘法得到一個(gè)預(yù)測評分,損失為均方誤差。
inference = tf.reduce_sum(user_combine_layer_flat * movie_combine_layer_flat, axis=1)
inference = tf.expand_dims(inference, axis=1)
cost = tf.losses.mean_squared_error(targets, inference)
loss = tf.reduce_mean(cost)
global_step = tf.Variable(0, name="global_step", trainable=False)
optimizer = tf.train.AdamOptimizer(lr) # 傳入學(xué)習(xí)率
gradients = optimizer.compute_gradients(loss) # cost
train_op = optimizer.apply_gradients(gradients, global_step=global_step)
- 模型保存
保存的模型包括:處理后的訓(xùn)練數(shù)據(jù)、訓(xùn)練完成后的網(wǎng)絡(luò)、用戶特征矩陣、電影特征矩陣。
- 損失圖像
?
- 經(jīng)過簡單的調(diào)參。
batch_size
?對Loss的影響較大,但是batch_size
?過大,損失會有比較大的抖動情況。隨著學(xué)習(xí)率逐漸減小,損失會先減小后增大,所以最終確定參數(shù)還是原作者的固定參數(shù)效果較好。
5. 推薦
加了隨機(jī)因素保證對相同電影推薦時(shí)推薦結(jié)果的不一致
-
給用戶推薦喜歡的電影:使用用戶特征向量與電影特征矩陣計(jì)算所有電影的評分,取評分最高的 topK個(gè)
-
推薦相似的電影:計(jì)算選擇電影特征向量與整個(gè)電影特征矩陣的余弦相似度,取相似度最大的 topK 個(gè)
-
推薦看過的用戶還喜歡看的電影
? 3.1 首先選出喜歡某個(gè)電影的 topK 個(gè)人,得到這幾個(gè)人的用戶特征向量
? 3.2 計(jì)算這幾個(gè)人對所有電影的評分
? 3.3 選擇每個(gè)人評分最高的電影作為推薦
三. Web展示端
1. django框架開發(fā)web
? 由于給定的數(shù)據(jù)集中并未有用戶的其它信息,所以僅展示了“推薦相似的電影”和“推薦看過的用戶還喜歡看的電影”,沒有展示“給用戶推薦喜歡的電影”這個(gè)模塊,并且數(shù)據(jù)集也未有電影的中文名稱、圖片等數(shù)據(jù),所以我在web項(xiàng)目中加了一個(gè)豆瓣的爬蟲,每次推薦都請求數(shù)據(jù),并進(jìn)行相應(yīng)的解析和封裝。
? 在服務(wù)器啟動的時(shí)候就加載模型,并且把tensorflow的session提前封裝好,在調(diào)用相關(guān)方法時(shí),直接傳入該全局session,避免了每次請求都加載模型。
? 前端請求推薦的耗時(shí)大部分是爬蟲請求的耗時(shí),并且訪問頻率過快會被豆瓣拒絕請求一段時(shí)間.
2. 展示截圖
-
后臺推薦結(jié)果
? 給用戶推薦喜歡的電影
-
? 推薦相似的電影
推薦看過的用戶還喜歡看的電影
?
四. 實(shí)驗(yàn)項(xiàng)目自評與總結(jié)
? 通過本次實(shí)驗(yàn)深度學(xué)習(xí)算是跨入了門檻,對tensorflow框架的基本使用有了一定的了解,并且此次實(shí)驗(yàn)的選題為推薦,是我比較喜歡的一個(gè)方向,之前對協(xié)同過濾等算法有所研究,此次利用深層網(wǎng)絡(luò)對數(shù)據(jù)的特征進(jìn)行提取更加深了我對推薦的理解。
? 當(dāng)然,本次實(shí)驗(yàn)的核心代碼和模型架構(gòu)是copy的,但我對模型的每一步都進(jìn)行了演算推導(dǎo),并整理成該文檔,除此我把源碼進(jìn)行了面向?qū)ο蠓庋b,增強(qiáng)了源碼的復(fù)用性和可用性,對推薦相關(guān)方法也進(jìn)行了微小的調(diào)整,解決了模型多次加載問題,最后增加了該項(xiàng)目的web展示端。
? 此次實(shí)驗(yàn)收貨頗豐,但是該系統(tǒng)還存在一系列問題:如模型的局限性,即該系統(tǒng)只能對數(shù)據(jù)集中的電影和用戶進(jìn)行推薦,我沒有再找到具有相關(guān)字段的數(shù)據(jù),所以訓(xùn)練數(shù)據(jù)量相對較小,適用性也比較窄。
? 網(wǎng)絡(luò)中有很多對MovieLens數(shù)據(jù)集的推薦算法,我想在學(xué)習(xí)了相關(guān)算法之后,能把這些算法用到工業(yè)界或者傳統(tǒng)業(yè)會比針對一個(gè)已存在幾十年的數(shù)據(jù)集提高那百分之零點(diǎn)幾的準(zhǔn)確率或降低微小的誤差更有意義,當(dāng)然,要解決的問題也會更多,加油吧!
以下是我的推導(dǎo)手稿截圖:
?
五. 參考文獻(xiàn)
【1】Convolutional Neural Networks for Sentence Classification
【2】Understanding Convolutional Neural Networks for NLP