2019年7月27日土曜日

Pythonで文章から複合語を抽出する

MeCabで文章を形態素解析すると品詞ごとに分解してくれるが、分解してほしくない語まで分解してしまうことがある。例えば「江戸っ子」をMeCabで形態素解析すると以下のように2つに分解される。


分解させたくない語はMeCabの辞書に登録すればひとつの語として処理してくれるが、そもそもどの語を登録すればよいか文書すべてを読んで確認するのは、文章量が多いときには現実的ではない。そこで、文章から用語を自動抽出できるPythonモジュールのtermextractを使って、MeCabでは複数の語に分解されてしまう複合語を自動抽出してみる。


環境


Windows10のWSL(Ubuntu 18.04)。


MeCabのインストールはもう少し簡単にKH Coderで新語に対応するためにmecab-ipadic-NEologdを使うを参照。

termextractのインストール


用語を自動抽出するためのPythonモジュールtermextractをインストールする。インストール用のファイルをダウンロードして、スクリプトを実行する。



複合語を抽出するテキスト


termextractで複合語を抽出するテキストを用意する。ここでは青空文庫の『坊っちゃん』を使う。Pythonで青空文庫の作品テキストからルビなどを取り除くの方法で、ダウンロードした『坊っちゃん』のテキストからルビなどを取り除いてbochan.txtとして保存しておく。


『坊っちゃん』から複合語を抽出する


termextractはテキストファイルを直接読み込んで使うことも、MeCabやjanomeで形態素解析した結果を使うこともできる。ここではMeCabで『坊っちゃん』のテキストを形態素解析して、その結果を使う。termextractが抽出する用語には複合語だけでなく単語も含まれるので、単語を省いて、重要度が高い複合語トップ10を出力する。
import termextract.mecab
import termextract.core
from collections import Counter
import MeCab

m = MeCab.Tagger('-Ochasen')
m.parse('')

def get_mecab_tagged(text):
    # MeCabで形態素解析

    node = m.parseToNode(text)

    buf = ''

    # BOS/EOS
    while node:
        # 表層形が空白(BOS/EOS)の場合は除外
        if node.surface:
            buf += node.surface + '\t' + node.feature + '\n'
        node = node.next

    return buf

def term_ext(tagged_text):
    # 用語の抽出

    # Frequency(単語の出現頻度)
    frequency = termextract.mecab.cmp_noun_dict(tagged_text)

    # FrequencyからLR(単語の左右の連接情報)を生成
    lr = termextract.core.score_lr(
        frequency,
        ignore_words=termextract.mecab.IGNORE_WORDS,
        lr_mode=1, average_rate=1)

    # FrequencyとLRからFLRの重要度を求める
    term_imp = termextract.core.term_importance(frequency, lr)

    return Counter(term_imp)

def remove_single_words(terms):
    #単語を省く

    c = Counter()
    for cmp, value in terms.items():
        if len(cmp.split(' ')) != 1:
            c[termextract.core.modify_agglutinative_lang(cmp)] = value

    return c

def main():
    with open('bochan.txt') as f:
        text = f.read()

    tagged_text = get_mecab_tagged(text)

    terms = term_ext(tagged_text)

    compound = remove_single_words(terms)

    # 重要度が高い複合語トップ10を出力
    for cmp, value in compound.most_common(10):
        print(cmp, value, sep='\t')

if __name__ == '__main__':
    main()    

以下のように複合語とその重要度の一覧が得られる。



mecab-ipadic-NEologdとの比較


termextractで抽出した複合語を、WEBサイトをクロールして定期的に新語を追加しているmecab-ipadic-NEologd(2019年2月17日作成)で形態素解析した結果と比べてみる。mecab-ipadic-NEologdのインストールはmecab-ipadic-NEologdで形態素解析を新語に対応させるを参照。

比較するコードは、先述のコードに以下を追記する。
m_neologd = MeCab.Tagger('-Ochasen -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
m_neologd.parse('')

def get_freq():
    # 単語の出現頻度を取得

    with open('bochan.txt') as f:
        text = f.read()

    base_list = []

    node = m_neologd.parseToNode(text)
    while node:
        # 表層形が空白(BOS/EOS)の場合は除外
        if not node.surface:
            node = node.next
            continue

        feature_split = node.feature.split(',')
        base_form = feature_split[6]

        # 原型をリストに追加
        base_list.append(base_form)

        node = node.next

    return Counter(base_list)

def main()
    # main関数に以下を追記
    term_count = get_freq()

    for key, value in compound.most_common(10):
        print(key, term_count[key], value)

コードを実行すると、termextractで抽出した複合語、mecab-ipadic-NEologdを使ったときの出現頻度、termextractで求めた重要度が出力される。「控所」「文学士」「赤手拭」の出現頻度が0で、mecab-ipadic-NEologdで抽出できておらずtermextractで抽出できていることがわかる。



0 件のコメント:

コメントを投稿