形態素解析で代名詞+助詞+名詞を文章から抜き出す【Python, 自然言語処理, Janome】

73, 2020-10-10

目次

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.Analyzerjanome.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変数には「坊っちゃん」の文章を保存しておきます。
夏目漱石の「坊っちゃん」は青空文庫で無料で公開されています。

以上です。



この記事のアンケートを送信する