Elasticsearch filter에서 한자로 검색했을 때 일치하는 한글 결과로 tokenizing하게 해주는 filter가 있다. 해당 filter는 nori-readingform이다. 적용 방법은 기존에 synonmys나 speech필터 적용과 동일하다.

 

인덱스 생성


위에서 부터 사용했던 인덱스에 nori_readingform 필터를 추가해서 생성만 해주면 된다.

PUT wedul_anaylyzer
{
  "settings": {
    "index" : {
      "analysis" : {
        "tokenizer": {
          "nori_user_dict": {
            "type": "nori_tokenizer",
            "decompound_mode": "none",
            "user_dictionary": "dic/nori_userdict_ko.txt"
          }
        },
        "analyzer" : {
          "custom_analyze" : {
            "type": "custom",
            "tokenizer" : "nori_user_dict",
            "filter": [
              "my_posfilter",
              "nori_readingform"
            ]
          }
        },
        "filter": {
          "my_posfilter": {
            "type": "nori_part_of_speech",
            "stoptags": [
              "NP", "UNKNOWN"
            ]
          }
        }
      }
    }
  }
}

이렇게 만든 인덱스를 이용해서 한자를 이용해서 한글 내용을 뽑아내보자

결과


행복이라는 한자를 입력하여 검색해보자. 필터가 정상적으로 적용된다면 행복이라는 내용을 가진 결과가 나올것이다

GET wedul_analyzer/_analyze
{
"analyzer": "custom_analyze",
"text": "幸福 사랑"
}

결과는 정상적으로 행복 그리고 사랑이라는 단어로 추출되었다. nori를 공부하면서 좋은 기본 필터 많은걸 알게 되서 좋다.

Elasticsearch를 사용하여 analyze를 사용하다가 조사, 형용사 등등을 제외하고 형태소 토크나이즈가 되어야 했다. 그래서 정식 문서를 찾아보더니 nori_part_of_speech라는 필터가 있었다.

우선 저번 시간에 만들었던 wedul_analyzer 인덱스를 이용해서 토크나이즈를 해보자.

{
  "tokens": [
    {
      "token": "바보",
      "start_offset": 0,
      "end_offset": 2,
      "type": "word",
      "position": 0
    },
    {
      "token": "위들",
      "start_offset": 3,
      "end_offset": 5,
      "type": "word",
      "position": 1
    },
    {
      "token": "이",
      "start_offset": 5,
      "end_offset": 6,
      "type": "word",
      "position": 2
    },
    {
      "token": "집에",
      "start_offset": 7,
      "end_offset": 9,
      "type": "word",
      "position": 3
    },
    {
      "token": "서",
      "start_offset": 9,
      "end_offset": 10,
      "type": "word",
      "position": 4
    },
    {
      "token": "나",
      "start_offset": 11,
      "end_offset": 12,
      "type": "word",
      "position": 5
    },
    {
      "token": "왔다",
      "start_offset": 12,
      "end_offset": 14,
      "type": "word",
      "position": 6
    }
  ]
}

여기서 '나'와 '왔다'를 없애고 토크나이즈 결과가 나왔으면 좋겠다.

그럼 '나'와 '왔다'의 형태소가 어떻게 되는지 우선 알아보자. analyzer api에 explain: true 옵션을 부여하면 해당 토크나이즈에 분리된 형태소들의 정보가 나온다.

GET _analyze
{
  "analyzer": "nori",
  "explain": true, 
  "text": "바보 위들이 집에서 나왔다"
}

'나'와 '왔다'는 NP와 UNKNOWN이다.  이 두개를 nori_part_of_speech필터를 이용해서 제거해보자.

 {
          "token": "나",
          "start_offset": 11,
          "end_offset": 12,
          "type": "word",
          "position": 6,
          "bytes": "[eb 82 98]",
          "leftPOS": "NP(Pronoun)",
          "morphemes": null,
          "posType": "MORPHEME",
          "positionLength": 1,
          "reading": null,
          "rightPOS": "NP(Pronoun)",
          "termFrequency": 1
        },
        {
          "token": "왔다",
          "start_offset": 12,
          "end_offset": 14,
          "type": "word",
          "position": 7,
          "bytes": "[ec 99 94 eb 8b a4]",
          "leftPOS": "UNKNOWN(Unknown)",
          "morphemes": null,
          "posType": "MORPHEME",
          "positionLength": 1,
          "reading": null,
          "rightPOS": "UNKNOWN(Unknown)",
          "termFrequency": 1
        }

custom analyzer를 만들면서 nori_part_of_speech 필터를 추가해주면된다. 이 필터에서 stoptags 배열에 제거하고 싶은 형태소 요형을 추가하면 해당 형태소를 제거한 결과만 출력된다.

PUT wedul_anaylyzer
{
  "settings": {
    "index" : {
      "analysis" : {
        "tokenizer": {
          "nori_user_dict": {
            "type": "nori_tokenizer",
            "decompound_mode": "none",
            "user_dictionary": "dic/nori_userdict_ko.txt"
          }
        },
        "analyzer" : {
          "custom_analyze" : {
            "type": "custom",
            "tokenizer" : "nori_user_dict",
            "filter": [
              "my_posfilter"
            ]
          }
        },
        "filter": {
          "my_posfilter": {
            "type": "nori_part_of_speech",
            "stoptags": [
              "NP", "UNKNOWN"
            ]
          }
        }
      }
    }
  }
}

이렇게 만든 analyze를 이용해서 다시한번 확인해보자. 

아래 결과 처럼 '나'와 '왔다' 두개의 형태소가 사라진 것을 확인할 수 있다.

{
  "tokens": [
    {
      "token": "바보",
      "start_offset": 0,
      "end_offset": 2,
      "type": "word",
      "position": 0
    },
    {
      "token": "위들",
      "start_offset": 3,
      "end_offset": 5,
      "type": "word",
      "position": 1
    },
    {
      "token": "이",
      "start_offset": 5,
      "end_offset": 6,
      "type": "word",
      "position": 2
    },
    {
      "token": "집에",
      "start_offset": 7,
      "end_offset": 9,
      "type": "word",
      "position": 3
    },
    {
      "token": "서",
      "start_offset": 9,
      "end_offset": 10,
      "type": "word",
      "position": 4
    }
  ]
}

 

기본적으로 stoptags를 적용하지 않으면 10몇가지의 형태소 종류가 기본으로 배제된다.

NP, VPC등 형태소들에 대한 용어는 하단 사이트에 잘 정리되어 있다.

https://coding-start.tistory.com/167
http://kkma.snu.ac.kr/documents/?doc=postag

 

꼬꼬마, 한글 형태소 분석기 (Kind Korean Morpheme Analyzer, KKMA)

꼬꼬마 한국어 형태소 분석기 한글 형태소 품사 (Part Of Speech, POS) 태그표 한글 형태소의 품사를 '체언, 용언, 관형사, 부사, 감탄사, 조사, 어미, 접사, 어근, 부호, 한글 이외'와 같이 나누고 각 세부 품사를 구분한다. 대분류 세종 품사 태그 심광섭 품사 태그 KKMA 단일 태그 V 1.0 태그 설명 Class 설명 묶음1 묶음2 태그 설명 확률태그 저장사전 체언 NNG 일반 명사 NN 명사 N NN NNG 보통 명사 NNA no

kkma.snu.ac.kr

 

출처
https://www.elastic.co/guide/en/elasticsearch/plugins/6.4/analysis-nori-speech.html

elasticsearch에서 검색기능을 넣다가 2080이라는 키워드를 검색 했을때와 이공팔공이라는 검색어를 입력했을 때 두개 모두 동일한 데이터를 출력하도록 지정하고 싶었다.

그래서 synonyms 필터를 만들기로 했다.

우선 synonyms 필터를 만들어서 사용하기 위해서는 동의어에 대한 정리가 되어있는 사전을 만들어야 한다.

사전 생성 방법은 다음과 같고 아래 링크를 참조해서 간단하게 사전을 만들었다.
https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-tokenfilter.html

파일명은 synonyms.txt이고 내용은 다음과 같다.

synonyms.txt

노레바,noreva,노래바
airpods,에어팟,airpod,airpot
2080,이공팔공

 

동의어 정리 사전을 elasticsearch에 넣어줘야하고 경로는 다음과 같다.

/usr/share/elasticsearch/config/analysis



나는 docker를 사용하기 때문에 생성한 파일을 다음 명령어를 통해 위치시켰다.

docker cp synonyms.txt elastic6.4:/usr/share/elasticsearch/config/analysis



그럼 이 필터를 사용하기 위해서 인덱스를 만들 때 설정을 달아서 추가해주자.
간단하게 keyword 하나만 존재하는 인덱스를 만들고 synonyms filter를 사용하게 해보자

여기서 사용하 tokenizer는 nori_tokenizer를 사용했다.
간단한 예제니 보면 바로 이해할 수 있다.

PUT synonyms_test
{
  "settings": {
      "index" : {
          "analysis" : {
              "analyzer" : {
                  "synonym" : {
                      "tokenizer" : "nori_tokenizer",
                      "filter" : ["synonym"]
                  }
              },
              "filter" : {
                  "synonym" : {
                      "type" : "synonym",
                      "synonyms_path" : "analysis/synonyms.txt"
                  }
              }
          }
      }
  },
  "mappings": {
    "_doc": {
      "dynamic": "false",
      "properties": {
        "keyword": {
          "type": "text",
          "analyzer": "synonym",
          "search_analyzer": "synonym",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          }
        }
      }
    }
  }
}

데이터를 2080과 이공팔공 두개를 삽입했다.

POST synonyms_test/_doc
{
  "keyword":"2080"
}

POST synonyms_test/_doc
{
  "keyword":"이공팔공"
}

그럼 이 두개의 키워드가 동일하게 하나의 명령어로 검색이 되는지 확인해보자.

GET synonyms_test/_search
{
  "query": {
    "match": {
      "keyword": "이공팔공"
    }
  }
}

2080과 이공팔공으로 검색한 결과

엘라스틱 서치를 쓰면서 기존에 형태소 분석기를 아리랑, 은전한닢, open korea등을 사용했었다.


근데 이번에 6.4버전이 출시 되면서 Elasticsearch에서 기본으로 제공하는 한글 형태소 분석기가 나왔다. 이름은 nori(노리)이다. 

노리는 놀이라는 뜻에서 가져왔으며 mecab-ko-dic 사전을 이용하지만 사전을 압축하므로 기존 형태소 분석기와 비교하여 메모리를 적게 쓰고 훨씬 빠르다.


그럼 Docker에 엘라스틱서치 6.4와 Kibana 6.4를 설치하고 Nori 플러그인을 설치해서 한글 형태소분석기를 사용해보자.


Docker에 Elasticsearch와 Kibana 설치

1
2
docker run --9200:9200 -9300:9300 --name elastic -"discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.4.0
docker run ---link elastic:elastic-url -"ELASTICSEARCH_URL=http://elastic-url:9200" -5601:5601 --name kibana docker.elastic.co/kibana/kibana:6.4.0
cs

설치를 완료하고 docker process를 확인하여 정상 동작하는지 확인한다.



Elasticsearch에 Nori 플러그인 설치

엘라스틱서치 bash셀에 접근해서 anaysis-nori를 설치한다.

1
2
3
4
5
6
// elasticsearch의 bash셀 접근
docker exec -it elastic /bin/bash
 
 
// 노리 플러그인 설치
bin/elasticsearch-plugin install analysis-nori
cs


설치가 완료되면 elasticsearch 프로세스를 재 시작 한다. 그러면서 프로세스가 올라올때 Nori 플러그인이 정상적으로 올라오는지 확인해보자.

1
[2018-10-13T01:26:05,498][INFO ][o.e.p.PluginsService     ] [dFC4eSy] loaded plugin [analysis-nori]
cs


그리고 Kibana를 사용해서 분석 플러그인 동작을 확인하자

1
2
3
4
5
6
7
GET _analyze
{
  "analyzer": "nori",
  "text" : "안녕 나는 바보 위들이야."
}
 
 
cs




사전 추가하기

위에 결과를 보면 위들이라는건 하나의 대명사로써 내 별칭인데 '들'이라는 조사를 제거하고 보여주느라 위들이라는 단어가 사라졌다. 이를 해결하기위해 사전을 만들어서 위들을 추가해보자.


먼저 사전에 사용될 사전은 mecab-ko-dic을 사용한다. 플러그인 설치했던것처럼 Elasticsearch에 접속하여 config/userdict_ko.txt를 생성한다. 그리고 그 txtx파일에 "위들"을 추가한다.


그리고 해당 사전을 사용하도록 anaylzer를 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PUT nori_sample
{
  "settings": {
    "index": {
      "analysis": {
        "tokenizer": {
          "nori_user_dict": {
            "type": "nori_tokenizer",
            "decompound_mode": "mixed",
            "user_dictionary": "userdict_ko.txt"
          }
        },
        "analyzer": {
          "my_analyzer": {
            "type": "custom",
            "tokenizer": "nori_user_dict"
          }
        }
      }
    }
  }
}
cs


그리고 다시한번 조회해보자.

1
2
3
4
5
6
GET nori_sample/_analyze
{
  "analyzer": "my_analyzer",
  "text" : "안녕 나는 바보 위들이야."
}
 
cs

결과를 확인해보면 정상적으로 위들이 형태소분석기에 의해 잘 나누어지는 것을 볼 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
{
  "tokens": [
    {
      "token": "안녕",
      "start_offset": 0,
      "end_offset": 2,
      "type": "word",
      "position": 0
    },
    {
      "token": "나",
      "start_offset": 3,
      "end_offset": 4,
      "type": "word",
      "position": 1
    },
    {
      "token": "는",
      "start_offset": 4,
      "end_offset": 5,
      "type": "word",
      "position": 2
    },
    {
      "token": "바보",
      "start_offset": 6,
      "end_offset": 8,
      "type": "word",
      "position": 3
    },
    {
      "token": "위들",
      "start_offset": 9,
      "end_offset": 11,
      "type": "word",
      "position": 4
    },
    {
      "token": "이",
      "start_offset": 11,
      "end_offset": 12,
      "type": "word",
      "position": 5
    },
    {
      "token": "야",
      "start_offset": 12,
      "end_offset": 13,
      "type": "word",
      "position": 6
    }
  ]
}
cs



참고

https://www.elastic.co/guide/en/elasticsearch/plugins/6.4/analysis-nori.html

https://www.elastic.co/kr/blog/nori-the-official-elasticsearch-plugin-for-korean-language-analysis

https://www.elastic.co/guide/en/elasticsearch/plugins/6.4/analysis-nori-tokenizer.html

엘라시틱 서치 간단정리

score 계산 알고리즘

- tf-idf와 bm25 사용 (엘라스틱 서치 5.0 부터는 bm25로 사용한다고 한다.)
https://www.popit.kr/bm25-elasticsearch-5-0%EC%97%90%EC%84%9C-%EA%B2%80%EC%83%89%ED%95%98%EB%8A%94-%EC%83%88%EB%A1%9C%EC%9A%B4-%EB%B0%A9%EB%B2%95/


※최소 만족해야하는 score 지정 min_score

검색된 데이터중에서 score가 6이상인 문서만 추출
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-min-score.html

1
2
3
4
5
6
7
8
9
10
11
12
GET /ws_play/play/_search
{
  "query": {
    "match": {
      "title": {
          "query" : "dd",
          "minimum_should_match": "50%"
      }
    }
  },
  "min_score": 6
}
cs


※일치하는 term % 지정 (minmum_should_match)
%이상 일치 또는 %이상 불일치 여부에 따라 반환되는 데이터 걸러내기
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-minimum-should-match.html


※6.4 이후버전부터 기본으로 추가되는 한글 형태소 분석기 노리(nori)
- 기존에 arirang과 은전한닢 그리고 open korean text를 별도로 설치해서 사용했었는데, 6.4부터는 기본으로 탑재



+ Recent posts