别名发现实战:候选召回与判别打分全流程解析

📅 发布时间:2026/10/20 13:23:55
别名发现实战:候选召回与判别打分全流程解析
自然语言处理里有个特别容易被忽略、又特别磨人的问题就是别名发现。做搜索、做知识图谱、做电商的同学多少都碰过这种场景用户明明输入的是“某数字科技”库里存的是“四零九科技有限公司”因为字面差异太大直接匹配不上。公众号这边更新NLP系列写到第五十篇我也第一次正式把“别名发现”当成一个独立的建模问题完整跑了一遍“候选召回 判别打分”的流程中间踩了不少坑也把几个原本觉得含糊的东西彻底想明白了。这篇就把这次尝试的完整思路、关键技术选择和实际操作过程中的教训记下来给同样在折腾实体对齐、别名挖掘、搜索召回问题的朋友做个参考。这个任务说小很小说大也很大。小到可以做一份同义词词典大到可以支撑一个千万级实体的知识库去重和检索。我这次的定位是“第一步”不做全量图中推理不做复杂预训练模型先把数据、召回、判别模型和评测跑通再考虑后续扩展。1. 这个项目到底在解决什么问题1.1 别名的典型场景和业务痛点别名Alias指的是同一个实体在不同语境下的不同称呼。举个例子一个商家可能有注册全称“某某市四零九网络科技有限公司”对外品牌叫“四零九科技”用户在评论里可能写成“409公司”在搜索时可能直接输入“四零九”。这些表达指向的是同一个底层实体但在字符串层面几乎没有任何相关性。以前遇到这种问题多数团队会直接用规则硬怼。常见的做法是维护一份“别名映射表”把高频别名人工录进去再写几条字符串归一化规则比如去掉“有限公司”、把中文数字转成阿拉伯数字。规则表用久了会有两个明显问题一个是维护成本越来越离谱新词永远追不上用户创造力的速度另一个是规则之间互相打架比如“某银行”和“某商业银行”到底算不算别名需要人工反复拍板边界情况多得让人头皮发麻。更有意思的是别名的粒度在不同场景中还不一样。在商品库中可能是“某某旗舰店”和“某某官方店”的关系在人物库中可能是“某老师”和“某教授”的关系在地点库中可能是“某地铁站”和“某枢纽站”的关系。所以做这个项目的第一步不是急着选模型而是和业务方把“别名”的定义边界划定避免后面验收时各说各话。1.2 两类主流的实现路线业内做别名发现大体上可以分成两派。一类是基于规则加词典的路线适合实体数量少、领域非常封闭、命名规律强的场景。比如身份证字段、银行机构代码这类有强规范约束的数据建一套完备的归一化规则比训练模型更靠谱。但这类方案几乎无法泛化换一个领域就得重新写一遍规则而且面对“有意思”“没意义”这类人类自由发挥的昵称型别名时很容易转为无效。另一类是数据驱动路线核心思路是“候选召回 判别排序”。先通过一些宽泛的字符串或语义手段把每个实体的潜在别名捞出来再用一个分类模型判断“两个名字到底是不是同一个实体”。这类方案工程上更复杂但迁移到新领域时只需要重新标注一批数据模型依然能工作扩展性明显更好。我这次选择的是第二类路线原因很直接我手上的实体域比较大别名构成又比较复杂纯规则大概率会在第一个月就把人消耗完。数据驱动虽然前期要投入数据标注和特征工程但后面无论是迭代还是换领域收益都更稳定。1.3 为什么第一版必须做“粗召回 精排”可能有人会问既然要判断两个名字是不是同一个实体为什么不直接全量两两比较理论上当然可以做但实际数据量稍微一上去就完了。假设实体库里有 100 万个实体两两组合就是万亿级别的关系对哪怕只用字符串距离这种轻量计算单机上也要跑到地老天荒。“粗召回 精排”是把问题拆成两步。第一步先用便宜的计算手段比如编辑距离、n元文法倒排索引把每个实体最有可能的几百个候选别名捞出来。第二步再对候选对用判别模型打分最终种下别名关系。这个架构的本质是把昂贵的语义判断留到更少的数据上换来工程上的可扩展性。这个取舍看起来没什么技术含量但在实际项目中非常关键。很多方案一开始就上语义向量、上大模型结果候选生成阶段迟迟做不完训练数据都无法有效构造。我个人的体会是先低成本跑通一条基线把数据流、评测方式和基础设施稳定下来再回头优化任何一个环节都来得及。2. 数据准备整个项目的隐形护城河2.1 候选样本都从哪里来训练一个判别模型的先决条件是有一批“正例”和“负例”。正例是两个字符串指向同一个真实实体负例是两个字符串看起来沾边但指向不同实体。正例数据最容易想到的是业务侧沉淀的种子别名比如客服工单里的同义映射、用户搜索后点击同一结果的行为记录。搜索点击日志是非常高质量的弱监督来源用户在搜索框输入“四零九科技”然后点击了详情页“四零九科技有限公司”这两条文本就有很高的概率构成别名关系。再进一步可以从百科页面的重定向、引号备注、括号注释中抽取候选比如“某大厦又称某中心”这种结构就是天然的别名标注。负例数据相对麻烦一些。负例不能随便抽两个完全不相干的字符串那样的负例太简单模型学不到真正的判别能力。比较好的负例来源是“召回链路里给出的错误候选”也就是本来通过字符串相似看到了一起但实际属于不同实体的对。这种负例最贴合线上真实分布。我这次搭建数据管道时把数据来源分成三块历史交互日志、人工种子别名、以及自动挖掘生成的候选对。其中日志量最大人工种子质量最高自动挖掘用来补足长尾场景。三类数据合并后先做一轮严格清洗再去标注。2.2 标注规则的制定和注意点别名标注看着是小事但规则定不好模型训练必翻车。最初我把标注任务外包给协作的同学时只给了一句“判断两个名字是不是指同一个东西”结果发现不同标注员的判断标准差异非常大。有人把“简称”当别名有人把“叫错的名字”也当别名还有人把同一实体在不同时间的旧新名字都算上导致数据集噪声在不知不觉中被拉高。后来重新梳理了标注规范明确四点第一别名要求指向同一个现实实体但字符串形式不同第二过度称呼、错别字、语音输入产生的谐音只要在现实语境中确实指向同一实体也可以作为正例第三上下位概念不算别名“某大学”和“某大学计算机学院”是不同粒度的实体第四拿不准的个案必须标“不确定”选项而不是强行二选一。标注一致性也不能忽视。我抽查了两组标注员的重合率初期只有不到八成发现大家对人名类、地点类的判断标准分歧特别大。后来把分歧样本拿出来逐条讨论补了几条细则一致性才明显提升。数据质量这块没有捷径前期多花一点时间后面模型训练会省一大笔力气。2.3 弱监督数据怎么用才稳用搜索点击日志、共现统计这类弱监督数据时最大风险是“虚假共现”。用户可能在同一时间搜索并点击了两个不同的实体但那只是因为列表页把两个实体排在了一起不代表它们是别名。很多早期做实体链接的项目都栽在这种“伪正例”上面。我的处理办法是多层过滤。第一层是点击归因只有点击了结果页且停留时间达到某个阈值的记录才保留排除误触。第二层是频次过滤同一个候选对至少在多个独立会话中出现过才接受为正例。第三层是文本归一化校验如果两个字符串完全一致那不需要模型来判直接合并即可。弱监督数据的角色定位也要清楚。我把它当成“候选生成器”而不是“标注替换品”。批量弱监督样本会拿来扩充候选池、做召回评测但真正用于训练判别模型的核心数据集一定是经过人工抽检和修正的高置信样本。数据管道里必须有这一层“降噪闸门”否则模型训练出来的信心分数根本靠不住。3. 第一层候选召回的实现细节3.1 基于编辑距离的召回怎么调优召回是别名发现的地基。如果候选池里根本没有正确答案判别模型再强也白搭。所以我花了比较多的时间在召回这一层目标只有一个高召回宁可牺牲一些精确率后面还有精排模型兜底。最早用的就是经典的编辑距离也就是把一个字符串变成另一个字符串所需的最少增删改次数。这个指标实现简单对短字符场景特别有效。但直接用原始编辑距离有两个问题一个是“长字符串被短字符串支配”比如“某市四零九网络科技有限公司”和“四零九科技”编辑距离的绝对值虽然大但相对长度来看它们高度相似另一个问题是中文特有的音近字、形近字编辑距离完全感知不到。针对第一个问题我用的是归一化编辑距离也就是编辑距离除以两个字符串中较长的长度把阈值统一到 0 到 1 的区间再结合绝对阈值控制极短串的误召回。针对第二个问题我加了一个带权重的编辑距离对“音近字”替换的惩罚降低对“数字与中文数字互换”的替换也降低比如“409”和“四零九”之间的距离可以明显小于“409”和“四一零”之间的距离。带权重的编辑距离本质上是在编辑距离里面塞先验知识实现起来也不复杂改成动态规划时把替换代价从固定 1 换成一张结构映射表就行。第一版跑出来的效果很明显谐音、中文数字这类以前完全召不回的样本基本都能进候选池了。3.2 用 n 元文法做倒排召回编辑距离召回的缺点是计算成本高而且对部分重合的句子不敏感。比如“某商业银行”和“某银行”之间编辑距离会因为增加“商业”两个字而被拉大但 Jaccard 相似度能看到它们共同拥有“某银行”这一段。在中文这种没有显式分隔符的语言里基于字符的 n 元文法n-gram召回非常实用。做法也很直白把每个实体名切成字符级别的二元文法比如“四零九科技”会切出“四零”“零九”“九科”“科技”四个二词片段然后为每个片断建倒排索引把它映射到包含该片段的所有实体名。查询一个新的候选字符串时同样切出它的二元文法去索引里取回所有共享至少一个文法的实体再用一个 Jaccard 系数或重合比例过滤掉噪声。实际调参中我发现 n 取 2 在中文场景下平衡得最好。n 取 1 召回率更高但候选量爆炸每个单字符能撞出一大堆无关结果n 取 3 精确性更好但对简繁体、错别字的容忍度又变差。还有一个细节是在切分前必须做字符归一化比如全角转半角、繁体转简体否则同一个词会因为字符编码差异被拆成两个完全不相关的文文法。3.3 用向量召回补足语义泛化字符串召回解决的是“长得像”的问题但有一类别名完全不像比如某公司对外品牌叫“蓝鲸”实际注册名叫“某海域科技发展有限公司”。这种别名没有任何字符层面的重叠编辑距离和 n 元文法全部失效只能靠语义泛化来补。我用了两套语义召回方案。第一套是传统一点的套路对实体名做分词把每个词的词向量平均成一个句向量然后用余弦相似度在向量库中做最近邻检索。这一套的好处是部署简单、可控性好在词语级语义上有不错的泛化能力。第二套是直接用一个中文预训练模型把实体名编码成向量再走同样的向量检索效果更好但资源占用也更高。需要提醒的是向量召回不能大包大揽。它对高频实体的命名语义学得好但对低频、罕见的专有名词向量表示往往不稳定容易把完全不沾边的名称拉得很近。所以我并没有让向量召回替代前面的字符串召回而是把它作为第三路候选来源最后做多路合并。三个召回源各管一段漏掉的概率才会真正降下来。3.4 召回合并且去重的关键细节多路召回会带来的新问题是候选集合膨胀。如果一个实体在三路召回中分别命中了几百个候选合并起来可能上千个精排模型直接处理这么多候选性能和效果都有压力。所以我做了一个轻量级“候选闸门”。合并规则是这样的每路过来的候选都带一个基础打分编辑器距离给一个正则化相似度n 元文法给 Jaccard 或重合比例向量召回给余弦相似度。我在合并前设置了一个硬阈值和 top-k 上限去掉相似度接近零的明显噪声再对三路候选做一个并集保留。遇到同一个候选从多路回来时得分采用路数叠加加分的方式因为多路证据往往比单路更可靠。去重也不能只看字符串完全相同。类似“四零九科技”和“四零九科技公司”这种单纯带后缀的情况我通过二次归一化处理去掉“公司”“有限”“集团”这类高频噪音词后再判重合并候选数量能再压缩不少。合并后还会再一次过滤掉与实体自身完全一致的候选避免把“自己”也送进精排层。4. 第二层判别模型的选择与训练4.1 特征工程先做对模型才有意义召回层送来的每个候选对最终要由判别模型给一个“是否别名”的分数。第一版我没有选择一上手就用预训练模型直接做二分类而是先搭了一套特征工程原因只有一个训练数据量和问题规模还撑不起复杂的模型结构而且可解释性对排查问题太重要。特征大致分成四组。第一组是字符相似度特征包括归一化编辑距离、字符 Jaccard、最长公共子序列长度、二元文法 Jaccard 等。第二组是长度差特征比如长度差绝对值、长度比对、是否是包含关系。第三组是子串特征比如较短字符串是否出现在较长字符串中较短字符串中连续公共片段的占比。第四组是语义特征比如分词向量的余弦相似度以及预训练编码向量的相似度。特征工程看着琐碎真正影响模型效果的是“归一化”。编辑距离如果不归一化长串会在特征空间里占据无意义的优势长度差不处理短别名对和长实体对会混在一起。我把所有连续型特征都做了处理尽量压缩在统一区间再让模型去学组合关系这样梯度提升树建树时更容易切出稳定的节点。这里还有一个反直觉的经验特征不要过度叠加。我一开始把几十个特征全部塞进去模型训练时间变长而且不少特征高度相关造成了冗余。后来用特征重要性排序筛掉三分之一线上和验证集的表现反而更稳。少而精的特征比一锅端的效果更可控。4.2 为什么第一版选了梯度提升树而不是预训练模型做中文文本分类很多人默认就会想到用预训练模型。但我这次在判别层第一版坚定选择了梯度提升树这里用了主流的 LightGBM 实现主要还是从现实条件出发。首先是数据量。预训练模型需要足够多的样本才能微调出稳定效果而我第一版能用的人工标注数据只有几万对。这个量级下梯度提升树配合手工特征完全能和预训练模型掰手腕甚至因为特征中有大量人工知识在小样本上反而更稳。其次是迭代速度。梯度提升树训练以分钟为单位排错、试特征非常快预训练模型一轮微调往往要小时级别第一版探索阶段完全耗不起。维度梯度提升树方案预训练模型方案数据需求量几万对即可启动通常需要更大规模才能发挥优势训练与迭代速度分钟级验证特征很方便小时级调试成本高可解释性特征重要性、决策树路径清晰依赖注意力权重解释效果有限对字符表面特征的掌握强特征工程可精准控制较弱需额外设计也会反映在字符层面硬件资源单机 CPU 基本够用需要较大显存或推理加速当然这不代表预训练模型没用。我的计划是后续等数据累积到几十万对时把预训练模型作为判别层的主力之一与梯度提升树做集成或者干脆替换。第一版先把地基打好既不盲从潮流也不把自己锁死。4.3 训练集划分和指标设计的坑训练集划分如果不仔细会直接导致评估结果虚高。最常见的坑就是随机划分时同一个实体的多个候选对被同时分进训练集和验证集造成数据泄漏。比如实体 A 的别名 a1 在训练集别名 a2 在验证集两者高度相似验证分数自然漂亮。正确做法是“实体级划分”先按真实实体凭证 Group 分组再整组划分保证同一个实体的所有候选对只出现在同一个数据分区中。负样本采样比例也是个容易踩坑的点。线上真实的候选池里绝大多数候选对都是负例正负比可能只有 1:100 甚至更低。训练时如果正负比设为 1:1模型虽然收敛快但上线后会因为正例预测倾向过高而打出一堆误报。我最后训练时正负比控制在 1:3 到 1:5 之间同时保留了一个完全模拟线上分布的测试集用来校准和观察实际效果。指标方面我除了看常规的准确率、召回率、F1还额外关心两个东西一个是 PK也就是在给每个实体取前 K 个候选时真实别名有多少被排进去了这个直接反映精排层在召回后的“头部命中率”另一个是覆盖率也就是有多少实体能至少被一个候选正确覆盖。这两个指标比总体 F1 更能反映在实际业务中的可用性。5. 离线评测与失败案例分析5.1 指标怎么定才算真正有效第一版训练完成后我没有急着看训练集上的指标而是直接换了一套完全独立的评测集做盲评。评测集从留出的日志和人工标注中构造和训练数据的分布有细微差异这样能暴露出模型真正的泛化能力。评测维度分成四个层面字面同义、简称与全称、谐音与错别字、语义联想。我专门为每一类设计了评测子集而不是打个笼统的准确率就完事。字面同义类最常见模型表现也最好简称与全称类因为长度差异大初期效果一般谐音与错别字类在加入权重编辑距离和特征工程后有明显提升语义联想类最难比如“蓝鲸”指向“某海域科技”这类依赖向量召回和语义特征第一版效果并不理想。这个分层评测最大的价值是告诉我“哪里还有机会”而不是只看一个总分自我安慰。后面每次迭代我都会对照这四个子集看涨跌防止某一类指标被牺牲掉。5.2 第一版模型的主要错误类型人工检查误报样本时我把错误分成三类来盘。第一类是“包含词误伤”比如“某理工大学”和“某工业大学”共享了很多常用词算出的字符相似度很高但实际上是完全不同的实体。这类错误说明模型还在过度依赖表面重合没有真正抓住限定词的区别。第二类是“前后缀噪声”比如“某科技有限公司”和“某技术有限公司”之间只差一个“科”字从字符角度看相似度极高但一个是做信息技术的一个是做机械技术的八竿子打不着。这种问题靠特征工程很难彻底解决需要在负样本里保留大量这种“高度相似但语义不同”的硬负例逼着模型学边界。第三类是“低频长尾漏召回”典型表现为一个实体只有一个别名而且别名特别口语化比如“大苹果”指向“某之珠大厦”。这类样本在整个数据中占比不高所以对总体指标影响不大但业务上往往就是这种长尾别名最值钱。第一版模型对这类样本几乎无计可施我把它们单独标记出来留给后续用知识库、上下文信息来补。5.3 误报样本的复盘挑两个典型案例说说复盘过程。案例一候选对“某医科大学附属医院”和“某医学院附属医院”模型给出接近 0.9 的高概率人工标注却是负例。失败原因很直接两个字符串长度大、公共子串多“医科”“大学”“附属医院”这些片段的权重在特征里太强模型忽略了单个词的差异只能靠语义判断。这个案例让我意识到单纯字符层面的特征已经到了瓶颈必须引入外部知识或者说更细粒度的限定信息。案例二候选对“某地方商业银行”和“某市商业银行”人工标注是正例但模型只给了 0.4 分。原因在于负样本中包含了大量包含“地方商业银行”和“某市商业银行”的不同机构对模型学到了“这两个词出现的场景通常不是同一个实体”。这个案例暴露了样本分布问题把地域性的歧义样本和真正同义样本混在一起模型很难分清。后来我在训练时加强了地域限定词的匹配特征并增加了一组专门标注的同义样本分数才提上来。复盘的价值不在于推翻模型而在于找到数据和特征的偏差。每跑一轮评测我都能从误报里清晰地看到下一步该往哪里走。6. 常见问题排查实录与避坑指南6.1 候选量太大导致接口延迟第一次上线 demo 时我发现单个实体的候选召回平均耗时超过预算值主要瓶颈在向量召回那一路。当时向量召回用的是精确最近邻实体多之后内存和计算都成了问题。后来把精确检索换成了近似最近邻索引参数调到召回率下降不超过 1 个百分点的水平延迟直接从几百毫秒降到了几十毫秒。另一个隐性问题是 n 元文法倒排索引的内存占用。当实体以千万计二元文法索引会让内存迅速膨胀。我做了两件事一是把高无效的单字符文文法过滤掉比如“的”“了”“有”这类高频噪音字符不建索引二是对超高频文文法设置截断只保留其下的一部分候选防止一个索引桶拖垮内存。这些优化都在召回效果基本不变的前提下完成属于性价比非常高的工程动作。6.2 中文文本标准化不一致文本标准化看着基础做不好会处处碰壁。繁体简体、全角半角、大小写、连续空格、标点符号这些因素如果不在最前面统一掉后面所有特征计算都会引入噪声。比如“”和“Tencent”一个全角一个半角在字符层面是完全不同的串再比如“大厦”和“大廈”简繁体的差别同样会切断 n 元文法的重合。我维护了一个标准化的处理流水线顺序固定全角转半角繁体转简体去除标点去除连续空白统一大小写最后再做数字和中文数字的映射解析。特别要注意数字解析因为“409”和“四零九”在业务中经常指同一个编号我的标准化层会保留一份数字变体供编辑距离和特征计算时单独使用。标准化也带来一个副作用把特征空间压缩后模型会看到更多原本不同的字符串变得相同从而影响训练正负例的区分。我的解决办法是训练和预测共用同一套标准化函数保证输入口径一致并把原始字符串的差异度单独作为一个特征保留下来让模型知道“尽管标准化后相同但原串可能并不同”。6.3 标签置信度和实际业务并不完全对齐模型训练时用的是二分类标签但线上业务可能更需要的是置信度排序。很多候选对哪怕分不高只要排在前列业务上就能收到“人工确认”流程里反之一个高分样本如果误报造成的负面影响会非常直接。所以我实际上是把模型输出当作排序分来用而不是直接套一个硬阈值。上线前我用网格搜索找了一组阈值保证误报率在可容忍范围内。阈值不是一个定死的数字而是根据业务场景动态变化的如果业务方要求高召回就把阈值调低如果要求高精确就调高。这就是为什么第一版必须选可解释性强的模型否则调阈值时根本不知道模型对某个样本的置信度到底受哪些因素影响。6.4 训练推理特征不一致有一个特别隐蔽的坑训练和推理时特征分布不一致。我在第一版代码里训练时用了一部分“标准化后的字符串”计算特征但推理时忘了先做同样的标准化导致模型看到的数据形态完全不对线上效果断崖式下跌。这种问题不在模型容量纯粹是工程上的粗心。排查方法也很简单粗暴。我把线上日志里的输入与特征抽取结果全部记录到一张调试表中随机抽几十条样本和训练时同一输入的特征做逐字段对比。发现偏差之后我写了一个统一入口函数训练和推理都只允许调用这一个函数从代码结构上杜绝了两套逻辑。大家以后如果遇到模型线上效果奇怪先别急着调参第一步去看特征一致性。7. 后续还能怎么扩展7.1 从离散特征走向向量特征融合第一版特征工程已经验证了“字符相似度 梯度提升树”是一条稳妥的基线路径。后续我会逐步把预训练模型编码向量融进特征集试验模型不管是作为独立特征还是与文本相似度特征一起进入树模型评价都保留分层测试确保原有效果不回退。更激进一点的方案是把候选对直接送入一个深度交互模型让模型在字符和语义层面同时进行匹配判断。这类方案在别名判别的经典评测上通常能刷高几个点但对训练数据量和资源的要求也明显更高。我会等数据规模达到一定量级后再上避免过早给基础设施背负重。7.2 把别名发现融入知识图谱更新链路这个项目的最终价值不只是离线出一份别名表而是让它在线上持续运转。我计划把模型做成一个周期性管道每天拉取新增日志经过弱监督候选生成和判别模型打分产出高置信的别名关系再人工抽检后写入知识图谱。这样图谱里的实体别名可以不断自我更新而不是几个月才人工同步一次。这也会带来新的技术问题比如增量训练、概念漂移、人工确认环节的效率优化等。目前我已经在其中一个模块上做了原型逻辑上是通顺的只是需要更多稳定性打磨。7.3 一个值得尝试的方向主动学习标注成本是别名发现项目绕不开的坎。第一版我主要靠人工标注撑起来但如果想覆盖更多领域必须引入主动学习让模型自己挑出“最不确定”的样本交给人工标注而不是随机抽样标注。主动学习的具体做法是用当前模型跑一遍未标注候选池把置信度落在中间区间的样本优先挑出来送标注。这些样本信息量最大标注完后再训练对模型边界的影响也最明显。我预计这样可以把达到目标效果所需的人工标注量压缩 30% 到 50%这对项目后续扩张意义很大。把整个第一版跑完我最深的体会是别名发现真正难的不是模型结构而是怎么把数据、特征、召回和评测这四个环节串成一条可持续迭代的流水线。很多团队一上来就想用最前沿的模型结果往往卡在候选召回不达标、负样本质量不干净这类“不性感”的问题上。我这次选择先稳扎稳打把每个环节都跑通并留下清晰的优化空间从当前结果看这条路线是值得继续走下去的。如果大家也正在做实体对齐或者别名挖掘建议先别急着堆模型把手里的数据管道和特征一致性打磨好你会发现在这一步省下的时间远比后面调模型节省得多。