ユーニックス総合研究所

  • home
  • archives
  • spacy-obj

spaCyで目的語を抽出する【自然言語処理, Python】

spaCyで目的語を抽出する

私たちが使う言語は「自然言語」と言います。
そしてその自然言語をプログラム的に解析することを「自然言語処理」と言います。

Pythonには自然言語処理を行うライブラリspaCy(スパイシー)があります。
今回はこのspaCyを使って日本語の文章から目的語を抽出するプログラムを作ってみたいと思います。

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

  • spaCyとは?
  • 目的語とは?
  • spaCyの基本的な使い方
  • spaCyで目的語を抽出

関連記事

spaCyの品詞ラベル(POS)一覧(日本語訳)【自然言語処理, Python】
spaCyのis_punctで句読点かどうか調べる【自然言語処理, Python】
spaCyのdisplacy.serve()の使い方【自然言語処理, Python】
spaCyとGiNZAで名詞を抽出する【自然言語処理, Python】
spaCyで虫食い文章を穴埋めする【自然言語処理, Python】

spaCyとは?

spaCyとはプログラミング言語のPythonとCythonで開発されたライブラリです。
自然言語を解析することができます。
さまざまな言語で学習済み統計モデルを使うことが出来ます。
オープンソースで、MITライセンスで利用することができます。

spaCyではGiNZAによる統計モデルをロードすることで、日本語の文章を解析することができます。
具体的には字句解析、構文解析などです。
GiNZAは日本語処理のための自然言語ライブラリです。
GiNZAはリクルートと国立国語研究所の共同研究で生まれました。

spaCyによる日本語の文章の解析では、基本的にはspaCyとGiNZAのモデルを組み合わせて使います。

目的語とは?

目的語とは、動詞などの動作の影響を受ける単語を指します。

助詞の「を」が付く単語です。
直接目的語と間接目的語に区別されることがあります。

たとえば「私は猫を撫でた」という文章では、目的語は「猫」です。
「撫でた」という動作を受けるのが「猫」だからです。
ほかにも「猫は私を舐めた」という文章では、目的語は「私」になります。
「舐めた」という動作を受けるのが「私」だからです。

spaCyの基本的な使い方

spaCyを使うにはspacyモジュールをインポートします。
それから統計モデルをロードします。
ここではGiNZAの統計モデルをロードします。

import spacy  

nlp = spacy.load('ja_ginza')  

spacy.load()の返り値には慣例的にnlpと命名します。
nlpに文章を渡すとその文章を解析することができます。

doc = nlp('私は猫を撫でた')  

nlpの返り値には慣例的にdocと命名します。
このdocのメソッドや属性を使うことで解析した結果を参照することが可能です。
たとえばdoc.sentsを参照すると、文章全体のセンテンスを取得できます。

import spacy  

nlp = spacy.load('ja_ginza')  
doc = nlp('私は猫を撫でた。猫も私を舐めた。')  

for sent in doc.sents:  # センテンスを参照  
    print(sent)  

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

私は猫を撫でた。  
猫も私を舐めた。  

spaCyで目的語を抽出

spaCyは文章の依存構造を解析します。
依存構造はたとえば単語間の係り受けの関係などです。
中でもspaCyは依存構造の解析で、単語にラベルを振ってその単語の依存構造における役割を保存します。
これはトークンのdep属性がそうです。

この属性を参照することにより、その単語が依存構造においてどういう役割を持っているか知ることができます。
たとえばトークンのdepobjであれば、そのトークンは目的語の役割を持っているという具合です。

spaCyでトークン列を参照するにはdocfor文で回します。
そしてトークンのdepを参照するには↓のようにします。

for token in doc:  
    print(token.dep)  

目的語を表すobjは、spacy.symbolsモジュール内に定義されています。

from spacy.symbols import obj  

depの値をobjと比較することでそのトークンが目的語かどうか判定できます。

for token in doc:  
    print(token.dep == obj)  

つまり目的語であるトークンのテキストを表示するコードは↓のようになります。

import spacy  
from spacy.symbols import obj  

nlp = spacy.load('ja_ginza')  # モデルをロード  
doc = nlp('私は猫を撫でた')  # 文章を解析  

for tok in doc:  
    if tok.dep == obj:  # トークンが目的語なら  
        print(tok.text)  # テキストを表示  

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

質問への回答

質問をいただきました。

{猫:撫でた}と表示したいです。

「撫で」が動詞(VERB)になるので、「撫で」で引っかけて取得することができます。
それから「撫でた」の「た」は助動詞(AUX)になりますのでこれも引っかけます。

import spacy  
from spacy.symbols import obj, VERB, AUX  

nlp = spacy.load('ja_ginza')  # モデルをロード  
doc = nlp('私は猫を撫でた')  # 文章を解析  

s = ''  

for tok in doc:  
    if tok.dep == obj:  # トークンが目的語なら  
        print(tok.text)  # テキストを表示  
        s += tok.text + ': '  
    elif tok.pos == VERB or tok.pos == AUX:  # トークンが動詞または助動詞なら  
        print(tok.text)  # テキストを表示  
        s += tok.text  
        # 「撫で」は動詞、「た」は助動詞になる  

print(s)  # 猫: 撫でた  

ただこの方法は汎用的ではありません。
目的語や動詞が複数登場した場合は取得結果がおかしくなる可能性があります。

依存構造を親子関係からたどる方法もあります。

import spacy  
from spacy.symbols import obj, VERB, AUX  
# import deplacy  

nlp = spacy.load('ja_ginza')  # モデルをロード  
doc = nlp('私は猫を撫でた')  # 文章を解析  
# deplacy.render(doc)  

s = ''  

for tok in doc:  
    if tok.dep == obj:  
        s += tok.text + ': '  
        s += tok.head.text  
        s += list(tok.head.rights)[0].text  

print(s)  # 猫: 撫でた  

tok.headで親のトークン、tok.rightsで右の子供(ジェネレータ)を参照できます。
この方法も汎用性があるかと言われると微妙ですが、もっとコードを複雑にすればある程度汎用的にできると思います。

docの木構造を視覚的に見たい場合は、deplacyという便利ライブラリがあります。
pip install deplacyでインストールできます。
これを使うと↓のように表示できます。

私   PRON ═╗<══╗ nsubj  
は   ADP  <╝   ║ case  
猫   NOUN ═╗<╗ ║ obj  
を   ADP  <╝ ║ ║ case  
撫で VERB ═╗═╝═╝ ROOT  
た   AUX  <╝     aux  

おわりに

今回はspaCyで目的語を抽出してみました。
目的語がわかると動詞の行為の対象を得ることができます。
「だれが『だれに』やった」の「だれに」の部分がわかるということになります。
この記事が参考になれば幸いです。

🦝 < もっと撫でて

🐭 < 目的語がしゃべった