【问题标题】:How can I add a specific substring to tokenize on in spaCy?如何在 spaCy 中添加特定的子字符串以进行标记?
【发布时间】:2020-10-01 23:20:16
【问题描述】:

我正在使用spaCy 来标记一个字符串,并且该字符串很可能包含一个特定的子字符串。如果存在子字符串,我希望spaCy 将子字符串视为令牌,而不管它具有任何其他规则。我想保持所有其他规则不变。这可能吗?

举一个具体的例子,假设感兴趣的子字符串是'banana';我希望将'I like bananabread.' 标记为['I', 'like', 'banana', 'bread', '.']

我从这里去哪里(请记住,我希望保持其余的标记器规则不变)?我尝试在前缀、后缀和中缀中添加'banana',但没有成功。

【问题讨论】:

    标签: python nlp tokenize spacy


    【解决方案1】:

    将字符串添加为前缀、后缀和中缀应该可以,但是根据您使用的 spacy 版本,您可能在测试时遇到了缓存错误。此错误已在 v2.2+ 中修复。

    使用 spacy v2.3.2:

    import spacy
    nlp = spacy.load("en_core_web_sm")
    
    text = "I like bananabread."
    assert [t.text for t in nlp(text)] == ['I', 'like', 'bananabread', '.']
    
    prefixes = ("banana",) + nlp.Defaults.prefixes
    suffixes = ("banana",) + nlp.Defaults.suffixes
    infixes = ("banana",) + nlp.Defaults.infixes
    
    prefix_regex = spacy.util.compile_prefix_regex(prefixes)
    suffix_regex = spacy.util.compile_suffix_regex(suffixes)
    infix_regex = spacy.util.compile_infix_regex(infixes)
    
    nlp.tokenizer.prefix_search = prefix_regex.search
    nlp.tokenizer.suffix_search = suffix_regex.search
    nlp.tokenizer.infix_finditer = infix_regex.finditer
    
    assert [t.text for t in nlp(text)]  == ['I', 'like', 'banana', 'bread', '.']
    

    (在 v2.1 或更早版本中,tokenizer 自定义仍然适用于新加载的 nlp,但如果您已经使用 nlp 管道处理了一些文本,然后修改设置,那么错误是它将使用缓存中存储的标记化而不是新设置。)

    【讨论】:

    • 我感到很困惑——这正是我昨天试图做的,我很难找到你的代码和我的代码之间的任何区别。很可能,我只是在我的脚本中以错误的顺序评估代码,然后假设这些实用函数没有按照我的想法做。我也在使用 spaCy 2.3.2。
    • 如果不是缓存问题,另外需要注意的是词缀的顺序很重要,因为如果找到,它将使用第一个匹配项。因此,如果您的子字符串包含另一个较短的词缀,则可以首先找到该词缀,并且标记器会分解子字符串。您通常希望将最长的词缀放在列表的首位。您可以使用nlp.tokenizer.explain(text) 来调试正在发生的事情。
    【解决方案2】:

    标记化发生在 spaCy 管道的开头,因此您应该首先对文本进行预处理。

    我写了一个函数,它使用正则表达式来填充复合词中的子字符串:

    import re
    
    text = 'I eat bananas and bananabread at the bookstore.'
    
    def separate_compound_toks(text):
        anti_compound = sorted(['banana', 'store'])
        anti_compound = "|".join(t.lower() for t in anti_compound)
        # pad word from end
        pattern_a = re.compile(r'(?i)({sub})(?=[a-z]{{3,}})'.format(sub=anti_compound))
        text = re.sub(pattern_a, r'\1 ', text)
        # pad word from beginning
        pattern_b = re.compile(r'(?i)(?<![^a-z])({sub})'.format(sub=anti_compound))
        text = re.sub(pattern_b, r' \1', text)
        return text
    
    
    import spacy
    nlp = spacy.load("en_core_web_sm")
     
    doc = nlp(separate_compound_toks(text))
    print([tok.text for tok in doc])
    # ['I', 'eat', 'bananas', 'and', 'banana', 'bread', 'at', 'the', 'book', 'store', '.']
    

    【讨论】:

    • 您是否有理由更喜欢这种策略而不是 @aab 的答案?
    • @BioBroo 这两种方法都有其优点,我相信这种方法更可定制。在这里,您可以将 bananas 标记为单个标记,而不是 ['banana', 's']
    猜你喜欢
    • 2017-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多