田舎で並行処理の夢を見る

試していることなど...需要がないかもしれないけど細々とアウトプットしてます

形態素解析をササッと試すならMecabよりもjanomeが良い感じ

janomeとは

janomeの公式ページ(可愛い絵がありますねぇ!)
pythonで書かれている日本語の形態素解析ツールです
自然言語処理などでよく形態素解析ってのをやります
どういうものかというと

手をつないだら行ってみよう

こいつをjanome形態素解析すると

手 名詞,一般,,,,,手,テ,テ
を 助詞,格助詞,一般,,,,を,ヲ,ヲ
つない 動詞,自立,
,,五段・ガ行,連用タ接続,つなぐ,ツナイ,ツナイ
だら 助動詞,
,,,特殊・タ,仮定形,だ,ダラ,ダラ
行っ 動詞,自立,,,五段・カ行促音便,連用タ接続,行く,イッ,イッ
て 助詞,接続助詞,,,,,て,テ,テ
みよ 動詞,非自立,,,一段,未然ウ接続,みる,ミヨ,ミヨ
う 助動詞,,,*,不変化型,基本形,う,ウ,ウ

こんなものが返ってきます
つまりは指定の文章がどんな日本語情報で構成されているかということを
解析してくれているわけです。語弊ありましたらすみません。日本語が下手なので

元々、僕自身はMecabというものを使ってたんですけど
インストールがだるかったり、辞書の準備がだるかったりで毎度結構苦しめられました
そしたら何とpipでちょちょいとインストールできるjanomeたるものを教えてもらいまして
開発環境の準備がめちゃ楽になったでという話です

とはいいつつも、janomeMecabのipadicを使っているようなので
僕の苦労を負担してくれていることですね。圧倒的感謝...

janomeをインストールする

> pip install janome
> pip3 install janome

たったこれだけ、もう終わり

いざ、試してみる

最も基本的な部分のみを扱います

#janomeのTokenizerをインポート
from janome.tokenizer import Tokenizer

t = Tokenizer()
res = t.tokenize("手をつないだら行ってみよう")
print(res)

#result: この時点で結果が見れない
# [<janome.tokenizer.Token at 0x112151710>,
#  <janome.tokenizer.Token at 0x112151748>,
#  <janome.tokenizer.Token at 0x1121517f0>,
#  <janome.tokenizer.Token at 0x112151828>,
#  <janome.tokenizer.Token at 0x112151860>,
#  <janome.tokenizer.Token at 0x112151898>,
#  <janome.tokenizer.Token at 0x1121518d0>,
#  <janome.tokenizer.Token at 0x112151908>]

forを使えば一応、解析結果を覗くことができる

for r in res:
    print(r)

#result:
# 手  名詞,一般,*,*,*,*,手,テ,テ
# を  助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
# つない    動詞,自立,*,*,五段・ガ行,連用タ接続,つなぐ,ツナイ,ツナイ
# だら   助動詞,*,*,*,特殊・タ,仮定形,だ,ダラ,ダラ
# 行っ   動詞,自立,*,*,五段・カ行促音便,連用タ接続,行く,イッ,イッ
# て  助詞,接続助詞,*,*,*,*,て,テ,テ
# みよ   動詞,非自立,*,*,一段,未然ウ接続,みる,ミヨ,ミヨ
# う  助動詞,*,*,*,不変化型,基本形,う,ウ,ウ

が、ダサいのでやりたくない
解析結果の配列に対して.part_of_speechメゾットを呼び出してあれこれすることで
各単語の品詞が何かを取得することが可能

res = [f.part_of_speech.split(",")[0] for f in res]
print(res)

#result:
#['名詞', '助詞', '動詞', '助動詞', '動詞', '助詞', '動詞', '助動詞']

よくやるのが名詞だけほしいとか、名詞と形容詞がほしいという作業で
指定の品詞だけを取り出しつつ
.surfaceメゾットを使って解析結果から単語を取得してみる

#1つの品詞だけ
res = [token.surface for token in res if token.part_of_speech.split(",")[0] == u"名詞"]
print(res) #result: ['手']

#複数の品詞
target = [u"名詞", u"動詞"]
res = [token.surface for token in res if token.part_of_speech.split(",")[0] in target]
print(res) #result: ['手', 'つない', '行っ', 'みよ']

良い感じですね
使い勝手がいいように関数化しておきましょう

from janome.tokenizer import Tokenizer

def parser(value_str, tag=u"名詞"):
    t = Tokenizer()
    res = t.tokenize(value_str)
    if isinstance(tag, list):
        return [token.surface for token in res if token.part_of_speech.split(",")[0] in tag]
    else:
        return [token.surface for token in res if token.part_of_speech.split(",")[0] == tag]
    
parser("手をつないだら行ってみよう")
#result: ['手']

このメゾットをforとか内包表記の中から呼び出すと
毎回Tokenizerのインスタンスを作って作って...と
かなり効率が悪そうなのでついでにクラス化しておきます

class JanomeParser:
    def __init__(self):
        self.t = Tokenizer()
        
    def parser(self, value_str, tag=u"名詞"):
        res = self.t.tokenize(value_str)
        if isinstance(tag, list):
            return [token.surface for token in res if token.part_of_speech.split(",")[0] in tag]
        else:
            return [token.surface for token in res if token.part_of_speech.split(",")[0] == tag]
        
j = JanomeParser()
j.parser("手をつないだら行ってみよう")
#result: ['手']

一応、速度を比較

jupyter notebookの%timeを使って計測します
対象のデータはこちら

values = [
    "こんな晴れた日は",
    "二人で丘に登ろう",
    "港が見渡せる丘に",
    "どんな空が思い浮かぶ",
    "教えておくれ",
    "キスしたい気分さ",
    "何もない午後の入江を往く船をただ見つめていた",
    "どうすれば時が戻る",
    "眩しい太陽の下で",
    "どれだけ涙流れても",
    "静かに海は広がる"
]

関数

%time 
[ parser(v) for v in values]

"""
CPU times: user 3 µs, sys: 1 µs, total: 4 µs
Wall time: 10 µs
"""

クラス

%time
j = JanomeParser()
[ j.parser(v) for v in values]

"""
CPU times: user 3 µs, sys: 1 µs, total: 4 µs
Wall time: 9.78 µs
"""

若干クラスの方が早い。関数の方が早い時もあった
データを1万ぐらいにしたら明確に差が出てきてほしいと思った
このデータ数で比較すると正直あまり変わらなかったです
1万行のcsvを用意して改めて比較してみます

まとめ

janomeいいぜ
次はこいつをelixirから呼び出してやろうと思ってます
って話をパイセンにしたらMecabをelixirでラップしろって怒られました
確かに...その通りジャマイカ...

あ、でも次はやっぱりelixirから呼び出します