推薦系統遇上深度學習(二十八)--知識圖譜與推薦系統結合之MKR模型原理及實現

知識圖譜特徵學習在推薦系統中的應用步驟大致有以下三種方式:

依次訓練的方法主要有:Deep Knowledge-aware Network(DKN) 聯合訓練的方法主要有:Ripple Network 交替訓練主要採用multi-task的思路,主要方法有:Multi-task Learning for KG enhanced Recommendation (MKR)

本文先來介紹交替訓練的方法MKR。

網上沒有找到相關的論文,只有在一篇帖子裏有所介紹,github上可以找到源代碼進行學習。

1、MKR原理介紹

由於推薦系統中的物品和知識圖譜中的實體存在重合,因此可以採用多任務學習的框架,將推薦系統和知識圖譜特徵學習視爲兩個分離但是相關的任務,進行交替式的學習。

MKR的模型框架如下圖,其中左側是推薦系統任務,右側是知識圖譜特徵學習任務。推薦部分的輸入是用戶和物品的特徵表示,點擊率的預估值作爲輸出。知識圖譜特徵學習部分使用的是三元組的頭節點和關係作爲輸入,預測的尾節點作爲輸出:

由於推薦系統中的物品和知識圖譜中的實體存在重合,所以兩個任務並非相互獨立。所以作者在兩個任務中設計了交叉特徵共享單元(cross-feature-sharing units)作爲兩者的連接紐帶。

交叉特徵共享單元是一個可以讓兩個任務交換信息的模塊。由於物品向量和實體向量實際上是對同一個對象的兩種描述,他們之間的信息交叉共享可以讓兩者都獲得來自對方的額外信息,從而彌補了自身的信息稀疏性的不足,其結構如下:

關於這個交叉單元具體實現,大家可以參照代碼進行理解。

最後是損失函數部分,由於是交替訓練的方式,所以在訓練時首先固定推薦系統模塊的參數,訓練知識圖譜特徵學習模塊的參數;然後固定知識圖譜特徵學習模塊的參數,訓練推薦系統模塊的參數。

推薦系統模塊是點擊率預估模型,損失函數是對數損失加l2正則項;知識圖譜特徵學習模塊希望預測得到的tail向量和真實的tail向量相近,因此首先計算二者的內積(內積可近似表示向量之間的餘弦相似度),內積經過sigmoid之後取相反數,再加上l2正則項,即得到了知識圖譜特徵學習模塊的損失。關於損失的計算,我們在代碼裏可以更清楚的看到。

2、MKR模型tensorflow實現

本文的代碼地址爲:https://github.com/princewen/tensorflow_practice/tree/master/recommendation/Basic-MKR-Demo 參考代碼地址爲:https://github.com/hwwang55/MKR 數據下載地址爲:https://pan.baidu.com/s/1uHkQXK_ozAgBWcMUMzOfZQ 密碼:qw30

在對數據進行預處理後,我們得到了兩個文件:kg_final.txt和rating_final.txt

rating_final.txt數據形式如下,三列分別是user-id,item-id以及label(0是通過負採樣得到的,正負樣本比例爲1:1)。

kg_final.txt格式如下,三類分別代表h,r,t(這裏entity和item用的是同一套id):

好了,接下來我們重點介紹一下我們的MKR框架的構建。

模型輸入

模型輸入有以下幾部分:用戶的id、物品的id、推薦系統部分的label、知識圖譜三元組的head、relation、tail的對應id:

def _build_inputs(self):
    self.user_indices = tf.placeholder(tf.int32,[None],'user_indices')
    self.item_indices = tf.placeholder(tf.int32,[None],'item_indices')
    self.labels = tf.placeholder(tf.float32,[None],'labels')
    self.head_indices = tf.placeholder(tf.int32,[None],'head_indices')
    self.tail_indices = tf.placeholder(tf.int32,[None],'tail_indices')
    self.relation_indices = tf.placeholder(tf.int32,[None],'relation_indices')

低層網絡構建

低層網絡指下面的部分:

可以看到,user_id、item_id、head_id以及relation_id首先轉換爲對應的embedding,user_id和relation_id經由多層神經網絡向上傳播、而head_id和item_id經過交叉單元進行傳播。

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):
        user_mlp = Dense(input_dim=args.dim,output_dim=args.dim)
        tail_mlp = Dense(input_dim=args.dim,output_dim = args.dim)
        cc_unit = CrossCompressUnit(args.dim)

        self.user_embeddings = user_mlp(self.user_embeddings)
        self.item_embeddings,self.head_embeddings = cc_unit([self.item_embeddings,self.head_embeddings])
        self.tail_embeddings = tail_mlp(self.tail_embeddings)

        self.vars_rs.extend(user_mlp.vars)
        self.vars_rs.extend(cc_unit.vars)
        self.vars_kge.extend(tail_mlp.vars)
        self.vars_kge.extend(cc_unit.vars)

接下來,我們來看一下交叉單元的代碼:

v,e = inputs

v = tf.expand_dims(v,dim=2)
e = tf.expand_dims(e,dim=1)


# [batch_size, dim, dim]
c_matrix = tf.matmul(v, e)
c_matrix_transpose = tf.transpose(c_matrix, perm=[0, 2, 1])

# [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 = tf.reshape(tf.matmul(c_matrix,self.weight_vv) + tf.matmul(c_matrix_transpose,self.weight_ev),[-1,self.dim]) + self.bias_v

e_output = tf.reshape(tf.matmul(c_matrix, self.weight_ve) + tf.matmul(c_matrix_transpose, self.weight_ee),
                      [-1, self.dim]) + self.bias_e

return v_output,e_output

item對應的embedding用v表示,head對應的embedding用e表示,二者初始情況下都是batch * dim大小的。過程如下: 1、v擴展成三維batch * dim * 1,e擴展成三維batch * 1 * dim,隨後二者進行矩陣相乘v * e,我們知道三維矩陣相乘實際上是後兩維進行運算,因此得到c_matrix的大小爲 batch * dim * dim 2、對得到的c_matrix進行轉置,得到c_matrix_transpose,大小爲batch * dim * dim。這相當於將e擴展成三維batch * dim * 1,v擴展成三維batch * 1 * dim,隨後二者進行矩陣相乘e * v。這是兩種不同的特徵交叉方式。 3、對c_matrix和c_matrix_transpose 進行reshape操作,變爲(batch * dim ) * dim的二維矩陣 4、定義兩組不同的參數和偏置,分別得到交叉後的v_output和e_output.

高層網絡構建

高層網絡指下面的部分:

對於推薦部分,可以採用內積直接得到CTR的預估值,也可以經過多層神經網絡得到預估值;對於知識圖譜部分,將head和relation對應的向量進行拼接,經過多層神經網絡,得到一個tail對應向量的預估值,並與真實的tail向量計算內積。代碼如下:

def _build_high_layers(self,args):
    #RS
    use_inner_product = True
    if use_inner_product:
        self.scores = tf.reduce_sum(self.user_embeddings*self.item_embeddings,axis=1)
    else:
        self.user_item_concat = tf.concat([self.user_embeddings,self.item_embeddings],axis=1)
        for _ in range(args.H - 1):
            rs_mlp = Dense(input_dim = args.dim * 2 , output_dim = args.dim * 2)
            self.user_item_concat = rs_mlp(self.user_item_concat)
            self.vars_rs.extend(rs_mlp.vars)

        rs_pred_mlp = Dense(input_dim=args.dim * 2,output_dim=1)
        self.scores = tf.squeeze(rs_pred_mlp(self.user_item_concat))
        self.vars_rs.extend(rs_pred_mlp)

    self.scores_normalized = tf.nn.sigmoid(self.scores)

    #KGE
    self.head_relation_concat = tf.concat([self.head_embeddings,self.relation_embeddings],axis=1)
    for _ in range(args.H - 1):
        kge_mlp = Dense(input_dim=args.dim * 2,output_dim = args.dim * 2)
        self.head_relation_concat = kge_mlp(self.head_relation_concat)
        self.vars_kge.extend(kge_mlp.vars)

    kge_pred_mlp = Dense(input_dim=args.dim * 2,output_dim = args.dim)
    self.tail_pred = kge_pred_mlp(self.head_relation_concat)
    self.vars_kge.extend(kge_pred_mlp.vars)
    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))

定義損失

推薦系統部分的損失是對數損失加l2正則項:

# RS
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

知識圖譜特徵學習模塊用上一步計算的scores_kge的相反數再加上l2正則項:

# KGE
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:
    self.l2_loss_kge += tf.nn.l2_loss(var)
self.loss_kge = self.base_loss_kge + self.l2_loss_kge * args.l2_weight

參考文獻

1、http://baijiahao.baidu.com/s?id=1602210213239784098&wfr=spider&for=pc

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