Janomeとは
Pure Python(通常のpythonのみ)
で書かれている日本語の形態素解析のためのパッケージです。自然言語処理で主に行われる「形態素解析」を気軽に行うことが出来ます。そもそも「形態素解析って何?」という方のために形態素解析結果をお見せしますと以下のようになります。
(B'z 今宵月の見える丘により)
手をつないだら行ってみよう
形態素解析の結果
手 名詞,一般,*,*,*,*,手,テ,テ を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ つない 動詞,自立,*,*,五段・ガ行,連用タ接続,つなぐ,ツナイ,ツナイ だら 助動詞,*,*,*,特殊・タ,仮定形,だ,ダラ,ダラ 行っ 動詞,自立,*,*,五段・カ行促音便,連用タ接続,行く,イッ,イッ て 助詞,接続助詞,*,*,*,*,て,テ,テ みよ 動詞,非自立,*,*,一段,未然ウ接続,みる,ミヨ,ミヨ う 助動詞,*,*,*,不変化型,基本形,う,ウ,ウ
Janome
ではこのような結果が返ってきます。形態素解析の対象とした文章(今回は「手をつないだら行ってみよう 」)を品詞ごとに区切り、それぞれの品詞の情報を教えてくれると言えば良いでしょうか。自分は古典の活用形を覚えるのが苦手だったので、形態素解析結果のそれぞれの品詞がどのような特性を持つのかは全く分かりませんが、重要なのは「形態素解析パッケージによって文章を品詞に分解出来る」という点です。
僕自身は元々、Mecab
という形態素解析エンジンものを使っていましたが、必要な外部パッケージのインストール、ユーザー辞書の準備がそこそこ面倒で毎度、環境構築するたびに結構苦しめられました。
そしたら何とpip
のみでササッとインストールできるJanome
を教えてもらいまして、開発環境の準備がめちゃ楽になりました。
とはいいつつも、Janome
の内部ではMecab
のipadic
を使っているようなので、僕の苦労をJanome
が負担してくれていることですね。
圧倒的感謝...
Janomeをインストールする
※Python
はインストール済みであり、ver3.4以上を想定しています
(すでにJanome
がインストール済みの方はこの章をすっとばして下さい)
仮想環境を作成するためのpython
の標準パッケージである、venv
を使用してプロジェクト仮想環境を作りJanome
をインストールする場合は以下を参照下さい。ローカル環境にグローバルにJanome
をインストールはこの項目を飛ばしてください。特に理由がなければローカル環境を可能な限りクリーンに保つために仮想環境を作成したインストールをオススメします。
venvを使って仮想環境を作成してJanomeをインストール
まずは適当な名前でディレクトリを作成します。
$ mkdir janome_sample
$ ls
janome_sample
作成したディレクトリの内部に移動します。
$ cd janome_sample
venv
を使って仮想環境を作成しましょう。.venv
の部分は任意の名前で問題ありません。
$ python3 -m venv .venv
無事に仮想環境が作成されたかを確認します。
$ ls -a | grep ".venv"
.venv
仮想環境を立ち上げて、pip
を使用して、Janome
をインストールします。
$ source .venv/bin/activate (.venv) $ pip install janome
Collecting janome Cache entry deserialization failed, entry ignored Downloading https://files.pythonhosted.org/packages/79/f0/bd7f90806132d7d9d642d418bdc3e870cfdff5947254ea3cab27480983a7/Janome-0.3.10-py2.py3-none-any.whl (21.5MB) 100% |████████████████████████████████| 21.5MB 53kB/s Installing collected packages: janome Successfully installed janome-0.3.10
無事にインストールされたかを確認します。
$ pip freeze
Janome==0.3.10
venv
で作成した仮想環境にpip
経由でJanome
がインストールされていることを確認出来ました。
ローカル環境にグローバルにインストール
$ pip install janome
or
$ pip3 install janome
たったこれだけ、もう終わり。Mecab
が使えるようになるまでのステップと比べると圧倒的に楽です。
qiita.com
形態素解析をするまで
全体のコード(jupyter notebook
)をgit
に上げていますのでご参照下さい。
github.com
最も基本的な部分のみを試してみます。
# janomeのTokenizerをインポート from janome.tokenizer import Tokenizer t = Tokenizer() res = t.tokenize("手をつないだら行ってみよう") print(res) #result: この時点で結果が思った様に表示されない # [<janome.tokenizer.Token at 0x112151710>, # : # : # <janome.tokenizer.Token at 0x112151908>]
for
を使えば先ほどの様な解析結果を覗くことが出来ます。
for r in res: print(r) #result: # 手 名詞,一般,*,*,*,*,手,テ,テ # を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ # つない 動詞,自立,*,*,五段・ガ行,連用タ接続,つなぐ,ツナイ,ツナイ # だら 助動詞,*,*,*,特殊・タ,仮定形,だ,ダラ,ダラ # 行っ 動詞,自立,*,*,五段・カ行促音便,連用タ接続,行く,イッ,イッ # て 助詞,接続助詞,*,*,*,*,て,テ,テ # みよ 動詞,非自立,*,*,一段,未然ウ接続,みる,ミヨ,ミヨ # う 助動詞,*,*,*,不変化型,基本形,う,ウ,ウ
特定の品詞に該当する単語だけを抽出する
解析結果の配列(先ほどの例でいうres
)に対して.part_of_speech
メゾットを呼び出すことで、それぞれの単語の品詞を確認する事が出来ます。
res[0].part_of_speech # '名詞,一般,*,*'
この.part_of_speech
の戻り値('名詞,一般,*,*'
という情報)はただの文字列なので,
でsplitして、内包表記で上手く処理してあげることで各単語の品詞情報だけを取得する処理を記述する事が出来ます。
[f.part_of_speech.split(",")[0] for f in res] # ['名詞', '助詞', '動詞', '助動詞', '動詞', '助詞', '動詞', '助動詞']
自然言語処理の前処理で頻出するのが名詞
だけ取得したい、名詞
と形容詞
だけ取得したいという作業です。特定の品詞だけを取り出しつつ .surface
メゾットを使って解析結果から該当する品詞の単語の一覧を取得します。
#1つの品詞だけ [token.surface for token in res if token.part_of_speech.split(",")[0] == u"名詞"] # ['手'] #複数の品詞 target = [u"名詞", u"動詞"] [token.surface for token in res if token.part_of_speech.split(",")[0] in target] # ['手', 'つない', '行っ', 'みよ']
可能か限り、内包表記を使用して記述しているのは少しでも速度を落とさないようにするためです。この手の自然言語処理での前処理は何万というデータに対して行うことが多いため、極力速度が落ちるような記述(通常のfor
文など)は避けています。
良い感じですね。使い勝手がいいように関数化しておきましょう。
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: ['手']
この関数はこのままでは、呼び出されるたびにTokenizer
のインスタンスを作ってしまい、パフォーマンスが悪そうなので一度だけ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: ['手']
これでクラス化も完了しました。
ユーザー辞書の作成と読み込み
Mecab
と.csv
形式で作成したファイルを.dic
形式にcompileする必要がないため、pandas
を使って簡単にユーザー辞書を作成することが出来ます。以下のコードは単語のリスト([]string)からJanome
指定の.csv
形式のファイルを作成する関数になります。
コピペで使えます~
# pandasがインストールされていない場合はインストールしてください # > pip install pandas import pandas pd def create_user_dic(words, dic_name): """ TASK: 指定された単語群からJanome指定のユーザー辞書.csvファイルを作成する words: []string -> 辞書に登録したい単語群 dic_name: string -> __.csvに該当するファイル名 return void """ df = words_to_df(words) df = to_janome_csv_style(df) save_df_to_csv(df, dic_name) def words_to_df(words): """ TASK: 単語リストをpandas.DataFrame形式に変換する wrods: []string -> 対象の単語群 return pandas.DataFrame """ to_df_list = [[w] for w in words] return pd.DataFrame(words, columns=["単語"]) def to_janome_csv_style(df): """ TASK: 読み込まれたdfをjanomeが指定するユーザー辞書.csv形式に変換する df: pandas.DataFrame -> 単語リストから生成されたdf return pandas.DataFrame """ return df.assign( a=df.pipe(lambda df: -1), b=df.pipe(lambda df: -1), c=df.pipe(lambda df: 1000), d=df.pipe(lambda df: "名詞"), e=df.pipe(lambda df: "一般"), f=df.pipe(lambda df: "*"), g=df.pipe(lambda df: "*"), h=df.pipe(lambda df: "*"), i=df.pipe(lambda df: "*"), j=df.pipe(lambda df: df["単語"]), k=df.pipe(lambda df: "*"), l=df.pipe(lambda df: "*"), ) def save_df_to_csv(df, file_name): """ TASK: dfを.csv形式で保存する df: pandas.DataFrame -> to_janome_csv_style()の戻り値のdf file_name: ファイルの保存名(拡張子は含まない) return void """ df.to_csv(f"{file_name}.csv", header=False, index=False, encoding="cp932")
使い方は簡単でcreate_user_dic
に辞書として登録したいワードとファイル保存名(.csvを除いた)を渡すだけになります。
words = ["果てない思い", "ウルトラソウル"] create_user_dic(words, "B'z_dic")
関数を実行したカレントディレクトリに.csv
形式のファイルが生成されているかと思います。
$ ls
"B'z_dic.csv"
これでユーザー辞書の作成は完了です。
ユーザー辞書の読み込み
ユーザー辞書が読み込まれていない状態だと以下のような結果になります。
# 名詞だけを取得 parser("果てない思い") # ['思い'] parser("ウルトラソウル") # ['ウルトラ', 'ソウル']
せっかくユーザー辞書を作成したので読み込むようにしましょう。めちゃくちゃ簡単です。Tokenizer
インスタンスの生成時に、ユーザー辞書.csv
のパスを引数に指定するだけで読み込まれます。
# 作成したユーザー辞書へのパス USER_DIC_PATH = "B'z_dic.csv" # 簡略化のため定数値からユーザー辞書を指定 def parser(value_str, tag=u"名詞"): t = Tokenizer(USER_DIC_PATH, udic_enc="cp932") 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("果てない思い") # ['果てない思い']
parser("ウルトラソウル") # ['ウルトラソウル']
ユーザー辞書を読み込むように更新して、単語が分割されないようになりました。無事にユーザー辞書の読み込みに成功しているということになります。コード量が多くなってしまうため、ブログの方には貼りませんが、jupyter notebook
内にてインスタンス生成時に、ユーザー辞書のパスを指定するものも作成しましたので、よければ覗いて見てください。
まとめ
Janome
はいかがでしたか。インストールも簡単で使い方もシンプル。特に使用するまでに外部のパッケージをあれこれいれる必要がなく、「形態素解析」の実行にまで非常にスムーズに辿り着くことが出来ます。速度に関してはMecab
には劣ってしまうのですが、形態素解析を少し試した時にはJanome
が非常に良いのではないでしょうか。
こちらの記事では紹介していないJanome
の様々な便利機能がまだまだあり、こちらで非常に丁寧に紹介されていますので、ぜひご覧ください。私も非常に勉強させて頂きました。
おまけ: 関数とクラス内関数で実行速度を比較
1万回の実行結果の平均値を比べてみました。コードはjupyter notebook
の一番下のところにあります。割と量が多くなってしまったのでリンクを再度、貼っておきます。
実行形式 | 試行回数 | 平均値 |
---|---|---|
関数 | 10,000 | 0.08182s |
クラス内関数 | 10,000 | 0.08891s |
速度に関しては若干、関数形式の方が勝る結果もありましたが、ほとんど誤差といっても良いかと思います。実際は速度以外にもメモリ使用率の問題などもあるので、一概にこの結果だけからどちらが良いかとは言えませんが、速度上では関数形式の方が速いということが分かりました。