kgneng2 / blokg

blog
MIT License
0 stars 0 forks source link

elasticsearch token analyzer #44

Open kgneng2 opened 3 years ago

kgneng2 commented 3 years ago

image

기본적으로 Charfilter 에 의해 공백 콤마등의 문자는 제거 한다.

standard analyzer를 사용한다.

image

standard tokenizer

" I'm in the mood for drinking semi-dry red wine! "
=> [I'm,in,the,mood,for,drinking,semi,dry,red,wine]

letter tokenizer

lowercase => letter + 소문자 변경 진행

{
    "tokenizer" : "lowercase",
    "text" : " I'm in the mood for drinking semi-dry red wine! "
}

edge n gram tokenizer

shingle

req

GET /_analyze
{
  "tokenizer": "whitespace",
  "filter": [ "shingle" ],
  "text": "quick brown fox jumps"
}

"output_unigrams": false : 추가시 한단어 짜리는 안나옴. min_shingle_size, max_shingle_size만 포함

res

[ quick, quick brown, brown, brown fox, fox, fox jumps, jumps ]

token filter

standard token filter

stop token filter

Query

kgneng2 commented 3 years ago
PUT /my_index/default/_mapping
{
  "properties": {
    "description": {
      "type":"text",
      "analyzer":"my_custom_analyzer"
    },
    "teaser":{
      "type":"text",
      "analyzer": "standard"
    }
  }
}

POST /my_index/default/1
{
  "description":":)",
  "teaser":":)"
}

GET /my_index/default/_search
{
  "query": {
    "match": {
      "description":"_happy_"
    }
  }
}
kgneng2 commented 3 years ago

es score 계산

TF-IDF

TF

                      "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
                      "details": [
                        {
                          "value": 1,
                          "description": "termFreq=1.0",
                          "details": [

                          ]
                        },
                        {
                          "value": 1.2,
                          "description": "parameter k1",
                          "details": [

                          ]
                        },
                        {
                          "value": 0.75,
                          "description": "parameter b",
                          "details": [

                          ]
                        },
                        {
                          "value": 6.9182334,
                          "description": "avgFieldLength",
                          "details": [

                          ]
                        },
                        {
                          "value": 4,
                          "description": "fieldLength",
                          "details": [

                          ]
                        }

IDF

"description" : idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
                      "details": [
                        {
                          "value": 448,
                          "description": "docFreq",
                          "details": [

                          ]
                        },
                        {
                          "value": 1012159,
                          "description": "docCount",
                          "details": [

                          ]
                        }
                      ]

TF * IDF 값이 최종 스코어를 의미한다.

Reference

kgneng2 commented 3 years ago

auto complete 조사

Prefix query

Fuzzy query

Match Phrase Prefix

Combine Query

ES autocomplete index 조사

GET _mapping
{
  "autocomplete_test_1" : {
    "mappings" : {
      "properties" : {
        "word" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword"
            }
          },
          "analyzer" : "linguist2_analyzer"
        }
      }
    }
  }
}

prefix 쿼리시

Suggest


두개의 방법이 있는거같다.

  1. tokenize를 해서 filter 및 설정을 진행후, query matching을 통해서 진행한다. ( autocomplte 기능을 제공해줌.)

PUT autocomplete_test_2 { "settings": { "analysis": { "analyzer": { "autocomplete": { "tokenizer": "autocomplete", "filter": [ "lowercase" ] }, "autocomplete_search": { "tokenizer": "lowercase" } }, "tokenizer": { "autocomplete": { "type": "edge_ngram", "min_gram": 1, "max_gram": 20, "token_chars": [ "letter", "digit" ] } } } }, "mappings": { "properties": { "word": { "type": "text", "analyzer": "autocomplete", "search_analyzer": "autocomplete_search" } } } }


2. ES에서 제공해주는 suggest를 활용한다.
kgneng2 commented 3 years ago

Query

bool query

kgneng2 commented 3 years ago

mutli_match에 대해서

operator and minimum_should_match

{
  "multi_match" : {
    "query":      "Will Smith",
    "type":       "best_fields",
    "fields":     [ "first_name", "last_name" ],
    "operator":   "and" 
  }
}

  (+first_name:will +first_name:smith)
| (+last_name:will  +last_name:smith)

첫 번째는 Most_field type은 field마다 Operator, minimum_should_match를 적용하지만 Cross_fields type은 Term마다 적용한다.

두 번째는 관련성이다. 만약 “Will Smith”이라는 이름을 검색한다고 생각해보자. 검색 시 ‘Will’, ‘Smith’이라는 두개의 Terms는 각 각의 last_name, first_name Field에 대해 검색할 것이다. 결과는 ”Smith Jones”가 “Will Smith” 보다 점수가 높을 것이다.

kgneng2 commented 3 years ago

es doc 참고

https://esbook.kimjmin.net/06-text-analysis/6.7-stemming/6.7.2-nori

kgneng2 commented 3 years ago
{
  "explain": true,
  "query": {
    "bool": {
      "must": {
        "match": {
          "CountryCode": "KR"
        }
      },
      "should": [
        {
          "terms": {
            "hotelId": [
              "4757718",
              "4943464",
              "4996410",
              "5257994",
              "5279852",
              "5281864",
              "5281890"
            ]
          }
        },
        {
          "match": {
            "placeKo": "화성시"
          }
        },
        {
          "multi_match": {
            "query": "스타즈호텔 동탄",
            "fields": [
              "nameKo",
              "featuredNameKo",
              "normNameKo",
              "baseNameKo"
            ],
            "type": "cross_fields",
            "minimum_should_match": "100%",
            "analyzer": "query_ko"
          }
        },
        {
          "multi_match": {
            "query": "staz hotel dongtan",
            "fields": [
              "nameEn",
              "featuredNameEn",
              "normNameEn",
              "baseNameEn"
            ],
            "type": "cross_fields",
            "minimum_should_match": "50%",
            "analyzer": "query_en"
          }
        }
      ]
    }
  }
}

이거어떠카냐..

kgneng2 commented 3 years ago

자동완성 매핑 참고

예시 sample

autocomplete index setting 예시..

{
  "settings": {
    "analysis": {
      "filter": {
        "hotel_synonym": {
          "type": "synonym",
          "synonyms_path": "analysis/hotel_synonym.txt"
        }
      },
      "analyzer": {
        "autocomplete": {
          "tokenizer": "autocomplete",
          "filter": [
            "lowercase",
            "hotel_synonym"
          ]
        },
        "autocomplete_search": {
          "tokenizer": "lowercase"
        }
      },
      "tokenizer": {
        "autocomplete": {
          "type": "edge_ngram",
          "min_gram": 1,
          "max_gram": 20,
          "token_chars": [
            "letter",
            "digit"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "word": {
        "type": "text",
        "analyzer": "autocomplete",
        "search_analyzer": "autocomplete_search"
      }
    }
  }
}
kgneng2 commented 3 years ago

tokenizer 를 진행하면 일단 짜름 filter를 하면 tokenizer된거에대해서 map함수를 갈김

req

GET _analyze
{
  "tokenizer": "standard",

  "text": "메리어트 뉴욕"
}

res

{
  "tokens" : [
    {
      "token" : "메리어트",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "<HANGUL>",
      "position" : 0
    },
    {
      "token" : "뉴욕",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "<HANGUL>",
      "position" : 1
    }
  ]
}

req with filter

GET _analyze
{
  "tokenizer": "standard",
  "filter": [
    {
      "type": "edge_ngram",
      "min_gram": 1,
      "max_gram": 20
    }
  ],
  "text": "메리어트 뉴욕"
}

response

{
  "tokens" : [
    {
      "token" : "메",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "<HANGUL>",
      "position" : 0
    },
    {
      "token" : "메리",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "<HANGUL>",
      "position" : 0
    },
    {
      "token" : "메리어",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "<HANGUL>",
      "position" : 0
    },
    {
      "token" : "메리어트",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "<HANGUL>",
      "position" : 0
    },
    {
      "token" : "뉴",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "<HANGUL>",
      "position" : 1
    },
    {
      "token" : "뉴욕",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "<HANGUL>",
      "position" : 1
    }
  ]
}
kgneng2 commented 3 years ago

https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer.html

A search_analyzer setting for non-phrase queries that will remove stop words
A search_quote_analyzer setting for phrase queries that will not remove stop words
kgneng2 commented 3 years ago

단순 n_gram으로 테스트진행

{
  "autocomplete_test_2" : {
    "aliases" : { },
    "mappings" : {
      "properties" : {
        "word" : {
          "type" : "text",
          "analyzer" : "autocomplete",
          "search_analyzer" : "autocomplete_search"
        }
      }
    },
    "settings" : {
      "index" : {
        "number_of_shards" : "1",
        "provided_name" : "autocomplete_test_2",
        "creation_date" : "1619084078796",
        "analysis" : {
          "analyzer" : {
            "autocomplete" : {
              "filter" : [
                "lowercase"
              ],
              "tokenizer" : "autocomplete"
            },
            "autocomplete_search" : {
              "tokenizer" : "lowercase"
            }
          },
          "tokenizer" : {
            "autocomplete" : {
              "token_chars" : [
                "letter",
                "digit"
              ],
              "min_gram" : "1",
              "type" : "edge_ngram",
              "max_gram" : "20"
            }
          }
        },
        "number_of_replicas" : "1",
        "uuid" : "-8FO42iGTzyvqlEuurgttw",
        "version" : {
          "created" : "7100299"
        }
      }
    }
  }
}

query

GET autocomplete_test_2/_search
{
  "explain": true, 
  "query" :{
    "match": {
      "word": "여성 트레인"
    }
  }
}

여성속옷이 더 높은 결과를 도출, 이유는 "dl, length of field", 이 값 수치가 여성트레이닝복 보다 작기때문 오타로 인한(?) 또는 자모분리가 안된 ngram 분리일 경우 발생하는 현상입니다.

해결책은 fuziness를 도입하거나, 한글 자모 분리값을 이용해 보도록합니다.

kgneng2 commented 3 years ago

search analzyer


  | Analysis settings to define the custom autocomplete analyzer.
  | The text field uses the autocomplete analyzer at index time, but the standard analyzer at search time.
  | This field is indexed as the terms: [ q, qu, qui, quic, quick, b, br, bro, brow, brown, f, fo, fox ]
  | The query searches for both of these terms: [ quick, br ]
kgneng2 commented 3 years ago

https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-match-bool-prefix-query.html

match-bool-prefix

GET /_search
{
  "query": {
    "match_bool_prefix" : {
      "message" : "quick brown f"
    }
  }
}
GET /_search
{
  "query": {
    "bool" : {
      "should": [
        { "term": { "message": "quick" }},
        { "term": { "message": "brown" }},
        { "prefix": { "message": "f"}}
      ]
    }
  }
}

analyzer는 기본 매핑된 analyzer를 이용함니다.