본문 바로가기
프로그래밍/Elasticsearch

[elastic search] n-gram 을 통한 부분 단어 검색

by 동네로봇 2021. 1. 24.

n-gram analyzer 를 사용하여 일부 단어를 통해 원하는 검색 결과를 찾는 방법을 한 번 살펴보자.

 

먼저 아래와 같은 구조로 인덱스를 생성하고자 한다. 

 

es 데이터 예시

{
"_source" : {
      "data": {
          "essential" : {
              "person" : {
                  "name" : {
                      "first" : "johnny",
                          "last" : "wang"
                  },
                  "birth" : {
                      "date" : "2020.01.01"
                  },
                  "introduce" : {
                  	  "self" : "안녕하세요. 반갑습니다. 조니 왕입니다."
                  }
              }
          }
      }
   }
}

 

인덱스의 필드 중 "_source.data.essential.person.name.first"와 "_source.data.essential.person.introduce.self" 필드에 n-gram 검색 기능을 적용하고자 한다.

 

인덱스 생성 시 n-gram 분석 툴을 세팅해주고, 어떤 필드에서 n-gram 을 사용할 것인지를 mapping 쿼리 안에 명시해주어야 한다. 

아래 쿼리를 보면 쉽게 이해가 될 것이다.

 

인덱스 생성 쿼리

# create person index
PUT person-000001
{
	"settings": {
		"analysis": {
		  "analyzer": {
			"text_ngram_analyzer": {
			  "tokenizer": "text_ngram_analyzer"
			}
		  },
		  "tokenizer": {
			"text_ngram_analyzer": {
			  "type": "nGram",
			  "min_gram": "1",
			  "max_gram": "30",
			  "token_chars": [
				"letter",
				"digit",
				"whitespace",
				"punctuation"
			  ]
			}
		  }
		}
	  },
	  "mappings": {
		"_doc": {	
			"properties" : {			
				"data": {
					"properties" : {
					  "essential" : {
						"properties" : {
							  "person" : {
								"properties" : {
									  "name" : {
										"properties" : {
										  "first" :  {
											  "type": "text",
											  "fields": {
												"keyword": {
												  "type": "keyword",
												  "ignore_above": 256
												},
												"ngram": {
												  "type": "text",
												  "analyzer": "text_ngram_analyzer"
												}
											  }
											},
										  "last" : {
											  "type": "text",
											  "fields": {
												"keyword": {
												  "type": "keyword",
												  "ignore_above": 256
												}
											  }
											}
										  }
										},
									  "birth" : {
										"properties" : {
										  "date" : {
											  "type": "text",
											  "fields": {
												"keyword": {
												  "type": "keyword",
												  "ignore_above": 256
												}
											  }
											}
										}
									  },
									  "introduce" : {
										"properties" : {
										  "self" : {
											  "type": "text",
											  "fields": {
												"keyword": {
												  "type": "keyword",
												  "ignore_above": 256
												},
												"ngram": {
												  "type": "text",
												  "analyzer": "text_ngram_analyzer"
												}
											  }
											}
										}
									  }
						 }
				}
			}
		}
	}
}

 

좀 길긴 길지만..;;; 부분적으로 나누어서 보면 쉽게 이해할 수 있습니다... ㅎㅎ... notepad 에 따로 빼서 전체적인 구조를 파악하는 걸 추천!!

 

먼저 크게는 "settings" 와 "mappings" 로 나눌 수 있는데, "settings" 부분에서는 샤드의 개수나 파이프라인 설정 등 이 인덱스에 적용할 큰 세팅들을 명시해주는 부분이다. 

위의 예시에서는 검색을 위한 n-gram 을 설정해주었다. 

 

생성 쿼리를 조금씩 쪼개서 자세히 살펴보자.

"analyzer": {
  "text_ngram_analyzer": {
  	"tokenizer": "text_ngram_analyzer"
  }
}

이 부분은 이 analyzer의 이름을 설정하는 부분이다. "text_ngram_analyzer" 부분은 내 마음대로 설정하면 된다.

 

 "tokenizer": {
			"text_ngram_analyzer": {
			  "type": "nGram",
			  "min_gram": "1",
			  "max_gram": "30",
			  "token_chars": [
				"letter",
				"digit",
				"whitespace",
				"punctuation"
			  ]
			}
		  }
		}

이 부분에서는 내가 생성한 tokenizer 의 설정을 세팅하는 부분이다. 

- type : 어떤 툴을 사용하여 텍스트 분석을 할 것인지 (nGram을 사용하겠다)
- min_gram : 단어를 분해할 최소 단위

- max_gram : 단어를 분해할 최대 단위

- token_chars : 어떤 형태의 단어를 토큰화의 기준에 포함할 것인지 (설명 참고 : www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html

 

예시) 

만약에 min_gram : 1, max_gram : 5, token_chars : [text, whitespace] 로 설정하고  예시 문장이 "안녕 하세요." 인 경우, 

문장은 

"안", "녕", " ", "하", "세", "요", "안녕", "녕 ", " 하", "하세", "세요", "안녕 ", ........, "녕 하세요"

로 토큰화된다. 

 

 

"setting" 부분 다음의 "mapping" 부분은 실제 데이터가 들어갈 필드에 대해 자세히 설정한다.

 

맨 위의 es 예시 데이터에서 ngram을 적용할 필드는 "name.first" "introduce.self" 이다.

필드 생성에서 내가 설정한 "text_ngram_analyzer"를 사용할 것이라고 설정해준다.

"first" :  {
  "type": "text",
  "fields": {
  "keyword": {
    "type": "keyword",
    "ignore_above": 256
  },
  "ngram": {
    "type": "text",
    "analyzer": "text_ngram_analyzer"
  }
}

 

이렇게 인덱스를 생성한 후 검색에 필요한 es 예시 데이터를 넣어준다. 

es에 데이터를 밀어넣는 동시에 ngram 을 설정한 필드는 토큰화 된 모든 단어를 가지고 있다. 

 

내가 설정한 analyzer에 의해서 해당 필드가 어떻게 토큰화 되었는지 확인하기 위한 쿼리는 다음과 같다.

ngram 토큰 검색 쿼리

#ngram 토큰 검색
GET person-000001/_doc/2nv4BXIBdzk5kjug30rM/_termvector?fields=data.essential.person.name.first.ngram

 

그럼 데이터를 검색해보자. 맨 위에 적은 es 예시 데이터에서 자기소개 부분을 검색해보도록 하겠다.

 

ngram 데이터 검색 쿼리

#ngram 검색 1
GET 11001-*/_search
{
  "from": "0",
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "data.essetial.person.introduce.self.ngram": {
              "value": "안녕하세"
            }
          }
        }
      ]
    }
  },
  "size": "100",
  "sort": [
    {
      "data.essetial.person.birth": "desc"
    }
  ]
}

bool.must.term 부분에 내가 검색하고자 하는 단어를 입력하면,

es에 저장된 데이터 중 self 필드에 "안녕하세"가 들어간 데이터의 개수와 상세 결과를 리턴받을 수 있다. 

 

그 외에 다른 부분 "from", "size" 는 해당 검색 결과를 0부터 100개까지 출력하겠다는 뜻이고, birth 의 내림차순으로 정렬하여 결과를 보겠다는 뜻이다.