言語処理100本ノック-57:係り受け解析

[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)[「第6章: 英語テキストの処理」](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch6)の[57本目「係り受け解析」](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec57)記録です。
[「言語処理100本ノック-44:係り受け木の可視化」](https://qiita.com/FukuharaYohei/items/322447d75549f0d00f3c)でやった内容のStanford CoreNLP版ですね。かなりのコードを流用しています。

# 参考リンク

|リンク|備考|
|:--|:--|
|[057.係り受け解析.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/06.%E8%8B%B1%E8%AA%9E%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E5%87%A6%E7%90%86/057.%E4%BF%82%E3%82%8A%E5%8F%97%E3%81%91%E8%A7%A3%E6%9E%90.ipynb)|回答プログラムのGitHubリンク|
|[素人の言語処理100本ノック:57]()|多くのソース部分のコピペ元|
|[Stanford Core NLP公式](https://stanfordnlp.github.io/CoreNLP/)|最初に見ておくStanford Core NLPのページ|

# 環境

|種類|バージョン|内容|
|:--|:--|:--|
|OS|Ubuntu18.04.01 LTS|仮想で動かしています|
|pyenv|1.2.16|複数Python環境を使うことがあるのでpyenv使っています|
|Python|3.8.1|pyenv上でpython3.8.1を使っています<br>パッケージはvenvを使って管理しています|
|Stanford CoreNLP|3.9.2|インストールしたのが1年前で詳しく覚えていないです・・・<br>1年たってもそれが最新だったのでそのまま使いました|
|openJDK|1.8.0_242|他目的でインストールしていたJDKをそのまま使いました|

上記環境で、以下のPython追加パッケージを使っています。通常のpipでインストールするだけです。

|種類|バージョン|
|:--|:--|
|pydot|1.4.1|

# [第6章: 英語テキストの処理](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch6)
## 学習内容
> Stanford Core NLPを用いた英語のテキスト処理を通じて，自然言語処理の様々な基盤技術を概観します．
>> [Stanford Core NLP](https://stanfordnlp.github.io/CoreNLP/), ステミング, 品詞タグ付け, 固有表現抽出, 共参照解析, 係り受け解析, 句構造解析, S式

## ノック内容
> 英語のテキスト（[nlp.txt](http://www.cl.ecei.tohoku.ac.jp/nlp100/data/nlp.txt)）に対して，以下の処理を実行せよ．

### [57. 係り受け解析](http://www.cl.ecei.tohoku.ac.jp/nlp100/#sec57)
> Stanford Core NLPの係り受け解析の結果（collapsed-dependencies）を有向グラフとして可視化せよ．可視化には，係り受け木を[DOT言語](http://ja.wikipedia.org/wiki/DOT%E8%A8%80%E8%AA%9E)に変換し，[Graphviz](http://www.graphviz.org/)を用いるとよい．また，Pythonから有向グラフを直接的に可視化するには，[pydot](https://code.google.com/p/pydot/)を使うとよい．

### 課題補足(「係り受け」について)
係り受けはStanford CoreNLP(英語?)では"Dependencies"と呼ばれ、Stanford CoreNLPの仕組みは[Stanford Dependencies](https://nlp.stanford.edu/software/stanford-dependencies.shtml)に載っています。

どうも2種類あるらしく、今回対象とするのは`collapsed-dependencies`の方です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/c709dc10-8fd6-c13e-bcaa-48b903f81d46.png)

やった後に気づきたのですが、有向グラフでエッジ間の関係性(prep_onとか)を追加で書き込めばもっとわかりやすかったです。

# 回答
## 回答プログラム [057.係り受け解析.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/06.%E8%8B%B1%E8%AA%9E%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E5%87%A6%E7%90%86/057.%E4%BF%82%E3%82%8A%E5%8F%97%E3%81%91%E8%A7%A3%E6%9E%90.ipynb)

```python
import xml.etree.ElementTree as ET

import pydot

for i, sentence in enumerate(ET.parse('./nlp.txt.xml').iterfind('./document/sentences/sentence')):
    edges = []
    for dependency in sentence.iterfind('./dependencies[@type="collapsed-dependencies"]/dep'):
        
        # 句読点除外
        if dependency.get('type') != 'punct':
            governor = dependency.find('./governor')
            dependent = dependency.find('./dependent')
            edges.append(((governor.get('idx'), governor.text), 
                          (dependent.get('idx'), dependent.text)))
    if len(edges) > 0:
        graph = pydot.graph_from_edges(edges, directed=True)
        graph.write_jpeg('057.graph_{}.jpeg'.format(i))
    
    if i > 5:
        break
```

## 回答解説
### XMLファイルのパス
以下のXMLファイルのパスと目的とする係り元、係り先のマッピングです。第5階層の`dependencies`タグは属性`type`が`collapsed-dependencies`のものを対象とします。

|出力|第1階層|第2階層|第3階層|第4階層|第5階層|第6階層|第7階層|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|係り元|root|document|sentences|sentence|dependencies|dep|governor|
|係り先|root|document|sentences|sentence|dependencies|dep|dependent|

XMLファイルは[GitHub](https://github.com/YoheiFukuhara/nlp100/blob/master/06.%E8%8B%B1%E8%AA%9E%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E5%87%A6%E7%90%86/nlp.txt.xml)に置いています。

```xml:nlp.txt.xml(抜粋)
<root>
  <document>
    <docId>nlp.txt</docId>
    <sentences>
      <sentence id="1">

--中略--

        <dependencies type="collapsed-dependencies">
          <dep type="root">
            <governor idx="0">ROOT</governor>
            <dependent idx="18">field</dependent>
          </dep>
          <dep type="amod">
            <governor idx="3">processing</governor>
            <dependent idx="1">Natural</dependent>
          </dep>
          <dep type="compound">
            <governor idx="3">processing</governor>
            <dependent idx="2">language</dependent>
          </dep>
```

### Pydotを使った有向グラフ表示
以下のコード部分です。やっていることは、[「言語処理100本ノック-44:係り受け木の可視化」](https://qiita.com/FukuharaYohei/items/322447d75549f0d00f3c)と同じなので解説しません。ただ、エッジ間の関係性を[Graphviz](https://graphviz.readthedocs.io/en/stable/manual.html)や[networkx](https://networkx.github.io/)を使って追加すればよかったと、後悔しています。
記事[「networkxでマルチグラフとか、綺麗なグラフを書く【python】」](https://cppx.hatenablog.com/entry/2018/12/11/025430)や記事[「Python上でGraphvizを使って綺麗なグラフを描く」](https://programgenjin.hatenablog.com/entry/2019/02/26/075121)あたりを参考にすれば書けそう。
そもそも[pydot](https://pypi.org/project/pydot/)は、2018年12月から更新されていないので今後も続くのか心配です。

```python:
for i, sentence in enumerate(ET.parse('./nlp.txt.xml').iterfind('./document/sentences/sentence')):
    edges = []
    for dependency in sentence.iterfind('./dependencies[@type="collapsed-dependencies"]/dep'):
        
        # 句読点除外
        if dependency.get('type') != 'punct':
            governor = dependency.find('./governor')
            dependent = dependency.find('./dependent')
            edges.append(((governor.get('idx'), governor.text), 
                          (dependent.get('idx'), dependent.text)))
    if len(edges) > 0:
        graph = pydot.graph_from_edges(edges, directed=True)
        graph.write_jpeg('057.graph_{}.jpeg'.format(i))
```

### 出力結果(実行結果)
プログラム実行すると以下の結果が出力されます(最初の3文のみ)。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/0fef2fc3-6e95-69cd-a22a-32a2fc276e9d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/9bcfd534-e042-2f3f-0ffe-c9b9d6c7ea95.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/69f5b374-dac7-2bce-52aa-d3aa81b9b464.png)