【问题标题】:How to detokenize spacy text without doc context?如何在没有文档上下文的情况下对 spacy 文本进行去标记?
【发布时间】:2018-05-14 12:33:49
【问题描述】:

我有一个序列到序列模型,该模型使用由 spacy 的标记化形成的标记进行训练。这既是编码器又是解码器。

输出是来自 seq2seq 模型的标记流。我想对文本进行去标记化以形成自然文本。

例子:

输入到 Seq2Seq: 一些文本

Seq2Seq 的输出:这不起作用。

spacy 中是否有任何 API 可以通过其分词器中的规则来反转分词?

【问题讨论】:

    标签: nlp spacy


    【解决方案1】:

    在内部 spaCy 跟踪一个布尔数组来判断标记是否有尾随空格。您需要此数组将字符串重新组合在一起。如果您使用的是 seq2seq 模型,则可以分别预测空间。

    James Bradbury(TorchText 的作者)正是在向我抱怨这一点。他说得对,当我在 spaCy 中设计标记化系统时,我没有考虑 seq2seq 模型。他开发了 revtok 来解​​决他的问题。

    基本上,revtok 所做的(如果我理解正确的话)是将两个额外的位打包到词位 ID 上:词位是否与前面的空格有关联,以及它是否与后面的空格有关联。在词位都具有空间亲和性的标记之间插入空格。

    这是为 spaCy Doc 找到这些位的代码:

    def has_pre_space(token):
        if token.i == 0:
            return False
        if token.nbor(-1).whitespace_:
            return True
        else:
            return False
    
    def has_space(token):
        return token.whitespace_
    

    诀窍是当或者当前词位说“没有尾随空格”下一个词位说“没有前导空格”时,你删除一个空格。这意味着您可以使用频率统计信息来决定将这两个词位中的哪一个“归咎于”空间不足。

    James 的观点是,这种策略为单词预测决策增加了很少的熵。替代方案将使用 hello."Hello 等条目扩展词典。他的方法两者都没有,因为您可以将字符串hello. 编码为(hello, 1, 0), (., 1, 1)(hello, 1, 0), (., 0, 1)。这个选择很简单:我们绝对应该将空间不足“归咎于”时期。

    【讨论】:

    • 不幸的是,revtok 是一种不同的分词算法,它不适用于使用 spaCy 分词的句子;(
    【解决方案2】:

    TL;DR 我已经编写了一个尝试这样做的代码,sn-p 在下面。


    另一种计算复杂度为 O(n^2) * 的方法是使用我刚刚编写的函数。 主要思想是“什么斯帕西分裂,应该重新加入!”

    代码:

    #!/usr/bin/env python                     
    import spacy     
    import string
    
                        
                                                                                                  
                                                   
    class detokenizer:                                                                            
        """ This class is an attempt to detokenize spaCy tokenized sentence """
        def __init__(self, model="en_core_web_sm"):             
            self.nlp = spacy.load(model)
         
        def __call__(self, tokens : list):
            """ Call this method to get list of detokenized words """                     
            while self._connect_next_token_pair(tokens):
                pass              
            return tokens                                                                         
                                                   
        def get_sentence(self, tokens : list) -> str:                                                                                                                                            
            """ call this method to get detokenized sentence """            
            return " ".join(self(tokens))
                                                   
        def _connect_next_token_pair(self, tokens : list):                  
            i = self._find_first_pair(tokens)
            if i == -1:                                                                                                                                                                          
                return False                                                                                                                 
            tokens[i] = tokens[i] + tokens[i+1]                                                   
            tokens.pop(i+1)                                                                                                                                                                       
            return True                                                                                                                                                                          
                                                                                                                                                                                                 
                                                                                                                                                                                                 
        def _find_first_pair(self,tokens):                                                                                                                                                       
            if len(tokens) <= 1:                                                                                                                                                                 
                return -1                                                                         
            for i in range(len(tokens)-1):
                if self._would_spaCy_join(tokens,i):                                
                    return i
            return -1                                                                             
                                                   
        def _would_spaCy_join(self, tokens, index):                                       
            """             
            Check whether the sum of lengths of spaCy tokenized words is equal to the length of joined and then spaCy tokenized words...                                                                  
                            
            In other words, we say we should join only if the join is reversible.          
            eg.:             
                for the text ["The","man","."]
                we would joins "man" with "."
                but wouldn't join "The" with "man."                                               
            """                                    
        left_part = tokens[index]
        right_part = tokens[index+1]
        length_before_join = len(self.nlp(left_part)) + len(self.nlp(right_part))
        length_after_join = len(self.nlp(left_part + right_part))
        if self.nlp(left_part)[-1].text in string.punctuation:
            return False
        return length_before_join == length_after_join 
    

    用法:

    import spacy                           
    dt = detokenizer()                     
    
    sentence = "I am the man, who dont dont know. And who won't. be doing"
    nlp = spacy.load("en_core_web_sm")      
    spaCy_tokenized = nlp(sentence)                      
    
    string_tokens = [a.text for a in spaCy_tokenized]           
    
    detokenized_sentence = dt.get_sentence(string_tokens)
    list_of_words = dt(string_tokens)
    
    print(sentence)    
    print(detokenized_sentence)
    print(string_tokens)
    print(list_of_words)
    

    输出:

    I am the man, who dont dont know. And who won't. be doing
    I am the man, who dont dont know. And who won't . be doing
    ['I', 'am', 'the', 'man', ',', 'who', 'do', 'nt', 'do', 'nt', 'know', '.', 'And', 'who', 'wo', "n't", '.', 'be', 'doing']
    ['I', 'am', 'the', 'man,', 'who', 'dont', 'dont', 'know.', 'And', 'who', "won't", '.', 'be', 'doing']
    

    缺点:

    在这种方法中,您可以轻松合并“do”和“nt”,以及去除点“.”之间的空格。和前面的词。 这种方法并不完美,因为有多种可能的句子组合会导致特定的 spaCy 标记化。

    当你只有 spaCy 分隔的文本时,我不确定是否有一种方法可以完全去标记一个句子,但这是我所拥有的最好的。


    在谷歌上搜索了几个小时后,只有几个答案出现了,这个堆栈问题在我的 3 个 chrome 选项卡上打开;),它写的基本上是 “不要使用 spaCy , 使用 revtok"。由于我无法更改其他研究人员选择的标记化,我必须开发自己的解决方案。希望它可以帮助某人;)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-14
      • 1970-01-01
      • 2020-10-28
      • 2018-02-13
      • 2023-04-02
      • 2015-02-22
      • 2022-11-02
      • 1970-01-01
      相关资源
      最近更新 更多