代码之家  ›  专栏  ›  技术社区  ›  Sai Prasanna

如何在没有文档上下文的情况下定义空间文本?

  •  3
  • Sai Prasanna  · 技术社区  · 6 年前

    我有一个序列到序列的模型,该模型基于spacy的标记化形成的标记进行训练。这是编码器和解码器。

    输出是来自seq2seq模型的令牌流。我想对文本进行细化,形成自然的文本。

    示例:

    Seq2Seq的输入: 一些文本

    Seq2Seq的输出: 这行不通。

    spacy中是否有任何API可以反转其标记器中规则执行的标记化?

    2 回复  |  直到 6 年前
        1
  •  9
  •   Jindřich    5 年前

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

    詹姆斯·布拉德伯里(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_
    

    诀窍在于,当 任何一个 当前的词素表示“无尾随空格” 下一个词素是“无前导空格”。这意味着您可以使用频率统计信息来决定这两个词素中的哪一个因空间不足而“受到责备”。

    詹姆斯的观点是,这种策略给单词预测决策增加的熵很少。备用方案将使用以下条目扩展词典 hello. "Hello .他的方法两者都不行,因为你可以对字符串进行编码 你好 作为其中之一 (hello, 1, 0), (., 1, 1) 或作为 (hello, 1, 0), (., 0, 1) .这个选择很简单:我们绝对应该“责怪”这个时期,因为它缺少空间。

        2
  •  1
  •   Davide Fiocco    4 年前

    TL;博士 我已经编写了一段代码来尝试这样做,下面是代码片段。


    另一种计算复杂度为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”,以及点“”之间的条形空间和前面的单词。 这种方法并不完美,因为有多种可能的句子组合,导致特定的空间标记化。

    我不确定当你只有空格分隔的文本时,是否有一种方法可以完全删除一个句子,但这是我最好的方法。


    在谷歌上搜索了几个小时后,只找到了几个答案,我在chrome上的3个选项卡上打开了这个堆积如山的问题;),它写的基本上是 “不要使用spaCy,请使用revtok” .由于我无法改变其他研究人员选择的标记化,我不得不开发自己的解决方案。希望它能帮助别人;)