NewBeeNLP原創出品 node
公衆號專欄做者@上杉翔二 git
清閒會 · 信息檢索github
圖神經網絡能夠說是如今AI領域的超級寵兒。針對推薦系統的稀疏性問題,圖方法還真的很適合,主要緣由有下:
web
-
推薦系統中存在不少的圖結構,如二部圖,序列圖,社交關係圖,知識語義圖等 -
GNN比傳統的隨機遊走等能有更好的表現
「PinSage」和「EGES」都是很好的落地實踐方法,也是這篇文章的重點。不過首先來看一下對於user-item二部圖的通常處理方法「GCMC」,通用的捕捉主要是須要處理四步,算法
-
構圖,包括如何採樣 -
鄰居聚合,包括多少信息應該被選擇 -
信息更新,如何整合中心節點的信息 -
最後的節點表示,而後再作下游的推薦預測任務。
PS!文末有咱們新創建的『圖神經網絡』專題討論組,名額有限,趕忙加入一塊兒精準交流吧!數據庫
GCMC
論文:Graph Convolutional Matrix Completion 地址:https://arxiv.org/pdf/1706.02263.pdf arxiv訪問不方便的同窗後臺回覆『0012』直接獲取論文
發表在KDD2018,將user-item矩陣補全問題抽象成了二分圖的鏈接預測問題。每種鏈接預測的邊能夠視爲lable(如點擊,購買,收藏等等,1-5的評分也能夠),而後用二部圖的方法來進行連接預測(即預測是否點擊或者預測評分是多少,就變成了分類預測問題)。微信
雖然也能夠圖特徵提取模型和連接預測模型,不過本文具體方式上如上圖,是使用圖自編碼器進行端到端建模。網絡
Graph convolutional encoder
使用節點消息傳遞來更新模型參數,好比從item j 到 user i:session
其中c是正則化,W是控制變類型的權重,x是j的特徵。從user到item固然也是一樣的方法,而後對每一個節點都進行這樣的消息傳遞,最後就能獲得每一個節點的表示(如user的表示是用一個權重W對其全部相鄰節點的表示聚合獲得)。app
Bilinear decoder
重建鏈路,即把每一個不一樣的評分等級(或者點擊,購買等)看做是一類
其中 是用戶,商品的表示,Q是轉化矩陣,最後計算一個softma分數來預測「分類」就能夠了。
GCMC使用消息傳遞+自編碼的思想,實際上在大圖應用中對每一個節點作消息傳遞太過複雜。在實踐應用中仍是使用隨機遊走+其餘,接下主要介紹PinSage,EGES。
PinSage
論文:Graph Convolutional Neural Networks for Web-Scale Recommender Systems 地址:https://arxiv.org/abs/1806.01973 arxiv訪問不方便的同窗後臺回覆『0013』直接獲取論文
PinSage,一個可以學習節點嵌入的隨機遊走GCN,由Pinterest公司和Stanford完成的工做,首次將圖方法落地到了工業界。PinSage的理論背景是基於GraphSAGE[1],即概括(inductive)式的學習,直接學習聚合函數而不是固定的節點,這也是其餘的圖算法如GCN等等直推式(transductive)方法沒法作到的,更能知足實際中的圖節點是不斷變化的需求(節點和關係都會不斷的變化)。
目地
首先看看需求。Pinterest公司是世界上最大的圖片社交分享網站,業務採用瀑布流的形式向用戶展示圖片(抖音也很像這種模式),而且容許用戶建立和管理主題圖片集合。網站上的大量圖片稱爲pins,而用戶喜歡的圖片集合(相似收藏夾),即稱爲pins釘在畫板 的pinboards上。因而pins和boards就造成了如開頭圖片所示的二部圖形式。
挖掘這種二部圖的目的在哪裏?分析用戶興趣,幫助用戶發現和匹配他們感興趣的圖片(商品)。雖然Pinterest的數據顯然都是一堆圖片,可是圖片節點自己的信息是沒法經過CNN-based方法來解決的。如圖像識別,牀欄和花園柵欄都是條狀的「欄」,被分爲一類的機率很大,這並不能提供不少有用的信息,可是若是看看這兩個圖片在Graph中的位置就會發現區別很大,由於大門和花園柵欄一般會成爲鄰居節點,可是牀和大門卻不多相鄰。這也就是圖的優勢,能夠經過鄰居節點的信息,位置獲得更豐富的嵌入特徵。
模型
模型的輸入Graph是數十億對象的web-scale graph(30億個節點,180億),節點是圖片(須要注意的是,節點特徵包括視覺特徵和文本特徵)。而後能夠將圖分紅Pin和Pinboard(實際上能夠看做是pin的上下文信息),依照關係能夠構建二部圖。
PinSage基於GraphSAGE,GraphSAGE[2]博主之前已經整理過了,因此不作過多的展開。簡單來講它的核心思想就是學習聚合節點的鄰居特徵生成當前節點的信息的「聚合函數」,有了聚合函數無論圖如何變化,均可以經過當前已知各個節點的特徵和鄰居關係,獲得節點的embedding特徵。
GraphSage(Graph SAmple and aggreGatE),很重要的兩步就是Sample採樣和Aggregate聚合,PinSage也是同樣。
首先是Convolve部分,這部分至關於GraphSage算法的聚合階段過程,僞代碼以下:
輸入是當下節點u的嵌入 ,而後獲得它的鄰居節點 ,而後主要對應僞碼的1,2,3步:
-
聚合鄰居。能夠看到,全部的鄰居節點特徵 都通過一層dense層(由Q和q參數控制,再ReLU激活),再由聚合器或池化函數 (如mean等)將全部鄰居節點的信息聚合獲得 -
更新當前節點的特徵。將原特徵 和 拼接後再通過一層dense層(W,w參數控制,再ReLU激活) -
最後歸一化。直接對上一步獲得的特徵歸一化
因此其實Convolve和GraphSage的不一樣之處就在於聚合鄰居特徵前多作了一步dense層抽象特徵。
而後是minibatch對應着採樣鄰居部分,僞代碼以下:
採樣方法實際上就是在某個Minibatch內,以全部節點做爲起始節點,而後作一個bfs去獲取必定數量的鄰居節點(步長固定爲K的路徑),最後按照逐層方式進行多層的卷積。
-
2~7行是鄰居採樣階段。不沿用GraphSage的隨機採樣,而是使用訪問數做爲重要性採樣。即每次從當前節點出發隨機走,雖然一開始是平均的,遊走不少次以後,被走到的次數越多的節點,它相對於當前節點的重要性就越高,最終選取top-t的鄰居。 -
而後後面就是Convolve操做了逐層的生成節點嵌入特徵。特別注意就是要通過dense以後才更新特徵(僞碼15-16)。
訓練損失使用的是常規負採樣以後,再使用的max-margin ranking loss,即最大化正例之間的類似性,同時保證與負例之間類似性小於正例間的類似性:
訓練技巧
-
簡單負採樣會致使模型分辨的粒度過粗,沒針對性,特別是數據量如此大的狀況下。因此增長了「hard」負樣本,即根據當前節點相關的PageRank排名,選排名在2000-5000之間的items爲「hard」負樣本候選集,再進行隨機採樣,以增長訓練難度。
-
Multi-GPU形式,minibatch取值爲512-4096不等,大的batchsize可能會致使收斂困難。因此使用warmup:在第一個epoch中將學習率線性提高到最高,後面的epoch中再逐步指數降低。
-
Minibatch裏具備較多樣本,一個GPU沒法計算。因此將這個Minibatch的圖切成不少個子圖,每一個子圖在一個GPU上,來作GPU並行的求梯度,最後再將梯度聚集起來更新參數。
-
爲了大規模計算設定的兩個MapReduce任務:
-
執行聚合全部pins的嵌入特徵。即把全部的pins映射到一個低緯度空間 -
執行採樣鄰居特徵獲得board的嵌入特徵,即直接經過採樣鄰接節點的特徵來得到。向量最終都會存在數據庫中供下游任務使用,實驗證實這種方法在不到24小時內能夠爲全部30億樣本生成Embedding。
PinSage的工程啓示
-
在鄰接點的獲取中,採用基於Random Walk的重要性採樣 -
Hard負樣本抽取 -
基於鄰接點權重的重要性池化操做 -
利用圖片,文字信息構建初始特徵向量 -
階段性存儲節點最新的Embedding,避免重複計算
PinSage和node2vec,DeepWalk的區別?
-
node2vec,DeepWalk是無監督訓練,而PinSage是有監督訓練 -
node2vec,DeepWalk不能用節點特徵,PinSage能夠 -
node2vec,DeepWalk的參數和節點呈線性關係,很難用在超大型的圖上
若是隻用徹底hard的負採樣會怎麼樣?
模型收斂速度減半,迭代次數加倍。因此其實PinSage使用的是一種curriculum的方式,即第一輪簡單負採樣,幫助模型快速收斂,而後再逐步加入hard的樣本,如第n輪使給負樣本集合中增長n-1個負樣本。直觀來說,PinSage有12億個樣本做爲訓練正例,每一個batch500個負例,每張圖又有6個hard負例。
負採樣之道
我以前也有一個疑問,就是爲何不使用「曝光未點擊」的樣本呢?
這裏我也是弄混了兩個概念,因此就暫時整理在這篇文章裏面吧。弄混的地方就是通常推薦系統的召回和排序的採樣是不同的!對於排序的效果提高才會比較講究「真負」樣本,因此通常就拿「曝光未點擊」的樣本,但這裏反應的每每是用戶的直接反饋。可是召回不同,召回階段不只僅是它的候選集大而已,與排序側須要挖掘用戶可能的喜愛不一樣(其獲得的list已經至關規整了,因此某種程度曝光未點擊策略只能算是個trick而已),召回須要將可能喜歡的儘量得與其餘海量不靠譜的樣本分隔開(這裏的候選集會是千差萬別的),因此在召回側若是隻拿用戶的反饋訓練,將會一葉障目致使更多不靠譜的都篩選不掉。
因此隨機採樣或許會是一個好選擇。可是這裏又有兩個坑:一是儘可能不要隨機選擇,由於推薦中充斥着二八定律和熱門商品等等,很容易被綁架,因此通常最好對熱門的降採樣,這就和word2vec很像了。二是隨機選擇的能力有限,沒法學到細粒度的差別。
因此Hand Negative是很重要的。一方面和用戶匹配程度最高的是正樣本,另外一方面徹底差別很大的是負樣本,而hard negative須要找到難度適中的,沒那麼類似的樣本,因此纔有剛剛PinSage也使用的2000-5000的這個範圍,而且先簡單負採樣,再逐步加hard,且按照facebook EBR提到的,easy:hard維持在100:1是比較合理的。
EGES
![](http://static.javashuo.com/static/loading.gif)
論文:Billion-scale Commodity Embedding for E-commerce Recommendation in Alibaba 地址:https://arxiv.org/abs/1803.02349 arxiv訪問不方便的同窗後臺回覆『0014』直接獲取論文
一樣的阿里其實也基於GraphSAGE作過相關工做(或許GraphSAGE在工業落地上是最有優點的了),發自KDD 2018。爲了解決在推薦系統中的老三樣困難:可擴展性,稀疏性和冷啓動。因此提出了:
-
能夠根據用戶的歷史行爲來構建商品圖(如上圖的a和b,根據用戶的一些點擊記錄,依照連續點擊應該存在關係,能夠構造出如b同樣的item graph),並學習圖中全部商品的嵌入(如圖c的帶權隨機遊走採樣,用deepwalk[3]思想基於skip-gram訓練embedding)。這一步就是做者描述的Base Graph Embedding(BGE)。
-
爲了減輕稀疏性和冷啓動問題,將邊信息合併到圖嵌入框架中。即增長商品的屬性,如品牌,價格等。而後就升級成了Graph Embedding with Side information (GES),而且對不一樣的side Information加權獲得Enhanced Graph Embedding with Side information (EGES)。
插一句這個歷史行爲構圖其實就是session-based會話推薦了,這個下一篇文章會繼續詳細介紹。EGES模型圖以下:
看圖可知,就是把特徵one-hot以後Embedding,再用注意力算一次權重H以後concat因此的特徵,最後一層dense就獲得了商品的特徵。EGES只是把side Information帶權加入進來,即把每一個item的特徵變豐富了。以後仍是用deepwalk[4]思想採樣變「句子」,基於skip-gram訓練embedding,DeepWalk博主整理過了就不說了吧,最後也是一個負採樣訓練的常規操做。
SR-GNN
會話序列推薦的圖應用,發自AAAI 2019,先放連接:
論文:Session-based Recommendation with Graph Neural Networks 地址:https://arxiv.org/abs/1811.00855 arxiv訪問不方便的同窗後臺回覆『0015』直接獲取論文
這篇文章和上一篇很像,只是否是用DeepWalk,而是GNN的。首先,會話推薦是指,對於一個用戶的點擊序列(即session),預測下一個要點擊的物品。即輸入全部的物品V={v1,v2,...,vm} ,在給定某個session爲s=[v1,v2,...,vn]的用戶點擊序列,預測下一個要點擊的物品vn+1。
現有基於會話的推薦,方法主要集中於循環神經網絡和馬爾可夫鏈,可是存在如下缺陷:
-
當一個session中用戶的行爲數量十分有限時,RNN很難產生好的用戶特徵 -
馬爾可夫鏈很是依賴數據獨立性的假設 -
同時,物品之間的轉移模式在會話推薦中是十分重要,但RNN和馬爾可夫過程只對相鄰的兩個物品的單向轉移關係進行建模,而忽略了會話中其餘的物品(即上下文)。
因此不如直接將會話序列建模爲圖結構數據,並使用圖神經網絡捕獲複雜的項目物品item間轉換,每個會話利用注意力機制將總體偏好與當前偏好結合進行表示。同時這種方式也就不依賴用戶的表示了,徹底只基於會話內部的潛在向量得到Embedding,而後預測下一個點擊。
Graph Neural Networks
對於構圖的表示,能夠看模型圖的最左邊紅色部分,對於一連串的點擊序列,就直接利用點擊關係構圖就ok。而後抽取這個會話圖中的每一個物品的Embedding向量,利用物品的Embedding向量再進行預測。
抽取序列中物品特徵的GNN部分使用 Gated GNN,其計算公式爲:
H和b是權重矩陣和偏置,v是點擊物品序列。
是由兩個鄰接矩陣,即入度和出度A(in)和A(out)矩陣拼接而成的(n, 2n)的矩陣,而
能夠理解成矩陣的第i行(1, 2n)。以下圖所示:
表明的就是紅色的那一行,這樣作的目的是使模型能結合出度入度以學習到更復雜的圖上下文關係。至此GNN的部分實際上就結束了.....後面的四個公式就是普通的RNN了,即利用某點在圖中的特徵表示再RNN。
def get_slice(self, i):
inputs, mask, targets = self.inputs[i], self.mask[i], self.targets[i]
items, n_node, A, alias_inputs = [], [], [], []
for u_input in inputs:
n_node.append(len(np.unique(u_input)))
max_n_node = np.max(n_node)#最大的session的item數目
for u_input in inputs:
node = np.unique(u_input)#unique的item
items.append(node.tolist() + (max_n_node - len(node)) * [0])#不夠的補0
u_A = np.zeros((max_n_node, max_n_node))#user的鄰接矩陣
for i in np.arange(len(u_input) - 1):
if u_input[i + 1] == 0: #爲0說明這個session已經結束了
break
u = np.where(node == u_input[i])[0][0]
v = np.where(node == u_input[i + 1])[0][0]
u_A[u][v] = 1
\#最終想要的鄰接矩陣A是入度和出度A(in)和A(out)矩陣拼接而成的(n, 2n)的矩陣
u_sum_in = np.sum(u_A, 0) #按0維度sum,即入度總數
u_sum_in[np.where(u_sum_in == 0)] = 1 #防止沒有某節點沒有入度而除了0
u_A_in = np.divide(u_A, u_sum_in) #平均一下
u_sum_out = np.sum(u_A, 1) #同理按1sum,算一下出度
u_sum_out[np.where(u_sum_out == 0)] = 1
u_A_out = np.divide(u_A.transpose(), u_sum_out) #須要轉置一下
u_A = np.concatenate([u_A_in, u_A_out]).transpose() #最後拼接二者
A.append(u_A)
alias_inputs.append([np.where(node == i)[0][0] for i in u_input])
return alias_inputs, A, items, mask, targets
拿到A以後再作GNN,按上面圖中的公式就能夠了。
def GNNCell(self, A, hidden):
\#由於A是入度和出度兩個矩陣拼接獲得的,因此要分0:A.shape[1]和A.shape[1]: 2 * A.shape[1]分別作linear變換
input_in = torch.matmul(A[:, :, :A.shape[1]], self.linear_edge_in(hidden)) + self.b_iah
input_out = torch.matmul(A[:, :, A.shape[1]: 2 * A.shape[1]], self.linear_edge_out(hidden)) + self.b_oah
inputs = torch.cat([input_in, input_out], 2)#而後再拼接
gi = F.linear(inputs, self.w_ih, self.b_ih)#輸入門
gh = F.linear(hidden, self.w_hh, self.b_hh)#記憶門
i_r, i_i, i_n = gi.chunk(3, 2)#沿2維度分3塊,由於線性變換這三門是一塊兒作的
h_r, h_i, h_n = gh.chunk(3, 2)
\#三個gate
resetgate = torch.sigmoid(i_r + h_r)
inputgate = torch.sigmoid(i_i + h_i)
newgate = torch.tanh(i_n + resetgate * h_n)
hy = newgate + inputgate * (hidden - newgate)
return hy
Attention策略
獲得item的特徵向量後(模型圖中的彩色條),應用一個注意力機制。有一點不一樣的是做者認爲在當前的會話序列中,最後一個物品是很是重要的,因此單獨將它做爲 ,而後計算其餘的物品與最後一個物品之間的相關性,再加權就獲得了 以考慮到全局信息:
接着將獲得的 和 鏈接,輸入到一個線性層
最後使用 和每一個物品的embedding進行內積計算,再softmax獲得最終每一個物品的點擊率,最後交叉熵獲得損失函數:
def compute_scores(self, hidden, mask):
\#計算全局和局部,ht是最後一個item,做者認爲它表明短時間十分重要
ht = hidden[torch.arange(mask.shape[0]).long(), torch.sum(mask, 1) - 1] # batch_size x latent_size
q1 = self.linear_one(ht).view(ht.shape[0], 1, ht.shape[1]) # batch_size x 1 x latent_size
q2 = self.linear_two(hidden) # batch_size x seq_length x latent_size
alpha = self.linear_three(torch.sigmoid(q1 + q2)) #計算全局注意力權重
\# 不算被mask的部分的sum來表明全局的特徵a
a = torch.sum(alpha * hidden * mask.view(mask.shape[0], -1, 1).float(), 1)
if not self.nonhybrid: #globe+local
a = self.linear_transform(torch.cat([a, ht], 1))#將局部和全局s1和sg拼接
b = self.embedding.weight[1:] # n_nodes x latent_size
scores = torch.matmul(a, b.transpose(1, 0)) #再作一次特徵變換
return scores
完整的代碼中文逐行筆記:https://github.com/nakaizura/Source-Code-Notebook/tree/master/SR-GNN
總結
固然只要有關係的地方就能構圖,就能抽圖特徵,特別是最近Graph這麼火....萬物皆可Graph。好比有人的地方就能有社交關係等等,深刻挖掘屬性等等,最近也有不少論文是針對屬性中的異構信息[5]進行挖掘,下一篇博文再整理。
一塊兒交流
想和你一塊兒學習進步!咱們新創建了『圖神經網絡』專題討論組,歡迎感興趣的同窗加入一塊兒交流。爲防止小廣告形成信息騷擾,麻煩添加個人微信,手動邀請你
本文參考資料
GraphSAGE: https://blog.csdn.net/qq_39388410/article/details/103903631
[2]GraphSAGE: https://blog.csdn.net/qq_39388410/article/details/103903631
[3]deepwalk: https://blog.csdn.net/qq_39388410/article/details/103859078
[4]deepwalk: https://blog.csdn.net/qq_39388410/article/details/103859078
[5]異構信息: https://blog.csdn.net/qq_39388410/article/details/104344930
- END -
![](http://static.javashuo.com/static/loading.gif)
2020-09-21
![](http://static.javashuo.com/static/loading.gif)
2020-11-03
![](http://static.javashuo.com/static/loading.gif)
2020-12-03
![](http://static.javashuo.com/static/loading.gif)
本文分享自微信公衆號 - NewBeeNLP(NewBeeNLP)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。