原創:阿里雲計算平臺事業部 沈雯婷、艾寶樂
Overview
Graph-Learn(GL) 是阿里巴巴開源的高性能工業級大規模圖學習系統,本文將對GL的用戶接口做一個概覽,並介紹GL豐富的圖採樣算法,以及GL靈活統一的GNNs模型框架,幫助用戶快速上手GL。
項目地址:https://github.com/alibaba/graph-learn 。
圖數據廣泛存在於我們生活中,社交網絡、互聯網、交通網等等都是天然的圖數據。在阿里內部場景,用戶物品的交互數據,安全風控領域的用戶、評論、交易等都構成圖,圖可以說是數據最為廣泛的存在形式。
深度學習在文本,語音,圖像等數據上取得了很大成功,GNNs(圖神經網絡)是深度學習在圖結構數據上的應用。對於圖這一非歐空間的數據,如何將它和深度學習結合起來是GNNs首先要解決的問題。GNNs早期的研究主要集中在Spectral-based 方法上,這種方法理論比較充分,但是難以用於大規模場景。近些年以GraphSage為代表的Spatial-based方法在性能,泛化性,靈活性等方面均體現出了優勢。GraphSage通過採樣子圖進行按批次訓練的方式在Pinterest推薦場景進行了實際應用,並取得了很好的效果。此外,像DeepWalk等Graph embedding算法和TransE等知識圖譜模型的訓練都是基於採樣的方法進行設計和訓練。
阿里很多場景的數據都是十億級別節點,百億邊的規模,針對阿里眾多場景的實際需求和特點,我們設計開發了簡潔易用,高性能的工業級大規模圖學習系統Graph-Learn(GL) 。GL主要面向spatial-based的GNNs算法,同時支持graph embedding, 知識圖譜等常見圖學習算法。
GL的整體模塊如下圖所示,底層由圖引擎和NN引擎組成,通過數據模型管理採樣後的圖數據從而利用NN引擎的計算能力接入上層算法。
Graph Engine提供了分佈式大規模圖的查詢、採樣等操作。基於簡潔的接口,你可以遍歷圖、得到鄰居樣本、得到所需屬性,從而自己組織數據,構造模型。
Data Model是GL的基礎數據模型,我們以種子節點(seed nodes/edges)和鄰域(receptive fields /multi-hops neighbors)組成的子圖為基本編程對象,並提供了對接上層神經網絡模型的數據轉換功能。基於數據模型,可以輕鬆管理樣本,並專注於模型開發。
基於Data Model,Graph learning models通過抽象多種encoders模塊可以將圖數據轉換為最終的embedding。GL提供了多個built-in的GNNs models,並封裝了若干常用的encoders和對應的圖卷積層,圖聚合層。Graph learning models提供了e2e的模型訓練評估過程,可以在單機和分佈式環境一鍵運行,同時基於內置的各個模塊也可以方便快速構建自己的訓練過程和算法模型。
圖引擎
簡潔的接口:從裸數據到GNN樣本,只差一行python
Graph Engine包含圖對象模塊、採樣模塊和查詢模塊,這些 Graph 上的模塊接口通過一套Gremlin-like API表達。如何從裸數據構造單機或分佈式的圖、如何在圖上游走、如何遍歷、如何採樣鄰域、查詢哪些field,以及如何用這些數據構造圖神經網絡模型所需的樣本,整個過程只需要一句python表示,類似data-flow的查詢語句:
gl.Graph().node().edge().init().V().outV().sample().by().values()
我們將這一行代碼拆分到圖對象接口、採樣接口和查詢接口中進行解釋。
(1) GL圖對象
圖對象模塊用於將結構化的圖數據轉換為邏輯圖對象。GL的入口非常簡單,載入graphlearn庫,構建一個 Graph 邏輯對象,後續所有的操作都在這個 Graph 對象上進行。
import graphlearn as gl
g = gl.Graph()
- 圖數據格式:靈活的schema,多變的數據類型
GL圖數據格式靈活,支持float,int,string類型的屬性,支持帶權重、標籤。在現實的場景中,數據格式多變,通過config文件描述非常複雜,容易寫錯;有些GNN系統不支持多種類型的屬性。
下面的示例描述了數據中存在string和float類型的兩列屬性和權重、標籤列。
decoder = gl.Decoder(attr_types=["string", "float"], weighted=True, labeled=True)
- 數據源載入和拓撲描述:同構、異構、多數據源,通通支持
GL提供了 node
和 edge
兩個簡單的接口來支持頂點和邊的數據源載入,同時在這兩個接口中描述圖的拓撲結構,比如“buy”邊的源頂點類型是“user”,目的頂點類型是“item”。這個拓撲結構在異構圖中十分重要,是後續採樣路徑meta-path的依據,也是查詢的實體類型的基礎。
g.node(data_source, node_type, decoder) \
.node(data_source, node_type, decoder) \
.edge(data_source, (src_node_type, dst_node_type, edge_type), decoder)
- Graph Engine啟動:快速拉起大規模分佈式圖引擎
GL提供單機的版本, 通過init 接口快速啟動Graph Engine,至此,圖對象已經構造完畢,查詢、採樣操作就可以在 Graph 上進行了。
g.init()
在大規模的場景下,圖頂點可達億級別,邊可達千億級別,不管是圖結構還是圖屬性,都無法完全載入單機內存。GL提供分佈式的Graph Engine,速度非常快,使用上也非常簡單,只需要在 init
中加幾個參數。
g.init({"server_count": N, "client_count": M}, task_name, job_index)
(2) 採樣接口
採樣在GL中通過遊走路徑和採樣策略進行描述。
如下示例中,表達的是一個遍歷和二跳鄰居採樣,即遍歷圖獲得64個用戶,對每個用戶根據邊的權重採樣50個他們購買的商品的相似商品。
q = g.V("user").bath(64).outV("buy").sample(5).outV("similar-to").sample(10).by("edge_weight").values()
除了採樣正鄰居,GL也提供了負採樣,只需要將示例中的 outV
改為 outNeg
即可。
GL提供了非常豐富的內置的採樣和負採樣策略,也支持自定義採樣策略的實現,我們將在下一章詳細解析大規模採樣算法在GL中的實現。
(3) 查詢接口
GL提供了 Nodes
和 Edges
兩個基礎的數據類型,作為遍歷、採樣的結果。為了獲取 Nodes
的int類型的屬性,可以調用如下接口進行查詢,得到的是numpy array數據結構。
nodes = g.run(q)
nodes[2].int_atrs # nodes is a list of Nodes, include Nodes of user,
# Nodes of item for 1 hop, Nodes of item of 2 hop.
g.run(q)
可以多次執行,遍歷圖中的頂點和他們的邊,直到遍歷完畢。因此,上述採樣和查詢的結果可以作為generator接入tf或pytorch等NN引擎作為數據源,從而深度定製GNN模型。
GL也提供了數據模型的封裝,來接管數據採樣流程和樣本組織。
豐富的採樣:GNN落地工業級場景的基石
圖採樣算法是GNN落地工業級大規模場景的基石,GL提供了非常豐富靈活的採樣算法。
本節將對GL的採樣實現和各個採樣算子做簡單介紹,採樣在GNNs中起著重要作用,並已經在很多場景得到了實際應用。
採樣和查詢是GL圖引擎部分對外提供服務的基本接口,採樣和查詢構建於分佈式圖存儲之上,以Python API的形式對外提供,如下圖所示。查詢包括圖的節點和邊的遍歷,節點和邊的屬性、權重、標籤等查詢。採樣分為鄰居採樣和負採樣,鄰居採樣提供採樣子圖的功能,負採樣主要面向非監督學習場景,系統直接提供採樣負樣本的功能,不需要用戶額外產生負樣本。
基於阿里內部大量業務場景的需求總結和實際應用,系統集成了若干種常用的採樣和負採樣方法(Built-in Samplers)。雖然這些採樣方法基本覆蓋了大部分需求,但是因為採樣方法和算法效果關係密切,甚至直接關係到模型的成敗,因此,開放採樣編程接口,方便用戶進行其他自定義採樣方法(Custom Sampler)的探索是我們系統的一個重要功能以及GNN創新的一大方向。
GL的圖引擎是CS架構,具體的採樣實現流程如下圖所示:給定一批需要採樣的源節點,首先通過Partitioner去查詢包含這些源節點的子圖在哪個server上,圖中src id為1,3的被partition到了server 1上,2,4被partition到了server 2上。然後,將這些src ids發送到對應的server上,查詢子圖,並調用本地採樣方法去執行採樣操作。目前系統集成了若干種常見的採樣算子,也可以支持用戶自定義自己的採樣算子。本地採樣結束後,需要將採樣後的結果按照原始src ids的順序進行拼接返回。Partition目前使用系統內置的hash劃分方法,將來我們會支持不同的Partition策略來進行更加高效的圖劃分,提高採樣和計算性能。
對於負採樣來說,沒有partition和stitch過程,一個batch的輸入數據,會隨機或者按照分佈選擇一個server,然後在這個選定server上進行負採樣,這樣可以做到全局負採樣的效果。
我們底層的數據結構使用Tensor的形式實現,支持常見數值類型和string類型的Tensor。基於Tensor這種規整的格式,系統可以將採樣的Partition,Stitch和分佈式執行過程自動化,因此,新增一個採樣算子時只需要寫具體的本地採樣邏輯即可。
GL採用了分佈式圖存儲和採樣,使得規模可以輕鬆擴展到上百臺機器,支撐起十億節點,百億邊的日常任務。此外,針對不同的採樣策略,我們採用了熱點緩存,慢機遷移等眾多優化,使得在規模提升的同時,能夠保持高性能地採樣。
鄰居採樣
GL支持異構圖,同構圖是異構圖的一種特例,因此圖採樣需要指定meta path,系統按照指定的meta path採樣得到多跳鄰居,不同的採樣算子按照不同算法來返回鄰居。目前built-in的採樣算子包括以下幾種:
- random:隨機採樣鄰居,每次在給定節點的所有鄰居里隨機選擇一個鄰居。
- edge_weight:按照邊的權重為概率分佈進行採樣。
- topk:以edge_weight為大小對鄰居進行排序,並返回topk個。鄰居數不足要求個數會循環填充。
- in_degree:按照鄰居的入度大小為概率分佈進行採樣。
- full neighbor: 返回給定點的所有鄰居。
前四種採樣都需要指定meta path,並且需要指定需要採的鄰居個數,採樣結果以dense的形式返回,這是由於採樣後按batch訓練的時候需要樣本對齊。當然對於原始GCN等需要獲取源節點的所有鄰居的算法,我們支持採樣返回節點的所有鄰居,而不做上採樣/下采樣,我們稱這種採樣為full neighbor採樣,採樣後的結果會以sparse的形式返回。
以上圖為例,假設需要採樣src id為1的節點的鄰居,指定的鄰居個數是2。那麼random會從[2, 3, 4, 5]裡隨機採樣兩個;edge_weight以邊權重為概率分佈返回兩個;in_degree採樣以入度為概率分佈返回兩個;topk會返回邊權重最大的兩個,即[5, 4]。如果指定採樣個數為6,則topk會做循環填充,返回結果為[5, 4, 3, 2, 5, 4];full neighbor採樣的話則會返回實際個數的全部鄰居[2, 3, 4, 5]。
以上採樣算法裡按概率分佈的採樣均採用了AliasMethod實現,因此複雜度都是常數時間。單機在512 batch size、採樣10個1hop、15個2hop鄰居id的情況下,耗時在毫秒級別。此外針對不同的策略系統有不同的優化,比如對於topk,我們在構圖時就會預先按照edge weight為大小對底層存儲表進行排序。
上面介紹的算法以node-wise(layer sampling)採樣為主,node-wise採樣隨著採樣的跳數增加,鄰居數目會急劇上升,在多跳(>2)情況下尤其明顯,因此近年來layer-wise的採樣也被應用於不同GNN算法,我們也正在探索開發layer-wise(graph sampling)的採樣,希望在多跳場景下帶來進一步的性能優化和內存節省。
負採樣
負採樣就是給定源節點,返回和它不相連的目標節點。負採樣同時支持本地和全局負採樣,對於一個batch的樣本要採樣其負樣本時,可以按照一定的概率分佈選擇一個server,然後在該server上採樣。如果選擇本地負採樣,則只在本機進行負採樣。目前built-in的負採樣算子包括以下幾種:
- random:隨機採樣和給定源節點不相連的目的節點。
- in_degree:按照目的節點的入度分佈,返回與源節點不相連的目的節點。
按照入度分佈的in_degree負採樣實現上採用AliasMethod[5],會在首次採樣前提前構建好Alias Table,因此採樣是常數時間複雜度。此外負採樣我們默認是嚴格負採樣,但是出於性能和極端情形的考慮,我們支持用戶端設置一個閾值來控制嚴格程度。負採樣可能對算法效果也會產生很大影響,因此負採樣算子如何和圖劃分結合,如何高效負採樣,以及按何種分佈負採樣都是值得探索和研究的地方。
自定義採樣
上面介紹了GL開發過程中,結合實際業務場景需求系統已經集成的鄰居採樣和負採樣算子。雖然這些built-in的採樣算子滿足了大部分GNNs和其他圖學習算法的需求,但是,考慮到每個具體業務場景的採樣、負採樣需求不同,而且採樣、負採樣策略對算法的構建和效果有著重要影響,因此我們也允許用戶自定義採樣算子。自定義採樣算子主要流程包括:
a)在C++文件中註冊並實現新Sampler
class MySampler : public Operator {
public:
Status Process(const OpRequest* req, OpResponse* res) override {
//TODO
}
};
REGISTER_OPERATOR("my_sampler", MySampler);
b)在Python端調用已註冊的算子
g.sample(count).by("my_sampler")...
算法模型
底層的圖引擎提供了分佈式存儲和採樣查詢功能,基於圖引擎,GL實現了一套統一的圖學習框架,可以和常見的深度學習框架比如TensorFlow, PyTorch進行結合。
Data Model:那些Dirty work系統幫你做了
圖引擎提供的採樣結果如何高效得和頂層神經網絡模型結合是實現圖學習算法的重要一環,為了簡化和優化這一過程,GL封裝了一套適配不同深度學習框架的數據模型。首先,將採樣後的結果以EgoGraph
的形式進行封裝,為了兼容各個不同的深度學習引擎,EgoGraph
中的數據類型為numpy array。EgoGraph
在後面NN部分使用時需要轉換成對應的tensor格式EgoTensor
, 這一轉換過程通過EgoFlow來管理和優化。
Graph Learning Models:靈活、高效、統一
所有的圖學習模型都需要編碼器來對點、邊或者子圖進行編碼從而得到點、邊或者子圖的embedding。GL首先實現了若干特徵編碼器,用來對採樣後的EgoTensor
進行編碼,然後,使用圖編碼器來對原始embedding進行編碼得到最終的embedding。對於大多數GNNs算法,圖編碼器實際是aggregate和combine過程,通過不同的卷積層可以輕鬆搭建一個圖編碼器。
基於這些編碼器,GL封裝了多個built-in的模型,包括:GraphSAGE,Bipartite GraphSAGE,GCN,GAT,DeepWalk,LINE,TransE,同時也封裝了很多常見loss函數,opimizer以及訓練預測過程,用戶可以快速進行本地和分佈式訓練。在GL中運行以上模型,只需要一鍵執行python腳本。
比如我們要執行一個二部圖的GraphSAGE算法,樣例數據已經準備好了,在examples/data目錄下,運行腳本就可以得到數據,模型訓練可直接調用如下腳本。
cd examples/tf/bipartite_graphsage
python train_unsupervised.py
我們也將模型進行了抽象,包括Layers、Aggregators的複用,方便用戶搭建自定義不同的圖學習模型。GNNs是近年來圖數據分析與應用的熱點研究問題,學術屆和工業界都在不斷提出新的模型,我們也將繼續探索,不斷完善模型,並提升大規模下的性能。
總結和展望
GL上的GNNs模型正在快速發展迭代,我們也將把更多在阿里的大規模業務上經過驗證的GNNs模型開源出來,歡迎大家加入到GL的共建中。
項目地址:https://github.com/alibaba/graph-learn
歡迎感興趣的同學加入我們的團隊 [email protected] 。