Elasticsearchのための新しい形態素解析器 「Sudachi」

## tl;dr （要約）

- Kuromojiに代わる新しい形態素解析器「[Sudachi](https://github.com/WorksApplications/Sudachi)」
- なにが良いの？
    - 最新の辞書
    - 企業（ワークスアプリケーションズ）による継続的な更新
    - 複数の分割単位 → 検索用途での再現率と適合率の向上
    - プラグインによる拡張
    - 省メモリ
- Elasticsearchで使いたい
    - プラグイン: [WorksApplications/elasticsearch-sudachi](https://github.com/WorksApplications/elasticsearch-sudachi)
    - 使い方は当記事の後半をご覧ください

*注: この記事の執筆者はSudachiの開発に関わっています*


## さまざまな形態素解析器

形態素解析は、伝統的な自然言語処理（Natural Language Processing, NLP）において基盤となる技術です。そして世の中にはさまざまな形態素解析器が存在します。販売されているものもありますが、一般に公開されているものだけでもいくつか例をあげると、[JUMAN](http://nlp.ist.i.kyoto-u.ac.jp/index.php?JUMAN)とRNNLMを利用した[JUMAN++](http://nlp.ist.i.kyoto-u.ac.jp/index.php?JUMAN++)、チャンピオン・セグメンターこと[ChaSen](http://chasen-legacy.osdn.jp/)とその後続で現在最も著名であろう[MeCab](http://taku910.github.io/mecab/)、点推定により部分アノテーションデータから学習できる[KyTea](http://www.phontron.com/kytea/index-ja.html)、Elasticsearchにも公式採用されている[kuromoji](http://www.atilika.org/)、そしてそれぞれピュアGo/JavaScript/Python実装で辞書同梱の[kagome](https://github.com/ikawaha/kagome)/[kuromoji.js](https://github.com/takuyaa/kuromoji.js)/[Janome](http://mocobeta.github.io/janome/)などなど。ソフトウェアではなくWeb APIという形で提供されるものもあります（[Yahoo! JAPAN テキスト解析Web API](https://developer.yahoo.co.jp/webapi/jlp/ma/v1/parse.html)など）。さまざまなものがあるので、複数の解析器をまとめて利用できるライブラリ（[JapaneseTokenizers](https://github.com/Kensuke-Mitsuzawa/JapaneseTokenizers)）もあったりします。

他にも、いわゆる形態素解析の定義からは少し外れますが、JavaScript実装の[Rakuten MA](https://github.com/rakuten-nlp/rakutenma)（単語の分かち書き＋品詞付与）や、同じくJavaScript製のコンパクトな分かち書き器[TinySegmenter](http://chasen.org/~taku/software/TinySegmenter/)、そしてより最近ではニューラル手法によるNLPのためのトークナイザ[SentencePiece](https://github.com/google/sentencepiece)などもあります。また、これまでの形態素解析を抜本的に改善しようという高い理念のもとに開発されている日本語解析システム[雪だるま](http://snowman.jnlp.org/snowman)も特筆すべきプロジェクトの一つでしょう。

形態素解析はほとんどの場合、辞書と、アノテーションされた学習データもしくは学習済のモデルが必要です[^1]。言語資源としては、Unidic辞書/BCCWJコーパスやIPA辞書/コーパス、Juman辞書/京都コーパス、NAIST JDicなどが主なものです。これらは頻繁に更新されるわけではないので、Web上のテキストから新語を収集・収録している[NEologd](https://github.com/neologd)を併用されている方も多いのではないでしょうか。

2018年3月に開催される[言語処理学会第24回年次大会(NLP2018)](http://www.anlp.jp/nlp2018/)では「[形態素解析の今とこれから](https://sites.google.com/view/nlp2018ws)」と題したワークショップが予定されており、活発な議論が期待されます。また、MeCabやSentencePieceの作者でもある工藤拓さんによる[一冊まるまる形態素解析という狂気じみた本](https://twitter.com/taku910/status/940116830957346816)も近日刊行予定とのことです。まさになぜか今、形態素解析がアツいのかもしれません。


## ... and again Yet Another 形態素解析器 「Sudachi」

そんな中、2017年につくられた新たな解析器が「Sudachi」です。既にさまざまなツールがある中、商業利用に耐えうる、より高品質で使い勝手の良い形態素解析器を標榜するものです。

開発は、株式会社ワークスアプリケーションズ下の機関である[ワークス徳島人工知能NLP研究所](http://nlp.worksap.co.jp/)を主として行われています。**企業によるバックアップ**のもと、**オープンソースソフトウェア**としてコードおよび言語資源を一般に公開しています。ライセンスは**Apache-2.0**です。

Sudachiの特徴としてまず、**最新の辞書**が用意されているという点が挙げられます。既存の形態素解析器における辞書は、10年以上も前に更新が止まってしまっており、新語・固有表現も不足しています。一方でSudachiでは、UniDicとNEologdをもとに専門家が調整した高品質で大規模な資源を用意しており、今後10年は継続して更新していく予定です。

他には、**複数の分割単位**での出力という機能があります。一般に形態素解析器はひとつの単位でしか出力できませんが、SudachiではA,B,Cの3単位での出力が可能です。これはSudachiのシステム辞書に分割情報を付与することで実現しています。

例えば「*医薬品安全管理責任者*」という入力のときには以下3種類の出力が可能です。

```
A: 医薬 / 品 / 安全 / 管理 / 責任 / 者
B: 医薬品 / 安全 / 管理 / 責任者
C: 医薬品安全管理責任者
```

従来の形態素解析器では、AもしくはBのような単位でしか区切れませんでしたが、Sudachiではそれに加えてCのように固有表現も意味を持つ言語単位として判別することが可能です。

これにより例えば検索用途においては、短単位と長単位を併用することで、再現率と適合率を向上することができます（後述するElasticseachプラグインのsearchモードでは、A単位とC単位が併用されます）。

分割情報に加えて、Sudachiのシステム辞書では以下の**表記正規化**がカバーされています。

- 送り違い
  - *打込む → 打ち込む*
- 字種
  - *かつ丼 → カツ丼*
- 異体字
  - *附属 → 付属*
- 誤用
  - *シュミレーション → シミュレーション*
- 縮約
  - *ちゃあ → ては*

このように高品質のシステム辞書ですが、利用者が自ら複数の**ユーザー辞書**を用意して拡張することも可能です。

またこの他にも、**プラグインによる機能拡張**が可能です。これまで解析器の外で行っていた正規化や補正処理などのよくおこなわれる処理を同梱することができます。以下に述べるようなプラグインの種類があり、例に挙げたいくつかは既にシステム提供プラグインとして入っており、ほかも現在開発中です。

- 入力テキスト: 異体字統制、長音記号正規化、など
- 未知語: 1文字未知語ノードの作成、MeCab互換処理、など
- 単語の連接: 品詞接続禁制、コスト値の書き換え、など
- 出力解補正: 敬称や前後関係から人名部を推定、漢数詞や位取りの正規化、など

もちろん、自分でプラグインをつくって差し込むことも可能です。例えば、独自の未知語処理をしたり、対象データにあわせて品番や型式などの抽出をすることができます。

また、メモリマップ利用による複数JVMでの辞書共有により、**省メモリ**を実現しています。実装の工夫については、[JJUG CCC 2017 Fallでの発表](https://www.slideshare.net/WorksApplications/java-82794239)をご覧ください。

MeCab、Kuromojiと比較すると以下のようになります。

|             | Sudachi | MeCab | kuromoji |
| :---------- | :-----: | :---: | :------: |
| 分割単位の併用     |    ○    |   ×   |   △ ^1   |
| 文字正規化、表記正規化 |    ○    |   ×   |   △ ^2   |
| まとめ上げ、補正処理  |    ○    |   ×   |   △ ^2   |
| 複数ユーザ辞書の利用  |    ○    |   ○   |    ×     |
| 省メモリ        |    ◎    |   △   |    ○     |
| 解析精度        |    ○    |   ○   |    ○     |
| 解析速度        |    △    |   ○   |    △     |

- ^1: n-best解による近似
- ^2: Lucene フィルター併用

まだ公開して日は浅いですが、既に[SyncThought社さん](https://japan.cnet.com/release/30219442/)での採用例もあります。

ここから、各種プラグインの作成、高速化や解析精度向上、同義語辞書との連携、分割情報や正規化表記の拡充、読み情報の整備、ユーザー辞書まわりの使い勝手向上、などなど、様々な面で形態素解析器として強化していきます。また、あわせてPython実装（その名も[SudachiPy](https://github.com/WorksApplications/SudachiPy)）も現在開発中です。簡単にインストールできて、手軽にデータ分析などに使えるよう整備していく予定です。

Sudachiについての解説は、以下の資料もあわせてご覧ください。

- [形態素解析の話](https://www.slideshare.net/WorksApplications/ss-78025845) by 高岡一馬 @ [NLPエンジニアによる自然言語処理の実用化にむけた勉強会](https://wap.connpass.com/event/59358/), 2017/6/22
- [Javaでつくる本格形態素解析器](https://www.slideshare.net/WorksApplications/java-82794239) by 高岡一馬 @ [JJUG CCC 2017 Fall](http://www.java-users.jp/ccc2017fall/), 2017/11/18

各種ソースコードはこちらから。

- [WorksApplications/Sudachi](https://github.com/WorksApplications/Sudachi) - 本家Java版
- [WorksApplications/elasticsearch-sudachi](https://github.com/WorksApplications/elasticsearch-sudachi) - Elasticsearch用プラグイン
- [WorksApplications/SudachiPy](https://github.com/WorksApplications/SudachiPy) - Python版（開発中）


## Sudachi for Elasticsearch

では実際に、SudachiをElasticsearchで動かしてみましょう！

今回の検証環境は以下のとおりです。

- Elasticsearch 5.6.1
- Ubuntu 17.10
- Java 1.8.0_151

（環境やコードが整備されてきたため、以前書かれた [SudachiをElasticsearchで使用する手順#1 - Qiita](https://qiita.com/matsuda_vla/items/9922e34e7623900eadc1) とは大きく違う手順に変わっています）

### プラグインの入手とビルド

Elasticsearch用のSudachiプラグインは [WorksApplications/elasticsearch-sudachi](https://github.com/WorksApplications/elasticsearch-sudachi) にあります。

このプラグインの`pom.xml`ではElasticsearchのバージョンを`5.6.1`としており、今回の検証でもバージョンをあわせています。5.6.xであれば、この部分を合わせて書き換えれば問題なく動作するでしょう。6系向けは現在対応中です、しばしお待ちください。

2018-01-09 追記: @matsuda_vla さんが、6系で動かした手順を書いてくださっています。
[SudachiをElasticsearch6.1.1で使用する手順（非公式） - Qiita](https://qiita.com/matsuda_vla/items/1e1779d5a4776b6e645a)

まずソースコードを持ってきて、ビルドします。

```bash
$ git clone https://github.com/WorksApplications/elasticsearch-sudachi.git
$ cd elasticsearch-sudachi
$ mvn package
```

ビルドが無事完了すると、 `elasticsearch-sudachi/target/releases/` に `analysis-sudachi-1.0.0-SNAPSHOT.zip` というような名前のファイルができます。

(2017-12-19追記: 自身でビルドする代わりに、[Snapshot版リポジトリ](https://oss.sonatype.org/content/repositories/snapshots/com/worksap/nlp/analysis-sudachi/)からダウンロードすることもできます。Kengo Todaさん、ご指摘ありがとうございました）

### プラグインのインストール

次に、ビルドしたものをインストールします。これにはElasticsearchに用意されている`elasticsearch-plugin`というスクリプトを使います。`install`コマンドで、先ほどビルドしたファイルのパスを指定してください。

インストール後、`list`コマンドの結果に`analysis-sudachi`が含まれていれば成功です。

```bash
$ sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install file:///path/to/builded/file/analysis-sudachi-1.0.0-SNAPSHOT.zip
$ /usr/share/elasticsearch/bin/elasticsearch-plugin list
analysis-sudachi
```

### 辞書の入手と設置

インストールに加えてもう一つ準備として、辞書ファイルを適切な場所に配置する必要があります。辞書ファイルは以下のURLからダウンロードできます。

[Index of /repositories/snapshots/com/worksap/nlp/sudachi/0.1.1-SNAPSHOT](https://oss.sonatype.org/content/repositories/snapshots/com/worksap/nlp/sudachi/0.1.1-SNAPSHOT/)

ここから最新版の`dictionary_core`もしくは`dictionary_full`を入手してください。

- `dictionary_core`: 専門家による語彙の追加や調整がなされた高品質な辞書。UniDicをベースに、会社名、住所、駅名、人名などが追加されている。語彙数150万程度
- `dictionary_full`: `dictionary_core`へ語彙をさらに追加した上位集合。語彙数300万弱程度

```bash
$ wget https://oss.sonatype.org/content/repositories/snapshots/com/worksap/nlp/sudachi/0.1.1-SNAPSHOT/sudachi-0.1.1-20171208.130044-8-dictionary-core.tar.bz2
$ tar xf sudachi-0.1.1-20171208.130044-8-dictionary-core.tar.bz2
$ ls
system_core.dic
```

入手した辞書ファイルは、基本的に`$ES_HOME/sudachi/`下に置きます。後述の設定ファイルを書き換えることで、別の場所に設置したり、ファイルの名前を変えたりすることができます。

```bash
$ sudo mkdir /etc/elasticsearch/sudachi
$ sudo mv system_core.dic /etc/elasticsearch/sudachi
```

### プラグインの確認

最後に、Elasticsearchを再起動して、プラグインが読み込まれているか確認してみましょう。`plugins`に`analysis-sudachi`が入っていれば成功です。

```bash
$ sudo /etc/init.d/elasticsearch restart
$ curl -X GET 'http://localhost:9200/_nodes/plugins?pretty'

...
  "plugins" : [
    {
      "name" : "analysis-sudachi",
      "version" : "1.0.0-SNAPSHOT",
      "description" : "The Japanese (Sudachi) Analysis plugin integrates Lucene Sudachi analysis module into elasticsearch.",
      "classname" : "com.worksap.nlp.elasticsearch.sudachi.plugin.AnalysisSudachiPlugin",
      "has_native_controller" : false
    }
  ]
...

```

### インデックスのための設定

これでElasticsearchでSudachiを使う準備が整いました。次にインデックスを作成して、実際に解析結果を見てみましょう。

まず、インデックスを作成するための設定を用意します。ここでは`analysis_sudachi_settings.json`という名前のファイルに以下の内容を書き込みました。

- `tokenizer`
  - `sudachi_tokenizer` を指定
- `mode`
  - `normal`, `search`, `extended` から指定。デフォルトは`search`
  - `normal`: 通常の分割
  - `search`: A単位とC単位
  - `extended`: `search`に加えて未知語のユニグラム
- `discard_punctuation`
  - 句読点を除くか。デフォルトは`true`
- `settings_path`: Sudachiの設定ファイルのパス
  - 指定がなければ、Sudachiに同梱されている [sudachi.json](https://github.com/WorksApplications/Sudachi/blob/develop/src/main/resources/sudachi.json) を利用
  - この設定ファイルで辞書ファイル名などを指定
  - デフォルトでは辞書ファイル名（`systemDict`）は`system_core.dic`となっている
  - `system_full.dic`を利用したり、異なる名前の辞書ファイルを利用する際には、上の`sudachi.json`を書き換えたような設定ファイルを用意し、この`settings_path`で指定
  - 相対パスの場合、後述の`resources_path`からの相対
  - 基本的に`$ES_HOME`以下でないとNG
- `resources_path`: 辞書ファイルのあるディレクトリのパス
  - 指定がなければ、`$ES_HOME/sudachi/`
  - 基本的に`$ES_HOME`以下でないとNG

```json:analysis_sudachi_settings.json
{
  "settings": {
    "index": {
      "analysis": {
        "tokenizer": {
          "sudachi_tokenizer": {
            "type": "sudachi_tokenizer",
            "mode": "search",
	    "discard_punctuation": true,
            "resources_path": "/etc/elasticsearch/sudachi",
            "settings_path": "/etc/elasticsearch/sudachi/sudachi.json"
          }
        },
        "analyzer": {
          "sudachi_analyzer": {
            "filter": [
			],
            "tokenizer": "sudachi_tokenizer",
            "type": "custom"
          }
        }
      }
    }
  }
}
```

### インデックスの作成

この設定を書いた`analysis_sudachi_settings.json`を使って、インデックスを作成します。ここでは`sudachi_test`という名前で作ってみましょう。

```bash
$ curl -X PUT 'http://localhost:9200/sudachi_test/' -d @analysis_sudachi_settings.json
{"acknowledged":true,"shards_acknowledged":true,"index":"sudachi_test"}
```

作成したインデックスが正しく設定されているか見てみましょう。

```bash
$ curl -X GET 'http://localhost:9200/sudachi_test/?pretty'
{
  "sudachi_test" : {
    "aliases" : { },
    "mappings" : { },
    "settings" : {
      "index" : {
        "number_of_shards" : "5",
        "provided_name" : "sudachi_test",
        "creation_date" : "1512992783788",
        "analysis" : {
          "analyzer" : {
            "sudachi_analyzer" : {
              "type" : "custom",
              "tokenizer" : "sudachi_tokenizer"
            }
          },
          "tokenizer" : {
            "sudachi_tokenizer" : {
              "mode" : "search",
              "resources_path" : "/etc/elasticsearch",
              "type" : "sudachi_tokenizer",
              "discard_punctuation" : "true"
            }
          }
        },
        "number_of_replicas" : "1",
        "uuid" : "DBqj21t7STS8ZE2gM32MXw",
        "version" : {
          "created" : "5060199"
        }
      }
    }
  }
}
```

### Sudachiプラグインを使った解析

これでSudachiプラグインの導入が終わりました。実際にどのように解析されるか、見てみましょう。

以下の例では「東京都知事選挙」というテキストを渡していますが、複数の分割単位（"東京都知事選挙"という塊と、"東京/都/知事/選挙"という各部分）が出力されているのが確認できます。これは`search`モードと`system_full`辞書をつかった結果です。分割モードを変えたり、辞書を変更したり後述のフィルターを加えることで、この結果も違うものになります。

```bash
$ curl -X GET 'http://localhost:9200/sudachi_test/_analyze?analyzer=sudachi_analyzer&pretty' -d "東京都知事選挙"
{
  "tokens" : [
    {
      "token" : "東京都知事選挙",
      "start_offset" : 0,
      "end_offset" : 7,
      "type" : "word",
      "position" : 0,
      "positionLength" : 4
    },
    {
      "token" : "東京",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "都",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "知事",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "選挙",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "word",
      "position" : 3
    }
  ]
}
```

### 様々なフィルター

上のようにナマのSudachiを使うだけでなく、用途に応じて様々なフィルターを適用することもできます。以下のフィルターが用意されています。

- `sudachi_part_of_speech`
  - 設定に書かれた品詞のトークンを除外
  - デフォルト設定は [stoptags.txt](https://github.com/WorksApplications/elasticsearch-sudachi/blob/develop/src/main/resources/com/worksap/nlp/lucene/sudachi/ja/stoptags.txt)
- `sudachi_ja_stop`
  - 日本語のストップワードを除外
  - [stopwords.txt](https://github.com/WorksApplications/elasticsearch-sudachi/blob/develop/src/main/resources/com/worksap/nlp/lucene/sudachi/ja/stopwords.txt) でストップワードの一覧を確認可能
- `sudachi_baseform`
  - 動詞と形容詞の終止形化
  - 例: *飲み → 飲む*
- `sudachi_readingform`
  - カタカナ、もしくはローマ字の読みに変換
  - 例1: *寿司 → スシ*
  - 例2: *寿司 → sushi*

これらのフィルターは基本的に、Kuromojiのものとほぼ互換性があるように作成しています。多くの場合、`kuromoji`という部分を`sudachi`と書き換えるだけで動作するでしょう。より詳しい設定方法や利用例は、[READMEの「Corresponding Filter」セクション](https://github.com/WorksApplications/elasticsearch-sudachi#corresponding-filter)をご覧ください。

## おわりに

今回は形態素解析器「Sudachi」と、そのElasticsearchでの利用について紹介しました。

整備された短単位と長単位、語義的・文字的な表記統制、プラグインによる柔軟な拡張など、Sudachiのさまざまな特徴は、Elasticsearchでの用途において大きなメリットを提供することができると思っています。

これからさらにSudachiを強化していきますが、まだまだ知見や利用例、ドキュメントが不足しています。利用してみて、分からない点やご意見ご注文、怪しい挙動などありましたら、ぜひGitHubのIssueを立てたりしていただけると嬉しいです！

ちなみに明日の [Elastic{ON} Tokyo 2017](https://www.elastic.co/elasticon/tour/2017/tokyo) にも徳島から上京して参加します。徳島県のゆるキャラ「すだちくん」のシャツを着て銘菓「すだちパイ」を持っていきますので、見かけたらぜひお声がけください！

![sudachi.jpg](https://qiita-image-store.s3.amazonaws.com/0/6438/a5cca6dd-85b0-45c1-57d3-27a788e2d299.jpeg)


明日12/14の[Elastic stack Advent Calendar 2017](https://qiita.com/advent-calendar/2017/elasticsearch)は、[ZooBonta](https://qiita.com/ZooBonta)さんによるLogstash関連の記事です！

[^1]: 教師なしの形態素解析や単語分割についてはここでは言及しませんが、教師ありとは別に非常にエキサイティングな分野だと思っています！興味のある方は[(持橋+ 2009)](http://chasen.org/~daiti-m/paper/nl190segment.pdf)やその[解説](http://chasen.org/~daiti-m/paper/nl190segment-slides.pdf), [実装](http://musyoku.github.io/2016/12/14/%E3%83%99%E3%82%A4%E3%82%BA%E9%9A%8E%E5%B1%A4%E8%A8%80%E8%AA%9E%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AB%E3%82%88%E3%82%8B%E6%95%99%E5%B8%AB%E3%81%AA%E3%81%97%E5%BD%A2%E6%85%8B%E7%B4%A0%E8%A7%A3%E6%9E%90/)、[(内海+ 2015)](http://www.anlp.jp/proceedings/annual_meeting/2015/pdf_dir/C6-3.pdf)やその[解説](https://tech.d-itlab.co.jp/nlp/420/)、[(Fujii+ 2017)](https://aclanthology.coli.uni-saarland.de/papers/Q17-1013/q17-1013)、そしてデータ工学ロボティクス研究会での[持橋先生による資料](http://chasen.org/~daiti-m/paper/Robotics-20130327.pdf)や、TokyoCLでの[若林先生の資料](https://sites.google.com/site/tokyoclweb/tokyocl-meeting/di9huimianqianghui)などをご参照ください


## （追記） Sudachi 公式 Slack

開発者やユーザーの方々が質問したり議論するためのSlackワークスペースを用意しています。質問や要望等は、ぜひこちらへお寄せください！

https://github.com/WorksApplications/Sudachi#slack-1