Janomeを使ってPythonで形態素解析

# TL;DR

* Pythonで形態素解析をしたい
* [mecab-python3](https://pypi.org/project/mecab-python3/)も良いが、[MeCab](https://taku910.github.io/mecab/)を入れたりするのは避けたい
    * 要は、`pip`で完結させたい
* [Janome](https://mocobeta.github.io/janome/)を使うのがいいのではないだろうか


# Janome

Pythonで実装された、形態素解析器だそうです。

[Janome](https://mocobeta.github.io/janome/)

[作者様](https://medium.com/@mocobeta/about-me-b28838ba631f)は、Apache Luceneのコミッターをされていらっしゃいますね。

APIリファレンスは、こちら。

[Janome API reference](https://mocobeta.github.io/janome/api/)

以下の特徴を持つようです。

* Python 2.7または3.3以上で動作
* `Tokenizer`を使った、形態素解析ライブラリ
* `janome`スクリプトを使用したコマンドラインでの実行が可能
* デフォルトの辞書は`mecab-ipadic-2.7.0-20070801`
* ユーザー定義辞書の利用
* `mmap`のサポート
* [Graphviz](https://graphviz.gitlab.io/)ファイルの作成
* `Analyzer`フレームワーク（experimental）

などなど。

[Q＆A](https://mocobeta.github.io/janome/#id20)にもありますが、速度自体はMeCabには敵わないようなので、用途に合わないようであればMeCabを使用する方がよいでしょう。


# 環境

今回の環境は、こちらです。

```shell
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.3 LTS
Release:	18.04
Codename:	bionic


$ python3 -V
Python 3.6.8
```

この環境に、Janomeをインストールして簡単に形態素解析して遊んでみます。


# Janomeのインストール

`pip`でインストールすれば完了です。

```shell
$ pip3 install janome
```

今回のJanomeのバージョンは、こちら。

```shell
$ pip3 freeze
Janome==0.3.9
```

# `Tokenizer`を使う

では、ドキュメントを見ながら、まずは`Tokenizer`を使ってみましょう。

[使い方](https://mocobeta.github.io/janome/#id8)

サンプルコード。

hello_tokenizer.py

```python
from janome.tokenizer import Tokenizer

tokenizer = Tokenizer()

sentenses = [
    "ギョエェェエーと叫ぶだけの人生だった。",
    "最終兵器は懐にしまいます。",
    "お前は助からない。",
    "ログを読め。",
    "さあご一緒に！「TRUE 1 が TRUE！ TRUE 0 が FALSE！」"
]

for sentence in sentenses:
    print("=============================================")
    print(sentence)
    
    for token in tokenizer.tokenize(sentence):
        print("    " + str(token))
```

`Tokenizer`のインスタンスを作成して

```python
tokenizer = Tokenizer()
```

形態素解析したい文字列を`tokenize`メソッドに渡せばOKです。

```python
    for token in tokenizer.tokenize(sentence):
```

実行結果。

```
$ python3 hello_tokenizer.py
=============================================
ギョエェェエーと叫ぶだけの人生だった。
    ギョエェェエー	名詞,一般,*,*,*,*,ギョエェェエー,*,*
    と	助詞,格助詞,一般,*,*,*,と,ト,ト
    叫ぶ	動詞,自立,*,*,五段・バ行,基本形,叫ぶ,サケブ,サケブ
    だけ	助詞,副助詞,*,*,*,*,だけ,ダケ,ダケ
    の	助詞,連体化,*,*,*,*,の,ノ,ノ
    人生	名詞,一般,*,*,*,*,人生,ジンセイ,ジンセイ
    だっ	助動詞,*,*,*,特殊・ダ,連用タ接続,だ,ダッ,ダッ
    た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
    。	記号,句点,*,*,*,*,。,。,。
=============================================
最終兵器は懐にしまいます。
    最終	名詞,一般,*,*,*,*,最終,サイシュウ,サイシュー
    兵器	名詞,一般,*,*,*,*,兵器,ヘイキ,ヘイキ
    は	助詞,係助詞,*,*,*,*,は,ハ,ワ
    懐	名詞,一般,*,*,*,*,懐,フトコロ,フトコロ
    に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
    しまい	動詞,自立,*,*,五段・ワ行促音便,連用形,しまう,シマイ,シマイ
    ます	助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
    。	記号,句点,*,*,*,*,。,。,。
=============================================
お前は助からない。
    お前	名詞,代名詞,一般,*,*,*,お前,オマエ,オマエ
    は	助詞,係助詞,*,*,*,*,は,ハ,ワ
    助から	動詞,自立,*,*,五段・ラ行,未然形,助かる,タスカラ,タスカラ
    ない	助動詞,*,*,*,特殊・ナイ,基本形,ない,ナイ,ナイ
    。	記号,句点,*,*,*,*,。,。,。
=============================================
ログを読め。
    ログ	名詞,サ変接続,*,*,*,*,ログ,ログ,ログ
    を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
    読め	動詞,自立,*,*,五段・マ行,命令ｅ,読む,ヨメ,ヨメ
    。	記号,句点,*,*,*,*,。,。,。
=============================================
さあご一緒に！「TRUE 1 が TRUE！ TRUE 0 が FALSE！」
    さあ	感動詞,*,*,*,*,*,さあ,サア,サー
    ご	接頭詞,名詞接続,*,*,*,*,ご,ゴ,ゴ
    一緒	名詞,サ変接続,*,*,*,*,一緒,イッショ,イッショ
    に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
    ！	記号,一般,*,*,*,*,！,！,！
    「	記号,括弧開,*,*,*,*,「,「,「
    TRUE	名詞,固有名詞,組織,*,*,*,TRUE,*,*
     	記号,空白,*,*,*,*, ,*,*
    1	名詞,数,*,*,*,*,1,*,*
     	記号,空白,*,*,*,*, ,*,*
    が	接続詞,*,*,*,*,*,が,ガ,ガ
     	記号,空白,*,*,*,*, ,*,*
    TRUE	名詞,固有名詞,組織,*,*,*,TRUE,*,*
    ！	記号,一般,*,*,*,*,！,！,！
     	記号,空白,*,*,*,*, ,*,*
    TRUE	名詞,固有名詞,組織,*,*,*,TRUE,*,*
     	記号,空白,*,*,*,*, ,*,*
    0	名詞,数,*,*,*,*,0,*,*
     	記号,空白,*,*,*,*, ,*,*
    が	接続詞,*,*,*,*,*,が,ガ,ガ
     	記号,空白,*,*,*,*, ,*,*
    FALSE	名詞,固有名詞,組織,*,*,*,FALSE,*,*
    ！	記号,一般,*,*,*,*,！,！,！
    」	記号,括弧閉,*,*,*,*,」,」,」
```

形態素解析の結果は、`Token`として取得できます。

https://mocobeta.github.io/janome/api/janome.html#janome.tokenizer.Token

`Token`自体を文字列として出力した時になにが出ているかは、こちらを参照するとよいでしょう。

https://github.com/mocobeta/janome/blob/0.3.9/janome/tokenizer.py#L131-L143

例えば、表層形（surface）と基本形（base_form）を出力してみます。

```python
for sentence in sentenses:
    print("=============================================")
    print(sentence)
    
    for token in tokenizer.tokenize(sentence):
        print("    " + token.surface + " | " + token.base_form)
```

こんな感じの結果に。

```shell
=============================================
ギョエェェエーと叫ぶだけの人生だった。
    ギョエェェエー | ギョエェェエー
    と | と
    叫ぶ | 叫ぶ
    だけ | だけ
    の | の
    人生 | 人生
    だっ | だ
    た | た
    。 | 。
=============================================
最終兵器は懐にしまいます。
    最終 | 最終
    兵器 | 兵器
    は | は
    懐 | 懐
    に | に
    しまい | しまう
    ます | ます
    。 | 。
=============================================
お前は助からない。
    お前 | お前
    は | は
    助から | 助かる
    ない | ない
    。 | 。
=============================================
ログを読め。
    ログ | ログ
    を | を
    読め | 読む
    。 | 。
=============================================
さあご一緒に！「TRUE 1 が TRUE！ TRUE 0 が FALSE！」
    さあ | さあ
    ご | ご
    一緒 | 一緒
    に | に
    ！ | ！
    「 | 「
    TRUE | TRUE
      |  
    1 | 1
      |  
    が | が
      |  
    TRUE | TRUE
    ！ | ！
      |  
    TRUE | TRUE
      |  
    0 | 0
      |  
    が | が
      |  
    FALSE | FALSE
    ！ | ！
    」 | 」
```

活用形で書かれていた場合に、基本形が確認できたりします。

```shell
=============================================
ログを読め。
    ログ | ログ
    を | を
    読め | 読む
    。 | 。
```

表層形のみがあればよい場合は、分かち書きモードを使用するとよいみたいですよ。

[分かち書きモード](https://mocobeta.github.io/janome/#id16)


# `janome`スクリプトを使う

コマンドラインからJanomeを使う場合は、`janome`スクリプトを使用します。

`mecab`コマンドのように使えます。

`echo`などからパイプで渡してもいいですし

```shell
$ echo 'ログを読め。' | janome
ログ	名詞,サ変接続,*,*,*,*,ログ,ログ,ログ
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
読め	動詞,自立,*,*,五段・マ行,命令ｅ,読む,ヨメ,ヨメ
。	記号,句点,*,*,*,*,。,。,。
```

1度コマンドを起動して

```shell
$ janome
```

対話的に入力してもOKです。

```shell
私は、東京都に行く。
私	名詞,代名詞,一般,*,*,*,私,ワタシ,ワタシ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
、	記号,読点,*,*,*,*,、,、,、
東京	名詞,固有名詞,地域,一般,*,*,東京,トウキョウ,トーキョー
都	名詞,接尾,地域,*,*,*,都,ト,ト
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
行く	動詞,自立,*,*,五段・カ行促音便,基本形,行く,イク,イク
。	記号,句点,*,*,*,*,。,。,。
```

ヘルプ。

```shell
$ janome -h
usage: janome [-h] [-e ENC] [--udic UDIC] [--udic-enc UDIC_ENC]
              [--udic-type UDIC_TYPE] [-m [MMAP]] [-g [GRAPHVIZ]]
              [--gv-out GV_OUT] [--gv-format GV_FORMAT] [--version]

optional arguments:
  -h, --help            show this help message and exit
  -e ENC, --enc ENC     Input encoding. Default is 'utf8'
  --udic UDIC           Path to user dictionary file
  --udic-enc UDIC_ENC   User dictionary encoding. Default is 'utf8'
  --udic-type UDIC_TYPE
                        User dictionary type, 'ipadic' or 'simpledic.' Default
                        is 'ipadic'
  -m [MMAP], --mmap [MMAP]
                        Use mmap mode
  -g [GRAPHVIZ], --graphviz [GRAPHVIZ]
                        Output visualized lattice graph by Graphviz
  --gv-out GV_OUT       Graphviz output file path. This option is used with -g
                        or --graphviz
  --gv-format GV_FORMAT
                        Graphviz output format. default is 'png'. This option
                        is used with -g or --graphviz. See
                        https://graphviz.gitlab.io/_pages/doc/info/output.html
                        for the supported formats.
  --version             show program's version number and exit
```


# `Analyzer`フレームワーク

Janomeには、`Ananlyzer`フレームワークというものがあり、文字の正規化などの前処理を行う`CharFilter`、形態素解析後の後処理を行う`TokenFilter`、`CharFilter`、`Tokenizer`、`TokenFilter`を組み合わせて構成する`Analyzer`から成っています。

[(experimental) Analyzer フレームワーク](https://mocobeta.github.io/janome/#experimental-analyzer-v0-3-4)

https://mocobeta.github.io/janome/api/janome.html#module-janome.analyzer

https://mocobeta.github.io/janome/api/janome.html#module-janome.charfilter

https://mocobeta.github.io/janome/api/janome.html#module-janome.tokenfilter

今回は、先ほどのサンプルで扱った文をちょっと修正して、遊んでみます。

`CharFilter`として`UnicodeNormalizeCharFilter`を選んで全角英数字を正規化し（デフォルトでNFKC）、`TokenFilter`として`POSStopFilter`を使って特定の品詞を除去、`LowerCaseFilter`で小文字化を行います。

hello_analyzer.py

```python
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.charfilter import *
from janome.tokenfilter import *

text = "さあご一緒に！ ＴＲＵＥ １ が ＴＲＵＥ ＴＲＵＥ ０ が ＦＡＬＳＥ！"

char_filters = [ UnicodeNormalizeCharFilter() ]
tokenizer = Tokenizer()
token_filters = [ POSStopFilter(["記号", "助詞", "接続詞"]), LowerCaseFilter() ]

analyzer = Analyzer(char_filters, tokenizer, token_filters)

for token in analyzer.analyze(text):
    print(token)
```

結果は、こちら。

```shell
$ python3 hello_analyzer.py 
さあ	感動詞,*,*,*,*,*,さあ,サア,サー
ご	接頭詞,名詞接続,*,*,*,*,ご,ゴ,ゴ
一緒	名詞,サ変接続,*,*,*,*,一緒,イッショ,イッショ
!	名詞,サ変接続,*,*,*,*,!,*,*
true	名詞,固有名詞,組織,*,*,*,true,*,*
1	名詞,数,*,*,*,*,1,*,*
true	名詞,固有名詞,組織,*,*,*,true,*,*
true	名詞,固有名詞,組織,*,*,*,true,*,*
0	名詞,数,*,*,*,*,0,*,*
false	名詞,固有名詞,組織,*,*,*,false,*,*
!	名詞,サ変接続,*,*,*,*,!,*,*
```

簡単に使えて、良さそうですね。