模式中具有可选后缀的 SpaCy Matcher 会报告同一文本的多个匹配项

问题描述 投票:0回答:1

使用以下匹配器规则:

{'label': 'R-1',
 'pattern': [{'TEXT': 'MyLabel'}, {'TEXT': ':', 'OP': '?'}],
 'greedy': 'LONGEST', }

文字:“MyLabel:一些价值”

我得到两个匹配:“MyLabel”和“MyLabel:”

对我来说,这非常令人惊讶 - 我原本以为“MyLabel:”上会出现一场比赛。 添加新的贪婪标志没有任何区别。

  • 这是预期行为还是错误?
  • 我该如何确定第二场比赛确实只是第一场比赛的子集?
  • 较短的比赛总是会在较长的比赛之前报告吗?

SpaCy 版本 3.7.5

nlp spacy matcher
1个回答
0
投票

我会说,您使用 SpaCy

Matcher
观察到的行为是预期的,并且它不是错误。当您使用
{'TEXT': ':', 'OP': '?'}
模式时,
OP: '?'
运算符意味着冒号是可选的,因此匹配器将生成较短和较长的匹配,如您所见。

说明:

  • 图案
    {'TEXT': 'MyLabel'}, {'TEXT': ':', 'OP': '?'}
  • 文字
    'MyLabel: Some Value'

因此对于这个模式,SpaCy 将尝试匹配:

    单独
  1. 'MyLabel'
    (因为冒号是可选的)。
  2. 'MyLabel:'
    (因为可以包含冒号)。

因此,您将得到两个匹配项:

'MyLabel'
'MyLabel:'

现在回答您的问题:

  1. 这是预期行为还是错误?

    • 这是预期的行为。
      OP: '?'
      运算符允许选择性地匹配冒号,从而导致多个匹配。
  2. 我该如何确定第二场比赛确实只是第一场比赛的子集?

    • 要确定一个匹配是否是另一个匹配的子集,您可以比较匹配的开始索引和结束索引。较长的匹配将具有相同的开始索引但不同的结束索引。现在我甚至使用 spacy 版本 3.7.5 编写了下面的代码,请参阅下面的详细信息
pip show spacy
Name: spacy
Version: 3.7.5
Summary: Industrial-strength Natural Language Processing (NLP) in Python
Home-page: https://spacy.io
Author: Explosion
Author-email: [email protected]
License: MIT
Location: /home/adesoji/Downloads/visis-backend-assessment-Adesoji/visisenv/lib/python3.11/site-packages
Requires: catalogue, cymem, jinja2, langcodes, murmurhash, numpy, packaging, preshed, pydantic, requests, setuptools, spacy-legacy, spacy-loggers, srsly, thinc, tqdm, typer, wasabi, weasel
Required-by: en-core-web-sm

现在代码示例:

import spacy
from spacy.matcher import Matcher

nlp = spacy.load("en_core_web_sm")
doc = nlp("MyLabel: Some Value")

matcher = Matcher(nlp.vocab)
pattern = [{'TEXT': 'MyLabel'}, {'TEXT': ':', 'OP': '?'}]
matcher.add("R-1", [pattern])

matches = matcher(doc)
for match_id, start, end in matches:
    span = doc[start:end]
    print(f"Match: {span.text}, Start: {start}, End: {end}")

# Now, we Determine if one match is a subset of another
matches.sort(key=lambda x: (x[1], -x[2]))  # Sort by start index, then by end index descending
filtered_matches = []
last_end = -1
for match_id, start, end in matches:
    if start >= last_end:  # This is for Avoiding adding subsets
        filtered_matches.append((match_id, start, end))
        last_end = end

for match_id, start, end in filtered_matches:
    span = doc[start:end]
    print(f"Filtered Match: {span.text}")

现在,此代码将过滤掉较短的匹配项,您的输出将是

Match: MyLabel, Start: 0, End: 1
Match: MyLabel:, Start: 0, End: 2
Filtered Match: MyLabel:   , you can see MYLabel: with the colon symbol there

  1. 现在较短的比赛总是会在较长的比赛之前报告吗?
    • 我不认为比赛不能保证按特定顺序进行报道。因此,为了处理这个问题,您可以按开始和结束索引对匹配进行排序,如上面的代码示例所示。现在,排序后,您现在可以过滤掉较长匹配的子集的匹配。

另一种替代解决方案:

如果您想确保只返回最长的匹配项,您可以更改定义模式的方式:

pattern = [{'TEXT': 'MyLabel'}, {'TEXT': ':', 'OP': '?', 'greedy': 'LONGEST'}]

请注意,

greedy
标志不会改变匹配本身的行为,而是会影响在某些自定义设置中处理重叠的方式。

现在回到我解释的摘要:

  • 由于可选的
    OP: '?'
    运算符,您所看到的行为是设计使然的。
  • 此外,您可以通过比较比赛的开始和结束索引来过滤出较短的比赛。
  • 此外,按开始和结束索引对匹配进行排序可以让您仅保留最长的、不重叠的匹配。
© www.soinside.com 2019 - 2024. All rights reserved.