如何判斷多賬號是同一個人?用圖技術搞定 ID Mapping

NebulaGraph發表於2023-03-08

基於圖資料庫 NebulaGraph 的 ID Resolution 方法與程式碼示例

原文出處:https://discuss.nebula-graph.com.cn/t/topic/11873

本文是一個基於圖資料庫 NebulaGraph 上的圖演算法、圖資料庫、圖神經網路的 ID-Mapping 方法綜述,除了基本方法思想的介紹之外,我還給大家弄了可以跑的 Playground。

基於圖資料庫的使用者 ID 識別方法使用者

ID 識別,是一個很常見的圖技術應用場景,在不同的語境下它可能還被叫做 Entity Correlation(實體關聯)、Entity Linking(實體連結)、ID Mapping(身份對映)等等。ID 識別解決的問題是找出相同的使用者在同一個系統或者不同系統中的不同賬號

由於 ID 識別天然地是一個關聯關係問題,也是一個典型的圖、圖資料庫應用場景。

建立圖譜

圖建模

我們從一個最簡單、直接的圖譜開始,如下邊的圖結構示意顯示,我們定義了點:

  • user

    • Prop: [name, email, birthday, address, phone_num]
  • phone
  • email
  • device
  • ip
  • address

在他們之間有很自然的邊:

  • used_device

    • Prop: time
  • logged_in_from

    • Prop: time
  • has_phone
  • has_address
  • has_email

建立圖譜

資料集

這份資料是開源的,地址在 https://github.com/wey-gu/identity-correlation-datagen

資料寫入

資料寫入我們用一行部署圖資料庫服務的 nebula-up:https://github.com/wey-gu/nebula-up/

curl -fsSL nebula-up.siwei.io/install.sh | bash

圖建模的 Schema 對應的 NebulaGraph DDL 是:

# 建立一個叫做 entity_resolution 的圖空間
CREATE SPACE entity_resolution (vid_type=FIXED_STRING(30));
USE entity_resolution;

# 建立點的型別 TAG

CREATE TAG `user` (`name` string NOT NULL, `email` string NOT NULL, `phone_num` string NOT NULL, `birthday` date NOT NULL, `address` string NOT NULL);

CREATE TAG `address` (`address` string NOT NULL);
CREATE TAG `device` (`uuid` string NOT NULL);
CREATE TAG `email` ();
CREATE TAG `ip` ();
CREATE TAG `phone` ();

# 建立邊的型別 Edge Type

CREATE EDGE `used_device` (`time` timestamp NOT NULL);
CREATE EDGE `logged_in_from` (`time` timestamp NOT NULL);
CREATE EDGE `has_phone` ();
CREATE EDGE `has_address` ();
CREATE EDGE `has_email` ();

對於寫入資料的 DML,這裡只給出 useremail 型別點、has_email 型別邊的例子:

INSERT VERTEX `user` (`email`, `name`, `birthday`, `address`, `phone_num`) VALUES
    "user_1":("heathermoore@johnson.com","Miranda Miller",date("1957-08-27"),"Brittany Forge Apt. 718 East Eric  WV 97881","+1-652-450-5443x00562"),
    "user_2":("holly@welch.org","Holly Pollard",date("1990-10-19"),"1 Amanda Freeway Lisaland  NJ 94933","600-192-2985x041"),
    "user_3":("julia.h.24@gmail.com","Julia Hall",date("1927-08-24"),"Rodriguez Track East Connorfort  NC 63144","1248361783"),
    "user_4":("franklin.b@gibson.biz","Franklin Barnett",date("2020-03-01"),"Richard Curve Kingstad  AZ 05660","(224)497-9312"),
    "user_5":("4kelly@yahoo.com","April Kelly",date("1967-12-01"),"Schmidt Key Lake Charles  AL 36174","410.138.1816x98702"),
    "user_6":("steven.web@johnson.com","Steven Webb",date("1955-04-24"),"5 Joanna Key Suite 704 Frankshire  OK 03035","3666519376"),
    "user_7":("Jessica_Torres@morris.com","Jessica Torres",date("1958-09-03"),"1 Payne Circle Mitchellfort  LA 73053","535-357-3112x4903"),
    "user_8":("brettglenn@gmail.com","Brett Glenn",date("1992-09-03"),"Weber Unions Eddieland  MT 64619","660.391.3730"),
    "user_9":("veronica.j@yahoo.com","Veronica Jordan",date("1947-06-08"),"2 Klein Mission New Annetteton  HI 05775","810-252-6218"),
    "user_10":("steven@phelps-craig.info","Steven Brooks",date("1954-06-14"),"1 Vanessa Stravenue Suite 184 Baileyville  NY 46381","+1-665-328-8103x3448"),
    "user_11":("ReginaldTheMan@hotmail.com","Reginald Mccullough",date("1915-04-12"),"John Garden Port John  LA 54602","030.088.4523x94511"),
    "user_12":("Jennifer.f@carroll-acosta.com","Jennifer Foster",date("1988-04-30"),"11 Webb Groves Tiffanyside  MN 14566","(489)306-8558x98227"),
    "user_13":("Philip66@yahoo.com","Philip Garcia",date("1955-12-01"),"70 Robinson Locks Suite 113 East Veronica  ND 87845","490-088-7610x9437"),
    "user_14":("Ann@hernandez.com","Ann Williams",date("1947-05-28"),"24 Mcknight Port Apt. 028 Sarahborough  MD 38195","868.057.4056x4814"),
    "user_15":("Jessica@turner.com","Jessica Stewart",date("1951-11-28"),"0337 Mason Corner Apt. 900 Toddmouth  FL 61464","(335)408-3835x883"),
    "user_16":("Sandra311@hotmail.com","Sandra Dougherty",date("1908-06-03"),"7 Davis Station Apt. 691 Pittmanfort  HI 29746","+1-189-827-0744x27614"),
    "user_17":("Sharon91@gmail.com","Sharon Mccoy",date("1958-09-01"),"1 Southport Street Apt. 098 Westport  KY 85907","(814)898-9079x898"),
    "user_18":("Sharon91+001@gmail.com","Kathryn Miller",date("1958-09-01"),"1 Southport Street Apt. 098 Westport  KY 85907","(814)898-9079x898"),
    "user_19":("brettglenn@googlemail.com","Bretty Glenn",date("1991-09-03"),"Weber Unions Eddieland  MT 64619","660-391-3730"),
    "user_20":("julia.h.24@yahoo.com","Julia H.",date("1927-08-24"),"Rodriguez Track East Connorfort NC 63144","1248361783"),
    "user_21":("holly@welch.org","Holly",date("0000-10-19"),"1 Amanda Freeway Lisaland  NJ 94933","(600)-192-2985"),
    "user_22":("veronica.j@yahoo.com","Veronica Jordan",date("0000-06-08"),"2 Klein HI 05775","(810)-252-6218"),
    "user_23":("4kelly@hotmail.com","Kelly April",date("2010-01-01"),"Schmidt Key Lake Charles AL 13617","410-138-1816");

INSERT VERTEX `email` () VALUES
    "heathermoore@johnson.com":(),
    "holly@welch.org":(),
    "julia.h.24@gmail.com":(),
    "franklin.b@gibson.biz":(),
    "4kelly@yahoo.com":(),
    "steven.web@johnson.com":(),
    "Jessica_Torres@morris.com":(),
    "brettglenn@gmail.com":(),
    "veronica.j@yahoo.com":(),
    "steven@phelps-craig.info":(),
    "ReginaldTheMan@hotmail.com":(),
    "Jennifer.f@carroll-acosta.com":(),
    "Philip66@yahoo.com":(),
    "Ann@hernandez.com":(),
    "Jessica@turner.com":(),
    "Sandra311@hotmail.com":(),
    "Sharon91@gmail.com":(),
    "Sharon91+001@gmail.com":(),
    "brettglenn@googlemail.com":(),
    "julia.h.24@yahoo.com":(),
    "holly@welch.org":(),
    "veronica.j@yahoo.com":(),
    "4kelly@hotmail.com":();

INSERT VERTEX `ip` () VALUES
    "202.123.513.12":(),
    "202.41.23.11":(),
    "143.1.23.4":(),
    "143.1.23.12":(),
    "153.42.2.8":(),
    "9.1.4.1":();

INSERT VERTEX `device`(`uuid`) VALUES
    "device_0":("2a8e791d-0183-4df2-aa36-5ac82151be93"),
    "device_1":("f9be6a11-f74b-45f5-a9ea-bb3af5a868a2"),
    "device_2":("ae083379-91f5-4cd3-b2b3-273960979dab"),
    "device_3":("c0981d43-1e59-4cd5-a1e1-e88cd9e792a5"),
    "device_4":("e730dd8a-fcd3-47b4-be4a-0190610e6f02");


INSERT EDGE `has_email` () VALUES
    "user_1"->"heathermoore@johnson.com":(),
    "user_2"->"holly@welch.org":(),
    "user_3"->"julia.h.24@gmail.com":(),
    "user_4"->"franklin.b@gibson.biz":(),
    "user_5"->"4kelly@yahoo.com":(),
    "user_6"->"steven.web@johnson.com":(),
    "user_7"->"Jessica_Torres@morris.com":(),
    "user_8"->"brettglenn@gmail.com":(),
    "user_9"->"veronica.j@yahoo.com":(),
    "user_10"->"steven@phelps-craig.info":(),
    "user_11"->"ReginaldTheMan@hotmail.com":(),
    "user_12"->"Jennifer.f@carroll-acosta.com":(),
    "user_13"->"Philip66@yahoo.com":(),
    "user_14"->"Ann@hernandez.com":(),
    "user_15"->"Jessica@turner.com":(),
    "user_16"->"Sandra311@hotmail.com":(),
    "user_17"->"Sharon91@gmail.com":(),
    "user_18"->"Sharon91+001@gmail.com":(),
    "user_19"->"brettglenn@googlemail.com":(),
    "user_20"->"julia.h.24@yahoo.com":(),
    "user_21"->"holly@welch.org":(),
    "user_22"->"veronica.j@yahoo.com":(),
    "user_23"->"4kelly@hotmail.com":();

INSERT EDGE `used_device` (`time`) VALUES
    "user_2"->"device_0":(timestamp("2021-03-01T08:00:00")),
    "user_21"->"device_0":(timestamp("2021-03-01T08:01:00")),
    "user_18"->"device_1":(timestamp("2021-03-01T08:02:00")),
    "user_17"->"device_1":(timestamp("2021-03-01T08:03:00")),
    "user_22"->"device_2":(timestamp("2021-03-01T08:04:00")),
    "user_9"->"device_3":(timestamp("2021-03-01T08:05:00")),
    "user_9"->"device_2":(timestamp("2021-03-01T08:06:00")),
    "user_23"->"device_4":(timestamp("2021-03-01T08:07:00"));

INSERT EDGE `logged_in_from` (`time`) VALUES
    "user_2"->"202.123.513.12":(timestamp("2021-03-01T08:00:00")),
    "user_21"->"202.41.23.11":(timestamp("2021-03-01T08:01:00")),
    "user_18"->"143.1.23.4":(timestamp("2021-03-01T08:02:00")),
    "user_17"->"143.1.23.12":(timestamp("2021-03-01T08:03:00")),
    "user_22"->"153.42.2.8":(timestamp("2021-03-01T08:04:00")),
    "user_9"->"153.42.2.8":(timestamp("2021-03-01T08:05:00")),
    "user_9"->"153.42.2.8":(timestamp("2021-03-01T08:06:00")),
    "user_23"->"9.1.4.1":(timestamp("2021-03-01T08:07:00"));

根據確定規則獲取 ID 對映關係

這種方式是最簡單、直接的方法,在特定的場景下也可能是有用的。試想象 email、IP 地址、上網裝置這些有嚴格的結構的資料,在它們成為圖譜中的點的時候,簡單的相等關係就足以找出這樣對應關係,比如:

  • 擁有相同的 email
  • 使用過相同的 IP 地址
  • 使用過相同的裝置

在前面的圖譜、圖資料庫中,擁有相同的 email 可以直接表達為如下的圖模式(Graph Pattern)。

(:user)-[:has_email]->(:email)<-[:has_email]-[:user]

下圖為頂點 user 與邊 has_email 的一個圖的視覺化結果,可以看到這其中有兩個三個點相連的串正是符合擁有相同 email 的模式的點。

根據確定規則獲取 ID 對映關係

這個結果的資料來源在 https://github.com/wey-gu/identity-correlation-datagen/tree/main/sample/hand_crafted。如果透過線上訪問原文,你可以滑鼠懸停(獲取點上的屬性)和框選放大每一個點和子圖哦。

在構建 ID Mapping 系統的過程中,我們透過圖資料庫直接查詢,視覺化渲染結果來看到等效的洞察。這個查詢可以寫成:

MATCH p=(:user)-[:has_email]->(:email)<-[:has_email]-(:user)
RETURN p limit 10

視覺化圖探索工具 NebulaGraph Studio 中的查詢結果:

根據確定規則獲取 ID 對映關係

同樣,在上面互動圖中可以放大看到這兩對擁有相同 email 關聯起來的賬號:

根據確定規則獲取 ID 對映關係

然而,在更多真實世界中,這樣的模式匹配往往不能解決更多稍微複雜一點的情形:

比如,從上邊的圖中我們可以看到這兩個匹配了的對映中,holly@welch.org 關聯下的兩個使用者的姓名是不同的,而 veronica.j@yahoo.com 關聯下的兩個使用者姓名是完全相同的。

user_2,holly@welch.org,Holly Pollard,1990-10-19,1 Amanda Freeway Lisaland  NJ 94933,600-192-2985x041
user_21,holly@welch.org,Holly,0000-10-19,1 Amanda Freeway Lisaland  NJ 94933,(600)-192-2985

再比如 Sharon91@gmail.comSharon91+001@gmail.com ,這兩個人的姓名不同,但是手機和地址卻是相同的。

user_17,Sharon91@gmail.com,Sharon Mccoy,1958-09-01,1 Southport Street Apt. 098 Westport  KY 85907,(814)898-9079x898
user_18,Sharon91+001@gmail.com,Kathryn Miller,1958-09-01,1 Southport Street Apt. 098 Westport  KY 85907,(814)898-9079x898

比較慶幸的是,我們只需要增加類似於“擁有相同郵箱”、“擁有相同地址”、“擁有相同電話”等其他條件就可以把這種情況考慮進來了,而隨之而來的問題是:

  • 不是所有的資料都至少存在某一個確定條件的相等(二元的是與否),所以不存在一條確定的邊去連線它們,比如這兩個賬戶中:
user_5,4kelly@yahoo.com,April Kelly,1967-12-01,Schmidt Key Lake Charles AL 36174,410.138.1816x98702
user_23,4kelly@hotmail.com,Kelly April,2010-01-01,Schmidt Key Lake Charles AL 13617,410-138-1816

非確定規則基於複合條件量化方法

前面提到了幾種確定規則無法處理的情況,它們可以歸結為這兩點:

  1. 需要多因素(規則)進行綜合考慮與判定
  2. 需要對非確定條件(屬性)進行處理,挖掘隱含相等、相似的關聯關係(邊)

對於 1. ,很自然可以想到對多種關聯條件進行量化評分 score,按照多種條件的重要程度進行加權,給出認定為關聯的總分的閾值。

有了多因素評分的機制,我們只需要考慮如何在確定的多因素基礎之上,增加對不確定因素的處理,從而解決 2. 的情況。這裡,非確定的條件可能是:

a. 表現結構化資料的相似性:Sharon91@gmail.comSharon91+001@gmail.com

b. 表現非結構化資料的相似性:

  • Schmidt Key Lake Charles AL 36174Schmidt Key Lake Charles AL 13617
  • 600-192-2985x041(600)-192-2985

對於 a. 的結構化資料中的相似性,有兩個思路是可以考慮的:

  • 直接進行兩個值的相似度

    • 直接判定子字串
    • 運算 Jaccard Index 等類似的相似度演算法
  • 拆分為更細粒的多個屬性

    • 將 email foo+num@bar.com 拆分成三個子屬性 email_handle: foo, email_alias: num, email_domain: bar.com,基於此可以設計詳細的確定性規則:email.handle 相等,甚至再在此基礎上應用其他非確定規則;有時候,比如對於 email_domain 欄位,我們還知道 gmail.com 和 googlemail.com 是等價的,這裡的處理也是可以考慮的。像是 user_19,brettglenn@googlemail.comuser_8,brettglenn@gmail.com,但從郵箱判斷背後就是同一個持有者。

而對於 b. 的非結構屬性相似性距離,處理方式可以根據具體的 domain knowledge 千差萬別:

Schmidt Key Lake Charles AL 36174Schmidt Key Lake Charles AL 13617 的地址資訊,除了可以用值的相似度之外,還可以把它轉換成地理型別的屬性,比如一個經緯度組成的點,從而計算兩個點之間的地理距離,根據給定的距離值來打分。

→→ 偷偷告訴你:NebulaGraph 圖資料庫中原生支援地理型別的屬性與索引,可以直接建立 Point 型別的地理屬性,並計算兩個 Point 之間的距離。

  • 對於 600-192-2985x041(600)-192-2985 這種字串形式的電話號碼,則可以統一轉化為<國家碼> + <區域碼> + <本地號碼> + <分機號>這樣的結構化資料,進一步按照結構化資料的方式處理。
  • 如果賬號存在圖片物件 URL,可以對比其檔案相似度。

另外,對於非結構屬性的相似性計算我們要儘量避免兩兩窮舉運算的方式(笛卡爾積),因為這是一個指數增長的量級,一個可行的方法是隻比較建立了確定性關係(比如相同郵件字首:email_handle,地址在相同街區,IP 在同一個網段等)的實體。

小結

總結來看,為了解決真實世界資料的複雜情形,基於複合條件的量化方法有:

  • 透過細化結構資料(比如郵箱欄位拆分為子屬性或者點)、或者轉變為結構化資料(處理字串形式的電話號碼)建立相似結構化資料之間的確定關聯;
  • 在有限存在確定性關聯的點之間(避免兩兩窮舉),運算其他量化、非確定相似性(字元距離、地理距離等、圖片檔案相似度);
  • 為不同關係賦予加權,計算相似度總分;

基於複合條件量化方法實操

下面,我們來給出這系列方法的實操案例。

  1. 細化結構資料

透過細化結構資料(比如郵箱欄位拆分為子屬性或者點)、或者轉變為結構化資料(處理字串形式的電話號碼)建立相似結構化資料之間的確定關聯;

首先,我們把 email 的點拆成字首 email_handle 與字尾 email_domain,自然地,會產生這樣的邊:

  • has_email_with_handle (user -> email_handle)
  • has_email_with_domain (user -> email_domain)
  • with_handle (email -> email_handle)
  • with_domain (email -> email_domain)

可以預見 email_domain 是一個潛在的超級節點,並且,它的區分度在很多情況下是很小的,比如 gmail.com 這個公共郵箱字尾沒有很大的關聯性意義。我們可以只留下 email.handle 作為點,而對於 email_domain,把它留在邊中作為屬性:

  • has_email_with_handle (user -> email_handle)

    • Prop:

      • email_domain
  • with_handle (email -> email_handle)

    • Prop:

      • email_domain

對應的新的點型別、邊型別的 NebulaGraph DDL 語句:

# 新的點型別
CREATE TAG `email_handle` ();

# 新的邊型別
CREATE EDGE `has_email_with_handle` (`email_domain` string NOT NULL);
CREATE EDGE `with_handle` (`email_domain` string NOT NULL);

對應新的點、邊的 DML 語句:

INSERT VERTEX `email_handle` () VALUES
    "4kelly":(),
    "Ann":(),
    "brettglenn":(),
    "franklin.b":(),
    "heathermoore":(),
    "holly":(),
    "Jennifer.f":(),
    "Jessica":(),
    "Jessica_Torres":(),
    "julia.h.24":(),
    "Philip66":(),
    "ReginaldTheMan":(),
    "Sandra311":(),
    "Sharon91":(),
    "steven":(),
    "steven.web":(),
    "veronica.j":();

INSERT EDGE `has_email_with_handle` (`email_domain`) VALUES
    "user_1"->"heathermoore":("johnson.com"),
    "user_2"->"holly":("welch.org"),
    "user_3"->"julia.h.24":("gmail.com"),
    "user_4"->"franklin.b":("gibson.biz"),
    "user_5"->"4kelly":("yahoo.com"),
    "user_6"->"steven.web":("johnson.com"),
    "user_7"->"Jessica_Torres":("morris.com"),
    "user_8"->"brettglenn":("gmail.com"),
    "user_9"->"veronica.j":("yahoo.com"),
    "user_10"->"steven":("phelps-craig.info"),
    "user_11"->"ReginaldTheMan":("hotmail.com"),
    "user_12"->"Jennifer.f":("carroll-acosta.com"),
    "user_13"->"Philip66":("yahoo.com"),
    "user_14"->"Ann":("hernandez.com"),
    "user_15"->"Jessica":("turner.com"),
    "user_16"->"Sandra311":("hotmail.com"),
    "user_17"->"Sharon91":("gmail.com"),
    "user_18"->"Sharon91":("gmail.com"),
    "user_19"->"brettglenn":("googlemail.com"),
    "user_20"->"julia.h.24":("yahoo.com"),
    "user_21"->"holly":("welch.org"),
    "user_22"->"veronica.j":("yahoo.com"),
    "user_23"->"4kelly":("hotmail.com");

INSERT EDGE `with_handle` (`email_domain`) VALUES
    "heathermoore@johnson.com"->"heathermoore":("johnson.com"),
    "holly@welch.org"->"holly":("welch.org"),
    "julia.h.24@gmail.com"->"julia.h.24":("gmail.com"),
    "franklin.b@gibson.biz"->"franklin.b":("gibson.biz"),
    "4kelly@yahoo.com"->"4kelly":("yahoo.com"),
    "steven.web@johnson.com"->"steven.web":("johnson.com"),
    "Jessica_Torres@morris.com"->"Jessica_Torres":("morris.com"),
    "brettglenn@gmail.com"->"brettglenn":("gmail.com"),
    "veronica.j@yahoo.com"->"veronica.j":("yahoo.com"),
    "steven@phelps-craig.info"->"steven":("phelps-craig.info"),
    "ReginaldTheMan@hotmail.com"->"ReginaldTheMan":("hotmail.com"),
    "Jennifer.f@carroll-acosta.com"->"Jennifer.f":("carroll-acosta.com"),
    "Philip66@yahoo.com"->"Philip66":("yahoo.com"),
    "Ann@hernandez.com"->"Ann":("hernandez.com"),
    "Jessica@turner.com"->"Jessica":("turner.com"),
    "Sandra311@hotmail.com"->"Sandra311":("hotmail.com"),
    "Sharon91@gmail.com"->"Sharon91":("gmail.com"),
    "Sharon91+001@gmail.com"->"Sharon91":("gmail.com"),
    "brettglenn@googlemail.com"->"brettglenn":("googlemail.com"),
    "julia.h.24@yahoo.com"->"julia.h.24":("yahoo.com"),
    "holly@welch.org"->"holly":("welch.org"),
    "veronica.j@yahoo.com"->"veronica.j":("yahoo.com"),
    "4kelly@hotmail.com"->"4kelly":("hotmail.com");

可以看到,經過這個處理,我們已經得到更多關聯的使用者了,它可以用這個圖查詢表達:

MATCH p=(:user)-[:has_email_with_handle]->(:email_handle)<-[:has_email_with_handle]-(:user)
RETURN p limit 10

基於複合條件量化方法實操

  1. 非確定性相似性

在有限存在確定性關聯的點之間(避免兩兩窮舉),運算其他量化、非確定相似性(字元距離、地理距離等、圖片檔案相似度)來判斷是否是一個 ID。

這裡用地址的地理距離來做為例子,我們預先處理每一個地址,將它們的經緯度匯入圖譜。

我們需要更改地址這個點的型別 address 的 schema:

  • address

    • Prop: geo_point(geography(point) 經緯度型別)
      對應過來,它的 DDL 變化是:
-CREATE TAG `address` ()
+CREATE TAG `address`(`geo_point` geography(point));

在已經建立了初始的 address TAG 之上,可以用 ALTER TAG 的 DDL 去修改 address 的定義:

ALTER TAG `address` ADD (`geo_point` geography(point));

可以用 SHOW CREATE TAG 檢視修改之後的 Schema

(root@nebula) [entity_resolution]> SHOW CREATE TAG `address`
+-----------+------------------------------------+
| Tag       | Create Tag                         |
+-----------+------------------------------------+
| "address" | "CREATE TAG `address` (            |
|           |  `address` string NOT NULL,        |
|           |  `geo_point` geography(point) NULL |
|           | ) ttl_duration = 0, ttl_col = """  |
+-----------+------------------------------------+

對應的點、邊的 DML:

# 插入邊
INSERT EDGE `has_address` () VALUES
    "user_1"->"addr_0":(),
    "user_2"->"addr_15":(),
    "user_3"->"addr_18":(),
    "user_4"->"addr_1":(),
    "user_5"->"addr_2":(),
    "user_6"->"addr_3":(),
    "user_7"->"addr_4":(),
    "user_8"->"addr_14":(),
    "user_9"->"addr_5":(),
    "user_10"->"addr_6":(),
    "user_11"->"addr_7":(),
    "user_12"->"addr_8":(),
    "user_13"->"addr_9":(),
    "user_14"->"addr_10":(),
    "user_15"->"addr_11":(),
    "user_16"->"addr_12":(),
    "user_17"->"addr_13":(),
    "user_18"->"addr_13":(),
    "user_19"->"addr_14":(),
    "user_20"->"addr_18":(),
    "user_21"->"addr_15":(),
    "user_22"->"addr_16":(),
    "user_23"->"addr_17":();

# 插入點,geo_point 是地址的經緯度
INSERT VERTEX `address` (`address`, `geo_point`) VALUES
    "addr_0":("Brittany Forge Apt. 718 East Eric  WV 97881", ST_Point(1,2)),
    "addr_1":("Richard Curve Kingstad  AZ 05660", ST_Point(3,4)),
    "addr_2":("Schmidt Key Lake Charles  AL 36174", ST_Point(13.13,-87.65)),
    "addr_3":("5 Joanna Key Suite 704 Frankshire  OK 03035", ST_Point(5,6)),
    "addr_4":("1 Payne Circle Mitchellfort  LA 73053", ST_Point(7,8)),
    "addr_5":("2 Klein Mission New Annetteton  HI 05775", ST_Point(9,10)),
    "addr_6":("1 Vanessa Stravenue Suite 184 Baileyville  NY 46381", ST_Point(11,12)),
    "addr_7":("John Garden Port John  LA 54602", ST_Point(13,14)),
    "addr_8":("11 Webb Groves Tiffanyside  MN 14566", ST_Point(15,16)),
    "addr_9":("70 Robinson Locks Suite 113 East Veronica  ND 87845", ST_Point(17,18)),
    "addr_10":("24 Mcknight Port Apt. 028 Sarahborough  MD 38195", ST_Point(19,20)),
    "addr_11":("0337 Mason Corner Apt. 900 Toddmouth  FL 61464", ST_Point(21,22)),
    "addr_12":("7 Davis Station Apt. 691 Pittmanfort  HI 29746", ST_Point(23,24)),
    "addr_13":("1 Southport Street Apt. 098 Westport  KY 85907", ST_Point(120.12,30.16)),
    "addr_14":("Weber Unions Eddieland  MT 64619", ST_Point(25,26)),
    "addr_15":("1 Amanda Freeway Lisaland  NJ 94933", ST_Point(27,28)),
    "addr_16":("2 Klein HI 05775", ST_Point(9,10)),
    "addr_17":("Schmidt Key Lake Charles AL 13617", ST_Point(13.12, -87.60)),
    "addr_18":("Rodriguez Track East Connorfort  NC 63144", ST_Point(29,30));

有了經緯度資訊,結合 NebulaGraph 對於 Geo Spatial 空間地理屬性的原生處理能力,我們可以輕鬆獲得兩個點之間的距離(單位:米)

如下,ST_Distance(ST_Point(13.13, -87.65),ST_Point(13.12, -87.60)) 表示兩個地球上的點 ST_Point(13.13, -87.65)ST_Point(13.12, -87.60) 之間的距離是 5559.9459840993895 米。

RETURN ST_Distance(ST_Point(13.13, -87.65),ST_Point(13.12, -87.60)) AS distance;
+--------------------+
| distance           |
+--------------------+
| 5559.9459840993895 |
+--------------------+

那麼,我們可以用查詢語句來表達“所有擁有相同郵箱字首使用者之間的距離”:

MATCH (v_start:user)-[:has_email_with_handle]->(:email_handle)<-[:has_email_with_handle]-(v_end:user)
MATCH (v_start:user)-[:has_address]->(a_start:address)
MATCH (v_end:user)-[:has_address]->(a_end:address)
RETURN v_start, v_end, ST_Distance(a_start.address.geo_point, a_end.address.geo_point) AS distance, a_start, a_end;

這裡,為了展現出針對“非確定性”條件之間的“相似性”,我們可以把地址中字串完全相同的結果過濾掉,WHERE a_start.address.address != a_end.address.address,如此:

MATCH (v_start:user)-[:has_email_with_handle]->(:email_handle)<-[:has_email_with_handle]-(v_end:user)
MATCH (v_start:user)-[:has_address]->(a_start:address)
MATCH (v_end:user)-[:has_address]->(a_end:address)
WHERE a_start.address.address != a_end.address.address
RETURN v_start.`user`.name, v_end.`user`.name, ST_Distance(a_start.address.geo_point, a_end.address.geo_point) AS distance, a_start.address.address, a_end.address.address

它的結果是:

+-------------------+-------------------+--------------------+--------------------------------------------+--------------------------------------------+
| v_start.user.name | v_end.user.name   | distance           | a_start.address.address                    | a_end.address.address                      |
+-------------------+-------------------+--------------------+--------------------------------------------+--------------------------------------------+
| "April Kelly"     | "Kelly April"     | 5559.9459840993895 | "Schmidt Key Lake Charles  AL 36174"       | "Schmidt Key Lake Charles AL 13617"        |
| "Veronica Jordan" | "Veronica Jordan" | 0.0                | "2 Klein Mission New Annetteton  HI 05775" | "2 Klein HI 05775"                         |
| "Kelly April"     | "April Kelly"     | 5559.9459840993895 | "Schmidt Key Lake Charles AL 13617"        | "Schmidt Key Lake Charles  AL 36174"       |
| "Veronica Jordan" | "Veronica Jordan" | 0.0                | "2 Klein HI 05775"                         | "2 Klein Mission New Annetteton  HI 05775" |
+-------------------+-------------------+--------------------+--------------------------------------------+--------------------------------------------+

可以看出:

  • user_5user_23 之間的地址距離只相差 5559 米,因為他們的地址就在一個街區
  • user_9user_13 之間距離相差 0 米,因為它們(“2 Klein Mission New Annetteton HI 05775” 與 “2 Klein HI 05775”)實際上是完全相同的地址。

這就是利用屬性的具體含義(domain knowledge)計算的實質距離的一個最好的詮釋,大家可以藉助於圖資料庫中查詢語句描述能力或者利用其他系統去運算使用者間非確定性特徵的量化距離/相似度。

  1. 加權評分

為不同關係賦予加權,計算相似度總分;

下邊是一個在實際應用中,可以綜合考量的多種關聯關係,包括但不限於:

確定性關係

  • 同名(精確匹配)
  • 相同電話(格式化處理)
  • 使用過相同裝置(精確匹配)
  • 同郵件字首(精細化處理)

非確定性

  • 地址距離(處理成經緯度,計算地球球面距離)
  • 頭像圖片背景相似度(訓練模型計算影像距離)

一個很符合直覺的方法就是將多種條件按照不同的權重加權,獲得兩點間的總“疑似相同賬號”的評分。

本例中,為求簡潔,我們只給出考慮“同郵件字首”、“同名”與“地理距離小於 10KM”的綜合加權,並且認為兩個因素的權重都是 1。

注,為了防止兩兩全匹配,我們從相同郵件字首條件作為初始匹配條件。

MATCH (v_start:user)-[:has_email_with_handle]->(:email_handle)<-[:has_email_with_handle]-(v_end:user)
MATCH (v_start:user)-[:has_address]->(a_start:address)
MATCH (v_end:user)-[:has_address]->(a_end:address)
WITH id(v_start) AS s, id(v_end) AS e, v_start.`user`.name AS s_name, v_end.`user`.name AS e_name, ST_Distance(a_start.address.geo_point, a_end.address.geo_point) AS distance
RETURN s, e, 1 AS shared_email_handle, s_name == e_name AS shared_name, distance < 10000 AS shared_location

結果是

+-----------+-----------+---------------------+-------------+-----------------+
| s         | e         | shared_email_handle | shared_name | shared_location |
+-----------+-----------+---------------------+-------------+-----------------+
| "user_5"  | "user_23" | 1                   | false       | true            |
| "user_9"  | "user_22" | 1                   | true        | true            |
| "user_21" | "user_2"  | 1                   | false       | true            |
| "user_2"  | "user_21" | 1                   | false       | true            |
| "user_22" | "user_9"  | 1                   | true        | true            |
| "user_20" | "user_3"  | 1                   | false       | true            |
| "user_3"  | "user_20" | 1                   | false       | true            |
| "user_18" | "user_17" | 1                   | false       | true            |
| "user_17" | "user_18" | 1                   | false       | true            |
| "user_19" | "user_8"  | 1                   | false       | true            |
| "user_8"  | "user_19" | 1                   | false       | true            |
| "user_23" | "user_5"  | 1                   | false       | true            |
+-----------+-----------+---------------------+-------------+-----------------+

然後,我們計算加權分數:

MATCH (v_start:user)-[:has_email_with_handle]->(:email_handle)<-[:has_email_with_handle]-(v_end:user)
MATCH (v_start:user)-[:has_address]->(a_start:address)
MATCH (v_end:user)-[:has_address]->(a_end:address)
WITH id(v_start) AS s, id(v_end) AS e, v_start.`user`.name AS s_name, v_end.`user`.name AS e_name, ST_Distance(a_start.address.geo_point, a_end.address.geo_point) AS distance
WITH s, e, 1 AS shared_email_handle, CASE WHEN s_name == e_name THEN 1 ELSE 0 END AS shared_name, CASE WHEN distance < 10000 THEN 1 ELSE 0 END AS shared_location
RETURN s, e, (shared_email_handle + shared_name + shared_location) AS score
ORDER BY score DESC

結果是

+-----------+-----------+-------+
| s         | e         | score |
+-----------+-----------+-------+
| "user_9"  | "user_22" | 3     |
| "user_22" | "user_9"  | 3     |
| "user_5"  | "user_23" | 2     |
| "user_21" | "user_2"  | 2     |
| "user_2"  | "user_21" | 2     |
| "user_20" | "user_3"  | 2     |
| "user_3"  | "user_20" | 2     |
| "user_18" | "user_17" | 2     |
| "user_17" | "user_18" | 2     |
| "user_19" | "user_8"  | 2     |
| "user_8"  | "user_19" | 2     |
| "user_23" | "user_5"  | 2     |
+-----------+-----------+-------+

利用 Active Learning 的方法互動式學習評分權重

實際應用中,不同因素的加權關係也不是那麼容易給出的,我們可以利用有限的人力判斷進行 Active Learning 的互動訓練來習得權重。

利用新的邊連線不同方法

進一步,對於這些確定(是否二元的)或非確定(量化的)關係,利用相簿與外部系統獲得了關聯關係之後,常常可以直接把它們定義為圖譜中直連的邊,寫回相簿,提供給其他演算法、系統作為輸入,做進一步迭代、計算。

建立單獨的直連邊

假設之前對郵件、地址、姓名的處理之後,把結果作為使用者實體之前的直連邊插入圖譜,這些種邊叫做:

  • shared_similar_email
  • shared_similar_location
  • shared_name
# DDL
CREATE EDGE `shared_similar_email` ();
CREATE EDGE `shared_similar_location` ();
CREATE EDGE `shared_name` ();
# DML

INSERT EDGE `shared_similar_email` () VALUES
    "user_5" ->"user_23":(),
    "user_9" ->"user_22":(),
    "user_21"->"user_2" :(),
    "user_2" ->"user_21":(),
    "user_22"->"user_9" :(),
    "user_20"->"user_3" :(),
    "user_3" ->"user_20":(),
    "user_18"->"user_17":(),
    "user_17"->"user_18":(),
    "user_19"->"user_8" :(),
    "user_8" ->"user_19":(),
    "user_23"->"user_5" :();

INSERT EDGE `shared_name` () VALUES
    "user_9" ->"user_22":(),
    "user_22"->"user_9" :();

INSERT EDGE `shared_similar_location` () VALUES
    "user_5" ->"user_23":(),
    "user_9" ->"user_22":(),
    "user_21"->"user_2" :(),
    "user_2" ->"user_21":(),
    "user_22"->"user_9" :(),
    "user_20"->"user_3" :(),
    "user_3" ->"user_20":(),
    "user_18"->"user_17":(),
    "user_17"->"user_18":(),
    "user_19"->"user_8" :(),
    "user_8" ->"user_19":(),
    "user_23"->"user_5" :();

建立複合評分之後的邊

比如,我們查詢綜合分數大於 2 的點:

MATCH (v_start:user)-[:has_email_with_handle]->(:email_handle)<-[:has_email_with_handle]-(v_end:user)
MATCH (v_start:user)-[:has_address]->(a_start:address)
MATCH (v_end:user)-[:has_address]->(a_end:address)
WITH id(v_start) AS s, id(v_end) AS e, v_start.`user`.name AS s_name, v_end.`user`.name AS e_name, ST_Distance(a_start.address.geo_point, a_end.address.geo_point) AS distance
WITH s, e, 1 AS shared_email_handle, CASE WHEN s_name == e_name THEN 1 ELSE 0 END AS shared_name, CASE WHEN distance < 10000 THEN 1 ELSE 0 END AS shared_location
WITH s, e, (shared_email_handle + shared_name + shared_location) AS score
WHERE score > 2
RETURN s, e, score
ORDER BY score DESC

然後根據返回結果建立新的邊:

# DDL
CREATE EDGE `is_similar_to` (score int NOT NULL);

# DML
INSERT EDGE `is_similar_to` (`score`) VALUES
    "user_22" ->"user_9":(3),
    "user_9" ->"user_22":(3);

基於圖演算法的方法

前面的方法中我們直接利用了使用者的各項屬性、行為事件中產生的關係,並利用各種屬性、值相似度的方法建立了基於機率或者帶有評分的關聯關係。而在透過其他方法增加了新的邊之後的圖上,我們也可以利用圖演算法的方法來對映潛在的相同使用者 ID。

圖相似性演算法

利用節點相似性圖演算法,比如 Jaccard Index、餘弦相似度等,我們可以在 a. 利用相簿上的圖計算平臺全量計算相似度,或者 b. 用圖查詢語句實現全圖/給定的點之間的相似度,最後給相似度一定的閾值來幫助建立新的(考慮了涉及邊的)對映關係。

這裡的 Jaccard Index 和我們前面提到的比較兩個字串的方法本質是一樣的,不過我們現在提及的是應用在圖上的點之間存在相連點作為演算法中的“交集”的實現。

社群發現演算法

順理成章的,我們還可以用社群發現的演算法全圖找出給定的基於邊之下的社群劃分,除錯演算法,使得目標劃分社群內部點為估計的相同使用者。

基於圖演算法的方法

基於圖查詢的 Jaccard 實現

Jaccard Index 是一個描述兩個集合距離的定義公式,非常簡單、符合直覺,它的定義為:

$$ J(A,B)= \frac {|A\cap B|}{|A\cup B|} $$

這裡,我們把交集理解為 A 與 B 共同連線的點(裝置、IP、郵箱字首、地址),而並集理解為這幾種關係下與 A 或者 B 直連的所有點。於是,我們用這樣的 NebulaGraph openCypher 查詢就可以算出至少包含一跳關係的點和它相關的點、以及 Jaccard Index 值,越大代表關聯度越大。

MATCH (v_start:user)-[:used_device|logged_in_from|has_email_with_handle|has_address]->(shared_components)<-[:used_device|logged_in_from|has_email_with_handle|has_address]-(v_end:user)
WITH v_start, v_end, count(shared_components) AS intersection_size
MATCH (v_start:user)-[:used_device|logged_in_from|has_email_with_handle|has_address]->(shared_components)
WITH id(v_start) AS v_start, v_end, intersection_size, COLLECT(id(shared_components)) AS set_a
MATCH (v_end:user)-[:used_device|logged_in_from|has_email_with_handle|has_address]->(shared_components)
WITH  v_start, id(v_end) AS v_end, intersection_size, set_a, COLLECT(id(shared_components)) AS set_b
WITH v_start, v_end, toFloat(intersection_size) AS intersection_size, toSet(set_a + set_b) AS A_U_B
RETURN v_start, v_end, intersection_size/size(A_U_B) AS jaccard_index
ORDER BY jaccard_index DESC

我們可以看到結果裡:

+-----------+-----------+---------------------+
| v_start   | v_end     | jaccard_index       |
+-----------+-----------+---------------------+
| "user_8"  | "user_19" | 1.0                 |
| "user_19" | "user_8"  | 1.0                 |
| "user_20" | "user_3"  | 0.6666666666666666  |
| "user_3"  | "user_20" | 0.6666666666666666  |
| "user_21" | "user_2"  | 0.6                 |
| "user_18" | "user_17" | 0.6                 |
| "user_17" | "user_18" | 0.6                 |
| "user_2"  | "user_21" | 0.6                 |
| "user_22" | "user_9"  | 0.5                 |
| "user_9"  | "user_22" | 0.5                 |
| "user_23" | "user_5"  | 0.2                 |
| "user_5"  | "user_23" | 0.2                 |
| "user_21" | "user_20" | 0.16666666666666666 |
| "user_20" | "user_21" | 0.16666666666666666 |
+-----------+-----------+---------------------+

user_8 與 user_19 的係數是最大的的,讓我們看看他們之間的連線?

FIND ALL PATH FROM "user_8" TO "user_19" OVER * BIDIRECT YIELD path AS p;

果然,他們之間的相似度很大:

基於圖演算法的方法

基於 NebulaGraph Algorithm 圖計算平臺的 Jaccard 方法
前面方法的侷限

利用圖資料庫查詢計算 Jaccard 係數的方法有兩方面侷限。

首先,為了防止兩兩運算,我們假設了所有值得被運算的點之間已經存在某種確定連結(對應 MATCH 第一行),雖然這樣的假設在大部分情況下是可以粗略被接受的,但是它是一種壓縮和妥協。

其次,在資料量很大的情形裡,這樣的查詢將不具有可操作性。

更 Scale 的方法

為了能處理更大規模,我們可以利用 Spark 等平行計算平臺進行演算法執行;

在全圖運算時,我們可以利用區域性敏感雜湊 MinHash 來對兩兩比對降維。慶幸的是,Spark 中提供了 MinHash 的實現供我們使用!

參考:

MinHash 的思想是用機率去有損估計 Jaccard 係數,這裡的降維體現在它用 bit map 去數字化每一個集合,隨機定義不同的集合上的 shuffle(亂序)變換,取變換之後 hash 的最小值。這裡,兩個集合的隨機變換後最小值相等的機率是等於 Jaccard 係數的。所以,這樣偷樑換柱,就把需要兩兩集合運算比較的演算法變成只需要對每一個集合做常數次隨機變換取最小的降維近似運算了。

在圖上,對於每一個點,我們認為它的鄰居就是這個點的集合,那麼在 Spark 中運算 Jaccard 係數的過程就是:

  1. 獲取每一個點的鄰居集合
  2. 對點的鄰居進行 MinHash 運算,獲得 Jaccard 係數

慶幸的是,開源的 NebulaGraph Algorithm 已經提供了這個演算法的實現,感興趣的同學可以訪問 nebula-algorithm/src/main/scala/com/vesoft/nebula/algorithm/lib/JaccardAlgo.scala 瞭解它的實現,而我們只需要呼叫 NebulaGraph Algorithm 就可以了,使用方法參考 NebulaGraph Algorithm 文件。

而配置中 jaccard.tol 的意涵是 approxSimilarityJoin 中的 threshold

def approxSimilarityJoin(
    datasetA: Dataset[_],
    datasetB: Dataset[_],
    threshold: Double,
    distCol: String): Dataset[_] = {
    ...
    // Filter the joined datasets where the distance are smaller than the threshold.
    joinedDatasetWithDist.filter(col(distCol) < threshold)

讀者到這裡應該會注意到,這個方法顯然是假設所有的點都是使用者實體,邊是它們之間的直連關係的。所以在應用這個方法之前,我們需要建立經過預處理的直連邊,這個步驟正是前面章節“利用新的邊連線不同方法”中的內容。

基於 NebulaGraph Algorithm 圖計算平臺社群發現演算法

提到基於全圖的演算法,我們自然可以想到可以利用社群發現的手段去幫助識別相同使用者的不同賬號,弱聯通分量(WCC)、Louvain 演算法都是常見的手段。

同樣,NebulaGraph Algorithm 開箱即用地提供了這兩種演算法,我們可以很容易在 NebulaGraph 得出社群劃分,並在此基礎上做複合方法的識別。

上手基於 NebulaGraph Algorithm 圖計算方法

因為篇幅關係,這裡不展示 NebulaGraph Algorithm 方法的上手環節,類似於在之前 Fraud Detection 方法文章中的對應章節,你可以利用 nebula-up 的 all-in-one 模式,一行命令搭建這樣的環境並親自體驗。

nebula-up 部署命令:

curl -fsSL nebula-up.siwei.io/all-in-one.sh | bash -s -- v3 spark

基於圖神經網路的方法

我們注意到,在將以上不同的方法相結合的時候,會把前導方法的結果作為圖上的邊,進而作為後面方法的輸入,而相同使用者 ID 的識別本質上就是在圖上去預測使用者之間連結、邊。

在 GNN 的方法中,除了我們在欺詐檢測中利用到的節點分類(屬性預測)之外,連結預測(Link Prediction)也是另一個常見的演算法目標和應用場景。自然地,可以想到用 GNN 的方法結合非 GNN 方法或者已有人為標註的連結,來學習、預測圖上的 ID 對映。

值得注意的是,GNN 的方法只能利用數字型的 feature、屬性,我們沒辦法把非數字型的屬性像在分類情況裡那樣列舉為數值。相反,我們在搞真正的 GNN 之前,可以用其他的圖方法去建立基於打分、或者相似度的邊建立。這時候,這些前面的方法成為了 GNN 鏈路預測的特徵工程。

基於 GNN 的實操

和在 “基於 NebulaGraph 圖資料庫的欺詐檢測方法與程式碼示例” 的欺詐檢測類似,我將給出的例子也是 GNN 結合圖資料庫做實時預測的例子。

HDE[ICDM2021]

我們利用 Heterogeneous Graph Neural Network with Distance Encoding 給出的方法來做 Inductive Learning 的異構 GNN 上的鏈路預測。同時,我們將用一個更方便的 GNN 工具——OpenHGNN。有了它,本例中的程式碼量也會大大下降。

簡單介紹下,OpenHGNN 是由北郵 GAMMA Lab 開發的基於 PyTorch 和 DGL 的開源異質圖神經網路工具包。

資料集

本例的資料集是前面建立在 NebulaGraph 的圖譜,藉助於 nebula-dgl,我們可以一行程式碼把 NebulaGraph 中的圖載入到 DGL 之中。

參考:

  1. 這裡,我們使用的的工具為 Deep Graph library(DGL)作為 NebulaGraph 圖資料庫和他們之間的橋樑,nebula-dgl
  2. 你可以直接 load 這個 .ngql 檔案到 NebulaGraph。.ngl 檔案https://github.com/wey-gu/identity-correlation-datagen/raw/main/sample/hand_crafted/entity_resolution.ngql
資料處理

為了將 NebulaGraph 圖譜進行工程處理、序列化成為 DGL 的圖物件,我們要透過 nebula-dgl 的 YAML 配置檔案 API 描述所需的點、邊型別以及關心的屬性(特徵)。

我們看下現在的圖中有哪些點、邊型別:

(root@nebula) [entity_resolution]> SHOW TAGS
+----------------+
| Name           |
+----------------+
| "address"      |
| "device"       |
| "email"        |
| "email_handle" |
| "ip"           |
| "phone"        |
| "user"         |
+----------------+
Got 7 rows (time spent 1335/7357 us)

(root@nebula) [entity_resolution]> SHOW EDGES
+---------------------------+
| Name                      |
+---------------------------+
| "has_address"             |
| "has_email"               |
| "has_email_with_handle"   |
| "has_phone"               |
| "is_similar_to"           |
| "logged_in_from"          |
| "shared_name"             |
| "shared_similar_email"    |
| "shared_similar_location" |
| "used_device"             |
| "with_handle"             |
+---------------------------+
Got 11 rows (time spent 1439/30418 us)

在本例中,我們不考慮屬性(特徵)。

nebulagraph_entity_resolution_dgl_mapper.yaml
---
# If vertex id is string-typed, remap_vertex_id must be true.
remap_vertex_id: True
space: entity_resolution
# str or int
vertex_id_type: int
vertex_tags:
  - name: user
  - name: address
  - name: device
  - name: email_handle
  - name: ip
edge_types:
  - name: has_email_with_handle
    start_vertex_tag: user
    end_vertex_tag: email_handle
  - name: is_similar_to
    start_vertex_tag: user
    end_vertex_tag: user
  - name: shared_similar_location
    start_vertex_tag: user
    end_vertex_tag: user
  - name: has_address
    start_vertex_tag: user
    end_vertex_tag: address
  - name: logged_in_from
    start_vertex_tag: user
    end_vertex_tag: ip
  - name: used_device
    start_vertex_tag: user
    end_vertex_tag: device

然後,我們在安裝好 nebula-dgl 之後只需要這幾行程式碼就可以將 NebulaGraph 中的這張圖構造為 DGL 的 DGLHeteroGraph 圖物件:

from nebula_dgl import NebulaLoader


nebula_config = {
    "graph_hosts": [
                ('graphd', 9669),
                ('graphd1', 9669),
                ('graphd2', 9669)
            ],
    "nebula_user": "root",
    "nebula_password": "nebula",
}

# load feature_mapper from yaml file
with open('nebulagraph_entity_resolution_dgl_mapper.yaml', 'r') as f:
    feature_mapper = yaml.safe_load(f)

nebula_loader = NebulaLoader(nebula_config, feature_mapper)
g = nebula_loader.load()

g = g.to('cpu')
device = torch.device('cpu')
模型訓練

參考 custom_link_prediction_dataset.py

HDE_link_predict.py
import torch as th
from openhgnn import Experiment
from openhgnn.dataset import AsLinkPredictionDataset, generate_random_hg
from dgl import transforms as T
from dgl import DGLHeteroGraph
from dgl.data import DGLDataset
from dgl.dataloading.negative_sampler import GlobalUniform

meta_paths_dict = {'APA': [('user', 'has_email_with_handle', 'email_handle'), ('user', 'is_similar_to', 'user'), ('user', 'shared_similar_location', 'user'), ('user', 'has_address', 'address'), ('user', 'logged_in_from', 'ip'), ('user', 'used_device', 'device')]}
target_link = [('user', 'is_similar_to', 'user')]
target_link_r = [('user', 'is_similar_to', 'user')]


class MyLPDataset(DGLDataset):
    def __init__(self, g):
        super().__init__(name='entity_resolution', force_reload=True)
        self.g = g

    def process(self):
        # Generate a random heterogeneous graph with labels on target node type.
        self._g = transform_hg(self.g)

    # Some models require meta paths, you can set meta path dict for this dataset.
    @property
    def meta_paths_dict(self):
        return meta_paths_dict

    def __getitem__(self, idx):
        return self._g

    def __len__(self):
        return 1


def transform_hg(g: DGLHeteroGraph) -> DGLHeteroGraph:
    transform = T.Compose([T.ToSimple(), T.AddReverse()])
    hg = transform(g)
    return hg


def train_with_custom_lp_dataset(dataset):
    experiment = Experiment(model='HDE', dataset=dataset, task='link_prediction', gpu=-1)
    experiment.run()


myLPDataset = AsLinkPredictionDataset(
    MyLPDataset(g),
    target_link=target_link,
    target_link_r=target_link_r,
    split_ratio=[0.8, 0.1, 0.1],
    force_reload=True)

train_with_custom_lp_dataset(myLPDataset)

TBD:尚需把 g 處理成為 MyLPDataset() 可以接受的資料。

儲存模型

OpenHGNN 中儲存自定義資料集的模型的支援,有些問題,參考 https://github.com/BUPT-GAMMA/OpenHGNN/issues/112

應用落地

參考:https://github.com/wey-gu/NebulaGraph-Fraud-Detection-GNN


謝謝你讀完本文 (///▽///)

NebulaGraph Desktop,Windows 和 macOS 使用者安裝圖資料庫的綠色通道,10s 拉起搞定海量資料的圖服務。通道傳送門:http://c.nxw.so/6Tekg

想看原始碼的小夥伴可以前往 GitHub 閱讀、使用、(^з^)-☆ star 它 -> GitHub;和其他的 NebulaGraph 使用者一起交流圖資料庫技術和應用技能,留下「你的名片」一起玩耍呢~

相關文章