Elasticsearch 에 텍스트 입력시 필드를 인덱싱하고 Documents 화 할때
Lucene 엔진에 의해 텍스트가 분석되어 입력된다.
이때 텍스트를 분석하는 엔진을 Analyer 라고 한다.
Lucene 에서 제공하는 Analyer 는 하나의 Tokenizer 와 다수의 Filter 로 구성된다.
Filter 는 CharFilter 와 TokenFilter 의 두 가지가 있는데
CharFilter 는 입력된 문자열에서 불필요한 문자를 normalization 하기 위해
사용되며 TokenFilter 는 tokenizer 에 의해 분해된 token 에 대한 Filter 처리를 하게 된다.
기본적으로 CharFilter 에 의해 공백 콤마 등의 문자를 삭제하며
예로 문서의 유형별로 xml 일 경우 <..> 의 문자열들을 삭제하게 된다.
Tokenizer 는 CharFilter 와 비슷한 일을 하지만 입력 값이 CharFilter 가
입력이 character stream 에 반해 Tokenizer 는 token stream 을 사용하게 된다.
처음 Elasticsearch 를 설치하면 standard analyzer 를 사용하게 되는데 여기에는 CharFilter 가 없다.
따라서 모든 값이 바로 Tokenizer 로 전달된다.
그리고 기본 설치된 Standard Tokenizer 는 단순히 whitespace 기준으로 토큰을 자른다.
다음으로 기본 Standard Token Filter 는 단순히 모든 대문자를 소문자로 바꾸는 역할만 한다.
Using Analyzer API
예를 들어 아래와 같이 default 로 적용되는
standard analyzer 의 Tokenizer 까지만 사용하여 결과를 조회시
파싱된 토큰 결과가 어떻게 나오는지 살펴보자.
POST _analyze
{
"tokenizer": "standard",
"text":"I'm in the mood for drinking semi-dry red wine!"
}
|
그 결과는 아래와 같은데 공백과 - 문자를 파싱하였고 대문자를 소문자로 바꿔 저장하지 않았다.
{
"tokens" : [
{
"token" : "I'm",
"start_offset" : 0,
"end_offset" : 3,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "in",
"start_offset" : 4,
"end_offset" : 6,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "the",
"start_offset" : 7,
"end_offset" : 10,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "mood",
"start_offset" : 11,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 3
},
{
"token" : "for",
"start_offset" : 16,
"end_offset" : 19,
"type" : "<ALPHANUM>",
"position" : 4
},
{
"token" : "drinking",
"start_offset" : 20,
"end_offset" : 28,
"type" : "<ALPHANUM>",
"position" : 5
},
{
"token" : "semi",
"start_offset" : 29,
"end_offset" : 33,
"type" : "<ALPHANUM>",
"position" : 6
},
{
"token" : "dry",
"start_offset" : 34,
"end_offset" : 37,
"type" : "<ALPHANUM>",
"position" : 7
},
{
"token" : "red",
"start_offset" : 38,
"end_offset" : 41,
"type" : "<ALPHANUM>",
"position" : 8
},
{
"token" : "wine",
"start_offset" : 42,
"end_offset" : 46,
"type" : "<ALPHANUM>",
"position" : 9
}
]
}
|
이번에는 Tokenizer 까지 사용하는게 아니라 Analyzer 를 사용해보자.
POST _analyze
{
"analyzer":"standard",
"text":"I'm in the mood for drinking semi-dry red wine!"
}
|
이 경우 CharFilter 와 TokenFilter 를 다 적용하게 되므로 아래와 같이 영문자가 소문자로 바뀌게 된다.
{
"tokens" : [
{
"token" : "i'm",
"start_offset" : 0,
"end_offset" : 3,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "in",
"start_offset" : 4,
"end_offset" : 6,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "the",
"start_offset" : 7,
"end_offset" : 10,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "mood",
"start_offset" : 11,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 3
},
{
"token" : "for",
"start_offset" : 16,
"end_offset" : 19,
"type" : "<ALPHANUM>",
"position" : 4
},
{
"token" : "drinking",
"start_offset" : 20,
"end_offset" : 28,
"type" : "<ALPHANUM>",
"position" : 5
},
{
"token" : "semi",
"start_offset" : 29,
"end_offset" : 33,
"type" : "<ALPHANUM>",
"position" : 6
},
{
"token" : "dry",
"start_offset" : 34,
"end_offset" : 37,
"type" : "<ALPHANUM>",
"position" : 7
},
{
"token" : "red",
"start_offset" : 38,
"end_offset" : 41,
"type" : "<ALPHANUM>",
"position" : 8
},
{
"token" : "wine",
"start_offset" : 42,
"end_offset" : 46,
"type" : "<ALPHANUM>",
"position" : 9
}
]
}
|
Index
Elasticsearch 에서 위처럼 Analyzer 를 사용하여 Token 을 빼고나면
이를 이용해서 Indexing 을 한다.
그래서 검색시 해당 Token 이 Docuemnt 에 포함되었는지 쉽게 찾을 수 있는 것이다.
CharFilter
Elasticsearch 는 기본적으로 3가지 CharFilter 를 제공한다.
HTML Strip Character Filter (html_strip)
Mapping Character Filter(mapping)
Im so _sad_
Im so :(
|
Pattern Replace Filter (pattern_replace)
Pattern: ([a-zA-Z0-9]+)(-?)
Replacement:$1
aaa-bbb-ccc
aaabbbccc
|
Tokenizer
Tokenizer 는 세 가지 범주로 분류된다.
Word Oriented Tokenizers
단순히 단어 수준에서 작동하기 때문에 출력을 사람이 쉽게 읽을 수 있다.
일반적으로 full text 를 tokenizing 하는데 사용한다.
Standard Tokenizer (standard)
공백 기반으로 Tokening 하며 특수분자는 제외된다.
" I'm in the mood for drinking semi-dry red wine! "
=> [I'm,in,the,mood,for,drinking,semi,dry,red,wine]
|
Letter Tokenizer (letter)
char 가 아닌 문자 기반으로 Tokening 한다.
Lowercase Tokenizer (lowercase)
Letter Tokenizer + 소문자 변경이다.
" I'm in the mood for drinking semi-dry red wine! "
=> [i,m,in,the,mood,for,drinking,semi,dry,red,wine]
|
Whitespace Tokenizer (whitespace)
정확하게 공백으로만 Tokening 된다.
" I'm in the mood for drinking semi-dry red wine! "
=> [I'm,in,the,mood,for,drinking,semi,dry,red,wine!]
|
UAX URL Email Tokenizer (uax_url_email)
Partial Word Tokenizers
부분 문자열 기반으로 Tokening 한다.
N-Gram Tokenizer (ngram)
모든 부분 문자열들로 Tokening 한다.
"Red wine"
=>[Re,Red,ed,wi,win,wine,in,ine,ne]
|
Edge N-Gram Tokenizer (edge_ngram)
첫 문자를 포함하는 부분 문자열들로 Tokening 한다.
보통 검색에서 단어를 suggest 시 사용하게 된다.
"Red wine"
=>[Re,Red,,wi,win,wine]
|
Structured Text Tokenizers
e-mail, zip-code, ID 등의 정형화된 Text 를 Tokening 할 때 사용한다.
Keyword Tokenizer (keyword)
이상하게 들리겠지만 입력을 받아 키워드로 저장한다.
도시 이름인 New Yock 처럼 특정 키워드가 의미가 있는 경우 사용한다.
" I'm in the mood for drinking semi-dry red wine! "
=> [I'm in the mood for drinking semi dry red wine!]
|
Pattern Tokenizer (pattern)
분할 텍스트 정규 표현식 기반으로 Tokening 한다.
기본 패턴은 \W+단어가 아닌 문자를 만날 때마다 텍스트를 분할하는 패턴이다.
"I, like, red, wine!"
=>[I,like,red,wine!]
|
TokenFilter
Text 를 Token 으로 분리하는 필터이다
Standard Token Filter (standard)
기본적으로 아무것도 하지 않는다.
[I'm,in,the,mood,for,drinking,semi,dry,red,wine]
=> [I'm,in,the,mood,for,drinking,semi,dry,red,wine]
|
Lowercase Token Filter (lowercase)
입력된 Token 입력을 소문자로 변경하는 Token 필터이다.
[I'm,in,the,mood,for,drinking,semi,dry,red,wine]
=> [i'm,in,the,mood,for,drinking,semi,dry,red,wine]
|
Uppercase Token Filter (lowercase)
입력된 Token 입력을 대문자로 변경하는 Token 필터이다.
[I'm,in,the,mood,for,drinking,semi,dry,red,wine]
=> [I'M,IN,THE,MOOD,FOR,DRINKING,SEMI,DRY,RED,WINE]
|
NGram Token Filter (nGram)
입력된 Token 입력을 nGram 으로 변경하는 Token 필터이다.
[Red,wine]
=>[Re,Red,ed,wi,win,wine,in,ine,ne]
|
Edge NGram Token Filter (edgeNGram)
입력된 Token 입력을 Edge nGram 으로 변경하는 Token 필터이다.
[Red,wine]
=>[Re,Red,wi,win,wine]
|
Stop Token Filter (stop)
의미있는 단어 ( stop word ) 기반의 Token 필터이다.
[I'm,in,the,mood,for,drinking,semi,dry,red,wine]
=> [I'm,mood,drinking,semi,dry,red,wine]
|
Word Delimiter Token Filter (word-delimiter)
대문자 및 영문자가 아닌 부분을 나누며 숫자도 분리되는
Token 필터로 한개 이상의 char 를 가진 것만 결과로 나오게 된다.
[Wi-Fi,PowerShell,CE1000,Andy's]
=> [Wi,Fi,Power,Shell,CE,1000,Andy]
|
Stemmer Token Filter (stemmer)
Stem word 기반으로 분리하는 Token 필터이다.
[I'm,in,the,mood,for,drinking,semi,dry,red,wine]
=> [I'm,mood,drink,semi,dry,red,wine]
|
Keyword Marker Token Filter (keyword_marker)
Stem word 기반으로 분리하는 Token 필터이지만 Keyword 는 분리하지 않는다.
Protected terms:[drinking]
[I'm,in,the,mood,for,drinking,semi,dry,red,wine]
=> [I'm,mood,drinking,semi,dry,red,wine]
|
Snowball Token Filter (snowball)
Stem word 기반으로 분리하는 Token 필터인데 Snawball 이라는 알고리즘 기반이다.
[I'm,in,the,mood,for,drinking,semi,dry,red,wine]
=> [I'm,mood,drink,semi,dry,red,wine]
|
Synonym Token Filter (synonym)
동의어를 넣어주는 Token 필터이다.
[I,am,very,happy]
=> [I,am,very,happy/delighted]
|
Analyzer
앞서 다룬 Filter 와 Tokenizer 를 포함하고 있는 컴포넌트이다.
Standard Analyzer (standard)
기본적으로 텍스트를 단어 경계로 단어를 분리하고 구두점을 제거하고 소문자로 변경한다.
옵션으로 stop words 를 제거할 수 있다.
" I'm in the mood for drinking semi-dry red wine! "
=> [i,m,in,the,mood,for,drinking,semi,dry,red,wine]
|
Simple Analyzer (simple)
기본적으로 텍스트를 단어 경계로 단어를 분리하고 구두점을 제거하고 소문자로 변경한다.
" I'm in the mood for drinking semi-dry red wine! "
=> [i,m,in,the,mood,for,drinking,semi,dry,red,wine]
|
Stop Analyzer
Simple Analyzer + Stop Word 제거
" I'm in the mood for drinking semi-dry red wine! "
=> [i,m,,mood,drinking,semi,dry,red,wine]
|
Language Analyzers (english, ...)
각 언어별 형태소 분석된 기반으로 Analysis 한다.
" I'm in the mood for drinking semi-dry red wine! "
=> [i'm,mood,drink,semi,dry,red,wine]
|
Keyword Analyzers (keyword)
" I'm in the mood for drinking semi-dry red wine! "
=> [i'm in the mood for drinking semi-dry red wine!]
|
Pattern Analyzers (pattern)
"I, like, red, wine! "
=> [I,like,red,wine!]
|
Creating Custom Analyzer
아래와 같이 my_custom_analyzer 를 Index 생성시 설정하였다.
char_filter 로 happy 와 sad 를 🙂 와 😞 로 설정하였고
tokenizer 로 특수문자를 제거하였고
token_filter 로 stop 토큰 필터를 넣었다. ( 의미없는 문자 and ,a , the 삭제)
PUT my_index
{
"settings": {
"analysis": {
"analyzer": {
"my_custom_analyzer": {
"type": "custom",
"char_filter": [
"emoticons"
],
"tokenizer": "punctuation",
"filter": [
"lowercase",
"english_stop"
]
}
},
"tokenizer": {
"punctuation": {
"type": "pattern",
"pattern": "[ .,!?]"
}
},
"char_filter": {
"emoticons": {
"type": "mapping",
"mappings": [
":) => _happy_",
":( => _sad_"
]
}
},
"filter": {
"english_stop": {
"type": "stop",
"stopwords": "_english_"
}
}
}
}
}
|
그리고 사용한 결과는 아래와 같다.
POST my_index/_analyze
{
"text": "I'm a :) person, and you?"
}
|
{
"tokens" : [
{
"token" : "i'm",
"start_offset" : 0,
"end_offset" : 3,
"type" : "word",
"position" : 0
},
{
"token" : "_happy_",
"start_offset" : 6,
"end_offset" : 8,
"type" : "word",
"position" : 2
},
{
"token" : "person",
"start_offset" : 9,
"end_offset" : 15,
"type" : "word",
"position" : 3
},
{
"token" : "you",
"start_offset" : 21,
"end_offset" : 24,
"type" : "word",
"position" : 5
}
]
}
|
Using Analyzer in mappings
위의 my_index 에 Document 삽입 전 Mapping 단계에서 사용할 Analyzer 를 지정하고
실제로 Document 삽입후 변경된 문자열이 잘 검색되는지 확인해 보자.
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_"
}
}
}
|
{
"took" : 17,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "my_index",
"_type" : "default",
"_id" : "1",
"_score" : 0.2876821,
"_source" : {
"description" : ":) drinking :(",
"teaser" : ":) drinking :("
}
}
]
}
}
|
위와 같이 "description":"_happy_" 인 Document 찾는 쿼리를 실행하였는데
검색이 되는 것을 확인할 수 있다.
Adding Analyzer in an existing index
기존 Index 가 있을 때 Analyzer 를 변경하고자 하면 _close / _open API 를 사용하면 된다.
POST /my_index/_close
PUT /my_index/_settings
{
"analysis": {
"analyzer": {
"french_stop":{
"type":"standard",
"stopwords":"_french_"
}
}
}
}
POST /my_index/_open
|
'Monitoring > Elasticsearch' 카테고리의 다른 글
09. Compound Query (0) | 2020.01.17 |
---|---|
08. Query (0) | 2020.01.17 |
06. Mapping (0) | 2020.01.17 |
05. Document (0) | 2020.01.17 |
04. Cluster (0) | 2020.01.17 |