TensorFlow工程實戰(三):結合知識圖譜實現電影推薦系統

本文藉助多任務學習端到端框架MKR,從知識圖譜中找出電影間的潛在特徵,並藉助該特徵及電影評分數據集,實現基於電影的推薦系統。

本文摘選自電子工業出版社出版、李金洪編著的《深度學習之TensorFlow工程化項目實戰》一書的實例38:TensorFlow結合知識圖譜實現基於電影的推薦系統。

知識圖譜(Knowledge Graph,KG)可以理解成一個知識庫,用來存儲實體與實體之間的關係。知識圖譜可以爲機器學習算法提供更多的信息,幫助模型更好地完成任務。

在推薦算法中融入電影的知識圖譜,能夠將沒有任何歷史數據的新電影精準地推薦給目標用戶。

實例描述

現有一個電影評分數據集和一個電影相關的知識圖譜。電影評分數據集裏包含用戶、電影及評分;電影相關的知識圖譜中包含電影的類型、導演等屬性。

要求:從知識圖譜中找出電影間的潛在特徵,並藉助該特徵及電影評分數據集,實現基於電影的推薦系統。

本實例使用了一個多任務學習的端到端框架MKR。該框架能夠將兩個不同任務的低層特徵抽取出來,並融合在一起實現聯合訓練,從而達到最優的結果。有關MKR的更多介紹可以參考以下鏈接:

https://arxiv.org/pdf/1901.08907.pdf

一、準備數據集

在上述論文的相關代碼鏈接中有3個數據集:圖書數據集、電影數據集和音樂數據集。本例使用電影數據集,具體鏈接如下:

https://github.com/hwwang55/MKR/tree/master/data/movie

該數據集中一共有3個文件。

  • item_index2entity_id.txt:電影的ID與序號。具體內容如圖1所示,第1列是電影ID,第2列是序號。
  • kg.txt:電影的知識圖譜。圖2中顯示了知識圖譜的SPO三元組(Subject-Predicate-Object),第1列是電影ID,第2列是關係,第3列是目標實體。
  • ratings.dat:用戶的評分數據集。具體內容如圖3所示,列與列之間用“::”符號進行分割,第1列是用戶ID,第2列是電影ID,第3列是電影評分,第4列是評分時間(可以忽略)。

二、預處理數據

數據預處理主要是對原始數據集中的有用數據進行提取、轉化。該過程會生成兩個文件。

  • kg_final.txt:轉化後的知識圖譜文件。將文件kg.txt中的字符串類型數據轉成序列索引類型數據,如圖4所示。
  • ratings_final.txt:轉化後的用戶評分數據集。第1列將ratings.dat中的用戶ID變成序列索引。第2列沒有變化。第3列將ratings.dat中的評分按照閾值5進行轉化,如果評分大於等於5,則標註爲1,表明用戶對該電影感興趣。否則標註爲0,表明用戶對該電影不感興趣。具體內容如圖5所示。

三、搭建MKR模型

MKR模型由3個子模型組成,完整結構如圖6所示。具體描述如下。

  • 推薦算法模型:如圖6的左側部分所示,將用戶和電影作爲輸入,模型的預測結果爲用戶對該電影的喜好分數,數值爲0~1。
  • 交叉壓縮單元模型:如圖6的中間部分,在低層將左右兩個模型橋接起來。將電影評分數據集中的電影向量與知識圖譜中的電影向量特徵融合起來,再分別放回各自的模型中,進行監督訓練。
  • 知識圖譜詞嵌入(Knowledge Graph Embedding,KGE)模型:如圖6的右側部分,將知識圖譜三元組中的前2個(電影ID和關係實體)作爲輸入,預測出第3個(目標實體)。


圖6 MKR框架

在3個子模型中,最關鍵的是交叉壓縮單元模型。下面就先從該模型開始一步一步地實現MKR框架。

1. 交叉壓縮單元模型

交叉壓縮單元模型可以被當作一個網絡層疊加使用。如圖7所示的是交叉壓縮單元在第l層到第l+1層的結構。圖7中,最下面一行爲該單元的輸入,左側的v_l是用戶評論電影數據集中的電影向量,右側的e_l是知識圖譜中的電影向量。


圖7 交叉壓縮單元模型的結構

交叉壓縮單元模型的具體處理過程如下:

(1)將v_l與e_l進行矩陣相乘得到c_l。
(2)將c_l複製一份,並進行轉置得到c_l^T。實現特徵交叉融合。
(3)將c_l經過權重矩陣w_l^vv進行線性變化(c_l與w_l^vv矩陣相乘)。
(4)將c_l^T經過權重矩陣w_l^ev進行線性變化。
(5)將(3)與(4)的結果相加,再與偏置參數b_l^v相加,得到v_(l+1)。v_(l+1)將用於推薦算法模型的後續計算。
(6)按照第(3)、(4)、(5)步的做法,同理可以得到e_(l+1)。e_(l+1)將用於知識圖譜詞嵌入模型的後續計算。

用tf.layer接口實現交叉壓縮單元模型,具體代碼如下。

代碼7-14 MKR

01	import numpy as np
02	import tensorflow as tf
03	from sklearn.metrics import roc_auc_score
04	from tensorflow.python.layers import base
05	
06	class CrossCompressUnit(base.Layer):                  		#定義交叉壓縮單元模型類
07	    def __init__(self, dim, name=None):
08	       super(CrossCompressUnit, self).__init__(name)
09	       self.dim = dim
10	       self.f_vv = tf.layers.Dense(1, use_bias = False)	#構建權重矩陣
11	       self.f_ev = tf.layers.Dense(1, use_bias = False)
12	       self.f_ve = tf.layers.Dense(1, use_bias = False)
13	       self.f_ee = tf.layers.Dense(1, use_bias = False)
14	       self.bias_v = self.add_weight(name='bias_v', 		#構建偏置權重
15	                                           shape=dim, 
16	                                           initializer=tf.zeros_initializer())	       self.bias_e = self.add_weight(name='bias_e', 
17	                                           shape=dim, 
18	                                           initializer=tf.zeros_initializer())
19	
20	    def _call(self, inputs):
21	        v, e = inputs					#v和e的形狀爲[batch_size, dim]
22	        v = tf.expand_dims(v, dim=2)	#v的形狀爲 [batch_size, dim, 1]
23	        e = tf.expand_dims(e, dim=1)	#e的形狀爲 [batch_size, 1, dim]
24	
25	        c_matrix = tf.matmul(v, e)#c_matrix的形狀爲 [batch_size, dim, dim]
26	        c_matrix_transpose = tf.transpose(c_matrix, perm=[0, 2, 1])
27	        #c_matrix的形狀爲[batch_size * dim, dim]
28	        c_matrix = tf.reshape(c_matrix, [-1, self.dim])
29	        c_matrix_transpose = tf.reshape(c_matrix_transpose, [-1, self.dim])
30	
31	        #v_output的形狀爲[batch_size, dim]
32	        v_output = tf.reshape( 
33	                          self.f_vv(c_matrix) + self.f_ev(c_matrix_transpose),
34	                          [-1, self.dim]
35	                                 ) + self.bias_v
36	
37	        e_output = tf.reshape(
38	                          self.f_ve(c_matrix) + self.f_ee(c_matrix_transpose),
39	                          [-1, self.dim]
40	                                  ) + self.bias_e
41	    #返回結果
42	    return v_output, e_output

代碼第10行,用tf.layers.Dense方法定義了不帶偏置的全連接層,並在代碼第34行,將該全連接層作用於交叉後的特徵向量,實現壓縮的過程。

2. 將交叉壓縮單元模型集成到MKR框架中

在MKR框架中,推薦算法模型和知識圖譜詞嵌入模型的處理流程幾乎一樣。可以進行同步處理。在實現時,將整個處理過程橫向拆開,分爲低層和高層兩部分。

  • 低層:將所有的輸入映射成詞嵌入向量,將需要融合的向量(圖6中的v和h)輸入交叉壓縮單元,不需要融合的向量(圖6中的u和r)進行同步的全連接層處理。
  • 高層:推薦算法模型和知識圖譜詞嵌入模型分別將低層的傳上來的特徵連接在一起,通過全連接層迴歸到各自的目標結果。

具體實現的代碼如下。

代碼7-14 MKR(續)

43	class MKR(object):
44	    def __init__(self, args, n_users, n_items, n_entities, n_relations):
45	        self._parse_args(n_users, n_items, n_entities, n_relations)
46	        self._build_inputs()
47	        self._build_low_layers(args) 	#構建低層模型
48	        self._build_high_layers(args) 	#構建高層模型
49	        self._build_loss(args)
50	        self._build_train(args)
51	
52	    def _parse_args(self, n_users, n_items, n_entities, n_relations):
53	        self.n_user = n_users
54	        self.n_item = n_items
55	        self.n_entity = n_entities
56	        self.n_relation = n_relations
57	
58	        #收集訓練參數,用於計算l2損失
59	        self.vars_rs = []
60	        self.vars_kge = []
61	
62	    def _build_inputs(self):
63	        self.user_indices=tf.placeholder(tf.int32, [None], 'userInd')
64	        self.item_indices=tf.placeholder(tf.int32, [None],'itemInd')
65	        self.labels = tf.placeholder(tf.float32, [None], 'labels')
66	        self.head_indices =tf.placeholder(tf.int32, [None],'headInd')
67	        self.tail_indices =tf.placeholder(tf.int32, [None], 'tail_indices')
68	        self.relation_indices=tf.placeholder(tf.int32, [None], 'relInd')
69	    def _build_model(self, args): 
70	        self._build_low_layers(args)
71	        self._build_high_layers(args)
72	
73	    def _build_low_layers(self, args):
74	        #生成詞嵌入向量
75	        self.user_emb_matrix = tf.get_variable('user_emb_matrix',
76	                                                       [self.n_user, args.dim])
77	        self.item_emb_matrix = tf.get_variable('item_emb_matrix',
78	                                                       [self.n_item, args.dim])
79	        self.entity_emb_matrix = tf.get_variable('entity_emb_matrix',
80	                                                       [self.n_entity, args.dim])
81	        self.relation_emb_matrix = tf.get_variable('relation_emb_matrix',
82	                                                      [self.n_relation, args.dim])
83	
84	        #獲取指定輸入對應的詞嵌入向量,形狀爲[batch_size, dim]
85	        self.user_embeddings = tf.nn.embedding_lookup(
86	                                      self.user_emb_matrix, self.user_indices)
87	        self.item_embeddings = tf.nn.embedding_lookup(
88	                                      self.item_emb_matrix, self.item_indices)
89	        self.head_embeddings = tf.nn.embedding_lookup(
90	                                     self.entity_emb_matrix, self.head_indices)
91	        self.relation_embeddings = tf.nn.embedding_lookup(
92	                              self.relation_emb_matrix, self.relation_indices)
93	        self.tail_embeddings = tf.nn.embedding_lookup(
94	                                   self.entity_emb_matrix, self.tail_indices)
95	
96	        for _ in range(args.L):#按指定參數構建多層MKR結構
97	            #定義全連接層
98	            user_mlp = tf.layers.Dense(args.dim, activation=tf.nn.relu)
99	            tail_mlp = tf.layers.Dense(args.dim, activation=tf.nn.relu)
100	            cc_unit = CrossCompressUnit(args.dim)#定義CrossCompress單元
101	            #實現MKR結構的正向處理
102	            self.user_embeddings = user_mlp(self.user_embeddings)
103	            self.tail_embeddings = tail_mlp(self.tail_embeddings)
104	            self.item_embeddings, self.head_embeddings = cc_unit(
105	                               [self.item_embeddings, self.head_embeddings])
106	            #收集訓練參數
107	            self.vars_rs.extend(user_mlp.variables)
108	            self.vars_kge.extend(tail_mlp.variables) 
109	            self.vars_rs.extend(cc_unit.variables)
110	            self.vars_kge.extend(cc_unit.variables)
111	                        
112	    def _build_high_layers(self, args):
113	        #推薦算法模型
114	        use_inner_product = True 		#指定相似度分數計算的方式
115	        if use_inner_product:    		#內積方式
116	            #self.scores的形狀爲[batch_size]
117	            self.scores = tf.reduce_sum(self.user_embeddings * self.item_embeddings, axis=1)
118	        else:
119	            #self.user_item_concat的形狀爲[batch_size, dim * 2]
120	            self.user_item_concat = tf.concat(
121	                       [self.user_embeddings, self.item_embeddings], axis=1)
122	            for _ in range(args.H - 1):
123	                rs_mlp = tf.layers.Dense(args.dim * 2, activation=tf.nn.relu)
124	                #self.user_item_concat的形狀爲[batch_size, dim * 2]
125	                self.user_item_concat = rs_mlp(self.user_item_concat)
126	                self.vars_rs.extend(rs_mlp.variables)
127	            #定義全連接層
128	            rs_pred_mlp = tf.layers.Dense(1, activation=tf.nn.relu)
129	            #self.scores的形狀爲[batch_size]
130	            self.scores = tf.squeeze(rs_pred_mlp(self.user_item_concat))
131	            self.vars_rs.extend(rs_pred_mlp.variables)  #收集參數
132	        self.scores_normalized = tf.nn.sigmoid(self.scores)
133	
134	        #知識圖譜詞嵌入模型
135	        self.head_relation_concat = tf.concat(  #形狀爲[batch_size, dim * 2]
136	                  [self.head_embeddings, self.relation_embeddings], axis=1)
137	        for _ in range(args.H - 1):
138	            kge_mlp = tf.layers.Dense(args.dim * 2, activation=tf.nn.relu)
139	            #self.head_relation_concat的形狀爲[batch_size, dim* 2]
140	            self.head_relation_concat = kge_mlp(self.head_relation_concat)
141	            self.vars_kge.extend(kge_mlp.variables)
142	
143	        kge_pred_mlp = tf.layers.Dense(args.dim, activation=tf.nn.relu)
144	        #self.tail_pred的形狀爲[batch_size, args.dim]
145	        self.tail_pred = kge_pred_mlp(self.head_relation_concat)
146	        self.vars_kge.extend(kge_pred_mlp.variables)
147	        self.tail_pred = tf.nn.sigmoid(self.tail_pred)
148	
149	        self.scores_kge = tf.nn.sigmoid(tf.reduce_sum(self.tail_embeddings * self.tail_pred, axis=1))
150	        self.rmse = tf.reduce_mean(
151	            tf.sqrt(tf.reduce_sum(tf.square(self.tail_embeddings - self.tail_pred), axis=1) / args.dim))

代碼第73~90行(書中第115~132行)是推薦算法模型的高層處理部分,該部分有兩種處理方式:

  • 使用內積的方式,計算用戶向量和電影向量的相似度。有關相似度的更多知識,可以參考8.1.10小節的注意力機制。
  • 將用戶向量和電影向量連接起來,再通過全連接層處理計算出用戶對電影的喜好分值。

代碼第90行(書中第132行),通過激活函數sigmoid對分值結果scores進行非線性變化,將模型的最終結果映射到標籤的值域中。

代碼第94~110行(書中第136~152行)是知識圖譜詞嵌入模型的高層處理部分。具體步驟如下:

(1)將電影向量和知識圖譜中的關係向量連接起來。

(2)將第(1)步的結果通過全連接層處理,得到知識圖譜三元組中的目標實體向量。

(3)將生成的目標實體向量與真實的目標實體向量矩陣相乘,得到相似度分值。

(4)對第(3)步的結果進行激活函數sigmoid計算,將值域映射到0~1中。

3. 實現MKR框架的反向結構

MKR框架的反向結構主要是loss值的計算,其loss值一共分爲3部分:推薦算法模型模型的loss值、知識圖譜詞嵌入模型的loss值和參數權重的正則項。具體實現的代碼如下。

代碼7-14 MKR(續)
152 def _build_loss(self, args):
153 #計算推薦算法模型的loss值
154 self.base_loss_rs = tf.reduce_mean(
155 tf.nn.sigmoid_cross_entropy_with_logits(labels=self.labels, logits=self.scores))
156 self.l2_loss_rs = tf.nn.l2_loss(self.user_embeddings) + tf.nn.l2_loss (self.item_embeddings)
157 for var in self.vars_rs:
158 self.l2_loss_rs += tf.nn.l2_loss(var)
159 self.loss_rs = self.base_loss_rs + self.l2_loss_rs * args.l2_weight
160
161 #計算知識圖譜詞嵌入模型的loss值
162 self.base_loss_kge = -self.scores_kge
163 self.l2_loss_kge = tf.nn.l2_loss(self.head_embeddings) + tf.nn.l2_loss (self.tail_embeddings)
164 for var in self.vars_kge: #計算L2正則
165 self.l2_loss_kge += tf.nn.l2_loss(var)
166 self.loss_kge = self.base_loss_kge + self.l2_loss_kge * args.l2_weight
167
168 def _build_train(self, args): #定義優化器
169 self.optimizer_rs = tf.train.AdamOptimizer(args.lr_rs).minimize(self.loss_rs)
170 self.optimizer_kge = tf.train.AdamOptimizer(args.lr_kge). minimize(self. loss_kge)
171
172 def train_rs(self, sess, feed_dict): #訓練推薦算法模型
173 return sess.run([self.optimizer_rs, self.loss_rs], feed_dict)
174
175 def train_kge(self, sess, feed_dict): #訓練知識圖譜詞嵌入模型
176 return sess.run([self.optimizer_kge, self.rmse], feed_dict)
177
178 def eval(self, sess, feed_dict): #評估模型
179 labels, scores = sess.run([self.labels, self.scores_normalized], feed_dict)
180 auc = roc_auc_score(y_true=labels, y_score=scores)
181 predictions = [1 if i >= 0.5 else 0 for i in scores]
182 acc = np.mean(np.equal(predictions, labels))
183 return auc, acc
184
185 def get_scores(self, sess, feed_dict):
186 return sess.run([self.item_indices, self.scores_normalized], feed_dict)
代碼第173、176行, 分別是訓練推薦算法模型和訓練知識圖譜詞嵌入模型的方法。因爲在訓練的過程中,兩個子模型需要交替的進行獨立訓練,所以將其分開定義。
四、訓練模型並輸出結果
訓練模型的代碼在本書配套的“7-15 train.py”文件中,讀者可以自行參考。代碼運行後輸出以下結果:
……
epoch 9 train auc: 0.9540 acc: 0.8817 eval auc: 0.9158 acc: 0.8407 test auc: 0.9155 acc: 0.8399
在輸出的結果中,分別顯示了模型在訓練、評估、測試環境下的分值。

本文摘選自電子工業出版社出版、李金洪編著的《深度學習之TensorFlow工程化項目實戰》一書,更多實戰內容點此查看。

本文經授權發佈,轉載請聯繫電子工業出版社。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章