Rust初心者がRust製の日本語形態素解析器の開発を引き継いでみた

[Rust初心者がRustで全文検索サーバを作ってみた](https://qiita.com/mosuka/items/3517ef4a1eb07fa2661f)の続きです。
この記事では、Rust製の日本語形態素解析器の開発を引き継いだ経緯と、その使い方を簡単に紹介します。

## 開発を引き継ぐことになった経緯

冒頭でも書きましたが、Rust初心者がRustで全文検索サーバを作っています。全文検索サーバを作り始めると、日本人なのでどうしても日本語のドキュメントをインデックスして検索したくなります。日本語を上手く検索インデックスに登録するには日本語形態素解析器を利用するのが一般的です。
日本語形態素解析器、どれを使おうかな？とユーザの立場でいたのが、いつの間にか開発を引き継ぐことになりました。

日本語形態素解析器といえば、大御所の[MeCab](https://taku910.github.io/mecab/)があります。歴史も長く、Python、Ruby、JavaからMeCabを利用するためのバインディングも用意されています。
OSSの検索エンジンの世界では[Lucene](https://lucene.apache.org/)/[Solr](https://lucene.apache.org/solr/)、[Elasticsearch](https://www.elastic.co/elasticsearch)が多くの企業や団体で採用されていますが、その中で日本語を扱うために利用されているのがJava製の日本語形態素解析器Kuromoji([Atilika版](https://github.com/atilika/kuromoji)/[Lucene版](https://github.com/apache/lucene-solr/tree/master/lucene/analysis/kuromoji))です。MeCabとは異なり、形態素辞書をJarの中に同梱し、Jarファイルの配布によって簡単にデプロイできるのも特徴の一つです。最近では[Sudachi](https://github.com/WorksApplications/Sudachi)というJava製の日本語形態素解析器も登場し、一部ではElasticsearchで利用され始めているようです。KuromojiがLuceneにコントリビュートされる前は、[Sen](https://www.mlab.im.dendai.ac.jp/~yamada/ir/MorphologicalAnalyzer/Sen.html)や、[lucene-gosen](https://github.com/lucene-gosen/lucene-gosen)を別途ダウンロードしてクラスパスを通して使っていました。
Java以外でも、Python実装の[Janome](https://mocobeta.github.io/janome/en/)、Go実装の[Kagome](https://github.com/ikawaha/kagome)、JavaScript実装の[kuromoji.js](https://github.com/takuyaa/kuromoji.js)など、多くのプログラミング言語で日本語形態素解析器は開発されています。

では、Rustはどうなのかというと、もちろんRustで実装された日本語形態素解析器も既に存在します。
- [Yoin](https://github.com/agatan/yoin)
- [kuromoji-rs](https://github.com/fulmicoton/kuromoji-rs)
- [sudachi.rs](https://github.com/sorami/sudachi.rs)

Yoinとkuromoji-rsはどちらもIPADICを形態素辞書として組み込んだ[FST(Finite-state transducer)](https://en.wikipedia.org/wiki/Finite-state_transducer)を利用した日本語形態素解析器のようです。
特にkuromoji-rsはトークナイザにKuromojiのsearchモードと同じ機能を実装しているようです。
sudachi.rsはJava製の形態素解析器SudachiのRust実装です。詳しくはこちら([Rustによる自然言語処理ツールの実装: 形態素解析器「sudachi.rs」](https://qiita.com/sorami/items/7934fec2074c493c0f7d))を参照してください。

kuromoji-rsの開発者[Paul Masurel](https://twitter.com/fulmicoton)氏は、Rust製の全文検索ライブラリ[Tantivy](https://github.com/tantivy-search/tantivy)の開発者でもあります。
既にTantivyを使って全文検索サーバを開発しているので、同じ人が開発しているなら、これにしてみようと言うことでkuromoji-rsを使ってみることにします。

kuromoji-rsはライブラリとして提供されているため、まずはその使い方を理解するため、MeCabのようなCLIを作ってみる事から始めます。
CLIを作り始める際に`Cargo.toml`にkuromoji-rsへの依存を記述しようとしたのですが、どうやらまだ[crates.io](https://crates.io/)に登録されていいないようです。crates.ioへ登録する予定はないかとGitHubで[問い合わせ](https://github.com/fulmicoton/kuromoji-rs/issues/1)しますが、次のような返事をもらいます。

> Would you take over the project :) ?

思わぬ返事に戸惑いましたが、ふと昨年末のKuromoji忘年会に参加した帰りのツイートを思い出します。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">エンジニアの嗜みとして形態素解析器少しくらい触ったほうがいいかな？</p>&mdash; Minoru OSUKA (@minoru_osuka) <a href="https://twitter.com/minoru_osuka/status/1206932885556449281?ref_src=twsrc%5Etfw">December 17, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

興味はあったものの、なかなか手を出せないでいた形態素解析器。エンジニアの嗜みとして少しはかじっていたほうがいいのは？とも考えていたので、自信はなかったのですが、kuromoji-rsの開発を引き継ぐことにしました。
元々、kuromoji-rsはPoC的なプロジェクトらしく、作ってはみたものの、開発者自身があまり時間を割くことができず、以前からプロジェクトを引き継いてくれる人を探していたようでした。
引き継ぐにあたっては次のことだけお願いされました。
- MIT Licenseでの公開
- Licenseに自分の名前を残すこと
- Kuromojiという名前をプロジェクト名に使用しない (Kuromojiのプロジェクト名ハイジャックで彼らに迷惑をかけないため)
上記のことを守ってくれさえすれば好きにしてくれていいとのことでした。

## Lindera

さて、開発を引き継ぐにあたって、プロジェクトに新しい名前が必要です。いろいろ案を考えましたが、最終的に「**Lindera**」と名付けました。
[クロモジ属](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%A2%E3%82%B8%E5%B1%9E)がプロジェクト名の由来です。

Linderaは次のOrganizationで開発を行っています。
- https://github.com/lindera-morphology

Linderaは形態素解析のライブラリとコマンドを提供するプロジェクトです。次のリポジトリから構成されています。
- [lindera](https://github.com/lindera-morphology/lindera): Tokenizer、Formatter
- [lindera-core](https://github.com/lindera-morphology/lindera-core): Dictionary struct、Viterbi algorithm
- [lindera-dictionary](https://github.com/lindera-morphology/lindera-dictionary): Dictionary Loader
- [lindera-ipadic](https://github.com/lindera-morphology/lindera-ipadic): IPADIC Loader
- [lindera-ipadic-builder](https://github.com/lindera-morphology/lindera-ipadic-builder) IPADIC Builder
- [indera-cli](https://github.com/lindera-morphology/lindera-cli): Command-line interface

## CLIを使ってみる

Lindera(旧kuromoji-rs)ですが、先述したようにライブラリターゲットでの提供だったので、ちょっと試したいと思ってもRustでコーディングしないといけません。そこで、このLinderaを簡単に試したりできる、デモンストレーションを行えるCLIを用意しました。
まだ、各プラットフォーム(Windows、OS X、Linux)向けにコンパイル済みの実行可能なバイナリターゲットの提供はできていません(後でバイナリターゲットの提供を予定してます)。面倒ですが、Cargoをインストールして、ソースコードからビルドするか、Cargoからインストールする必要があります。

RustとCargoのインストールはこちらを参照してください。
https://doc.rust-lang.org/cargo/getting-started/installation.html

Cargoからインストールする場合は次のコマンドでインストールできます。`cargo install`した場合は`${HOME}/.cargo/bin/lindera`にバイナリがインストールされます。

```
% cargo install lindera-cli
```

ソースからビルドする場合は次のコマンドでビルドできます。ビルドされたバイナリはリポジトリのディレクトリ以下の`bin/lindera`にバイナリがコピーされます。

```
% cd ${HOME}
% git clone https://github.com/lindera-morphology/lindera-cli.git
% cd lindera-cli
% make build
```

インストール、またはビルドしたバイナリをパスの通ったところにおいてもらうと、以下の例が実行しやすいです。

では、早速使ってみましょう。`lindera`コマンドはデフォルトでIPADICの形態素辞書を内包していますので追加で辞書をインストールする必要はありません。次のようにすぐに実行できます。

```
% echo "関西国際空港限定トートバッグ" | lindera
関西国際空港    名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定    名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ    UNK,*,*,*,*,*,*,*,*
EOS
```

### トークナイズモード

Linderaには`normal`と`decompose`の２つのトークナイズモードを指定することができます。モードの切替は`--mode`または`-m`で行えます。

`normal`モードは、形態素辞書に登録されている単語に沿ってトークナイズします。`--mode`または`-m`を省略すると`normal`モードでのトークナイズとなります。

```
% echo "関西国際空港限定トートバッグ" | lindera -m normal
関西国際空港    名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定    名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ    UNK,*,*,*,*,*,*,*,*
EOS
```

`decompose`モードは、分割可能な複合語を更に細かく分割します。Kuromojiの`search`モードと同じです(名前を変えました)。

```
% echo "関西国際空港限定トートバッグ" | lindera -m decompose
関西    名詞,固有名詞,地域,一般,*,*,関西,カンサイ,カンサイ
国際    名詞,一般,*,*,*,*,国際,コクサイ,コクサイ
空港    名詞,一般,*,*,*,*,空港,クウコウ,クーコー
限定    名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ    UNK,*,*,*,*,*,*,*,*
EOS
```

同じ`関西国際空港限定トートバッグ`というテキストをトークナイズしていますが、`normal`と`decompose`では`関西国際空港`の分割のされ方が違います。`decompose`モードは、検索エンジンなどで[再現率(recall)](https://en.wikipedia.org/wiki/Precision_and_recall)の向上を目的とする場合に適したモードです。
用途に合わせてトークナイズモードを使い分けていただければと思います。

### 出力フォーマット

Linderaは`mecab`、`wakati`、`json`フォーマットで解析結果を出力することができます。出力フォーマットの切り替えは`--output`または`-o`で行えます。

`mecab`は解析結果をMeCabに似たフォーマットで出力します。出力モードを省略したときのデフォルトです。トークンに付随する情報がカンマ(`,`)区切りで出力されます。MeCabで馴染みのある出力フォーマットです。

```
% echo "関西国際空港限定トートバッグ" | lindera -o mecab
関西国際空港	名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定	名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ	UNK
EOS
```

`wakati`はトークンに付随する情報は省略して、トークンのテキストだけを空白(` `)区切りで出力します。分割のされ方だけ確認するならこれで十分ですね。

```
% echo "関西国際空港限定トートバッグ" | lindera -o wakati
関西国際空港 限定 トートバッグ
```

`json`は解析結果をJSONフォーマットで出力します。MeCabには無い出力モードです。CLIなどで実行結果をJSON出力し、結果を加工して利用することが簡単になります。
Linderaで解析した結果から特定の品詞だけをフィルタするなど、`grep`、`sed`や`awk`だけでなく`jq`コマンドとも組み合わせられると便利かなと思い追加しました。

```
% echo "関西国際空港限定トートバッグ" | lindera -o json
[
  {
    "text": "関西国際空港",
    "detail": [
      "名詞",
      "固有名詞",
      "組織",
      "*",
      "*",
      "*",
      "関西国際空港",
      "カンサイコクサイクウコウ",
      "カンサイコクサイクーコー"
    ]
  },
  {
    "text": "限定",
    "detail": [
      "名詞",
      "サ変接続",
      "*",
      "*",
      "*",
      "*",
      "限定",
      "ゲンテイ",
      "ゲンテイ"
    ]
  },
  {
    "text": "トートバッグ",
    "detail": [
      "UNK"
    ]
  }
]
```

`mecab`や`json`フォーマットで出力されるトークンに付随する情報については、使用している辞書のビルダのリポジトリに記載しています。
デフォルトのIPADICを使用した際のフォーマットは、次のページを参照してください。
- [Lindera IPADIC Builder / Dictionary format](https://github.com/lindera-morphology/lindera/tree/master/lindera-ipadic-builder#dictionary-format)

## 辞書の切り替え

kuromoji-rsは内包するIPADICしか使用できませんでしたが、Linderaではいくつかの日本語形態素辞書をサポートしています。
Lindera向けの形態素辞書の構築と、使用する方法を紹介します。

### UniDic

UniDicについてはこちらを参照してください。
- [UniDicとは？](https://unidic.ninjal.ac.jp/about_unidic)

このUniDicをLinderaから利用できるようにするためには、事前に辞書をビルドする必要があります。UniDicをビルドするためのリポジトリはこちらです。
- [lindera-unidic-builder](https://github.com/lindera-morphology/lindera-unidic-builder)

```
% git clone https://github.com/lindera-morphology/lindera-unidic-builder.git
% cd lindera-unidic-builder
% make lindera-unidic
```

上記コマンドを実行すると、Lindera向けにビルドされた辞書が`lindera-unidic-2.1.2`というディレクトリの下に作成されます。
辞書のあるディレクトリを指定する場合は`--dictionary-directory`または`-d`フラグを使用します。

```
% echo "関西国際空港限定トートバッグ" | lindera -d ./lindera-unidic-2.1.2
関西	名詞,固有名詞,地名,一般,*,*,カンサイ,カンサイ,関西,カンサイ,関西,カンサイ,固,*,*,*,*
国際	名詞,普通名詞,一般,*,*,*,コクサイ,国際,国際,コクサイ,国際,コクサイ,漢,*,*,*,*
空港	名詞,普通名詞,一般,*,*,*,クウコウ,空港,空港,クーコー,空港,クーコー,漢,*,*,*,*
限定	名詞,普通名詞,サ変可能,*,*,*,ゲンテイ,限定,限定,ゲンテー,限定,ゲンテー,漢,*,*,*,*
トート	名詞,普通名詞,一般,*,*,*,トート,トート,トート,トート,トート,トート,外,*,*,*,*
バッグ	名詞,普通名詞,一般,*,*,*,バッグ,バッグ-bag,バッグ,バッグ,バッグ,バッグ,外,*,*,*,*
EOS
```

### IPADIC-NEologd

IPADIC-NEologdについてはこちらを参照してください。
- [mecab-ipadic-NEologd](https://github.com/neologd/mecab-ipadic-neologd)

こちらもUniDicと同様に事前に辞書のビルドを行います。IPADIC-NEologdをビルドするためのリポジトリはこちらです。
- [lindera-ipadic-neologd-builder](https://github.com/lindera-morphology/lindera-ipadic-neologd-builder)

```
% git clone https://github.com/lindera-morphology/lindera-ipadic-neologd-builder.git
% cd lindera-ipadic-neologd-builder
% make lindera-ipadic-neologd
```

上記コマンドを実行すると、Lindera向けにビルドされた辞書が`lindera-ipadic-2.7.0-20070801-neologd-20200130`というディレクトリの下に作成されます。

```
% echo "関西国際空港限定トートバッグ" | lindera -d ./lindera-ipadic-2.7.0-20070801-neologd-20200130
関西国際空港	名詞,固有名詞,組織,*,*,*,関西国際空港,カンサイコクサイクウコウ,カンサイコクサイクーコー
限定	名詞,サ変接続,*,*,*,*,限定,ゲンテイ,ゲンテイ
トートバッグ	名詞,固有名詞,一般,*,*,*,トートバッグ,トートバッグ,トートバッグ
EOS
```

いかがでしょうか。辞書を切り替えると同じテキストでもトークナイズのされ方、付随する品詞情報など違うと思います。
形態素辞書も用途に合わせて切り替えることをお勧めします。

## 番外編

日本語形態素解析をするためにLinderaの開発をしていますが、実は辞書ソースのフォーマットが同じであれば、少し手を加えるだけでLinderaで利用できるようになります。
日本語以外でも扱うことができます。

### ko-dic

ko-dicは、韓国語の形態素辞書です。辞書のソースフォーマットは基本的に他のMeCab向け形態素辞書と同じなのでLinderaから利用できるようにしてみました。

ko-dicについてはこちらを参照してください。
- [mecab-ko-dic](https://bitbucket.org/eunjeon/mecab-ko-dic/src/master/)

こちらも他の辞書と同様に事前に辞書のビルドを行います。ko-dicをビルドするためのリポジトリはこちらです。
- [lindera-ko-dic-builder](https://github.com/lindera-morphology/lindera-ko-dic-builder)

```
% git clone https://github.com/lindera-morphology/lindera-ko-dic-builder.git
% cd lindera-ko-dic-builder
% make lindera-ko-dic
```

上記コマンドを実行すると、Lindera向けにビルドされた辞書が`lindera-ko-dic-2.1.1-20180720`というディレクトリの下に作成されます。

```
% echo "간사이국제공항한정토트백" | lindera -d ./lindera-ko-dic-2.1.1-20180720
간사이	NNG,*,F,간사이,Compound,*,*,간/NNG/*+사이/NNG/*
국제공항	NNG,*,T,국제공항,Compound,*,*,국제/NNG/*+공항/NNG/*
한정	NNG,*,T,한정,*,*,*,*
토트백	NNG,*,T,토트백,Compound,*,*,토트/NNP/인명+백/NNG/*
EOS
```

精度はわかりませんが、なんとなくうまく解析できていそうな感じです。
Luceneにも韓国語形態素解析器Noriがありますが、Linderaと同じこのko-dicを利用しています。
また、Nori自体はKuromojiをフォークして辞書をko-dicに差し替えたもののようなので、おそらく同じような解析結果が得られるだろうと期待しています。
- https://issues.apache.org/jira/browse/LUCENE-8231

同様に中国語についても辞書のソースさえ用意できれば、形態素解析が行えるようになるかもしれません。
日本語だけでなく韓国語、中国語までサポートしてみたくなりました。

## おわりに

前回同様、個人的なプロジェクトの紹介となって大変恐縮です。今回はLinderaの開発引き継ぎ経緯と使い方の簡単な紹介をさせていただきました。
このような、開発を引き継ぐ機会を与えてくれて、リリースのサポートをしてくれたた[Paul](https://twitter.com/fulmicoton)さん、開発に協力してくれた[johtani](https://twitter.com/johtani)さん、[ikawaha](https://twitter.com/ikawaha)さんに改めて感謝いたします。

最後に、もしLinderaに興味を持っていただけたなら、ぜひ使ってみていただけたらと思います。