ElasticsearchでKuromoji Tokenizerを試す

ElasticsearchでKuromoji Tokenizerを試してみたメモです。

前回、NGram TokenizerでN-Gramを試してみたので、
今回は形態素解析であるKuromoji Tokenizerを試してみました。

Ubuntu上でElasticsearch5.4.0で試してみます。

kuromoji

kuromojiは日本語の形態素解析器です。
N-Gramでは単語をN文字ごとに分割するのに対して、
kuromojiでは辞書を利用して文章を単語に分割します。

インストール・起動

kuromoji tokenizerはデフォルトでは入っていないのでインストールする必要があります。
Japanese (kuromoji) Analysis pluginをインストールします。
Japanese (kuromoji) Analysis pluginには下記のトークナイザとフィルタが入っており、
kuromoji tokenizerが含まれています。

  • kuromoji_tokenizer
  • kuromoji_baseform token filter
  • kuromoji_part_of_speech token filter
  • cjk_width token filter
  • ja_stop token filter
  • kuromoji_stemmer token filter
  • lowercase token filter

elasticsearch-pluginを利用してインストールします。

$ sudo bin/elasticsearch-plugin install analysis-kuromoji
-> Downloading analysis-kuromoji from elastic
[=================================================] 100%
-> Installed analysis-kuromoji

インストール後は再起動する必要があります。

$ ./elasticsearch

プラグインがインストールされたか、_nodes/pluginsのエンドポイントを指定して、確認してみます。
pluginsにanalysis-kuromojiが含まれているのが分かります。

$ curl -X GET 'http://localhost:9200/_nodes/plugins?pretty'
{
  "_nodes" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "cluster_name" : "elasticsearch",
  "nodes" : {
    "7xgQmDgJS1mPi02p2LzUrg" : {
      "name" : "7xgQmDg",
      "transport_address" : "10.0.2.15:9300",
      "host" : "10.0.2.15",
      "ip" : "10.0.2.15",
      "version" : "5.4.0",
      "build_hash" : "780f8c4",
      "roles" : [
        "master",
        "data",
        "ingest"
      ],
      "plugins" : [
        {
          "name" : "analysis-kuromoji",
          "version" : "5.4.0",
          "description" : "The Japanese (kuromoji) Analysis plugin integrates Lucene kuromoji analysis module into elasticsearch.",
          "classname" : "org.elasticsearch.plugin.analysis.kuromoji.AnalysisKuromojiPlugin",
          "has_native_controller" : false
        }
      ],
      "modules" : [
        {
          "name" : "aggs-matrix-stats",
          "version" : "5.4.0",
          "description" : "Adds aggregations whose input are a list of numeric fields and output includes a matrix.",
          "classname" : "org.elasticsearch.search.aggregations.matrix.MatrixAggregationPlugin",
          "has_native_controller" : false
        },
        :
        :
        :
        :

アナライザ設定

アナライザを設定するためのjsonファイルを用意します。

analyzerの項目にはmy_kuromoji_analyzerという名前を設定し、
typeにcustomを設定し、tokenizerにはkuromoji_tokenizerを指定します。

kuromoji_analysis.json

{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_kuromoji_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_tokenizer"
        }
      }
    }
  }
}

kuromoji_analysis.jsonを指定してmy_indexというインデックスを作成。

$ curl -X PUT 'localhost:9200/my_index' -d @kuromoji_analysis.json

インデックスを確認してみると、analyzerのtokenizerの項目に
kuromoji_tokenizerが設定されていることが分かります。

$ curl -X GET 'localhost:9200/my_index/?pretty'
{
  "my_index" : {
    "aliases" : { },
    "mappings" : { },
    "settings" : {
      "index" : {
        "number_of_shards" : "5",
        "provided_name" : "my_index",
        "creation_date" : "1495799529581",
        "analysis" : {
          "analyzer" : {
            "my_kuromoji_analyzer" : {
              "type" : "custom",
              "tokenizer" : "kuromoji_tokenizer"
            }
          }
        },
        "number_of_replicas" : "1",
        "uuid" : "fFuf3TV7SZSseri8U_t6nQ",
        "version" : {
          "created" : "5040099"
        }
      }
    }
  }
}

確認

アナライザを確認するには/_analyzeでanalyze APIのエンドポイントを指定します。
analyzerでmy_kuromoji_analyzerを指定し、textにアナライズ対象の文章を指定します。

実行してみると、日本語の単語ごとに分割されていることが分かります。

$ curl -X POST 'localhost:9200/my_index/_analyze?pretty' -d '{"analyzer": "my_kuromoji_analyzer", "text": "吾輩は猫である。"}'
{
  "tokens" : [
    {
      "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" : 4,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : "ある",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "word",
      "position" : 4
    }
  ]
}

mapping定義

マッピングでフィールドごとにアナライザを設定できます。
titleとsentenceというフィールドを持つnatsumeタイプを定義します。
titleとsentenceフィールドにはそれぞれanalyzerにmy_kuromoji_analyzerを指定します。

mapping.json

{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_kuromoji_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_tokenizer"
        }
      }
    }
  },
  "mappings": {
    "natsume": {
      "properties": {
        "title": {
          "type": "string",
          "index": "analyzed",
          "analyzer": "my_kuromoji_analyzer"
        },
        "sentence": {
          "type": "string",
          "index": "analyzed",
          "analyzer": "my_kuromoji_analyzer"
        }
      }
    }
  }
}

先ほど作成したインデックスを削除

curl -X DELETE 'localhost:9200/my_index'

mapping.jsonを指定してmy_indexを再作成。

$ curl -X PUT 'localhost:9200/my_index/' -d @mapping.json

インデックスを確認してみるとtitleとsentenceフィールドにmy_kuromoji_analyzerが設定されてるのが分かります。

$ curl -X GET 'localhost:9200/my_index/?pretty'
{
  "my_index" : {
    "aliases" : { },
    "mappings" : {
      "natsume" : {
        "properties" : {
          "sentence" : {
            "type" : "text",
            "analyzer" : "my_kuromoji_analyzer"
          },
          "title" : {
            "type" : "text",
            "analyzer" : "my_kuromoji_analyzer"
          }
        }
      }
    },
    "settings" : {
      "index" : {
        "number_of_shards" : "5",
        "provided_name" : "my_index",
        "creation_date" : "1495800487967",
        "analysis" : {
          "analyzer" : {
            "my_kuromoji_analyzer" : {
              "type" : "custom",
              "tokenizer" : "kuromoji_tokenizer"
            }
          }
        },
        "number_of_replicas" : "1",
        "uuid" : "7LAnD9sqTPa1ttdI-vK_YQ",
        "version" : {
          "created" : "5040099"
        }
      }
    }
  }
}

確認

確認用のドキュメントを登録。

curl -X PUT 'localhost:9200/my_index/natsume/1?pretty' -d '
{
  "title": "坊っちゃん",
  "sentence": "親譲りの無鉄砲で小供の時から損ばかりしている。"
}'
curl -X PUT 'localhost:9200/my_index/natsume/2?pretty' -d '
{
  "title": "吾輩は猫である",
  "sentence": "吾輩は猫である。名前はまだ無い。"
}'

matchクエリでsentenceフィールドを指定して検索してみます。

"吾輩"で検索。

$ curl -X GET 'http://localhost:9200/my_index/_search?pretty' -d '{"query":{"match":{"sentence":"吾輩"}}}'

kuromojiで分割されている単語なのでヒットしました。

{
  "took" : 37,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.27233246,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "natsume",
        "_id" : "2",
        "_score" : 0.27233246,
        "_source" : {
          "title" : "吾輩は猫である",
          "sentence" : "吾輩は猫である。名前はまだ無い。"
        }
      }
    ]
  }
}

"吾輩は"で検索。

$ curl -X GET 'http://localhost:9200/my_index/_search?pretty' -d '{"query":{"match":{"sentence":"吾輩が"}}}'

分割された"吾輩"で単語がヒットします。

{
  "took" : 11,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 0.27233246,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "natsume",
        "_id" : "2",
        "_score" : 0.27233246,
        "_source" : {
          "title" : "吾輩は猫である",
          "sentence" : "吾輩は猫である。名前はまだ無い。"
        }
      }
    ]
  }
}

"吾"で検索。

>|json|
$ curl -X GET 'http://localhost:9200/my_index/_search?pretty' -d '{"query":{"match":{"sentence":"吾"}}}'

分割された単語には含まれないのでヒットしません。

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 0,
    "max_score" : null,
    "hits" : [ ]
  }
}

"子猫"で検索。

$ curl -X GET 'http://localhost:9200/my_index/_search?pretty' -d '{"query":{"match":{"sentence":"子猫"}}}'

"子猫"はkuromojiで"子"と"猫"に分割されないので、ヒットしません。

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 0,
    "max_score" : null,
    "hits" : [ ]
  }
}

終わり