【问题标题】:How to get the dependency tree with spaCy?如何使用 spaCy 获取依赖树?
【发布时间】:2016-08-05 05:52:01
【问题描述】:

我一直试图找到如何使用 spaCy 获取依赖树,但我找不到任何关于如何获取树的信息,只能在 how to navigate the tree 上找到。

【问题讨论】:

    标签: python spacy


    【解决方案1】:

    虽然 spaCy 库在过去 5 年中可能发生了一些变化,但 @Mark Amery 的方法非常有效。这就是我一直在做的分解页面和副本页面中的句子,以获得名义上描述的功能,以及与它们相关联的 NP 或 VP。我们采取的另一种方法(这可能在过去 5 年中出现在 SpaCy 中)......祖先的孩子,你基本上会找到指向主语、宾语和根的头部。您可以将它们分解为基于并列或连词等修饰语的子句,这将告诉您这些子句是对功能描述的补充还是核心。这样你就可以重写句子,我主要这样做是为了去掉多余的东西并创建包含硬细节的片段。不知道是否对其他人有帮助,但这是我在与 SpaCy 基于张量的建模相比,在纸上绘制 nsubj、dobj、连词和 pobj 数周后遵循的策略。 IMO,值得注意的是,SpaCy 所做的标记似乎总是 100% 正确 - 每次,即使片段相距 20 个字,在写得很糟糕的运行中也是如此。我从来不用猜测它的输出——这显然是无价的。

    【讨论】:

      【解决方案2】:

      您可以使用下面的库来查看您的依赖树,发现它非常有用!

      import spacy
      from spacy import displacy
      
      nlp = spacy.load('en')
      doc = nlp(u'This is a sentence.')
      displacy.serve(doc, style='dep')
      

      你可以用浏览器打开它,它看起来像:

      要生成 SVG 文件:

      from pathlib import Path
      
      output_path = Path("yourpath/.svg")
      svg = displacy.render(doc, style='dep')
      with output_path.open("w", encoding="utf-8") as fh:
          fh.write(svg)
      

      【讨论】:

      • JFI.. serve 选项还将树作为网络链接打开。对于静态图像(渲染选项可用displacy.render(doc, style='dep')
      • 要添加到@prashanth 的评论中,您还可以打印到图像(您可以打开/查看,例如在 Firefox 中;请注意,您需要指定完整路径(/home/victoria/...,而不是~/...)。代码:from pathlib import Path; output_path = Path("/home/victoria/dependency_plot.svg"); svg = displacy.render(doc, style='dep', jupyter=False); output_path.open("w", encoding="utf-8").write(svg)
      • 奇怪的是,当我渲染为 svg 时,依赖标签丢失了,当我用 serve 渲染时,它们被打印出来了。
      【解决方案3】:

      树本身并不是一个对象;您只需通过令牌之间的关系导航它。这就是文档谈论导航树而不是“获取”它的原因。

      首先,让我们解析一些文本以获取Doc 对象:

      >>> import spacy
      >>> nlp = spacy.load('en_core_web_sm')
      >>> doc = nlp('First, I wrote some sentences. Then spaCy parsed them. Hooray!')
      

      docSequenceToken 对象:

      >>> doc[0]
      First
      >>> doc[1]
      ,
      >>> doc[2]
      I
      >>> doc[3]
      wrote
      

      但它没有单个根令牌。我们解析了一个由三个句子组成的文本,所以有三棵不同的树,每棵树都有自己的根。如果我们想从每个句子的根开始解析,首先将句子作为不同的对象是有帮助的。幸运的是,doc 通过.sents 属性向我们公开了这些:

      >>> sentences = list(doc.sents)
      >>> for sentence in sentences:
      ...     print(sentence)
      ... 
      First, I wrote some sentences.
      Then spaCy parsed them.
      Hooray!
      

      这些句子中的每一个都是一个Span,带有一个指向其根标记的.root 属性。通常,词根标记将是句子的主要动词(尽管对于不寻常的句子结构,例如没有动词的句子,这可能不是真的):

      >>> for sentence in sentences:
      ...     print(sentence.root)
      ... 
      wrote
      parsed
      Hooray
      

      找到根令牌后,我们可以通过每个令牌的.children 属性向下导航树。例如,让我们找出第一句中动词的主语和宾语。每个子令牌describes its relationship with its parent.dep_ 属性;例如,'nsubj' 中的 dep_ 表示令牌是其父代的名义主语

      >>> root_token = sentences[0].root
      >>> for child in root_token.children:
      ...     if child.dep_ == 'nsubj':
      ...         subj = child
      ...     if child.dep_ == 'dobj':
      ...         obj = child
      ... 
      >>> subj
      I
      >>> obj
      sentences
      

      我们同样可以通过查看这些令牌的其中一个子代来继续往下走:

      >>> list(obj.children)
      [some]
      

      因此,使用上面的属性,您可以导航整个树。如果你想可视化一些依赖树例如句子来帮助你理解结构,我建议玩displaCy

      【讨论】:

        【解决方案4】:

        我不知道这是一个新的 API 调用还是什么,但 Document 类上有一个 .print_tree() 方法可以快速解决这个问题。

        https://spacy.io/api/doc#print_tree

        它将依赖关系树转储为 JSON。它处理多个句子根和所有这些:

            import spacy    
            nlp = spacy.load('en')
            doc1 = nlp(u'This is the way the world ends.  So you say.')  
            print(doc1.print_tree(light=True))
        

        print_tree 这个名字有点用词不当,该方法本身不打印任何内容,而是返回一个字典列表,每个句子一个。

        【讨论】:

        • Doc.print_tree 方法在 v2.1 中被弃用,取而代之的是 Doc.to_json。
        【解决方案5】:

        我对解析还没有足够的了解。然而,我的文献研究结果表明,spaCy 有一个 shift-reduce 依赖解析算法。这会解析问题/句子,从而生成解析树。为了可视化这一点,您可以使用 DisplaCy,它是 CSS 和 Javascript 的组合,可与 Python 和 Cython 一起使用。 此外,您可以使用 SpaCy 库进行解析,并导入自然语言工具包 (NLTK)。希望这会有所帮助

        【讨论】:

          【解决方案6】:

          我还需要在完整代码下面这样做:

          import sys
          def showTree(sent):
              def __showTree(token):
                  sys.stdout.write("{")
                  [__showTree(t) for t in token.lefts]
                  sys.stdout.write("%s->%s(%s)" % (token,token.dep_,token.tag_))
                  [__showTree(t) for t in token.rights]
                  sys.stdout.write("}")
              return __showTree(sent.root)
          

          如果你想要终端的间距:

          def showTree(sent):
              def __showTree(token, level):
                  tab = "\t" * level
                  sys.stdout.write("\n%s{" % (tab))
                  [__showTree(t, level+1) for t in token.lefts]
                  sys.stdout.write("\n%s\t%s [%s] (%s)" % (tab,token,token.dep_,token.tag_))
                  [__showTree(t, level+1) for t in token.rights]
                  sys.stdout.write("\n%s}" % (tab))
              return __showTree(sent.root, 1)
          

          【讨论】:

          • 我刚刚添加了用于打印的模块以及在正则表达式模式上匹配树(或查找节点)。如果你有兴趣,这里是链接:github.com/krzysiekfonal/grammaregex 它还没有准备好安装,但本周应该可以通过 pip 完成并提供。
          【解决方案7】:

          如果有人想轻松查看 spacy 生成的依赖树,一种解决方案是将其转换为 nltk.tree.Tree 并使用 nltk.tree.Tree.pretty_print 方法。这是一个例子:

          import spacy
          from nltk import Tree
          
          
          en_nlp = spacy.load('en')
          
          doc = en_nlp("The quick brown fox jumps over the lazy dog.")
          
          def to_nltk_tree(node):
              if node.n_lefts + node.n_rights > 0:
                  return Tree(node.orth_, [to_nltk_tree(child) for child in node.children])
              else:
                  return node.orth_
          
          
          [to_nltk_tree(sent.root).pretty_print() for sent in doc.sents]
          

          输出:

                          jumps                  
            ________________|____________         
           |    |     |     |    |      over     
           |    |     |     |    |       |        
           |    |     |     |    |      dog      
           |    |     |     |    |    ___|____    
          The quick brown  fox   .  the      lazy
          

          编辑:要更改令牌表示,您可以这样做:

          def tok_format(tok):
              return "_".join([tok.orth_, tok.tag_])
          
          
          def to_nltk_tree(node):
              if node.n_lefts + node.n_rights > 0:
                  return Tree(tok_format(node), [to_nltk_tree(child) for child in node.children])
              else:
                  return tok_format(node)
          

          结果:

                                   jumps_VBZ                           
             __________________________|___________________             
            |       |        |         |      |         over_IN        
            |       |        |         |      |            |            
            |       |        |         |      |          dog_NN        
            |       |        |         |      |     _______|_______     
          The_DT quick_JJ brown_JJ   fox_NN  ._. the_DT         lazy_JJ
          

          【讨论】:

          • 但在我看来,拥有/保留 Spacy 的依赖项和邮资非常重要。
          • 干得好!有没有简单的方法在两个节点之间添加依赖标签?
          • @DavidBatista 看到我的编辑。如果您想在树中添加一些其他内容,请编辑 tok_format(tok)。你也应该阅读文档。 Spacy 使用 2 种不同的 POS 表示 tok.pos_tok.tag_spacy.io/docs/#token-postags
          • tok.dep_ 属性也可以与tok.tag_ 一起使用来表示句法依赖关系。
          【解决方案8】:

          事实证明,该树在文档中可用through the tokens

          你想找到树的根吗,你可以浏览一下文档:

          def find_root(docu):
              for token in docu:
                  if token.head is token:
                      return token
          

          然后导航树,令牌有API来获取through the children

          【讨论】:

          • 一个文档每个句子有 1 个词根
          • 无需遍历令牌即可找到根;您可以访问句子的.root 属性。我会发布我自己的答案来说明这一点。
          猜你喜欢
          • 1970-01-01
          • 2018-08-23
          • 1970-01-01
          • 2019-09-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-07
          • 1970-01-01
          相关资源
          最近更新 更多