KoNLPy의 트위터 한국어 분석기 (현 오픈 한국어 분석기)는 속도도 빠르고 다양한 사전도 확보하고 있는 한국어 분석기입니다. 하지만 컴파일이 되어 있는 형태로 KoNLPy에 들어가 있기 때문에 사용자 사전의 추가가 힘들고*, 내가 원하는 임의의 태그를 지정할 수 없습니다.

customized_KoNLPy는 확실히 알고 있는 단어들에 대해서는 라이브러리를 거치지 않고 주어진 어절을 아는 단어들로 토크나이징 / 품사판별을 하는 기능을 제공합니다. 이를 위해 template 기반 토크나이징을 수행합니다.

사전: {'아이오아이': 'Noun', '는': 'Josa'}
탬플릿: Noun + Josa

위와 같은 단어 리스트와 탬플릿이 있다면 '아이오아이는' 이라는 어절은 [('아이오아이', 'Noun'), ('는', 'Josa')]로 분리됩니다.

* Scala 코드를 이용할 경우에는 사용자 사전의 추가가 매우 쉽습니다 참고

KoNLPy의 버전은 0.4.4 기준입니다. KoNLPy의 Twitter를 이용하여 '우리아이오아이는 정말 이뻐요'라는 문장을 처리하면 '아이오' + '아이'로 명사가 잘못 인식됩니다. 트와이스의 'tt' 역시 명사보다는 영어로 인식됩니다. 한국어 분석기 이지만, tt는 명사로 미리 분류하고 싶습니다.


In [18]:
import konlpy
konlpy.__version__


Out[18]:
'0.4.4'

In [1]:
from konlpy.tag import Twitter as OriginalTwitter

twitter_original = OriginalTwitter()
print(twitter_original.pos('우리아이오아이는 정말 이뻐요'),'\n')
print(twitter_original.pos('트둥이꺼 tt도 좋아요'))


[('우리', 'Noun'), ('아이오', 'Noun'), ('아이', 'Noun'), ('는', 'Josa'), ('정말', 'Noun'), ('이뻐', 'Adjective'), ('요', 'Eomi')] 

[('트', 'Noun'), ('둥이', 'Noun'), ('꺼', 'Suffix'), ('tt', 'Alpha'), ('도', 'Noun'), ('좋', 'Adjective'), ('아요', 'Eomi')]

customized_KoNLPy 에는 현재 트위터 한국어 분석기 만을 이용하는 wrapping class만 제공되고 있습니다. customized_KoNLPy의 Twitter는 본래 KoNLPy의 tag에 추가되는 함수가 있습니다.

Twitter.add_dictionary(words, tag)는 사용자가 사전을 추가할 수 있는 부분입니다. 단어를 하나씩 추가할 수 있습니다. 추가한 뒤 Twitter의 숨김 변수인 _dictionary._pos2words를 확인해보면 입력한 단어들을 볼 수 있습니다.

git clone을 한 상태에서 tutorial code를 이용하신다면 아래의 코드를 실행하여 path를 추가하십시요


In [2]:
import sys
sys.path.append('../')

In [3]:
from ckonlpy.tag import Twitter

twitter = Twitter()

twitter.add_dictionary('이', 'Modifier')
twitter.add_dictionary('우리', 'Modifier')
twitter.add_dictionary('이번', 'Modifier')
twitter.add_dictionary('아이오아이', 'Noun')
twitter.add_dictionary('행사', 'Noun')
twitter.add_dictionary('아이', 'Noun')
twitter.add_dictionary('번것', 'Noun')
twitter.add_dictionary('것', 'Noun')
twitter.add_dictionary('은', 'Josa')
twitter.add_dictionary('는', 'Josa')
twitter._dictionary._pos2words


Out[3]:
{'Josa': {'는', '은'},
 'Modifier': {'우리', '이', '이번'},
 'Noun': {'것', '번것', '아이', '아이오아이', '행사'}}

사전을 추가한 뒤, '아이오아이'가 명사로 제대로 인식됨을 확인할 수 있습니다.


In [4]:
twitter.pos('우리아이오아이는 정말 이뻐요')


Out[4]:
[('우리', 'Modifier'),
 ('아이오아이', 'Noun'),
 ('는', 'Josa'),
 ('정말', 'Noun'),
 ('이뻐', 'Adjective'),
 ('요', 'Eomi')]

In [5]:
twitter.pos('아이오아이 이뻐요')


Out[5]:
[('아이오아이', 'Noun'), ('이뻐', 'Adjective'), ('요', 'Eomi')]

사전을 추가할 때, 하나의 품사에 대하여 동시에 여러 개의 단어셋을 입력할 수도 있습니다.

Twitter.add_dictionary(words, tag)는 한번에 list of str 형식의 여러 개의 단어들을 입력할 수도 있습니다.


In [6]:
twitter.add_dictionary(['트와이스', 'tt', '트둥이', '꺼', '우리'], 'Noun')
twitter._dictionary._pos2words


Out[6]:
{'Josa': {'는', '은'},
 'Modifier': {'우리', '이', '이번'},
 'Noun': {'tt', '것', '꺼', '번것', '아이', '아이오아이', '우리', '트둥이', '트와이스', '행사'}}

In [7]:
twitter.pos('트와이스tt는 좋아요')


Out[7]:
[('트와이스', 'Noun'),
 ('tt', 'Noun'),
 ('는', 'Josa'),
 ('좋', 'Adjective'),
 ('아요', 'Eomi')]

트위터 분석기의 조사사전을 이용할 수도 있습니다. Twitter()를 만들 때 argument를 넣을 수 있습니다.


In [8]:
twitter1 = Twitter(load_default_dictionary=True)
len(twitter1._dictionary._pos2words['Josa'])


Out[8]:
513

하지만 아직 '우리트둥이꺼tt는' 이라는 어절이 제대로 인식되지 않습니다. 그 이유는 templates에 'Noun + Noun + Josa'가 없었기 때문입니다. 이 경우에는 KoNLPy에 해당 어절을 분석하라고 보냅니다. 하지만 '트둥이'라는 단어를 알지 못해서 제대로 인식되지 않습니다.


In [9]:
twitter.pos('우리트둥이꺼tt는 좋아요')


Out[9]:
[('우', 'Adverb'),
 ('리트', 'Noun'),
 ('둥이', 'Noun'),
 ('꺼', 'Suffix'),
 ('tt', 'Alpha'),
 ('는', 'Verb'),
 ('좋', 'Adjective'),
 ('아요', 'Eomi')]

현재는 customized_tagger로 탬플릿 기반 토크나이저를 이용하고 있습니다. 어떤 탬플릿이 들어있는지 확인하기 위해서는 아래 부분을 확인하면 됩니다.

twitter._customized_tagger.templates

현재는 다음의 탬플릿이 입력되어 있습니다.


In [10]:
twitter._customized_tagger.templates


Out[10]:
[('Noun',),
 ('Noun', 'Noun'),
 ('Noun', 'Josa'),
 ('Noun', 'Josa', 'Noun'),
 ('Noun', 'Noun', 'Josa'),
 ('Modifier', 'Noun'),
 ('Modifier', 'Noun', 'Noun'),
 ('Modifier', 'Noun', 'Josa'),
 ('Modifier', 'Noun', 'Noun', 'Josa')]

기본 탬플릿은 customized_konlpy/data/templates/twitter_templates0 에 저장되어 있습니다. text 형식의 파일이며, 띄어쓰기로 아래와 같은 기본 템플릿을 지정하면 됩니다.


In [11]:
cat ../ckonlpy/data/templates/twitter_templates0


Noun
Noun Noun
Noun Josa
Noun Josa Noun
Noun Noun Josa
Modifier Noun
Modifier Noun Noun
Modifier Noun Josa
Modifier Noun Noun Josa

작업 중 탬플릿을 추가하고 싶다면, 탬플릿은 하나 단위로 tuple of str의 형식으로 입력할 수 있습니다. _customized_tagger.add_a_templated()은 중복되는 탬플릿이 아닌지 확인한 다음 탬플릿을 추가하는 함수입니다.


In [12]:
twitter._customized_tagger.add_a_template(('Modifier', 'Noun', 'Noun', 'Noun', 'Josa'))
twitter._customized_tagger.templates


Out[12]:
[('Noun',),
 ('Noun', 'Noun'),
 ('Noun', 'Josa'),
 ('Noun', 'Josa', 'Noun'),
 ('Noun', 'Noun', 'Josa'),
 ('Modifier', 'Noun'),
 ('Modifier', 'Noun', 'Noun'),
 ('Modifier', 'Noun', 'Josa'),
 ('Modifier', 'Noun', 'Noun', 'Josa'),
 ('Modifier', 'Noun', 'Noun', 'Noun', 'Josa')]

('Noun', 'Noun', 'Josa')가 입력되었고, '트와이스', 'tt'가 명사인지 알고 있기 때문에 아래 문장은 제대로 인식이 됩니다.


In [13]:
twitter.pos('우리트둥이꺼tt는 좋아요')


Out[13]:
[('우리', 'Modifier'),
 ('트둥이', 'Noun'),
 ('꺼', 'Noun'),
 ('tt', 'Noun'),
 ('는', 'Josa'),
 ('좋', 'Adjective'),
 ('아요', 'Eomi')]

사전을 추가할 때, 트위터 한국어 분석기에 존재하지 않는 태그가 들어가는 것을 방지하기 위해 tag의 값을 확인하는 부분이 구현되어 있습니다.

twitter.tagset

>>> {'Adjective': '형용사',
     'Adverb': '부사',
     'Alpha': '알파벳',
     'Conjunction': '접속사',
     'Determiner': '관형사',
     'Eomi': '어미',
     'Exclamation': '감탄사',
     'Foreign': '외국어, 한자 및 기타기호',
     'Hashtag': '트위터 해쉬태그',
     'Josa': '조사',
     'KoreanParticle': '(ex: ㅋㅋ)',
     'Modifier': '관형사',
     'Noun': '명사',
     'Number': '숫자',
     'PreEomi': '선어말어미',
     'Punctuation': '구두점',
     'ScreenName': '트위터 아이디',
     'Suffix': '접미사',
     'Unknown': '미등록어',
     'Verb': '동사'}

twitter.tagset에 등록되어 있지 않는 품사에 대해서는 ValueError를 raise 합니다.


In [14]:
twitter.add_dictionary('lovit', 'Name')


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-14-849546ef1046> in <module>()
----> 1 twitter.add_dictionary('lovit', 'Name')

/mnt/lovit/git/customized_konlpy/ckonlpy/tag/_twitter.py in add_dictionary(self, words, tag, force)
     53     def add_dictionary(self, words, tag, force=False):
     54         if (not force) and (not (tag in self.tagset)):
---> 55             raise ValueError('%s is not available tag' % tag)
     56         self._dictionary.add_dictionary(words, tag)
     57 

ValueError: Name is not available tag

하지만 Twitter.add_dictionary(words, tag, force=True)로 단어를 사전에 입력하면 알려지지 않은 품사라 하더라도 입력할 수 있습니다.


In [15]:
twitter.add_dictionary('lovit', 'Name', force=True)
twitter._dictionary._pos2words


Out[15]:
{'Josa': {'는', '은'},
 'Modifier': {'우리', '이', '이번'},
 'Name': {'lovit'},
 'Noun': {'tt', '것', '꺼', '번것', '아이', '아이오아이', '우리', '트둥이', '트와이스', '행사'}}

'Name'이라는 클래스 (더이상 품사가 아니므로)를 이용하는 탬플릿을 하나 입력한 뒤 pos에 입력하면 어절 'lovit은' customized_tagger에 의하여 처리가 되고, 사용자 사전에 알려지지 않은 어절인 '졸려'는 본래의 트위터 분석기에 의하여 처리가 됩니다.


In [16]:
twitter._customized_tagger.add_a_template(('Name', 'Josa'))
print(twitter._customized_tagger.templates)
twitter.pos('lovit은 이름입니다.')


[('Noun',), ('Noun', 'Noun'), ('Noun', 'Josa'), ('Noun', 'Josa', 'Noun'), ('Noun', 'Noun', 'Josa'), ('Modifier', 'Noun'), ('Modifier', 'Noun', 'Noun'), ('Modifier', 'Noun', 'Josa'), ('Modifier', 'Noun', 'Noun', 'Josa'), ('Modifier', 'Noun', 'Noun', 'Noun', 'Josa'), ('Name', 'Josa')]
Out[16]:
[('lovit', 'Name'),
 ('은', 'Josa'),
 ('이름', 'Noun'),
 ('입니', 'Adjective'),
 ('다', 'Eomi'),
 ('.', 'Punctuation')]

Templates를 이용하여도 후보가 여러 개 나올 수 있습니다. 여러 개 후보 중에서 best 를 선택하는 함수를 직접 디자인 할 수 도 있습니다. 이처럼 몇 개의 점수 기준을 만들고, 각 기준의 weight를 부여하는 방식은 트위터 분석기에서 이용하는 방식인데, 직관적이고 튜닝 가능해서 매우 좋은 방식이라 생각합니다.


In [17]:
score_weights = {
    'num_nouns': -0.1,
    'num_words': -0.2,
    'no_noun': -1
}

def my_score(candidate):
    num_nouns = len([w for w,t in candidate if t == 'Noun'])
    num_words = len(candidate)
    no_noun = 1 if num_nouns == 0 else 0
    
    score = (num_nouns * score_weights['num_nouns'] 
             + num_words * score_weights['num_words']
             + no_noun * score_weights['no_noun'])
    return score

twitter.set_selector(score_weights, my_score)


[('이', 'Noun'), ('것', 'Noun'), ('은', 'Josa'), ('테', 'Noun'), ('스트', 'Noun')]
[('이것', 'Noun'), ('은', 'Josa'), ('테', 'Noun'), ('스트', 'Noun')]
[('이것', 'Noun'), ('은', 'Josa'), ('테스트', 'Noun')]
best: [('이것', 'Noun'), ('은', 'Josa'), ('테스트', 'Noun')]

In [ ]: