推薦系統(tǒng)遇上深度學(xué)習(xí)(二十七)--知識(shí)圖譜與推薦系統(tǒng)結(jié)合之RippleNet模型原理及實(shí)現(xiàn)

知識(shí)圖譜特征學(xué)習(xí)在推薦系統(tǒng)中的應(yīng)用步驟大致有以下三種方式:

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

本文先來(lái)介紹聯(lián)合訓(xùn)練的方法Ripple Network。

論文下載地址為:https://arxiv.org/abs/1803.03467

1、RippleNet原理

1.1 RippleNet背景

在上一篇中我們介紹了Deep Knowledge-aware Network(DKN),在DKN中,我們需要首先學(xué)習(xí)到entity的向量和relation的向量,但是學(xué)習(xí)到的向量,其目的是為了還原知識(shí)圖譜中的三元組關(guān)系,而并非是為了我們的推薦任務(wù)而學(xué)習(xí)的。因此今天我們來(lái)介紹一下知識(shí)圖譜和推薦系統(tǒng)進(jìn)行聯(lián)合訓(xùn)練的一種網(wǎng)絡(luò)結(jié)構(gòu):RippleNet。

Ripple是波紋的意思,RippleNet就是模擬用戶(hù)興趣在知識(shí)圖譜上的一個(gè)傳播過(guò)程,如下圖所示:

如上圖,用戶(hù)的興趣以其歷史記錄為中心,在知識(shí)圖譜上逐層向外擴(kuò)散,而在擴(kuò)散過(guò)程中不斷的衰減,類(lèi)似于水中的波紋,因此稱(chēng)為RippleNet。

1.2 RippleNet網(wǎng)絡(luò)結(jié)構(gòu)

我們先來(lái)介紹兩個(gè)相關(guān)的定義:
Relevant Entity:在給定知識(shí)圖譜的情況下,用戶(hù)u的k-hop相關(guān)實(shí)體定義如下:

特別地,用戶(hù)u的0-hop相關(guān)實(shí)體即用戶(hù)的歷史記錄。

Ripple Set:用戶(hù)u的k-hop ripple set被定義為以k-1 Relevant Entity 為head的相關(guān)三元組:

這里,為避免Ripple Set過(guò)大,一般都會(huì)設(shè)定一個(gè)最大的長(zhǎng)度,進(jìn)行截?cái)?。另一方面,?gòu)建的知識(shí)圖譜都是有向圖,只考慮點(diǎn)的出度。

接下來(lái),我們來(lái)看看RippleNet的網(wǎng)絡(luò)結(jié)構(gòu):

可以看到,最終的預(yù)測(cè)值是通過(guò)item embedding和user embedding得到的,item embedding通過(guò)embedding 層可以直接得到,關(guān)鍵是user embedding的獲取。user embedding是通過(guò)圖中的綠色矩形表示的向量相加得到的,接下來(lái),我們以第一個(gè)綠色矩形表示的向量為例,來(lái)看一下具體是如何計(jì)算的。

第一個(gè)綠色矩形表示的向量,需要使用的是1-hop的ripple set,對(duì)于set中的每一個(gè)(h,r,t),會(huì)計(jì)算一個(gè)與item-embedding的相關(guān)性,相關(guān)性計(jì)算公式如下:

最后通過(guò)加權(quán)所有t對(duì)應(yīng)的embedding,就得到了第一個(gè)綠色矩形表示的向量,表示用戶(hù)興趣經(jīng)第一輪擴(kuò)散后的結(jié)果:

接下來(lái),我們重復(fù)上面的過(guò)程,假設(shè)一共H次,那么最終user embedding的結(jié)果為:

而最終的預(yù)測(cè)值計(jì)算如下:

1.3 RippleNet損失函數(shù)

在給定知識(shí)圖譜G,用戶(hù)的隱式反饋(即用戶(hù)的歷史記錄)Y時(shí),我們希望最大化后驗(yàn)概率:

后驗(yàn)概率展開(kāi)如下:

其中,我們認(rèn)為參數(shù)的先驗(yàn)概率服從0均值的正態(tài)分布:

第二項(xiàng)的似然函數(shù)形式如下:

上面的式子搞得我有點(diǎn)懵,后面應(yīng)該是一個(gè)具體的概率值而不是一個(gè)正態(tài)分布,G在θ條件下的分布也是一個(gè)0均值的正態(tài)分布,后面應(yīng)該是取得Ih,r,t-hTRt的一個(gè)概率,由于我們希望我們得到的指數(shù)圖譜特征表示能夠更好的還原三元組關(guān)系,因此希望Ih,r,t-hTRt越接近0越好。

第三項(xiàng)沒(méi)什么問(wèn)題,即我們常用的二分類(lèi)似然函數(shù):

因此,我們可以得到RippleNet的損失函數(shù)形式如下:

2、RippleNet的Tensorflow實(shí)現(xiàn)

本文的代碼地址如下:https://github.com/princewen/tensorflow_practice/tree/master/recommendation/Basic-RippleNet-Demo

參考的代碼地址為:https://github.com/hwwang55/RippleNet

數(shù)據(jù)下載地址為::https://pan.baidu.com/s/13vL-z5Wk3jQFfmVIPXDovw 密碼:infx

在對(duì)數(shù)據(jù)進(jìn)行預(yù)處理后,我們得到了兩個(gè)文件:kg_final.txt和rating_final.txt

rating_final.txt數(shù)據(jù)形式如下,三列分別是user-id,item-id以及l(fā)abel(0是通過(guò)負(fù)采樣得到的,正負(fù)樣本比例為1:1)。

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

好了,接下來(lái)我們重點(diǎn)介紹一下我們的RippleNet網(wǎng)絡(luò)的構(gòu)建。

網(wǎng)絡(luò)輸入

網(wǎng)絡(luò)輸入主要有item的id,label以及對(duì)應(yīng)的用戶(hù)的ripple set:

def _build_inputs(self):
    self.items = tf.placeholder(dtype=tf.int32, shape=[None], name="items")
    self.labels = tf.placeholder(dtype=tf.float64, shape=[None], name="labels")
    self.memories_h = []
    self.memories_r = []
    self.memories_t = []

    for hop in range(self.n_hop):
        self.memories_h.append(
            tf.placeholder(dtype=tf.int32, shape=[None, self.n_memory], name="memories_h_" + str(hop)))
        self.memories_r.append(
            tf.placeholder(dtype=tf.int32, shape=[None, self.n_memory], name="memories_r_" + str(hop)))
        self.memories_t.append(
            tf.placeholder(dtype=tf.int32, shape=[None, self.n_memory], name="memories_t_" + str(hop)))

embedding層構(gòu)建

這里需要的embedding主要有entity的embedding(與item 的embedding共用)和relation的embedding,假設(shè)embedding的長(zhǎng)度為dim,那么注意到由于relation是要用來(lái)鏈接head和tail的,所以它的embedding的維度為dim * dim:

def _build_embeddings(self):
    self.entity_emb_matrix = tf.get_variable(name="entity_emb_matrix", dtype=tf.float64,
                                             shape=[self.n_entity, self.dim],
                                             initializer=tf.contrib.layers.xavier_initializer())
    self.relation_emb_matrix = tf.get_variable(name="relation_emb_matrix", dtype=tf.float64,
                                               shape=[self.n_relation, self.dim, self.dim],
                                               initializer=tf.contrib.layers.xavier_initializer())

模型構(gòu)建

模型構(gòu)建的代碼如下,可以看到我們建立了一個(gè)transform_matrix的tensor,這個(gè)tensor就是用來(lái)更新計(jì)算過(guò)程中的item-embedding的,我們后面會(huì)詳細(xì)介紹:

def _build_model(self):
    # transformation matrix for updating item embeddings at the end of each hop
    self.transform_matrix = tf.get_variable(name="transform_matrix", shape=[self.dim, self.dim], dtype=tf.float64,
                                            initializer=tf.contrib.layers.xavier_initializer())

    # [batch size, dim]
    self.item_embeddings = tf.nn.embedding_lookup(self.entity_emb_matrix, self.items)

    self.h_emb_list = []
    self.r_emb_list = []
    self.t_emb_list = []
    for i in range(self.n_hop):
        # [batch size, n_memory, dim]
        self.h_emb_list.append(tf.nn.embedding_lookup(self.entity_emb_matrix, self.memories_h[i]))

        # [batch size, n_memory, dim, dim]
        self.r_emb_list.append(tf.nn.embedding_lookup(self.relation_emb_matrix, self.memories_r[i]))

        # [batch size, n_memory, dim]
        self.t_emb_list.append(tf.nn.embedding_lookup(self.entity_emb_matrix, self.memories_t[i]))

    o_list = self._key_addressing()

    self.scores = tf.squeeze(self.predict(self.item_embeddings, o_list))
    self.scores_normalized = tf.sigmoid(self.scores)

上面用到了兩個(gè)函數(shù),分別是_key_addressing()和predict(),接下來(lái),我們來(lái)介紹這兩個(gè)函數(shù)。

_key_addressing()是用來(lái)的到我們的olist的,即我們?cè)赗ippleNet中的綠色矩形表示的向量:

def _key_addressing(self):
    o_list = []
    for hop in range(self.n_hop):
        # [batch_size, n_memory, dim, 1]
        h_expanded = tf.expand_dims(self.h_emb_list[hop], axis=3)
        # [batch_size, n_memory, dim]
        Rh = tf.squeeze(tf.matmul(self.r_emb_list[hop], h_expanded), axis=3)
        # [batch_size, dim, 1]
        v = tf.expand_dims(self.item_embeddings, axis=2)
        # [batch_size, n_memory]
        probs = tf.squeeze(tf.matmul(Rh, v), axis=2)
        # [batch_size, n_memory]
        probs_normalized = tf.nn.softmax(probs)
        # [batch_size, n_memory, 1]
        probs_expanded = tf.expand_dims(probs_normalized, axis=2)
        # [batch_size, dim]
        o = tf.reduce_sum(self.t_emb_list[hop] * probs_expanded, axis=1)

        self.item_embeddings = self.update_item_embedding(self.item_embeddings, o)
        o_list.append(o)
    return o_list

可以看到,在上面的代碼中,我們計(jì)算的是ripple set中每一個(gè)(h,r,t)和item-embedding的相關(guān)性,再每一個(gè)hop計(jì)算完成后,有一個(gè)update_item_embedding的操作,在這里面,我們可以選擇不同的替換策略:

def update_item_embedding(self, item_embeddings, o):
    if self.item_update_mode == "replace":
        item_embeddings = o
    elif self.item_update_mode == "plus":
        item_embeddings = item_embeddings + o
    elif self.item_update_mode == "replace_transform":
        item_embeddings = tf.matmul(o, self.transform_matrix)
    elif self.item_update_mode == "plus_transform":
        item_embeddings = tf.matmul(item_embeddings + o, self.transform_matrix)
    else:
        raise Exception("Unknown item updating mode: " + self.item_update_mode)
    return item_embeddings

在得到olist之后,我們可以只用olist里面最后一個(gè)向量,也可以選擇相加所有的向量,來(lái)代表user-embedding,并最終計(jì)算得到預(yù)測(cè)值:

def predict(self, item_embeddings, o_list):
    y = o_list[-1]
    if self.using_all_hops:
        for i in range(self.n_hop - 1):
            y += o_list[i]

    # [batch_size]
    scores = tf.reduce_sum(item_embeddings * y, axis=1)
    return scores

計(jì)算損失

我們前面提到了,模型的loss最終由三部分組成,在取對(duì)數(shù)后,三部分損失分別表示對(duì)數(shù)損失、知識(shí)圖譜特征表示的損失,正則化損失:

def _build_loss(self):
    self.base_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=self.labels, logits=self.scores))

    self.kge_loss = 0
    for hop in range(self.n_hop):
        h_expanded = tf.expand_dims(self.h_emb_list[hop], axis=2)
        t_expanded = tf.expand_dims(self.t_emb_list[hop], axis=3)
        hRt = tf.squeeze(tf.matmul(tf.matmul(h_expanded, self.r_emb_list[hop]), t_expanded))
        self.kge_loss += tf.reduce_mean(tf.sigmoid(hRt))
    self.kge_loss = -self.kge_weight * self.kge_loss

    self.l2_loss = 0
    for hop in range(self.n_hop):
        self.l2_loss += tf.reduce_mean(tf.reduce_sum(self.h_emb_list[hop] * self.h_emb_list[hop]))
        self.l2_loss += tf.reduce_mean(tf.reduce_sum(self.t_emb_list[hop] * self.t_emb_list[hop]))
        self.l2_loss += tf.reduce_mean(tf.reduce_sum(self.r_emb_list[hop] * self.r_emb_list[hop]))
        if self.item_update_mode == "replace nonlinear" or self.item_update_mode == "plus nonlinear":
            self.l2_loss += tf.nn.l2_loss(self.transform_matrix)
    self.l2_loss = self.l2_weight * self.l2_loss

    self.loss = self.base_loss + self.kge_loss + self.l2_loss

好了,代碼的部分我們就介紹完了,如果大家感興趣,可以下載相應(yīng)的代碼和數(shù)據(jù),進(jìn)行相應(yīng)的編寫(xiě)和調(diào)試喲!

參考文獻(xiàn):
1、論文:https://arxiv.org/abs/1803.03467

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

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

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