형태소 분석
더 많은 작업
- Morphological analysis; 형태소 분석
- 어절을 뜻을 가진 가장 작은 단위인 형태소로 나누고, 원형을 되살리고, 품사를 붙이는 처리
형태소 분석(morphological analysis)은 "먹었다"를 "먹/동사 + 었/선어말 어미 + 다/종결 어미"로 나누듯, 어절을 형태소로 쪼개고 활용·축약으로 바뀐 모양을 원래 형태로 되돌린 뒤 각 형태소에 품사 꼬리표를 붙이는 처리다. 엄밀히는 분절과 원형 복원이 형태소 분석, 품사를 붙이는 것이 품사 태깅(POS tagging)이지만, 한국어 도구는 둘을 한 번에 하므로 보통 묶어서 형태소 분석이라 부른다.
한국어는 어간에 조사와 어미가 붙는 교착어라 공백 단위 처리가 잘 맞지 않는다. 이 배경과 하위 단어 토큰화와의 관계는 자연어 처리, 토큰화 (자연어 처리) 문서에 있다. 이 문서는 분석기가 내부에서 어떻게 동작하는지, 품사 태그 체계, 주요 한국어 분석기의 비교, 코드 사용법을 다룬다.
한국어 형태소 분석기는 세 가지 일을 동시에 한다.
- 분절: "학교에서도"를 "학교 / 에서 / 도"로 나눈다. 띄어쓰기가 틀린 입력("아버지가방에들어가신다")에서는 어디서 끊을지 자체가 문제다.
- 원형 복원: 표면형과 기저형이 다르다. "굴러도"는 "구르/VV + 어도/EC", "나는"(날다)은 "날/VV + 는/ETM"이다. 불규칙 활용, ㄹ 탈락, 축약("했다" = 하/VV + 였/EP + 다/EF)을 되돌려야 한다.
- 중의성 해소: 같은 표면형이 여러 분석을 가진다. "나는 밥을 먹는다"의 "나는"은 대명사 + 조사, "하늘을 나는 자동차"의 "나는"은 동사 + 관형형 어미다. 문맥을 봐야 고를 수 있다.[1]
사전 기반 분석기의 일반적인 흐름은 다음과 같다.
- 사전 탐색: 입력의 각 위치에서 시작하는 모든 사전 항목(형태소 후보)을 찾는다. 사전은 트라이(trie)나 이중 배열 트라이로 저장해 접두사 검색을 빠르게 한다.
- 격자(lattice) 구성: 후보 형태소를 노드로, 이어질 수 있는 후보끼리를 간선으로 잇는다. "아버지가방에"에서는 "아버지+가+방"과 "아버지+가방"이 모두 경로가 된다.
- 비용 계산: 각 노드에 단어 비용(그 형태소가 나올 가능성), 각 간선에 연접 비용(앞 품사 다음에 뒤 품사가 올 가능성)을 매긴다.
- 최적 경로: 비용 합이 가장 작은 경로를 비터비 알고리즘(동적 계획법)으로 찾는다.
- 미등록어 처리: 사전에 없는 문자열은 글자 종류(한글, 영문, 숫자)별 규칙으로 명사 후보를 만든다.
비용을 어떻게 정하느냐가 분석기의 성능을 가른다. 은닉 마르코프 모델처럼 말뭉치에서 확률을 세는 방식(은닉 마르코프 모델 참고)이 있고, MeCab은 조건부 랜덤 필드(CRF)로 비용을 말뭉치에서 학습하고 비터비로 해를 찾는다.[2] Kiwi는 가까운 문맥을 보는 통계적 언어 모델과 먼 문맥을 보는 스킵-바이그램(Skip-Bigram) 모델을 함께 써 중의성을 푼다.[3]
음절마다 "형태소 꼬리표 + 경계" 꼬리표를 붙이는 시퀀스 레이블링으로 바꿔 신경망으로 푸는 방식도 있다. 카카오의 khaiii는 세종 말뭉치를 정제한 약 85만 문장(천만 어절)으로 학습한 합성곱 신경망(CNN) 분류기로, 속도를 위해 순환 신경망 대신 CNN을 골랐다고 밝혔다.[4] 음절 단위 꼬리표만으로는 원형 복원("굴러" → "구르 + 어")이 바로 나오지 않으므로 복원 사전이나 규칙을 함께 쓴다. 인코더-디코더로 형태소 열을 직접 생성하는 방식도 연구되었다.
국내에서 가장 널리 기준이 되는 것은 국립국어원 21세기 세종계획 말뭉치의 품사 태그(세종 품사 태그)다. Komoran, Mecab-ko, Kiwi, khaiii가 이를 바탕으로 하며, 분석기마다 일부를 고치거나 더했다. 아래는 Kiwi 문서에 정리된 세종 기반 태그다.[5]
| 대분류 | 태그 |
|---|---|
| 체언 | NNG 일반 명사, NNP 고유 명사, NNB 의존 명사, NR 수사, NP 대명사 |
| 용언 | VV 동사, VA 형용사, VX 보조 용언, VCP 긍정 지정사(이다), VCN 부정 지정사(아니다) |
| 수식언 | MM 관형사, MAG 일반 부사, MAJ 접속 부사 |
| 독립언 | IC 감탄사 |
| 조사 | JKS 주격, JKC 보격, JKG 관형격, JKO 목적격, JKB 부사격, JKV 호격, JKQ 인용격, JX 보조사, JC 접속 조사 |
| 어미 | EP 선어말 어미, EF 종결 어미, EC 연결 어미, ETN 명사형 전성 어미, ETM 관형형 전성 어미 |
| 접사·어근 | XPN 체언 접두사, XSN 명사 파생 접미사, XSV 동사 파생 접미사, XSA 형용사 파생 접미사, XR 어근 |
| 부호·외국어 | SF 마침표·물음표·느낌표, SP 쉼표·가운뎃점·쌍점·빗금, SS 따옴표·괄호, SE 줄임표, SO 붙임표, SW 기타 기호, SL 외국어(로마자), SH 한자, SN 숫자 |
Kiwi는 여기에 부사 파생 접미사(XSM), 여는·닫는 부호(SSO, SSC), URL·이메일·해시태그·이모지(W_ 계열), 사용자 정의 태그 등을 더했다.[5]
세종 체계와 다른 태그를 쓰는 분석기도 있다. 꼬꼬마는 ECD, EFN, EPT, VXV처럼 더 잘게 나눈 자체 태그를, 한나눔은 KAIST 태그(ncn, jcm 등)를, Okt는 Noun, Verb, Josa, Eomi 같은 영어 이름의 굵은 태그를 쓴다.[6] 여러 분석기를 섞어 쓰거나 바꿀 때는 KoNLPy가 공개한 한국어 품사 태그 비교표로 대응 관계를 확인한다.
| 분석기 | 개발 | 구현 | 태그 | 특징 |
|---|---|---|---|---|
| 한나눔(Hannanum) | KAIST 시맨틱웹연구센터, 1999년부터 | 자바 | KAIST 태그 | HMM 기반 태거, 9개 또는 22개 태그로 출력 선택[6] |
| 꼬꼬마(Kkma) | 서울대 IDS 연구실 | 자바 | 자체 세분 태그 | 문장 분리 기능 제공, 띄어쓰기 없는 긴 문자열에 약하다는 보고가 있음[6] |
| 코모란(Komoran) | 샤인웨어, 2013년부터 | 자바 | 세종 기반 | 띄어쓰기를 포함한 구절도 사용자 사전에 등록 가능[6] |
| Mecab-ko | 은전한닢 프로젝트 | C++ | 세종 기반(일부 수정) | 일본어 MeCab을 한국어용으로 고친 것. 사전 mecab-ko-dic. CRF 학습 비용, 매우 빠름[6] |
| Okt(Open Korean Text) | 류호현(Will Hohyon Ryu) | 스칼라 | 자체 굵은 태그 | 옛 이름 Twitter. 정규화("되나욬" → "되나요"), 어간 추출 옵션, 구 추출[6] |
| Kiwi | 이민철(bab2min) | C++ (파이썬 바인딩 kiwipiepy) | 세종 기반 확장 | 웹 텍스트 약 87%, 문어 약 94% 정확도로 소개. 0.13.0부터 간단한 오타 교정, 띄어쓰기 교정·문장 분리 제공[5] |
| khaiii | 카카오 | C++ | 세종 기반 | CNN 음절 분류. v0.4 large 모델 F1 96.71(세종 기준)[4] |
KoNLPy는 한나눔, 꼬꼬마, 코모란, Mecab, Okt를 같은 방식(morphs, nouns, pos)으로 부르게 묶은 파이썬 패키지다. 자바 기반 분석기는 JPype로 JVM을 띄워 호출하며, KoNLPy의 Mecab 클래스는 윈도우를 지원하지 않는다.[6] Kiwi는 0.24.0부터 Apache 2.0 라이선스로 배포되며 0.23.2 이하는 LGPL이다.[5]
KoNLPy 문서의 비교(KoNLPy 0.4.1, 파이썬 2.7, 4코어 인텔 i7)에서 10만 글자 문서에 pos를 실행한 시간은 꼬꼬마 35.7초, 코모란 25.6초, 한나눔 8.8초, Okt 2.5초, Mecab 0.28초였다. 클래스 로딩 시간은 꼬꼬마 5.7초, 코모란 5.5초, Okt 1.5초, 한나눔 0.66초, Mecab 0.0007초였다.[1] 이 수치는 KoNLPy 안의 래퍼끼리 비교한 오래된 결과로, 각 분석기의 원래 배포판이나 최신 버전 성능과는 다를 수 있다. 수백만 문서를 처리하는 검색 색인에서는 C++ 기반(Mecab-ko, Kiwi)이 주로 고려된다.
KoNLPy 문서의 비교에서 "아버지가방에들어가신다"를 꼬꼬마와 Mecab은 "아버지 + 가 + 방"으로, 코모란은 전체를 고유 명사 하나로, Okt는 "아버지 + 가방"으로 나눴다. "하늘을 나는 자동차"의 "나는"은 꼬꼬마만 "날/VV + 는/ETD"로 분석했고 나머지는 대명사 "나"로 분석했다.[1] 어느 분석기도 모든 문장에서 맞지는 않으므로, 자기 데이터 표본으로 직접 비교해 고른다.
kiwipiepy 0.24.0으로 실행한 결과다.
from kiwipiepy import Kiwi
kiwi = Kiwi()
for t in kiwi.tokenize("형태소 분석기는 한국어 처리의 첫 단계다."):
print(t.form, t.tag, t.start, t.len)
#형태소 NNG 0 3 / 분석기 NNG 4 3 / 는 JX 7 1 / 한국어 NNP 9 3 / 처리 NNG 13 2
#의 JKG 15 1 / 첫 MM 17 1 / 단계 NNG 19 2 / 이 VCP 21 0 / 다 EF 21 1 / . SF 22 1
print(kiwi.space("아버지가방에들어가신다")) # 아버지가 방에 들어가신다
#상위 n개 분석 후보와 점수(로그 확률)
for tokens, score in kiwi.analyze("하늘을 나는 자동차", top_n=2):
print(round(score, 2), " ".join(f"{t.form}/{t.tag}" for t in tokens))
#-29.11 하늘/NNG 을/JKO 나/VV 는/ETM 자동차/NNG
#-33.21 하늘/NNG 을/JKO 날/VV 는/ETM 자동차/NNG
s = "리랭커로 후보를 다시 정렬했다"
print([(t.form, t.tag) for t in kiwi.tokenize(s)][:2])
#[('리랭커로', 'NNP'), ('후보', 'NNG')] 미등록어라 조사까지 한 덩어리
kiwi.add_user_word("리랭커", "NNG", 0) # 사용자 사전에 등록
print([(t.form, t.tag) for t in kiwi.tokenize(s)][:3])
#[('리랭커', 'NNG'), ('로', 'JKB'), ('후보', 'NNG')]
두 번째 예에서 1순위 후보는 "나/VV"(나다), 2순위가 "날/VV"(날다)다. 형태소 분석이 하나의 정답만 내는 것이 아니라 점수 붙은 후보 중 하나를 고르는 일임을 보여 준다. 출력의 start, len은 원문 글자 위치라서 개체명 인식처럼 원문 범위가 필요한 작업에 그대로 쓸 수 있다. "이"(VCP)처럼 길이가 0인 형태소는 원문에 글자로 드러나지 않고 복원된 것이다.
from konlpy.tag import Okt, Komoran
okt = Okt()
print(okt.pos("이것도 되나욬ㅋㅋ", norm=True, stem=True))
#[('이', 'Determiner'), ('것', 'Noun'), ('도', 'Josa'), ('되다', 'Verb'), ('ㅋㅋ', 'KoreanParticle')]
komoran = Komoran(userdic="user.tsv") # 각 줄: 단어<TAB>품사
print(komoran.pos("혹시 바람과 함께 사라지다 봤어?"))
출력 예는 KoNLPy 공식 문서의 것이다.[6] Mecab-ko는 pip install python-mecab-ko 후 from mecab import MeCab으로 같은 morphs, nouns, pos 메서드를 쓸 수 있다.[7]
- 검색 색인: 엘라스틱서치의 한국어 분석 플러그인 nori는 루씬의 nori 모듈을 쓰며 mecab-ko-dic 사전으로 형태소를 분석한다.[8] 조사·어미를 떼어 "학교에서"로도 "학교"를 찾을 수 있게 하는 것이 목적이다. BM25 같은 어휘 검색의 품질이 형태소 분석에 크게 좌우된다.
- 언어 모델 토큰화 전처리: 형태소로 먼저 자른 뒤 BPE를 적용하면 여러 한국어 과제에서 좋았다는 연구가 있고(토큰화 (자연어 처리) 참고), KLUE 논문도 형태소 단위 사전 분할과 BPE의 조합이 형태소 수준 과제에 효과적이었다고 보고했다.[9]
- 텍스트 마이닝: 명사 추출, 불용어 제거, 키워드 빈도 분석에 쓴다(텍스트 마이닝 참고).
| 요구 | 고려할 분석기 |
|---|---|
| 대량 처리 속도 | Mecab-ko, Kiwi |
| 윈도우에서 설치 간편, 자바 없이 | Kiwi(kiwipiepy) |
| SNS·구어체 정규화 | Okt, Kiwi(오타 교정) |
| 사용자 사전으로 도메인 용어 고정 | 코모란, Mecab-ko, Kiwi |
| 엘라스틱서치 색인 | nori(mecab-ko-dic 기반) |
- 미등록어: 신조어, 제품명, 인명은 사전에 없어 잘못 쪼개진다("영등포구청역"을 "영등포구 + 청역"으로 나눈 예가 KoNLPy 문서에 있다).[6] 사용자 사전 관리가 실무의 큰 부분이다.
- 분석기 간 불일치: 같은 세종 기반이라도 분석 단위와 태그 세부가 달라, 분석기를 바꾸면 색인과 모델을 다시 만들어야 한다.
- 띄어쓰기·맞춤법 오류: 사용자 글에서 정확도가 떨어진다. Kiwi는 웹 텍스트 정확도를 문어보다 낮은 약 87%로 소개한다.[5]
- 대형 언어 모델과의 관계: 하위 단어 토큰화를 쓰는 언어 모델은 형태소 분석 없이도 학습되므로, 언어 모델 입력 단계에서는 필수가 아니게 되었다. 다만 검색 색인, 형태소 수준의 분석, 정확한 원문 위치가 필요한 작업에서는 여전히 쓰인다.
- ↑ 1.0 1.1 1.2 KoNLPy 문서, 형태소 분석 및 품사 태깅
- ↑ MeCab 공식 페이지; Taku Kudo, Kaoru Yamamoto, Yuji Matsumoto, 〈Applying Conditional Random Fields to Japanese Morphological Analysis〉, EMNLP 2004. ACL Anthology
- ↑ 이민철, 〈Kiwi: 통계적 언어 모델과 Skip-Bigram을 이용한 한국어 형태소 분석기 구현〉, 《디지털인문학》 1(1), 2024. DOI
- ↑ 4.0 4.1 kakao/khaiii GitHub 저장소
- ↑ 5.0 5.1 5.2 5.3 5.4 bab2min/Kiwi GitHub 저장소 README
- ↑ 6.0 6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 KoNLPy 문서, tag Package
- ↑ python-mecab-ko GitHub 저장소
- ↑ Elastic, Korean (nori) analysis plugin
- ↑ Sungjoon Park 외, 〈KLUE: Korean Language Understanding Evaluation〉, 2021. arXiv:2105.09680