【Elasticsearch 入門3】Analyzer の設定と日本語の全部検索

本記事は Elasticsearch 入門記事の第3回「Analyzer の設定」です。

その他の Elasticsearch の使い方は以下の記事をご覧ください。

  1. Elasticsearch とは (概要/できること/検索方法)
  2. マッピング管理とテンプレート
  3. Analyzer の設定 (日本語検索、トークナイズ)
  4. 集計・分類 (Aggregations)
  5. Dynamic index settings の変更
  6. データ構造
スポンサーリンク

Analyzer とは

Analyzer とはAnalyzer とは、テキストを分析 (転置インデックスを作成) するルールです。

Analyzer は以下の3つの要素によって構成されます。

Analyzer の要素説明
Char Filterドキュメントを前処理 (加工、フィルタリング)
Tokenizer Filterドキュメントをトークン(単語)に分割
Token filterトークン (単語) のフィルタリング

Char Filter とは

Char Filter では、ドキュメントの前処理 (加工、フィルタリング) をします。
Char Filter の種類説明
HTML Strip Character FilterHTML タグを除去<p>タグを除去
<p>Elasticsearch</p> → Elasticsearch
Mapping character filterマッピングを作成アラビア数字をラテン数字にマッピング
[٠‎١٢٣٤٥٦٧٨‎٩]‎ → [0123456789]
Pattern replace character filter正規表現で置換ハイフンをアンダースコアに置換
[123-456-789] → [123_456_789]

Char Filter の使い方

GET /_analyze
{
  "char_filter" : ["html_strip"],
  "text" : "<p>Elasticsearch is simple</p>"
}
      "token" : """Elasticsearch is simple""",

<p> タグが除去できました。

Tokenizer とは

Tokenizer では、ドキュメントをトークナイズします。(トークン [=通常は単語] に分割)
Tokenizer の種類説明
■単語指向分割
(形態素解析)
Standard Tokenizer
Letter Tokenizer
Lowercase Tokenizer
Whitespace Tokenizer
UAX URL Email Tokenizer
Classic Tokenizer
Thai Tokenizer
空白等で単語を分割Elasticsearch is simple
→ [Elasticsearch, is, simple]
■N-gram 分割
(Partial Words)
N-Gram Tokenizer
Edge N-Gram Tokenizer
N 文字で単語を分割N-Gram: quick
→ [qu, ui, ic, ck].

Edge N-Gram: quick
→ [q, qu, qui, quic, quick]
■構造化テキスト分割
Keyword Tokenizer
Pattern Tokenizer
Simple Pattern Tokenizer
Char Group Tokenizer
Simple Pattern Split Tokenizer
Path Tokenizer
一定規則で単語を分割/foo/bar/baz
→ [/foo, /foo/bar, /foo/bar/baz ]

形態素解析とは

形態素解析とは、ドキュメント(文章) を形態素 (≒単語) に分割することです。

形態素解析には、主に次の2種類があります。

  • 空白で分割
  • 辞書/ラティスで分割
空白で分割

空白でドキュメント (文章) をトークン (単語) に分割する方法です。

Elasticsearch is simple --> [Elasticsearch], [is], [simple]

日本語の場合は、空白で文章が区切られないので、後述する「辞書/ラティス」で分割します。

辞書/ラティスで分割
  • 辞書とは、単語と品詞の一覧です。
  • ラティスとは、全ての単語の組み合わせをグラフにしたものです。
https://techlife.cookpad.com/entry/2016/05/11/170000#f-f30ff2bc

以下の2つのコストの合計が最も低い経路を選択します。

  • 生起コスト:単語の出現しやすさ (例: 「アベック」より「カップル」の方がよく言う)
  • 連接コスト:2つの単語の繋がりやすさ (動詞の後は格助詞が来ない [例: ドアを開ける"を"])

なお、辞書に載ってない単語の検索漏れが発生する欠点があります。

N-gram とは

N-gram とは、ドキュメント (文章) を N 文字で分割する方法です。
  • 2-gram (bigram) の場合:quick → [qu, ui, ic, ck]
  • 3-gram (trigram) の場合:quick → [qui, uic, ick]

N-gram は、辞書に載ってない単語でも検索できる一方で、検索ノイズが大きくなります。
(2-gram の場合、ice → [ic, ce] で quick → [qu, ui, ic, ck] がヒットするなど。)

Tokenizer の使い方

文字列を空白でトークナイズします。

GET /_analyze
{
 "tokenizer": "whitespace",
 "text": "Elasticsearch is simple"
}
  "tokens" : [
    {
      "token" : "Elasticsearch",
      "start_offset" : 0,
      "end_offset" : 13,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "is",
      "start_offset" : 14,
      "end_offset" : 16,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "simple",
      "start_offset" : 17,
      "end_offset" : 23,
      "type" : "word",
      "position" : 2
    }

[Elasticsearch is simple] を [Elasticsearch], [is], [simple] の3つのトークンに分割できました。

Token filter とは

Token filter では、不要なトークン(通常は単語) をフィルタリングします。
Token Filter の種類説明
Lower case Token Filterトークンを小文字に変換[Google] → [google]
Stop Token Filterストップワードを設定[Elasticsearch is simple]
→ [Elasticsearch, simple]
Stemmer Token Filterステミングを設定[swims], [swimming], [swimmer]
→ [swim]
Synonym Token Filterシノニムを設定[jump], [leap] → [jump]

ストップワードとは

ストップワードとは、検索対象から指定した単語を除外することです。
冠詞 (a, an, the) や前置詞 (to, of, at) を除外します。

例:[Elasticsearch is simple] → [Elasticsearch, simple]

ステミングとは

ステミングとは、語幹 (語尾が変化する語の、変化しない部分) で検索する方法です。
語尾が異なる単語でも検索にヒットするようにします。

例:[swims], [swimming], [swimmer] → [swim]

シノニムとは

シノニムとは、異なる単語を同じ単語とみなすことです。
同義語の検索などで利用します。

例:[jump], [leap] → [jump]

Token filter の使い方

ストップワードで is をフィルタリングします。

GET /_analyze
{
  "tokenizer": "standard",
  "filter": [ "stop" ],
  "text": "Elasticsearch is simple"
}
  "tokens" : [
    {
      "token" : "Elasticsearch",
      "start_offset" : 0,
      "end_offset" : 13,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "simple",
      "start_offset" : 17,
      "end_offset" : 23,
      "type" : "<ALPHANUM>",
      "position" : 2
    }

ストップワードのデフォルトの設定に従って、"is" がフィルタリングされています。

When not customized, the filter removes the following English stop words by default:

a, an, and, are, as, at, be, but, by, for, if, in, into, is, it, no, not, of, on, or, such, that, the, their, then, there, these, they, this, to, was, will, with

https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-tokenfilter.html#analysis-stop-tokenfilter

Analyzer の種類と一覧

Analyzer には以下の2種類があります。

Analyzer の種類説明
Built-in analyzerChar Filter, Tokenizer, Token Filter が事前に定義されている
Custom analyzerChar Filter, Tokenizer, Token Filter を自分で定義する

Elasticsearch に事前に組み込まれている Built-in analyzer の一覧は以下のとおりです。

Built-in analyzer reference | Elasticsearch Guide [8.12] | Elastic

Analyzer の設定を何もしない場合、Standard Analyzer を使用します。

GET /_analyze
{
  "analyzer": "whitespace",
  "text": "Elasticsearch is simple"
}
    {
      "token" : "Elasticsearch",
      "start_offset" : 0,
      "end_offset" : 13,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "is",
      "start_offset" : 14,
      "end_offset" : 16,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "simple",
      "start_offset" : 17,
      "end_offset" : 23,
      "type" : "word",
      "position" : 2
    }

Built-in analyzer の設定

インデックス (built-in) の text フィールドに、Built-in analyzer (Whitespace analyzer) を設定してみます。

PUT built-in
{
  "mappings": {
    "properties": {
      "text": {
        "type":     "text",
        "analyzer": "whitespace"
      }
    }
  }
}
Built-in analyzer のトークナイズ結果を確認
GET built-in/_analyze
{
  "analyzer": "whitespace",
  "text": "Elasticsearch is simple"
}
  "tokens" : [
    {
      "token" : "Elasticsearch",
      "start_offset" : 0,
      "end_offset" : 13,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "is",
      "start_offset" : 14,
      "end_offset" : 16,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "simple",
      "start_offset" : 17,
      "end_offset" : 23,
      "type" : "word",
      "position" : 2
    }

空白単位でトークンを生成していることが確認できます。

設定した Built-in analyzer で検索
PUT built-in/_doc/1
{
  "text":"Elasticsearch is simple"
}
GET built-in/_search
{
  "query": {
    "match": {
      "text": "Elasticsearch"
    }
  }
}
    "hits" : [
      {
        "_index" : "built-in",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2876821,
        "_source" : {
          "text" : "Elasticsearch is simple"

空白単位の単語で検索できることが確認できます。

Custom analyzer の設定

以下のような Custom analyzer を作成してみます。

Analyzer の要素構成やりたいこと
Char FilterHTML strip character filterドキュメントから <p> タグを除去
TokenizerWhitespace tokenizerドキュメントを空白でトークン (単語) に分割
Token filterStop token filter
Lowercase token filter
トークンから "This", "is" を除去
大文字のトークンを小文字にする

インデックス (custom) の text フィールドに、Custom Analyzer (my_custom_analyzer) を設定します。

PUT custom
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_custom_analyzer": {
          "type": "custom",
          "char_filter": "html_strip",
          "tokenizer": "whitespace",
          "filter": ["lowercase","stop"]
        }
      }
    }
  },  
  "mappings": {
    "properties": {
      "text": {
        "type":     "text",
        "analyzer": "my_custom_analyzer"
      }
    }
  }
}
Custom analyzer のトークナイズ結果を確認
POST custom/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text": "<p>This is Elasticsearch</p>"
}
{
  "tokens" : [
    {
      "token" : "elasticsearch",
      "start_offset" : 11,
      "end_offset" : 24,
      "type" : "word",
      "position" : 2

Custom analyzer (my_custom_analyzer) で設定したとおり、HTML タグは消え、"This", "is" トークンは削除され、"Elasticsearch" トークンは小文字になっています。

設定した Custom analyzer で検索
PUT custom/_doc/1
{
  "text":"<p>Elasticsearch is simple</p>"
}
GET custom/_search
{
  "query": {
    "match": {
      "text": "elasticsearch"
    }
  }
}
    "hits" : [
      {
        "_index" : "custom",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2876821,
        "_source" : {
          "text" : "

Elasticsearch is simple

"

Elasticsearch が小文字でも正しく検索できています。

スポンサーリンク

kuromoji analyzer (日本語検索)

kuromoji analyzer とは

kuromoji analyzer とは、日本語をトークンに分割する Analyzer です。

デフォルトの Standard Analyzer では、日本語をうまくトークンに分割できないため、日本語を適切に検索できません。

Standard Analyzer を使用して日本語を検索した場合

POST _analyze
{
  "analyzer": "standard",
  "text": "東京都に行く"
}

日本語には単語の間にスペースがないため、1文字ずつトークンナイズされます。

  "tokens" : [
    {
      "token" : "東",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "",
      "position" : 0
    },
    {
      "token" : "京",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "",
      "position" : 1
    },
    {
      "token" : "都",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "",
      "position" : 2
    },
    {
      "token" : "に",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "",
      "position" : 3
    },
    {
      "token" : "行",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "",
      "position" : 4
    },
    {
      "token" : "く",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "",
      "position" : 5
    }

1文字ずつトークンに分割することの何が問題かというと、1文字でも同じ文字が含まれると検索にヒットします。

例えば、「か"に"」は、「東京"に"行く」というドキュメントにヒットします。

PUT standard/_doc/1
{
  "text":"東京都に行く"
}
GET standard/_search
{
  "query": {
    "match": {
      "text": "かに"
    }
  }
}
    "hits" : [
      {
        "_index" : "standard",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.18232156,
        "_source" : {
          "analyzer" : "standard",
          "text" : "東京都に行く"

そのため、日本語を検索する場合は、kuromoji Analyzer を使用します。

なお、kuromoji Analyzer は以下の要素から構成されます。

Analyzerkuromoji Analyzer の構成要素
Char FilterCJKWidthCharFilter (Apache Lucene)
Tokenizerkuromoji_tokenizer
Token Filterkuromoji_baseform token filter 
kuromoji_part_of_speech token filter
ja_stop token filter
kuromoji_stemmer token filter
lowercase token filter

kuromoji Analysis Plugin をインストール

kuromoji Analyzer を利用するためには、Elasticsearchkuromoji Analysis Plugin をインストールする必要があります。

docker を利用しない方法

sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji
sudo systemctl restart elasticsearch
FROM docker.elastic.co/elasticsearch/elasticsearch:7.12.1
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji
version: '3'
services:
  elasticsearch:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200
  kibana:
    image: kibana:7.12.1
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200

※既に Elasticsearch, kibana コンテナを起動してる場合は、docker-compose down を実行してください。

kuromoji Analyzer の使い方

まずは、インデックス (demo_analyzer) に kuromoji Analyzer を設定します。

PUT demo_analyzer
{
  "mappings": {
    "properties": {
      "text":{
        "type": "text",
        "analyzer": "kuromoji"
      }
    }
  }
}
kuromoji Analyzer のトークナイズ結果

トークナイズの結果を見ると、日本語らしくトークナイズされています。

POST _analyze
{
  "analyzer": "kuromoji",
  "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" : 4,
      "end_offset" : 6,
      "type" : "word",
      "position" : 3
    }

トークナイズ結果から、「京都」の検索ノイズとして「東京都」を回避できます。

kuromoji Analyzer で検索

次に demo_analyzer インデックスでドキュメントを作成し、日本語検索を行います。

PUT demo_analyzer/_doc/1
{
  "text":"東京都に行く"
}
GET demo_analyzer/_search
{
  "query": {
    "match": {
      "text": "東京"
    }
  }
}
    "hits" : [
      {
        "_index" : "demo_analyzer",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.2876821,
        "_source" : {
          "text" : "東京都に行く"
        }
      }

無事に日本語で検索ができるようになりました。

スポンサーリンク

関連情報

関連記事

  1. Elasticsearch とは (概要/できること/検索方法)
  2. マッピング管理とテンプレート
  3. Analyzer の設定 (日本語検索、トークナイズ)
  4. 集計・分類 (Aggregations)
  5. Dynamic index settings の変更
  6. データ構造