2022年3月9日水曜日

PythonとMeCabでUniDicを使う

PythonとMeCabで形態素解するときに利用できる辞書としては、IPA辞書やネットの資源を利用してIPA辞書をカスタマイズしたmecab-ipadic-NEologdがある。

Raspberry PiのPython3でMecabを使う

mecab-ipadic-NEologdで形態素解析を新語に対応させる

IPA辞書はしばらく更新されていないが、mecab-ipadic-NEologdは定期的に更新されている。ただmecab-ipadic-NEologdは場合によっては副作用がある。

mecab-ipadic-NEologdの辞書を修正する

そこで今回は、今も更新がされているUniDicを使って形態素解析をしてみる。


環境

WSL2(Ubuntu20.04)。

$ lsb_release -dr
Description:    Ubuntu 20.04.3 LTS
Release:        20.04
$ python3 -V
Python 3.8.10


必要なPythonモジュールとUniDicのインストール

まずはPythonでMeCabとUniDicを使用するため、MeCabのPythonラッパーであるmecab-python3とunidic-pyをインストールする。また、あとでUniDicとIPA辞書の比較をするので、IPA辞書もインストールする。

$ pip3 install mecab-python3 unidic ipadic
$ pip3 show mecab-python3
Name: mecab-python3
Version: 1.0.5
...
$ pip3 show unidic
Name: unidic
Version: 1.1.0
...
$ pip3 show ipadic
Name: ipadic
Version: 1.0.0
...

インストールできたらUniDicをダウンロードしておく。

$ python3 -m unidic download


MeCabとUniDicで形態素解析をする

環境が整ったので、UniDicで形態素解析をしてみる。

import MeCab
import unidic

tagger = MeCab.Tagger('-d ' + unidic.DICDIR)
text = '青いカザミドリがクルクル回る'
res = tagger.parse(text)
print(res)

上記コードを実行すると以下の結果が得られる。結果は分割された語ごとに、pos1からlemma_idまでがカンマ区切りになっている。結果の各項目についてはunidic-pyに説明がある。

また、「カザミドリ」は辞書に登録されていないのですべての項目はない。このフォーマットはunidic-pyがインストールされたディレクトリ配下のpython3.8/site-packages/unidic/dicdir/dicrcなどに記述されている。

青い    形容詞,一般,,,形容詞,連体形-一般,アオイ,青い,青い,アオイ,青い,アオイ,和,"","","","","","",相,アオイ,アオイ,アオイ,アオイ,"2","C1","",73950814151361,269

カザミドリ      名詞,普通名詞,一般,,,

が      助詞,格助詞,,,,,ガ,が,が,ガ,が,ガ,和,"","","","","","",格助,ガ,ガ,ガ,ガ,"","動詞%F2@0,名詞%F1","",2168520431510016,7889

クルクル        副詞,,,,,,クルクル,くるくる,クルクル,クルクル,クルクル,クルクル,和,"","","","","","",相,クルクル,クルクル,クルクル,クルクル,"1","","",2892273994048000,10522

回る    動詞,非自立可能,,,五段-ラ行,連体形-一般,マワル,回る,回る,マワル,回る,マワル,和,"","","","","","",用,マワル,マワル,マワル,マワル,"0","C2","",9928873533907649,36121

EOS

次に、書字形基本形だけを取り出してみる。

import MeCab
import unidic

tagger = MeCab.Tagger('-d ' + unidic.DICDIR)
text = '青いカザミドリがクルクル回る'
node = tagger.parseToNode(text)
tokens = []
while node:
    features = node.feature.split(',')
    if features[0] == 'BOS/EOS':
        # BOS/EOSをスキップ
        node = node.next
        continue
    if len(features) >= 11:
        # 書字形基本形
        base_form = features[10]
    else:
        # 書字形基本形がない場合は表層形を使う
        base_form = node.surface

    tokens.append(base_form)
    node = node.next

print(tokens)

結果は以下の通り。

['青い', 'カザミドリ', 'が', 'クルクル', '回る']

さらにUniDicを使うと語彙素も取得できる。語彙素とは異なる形態であるが同じ語であると考えられるものからなる語の集合。「おおきな蒼いふるどけい」を形態素解析すると以下の結果が得られる。品詞からはじまる項目の8番目が語彙素で、語彙素だけを取り出すと['大きな', '青い', 'フル', '時計']となる。「おおきな」が「大きな」、「蒼い」が「青い」などとなる。表記ゆれ対策に使えるかもしれない。

おおきな        連体詞,,,,,,オオキナ,大きな,おおきな,オーキナ,おおきな,オーキナ,和,"","","","","","",相,オオキナ,オオキナ,オオキナ,オオキナ,"1","","",1254551491584512,4564

蒼い    形容詞,一般,,,形容詞,連体形-一般,アオイ,青い,蒼い,アオイ,蒼い,アオイ,和,"","","","","","",相,アオイ,アオイ,アオイ,アオイ,"2","C1","",73950881260225,269

ふる    名詞,固有名詞,地名,一般,,,フル,フル,ふる,フル,ふる,フル,固,"","","","","","",地名,フル,フル,フル,フル,"1","","",56409902964417024,205218

どけい  名詞,普通名詞,一般,,,,トケイ,時計,どけい,ドケー,どけい,ドケー,漢,"ト濁","濁音形","","","","",体,ドケイ,ドケイ,ドケイ,トケイ,"0","C2","",7244141050348032,26354


IPA辞書との比較

UniDicとIPA辞書でどの程度違いがあるのか比較してみる。形態素解析するテキストは、Pythonで青空文庫の作品テキストからルビなどを取り除くの方法でルビなどを除去した青空文庫の『坊ちゃん』。botxhan.txtとして保存しておく。このテキストを形態素解析して、IPA辞書の原型とUnidicの書字形基本形の数をカウントする。以下は名詞のみをカウントして出現数上位20を比較するコード。

from collections import Counter

import MeCab
import unidic
import ipadic

def count(text, dic_arg, base_form_idx):
    tagger = MeCab.Tagger(dic_arg)
    node = tagger.parseToNode(text)
    tokens = []
    while node:
        feature_split = node.feature.split(',')
        pos1 = feature_split[0]

        if pos1 != '名詞':
            node = node.next
            continue

        if len(feature_split) >= base_form_idx+1:
            # 書字形基本形/原形
            base_form = feature_split[base_form_idx]
        else:
            # 書字形基本形/原形がない場合は表層形を使う
            base_form = node.surface

        tokens.append(base_form)
        node = node.next

    return Counter(tokens)

def main():
    # 『坊ちゃん』のテキスト読み込み
    with open('./botchan.txt', encoding='utf8') as f:
        text = f.read()

    unidic_count = count(text, '-d ' + unidic.DICDIR, base_form_idx=10)
    ipadic_count = count(text, ipadic.MECAB_ARGS, base_form_idx=6)

    for rank, (ucnt, icnt) in enumerate(zip(unidic_count.most_common(20), ipadic_count.most_common(20))):
        print(rank+1, ucnt, icnt)

if __name__ == '__main__':
    main()

結果は以下の通り。左側がUnidic、右側がIPA辞書を使った結果。

1 ('事', 292) ('おれ', 472)
2 ('もの', 220) ('の', 373)
3 ('赤', 175) ('事', 291)
4 ('シャツ', 170) ('*', 236)
5 ('山嵐', 154) ('もの', 218)
6 ('一', 153) ('ん', 216)
7 ('時', 115) ('人', 213)
8 ('方', 108) ('君', 184)
9 ('学校', 107) ('赤', 178)
10 ('人', 93) ('一', 173)
11 ('清', 89) ('よう', 173)
12 ('野', 88) ('シャツ', 170)
13 ('顔', 80) ('山嵐', 155)
14 ('気', 79) ('何', 146)
15 ('生徒', 77) ('二', 120)
16 ('今', 73) ('方', 115)
17 ('奴', 72) ('時', 108)
18 ('ところ', 72) ('それ', 100)
19 ('二', 71) ('これ', 99)
20 ('うち', 70) ('三', 92)

形容詞のみにした場合も比較してみる。

1 ('ない', 418) ('ない', 263)
2 ('いい', 121) ('いい', 114)
3 ('よい', 44) ('よい', 42)
4 ('わるい', 36) ('わるい', 36)
5 ('早い', 26) ('早い', 25)
6 ('悪るい', 23) ('長い', 17)
7 ('面白い', 17) ('面白い', 16)
8 ('長い', 17) ('強い', 15)
9 ('強い', 16) ('大きい', 11)
10 ('えらい', 11) ('小さい', 10)
11 ('小さい', 10) ('大人しい', 10)
12 ('大人しい', 10) ('えらい', 10)
13 ('旨い', 10) ('旨い', 10)
14 ('狭い', 10) ('狭い', 10)
15 ('大きい', 10) ('痛い', 9)
16 ('痛い', 9) ('蒼い', 9)
17 ('蒼い', 9) ('まずい', 9)
18 ('難有し', 9) ('嬉しい', 8)
19 ('嬉しい', 8) ('っぽい', 8)
20 ('むずかしい', 8) ('暗い', 8) 

この結果をみただけではUniDicとIPA辞書のどちらがいいのかの判断はできないが、それなりに違いはありそう。


0 件のコメント:

コメントを投稿