ElasticSearch 6.4.x 노리(Nori) 형태소 분석기
ElasticSearch 는 루신을 기반으로 하는 전문 텍스트 검색 엔진이다. ElasticSearch 는 어떤 문장에 대해서 이를 분해한다. 문제는 ElasticSearch 가 외국에서 만든거라서 영어를 기반으로한 문장에 대해서는 분해하고 분석해준다. 하지만 그외에 대해서는 공백을 기반으로 문장을 분석한다.
여기서 문장에 대해서 좀 더 생각을 해봐야 한다. 문장을 분석할때에는 약간의 지식이 필요하다. 예를들어 다음과 같은게 있다고 하자.
고양이는 귀엽다
ElasticSearch 가 이 문장을 분석하면 ‘고양이는’, ‘귀엽다’ 로 분해한다. 하지만 이 문장에는 ‘고양이’ 라는 명사가 있고 ‘는’ 이라는 조사, ‘귀엽다’ 라는 동사가 존재한다. (정확한지는 나도 잘 모르겠다.)
이렇게 ElasticSearch 가 한글을 분석할 수 있도록하기 위해서는 한글이 이해시킬 수 있는 일종의 분석엔진 같은 것이 필요하게 되는데 이것이 바로 ‘한글 형태소 분석기’ 라고 한다. 몇가지 한글 형태소 분석기 엔진이 존재한다.
- 아리랑
- 은전한잎
- 노리(nori)
ElasticSearch 를 제작하는 Elastic 사에서는 한글 형태소 분석기 Nori(노리) 를 내놓았다. 최신버전에 잘 설치되며 잘 동작한다.
설치
설치는 플러그인 설치로 끝난다.
1 |
]$ sudo bin/elasticsearch-plugin remove analysis-nori |
설치할때는 마스터 노드, 데이터 노드에 설치를 해야 한다.
인덱스 분석에 노리 추가
ElasticSearch 는 알고 있겠지만 Index 단위로 데이터를 다룬다. Index 에는 데이터를 담기위한 그릇을 정의해야 하는데, 이것이 바로 Mapping 이다. 이 매핑에는 각 필드(Field)에 대한 데이터 타입을 정의하는데, 여기서 Index 에 대한 형태소 분석기로서 노리(Nori) 를 지정해줘야 한다. 기사를 인데싱하는 매핑에 예를 들면 다음과 같다.
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 |
{ "settings": { "index": { "analysis": { "tokenizer": { "nori_dict": { "type": "nori_tokenizer", "decompound_mode": "mixed" } }, "analyzer": { "korean": { "type": "custom", "tokenizer": "nori_dict" } } } } }, "mappings": { "article": { "properties": { "title": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } }, "analyzer": "korean" }, "written": { "type": "date", "format": "dateOptionalTime" }, "Content": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } }, "analyzer": "korean" } } } } } |
analysis 에서 tokenizer 에 nori_tokenizer 를 지정하고 analyzer 에 이것을 korean 으로 지정한다.
이렇게 매핑(Mapping) 을 정의하고 다음과 같이 한글 데이터를 입력해준다.
1 |
]$ curl -H 'Content-type: application/json' -XPOST http://127.0.0.1:9201/articles/article/ -d '{ "title": "韓원정대원 선배 \"홀어머니 두고 자연재해로 떠나..억울할 것\"", "date":"2018-10-13T08:35:35", "Content": "(서울=연합뉴스) 황재하 기자 = \"등반해보지도 못하고 자연재해로 그렇게 됐으니까 억울한 거죠. 힘 한 번 못 쓰고…허무하죠.\" 히말라야 등반 도중 베이스캠프 근처에서 눈 폭풍에 휩쓸리며 숨진 '2018 코리안웨이 구르자히말 원정대' 장비 담당 유영직(51) 대원의 안타까운 소식을 접한 선배 황모(52) 씨는 13일 연합뉴스와의 통화에서 이같이 말했다. 유"}' |
테스트
먼저 인덱스 검색대신 문장에 대한 분석을 어떻게 하는지에 대해서 살펴보자.
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 |
curl -H 'Content-type: application/json' -XGET 'http://192.168.96.6:9201/articles/_analyze' -d '{ "text": "고양이 냐옹냐옹", "explain": true }' { "detail": { "custom_analyzer": false, "analyzer": { "name": "default", "tokens": [ { "token": "고양이", "start_offset": 0, "end_offset": 3, "type": "<HANGUL>", "position": 0, "bytes": "[ea b3 a0 ec 96 91 ec 9d b4]", "positionLength": 1, "termFrequency": 1 }, { "token": "냐옹냐옹", "start_offset": 4, "end_offset": 8, "type": "<HANGUL>", "position": 1, "bytes": "[eb 83 90 ec 98 b9 eb 83 90 ec 98 b9]", "positionLength": 1, "termFrequency": 1 } ] } } } |
위 내용을 보면 기본 형태소 분석기를 이용해 ‘고양이 냐옹냐옹’ 을 분석한 결과다. 공백을 기준으로만 한글을 분석했음을 알 수 있다. 그렇다면 설치한 노리 한글 형태소 분석기는 어떤 결과를 보여줄까?
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
curl -H 'Content-type: application/json' -XGET 'http://192.168.96.6:9201/articles/_analyze' -d '{ "analyzer": "korean", "text": "고양이 냐옹냐옹", "explain": true }' { "detail": { "custom_analyzer": true, "tokenizer": { "name": "nori_dict", "tokens": [ { "token": "고양이", "start_offset": 0, "end_offset": 3, "type": "word", "position": 0, "bytes": "[ea b3 a0 ec 96 91 ec 9d b4]", "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "positionLength": 1, "reading": null, "rightPOS": "NNG(General Noun)", "termFrequency": 1 }, { "token": "냐", "start_offset": 4, "end_offset": 5, "type": "word", "position": 1, "positionLength": 2, "bytes": "[eb 83 90]", "leftPOS": "VCP(Positive designator)", "morphemes": "이/VCP(Positive designator)+냐/E(Verbal endings)", "posType": "INFLECT", "reading": null, "rightPOS": "E(Verbal endings)", "termFrequency": 1 }, { "token": "이", "start_offset": 4, "end_offset": 5, "type": "word", "position": 1, "bytes": "[ec 9d b4]", "leftPOS": "VCP(Positive designator)", "morphemes": null, "posType": "MORPHEME", "positionLength": 1, "reading": null, "rightPOS": "VCP(Positive designator)", "termFrequency": 1 }, { "token": "냐", "start_offset": 4, "end_offset": 5, "type": "word", "position": 2, "bytes": "[eb 83 90]", "leftPOS": "E(Verbal endings)", "morphemes": null, "posType": "MORPHEME", "positionLength": 1, "reading": null, "rightPOS": "E(Verbal endings)", "termFrequency": 1 }, { "token": "옹", "start_offset": 5, "end_offset": 6, "type": "word", "position": 3, "bytes": "[ec 98 b9]", "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "positionLength": 1, "reading": null, "rightPOS": "NNG(General Noun)", "termFrequency": 1 }, { "token": "냐", "start_offset": 6, "end_offset": 7, "type": "word", "position": 4, "positionLength": 2, "bytes": "[eb 83 90]", "leftPOS": "VCP(Positive designator)", "morphemes": "이/VCP(Positive designator)+냐/E(Verbal endings)", "posType": "INFLECT", "reading": null, "rightPOS": "E(Verbal endings)", "termFrequency": 1 }, { "token": "이", "start_offset": 6, "end_offset": 7, "type": "word", "position": 4, "bytes": "[ec 9d b4]", "leftPOS": "VCP(Positive designator)", "morphemes": null, "posType": "MORPHEME", "positionLength": 1, "reading": null, "rightPOS": "VCP(Positive designator)", "termFrequency": 1 }, { "token": "냐", "start_offset": 6, "end_offset": 7, "type": "word", "position": 5, "bytes": "[eb 83 90]", "leftPOS": "E(Verbal endings)", "morphemes": null, "posType": "MORPHEME", "positionLength": 1, "reading": null, "rightPOS": "E(Verbal endings)", "termFrequency": 1 }, { "token": "옹", "start_offset": 7, "end_offset": 8, "type": "word", "position": 6, "bytes": "[ec 98 b9]", "leftPOS": "NNG(General Noun)", "morphemes": null, "posType": "MORPHEME", "positionLength": 1, "reading": null, "rightPOS": "NNG(General Noun)", "termFrequency": 1 } ] } } } |
기본 형태소 분석기와는 다르게 한글에 대한 형태소 분석의 결과를 보여준다. ‘고양이’가 명사임을 인식하고 있다.
한글 형태소 분석은 이런것이다. 조사와 합쳐진 명사만을 검색어로 입력하더라도 검색 결과를 보여준다.