데이터과학 삼학년

soynlp 한국어 형태소 분석기(학습형 형태소 분리기) 본문

Natural Language Processing

soynlp 한국어 형태소 분석기(학습형 형태소 분리기)

Dan-k 2020. 6. 8. 17:32
반응형

soynlp라는 한국어 형태소 분석기에 대해 소개하려 한다.

한국어 형태소 분석기로 가장 잘알려진 것은 koNLPy라이브러리로 이안에

mecab, komoran, kokoma, twitter(okt)등 많은 한국어 형태소 분석기가 있다.

 

이들의 공통점은 미리 한국어 기반의 사전을 정의하여 그 사전을 가지고 형태소를 분석하는 것이다.

 

하지만 koNLPy에서 제공하는 형태소분석기는 형태소 기반으로 문서를 토큰화할 수 있는 기능을 제공하지만 새롭게 만들어진 미등록 단어들은 인식이 잘 되지 않는 단점을 가지고 있다. 
이를 해결하기 위해서는 사용자 사전에 단어를 등록하는 절치를 거쳐야 하는 번거로움이 있다.


soynlp는 이러한 과정을 돕기 위해 사용자 사전과 형태소분석 없이 corpus를 알고리즘(cohesion, branch entropy, accessor variety) 기반으로 학습하고, 
score를 매겨 이를 이용해 토큰화를 할 수 있는 기능을 제공한다.
이러한 soynlp의 방식은 한국어 처리 뿐만 아니라 다른 언어의 경우도 토큰화할 수 있을 것으로 보인다. 
다만 Konlpy의 사전 기반과 같이 어원단위의 토큰화는 불가능하다.(ex.집에 간다. --> 집,에, 가다 등)

 

오늘은 soynlp가 corpus를 학습하여 형태소를 분리하는 알고리즘을 살펴보고 이를 활용한 형태소 분석을 실행해 보려 한다.

 

 

패키지 설치 및 예제 파일 다운로드

!pip3 install soynlp
!wget https://raw.githubusercontent.com/lovit/soynlp/master/tutorials/2016-10-20.txt -O 2016-10-20.txt

말뭉치 생성

from soynlp import DoublespaceLineCorpus

# 문서 단위 말뭉치 생성 
corpus = DoublespaceLineCorpus("2016-10-20.txt")
len(corpus)  # 문서의 갯수

앞 5개 문장 출력

# 앞 5개의 문서 인쇄
i = 0
for d in corpus:
    print(i, d)
    i += 1
    if i > 4:
        break
        
==========
0 
1 19  1990  52 1 22
2 오패산터널 총격전 용의자 검거 서울 연합뉴스 경찰 관계자들이 19일 오후 서울 강북구 오패산 터널 인근에서 사제 총기를 발사해 경찰을 살해한 용의자 성모씨를 검거하고 있다 성씨는 검거 당시 서바이벌 게임에서 쓰는 방탄조끼에 헬멧까지 착용한 상태였다 독자제공 영상 캡처 연합뉴스  서울 연합뉴스 김은경 기자 사제 총기로 경찰을 살해한 범인 성모 46 씨는 주도면밀했다  경찰에 따르면 성씨는 19일 오후 강북경찰서 인근 부동산 업소 밖에서 부동산업자 이모 67 씨가 나오기를 기다렸다 이씨와는 평소에도 말다툼을 자주 한 것으로 알려졌다  이씨가 나와 걷기 시작하자 성씨는 따라가면서 미리 준비해온 사제 총기를 이씨에게 발사했다 총알이 빗나가면서 이씨는 도망갔다 그 빗나간 총알은 지나가던 행인 71 씨의 배를 스쳤다  성씨는 강북서 인근 치킨집까지 이씨 뒤를 쫓으며 실랑이하다 쓰러뜨린 후 총기와 함께 가져온 망치로 이씨 머리를 때렸다  이 과정에서 오후 6시 20분께 강북구 번동 길 위에서 사람들이 싸우고 있다 총소리가 났다 는 등의 신고가 여러건 들어왔다  5분 후에 성씨의 전자발찌가 훼손됐다는 신고가 보호관찰소 시스템을 통해 들어왔다 성범죄자로 전자발찌를 차고 있던 성씨는 부엌칼로 직접 자신의 발찌를 끊었다  용의자 소지 사제총기 2정 서울 연합뉴스 임헌정 기자 서울 시내에서 폭행 용의자가 현장 조사를 벌이던 경찰관에게 사제총기를 발사해 경찰관이 숨졌다 19일 오후 6시28분 강북구 번동에서 둔기로 맞았다 는 폭행 피해 신고가 접수돼 현장에서 조사하던 강북경찰서 번동파출소 소속 김모 54 경위가 폭행 용의자 성모 45 씨가 쏜 사제총기에 맞고 쓰러진 뒤 병원에 옮겨졌으나 숨졌다 사진은 용의자가 소지한 사제총기  신고를 받고 번동파출소에서 김창호 54 경위 등 경찰들이 오후 6시 29분께 현장으로 출동했다 성씨는 그사이 부동산 앞에 놓아뒀던 가방을 챙겨 오패산 쪽으로 도망간 후였다  김 경위는 오패산 터널 입구 오른쪽의 급경사에서 성씨에게 접근하다가 오후 6시 33분께 풀숲에 숨은 성씨가 허공에 난사한 10여발의 총알 중 일부를 왼쪽 어깨 뒷부분에 맞고 쓰러졌다  김 경위는 구급차가 도착했을 때 이미 의식이 없었고 심폐소생술을 하며 병원으로 옮겨졌으나 총알이 폐를 훼손해 오후 7시 40분께 사망했다  김 경위는 외근용 조끼를 입고 있었으나 총알을 막기에는 역부족이었다  머리에 부상을 입은 이씨도 함께 병원으로 이송됐으나 생명에는 지장이 없는 것으로 알려졌다  성씨는 오패산 터널 밑쪽 숲에서 오후 6시 45분께 잡혔다  총격현장 수색하는 경찰들 서울 연합뉴스 이효석 기자 19일 오후 서울 강북구 오패산 터널 인근에서 경찰들이 폭행 용의자가 사제총기를 발사해 경찰관이 사망한 사건을 조사 하고 있다  총 때문에 쫓던 경관들과 민간인들이 몸을 숨겼는데 인근 신발가게 직원 이모씨가 다가가 성씨를 덮쳤고 이어 현장에 있던 다른 상인들과 경찰이 가세해 체포했다  성씨는 경찰에 붙잡힌 직후 나 자살하려고 한 거다 맞아 죽어도 괜찮다 고 말한 것으로 전해졌다  성씨 자신도 경찰이 발사한 공포탄 1발 실탄 3발 중 실탄 1발을 배에 맞았으나 방탄조끼를 입은 상태여서 부상하지는 않았다  경찰은 인근을 수색해 성씨가 만든 사제총 16정과 칼 7개를 압수했다 실제 폭발할지는 알 수 없는 요구르트병에 무언가를 채워두고 심지를 꽂은 사제 폭탄도 발견됐다  일부는 숲에서 발견됐고 일부는 성씨가 소지한 가방 안에 있었다
3 테헤란 연합뉴스 강훈상 특파원 이용 승객수 기준 세계 최대 공항인 아랍에미리트 두바이국제공항은 19일 현지시간 이 공항을 이륙하는 모든 항공기의 탑승객은 삼성전자의 갤럭시노트7을 휴대하면 안 된다고 밝혔다  두바이국제공항은 여러 항공 관련 기구의 권고에 따라 안전성에 우려가 있는 스마트폰 갤럭시노트7을 휴대하고 비행기를 타면 안 된다 며 탑승 전 검색 중 발견되면 압수할 계획 이라고 발표했다  공항 측은 갤럭시노트7의 배터리가 폭발 우려가 제기된 만큼 이 제품을 갖고 공항 안으로 들어오지 말라고 이용객에 당부했다  이런 조치는 두바이국제공항 뿐 아니라 신공항인 두바이월드센터에도 적용된다  배터리 폭발문제로 회수된 갤럭시노트7 연합뉴스자료사진
4 브뤼셀 연합뉴스 김병수 특파원 독일 정부는 19일 원자력발전소를 폐쇄하기로 함에 따라 원자력 발전소 운영자들에게 핵폐기물 처리를 지원하는 펀드에 235억 유로 260억 달러 29조 원 를 지불하도록 하는 계획을 승인했다고 언론들이 보도했다  앞서 독일은 5년 전 일본 후쿠시마 원전사태 이후 오는 2022년까지 원전 17기를 모두 폐쇄하기로 하고 오는 2050년까지 전기생산량의 80 를 재생에너지로 충당하는 것을 목표로 세웠다  이날 내각을 통과한 법안은 원전 운영자들이 원전 해체와 핵폐기물 처리를 위한 포장을 책임지고 정부는 핵폐기물 보관을 책임지도록 했다  독일 경제부는 전력회사들과 공식적인 접촉은 아직 합의되지 않았다고 밝혔다  독일 원자력 발전소 연합뉴스 자료사진

 

soynlp는 형태소를 분류하는 알고리즘이 3개가 존재하고 

word_extractor를 이용하여 세개의 알고리즘에 대해 계산한 각 단어별 score를 집계한다 
원하는 알고리즘을 선택하여 형태소 분리를 적용하면 된다.

%%time
from soynlp.word import WordExtractor

word_extractor = WordExtractor()
word_extractor.train(corpus)
========
training was done. used memory 0.811 Gb
CPU times: user 49.6 s, sys: 1.67 s, total: 51.2 s
Wall time: 51.2 s


word_score = word_extractor.extract()
==========
all cohesion probabilities was computed. # words = 223348
all branching entropies was computed # words = 360721
all accessor variety was computed # words = 360721

Cohesion

Cohesion은 문자열을 글자단위로 분리하여 부분문자열(substring)을 만들 때 왼쪽부터 문맥을 증가시키면서 각 문맥이 주어졌을 때 그 다음 글자가 나올 확률을 계산하여 누적곱을 한 값이다.

예를 들어 "연합뉴스가"라는 문자열이 있는 경우, 각 부분문자열의 cohesion은 다음과 같다. 한 글자는 cohesion을 계산하지 않는다.

하나의 단어를 중간에서 나눈 경우, 다음 글자를 예측하기 쉬우므로 조건부확률의 값은 크다. 하지만 단어가 종료된 다음에 여러가지 조사나 결합어가 오는 경우에는 다양한 경우가 가능하므로 조건부확률의 값이 작아진다. 따라서 cohesion값이 가장 큰 위치가 하나의 단어를 이루고 있을 가능성이 높다.

word_score["연합"].cohesion_forward ## '연' 다음 '합'이 나올 확률
0.1943363253634125
word_score["연합뉴"].cohesion_forward ## '연합' 다음 '뉴' 가 나올 확률
0.43154839105434084
word_score["연합뉴스"].cohesion_forward ## '연합류' 다음 '스' 가 나올 확률
0.5710254410737682
word_score["연합뉴스는"].cohesion_forward
0.1535595043355021

Branching Entropy

Branching Entropy는 조건부 확률의 값이 아니라 확률분포의 엔트로피값을 사용한다. 만약 하나의 단어를 중간에서 끊으면 다음에 나올 글자는 쉽게 예측이 가능하다 즉, 여러가지 글자 중 특정한 하나의 글자가 확률이 높다. 따라서 엔트로피값이 0에 가까운 값으로 작아진다. 하지만 하나의 단어가 완결되는 위치에는 다양한 조사나 결합어가 올 수 있으므로 여러가지 글자의 확률이 비슷하게 나오고 따라서 엔트로피값이 높아진다.

word_score["연합"].right_branching_entropy
0.4272123671174287
# '연합뉴' 다음에는 항상 '스'만 나온다.
word_score["연합뉴"].right_branching_entropy
-0.0
word_score["연합뉴스"].right_branching_entropy
3.896781076102208
word_score["연합뉴스는"].right_branching_entropy
0.410116318288409

Accessor Variety

Accessor Variety는 확률분포를 구하지 않고 단순히 특정 문자열 다음에 나올 수 있는 글자의 종류만 계산한다. 글자의 종류가 많다면 엔트로피가 높아지리 것이라고 추정하는 것이다.

word_score["연합"].right_accessor_variety
42
# '연합뉴' 다음에는 항상 '스'만 나온다.
word_score["연합뉴"].right_accessor_variety
1
word_score["연합뉴스"].right_accessor_variety
158
word_score["연합뉴스는"].right_accessor_variety
2

 

위에서 처리한 알고리즘별 단어별 score dictionary를 이용하여 토큰화한다.

 

L-토큰화

from soynlp.tokenizer import LTokenizer

scores = {word:score.cohesion_forward for word, score in word_score.items()}
l_tokenizer = LTokenizer(scores=scores)

l_tokenizer.tokenize("안전성에 문제있는 스마트폰을 휴대하고 탑승할 경우에 압수한다", flatten=False)
=======
[('안전', '성에'),
 ('문제', '있는'),
 ('스마트폰', '을'),
 ('휴대', '하고'),
 ('탑승', '할'),
 ('경우', '에'),
 ('압수', '한다')]

최대 점수 토큰화

from soynlp.tokenizer import MaxScoreTokenizer

maxscore_tokenizer = MaxScoreTokenizer(scores=scores)
maxscore_tokenizer.tokenize("안전성에문제있는스마트폰을휴대하고탑승할경우에압수한다")
========
['안전',
 '성에',
 '문제',
 '있는',
 '스마트폰',
 '을',
 '휴대',
 '하고',
 '탑승',
 '할',
 '경우',
 '에',
 '압수',
 '한다']

그외 다양한 토크나이저 존재

Normalizer

대화 데이터, 댓글 데이터에 등장하는 반복되는 이모티콘의 정리 및 한글, 혹은 텍스트만 남기기 위한 함수를 제공합니다.

from soynlp.normalizer import *

emoticon_normalize('ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ쿠ㅜㅜㅜㅜㅜㅜ', num_repeats=3)
# 'ㅋㅋㅋㅜㅜㅜ'

repeat_normalize('와하하하하하하하하하핫', num_repeats=2)
# '와하하핫'

only_hangle('가나다ㅏㅑㅓㅋㅋ쿠ㅜㅜㅜabcd123!!아핫')
# '가나다ㅏㅑㅓㅋㅋ쿠ㅜㅜㅜ 아핫'

only_hangle_number('가나다ㅏㅑㅓㅋㅋ쿠ㅜㅜㅜabcd123!!아핫')
# '가나다ㅏㅑㅓㅋㅋ쿠ㅜㅜㅜ 123 아핫'

only_text('가나다ㅏㅑㅓㅋㅋ쿠ㅜㅜㅜabcd123!!아핫')
# '가나다ㅏㅑㅓㅋㅋ쿠ㅜㅜㅜabcd123!!아핫'

한국어 뿐만 아니라 이러한 soynlp의 방식은 한국어 처리 뿐만 아니라 다른 언어의 경우도 토큰화할 수 있을 것으로 보인다. 다만 Konlpy의 사전 기반과 같이 어원단위의 토큰화는 불가능하다.(ex.집에 간다. --> 집,에, 가다 등)

 

Part of Speech Tagger

soynlp에도 Konlpy와 같이 사전이 준비되어 있다면 적용할 수 있음

pos_dict = {
    'Adverb': {'너무', '매우'}, 
    'Noun': {'너무너무너무', '아이오아이', '아이', '노래', '오', '이', '고양'},
    'Josa': {'는', '의', '이다', '입니다', '이', '이는', '를', '라', '라는'},
    'Verb': {'하는', '하다', '하고'},
    'Adjective': {'예쁜', '예쁘다'},
    'Exclamation': {'우와'}    
}

from soynlp.postagger import Dictionary
from soynlp.postagger import LRTemplateMatcher
from soynlp.postagger import LREvaluator
from soynlp.postagger import SimpleTagger
from soynlp.postagger import UnknowLRPostprocessor

dictionary = Dictionary(pos_dict)
generator = LRTemplateMatcher(dictionary)    
evaluator = LREvaluator()
postprocessor = UnknowLRPostprocessor()
tagger = SimpleTagger(generator, evaluator, postprocessor)

sent = '너무너무너무는아이오아이의노래입니다!!'
print(tagger.tag(sent))
# [('너무너무너무', 'Noun'),
#  ('는', 'Josa'),
#  ('아이오아이', 'Noun'),
#  ('의', 'Josa'),
#  ('노래', 'Noun'),
#  ('입니다', 'Josa'),
#  ('!!', None)]

 

형태소 분류기

from soynlp.word import WordExtractor
from soynlp.tokenizer import MaxScoreTokenizer

corpus = data.contents
word_extractor = WordExtractor()
word_extractor.train(corpus)
word_score = word_extractor.extract()
scores = {word:score.cohesion_forward for word, score in word_score.items()}
maxscore_tokenizer = MaxScoreTokenizer(scores=scores)

def soynlp_morphs(contents):
    return ' '.join(maxscore_tokenizer.tokenize(contents))
출처 : https://datascienceschool.net/view-notebook/31eaecec4187428a8dfcab5f686bda8b/
 

Data Science School

Data Science School is an open space!

datascienceschool.net

출처 : https://github.com/lovit/soynlp
 

lovit/soynlp

한국어 자연어처리를 위한 파이썬 라이브러리입니다. 단어 추출/ 토크나이저 / 품사판별/ 전처리의 기능을 제공합니다. - lovit/soynlp

github.com

 

728x90
반응형
LIST
Comments