言語処理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を使うとよい.
####出来上がったコード:
# 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))
####実行結果:
文の先頭からいくつかを貼り付けます。
全体の結果はGitHubにアップしています。
###係り受け解析
今回の問題は問題44の英語版です。graph_from_edges_ex()
は、問題44からそのまま持ってきて使っています。
Stanford Core NLPの係り受け解析は、DependencyParseAnnotatorによるとデフォルトで3種類あるようです。問題で指定されている collapsed-dependencies はその1種類で、解析結果がxmlファイルの<dependencies type="collapsed-dependencies">
タグで出力されています。
<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 非移植(日本語訳)です。