乐趣区

关于风险控制:如何判断多账号是同一个人用图技术搞定-ID-Mapping

原文出处: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 的模式的点。

这个后果的数据源在 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 中的查问后果:

同样,在下面交互图中能够放大看到这两对领有雷同 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.com 与 4kelly@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
  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 的实现供咱们应用!

参考:

  • https://aksakalli.github.io/2016/03/01/jaccard-similarity-with-minhash.html
  • https://en.wikipedia.org/wiki/MinHash
  • https://spark.apache.org/docs/3.1.1/api/python/reference/api/pyspark.ml.feature.MinHashLSH.html
  • https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/feature/LSH.scala

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 用户一起交换图数据库技术和利用技能,留下「你的名片」一起游玩呢~

退出移动版