自動タグ付け機能でも作ってみる - 4: MeCab + Python を試してみる

前提

まずは、形態素解析を使って文章→単語(形態素)の分割部分を作る。

先の構成図で言うところの、 MorphoAnalyzer である。

インストール

MeCabのインストールは、MacならHomebrewに頼ってしまいたい。

brew install mecab

試しに使ってみる

まずはものは試し、お定まり(?)の 走れメロス をぶち込んでみる。

$ echo "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。" | mecab
メロス  名詞,一般,*,*,*,*,*
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
激怒    名詞,サ変接続,*,*,*,*,激怒,ゲキド,ゲキド
し      動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。      記号,句点,*,*,*,*,。,。,。
必ず    副詞,助詞類接続,*,*,*,*,必ず,カナラズ,カナラズ
、      記号,読点,*,*,*,*,、,、,、
かの    連体詞,*,*,*,*,*,かの,カノ,カノ
邪智    名詞,一般,*,*,*,*,邪智,ジャチ,ジャチ
暴虐    名詞,一般,*,*,*,*,暴虐,ボウギャク,ボーギャク
の      助詞,連体化,*,*,*,*,の,ノ,ノ
王      名詞,一般,*,*,*,*,王,オウ,オー
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
除か    動詞,自立,*,*,五段・カ行イ音便,未然形,除く,ノゾカ,ノゾカ
なけれ  助動詞,*,*,*,特殊・ナイ,仮定形,ない,ナケレ,ナケレ
ば      助詞,接続助詞,*,*,*,*,ば,バ,バ
なら    動詞,非自立,*,*,五段・ラ行,未然形,なる,ナラ,ナラ
ぬ      助動詞,*,*,*,特殊・ヌ,基本形,ぬ,ヌ,ヌ
と      助詞,格助詞,引用,*,*,*,と,ト,ト
決意    名詞,サ変接続,*,*,*,*,決意,ケツイ,ケツイ
し      動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。      記号,句点,*,*,*,*,。,。,。
EOS

ふむ。

Pythonから使ってみる

Pythonで呼び出すとどうかな。

import MeCab


def mecab_tryout() -> None:
    text: str = "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。"
    tagger = MeCab.Tagger()
    parsed = tagger.parse(text)
    print(parsed)

if __name__ == "__main__":
    mecab_tryout()
$ python mecab_tryout.py
メロス  名詞,一般,*,*,*,*,*
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
激怒    名詞,サ変接続,*,*,*,*,激怒,ゲキド,ゲキド
し      動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。      記号,句点,*,*,*,*,。,。,。
必ず    副詞,助詞類接続,*,*,*,*,必ず,カナラズ,カナラズ
、      記号,読点,*,*,*,*,、,、,、
かの    連体詞,*,*,*,*,*,かの,カノ,カノ
邪智    名詞,一般,*,*,*,*,邪智,ジャチ,ジャチ
暴虐    名詞,一般,*,*,*,*,暴虐,ボウギャク,ボーギャク
の      助詞,連体化,*,*,*,*,の,ノ,ノ
王      名詞,一般,*,*,*,*,王,オウ,オー
を      助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
除か    動詞,自立,*,*,五段・カ行イ音便,未然形,除く,ノゾカ,ノゾカ
なけれ  助動詞,*,*,*,特殊・ナイ,仮定形,ない,ナケレ,ナケレ
ば      助詞,接続助詞,*,*,*,*,ば,バ,バ
なら    動詞,非自立,*,*,五段・ラ行,未然形,なる,ナラ,ナラ
ぬ      助動詞,*,*,*,特殊・ヌ,基本形,ぬ,ヌ,ヌ
と      助詞,格助詞,引用,*,*,*,と,ト,ト
決意    名詞,サ変接続,*,*,*,*,決意,ケツイ,ケツイ
し      動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
た      助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
。      記号,句点,*,*,*,*,。,。,。
EOS

結果は同じ…というかこれ、文字列で結果返ってくるのか。使いにくいな!

公式の とりあえず解析してみる を参考にすると、 各列は左から、

表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音

となっているらしい。

出力をプログラムチックに扱いやすくする

活用とか読みとか発音は要らないので、 出力フォーマット をいじってみる。 ついでに、品詞も「名詞」とか「形容詞」といった文字列で扱うのはあまりにもナンセンスなので、 品詞ID で出力したほうが扱いやすい。

出力フォーマットなどの指定は、 Tagger の引数にCLIに指定するのと同じ感じで渡せばいいらしい。 これCLI呼んでるだけだろ…

import MeCab
from typing import List


def mecab_tryout() -> None:
    text: str = "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。"

    node_format: str = '%m\\t%h\\n'
    args: List[str] = [
        '--node-format ' + node_format,
        '--unk-format ' + node_format,
        '--eos-format EOS\\t\\n',
    ]
    arg: str = ' '.join(args)
    tagger = MeCab.Tagger(arg)
    parsed = tagger.parse(text)
    print(parsed)


if __name__ == "__main__":
    mecab_tryout()

結果はご覧の通り。うん、扱いやすい

$ python mecab_tryout.py
メロス  3816
激怒    3631257
必ず    359
かの    68
邪智    38
暴虐    38243813
除か    31
なけれ  2518
なら    332514
決意    3631257
EOS

EOS なんて要らないから消したい!と思ったのだけれど、どうやっても消せず。 あとで除去することにしようと思う。

出力やデバッグのことも考える

品詞ID→品詞名の一覧も作っておく。先の公式の「品詞ID」からコピペしただけだけど。

class Feature(object):
    def __init__(self, clazz: str, subclazz: List[str] = []) -> None:
        self.clazz = clazz
        self.subclazz = subclazz


_features: Dict[int, Feature] = {
    0: Feature("その他", ["間投"]),
    1: Feature("フィラー"),
    2: Feature("感動詞"),
    3: Feature("記号", ["アルファベット"]),
    4: Feature("記号", ["一般"]),
    5: Feature("記号", ["括弧開"]),
    6: Feature("記号", ["括弧閉"]),
    7: Feature("記号", ["句点"]),
    8: Feature("記号", ["空白"]),
    9: Feature("記号", ["読点"]),
    10: Feature("形容詞", ["自立"]),
    11: Feature("形容詞", ["接尾"]),
    12: Feature("形容詞", ["非自立"]),
    13: Feature("助詞", ["格助詞", "一般"]),
    14: Feature("助詞", ["格助詞", "引用"]),
    15: Feature("助詞", ["格助詞", "連語"]),
    16: Feature("助詞", ["係助詞"]),
    17: Feature("助詞", ["終助詞"]),
    18: Feature("助詞", ["接続助詞"]),
    19: Feature("助詞", ["特殊"]),
    20: Feature("助詞", ["副詞化"]),
    21: Feature("助詞", ["副助詞"]),
    22: Feature("助詞", ["副助詞/並立助詞/終助詞"]),
    23: Feature("助詞", ["並立助詞"]),
    24: Feature("助詞", ["連体化"]),
    25: Feature("助動詞"),
    26: Feature("接続詞"),
    27: Feature("接頭詞", ["形容詞接続"]),
    28: Feature("接頭詞", ["数接続"]),
    29: Feature("接頭詞", ["動詞接続"]),
    30: Feature("接頭詞", ["名詞接続"]),
    31: Feature("動詞", ["自立"]),
    32: Feature("動詞", ["接尾"]),
    33: Feature("動詞", ["非自立"]),
    34: Feature("副詞", ["一般"]),
    35: Feature("副詞", ["助詞類接続"]),
    36: Feature("名詞", ["サ変接続"]),
    37: Feature("名詞", ["ナイ形容詞語幹"]),
    38: Feature("名詞", ["一般"]),
    39: Feature("名詞", ["引用文字列"]),
    40: Feature("名詞", ["形容動詞語幹"]),
    41: Feature("名詞", ["固有名詞", "一般"]),
    42: Feature("名詞", ["固有名詞", "人名", "一般"]),
    43: Feature("名詞", ["固有名詞", "人名", "姓"]),
    44: Feature("名詞", ["固有名詞", "人名", "名"]),
    45: Feature("名詞", ["固有名詞", "組織"]),
    46: Feature("名詞", ["固有名詞", "地域", "一般"]),
    47: Feature("名詞", ["固有名詞", "地域", "国"]),
    48: Feature("名詞", ["数"]),
    49: Feature("名詞", ["接続詞的"]),
    50: Feature("名詞", ["接尾", "サ変接続"]),
    51: Feature("名詞", ["接尾", "一般"]),
    52: Feature("名詞", ["接尾", "形容動詞語幹"]),
    53: Feature("名詞", ["接尾", "助数詞"]),
    54: Feature("名詞", ["接尾", "助動詞語幹"]),
    55: Feature("名詞", ["接尾", "人名"]),
    56: Feature("名詞", ["接尾", "地域"]),
    57: Feature("名詞", ["接尾", "特殊"]),
    58: Feature("名詞", ["接尾", "副詞可能"]),
    59: Feature("名詞", ["代名詞", "一般"]),
    60: Feature("名詞", ["代名詞", "縮約"]),
    61: Feature("名詞", ["動詞非自立的"]),
    62: Feature("名詞", ["特殊", "助動詞語幹"]),
    63: Feature("名詞", ["非自立", "一般"]),
    64: Feature("名詞", ["非自立", "形容動詞語幹"]),
    65: Feature("名詞", ["非自立", "助動詞語幹"]),
    66: Feature("名詞", ["非自立", "副詞可能"]),
    67: Feature("名詞", ["副詞可能"]),
    68: Feature("連体詞"),
}