ユーニックス総合研究所

  • home
  • archives
  • spacy-print-dep-tree

spaCyで依存構造を自力で出力する【自然言語処理】

spaCyで依存構造を出力する

今回は自然言語処理ライブラリであるspaCyを使い日本語の文章を解析し、その依存構造を出力するということをPythonでやってみたいと思います。

日本語の文章の依存構造を自力で出力することにより、spaCyによる依存構造の走査を把握することができます。
再帰的にトークンの子供を走査して、そのトークンの持つテキストを出力します。

今回は具体的には↓を見ていきます。

  • spaCyとは?
  • 依存関係とは?
  • spaCyの基本的な使い方
  • deplacyによる依存構造の出力
  • 自力で依存構造の出力

spaCyとは?

spaCy(スパイシー)とはExplosion AIが開発している自然言語処理ライブラリです。
PythonとCythonで書かれています。
オープンソースなプロジェクトで、MITライセンスで利用することができます。
様々な自然言語の学習済み統計モデルを利用し、文章を解析することができます。

spaCyで日本語の文章を解析する場合は統計モデルにGiNZAのモデルを使います。
GiNZAはリクルートと国立国語研究所の共同研究で生まれた自然言語ライブラリです。

依存関係とは?

文章における依存関係とは、単語や文節間の依存関係のことです。
依存関係とは修飾/被修飾関係や係り受け関係のことを言います。

文章の依存関係を解析することを依存構造解析と言い、依存構造は通常はツリー構造で表現されます。
この木構造は単語や文節をノードとして持っています。
文節や単語は修飾/被修飾の親子関係で表現されます。

依存構造を解析することによってどの単語がどの単語に係っているかとか、どの単語がどの単語を修飾しているかなどがわかります。
自然言語処理ではこの依存構造を扱うことによって意味解析や文脈解析に発展するということになります。

spaCyの基本的な使い方

spaCyはPythonではspacyモジュールをインポートして使います。

import spacy  

それから最初に統計モデルのロードを行います。
今回はGiNZAの統計モデルを使いますので、spacy.load()に文字列ja_ginzaを渡します。

nlp = spacy.load('ja_ginza')  

spacy.load()の返り値はja_ginzaを読み込んだ場合はginza.Japaneseクラスが返ってきます。
この返り値には慣例的にnlpと命名します。

nlpに文字列を渡すと解析を行うことができます。

doc = nlp('棚の上にあるミカン')  

オブジェクトにしたnlpは慣例的にdocと命令されます。
このdocから解析した結果にアクセスすることができます。
たとえばdocfor文で回すとトークン列を取得できます。

import spacy  

nlp = spacy.load('ja_ginza')  # モデルのロード  
doc = nlp('棚の上にあるミカン')  # 文章解析  

for tok in doc:  # トークン列の抜き出し  
    print(tok.text)  

↑のコードを実行すると↓のような結果になります。

棚  
の  
上  
に  
ある  
ミカン  

deplacyによる依存構造の出力

deplacyというモジュールを使うとspaCyの解析した構造を簡単に画面に出力することができます。
pipでインストールが必要です。

$ pip deplacy  

deplacyを使うにはdeplacyモジュールをインポートします。
それからdeplacy.render()docを渡します。

import spacy  
import deplacy  

nlp = spacy.load('ja_ginza')  # モデルのロード  
doc = nlp('棚の上にあるミカン')  # 文章の解析  

deplacy.render(doc, Japanese=True)  # 依存構造の出力  

↑のコードを実行すると↓のような結果になります。

棚     NOUN ═╗<╗     nmod(体言による連体修飾語)  
の     ADP  <╝ ║     case(格表示)  
上     NOUN ═╗═╝<╗   iobj(間接目的語)  
に     ADP  <╝   ║   case(格表示)  
ある   VERB ═════╝<╗ acl(連体修飾節)  
ミカン NOUN ═══════╝ ROOT(親)  

↑のように矢印で依存関係が表現されたテキストが出力されます。

自力で依存構造の出力

nlpで解析したdocから木構造のトークン列を参照することができますが、このトークン列は各トークンごとにその依存先のトークンを持っています。
これは例えばtoken.childrenなどで参照することができます。

import spacy  

nlp = spacy.load('ja_ginza')  # モデルのロード  
doc = nlp('棚の上にあるミカン')  # 文章の解析  

for token in doc:  
    print(token.children)  # トークンの持つ子供を参照  

↑のコードを実行すると↓のような出力が得られます。

<generator object at 0x7f3256a04550>  
<generator object at 0x7f3256a04550>  
<generator object at 0x7f3256a04550>  
<generator object at 0x7f3256a04550>  
<generator object at 0x7f3256a04550>  
<generator object at 0x7f3256a04550>  

token.childrenの参照でジェネレーターが返ってきてますね。
token.childrenlist()に渡すと、

for token in doc:  
    print(list(token.children))  # トークンの持つ子供を参照  

↓のように子要素のトークンが出力されます。

[の]  
[]  
[棚, に]  
[]  
[上]  
[ある]  

トークン自体のテキストも出力すると↓のようになります。

for token in doc:  
    print(token.text, list(token.children))  # トークンの持つ子供を参照  
棚 [の]  
の []  
上 [棚, に]  
に []  
ある [上]  
ミカン [ある]  

先ほどの依存構造のツリーと見比べると、矢印の通りに子供が生成されているのがわかります。
このtoken.childrenlist()に渡した結果は、トークン列です。
そのためこのトークンを参照することで再帰的に子供を辿ることができます。

これを利用して再帰関数を定義すれば、非常にチープな木構造の出力器を作ることができます。
↓のように定義します。

import spacy  

nlp = spacy.load('ja_ginza')  # モデルのロード  
doc = nlp('棚の上にあるミカン')  # 文章の解析  

def show_tree(token, dep=0):  
    """  
    再帰的にtoken.childrenを辿る再帰関数  
    """  
    print(dep * '_' + token.text)  
    for child in token.children:  
        show_tree(child, dep + 1)  

# docからセンテンスを取り出しルート要素から木構造を出力する  
for sent in doc.sents:  
    show_tree(sent.root)  

↑のコードを実行すると↓のような出力になります。

ミカン  
_ある  
__上  
___棚  
____の  
___に  

doc.sentsで文章全体をセンテンスごとに取り出すことができます。
そしてこのセンテンスはrootという属性を持っています。
これはツリー構造におけるルートのトークンです。

show_tree()という関数にこのルートのトークンを渡し、show_tree()内ではトークンのchildrenを参照します。
childrenのトークンを再帰的にshow_tree()に渡して、depというカウント変数を増加させます。
depの値だけインデントしてトークンのtextを出力します。

おわりに

今回はspaCyで依存構造のツリー構造を出力してみました。
依存構造の出力はtoken.childrenを参照すれば簡単に行えます。

🦝 < 依存関係を制する者は

🐭 < NLPを制す