素人の言語処理100本ノック:57


[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の挑戦記録です。環境はUbuntu 16.04 LTS ＋ Python 3.5.2 \:\: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧は[こちら](http://qiita.com/segavvy/items/fb50ba8097d59475f760)からどうぞ。

## 第6章: 英語テキストの処理
>英語のテキスト（nlp.txt）に対して，以下の処理を実行せよ．

###56. 係り受け解析
>Stanford Core NLPの係り受け解析の結果（collapsed-dependencies）を有向グラフとして可視化せよ．可視化には，係り受け木をDOT言語に変換し，Graphvizを用いるとよい．また，Pythonから有向グラフを直接的に可視化するには，pydotを使うとよい．

####出来上がったコード：

```python:main.py
# coding: utf-8
import os
import subprocess
import xml.etree.ElementTree as ET
import pydot_ng as pydot

fname = 'nlp.txt'
fname_parsed = 'nlp.txt.xml'


def parse_nlp():
	'''nlp.txtをStanford Core NLPで解析しxmlファイルへ出力
	すでに結果ファイルが存在する場合は実行しない
	'''
	if not os.path.exists(fname_parsed):

		# StanfordCoreNLP実行、標準エラーはparse.outへ出力
		subprocess.run(
			'java -cp "/usr/local/lib/stanford-corenlp-full-2016-10-31/*"'
			' -Xmx2g'
			' edu.stanford.nlp.pipeline.StanfordCoreNLP'
			' -annotators tokenize,ssplit,pos,lemma,ner,parse,dcoref'
			' -file ' + fname + ' 2>parse.out',
			shell=True,		# shellで実行
			check=True		# エラーチェックあり
		)


def graph_from_edges_ex(edge_list, directed=False):
	'''pydot_ng.graph_from_edges()のノード識別子への対応版

	graph_from_edges()のedge_listで指定するタプルは
	識別子とグラフ表示時のラベルが同一のため、
	ラベルが同じだが実体が異なるノードを表現することができない。
	例えば文の係り受けをグラフにする際、文の中に同じ単語が
	複数出てくると、それらのノードが同一視されて接続されてしまう。

	この関数ではedge_listとして次の書式のタプルを受け取り、
	ラベルが同一でも識別子が異なるノードは別ものとして扱う。

	edge_list = [((識別子1,ラベル1),(識別子2,ラベル2)), ...]

	識別子はノードを識別するためのもので表示されない。
	ラベルは表示用で、同じでも識別子が異なれば別のノードになる。

	なお、オリジナルの関数にあるnode_prefixは未実装。

	戻り値：
	pydot.Dotオブジェクト
	'''

	if directed:
		graph = pydot.Dot(graph_type='digraph')

	else:
		graph = pydot.Dot(graph_type='graph')

	for edge in edge_list:

		id1 = str(edge[0][0])
		label1 = str(edge[0][1])
		id2 = str(edge[1][0])
		label2 = str(edge[1][1])

		# ノード追加
		graph.add_node(pydot.Node(id1, label=label1))
		graph.add_node(pydot.Node(id2, label=label2))

		# エッジ追加
		graph.add_edge(pydot.Edge(id1, id2))

	return graph


# nlp.txtを解析
parse_nlp()

# 解析結果のxmlをパース
root = ET.parse(fname_parsed)

# sentence列挙、1文ずつ処理
for sentence in root.iterfind('./document/sentences/sentence'):
	sent_id = int(sentence.get('id'))		# sentenceのid

	edges = []

	# dependencies列挙
	for dep in sentence.iterfind(
		'./dependencies[@type="collapsed-dependencies"]/dep'
	):

		# 句読点はスキップ
		if dep.get('type') != 'punct':

			# governor、dependent取得、edgesに追加
			govr = dep.find('./governor')
			dept = dep.find('./dependent')
			edges.append(
				((govr.get('idx'), govr.text), (dept.get('idx'), dept.text))
			)

	# 描画
	if len(edges) > 0:
		graph = graph_from_edges_ex(edges, directed=True)
		graph.write_png('{}.png'.format(sent_id))
```

####実行結果：

文の先頭からいくつかを貼り付けます。

**1.png**
![Kobito.jeqBwe.png](https://qiita-image-store.s3.amazonaws.com/0/139624/3328fc00-f93d-bcc1-bdd7-d0780b83f1e3.png "Kobito.jeqBwe.png")

**2.png**
![Kobito.Qvt44t.png](https://qiita-image-store.s3.amazonaws.com/0/139624/cbf3b75e-0117-ffc2-48a1-d2ae0a129432.png "Kobito.Qvt44t.png")

**3.png**
![Kobito.VO2v84.png](https://qiita-image-store.s3.amazonaws.com/0/139624/f7ad7957-aad0-df65-c2e8-2262c4dd47bb.png "Kobito.VO2v84.png")

**4.png**
![Kobito.MH5yWi.png](https://qiita-image-store.s3.amazonaws.com/0/139624/3c44c10e-1d4a-7a15-2dcb-a48477d8771b.png "Kobito.MH5yWi.png")

**5.png**
![Kobito.d8dyTi.png](https://qiita-image-store.s3.amazonaws.com/0/139624/08aebdef-36a0-7274-e51a-13a3c66f404f.png "Kobito.d8dyTi.png")

全体の結果は[GitHub](https://github.com/segavvy/nlp100_Python/tree/master/57)にアップしています。

###係り受け解析
今回の問題は[問題44](http://qiita.com/segavvy/items/d1a9a8d87d8dc10a8f15)の英語版です。`graph_from_edges_ex()`は、[問題44](http://qiita.com/segavvy/items/d1a9a8d87d8dc10a8f15)からそのまま持ってきて使っています。

Stanford Core NLPの係り受け解析は、[DependencyParseAnnotator](http://stanfordnlp.github.io/CoreNLP/depparse.html)によるとデフォルトで3種類あるようです。問題で指定されている collapsed-dependencies はその1種類で、解析結果がxmlファイルの`<dependencies type="collapsed-dependencies">`タグで出力されています。

```console:端末：nlp.txt.xmlの途中部分
        <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>
          <dep type="nmod:from">
            <governor idx="18">field</governor>
            <dependent idx="3">processing</dependent>
          </dep>
          <dep type="case">
            <governor idx="3">processing</governor>
            <dependent idx="4">From</dependent>
          </dep>
          <dep type="dep">
            <governor idx="3">processing</governor>
            <dependent idx="5">Wikipedia</dependent>
          </dep>
          <dep type="punct">
            <governor idx="18">field</governor>
            <dependent idx="6">,</dependent>
          </dep>
          <dep type="det">
            <governor idx="12">processing</governor>
            <dependent idx="7">the</dependent>
          </dep>
          <dep type="amod">
            <governor idx="12">processing</governor>
            <dependent idx="8">free</dependent>
          </dep>
          <dep type="compound">
            <governor idx="12">processing</governor>
            <dependent idx="9">encyclopedia</dependent>
          </dep>
          <dep type="amod">
            <governor idx="12">processing</governor>
            <dependent idx="10">Natural</dependent>
          </dep>
          <dep type="compound">
            <governor idx="12">processing</governor>
            <dependent idx="11">language</dependent>
          </dep>
          <dep type="nsubj">
            <governor idx="18">field</governor>
            <dependent idx="12">processing</dependent>
          </dep>
（中略）
        </dependencies>
```

`<governor>`が係り元、`<dependent>`が係り先の情報なので、これらを取り出せばあとは[問題44](http://qiita.com/segavvy/items/d1a9a8d87d8dc10a8f15)と同じです。

###ROOTノード
グラフを書いて見ると分かるのですが、一番最初に必ず「ROOT」というノードがあり、`<dep>`タグのtype属性が「root」になっていて、これが係り受け木の根っこを示すようです。[Stanford typed dependencies manual](http://nlp.stanford.edu/software/dependencies_manual.pdf)の「2 Definitions of the Stanford typed dependencies」の「root:」の説明にもそれらしいことが書いてありました。

取り除いた方が良いのかも知れませんが、今回はそのまま残しています。

###句読点などの記号の除去
[問題42](http://qiita.com/segavvy/items/58894f76dba367b2925b)や[問題43](http://qiita.com/segavvy/items/30d14259aa350f88852f)で日本語の係り受け解析をやっていた時は「句読点などの記号は出力しないようにせよ」という指示がありました。今回は特に指示がありませんが、邪魔なので同様に出力を抑制しています。

`<dep>`タグのtype属性が「punct」のものが句読点を示しているようです。[Stanford typed dependencies manual](http://nlp.stanford.edu/software/dependencies_manual.pdf)の「2 Definitions of the Stanford typed dependencies」の「punct:」の説明にもそれらしいことが書いてあるので、この「punct」を除く形で実装してみました。

　
58本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。
<hr>

*実行結果には、[100本ノックで用いるコーパス・データ](http://www.cl.ecei.tohoku.ac.jp/nlp100/#data)で配布されているデータの一部が含まれます。この第6章で用いているデータのライセンスは[クリエイティブ・コモンズ 表示-継承 3.0 非移植](https://creativecommons.org/licenses/by-sa/3.0/legalcode)（[日本語訳](https://creativecommons.org/licenses/by-sa/3.0/deed.ja)）です。*