본문 바로가기

카테고리 없음

Treform-3

<목차>
1. Treform 형태소 분석기 구동 예제
2.  코드 분석

 

  이번 글에서는 형태소 분석기 구동 시험을 다루고자 합니다. 먼저 github에 업로드 되어있는 코드를 살펴보고, 이에 대해서 line by line(한 줄씩) 살펴보려고 합니다. Treform의 전반적인 구조를 개관하였으므로, 이후의 내용들은 예제 구동과 이를 활용할 방법을 남기려고 합니다. 필요에 따라서는 예제와 연관된 논문도 요약해보려 합니다.

1. Treform 형태소 분석기 구동 예제

# -*- coding: utf-8 -*-
import treform as ptm
import time
from collections import Counter
mecab_path='C:\\mecab\\mecab-ko-dic'
komoran = ptm.tokenizer.Komoran()
kkma = ptm.tokenizer.KokomaKorean()
twitter = ptm.tokenizer.TwitterKorean()
kiwi=ptm.tokenizer.Kiwi(path=None)
mecab = ptm.tokenizer.MeCab(mecab_path)

#test sentence
sent="상이 평양을 떠나 영변부 ( 寧邊府 ) 로 향하였다. 윤두수·김명원·이원익은 머물러 평양을 지키고 대신 최흥원·유홍·정철 ( 鄭澈 )  등은 수행했다 "
#performance (speed) measure
sents=["부산 등지에 주둔했던 적이 군사를 합쳐 대대적으로 진주 ( 晋州 ) 를 포위하였다. 당초에 적이 유숭인 ( 柳崇仁 ) 의 군사를 패배시키고 여러 고을을 분탕질한 뒤 진주로 향하려 하였다. 이에 김성일 ( 金誠一 ) 이 호남에 구원을 청하자 의병장 최경회 ( 崔慶會 ) ·임계영 ( 任啓英 ) 이 달려왔다. 적이 진주에 육박했을 때 유숭인이 말을 달려 성 아래에 이르러 들어가려고 하였는데, 김시민 ( 金時敏 ) 이 장수의 명령 계통이 전일하지 못할까 염려하여 성문을 닫고 받아들이지 않으면서 말하기를 ‘성문을 계엄중에 열고 닫을 때 창졸간에 변이 있게 될까 염려되니 주장 ( 主將 ) 은 밖에서 응원해 주면 좋겠다.’ 하였다. 유숭인이 돌아오다 적을 만나 패하여 사천 현감 ( 泗川縣監 )  정득열 ( 鄭得悅 ) , 권관 주대청 ( 朱大淸 )  등과 함께 모두 전사하였다. 곽재우 ( 郭再祐 ) 가 김시민이 유숭인을 받아들이지 않았다는 말을 듣고 감탄하기를 ‘이 계책이 성을 온전하게 하기에 충분하니 진주 사람들의 복이다.’ 하였다."
       "김성일이 의병장 곽재우·이달 ( 李達 )  등을 보내어 진주를 구원하게 하고, 사잇길로 군기 ( 軍器 ) 를 수송하게 하였는데, 목사 김시민이 적병을 크게 격파하여 진주가 포위에서 풀렸다. 당초 왜장이 군사 수만 명을 모두 동원하여 진주성을 포위하였는데 성 안의 군사는 3천여 명이었다. 김시민이 여러 성첩을 나누어 지키게 하면서 조용히 기다리도록 하니 성 안이 적요하였다. 적이 기치 ( 旗幟 ) 와 개삽 ( 蓋翣 ) 을 많이 설치하고 금으로 꾸민 가면에 의복을 이상하게 차려 입어 햇빛에 번쩍이고 바람에 펄럭이니 온갖 형상에 눈이 부시고 정신이 어지러울 지경이었다. 왜장 6명이 진 ( 陣 ) 을 나누어 전투를 독려하였는데 총수 ( 銃手 )  수천 명이 항상 산 위에서 성 안을 향해 일제히 쏘아대니 그 형세가 번개가 치고 우박이 내리는 듯하였으며, 부르짖는 소리가 천지를 진동시켰다. 그러나 김시민은 군사들로 하여금 움직이지 말고 적들의 소리가 약해지기를 기다려 즉시 포 ( 砲 ) 를 쏘고 북을 울리며 응전하게 하였다.적이 대나무와 소나무 가지를 많이 베어 엮어서 막이를 만들고 흙을 쌓아 그 속을 채워 우리 군사가 모르게 대나무 사다리 수천 개를 만들었는데 한 칸 너비쯤 되는 것으로 그 위에 망석 ( 網席 ) 을 덮어 많은 군사가 동시에 일제히 오르게 하려 하였으며, 3층의 산대 ( 山臺 ) 를 만들어 성첩을 내려다 보게 하였다. 김시민은 화구 ( 火具 ) 를 미리 준비하고 화약 ( 火藥 ) 을 종이에 싸서 풀로 묶어 성 위에 감춰두게 하고 대포 ( 大砲 )  및 대석 ( 大石 ) 을 나누어 설치하게 하였으며, 여장 ( 女墻 )  안에는 가마솥을 비치하고 물을 끓여 대기하도록 하였다.적이 공격할 장비를 모두 갖추고 사면으로 육박하자, 성 안에서 현자총 ( 玄字銃 ) 을 쏘아 산대의 적을 맞춰 떨어뜨리고, 화약과 풀로 송장 ( 松障 ) 을 태웠으며, 대포로 대나무로 엮은 긴 사다리를 부수고, 끓인 물을 퍼붓기도 하고 큰 돌을 던지기도 하여 여러 가지의 공격용 장비를 격파하였다. 9월 10일 밤중에 적병이 거짓 물러가는 체하다가 몰래 되돌아와 적의 대장이 직접 전투를 독려하였다. 여러 왜적이 모두 방패로 가리고 머리를 감싸고서 처음에는 동문 ( 東門 ) 을 공격하였는데, 앞에서 한꺼번에 올라가게 하고 뒤에서는 천개의 총으로 일제히 사격하여 성 위에 사람이 설 수 없게 하였다. 그러나 김시민은 무리를 지휘하여 활과 쇠뇌와 포를 쏘고 돌을 굴려 내리니, 적병이 이르는 곳마다 죽어 넘어져 쓰러진 시체가 삼대처럼 즐비하여 일단 공격을 완전히 좌절시켰다.바야흐로 전투가 무르익을 무렵 또 하나의 대진 ( 大陳 ) 이 동문의 경우처럼 갑자기 북성 ( 北城 ) 을 공격하였다. 이에 만호 최덕량 ( 崔德良 )  등이 죽기를 무릅쓰고 대항해 싸우며 일사불란하게 막아내었는데, 동녘이 밝아오자 조금 뜸해졌다. 성 안의 나무와 돌, 기와, 띠풀 등이 거의 없어졌으며 시민도 탄환에 맞아 누워 있었다. 이때 곤양 군수 ( 昆陽郡守 )  이광악 ( 李光岳 ) 이 왜장을 쏘아 죽이니 한낮이 되어서야 적진이 비로소 퇴각하며 시체를 태우고 포위를 풀고 흩어졌다. 성이 포위당한 10여 일 동안 4∼5차례 큰 전투를 벌이면서 안팎에서 힘껏 싸웠으므로 적이 먼저 도망하였다.바야흐로 포위하고 주둔할 때에 양도 ( 兩道 ) 의 구원병은 모두 요새에 웅거하여 결진 ( 結陣 ) 하고서 밤이면 가까운 산에 올라 성 안과 함께 불을 들어 북을 치며 서로 응원하였으나 감히 함부로 공격하지 못하였다. 적이 나누어 이웃 고을을 노략질하자, 구원병이 요로에서 막고 습격하여 상당수를 살해하거나 상처를 입혔는데, 김준민 ( 金俊民 ) 은 여러 번 싸움에서 완전하였으므로 적이 감히 침범하지 못하였다.적이 이미 퇴각하여 본 소굴로 돌아갔으므로 여러 고을이 모두 수복되었다. 김시민의 병이 심해지자 김성일이 서예원 ( 徐禮元 ) 을 대신하게 하였다. 서예원은 완력은 있으나 어리석은 겁쟁이로 재능이 없는데, 그의 형 서인원 ( 徐仁元 ) 이 명사 ( 名士 ) 이기 때문에 특별히 발탁하여 변수 ( 邊帥 ) 를 삼았었다. 그가 북도 ( 北道 ) 에 있을 때에는 수급 ( 首級 ) 을 거짓으로 만들어 공을 자랑하여 직질이 올랐었으므로 조헌 ( 趙憲 ) 이 매번 상소하여 그의 죄를 논하였었다. 이때에 이르러 김해 부사 ( 金海府使 ) 로 성을 버리고 도망하였다가 김성일을 따라다녔으므로 김시민을 대신하였는데, 이때부터 진주성의 수비는 다시 전일과 같지 못하였다."
       "왜적이 처음 우리 지경에 침입하여 성세 ( 聲勢 ) 를 크게 펼치고 척후병을 사방으로 보내어 깊고 험준한 곳을 끝까지 수색한 다음 우리 군사가 웅거할 데가 없게 한 뒤에야 형편대로 진을 세우고 사방으로 흩어져 살해하고 노략질하였다. 좌도는 동래 ( 東萊 ) 로부터 위로 현풍 ( 玄風 ) 까지 10고을, 우도는 웅천 ( 熊川 ) 으로부터 위로 문경 ( 聞慶 ) 까지 12고을을 주둔하는 진으로 삼았는데 한 주둔지에 군사가 천 명을 넘지 않았으며, 해구 ( 海口 ) 의 몇 진에만 각기 수천 명을 주둔시켰으니, 대략 영남에 머문 군사는 4만∼5만 명에 불과하였다. 그들의 장기 ( 長技 ) 는 조총 ( 鳥銃 ) 과 단검 ( 短劒 ) 뿐으로, 항상 엄습하고 속히 진군하는 것으로 승리하였다. 또 밤낮으로 몰래 부대를 바꾸어 계속 왕래하여 그들의 수효가 많게 보이도록 하였다."
       "복수 ( 復讐 ) 할 사람을 불러 모아 군사를 일으켰다. 처음에 고경명 ( 高敬命 ) 이 패한 뒤 그의 아들 전 현령 고종후 ( 高從厚 ) 가 상복을 입고 종군하며 부친의 남은 병사를 거두어 별군 ( 別軍 ) 을 만들었다. 이때에 이르러 체찰사 정철 ( 鄭澈 ) 이 조정의 뜻을 선포하며 권유하자 홍계남 ( 洪季男 ) 이 맨 먼저 여러 도에 편지를 보내니, 조헌의 아들 조완도 ( 趙完堵 )  등이 호응하였다. 또 고종후로 하여금 사노 ( 寺奴 ) 를 뽑아 군사를 삼도록 하였다."
       "익성군 ( 益城君 )  홍성민 ( 洪聖民 ) 을 기복 ( 起復 ) 시켜 대제학으로 삼았으나 극력 사양하므로 임명하지 않았다."
       "김시민 ( 金時敏 ) 을 경상 우병사로 삼았는데, 얼마 뒤에 졸 ( 卒 ) 하였다."]

tokens = []

def extract_posTag(text):
    result = kiwi.inst.analyze(text)
    for token, pos, start, lengh_sentence in result[0][0]:#[0][0]
            #if pos.startswith('N'):
        yield token, pos

taggers = [komoran, kkma, twitter, mecab, kiwi]
names = 'komoran kkma twitter mecab kiwi'.split()

for name, tagger in zip(names, taggers):
    t = time.time()
    if name!='kiwi':
        tokens.append(
        [pos for sent in sents for pos in tagger.inst.pos(sent)])
    else :
         tokens.append([pos for sent in sents for pos in kiwi(sent)])
    t = time.time() - t
    print('{:8}: {:.3f} secs'.format(name, t))

#print first 15 words in the first sentence --> example of out of vocabulary problem
print(tokens[0][:15])
print('\n\n')

#word frequency calculation
counter = Counter(tokens[0])
counter = {
    word:freq for word, freq in counter.items()
    if (freq >= 4) and (word[1][:2] == 'NN')
}
print(sorted(counter.items(), key=lambda x:-x[1]))
print('\n\n')

#using all POS tokenizer
for name, tokens_ in zip(names, tokens):
    print('\n\nTagger name = {}'.format(name))
    counter = Counter(tokens_)
    counter = {word:freq for word, freq in counter.items()
               if (freq >= 4) and (word[1][:1] == 'N')}
    print(sorted(counter.items(), key=lambda x:x[1], reverse=True))

 

2.  코드 분석

 1) import와 각 클래스 객체 생성

# -*- coding: utf-8 -*-
import treform as ptm
import time
from collections import Counter
mecab_path='C:\\mecab\\mecab-ko-dic'
komoran = ptm.tokenizer.Komoran()
kkma = ptm.tokenizer.KokomaKorean()
twitter = ptm.tokenizer.TwitterKorean()
kiwi=ptm.tokenizer.Kiwi(path=None)
mecab = ptm.tokenizer.MeCab(mecab_path)

  Treform의 경우에는 라이브러리(Library) 구로조 볼 수 있습니다. 이용자들이 구조를 파악하고 이를 활용하도록 하는 개발자의 의도를 파악할 수 있습니다. 이를 위하여 다양한 패키지, 라이브러리와 프레임워크를 활용하고 있습니다.

  형태소 분석기 예제 구동에 있어서 가장 우선 수행되는 부분으로써 패키지를 불러들이고, 클래스들의 객체를 선언합니다.

 2) 분석 데이터

sent="상이 평양을 떠나 영변부 ( 寧邊府 ) 로 향하였다. 윤두수·김명원·이원익은 머물러 평양을 지키고 대신 최흥원·유홍·정철 ( 鄭澈 )  등은 수행했다 "
#performance (speed) measure
sents=["부산 등지에 주둔했던 적이 군사를 합쳐 대대적으로 진주 ( 晋州 ) 를 포위하였다. 당초에 적이 유숭인 ( 柳崇仁 ) 의 군사를 패배시키고 여러 고을을 분탕질한 뒤 진주로 향하려 하였다. 이에 김성일 ( 金誠一 ) 이 호남에 구원을 청하자 의병장 최경회 ( 崔慶會 ) ·임계영 ( 任啓英 ) 이 달려왔다. 적이 진주에 육박했을 때 유숭인이 말을 달려 성 아래에 이르러 들어가려고 하였는데, 김시민 ( 金時敏 ) 이 장수의 명령 계통이 전일하지 못할까 염려하여 성문을 닫고 받아들이지 않으면서 말하기를 ‘성문을 계엄중에 열고 닫을 때 창졸간에 변이 있게 될까 염려되니 주장 ( 主將 ) 은 밖에서 응원해 주면 좋겠다.’ 하였다. 유숭인이 돌아오다 적을 만나 패하여 사천 현감 ( 泗川縣監 )  정득열 ( 鄭得悅 ) , 권관 주대청 ( 朱大淸 )  등과 함께 모두 전사하였다. 곽재우 ( 郭再祐 ) 가 김시민이 유숭인을 받아들이지 않았다는 말을 듣고 감탄하기를 ‘이 계책이 성을 온전하게 하기에 충분하니 진주 사람들의 복이다.’ 하였다."
      "..중략.."
       "김시민 ( 金時敏 ) 을 경상 우병사로 삼았는데, 얼마 뒤에 졸 ( 卒 ) 하였다."]

  다음은 형태소 분석을 수행할 텍스트입니다. 조선왕조실록의 임진왜란 시기(선조수정실록 26권, 선조 25년 10월 1일 정해 1번째기사)입니다.[각주:1]

해당 시기를 기점으로 8개의 기사(紀事)를 대상으로 이번 예제를 진행하고자 합니다.

sent는 중간의 형태소 분석이 제대로 되는가를 시험적으로 출력하기 위한 데이터이며, 전반적인 예제는 sents로 진행됩니다.

 

 3) 형태소 분석기별 구동시간 비교 및 형태소 분석 예시

  Github에 업로드된 Treform은 총 4개의 형태소 분석기를 활용할 수 있습니다. 이와 관련하여 카카오 엔터프라이즈의 이민철 연구원의 kiwi[footnoete] https://bab2min.github.io/kiwipiepy/v0.8.0/kr/ [/footnote]를 추가적으로 활용하였습니다. 이와 관련된 코드는 바로 이전 글인 Treform-2에서 다루었습니다.

  

  많은 분들이 komoran을 사용하시리라 예상됩니다. 기본적으로 komoran은 사용자 사전을 지원합니다. 이를통하여 텍스트를 분석함에 있어서 신조어(新造語)를 사용자 사전을 통하여 추가하는 작업이 가능합니다. 하지만, 이러한 사용자 사전은 기존의 komoran 개발자들이 추가한 단어보다 후 순위로 적용되기 때문에, 이미 형태소 분석으로 형태소 수준으로 해체된 단어들에 대하여 적용하여도 큰 효과를 보기 어렵습니다. 이러한 komoran의 사용자 사전이 가진 문제점을 mecab과 kiwi의 경우에는 어느정도 보완된 것으로 확인됩니다.

 

tokens = []

taggers = [komoran, kkma, twitter, mecab, kiwi]
names = 'komoran kkma twitter mecab kiwi'.split()

for name, tagger in zip(names, taggers):
    t = time.time()
    if name!='kiwi':
        tokens.append(
        [pos for sent in sents for pos in tagger.inst.pos(sent)])
    else :
         tokens.append([pos for sent in sents for pos in kiwi(sent)])
    t = time.time() - t
    print('{:8}: {:.3f} secs'.format(name, t))
    
#print first 15 words in the first sentence --> example of out of vocabulary problem
print(tokens[0][:15])
print('\n\n')

  다음은 sent 리스트에 담겨있는 하나의 기사를 대상으로 형태소 분석을 수행했을때 걸리는 시간을 도출합니다. 결과값은 아래와 같습니다. 

 

형태소 분석기별 수행 시간

총 6개의 기사(紀事) 분석에 소요된 시간으로써, mecab[각주:2]<komoran[각주:3] <kiwi<twitter(okt)[각주:4]<kkma 순으로 시간이 오래 걸림을 확인할 수 있었습니다. 각 형태소 분석기와 관련한 정보는 각주로 첨부하였습니다.

  위의 코드에서 반복문을 다소 요약하여 한줄로 표현한 부분이 있습니다. 해당 부분은 기존의 for 반복문을 간소화한 부분으로 이를 다시 풀어내면 하단과 같습니다.

for sent in sents :
	for pos in tagger.inst.pos(sent):
    	tokens.append(pos)
        
 #append와 extend 차이점 : list에 추가할 때 저장 방식의 차이
 #append -> [a,b,c,[d,e]]
 #extend -> [a,b,d,e]

  다음은 형태소 분석기에 따른 명사 단어들을 추출하여 결과값을 분석합니다. tokens[0]는 taggers리스트에 담겨있는 순서에 따라서 taggers[0]에 해당하는 komoran을 가져오게 됩니다. NN으로 시작하는 태그(NN, NN*으로 표현가능)인 형태소들을 대상으로 문헌에서 출현하는 횟수가 4이상인 단어들만을 추출하였습니다.

형태소 품사에 따른 비교표는 주석을 참고해주시길 바랍니다.[각주:5]

 4) 형태소 분석기에 따른 수행 결과 비교

#word frequency calculation
counter = Counter(tokens[0])
counter = {
    word:freq for word, freq in counter.items()
    if (freq >= 4) and (word[1][:2] == 'NN')
}
print(sorted(counter.items(), key=lambda x:-x[1]))
print('\n\n')

  마지막은 형태소 분석기에 따라서 결과값에 대한 비교입니다. 바로 윗줄과 동일한 양식으로 출력하게 됩니다.

#using all POS tokenizer
for name, tokens_ in zip(names, tokens):
    print('\n\nTagger name = {}'.format(name))
    counter = Counter(tokens_)
    counter = {word:freq for word, freq in counter.items()
               if (freq >= 4) and (word[1][:1] == 'N')}
    print(sorted(counter.items(), key=lambda x:x[1], reverse=True))

상단의 그림에 있는 '경고'문구처럼 Windows에서는 기본적으로는 Mecab을 지원하지 않습니다. 그러나 별도의 과정을 거치면 Mecab을 Windows에서도 사용할 수 있습니다.[각주:6]

 

 

마무리.

  이번 형태소 분석기 예제는 이전 글에 비하여 개인사로 인하여 시간이 오래 걸렸습니다. 형태소 분석기 예제 구동의 의의는 두 가지로 볼 수 있습니다. 

  1.  다양한 예제에서 사용될 형태소 분석기들을 구동하며, 스스로에게 맞는 형태소 분석기를 결과값을 통하여 찾는다.

 

  2. kiwi를 import함으로써, 최신의 형태소 분석기 사용을 시도해본다.

 

다음 글은 워드 클라우드 예제를 다루겠습니다.