유사도 검색

텍스트 분석의 기초

텍스트 데이터 분석으 숫자, 날짜, 시간과 같은 다른 타입의 데이터 분석과는 다르다.

문자열 타입으로 지정하거나 종종 해당 필드에 정확히 일치하는 쿼리를 수행할 수 있다.

비구조화된 텍스트 분석에 대해 분석

텍스트 타입을 가진 모든 필드는 분석기를 통해분석된다.

  • 일래스틱서치 분석기 이해
  • 내장형 분석기 사용
  • 맞춤형 분석기로 자동완성 구현

일래스틱서치 분석기 이해

일래스틱 서치는 역색인 구조를 사용한다.

분석기는 도큐먼트와 도큐먼트에 속한 각 필드를 가져와 용어를 추출하여, 검색할수있도록 색인되고, 색인된 용어는 턱정 검색어로 도큐먼트를 찾을때 결과로 포함된다.

분석기가 수행하는 시점

  • 색인 시점
  • 검색 시점

분석기의 핵심 : 도큐먼트 필드를 분석하고 실제 색인을 만드는일

도큐먼트 색인전에 텍스트 타입을 가진 모든 분석이 필요하다.

분석기의 구성요소

  • 문자필터
  • 토크나이저
  • 토큰필터

문자필터(char mapping filter)

입력필드의 문자를 추가, 삭제, 변경 가능

문자 필터는 분석기 과정의 맨앞에 위치한다.

그래서 :). , :( :D. 와 같은 이모티콘들을 텍스트로 변환을 할수있다.

토크나이저

분석기는 정확히 하나의 토크나이저를 갖고있다. 문자열을 받아 토큰스트림을 생성하는 역할을 한다.

이렇게 토큰은 역색인을 만들때 사용된다.

표준 토크나이저는 문자열을 공백문자와, 구두점을 기반으로 분해한다.

POST _analyze
{
  "tokenizer": "standard",
  "text" : "tokenizer breaks character into token!"
}

결과

{
  "tokens" : [
    {
      "token" : "tokenizer",
      "start_offset" : 0,
      "end_offset" : 9,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "breaks",
      "start_offset" : 10,
      "end_offset" : 16,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "character",
      "start_offset" : 17,
      "end_offset" : 26,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "into",
      "start_offset" : 27,
      "end_offset" : 31,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "token",
      "start_offset" : 32,
      "end_offset" : 37,
      "type" : "<ALPHANUM>",
      "position" : 4
    }
  ]
}

Start_offset, end_offset 항목을 확인이 가능하고 토큰필터가 있는 경우 추가로 처리 가능

토큰필터

모든 토큰 필터는 수신한 입력 토큰 스트림에 있는 토큰의 추가, 삭제, 변경이 가능하다.

분석기는 여러 토큰필터를 가질 수 있으며, 각 토큰 필터의 결과는 모든 토큰필터가 동작할 때까지 다음 토큰필터로 전송된다.

내장 토큰필터에는 아래와 같음

소문자 토큰필터(Lowercase Token Filter): 입력 데이터의 모든 토큰을 소문자로 치환

불용어 토큰필터(Stop Token Filter) : 문맥에서 의미 없는 단어를 제거

영어 같은 경우에는 is, a , an, the 와 같은 관사, 전치사, 조사, 접속사 등

내장형 분석기 사용

자주 사용하는 분석기에는

  • 표준분석기
  • 언어분석기
  • 공백분석기

표준 기본 동작방식

PUT index_standard_analyzer
{
  "settings": {
    "analysis": {
      "analyzer": {
        "std": {
          "type": "standard"
        }
      }
    }
  },
  "mappings":{
    "my_type": {
      "properties":{
        "my_text":{
          "type":"text",
          "analyzer": "std"
        }
      }
    }
  }
}
  1. std라는 이름을 가진 분석기를 명시적으로 정의
  2. 분석기 타입은 standard로 설정 하고 다른추가구성을 하지 않음
  3. 인덱스에 my_type 이라는 타입을 지정
  4. my_text 라는 필드에 필드 레벨 분석기를 명시

색인 테스트

POST index_standard_analyzer/_analyze 
{
  "field": "my_text",
  "text" : "The Standard Analyzer works this way."
}

결과 :

{
  "tokens" : [
    {
      "token" : "the",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "standard",
      "start_offset" : 4,
      "end_offset" : 12,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "analyzer",
      "start_offset" : 13,
      "end_offset" : 21,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "works",
      "start_offset" : 22,
      "end_offset" : 27,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "this",
      "start_offset" : 28,
      "end_offset" : 32,
      "type" : "<ALPHANUM>",
      "position" : 4
    },
    {
      "token" : "way",
      "start_offset" : 33,
      "end_offset" : 36,
      "type" : "<ALPHANUM>",
      "position" : 5
    }
  ]
}

영어 불용어 추가하기

PUT index_standard_analyzer_english
{
  "settings": {
    "analysis": {
      "analyzer": {
        "std": {
          "type": "standard",
          "stopwords": "_english_"
        }
      }
    }
  },
  "mappings":{
    "my_type": {
      "properties":{
        "my_text":{
          "type":"text",
          "analyzer": "std"
        }
      }
    }
  }
}

Std 라는 명시적인 분석기 안에 stopwords의 값에 english라고 설정

그리고 만들어진 분석기를 실행하면

POST index_standard_analyzer_english/_analyze 
{
  "field": "my_text",
  "text" : "The Standard Analyzer works this way."
}

결과는 다음과 같이 the, this가 삭제되고 보여줌을 알수 있다.

POST index_standard_analyzer_english/_analyze 
{
  "field": "my_text",
  "text" : "The Standard Analyzer works this way."
}

AmazonData

https://github.com/pranav-shukla/learningelasticstack

를 통해서 데이터를 가지고오려면위의 Readme.md를 참고하면된다.

logstash를 사용한다.

데이터 삽입 후에 쿼리로 데이터를 검색하면

GET /amazon_products/products/_search
{
  "query":{
    "match_all": {}
  }
}

구조화된 대부분의 쿼리는 유사도 기반의 스코어가 필요하지 않으며, 결과에 어떤 항목이 포함돼 있는지 단순히 "예", "아니요"로 나타냄

구조화된 검색 쿼리는 용어 레벨쿼리 (Term Level. Query) 라고 부른다.

검색을 할때 용어에 수행할 분석기가 따로 없기 때문에 역색인에 용어 레벨 쿼리를 직접 실행한다.

구조화된 쿼리 또는 용어 쿼리

  • Range 쿼리
  • Exist 쿼리
  • Term 쿼리
  • Terms 쿼리

Range 쿼리

자연적 질서를 가진 데이터 타입 필드에 적용 가능 (정수, integer, long, date)

이러한 데이터 타입은 순서 정렬을 하고 있으므로 범위를 나타내는 Range 쿼리를 사용할수 있다.

  • 숫자타입
  • 스코어 증폭( score Boosting)
  • 날짜 범위

숫자타입

$10~$20 범위의 제품 검색

GET /amazon_products/products/_search
{
  "query": {
    "range": {
      "price": {
        "gte":10,
        "lte":20
      }
    }
  }
}

결과 :

  "took" : 22,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 278,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "amazon_products",
        "_type" : "products",
        "_id" : "Fx7-4WgBWet64OX_NA57",
        "_score" : 1.0,
        "_source" : {
          "id" : "b000ap2wyw",
          "description" : null,
          "price" : "19.99",
          "manufacturer" : "topics entertainment",
          "title" : "kids power fun for girls"
        }
      },

Max_score가 1인 이유는 range쿼리는 중요도 혹은 유사도를 고려하지 않은 구조화된 쿼리로 필터를 실행

스코어링을 하지 않고 모든 문서를 스코어1로 나타냄

스코어 증폭

다른 쿼리와 함께 Range 쿼리를 사용하고, 일부 조건을 만족할 경우, 결과 도큐먼트에 더 높은 스코어를 할당 하고 싶다면, 어떻게??

쿼리에 여러 타입을 결합할 수 있는 Bool 쿼리와 같이 복합 쿼리 사용

GET /amazon_products/products/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "range": {
      "price": {
        "gte":10,
        "lte":20,
        "boost":2.2
      }
    }
  }
}

날짜 범위

날짜는 기본적으로 정렬되기 때문에 Range 쿼리를 날짜 필드에도 적용할 수 있다.

날짜범위 쿼리에는 날짜형식을 지정한다.

gte가 이전 날짜, lte가 마지막 날짜

GET /orders/order/_search
{
  "query": {
    "range": {
      "orderDate" : {
        "gte": "01/09/2017",
        "lte": "30/09/2017",
        "format": "dd/MM/yyyy"
      }
    }
  }
}

now는 현재 시간, now-7d는 현재로써 7일전 밀리초까지 정밀도의 조건을 가지고 있다.

y(년) M(월) w(주) d(일) h(시간) H(시간) m(분) s(초)

시간단위 하루 반올림 : 예를 들면 "gte" : "now-7d/d", "lte" : "now/d" 사용,
'/d'를 지정하면 시간을 일 단위로 반올림한다.

Exist 쿼리

특정 필드에 null 값과 공백이 아닌 레코드만 가져 오고 싶을 때 유용하게 사용할수 있음

GET /amazon_products/products/_search
{
  "query":{
    "exists":{
      "field": "description"
    }
  }
}

제품 필드가 정의된 모든 제품을 가지고오는 쿼리

Term 쿼리

특정 제조 업체가 만든 제품을찾으려면?

GET /amazon_products/products/_search
{
  "query":{
    "term":{
      "manufacturer.raw": "victory multimedia"
    }
  }
}

Victory multimedia 라고 검색하면 단순히 victory 또는 multimedia 문자열만 포함된 제조업체가 결과로 나오는 것은 아니다.

Term 쿼리는 용어 분석을 수행하지 않는다는 의미에서 하위 레벨 쿼리 이다.

기본적으로는 score없이 필터 컨텍스트에서 term 쿼리를 실행하려면, constant_score 필터를 사용해야한다.

GET /amazon_products/products/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "term": {
          "manufacturer.raw": "victory multimedia"
        }
      }
    }
  }
}

모든 도큐먼트에 스코어 1인 결과를 반환

다른 타입의 쿼리를 변환하고 쿼리를 결합하는데 유용한 복합쿼리도 있음.

전문 텍스트 쿼리는 구조화되지 않은 텍스트 필드에 수행할 수 있다.

분석과정으로 인식 , 실제 검색연산을 수헹하기 전에 검색 용어를 대상으로 분석 실행.

우선 필드 레벨에 search_analyzer가 정의 됐는지 확인해 올바른 분석기를 찾음.

이러한 쿼리를 상위레벨 쿼리라고 한다. 이경우 표준 분석기를 사용하므로 역색인은 모든 분할된 용어를 포함한다.

전문텍스트 쿼리

  • Match 쿼리
  • Match Pharse 쿼리
  • Multi Match 쿼리

Match 쿼리

기본 필드에 사용된 분석기를 인식하는 상위레벨쿼리중 하나.

GET /amazon_products/products/_search
{
  "query":{
    "match": {
      "manufacturer": "victory multimedia"
    }
  }
}

이 쿼리는 실질적으로

{
    "query":{
        "bool" : {
            "should":[
                {"term": {"title": "victory"}},
                {"term": {"title": "multimedia"}},
            ]
        }
    }
}

와 같이 실행된다.

  • 모든 도큐먼트의 manufacturer 필드에서 victory multimedia 용어를 검색
  • 가장 일치하는 도큐먼트를 찾아 스코어에 따라 내림차순으로 정렬한다.
  • 도큐먼트가 같은 순서로 나란히 놓인 경우, 도큐먼트 두 용어를 모두 갖고 있지만, 같은 순서가 아니거나, 서로 인접하지 않는 다른도큐먼트보다 더 높은 스코어를 가져야 한다.
  • 높은 스코어에서 낮은 스코어까지 적합한 도큐먼트를 찾아낸다.

다음과 같이 일반적인 옵션 몇가지를 살펴본다.

  • operator
  • Minimum_should_match
  • Fuzziness

Operator

지정된 검색어가 분석기를 적용해 여러 용어로 나뉘면 개별 용어에 따른 결과를 결합하는 방법이 필요함.

GET /amazon_products/products/_search
{
  "query":{
    "match":{
      "manufacturer": {
        "query": "victory multimedia",
        "operator": "and"
      }
    }
  }
}

해당 쿼리는 도큐먼트의 manufacturer 필드에 victory와 multimedia 값이 모두 포함한 결과를 찾는다.

minimum_should_match

연산자 and 를 적용하는 대신 or 연산자를 그대로 사용하되, 결과로 나오는 도큐먼트에서 일치하는 용어의 최소 개수를 지정할 수 있다.

결과로 나오는 도큐먼트에서 일치하는 용어의 최소 개수를 지정할 수 있다.

GET /amazon_products/products/_search
{
  "query":{
    "match": {
      "manufacturer":{
        "query": "victory multimedia",
        "minimum_should_match":2
      }
    }
  }
}

and연산자와 비슷한 방식으로 동작하고, 쿼리는 2개의 검색용어를 갖고 있으며, 최소한 2개의 용어가 일치 해야한다고 지정함

2개의 용어를 검색한다면 and연산이 되고, 2개중 1로 선택을 한다면 or가 된다.

Fuzziness

match쿼리에 fuzziness매개 변수를 사용해 Fuzzy 쿼리로 변경 할 수 있다.

Fuzzyness 는 레벤슈타인 거리 알고리즘을 기반으로 함, 원본 문자열을 다른 문자열로 변경하기 위한 편집 횟수를 측정한다.

매개변수 : 0,1,2, Auto 값중 하나를 사용할 수 있다.

GET /amazon_products/products/_search
{
  "query": {
    "match":{
      "manufacturer": {
        "query": "victor multimedia",
        "fuzziness":1
      }
    }
  }
}

Match Phrase 쿼리

분할해서 용어를 검색하는것 보다 일련의 단어를 일치 시키려면, match_phrase 쿼리를 사용한다.

GET /amazon_products/products/_search
{
  "query": {
    "match_phrase": {
      "description": {
        "query":"know the value"
      }
    }
  }
}

slop매개변수를 사용하면 쿼리 시점에서 건너뛸수 있는 단어의 개수를 추가할 수 있다.

Multi Match 쿼리

Match 쿼리의 확장 버전, 여러 필드에서 일치하는 쿼리를 실행 할 수 있음

다양한 옵션을 위해, 도큐먼트의 전체 스코어를 계산 할 수 있다.

Multi_match 쿼리 사용하기

사용자가 검색할 때 제목과 설명 필드 모두 조회해야할 경우

GET /amazon_products/products/_search
{
  "query":{
    "multi_match": {
      "query": "monitor aquarium",
      "fields": [
        "title",
        "description"
      ]
    }
  }
}

결과 : title 과 description 필드에 monitor 와 aquarium이 들어가는 모든 다큐먼트를 찾는다.

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 120,
    "max_score" : 12.203196,
    "hits" : [
      {
        "_index" : "amazon_products",
        "_type" : "products",
        "_id" : "MaBeBWkBUwGsIMY0bJOy",
        "_score" : 12.203196,
        "_source" : {
          "description" : "my sim aquarium has all the simulated fish and fish tank accessories you need to turn your monitor into a gorgeous fish tank.",
          "manufacturer" : "viva media",
          "id" : "b000f613x2",
          "price" : "19.99",
          "title" : "my sim aquarium"
        }
      },

특정 필드의 스코어를 높이려면 스코어를 높이려는 필드^숫자를 적어주면 해당하는 배 만큼 스코어를 높여 준다.

Fields: [ field ^2]

복합쿼리 작성

하나 이상의 쿼리를 결합해 더 복잡한 쿼리를 작성함

일부 복합 쿼리는 스코어 쿼리를 스코어를 스코어가 없는 쿼리를 변환하고, 다중 스코어 및 스코어가 없는 쿼리를 결합하기도한다.

  • Constant score 쿼리
  • Bool 쿼리

Constant score 쿼리

구조화된 데이터 검색은 스코어링이 필요 없다. 그렇기 때문에 Constant score 쿼리를 사용하면 일반적으로 쿼리 컨텍스트에서 실행하는 스코어링 쿼리를 필터 컨텍스트에서 실행하는 스코어가 없는 쿼리로 변환이 가능하다.

term쿼리를 실행하면 도큐먼트를 분류하고, 모든 항목에 스코어를 할당한다.

GET /amazon_products/products/_search
{
  "query":{
    "term": {
      "manufacturer": "victory multimedia"
    }
  }
}

GET /amazon_products/products/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "term": {
          "manufacturer": "victory multimedia"
        }
      },
      "boost":1.2
    }
  }
}

밑의 쿼리는 필터 컨텍스트에서 실행 하도록 변경된다.

또한 기본적으로 중립을 의미하는 1이라는 스코어를 할당한다. 또는 스코어를 1로 할당하지 않고 boost매개변수를 지정하면 원하는 스코어를 지정할 수 있다.

Bool쿼리

복잡한 쿼리 타입을 작성하는데 유용

sql에서 where 절과and or 조건을 사용해 필터링하는 방법을 알고 있듯이 , Bool쿼리를 사용하면 다중 스코어링 및 스코어를 계산하지 않는 쿼리를 결합 할 수 있다.

GET /amazon_products/products/_search
{
  "query": {
    "bool" : {
      "must":{},     # 쿼리 컨텍스트에서 실행되는 스코어를 계산하는 쿼리
      "should":{},     # 쿼리 컨텍스트에서 실행되는 스코어를 계산하는 쿼리
      "filter":{},     # 필터 컨텍스트에서 실행되는 스코어를 계산하지 않는 쿼리
      "must_not":{}  # 필어 컨텍스트에서 실행되는 스코어를 계산하지 않는 쿼리
    }
  }
}

구조화된 검색을 수행하는 스코어를 계한하지 않는 쿼리를 구성하는 방법

  • OR 조건 결합
  • AND 및 OR 조건 결합
  • NOT 조건 추가
GET /amazon_products/products/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "should": [
            {
              "range": {
                "price": {
                  "gte": 10,
                  "lte": 13
                }
              }
            },
            {
              "term": {
                "manufacturer": {
                  "value": "valuesoft"
                }
              }
            }
          ]
        }
      }
    }
  }
}

+ Recent posts