前言
图分析是数据科学中的一个重要领域,其专注于通过图结构来表示数据,并执行各种计算和分析任务。图结构由节点(或称为顶点)和边组成,节点通常代表实体,边表示实体之间的关系。图计算广泛应用于社交网络分析、推荐系统、知识图谱、网络和IT运营、供应链管理、生物信息等多个领域。
PolarDB PostgreSQL版(下文简称为 PolarDB)是一款阿里云自主研发的云原生关系型数据库产品,100% 兼容 PostgreSQL,高度兼容Oracle语法(公有云版支持Oracle语法);采用基于 Shared-Storage 的存储计算分离架构,具有极致弹性、毫秒级延迟、HTAP 的能力和高可靠、高可用、弹性扩展等企业级数据库特性。同时,PolarDB 具有大规模并行计算能力,可以应对OLTP与OLAP混合负载。
本文介绍的图分析能力,依托阿里云云原生关系型数据库PolarDB For PostgreSQL版建设输出。
业务场景
背景信息
在现代欺诈和各类金融犯罪中,欺诈者通过改变自身身份等达到逃避风控规则的欺诈目的。通过图数据库建立跟踪用户行为的图结构,实时分析欺诈行为的离散数据,识别欺诈环,可以快速防范和解决欺诈行为。
数据和模型
IEEE-CIS Fraud Detection数据集是金融交易领域公开数据集。数据是由电商平台Vesta提供的交易记录,包括了交易相关的设备、地址、邮箱等信息。通过该数据集可以识别欺诈交易,并进行风险预测。
原始数据为csv格式,包含交易信息(交易号,地址,邮箱等)以及交易识别信息(设备信息,设备类型等):
数据集包含了众多的信息,本文中将数据模型抽象为下图,实际业务场景可根据实际情况进行调整:
其中:
- 点:
- transaction(交易)
- product(交易产品)
- addr1(交易地址1)
- addr2(交易地址2)
- emaildomain(交易时使用的邮箱域名)
- deviceinfo(交易设备信息)
- devicetype(交易设备类型)
- 边:
- transaction_product(交易与产品关系)
- transaction_addr1(交易与地址1关系)
- transaction_addr2(交易与地址2关系)
- transaction_emaildomain(交易与邮箱域名关系)
- transaction_deviceinfo(交易与设备信息关系)
- transaction_devicetype(交易与设备类型关系)
以上模型以交易为中心,通过交易号(transactionid)进行关联。
最佳实践
技术实现
PolarDB
PolarDB 图数据库引擎(兼容Apache AGE,A Graph Extension) 是一个为 PostgreSQL系列数据库打造的扩展,旨在增强其处理图数据的能力。AGE 旨在结合关系型数据库与图数据库的优势,提供一个高性能、灵活且易于扩展的解决方案。
AGE主要包含以下特点
- 完全兼容PostgreSQL
AGE 是PolarDB PostgreSQL版的一个扩展,这意味着可以在现有的PolarDB数据库中使用,而无需重新构建数据库。AGE 继承了 PolarDB 的所有强大功能,包括事务、并发控制、以及多种索引和优化技术。
- 统一的图形和关系型查询
AGE 允许同时处理关系型数据和图形数据,可以在同一个查询中混合使用 SQL 和图查询语言。这使得处理复杂的数据模型更加容易和高效。
- 支持 Cypher 查询语言
AGE 支持使用 Cypher 查询语言,这是一种专为图数据库设计的查询语言,语法简单且灵活。为用户提供了一种直观的方式来进行图数据的查询和操作。
- 高性能
通过结合 PolarDB 的优化技术和专为图数据设计的索引,AGE 能够高效地处理大规模图形数据和复杂的图形查询。
如上所述, 借助于AGE强大的能力,PolarDB可以简单、高效地处理各类图查询。
Age-viewer
age-viewer 是Age项目自带的图数据可视化工具,可将查询结果进行可视化的表达。
建议配置
为了得到良好的体验,建议使用以下配置:
项目 |
推荐配置 |
PolarDB 版本 |
标准版 兼容PostgreSQL 14 |
CPU |
>16 Core |
内存 |
>64 GB |
磁盘 |
>100GB (AUTOPL) |
版本 |
>2.0.14.23.1 |
数据库准备
数据库中需要预先创建age
扩展
create extension age;
在每个会话执行时,需要设置search_path并执行SQL来加载扩展:
SET search_path = ag_catalog, "$user", public; select * from get_cypher_keywords() limit 0;
如不想在每个会话中设置search_path,可对数据库进行该项操作
ALTER DATABASE <dbname> SET search_path = ag_catalog, "$user", public; ALTER DATABASE <dbname> SET session_preload_libraries TO 'age';
数据入库
创建图
使用函数create_graph 可以创建一个图,创建时需要指定图的名称(此处以"fraud_graph" 为例)。
SELECT create_graph('fraud_graph');
插入节点和边
由于下载的数据为csv文件,无法直接作为点边结构的图数据进行入库。
本文附录中提供了将数据转换为PolarDB中vertex和edge的的python脚本,转换后如下所示:
- 部分点数据
SELECT * FROM cypher('fraud_graph', $$ MERGE (v:transaction {transactionid : 2990783, isfraud : 0 } ) RETURN v $$ ) as (n agtype); SELECT * FROM cypher('fraud_graph', $$ MERGE (v:product {productid : 158945 } ) RETURN v $$ ) as (n agtype); SELECT * FROM cypher('fraud_graph', $$ MERGE (v:addr1 {addr1 : '299.0' } ) RETURN v $$ ) as (n agtype); SELECT * FROM cypher('fraud_graph', $$ MERGE (v:addr2 {addr2 : '87.0' } ) RETURN v $$ ) as (n agtype); SELECT * FROM cypher('fraud_graph', $$ MERGE (v:emaildomain {emaildomain : 'gmail.com' } ) RETURN v $$ ) as (n agtype); SELECT * FROM cypher('fraud_graph', $$ MERGE (v:deviceinfo {deviceinfo : 'SM-G920V Build/NRD90M' } ) RETURN v $$ ) as (n agtype); SELECT * FROM cypher('fraud_graph', $$ MERGE (v:devicetype {devicetype : 'mobile' } ) RETURN v $$ ) as (n agtype); ...
- 部分边数据
SELECT * FROM cypher('fraud_graph', $$ MATCH (a:transaction), (b:product) WHERE a.transactionid = 2990783 AND b.productid = 158945 MERGE (a)-[e:transaction_product]->(b) RETURN e$$) as (e agtype); SELECT * FROM cypher('fraud_graph', $$ MATCH (a:transaction), (b:addr1) WHERE a.transactionid = 2990783 AND b.addr1 = '299.0' MERGE (a)-[e:transaction_addr1]->(b) RETURN e$$) as (e agtype); SELECT * FROM cypher('fraud_graph', $$ MATCH (a:transaction), (b:addr2) WHERE a.transactionid = 2990783 AND b.addr2 = '87.0' MERGE (a)-[e:transaction_addr2]->(b) RETURN e$$) as (e agtype); SELECT * FROM cypher('fraud_graph', $$ MATCH (a:transaction), (b:emaildomain) WHERE a.transactionid = 2990783 AND b.emaildomain = 'gmail.com' MERGE (a)-[e:transaction_emaildomain_p]->(b) RETURN e$$) as (e agtype); SELECT * FROM cypher('fraud_graph', $$ MATCH (a:transaction), (b:deviceinfo) WHERE a.transactionid = 2999403 AND b.deviceinfo = 'SM-G920V Build/NRD90M' MERGE (a)-[e:transaction_deviceinfo]->(b) RETURN e$$) as (e agtype); SELECT * FROM cypher('fraud_graph', $$ MATCH (a:transaction), (b:devicetype) WHERE a.transactionid = 2999404 AND b.devicetype = 'mobile' MERGE (a)-[e:transaction_devicetype]->(b) RETURN e$$) as (e agtype); ...
将转换后的结果保存为sql文件,配合客户端工具,如psql等可完成数据导入。
使用示例
简单查询
数据统计
- 节点数量
SELECT * FROM cypher('fraud_graph', $$ MATCH (n) RETURN count(*) $$) as (number_of_vertex agtype); number_of_vertex ---- 1076004
- transaction节点数量
SELECT * FROM cypher('fraud_graph', $$ MATCH (n:transaction) RETURN count(*) $$) as (number_of_transaction agtype); number_of_transaction ---- 545591
- 数据中标识为欺诈交易的数量
SELECT * FROM cypher('fraud_graph', $$ MATCH (n:transaction) WHERE n.isfraud = 1 RETURN count(*) $$) as (number_of_fraud_transaction agtype); number_of_fraud_transaction ---- 18919
- 边数量
SELECT * FROM cypher('fraud_graph', $$ MATCH ()-[r]->() RETURN count(*) $$) as (number_of_edge agtype); number ------ 2131254
过滤查询、排序查询
- 查询ID为2988706的交易信息, 所有的关联信息
SELECT * FROM cypher('fraud_graph', $$ MATCH (n:transaction)-[r]->(v) WHERE n.transactionid = 2988706 RETURN v $$) as (e agtype); --------- {"id": 2251799813685249, "label": "addr2", "properties": {"addr2": "87.0"}}::vertex {"id": 2533274790395906, "label": "emaildomain", "properties": {"emaildomain": "gmail.com"}}::vertex {"id": 2533274790395906, "label": "emaildomain", "properties": {"emaildomain": "gmail.com"}}::vertex {"id": 1970324836974595, "label": "addr1", "properties": {"addr1": "325.0"}}::vertex {"id": 1125899906844295, "label": "product", "properties": {"productid": 137934}}::vertex
age-viewer 中使用以下sql预览结果:
SELECT * FROM cypher('fraud_graph', $$ MATCH (n:transaction)-[r]->(v) WHERE n.transactionid = 2988706 RETURN [n,r,v]::path $$) as (e agtype);
通用场景
K阶邻居
k阶邻居(k-nearest neighbors, KNN)方法利用数据节点之间的相似性,来识别潜在的欺诈行为。k阶邻居可以帮助评价数据节点之前的相似性。通过寻找与该数据节点特征相似的k个邻居,可以判断该节点是否正常。例如,一个交易如果与大多数邻居(交易节点)在金额、地点、时间等特征上存在显著差异,可能会被标记为可疑。
- 查询和交易记录2988706有相同地址的其他交易(2阶邻居)记录。后续可以根据这些邻居的相关信息来对这个交易的可疑性进行判别。
SELECT * FROM cypher('fraud_graph', $$ MATCH (n:transaction)-[:transaction_addr1]->(:addr1)<-[:transaction_addr1]-(t:transaction) WHERE n.transactionid = 2988706 RETURN t $$) as (e agtype); ----- {"id": 844424930131972, "label": "transaction", "properties": {"isfraud": 0, "transactionid": 2987001}}::vertex {"id": 844424930131978, "label": "transaction", "properties": {"isfraud": 0, "transactionid": 2987007}}::vertex {"id": 844424930132041, "label": "transaction", "properties": {"isfraud": 0, "transactionid": 2987070}}::vertex {"id": 844424930132053, "label": "transaction", "properties": {"isfraud": 0, "transactionid": 2987082}}::vertex ....
age-viewer 中使用以下sql预览结果:
SELECT * FROM cypher('fraud_graph', $$ MATCH (n:transaction)-[r:transaction_addr1]->(a:addr1)<-[r2:transaction_addr1]-(t:transaction) WHERE n.transactionid = 2988706 RETURN [n,r,a,r2,t]::path LIMIT 50 $$) as (e agtype);
注: 仅返回50条记录,返回数据量太多会导致前端卡死
路径检索
在欺诈交易识别中,路径的概念主要体现在利用图论和网络分析的方法来识别潜在的欺诈行为。在金融交易和网络中,交易往往可以视为一个图,节点代表账户或客户,边代表交易活动。通过分析这个图的结构,可以发现一些异常模式和欺诈行为。通过计算交易之间的路径,可以快速识别从一个可疑账户到其他账户的交易链,有助于揭示看似不相关的账户之间的隐秘联系。
- 查询交易记录2987000和交易记录2987172基于某个虚假交易的关联路径,从两个看似没有直接关系的交易中找出关联的所有虚假交易链路。
SELECT * FROM cypher('fraud_graph', $$ MATCH (n:transaction)-[r]->(v)<-[r1]-(t:transaction)-[r2]->(v2)<-[r3]-(k:transaction) WHERE n.transactionid = 2987000 and k.transactionid=2987172 and t.isfraud = 1 RETURN t $$) as (e agtype); ---- {"id": 844424930618281, "label": "transaction", "properties": {"isfraud": 1, "transactionid": 3473312}}::vertex {"id": 844424930626886, "label": "transaction", "properties": {"isfraud": 1, "transactionid": 3481917}}::vertex {"id": 844424930649640, "label": "transaction", "properties": {"isfraud": 1, "transactionid": 3504671}}::vertex {"id": 844424930631805, "label": "transaction", "properties": {"isfraud": 1, "transactionid": 3486836}}::vertex {"id": 844424930641980, "label": "transaction", "properties": {"isfraud": 1, "transactionid": 3497011}}::vertex {"id": 844424930644942, "label": "transaction", "properties": {"isfraud": 1, "transactionid": 3499973}}::vertex
age-viewer 中使用以下sql预览结果:
SELECT * FROM cypher('fraud_graph', $$ MATCH (n:transaction)-[r]->(v)<-[r1]-(t:transaction)-[r2]->(v2)<-[r3]-(k:transaction) WHERE n.transactionid = 2987000 and k.transactionid=2987172 and t.isfraud = 1 RETURN [n,r,v,r1,t,r2,v2,r3,k]::path LIMIT 50 $$) as (e agtype);
共同邻居
在欺诈交易识别中,共同邻居判断(Common Neighbor Judgment)是一种基于社交网络或交易网络分析的技术,用于识别可能的欺诈行为。该方法主要基于图论的基础,通过分析交易参与者之间的关系网络,寻找可能的可疑交易模式。如果两个交易有多个共同邻居,而这些邻居在交易行为上存在异常(例如高频率的交易、异常金额等),则这两个交易者之间的交易可能存在欺诈风险。
- 查询交易记录2987000和交易记录2987172的共同邻居,从而找到具备相同属性(地址、设备)的交易记录。
SELECT * FROM cypher('fraud_graph', $$ MATCH (n:transaction)-[]->(v)<-[]-(t:transaction) WHERE n.transactionid = 2987000 and t.transactionid=2987172 RETURN v $$) as (e agtype); ---- {"id": 2251799813685249, "label": "addr2", "properties": {"addr2": "87.0"}}::vertex {"id": 1970324836974594, "label": "addr1", "properties": {"addr1": "315.0"}}::vertex
age-viewer 中使用以下sql预览结果:
SELECT * FROM cypher('fraud_graph', $$ MATCH (n:transaction)-[r]->(v)<-[r1]-(t:transaction) WHERE n.transactionid = 2987000 and t.transactionid=2987172 RETURN [n,r,v,r1,t]::path $$) as (e agtype);
Jaccard相似度
Jaccard相似度用于衡量两个集合之间的相似性,其公式为:
J(A, B) = \frac{|A \cap B|}{|A \cup B|}
其中:
- J(A,B) 是集合 A 和 B 的Jaccard相似度。
- ∣A∩B∣是集合 A 和 B的交集。
- ∣A∪B∣是集合 A 和 B 的并集。
在欺诈识别中,Jaccard相似度可以有多种用途,如:
- 模式识别:欺诈行为往往表现出一些共性特征。通过计算用户交易或行为的Jaccard相似度,可以识别出相似的交易模式,从而帮助检测潜在的欺诈活动;
- 客户群体分析:在分析客户的行为时,可以利用Jaccard相似度来比较不同客户之间的相似性。相似度较高的客户可能存在相似的风险特征。
在本示例中,两个交易可以根据其关联的地址,邮箱以及地址等信息计算其Jaccard相似度,如果关联的信息重叠度很大,则说明交易的相似度很高。
创建以下函数用于获取特定交易的所有关联节点,返回所有点的id数组
CREATE OR REPLACE FUNCTION find_ids(transactionid integer) RETURNS bigint[] LANGUAGE plpgsql AS $function$ DECLARE sql VARCHAR; ids bigint[]; BEGIN sql := 'SELECT array_agg(cast(r as bigint)) FROM ( SELECT * FROM cypher(''fraud_graph'', $$ MATCH (n:transaction)-[]->(v) WHERE n.transactionid = ' || text($1) || 'RETURN id(v) $$) as (e agtype)) as t;'; EXECUTE sql INTO ids; return ids; END $function$;
辅助函数,用于对数组进行Union和Intersection的操作
create or replace function array_union(anyarray, anyarray) returns anyarray language sql immutable as $$ select array_agg(a order by a) from ( select distinct unnest($1 || $2) as a ) s; $$; create or replace function array_intersection(anyarray, anyarray) returns anyarray language sql immutable as $$ select array_agg(e) from ( select unnest($1) intersect select unnest($2) ) as dt(e) $$;
最后,创建函数对两个交易进行jaccard相似度的计算
CREATE OR REPLACE FUNCTION jaccardSimilarity(tid1 integer, tid2 integer) RETURNS float8 LANGUAGE plpgsql AS $function$ DECLARE sql VARCHAR; ids1 bigint[]; ids2 bigint[]; union_list bigint[]; intersection_list bigint[]; BEGIN ids1 = find_ids($1); ids2 = find_ids($2); union_list = array_union(ids1, ids2); -- union intersection_list = array_intersection(ids1, ids2); -- intersection RETURN CASE WHEN array_length(union_list,1) = 0 THEN 0 ELSE array_length(intersection_list,1) * 1.0/ array_length(union_list,1) END AS jaccardSimilarity; END $function$;
使用时可以对指定两个交易的id进行
SELECT jaccardSimilarity(2987000, 2987172); jaccardsimilarity ---- 0.4
如果需要对所有的交易进行相似度对比,可以充分利用PolarDB的存储过程能力,完成更为复杂的相似度计算任务,从而满足检测欺诈交易的需求。
例如,可以使用以下SQL找出与交易2987002具有相同地址1和地址2以及相同的邮箱域名的所有交易并按照jaccard相似度进行排序,找出相似度最大的50个交易:
WITH tmp AS (SELECT cast(r as integer) as transactionid FROM cypher('fraud_graph', $$ MATCH (n:transaction)-[:transaction_addr2]->(:addr2)<-[:transaction_addr2]-(t:transaction) WHERE n.transactionid = 2987002 MATCH (n:transaction)-[:transaction_addr1]->(:addr1)<-[:transaction_addr1]-(t:transaction) WHERE n.transactionid = 2987002 MATCH (n:transaction)-[:transaction_emaildomain_p]->(:emaildomain)<-[:transaction_emaildomain_p]-(t:transaction) WHERE n.transactionid = 2987002 RETURN t.transactionid $$) as (e agtype) ) SELECT transactionid, jaccardSimilarity(2987002, transactionid) as jaccardSimilarity FROM tmp ORDER by jaccardSimilarity DESC LIMIT 50; transactionid | jaccardsimilarity ---------------+------------------- 3323911 | 0.6 3328911 | 0.6 3009043 | 0.6 3039416 | 0.6 3039425 | 0.6 2993652 | 0.6 3045027 | 0.6 3037644 | 0.6 3045041 | 0.6 ...
age-viewer 中使用以下sql预览结果(结果已在之前计算完成, 只取了前10,如有需要可自行放到list中):
SELECT * FROM cypher('fraud_graph', $$ MATCH (n:transaction)-[r]->(v)<-[r2]-(t:transaction) WHERE n.transactionid = 2987002 AND t.transactionid IN [3323911, 3328911,3009043,3039416,3039425,2993652,3045027,3037644,3045041,3045049,3045279] RETURN [n,r,v,r2,t]::path $$) as (e agtype);
总结
本文介绍了如何利用阿里云云原生关系型数据库PolarDB PostgreSQL版的图分析能力来进行图数据分析。
PolarDB结合AGE扩展,提供了图数据计算分析的功能, 包括使用Cypher查询语言,高效处理查询图数据。本文以金融交易领域公开数据集为例,示例了在交易欺诈识别场景下,执行图查询来发现欺诈交易:例如,查询与欺诈交易有相同的地址信息或设备信息,找出欺诈交易的关联关系,计算交易与欺诈交易的相似度关系,以便进行欺诈预警。PolarDB在关系型数据库的基础上,提供了图分析能力,为企业的统一数据管理和分析,提供了强有力的支撑。
试用体验
欢迎访问PolarDB免费试用页面,选择试用“云原生数据库PolarDB PostgreSQL版”,体验PolarDB的图计算能力
附录
数据转换脚本如有需要请联系PolarDB-PG图数据库引擎支持团队或提工单方式获取。