基于规则的匹配

查找短语和单词,并匹配实体
在原始文本上使用正则表达式匹配,spaCy的基于规则的匹配器引擎和组件不仅可以找到所需的单词和短语,还可以让您访问文档中的词及其相互关系。这意味着您可以轻松访问和分析周围的词,将跨度合并为单个词或加入到doc.ents中的命名实体中。

我应该使用规则还是训练一模型模型?
对于复杂的任务,通常最好训练一个基于统计的命名实体识别模型。但是,统计模型需要训练数据,因此在许多情况下,基于规则的方法更实用。在项目初始阶段尤其如此:你可以使用基于规则的方法作为数据收集过程的一部分,以帮助您“引导”统计模型。

如果您有一些示例并且希望您的系统能够基于这些示例进行泛化(generalize),那么就应该训练模型。如果在当前上下文有线索,它会更加有效。例如,如果您尝试检测人名或公司名称,您的应用程序可能会受益于统计命名实体识别模型。

如果你想在数据中查找有限数量的示例,那么基于规则的系统就是一个很好的选择,或者你可以找到一个词规则或者正则表达式来准确的表达一个模式
。例如,你可以通过基于规则的方法很好地处理国家名称、IP地址或URL等。

您还可以将这两种方法结合使用,可以使用规则改进统计模型,以处理非常具体的情况并提高准确性。有关详细信息,请参阅基于规则的实体识别部分

什么时候应该使用词匹配器(Matcher)和短语匹配器?
如果你已经拥有一个大型术语列表或一个由单个或多个token语组成的地名录,此时你想找到一个确切的实例,那么PhraseMacher就非常有用。
spaCy v2.1.0开始,您还可以通过LOWER属性进行快速且不区分大小写的匹配。

Matcher没有PhraseMatcher那么快,因为他会比较所有token的属性。但是,它允许你使用模型预测的词汇属性和语言特征为正在寻找的tokens编写一个非常抽象的表示。例如,你可以查找一个跟在“love”或“like”动词后,或可选限定符或者一个不少于10个字符的token后的名词,

Token-based匹配

spaCy自带规则匹配引擎Matcher, 对tokens的操作类似于正则表达式。规则可以引用token注释(例如标记text或tag_,或者如IS_PUNCT的标志)。规则匹配器还允许您传入自定义回调以处理匹配项 - 例如,可以合并实体和应用自定义标签。您还可以将模式与实体ID相关联,以允许进行一些基本的实体链接或消歧。要匹配大型术语列表,您可以使用 PhraseMatcher,它接受Doc对象作为匹配模式。

添加模式

假设我们想让 spaCy 找到三个token的组合:

  1. 小写形式与“hello”匹配的token,例如“Hello”或“HELLO”。
  2. is_punct标记设置为True的标记,即任何标点符号。
  3. 小写形式与“world”匹配的标记,例如“World”或“WORLD”。
[{"LOWER": "hello"}, {"IS_PUNCT": True}, {"LOWER": "world"}]

!!!重要的提示
在编写模式时,记住每个字典代表一个token。如果 spaCy 的分词处理与模式中定义的token不匹配,则该模式不会产生任何结果。在开发复杂模式时,请确保根据 spaCy 的标记化检查示例:

doc = nlp("A complex-example,!")
print([token.text for token in doc])

首先,我们用一个词汇来初始化Matcher。匹配器必须始终与其操作的文档共享相同的词汇。我们现在可以调用matcher.add()并传入一个ID和一个模式列表作为参数。
spaCy v 3.0 · Python 3 基于 Binder

import spacy
from spacy.matcher import Matcher

nlp = spacy.load("en_core_web_sm")
matcher = Matcher(nlp.vocab)
# Add match ID "HelloWorld" with no callback and one pattern
pattern = [{"LOWER": "hello"}, {"IS_PUNCT": True}, {"LOWER": "world"}]
# 将会匹配到“hello”+“标点”+“world”
matcher.add("HelloWorld", [pattern])

doc = nlp("Hello, world! Hello world!")
matches = matcher(doc)
for match_id, start, end in matches:
    string_id = nlp.vocab.strings[match_id]  # Get string representation
    span = doc[start:end]  # The matched span
    print(match_id, string_id, start, end, span.text)

结果:
15578876784678163569 HelloWorld 0 3 Hello, world

匹配器返回一个(match_id, start, end)元组的列表——在本例中就是[('15578876784678163569', 0, 3)],它映射到我们原始文档的doc[0:3]。该match_id是字符串ID的“HelloWorld”的散列值的。要获取字符串值,你可以在StringStore中查询。

for match_id, start, end in matches:
    string_id = nlp.vocab.strings[match_id]  # 'HelloWorld'
    span = doc[start:end]                    # The matched span

或者,我们还可以选择添加多个模式,例如,还可以匹配“hello”和“world”之间没有标点符号的序列:

patterns = [
    [{"LOWER": "hello"}, {"IS_PUNCT": True}, {"LOWER": "world"}],
    [{"LOWER": "hello"}, {"LOWER": "world"}]
]
matcher.add("HelloWorld", patterns)

默认情况下,匹配器将只返回匹配项而不执行任何其他操作,例如合并实体或分配标签。这完全取决于您,你可以通过向add()函数传入不同的回调函数作为on_match参数来单独定义每个模式。这很有用,因为它允许您编写完全自定义和模式特定的逻辑。例如,您可能希望将一些模式合并为一个token,同时为其他模式类型添加实体标签。这个时候你不必为每个进程创建不同的匹配器。

可用的token属性

可用的Token模式键对应于许多 Token 属性. 基于规则的匹配支持的属性有:

ATTRIBUTE DESCRIPTION
ORTH 准确的Token逐字文本. TYPE:str
TEXT 准确的Token逐字文本. TYPE:str
LOWER Token文本的小写形式.TYPE:str
LENGTH Token文本的长度.TYPE:int
IS_ALPHA, IS_ASCII, IS_DIGIT Token文本由字母字符、ASCII 字符、数字组成.TYPE:bool
IS_LOWER, IS_UPPER, IS_TITLE Token文本是小写、大写、标题.TYPE:bool
IS_PUNCT, IS_SPACE, IS_STOP Token文本是标点符号、空格、停止词.TYPE:bool
IS_SENT_START Token是句子的开始.TYPE:bool
LIKE_NUM, LIKE_URL, LIKE_EMAIL Token文本类似于数字、URL、电子邮件.TYPE:bool
SPACY Token有一个结尾空格.TYPE:bool
POS, TAG, MORPH, DEP, LEMMA, SHAPE 令牌的简单和扩展词性标签、形态分析、依赖标签、词根、形状(shape)。请注意,这些属性的值区分大小写。有关可用词性标签和依赖项标签的列表,请参阅注释规范TYPE:str
ENT_TYPE Token的实体标签。 TYPE:str
_ 自定义扩展属性的属性. TYPE:Dict[str, Any]
OP 运算符和量词,限定Token模式的匹配次数。TYPE:str

属性名称区分大小写吗?
不区分。spaCy会进行名称规范化处理,{"LOWER": "text"}{"lower": "text"}会得到相同的结果。使用大写版本主要是一种约定,以明确这些属性是“特殊的”并且不完全映射到像Token.lower和Token.lower_这样的标记属性 。

为什么不支持所有Token属性?
spaCy无法提供对所有属性的访问,因为Matcher循环遍历Cython数据,而不是Python对象。在匹配器内部,我们正在处理一个TokenC struct – 我们没有Token实例. 这意味着所有引用计算属性的属性都无法访问。

大写的属性名称类似于LOWERIS_PUNCT引用自spacy.attrs枚举表。它们被传递到一个本质上是一个大case/switch语句的函数中,以确定要返回的结构字段。相同的属性标识符用于Doc.to_array,以及代码中需要描述此类字段的其他一些地方。

提示:试用交互式匹配器
匹配器演示
该交互式匹配器通过创建基于规则的匹配起并在你的文本上运行他们来测试你的的Matcher。每个Token可以设置多个属性,如文本值、词性标记或布尔标志。基于token的视图可以让你看到spaCy是如何处理您的文本的,以及为什么可以成功匹配,或者为什么无法匹配。

扩展模式语法和属性

Token模式也可以映射到属性字典,而不仅仅是映射到单个值。例如,指定词根的值应该是值列表的一部分,或设置最小字符长度。可用的属性如下表:

例子
# Matches "love cats" or "likes flowers"
pattern1 = [{"LEMMA": {"IN": ["like", "love"]}},
            {"POS": "NOUN"}]

# Matches tokens of length >= 10
pattern2 = [{"LENGTH": {">=": 10}}]

# Match based on morph attributes
pattern3 = [{"MORPH": {"IS_SUBSET": ["Number=Sing", "Gender=Neut"]}}]
# "", "Number=Sing" and "Number=Sing|Gender=Neut" will match as subsets
# "Number=Plur|Gender=Neut" will not match
# "Number=Sing|Gender=Neut|Polite=Infm" will not match because it's a superset
属性 描述
IN 属性值是列表的成员。TYPE:Any
NOT_IN 属性值不是列表的成员。TYPE:Any
IS_SUBSET 属性值(用于MORPH或自定义列表属性)是列表的子集。TYPE:Any
IS_SUPERSET 属性值(用于MORPH或自定义列表属性)是列表的超集。TYPE:Any
INTERSECTS 属性值(用于MORPH或自定义列表属性)与列表具有非空交集。

Any
==, >=, <=, >, <| 属性值等于、大于或等于、小于或等于、大于或小于。TYPE:Union[int, float]

常用表达式

在某些情况下,仅匹配token和token属性是不够的——例如,你可能希望匹配一个单词的不同拼写,而想为每个拼写添加新模式。

pattern = [{"TEXT": {"REGEX": "^[Uu](\.?|nited)$"}},
           {"TEXT": {"REGEX": "^[Ss](\.?|tates)$"}},
           {"LOWER": "president"}]

REGEX操作允许你为任何属性定义规则,包括自定义属性。它始终需要应用于像TEXT,LOWER或TAG 之类的属性:

# Match different spellings of token texts
pattern = [{"TEXT": {"REGEX": "deff?in[ia]tely"}}]

# Match tokens with fine-grained POS tags starting with 'V'
pattern = [{"TAG": {"REGEX": "^V"}}]

# Match custom attribute values with regular expressions
pattern = [{"_": {"country": {"REGEX": "^[Uu](nited|\.?) ?[Ss](tates|\.?)$"}}}]

!!!重要的提示
使用REGEX运算符时,请记住它对单个标记token而不是整个文本进行操作。每个表达式都将匹配一个token。如果您需要对整个文本进行匹配,请参阅有关对整个文本进行正则表达式匹配的详细信息

全文匹配正则表达式

如果您的表达式适用于多个标记,一个简单的解决方案是匹配doc.textwithre.finditer并使用 Doc.char_spanSpan从匹配的字符索引创建 a 的方法。如果匹配的字符没有映射到一个或多个有效Token,则Doc.char_span返回None

什么是有效的令牌序列?
在该示例中,表达式也将匹配"US"在"USA"。但是, "USA"是单个标记,而Span对象是Token序列。所以 "US"不能是它自己的span,因为它没有在token边界上结束。

import spacy
import re

nlp = spacy.load("en_core_web_sm")
doc = nlp("The United States of America (USA) are commonly known as the United States (U.S. or US) or America.")

expression = r"[Uu](nited|\.?) ?[Ss](tates|\.?)"
for match in re.finditer(expression, doc.text):
    start, end = match.span()
    span = doc.char_span(start, end)
    # This is a Span object or None if match doesn't map to valid token sequence
    if span is not None:
        print("Found match:", span.text)

结果

Found match: United States
Found match: United States
Found match: U.S.
Found match: US

如何将match扩展为有效的令牌序列?
在某些情况下,您可能希望将匹配扩展到最近的token边界,你就需要为 "USA"创建一个span,即使只子串"US"会被匹配。您可以使用文档中tokens的字符偏移量(Token.idx](https://spacy.io/api/token#attributes))来计算它,可作为 .。这样你就可以个创建一个列表包含有效token的开始和结束边界,并给你留下一个基础的算法问题:给定一个数字,找到下一个最小数(token开始)或下一个最大数(token结束)。这将是最接近有效token的边界。

有很多实现方法,最直接的方法是在Doc中创建一个基于字符的字典表,索引到所在的token中。它易于编写且不易出错,并为您提供恒定的查找时间:您只需为每个Doc创建一次词典。

chars_to_tokens = {}
for token in doc:
    for i in range(token.idx, token.idx + len(token.text)):
        chars_to_tokens[i] = token.i

然后,您可以在给定位置查找字符,并获取该字符所属的相应token的索引。你的span就是doc[token_start:token_end]。 如果字符不在dict中,则意味着它是(white)空格token被拆分的。不过,这希望不应该发生,因为这意味着您的正则表达式正在生成包含前导或后续空格的匹配项。

span = doc.char_span(start, end)
if span is not None:
    print("Found match:", span.text)
else:
    start_token = chars_to_tokens.get(start)
    end_token = chars_to_tokens.get(end)
    if start_token is not None and end_token is not None:
        span = doc[start_token:end_token + 1]
        print("Found closest match:", span.text)
运算符和量词

匹配器还允许您使用指定为'OP'键的量词。量词让您定义要匹配的token序列,例如一个或多个标点符号,或指定可选token。请注意,不能使用嵌套或作用域量词——你可以使用on_match回调来实现。

OP运算符 描述
! 否定模式,要求它精确匹配 0 次。
? 使模式可选,允许它匹配 0 次或 1 次

+|要求模式匹配 1 次或多次。
*| 允许模式匹配零次或多次。

pattern = [{"LOWER": "hello"},
           {"IS_PUNCT": True, "OP": "?"}]