形態素解析で代名詞+助詞+名詞を文章から抜き出す【Python, 自然言語処理, Janome】
目次
Pythonで形態素解析
人間が発する言語を「自然言語」と言い、それの解析を「自然言語処理」といいます。
さらに日本語の文章を単語ごとに分割することを「形態素解析」と言い、これを行うライブラリは「MeCab」などが有名です。
Pythonには「Janome」という形態素解析ライブラリがあり、こちらは手軽に導入することが可能です。
Janomeを使い、夏目漱石の「坊ちゃん」の文章を解析して、「代名詞 + 助詞 + 名詞」の組み合わせを文章から抜き出す処理を書きたいと思います。
代名詞と言うのは「俺」「お前」「それ」などで、助詞は「て」「に」「を」「は」、名詞は「リンゴ」「車」などの単語のことです。
Janomeのインストール
pip
で形態素解析ライブラリのJanomeをインストールします。
$ pip install janome $ pip freeze Janome==0.4.0
Janomeの使い方
Janomeで形態素解析を行いますが、最初にjanome.tokenizer.Tokenizer
をインポートします。
from janome.tokenizer import Tokenizer
Tokenizer
は文章を形態素解析して、単語ごとに分割し、ジェネレーターを返すクラスです。
Tokenizer.tokenize()
メソッドに文章を渡して実行することで解析を行えます。
from janome.tokenizer import Tokenizer t = Tokenizer() toks = t.tokenize('ここに日本語の文章') print(type(toks))
Tokenizer.tokenize()
の戻り値はgenerator
です。
<class 'generator'>
これをfor
文などで回すと単語の情報を含んだトークンを取り出せます。
for tok in toks: print(tok)
ここ 名詞,代名詞,一般,*,*,*,ここ,ココ,ココ に 助詞,格助詞,一般,*,*,*,に,ニ,ニ 日本語 名詞,一般,*,*,*,*,日本語,ニホンゴ,ニホンゴ の 助詞,連体化,*,*,*,*,の,ノ,ノ 文章 名詞,一般,*,*,*,*,文章,ブンショウ,ブンショー
トークンの情報
Tokenizer.tokenize()
で生成されるトークン列のトークンにはさまざまな属性がありますが、今回使用する属性は↓の通りです。
surface
part_of_speech
Token.surface
は「表層形」と呼ばれる単語の元の文章のままの文字列のことです。
このToken.surface
を参照することで元の文章における単語の表現を取得できます。
from janome.tokenizer import Tokenizer t = Tokenizer() toks = list(t.tokenize('ここに日本語の文章')) print(toks[0].surface) # 最初の単語のsurfaceを参照する
ここ
Token.part_of_speech
はカンマ区切りで並べられた品詞の列です。
単語の品詞を判定したい場合はこのToken.part_of_speech
を参照します。
今回の例で言えば「代名詞」と「助詞」、「名詞」を判定したいわけなので、このToken.part_of_speech
にそれらの文字列が含まれているかどうかチェックします。
from janome.tokenizer import Tokenizer t = Tokenizer() toks = t.tokenize('ここに日本語の文章') for tok in toks: print(tok.part_of_speech) # 品詞を出力
名詞,代名詞,一般,* 助詞,格助詞,一般,* 名詞,一般,*,* 助詞,連体化,*,* 名詞,一般,*,*
ちなみにjanome.analyzer.Analyzer
とjanome.tokenfilter
を使えば特定の品詞のトークンのみを文章から抽出することが出来ます。
しかし、今回は文脈上の「代名詞 + 助詞 + 名詞」の組み合わせを判定したいので、ここでは使用方法などは割愛します。
「代名詞 + 助詞 + 名詞」の組み合わせを抽出する
Janomeで文章を形態素解析してトークン列に分割したら、あとは自前のアナライズ関数を使って「代名詞 + 助詞 + 名詞」の組み合わせを抽出します。
このアナライズ関数は「状態遷移」という技術を使います。
状態を持つ変数m
を定義し、解析の過程でこの変数m
を変化させます。
変数m
の値に応じて解析方法を柔軟に変更し、目的のトークン列があるかどうか判定します。
トークン列を抜き出したらyield
文で結果を生成し、呼び出し元で参照できるようにします。
def analyze(toks): """ 「代名詞 + 助詞 + 名詞」の組み合わせのトークン列を抽出する """ m = 0 buf = [] for tok in toks: p = tok.part_of_speech.split(',') if m == 0: if '代名詞' in p: m = 10 buf.append(tok) elif m == 10: # found 代名詞 if '助詞' in p: m = 20 buf.append(tok) elif '代名詞' in p: buf = [tok] else: buf = [] m = 0 elif m == 20: # found 助詞 if '名詞' in p: buf.append(tok) yield buf buf = [] m = 0 else: buf = [] m = 0
ちなみに状態遷移を使わなくても書けます。
↓の例は状態遷移を使わずに単純に現在のトークンの2つ先まで先読みする方法です。
こちらのほうが解法としてはシンプルですね。
def analyze(toks): """ 「代名詞 + 助詞 + 名詞」の組み合わせのトークン列を抽出する """ toks = list(toks) i = 0 while i < len(toks) - 2: t1 = toks[i] t2 = toks[i + 1] t3 = toks[i + 2] i += 1 if '代名詞' in t1.part_of_speech.split(',') and \ '助詞' in t2.part_of_speech.split(',') and \ '名詞' in t3.part_of_speech.split(','): yield [t1, t2, t3]
(^ _ ^) | 状態遷移に頼りすぎな私 |
コード全文は↓になります。
# coding: utf-8 from janome.tokenizer import Tokenizer def dump(toks): for tok in toks: print(tok) def analyze(toks): """ 「代名詞 + 助詞 + 名詞」の組み合わせのトークン列を抽出する """ toks = list(toks) i = 0 while i < len(toks) - 2: t1 = toks[i] t2 = toks[i + 1] t3 = toks[i + 2] i += 1 if '代名詞' in t1.part_of_speech.split(',') and \ '助詞' in t2.part_of_speech.split(',') and \ '名詞' in t3.part_of_speech.split(','): yield [t1, t2, t3] def main(): # 「坊っちゃん」の文章 content = ''' 親譲おやゆずりの無鉄砲むてっぽうで...(中略) ''' t = Tokenizer() toks = t.tokenize(content) for toks in analyze(toks): s = ''.join([tok.surface for tok in toks]) print(s) if __name__ == '__main__': main()
↑のコード中の夏目漱石の「坊ちゃん」の文章を解析すると↓のような結果になります。
君の指 これは命 こっちの胸 おれの袷 おれの二の腕 おれの袷
おれの二の腕
淑女の皆さんには刺激の強いワードが出てきてしまいました。
↑のコードのcontent
変数には「坊っちゃん」の文章を保存しておきます。
夏目漱石の「坊っちゃん」は青空文庫で無料で公開されています。
以上です。