”本文是一个依据 NebulaGraph 上的图数据库、图算法、图神经网络的 ID Resolution/ID Mapping 办法总述,除了基本办法思维的介绍之外,我还给咱们弄了能够跑的 Playground。“ –  NebulaGraph 古思为

依据图数据库的用户 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

数据

这份数据是开源的,地址在 github.com/wey-gu/iden…

写入 NebulaGraph

运用 Nebula Up,一行布置 NebulaGraph

地址:github.com/wey-gu/nebu…

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 的办法的点。

注:

  • 这个成果的数据源在 github.com/wey-gu/iden…
  • 假如经过线上访问本文,你能够鼠标悬停(获取点上的特点)和框选扩大每一个点和子图哦。

明显,在构建 ID Mapping 体系的进程中,咱们便是经过在图数据库中直接查询,可视化渲染成果来看到等效的洞察,这个查询能够写成:

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

NebulaGraph 中的查询成果

相同,在上边交互图中能够扩大看到这两对具有相同 email 相关起来的账号:

可是,在更多实在世界中,这样的办法匹配往往不能处理更多稍微复杂一点的景象:

比方从上边的图中咱们能够看到这两个匹配了的映射中,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
  • 如何体现 4kelly@yahoo.com4kelly@hotmail.com 的类似性?
  • 如何将多种匹配规矩的信息都归入相关体系?

非确认规矩依据复合条件量化办法

前边说到了几种确认规矩无法处理的状况,它们能够归结为这两点:

  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

2. 非确认性类似性

在有限存在确认性相关的点之间(避免两两穷举),运算其他量化、非确认类似性(字符间隔、地舆间隔等、图片文件类似度);

这儿用地址的地舆间隔来做为比如,咱们预先处理每一个地址,将它们的经纬度导入图谱。

这样,咱们更改地址这个点的类型 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)核算的本质间隔的一个最好的诠释,咱们能够借助于图数据库中查询句子描绘才能或许运用其他体系去运算用户间非确认性特征的量化间隔/类似度。

3. 加权评分

为不同联系赋予加权,核算类似度总分;

下边是一个在实际运用中,能够归纳考量的多种相相联系,包括但不限于:

确认性联系

  • 同名(精确匹配)
  • 相同电话(格式化处理)
  • 运用过相同设备(精确匹配)
  • 同邮件前缀(精细化处理)

非确认性

  • 地址间隔(处理成经纬度,核算地球球面间隔)
  • 头像图片布景类似度(练习模型核算图画间隔)

一个很直觉的办法便是将多种条件按照不同的权重加权,取得两点间的总“疑似相同账号”的评分。

本例中,为求简洁,咱们只给出考虑“同邮件前缀”、“同名”与“地舆间隔小于 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∣AcapB∣∣AcupB∣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 的完成供咱们运用!

参阅:

  • aksakalli.github.io/2016/03/01/…
  • en.wikipedia.org/wiki/MinHas…
  • spark.apache.org/docs/3.1.1/…
  • github.com/apache/spar…

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 的办法结合 1. 非 GNN 办法取得的、 2. 现已有的人为标示的链接,来学习、猜测图上的 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。

    • DGL: www.dgl.ai/

    • Nebula-DGL: github.com/wey-gu/nebu…

  2. 你能够直接 load 这个 .ngql 文件到 NebulaGraph。

    • github.com/wey-gu/iden…

数据处理

为了将 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)

尚需把 g 处理成为 MyLPDataset() 能够承受的数据,这儿也先略过。

保存模型

OpenHGNN 中保存自界说数据集的模型的支撑,有些问题,参阅 github.com/BUPT-GAMMA/…

运用落地

参阅:github.com/wey-gu/Nebu…

Feature image credit by Cosmin Serbin

交流图数据库技能?加入 NebulaGraph 交流群请先填写下你的 NebulaGraph 手刺,Nebula 小助手会拉你进群哦~~

官网:nebula-graph.com.cn

GitHub:github.com/vesoft-inc/…

免费开源,能够右上角点 Star(7.9K) 支撑/保藏下~