电商风控图模板

举报
HWCloudAI 发表于 2022/12/28 10:47:48 2022/12/28
【摘要】 电商风控互联网电商为扩张业务、推广产品,会采用奖励、返点、打折等手段实现用户和商户裂变。例如,商品的链接在商户或客户间转发时将会附带一标识ID,若商品被购买,具有该标识ID的商户或用户均会得到一笔奖励。而在裂变过程中,存在薅羊毛、虚开账号等损害平台或商家利益的行为。基于图模型的互联网电商风控,将提供客群管控、羊毛党发现等解决方案,有效帮助客户降低损失。 图模型以下是电商风控图模型元数据。 ...

电商风控

互联网电商为扩张业务、推广产品,会采用奖励、返点、打折等手段实现用户和商户裂变。例如,商品的链接在商户或客户间转发时将会附带一标识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']

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。