JangGeonWu
janggeonwu97
JangGeonWu
전체 방문자
오늘
어제
  • 분류 전체보기 (78)
    • SQLD (21)
    • 개인 공부용 (17)
    • Django (9)
    • Tableau (6)
    • ElasticSearch (8)
    • 빅데이터 엔지니어 (5)
    • Spring 퀵 스타트 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • 개인 공부 기록용 블로그

인기 글

최근 글

티스토리

hELLO · Designed By 정상우.
JangGeonWu

janggeonwu97

엘라스틱서치 오타 교정, 자동 완성 등 구현
ElasticSearch

엘라스틱서치 오타 교정, 자동 완성 등 구현

2022. 11. 14. 12:16

ICU Analysis Plugin을 통한 오타 교정 구현

ICU Analysis Plugin 설치

elasticsearch-plugin install anlaysis-icu

 

인덱스생성

 

Kibana의 Machine Learning탭의 Data Visualizer 기능 사용, 간단하게 인덱스 생성

 

Machine Learning 탭 -> Upload File -> kname, ename이 담긴 csv 파일 불러오기

csv에 헤더가 있어, Has header row 옵션 선택. 상황에 따라서 알맞게 설정할 것

import -> advanced -> 인덱스명, 인덱스 셋팅과 맵핑 각각 설정

 

index settings

{
  "number_of_shards": "1",
  "analysis": {
    "filter": {
      "my_filter": {
        "mode": "decompose",
        "name": "nfc",
        "type": "icu_normalizer"
      }
    },
    "analyzer": {
      "nfd_analyzer": {
        "filter": [
            "lowercase"
        ],
        "char_filter": [
          "nfd_normalizer"
        ],
        "tokenizer": "standard"
        }
      },
    "char_filter": {
      "nfd_normalizer": {
        "mode": "compose",
        "name": "nfc",
        "type": "icu_normalizer"
      }
    }
  }
}

Mappings

{
  "properties": {
    "ename": {
      "type": "text",
      "fields":{
        "raw": {
          "type": "keyword"
        },
        "spell": {
          "type": "text",
          "analyzer": "nfd_analyzer"
        }
      }
    },
    "kname": {
      "type": "text",
      "fields":{
        "raw": {
          "type": "keyword"
        },
        "spell": {
          "type": "text",
          "analyzer": "nfd_analyzer"
        }
      }
    }
  }
}

저 붉은 원에 있던 Import 누르면, 위와 같이 Index pattern 생성됨

 

검색 API의 suggester를 사용, 테스트

검색 실패

아래처럼 온전한 텍스트 기준으로만 검색됨

검색 성공


확장된 Ngram 검색 적용한 자동완성 구현

 

Ngram 분석기

  • 음절 단위로 토큰 생성, 재현율은 높으나 정확도는 떨어짐
  • 첫 음절을 기준으로 max_gram에서 지정한 길이만큼 토큰을 생성

(예시)

원문 -> 아버지가 방에 들어가신다

1단계 분석 -> [아버지가, 방에, 들어가신다]

2단계 분석

[아, 아버, 아버지, 아버지가, 버, 버지, 버지가, 지 지가, 가]

[방, 방에, 에]

[들, 들어, 들어가, 들어가신, 들어가신다, 어, 어가, 어가신, 어가신다, 가, 가신, 가신다, 신, 신다, 다]

 

Edge Ngram 분석기

  • 대부분 Ngram과 유사하게 동작
  • 지정한 토크나이저의 특성에 따라 Ngram이 일어남

(예시)

원문 -> 아버지가 방에 들어가신다

1단계 분석 -> [아버지가, 방에, 들어가신다]

2단계 분석

[아, 아버, 아버지, 아버지가]

[방, 방에, 에]

[들, 들어, 들어가, 들어가신, 들어가신다]

 

Edge Ngram Back 분석기

  • Edge Ngram과 반대로 동작하는 토크나이저를 사용
  • 옵션으로 'side:back'을 반드시 설정해야함

(예시)

원문 -> 아버지가 방에 들어가신다

1단계 분석 -> [아버지가, 방에, 들어가신다]

2단계 분석

[아, 버, 아버, 지, 버지, 아버지, 가, 지가, 버지가, 아버지가]

[방, 방에, 에]

[들, 어, 들어, 가, 들어가, 신, 가신, 어가신, 들어가신, 다, 신다, 가신다, 어가신다, 들어가신다]

 

Ngram이 글자 단위로 토큰을 생성하기 때문, Ngram 분석기, Edge Ngram 분석기, Edge Ngram Back 분석기라는 총 세가지 분석기를 모두 사용 시 어떤 부분일치도 구현 가능

 

아래처럼 토큰으로 생성

// Ngram Tokenizer 정의
"ngram_tokenizer": {
	"type": "nGram",
    "min_gram" : "1",
    "max_gram" : "50", // 최대 50글자까지 자름
    "token_chars": [
    	"letter",
        "digit",
        "punctuation",
        "symbol"
       ]
}
 
// Edge Ngram Tokenizer 정의
"edge_ngram_tokenizer": {
	"type": "edgeNGram",
    "min_gram" : "1",
    "max_gram" : "50",
    "token_chars": [
    	"letter",
        "digit",
        "punctuation",
        "symbol"
       ]
}

Ngram의 경우 기본 필터로 동작하나, Edge Ngram은 토큰을 잘라내는 방식에 따라서 front와 back 옵션을 따로 필터로 정의해야 함

// Front 옵션
"edge_ngram_filter_front": {
	"type": "edgeNGram",
    "min_gram": "1",
    "max_gram": "50",
    "side": "front"
}
// Back 옵션
"edge_ngram_filter_back": {
	"type": "edgeNGram",
    "min_gram": "1",
    "max_gram": "50",
    "side": "back"
}

분석기는 내부적으로 토크나이저와 필터로 이루저인다.

앞에서 정의한 토크나이저와 필터를 이용해서 custom 분석기를 정의한다.

// Ngram 분석기
"ngram_analyzer": {
	"type": "custom",
    "tokenizer": "ngram_tokenizer",
    "filter": [
    	"lowercase",
        "trim"]
}

// Edge Ngram 분석기
"edge_ngram_analyzer": {
	"type": "custom",
    "tokenizer": "edge_ngram_tokenizer",
    "filter": [
    	"lowercase",
        "trim",
        "edge_ngram_filter_front"]
}        

// Edge Ngram Back 분석기
"edge_ngram_analyzer_back": {
	"type": "custom",
    "tokenizer": "edge_ngram_tokenizer",
    "filter": [
    	"lowercase",
        "trim",
        "edge_ngram_filter_back"]
}

ac_test 인덱스 생성, 아래와 같이 index setting, mapping 시도

..전에 이런 csv 생성

 

index settings

{
  "number_of_shards": "5",
  "number_of_replicas": "1",
  "analysis": {
    "analyzer": {
      "ngram_analyzer": {
        "type": "custom",
        "tokenizer": "ngram_tokenizer",
        "filter": [
          "lowercase",
          "trim"]
      },
      "edge_ngram_analyzer": {
        "type": "custom",
        "tokenizer": "edge_ngram_tokenizer",
        "filter": [
          "lowercase",
          "trim",
          "edge_ngram_filter_front"]
      },
      "edge_ngram_analyzer_back": {
        "type": "custom",
        "tokenizer": "edge_ngram_tokenizer",
        "filter": [
          "lowercase",
          "trim",
          "edge_ngram_filter_back"]
      }
    },
    "tokenizer": {
      "ngram_tokenizer": {
        "type": "nGram",
        "min_gram": "1",
        "max_gram": "50",
        "token_chars": [
          "letter",
          "digit",
          "punctuation",
          "symbol"
          ]
      },
      "edge_ngram_tokenizer": {
        "type": "edgeNGram",
        "min_gram": "1",
        "max_gram": "50",
        "token_chars": [
          "letter",
          "digit",
          "punctuation",
          "symbol"
          ]
      }  
    },
    "filter": {
      "edge_ngram_filter_front": {
        "type": "edgeNGram",
        "min_gram": "1",
        "max_gram": "50",
        "side": "front"
      },
      "edge_ngram_filter_back": {
        "type": "edgeNGram",
        "min_gram": "1",
        "max_gram": "50",
        "side": "back"
      }
    }
  }
}

 

mappings

{
  "properties": {
    "kname": {
      "type": "text",
      "boost": 30
    },
    "knameNgram": {
      "type": "text",
      "analyzer": "ngram_analyzer",
      "search_analyzer": "ngram_analyzer",
      "boost": 3
    },
    "knameNgramEdge": {
      "type": "text",
      "analyzer": "edge_ngram_analyzer",
      "search_analyzer": "edge_ngram_analyzer",
      "boost": 2
    },
    "knameNgramEdgeBack": {
      "type": "text",
      "analyzer": "edge_ngram_analyzer_back",
      "search_analyzer": "ngram_analyzer",
      "boost": 1
    }
  }
}

위와 같은 에러 발생

에러 해결한 페이지 -> https://stackoverflow.com/questions/57395902/trying-to-set-the-max-gram-and-min-gram-in-elasticsearch

max_ngram_diff를 정의하여 해결

 

자동완성 테스트

입력

아래는 위 입력에 따른 결과

{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 71,
      "relation" : "eq"
    },
    "max_score" : 59.193916,
    "hits" : [
      {
        "_index" : "ac_test",
        "_type" : "_doc",
        "_id" : "7QYKdIQBWnIaSm9IbmS8",
        "_score" : 59.193916,
        "_source" : {
          "kname" : "모나스트렐 모",
          "knameNgramEdge" : "모나스트렐 모",
          "knameNgram" : "모나스트렐 모",
          "knameNgramEdgeBack" : "모나스트렐 모"
        }
      },
      {
        "_index" : "ac_test",
        "_type" : "_doc",
        "_id" : "kQYKdIQBWnIaSm9Ib4nv",
        "_score" : 58.27175,
        "_source" : {
          "kname" : "SF 모나스트렐",
          "knameNgramEdge" : "SF 모나스트렐",
          "knameNgram" : "SF 모나스트렐",
          "knameNgramEdgeBack" : "SF 모나스트렐"
        }
      },
      {
        "_index" : "ac_test",
        "_type" : "_doc",
        "_id" : "RwYKdIQBWnIaSm9IbVy9",
        "_score" : 58.06884,
        "_source" : {
          "kname" : "롤카 모나스트렐",
          "knameNgramEdge" : "롤카 모나스트렐",
          "knameNgram" : "롤카 모나스트렐",
          "knameNgramEdgeBack" : "롤카 모나스트렐"
        }
      },
      {
        "_index" : "ac_test",
        "_type" : "_doc",
        "_id" : "dAYKdIQBWnIaSm9IbE5l",
        "_score" : 56.657745,
        "_source" : {
          "kname" : "모나스띠에 쉬라즈",
          "knameNgramEdge" : "모나스띠에 쉬라즈",
          "knameNgram" : "모나스띠에 쉬라즈",
          "knameNgramEdgeBack" : "모나스띠에 쉬라즈"
        }
      },
      {
        "_index" : "ac_test",
        "_type" : "_doc",
        "_id" : "jgYKdIQBWnIaSm9Ib4nv",
        "_score" : 56.535797,
        "_source" : {
          "kname" : "SF 모나스트렐 로블",
          "knameNgramEdge" : "SF 모나스트렐 로블",
          "knameNgram" : "SF 모나스트렐 로블",
          "knameNgramEdgeBack" : "SF 모나스트렐 로블"
        }
      },
      {
        "_index" : "ac_test",
        "_type" : "_doc",
        "_id" : "qwYKdIQBWnIaSm9IbVi9",
        "_score" : 55.2638,
        "_source" : {
          "kname" : "바라온다 모나스트렐",
          "knameNgramEdge" : "바라온다 모나스트렐",
          "knameNgram" : "바라온다 모나스트렐",
          "knameNgramEdgeBack" : "바라온다 모나스트렐"
        }
      },
      {
        "_index" : "ac_test",
        "_type" : "_doc",
        "_id" : "VwYKdIQBWnIaSm9IaSG6",
        "_score" : 55.203705,
        "_source" : {
          "kname" : "모나 리자",
          "knameNgramEdge" : "모나 리자",
          "knameNgram" : "모나 리자",
          "knameNgramEdgeBack" : "모나 리자"
        }
      },
      {
        "_index" : "ac_test",
        "_type" : "_doc",
        "_id" : "dwYKdIQBWnIaSm9Ib4rw",
        "_score" : 55.045807,
        "_source" : {
          "kname" : "살리나, 모나스트렐",
          "knameNgramEdge" : "살리나, 모나스트렐",
          "knameNgram" : "살리나, 모나스트렐",
          "knameNgramEdgeBack" : "살리나, 모나스트렐"
        }
      },
      {
        "_index" : "ac_test",
        "_type" : "_doc",
        "_id" : "dQYKdIQBWnIaSm9IbE5l",
        "_score" : 55.023262,
        "_source" : {
          "kname" : "모나스띠에 소비뇽 블랑",
          "knameNgramEdge" : "모나스띠에 소비뇽 블랑",
          "knameNgram" : "모나스띠에 소비뇽 블랑",
          "knameNgramEdgeBack" : "모나스띠에 소비뇽 블랑"
        }
      },
      {
        "_index" : "ac_test",
        "_type" : "_doc",
        "_id" : "QgYKdIQBWnIaSm9Ib4rw",
        "_score" : 54.9273,
        "_source" : {
          "kname" : "마이 펫, 모나스트렐 로제",
          "knameNgramEdge" : "마이 펫, 모나스트렐 로제",
          "knameNgram" : "마이 펫, 모나스트렐 로제",
          "knameNgramEdgeBack" : "마이 펫, 모나스트렐 로제"
        }
      }
    ]
  }
}

 


java-cafe 설치, 초성 검색 - 오타 교정 - 한영 변환 구현

 

https://github.com/javacafe-project/elasticsearch-plugin/releases/tag/v7.0.0

 

Release javacafe-analyzer-7.0.0 · javacafe-project/elasticsearch-plugin

자바카페 검색 플러그인 엘라스틱서치 7.0.0 버전 대응 플러그인

github.com

설치한 압축파일의 plugin-descriptor.properties 이렇게 수정

아래와 같이 플러그인 설치

 

한글 초성 검색 구현하기

ac_test 인덱스 생성, 아래와 같이 index setting, mapping 시도

..전에 이런 csv 생성

index settings

{
  "number_of_shards": "5",
  "number_of_replicas": "1",
  "max_ngram_diff": "50",
  "analysis": {
    "analyzer": {
      "chosung_index_analyzer": {
        "type": "custom",
        "tokenizer": "keyword",
        "filter": [
          "javacafe_chosung_filter",
          "lowercase",
          "trim",
          "edge_ngram_filter_front"
          ]
      },
      "chosung_search_analyzer": {
        "type": "custom",
        "tokenizer": "keyword",
        "filter": [
          "javacafe_chosung_filter",
          "lowercase",
          "trim"
          ]
      }
    },
    "tokenizer": {
      "edge_ngram_tokenizer": {
        "type": "edgeNGram",
        "min_gram": "1",
        "max_gram": "50",
        "token_chars": [
          "letter",
          "digit",
          "punctuation",
          "symbol"
          ]
      }
    },
    "filter": {
      "edge_ngram_filter_front": {
        "type": "edgeNGram",
        "min_gram": "1",
        "max_gram": "50",
        "side": "front"
      },
      "javacafe_chosung_filter": {
        "type": "javacafe_chosung"
      }
    }
  }
}

mapping

{
  "properties": {
    "kname": {
      "type": "text",
      "boost": 30
    },
    "knameChosung": {
      "type": "text",
      "analyzer": "chosung_index_analyzer",
      "search_analyzer": "chosung_search_analyzer",
      "boost": 10
    }
  }
}

테스트, 초성 검색 수행하기

성공!

 


Term Suggester API를 이용한 오타 교정

 

엘라스틱서치에서는 철자를 교정하기 위해서 suggest API를 제공하고 있지만, 기본적으로 한글의 경우에는 잘 동작하지 않는다. 한글을 자소 단위로 분해해서 넣지 않으면 편집거리가 글자별로 적용되기 때문에 한글에 적용하기에는 다소 무리가 있다.

따라서, 자바카페 플러그인 내부에 오타 교정을 위한 로직을 활용해서 오타 교정을 구현해본다.

 

우선, 아래와 같이 index setting을 수행한다.

index setting

{
  "number_of_shards": 1,
  "analysis": {
    "analyzer": {
      "kor_spell_analyzer": {
        "type": "custom",
        "tokenizer": "standard",
        "filter": [
          "trim",
          "lowercase",
          "javacafe_spell"
          ]
      }
    }
  }
}

mapping

{
  "properties": {
    "kname": {
      "type": "text",
      "copy_to": ["suggest"]
    },
    "suggest": {
      "type": "completion",
      "analyzer": "kor_spell_analyzer"
    }
  }
}

 


한영 변환 기능 구현

ac_test 인덱스 생성, 아래와 같이 index setting, mapping 시도

 

 

index settings

{
  "number_of_shards": 1,
  "analysis": {
    "analyzer": {
    "kor2eng_analyzer": {
      "type": "custom",
      "tokenizer": "standard",
      "filter": [
        "trim",
        "lowercase",
        "javacafe_kor2eng"
        ]
    },
    "eng2kor_analyzer": {
      "type": "custom",
      "tokenizer": "standard",
      "filter": [
        "trim",
        "lowercase",
        "javacafe_eng2kor"
        ]
      }
    }
  }
}

Mappings

{
  "properties": {
    "ename": {
      "type": "text",
      "copy_to": ["kor2eng_suggest"]
    },
    "kname": {
      "type": "text",
      "copy_to": ["eng2kor_suggest"]
    },
    "kor2eng_suggest": {
      "type": "text",
      "analyzer": "standard",
      "search_analyzer": "kor2eng_analyzer"
    },
    "eng2kor_suggest": {
      "type": "text",
      "analyzer": "standard",
      "search_analyzer": "eng2kor_analyzer"
    }
  }
}

한영 변환 테스트

 


엘라스틱 서치를 이용한 다양한 검색 보조 기능을 구현해보았다.

 

이 기능들을 앞으로 어떻게 쓸지에 대해서는 많은 의논이 필요해보인다.

끝!

'ElasticSearch' 카테고리의 다른 글

로그스태시(Logstash) 기본  (0) 2022.11.21
개인기록 - 오타교정 및 자동완성  (0) 2022.11.15
엘라스틱서치, 검색  (0) 2022.11.10
인덱스 템플릿과 분석기  (0) 2022.11.09
엘라스틱서치 기본  (0) 2022.11.09
    'ElasticSearch' 카테고리의 다른 글
    • 로그스태시(Logstash) 기본
    • 개인기록 - 오타교정 및 자동완성
    • 엘라스틱서치, 검색
    • 인덱스 템플릿과 분석기
    JangGeonWu
    JangGeonWu

    티스토리툴바