圖神經網絡用於推薦系統問題(NGCF,LightGCN)

在這裏插入圖片描述
何向南老師組的又兩大必讀論文,分別發在SIGIR19’和SIGIR20’。

Neural Graph Collaborative Filtering
協同過濾(collaborative filtering)的基本假設是相似的用戶會對物品展現出相似的偏好,自從全面進入深度學習領域之後,一般主要是先在隱空間中學習關於user和item的embedding,然後重建兩者的交互即interaction modeling,如MF做內積,NCF模擬高階交互等。但是他們並沒有把user和item的交互信息本身編碼進 embedding 中,這就是NGCF想解決的點:顯式建模User-Item 之間的高階連接性來提升 embedding

High-order Connectivity
解釋高階連通性如上圖,圖1左邊爲一般CF中user-item交互的二部圖,雙圓圈表示此時需要預測的用戶u1,對於u1我們可以把有關他的連接擴展成右圖的樹形結構,l是能到達的路徑長度(或者可以叫跳數),l=1表明能一步到達u1的item,此時可以看到最外層的跳數相同的i4跟i5相比(l都爲3),用戶u1對i4的興趣可能要比i5高,因爲i4->u2->i2->u1、i4->u3->i3->u1有兩條路徑,而i5->u2->i2->u1只有一條,所以i4的相似性會更高。所以如果能擴展成這樣的路徑連通性來解釋用戶的興趣,就是高階連通性。

NGCF的完整模型如下圖,可以分爲三個部分來看:

  • Embeddings:對user和item的嵌入向量,普通的用id來嵌入就可以了
  • Embedding Propagation Layers:挖掘高階連通性關係來捕捉交互以細化Embedding的多個嵌入傳播層
  • Prediction Layer:用更新之後帶有交互信息的 user 和 item Embedding來進行預測
    在這裏插入圖片描述

主要就是中間的交互信息捕捉怎麼構建,主要思路是藉助GNN的message passing消息傳遞機制:mui=1NuNi(W1ei+W2(eieu))m_{u\leftarrow i}=\frac{1}{\sqrt {|N_u||N_i|}}(W_1e_i+W_2(e_i\odot e_u))其中Mu←i是消息嵌入(即要傳播的信息),使用embedding後的user,item的特徵eu,eie_u,e_i作爲輸入,然後兩者計算內積相似度來控制鄰域的信息,再加回到item上,用權重W控制權重,最後的N是u和i的度用來歸一化係數,可以看做是折扣係數,隨着傳播路徑長度的增大,信息慢慢衰減。

博主自己的理解是實際上做了一個小型的attention從領域的item整合信息,所以下一步就是用這些鄰域信息更新user:eu(1)=LeakyReLU(muu+iNumui)e^{(1)}_u=LeakyReLU(m_{u\leftarrow u}+\sum_{i\in N_u}m_{u\leftarrow i})上標(1)表示一階聚合,可以看到從領域的i中整合信息又考慮到了自身節點的信息,最後再激活一下。高階傳播實際就是將上述的一階傳播堆疊多層,這樣經過 l 次聚合,每個節點都會融合其 l 階鄰居的信息,也就得到了節點的 l 階表示,具體的傳播如下圖:
在這裏插入圖片描述對應user u2來說,先由直接連接的i2,i4,i5開始是一階,進行上述的聚合更新之後,聚合二階鄰居(此時u1也完成了更新),此時用u1和u2更新i2,同樣的其他的item也會相應的更新,最後再由i1,i2,i3來更新u1,這樣通過相互更新相互迭代,就完成了開篇那張圖的樹形結果。

如果把上述的更新換成矩陣形式的話:
E(l)=σ((L+I)E(l1)W1(l)+LE(l1)E(l1)W2(l))E^{(l)}=\sigma((L+I)E^{(l-1)}W^{(l)}_1+LE^{(l-1)}\odot E^{(l-1)}W^{(l)}_2)其中L=D1/2AD1/2L=D^{-1/2}AD^{-1/2},這其實和GCN很像了。最後再將 L 階的節點表示全部readout,分拼接起來作爲最終的節點表示,再內積得到預測結果:yNGCF(u,i)=euTeiy'_{NGCF}(u,i)={e^*_u}^Te^*_i損失函數就是常規的成對loss操作Loss=(u,i,j)Olnσ(yuiyuj)+λΘ2Loss=\sum_{(u,i,j)\in O} -ln \sigma(y'_{ui}-y'_{uj})+\lambda||\Theta||^2

最後再看看ngcf的關鍵代碼:

def _create_ngcf_embed(self):
        # Generate a set of adjacency sub-matrix.
        # 使用矩陣的解法,所以先得到鄰接矩陣
        if self.node_dropout_flag:
            # node dropout.
            A_fold_hat = self._split_A_hat_node_dropout(self.norm_adj)
        else:
            A_fold_hat = self._split_A_hat(self.norm_adj)
        #最初的嵌入形式
        ego_embeddings = tf.concat([self.weights['user_embedding'], self.weights['item_embedding']], axis=0)

        all_embeddings = [ego_embeddings]
        #執行k層的消息傳播
        for k in range(0, self.n_layers):
            temp_embed = []
            for f in range(self.n_fold):
                temp_embed.append(tf.sparse_tensor_dense_matmul(A_fold_hat[f], ego_embeddings))

            # u到u,聚合u所有鄰居的消息
            side_embeddings = tf.concat(temp_embed, 0)
            # 特徵變換矩陣
            sum_embeddings = tf.nn.leaky_relu(
                tf.matmul(side_embeddings, self.weights['W_gc_%d' % k]) + self.weights['b_gc_%d' % k])

            # i到u,合併自嵌入,鄰居的嵌入
            bi_embeddings = tf.multiply(ego_embeddings, side_embeddings)
            # 再次變換特徵
            bi_embeddings = tf.nn.leaky_relu(
                tf.matmul(bi_embeddings, self.weights['W_bi_%d' % k]) + self.weights['b_bi_%d' % k])

            #非線性激活函數,u到u和i到u的兩部分
            ego_embeddings = sum_embeddings + bi_embeddings

            # message dropout.
            ego_embeddings = tf.nn.dropout(ego_embeddings, 1 - self.mess_dropout[k])

            #正則化
            norm_embeddings = tf.math.l2_normalize(ego_embeddings, axis=1)

            all_embeddings += [norm_embeddings]

        all_embeddings = tf.concat(all_embeddings, 1)#拼一起
        u_g_embeddings, i_g_embeddings = tf.split(all_embeddings, [self.n_users, self.n_items], 0)
        return u_g_embeddings, i_g_embeddings

paper:https://arxiv.org/abs/1905.08108
code:https://github.com/xiangwang1223/neural_graph_collaborative_filtering

在這裏插入圖片描述

LightGCN: Simplifying and Powering Graph Convolution Network for Recommendation
前一篇的NGCF主要遵循標準GCN變形得到,包括使用非線性激活函數和特徵變換矩陣w1和W2。然而作者認爲實際上這兩種操作對於CF並沒什麼大用,理由在於不管是user還是item,他們的輸入都只是ID嵌入得到的,即根本沒有具體的語義(一般在GCN的應用場景中每個節點會帶有很多的其他屬性),所以在這種情況下,執行多個非線性轉換不會有助於學習更好的特性;更糟糕的是,它可能會增加訓練的困難,降低推薦的結果。

所以將非線性激活函數non-linear和特徵變換矩陣feature transformation都去掉,只增加一組權重係數來鄰域聚集weighted aggregate不同gcn層輸出的嵌入爲最終的嵌入,大大簡化了模型。即在LightGCN中,只採用簡單的加權和聚合器,放棄了特徵變換和非線性激活的使用,所以公式變爲(只剩下):
eu(k+1)=iNu1NuNiei(k)e^{(k+1)}_u=\sum_{i\in N_u} \frac{1}{\sqrt{|N_u|}\sqrt{|N_i|}}e^{(k)}_iei(k+1)=uNi1NiNueu(k)e^{(k+1)}_i=\sum_{u\in N_i} \frac{1}{\sqrt{|N_i|}\sqrt{|N_u|}}e^{(k)}_u而且從上式可以看到它只聚合連接的鄰居,連自連接都沒有。最後的K層就直接組合在每個層上獲得的嵌入,以形成用戶(項)的最終表示:eu=k=0Kαkeu(k)e_u=\sum^K_{k=0} \alpha_k e^{(k)}_uei=k=0Kαkei(k)e_i=\sum^K_{k=0} \alpha_k e^{(k)}_i其中αk設置爲1/(K+1)時效果最好。其餘的部分就和NGCF一模一樣。

爲什麼要組合所有層?

  • GCN隨着層數的增加會過平滑,直接用最後一層不合理
  • 不同層的嵌入捕獲不同的語義,而且更高層能捕獲更高階的信息,結合起來更加全面
  • 將不同層的嵌入與加權和結合起來,可以捕獲具有自連接的圖卷積的效果,這是GCNs中的一個重要技巧

同樣看一下關鍵部分的代碼,代碼量真的要簡潔許多:

def _create_lightgcn_embed(self):
        if self.node_dropout_flag:
            A_fold_hat = self._split_A_hat_node_dropout(self.norm_adj)
        else:
            A_fold_hat = self._split_A_hat(self.norm_adj)
        
        ego_embeddings = tf.concat([self.weights['user_embedding'], self.weights['item_embedding']], axis=0)
        all_embeddings = [ego_embeddings]
        
        for k in range(0, self.n_layers):
            temp_embed = []
            for f in range(self.n_fold):
                temp_embed.append(tf.sparse_tensor_dense_matmul(A_fold_hat[f], ego_embeddings))
                
            #歸一化已經在A裏面得到A_hat了,所以這裏直接聚合就完了
            side_embeddings = tf.concat(temp_embed, 0)
            ego_embeddings = side_embeddings
            all_embeddings += [ego_embeddings]
        all_embeddings=tf.stack(all_embeddings,1)#然後拼一起
        all_embeddings=tf.reduce_mean(all_embeddings,axis=1,keepdims=False)
        u_g_embeddings, i_g_embeddings = tf.split(all_embeddings, [self.n_users, self.n_items], 0)
        return u_g_embeddings, i_g_embeddings

paper:https://arxiv.org/abs/2002.02126
code:https://github.com/kuandeng/LightGCN


完整的關鍵代碼中文註釋可以參考:https://github.com/nakaizura/Source-Code-Notebook/

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