Nori에서 사용자 정의 사전을 사용하는 방법 (How to use a custom dictionary in Nori) - Elasticsearch 8.0 이하

NoCoDe·2021년 11월 12일
0

이 문서는 Nori를 개발한 Jim Ferenczi 님(프랑스 엔지니어)이 작성한 'How to use a custom dictionary in Nori'의 문서를 기반으로 Nori, Lucene, Elasticsearch 버전을 '21. 12 기준의 릴리즈 버전으로 빌드하기 위해서 원문을 한글로 번역하고 일부분은 보완한 문서 입니다.

원문

https://github.com/jimczi/nori/blob/master/how-to-custom-dict.asciidoc

Lucene의 한국어 분석기인 Nori는 mecab-ko-dic의 특정 버전에서 빌드되었습니다.
이 문서에서는 사용자 지정 사전을 사용하는 배포판을 만드는 방법을 보여줍니다.
이 작업은 수동으로 수행되며 여러 단계가 필요하므로 기존 사전에 일부 단어를 추가하려는 경우에는 이 작업을 수행하지 마십시오.
이것은 Elasticsearch’s plugin 자체에서 사용자 사전을 제공하여 수행되지만 도메인별 어휘가 많으면(수 천개) 추가 규칙을 사용하여 원래 사전을 다시 빌드한 이후에는 다음과 같은 장점을 얻을 수 있습니다:

  • user-dic(사용자 사전) 접근 방식과 비교하여 메모리 사용량을 줄입니다.
  • analyzer/tokenizer 생성 속도를 높입니다.
  • analysis 속도를 높입니다.

전제 조건

Lucene 과 Elasticsearch를 컴파일 해야 하므로 로컬 시스템에 ant , gradle 7.3+java 16+가 설치되어 있는지 확인하십시오.

로컬 시스템으로 MacOS를 사용하는 경우 컴파일에 필요한 autoconf , automakelibtool을 설치합니다.

$ brew install autoconf automake libtool coreutils

또한 MacOS에서는 ._ 백업 파일이 생성되는 것을 방지하기 위해서 .zshrc 파일또는 .bash_profile에 환경변수를 추가 합니다.

참고 : https://astrocoke.tistory.com/19

export COPYFILE_DISABLE=1

MeCab 설치

먼저 MeCab을 설치해야 합니다, mecab-ko를 다운로드 하십시오. (한국어용 MeCab fork) 이곳에서 다운로드 하세요.

다운로드 받은 파일의 압축을 해제한 디렉토리에서 다음의 명령을 실행하여 MeCab을 설치합니다:

$ ./configure
$ make
$ sudo make install

컴파일이 완료된 이후에 아래와 같이 실행할 수 있어야 합니다:

$ mecab -v
mecab of 0.996/ko-0.9.2

mecab-ko-dic 설치

mecab-ko-dic (다운로드)의 최신 버전을 다운로드 합니다. Nori가 기본적으로 사용하는 사전입니다. 다운로드 받은 파일의 압축을 해제한 디렉토리로 이동합니다:

$ tar xvf mecab-ko-dic-2.1.1-20180720.tar.gz
$ cd mecab-ko-dic-2.1.1-20180720

다음의 명령을 실행하여 사전을 설치합니다:

$ autoreconf
$ ./configure
$ make
$ sudo make install

사용자 정의 단어 추가

이 섹션에서는 이전 단계에서 다운로드 한 원래 배포판에 사용자 정의 단어를 추가하는 방법을 설명합니다.

mecab-ko-dic의 user-dic 디렉토리에 csv 확장자 (예: custom-words.csv )의 파일을 만듭니다. 디렉토리는 다음과 같습니다:

$ ls
AUTHORS           EP.csv            IC.csv            MM.csv            NNBC.csv          Person-actor.csv  README            VX.csv            XSV.csv           config.log        install-sh        model.def         unk.def
COPYING           ETM.csv           INSTALL           Makefile          NNG.csv           Person.csv        Symbol.csv        Wikipedia.csv     aclocal.m4        config.status     left-id.def       pos-id.def        unk.dic
ChangeLog         ETN.csv           Inflect.csv       Makefile.am       NNP.csv           Place-address.csv VA.csv            XPN.csv           autogen.sh        configure         matrix.bin        rewrite.def       user-dic
CoinedWord.csv    Foreign.csv       J.csv             Makefile.in       NP.csv            Place-station.csv VCN.csv           XR.csv            char.bin          configure.ac      matrix.def        right-id.def
EC.csv            Group.csv         MAG.csv           NEWS              NR.csv            Place.csv         VCP.csv           XSA.csv           char.def          dicrc             missing           sys.dic
EF.csv            Hanja.csv         MAJ.csv           NNB.csv           NorthKorea.csv    Preanalysis.csv   VV.csv            XSN.csv           clean             feature.def       model.bin         tools
$ ls user-dic
README.md        custom-words.csv nnp.csv          person.csv       place.csv

다른 파일 person.csv, place.csvnnp.csv 를 삭제합니다. (사용자 정의 항목의 예가 포함되어 있습니다):

$ rm user-dic/person.csv user-dic/place.csv user-dic/nnp.csv
$ ls user-dic
README.md        custom-words.csv
주의추가 파일 삭제는 필수 입니다, place.csv 에는 Nori가 구문 분석할 수 없는 항목이 들어 있습니다.

csv 파일에 사용자 정의 단어를 추가합니다:

  • 고유명사 추가:
대우,,,,NNP,*,F,대우,*,*,*,*
구글,,,,NNP,*,T,구글,*,*,*,*
  • 인명 추가:
까비,,,,NNP,인명,F,까비,*,*,*,*
  • 지역명 추가:
세종,,,,NNP,지명,T,세종,*,*,*,*
세종시,,,,NNP,지명,F,세종시,Compound,*,*,세종/NNP/지명+시/NNG/*

다른 품사를 추가해야 하는 경우 전체 목록을 이곳에서 확인 할 수 있습니다.

다음의 명령을 실행하십시오:

$ ./tools/add-userdic.sh

MacOs에서 'no such file or directory' 에러가 발생하면 아래의 URL을 참고하여 add-userdic.sh를 수정해 줍니다.

no such file or directory:/../dicrc 해결하기
https://lsjsj92.tistory.com/585

실행한 후 mecab-ko-dic 디렉토리는 다음과 같습니다:

$ ls
AUTHORS               ETN.csv               MAG.csv               NNBC.csv              Place-address.csv     VCP.csv               XSV.csv               configure             missing               unk.def
COPYING               Foreign.csv           MAJ.csv               NNG.csv               Place-station.csv     VV.csv                aclocal.m4            configure.ac          model.bin             unk.dic
ChangeLog             Group.csv             MM.csv                NNP.csv               Place.csv             VX.csv                autogen.sh            dicrc                 model.def             user-custom-words.csv
CoinedWord.csv        Hanja.csv             Makefile              NP.csv                Preanalysis.csv       Wikipedia.csv         char.bin              feature.def           pos-id.def            user-dic
EC.csv                IC.csv                Makefile.am           NR.csv                README                XPN.csv               char.def              install-sh            rewrite.def
EF.csv                INSTALL               Makefile.in           NorthKorea.csv        Symbol.csv            XR.csv                clean                 left-id.def           right-id.def
EP.csv                Inflect.csv           NEWS                  Person-actor.csv      VA.csv                XSA.csv               config.log            matrix.bin            sys.dic
ETM.csv               J.csv                 NNB.csv               Person.csv            VCN.csv               XSN.csv               config.status         matrix.def            tools

user-custom-words.csv 파일이 존재하고 사용자 정의 엔트리의 확장 버전이 포함되어 있는지 확인하십시오:

$ ls user-custom-words.csv
$ cat user-custom-words.csv
대우,1786,3545,3821,NNP,*,F,대우,*,*,*,*
구글,1786,3546,2953,NNP,*,T,구글,*,*,*,*
까비,1788,3549,5472,NNP,인명,F,까비,*,*,*,*
세종,1789,3553,5515,NNP,지명,T,세종,*,*,*,*
세종시,1789,3552,5497,NNP,지명,F,세종시,Compound,*,*,세종/NNP/지명+시/NNG/*

다음 명령을 사용하여 수정된 사전의 압축 파일을 만듭니다:

$ tar cvzf custom-mecab-ko-dic.tar.gz mecab-ko-dic-2.1.1-20180720

다음 섹션에서 이 압축파일을 사용하여 Lucene 모듈을 빌드 합니다.

Lucene 바이너리 사전 구축

Nori 모듈은 mecab-ko-dic 배포판에서 만든 바이너리 사전을 사용합니다. 이 섹션에서는 수정된 배포판을 사용하여 Lucene 한국어 모듈의 바이너리 사전을 작성합니다. 사전은 소스에서 빌드되고 jar 내부에 패키징되므로 Lucene을 체크아웃 해야 합니다. Lucene 8.10.1용 사용자 정의 jar를 생성 합니다:

$ git clone -b branch_8x https://github.com/apache/lucene-solr.git
$ cd lucene-solr
$ git checkout tags/releases/lucene-solr/8.10.1

텍스트 편집기 (예: vim)로 lucene/analysis/nori/ivy.xml 을 열고 URL의 주소 행을 수정 합니다:

<artifact name="mecab-ko-dic" type=".tar.gz" url="https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/mecab-ko-dic-2.0.3-20170922.tar.gz" />

URL의 주소 행은 아래와 같이 수정합니다:

<artifact name="mecab-ko-dic" type=".tar.gz" url="file:///change/me/custom-mecab-ko-dic.tar.gz " />

다음으로 lucene/analysis/nori/build.xml 을 열고 value 행을 수정 합니다:

<property name="dict.version" value="mecab-ko-dic-2.0.3-20170922" />

value 행은 아래와 같이 수정합니다:

<property name="dict.version" value="mecab-ko-dic-2.1.1-20180720" />

이렇게 하면 원본 사전이 이전 단계에서 수정한 사전으로 바뀝니다.

이 문서에서는 원문과 다르게 mecab-ko-dic 2.1.1 버전을 사용하므로 변경전 사전에는 다른 POS 태그 목록이 있고 일부 POS 태그에는 다른 ID가 있으므로 에러 없이 새로운 사전을 빌드하려면 JAVA 소스코드를 수정해야 합니다.

POS 태그 목록에 UNIT을 추가하고 UnknownDictionaryBuilder의 32행을 다음과 같이 변경하는 경우:

$ vim lucene-solr/lucene/analysis/nori/src/java/org/apache/lucene/analysis/ko/util/UnknownDictionaryBuilder.java

private static final String NGRAM_DICTIONARY_ENTRY = "NGRAM,1798,3559,3677,SY,*,*,*,*,*,*,*";

아래와 같이 수정합니다:

private static final String NGRAM_DICTIONARY_ENTRY = "NGRAM,1801,3566,3640,SY,*,*,*,*,*,*,*";

mecab ko dic

기존에 ant build한 mecab ko dic 과거 사전의 cache 압축 파일을 삭제 하기 위해서

~/.ivy2/cache 로 이동하여 아래의 명령을 실행 합니다:

$ rm -rf ./mecab

lucene/analysis/nori 로 이동하여 아래의 명령을 실행 합니다:

$ ant regenerate

그러면 src/resources/org/apache/lucene/analysis/ko/dict/ 의 새로운 사전에서 새 바이너리 사전을 생성합니다. 바이너리 사전이 존재하고 원본 사전과 다른지 확인하십시오:

$ git status .
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   ivy.xml
	modified:   src/resources/org/apache/lucene/analysis/ko/dict/TokenInfoDictionary$buffer.dat
	modified:   src/resources/org/apache/lucene/analysis/ko/dict/TokenInfoDictionary$fst.dat
	modified:   src/resources/org/apache/lucene/analysis/ko/dict/TokenInfoDictionary$targetMap.dat
	modified:   src/tools/java/org/apache/lucene/analysis/ko/util/TokenInfoDictionaryBuilder.java

no changes added to commit (use "git add" and/or "git commit -a")

이제 사용자 정의 사전을 사용하여 모듈을 배포할 jar를 만들 수 있습니다:

$ ant jar

사용자 정의 모듈의 jar는 lucene 체크아웃한 루트 경로의 lucene/build/analysis/nori/lucene-analyzers-nori-8.10.1-SNAPSHOT.jar 에서 찾을 수 있습니다. 이 파일을 복사하세요. 다음 단계에서 필요합니다.

Elasticsearch의 사용자 정의 플러그인 빌드

이 섹션에서는 이전 단계에서 생성된 Lucene 모듈 jar를 사용하여 Nori용 Elasticsearch 플러그인의 사용자 정의 버전을 빌드할 것입니다.

첫 번째 작업은 Elasticsearch 7.16.0 코드를 체크아웃 하는 것입니다.

$ git clone -b v7.16.0 https://github.com/elastic/elasticsearch

텍스트 편집기 (예: vim)로 elasticsearch/plugins/analysis-nori 로 이동하여 build.gradle 파일을 열고 다음의 행을 변경하십시오:

api "org.apache.lucene:lucene-analyzers-nori:${versions.lucene}"

아래와 같이 수정합니다:

api files('/change/me/lucene-analyzers-nori-8.10.1-SNAPSHOT.jar')

이렇게 하면 이전 단계에서 빌드된 jar를 사용하여 플러그인을 빌드하도록 gradle에 설정합니다.

analysis-nori 디렉토리에서 다음의 명령을 실행하여 플러그인에 사용자 지정 배포를 생성합니다:

$ gradle assemble
...
BUILD SUCCESSFUL in 2m 11s
36 actionable tasks: 28 executed, 8 up-to-date
$ ls build/distributions
analysis-nori-7.16.0-SNAPSHOT-javadoc.jar analysis-nori-7.16.0-SNAPSHOT-sources.jar analysis-nori-7.16.0-SNAPSHOT.jar         analysis-nori-7.16.0-SNAPSHOT.pom         analysis-nori-7.16.0-SNAPSHOT.zip

명령이 성공하면 Elasticsearch 내에서 사용할 수 있는 zip 배포판이 build/distributions에 있습니다. 이 파일을 복사합니다. 다음 단계에서 필요합니다.

Elasticsearch에서 테스트

Elasticsearch 에서 7.16.0 버전을 다운로드 받습니다.

압축파일을 해제한 후 Elasticsearch 디렉토리에서 다음 명령을 실행합니다:

$ ./bin/elasticsearch-plugin install file:///change/me/analysis-nori-7.16.0-SNAPSHOT.zip

이제 완료 되었습니다, Elasticsearch를 시작하고 사용자 정의 단어가 인식되는지 확인할 수 있습니다:

$ ./bin/elasticsearch

다음과 같이 Nori 분석기를 사용해 보십시오:

POST _analyze
{
	"text": "대우그룹",
	"analyzer": "nori",
	"explain": true
}

결과는 다음과 같아야 합니다:

{
    "detail": {
        "custom_analyzer": false,
        "analyzer": {
            "name": "org.apache.lucene.analysis.ko.KoreanAnalyzer",
            "tokens": [
                {
                    "token": "대우",
                    "start_offset": 0,
                    "end_offset": 2,
                    "type": "word",
                    "position": 0,
                    "bytes": "[eb 8c 80 ec 9a b0]",
                    "leftPOS": "NNP(Proper Noun))",
                    "morphemes": null,
                    "posType": "MORPHEME",
                    "positionLength": 1,
                    "reading": null,
                    "rightPOS": "NNP(Proper Noun)",
                    "termFrequency": 1
                },
                {
                    "token": "그룹",
                    "start_offset": 2,
                    "end_offset": 4,
                    "type": "word",
                    "position": 1,
                    "bytes": "[ea b7 b8 eb a3 b9]",
                    "leftPOS": "NNG(General Noun)",
                    "morphemes": null,
                    "posType": "MORPHEME",
                    "positionLength": 1,
                    "reading": null,
                    "rightPOS": "NNG(General Noun)",
                    "termFrequency": 1
                }
            ]
        }
    }
}

지금부터는 mecab-ko-dic 버전을 최신 버전으로 빌드해야 하는 이유를 확인하기 위해서 Elasticsearch plugin으로 배포되는 Nori plugin과 사용자 정의로 빌드한 Nori plugin의 분석 결과를 비교해 보겠습니다.

이전 단계에서 설치한 Nori plugin을 삭제한 후 배포판 Nori plugin을 설치 합니다. Elasticsearch 디렉토리에서 다음 명령을 실행합니다:

$ ./bin/elasticsearch-plugin remove analysis-nori
$ ./bin/elasticsearch-plugin install analysis-nori

Elasticsearch를 시작하고 배포판의 분석결과를 확인합니다:

./bin/elasticsearch

다음과 같이 Nori의 분석결과를 요청 합니다:

POST _analyze
{
	"text": "아버지가방에들어가신다.",
	"analyzer": "nori",
	"explain": true
}

결과는 다음과 같이 '가방'이 명사로 분석되는 것을 확인하실 수 있습니다:

{
  "detail" : {
    "custom_analyzer" : false,
    "analyzer" : {
      "name" : null,
      "tokens" : [
        {
          "token" : "아버지",
          "start_offset" : 0,
          "end_offset" : 3,
          "type" : "word",
          "position" : 0,
          "bytes" : "[ec 95 84 eb b2 84 ec a7 80]",
          "leftPOS" : "NNG(General Noun)",
          "morphemes" : null,
          "posType" : "MORPHEME",
          "positionLength" : 1,
          "reading" : null,
          "rightPOS" : "NNG(General Noun)",
          "termFrequency" : 1
        },
        {
          "token" : "가방",
          "start_offset" : 3,
          "end_offset" : 5,
          "type" : "word",
          "position" : 1,
          "bytes" : "[ea b0 80 eb b0 a9]",
          "leftPOS" : "NNG(General Noun)",
          "morphemes" : null,
          "posType" : "MORPHEME",
          "positionLength" : 1,
          "reading" : null,
          "rightPOS" : "NNG(General Noun)",
          "termFrequency" : 1
        },
        {
          "token" : "들어가",
          "start_offset" : 6,
          "end_offset" : 9,
          "type" : "word",
          "position" : 3,
          "bytes" : "[eb 93 a4 ec 96 b4 ea b0 80]",
          "leftPOS" : "VV(Verb)",
          "morphemes" : null,
          "posType" : "MORPHEME",
          "positionLength" : 1,
          "reading" : null,
          "rightPOS" : "VV(Verb)",
          "termFrequency" : 1
        }
      ]
    }
  }
}

다음으로 이전 단계와 같이 사용자 정의 Nori plugin을 설치 합니다. Elasticsearch 디렉토리에서 다음 명령을 실행합니다:

$ ./bin/elasticsearch-plugin install file:///change/me/analysis-nori-7.16.0-SNAPSHOT.zip

Elasticsearch를 시작합니다:

$ ./bin/elasticsearch

같은 문장으로 Nori에 분석요청을 합니다:

POST _analyze
{
	"text": "아버지가방에들어가신다.",
	"analyzer": "nori",
	"explain": true
}

결과는 다음과 같이 '방'으로 분석 됩니다:

{
  "detail" : {
    "custom_analyzer" : false,
    "analyzer" : {
      "name" : null,
      "tokens" : [
        {
          "token" : "아버지",
          "start_offset" : 0,
          "end_offset" : 3,
          "type" : "word",
          "position" : 0,
          "bytes" : "[ec 95 84 eb b2 84 ec a7 80]",
          "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" : 2,
          "bytes" : "[eb b0 a9]",
          "leftPOS" : "NNG(General Noun)",
          "morphemes" : null,
          "posType" : "MORPHEME",
          "positionLength" : 1,
          "reading" : null,
          "rightPOS" : "NNG(General Noun)",
          "termFrequency" : 1
        },
        {
          "token" : "들어가",
          "start_offset" : 6,
          "end_offset" : 9,
          "type" : "word",
          "position" : 4,
          "bytes" : "[eb 93 a4 ec 96 b4 ea b0 80]",
          "leftPOS" : "VV(Verb)",
          "morphemes" : null,
          "posType" : "MORPHEME",
          "positionLength" : 1,
          "reading" : null,
          "rightPOS" : "VV(Verb)",
          "termFrequency" : 1
        }
      ]
    }
  }
}
profile
Search engine developer / platform architect - software engineer

0개의 댓글