【问题标题】:Chunking Stanford Named Entity Recognizer (NER) outputs from NLTK format将来自 NLTK 格式的斯坦福命名实体识别器 (NER) 输出分块
【发布时间】:2015-02-22 02:42:51
【问题描述】:

我在 NLTK 中使用 NER 来查找句子中的人、位置和组织。我能够产生这样的结果:

[(u'Remaking', u'O'), (u'The', u'O'), (u'Republican', u'ORGANIZATION'), (u'Party', u'ORGANIZATION')]

是否可以通过使用它来将事物组合在一起? 我想要的是这样的:

u'Remaking'/ u'O', u'The'/u'O', (u'Republican', u'Party')/u'ORGANIZATION'

谢谢!

【问题讨论】:

    标签: python nlp nltk stanford-nlp named-entity-recognition


    【解决方案1】:

    这是另一个使用 itertoolsgroupby 迭代器对斯坦福 NER 结果进行分组的简短实现:

    def grouptags(tags, ignore="O", join=" "):
        from itertools import groupby
        for c,g in groupby(tags, lambda t: t[1]):
            if ignore is None or c != ignore:
                if join is None:
                    entity = [e for e,_ in g]
                else:
                    entity = join.join(e for e,_ in g)
                yield(c, entity)
    

    函数grouptags有两个选项:

    • ignore:指定在输出中被忽略和省略的类(默认值:“O”)。如果为 None,则返回所有实体。
    • join:指定用于连接部件的字符(默认:“”)。如果 None,则将未连接的部分作为列表返回。

    【讨论】:

      【解决方案2】:

      您可以使用标准的 NLTK 方式使用 nltk.Tree 来表示块。这可能意味着你必须稍微改变你的表现形式。

      我通常将NER-tagged句子表示为三元组列表

      sentence = [('Andrew', 'NNP', 'PERSON'), ('is', 'VBZ', 'O'), ('part', 'NN', 'O'), ('of', 'IN', 'O'), ('the', 'DT', 'O'), ('Republican', 'NNP', 'ORGANIZATION'), ('Party', 'NNP', 'ORGANIZATION'), ('in', 'IN', 'O'), ('Dallas', 'NNP', 'LOCATION')]
      

      当我使用外部工具对句子进行 NER 标记时,我会这样做。现在您可以将这句话转换为 NLTK 表示:

      from nltk import Tree
      
      
      def IOB_to_tree(iob_tagged):
          root = Tree('S', [])
          for token in iob_tagged:
              if token[2] == 'O':
                  root.append((token[0], token[1]))
              else:
                  try:
                      if root[-1].label() == token[2]:
                          root[-1].append((token[0], token[1]))
                      else:
                          root.append(Tree(token[2], [(token[0], token[1])]))
                  except:
                      root.append(Tree(token[2], [(token[0], token[1])]))
      
          return root
      
      
      sentence = [('Andrew', 'NNP', 'PERSON'), ('is', 'VBZ', 'O'), ('part', 'NN', 'O'), ('of', 'IN', 'O'), ('the', 'DT', 'O'), ('Republican', 'NNP', 'ORGANIZATION'), ('Party', 'NNP', 'ORGANIZATION'), ('in', 'IN', 'O'), ('Dallas', 'NNP', 'LOCATION')]
      print IOB_to_tree(sentence)
      

      表示形式的变化是有道理的,因为您当然需要 POS 标签来进行 NER 标记。

      最终结果应如下所示:

      (S
        (PERSON Andrew/NNP)
        is/VBZ
        part/NN
        of/IN
        the/DT
        (ORGANIZATION Republican/NNP Party/NNP)
        in/IN
        (LOCATION Dallas/NNP))
      

      【讨论】:

        【解决方案3】:

        它看起来很长,但确实有效:

        ner_output = [(u'Remaking', u'O'), (u'The', u'O'), (u'Republican', u'ORGANIZATION'), (u'Party', u'ORGANIZATION')]
        chunked, pos = [], ""
        for i, word_pos in enumerate(ner_output):
            word, pos = word_pos
            if pos in ['PERSON', 'ORGANIZATION', 'LOCATION'] and pos == prev_tag:
                chunked[-1]+=word_pos
            else:
                chunked.append(word_pos)
            prev_tag = pos
        
        clean_chunked = [tuple([" ".join(wordpos[::2]), wordpos[-1]]) if len(wordpos)!=2 else wordpos for wordpos in chunked]
        
        print clean_chunked
        

        [出]:

        [(u'Remaking', u'O'), (u'The', u'O'), (u'Republican Party', u'ORGANIZATION')]
        

        更多详情:

        第一个“带内存”的 for 循环实现了这样的效果:

        [(u'Remaking', u'O'), (u'The', u'O'), (u'Republican', u'ORGANIZATION', u'Party', u'ORGANIZATION')]
        

        你会意识到所有的 Name Enitties 在一个元组中都会有超过 2 个项目,而你想要的是作为列表中元素的单词,即 (u'Republican', u'ORGANIZATION', u'Party', u'ORGANIZATION') 中的 'Republican Party',所以你会做类似的事情获取偶数元素:

        >>> x = [0,1,2,3,4,5,6]
        >>> x[::2]
        [0, 2, 4, 6]
        >>> x[1::2]
        [1, 3, 5]
        

        然后你也意识到NE元组中的最后一个元素是你想要的标签,所以你会这样做`

        >>> x = (u'Republican', u'ORGANIZATION', u'Party', u'ORGANIZATION')
        >>> x[::2]
        (u'Republican', u'Party')
        >>> x[-1]
        u'ORGANIZATION'
        

        这有点临时和冗长,但我希望它有所帮助。这是一个函数,Blessed Christmas:

        ner_output = [(u'Remaking', u'O'), (u'The', u'O'), (u'Republican', u'ORGANIZATION'), (u'Party', u'ORGANIZATION')]
        
        
        def rechunk(ner_output):
            chunked, pos = [], ""
            for i, word_pos in enumerate(ner_output):
                word, pos = word_pos
                if pos in ['PERSON', 'ORGANIZATION', 'LOCATION'] and pos == prev_tag:
                    chunked[-1]+=word_pos
                else:
                    chunked.append(word_pos)
                prev_tag = pos
        
        
            clean_chunked = [tuple([" ".join(wordpos[::2]), wordpos[-1]]) 
                            if len(wordpos)!=2 else wordpos for wordpos in chunked]
        
            return clean_chunked
        
        
        print rechunk(ner_output)
        

        【讨论】:

        • 我将 chunked, pos = [], "" 更改为 chunked, pos, prev_tag = [], "", None,我认为这样更有意义。 :) 但是在处理两个连续的实体时仍然有点尴尬,例如:Person Person O。非常感谢。
        • 连续的NE很少见,我正在为其他工作找到它们,如果你找到它们,你能帮忙发布一两个例子吗? =)
        • @alvas 说你有两个逗号分隔的名字.." PER1_NAME、PER2_NAME 和其他人是好朋友"..?
        • 逗号将出现在 NE 标签之间。并且提取连续的 NNP 标签仍然有效。
        【解决方案4】:

        这实际上将出现在 CoreNLP 的下一个版本中,名称为 MentionsAnnotator。不过,它可能不会直接从 NLTK 获得,除非 NLTK 人员希望支持它以及标准的斯坦福 NER 接口。

        无论如何,目前你必须复制我链接到的代码(它使用LabeledChunkIdentifier 来完成脏活)或者用 Python 编写你自己的后处理器。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-11-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多