知識圖譜(Knowledge Graph,KG)可以理解成一個知識庫,用來存儲實體與實體之間的關系。知識圖譜可以為機器學習算法提供更多的信息,幫助模型更好地完成任務。
在推薦算法中融入電影的知識圖譜,能夠將沒有任何歷史數據的新電影精准地推薦給目標用戶。
實例描述
現有一個電影評分數據集和一個電影相關的知識圖譜。電影評分數據集里包含用戶、電影及評分;電影相關的知識圖譜中包含電影的類型、導演等屬性。
要求:從知識圖譜中找出電影間的潛在特征,並借助該特征及電影評分數據集,實現基於電影的推薦系統。
本實例使用了一個多任務學習的端到端框架MKR。該框架能夠將兩個不同任務的低層特征抽取出來,並融合在一起實現聯合訓練,從而達到最優的結果。有關MKR的更多介紹可以參考以下鏈接:
https://arxiv.org/pdf/1901.08907.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中,最下面一行為該單元的輸入,左側的是用戶評論電影數據集中的電影向量,右側的是知識圖譜中的電影向量。

圖7 交叉壓縮單元模型的結構
交叉壓縮單元模型的具體處理過程如下:
(1)將與進行矩陣相乘得到。
(2)將復制一份,並進行轉置得到。實現特征交叉融合。
(3)將經過權重矩陣進行線性變化(與矩陣相乘)。
(4)將經過權重矩陣進行線性變化。
(5)將(3)與(4)的結果相加,再與偏置參數相加,得到。將用於推薦算法模型的后續計算。
(6)按照第(3)、(4)、(5)步的做法,同理可以得到。將用於知識圖譜詞嵌入模型的后續計算。
用tf.layer接口實現交叉壓縮單元模型,具體代碼如下。
代碼:
import numpy as np import tensorflow as tf from sklearn.metrics import roc_auc_score from tensorflow.python.layers import base class CrossCompressUnit(base.Layer): #定義交叉壓縮單元模型類 def __init__(self, dim, name=None): super(CrossCompressUnit, self).__init__(name) self.dim = dim self.f_vv = tf.layers.Dense(1, use_bias = False) #構建權重矩陣 self.f_ev = tf.layers.Dense(1, use_bias = False) self.f_ve = tf.layers.Dense(1, use_bias = False) self.f_ee = tf.layers.Dense(1, use_bias = False) self.bias_v = self.add_weight(name='bias_v', #構建偏置權重 shape=dim, initializer=tf.zeros_initializer()) self.bias_e = self.add_weight(name='bias_e', shape=dim, initializer=tf.zeros_initializer()) def _call(self, inputs): v, e = inputs #v和e的形狀為[batch_size, dim] v = tf.expand_dims(v, dim=2) #v的形狀為 [batch_size, dim, 1] e = tf.expand_dims(e, dim=1) #e的形狀為 [batch_size, 1, dim] c_matrix = tf.matmul(v, e)#c_matrix的形狀為 [batch_size, dim, dim] c_matrix_transpose = tf.transpose(c_matrix, perm=[0, 2, 1]) #c_matrix的形狀為[batch_size * dim, dim] c_matrix = tf.reshape(c_matrix, [-1, self.dim]) c_matrix_transpose = tf.reshape(c_matrix_transpose, [-1, self.dim]) #v_output的形狀為[batch_size, dim] v_output = tf.reshape( self.f_vv(c_matrix) + self.f_ev(c_matrix_transpose), [-1, self.dim] ) + self.bias_v e_output = tf.reshape( self.f_ve(c_matrix) + self.f_ee(c_matrix_transpose), [-1, self.dim] ) + self.bias_e #返回結果 return v_output, e_output
代碼第10行,用tf.layers.Dense方法定義了不帶偏置的全連接層,並在代碼第34行,將該全連接層作用於交叉后的特征向量,實現壓縮的過程。
2.將交叉壓縮單元模型集成到MKR框架中
在MKR框架中,推薦算法模型和知識圖譜詞嵌入模型的處理流程幾乎一樣。可以進行同步處理。在實現時,將整個處理過程橫向拆開,分為低層和高層兩部分。
—低層:將所有的輸入映射成詞嵌入向量,將需要融合的向量(圖6中的v和h)輸入交叉壓縮單元,不需要融合的向量(圖6中的u和r)進行同步的全連接層處理。
—高層:推薦算法模型和知識圖譜詞嵌入模型分別將低層的傳上來的特征連接在一起,通過全連接層回歸到各自的目標結果。
具體實現的代碼如下。
代碼7-14 MKR(續)
class MKR(object): def __init__(self, args, n_users, n_items, n_entities, n_relations): self._parse_args(n_users, n_items, n_entities, n_relations) self._build_inputs() self._build_low_layers(args) #構建低層模型 self._build_high_layers(args) #構建高層模型 self._build_loss(args) self._build_train(args) def _parse_args(self, n_users, n_items, n_entities, n_relations): self.n_user = n_users self.n_item = n_items self.n_entity = n_entities self.n_relation = n_relations #收集訓練參數,用於計算l2損失 self.vars_rs = [] self.vars_kge = [] def _build_inputs(self): self.user_indices=tf.placeholder(tf.int32, [None], 'userInd') self.item_indices=tf.placeholder(tf.int32, [None],'itemInd') self.labels = tf.placeholder(tf.float32, [None], 'labels') self.head_indices =tf.placeholder(tf.int32, [None],'headInd') self.tail_indices =tf.placeholder(tf.int32, [None], 'tail_indices') self.relation_indices=tf.placeholder(tf.int32, [None], 'relInd') def _build_model(self, args): self._build_low_layers(args) self._build_high_layers(args) def _build_low_layers(self, args): #生成詞嵌入向量 self.user_emb_matrix = tf.get_variable('user_emb_matrix', [self.n_user, args.dim]) self.item_emb_matrix = tf.get_variable('item_emb_matrix', [self.n_item, args.dim]) self.entity_emb_matrix = tf.get_variable('entity_emb_matrix', [self.n_entity, args.dim]) self.relation_emb_matrix = tf.get_variable('relation_emb_matrix', [self.n_relation, args.dim]) #獲取指定輸入對應的詞嵌入向量,形狀為[batch_size, dim] self.user_embeddings = tf.nn.embedding_lookup( self.user_emb_matrix, self.user_indices) self.item_embeddings = tf.nn.embedding_lookup( self.item_emb_matrix, self.item_indices) self.head_embeddings = tf.nn.embedding_lookup( self.entity_emb_matrix, self.head_indices) self.relation_embeddings = tf.nn.embedding_lookup( self.relation_emb_matrix, self.relation_indices) self.tail_embeddings = tf.nn.embedding_lookup( self.entity_emb_matrix, self.tail_indices) for _ in range(args.L):#按指定參數構建多層MKR結構 #定義全連接層 user_mlp = tf.layers.Dense(args.dim, activation=tf.nn.relu) tail_mlp = tf.layers.Dense(args.dim, activation=tf.nn.relu) cc_unit = CrossCompressUnit(args.dim)#定義CrossCompress單元 #實現MKR結構的正向處理 self.user_embeddings = user_mlp(self.user_embeddings) self.tail_embeddings = tail_mlp(self.tail_embeddings) self.item_embeddings, self.head_embeddings = cc_unit( [self.item_embeddings, self.head_embeddings]) #收集訓練參數 self.vars_rs.extend(user_mlp.variables) self.vars_kge.extend(tail_mlp.variables) self.vars_rs.extend(cc_unit.variables) self.vars_kge.extend(cc_unit.variables) def _build_high_layers(self, args): #推薦算法模型 use_inner_product = True #指定相似度分數計算的方式 if use_inner_product: #內積方式 #self.scores的形狀為[batch_size] self.scores = tf.reduce_sum(self.user_embeddings * self.item_embeddings, axis=1) else: #self.user_item_concat的形狀為[batch_size, dim * 2] self.user_item_concat = tf.concat( [self.user_embeddings, self.item_embeddings], axis=1) for _ in range(args.H - 1): rs_mlp = tf.layers.Dense(args.dim * 2, activation=tf.nn.relu) #self.user_item_concat的形狀為[batch_size, dim * 2] self.user_item_concat = rs_mlp(self.user_item_concat) self.vars_rs.extend(rs_mlp.variables) #定義全連接層 rs_pred_mlp = tf.layers.Dense(1, activation=tf.nn.relu) #self.scores的形狀為[batch_size] self.scores = tf.squeeze(rs_pred_mlp(self.user_item_concat)) self.vars_rs.extend(rs_pred_mlp.variables) #收集參數 self.scores_normalized = tf.nn.sigmoid(self.scores) #知識圖譜詞嵌入模型 self.head_relation_concat = tf.concat( #形狀為[batch_size, dim * 2] [self.head_embeddings, self.relation_embeddings], axis=1) for _ in range(args.H - 1): kge_mlp = tf.layers.Dense(args.dim * 2, activation=tf.nn.relu) #self.head_relation_concat的形狀為[batch_size, dim* 2] self.head_relation_concat = kge_mlp(self.head_relation_concat) self.vars_kge.extend(kge_mlp.variables) kge_pred_mlp = tf.layers.Dense(args.dim, activation=tf.nn.relu) #self.tail_pred的形狀為[batch_size, args.dim] self.tail_pred = kge_pred_mlp(self.head_relation_concat) self.vars_kge.extend(kge_pred_mlp.variables) self.tail_pred = tf.nn.sigmoid(self.tail_pred) self.scores_kge = tf.nn.sigmoid(tf.reduce_sum(self.tail_embeddings * self.tail_pred, axis=1)) self.rmse = tf.reduce_mean( tf.sqrt(tf.reduce_sum(tf.square(self.tail_embeddings - self.tail_pred), axis=1) / args.dim))
代碼第115~132行是推薦算法模型的高層處理部分,該部分有兩種處理方式:
- 使用內積的方式,計算用戶向量和電影向量的相似度。有關相似度的更多知識,可以參考8.1.10小節的注意力機制。
- 將用戶向量和電影向量連接起來,再通過全連接層處理計算出用戶對電影的喜好分值。
代碼第132行,通過激活函數sigmoid對分值結果scores進行非線性變化,將模型的最終結果映射到標簽的值域中。
代碼第136~152行是知識圖譜詞嵌入模型的高層處理部分。具體步驟如下:
(1)將電影向量和知識圖譜中的關系向量連接起來。
(2)將第(1)步的結果通過全連接層處理,得到知識圖譜三元組中的目標實體向量。
(3)將生成的目標實體向量與真實的目標實體向量矩陣相乘,得到相似度分值。
(4)對第(3)步的結果進行激活函數sigmoid計算,將值域映射到0~1中。
3.實現MKR框架的反向結構
MKR框架的反向結構主要是loss值的計算,其loss值一共分為3部分:推薦算法模型模型的loss值、知識圖譜詞嵌入模型的loss值和參數權重的正則項。具體實現的代碼如下。
代碼7-14 MKR(續)
def _build_loss(self, args): #計算推薦算法模型的loss值 self.base_loss_rs = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(labels=self.labels, logits=self.scores)) self.l2_loss_rs = tf.nn.l2_loss(self.user_embeddings) + tf.nn.l2_loss (self.item_embeddings) for var in self.vars_rs: self.l2_loss_rs += tf.nn.l2_loss(var) self.loss_rs = self.base_loss_rs + self.l2_loss_rs * args.l2_weight #計算知識圖譜詞嵌入模型的loss值 self.base_loss_kge = -self.scores_kge self.l2_loss_kge = tf.nn.l2_loss(self.head_embeddings) + tf.nn.l2_loss (self.tail_embeddings) for var in self.vars_kge: #計算L2正則 self.l2_loss_kge += tf.nn.l2_loss(var) self.loss_kge = self.base_loss_kge + self.l2_loss_kge * args.l2_weight def _build_train(self, args): #定義優化器 self.optimizer_rs = tf.train.AdamOptimizer(args.lr_rs).minimize(self.loss_rs) self.optimizer_kge = tf.train.AdamOptimizer(args.lr_kge). minimize(self. loss_kge) def train_rs(self, sess, feed_dict): #訓練推薦算法模型 return sess.run([self.optimizer_rs, self.loss_rs], feed_dict) def train_kge(self, sess, feed_dict): #訓練知識圖譜詞嵌入模型 return sess.run([self.optimizer_kge, self.rmse], feed_dict) def eval(self, sess, feed_dict): #評估模型 labels, scores = sess.run([self.labels, self.scores_normalized], feed_dict) auc = roc_auc_score(y_true=labels, y_score=scores) predictions = [1 if i >= 0.5 else 0 for i in scores] acc = np.mean(np.equal(predictions, labels)) return auc, acc def get_scores(self, sess, feed_dict): 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
在輸出的結果中,分別顯示了模型在訓練、評估、測試環境下的分值。
轉載:
https://cloud.tencent.com/developer/article/1492199