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

[言語処理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)からどうぞ。

## 第5章: 係り受け解析
>夏目漱石の小説『吾輩は猫である』の文章（neko.txt）をCaboChaを使って係り受け解析し，その結果をneko.txt.cabochaというファイルに保存せよ．このファイルを用いて，以下の問に対応するプログラムを実装せよ．

###44. 係り受け木の可視化
>与えられた文の係り受け木を有向グラフとして可視化せよ．可視化には，係り受け木をDOT言語に変換し，Graphvizを用いるとよい．また，Pythonから有向グラフを直接的に可視化するには，pydotを使うとよい．

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

```python:main.py
# coding: utf-8
import CaboCha
import re
import pydot_ng as pydot

fname = 'neko.txt.tmp'
fname_parsed = 'neko.txt.cabocha.tmp'


def parse_neko():
	'''「吾輩は猫である」を係り受け解析
	「吾輩は猫である」(neko.txt)を係り受け解析してneko.txt.cabochaに保存する
	'''
	with open(fname) as data_file, \
			open(fname_parsed, mode='w') as out_file:

		cabocha = CaboCha.Parser()
		for line in data_file:
			out_file.write(
				cabocha.parse(line).toString(CaboCha.FORMAT_LATTICE)
			)


class Morph:
	'''
	形態素クラス
	表層形（surface）、基本形（base）、品詞（pos）、品詞細分類1（pos1）を
	メンバー変数に持つ
	'''
	def __init__(self, surface, base, pos, pos1):
		'''初期化'''
		self.surface = surface
		self.base = base
		self.pos = pos
		self.pos1 = pos1

	def __str__(self):
		'''オブジェクトの文字列表現'''
		return 'surface[{}]\tbase[{}]\tpos[{}]\tpos1[{}]'\
			.format(self.surface, self.base, self.pos, self.pos1)


class Chunk:
	'''
	文節クラス
	形態素（Morphオブジェクト）のリスト（morphs）、係り先文節インデックス番号（dst）、
	係り元文節インデックス番号のリスト（srcs）をメンバー変数に持つ
	'''

	def __init__(self):
		'''初期化'''
		self.morphs = []
		self.srcs = []
		self.dst = -1

	def __str__(self):
		'''オブジェクトの文字列表現'''
		surface = ''
		for morph in self.morphs:
			surface += morph.surface
		return '{}\tsrcs{}\tdst[{}]'.format(surface, self.srcs, self.dst)

	def normalized_surface(self):
		'''句読点などの記号を除いた表層形'''
		result = ''
		for morph in self.morphs:
			if morph.pos != '記号':
				result += morph.surface
		return result

	def chk_pos(self, pos):
		'''指定した品詞（pos）を含むかチェックする

		戻り値：
		品詞（pos）を含む場合はTrue
		'''
		for morph in self.morphs:
			if morph.pos == pos:
				return True
		return False


def neco_lines():
	'''「吾輩は猫である」の係り受け解析結果のジェネレータ
	「吾輩は猫である」の係り受け解析結果を順次読み込んで、
	1文ずつChunkクラスのリストを返す

	戻り値：
	1文のChunkクラスのリスト
	'''
	with open(fname_parsed) as file_parsed:

		chunks = dict()		# idxをkeyにChunkを格納
		idx = -1

		for line in file_parsed:

			# 1文の終了判定
			if line == 'EOS\n':

				# Chunkのリストを返す
				if len(chunks) > 0:

					# chunksをkeyでソートし、valueのみ取り出し
					sorted_tuple = sorted(chunks.items(), key=lambda x: x[0])
					yield list(zip(*sorted_tuple))[1]
					chunks.clear()

				else:
					yield []

			# 先頭が*の行は係り受け解析結果なので、Chunkを作成
			elif line[0] == '*':

				# Chunkのインデックス番号と係り先のインデックス番号取得
				cols = line.split(' ')
				idx = int(cols[1])
				dst = int(re.search(r'(.*?)D', cols[2]).group(1))

				# Chunkを生成（なければ）し、係り先のインデックス番号セット
				if idx not in chunks:
					chunks[idx] = Chunk()
				chunks[idx].dst = dst

				# 係り先のChunkを生成（なければ）し、係り元インデックス番号追加
				if dst != -1:
					if dst not in chunks:
						chunks[dst] = Chunk()
					chunks[dst].srcs.append(idx)

			# それ以外の行は形態素解析結果なので、Morphを作りChunkに追加
			else:

				# 表層形はtab区切り、それ以外は','区切りでバラす
				cols = line.split('\t')
				res_cols = cols[1].split(',')

				# Morph作成、リストに追加
				chunks[idx].morphs.append(
					Morph(
						cols[0],		# surface
						res_cols[6],	# base
						res_cols[0],	# pos
						res_cols[1]		# pos1
					)
				)


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


# 対象文字列を入力してもらい、そのままfnameに保存
with open(fname, mode='w') as out_file:
	out_file.write(input('文字列を入力してください--> '))

# 係り受け解析
parse_neko()

# 1文ずつリスト作成
for chunks in neco_lines():

	# 係り先があるものを列挙
	edges = []
	for i, chunk in enumerate(chunks):
		if chunk.dst != -1:

			# 記号を除いた表層形をチェック、空なら除外
			src = chunk.normalized_surface()
			dst = chunks[chunk.dst].normalized_surface()
			if src != '' and dst != '':
				edges.append(((i, src), (chunk.dst, dst)))

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

####実行結果：

実行結果をいくつか載せておきます。

```console:端末
文字列を入力してください--> 　どこで生れたかとんと見当がつかぬ。
```

![Kobito.4Jss2O.png](https://qiita-image-store.s3.amazonaws.com/0/139624/31e60280-d763-4808-925b-c11f6ae4c26a.png "Kobito.4Jss2O.png")



```console:端末
文字列を入力してください--> 何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。
```

![Kobito.XJs7SQ.png](https://qiita-image-store.s3.amazonaws.com/0/139624/a8383e17-e853-4b3b-69e3-196dabfd667e.png "Kobito.XJs7SQ.png")


```console:端末
文字列を入力してください--> 吾輩はここで始めて人間というものを見た。
```

![Kobito.9krwjh.png](https://qiita-image-store.s3.amazonaws.com/0/139624/d84de114-ef0a-dc70-a4b3-2c0c7acc3006.png "Kobito.9krwjh.png")


いい感じ！

###pydotのインストール

まず、問題に取り組む前に環境構築です。
問題で勧められている`pydot`ですが、Python 3に対応した`pydot-ng`というものがありましたので今回はこれを利用しました。
`pydot-ng`には`pyparsing`と`GraphViz`が必要なので、今回追加で必要なのは次の3つです。

| 必要なパッケージ | 概要 | オフィシャルサイト |
|--------|--------|-----|
|pyparsing|DOT言語のファイルを解析する際に利用されるライブラリです。DOT言語は、データ構造を表すグラフを表現するためのデータ記述言語です。|[pyparsing](http://pyparsing.wikispaces.com)|
|GraphViz| DOT言語のグラフ情報から画像ファイルを生成するツールです。|[Graphviz - Graph Visualization Software](http://www.graphviz.org/Documentation.php)|
|pydot-ng|GraphVizをPythonで使うためのライブラリです。pydotと互換があります。|[pydot - Python interface to Graphviz's Dot language](https://github.com/pydot/pydot-ng)|

####pyparsingのインストール

condaで探してみたところ、すでに入っていました。`*`が付いているのはその印です。どうやらAnacondaでインストールされていたようです。

```console:端末（condaでpyparsingの検索）
segavvy@ubuntu:~$ conda search pyparsing
Fetching package metadata .......
pyparsing                    1.5.6                    py26_0  defaults        
                             1.5.6                    py27_0  defaults        
                             1.5.6                    py33_0  defaults        
                             2.0.1                    py26_0  defaults        
                             2.0.1                    py27_0  defaults        
                             2.0.1                    py33_0  defaults        
                             2.0.1                    py34_0  defaults        
                             2.0.3                    py26_0  defaults        
                             2.0.3                    py27_0  defaults        
                             2.0.3                    py33_0  defaults        
                             2.0.3                    py34_0  defaults        
                             2.0.3                    py35_0  defaults        
                             2.1.1                    py27_0  defaults        
                             2.1.1                    py34_0  defaults        
                             2.1.1                    py35_0  defaults        
                             2.1.4                    py27_0  defaults        
                             2.1.4                    py34_0  defaults        
                          *  2.1.4                    py35_0  defaults 
```

####GraphVizのインストール

続いてGraphVizのインストールです。
condaで見つかったので簡単にインストールできました。ついでにいろいろ新しくしてくれた模様です。いいですね。

```console:端末（condaでGraphVizのインストール）
segavvy@ubuntu:~$ conda search graphviz
Fetching package metadata .......
graphviz                     2.38.0                        2  defaults        
segavvy@ubuntu:~$ conda install graphviz
Fetching package metadata .......
Solving package specifications: ..........

Package plan for installation in environment /home/segavvy/anaconda3:

The following packages will be downloaded:

    package                    |            build
    ---------------------------|-----------------
    conda-env-2.6.0            |                0          502 B
    expat-2.1.0                |                0         365 KB
    libtool-2.4.2              |                0         547 KB
    pixman-0.32.6              |                0         2.4 MB
    glib-2.43.0                |                1         5.4 MB
    ruamel_yaml-0.11.14        |           py35_0         377 KB
    conda-4.2.12               |           py35_0         387 KB
    cairo-1.12.18              |                6         594 KB
    harfbuzz-0.9.39            |                1         1.1 MB
    pango-1.39.0               |                1         668 KB
    graphviz-2.38.0            |                2        12.0 MB
    ------------------------------------------------------------
                                           Total:        23.7 MB

The following NEW packages will be INSTALLED:

    cairo:       1.12.18-6    
    expat:       2.1.0-0      
    glib:        2.43.0-1     
    graphviz:    2.38.0-2     
    harfbuzz:    0.9.39-1     
    libtool:     2.4.2-0      
    pango:       1.39.0-1     
    pixman:      0.32.6-0     

The following packages will be UPDATED:

    conda:       4.1.6-py35_0  --> 4.2.12-py35_0 
    conda-env:   2.5.1-py35_0  --> 2.6.0-0       
    ruamel_yaml: 0.11.7-py35_0 --> 0.11.14-py35_0

Proceed ([y]/n)? y

Fetching packages ...
conda-env-2.6. 100% |################################| Time: 0:00:00 533.05 kB/s
expat-2.1.0-0. 100% |################################| Time: 0:00:00   1.35 MB/s
libtool-2.4.2- 100% |################################| Time: 0:00:00   2.70 MB/s
pixman-0.32.6- 100% |################################| Time: 0:00:01   2.49 MB/s
glib-2.43.0-1. 100% |################################| Time: 0:00:01   2.99 MB/s
ruamel_yaml-0. 100% |################################| Time: 0:00:00   5.87 MB/s
conda-4.2.12-p 100% |################################| Time: 0:00:00   3.74 MB/s
cairo-1.12.18- 100% |################################| Time: 0:00:00   5.90 MB/s
harfbuzz-0.9.3 100% |################################| Time: 0:00:00   6.05 MB/s
pango-1.39.0-1 100% |################################| Time: 0:00:00   2.71 MB/s
graphviz-2.38. 100% |################################| Time: 0:00:01   7.16 MB/s
Extracting packages ...
[      COMPLETE      ]|###################################################| 100%
Unlinking packages ...
[      COMPLETE      ]|###################################################| 100%
Linking packages ...
[      COMPLETE      ]|###################################################| 100%
```

GraphVizが正しく入ると`dot`コマンドが使えるようになるので、確認しておきます。

```console:端末（GraphVizのインストール確認）
segavvy@ubuntu:~$ dot -V
dot - graphviz version 2.38.0 (20140413.2041)
```

####pydot-ngのインストール

ここまでは順調でしたがpydot-ngのインストールでつまずきました。環境が合わないというエラーのようです。

```console:端末（condaでのpydot-ngインストールエラー）
segavvy@ubuntu:~$ conda install pydot-ng
Fetching package metadata .......
Solving package specifications: ....


UnsatisfiableError: The following specifications were found to be in conflict:
  - pydot-ng
  - python 3.5*
Use "conda info <package>" to see the dependencies for each package.

```

言われたとおり`conda info`してみます。

```console:端末（pydot-ngのパッケージ情報確認）
segavvy@ubuntu:~$ conda info pydot-ng
Fetching package metadata .......

pydot-ng 1.0.0.15 py27_0
------------------------
file name   : pydot-ng-1.0.0.15-py27_0.tar.bz2
name        : pydot-ng
version     : 1.0.0.15
build number: 0
build string: py27_0
channel     : defaults
size        : 45 KB
date        : 2015-09-09
fn          : pydot-ng-1.0.0.15-py27_0.tar.bz2
license     : MIT
md5         : 8b81a344723e64ec3545b5f030caca47
priority    : 0
schannel    : defaults
url         : https://repo.continuum.io/pkgs/free/linux-64/pydot-ng-1.0.0.15-py27_0.tar.bz2
dependencies:
    pyparsing
    python 2.7*

pydot-ng 1.0.0.15 py34_0
------------------------
file name   : pydot-ng-1.0.0.15-py34_0.tar.bz2
name        : pydot-ng
version     : 1.0.0.15
build number: 0
build string: py34_0
channel     : defaults
size        : 46 KB
date        : 2015-09-09
fn          : pydot-ng-1.0.0.15-py34_0.tar.bz2
license     : MIT
md5         : 13e3a10b45edfb38d91a51d6b3ccabc7
priority    : 0
schannel    : defaults
url         : https://repo.continuum.io/pkgs/free/linux-64/pydot-ng-1.0.0.15-py34_0.tar.bz2
dependencies:
    pyparsing
    python 3.4*
```

どうやらPython 3.4用のパッケージはPython 3.5には入れられない、ということのようです。でも世の中Python 3.5で使っている方もいるので、0.1の差で弾かないで欲しい...
condaのサイトの説明（[Resolution: Fix the conflicts in the installation request](http://conda.pydata.org/docs/troubleshooting.html#unsatisfiable)）も見てみましたが、無理やり入れる方法はわかりませんでした。

仕方ないのでcondaでのインストールは諦めて、pipで挑戦です。

```console:端末（pipでpydot-ngを検索）
segavvy@ubuntu:~$ pip search pydot-ng
pypayd-ng (0.0.6)                          - A small daemon for processing
                                             bitcoin payments compatible with
                                             modern HD wallets
maestro-ng (0.4.1)                         - Orchestrator for multi-host
                                             Docker deployments
django-auth-ldap-ng (1.7.6)                - Django LDAP authentication
                                             backend
pydot (1.2.3)                              - Python interface to Graphviz's
                                             Dot
telescope-ng (0.1)                         - Observe SPARQLing RDF
                                             constellations through Python
                                             objects.
django-dajax-ng (0.9.4)                    - Easy to use library to create
                                             asynchronous presentation logic
                                             with django and dajaxice-ng
django-dajaxice-ng (0.7.0.7)               - Agnostic and easy to use ajax
                                             library for django
django-macaddress-ng (1.1.1)               - MAC address model and form fields
                                             for Django apps.
django-rest-hooks-ng (1.1.1)               - A powerful mechanism for sending
                                             real time API notifications via a
                                             new subscription model.
django-ninjapaginator-ng (0.1.6)           - Django application with multiple
                                             type of pagination integrated
cmsplugin-text-ng (0.6)                    - django-cms improved text plugin
ApplianceKit-NG (0.6.2)                    - Tools to programatically create
                                             distribution images from any
                                             distribution
pydot-ng (1.0.0)                           - Python interface to Graphviz's
                                             Dot
suds-ng (0.4.1)                            - Lightweight SOAP client - fork of
                                             suds
leancloud-sdk-ng (2.0.0)                   - LeanCloud Python SDK
django-extra-views-ng (0.3.3)              - Extra class-based views for
                                             Django
django-cas-ng (3.5.6)                      - CAS 1.0/2.0 client authentication
                                             backend for Django (inherited
                                             from django-cas)
booby-ng (0.8.3.post1)                     - Data modeling and validation
                                             Python library
keyserver-ng (0.1.0)                       - PGP keyserver with email and key
                                             validation.
pysensu-ng (0.7.0)                         - This is a client to interact with
                                             the Sensu API
django-crispy-forms-ng (2.0.0)             - Best way to have Django DRY forms
                                             of the next generation.
caffeine-ng (3.3.8)                        - A status bar application able to
                                             temporarily prevent the
django-oauth2-provider-ng (0.2.7.4)        - Provide OAuth2 access to your app
Flask-CDN-NG (1.3.0)                       - Serve the static files in your
                                             Flask app from a CDN.
tlslite-ng (0.7.0-alpha2)                  - Pure python implementation of SSL
                                             and TLS.
python-kyototycoon-ng (0.7.3)              - Python client library for the
                                             Kyoto Tycoon key-value store
brutal-ng (0.3.11)                         - The new generation of brutal, a
                                             multi-network asynchronouschat
                                             bot framework using twisted
django-timezone-field-ng (2.0)             - A Django app providing database
                                             and form fields for pytz timezone
                                             objects.
django-tagging-ng (0.3.3)                  - Enhanced tagging application for
                                             Django, based on django-tagging
cplay-ng (2.1.2)                           - A curses front-end for various
                                             audio players
arrow-ng (0.5.0)                           - Better dates and times for Python
ng-mini (0.1.1)                            - rrdtool based server monitor
                                             tool,and a web interface include
django-filebased-email-backend-ng (2.0.2)  - A better 'file' email backend for
                                             Django
micromodels-ng (0.6.3)                     - Declarative dictionary-based
                                             model classes for Python
iencode-ng (0.9.5)                         - iPhone video encoding tools.
django-polymorphic-ng (0.8.0)              - Seamless Polymorphic Inheritance
                                             for Django Models
django-mockups-ng (0.5.0)                  - Provides tools to auto generate
                                             content.
pytest-datadir-ng (1.1.0)                  - Fixtures for pytest allowing test
                                             functions/methods to easily
                                             retrieve test resources from the
                                             local filesystem.
django-js-utils-ng (0.5.futu)              - Django URL Exposure to Javascript
crontab-ng (0.20.3)                        - Parse and use crontab schedules
                                             in Python
purity-ng (0.1)                            - general purpose purity testing
                                             software
django-tastypie-swagger-ng (0.1.3)         - An adapter to use swagger-ui with
                                             django-tastypie
django-multilingual-ng (0.1.21)            - Multilingual extension for Django
                                             - NG
wymypy-ng (2.1)                            - Simple web interface for
                                             controlling MPD
sinaweibopy-ng (1.1.5)                     - Sina Weibo OAuth 2 API Python SDK
pyLisp-NG (2.0.0)                          - A very simple implementation of
                                             Lisp in Python that is perfectly
                                             suitable for Python projects
                                             needing Lisp-like capabilities.
checkbox-ng (0.29)                         - Checkbox - Command Line Test
                                             Runner
You are using pip version 8.1.2, however version 9.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
```

なんか検索ノイズだらけですが、10数行目のところにpydot-ngがありました。
最後の2行でpipそのもののアップグレードをお勧めされているので、先にpipをアップグレードします。

```console:端末（pipのアップグレード）
segavvy@ubuntu:~$ pip install --upgrade pip
Collecting pip
  Downloading pip-9.0.1-py2.py3-none-any.whl (1.3MB)
    100% |████████████████████████████████| 1.3MB 827kB/s 
Installing collected packages: pip
  Found existing installation: pip 8.1.2
    Uninstalling pip-8.1.2:
      Successfully uninstalled pip-8.1.2
Successfully installed pip-9.0.1
```

続いてpydot-ngのインストールです。

```console:端末（pipでpydot-ngのインストール）
segavvy@ubuntu:~$ pip install pydot-ng
Collecting pydot-ng
  Downloading pydot_ng-1.0.0.zip
Requirement already satisfied: pyparsing>=2.0.1 in ./anaconda3/lib/python3.5/site-packages (from pydot-ng)
Building wheels for collected packages: pydot-ng
  Running setup.py bdist_wheel for pydot-ng ... done
  Stored in directory: /home/segavvy/.cache/pip/wheels/4f/09/d5/f96fd2578831e1b9021c634f057ab5306a3e4287efa800de29
Successfully built pydot-ng
Installing collected packages: pydot-ng
Successfully installed pydot-ng-1.0.0
```

無事インストールできました。
Pythonでimportできることを確認しておきます。なお、[Installation:](https://github.com/pydot/pydot-ng/blob/master/README.rst#installation)のところにあるように、import時は`pydot_ng`です。ngの前は`-`（ハイフン）ではなく`_`（アンダースコア）なのでご注意ください。

```console:端末
segavvy@ubuntu:~$ python
Python 3.5.2 |Anaconda 4.1.1 (64-bit)| (default, Jul  2 2016, 17:53:06) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pydot_ng
>>> 
```

これでやっと問題が始められます。ふぅ。

###問題42のコードの再利用

今回の問題は「吾輩は猫である」（`neko.txt`）とは関係なく「与えられた文」を処理するのですが、グラフを書く直前までにやることは[問題42](http://qiita.com/segavvy/items/58894f76dba367b2925b)とまったく同じです。そこでコードを最大限再利用するために（＝最大限手を抜くために^^;）、入力された内容で「吾輩は猫である」の代わりのファイルを作り、これまでの処理をそのまま流す形にしました。
クラスと関数は問題42のままですが、ファイル名は`neko.txt.tmp`と`neko.txt.cabocha.tmp`に変更しています。

###有向グラフとは
ウィキブックスの[グラフ理論](https://ja.wikibooks.org/wiki/グラフ理論)の解説が分かりやすいです。最初の方を読めば、今回の問題には十分そうでした。

###pydotの使い方
pydotはまとまった解説が見つけられず、ちょっと苦戦しました。いろんなサイトから寄せ集めてきた知識をベースに、最終的にシンプルなコードに落ち着きました。

まず最初に、ノード（今回の実行結果の図における丸で囲まれた節の部分）をペアにしたタプルを作って、リストに詰め込んでいきます。このノードのペアがエッジ（実行結果の図における線の部分）で結ばれる形になります。

詰め込み終わったら~~`pydot.graph_from_edges()`~~`graph_from_edges_ex()`で、`pydot.Dot`オブジェクトに変換します。この時に`directed=True`を指定すると有向グラフとなり、エッジが矢印になります。この関数のおかげで、DOT言語を知らなくても簡単にグラフが書けます。問題文にある「Pythonから有向グラフを直接的に可視化するには，pydotを使うとよい．」の「直接的」とは、DOT言語を使わずに済むことを指しているようです。

最後に`pydot.Dot.write()`でファイルに出力します。この関数は`write_png()`や`write_pdf()`や`write_ps()`といった感じで、関数名によってフォーマットを指定できます。今回はpngにしてみました。

*2016/12/25追記*
*コードを修正しました。当初はpydotに用意されている`pydot.graph_from_edges()`を使ったシンプルなコードだったのですが、これだと1つの文の中で同じ表記の文節があった場合に、混ざってしまうことに気づきました。*
*例えば「今日の天気は晴れでしたが、明日の天気は悪くなるそうです。」という文の場合、修正前のコードだと次のようになってしまいます。*

![Kobito.x0sZW8.png](https://qiita-image-store.s3.amazonaws.com/0/139624/9028c987-cb78-fbcb-ec50-47d1407c36f9.png "Kobito.x0sZW8.png")


*しかし、文中に出てくる2つの「天気」は別物で、次のように区別する必要があります。*

![Kobito.kcxARN.png](https://qiita-image-store.s3.amazonaws.com/0/139624/c5ff67fe-4f50-dc18-2956-744d172e9051.png "Kobito.kcxARN.png")

*そこで、stack overflowにあった [pydot: is it possible to plot two different nodes with the same string in them?](http://stackoverflow.com/questions/13012621/pydot-is-it-possible-to-plot-two-different-nodes-with-the-same-string-in-them) という書き込みと`pydot.graph_from_edges()`のソースを参考に、`graph_from_edges_ex()`を作りました。*

###pydotの仕様の調べ方？
前述のようにpydotについてはまとまった解説が見つけられなかったため、仕様が分からない部分は`help()`で確認しました。

Pythonのモジュールは、docstringという仕組みに従ってソースにコメントが入って入れば`help()`で確認できます。例えば`pydot.graph_from_edges()`の仕様は、次のような感じで確認できます。

```console:端末（pydot.graph_from_edgesの仕様の確認）
segavvy@ubuntu:~/ドキュメント/言語処理100本ノック2015/44$ python
Python 3.5.2 |Anaconda 4.1.1 (64-bit)| (default, Jul  2 2016, 17:53:06) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pydot_ng as pydot
>>> help(pydot.graph_from_edges)
```

```console:help(pydot.graph_from_edges)
Help on function graph_from_edges in module pydot_ng:

graph_from_edges(edge_list, node_prefix='', directed=False)
    Creates a basic graph out of an edge list.
    
    The edge list has to be a list of tuples representing
    the nodes connected by the edge.
    The values can be anything: bool, int, float, str.
    
    If the graph is undirected by default, it is only
    calculated from one of the symmetric halves of the matrix.
```

ただ、これでもちょっと説明が足りないので、ソースも覗いてみました。ソースの場所は`__file__`で確認できます。

```console:端末（pydot_ngのソースの場所の確認）
>>> print(pydot.__file__)
/home/segavvy/anaconda3/lib/python3.5/site-packages/pydot_ng/__init__.py
```

このファイルを開いて`def graph_from_edges`で検索すると実装部分が確認できます。Pythonで作られているモジュールは、この流れでソースの確認できるみたいです。仕様が良く分からないものはソースを確認するのが良さそうですね。

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

*実行結果には、[100本ノックで用いるコーパス・データ](http://www.cl.ecei.tohoku.ac.jp/nlp100/#data)で配布されているデータの一部が含まれます。この第5章で用いているデータは[青空文庫](http://www.aozora.gr.jp)で公開されている夏目漱石の長編小説『吾輩は猫である』が元になっています。*