You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spacy Matcher匹配优先级配置 实现最长匹配、去重叠匹配

spaCy物理量模式匹配问题修复方案

问题复现

基于spaCy构建物理模式库,目标是实现时间、速度两类物理量的灵活检测,初始编写的匹配规则与执行代码如下:

import spacy
from spacy.matcher import Matcher

nlp = spacy.load("en_core_web_sm")
time_pattern = [
    [
    {'LIKE_NUM': True, 'OP': '?'},
    {'LOWER':{'IN': ['time', 's','h','min']}},
    {'LOWER': {"IN": ['maximum','minimum','min','max']}, 'OP':'?'}
    ]
]
speed_pattern = [
    [
    {'LIKE_NUM': True, 'OP': '?'},
    {'LOWER':{"IN": ['km', 'm']}},
    {'IS_PUNCT': True},
    {'LOWER' : {"IN": ['h','hour','s','min']}}
    ]
]


matcher=Matcher(nlp.vocab, validate =True)
matcher.add("SPEED", speed_pattern)
matcher.add("TIME", time_pattern)

doc=nlp("a certain time, more about 23 min, can't get above 25 km/h")

for id_match, start, end in matcher(doc):
    match_label=nlp.vocab[id_match].text
    print(match_label, '<--', doc[start:end])

对测试文本a certain time, more about 23 min, can't get above 25 km/h运行后,得到不符合预期的匹配结果:

  • TIME <-- time
  • TIME <-- 23 min
  • TIME <-- min
  • SPEED <-- 25 km/h
  • SPEED <-- km/h
  • TIME <-- h

核心问题共两点:

  1. 短匹配未被过滤:同一语义片段同时触发长、短两个匹配时,短匹配被保留,例如23 min片段同时命中长度为2的完整匹配和长度为1的min短匹配
  2. 重叠重复匹配:已被长规则命中的片段内token,被其他规则重复识别,例如速度匹配25 km/h中的h,又被时间规则识别为时间量

预期效果为:同一语义片段仅匹配一次,优先匹配最长符合规则的片段,已被匹配的token不再参与后续匹配。


修复方案

spaCy原生Matcher默认返回所有命中的候选片段,不会自动做最长优先和去重,按以下两步调整即可达到要求:

第一步:优化匹配规则,减少无意义裸单位匹配

原规则将数值前缀设为可选,会大量触发不带数值的裸单位匹配(如单独的min/h/km/h),调整规则逻辑:

  • 带单位的物理量匹配要求数值必填,将数值匹配的OP参数从?(可选)改为+(至少匹配1个)
  • 泛指的time概念单独拆为一条规则,避免和单位匹配逻辑冲突
  • 规则添加时优先加高优先级规则:速度规则优先级高于时间规则(速度模式本身包含时间单位,优先匹配更长的速度语义)

调整后的规则代码:

time_pattern = [
    [
    # 带数值的时间量,数值必填
    {'LIKE_NUM': True, 'OP': '+'},
    {'LOWER':{'IN': ['s','h','min','second','minute','hour']}},
    {'LOWER': {"IN": ['maximum','minimum','min','max']}, 'OP':'?'}
    ],
    # 单独匹配泛指的time概念
    [{'LOWER': 'time'}]
]
speed_pattern = [
    [
    {'LIKE_NUM': True, 'OP': '+'},
    {'LOWER':{"IN": ['km', 'm', 'kilometer', 'meter']}},
    {'IS_PUNCT': True},
    {'LOWER' : {"IN": ['h','hour','s','min','second','minute']}}
    ]
]

matcher=Matcher(nlp.vocab, validate =True)
# 先添加高优先级的速度规则
matcher.add("SPEED", speed_pattern)
matcher.add("TIME", time_pattern)

第二步:增加最长非重叠过滤逻辑

拿到Matcher返回的所有候选匹配后,加一层过滤逻辑:

  1. 所有候选按匹配长度从长到短排序,长度相同的保持原有规则优先级
  2. 维护已占用token的位置集合,遍历排序后的候选:如果当前匹配的所有token都未被占用,则保留该匹配,同时标记对应token为已占用;如果存在任意token已被占用,直接丢弃当前匹配

过滤逻辑代码:

all_matches = []
for match_id, start, end in matcher(doc):
    label = nlp.vocab[match_id].text
    # 存储格式:(负匹配长度, 起始位置, 结束位置, 标签),负长度用于升序排序时自动把长匹配排前面
    all_matches.append( (-(end-start), start, end, label) )

# 按长度降序排序,同长度匹配保持规则添加时的优先级
all_matches.sort()
used_tokens = set()
final_matches = []

for _, start, end, label in all_matches:
    token_range = set(range(start, end))
    # 仅当当前匹配和已匹配片段无重叠时保留
    if token_range.isdisjoint(used_tokens):
        final_matches.append( (label, doc[start:end]) )
        used_tokens.update(token_range)

# 输出最终匹配结果
for label, span in final_matches:
    print(label, '<--', span)

运行后得到符合预期的输出:

TIME <-- time
TIME <-- 23 min
SPEED <-- 25 km/h

无短片段重复匹配,无跨规则重叠识别,长匹配优先保留。


内容的提问来源于stack exchange,提问作者Shmou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 01:21:34