トークナイザをいい感じに切り替えるライブラリ konoha を作った

# TL; DR

文のトークン化のためのライブラリである [konoha](https://github.com/himkt/konoha) の紹介をします．
(旧 tiny_tokenizer)
↓みたいな感じで使えます．なにとぞ〜

```python
from konoha import WordTokenizer

sentence = '自然言語処理を勉強しています'

tokenizer = WordTokenizer('MeCab')
print(tokenizer.tokenize(sentence))  # -> [自然, 言語, 処理, を, 勉強, し, て, い, ます]

tokenizer = WordTokenizer('Kytea')
print(tokenizer.tokenize(sentence))  # -> [自然, 言語, 処理, を, 勉強, し, て, い, ま, す]

tokenizer = WordTokenizer('Sentencepiece', model_path="data/model.spm")
print(tokenizer.tokenize(sentence))  # -> [▁, 自然, 言語, 処理, を, 勉強, し, ています]
```

# はじめに: トークナイザとは

日本語は英語などの言語と異なり，単語境界に明確な区切り文字が存在しません．
このため，日本語の解析を行う場合，まずは文をなんらかの単位（例えば単語）に分割する必要があります．
この分割の処理には単語単位で分割するもの，単語をさらに細かく区切るサブワード単位に分割するもの，
そして文中の文字列を１文字ずつに分割するものなどが存在します．
本稿では，上に挙げた単位で分割された部分文字列のことをトークンと呼びます．
トークン化には様々な手法が存在します．
日本語テキストの解析で広く行われている形態素解析器も，単語分割を行なっています．
（形態素解析は単語分割の他に語の見出し語化と品詞タグ推定を行っています）

単語単位の分割には，辞書を用いてラティスを構築し，その後最適な単語列を決定する MeCab や
文字レベルで単語境界を判定する Kytea などのアルゴリズムが存在します．
これらのアルゴリズムは同じ分割結果を返すこともあれば，異なる分割結果を返すこともあります．
また，分割のアルゴリズムが同一な場合でも，品詞体系が異なると単語の単位も変わります．

- [IPADic の品詞体系](https://hayashibe.jp/tr/mecab/dictionary/ipadic)
- [UniDic の品詞体系](https://hayashibe.jp/tr/mecab/dictionary/unidic/pos)

サブワードとは，単語をさらに細かく区切ったものです．
ニューラル機械翻訳などで有効性が確認されてます．
日本語テキスト解析で使われている代表的なサブワード単位のトークナイザとして
[Sentencepiece](https://github.com/google/sentencepiece) が有名です．

- [MeCab の詳しい説明](https://techlife.cookpad.com/entry/2016/05/11/170000)（単語分割についても触れられています）
- [Kytea の単語分割方式の説明](http://www.phontron.com/kytea/method-ja.html)
- [様々な品詞体系・トークナイザの比較](https://www.anlp.jp/proceedings/annual_meeting/2016/pdf_dir/D6-5.pdf)
- [Sentencepiece の解説](https://qiita.com/taku910/items/7e52f1e58d0ea6e7859c)

# トークナイザどうするの問題

日本語のテキストマイニングを行う方々は
普段 MeCab + NEologd を使うことが多いでしょうか．
研究ではしばしば Kytea を使っている例もあるかもしれません．
また，最近話題の係り受け解析ライブラリの [Ginza](https://megagonlabs.github.io/ginza/) では
SudachiPy (形態素解析器 Sudachi の Python 実装) という形態素解析器を使っているなど，
様々な解析器が単語レベルの解析に用いられています．
どの解析器を利用するのがもっとも適しているのかを見極めるのは難しいです．

さらに，近年主に機械翻訳の文脈では
「形態素解析器の分かち書き結果を使うよりもサブワード単位にトークンを分割した方がタスクの性能が良い」
ことも報告されており，サブワード単位の分割を採用することも多いです．

文字レベルのトークン化には，単語の種類数と比較して文字の種類が少ないという特徴があります．
単語の種類数は一般的に文字の種類数よりもはるかに多く，文字レベルでトークン化を行うことはボキャブラリサイズを抑える効果があります．
例えば，固有表現抽出の研究では [文字レベルの特徴量を LSTM の特徴量に追加する](https://arxiv.org/abs/1603.01360) アプローチが存在し，
最近の研究でもこのアプローチを採用している研究は多いです．
（リンクに挙げた論文は古いですが，私の好きな論文です）

このような状況にある中で，私たちはどの単位で文をトークン化すれば良いのでしょうか？
一般的には，この問いに対する回答は「**タスク依存**」であり，唯一の回答は存在しないと理解しています．
このため，「多くの人が使っているから MeCab+NEologd を使う」
「論文はサブワードを採用してるからとりあえずサブワードでいく」
などの選択肢が取られがちです．

近年の自然言語処理のタスク（特にニューラルネットワークを使う場合）には
トークン化と同等以上に注意を払わなければならない事柄が数多く存在します．
（例. アーキテクチャ，隠れ層の次元，オプティマイザ，学習率...etc），
このような背景があり，どの方式でトークン化を行うかは問題に取り組む初めの段階で
「えいや」と決められていることが多いと思いのが現状だと思います．
しかし，他のトークン化方法は本当に試す価値はないのでしょうか？
私は様々なトークン化方式を試してみることには価値があると思っているので，
トークン化の方式の切り替えを簡単に行うためのライブラリを開発しました．
これが konoha の開発理由となっています．

# 色々なトークナイザ・色々な API

トークナイザの切り替えを行うには，しばしば若干のコストがかかります．
上に示した形態素解析器・トークナイザはすべて Python のラッパーが用意されており，
ユーザはラッパーライブラリをインストールすることでこれらのツールを Python から利用可能です．

しかしながら，ラッパーライブラリはそれぞれ異なるイディオムで API を提供しています．
（それぞれの解析器，およびそれらのラッパーライブラリの作者が異なるため，自然なことだと思います）
このため，複数の解析器の出力を状況に応じて切り替えて利用したい場合には，
自分でそれらの API のイディオムの差を吸収する層を自分で実装する必要があります．

# 先行事例

[JapaneseTokenizer](https://qiita.com/Kensuke-Mitsuzawa/items/1d2837b374152c9758c9) というライブラリが存在します．
(GitHub リポジトリ: [Kensuke-Mitsuzawa/JapaneseTokenizers](https://github.com/Kensuke-Mitsuzawa/JapaneseTokenizers))
JapaneseTokenizer も konoha と同様に複数のトークナイザのラッパーを提供しています．
JapaneseTokenizer は複数の形態素解析器を扱うインターフェースを提供しています．
JapaneseTokenizer には文の解析結果を特定の品詞タグでフィルタリングするなどの
テキスト解析を行う際に役立つ実用的な機能が多く実装されており，
複数の形態素解析器の結果を活用した自然言語処理を行う際にとても便利なツールです．


# konoha

一方で，tiny tokenizer は現時点では品詞のフィルタリングなどの機能を一切提供していません．
tiny tokenizer は各解析器のトークン化の処理を抽象化するライブラリであり，
JapaneseTokenizer が対象としていないサブワード単位の分割や文字レベルの分割の機能を提供しています．

本ライブラリの位置付けは Python ラッパーのラッパーです．
解析器の Python ラッパーを提供してくださっている皆さんに感謝しつつ，
それらのライブラリのインターフェースの差異を吸収する，というのが本ライブラリの趣旨となっています．
konoha を利用することで，ユーザは統一された API で複数の解析器を利用できるようになります．

## トークン化

まずは MeCab を使った例をお見せします．
この例では，辞書は mecab-ipadic を使用しています．
macOS を使っている場合は `mecab`, `mecab-ipadic`，
Ubuntu を使っている場合は上記に加えて `libmecab-dev` をインストールしておいてください．
他のディストリビューションについては動作未検証です．
（`mecab`, `mecab-config` が実行できて辞書がインストールされていれば問題なく動作すると思います）
なお， GitHub のリポジトリにある Dockerfile をビルドしていただいて環境を作っていただけると一通り準備が済みます．

- コード

```python
from konoha import WordTokenizer

sentence = '自然言語処理を勉強しています'

tokenizer = WordTokenizer('MeCab')
print(tokenizer.tokenize(sentence))
```

- 出力

```
[自然, 言語, 処理, を, 勉強, し, て, い, ます]
```

次に Kytea を使ってみます．
こちらについても， Kytea をビルドする必要があります．
（こちらもリポジトリの Dockerfile を参考にしていただければと思います）

- コード

```python
from konoha import WordTokenizer

sentence = '自然言語処理を勉強しています'

tokenizer = WordTokenizer('Kytea')
print(tokenizer.tokenize(sentence))
```

- 出力

```
[自然, 言語, 処理, を, 勉強, し, て, い, ま, す]
```

また，文をサブワード単位に分割したい場合， Sentencepiece を使うことが可能です．
Sentencepiece を用いる場合には，モデルファイルを指定する必要があります．
`model_path` にモデルファイルへのパスを渡してください．

- コード

```python
from konoha import WordTokenizer

sentence = '自然言語処理を勉強しています'

tokenizer = WordTokenizer('Sentencepiece', model_path="data/model.spm")
print(tokenizer.tokenize(sentence))
```

- 出力

```
[▁, 自然, 言語, 処理, を, 勉強, し, ています]
```

このように， `WordTokenizer` に渡す引数の値を変更するだけで複数の解析器を統一的に利用できます．
これにより，実験段階で様々なトークナイザを試すことが簡単になります．

## 品詞推定

トークナイザの中には形態素解析器も含まれています．
現在 konoha がサポートしているトークナイザのうち，
形態素解析器であるものは `MeCab`，`Kytea` および `Sudachi (SudachiPy)` です．
これらについては，トークン化を行なった際に品詞タグなどの形態素解析器が付与する情報を得るかどうかを
オプションで制御できます．

`SudachiPy` を利用する例を以下に示します．

- コード

```python
from konoha import WordTokenizer

sentence = '自然言語処理を勉強しています'

tokenizer = WordTokenizer('Sudachi', mode='A', with_postag=True)
print(tokenizer.tokenize(sentence))
```

- 出力

```
[自然 (名詞), 言語 (名詞), 処理 (名詞), を (助詞), 勉強 (名詞), し (動詞), て (
助詞), い (動詞), ます (助動詞)]
```

`tokenizer.tokenize` の出力は `Token` クラスのインスタンスです．
`Token` クラスでは以下に示すインスタンス変数が定義されています．
（`Token` クラスの docstring より抜粋）

解析器が返さない情報については `None` になります．
例えば， `token.normalized_form` は `SudachiPy` を利用し，
かつ `with_postag` が `True` となっている場合にのみ `None` ではない値となります．
（`token` は `tokenizer.tokenize` が出力するトークン列の配列のひとつの要素です）

```
"""
surface (str)
    surface (original form) of a word
postag (str, default: None)
    part-of-speech tag of a word (optional)
postag2 (str, default: None)
    detailed part-of-speech tag of a word (optional)
postag3 (str, default: None)
    detailed part-of-speech tag of a word (optional)
postag4 (str, default: None)
    detailed part-of-speech tag of a word (optional)
inflection (str, default: None)
    conjugate type of word (optional)
conjugation (str, default: None)
    conjugate type of word (optional)
base_form (str, default: None)
    base form of a word
yomi (str, default: None)
    yomi of a word (optional)
pron (str, default: None)
    pronounciation of a word (optional)
normalized_form (str, default: None)
    normalized form of a word (optional)
    Note that normalized_form is only
    available on SudachiPy
"""
```

## 自分で用意したユーザ辞書を使う (MeCab)

ユーザ辞書を利用したい場合には， `WordTokenizer` の `user_dictionary_path`
という名前の引数にユーザ辞書へのパスを渡します．

```python
from konoha import WordTokenizer

sentence = '自然言語処理を勉強しています'

tokenizer = WordTokenizer('MeCab', user_dictionary_path="path/to/user_dict")
print(tokenizer.tokenize())
```


## 自分で用意したシステム辞書を使う (MeCab)

`mecab-ipadic-NEologd` を利用したい，
または自分でコーパスを用いて再学習したシステム辞書を利用したい場合には，
システム辞書を指定してトークナイザを生成することが可能です．
`WordTokenizer` に `system_dictionary_path` という引数が生えているので，
そこに利用したいシステム辞書へのパスを与えてください．

```python
from konoha import WordTokenizer

sentence = '自然言語処理を勉強しています'

tokenizer = WordTokenizer('MeCab', system_dictionary_path="path/to/system_dict")
print(tokenizer.tokenize())
```


# まとめ

本稿では，複数のトークナイザを同じインターフェースで利用するためのライブラリである
konoha の紹介を行いました．
テキスト解析を行う初めの段階でどの解析器を使うか迷う際に本ライブラリを使うことで
解析器の切り替えを容易にすることが可能です．
また，自分は MeCab を使って実験をする予定だが先行研究が他の解析器を使っているため
比較用に別の解析器を使うためのコードを書かなければならない，というケースにおいても
konoha を入れることで手間なく同一のコードで実験を行えます．
現場で自然言語処理をする方々，研究で自然言語処理をしている方々の一助になったら嬉しいです．
よかったらぜひ使ってみてください，よろしくお願いします．


---

なお， Ubuntu 18.04 で Kytea をビルドするためには [このプルリクエスト](https://github.com/neubig/kytea/pull/24)
を取り込む必要があります． konoha のリポジトリにある Dockerfile を参考にしていただければと思います．