<備忘録> Elasticsearch、Kibana、Sudachi環境を構築する

# はじめに
Elasticsearch、Kibana、Sudachiをゼロから構築する手順を記します。
また、Sudachiのプラグインの設定を変更し、トークナイズの挙動を変える手順まで記します。

# 環境
| 環境 | バージョン |
| --- | --- |
| OS | Ubuntu 18.04.3 LTS |
| Java | OpenJDK 1.8.0_232 |
| Elasticsearch | 7.5.1 |
| Kibana | 7.5.1 |

# Elasticsearchのインストール

### カーネルパラメータを変更しておく
Elasticsearchが利用可能なメモリマップを増やしておきます。

```
$ sudo vi /etc/sysctl.d/99-sysctl.conf
vm.max_map_count = 262144

$ sudo sysctl --system
```

### Elasticsearchのダウンロードと展開
公式サイトでElasticsearchを[ダウンロード](https://www.elastic.co/jp/downloads/elasticsearch)します。
あるいはwgetで直接入手可能です。

```
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.5.1-linux-x86_64.tar.gz
```

ダウンロードが完了したら解凍・配置します。
配置先は`/opt/elasticsearch`としました。
root権限では起動できないようで、当該ディレクトリの所有者を適切なユーザに設定しておきます。

```
$ tar xvzf elasticsearch-7.5.1-linux-x86_64.tar.gz
$ sudo mv elasticsearch-7.5.1 /opt/elasticsearch
$ sudo chown -R /opt/elasticsearch [GROUP:USER]
```

### 環境変数の設定
Elasticsearchのルートディレクトリを環境変数`ES_HOME`に設定します。

```
export ES_HOME=/opt/elasticsearch
```

### Elasticsearchの設定を変更する
ここではシングルノードで動作させ、ローカル以外からもアクセス可能とするように設定します。

```
$ vi /opt/elasticsearc/config/elasticsearch.yml
network.host: 0.0.0.0
discovery.seed_hosts: ["127.0.0.1", "[::1]"]
discovery.type: single-node
```

### Elasticsearchの起動

Elasticsearchの起動は以下の通りです。-dオプションでバックグラウンドで動作します。

```
$ cd /opt/elasticsearch
$ ./bin/elasticsearch -d -p pid
```

Webブラウザで `http://[ホスト]:9200` にアクセスすると、以下のように表示されます。

```
{
  "name" : [サーバ名],
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "dudQ9w_mR9i3_JbJOod7Zw",
  "version" : {
    "number" : "7.5.1",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "3ae9ac9a93c95bd0cdc054951cf95d88e1e18d96",
    "build_date" : "2019-12-16T22:57:37.835892Z",
    "build_snapshot" : false,
    "lucene_version" : "8.3.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}
```

また、`http://[ホスト]:9200/_cat/health?v` にアクセスすると、クラスタの状況を確認できます。

```
epoch      timestamp cluster       status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1577875702 10:48:22  elasticsearch yellow          1         1      4   4    0    0        1             0                  -                 80.0%
```

# Kibanaのインストール
### Kibanaのダウンロードと展開
公式サイトでKibanaを[ダウンロード](https://www.elastic.co/jp/downloads/kibana)します。
あるいはwgetで直接入手可能です。

```
$ wget https://artifacts.elastic.co/downloads/kibana/kibana-7.5.1-linux-x86_64.tar.gz
```

ダウンロードが完了したら解凍・配置します。
配置先は`/opt/kibana`としました。

```
$ tar xvzf kibana-7.5.1-linux-x86_64.tar.gz
$ sudo mv kibana-7.5.1-linux-x86_64 /opt/kibana
```

### Kiabanaの設定を変更する
KibanaとElasticsearchを接続し、リモートからもアクセス可能に設定します。

```
$ sudo vi /opt/kibana/config/kibana.yml
server.host: "0.0.0.0"
elasticsearch.hosts: ["http://[Elasticsearchのホスト]:9200"]
```

### Kibanaの起動
Kibanaを起動し、Webブラウザでアクセスしてみます。

```
$ /opt/kibana/bin/kibana
```

`http://[ホスト]:5601` にアクセスするとKibanaの画面が表示されます。

左側のツールバーの「Dev Tools」にアクセスするとクエリを実行できる「Console」が表示されます。
Consoleに以下のクエリを入力・実行し動作確認します。

```
get _cat/health?v
```

右側のペインに以下のように出力されます。

```
epoch      timestamp cluster       status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1577881593 12:26:33  elasticsearch green           1         1      3   3    0    0        0             0                  -                100.0%
```

# Sudachiのインストール
Elasticsearch用のプラグインをGithubから入手します。

```
$ git clone https://github.com/WorksApplications/elasticsearch-sudachi.git
```

プラグインをMavenでパッケージングします。この時、Elasticsearchのバージョンをpom.xmlに記述します。
パッケージは`target/release/ディレクトリ配下`に作成されます。

```
$ cd elasticsearch-sudachi
$ vi pom.xml
<elasticsearch.version>7.5.1</elasticsearch.version>

$ mvn package
```

無事に成功すると、`BUILD SUCCESS`と表示されます。
作成されたプラグインをElasticserachにインストールします。インストールするファイルはコンソールの最後に表示されるZIPファイルです。
`[INFO] Building zip: /somewhere/elasticsearch-sudachi/target/releases/analysis-sudachi-elasticsearch7.5-1.3.3-SNAPSHOT.zip`


```
$ /opt/elasticsearch/bin/elasticsearch-plugin install file:///[プラグインのパッケージファイルパス(zip)]

[=================================================] 100%??
-> Installed analysis-sudachi
```

正しくインストールされているか確認します。

```
$ /opt/elasticsearch/bin/elasticsearch-plugin list
analysis-sudachi
```

# Sudachiの辞書のインストール
Sudachiの辞書を入手し、設定します。辞書は[公式サイト](https://github.com/WorksApplications/SudachiDict)から入手できます。
Sudachiの辞書にはsmall, core, fullの3種類がありますが、ここではfullを使用します。

Sudachiの辞書や設定は以下のフォルダに格納することにします。
`/opt/elasticsearch/config/sudachi_tokenizer`

```
# 辞書をダウンロードして解凍する。
$ wget https://object-storage.tyo2.conoha.io/v1/nc_2520839e1f9641b08211a5c85243124a/sudachi/sudachi-dictionary-20191224-full.zip
$ unzip sudachi-dictionary-20191224-full.zip

# Sudachiの辞書や設定用のディレクトリを作成し、辞書ファイルをコピーする。
$ mkdir /opt/elasticsearch/config/sudachi_tokenizer
$ cp sudachi-dictionary-20191224/system_full.dic /opt/elasticsearch/config/sudachi_tokenizer
```

Sudachiの設定にはプラグイン以外にSudachi本体が必要になるため、Githubから入手します。
そして、fullの辞書ファイル用の設定ファイル(json)をコピーします。

```
$ git clone https://github.com/WorksApplications/Sudachi.git
$ cp Sudachi/src/main/resources/sudachi_fulldict.json /opt/elasticsearch/config/sudachi_tokenizer/
```

ここで一度、ElasticsearchとKibanaを再起動しておきます。
Elasticsearchの停止と起動の例は以下の通りです。
本手順ではKibanaはフロントで動作していると思いますのでCTRL+Cで停止し、`/opt/kibana/bin/kibana`を実行します。

```
$ cd /opt/elasticsearch
$ kill -9 `cat pid`
$ ./bin/elasticsearch -d -p pid
```

# Sudachiの動作確認
Sudachiが正しく動作するか確認します。ここではKibanaを使って、テスト用のインデックスを作成してみます。
WebブラウザでKibana(`http://[Kibanaホスト]:5601`)にアクセスし、Dev ToolsのConsoleを表示します。

### Elasticsearchのインデックス登録
ElasticsearchのインデックスはRDBにおけるテーブルに相当するものです。
ここではSudachiの動作確認用のインデックスを登録してみます。

Consoleに以下のようなクエリを貼り付けて実行すると、インデックスが作成されます。

```
PUT /test_sudachi
{
  "settings": {
    "number_of_shards": "1",
    "analysis": {
      "analyzer": {
        "sudachi_analyzer": {
          "filter": [],
          "type": "custom",
          "tokenizer": "sudachi_tokenizer"
        }
      },
      "tokenizer": {
        "sudachi_tokenizer": {
          "mode": "search",
          "type": "sudachi_tokenizer",
          "discard_punctuation": "true",
          "resources_path": "/opt/elasticsearch/config/sudachi_tokenizer/",
          "settings_path": "/opt/elasticsearch/config/sudachi_tokenizer/sudachi_fulldict.json"            
}}}}}
```

右ペインに以下のように表示されれば成功です。

```
{ "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "test_sudachi" }
```

また、以下のクエリを実行すると当該インデックスの設定情報が表示されます。

```
GET test_sudachi
```

### Sudachiで分かち書きしてみる
実際に適当な文字列を分かち書きしてみます。Consoleに以下のクエリを入力して実行します。

```
GET /test_sudachi/_analyze
{ "analyzer":"sudachi_analyzer",
  "text":"私は青いペンを持っている。しかし、赤いペンは持っていない。" }
```

右ペインに分かち書きした結果が出力されます。

```
{ "tokens" : [
  { "token" : "私", "start_offset" : 0, "end_offset" : 1, "type" : "word", "position" : 0  },
  { "token" : "は", "start_offset" : 1, "end_offset" : 2, "type" : "word", "position" : 1  },
  { "token" : "青い", "start_offset" : 2, "end_offset" : 4, "type" : "word", "position" : 2  },
  { "token" : "ペン", "start_offset" : 4, "end_offset" : 6, "type" : "word", "position" : 3  },
  { "token" : "を", "start_offset" : 6, "end_offset" : 7, "type" : "word", "position" : 4  },
  { "token" : "持っ", "start_offset" : 7, "end_offset" : 9, "type" : "word", "position" : 5  },
  { "token" : "て", "start_offset" : 9, "end_offset" : 10, "type" : "word", "position" : 6  },
  { "token" : "いる", "start_offset" : 10, "end_offset" : 12, "type" : "word", "position" : 7  },
  { "token" : "しかし", "start_offset" : 13, "end_offset" : 16, "type" : "word", "position" : 8  },
  { "token" : "赤い", "start_offset" : 17, "end_offset" : 19, "type" : "word", "position" : 9  },
  { "token" : "ペン", "start_offset" : 19, "end_offset" : 21, "type" : "word", "position" : 10  },
  { "token" : "は", "start_offset" : 21, "end_offset" : 22, "type" : "word", "position" : 11  },
  { "token" : "持っ", "start_offset" : 22, "end_offset" : 24, "type" : "word", "position" : 12  },
  { "token" : "て", "start_offset" : 24, "end_offset" : 25, "type" : "word", "position" : 13  },
  { "token" : "い", "start_offset" : 25, "end_offset" : 26, "type" : "word", "position" : 14  },
  { "token" : "ない", "start_offset" : 26, "end_offset" : 28, "type" : "word", "position" : 15  }
]}
```

# Sudachiの設定変更
Sudachiはプラグインによってトークナイズの挙動を変更できます。
ここでは、以下の2つのプラグインを使ってみます。

* 除外する品詞の設定 (sudachi_part_of_speech)
* 動詞と形容詞の終止形化 (sudachi_baseform)

Sudachiの挙動を変更するには、該当のインデックスの設定をREST-APIで変更する必要があります。

### インデックスの変更の流れ
インデックスの設定を変更する流れは以下の通りです。

* インデックスのクローズ
* 設定変更
* インデックスのオープン

### インデックスのクローズ
`/[インデックス]/_close`をPOSTで実行します。

KibanaのConsoleで以下を実行するとクローズします。

```
POST /test_sudachi/_close
```

結果は以下の通りです。

```
{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "indices" : {
    "test_sudachi" : {
      "closed" : true
}}}
```

### インデックスの設定変更
該当のインデックスをクローズしたらインデックスの設定を変更します。
インデックスの更新は`/[インデックス]/_settings`をPUTで実行します。

以下はSudachiのプラグインとして`sudachi_baseform`と`sudachi_part_of_speech`を使用するように変更しています。
また、`sudachi_part_of_speech`では、除外する品詞として`"接続詞","助動詞","助詞","記号","補助記号","接続詞"`を指定しています。

なお指定できる品詞は、Elasticsearchプラグインの以下のファイルを参照して下さい。

`elasticsearch-sudachi/src/main/resources/com/worksap/nlp/lucene/sudachi/ja/stoptags.txt`

```
PUT /test_sudachi/_settings
{
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "sudachi_analyzer": {
            "filter": [
              "sudachi_baseform",
              "my_posfilter"
	    ],
            "tokenizer": "sudachi_tokenizer",
            "type": "custom"
          }
        },
        "filter":{
         "my_posfilter":{
          "type":"sudachi_part_of_speech",
          "stoptags":[
            "接続詞","助動詞","助詞","記号","補助記号","接続詞"
            ]
}}}}}}
```

結果は以下の通りです。

```
{ "acknowledged" : true }
```

### インデックスのオープン

結果は以下の通りです。

```
{ "acknowledged" : true,
  "shards_acknowledged" : true }
```

### 分かち書きしてみる
先ほどと同じ文章を分かち書きしてみます。

```
GET /test_sudachi/_analyze
{ "analyzer":"sudachi_analyzer",
  "text":"私は青いペンを持っている。しかし、赤いペンは持っていない。" }
```

結果は以下の通りです。
動詞が原形になっており、接続詞など除外した単語は出力されていません。

```
{ "tokens" : [
  {"token" : "私", "start_offset" : 0, "end_offset" : 1, "type" : "word", "position" : 0 },
  {"token" : "青い", "start_offset" : 2, "end_offset" : 4, "type" : "word", "position" : 2 },
  {"token" : "ペン", "start_offset" : 4, "end_offset" : 6, "type" : "word", "position" : 3 },
  {"token" : "持つ", "start_offset" : 7, "end_offset" : 9, "type" : "word", "position" : 5 },
  {"token" : "いる", "start_offset" : 10, "end_offset" : 12, "type" : "word", "position" : 7 },
  {"token" : "赤い", "start_offset" : 17, "end_offset" : 19, "type" : "word", "position" : 9 },
  {"token" : "ペン", "start_offset" : 19, "end_offset" : 21, "type" : "word", "position" : 10 },
  {"token" : "持つ", "start_offset" : 22, "end_offset" : 24, "type" : "word", "position" : 12 },
  {"token" : "いる", "start_offset" : 25, "end_offset" : 26, "type" : "word", "position" : 14 }
]}
```

### インデックスの削除
最後に作成したインデックスの削除です。
インデックスの削除は`/[インデックス]/?pretty`をDELETEで実行します。

```
DELETE /test_sudachi?pretty
```

結果は以下の通りです。

```
{ "acknowledged" : true }
```

# まとめ
Elasticksearch、Kibana、Sudachiをゼロから構築する手順を記しました。
ElasticsearchのREST-APIを使用してインデックスの設定を変更し、Sudachiプラグインによってトークナイズの挙動を変更してみました。