Edited at

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

More than 1 year has passed since last update.

言語処理100本ノック 2015の挑戦記録です。環境はUbuntu 16.04 LTS + Python 3.5.2 :: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧はこちらからどうぞ。


第6章: 英語テキストの処理


英語のテキスト(nlp.txt)に対して,以下の処理を実行せよ.



56. 係り受け解析


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



出来上がったコード:


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

2.png

Kobito.Qvt44t.png

3.png

Kobito.VO2v84.png

4.png

Kobito.MH5yWi.png

5.png

Kobito.d8dyTi.png

全体の結果はGitHubにアップしています。


係り受け解析

今回の問題は問題44の英語版です。graph_from_edges_ex()は、問題44からそのまま持ってきて使っています。

Stanford Core NLPの係り受け解析は、DependencyParseAnnotatorによるとデフォルトで3種類あるようです。問題で指定されている collapsed-dependencies はその1種類で、解析結果がxmlファイルの<dependencies type="collapsed-dependencies">タグで出力されています。


端末: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と同じです。


ROOTノード

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

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


句読点などの記号の除去

問題42問題43で日本語の係り受け解析をやっていた時は「句読点などの記号は出力しないようにせよ」という指示がありました。今回は特に指示がありませんが、邪魔なので同様に出力を抑制しています。

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

 

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


実行結果には、100本ノックで用いるコーパス・データで配布されているデータの一部が含まれます。この第6章で用いているデータのライセンスはクリエイティブ・コモンズ 表示-継承 3.0 非移植日本語訳)です。