Patrick's 데이터 세상

형태소 분석기 정리, 사용자 사전 추가 feat. Pororo, Okt, Mecab, Soynlp, Kiwi 본문

Deep Learning/NLP 개발

형태소 분석기 정리, 사용자 사전 추가 feat. Pororo, Okt, Mecab, Soynlp, Kiwi

patrick610 2023. 2. 28. 18:23
반응형
SMALL

 

 

 

 

이번 포스팅에서는 회사 업무에서 사용했던 형태소 분석기의 간략한 설명과 code 예시를 정리해보려고 한다.

형태소 분석기는 Pororo, Okt(Open Korean Text), Mecab, Soynlp LTokenizer 등을 사용하였다.

 

 

구축 환경 : Google Colaboratory Pro Plus

 

 

👉🏻 Pororo

뽀로로는 카카오 브레인(Kakao Brain)에서 개발한 자연어 처리 라이브러리이다.

자연어 처리와 음성 관련 태스크를 수행하기 위한 목적으로 만들어졌다.

뽀로로는 설치가 좀 까다롭다. model을 load 하는 방식이다 보니 cuda를 사용하는 부분이 있는데 server에서 따로 container 가상화를 구축하고 있지 않은 경우에서는 충돌이 일어나기 때문에 내 정신 건강을 위해 코랩 환경에서 속 편하게 torch 삭제 후 cpuonly torch로 재설치했다...

python 3.9 버전에서는 설치되지 않는 것으로 보임.

 

Install

pip3 install pip==20.0.2 pororo fairseq==0.10.2

pip3 uninstall torch torchvision
pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cpu   # cpuonly
import torch

print("Torch version:{}".format(torch.__version__))

 

Source

from pororo import Pororo

Pororo.available_models("dp")
dp = Pororo(task="dep_parse", lang="ko")

dp('중간고사 점수 내가 반에서 제일 잘 받음')

Pororo.available_models 태스크는 92개로 굉장히 많다.

Available tasks are ['mrc', 'rc', 'qa', 'question_answering', 'dp',... 'qg', 'question_generation', 'age_suitability']

그중에서도 task를 dp(Dependency Parsing, 의존성 구문 분석)로 세팅하고 Pororo에 작업 종류를 'dep_parse'로 언어를 'ko'로 파라미터 넣고 인스턴스 생성한다.

생성된 인스턴스에 '중간고사 점수 내가 반에서 제일 잘 받음' 해당 문장을 넣으면 위처럼 의존 구문 분석되어 return 된다. (index 0: num, index 1: token, index 2: token head, index 3: pos)

참고

 

 

 

👉🏻 Okt

Okt(Open Korean Text)는 트위터에서 만든 오픈소스 한국어 처리기인 twitter-korean-text를 이어서 만드는 프로젝트이다.

명사, 동사, 부사 위주로 사용하는 것을 목표로 삼았기 때문에 속도 면에서 꼬꼬마, 코모란 보다 속도가 매우 빠르다.

 

 

Install

pip3 install konlpy
curl -s https://raw.githubusercontent.com/teddylee777/machine-learning/master/99-Misc/01-Colab/mecab-colab.sh | bash

Okt는 konlpy가 설치되어 있으면 바로 사용할 수 있다.

colab에서 설치하는 두 번째 코드로 사용하면 mecab과 okt 둘 다 사용할 수 있다.

 

 

Source

from konlpy.tag import Okt

okt = Okt()

print(f"pos 품사 추출 : {okt.pos('중간고사 점수 내가 반에서 제일 잘 받음')}")
print(f"morphs 형태소 추출 : {okt.morphs('중간고사 점수 내가 반에서 제일 잘 받음')}")
print(f"nouns 명사 추출 : {okt.nouns('중간고사 점수 내가 반에서 제일 잘 받음')}")
print(f"phrases 구 추출 : {okt.phrases('중간고사 점수 내가 반에서 제일 잘 받음')}")

okt에서 가장 많이 사용하는 method이다.

형태소에 품사를 붙여서 추출 : pos, 형태소만 추출 : morphs, 명사 추출 : nouns, 구 추출 :phrases 

twitter 구어체를 기반으로 개발되었기 때문에 구어체 tokenize에 좀 더 적합하다 판단하여 채택하여 사용하였다.

구 추출이 필요한 상황이 있었는데 다른 형태소 분석기에서 없어서 okt의 phrases를 활용하여 사용하였다.

 

 

Add User Dictionary

from konlpy.tag import Okt

okt = Okt()

print(okt.pos("순대국 먹고 싶다."))
print(okt.pos("순댓국 먹고 싶다."))
print(okt.pos("패스트파이브에서 일을 합니다."))
print(okt.pos("아이오아이는 정말 이뻐요."))

순댓국, 패스트파이브 등이 잘 추출이 안 되는 모습.

사용자 사전으로 명사에 추가해보자.

 

# 패키지에 설치된 java 경로에 jar파일 압축 풀기
import os

os.chdir('/usr/local/lib/python3.8/dist-packages/konlpy/java')
os.getcwd() 

jar xvf open-korean-text-2.1.0.jar


# data 확인
with open(f"/usr/local/lib/python3.8/dist-packages/konlpy/java/org/openkoreantext/processor/util/noun/names.txt") as f:
    data = f.read()
    
print(data)

# 새 단어 추가
data += '순댓국\n순대국\n아이오아이\n패스트파이브\n'

# 사전 저장
with open("/usr/local/lib/python3.8/dist-packages/konlpy/java/org/openkoreantext/processor/util/noun/names.txt", 'w') as f:
    f.write(data)
    
print(data)

# 재압축
jar cvf open-korean-text-2.1.0.jar org
rm org


# test
from konlpy.tag import Okt

okt = Okt()

print(okt.pos("순대국 먹고 싶다."))
print(okt.pos("순댓국 먹고 싶다."))
print(okt.pos("패스트파이브에서 일을 합니다."))
print(okt.pos("아이오아이는 정말 이뻐요."))

정상적으로 추출되는 것을 볼 수 있다.

 

 

👉🏻 Mecab

일본어 형태소 분석기로 개발된 메캅(영어 표기 미캡)이다.

일본어와 문법 체계가 비슷한 한국어를 위해 Mecab을 한국어용으로 포팅한 은전한닢이라는 프로젝트가 진행되어 구축되어 한국어 자연어처리 발전에 크게 기여한 형태소 분석기이다.

체감 상으로 보편적으로 가장 많이 쓰는 형태소 분석기이고 설치가 굉장히 번거롭다는 단점이 있다. 아래 설치 방법.

Mecab 설치

colab 환경에서는 아래 코드 한 줄로 설치 가능하다.

 

 

Install

curl -s https://raw.githubusercontent.com/teddylee777/machine-learning/master/99-Misc/01-Colab/mecab-colab.sh | bash

 

 

Source

from konlpy.tag import Mecab

m = Mecab()

print(f"pos 품사 추출 : {m.pos('중간고사 점수 내가 반에서 제일 잘 받음')}")
print(f"morphs 형태소 추출 : {m.morphs('중간고사 점수 내가 반에서 제일 잘 받음')}")
print(f"nouns 명사 추출 : {m.nouns('중간고사 점수 내가 반에서 제일 잘 받음')}")

mecab은 pos, morphs, nouns 3개의 메서드가 있다.

형태소에 품사를 붙여서 추출 : pos, 형태소만 추출 : morphs, 명사 추출 : nouns

여담으로 최근에 파이썬에서 설치 없이 사용하는 Pecab이라는 라이브러리도 사용해 봤는데 오류도 있고 속도가 굉장히 느려서 쓸 것이 못될 듯하다.

 

 

Add User Dictionary

from konlpy.tag import Mecab

m = Mecab()

print(m.pos("순대국 먹고 싶다."))
print(m.pos("순댓국 먹고 싶다."))
print(m.pos("패스트파이브에서 일을 합니다."))
print(m.pos("아이오아이는 정말 이뻐요."))

순대국, 패스트파이브 등이 잘 추출이 안 되는 모습.

마찬가지로 사용자 사전으로 명사에 추가해 보자.

 

# 종성 추가
pip3 install jamo

from jamo import h2j, j2hcj

def get_jongsung_TF(sample_text):
    sample_text_list = list(sample_text)
    last_word = sample_text_list[-1]
    last_word_jamo_list = list(j2hcj(h2j(last_word)))
    last_jamo = last_word_jamo_list[-1]

    jongsung_TF = "T"

    if last_jamo in ['ㅏ', 'ㅑ', 'ㅓ', 'ㅕ', 'ㅗ', 'ㅛ', 'ㅜ', 'ㅠ', 'ㅡ', 'ㅣ', 'ㅘ', 'ㅚ', 'ㅙ', 'ㅝ', 'ㅞ', 'ㅢ', 'ㅐ,ㅔ', 'ㅟ', 'ㅖ', 'ㅒ']:
        jongsung_TF = "F"

    return jongsung_TF
    

# tmp 경로에 저장된 사용자 사전 명사에 새로운 단어 추가
with open('/tmp/mecab-ko-dic-2.1.1-20180720/user-dic/nnp.csv', "r", encoding='utf-8') as f:
  user_dict = f.readlines()

# 단어 추가
add_list = ['순댓국', '순대국', '아이오아이', '패스트파이브']

for word in add_list:
  jongsung_TF = get_jongsung_TF(word)
  line = '{},*,*,*,NNP,*,{},{},*,*,*,*,*\n'.format(word, jongsung_TF, word)

  user_dict.append(line)
  
print(user_dict[-10:])

# 재저장
with open('/tmp/mecab-ko-dic-2.1.1-20180720/user-dic/nnp.csv', 'w', encoding='utf-8') as f:
  for line in user_dict:
    f.write(line)
    
# test
from konlpy.tag import Mecab

m = Mecab(dicpath='/tmp/mecab-ko-dic-2.1.1-20180720')

print(m.pos("순대국 먹고 싶다."))
print(m.pos("순댓국 먹고 싶다."))
print(m.pos("패스트파이브에서 일을 합니다."))
print(m.pos("아이오아이는 정말 이뻐요."))

인스턴스 생성 시에 dicpath 파라미터로 tmp에 사용자 사전을 저장한 경로로 지정해 준다.

정상적으로 추출하는 것을 볼 수 있다.

 

 

👉🏻 Soynlp

다음 형태소 분석기는 Soynlp의 LTokenizer이다.

Soynlp는 학습하지 못한 단어를 인식하지 못하는 미등록단어(OOV, out of vocabulary) 이슈에 대응하는 방법으로 사용자 사전과 형태소 분석 없이 문자열에서 왼쪽부터 각 문맥이 주어졌을 때 그다음 글자가 나올 확률을 계산해서 누적곱을 하는 WordExtractor의 cohesion score를 활용한 한국어 자연어 처리 라이브러리이다.

여기서 L-Tokenizer는 왼쪽에 오는 L토큰인 체언(명사, 대명사)이나 동사, 형용사 등 다음 오른쪽에 오는 R토큰인 조사, 동사, 형용사인데 L토큰 점수를 비교하여 가장 점수가 높은 다음 단어를 찾는 것.

 

 

Install

pip3 install soynlp

 

 

Source

df.shape
(9141, 1)

sentences = df.u.values.tolist()
sentences = df.u.values.tolist()
sentences = sum(sentences, [])
print(sentences[:5])
['나 중간고사 반에서 1등했어', '중간고사 점수 내가 반에서 제일 잘 받음', '나 반에서 중간 성적 제일 좋아', '우리 반에서 내가 시험 제일 잘 봤다', '중간고사 반 1등 먹음']

 

총 9,141개의 대화 리스트 생성.

# WordExtractor
from soynlp.word import WordExtractor

word_extractor = WordExtractor(
    min_frequency=10,
    min_cohesion_forward=0.05, 
    min_right_branching_entropy=0.0
)

word_extractor.train(sentences)  # list of str or like
# word_extractor.save(cfg.soynlp)    # 모델 저장

words = word_extractor.extract()
print(len(words))
print(words['중간고사'])

생성된 9,141개의 문장을 토대로 WordExtractor는 통계치를 이용하여 각 단어의 경계 점수를 학습하여 return 한다.

from soynlp.tokenizer import LTokenizer

cohesion_score = {word:score.cohesion_forward for word, score in words.items()}
tokenizer = LTokenizer(scores=cohesion_score)

sent = "내가 중간고사에서 점수 제일 잘 받았다."
print(tokenizer.tokenize(sent))
['내가', '중간고사', '에서', '점수', '제일', '잘', '받았', '다.']

각 단어의 경계 점수가 있는 words 변수를 활용하여 cohension 점수를 토대로 LTokenizer를 사용한다.

임의의 문장을 넣었을 때 꽤나 형태소가 잘 분리되는 것으로 보인다.

 

 

Add User Dictionary

soynlp에서는 WordExtractor에 새로운 단어를 추가하여 cohension 점수를 고정으로 1로 지정하여 추출하게 하는 방법으로 사용자 사전을 사용한다.

sent = "홍대 치킨집이 선행했대. 레게노다. 돈쭐내주자!"
print(tokenizer.tokenize(sent))
['홍대', '치킨', '집이', '선행했대.', '레게노다.', '돈쭐내주자!']

 

위에서 생성된 LTokenizer에 새로운 아무런 문장을 추가하면 선행, 레게노, 돈쭐 등의 단어를 추출하지 못하는 것을 볼 수 있다.

cohesion_score["선행"] = 1.0
cohesion_score["레게노"] = 1.0
cohesion_score["돈쭐"] = 1.0

위 코드에서 cohension_score dict 변수에 직접 추가한다.

sent = "홍대 치킨집이 선행했대. 레게노다. 돈쭐내주자!"
print(tokenizer.tokenize(sent))
['홍대', '치킨', '집이', '선행', '했대.', '레게노', '다.', '돈쭐', '내주자!']

필요한 토큰이 잘 분리되었다.

 

 

👉🏻 Kiwi

한국어 형태소 분석기인 Kiwi(Korean Intelligent Word Identifier)의 Python 모듈.

C++로 작성되었고 다른 패키지에 의존성이 없는 것이 특징.

품사 태그는 Mecab과 같이 세종 품사 태그를 기초로 하되, 일부 품사 태그를 추가/수정하여 사용한다.

 

기존 형태소 분석기보다 꽤나 괜찮다는 소식이 있어 실무에 적용해보았다.

 

 

Install

pip3 install kiwipiepy

 

Source

from kiwipiepy import Kiwi
from kiwipiepy.utils import Stopwords
kiwi = Kiwi()
stopwords = Stopwords()

 

Add User Dictionary

 

사용자 사전 직접 추가

kiwi는 add_user_word 메서드로 굉장히 편하게 사용자 사전에 새로운 형태소를 추가할 수 있다.

kiwi.add_user_word(word, tag, score, orig_word)
kiwi.add_user_word('맛점', 'NNP', 0)

word : 등록할 형태소. 현재는 공백 문자를 포함하지 않은 문자열만 등록 가능.

tag : 등록할 형태소의 품사. 기본값 NNP.

score : 등록할 형태소의 점수. 동일한 형태로 분석될 가능성이 있는 경우 값이 클수록 해당 형태소가 더 우선권을 가지고 추출.

orig_word : 추가할 형태소가 특정 형태소의 변형인 경우 이 인자로 원본 형태소를 넘겨줄 수 있고 없는 경우 생략.

 

사용자 사전 입력 성공 시 True, 실패 시 False 반환.

 

코퍼스에서 미등록 단어 추출하여 추가

Kiwi.extract_words(texts, min_cnt, max_word_len, min_score)
Kiwi.extract_add_words(texts, min_cnt, max_word_len, min_score, pos_score)

texts : Iterable[str] 형태의 분석할 텍스트.
min_cnt : 추출할 단어가 입력 텍스트 내에서 최소 몇 번 이상 등장하는 지 결정.

max_word_len : 추출할 단어의 최대 길이.

min_score : 추출할 단어의 최소 단어 점수.

pos_socre : 추출할 단어의 최소 명사 점수.

lm_filter : 품사 및 언어 모델을 이용한 필터링 사용 여부.

 

 

그 외에도 다양한 기능으로 사용자 사전을 관리 할 수 있다.

# 사용자 사전에 기분석 형태를 등록
Kiwi.add_pre_analyzed_word(form, analyzed, score=0.0)
# 규칙에 의해 변형된 형태소를 일괄적으로 추가
Kiwi.add_rule(tag, replacer, score)
# add_rule 메서드와 동일한 역할을 수행하되, 변형 규칙에 정규표현식을 사용
Kiwi.add_re_rule(tag, pattern, repl, score)
# 파일로부터 사용자 사전을 불러옴.
Kiwi.load_user_dictionary(user_dict_path)

 

분석

 

형태소 분석

Kiwi.tokenize(text, match_option, normalize_coda=False)

kiwi.tokenize("안 먹었엌ㅋㅋ", normalize_coda=False)
결과 : [Token(form='안', tag='NNP', start=0, len=1), 
	Token(form='먹었엌', tag='NNP', start=2, len=3), 
	Token(form='ㅋㅋ', tag='SW', start=5, len=2)]

입력된 Text를 형태소 분석하여 그 결과를 품사와 함께 반환합니다. (리스트 형태 반환)

normalize_coda : ㅋㅋㅋ, ㅎㅎㅎ와 같은 초성체가 뒤따라와 받침으로 들어가서 분석에 실패하는 문제를 해결.

 

사용해보니 물론 완벽하진 않지만 다른 형태소 분석기와 비교하여 형태소를 굉장히 깔끔하게 잘 뽑아낸다.

 

 

그 외에 분석 관련 메서드는 참고.

# 입력된 텍스트를 형태소 분석하여 결과 반환. 총 top_n개의 결과로 출력
Kiwi.analyze(text, top_n, match_option, normalize_coda=False, z_coda=True, split_complex=False, blocklist=None)
# 입력 텍스트를 문장 단위로 분할하여 반환.
Kiwi.split_into_sents(text, match_options=Match.ALL, normalize_coda=False, z_coda=True, split_complex=False, blocklist=None, return_tokens=False)
# 여러 텍스트 조각을 문맥을 고려해 적절한 공백을 사이에 삽입하여 합침
Kiwi.glue(text_chunks, insert_new_lines=None, return_space_insertions=False)
# 입력 텍스트에서 띄어쓰기 교정
Kiwi.space(text, reset_whitespace=False)
# 형태소들을 결합하여 문장으로 복원
Kiwi.join(morphs, lm_search=True)

 

 

 


정리

한국어 형태소 분석기를 다양하게 분석해 보았고 각 분석기를 용도에 맞게 사용하면 될 것 같다.

새로운 분석기는 계속 정리할 예정이다.

 

 

 

 

반응형
LIST
Comments