4、实体识别与对齐:同名消歧、企业实体对齐、个人-企业关联识别
好,咱们进入第四章。这一章讲的是实体识别与对齐,说白了就是解决「同一个对象在不同数据源里长啥样」的问题。
我刚开始做金融风控的时候,遇到过一件特尴尬的事。两个系统里都出现了「张三」,一个显示信用良好,另一个显示逾期黑名单。结果呢?其实是两个不同的张三,同名不同人。那一次差点导致错误授信。嗯,从那以后,我对实体对齐这件事就特别上心。
4.1 同名消歧:别让「李逵」和「李鬼」混在一起
同名消歧,核心任务就是判断两条记录是不是指向同一个真实实体。
为什么会同名?说白了,中国14亿人,常用名就那么几千个。你想想看,「王伟」「张勇」这种名字,一个城市里能找出几十个。光靠名字匹配,肯定翻车。
我个人习惯的做法是:多维度特征联合判断。不能只看名字,还得看身份证号、出生日期、住址、手机号、甚至社交关系。
核心思路:将实体表示为特征向量,通过相似度计算判断是否同一人。
举个例子,我常用的消歧特征包括:
- 身份标识特征:身份证号(最硬)、护照号、社保号
- 生物特征:人脸特征(如果有)、指纹(极少用)
- 行为特征:常用IP、设备ID、登录地点
- 关系特征:共同联系人、共同企业、共同地址
这里有个避坑指南:我曾经只靠身份证号做消歧,结果发现很多数据源里身份证号是缺失的,或者被脱敏了。后来我改用「多特征加权融合」的方式,哪怕身份证号缺失,靠手机号+地址也能达到90%以上的准确率。
代码实现上,我一般这样写:
def entity_resolution(record_a, record_b, weights):
"""
实体消歧:计算两条记录的相似度
weights: 各特征的权重字典
"""
score = 0.0
total_weight = 0.0
# 身份证号匹配(最硬)
if record_a['id_card'] and record_b['id_card']:
if record_a['id_card'] == record_b['id_card']:
score += weights['id_card']
total_weight += weights['id_card']
# 手机号匹配
if record_a['phone'] and record_b['phone']:
if record_a['phone'] == record_b['phone']:
score += weights['phone']
total_weight += weights['phone']
# 地址相似度(用Jaccard或编辑距离)
if record_a['address'] and record_b['address']:
addr_sim = jaccard_similarity(record_a['address'], record_b['address'])
score += weights['address'] * addr_sim
total_weight += weights['address']
# 最终得分
final_score = score / total_weight if total_weight > 0 else 0
return final_score > THRESHOLD # 阈值需根据业务调优
小技巧:阈值不要拍脑袋定。我习惯用「验证集+KS统计量」来选最优阈值,这样能保证在召回率和精确率之间找到平衡点。
4.2 企业实体对齐:从「北京字节跳动」到「抖音有限公司」
企业实体对齐比个人消歧更复杂。为什么?因为企业名称变化多端啊。
你想想看,「阿里巴巴集团控股有限公司」和「阿里巴巴(中国)网络技术有限公司」是不是同一家?严格来说不是,但有关联。而「北京字节跳动科技有限公司」和「抖音有限公司」其实是同一家(字节跳动后来改名了)。
我遇到过最头疼的情况是:一个数据源里叫「深圳市腾讯计算机系统有限公司」,另一个叫「腾讯科技(深圳)有限公司」。名字差了好几个字,但其实是同一栋楼里的不同法律实体。
我的做法分三步:
- 标准化清洗:去掉「有限」「公司」「股份」等后缀,统一全角半角,去除空格
- 模糊匹配:用编辑距离、Jaccard相似度、甚至拼音相似度
- 知识库辅助:接入工商数据,用统一社会信用代码做硬匹配
这里我画了一张流程图,帮你理清企业对齐的整体逻辑:
你看,这个流程里最关键的是「阈值」怎么设。设高了,漏掉很多真实匹配;设低了,又会产生大量误报。我一般用网格搜索+业务验证来调参。
4.3 个人-企业关联识别:谁和谁是一伙的
这一块是金融关系网络里最值钱的信息。一个人可能同时是多家公司的法人、股东、高管。一家公司也可能通过层层持股控制另一家公司。
我做过一个项目,要识别一个借款人的「实际控制人」是谁。表面上看,借款人是A公司,法人是张三。但深挖下去,张三只是挂名,实际控制人是李四,而李四通过B公司、C公司间接持股A公司。
这种关联怎么识别?我总结了几种常见模式:
| 关联类型 | 典型特征 | 识别方法 |
|---|---|---|
| 法人-企业 | 工商登记中的法定代表人字段 | 直接匹配工商数据 |
| 股东-企业 | 持股比例、出资额 | 股权穿透分析 |
| 高管-企业 | 董事、监事、经理等职位 | 工商数据+企业年报 |
| 实际控制人 | 通过多层持股间接控制 | 股权链追踪(DFS/BFS) |
| 亲属关联 | 同住址、同手机、同IP | 多源数据交叉验证 |
注意:个人-企业关联识别最大的坑是「数据时效性」。工商数据更新有延迟,可能你查到的法人信息已经是半年前的了。我建议每次做关联分析前,先检查数据的时间戳,必要时用多个数据源交叉验证。
代码实现上,我习惯用图数据库来存储这些关联关系。Neo4j或者JanusGraph都行。下面是一个简单的股权穿透查询示例:
# 使用NetworkX模拟股权穿透
import networkx as nx
def find_ultimate_controller(G, company_node, threshold=0.5):
"""
找到公司的实际控制人
G: 股权关系图(节点=个人/企业,边=持股比例)
company_node: 目标公司
threshold: 控制权阈值(持股比例乘积 > threshold 视为控制)
"""
controllers = []
# 向上游追溯股东
for predecessor in G.predecessors(company_node):
edge_data = G.get_edge_data(predecessor, company_node)
share_ratio = edge_data.get('share_ratio', 0)
# 如果是个人股东
if G.nodes[predecessor]['type'] == 'person':
if share_ratio > threshold:
controllers.append((predecessor, share_ratio))
else:
# 如果是企业股东,递归追溯
sub_controllers = find_ultimate_controller(G, predecessor, threshold)
for person, ratio in sub_controllers:
combined_ratio = ratio * share_ratio
if combined_ratio > threshold:
controllers.append((person, combined_ratio))
return controllers
实战经验:股权穿透时要注意「循环持股」的问题。有些公司之间互相持股,形成死循环。我一般会设置最大深度(比如5层),同时用visited集合防止重复访问。
最后说一句,实体识别与对齐这件事,没有100%完美的方案。我做了这么多年,依然会遇到边界案例。但只要你把特征工程做扎实,多维度交叉验证,准确率做到95%以上是完全可行的。
嗯,这一章就到这里。记住:数据质量决定模型上限,实体对齐决定数据质量。