电商风控图模板
电商风控
互联网电商为扩张业务、推广产品,会采用奖励、返点、打折等手段实现用户和商户裂变。例如,商品的链接在商户或客户间转发时将会附带一标识ID,若商品被购买,具有该标识ID的商户或用户均会得到一笔奖励。而在裂变过程中,存在薅羊毛、虚开账号等损害平台或商家利益的行为。
基于图模型的互联网电商风控,将提供客群管控、羊毛党发现等解决方案,有效帮助客户降低损失。
图模型
以下是电商风控图模型元数据。
创建图引擎实例
创建图引擎实例的详细流程可参考链接:https://bbs.huaweicloud.cn/blogs/296812
Notebook源码
配置参数的定义。config类的属性依次为“终端节点”、“IAM 用户名”、“IAM 用户密码”、“IAM 用户所属账户名”、“项目名称”、“公网访问地址”、“项目ID”、“token值”,其获取方式可参考链接:https://bbs.huaweicloud.cn/blogs/296930。
import requests
import json
import time
class config(object):
def __init__(self, iam_url, user_name, password, domain_name, project_name, eip, project_id, graph_name):
self.iam_url = iam_url
self.user_name = user_name
self.password = password
self.domain_name = domain_name
self.project_name = project_name
self.eip = eip
self.project_id = project_id
self.graph_name = graph_name
self.token = self.get_token()
def get_token(self):
url = ('https://{}/v3/auth/tokens').format(self.iam_url)
headers = {'Content-Type': 'application/json;charset=utf8'}
body = json.dumps({"auth": { \
"identity": { \
"methods": ["password"], \
"password": { \
"user": { \
"name": self.user_name, \
"password": self.password, \
"domain": { \
"name": self.domain_name \
} \
} \
} \
}, \
"scope": { \
"project": { \
"name": self.project_name \
} \
} \
} \
})
r = requests.post(url=url, data=body, verify=False, headers=headers)
return r.headers['x-subject-token']
调用API接口的定义。
class GESFunc(object):
def __init__(self, eip, project_id, graph_name, token):
self.graph_name = graph_name
self.headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'}
self.project_id = project_id
self.eip = eip
def connected_component(self):
url = ('http://{}/ges/v1.0/{}/graphs/{}/subgraphs/action?action_id=execute-algorithm').format(self.eip,
self.project_id,
self.graph_name)
subgraph_creator = {
"name": "filtered",
"parameters":
{
"vertex_filter":
{
"property_filter":
{
"leftvalue":
{
"label_name": "labelName"
},
"predicate": "=",
"rightvalue":
{
"value": "store"
}
}
}
}}
parameters = {}
body = json.dumps({"algorithmName": "connected_component",
"graphName": self.graph_name,
"subgraphCreator": subgraph_creator,
"parameters": parameters})
r = requests.post(url=url, data=body, headers=self.headers)
return r.json()['jobId']
def get_job(self, job_id):
url = ('http://{}/ges/v1.0/{}/graphs/{}/jobs/{}/status').format(self.eip,
self.project_id,
self.graph_name,
job_id)
r = requests.get(url=url, headers=self.headers)
output = r.json()['data']['outputs']
return output
def delete_edge(self, source_vertex, target_vertex):
url = ('http://{}/ges/v1.0/{}/graphs/{}/edges?source={}&target={}').format(self.eip,
self.project_id,
self.graph_name,
source_vertex,
target_vertex)
r = requests.delete(url=url, headers=self.headers)
output = r.json()['result']
return output
def add_edge(self, source_vertex, target_vertex, label):
url = ('http://{}/ges/v1.0/{}/graphs/{}/edges').format(self.eip, self.project_id, self.graph_name)
body = json.dumps({"source": source_vertex,
"target": target_vertex,
"label": label
})
time.sleep(2)
r = requests.post(url=url, data=body, headers=self.headers)
time.sleep(2)
output = r.json()['result']
return output
def get_cypher(self, statement, idList):
url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=execute-cypher-query').format(self.eip,
self.project_id,
self.graph_name)
statements = [{
"statement": statement,
"resultDataContents": ["row"],
"parameters": {"idList": idList},
"includeStats": False
}]
body = json.dumps({"statements": statements})
r = requests.post(url=url, data=body, headers=self.headers)
output = r.json()['results']
return output
def get_community_info(self, output):
community_list = output['community']
community_dict = {}
for community in community_list:
vertex_id = list(community.keys())[0]
community_id = community[vertex_id]
if community_id not in community_dict:
component = set()
component.add(vertex_id)
community_dict[community_id] = component
else:
community_dict[community_id].add(vertex_id)
return community_dict
def community_change_info(self, monitor_id, old_community_info, new_community_info):
monitor_set = set()
new_monitor_set = set()
change_id_list = []
for community_id in old_community_info:
if monitor_id in old_community_info[community_id]:
monitor_set = old_community_info[community_id]
for community_id in new_community_info:
if monitor_id in new_community_info[community_id]:
new_monitor_set = new_community_info[community_id]
for id in monitor_set:
if id not in new_monitor_set:
change_id_list.append(id)
return change_id_list
创建好图之后,就可以对其进行查询和分析了。
config = config('', '', '', '', '', '', '', '')
test = GESFunc(config.eip, config.project_id, config.graph_name, config.token)
羊毛党发现
在电商中,可能存在客户“薅羊毛”的行为。例如,电商平台为用户裂变,对新注册账号会有一些奖励活动,用户为获得更多奖励会在平台上注册多个账号。然而,过多的虚假账号会造成电商平台成本上升,利润下降。
在本案例中,
首先,通过电话号码(1XX725080460)和地址ID(0f1061e6-e903-11eb-8084-643e8cb8138c)找出马甲客户,该客户为给定电话号码和地址ID的共同邻居,共找出两个节点VRG 7和VRG 48。
然后,通过VRG 7和VRG 48反方向查询可找到客户节点VRG 8和VRG 49。通过对VRG 7、VRG 48、VRG 8和VRG 49相互关联性的分析,可研判VRG 8和VRG 49是否是马甲客户的帮凶,还是普通受欺诈的无辜百姓。
idList = []
statement = "match (a)-[r1]->(common)<-[r2]-(b) where id(a)='0f1061e6-e903-11eb-8084-643e8cb8138c' and id(b)='1XX725080460' return common"
result = test.get_cypher(statement, idList)
通过地址和电话信息发现羊毛党可能为:
print(result)
[{'columns': ['common'], 'data': [{'row': [{'billing_street': '李新庄镇', 'billing_province': '山东省', 'billing_city': '菏泽市', 'email_address': 'anonymized@corporation.com', 'identity_id': '623062109986XXXX', 'last_activity_date': '2018-10-24', 'customer_name': 'VRG 7', 'billing_district': '单县', 'created_date': '2018-10-24'}], 'meta': [{'id': '0011R00001z10NgQAI', 'type': 'node', 'labels': ['customer']}]}, {'row': [{'billing_street': '李新庄镇', 'billing_province': '山东省', 'billing_city': '菏泽市', 'email_address': 'anonymized@corporation.com', 'identity_id': '623062109986XXXX', 'last_activity_date': '2018-10-24', 'customer_name': 'VRG 48', 'billing_district': '单县', 'created_date': '2018-10-24'}], 'meta': [{'id': '0013600000FSw9KAAT', 'type': 'node', 'labels': ['customer']}]}]}]
statement = "match (a)-[r1]->(common)<-[r2]-(b), (common)-[r3]->(c) where id(a)='0f1061e6-e903-11eb-8084-643e8cb8138c' and id(b)='1XX725080460' return c"
result = test.get_cypher(statement, idList)
与该羊毛党具有较大相关性的客户为:
print(result)
[{'columns': ['c'], 'data': [{'row': [{'billing_street': '东二营镇', 'billing_province': '天津市', 'billing_city': '市辖区', 'email_address': 'anonymized@corporation.com', 'identity_id': '623062573189XXXX', 'last_activity_date': '2018-10-24', 'customer_name': 'VRG 8', 'billing_district': '蓟州区', 'created_date': '2018-10-24'}], 'meta': [{'id': '0011R00001z1109QAA', 'type': 'node', 'labels': ['customer']}]}, {'row': [{'billing_street': '四平市铁东区城东乡', 'billing_province': '吉林省', 'billing_city': '四平市', 'email_address': 'anonymized@corporation.com', 'identity_id': '6226632105938XXXX', 'last_activity_date': '2018-08-02', 'customer_name': 'VRG 49', 'billing_district': '铁东区', 'created_date': '2016-04-10'}], 'meta': [{'id': '0013600000FSwALAA1', 'type': 'node', 'labels': ['customer']}]}]}]
time.sleep(5)
社群管控
商户的裂变呈现树状结构,较为顶层的商户往往体量较大,收益较高。然而,底层商户的频繁变动可能对顶层商户的收益产生影响。
平台希望管控好底层商户从而实现顶层商户的稳定。
本案例中,针对给定的顶层商户(0031R00001vSBTkQAO),监控其发展的商户是否较为稳定。首先,检测出给定商户所在的社群,然后,通过删边操作模拟随时间变化的下线流失,并再次检测给定商户社群,最后,给出变化的商户的名单。通过检测发现,0031R00001vRlHbQAK、0031R00001vRlYiQAK、0031R00001uqjUQQAY、0031R00001uoQr5QAE、0031R00001vS9WcQAK、0031R00001vSB7DQAW这六个下线脱离了与给定顶层商户的联系并自立门户,将对给定顶层商户产生一定的经济损失。
job_id = test.connected_component()
result = test.get_job(job_id)
old_community_info = test.get_community_info(result)
monitor_id = '0031R00001vSBTkQAO'
删边模拟顶层客户下线的流失:
result = test.delete_edge('Samantha-Mars', '0031R00001uoQr5QAE')
job_id = test.connected_component()
result = test.get_job(job_id)
test.add_edge('Samantha-Mars', '0031R00001uoQr5QAE', 'transfer')
'success'
new_community_info = test.get_community_info(result)
change_id_list = test.community_change_info(monitor_id, old_community_info, new_community_info)
变化的商户为:
print(change_id_list)
['0031R00001vRlYiQAK', '0031R00001vSB7DQAW', '0031R00001uqjUQQAY', '0031R00001vS9WcQAK', '0031R00001vRlHbQAK', '0031R00001uoQr5QAE']
- 点赞
- 收藏
- 关注作者
评论(0)